#include "screen.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "src/settings.h" #include "x.h" screen_info *screens; 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; void randr_init(); void randr_update(); void xinerama_update(); void screen_update_fallback(); static void x_follow_setup_error_handler(void); 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() { 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); if (xRMS == NULL) { dpi = 0; return 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); } return dpi; } void init_screens() { if (!settings.force_xinerama) { 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(screen_info)); memset(screens, 0, n * sizeof(screen_info)); screens_len = n; } void randr_init() { int randr_error_base = 0; if (!XRRQueryExtension(xctx.dpy, &randr_event_base, &randr_error_base)) { fprintf(stderr, "Could not initialize the RandR extension, falling back to single monitor mode.\n"); return; } XRRQueryVersion(xctx.dpy, &randr_major_version, &randr_minor_version); XRRSelectInput(xctx.dpy, RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)), RRScreenChangeNotifyMask); } void randr_update() { if (randr_major_version < 1 || (randr_major_version == 1 && randr_minor_version < 5)) { fprintf(stderr, "Server RandR version too low (%i.%i). Falling back to single monitor mode\n", randr_major_version, randr_minor_version); screen_update_fallback(); return; } int n = 0; XRRMonitorInfo *m = XRRGetMonitors(xctx.dpy, RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)), true, &n); if (n < 1) { fprintf(stderr, "Get monitors reported %i monitors, falling back to single monitor mode\n", n); screen_update_fallback(); return; } assert(m); alloc_screen_ar(n); for (int i = 0; i < n; i++) { screens[i].dim.x = m[i].x; screens[i].dim.y = m[i].y; screens[i].dim.w = m[i].width; screens[i].dim.h = m[i].height; screens[i].dim.mmh = m[i].mheight; } XRRFreeMonitors(m); } static int autodetect_dpi(screen_info *scr) { return (double)scr->dim.h * 25.4 / (double)scr->dim.mmh; } void screen_check_event(XEvent event) { if (event.type == randr_event_base + RRScreenChangeNotify) randr_update(); } void xinerama_update() { int n; XineramaScreenInfo *info = XineramaQueryScreens(xctx.dpy, &n); if (!info) { fprintf(stderr, "(Xinerama) Could not get screen info, falling back to single monitor mode\n"); screen_update_fallback(); return; } alloc_screen_ar(n); for (int i = 0; i < n; i++) { screens[i].dim.x = info[i].x_org; screens[i].dim.y = info[i].y_org; screens[i].dim.h = info[i].height; screens[i].dim.w = info[i].width; } XFree(info); } void screen_update_fallback() { alloc_screen_ar(1); int screen; if (settings.monitor >= 0) screen = settings.monitor; else screen = DefaultScreen(xctx.dpy); screens[0].dim.w = DisplayWidth(xctx.dpy, screen); screens[0].dim.h = DisplayHeight(xctx.dpy, screen); } /* * Select the screen on which the Window * should be displayed. */ screen_info *get_active_screen() { int ret = 0; if (settings.monitor > 0 && settings.monitor < screens_len) { ret = settings.monitor; goto sc_cleanup; } x_follow_setup_error_handler(); if (settings.f_mode == FOLLOW_NONE) { ret = XDefaultScreen(xctx.dpy); goto sc_cleanup; } else { int x, y; assert(settings.f_mode == FOLLOW_MOUSE || settings.f_mode == FOLLOW_KEYBOARD); Window root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)); if (settings.f_mode == FOLLOW_MOUSE) { int dummy; unsigned int dummy_ui; Window dummy_win; XQueryPointer(xctx.dpy, root, &dummy_win, &dummy_win, &x, &y, &dummy, &dummy, &dummy_ui); } if (settings.f_mode == FOLLOW_KEYBOARD) { Window focused = get_focused_window(); if (focused == 0) { /* something went wrong. Fallback to default */ ret = XDefaultScreen(xctx.dpy); goto sc_cleanup; } Window child_return; XTranslateCoordinates(xctx.dpy, focused, root, 0, 0, &x, &y, &child_return); } for (int i = 0; i < screens_len; i++) { if (INRECT(x, y, screens[i].dim.x, screens[i].dim.y, screens[i].dim.w, screens[i].dim.h)) { ret = i; } } if (ret > 0) goto sc_cleanup; /* something seems to be wrong. Fallback to default */ ret = XDefaultScreen(xctx.dpy); goto sc_cleanup; } sc_cleanup: x_follow_tear_down_error_handler(); assert(screens); assert(ret >= 0 && ret < screens_len); return &screens[ret]; } double get_dpi_for_screen(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. */ static Window get_focused_window(void) { Window focused = 0; Atom type; int format; unsigned long nitems, bytes_after; unsigned char *prop_return = NULL; Window root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)); Atom netactivewindow = XInternAtom(xctx.dpy, "_NET_ACTIVE_WINDOW", false); XGetWindowProperty(xctx.dpy, root, netactivewindow, 0L, sizeof(Window), false, XA_WINDOW, &type, &format, &nitems, &bytes_after, &prop_return); if (prop_return) { focused = *(Window *)prop_return; XFree(prop_return); } return focused; } static void x_follow_setup_error_handler(void) { dunst_follow_errored = false; XFlush(xctx.dpy); XSetErrorHandler(FollowXErrorHandler); } static int x_follow_tear_down_error_handler(void) { XFlush(xctx.dpy); XSync(xctx.dpy, false); XSetErrorHandler(NULL); return dunst_follow_errored; } static int FollowXErrorHandler(Display *display, XErrorEvent *e) { dunst_follow_errored = true; char err_buf[BUFSIZ]; XGetErrorText(display, e->error_code, err_buf, BUFSIZ); fputs(err_buf, stderr); fputs("\n", stderr); return 0; } /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */