From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / driver / xscreensaver.c
index 1ac1d68c16da2ae8312631958807dd83b0c475a4..dd27cae56f679727d5b42ab9679266c120692942 100644 (file)
@@ -1,4 +1,4 @@
-/* xscreensaver, Copyright (c) 1991-2008 Jamie Zawinski <jwz@jwz.org>
+/* xscreensaver, Copyright (c) 1991-2017 Jamie Zawinski <jwz@jwz.org>
  *
  * Permission to use, copy, modify, distribute, and sell this software and its
  * documentation for any purpose is hereby granted without fee, provided that
  *   subwindows.  It is an incredible misdesign that one client can make
  *   another client malfunction in this way.
  *
+ *   But here's a new kink that started showing up in late 2014: GNOME programs
+ *   don't actually select for or receive KeyPress events! They do it behind
+ *   the scenes through some kind of Input Method magic, even when running in
+ *   an en_US locale.  However, in that case, those applications *do* seem to
+ *   update the _NET_WM_USER_TIME on their own windows every time they have
+ *   received a secret KeyPress, so we *also* monitor that property on every
+ *   window, and treat changes to it as identical to KeyPress.
+ *
  *   To detect mouse motion, we periodically wake up and poll the mouse
  *   position and button/modifier state, and notice when something has
  *   changed.  We make this check every five seconds by default, and since the
 #include <ctype.h>
 #include <X11/Xlib.h>
 
+#ifdef ENABLE_NLS
+# include <locale.h>
+# include <libintl.h>
+#endif /* ENABLE_NLS */
+
 #include <X11/Xlibint.h>
 
 #include <X11/Xatom.h>
@@ -266,17 +279,24 @@ ERROR!  You must not include vroot.h in this file.
 static void
 do_help (saver_info *si)
 {
+  char *s, year[5];
+  s = strchr (screensaver_id, '-');
+  s = strrchr (s, '-');
+  s++;
+  strncpy (year, s, 4);
+  year[4] = 0;
+
   fflush (stdout);
   fflush (stderr);
   fprintf (stdout, "\
-xscreensaver %s, copyright (c) 1991-2008 by Jamie Zawinski <jwz@jwz.org>\n\
+xscreensaver %s, copyright (c) 1991-%s 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\
   that program lets you configure the screen saver graphically,\n\
   including timeouts, locking, and display modes.\n\
 \n",
-         si->version);
+         si->version, year);
   fprintf (stdout, "\
   Just getting started?  Try this:\n\
 \n\
@@ -285,7 +305,7 @@ xscreensaver %s, copyright (c) 1991-2008 by Jamie Zawinski <jwz@jwz.org>\n\
 \n\
   For updates, online manual, and FAQ, please see the web page:\n\
 \n\
-       http://www.jwz.org/xscreensaver/\n\
+       https://www.jwz.org/xscreensaver/\n\
 \n");
 
   fflush (stdout);
@@ -294,14 +314,29 @@ xscreensaver %s, copyright (c) 1991-2008 by Jamie Zawinski <jwz@jwz.org>\n\
 }
 
 
+Bool in_signal_handler_p = 0;  /* I hate C so much... */
+
 char *
-timestring (void)
+timestring (time_t when)
 {
-  time_t now = time ((time_t *) 0);
-  char *str = (char *) ctime (&now);
-  char *nl = (char *) strchr (str, '\n');
-  if (nl) *nl = 0; /* take off that dang newline */
-  return str;
+  if (in_signal_handler_p)
+    {
+      /* Turns out that ctime() and even localtime_r() call malloc() on Linux!
+         So we can't call them from inside SIGCHLD.  WTF.
+       */
+      static char buf[30];
+      strcpy (buf, "... ... ..   signal ....");
+      return buf;
+    }
+  else
+    {
+      char *str, *nl;
+      if (! when) when = time ((time_t *) 0);
+      str = (char *) ctime (&when);
+      nl = (char *) strchr (str, '\n');
+      if (nl) *nl = 0; /* take off that dang newline */
+      return str;
+    }
 }
 
 static Bool blurb_timestamp_p = True;   /* kludge */
