summaryrefslogtreecommitdiffstats
path: root/driver/dialog.c
diff options
context:
space:
mode:
Diffstat (limited to 'driver/dialog.c')
-rw-r--r--driver/dialog.c2507
1 files changed, 2507 insertions, 0 deletions
diff --git a/driver/dialog.c b/driver/dialog.c
new file mode 100644
index 0000000..fce74c4
--- /dev/null
+++ b/driver/dialog.c
@@ -0,0 +1,2507 @@
+/* dialog.c --- the password dialog and splash screen.
+ * xscreensaver, Copyright © 1993-2021 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ */
+
+
+/* This file renders the unlock dialog and splash screen, using Xlib and Xft.
+ * One significant complication is that it must read raw XInput2 events to
+ * get keyboard and mouse input, as the "xscreensaver" process has the mouse
+ * and keyboard grabbed while this is running.
+ *
+ * It might be possible to implement this file using Gtk instead of Xlib,
+ * but the grab situation might make that tricky: those events would have to
+ * be re-sent to the toolkit widgets in a way that it would understand them.
+ * Also, toolkits tend to assume that a window manager exists, and this
+ * window must be an OverrideRedirect window with no focus management.
+ *
+ * Crashes here are interpreted as "unauthorized" and do not unlock the
+ * screen.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdio.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <time.h>
+#include <sys/time.h>
+#include <pwd.h>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#ifdef HAVE_UNAME
+# include <sys/utsname.h>
+#endif /* HAVE_UNAME */
+#include <ctype.h>
+#include <pwd.h>
+
+#include <X11/Xproto.h> /* for CARD32 */
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/extensions/XInput2.h>
+#include <X11/Intrinsic.h>
+
+#ifdef ENABLE_NLS
+# include <locale.h>
+# include <libintl.h>
+# define _(S) gettext(S)
+#else
+# define _(S) (S)
+#endif
+
+#ifdef HAVE_XKB
+# include <X11/XKBlib.h>
+# include <X11/extensions/XKB.h>
+#endif
+
+#include "version.h"
+#include "blurb.h"
+#include "auth.h"
+#include "atoms.h"
+#include "screens.h"
+#include "xft.h"
+#include "xftwrap.h"
+#include "xinput.h"
+#include "resources.h"
+#include "visual.h"
+#include "font-retry.h"
+#include "prefs.h"
+#include "usleep.h"
+
+extern Bool debug_p;
+
+#undef DEBUG_METRICS
+#undef DEBUG_STACKING
+
+#define LOCK_FAILURE_ATOM "_XSCREENSAVER_AUTH_FAILURES"
+
+#undef MAX
+#undef MIN
+#define MAX(a,b) ((a)>(b)?(a):(b))
+#define MIN(a,b) ((a)<(b)?(a):(b))
+
+#define MAX_BYTES_PER_CHAR 8 /* UTF-8 uses up to 6 bytes */
+#define MAX_PASSWD_CHARS 280 /* Longest possible passphrase */
+
+typedef struct window_state window_state;
+
+
+typedef enum {
+ AUTH_READ, /* reading input or ready to do so */
+ AUTH_SUCCESS, /* auth success, unlock */
+ AUTH_FAIL, /* auth fail */
+ AUTH_CANCEL, /* user canceled, or typed blank password */
+ AUTH_TIME, /* timed out */
+ AUTH_FINISHED, /* user pressed enter */
+ AUTH_NOTIFY /* displaying message after finished */
+} auth_state;
+
+
+/* A mini-toolkit for rendering text labels, input fields, and buttons.
+ */
+typedef enum { CENTER, LEFT, RIGHT } line_align;
+
+typedef struct {
+ Bool down_p;
+ XRectangle rect;
+ char *cmd;
+ void (*fn) (window_state *ws);
+ Bool disabled_p;
+} line_button_state;
+
+
+typedef struct {
+ char *text;
+ XftFont *font;
+ XftColor fg;
+ Pixel bg;
+ enum { LABEL, BUTTON, TEXT, TEXT_RO } type;
+ line_align align;
+ Bool float_p;
+ Bool i_beam;
+ line_button_state *button;
+} dialog_line;
+
+
+/* Global state.
+ */
+struct window_state {
+ XtAppContext app;
+ Display *dpy;
+ Screen *screen;
+ Position cx, cy, x, y;
+ Dimension min_height;
+ Window window;
+ Colormap cmap;
+
+ Bool splash_p;
+ auth_state auth_state;
+ int xi_opcode;
+ int xkb_opcode;
+
+ /* Variant strings
+ */
+ char *version;
+ char *user;
+ int nmsgs;
+ const auth_message *msgs;
+
+ /* "Characters" in the password may be a variable number of bytes long.
+ plaintext_passwd contains the raw bytes.
+ plaintext_passwd_char_size indicates the size in bytes of each character,
+ so that we can make backspace work.
+ censored_passwd is the asterisk version.
+
+ Maybe it would be more sensible to use uint32_t and utils/utf8wc.c here,
+ but the multi-byte string returned by XLookupString might not be UTF-8
+ (see comment in handle_keypress).
+ */
+ char plaintext_passwd [MAX_PASSWD_CHARS * MAX_BYTES_PER_CHAR];
+ char censored_passwd [MAX_PASSWD_CHARS * MAX_BYTES_PER_CHAR];
+ char plaintext_passwd_char_size [MAX_PASSWD_CHARS];
+
+ XComposeStatus compose_status;
+
+ XtIntervalId timer;
+
+ XtIntervalId cursor_timer; /* Blink the I-beam */
+ int i_beam;
+
+ double start_time, end_time;
+
+ Bool show_stars_p; /* "I regret that I have but one asterisk for my country."
+ -- Nathan Hale, 1776. */
+ Bool caps_p; /* Whether we saw a keypress with caps-lock on */
+
+ char *dialog_theme;
+ char *heading_label;
+ char *body_label;
+ char *hostname_label;
+ char *date_format;
+ char *kbd_layout_label;
+ char *newlogin_cmd;
+
+ /* Resources for fonts and colors */
+ XftDraw *xftdraw;
+ XftFont *heading_font;
+ XftFont *body_font;
+ XftFont *error_font;
+ XftFont *label_font;
+ XftFont *date_font;
+ XftFont *button_font;
+ XftFont *hostname_font;
+
+ Pixel foreground;
+ Pixel background;
+ XftColor xft_foreground;
+ XftColor xft_text_foreground;
+ XftColor xft_button_foreground;
+ XftColor xft_error_foreground;
+ Pixel passwd_background;
+ Pixel thermo_foreground;
+ Pixel thermo_background;
+ Pixel shadow_top;
+ Pixel shadow_bottom;
+ Pixel border_color;
+ Pixel button_background;
+ Pixel logo_background;
+
+ Dimension preferred_logo_width;
+ Dimension preferred_logo_height;
+ Dimension thermo_width;
+ Dimension internal_padding;
+ Dimension shadow_width;
+ Dimension border_width;
+
+ Pixmap logo_pixmap;
+ Pixmap logo_clipmask;
+ unsigned int logo_width, logo_height;
+ int logo_npixels;
+ unsigned long *logo_pixels;
+
+ line_button_state newlogin_button_state;
+ line_button_state unlock_button_state;
+ line_button_state demo_button_state;
+ line_button_state help_button_state;
+};
+
+
+static void
+draw_shaded_rectangle (Display *dpy, Window window,
+ int x, int y,
+ int width, int height,
+ int thickness,
+ unsigned long top_color,
+ unsigned long bottom_color)
+{
+ XPoint points[4];
+ XGCValues gcv;
+ GC gc1, gc2;
+ if (thickness == 0) return;
+
+ gcv.foreground = top_color;
+ gc1 = XCreateGC (dpy, window, GCForeground, &gcv);
+ gcv.foreground = bottom_color;
+ gc2 = XCreateGC (dpy, window, GCForeground, &gcv);
+
+ points [0].x = x;
+ points [0].y = y;
+ points [1].x = x + width;
+ points [1].y = y;
+ points [2].x = x + width - thickness;
+ points [2].y = y + thickness;
+ points [3].x = x;
+ points [3].y = y + thickness;
+ XFillPolygon (dpy, window, gc1, points, 4, Convex, CoordModeOrigin);
+
+ points [0].x = x;
+ points [0].y = y + thickness;
+ points [1].x = x;
+ points [1].y = y + height;
+ points [2].x = x + thickness;
+ points [2].y = y + height - thickness;
+ points [3].x = x + thickness;
+ points [3].y = y + thickness;
+ XFillPolygon (dpy, window, gc1, points, 4, Convex, CoordModeOrigin);
+
+ points [0].x = x + width;
+ points [0].y = y;
+ points [1].x = x + width - thickness;
+ points [1].y = y + thickness;
+ points [2].x = x + width - thickness;
+ points [2].y = y + height - thickness;
+ points [3].x = x + width;
+ points [3].y = y + height - thickness;
+ XFillPolygon (dpy, window, gc2, points, 4, Convex, CoordModeOrigin);
+
+ points [0].x = x;
+ points [0].y = y + height;
+ points [1].x = x + width;
+ points [1].y = y + height;
+ points [2].x = x + width;
+ points [2].y = y + height - thickness;
+ points [3].x = x + thickness;
+ points [3].y = y + height - thickness;
+ XFillPolygon (dpy, window, gc2, points, 4, Convex, CoordModeOrigin);
+
+ XFreeGC (dpy, gc1);
+ XFreeGC (dpy, gc2);
+}
+
+#define IBEAM_WIDTH 2
+
+static void
+draw_i_beam (Display *dpy, Drawable d, Pixel color, int x, int y, int height)
+{
+ XGCValues gcv;
+ GC gc;
+ gcv.foreground = color;
+ gcv.line_width = IBEAM_WIDTH;
+ gc = XCreateGC (dpy, d, GCForeground | GCLineWidth, &gcv);
+ XDrawLine (dpy, d, gc, x, y, x, y + height); /* Ceci n'est pas une pipe */
+ XFreeGC (dpy, gc);
+}
+
+
+static int
+draw_dialog_line (window_state *ws, Drawable d, dialog_line *line,
+ int left, int right, int y, Bool clear_p)
+{
+ int w = right - left;
+ int h;
+ int xpad = 0, ypad = 0;
+ XGlyphInfo overall;
+ line_align align = line->align;
+ int oleft = left;
+ int tleft = left;
+ int oright = right;
+ int clip_w = 0;
+ int gutter = 0;
+ XRectangle rect;
+ int xoff2 = 0;
+ int yoff2 = 0;
+ char *text2 = 0;
+ char *text = line->text;
+ int nlines = 1;
+
+ /* Adjust left/right margins based on the type of the line.
+ */
+ switch (line->type) {
+ case LABEL:
+ if (line->float_p && line->align == LEFT)
+ {
+ /* Add 1px to leave a little padding between the top border of the
+ label and the ascenders. */
+ ypad = ws->shadow_width + 1;
+ right = left + w/2 - ws->shadow_width * 2 - line->font->ascent / 2;
+ align = RIGHT;
+ }
+
+ if (*line->text)
+ text = text2 = xft_word_wrap (ws->dpy, line->font, line->text,
+ right - left);
+ break;
+
+ case BUTTON: /* box is fixed width at 1/3, text centered */
+ align = CENTER;
+ xpad = 0;
+ /* make the buttons a little taller than everything else */
+ /* Add 1px as above */
+ ypad = ws->shadow_width + line->font->ascent / 2 + 1;
+ gutter = ws->shadow_width;
+ clear_p = True;
+
+ switch (line->align) {
+ case LEFT:
+ right = left + w/3 - xpad;
+ break;
+ case CENTER:
+ xpad = ws->shadow_width * 2;
+ left += w/3 + xpad;
+ right -= w/3 + xpad;
+ break;
+ case RIGHT:
+ left = right - w/3 + xpad;
+ break;
+ }
+ oright = right;
+ xpad = 0;
+ break;
+
+ case TEXT: /* box is fixed width at 1/2, text left */
+ case TEXT_RO:
+ align = LEFT;
+ oleft = left + xoff2;
+ clear_p = True;
+ xpad = ws->shadow_width + line->font->ascent / 4;
+ /* Add 1px as above */
+ ypad = ws->shadow_width + 1;
+ gutter = ws->shadow_width;
+ if (gutter < 2) gutter = 2;
+
+ switch (line->align) {
+ case LEFT:
+ right = left + w/2;
+ break;
+ case RIGHT:
+ left = right - w/2;
+ break;
+ case CENTER:
+ abort();
+ break;
+ }
+
+ /* If the text is longer than the field, scroll horizontally to show
+ the end of the text instead of the beginning.
+ */
+ XftTextExtentsUtf8_multi (ws->dpy, line->font, (FcChar8 *) text,
+ strlen(text), &overall);
+ if (overall.width >= w/2 - ws->shadow_width * 2 - IBEAM_WIDTH)
+ {
+ align = RIGHT;
+ left = right - w/2;
+ }
+ break;
+
+ default: abort(); break;
+ }
+
+ /* Clear out the area we're about to overwrite.
+ */
+ h = nlines * (line->font->ascent + line->font->descent) + ypad*2;
+ if (clear_p)
+ {
+ GC gc;
+ XGCValues gcv;
+ gcv.foreground = line->bg;
+ gc = XCreateGC (ws->dpy, d, GCForeground, &gcv);
+ XFillRectangle (ws->dpy, d, gc, left, y, oright-left, h);
+ XFreeGC (ws->dpy, gc);
+ }
+
+ /* Draw borders if necessary.
+ */
+ switch (line->type) {
+ case LABEL: break;
+ case BUTTON: case TEXT: case TEXT_RO:
+ {
+ Bool in_p = (line->type != BUTTON);
+ if (line->button)
+ {
+ line->button->rect.x = left;
+ line->button->rect.y = y;
+ line->button->rect.width = right-left;
+ line->button->rect.height = h;
+ in_p = line->button->down_p || line->button->disabled_p;
+ }
+ tleft = left;
+ draw_shaded_rectangle (ws->dpy, d,
+ left, y, right-left, h,
+ ws->shadow_width,
+ (in_p ? ws->shadow_bottom : ws->shadow_top),
+ (in_p ? ws->shadow_top : ws->shadow_bottom));
+ clip_w = ws->shadow_width;
+ }
+ break;
+ default: abort(); break;
+ }
+
+ /* Draw the text inside our box.
+ */
+ nlines = XftTextExtentsUtf8_multi (ws->dpy, line->font, (FcChar8 *) text,
+ strlen(text), &overall);
+ w = overall.width - overall.x;
+ switch (align) {
+ case LEFT: left = left + xpad; break;
+ case RIGHT: left = right - w - xpad; break;
+ case CENTER:
+ oleft = left;
+ left = left + xpad + (right - left - w) / 2;
+ if (left < oleft) left = oleft;
+ break;
+ default: abort(); break;
+ }
+
+ rect.x = MAX (oleft, MAX (left, tleft + clip_w));
+ rect.width = MIN (oright, right) - rect.x - clip_w;
+ rect.y = y + ypad - overall.y + line->font->ascent;
+ rect.height = overall.height;
+
+ XftDrawSetClipRectangles (ws->xftdraw, 0, 0, &rect, 1);
+
+ if (line->type == BUTTON &&
+ line->button &&
+ (line->button->down_p || line->button->disabled_p))
+ xoff2 = yoff2 = MIN (ws->shadow_width, line->font->ascent/2);
+
+ XftDrawStringUtf8_multi (ws->xftdraw, &line->fg, line->font,
+ left + xoff2,
+ y + ypad + yoff2 + line->font->ascent,
+ (FcChar8 *) text, strlen (text),
+ (align == LEFT ? 1 : align == CENTER ? 0 : -1));
+# ifdef DEBUG_METRICS
+ {
+ GC gc;
+ XGCValues gcv;
+ int yy = y + ypad + yoff2 + line->font->ascent;
+ gcv.foreground = line->fg.pixel;
+ gc = XCreateGC (ws->dpy, d, GCForeground, &gcv);
+ /* draw a line on the baseline of the text */
+ XDrawLine (ws->dpy, d, gc, 0, yy, right, yy);
+ yy -= line->font->ascent;
+ /* a line above the ascenders */
+ XDrawLine (ws->dpy, d, gc, left, yy, right, yy);
+ yy += line->font->ascent + line->font->descent;
+ /* and below the descenders */
+ XDrawLine (ws->dpy, d, gc, left, yy, right, yy);
+ XFreeGC (ws->dpy, gc);
+ }
+# endif
+
+ if (line->i_beam)
+ draw_i_beam (ws->dpy, d,
+ ws->foreground,
+ left + xoff2 + overall.width,
+ y + ypad + yoff2,
+ line->font->ascent + line->font->descent);
+
+ XftDrawSetClip (ws->xftdraw, 0);
+
+ if (text2) free (text2);
+
+ y += ypad*2 + (nlines * (line->font->ascent + line->font->descent)) + gutter;
+ return y;
+}
+
+
+static int
+draw_dialog_lines (window_state *sp, Drawable d, dialog_line *lines,
+ int left, int right, int top)
+{
+ int i;
+ int maxy = 0;
+ for (i = 0; lines[i].text; i++)
+ {
+ Bool clear_p = (i > 0 && lines[i-1].float_p ? False : True);
+ int y = draw_dialog_line (sp, d, &lines[i], left, right, top, clear_p);
+ if (y > maxy) maxy = y;
+ if (! lines[i].float_p)
+ top = maxy;
+ }
+ return top;
+}
+
+
+static pid_t
+fork_and_exec (Display *dpy, int argc, char **argv)
+{
+ char buf [255];
+ pid_t forked = fork();
+ switch ((int) forked) {
+ case -1:
+ sprintf (buf, "%s: couldn't fork", blurb());
+ perror (buf);
+ break;
+
+ case 0:
+ close (ConnectionNumber (dpy)); /* close display fd */
+ execvp (argv[0], argv); /* shouldn't return. */
+
+ sprintf (buf, "%s: pid %lu: couldn't exec %s", blurb(),
+ (unsigned long) getpid(), argv[0]);
+ perror (buf);
+ exit (1); /* exits child fork */
+ break;
+
+ default: /* parent fork */
+ if (verbose_p)
+ {
+ int i;
+ fprintf (stderr, "%s: pid %lu: launched",
+ blurb(), (unsigned long) forked);
+ for (i = 0; i < argc; i++)
+ fprintf (stderr, " %s", argv[i]);
+ fprintf (stderr, "\n");
+ }
+ break;
+ }
+
+ return forked;
+}
+
+
+/* Loading resources
+ */
+static void
+resource_keys (window_state *ws, const char **name, const char **rclass)
+{
+ const char *theme = ws->dialog_theme;
+ const char *name2 = (ws->splash_p ? "splash" : "passwd");
+ const char *class2 = "Dialog";
+ static char res[200], rclass2[200];
+ char *s;
+
+ /* First try $THEME."Dialog.value" */
+ sprintf (res, "%s.%s.%s", theme, name2, *name);
+ sprintf (rclass2, "%s.%s.%s", theme, class2, *rclass);
+ s = get_string_resource (ws->dpy, res, rclass2);
+ if (s && *s) goto DONE;
+
+ /* Next try "default.Dialog.value" */
+ if (s) free (s);
+ theme = "default";
+ sprintf (res, "%s.%s.%s", theme, name2, *name);
+ sprintf (rclass2, "%s.%s.%s", theme, class2, *rclass);
+ s = get_string_resource (ws->dpy, res, rclass2);
+ if (s && *s) goto DONE;
+
+ /* Next try "Dialog.value" */
+ if (s) free (s);
+ sprintf (res, "%s.%s", theme, *name);
+ sprintf (rclass2, "%s.%s", theme, *rclass);
+ s = get_string_resource (ws->dpy, res, rclass2);
+ if (s && *s) goto DONE;
+
+ DONE:
+ *name = res;
+ *rclass = rclass2;
+ if (s) free (s);
+}
+
+
+static char *
+get_str (window_state *ws, const char *name, const char *rclass)
+{
+ resource_keys (ws, &name, &rclass);
+ return get_string_resource (ws->dpy, (char *) name, (char *) rclass);
+}
+
+
+static XftFont *
+get_font (window_state *ws, const char *name)
+{
+ const char *rclass = "Font";
+ XftFont *f;
+ char *s;
+ resource_keys (ws, &name, &rclass);
+ s = get_string_resource (ws->dpy, (char *) name, (char *) rclass);
+ if (!s || !*s)
+ s = "sans-serif 14";
+ f = load_xft_font_retry (ws->dpy, DefaultScreen(ws->dpy), s);
+ if (!f) abort();
+ return f;
+}
+
+static unsigned long
+get_color (window_state *ws, const char *name, const char *rclass)
+{
+ resource_keys (ws, &name, &rclass);
+ return get_pixel_resource (ws->dpy, DefaultColormapOfScreen (ws->screen),
+ (char *) name, (char *) rclass);
+}
+
+static void
+get_xft_color (window_state *ws, XftColor *ret,
+ const char *name, const char *rclass)
+{
+ char *s;
+ resource_keys (ws, &name, &rclass);
+ s = get_string_resource (ws->dpy, (char *) name, (char *) rclass);
+ if (!s || !*s) s = "black";
+ XftColorAllocName (ws->dpy,
+ DefaultVisualOfScreen(ws->screen),
+ DefaultColormapOfScreen (ws->screen),
+ s, ret);
+}
+
+static int
+get_int (window_state *ws, const char *name, const char *rclass)
+{
+ resource_keys (ws, &name, &rclass);
+ return get_integer_resource (ws->dpy, (char *) name, (char *) rclass);
+}
+
+
+/* Decide where on the X11 screen to place the dialog.
+ This is complicated because, in the face of RANDR and Xinerama, we want
+ to center it on a *monitor*, not on what X calls a 'Screen'. So get the
+ monitor state, then figure out which one of those the mouse is in.
+ */
+static void
+splash_pick_window_position (Display *dpy, Position *xP, Position *yP)
+{
+ Window pointer_root, pointer_child;
+ int root_x = 0, root_y = 0, win_x, win_y;
+ unsigned int mask;
+ monitor **monitors;
+ monitor *m = 0;
+ int i;
+
+ XQueryPointer (dpy, RootWindow (dpy, 0),
+ &pointer_root, &pointer_child,
+ &root_x, &root_y, &win_x, &win_y, &mask);
+
+ monitors = scan_monitors (dpy);
+ if (!monitors || !*monitors) abort();
+
+ for (i = 0; monitors[i]; i++)
+ {
+ monitor *m0 = monitors[i];
+ if (m0->sanity == S_SANE &&
+ root_x >= m0->x &&
+ root_y >= m0->y &&
+ root_x < m0->x + m0->width &&
+ root_y < m0->y + m0->height)
+ {
+ m = m0;
+ break;
+ }
+ }
+
+ if (!m)
+ {
+ if (verbose_p)
+ fprintf (stderr, "%s: mouse is not on any monitor?\n", blurb());
+ m = monitors[0];
+ }
+ else if (verbose_p)
+ fprintf (stderr,
+ "%s: mouse is at %d,%d on monitor %d %dx%d+%d+%d \"%s\"\n",
+ blurb(), root_x, root_y, m->id,
+ m->width, m->height, m->x, m->y,
+ (m->desc ? m->desc : ""));
+
+ *xP = m->x + m->width/2;
+ *yP = m->y + m->height/2;
+
+ free_monitors (monitors);
+}
+
+
+static void unlock_cb (window_state *ws);
+
+
+/* This program only needs one option from the init file, so it
+ just reads the .ad file and the .xscreensaver file directly rather
+ than going through Xt and Xrm.
+ */
+static void init_line_handler (int lineno,
+ const char *key, const char *val,
+ void *closure)
+{
+ window_state *ws = (window_state *) closure;
+ if (val && *val && !strcmp (key, "dialogTheme"))
+ {
+ if (ws->dialog_theme) free (ws->dialog_theme);
+ ws->dialog_theme = strdup (val);
+ }
+}
+
+static void
+read_init_file_simple (window_state *ws)
+{
+ const char *home = getenv("HOME");
+ const char *fn1 = AD_DIR "/XScreenSaver";
+ char *fn2;
+ if (!home || !*home) return;
+ fn2 = (char *) malloc (strlen(home) + 40);
+ sprintf (fn2, "%s/.xscreensaver", home);
+
+ if (debug_p)
+ fprintf (stderr, "%s: reading %s\n", blurb(), fn1);
+ parse_init_file (fn1, init_line_handler, ws);
+
+ if (debug_p)
+ fprintf (stderr, "%s: reading %s\n", blurb(), fn2);
+ parse_init_file (fn2, init_line_handler, ws);
+
+ if (verbose_p)
+ fprintf (stderr, "%s: theme: %s\n", blurb(),
+ (ws->dialog_theme ? ws->dialog_theme : "none"));
+}
+
+
+static void
+grab_keyboard_and_mouse (window_state *ws)
+{
+ /* If we have been launched by xscreensaver, these grabs won't succeed,
+ and that is expected. But if we are being run manually for debugging,
+ they are necessary to avoid having events seen by two apps at once.
+ (We don't bother to ungrab, that happens when we exit.)
+ */
+ Display *dpy = ws->dpy;
+ Window root = RootWindowOfScreen (ws->screen);
+ XGrabKeyboard (dpy, root, True, GrabModeAsync, GrabModeAsync, CurrentTime);
+ XGrabPointer (dpy, root, True,
+ (ButtonPressMask | ButtonReleaseMask |
+ EnterWindowMask | LeaveWindowMask |
+ PointerMotionMask | PointerMotionHintMask |
+ Button1MotionMask | Button2MotionMask |
+ Button3MotionMask | Button4MotionMask |
+ Button5MotionMask | ButtonMotionMask),
+ GrabModeAsync, GrabModeAsync, root,
+ None, CurrentTime);
+}
+
+
+static void
+get_keyboard_layout (window_state *ws)
+{
+# ifdef HAVE_XKB
+ XkbStateRec state;
+ XkbDescPtr desc = 0;
+ Atom name = 0;
+ char *namestr = 0;
+
+ if (! ws->xkb_opcode)
+ {
+ if (! XkbQueryExtension (ws->dpy, 0, &ws->xkb_opcode, 0, 0, 0))
+ {
+ ws->xkb_opcode = -1; /* Only try once */
+ if (verbose_p)
+ fprintf (stderr, "%s: XkbQueryExtension failed\n", blurb());
+ return;
+ }
+
+ if (! XkbSelectEvents (ws->dpy, XkbUseCoreKbd,
+ XkbMapNotifyMask | XkbStateNotifyMask,
+ XkbMapNotifyMask | XkbStateNotifyMask))
+ {
+ if (verbose_p)
+ fprintf (stderr, "%s: XkbSelectEvents failed\n", blurb());
+ }
+ }
+
+ if (XkbGetState (ws->dpy, XkbUseCoreKbd, &state))
+ {
+ if (verbose_p)
+ fprintf (stderr, "%s: XkbGetState failed\n", blurb());
+ return;
+ }
+ desc = XkbGetKeyboard (ws->dpy, XkbAllComponentsMask, XkbUseCoreKbd);
+ if (!desc || !desc->names)
+ {
+ if (verbose_p)
+ fprintf (stderr, "%s: XkbGetKeyboard failed\n", blurb());
+ goto DONE;
+ }
+ name = desc->names->groups[state.group];
+ namestr = (name ? XGetAtomName (ws->dpy, name) : 0);
+ if (!namestr)
+ {
+ if (verbose_p)
+ fprintf (stderr, "%s: XkbGetKeyboard returned null layout\n", blurb());
+ goto DONE;
+ }
+
+ if (ws->kbd_layout_label)
+ free (ws->kbd_layout_label);
+ ws->kbd_layout_label = namestr;
+
+ if (verbose_p)
+ fprintf (stderr, "%s: kbd layout: %s\n", blurb(),
+ namestr ? namestr : "null");
+
+ DONE:
+ if (desc) XFree (desc);
+# endif /* HAVE_XKB */
+}
+
+
+static double
+double_time (void)
+{
+ struct timeval now;
+# ifdef GETTIMEOFDAY_TWO_ARGS
+ struct timezone tzp;
+ gettimeofday(&now, &tzp);
+# else
+ gettimeofday(&now);
+# endif
+
+ return (now.tv_sec + ((double) now.tv_usec * 0.000001));
+}
+
+
+static void
+create_window (window_state *ws, int w, int h)
+{
+ XSetWindowAttributes attrs;
+ unsigned long attrmask;
+ Window ow = ws->window;
+
+ attrmask = CWOverrideRedirect | CWEventMask;
+ attrs.override_redirect = True;
+ attrs.event_mask = ExposureMask | VisibilityChangeMask;
+ ws->window = XCreateWindow (ws->dpy,
+ RootWindowOfScreen(ws->screen),
+ ws->x, ws->y, w, h, 0,
+ DefaultDepthOfScreen (ws->screen),
+ InputOutput,
+ DefaultVisualOfScreen(ws->screen),
+ attrmask, &attrs);
+ XSetWindowBackground (ws->dpy, ws->window, ws->background);
+ XSetWindowColormap (ws->dpy, ws->window, ws->cmap);
+ xscreensaver_set_wm_atoms (ws->dpy, ws->window, w, h, 0);
+
+ if (ow)
+ {
+ XMapRaised (ws->dpy, ws->window);
+ XDestroyWindow (ws->dpy, ow);
+ }
+}
+
+
+/* Loads resources and creates and returns the global window state.
+ */
+static window_state *
+window_init (Widget root_widget, Bool splash_p)
+{
+ Display *dpy = XtDisplay (root_widget);
+ Screen *screen = XtScreen (root_widget);
+ window_state *ws;
+ Bool resource_error_p = False;
+
+ ws = (window_state *) calloc (1, sizeof(*ws));
+ if (!ws) abort();
+
+ ws->splash_p = splash_p;
+ ws->dpy = dpy;
+ ws->screen = screen;
+ ws->app = XtWidgetToApplicationContext (root_widget);
+
+ /* Read default theme from resources before the init file. */
+ ws->dialog_theme =
+ get_string_resource (ws->dpy, "dialogTheme", "DialogTheme");
+ if (!ws->dialog_theme || !*ws->dialog_theme)
+ ws->dialog_theme = strdup ("default");
+
+ /* Read theme from init file before any other resources. */
+ read_init_file_simple (ws);
+
+ {
+ struct passwd *p = getpwuid (getuid());
+ if (!p || !p->pw_name || !*p->pw_name) abort();
+ ws->user = p->pw_name;
+ }
+
+ ws->cmap = XCreateColormap (dpy, RootWindowOfScreen (screen), /* Old skool */
+ DefaultVisualOfScreen (screen),
+ AllocNone);
+
+ ws->newlogin_cmd = get_str (ws, "newLoginCommand", "NewLoginCommand");
+ ws->date_format = get_str (ws, "dateFormat", "DateFormat");
+ ws->show_stars_p =
+ get_boolean_resource (ws->dpy, "passwd.asterisks", "Passwd.Boolean");
+
+ /* Put the version number in the label. */
+ {
+ char *version = strdup (screensaver_id + 17);
+ char *year = strchr (version, '-');
+ char *s = strchr (version, ' ');
+ *s = 0;
+ year = strchr (year+1, '-') + 1;
+ s = strchr (year, ')');
+ *s = 0;
+ ws->heading_label = (char *) malloc (100);
+ ws->version = strdup(version);
+ sprintf (ws->heading_label, "XScreenSaver %.4s, v%.10s", year, version);
+
+ if (splash_p)
+ {
+ ws->body_label = (char *) malloc (100);
+ sprintf (ws->body_label,
+ _("Copyright \xC2\xA9 1991-%.4s by\nJamie Zawinski <jwz@jwz.org>"),
+ year);
+ }
+ }
+
+ ws->heading_font = get_font (ws, "headingFont");
+ ws->button_font = get_font (ws, "buttonFont");
+ ws->body_font = get_font (ws, "bodyFont");
+ ws->error_font = get_font (ws, "errorFont");
+ ws->label_font = get_font (ws, "labelFont");
+ ws->date_font = get_font (ws, "dateFont");
+ ws->hostname_font = get_font (ws, "unameFont");
+
+ ws->foreground = get_color (ws, "foreground", "Foreground");
+ ws->background = get_color (ws, "background", "Background");
+
+ get_xft_color (ws, &ws->xft_foreground, "foreground", "Foreground");
+ get_xft_color (ws, &ws->xft_text_foreground,
+ "text.foreground", "Text.Foreground");
+ get_xft_color (ws, &ws->xft_error_foreground,
+ "error.foreground", "Error.Foreground");
+ get_xft_color (ws, &ws->xft_button_foreground,
+ "button.foreground", "Button.Foreground");
+
+ ws->shadow_top = get_color (ws, "topShadowColor", "Foreground");
+ ws->shadow_bottom = get_color (ws, "bottomShadowColor", "Background");
+ ws->border_color = get_color (ws, "borderColor", "BorderColor");
+ ws->passwd_background = get_color (ws, "text.background", "Text.Background");
+ ws->button_background =
+ get_color (ws, "button.background", "Button.Background");
+ ws->thermo_foreground =
+ get_color (ws, "thermometer.foreground", "Thermometer.Foreground");
+ ws->thermo_background =
+ get_color ( ws, "thermometer.background", "Thermometer.Background");
+ ws->logo_background = get_color ( ws, "logo.background", "Logo.Background");
+
+ if (resource_error_p)
+ {
+ /* Make sure the error messages show up. */
+ ws->foreground = BlackPixelOfScreen (screen);
+ ws->background = WhitePixelOfScreen (screen);
+ }
+
+ ws->preferred_logo_width = get_int (ws, "logo.width", "Logo.Width");
+ ws->preferred_logo_height = get_int (ws, "logo.height", "Logo.Height");
+ ws->thermo_width = get_int (ws, "thermometer.width", "Thermometer.Width");
+ ws->shadow_width = get_int (ws, "shadowWidth", "ShadowWidth");
+ ws->border_width = get_int (ws, "borderWidth", "BorderWidth");
+ ws->internal_padding =
+ get_int (ws, "internalPadding", "InternalPadding");
+
+ if (ws->preferred_logo_width == 0) ws->preferred_logo_width = 150;
+ if (ws->preferred_logo_height == 0) ws->preferred_logo_height = 150;
+ if (ws->internal_padding == 0) ws->internal_padding = 15;
+ if (ws->thermo_width == 0) ws->thermo_width = ws->shadow_width;
+
+ if (ws->splash_p) ws->thermo_width = 0;
+
+# ifdef HAVE_UNAME
+ if (!splash_p &&
+ get_boolean_resource (ws->dpy, "passwd.uname", "Passwd.Boolean"))
+ {
+ struct utsname uts;
+ if (!uname (&uts) && *uts.nodename)
+ ws->hostname_label = strdup (uts.nodename);
+ }
+# endif
+
+ get_keyboard_layout (ws);
+
+ /* Load the logo pixmap, based on font size */
+ {
+ int x, y;
+ unsigned int bw, d;
+ Window root = RootWindowOfScreen(ws->screen);
+ Visual *visual = DefaultVisualOfScreen (ws->screen);
+ int logo_size = (ws->heading_font->ascent > 24 ? 2 : 1);
+ ws->logo_pixmap = xscreensaver_logo (screen, visual, root, ws->cmap,
+ ws->background,
+ &ws->logo_pixels, &ws->logo_npixels,
+ &ws->logo_clipmask, logo_size);
+ if (!ws->logo_pixmap) abort();
+ XGetGeometry (dpy, ws->logo_pixmap, &root, &x, &y,
+ &ws->logo_width, &ws->logo_height, &bw, &d);
+ }
+
+ splash_pick_window_position (ws->dpy, &ws->cx, &ws->cy);
+
+ ws->x = ws->y = 0;
+ create_window (ws, 1, 1);
+
+ /* Select SubstructureNotifyMask on the root window so that we know
+ when another process has mapped a window, so that we can make our
+ window always be on top. */
+ {
+ Window root = RootWindowOfScreen (ws->screen);
+ XWindowAttributes xgwa;
+ XGetWindowAttributes (ws->dpy, root, &xgwa);
+ XSelectInput (ws->dpy, root,
+ xgwa.your_event_mask | SubstructureNotifyMask);
+ }
+
+
+ ws->newlogin_button_state.cmd = ws->newlogin_cmd;
+ ws->demo_button_state.cmd =
+ get_string_resource (ws->dpy, "demoCommand", "Command");
+ {
+ char *load = get_string_resource (ws->dpy, "loadURL", "Command");
+ char *url = get_string_resource (ws->dpy, "helpURL", "URL");
+ if (load && *load && url && *url)
+ {
+ char *cmd = (char *) malloc (strlen(load) + (strlen(url) * 5) + 10);
+ sprintf (cmd, load, url, url, url, url, url);
+ ws->help_button_state.cmd = cmd;
+ }
+ }
+
+ ws->unlock_button_state.fn = unlock_cb;
+
+ grab_keyboard_and_mouse (ws);
+
+ return ws;
+}
+
+
+#ifdef DEBUG_STACKING
+static void
+describe_window (Display *dpy, Window w)
+{
+ XClassHint ch;
+ char *name = 0;
+ if (XGetClassHint (dpy, w, &ch))
+ {
+ fprintf (stderr, "0x%lx \"%s\", \"%s\"\n", (unsigned long) w,
+ ch.res_class, ch.res_name);
+ XFree (ch.res_class);
+ XFree (ch.res_name);
+ }
+ else if (XFetchName (dpy, w, &name) && name)
+ {
+ fprintf (stderr, "0x%lx \"%s\"\n", (unsigned long) w, name);
+ XFree (name);
+ }
+ else
+ {
+ fprintf (stderr, "0x%lx (untitled)\n", (unsigned long) w);
+ }
+}
+#endif /* DEBUG_STACKING */
+
+
+/* Returns true if some other window is on top of this one.
+ */
+static Bool
+window_occluded_p (Display *dpy, Window window)
+{
+ int screen;
+
+# ifdef DEBUG_STACKING
+ fprintf (stderr, "\n");
+# endif
+
+ for (screen = 0; screen < ScreenCount (dpy); screen++)
+ {
+ int i;
+ Window root = RootWindow (dpy, screen);
+ Window root2 = 0, parent = 0, *kids = 0;
+ unsigned int nkids = 0;
+ Bool saw_our_window_p = False;
+ Bool saw_later_window_p = False;
+ if (! XQueryTree (dpy, root, &root2, &parent, &kids, &nkids))
+ {
+# ifdef DEBUG_STACKING
+ fprintf (stderr, "%s: XQueryTree failed\n", blurb());
+# endif
+ continue;
+ }
+
+ for (i = 0; i < nkids; i++)
+ {
+ if (kids[i] == window)
+ {
+ saw_our_window_p = True;
+# ifdef DEBUG_STACKING
+ fprintf (stderr, "%s: our window: ", blurb());
+ describe_window (dpy, kids[i]);
+# endif
+ }
+ else if (saw_our_window_p)
+ {
+ saw_later_window_p = True;
+# ifdef DEBUG_STACKING
+ fprintf (stderr, "%s: higher window: ", blurb());
+ describe_window (dpy, kids[i]);
+# endif
+ break;
+ }
+ else
+ {
+# ifdef DEBUG_STACKING
+ fprintf (stderr, "%s: lower window: ", blurb());
+ describe_window (dpy, kids[i]);
+# endif
+ }
+ }
+
+ if (kids)
+ XFree ((char *) kids);
+
+ if (saw_later_window_p)
+ return True;
+ else if (saw_our_window_p)
+ return False;
+ /* else our window is not on this screen; keep going, try the next. */
+ }
+
+ /* Window doesn't exist? */
+# ifdef DEBUG_STACKING
+ fprintf (stderr, "%s: our window isn't on the screen\n", blurb());
+# endif
+ return False;
+}
+
+
+/* Strip leading and trailing whitespace. */
+static char *
+trim (const char *s)
+{
+ char *s2;
+ int L;
+ if (!s) return 0;
+ while (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n')
+ s++;
+ s2 = strdup (s);
+ L = strlen (s2);
+ while (L > 0 &&
+ (s2[L-1] == ' ' || s2[L-1] == '\t' ||
+ s2[L-1] == '\r' || s2[L-1] == '\n'))
+ s2[--L] = 0;
+ return s2;
+}
+
+
+/* Repaint the entire window.
+ */
+static void
+window_draw (window_state *ws)
+{
+ Display *dpy = ws->dpy;
+ Screen *screen = DefaultScreenOfDisplay (dpy);
+ Window root = RootWindowOfScreen (screen);
+ Visual *visual = DefaultVisualOfScreen(screen);
+ int depth = DefaultDepthOfScreen (screen);
+ XWindowAttributes xgwa;
+
+# define MIN_COLUMNS 22 /* Set window width based on headingFont ascent. */
+
+ int ext_border = (ws->internal_padding / 2 +
+ ws->shadow_width + ws->border_width);
+
+ Pixmap dbuf;
+ unsigned int logo_frame_width, logo_frame_height;
+ unsigned int window_width, window_height;
+ unsigned int text_left, text_right;
+ unsigned int thermo_x;
+ unsigned int x, y;
+ GC gc;
+ XGCValues gcv;
+ char date_text[100];
+ time_t now = time ((time_t *) 0);
+ struct tm *tm = localtime (&now);
+ double ratio = 1 - ((double_time() - ws->start_time) /
+ (ws->end_time - ws->start_time));
+ dialog_line *lines =
+ (dialog_line *) calloc (ws->nmsgs + 40, sizeof(*lines));
+ Bool emitted_user_p = False;
+ int i = 0, j;
+
+ XGetWindowAttributes (ws->dpy, ws->window, &xgwa);
+
+ if (!lines) abort();
+
+ strftime (date_text, sizeof(date_text)-2, ws->date_format, tm);
+
+ logo_frame_width = (ws->logo_width + ws->internal_padding * 2 +
+ ws->shadow_width * 2);
+ logo_frame_height = logo_frame_width;
+ if (logo_frame_width < ws->preferred_logo_width)
+ logo_frame_width = ws->preferred_logo_width;
+ if (logo_frame_height < ws->preferred_logo_height)
+ logo_frame_height = ws->preferred_logo_height;
+
+ thermo_x = ext_border * 1.5 + logo_frame_width + ws->shadow_width;
+ text_left = (thermo_x + ws->internal_padding +
+ (ws->thermo_width
+ ? ws->thermo_width + ws->shadow_width * 3
+ : 0));
+ text_right = text_left + ws->heading_font->ascent * MIN_COLUMNS;
+ window_width = text_right + ws->internal_padding + ext_border;
+ window_height = window_width * 3; /* reduced later */
+
+ dbuf = XCreatePixmap (dpy, root, window_width, window_height, depth);
+ gc = XCreateGC (dpy, dbuf, 0, &gcv);
+ XSetForeground (dpy, gc, ws->background);
+ XFillRectangle (dpy, dbuf, gc, 0, 0, window_width, window_height);
+
+ if (ws->xftdraw)
+ XftDrawDestroy (ws->xftdraw);
+ ws->xftdraw = XftDrawCreate (dpy, dbuf, visual, xgwa.colormap);
+
+ lines[i].text = ws->heading_label; /* XScreenSaver */
+ lines[i].font = ws->heading_font;
+ lines[i].fg = ws->xft_foreground;
+ lines[i].bg = ws->background;
+ lines[i].type = LABEL;
+ lines[i].align = CENTER;
+ i++;
+
+ if (time ((time_t *) 0) - XSCREENSAVER_RELEASED > 60*60*24*30*17)
+ {
+ lines[i].text = _("Update available!\nThis version is very old.\n");
+ lines[i].font = ws->error_font;
+ lines[i].fg = ws->xft_error_foreground;
+ lines[i].bg = ws->background;
+ lines[i].type = LABEL;
+ lines[i].align = CENTER;
+ i++;
+ }
+ else if (strstr (ws->version, "a") ||
+ strstr (ws->version, "b"))
+ {
+ lines[i].text = _("PRE-RELEASE VERSION");
+ lines[i].font = ws->error_font;
+ lines[i].fg = ws->xft_error_foreground;
+ lines[i].bg = ws->background;
+ lines[i].type = LABEL;
+ lines[i].align = CENTER;
+ i++;
+ }
+
+ if (ws->hostname_label && *ws->hostname_label)
+ {
+ lines[i].text = ws->hostname_label;
+ lines[i].font = ws->hostname_font;
+ lines[i].fg = ws->xft_foreground;
+ lines[i].bg = ws->background;
+ lines[i].type = LABEL;
+ lines[i].align = CENTER;
+ i++;
+ }
+
+# define BLANK_LINE \
+ lines[i].text = ""; \
+ lines[i].font = ws->body_font; \
+ lines[i].fg = ws->xft_foreground; \
+ lines[i].bg = ws->background; \
+ lines[i].type = LABEL; \
+ lines[i].align = CENTER; \
+ i++
+
+ BLANK_LINE;
+
+ if (debug_p && !ws->splash_p)
+ {
+ lines[i].text =
+ _("DEBUG MODE:\nAll keystrokes are being logged to stderr.\n");
+ lines[i].font = ws->error_font;
+ lines[i].fg = ws->xft_error_foreground;
+ lines[i].bg = ws->background;
+ lines[i].type = LABEL;
+ lines[i].align = CENTER;
+ i++;
+ }
+
+ if (ws->body_label && *ws->body_label)
+ {
+ lines[i].text = ws->body_label; /* Copyright or error message */
+ lines[i].font = ws->body_font;
+ lines[i].fg = ws->xft_foreground;
+ lines[i].bg = ws->background;
+ lines[i].type = LABEL;
+ lines[i].align = CENTER;
+ i++;
+
+ BLANK_LINE;
+ }
+
+ for (j = 0; j < ws->nmsgs; j++) /* PAM msgs */
+ {
+ switch (ws->msgs[j].type) {
+ case AUTH_MSGTYPE_INFO:
+ case AUTH_MSGTYPE_ERROR:
+ lines[i].text = trim (ws->msgs[j].msg);
+ lines[i].font = (ws->msgs[j].type == AUTH_MSGTYPE_ERROR
+ ? ws->error_font
+ : ws->body_font);
+ lines[i].fg = (ws->msgs[j].type == AUTH_MSGTYPE_ERROR
+ ? ws->xft_error_foreground
+ : ws->xft_foreground);
+ lines[i].bg = ws->background;
+ lines[i].type = LABEL;
+ lines[i].align = CENTER;
+ i++;
+ break;
+
+ case AUTH_MSGTYPE_PROMPT_NOECHO:
+ case AUTH_MSGTYPE_PROMPT_ECHO:
+
+ /* Show the logged in user before the first password field. */
+ if (!emitted_user_p)
+ {
+ lines[i].text = _("Username:");
+ lines[i].font = ws->label_font;
+ lines[i].fg = ws->xft_foreground;
+ lines[i].bg = ws->background;
+ lines[i].type = LABEL;
+ lines[i].align = LEFT;
+ lines[i].float_p = True;
+ i++;
+
+ lines[i].text = ws->user; /* $USER */
+ lines[i].font = ws->label_font;
+ lines[i].fg = ws->xft_text_foreground;
+ lines[i].bg = ws->passwd_background;
+ lines[i].type = TEXT_RO;
+ lines[i].align = RIGHT;
+ i++;
+ }
+
+ lines[i].text = trim (ws->msgs[j].msg); /* PAM prompt text */
+ lines[i].font = ws->label_font;
+ lines[i].fg = ws->xft_foreground;
+ lines[i].bg = ws->background;
+ lines[i].type = LABEL;
+ lines[i].align = LEFT;
+ lines[i].float_p = True;
+ i++;
+
+ lines[i].text = (ws->auth_state == AUTH_FINISHED
+ ? _("Checking...") :
+ ws->msgs[j].type == AUTH_MSGTYPE_PROMPT_ECHO
+ ? ws->plaintext_passwd /* Hopefully UTF-8 */
+ : ws->show_stars_p
+ ? ws->censored_passwd
+ : "");
+ lines[i].font = ws->label_font;
+ lines[i].fg = ws->xft_text_foreground;
+ lines[i].bg = ws->passwd_background;
+ lines[i].type = TEXT;
+ lines[i].align = RIGHT;
+ lines[i].i_beam = (ws->i_beam && ws->auth_state != AUTH_FINISHED);
+ i++;
+
+ /* Show the current time below the first password field only. */
+ if (*date_text && !emitted_user_p)
+ {
+ lines[i].text = date_text;
+ lines[i].font = ws->date_font;
+ lines[i].fg = ws->xft_foreground;
+ lines[i].bg = ws->background;
+ lines[i].type = LABEL;
+ lines[i].align = RIGHT;
+ i++;
+ }
+
+ /* Show the current keyboard layout below that. */
+ if (ws->kbd_layout_label && *ws->kbd_layout_label && !emitted_user_p)
+ {
+ lines[i].text = ws->kbd_layout_label;
+ lines[i].font = ws->date_font;
+ lines[i].fg = ws->xft_foreground;
+ lines[i].bg = ws->background;
+ lines[i].type = LABEL;
+ lines[i].align = RIGHT;
+ i++;
+ }
+
+ emitted_user_p = True;
+ break;
+
+ default:
+ abort();
+ break;
+ }
+ }
+
+ lines[i].text = 0;
+ y = draw_dialog_lines (ws, dbuf, lines,
+ text_left, text_right,
+ ws->border_width + ws->internal_padding +
+ ws->shadow_width);
+ window_height = y;
+ window_height += (ws->button_font->ascent * 4);
+ window_height += (ws->internal_padding + ws->shadow_width * 2 +
+ ws->border_width);
+
+
+ /* Keep logo area square or taller */
+ if (window_height < logo_frame_height + ws->shadow_width * 4)
+ window_height = logo_frame_height + ws->shadow_width * 4;
+
+ /* Fitt's Law: It is distracting to reduce the height of the window
+ after creation. */
+ if (window_height < ws->min_height)
+ window_height = ws->min_height;
+ ws->min_height = window_height;
+
+
+
+ /* Now do a second set of lines for the buttons at the bottom. */
+
+ memset (lines, 0, sizeof(*lines));
+ i = 0;
+
+ if (ws->splash_p)
+ {
+ lines[i].text = _("Settings");
+ lines[i].font = ws->button_font;
+ lines[i].fg = ws->xft_button_foreground;
+ lines[i].bg = ws->button_background;
+ lines[i].type = BUTTON;
+ lines[i].align = LEFT;
+ lines[i].float_p = True;
+ lines[i].button = &ws->demo_button_state;
+ i++;
+
+ lines[i].text = _("Help");
+ lines[i].font = ws->button_font;
+ lines[i].fg = ws->xft_button_foreground;
+ lines[i].bg = ws->button_background;
+ lines[i].type = BUTTON;
+ lines[i].align = RIGHT;
+ lines[i].button = &ws->help_button_state;
+ i++;
+ }
+ else
+ {
+ if (ws->newlogin_cmd && *ws->newlogin_cmd)
+ {
+ lines[i].text = _("New Login");
+ lines[i].font = ws->button_font;
+ lines[i].fg = ws->xft_button_foreground;
+ lines[i].bg = ws->button_background;
+ lines[i].type = BUTTON;
+ lines[i].align = LEFT;
+ lines[i].float_p = True;
+ lines[i].button = &ws->newlogin_button_state;
+ i++;
+ }
+
+ lines[i].text = _("OK");
+ lines[i].font = ws->button_font;
+ lines[i].fg = ws->xft_button_foreground;
+ lines[i].bg = ws->button_background;
+ lines[i].type = BUTTON;
+ lines[i].align = RIGHT;
+ lines[i].button = &ws->unlock_button_state;
+ i++;
+ }
+
+ lines[i].text = 0;
+ y = draw_dialog_lines (ws, dbuf, lines,
+ text_left, text_right,
+ window_height - ws->internal_padding -
+ ext_border -
+ ws->shadow_width -
+ (ws->button_font->ascent * 2));
+
+ /* The thermometer */
+ if (ws->thermo_width)
+ {
+ if (ws->auth_state != AUTH_NOTIFY)
+ {
+ int thermo_w = ws->thermo_width;
+ int thermo_h = window_height - ext_border * 2;
+ int thermo_h2 = thermo_h - ws->shadow_width * 2;
+ int thermo_h3 = thermo_h2 * (1.0 - ratio);
+
+ XSetForeground (dpy, gc, ws->thermo_foreground);
+ XFillRectangle (dpy, dbuf, gc,
+ thermo_x + ws->shadow_width,
+ ext_border + ws->shadow_width,
+ thermo_w, thermo_h2);
+ if (thermo_h3 > 0)
+ {
+ XSetForeground (dpy, gc, ws->thermo_background);
+ XFillRectangle (dpy, dbuf, gc,
+ thermo_x + ws->shadow_width,
+ ext_border + ws->shadow_width,
+ thermo_w, thermo_h3);
+ }
+ }
+
+ draw_shaded_rectangle (dpy, dbuf,
+ thermo_x, ext_border,
+ ws->thermo_width + ws->shadow_width * 2,
+ window_height - ext_border * 2,
+ ws->shadow_width,
+ ws->shadow_bottom, ws->shadow_top);
+ }
+
+ /* The logo, centered vertically.
+ */
+ {
+ int bot = window_height - ext_border * 2;
+ int xoff = (logo_frame_width - ws->logo_width) / 2;
+ int yoff = (bot - ws->logo_height) / 2;
+ x = ext_border;
+ y = ext_border;
+ XSetForeground (dpy, gc, ws->logo_background);
+ XFillRectangle (dpy, dbuf, gc, x, y, logo_frame_width, bot);
+ XSetForeground (dpy, gc, ws->foreground);
+ XSetBackground (dpy, gc, ws->background);
+ XSetClipMask (dpy, gc, ws->logo_clipmask);
+ XSetClipOrigin (dpy, gc, x + xoff, y + yoff);
+ XCopyArea (dpy, ws->logo_pixmap, dbuf, gc, 0, 0,
+ ws->logo_width, ws->logo_height,
+ x + xoff, y + yoff);
+ XSetClipMask (dpy, gc, 0);
+ draw_shaded_rectangle (dpy, dbuf,
+ x, y,
+ logo_frame_width,
+ bot,
+ ws->shadow_width,
+ ws->shadow_bottom, ws->shadow_top);
+ }
+
+ /* The window's shadow */
+ draw_shaded_rectangle (dpy, dbuf,
+ ws->border_width, ws->border_width,
+ window_width - ws->border_width * 2,
+ window_height - ws->border_width * 2,
+ ws->shadow_width,
+ ws->shadow_top, ws->shadow_bottom);
+
+ /* The window's border */
+ draw_shaded_rectangle (dpy, dbuf,
+ 0, 0, window_width, window_height,
+ ws->border_width,
+ ws->border_color, ws->border_color);
+
+
+ /* Now that everything has been rendered into the pixmap, reshape the window
+ and copy the pixmap to it. This double-buffering is to prevent flicker.
+
+ You'd think we could just reshape the window and then XMapRaised, but no.
+ With the XCompose extension enabled and the "Mutter" window manager, the
+ dialog window was sometimes not appearing on the screen, despite the fact
+ that XQueryTree reported it as the topmost window. The mouse pointer also
+ reflected it being there, but it wasn't visible. This is probably related
+ to the "XCompositeGetOverlayWindow" window in some way, which is a magic,
+ invisible window that is secretly excluded from the list returned by
+ XQueryTree, but I can't figure out what was really going on, except that
+ XMapRaised did not make my OverrideRedirect window appear topmost on the
+ screen.
+
+ However! Though XMapRaised was not working, it turns out that destroying
+ and re-creating the window *does* make it appear. So we do that, any time
+ the window's shape has changed, or some other window has raised above it.
+
+ Calling XQueryTree at 30fps could conceivably be a performance problem,
+ if there are thousands of windows on the screen. But here we are.
+ */
+ {
+ Bool size_changed_p, occluded_p;
+
+ /* It's distracting to move or shrink the window after creating it. */
+ if (xgwa.height > 100 && xgwa.height > window_height)
+ window_height = xgwa.height;
+ if (! ws->x)
+ {
+ ws->x = ws->cx - (window_width / 2);
+ ws->y = ws->cy - (window_height / 2);
+ }
+
+ /* If there is any change to the window's size, or if the window is
+ not on top, destroy and re-create the window. */
+ size_changed_p = !(xgwa.x == ws->x &&
+ xgwa.y == ws->y &&
+ xgwa.width == window_width &&
+ xgwa.height == window_height);
+ occluded_p = (!size_changed_p &&
+ window_occluded_p (ws->dpy, ws->window));
+
+ if (size_changed_p || occluded_p)
+ {
+# if 0 /* Window sometimes disappears under Mutter 3.30.2, Feb 2021. */
+ XWindowChanges wc;
+ wc.x = ws->x;
+ wc.y = ws->y;
+ wc.width = window_width;
+ wc.height = window_height;
+ if (verbose_p)
+ fprintf (stderr, "%s: reshaping window %dx%d+%d+%d\n", blurb(),
+ wc.width, wc.height, wc.x, wc.y);
+ XConfigureWindow (ws->dpy, ws->window, CWX|CWY|CWWidth|CWHeight, &wc);
+# else
+ if (verbose_p)
+ fprintf (stderr, "%s: re-creating window: %s\n", blurb(),
+ size_changed_p ? "size changed" : "occluded");
+ create_window (ws, window_width, window_height);
+# endif
+ XMapRaised (ws->dpy, ws->window);
+ XInstallColormap (ws->dpy, ws->cmap);
+ }
+ }
+
+ XFreeGC (dpy, gc);
+ gc = XCreateGC (dpy, ws->window, 0, &gcv);
+ XCopyArea (dpy, dbuf, ws->window, gc, 0, 0,
+ window_width, window_height, 0, 0);
+ XSync (dpy, False);
+ XFreeGC (dpy, gc);
+ XFreePixmap (dpy, dbuf);
+ free (lines);
+
+ if (verbose_p > 1)
+ {
+ static time_t last = 0;
+ static int count = 0;
+ count++;
+ if (now > last)
+ {
+ double fps = count / (double) (now - last);
+ fprintf (stderr, "%s: FPS: %0.1f\n", blurb(), fps);
+ count = 0;
+ last = now;
+ }
+ }
+}
+
+
+/* Unmaps the window and frees window_state.
+ */
+static void
+destroy_window (window_state *ws)
+{
+ XEvent event;
+
+ memset (ws->plaintext_passwd, 0, sizeof(ws->plaintext_passwd));
+ memset (ws->plaintext_passwd_char_size, 0,
+ sizeof(ws->plaintext_passwd_char_size));
+ memset (ws->censored_passwd, 0, sizeof(ws->censored_passwd));
+
+ if (ws->timer)
+ {
+ XtRemoveTimeOut (ws->timer);
+ ws->timer = 0;
+ }
+
+ if (ws->cursor_timer)
+ {
+ XtRemoveTimeOut (ws->cursor_timer);
+ ws->cursor_timer = 0;
+ }
+
+ while (XCheckMaskEvent (ws->dpy, PointerMotionMask, &event))
+ if (verbose_p)
+ fprintf (stderr, "%s: discarding MotionNotify event\n", blurb());
+
+ if (ws->window)
+ {
+ XDestroyWindow (ws->dpy, ws->window);
+ ws->window = 0;
+ }
+
+ if (ws->heading_label) free (ws->heading_label);
+ if (ws->date_format) free (ws->date_format);
+ if (ws->hostname_label) free (ws->hostname_label);
+ if (ws->kbd_layout_label) free (ws->kbd_layout_label);
+
+ if (ws->heading_font) XftFontClose (ws->dpy, ws->heading_font);
+ if (ws->body_font) XftFontClose (ws->dpy, ws->body_font);
+ if (ws->label_font) XftFontClose (ws->dpy, ws->label_font);
+ if (ws->date_font) XftFontClose (ws->dpy, ws->date_font);
+ if (ws->button_font) XftFontClose (ws->dpy, ws->button_font);
+ if (ws->hostname_font) XftFontClose (ws->dpy, ws->hostname_font);
+
+ XftColorFree (ws->dpy, DefaultVisualOfScreen (ws->screen),
+ DefaultColormapOfScreen (ws->screen),
+ &ws->xft_foreground);
+ XftColorFree (ws->dpy, DefaultVisualOfScreen (ws->screen),
+ DefaultColormapOfScreen (ws->screen),
+ &ws->xft_button_foreground);
+ XftColorFree (ws->dpy, DefaultVisualOfScreen (ws->screen),
+ DefaultColormapOfScreen (ws->screen),
+ &ws->xft_text_foreground);
+ XftColorFree (ws->dpy, DefaultVisualOfScreen (ws->screen),
+ DefaultColormapOfScreen (ws->screen),
+ &ws->xft_error_foreground);
+ XftDrawDestroy (ws->xftdraw);
+
+# if 0 /* screw this, we're exiting anyway */
+ if (ws->foreground != black && ws->foreground != white)
+ XFreeColors (ws->dpy, ws->cmap, &ws->foreground, 1, 0L);
+ if (ws->background != black && ws->background != white)
+ XFreeColors (ws->dpy, ws->cmap, &ws->background, 1, 0L);
+ if (ws->button_background != black && ws->button_background != white)
+ XFreeColors (ws->dpy, ws->cmap, &ws->button_background, 1, 0L);
+ if (ws->passwd_background != black && ws->passwd_background != white)
+ XFreeColors (ws->dpy, ws->cmap, &ws->passwd_background, 1, 0L);
+ if (ws->thermo_foreground != black && ws->thermo_foreground != white)
+ XFreeColors (ws->dpy, ws->cmap, &ws->thermo_foreground, 1, 0L);
+ if (ws->thermo_background != black && ws->thermo_background != white)
+ XFreeColors (ws->dpy, ws->cmap, &ws->thermo_background, 1, 0L);
+ if (ws->logo_background != black && ws->logo_background != white)
+ XFreeColors (ws->dpy, ws->cmap, &ws->logo_background, 1, 0L);
+ if (ws->shadow_top != black && ws->shadow_top != white)
+ XFreeColors (ws->dpy, ws->cmap, &ws->shadow_top, 1, 0L);
+ if (ws->shadow_bottom != black && ws->shadow_bottom != white)
+ XFreeColors (ws->dpy, ws->cmap, &ws->shadow_bottom, 1, 0L);
+# endif
+
+ if (ws->logo_pixmap)
+ XFreePixmap (ws->dpy, ws->logo_pixmap);
+ if (ws-> logo_clipmask)
+ XFreePixmap (ws->dpy, ws->logo_clipmask);
+ if (ws->logo_pixels)
+ {
+ if (ws->logo_npixels)
+ XFreeColors (ws->dpy, ws->cmap, ws->logo_pixels, ws->logo_npixels, 0L);
+ free (ws->logo_pixels);
+ ws->logo_pixels = 0;
+ ws->logo_npixels = 0;
+ }
+
+ XSync (ws->dpy, False);
+ memset (ws, 0, sizeof(*ws));
+ free (ws);
+
+}
+
+
+static void
+unlock_cb (window_state *ws)
+{
+ if (ws->auth_state == AUTH_READ)
+ ws->auth_state = AUTH_FINISHED;
+}
+
+
+/* We store the count and last time of authorization failures on a property
+ on the root window, so that on subsequent runs of this program that
+ succeed, we can warn the user that someone tried to log in and failed.
+ */
+static void
+persistent_auth_status_failure (window_state *ws,
+ Bool increment_p, Bool clear_p,
+ int *count_ret,
+ time_t *time_ret)
+{
+ Display *dpy = ws->dpy;
+ Window w = RootWindow (dpy, 0); /* always screen 0 */
+ Atom prop = XInternAtom (ws->dpy, LOCK_FAILURE_ATOM, False);
+ int count = 0;
+ time_t tt = 0;
+
+ Atom type;
+ unsigned char *dataP = 0;
+ int format;
+ unsigned long nitems, bytesafter;
+
+ if (increment_p && clear_p) abort();
+
+ /* Read the old property so that we can increment it. */
+ if (XGetWindowProperty (dpy, w, prop,
+ 0, 999, False, XA_INTEGER,
+ &type, &format, &nitems, &bytesafter,
+ &dataP)
+ == Success
+ && type == XA_INTEGER
+ && nitems >= 2
+ && dataP)
+ {
+ count = ((PROP32 *) dataP) [0];
+ tt = ((PROP32 *) dataP) [1]; /* Y2038 bug: unsigned 32 bit time_t */
+ if (verbose_p)
+ fprintf (stderr, "%s: previous auth failures: %d @ %lu\n",
+ blurb(), count, (unsigned long) tt);
+ }
+
+ if (dataP)
+ XFree (dataP);
+
+ if (clear_p)
+ {
+ XDeleteProperty (dpy, w, prop);
+ if (verbose_p)
+ fprintf (stderr, "%s: deleted auth failure property\n", blurb());
+ }
+ else if (increment_p)
+ {
+ PROP32 vv[2];
+ count++;
+
+ /* Remember the time of the *oldest* failed login. A failed login
+ 5 seconds ago does not mean we should skip warning about a failed
+ login yesterday.
+ */
+ if (tt <= 0) tt = time ((time_t *) 0);
+
+ vv[0] = (PROP32) count;
+ vv[1] = (PROP32) tt;
+ XChangeProperty (dpy, w, prop, XA_INTEGER, 32,
+ PropModeReplace, (unsigned char *) vv, 2);
+ if (verbose_p)
+ fprintf (stderr, "%s: saved auth failure: %d @ %lu\n",
+ blurb(), count, (unsigned long) tt);
+ }
+
+ if (count_ret) *count_ret = count;
+ if (time_ret) *time_ret = tt;
+}
+
+
+
+static void
+handle_keypress (window_state *ws, XKeyEvent *event)
+{
+ unsigned char decoded [MAX_BYTES_PER_CHAR * 10]; /* leave some slack */
+ KeySym keysym = 0;
+
+ /* XLookupString may return more than one character via XRebindKeysym;
+ and on some systems it returns multi-byte UTF-8 characters (contrary
+ to its documentation, which says it returns only Latin1.)
+
+ It seems to only do so, however, if setlocale() has been called.
+ See the code inside ENABLE_NLS in xscreensaver-auth.c.
+
+ The X Keyboard Extension X11R6.4 documentation says: "When Xkb is
+ present, XLookupString is allowed, but not required, to return strings
+ in character sets other than ISO Latin-1, depending on the current
+ locale." So I guess that means that multi-byte strings returned by
+ XLookupString might not be UTF-8, and thus might not be compatible
+ with XftDrawStringUtf8.
+ */
+ int decoded_size = XLookupString (event, (char *)decoded, sizeof(decoded),
+ &keysym, &ws->compose_status);
+
+ if (decoded_size > MAX_BYTES_PER_CHAR)
+ {
+ /* The multi-byte character returned is too large. */
+ XBell (ws->dpy, 0);
+ return;
+ }
+
+ decoded[decoded_size] = 0;
+
+ /* Add 10% to the time remaining every time a key is pressed, but don't
+ go past the max duration. */
+ {
+ time_t now = time ((time_t *) 0);
+ int max = get_seconds_resource (ws->dpy, "passwdTimeout", "Time");
+ int remain = ws->end_time - now;
+ remain *= 1.1;
+ if (remain > max) remain = max;
+ if (remain < 3) remain = 3;
+ ws->end_time = now + remain;
+ }
+
+
+ if (decoded_size == 1) /* Handle single-char commands */
+ {
+ switch (*decoded)
+ {
+ case '\010': case '\177': /* Backspace */
+ {
+ /* kludgey way to get the number of "logical" characters. */
+ int nchars = strlen (ws->plaintext_passwd_char_size);
+ int nbytes = strlen (ws->plaintext_passwd);
+ if (nbytes <= 0)
+ XBell (ws->dpy, 0);
+ else
+ {
+ int i;
+ for (i = ws->plaintext_passwd_char_size[nchars-1]; i >= 0; i--)
+ {
+ if (nbytes < 0) abort();
+ ws->plaintext_passwd[nbytes--] = 0;
+ }
+ ws->plaintext_passwd_char_size[nchars-1] = 0;
+ }
+ }
+ break;
+
+ case '\012': case '\015': /* Enter */
+ unlock_cb (ws);
+ break;
+
+ case '\033': /* Escape */
+ ws->auth_state = AUTH_CANCEL;
+ break;
+
+ case '\025': case '\030': /* Erase line */
+ memset (ws->plaintext_passwd, 0, sizeof (ws->plaintext_passwd));
+ memset (ws->plaintext_passwd_char_size, 0,
+ sizeof (ws->plaintext_passwd_char_size));
+ break;
+
+ default:
+ if (*decoded < ' ' && *decoded != '\t') /* Other ctrl char */
+ XBell (ws->dpy, 0);
+ else
+ goto SELF_INSERT;
+ break;
+ }
+ }
+ else
+ {
+ int nbytes, nchars;
+ SELF_INSERT:
+ nbytes = strlen (ws->plaintext_passwd);
+ nchars = strlen (ws->plaintext_passwd_char_size);
+ if (nchars + 1 >= sizeof (ws->plaintext_passwd_char_size)-1 ||
+ nbytes + decoded_size >= sizeof (ws->plaintext_passwd)-1)
+ XBell (ws->dpy, 0); /* overflow */
+ else if (decoded_size == 0)
+ ; /* Non-inserting keysym (Shift, Ctrl) */
+ else
+ {
+ ws->plaintext_passwd_char_size[nchars] = decoded_size;
+ ws->plaintext_passwd_char_size[nchars+1] = 0;
+ memcpy (ws->plaintext_passwd + nbytes, decoded, decoded_size);
+ ws->plaintext_passwd[nbytes + decoded_size] = 0;
+ }
+ }
+
+ /* Construct the string of asterisks. */
+ {
+ char *out = ws->censored_passwd;
+ int i;
+ *out = 0;
+ for (i = 0; i < MAX_PASSWD_CHARS && ws->plaintext_passwd_char_size[i]; i++)
+ {
+ const char *b = /* "\xE2\x80\xA2"; */ /* U+2022 Bullet */
+ "\xe2\x97\x8f"; /* U+25CF Black Circle */
+ strcat (out, b);
+ out += strlen(out);
+ }
+ }
+}
+
+
+static Bool
+handle_button (window_state *ws, XEvent *xev, line_button_state *bs)
+{
+ Bool mouse_in_box =
+ (xev->xbutton.x_root >= (ws->x + bs->rect.x) &&
+ xev->xbutton.x_root < (ws->x + bs->rect.x + bs->rect.width) &&
+ xev->xbutton.y_root >= (ws->y + bs->rect.y) &&
+ xev->xbutton.y_root < (ws->y + bs->rect.y + bs->rect.height));
+
+ bs->down_p = (!bs->disabled_p &&
+ mouse_in_box &&
+ xev->xany.type != ButtonRelease);
+
+ if (xev->xany.type == ButtonRelease && mouse_in_box && !bs->disabled_p)
+ {
+ bs->disabled_p = True; /* Only allow them to press the button once. */
+ if (bs->fn)
+ bs->fn (ws);
+ else if (bs->cmd)
+ {
+ int ac = 0;
+ char *av[10];
+ av[ac++] = "/bin/sh";
+ av[ac++] = "-c";
+ av[ac++] = bs->cmd;
+ av[ac] = 0;
+ fork_and_exec (ws->dpy, ac, av);
+ }
+ else
+ XBell (ws->dpy, 0);
+ }
+ return mouse_in_box;
+}
+
+
+static Bool
+handle_event (window_state *ws, XEvent *xev)
+{
+ Bool refresh_p = False;
+ switch (xev->xany.type) {
+ case KeyPress:
+ if (ws->splash_p)
+ ws->auth_state = AUTH_CANCEL;
+ else
+ {
+ handle_keypress (ws, &xev->xkey);
+ ws->caps_p = (xev->xkey.state & LockMask);
+ if (ws->auth_state == AUTH_NOTIFY)
+ ws->auth_state = AUTH_CANCEL;
+ }
+ refresh_p = True;
+ break;
+
+ case ButtonPress:
+ case ButtonRelease:
+ {
+ if (! (handle_button (ws, xev, &ws->newlogin_button_state) ||
+ handle_button (ws, xev, &ws->unlock_button_state) ||
+ handle_button (ws, xev, &ws->demo_button_state) ||
+ handle_button (ws, xev, &ws->help_button_state)))
+ if (ws->splash_p && xev->xany.type == ButtonRelease)
+ ws->auth_state = AUTH_CANCEL;
+ refresh_p = True;
+ }
+ default:
+ break;
+ }
+ return refresh_p;
+}
+
+
+/* Blink the I-beam cursor. */
+static void
+cursor_timer (XtPointer closure, XtIntervalId *id)
+{
+ window_state *ws = (window_state *) closure;
+ int timeout = 0.7 * 1000 * (ws->i_beam ? 0.25 : 0.75);
+ if (ws->cursor_timer)
+ XtRemoveTimeOut (ws->cursor_timer);
+ ws->cursor_timer =
+ XtAppAddTimeOut (ws->app, timeout, cursor_timer, (XtPointer) ws);
+ ws->i_beam = !ws->i_beam;
+}
+
+
+/* Redraw the window for the thermometer, and exit if the time has expired.
+ */
+static void
+thermo_timer (XtPointer closure, XtIntervalId *id)
+{
+ window_state *ws = (window_state *) closure;
+ int timeout = 1000/30; /* FPS */
+ double now = double_time();
+ if (now >= ws->end_time)
+ ws->auth_state = AUTH_TIME;
+ if (ws->timer) XtRemoveTimeOut (ws->timer);
+ ws->timer = XtAppAddTimeOut (ws->app, timeout, thermo_timer, (XtPointer) ws);
+}
+
+
+static void
+gui_main_loop (window_state *ws, Bool splash_p, Bool notification_p)
+{
+ int timeout;
+ Bool refresh_p = True;
+
+ if (splash_p)
+ {
+ timeout = get_seconds_resource (ws->dpy, "splashDuration", "Time");
+ if (timeout <= 1) timeout = 1;
+ }
+ else if (ws->auth_state == AUTH_NOTIFY)
+ timeout = 5;
+ else
+ {
+ timeout = get_seconds_resource (ws->dpy, "passwdTimeout", "Time");
+ if (timeout <= 5) timeout = 5;
+ cursor_timer (ws, 0);
+ }
+
+ ws->start_time = double_time();
+ ws->end_time = ws->start_time + timeout;
+
+ /* Since the "xscreensaver" process holds the mouse and keyboard grabbed
+ while "xscreensaver-auth" is running, we don't receive normal KeyPress
+ events. That means that the XInput2 extension is required in order to
+ snoop on the keyboard in a way that bypasses grabs.
+ */
+ if (! ws->xi_opcode)
+ {
+ Bool ov = verbose_p;
+ verbose_p = False;
+ init_xinput (ws->dpy, &ws->xi_opcode);
+ verbose_p = ov;
+ }
+
+ thermo_timer (ws, 0);
+ window_draw (ws);
+
+ while (ws->auth_state == AUTH_READ ||
+ ws->auth_state == AUTH_NOTIFY)
+ {
+ XEvent xev;
+ XtInputMask m = XtAppPending (ws->app);
+
+ if (m & XtIMXEvent)
+ /* Process timers then block on an X event (which we know is there) */
+ XtAppNextEvent (ws->app, &xev);
+ else
+ {
+ if (m)
+ /* Process timers only, don't block */
+ XtAppProcessEvent (ws->app, m);
+ else
+ {
+ if (refresh_p)
+ {
+ /* Redraw when outstanding events have been processed. */
+ window_draw (ws);
+ refresh_p = False;
+ }
+
+ /* No way to say "block until timer *or* X pending".
+ Without this, the timer that changes auth_state will fire but
+ then we will still be blocked until the next X event. */
+ usleep (1000000/30);
+ }
+ continue;
+ }
+
+ if ((m & ~XtIMXEvent) && !ws->splash_p)
+ refresh_p = True; /* In auth mode, all timers refresh */
+
+ if (verbose_p || debug_p)
+ print_xinput_event (ws->dpy, &xev, "");
+
+ /* Convert XInput events to Xlib events, for simplicity and familiarity.
+ */
+ if (xev.xcookie.type == GenericEvent &&
+ xev.xcookie.extension == ws->xi_opcode &&
+ (xev.xcookie.data || XGetEventData (ws->dpy, &xev.xcookie)))
+ {
+ XEvent ev2;
+ Bool ok =
+ xinput_event_to_xlib (xev.xcookie.evtype, xev.xcookie.data, &ev2);
+ XFreeEventData (ws->dpy, &xev.xcookie);
+ if (ok)
+ xev = ev2;
+ }
+
+ if (handle_event (ws, &xev))
+ refresh_p = True;
+
+ XtDispatchEvent (&xev);
+
+ switch (xev.xany.type) {
+
+ /* I don't think we ever receive these, but if we do, redraw. */
+ case Expose: case GraphicsExpose:
+ refresh_p = True;
+ break;
+
+ /* Likewise, receiving this event would be ideal, but we don't. */
+ case VisibilityNotify:
+ refresh_p = True;
+ if (verbose_p > 1)
+ fprintf (stderr, "%s: VisibilityNotify\n", blurb());
+ break;
+
+ /* When another override-redirect window is raised above us,
+ we receive several ConfigureNotify events on the root window. */
+ case ConfigureNotify:
+ if (verbose_p > 1)
+ fprintf (stderr, "%s: ConfigureNotify\n", blurb());
+ break;
+
+ case MappingNotify:
+ /* This event is supposed to be sent when the keymap is changed.
+ You would think that typing XK_ISO_Next_Group to change the
+ keyboard layout would count as such. It does not. */
+ if (verbose_p)
+ fprintf (stderr, "%s: MappingNotify\n", blurb());
+ get_keyboard_layout (ws);
+ refresh_p = True;
+ break;
+
+ default:
+ break;
+ }
+
+ /* Since MappingNotify doesn't work, we have to do this crap instead. */
+ if (xev.xany.type == ws->xkb_opcode)
+ {
+ XkbEvent *xkb = (XkbEvent *) &xev;
+ if (verbose_p)
+ fprintf (stderr, "%s: XKB event %d\n", blurb(), xkb->any.xkb_type);
+ get_keyboard_layout (ws);
+ refresh_p = True;
+ }
+ }
+
+ /* Re-raw the window one last time, since it might sit here for a while
+ while PAM does it's thing. */
+ window_draw (ws);
+ XSync (ws->dpy, False);
+
+ if (verbose_p) {
+ const char *kind = (splash_p ? "splash" :
+ notification_p ? "notification" : "authentication");
+ switch (ws->auth_state) {
+ case AUTH_FINISHED:
+ fprintf (stderr, "%s: %s input finished\n", blurb(), kind); break;
+ case AUTH_CANCEL:
+ fprintf (stderr, "%s: %s canceled\n", blurb(), kind); break;
+ case AUTH_TIME:
+ fprintf (stderr, "%s: %s timed out\n", blurb(), kind); break;
+ default: break;
+ }
+ }
+}
+
+
+/* Pops up a dialog and waits for the user to complete it.
+ Returns 0 on successful completion.
+ Updates 'resp' with any entered response.
+ */
+static Bool
+dialog_session (window_state *ws,
+ int nmsgs,
+ const auth_message *msgs,
+ auth_response *resp)
+{
+ int i;
+
+ ws->auth_state = AUTH_READ;
+ ws->nmsgs = nmsgs;
+ ws->msgs = msgs;
+
+ memset (ws->plaintext_passwd, 0, sizeof(ws->plaintext_passwd));
+ memset (ws->plaintext_passwd_char_size, 0,
+ sizeof(ws->plaintext_passwd_char_size));
+ memset (ws->censored_passwd, 0, sizeof(ws->censored_passwd));
+ ws->unlock_button_state.disabled_p = False;
+
+ gui_main_loop (ws, False, False);
+
+ if (ws->auth_state != AUTH_FINISHED)
+ return True; /* Timed out or canceled */
+
+ /* Find the (at most one) input field in the previous batch and return
+ the entered plaintext to it. */
+ for (i = 0; i < nmsgs; i++)
+ {
+ if (msgs[i].type == AUTH_MSGTYPE_PROMPT_ECHO ||
+ msgs[i].type == AUTH_MSGTYPE_PROMPT_NOECHO)
+ {
+ if (resp[i].response) abort();
+ resp[i].response = strdup(ws->plaintext_passwd);
+ }
+ }
+
+ ws->nmsgs = 0;
+ ws->msgs = 0;
+
+ return False;
+}
+
+
+/* To retain this across multiple calls from PAM to xscreensaver_auth_conv. */
+window_state *global_ws = 0;
+
+
+/* The authentication conversation function.
+
+ Like a PAM conversation function, this accepts multiple messages in a
+ single round. We can only do one text entry field in the dialog at a
+ time, so if there is more than one entry, multiple dialogs will be used.
+
+ PAM might call this multiple times before authenticating. We are unable
+ to combine multiple messages onto a single dialog if PAM splits them
+ between calls to this function.
+
+ Returns True on success. If the user timed out or cancelled, we just exit.
+ */
+Bool
+xscreensaver_auth_conv (void *closure,
+ int nmsgs,
+ const auth_message *msgs,
+ auth_response **resp)
+{
+ Widget root_widget = (Widget) closure;
+ int i;
+ int prev_msg = 0;
+ int field_count = 0;
+ auth_response *responses;
+ window_state *ws = global_ws;
+
+ if (!ws)
+ ws = global_ws = window_init (root_widget, False);
+
+ responses = calloc (nmsgs, sizeof(*responses));
+ if (!responses) abort();
+
+ for (i = 0; i < nmsgs; i++)
+ {
+ if (msgs[i].type == AUTH_MSGTYPE_PROMPT_ECHO ||
+ msgs[i].type == AUTH_MSGTYPE_PROMPT_NOECHO)
+ {
+ /* A text input field. */
+
+ if (field_count > 0)
+ {
+ /* This is the second one -- we must run the dialog on
+ the field collected so far. */
+ if (dialog_session (ws,
+ i - prev_msg,
+ msgs + prev_msg,
+ responses + prev_msg))
+ goto END;
+ prev_msg = i;
+ field_count = 0;
+ }
+
+ field_count++;
+ }
+ }
+
+ if (prev_msg < i || nmsgs == 0)
+ /* Run the dialog on the stuff that's left. This happens if there was
+ more than one text field. */
+ dialog_session (ws,
+ i - prev_msg,
+ msgs + prev_msg,
+ responses + prev_msg);
+
+ END:
+
+ switch (ws->auth_state) {
+ case AUTH_CANCEL:
+ case AUTH_TIME:
+ /* No need to return to PAM or clean up. We're outta here!
+ Exit with 0 to distinguish it from our "success" or "failure"
+ exit codes. */
+ destroy_window (ws);
+ exit (0);
+ break;
+ case AUTH_FINISHED:
+ *resp = responses;
+ return True;
+ default:
+ abort();
+ break;
+ }
+}
+
+
+/* Called after authentication is complete so that we can present a "nope"
+ dialog if it failed, or snitch on previous failed login attempts.
+ */
+void
+xscreensaver_auth_finished (void *closure, Bool authenticated_p)
+{
+ Widget root_widget = (Widget) closure;
+ window_state *ws = global_ws;
+ char msg[1024];
+ int unlock_failures = 0;
+ time_t failure_time = 0;
+ Bool prompted_p = !!ws;
+
+ /* If this was called without xscreensaver_auth_conv() ever having been
+ called, then either PAM decided that the user is authenticated without
+ a prompt (e.g. a bluetooth fob); or there was an error initializing
+ passwords (e.g., shadow passwords but not setuid.)
+ */
+ if (!ws)
+ ws = global_ws = window_init (root_widget, False);
+
+ if (authenticated_p)
+ {
+ /* Read the old failure count, and delete it. */
+ persistent_auth_status_failure (ws, False, True,
+ &unlock_failures, &failure_time);
+ }
+ else
+ {
+ /* Increment the failure count. */
+ persistent_auth_status_failure (ws, True, False,
+ &unlock_failures, &failure_time);
+ }
+
+ /* If we have something to say, put the dialog back up for a few seconds
+ to display it. Otherwise, don't bother.
+ */
+ if (!authenticated_p && !prompted_p)
+ strcpy (msg, _("Password initialization failed"));
+ else if (!authenticated_p && ws && ws->caps_p)
+ strcpy (msg, _("Authentication failed (Caps Lock?)"));
+ else if (!authenticated_p)
+ strcpy (msg, _("Authentication failed!"));
+ else if (authenticated_p && unlock_failures > 0)
+ {
+ time_t now = time ((time_t *) 0);
+ int sec = now - failure_time;
+ int min = (sec + 30) / 60;
+ int hours = (min + 30) / 60;
+ int days = (hours + 12) / 24;
+ char ago[100];
+ int warning_slack =
+ get_integer_resource (ws->dpy, "authWarningSlack", "AuthWarningSlack");
+
+ if (sec < warning_slack)
+ {
+ if (verbose_p)
+ fprintf (stderr, "%s: ignoring recent unlock failures:"
+ " %d within %d sec\n",
+ blurb(), unlock_failures, warning_slack);
+ goto END;
+ }
+ else if (days > 1) sprintf (ago, _("%d days ago"), days);
+ else if (hours > 1) sprintf (ago, _("%d hours ago"), hours);
+ else if (min > 1) sprintf (ago, _("%d minutes ago"), min);
+ else sprintf (ago, _("just now"));
+
+ if (unlock_failures == 1)
+ sprintf (msg, _("There has been 1 failed login attempt, %s."), ago);
+ else
+ sprintf (msg,
+ _("There have been %d failed login attempts, oldest %s."),
+ unlock_failures, ago);
+ }
+ else
+ {
+ /* No need to pop up a window. Authenticated, and there are no previous
+ failures to report.
+ */
+ goto END;
+ }
+
+ if (!*msg) abort();
+ ws->body_label = strdup (msg);
+
+ ws->auth_state = AUTH_NOTIFY;
+ gui_main_loop (ws, False, True);
+
+ END:
+ destroy_window (global_ws);
+}
+
+
+void
+xscreensaver_splash (void *closure)
+{
+ Widget root_widget = (Widget) closure;
+ window_state *ws = window_init (root_widget, True);
+ ws->auth_state = AUTH_READ;
+ gui_main_loop (ws, True, False);
+ destroy_window (ws);
+ exit (0);
+}