]> git.hungrycats.org Git - xscreensaver/blob - driver/windows.c
From https://www.jwz.org/xscreensaver/xscreensaver-6.09.tar.gz
[xscreensaver] / driver / windows.c
1 /* windows.c --- turning the screen black; dealing with visuals, virtual roots.
2  * xscreensaver, Copyright © 1991-2022 Jamie Zawinski <jwz@jwz.org>
3  *
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 
10  * implied warranty.
11  */
12
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
16
17 #ifdef HAVE_UNAME
18 # include <sys/utsname.h>       /* for uname() */
19 #endif /* HAVE_UNAME */
20
21 #include <stdio.h>
22 #include <pwd.h>                /* for getpwuid() */
23 #include <X11/Xlib.h>
24 #include <X11/Xutil.h>          /* for XSetClassHint() */
25 #include <X11/Xatom.h>
26 #include <X11/Intrinsic.h>
27 #include <time.h>
28
29 #ifdef HAVE_XF86VMODE
30 # include <X11/extensions/xf86vmode.h>
31 #endif /* HAVE_XF86VMODE */
32
33 #ifdef HAVE_XINERAMA
34 # include <X11/extensions/Xinerama.h>
35 #endif /* HAVE_XINERAMA */
36
37 #include "xscreensaver.h"
38 #include "atoms.h"
39 #include "visual.h"
40 #include "screens.h"
41 #include "screenshot.h"
42 #include "fade.h"
43 #include "resources.h"
44 #include "xft.h"
45 #include "font-retry.h"
46
47
48 extern int kill (pid_t, int);           /* signal() is in sys/signal.h... */
49
50 #ifdef _VROOT_H_
51 ERROR!  You must not include vroot.h in this file.
52 #endif
53
54
55 static void reset_watchdog_timer (saver_info *si);
56
57 void
58 store_saver_status (saver_info *si)
59 {
60   /* The contents of XA_SCREENSAVER_STATUS has LOCK/BLANK/0 in the first slot,
61      the time at which that state began in the second slot, and the ordinal of
62      the running hacks on each screen (1-based) in subsequent slots.  Since
63      we don't know the blank-versus-lock status here, we leave whatever was
64      there before unchanged: it will be updated by "xscreensaver".
65
66      XA_SCREENSAVER_STATUS is stored on the (real) root window of screen 0.
67
68      XA_SCREENSAVER_VERSION and XA_SCREENSAVER_ID are stored on the unmapped
69      window created by the "xscreensaver" process.  ClientMessage events are
70      sent to that window, and the responses are sent via the
71      XA_SCREENSAVER_RESPONSE property on it.
72
73      These properties are not used on the windows created by "xscreensaver-gfx"
74      for use by the display hacks.
75
76      See the different version of this function in xscreensaver.c.
77    */
78   Display *dpy = si->dpy;
79   Window w = RootWindow (dpy, 0);  /* always screen 0 */
80   Atom type;
81   unsigned char *dataP = 0;
82   PROP32 *status = 0;
83   int format;
84   unsigned long nitems, bytesafter;
85   int nitems2 = si->nscreens + 2;
86   int i;
87
88   /* Read the old property, so we can change just our parts. */
89   XGetWindowProperty (dpy, w,
90                       XA_SCREENSAVER_STATUS,
91                       0, 999, False, XA_INTEGER,
92                       &type, &format, &nitems, &bytesafter,
93                       &dataP);
94
95   status = (PROP32 *) calloc (nitems2, sizeof(PROP32));
96
97   if (dataP && type == XA_INTEGER && nitems >= 3)
98     {
99       status[0] = ((PROP32 *) dataP)[0];
100       status[1] = ((PROP32 *) dataP)[1];
101     }
102
103   for (i = 0; i < si->nscreens; i++)
104     {
105       saver_screen_info *ssi = &si->screens[i];
106       status[2 + i] = ssi->current_hack + 1;  /* 1-based */
107     }
108
109   XChangeProperty (si->dpy, w, XA_SCREENSAVER_STATUS, XA_INTEGER, 32,
110                    PropModeReplace, (unsigned char *) status, nitems2);
111   XSync (dpy, False);
112
113   if (si->prefs.debug_p && si->prefs.verbose_p)
114     {
115       int i;
116       fprintf (stderr, "%s: wrote status property: 0x%lx: %s", blurb(),
117                (unsigned long) w,
118                (status[0] == XA_LOCK  ? "LOCK" :
119                 status[0] == XA_BLANK ? "BLANK" :
120                 status[0] == 0 ? "0" : "???"));
121       for (i = 1; i < nitems; i++)
122         fprintf (stderr, ", %lu", status[i]);
123       fprintf (stderr, "\n");
124     }
125
126   free (status);
127   if (dataP)
128     XFree (dataP);
129 }
130
131
132 static void
133 initialize_screensaver_window_1 (saver_screen_info *ssi)
134 {
135   saver_info *si = ssi->global;
136   saver_preferences *p = &si->prefs;
137   Bool install_cmap_p = ssi->install_cmap_p;   /* not p->install_cmap_p */
138
139   /* This resets the screensaver window as fully as possible, since there's
140      no way of knowing what some random client may have done to us in the
141      meantime.  We could just destroy and recreate the window, but that has
142      its own set of problems.  (Update: that's exactly what we're doing
143      these days.)
144    */
145   XColor black;
146   XSetWindowAttributes attrs;
147   unsigned long attrmask;
148   static Bool printed_visual_info = False;  /* only print the message once. */
149   Window horked_window = 0;
150
151   black.red = black.green = black.blue = 0;
152
153   if (ssi->cmap == DefaultColormapOfScreen (ssi->screen))
154     ssi->cmap = 0;
155
156   if (ssi->current_visual != DefaultVisualOfScreen (ssi->screen))
157     /* It's not the default visual, so we have no choice but to install. */
158     install_cmap_p = True;
159
160   if (install_cmap_p)
161     {
162       if (! ssi->cmap)
163         {
164           ssi->cmap = XCreateColormap (si->dpy,
165                                        RootWindowOfScreen (ssi->screen),
166                                       ssi->current_visual, AllocNone);
167           if (! XAllocColor (si->dpy, ssi->cmap, &black)) abort ();
168           ssi->black_pixel = black.pixel;
169         }
170     }
171   else
172     {
173       Colormap def_cmap = DefaultColormapOfScreen (ssi->screen);
174       if (ssi->cmap)
175         {
176           XFreeColors (si->dpy, ssi->cmap, &ssi->black_pixel, 1, 0);
177           if (ssi->cmap != def_cmap)
178             XFreeColormap (si->dpy, ssi->cmap);
179         }
180       ssi->cmap = def_cmap;
181       ssi->black_pixel = BlackPixelOfScreen (ssi->screen);
182     }
183
184   attrmask = (CWOverrideRedirect | CWEventMask | CWBackingStore | CWColormap |
185               CWBackPixel | CWBackingPixel | CWBorderPixel);
186   attrs.override_redirect = True;
187
188   attrs.event_mask = (KeyPressMask | KeyReleaseMask |
189                       ButtonPressMask | ButtonReleaseMask |
190                       PointerMotionMask);
191
192   attrs.backing_store = Always;
193   attrs.colormap = ssi->cmap;
194   attrs.background_pixel = ssi->black_pixel;
195   attrs.backing_pixel = ssi->black_pixel;
196   attrs.border_pixel = ssi->black_pixel;
197
198   printed_visual_info = True;  /* Too noisy */
199
200   if (!p->verbose_p || printed_visual_info)
201     ;
202   else if (ssi->current_visual == DefaultVisualOfScreen (ssi->screen))
203     {
204       fprintf (stderr, "%s: %d: visual ", blurb(), ssi->number);
205       describe_visual (stderr, ssi->screen, ssi->current_visual,
206                        install_cmap_p);
207     }
208   else
209     {
210       fprintf (stderr, "%s: using visual:   ", blurb());
211       describe_visual (stderr, ssi->screen, ssi->current_visual,
212                        install_cmap_p);
213       fprintf (stderr, "%s: default visual: ", blurb());
214       describe_visual (stderr, ssi->screen,
215                        DefaultVisualOfScreen (ssi->screen),
216                        ssi->install_cmap_p);
217     }
218   printed_visual_info = True;
219
220   if (ssi->error_dialog)
221     {
222       defer_XDestroyWindow (si->app, si->dpy, ssi->error_dialog);
223       ssi->error_dialog = 0;
224     }
225
226   if (ssi->screensaver_window)
227     {
228       XWindowChanges changes;
229       unsigned int changesmask = CWX|CWY|CWWidth|CWHeight|CWBorderWidth;
230       changes.x = ssi->x;
231       changes.y = ssi->y;
232       changes.width = ssi->width;
233       changes.height = ssi->height;
234       changes.border_width = 0;
235
236       /* XConfigureWindow and XChangeWindowAttributes can fail if a hack did
237          something weird to the window.  In that case, we must destroy and
238          re-create it. */
239       if (! (XConfigureWindow (si->dpy, ssi->screensaver_window,
240                                changesmask, &changes) &&
241              XChangeWindowAttributes (si->dpy, ssi->screensaver_window,
242                                       attrmask, &attrs)))
243         {
244           horked_window = ssi->screensaver_window;
245           ssi->screensaver_window = 0;
246         }
247     }
248
249   if (!ssi->screensaver_window)
250     {
251       ssi->screensaver_window =
252         XCreateWindow (si->dpy, RootWindowOfScreen (ssi->screen),
253                        ssi->x, ssi->y, ssi->width, ssi->height,
254                        0, ssi->current_depth, InputOutput,
255                        ssi->current_visual, attrmask, &attrs);
256       xscreensaver_set_wm_atoms (si->dpy, ssi->screensaver_window,
257                                  ssi->width, ssi->height, 0);
258
259       if (horked_window)
260         {
261           fprintf (stderr,
262             "%s: someone horked our saver window (0x%lx)!  Recreating it...\n",
263                    blurb(), (unsigned long) horked_window);
264           defer_XDestroyWindow (si->app, si->dpy, horked_window);
265         }
266
267       if (p->verbose_p > 1)
268         fprintf (stderr, "%s: %d: saver window is 0x%lx\n",
269                  blurb(), ssi->number,
270                  (unsigned long) ssi->screensaver_window);
271     }
272
273   if (!ssi->cursor)
274     {
275       Pixmap bit =
276         XCreatePixmapFromBitmapData (si->dpy, ssi->screensaver_window,
277                                      "\000", 1, 1,
278                                      BlackPixelOfScreen (ssi->screen),
279                                      BlackPixelOfScreen (ssi->screen),
280                                      1);
281       ssi->cursor = XCreatePixmapCursor (si->dpy, bit, bit, &black, &black,
282                                          0, 0);
283       XFreePixmap (si->dpy, bit);
284     }
285
286   XSetWindowBackground (si->dpy, ssi->screensaver_window, ssi->black_pixel);
287
288   if (si->demoing_p)
289     XUndefineCursor (si->dpy, ssi->screensaver_window);
290   else
291     XDefineCursor (si->dpy, ssi->screensaver_window, ssi->cursor);
292 }
293
294
295 void
296 initialize_screensaver_window (saver_info *si)
297 {
298   int i;
299   for (i = 0; i < si->nscreens; i++)
300     initialize_screensaver_window_1 (&si->screens[i]);
301 }
302
303
304 static void
305 raise_window (saver_screen_info *ssi)
306 {
307   saver_info *si = ssi->global;
308   if (ssi->error_dialog)
309     {
310       /* Make the error be topmost, and the saver be directly below it. */
311       Window windows[2];
312       windows[0] = ssi->error_dialog;
313       windows[1] = ssi->screensaver_window;
314       XMapRaised (si->dpy, windows[0]);
315       XRestackWindows (si->dpy, windows, countof(windows));
316     }
317   else
318     XMapRaised (si->dpy, ssi->screensaver_window);
319
320   if (ssi->cmap)
321     XInstallColormap (si->dpy, ssi->cmap);
322 }
323
324
325 /* Called when the RANDR (Resize and Rotate) extension tells us that
326    the size of the screen has changed while the screen was blanked.
327    Call update_screen_layout() first, then call this to synchronize
328    the size of the saver windows to the new sizes of the screens.
329  */
330 void
331 resize_screensaver_window (saver_info *si)
332 {
333   saver_preferences *p = &si->prefs;
334   int i;
335
336   for (i = 0; i < si->nscreens; i++)
337     {
338       saver_screen_info *ssi = &si->screens[i];
339       XWindowAttributes xgwa;
340
341       /* Make sure a window exists -- it might not if a monitor was just
342          added for the first time.
343        */
344       if (! ssi->screensaver_window)
345         {
346           initialize_screensaver_window_1 (ssi);
347           if (p->verbose_p)
348             fprintf (stderr,
349                      "%s: %d: newly added window 0x%lx %dx%d+%d+%d\n",
350                      blurb(), i, (unsigned long) ssi->screensaver_window,
351                      ssi->width, ssi->height, ssi->x, ssi->y);
352         }
353
354       /* Make sure the window is the right size -- it might not be if
355          the monitor changed resolution, or if a badly-behaved hack
356          screwed with it.
357        */
358       XGetWindowAttributes (si->dpy, ssi->screensaver_window, &xgwa);
359       if (xgwa.x      != ssi->x ||
360           xgwa.y      != ssi->y ||
361           xgwa.width  != ssi->width ||
362           xgwa.height != ssi->height)
363         {
364           XWindowChanges changes;
365           unsigned int changesmask = CWX|CWY|CWWidth|CWHeight|CWBorderWidth;
366           changes.x      = ssi->x;
367           changes.y      = ssi->y;
368           changes.width  = ssi->width;
369           changes.height = ssi->height;
370           changes.border_width = 0;
371
372           if (p->verbose_p)
373             fprintf (stderr,
374                      "%s: %d: resize 0x%lx from %dx%d+%d+%d to %dx%d+%d+%d\n",
375                      blurb(), i, (unsigned long) ssi->screensaver_window,
376                      xgwa.width, xgwa.height, xgwa.x, xgwa.y,
377                      ssi->width, ssi->height, ssi->x, ssi->y);
378
379           if (! XConfigureWindow (si->dpy, ssi->screensaver_window,
380                                   changesmask, &changes))
381             fprintf (stderr, "%s: %d: someone horked our saver window"
382                      " (0x%lx)!  Unable to resize it!\n",
383                      blurb(), i, (unsigned long) ssi->screensaver_window);
384         }
385
386       /* Now (if blanked) make sure that it's mapped and running a hack --
387          it might not be if we just added it.  (We also might be re-using
388          an old window that existed for a previous monitor that was
389          removed and re-added.)
390
391          Note that spawn_screenhack() calls select_visual() which may destroy
392          and re-create the window via initialize_screensaver_window_1().
393        */
394       raise_window (ssi);
395       XSync (si->dpy, False);
396       if (! ssi->pid)
397         spawn_screenhack (ssi);
398     }
399
400   /* Kill off any savers running on no-longer-extant monitors.
401    */
402   for (; i < si->ssi_count; i++)
403     {
404       saver_screen_info *ssi = &si->screens[i];
405       if (ssi->pid)
406         kill_screenhack (ssi);
407       if (ssi->screensaver_window)
408         {
409           XUnmapWindow (si->dpy, ssi->screensaver_window);
410         }
411     }
412 }
413
414
415 static void 
416 raise_windows (saver_info *si)
417 {
418   int i;
419   for (i = 0; i < si->nscreens; i++)
420     {
421       saver_screen_info *ssi = &si->screens[i];
422       raise_window (ssi);
423     }
424 }
425
426
427 /* Called only once, before the main loop begins.
428  */
429 void 
430 blank_screen (saver_info *si)
431 {
432   saver_preferences *p = &si->prefs;
433   Bool user_active_p = False;
434   int i;
435
436   initialize_screensaver_window (si);
437   sync_server_dpms_settings (si->dpy, p);
438
439   /* Save a screenshot.  Must be before fade-out. */
440   for (i = 0; i < si->nscreens; i++)
441     {
442       saver_screen_info *ssi = &si->screens[i];
443       if (ssi->screenshot)
444         XFreePixmap (si->dpy, ssi->screenshot);
445       ssi->screenshot =
446         screenshot_grab (si->dpy, ssi->screensaver_window, False, p->verbose_p);
447     }
448
449   if (p->fade_p &&
450       !si->demoing_p &&
451       !si->emergency_p)
452     {
453       Window *current_windows = (Window *)
454         malloc (si->nscreens * sizeof(*current_windows));
455       if (!current_windows) abort();
456
457       for (i = 0; i < si->nscreens; i++)
458         {
459           saver_screen_info *ssi = &si->screens[i];
460           current_windows[i] = ssi->screensaver_window;
461           /* Ensure that the default background of the window is really black,
462              not a pixmap or something.  (This does not clear the window.) */
463           XSetWindowBackground (si->dpy, ssi->screensaver_window,
464                                 ssi->black_pixel);
465         }
466
467       if (p->verbose_p) fprintf (stderr, "%s: fading...\n", blurb());
468
469       /* This will take several seconds to complete. */
470       user_active_p = fade_screens (si->app, si->dpy,
471                                     current_windows, si->nscreens,
472                                     p->fade_seconds / 1000.0,
473                                     True,  /* out_p */
474                                     True,  /* from_desktop_p */
475                                     &si->fade_state);
476       free (current_windows);
477
478       if (!p->verbose_p)
479         ;
480       else if (user_active_p)
481         fprintf (stderr, "%s: fading aborted\n", blurb());
482       else
483         fprintf (stderr, "%s: fading done\n", blurb());
484     }
485
486   raise_windows (si);
487   reset_watchdog_timer (si);
488
489   /* user_active_p means that the user aborted the fade-out -- but that does
490      not mean that we are necessarily about to exit.  If we are locking, then
491      the user activity will cause the unlock dialog to appear, but
492      authentication might not succeed. */
493
494   for (i = 0; i < si->nscreens; i++)
495     /* This also queues each screen's cycle_timer. */
496     spawn_screenhack (&si->screens[i]);
497
498   /* Turn off "next" and "prev" modes after they have happened once. */
499    if (si->selection_mode < 0)
500      si->selection_mode = 0;
501
502   /* If we are blanking only, optionally power down monitor right now. */
503   if (p->mode == BLANK_ONLY &&
504       p->dpms_quickoff_p)
505     monitor_power_on (si, False);
506 }
507
508
509 /* Called only once, upon receipt of SIGTERM, just before exiting.
510  */
511 void
512 unblank_screen (saver_info *si)
513 {
514   saver_preferences *p = &si->prefs;
515   Bool unfade_p = p->unfade_p;
516   int i;
517
518   monitor_power_on (si, True);
519
520   if (si->demoing_p)
521     unfade_p = False;
522
523   if (unfade_p)
524     {
525       double seconds = p->fade_seconds / 1000.0;
526       double ratio = 1/3.0;
527       Window *current_windows = (Window *)
528         calloc(sizeof(Window), si->nscreens);
529       Bool interrupted_p = False;
530
531       for (i = 0; i < si->nscreens; i++)
532         {
533           saver_screen_info *ssi = &si->screens[i];
534           current_windows[i] = ssi->screensaver_window;
535         }
536
537       if (p->verbose_p) fprintf (stderr, "%s: unfading...\n", blurb());
538
539       monitor_power_on (si, True);
540
541       /* When we fade in to the desktop, first fade out from the saver to
542          black, then fade in from black to the desktop. */
543       interrupted_p = fade_screens (si->app, si->dpy,
544                                     current_windows, si->nscreens,
545                                     seconds * ratio,
546                                     True,  /* out_p */
547                                     False, /* from_desktop_p */
548                                     &si->fade_state);
549       if (! interrupted_p)
550         interrupted_p = fade_screens (si->app, si->dpy,
551                                       current_windows, si->nscreens,
552                                       seconds * (1-ratio),
553                                       False, /* out_p */
554                                       False, /* from_desktop_p */
555                                       &si->fade_state);
556       free (current_windows);
557
558       if (p->verbose_p)
559         fprintf (stderr, "%s: unfading done%s\n", blurb(),
560                  (interrupted_p ? " (interrupted)" : ""));
561     }
562   else
563     {
564       for (i = 0; i < si->nscreens; i++)
565         {
566           saver_screen_info *ssi = &si->screens[i];
567           if (ssi->cmap)
568             {
569               Colormap c = DefaultColormapOfScreen (ssi->screen);
570               /* avoid technicolor */
571               XSetWindowBackground (si->dpy, ssi->screensaver_window,
572                                     BlackPixelOfScreen (ssi->screen));
573               XClearWindow (si->dpy, ssi->screensaver_window);
574               if (c) XInstallColormap (si->dpy, c);
575             }
576           XUnmapWindow (si->dpy, ssi->screensaver_window);
577         }
578     }
579 }
580
581
582 static Visual *
583 get_screen_gl_visual (saver_info *si, int real_screen_number)
584 {
585   int nscreens = ScreenCount (si->dpy);
586
587   if (! si->best_gl_visuals)
588     {
589       int i;
590       si->best_gl_visuals = (Visual **) 
591         calloc (nscreens + 1, sizeof (*si->best_gl_visuals));
592
593       for (i = 0; i < nscreens; i++)
594         if (! si->best_gl_visuals[i])
595           si->best_gl_visuals[i] = 
596             get_best_gl_visual (si, ScreenOfDisplay (si->dpy, i));
597     }
598
599   if (real_screen_number < 0 || real_screen_number >= nscreens) abort();
600   return si->best_gl_visuals[real_screen_number];
601 }
602
603
604 Bool
605 select_visual (saver_screen_info *ssi, const char *visual_name)
606 {
607   XWindowAttributes xgwa;
608   saver_info *si = ssi->global;
609   saver_preferences *p = &si->prefs;
610   Bool install_cmap_p = p->install_cmap_p;
611   Bool was_installed_p = (ssi->cmap != DefaultColormapOfScreen(ssi->screen));
612   Visual *new_v = 0;
613   Bool got_it;
614
615   /* On some systems (most recently, MacOS X) OpenGL programs get confused
616      when you kill one and re-start another on the same window.  So maybe
617      it's best to just always destroy and recreate the xscreensaver window
618      when changing hacks, instead of trying to reuse the old one?
619    */
620   Bool always_recreate_window_p = True;
621
622   get_screen_gl_visual (si, 0);   /* let's probe all the GL visuals early */
623
624   /* We make sure the existing window is actually on ssi->screen before
625      trying to use it, in case things moved around radically when monitors
626      were added or deleted.  If we don't do this we could get a BadMatch
627      even though the depths match.  I think.
628    */
629   memset (&xgwa, 0, sizeof(xgwa));
630   if (ssi->screensaver_window)
631     XGetWindowAttributes (si->dpy, ssi->screensaver_window, &xgwa);
632
633   if (visual_name && *visual_name)
634     {
635       if (!strcasecmp(visual_name, "default-i"))
636         {
637           visual_name = "default";
638           install_cmap_p = True;
639         }
640       else if (!strcasecmp(visual_name, "default-n"))
641         {
642           visual_name = "default";
643           install_cmap_p = False;
644         }
645       else if (!strcasecmp(visual_name, "GL"))
646         {
647           new_v = get_screen_gl_visual (si, ssi->real_screen_number);
648           if (!new_v && p->verbose_p)
649             fprintf (stderr, "%s: no GL visuals\n", blurb());
650         }
651
652       if (!new_v)
653         new_v = get_visual (ssi->screen, visual_name, True, False);
654     }
655   else
656     {
657       new_v = ssi->default_visual;
658     }
659
660   got_it = !!new_v;
661
662   if (new_v && new_v != DefaultVisualOfScreen(ssi->screen))
663     /* It's not the default visual, so we have no choice but to install. */
664     install_cmap_p = True;
665
666   ssi->install_cmap_p = install_cmap_p;
667
668   if ((ssi->screen != xgwa.screen) ||
669       (new_v &&
670        (always_recreate_window_p ||
671         (ssi->current_visual != new_v) ||
672         (install_cmap_p != was_installed_p))))
673     {
674       Colormap old_c = ssi->cmap;
675       Window old_w = ssi->screensaver_window;
676       if (! new_v) 
677         new_v = ssi->current_visual;
678
679       if (p->verbose_p)
680         {
681 #if 0
682           fprintf (stderr, "%s: %d: visual ", blurb(), ssi->number);
683           describe_visual (stderr, ssi->screen, new_v, install_cmap_p);
684 #endif
685 #if 0
686           fprintf (stderr, "%s:                  from ", blurb());
687           describe_visual (stderr, ssi->screen, ssi->current_visual,
688                            was_installed_p);
689 #endif
690         }
691
692       ssi->current_visual = new_v;
693       ssi->current_depth = visual_depth(ssi->screen, new_v);
694       ssi->cmap = 0;
695       ssi->screensaver_window = 0;
696
697       initialize_screensaver_window_1 (ssi);
698       raise_window (ssi);
699
700       /* Now we can destroy the old window without horking our grabs. */
701       defer_XDestroyWindow (si->app, si->dpy, old_w);
702
703       if (p->verbose_p > 1)
704         fprintf (stderr, "%s: %d: destroyed old saver window 0x%lx\n",
705                  blurb(), ssi->number, (unsigned long) old_w);
706
707       if (old_c &&
708           old_c != DefaultColormapOfScreen (ssi->screen))
709         XFreeColormap (si->dpy, old_c);
710     }
711
712   return got_it;
713 }
714
715
716 /* Synchronize the contents of si->ssi to the current state of the monitors.
717    Doesn't change anything if nothing has changed; otherwise, alters and
718    reuses existing saver_screen_info structs as much as possible.
719    Returns True if anything changed.
720  */
721 Bool
722 update_screen_layout (saver_info *si)
723 {
724   monitor **monitors = scan_monitors (si->dpy);
725   int count = 0;
726   int good_count = 0;
727   int i, j;
728   int seen_screens[100] = { 0, };
729
730   if (! monitor_layouts_differ_p (monitors, si->monitor_layout))
731     {
732       free_monitors (monitors);
733       return False;
734     }
735
736   free_monitors (si->monitor_layout);
737   si->monitor_layout = monitors;
738
739   while (monitors[count])
740     {
741       if (monitors[count]->sanity == S_SANE)
742         good_count++;
743       count++;
744     }
745
746   if (si->ssi_count == 0)
747     {
748       si->ssi_count = 10;
749       si->screens = (saver_screen_info *)
750         calloc (sizeof(*si->screens), si->ssi_count);
751     }
752
753   if (si->ssi_count < count)
754     {
755       si->screens = (saver_screen_info *)
756         realloc (si->screens, sizeof(*si->screens) * count);
757       memset (si->screens + si->ssi_count, 0,
758               sizeof(*si->screens) * (count - si->ssi_count));
759       si->ssi_count = count;
760     }
761
762   if (! si->screens) abort();
763
764   si->nscreens = good_count;
765
766   /* Regenerate the list of GL visuals as needed. */
767   if (si->best_gl_visuals)
768     free (si->best_gl_visuals);
769   si->best_gl_visuals = 0;
770
771   for (i = 0, j = 0; i < count; i++)
772     {
773       monitor *m = monitors[i];
774       saver_screen_info *ssi = &si->screens[j];
775       int sn;
776       if (monitors[i]->sanity != S_SANE) continue;
777
778       ssi->global = si;
779       ssi->number = j;
780
781       sn = screen_number (m->screen);
782       ssi->screen = m->screen;
783       ssi->real_screen_number = sn;
784       ssi->real_screen_p = (seen_screens[sn] == 0);
785       seen_screens[sn]++;
786
787       ssi->default_visual =
788         get_visual_resource (ssi->screen, "visualID", "VisualID", False);
789       ssi->current_visual = ssi->default_visual;
790       ssi->current_depth = visual_depth (ssi->screen, ssi->current_visual);
791
792       ssi->x      = m->x;
793       ssi->y      = m->y;
794       ssi->width  = m->width;
795       ssi->height = m->height;
796
797 # ifndef DEBUG_MULTISCREEN
798       {
799         saver_preferences *p = &si->prefs;
800         if (p->debug_p)
801           ssi->width /= 2;
802       }
803 # endif
804
805       j++;
806     }
807
808   return True;
809 }
810
811
812 /* When the screensaver is active, this timer will periodically change
813    the running program.  Each screen has its own timer.
814  */
815 void
816 cycle_timer (XtPointer closure, XtIntervalId *id)
817 {
818   saver_screen_info *ssi = (saver_screen_info *) closure;
819   saver_info *si = ssi->global;
820
821   if (ssi->error_dialog)
822     {
823       defer_XDestroyWindow (si->app, si->dpy, ssi->error_dialog);
824       ssi->error_dialog = 0;
825     }
826
827   maybe_reload_init_file (si);
828   kill_screenhack (ssi);
829   raise_window (ssi);
830
831   /* We could do a fade-out of just this screen here; but that would only work
832      if the fade method is SHM, not gamma or colormap.  It would also only
833      look right if the cycle timers never fire at the same time, which is
834      currently the case. */
835
836   XSync (si->dpy, False);
837   spawn_screenhack (ssi);  /* This also re-adds the cycle_id timer */
838 }
839
840
841 /* Called when a screenhack has exited unexpectedly.
842    We print a notification on the window, and in a little while, launch
843    a new hack (rather than waiting for the cycle timer to fire).
844  */
845 void
846 screenhack_obituary (saver_screen_info *ssi,
847                      const char *name, const char *error)
848 {
849   saver_info *si = ssi->global;
850   saver_preferences *p = &si->prefs;
851   Time how_long = p->cycle;
852   Time max = 1000 * 60;         /* Message stays up no longer than this */
853   Window window;
854   Visual *visual;
855   XSetWindowAttributes attrs;
856   XWindowChanges changes;
857   unsigned long attrmask;
858   XftFont *font;
859   XftColor fg;
860   XftDraw *xftdraw;
861   XGlyphInfo overall;
862   XGCValues gcv;
863   GC gc;
864   char *fn, *cn;
865   char buf[255];
866   int x, y, pad;
867   int bw = 4;
868   Colormap cmap;
869
870   /* Restart the cycle timer, to take down the error dialog and launch
871      a new hack.
872    */
873   if (how_long > max)
874     how_long = max;
875   if (ssi->cycle_id)
876     XtRemoveTimeOut (ssi->cycle_id);
877   ssi->cycle_id =
878     XtAppAddTimeOut (si->app, how_long, cycle_timer, (XtPointer) ssi);
879   ssi->cycle_at = time ((time_t *) 0) + how_long / 1000;
880   if (p->verbose_p)
881     fprintf (stderr, "%s: %d: cycling in %lu sec\n", blurb(), ssi->number,
882              how_long / 1000);
883
884   /* Render an error message while we wait.
885
886      We can't just render text on top of ssi->screensaver_window because
887      if there was an OpenGL hack running on it, Xlib graphics might not
888      show up at all.  Likewise, creating a sub-window doesn't work.
889      So it must be a top-level override-redirect window atop the saver.
890    */
891   cmap = ssi->cmap ? ssi->cmap : DefaultColormapOfScreen (ssi->screen);
892   window = ssi->error_dialog;
893   if (window) defer_XDestroyWindow (si->app, si->dpy, window);
894   attrs.override_redirect = True;
895   attrs.background_pixel = ssi->black_pixel;
896   attrs.border_pixel = ssi->black_pixel;
897   attrs.backing_store = Always;
898   attrs.colormap = cmap;
899   attrmask = (CWOverrideRedirect | CWBackPixel | CWBorderPixel | 
900               CWBackingStore | CWColormap);
901   visual = ssi->current_visual;
902   window = ssi->error_dialog =
903     XCreateWindow (si->dpy, RootWindowOfScreen (ssi->screen),
904                    0, 0, 1, 1, 0, ssi->current_depth, InputOutput, visual,
905                    attrmask, &attrs);
906
907   fn = get_string_resource (si->dpy, "errorFont", "Font");
908   cn = get_string_resource (si->dpy, "errorColor", "Color");
909   if (!fn || !*fn) fn = strdup ("monospace bold 16");
910   if (!cn || !*cn) cn = strdup ("#FF0000");
911
912   font = load_xft_font_retry (si->dpy, screen_number (ssi->screen), fn);
913   XftColorAllocName (si->dpy, visual, cmap, cn, &fg);
914   xftdraw = XftDrawCreate (si->dpy, window, visual, cmap);
915
916   gcv.foreground =
917     get_pixel_resource (si->dpy, cmap, "errorColor", "Color");
918   gcv.line_width = bw;
919   gc = XCreateGC (si->dpy, window, GCForeground | GCLineWidth, &gcv);
920
921   if (name && *name)
922     sprintf (buf, "\"%.100s\" %.100s", name, error);
923   else
924     sprintf (buf, "%.100s", error);
925
926   XftTextExtentsUtf8 (si->dpy, font, (FcChar8 *) buf, strlen(buf), &overall);
927   x = (ssi->width - overall.width) / 2;
928   y = (ssi->height - overall.height) / 2 + font->ascent;
929   pad = bw + font->ascent * 2;
930
931   attrmask = CWX | CWY | CWWidth | CWHeight;
932   changes.x = ssi->x + x - pad;
933   changes.y = ssi->y + y - (font->ascent + pad);
934   changes.width = overall.width + pad * 2;
935   changes.height = font->ascent + font->descent + pad * 2;
936   XConfigureWindow (si->dpy, window, attrmask, &changes);
937   xscreensaver_set_wm_atoms (si->dpy, window, changes.width, changes.height,
938                              ssi->screensaver_window);
939   XMapRaised (si->dpy, window);
940   XClearWindow (si->dpy, window);
941
942   XDrawRectangle (si->dpy, window, gc, gcv.line_width/2, gcv.line_width/2,
943                   changes.width - gcv.line_width,
944                   changes.height - gcv.line_width);
945
946   x = pad;
947   y = font->ascent + pad;
948   XftDrawStringUtf8 (xftdraw, &fg, font, x, y, (FcChar8 *) buf, strlen (buf));
949   XSync (si->dpy, False);
950
951   XFreeGC (si->dpy, gc);
952   XftDrawDestroy (xftdraw);
953   /* XftColorFree (si->dpy, visual, cmap, &fg); */
954   XftFontClose (si->dpy, font);
955   free (fn);
956   free (cn);
957 }
958
959
960 /* This timer goes off every few minutes to try and clean up anything that has
961    gone wrong.  It raises the windows, in case some other window has been
962    mapped on top of them, and re-sets the server's DPMS settings.
963
964    Maybe we should respond to Expose events to detect when another window has
965    raised above us and re-raise ourselves sooner.  But that would result in us
966    fighting against "xscreensaver-auth" which tries very hard to be on top.
967  */
968 static void
969 watchdog_timer (XtPointer closure, XtIntervalId *id)
970 {
971   saver_info *si = (saver_info *) closure;
972   saver_preferences *p = &si->prefs;
973   Bool running_p, on_p, terminating_p;
974
975   /* If the DPMS settings on the server have changed, change them back to
976      what ~/.xscreensaver says they should be. */
977   sync_server_dpms_settings (si->dpy, p);
978
979   if (si->prefs.debug_p)
980     fprintf (stderr, "%s: watchdog timer raising screen\n", blurb());
981
982   raise_windows (si);
983
984   running_p = any_screenhacks_running_p (si);
985   on_p = monitor_powered_on_p (si->dpy);
986   terminating_p = si->terminating_p;
987   if (running_p && !on_p)
988     {
989       int i;
990       if (si->prefs.verbose_p)
991         fprintf (stderr,
992                  "%s: monitor has powered down; killing running hacks\n",
993                  blurb());
994       for (i = 0; i < si->nscreens; i++)
995         kill_screenhack (&si->screens[i]);
996       /* Do not clear current_hack here. */
997     }
998   else if (terminating_p)
999     {
1000        /* If we are in the process of shutting down and are about to exit,
1001           don't re-launch anything just because the monitor came back on. */
1002     }
1003   else if (!running_p && on_p)
1004     {
1005       /* If the hack number is set but no hack is running, it is because the
1006          hack was killed when the monitor powered off, above.  This assumes
1007          that kill_screenhack() clears pid but not current_hack.  Start the
1008          hack going again.  The cycle_timer will also do this (unless "cycle"
1009          is 0) but watchdog_timer runs more frequently.
1010        */
1011       if (si->nscreens > 0 && si->screens[0].current_hack >= 0)
1012         {
1013           int i;
1014           if (si->prefs.verbose_p)
1015             fprintf (stderr,
1016                      "%s: monitor has powered back on; re-launching hacks\n",
1017                      blurb());
1018           for (i = 0; i < si->nscreens; i++)
1019             spawn_screenhack (&si->screens[i]);
1020         }
1021     }
1022
1023   /* Re-schedule this timer.  The watchdog timer defaults to a bit less
1024      than the hack cycle period, but is never longer than one hour.
1025    */
1026   si->watchdog_id = 0;
1027   reset_watchdog_timer (si);
1028 }
1029
1030
1031 static void
1032 reset_watchdog_timer (saver_info *si)
1033 {
1034   saver_preferences *p = &si->prefs;
1035
1036   if (si->watchdog_id)
1037     {
1038       XtRemoveTimeOut (si->watchdog_id);
1039       si->watchdog_id = 0;
1040     }
1041
1042   if (p->watchdog_timeout <= 0) return;
1043   si->watchdog_id = XtAppAddTimeOut (si->app, p->watchdog_timeout,
1044                                      watchdog_timer, (XtPointer) si);
1045   if (p->debug_p)
1046     fprintf (stderr, "%s: restarting watchdog_timer (%ld, %ld)\n",
1047              blurb(), p->watchdog_timeout, si->watchdog_id);
1048 }