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