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;
81 static char typed_passwd [PASSWDLEN];
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), PASSWDLEN);
104 # endif /* HAVE_ATHENA */
105 if (passwd_valid_p (typed_passwd))
106 passwd_state = pw_ok;
108 passwd_state = pw_fail;
111 user_vms = getenv("USER");
112 if (validate_user(user_vms,typed_passwd) == 1)
113 passwd_state = pw_ok;
115 passwd_state = pw_fail;
119 #if defined(HAVE_MOTIF) && defined(VERIFY_CALLBACK_WORKS)
121 /* It looks to me like adding any modifyVerify callback causes
122 Motif 1.1.4 to free the the TextF_Value() twice. I can't see
123 the bug in the Motif source, but Purify complains, even if
124 check_passwd_cb() is a no-op.
126 Update: Motif 1.2.1 also loses, but in a different way: it
127 writes beyond the end of a malloc'ed block in ModifyVerify().
128 Probably this block is the text field's text.
132 check_passwd_cb (Widget button, XtPointer client_data, XtPointer call_data)
134 XmTextVerifyCallbackStruct *vcb = (XmTextVerifyCallbackStruct *) call_data;
136 if (passwd_state != pw_read)
138 else if (vcb->reason == XmCR_ACTIVATE)
140 passwd_done_cb (0, 0, 0);
142 else if (vcb->text->length > 1) /* don't allow "paste" operations */
146 else if (vcb->text->ptr != 0)
149 strncat (typed_passwd, vcb->text->ptr, vcb->text->length);
150 typed_passwd [vcb->endPos + vcb->text->length] = 0;
151 for (i = 0; i < vcb->text->length; i++)
152 vcb->text->ptr [i] = '*';
156 # else /* HAVE_ATHENA || !VERIFY_CALLBACK_WORKS */
158 static void keypress (Widget w, XEvent *event, String *av, Cardinal *ac);
159 static void backspace (Widget w, XEvent *event, String *av, Cardinal *ac);
160 static void kill_line (Widget w, XEvent *event, String *av, Cardinal *ac);
161 static void done (Widget w, XEvent *event, String *av, Cardinal *ac);
163 static XtActionsRec actions[] = {{"keypress", keypress},
164 {"backspace", backspace},
165 {"kill_line", kill_line},
170 # if 0 /* oh fuck, why doesn't this work? */
171 static char translations[] = "\
172 <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\
182 static char translations[] = "<Key>:keypress()";
184 # endif /* HAVE_MOTIF */
188 text_field_set_string (Widget widget, char *text, int position)
191 XmTextFieldSetString (widget, text);
192 XmTextFieldSetInsertionPosition (widget, position);
194 #else /* HAVE_ATHENA */
200 block.length = strlen (text);
203 if (block.length == 0)
205 buf = XawDialogGetValueString(passwd_form);
207 end_pos = strlen(buf);
211 XawTextReplace (widget, 0, end_pos, &block);
212 XawTextSetInsertionPoint (widget, position);
213 #endif /* HAVE_ATHENA */
218 keypress (Widget w, XEvent *event, String *argv, Cardinal *argc)
221 char s [sizeof (typed_passwd)];
222 int size = XLookupString ((XKeyEvent *) event, s, sizeof (s), 0, 0);
223 if (size != 1) return;
225 /* hack because I can't get translations to dance to my tune... */
226 if (*s == '\010') { backspace (w, event, argv, argc); return; }
227 if (*s == '\177') { backspace (w, event, argv, argc); return; }
228 if (*s == '\025') { kill_line (w, event, argv, argc); return; }
229 if (*s == '\030') { kill_line (w, event, argv, argc); return; }
230 if (*s == '\012') { done (w, event, argv, argc); return; }
231 if (*s == '\015') { done (w, event, argv, argc); return; }
233 i = j = strlen (typed_passwd);
234 typed_passwd [i] = *s;
239 text_field_set_string (passwd_text, s, j + 1);
243 backspace (Widget w, XEvent *event, String *argv, Cardinal *argc)
245 char s [sizeof (typed_passwd)];
246 int i = strlen (typed_passwd);
250 typed_passwd [--i] = 0;
255 text_field_set_string (passwd_text, s, j + 1);
259 kill_line (Widget w, XEvent *event, String *argv, Cardinal *argc)
261 memset (typed_passwd, 0, sizeof (typed_passwd));
262 text_field_set_string (passwd_text, "", 0);
266 done (Widget w, XEvent *event, String *argv, Cardinal *argc)
268 passwd_done_cb (w, 0, 0);
271 #endif /* HAVE_ATHENA || !VERIFY_CALLBACK_WORKS */
274 extern void skull (Display *, Window, GC, GC, int, int, int, int);
277 roger (Widget button, XtPointer client_data, XtPointer call_data)
279 Display *dpy = XtDisplay (button);
280 Screen *screen = XtScreen (button);
281 Window window = XtWindow (button);
286 GC draw_gc, erase_gc;
289 XWindowAttributes xgwa;
290 XGetWindowAttributes (dpy, window, &xgwa);
291 cmap = xgwa.colormap;
292 if (xgwa.width > xgwa.height) size = xgwa.height;
293 else size = xgwa.width;
294 if (size > 40) size -= 30;
295 x = (xgwa.width - size) / 2;
296 y = (xgwa.height - size) / 2;
297 XtSetArg (av [ac], XtNforeground, &fg); ac++;
298 XtSetArg (av [ac], XtNbackground, &bg); ac++;
299 XtGetValues (button, av, ac);
300 /* if it's black on white, swap it cause it looks better (hack hack) */
301 if (fg == BlackPixelOfScreen (screen) && bg == WhitePixelOfScreen (screen))
302 fg = WhitePixelOfScreen (screen), bg = BlackPixelOfScreen (screen);
304 erase_gc = XCreateGC (dpy, window, GCForeground, &gcv);
306 draw_gc = XCreateGC (dpy, window, GCForeground, &gcv);
307 XFillRectangle (dpy, window, erase_gc, 0, 0, xgwa.width, xgwa.height);
308 skull (dpy, window, draw_gc, erase_gc, x, y, size, size);
309 XFreeGC (dpy, draw_gc);
310 XFreeGC (dpy, erase_gc);
314 make_passwd_dialog (saver_info *si)
317 saver_screen_info *ssi = si->default_screen;
318 Widget parent = ssi->toplevel_shell;
320 if (ssi->demo_cmap &&
321 ssi->demo_cmap != ssi->cmap &&
322 ssi->demo_cmap != DefaultColormapOfScreen (ssi->screen))
324 XFreeColormap (si->dpy, ssi->demo_cmap);
328 if (ssi->default_visual == DefaultVisualOfScreen (ssi->screen))
329 ssi->demo_cmap = DefaultColormapOfScreen (ssi->screen);
331 ssi->demo_cmap = XCreateColormap (si->dpy,
332 RootWindowOfScreen (ssi->screen),
333 ssi->default_visual, AllocNone);
335 create_passwd_dialog (parent, ssi->default_visual, ssi->demo_cmap);
339 XtVaSetValues(passwd_form, XtNvalue, typed_passwd, 0);
341 XawDialogAddButton(passwd_form,"ok", passwd_done_cb, 0);
342 XawDialogAddButton(passwd_form,"cancel", passwd_cancel_cb, 0);
343 passwd_done = XtNameToWidget(passwd_form,"ok");
344 passwd_text = XtNameToWidget(passwd_form,"value");
346 XtAppAddActions(XtWidgetToApplicationContext(passwd_text),
347 actionsList, XtNumber(actionsList));
348 XtOverrideTranslations(passwd_text, XtParseTranslationTable(Translations));
350 #else /* HAVE_MOTIF */
352 XtAddCallback (passwd_done, XmNactivateCallback, passwd_done_cb, 0);
353 XtAddCallback (passwd_cancel, XmNactivateCallback, passwd_cancel_cb, 0);
354 XtAddCallback (roger_label, XmNexposeCallback, roger, 0);
356 # ifdef VERIFY_CALLBACK_WORKS
357 XtAddCallback (passwd_text, XmNmodifyVerifyCallback, check_passwd_cb, 0);
358 XtAddCallback (passwd_text, XmNactivateCallback, check_passwd_cb, 0);
360 XtAddCallback (passwd_text, XmNactivateCallback, passwd_done_cb, 0);
361 XtOverrideTranslations (passwd_text, XtParseTranslationTable (translations));
364 # if defined(HAVE_MOTIF) && (XmVersion >= 1002)
365 /* The focus stuff changed around; this didn't exist in 1.1.5. */
366 XtVaSetValues (passwd_form, XmNinitialFocus, passwd_text, 0);
369 /* Another random thing necessary in 1.2.1 but not 1.1.5... */
370 XtVaSetValues (roger_label, XmNborderWidth, 2, 0);
372 #endif /* HAVE_MOTIF */
376 struct passwd *pw = getpwuid (getuid ());
377 username = pw->pw_name;
379 #else /* VMS -- from "R.S.Niranjan" <U00C782%BRKVC1@navistar.com> who says
380 that on OpenVMS 6.1, using `struct passwd' crashes... */
381 username = getenv("USER");
384 format_into_label (passwd_label1, si->version);
385 format_into_label (passwd_label3, (username ? username : "???"));
388 static int passwd_idle_timer_tick;
389 static XtIntervalId passwd_idle_id;
392 passwd_idle_timer (XtPointer closure, XtIntervalId *id)
394 saver_info *si = (saver_info *) closure;
395 saver_preferences *p = &si->prefs;
397 Display *dpy = XtDisplay (passwd_form);
399 Window window = XtWindow (passwd_form);
401 Window window = XtWindow (XtParent(passwd_done));
403 static Dimension x, y, d, s, ss;
405 int max = p->passwd_timeout / 1000;
407 idle_timer ((XtPointer) si, id);
409 if (passwd_idle_timer_tick == max) /* first time */
413 unsigned long fg, bg, ts, bs;
414 Dimension w = 0, h = 0;
415 XtVaGetValues(XtParent(passwd_done),
418 XtVaGetValues(passwd_done,
423 XmNtopShadowColor, &ts,
424 XmNbottomShadowColor, &bs,
427 if (ts != bg && ts != fg)
429 if (bs != bg && bs != fg)
440 #else /* HAVE_ATHENA */
444 unsigned long fg, bg;
445 XtSetArg (av [ac], XtNheight, &d); ac++;
446 XtGetValues (passwd_done, av, ac);
448 XtSetArg (av [ac], XtNwidth, &x); ac++;
449 XtSetArg (av [ac], XtNheight, &y); ac++;
450 XtSetArg (av [ac], XtNforeground, &fg); ac++;
451 XtSetArg (av [ac], XtNbackground, &bg); ac++;
452 XtGetValues (passwd_form, av, ac);
457 #endif /* HAVE_ATHENA */
460 if (gc) XFreeGC (dpy, gc);
461 gc = XCreateGC (dpy, window, GCForeground, &gcv);
462 s = 360*64 / (passwd_idle_timer_tick - 1);
464 XFillArc (dpy, window, gc, x, y, d, d, 0, 360*64);
465 XSetForeground (dpy, gc, bg);
471 if (--passwd_idle_timer_tick)
473 passwd_idle_id = XtAppAddTimeOut (si->app, 1000, passwd_idle_timer,
475 XFillArc (dpy, window, gc, x, y, d, d, ss, s);
483 pop_up_athena_dialog_box (Widget parent, Widget focus, Widget dialog,
484 Widget form, int where)
486 /* modified from demo.c */
487 /* I'm sure this is the wrong way to pop up a dialog box, but I can't
488 figure out how else to do it.
490 It's important that the screensaver dialogs not get decorated or
491 otherwise reparented by the window manager, because they need to be
492 children of the *real* root window, not the WM's virtual root, in
493 order for us to guarentee that they are visible above the screensaver
498 Dimension sw, sh, x, y, w, h;
500 XtRealizeWidget(dialog);
501 sw = WidthOfScreen (XtScreen (dialog));
502 sh = HeightOfScreen (XtScreen (dialog));
504 XtSetArg (av [ac], XtNwidth, &w); ac++;
505 XtSetArg (av [ac], XtNheight, &h); ac++;
506 XtGetValues (form, av, ac);
509 case 0: /* center it in the top-right quadrant */
510 x = (sw/2 + w) / 2 + (sw/2) - w;
511 y = (sh/2 + h) / 2 - h;
513 case 1: /* center it in the bottom-right quadrant */
514 x = (sw/2 + w) / 2 + (sw/2) - w;
515 y = (sh/2 + h) / 2 + (sh/2) - h;
517 case 2: /* center it on the screen */
518 x = (sw + w) / 2 - w;
519 y = (sh + h) / 2 - h;
524 if (x + w > sw) x = sw - w;
525 if (y + h > sh) y = sh - h;
527 XtVaSetValues(dialog,
535 XtPopup(dialog,XtGrabNone);
536 steal_focus_and_colormap (focus);
540 passwd_set_label (char *buf, int len)
545 label=XtNameToWidget(XtParent(passwd_text),"*label");
550 #endif /* HAVE_ATHENA */
553 pop_passwd_dialog (saver_info *si)
555 saver_preferences *p = &si->prefs;
556 saver_screen_info *ssi = si->default_screen;
557 Widget parent = ssi->toplevel_shell;
558 Display *dpy = XtDisplay (passwd_dialog);
563 typed_passwd [0] = 0;
564 passwd_state = pw_read;
565 text_field_set_string (passwd_text, "", 0);
567 /* In case one of the hacks has unmapped it temporarily...
568 Get that sucker on stage now! */
569 for (i = 0; i < si->nscreens; i++)
570 XMapRaised(si->dpy, si->screens[i].screensaver_window);
572 XGetInputFocus (dpy, &focus, &revert_to);
573 #if defined(HAVE_MOTIF) && !defined(DESTROY_WORKS)
574 /* This fucker blows up if we destroy the widget. I can't figure
575 out why. The second destroy phase dereferences freed memory...
576 So we just keep it around; but unrealizing or unmanaging it
577 doesn't work right either, so we hack the window directly. FMH.
579 if (XtWindow (passwd_form))
580 XMapRaised (dpy, XtWindow (passwd_dialog));
584 pop_up_athena_dialog_box (parent, passwd_text, passwd_dialog,
587 pop_up_dialog_box (passwd_dialog, passwd_form,
589 (si->prefs.debug_p ? 69 : 0) +
592 XtManageChild (passwd_form);
595 #if defined(HAVE_MOTIF) && (XmVersion < 1002)
596 /* The focus stuff changed around; this causes problems in 1.2.1
597 but is necessary in 1.1.5. */
598 XmProcessTraversal (passwd_text, XmTRAVERSE_CURRENT);
601 passwd_idle_timer_tick = p->passwd_timeout / 1000;
602 passwd_idle_id = XtAppAddTimeOut (si->app, 1000, passwd_idle_timer,
607 roger(roger_label, 0, 0);
608 #endif /* HAVE_ATHENA */
611 if (!si->prefs.debug_p)
613 XGrabServer (dpy); /* ############ DANGER! */
615 /* this call to ungrab used to be in main_loop() - see comment in
616 xscreensaver.c around line 857. */
617 ungrab_keyboard_and_mouse (si->dpy);
619 while (passwd_state == pw_read)
622 XtAppNextEvent (si->app, &event);
623 /* wait for timer event */
624 if (event.xany.type == 0 && passwd_idle_timer_tick == 0)
625 passwd_state = pw_time;
626 XtDispatchEvent (&event);
629 XSync (dpy, False); /* ###### (danger over) */
631 if (passwd_state != pw_time)
632 XtRemoveTimeOut (passwd_idle_id);
634 if (passwd_state != pw_ok)
637 switch (passwd_state)
639 case pw_time: lose = "Timed out!"; break;
640 case pw_fail: lose = "Sorry!"; break;
641 case pw_cancel: lose = 0; break;
645 XmProcessTraversal (passwd_cancel, 0); /* turn off I-beam */
650 /* show the message */
651 passwd_set_label(lose,strlen(lose)+1);
653 /* and clear the password line */
654 memset(typed_passwd, 0, PASSWDLEN);
655 text_field_set_string (passwd_text, "", 0);
657 text_field_set_string (passwd_text, lose, strlen (lose) + 1);
659 passwd_idle_timer_tick = 1;
660 passwd_idle_id = XtAppAddTimeOut (si->app, 3000, passwd_idle_timer,
665 XtAppNextEvent (si->app, &event);
666 if (event.xany.type == 0 && /* wait for timer event */
667 passwd_idle_timer_tick == 0)
669 XtDispatchEvent (&event);
673 memset (typed_passwd, 0, sizeof (typed_passwd));
674 text_field_set_string (passwd_text, "", 0);
675 XtSetKeyboardFocus (parent, None);
678 XtDestroyWidget (passwd_dialog);
681 XUnmapWindow (XtDisplay (passwd_dialog), XtWindow (passwd_dialog));
684 XErrorHandler old_handler = XSetErrorHandler (BadWindow_ehandler);
685 /* I don't understand why this doesn't refocus on the old selected
686 window when MWM is running in click-to-type mode. The value of
687 `focus' seems to be correct. */
688 XSetInputFocus (dpy, focus, revert_to, CurrentTime);
690 XSetErrorHandler (old_handler);
693 /* Since we installed our colormap to display the dialog properly, put
694 the old one back, so that the screensaver_window is now displayed
696 for (i = 0; i < si->nscreens; i++)
698 saver_screen_info *ssi = &si->screens[i];
700 XInstallColormap (si->dpy, ssi->cmap);
703 return (passwd_state == pw_ok ? True : False);
707 unlock_p (saver_info *si)
709 static Bool initted = False;
712 #ifndef VERIFY_CALLBACK_WORKS
713 XtAppAddActions (si->app, actions, XtNumber (actions));
719 make_passwd_dialog (si);
720 return pop_passwd_dialog (si);
723 #endif /* !NO_LOCKING -- whole file */