ftp://ftp.linux.ncsu.edu/mirror/ftp.redhat.com/pub/redhat/linux/enterprise/4/en/os...
[xscreensaver] / driver / xscreensaver.c
index a70de0e4043a2eba2e497f7b36210824cd85c530..ab39b84e62341db668725061a05b382b7e0bc23c 100644 (file)
  *   on (nearly) all windows; or by waiting for the MIT-SCREEN-SAVER extension
  *   to send us a "you are idle" event.
  *
- *   Then, we map a full screen black window (or, in the case of the 
- *   MIT-SCREEN-SAVER extension, use the one it gave us.)
+ *   Then, we map a full screen black window.
  *
  *   We place a __SWM_VROOT property on this window, so that newly-started
- *   clients will think that this window is a "virtual root" window.
+ *   clients will think that this window is a "virtual root" window (as per
+ *   the logic in the historical "vroot.h" header.)
  *
  *   If there is an existing "virtual root" window (one that already had
  *   an __SWM_VROOT property) then we remove that property from that window.
  *   When they are, we kill the inferior process, unmap the window, and restore
  *   the __SWM_VROOT property to the real virtual root window if there was one.
  *
- *   While we are waiting, we also set up timers so that, after a certain 
- *   amount of time has passed, we can start a different screenhack.  We do
- *   this by killing the running child process with SIGTERM, and then starting
- *   a new one in the same way.
+ *   On multi-screen systems, we do the above on each screen, and start
+ *   multiple programs, each with a different value of $DISPLAY.
+ *
+ *   On Xinerama systems, we do a similar thing, but instead create multiple
+ *   windows on the (only) display, and tell the subprocess which one to use
+ *   via the $XSCREENSAVER_WINDOW environment variable -- this trick requires
+ *   a recent (Aug 2003) revision of vroot.h.
+ *
+ *   While we are waiting for user activity, we also set up timers so that,
+ *   after a certain amount of time has passed, we can start a different
+ *   screenhack.  We do this by killing the running child process with
+ *   SIGTERM, and then starting a new one in the same way.
  *
  *   If there was a real virtual root, meaning that we removed the __SWM_VROOT
  *   property from it, meaning we must (absolutely must) restore it before we
@@ -57,7 +65,7 @@
  *   can really fuck up the world by killing this process with "kill -9".
  *
  *   This program accepts ClientMessages of type SCREENSAVER; these messages
- *   may contain the atom ACTIVATE or DEACTIVATE, meaning to turn the 
+ *   may contain the atoms ACTIVATE, DEACTIVATE, etc, meaning to turn the 
  *   screensaver on or off now, regardless of the idleness of the user,
  *   and a few other things.  The included "xscreensaver-command" program
  *   sends these messsages.
  *   "client changing event mask" problem that the KeyPress events hack does.
  *   I think polling is more reliable.
  *
