1 /* lock.c --- handling the password dialog for locking-mode.
2 * xscreensaver, Copyright (c) 1993-1997 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"
29 extern char *getenv(const char *name);
30 extern int validate_user(char *name, char *password);
31 static char * user_vms;
37 # include <X11/Shell.h>
38 # include <X11/StringDefs.h>
39 # include <X11/Xaw/Text.h>
40 # include <X11/Xaw/Label.h>
41 # include <X11/Xaw/Dialog.h>
43 static void passwd_done_cb (Widget, XtPointer, XtPointer);
44 static XtActionsRec actionsList[] =
46 {"passwdentered", (XtActionProc) passwd_done_cb},
49 static char Translations[] =
51 <Key>Return: passwdentered()\
54 #else /* HAVE_MOTIF */
58 # include <Xm/TextF.h>
60 #endif /* HAVE_MOTIF */
62 extern Widget passwd_dialog;
63 extern Widget passwd_form;
64 extern Widget roger_label;
65 extern Widget passwd_label1;
66 extern Widget passwd_label3;
67 extern Widget passwd_cancel;
70 extern Widget passwd_text;
71 extern Widget passwd_done;
72 #else /* HAVE_ATHENA */
73 static Widget passwd_text = 0; /* gag... */
74 static Widget passwd_done = 0;
75 #endif /* HAVE_ATHENA */
79 static enum { pw_read, pw_ok, pw_fail, pw_cancel, pw_time } passwd_state;
80 static char typed_passwd [80];
83 #if defined(HAVE_ATHENA) || (XmVersion >= 1002)
84 /* The `destroy' bug apears to be fixed as of Motif 1.2.1, but
85 the `verify-callback' bug is still present. */
86 # define DESTROY_WORKS
90 passwd_cancel_cb (Widget button, XtPointer client_data, XtPointer call_data)
92 passwd_state = pw_cancel;
96 passwd_done_cb (Widget button, XtPointer client_data, XtPointer call_data)
98 if (passwd_state != pw_read) return; /* already done */
102 strncpy(typed_passwd, XawDialogGetValueString(passwd_form),
103 sizeof(typed_passwd)-1);
104 typed_passwd[sizeof(typed_passwd)-1] = 0;
105 # endif /* HAVE_ATHENA */
106 if (passwd_valid_p (typed_passwd))
107 passwd_state = pw_ok;
109 passwd_state = pw_fail;
112 user_vms = getenv("USER");
113 if (validate_user(user_vms,typed_passwd) == 1)
114 passwd_state = pw_ok;
116 passwd_state = pw_fail;
120 #if defined(HAVE_MOTIF) && defined(VERIFY_CALLBACK_WORKS)
122 /* It looks to me like adding any modifyVerify callback causes
123 Motif 1.1.4 to free the the TextF_Value() twice. I can't see
124 the bug in the Motif source, but Purify complains, even if
125 check_passwd_cb() is a no-op.
127 Update: Motif 1.2.1 also loses, but in a different way: it
128 writes beyond the end of a malloc'ed block in ModifyVerify().
129 Probably this block is the text field's text.
133 check_passwd_cb (Widget button, XtPointer client_data, XtPointer call_data)
135 XmTextVerifyCallbackStruct *vcb = (XmTextVerifyCallbackStruct *) call_data;
137 if (passwd_state != pw_read)
139 else if (vcb->reason == XmCR_ACTIVATE)
141 passwd_done_cb (0, 0, 0);
143 else if (vcb->text->length > 1) /* don't allow "paste" operations */
147 else if (vcb->text->ptr != 0)
150 int L = vcb->text->length;
151 if (L >= sizeof(typed_passwd))
152 L = sizeof(typed_passwd)-1;
153 strncat (typed_passwd, vcb->text->ptr, L);
154 typed_passwd [vcb->endPos + L] = 0;
155 for (i = 0; i < vcb->text->length; i++)
156 vcb->text->ptr [i] = '*';
160 # else /* HAVE_ATHENA || !VERIFY_CALLBACK_WORKS */
162 static void keypress (Widget w, XEvent *event, String *av, Cardinal *ac);
163 static void backspace (Widget w, XEvent *event, String *av, Cardinal *ac);
164 static void kill_line (Widget w, XEvent *event, String *av, Cardinal *ac);
165 static void done (Widget w, XEvent *event, String *av, Cardinal *ac);
167 static XtActionsRec actions[] = {{"keypress", keypress},
168 {"backspace", backspace},
169 {"kill_line", kill_line},
174 # if 0 /* oh fuck, why doesn't this work? */
175 static char translations[] = "\
176 <Key>BackSpace: backspace()\n\
177 <Key>Delete: backspace()\n\
178 Ctrl<Key>H: backspace()\n\
179 Ctrl<Key>U: kill_line()\n\
180 Ctrl<Key>X: kill_line()\n\
181 Ctrl<Key>J: done()\n\
182 Ctrl<Key>M: done()\n\
186 static char translations[] = "<Key>:keypress()";
188 # endif /* HAVE_MOTIF */
192 text_field_set_string (Widget widget, char *text, int position)
195 XmTextFieldSetString (widget, text);
196 XmTextFieldSetInsertionPosition (widget, position);
198 #else /* HAVE_ATHENA */
204 block.length = strlen (text);
207 if (block.length == 0)
209 buf = XawDialogGetValueString(passwd_form);
211 end_pos = strlen(buf);
215 XawTextReplace (widget, 0, end_pos, &block);
216 XawTextSetInsertionPoint (widget, position);
217 #endif /* HAVE_ATHENA */
222 keypress (Widget w, XEvent *event, String *argv, Cardinal *argc)
225 char s [sizeof(typed_passwd)];
226 int size = XLookupString ((XKeyEvent *) event, s, sizeof(s)-1, 0, 0);
227 if (size != 1) return;
229 /* hack because I can't get translations to dance to my tune... */
230 if (*s == '\010') { backspace (w, event, argv, argc); return; }
231 if (*s == '\177') { backspace (w, event, argv, argc); return; }
232 if (*s == '\025') { kill_line (w, event, argv, argc); return; }
233 if (*s == '\030') { kill_line (w, event, argv, argc); return; }
234 if (*s == '\012') { done (w, event, argv, argc); return; }
235 if (*s == '\015') { done (w, event, argv, argc); return; }
237 i = j = strlen (typed_passwd);
239 if (i >= (sizeof(typed_passwd)-1))
241 XBell(XtDisplay(w), 0);
245 typed_passwd [i] = *s;
250 text_field_set_string (passwd_text, s, j + 1);
254 backspace (Widget w, XEvent *event, String *argv, Cardinal *argc)
256 char s [sizeof(typed_passwd)];
257 int i = strlen (typed_passwd);
261 typed_passwd [--i] = 0;
266 text_field_set_string (passwd_text, s, j + 1);
270 kill_line (Widget w, XEvent *event, String *argv, Cardinal *argc)
272 memset (typed_passwd, 0, sizeof(typed_passwd));
273 text_field_set_string (passwd_text, "", 0);
277 done (Widget w, XEvent *event, String *argv, Cardinal *argc)
279 passwd_done_cb (w, 0, 0);
282 #endif /* HAVE_ATHENA || !VERIFY_CALLBACK_WORKS */
285 extern void skull (Display *, Window, GC, GC, int, int, int, int);
288 roger (Widget button, XtPointer client_data, XtPointer call_data)
290 Display *dpy = XtDisplay (button);
291 Screen *screen = XtScreen (button);
292 Window window = XtWindow (button);
297 GC draw_gc, erase_gc;
300 XWindowAttributes xgwa;
301 XGetWindowAttributes (dpy, window, &xgwa);
302 cmap = xgwa.colormap;
303 if (xgwa.width > xgwa.height) size = xgwa.height;
304 else size = xgwa.width;
305 if (size > 40) size -= 30;
306 x = (xgwa.width - size) / 2;
307 y = (xgwa.height - size) / 2;
308 XtSetArg (av [ac], XtNforeground, &fg); ac++;
309 XtSetArg (av [ac], XtNbackground, &bg); ac++;
310 XtGetValues (button, av, ac);
311 /* if it's black on white, swap it cause it looks better (hack hack) */
312 if (fg == BlackPixelOfScreen (screen) && bg == WhitePixelOfScreen (screen))
313 fg = WhitePixelOfScreen (screen), bg = BlackPixelOfScreen (screen);
315 erase_gc = XCreateGC (dpy, window, GCForeground, &gcv);
317 draw_gc = XCreateGC (dpy, window, GCForeground, &gcv);
318 XFillRectangle (dpy, window, erase_gc, 0, 0, xgwa.width, xgwa.height);
319 skull (dpy, window, draw_gc, erase_gc, x, y, size, size);
320 XFreeGC (dpy, draw_gc);
321 XFreeGC (dpy, erase_gc);
325 make_passwd_dialog (saver_info *si)
328 saver_screen_info *ssi = si->default_screen;
329 Widget parent = ssi->toplevel_shell;
331 if (ssi->demo_cmap &&
332 ssi->demo_cmap != ssi->cmap &&
333 ssi->demo_cmap != DefaultColormapOfScreen (ssi->screen))
335 XFreeColormap (si->dpy, ssi->demo_cmap);
339 if (ssi->default_visual == DefaultVisualOfScreen (ssi->screen))
340 ssi->demo_cmap = DefaultColormapOfScreen (ssi->screen);
342 ssi->demo_cmap = XCreateColormap (si->dpy,
343 RootWindowOfScreen (ssi->screen),
344 ssi->default_visual, AllocNone);
346 create_passwd_dialog (parent, ssi->default_visual, ssi->demo_cmap);
350 XtVaSetValues(passwd_form, XtNvalue, typed_passwd, 0);
352 XawDialogAddButton(passwd_form,"ok", passwd_done_cb, 0);
353 XawDialogAddButton(passwd_form,"cancel", passwd_cancel_cb, 0);
354 passwd_done = XtNameToWidget(passwd_form,"ok");
355 passwd_text = XtNameToWidget(passwd_form,"value");
357 XtAppAddActions(XtWidgetToApplicationContext(passwd_text),
358 actionsList, XtNumber(actionsList));
359 XtOverrideTranslations(passwd_text, XtParseTranslationTable(Translations));
361 #else /* HAVE_MOTIF */
363 XtAddCallback (passwd_done, XmNactivateCallback, passwd_done_cb, 0);
364 XtAddCallback (passwd_cancel, XmNactivateCallback, passwd_cancel_cb, 0);
365 XtAddCallback (roger_label, XmNexposeCallback, roger, 0);
367 # ifdef VERIFY_CALLBACK_WORKS
368 XtAddCallback (passwd_text, XmNmodifyVerifyCallback, check_passwd_cb, 0);
369 XtAddCallback (passwd_text, XmNactivateCallback, check_passwd_cb, 0);
371 XtAddCallback (passwd_text, XmNactivateCallback, passwd_done_cb, 0);
372 XtOverrideTranslations (passwd_text, XtParseTranslationTable (translations));
375 # if defined(HAVE_MOTIF) && (XmVersion >= 1002)
376 /* The focus stuff changed around; this didn't exist in 1.1.5. */
377 XtVaSetValues (passwd_form, XmNinitialFocus, passwd_text, 0);
380 /* Another random thing necessary in 1.2.1 but not 1.1.5... */
381 XtVaSetValues (roger_label, XmNborderWidth, 2, 0);
383 #endif /* HAVE_MOTIF */
387 struct passwd *pw = getpwuid (getuid ());
388 username = pw->pw_name;
390 #else /* VMS -- from "R.S.Niranjan" <U00C782%BRKVC1@navistar.com> who says
391 that on OpenVMS 6.1, using `struct passwd' crashes... */
392 username = getenv("USER");
395 format_into_label (passwd_label1, si->version);
396 format_into_label (passwd_label3, (username ? username : "???"));
399 static int passwd_idle_timer_tick = -1;
400 static XtIntervalId passwd_idle_id;
403 passwd_idle_timer (XtPointer closure, XtIntervalId *id)
405 saver_info *si = (saver_info *) closure;
406 saver_preferences *p = &si->prefs;
408 Display *dpy = XtDisplay (passwd_form);
410 Window window = XtWindow (passwd_form);
412 Window window = XtWindow (XtParent(passwd_done));
414 static Dimension x, y, d, s, ss;
416 int max = p->passwd_timeout / 1000;
418 idle_timer ((XtPointer) si, id);
420 if (passwd_idle_timer_tick == max) /* first time */
424 unsigned long fg = 0, bg = 0, ts = 0, bs = 0;
425 Dimension w = 0, h = 0;
426 XtVaGetValues(XtParent(passwd_done),
429 XtVaGetValues(passwd_done,
434 XmNtopShadowColor, &ts,
435 XmNbottomShadowColor, &bs,
438 if (ts != bg && ts != fg)
440 if (bs != bg && bs != fg)
451 #else /* HAVE_ATHENA */
455 unsigned long fg = 0, bg = 0;
456 XtSetArg (av [ac], XtNheight, &d); ac++;
457 XtGetValues (passwd_done, av, ac);
459 XtSetArg (av [ac], XtNwidth, &x); ac++;
460 XtSetArg (av [ac], XtNheight, &y); ac++;
461 XtSetArg (av [ac], XtNforeground, &fg); ac++;
462 XtSetArg (av [ac], XtNbackground, &bg); ac++;
463 XtGetValues (passwd_form, av, ac);
468 #endif /* HAVE_ATHENA */
471 if (gc) XFreeGC (dpy, gc);
472 gc = XCreateGC (dpy, window, GCForeground, &gcv);
473 s = 360*64 / (passwd_idle_timer_tick - 1);
475 XFillArc (dpy, window, gc, x, y, d, d, 0, 360*64);
476 XSetForeground (dpy, gc, bg);
482 if (--passwd_idle_timer_tick)
484 passwd_idle_id = XtAppAddTimeOut (si->app, 1000, passwd_idle_timer,
486 XFillArc (dpy, window, gc, x, y, d, d, ss, s);
495 pop_up_athena_dialog_box (Widget parent, Widget focus, Widget dialog,
496 Widget form, int where)
498 /* modified from demo.c */
499 /* I'm sure this is the wrong way to pop up a dialog box, but I can't
500 figure out how else to do it.
502 It's important that the screensaver dialogs not get decorated or
503 otherwise reparented by the window manager, because they need to be
504 children of the *real* root window, not the WM's virtual root, in
505 order for us to guarentee that they are visible above the screensaver
510 Dimension sw, sh, x, y, w, h;
512 XtRealizeWidget(dialog);
513 sw = WidthOfScreen (XtScreen (dialog));
514 sh = HeightOfScreen (XtScreen (dialog));
516 XtSetArg (av [ac], XtNwidth, &w); ac++;
517 XtSetArg (av [ac], XtNheight, &h); ac++;
518 XtGetValues (form, av, ac);
521 case 0: /* center it in the top-right quadrant */
522 x = (sw/2 + w) / 2 + (sw/2) - w;
523 y = (sh/2 + h) / 2 - h;
525 case 1: /* center it in the bottom-right quadrant */
526 x = (sw/2 + w) / 2 + (sw/2) - w;
527 y = (sh/2 + h) / 2 + (sh/2) - h;
529 case 2: /* center it on the screen */
530 x = (sw + w) / 2 - w;
531 y = (sh + h) / 2 - h;
536 if (x + w > sw) x = sw - w;
537 if (y + h > sh) y = sh - h;
539 XtVaSetValues(dialog,
547 XtPopup(dialog,XtGrabNone);
548 steal_focus_and_colormap (focus);
552 passwd_set_label (char *buf, int len)
557 label=XtNameToWidget(XtParent(passwd_text),"*label");
562 #endif /* HAVE_ATHENA */
565 pop_passwd_dialog (saver_info *si)
567 saver_preferences *p = &si->prefs;
568 saver_screen_info *ssi = si->default_screen;
569 Widget parent = ssi->toplevel_shell;
570 Display *dpy = XtDisplay (passwd_dialog);
575 typed_passwd [0] = 0;
576 passwd_state = pw_read;
577 text_field_set_string (passwd_text, "", 0);
579 /* In case one of the hacks has unmapped it temporarily...
580 Get that sucker on stage now! */
581 for (i = 0; i < si->nscreens; i++)
582 XMapRaised(si->dpy, si->screens[i].screensaver_window);
584 XGetInputFocus (dpy, &focus, &revert_to);
585 #if defined(HAVE_MOTIF) && !defined(DESTROY_WORKS)
586 /* This fucker blows up if we destroy the widget. I can't figure
587 out why. The second destroy phase dereferences freed memory...
588 So we just keep it around; but unrealizing or unmanaging it
589 doesn't work right either, so we hack the window directly. FMH.
591 if (XtWindow (passwd_form))
592 XMapRaised (dpy, XtWindow (passwd_dialog));
596 pop_up_athena_dialog_box (parent, passwd_text, passwd_dialog,
599 pop_up_dialog_box (passwd_dialog, passwd_form,
600 /* for debugging -- don't ask */
601 (si->prefs.debug_p ? 69 : 0) +
603 XtManageChild (passwd_form);
606 #if defined(HAVE_MOTIF) && (XmVersion < 1002)
607 /* The focus stuff changed around; this causes problems in 1.2.1
608 but is necessary in 1.1.5. */
609 XmProcessTraversal (passwd_text, XmTRAVERSE_CURRENT);
612 passwd_idle_timer_tick = p->passwd_timeout / 1000;
613 passwd_idle_id = XtAppAddTimeOut (si->app, 1000, passwd_idle_timer,
618 roger(roger_label, 0, 0);
619 #endif /* HAVE_ATHENA */
621 if (!si->prefs.debug_p)
622 XGrabServer (dpy); /* ############ DANGER! */
624 /* this call to ungrab used to be in main_loop() - see comment in
625 xscreensaver.c around line 857. */
626 ungrab_keyboard_and_mouse (si);
628 while (passwd_state == pw_read)
631 XtAppNextEvent (si->app, &event);
632 /* wait for timer event */
633 if (event.xany.type == 0 && passwd_idle_timer_tick == 0)
634 passwd_state = pw_time;
635 XtDispatchEvent (&event);
638 XSync (dpy, False); /* ###### (danger over) */
640 if (passwd_state != pw_time)
641 XtRemoveTimeOut (passwd_idle_id);
643 if (passwd_state != pw_ok)
646 switch (passwd_state)
648 case pw_time: lose = "Timed out!"; break;
649 case pw_fail: lose = "Sorry!"; break;
650 case pw_cancel: lose = 0; break;
654 XmProcessTraversal (passwd_cancel, 0); /* turn off I-beam */
659 /* show the message */
660 passwd_set_label(lose,strlen(lose)+1);
662 /* and clear the password line */
663 memset(typed_passwd, 0, sizeof(typed_passwd));
664 text_field_set_string (passwd_text, "", 0);
666 text_field_set_string (passwd_text, lose, strlen (lose) + 1);
668 passwd_idle_timer_tick = 1;
669 passwd_idle_id = XtAppAddTimeOut (si->app, 3000, passwd_idle_timer,
674 XtAppNextEvent (si->app, &event);
675 if (event.xany.type == 0 && /* wait for timer event */
676 passwd_idle_timer_tick == 0)
678 XtDispatchEvent (&event);
682 memset (typed_passwd, 0, sizeof(typed_passwd));
683 text_field_set_string (passwd_text, "", 0);
684 XtSetKeyboardFocus (parent, None);
687 XtDestroyWidget (passwd_dialog);
690 XUnmapWindow (XtDisplay (passwd_dialog), XtWindow (passwd_dialog));
693 XErrorHandler old_handler = XSetErrorHandler (BadWindow_ehandler);
694 /* I don't understand why this doesn't refocus on the old selected
695 window when MWM is running in click-to-type mode. The value of
696 `focus' seems to be correct. */
697 XSetInputFocus (dpy, focus, revert_to, CurrentTime);
699 XSetErrorHandler (old_handler);
702 /* Since we installed our colormap to display the dialog properly, put
703 the old one back, so that the screensaver_window is now displayed
705 for (i = 0; i < si->nscreens; i++)
707 saver_screen_info *ssi = &si->screens[i];
709 XInstallColormap (si->dpy, ssi->cmap);
712 return (passwd_state == pw_ok ? True : False);
716 unlock_p (saver_info *si)
718 static Bool initted = False;
721 #ifndef VERIFY_CALLBACK_WORKS
722 XtAppAddActions (si->app, actions, XtNumber (actions));
728 make_passwd_dialog (si);
729 return pop_passwd_dialog (si);
732 #endif /* !NO_LOCKING -- whole file */