@@ -314,7 +349,7 @@ blurb (void)
   else
     {
       static char buf[255];
-      char *ct = timestring();
+      char *ct = timestring(0);
       int n = strlen(progname);
       if (n > 100) n = 99;
       strncpy(buf, progname, n);
@@ -390,7 +425,7 @@ saver_ehandler (Display *dpy, XErrorEvent *error)
    "    won't work.  A \"log.txt\" file will also be written.  Please *do*\n"
    "    include the complete \"log.txt\" file with your bug report.\n"
    "\n"
-   "    http://www.jwz.org/xscreensaver/bugs.html explains how to create\n"
+   "    https://www.jwz.org/xscreensaver/bugs.html explains how to create\n"
    "    the most useful bug reports, and how to examine core files.\n"
    "\n"
    "    The more information you can provide, the better.  But please\n"
@@ -460,8 +495,8 @@ startup_ehandler (String name, String type, String class,
     }
 
   fprintf (stderr, "\n"
-          "              http://www.jwz.org/xscreensaver/faq.html\n"
-          "              http://www.jwz.org/xscreensaver/man.html\n"
+          "              https://www.jwz.org/xscreensaver/faq.html\n"
+          "              https://www.jwz.org/xscreensaver/man.html\n"
           "\n");
 
   fflush (stderr);
@@ -495,7 +530,7 @@ set_version_string (saver_info *si, int *argc, char **argv)
       *s = '_';
   }
 
-  si->version = (char *) malloc (5);
+  si->version = (char *) malloc (32);
   memcpy (si->version, screensaver_id + 17, 4);
   si->version [4] = 0;
 }
@@ -574,6 +609,19 @@ lock_initialization (saver_info *si, int *argc, char **argv)
         }
     }
 
+  /* Like MacOS, locking under Wayland's embedded X11 server does not work.
+     (X11 grabs don't work because the Wayland window manager lives at a
+     higher level than the X11 emulation layer.)
+   */
+  if (!si->locking_disabled_p && getenv ("WAYLAND_DISPLAY"))
+    {
+      si->locking_disabled_p = True;
+      si->nolock_reason = "Cannot lock securely under Wayland";
+    }
+
+  if (si->prefs.debug_p)    /* But allow locking anyway in debug mode. */
+    si->locking_disabled_p = False;
+
 #endif /* NO_LOCKING */
 }
 
@@ -630,6 +678,7 @@ connect_to_server (saver_info *si, int *argc, char **argv)
   XA_XSETROOT_ID = XInternAtom (si->dpy, "_XSETROOT_ID", False);
   XA_ESETROOT_PMAP_ID = XInternAtom (si->dpy, "ESETROOT_PMAP_ID", False);
   XA_XROOTPMAP_ID = XInternAtom (si->dpy, "_XROOTPMAP_ID", False);
+  XA_NET_WM_USER_TIME = XInternAtom (si->dpy, "_NET_WM_USER_TIME", False);
   XA_ACTIVATE = XInternAtom (si->dpy, "ACTIVATE", False);
   XA_DEACTIVATE = XInternAtom (si->dpy, "DEACTIVATE", False);
   XA_RESTART = XInternAtom (si->dpy, "RESTART", False);
@@ -701,7 +750,7 @@ process_command_line (saver_info *si, int *argc, char **argv)
     You control a running xscreensaver process by sending it messages\n\
     with `xscreensaver-demo' or `xscreensaver-command'.\n\
 .   See the man pages for details, or check the web page:\n\
-    http://www.jwz.org/xscreensaver/\n\n");
+    https://www.jwz.org/xscreensaver/\n\n");
            }
 
          exit (1);
