1 /* lock.c --- handling the password dialog for locking-mode.
2 * xscreensaver, Copyright (c) 1993-1998 Jamie Zawinski <jwz@jwz.org>
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
13 /* Athena locking code contributed by Jon A. Christopher <jac8782@tamu.edu> */
14 /* Copyright 1997, with the same permissions as above. */
20 #ifndef NO_LOCKING /* whole file */
22 #include <X11/StringDefs.h>
23 #include <X11/Intrinsic.h>
24 #include <X11/IntrinsicP.h> /* for XtResizeWidget */
25 #include "xscreensaver.h"
26 #include "resources.h"
31 extern char *getenv(const char *name);
32 extern int validate_user(char *name, char *password);
38 # include <X11/Shell.h>
39 # include <X11/StringDefs.h>
40 # include <X11/Xaw/Text.h>
41 # include <X11/Xaw/Label.h>
42 # include <X11/Xaw/Dialog.h>
44 #else /* HAVE_MOTIF */
48 # include <Xm/TextF.h>
50 #endif /* HAVE_MOTIF */
53 ERROR! You must not include vroot.h in this file.
56 extern Widget passwd_dialog;
57 extern Widget passwd_form;
58 extern Widget roger_label;
59 extern Widget passwd_label1;
60 extern Widget passwd_label3;
61 extern Widget passwd_cancel;
64 extern Widget passwd_text;
65 extern Widget passwd_done;
66 #else /* HAVE_ATHENA */
67 static Widget passwd_text = 0; /* gag... */
68 static Widget passwd_done = 0;
69 #endif /* HAVE_ATHENA */
73 static enum { pw_read, pw_ok, pw_fail, pw_cancel, pw_time } passwd_state;
74 static char typed_passwd [80];
77 #if defined(HAVE_ATHENA) || (XmVersion >= 1002)
78 /* The `destroy' bug apears to be fixed as of Motif 1.2.1, but
79 the `verify-callback' bug is still present. */
80 # define DESTROY_WORKS
84 passwd_cancel_cb (Widget button, XtPointer client_data, XtPointer call_data)
86 passwd_state = pw_cancel;
92 vms_passwd_valid_p(char *pw)
94 char *u = getenv("USER");
95 return (validate_user (i, typed_passwd) == 1);
97 # undef passwd_valid_p
98 # define passwd_valid_p vms_passwd_valid_p
104 passwd_done_cb (Widget button, XtPointer client_data, XtPointer call_data)
106 if (passwd_state != pw_read) return; /* already done */
108 if (passwd_valid_p (typed_passwd))
109 passwd_state = pw_ok;
111 passwd_state = pw_fail;
115 #if defined(HAVE_MOTIF) && defined(VERIFY_CALLBACK_WORKS)
117 /* It looks to me like adding any modifyVerify callback causes
118 Motif 1.1.4 to free the the TextF_Value() twice. I can't see
119 the bug in the Motif source, but Purify complains, even if
120 check_passwd_cb() is a no-op.
122 Update: Motif 1.2.1 also loses, but in a different way: it
123 writes beyond the end of a malloc'ed block in ModifyVerify().
124 Probably this block is the text field's text.
128 check_passwd_cb (Widget button, XtPointer client_data, XtPointer call_data)
130 XmTextVerifyCallbackStruct *vcb = (XmTextVerifyCallbackStruct *) call_data;
132 if (passwd_state != pw_read)
134 else if (vcb->reason == XmCR_ACTIVATE)
136 passwd_done_cb (0, 0, 0);
138 else if (vcb->text->length > 1) /* don't allow "paste" operations */
142 else if (vcb->text->ptr != 0)
145 int L = vcb->text->length;
146 if (L >= sizeof(typed_passwd))
147 L = sizeof(typed_passwd)-1;
148 strncat (typed_passwd, vcb->text->ptr, L);
149 typed_passwd [vcb->endPos + L] = 0;
150 for (i = 0; i < vcb->text->length; i++)
151 vcb->text->ptr [i] = '*';
155 # else /* HAVE_ATHENA || !VERIFY_CALLBACK_WORKS */
157 static void keypress (Widget w, XEvent *event, String *av, Cardinal *ac);
158 static void backspace (Widget w, XEvent *event, String *av, Cardinal *ac);
159 static void kill_line (Widget w, XEvent *event, String *av, Cardinal *ac);
160 static void done (Widget w, XEvent *event, String *av, Cardinal *ac);
162 static XtActionsRec actions[] = {{"keypress", keypress},
163 {"backspace", backspace},
164 {"kill_line", kill_line},
168 # if 0 /* This works for Athena, but not Motif: keypress() gets called
169 for all keys anyway. So, the implementation of keypress()
170 has BackSpace, etc, hardcoded into it instead. FMH!
172 static char translations[] = "<Key>BackSpace: backspace()\n"
173 "<Key>Delete: backspace()\n"
174 "Ctrl<Key>H: backspace()\n"
175 "Ctrl<Key>U: kill_line()\n"
176 "Ctrl<Key>X: kill_line()\n"
177 "Ctrl<Key>J: done()\n"
178 "Ctrl<Key>M: done()\n"
179 "<Key>: keypress()\n";
181 static char translations[] = "<Key>: keypress()\n";
186 text_field_set_string (Widget widget, char *text, int position)
189 XmTextFieldSetString (widget, text);
190 XmTextFieldSetInsertionPosition (widget, position);
192 #else /* HAVE_ATHENA */
198 block.length = strlen (text);
201 if (block.length == 0)
203 buf = XawDialogGetValueString(passwd_form);
205 end_pos = strlen(buf);
209 XawTextReplace (widget, 0, end_pos, &block);
210 XawTextSetInsertionPoint (widget, position);
211 #endif /* HAVE_ATHENA */
216 keypress (Widget w, XEvent *event, String *argv, Cardinal *argc)
219 char s [sizeof(typed_passwd)];
220 int size = XLookupString ((XKeyEvent *) event, s, sizeof(s)-1, 0, 0);
221 if (size != 1) return;
223 /* hack because I can't get translations to dance to my tune... */
224 if (*s == '\010') { backspace (w, event, argv, argc); return; }
225 if (*s == '\177') { backspace (w, event, argv, argc); return; }
226 if (*s == '\025') { kill_line (w, event, argv, argc); return; }
227 if (*s == '\030') { kill_line (w, event, argv, argc); return; }
228 if (*s == '\012') { done (w, event, argv, argc); return; }
229 if (*s == '\015') { done (w, event, argv, argc); return; }
231 i = j = strlen (typed_passwd);
233 if (i >= (sizeof(typed_passwd)-1))
235 XBell(XtDisplay(w), 0);
239 typed_passwd [i] = *s;
244 text_field_set_string (passwd_text, s, j + 1);
248 backspace (Widget w, XEvent *event, String *argv, Cardinal *argc)
250 char s [sizeof(typed_passwd)];
251 int i = strlen (typed_passwd);
255 typed_passwd [--i] = 0;
260 text_field_set_string (passwd_text, s, j + 1);
264 kill_line (Widget w, XEvent *event, String *argv, Cardinal *argc)
266 memset (typed_passwd, 0, sizeof(typed_passwd));
267 text_field_set_string (passwd_text, "", 0);
271 done (Widget w, XEvent *event, String *argv, Cardinal *argc)
273 passwd_done_cb (w, 0, 0);
276 #endif /* HAVE_ATHENA || !VERIFY_CALLBACK_WORKS */
280 make_passwd_dialog (saver_info *si)
283 saver_screen_info *ssi = si->default_screen;
284 Widget parent = ssi->toplevel_shell;
286 if (ssi->demo_cmap &&
287 ssi->demo_cmap != ssi->cmap &&
288 ssi->demo_cmap != DefaultColormapOfScreen (ssi->screen))
290 XFreeColormap (si->dpy, ssi->demo_cmap);
294 if (ssi->default_visual == DefaultVisualOfScreen (ssi->screen))
295 ssi->demo_cmap = DefaultColormapOfScreen (ssi->screen);
297 ssi->demo_cmap = XCreateColormap (si->dpy,
298 RootWindowOfScreen (ssi->screen),
299 ssi->default_visual, AllocNone);
301 create_passwd_dialog (parent, ssi->default_visual, ssi->demo_cmap);
304 XtVaSetValues(passwd_form, XtNvalue, typed_passwd, 0);
306 XawDialogAddButton(passwd_form,"ok", passwd_done_cb, 0);
307 XawDialogAddButton(passwd_form,"cancel", passwd_cancel_cb, 0);
308 passwd_done = XtNameToWidget(passwd_form,"ok");
309 passwd_text = XtNameToWidget(passwd_form,"value");
311 XtAppAddActions(XtWidgetToApplicationContext(passwd_text),
312 actions, XtNumber(actions));
313 XtOverrideTranslations(passwd_text, XtParseTranslationTable(translations));
315 /* Lose the label on the inner dialog. */
317 Widget w = XtNameToWidget(passwd_form, "label");
318 if (w) XtUnmanageChild(w);
321 #else /* HAVE_MOTIF */
323 XtAddCallback (passwd_done, XmNactivateCallback, passwd_done_cb, 0);
324 XtAddCallback (passwd_cancel, XmNactivateCallback, passwd_cancel_cb, 0);
325 XtAddCallback (roger_label, XmNexposeCallback, roger, 0);
327 # ifdef VERIFY_CALLBACK_WORKS
328 XtAddCallback (passwd_text, XmNmodifyVerifyCallback, check_passwd_cb, 0);
329 XtAddCallback (passwd_text, XmNactivateCallback, check_passwd_cb, 0);
330 # else /* !VERIFY_CALLBACK_WORKS */
331 XtAddCallback (passwd_text, XmNactivateCallback, passwd_done_cb, 0);
332 XtOverrideTranslations (passwd_text, XtParseTranslationTable (translations));
333 # endif /* !VERIFY_CALLBACK_WORKS */
335 # if defined(HAVE_MOTIF) && (XmVersion >= 1002)
336 /* The focus stuff changed around; this didn't exist in 1.1.5. */
337 XtVaSetValues (passwd_form, XmNinitialFocus, passwd_text, 0);
338 # endif /* HAVE_MOTIF && XmVersion >= 1002 */
340 /* Another random thing necessary in 1.2.1 but not 1.1.5... */
341 XtVaSetValues (roger_label, XmNborderWidth, 2, 0);
343 #endif /* HAVE_MOTIF */
347 struct passwd *pw = getpwuid (getuid ());
348 username = pw->pw_name;
350 #else /* VMS -- from "R.S.Niranjan" <U00C782%BRKVC1@navistar.com> who says
351 that on OpenVMS 6.1, using `struct passwd' crashes... */
352 username = getenv("USER");
355 format_into_label (passwd_label1, si->version);
356 format_into_label (passwd_label3, (username ? username : "???"));
359 static int passwd_idle_timer_tick = -1;
360 static XtIntervalId passwd_idle_id;
363 passwd_idle_timer (XtPointer closure, XtIntervalId *id)
365 saver_info *si = (saver_info *) closure;
366 saver_preferences *p = &si->prefs;
368 Display *dpy = XtDisplay (passwd_form);
370 Window window = XtWindow (passwd_form);
372 Window window = XtWindow (XtParent(passwd_done));
374 static Dimension x, y, d, s, ss;
376 int max = p->passwd_timeout / 1000;
378 idle_timer ((XtPointer) si, id);
380 if (passwd_idle_timer_tick == max) /* first time */
384 unsigned long fg = 0, bg = 0, ts = 0, bs = 0;
385 Dimension w = 0, h = 0;
386 XtVaGetValues(XtParent(passwd_done),
389 XtVaGetValues(passwd_done,
393 XtVaGetValues(passwd_form,
396 XmNtopShadowColor, &ts,
397 XmNbottomShadowColor, &bs,
400 if (ts != bg && ts != fg)
402 if (bs != bg && bs != fg)
410 # ifdef __sgi /* Kludge -- SGI's Motif hacks place buttons differently. */
412 static int sgi_mode = -1;
414 sgi_mode = get_boolean_resource("sgiMode", "sgiMode") ? 1 : 0;
424 #else /* HAVE_ATHENA */
428 unsigned long fg = 0, bg = 0;
429 XtSetArg (av [ac], XtNheight, &d); ac++;
430 XtGetValues (passwd_done, av, ac);
432 XtSetArg (av [ac], XtNwidth, &x); ac++;
433 XtSetArg (av [ac], XtNheight, &y); ac++;
434 XtSetArg (av [ac], XtNforeground, &fg); ac++;
435 XtSetArg (av [ac], XtNbackground, &bg); ac++;
436 XtGetValues (passwd_form, av, ac);
441 #endif /* HAVE_ATHENA */
444 if (gc) XFreeGC (dpy, gc);
445 gc = XCreateGC (dpy, window, GCForeground, &gcv);
446 s = 360*64 / (passwd_idle_timer_tick - 1);
448 XFillArc (dpy, window, gc, x, y, d, d, 0, 360*64);
449 XSetForeground (dpy, gc, bg);
455 if (--passwd_idle_timer_tick)
457 passwd_idle_id = XtAppAddTimeOut (si->app, 1000, passwd_idle_timer,
459 XFillArc (dpy, window, gc, x, y, d, d, ss, s);
466 pop_passwd_dialog (saver_info *si)
468 saver_preferences *p = &si->prefs;
469 saver_screen_info *ssi = si->default_screen;
470 Widget parent = ssi->toplevel_shell;
471 Display *dpy = XtDisplay (passwd_dialog);
475 Window grab_window = RootWindowOfScreen(si->screens[0].screen);
477 typed_passwd [0] = 0;
478 passwd_state = pw_read;
479 text_field_set_string (passwd_text, "", 0);
481 /* In case one of the hacks has unmapped it temporarily...
482 Get that sucker on stage now! */
483 for (i = 0; i < si->nscreens; i++)
484 XMapRaised(si->dpy, si->screens[i].screensaver_window);
486 XGetInputFocus (dpy, &focus, &revert_to);
487 #if defined(HAVE_MOTIF) && !defined(DESTROY_WORKS)
488 /* This fucker blows up if we destroy the widget. I can't figure
489 out why. The second destroy phase dereferences freed memory...
490 So we just keep it around; but unrealizing or unmanaging it
491 doesn't work right either, so we hack the window directly. FMH.
493 if (XtWindow (passwd_form))
494 XMapRaised (dpy, XtWindow (passwd_dialog));
495 #endif /* HAVE_MOTIF && !DESTROY_WORKS */
497 monitor_power_on (si);
498 pop_up_dialog_box (passwd_dialog, passwd_form,
499 /* for debugging -- don't ask */
500 (si->prefs.debug_p ? 69 : 0) +
502 XtManageChild (passwd_form);
505 steal_focus_and_colormap (passwd_text);
507 /* For some reason, the passwd_form box is not stretching all the way
508 to the right edge of the window, despite being XtChainRight.
509 So... resize it by hand.
512 Dimension x=0, w=0, h=0;
513 XtVaGetValues(passwd_form, XtNx, &x, XtNwidth, &w, XtNheight, &h, 0);
514 XtVaGetValues(XtParent(passwd_form), XtNwidth, &w, 0);
517 if (w > 0) XtResizeWidget(passwd_form, w, h, 0);
520 #endif /* HAVE_ATHENA */
523 #if defined(HAVE_MOTIF) && (XmVersion < 1002)
524 /* The focus stuff changed around; this causes problems in 1.2.1
525 but is necessary in 1.1.5. */
526 XmProcessTraversal (passwd_text, XmTRAVERSE_CURRENT);
527 #endif /* HAVE_MOTIF && XmVersion < 1002 */
529 passwd_idle_timer_tick = p->passwd_timeout / 1000;
530 passwd_idle_id = XtAppAddTimeOut (si->app, 1000, passwd_idle_timer,
535 roger(roger_label, 0, 0);
536 #endif /* HAVE_ATHENA */
539 /* Make sure the mouse cursor is visible.
540 Since the screensaver was already active, we had already called
541 grab_keyboard_and_mouse() with our "invisible" Cursor object.
542 Now we need to change that. (cursor == 0 means "server default
545 if (grab_window != si->mouse_grab_window ||
546 grab_window != si->keyboard_grab_window)
548 "%s: WARNING: expected mouse and keyboard grabs on 0x%x,\n"
549 "\tbut mouse-grab is 0x%x and keyboard-grab is 0x%x.\n",
551 (unsigned long) grab_window,
552 (unsigned long) si->mouse_grab_window,
553 (unsigned long) si->keyboard_grab_window);
556 fprintf(stderr, "%s: re-grabbing keyboard and mouse to expose cursor.\n",
558 grab_keyboard_and_mouse (si, grab_window, 0);
561 if (!si->prefs.debug_p)
562 XGrabServer (dpy); /* ############ DANGER! */
564 while (passwd_state == pw_read)
567 XtAppNextEvent (si->app, &event);
568 /* wait for timer event */
569 if (event.xany.type == 0 && passwd_idle_timer_tick == 0)
570 passwd_state = pw_time;
571 XtDispatchEvent (&event);
574 XSync (dpy, False); /* ###### (danger over) */
577 /* Now turn off the mouse cursor again.
580 fprintf(stderr, "%s: re-grabbing keyboard and mouse to hide cursor.\n",
582 grab_keyboard_and_mouse (si, grab_window, si->screens[0].cursor);
585 if (passwd_state != pw_time)
586 XtRemoveTimeOut (passwd_idle_id);
588 if (passwd_state != pw_ok)
591 switch (passwd_state)
593 case pw_time: lose = "Timed out!"; break;
594 case pw_fail: lose = "Sorry!"; break;
595 case pw_cancel: lose = 0; break;
600 XmProcessTraversal (passwd_cancel, 0); /* turn off I-beam */
601 #else /* HAVE_ATHENA */
602 steal_focus_and_colormap (passwd_done);
603 #endif /* HAVE_ATHENA */
607 text_field_set_string (passwd_text, lose, strlen (lose) + 1);
609 passwd_idle_timer_tick = 1;
610 passwd_idle_id = XtAppAddTimeOut (si->app, 3000, passwd_idle_timer,
615 XtAppNextEvent (si->app, &event);
616 if (event.xany.type == 0 && /* wait for timer event */
617 passwd_idle_timer_tick == 0)
619 XtDispatchEvent (&event);
623 memset (typed_passwd, 0, sizeof(typed_passwd));
624 text_field_set_string (passwd_text, "", 0);
625 XtSetKeyboardFocus (parent, None);
628 XtDestroyWidget (passwd_dialog);
630 #else /* !DESTROY_WORKS */
631 XUnmapWindow (XtDisplay (passwd_dialog), XtWindow (passwd_dialog));
632 #endif /* !DESTROY_WORKS */
634 XErrorHandler old_handler = XSetErrorHandler (BadWindow_ehandler);
635 /* I don't understand why this doesn't refocus on the old selected
636 window when MWM is running in click-to-type mode. The value of
637 `focus' seems to be correct. */
638 XSetInputFocus (dpy, focus, revert_to, CurrentTime);
640 XSetErrorHandler (old_handler);
643 /* Since we installed our colormap to display the dialog properly, put
644 the old one back, so that the screensaver_window is now displayed
646 for (i = 0; i < si->nscreens; i++)
648 saver_screen_info *ssi = &si->screens[i];
650 XInstallColormap (si->dpy, ssi->cmap);
653 return (passwd_state == pw_ok ? True : False);
657 unlock_p (saver_info *si)
659 static Bool initted = False;
663 #ifndef VERIFY_CALLBACK_WORKS
664 XtAppAddActions (si->app, actions, XtNumber (actions));
665 #endif /* !VERIFY_CALLBACK_WORKS */
671 make_passwd_dialog (si);
672 return pop_passwd_dialog (si);
675 #endif /* !NO_LOCKING -- whole file */