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 <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 */
52 extern Widget passwd_dialog;
53 extern Widget passwd_form;
54 extern Widget roger_label;
55 extern Widget passwd_label1;
56 extern Widget passwd_label3;
57 extern Widget passwd_cancel;
60 extern Widget passwd_text;
61 extern Widget passwd_done;
62 #else /* HAVE_ATHENA */
63 static Widget passwd_text = 0; /* gag... */
64 static Widget passwd_done = 0;
65 #endif /* HAVE_ATHENA */
69 static enum { pw_read, pw_ok, pw_fail, pw_cancel, pw_time } passwd_state;
70 static char typed_passwd [80];
73 #if defined(HAVE_ATHENA) || (XmVersion >= 1002)
74 /* The `destroy' bug apears to be fixed as of Motif 1.2.1, but
75 the `verify-callback' bug is still present. */
76 # define DESTROY_WORKS
80 passwd_cancel_cb (Widget button, XtPointer client_data, XtPointer call_data)
82 passwd_state = pw_cancel;
88 vms_passwd_valid_p(char *pw)
90 char *u = getenv("USER");
91 return (validate_user (i, typed_passwd) == 1);
93 # undef passwd_valid_p
94 # define passwd_valid_p vms_passwd_valid_p
100 passwd_done_cb (Widget button, XtPointer client_data, XtPointer call_data)
102 if (passwd_state != pw_read) return; /* already done */
104 if (passwd_valid_p (typed_passwd))
105 passwd_state = pw_ok;
107 passwd_state = pw_fail;
111 #if defined(HAVE_MOTIF) && defined(VERIFY_CALLBACK_WORKS)
113 /* It looks to me like adding any modifyVerify callback causes
114 Motif 1.1.4 to free the the TextF_Value() twice. I can't see
115 the bug in the Motif source, but Purify complains, even if
116 check_passwd_cb() is a no-op.
118 Update: Motif 1.2.1 also loses, but in a different way: it
119 writes beyond the end of a malloc'ed block in ModifyVerify().
120 Probably this block is the text field's text.
124 check_passwd_cb (Widget button, XtPointer client_data, XtPointer call_data)
126 XmTextVerifyCallbackStruct *vcb = (XmTextVerifyCallbackStruct *) call_data;
128 if (passwd_state != pw_read)
130 else if (vcb->reason == XmCR_ACTIVATE)
132 passwd_done_cb (0, 0, 0);
134 else if (vcb->text->length > 1) /* don't allow "paste" operations */
138 else if (vcb->text->ptr != 0)
141 int L = vcb->text->length;
142 if (L >= sizeof(typed_passwd))
143 L = sizeof(typed_passwd)-1;
144 strncat (typed_passwd, vcb->text->ptr, L);
145 typed_passwd [vcb->endPos + L] = 0;
146 for (i = 0; i < vcb->text->length; i++)
147 vcb->text->ptr [i] = '*';
151 # else /* HAVE_ATHENA || !VERIFY_CALLBACK_WORKS */
153 static void keypress (Widget w, XEvent *event, String *av, Cardinal *ac);
154 static void backspace (Widget w, XEvent *event, String *av, Cardinal *ac);
155 static void kill_line (Widget w, XEvent *event, String *av, Cardinal *ac);
156 static void done (Widget w, XEvent *event, String *av, Cardinal *ac);
158 static XtActionsRec actions[] = {{"keypress", keypress},
159 {"backspace", backspace},
160 {"kill_line", kill_line},
164 # if 0 /* This works for Athena, but not Motif: keypress() gets called
165 for all keys anyway. So, the implementation of keypress()
166 has BackSpace, etc, hardcoded into it instead. FMH!
168 static char translations[] = ("<Key>BackSpace: backspace()\n"
169 "<Key>Delete: backspace()\n"
170 "Ctrl<Key>H: backspace()\n"
171 "Ctrl<Key>U: kill_line()\n"
172 "Ctrl<Key>X: kill_line()\n"
173 "Ctrl<Key>J: done()\n"
174 "Ctrl<Key>M: done()\n"
175 "<Key>: keypress()\n");
177 static char translations[] = ("<Key>: keypress()\n");
182 text_field_set_string (Widget widget, char *text, int position)
185 XmTextFieldSetString (widget, text);
186 XmTextFieldSetInsertionPosition (widget, position);
188 #else /* HAVE_ATHENA */
194 block.length = strlen (text);
197 if (block.length == 0)
199 buf = XawDialogGetValueString(passwd_form);
201 end_pos = strlen(buf);
205 XawTextReplace (widget, 0, end_pos, &block);
206 XawTextSetInsertionPoint (widget, position);
207 #endif /* HAVE_ATHENA */
212 keypress (Widget w, XEvent *event, String *argv, Cardinal *argc)
215 char s [sizeof(typed_passwd)];
216 int size = XLookupString ((XKeyEvent *) event, s, sizeof(s)-1, 0, 0);
217 if (size != 1) return;
219 /* hack because I can't get translations to dance to my tune... */
220 if (*s == '\010') { backspace (w, event, argv, argc); return; }
221 if (*s == '\177') { backspace (w, event, argv, argc); return; }
222 if (*s == '\025') { kill_line (w, event, argv, argc); return; }
223 if (*s == '\030') { kill_line (w, event, argv, argc); return; }
224 if (*s == '\012') { done (w, event, argv, argc); return; }
225 if (*s == '\015') { done (w, event, argv, argc); return; }
227 i = j = strlen (typed_passwd);
229 if (i >= (sizeof(typed_passwd)-1))
231 XBell(XtDisplay(w), 0);
235 typed_passwd [i] = *s;
240 text_field_set_string (passwd_text, s, j + 1);
244 backspace (Widget w, XEvent *event, String *argv, Cardinal *argc)
246 char s [sizeof(typed_passwd)];
247 int i = strlen (typed_passwd);
251 typed_passwd [--i] = 0;
256 text_field_set_string (passwd_text, s, j + 1);
260 kill_line (Widget w, XEvent *event, String *argv, Cardinal *argc)
262 memset (typed_passwd, 0, sizeof(typed_passwd));
263 text_field_set_string (passwd_text, "", 0);
267 done (Widget w, XEvent *event, String *argv, Cardinal *argc)
269 passwd_done_cb (w, 0, 0);
272 #endif /* HAVE_ATHENA || !VERIFY_CALLBACK_WORKS */
275 extern void skull (Display *, Window, GC, GC, int, int, int, int);
278 roger (Widget button, XtPointer client_data, XtPointer call_data)
280 Display *dpy = XtDisplay (button);
281 Screen *screen = XtScreen (button);
282 Window window = XtWindow (button);
287 GC draw_gc, erase_gc;
290 XWindowAttributes xgwa;
291 XGetWindowAttributes (dpy, window, &xgwa);
292 cmap = xgwa.colormap;
293 if (xgwa.width > xgwa.height) size = xgwa.height;
294 else size = xgwa.width;
295 if (size > 40) size -= 30;
296 x = (xgwa.width - size) / 2;
297 y = (xgwa.height - size) / 2;
298 XtSetArg (av [ac], XtNforeground, &fg); ac++;
299 XtSetArg (av [ac], XtNbackground, &bg); ac++;
300 XtGetValues (button, av, ac);
301 /* if it's black on white, swap it cause it looks better (hack hack) */
302 if (fg == BlackPixelOfScreen (screen) && bg == WhitePixelOfScreen (screen))
303 fg = WhitePixelOfScreen (screen), bg = BlackPixelOfScreen (screen);
305 erase_gc = XCreateGC (dpy, window, GCForeground, &gcv);
307 draw_gc = XCreateGC (dpy, window, GCForeground, &gcv);
308 XFillRectangle (dpy, window, erase_gc, 0, 0, xgwa.width, xgwa.height);
309 skull (dpy, window, draw_gc, erase_gc, x, y, size, size);
310 XFreeGC (dpy, draw_gc);
311 XFreeGC (dpy, erase_gc);
315 make_passwd_dialog (saver_info *si)
318 saver_screen_info *ssi = si->default_screen;
319 Widget parent = ssi->toplevel_shell;
321 if (ssi->demo_cmap &&
322 ssi->demo_cmap != ssi->cmap &&
323 ssi->demo_cmap != DefaultColormapOfScreen (ssi->screen))
325 XFreeColormap (si->dpy, ssi->demo_cmap);
329 if (ssi->default_visual == DefaultVisualOfScreen (ssi->screen))
330 ssi->demo_cmap = DefaultColormapOfScreen (ssi->screen);
332 ssi->demo_cmap = XCreateColormap (si->dpy,
333 RootWindowOfScreen (ssi->screen),
334 ssi->default_visual, AllocNone);
336 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 actions, XtNumber(actions));
348 XtOverrideTranslations(passwd_text, XtParseTranslationTable(translations));
350 /* Lose the label on the inner dialog. */
352 Widget w = XtNameToWidget(passwd_form, "label");
353 if (w) XtUnmanageChild(w);
356 #else /* HAVE_MOTIF */
358 XtAddCallback (passwd_done, XmNactivateCallback, passwd_done_cb, 0);
359 XtAddCallback (passwd_cancel, XmNactivateCallback, passwd_cancel_cb, 0);
360 XtAddCallback (roger_label, XmNexposeCallback, roger, 0);
362 # ifdef VERIFY_CALLBACK_WORKS
363 XtAddCallback (passwd_text, XmNmodifyVerifyCallback, check_passwd_cb, 0);
364 XtAddCallback (passwd_text, XmNactivateCallback, check_passwd_cb, 0);
365 # else /* !VERIFY_CALLBACK_WORKS */
366 XtAddCallback (passwd_text, XmNactivateCallback, passwd_done_cb, 0);
367 XtOverrideTranslations (passwd_text, XtParseTranslationTable (translations));
368 # endif /* !VERIFY_CALLBACK_WORKS */
370 # if defined(HAVE_MOTIF) && (XmVersion >= 1002)
371 /* The focus stuff changed around; this didn't exist in 1.1.5. */
372 XtVaSetValues (passwd_form, XmNinitialFocus, passwd_text, 0);
373 # endif /* HAVE_MOTIF && XmVersion >= 1002 */
375 /* Another random thing necessary in 1.2.1 but not 1.1.5... */
376 XtVaSetValues (roger_label, XmNborderWidth, 2, 0);
378 #endif /* HAVE_MOTIF */
382 struct passwd *pw = getpwuid (getuid ());
383 username = pw->pw_name;
385 #else /* VMS -- from "R.S.Niranjan" <U00C782%BRKVC1@navistar.com> who says
386 that on OpenVMS 6.1, using `struct passwd' crashes... */
387 username = getenv("USER");
390 format_into_label (passwd_label1, si->version);
391 format_into_label (passwd_label3, (username ? username : "???"));
394 static int passwd_idle_timer_tick = -1;
395 static XtIntervalId passwd_idle_id;
398 passwd_idle_timer (XtPointer closure, XtIntervalId *id)
400 saver_info *si = (saver_info *) closure;
401 saver_preferences *p = &si->prefs;
403 Display *dpy = XtDisplay (passwd_form);
405 Window window = XtWindow (passwd_form);
407 Window window = XtWindow (XtParent(passwd_done));
409 static Dimension x, y, d, s, ss;
411 int max = p->passwd_timeout / 1000;
413 idle_timer ((XtPointer) si, id);
415 if (passwd_idle_timer_tick == max) /* first time */
419 unsigned long fg = 0, bg = 0, ts = 0, bs = 0;
420 Dimension w = 0, h = 0;
421 XtVaGetValues(XtParent(passwd_done),
424 XtVaGetValues(passwd_done,
429 XmNtopShadowColor, &ts,
430 XmNbottomShadowColor, &bs,
433 if (ts != bg && ts != fg)
435 if (bs != bg && bs != fg)
443 # ifdef __sgi /* Kludge -- SGI's Motif hacks place buttons differently. */
445 static int sgi_mode = -1;
447 sgi_mode = get_boolean_resource("sgiMode", "sgiMode") ? 1 : 0;
457 #else /* HAVE_ATHENA */
461 unsigned long fg = 0, bg = 0;
462 XtSetArg (av [ac], XtNheight, &d); ac++;
463 XtGetValues (passwd_done, av, ac);
465 XtSetArg (av [ac], XtNwidth, &x); ac++;
466 XtSetArg (av [ac], XtNheight, &y); ac++;
467 XtSetArg (av [ac], XtNforeground, &fg); ac++;
468 XtSetArg (av [ac], XtNbackground, &bg); ac++;
469 XtGetValues (passwd_form, av, ac);
474 #endif /* HAVE_ATHENA */
477 if (gc) XFreeGC (dpy, gc);
478 gc = XCreateGC (dpy, window, GCForeground, &gcv);
479 s = 360*64 / (passwd_idle_timer_tick - 1);
481 XFillArc (dpy, window, gc, x, y, d, d, 0, 360*64);
482 XSetForeground (dpy, gc, bg);
488 if (--passwd_idle_timer_tick)
490 passwd_idle_id = XtAppAddTimeOut (si->app, 1000, passwd_idle_timer,
492 XFillArc (dpy, window, gc, x, y, d, d, ss, s);
499 pop_passwd_dialog (saver_info *si)
501 saver_preferences *p = &si->prefs;
502 saver_screen_info *ssi = si->default_screen;
503 Widget parent = ssi->toplevel_shell;
504 Display *dpy = XtDisplay (passwd_dialog);
509 typed_passwd [0] = 0;
510 passwd_state = pw_read;
511 text_field_set_string (passwd_text, "", 0);
513 /* In case one of the hacks has unmapped it temporarily...
514 Get that sucker on stage now! */
515 for (i = 0; i < si->nscreens; i++)
516 XMapRaised(si->dpy, si->screens[i].screensaver_window);
518 XGetInputFocus (dpy, &focus, &revert_to);
519 #if defined(HAVE_MOTIF) && !defined(DESTROY_WORKS)
520 /* This fucker blows up if we destroy the widget. I can't figure
521 out why. The second destroy phase dereferences freed memory...
522 So we just keep it around; but unrealizing or unmanaging it
523 doesn't work right either, so we hack the window directly. FMH.
525 if (XtWindow (passwd_form))
526 XMapRaised (dpy, XtWindow (passwd_dialog));
527 #endif /* HAVE_MOTIF && !DESTROY_WORKS */
529 monitor_power_on (si);
530 pop_up_dialog_box (passwd_dialog, passwd_form,
531 /* for debugging -- don't ask */
532 (si->prefs.debug_p ? 69 : 0) +
534 XtManageChild (passwd_form);
537 steal_focus_and_colormap (passwd_text);
539 /* For some reason, the passwd_form box is not stretching all the way
540 to the right edge of the window, despite being XtChainRight.
541 So... resize it by hand.
544 Dimension x=0, w=0, h=0;
545 XtVaGetValues(passwd_form, XtNx, &x, XtNwidth, &w, XtNheight, &h, 0);
546 XtVaGetValues(XtParent(passwd_form), XtNwidth, &w, 0);
549 if (w > 0) XtResizeWidget(passwd_form, w, h, 0);
552 #endif /* HAVE_ATHENA */
555 #if defined(HAVE_MOTIF) && (XmVersion < 1002)
556 /* The focus stuff changed around; this causes problems in 1.2.1
557 but is necessary in 1.1.5. */
558 XmProcessTraversal (passwd_text, XmTRAVERSE_CURRENT);
559 #endif /* HAVE_MOTIF && XmVersion < 1002 */
561 passwd_idle_timer_tick = p->passwd_timeout / 1000;
562 passwd_idle_id = XtAppAddTimeOut (si->app, 1000, passwd_idle_timer,
567 roger(roger_label, 0, 0);
568 #endif /* HAVE_ATHENA */
570 if (!si->prefs.debug_p)
571 XGrabServer (dpy); /* ############ DANGER! */
573 /* this call to ungrab used to be in main_loop() - see comment in
574 xscreensaver.c around line 857. */
575 ungrab_keyboard_and_mouse (si);
577 while (passwd_state == pw_read)
580 XtAppNextEvent (si->app, &event);
581 /* wait for timer event */
582 if (event.xany.type == 0 && passwd_idle_timer_tick == 0)
583 passwd_state = pw_time;
584 XtDispatchEvent (&event);
587 XSync (dpy, False); /* ###### (danger over) */
589 if (passwd_state != pw_time)
590 XtRemoveTimeOut (passwd_idle_id);
592 if (passwd_state != pw_ok)
595 switch (passwd_state)
597 case pw_time: lose = "Timed out!"; break;
598 case pw_fail: lose = "Sorry!"; break;
599 case pw_cancel: lose = 0; break;
604 XmProcessTraversal (passwd_cancel, 0); /* turn off I-beam */
605 #else /* HAVE_ATHENA */
606 steal_focus_and_colormap (passwd_done);
607 #endif /* HAVE_ATHENA */
611 text_field_set_string (passwd_text, lose, strlen (lose) + 1);
613 passwd_idle_timer_tick = 1;
614 passwd_idle_id = XtAppAddTimeOut (si->app, 3000, passwd_idle_timer,
619 XtAppNextEvent (si->app, &event);
620 if (event.xany.type == 0 && /* wait for timer event */
621 passwd_idle_timer_tick == 0)
623 XtDispatchEvent (&event);
627 memset (typed_passwd, 0, sizeof(typed_passwd));
628 text_field_set_string (passwd_text, "", 0);
629 XtSetKeyboardFocus (parent, None);
632 XtDestroyWidget (passwd_dialog);
634 #else /* !DESTROY_WORKS */
635 XUnmapWindow (XtDisplay (passwd_dialog), XtWindow (passwd_dialog));
636 #endif /* !DESTROY_WORKS */
638 XErrorHandler old_handler = XSetErrorHandler (BadWindow_ehandler);
639 /* I don't understand why this doesn't refocus on the old selected
640 window when MWM is running in click-to-type mode. The value of
641 `focus' seems to be correct. */
642 XSetInputFocus (dpy, focus, revert_to, CurrentTime);
644 XSetErrorHandler (old_handler);
647 /* Since we installed our colormap to display the dialog properly, put
648 the old one back, so that the screensaver_window is now displayed
650 for (i = 0; i < si->nscreens; i++)
652 saver_screen_info *ssi = &si->screens[i];
654 XInstallColormap (si->dpy, ssi->cmap);
657 return (passwd_state == pw_ok ? True : False);
661 unlock_p (saver_info *si)
663 static Bool initted = False;
667 #ifndef VERIFY_CALLBACK_WORKS
668 XtAppAddActions (si->app, actions, XtNumber (actions));
669 #endif /* !VERIFY_CALLBACK_WORKS */
675 make_passwd_dialog (si);
676 return pop_passwd_dialog (si);
679 #endif /* !NO_LOCKING -- whole file */