@@ -718,6 +767,13 @@ print_banner (saver_info *si)
 {
   saver_preferences *p = &si->prefs;
 
+  char *s, year[5];
+  s = strchr (screensaver_id, '-');
+  s = strrchr (s, '-');
+  s++;
+  strncpy (year, s, 4);
+  year[4] = 0;
+
   /* This resource gets set some time before the others, so that we know
      whether to print the banner (and so that the banner gets printed before
      any resource-database-related error messages.)
@@ -730,9 +786,9 @@ print_banner (saver_info *si)
 
   if (p->verbose_p)
     fprintf (stderr,
-            "%s %s, copyright (c) 1991-2008 "
+            "%s %s, copyright (c) 1991-%s "
             "by Jamie Zawinski <jwz@jwz.org>.\n",
-            progname, si->version);
+            progname, si->version, year);
 
   if (p->debug_p)
     fprintf (stderr, "\n"
@@ -748,6 +804,17 @@ print_banner (saver_info *si)
             "\n",
             blurb());
 
+  if (p->verbose_p && decrepit_p ())
+    fprintf (stderr, "\n"
+             "*************************************"
+             "**************************************\n"
+            "%s: Warning: this version of xscreensaver is VERY OLD!\n"
+            "%s: Please upgrade!  https://www.jwz.org/xscreensaver/\n"
+             "*************************************"
+             "**************************************\n"
+            "\n",
+             blurb(), blurb());
+
   if (p->verbose_p)
     {
       if (!si->uid_message || !*si->uid_message)
@@ -857,12 +924,14 @@ initialize_server_extensions (saver_info *si)
   Bool server_has_sgi_saver_extension_p = False;
   Bool server_has_mit_saver_extension_p = False;
   Bool system_has_proc_interrupts_p = False;
+  Bool server_has_xinput_extension_p = False;
   const char *piwhy = 0;
 
   si->using_xidle_extension = p->use_xidle_extension;
   si->using_sgi_saver_extension = p->use_sgi_saver_extension;
   si->using_mit_saver_extension = p->use_mit_saver_extension;
   si->using_proc_interrupts = p->use_proc_interrupts;
+  si->using_xinput_extension = p->use_xinput_extension;
 
 #ifdef HAVE_XIDLE_EXTENSION
   {
@@ -886,6 +955,10 @@ initialize_server_extensions (saver_info *si)
   system_has_proc_interrupts_p = query_proc_interrupts_available (si, &piwhy);
 #endif
 
+#ifdef HAVE_XINPUT
+  server_has_xinput_extension_p = query_xinput_extension (si);
+#endif
+
   if (!server_has_xidle_extension_p)
     si->using_xidle_extension = False;
   else if (p->verbose_p)
@@ -928,6 +1001,8 @@ initialize_server_extensions (saver_info *si)
       int nscreens = ScreenCount (si->dpy);  /* number of *real* screens */
       int i;
 
+      si->using_randr_extension = TRUE;
+
       if (p->verbose_p)
        fprintf (stderr, "%s: selecting RANDR events\n", blurb());
       for (i = 0; i < nscreens; i++)
@@ -940,6 +1015,28 @@ initialize_server_extensions (saver_info *si)
     }
 # endif /* HAVE_RANDR */
 
+#ifdef HAVE_XINPUT
+  if (!server_has_xinput_extension_p)
+    si->using_xinput_extension = False;
+  else
+    {
+      if (si->using_xinput_extension)
+        init_xinput_extension(si);
+
+      if (p->verbose_p)
+        {
+          if (si->using_xinput_extension)
+            fprintf (stderr,
+                     "%s: selecting events from %d XInputExtension devices.\n",
+                     blurb(), si->num_xinput_devices);
+          else
+            fprintf (stderr,
+                     "%s: not using XInputExtension.\n",
+                     blurb());
+        }
+    }
+#endif
+
   if (!system_has_proc_interrupts_p)
     {
       si->using_proc_interrupts = False;
@@ -1061,6 +1158,7 @@ maybe_reload_init_file (saver_info *si)
       sync_server_dpms_settings (si->dpy,
                                  (p->dpms_enabled_p  &&
                                   p->mode != DONT_BLANK),
+                                 p->dpms_quickoff_p,
                                  p->dpms_standby / 1000,
                                  p->dpms_suspend / 1000,
                                  p->dpms_off / 1000,
@@ -1100,10 +1198,10 @@ main_loop (saver_info *si)
        {
          if (si->demoing_p)
            fprintf (stderr, "%s: demoing %d at %s.\n", blurb(),
-                    si->selection_mode, timestring());
+                    si->selection_mode, timestring(0));
          else
             fprintf (stderr, "%s: blanking screen at %s.\n", blurb(),
-                     timestring());
+                     timestring(0));
        }
 
       maybe_reload_init_file (si);
