From http://www.jwz.org/xscreensaver/xscreensaver-5.27.tar.gz
[xscreensaver] / driver / lock.c
1 /* lock.c --- handling the password dialog for locking-mode.
2  * xscreensaver, Copyright (c) 1993-2014 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 #if defined(HAVE_XF86MISCSETGRABKEYSSTATE) || defined(HAVE_XF86VMODE)
1426
1427 static Bool error_handler_hit_p = False;
1428
1429 static int
1430 ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
1431 {
1432   error_handler_hit_p = True;
1433   return 0;
1434 }
1435
1436 #endif /* HAVE_XF86MISCSETGRABKEYSSTATE || HAVE_XF86VMODE */
1437
1438
1439 #ifdef HAVE_XHPDISABLERESET
1440 /* This function enables and disables the C-Sh-Reset hot-key, which
1441    normally resets the X server (logging out the logged-in user.)
1442    We don't want random people to be able to do that while the
1443    screen is locked.
1444  */
1445 static void
1446 hp_lock_reset (saver_info *si, Bool lock_p)
1447 {
1448   static Bool hp_locked_p = False;
1449
1450   /* Calls to XHPDisableReset and XHPEnableReset must be balanced,
1451      or BadAccess errors occur.  (It's ok for this to be global,
1452      since it affects the whole machine, not just the current screen.)
1453   */
1454   if (hp_locked_p == lock_p)
1455     return;
1456
1457   if (lock_p)
1458     XHPDisableReset (si->dpy);
1459   else
1460     XHPEnableReset (si->dpy);
1461   hp_locked_p = lock_p;
1462 }
1463 #endif /* HAVE_XHPDISABLERESET */
1464
1465 \f
1466 #ifdef HAVE_XF86MISCSETGRABKEYSSTATE
1467
1468 /* This function enables and disables the Ctrl-Alt-KP_star and 
1469    Ctrl-Alt-KP_slash hot-keys, which (in XFree86 4.2) break any
1470    grabs and/or kill the grabbing client.  That would effectively
1471    unlock the screen, so we don't like that.
1472
1473    The Ctrl-Alt-KP_star and Ctrl-Alt-KP_slash hot-keys only exist
1474    if AllowDeactivateGrabs and/or AllowClosedownGrabs are turned on
1475    in XF86Config.  I believe they are disabled by default.
1476
1477    This does not affect any other keys (specifically Ctrl-Alt-BS or
1478    Ctrl-Alt-F1) but I wish it did.  Maybe it will someday.
1479  */
1480 static void
1481 xfree_lock_grab_smasher (saver_info *si, Bool lock_p)
1482 {
1483   saver_preferences *p = &si->prefs;
1484   int status;
1485   int event, error;
1486   XErrorHandler old_handler;
1487
1488   if (!XF86MiscQueryExtension(si->dpy, &event, &error))
1489     return;
1490
1491   XSync (si->dpy, False);
1492   error_handler_hit_p = False;
1493   old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
1494   XSync (si->dpy, False);
1495   status = XF86MiscSetGrabKeysState (si->dpy, !lock_p);
1496   XSync (si->dpy, False);
1497   if (error_handler_hit_p) status = 666;
1498
1499   if (!lock_p && status == MiscExtGrabStateAlready)
1500     status = MiscExtGrabStateSuccess;  /* shut up, consider this success */
1501
1502   if (p->verbose_p && status != MiscExtGrabStateSuccess)
1503     fprintf (stderr, "%s: error: XF86MiscSetGrabKeysState(%d) returned %s\n",
1504              blurb(), !lock_p,
1505              (status == MiscExtGrabStateSuccess ? "MiscExtGrabStateSuccess" :
1506               status == MiscExtGrabStateLocked  ? "MiscExtGrabStateLocked"  :
1507               status == MiscExtGrabStateAlready ? "MiscExtGrabStateAlready" :
1508               status == 666 ? "an X error" :
1509               "unknown value"));
1510
1511   XSync (si->dpy, False);
1512   XSetErrorHandler (old_handler);
1513   XSync (si->dpy, False);
1514 }
1515 #endif /* HAVE_XF86MISCSETGRABKEYSSTATE */
1516
1517
1518 \f
1519 /* This function enables and disables the C-Alt-Plus and C-Alt-Minus
1520    hot-keys, which normally change the resolution of the X server.
1521    We don't want people to be able to switch the server resolution
1522    while the screen is locked, because if they switch to a higher
1523    resolution, it could cause part of the underlying desktop to become
1524    exposed.
1525  */
1526 #ifdef HAVE_XF86VMODE
1527
1528 static void
1529 xfree_lock_mode_switch (saver_info *si, Bool lock_p)
1530 {
1531   static Bool any_mode_locked_p = False;
1532   saver_preferences *p = &si->prefs;
1533   int screen;
1534   int real_nscreens = ScreenCount (si->dpy);
1535   int event, error;
1536   Bool status;
1537   XErrorHandler old_handler;
1538
1539   if (any_mode_locked_p == lock_p)
1540     return;
1541   if (!XF86VidModeQueryExtension (si->dpy, &event, &error))
1542     return;
1543
1544   for (screen = 0; screen < real_nscreens; screen++)
1545     {
1546       XSync (si->dpy, False);
1547       old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
1548       error_handler_hit_p = False;
1549       status = XF86VidModeLockModeSwitch (si->dpy, screen, lock_p);
1550       XSync (si->dpy, False);
1551       XSetErrorHandler (old_handler);
1552       if (error_handler_hit_p) status = False;
1553
1554       if (status)
1555         any_mode_locked_p = lock_p;
1556
1557       if (!status && (p->verbose_p || !lock_p))
1558         /* Only print this when verbose, or when we locked but can't unlock.
1559            I tried printing this message whenever it comes up, but
1560            mode-locking always fails if DontZoom is set in XF86Config. */
1561         fprintf (stderr, "%s: %d: unable to %s mode switching!\n",
1562                  blurb(), screen, (lock_p ? "lock" : "unlock"));
1563       else if (p->verbose_p)
1564         fprintf (stderr, "%s: %d: %s mode switching.\n",
1565                  blurb(), screen, (lock_p ? "locked" : "unlocked"));
1566     }
1567 }
1568 #endif /* HAVE_XF86VMODE */
1569
1570 \f
1571 /* If the viewport has been scrolled since the screen was blanked,
1572    then scroll it back to where it belongs.  This function only exists
1573    to patch over a very brief race condition.
1574  */
1575 static void
1576 undo_vp_motion (saver_info *si)
1577 {
1578 #ifdef HAVE_XF86VMODE
1579   saver_preferences *p = &si->prefs;
1580   int screen;
1581   int real_nscreens = ScreenCount (si->dpy);
1582   int event, error;
1583
1584   if (!XF86VidModeQueryExtension (si->dpy, &event, &error))
1585     return;
1586
1587   for (screen = 0; screen < real_nscreens; screen++)
1588     {
1589       saver_screen_info *ssi = &si->screens[screen];
1590       int x, y;
1591       Bool status;
1592
1593       if (ssi->blank_vp_x == -1 && ssi->blank_vp_y == -1)
1594         break;
1595       if (!XF86VidModeGetViewPort (si->dpy, screen, &x, &y))
1596         return;
1597       if (ssi->blank_vp_x == x && ssi->blank_vp_y == y)
1598         return;
1599     
1600       /* We're going to move the viewport.  The mouse has just been grabbed on
1601          (and constrained to, thus warped to) the password window, so it is no
1602          longer near the edge of the screen.  However, wait a bit anyway, just
1603          to make sure the server drains its last motion event, so that the
1604          screen doesn't continue to scroll after we've reset the viewport.
1605        */
1606       XSync (si->dpy, False);
1607       usleep (250000);  /* 1/4 second */
1608       XSync (si->dpy, False);
1609
1610       status = XF86VidModeSetViewPort (si->dpy, screen,
1611                                        ssi->blank_vp_x, ssi->blank_vp_y);
1612
1613       if (!status)
1614         fprintf (stderr,
1615                  "%s: %d: unable to move vp from (%d,%d) back to (%d,%d)!\n",
1616                  blurb(), screen, x, y, ssi->blank_vp_x, ssi->blank_vp_y);
1617       else if (p->verbose_p)
1618         fprintf (stderr,
1619                  "%s: %d: vp moved to (%d,%d); moved it back to (%d,%d).\n",
1620                  blurb(), screen, x, y, ssi->blank_vp_x, ssi->blank_vp_y);
1621     }
1622 #endif /* HAVE_XF86VMODE */
1623 }
1624
1625
1626 \f
1627 /* Interactions
1628  */
1629
1630 static void
1631 passwd_animate_timer (XtPointer closure, XtIntervalId *id)
1632 {
1633   saver_info *si = (saver_info *) closure;
1634   int tick = 166;
1635   passwd_dialog_data *pw = si->pw_data;
1636
1637   if (!pw) return;
1638
1639   pw->ratio -= (1.0 / ((double) si->prefs.passwd_timeout / (double) tick));
1640   if (pw->ratio < 0)
1641     {
1642       pw->ratio = 0;
1643       if (si->unlock_state == ul_read)
1644         si->unlock_state = ul_time;
1645     }
1646
1647   update_passwd_window (si, 0, pw->ratio);
1648
1649   if (si->unlock_state == ul_read)
1650     pw->timer = XtAppAddTimeOut (si->app, tick, passwd_animate_timer,
1651                                  (XtPointer) si);
1652   else
1653     pw->timer = 0;
1654
1655   idle_timer ((XtPointer) si, 0);
1656 }
1657
1658
1659 static XComposeStatus *compose_status;
1660
1661 static void
1662 handle_login_button (saver_info *si, XEvent *event)
1663 {
1664   saver_preferences *p = &si->prefs;
1665   Bool mouse_in_box = False;
1666   Bool hit_p = False;
1667   passwd_dialog_data *pw = si->pw_data;
1668   saver_screen_info *ssi = pw->prompt_screen;
1669
1670   if (! pw->login_button_enabled_p)
1671     return;
1672
1673   mouse_in_box = 
1674     (event->xbutton.x >= pw->login_button_x &&
1675      event->xbutton.x <= pw->login_button_x + pw->login_button_width &&
1676      event->xbutton.y >= pw->login_button_y &&
1677      event->xbutton.y <= pw->login_button_y + pw->login_button_height);
1678
1679   if (ButtonRelease == event->xany.type &&
1680       pw->login_button_down_p &&
1681       mouse_in_box)
1682     {
1683       /* Only allow them to press the button once: don't want to
1684          accidentally launch a dozen gdm choosers if the machine
1685          is being slow.
1686        */
1687       hit_p = True;
1688       pw->login_button_enabled_p = False;
1689     }
1690
1691   pw->login_button_down_p = (mouse_in_box &&
1692                              ButtonRelease != event->xany.type);
1693
1694   update_passwd_window (si, 0, pw->ratio);
1695
1696   if (hit_p)
1697     fork_and_exec (ssi, p->new_login_command);
1698 }
1699
1700
1701 static void
1702 handle_unlock_button (saver_info *si, XEvent *event)
1703 {
1704   Bool mouse_in_box = False;
1705   passwd_dialog_data *pw = si->pw_data;
1706
1707   mouse_in_box =
1708     (event->xbutton.x >= pw->unlock_button_x &&
1709      event->xbutton.x <= pw->unlock_button_x + pw->unlock_button_width &&
1710      event->xbutton.y >= pw->unlock_button_y &&
1711      event->xbutton.y <= pw->unlock_button_y + pw->unlock_button_height);
1712
1713   if (ButtonRelease == event->xany.type &&
1714       pw->unlock_button_down_p &&
1715       mouse_in_box)
1716     finished_typing_passwd (si, pw);
1717
1718   pw->unlock_button_down_p = (mouse_in_box &&
1719                                 ButtonRelease != event->xany.type);
1720 }
1721
1722
1723 static void
1724 finished_typing_passwd (saver_info *si, passwd_dialog_data *pw)
1725 {
1726   if (si->unlock_state == ul_read)
1727     {
1728       update_passwd_window (si, "Checking...", pw->ratio);
1729       XSync (si->dpy, False);
1730
1731       si->unlock_state = ul_finished;
1732       update_passwd_window (si, "", pw->ratio);
1733     }
1734 }
1735
1736 static void
1737 handle_passwd_key (saver_info *si, XKeyEvent *event)
1738 {
1739   passwd_dialog_data *pw = si->pw_data;
1740   unsigned char decoded [MAX_BYTES_PER_CHAR * 10]; /* leave some slack */
1741   KeySym keysym = 0;
1742
1743   /* XLookupString may return more than one character via XRebindKeysym;
1744      and on some systems it returns multi-byte UTF-8 characters (contrary
1745      to its documentation, which says it returns only Latin1.)
1746
1747      It seems to only do so, however, if setlocale() has been called.
1748      See the code inside ENABLE_NLS in xscreensaver.c.
1749    */
1750   int decoded_size = XLookupString (event, (char *)decoded, sizeof(decoded),
1751                                     &keysym, compose_status);
1752
1753 #if 0
1754   {
1755     const char *ks = XKeysymToString (keysym);
1756     int i;
1757     fprintf(stderr, "## %-12s\t=> %d\t", (ks ? ks : "(null)"), decoded_size);
1758     for (i = 0; i < decoded_size; i++)
1759       fprintf(stderr, "%c", decoded[i]);
1760     fprintf(stderr, "\t");
1761     for (i = 0; i < decoded_size; i++)
1762       fprintf(stderr, "\\%03o", ((unsigned char *)decoded)[i]);
1763     fprintf(stderr, "\n");
1764   }
1765 #endif
1766
1767   if (decoded_size > MAX_BYTES_PER_CHAR)
1768     {
1769       /* The multi-byte character returned is too large. */
1770       XBell (si->dpy, 0);
1771       return;
1772     }
1773
1774   decoded[decoded_size] = 0;
1775   pw->passwd_changed_p = True;
1776
1777   /* Add 10% to the time remaining every time a key is pressed. */
1778   pw->ratio += 0.1;
1779   if (pw->ratio > 1) pw->ratio = 1;
1780
1781   if (decoded_size == 1)                /* Handle single-char commands */
1782     {
1783       switch (*decoded)
1784         {
1785         case '\010': case '\177':                       /* Backspace */
1786           {
1787             /* kludgey way to get the number of "logical" characters. */
1788             int nchars = strlen (pw->typed_passwd_char_size);
1789             int nbytes = strlen (pw->typed_passwd);
1790             if (nbytes <= 0)
1791               XBell (si->dpy, 0);
1792             else
1793               {
1794                 int i;
1795                 for (i = pw->typed_passwd_char_size[nchars-1]; i >= 0; i--)
1796                   {
1797                     if (nbytes < 0) abort();
1798                     pw->typed_passwd[nbytes--] = 0;
1799                   }
1800                 pw->typed_passwd_char_size[nchars-1] = 0;
1801               }
1802           }
1803           break;
1804
1805         case '\012': case '\015':                       /* Enter */
1806           finished_typing_passwd (si, pw);
1807           break;
1808
1809         case '\033':                                    /* Escape */
1810           si->unlock_state = ul_cancel;
1811           break;
1812
1813         case '\025': case '\030':                       /* Erase line */
1814           memset (pw->typed_passwd, 0, sizeof (pw->typed_passwd));
1815           memset (pw->typed_passwd_char_size, 0, 
1816                   sizeof (pw->typed_passwd_char_size));
1817           break;
1818
1819         default:
1820           if (*decoded < ' ' && *decoded != '\t')       /* Other ctrl char */
1821             XBell (si->dpy, 0);
1822           else
1823             goto SELF_INSERT;
1824           break;
1825         }
1826     }
1827   else
1828     {
1829       int nbytes, nchars;
1830     SELF_INSERT:
1831       nbytes = strlen (pw->typed_passwd);
1832       nchars = strlen (pw->typed_passwd_char_size);
1833       if (nchars + 1 >= sizeof (pw->typed_passwd_char_size)-1 ||
1834           nbytes + decoded_size >= sizeof (pw->typed_passwd)-1)  /* overflow */
1835         XBell (si->dpy, 0);
1836       else
1837         {
1838           pw->typed_passwd_char_size[nchars] = decoded_size;
1839           pw->typed_passwd_char_size[nchars+1] = 0;
1840           memcpy (pw->typed_passwd + nbytes, decoded, decoded_size);
1841           pw->typed_passwd[nbytes + decoded_size] = 0;
1842         }
1843     }
1844
1845   if (pw->echo_input)
1846     {
1847       /* If the input is wider than the text box, only show the last portion,
1848          to simulate a horizontally-scrolling text field. */
1849       int chars_in_pwfield = (pw->passwd_field_width /
1850                               pw->passwd_font->max_bounds.width);
1851       const char *output = pw->typed_passwd;
1852       if (strlen(output) > chars_in_pwfield)
1853         output += (strlen(output) - chars_in_pwfield);
1854       update_passwd_window (si, output, pw->ratio);
1855     }
1856   else if (pw->show_stars_p)
1857     {
1858       int nchars = strlen (pw->typed_passwd_char_size);
1859       char *stars = 0;
1860       stars = (char *) malloc(nchars + 1);
1861       memset (stars, '*', nchars);
1862       stars[nchars] = 0;
1863       update_passwd_window (si, stars, pw->ratio);
1864       free (stars);
1865     }
1866   else
1867     {
1868       update_passwd_window (si, "", pw->ratio);
1869     }
1870 }
1871
1872
1873 static void
1874 passwd_event_loop (saver_info *si)
1875 {
1876   saver_preferences *p = &si->prefs;
1877   char *msg = 0;
1878
1879   /* We have to go through this union bullshit because gcc-4.4.0 has
1880      stricter struct-aliasing rules.  Without this, the optimizer
1881      can fuck things up.
1882    */
1883   union {
1884     XEvent x_event;
1885 # ifdef HAVE_RANDR
1886     XRRScreenChangeNotifyEvent xrr_event;
1887 # endif /* HAVE_RANDR */
1888   } event;
1889
1890   passwd_animate_timer ((XtPointer) si, 0);
1891   reset_watchdog_timer (si, False);     /* Disable watchdog while dialog up */
1892
1893   while (si->unlock_state == ul_read)
1894     {
1895       XtAppNextEvent (si->app, &event.x_event);
1896
1897 #ifdef HAVE_RANDR
1898       if (si->using_randr_extension &&
1899           (event.x_event.type == 
1900            (si->randr_event_number + RRScreenChangeNotify)))
1901         {
1902           /* The Resize and Rotate extension sends an event when the
1903              size, rotation, or refresh rate of any screen has changed. */
1904
1905           if (p->verbose_p)
1906             {
1907               /* XRRRootToScreen is in Xrandr.h 1.4, 2001/06/07 */
1908               int screen = XRRRootToScreen(si->dpy, event.xrr_event.window);
1909                 fprintf (stderr, "%s: %d: screen change event received\n",
1910                          blurb(), screen);
1911             }
1912
1913 #ifdef RRScreenChangeNotifyMask
1914           /* Inform Xlib that it's ok to update its data structures. */
1915           XRRUpdateConfiguration(&event.x_event); /* Xrandr.h 1.9, 2002/09/29*/
1916 #endif /* RRScreenChangeNotifyMask */
1917
1918           /* Resize the existing xscreensaver windows and cached ssi data. */
1919           if (update_screen_layout (si))
1920             {
1921               if (p->verbose_p)
1922                 {
1923                   fprintf (stderr, "%s: new layout:\n", blurb());
1924                   describe_monitor_layout (si);
1925                 }
1926               resize_screensaver_window (si);
1927             }
1928         }
1929       else
1930 #endif /* HAVE_RANDR */
1931
1932       if (event.x_event.xany.window == si->passwd_dialog && 
1933           event.x_event.xany.type == Expose)
1934         draw_passwd_window (si);
1935       else if (event.x_event.xany.type == KeyPress)
1936         {
1937           handle_passwd_key (si, &event.x_event.xkey);
1938           si->pw_data->caps_p = (event.x_event.xkey.state & LockMask);
1939         }
1940       else if (event.x_event.xany.type == ButtonPress || 
1941                event.x_event.xany.type == ButtonRelease)
1942         {
1943           si->pw_data->button_state_changed_p = True;
1944           handle_unlock_button (si, &event.x_event);
1945           if (si->pw_data->login_button_p)
1946             handle_login_button (si, &event.x_event);
1947         }
1948       else
1949         XtDispatchEvent (&event.x_event);
1950     }
1951
1952   switch (si->unlock_state)
1953     {
1954     case ul_cancel: msg = ""; break;
1955     case ul_time: msg = "Timed out!"; break;
1956     case ul_finished: msg = "Checking..."; break;
1957     default: msg = 0; break;
1958     }
1959
1960   if (p->verbose_p)
1961     switch (si->unlock_state) {
1962     case ul_cancel:
1963       fprintf (stderr, "%s: input cancelled.\n", blurb()); break;
1964     case ul_time:
1965       fprintf (stderr, "%s: input timed out.\n", blurb()); break;
1966     case ul_finished:
1967       fprintf (stderr, "%s: input finished.\n", blurb()); break;
1968     default: break;
1969     }
1970
1971   if (msg)
1972     {
1973       si->pw_data->i_beam = 0;
1974       update_passwd_window (si, msg, 0.0);
1975       XSync (si->dpy, False);
1976
1977       /* Swallow all pending KeyPress/KeyRelease events. */
1978       {
1979         XEvent e;
1980         while (XCheckMaskEvent (si->dpy, KeyPressMask|KeyReleaseMask, &e))
1981           ;
1982       }
1983     }
1984
1985   reset_watchdog_timer (si, True);      /* Re-enable watchdog */
1986 }
1987
1988
1989 static void
1990 handle_typeahead (saver_info *si)
1991 {
1992   passwd_dialog_data *pw = si->pw_data;
1993   int i;
1994   if (!si->unlock_typeahead)
1995     return;
1996
1997   pw->passwd_changed_p = True;
1998
1999   i = strlen (si->unlock_typeahead);
2000   if (i >= sizeof(pw->typed_passwd) - 1)
2001     i = sizeof(pw->typed_passwd) - 1;
2002
2003   memcpy (pw->typed_passwd, si->unlock_typeahead, i);
2004   pw->typed_passwd [i] = 0;
2005   {
2006     int j;
2007     char *c = pw->typed_passwd_char_size;
2008     for (j = 0; j < i; j++)
2009       *c++ = 1;
2010     *c = 0;
2011   }
2012
2013   memset (si->unlock_typeahead, '*', strlen(si->unlock_typeahead));
2014   si->unlock_typeahead[i] = 0;
2015   update_passwd_window (si, si->unlock_typeahead, pw->ratio);
2016
2017   free (si->unlock_typeahead);
2018   si->unlock_typeahead = 0;
2019 }
2020
2021
2022 /**
2023  * Returns a copy of the input string with trailing whitespace removed.
2024  * Whitespace is anything considered so by isspace().
2025  * It is safe to call this with NULL, in which case NULL will be returned.
2026  * The returned string (if not NULL) should be freed by the caller with free().
2027  */
2028 static char *
2029 remove_trailing_whitespace(const char *str)
2030 {
2031   size_t len;
2032   char *newstr, *chr;
2033
2034   if (!str)
2035     return NULL;
2036
2037   len = strlen(str);
2038
2039   newstr = malloc(len + 1);
2040   if (!newstr)
2041     return NULL;
2042
2043   (void) strcpy(newstr, str);
2044   chr = newstr + len;
2045   while (isspace(*--chr) && chr >= newstr)
2046     *chr = '\0';
2047
2048   return newstr;
2049 }
2050
2051
2052 /*
2053  * The authentication conversation function.
2054  * Like a PAM conversation function, this accepts multiple messages in a single
2055  * round. It then splits them into individual messages for display on the
2056  * passwd dialog. A message sequence of info or error followed by a prompt will
2057  * be reduced into a single dialog window.
2058  *
2059  * Returns 0 on success or -1 if some problem occurred (cancelled, OOM, etc.)
2060  */
2061 int
2062 gui_auth_conv(int num_msg,
2063           const struct auth_message auth_msgs[],
2064           struct auth_response **resp,
2065           saver_info *si)
2066 {
2067   int i;
2068   const char *info_msg, *prompt;
2069   struct auth_response *responses;
2070
2071   if (si->unlock_state == ul_cancel ||
2072       si->unlock_state == ul_time)
2073     /* If we've already cancelled or timed out in this PAM conversation,
2074        don't prompt again even if PAM asks us to! */
2075     return -1;
2076
2077   if (!(responses = calloc(num_msg, sizeof(struct auth_response))))
2078     goto fail;
2079
2080   for (i = 0; i < num_msg; ++i)
2081     {
2082       info_msg = prompt = NULL;
2083
2084       /* See if there is a following message that can be shown at the same
2085        * time */
2086       if (auth_msgs[i].type == AUTH_MSGTYPE_INFO
2087           && i+1 < num_msg
2088           && (   auth_msgs[i+1].type == AUTH_MSGTYPE_PROMPT_NOECHO
2089               || auth_msgs[i+1].type == AUTH_MSGTYPE_PROMPT_ECHO)
2090          )
2091         {
2092           info_msg = auth_msgs[i].msg;
2093           prompt = auth_msgs[++i].msg;
2094         }
2095       else
2096         {
2097           if (   auth_msgs[i].type == AUTH_MSGTYPE_INFO
2098               || auth_msgs[i].type == AUTH_MSGTYPE_ERROR)
2099             info_msg = auth_msgs[i].msg;
2100           else
2101             prompt = auth_msgs[i].msg;
2102         }
2103
2104       {
2105         char *info_msg_trimmed, *prompt_trimmed;
2106
2107         /* Trailing whitespace looks bad in a GUI */
2108         info_msg_trimmed = remove_trailing_whitespace(info_msg);
2109         prompt_trimmed = remove_trailing_whitespace(prompt);
2110
2111         if (make_passwd_window(si, info_msg_trimmed, prompt_trimmed,
2112                                auth_msgs[i].type == AUTH_MSGTYPE_PROMPT_ECHO
2113                                ? True : False)
2114             < 0)
2115           goto fail;
2116
2117         if (info_msg_trimmed)
2118           free(info_msg_trimmed);
2119
2120         if (prompt_trimmed)
2121           free(prompt_trimmed);
2122       }
2123
2124       compose_status = calloc (1, sizeof (*compose_status));
2125       if (!compose_status)
2126         goto fail;
2127
2128       si->unlock_state = ul_read;
2129
2130       handle_typeahead (si);
2131       passwd_event_loop (si);
2132
2133       if (si->unlock_state == ul_cancel)
2134         goto fail;
2135
2136       responses[i].response = strdup(si->pw_data->typed_passwd);
2137
2138       /* Cache the first response to a PROMPT_NOECHO to save prompting for
2139        * each auth mechanism. */
2140       if (si->cached_passwd == NULL &&
2141           auth_msgs[i].type == AUTH_MSGTYPE_PROMPT_NOECHO)
2142         si->cached_passwd = strdup(responses[i].response);
2143
2144       free (compose_status);
2145       compose_status = 0;
2146     }
2147
2148   *resp = responses;
2149
2150   return (si->unlock_state == ul_finished) ? 0 : -1;
2151
2152 fail:
2153   if (compose_status)
2154     free (compose_status);
2155   compose_status = 0;
2156
2157   if (responses)
2158     {
2159       for (i = 0; i < num_msg; ++i)
2160         if (responses[i].response)
2161           free (responses[i].response);
2162       free (responses);
2163     }
2164
2165   return -1;
2166 }
2167
2168
2169 void
2170 auth_finished_cb (saver_info *si)
2171 {
2172   char buf[1024];
2173   const char *s;
2174
2175   /* If we have something to say, put the dialog back up for a few seconds
2176      to display it.  Otherwise, don't bother.
2177    */
2178
2179   if (si->unlock_state == ul_fail &&            /* failed with caps lock on */
2180       si->pw_data && si->pw_data->caps_p)
2181     s = "Authentication failed (Caps Lock?)";
2182   else if (si->unlock_state == ul_fail)         /* failed without caps lock */
2183     s = "Authentication failed!";
2184   else if (si->unlock_state == ul_success &&    /* good, but report failures */
2185            si->unlock_failures > 0)
2186     {
2187       if (si->unlock_failures == 1)
2188         s = "There has been\n1 failed login attempt.";
2189       else
2190         {
2191           sprintf (buf, "There have been\n%d failed login attempts.",
2192                    si->unlock_failures);
2193           s = buf;
2194         }
2195       si->unlock_failures = 0;
2196     }
2197   else                                          /* good, with no failures, */
2198     goto END;                                   /* or timeout, or cancel. */
2199
2200   make_passwd_window (si, s, NULL, True);
2201   XSync (si->dpy, False);
2202
2203   {
2204     int secs = 4;
2205     time_t start = time ((time_t *) 0);
2206     XEvent event;
2207     while (time ((time_t *) 0) < start + secs)
2208       if (XPending (si->dpy))
2209         {
2210           XNextEvent (si->dpy, &event);
2211           if (event.xany.window == si->passwd_dialog && 
2212               event.xany.type == Expose)
2213             draw_passwd_window (si);
2214           else if (event.xany.type == ButtonPress || 
2215                    event.xany.type == KeyPress)
2216             break;
2217           XSync (si->dpy, False);
2218         }
2219       else
2220         usleep (250000);  /* 1/4 second */
2221   }
2222
2223  END:
2224   if (si->pw_data)
2225     destroy_passwd_window (si);
2226 }
2227
2228
2229 Bool
2230 unlock_p (saver_info *si)
2231 {
2232   saver_preferences *p = &si->prefs;
2233
2234   if (!si->unlock_cb)
2235     {
2236       fprintf(stderr, "%s: Error: no unlock function specified!\n", blurb());
2237       return False;
2238     }
2239
2240   raise_window (si, True, True, True);
2241
2242   xss_authenticate(si, p->verbose_p);
2243
2244   return (si->unlock_state == ul_success);
2245 }
2246
2247
2248 void
2249 set_locked_p (saver_info *si, Bool locked_p)
2250 {
2251   si->locked_p = locked_p;
2252
2253 #ifdef HAVE_XHPDISABLERESET
2254   hp_lock_reset (si, locked_p);                 /* turn off/on C-Sh-Reset */
2255 #endif
2256 #ifdef HAVE_XF86VMODE
2257   xfree_lock_mode_switch (si, locked_p);        /* turn off/on C-Alt-Plus */
2258 #endif
2259 #ifdef HAVE_XF86MISCSETGRABKEYSSTATE
2260   xfree_lock_grab_smasher (si, locked_p);       /* turn off/on C-Alt-KP-*,/ */
2261 #endif
2262
2263   store_saver_status (si);                      /* store locked-p */
2264 }
2265
2266
2267 #else  /*  NO_LOCKING -- whole file */
2268
2269 void
2270 set_locked_p (saver_info *si, Bool locked_p)
2271 {
2272   if (locked_p) abort();
2273 }
2274
2275 #endif /* !NO_LOCKING */