1 /* lock.c --- handling the password dialog for locking-mode.
2 * xscreensaver, Copyright (c) 1993-1998 Jamie Zawinski <jwz@netscape.com>
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 "xscreensaver.h"
25 #include "resources.h"
30 extern char *getenv(const char *name);
31 extern int validate_user(char *name, char *password);
32 static char * user_vms;
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 static void passwd_done_cb (Widget, XtPointer, XtPointer);
45 static XtActionsRec actionsList[] =
47 {"passwdentered", (XtActionProc) passwd_done_cb},
50 static char Translations[] =
52 <Key>Return: passwdentered()\
55 #else /* HAVE_MOTIF */
59 # include <Xm/TextF.h>
61 #endif /* HAVE_MOTIF */
63 extern Widget passwd_dialog;
64 extern Widget passwd_form;
65 extern Widget roger_label;
66 extern Widget passwd_label1;
67 extern Widget passwd_label3;
68 extern Widget passwd_cancel;
71 extern Widget passwd_text;
72 extern Widget passwd_done;
73 #else /* HAVE_ATHENA */
74 static Widget passwd_text = 0; /* gag... */
75 static Widget passwd_done = 0;
76 #endif /* HAVE_ATHENA */
80 static enum { pw_read, pw_ok, pw_fail, pw_cancel, pw_time } passwd_state;
81 static char typed_passwd [80];
84 #if defined(HAVE_ATHENA) || (XmVersion >= 1002)
85 /* The `destroy' bug apears to be fixed as of Motif 1.2.1, but
86 the `verify-callback' bug is still present. */
87 # define DESTROY_WORKS
91 passwd_cancel_cb (Widget button, XtPointer client_data, XtPointer call_data)
93 passwd_state = pw_cancel;
97 passwd_done_cb (Widget button, XtPointer client_data, XtPointer call_data)
99 if (passwd_state != pw_read) return; /* already done */
103 strncpy(typed_passwd, XawDialogGetValueString(passwd_form),
104 sizeof(typed_passwd)-1);
105 typed_passwd[sizeof(typed_passwd)-1] = 0;
106 # endif /* HAVE_ATHENA */
107 if (passwd_valid_p (typed_passwd))
108 passwd_state = pw_ok;
110 passwd_state = pw_fail;
113 user_vms = getenv("USER");
114 if (validate_user(user_vms,typed_passwd) == 1)
115 passwd_state = pw_ok;
117 passwd_state = pw_fail;
121 #if defined(HAVE_MOTIF) && defined(VERIFY_CALLBACK_WORKS)
123 /* It looks to me like adding any modifyVerify callback causes
124 Motif 1.1.4 to free the the TextF_Value() twice. I can't see
125 the bug in the Motif source, but Purify complains, even if
126 check_passwd_cb() is a no-op.
128 Update: Motif 1.2.1 also loses, but in a different way: it
129 writes beyond the end of a malloc'ed block in ModifyVerify().
130 Probably this block is the text field's text.
134 check_passwd_cb (Widget button, XtPointer client_data, XtPointer call_data)
136 XmTextVerifyCallbackStruct *vcb = (XmTextVerifyCallbackStruct *) call_data;
138 if (passwd_state != pw_read)
140 else if (vcb->reason == XmCR_ACTIVATE)
142 passwd_done_cb (0, 0, 0);
144 else if (vcb->text->length > 1) /* don't allow "paste" operations */
148 else if (vcb->text->ptr != 0)
151 int L = vcb->text->length;
152 if (L >= sizeof(typed_passwd))
153 L = sizeof(typed_passwd)-1;
154 strncat (typed_passwd, vcb->text->ptr, L);
155 typed_passwd [vcb->endPos + L] = 0;
156 for (i = 0; i < vcb->text->length; i++)
157 vcb->text->ptr [i] = '*';
161 # else /* HAVE_ATHENA || !VERIFY_CALLBACK_WORKS */
163 static void keypress (Widget w, XEvent *event, String *av, Cardinal *ac);
164 static void backspace (Widget w, XEvent *event, String *av, Cardinal *ac);
165 static void kill_line (Widget w, XEvent *event, String *av, Cardinal *ac);
166 static void done (Widget w, XEvent *event, String *av, Cardinal *ac);
168 static XtActionsRec actions[] = {{"keypress", keypress},
169 {"backspace", backspace},
170 {"kill_line", kill_line},
175 # if 0 /* oh fuck, why doesn't this work? */
176 static char translations[] = "\
177 <Key>BackSpace: backspace()\n\
178 <Key>Delete: backspace()\n\
179 Ctrl<Key>H: backspace()\n\
180 Ctrl<Key>U: kill_line()\n\
181 Ctrl<Key>X: kill_line()\n\
182 Ctrl<Key>J: done()\n\
183 Ctrl<Key>M: done()\n\
187 static char translations[] = "<Key>:keypress()";
189 # endif /* HAVE_MOTIF */
193 text_field_set_string (Widget widget, char *text, int position)
196 XmTextFieldSetString (widget, text);
197 XmTextFieldSetInsertionPosition (widget, position);
199 #else /* HAVE_ATHENA */
205 block.length = strlen (text);
208 if (block.length == 0)
210 buf = XawDialogGetValueString(passwd_form);
212 end_pos = strlen(buf);
216 XawTextReplace (widget, 0, end_pos, &block);
217 XawTextSetInsertionPoint (widget, position);
218 #endif /* HAVE_ATHENA */
223 keypress (Widget w, XEvent *event, String *argv, Cardinal *argc)
226 char s [sizeof(typed_passwd)];
227 int size = XLookupString ((XKeyEvent *) event, s, sizeof(s)-1, 0, 0);
228 if (size != 1) return;
230 /* hack because I can't get translations to dance to my tune... */
231 if (*s == '\010') { backspace (w, event, argv, argc); return; }
232 if (*s == '\177') { backspace (w, event, argv, argc); return; }
233 if (*s == '\025') { kill_line (w, event, argv, argc); return; }
234 if (*s == '\030') { kill_line (w, event, argv, argc); return; }
235 if (*s == '\012') { done (w, event, argv, argc); return; }
236 if (*s == '\015') { done (w, event, argv, argc); return; }
238 i = j = strlen (typed_passwd);
240 if (i >= (sizeof(typed_passwd)-1))
242 XBell(XtDisplay(w), 0);
246 typed_passwd [i] = *s;
251 text_field_set_string (passwd_text, s, j + 1);
255 backspace (Widget w, XEvent *event, String *argv, Cardinal *argc)
257 char s [sizeof(typed_passwd)];
258 int i = strlen (typed_passwd);
262 typed_passwd [--i] = 0;
267 text_field_set_string (passwd_text, s, j + 1);
271 kill_line (Widget w, XEvent *event, String *argv, Cardinal *argc)
273 memset (typed_passwd, 0, sizeof(typed_passwd));
274 text_field_set_string (passwd_text, "", 0);
278 done (Widget w, XEvent *event, String *argv, Cardinal *argc)
280 passwd_done_cb (w, 0, 0);
283 #endif /* HAVE_ATHENA || !VERIFY_CALLBACK_WORKS */
286 extern void skull (Display *, Window, GC, GC, int, int, int, int);
289 roger (Widget button, XtPointer client_data, XtPointer call_data)
291 Display *dpy = XtDisplay (button);
292 Screen *screen = XtScreen (button);
293 Window window = XtWindow (button);
298 GC draw_gc, erase_gc;
301 XWindowAttributes xgwa;
302 XGetWindowAttributes (dpy, window, &xgwa);
303 cmap = xgwa.colormap;
304 if (xgwa.width > xgwa.height) size = xgwa.height;
305 else size = xgwa.width;
306 if (size > 40) size -= 30;
307 x = (xgwa.width - size) / 2;
308 y = (xgwa.height - size) / 2;
309 XtSetArg (av [ac], XtNforeground, &fg); ac++;
310 XtSetArg (av [ac], XtNbackground, &bg); ac++;
311 XtGetValues (button, av, ac);
312 /* if it's black on white, swap it cause it looks better (hack hack) */
313 if (fg == BlackPixelOfScreen (screen) && bg == WhitePixelOfScreen (screen))
314 fg = WhitePixelOfScreen (screen), bg = BlackPixelOfScreen (screen);
316 erase_gc = XCreateGC (dpy, window, GCForeground, &gcv);
318 draw_gc = XCreateGC (dpy, window, GCForeground, &gcv);
319 XFillRectangle (dpy, window, erase_gc, 0, 0, xgwa.width, xgwa.height);
320 skull (dpy, window, draw_gc, erase_gc, x, y, size, size);
321 XFreeGC (dpy, draw_gc);
322 XFreeGC (dpy, erase_gc);
326 make_passwd_dialog (saver_info *si)
329 saver_screen_info *ssi = si->default_screen;
330 Widget parent = ssi->toplevel_shell;
332 if (ssi->demo_cmap &&
333 ssi->demo_cmap != ssi->cmap &&
334 ssi->demo_cmap != DefaultColormapOfScreen (ssi->screen))
336 XFreeColormap (si->dpy, ssi->demo_cmap);
340 if (ssi->default_visual == DefaultVisualOfScreen (ssi->screen))
341 ssi->demo_cmap = DefaultColormapOfScreen (ssi->screen);
343 ssi->demo_cmap = XCreateColormap (si->dpy,
344 RootWindowOfScreen (ssi->screen),
345 ssi->default_visual, AllocNone);
347 create_passwd_dialog (parent, ssi->default_visual, ssi->demo_cmap);
351 XtVaSetValues(passwd_form, XtNvalue, typed_passwd, 0);
353 XawDialogAddButton(passwd_form,"ok", passwd_done_cb, 0);
354 XawDialogAddButton(passwd_form,"cancel", passwd_cancel_cb, 0);
355 passwd_done = XtNameToWidget(passwd_form,"ok");
356 passwd_text = XtNameToWidget(passwd_form,"value");
358 XtAppAddActions(XtWidgetToApplicationContext(passwd_text),
359 actionsList, XtNumber(actionsList));
360 XtOverrideTranslations(passwd_text, XtParseTranslationTable(Translations));
362 #else /* HAVE_MOTIF */
364 XtAddCallback (passwd_done, XmNactivateCallback, passwd_done_cb, 0);
365 XtAddCallback (passwd_cancel, XmNactivateCallback, passwd_cancel_cb, 0);
366 XtAddCallback (roger_label, XmNexposeCallback, roger, 0);
368 # ifdef VERIFY_CALLBACK_WORKS
369 XtAddCallback (passwd_text, XmNmodifyVerifyCallback, check_passwd_cb, 0);
370 XtAddCallback (passwd_text, XmNactivateCallback, check_passwd_cb, 0);
372 XtAddCallback (passwd_text, XmNactivateCallback, passwd_done_cb, 0);
373 XtOverrideTranslations (passwd_text, XtParseTranslationTable (translations));
376 # if defined(HAVE_MOTIF) && (XmVersion >= 1002)
377 /* The focus stuff changed around; this didn't exist in 1.1.5. */
378 XtVaSetValues (passwd_form, XmNinitialFocus, passwd_text, 0);
381 /* Another random thing necessary in 1.2.1 but not 1.1.5... */
382 XtVaSetValues (roger_label, XmNborderWidth, 2, 0);
384 #endif /* HAVE_MOTIF */
388 struct passwd *pw = getpwuid (getuid ());
389 username = pw->pw_name;
391 #else /* VMS -- from "R.S.Niranjan" <U00C782%BRKVC1@navistar.com> who says
392 that on OpenVMS 6.1, using `struct passwd' crashes... */
393 username = getenv("USER");
396 format_into_label (passwd_label1, si->version);
397 format_into_label (passwd_label3, (username ? username : "???"));
400 static int passwd_idle_timer_tick = -1;
401 static XtIntervalId passwd_idle_id;
404 passwd_idle_timer (XtPointer closure, XtIntervalId *id)
406 saver_info *si = (saver_info *) closure;
407 saver_preferences *p = &si->prefs;
409 Display *dpy = XtDisplay (passwd_form);
411 Window window = XtWindow (passwd_form);
413 Window window = XtWindow (XtParent(passwd_done));
415 static Dimension x, y, d, s, ss;
417 int max = p->passwd_timeout / 1000;
419 idle_timer ((XtPointer) si, id);
421 if (passwd_idle_timer_tick == max) /* first time */
425 unsigned long fg = 0, bg = 0, ts = 0, bs = 0;
426 Dimension w = 0, h = 0;
427 XtVaGetValues(XtParent(passwd_done),
430 XtVaGetValues(passwd_done,
435 XmNtopShadowColor, &ts,
436 XmNbottomShadowColor, &bs,
439 if (ts != bg && ts != fg)
441 if (bs != bg && bs != fg)
449 #ifdef __sgi /* Kludge -- SGI's Motif hacks place buttons differently. */
451 static int sgi_mode = -1;
453 sgi_mode = get_boolean_resource("sgiMode", "sgiMode") ? 1 : 0;
463 #else /* HAVE_ATHENA */
467 unsigned long fg = 0, bg = 0;
468 XtSetArg (av [ac], XtNheight, &d); ac++;
469 XtGetValues (passwd_done, av, ac);
471 XtSetArg (av [ac], XtNwidth, &x); ac++;
472 XtSetArg (av [ac], XtNheight, &y); ac++;
473 XtSetArg (av [ac], XtNforeground, &fg); ac++;
474 XtSetArg (av [ac], XtNbackground, &bg); ac++;
475 XtGetValues (passwd_form, av, ac);
480 #endif /* HAVE_ATHENA */
483 if (gc) XFreeGC (dpy, gc);
484 gc = XCreateGC (dpy, window, GCForeground, &gcv);
485 s = 360*64 / (passwd_idle_timer_tick - 1);
487 XFillArc (dpy, window, gc, x, y, d, d, 0, 360*64);
488 XSetForeground (dpy, gc, bg);
494 if (--passwd_idle_timer_tick)
496 passwd_idle_id = XtAppAddTimeOut (si->app, 1000, passwd_idle_timer,
498 XFillArc (dpy, window, gc, x, y, d, d, ss, s);
507 pop_up_athena_dialog_box (Widget parent, Widget focus, Widget dialog,
508 Widget form, int where)
510 /* modified from demo.c */
511 /* I'm sure this is the wrong way to pop up a dialog box, but I can't
512 figure out how else to do it.
514 It's important that the screensaver dialogs not get decorated or
515 otherwise reparented by the window manager, because they need to be
516 children of the *real* root window, not the WM's virtual root, in
517 order for us to guarentee that they are visible above the screensaver
522 Dimension sw, sh, x, y, w, h;
524 XtRealizeWidget(dialog);
525 sw = WidthOfScreen (XtScreen (dialog));
526 sh = HeightOfScreen (XtScreen (dialog));
528 XtSetArg (av [ac], XtNwidth, &w); ac++;
529 XtSetArg (av [ac], XtNheight, &h); ac++;
530 XtGetValues (form, av, ac);
533 case 0: /* center it in the top-right quadrant */
534 x = (sw/2 + w) / 2 + (sw/2) - w;
535 y = (sh/2 + h) / 2 - h;
537 case 1: /* center it in the bottom-right quadrant */
538 x = (sw/2 + w) / 2 + (sw/2) - w;
539 y = (sh/2 + h) / 2 + (sh/2) - h;
541 case 2: /* center it on the screen */
542 x = (sw + w) / 2 - w;
543 y = (sh + h) / 2 - h;
548 if (x + w > sw) x = sw - w;
549 if (y + h > sh) y = sh - h;
551 XtVaSetValues(dialog,
559 XtPopup(dialog,XtGrabNone);
560 steal_focus_and_colormap (focus);
564 passwd_set_label (char *buf, int len)
569 label=XtNameToWidget(XtParent(passwd_text),"*label");
574 #endif /* HAVE_ATHENA */
577 pop_passwd_dialog (saver_info *si)
579 saver_preferences *p = &si->prefs;
580 saver_screen_info *ssi = si->default_screen;
581 Widget parent = ssi->toplevel_shell;
582 Display *dpy = XtDisplay (passwd_dialog);
587 typed_passwd [0] = 0;
588 passwd_state = pw_read;
589 text_field_set_string (passwd_text, "", 0);
591 /* In case one of the hacks has unmapped it temporarily...
592 Get that sucker on stage now! */
593 for (i = 0; i < si->nscreens; i++)
594 XMapRaised(si->dpy, si->screens[i].screensaver_window);
596 XGetInputFocus (dpy, &focus, &revert_to);
597 #if defined(HAVE_MOTIF) && !defined(DESTROY_WORKS)
598 /* This fucker blows up if we destroy the widget. I can't figure
599 out why. The second destroy phase dereferences freed memory...
600 So we just keep it around; but unrealizing or unmanaging it
601 doesn't work right either, so we hack the window directly. FMH.
603 if (XtWindow (passwd_form))
604 XMapRaised (dpy, XtWindow (passwd_dialog));
607 monitor_power_on (si);
609 pop_up_athena_dialog_box (parent, passwd_text, passwd_dialog,
612 pop_up_dialog_box (passwd_dialog, passwd_form,
613 /* for debugging -- don't ask */
614 (si->prefs.debug_p ? 69 : 0) +
616 XtManageChild (passwd_form);
619 #if defined(HAVE_MOTIF) && (XmVersion < 1002)
620 /* The focus stuff changed around; this causes problems in 1.2.1
621 but is necessary in 1.1.5. */
622 XmProcessTraversal (passwd_text, XmTRAVERSE_CURRENT);
625 passwd_idle_timer_tick = p->passwd_timeout / 1000;
626 passwd_idle_id = XtAppAddTimeOut (si->app, 1000, passwd_idle_timer,
631 roger(roger_label, 0, 0);
632 #endif /* HAVE_ATHENA */
634 if (!si->prefs.debug_p)
635 XGrabServer (dpy); /* ############ DANGER! */
637 /* this call to ungrab used to be in main_loop() - see comment in
638 xscreensaver.c around line 857. */
639 ungrab_keyboard_and_mouse (si);
641 while (passwd_state == pw_read)
644 XtAppNextEvent (si->app, &event);
645 /* wait for timer event */
646 if (event.xany.type == 0 && passwd_idle_timer_tick == 0)
647 passwd_state = pw_time;
648 XtDispatchEvent (&event);
651 XSync (dpy, False); /* ###### (danger over) */
653 if (passwd_state != pw_time)
654 XtRemoveTimeOut (passwd_idle_id);
656 if (passwd_state != pw_ok)
659 switch (passwd_state)
661 case pw_time: lose = "Timed out!"; break;
662 case pw_fail: lose = "Sorry!"; break;
663 case pw_cancel: lose = 0; break;
667 XmProcessTraversal (passwd_cancel, 0); /* turn off I-beam */
672 /* show the message */
673 passwd_set_label(lose,strlen(lose)+1);
675 /* and clear the password line */
676 memset(typed_passwd, 0, sizeof(typed_passwd));
677 text_field_set_string (passwd_text, "", 0);
679 text_field_set_string (passwd_text, lose, strlen (lose) + 1);
681 passwd_idle_timer_tick = 1;
682 passwd_idle_id = XtAppAddTimeOut (si->app, 3000, passwd_idle_timer,
687 XtAppNextEvent (si->app, &event);
688 if (event.xany.type == 0 && /* wait for timer event */
689 passwd_idle_timer_tick == 0)
691 XtDispatchEvent (&event);
695 memset (typed_passwd, 0, sizeof(typed_passwd));
696 text_field_set_string (passwd_text, "", 0);
697 XtSetKeyboardFocus (parent, None);
700 XtDestroyWidget (passwd_dialog);
703 XUnmapWindow (XtDisplay (passwd_dialog), XtWindow (passwd_dialog));
706 XErrorHandler old_handler = XSetErrorHandler (BadWindow_ehandler);
707 /* I don't understand why this doesn't refocus on the old selected
708 window when MWM is running in click-to-type mode. The value of
709 `focus' seems to be correct. */
710 XSetInputFocus (dpy, focus, revert_to, CurrentTime);
712 XSetErrorHandler (old_handler);
715 /* Since we installed our colormap to display the dialog properly, put
716 the old one back, so that the screensaver_window is now displayed
718 for (i = 0; i < si->nscreens; i++)
720 saver_screen_info *ssi = &si->screens[i];
722 XInstallColormap (si->dpy, ssi->cmap);
725 return (passwd_state == pw_ok ? True : False);
729 unlock_p (saver_info *si)
731 static Bool initted = False;
734 #ifndef VERIFY_CALLBACK_WORKS
735 XtAppAddActions (si->app, actions, XtNumber (actions));
741 make_passwd_dialog (si);
742 return pop_passwd_dialog (si);
745 #endif /* !NO_LOCKING -- whole file */