From http://www.jwz.org/xscreensaver/xscreensaver-5.38.tar.gz
[xscreensaver] / driver / lock.c
1 /* lock.c --- handling the password dialog for locking-mode.
2  * xscreensaver, Copyright (c) 1993-2017 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 /* Athena locking code contributed by Jon A. Christopher <jac8782@tamu.edu> */
14 /* Copyright 1997, with the same permissions as above. */
15
16 #ifdef HAVE_CONFIG_H
17 # include "config.h"
18 #endif
19
20 #include <ctype.h>
21 #include <X11/Intrinsic.h>
22 #include <X11/cursorfont.h>
23 #include <X11/Xos.h>            /* for time() */
24 #include <time.h>
25 #include <sys/time.h>
26 #include "xscreensaver.h"
27 #include "resources.h"
28 #include "mlstring.h"
29 #include "auth.h"
30
31 #ifndef NO_LOCKING              /* (mostly) whole file */
32
33 #ifdef HAVE_XHPDISABLERESET
34 # include <X11/XHPlib.h>
35   static void hp_lock_reset (saver_info *si, Bool lock_p);
36 #endif /* HAVE_XHPDISABLERESET */
37
38 #ifdef HAVE_XF86VMODE
39 # include <X11/extensions/xf86vmode.h>
40   static void xfree_lock_mode_switch (saver_info *si, Bool lock_p);
41 #endif /* HAVE_XF86VMODE */
42
43 #ifdef HAVE_XF86MISCSETGRABKEYSSTATE
44 # include <X11/extensions/xf86misc.h>
45   static void xfree_lock_grab_smasher (saver_info *si, Bool lock_p);
46 #endif /* HAVE_XF86MISCSETGRABKEYSSTATE */
47
48 #ifdef HAVE_RANDR
49 # include <X11/extensions/Xrandr.h>
50 #endif /* HAVE_RANDR */
51
52 #ifdef _VROOT_H_
53 ERROR!  You must not include vroot.h in this file.
54 #endif
55
56 #ifdef HAVE_UNAME
57 # include <sys/utsname.h> /* for hostname info */
58 #endif /* HAVE_UNAME */
59 #include <ctype.h>
60
61 #ifndef VMS
62 # include <pwd.h>
63 #else /* VMS */
64
65 extern char *getenv(const char *name);
66 extern int validate_user(char *name, char *password);
67
68 static Bool
69 vms_passwd_valid_p(char *pw, Bool verbose_p)
70 {
71   return (validate_user (getenv("USER"), typed_passwd) == 1);
72 }
73 # undef passwd_valid_p
74 # define passwd_valid_p vms_passwd_valid_p
75
76 #endif /* VMS */
77
78 #define SAMPLE_INPUT "MMMMMMMMMMMM"
79
80
81 #undef MAX
82 #define MAX(a,b) ((a)>(b)?(a):(b))
83
84 typedef struct info_dialog_data info_dialog_data;
85
86
87 #define MAX_BYTES_PER_CHAR 8    /* UTF-8 uses no more than 3, I think */
88 #define MAX_PASSWD_CHARS   128  /* Longest possible passphrase */
89
90 struct passwd_dialog_data {
91
92   saver_screen_info *prompt_screen;
93   int previous_mouse_x, previous_mouse_y;
94
95   /* "Characters" in the password may be a variable number of bytes long.
96      typed_passwd contains the raw bytes.
97      typed_passwd_char_size indicates the size in bytes of each character,
98      so that we can make backspace work.
99    */
100   char typed_passwd [MAX_PASSWD_CHARS * MAX_BYTES_PER_CHAR];
101   char typed_passwd_char_size [MAX_PASSWD_CHARS];
102   
103   XtIntervalId timer;
104   int i_beam;
105
106   float ratio;
107   Position x, y;
108   Dimension width;
109   Dimension height;
110   Dimension border_width;
111
112   Bool echo_input;
113   Bool show_stars_p; /* "I regret that I have but one asterisk for my country."
114                         -- Nathan Hale, 1776. */
115
116   char *heading_label;
117   char *body_label;
118   char *user_label;
119   mlstring *info_label;
120   /* The entry field shall only be displayed if prompt_label is not NULL */
121   mlstring *prompt_label;
122   char *date_label;
123   char *passwd_string;
124   Bool passwd_changed_p; /* Whether the user entry field needs redrawing */
125   Bool caps_p;           /* Whether we saw a keypress with caps-lock on */
126   char *unlock_label;
127   char *login_label;
128   char *uname_label;
129
130   Bool show_uname_p;
131
132   XFontStruct *heading_font;
133   XFontStruct *body_font;
134   XFontStruct *label_font;
135   XFontStruct *passwd_font;
136   XFontStruct *date_font;
137   XFontStruct *button_font;
138   XFontStruct *uname_font;
139
140   Pixel foreground;
141   Pixel background;
142   Pixel border;
143   Pixel passwd_foreground;
144   Pixel passwd_background;
145   Pixel thermo_foreground;
146   Pixel thermo_background;
147   Pixel shadow_top;
148   Pixel shadow_bottom;
149   Pixel button_foreground;
150   Pixel button_background;
151
152   Dimension preferred_logo_width, logo_width;
153   Dimension preferred_logo_height, logo_height;
154   Dimension thermo_width;
155   Dimension internal_border;
156   Dimension shadow_width;
157
158   Dimension passwd_field_x, passwd_field_y;
159   Dimension passwd_field_width, passwd_field_height;
160
161   Dimension unlock_button_x, unlock_button_y;
162   Dimension unlock_button_width, unlock_button_height;
163
164   Dimension login_button_x, login_button_y;
165   Dimension login_button_width, login_button_height;
166
167   Dimension thermo_field_x, thermo_field_y;
168   Dimension thermo_field_height;
169
170   Pixmap logo_pixmap;
171   Pixmap logo_clipmask;
172   int logo_npixels;
173   unsigned long *logo_pixels;
174
175   Cursor passwd_cursor;
176   Bool unlock_button_down_p;
177   Bool login_button_down_p;
178   Bool login_button_p;
179   Bool login_button_enabled_p;
180   Bool button_state_changed_p; /* Refers to both buttons */
181
182   Pixmap save_under;
183   Pixmap user_entry_pixmap;
184 };
185
186 static void draw_passwd_window (saver_info *si);
187 static void update_passwd_window (saver_info *si, const char *printed_passwd,
188                                   float ratio);
189 static void destroy_passwd_window (saver_info *si);
190 static void undo_vp_motion (saver_info *si);
191 static void finished_typing_passwd (saver_info *si, passwd_dialog_data *pw);
192 static void cleanup_passwd_window (saver_info *si);
193 static void restore_background (saver_info *si);
194
195 extern void xss_authenticate(saver_info *si, Bool verbose_p);
196
197 static int
198 new_passwd_window (saver_info *si)
199 {
200   passwd_dialog_data *pw;
201   Screen *screen;
202   Colormap cmap;
203   saver_screen_info *ssi = &si->screens [mouse_screen (si)];
204
205   pw = (passwd_dialog_data *) calloc (1, sizeof(*pw));
206   if (!pw)
207     return -1;
208
209   /* Display the button only if the "newLoginCommand" pref is non-null.
210    */
211   pw->login_button_p = (si->prefs.new_login_command &&
212                         *si->prefs.new_login_command);
213
214   pw->passwd_cursor = XCreateFontCursor (si->dpy, XC_top_left_arrow);
215
216   pw->prompt_screen = ssi;
217
218   screen = pw->prompt_screen->screen;
219   cmap = DefaultColormapOfScreen (screen);
220
221   pw->show_stars_p = get_boolean_resource(si->dpy, "passwd.asterisks", 
222                                           "Boolean");
223   
224   pw->heading_label = get_string_resource (si->dpy, "passwd.heading.label",
225                                            "Dialog.Label.Label");
226   pw->body_label = get_string_resource (si->dpy, "passwd.body.label",
227                                         "Dialog.Label.Label");
228   pw->user_label = get_string_resource (si->dpy, "passwd.user.label",
229                                         "Dialog.Label.Label");
230   pw->unlock_label = get_string_resource (si->dpy, "passwd.unlock.label",
231                                           "Dialog.Button.Label");
232   pw->login_label = get_string_resource (si->dpy, "passwd.login.label",
233                                          "Dialog.Button.Label");
234
235   pw->date_label = get_string_resource (si->dpy, "dateFormat", "DateFormat");
236
237   if (!pw->heading_label)
238     pw->heading_label = strdup("ERROR: RESOURCES NOT INSTALLED CORRECTLY");
239   if (!pw->body_label)
240     pw->body_label = strdup("ERROR: RESOURCES NOT INSTALLED CORRECTLY");
241   if (!pw->user_label) pw->user_label = strdup("ERROR");
242   if (!pw->date_label) pw->date_label = strdup("ERROR");
243   if (!pw->unlock_label) pw->unlock_label = strdup("ERROR (UNLOCK)");
244   if (!pw->login_label) pw->login_label = strdup ("ERROR (LOGIN)") ;
245
246   /* Put the version number in the label. */
247   {
248     char *s = (char *) malloc (strlen(pw->heading_label) + 20);
249     sprintf(s, pw->heading_label, si->version);
250     free (pw->heading_label);
251     pw->heading_label = s;
252   }
253
254   /* Get hostname info */
255   pw->uname_label = strdup(""); /* Initialy, write nothing */
256
257 # ifdef HAVE_UNAME
258   {
259     struct utsname uts;
260
261     if (uname (&uts) == 0)
262       {
263 #if 0 /* Get the full hostname */
264         {
265           char *s;
266           if ((s = strchr(uts.nodename, '.')))
267             *s = 0;
268         }
269 #endif
270         char *s = strdup (uts.nodename);
271         free (pw->uname_label);
272         pw->uname_label = s;
273       }
274   }
275 # endif
276
277   pw->passwd_string = strdup("");
278
279   pw->heading_font =
280     splash_load_font (si->dpy, "passwd.headingFont", "Dialog.Font");
281   pw->button_font =
282     splash_load_font (si->dpy, "passwd.buttonFont", "Dialog.Font");
283   pw->body_font =
284     splash_load_font (si->dpy, "passwd.bodyFont", "Dialog.Font");
285   pw->label_font =
286     splash_load_font (si->dpy, "passwd.labelFont", "Dialog.Font");
287   pw->passwd_font =
288     splash_load_font (si->dpy, "passwd.passwdFont", "Dialog.Font");
289   pw->date_font =
290     splash_load_font (si->dpy, "passwd.dateFont", "Dialog.Font");
291   pw->uname_font =
292     splash_load_font (si->dpy, "passwd.unameFont", "Dialog.Font");
293   
294   pw->show_uname_p = get_boolean_resource(si->dpy, "passwd.uname", "Boolean");
295
296   pw->foreground = get_pixel_resource (si->dpy, cmap,
297                                        "passwd.foreground",
298                                        "Dialog.Foreground" );
299   pw->background = get_pixel_resource (si->dpy, cmap,
300                                        "passwd.background",
301                                        "Dialog.Background" );
302   pw->border = get_pixel_resource (si->dpy, cmap,
303                                        "passwd.borderColor",
304                                        "Dialog.borderColor");
305
306   if (pw->foreground == pw->background)
307     {
308       /* Make sure the error messages show up. */
309       pw->foreground = BlackPixelOfScreen (screen);
310       pw->background = WhitePixelOfScreen (screen);
311     }
312
313   pw->passwd_foreground = get_pixel_resource (si->dpy, cmap,
314                                               "passwd.text.foreground",
315                                               "Dialog.Text.Foreground" );
316   pw->passwd_background = get_pixel_resource (si->dpy, cmap,
317                                               "passwd.text.background",
318                                               "Dialog.Text.Background" );
319   pw->button_foreground = get_pixel_resource (si->dpy, cmap, 
320                                               "splash.Button.foreground",
321                                               "Dialog.Button.Foreground" );
322   pw->button_background = get_pixel_resource (si->dpy, cmap,
323                                               "splash.Button.background",
324                                               "Dialog.Button.Background" );
325   pw->thermo_foreground = get_pixel_resource (si->dpy, cmap,
326                                               "passwd.thermometer.foreground",
327                                               "Dialog.Thermometer.Foreground");
328   pw->thermo_background = get_pixel_resource ( si->dpy, cmap,
329                                               "passwd.thermometer.background",
330                                               "Dialog.Thermometer.Background");
331   pw->shadow_top = get_pixel_resource ( si->dpy, cmap,
332                                        "passwd.topShadowColor",
333                                        "Dialog.Foreground" );
334   pw->shadow_bottom = get_pixel_resource (si->dpy, cmap, 
335                                           "passwd.bottomShadowColor",
336                                           "Dialog.Background" );
337
338   pw->preferred_logo_width = get_integer_resource (si->dpy, 
339                                                    "passwd.logo.width",
340                                                    "Dialog.Logo.Width");
341   pw->preferred_logo_height = get_integer_resource (si->dpy,
342                                                     "passwd.logo.height",
343                                                     "Dialog.Logo.Height");
344   pw->thermo_width = get_integer_resource (si->dpy, "passwd.thermometer.width",
345                                            "Dialog.Thermometer.Width");
346   pw->internal_border = get_integer_resource (si->dpy,
347                                               "passwd.internalBorderWidth",
348                                               "Dialog.InternalBorderWidth");
349   pw->shadow_width = get_integer_resource (si->dpy, "passwd.shadowThickness",
350                                            "Dialog.ShadowThickness");
351
352   if (pw->preferred_logo_width == 0)  pw->preferred_logo_width = 150;
353   if (pw->preferred_logo_height == 0) pw->preferred_logo_height = 150;
354   if (pw->internal_border == 0) pw->internal_border = 15;
355   if (pw->shadow_width == 0) pw->shadow_width = 4;
356   if (pw->thermo_width == 0) pw->thermo_width = pw->shadow_width;
357
358
359   /* We need to remember the mouse position and restore it afterward, or
360      sometimes (perhaps only with Xinerama?) the mouse gets warped to
361      inside the bounds of the lock dialog window.
362    */
363   {
364     Window pointer_root, pointer_child;
365     int root_x, root_y, win_x, win_y;
366     unsigned int mask;
367     pw->previous_mouse_x = 0;
368     pw->previous_mouse_y = 0;
369     if (XQueryPointer (si->dpy, RootWindowOfScreen (pw->prompt_screen->screen),
370                        &pointer_root, &pointer_child,
371                        &root_x, &root_y, &win_x, &win_y, &mask))
372       {
373         pw->previous_mouse_x = root_x;
374         pw->previous_mouse_y = root_y;
375         if (si->prefs.verbose_p)
376           fprintf (stderr, "%s: %d: mouse is at %d,%d.\n",
377                    blurb(), pw->prompt_screen->number,
378                    pw->previous_mouse_x, pw->previous_mouse_y);
379       }
380     else if (si->prefs.verbose_p)
381       fprintf (stderr, "%s: %d: unable to determine mouse position?\n",
382                blurb(), pw->prompt_screen->number);
383   }
384
385   /* Before mapping the window, save a pixmap of the current screen.
386      When we lower the window, we restore these bits.  This works,
387      because the running screenhack has already been sent SIGSTOP, so
388      we know nothing else is drawing right now! */
389   {
390     XGCValues gcv;
391     GC gc;
392     pw->save_under = XCreatePixmap (si->dpy,
393                                     pw->prompt_screen->screensaver_window,
394                                     pw->prompt_screen->width,
395                                     pw->prompt_screen->height,
396                                     pw->prompt_screen->current_depth);
397     gcv.function = GXcopy;
398     gc = XCreateGC (si->dpy, pw->save_under, GCFunction, &gcv);
399     XCopyArea (si->dpy, pw->prompt_screen->screensaver_window,
400                pw->save_under, gc,
401                0, 0,
402                pw->prompt_screen->width, pw->prompt_screen->height,
403                0, 0);
404     XFreeGC (si->dpy, gc);
405   }
406
407   si->pw_data = pw;
408   return 0;
409 }
410
411
412 Bool debug_passwd_window_p = False;  /* used only by test-passwd.c */
413
414
415 /**
416  * info_msg and prompt may be NULL.
417  */
418 static int
419 make_passwd_window (saver_info *si,
420                     const char *info_msg,
421                     const char *prompt,
422                     Bool echo)
423 {
424   XSetWindowAttributes attrs;
425   unsigned long attrmask = 0;
426   passwd_dialog_data *pw;
427   Screen *screen;
428   Colormap cmap;
429   Dimension max_string_width_px;
430   saver_screen_info *ssi = &si->screens [mouse_screen (si)];
431
432   cleanup_passwd_window (si);
433
434   if (! ssi)   /* WTF?  Trying to prompt while no screens connected? */
435     return -1;
436
437   if (!si->pw_data)
438     if (new_passwd_window (si) < 0)
439       return -1;
440
441   if (!(pw = si->pw_data))
442     return -1;
443
444   pw->ratio = 1.0;
445
446   pw->prompt_screen = ssi;
447   if (si->prefs.verbose_p)
448     fprintf (stderr, "%s: %d: creating password dialog (\"%s\")\n",
449              blurb(), pw->prompt_screen->number,
450              info_msg ? info_msg : "");
451
452   screen = pw->prompt_screen->screen;
453   cmap = DefaultColormapOfScreen (screen);
454
455   pw->echo_input = echo;
456
457   max_string_width_px = ssi->width
458       - pw->shadow_width * 4
459       - pw->border_width * 2
460       - pw->thermo_width
461       - pw->preferred_logo_width
462       - pw->internal_border * 2;
463   /* As the string wraps it makes the window taller which makes the logo wider
464    * which leaves less room for the text which makes the string wrap. Uh-oh, a
465    * loop. By wrapping at a bit less than the available width, there's some
466    * room for the dialog to grow without going off the edge of the screen. */
467   max_string_width_px *= 0.75;
468
469   if (!info_msg && decrepit_p())
470     info_msg = ("\n"
471                 "This version of XScreenSaver\n"
472                 "is very old! Please upgrade!\n");
473
474   pw->info_label = mlstring_new(info_msg ? info_msg : pw->body_label,
475                                 pw->label_font, max_string_width_px);
476
477   {
478     int direction, ascent, descent;
479     XCharStruct overall;
480
481     pw->width = 0;
482     pw->height = 0;
483
484     /* Measure the heading_label. */
485     XTextExtents (pw->heading_font,
486                   pw->heading_label, strlen(pw->heading_label),
487                   &direction, &ascent, &descent, &overall);
488     if (overall.width > pw->width) pw->width = overall.width;
489     pw->height += ascent + descent;
490
491     /* Measure the uname_label. */
492     if ((strlen(pw->uname_label)) && pw->show_uname_p)
493       {
494         XTextExtents (pw->uname_font,
495                       pw->uname_label, strlen(pw->uname_label),
496                       &direction, &ascent, &descent, &overall);
497         if (overall.width > pw->width) pw->width = overall.width;
498         pw->height += ascent + descent;
499       }
500
501     {
502       Dimension w2 = 0, w3 = 0, button_w = 0;
503       Dimension h2 = 0, h3 = 0, button_h = 0;
504       const char *passwd_string = SAMPLE_INPUT;
505
506       /* Measure the user_label. */
507       XTextExtents (pw->label_font,
508                     pw->user_label, strlen(pw->user_label),
509                     &direction, &ascent, &descent, &overall);
510       if (overall.width > w2)  w2 = overall.width;
511       h2 += ascent + descent;
512
513       /* Measure the info_label. */
514       if (pw->info_label->overall_width > pw->width)
515         pw->width = pw->info_label->overall_width;
516       h2 += pw->info_label->overall_height;
517
518       /* Measure the user string. */
519       XTextExtents (pw->passwd_font,
520                     si->user, strlen(si->user),
521                     &direction, &ascent, &descent, &overall);
522       overall.width += (pw->shadow_width * 4);
523       ascent += (pw->shadow_width * 4);
524       if (overall.width > w3)  w3 = overall.width;
525       h3 += ascent + descent;
526
527       /* Measure the (dummy) passwd_string. */
528       if (prompt)
529         {
530           XTextExtents (pw->passwd_font,
531                         passwd_string, strlen(passwd_string),
532                         &direction, &ascent, &descent, &overall);
533           overall.width += (pw->shadow_width * 4);
534           ascent += (pw->shadow_width * 4);
535           if (overall.width > w3)  w3 = overall.width;
536           h3 += ascent + descent;
537
538           /* Measure the prompt_label. */
539           max_string_width_px -= w3;
540           pw->prompt_label = mlstring_new (prompt, pw->label_font, 
541                                            max_string_width_px);
542
543           if (pw->prompt_label->overall_width > w2)
544             w2 = pw->prompt_label->overall_width;
545
546           h2 += pw->prompt_label->overall_height;
547
548           w2 = w2 + w3 + (pw->shadow_width * 2);
549           h2 = MAX (h2, h3);
550         }
551
552       /* The "Unlock" button. */
553       XTextExtents (pw->label_font,
554                     pw->unlock_label, strlen(pw->unlock_label),
555                     &direction, &ascent, &descent, &overall);
556       button_w = overall.width;
557       button_h = ascent + descent;
558
559       /* Add some horizontal padding inside the button. */
560       button_w += ascent;
561       
562       button_w += ((ascent + descent) / 2) + (pw->shadow_width * 2);
563       button_h += ((ascent + descent) / 2) + (pw->shadow_width * 2);
564
565       pw->unlock_button_width = button_w;
566       pw->unlock_button_height = button_h;
567
568       w2 = MAX (w2, button_w);
569       h2 += button_h * 1.5;
570
571       /* The "New Login" button */
572       pw->login_button_width = 0;
573       pw->login_button_height = 0;
574
575       if (pw->login_button_p)
576         {
577           pw->login_button_enabled_p = True;
578
579           /* Measure the "New Login" button */
580           XTextExtents (pw->button_font, pw->login_label,
581                         strlen (pw->login_label),
582                         &direction, &ascent, &descent, &overall);
583           button_w = overall.width;
584           button_h = ascent + descent;
585
586           /* Add some horizontal padding inside the buttons. */
587           button_w += ascent;
588
589           button_w += ((ascent + descent) / 2) + (pw->shadow_width * 2);
590           button_h += ((ascent + descent) / 2) + (pw->shadow_width * 2);
591
592           pw->login_button_width = button_w;
593           pw->login_button_height = button_h;
594
595           if (button_h > pw->unlock_button_height)
596             h2 += (button_h * 1.5 - pw->unlock_button_height * 1.5);
597
598           /* Use (2 * shadow_width) spacing between the buttons. Another
599              (2 * shadow_width) is required to account for button shadows. */
600           w2 = MAX (w2, 
601                     button_w + pw->unlock_button_width +
602                     (pw->shadow_width * 4));
603         }
604
605       if (w2 > pw->width)  pw->width  = w2;
606       pw->height += h2;
607     }
608
609     pw->width  += (pw->internal_border * 2);
610     pw->height += (pw->internal_border * 4);
611
612     pw->width += pw->thermo_width + (pw->shadow_width * 3);
613
614     if (pw->preferred_logo_height > pw->height)
615       pw->height = pw->logo_height = pw->preferred_logo_height;
616     else if (pw->height > pw->preferred_logo_height)
617       pw->logo_height = pw->height;
618
619     pw->logo_width = pw->logo_height;
620
621     pw->width += pw->logo_width;
622   }
623
624   attrmask |= CWOverrideRedirect; attrs.override_redirect = True;
625
626   if (debug_passwd_window_p)
627     attrs.override_redirect = False;  /* kludge for test-passwd.c */
628
629   attrmask |= CWEventMask;
630   attrs.event_mask = (ExposureMask | KeyPressMask |
631                       ButtonPressMask | ButtonReleaseMask);
632
633   /* Figure out where on the desktop to place the window so that it will
634      actually be visible; this takes into account virtual viewports as
635      well as Xinerama. */
636   {
637     saver_screen_info *ssi = &si->screens [mouse_screen (si)];
638     int x = ssi->x;
639     int y = ssi->y;
640     int w = ssi->width;
641     int h = ssi->height;
642     if (si->prefs.debug_p) w /= 2;
643     pw->x = x + ((w + pw->width) / 2) - pw->width;
644     pw->y = y + ((h + pw->height) / 2) - pw->height;
645     if (pw->x < x) pw->x = x;
646     if (pw->y < y) pw->y = y;
647   }
648
649   pw->border_width = get_integer_resource (si->dpy, "passwd.borderWidth",
650                                            "Dialog.BorderWidth");
651
652   /* Only create the window the first time around */
653   if (!si->passwd_dialog)
654     {
655       si->passwd_dialog =
656         XCreateWindow (si->dpy,
657                        RootWindowOfScreen(screen),
658                        pw->x, pw->y, pw->width, pw->height, pw->border_width,
659                        DefaultDepthOfScreen (screen), InputOutput,
660                        DefaultVisualOfScreen(screen),
661                        attrmask, &attrs);
662       XSetWindowBackground (si->dpy, si->passwd_dialog, pw->background);
663       XSetWindowBorder (si->dpy, si->passwd_dialog, pw->border);
664
665       /* We use the default visual, not ssi->visual, so that the logo pixmap's
666          visual matches that of the si->passwd_dialog window. */
667       pw->logo_pixmap = xscreensaver_logo (ssi->screen,
668                                            /* ssi->current_visual, */
669                                            DefaultVisualOfScreen(screen),
670                                            si->passwd_dialog, cmap,
671                                            pw->background, 
672                                            &pw->logo_pixels, &pw->logo_npixels,
673                                            &pw->logo_clipmask, True);
674     }
675   else /* On successive prompts, just resize the window */
676     {
677       XWindowChanges wc;
678       unsigned int mask = CWX | CWY | CWWidth | CWHeight;
679
680       wc.x = pw->x;
681       wc.y = pw->y;
682       wc.width = pw->width;
683       wc.height = pw->height;
684
685       XConfigureWindow (si->dpy, si->passwd_dialog, mask, &wc);
686     }
687
688   restore_background(si);
689
690   XMapRaised (si->dpy, si->passwd_dialog);
691   XSync (si->dpy, False);
692
693   move_mouse_grab (si, si->passwd_dialog,
694                    pw->passwd_cursor,
695                    pw->prompt_screen->number);
696   undo_vp_motion (si);
697
698   si->pw_data = pw;
699
700   if (cmap)
701     XInstallColormap (si->dpy, cmap);
702   draw_passwd_window (si);
703
704   return 0;
705 }
706
707
708 static void
709 draw_passwd_window (saver_info *si)
710 {
711   passwd_dialog_data *pw = si->pw_data;
712   XGCValues gcv;
713   GC gc1, gc2;
714   int spacing, height;
715   int x1, x2, x3, y1, y2;
716   int sw;
717   int tb_height;
718
719   /* Force redraw */
720   pw->passwd_changed_p = True;
721   pw->button_state_changed_p = True;
722
723   /* This height is the height of all the elements, not to be confused with
724    * the overall window height which is pw->height. It is used to compute
725    * the amount of spacing (padding) between elements. */
726   height = (pw->heading_font->ascent + pw->heading_font->descent +
727             pw->info_label->overall_height +
728             MAX (((pw->label_font->ascent + pw->label_font->descent) +
729                   (pw->prompt_label ? pw->prompt_label->overall_height : 0)),
730                  ((pw->passwd_font->ascent + pw->passwd_font->descent) +
731                   (pw->shadow_width * 2)) * (pw->prompt_label ? 2 : 1)) +
732             pw->date_font->ascent + pw->date_font->descent);
733
734   if ((strlen(pw->uname_label)) && pw->show_uname_p)
735     height += (pw->uname_font->ascent + pw->uname_font->descent);
736
737   height += ((pw->button_font->ascent + pw->button_font->descent) * 2 +
738              2 * pw->shadow_width);
739
740   spacing = ((pw->height - 2 * pw->shadow_width
741                - pw->internal_border - height)
742              / 10);
743
744   if (spacing < 0) spacing = 0;
745
746   gcv.foreground = pw->foreground;
747   gc1 = XCreateGC (si->dpy, si->passwd_dialog, GCForeground, &gcv);
748   gc2 = XCreateGC (si->dpy, si->passwd_dialog, GCForeground, &gcv);
749   x1 = pw->logo_width + pw->thermo_width + (pw->shadow_width * 3);
750   x3 = pw->width - (pw->shadow_width * 2);
751   y1 = (pw->shadow_width * 2) + spacing + spacing;
752
753   /* top heading
754    */
755   XSetFont (si->dpy, gc1, pw->heading_font->fid);
756   sw = string_width (pw->heading_font, pw->heading_label);
757   x2 = (x1 + ((x3 - x1 - sw) / 2));
758   y1 += spacing + pw->heading_font->ascent + pw->heading_font->descent;
759   XDrawString (si->dpy, si->passwd_dialog, gc1, x2, y1,
760                pw->heading_label, strlen(pw->heading_label));
761
762   /* uname below top heading
763    */
764   if ((strlen(pw->uname_label)) && pw->show_uname_p)
765     {
766       XSetFont (si->dpy, gc1, pw->uname_font->fid);
767       y1 += spacing + pw->uname_font->ascent + pw->uname_font->descent;
768       sw = string_width (pw->uname_font, pw->uname_label);
769       x2 = (x1 + ((x3 - x1 - sw) / 2));
770       XDrawString (si->dpy, si->passwd_dialog, gc1, x2, y1,
771                    pw->uname_label, strlen(pw->uname_label));
772     }
773
774   /* the info_label (below uname)
775    */
776   x2 = (x1 + ((x3 - x1 - pw->info_label->overall_width) / 2));
777   y1 += spacing + pw->info_label->font_height / 2;
778   mlstring_draw(si->dpy, si->passwd_dialog, gc1, pw->info_label,
779                 x2, y1);
780   y1 += pw->info_label->overall_height;
781
782
783   tb_height = (pw->passwd_font->ascent + pw->passwd_font->descent +
784                (pw->shadow_width * 4));
785
786   /* the "User:" prompt
787    */
788   y2 = y1;
789   XSetForeground (si->dpy, gc1, pw->foreground);
790   XSetFont (si->dpy, gc1, pw->label_font->fid);
791   y1 += (spacing + tb_height + pw->shadow_width);
792   x2 = (x1 + pw->internal_border +
793         MAX(string_width (pw->label_font, pw->user_label),
794             pw->prompt_label ? pw->prompt_label->overall_width : 0));
795   XDrawString (si->dpy, si->passwd_dialog, gc1,
796                x2 - string_width (pw->label_font, pw->user_label),
797                y1 - pw->passwd_font->descent,
798                pw->user_label, strlen(pw->user_label));
799
800   /* the prompt_label prompt
801    */
802   if (pw->prompt_label)
803     {
804       y1 += tb_height - pw->label_font->ascent + pw->shadow_width;
805       mlstring_draw(si->dpy, si->passwd_dialog, gc1, pw->prompt_label,
806                     x2 - pw->prompt_label->overall_width, y1);
807     }
808
809   /* the "user name" text field
810    */
811   y1 = y2;
812   XSetForeground (si->dpy, gc1, pw->passwd_foreground);
813   XSetForeground (si->dpy, gc2, pw->passwd_background);
814   XSetFont (si->dpy, gc1, pw->passwd_font->fid);
815   y1 += (spacing + tb_height);
816   x2 += (pw->shadow_width * 4);
817
818   pw->passwd_field_width = x3 - x2 - pw->internal_border;
819   pw->passwd_field_height = (pw->passwd_font->ascent +
820                              pw->passwd_font->descent +
821                              pw->shadow_width);
822
823   XFillRectangle (si->dpy, si->passwd_dialog, gc2,
824                   x2 - pw->shadow_width,
825                   y1 - (pw->passwd_font->ascent + pw->passwd_font->descent),
826                   pw->passwd_field_width, pw->passwd_field_height);
827   XDrawString (si->dpy, si->passwd_dialog, gc1,
828                x2,
829                y1 - pw->passwd_font->descent,
830                si->user, strlen(si->user));
831
832   /* the password/prompt text field
833    */
834   if (pw->prompt_label)
835     {
836       y1 += (spacing + pw->prompt_label->overall_height + pw->shadow_width*2);
837
838       pw->passwd_field_x = x2 - pw->shadow_width;
839       pw->passwd_field_y = y1 - (pw->passwd_font->ascent +
840                                  pw->passwd_font->descent);
841     }
842
843   /* The shadow around the text fields
844    */
845   y1 = y2;
846   y1 += (spacing + (pw->shadow_width * 3));
847   x1 = x2 - (pw->shadow_width * 2);
848   x2 = pw->passwd_field_width + (pw->shadow_width * 2);
849   y2 = pw->passwd_field_height + (pw->shadow_width * 2);
850
851   draw_shaded_rectangle (si->dpy, si->passwd_dialog,
852                          x1, y1, x2, y2,
853                          pw->shadow_width,
854                          pw->shadow_bottom, pw->shadow_top);
855
856   if (pw->prompt_label)
857     {
858       y1 += (spacing + pw->prompt_label->overall_height + pw->shadow_width*2);
859       draw_shaded_rectangle (si->dpy, si->passwd_dialog,
860                              x1, y1, x2, y2,
861                              pw->shadow_width,
862                              pw->shadow_bottom, pw->shadow_top);
863     }
864
865
866   /* The date, below the text fields
867    */
868   {
869     char buf[100];
870     time_t now = time ((time_t *) 0);
871     struct tm *tm = localtime (&now);
872     memset (buf, 0, sizeof(buf));
873     strftime (buf, sizeof(buf)-1, pw->date_label, tm);
874
875     XSetFont (si->dpy, gc1, pw->date_font->fid);
876     y1 += pw->shadow_width;
877     y1 += (spacing + tb_height);
878     y1 += spacing/2;
879     sw = string_width (pw->date_font, buf);
880     x2 = x1 + x2 - sw;
881     XDrawString (si->dpy, si->passwd_dialog, gc1, x2, y1, buf, strlen(buf));
882   }
883
884   /* Set up the GCs for the "New Login" and "Unlock" buttons.
885    */
886   XSetForeground(si->dpy, gc1, pw->button_foreground);
887   XSetForeground(si->dpy, gc2, pw->button_background);
888   XSetFont(si->dpy, gc1, pw->button_font->fid);
889
890   /* The "Unlock" button */
891   x2 = pw->width - pw->internal_border - (pw->shadow_width * 2);
892
893   /* right aligned button */
894   x1 = x2 - pw->unlock_button_width;
895
896   /* Add half the difference between y1 and the internal edge.
897    * It actually looks better if the internal border is ignored. */
898   y1 += ((pw->height - MAX (pw->unlock_button_height, pw->login_button_height)
899           - spacing - y1)
900          / 2);
901
902   pw->unlock_button_x = x1;
903   pw->unlock_button_y = y1;
904
905   /* The "New Login" button
906    */
907   if (pw->login_button_p)
908     {
909       /* Using the same GC as for the Unlock button */
910
911       sw = string_width (pw->button_font, pw->login_label);
912
913       /* left aligned button */
914       x1 = (pw->logo_width + pw->thermo_width + (pw->shadow_width * 3) +
915             pw->internal_border);
916
917       pw->login_button_x = x1;
918       pw->login_button_y = y1;
919     }
920
921   /* The logo
922    */
923   x1 = pw->shadow_width * 6;
924   y1 = pw->shadow_width * 6;
925   x2 = pw->logo_width - (pw->shadow_width * 12);
926   y2 = pw->logo_height - (pw->shadow_width * 12);
927
928   if (pw->logo_pixmap)
929     {
930       Window root;
931       int x, y;
932       unsigned int w, h, bw, d;
933       XGetGeometry (si->dpy, pw->logo_pixmap, &root, &x, &y, &w, &h, &bw, &d);
934       XSetForeground (si->dpy, gc1, pw->foreground);
935       XSetBackground (si->dpy, gc1, pw->background);
936       XSetClipMask (si->dpy, gc1, pw->logo_clipmask);
937       XSetClipOrigin (si->dpy, gc1, 
938                       x1 + ((x2 - (int)w) / 2), 
939                       y1 + ((y2 - (int)h) / 2));
940       if (d == 1)
941         XCopyPlane (si->dpy, pw->logo_pixmap, si->passwd_dialog, gc1,
942                     0, 0, w, h,
943                     x1 + ((x2 - (int)w) / 2),
944                     y1 + ((y2 - (int)h) / 2),
945                     1);
946       else
947         XCopyArea (si->dpy, pw->logo_pixmap, si->passwd_dialog, gc1,
948                    0, 0, w, h,
949                    x1 + ((x2 - (int)w) / 2),
950                    y1 + ((y2 - (int)h) / 2));
951     }
952
953   /* The thermometer
954    */
955   XSetForeground (si->dpy, gc1, pw->thermo_foreground);
956   XSetForeground (si->dpy, gc2, pw->thermo_background);
957
958   pw->thermo_field_x = pw->logo_width + pw->shadow_width;
959   pw->thermo_field_y = pw->shadow_width * 5;
960   pw->thermo_field_height = pw->height - (pw->shadow_width * 10);
961
962 #if 0
963   /* Solid border inside the logo box. */
964   XSetForeground (si->dpy, gc1, pw->foreground);
965   XDrawRectangle (si->dpy, si->passwd_dialog, gc1, x1, y1, x2-1, y2-1);
966 #endif
967
968   /* The shadow around the logo
969    */
970   draw_shaded_rectangle (si->dpy, si->passwd_dialog,
971                          pw->shadow_width * 4,
972                          pw->shadow_width * 4,
973                          pw->logo_width - (pw->shadow_width * 8),
974                          pw->logo_height - (pw->shadow_width * 8),
975                          pw->shadow_width,
976                          pw->shadow_bottom, pw->shadow_top);
977
978   /* The shadow around the thermometer
979    */
980   draw_shaded_rectangle (si->dpy, si->passwd_dialog,
981                          pw->logo_width,
982                          pw->shadow_width * 4,
983                          pw->thermo_width + (pw->shadow_width * 2),
984                          pw->height - (pw->shadow_width * 8),
985                          pw->shadow_width,
986                          pw->shadow_bottom, pw->shadow_top);
987
988 #if 1
989   /* Solid border inside the thermometer. */
990   XSetForeground (si->dpy, gc1, pw->foreground);
991   XDrawRectangle (si->dpy, si->passwd_dialog, gc1, 
992                   pw->thermo_field_x, pw->thermo_field_y,
993                   pw->thermo_width - 1, pw->thermo_field_height - 1);
994 #endif
995
996   /* The shadow around the whole window
997    */
998   draw_shaded_rectangle (si->dpy, si->passwd_dialog,
999                          0, 0, pw->width, pw->height, pw->shadow_width,
1000                          pw->shadow_top, pw->shadow_bottom);
1001
1002   XFreeGC (si->dpy, gc1);
1003   XFreeGC (si->dpy, gc2);
1004
1005   update_passwd_window (si, pw->passwd_string, pw->ratio);
1006 }
1007
1008 static void
1009 draw_button(Display *dpy,
1010             Drawable dialog,
1011             XFontStruct *font,
1012             unsigned long foreground, unsigned long background,
1013             char *label,
1014             int x, int y,
1015             int width, int height,
1016             int shadow_width,
1017             Pixel shadow_light, Pixel shadow_dark,
1018             Bool button_down)
1019 {
1020   XGCValues gcv;
1021   GC gc1, gc2;
1022   int sw;
1023   int label_x, label_y;
1024
1025   gcv.foreground = foreground;
1026   gcv.font = font->fid;
1027   gc1 = XCreateGC(dpy, dialog, GCForeground|GCFont, &gcv);
1028   gcv.foreground = background;
1029   gc2 = XCreateGC(dpy, dialog, GCForeground, &gcv);
1030
1031   XFillRectangle(dpy, dialog, gc2,
1032                  x, y, width, height);
1033
1034   sw = string_width(font, label);
1035
1036   label_x = x + ((width - sw) / 2);
1037   label_y = (y + (height - (font->ascent + font->descent)) / 2 + font->ascent);
1038
1039   if (button_down)
1040     {
1041       label_x += 2;
1042       label_y += 2;
1043     }
1044
1045   XDrawString(dpy, dialog, gc1, label_x, label_y, label, strlen(label));
1046
1047   XFreeGC(dpy, gc1);
1048   XFreeGC(dpy, gc2);
1049
1050   draw_shaded_rectangle(dpy, dialog, x, y, width, height,
1051                         shadow_width, shadow_light, shadow_dark);
1052 }
1053
1054 static void
1055 update_passwd_window (saver_info *si, const char *printed_passwd, float ratio)
1056 {
1057   passwd_dialog_data *pw = si->pw_data;
1058   XGCValues gcv;
1059   GC gc1, gc2;
1060   int x, y;
1061   XRectangle rects[1];
1062
1063   pw->ratio = ratio;
1064   gcv.foreground = pw->passwd_foreground;
1065   gcv.font = pw->passwd_font->fid;
1066   gc1 = XCreateGC (si->dpy, si->passwd_dialog, GCForeground|GCFont, &gcv);
1067   gcv.foreground = pw->passwd_background;
1068   gc2 = XCreateGC (si->dpy, si->passwd_dialog, GCForeground, &gcv);
1069
1070   if (printed_passwd)
1071     {
1072       char *s = strdup (printed_passwd);
1073       if (pw->passwd_string) free (pw->passwd_string);
1074       pw->passwd_string = s;
1075     }
1076
1077   if (pw->prompt_label)
1078     {
1079
1080       /* the "password" text field
1081        */
1082       rects[0].x =  pw->passwd_field_x;
1083       rects[0].y =  pw->passwd_field_y;
1084       rects[0].width = pw->passwd_field_width;
1085       rects[0].height = pw->passwd_field_height;
1086
1087       /* The user entry (password) field is double buffered.
1088        * This avoids flickering, particularly in synchronous mode. */
1089
1090       if (pw->passwd_changed_p)
1091         {
1092           pw->passwd_changed_p = False;
1093
1094           if (pw->user_entry_pixmap)
1095             {
1096               XFreePixmap(si->dpy, pw->user_entry_pixmap);
1097               pw->user_entry_pixmap = 0;
1098             }
1099
1100           pw->user_entry_pixmap = 
1101             XCreatePixmap (si->dpy, si->passwd_dialog,
1102                            rects[0].width, rects[0].height, 
1103                            DefaultDepthOfScreen (pw->prompt_screen->screen));
1104
1105           XFillRectangle (si->dpy, pw->user_entry_pixmap, gc2,
1106                           0, 0, rects[0].width, rects[0].height);
1107
1108           XDrawString (si->dpy, pw->user_entry_pixmap, gc1,
1109                        pw->shadow_width,
1110                        pw->passwd_font->ascent,
1111                        pw->passwd_string, strlen(pw->passwd_string));
1112
1113           /* Ensure the new pixmap gets copied to the window */
1114           pw->i_beam = 0;
1115
1116         }
1117
1118       /* The I-beam
1119        */
1120       if (pw->i_beam == 0)
1121         {
1122           /* Make the I-beam disappear */
1123           XCopyArea(si->dpy, pw->user_entry_pixmap, si->passwd_dialog, gc2,
1124                     0, 0, rects[0].width, rects[0].height,
1125                     rects[0].x, rects[0].y);
1126         }
1127       else if (pw->i_beam == 1)
1128         {
1129           /* Make the I-beam appear */
1130           x = (rects[0].x + pw->shadow_width +
1131                string_width (pw->passwd_font, pw->passwd_string));
1132           y = rects[0].y + pw->shadow_width;
1133
1134           if (x > rects[0].x + rects[0].width - 1)
1135             x = rects[0].x + rects[0].width - 1;
1136           XDrawLine (si->dpy, si->passwd_dialog, gc1, 
1137                      x, y,
1138                      x, y + pw->passwd_font->ascent + 
1139                      pw->passwd_font->descent-1);
1140         }
1141
1142       pw->i_beam = (pw->i_beam + 1) % 4;
1143
1144     }
1145
1146   /* the thermometer
1147    */
1148   y = (pw->thermo_field_height - 2) * (1.0 - pw->ratio);
1149   if (y > 0)
1150     {
1151       XFillRectangle (si->dpy, si->passwd_dialog, gc2,
1152                       pw->thermo_field_x + 1,
1153                       pw->thermo_field_y + 1,
1154                       pw->thermo_width-2,
1155                       y);
1156       XSetForeground (si->dpy, gc1, pw->thermo_foreground);
1157       XFillRectangle (si->dpy, si->passwd_dialog, gc1,
1158                       pw->thermo_field_x + 1,
1159                       pw->thermo_field_y + 1 + y,
1160                       pw->thermo_width-2,
1161                       MAX (0, pw->thermo_field_height - y - 2));
1162     }
1163
1164   if (pw->button_state_changed_p)
1165     {
1166       pw->button_state_changed_p = False;
1167
1168       /* The "Unlock" button
1169        */
1170       draw_button(si->dpy, si->passwd_dialog, pw->button_font,
1171                   pw->button_foreground, pw->button_background,
1172                   pw->unlock_label,
1173                   pw->unlock_button_x, pw->unlock_button_y,
1174                   pw->unlock_button_width, pw->unlock_button_height,
1175                   pw->shadow_width,
1176                   (pw->unlock_button_down_p ? pw->shadow_bottom :
1177                    pw->shadow_top),
1178                   (pw->unlock_button_down_p ? pw->shadow_top :
1179                    pw->shadow_bottom),
1180                   pw->unlock_button_down_p);
1181
1182       /* The "New Login" button
1183        */
1184       if (pw->login_button_p)
1185         {
1186           draw_button(si->dpy, si->passwd_dialog, pw->button_font,
1187                       (pw->login_button_enabled_p
1188                        ? pw->passwd_foreground
1189                        : pw->shadow_bottom),
1190                       pw->button_background,
1191                       pw->login_label,
1192                       pw->login_button_x, pw->login_button_y,
1193                       pw->login_button_width, pw->login_button_height,
1194                       pw->shadow_width,
1195                       (pw->login_button_down_p
1196                        ? pw->shadow_bottom
1197                        : pw->shadow_top),
1198                       (pw->login_button_down_p
1199                        ? pw->shadow_top
1200                        : pw->shadow_bottom),
1201                       pw->login_button_down_p);
1202         }
1203     }
1204
1205   XFreeGC (si->dpy, gc1);
1206   XFreeGC (si->dpy, gc2);
1207   XSync (si->dpy, False);
1208 }
1209
1210
1211 void
1212 restore_background (saver_info *si)
1213 {
1214   passwd_dialog_data *pw = si->pw_data;
1215   saver_screen_info *ssi = pw->prompt_screen;
1216   XGCValues gcv;
1217   GC gc;
1218
1219   gcv.function = GXcopy;
1220
1221   gc = XCreateGC (si->dpy, ssi->screensaver_window, GCFunction, &gcv);
1222
1223   XCopyArea (si->dpy, pw->save_under,
1224              ssi->screensaver_window, gc,
1225              0, 0,
1226              ssi->width, ssi->height,
1227              0, 0);
1228
1229   XFreeGC (si->dpy, gc);
1230 }
1231
1232
1233 /* Frees anything created by make_passwd_window */
1234 static void
1235 cleanup_passwd_window (saver_info *si)
1236 {
1237   passwd_dialog_data *pw;
1238
1239   if (!(pw = si->pw_data))
1240     return;
1241
1242   if (pw->info_label)
1243     {
1244       mlstring_free(pw->info_label);
1245       pw->info_label = 0;
1246     }
1247
1248   if (pw->prompt_label)
1249     {
1250       mlstring_free(pw->prompt_label);
1251       pw->prompt_label = 0;
1252     }
1253
1254   memset (pw->typed_passwd, 0, sizeof(pw->typed_passwd));
1255   memset (pw->typed_passwd_char_size, 0, sizeof(pw->typed_passwd_char_size));
1256   memset (pw->passwd_string, 0, strlen(pw->passwd_string));
1257
1258   if (pw->timer)
1259     {
1260       XtRemoveTimeOut (pw->timer);
1261       pw->timer = 0;
1262     }
1263
1264   if (pw->user_entry_pixmap)
1265     {
1266       XFreePixmap(si->dpy, pw->user_entry_pixmap);
1267       pw->user_entry_pixmap = 0;
1268     }
1269 }
1270
1271
1272 static void
1273 destroy_passwd_window (saver_info *si)
1274 {
1275   saver_preferences *p = &si->prefs;
1276   passwd_dialog_data *pw = si->pw_data;
1277   saver_screen_info *ssi = pw->prompt_screen;
1278   Colormap cmap = DefaultColormapOfScreen (ssi->screen);
1279   Pixel black = BlackPixelOfScreen (ssi->screen);
1280   Pixel white = WhitePixelOfScreen (ssi->screen);
1281   XEvent event;
1282
1283   cleanup_passwd_window (si);
1284
1285   if (si->cached_passwd)
1286     {
1287       char *wipe = si->cached_passwd;
1288
1289       while (*wipe)
1290         *wipe++ = '\0';
1291
1292       free(si->cached_passwd);
1293       si->cached_passwd = NULL;
1294     }
1295
1296   move_mouse_grab (si, RootWindowOfScreen (ssi->screen),
1297                    ssi->cursor, ssi->number);
1298
1299   if (pw->passwd_cursor)
1300     XFreeCursor (si->dpy, pw->passwd_cursor);
1301
1302   if (p->verbose_p)
1303     fprintf (stderr, "%s: %d: moving mouse back to %d,%d.\n",
1304              blurb(), ssi->number,
1305              pw->previous_mouse_x, pw->previous_mouse_y);
1306
1307   XWarpPointer (si->dpy, None, RootWindowOfScreen (ssi->screen),
1308                 0, 0, 0, 0,
1309                 pw->previous_mouse_x, pw->previous_mouse_y);
1310   XSync (si->dpy, False);
1311
1312   while (XCheckMaskEvent (si->dpy, PointerMotionMask, &event))
1313     if (p->verbose_p)
1314       fprintf (stderr, "%s: discarding MotionNotify event.\n", blurb());
1315
1316 #ifdef HAVE_XINPUT
1317   if (si->using_xinput_extension && si->xinput_DeviceMotionNotify)
1318     while (XCheckTypedEvent (si->dpy, si->xinput_DeviceMotionNotify, &event))
1319       if (p->verbose_p)
1320         fprintf (stderr, "%s: discarding DeviceMotionNotify event.\n",
1321                  blurb());
1322 #endif
1323
1324   if (si->passwd_dialog)
1325     {
1326       if (si->prefs.verbose_p)
1327         fprintf (stderr, "%s: %d: destroying password dialog.\n",
1328                  blurb(), pw->prompt_screen->number);
1329
1330       XDestroyWindow (si->dpy, si->passwd_dialog);
1331       si->passwd_dialog = 0;
1332     }
1333   
1334   if (pw->save_under)
1335     {
1336       restore_background(si);
1337       XFreePixmap (si->dpy, pw->save_under);
1338       pw->save_under = 0;
1339     }
1340
1341   if (pw->heading_label) free (pw->heading_label);
1342   if (pw->body_label)    free (pw->body_label);
1343   if (pw->user_label)    free (pw->user_label);
1344   if (pw->date_label)    free (pw->date_label);
1345   if (pw->login_label)   free (pw->login_label);
1346   if (pw->unlock_label)  free (pw->unlock_label);
1347   if (pw->passwd_string) free (pw->passwd_string);
1348   if (pw->uname_label)   free (pw->uname_label);
1349
1350   if (pw->heading_font) XFreeFont (si->dpy, pw->heading_font);
1351   if (pw->body_font)    XFreeFont (si->dpy, pw->body_font);
1352   if (pw->label_font)   XFreeFont (si->dpy, pw->label_font);
1353   if (pw->passwd_font)  XFreeFont (si->dpy, pw->passwd_font);
1354   if (pw->date_font)    XFreeFont (si->dpy, pw->date_font);
1355   if (pw->button_font)  XFreeFont (si->dpy, pw->button_font);
1356   if (pw->uname_font)   XFreeFont (si->dpy, pw->uname_font);
1357
1358   if (pw->foreground != black && pw->foreground != white)
1359     XFreeColors (si->dpy, cmap, &pw->foreground, 1, 0L);
1360   if (pw->background != black && pw->background != white)
1361     XFreeColors (si->dpy, cmap, &pw->background, 1, 0L);
1362   if (!(pw->button_foreground == black || pw->button_foreground == white))
1363     XFreeColors (si->dpy, cmap, &pw->button_foreground, 1, 0L);
1364   if (!(pw->button_background == black || pw->button_background == white))
1365     XFreeColors (si->dpy, cmap, &pw->button_background, 1, 0L);
1366   if (pw->passwd_foreground != black && pw->passwd_foreground != white)
1367     XFreeColors (si->dpy, cmap, &pw->passwd_foreground, 1, 0L);
1368   if (pw->passwd_background != black && pw->passwd_background != white)
1369     XFreeColors (si->dpy, cmap, &pw->passwd_background, 1, 0L);
1370   if (pw->thermo_foreground != black && pw->thermo_foreground != white)
1371     XFreeColors (si->dpy, cmap, &pw->thermo_foreground, 1, 0L);
1372   if (pw->thermo_background != black && pw->thermo_background != white)
1373     XFreeColors (si->dpy, cmap, &pw->thermo_background, 1, 0L);
1374   if (pw->shadow_top != black && pw->shadow_top != white)
1375     XFreeColors (si->dpy, cmap, &pw->shadow_top, 1, 0L);
1376   if (pw->shadow_bottom != black && pw->shadow_bottom != white)
1377     XFreeColors (si->dpy, cmap, &pw->shadow_bottom, 1, 0L);
1378
1379   if (pw->logo_pixmap)
1380     XFreePixmap (si->dpy, pw->logo_pixmap);
1381   if (pw-> logo_clipmask)
1382     XFreePixmap (si->dpy, pw->logo_clipmask);
1383   if (pw->logo_pixels)
1384     {
1385       if (pw->logo_npixels)
1386         XFreeColors (si->dpy, cmap, pw->logo_pixels, pw->logo_npixels, 0L);
1387       free (pw->logo_pixels);
1388       pw->logo_pixels = 0;
1389       pw->logo_npixels = 0;
1390     }
1391
1392   if (pw->save_under)
1393     XFreePixmap (si->dpy, pw->save_under);
1394
1395   if (cmap)
1396     XInstallColormap (si->dpy, cmap);
1397
1398   memset (pw, 0, sizeof(*pw));
1399   free (pw);
1400   si->pw_data = 0;
1401 }
1402
1403
1404 #if defined(HAVE_XF86MISCSETGRABKEYSSTATE) || defined(HAVE_XF86VMODE)
1405
1406 static Bool error_handler_hit_p = False;
1407
1408 static int
1409 ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
1410 {
1411   error_handler_hit_p = True;
1412   return 0;
1413 }
1414
1415 #endif /* HAVE_XF86MISCSETGRABKEYSSTATE || HAVE_XF86VMODE */
1416
1417
1418 #ifdef HAVE_XHPDISABLERESET
1419 /* This function enables and disables the C-Sh-Reset hot-key, which
1420    normally resets the X server (logging out the logged-in user.)
1421    We don't want random people to be able to do that while the
1422    screen is locked.
1423  */
1424 static void
1425 hp_lock_reset (saver_info *si, Bool lock_p)
1426 {
1427   static Bool hp_locked_p = False;
1428
1429   /* Calls to XHPDisableReset and XHPEnableReset must be balanced,
1430      or BadAccess errors occur.  (It's ok for this to be global,
1431      since it affects the whole machine, not just the current screen.)
1432   */
1433   if (hp_locked_p == lock_p)
1434     return;
1435
1436   if (lock_p)
1437     XHPDisableReset (si->dpy);
1438   else
1439     XHPEnableReset (si->dpy);
1440   hp_locked_p = lock_p;
1441 }
1442 #endif /* HAVE_XHPDISABLERESET */
1443
1444 \f
1445 #ifdef HAVE_XF86MISCSETGRABKEYSSTATE
1446
1447 /* This function enables and disables the Ctrl-Alt-KP_star and 
1448    Ctrl-Alt-KP_slash hot-keys, which (in XFree86 4.2) break any
1449    grabs and/or kill the grabbing client.  That would effectively
1450    unlock the screen, so we don't like that.
1451
1452    The Ctrl-Alt-KP_star and Ctrl-Alt-KP_slash hot-keys only exist
1453    if AllowDeactivateGrabs and/or AllowClosedownGrabs are turned on
1454    in XF86Config.  I believe they are disabled by default.
1455
1456    This does not affect any other keys (specifically Ctrl-Alt-BS or
1457    Ctrl-Alt-F1) but I wish it did.  Maybe it will someday.
1458  */
1459 static void
1460 xfree_lock_grab_smasher (saver_info *si, Bool lock_p)
1461 {
1462   saver_preferences *p = &si->prefs;
1463   int status;
1464   int event, error;
1465   XErrorHandler old_handler;
1466
1467   if (!XF86MiscQueryExtension(si->dpy, &event, &error))
1468     return;
1469
1470   XSync (si->dpy, False);
1471   error_handler_hit_p = False;
1472   old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
1473   XSync (si->dpy, False);
1474   status = XF86MiscSetGrabKeysState (si->dpy, !lock_p);
1475   XSync (si->dpy, False);
1476   if (error_handler_hit_p) status = 666;
1477
1478   if (!lock_p && status == MiscExtGrabStateAlready)
1479     status = MiscExtGrabStateSuccess;  /* shut up, consider this success */
1480
1481   if (p->verbose_p && status != MiscExtGrabStateSuccess)
1482     fprintf (stderr, "%s: error: XF86MiscSetGrabKeysState(%d) returned %s\n",
1483              blurb(), !lock_p,
1484              (status == MiscExtGrabStateSuccess ? "MiscExtGrabStateSuccess" :
1485               status == MiscExtGrabStateLocked  ? "MiscExtGrabStateLocked"  :
1486               status == MiscExtGrabStateAlready ? "MiscExtGrabStateAlready" :
1487               status == 666 ? "an X error" :
1488               "unknown value"));
1489
1490   XSync (si->dpy, False);
1491   XSetErrorHandler (old_handler);
1492   XSync (si->dpy, False);
1493 }
1494 #endif /* HAVE_XF86MISCSETGRABKEYSSTATE */
1495
1496
1497 \f
1498 /* This function enables and disables the C-Alt-Plus and C-Alt-Minus
1499    hot-keys, which normally change the resolution of the X server.
1500    We don't want people to be able to switch the server resolution
1501    while the screen is locked, because if they switch to a higher
1502    resolution, it could cause part of the underlying desktop to become
1503    exposed.
1504  */
1505 #ifdef HAVE_XF86VMODE
1506
1507 static void
1508 xfree_lock_mode_switch (saver_info *si, Bool lock_p)
1509 {
1510   static Bool any_mode_locked_p = False;
1511   saver_preferences *p = &si->prefs;
1512   int screen;
1513   int real_nscreens = ScreenCount (si->dpy);
1514   int event, error;
1515   Bool status;
1516   XErrorHandler old_handler;
1517
1518   if (any_mode_locked_p == lock_p)
1519     return;
1520   if (!XF86VidModeQueryExtension (si->dpy, &event, &error))
1521     return;
1522
1523   for (screen = 0; screen < real_nscreens; screen++)
1524     {
1525       XSync (si->dpy, False);
1526       old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
1527       error_handler_hit_p = False;
1528       status = XF86VidModeLockModeSwitch (si->dpy, screen, lock_p);
1529       XSync (si->dpy, False);
1530       XSetErrorHandler (old_handler);
1531       if (error_handler_hit_p) status = False;
1532
1533       if (status)
1534         any_mode_locked_p = lock_p;
1535
1536       if (!status && (p->verbose_p || !lock_p))
1537         /* Only print this when verbose, or when we locked but can't unlock.
1538            I tried printing this message whenever it comes up, but
1539            mode-locking always fails if DontZoom is set in XF86Config. */
1540         fprintf (stderr, "%s: %d: unable to %s mode switching!\n",
1541                  blurb(), screen, (lock_p ? "lock" : "unlock"));
1542       else if (p->verbose_p)
1543         fprintf (stderr, "%s: %d: %s mode switching.\n",
1544                  blurb(), screen, (lock_p ? "locked" : "unlocked"));
1545     }
1546 }
1547 #endif /* HAVE_XF86VMODE */
1548
1549 \f
1550 /* If the viewport has been scrolled since the screen was blanked,
1551    then scroll it back to where it belongs.  This function only exists
1552    to patch over a very brief race condition.
1553  */
1554 static void
1555 undo_vp_motion (saver_info *si)
1556 {
1557 #ifdef HAVE_XF86VMODE
1558   saver_preferences *p = &si->prefs;
1559   int screen;
1560   int real_nscreens = ScreenCount (si->dpy);
1561   int event, error;
1562
1563   if (!XF86VidModeQueryExtension (si->dpy, &event, &error))
1564     return;
1565
1566   for (screen = 0; screen < real_nscreens; screen++)
1567     {
1568       saver_screen_info *ssi = &si->screens[screen];
1569       int x, y;
1570       Bool status;
1571
1572       if (ssi->blank_vp_x == -1 && ssi->blank_vp_y == -1)
1573         break;
1574       if (!XF86VidModeGetViewPort (si->dpy, screen, &x, &y))
1575         return;
1576       if (ssi->blank_vp_x == x && ssi->blank_vp_y == y)
1577         return;
1578     
1579       /* We're going to move the viewport.  The mouse has just been grabbed on
1580          (and constrained to, thus warped to) the password window, so it is no
1581          longer near the edge of the screen.  However, wait a bit anyway, just
1582          to make sure the server drains its last motion event, so that the
1583          screen doesn't continue to scroll after we've reset the viewport.
1584        */
1585       XSync (si->dpy, False);
1586       usleep (250000);  /* 1/4 second */
1587       XSync (si->dpy, False);
1588
1589       status = XF86VidModeSetViewPort (si->dpy, screen,
1590                                        ssi->blank_vp_x, ssi->blank_vp_y);
1591
1592       if (!status)
1593         fprintf (stderr,
1594                  "%s: %d: unable to move vp from (%d,%d) back to (%d,%d)!\n",
1595                  blurb(), screen, x, y, ssi->blank_vp_x, ssi->blank_vp_y);
1596       else if (p->verbose_p)
1597         fprintf (stderr,
1598                  "%s: %d: vp moved to (%d,%d); moved it back to (%d,%d).\n",
1599                  blurb(), screen, x, y, ssi->blank_vp_x, ssi->blank_vp_y);
1600     }
1601 #endif /* HAVE_XF86VMODE */
1602 }
1603
1604
1605 \f
1606 /* Interactions
1607  */
1608
1609 static void
1610 passwd_animate_timer (XtPointer closure, XtIntervalId *id)
1611 {
1612   saver_info *si = (saver_info *) closure;
1613   int tick = 166;
1614   passwd_dialog_data *pw = si->pw_data;
1615
1616   if (!pw) return;
1617
1618   pw->ratio -= (1.0 / ((double) si->prefs.passwd_timeout / (double) tick));
1619   if (pw->ratio < 0)
1620     {
1621       pw->ratio = 0;
1622       if (si->unlock_state == ul_read)
1623         si->unlock_state = ul_time;
1624     }
1625
1626   update_passwd_window (si, 0, pw->ratio);
1627
1628   if (si->unlock_state == ul_read)
1629     pw->timer = XtAppAddTimeOut (si->app, tick, passwd_animate_timer,
1630                                  (XtPointer) si);
1631   else
1632     pw->timer = 0;
1633
1634   idle_timer ((XtPointer) si, 0);
1635 }
1636
1637
1638 static XComposeStatus *compose_status;
1639
1640 static void
1641 handle_login_button (saver_info *si, XEvent *event)
1642 {
1643   saver_preferences *p = &si->prefs;
1644   Bool mouse_in_box = False;
1645   Bool hit_p = False;
1646   passwd_dialog_data *pw = si->pw_data;
1647   saver_screen_info *ssi = pw->prompt_screen;
1648
1649   if (! pw->login_button_enabled_p)
1650     return;
1651
1652   mouse_in_box = 
1653     (event->xbutton.x >= pw->login_button_x &&
1654      event->xbutton.x <= pw->login_button_x + pw->login_button_width &&
1655      event->xbutton.y >= pw->login_button_y &&
1656      event->xbutton.y <= pw->login_button_y + pw->login_button_height);
1657
1658   if (ButtonRelease == event->xany.type &&
1659       pw->login_button_down_p &&
1660       mouse_in_box)
1661     {
1662       /* Only allow them to press the button once: don't want to
1663          accidentally launch a dozen gdm choosers if the machine
1664          is being slow.
1665        */
1666       hit_p = True;
1667       pw->login_button_enabled_p = False;
1668     }
1669
1670   pw->login_button_down_p = (mouse_in_box &&
1671                              ButtonRelease != event->xany.type);
1672
1673   update_passwd_window (si, 0, pw->ratio);
1674
1675   if (hit_p)
1676     fork_and_exec (ssi, p->new_login_command);
1677 }
1678
1679
1680 static void
1681 handle_unlock_button (saver_info *si, XEvent *event)
1682 {
1683   Bool mouse_in_box = False;
1684   passwd_dialog_data *pw = si->pw_data;
1685
1686   mouse_in_box =
1687     (event->xbutton.x >= pw->unlock_button_x &&
1688      event->xbutton.x <= pw->unlock_button_x + pw->unlock_button_width &&
1689      event->xbutton.y >= pw->unlock_button_y &&
1690      event->xbutton.y <= pw->unlock_button_y + pw->unlock_button_height);
1691
1692   if (ButtonRelease == event->xany.type &&
1693       pw->unlock_button_down_p &&
1694       mouse_in_box)
1695     finished_typing_passwd (si, pw);
1696
1697   pw->unlock_button_down_p = (mouse_in_box &&
1698                                 ButtonRelease != event->xany.type);
1699 }
1700
1701
1702 static void
1703 finished_typing_passwd (saver_info *si, passwd_dialog_data *pw)
1704 {
1705   if (si->unlock_state == ul_read)
1706     {
1707       update_passwd_window (si, "Checking...", pw->ratio);
1708       XSync (si->dpy, False);
1709
1710       si->unlock_state = ul_finished;
1711       update_passwd_window (si, "", pw->ratio);
1712     }
1713 }
1714
1715 static void
1716 handle_passwd_key (saver_info *si, XKeyEvent *event)
1717 {
1718   passwd_dialog_data *pw = si->pw_data;
1719   unsigned char decoded [MAX_BYTES_PER_CHAR * 10]; /* leave some slack */
1720   KeySym keysym = 0;
1721
1722   /* XLookupString may return more than one character via XRebindKeysym;
1723      and on some systems it returns multi-byte UTF-8 characters (contrary
1724      to its documentation, which says it returns only Latin1.)
1725
1726      It seems to only do so, however, if setlocale() has been called.
1727      See the code inside ENABLE_NLS in xscreensaver.c.
1728    */
1729   int decoded_size = XLookupString (event, (char *)decoded, sizeof(decoded),
1730                                     &keysym, compose_status);
1731
1732 #if 0
1733   {
1734     const char *ks = XKeysymToString (keysym);
1735     int i;
1736     fprintf(stderr, "## %-12s\t=> %d\t", (ks ? ks : "(null)"), decoded_size);
1737     for (i = 0; i < decoded_size; i++)
1738       fprintf(stderr, "%c", decoded[i]);
1739     fprintf(stderr, "\t");
1740     for (i = 0; i < decoded_size; i++)
1741       fprintf(stderr, "\\%03o", ((unsigned char *)decoded)[i]);
1742     fprintf(stderr, "\n");
1743   }
1744 #endif
1745
1746   if (decoded_size > MAX_BYTES_PER_CHAR)
1747     {
1748       /* The multi-byte character returned is too large. */
1749       XBell (si->dpy, 0);
1750       return;
1751     }
1752
1753   decoded[decoded_size] = 0;
1754   pw->passwd_changed_p = True;
1755
1756   /* Add 10% to the time remaining every time a key is pressed. */
1757   pw->ratio += 0.1;
1758   if (pw->ratio > 1) pw->ratio = 1;
1759
1760   if (decoded_size == 1)                /* Handle single-char commands */
1761     {
1762       switch (*decoded)
1763         {
1764         case '\010': case '\177':                       /* Backspace */
1765           {
1766             /* kludgey way to get the number of "logical" characters. */
1767             int nchars = strlen (pw->typed_passwd_char_size);
1768             int nbytes = strlen (pw->typed_passwd);
1769             if (nbytes <= 0)
1770               XBell (si->dpy, 0);
1771             else
1772               {
1773                 int i;
1774                 for (i = pw->typed_passwd_char_size[nchars-1]; i >= 0; i--)
1775                   {
1776                     if (nbytes < 0) abort();
1777                     pw->typed_passwd[nbytes--] = 0;
1778                   }
1779                 pw->typed_passwd_char_size[nchars-1] = 0;
1780               }
1781           }
1782           break;
1783
1784         case '\012': case '\015':                       /* Enter */
1785           finished_typing_passwd (si, pw);
1786           break;
1787
1788         case '\033':                                    /* Escape */
1789           si->unlock_state = ul_cancel;
1790           break;
1791
1792         case '\025': case '\030':                       /* Erase line */
1793           memset (pw->typed_passwd, 0, sizeof (pw->typed_passwd));
1794           memset (pw->typed_passwd_char_size, 0, 
1795                   sizeof (pw->typed_passwd_char_size));
1796           break;
1797
1798         default:
1799           if (*decoded < ' ' && *decoded != '\t')       /* Other ctrl char */
1800             XBell (si->dpy, 0);
1801           else
1802             goto SELF_INSERT;
1803           break;
1804         }
1805     }
1806   else
1807     {
1808       int nbytes, nchars;
1809     SELF_INSERT:
1810       nbytes = strlen (pw->typed_passwd);
1811       nchars = strlen (pw->typed_passwd_char_size);
1812       if (nchars + 1 >= sizeof (pw->typed_passwd_char_size)-1 ||
1813           nbytes + decoded_size >= sizeof (pw->typed_passwd)-1)  /* overflow */
1814         XBell (si->dpy, 0);
1815       else
1816         {
1817           pw->typed_passwd_char_size[nchars] = decoded_size;
1818           pw->typed_passwd_char_size[nchars+1] = 0;
1819           memcpy (pw->typed_passwd + nbytes, decoded, decoded_size);
1820           pw->typed_passwd[nbytes + decoded_size] = 0;
1821         }
1822     }
1823
1824   if (pw->echo_input)
1825     {
1826       /* If the input is wider than the text box, only show the last portion,
1827          to simulate a horizontally-scrolling text field. */
1828       int chars_in_pwfield = (pw->passwd_field_width /
1829                               pw->passwd_font->max_bounds.width);
1830       const char *output = pw->typed_passwd;
1831       if (strlen(output) > chars_in_pwfield)
1832         output += (strlen(output) - chars_in_pwfield);
1833       update_passwd_window (si, output, pw->ratio);
1834     }
1835   else if (pw->show_stars_p)
1836     {
1837       int nchars = strlen (pw->typed_passwd_char_size);
1838       char *stars = 0;
1839       stars = (char *) malloc(nchars + 1);
1840       memset (stars, '*', nchars);
1841       stars[nchars] = 0;
1842       update_passwd_window (si, stars, pw->ratio);
1843       free (stars);
1844     }
1845   else
1846     {
1847       update_passwd_window (si, "", pw->ratio);
1848     }
1849 }
1850
1851
1852 static void
1853 passwd_event_loop (saver_info *si)
1854 {
1855   saver_preferences *p = &si->prefs;
1856   char *msg = 0;
1857
1858   /* We have to go through this union bullshit because gcc-4.4.0 has
1859      stricter struct-aliasing rules.  Without this, the optimizer
1860      can fuck things up.
1861    */
1862   union {
1863     XEvent x_event;
1864 # ifdef HAVE_RANDR
1865     XRRScreenChangeNotifyEvent xrr_event;
1866 # endif /* HAVE_RANDR */
1867   } event;
1868
1869   passwd_animate_timer ((XtPointer) si, 0);
1870   reset_watchdog_timer (si, False);     /* Disable watchdog while dialog up */
1871
1872   while (si->unlock_state == ul_read)
1873     {
1874       XtAppNextEvent (si->app, &event.x_event);
1875
1876 #ifdef HAVE_RANDR
1877       if (si->using_randr_extension &&
1878           (event.x_event.type == 
1879            (si->randr_event_number + RRScreenChangeNotify)))
1880         {
1881           /* The Resize and Rotate extension sends an event when the
1882              size, rotation, or refresh rate of any screen has changed. */
1883
1884           if (p->verbose_p)
1885             {
1886               /* XRRRootToScreen is in Xrandr.h 1.4, 2001/06/07 */
1887               int screen = XRRRootToScreen(si->dpy, event.xrr_event.window);
1888                 fprintf (stderr, "%s: %d: screen change event received\n",
1889                          blurb(), screen);
1890             }
1891
1892 #ifdef RRScreenChangeNotifyMask
1893           /* Inform Xlib that it's ok to update its data structures. */
1894           XRRUpdateConfiguration(&event.x_event); /* Xrandr.h 1.9, 2002/09/29*/
1895 #endif /* RRScreenChangeNotifyMask */
1896
1897           /* Resize the existing xscreensaver windows and cached ssi data. */
1898           if (update_screen_layout (si))
1899             {
1900               if (p->verbose_p)
1901                 {
1902                   fprintf (stderr, "%s: new layout:\n", blurb());
1903                   describe_monitor_layout (si);
1904                 }
1905               resize_screensaver_window (si);
1906             }
1907         }
1908       else
1909 #endif /* HAVE_RANDR */
1910
1911       if (event.x_event.xany.window == si->passwd_dialog && 
1912           event.x_event.xany.type == Expose)
1913         draw_passwd_window (si);
1914       else if (event.x_event.xany.type == KeyPress)
1915         {
1916           handle_passwd_key (si, &event.x_event.xkey);
1917           si->pw_data->caps_p = (event.x_event.xkey.state & LockMask);
1918         }
1919       else if (event.x_event.xany.type == ButtonPress || 
1920                event.x_event.xany.type == ButtonRelease)
1921         {
1922           si->pw_data->button_state_changed_p = True;
1923           handle_unlock_button (si, &event.x_event);
1924           if (si->pw_data->login_button_p)
1925             handle_login_button (si, &event.x_event);
1926         }
1927       else
1928         XtDispatchEvent (&event.x_event);
1929     }
1930
1931   switch (si->unlock_state)
1932     {
1933     case ul_cancel: msg = ""; break;
1934     case ul_time: msg = "Timed out!"; break;
1935     case ul_finished: msg = "Checking..."; break;
1936     default: msg = 0; break;
1937     }
1938
1939   if (p->verbose_p)
1940     switch (si->unlock_state) {
1941     case ul_cancel:
1942       fprintf (stderr, "%s: input cancelled.\n", blurb()); break;
1943     case ul_time:
1944       fprintf (stderr, "%s: input timed out.\n", blurb()); break;
1945     case ul_finished:
1946       fprintf (stderr, "%s: input finished.\n", blurb()); break;
1947     default: break;
1948     }
1949
1950   if (msg)
1951     {
1952       si->pw_data->i_beam = 0;
1953       update_passwd_window (si, msg, 0.0);
1954       XSync (si->dpy, False);
1955
1956       /* Swallow all pending KeyPress/KeyRelease events. */
1957       {
1958         XEvent e;
1959         while (XCheckMaskEvent (si->dpy, KeyPressMask|KeyReleaseMask, &e))
1960           ;
1961       }
1962     }
1963
1964   reset_watchdog_timer (si, True);      /* Re-enable watchdog */
1965 }
1966
1967
1968 static void
1969 handle_typeahead (saver_info *si)
1970 {
1971   passwd_dialog_data *pw = si->pw_data;
1972   int i;
1973   if (!si->unlock_typeahead)
1974     return;
1975
1976   pw->passwd_changed_p = True;
1977
1978   i = strlen (si->unlock_typeahead);
1979   if (i >= sizeof(pw->typed_passwd) - 1)
1980     i = sizeof(pw->typed_passwd) - 1;
1981
1982   memcpy (pw->typed_passwd, si->unlock_typeahead, i);
1983   pw->typed_passwd [i] = 0;
1984   {
1985     int j;
1986     char *c = pw->typed_passwd_char_size;
1987     for (j = 0; j < i; j++)
1988       *c++ = 1;
1989     *c = 0;
1990   }
1991
1992   memset (si->unlock_typeahead, '*', strlen(si->unlock_typeahead));
1993   si->unlock_typeahead[i] = 0;
1994   update_passwd_window (si, si->unlock_typeahead, pw->ratio);
1995
1996   free (si->unlock_typeahead);
1997   si->unlock_typeahead = 0;
1998 }
1999
2000
2001 /**
2002  * Returns a copy of the input string with trailing whitespace removed.
2003  * Whitespace is anything considered so by isspace().
2004  * It is safe to call this with NULL, in which case NULL will be returned.
2005  * The returned string (if not NULL) should be freed by the caller with free().
2006  */
2007 static char *
2008 remove_trailing_whitespace(const char *str)
2009 {
2010   size_t len;
2011   char *newstr, *chr;
2012
2013   if (!str)
2014     return NULL;
2015
2016   len = strlen(str);
2017
2018   newstr = malloc(len + 1);
2019   if (!newstr)
2020     return NULL;
2021
2022   (void) strcpy(newstr, str);
2023   chr = newstr + len;
2024   while (isspace(*--chr) && chr >= newstr)
2025     *chr = '\0';
2026
2027   return newstr;
2028 }
2029
2030
2031 /*
2032  * The authentication conversation function.
2033  * Like a PAM conversation function, this accepts multiple messages in a single
2034  * round. It then splits them into individual messages for display on the
2035  * passwd dialog. A message sequence of info or error followed by a prompt will
2036  * be reduced into a single dialog window.
2037  *
2038  * Returns 0 on success or -1 if some problem occurred (cancelled, OOM, etc.)
2039  */
2040 int
2041 gui_auth_conv(int num_msg,
2042           const struct auth_message auth_msgs[],
2043           struct auth_response **resp,
2044           saver_info *si)
2045 {
2046   int i;
2047   const char *info_msg, *prompt;
2048   struct auth_response *responses;
2049
2050   if (si->unlock_state == ul_cancel ||
2051       si->unlock_state == ul_time)
2052     /* If we've already cancelled or timed out in this PAM conversation,
2053        don't prompt again even if PAM asks us to! */
2054     return -1;
2055
2056   if (!(responses = calloc(num_msg, sizeof(struct auth_response))))
2057     goto fail;
2058
2059   for (i = 0; i < num_msg; ++i)
2060     {
2061       info_msg = prompt = NULL;
2062
2063       /* See if there is a following message that can be shown at the same
2064        * time */
2065       if (auth_msgs[i].type == AUTH_MSGTYPE_INFO
2066           && i+1 < num_msg
2067           && (   auth_msgs[i+1].type == AUTH_MSGTYPE_PROMPT_NOECHO
2068               || auth_msgs[i+1].type == AUTH_MSGTYPE_PROMPT_ECHO)
2069          )
2070         {
2071           info_msg = auth_msgs[i].msg;
2072           prompt = auth_msgs[++i].msg;
2073         }
2074       else
2075         {
2076           if (   auth_msgs[i].type == AUTH_MSGTYPE_INFO
2077               || auth_msgs[i].type == AUTH_MSGTYPE_ERROR)
2078             info_msg = auth_msgs[i].msg;
2079           else
2080             prompt = auth_msgs[i].msg;
2081         }
2082
2083       {
2084         char *info_msg_trimmed, *prompt_trimmed;
2085
2086         /* Trailing whitespace looks bad in a GUI */
2087         info_msg_trimmed = remove_trailing_whitespace(info_msg);
2088         prompt_trimmed = remove_trailing_whitespace(prompt);
2089
2090         if (make_passwd_window(si, info_msg_trimmed, prompt_trimmed,
2091                                auth_msgs[i].type == AUTH_MSGTYPE_PROMPT_ECHO
2092                                ? True : False)
2093             < 0)
2094           goto fail;
2095
2096         if (info_msg_trimmed)
2097           free(info_msg_trimmed);
2098
2099         if (prompt_trimmed)
2100           free(prompt_trimmed);
2101       }
2102
2103       compose_status = calloc (1, sizeof (*compose_status));
2104       if (!compose_status)
2105         goto fail;
2106
2107       si->unlock_state = ul_read;
2108
2109       handle_typeahead (si);
2110       passwd_event_loop (si);
2111
2112       if (si->unlock_state == ul_cancel)
2113         goto fail;
2114
2115       responses[i].response = strdup(si->pw_data->typed_passwd);
2116
2117       /* Cache the first response to a PROMPT_NOECHO to save prompting for
2118        * each auth mechanism. */
2119       if (si->cached_passwd == NULL &&
2120           auth_msgs[i].type == AUTH_MSGTYPE_PROMPT_NOECHO)
2121         si->cached_passwd = strdup(responses[i].response);
2122
2123       free (compose_status);
2124       compose_status = 0;
2125     }
2126
2127   *resp = responses;
2128
2129   return (si->unlock_state == ul_finished) ? 0 : -1;
2130
2131 fail:
2132   if (compose_status)
2133     free (compose_status);
2134   compose_status = 0;
2135
2136   if (responses)
2137     {
2138       for (i = 0; i < num_msg; ++i)
2139         if (responses[i].response)
2140           free (responses[i].response);
2141       free (responses);
2142     }
2143
2144   return -1;
2145 }
2146
2147
2148 void
2149 auth_finished_cb (saver_info *si)
2150 {
2151   char buf[1024];
2152   const char *s;
2153
2154   /* If we have something to say, put the dialog back up for a few seconds
2155      to display it.  Otherwise, don't bother.
2156    */
2157
2158   if (si->unlock_state == ul_fail &&            /* failed with caps lock on */
2159       si->pw_data && si->pw_data->caps_p)
2160     s = "Authentication failed (Caps Lock?)";
2161   else if (si->unlock_state == ul_fail)         /* failed without caps lock */
2162     s = "Authentication failed!";
2163   else if (si->unlock_state == ul_success &&    /* good, but report failures */
2164            si->unlock_failures > 0)
2165     {
2166       if (si->unlock_failures == 1)
2167         s = "There has been\n1 failed login attempt.";
2168       else
2169         {
2170           sprintf (buf, "There have been\n%d failed login attempts.",
2171                    si->unlock_failures);
2172           s = buf;
2173         }
2174       si->unlock_failures = 0;
2175
2176       /* ignore failures if they all were too recent */
2177       if (time((time_t *) 0) - si->unlock_failure_time 
2178           < si->prefs.auth_warning_slack)
2179         goto END;
2180     }
2181   else                                          /* good, with no failures, */
2182     goto END;                                   /* or timeout, or cancel. */
2183
2184   make_passwd_window (si, s, NULL, True);
2185   XSync (si->dpy, False);
2186
2187   {
2188     int secs = 4;
2189     time_t start = time ((time_t *) 0);
2190     XEvent event;
2191     while (time ((time_t *) 0) < start + secs)
2192       if (XPending (si->dpy))
2193         {
2194           XNextEvent (si->dpy, &event);
2195           if (event.xany.window == si->passwd_dialog && 
2196               event.xany.type == Expose)
2197             draw_passwd_window (si);
2198           else if (event.xany.type == ButtonPress || 
2199                    event.xany.type == KeyPress)
2200             break;
2201           XSync (si->dpy, False);
2202         }
2203       else
2204         usleep (250000);  /* 1/4 second */
2205   }
2206
2207  END:
2208   if (si->pw_data)
2209     destroy_passwd_window (si);
2210 }
2211
2212
2213 Bool
2214 unlock_p (saver_info *si)
2215 {
2216   saver_preferences *p = &si->prefs;
2217
2218   if (!si->unlock_cb)
2219     {
2220       fprintf(stderr, "%s: Error: no unlock function specified!\n", blurb());
2221       return False;
2222     }
2223
2224   raise_window (si, True, True, True);
2225
2226   xss_authenticate(si, p->verbose_p);
2227
2228   return (si->unlock_state == ul_success);
2229 }
2230
2231
2232 void
2233 set_locked_p (saver_info *si, Bool locked_p)
2234 {
2235   si->locked_p = locked_p;
2236
2237 #ifdef HAVE_XHPDISABLERESET
2238   hp_lock_reset (si, locked_p);                 /* turn off/on C-Sh-Reset */
2239 #endif
2240 #ifdef HAVE_XF86VMODE
2241   xfree_lock_mode_switch (si, locked_p);        /* turn off/on C-Alt-Plus */
2242 #endif
2243 #ifdef HAVE_XF86MISCSETGRABKEYSSTATE
2244   xfree_lock_grab_smasher (si, locked_p);       /* turn off/on C-Alt-KP-*,/ */
2245 #endif
2246
2247   store_saver_status (si);                      /* store locked-p */
2248 }
2249
2250
2251 #else  /*  NO_LOCKING -- whole file */
2252
2253 void
2254 set_locked_p (saver_info *si, Bool locked_p)
2255 {
2256   if (locked_p) abort();
2257 }
2258
2259 #endif /* !NO_LOCKING */