- *   None of this crap happens if we're using one of the extensions, so install
- *   one of them if the description above sounds just too flaky to live.  It
- *   is, but those are your choices.
+ *   On systems with /proc/interrupts (Linux) we poll that file and note when
+ *   the interrupt counter numbers on the "keyboard" and "PS/2" lines change.
+ *   (There is no reliable way, using /proc/interrupts, to detect non-PS/2
+ *   mice, so it doesn't help for serial or USB mice.)
+ *
+ *   None of this crap happens if we're using one of the extensions.  Sadly,
+ *   the XIdle extension hasn't been available for many years; the SGI
+ *   extension only exists on SGIs; and the MIT extension, while widely
+ *   deployed, is garbage in several ways.
  *
  *   A third idle-detection option could be implemented (but is not): when
  *   running on the console display ($DISPLAY is `localhost`:0) and we're on a
 # include <X11/extensions/xidle.h>
 #endif /* HAVE_XIDLE_EXTENSION */
 
+#ifdef HAVE_XINERAMA
+# include <X11/extensions/Xinerama.h>
+#endif /* HAVE_XINERAMA */
+
 #include "xscreensaver.h"
 #include "version.h"
 #include "yarandom.h"
@@ -237,7 +255,7 @@ do_help (saver_info *si)
   fflush (stdout);
   fflush (stderr);
   fprintf (stdout, "\
-xscreensaver %s, copyright (c) 1991-2003 by Jamie Zawinski <jwz@jwz.org>\n\
+xscreensaver %s, copyright (c) 1991-2004 by Jamie Zawinski <jwz@jwz.org>\n\
 \n\
   All xscreensaver configuration is via the `~/.xscreensaver' file.\n\
   Rather than editing that file by hand, just run `xscreensaver-demo':\n\
@@ -311,11 +329,14 @@ saver_ehandler (Display *dpy, XErrorEvent *error)
           blurb());
 
   for (i = 0; i < si->nscreens; i++)
-    fprintf (real_stderr, "%s: screen %d: 0x%x, 0x%x, 0x%x\n",
-             blurb(), i,
-             (unsigned int) RootWindowOfScreen (si->screens[i].screen),
-             (unsigned int) si->screens[i].real_vroot,
-             (unsigned int) si->screens[i].screensaver_window);
+    {
+      saver_screen_info *ssi = &si->screens[i];
+      fprintf (real_stderr, "%s: screen %d/%d: 0x%x, 0x%x, 0x%x\n",
+               blurb(), ssi->real_screen_number, ssi->number,
+               (unsigned int) RootWindowOfScreen (si->screens[i].screen),
+               (unsigned int) si->screens[i].real_vroot,
+               (unsigned int) si->screens[i].screensaver_window);
+    }
 
   fprintf (real_stderr, "\n"
           "#######################################"
@@ -698,7 +719,7 @@ print_banner (saver_info *si)
 
   if (p->verbose_p)
     fprintf (stderr,
-            "%s %s, copyright (c) 1991-2003 "
+            "%s %s, copyright (c) 1991-2004 "
             "by Jamie Zawinski <jwz@jwz.org>.\n",
             progname, si->version);
 
@@ -769,18 +790,126 @@ initialize_per_screen_info (saver_info *si, Widget toplevel_shell)
   Bool found_any_writable_cells = False;
   int i;
 
-  si->nscreens = ScreenCount(si->dpy);
-  si->screens = (saver_screen_info *)
-    calloc(sizeof(saver_screen_info), si->nscreens);
+# ifdef HAVE_XINERAMA
+  {
+    int event, error;
+    si->xinerama_p = (XineramaQueryExtension (si->dpy, &event, &error) &&
+                      XineramaIsActive (si->dpy));
+  }
+
+  if (si->xinerama_p && ScreenCount (si->dpy) != 1)
+    {
+      si->xinerama_p = False;
+      if (si->prefs.verbose_p)
+       fprintf (stderr,
+                 "%s: Xinerama AND %d screens?  Disabling Xinerama support!\n",
+                 blurb(), ScreenCount(si->dpy));
+    }
+
+  if (si->xinerama_p)
+    {
+      XineramaScreenInfo *xsi = XineramaQueryScreens (si->dpy, &si->nscreens);
+      if (!xsi)
+        si->xinerama_p = False;
+      else
+        {
+          si->screens = (saver_screen_info *)
+            calloc(sizeof(saver_screen_info), si->nscreens);
+          for (i = 0; i < si->nscreens; i++)
+            {
+              si->screens[i].x      = xsi[i].x_org;
+              si->screens[i].y      = xsi[i].y_org;
+              si->screens[i].width  = xsi[i].width;
+              si->screens[i].height = xsi[i].height;
+            }
+          XFree (xsi);
+        }
+      si->default_screen = &si->screens[0];
+      si->default_screen->real_screen_p = True;
+    }
+# endif /* !HAVE_XINERAMA */
+
+  if (!si->xinerama_p)
+    {
+      si->nscreens = ScreenCount(si->dpy);
+      si->screens = (saver_screen_info *)
+        calloc(sizeof(saver_screen_info), si->nscreens);
+      si->default_screen = &si->screens[DefaultScreen(si->dpy)];
+
+      for (i = 0; i < si->nscreens; i++)
+        {
+          saver_screen_info *ssi = &si->screens[i];
+          ssi->width  = DisplayWidth  (si->dpy, i);
+          ssi->height = DisplayHeight (si->dpy, i);
+          ssi->real_screen_p = True;
+          ssi->real_screen_number = i;
+        }
+    }
 
-  si->default_screen = &si->screens[DefaultScreen(si->dpy)];
 
+  /* In "quad mode", we use the Xinerama code to pretend that there are 4
+     screens for every physical screen, and run four times as many hacks...
+   */
+  if (si->prefs.quad_p)
+    {
+      int ns2 = si->nscreens * 4;
+      saver_screen_info *ssi2 = (saver_screen_info *)
+        calloc(sizeof(saver_screen_info), ns2);
+
+      for (i = 0; i < si->nscreens; i++)
+        {
+          saver_screen_info *old = &si->screens[i];
+
+          if (si->prefs.debug_p) old->width = old->width / 2;
+
+          ssi2[i*4  ] = *old;
+          ssi2[i*4+1] = *old;
+          ssi2[i*4+2] = *old;
+          ssi2[i*4+3] = *old;
+
+          ssi2[i*4  ].width  /= 2;
+          ssi2[i*4  ].height /= 2;
+
+          ssi2[i*4+1].x      += ssi2[i*4  ].width;
+          ssi2[i*4+1].width  -= ssi2[i*4  ].width;
+          ssi2[i*4+1].height /= 2;
+
+          ssi2[i*4+2].y      += ssi2[i*4  ].height;
+          ssi2[i*4+2].width  /= 2;
+          ssi2[i*4+2].height -= ssi2[i*4  ].height;
+
+          ssi2[i*4+3].x      += ssi2[i*4+2].width;
+          ssi2[i*4+3].y      += ssi2[i*4+2].height;
+          ssi2[i*4+3].width  -= ssi2[i*4+2].width;
+          ssi2[i*4+3].height -= ssi2[i*4+2].height;
+
+          ssi2[i*4+1].real_screen_p = False;
+          ssi2[i*4+2].real_screen_p = False;
+          ssi2[i*4+3].real_screen_p = False;
+        }
+
+      si->nscreens = ns2;
+      free (si->screens);
+      si->screens = ssi2;
+      si->default_screen = &si->screens[DefaultScreen(si->dpy) * 4];
+      si->xinerama_p = True;
+    }
+
+  /* finish initializing the screens.
+   */
   for (i = 0; i < si->nscreens; i++)
     {
       saver_screen_info *ssi = &si->screens[i];
       ssi->global = si;
-      ssi->screen = ScreenOfDisplay (si->dpy, i);
+
       ssi->number = i;
+      ssi->screen = ScreenOfDisplay (si->dpy, ssi->real_screen_number);
+
+      if (!si->xinerama_p)
+        {
+          ssi->width  = WidthOfScreen  (ssi->screen);
+          ssi->height = HeightOfScreen (ssi->screen);
+        }
 
       /* Note: we can't use the resource ".visual" because Xt is SO FUCKED. */
       ssi->default_visual =
@@ -896,6 +1025,19 @@ initialize_server_extensions (saver_info *si)
                 blurb());
     }
 
+  /* These are incompatible (or at least, our support for them is...) */
+  if (si->xinerama_p && si->using_mit_saver_extension)
+    {
+      si->using_mit_saver_extension = False;
+      if (p->verbose_p)
+        fprintf (stderr, "%s: Xinerama in use: disabling MIT-SCREEN-SAVER.\n",
+                 blurb());
+    }
+
+#ifdef HAVE_RANDR
+  query_randr_extension (si);
+#endif
+
   if (!system_has_proc_interrupts_p)
     {
       si->using_proc_interrupts = False;
@@ -960,8 +1102,12 @@ select_events (saver_info *si)
      for window creation events, so that new subwindows will be noticed.
    */
   for (i = 0; i < si->nscreens; i++)
-    start_notice_events_timer (si, RootWindowOfScreen (si->screens[i].screen),
-                               False);
+    {
+      saver_screen_info *ssi = &si->screens[i];
+      if (ssi->real_screen_p)
+        start_notice_events_timer (si,
+           RootWindowOfScreen (si->screens[i].screen), False);
+    }
 
   if (p->verbose_p)
     fprintf (stderr, " done.\n");
@@ -1048,6 +1194,24 @@ main_loop (saver_info *si)
           continue;
         }
 
+      /* Since we're about to blank the screen, kill the de-race timer,
+         if any.  It might still be running if we have unblanked and then
+         re-blanked in a short period (e.g., when using the "next" button
+         in xscreensaver-demo.)
+       */
+      if (si->de_race_id)
+        {
+          if (p->verbose_p)
+            fprintf (stderr, "%s: stopping de-race timer (%d remaining.)\n",
+                     blurb(), si->de_race_ticks);
+          XtRemoveTimeOut (si->de_race_id);
+          si->de_race_id = 0;
+        }
+
+
+      /* Now, try to blank.
+       */
+
       if (! blank_screen (si))
         {
           /* We were unable to grab either the keyboard or mouse.
@@ -1058,13 +1222,29 @@ main_loop (saver_info *si)
              see any events, and the display would be wedged.
 
              So, just go around the loop again and wait for the
-             next bout of idleness.
+             next bout of idleness.  (If the user remains idle, we
+             will next try to blank the screen again in no more than
+             60 seconds.)
           */
+          Time retry = 60 * 1000;
+          if (p->timeout < retry)
+            retry = p->timeout;
 
-          fprintf (stderr,
+          if (p->debug_p)
+            {
+              fprintf (stderr,
+                  "%s: DEBUG MODE: unable to grab -- BLANKING ANYWAY.\n",
+                       blurb());
+            }
+          else
+            {
+              fprintf (stderr,
                   "%s: unable to grab keyboard or mouse!  Blanking aborted.\n",
-                   blurb());
-          continue;
+                       blurb());
+
+              schedule_wakeup_event (si, retry, p->debug_p);
+              continue;
+            }
         }
 
       kill_screenhack (si);
@@ -1206,52 +1386,10 @@ main_loop (saver_info *si)
          si->lock_id = 0;
        }
 