@@ -1112,7 +1210,7 @@ main_loop (saver_info *si)
         {
           if (p->verbose_p)
             fprintf (stderr, "%s: idle with blanking disabled at %s.\n",
-                     blurb(), timestring());
+                     blurb(), timestring(0));
 
           /* Go around the loop and wait for the next bout of idleness,
              or for the init file to change, or for a remote command to
@@ -1153,6 +1251,10 @@ main_loop (saver_info *si)
              we would never be able to un-blank it!  We would never
              see any events, and the display would be wedged.
 
+             In particular, without that keyboard grab, we will be
+             unable to ever read keypresses on the unlock dialog.
+             You can't unlock if you can't type your password.
+
              So, just go around the loop again and wait for the
              next bout of idleness.  (If the user remains idle, we
              will next try to blank the screen again in no more than
@@ -1174,6 +1276,12 @@ main_loop (saver_info *si)
                   "%s: unable to grab keyboard or mouse!  Blanking aborted.\n",
                        blurb());
 
+              /* Since we were unable to blank, clearly we're not locked,
+                 but we might have been prematurely marked as locked by
+                 the LOCK ClientMessage. */
+              if (si->locked_p)
+                set_locked_p (si, False);
+
               schedule_wakeup_event (si, retry, p->debug_p);
               continue;
             }
@@ -1189,6 +1297,20 @@ main_loop (saver_info *si)
         for (i = 0; i < si->nscreens; i++)
           spawn_screenhack (&si->screens[i]);
 
+      /* If we are blanking only, optionally power down monitor right now. */
+      if (p->mode == BLANK_ONLY &&
+          p->dpms_enabled_p && 
+          p->dpms_quickoff_p)
+        {
+          sync_server_dpms_settings (si->dpy, True,
+                                     p->dpms_quickoff_p,
+                                     p->dpms_standby / 1000,
+                                     p->dpms_suspend / 1000,
+                                     p->dpms_off / 1000,
+                                     False);
+          monitor_power_on (si, False);
+        }
+
       /* Don't start the cycle timer in demo mode. */
       if (!si->demoing_p && p->cycle)
        si->cycle_id = XtAppAddTimeOut (si->app,
@@ -1290,7 +1412,7 @@ main_loop (saver_info *si)
 
       if (p->verbose_p)
        fprintf (stderr, "%s: unblanking screen at %s.\n",
-                blurb(), timestring ());
+                blurb(), timestring (0));
 
       /* Kill before unblanking, to stop drawing as soon as possible. */
       for (i = 0; i < si->nscreens; i++)
@@ -1344,6 +1466,33 @@ main (int argc, char **argv)
   struct passwd *spasswd;
   int i;
 
+  /* It turns out that if we do setlocale (LC_ALL, "") here, people
+     running in Japanese locales get font craziness on the password
+     dialog, presumably because it is displaying Japanese characters
+     in a non-Japanese font.  However, if we don't call setlocale()
+     at all, then XLookupString() never returns multi-byte UTF-8
+     characters when people type non-Latin1 characters on the
+     keyboard.
+
+     The current theory (and at this point, I'm really guessing!) is
+     that using LC_CTYPE instead of LC_ALL will make XLookupString()
+     behave usefully, without having the side-effect of screwing up
+     the fonts on the unlock dialog.
+
+     See https://bugs.launchpad.net/ubuntu/+source/xscreensaver/+bug/671923
+     from comment #20 onward.
+
+       -- jwz, 24-Sep-2011
+   */
+#ifdef ENABLE_NLS
+  if (!setlocale (LC_CTYPE, ""))
+    fprintf (stderr, "%s: warning: could not set default locale\n",
+             progname);
+
+  bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+  textdomain (GETTEXT_PACKAGE);
+#endif /* ENABLE_NLS */
+
   memset(si, 0, sizeof(*si));
   global_si_kludge = si;       /* I hate C so much... */
 
@@ -1403,7 +1552,7 @@ main (int argc, char **argv)
   if (p->verbose_p) analyze_display (si);
   initialize_server_extensions (si);
 
-  si->blank_time = time ((time_t) 0); /* must be before ..._window */
+  si->blank_time = time ((time_t *) 0); /* must be before ..._window */
   initialize_screensaver_window (si);
 
   select_events (si);
