diff --git a/src/draw.c b/src/draw.c index e1032c0..7dbe55f 100644 --- a/src/draw.c +++ b/src/draw.c @@ -237,7 +237,7 @@ static PangoLayout *layout_create(cairo_t *c) struct screen_info *screen = get_active_screen(); PangoContext *context = pango_cairo_create_context(c); - pango_cairo_context_set_resolution(context, get_dpi_for_screen(screen)); + pango_cairo_context_set_resolution(context, screen_dpi_get(screen)); PangoLayout *layout = pango_layout_new(context); diff --git a/src/x11/screen.c b/src/x11/screen.c index a9ac74a..7c3347f 100644 --- a/src/x11/screen.c +++ b/src/x11/screen.c @@ -25,8 +25,6 @@ int screens_len; bool dunst_follow_errored = false; -int randr_event_base = 0; - static int randr_major_version = 0; static int randr_minor_version = 0; @@ -39,59 +37,84 @@ static int x_follow_tear_down_error_handler(void); static int FollowXErrorHandler(Display *display, XErrorEvent *e); static Window get_focused_window(void); -static double get_xft_dpi_value(void) + +/** + * A cache variable to cache the Xft.dpi xrdb values. + * We do not expect to change xrdb often, but there's much + * overhead to query it once. + * + * @retval -DBL_MAX: uncached + * @retval <=0: Invalid and unusable value + * @retval >0: valid + */ +double screen_dpi_xft_cache = -DBL_MAX; + +void screen_dpi_xft_cache_purge(void) { - static double dpi = -1; - //Only run this once, we don't expect dpi changes during runtime - if (dpi <= -1) { - XrmInitialize(); - char *xRMS = XResourceManagerString(xctx.dpy); + screen_dpi_xft_cache = -DBL_MAX; +} - if (!xRMS) { - dpi = 0; - return 0; - } +static double screen_dpi_get_from_xft(void) +{ + if (screen_dpi_xft_cache == -DBL_MAX) { + screen_dpi_xft_cache = 0; - XrmDatabase xDB = XrmGetStringDatabase(xRMS); char *xrmType; XrmValue xrmValue; - - if (XrmGetResource(xDB, "Xft.dpi", "Xft.dpi", &xrmType, &xrmValue)) { - dpi = strtod(xrmValue.addr, NULL); - } else { - dpi = 0; - } - XrmDestroyDatabase(xDB); + XrmDatabase db = XrmGetDatabase(xctx.dpy); + ASSERT_OR_RET(db, screen_dpi_xft_cache); + if (XrmGetResource(db, "Xft.dpi", "Xft.dpi", &xrmType, &xrmValue)) + screen_dpi_xft_cache = strtod(xrmValue.addr, NULL); } - return dpi; + return screen_dpi_xft_cache; +} + +static double screen_dpi_get_from_monitor(struct screen_info *scr) +{ + return (double)scr->h * 25.4 / (double)scr->mmh; +} + +double screen_dpi_get(struct screen_info *scr) +{ + if ( ! settings.force_xinerama + && settings.per_monitor_dpi) + return screen_dpi_get_from_monitor(scr); + + if (screen_dpi_get_from_xft() > 0) + return screen_dpi_get_from_xft(); + + // Calculate the DPI on the overall screen size. + // xrandr --dpi does only change the overall screen's millimeters, + // but not the physical screen's sizes. + // + // The screen parameter is XDefaultScreen(), as our scr->id references + // the xrandr monitor and not the xrandr screen + return ((((double)DisplayWidth (xctx.dpy, XDefaultScreen(xctx.dpy))) * 25.4) / + ((double)DisplayWidthMM(xctx.dpy, XDefaultScreen(xctx.dpy)))); } void init_screens(void) { - if (!settings.force_xinerama) { + if (settings.force_xinerama) { + xinerama_update(); + } else { randr_init(); randr_update(); - } else { - xinerama_update(); } } void alloc_screen_ar(int n) { assert(n > 0); - if (n <= screens_len) return; - - screens = g_realloc(screens, n * sizeof(struct screen_info)); - - memset(screens, 0, n * sizeof(struct screen_info)); - + g_free(screens); + screens = g_malloc0(n * sizeof(struct screen_info)); screens_len = n; } void randr_init(void) { - int randr_error_base = 0; - if (!XRRQueryExtension(xctx.dpy, &randr_event_base, &randr_error_base)) { + int ignored; + if (!XRRQueryExtension(xctx.dpy, &ignored, &ignored)) { LOG_W("Could not initialize the RandR extension. " "Falling back to single monitor mode."); return; @@ -138,20 +161,15 @@ void randr_update(void) XRRFreeMonitors(m); } -static int autodetect_dpi(struct screen_info *scr) +bool screen_check_event(XEvent *ev) { - return (double)scr->h * 25.4 / (double)scr->mmh; -} - -void screen_check_event(XEvent event) -{ - if (event.type == randr_event_base + RRScreenChangeNotify) { + if (XRRUpdateConfiguration(ev)) { LOG_D("XEvent: processing 'RRScreenChangeNotify'"); randr_update(); - } else { - LOG_D("XEvent: Ignoring '%d'", event.type); + return true; } + return false; } void xinerama_update(void) @@ -355,18 +373,6 @@ sc_cleanup: return &screens[ret]; } -double get_dpi_for_screen(struct screen_info *scr) -{ - double dpi = 0; - if ((!settings.force_xinerama && settings.per_monitor_dpi && - (dpi = autodetect_dpi(scr)))) - return dpi; - else if ((dpi = get_xft_dpi_value())) - return dpi; - else - return 96; -} - /* * Return the window that currently has * the keyboard focus. diff --git a/src/x11/screen.h b/src/x11/screen.h index 864c230..fb83bb7 100644 --- a/src/x11/screen.h +++ b/src/x11/screen.h @@ -17,10 +17,11 @@ struct screen_info { }; void init_screens(void); -void screen_check_event(XEvent event); +void screen_dpi_xft_cache_purge(void); +bool screen_check_event(XEvent *ev); struct screen_info *get_active_screen(void); -double get_dpi_for_screen(struct screen_info *scr); +double screen_dpi_get(struct screen_info *scr); /** * Find the currently focused window and check if it's in diff --git a/src/x11/x.c b/src/x11/x.c index 07d658f..55267e8 100644 --- a/src/x11/x.c +++ b/src/x11/x.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include "../dbus.h" @@ -55,6 +56,8 @@ bool dunst_grab_errored = false; static bool fullscreen_last = false; +static void XRM_update_db(void); + static void x_shortcut_init(struct keyboard_shortcut *ks); static int x_shortcut_grab(struct keyboard_shortcut *ks); static void x_shortcut_ungrab(struct keyboard_shortcut *ks); @@ -333,9 +336,18 @@ gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer ev.xcreatewindow.override_redirect == 0) XRaiseWindow(xctx.dpy, win->xwin); break; + case PropertyNotify: + if (ev.xproperty.atom == XA_RESOURCE_MANAGER) { + LOG_D("XEvent: processing PropertyNotify for Resource manager"); + XRM_update_db(); + screen_dpi_xft_cache_purge(); + draw(); + break; + } + /* Explicitly fallthrough. Other PropertyNotify events, e.g. catching + * _NET_WM get handled in the Focus(In|Out) section */ case FocusIn: case FocusOut: - case PropertyNotify: LOG_D("XEvent: Checking for active screen changes"); fullscreen_now = have_fullscreen_window(); scr = get_active_screen(); @@ -355,7 +367,10 @@ gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer } break; default: - screen_check_event(ev); + if (!screen_check_event(&ev)) { + LOG_D("XEvent: Ignoring '%d'", ev.type); + } + break; } } @@ -437,6 +452,47 @@ void x_free(void) XCloseDisplay(xctx.dpy); } +static int XErrorHandlerDB(Display *display, XErrorEvent *e) +{ + char err_buf[BUFSIZ]; + XGetErrorText(display, e->error_code, err_buf, BUFSIZ); + LOG_W("%s", err_buf); + return 0; +} + +static void XRM_update_db(void) +{ + XrmDatabase db; + XTextProperty prop; + Window root; + // We shouldn't destroy the first DB coming + // from the display object itself + static bool runonce = false; + + XFlush(xctx.dpy); + XSetErrorHandler(XErrorHandlerDB); + + root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)); + + XLockDisplay(xctx.dpy); + if (XGetTextProperty(xctx.dpy, root, &prop, XA_RESOURCE_MANAGER)) { + if (runonce) { + db = XrmGetDatabase(xctx.dpy); + XrmDestroyDatabase(db); + } + + db = XrmGetStringDatabase((const char*)prop.value); + XrmSetDatabase(xctx.dpy, db); + } + XUnlockDisplay(xctx.dpy); + + runonce = true; + + XFlush(xctx.dpy); + XSync(xctx.dpy, false); + XSetErrorHandler(NULL); +} + /* * Setup X11 stuff */ @@ -468,6 +524,8 @@ void x_setup(void) init_screens(); x_shortcut_grab(&settings.history_ks); + + XrmInitialize(); } struct geometry x_parse_geometry(const char *geom_str) @@ -616,9 +674,17 @@ struct window_x11 *x_win_create(void) win->esrc = x_win_reg_source(win); - long root_event_mask = SubstructureNotifyMask; + /* SubstructureNotifyMask is required for receiving CreateNotify events + * in order to raise the window when something covers us. See #160 + * + * PropertyChangeMask is requred for getting screen change events when follow_mode != none + * and it's also needed to receive + * XA_RESOURCE_MANAGER events to update the dpi when + * the xresource value is updated + */ + long root_event_mask = SubstructureNotifyMask | PropertyChangeMask; if (settings.f_mode != FOLLOW_NONE) { - root_event_mask |= FocusChangeMask | PropertyChangeMask; + root_event_mask |= FocusChangeMask; } XSelectInput(xctx.dpy, root, root_event_mask);