-      /* It's possible that a race condition could have led to the saver
-         window being unexpectedly still mapped.  This can happen like so:
-
-          - screen is blanked
-          - hack is launched
-          - that hack tries to grab a screen image( it does this by
-            first unmapping the saver window, then remapping it.)
-          - hack unmaps window
-          - hack waits
-          - user becomes active
-          - hack re-maps window (*)
-          - driver kills subprocess
-          - driver unmaps window (**)
-
-         The race is that (*) might have been sent to the server before
-         the client process was killed, but, due to scheduling randomness,
-         might not have been received by the server until after (**).
-         In other words, (*) and (**) might happen out of order, meaning
-         the driver will unmap the window, and then after that, the
-         recently-dead client will re-map it.  This leaves the user
-         locked out (it looks like a desktop, but it's not!)
-
-         To avoid this: after un-blanking the screen, sleep for a second,
-         and then really make sure the window is unmapped.
-       */
-      {
-        int i;
-        XSync (si->dpy, False);
-        sleep (1);
-        for (i = 0; i < si->nscreens; i++)
-          {
-            saver_screen_info *ssi = &si->screens[i];
-            Window w = ssi->screensaver_window;
-            XWindowAttributes xgwa;
-            XGetWindowAttributes (si->dpy, w, &xgwa);
-            if (xgwa.map_state != IsUnmapped)
-              {
-                if (p->verbose_p)
-                  fprintf (stderr,
-                           "%s: %d: client race! emergency unmap 0x%lx.\n",
-                           blurb(), i, (unsigned long) w);
-                XUnmapWindow (si->dpy, w);
-              }
-          }
-        XSync (si->dpy, False);
-      }
+      /* Since we're unblanked now, break race conditions and make
+         sure we stay that way (see comment in timers.c.) */
+      if (! si->de_race_id)
+        de_race_timer ((XtPointer) si, 0);
     }
 }
 
