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