From http://www.jwz.org/xscreensaver/xscreensaver-5.30.tar.gz
[xscreensaver] / driver / lock.c
index bba326b0be97f10e1e4568ac9e13e0fa9b715bad..7c92be60d6375bda6dbb69c47191d1ab833919ef 100644 (file)
@@ -1,5 +1,5 @@
 /* lock.c --- handling the password dialog for locking-mode.
- * xscreensaver, Copyright (c) 1993-2007 Jamie Zawinski <jwz@jwz.org>
+ * xscreensaver, Copyright (c) 1993-2014 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
 
 #ifndef NO_LOCKING              /* (mostly) whole file */
 
-#ifdef HAVE_SYSLOG
-# include <syslog.h>
-#endif /* HAVE_SYSLOG */
-
 #ifdef HAVE_XHPDISABLERESET
 # include <X11/XHPlib.h>
   static void hp_lock_reset (saver_info *si, Bool lock_p);
@@ -49,6 +45,9 @@
   static void xfree_lock_grab_smasher (saver_info *si, Bool lock_p);
 #endif /* HAVE_XF86MISCSETGRABKEYSSTATE */
 
+#ifdef HAVE_RANDR
+# include <X11/extensions/Xrandr.h>
+#endif /* HAVE_RANDR */
 
 #ifdef _VROOT_H_
 ERROR!  You must not include vroot.h in this file.
@@ -84,12 +83,23 @@ vms_passwd_valid_p(char *pw, Bool verbose_p)
 
 typedef struct info_dialog_data info_dialog_data;
 