@@ -1288,7 +1426,7 @@ main (int argc, char **argv)
   blurb_timestamp_p = p->timestamp_p;  /* kludge */
   initialize_per_screen_info (si, shell); /* also sets si->fading_possible_p */
 
-  /* We can only issue this warnings now. */
+  /* We can only issue this warning now. */
   if (p->verbose_p && !si->fading_possible_p && (p->fade_p || p->unfade_p))
     fprintf (stderr,
              "%s: there are no PseudoColor or GrayScale visuals.\n"
@@ -1296,8 +1434,12 @@ main (int argc, char **argv)
              blurb(), blurb());
 
   for (i = 0; i < si->nscreens; i++)
-    if (ensure_no_screensaver_running (si->dpy, si->screens[i].screen))
-      exit (1);
+    {
+      saver_screen_info *ssi = &si->screens[i];
+      if (ssi->real_screen_p)
+        if (ensure_no_screensaver_running (si->dpy, si->screens[i].screen))
+          exit (1);
+    }
 
   lock_initialization (si, &argc, argv);
   print_lock_failure_banner (si);
@@ -1521,6 +1663,14 @@ handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p)
     {
       if (until_idle_p)
        {
+          if (p->mode == DONT_BLANK)
+            {
+              clientmessage_response(si, window, True,
+                         "ACTIVATE ClientMessage received in DONT_BLANK mode.",
+                                     "screen blanking is currently disabled.");
+              return False;
+            }
+
          clientmessage_response(si, window, False,
                                 "ACTIVATE ClientMessage received.",
                                 "activating.");
@@ -1625,6 +1775,14 @@ handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p)
       char buf2 [255];
       long which = event->xclient.data.l[1];
 
+      if (p->mode == DONT_BLANK)
+        {
+          clientmessage_response(si, window, True,
+                           "SELECT ClientMessage received in DONT_BLANK mode.",
+                                 "screen blanking is currently disabled.");
+          return False;
+        }
+
       sprintf (buf, "SELECT %ld ClientMessage received.", which);
       sprintf (buf2, "activating (%ld).", which);
       clientmessage_response (si, window, False, buf, buf2);
@@ -1744,7 +1902,11 @@ handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p)
                              "not compiled with support for locking.",
                              "locking not enabled.");
 #else /* !NO_LOCKING */
