1 /* screens.c --- dealing with RANDR, Xinerama, and VidMode Viewports.
2 * xscreensaver, Copyright (c) 1991-2008 Jamie Zawinski <jwz@jwz.org>
4 * Permission to use, copy, modify, distribute, and sell this software and its
5 * documentation for any purpose is hereby granted without fee, provided that
6 * the above copyright notice appear in all copies and that both that
7 * copyright notice and this permission notice appear in supporting
8 * documentation. No representations are made about the suitability of this
9 * software for any purpose. It is provided "as is" without express or
13 /* There are a bunch of different mechanisms for multiple monitors
14 * available in X. XScreenSaver needs to care about this for two
15 * reasons: first, to ensure that all visible areas go black; and
16 * second, so that the windows of screen savers exactly fill the
17 * glass of each monitor (instead of one saver spanning multiple
18 * monitors, or a monitor displaying only a sub-rectangle of the
23 * This is the original way. Each monitor gets its own display
24 * number. :0.0 is the first one, :0.1 is the next, etc. The
25 * value of $DISPLAY determines which screen windows open on by
26 * default. A single app can open windows on multiple screens
27 * with the same display connection, but windows cannot be moved
28 * from one screen to another. The mouse can be moved from one
29 * screen to another, though. Screens may be different depths
30 * (e.g., one can be TrueColor and one can be PseudoColor.)
31 * Screens cannot be resized or moved without restarting X.
33 * Everyone hates this way of doing things because of the
34 * inability to move a window from one screen to another without
35 * restarting the application.
39 * There is a single giant root window that spans all the
40 * monitors. All monitors are the same depth, and windows can be
41 * moved around. Applications can learn which rectangles are
42 * actually visible on monitors by querying the Xinerama server
43 * extension. (If you don't do that, you end up with dialog
44 * boxes that try to appear in the middle of the screen actually
45 * spanning the gap between two monitors.)
47 * Xinerama doesn't work with DRI, which means that if you use
48 * it, you lose hardware acceleration on OpenGL programs. Also,
49 * screens can't be resized or moved without restarting X.
51 * 3) Vidmode Viewports:
53 * With this extension, the root window can be bigger than the
54 * monitor. Moving the mouse near the edges of the screen
55 * scrolls around, like a pan-and-scan movie. There can also be
56 * a hot key for changing the monitor's resolution (zooming
59 * Trying to combine this with Xinerama crashes the server, so
60 * you can only use this if you have only a single screen, or are
61 * in old-multi-screen mode.
63 * Also, half the time it doesn't work at all: it tends to lie
64 * about the size of the rectangle in use.
68 * The first version of the "Resize and Rotate" extension let you
69 * change the resolution of a screen on the fly. The root window
70 * would actually resize. However, it was also incompatible with
71 * Xinerama (did it crash, or just do nothing? I can't remember)
72 * so you needed to be in single-screen or old multi-screen mode.
73 * I believe RANDR could co-exist with Vidmode Viewports, but I'm
78 * Finally, RANDR added the functionality of Xinerama, plus some.
79 * Each X screen (in the sense of #1, "multi-screen") can have a
80 * number of sub-rectangles that are displayed on monitors, and
81 * each of those sub-rectangles can be displayed on more than one
82 * monitor. So it's possible (I think) to have a hybrid of
83 * multi-screen and Xinerama (e.g., to have two monitors running
84 * in one depth, and three monitors running in another?)
85 * Typically though, there will be a single X screen, with
86 * Xinerama-like division of that large root window onto multiple
87 * monitors. Also everything's dynamic: monitors can be added,
88 * removed, and resized at runtime.
90 * I believe that as of RANDR 1.2, the Xinerama extension still
91 * exists but only as a compatiblity layer: it's actually
92 * returning data from the RANDR extension.
94 * Though RANDR 1.2 allows the same image to be cloned onto more
95 * than one monitor, and also allows one monitor to show a
96 * subsection of something on another monitor (e.g., the
97 * rectangles can be enclosed or overlap). Since there's no way
98 * to put seperate savers on those duplicated-or-overlapping
99 * monitors, xscreensaver just ignores them (which allows them to
100 * display duplicates or overlaps).
107 #include <X11/Xlib.h>
110 # include <X11/extensions/Xrandr.h>
111 #endif /* HAVE_RANDR */
114 # include <X11/extensions/Xinerama.h>
115 #endif /* HAVE_XINERAMA */
117 #ifdef HAVE_XF86VMODE
118 # include <X11/extensions/xf86vmode.h>
119 #endif /* HAVE_XF86VMODE */
121 /* This file doesn't need the Xt headers, so stub these types out... */
123 #define XtAppContext void*
124 #define XrmDatabase void*
125 #define XtIntervalId void*
126 #define XtPointer void*
129 #include "xscreensaver.h"
133 typedef enum { S_SANE, S_ENCLOSED, S_DUPLICATE, S_OVERLAP,
134 S_OFFSCREEN, S_DISABLED } monitor_sanity;
136 /* 'typedef monitor' is in types.h */
141 int x, y, width, height;
142 monitor_sanity sanity; /* I'm not crazy you're the one who's crazy */
143 int enemy; /* which monitor it overlaps or duplicates */
147 free_monitors (monitor **monitors)
149 monitor **m2 = monitors;
150 if (! monitors) return;
153 if ((*m2)->desc) free ((*m2)->desc);
164 xinerama_scan_monitors (Display *dpy)
166 Screen *screen = DefaultScreenOfDisplay (dpy);
167 int event, error, nscreens, i;
168 XineramaScreenInfo *xsi;
171 if (! XineramaQueryExtension (dpy, &event, &error))
174 if (! XineramaIsActive (dpy))
177 xsi = XineramaQueryScreens (dpy, &nscreens);
180 monitors = (monitor **) calloc (nscreens + 1, sizeof(*monitors));
181 if (!monitors) return 0;
183 for (i = 0; i < nscreens; i++)
185 monitor *m = (monitor *) calloc (1, sizeof (monitor));
191 m->width = xsi[i].width;
192 m->height = xsi[i].height;
197 #endif /* HAVE_XINERAMA */
200 #ifdef HAVE_XF86VMODE
203 vidmode_scan_monitors (Display *dpy)
205 int event, error, nscreens, i;
208 /* Note that XF86VidModeGetViewPort() tends to be full of lies on laptops
209 that have a docking station or external monitor that runs in a different
210 resolution than the laptop's screen:
212 http://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=81593
213 http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=208417
214 http://bugs.xfree86.org/show_bug.cgi?id=421
216 Presumably this is fixed by using RANDR instead of VidMode.
219 # ifdef HAVE_XINERAMA
220 /* Attempts to use the VidMode extension when the Xinerama extension is
221 active can result in a server crash! Yay! */
222 if (XQueryExtension (dpy, "XINERAMA", &error, &event, &error))
224 # endif /* !HAVE_XINERAMA */
226 if (! XF86VidModeQueryExtension (dpy, &event, &error))
229 nscreens = ScreenCount (dpy);
230 monitors = (monitor **) calloc (nscreens + 1, sizeof(*monitors));
231 if (!monitors) return 0;
233 for (i = 0; i < nscreens; i++)
235 monitor *m = (monitor *) calloc (1, sizeof (monitor));
236 XF86VidModeModeLine ml;
238 Screen *screen = ScreenOfDisplay (dpy, i);
244 if (! safe_XF86VidModeGetViewPort (dpy, i, &m->x, &m->y))
247 if (XF86VidModeGetModeLine (dpy, i, &dot, &ml))
249 m->width = ml.hdisplay;
250 m->height = ml.vdisplay;
253 /* Apparently, though the server stores the X position in increments of
254 1 pixel, it will only make changes to the *display* in some other
255 increment. With XF86_SVGA on a Thinkpad, the display only updates
256 in multiples of 8 pixels when in 8-bit mode, and in multiples of 4
257 pixels in 16-bit mode. I don't know what it does in 24- and 32-bit
258 mode, because I don't have enough video memory to find out.
260 I consider it a bug that XF86VidModeGetViewPort() is telling me the
261 server's *target* scroll position rather than the server's *actual*
262 scroll position. David Dawes agrees, and says they may fix this in
263 XFree86 4.0, but it's notrivial.
265 He also confirms that this behavior is server-dependent, so the
266 actual scroll position cannot be reliably determined by the client.
267 So... that means the only solution is to provide a ``sandbox''
268 around the blackout window -- we make the window be up to N pixels
269 larger than the viewport on both the left and right sides. That
270 means some part of the outer edges of each hack might not be
271 visible, but screw it.
273 I'm going to guess that 16 pixels is enough, and that the Y dimension
274 doesn't have this problem.
276 The drawback of doing this, of course, is that some of the screenhacks
277 will still look pretty stupid -- for example, "slidescreen" will cut
278 off the left and right edges of the grid, etc.
281 if (m->x > 0 && m->x < m->width - ml.hdisplay)
283 /* Not at left edge or right edge:
284 Round X position down to next lower multiple of FUDGE.
285 Increase width by 2*FUDGE in case some server rounds up.
287 m->x = ((m->x - 1) / FUDGE) * FUDGE;
288 m->width += (FUDGE * 2);
296 #endif /* HAVE_XF86VMODE */
302 randr_scan_monitors (Display *dpy)
304 int event, error, major, minor, nscreens, i, j;
306 Bool new_randr_p = False;
308 if (! XRRQueryExtension (dpy, &event, &error))
311 if (! XRRQueryVersion (dpy, &major, &minor))
314 if (major <= 0) /* Protocol was still in flux back then -- fuck it. */
317 # ifdef HAVE_RANDR_12
318 new_randr_p = (major > 1 || (major == 1 && minor >= 2));
322 /* RANDR 1.0 -- no Xinerama-like virtual screens. */
323 nscreens = ScreenCount (dpy);
324 else /* RANDR 1.2 or newer -- built-in Xinerama */
326 # ifdef HAVE_RANDR_12
327 int xsc = ScreenCount (dpy);
329 /* Add up the virtual screens on each X screen. */
330 for (i = 0; i < xsc; i++)
332 XRRScreenResources *res =
333 XRRGetScreenResources (dpy, RootWindow (dpy, i));
334 nscreens += res->noutput;
335 XRRFreeScreenResources (res);
337 # endif /* HAVE_RANDR_12 */
340 monitors = (monitor **) calloc (nscreens + 1, sizeof(*monitors));
341 if (!monitors) return 0;
343 for (i = 0, j = 0; i < ScreenCount (dpy); i++)
345 Screen *screen = ScreenOfDisplay (dpy, j);
347 if (! new_randr_p) /* RANDR 1.0 */
349 XRRScreenConfiguration *rrc;
350 monitor *m = (monitor *) calloc (1, sizeof (monitor));
355 rrc = XRRGetScreenInfo (dpy, RootWindowOfScreen (screen));
360 XRRScreenSize *rrsizes;
363 size = XRRConfigCurrentConfiguration (rrc, &rot);
364 rrsizes = XRRConfigSizes (rrc, &nsizes);
366 if (rot & (RR_Rotate_90|RR_Rotate_270))
368 m->width = rrsizes[size].height;
369 m->height = rrsizes[size].width;
373 m->width = rrsizes[size].width;
374 m->height = rrsizes[size].height;
377 /* don't free 'rrsizes' */
378 XRRFreeScreenConfigInfo (rrc);
381 else /* RANDR 1.2 or newer */
383 # ifdef HAVE_RANDR_12
385 XRRScreenResources *res =
386 XRRGetScreenResources (dpy, RootWindowOfScreen (screen));
387 for (k = 0; k < res->noutput; k++)
389 monitor *m = (monitor *) calloc (1, sizeof (monitor));
390 XRROutputInfo *rroi = XRRGetOutputInfo (dpy, res,
392 RRCrtc crtc = (rroi->crtc ? rroi->crtc : rroi->crtcs[0]);
393 XRRCrtcInfo *crtci = XRRGetCrtcInfo (dpy, res, crtc);
397 m->id = (i * 1000) + j;
398 m->desc = (rroi->name ? strdup (rroi->name) : 0);
402 if (crtci->rotation & (RR_Rotate_90|RR_Rotate_270))
404 m->width = crtci->height;
405 m->height = crtci->width;
409 m->width = crtci->width;
410 m->height = crtci->height;
415 if (rroi->connection == RR_Disconnected)
416 m->sanity = S_DISABLED;
417 /* #### do the same for RR_UnknownConnection? */
419 XRRFreeCrtcInfo (crtci);
420 XRRFreeOutputInfo (rroi);
422 XRRFreeScreenResources (res);
423 # endif /* HAVE_RANDR_12 */
430 #endif /* HAVE_RANDR */
434 basic_scan_monitors (Display *dpy)
436 int nscreens = ScreenCount (dpy);
438 monitor **monitors = (monitor **) calloc (nscreens + 1, sizeof(*monitors));
439 if (!monitors) return 0;
441 for (i = 0; i < nscreens; i++)
443 Screen *screen = ScreenOfDisplay (dpy, i);
444 monitor *m = (monitor *) calloc (1, sizeof (monitor));
450 m->width = WidthOfScreen (screen);
451 m->height = HeightOfScreen (screen);
457 #ifdef DEBUG_MULTISCREEN
459 /* If DEBUG_MULTISCREEN is defined, then in "-debug" mode, xscreensaver
460 will pretend that it is changing the number of connected monitors
461 every few seconds, using the geometries in the following list,
462 for stress-testing purposes.
465 debug_scan_monitors (Display *dpy)
467 static const char * const geoms[] = {
471 "800x600+0+22,800x600+800+22",
472 "800x600+0+22,800x600+800+22,800x600+300+622",
473 "800x600+0+22,800x600+800+22,800x600+0+622,800x600+800+622",
474 "640x480+0+22,640x480+640+22,640x480+0+502,640x480+640+502",
475 "640x480+240+22,640x480+0+502,640x480+640+502",
476 "640x480+0+200,640x480+640+200",
478 "320x200+0+22,320x200+320+22,320x200+640+22,320x200+960+22,320x200+0+222,320x200+320+222,320x200+640+222,320x200+960+222,320x200+0+422,320x200+320+422,320x200+640+422,320x200+960+422,320x200+0+622,320x200+320+622,320x200+640+622,320x200+960+622,320x200+0+822,320x200+320+822,320x200+640+822,320x200+960+822"
480 static int index = 0;
481 monitor **monitors = (monitor **) calloc (100, sizeof(*monitors));
483 Screen *screen = DefaultScreenOfDisplay (dpy);
485 char *s = strdup (geoms[index]);
486 char *token = strtok (s, ",");
489 monitor *m = calloc (1, sizeof (monitor));
493 if (4 != sscanf (token, "%dx%d+%d+%d%c",
494 &m->width, &m->height, &m->x, &m->y, &c))
498 monitors[nscreens++] = m;
499 token = strtok (0, ",");
503 index = (index+1) % countof(geoms);
507 #endif /* DEBUG_MULTISCREEN */
512 quadruple (monitor **monitors, Bool debug_p)
516 while (monitors[count])
518 monitors2 = (monitor **) calloc (count * 4 + 1, sizeof(*monitors));
519 if (!monitors2) abort();
521 for (i = 0, j = 0; i < count; i++)
524 for (k = 0; k < 4; k++)
526 monitors2[j+k] = (monitor *) calloc (1, sizeof (monitor));
527 *monitors2[j+k] = *monitors[i];
528 monitors2[j+k]->width /= (debug_p ? 4 : 2);
529 monitors2[j+k]->height /= 2;
530 monitors2[j+k]->id = (monitors[i]->id * 4) + k;
531 monitors2[j+k]->name = (monitors[i]->name
532 ? strdup (monitors[i]->name) : 0);
534 monitors2[j+1]->x += monitors2[j]->width;
535 monitors2[j+2]->y += monitors2[j]->height;
536 monitors2[j+3]->x += monitors2[j]->width;
537 monitors2[j+3]->y += monitors2[j]->height;
541 free_monitors (monitors);
544 #endif /* QUAD_MODE */
548 scan_monitors (saver_info *si)
550 saver_preferences *p = &si->prefs;
551 monitor **monitors = 0;
553 # ifdef DEBUG_MULTISCREEN
554 if (! monitors) monitors = debug_scan_monitors (si->dpy);
558 if (! p->getviewport_full_of_lies_p)
559 if (! monitors) monitors = randr_scan_monitors (si->dpy);
562 # ifdef HAVE_XF86VMODE
563 if (! monitors) monitors = vidmode_scan_monitors (si->dpy);
566 # ifdef HAVE_XF86VMODE
567 if (! monitors) monitors = xinerama_scan_monitors (si->dpy);
570 if (! monitors) monitors = basic_scan_monitors (si->dpy);
574 monitors = quadruple (monitors, p->debug_p);
582 monitors_overlap_p (monitor *a, monitor *b)
584 /* Two rectangles overlap if the max of the tops is less than the
585 min of the bottoms and the max of the lefts is less than the min
590 # define MAX(A,B) ((A)>(B)?(A):(B))
591 # define MIN(A,B) ((A)<(B)?(A):(B))
593 int maxleft = MAX(a->x, b->x);
594 int maxtop = MAX(a->y, b->y);
595 int minright = MIN(a->x + a->width - 1, b->x + b->width);
596 int minbot = MIN(a->y + a->height - 1, b->y + b->height);
597 return (maxtop < minbot && maxleft < minright);
601 /* Mark the ones that overlap, etc.
604 check_monitor_sanity (monitor **monitors)
608 while (monitors[count])
611 # define X1 monitors[i]->x
612 # define X2 monitors[j]->x
613 # define Y1 monitors[i]->y
614 # define Y2 monitors[j]->y
615 # define W1 monitors[i]->width
616 # define W2 monitors[j]->width
617 # define H1 monitors[i]->height
618 # define H2 monitors[j]->height
620 /* If a monitor is enclosed by any other monitor, that's insane.
622 for (i = 0; i < count; i++)
623 for (j = 0; j < count; j++)
625 monitors[i]->sanity == S_SANE &&
626 monitors[j]->sanity == S_SANE &&
629 (X2+W2) <= (X1+W1) &&
636 monitors[j]->sanity = S_DUPLICATE;
638 monitors[j]->sanity = S_ENCLOSED;
639 monitors[j]->enemy = i;
642 /* After checking for enclosure, check for other lossage against earlier
643 monitors. We do enclosure first so that we make sure to pick the
646 for (i = 0; i < count; i++)
647 for (j = 0; j < i; j++)
649 if (monitors[i]->sanity != S_SANE) continue; /* already marked */
650 if (monitors[j]->sanity != S_SANE) continue;
652 if (monitors_overlap_p (monitors[i], monitors[j]))
654 monitors[i]->sanity = S_OVERLAP;
655 monitors[i]->enemy = j;
659 /* Finally, make sure all monitors are enclosed by their X screen.
660 Xinerama sometimes reports 1024x768 VPs at -1936862040, -1953705044.
662 for (i = 0; i < count; i++)
664 int sw = WidthOfScreen (monitors[i]->screen) * 2;
665 int sh = HeightOfScreen (monitors[i]->screen) * 2;
666 if (monitors[i]->sanity != S_SANE) continue; /* already marked */
667 if (X1 < 0 || Y1 < 0 ||
668 W1 <= 0 || H1 <= 0 ||
669 X1+W1 > sw || Y1+H1 > sh)
671 monitors[i]->sanity = S_OFFSCREEN;
672 monitors[i]->enemy = 0;
688 layouts_differ_p (monitor **a, monitor **b)
690 if (!a || !b) return True;
695 if ((*a)->screen != (*b)->screen ||
696 (*a)->x != (*b)->x ||
697 (*a)->y != (*b)->y ||
698 (*a)->width != (*b)->width ||
699 (*a)->height != (*b)->height)
712 describe_monitor_layout (saver_info *si)
714 monitor **monitors = si->monitor_layout;
718 while (monitors[count])
720 if (monitors[count]->sanity == S_SANE)
728 fprintf (stderr, "%s: no screens!\n", blurb());
732 fprintf (stderr, "%s: screens in use: %d\n", blurb(), good_count);
733 for (i = 0; i < count; i++)
735 monitor *m = monitors[i];
736 if (m->sanity != S_SANE) continue;
737 fprintf (stderr, "%s: %3d/%d: %dx%d+%d+%d",
738 blurb(), m->id, screen_number (m->screen),
739 m->width, m->height, m->x, m->y);
740 if (m->desc && *m->desc) fprintf (stderr, " (%s)", m->desc);
741 fprintf (stderr, "\n");
745 fprintf (stderr, "%s: rejected screens: %d\n", blurb(), bad_count);
746 for (i = 0; i < count; i++)
748 monitor *m = monitors[i];
749 monitor *e = monitors[m->enemy];
750 if (m->sanity == S_SANE) continue;
751 fprintf (stderr, "%s: %3d/%d: %dx%d+%d+%d",
752 blurb(), m->id, screen_number (m->screen),
753 m->width, m->height, m->x, m->y);
754 if (m->desc && *m->desc) fprintf (stderr, " (%s)", m->desc);
755 fprintf (stderr, " -- ");
758 case S_SANE: abort(); break;
760 fprintf (stderr, "enclosed by %d (%dx%d+%d+%d)\n",
761 e->id, e->width, e->height, e->x, e->y);
764 fprintf (stderr, "duplicate of %d\n", e->id);
767 fprintf (stderr, "overlaps %d (%dx%d+%d+%d)\n",
768 e->id, e->width, e->height, e->x, e->y);
771 fprintf (stderr, "off screen (%dx%d)\n",
772 WidthOfScreen (e->screen),
773 HeightOfScreen (e->screen));
776 fprintf (stderr, "output disabled\n");
785 /* Synchronize the contents of si->ssi to the current state of the monitors.
786 Doesn't change anything if nothing has changed; otherwise, alters and
787 reuses existing saver_screen_info structs as much as possible.
788 Returns True if anything changed.
791 update_screen_layout (saver_info *si)
793 monitor **monitors = scan_monitors (si);
797 int seen_screens[100] = { 0, };
799 if (! layouts_differ_p (monitors, si->monitor_layout))
801 free_monitors (monitors);
805 free_monitors (si->monitor_layout);
806 si->monitor_layout = monitors;
807 check_monitor_sanity (si->monitor_layout);
809 while (monitors[count])
811 if (monitors[count]->sanity == S_SANE)
816 if (si->ssi_count == 0)
819 si->screens = (saver_screen_info *)
820 calloc (sizeof(*si->screens), si->ssi_count);
823 if (si->ssi_count <= good_count)
825 si->ssi_count = good_count + 10;
826 si->screens = (saver_screen_info *)
827 realloc (si->screens, sizeof(*si->screens) * si->ssi_count);
828 memset (si->screens + si->nscreens, 0,
829 sizeof(*si->screens) * (si->ssi_count - si->nscreens));
832 if (! si->screens) abort();
834 si->nscreens = good_count;
836 /* Regenerate the list of GL visuals as needed. */
837 if (si->best_gl_visuals)
838 free (si->best_gl_visuals);
839 si->best_gl_visuals = 0;
841 for (i = 0, j = 0; i < count; i++)
843 monitor *m = monitors[i];
844 saver_screen_info *ssi = &si->screens[j];
845 Screen *old_screen = ssi->screen;
847 if (monitors[i]->sanity != S_SANE) continue;
852 sn = screen_number (m->screen);
853 ssi->screen = m->screen;
854 ssi->real_screen_number = sn;
855 ssi->real_screen_p = (seen_screens[sn] == 0);
858 ssi->default_visual =
859 get_visual_resource (ssi->screen, "visualID", "VisualID", False);
860 ssi->current_visual = ssi->default_visual;
861 ssi->current_depth = visual_depth (ssi->screen, ssi->current_visual);
863 /* If the screen changed (or if this is the first time) we need
864 a new toplevel shell for this screen's depth.
866 if (ssi->screen != old_screen)
867 initialize_screen_root_widget (ssi);
869 ssi->poll_mouse_last_root_x = -1;
870 ssi->poll_mouse_last_root_y = -1;
874 ssi->width = m->width;
875 ssi->height = m->height;
877 # ifndef DEBUG_MULTISCREEN
879 saver_preferences *p = &si->prefs;
892 si->default_screen = &si->screens[0];