@@ -1413,6 +1562,7 @@ main (int argc, char **argv)
   sync_server_dpms_settings (si->dpy,
                              (p->dpms_enabled_p  &&
                               p->mode != DONT_BLANK),
+                             p->dpms_quickoff_p,
                              p->dpms_standby / 1000,
                              p->dpms_suspend / 1000,
                              p->dpms_off / 1000,
@@ -1677,29 +1827,47 @@ handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p)
     }
   else if (type == XA_DEACTIVATE)
     {
-      if (! until_idle_p)
-       {
-          if (si->throttled_p && p->verbose_p)
-            fprintf (stderr, "%s: unthrottled.\n", blurb());
-         si->throttled_p = False;
+# if 0
+      /* When -deactivate is received while locked, pop up the dialog box
+         instead of just ignoring it.  Some people depend on this behavior
+         to be able to unlock by using e.g. a fingerprint reader without
+         also having to click the mouse first.
+       */
+      if (si->locked_p) 
+        {
+          clientmessage_response(si, window, False,
+              "DEACTIVATE ClientMessage received while locked: ignored.",
+              "screen is locked.");
+        }
+      else
+# endif /* 0 */
+        {
+          if (! until_idle_p)
+            {
+              if (si->throttled_p && p->verbose_p)
+                fprintf (stderr, "%s: unthrottled.\n", blurb());
+              si->throttled_p = False;
 
-         clientmessage_response(si, window, False,
-                                "DEACTIVATE ClientMessage received.",
-                                "deactivating.");
-         if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
-           {
-             XForceScreenSaver (si->dpy, ScreenSaverReset);
-             return False;
-           }
-         else
-           {
-             return True;
-           }
-       }
-      clientmessage_response(si, window, False,
-     "ClientMessage DEACTIVATE received while inactive: resetting idle timer.",
-                            "not active: idle timer reset.");
-      reset_timers (si);
+              clientmessage_response(si, window, False,
+                                     "DEACTIVATE ClientMessage received.",
+                                     "deactivating.");
+              if (si->using_mit_saver_extension ||
+                  si->using_sgi_saver_extension)
+                {
+                  XForceScreenSaver (si->dpy, ScreenSaverReset);
+                  return False;
+                }
+              else
+                {
+                  return True;
+                }
+            }
+          clientmessage_response(si, window, False,
+                          "ClientMessage DEACTIVATE received while inactive: "
+                          "resetting idle timer.",
+                                 "not active: idle timer reset.");
+          reset_timers (si);
+        }
     }
   else if (type == XA_CYCLE)
     {
@@ -1902,7 +2070,16 @@ handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p)
                            : "locking.");
          sprintf (buf, "LOCK ClientMessage received; %s", response);
          clientmessage_response (si, window, False, buf, response);
+
+          /* Note that this leaves things in a slightly inconsistent state:
+             we are blanked but not locked.  And blanking might actually
+             fail if we can't get the grab. */
          set_locked_p (si, True);
+
+           /* Have to set the time or xscreensaver-command doesn't
+              report the LOCK state change. */
+           si->blank_time = time ((time_t *) 0);
+
          si->selection_mode = 0;
          si->demoing_p = False;
 
@@ -2124,8 +2301,14 @@ analyze_display (saver_info *si)
 #     endif
    }, { "DRI",                                 "DRI",
         True,  0
+   }, { "NV-CONTROL",                           "NVidia",
+        True,  0
+   }, { "NV-GLX",                               "NVidia GLX",
+        True,  0
    }, { "Apple-DRI",                            "Apple-DRI (XDarwin)",
         True,  0
+   }, { "XInputExtension",                      "XInput",
+        True,  0
    },
   };
 
@@ -2141,7 +2324,6 @@ analyze_display (saver_info *si)
       char buf [255];
       int maj = 0, min = 0;
       int dummy1, dummy2, dummy3;
-      int j;
 
       /* Most of the extension version functions take 3 args,
          writing results into args 2 and 3, but some take more.
@@ -2154,7 +2336,6 @@ analyze_display (saver_info *si)
       if (!XQueryExtension (si->dpy, exts[i].name, &op, &event, &error))
         continue;
       sprintf (buf, "%s:   ", blurb());
-      j = strlen (buf);
       strcat (buf, exts[i].desc);
 
       if (!version_fn_2)
@@ -2182,11 +2363,13 @@ analyze_display (saver_info *si)
       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++)
+      for (j = 0; j < out_count; j++) {
+       if (vi_out[j].depth >= 32) continue;
        if (vi_out[j].class == PseudoColor)
          colormapped_depths |= (1 << vi_out[j].depth);
        else
          non_mapped_depths  |= (1 << vi_out[j].depth);
+       }
       XFree ((char *) vi_out);
 
       if (colormapped_depths)