 c547f6eec9
			
		
	
	
		c547f6eec9
		
	
	
	
	
		
			
			Since after the release a lot of downstream users will not be building from source, it makes a lot more sense to have an option to fall back to the Xinerama extension for those that are still on systems that do not support RandR.
		
			
				
	
	
		
			303 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			303 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| #include "screen.h"
 | |
| 
 | |
| #include <X11/X.h>
 | |
| #include <X11/Xatom.h>
 | |
| #include <X11/Xlib.h>
 | |
| #include <X11/Xresource.h>
 | |
| #include <X11/extensions/randr.h>
 | |
| #include <X11/extensions/Xrandr.h>
 | |
| #include <X11/extensions/Xinerama.h>
 | |
| #include <assert.h>
 | |
| #include <glib.h>
 | |
| #include <locale.h>
 | |
| #include <stdbool.h>
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| 
 | |
| #include "src/settings.h"
 | |
| #include "x.h"
 | |
| 
 | |
| screen_info *screens;
 | |
| int screens_len;
 | |
| 
 | |
| bool dunst_follow_errored = false;
 | |
| 
 | |
| 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;
 | |
| }
 | |
| 
 | |
| int randr_event_base = 0;
 | |
| 
 | |
| 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;
 | |
|         }
 | |
|         XRRSelectInput(xctx.dpy, RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)), RRScreenChangeNotifyMask);
 | |
| }
 | |
| 
 | |
| void randr_update()
 | |
| {
 | |
|         int n;
 | |
|         XRRMonitorInfo *m = XRRGetMonitors(xctx.dpy, RootWindow(xctx.dpy, DefaultScreen(xctx.dpy)), true, &n);
 | |
| 
 | |
|         if (m == NULL || n == -1) {
 | |
|                 fprintf(stderr, "(RandR) 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 = 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 ((dpi = get_xft_dpi_value()))
 | |
|                 return dpi;
 | |
|         else if (settings.per_monitor_dpi && (dpi = autodetect_dpi(scr)))
 | |
|                 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: */
 |