+
+#define MAX_BYTES_PER_CHAR 8   /* UTF-8 uses no more than 3, I think */
+#define MAX_PASSWD_CHARS   128 /* Longest possible passphrase */
+
 struct passwd_dialog_data {
 
   saver_screen_info *prompt_screen;
   int previous_mouse_x, previous_mouse_y;
 
-  char typed_passwd [80];
+  /* "Characters" in the password may be a variable number of bytes long.
+     typed_passwd contains the raw bytes.
+     typed_passwd_char_size indicates the size in bytes of each character,
+     so that we can make backspace work.
+   */
+  char typed_passwd [MAX_PASSWD_CHARS * MAX_BYTES_PER_CHAR];
+  char typed_passwd_char_size [MAX_PASSWD_CHARS];
+  
   XtIntervalId timer;
   int i_beam;
 
@@ -112,6 +122,7 @@ struct passwd_dialog_data {
   char *date_label;
   char *passwd_string;
   Bool passwd_changed_p; /* Whether the user entry field needs redrawing */
+  Bool caps_p;          /* Whether we saw a keypress with caps-lock on */
   char *unlock_label;
   char *login_label;
   char *uname_label;
@@ -128,6 +139,7 @@ struct passwd_dialog_data {
 
   Pixel foreground;
   Pixel background;
+  Pixel border;
   Pixel passwd_foreground;
   Pixel passwd_background;
   Pixel thermo_foreground;
@@ -182,7 +194,7 @@ static void restore_background (saver_info *si);
 
 extern void xss_authenticate(saver_info *si, Bool verbose_p);
 
-static void
+static int
 new_passwd_window (saver_info *si)
 {
   passwd_dialog_data *pw;
@@ -193,7 +205,7 @@ new_passwd_window (saver_info *si)
 
   pw = (passwd_dialog_data *) calloc (1, sizeof(*pw));
   if (!pw)
-    return;
+    return -1;
 
   /* Display the button only if the "newLoginCommand" pref is non-null.
    */
@@ -203,9 +215,6 @@ new_passwd_window (saver_info *si)
   pw->passwd_cursor = XCreateFontCursor (si->dpy, XC_top_left_arrow);
 
   pw->prompt_screen = ssi;
-  if (si->prefs.verbose_p)
-    fprintf (stderr, "%s: %d: creating password dialog.\n",
-             blurb(), pw->prompt_screen->number);
 
   screen = pw->prompt_screen->screen;
   cmap = DefaultColormapOfScreen (screen);
@@ -311,6 +320,9 @@ new_passwd_window (saver_info *si)
   pw->background = get_pixel_resource (si->dpy, cmap,
                                       "passwd.background",
                                       "Dialog.Background" );
+  pw->border = get_pixel_resource (si->dpy, cmap,
+                                      "passwd.borderColor",
+                                      "Dialog.borderColor");
 
   if (pw->foreground == pw->background)
     {
@@ -333,10 +345,10 @@ new_passwd_window (saver_info *si)
                                               "Dialog.Button.Background" );
   pw->thermo_foreground = get_pixel_resource (si->dpy, cmap,
                                              "passwd.thermometer.foreground",
-                                             "Dialog.Thermometer.Foreground" );
+                                             "Dialog.Thermometer.Foreground");
   pw->thermo_background = get_pixel_resource ( si->dpy, cmap,
                                              "passwd.thermometer.background",
-                                             "Dialog.Thermometer.Background" );
+                                             "Dialog.Thermometer.Background");
   pw->shadow_top = get_pixel_resource ( si->dpy, cmap,
                                       "passwd.topShadowColor",
                                       "Dialog.Foreground" );
@@ -344,13 +356,16 @@ new_passwd_window (saver_info *si)
                                          "passwd.bottomShadowColor",
                                          "Dialog.Background" );
 
-  pw->preferred_logo_width = get_integer_resource (si->dpy, "passwd.logo.width",
+  pw->preferred_logo_width = get_integer_resource (si->dpy, 
+                                                   "passwd.logo.width",
                                                   "Dialog.Logo.Width");
-  pw->preferred_logo_height = get_integer_resource (si->dpy, "passwd.logo.height",
+  pw->preferred_logo_height = get_integer_resource (si->dpy,
+                                                    "passwd.logo.height",
                                                    "Dialog.Logo.Height");
   pw->thermo_width = get_integer_resource (si->dpy, "passwd.thermometer.width",
                                           "Dialog.Thermometer.Width");
-  pw->internal_border = get_integer_resource (si->dpy, "passwd.internalBorderWidth",
+  pw->internal_border = get_integer_resource (si->dpy,
+                                              "passwd.internalBorderWidth",
                                              "Dialog.InternalBorderWidth");
   pw->shadow_width = get_integer_resource (si->dpy, "passwd.shadowThickness",
                                           "Dialog.ShadowThickness");
@@ -389,10 +404,9 @@ new_passwd_window (saver_info *si)
   }
 
   /* Before mapping the window, save a pixmap of the current screen.
-     When we lower the window, we
-     restore these bits.  This works, because the running screenhack
-     has already been sent SIGSTOP, so we know nothing else is drawing
-     right now! */
+     When we lower the window, we restore these bits.  This works,
+     because the running screenhack has already been sent SIGSTOP, so
+     we know nothing else is drawing right now! */
   {
     XGCValues gcv;
     GC gc;
@@ -412,13 +426,17 @@ new_passwd_window (saver_info *si)
   }
 
   si->pw_data = pw;
+  return 0;
 }
 
 
+Bool debug_passwd_window_p = False;  /* used only by test-passwd.c */
+
+
 /**
  * info_msg and prompt may be NULL.
  */
-static void
+static int
 make_passwd_window (saver_info *si,
                    const char *info_msg,
                    const char *prompt,
@@ -434,18 +452,23 @@ make_passwd_window (saver_info *si,
 
   cleanup_passwd_window (si);
 
+  if (! ssi)   /* WTF?  Trying to prompt while no screens connected? */
+    return -1;
+
   if (!si->pw_data)
-    new_passwd_window (si);
+    if (new_passwd_window (si) < 0)
+      return -1;
 
   if (!(pw = si->pw_data))
-    return;
+    return -1;
 
   pw->ratio = 1.0;
 
   pw->prompt_screen = ssi;
   if (si->prefs.verbose_p)
-    fprintf (stderr, "%s: %d: creating password dialog.\n",
-             blurb(), pw->prompt_screen->number);
+    fprintf (stderr, "%s: %d: creating password dialog (\"%s\")\n",
+             blurb(), pw->prompt_screen->number,
+             info_msg ? info_msg : "");
 
   screen = pw->prompt_screen->screen;
   cmap = DefaultColormapOfScreen (screen);
@@ -464,6 +487,11 @@ make_passwd_window (saver_info *si,
    * room for the dialog to grow without going off the edge of the screen. */
   max_string_width_px *= 0.75;
 
+  if (!info_msg && senescent_p())
+    info_msg = ("\n"
+                "This version of XScreenSaver\n"
+                "is very old! Please upgrade!\n");
+
   pw->info_label = mlstring_new(info_msg ? info_msg : pw->body_label,
                                pw->label_font, max_string_width_px);
 
@@ -504,7 +532,8 @@ make_passwd_window (saver_info *si,
       h2 += ascent + descent;
 
       /* Measure the info_label. */
-      if (pw->info_label->overall_width > pw->width) pw->width = pw->info_label->overall_width;
+      if (pw->info_label->overall_width > pw->width)
+        pw->width = pw->info_label->overall_width;
        h2 += pw->info_label->overall_height;
 
       /* Measure the user string. */
@@ -529,9 +558,11 @@ make_passwd_window (saver_info *si,
 
          /* Measure the prompt_label. */
          max_string_width_px -= w3;
-         pw->prompt_label = mlstring_new(prompt, pw->label_font, max_string_width_px);
+         pw->prompt_label = mlstring_new (prompt, pw->label_font, 
+                                           max_string_width_px);
 
-         if (pw->prompt_label->overall_width > w2) w2 = pw->prompt_label->overall_width;
+         if (pw->prompt_label->overall_width > w2)
+            w2 = pw->prompt_label->overall_width;
 
          h2 += pw->prompt_label->overall_height;
 
@@ -587,7 +618,9 @@ make_passwd_window (saver_info *si,
 
          /* Use (2 * shadow_width) spacing between the buttons. Another
             (2 * shadow_width) is required to account for button shadows. */
-         w2 = MAX (w2, button_w + pw->unlock_button_width + (pw->shadow_width * 4));
+         w2 = MAX (w2, 
+                    button_w + pw->unlock_button_width +
+                    (pw->shadow_width * 4));
         }
 
       if (w2 > pw->width)  pw->width  = w2;
@@ -611,6 +644,9 @@ make_passwd_window (saver_info *si,
 
   attrmask |= CWOverrideRedirect; attrs.override_redirect = True;
 
+  if (debug_passwd_window_p)
+    attrs.override_redirect = False;  /* kludge for test-passwd.c */
+
   attrmask |= CWEventMask;
   attrs.event_mask = (ExposureMask | KeyPressMask |
                       ButtonPressMask | ButtonReleaseMask);
@@ -619,10 +655,11 @@ make_passwd_window (saver_info *si,
      actually be visible; this takes into account virtual viewports as
      well as Xinerama. */
   {
-    int x, y, w, h;
-    get_screen_viewport (pw->prompt_screen, &x, &y, &w, &h,
-                         pw->previous_mouse_x, pw->previous_mouse_y,
-                         si->prefs.verbose_p);
+    saver_screen_info *ssi = &si->screens [mouse_screen (si)];
+    int x = ssi->x;
+    int y = ssi->y;
+    int w = ssi->width;
+    int h = ssi->height;
     if (si->prefs.debug_p) w /= 2;
     pw->x = x + ((w + pw->width) / 2) - pw->width;
     pw->y = y + ((h + pw->height) / 2) - pw->height;
@@ -644,6 +681,7 @@ make_passwd_window (saver_info *si,
                       DefaultVisualOfScreen(screen),
                       attrmask, &attrs);
       XSetWindowBackground (si->dpy, si->passwd_dialog, pw->background);
+      XSetWindowBorder (si->dpy, si->passwd_dialog, pw->border);
 
       /* We use the default visual, not ssi->visual, so that the logo pixmap's
         visual matches that of the si->passwd_dialog window. */
@@ -683,6 +721,8 @@ make_passwd_window (saver_info *si,
   if (cmap)
     XInstallColormap (si->dpy, cmap);
   draw_passwd_window (si);
+
+  return 0;
 }
 
 
@@ -713,7 +753,7 @@ draw_passwd_window (saver_info *si)
             pw->date_font->ascent + pw->date_font->descent);
 
   if ((strlen(pw->uname_label)) && pw->show_uname_p)
-    height += (pw->uname_font->ascent + pw->uname_font->descent); /* for uname */
+    height += (pw->uname_font->ascent + pw->uname_font->descent);
 
   height += ((pw->button_font->ascent + pw->button_font->descent) * 2 +
              2 * pw->shadow_width);
@@ -814,7 +854,7 @@ draw_passwd_window (saver_info *si)
    */
   if (pw->prompt_label)
     {
-      y1 += (spacing + pw->prompt_label->overall_height + pw->shadow_width * 2);
+      y1 += (spacing + pw->prompt_label->overall_height + pw->shadow_width*2);
 
       pw->passwd_field_x = x2 - pw->shadow_width;
       pw->passwd_field_y = y1 - (pw->passwd_font->ascent +
@@ -836,7 +876,7 @@ draw_passwd_window (saver_info *si)
 
   if (pw->prompt_label)
     {
-      y1 += (spacing + pw->prompt_label->overall_height + pw->shadow_width * 2);
+      y1 += (spacing + pw->prompt_label->overall_height + pw->shadow_width*2);
       draw_shaded_rectangle (si->dpy, si->passwd_dialog,
                             x1, y1, x2, y2,
                             pw->shadow_width,
@@ -915,7 +955,9 @@ draw_passwd_window (saver_info *si)
       XSetForeground (si->dpy, gc1, pw->foreground);
       XSetBackground (si->dpy, gc1, pw->background);
       XSetClipMask (si->dpy, gc1, pw->logo_clipmask);
-      XSetClipOrigin (si->dpy, gc1, x1 + ((x2 - (int)w) / 2), y1 + ((y2 - (int)h) / 2));
+      XSetClipOrigin (si->dpy, gc1, 
+                      x1 + ((x2 - (int)w) / 2), 
+                      y1 + ((y2 - (int)h) / 2));
       if (d == 1)
         XCopyPlane (si->dpy, pw->logo_pixmap, si->passwd_dialog, gc1,
                     0, 0, w, h,
@@ -1076,9 +1118,10 @@ update_passwd_window (saver_info *si, const char *printed_passwd, float ratio)
              pw->user_entry_pixmap = 0;
            }
 
-         pw->user_entry_pixmap = XCreatePixmap(si->dpy, si->passwd_dialog,
-             rects[0].width, rects[0].height, pw->prompt_screen->current_depth);
-
+         pw->user_entry_pixmap = 
+            XCreatePixmap (si->dpy, si->passwd_dialog,
+                           rects[0].width, rects[0].height, 
+                           DefaultDepthOfScreen (pw->prompt_screen->screen));
 
          XFillRectangle (si->dpy, pw->user_entry_pixmap, gc2,
                          0, 0, rects[0].width, rects[0].height);
@@ -1113,7 +1156,8 @@ update_passwd_window (saver_info *si, const char *printed_passwd, float ratio)
            x = rects[0].x + rects[0].width - 1;
          XDrawLine (si->dpy, si->passwd_dialog, gc1, 
                     x, y,
-                    x, y + pw->passwd_font->ascent + pw->passwd_font->descent-1);
+                    x, y + pw->passwd_font->ascent + 
+                     pw->passwd_font->descent-1);
        }
 
       pw->i_beam = (pw->i_beam + 1) % 4;
@@ -1150,8 +1194,10 @@ update_passwd_window (saver_info *si, const char *printed_passwd, float ratio)
                  pw->unlock_button_x, pw->unlock_button_y,
                  pw->unlock_button_width, pw->unlock_button_height,
                  pw->shadow_width,
-                 (pw->unlock_button_down_p ? pw->shadow_bottom : pw->shadow_top),
-                 (pw->unlock_button_down_p ? pw->shadow_top : pw->shadow_bottom),
+                 (pw->unlock_button_down_p ? pw->shadow_bottom :
+                   pw->shadow_top),
+                 (pw->unlock_button_down_p ? pw->shadow_top :
+                   pw->shadow_bottom),
                  pw->unlock_button_down_p);
 
       /* The "New Login" button
@@ -1227,6 +1273,7 @@ cleanup_passwd_window (saver_info *si)
     }
 
   memset (pw->typed_passwd, 0, sizeof(pw->typed_passwd));
+  memset (pw->typed_passwd_char_size, 0, sizeof(pw->typed_passwd_char_size));
   memset (pw->passwd_string, 0, strlen(pw->passwd_string));
 
   if (pw->timer)
@@ -1281,13 +1328,26 @@ destroy_passwd_window (saver_info *si)
   XWarpPointer (si->dpy, None, RootWindowOfScreen (ssi->screen),
                 0, 0, 0, 0,
                 pw->previous_mouse_x, pw->previous_mouse_y);
+  XSync (si->dpy, False);
 
   while (XCheckMaskEvent (si->dpy, PointerMotionMask, &event))
     if (p->verbose_p)
       fprintf (stderr, "%s: discarding MotionNotify event.\n", blurb());
 
+#ifdef HAVE_XINPUT
+  if (si->using_xinput_extension && si->xinput_DeviceMotionNotify)
+    while (XCheckTypedEvent (si->dpy, si->xinput_DeviceMotionNotify, &event))
+      if (p->verbose_p)
+       fprintf (stderr, "%s: discarding DeviceMotionNotify event.\n",
+                 blurb());
+#endif
+
   if (si->passwd_dialog)
     {
+      if (si->prefs.verbose_p)
+        fprintf (stderr, "%s: %d: destroying password dialog.\n",
+                 blurb(), pw->prompt_screen->number);
+
       XDestroyWindow (si->dpy, si->passwd_dialog);
       si->passwd_dialog = 0;
     }
@@ -1362,6 +1422,8 @@ destroy_passwd_window (saver_info *si)
 }
 
 
+#if defined(HAVE_XF86MISCSETGRABKEYSSTATE) || defined(HAVE_XF86VMODE)
+
 static Bool error_handler_hit_p = False;
 
 static int
@@ -1371,6 +1433,8 @@ ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
   return 0;
 }
 
+#endif /* HAVE_XF86MISCSETGRABKEYSSTATE || HAVE_XF86VMODE */
+
 
 #ifdef HAVE_XHPDISABLERESET
 /* This function enables and disables the C-Sh-Reset hot-key, which
@@ -1418,8 +1482,12 @@ xfree_lock_grab_smasher (saver_info *si, Bool lock_p)
 {
   saver_preferences *p = &si->prefs;
   int status;
-
+  int event, error;
   XErrorHandler old_handler;
+
+  if (!XF86MiscQueryExtension(si->dpy, &event, &error))
+    return;
+
   XSync (si->dpy, False);
   error_handler_hit_p = False;
   old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
@@ -1463,6 +1531,7 @@ xfree_lock_mode_switch (saver_info *si, Bool lock_p)
   static Bool any_mode_locked_p = False;
   saver_preferences *p = &si->prefs;
   int screen;
+  int real_nscreens = ScreenCount (si->dpy);
   int event, error;
   Bool status;
   XErrorHandler old_handler;
@@ -1472,7 +1541,7 @@ xfree_lock_mode_switch (saver_info *si, Bool lock_p)
   if (!XF86VidModeQueryExtension (si->dpy, &event, &error))
     return;
 
-  for (screen = 0; screen < (si->xinerama_p ? 1 : si->nscreens); screen++)
+  for (screen = 0; screen < real_nscreens; screen++)
     {
       XSync (si->dpy, False);
       old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
@@ -1509,12 +1578,13 @@ undo_vp_motion (saver_info *si)
 #ifdef HAVE_XF86VMODE
   saver_preferences *p = &si->prefs;
   int screen;
+  int real_nscreens = ScreenCount (si->dpy);
   int event, error;
 
   if (!XF86VidModeQueryExtension (si->dpy, &event, &error))
     return;
 
-  for (screen = 0; screen < si->nscreens; screen++)
+  for (screen = 0; screen < real_nscreens; screen++)
     {
       saver_screen_info *ssi = &si->screens[screen];
       int x, y;
@@ -1667,85 +1737,129 @@ static void
 handle_passwd_key (saver_info *si, XKeyEvent *event)
 {
   passwd_dialog_data *pw = si->pw_data;
-  int pw_size = sizeof (pw->typed_passwd) - 1;
-  char *typed_passwd = pw->typed_passwd;
-  char s[2];
-  char *stars = 0;
-  int i;
-  int size = XLookupString (event, s, 1, 0, compose_status);
+  unsigned char decoded [MAX_BYTES_PER_CHAR * 10]; /* leave some slack */
+  KeySym keysym = 0;
 
-  if (size != 1) return;
+  /* XLookupString may return more than one character via XRebindKeysym;
+     and on some systems it returns multi-byte UTF-8 characters (contrary
+     to its documentation, which says it returns only Latin1.)
 
-  s[1] = 0;
+     It seems to only do so, however, if setlocale() has been called.
+     See the code inside ENABLE_NLS in xscreensaver.c.
+   */
+  int decoded_size = XLookupString (event, (char *)decoded, sizeof(decoded),
+                                    &keysym, compose_status);
 
+#if 0
+  {
+    const char *ks = XKeysymToString (keysym);
+    int i;
+    fprintf(stderr, "## %-12s\t=> %d\t", (ks ? ks : "(null)"), decoded_size);
+    for (i = 0; i < decoded_size; i++)
+      fprintf(stderr, "%c", decoded[i]);
+    fprintf(stderr, "\t");
+    for (i = 0; i < decoded_size; i++)
+      fprintf(stderr, "\\%03o", ((unsigned char *)decoded)[i]);
+    fprintf(stderr, "\n");
+  }
+#endif
+
+  if (decoded_size > MAX_BYTES_PER_CHAR)
+    {
+      /* The multi-byte character returned is too large. */
+      XBell (si->dpy, 0);
+      return;
+    }
+
+  decoded[decoded_size] = 0;
   pw->passwd_changed_p = True;
 
   /* Add 10% to the time remaining every time a key is pressed. */
   pw->ratio += 0.1;
   if (pw->ratio > 1) pw->ratio = 1;
 
-  switch (*s)
+  if (decoded_size == 1)               /* Handle single-char commands */
     {
-    case '\010': case '\177':                          /* Backspace */
-      if (!*typed_passwd)
-       XBell (si->dpy, 0);
-      else
-       typed_passwd [strlen(typed_passwd)-1] = 0;
-      break;
-
-    case '\025': case '\030':                          /* Erase line */
-      memset (typed_passwd, 0, pw_size);
-      break;
-
-    case '\012': case '\015':                          /* Enter */
-      finished_typing_passwd(si, pw);
-      break;
-
-    case '\033':                                       /* Escape */
-      si->unlock_state = ul_cancel;
-      break;
-
-    default:
-      /* Though technically the only illegal characters in Unix passwords
-         are LF and NUL, most GUI programs (e.g., GDM) use regular text-entry
-         fields that only let you type printable characters.  So, people
-         who use funky characters in their passwords are already broken.
-         We follow that precedent.
-       */
-      if (isprint ((unsigned char) *s))
+      switch (*decoded)
         {
-          i = strlen (typed_passwd);
-          if (i >= pw_size-1)
+        case '\010': case '\177':                      /* Backspace */
+          {
+            /* kludgey way to get the number of "logical" characters. */
+            int nchars = strlen (pw->typed_passwd_char_size);
+            int nbytes = strlen (pw->typed_passwd);
+            if (nbytes <= 0)
+              XBell (si->dpy, 0);
+            else
+              {
+                int i;
+                for (i = pw->typed_passwd_char_size[nchars-1]; i >= 0; i--)
+                  {
+                    if (nbytes < 0) abort();
+                    pw->typed_passwd[nbytes--] = 0;
+                  }
+                pw->typed_passwd_char_size[nchars-1] = 0;
+              }
+          }
+          break;
+
+        case '\012': case '\015':                      /* Enter */
+          finished_typing_passwd (si, pw);
+          break;
+
+        case '\033':                                   /* Escape */
+          si->unlock_state = ul_cancel;
+          break;
+
+        case '\025': case '\030':                      /* Erase line */
+          memset (pw->typed_passwd, 0, sizeof (pw->typed_passwd));
+          memset (pw->typed_passwd_char_size, 0, 
+                  sizeof (pw->typed_passwd_char_size));
+          break;
+
+        default:
+          if (*decoded < ' ' && *decoded != '\t')      /* Other ctrl char */
             XBell (si->dpy, 0);
           else
-            {
-              typed_passwd [i] = *s;
-              typed_passwd [i+1] = 0;
-            }
+            goto SELF_INSERT;
+          break;
         }
-      else
+    }
+  else
+    {
+      int nbytes, nchars;
+    SELF_INSERT:
+      nbytes = strlen (pw->typed_passwd);
+      nchars = strlen (pw->typed_passwd_char_size);
+      if (nchars + 1 >= sizeof (pw->typed_passwd_char_size)-1 ||
+          nbytes + decoded_size >= sizeof (pw->typed_passwd)-1)  /* overflow */
         XBell (si->dpy, 0);
-      break;
+      else
+        {
+          pw->typed_passwd_char_size[nchars] = decoded_size;
+          pw->typed_passwd_char_size[nchars+1] = 0;
+          memcpy (pw->typed_passwd + nbytes, decoded, decoded_size);
+          pw->typed_passwd[nbytes + decoded_size] = 0;
+        }
     }
 
   if (pw->echo_input)
     {
-      /* If the input is wider than the text box, only show the last portion.
-       * This simulates a horizontally scrolling text field. */
+      /* If the input is wider than the text box, only show the last portion,
+         to simulate a horizontally-scrolling text field. */
       int chars_in_pwfield = (pw->passwd_field_width /
                              pw->passwd_font->max_bounds.width);
-
-      if (strlen(typed_passwd) > chars_in_pwfield)
-       typed_passwd += (strlen(typed_passwd) - chars_in_pwfield);
-
-      update_passwd_window(si, typed_passwd, pw->ratio);
+      const char *output = pw->typed_passwd;
+      if (strlen(output) > chars_in_pwfield)
+       output += (strlen(output) - chars_in_pwfield);
+      update_passwd_window (si, output, pw->ratio);
     }
   else if (pw->show_stars_p)
     {
-      i = strlen(typed_passwd);
-      stars = (char *) malloc(i+1);
-      memset (stars, '*', i);
-      stars[i] = 0;
+      int nchars = strlen (pw->typed_passwd_char_size);
+      char *stars = 0;
+      stars = (char *) malloc(nchars + 1);
+      memset (stars, '*', nchars);
+      stars[nchars] = 0;
       update_passwd_window (si, stars, pw->ratio);
       free (stars);
     }
@@ -1761,31 +1875,78 @@ passwd_event_loop (saver_info *si)
 {
   saver_preferences *p = &si->prefs;
   char *msg = 0;
-  XEvent event;
-  unsigned int caps_p = 0;
+
+  /* We have to go through this union bullshit because gcc-4.4.0 has
+     stricter struct-aliasing rules.  Without this, the optimizer
+     can fuck things up.
+   */
+  union {
+    XEvent x_event;
+# ifdef HAVE_RANDR
+    XRRScreenChangeNotifyEvent xrr_event;
+# endif /* HAVE_RANDR */
+  } event;
 
   passwd_animate_timer ((XtPointer) si, 0);
+  reset_watchdog_timer (si, False);    /* Disable watchdog while dialog up */
 
   while (si->unlock_state == ul_read)
     {
-      XtAppNextEvent (si->app, &event);
-      if (event.xany.window == si->passwd_dialog && event.xany.type == Expose)
+      XtAppNextEvent (si->app, &event.x_event);
+
+#ifdef HAVE_RANDR
+      if (si->using_randr_extension &&
+          (event.x_event.type == 
+           (si->randr_event_number + RRScreenChangeNotify)))
+        {
+          /* The Resize and Rotate extension sends an event when the
+             size, rotation, or refresh rate of any screen has changed. */
+
+          if (p->verbose_p)
+            {
+              /* XRRRootToScreen is in Xrandr.h 1.4, 2001/06/07 */
+              int screen = XRRRootToScreen(si->dpy, event.xrr_event.window);
+                fprintf (stderr, "%s: %d: screen change event received\n",
+                         blurb(), screen);
+            }
+
+#ifdef RRScreenChangeNotifyMask
+          /* Inform Xlib that it's ok to update its data structures. */
+          XRRUpdateConfiguration(&event.x_event); /* Xrandr.h 1.9, 2002/09/29*/
+#endif /* RRScreenChangeNotifyMask */
+
+          /* Resize the existing xscreensaver windows and cached ssi data. */
+          if (update_screen_layout (si))
+            {
+              if (p->verbose_p)
+                {
+                  fprintf (stderr, "%s: new layout:\n", blurb());
+                  describe_monitor_layout (si);
+                }
+              resize_screensaver_window (si);
+            }
+        }
+      else
+#endif /* HAVE_RANDR */
+
+      if (event.x_event.xany.window == si->passwd_dialog && 
+          event.x_event.xany.type == Expose)
        draw_passwd_window (si);
-      else if (event.xany.type == KeyPress)
+      else if (event.x_event.xany.type == KeyPress)
         {
-          handle_passwd_key (si, &event.xkey);
-          caps_p = (event.xkey.state & LockMask);
+          handle_passwd_key (si, &event.x_event.xkey);
+          si->pw_data->caps_p = (event.x_event.xkey.state & LockMask);
         }
-      else if (event.xany.type == ButtonPress || 
-               event.xany.type == ButtonRelease)
+      else if (event.x_event.xany.type == ButtonPress || 
+               event.x_event.xany.type == ButtonRelease)
        {
          si->pw_data->button_state_changed_p = True;
-         handle_unlock_button (si, &event);
+         handle_unlock_button (si, &event.x_event);
          if (si->pw_data->login_button_p)
-           handle_login_button (si, &event);
+           handle_login_button (si, &event.x_event);
        }
       else
-       XtDispatchEvent (&event);
+       XtDispatchEvent (&event.x_event);
     }
 
   switch (si->unlock_state)
@@ -1796,73 +1957,15 @@ passwd_event_loop (saver_info *si)
     default: msg = 0; break;
     }
 
-  if (si->unlock_state == ul_fail)
-    si->unlock_failures++;
-
   if (p->verbose_p)
-    switch (si->unlock_state)
-      {
-      case ul_fail:
-       fprintf (stderr, "%s: auth/input incorrect!%s\n", blurb(),
-                 (caps_p ? "  (CapsLock)" : ""));
-        break;
-      case ul_cancel:
-       fprintf (stderr, "%s: input cancelled.\n", blurb()); break;
-      case ul_time:
-       fprintf (stderr, "%s: input timed out.\n", blurb()); break;
-      case ul_finished:
-       fprintf (stderr, "%s: input finished.\n", blurb()); break;
-      default: break;
-      }
-
-#ifdef HAVE_SYSLOG
-  if (si->unlock_state == ul_fail)
-    {
-      /* If they typed a password (as opposed to just hitting return) and
-        the password was invalid, log it.
-      */
-      struct passwd *pw = getpwuid (getuid ());
-      char *d = DisplayString (si->dpy);
-      char *u = (pw && pw->pw_name ? pw->pw_name : "???");
-      int opt = 0;
-      int fac = 0;
-
-# ifdef LOG_PID
-      opt = LOG_PID;
-# endif
-
-# if defined(LOG_AUTHPRIV)
-      fac = LOG_AUTHPRIV;
-# elif defined(LOG_AUTH)
-      fac = LOG_AUTH;
-# else
-      fac = LOG_DAEMON;
-# endif
-
-      if (!d) d = "";
-      openlog (progname, opt, fac);
-      syslog (LOG_NOTICE, "FAILED LOGIN %d ON DISPLAY \"%s\", FOR \"%s\"",
-             si->unlock_failures, d, u);
-      closelog ();
-    }
-#endif /* HAVE_SYSLOG */
-
-  if (si->unlock_state == ul_fail)
-    XBell (si->dpy, False);
-
-  if (si->unlock_state == ul_success && si->unlock_failures != 0)
-    {
-      if (si->unlock_failures == 1)
-       fprintf (real_stderr,
-                "%s: WARNING: 1 failed attempt to unlock the screen.\n",
-                blurb());
-      else
-       fprintf (real_stderr,
-                "%s: WARNING: %d failed attempts to unlock the screen.\n",
-                blurb(), si->unlock_failures);
-      fflush (real_stderr);
-
-      si->unlock_failures = 0;
+    switch (si->unlock_state) {
+    case ul_cancel:
+      fprintf (stderr, "%s: input cancelled.\n", blurb()); break;
+    case ul_time:
+      fprintf (stderr, "%s: input timed out.\n", blurb()); break;
+    case ul_finished:
+      fprintf (stderr, "%s: input finished.\n", blurb()); break;
+    default: break;
     }
 
   if (msg)
@@ -1870,7 +1973,6 @@ passwd_event_loop (saver_info *si)
       si->pw_data->i_beam = 0;
       update_passwd_window (si, msg, 0.0);
       XSync (si->dpy, False);
-      sleep (1);
 
       /* Swallow all pending KeyPress/KeyRelease events. */
       {
@@ -1879,6 +1981,8 @@ passwd_event_loop (saver_info *si)
          ;
       }
     }
+
+  reset_watchdog_timer (si, True);     /* Re-enable watchdog */
 }
 
 
@@ -1898,6 +2002,13 @@ handle_typeahead (saver_info *si)
 
   memcpy (pw->typed_passwd, si->unlock_typeahead, i);
   pw->typed_passwd [i] = 0;
+  {
+    int j;
+    char *c = pw->typed_passwd_char_size;
+    for (j = 0; j < i; j++)
+      *c++ = 1;
+    *c = 0;
+  }
 
   memset (si->unlock_typeahead, '*', strlen(si->unlock_typeahead));
   si->unlock_typeahead[i] = 0;
@@ -1926,11 +2037,10 @@ remove_trailing_whitespace(const char *str)
   len = strlen(str);
 
   newstr = malloc(len + 1);
-  (void) strcpy(newstr, str);
-
   if (!newstr)
     return NULL;
 
+  (void) strcpy(newstr, str);
   chr = newstr + len;
   while (isspace(*--chr) && chr >= newstr)
     *chr = '\0';
@@ -1946,7 +2056,7 @@ remove_trailing_whitespace(const char *str)
  * passwd dialog. A message sequence of info or error followed by a prompt will
  * be reduced into a single dialog window.
  *
- * Returns 0 on success or -1 if some problem occurred (cancelled auth, OOM, ...)
+ * Returns 0 on success or -1 if some problem occurred (cancelled, OOM, etc.)
  */
 int
 gui_auth_conv(int num_msg,
@@ -1958,6 +2068,12 @@ gui_auth_conv(int num_msg,
   const char *info_msg, *prompt;
   struct auth_response *responses;
 
+  if (si->unlock_state == ul_cancel ||
+      si->unlock_state == ul_time)
+    /* If we've already cancelled or timed out in this PAM conversation,
+       don't prompt again even if PAM asks us to! */
+    return -1;
+
   if (!(responses = calloc(num_msg, sizeof(struct auth_response))))
     goto fail;
 
@@ -1992,9 +2108,11 @@ gui_auth_conv(int num_msg,
        info_msg_trimmed = remove_trailing_whitespace(info_msg);
        prompt_trimmed = remove_trailing_whitespace(prompt);
 
-       make_passwd_window(si, info_msg_trimmed, prompt_trimmed,
-                          auth_msgs[i].type == AUTH_MSGTYPE_PROMPT_ECHO
-                          ? True : False);
+       if (make_passwd_window(si, info_msg_trimmed, prompt_trimmed,
+                               auth_msgs[i].type == AUTH_MSGTYPE_PROMPT_ECHO
+                               ? True : False)
+            < 0)
+          goto fail;
 
        if (info_msg_trimmed)
          free(info_msg_trimmed);
@@ -2034,6 +2152,7 @@ gui_auth_conv(int num_msg,
 fail:
   if (compose_status)
     free (compose_status);
+  compose_status = 0;
 
   if (responses)
     {
@@ -2050,23 +2169,65 @@ fail:
 void
 auth_finished_cb (saver_info *si)
 {
-  if (si->unlock_state == ul_fail)
+  char buf[1024];
+  const char *s;
+
+  /* If we have something to say, put the dialog back up for a few seconds
+     to display it.  Otherwise, don't bother.
+   */
+
+  if (si->unlock_state == ul_fail &&           /* failed with caps lock on */
+      si->pw_data && si->pw_data->caps_p)
+    s = "Authentication failed (Caps Lock?)";
+  else if (si->unlock_state == ul_fail)                /* failed without caps lock */
+    s = "Authentication failed!";
+  else if (si->unlock_state == ul_success &&   /* good, but report failures */
+           si->unlock_failures > 0)
     {
-      make_passwd_window (si, "Authentication failed!", NULL, True);
-      sleep (2); /* Not very nice, I know */
+      if (si->unlock_failures == 1)
+        s = "There has been\n1 failed login attempt.";
+      else
+        {
+          sprintf (buf, "There have been\n%d failed login attempts.",
+                   si->unlock_failures);
+          s = buf;
+        }
+      si->unlock_failures = 0;
 
-      /* Swallow any keyboard or mouse events that were received while the
-       * dialog was up */
-      {
-       XEvent e;
-       long mask = (KeyPressMask | KeyReleaseMask |
-                    ButtonPressMask | ButtonReleaseMask);
-       while (XCheckMaskEvent (si->dpy, mask, &e))
-         ;
-      }
+      /* ignore failures if they all were too recent */
+      if (time((time_t *) 0) - si->unlock_failure_time 
+          < si->prefs.auth_warning_slack)
+       goto END;
     }
+  else                                         /* good, with no failures, */
+    goto END;                                  /* or timeout, or cancel. */
+
+  make_passwd_window (si, s, NULL, True);
+  XSync (si->dpy, False);
+
+  {
+    int secs = 4;
+    time_t start = time ((time_t *) 0);
+    XEvent event;
+    while (time ((time_t *) 0) < start + secs)
+      if (XPending (si->dpy))
+        {
+          XNextEvent (si->dpy, &event);
+          if (event.xany.window == si->passwd_dialog && 
+              event.xany.type == Expose)
+            draw_passwd_window (si);
+          else if (event.xany.type == ButtonPress || 
+                   event.xany.type == KeyPress)
+            break;
+          XSync (si->dpy, False);
+        }
+      else
+        usleep (250000);  /* 1/4 second */
+  }
 
-  destroy_passwd_window (si);
+ END:
+  if (si->pw_data)
+    destroy_passwd_window (si);
 }
 
 
@@ -2083,9 +2244,6 @@ unlock_p (saver_info *si)
 
   raise_window (si, True, True, True);
 
-  if (p->verbose_p)
-    fprintf (stderr, "%s: prompting for password.\n", blurb());
-
   xss_authenticate(si, p->verbose_p);
 
   return (si->unlock_state == ul_success);