-      if (si->locking_disabled_p)
+      if (p->mode == DONT_BLANK)
+        clientmessage_response(si, window, True,
+                             "LOCK ClientMessage received in DONT_BLANK mode.",
+                               "screen blanking is currently disabled.");
+      else if (si->locking_disabled_p)
        clientmessage_response (si, window, True,
                      "LOCK ClientMessage received, but locking is disabled.",
                              "locking not enabled.");
@@ -1943,16 +2105,28 @@ analyze_display (saver_info *si)
         False
 #     endif
    }, { "XINERAMA",                             "Xinerama",
+#     ifdef HAVE_XINERAMA
+        True
+#     else
+        False
+#     endif
+   }, { "RANDR",                                "Resize-and-Rotate",
+#     ifdef HAVE_RANDR
         True
+#     else
+        False
+#     endif
    }, { "Apple-DRI",                            "Apple-DRI (XDarwin)",
         True
    },
   };
 
-  fprintf (stderr, "%s: running on display \"%s\" (%d screen%s).\n",
+  fprintf (stderr, "%s: running on display \"%s\" (%d %sscreen%s).\n",
            blurb(),
           DisplayString(si->dpy),
-           si->nscreens, (si->nscreens == 1 ? "" : "s"));
+           si->nscreens,
+           (si->xinerama_p ? "Xinerama " : ""),
+           (si->nscreens == 1 ? "" : "s"));
   fprintf (stderr, "%s: vendor is %s, %d.\n", blurb(),
           ServerVendor(si->dpy), VendorRelease(si->dpy));
 
@@ -1978,11 +2152,15 @@ analyze_display (saver_info *si)
 
   for (i = 0; i < si->nscreens; i++)
     {
+      saver_screen_info *ssi = &si->screens[i];
       unsigned long colormapped_depths = 0;
       unsigned long non_mapped_depths = 0;
       XVisualInfo vi_in, *vi_out;
       int out_count;
-      vi_in.screen = i;
+
+      if (!ssi->real_screen_p) continue;
+
+      vi_in.screen = ssi->real_screen_number;
       vi_out = XGetVisualInfo (si->dpy, VisualScreenMask, &vi_in, &out_count);
       if (!vi_out) continue;
       for (j = 0; j < out_count; j++)
@@ -1994,7 +2172,8 @@ analyze_display (saver_info *si)
 
       if (colormapped_depths)
        {
-         fprintf (stderr, "%s: screen %d colormapped depths:", blurb(), i);
+         fprintf (stderr, "%s: screen %d colormapped depths:", blurb(),
+                   ssi->real_screen_number);
          for (j = 0; j < 32; j++)
            if (colormapped_depths & (1 << j))
              fprintf (stderr, " %d", j);
@@ -2003,13 +2182,27 @@ analyze_display (saver_info *si)
       if (non_mapped_depths)
        {
          fprintf (stderr, "%s: screen %d non-colormapped depths:",
-                   blurb(), i);
+                   blurb(), ssi->real_screen_number);
          for (j = 0; j < 32; j++)
            if (non_mapped_depths & (1 << j))
              fprintf (stderr, " %d", j);
          fprintf (stderr, ".\n");
        }
     }
+
+  if (si->xinerama_p)
+    {
+      fprintf (stderr, "%s: Xinerama layout:\n", blurb());
+      for (i = 0; i < si->nscreens; i++)
+        {
+          saver_screen_info *ssi = &si->screens[i];
+          fprintf (stderr, "%s:   %c %d/%d: %dx%d+%d+%d\n",
+                   blurb(),
+                   (ssi->real_screen_p ? '+' : ' '),
+                   ssi->number, ssi->real_screen_number,
+                   ssi->width, ssi->height, ssi->x, ssi->y);
+        }
+    }
 }
 
 Bool