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