summaryrefslogtreecommitdiffstats
path: root/driver
diff options
context:
space:
mode:
authorSimon Rettberg2021-04-06 14:23:46 +0200
committerSimon Rettberg2021-04-06 14:23:46 +0200
commit26b6e4255d4b9ff79a6dca10de5bec7bfc8691f9 (patch)
treea51e1637554bcd84e63cccb1cb220c898a2c4ee8 /driver
parent5.44 (diff)
downloadxscreensaver-26b6e4255d4b9ff79a6dca10de5bec7bfc8691f9.tar.gz
xscreensaver-26b6e4255d4b9ff79a6dca10de5bec7bfc8691f9.tar.xz
xscreensaver-26b6e4255d4b9ff79a6dca10de5bec7bfc8691f9.zip
xscreensaver 6.00
Diffstat (limited to 'driver')
-rw-r--r--driver/Makefile.in1119
-rw-r--r--driver/XScreenSaver-Xm.ad2
-rw-r--r--driver/XScreenSaver.ad.in546
-rw-r--r--driver/XScreenSaver_Xm_ad.h108
-rw-r--r--driver/XScreenSaver_ad.h534
-rw-r--r--driver/atoms.c70
-rw-r--r--driver/atoms.h38
-rw-r--r--driver/atomswm.c101
-rw-r--r--driver/auth.h79
-rw-r--r--driver/blurb.c50
-rw-r--r--driver/blurb.h11
-rw-r--r--driver/clientmsg.c123
-rw-r--r--driver/clientmsg.h19
-rw-r--r--driver/demo-Gtk-conf.c24
-rw-r--r--driver/demo-Gtk.c535
-rw-r--r--driver/demo-Xm-widgets.c49
-rw-r--r--driver/demo-Xm.c166
-rw-r--r--driver/dialog.c2507
-rw-r--r--driver/dpms.c190
-rw-r--r--driver/exec.c32
-rw-r--r--driver/exts.c238
-rw-r--r--driver/fade.c1749
-rw-r--r--driver/fade.h20
-rw-r--r--driver/passwd-helper.c64
-rw-r--r--driver/passwd-kerberos.c45
-rw-r--r--driver/passwd-pam.c312
-rw-r--r--driver/passwd-pwent.c110
-rw-r--r--driver/passwd.c319
-rw-r--r--driver/prefs.c1694
-rw-r--r--driver/prefs.h28
-rw-r--r--driver/prefsw.c1555
-rw-r--r--driver/remote.c288
-rw-r--r--driver/remote.h3
-rw-r--r--driver/screens.c592
-rw-r--r--driver/screens.h37
-rw-r--r--driver/screensaver-properties.desktop.in2
-rw-r--r--driver/setuid.c229
-rw-r--r--driver/subprocs.c1271
-rw-r--r--driver/test-fade.c172
-rw-r--r--driver/test-grab.c146
-rw-r--r--driver/test-passwd.c333
-rw-r--r--driver/test-randr.c25
-rw-r--r--driver/test-screens.c31
-rw-r--r--driver/test-uid.c14
-rw-r--r--driver/test-vp.c24
-rw-r--r--driver/test-xdpms.c47
-rw-r--r--driver/test-xinerama.c26
-rw-r--r--driver/test-xinput.c306
-rw-r--r--driver/test-xkb.c89
-rw-r--r--driver/test-yarandom.c3
-rw-r--r--driver/types.h306
-rw-r--r--driver/windows.c2059
-rw-r--r--driver/xdpyinfo.c112
-rw-r--r--driver/xinput.c381
-rw-r--r--driver/xinput.h19
-rw-r--r--driver/xscreensaver-auth.c340
-rw-r--r--driver/xscreensaver-auth.man27
-rw-r--r--driver/xscreensaver-command.c152
-rw-r--r--driver/xscreensaver-command.man268
-rw-r--r--driver/xscreensaver-gfx.c595
-rw-r--r--driver/xscreensaver-gfx.man28
-rw-r--r--driver/xscreensaver-settings.man420
-rw-r--r--driver/xscreensaver-systemd.c1090
-rw-r--r--driver/xscreensaver-systemd.man80
-rw-r--r--driver/xscreensaver.c4185
-rw-r--r--driver/xscreensaver.h134
-rw-r--r--driver/xscreensaver.man1180
-rw-r--r--driver/xscreensaver.ui2994
68 files changed, 19576 insertions, 10869 deletions
diff --git a/driver/Makefile.in b/driver/Makefile.in
index 7baf504..0e986a9 100644
--- a/driver/Makefile.in
+++ b/driver/Makefile.in
@@ -1,9 +1,9 @@
-# driver/Makefile.in --- xscreensaver, Copyright (c) 1997-2010 Jamie Zawinski.
+# driver/Makefile.in --- xscreensaver, Copyright © 1997-2021 Jamie Zawinski.
# the `../configure' script generates `driver/Makefile' from this file.
@SET_MAKE@
.SUFFIXES:
-.SUFFIXES: .c .m .o
+.SUFFIXES: .c .m .o .desktop .desktop.in
srcdir = @srcdir@
VPATH = @srcdir@
@@ -19,42 +19,36 @@ datadir = @datadir@
localedir = @PO_DATADIR@/locale
mandir = @mandir@
libexecdir = @libexecdir@
-mansuffix = 1
-manNdir = $(mandir)/man$(mansuffix)
+sysconfdir = @sysconfdir@
-INTLTOOL_MERGE = @INTLTOOL_MERGE@
+# A = suffix for user commands in /usr/bin/
+# B = suffix for helper programs in /usr/libexec/xscreensaver/
+mansuffixA = 1
+mansuffixB = 6
GTK_DATADIR = @GTK_DATADIR@
GTK_APPDIR = $(GTK_DATADIR)/applications
GTK_ICONDIR = $(GTK_DATADIR)/pixmaps
-GTK_GLADEDIR = $(GTK_DATADIR)/xscreensaver/glade
+GTK_UIDIR = $(GTK_DATADIR)/xscreensaver/ui
+
+HACKDIR = @HACKDIR@
HACK_CONF_DIR = @HACK_CONF_DIR@
CC = @CC@
-OBJCC = @OBJCC@
CFLAGS = @CFLAGS@
LDFLAGS = @LDFLAGS@
DEFS = @DEFS@
-INTL_DEFS = -DLOCALEDIR=\"$(localedir)\"
-SUBP_DEFS = $(DEFS) -DDEFAULT_PATH_PREFIX='"@HACKDIR@"'
-GTK_DEFS = $(DEFS) -DDEFAULT_ICONDIR='"$(GTK_GLADEDIR)"'
-CONF_DEFS = -DHACK_CONFIGURATION_PATH='"$(HACK_CONF_DIR)"'
LIBS = @LIBS@
-INTL_LIBS = @INTLLIBS@
-JPEG_LIBS = @JPEG_LIBS@
-PERL = @PERL@
DEPEND = @DEPEND@
DEPEND_FLAGS = @DEPEND_FLAGS@
DEPEND_DEFINES = @DEPEND_DEFINES@
-SHELL = /bin/sh
INSTALL = @INSTALL@
SUID_FLAGS = -o root -m 4755
INSTALL_PROGRAM = @INSTALL_PROGRAM@
-INSTALL_SCRIPT = @INSTALL_SCRIPT@
-INSTALL_SETUID = @INSTALL_SETUID@
+INSTALL_SETUID = $(INSTALL_PROGRAM) $(SUID_FLAGS)
INSTALL_DATA = @INSTALL_DATA@
INSTALL_DIRS = @INSTALL_DIRS@
@@ -62,8 +56,11 @@ X_CFLAGS = @X_CFLAGS@
X_LIBS = @X_LIBS@
X_PRE_LIBS = @X_PRE_LIBS@
X_EXTRA_LIBS = @X_EXTRA_LIBS@
-XMU_LIBS = @XMU_LIBS@
PNG_LIBS = @PNG_LIBS@
+XFT_LIBS = @XFT_LIBS@
+
+INTLTOOL_MERGE = @INTLTOOL_MERGE@
+INTL_LIBS = @INTLLIBS@
# Note:
#
@@ -74,15 +71,18 @@ PNG_LIBS = @PNG_LIBS@
# (e.g., -lsocket, -lnsl, etc.)
#
# I think (but am not totally sure) that LIBS is also really "LDFLAGS".
-#
-# SAVER_LIBS is the link line for "xscreensaver", and
-# CMD_LIBS is the link line for "xscreensaver-command".
-# GETIMG_LIBS is the link line for "xscreensaver-getimage".
AD_DIR = @APPDEFAULTS@
-PAM_DIR = /etc/pam.d
-PAM_CONF = /etc/pam.conf
+
+# $(sysconfdir) is either /usr/local/etc or /usr/etc but this must be /etc.
+PAM_ROOT = /etc
+PAM_DIR = $(PAM_ROOT)/pam.d
+PAM_CONF = $(PAM_ROOT)/pam.conf
+
+ICON_SRC = $(UTILS_SRC)/images
+LOGO = $(ICON_SRC)/logo-50.xpm
+GTK_ICONS = $(ICON_SRC)/screensaver-*.png
UTILS_SRC = $(srcdir)/../utils
UTILS_BIN = ../utils
@@ -90,11 +90,40 @@ UTILS_BIN = ../utils
INCLUDES_1 = -I. -I$(srcdir) -I$(UTILS_SRC) -I..
INCLUDES = $(INCLUDES_1) @INCLUDES@
-MOTIF_SRCS = demo-Xm.c demo-Xm-widgets.c
-MOTIF_OBJS = demo-Xm.o demo-Xm-widgets.o
+LIBS_PRE = $(LIBS) $(X_LIBS) $(X_PRE_LIBS)
+LIBS_POST = $(X_EXTRA_LIBS)
-GTK_SRCS = demo-Gtk.c demo-Gtk-conf.c
-GTK_OBJS = demo-Gtk.o demo-Gtk-conf.o @GTK_EXTRA_OBJS@
+XDPMS_LIBS = @XDPMS_LIBS@
+XINERAMA_LIBS = @XINERAMA_LIBS@ $(FIXME)
+XINPUT_LIBS = -lXi
+XML_LIBS = @XML_LIBS@
+
+DAEMON_DEFS = -DDEFAULT_PATH_PREFIX='"@HACKDIR@"' -DAD_DIR='"$(AD_DIR)"'
+DAEMON_SRCS = xscreensaver.c blurb.c atoms.c clientmsg.c xinput.c prefs.c
+DAEMON_OBJS = xscreensaver.o blurb.o atoms.o clientmsg.o xinput.o prefs.o \
+ $(UTILS_BIN)/xmu.o
+DAEMON_LIBS = $(LIBS_PRE) $(XINPUT_LIBS) -lX11 $(LIBS_POST)
+
+GFX_DEFS = -DLOCALEDIR=\"$(localedir)\"
+GFX_SRCS = xscreensaver-gfx.c screens.c windows.c subprocs.c \
+ exec.c prefsw.c dpms.c fade.c exts.c atomswm.c
+GFX_OBJS = xscreensaver-gfx.o screens.o windows.o subprocs.o \
+ exec.o prefsw.o dpms.o fade.o exts.o atomswm.o \
+ prefs.o blurb.o atoms.o clientmsg.o xinput.o \
+ $(UTILS_BIN)/xmu.o \
+ $(UTILS_BIN)/yarandom.o \
+ $(UTILS_BIN)/resources.o \
+ $(UTILS_BIN)/visual.o \
+ $(UTILS_BIN)/usleep.o \
+ $(UTILS_BIN)/font-retry.o \
+ $(UTILS_BIN)/logo.o \
+ $(UTILS_BIN)/minixpm.o \
+ $(UTILS_BIN)/xft.o \
+ $(UTILS_BIN)/utf8wc.o \
+ $(UTILS_BIN)/xshm.o \
+ $(UTILS_BIN)/aligned_malloc.o
+GFX_LIBS = $(LIBS_PRE) $(XFT_LIBS) $(XDPMS_LIBS) $(XINERAMA_LIBS) \
+ @SAVER_LIBS@ -lXt -lX11 -lXext -lXi $(LIBS_POST) $(INTL_LIBS)
PWENT_SRCS = passwd-pwent.c
PWENT_OBJS = passwd-pwent.o
@@ -108,209 +137,186 @@ PAM_OBJS = passwd-pam.o
PWHELPER_SRCS = passwd-helper.c
PWHELPER_OBJS = passwd-helper.o
-LOCK_SRCS_1 = lock.c passwd.c
-LOCK_OBJS_1 = lock.o passwd.o
-NOLOCK_SRCS_1 = lock.c
-NOLOCK_OBJS_1 = lock.o
-
-SYSTEMD_SRCS = xscreensaver-systemd.c
-SYSTEMD_OBJS = xscreensaver-systemd.o
-SYSTEMD_LIBS = -lsystemd
-
-TEST_SRCS = test-passwd.c test-uid.c test-xdpms.c test-grab.c \
- test-apm.c test-fade.c test-xinerama.c test-vp.c \
- test-randr.c xdpyinfo.c test-mlstring.c test-screens.c \
- test-yarandom.c
-TEST_EXES = test-passwd test-uid test-xdpms test-grab \
- test-apm test-fade test-xinerama test-vp \
- test-randr xdpyinfo test-mlstring test-screens \
- test-yarandom
-
-MOTIF_LIBS = @MOTIF_LIBS@ @PNG_LIBS@ $(XMU_LIBS)
-GTK_LIBS = @GTK_LIBS@ $(XMU_LIBS)
-XML_LIBS = @XML_LIBS@
-
-XDPMS_LIBS = @XDPMS_LIBS@
-XINERAMA_LIBS = @XINERAMA_LIBS@
-XINPUT_LIBS = @XINPUT_LIBS@
-
PASSWD_SRCS = @PASSWD_SRCS@
PASSWD_OBJS = @PASSWD_OBJS@
-PASSWD_LIBS = @PASSWD_LIBS@
LOCK_SRCS = @LOCK_SRCS@
LOCK_OBJS = @LOCK_OBJS@
-XMU_SRCS = @XMU_SRCS@
-XMU_OBJS = @XMU_OBJS@
-
-GL_SRCS = @SAVER_GL_SRCS@
-GL_OBJS = @SAVER_GL_OBJS@
-GL_LIBS = @SAVER_GL_LIBS@
-
-ICON_SRC = $(UTILS_SRC)/images
-LOGO = $(ICON_SRC)/logo-50.xpm
-GTK_ICONS = $(ICON_SRC)/screensaver-*.png
-DEMO_UTIL_SRCS = $(UTILS_SRC)/resources.c $(UTILS_SRC)/usleep.c \
- $(UTILS_SRC)/visual.c $(XMU_SRCS)
-DEMO_UTIL_OBJS = $(UTILS_BIN)/resources.o $(UTILS_BIN)/usleep.o \
- $(UTILS_BIN)/visual.o $(XMU_OBJS)
-
-SAVER_UTIL_SRCS = $(UTILS_SRC)/fade.c $(UTILS_SRC)/overlay.c \
- $(UTILS_SRC)/logo.c $(UTILS_SRC)/yarandom.c \
- $(UTILS_SRC)/minixpm.c $(UTILS_SRC)/font-retry.c \
- $(DEMO_UTIL_SRCS)
-SAVER_UTIL_OBJS = $(UTILS_BIN)/fade.o $(UTILS_BIN)/overlay.o \
- $(UTILS_BIN)/logo.o $(UTILS_BIN)/yarandom.o \
- $(UTILS_BIN)/minixpm.o $(UTILS_BIN)/font-retry.o \
- $(DEMO_UTIL_OBJS)
-
-GETIMG_SRCS_1 = xscreensaver-getimage.c
-GETIMG_OBJS_1 = xscreensaver-getimage.o
-
-GETIMG_SRCS = $(GETIMG_SRCS_1) \
- $(UTILS_BIN)/colorbars.o $(UTILS_BIN)/resources.o \
- $(UTILS_BIN)/yarandom.o $(UTILS_BIN)/visual.o \
- $(UTILS_BIN)/usleep.o $(UTILS_BIN)/hsv.o \
- $(UTILS_BIN)/colors.o $(UTILS_BIN)/grabscreen.o \
- $(UTILS_BIN)/logo.o $(UTILS_BIN)/minixpm.o prefs.o \
- $(XMU_SRCS)
-
-GETIMG_OBJS = $(GETIMG_OBJS_1) \
- $(UTILS_BIN)/colorbars.o $(UTILS_BIN)/resources.o \
- $(UTILS_BIN)/yarandom.o $(UTILS_BIN)/visual.o \
- $(UTILS_BIN)/usleep.o $(UTILS_BIN)/hsv.o \
- $(UTILS_BIN)/colors.o $(UTILS_BIN)/grabscreen.o \
- $(UTILS_BIN)/logo.o $(UTILS_BIN)/minixpm.o prefs.o \
- $(XMU_OBJS)
-
-SAVER_SRCS_1 = xscreensaver.c windows.c screens.c timers.c subprocs.c \
- exec.c xset.c splash.c setuid.c stderr.c mlstring.c
-SAVER_OBJS_1 = xscreensaver.o windows.o screens.o timers.o subprocs.o \
- exec.o xset.o splash.o setuid.o stderr.o mlstring.o
-
-SAVER_SRCS = $(SAVER_SRCS_1) prefs.c dpms.c $(LOCK_SRCS) \
- $(SAVER_UTIL_SRCS) $(GL_SRCS)
-SAVER_OBJS = $(SAVER_OBJS_1) prefs.o dpms.o $(LOCK_OBJS) \
- $(SAVER_UTIL_OBJS) $(GL_OBJS)
+AUTH_DEFS = -DLOCALEDIR=\"$(localedir)\" -DAD_DIR='"$(AD_DIR)"'
+AUTH_SRCS = xscreensaver-auth.c dialog.c passwd.c setuid.c
+AUTH_OBJS = xscreensaver-auth.o $(AUTH_OBJS_1)
+AUTH_OBJS_1 = dialog.o passwd.o setuid.o \
+ @PASSWD_OBJS@ \
+ blurb.o screens.o xinput.o prefs.o atoms.o atomswm.o \
+ $(UTILS_BIN)/xft.o \
+ $(UTILS_BIN)/xftwrap.o \
+ $(UTILS_BIN)/utf8wc.o \
+ $(UTILS_BIN)/font-retry.o \
+ $(UTILS_BIN)/yarandom.o \
+ $(UTILS_BIN)/usleep.o \
+ $(UTILS_BIN)/resources.o \
+ $(UTILS_BIN)/logo.o \
+ $(UTILS_BIN)/minixpm.o
+AUTH_LIBS = $(LIBS_PRE) $(XFT_LIBS) $(XINPUT_LIBS) $(XINERAMA_LIBS) \
+ @SAVER_LIBS@ -lXt -lX11 -lXext -lXi \
+ @PASSWD_LIBS@ $(LIBS_POST) $(INTL_LIBS)
+
+SYSTEMD_DEFS =
+SYSTEMD_SRCS = xscreensaver-systemd.c
+SYSTEMD_OBJS = xscreensaver-systemd.o blurb.o $(UTILS_BIN)/yarandom.o
+SYSTEMD_LIBS = $(LIBS_PRE) @SYSTEMD_LIBS@ -lX11 $(LIBS_POST)
+CMD_DEFS =
CMD_SRCS = remote.c xscreensaver-command.c
-CMD_OBJS = remote.o xscreensaver-command.o
-
-DEMO_SRCS_1 = prefs.c dpms.c
-DEMO_OBJS_1 = prefs.o dpms.o
-
-DEMO_SRCS = $(DEMO_SRCS_1) remote.c exec.c $(DEMO_UTIL_SRCS)
-DEMO_OBJS = $(DEMO_OBJS_1) remote.o exec.o $(DEMO_UTIL_OBJS)
-
-PDF2JPEG_SRCS = pdf2jpeg.m
-PDF2JPEG_OBJS = pdf2jpeg.o
-PDF2JPEG_LIBS = -framework Cocoa
-
-SAVER_LIBS = $(LIBS) $(X_LIBS) $(XMU_LIBS) @SAVER_LIBS@ \
- $(XDPMS_LIBS) $(XINERAMA_LIBS) $(GL_LIBS) $(X_PRE_LIBS) \
- -lXt -lX11 -lXext $(X_EXTRA_LIBS) \
- $(PASSWD_LIBS) $(INTL_LIBS)
-
-CMD_LIBS = $(LIBS) $(X_LIBS) \
- $(X_PRE_LIBS) -lX11 -lXext $(X_EXTRA_LIBS)
-
-GETIMG_LIBS = $(LIBS) $(X_LIBS) $(PNG_LIBS) $(JPEG_LIBS) \
- $(X_PRE_LIBS) -lXt -lX11 $(XMU_LIBS) -lXext $(X_EXTRA_LIBS)
-
-EXES = xscreensaver xscreensaver-command xscreensaver-demo \
- xscreensaver-getimage @EXES_OSX@ @EXES_SYSTEMD@
-EXES2 = @ALL_DEMO_PROGRAMS@
-EXES_OSX = pdf2jpeg
+CMD_OBJS = remote.o xscreensaver-command.o blurb.o atoms.o clientmsg.o
+CMD_LIBS = $(LIBS_PRE) $(XINPUT_LIBS) -lX11 -lXext $(LIBS_POST)
+
+GTK_DEFS = -DHACK_CONFIGURATION_PATH='"$(HACK_CONF_DIR)"' \
+ -DDEFAULT_PATH_PREFIX='"@HACKDIR@"' \
+ -DDEFAULT_ICONDIR='"$(GTK_UIDIR)"' \
+ -DLOCALEDIR=\"$(localedir)\" \
+ -I$(ICON_SRC)
+GTK_SRCS = demo-Gtk.c demo-Gtk-conf.c
+GTK_OBJS = demo-Gtk.o demo-Gtk-conf.o \
+ blurb.o exec.o prefs.o prefsw.o dpms.o remote.o \
+ clientmsg.o atoms.o \
+ $(UTILS_BIN)/xmu.o \
+ $(UTILS_BIN)/resources.o \
+ $(UTILS_BIN)/visual.o \
+ $(UTILS_BIN)/usleep.o
+GTK_LIBS = $(LIBS_PRE) $(INTL_LIBS) $(XDPMS_LIBS) \
+ $(XINERAMA_LIBS) $(XML_LIBS) @GTK_LIBS@ \
+ -lXt -lX11 -lXext -lXi $(LIBS_POST)
+
+MOTIF_DEFS = -DHACK_CONFIGURATION_PATH='"$(HACK_CONF_DIR)"' \
+ -DDEFAULT_PATH_PREFIX='"@HACKDIR@"'
+MOTIF_SRCS = demo-Xm.c demo-Xm-widgets.c
+MOTIF_OBJS = demo-Xm.o demo-Xm-widgets.o \
+ blurb.o exec.o prefs.o prefsw.o dpms.o remote.o \
+ clientmsg.o atoms.o \
+ $(UTILS_BIN)/xmu.o \
+ $(UTILS_BIN)/resources.o \
+ $(UTILS_BIN)/visual.o \
+ $(UTILS_BIN)/usleep.o
+MOTIF_LIBS = $(LIBS_PRE) $(XDPMS_LIBS) $(XDPMS_LIBS) @MOTIF_LIBS@ \
+ @PNG_LIBS@ -lXt -lX11 -lXext -lXi $(LIBS_POST)
+
+TEST_SRCS = test-passwd.c test-uid.c test-xdpms.c test-grab.c \
+ test-fade.c test-xinerama.c test-vp.c test-randr.c \
+ xdpyinfo.c test-screens.c test-yarandom.c test-xinput.c \
+ test-xkb.c
+TEST_EXES = test-passwd test-uid test-xdpms test-grab \
+ test-fade test-xinerama test-vp test-randr \
+ xdpyinfo test-screens test-yarandom test-xinput \
+ test-xkb
+
+EXES = xscreensaver xscreensaver-command xscreensaver-settings
+UTIL_EXES = xscreensaver-gfx @EXES_SYSTEMD@
+SETUID_EXES = xscreensaver-auth
+DEMO_EXES = @ALL_DEMO_PROGRAMS@
EXES_SYSTEMD = xscreensaver-systemd
-SCRIPTS_1 = xscreensaver-getimage-file xscreensaver-getimage-video \
- xscreensaver-text
-SCRIPTS_OSX = xscreensaver-getimage-desktop
-SCRIPTS = $(SCRIPTS_1) @SCRIPTS_OSX@
-
HDRS = XScreenSaver_ad.h XScreenSaver_Xm_ad.h \
xscreensaver.h prefs.h remote.h exec.h \
- demo-Gtk-conf.h auth.h mlstring.h types.h
-MEN_1 = xscreensaver.man xscreensaver-demo.man \
- xscreensaver-command.man \
- xscreensaver-text.man \
- xscreensaver-getimage.man \
- xscreensaver-getimage-file.man \
- xscreensaver-getimage-video.man \
- xscreensaver-systemd.man
-MEN_OSX = xscreensaver-getimage-desktop.man pdf2jpeg.man
-MEN = $(MEN_1) @MEN_OSX@
+ demo-Gtk-conf.h auth.h types.h blurb.h atoms.h clientmsg.h \
+ screens.h xinput.h fade.h
+MENA = xscreensaver.man xscreensaver-settings.man \
+ xscreensaver-command.man
+MENB = xscreensaver-gfx.man xscreensaver-auth.man \
+ xscreensaver-command.man xscreensaver-systemd.man
EXTRAS = README Makefile.in \
XScreenSaver.ad.in XScreenSaver-Xm.ad xscreensaver.pam.in \
- xscreensaver-demo.glade2.in xscreensaver-demo.glade2p \
- screensaver-properties.desktop.in \
+ xscreensaver.ui screensaver-properties.desktop.in \
.gdbinit
-VMSFILES = compile_axp.com compile_decc.com link_axp.com link_decc.com \
- vms-getpwnam.c vms-pwd.h vms-hpwd.c vms-validate.c \
- vms_axp.opt vms_axp_12.opt vms_decc.opt vms_decc_12.opt
-TARFILES = $(EXTRAS) $(VMSFILES) $(SAVER_SRCS_1) $(SYSTEMD_SRCS) \
- $(MOTIF_SRCS) $(GTK_SRCS) $(PWENT_SRCS) $(PWHELPER_SRCS) \
- $(KERBEROS_SRCS) $(PAM_SRCS) $(LOCK_SRCS_1) $(DEMO_SRCS_1) \
- $(CMD_SRCS) $(GETIMG_SRCS_1) $(PDF2JPEG_SRCS) $(HDRS) \
- $(SCRIPTS_1) $(SCRIPTS_OSX) $(MEN_1) $(MEN_OSX) \
- $(TEST_SRCS)
+TARFILES = $(DAEMON_SRCS) $(GFX_SRCS) $(AUTH_SRCS) $(SYSTEMD_SRCS) \
+ $(CMD_SRCS) $(GTK_SRCS) $(MOTIF_SRCS) $(PWENT_SRCS) \
+ $(PWHELPER_SRCS) $(KERBEROS_SRCS) $(PAM_SRCS) \
+ $(HDRS) $(MENA) $(MENB) $(TEST_SRCS) $(EXTRAS)
+default: $(EXES) $(UTIL_EXES) $(SETUID_EXES)
+all: $(EXES) $(UTIL_EXES) $(SETUID_EXES) $(DEMO_EXES)
+tests: $(TEST_EXES)
-default: $(EXES)
-all: $(EXES) $(EXES2)
-tests: $(TEST_EXES)
-install: install-program install-ad install-scripts \
- install-gnome install-man install-xml install-pam
-uninstall: uninstall-program uninstall-ad \
- uninstall-gnome uninstall-man uninstall-xml
+##############################################################################
+#
+# Installation
+#
+##############################################################################
-install-strip:
- $(MAKE) INSTALL_PROGRAM='$(INSTALL_PROGRAM) -s' \
- install
+install: install-program install-ad install-man install-xml \
+ install-pam install-gnome
+uninstall: uninstall-program uninstall-ad uninstall-man uninstall-xml \
+ uninstall-gnome
-install-program: $(EXES)
- @if [ ! -d $(install_prefix)$(bindir) ]; then \
- $(INSTALL_DIRS) $(install_prefix)$(bindir) ; \
+install-strip:
+ $(MAKE) INSTALL_PROGRAM='$(INSTALL_PROGRAM) -s' install
+
+install-program:: $(EXES)
+ @if [ ! -d $(install_prefix)$(bindir) ]; then \
+ $(INSTALL_DIRS) $(install_prefix)$(bindir) ; \
+ fi ; \
+ if [ ! -d $(install_prefix)$(HACKDIR) ]; then \
+ $(INSTALL_DIRS) $(install_prefix)$(HACKDIR) ; \
fi
- @inst="$(INSTALL_PROGRAM)" ; \
- if [ @NEED_SETUID@ = yes ]; then \
- me=`PATH="$$PATH:/usr/ucb" whoami` ; \
- if [ "$$me" = root ]; then \
- inst="$(INSTALL_SETUID)" ; \
- else \
- e=echo ; \
- $$e "" ;\
- $$e " ####################################################################";\
- $$e " Warning: xscreensaver has been compiled with support for shadow" ;\
- $$e " passwords. If your system actually uses shadow passwords," ;\
- $$e " then xscreensaver must be installed as a setuid root" ;\
- $$e " program in order for locking to work. To do this, you" ;\
- $$e " must run 'make install' as 'root', not as '$$me'." ;\
- $$e "" ;\
- $$e " For now, xscreensaver will be installed non-setuid, which" ;\
- $$e " means that locking might not work. (Try it and see.)" ;\
- $$e " ####################################################################";\
- $$e "" ;\
- fi ; \
- fi ; \
- echo $$inst xscreensaver $(install_prefix)$(bindir)/xscreensaver ; \
- $$inst xscreensaver $(install_prefix)$(bindir)/xscreensaver
- @for exe in xscreensaver-command xscreensaver-demo \
- xscreensaver-getimage @EXES_OSX@ @EXES_SYSTEMD@ ; do \
- echo $(INSTALL_PROGRAM) $$exe $(install_prefix)$(bindir)/$$exe ; \
- $(INSTALL_PROGRAM) $$exe $(install_prefix)$(bindir)/$$exe ; \
+
+install-program:: $(EXES)
+ @inst="$(INSTALL_PROGRAM)" ; \
+ for exe in $(EXES); do \
+ echo $$inst $$exe $(install_prefix)$(bindir)/$$exe ; \
+ $$inst $$exe $(install_prefix)$(bindir)/$$exe ; \
+ done
+
+install-program:: $(UTIL_EXES)
+ @inst="$(INSTALL_PROGRAM)" ; \
+ for exe in $(UTIL_EXES); do \
+ echo $$inst $$exe $(install_prefix)$(HACKDIR)/$$exe ; \
+ $$inst $$exe $(install_prefix)$(HACKDIR)/$$exe ; \
done
+install-program:: $(SETUID_EXES)
+ @inst="$(INSTALL_PROGRAM)" ; \
+ idir="$(install_prefix)$(HACKDIR)" ; \
+ if [ @SETUID_AUTH@ = yes ]; then \
+ inst="$(INSTALL_SETUID)" ; \
+ else \
+ inst="$(INSTALL_PROGRAM)" ; \
+ fi ; \
+ for exe in $(SETUID_EXES); do \
+ echo $$inst $$exe $$idir/$$exe ; \
+ if $$inst $$exe $$idir/$$exe ; then \
+ true ; \
+ elif [ @SETUID_AUTH@ = yes ]; then \
+ echo $(INSTALL_PROGRAM) $$exe $$idir/$$exe ; \
+ if $(INSTALL_PROGRAM) $$exe $$idir/$$exe ; then \
+ echo "" ; \
+ echo "WARNING: unable to install $$exe setuid." ; \
+ echo "WARNING: authentication may not work!" ; \
+ echo "" ; \
+ else \
+ exit 1 ; \
+ fi ; \
+ else \
+ exit 1 ; \
+ fi ; \
+ done
+
+
+# Symlink from xscreensaver-demo -> xscreensaver-settings in /usr/bin/
+install-program::
+ @D=$(install_prefix)$(bindir) ; \
+ echo ln -sf xscreensaver-settings $$D/xscreensaver-demo ; \
+ ln -sf xscreensaver-settings $$D/xscreensaver-demo
+
install-ad: XScreenSaver.ad
@if [ ! -d $(install_prefix)$(AD_DIR) ]; then \
$(INSTALL_DIRS) $(install_prefix)$(AD_DIR) ; \
- fi
- @-echo $(INSTALL_DATA) XScreenSaver.ad \
+ fi ; \
+ echo $(INSTALL_DATA) XScreenSaver.ad \
$(install_prefix)$(AD_DIR)/XScreenSaver ; \
if $(INSTALL_DATA) XScreenSaver.ad \
$(install_prefix)$(AD_DIR)/XScreenSaver ; then \
@@ -331,101 +337,106 @@ install-ad: XScreenSaver.ad
$$e "" ;\
$$e " ####################################################################";\
$$e " Warning: unable to install $(install_prefix)$(AD_DIR)/XScreenSaver" ;\
- $$e " The directory is unwritable. This is probably ok;" ;\
- $$e " xscreensaver should work without that file." ;\
+ $$e " This is probably ok; it should work without that file." ;\
$$e " ####################################################################";\
$$e "" ;\
exit 0 ; \
fi \
fi
-install-scripts: $(SCRIPTS) munge-scripts
- @for program in $(SCRIPTS); do \
- if [ -r $$program ] ; then \
- p=$$program ; \
- else \
- p=$(srcdir)/$$program ; \
- fi ; \
- echo $(INSTALL_SCRIPT) $$p \
- $(install_prefix)$(bindir)/$$program ; \
- $(INSTALL_SCRIPT) $$p \
- $(install_prefix)$(bindir)/$$program ; \
- done
-
-munge-scripts: $(SCRIPTS)
- @tmp=/tmp/mf.$$$$ ; \
- perl="${PERL}" ; \
- rm -f $$tmp ; \
- for program in $(SCRIPTS); do \
- sed "s@^\(#!\)\(/[^ ]*/perl[^ ]*\)\(.*\)\$$@\1$$perl\3@" \
- < $(srcdir)/$$program > $$tmp ; \
- if cmp -s $(srcdir)/$$program $$tmp ; then \
- true ; \
- else \
- echo "$$program: setting interpreter to $$perl" >&2 ; \
- cat $$tmp > ./$$program ; \
- fi ; \
- done ; \
- rm -f $$tmp
-
# When installing man pages, we install "foo.man" as "foo.N" and update
# the .TH line in the installed file with one like
#
# .TH XScreenSaver N "V.VV (DD-MMM-YYYY)" "X Version 11"
#
-# where N is the manual section suffix.
+# where N is the manual section suffix (usually 1 or 6, depending).
#
-install-man: $(MEN)
- @men="$(MEN)" ; \
+install-man:: $(MENA) $(MENB)
+ @ \
U=$(UTILS_SRC)/version.h ; \
- V=`sed -n 's/.*xscreensaver \([0-9]\.[^)]*)\).*/\1/p' < $$U` ; \
- T=/tmp/xs$$$$.$(mansuffix) ; \
- TH=".TH XScreenSaver $(mansuffix) \"$$V\" \"X Version 11\"" ; \
- echo "installing man pages: $$TH" ; \
+ V=`sed -n 's/.*xscreensaver \([0-9]\.[^)]*)\).*/\1/p' < $$U | \
+ head -1` ; \
+ T=/tmp/xs$$$$.man ; \
+ SUFA=$(mansuffixA) ; \
+ SUFB=$(mansuffixB) ; \
\
- if [ ! -d $(install_prefix)$(manNdir) ]; then \
- $(INSTALL_DIRS) $(install_prefix)$(manNdir) ; \
- fi ; \
+ INST() { \
+ TH=".TH XScreenSaver $$SUF \"$$V\" \"X Version 11\"" ; \
+ DIR="$(install_prefix)$(mandir)/man$$SUF" ; \
+ if [ ! -d $$DIR ]; then \
+ echo $(INSTALL_DIRS) $$DIR ; \
+ $(INSTALL_DIRS) $$DIR ; \
+ fi ; \
\
- for man in $$men; do \
- instname=`echo $$man | sed 's/\.man$$/\.$(mansuffix)/'` ; \
- manbase=`echo $$man | sed 's/\.man$$//'` ; \
- TH=".TH $$manbase $(mansuffix) \"$$V\" \"X Version 11\" \"XScreenSaver manual\"" ; \
sed -e "s/^\.TH.*/$$TH/" \
- -e 's/^\(\.BR xscr.*(\)[^()]\(.*\)/\1$(mansuffix)\2/' \
- -e 's@(MANSUFFIX)@($(mansuffix))@g' \
+ -e "s/^\(\.BR xscreens[^ ]* (\)[0-9]\(.*\)/\1$$SUFA\2/" \
+ -e "s@(MANSUFFIX)@($$SUFB)@g" \
< $(srcdir)/$$man > $$T ; \
- echo $(INSTALL_DATA) $(srcdir)/$$man \
- $(install_prefix)$(manNdir)/$$instname ; \
- $(INSTALL_DATA) $$T \
- $(install_prefix)$(manNdir)/$$instname ; \
- done ; \
+ manbase=`echo $$man | sed 's/\.man$$//'` ; \
+ echo $(INSTALL_DATA) $$man $$DIR/$$manbase.$$SUF ; \
+ $(INSTALL_DATA) $$T $$DIR/$$manbase.$$SUF ; \
+ } ; \
+ \
+ SUF=$$SUFA ; for man in $(MENA); do INST ; done ; \
+ SUF=$$SUFB ; for man in $(MENB); do INST ; done ; \
rm -f $$T
-uninstall-program:
- @for program in $(EXES) $(SCRIPTS); do \
+# Symlink from xscreensaver-demo.1 -> xscreensaver-settings.1 in /usr/man/man1/
+install-man::
+ @D=$(install_prefix)$(mandir)/man$(mansuffixA) ; \
+ F1=xscreensaver-settings.$(mansuffixA) ; \
+ F2=xscreensaver-demo.$(mansuffixA) ; \
+ echo ln -sf $$F1 $$D/$$F2 ; \
+ ln -sf $$F1 $$D/$$F2
+
+# These used to be in driver/ and installed into $(bindir)
+# Now they are in hacks/ and are installed into ${libexecdir}/xscreensaver/
+OLD_EXES = xscreensaver-getimage xscreensaver-getimage-file \
+ xscreensaver-getimage-video xscreensaver-text \
+ xscreensaver-systemd
+OLD_MEN = xscreensaver-getimage.man xscreensaver-getimage-file.man \
+ xscreensaver-getimage-video.man xscreensaver-text.man \
+ xscreensaver-demo.man
+uninstall-program::
+ @for program in $(EXES) $(OLD_EXES); do \
echo rm -f $(install_prefix)$(bindir)/$$program ; \
rm -f $(install_prefix)$(bindir)/$$program ; \
done
+uninstall-program::
+ @for program in $(UTIL_EXES) $(SETUID_EXES); do \
+ echo rm -f $(install_prefix)$(HACKDIR)/$$program ; \
+ rm -f $(install_prefix)$(HACKDIR)/$$program ; \
+ done
+
uninstall-ad:
rm -f $(install_prefix)$(AD_DIR)/XScreenSaver
uninstall-man:
- @men="$(MEN)" ; \
+ @men="$(MEN) $(OLD_MEN)" ; \
for man in $$men; do \
instname=`echo $$man | sed 's/\.man$$/\.$(mansuffix)/'` ; \
- echo rm -f $(install_prefix)$(manNdir)/$$instname* ; \
- rm -f $(install_prefix)$(manNdir)/$$instname* ; \
+ echo rm -f $(install_prefix)$(manAdir)/$$instname* ; \
+ rm -f $(install_prefix)$(manAdir)/$$instname* ; \
done
install-pam: xscreensaver.pam
- @src="xscreensaver.pam" ; \
- dest=`sed -n 's/.*PAM_SERVICE_NAME[ ]*"\([^"]*\)".*$$/\1/p' \
+ @src="xscreensaver.pam" ; \
+ name=`sed -n 's/.*PAM_SERVICE_NAME[ ]*"\([^"]*\)".*$$/\1/p' \
< ../config.h` ; \
dir="$(install_prefix)$(PAM_DIR)" ; \
conf="$(PAM_CONF)" ; \
\
+ if [ -z "$$name" ]; then \
+ echo "PAM not configured, not installing" >&2 ; \
+ exit 0 ; \
+ fi ; \
+ \
+ if [ ! -d $(install_prefix)$(PAM_ROOT) ]; then \
+ echo $(INSTALL_DIRS) $(install_prefix)$(PAM_ROOT) ; \
+ $(INSTALL_DIRS) $(install_prefix)$(PAM_ROOT) ; \
+ fi ; \
+ \
if [ -d $$dir ] ; then \
\
if [ -f $$dir/xdm ]; then \
@@ -435,52 +446,50 @@ install-pam: xscreensaver.pam
fi ; \
\
if [ -z "$$src2" ]; then \
- echo $(INSTALL_DATA) $$src $$dir/$$dest ; \
- $(INSTALL_DATA) $$src $$dir/$$dest ; \
+ echo $(INSTALL_DATA) $$src $$dir/$$name ; \
+ $(INSTALL_DATA) $$src $$dir/$$name ; \
else \
- src="xscreensaver.pam.$$$$" ; \
- echo "grep '^#%\|^auth\|^@include' $$src2 > $$src" ; \
- grep '^#%\|^auth\|^@include' $$src2 > $$src ; \
- echo $(INSTALL_DATA) $$src $$dir/$$dest ; \
- $(INSTALL_DATA) $$src $$dir/$$dest ; \
- echo rm -f $$src ; \
- rm -f $$src ; \
+ tmp="xscreensaver.pam.$$$$" ; \
+ grep '^#%\|^auth\|^@include' $$src2 > $$tmp ; \
+ if cmp -s $$tmp $$dir/$$name ; then \
+ echo "$$dir/$$name unchanged" ; \
+ else \
+ echo "Updating contents of $$dir/$$name from $$src2" ; \
+ $(INSTALL_DATA) $$tmp $$dir/$$name ; \
+ fi ; \
+ rm -f $$tmp ; \
fi ; \
\
- if [ ! -f $$dir/$$dest ]; then \
+ if [ ! -f $$dir/$$name ]; then \
e=echo ; \
$$e "" ;\
$$e " ####################################################################";\
$$e " Warning: xscreensaver has been compiled with support for Pluggable" ;\
$$e " Authentication Modules (PAM). However, we were unable to" ;\
- $$e " install the file $$dir/$$dest. PAM is unlikely" ;\
- $$e " to work without this file (and old-style password" ;\
- $$e " authentication will be used instead, which may or may not" ;\
- $$e " work.)" ;\
+ $$e " install the file \"$$dir/$$name\". XScreenSaver is" ;\
+ $$e " unlikely to work without this file." ;\
$$e " ####################################################################";\
$$e "" ;\
fi ; \
- elif [ -f $$conf -a "x$$dest" != "x" ]; then \
- if ( grep $$dest $$conf >/dev/null ); then \
- echo "$$conf unchanged: already has an entry for $$dest" ; \
+ elif [ -f $$conf -a "x$$name" != "x" ]; then \
+ if ( grep $$name $$conf >/dev/null ); then \
+ echo "$$conf unchanged: already has an entry for $$name" ; \
else \
- src="pam.conf.$$$$" ; \
- echo "grep -v $$dest $$conf > $$src" ; \
- grep -v $$dest $$conf > $$src ; \
- extras=`sed -n "s/^login\(.*auth.*\)$$/$$dest\1/p" $$conf`; \
- echo "$$extras" >> $$src ; \
+ tmp="pam.conf.$$$$" ; \
+ grep -v $$name $$conf > $$tmp ; \
+ extras=`sed -n "s/^login\(.*auth.*\)$$/$$name\1/p" $$conf`; \
+ echo "$$extras" >> $$tmp ; \
if [ "x$$extras" = "x" ]; then \
echo "Error: no login rules in $$conf?" >&2 ; \
else \
- echo "adding $$dest rules to $$src:" ; \
+ echo "adding $$name rules to $$conf:" ; \
+ echo "" ; \
echo "$$extras" | sed 's/^/ /' ; \
fi ; \
- echo $(INSTALL_DATA) $$src $$conf ; \
- $(INSTALL_DATA) $$src $$conf ; \
- echo rm -f $$src ; \
- rm -f $$src ; \
+ $(INSTALL_DATA) $$tmp $$conf ; \
+ rm -f $$tmp ; \
fi ; \
- if ( grep $$dest $$conf >/dev/null ); then \
+ if ( grep $$name $$conf >/dev/null ); then \
echo ; \
else \
e=echo ; \
@@ -489,12 +498,17 @@ install-pam: xscreensaver.pam
$$e " Warning: xscreensaver has been compiled with support for Pluggable" ;\
$$e " Authentication Modules (PAM). However, we were unable to" ;\
$$e " install xscreensaver rules in the file $$conf." ;\
- $$e " PAM is unlikely to work without this (and old-style" ;\
- $$e " password authentication will be used instead, which may" ;\
- $$e " or may not work.)" ;\
+ $$e " XScreenSaver is unlikely to work without this." ;\
$$e " ####################################################################";\
$$e "" ;\
fi ; \
+ else \
+ e=echo ; \
+ $$e "" ;\
+ $$e " ####################################################################";\
+ $$e " Warning: $$dir/ does not exist, not installing PAM config." ;\
+ $$e " ####################################################################";\
+ $$e "" ;\
fi
# screensaver-properties.desktop
@@ -529,43 +543,43 @@ install-gnome:: $(LOGO)
fi
# ../utils/images/screensaver-*.png
-# into /usr/share/xscreensaver/glade/
+# into /usr/share/xscreensaver/ui/
install-gnome::
- @if [ "$(GTK_DATADIR)" != "" ]; then \
- if [ ! -d "$(install_prefix)$(GTK_GLADEDIR)" ]; then \
- echo $(INSTALL_DIRS) "$(install_prefix)$(GTK_GLADEDIR)" ;\
- $(INSTALL_DIRS) "$(install_prefix)$(GTK_GLADEDIR)" ;\
+ @if [ "$(GTK_DATADIR)" != "" ]; then \
+ if [ ! -d "$(install_prefix)$(GTK_UIDIR)" ]; then \
+ echo $(INSTALL_DIRS) "$(install_prefix)$(GTK_UIDIR)" ;\
+ $(INSTALL_DIRS) "$(install_prefix)$(GTK_UIDIR)" ;\
fi ;\
for target in $(GTK_ICONS) ; do \
dest=`echo $$target | sed 's@^.*/@@'` ;\
echo $(INSTALL_DATA) $$target \
- $(install_prefix)$(GTK_GLADEDIR)/$$dest ;\
+ $(install_prefix)$(GTK_UIDIR)/$$dest ;\
$(INSTALL_DATA) $$target \
- $(install_prefix)$(GTK_GLADEDIR)/$$dest ;\
+ $(install_prefix)$(GTK_UIDIR)/$$dest ;\
done ;\
fi
-# xscreensaver-demo.glade2
-# into /usr/share/xscreensaver/glade/
-install-gnome:: xscreensaver-demo.glade2
+# xscreensaver.ui
+# into /usr/share/xscreensaver/ui/
+install-gnome:: xscreensaver.ui
@if [ "$(GTK_DATADIR)" != "" ]; then \
- if [ ! -d "$(install_prefix)$(GTK_GLADEDIR)" ]; then \
- echo $(INSTALL_DIRS) "$(install_prefix)$(GTK_GLADEDIR)" ;\
- $(INSTALL_DIRS) "$(install_prefix)$(GTK_GLADEDIR)" ;\
+ if [ ! -d "$(install_prefix)$(GTK_UIDIR)" ]; then \
+ echo $(INSTALL_DIRS) "$(install_prefix)$(GTK_UIDIR)" ;\
+ $(INSTALL_DIRS) "$(install_prefix)$(GTK_UIDIR)" ;\
fi ;\
- target=xscreensaver-demo.glade2 ;\
- echo $(INSTALL_DATA) $$target \
- $(install_prefix)$(GTK_GLADEDIR)/$$target ;\
- if $(INSTALL_DATA) $$target \
- $(install_prefix)$(GTK_GLADEDIR)/$$target ;\
+ target=xscreensaver.ui ;\
+ echo $(INSTALL_DATA) $(srcdir)/$$target \
+ $(install_prefix)$(GTK_UIDIR)/$$target ;\
+ if $(INSTALL_DATA) $(srcdir)/$$target \
+ $(install_prefix)$(GTK_UIDIR)/$$target ;\
then true ;\
else \
e=echo ; \
$$e "" ;\
$$e " ####################################################################";\
$$e " Warning: unable to install $$target into" ;\
- $$e " $(install_prefix)$(GTK_GLADEDIR)/." ;\
- $$e " Without this file, xscreensaver-demo will not" ;\
+ $$e " $(install_prefix)$(GTK_UIDIR)/." ;\
+ $$e " Without this file, xscreensaver-settings will not" ;\
$$e " be able to run properly." ;\
$$e " ####################################################################";\
$$e "" ;\
@@ -593,25 +607,34 @@ uninstall-gnome::
fi
# ../utils/images/screensaver-*.png
-# into /usr/share/xscreensaver/glade/
+# into /usr/share/xscreensaver/ui/
uninstall-gnome::
@if [ "$(GTK_DATADIR)" != "" ]; then \
for target in $(GTK_ICONS) ; do \
dest=`echo $$target | sed 's@^.*/@@'` ;\
- echo rm -f $(install_prefix)$(GTK_GLADEDIR)/$$dest ;\
- rm -f $(install_prefix)$(GTK_GLADEDIR)/$$dest ;\
+ echo rm -f $(install_prefix)$(GTK_UIDIR)/$$dest ;\
+ rm -f $(install_prefix)$(GTK_UIDIR)/$$dest ;\
done ;\
fi
-# xscreensaver-demo.glade2
-# into /usr/share/xscreensaver/glade/
-uninstall-gnome:: xscreensaver-demo.glade2
+# xscreensaver.ui
+# into /usr/share/xscreensaver/ui/
+uninstall-gnome::
@if [ "$(GTK_DATADIR)" != "" ]; then \
- target=xscreensaver-demo.glade2 ;\
- echo rm -f $(install_prefix)$(GTK_GLADEDIR)/$$target ;\
- rm -f $(install_prefix)$(GTK_GLADEDIR)/$$target ;\
+ for target in xscreensaver.ui xscreensaver-demo.ui ; do \
+ echo rm -f $(install_prefix)$(GTK_UIDIR)/$$target ;\
+ rm -f $(install_prefix)$(GTK_UIDIR)/$$target ;\
+ done ;\
+ rmdir "$(GTK_UIDIR)" ;\
+ rmdir "$(GTK_DATADIR)/xscreensaver" ;\
+ exit 0 ;\
fi
+# /usr/share/xscreensaver/glade/ no longer used
+uninstall-gnome::
+ -rm -rf $(GTK_DATADIR)/xscreensaver/glade
+
+
# /usr/share/xscreensaver/config/README
install-xml:
@dest=$(install_prefix)$(HACK_CONF_DIR) ; \
@@ -627,14 +650,24 @@ install-xml:
uninstall-xml:
rm -f $(install_prefix)$(HACK_CONF_DIR)/README
+
+##############################################################################
+#
+# Clean and dependencies
+#
+##############################################################################
+
clean:
- -rm -f *.o a.out core $(EXES) $(EXES2) $(TEST_EXES) \
+ -rm -f *.o a.out core $(EXES) $(UTIL_EXES) $(SETUID_EXES) \
+ $(DEMO_EXES) $(TEST_EXES) \
XScreenSaver_ad.h XScreenSaver_Xm_ad.h
distclean: clean
- -rm -f Makefile XScreenSaver.ad \
- TAGS *~ "#"* screensaver-properties.desktop \
- xscreensaver-demo.glade2 \
+ -rm -f \
+ TAGS *~ "#"* *.rej *.orig \
+ Makefile \
+ XScreenSaver.ad \
+ screensaver-properties.desktop \
xscreensaver.pam
# Adds all current dependencies to Makefile
@@ -642,7 +675,7 @@ depend: XScreenSaver_ad.h XScreenSaver_Xm_ad.h
$(DEPEND) -s '# DO NOT DELETE: updated by make depend' \
$(DEPEND_FLAGS) -- \
$(INCLUDES_1) $(DEFS) $(DEPEND_DEFINES) $(CFLAGS) $(X_CFLAGS) -- \
- $(SAVER_SRCS) $(CMD_SRCS) $(GETIMG_SRCS_1)
+ $(SAVER_SRCS) $(CMD_SRCS)
# Adds some dependencies to Makefile.in -- not totally accurate, but pretty
# close. This excludes dependencies on files in /usr/include, etc. It tries
@@ -655,8 +688,8 @@ distdepend: check_men update_ad_version XScreenSaver_ad.h XScreenSaver_Xm_ad.h
$(INCLUDES_1) $(DEFS) $(DEPEND_DEFINES) $(CFLAGS) $(X_CFLAGS) -- \
$(SAVER_SRCS_1) $(SYSTEMD_SRCS) $(MOTIF_SRCS) $(GTK_SRCS) \
$(PWENT_SRCS) $(PWHELPER_SRCS) $(KERBEROS_SRCS) $(PAM_SRCS) \
- $(LOCK_SRCS_1) $(DEMO_SRCS_1) $(CMD_SRCS) $(GETIMG_SRCS_1) \
- $(PDF2JPEG_SRCS) $(TEST_SRCS) 2>/dev/null | \
+ $(LOCK_SRCS_1) $(DEMO_SRCS_1) $(CMD_SRCS) \
+ $(TEST_SRCS) 2>/dev/null | \
sort -d | \
( \
awk '/^# .*Makefile.in ---/,/^# DO .*distdepend/' < Makefile.in ; \
@@ -679,12 +712,12 @@ update_ad_version::
@ \
files="XScreenSaver.ad.in ../hacks/config/README ../OSX/bindist.rtf" ; \
U=$(UTILS_SRC)/version.h ; \
- V=`sed -n 's/[^0-9]*\([0-9]\.[0-9][^. ]*\).*/\1/p' < $$U` ; \
+ V=`sed -n 's/[^0-9]*\([0-9]\.[0-9][^ ]*\).*/\1/p' < $$U | head -1` ; \
Y=`date '+%Y'` ; \
D=`date '+%d-%b-%Y'` ; \
for S in $$files ; do \
T=/tmp/xs.$$$$ ; \
- sed -e "s/\(.*version \)[0-9][0-9]*\.[0-9]*[ab]*[0-9]*\(.*\)/\1$$V\2/" \
+ sed -e "s/\(.*version \)[0-9][0-9]*\.[0-9.]*[ab]*[0-9]*\(.*\)/\1$$V\2/" \
-e "s/\([0-9][0-9]-[A-Z][a-z][a-z]-[0-9][0-9][0-9]*\)/$$D/" \
-e "s/\( [0-9][0-9][0-9][0-9]-\)[0-9][0-9][0-9][0-9] /\1$$Y /" \
< $$S > $$T ; \
@@ -701,9 +734,9 @@ TAGS: tags
tags:
find $(srcdir) -name '*.[chly]' -print | xargs etags -a
-echo_tarfiles:
+list_tarfiles:
@$(MAKE) XScreenSaver_ad.h XScreenSaver_Xm_ad.h 2>&1 >/dev/null
- @echo $(TARFILES)
+ @find $(TARFILES) -type f -print | sort
check_men:
@badmen="" ; \
@@ -723,11 +756,12 @@ check_men:
fi
-# Rules for noticing when the objects from the utils directory are out of
-# date with respect to their sources, and going and building them according
-# to the rules in their own Makefile...
+##############################################################################
+#
+# Dependencies on utils/
#
-$(UTILS_BIN)/fade.o: $(UTILS_SRC)/fade.c
+##############################################################################
+
$(UTILS_BIN)/overlay.o: $(UTILS_SRC)/overlay.c
$(UTILS_BIN)/resources.o: $(UTILS_SRC)/resources.c
$(UTILS_BIN)/usleep.o: $(UTILS_SRC)/usleep.c
@@ -740,157 +774,219 @@ $(UTILS_BIN)/colorbars.o: $(UTILS_SRC)/colorbars.c
$(UTILS_BIN)/hsv.o: $(UTILS_SRC)/hsv.c
$(UTILS_BIN)/colors.o: $(UTILS_SRC)/colors.c
$(UTILS_BIN)/grabscreen.o: $(UTILS_SRC)/grabscreen.c
+$(UTILS_BIN)/utf8wc.o: $(UTILS_SRC)/utf8wc.c
+$(UTILS_BIN)/xftwrap.o: $(UTILS_SRC)/xftwrap.c
$(UTILS_BIN)/font-retry.o: $(UTILS_SRC)/font-retry.c
-
-UTIL_OBJS = $(SAVER_UTIL_OBJS) $(UTILS_BIN)/colorbars.o \
- $(UTILS_BIN)/hsv.o $(UTILS_BIN)/colors.o \
- $(UTILS_BIN)/grabscreen.o
+$(UTILS_BIN)/xshm.o: $(UTILS_SRC)/xshm.c
+$(UTILS_BIN)/aligned_malloc.o: $(UTILS_SRC)/aligned_malloc.c
+
+
+UTIL_OBJS = $(UTILS_BIN)/overlay.o \
+ $(UTILS_BIN)/resources.o \
+ $(UTILS_BIN)/usleep.o \
+ $(UTILS_BIN)/visual.o \
+ $(UTILS_BIN)/xmu.o \
+ $(UTILS_BIN)/logo.o \
+ $(UTILS_BIN)/minixpm.o \
+ $(UTILS_BIN)/yarandom.o \
+ $(UTILS_BIN)/colorbars.o \
+ $(UTILS_BIN)/hsv.o \
+ $(UTILS_BIN)/colors.o \
+ $(UTILS_BIN)/grabscreen.o \
+ $(UTILS_BIN)/xft.o \
+ $(UTILS_BIN)/xftwrap.o \
+ $(UTILS_BIN)/utf8wc.o \
+ $(UTILS_BIN)/font-retry.o \
+ $(UTILS_BIN)/xshm.o \
+ $(UTILS_BIN)/aligned_malloc.o
$(UTIL_OBJS):
cd $(UTILS_BIN) ; \
$(MAKE) $(@F) CC="$(CC)" CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)"
+
+##############################################################################
+#
+# Compiling the daemon: xscreensaver, xscreensaver-gfx, xscreensaver-auth,
+# xscreensaver-systemd and xscreensaver-comand.
+#
+##############################################################################
+
# How we build object files in this directory.
+CC_ALL=$(INCLUDES) $(DEFS) $(CPPFLAGS) $(CFLAGS) $(X_CFLAGS)
.c.o:
- $(CC) -c $(INCLUDES) $(DEFS) $(CPPFLAGS) $(CFLAGS) $(X_CFLAGS) $<
-
-.m.o:
- $(OBJCC) -c $(INCLUDES) $(DEFS) $(CPPFLAGS) $(CFLAGS) $(X_CFLAGS) $<
+ $(CC) -c $(CC_ALL) $<
-# subprocs takes an extra -D option.
-subprocs.o: subprocs.c
- $(CC) -c $(INCLUDES) $(SUBP_DEFS) $(CPPFLAGS) $(CFLAGS) $(X_CFLAGS) \
- $(srcdir)/subprocs.c
+# # subprocs takes an extra -D option.
+# subprocs.o: subprocs.c
+# $(CC) -c $(CC_ALL) $(SUBP_DEFS) $<
# xscreensaver takes an extra -D option.
xscreensaver.o: xscreensaver.c
- $(CC) -c $(INCLUDES) $(DEFS) $(INTL_DEFS) $(CPPFLAGS) $(CFLAGS) $(X_CFLAGS) \
- $(srcdir)/xscreensaver.c
+ $(CC) -c $(CC_ALL) $(DAEMON_DEFS) $<
-# demo-Gtk takes extra -D options, and an extra -I option.
-demo-Gtk.o: demo-Gtk.c
- $(CC) -c $(INCLUDES) $(SUBP_DEFS) -I$(ICON_SRC) \
- $(GTK_DEFS) $(INTL_DEFS) $(CPPFLAGS) $(CFLAGS) $(X_CFLAGS) \
- $(srcdir)/demo-Gtk.c
+xscreensaver-auth.o: XScreenSaver_ad.h
+xscreensaver-auth.o: xscreensaver-auth.c
+ $(CC) -c $(CC_ALL) $(AUTH_DEFS) $<
-# demo-Gtk-conf takes an extra -D option.
-demo-Gtk-conf.o: demo-Gtk-conf.c
- $(CC) -c $(INCLUDES) $(CONF_DEFS) $(GTK_DEFS) $(CPPFLAGS) $(CFLAGS) $(X_CFLAGS) \
- $(srcdir)/demo-Gtk-conf.c
+xscreensaver: $(DAEMON_OBJS)
+ $(CC) $(LDFLAGS) -o $@ $(DAEMON_OBJS) $(DAEMON_LIBS)
+xscreensaver-gfx.o: XScreenSaver_ad.h
+xscreensaver-gfx.o: xscreensaver-gfx.c
+ $(CC) -c $(CC_ALL) $(GFX_DEFS) $<
+xscreensaver-gfx: $(GFX_OBJS)
+ $(CC) $(LDFLAGS) -o $@ $(GFX_OBJS) $(GFX_LIBS)
-# How we build the default app-defaults file into the program.
-#
-XScreenSaver_ad.h: XScreenSaver.ad
- $(SHELL) $(UTILS_SRC)/ad2c XScreenSaver.ad > XScreenSaver_ad.h
+dialog.o: dialog.c
+ $(CC) -c $(CC_ALL) $(AUTH_DEFS) $<
+xscreensaver-auth: $(AUTH_OBJS)
+ $(CC) $(LDFLAGS) -o $@ $(AUTH_OBJS) $(AUTH_LIBS)
-XScreenSaver_Xm_ad.h: XScreenSaver-Xm.ad
- $(SHELL) $(UTILS_SRC)/ad2c XScreenSaver-Xm.ad > XScreenSaver_Xm_ad.h
+xscreensaver-systemd: $(SYSTEMD_OBJS)
+ $(CC) $(LDFLAGS) -o $@ $(SYSTEMD_OBJS) $(SYSTEMD_LIBS) -lm
+
+xscreensaver-command: $(CMD_OBJS)
+ $(CC) $(LDFLAGS) -o $@ $(CMD_OBJS) $(CMD_LIBS)
-@INTLTOOL_DESKTOP_RULE@
-# The executables linked in this directory.
+##############################################################################
+#
+# Compiling the GUI, xscreensaver-settings
#
-xscreensaver: $(SAVER_OBJS)
- $(CC) $(LDFLAGS) -o $@ $(SAVER_OBJS) $(SAVER_LIBS) $(INTL_LIBS)
+##############################################################################
-xscreensaver-command: $(CMD_OBJS)
- $(CC) $(LDFLAGS) -o $@ $(CMD_OBJS) $(CMD_LIBS)
+demo-Gtk.o: XScreenSaver_ad.h
+demo-Gtk.o: demo-Gtk.c
+ $(CC) -c $(CC_ALL) $(GTK_DEFS) $<
+demo-Gtk-conf.o: demo-Gtk-conf.c
+ $(CC) -c $(CC_ALL) $(GTK_DEFS) $<
+
+xscreensaver-settings-Gtk: $(GTK_OBJS)
+ $(CC) $(LDFLAGS) -o $@ $(GTK_OBJS) $(GTK_LIBS)
-xscreensaver-demo: @PREFERRED_DEMO_PROGRAM@
+demo-Xm.o: XScreenSaver_ad.h
+demo-Xm.o: demo-Xm.c
+ $(CC) -c $(CC_ALL) $(GTK_DEFS) $<
+demo-Xm-widgets.o: demo-Xm-widgets.c
+ $(CC) -c $(CC_ALL) $(GTK_DEFS) $<
+
+xscreensaver-settings-Xm: $(MOTIF_OBJS)
+ $(CC) $(LDFLAGS) -o $@ $(MOTIF_OBJS) $(MOTIF_LIBS)
+
+xscreensaver-settings: @PREFERRED_DEMO_PROGRAM@
@if [ "@PREFERRED_DEMO_PROGRAM@" = "" ]; then \
echo "WARNING: neither GTK nor Motif are available," \
- "therefore no xscreensaver-demo!" ; \
+ "therefore no xscreensaver-settings!" ; \
rm -f $@@EXEEXT@ ; \
else \
echo cp -p @PREFERRED_DEMO_PROGRAM@@EXEEXT@ $@@EXEEXT@ ; \
cp -p @PREFERRED_DEMO_PROGRAM@@EXEEXT@ $@@EXEEXT@ ; \
fi
-xscreensaver-demo-Xm: $(DEMO_OBJS) $(MOTIF_OBJS)
- $(CC) $(LDFLAGS) -o $@ $(DEMO_OBJS) $(MOTIF_OBJS) $(LIBS) $(X_LIBS) \
- $(MOTIF_LIBS) $(INTL_LIBS) $(X_PRE_LIBS) -lXt -lX11 \
- $(XDPMS_LIBS) $(XINERAMA_LIBS) -lXext $(X_EXTRA_LIBS)
-xscreensaver-demo-Gtk: $(DEMO_OBJS) $(GTK_OBJS)
- $(CC) $(LDFLAGS) -o $@ $(DEMO_OBJS) $(GTK_OBJS) $(LIBS) $(X_LIBS) \
- $(GTK_LIBS) $(XML_LIBS) $(INTL_LIBS) $(X_PRE_LIBS) \
- -lXt -lX11 $(XDPMS_LIBS) $(XINERAMA_LIBS) -lXext $(X_EXTRA_LIBS)
+# How we build the default app-defaults file into the program.
+#
+XScreenSaver_ad.h::
+ @TMP=/tmp/xs$$$$.h ; \
+ IN="XScreenSaver.ad" ; \
+ OUT=XScreenSaver_ad.h ; \
+ $(UTILS_SRC)/ad2c $$IN > $$TMP ; \
+ if cmp -s $$TMP $$OUT ; then \
+ rm -f "$$TMP" ; \
+ else \
+ echo $(UTILS_SRC)/ad2c $$IN \> $$OUT ; \
+ mv $$TMP $$OUT ; \
+ fi
-demo-Gtk.o: XScreenSaver_ad.h
-demo-Xm.o: XScreenSaver_Xm_ad.h
-xscreensaver.o: XScreenSaver_ad.h
-xscreensaver-getimage.o: XScreenSaver_ad.h
+XScreenSaver_Xm_ad.h::
+ @TMP=/tmp/xs$$$$.h ; \
+ IN="XScreenSaver-Xm.ad" ; \
+ OUT=XScreenSaver_Xm_ad.h ; \
+ $(UTILS_SRC)/ad2c $$IN > $$TMP ; \
+ if cmp -s $$TMP $$OUT ; then \
+ rm -f "$$TMP" ; \
+ else \
+ echo $(UTILS_SRC)/ad2c $$IN \> $$OUT ; \
+ mv $$TMP $$OUT ; \
+ fi
-xscreensaver-getimage: $(GETIMG_OBJS)
- $(CC) $(LDFLAGS) -o $@ $(GETIMG_OBJS) $(GETIMG_LIBS) -lm
-pdf2jpeg: $(PDF2JPEG_OBJS)
- $(OBJCC) $(LDFLAGS) -o $@ $(PDF2JPEG_OBJS) $(PDF2JPEG_LIBS) -lm
+# Replace this with @INTLTOOL_DESKTOP_RULE@ once
+# https://bugs.launchpad.net/intltool/+bug/1749904 is fixed.
+.desktop.in.desktop: $(INTLTOOL_MERGE) $(top_srcdir)/po/*.po
+ $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) \
+ $(INTLTOOL_V_MERGE_OPTIONS) -d -u \
+ -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@
-xscreensaver-systemd: $(SYSTEMD_OBJS)
- $(CC) $(LDFLAGS) -o $@ $(SYSTEMD_OBJS) $(SYSTEMD_LIBS) -lm
-TEST_PASSWD_OBJS = test-passwd.o $(LOCK_OBJS_1) $(PASSWD_OBJS) \
- subprocs.o setuid.o splash.o prefs.o mlstring.o exec.o \
- $(SAVER_UTIL_OBJS)
-test-passwd.o: XScreenSaver_ad.h
+##############################################################################
+#
+# Debugging utilities, not built by default
+#
+##############################################################################
-test-passwd: $(TEST_PASSWD_OBJS) XScreenSaver_ad.h
- $(CC) $(LDFLAGS) -o $@ $(TEST_PASSWD_OBJS) $(SAVER_LIBS)
+TESTPASS_OBJS = test-passwd.o test-passwd-b.o $(AUTH_OBJS_1)
+TESTPATH_DEFS = -Dxscreensaver_auth_conv=test_auth_conv $(AUTH_DEFS)
+test-passwd-b.o: XScreenSaver_ad.h
+test-passwd-b.o: $(srcdir)/xscreensaver-auth.c
+ $(CC) -c $(CC_ALL) $(TESTPATH_DEFS) $< -o $@
+test-passwd: $(TESTPASS_OBJS)
+ $(CC) $(LDFLAGS) -o $@ $(TESTPASS_OBJS) $(AUTH_LIBS)
test-uid: test-uid.o
$(CC) $(LDFLAGS) -o $@ test-uid.o
-test-xdpms: test-xdpms.o
- $(CC) $(LDFLAGS) -o $@ test-xdpms.o $(LIBS) $(X_LIBS) $(XDPMS_LIBS) \
- $(X_PRE_LIBS) -lXt -lX11 -lXext $(X_EXTRA_LIBS)
+TESTDPMS_LIBS = $(LIBS_PRE) $(XDPMS_LIBS) -lXt -lX11 -lXext $(LIBS_POST)
+test-xdpms: test-xdpms.o blurb.o
+ $(CC) $(LDFLAGS) -o $@ test-xdpms.o blurb.o $(TESTDPMS_LIBS)
-test-xinerama: test-xinerama.o
- $(CC) $(LDFLAGS) -o $@ test-xinerama.o $(LIBS) $(X_LIBS) $(SAVER_LIBS) \
- $(X_PRE_LIBS) $(XINERAMA_LIBS) -lXt -lX11 -lXext $(X_EXTRA_LIBS)
+TESTXINPUT_OBJS = test-xinput.o blurb.o xinput.o
+TESTXINPUT_LIBS = $(LIBS_PRE) $(XDPMS_LIBS) -lXi -lXt -lX11 -lXext $(LIBS_POST)
+test-xinput: $(TESTXINPUT_OBJS)
+ $(CC) $(LDFLAGS) -o $@ $(TESTXINPUT_OBJS) $(TESTXINPUT_LIBS)
-test-vp: test-vp.o
- $(CC) $(LDFLAGS) -o $@ test-vp.o $(LIBS) $(X_LIBS) $(SAVER_LIBS) \
- $(X_PRE_LIBS) -lXt -lX11 -lXext $(X_EXTRA_LIBS)
+TESTXIN_LIBS = $(LIBS_PRE) $(XINERAMA_LIBS) -lXi -lXt -lX11 -lXext $(LIBS_POST)
+test-xinerama: test-xinerama.o blurb.o
+ $(CC) $(LDFLAGS) -o $@ test-xinerama.o blurb.o $(TESTXIN_LIBS)
-test-randr: test-randr.o
- $(CC) $(LDFLAGS) -o $@ test-randr.o $(LIBS) $(X_LIBS) $(SAVER_LIBS) \
- $(X_PRE_LIBS) -lXt -lX11 -lXext $(X_EXTRA_LIBS)
+TESTXKB_OBJS = test-xkb.o blurb.o
+TESTXKB_LIBS = $(LIBS_PRE) $(XDPMS_LIBS) -lXi -lXt -lX11 -lXext $(LIBS_POST)
+test-xkb: $(TESTXKB_OBJS)
+ $(CC) $(LDFLAGS) -o $@ $(TESTXKB_OBJS) $(TESTXKB_LIBS)
-test-grab: test-grab.o
- $(CC) $(LDFLAGS) -o $@ test-grab.o $(SAVER_LIBS)
+test-vp: test-vp.o blurb.o
+ $(CC) $(LDFLAGS) -o $@ test-vp.o blurb.o $(GFX_LIBS)
-test-apm: test-apm.o
- $(CC) $(LDFLAGS) -o $@ test-apm.o $(SAVER_LIBS) -lapm
+test-randr: test-randr.o blurb.o
+ $(CC) $(LDFLAGS) -o $@ test-randr.o blurb.o $(GFX_LIBS)
-test-mlstring.o: mlstring.c
-test-mlstring: test-mlstring.o
- $(CC) -DTEST $(LDFLAGS) -o $@ test-mlstring.o $(SAVER_LIBS)
+TESTGRAB_LIBS = $(LIBS_PRE) -lXt -lX11 -lXext $(LIBS_POST)
+test-grab: test-grab.o blurb.o
+ $(CC) $(LDFLAGS) -o $@ test-grab.o blurb.o $(TESTGRAB_LIBS)
-TEST_FADE_OBJS = test-fade.o $(UTILS_BIN)/fade.o $(DEMO_UTIL_OBJS)
-test-fade: test-fade.o $(UTILS_BIN)/fade.o
- $(CC) $(LDFLAGS) -o $@ $(TEST_FADE_OBJS) $(SAVER_LIBS)
+TEST_FADE_OBJS = test-fade.o fade.o blurb.o atoms.o clientmsg.o xinput.o \
+ $(UTILS_BIN)/visual.o $(UTILS_BIN)/resources.o $(UTILS_BIN)/usleep.o \
+ $(UTILS_BIN)/logo.o $(UTILS_BIN)/minixpm.o $(UTILS_BIN)/xshm.o \
+ $(UTILS_BIN)/xmu.o $(UTILS_BIN)/aligned_malloc.o
+test-fade: $(TEST_FADE_OBJS)
+ $(CC) $(LDFLAGS) -o $@ $(TEST_FADE_OBJS) $(GFX_LIBS)
-TEST_SCREENS_OBJS = test-screens.o $(DEMO_UTIL_OBJS)
-test-screens.o: screens.c
-test-screens: test-screens.o
- $(CC) $(LDFLAGS) -o $@ $(TEST_SCREENS_OBJS) $(SAVER_LIBS)
+TEST_SCREENS_OBJS = test-screens.o screens.o blurb.o
+test-screens: $(TEST_SCREENS_OBJS)
+ $(CC) $(LDFLAGS) -o $@ $(TEST_SCREENS_OBJS) $(GFX_LIBS)
-test-yarandom: test-yarandom.o
- $(CC) -DTEST $(LDFLAGS) -o $@ test-yarandom.o $(UTILS_BIN)/yarandom.o
+test-yarandom: test-yarandom.o blurb.o
+ $(CC) -DTEST $(LDFLAGS) -o $@ test-yarandom.o blurb.o $(UTILS_BIN)/yarandom.o
+XDPY_DEFS = -DHAVE_GLX $(CPPFLAGS) $(CFLAGS) $(X_CFLAGS)
+XDPY_LIBS = $(LIBS_PRE) -lGL -lX11 -lXext $(LIBS_POST)
xdpyinfo.o: xdpyinfo.c
- $(CC) -c $(INCLUDES) -DHAVE_GLX $(CPPFLAGS) $(CFLAGS) $(X_CFLAGS) \
- $(srcdir)/xdpyinfo.c
-
+ $(CC) -c $(INCLUDES) $(XDPY_DEFS) $(srcdir)/xdpyinfo.c
xdpyinfo: xdpyinfo.o
- $(CC) $(LDFLAGS) -o $@ xdpyinfo.o \
- $(LIBS) $(X_LIBS) @GL_LIBS@ \
- $(X_PRE_LIBS) -lX11 -lXext $(X_EXTRA_LIBS) -lm
-
+ $(CC) $(LDFLAGS) -o $@ xdpyinfo.o $(XDPY_LIBS)
##############################################################################
#
@@ -900,150 +996,89 @@ demo-Gtk-conf.o: ../config.h
demo-Gtk-conf.o: $(srcdir)/demo-Gtk-conf.h
demo-Gtk-conf.o: $(UTILS_SRC)/xscreensaver-intl.h
demo-Gtk.o: XScreenSaver_ad.h
+demo-Gtk.o: $(srcdir)/atoms.h
+demo-Gtk.o: $(srcdir)/blurb.h
demo-Gtk.o: ../config.h
demo-Gtk.o: $(srcdir)/demo-Gtk-conf.h
-demo-Gtk.o: $(srcdir)/prefs.h
demo-Gtk.o: $(srcdir)/remote.h
demo-Gtk.o: $(srcdir)/types.h
demo-Gtk.o: $(UTILS_SRC)/resources.h
demo-Gtk.o: $(UTILS_SRC)/usleep.h
demo-Gtk.o: $(UTILS_SRC)/version.h
demo-Gtk.o: $(UTILS_SRC)/visual.h
+demo-Gtk.o: $(UTILS_SRC)/xmu.h
demo-Gtk.o: $(UTILS_SRC)/xscreensaver-intl.h
+demo-Xm.o: XScreenSaver_Xm_ad.h
+demo-Xm.o: XScreenSaver_ad.h
+demo-Xm.o: $(srcdir)/atoms.h
+demo-Xm.o: $(srcdir)/blurb.h
demo-Xm.o: ../config.h
+demo-Xm.o: $(srcdir)/remote.h
+demo-Xm.o: $(srcdir)/types.h
+demo-Xm.o: $(UTILS_SRC)/resources.h
+demo-Xm.o: $(UTILS_SRC)/version.h
+demo-Xm.o: $(UTILS_SRC)/visual.h
+demo-Xm.o: $(UTILS_SRC)/xmu.h
demo-Xm-widgets.o: ../config.h
-dpms.o: ../config.h
-dpms.o: $(srcdir)/prefs.h
-dpms.o: $(srcdir)/types.h
-dpms.o: $(srcdir)/xscreensaver.h
-exec.o: ../config.h
-exec.o: $(srcdir)/exec.h
-lock.o: $(srcdir)/auth.h
-lock.o: ../config.h
-lock.o: $(srcdir)/mlstring.h
-lock.o: $(srcdir)/prefs.h
-lock.o: $(srcdir)/types.h
-lock.o: $(UTILS_SRC)/resources.h
-lock.o: $(srcdir)/xscreensaver.h
-mlstring.o: $(srcdir)/mlstring.h
+passwd-helper.o: $(srcdir)/auth.h
+passwd-helper.o: $(srcdir)/blurb.h
passwd-helper.o: ../config.h
-passwd-helper.o: $(srcdir)/prefs.h
-passwd-helper.o: $(srcdir)/types.h
-passwd-helper.o: $(srcdir)/xscreensaver.h
+passwd-kerberos.o: $(srcdir)/auth.h
+passwd-kerberos.o: $(srcdir)/blurb.h
passwd-kerberos.o: ../config.h
-passwd.o: $(srcdir)/auth.h
-passwd.o: ../config.h
-passwd.o: $(srcdir)/prefs.h
-passwd.o: $(srcdir)/types.h
-passwd.o: $(srcdir)/xscreensaver.h
passwd-pam.o: $(srcdir)/auth.h
+passwd-pam.o: $(srcdir)/blurb.h
passwd-pam.o: ../config.h
-passwd-pam.o: $(srcdir)/types.h
+passwd-pwent.o: $(srcdir)/auth.h
+passwd-pwent.o: $(srcdir)/blurb.h
passwd-pwent.o: ../config.h
-prefs.o: ../config.h
-prefs.o: $(srcdir)/prefs.h
-prefs.o: $(srcdir)/types.h
-prefs.o: $(UTILS_SRC)/resources.h
-prefs.o: $(UTILS_SRC)/version.h
+remote.o: $(srcdir)/atoms.h
+remote.o: $(srcdir)/blurb.h
+remote.o: $(srcdir)/clientmsg.h
remote.o: ../config.h
remote.o: $(srcdir)/remote.h
-screens.o: ../config.h
-screens.o: $(srcdir)/prefs.h
-screens.o: $(srcdir)/types.h
-screens.o: $(UTILS_SRC)/visual.h
-screens.o: $(srcdir)/xscreensaver.h
-setuid.o: ../config.h
-setuid.o: $(srcdir)/prefs.h
-setuid.o: $(srcdir)/types.h
-setuid.o: $(srcdir)/xscreensaver.h
-splash.o: ../config.h
-splash.o: $(srcdir)/prefs.h
-splash.o: $(srcdir)/types.h
-splash.o: $(UTILS_SRC)/font-retry.h
-splash.o: $(UTILS_SRC)/resources.h
-splash.o: $(srcdir)/xscreensaver.h
-stderr.o: ../config.h
-stderr.o: $(srcdir)/prefs.h
-stderr.o: $(srcdir)/types.h
-stderr.o: $(UTILS_SRC)/resources.h
-stderr.o: $(UTILS_SRC)/visual.h
-stderr.o: $(srcdir)/xscreensaver.h
-subprocs.o: ../config.h
-subprocs.o: $(srcdir)/exec.h
-subprocs.o: $(srcdir)/prefs.h
-subprocs.o: $(srcdir)/types.h
-subprocs.o: $(UTILS_SRC)/visual.h
-subprocs.o: $(UTILS_SRC)/yarandom.h
-subprocs.o: $(srcdir)/xscreensaver.h
-test-apm.o: ../config.h
+test-fade.o: $(srcdir)/atoms.h
+test-fade.o: $(srcdir)/blurb.h
test-fade.o: ../config.h
-test-fade.o: $(srcdir)/prefs.h
+test-fade.o: $(srcdir)/fade.h
+test-fade.o: $(srcdir)/screens.h
test-fade.o: $(srcdir)/types.h
-test-fade.o: $(UTILS_SRC)/fade.h
+test-fade.o: $(UTILS_SRC)/resources.h
test-fade.o: $(srcdir)/xscreensaver.h
+test-grab.o: $(srcdir)/blurb.h
test-grab.o: ../config.h
-test-mlstring.o: $(srcdir)/mlstring.c
-test-mlstring.o: $(srcdir)/mlstring.h
-test-passwd.o: XScreenSaver_ad.h
test-passwd.o: $(srcdir)/auth.h
+test-passwd.o: $(srcdir)/blurb.h
test-passwd.o: ../config.h
-test-passwd.o: $(srcdir)/prefs.h
-test-passwd.o: $(srcdir)/types.h
-test-passwd.o: $(UTILS_SRC)/resources.h
-test-passwd.o: $(UTILS_SRC)/version.h
-test-passwd.o: $(UTILS_SRC)/visual.h
-test-passwd.o: $(srcdir)/xscreensaver.h
+test-randr.o: $(srcdir)/blurb.h
test-randr.o: ../config.h
+test-screens.o: $(srcdir)/blurb.h
test-screens.o: ../config.h
-test-screens.o: $(srcdir)/prefs.h
-test-screens.o: $(srcdir)/screens.c
-test-screens.o: $(srcdir)/types.h
+test-screens.o: $(srcdir)/screens.h
test-screens.o: $(UTILS_SRC)/visual.h
-test-screens.o: $(srcdir)/xscreensaver.h
test-uid.o: ../config.h
+test-vp.o: $(srcdir)/blurb.h
test-vp.o: ../config.h
+test-xdpms.o: $(srcdir)/blurb.h
test-xdpms.o: ../config.h
+test-xinerama.o: $(srcdir)/blurb.h
test-xinerama.o: ../config.h
+test-xinput.o: $(srcdir)/blurb.h
+test-xinput.o: ../config.h
+test-xinput.o: $(srcdir)/xinput.h
+test-xkb.o: $(srcdir)/blurb.h
+test-xkb.o: ../config.h
+test-yarandom.o: $(srcdir)/blurb.h
test-yarandom.o: ../config.h
test-yarandom.o: $(UTILS_SRC)/yarandom.h
-timers.o: ../config.h
-timers.o: $(srcdir)/prefs.h
-timers.o: $(srcdir)/types.h
-timers.o: $(srcdir)/xscreensaver.h
-windows.o: ../config.h
-windows.o: $(srcdir)/prefs.h
-windows.o: $(srcdir)/types.h
-windows.o: $(UTILS_SRC)/fade.h
-windows.o: $(UTILS_SRC)/visual.h
-windows.o: $(srcdir)/xscreensaver.h
+xscreensaver-command.o: $(srcdir)/atoms.h
+xscreensaver-command.o: $(srcdir)/blurb.h
xscreensaver-command.o: ../config.h
xscreensaver-command.o: $(srcdir)/remote.h
xscreensaver-command.o: $(UTILS_SRC)/version.h
-xscreensaver-getimage.o: ../config.h
-xscreensaver-getimage.o: XScreenSaver_ad.h
-xscreensaver-getimage.o: $(srcdir)/prefs.h
-xscreensaver-getimage.o: $(srcdir)/types.h
-xscreensaver-getimage.o: $(UTILS_SRC)/colorbars.h
-xscreensaver-getimage.o: $(UTILS_SRC)/grabscreen.h
-xscreensaver-getimage.o: $(UTILS_SRC)/resources.h
-xscreensaver-getimage.o: $(UTILS_SRC)/utils.h
-xscreensaver-getimage.o: $(UTILS_SRC)/version.h
-xscreensaver-getimage.o: $(UTILS_SRC)/visual.h
-xscreensaver-getimage.o: $(UTILS_SRC)/vroot.h
-xscreensaver-getimage.o: $(UTILS_SRC)/yarandom.h
-xscreensaver.o: XScreenSaver_ad.h
-xscreensaver.o: $(srcdir)/auth.h
-xscreensaver.o: ../config.h
-xscreensaver.o: $(srcdir)/prefs.h
-xscreensaver.o: $(srcdir)/types.h
-xscreensaver.o: $(UTILS_SRC)/resources.h
-xscreensaver.o: $(UTILS_SRC)/usleep.h
-xscreensaver.o: $(UTILS_SRC)/version.h
-xscreensaver.o: $(UTILS_SRC)/visual.h
-xscreensaver.o: $(UTILS_SRC)/yarandom.h
-xscreensaver.o: $(srcdir)/xscreensaver.h
-xset.o: ../config.h
-xset.o: $(srcdir)/prefs.h
-xset.o: $(srcdir)/types.h
-xset.o: $(srcdir)/xscreensaver.h
+xscreensaver-systemd.o: $(srcdir)/blurb.h
+xscreensaver-systemd.o: ../config.h
+xscreensaver-systemd.o: $(UTILS_SRC)/queue.h
+xscreensaver-systemd.o: $(UTILS_SRC)/version.h
+xscreensaver-systemd.o: $(UTILS_SRC)/yarandom.h
diff --git a/driver/XScreenSaver-Xm.ad b/driver/XScreenSaver-Xm.ad
index 6b04ae9..560c48e 100644
--- a/driver/XScreenSaver-Xm.ad
+++ b/driver/XScreenSaver-Xm.ad
@@ -1,4 +1,4 @@
-! Resources for the Motif dialog boxes of the "xscreensaver-demo" program.
+! Resources for the Motif dialog boxes of the "xscreensaver-settings" program.
!
*fontList: *-helvetica-medium-r-*-*-*-120-*-*-*-iso8859-1
*demoDialog*label1.fontList: *-helvetica-medium-r-*-*-*-140-*-*-*-iso8859-1
diff --git a/driver/XScreenSaver.ad.in b/driver/XScreenSaver.ad.in
index cfdd1a2..bfc6414 100644
--- a/driver/XScreenSaver.ad.in
+++ b/driver/XScreenSaver.ad.in
@@ -4,8 +4,8 @@
! a screen saver and locker for the X window system
! by Jamie Zawinski
!
-! version 5.44
-! 20-Mar-2020
+! version 6.00
+! 01-Apr-2021
!
! See "man xscreensaver" for more info. The latest version is always
! available at https://www.jwz.org/xscreensaver/
@@ -47,9 +47,8 @@
*memoryLimit: 0
*lock: False
*verbose: False
-*timestamp: True
*fade: True
-*unfade: False
+*unfade: True
*fadeSeconds: 0:00:03
*fadeTicks: 20
*splash: True
@@ -65,32 +64,11 @@
*textProgram: fortune
*textURL: https://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss
-! When a saver writes an error message to stdout/stderr, it can be printed
-! on the screen.
-!
-*captureStderr: True
-*overlayStderr: True
-*overlayTextForeground: #FFFF00
-*overlayTextBackground: #000000
-*font: *-medium-r-*-140-*-m-*
-
-! The default is to use these server extensions if available (as noted.)
-*sgiSaverExtension: True
-*xidleExtension: True
-*procInterrupts: True
-
-! Turning this on makes pointerHysteresis not work.
-*xinputExtensionDev: False
-
-! Set this to True if you are experiencing longstanding XFree86 bug #421
-! (xscreensaver not covering the whole screen)
-*GetViewPortIsFullOfLies: False
-
! This is what the "Settings" button on the splash screen runs.
-*demoCommand: xscreensaver-demo
+*demoCommand: xscreensaver-settings
! This is the URL loaded by the "Help" button on the splash screen,
-! and by the "Documentation" menu item in xscreensaver-demo.
+! and by the "Documentation" menu item in xscreensaver-settings.
*helpURL: https://www.jwz.org/xscreensaver/man.html
! loadURL -- how the "Help" buttons load the helpURL (/bin/sh syntax.)
@@ -98,11 +76,13 @@
!
! And there are so very many options to choose from!
!
-! Gnome 2.4, 2.6: (yelp can't display man pages, as of 2.6.3)
+! Gnome 2.4, 2.6:
!
@GNOME24@*loadURL: @WITH_BROWSER@ '%s'
-@GNOME24@*manualCommand: gnome-terminal --title '%s manual' \
-@GNOME24@ --command '/bin/sh -c "man %s; read foo"'
+@GNOME24@*manualCommand: yelp man:%s || \
+@GNOME24@ x-terminal-emulator -t '%s manual' \
+@GNOME24@ -e /bin/sh -c "man %s; read foo"
+
!
! Gnome 2.2:
!
@@ -117,21 +97,21 @@
!
! non-Gnome systems:
!
-@NOGNOME@*loadURL: firefox '%s' || mozilla '%s' || netscape '%s'
+@NOGNOME@*loadURL: x-www-browser '%s' || firefox '%s' || chromium-browser '%s'
@NOGNOME@*manualCommand: xterm -sb -fg black -bg gray75 -T '%s manual' \
@NOGNOME@ -e /bin/sh -c 'man "%s" ; read foo'
-! The format used for printing the date and time in the password dialog box
-! (see the strftime(3) manual page for details.)
-*dateFormat: %d-%b-%y (%a); %I:%M %p
-! For day month date:
-! *dateFormat: %a %b %d, %I:%M %p
-! To show the time only:
+! The strftime(3) format string for printing the time on the password dialog.
+*dateFormat: %I:%M %p, %a %b %e
+! DD MMM:
+! *dateFormat: %I:%M %p, %e %b (%a)
+! 12 hour time only:
! *dateFormat: %I:%M %p
-! For 24 hour time:
+! 24 hour time only:
! *dateFormat: %H:%M
-
+! ISO 8601:
+! *dateFormat: %Y-%m-%d %H:%M:%S
! This command is executed by the "New Login" button on the lock dialog.
! (That button does not appear on the dialog if this program does not exist.)
@@ -141,13 +121,11 @@
!
@NEW_LOGIN_COMMAND_P@*newLoginCommand: @NEW_LOGIN_COMMAND@
-
-! Change these at your peril:
-!
-XScreenSaver.pointerPollTime: 0:00:05
+! Mouse motions less than this many pixels per second are ignored.
+! This helps with cats, trucks and earthquakes.
XScreenSaver.pointerHysteresis: 10
-XScreenSaver.initialDelay: 0:00:00
-XScreenSaver.windowCreationTimeout: 0:00:30
+
+! Change this at your peril:
XScreenSaver.bourneShell: /bin/sh
@@ -157,176 +135,179 @@ XScreenSaver.bourneShell: /bin/sh
!
!=============================================================================
-! Note, the daemon uses Xlib XLoadFont, not Xft. If these fonts don't
-! exist, arcane heuristics are applied until we find something similar.
-!
-*Dialog.headingFont: -*-helvetica-bold-r-*-*-*-180-*-*-*-*-iso8859-1
-*Dialog.bodyFont: -*-helvetica-bold-r-*-*-*-140-*-*-*-*-iso8859-1
-*Dialog.labelFont: -*-helvetica-bold-r-*-*-*-140-*-*-*-*-iso8859-1
-*Dialog.unameFont: -*-helvetica-bold-r-*-*-*-120-*-*-*-*-iso8859-1
-*Dialog.buttonFont: -*-helvetica-bold-r-*-*-*-140-*-*-*-*-iso8859-1
-*Dialog.dateFont: -*-helvetica-medium-r-*-*-*-80-*-*-*-*-iso8859-1
-
-! Helvetica asterisks look terrible.
-*passwd.passwdFont: -*-courier-bold-r-*-*-*-140-*-*-*-iso8859-1
-
-! Whether to display the local host name in the unlock dialog.
-*passwd.uname: True
-
-! Whether typed passwords should echo as asterisks (true) or nothing (false)
-*passwd.asterisks: True
-
+! Which of the following color schemes is in use for the unlock dialog.
+*dialogTheme: default
-! The default color scheme for the unlock and splash dialogs.
-! This looks pretty close to the default Gtk theme.
-!
-*Dialog.foreground: #000000
-*Dialog.background: #E6E6E6
-*Dialog.Button.foreground: #000000
-*Dialog.Button.background: #F5F5F5
-*Dialog.text.foreground: #000000
-*Dialog.text.background: #FFFFFF
-*Dialog.topShadowColor: #FFFFFF
-*Dialog.bottomShadowColor: #CECECE
-*Dialog.logo.width: 210
-*Dialog.logo.height: 210
-*Dialog.internalBorderWidth: 24
-*Dialog.borderWidth: 1
-*Dialog.shadowThickness: 2
-*passwd.thermometer.foreground: #4464AC
-*passwd.thermometer.background: #FFFFFF
-*passwd.thermometer.width: 8
+! Resources for theme names are downcased with spaces stripped.
+*themeNames: Default, Borderless, Dark Gray, Borderless Black, \
+ Green Black, White, Blue, Aqua Black, Wine
+*Dialog.headingFont: sans-serif bold 16
+*Dialog.bodyFont: sans-serif 14
+*Dialog.errorFont: sans-serif bold 14
+*Dialog.labelFont: sans-serif bold 14
+*Dialog.unameFont: sans-serif 12
+*Dialog.buttonFont: sans-serif bold 14
+*Dialog.dateFont: sans-serif 9
-! A few other example color schemes.
-!
-! A convenient way to debug these is to build and run "driver/test-passwd".
-! Remember that configure overwrites XScreenSaver.ad from XScreenSaver.ad.in.
-
+! Whether to display the local host name in the unlock dialog.
+*passwd.uname: True
+
+! Whether typed passwords should echo as asterisks, or as nothing.
+*passwd.asterisks: True
+
+! The default theme is similar to the Gtk defaults.
+!
+*default.Dialog.foreground: #000000
+*default.Dialog.background: #E6E6E6
+*default.Dialog.button.foreground: #000000
+*default.Dialog.button.background: #F5F5F5
+*default.Dialog.logo.background: #BBBBBB
+*default.Dialog.text.foreground: #000000
+*default.Dialog.error.foreground: #FF0000
+*default.Dialog.text.background: #FFFFFF
+*default.Dialog.topShadowColor: #FFFFFF
+*default.Dialog.bottomShadowColor: #CECECE
+*default.Dialog.shadowWidth: 2
+*default.Dialog.logo.width: 210
+*default.Dialog.logo.height: 210
+*default.Dialog.thermometer.foreground: #4464AC
+*default.Dialog.thermometer.background: #FFFFFF
+*default.Dialog.thermometer.width: 8
+*default.Dialog.borderColor: #CECECE
+*default.Dialog.borderWidth: 0
+*default.Dialog.internalPadding: 24
! Borderless theme:
!
-! *Dialog.topShadowColor: #E6E6E6
-! *Dialog.bottomShadowColor: #E6E6E6
-! *passwd.thermometer.width: 6
-
+*borderless.Dialog.topShadowColor: #E6E6E6
+*borderless.Dialog.button.background: #FFFFFF
+*borderless.Dialog.bottomShadowColor: #E6E6E6
+*borderless.Dialog.logo.background: #E6E6E6
+*borderless.Dialog.borderColor: #888888
+*borderless.Dialog.thermometer.width: 6
+*borderless.Dialog.borderWidth: 1
! Dark gray theme:
!
-! *Dialog.foreground: #CCCCCC
-! *Dialog.background: #333333
-! *Dialog.topShadowColor: #444444
-! *Dialog.bottomShadowColor: #111111
-! *Dialog.text.foreground: #DDDDDD
-! *Dialog.text.background: #666666
-! *Dialog.Button.foreground: #CCCCCC
-! *Dialog.Button.background: #666666
-! *passwd.thermometer.foreground: #4464AC
-! *passwd.thermometer.background: #666666
-
-
-! Black borderless theme:
-!
-! *Dialog.foreground: #CCCCCC
-! *Dialog.background: #000000
-! *Dialog.topShadowColor: #000000
-! *Dialog.bottomShadowColor: #000000
-! *Dialog.text.foreground: #CCCCCC
-! *Dialog.text.background: #000000
-! *Dialog.Button.foreground: #CCCCCC
-! *Dialog.Button.background: #333333
-! *passwd.thermometer.foreground: #CCCCCC
-! *passwd.thermometer.background: #333333
-! *passwd.thermometer.width: 3
-
+*darkgray.Dialog.foreground: #CCCCCC
+*darkgray.Dialog.background: #333333
+*darkgray.Dialog.topShadowColor: #444444
+*darkgray.Dialog.bottomShadowColor: #111111
+*darkgray.Dialog.borderColor: #111111
+*darkgray.Dialog.text.foreground: #DDDDDD
+*darkgray.Dialog.text.background: #666666
+*darkgray.Dialog.button.foreground: #CCCCCC
+*darkgray.Dialog.button.background: #666666
+*darkgray.Dialog.logo.background: #444444
+*darkgray.Dialog.thermometer.foreground: #4464AC
+*darkgray.Dialog.thermometer.background: #666666
+*darkgray.Dialog.borderWidth: 0
+
+! Borderless black theme:
+!
+*borderlessblack.Dialog.foreground: #CCCCCC
+*borderlessblack.Dialog.background: #000000
+*borderlessblack.Dialog.topShadowColor: #000000
+*borderlessblack.Dialog.bottomShadowColor: #000000
+*borderlessblack.Dialog.text.foreground: #CCCCCC
+*borderlessblack.Dialog.text.background: #000000
+*borderlessblack.Dialog.button.foreground: #CCCCCC
+*borderlessblack.Dialog.button.background: #333333
+*borderlessblack.Dialog.logo.background: #000000
+*borderlessblack.Dialog.thermometer.foreground: #CCCCCC
+*borderlessblack.Dialog.thermometer.background: #333333
+*borderlessblack.Dialog.thermometer.width: 3
+*borderlessblack.Dialog.borderColor: #333333
+*borderlessblack.Dialog.borderWidth: 1
! Green on black theme:
!
-! *Dialog.foreground: #00FF00
-! *Dialog.background: #000000
-! *Dialog.topShadowColor: #000000
-! *Dialog.bottomShadowColor: #000000
-! *Dialog.shadowThickness: 1
-! *Dialog.text.foreground: #00FF00
-! *Dialog.text.background: #006600
-! *Dialog.Button.foreground: #00FF00
-! *Dialog.Button.background: #006600
-! *passwd.thermometer.foreground: #00CC00
-! *passwd.thermometer.background: #006600
-
+*greenblack.Dialog.foreground: #00FF00
+*greenblack.Dialog.background: #000000
+*greenblack.Dialog.topShadowColor: #000000
+*greenblack.Dialog.bottomShadowColor: #000000
+*greenblack.Dialog.shadowWidth: 1
+*greenblack.Dialog.text.foreground: #00FF00
+*greenblack.Dialog.text.background: #006600
+*greenblack.Dialog.button.foreground: #00FF00
+*greenblack.Dialog.button.background: #006600
+*greenblack.Dialog.logo.background: #000000
+*greenblack.Dialog.thermometer.foreground: #00CC00
+*greenblack.Dialog.thermometer.background: #006600
+*greenblack.Dialog.borderColor: #006600
+*greenblack.Dialog.borderWidth: 1
! White theme:
!
-! *Dialog.foreground: #000000
-! *Dialog.background: #FFFFFF
-! *Dialog.topShadowColor: #CCCCCC
-! *Dialog.bottomShadowColor: #CCCCCC
-! *Dialog.shadowThickness: 1
-! *Dialog.text.foreground: #000000
-! *Dialog.text.background: #FFFFFF
-! *Dialog.Button.foreground: #000000
-! *Dialog.Button.background: #FFFFFF
-
+*white.Dialog.foreground: #000000
+*white.Dialog.background: #FFFFFF
+*white.Dialog.topShadowColor: #CCCCCC
+*white.Dialog.bottomShadowColor: #CCCCCC
+*white.Dialog.shadowWidth: 1
+*white.Dialog.borderColor: #CCCCCC
+*white.Dialog.text.foreground: #000000
+*white.Dialog.text.background: #FFFFFF
+*white.Dialog.button.foreground: #000000
+*white.Dialog.button.background: #FFFFFF
+*white.Dialog.logo.background: #FFFFFF
+*white.Dialog.borderWidth: 0
! Blue theme:
!
-! *Dialog.foreground: #000000
-! *Dialog.background: #BBCCDD
-! *Dialog.topShadowColor: #CCDDEE
-! *Dialog.bottomShadowColor: #AABBCC
-! *Dialog.text.foreground: #000000
-! *Dialog.text.background: #DDEEFF
-! *Dialog.Button.foreground: #000000
-! *Dialog.Button.background: #DDEEFF
-! *passwd.thermometer.foreground: #5566AA
-! *passwd.thermometer.background: #BBCCDD
-
+*blue.Dialog.foreground: #000000
+*blue.Dialog.background: #BBCCDD
+*blue.Dialog.topShadowColor: #CCDDEE
+*blue.Dialog.bottomShadowColor: #AABBCC
+*blue.Dialog.borderColor: #AABBCC
+*blue.Dialog.text.foreground: #000000
+*blue.Dialog.text.background: #DDEEFF
+*blue.Dialog.button.foreground: #000000
+*blue.Dialog.button.background: #DDEEFF
+*blue.Dialog.logo.background: #BBCCDD
+*blue.Dialog.thermometer.foreground: #5566AA
+*blue.Dialog.thermometer.background: #BBCCDD
+*blue.Dialog.borderWidth: 0
! Aqua on black borderless theme:
!
-! *Dialog.foreground: #00EFEF
-! *Dialog.background: #000000
-! *Dialog.topShadowColor: #000000
-! *Dialog.bottomShadowColor: #000000
-! *Dialog.Button.foreground: #000000
-! *Dialog.Button.background: #2244EE
-! *Dialog.text.foreground: #2244EE
-! *Dialog.text.background: #EEEEEE
-! *Dialog.internalBorderWidth: 36
-! *Dialog.borderWidth: 4
-! *Dialog.shadowThickness: 2
-! *passwd.thermometer.foreground: #2244EE
-! *passwd.thermometer.background: #000088
-
+*aquablack.Dialog.foreground: #00EFEF
+*aquablack.Dialog.background: #000000
+*aquablack.Dialog.topShadowColor: #000000
+*aquablack.Dialog.bottomShadowColor: #000000
+*aquablack.Dialog.shadowWidth: 2
+*aquablack.Dialog.button.foreground: #000000
+*aquablack.Dialog.button.background: #2244EE
+*aquablack.Dialog.logo.background: #000000
+*aquablack.Dialog.text.foreground: #2244EE
+*aquablack.Dialog.text.background: #EEEEEE
+*aquablack.Dialog.thermometer.foreground: #2244EE
+*aquablack.Dialog.thermometer.background: #000088
+*aquablack.Dialog.borderColor: #000066
+*aquablack.Dialog.borderWidth: 1
+*aquablack.Dialog.internalPadding: 36
! Wine theme, similar to the login screen of "Ubuntu 18.04 Community".
!
-! *Dialog.foreground: #AD8FA6
-! *Dialog.background: #2C041E
-! *Dialog.topShadowColor: #2C041E
-! *Dialog.bottomShadowColor: #2C041E
-! *Dialog.text.foreground: #706B70
-! *Dialog.text.background: #F9F9F8
-! *Dialog.Button.foreground: #CFC8CB
-! *Dialog.Button.background: #4D2946
-! *passwd.thermometer.foreground: #AD8FA6
-! *passwd.thermometer.background: #4D2946
-! *passwd.thermometer.width: 6
+*wine.Dialog.foreground: #AD8FA6
+*wine.Dialog.background: #2C041E
+*wine.Dialog.topShadowColor: #2C041E
+*wine.Dialog.bottomShadowColor: #2C041E
+*wine.Dialog.text.foreground: #706B70
+*wine.Dialog.text.background: #F9F9F8
+*wine.Dialog.button.foreground: #5F585B
+*wine.Dialog.logo.background: #2C041E
+*wine.Dialog.thermometer.foreground: #AD8FA6
+*wine.Dialog.thermometer.background: #4D2946
+*wine.Dialog.borderColor: #4D2946
+*wine.Dialog.thermometer.width: 6
+*wine.Dialog.borderWidth: 1
-! Static text in the dialog boxes:
+! For displaying error messages about crashed screen savers.
!
-*passwd.heading.label: XScreenSaver %s
-*passwd.body.label: This screen is locked.
-*passwd.unlock.label: OK
-*passwd.login.label: New Login
-*passwd.user.label: Username:
-
-*splash.heading.label: XScreenSaver %s
-*splash.body.label: Copyright \251 1991-2020 by
-*splash.body2.label: Jamie Zawinski <jwz@jwz.org>
-*splash.demo.label: Settings
-*splash.help.label: Help
+*errorFont: sans-serif bold 18
+*errorColor: #FF0000
!=============================================================================
@@ -337,7 +318,7 @@ XScreenSaver.bourneShell: /bin/sh
! If you want to disable a screensaver, DO NOT remove it from this list:
! instead, mark it as inactive by placing a "-" at the beginning of the line.
!
-! You can use the `xscreensaver-demo' program to edit the current list of
+! You can use the `xscreensaver-settings' program to edit the current list of
! screen savers interactively.
!
!=============================================================================
@@ -579,111 +560,132 @@ XScreenSaver.bourneShell: /bin/sh
vfeedback -root \n\
@GL_KLUDGE@ GL: deepstars -root \n\
@GL_KLUDGE@ GL: gravitywell -root \n\
+@GL_KLUDGE@ GL: beats -root \n\
+@GL_KLUDGE@ GL: covid19 -root \n\
@GL_KLUDGE@ GL: etruscanvenus -root \n\
-@GL_KLUDGE@ GL: gibson -root \n
+@GL_KLUDGE@ GL: gibson -root \n\
+@GL_KLUDGE@ GL: headroom -root \n\
+@GL_KLUDGE@ GL: sphereeversion -root \n
!=============================================================================
!
! Pretty names for the hacks that have unusual capitalization.
-! Used by xscreensaver-demo.
+! Used by xscreensaver-settings.
!
!=============================================================================
-*hacks.antinspect.name: AntInspect
-*hacks.antmaze.name: AntMaze
-*hacks.antspotlight.name: AntSpotlight
-*hacks.binaryring.name: BinaryRing
-*hacks.blinkbox.name: BlinkBox
-*hacks.blitspin.name: BlitSpin
-*hacks.blocktube.name: BlockTube
-*hacks.bouncingcow.name: BouncingCow
-*hacks.boxfit.name: BoxFit
+*hacks.antinspect.name: Ant Inspect
+*hacks.antmaze.name: Ant Maze
+*hacks.antspotlight.name: Ant Spotlight
+*hacks.apple2.name: Apple ][
+*hacks.binaryring.name: Binary Ring
+*hacks.blinkbox.name: Blink Box
+*hacks.blitspin.name: Blit Spin
+*hacks.blocktube.name: Block Tube
+*hacks.bouncingcow.name: Bouncing Cow
+*hacks.boxfit.name: Box Fit
*hacks.bsod.name: BSOD
-*hacks.bubble3d.name: Bubble3D
-*hacks.ccurve.name: CCurve
-*hacks.cloudlife.name: CloudLife
-*hacks.companioncube.name: CompanionCube
-*hacks.cubestack.name: CubeStack
-*hacks.cubestorm.name: CubeStorm
-*hacks.cubetwist.name: CubeTwist
-*hacks.cubicgrid.name: CubicGrid
-*hacks.cwaves.name: CWaves
-*hacks.dangerball.name: DangerBall
-*hacks.decayscreen.name: DecayScreen
-*hacks.deepstars.name: DeepStars
+*hacks.bubble3d.name: Bubble 3D
+*hacks.ccurve.name: C Curve
+*hacks.cityflow.name: City Flow
+*hacks.cloudlife.name: Cloud Life
+*hacks.companioncube.name: Companion Cube
+*hacks.covid19.name: COVID19
+*hacks.cube21.name: Cube 21
+*hacks.cubestack.name: Cube Stack
+*hacks.cubestorm.name: Cube Storm
+*hacks.cubetwist.name: Cube Twist
+*hacks.cubicgrid.name: Cubic Grid
+*hacks.cwaves.name: C Waves
+*hacks.dangerball.name: Danger Ball
+*hacks.decayscreen.name: Decay Screen
+*hacks.deepstars.name: Deep Stars
*hacks.dnalogo.name: DNA Logo
-*hacks.dymaxionmap.name: DymaxionMap
-*hacks.energystream.name: EnergyStream
-*hacks.etruscanvenus.name: EtruscanVenus
-*hacks.euler2d.name: Euler2D
-*hacks.fadeplot.name: FadePlot
-*hacks.filmleader.name: FilmLeader
-*hacks.flipflop.name: FlipFlop
-*hacks.flipscreen3d.name: FlipScreen3D
-*hacks.fliptext.name: FlipText
-*hacks.fluidballs.name: FluidBalls
-*hacks.flyingtoasters.name: FlyingToasters
-*hacks.fontglide.name: FontGlide
-*hacks.fuzzyflakes.name: FuzzyFlakes
-*hacks.geodesicgears.name: GeodesicGears
+*hacks.dymaxionmap.name: Dymaxion Map
+*hacks.energystream.name: Energy Stream
+*hacks.etruscanvenus.name: Etruscan Venus
+*hacks.euler2d.name: Euler 2D
+*hacks.fadeplot.name: Fade Plot
+*hacks.fiberlamp.name: Fiber Lamp
+*hacks.filmleader.name: Film Leader
+*hacks.flipflop.name: Flip Flop
+*hacks.flipscreen3d.name: Flip Screen 3D
+*hacks.fliptext.name: Flip Text
+*hacks.fluidballs.name: Fluid Balls
+*hacks.flyingtoasters.name: Flying Toasters
+*hacks.fontglide.name: Font Glide
+*hacks.fuzzyflakes.name: Fuzzy Flakes
+*hacks.geodesicgears.name: Geodesic Gears
*hacks.gflux.name: GFlux
+*hacks.glblur.name: GL Blur
+*hacks.glcells.name: GL Cells
*hacks.gleidescope.name: Gleidescope
-*hacks.glforestfire.name: GLForestFire
+*hacks.glforestfire.name: GL Forest Fire
+*hacks.glhanoi.name: GL Hanoi
*hacks.glitchpeg.name: GlitchPEG
-*hacks.gravitywell.name: GravityWell
-*hacks.hyperball.name: HyperBall
-*hacks.hypercube.name: HyperCube
+*hacks.glknots.name: GL Knots
+*hacks.glmatrix.name: GL Matrix
+*hacks.glplanet.name: GL Planet
+*hacks.glschool.name: GL School
+*hacks.glslideshow.name: GL Slideshow
+*hacks.glsnake.name: GL Snake
+*hacks.gltext.name: GL Text
+*hacks.gravitywell.name: Gravity Well
+*hacks.hexstrut.name: Hex Strut
*hacks.ifs.name: IFS
-*hacks.imsmap.name: IMSMap
-*hacks.jigglypuff.name: JigglyPuff
-*hacks.juggler3d.name: Juggler3D
-*hacks.lcdscrub.name: LCDscrub
+*hacks.imsmap.name: IMS Map
+*hacks.jigglypuff.name: Jiggly Puff
+*hacks.juggler3d.name: Juggler 3D
+*hacks.lcdscrub.name: LCD Scrub
*hacks.lmorph.name: LMorph
*hacks.m6502.name: m6502
-*hacks.maze3d.name: Maze3D
-*hacks.memscroller.name: MemScroller
-*hacks.metaballs.name: MetaBalls
-*hacks.mirrorblob.name: MirrorBlob
-*hacks.moebiusgears.name: MoebiusGears
-*hacks.morph3d.name: Morph3D
-*hacks.nerverot.name: NerveRot
-*hacks.noseguy.name: NoseGuy
-*hacks.popsquares.name: PopSquares
-*hacks.projectiveplane.name:ProjectivePlane
-*hacks.quasicrystal.name: QuasiCrystal
-*hacks.raverhoop.name: RaverHoop
-*hacks.razzledazzle.name: RazzleDazzle
-*hacks.rd-bomb.name: RDbomb
-*hacks.rdbomb.name: RDbomb
-*hacks.romanboy.name: RomanBoy
-*hacks.rotzoomer.name: RotZoomer
-*hacks.rubikblocks.name: RubikBlocks
+*hacks.maze3d.name: Maze 3D
+*hacks.memscroller.name: Mem Scroller
+*hacks.metaballs.name: Meta Balls
+*hacks.mirrorblob.name: Mirror Blob
+*hacks.moebius.name: Möbius
+*hacks.moebiusgears.name: Möbius Gears
+*hacks.moire.name: Moiré
+*hacks.moire2.name: Moiré 2
+*hacks.morph3d.name: Morph 3D
+*hacks.nerverot.name: Nerve Rot
+*hacks.noseguy.name: Nose Guy
+*hacks.pacman.name: Pac-Man
+*hacks.photopile.name: Photo Pile
+*hacks.popsquares.name: Pop Squares
+*hacks.projectiveplane.name:Projective Plane
+*hacks.quasicrystal.name: Quasi-Crystal
+*hacks.raverhoop.name: Raver Hoop
+*hacks.razzledazzle.name: Razzle Dazzle
+*hacks.rd-bomb.name: RD-Bomb
+*hacks.rd-bomb.name: RD-Bomb
+*hacks.romanboy.name: Roman Boy
+*hacks.rotzoomer.name: Rot Zoomer
+*hacks.rubikblocks.name: Rubik Blocks
*hacks.sballs.name: SBalls
-*hacks.shadebobs.name: ShadeBobs
-*hacks.sierpinski3d.name: Sierpinski3D
-*hacks.skytentacles.name: SkyTentacles
-*hacks.slidescreen.name: SlideScreen
-*hacks.speedmine.name: SpeedMine
-*hacks.splitflap.name: SplitFlap
-*hacks.starwars.name: StarWars
-*hacks.stonerview.name: StonerView
+*hacks.shadebobs.name: Shade Bobs
+*hacks.sierpinski3d.name: Sierpinski 3D
+*hacks.skytentacles.name: Sky Tentacles
+*hacks.slidescreen.name: Slide Screen
+*hacks.speedmine.name: Speed Mine
+*hacks.sphereeversion.name: Sphere Eversion
+*hacks.splitflap.name: Split-Flap
+*hacks.starwars.name: Star Wars
+*hacks.stonerview.name: Stoner View
*hacks.t3d.name: T3D
-*hacks.testx11.name: TestX11
-*hacks.timetunnel.name: TimeTunnel
-*hacks.topblock.name: TopBlock
-*hacks.tronbit.name: TronBit
-*hacks.unknownpleasures.name:UnknownPleasures
+*hacks.testx11.name: Test X11
+*hacks.timetunnel.name: Time Tunnel
+*hacks.topblock.name: Top Block
+*hacks.tronbit.name: Tron Bit
+*hacks.unknownpleasures.name:Unknown Pleasures
*hacks.vfeedback.name: VFeedback
-*hacks.vidwhacker.name: VidWhacker
-*hacks.webcollage.name: WebCollage
-*hacks.whirlwindwarp.name: WhirlWindWarp
-*hacks.winduprobot.name: WindupRobot
+*hacks.vidwhacker.name: Vid Whacker
+*hacks.webcollage.name: Web Collage
+*hacks.whirlwindwarp.name: Whirlwind Warp
+*hacks.winduprobot.name: Windup Robot
*hacks.xanalogtv.name: XAnalogTV
*hacks.xrayswarm.name: XRaySwarm
-! obsolete, but still used by xscreensaver-demo-Xm.
-*hacks.documentation.isInstalled: True
-
! (xrdb prevention kludge: whole file) */
diff --git a/driver/XScreenSaver_Xm_ad.h b/driver/XScreenSaver_Xm_ad.h
new file mode 100644
index 0000000..371e0a2
--- /dev/null
+++ b/driver/XScreenSaver_Xm_ad.h
@@ -0,0 +1,108 @@
+"*fontList: *-helvetica-medium-r-*-*-*-120-*-*-*-iso8859-1",
+"*demoDialog*label1.fontList: *-helvetica-medium-r-*-*-*-140-*-*-*-iso8859-1",
+"*cmdText.fontList: *-courier-medium-r-*-*-*-120-*-*-*-iso8859-1",
+"*label0.fontList: *-helvetica-bold-r-*-*-*-140-*-*-*-iso8859-1",
+"XScreenSaver*doc.fontList: *-helvetica-medium-r-*-*-*-100-*-*-*-iso8859-1",
+"*foreground: #000000",
+"*background: #C0C0C0",
+"*XmTextField.foreground: #000000",
+"*XmTextField.background: #FFFFFF",
+"*list.foreground: #000000",
+"*list.background: #FFFFFF",
+"*ApplicationShell.title: XScreenSaver",
+"*warning.title: XScreenSaver",
+"*warning_popup.title: XScreenSaver",
+"*allowShellResize: True",
+"*autoUnmanage: False",
+"*menubar*file.labelString: File",
+"*menubar*file.mnemonic: F",
+"*file.blank.labelString: Blank Screen Now",
+"*file.blank.mnemonic: B",
+"*file.lock.labelString: Lock Screen Now",
+"*file.lock.mnemonic: L",
+"*file.kill.labelString: Kill Daemon",
+"*file.kill.mnemonic: K",
+"*file.restart.labelString: Restart Daemon",
+"*file.restart.mnemonic: R",
+"*file.exit.labelString: Exit",
+"*file.exit.mnemonic: E",
+"*menubar*edit.labelString: Edit",
+"*menubar*edit.mnemonic: E",
+"*edit.cut.labelString: Cut",
+"*edit.cut.mnemonic: u",
+"*edit.copy.labelString: Copy",
+"*edit.copy.mnemonic: C",
+"*edit.paste.labelString: Paste",
+"*edit.paste.mnemonic: P",
+"*menubar*help.labelString: Help",
+"*menubar*help.mnemonic: H",
+"*help.about.labelString: About...",
+"*help.about.mnemonic: A",
+"*help.docMenu.labelString: Documentation...",
+"*help.docMenu.mnemonic: D",
+"*demoTab.marginWidth: 10",
+"*optionsTab.marginWidth: 10",
+"*XmScrolledWindow.topOffset: 10",
+"*XmScrolledWindow.leftOffset: 10",
+"*demoTab.topOffset: 4",
+"*form1.bottomOffset: 10",
+"*form3.leftOffset: 10",
+"*form3.rightOffset: 10",
+"*frame.topOffset: 10",
+"*frame.bottomOffset: 10",
+"*enabled.topOffset: 10",
+"*visLabel.topOffset: 10",
+"*combo.topOffset: 10",
+"*form4.bottomOffset: 4",
+"*hr.bottomOffset: 4",
+"*XmComboBox.marginWidth: 0",
+"*XmComboBox.marginHeight: 0",
+"*demo.marginWidth: 30",
+"*demo.marginHeight: 4",
+"*man.marginWidth: 10",
+"*man.marginHeight: 4",
+"*down.leftOffset: 40",
+"*down.marginWidth: 4",
+"*down.marginHeight: 4",
+"*up.marginWidth: 4",
+"*up.marginHeight: 4",
+"*frame.traversalOn: False",
+"*list.automaticSelection: True",
+"*list.visibleItemCount: 20",
+"*doc.columns: 60",
+"*combo.columns: 11",
+"*demoTab.labelString: Graphics Demos",
+"*optionsTab.labelString: Screensaver Options",
+"*down.labelString: \\\\/ ",
+"*up.labelString: /\\\\ ",
+"*frameLabel.labelString: ",
+"*cmdLabel.labelString: Command Line:",
+"*cmdLabel.alignment: ALIGNMENT_BEGINNING",
+"*enabled.labelString: Enabled",
+"*visLabel.labelString: Visual:",
+"*visLabel.alignment: ALIGNMENT_END",
+"*visLabel.leftOffset: 20",
+"*demo.labelString: Demo",
+"*man.labelString: Documentation...",
+"*done.labelString: Quit",
+"*preferencesLabel.labelString: XScreenSaver Parameters",
+"*timeoutLabel.labelString: Saver Timeout",
+"*cycleLabel.labelString: Cycle Timeout",
+"*fadeSecondsLabel.labelString: Fade Duration",
+"*fadeTicksLabel.labelString: Fade Ticks",
+"*lockLabel.labelString: Lock Timeout",
+"*passwdLabel.labelString: Password Timeout",
+"*preferencesForm*XmTextField.columns: 8",
+"*verboseToggle.labelString: Verbose",
+"*cmapToggle.labelString: Install Colormap",
+"*fadeToggle.labelString: Fade Colormap",
+"*unfadeToggle.labelString: Unfade Colormap",
+"*lockToggle.labelString: Require Password",
+"*OK.marginWidth: 30",
+"*OK.marginHeight: 4",
+"*OK.leftOffset: 10",
+"*OK.bottomOffset: 10",
+"*Cancel.marginWidth: 30",
+"*Cancel.marginHeight: 4",
+"*Cancel.rightOffset: 10",
+"*Cancel.bottomOffset: 10",
diff --git a/driver/XScreenSaver_ad.h b/driver/XScreenSaver_ad.h
new file mode 100644
index 0000000..22e213a
--- /dev/null
+++ b/driver/XScreenSaver_ad.h
@@ -0,0 +1,534 @@
+"#error Do not run app-defaults files through xrdb!",
+"#error That does not do what you might expect.",
+"#error Put this file in /usr/lib/X11/app-defaults/XScreenSaver instead.",
+"*mode: random",
+"*timeout: 0:10:00",
+"*cycle: 0:10:00",
+"*lockTimeout: 0:00:00",
+"*passwdTimeout: 0:00:30",
+"*dpmsEnabled: False",
+"*dpmsQuickoffEnabled: False",
+"*dpmsStandby: 2:00:00",
+"*dpmsSuspend: 2:00:00",
+"*dpmsOff: 4:00:00",
+"*grabDesktopImages: True",
+"*grabVideoFrames: False",
+"*chooseRandomImages: True",
+"*imageDirectory: /Library/Desktop Pictures/",
+"*nice: 10",
+"*memoryLimit: 0",
+"*lock: False",
+"*verbose: False",
+"*fade: True",
+"*unfade: True",
+"*fadeSeconds: 0:00:03",
+"*fadeTicks: 20",
+"*splash: True",
+"*splashDuration: 0:00:05",
+"*visualID: default",
+"*installColormap: True",
+"*ignoreUninstalledPrograms: False",
+"*authWarningSlack: 20",
+"*textMode: file",
+"*textLiteral: XScreenSaver",
+"*textFile: ",
+"*textProgram: fortune",
+"*textURL: https://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss",
+"*demoCommand: xscreensaver-settings",
+"*helpURL: https://www.jwz.org/xscreensaver/man.html",
+"*loadURL: gnome-open '%s'",
+"*manualCommand: yelp man:%s || \
+ x-terminal-emulator -t '%s manual' \
+ -e /bin/sh -c \"man %s; read foo\"",
+"*dateFormat: %I:%M %p, %a %b %e",
+"*newLoginCommand: no-such-login-manager",
+"XScreenSaver.pointerHysteresis: 10",
+"XScreenSaver.bourneShell: /bin/sh",
+"*dialogTheme: default",
+"*themeNames: Default, Borderless, Dark Gray, Borderless Black, \
+ Green Black, White, Blue, Aqua Black, Wine",
+"*Dialog.headingFont: sans-serif bold 16",
+"*Dialog.bodyFont: sans-serif 14",
+"*Dialog.errorFont: sans-serif bold 14",
+"*Dialog.labelFont: sans-serif bold 14",
+"*Dialog.unameFont: sans-serif 12",
+"*Dialog.buttonFont: sans-serif bold 14",
+"*Dialog.dateFont: sans-serif 9",
+"*passwd.uname: True",
+"*passwd.asterisks: True",
+"*default.Dialog.foreground: #000000",
+"*default.Dialog.background: #E6E6E6",
+"*default.Dialog.button.foreground: #000000",
+"*default.Dialog.button.background: #F5F5F5",
+"*default.Dialog.logo.background: #BBBBBB",
+"*default.Dialog.text.foreground: #000000",
+"*default.Dialog.error.foreground: #FF0000",
+"*default.Dialog.text.background: #FFFFFF",
+"*default.Dialog.topShadowColor: #FFFFFF",
+"*default.Dialog.bottomShadowColor: #CECECE",
+"*default.Dialog.shadowWidth: 2",
+"*default.Dialog.logo.width: 210",
+"*default.Dialog.logo.height: 210",
+"*default.Dialog.thermometer.foreground: #4464AC",
+"*default.Dialog.thermometer.background: #FFFFFF",
+"*default.Dialog.thermometer.width: 8",
+"*default.Dialog.borderColor: #CECECE",
+"*default.Dialog.borderWidth: 0",
+"*default.Dialog.internalPadding: 24",
+"*borderless.Dialog.topShadowColor: #E6E6E6",
+"*borderless.Dialog.button.background: #FFFFFF",
+"*borderless.Dialog.bottomShadowColor: #E6E6E6",
+"*borderless.Dialog.logo.background: #E6E6E6",
+"*borderless.Dialog.borderColor: #888888",
+"*borderless.Dialog.thermometer.width: 6",
+"*borderless.Dialog.borderWidth: 1",
+"*darkgray.Dialog.foreground: #CCCCCC",
+"*darkgray.Dialog.background: #333333",
+"*darkgray.Dialog.topShadowColor: #444444",
+"*darkgray.Dialog.bottomShadowColor: #111111",
+"*darkgray.Dialog.borderColor: #111111",
+"*darkgray.Dialog.text.foreground: #DDDDDD",
+"*darkgray.Dialog.text.background: #666666",
+"*darkgray.Dialog.button.foreground: #CCCCCC",
+"*darkgray.Dialog.button.background: #666666",
+"*darkgray.Dialog.logo.background: #444444",
+"*darkgray.Dialog.thermometer.foreground: #4464AC",
+"*darkgray.Dialog.thermometer.background: #666666",
+"*darkgray.Dialog.borderWidth: 0",
+"*borderlessblack.Dialog.foreground: #CCCCCC",
+"*borderlessblack.Dialog.background: #000000",
+"*borderlessblack.Dialog.topShadowColor: #000000",
+"*borderlessblack.Dialog.bottomShadowColor: #000000",
+"*borderlessblack.Dialog.text.foreground: #CCCCCC",
+"*borderlessblack.Dialog.text.background: #000000",
+"*borderlessblack.Dialog.button.foreground: #CCCCCC",
+"*borderlessblack.Dialog.button.background: #333333",
+"*borderlessblack.Dialog.logo.background: #000000",
+"*borderlessblack.Dialog.thermometer.foreground: #CCCCCC",
+"*borderlessblack.Dialog.thermometer.background: #333333",
+"*borderlessblack.Dialog.thermometer.width: 3",
+"*borderlessblack.Dialog.borderColor: #333333",
+"*borderlessblack.Dialog.borderWidth: 1",
+"*greenblack.Dialog.foreground: #00FF00",
+"*greenblack.Dialog.background: #000000",
+"*greenblack.Dialog.topShadowColor: #000000",
+"*greenblack.Dialog.bottomShadowColor: #000000",
+"*greenblack.Dialog.shadowWidth: 1",
+"*greenblack.Dialog.text.foreground: #00FF00",
+"*greenblack.Dialog.text.background: #006600",
+"*greenblack.Dialog.button.foreground: #00FF00",
+"*greenblack.Dialog.button.background: #006600",
+"*greenblack.Dialog.logo.background: #000000",
+"*greenblack.Dialog.thermometer.foreground: #00CC00",
+"*greenblack.Dialog.thermometer.background: #006600",
+"*greenblack.Dialog.borderColor: #006600",
+"*greenblack.Dialog.borderWidth: 1",
+"*white.Dialog.foreground: #000000",
+"*white.Dialog.background: #FFFFFF",
+"*white.Dialog.topShadowColor: #CCCCCC",
+"*white.Dialog.bottomShadowColor: #CCCCCC",
+"*white.Dialog.shadowWidth: 1",
+"*white.Dialog.borderColor: #CCCCCC",
+"*white.Dialog.text.foreground: #000000",
+"*white.Dialog.text.background: #FFFFFF",
+"*white.Dialog.button.foreground: #000000",
+"*white.Dialog.button.background: #FFFFFF",
+"*white.Dialog.logo.background: #FFFFFF",
+"*white.Dialog.borderWidth: 0",
+"*blue.Dialog.foreground: #000000",
+"*blue.Dialog.background: #BBCCDD",
+"*blue.Dialog.topShadowColor: #CCDDEE",
+"*blue.Dialog.bottomShadowColor: #AABBCC",
+"*blue.Dialog.borderColor: #AABBCC",
+"*blue.Dialog.text.foreground: #000000",
+"*blue.Dialog.text.background: #DDEEFF",
+"*blue.Dialog.button.foreground: #000000",
+"*blue.Dialog.button.background: #DDEEFF",
+"*blue.Dialog.logo.background: #BBCCDD",
+"*blue.Dialog.thermometer.foreground: #5566AA",
+"*blue.Dialog.thermometer.background: #BBCCDD",
+"*blue.Dialog.borderWidth: 0",
+"*aquablack.Dialog.foreground: #00EFEF",
+"*aquablack.Dialog.background: #000000",
+"*aquablack.Dialog.topShadowColor: #000000",
+"*aquablack.Dialog.bottomShadowColor: #000000",
+"*aquablack.Dialog.shadowWidth: 2",
+"*aquablack.Dialog.button.foreground: #000000",
+"*aquablack.Dialog.button.background: #2244EE",
+"*aquablack.Dialog.logo.background: #000000",
+"*aquablack.Dialog.text.foreground: #2244EE",
+"*aquablack.Dialog.text.background: #EEEEEE",
+"*aquablack.Dialog.thermometer.foreground: #2244EE",
+"*aquablack.Dialog.thermometer.background: #000088",
+"*aquablack.Dialog.borderColor: #000066",
+"*aquablack.Dialog.borderWidth: 1",
+"*aquablack.Dialog.internalPadding: 36",
+"*wine.Dialog.foreground: #AD8FA6",
+"*wine.Dialog.background: #2C041E",
+"*wine.Dialog.topShadowColor: #2C041E",
+"*wine.Dialog.bottomShadowColor: #2C041E",
+"*wine.Dialog.text.foreground: #706B70",
+"*wine.Dialog.text.background: #F9F9F8",
+"*wine.Dialog.button.foreground: #5F585B",
+"*wine.Dialog.logo.background: #2C041E",
+"*wine.Dialog.thermometer.foreground: #AD8FA6",
+"*wine.Dialog.thermometer.background: #4D2946",
+"*wine.Dialog.borderColor: #4D2946",
+"*wine.Dialog.thermometer.width: 6",
+"*wine.Dialog.borderWidth: 1",
+"*errorFont: sans-serif bold 18",
+"*errorColor: #FF0000",
+"*programs: \
+ maze -root \\n\
+ GL: superquadrics -root \\n\
+ attraction -root \\n\
+ blitspin -root \\n\
+ greynetic -root \\n\
+ helix -root \\n\
+ hopalong -root \\n\
+ imsmap -root \\n\
+- noseguy -root \\n\
+- pyro -root \\n\
+ qix -root \\n\
+- rocks -root \\n\
+ rorschach -root \\n\
+ decayscreen -root \\n\
+ flame -root \\n\
+ halo -root \\n\
+ slidescreen -root \\n\
+ pedal -root \\n\
+ bouboule -root \\n\
+- braid -root \\n\
+ coral -root \\n\
+ deco -root \\n\
+ drift -root \\n\
+- fadeplot -root \\n\
+ galaxy -root \\n\
+ goop -root \\n\
+ grav -root \\n\
+ ifs -root \\n\
+ GL: jigsaw -root \\n\
+ julia -root \\n\
+- kaleidescope -root \\n\
+ GL: moebius -root \\n\
+ moire -root \\n\
+ GL: morph3d -root \\n\
+ mountain -root \\n\
+ munch -root \\n\
+ penrose -root \\n\
+ GL: pipes -root \\n\
+ rd-bomb -root \\n\
+ GL: rubik -root \\n\
+- sierpinski -root \\n\
+ slip -root \\n\
+ GL: sproingies -root \\n\
+ starfish -root \\n\
+ strange -root \\n\
+ swirl -root \\n\
+ triangle -root \\n\
+ xjack -root \\n\
+ xlyap -root \\n\
+ GL: atlantis -root \\n\
+ bsod -root \\n\
+ GL: bubble3d -root \\n\
+ GL: cage -root \\n\
+- crystal -root \\n\
+ cynosure -root \\n\
+ discrete -root \\n\
+ distort -root \\n\
+ epicycle -root \\n\
+ flow -root \\n\
+ GL: glplanet -root \\n\
+ interference -root \\n\
+ kumppa -root \\n\
+ GL: lament -root \\n\
+ moire2 -root \\n\
+ GL: sonar -root \\n\
+ GL: stairs -root \\n\
+ truchet -root \\n\
+- vidwhacker -root \\n\
+ blaster -root \\n\
+ bumps -root \\n\
+ ccurve -root \\n\
+ compass -root \\n\
+ deluxe -root \\n\
+- demon -root \\n\
+ GL: extrusion -root \\n\
+- loop -root \\n\
+ penetrate -root \\n\
+ petri -root \\n\
+ phosphor -root \\n\
+ GL: pulsar -root \\n\
+ ripples -root \\n\
+ shadebobs -root \\n\
+ GL: sierpinski3d -root \\n\
+ spotlight -root \\n\
+ squiral -root \\n\
+ wander -root \\n\
+- webcollage -root \\n\
+ xflame -root \\n\
+ xmatrix -root \\n\
+ GL: gflux -root \\n\
+- nerverot -root \\n\
+ xrayswarm -root \\n\
+ xspirograph -root \\n\
+ GL: circuit -root \\n\
+ GL: dangerball -root \\n\
+- GL: dnalogo -root \\n\
+ GL: engine -root \\n\
+ GL: flipscreen3d -root \\n\
+ GL: gltext -root \\n\
+ GL: menger -root \\n\
+ GL: molecule -root \\n\
+ rotzoomer -root \\n\
+ scooter -root \\n\
+ speedmine -root \\n\
+ GL: starwars -root \\n\
+ GL: stonerview -root \\n\
+ vermiculate -root \\n\
+ whirlwindwarp -root \\n\
+ zoom -root \\n\
+ anemone -root \\n\
+ apollonian -root \\n\
+ GL: boxed -root \\n\
+ GL: cubenetic -root \\n\
+ GL: endgame -root \\n\
+ euler2d -root \\n\
+ fluidballs -root \\n\
+ GL: flurry -root \\n\
+- GL: glblur -root \\n\
+ GL: glsnake -root \\n\
+ halftone -root \\n\
+ GL: juggler3d -root \\n\
+ GL: lavalite -root \\n\
+- polyominoes -root \\n\
+ GL: queens -root \\n\
+- GL: sballs -root \\n\
+ GL: spheremonics -root \\n\
+- thornbird -root \\n\
+ twang -root \\n\
+- GL: antspotlight -root \\n\
+ apple2 -root \\n\
+ GL: atunnel -root \\n\
+ barcode -root \\n\
+ GL: blinkbox -root \\n\
+ GL: blocktube -root \\n\
+ GL: bouncingcow -root \\n\
+ cloudlife -root \\n\
+ GL: cubestorm -root \\n\
+ eruption -root \\n\
+ GL: flipflop -root \\n\
+ GL: flyingtoasters -root \\n\
+ fontglide -root \\n\
+ GL: gleidescope -root \\n\
+ GL: glknots -root \\n\
+ GL: glmatrix -root \\n\
+- GL: glslideshow -root \\n\
+ GL: hypertorus -root \\n\
+- GL: jigglypuff -root \\n\
+ metaballs -root \\n\
+ GL: mirrorblob -root \\n\
+ piecewise -root \\n\
+ GL: polytopes -root \\n\
+ pong -root \\n\
+ popsquares -root \\n\
+ GL: surfaces -root \\n\
+ xanalogtv -root \\n\
+ abstractile -root \\n\
+ anemotaxis -root \\n\
+- GL: antinspect -root \\n\
+ fireworkx -root \\n\
+ fuzzyflakes -root \\n\
+ interaggregate -root \\n\
+ intermomentary -root \\n\
+ memscroller -root \\n\
+ GL: noof -root \\n\
+ pacman -root \\n\
+ GL: pinion -root \\n\
+ GL: polyhedra -root \\n\
+- GL: providence -root \\n\
+ substrate -root \\n\
+ wormhole -root \\n\
+- GL: antmaze -root \\n\
+ GL: boing -root \\n\
+ boxfit -root \\n\
+ GL: carousel -root \\n\
+ celtic -root \\n\
+ GL: crackberg -root \\n\
+ GL: cube21 -root \\n\
+ fiberlamp -root \\n\
+ GL: fliptext -root \\n\
+ GL: glhanoi -root \\n\
+ GL: tangram -root \\n\
+ GL: timetunnel -root \\n\
+ GL: glschool -root \\n\
+ GL: topblock -root \\n\
+ GL: cubicgrid -root \\n\
+ cwaves -root \\n\
+ GL: gears -root \\n\
+ GL: glcells -root \\n\
+ GL: lockward -root \\n\
+ m6502 -root \\n\
+ GL: moebiusgears -root \\n\
+ GL: voronoi -root \\n\
+ GL: hypnowheel -root \\n\
+ GL: klein -root \\n\
+- lcdscrub -root \\n\
+ GL: photopile -root \\n\
+ GL: skytentacles -root \\n\
+ GL: rubikblocks -root \\n\
+ GL: companioncube -root \\n\
+ GL: hilbert -root \\n\
+ GL: tronbit -root \\n\
+ GL: geodesic -root \\n\
+ hexadrop -root \\n\
+ GL: kaleidocycle -root \\n\
+ GL: quasicrystal -root \\n\
+ GL: unknownpleasures -root \\n\
+ binaryring -root \\n\
+ GL: cityflow -root \\n\
+ GL: geodesicgears -root \\n\
+ GL: projectiveplane -root \\n\
+ GL: romanboy -root \\n\
+ tessellimage -root \\n\
+ GL: winduprobot -root \\n\
+ GL: splitflap -root \\n\
+ GL: cubestack -root \\n\
+ GL: cubetwist -root \\n\
+ GL: discoball -root \\n\
+ GL: dymaxionmap -root \\n\
+ GL: energystream -root \\n\
+ GL: hexstrut -root \\n\
+ GL: hydrostat -root \\n\
+ GL: raverhoop -root \\n\
+ GL: splodesic -root \\n\
+ GL: unicrud -root \\n\
+ GL: esper -root \\n\
+ GL: vigilance -root \\n\
+ GL: crumbler -root \\n\
+ filmleader -root \\n\
+ glitchpeg -root \\n\
+ GL: handsy -root \\n\
+ GL: maze3d -root \\n\
+ GL: peepers -root \\n\
+ GL: razzledazzle -root \\n\
+ vfeedback -root \\n\
+ GL: deepstars -root \\n\
+ GL: gravitywell -root \\n\
+ GL: beats -root \\n\
+ GL: covid19 -root \\n\
+ GL: etruscanvenus -root \\n\
+ GL: gibson -root \\n\
+ GL: headroom -root \\n\
+ GL: sphereeversion -root \\n",
+"*hacks.antinspect.name: Ant Inspect",
+"*hacks.antmaze.name: Ant Maze",
+"*hacks.antspotlight.name: Ant Spotlight",
+"*hacks.apple2.name: Apple ][",
+"*hacks.binaryring.name: Binary Ring",
+"*hacks.blinkbox.name: Blink Box",
+"*hacks.blitspin.name: Blit Spin",
+"*hacks.blocktube.name: Block Tube",
+"*hacks.bouncingcow.name: Bouncing Cow",
+"*hacks.boxfit.name: Box Fit",
+"*hacks.bsod.name: BSOD",
+"*hacks.bubble3d.name: Bubble 3D",
+"*hacks.ccurve.name: C Curve",
+"*hacks.cityflow.name: City Flow",
+"*hacks.cloudlife.name: Cloud Life",
+"*hacks.companioncube.name: Companion Cube",
+"*hacks.covid19.name: COVID19",
+"*hacks.cube21.name: Cube 21",
+"*hacks.cubestack.name: Cube Stack",
+"*hacks.cubestorm.name: Cube Storm",
+"*hacks.cubetwist.name: Cube Twist",
+"*hacks.cubicgrid.name: Cubic Grid",
+"*hacks.cwaves.name: C Waves",
+"*hacks.dangerball.name: Danger Ball",
+"*hacks.decayscreen.name: Decay Screen",
+"*hacks.deepstars.name: Deep Stars",
+"*hacks.dnalogo.name: DNA Logo",
+"*hacks.dymaxionmap.name: Dymaxion Map",
+"*hacks.energystream.name: Energy Stream",
+"*hacks.etruscanvenus.name: Etruscan Venus",
+"*hacks.euler2d.name: Euler 2D",
+"*hacks.fadeplot.name: Fade Plot",
+"*hacks.fiberlamp.name: Fiber Lamp",
+"*hacks.filmleader.name: Film Leader",
+"*hacks.flipflop.name: Flip Flop",
+"*hacks.flipscreen3d.name: Flip Screen 3D",
+"*hacks.fliptext.name: Flip Text",
+"*hacks.fluidballs.name: Fluid Balls",
+"*hacks.flyingtoasters.name: Flying Toasters",
+"*hacks.fontglide.name: Font Glide",
+"*hacks.fuzzyflakes.name: Fuzzy Flakes",
+"*hacks.geodesicgears.name: Geodesic Gears",
+"*hacks.gflux.name: GFlux",
+"*hacks.glblur.name: GL Blur",
+"*hacks.glcells.name: GL Cells",
+"*hacks.gleidescope.name: Gleidescope",
+"*hacks.glforestfire.name: GL Forest Fire",
+"*hacks.glhanoi.name: GL Hanoi",
+"*hacks.glitchpeg.name: GlitchPEG",
+"*hacks.glknots.name: GL Knots",
+"*hacks.glmatrix.name: GL Matrix",
+"*hacks.glplanet.name: GL Planet",
+"*hacks.glschool.name: GL School",
+"*hacks.glslideshow.name: GL Slideshow",
+"*hacks.glsnake.name: GL Snake",
+"*hacks.gltext.name: GL Text",
+"*hacks.gravitywell.name: Gravity Well",
+"*hacks.hexstrut.name: Hex Strut",
+"*hacks.ifs.name: IFS",
+"*hacks.imsmap.name: IMS Map",
+"*hacks.jigglypuff.name: Jiggly Puff",
+"*hacks.juggler3d.name: Juggler 3D",
+"*hacks.lcdscrub.name: LCD Scrub",
+"*hacks.lmorph.name: LMorph",
+"*hacks.m6502.name: m6502",
+"*hacks.maze3d.name: Maze 3D",
+"*hacks.memscroller.name: Mem Scroller",
+"*hacks.metaballs.name: Meta Balls",
+"*hacks.mirrorblob.name: Mirror Blob",
+"*hacks.moebius.name: Möbius",
+"*hacks.moebiusgears.name: Möbius Gears",
+"*hacks.moire.name: Moiré",
+"*hacks.moire2.name: Moiré 2",
+"*hacks.morph3d.name: Morph 3D",
+"*hacks.nerverot.name: Nerve Rot",
+"*hacks.noseguy.name: Nose Guy",
+"*hacks.pacman.name: Pac-Man",
+"*hacks.photopile.name: Photo Pile",
+"*hacks.popsquares.name: Pop Squares",
+"*hacks.projectiveplane.name:Projective Plane",
+"*hacks.quasicrystal.name: Quasi-Crystal",
+"*hacks.raverhoop.name: Raver Hoop",
+"*hacks.razzledazzle.name: Razzle Dazzle",
+"*hacks.rd-bomb.name: RD-Bomb",
+"*hacks.rd-bomb.name: RD-Bomb",
+"*hacks.romanboy.name: Roman Boy",
+"*hacks.rotzoomer.name: Rot Zoomer",
+"*hacks.rubikblocks.name: Rubik Blocks",
+"*hacks.sballs.name: SBalls",
+"*hacks.shadebobs.name: Shade Bobs",
+"*hacks.sierpinski3d.name: Sierpinski 3D",
+"*hacks.skytentacles.name: Sky Tentacles",
+"*hacks.slidescreen.name: Slide Screen",
+"*hacks.speedmine.name: Speed Mine",
+"*hacks.sphereeversion.name: Sphere Eversion",
+"*hacks.splitflap.name: Split-Flap",
+"*hacks.starwars.name: Star Wars",
+"*hacks.stonerview.name: Stoner View",
+"*hacks.t3d.name: T3D",
+"*hacks.testx11.name: Test X11",
+"*hacks.timetunnel.name: Time Tunnel",
+"*hacks.topblock.name: Top Block",
+"*hacks.tronbit.name: Tron Bit",
+"*hacks.unknownpleasures.name:Unknown Pleasures",
+"*hacks.vfeedback.name: VFeedback",
+"*hacks.vidwhacker.name: Vid Whacker",
+"*hacks.webcollage.name: Web Collage",
+"*hacks.whirlwindwarp.name: Whirlwind Warp",
+"*hacks.winduprobot.name: Windup Robot",
+"*hacks.xanalogtv.name: XAnalogTV",
+"*hacks.xrayswarm.name: XRaySwarm",
diff --git a/driver/atoms.c b/driver/atoms.c
new file mode 100644
index 0000000..e8b6add
--- /dev/null
+++ b/driver/atoms.c
@@ -0,0 +1,70 @@
+/* xscreensaver-command, Copyright © 1991-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.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <X11/Xproto.h> /* for CARD32 */
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+#include "atoms.h"
+
+Atom XA_SCREENSAVER, XA_SCREENSAVER_VERSION, XA_SCREENSAVER_RESPONSE,
+ XA_SCREENSAVER_ID, XA_SCREENSAVER_STATUS, XA_SELECT, XA_DEMO, XA_EXIT,
+ XA_BLANK, XA_LOCK, XA_ACTIVATE, XA_SUSPEND, XA_NEXT, XA_PREV,
+ XA_DEACTIVATE, XA_CYCLE, XA_RESTART, XA_PREFS,
+ XA_NET_WM_PID, XA_NET_WM_STATE, XA_NET_WM_STATE_ABOVE,
+ XA_NET_WM_STATE_FULLSCREEN, XA_NET_WM_BYPASS_COMPOSITOR,
+ XA_NET_WM_WINDOW_TYPE, XA_NET_WM_WINDOW_TYPE_SPLASH,
+ XA_NET_WM_WINDOW_TYPE_DIALOG, XA_NET_WM_WINDOW_TYPE_NOTIFICATION,
+ XA_NET_WM_WINDOW_TYPE_NORMAL;
+
+void
+init_xscreensaver_atoms (Display *dpy)
+{
+# define A(N) XInternAtom (dpy, (N), False)
+ XA_SCREENSAVER = A("SCREENSAVER");
+ XA_SCREENSAVER_ID = A("_SCREENSAVER_ID");
+ XA_SCREENSAVER_VERSION = A("_SCREENSAVER_VERSION");
+ XA_SCREENSAVER_STATUS = A("_SCREENSAVER_STATUS");
+ XA_SCREENSAVER_RESPONSE = A("_SCREENSAVER_RESPONSE");
+
+ XA_ACTIVATE = A("ACTIVATE");
+ XA_DEACTIVATE = A("DEACTIVATE");
+ XA_SUSPEND = A("SUSPEND");
+ XA_RESTART = A("RESTART");
+ XA_CYCLE = A("CYCLE");
+ XA_NEXT = A("NEXT");
+ XA_PREV = A("PREV");
+ XA_SELECT = A("SELECT");
+ XA_EXIT = A("EXIT");
+ XA_DEMO = A("DEMO");
+ XA_PREFS = A("PREFS");
+ XA_LOCK = A("LOCK");
+ XA_BLANK = A("BLANK");
+
+ XA_NET_WM_PID = A("_NET_WM_PID");
+ XA_NET_WM_STATE = A("_NET_WM_STATE");
+ XA_NET_WM_STATE_ABOVE = A("_NET_WM_STATE_ABOVE");
+ XA_NET_WM_STATE_FULLSCREEN = A("_NET_WM_STATE_FULLSCREEN");
+ XA_NET_WM_BYPASS_COMPOSITOR = A("_NET_WM_BYPASS_COMPOSITOR");
+ XA_NET_WM_WINDOW_TYPE = A("_NET_WM_WINDOW_TYPE");
+ XA_NET_WM_WINDOW_TYPE_SPLASH = A("_NET_WM_WINDOW_TYPE_SPLASH");
+ XA_NET_WM_WINDOW_TYPE_DIALOG = A("_NET_WM_WINDOW_TYPE_DIALOG");
+ XA_NET_WM_WINDOW_TYPE_NOTIFICATION = A("_NET_WM_WINDOW_TYPE_NOTIFICATION");
+ XA_NET_WM_WINDOW_TYPE_NORMAL = A("_NET_WM_WINDOW_TYPE_NORMAL");
+# undef A
+}
diff --git a/driver/atoms.h b/driver/atoms.h
new file mode 100644
index 0000000..b5a7b7c
--- /dev/null
+++ b/driver/atoms.h
@@ -0,0 +1,38 @@
+/* xscreensaver-command, Copyright © 1991-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.
+ */
+
+#ifndef _XSCREENSAVER_ATOMS_H_
+#define _XSCREENSAVER_ATOMS_H_
+
+extern Atom XA_SCREENSAVER, XA_SCREENSAVER_VERSION, XA_SCREENSAVER_RESPONSE,
+ XA_SCREENSAVER_ID, XA_SCREENSAVER_STATUS, XA_SELECT, XA_DEMO, XA_EXIT,
+ XA_BLANK, XA_LOCK, XA_ACTIVATE, XA_SUSPEND, XA_NEXT, XA_PREV,
+ XA_DEACTIVATE, XA_CYCLE, XA_RESTART, XA_PREFS,
+ XA_NET_WM_PID, XA_NET_WM_STATE, XA_NET_WM_STATE_ABOVE,
+ XA_NET_WM_STATE_FULLSCREEN, XA_NET_WM_BYPASS_COMPOSITOR,
+ XA_NET_WM_WINDOW_TYPE, XA_NET_WM_WINDOW_TYPE_SPLASH,
+ XA_NET_WM_WINDOW_TYPE_DIALOG, XA_NET_WM_WINDOW_TYPE_NOTIFICATION,
+ XA_NET_WM_WINDOW_TYPE_NORMAL;
+
+extern void init_xscreensaver_atoms (Display *dpy);
+extern void xscreensaver_set_wm_atoms (Display *, Window,
+ int width, int height,
+ Window for_window);
+
+/* You might think that to store an array of 32-bit quantities onto a
+ server-side property, you would pass an array of 32-bit data quantities
+ into XChangeProperty(). You would be wrong. You have to use an array
+ of longs, even if long is 64 bits (using 32 of each 64.)
+ */
+typedef long PROP32;
+
+
+#endif /* _XSCREENSAVER_ATOMS_H_ */
diff --git a/driver/atomswm.c b/driver/atomswm.c
new file mode 100644
index 0000000..887dc2c
--- /dev/null
+++ b/driver/atomswm.c
@@ -0,0 +1,101 @@
+/* xscreensaver-command, Copyright © 1991-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.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdio.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <X11/Xproto.h> /* for CARD32 */
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+
+#include "atoms.h"
+
+#ifdef HAVE_UNAME
+# include <sys/utsname.h> /* for uname() */
+#endif /* HAVE_UNAME */
+
+
+/* Set some properties to hopefully tell the window manager to leave us alone.
+ This is used by xscreensaver-gfx and xscreensaver-auth but not xscreensaver.
+ */
+void
+xscreensaver_set_wm_atoms (Display *dpy, Window window, int width, int height,
+ Window for_window)
+{
+ XClassHint class_hints;
+ XSizeHints size_hints;
+# ifdef HAVE_UNAME
+ struct utsname uts;
+# endif
+ Atom va[10];
+ long vl[10];
+ class_hints.res_name = "xscreensaver"; /* not progname */
+ class_hints.res_class = "XScreenSaver";
+ size_hints.flags = PMinSize | PMaxSize;
+ size_hints.min_width = size_hints.max_width = width; /* non-resizable */
+ size_hints.min_height = size_hints.max_height = height;
+ XStoreName (dpy, window, "XScreenSaver");
+ XSetClassHint (dpy, window, &class_hints);
+ XSetWMNormalHints (dpy, window, &size_hints);
+
+ /* XA_WM_COMMAND and _NET_WM_PID are later updated by spawn_screenhack. */
+ XChangeProperty (dpy, window, XA_WM_COMMAND,
+ XA_STRING, 8, PropModeReplace,
+ (unsigned char *) class_hints.res_name,
+ strlen (class_hints.res_name));
+
+# ifdef HAVE_UNAME
+ if (! uname (&uts))
+ XChangeProperty (dpy, window,
+ XA_WM_CLIENT_MACHINE, XA_STRING, 8,
+ PropModeReplace, (unsigned char *) uts.nodename,
+ strlen (uts.nodename));
+# endif
+
+ /* In the olden days, OverrideRedirect meant that the window manager did
+ not see or touch our window, but these days, compositing WMs like to
+ get up to all sorts of shenanigans. I don't know whether setting these
+ properties has any effect, but they *might* tell the WM to keep its
+ grubby paws off of our windows.
+ */
+
+ vl[0] = 1; /* _NET_WM_BYPASS_COMPOSITOR = 1 */
+ XChangeProperty (dpy, window, XA_NET_WM_BYPASS_COMPOSITOR, XA_CARDINAL, 32,
+ PropModeReplace, (unsigned char *) vl, 1);
+
+ /* _NET_WM_STATE = [ _NET_WM_STATE_ABOVE, _NET_WM_STATE_FULLSCREEN ] */
+ va[0] = XA_NET_WM_STATE_ABOVE;
+ va[1] = XA_NET_WM_STATE_FULLSCREEN;
+ XChangeProperty (dpy, window, XA_NET_WM_STATE, XA_ATOM, 32,
+ PropModeReplace, (unsigned char *) va, 2);
+
+ /* As there is no _NET_WM_WINDOW_TYPE_SCREENSAVER, which property is
+ most likely to effectively communicate "on top always" to the WM?
+ _NET_WM_WINDOW_TYPE = NORMAL, SPLASH, DIALOG or NOTIFICATION? */
+ va[0] = XA_NET_WM_WINDOW_TYPE_NOTIFICATION;
+ XChangeProperty (dpy, window, XA_NET_WM_WINDOW_TYPE, XA_ATOM, 32,
+ PropModeReplace, (unsigned char *) va, 1);
+
+ if (for_window) /* This is the error dialog for a saver window */
+ {
+ va[0] = for_window;
+ /* _WM_TRANSIENT_FOR = screensaver_window */
+ XChangeProperty (dpy, window,
+ XA_WM_TRANSIENT_FOR, XA_WINDOW, 32,
+ PropModeReplace, (unsigned char *) va, 1);
+ }
+}
diff --git a/driver/auth.h b/driver/auth.h
index 65e00f3..40594ea 100644
--- a/driver/auth.h
+++ b/driver/auth.h
@@ -1,9 +1,7 @@
/* auth.h --- Providing authentication mechanisms.
- *
+ * Copyright © 1993-2021 Jamie Zawinski <jwz@jwz.org>
* (c) 2007, Quest Software, Inc. All rights reserved.
- *
- * This file is part of XScreenSaver,
- * Copyright (c) 1993-2004 Jamie Zawinski <jwz@jwz.org>
+ * This file is part of XScreenSaver.
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
@@ -13,10 +11,8 @@
* software for any purpose. It is provided "as is" without express or
* implied warranty.
*/
-#ifndef XSS_AUTH_H
-#define XSS_AUTH_H
-
-#include "types.h"
+#ifndef __XSCREENSAVER_AUTH_H__
+#define __XSCREENSAVER_AUTH_H__
#undef Bool
#undef True
@@ -25,7 +21,9 @@
#define True 1
#define False 0
-struct auth_message {
+extern Bool verbose_p;
+
+typedef struct {
enum {
AUTH_MSGTYPE_INFO,
AUTH_MSGTYPE_ERROR,
@@ -33,22 +31,61 @@ struct auth_message {
AUTH_MSGTYPE_PROMPT_ECHO
} type;
const char *msg;
-};
+} auth_message;
-struct auth_response {
+typedef struct {
char *response;
-};
+} auth_response;
-int
-gui_auth_conv(int num_msg,
- const struct auth_message auth_msgs[],
- struct auth_response **resp,
- saver_info *si);
-void
-xss_authenticate(saver_info *si, Bool verbose_p);
+/* To run all authentication methods.
+ */
+extern void disavow_privileges (void);
+extern Bool lock_priv_init (void);
+extern Bool lock_init (void);
+
+/* Returns true if authenticated. */
+extern Bool xscreensaver_auth (void *closure,
+ Bool (*conv_fn) (void *closure,
+ int nmsgs,
+ const auth_message *msg,
+ auth_response **resp),
+ void (*finished_fn) (void *closure,
+ Bool authenticated_p));
-void
-auth_finished_cb (saver_info *si);
+/* The implementations, called by xscreensaver_auth.
+ */
+#ifdef HAVE_KERBEROS
+extern Bool kerberos_lock_init (void);
+extern Bool kerberos_passwd_valid_p (void *closure, const char *plaintext);
#endif
+
+#ifdef HAVE_PAM
+extern Bool pam_priv_init (void);
+extern Bool pam_try_unlock (void *closure,
+ Bool (*conv_fn) (void *closure,
+ int nmsgs,
+ const auth_message *msg,
+ auth_response **resp));
+#endif
+
+#ifdef PASSWD_HELPER_PROGRAM
+extern Bool ext_priv_init (void);
+extern Bool ext_passwd_valid_p (void *closure, const char *plaintext);
+#endif
+
+extern Bool pwent_lock_init (void);
+extern Bool pwent_priv_init (void);
+extern Bool pwent_passwd_valid_p (void *closure, const char *plaintext);
+
+/* GUI conversation function to pass to xscreensaver_auth. */
+extern Bool xscreensaver_auth_conv (void *closure,
+ int num_msg,
+ const auth_message *msg,
+ auth_response **resp);
+extern void xscreensaver_auth_finished (void *closure, Bool authenticated_p);
+extern void xscreensaver_splash (void *root_widget);
+
+#endif /* __XSCREENSAVER_AUTH_H__ */
+
diff --git a/driver/blurb.c b/driver/blurb.c
new file mode 100644
index 0000000..d732a9b
--- /dev/null
+++ b/driver/blurb.c
@@ -0,0 +1,50 @@
+/* xscreensaver, Copyright © 1991-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.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "blurb.h"
+
+#include <string.h>
+#include <time.h>
+
+const char *progname = "";
+int verbose_p = 0;
+
+const char *
+blurb (void)
+{
+ static char buf[255] = { 0 };
+ struct tm tm;
+ time_t now;
+ int i;
+
+ now = time ((time_t *) 0);
+ localtime_r (&now, &tm);
+ i = strlen (progname);
+ if (i > 40) i = 40;
+ memcpy (buf, progname, i);
+ buf[i++] = ':';
+ buf[i++] = ' ';
+ buf[i++] = '0' + (tm.tm_hour >= 10 ? tm.tm_hour/10 : 0);
+ buf[i++] = '0' + (tm.tm_hour % 10);
+ buf[i++] = ':';
+ buf[i++] = '0' + (tm.tm_min >= 10 ? tm.tm_min/10 : 0);
+ buf[i++] = '0' + (tm.tm_min % 10);
+ buf[i++] = ':';
+ buf[i++] = '0' + (tm.tm_sec >= 10 ? tm.tm_sec/10 : 0);
+ buf[i++] = '0' + (tm.tm_sec % 10);
+ buf[i] = 0;
+ return buf;
+}
+
diff --git a/driver/blurb.h b/driver/blurb.h
new file mode 100644
index 0000000..6e33c76
--- /dev/null
+++ b/driver/blurb.h
@@ -0,0 +1,11 @@
+/* progname plus timestamp */
+
+#ifndef __BLURB_H__
+#define __BLURB_H__
+
+extern const char *progname;
+extern int verbose_p;
+extern const char *blurb (void);
+
+#endif /* __BLURB_H__ */
+
diff --git a/driver/clientmsg.c b/driver/clientmsg.c
new file mode 100644
index 0000000..f3d9d71
--- /dev/null
+++ b/driver/clientmsg.c
@@ -0,0 +1,123 @@
+/* xscreensaver, Copyright © 1991-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.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+
+#include "blurb.h"
+#include "atoms.h"
+#include "clientmsg.h"
+
+extern Bool verbose_p;
+
+static int
+error_handler (Display *dpy, XErrorEvent *error)
+{
+ return 0;
+}
+
+
+Window
+find_screensaver_window (Display *dpy, char **version)
+{
+ int i;
+ Window root = RootWindowOfScreen (DefaultScreenOfDisplay (dpy));
+ Window root2, parent, *kids;
+ unsigned int nkids;
+ XErrorHandler old_handler;
+ Window ret = 0;
+
+ XSync (dpy, False);
+ old_handler = XSetErrorHandler (error_handler);
+
+ if (version) *version = 0;
+
+ if (! XQueryTree (dpy, root, &root2, &parent, &kids, &nkids))
+ abort ();
+ if (root != root2)
+ abort ();
+ if (parent)
+ abort ();
+ if (! (kids && nkids))
+ goto DONE;
+ for (i = 0; i < nkids; i++)
+ {
+ Atom type;
+ int format;
+ unsigned long nitems, bytesafter;
+ unsigned char *v = 0;
+ int status;
+
+ /* We're walking the list of root-level windows and trying to find
+ the one that has a particular property on it. We need to trap
+ BadWindows errors while doing this, because it's possible that
+ some random window might get deleted in the meantime. (That
+ window won't have been the one we're looking for.)
+ */
+ status = XGetWindowProperty (dpy, kids[i],
+ XA_SCREENSAVER_VERSION,
+ 0, 200, False, XA_STRING,
+ &type, &format, &nitems, &bytesafter,
+ &v);
+ if (status == Success && type != None)
+ {
+ ret = kids[i];
+ if (version)
+ *version = (char *) v;
+ else
+ XFree (v);
+ goto DONE;
+ }
+ if (v) XFree (v);
+ }
+
+ DONE:
+ if (kids) XFree (kids);
+ XSetErrorHandler (old_handler);
+ return ret;
+}
+
+
+void
+clientmessage_response (Display *dpy, XEvent *xev, Bool ok, const char *msg)
+{
+ char *proto;
+ int L = 0;
+
+ if (verbose_p || !ok)
+ {
+ Atom cmd = xev->xclient.data.l[0];
+ char *name = XGetAtomName (dpy, cmd);
+ fprintf (stderr, "%s: ClientMessage %s: %s\n", blurb(),
+ (name ? name : "???"), msg);
+ }
+
+ L = strlen (msg);
+ proto = (char *) malloc (L + 2);
+ if (!proto) return;
+ proto[0] = (ok ? '+' : '-');
+ memcpy (proto+1, msg, L);
+ L++;
+ proto[L] = 0;
+
+ XChangeProperty (dpy, xev->xclient.window,
+ XA_SCREENSAVER_RESPONSE, XA_STRING, 8,
+ PropModeReplace, (unsigned char *) proto, L);
+ free (proto);
+}
diff --git a/driver/clientmsg.h b/driver/clientmsg.h
new file mode 100644
index 0000000..cd836d8
--- /dev/null
+++ b/driver/clientmsg.h
@@ -0,0 +1,19 @@
+/* xscreensaver, Copyright © 1991-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.
+ */
+
+#ifndef __CLIENTMSG_H__
+#define __CLIENTMSG_H__
+
+extern Window find_screensaver_window (Display *, char **version);
+extern void clientmessage_response (Display *, XEvent *, Bool ok,
+ const char *msg);
+
+#endif /* __CLIENTMSG_H__ */
diff --git a/driver/demo-Gtk-conf.c b/driver/demo-Gtk-conf.c
index bac6ecc..6d04215 100644
--- a/driver/demo-Gtk-conf.c
+++ b/driver/demo-Gtk-conf.c
@@ -1,5 +1,5 @@
/* demo-Gtk-conf.c --- implements the dynamic configuration dialogs.
- * xscreensaver, Copyright (c) 2001-2014 Jamie Zawinski <jwz@jwz.org>
+ * xscreensaver, Copyright (c) 2001-2020 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
@@ -59,8 +59,21 @@
# endif /* LIBXML_VERSION */
#endif /* xmlChildrenNode */
+#if (__GNUC__ >= 4) /* Ignore useless warnings generated by gtk.h */
+# undef inline
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wstrict-prototypes"
+# pragma GCC diagnostic ignored "-Wlong-long"
+# pragma GCC diagnostic ignored "-Wvariadic-macros"
+# pragma GCC diagnostic ignored "-Wpedantic"
+#endif
+
#include <gtk/gtk.h>
+#if (__GNUC__ >= 4)
+# pragma GCC diagnostic pop
+#endif
+
#include "demo-Gtk-conf.h"
/* Deal with deprecation of direct access to struct fields on the way to GTK3
@@ -1320,7 +1333,7 @@ parameters_to_cmd_line (GList *parms, gboolean default_p)
{
int L = g_list_length (parms);
int LL = 0;
- char **strs = (char **) calloc (sizeof (*parms), L);
+ char **strs = (char **) calloc (sizeof (*strs), L);
char *result;
char *out;
int i, j;
@@ -1428,7 +1441,7 @@ parse_command_line_into_parameters (const char *filename,
char *option = rest->data;
rest->data = 0;
- if (option[0] != '-' && option[0] != '+')
+ if (option && option[0] != '-' && option[0] != '+')
{
if (debug_p)
fprintf (stderr, "%s: WARNING: %s: not a switch: \"%s\"\n",
@@ -1737,7 +1750,8 @@ get_description (GList *parms, gboolean verbose_p)
s++;
else if (s[1] == ' ' || s[1] == '\t')
s++; /* next line is indented: leave newline */
- else if (!strncmp(s+1, "http:", 5))
+ else if (!strncmp(s+1, "http:", 5) ||
+ !strncmp(s+1, "https:", 5))
s++; /* next line begins a URL: leave newline */
else
s[0] = ' '; /* delete newline to un-fold this line */
@@ -1770,7 +1784,7 @@ get_description (GList *parms, gboolean verbose_p)
}
#if 0
- if (verbose_p)
+ /*if (verbose_p)*/
{
fprintf (stderr, "%s: text read is \"%s\"\n", blurb(),doc->string);
fprintf (stderr, "%s: description is \"%s\"\n", blurb(), d);
diff --git a/driver/demo-Gtk.c b/driver/demo-Gtk.c
index da98c53..b5e82e2 100644
--- a/driver/demo-Gtk.c
+++ b/driver/demo-Gtk.c
@@ -1,5 +1,5 @@
/* demo-Gtk.c --- implements the interactive demo-mode and options dialogs.
- * xscreensaver, Copyright (c) 1993-2020 Jamie Zawinski <jwz@jwz.org>
+ * 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
@@ -16,6 +16,8 @@
#ifdef HAVE_GTK /* whole file */
+#include "blurb.h"
+
#include <xscreensaver-intl.h>
#include <stdlib.h>
@@ -35,18 +37,13 @@
# include <locale.h>
#endif /* ENABLE_NLS */
-#ifndef VMS
-# include <pwd.h> /* for getpwuid() */
-#else /* VMS */
-# include "vms-pwd.h"
-#endif /* VMS */
-
#ifdef HAVE_UNAME
# include <sys/utsname.h> /* for uname() */
#endif /* HAVE_UNAME */
#include <stdio.h>
#include <time.h>
+#include <pwd.h> /* for getpwuid() */
#include <sys/stat.h>
#include <sys/time.h>
@@ -68,20 +65,19 @@
#include <X11/IntrinsicP.h>
#include <X11/ShellP.h>
-#ifdef HAVE_XMU
-# ifndef VMS
-# include <X11/Xmu/Error.h>
-# else /* VMS */
-# include <Xmu/Error.h>
-# endif
-#else
-# include "xmu.h"
-#endif
-
#ifdef HAVE_XINERAMA
# include <X11/extensions/Xinerama.h>
#endif /* HAVE_XINERAMA */
+#if (__GNUC__ >= 4) /* Ignore useless warnings generated by gtk.h */
+# undef inline
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wstrict-prototypes"
+# pragma GCC diagnostic ignored "-Wlong-long"
+# pragma GCC diagnostic ignored "-Wvariadic-macros"
+# pragma GCC diagnostic ignored "-Wpedantic"
+#endif
+
#include <gtk/gtk.h>
#ifdef HAVE_CRAPPLET
@@ -92,38 +88,36 @@
#include <gdk/gdkx.h>
#ifdef HAVE_GTK2
-# include <glade/glade-xml.h>
# include <gmodule.h>
#else /* !HAVE_GTK2 */
# define G_MODULE_EXPORT /**/
#endif /* !HAVE_GTK2 */
-#if defined(DEFAULT_ICONDIR) && !defined(GLADE_DIR)
-# define GLADE_DIR DEFAULT_ICONDIR
-#endif
-#if !defined(DEFAULT_ICONDIR) && defined(GLADE_DIR)
-# define DEFAULT_ICONDIR GLADE_DIR
-#endif
-
#ifndef HAVE_XML
/* Kludge: this is defined in demo-Gtk-conf.c when HAVE_XML.
It is unused otherwise, so in that case, stub it out. */
static const char *hack_configuration_path = 0;
#endif
+#if (__GNUC__ >= 4)
+# pragma GCC diagnostic pop
+#endif
#include "version.h"
-#include "prefs.h"
+#include "types.h"
#include "resources.h" /* for parse_time() */
-#include "visual.h" /* for has_writable_cells() */
#include "remote.h" /* for xscreensaver_command() */
+#include "visual.h"
+#include "atoms.h"
#include "usleep.h"
+#include "xmu.h"
#include "logo-50.xpm"
#include "logo-180.xpm"
#include "demo-Gtk-conf.h"
+#include "atoms.h"
#include <stdio.h>
#include <string.h>
@@ -182,14 +176,6 @@ static void hack_subproc_environment (Window preview_window_id, Bool debug_p);
#define countof(x) (sizeof((x))/sizeof((*x)))
-/* You might think that to read an array of 32-bit quantities out of a
- server-side property, you would pass an array of 32-bit data quantities
- into XGetWindowProperty(). You would be wrong. You have to use an array
- of longs, even if long is 64 bits (using 32 of each 64.)
- */
-typedef long PROP32;
-
-char *progname = 0;
char *progclass = "XScreenSaver";
XrmDatabase db;
@@ -208,7 +194,7 @@ typedef struct {
conf_data *cdata; /* private data for per-hack configuration */
#ifdef HAVE_GTK2
- GladeXML *glade_ui; /* Glade UI file */
+ GtkBuilder *gtk_ui; /* UI file */
#endif /* HAVE_GTK2 */
Bool debug_p; /* whether to print diagnostics */
@@ -247,13 +233,6 @@ typedef struct {
a closure object of our own down into the various widget callbacks. */
static state *global_state_kludge;
-Atom XA_VROOT;
-Atom XA_SCREENSAVER, XA_SCREENSAVER_RESPONSE, XA_SCREENSAVER_VERSION;
-Atom XA_SCREENSAVER_ID, XA_SCREENSAVER_STATUS, XA_SELECT, XA_DEMO;
-Atom XA_ACTIVATE, XA_SUSPEND, XA_BLANK, XA_LOCK, XA_RESTART, XA_EXIT;
-Atom XA_NEXT, XA_PREV;
-
-
static void populate_demo_window (state *, int list_elt);
static void populate_prefs_page (state *);
static void populate_popup_window (state *);
@@ -272,16 +251,15 @@ static void kill_preview_subproc (state *, Bool reset_p);
static void schedule_preview_check (state *);
-/* Prototypes of functions used by the Glade-generated code,
- to avoid warnings.
+/* Prototypes of functions used by the Gtk-generated code, to avoid warnings.
*/
-void exit_menu_cb (GtkMenuItem *, gpointer user_data);
-void about_menu_cb (GtkMenuItem *, gpointer user_data);
-void doc_menu_cb (GtkMenuItem *, gpointer user_data);
-void file_menu_cb (GtkMenuItem *, gpointer user_data);
-void activate_menu_cb (GtkMenuItem *, gpointer user_data);
-void lock_menu_cb (GtkMenuItem *, gpointer user_data);
-void kill_menu_cb (GtkMenuItem *, gpointer user_data);
+void exit_menu_cb (GtkAction *, gpointer user_data);
+void about_menu_cb (GtkAction *, gpointer user_data);
+void doc_menu_cb (GtkAction *, gpointer user_data);
+void file_menu_cb (GtkAction *, gpointer user_data);
+void activate_menu_cb (GtkAction *, gpointer user_data);
+void lock_menu_cb (GtkAction *, gpointer user_data);
+void kill_menu_cb (GtkAction *, gpointer user_data);
void restart_menu_cb (GtkWidget *, gpointer user_data);
void run_this_cb (GtkButton *, gpointer user_data);
void manual_cb (GtkButton *, gpointer user_data);
@@ -303,33 +281,14 @@ void settings_switch_page_cb (GtkNotebook *, GtkNotebookPage *,
gint page_num, gpointer user_data);
void settings_cancel_cb (GtkButton *, gpointer user_data);
void settings_ok_cb (GtkButton *, gpointer user_data);
+void preview_theme_cb (GtkWidget *, gpointer user_data);
static void kill_gnome_screensaver (void);
static void kill_kde_screensaver (void);
-
/* Some random utility functions
*/
-const char *blurb (void);
-
-const char *
-blurb (void)
-{
- time_t now = time ((time_t *) 0);
- char *ct = (char *) ctime (&now);
- static char buf[255];
- int n = strlen(progname);
- if (n > 100) n = 99;
- strncpy(buf, progname, n);
- buf[n++] = ':';
- buf[n++] = ' ';
- strncpy(buf+n, ct+11, 8);
- strcpy(buf+n+9, ": ");
- return buf;
-}
-
-
static GtkWidget *
name_to_widget (state *s, const char *name)
{
@@ -339,38 +298,49 @@ name_to_widget (state *s, const char *name)
if (!*name) abort();
#ifdef HAVE_GTK2
- if (!s->glade_ui)
+ if (!s->gtk_ui)
{
- /* First try to load the Glade file from the current directory;
+ /* First try to load the UI file from the current directory;
if there isn't one there, check the installed directory.
*/
-# define GLADE_FILE_NAME "xscreensaver-demo.glade2"
- const char * const files[] = { GLADE_FILE_NAME,
- GLADE_DIR "/" GLADE_FILE_NAME };
+# define UI_FILE "xscreensaver.ui"
+ const char * const files[] = { UI_FILE,
+ DEFAULT_ICONDIR "/" UI_FILE };
int i;
+
+ s->gtk_ui = gtk_builder_new ();
+
for (i = 0; i < countof (files); i++)
{
struct stat st;
if (!stat (files[i], &st))
{
- s->glade_ui = glade_xml_new (files[i], NULL, NULL);
- break;
+ GError* error = NULL;
+
+ if (gtk_builder_add_from_file (s->gtk_ui, files[i], &error))
+ break;
+ else
+ {
+ g_warning ("Couldn't load builder file %s: %s",
+ files[i], error->message);
+ g_error_free (error);
+ }
}
}
- if (!s->glade_ui)
+ if (i >= countof (files))
{
fprintf (stderr,
- "%s: could not load \"" GLADE_FILE_NAME "\"\n"
- "\tfrom " GLADE_DIR "/ or current directory.\n",
+ "%s: could not load \"" UI_FILE "\"\n"
+ "\tfrom " DEFAULT_ICONDIR "/ or current directory.\n",
blurb());
exit (-1);
}
-# undef GLADE_FILE_NAME
+# undef UI_FILE
- glade_xml_signal_autoconnect (s->glade_ui);
+ gtk_builder_connect_signals (s->gtk_ui, NULL);
}
- w = glade_xml_get_widget (s->glade_ui, name);
+ w = GTK_WIDGET (gtk_builder_get_object (s->gtk_ui, name));
#else /* !HAVE_GTK2 */
@@ -382,7 +352,7 @@ name_to_widget (state *s, const char *name)
#endif /* HAVE_GTK2 */
if (w) return w;
- fprintf (stderr, "%s: no widget \"%s\" (wrong Glade file?)\n",
+ fprintf (stderr, "%s: no widget \"%s\" (wrong UI file?)\n",
blurb(), name);
abort();
}
@@ -774,12 +744,12 @@ run_hack (state *s, int list_elt, Bool report_errors_p)
/* Button callbacks
According to Eric Lassauge, this G_MODULE_EXPORT crud is needed to make
- libglade work on Cygwin; apparently all Glade callbacks need this magic
- extra declaration. I do not pretend to understand.
+ GTK work on Cygwin; apparently all GTK callbacks need this magic extra
+ declaration. I do not pretend to understand.
*/
G_MODULE_EXPORT void
-exit_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
+exit_menu_cb (GtkAction *menu_action, gpointer user_data)
{
state *s = global_state_kludge; /* I hate C so much... */
flush_dialog_changes_and_save (s);
@@ -798,28 +768,27 @@ wm_toplevel_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data)
G_MODULE_EXPORT void
-about_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
+about_menu_cb (GtkAction *menu_action, gpointer user_data)
{
+#if 1
+ /* Let's just pop up the splash dialog instead. */
+ preview_theme_cb (NULL, user_data);
+#else
char msg [2048];
- char *vers = strdup (screensaver_id + 4);
- char *s, *s2;
char copy[1024];
- char year[5];
char *desc = _("For updates, check https://www.jwz.org/xscreensaver/");
- s = strchr (vers, ',');
+ 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;
- s += 2;
-
- s2 = vers;
- s2 = strrchr (vers, '-');
- s2++;
- strncpy (year, s2, 4);
- year[4] = 0;
/* Ole Laursen <olau@hardworking.dk> says "don't use _() here because
non-ASCII characters aren't allowed in localizable string keys."
- (I don't want to just use (c) instead of © because that doesn't
+ (I don't want to just use (c) instead of © because that doesn't
look as good in the plain-old default Latin1 "C" locale.)
*/
#ifdef HAVE_GTK2
@@ -837,7 +806,7 @@ about_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
#ifdef HAVE_CRAPPLET
{
const gchar *auth[] = { 0 };
- GtkWidget *about = gnome_about_new (progclass, vers, "", auth, desc,
+ GtkWidget *about = gnome_about_new (progclass, version, "", auth, desc,
"xscreensaver.xpm");
gtk_widget_show (about);
}
@@ -851,7 +820,8 @@ about_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
GtkWidget *dialog = gtk_dialog_new ();
GtkWidget *hbox, *icon, *vbox, *label1, *label2, *hb, *ok;
- GtkWidget *parent = GTK_WIDGET (menuitem);
+ GSList *proxies = gtk_action_get_proxies (menu_action);
+ GtkWidget *parent = GTK_WIDGET (proxies->data);
while (GET_PARENT (parent))
parent = GET_PARENT (parent);
@@ -871,7 +841,7 @@ about_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
vbox = gtk_vbox_new (FALSE, 0);
gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
- label1 = gtk_label_new (vers);
+ label1 = gtk_label_new (version);
gtk_box_pack_start (GTK_BOX (vbox), label1, TRUE, TRUE, 0);
gtk_label_set_justify (GTK_LABEL (label1), GTK_JUSTIFY_LEFT);
gtk_misc_set_alignment (GTK_MISC (label1), 0.0, 0.75);
@@ -928,11 +898,12 @@ about_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
gdk_window_show (GET_WINDOW (GTK_WIDGET (dialog)));
gdk_window_raise (GET_WINDOW (GTK_WIDGET (dialog)));
}
+#endif /* 0 */
}
G_MODULE_EXPORT void
-doc_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
+doc_menu_cb (GtkAction *menu_action, gpointer user_data)
{
state *s = global_state_kludge; /* I hate C so much... */
saver_preferences *p = &s->prefs;
@@ -947,11 +918,11 @@ doc_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
}
help_command = (char *) malloc (strlen (p->load_url_command) +
- (strlen (p->help_url) * 4) + 20);
+ (strlen (p->help_url) * 5) + 20);
strcpy (help_command, "( ");
sprintf (help_command + strlen(help_command),
p->load_url_command,
- p->help_url, p->help_url, p->help_url, p->help_url);
+ p->help_url, p->help_url, p->help_url, p->help_url, p->help_url);
strcat (help_command, " ) &");
if (system (help_command) < 0)
fprintf (stderr, "%s: fork error\n", blurb());
@@ -960,7 +931,7 @@ doc_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
G_MODULE_EXPORT void
-file_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
+file_menu_cb (GtkAction *menu_action, gpointer user_data)
{
state *s = global_state_kludge; /* I hate C so much... */
sensitize_menu_items (s, False);
@@ -968,7 +939,7 @@ file_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
G_MODULE_EXPORT void
-activate_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
+activate_menu_cb (GtkAction *menu_action, gpointer user_data)
{
state *s = global_state_kludge; /* I hate C so much... */
run_cmd (s, XA_ACTIVATE, 0);
@@ -976,7 +947,7 @@ activate_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
G_MODULE_EXPORT void
-lock_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
+lock_menu_cb (GtkAction *menu_action, gpointer user_data)
{
state *s = global_state_kludge; /* I hate C so much... */
run_cmd (s, XA_LOCK, 0);
@@ -984,7 +955,7 @@ lock_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
G_MODULE_EXPORT void
-kill_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
+kill_menu_cb (GtkAction *menu_action, gpointer user_data)
{
state *s = global_state_kludge; /* I hate C so much... */
run_cmd (s, XA_EXIT, 0);
@@ -998,7 +969,7 @@ restart_menu_cb (GtkWidget *widget, gpointer user_data)
flush_dialog_changes_and_save (s);
xscreensaver_command (GDK_DISPLAY(), XA_EXIT, 0, False, NULL);
sleep (1);
- if (system ("xscreensaver -nosplash &") < 0)
+ if (system ("xscreensaver -splash &") < 0)
fprintf (stderr, "%s: fork error\n", blurb());
await_xscreensaver (s);
@@ -1084,12 +1055,6 @@ demo_write_init_file (state *s, saver_preferences *p)
{
Display *dpy = GDK_DISPLAY();
-#if 0
- /* #### try to figure out why shit keeps getting reordered... */
- if (strcmp (s->prefs.screenhacks[0]->name, "DNA Lounge Slideshow"))
- abort();
-#endif
-
if (!write_init_file (dpy, p, s->short_version, False))
{
if (s->debug_p)
@@ -1191,7 +1156,7 @@ force_list_select_item (state *s, GtkWidget *list, int list_elt, Bool scroll_p)
if (!was) gtk_widget_set_sensitive (parent, True);
#ifdef HAVE_GTK2
model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
- g_assert (model);
+ if (!model) abort();
if (gtk_tree_model_iter_nth_child (model, &iter, NULL, list_elt))
{
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
@@ -1485,6 +1450,25 @@ flush_checkbox (GtkTreeModel *model,
#endif /* HAVE_GTK2 */
+
+static char *
+theme_name_strip (const char *s)
+{
+ const char *in = s;
+ char *s2 = strdup(s);
+ char *out = s2;
+ for (; *in; in++)
+ if (*in >= 'A' && *in <= 'Z')
+ *out++ = *in + ('a'-'A');
+ else if (*in == ' ' || *in == '\t')
+ ;
+ else
+ *out++ = *in;
+ *out = 0;
+ return s2;
+}
+
+
/* Flush out any changes made in the main dialog window (where changes
take place immediately: clicking on a checkbox causes the init file
to be written right away.)
@@ -1492,6 +1476,7 @@ flush_checkbox (GtkTreeModel *model,
static Bool
flush_dialog_changes_and_save (state *s)
{
+ Display *dpy = GDK_DISPLAY();
saver_preferences *p = &s->prefs;
saver_preferences P2, *p2 = &P2;
#ifdef HAVE_GTK2
@@ -1578,7 +1563,6 @@ flush_dialog_changes_and_save (state *s)
#if 0
CHECKBOX (p2->verbose_p, "verbose_button");
- CHECKBOX (p2->capture_stderr_p, "capture_button");
CHECKBOX (p2->splash_p, "splash_button");
#endif
@@ -1596,7 +1580,6 @@ flush_dialog_changes_and_save (state *s)
TEXT (p2->text_url, "text_url_entry");
}
- CHECKBOX (p2->install_cmap_p, "install_button");
CHECKBOX (p2->fade_p, "fade_button");
CHECKBOX (p2->unfade_p, "unfade_button");
SECONDS (&p2->fade_seconds, "fade_spinbutton");
@@ -1610,12 +1593,13 @@ flush_dialog_changes_and_save (state *s)
/* Warn if the image directory doesn't exist, when:
- not being warned before
- image directory is changed and the directory doesn't exist
- - image directory does not begin with http://
+ - image directory is not a URL
*/
if (p2->image_directory &&
*p2->image_directory &&
!directory_p (p2->image_directory) &&
- strncmp(p2->image_directory, "http://", 6) &&
+ strncmp(p2->image_directory, "http://", 7) &&
+ strncmp(p2->image_directory, "https://", 8) &&
( !already_warned_about_missing_image_directory ||
( p->image_directory &&
*p->image_directory &&
@@ -1634,11 +1618,8 @@ flush_dialog_changes_and_save (state *s)
/* Map the mode menu to `saver_mode' enum values. */
{
- GtkOptionMenu *opt = GTK_OPTION_MENU (name_to_widget (s, "mode_menu"));
- GtkMenu *menu = GTK_MENU (gtk_option_menu_get_menu (opt));
- GtkWidget *selected = gtk_menu_get_active (menu);
- GList *kids = gtk_container_children (GTK_CONTAINER (menu));
- int menu_elt = g_list_index (kids, (gpointer) selected);
+ GtkComboBox *opt = GTK_COMBO_BOX (name_to_widget (s, "mode_menu"));
+ int menu_elt = gtk_combo_box_get_active (opt);
if (menu_elt < 0 || menu_elt >= countof(mode_menu_order)) abort();
p2->mode = mode_menu_order[menu_elt];
}
@@ -1651,11 +1632,33 @@ flush_dialog_changes_and_save (state *s)
: -1);
}
+ /* Theme menu. */
+ {
+ GtkComboBox *cbox = GTK_COMBO_BOX (name_to_widget (s, "theme_menu"));
+ char *themes = get_string_resource (dpy, "themeNames", "ThemeNames");
+ int menu_index = gtk_combo_box_get_active (cbox);
+ char *token = themes;
+ char *name, *last;
+ int i = 0;
+ while ((name = strtok_r (token, ",", &last)))
+ {
+ token = 0;
+ if (i == menu_index)
+ {
+ char *name2 = theme_name_strip (name);
+ if (p->dialog_theme) free (p->dialog_theme);
+ p2->dialog_theme = name2;
+ }
+ i++;
+ }
+ }
+
# define COPY(field, name) \
if (p->field != p2->field) { \
changed = True; \
if (s->debug_p) \
- fprintf (stderr, "%s: %s => %d\n", blurb(), name, (int) p2->field); \
+ fprintf (stderr, "%s: %s => %ld\n", blurb(), \
+ name, (unsigned long) p2->field); \
} \
p->field = p2->field
@@ -1675,7 +1678,6 @@ flush_dialog_changes_and_save (state *s)
#if 0
COPY(verbose_p, "verbose_p");
- COPY(capture_stderr_p, "capture_stderr_p");
COPY(splash_p, "splash_p");
#endif
@@ -1690,6 +1692,7 @@ flush_dialog_changes_and_save (state *s)
COPY(grab_video_p, "grab_video_p");
COPY(random_image_p, "random_image_p");
+ COPY(dialog_theme, "dialog_theme");
# undef COPY
# define COPYSTR(FIELD,NAME) \
@@ -1717,14 +1720,7 @@ flush_dialog_changes_and_save (state *s)
if (changed)
{
- Display *dpy = GDK_DISPLAY();
- Bool enabled_p = (p->dpms_enabled_p && p->mode != DONT_BLANK);
- sync_server_dpms_settings (dpy, enabled_p, p->dpms_quickoff_p,
- p->dpms_standby / 1000,
- p->dpms_suspend / 1000,
- p->dpms_off / 1000,
- False);
-
+ sync_server_dpms_settings (GDK_DISPLAY(), p);
changed = demo_write_init_file (s, p);
}
@@ -1744,9 +1740,10 @@ flush_popup_changes_and_save (state *s)
int list_elt = selected_list_element (s);
GtkEntry *cmd = GTK_ENTRY (name_to_widget (s, "cmd_text"));
- GtkCombo *vis = GTK_COMBO (name_to_widget (s, "visual_combo"));
+ GtkComboBoxEntry *vis = GTK_COMBO_BOX_ENTRY (name_to_widget (s, "visual_combo"));
+ GtkEntry *visent = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (vis)));
- const char *visual = gtk_entry_get_text (GTK_ENTRY (GTK_COMBO (vis)->entry));
+ const char *visual = gtk_entry_get_text (visent);
const char *command = gtk_entry_get_text (cmd);
char c;
@@ -1791,7 +1788,7 @@ flush_popup_changes_and_save (state *s)
{
gdk_beep (); /* unparsable */
visual = "";
- gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry), _("Any"));
+ gtk_entry_set_text (visent, _("Any"));
}
changed = flush_changes (s, list_elt, -1, command, visual);
@@ -1839,21 +1836,8 @@ mode_menu_item_cb (GtkWidget *widget, gpointer user_data)
GtkWidget *list = name_to_widget (s, "list");
int list_elt;
- GList *menu_items =
- gtk_container_children (GTK_CONTAINER (GET_PARENT (widget)));
- int menu_index = 0;
- saver_mode new_mode;
-
- while (menu_items)
- {
- if (menu_items->data == widget)
- break;
- menu_index++;
- menu_items = menu_items->next;
- }
- if (!menu_items) abort();
-
- new_mode = mode_menu_order[menu_index];
+ int menu_index = gtk_combo_box_get_active (GTK_COMBO_BOX (widget));
+ saver_mode new_mode = mode_menu_order[menu_index];
/* Keep the same list element displayed as before; except if we're
switching *to* "one screensaver" mode from any other mode, set
@@ -1901,7 +1885,7 @@ list_activated_cb (GtkTreeView *list,
char *str;
int list_elt;
- g_return_if_fail (!gdk_pointer_is_grabbed ());
+ if (gdk_pointer_is_grabbed()) return;
str = gtk_tree_path_to_string (path);
list_elt = strtol (str, NULL, 10);
@@ -2305,7 +2289,12 @@ browse_text_program_cb (GtkButton *button, gpointer user_data)
}
-
+G_MODULE_EXPORT void
+preview_theme_cb (GtkWidget *w, gpointer user_data)
+{
+ if (system ("xscreensaver-auth --splash &") < 0)
+ fprintf (stderr, "%s: splash exec failed\n", blurb());
+}
G_MODULE_EXPORT void
@@ -2761,6 +2750,7 @@ update_list_sensitivity (state *s)
static void
populate_prefs_page (state *s)
{
+ Display *dpy = GDK_DISPLAY();
saver_preferences *p = &s->prefs;
Bool can_lock_p = True;
@@ -2813,7 +2803,6 @@ populate_prefs_page (state *s)
TOGGLE_ACTIVE ("lock_button", p->lock_p);
#if 0
TOGGLE_ACTIVE ("verbose_button", p->verbose_p);
- TOGGLE_ACTIVE ("capture_button", p->capture_stderr_p);
TOGGLE_ACTIVE ("splash_button", p->splash_p);
#endif
TOGGLE_ACTIVE ("dpms_button", p->dpms_enabled_p);
@@ -2821,7 +2810,6 @@ populate_prefs_page (state *s)
TOGGLE_ACTIVE ("grab_desk_button", p->grab_desktop_p);
TOGGLE_ACTIVE ("grab_video_button", p->grab_video_p);
TOGGLE_ACTIVE ("grab_image_button", p->random_image_p);
- TOGGLE_ACTIVE ("install_button", p->install_cmap_p);
TOGGLE_ACTIVE ("fade_button", p->fade_p);
TOGGLE_ACTIVE ("unfade_button", p->unfade_p);
@@ -2866,40 +2854,73 @@ populate_prefs_page (state *s)
p->tmode == TEXT_URL);
+ /* Theme menu */
+ {
+ GtkComboBox *cbox = GTK_COMBO_BOX (name_to_widget (s, "theme_menu"));
+
+ /* Without this, pref_changed_cb gets called an exponentially-increasing
+ number of times on the themes menu, despite the call to
+ gtk_list_store_clear(). */
+ static Bool done_once = False;
+
+ if (cbox && !done_once)
+ {
+ char *themes = get_string_resource (dpy, "themeNames", "ThemeNames");
+ char *token = themes;
+ char *name, *name2, *last;
+ GtkListStore *model;
+ GtkTreeIter iter;
+ int i = 0;
+ done_once = True;
+
+ g_object_get (G_OBJECT (cbox), "model", &model, NULL);
+ if (!model) abort();
+ gtk_list_store_clear (model);
+
+ gtk_signal_connect (GTK_OBJECT (cbox), "changed",
+ GTK_SIGNAL_FUNC (pref_changed_cb), (gpointer) s);
+
+ while ((name = strtok_r (token, ",", &last)))
+ {
+ int L;
+ token = 0;
+
+ /* Strip leading and trailing whitespace */
+ while (*name == ' ' || *name == '\t' || *name == '\n')
+ name++;
+ L = strlen(name);
+ while (L && (name[L-1] == ' ' || name[L-1] == '\t' ||
+ name[L-1] == '\n'))
+ name[--L] = 0;
+
+ gtk_list_store_append (model, &iter);
+ gtk_list_store_set (model, &iter, 0, name, -1);
+
+ name2 = theme_name_strip (name);
+ if (!strcmp (p->dialog_theme, name2))
+ gtk_combo_box_set_active (cbox, i);
+ free (name2);
+ i++;
+ }
+ }
+ }
+
+
/* Map the `saver_mode' enum to mode menu to values. */
{
- GtkOptionMenu *opt = GTK_OPTION_MENU (name_to_widget (s, "mode_menu"));
+ GtkComboBox *opt = GTK_COMBO_BOX (name_to_widget (s, "mode_menu"));
int i;
for (i = 0; i < countof(mode_menu_order); i++)
if (mode_menu_order[i] == p->mode)
break;
- gtk_option_menu_set_history (opt, i);
+ gtk_combo_box_set_active (opt, i);
update_list_sensitivity (s);
}
{
- Bool found_any_writable_cells = False;
- Bool fading_possible = False;
Bool dpms_supported = False;
-
Display *dpy = GDK_DISPLAY();
- int nscreens = ScreenCount(dpy); /* real screens, not Xinerama */
- int i;
- for (i = 0; i < nscreens; i++)
- {
- Screen *s = ScreenOfDisplay (dpy, i);
- if (has_writable_cells (s, DefaultVisualOfScreen (s)))
- {
- found_any_writable_cells = True;
- break;
- }
- }
-
- fading_possible = found_any_writable_cells;
-#ifdef HAVE_XF86VMODE_GAMMA
- fading_possible = True;
-#endif
#ifdef HAVE_DPMS_EXTENSION
{
@@ -2923,7 +2944,6 @@ populate_prefs_page (state *s)
*/
SENSITIZE ("dpms_frame", dpms_supported);
SENSITIZE ("dpms_button", dpms_supported);
- SENSITIZE ("dpms_quickoff_button", dpms_supported);
SENSITIZE ("dpms_standby_label", dpms_supported && p->dpms_enabled_p);
SENSITIZE ("dpms_standby_mlabel", dpms_supported && p->dpms_enabled_p);
@@ -2934,24 +2954,26 @@ populate_prefs_page (state *s)
SENSITIZE ("dpms_off_label", dpms_supported && p->dpms_enabled_p);
SENSITIZE ("dpms_off_mlabel", dpms_supported && p->dpms_enabled_p);
SENSITIZE ("dpms_off_spinbutton", dpms_supported && p->dpms_enabled_p);
+ SENSITIZE ("dpms_quickoff_button", dpms_supported);
- /* Colormaps
- */
- SENSITIZE ("cmap_frame", found_any_writable_cells || fading_possible);
- SENSITIZE ("install_button", found_any_writable_cells);
- SENSITIZE ("fade_button", fading_possible);
- SENSITIZE ("unfade_button", fading_possible);
-
- SENSITIZE ("fade_label", (fading_possible &&
- (p->fade_p || p->unfade_p)));
- SENSITIZE ("fade_spinbutton", (fading_possible &&
- (p->fade_p || p->unfade_p)));
+ SENSITIZE ("fade_label", (p->fade_p || p->unfade_p));
+ SENSITIZE ("fade_spinbutton", (p->fade_p || p->unfade_p));
# undef SENSITIZE
}
}
+/* Allow the documentation label to re-flow when the text is changed.
+ http://blog.borovsak.si/2009/05/wrapping-adn-resizing-gtklabel.html
+ */
+static void
+cb_allocate (GtkWidget *label, GtkAllocation *allocation, gpointer data)
+{
+ gtk_widget_set_size_request (label, allocation->width - 8, -1);
+}
+
+
static void
populate_popup_window (state *s)
{
@@ -2962,6 +2984,9 @@ populate_popup_window (state *s)
gtk_label_set_selectable (doc);
*/
+ g_signal_connect (G_OBJECT (doc), "size-allocate",
+ G_CALLBACK (cb_allocate), NULL);
+
# ifdef HAVE_XML
if (s->cdata)
{
@@ -2998,6 +3023,14 @@ populate_popup_window (state *s)
gtk_label_set_text (doc, (doc_string
? _(doc_string)
: _("No description available.")));
+
+ {
+ GtkWidget *w = name_to_widget (s, "dialog_vbox");
+ gtk_widget_hide (w);
+ gtk_widget_unrealize (w);
+ gtk_widget_realize (w);
+ gtk_widget_show (w);
+ }
}
@@ -3022,7 +3055,7 @@ sensitize_menu_items (state *s, Bool force_p)
static Bool running_p = False;
static time_t last_checked = 0;
time_t now = time ((time_t *) 0);
- const char *names[] = { "activate_menu", "lock_menu", "kill_menu",
+ const char *names[] = { "activate_action", "lock_action", "kill_action",
/* "demo" */ };
int i;
@@ -3034,8 +3067,8 @@ sensitize_menu_items (state *s, Bool force_p)
for (i = 0; i < countof(names); i++)
{
- GtkWidget *w = name_to_widget (s, names[i]);
- gtk_widget_set_sensitive (GTK_WIDGET(w), running_p);
+ GtkAction *a = GTK_ACTION (gtk_builder_get_object (s->gtk_ui, names[i]));
+ gtk_action_set_sensitive (a, running_p);
}
}
@@ -3107,7 +3140,7 @@ fix_text_entry_sizes (state *s)
/* Now fix the width of the combo box.
*/
w = GTK_WIDGET (name_to_widget (s, "visual_combo"));
- w = GTK_COMBO (w)->entry;
+ w = GTK_COMBO_BOX_ENTRY (w)->entry;
width = gdk_string_width (w->style->font, "PseudoColor___");
gtk_widget_set_usize (w, width, -2);
@@ -3313,7 +3346,7 @@ populate_demo_window (state *s, int list_elt)
GtkFrame *frame1 = GTK_FRAME (name_to_widget (s, "preview_frame"));
GtkFrame *frame2 = GTK_FRAME (name_to_widget (s, "opt_frame"));
GtkEntry *cmd = GTK_ENTRY (name_to_widget (s, "cmd_text"));
- GtkCombo *vis = GTK_COMBO (name_to_widget (s, "visual_combo"));
+ GtkComboBoxEntry *vis = GTK_COMBO_BOX_ENTRY (name_to_widget (s, "visual_combo"));
GtkWidget *list = GTK_WIDGET (name_to_widget (s, "list"));
if (p->mode == BLANK_ONLY)
@@ -3363,7 +3396,7 @@ populate_demo_window (state *s, int list_elt)
gtk_window_set_title (GTK_WINDOW (s->popup_widget), title);
}
- gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry),
+ gtk_entry_set_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (vis))),
(hack
? (hack->visual && *hack->visual
? hack->visual
@@ -3471,10 +3504,11 @@ initialize_sort_map (state *s)
char *name = (hack->name && *hack->name
? strdup (hack->name)
: make_hack_name (dpy, hack->command));
- char *str;
- for (str = name; *str; str++)
- *str = tolower(*str);
- sort_hack_cmp_names_kludge[i] = name;
+ gchar *s2 = g_str_to_ascii (name, 0); /* Sort "Möbius" properly */
+ gchar *s3 = g_ascii_strdown (s2, -1);
+ free (name);
+ free (s2);
+ sort_hack_cmp_names_kludge[i] = s3;
}
/* Sort list->hack map alphabetically
@@ -3782,7 +3816,7 @@ get_best_gl_visual (state *s)
char *av[10];
int ac = 0;
- av[ac++] = "xscreensaver-gl-helper";
+ av[ac++] = "xscreensaver-gl-visual";
av[ac] = 0;
if (pipe (fds))
@@ -4395,7 +4429,9 @@ gnome_screensaver_window (Screen *screen)
&bytesafter, &name)
== Success
&& type != None
- && !strcmp ((char *) name, "gnome-screensaver"))
+ && (!strcmp ((char *) name, "gnome-screensaver") ||
+ !strcmp ((char *) name, "mate-screensaver") ||
+ !strcmp ((char *) name, "cinnamon-screensaver")))
{
gnome_window = kids[i];
break;
@@ -4464,11 +4500,9 @@ the_network_is_not_the_computer (state *s)
lhost = "<UNKNOWN>";
else
lhost = uts.nodename;
-# elif defined(VMS)
- strcpy (lhost, getenv("SYS$NODE"));
-# else /* !HAVE_UNAME && !VMS */
+# else /* !HAVE_UNAME */
strcat (lhost, "<UNKNOWN>");
-# endif /* !HAVE_UNAME && !VMS */
+# endif /* !HAVE_UNAME */
if (p && p->pw_name)
luser = p->pw_name;
@@ -4791,10 +4825,7 @@ main (int argc, char **argv)
progname = real_progname;
- s->short_version = (char *) malloc (5);
- memcpy (s->short_version, screensaver_id + 17, 4);
- s->short_version [4] = 0;
-
+ s->short_version = XSCREENSAVER_VERSION;
/* Register our error message logger for every ``log domain'' known.
There's no way to do this globally, so I grepped the Gtk/Gdk sources
@@ -5007,7 +5038,7 @@ main (int argc, char **argv)
dpy = XtDisplay (toplevel_shell);
db = XtDatabase (dpy);
- XtGetApplicationNameAndClass (dpy, &progname, &progclass);
+ XtGetApplicationNameAndClass (dpy, (char **) &progname, &progclass);
XSetErrorHandler (demo_ehandler);
/* Let's just ignore these. They seem to confuse Irix Gtk... */
@@ -5046,6 +5077,7 @@ main (int argc, char **argv)
p->db = db;
s->nscreens = screen_count (dpy);
+ init_xscreensaver_atoms (dpy);
hack_environment (s); /* must be before initialize_sort_map() */
load_init_file (dpy, p);
@@ -5069,26 +5101,7 @@ main (int argc, char **argv)
}
#endif
-
- /* Intern the atoms that xscreensaver_command() needs.
- */
- XA_VROOT = XInternAtom (dpy, "__SWM_VROOT", False);
- XA_SCREENSAVER = XInternAtom (dpy, "SCREENSAVER", False);
- XA_SCREENSAVER_VERSION = XInternAtom (dpy, "_SCREENSAVER_VERSION",False);
- XA_SCREENSAVER_STATUS = XInternAtom (dpy, "_SCREENSAVER_STATUS", False);
- XA_SCREENSAVER_ID = XInternAtom (dpy, "_SCREENSAVER_ID", False);
- XA_SCREENSAVER_RESPONSE = XInternAtom (dpy, "_SCREENSAVER_RESPONSE", False);
- XA_SELECT = XInternAtom (dpy, "SELECT", False);
- XA_DEMO = XInternAtom (dpy, "DEMO", False);
- XA_ACTIVATE = XInternAtom (dpy, "ACTIVATE", False);
- XA_SUSPEND = XInternAtom (dpy, "SUSPEND", False);
- XA_BLANK = XInternAtom (dpy, "BLANK", False);
- XA_LOCK = XInternAtom (dpy, "LOCK", False);
- XA_NEXT = XInternAtom (dpy, "NEXT", False);
- XA_PREV = XInternAtom (dpy, "PREV", False);
- XA_EXIT = XInternAtom (dpy, "EXIT", False);
- XA_RESTART = XInternAtom (dpy, "RESTART", False);
-
+ init_xscreensaver_atoms (dpy);
/* Create the window and all its widgets.
*/
@@ -5171,31 +5184,32 @@ main (int argc, char **argv)
#endif /* !HAVE_GTK2 */
/* Hook up callbacks to the items on the mode menu. */
- {
- GtkOptionMenu *opt = GTK_OPTION_MENU (name_to_widget (s, "mode_menu"));
- GtkMenu *menu = GTK_MENU (gtk_option_menu_get_menu (opt));
- GList *kids = gtk_container_children (GTK_CONTAINER (menu));
- int i;
- for (i = 0; kids; kids = kids->next, i++)
- {
- gtk_signal_connect (GTK_OBJECT (kids->data), "activate",
- GTK_SIGNAL_FUNC (mode_menu_item_cb),
- (gpointer) s);
-
- /* The "random-same" mode menu item does not appear unless
- there are multple screens.
- */
- if (s->nscreens <= 1 &&
- mode_menu_order[i] == RANDOM_HACKS_SAME)
- gtk_widget_hide (GTK_WIDGET (kids->data));
- }
+ gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "mode_menu")),
+ "changed", GTK_SIGNAL_FUNC (mode_menu_item_cb),
+ (gpointer) s);
+ if (s->nscreens <= 1)
+ {
+ GtkComboBox *opt = GTK_COMBO_BOX (name_to_widget (s, "mode_menu"));
+ GtkTreeModel *list = gtk_combo_box_get_model (opt);
+ unsigned int i;
+ for (i = 0; i < countof(mode_menu_order); i++)
+ {
+ /* The "random-same" mode menu item does not appear unless
+ there are multiple screens.
+ */
+ if (mode_menu_order[i] == RANDOM_HACKS_SAME)
+ {
+ GtkTreeIter iter;
+ gtk_tree_model_iter_nth_child (list, &iter, NULL, i);
+ gtk_list_store_remove (GTK_LIST_STORE (list), &iter);
+ break;
+ }
+ }
- if (s->nscreens <= 1) /* recompute option-menu size */
- {
- gtk_widget_unrealize (GTK_WIDGET (menu));
- gtk_widget_realize (GTK_WIDGET (menu));
- }
- }
+ /* recompute option-menu size */
+ gtk_widget_unrealize (GTK_WIDGET (opt));
+ gtk_widget_realize (GTK_WIDGET (opt));
+ }
/* Handle the -prefs command-line argument. */
@@ -5299,7 +5313,7 @@ main (int argc, char **argv)
the_network_is_not_the_computer (s);
- if (senesculent_p())
+ if (time ((time_t *) 0) - XSCREENSAVER_RELEASED > 60*60*24*30*17)
warning_dialog (s->toplevel_widget,
_("Warning:\n\n"
"This version of xscreensaver is VERY OLD!\n"
@@ -5312,7 +5326,6 @@ main (int argc, char **argv)
),
D_NONE, 7);
-
/* Run the Gtk event loop, and not the Xt event loop. This means that
if there were Xt timers or fds registered, they would never get serviced,
and if there were any Xt widgets, they would never have events delivered.
diff --git a/driver/demo-Xm-widgets.c b/driver/demo-Xm-widgets.c
index cbe3393..d47259b 100644
--- a/driver/demo-Xm-widgets.c
+++ b/driver/demo-Xm-widgets.c
@@ -79,6 +79,8 @@ tab_cb (Widget button, XtPointer client_data, XtPointer ignored)
}
+extern Widget create_xscreensaver_demo (Widget parent);
+
Widget
create_xscreensaver_demo (Widget parent)
{
@@ -539,14 +541,12 @@ create_options_page (Widget parent)
timeoutLabel
cycleLabel
fadeSecondsLabel
- fadeTicksLabel
lockLabel
passwdLabel
timeoutText
cycleText
fadeSecondsText
- fadeTicksText
lockText
passwdText
@@ -566,11 +566,11 @@ create_options_page (Widget parent)
Arg av[64];
int ac = 0;
Widget children[100];
- Widget timeout_label, cycle_label, fade_seconds_label, fade_ticks_label;
+ Widget timeout_label, cycle_label, fade_seconds_label;
Widget lock_label, passwd_label, hr;
Widget preferences_form;
- Widget timeout_text, cycle_text, fade_text, fade_ticks_text;
+ Widget timeout_text, cycle_text, fade_text;
Widget lock_timeout_text, passwd_timeout_text, verbose_toggle;
Widget install_cmap_toggle, fade_toggle, unfade_toggle;
Widget lock_toggle, prefs_done, prefs_cancel;
@@ -601,10 +601,6 @@ create_options_page (Widget parent)
"fadeSecondsLabel", av, ac);
ac = 0;
XtSetArg(av[ac], XmNalignment, XmALIGNMENT_END); ac++;
- fade_ticks_label = XmCreateLabelGadget (preferences_form, "fadeTicksLabel",
- av, ac);
- ac = 0;
- XtSetArg(av[ac], XmNalignment, XmALIGNMENT_END); ac++;
lock_label = XmCreateLabelGadget (preferences_form, "lockLabel", av, ac);
ac = 0;
XtSetArg(av[ac], XmNalignment, XmALIGNMENT_END); ac++;
@@ -613,8 +609,6 @@ create_options_page (Widget parent)
timeout_text = XmCreateTextField (preferences_form, "timeoutText", av, ac);
cycle_text = XmCreateTextField (preferences_form, "cycleText", av, ac);
fade_text = XmCreateTextField (preferences_form, "fadeSecondsText", av, ac);
- fade_ticks_text = XmCreateTextField (preferences_form, "fadeTicksText",
- av, ac);
lock_timeout_text = XmCreateTextField (preferences_form, "lockText",
av, ac);
passwd_timeout_text = XmCreateTextField (preferences_form, "passwdText",
@@ -684,20 +678,6 @@ create_options_page (Widget parent)
XmNrightWidget, fade_text,
NULL);
- XtVaSetValues (fade_ticks_label,
- XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET,
- XmNtopOffset, 0,
- XmNtopWidget, fade_ticks_text,
- XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET,
- XmNbottomOffset, 0,
- XmNbottomWidget, fade_ticks_text,
- XmNleftAttachment, XmATTACH_FORM,
- XmNleftOffset, 20,
- XmNrightAttachment, XmATTACH_WIDGET,
- XmNrightOffset, 4,
- XmNrightWidget, fade_ticks_text,
- NULL);
-
XtVaSetValues (lock_label,
XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET,
XmNtopOffset, 0,
@@ -751,7 +731,7 @@ create_options_page (Widget parent)
XmNleftWidget, cycle_text,
NULL);
- XtVaSetValues (fade_ticks_text,
+ XtVaSetValues (lock_timeout_text,
XmNtopAttachment, XmATTACH_WIDGET,
XmNtopOffset, 2,
XmNtopWidget, fade_text,
@@ -760,15 +740,6 @@ create_options_page (Widget parent)
XmNleftWidget, fade_text,
NULL);
- XtVaSetValues (lock_timeout_text,
- XmNtopAttachment, XmATTACH_WIDGET,
- XmNtopOffset, 2,
- XmNtopWidget, fade_ticks_text,
- XmNleftAttachment, XmATTACH_OPPOSITE_WIDGET,
- XmNleftOffset, 0,
- XmNleftWidget, fade_ticks_text,
- NULL);
-
XtVaSetValues (passwd_timeout_text,
XmNtopAttachment, XmATTACH_WIDGET,
XmNtopOffset, 4,
@@ -822,10 +793,10 @@ create_options_page (Widget parent)
XtVaSetValues (unfade_toggle,
XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET,
XmNtopOffset, 0,
- XmNtopWidget, fade_ticks_text,
+ XmNtopWidget, lock_timeout_text,
XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET,
XmNbottomOffset, 0,
- XmNbottomWidget, fade_ticks_text,
+ XmNbottomWidget, lock_timeout_text,
XmNleftAttachment, XmATTACH_OPPOSITE_WIDGET,
XmNleftOffset, 0,
XmNleftWidget, fade_toggle,
@@ -836,10 +807,10 @@ create_options_page (Widget parent)
XtVaSetValues (lock_toggle,
XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET,
XmNtopOffset, 0,
- XmNtopWidget, lock_timeout_text,
+ XmNtopWidget, passwd_timeout_text,
XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET,
XmNbottomOffset, 0,
- XmNbottomWidget, lock_timeout_text,
+ XmNbottomWidget, passwd_timeout_text,
XmNleftAttachment, XmATTACH_OPPOSITE_WIDGET,
XmNleftOffset, 0,
XmNleftWidget, unfade_toggle,
@@ -872,13 +843,11 @@ create_options_page (Widget parent)
children[ac++] = timeout_label;
children[ac++] = cycle_label;
children[ac++] = fade_seconds_label;
- children[ac++] = fade_ticks_label;
children[ac++] = lock_label;
children[ac++] = passwd_label;
children[ac++] = timeout_text;
children[ac++] = cycle_text;
children[ac++] = fade_text;
- children[ac++] = fade_ticks_text;
children[ac++] = lock_timeout_text;
children[ac++] = passwd_timeout_text;
children[ac++] = verbose_toggle;
diff --git a/driver/demo-Xm.c b/driver/demo-Xm.c
index 3497641..547bbe9 100644
--- a/driver/demo-Xm.c
+++ b/driver/demo-Xm.c
@@ -1,5 +1,5 @@
/* demo-Xm.c --- implements the interactive demo-mode and options dialogs.
- * xscreensaver, Copyright (c) 1993-2003, 2005 Jamie Zawinski <jwz@jwz.org>
+ * 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
@@ -16,23 +16,20 @@
#ifdef HAVE_MOTIF /* whole file */
+#include "blurb.h"
+
#include <stdlib.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
-#ifndef VMS
-# include <pwd.h> /* for getpwuid() */
-#else /* VMS */
-# include "vms-pwd.h"
-#endif /* VMS */
-
#ifdef HAVE_UNAME
# include <sys/utsname.h> /* for uname() */
#endif /* HAVE_UNAME */
#include <stdio.h>
+#include <pwd.h> /* for getpwuid() */
#include <X11/Xproto.h> /* for CARD32 */
#include <X11/Xatom.h> /* for XA_INTEGER */
@@ -48,18 +45,6 @@
# include <X11/xpm.h>
#endif /* HAVE_XPM */
-#ifdef HAVE_XMU
-# ifndef VMS
-# include <X11/Xmu/Error.h>
-# else /* VMS */
-# include <Xmu/Error.h>
-# endif
-#else
-# include "xmu.h"
-#endif
-
-
-
#include <Xm/Xm.h>
#include <Xm/List.h>
#include <Xm/PushB.h>
@@ -78,11 +63,12 @@
#endif /* HAVE_XMCOMBOBOX */
#include "version.h"
-#include "prefs.h"
+#include "types.h"
#include "resources.h" /* for parse_time() */
-#include "visual.h" /* for has_writable_cells() */
#include "remote.h" /* for xscreensaver_command() */
-#include "usleep.h"
+#include "visual.h"
+#include "atoms.h"
+#include "xmu.h"
#include <stdio.h>
#include <string.h>
@@ -92,7 +78,6 @@
#define countof(x) (sizeof((x))/sizeof((*x)))
-char *progname = 0;
char *progclass = "XScreenSaver";
XrmDatabase db;
@@ -102,20 +87,12 @@ typedef struct {
static void *global_prefs_pair; /* I hate C so much... */
-char *blurb (void) { return progname; }
-
extern Widget create_xscreensaver_demo (Widget parent);
extern const char *visual_menu[];
static char *short_version = 0;
-Atom XA_VROOT;
-Atom XA_SCREENSAVER, XA_SCREENSAVER_RESPONSE, XA_SCREENSAVER_VERSION;
-Atom XA_SCREENSAVER_ID, XA_SCREENSAVER_STATUS, XA_SELECT, XA_DEMO;
-Atom XA_ACTIVATE, XA_SUSPEND, XA_BLANK, XA_LOCK, XA_RESTART, XA_EXIT;
-
-
static void populate_demo_window (Widget toplevel,
int which, prefs_pair *pair);
static void populate_prefs_page (Widget top, prefs_pair *pair);
@@ -297,7 +274,7 @@ run_hack (Widget widget, int which, Bool report_errors_p)
/* Button callbacks
*/
-void
+static void
exit_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
{
apply_changes_and_save (XtParent (button));
@@ -313,7 +290,7 @@ wm_close_cb (Widget widget, GdkEvent *event, XtPointer data)
}
#endif
-void
+static void
cut_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
{
/* #### */
@@ -323,7 +300,7 @@ cut_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
}
-void
+static void
copy_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
{
/* #### */
@@ -333,7 +310,7 @@ copy_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
}
-void
+static void
paste_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
{
/* #### */
@@ -343,7 +320,7 @@ paste_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
}
-void
+static void
about_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
{
char buf [2048];
@@ -356,8 +333,8 @@ about_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
sprintf (buf, "%s\n%s\n"
"\n"
- "This is the Motif version of \"xscreensaver-demo\". The Motif\n"
- "version is no longer maintained. Please use the GTK version\n"
+ "This is the Motif version of \"xscreensaver-settings\".\n"
+ "It is no longer maintained. Please use the GTK version\n"
"instead, which has many more features.\n"
"\n"
"For xscreensaver updates, check https://www.jwz.org/xscreensaver/",
@@ -368,7 +345,7 @@ about_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
}
-void
+static void
doc_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
{
prefs_pair *pair = (prefs_pair *) client_data;
@@ -385,39 +362,39 @@ doc_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
}
help_command = (char *) malloc (strlen (p->load_url_command) +
- (strlen (p->help_url) * 4) + 20);
+ (strlen (p->help_url) * 5) + 20);
strcpy (help_command, "( ");
sprintf (help_command + strlen(help_command),
p->load_url_command,
- p->help_url, p->help_url, p->help_url, p->help_url);
+ p->help_url, p->help_url, p->help_url, p->help_url, p->help_url);
strcat (help_command, " ) &");
system (help_command);
free (help_command);
}
-void
+static void
activate_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
{
run_cmd (XtParent (button), XA_ACTIVATE, 0);
}
-void
+static void
lock_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
{
run_cmd (XtParent (button), XA_LOCK, 0);
}
-void
+static void
kill_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
{
run_cmd (XtParent (button), XA_EXIT, 0);
}
-void
+static void
restart_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
{
#if 0
@@ -427,7 +404,7 @@ restart_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
apply_changes_and_save (button);
xscreensaver_command (XtDisplay (button), XA_EXIT, 0, False, NULL);
sleep (1);
- system ("xscreensaver -nosplash &");
+ system ("xscreensaver -splash &");
#endif
await_xscreensaver (button);
@@ -641,7 +618,7 @@ apply_changes_and_save (Widget widget)
return 0;
}
-void
+static void
run_this_cb (Widget button, XtPointer client_data, XtPointer ignored)
{
int which = selected_hack_number (XtParent (button));
@@ -651,7 +628,7 @@ run_this_cb (Widget button, XtPointer client_data, XtPointer ignored)
}
-void
+static void
manual_cb (Widget button, XtPointer client_data, XtPointer ignored)
{
prefs_pair *pair = (prefs_pair *) client_data;
@@ -695,7 +672,7 @@ manual_cb (Widget button, XtPointer client_data, XtPointer ignored)
}
-void
+static void
run_next_cb (Widget button, XtPointer client_data, XtPointer ignored)
{
prefs_pair *pair = (prefs_pair *) client_data;
@@ -725,7 +702,7 @@ run_next_cb (Widget button, XtPointer client_data, XtPointer ignored)
}
-void
+static void
run_prev_cb (Widget button, XtPointer client_data, XtPointer ignored)
{
prefs_pair *pair = (prefs_pair *) client_data;
@@ -781,7 +758,7 @@ hack_time_text (Widget button, const char *line, Time *store, Bool sec_p)
}
-void
+static void
prefs_ok_cb (Widget button, XtPointer client_data, XtPointer ignored)
{
prefs_pair *pair = (prefs_pair *) client_data;
@@ -825,7 +802,6 @@ prefs_ok_cb (Widget button, XtPointer client_data, XtPointer ignored)
MINUTES (&p2->timeout, "timeoutText");
MINUTES (&p2->cycle, "cycleText");
SECONDS (&p2->fade_seconds, "fadeSecondsText");
- INTEGER (&p2->fade_ticks, "fadeTicksText");
MINUTES (&p2->lock_timeout, "lockText");
SECONDS (&p2->passwd_timeout, "passwdText");
CHECKBOX (p2->verbose_p, "verboseToggle");
@@ -848,7 +824,6 @@ prefs_ok_cb (Widget button, XtPointer client_data, XtPointer ignored)
COPY(lock_timeout);
COPY(passwd_timeout);
COPY(fade_seconds);
- COPY(fade_ticks);
COPY(verbose_p);
COPY(install_cmap_p);
COPY(fade_p);
@@ -863,7 +838,7 @@ prefs_ok_cb (Widget button, XtPointer client_data, XtPointer ignored)
}
-void
+static void
prefs_cancel_cb (Widget button, XtPointer client_data, XtPointer ignored)
{
prefs_pair *pair = (prefs_pair *) client_data;
@@ -996,8 +971,6 @@ populate_prefs_page (Widget top, prefs_pair *pair)
XtVaSetValues (name_to_widget (top, "passwdText"), XmNvalue, s, NULL);
format_time (s, p->fade_seconds);
XtVaSetValues (name_to_widget (top, "fadeSecondsText"), XmNvalue, s, NULL);
- sprintf (s, "%u", p->fade_ticks);
- XtVaSetValues (name_to_widget (top, "fadeTicksText"), XmNvalue, s, NULL);
XtVaSetValues (name_to_widget (top, "verboseToggle"),
XmNset, p->verbose_p, NULL);
@@ -1009,42 +982,6 @@ populate_prefs_page (Widget top, prefs_pair *pair)
XmNset, p->unfade_p, NULL);
XtVaSetValues (name_to_widget (top, "lockToggle"),
XmNset, p->lock_p, NULL);
-
-
- {
- Bool found_any_writable_cells = False;
- Display *dpy = XtDisplay (top);
- int nscreens = ScreenCount(dpy);
- int i;
- for (i = 0; i < nscreens; i++)
- {
- Screen *s = ScreenOfDisplay (dpy, i);
- if (has_writable_cells (s, DefaultVisualOfScreen (s)))
- {
- found_any_writable_cells = True;
- break;
- }
- }
-
-#ifdef HAVE_XF86VMODE_GAMMA
- found_any_writable_cells = True; /* if we can gamma fade, go for it */
-#endif
-
- XtVaSetValues (name_to_widget (top, "fadeSecondsLabel"), XtNsensitive,
- found_any_writable_cells, NULL);
- XtVaSetValues (name_to_widget (top, "fadeTicksLabel"), XtNsensitive,
- found_any_writable_cells, NULL);
- XtVaSetValues (name_to_widget (top, "fadeSecondsText"), XtNsensitive,
- found_any_writable_cells, NULL);
- XtVaSetValues (name_to_widget (top, "fadeTicksText"), XtNsensitive,
- found_any_writable_cells, NULL);
- XtVaSetValues (name_to_widget (top, "cmapToggle"), XtNsensitive,
- found_any_writable_cells, NULL);
- XtVaSetValues (name_to_widget (top, "fadeToggle"), XtNsensitive,
- found_any_writable_cells, NULL);
- XtVaSetValues (name_to_widget (top, "unfadeToggle"), XtNsensitive,
- found_any_writable_cells, NULL);
- }
}
@@ -1054,7 +991,7 @@ sensitize_demo_widgets (Widget toplevel, Bool sensitive_p)
const char *names[] = { "cmdLabel", "cmdText", "enabled",
"visLabel", "combo", "demo", "man" };
int i;
- for (i = 0; i < sizeof(names)/countof(*names); i++)
+ for (i = 0; i < countof(names); i++)
{
Widget w = name_to_widget (toplevel, names[i]);
XtVaSetValues (w, XtNsensitive, sensitive_p, NULL);
@@ -1063,7 +1000,7 @@ sensitize_demo_widgets (Widget toplevel, Bool sensitive_p)
/* I don't know how to handle these yet... */
{
const char *names2[] = { "cut", "copy", "paste" };
- for (i = 0; i < sizeof(names2)/countof(*names2); i++)
+ for (i = 0; i < countof(names2); i++)
{
Widget w = name_to_widget (toplevel, names2[i]);
XtVaSetValues (w, XtNsensitive, FALSE, NULL);
@@ -1208,7 +1145,7 @@ pixmapify_buttons (Widget toplevel)
-char *
+static char *
get_hack_blurb (Display *dpy, screenhack *hack)
{
char *doc_string;
@@ -1301,8 +1238,8 @@ get_hack_blurb (Display *dpy, screenhack *hack)
# endif /* 0 */
doc_string = strdup (
"\n"
- "This is the Motif version of \"xscreensaver-demo\". The Motif "
- "version is no longer maintained. Please use the GTK version "
+ "This is the Motif version of \"xscreensaver-settings\"."
+ "It is no longer maintained. Please use the GTK version "
"instead, which has many more features."
"\n\n"
"If you were running the GTK version, there would be a preview "
@@ -1475,10 +1412,10 @@ sanity_check_resources (Widget toplevel)
{
const char *names[] = { "demoTab", "optionsTab", "cmdLabel", "visLabel",
"enabled", "demo", "man", "timeoutLabel",
- "cycleLabel", "fadeSecondsLabel", "fadeTicksLabel",
+ "cycleLabel", "fadeSecondsLabel",
"lockLabel", "passwdLabel" };
int i;
- for (i = 0; i < sizeof(names)/countof(*names); i++)
+ for (i = 0; i < countof(names); i++)
{
Widget w = name_to_widget (toplevel, names[i]);
const char *name = XtName(w);
@@ -1566,11 +1503,9 @@ the_network_is_not_the_computer (Widget parent)
lhost = "<UNKNOWN>";
else
lhost = uts.nodename;
-# elif defined(VMS)
- strcpy (lhost, getenv("SYS$NODE"));
-# else /* !HAVE_UNAME && !VMS */
+# else /* !HAVE_UNAME */
strcat (lhost, "<UNKNOWN>");
-# endif /* !HAVE_UNAME && !VMS */
+# endif /* !HAVE_UNAME */
if (p && p->pw_name)
luser = p->pw_name;
@@ -1738,7 +1673,7 @@ main (int argc, char **argv)
dpy = XtDisplay (toplevel_shell);
db = XtDatabase (dpy);
- XtGetApplicationNameAndClass (dpy, &progname, &progclass);
+ XtGetApplicationNameAndClass (dpy, (char **) &progname, &progclass);
XSetErrorHandler (demo_ehandler);
/* Complain about unrecognized command-line arguments.
@@ -1758,9 +1693,9 @@ main (int argc, char **argv)
}
}
- short_version = (char *) malloc (5);
- memcpy (short_version, screensaver_id + 17, 4);
- short_version [4] = 0;
+ short_version = strdup (screensaver_id + 17);
+ s = strchr (short_version, ' ');
+ *s = 0;
/* Load the init file, which may end up consulting the X resource database
and the site-wide app-defaults file. Note that at this point, it's
@@ -1789,22 +1724,7 @@ main (int argc, char **argv)
#endif
- /* Intern the atoms that xscreensaver_command() needs.
- */
- XA_VROOT = XInternAtom (dpy, "__SWM_VROOT", False);
- XA_SCREENSAVER = XInternAtom (dpy, "SCREENSAVER", False);
- XA_SCREENSAVER_VERSION = XInternAtom (dpy, "_SCREENSAVER_VERSION",False);
- XA_SCREENSAVER_STATUS = XInternAtom (dpy, "_SCREENSAVER_STATUS", False);
- XA_SCREENSAVER_ID = XInternAtom (dpy, "_SCREENSAVER_ID", False);
- XA_SCREENSAVER_RESPONSE = XInternAtom (dpy, "_SCREENSAVER_RESPONSE", False);
- XA_SELECT = XInternAtom (dpy, "SELECT", False);
- XA_DEMO = XInternAtom (dpy, "DEMO", False);
- XA_ACTIVATE = XInternAtom (dpy, "ACTIVATE", False);
- XA_SUSPEND = XInternAtom (dpy, "SUSPEND", False);
- XA_BLANK = XInternAtom (dpy, "BLANK", False);
- XA_LOCK = XInternAtom (dpy, "LOCK", False);
- XA_EXIT = XInternAtom (dpy, "EXIT", False);
- XA_RESTART = XInternAtom (dpy, "RESTART", False);
+ init_xscreensaver_atoms (dpy);
/* Create the window and all its widgets.
*/
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);
+}
diff --git a/driver/dpms.c b/driver/dpms.c
index a0dd7b8..15721ea 100644
--- a/driver/dpms.c
+++ b/driver/dpms.c
@@ -1,5 +1,5 @@
-/* dpms.c --- syncing the X Display Power Management values
- * xscreensaver, Copyright (c) 2001-2017 Jamie Zawinski <jwz@jwz.org>
+/* dpms.c --- syncing the X Display Power Management System values
+ * xscreensaver, Copyright © 2001-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
@@ -10,81 +10,33 @@
* implied warranty.
*/
-/* Display Power Management System (DPMS.)
-
- On XFree86 systems, "man xset" reports:
-
- -dpms The -dpms option disables DPMS (Energy Star) features.
- +dpms The +dpms option enables DPMS (Energy Star) features.
-
- dpms flags...
- The dpms option allows the DPMS (Energy Star)
- parameters to be set. The option can take up to three
- numerical values, or the `force' flag followed by a
- DPMS state. The `force' flags forces the server to
- immediately switch to the DPMS state specified. The
- DPMS state can be one of `standby', `suspend', or
- `off'. When numerical values are given, they set the
- inactivity period before the three modes are activated.
- The first value given is for the `standby' mode, the
- second is for the `suspend' mode, and the third is for
- the `off' mode. Setting these values implicitly
- enables the DPMS features. A value of zero disables a
- particular mode.
-
- However, note that the implementation is more than a little bogus,
- in that there is code in /usr/X11R6/lib/libXdpms.a to implement all
- the usual server-extension-querying utilities -- but there are no
- prototypes in any header file! Thus, the prototypes here. (The
- stuff in X11/extensions/dpms.h and X11/extensions/dpmsstr.h define
- the raw X protcol, they don't define the API to libXdpms.a.)
-
- Some documentation:
- Library: ftp://ftp.x.org/pub/R6.4/xc/doc/specs/Xext/DPMSLib.ms
- Protocol: ftp://ftp.x.org/pub/R6.4/xc/doc/specs/Xext/DPMS.ms
- */
-
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <stdio.h>
#include <X11/Xlib.h>
+#include <X11/Intrinsic.h>
-#ifdef HAVE_DPMS_EXTENSION /* almost the whole file */
+#include "xscreensaver.h"
-# include <X11/Xproto.h>
-# include <X11/extensions/dpms.h>
-/*# include <X11/extensions/dpmsstr.h>*/
+#ifndef HAVE_DPMS_EXTENSION /* almost the whole file */
- /* Why this crap is not in a header file somewhere, I have no idea. Losers!
- */
- extern Bool DPMSQueryExtension (Display *, int *event_ret, int *err_ret);
- extern Status DPMSGetVersion (Display *, int *major_ret, int *minor_ret);
- extern Bool DPMSCapable (Display *);
- extern Status DPMSInfo (Display *, CARD16 *power_level, BOOL *state);
- extern Status DPMSEnable (Display *dpy);
- extern Status DPMSDisable (Display *dpy);
- extern Status DPMSForceLevel (Display *, CARD16 level);
- extern Status DPMSSetTimeouts (Display *, CARD16 standby, CARD16 suspend,
- CARD16 off);
- extern Bool DPMSGetTimeouts (Display *, CARD16 *standby,
- CARD16 *suspend, CARD16 *off);
-
-#endif /* HAVE_DPMS_EXTENSION */
-
-
-/* This file doesn't need the Xt headers, so stub these types out... */
-#undef XtPointer
-#define XtAppContext void*
-#define XrmDatabase void*
-#define XtIntervalId void*
-#define XtPointer void*
-#define Widget void*
+void
+sync_server_dpms_settings (Display *dpy, struct saver_preferences *p)
+{
+ if (p->verbose_p)
+ fprintf (stderr, "%s: DPMS not supported at compile time\n", blurb());
+}
-#include "xscreensaver.h"
+Bool monitor_powered_on_p (Display *dpy) { return True; }
+void monitor_power_on (saver_info *si, Bool on_p) { return; }
+
+#else /* HAVE_DPMS_EXTENSION -- whole file */
+
+# include <X11/Xproto.h>
+# include <X11/extensions/dpms.h>
-#ifdef HAVE_DPMS_EXTENSION
static Bool error_handler_hit_p = False;
@@ -97,9 +49,7 @@ ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
void
-sync_server_dpms_settings (Display *dpy, Bool enabled_p, Bool dpms_quickoff_p,
- int standby_secs, int suspend_secs, int off_secs,
- Bool verbose_p)
+sync_server_dpms_settings (Display *dpy, struct saver_preferences *p)
{
int event = 0, error = 0;
BOOL o_enabled = False;
@@ -107,11 +57,29 @@ sync_server_dpms_settings (Display *dpy, Bool enabled_p, Bool dpms_quickoff_p,
CARD16 o_standby = 0, o_suspend = 0, o_off = 0;
Bool bogus_p = False;
+ Bool enabled_p = (p->dpms_enabled_p && p->mode != DONT_BLANK);
+ Bool dpms_quickoff_p = p->dpms_quickoff_p;
+ int standby_secs = p->dpms_standby / 1000;
+ int suspend_secs = p->dpms_suspend / 1000;
+ int off_secs = p->dpms_off / 1000;
+ Bool verbose_p = p->verbose_p;
+ static Bool warned_p = False;
+
+ /* If the monitor is currently powered off, defer any changes until
+ we are next called while it is powered on. */
+ if (! monitor_powered_on_p (dpy))
+ return;
+
+ /* Why did I do this? It makes DPMS never happen.
+ XSetScreenSaver (dpy, 0, 0, 0, 0);
+ XForceScreenSaver (dpy, ScreenSaverReset);
+ */
+
if (dpms_quickoff_p && !off_secs)
- {
- /* To do this, we might need to temporarily re-enable DPMS first. */
- off_secs = 0xFFFF;
- }
+ {
+ /* To do this, we might need to temporarily re-enable DPMS first. */
+ off_secs = 0xFFFF;
+ }
if (standby_secs == 0 && suspend_secs == 0 && off_secs == 0)
/* all zero implies "DPMS disabled" */
@@ -134,22 +102,25 @@ sync_server_dpms_settings (Display *dpy, Bool enabled_p, Bool dpms_quickoff_p,
if (! DPMSQueryExtension (dpy, &event, &error))
{
- if (verbose_p)
- fprintf (stderr, "%s: XDPMS extension not supported.\n", blurb());
+ if (verbose_p && !warned_p)
+ fprintf (stderr, "%s: XDPMS extension not supported\n", blurb());
+ warned_p = True;
return;
}
if (! DPMSCapable (dpy))
{
- if (verbose_p)
- fprintf (stderr, "%s: DPMS not supported.\n", blurb());
+ if (verbose_p && !warned_p)
+ fprintf (stderr, "%s: DPMS not supported\n", blurb());
+ warned_p = True;
return;
}
if (! DPMSInfo (dpy, &o_power, &o_enabled))
{
- if (verbose_p)
- fprintf (stderr, "%s: unable to get DPMS state.\n", blurb());
+ if (verbose_p && !warned_p)
+ fprintf (stderr, "%s: unable to get DPMS state\n", blurb());
+ warned_p = True;
return;
}
@@ -157,19 +128,20 @@ sync_server_dpms_settings (Display *dpy, Bool enabled_p, Bool dpms_quickoff_p,
{
if (! (enabled_p ? DPMSEnable (dpy) : DPMSDisable (dpy)))
{
- if (verbose_p)
- fprintf (stderr, "%s: unable to set DPMS state.\n", blurb());
+ if (verbose_p && !warned_p)
+ fprintf (stderr, "%s: unable to set DPMS state\n", blurb());
+ warned_p = True;
return;
}
else if (verbose_p)
- fprintf (stderr, "%s: turned DPMS %s.\n", blurb(),
+ fprintf (stderr, "%s: turned DPMS %s\n", blurb(),
enabled_p ? "on" : "off");
}
if (bogus_p)
{
if (verbose_p)
- fprintf (stderr, "%s: not setting bogus DPMS timeouts: %d %d %d.\n",
+ fprintf (stderr, "%s: not setting bogus DPMS timeouts: %d %d %d\n",
blurb(), standby_secs, suspend_secs, off_secs);
return;
}
@@ -177,7 +149,7 @@ sync_server_dpms_settings (Display *dpy, Bool enabled_p, Bool dpms_quickoff_p,
if (!DPMSGetTimeouts (dpy, &o_standby, &o_suspend, &o_off))
{
if (verbose_p)
- fprintf (stderr, "%s: unable to get DPMS timeouts.\n", blurb());
+ fprintf (stderr, "%s: unable to get DPMS timeouts\n", blurb());
return;
}
@@ -188,34 +160,34 @@ sync_server_dpms_settings (Display *dpy, Bool enabled_p, Bool dpms_quickoff_p,
if (!DPMSSetTimeouts (dpy, standby_secs, suspend_secs, off_secs))
{
if (verbose_p)
- fprintf (stderr, "%s: unable to set DPMS timeouts.\n", blurb());
+ fprintf (stderr, "%s: unable to set DPMS timeouts\n", blurb());
return;
}
else if (verbose_p)
- fprintf (stderr, "%s: set DPMS timeouts: %d %d %d.\n", blurb(),
+ fprintf (stderr, "%s: set DPMS timeouts: %d %d %d\n", blurb(),
standby_secs, suspend_secs, off_secs);
}
}
Bool
-monitor_powered_on_p (saver_info *si)
+monitor_powered_on_p (Display *dpy)
{
Bool result;
int event_number, error_number;
BOOL onoff = False;
CARD16 state;
- if (!DPMSQueryExtension(si->dpy, &event_number, &error_number))
+ if (!DPMSQueryExtension(dpy, &event_number, &error_number))
/* Server doesn't know -- assume the monitor is on. */
result = True;
- else if (!DPMSCapable(si->dpy))
+ else if (!DPMSCapable(dpy))
/* Server says the monitor doesn't do power management -- so it's on. */
result = True;
else
{
- DPMSInfo(si->dpy, &state, &onoff);
+ DPMSInfo(dpy, &state, &onoff);
if (!onoff)
/* Server says DPMS is disabled -- so the monitor is on. */
result = True;
@@ -235,17 +207,19 @@ monitor_powered_on_p (saver_info *si)
void
monitor_power_on (saver_info *si, Bool on_p)
{
- if ((!!on_p) != monitor_powered_on_p (si))
+ if ((!!on_p) != monitor_powered_on_p (si->dpy))
{
XErrorHandler old_handler;
int event_number, error_number;
+ static Bool warned_p = False;
if (!DPMSQueryExtension(si->dpy, &event_number, &error_number) ||
!DPMSCapable(si->dpy))
{
- if (si->prefs.verbose_p)
+ if (si->prefs.verbose_p && !warned_p)
fprintf (stderr,
- "%s: unable to power %s monitor: no DPMS extension.\n",
+ "%s: unable to power %s monitor: no DPMS extension\n",
blurb(), (on_p ? "on" : "off"));
+ warned_p = True;
return;
}
@@ -269,36 +243,12 @@ monitor_power_on (saver_info *si, Bool on_p)
XSetErrorHandler (old_handler);
/* Ignore error_handler_hit_p, just probe monitor instead */
- if ((!!on_p) != monitor_powered_on_p (si)) /* double-check */
+ if ((!!on_p) != monitor_powered_on_p (si->dpy)) /* double-check */
fprintf (stderr,
- "%s: DPMSForceLevel(dpy, %s) did not change monitor power state.\n",
+ "%s: DPMSForceLevel(dpy, %s) did not change monitor power state\n",
blurb(),
(on_p ? "DPMSModeOn" : "DPMSModeOff"));
}
}
-#else /* !HAVE_DPMS_EXTENSION */
-
-void
-sync_server_dpms_settings (Display *dpy, Bool enabled_p,
- Bool dpms_quickoff_p,
- int standby_secs, int suspend_secs, int off_secs,
- Bool verbose_p)
-{
- if (verbose_p)
- fprintf (stderr, "%s: DPMS support not compiled in.\n", blurb());
-}
-
-Bool
-monitor_powered_on_p (saver_info *si)
-{
- return True;
-}
-
-void
-monitor_power_on (saver_info *si, Bool on_p)
-{
- return;
-}
-
-#endif /* !HAVE_DPMS_EXTENSION */
+#endif /* HAVE_DPMS_EXTENSION -- whole file */
diff --git a/driver/exec.c b/driver/exec.c
index 38ca88a..8d1c45a 100644
--- a/driver/exec.c
+++ b/driver/exec.c
@@ -1,5 +1,5 @@
/* exec.c --- executes a program in *this* pid, without an intervening process.
- * xscreensaver, Copyright (c) 1991-2013 Jamie Zawinski <jwz@jwz.org>
+ * xscreensaver, Copyright © 1991-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
@@ -69,14 +69,6 @@
/* and also setrlimit() and RLIMIT_AS */
#endif
-#ifdef VMS
-# include <processes.h>
-# include <unixio.h> /* for close */
-# include <unixlib.h> /* for getpid */
-# define pid_t int
-# define fork vfork
-#endif /* VMS */
-
#include "exec.h"
extern const char *blurb (void);
@@ -84,8 +76,6 @@ extern const char *blurb (void);
static void nice_process (int nice_level);
-#ifndef VMS
-
static void
exec_simple_command (const char *command)
{
@@ -161,26 +151,12 @@ exec_complex_command (const char *shell, const char *command)
execvp (av[0], av); /* shouldn't return. */
}
-#else /* VMS */
-
-static void
-exec_vms_command (const char *command)
-{
- system (command);
- fflush (stderr);
- fflush (stdout);
- exit (1); /* Note that this only exits a child fork. */
-}
-
-#endif /* !VMS */
-
void
exec_command (const char *shell, const char *command, int nice_level)
{
int hairy_p;
-#ifndef VMS
nice_process (nice_level);
hairy_p = !!strpbrk (command, "*?$&!<>[];`'\\\"=");
@@ -204,10 +180,6 @@ exec_command (const char *shell, const char *command, int nice_level)
else
/* Otherwise, we can just exec the program directly. */
exec_simple_command (command);
-
-#else /* VMS */
- exec_vms_command (command);
-#endif /* VMS */
}
@@ -242,7 +214,7 @@ nice_process (int nice_level)
}
#else
fprintf (stderr,
- "%s: don't know how to change process priority on this system.\n",
+ "%s: don't know how to change process priority on this system\n",
blurb());
#endif
diff --git a/driver/exts.c b/driver/exts.c
new file mode 100644
index 0000000..641325d
--- /dev/null
+++ b/driver/exts.c
@@ -0,0 +1,238 @@
+/* xscreensaver, Copyright © 1991-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.
+ */
+
+/* Some random diagnostics printed in -verbose mode about what extensions
+ are available on the server, and their version numbers.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdio.h>
+#include <X11/Xlib.h>
+#include <X11/Xlibint.h>
+#include <X11/Intrinsic.h>
+
+#include <X11/extensions/XInput2.h>
+
+#ifdef HAVE_XSHM_EXTENSION
+# include <X11/extensions/XShm.h>
+#endif /* HAVE_XSHM_EXTENSION */
+
+#ifdef HAVE_DPMS_EXTENSION
+# include <X11/extensions/dpms.h>
+#endif /* HAVE_DPMS_EXTENSION */
+
+
+#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
+# include <X11/extensions/Xdbe.h>
+#endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
+
+#ifdef HAVE_XF86VMODE
+# include <X11/extensions/xf86vmode.h>
+#endif /* HAVE_XF86VMODE */
+
+#ifdef HAVE_XINERAMA
+# include <X11/extensions/Xinerama.h>
+#endif /* HAVE_XINERAMA */
+
+#ifdef HAVE_RANDR
+# include <X11/extensions/Xrandr.h>
+#endif /* HAVE_RANDR */
+
+#ifdef HAVE_XCOMPOSITE_EXTENSION
+# include <X11/extensions/Xcomposite.h>
+#endif
+
+#ifdef HAVE_XKB
+# include <X11/XKBlib.h>
+#endif
+
+#include "xscreensaver.h"
+
+
+void
+print_available_extensions (saver_info *si)
+{
+ int i, j;
+ static struct {
+ const char *name; const char *desc;
+ Bool useful_p;
+ Status (*version_fn) (Display *, int *majP, int *minP);
+ } exts[] = {
+
+# if 0
+ { "MIT-SCREEN-SAVER", "MIT Screen-Saver",
+ False, 0
+ },
+# endif
+ { "MIT-SHM", "Shared Memory",
+# ifdef HAVE_XSHM_EXTENSION
+ True, (Status (*) (Display*,int*,int*)) XShmQueryVersion /* 4 args */
+# else
+ False, 0
+# endif
+ }, { "DOUBLE-BUFFER", "Double-Buffering",
+# ifdef HAVE_DOUBLE_BUFFER_EXTENSION
+ True, XdbeQueryExtension
+# else
+ False, 0
+# endif
+ }, { "DPMS", "Power Management",
+# ifdef HAVE_DPMS_EXTENSION
+ True, DPMSGetVersion
+# else
+ False, 0
+# endif
+ }, { "GLX", "GLX",
+# ifdef HAVE_GL
+ True, 0
+# else
+ False, 0
+# endif
+ }, { "XFree86-VidModeExtension", "XF86 Video-Mode",
+# ifdef HAVE_XF86VMODE
+ True, XF86VidModeQueryVersion
+# else
+ False, 0
+# endif
+ }, { "XC-VidModeExtension", "XC Video-Mode",
+# ifdef HAVE_XF86VMODE
+ True, XF86VidModeQueryVersion
+# else
+ False, 0
+# endif
+ }, { "XINERAMA", "Xinerama",
+# ifdef HAVE_XINERAMA
+ True, XineramaQueryVersion
+# else
+ False, 0
+# endif
+ }, { "RANDR", "Resize-and-Rotate",
+# ifdef HAVE_RANDR
+ True, XRRQueryVersion
+# else
+ False, 0
+# endif
+ }, { "Composite", "Composite",
+# ifdef HAVE_XCOMPOSITE_EXTENSION
+ True, XCompositeQueryVersion
+# else
+ True, 0
+# endif
+ }, { "XKEYBOARD", "XKeyboard",
+# ifdef HAVE_XKB
+ True, 0,
+# else
+ False, 0
+# endif
+ }, { "DRI", "DRI", True, 0
+ }, { "NV-CONTROL", "NVidia", True, 0
+ }, { "NV-GLX", "NVidia GLX", True, 0
+ }, { "Apple-DRI", "Apple-DRI", True, 0
+ }, { "Apple-WM", "Apple-WM", True, 0
+ }, { "XInputExtension", "XInput", True, 0
+ },
+ };
+
+ fprintf (stderr, "%s: running on display \"%s\"\n", blurb(),
+ DisplayString(si->dpy));
+ fprintf (stderr, "%s: vendor is %s, %d\n", blurb(),
+ ServerVendor(si->dpy), VendorRelease(si->dpy));
+
+ fprintf (stderr, "%s: useful extensions:\n", blurb());
+ for (i = 0; i < countof(exts); i++)
+ {
+ int op = 0, event = 0, error = 0;
+ char buf [255];
+ int maj = 0, min = 0;
+ int dummy1, dummy2, dummy3;
+
+ /* Most of the extension version functions take 3 args,
+ writing results into args 2 and 3, but some take more.
+ We only ever care about the first two results, but we
+ pass in three extra pointers just in case.
+ */
+ Status (*version_fn_2) (Display*,int*,int*,int*,int*,int*) =
+ (Status (*) (Display*,int*,int*,int*,int*,int*)) exts[i].version_fn;
+
+ if (!XQueryExtension (si->dpy, exts[i].name, &op, &event, &error))
+ continue;
+ sprintf (buf, "%s: ", blurb());
+ strcat (buf, exts[i].desc);
+
+ if (!strcmp (exts[i].desc, "XInput"))
+ {
+ int maj = 999, min = 999; /* Desired */
+ XIQueryVersion (si->dpy, &maj, &min); /* Actual */
+ sprintf (buf+strlen(buf), " (%d.%d)", maj, min);
+ }
+ else if (!version_fn_2)
+ ;
+ else if (version_fn_2 (si->dpy, &maj, &min, &dummy1, &dummy2, &dummy3))
+ sprintf (buf+strlen(buf), " (%d.%d)", maj, min);
+ else
+ strcat (buf, " (unavailable)");
+
+ if (!exts[i].useful_p)
+ strcat (buf, " (disabled at compile time)");
+ fprintf (stderr, "%s\n", buf);
+ }
+
+# ifdef HAVE_LIBSYSTEMD
+ fprintf (stderr, "%s: libsystemd\n", blurb());
+# else
+ fprintf (stderr, "%s: libsystemd (disabled at compile time)\n", blurb());
+# endif
+
+ for (i = 0; i < si->nscreens; i++)
+ {
+ saver_screen_info *ssi = &si->screens[i];
+ unsigned long colormapped_depths = 0;
+ unsigned long non_mapped_depths = 0;
+ XVisualInfo vi_in, *vi_out;
+ int out_count;
+
+ if (!ssi->real_screen_p) continue;
+
+ vi_in.screen = ssi->real_screen_number;
+ vi_out = XGetVisualInfo (si->dpy, VisualScreenMask, &vi_in, &out_count);
+ if (!vi_out) continue;
+ for (j = 0; j < out_count; j++) {
+ if (vi_out[j].depth >= 32) continue;
+ if (vi_out[j].class == PseudoColor)
+ colormapped_depths |= (1 << vi_out[j].depth);
+ else
+ non_mapped_depths |= (1 << vi_out[j].depth);
+ }
+ XFree ((char *) vi_out);
+
+ if (colormapped_depths)
+ {
+ fprintf (stderr, "%s: screen %d colormapped depths:", blurb(),
+ ssi->real_screen_number);
+ for (j = 0; j < 32; j++)
+ if (colormapped_depths & (1 << j))
+ fprintf (stderr, " %d", j);
+ fprintf (stderr, "\n");
+ }
+ if (non_mapped_depths)
+ {
+ fprintf (stderr, "%s: screen %d non-colormapped depths:",
+ blurb(), ssi->real_screen_number);
+ for (j = 0; j < 32; j++)
+ if (non_mapped_depths & (1 << j))
+ fprintf (stderr, " %d", j);
+ fprintf (stderr, "\n");
+ }
+ }
+}
diff --git a/driver/fade.c b/driver/fade.c
new file mode 100644
index 0000000..9451582
--- /dev/null
+++ b/driver/fade.c
@@ -0,0 +1,1749 @@
+/* xscreensaver, Copyright © 1992-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.
+ */
+
+/* There are several different mechanisms here for fading the desktop to
+ black, and then fading it back in again.
+
+ - Colormaps: This only works on 8-bit displays, which basically haven't
+ existed since the 90s. It takes the current colormap, makes a writable
+ copy of it, and then animates the color cells to fade and unfade.
+
+ - XF86 Gamma or RANDR Gamma: These do the fade by altering the brightness
+ settings of the screen. This works on any system that has the "XF86
+ Video-Mode" extension (which is every modern system) AND ALSO has gamma
+ support in the video driver. But it turns out that as of 2021, the
+ Raspberry Pi HDMI video driver still does not support gamma. And there's
+ no way to determine that the video driver lacks gamma support even though
+ the extension exists. Since the Pi is probably the single most popular
+ platform for running X11 on the desktop these days, that makes this
+ method pretty much useless now.
+
+ - SGI VC: Same as the above, but only works on SGI.
+
+ - XSHM: This works by taking a screenshot and hacking the bits by hand.
+ It's slow. Also, in order to fade in from black to the desktop (possibly
+ hours after it faded out) it has to retain that first screenshot of the
+ desktop to fade back to. But if the desktop had changed in the meantime,
+ there will be a glitch at the end as it snaps from the screenshot to the
+ new current reality.
+
+ In summary, everything is terrible because X11 doesn't support alpha.
+
+
+ The fade process goes like this:
+
+ Screen saver activates:
+ - Fade out:
+ - Desktop is visible
+ - Save screenshot for later
+ - Map invisible temp windows
+ - Fade from desktop to black
+ - Erase saver windows to black and raise them
+ - Destroy temp windows
+
+ Screen saver deactivates:
+ - Fade out:
+ - Saver graphics are visible
+ - Map invisible temp windows
+ - Do not save a screenshot
+ - Fade from graphics to black
+ - Erase saver windows to black and raise them
+ - Destroy temp windows
+
+ - Fade in:
+ - Screen is black
+ - Map invisible temp windows
+ - Do not save a screenshot
+ - Unmap saver windows
+ - Fade from black to saved screenshot
+ - Destroy temp windows
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/time.h>
+
+#ifdef HAVE_JWXYZ
+# include "jwxyz.h"
+#else /* real X11 */
+# include <X11/Xlib.h>
+# include <X11/Xatom.h>
+# include <X11/Xproto.h>
+# include <X11/Intrinsic.h>
+#endif /* !HAVE_JWXYZ */
+
+#include "blurb.h"
+#include "visual.h"
+#include "usleep.h"
+#include "fade.h"
+#include "xshm.h"
+#include "atoms.h"
+#include "clientmsg.h"
+#include "xmu.h"
+
+/* Since gamma fading doesn't work on the Raspberry Pi, probably the single
+ most popular desktop Linux system these days, let's not use this fade
+ method even if the extension exists (which it does).
+ */
+#undef HAVE_XF86VMODE_GAMMA
+
+/* I'm not sure that the RANDR fade method brings anything to the party
+ that the XF86 method does See below.
+ */
+#undef HAVE_RANDR_12
+
+#define HAVE_XINPUT2 1 /* Mandatory */
+
+
+#ifdef HAVE_XINPUT2
+# include <X11/extensions/XInput2.h>
+# include "xinput.h"
+#endif
+
+
+typedef struct {
+ int nscreens;
+ Pixmap *screenshots;
+} fade_state;
+
+
+/* #### There's a bunch of duplicated code in the back half of the
+ four _fade and _whack functions that could probably be combined.
+ */
+#ifdef HAVE_SGI_VC_EXTENSION
+static int sgi_gamma_fade (XtAppContext, Display *, Window *wins, int count,
+ double secs, Bool out_p);
+#endif
+#ifdef HAVE_XF86VMODE_GAMMA
+static int xf86_gamma_fade (XtAppContext, Display *, Window *wins, int count,
+ double secs, Bool out_p);
+#endif
+#ifdef HAVE_RANDR_12
+static int randr_gamma_fade (XtAppContext, Display *, Window *wins, int count,
+ double secs, Bool out_p);
+#endif
+static int colormap_fade (XtAppContext, Display *, Window *wins, int count,
+ double secs, Bool out_p, Bool from_desktop_p);
+static int xshm_fade (XtAppContext, Display *,
+ Window *wins, int count, double secs,
+ Bool out_p, Bool from_desktop_p, fade_state *);
+
+
+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));
+}
+
+
+#ifdef HAVE_XINPUT2
+static int xi_opcode = -1;
+#endif
+
+/* Closure arg points to a Bool saying whether motion events count.
+ Motion aborts fade-out, but only clicks and keys abort fade-in.
+ */
+static Bool
+user_event_p (Display *dpy, XEvent *event, XPointer arg)
+{
+ Bool motion_p = *((Bool *) arg);
+
+ switch (event->xany.type) {
+ case KeyPress: case ButtonPress:
+ return True;
+ break;
+ case MotionNotify:
+ if (motion_p) return True;
+ break;
+# ifdef HAVE_XINPUT2
+ case GenericEvent:
+ {
+ XIRawEvent *re;
+ if (event->xcookie.extension != xi_opcode)
+ return False;
+ if (! event->xcookie.data)
+ XGetEventData (dpy, &event->xcookie);
+ if (! event->xcookie.data)
+ return False;
+ re = event->xcookie.data;
+
+ if (re->evtype == XI_RawKeyPress ||
+ re->evtype == XI_RawButtonPress)
+ return True;
+ else if (motion_p && re->evtype == XI_RawMotion)
+ return True;
+
+ /* Calling XFreeEventData here is bad news */
+ }
+ break;
+# endif /* HAVE_XINPUT2 */
+ default: break;
+ }
+
+ return False;
+}
+
+
+static Bool
+user_active_p (XtAppContext app, Display *dpy, Bool fade_out_p)
+{
+ XEvent event;
+ XtInputMask m;
+ Bool motion_p = fade_out_p; /* Motion aborts fade-out, not fade-in. */
+ motion_p = False; /* Naah, never abort on motion only */
+
+# ifdef HAVE_XINPUT2
+ if (xi_opcode == -1)
+ {
+ Bool ov = verbose_p;
+ xi_opcode = 0; /* only init once */
+ verbose_p = False; /* xscreensaver already printed this */
+ init_xinput (dpy, &xi_opcode);
+ verbose_p = ov;
+ }
+# endif
+
+ m = XtAppPending (app);
+ if (m & ~XtIMXEvent)
+ {
+ /* Process timers and signals only, don't block. */
+ if (verbose_p > 1)
+ fprintf (stderr, "%s: Xt pending %ld\n", blurb(), m);
+ XtAppProcessEvent (app, m);
+ }
+
+ /* If there is user activity, bug out. (Bug out on keypresses or
+ mouse presses, but not motion, and not release events. Bugging
+ out on motion made the unfade hack be totally useless, I think.)
+ */
+ if (XCheckIfEvent (dpy, &event, &user_event_p, (XPointer) &motion_p))
+ {
+ if (verbose_p > 1)
+ {
+ XIRawEvent *re = 0;
+ if (event.xany.type == GenericEvent && !event.xcookie.data)
+ {
+ XGetEventData (dpy, &event.xcookie);
+ re = event.xcookie.data;
+ }
+ fprintf (stderr, "%s: user input %d %d\n", blurb(),
+ event.xany.type,
+ (re ? re->evtype : -1));
+ }
+ XPutBackEvent (dpy, &event);
+ return True;
+ }
+
+ return False;
+}
+
+
+static void
+flush_user_input (Display *dpy)
+{
+ XEvent event;
+ Bool motion_p = True;
+ while (XCheckIfEvent (dpy, &event, &user_event_p, (XPointer) &motion_p))
+ if (verbose_p > 1)
+ {
+ XIRawEvent *re = 0;
+ if (event.xany.type == GenericEvent && !event.xcookie.data)
+ {
+ XGetEventData (dpy, &event.xcookie);
+ re = event.xcookie.data;
+ }
+ fprintf (stderr, "%s: flushed user event %d %d\n", blurb(),
+ event.xany.type,
+ (re ? re->evtype : -1));
+ }
+}
+
+
+/* This bullshit is needed because the VidMode and SHM extensions don't work
+ on remote displays. */
+static Bool error_handler_hit_p = False;
+
+static int
+ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
+{
+ if (verbose_p > 1)
+ XmuPrintDefaultErrorMessage (dpy, error, stderr);
+ error_handler_hit_p = True;
+ return 0;
+}
+
+
+/* Returns true if canceled by user activity. */
+Bool
+fade_screens (XtAppContext app, Display *dpy,
+ Window *saver_windows, int nwindows,
+ double seconds, Bool out_p, Bool from_desktop_p,
+ void **closureP)
+{
+ int status = False;
+ fade_state *state = 0;
+
+ if (nwindows <= 0) abort();
+ if (!saver_windows) abort();
+
+ if (!closureP) abort();
+ state = (fade_state *) *closureP;
+ if (!state)
+ {
+ state = (fade_state *) calloc (1, sizeof (*state));
+ *closureP = state;
+ }
+
+ if (from_desktop_p && !out_p)
+ abort(); /* Fading in from desktop makes no sense */
+
+ if (out_p)
+ flush_user_input (dpy); /* Flush at start of cycle */
+
+# ifdef HAVE_SGI_VC_EXTENSION
+ /* First try to do it by fading the gamma in an SGI-specific way... */
+ status = sgi_gamma_fade (app, dpy, saver_windows, nwindows, seconds, out_p);
+ if (status == 0 || status == 1)
+ return status; /* faded, possibly canceled */
+# endif
+
+# ifdef HAVE_RANDR_12
+ /* Then try to do it by fading the gamma in an RANDR-specific way... */
+ status = randr_gamma_fade (app, dpy, saver_windows, nwindows, seconds, out_p);
+ if (status == 0 || status == 1)
+ return status; /* faded, possibly canceled */
+# endif
+
+# ifdef HAVE_XF86VMODE_GAMMA
+ /* Then try to do it by fading the gamma in an XFree86-specific way... */
+ status = xf86_gamma_fade(app, dpy, saver_windows, nwindows, seconds, out_p);
+ if (status == 0 || status == 1)
+ return status; /* faded, possibly canceled */
+# endif
+
+ if (has_writable_cells (DefaultScreenOfDisplay (dpy),
+ DefaultVisual (dpy, 0)))
+ {
+ /* Do it the old-fashioned way, which only really worked on
+ 8-bit displays. */
+ status = colormap_fade (app, dpy, saver_windows, nwindows, seconds,
+ out_p, from_desktop_p);
+ if (status == 0 || status == 1)
+ return status; /* faded, possibly canceled */
+ }
+
+ /* Else do it the hard way, by hacking a screenshot. */
+ status = xshm_fade (app, dpy, saver_windows, nwindows, seconds, out_p,
+ from_desktop_p, state);
+ status = (status ? True : False);
+
+ return status;
+}
+
+/****************************************************************************
+
+ Colormap fading
+
+ ****************************************************************************/
+
+
+/* The business with `cmaps_per_screen' is to fake out the SGI 8-bit video
+ hardware, which is capable of installing multiple (4) colormaps
+ simultaneously. We have to install multiple copies of the same set of
+ colors in order to fill up all the available slots in the hardware color
+ lookup table, so we install an extra N colormaps per screen to make sure
+ that all screens really go black.
+
+ I'm told that this trick also works with XInside's AcceleratedX when using
+ the Matrox Millennium card (which also allows multiple PseudoColor and
+ TrueColor visuals to co-exist and display properly at the same time.)
+
+ This trick works ok on the 24-bit Indy video hardware, but doesn't work at
+ all on the O2 24-bit hardware. I guess the higher-end hardware is too
+ "good" for this to work (dammit.) So... I figured out the "right" way to
+ do this on SGIs, which is to ramp the monitor's gamma down to 0. That's
+ what is implemented in sgi_gamma_fade(), so we use that if we can.
+
+ Returns:
+ 0: faded normally
+ 1: canceled by user activity
+ -1: unable to fade because the extension isn't supported.
+ */
+static int
+colormap_fade (XtAppContext app, Display *dpy,
+ Window *saver_windows, int nwindows,
+ double seconds, Bool out_p, Bool from_desktop_p)
+{
+ int status = -1;
+ Colormap *window_cmaps = 0;
+ int i, j, k;
+ int cmaps_per_screen = 5;
+ int nscreens = ScreenCount(dpy);
+ int ncmaps = nscreens * cmaps_per_screen;
+ Colormap *fade_cmaps = 0;
+ Bool installed = False;
+ int total_ncolors;
+ XColor *orig_colors, *current_colors, *screen_colors, *orig_screen_colors;
+ int screen;
+
+ window_cmaps = (Colormap *) calloc(sizeof(Colormap), nwindows);
+ if (!window_cmaps) abort();
+ for (screen = 0; screen < nwindows; screen++)
+ {
+ XWindowAttributes xgwa;
+ XGetWindowAttributes (dpy, saver_windows[screen], &xgwa);
+ window_cmaps[screen] = xgwa.colormap;
+ }
+
+ error_handler_hit_p = False;
+
+ if (verbose_p > 1)
+ fprintf (stderr, "%s: colormap fade %s\n",
+ blurb(), (out_p ? "out" : "in"));
+
+ total_ncolors = 0;
+ for (i = 0; i < nscreens; i++)
+ total_ncolors += CellsOfScreen (ScreenOfDisplay(dpy, i));
+
+ orig_colors = (XColor *) calloc(sizeof(XColor), total_ncolors);
+ current_colors = (XColor *) calloc(sizeof(XColor), total_ncolors);
+
+ /* Get the contents of the colormap we are fading from or to. */
+ screen_colors = orig_colors;
+ for (i = 0; i < nscreens; i++)
+ {
+ Screen *sc = ScreenOfDisplay (dpy, i);
+ int ncolors = CellsOfScreen (sc);
+ Colormap cmap = (from_desktop_p || !out_p
+ ? DefaultColormapOfScreen(sc)
+ : window_cmaps[i]);
+ for (j = 0; j < ncolors; j++)
+ screen_colors[j].pixel = j;
+ XQueryColors (dpy, cmap, screen_colors, ncolors);
+
+ screen_colors += ncolors;
+ }
+
+ memcpy (current_colors, orig_colors, total_ncolors * sizeof (XColor));
+
+
+ /* Make the writable colormaps (we keep these around and reuse them.) */
+ if (!fade_cmaps)
+ {
+ fade_cmaps = (Colormap *) calloc(sizeof(Colormap), ncmaps);
+ for (i = 0; i < nscreens; i++)
+ {
+ Visual *v = DefaultVisual(dpy, i);
+ Screen *s = ScreenOfDisplay(dpy, i);
+ if (has_writable_cells (s, v))
+ for (j = 0; j < cmaps_per_screen; j++)
+ fade_cmaps[(i * cmaps_per_screen) + j] =
+ XCreateColormap (dpy, RootWindowOfScreen (s), v, AllocAll);
+ }
+ }
+
+ /* Run the animation at the maximum frame rate in the time allotted. */
+ {
+ double start_time = double_time();
+ double end_time = start_time + seconds;
+ double prev = 0;
+ double now;
+ int frames = 0;
+ double max = 1/60.0; /* max FPS */
+ while ((now = double_time()) < end_time)
+ {
+ double ratio = (end_time - now) / seconds;
+ if (!out_p) ratio = 1-ratio;
+
+ /* For each screen, compute the current value of each color...
+ */
+ orig_screen_colors = orig_colors;
+ screen_colors = current_colors;
+ for (j = 0; j < nscreens; j++)
+ {
+ int ncolors = CellsOfScreen (ScreenOfDisplay (dpy, j));
+ for (k = 0; k < ncolors; k++)
+ {
+ /* This doesn't take into account the relative luminance of the
+ RGB components (0.299, 0.587, and 0.114 at gamma 2.2) but
+ the difference is imperceptible for this application... */
+ screen_colors[k].red = orig_screen_colors[k].red * ratio;
+ screen_colors[k].green = orig_screen_colors[k].green * ratio;
+ screen_colors[k].blue = orig_screen_colors[k].blue * ratio;
+ }
+ screen_colors += ncolors;
+ orig_screen_colors += ncolors;
+ }
+
+ /* Put the colors into the maps...
+ */
+ screen_colors = current_colors;
+ for (j = 0; j < nscreens; j++)
+ {
+ int ncolors = CellsOfScreen (ScreenOfDisplay (dpy, j));
+ for (k = 0; k < cmaps_per_screen; k++)
+ {
+ Colormap c = fade_cmaps[j * cmaps_per_screen + k];
+ if (c)
+ XStoreColors (dpy, c, screen_colors, ncolors);
+ }
+ screen_colors += ncolors;
+ }
+
+ /* Put the maps on the screens, and then take the windows off the
+ screen. (only need to do this the first time through the loop.)
+ */
+ if (!installed)
+ {
+ for (j = 0; j < ncmaps; j++)
+ if (fade_cmaps[j])
+ XInstallColormap (dpy, fade_cmaps[j]);
+ installed = True;
+
+ if (!out_p)
+ for (j = 0; j < nwindows; j++)
+ {
+ XUnmapWindow (dpy, saver_windows[j]);
+ XClearWindow (dpy, saver_windows[j]);
+ }
+ }
+
+ XSync (dpy, False);
+
+ if (error_handler_hit_p)
+ goto DONE;
+ if (user_active_p (app, dpy, out_p))
+ {
+ status = 1; /* user activity status code */
+ goto DONE;
+ }
+ frames++;
+
+ if (now < prev + max)
+ usleep (1000000 * (prev + max - now));
+ prev = now;
+ }
+
+ if (verbose_p > 1)
+ fprintf (stderr, "%s: %.0f FPS\n", blurb(), frames / (now - start_time));
+ }
+
+ status = 0; /* completed fade with no user activity */
+
+ DONE:
+
+ if (orig_colors) free (orig_colors);
+ if (current_colors) free (current_colors);
+
+ /* If we've been given windows to raise after blackout, raise them before
+ releasing the colormaps.
+ */
+ if (out_p)
+ {
+ for (i = 0; i < nwindows; i++)
+ {
+ XClearWindow (dpy, saver_windows[i]);
+ XMapRaised (dpy, saver_windows[i]);
+ }
+ XSync(dpy, False);
+ }
+
+ /* Now put the target maps back.
+ If we're fading out, use the given cmap (or the default cmap, if none.)
+ If we're fading in, always use the default cmap.
+ */
+ for (i = 0; i < nscreens; i++)
+ {
+ Colormap cmap = window_cmaps[i];
+ if (!cmap || !out_p)
+ cmap = DefaultColormap(dpy, i);
+ XInstallColormap (dpy, cmap);
+ }
+
+ /* The fade (in or out) is complete, so we don't need the black maps on
+ stage any more.
+ */
+ for (i = 0; i < ncmaps; i++)
+ if (fade_cmaps[i])
+ {
+ XUninstallColormap(dpy, fade_cmaps[i]);
+ XFreeColormap(dpy, fade_cmaps[i]);
+ fade_cmaps[i] = 0;
+ }
+ free (window_cmaps);
+ free(fade_cmaps);
+ fade_cmaps = 0;
+
+ if (error_handler_hit_p) status = -1;
+ return status;
+}
+
+
+/****************************************************************************
+
+ SGI gamma fading
+
+ ****************************************************************************/
+
+#ifdef HAVE_SGI_VC_EXTENSION
+
+# include <X11/extensions/XSGIvc.h>
+
+struct screen_sgi_gamma_info {
+ int gamma_map; /* ??? always using 0 */
+ int nred, ngreen, nblue;
+ unsigned short *red1, *green1, *blue1;
+ unsigned short *red2, *green2, *blue2;
+ int gamma_size;
+ int gamma_precision;
+ Bool alpha_p;
+};
+
+
+static void sgi_whack_gamma(Display *dpy, int screen,
+ struct screen_sgi_gamma_info *info, float ratio);
+
+/* Returns:
+ 0: faded normally
+ 1: canceled by user activity
+ -1: unable to fade because the extension isn't supported.
+ */
+static int
+sgi_gamma_fade (XtAppContext app, Display *dpy,
+ Window *saver_windows, int nwindows,
+ double seconds, Bool out_p)
+{
+ int nscreens = ScreenCount(dpy);
+ struct timeval then, now;
+ int i, screen;
+ int status = -1;
+ struct screen_sgi_gamma_info *info = (struct screen_sgi_gamma_info *)
+ calloc(nscreens, sizeof(*info));
+
+ if (verbose_p > 1)
+ fprintf (stderr, "%s: sgi fade %s\n",
+ blurb(), (out_p ? "out" : "in"));
+
+ /* Get the current gamma maps for all screens.
+ Bug out and return -1 if we can't get them for some screen.
+ */
+ for (screen = 0; screen < nscreens; screen++)
+ {
+ if (!XSGIvcQueryGammaMap(dpy, screen, info[screen].gamma_map,
+ &info[screen].gamma_size,
+ &info[screen].gamma_precision,
+ &info[screen].alpha_p))
+ goto FAIL;
+
+ if (!XSGIvcQueryGammaColors(dpy, screen, info[screen].gamma_map,
+ XSGIVC_COMPONENT_RED,
+ &info[screen].nred, &info[screen].red1))
+ goto FAIL;
+ if (! XSGIvcQueryGammaColors(dpy, screen, info[screen].gamma_map,
+ XSGIVC_COMPONENT_GREEN,
+ &info[screen].ngreen, &info[screen].green1))
+ goto FAIL;
+ if (!XSGIvcQueryGammaColors(dpy, screen, info[screen].gamma_map,
+ XSGIVC_COMPONENT_BLUE,
+ &info[screen].nblue, &info[screen].blue1))
+ goto FAIL;
+
+ if (info[screen].gamma_precision == 8) /* Scale it up to 16 bits. */
+ {
+ int j;
+ for(j = 0; j < info[screen].nred; j++)
+ info[screen].red1[j] =
+ ((info[screen].red1[j] << 8) | info[screen].red1[j]);
+ for(j = 0; j < info[screen].ngreen; j++)
+ info[screen].green1[j] =
+ ((info[screen].green1[j] << 8) | info[screen].green1[j]);
+ for(j = 0; j < info[screen].nblue; j++)
+ info[screen].blue1[j] =
+ ((info[screen].blue1[j] << 8) | info[screen].blue1[j]);
+ }
+
+ info[screen].red2 = (unsigned short *)
+ malloc(sizeof(*info[screen].red2) * (info[screen].nred+1));
+ info[screen].green2 = (unsigned short *)
+ malloc(sizeof(*info[screen].green2) * (info[screen].ngreen+1));
+ info[screen].blue2 = (unsigned short *)
+ malloc(sizeof(*info[screen].blue2) * (info[screen].nblue+1));
+ }
+
+#ifdef GETTIMEOFDAY_TWO_ARGS
+ gettimeofday(&then, &tzp);
+#else
+ gettimeofday(&then);
+#endif
+
+ /* If we're fading in (from black), then first crank the gamma all the
+ way down to 0, then take the windows off the screen.
+ */
+ if (!out_p)
+ {
+ for (screen = 0; screen < nscreens; screen++)
+ sgi_whack_gamma(dpy, screen, &info[screen], 0.0);
+
+ for (screen = 0; screen < nwindows; screen++)
+ {
+ XUnmapWindow (dpy, saver_windows[screen]);
+ XClearWindow (dpy, saver_windows[screen]);
+ XSync(dpy, False);
+ }
+ }
+
+ /* Run the animation at the maximum frame rate in the time allotted. */
+ {
+ double start_time = double_time();
+ double end_time = start_time + seconds;
+ double prev = 0;
+ double now;
+ int frames = 0;
+ double max = 1/60.0; /* max FPS */
+ while ((now = double_time()) < end_time)
+ {
+ double ratio = (end_time - now) / seconds;
+ if (!out_p) ratio = 1-ratio;
+
+ for (screen = 0; screen < nwindows; screen++)
+ sgi_whack_gamma (dpy, screen, &info[screen], ratio);
+
+ if (error_handler_hit_p)
+ goto FAIL;
+ if (user_active_p (app, dpy, out_p))
+ {
+ status = 1; /* user activity status code */
+ goto DONE;
+ }
+ frames++;
+
+ if (now < prev + max)
+ usleep (1000000 * (prev + max - now));
+ prev = now;
+ }
+
+ if (verbose_p > 1)
+ fprintf (stderr, "%s: %.0f FPS\n", blurb(), frames / (now - start_time));
+ }
+
+ status = 0; /* completed fade with no user activity */
+
+ DONE:
+
+ if (out_p)
+ {
+ for (screen = 0; screen < nwindows; screen++)
+ {
+ XClearWindow (dpy, saver_windows[screen]);
+ XMapRaised (dpy, saver_windows[screen]);
+ }
+ XSync(dpy, False);
+ }
+
+ /* I can't explain this; without this delay, we get a flicker.
+ I suppose there's some lossage with stale bits being in the
+ hardware frame buffer or something, and this delay gives it
+ time to flush out. This sucks! */
+ usleep(100000); /* 1/10th second */
+
+ for (screen = 0; screen < nscreens; screen++)
+ sgi_whack_gamma(dpy, screen, &info[screen], 1.0);
+ XSync(dpy, False);
+
+ FAIL:
+ for (screen = 0; screen < nscreens; screen++)
+ {
+ if (info[screen].red1) free (info[screen].red1);
+ if (info[screen].green1) free (info[screen].green1);
+ if (info[screen].blue1) free (info[screen].blue1);
+ if (info[screen].red2) free (info[screen].red2);
+ if (info[screen].green2) free (info[screen].green2);
+ if (info[screen].blue2) free (info[screen].blue2);
+ }
+ free(info);
+
+ if (verbose_p > 1 && status)
+ fprintf (stderr, "%s: SGI fade %s failed\n",
+ blurb(), (out_p ? "out" : "in"));
+
+ if (error_handler_hit_p) status = -1;
+ return status;
+}
+
+static void
+sgi_whack_gamma (Display *dpy, int screen, struct screen_sgi_gamma_info *info,
+ float ratio)
+{
+ int k;
+
+ if (ratio < 0) ratio = 0;
+ if (ratio > 1) ratio = 1;
+ for (k = 0; k < info->gamma_size; k++)
+ {
+ info->red2[k] = info->red1[k] * ratio;
+ info->green2[k] = info->green1[k] * ratio;
+ info->blue2[k] = info->blue1[k] * ratio;
+ }
+
+ XSGIvcStoreGammaColors16(dpy, screen, info->gamma_map, info->nred,
+ XSGIVC_MComponentRed, info->red2);
+ XSGIvcStoreGammaColors16(dpy, screen, info->gamma_map, info->ngreen,
+ XSGIVC_MComponentGreen, info->green2);
+ XSGIvcStoreGammaColors16(dpy, screen, info->gamma_map, info->nblue,
+ XSGIVC_MComponentBlue, info->blue2);
+ XSync(dpy, False);
+}
+
+#endif /* HAVE_SGI_VC_EXTENSION */
+
+
+/****************************************************************************
+
+ XFree86 gamma fading
+
+ ****************************************************************************/
+
+#ifdef HAVE_XF86VMODE_GAMMA
+
+#include <X11/extensions/xf86vmode.h>
+
+typedef struct {
+ XF86VidModeGamma vmg;
+ int size;
+ unsigned short *r, *g, *b;
+} xf86_gamma_info;
+
+static int xf86_check_gamma_extension (Display *dpy);
+static Bool xf86_whack_gamma (Display *dpy, int screen,
+ xf86_gamma_info *ginfo, float ratio);
+
+/* Returns:
+ 0: faded normally
+ 1: canceled by user activity
+ -1: unable to fade because the extension isn't supported.
+ */
+static int
+xf86_gamma_fade (XtAppContext app, Display *dpy,
+ Window *saver_windows, int nwindows,
+ double seconds, Bool out_p)
+{
+ int nscreens = ScreenCount(dpy);
+ int screen;
+ int status = -1;
+ xf86_gamma_info *info = 0;
+
+ static int ext_ok = -1;
+
+ if (verbose_p > 1)
+ fprintf (stderr, "%s: xf86 fade %s\n",
+ blurb(), (out_p ? "out" : "in"));
+
+ /* Only probe the extension once: the answer isn't going to change. */
+ if (ext_ok == -1)
+ ext_ok = xf86_check_gamma_extension (dpy);
+
+ /* If this server doesn't have the gamma extension, bug out. */
+ if (ext_ok == 0)
+ goto FAIL;
+
+# ifndef HAVE_XF86VMODE_GAMMA_RAMP
+ if (ext_ok == 2) ext_ok = 1; /* server is newer than client! */
+# endif
+
+ info = (xf86_gamma_info *) calloc(nscreens, sizeof(*info));
+
+ /* Get the current gamma maps for all screens.
+ Bug out and return -1 if we can't get them for some screen.
+ */
+ for (screen = 0; screen < nscreens; screen++)
+ {
+ if (ext_ok == 1) /* only have gamma parameter, not ramps. */
+ {
+ if (!XF86VidModeGetGamma(dpy, screen, &info[screen].vmg))
+ goto FAIL;
+ }
+# ifdef HAVE_XF86VMODE_GAMMA_RAMP
+ else if (ext_ok == 2) /* have ramps */
+ {
+ if (!XF86VidModeGetGammaRampSize(dpy, screen, &info[screen].size))
+ goto FAIL;
+ if (info[screen].size <= 0)
+ goto FAIL;
+
+ info[screen].r = (unsigned short *)
+ calloc(info[screen].size, sizeof(unsigned short));
+ info[screen].g = (unsigned short *)
+ calloc(info[screen].size, sizeof(unsigned short));
+ info[screen].b = (unsigned short *)
+ calloc(info[screen].size, sizeof(unsigned short));
+
+ if (!(info[screen].r && info[screen].g && info[screen].b))
+ goto FAIL;
+
+# if 0
+ if (verbose_p > 1 && out_p)
+ {
+ int i;
+ fprintf (stderr, "%s: initial gamma ramps, size %d:\n",
+ blurb(), info[screen].size);
+ fprintf (stderr, "%s: R:", blurb());
+ for (i = 0; i < info[screen].size; i++)
+ fprintf (stderr, " %d", info[screen].r[i]);
+ fprintf (stderr, "\n%s: G:", blurb());
+ for (i = 0; i < info[screen].size; i++)
+ fprintf (stderr, " %d", info[screen].g[i]);
+ fprintf (stderr, "\n%s: B:", blurb());
+ for (i = 0; i < info[screen].size; i++)
+ fprintf (stderr, " %d", info[screen].b[i]);
+ fprintf (stderr, "\n");
+ }
+# endif /* 0 */
+
+ if (!XF86VidModeGetGammaRamp(dpy, screen, info[screen].size,
+ info[screen].r,
+ info[screen].g,
+ info[screen].b))
+ goto FAIL;
+ }
+# endif /* HAVE_XF86VMODE_GAMMA_RAMP */
+ else
+ abort();
+ }
+
+ /* If we're fading in (from black), then first crank the gamma all the
+ way down to 0, then take the windows off the screen.
+ */
+ if (!out_p)
+ {
+ for (screen = 0; screen < nscreens; screen++)
+ xf86_whack_gamma(dpy, screen, &info[screen], 0.0);
+ for (screen = 0; screen < nwindows; screen++)
+ {
+ XUnmapWindow (dpy, saver_windows[screen]);
+ XClearWindow (dpy, saver_windows[screen]);
+ XSync(dpy, False);
+ }
+ }
+
+ /* Run the animation at the maximum frame rate in the time allotted. */
+ {
+ double start_time = double_time();
+ double end_time = start_time + seconds;
+ double prev = 0;
+ double now;
+ int frames = 0;
+ double max = 1/60.0; /* max FPS */
+ while ((now = double_time()) < end_time)
+ {
+ double ratio = (end_time - now) / seconds;
+ if (!out_p) ratio = 1-ratio;
+
+ for (screen = 0; screen < nscreens; screen++)
+ xf86_whack_gamma (dpy, screen, &info[screen], ratio);
+
+ if (error_handler_hit_p)
+ goto FAIL;
+ if (user_active_p (app, dpy, out_p))
+ {
+ status = 1; /* user activity status code */
+ goto DONE;
+ }
+ frames++;
+
+ if (now < prev + max)
+ usleep (1000000 * (prev + max - now));
+ prev = now;
+ }
+
+ if (verbose_p > 1)
+ fprintf (stderr, "%s: %.0f FPS\n", blurb(), frames / (now - start_time));
+ }
+
+ status = 0; /* completed fade with no user activity */
+
+ DONE:
+
+ if (out_p)
+ {
+ for (screen = 0; screen < nwindows; screen++)
+ {
+ XClearWindow (dpy, saver_windows[screen]);
+ XMapRaised (dpy, saver_windows[screen]);
+ }
+ XSync(dpy, False);
+ }
+
+ /* I can't explain this; without this delay, we get a flicker.
+ I suppose there's some lossage with stale bits being in the
+ hardware frame buffer or something, and this delay gives it
+ time to flush out. This sucks! */
+ usleep(100000); /* 1/10th second */
+
+ for (screen = 0; screen < nscreens; screen++)
+ xf86_whack_gamma(dpy, screen, &info[screen], 1.0);
+ XSync(dpy, False);
+
+ FAIL:
+ if (info)
+ {
+ for (screen = 0; screen < nscreens; screen++)
+ {
+ if (info[screen].r) free(info[screen].r);
+ if (info[screen].g) free(info[screen].g);
+ if (info[screen].b) free(info[screen].b);
+ }
+ free(info);
+ }
+
+ if (verbose_p > 1 && status)
+ fprintf (stderr, "%s: xf86 fade %s failed\n",
+ blurb(), (out_p ? "out" : "in"));
+
+ if (error_handler_hit_p) status = -1;
+ return status;
+}
+
+
+static Bool
+safe_XF86VidModeQueryVersion (Display *dpy, int *majP, int *minP)
+{
+ Bool result;
+ XErrorHandler old_handler;
+ XSync (dpy, False);
+ error_handler_hit_p = False;
+ old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
+
+ result = XF86VidModeQueryVersion (dpy, majP, minP);
+
+ XSync (dpy, False);
+ XSetErrorHandler (old_handler);
+ XSync (dpy, False);
+
+ return (error_handler_hit_p
+ ? False
+ : result);
+}
+
+
+
+/* VidModeExtension version 2.0 or better is needed to do gamma.
+ 2.0 added gamma values; 2.1 added gamma ramps.
+ */
+# define XF86_VIDMODE_GAMMA_MIN_MAJOR 2
+# define XF86_VIDMODE_GAMMA_MIN_MINOR 0
+# define XF86_VIDMODE_GAMMA_RAMP_MIN_MAJOR 2
+# define XF86_VIDMODE_GAMMA_RAMP_MIN_MINOR 1
+
+
+
+/* Returns 0 if gamma fading not available; 1 if only gamma value setting
+ is available; 2 if gamma ramps are available.
+ */
+static int
+xf86_check_gamma_extension (Display *dpy)
+{
+ int event, error, major, minor;
+
+ if (!XF86VidModeQueryExtension (dpy, &event, &error))
+ return 0; /* display doesn't have the extension. */
+
+ if (!safe_XF86VidModeQueryVersion (dpy, &major, &minor))
+ return 0; /* unable to get version number? */
+
+ if (major < XF86_VIDMODE_GAMMA_MIN_MAJOR ||
+ (major == XF86_VIDMODE_GAMMA_MIN_MAJOR &&
+ minor < XF86_VIDMODE_GAMMA_MIN_MINOR))
+ return 0; /* extension is too old for gamma. */
+
+ if (major < XF86_VIDMODE_GAMMA_RAMP_MIN_MAJOR ||
+ (major == XF86_VIDMODE_GAMMA_RAMP_MIN_MAJOR &&
+ minor < XF86_VIDMODE_GAMMA_RAMP_MIN_MINOR))
+ return 1; /* extension is too old for gamma ramps. */
+
+ /* Copacetic */
+ return 2;
+}
+
+
+/* XFree doesn't let you set gamma to a value smaller than this.
+ Apparently they didn't anticipate the trick I'm doing here...
+ */
+#define XF86_MIN_GAMMA 0.1
+
+
+static Bool
+xf86_whack_gamma(Display *dpy, int screen, xf86_gamma_info *info,
+ float ratio)
+{
+ XErrorHandler old_handler;
+ XSync (dpy, False);
+ error_handler_hit_p = False;
+ old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
+
+ if (ratio < 0) ratio = 0;
+ if (ratio > 1) ratio = 1;
+
+ if (info->size == 0) /* we only have a gamma number, not a ramp. */
+ {
+ XF86VidModeGamma g2;
+
+ g2.red = info->vmg.red * ratio;
+ g2.green = info->vmg.green * ratio;
+ g2.blue = info->vmg.blue * ratio;
+
+# ifdef XF86_MIN_GAMMA
+ if (g2.red < XF86_MIN_GAMMA) g2.red = XF86_MIN_GAMMA;
+ if (g2.green < XF86_MIN_GAMMA) g2.green = XF86_MIN_GAMMA;
+ if (g2.blue < XF86_MIN_GAMMA) g2.blue = XF86_MIN_GAMMA;
+# endif
+
+ if (! XF86VidModeSetGamma (dpy, screen, &g2))
+ return -1;
+ }
+ else
+ {
+# ifdef HAVE_XF86VMODE_GAMMA_RAMP
+
+ unsigned short *r, *g, *b;
+ int i;
+ r = (unsigned short *) malloc(info->size * sizeof(unsigned short));
+ g = (unsigned short *) malloc(info->size * sizeof(unsigned short));
+ b = (unsigned short *) malloc(info->size * sizeof(unsigned short));
+
+ for (i = 0; i < info->size; i++)
+ {
+ r[i] = info->r[i] * ratio;
+ g[i] = info->g[i] * ratio;
+ b[i] = info->b[i] * ratio;
+ }
+
+ if (! XF86VidModeSetGammaRamp(dpy, screen, info->size, r, g, b))
+ return -1;
+
+ free (r);
+ free (g);
+ free (b);
+
+# else /* !HAVE_XF86VMODE_GAMMA_RAMP */
+ abort();
+# endif /* !HAVE_XF86VMODE_GAMMA_RAMP */
+ }
+
+ XSync (dpy, False);
+ XSetErrorHandler (old_handler);
+ XSync (dpy, False);
+
+ return status;
+}
+
+#endif /* HAVE_XF86VMODE_GAMMA */
+
+
+/****************************************************************************
+
+ RANDR gamma fading
+
+ ****************************************************************************
+
+
+ Dec 2020: I noticed that gamma fading was not working on a Raspberry Pi
+ with Raspbian 10.6, and wrote this under the hypothesis that the XF86
+ gamma fade code was failing and maybe the RANDR version would work better.
+ Then I discovered that gamma simply isn't supported by the Raspberry Pi
+ HDMI driver:
+
+ https://github.com/raspberrypi/firmware/issues/1274
+
+ I should have tried this first and seen it not work:
+
+ xrandr --output HDMI-1 --brightness .1
+
+ Since I still don't have an answer to the question of whether the XF86
+ gamma fading method works on modern Linux systems that also have RANDR,
+ I'm leaving this new code turned off for now, as it is largely untested.
+ The new code would be useful if:
+
+ A) The XF86 way doesn't work but the RANDR way does, or
+ B) There exist systems that have RANDR but do not have XF86.
+
+ But until Raspberry Pi supports gamma, both gamma methods fail to work
+ for far too many users for them to be used in XScreenSaver.
+ */
+#ifdef HAVE_RANDR_12
+
+# include <X11/extensions/Xrandr.h>
+
+typedef struct {
+ RRCrtc crtc;
+ Bool enabled_p;
+ XRRCrtcGamma *gamma;
+} randr_gamma_info;
+
+
+static int
+randr_check_gamma_extension (Display *dpy)
+{
+ int event, error, major, minor;
+ if (! XRRQueryExtension (dpy, &event, &error))
+ return 0;
+
+ if (! XRRQueryVersion (dpy, &major, &minor)) {
+ if (verbose_p > 1) fprintf (stderr, "%s: no randr ext\n", blurb());
+ return 0;
+ }
+
+ /* Reject if < 1.5. It's possible that 1.2 - 1.4 work, but untested. */
+ if (major < 1 || (major == 1 && minor < 5)) {
+ if (verbose_p > 1) fprintf (stderr, "%s: randr ext only version %d.%d\n",
+ blurb(), major, minor);
+ return 0;
+ }
+
+ return 1;
+}
+
+
+static void randr_whack_gamma (Display *dpy, int screen,
+ randr_gamma_info *ginfo, float ratio);
+
+/* Returns:
+ 0: faded normally
+ 1: canceled by user activity
+ -1: unable to fade because the extension isn't supported.
+ */
+static int
+randr_gamma_fade (XtAppContext app, Display *dpy,
+ Window *saver_windows, int nwindows,
+ double seconds, Bool out_p)
+{
+ int xsc = ScreenCount (dpy);
+ int nscreens = 0;
+ int j, screen;
+ int status = -1;
+ randr_gamma_info *info = 0;
+
+ static int ext_ok = -1;
+
+ if (verbose_p > 1)
+ fprintf (stderr, "%s: randr fade %s\n",
+ blurb(), (out_p ? "out" : "in"));
+
+ /* Only probe the extension once: the answer isn't going to change. */
+ if (ext_ok == -1)
+ ext_ok = randr_check_gamma_extension (dpy);
+
+ /* If this server doesn't have the RANDR extension, bug out. */
+ if (ext_ok == 0)
+ goto FAIL;
+
+ /* Add up the virtual screens on each X screen. */
+ for (screen = 0; screen < xsc; screen++)
+ {
+ XRRScreenResources *res =
+ XRRGetScreenResources (dpy, RootWindow (dpy, screen));
+ nscreens += res->noutput;
+ XRRFreeScreenResources (res);
+ }
+
+ if (nscreens <= 0)
+ goto FAIL;
+
+ info = (randr_gamma_info *) calloc(nscreens, sizeof(*info));
+
+ /* Get the current gamma maps for all screens.
+ Bug out and return -1 if we can't get them for some screen.
+ */
+ for (screen = 0, j = 0; screen < xsc; screen++)
+ {
+ XRRScreenResources *res =
+ XRRGetScreenResources (dpy, RootWindow (dpy, screen));
+ int k;
+ for (k = 0; k < res->noutput; k++, j++)
+ {
+ XRROutputInfo *rroi = XRRGetOutputInfo (dpy, res, res->outputs[j]);
+ RRCrtc crtc = (rroi->crtc ? rroi->crtc :
+ rroi->ncrtc ? rroi->crtcs[0] : 0);
+
+ info[j].crtc = crtc;
+ info[j].gamma = XRRGetCrtcGamma (dpy, crtc);
+
+ /* #### is this test sufficient? */
+ info[j].enabled_p = (rroi->connection != RR_Disconnected);
+
+# if 0
+ if (verbose_p > 1 && out_p)
+ {
+ int m;
+ fprintf (stderr, "%s: initial gamma ramps, size %d:\n",
+ blurb(), info[j].gamma->size);
+ fprintf (stderr, "%s: R:", blurb());
+ for (m = 0; m < info[j].gamma->size; m++)
+ fprintf (stderr, " %d", info[j].gamma->red[m]);
+ fprintf (stderr, "\n%s: G:", blurb());
+ for (m = 0; m < info[j].gamma->size; m++)
+ fprintf (stderr, " %d", info[j].gamma->green[m]);
+ fprintf (stderr, "\n%s: B:", blurb());
+ for (m = 0; m < info[j].gamma->size; m++)
+ fprintf (stderr, " %d", info[j].gamma->blue[m]);
+ fprintf (stderr, "\n");
+ }
+# endif /* 0 */
+
+ XRRFreeOutputInfo (rroi);
+ }
+ XRRFreeScreenResources (res);
+ }
+
+ /* If we're fading in (from black), then first crank the gamma all the
+ way down to 0, then take the windows off the screen.
+ */
+ if (!out_p)
+ {
+ for (screen = 0; screen < nscreens; screen++)
+ randr_whack_gamma(dpy, screen, &info[screen], 0.0);
+ for (screen = 0; screen < nwindows; screen++)
+ {
+ XUnmapWindow (dpy, saver_windows[screen]);
+ XClearWindow (dpy, saver_windows[screen]);
+ XSync(dpy, False);
+ }
+ }
+
+ /* Run the animation at the maximum frame rate in the time allotted. */
+ {
+ double start_time = double_time();
+ double end_time = start_time + seconds;
+ double prev = 0;
+ double now;
+ int frames = 0;
+ double max = 1/60.0; /* max FPS */
+ while ((now = double_time()) < end_time)
+ {
+ double ratio = (end_time - now) / seconds;
+ if (!out_p) ratio = 1-ratio;
+
+ for (screen = 0; screen < nwindows; screen++)
+ {
+ if (!info[screen].enabled_p)
+ continue;
+
+ randr_whack_gamma (dpy, screen, &info[screen], ratio);
+ }
+
+ if (error_handler_hit_p)
+ goto FAIL;
+ if (user_active_p (app, dpy, out_p))
+ {
+ status = 1; /* user activity status code */
+ goto DONE;
+ }
+ frames++;
+
+ if (now < prev + max)
+ usleep (1000000 * (prev + max - now));
+ prev = now;
+ }
+
+ if (verbose_p > 1)
+ fprintf (stderr, "%s: %.0f FPS\n", blurb(), frames / (now - start_time));
+ }
+
+ status = 0; /* completed fade with no user activity */
+
+ DONE:
+
+ if (out_p)
+ {
+ for (screen = 0; screen < nwindows; screen++)
+ {
+ XClearWindow (dpy, saver_windows[screen]);
+ XMapRaised (dpy, saver_windows[screen]);
+ }
+ XSync(dpy, False);
+ }
+
+ /* I can't explain this; without this delay, we get a flicker.
+ I suppose there's some lossage with stale bits being in the
+ hardware frame buffer or something, and this delay gives it
+ time to flush out. This sucks! */
+ /* #### That comment was about XF86, not verified with randr. */
+ usleep(100000); /* 1/10th second */
+
+ for (screen = 0; screen < nscreens; screen++)
+ randr_whack_gamma (dpy, screen, &info[screen], 1.0);
+ XSync(dpy, False);
+
+ FAIL:
+ if (info)
+ {
+ for (screen = 0; screen < nscreens; screen++)
+ {
+ if (info[screen].gamma) XRRFreeGamma (info[screen].gamma);
+ }
+ free(info);
+ }
+
+ if (verbose_p > 1 && status)
+ fprintf (stderr, "%s: randr fade %s failed\n",
+ blurb(), (out_p ? "out" : "in"));
+
+ return status;
+}
+
+
+static void
+randr_whack_gamma (Display *dpy, int screen, randr_gamma_info *info,
+ float ratio)
+{
+ XErrorHandler old_handler;
+ XRRCrtcGamma *g2;
+ int i;
+
+ XSync (dpy, False);
+ error_handler_hit_p = False;
+ old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
+
+ if (ratio < 0) ratio = 0;
+ if (ratio > 1) ratio = 1;
+
+ g2 = XRRAllocGamma (info->gamma->size);
+ for (i = 0; i < info->gamma->size; i++)
+ {
+ g2->red[i] = ratio * info->gamma->red[i];
+ g2->green[i] = ratio * info->gamma->green[i];
+ g2->blue[i] = ratio * info->gamma->blue[i];
+ }
+
+ XRRSetCrtcGamma (dpy, info->crtc, g2);
+ XRRFreeGamma (g2);
+
+ XSync (dpy, False);
+ XSetErrorHandler (old_handler);
+ XSync (dpy, False);
+
+ return 0;
+}
+
+#endif /* HAVE_RANDR_12 */
+
+
+/****************************************************************************
+
+ XSHM screen-shot fading
+
+ ****************************************************************************/
+
+typedef struct {
+ GC gc;
+ Window window;
+ Pixmap screenshot;
+ XImage *src, *intermediate;
+} xshm_fade_info;
+
+
+static int xshm_whack (Display *, XShmSegmentInfo *,
+ xshm_fade_info *, float ratio);
+
+/* Returns:
+ 0: faded normally
+ 1: canceled by user activity
+ -1: unknown error
+ */
+static int
+xshm_fade (XtAppContext app, Display *dpy,
+ Window *saver_windows, int nwindows, double seconds,
+ Bool out_p, Bool from_desktop_p, fade_state *state)
+{
+ int screen;
+ int status = -1;
+ xshm_fade_info *info = 0;
+ XShmSegmentInfo shm_info;
+ Window saver_window = 0;
+ XErrorHandler old_handler = 0;
+
+ XSync (dpy, False);
+ old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
+ error_handler_hit_p = False;
+
+ if (verbose_p > 1)
+ fprintf (stderr, "%s: SHM fade %s\n",
+ blurb(), (out_p ? "out" : "in"));
+
+ info = (xshm_fade_info *) calloc(nwindows, sizeof(*info));
+ if (!info) goto FAIL;
+
+ saver_window = find_screensaver_window (dpy, 0);
+ if (!saver_window) goto FAIL;
+
+ /* Retrieve a screenshot of the area covered by each window.
+ Windows might not be mapped.
+ Bug out and return -1 if we can't get one for some screen.
+ */
+
+ for (screen = 0; screen < nwindows; screen++)
+ {
+ XWindowAttributes xgwa;
+ Window root;
+ XGCValues gcv;
+ GC gc;
+ unsigned long attrmask = 0;
+ XSetWindowAttributes attrs;
+
+ XGetWindowAttributes (dpy, saver_windows[screen], &xgwa);
+ root = RootWindowOfScreen (xgwa.screen);
+
+ info[screen].src =
+ create_xshm_image (dpy, xgwa.visual, xgwa.depth,
+ ZPixmap, &shm_info, xgwa.width, xgwa.height);
+ if (!info[screen].src) goto FAIL;
+
+ info[screen].intermediate =
+ create_xshm_image (dpy, xgwa.visual, xgwa.depth,
+ ZPixmap, &shm_info, xgwa.width, xgwa.height);
+ if (!info[screen].intermediate) goto FAIL;
+
+ if (!out_p)
+ {
+ /* If we are fading in, retrieve the saved screenshot from
+ before we faded out. */
+ if (state->nscreens <= screen) goto FAIL;
+ info[screen].screenshot = state->screenshots[screen];
+ }
+ else
+ {
+ /* Create a pixmap and grab a screenshot into it. */
+ info[screen].screenshot =
+ XCreatePixmap (dpy, root, xgwa.width, xgwa.height, xgwa.depth);
+ if (!info[screen].screenshot) goto FAIL;
+
+ gcv.function = GXcopy;
+ gcv.subwindow_mode = IncludeInferiors;
+ gc = XCreateGC (dpy, root, GCFunction | GCSubwindowMode, &gcv);
+ XCopyArea (dpy, root, info[screen].screenshot, gc,
+ xgwa.x, xgwa.y, xgwa.width, xgwa.height, 0, 0);
+ XFreeGC (dpy, gc);
+ }
+
+ /* Create the fader window for the animation. */
+ attrmask = CWOverrideRedirect;
+ attrs.override_redirect = True;
+ info[screen].window =
+ XCreateWindow (dpy, root, xgwa.x, xgwa.y,
+ xgwa.width, xgwa.height, xgwa.border_width, xgwa.depth,
+ InputOutput, xgwa.visual,
+ attrmask, &attrs);
+ if (!info[screen].window) goto FAIL;
+ /* XSelectInput (dpy, info[screen].window,
+ KeyPressMask | ButtonPressMask); */
+
+ /* Copy the screenshot pixmap to the source image */
+ if (! get_xshm_image (dpy, info[screen].screenshot, info[screen].src,
+ 0, 0, ~0L, &shm_info))
+ goto FAIL;
+
+ gcv.function = GXcopy;
+ info[screen].gc = XCreateGC (dpy, info[screen].window, GCFunction, &gcv);
+ }
+
+ /* If we're fading out from the desktop, save our screen shots for later use.
+ But not if we're fading out from the savers to black. In that case we
+ don't want to overwrite the desktop screenshot with the current screenshot
+ which is of the final frames of the just-killed graphics hacks. */
+ if (from_desktop_p)
+ {
+ if (!out_p) abort();
+ for (screen = 0; screen < state->nscreens; screen++)
+ if (state->screenshots[screen])
+ XFreePixmap (dpy, state->screenshots[screen]);
+ if (state->screenshots)
+ free (state->screenshots);
+ state->nscreens = nwindows;
+ state->screenshots = calloc (nwindows, sizeof(*state->screenshots));
+ if (!state->screenshots)
+ state->nscreens = 0;
+ for (screen = 0; screen < state->nscreens; screen++)
+ state->screenshots[screen] = info[screen].screenshot;
+ }
+
+ for (screen = 0; screen < nwindows; screen++)
+ {
+ if (out_p)
+ /* Copy the screenshot to the fader window */
+ XSetWindowBackgroundPixmap (dpy, info[screen].window,
+ info[screen].screenshot);
+ else
+ {
+ XSetWindowBackgroundPixmap (dpy, info[screen].window, None);
+ XSetWindowBackground (dpy, info[screen].window, BlackPixel (dpy, 0));
+ }
+
+ XMapRaised (dpy, info[screen].window);
+
+ /* Now that we have mapped the screenshot on the fader windows,
+ take the saver windows off the screen. */
+ if (out_p)
+ {
+ XUnmapWindow (dpy, saver_windows[screen]);
+ XClearWindow (dpy, saver_windows[screen]);
+ }
+ }
+
+ /* Run the animation at the maximum frame rate in the time allotted. */
+ {
+ double start_time = double_time();
+ double end_time = start_time + seconds;
+ double prev = 0;
+ double now;
+ int frames = 0;
+ double max = 1/60.0; /* max FPS */
+ while ((now = double_time()) < end_time)
+ {
+ double ratio = (end_time - now) / seconds;
+ if (!out_p) ratio = 1-ratio;
+
+ for (screen = 0; screen < nwindows; screen++)
+ if (xshm_whack (dpy, &shm_info, &info[screen], ratio))
+ goto FAIL;
+
+ if (error_handler_hit_p)
+ goto FAIL;
+ if (user_active_p (app, dpy, out_p))
+ {
+ status = 1; /* user activity status code */
+ goto DONE;
+ }
+ frames++;
+
+ if (now < prev + max)
+ usleep (1000000 * (prev + max - now));
+ prev = now;
+ }
+
+ if (verbose_p > 1)
+ fprintf (stderr, "%s: %.0f FPS\n", blurb(), frames / (now - start_time));
+ }
+
+ status = 0; /* completed fade with no user activity */
+
+ DONE:
+
+ /* If we're fading out, we have completed the transition from what was
+ on the screen to black, using our fader windows. Now raise the saver
+ windows and take the fader windows off the screen. Since they're both
+ black, that will be imperceptible.
+ */
+ if (out_p)
+ {
+ for (screen = 0; screen < nwindows; screen++)
+ {
+ XClearWindow (dpy, saver_windows[screen]);
+ XMapRaised (dpy, saver_windows[screen]);
+ if (info[screen].window)
+ XUnmapWindow (dpy, info[screen].window);
+ }
+ }
+
+ XSync (dpy, False);
+
+ FAIL:
+
+ /* After fading in, take the saver windows off the screen before
+ destroying the occluding screenshot windows. */
+ if (!out_p)
+ {
+ for (screen = 0; screen < nwindows; screen++)
+ {
+ XUnmapWindow (dpy, saver_windows[screen]);
+ XClearWindow (dpy, saver_windows[screen]);
+ }
+ }
+
+ if (info)
+ {
+ for (screen = 0; screen < nwindows; screen++)
+ {
+ if (info[screen].src)
+ destroy_xshm_image (dpy, info[screen].src, &shm_info);
+ if (info[screen].intermediate)
+ destroy_xshm_image (dpy, info[screen].intermediate, &shm_info);
+ if (info[screen].window)
+ XDestroyWindow (dpy, info[screen].window);
+ if (info[screen].gc)
+ XFreeGC (dpy, info[screen].gc);
+ }
+ free (info);
+ }
+
+ /* If fading in, delete the screenshot pixmaps, and the list of them. */
+ if (!out_p && saver_window)
+ {
+ for (screen = 0; screen < state->nscreens; screen++)
+ if (state->screenshots[screen])
+ XFreePixmap (dpy, state->screenshots[screen]);
+ if (state->screenshots)
+ free (state->screenshots);
+ state->nscreens = 0;
+ state->screenshots = 0;
+ }
+
+ XSync (dpy, False);
+ XSetErrorHandler (old_handler);
+
+ if (error_handler_hit_p) status = -1;
+ if (verbose_p > 1 && status)
+ fprintf (stderr, "%s: SHM fade %s failed\n",
+ blurb(), (out_p ? "out" : "in"));
+
+ return status;
+}
+
+
+static int
+xshm_whack (Display *dpy, XShmSegmentInfo *shm_info,
+ xshm_fade_info *info, float ratio)
+{
+ unsigned char *inbits = (unsigned char *) info->src->data;
+ unsigned char *outbits = (unsigned char *) info->intermediate->data;
+ unsigned char *end = (outbits +
+ info->intermediate->bytes_per_line *
+ info->intermediate->height);
+ unsigned char ramp[256];
+ int i;
+
+ XSync (dpy, False);
+
+ if (ratio < 0) ratio = 0;
+ if (ratio > 1) ratio = 1;
+
+ for (i = 0; i < sizeof(ramp); i++)
+ ramp[i] = i * ratio;
+ while (outbits < end)
+ *outbits++ = ramp[*inbits++];
+
+ put_xshm_image (dpy, info->window, info->gc, info->intermediate, 0, 0, 0, 0,
+ info->intermediate->width, info->intermediate->height,
+ shm_info);
+ XSync (dpy, False);
+ return 0;
+}
diff --git a/driver/fade.h b/driver/fade.h
new file mode 100644
index 0000000..56725b5
--- /dev/null
+++ b/driver/fade.h
@@ -0,0 +1,20 @@
+/* xscreensaver, Copyright © 1992-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.
+ */
+
+#ifndef __FADE_H__
+#define __FADE_H__
+
+/* Returns true if canceled by user activity. */
+extern Bool fade_screens (XtAppContext app, Display *dpy,
+ Window *black_windows, int nwindows,
+ double seconds, Bool out_p, Bool from_desktop_p,
+ void **closureP);
+#endif /* __FADE_H__ */
diff --git a/driver/passwd-helper.c b/driver/passwd-helper.c
index a3a6b92..4b17c63 100644
--- a/driver/passwd-helper.c
+++ b/driver/passwd-helper.c
@@ -1,6 +1,6 @@
/* passwd-helper.c --- verifying typed passwords with external helper program
+ * xscreensaver, Copyright © 1993-2021 Jamie Zawinski <jwz@jwz.org>
* written by Olaf Kirch <okir@suse.de>
- * xscreensaver, Copyright (c) 1993-2005 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
@@ -11,6 +11,18 @@
* implied warranty.
*/
+
+/*****************************************************************************
+
+ I strongly suspect that this code has not been used in decades, and I
+ am considering removing it. These details should be hidden behind PAM.
+ If you are using this code, email me and tell me why. -- jwz, Feb 2021
+
+ *****************************************************************************/
+
+#error "email jwz@jwz.org about passwd-helper.c"
+
+
/* The idea here is to be able to run xscreensaver without any setuid bits.
* Password verification happens through an external program that you feed
* your password to on stdin. The external command is invoked with a user
@@ -35,18 +47,6 @@
#ifndef NO_LOCKING /* whole file */
-#include <X11/Xlib.h> /* not used for much... */
-
-/* This file doesn't need the Xt headers, so stub these types out... */
-#undef XtPointer
-#define XtAppContext void*
-#define XrmDatabase void*
-#define XtIntervalId void*
-#define XtPointer void*
-#define Widget void*
-
-#include "xscreensaver.h"
-
#include <stdlib.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
@@ -57,11 +57,14 @@
#include <sys/types.h>
#include <pwd.h>
#include <errno.h>
-
#include <sys/wait.h>
+#include "blurb.h"
+#include "auth.h"
+
+
static int
-ext_run (const char *user, const char *typed_passwd, int verbose_p)
+ext_run (const char *user, const char *typed_passwd)
{
int pfd[2], status;
pid_t pid;
@@ -70,10 +73,7 @@ ext_run (const char *user, const char *typed_passwd, int verbose_p)
return 0;
if (verbose_p)
- fprintf (stderr, "%s: ext_run (%s, %s)\n",
- blurb(), PASSWD_HELPER_PROGRAM, user);
-
- block_sigchld();
+ fprintf (stderr, "%s: EXT: %s\n", blurb(), PASSWD_HELPER_PROGRAM);
if ((pid = fork()) < 0) {
close(pfd[0]);
@@ -89,7 +89,7 @@ ext_run (const char *user, const char *typed_passwd, int verbose_p)
/* Helper is invoked as helper service-name [user] */
execlp(PASSWD_HELPER_PROGRAM, PASSWD_HELPER_PROGRAM, "xscreensaver", user, NULL);
if (verbose_p)
- fprintf(stderr, "%s: %s\n", PASSWD_HELPER_PROGRAM,
+ fprintf(stderr, "%s: EXT: %s\n", PASSWD_HELPER_PROGRAM,
strerror(errno));
exit(1);
}
@@ -106,14 +106,11 @@ ext_run (const char *user, const char *typed_passwd, int verbose_p)
if (errno == EINTR)
continue;
if (verbose_p)
- fprintf(stderr, "%s: ext_run: waitpid failed: %s\n",
+ fprintf(stderr, "%s: EXT: waitpid failed: %s\n",
blurb(), strerror(errno));
- unblock_sigchld();
return 0;
}
- unblock_sigchld();
-
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
return 0;
return 1;
@@ -126,37 +123,36 @@ ext_run (const char *user, const char *typed_passwd, int verbose_p)
to root.
*/
int
-ext_passwd_valid_p (const char *typed_passwd, int verbose_p)
+ext_passwd_valid_p (void *closure, const char *typed_passwd)
{
struct passwd *pw;
int res = 0;
if ((pw = getpwuid(getuid())) != NULL)
- res = ext_run (pw->pw_name, typed_passwd, verbose_p);
+ res = ext_run (pw->pw_name, typed_passwd);
endpwent();
#ifdef ALLOW_ROOT_PASSWD
if (!res)
- res = ext_run ("root", typed_passwd, verbose_p);
+ res = ext_run ("root", typed_passwd);
#endif /* ALLOW_ROOT_PASSWD */
return res;
}
-int
-ext_priv_init (int argc, char **argv, int verbose_p)
+Bool
+ext_priv_init (void)
{
/* Make sure the passwd helper exists */
if (access(PASSWD_HELPER_PROGRAM, X_OK) < 0) {
fprintf(stderr,
- "%s: warning: %s does not exist.\n"
- "%s: password authentication via "
- "external helper will not work.\n",
+ "%s: EXT: warning: %s does not exist.\n"
+ "%s: EXT password authentication will not work.\n",
blurb(), PASSWD_HELPER_PROGRAM, blurb());
- return 0;
+ return False;
}
- return 1;
+ return True;
}
#endif /* NO_LOCKING -- whole file */
diff --git a/driver/passwd-kerberos.c b/driver/passwd-kerberos.c
index 202e0eb..7d94ebd 100644
--- a/driver/passwd-kerberos.c
+++ b/driver/passwd-kerberos.c
@@ -1,6 +1,6 @@
/* kpasswd.c --- verify kerberos passwords.
- * written by Nat Lanza (magus@cs.cmu.edu) for
- * xscreensaver, Copyright (c) 1993-2004 Jamie Zawinski <jwz@jwz.org>
+ * xscreensaver, Copyright © 1993-2021 Jamie Zawinski <jwz@jwz.org>
+ * written by Nat Lanza (magus@cs.cmu.edu)
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
@@ -11,6 +11,17 @@
* implied warranty.
*/
+/*****************************************************************************
+
+ I strongly suspect that this code has not been used in decades, and I
+ am considering removing it. These details should be hidden behind PAM.
+ If you are using this code, email me and tell me why. -- jwz, Feb 2021
+
+ *****************************************************************************/
+
+#error "email jwz@jwz.org about passwd-kerberos.c"
+
+
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
@@ -48,9 +59,7 @@
# include <des.h>
#endif /* !HAVE_KERBEROS5 */
-#if !defined(VMS) && !defined(HAVE_ADJUNCT_PASSWD)
-# include <pwd.h>
-#endif
+#include <pwd.h>
#ifdef __bsdi__
@@ -60,13 +69,9 @@
# endif
#endif /* __bsdi__ */
-/* blargh */
-#undef Bool
-#undef True
-#undef False
-#define Bool int
-#define True 1
-#define False 0
+#include "blurb.h"
+#include "auth.h"
+
/* The user information we need to store */
#ifdef HAVE_DARWIN
@@ -78,10 +83,6 @@
static const char *tk_file;
#endif /* !HAVE_DARWIN */
-/* warning suppression: duplicated in passwd.c */
-extern Bool kerberos_lock_init (int argc, char **argv, Bool verbose_p);
-extern Bool kerberos_passwd_valid_p (const char *typed_passwd, Bool verbose_p);
-
/* Called at startup to grab user, instance, and realm information
from the user's ticketfile (remember, name.inst@realm). Since we're
@@ -102,7 +103,7 @@ extern Bool kerberos_passwd_valid_p (const char *typed_passwd, Bool verbose_p);
We don't use the arguments we're given, though.
*/
Bool
-kerberos_lock_init (int argc, char **argv, Bool verbose_p)
+kerberos_lock_init (void)
{
# ifdef HAVE_DARWIN
@@ -182,7 +183,7 @@ key_to_key(char *user, char *instance, char *realm, char *passwd, C_Block key)
some sites. So, we do a quick, painful hack with a tmpfile.
*/
Bool
-kerberos_passwd_valid_p (const char *typed_passwd, Bool verbose_p)
+kerberos_passwd_valid_p (void *closure, const char *typed_passwd)
{
# ifdef HAVE_DARWIN
return (klNoErr ==
@@ -201,10 +202,10 @@ kerberos_passwd_valid_p (const char *typed_passwd, Bool verbose_p)
/* temporarily switch to a new ticketfile.
I'm not using tmpnam() because it isn't entirely portable.
this could probably be fixed with autoconf. */
- newtkfile = malloc(80 * sizeof(char));
- memset(newtkfile, 0, sizeof(newtkfile));
-
- sprintf(newtkfile, "/tmp/xscrn-%i.XXXXXX", getpid());
+ char *tmpdir = getenv("TMPDIR");
+ if (!tmpdir || !*tmpdir) tmpdir = "/tmp";
+ newtkfile = malloc (strlen(tmpdir) + 40);
+ sprintf (newtkfile, "%s/xscreensaver.XXXXXX", tmpdir);
if( (fh = mkstemp(newtkfile)) < 0)
{
diff --git a/driver/passwd-pam.c b/driver/passwd-pam.c
index d463bc2..87942ab 100644
--- a/driver/passwd-pam.c
+++ b/driver/passwd-pam.c
@@ -1,7 +1,6 @@
/* passwd-pam.c --- verifying typed passwords with PAM
- * (Pluggable Authentication Modules.)
- * written by Bill Nottingham <notting@redhat.com> (and jwz) for
- * xscreensaver, Copyright (c) 1993-2017 Jamie Zawinski <jwz@jwz.org>
+ * xscreensaver, Copyright © 1993-2021 Jamie Zawinski <jwz@jwz.org>
+ * By Bill Nottingham <notting@redhat.com> and jwz.
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
@@ -10,26 +9,37 @@
* documentation. No representations are made about the suitability of this
* software for any purpose. It is provided "as is" without express or
* implied warranty.
- *
- * Some PAM resources:
- *
- * PAM home page:
- * http://www.us.kernel.org/pub/linux/libs/pam/
- *
- * PAM FAQ:
- * http://www.us.kernel.org/pub/linux/libs/pam/FAQ
- *
- * PAM Application Developers' Guide:
- * http://www.us.kernel.org/pub/linux/libs/pam/Linux-PAM-html/Linux-PAM_ADG.html
- *
- * PAM Mailing list archives:
- * http://www.linuxhq.com/lnxlists/linux-pam/
- *
- * Compatibility notes, especially between Linux and Solaris:
- * http://www.contrib.andrew.cmu.edu/u/shadow/pam.html
- *
- * The Open Group's PAM API documentation:
- * http://www.opengroup.org/onlinepubs/8329799/pam_start.htm
+ */
+
+/* PAM sucks in that there is no way to tell whether a particular service is
+ configured at all. That is, there is no way to tell the difference between
+ "authentication of the FOO service is not allowed" and "the user typed the
+ wrong password."
+
+ On RedHat 5.1 systems, if a service name is not known, it defaults to being
+ not allowed (because the fallback service, /etc/pam.d/other, is set to
+ `pam_deny'.)
+
+ On Solaris 2.6 systems, unknown services default to authenticating normally.
+
+ So, we require that an "xscreensaver" PAM service exist. This has a bad
+ failure mode, however: if that service doesn't exist, then XScreenSaver
+ will lock the screen, but is unable to unlock. (With the non-PAM password
+ code, XScreenSaver can refuse to lock, because it is able to determine up
+ front that it was unable to retrieve the password info.)
+
+ At startup, we check for the existence of /etc/pam.d/xscreensaver or
+ /etc/pam.conf, and if those don't exist, we print a warning that PAM is
+ probably not configured properly. This isn't *necessarily* correct, since
+ those files are not a part of PAM's C API, but it's how real-world systems
+ actually work.
+
+ Also note that FreeBSD's implementation of PAM requires the calling process
+ to be running as root during the entire interactive PAM conversation: it
+ can't ever disavow privileges. Linux's PAM implementation uses a setuid
+ helper so that a non-root process can still authenticate, as is right and
+ proper. Consequently, XScreenSaver does not support PAM on FreeBSD.
+ Dear FreeBSD, get your shit together.
*/
#ifdef HAVE_CONFIG_H
@@ -43,36 +53,17 @@
# include <unistd.h>
#endif
-extern char *blurb(void);
-
-
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
+#include <sys/stat.h>
#include <pwd.h>
#include <grp.h>
#include <security/pam_appl.h>
-#include <signal.h>
-#include <errno.h>
-#include <X11/Intrinsic.h>
-
-#include <sys/stat.h>
+#include "blurb.h"
#include "auth.h"
-extern sigset_t block_sigchld (void);
-extern void unblock_sigchld (void);
-
-/* blargh */
-#undef Bool
-#undef True
-#undef False
-#define Bool int
-#define True 1
-#define False 0
-
-#undef countof
-#define countof(x) (sizeof((x))/sizeof(*(x)))
/* Some time between Red Hat 4.2 and 7.0, the words were transposed
in the various PAM_x_CRED macro names. Yay!
@@ -89,10 +80,14 @@ static int pam_conversation (int nmsgs,
struct pam_response **resp,
void *closure);
-void pam_try_unlock(saver_info *si, Bool verbose_p,
- Bool (*valid_p)(const char *typed_passwd, Bool verbose_p));
+typedef struct {
+ Bool (*conv_fn) (void *closure,
+ int nmsgs,
+ const auth_message *msg,
+ auth_response **resp);
+ void *conv_fn_closure;
+} pam_conv_closure;
-Bool pam_priv_init (int argc, char **argv, Bool verbose_p);
#ifdef HAVE_PAM_FAIL_DELAY
/* We handle delays ourself.*/
@@ -116,52 +111,6 @@ Bool pam_priv_init (int argc, char **argv, Bool verbose_p);
#endif /* !PAM_STRERROR_TWO_ARGS */
-/* PAM sucks in that there is no way to tell whether a particular service
- is configured at all. That is, there is no way to tell the difference
- between "authentication of the FOO service is not allowed" and "the
- user typed the wrong password."
-
- On RedHat 5.1 systems, if a service name is not known, it defaults to
- being not allowed (because the fallback service, /etc/pam.d/other, is
- set to `pam_deny'.)
-
- On Solaris 2.6 systems, unknown services default to authenticating normally.
-
- So, we could simply require that the person who installs xscreensaver
- set up an "xscreensaver" PAM service. However, if we went that route,
- it would have a really awful failure mode: the failure mode would be that
- xscreensaver was willing to *lock* the screen, but would be unwilling to
- *unlock* the screen. (With the non-PAM password code, the analagous
- situation -- security not being configured properly, for example do to the
- executable not being installed as setuid root -- the failure mode is much
- more palettable, in that xscreensaver will refuse to *lock* the screen,
- because it can know up front that there is no password that will work.)
-
- Another route would be to have the service name to consult be computed at
- compile-time (perhaps with a configure option.) However, that doesn't
- really solve the problem, because it means that the same executable might
- work fine on one machine, but refuse to unlock when run on another
- machine.
-
- Another alternative would be to look in /etc/pam.conf or /etc/pam.d/ at
- runtime to see what services actually exist. But I think that's no good,
- because who is to say that the PAM info is actually specified in those
- files? Opening and reading those files is not a part of the PAM client
- API, so it's not guarenteed to work on any given system.
-
- An alternative I tried was to specify a list of services to try, and to
- try them all in turn ("xscreensaver", "xlock", "xdm", and "login").
- This worked, but it was slow (and I also had to do some contortions to
- work around bugs in Linux PAM 0.64-3.)
-
- So what we do today is, try PAM once, and if that fails, try the usual
- getpwent() method. So if PAM doesn't work, it will at least make an
- attempt at looking up passwords in /etc/passwd or /etc/shadow instead.
-
- This all kind of blows. I'm not sure what else to do.
- */
-
-
/* On SunOS 5.6, the `pam_conv.appdata_ptr' slot seems to be ignored, and
the `closure' argument to pc.conv always comes in as random garbage.
So we get around this by using a global variable instead. Shoot me!
@@ -172,97 +121,70 @@ Bool pam_priv_init (int argc, char **argv, Bool verbose_p);
static void *suns_pam_implementation_blows = 0;
-/**
- * This function is the PAM conversation driver. It conducts a full
- * authentication round by invoking the GUI with various prompts.
+/* This function invokes the PAM conversation. It conducts a full
+ authentication round by presenting the GUI with various prompts.
*/
-void
-pam_try_unlock(saver_info *si, Bool verbose_p,
- Bool (*valid_p)(const char *typed_passwd, Bool verbose_p))
+Bool
+pam_try_unlock (void *closure,
+ Bool (*conv_fn) (void *closure,
+ int nmsgs,
+ const auth_message *msg,
+ auth_response **resp))
{
const char *service = PAM_SERVICE_NAME;
pam_handle_t *pamh = 0;
int status = -1;
struct pam_conv pc;
-# ifdef HAVE_SIGTIMEDWAIT
- sigset_t set;
- struct timespec timeout;
-# endif /* HAVE_SIGTIMEDWAIT */
+ struct passwd *p = getpwuid (getuid());
+ const char *user = (p && p->pw_name && *p->pw_name ? p->pw_name : 0);
+ pam_conv_closure conv_closure;
+
+ if (!user)
+ {
+ fprintf (stderr, "%s: PAM: unable to get current user\n", blurb());
+ return False;
+ }
+ conv_closure.conv_fn = conv_fn;
+ conv_closure.conv_fn_closure = closure;
+ pc.appdata_ptr = &conv_closure;
pc.conv = &pam_conversation;
- pc.appdata_ptr = (void *) si;
/* On SunOS 5.6, the `appdata_ptr' slot seems to be ignored, and the
`closure' argument to pc.conv always comes in as random garbage. */
- suns_pam_implementation_blows = (void *) si;
+ suns_pam_implementation_blows = pc.appdata_ptr;
/* Initialize PAM.
*/
- status = pam_start (service, si->user, &pc, &pamh);
+ status = pam_start (service, user, &pc, &pamh);
if (verbose_p)
- fprintf (stderr, "%s: pam_start (\"%s\", \"%s\", ...) ==> %d (%s)\n",
- blurb(), service, si->user,
+ fprintf (stderr, "%s: PAM: pam_start (\"%s\", \"%s\", ...) ==> %d (%s)\n",
+ blurb(), service, user,
status, PAM_STRERROR (pamh, status));
if (status != PAM_SUCCESS) goto DONE;
- /* #### We should set PAM_TTY to the display we're using, but we
- don't have that handy from here. So set it to :0.0, which is a
- good guess (and has the bonus of counting as a "secure tty" as
- far as PAM is concerned...)
- */
{
- char *tty = strdup (":0.0");
+ char *tty = getenv ("DISPLAY");
+ if (!tty || !*tty) tty = ":0.0";
status = pam_set_item (pamh, PAM_TTY, tty);
if (verbose_p)
fprintf (stderr, "%s: pam_set_item (p, PAM_TTY, \"%s\") ==> %d (%s)\n",
blurb(), tty, status, PAM_STRERROR(pamh, status));
- free (tty);
}
- /* Try to authenticate as the current user.
- We must turn off our SIGCHLD handler for the duration of the call to
- pam_authenticate(), because in some cases, the underlying PAM code
- will do this:
-
- 1: fork a setuid subprocess to do some dirty work;
- 2: read a response from that subprocess;
- 3: waitpid(pid, ...) on that subprocess.
-
- If we (the ignorant parent process) have a SIGCHLD handler, then there's
- a race condition between steps 2 and 3: if the subprocess exits before
- waitpid() was called, then our SIGCHLD handler fires, and gets notified
- of the subprocess death; then PAM's call to waitpid() fails, because the
- process has already been reaped.
-
- I consider this a bug in PAM, since the caller should be able to have
- whatever signal handlers it wants -- the PAM documentation doesn't say
- "oh by the way, if you use PAM, you can't use SIGCHLD."
- */
-
PAM_NO_DELAY(pamh);
if (verbose_p)
fprintf (stderr, "%s: pam_authenticate (...) ...\n", blurb());
-# ifdef HAVE_SIGTIMEDWAIT
- timeout.tv_sec = 0;
- timeout.tv_nsec = 1;
- set =
-# endif /* HAVE_SIGTIMEDWAIT */
- block_sigchld();
status = pam_authenticate (pamh, 0);
-# ifdef HAVE_SIGTIMEDWAIT
- sigtimedwait (&set, NULL, &timeout);
- /* #### What is the portable thing to do if we don't have it? */
-# endif /* HAVE_SIGTIMEDWAIT */
- unblock_sigchld();
if (verbose_p)
fprintf (stderr, "%s: pam_authenticate (...) ==> %d (%s)\n",
blurb(), status, PAM_STRERROR(pamh, status));
- if (status == PAM_SUCCESS) /* Win! */
+ if (status == PAM_SUCCESS) /* So far so good... */
{
int status2;
@@ -341,22 +263,16 @@ pam_try_unlock(saver_info *si, Bool verbose_p,
(status2 == PAM_SUCCESS ? "Success" : "Failure"));
}
- if (status == PAM_SUCCESS)
- si->unlock_state = ul_success; /* yay */
- else if (si->unlock_state == ul_cancel ||
- si->unlock_state == ul_time)
- ; /* more specific failures ok */
- else
- si->unlock_state = ul_fail; /* generic failure */
+ return (status == PAM_SUCCESS);
}
Bool
-pam_priv_init (int argc, char **argv, Bool verbose_p)
+pam_priv_init (void)
{
/* We have nothing to do at init-time.
However, we might as well do some error checking.
- If "/etc/pam.d" exists and is a directory, but "/etc/pam.d/xlock"
+ If "/etc/pam.d" exists and is a directory, but "/etc/pam.d/xscreensaver"
does not exist, warn that PAM probably isn't going to work.
This is a priv-init instead of a non-priv init in case the directory
@@ -375,8 +291,8 @@ pam_priv_init (int argc, char **argv, Bool verbose_p)
{
if (stat (file, &st) != 0)
fprintf (stderr,
- "%s: warning: %s does not exist.\n"
- "%s: password authentication via PAM is unlikely to work.\n",
+ "%s: PAM: warning: %s does not exist.\n"
+ "%s: PAM: password authentication is unlikely to work.\n",
blurb(), file, blurb());
}
else if (stat (file2, &st) == 0)
@@ -396,8 +312,8 @@ pam_priv_init (int argc, char **argv, Bool verbose_p)
if (!ok)
{
fprintf (stderr,
- "%s: warning: %s does not list the `%s' service.\n"
- "%s: password authentication via PAM is unlikely to work.\n",
+ "%s: PAM: warning: %s does not list the `%s' service.\n"
+ "%s: PAM: password authentication is unlikely to work.\n",
blurb(), file2, PAM_SERVICE_NAME, blurb());
}
}
@@ -406,8 +322,8 @@ pam_priv_init (int argc, char **argv, Bool verbose_p)
else
{
fprintf (stderr,
- "%s: warning: neither %s nor %s exist.\n"
- "%s: password authentication via PAM is unlikely to work.\n",
+ "%s: PAM: warning: neither %s nor %s exist.\n"
+ "%s: PAM: password authentication is unlikely to work.\n",
blurb(), file2, file, blurb());
}
@@ -416,23 +332,22 @@ pam_priv_init (int argc, char **argv, Bool verbose_p)
}
+/* This is pam_conv->conv */
static int
pam_conversation (int nmsgs,
const struct pam_message **msg,
struct pam_response **resp,
- void *vsaver_info)
+ void *closure)
{
- int i, ret = -1;
- struct auth_message *messages = 0;
- struct auth_response *authresp = 0;
+ int i;
+ auth_message *messages = 0;
+ auth_response *authresp = 0;
struct pam_response *pam_responses;
- saver_info *si = (saver_info *) vsaver_info;
- Bool verbose_p;
+ pam_conv_closure *conv_closure;
/* On SunOS 5.6, the `closure' argument always comes in as random garbage. */
- si = (saver_info *) suns_pam_implementation_blows;
-
- verbose_p = si->prefs.verbose_p;
+ closure = suns_pam_implementation_blows;
+ conv_closure = closure;
/* Converting the PAM prompts into the XScreenSaver native format.
* It was a design goal to collapse (INFO,PROMPT) pairs from PAM
@@ -443,11 +358,9 @@ pam_conversation (int nmsgs,
* pass along whatever was passed in here.
*/
- messages = calloc(nmsgs, sizeof(struct auth_message));
- pam_responses = calloc(nmsgs, sizeof(*pam_responses));
-
- if (!pam_responses || !messages)
- goto end;
+ messages = calloc (nmsgs, sizeof(*messages));
+ pam_responses = calloc (nmsgs, sizeof(*pam_responses));
+ if (!pam_responses || !messages) abort();
if (verbose_p)
fprintf (stderr, "%s: pam_conversation (", blurb());
@@ -483,44 +396,25 @@ pam_conversation (int nmsgs,
if (verbose_p)
fprintf (stderr, ") ...\n");
- ret = si->unlock_cb(nmsgs, messages, &authresp, si);
-
- /* #### If the user times out, or hits ESC or Cancel, we return PAM_CONV_ERR,
- and PAM logs this as an authentication failure. It would be nice if
- there was some way to indicate that this was a "cancel" rather than
- a "fail", so that it wouldn't show up in syslog, but I think the
- only options are PAM_SUCCESS and PAM_CONV_ERR. (I think that
- PAM_ABORT means "internal error", not "cancel".) Bleh.
- */
-
- if (ret == 0)
- {
- for (i = 0; i < nmsgs; ++i)
- pam_responses[i].resp = authresp[i].response;
- }
+ /* This opens the dialog box and runs the X11 event loop.
+ It only returns if the user entered a password.
+ If they hit cancel, or timed out, it exited.
+ */
+ conv_closure->conv_fn (conv_closure->conv_fn_closure, nmsgs, messages,
+ &authresp);
-end:
- if (messages)
- free(messages);
+ for (i = 0; i < nmsgs; ++i)
+ pam_responses[i].resp = authresp[i].response;
- if (authresp)
- free(authresp);
+ if (messages) free (messages);
+ if (authresp) free (authresp);
if (verbose_p)
- fprintf (stderr, "%s: pam_conversation (...) ==> %s\n", blurb(),
- (ret == 0 ? "PAM_SUCCESS" : "PAM_CONV_ERR"));
-
- if (ret == 0)
- {
- *resp = pam_responses;
- return PAM_SUCCESS;
- }
-
- /* Failure only */
- if (pam_responses)
- free(pam_responses);
+ fprintf (stderr, "%s: pam_conversation (...) ==> PAM_SUCCESS\n",
+ blurb());
- return PAM_CONV_ERR;
+ *resp = pam_responses;
+ return PAM_SUCCESS;
}
#endif /* NO_LOCKING -- whole file */
diff --git a/driver/passwd-pwent.c b/driver/passwd-pwent.c
index bb0edfc..b6c74de 100644
--- a/driver/passwd-pwent.c
+++ b/driver/passwd-pwent.c
@@ -1,5 +1,5 @@
/* passwd-pwent.c --- verifying typed passwords with the OS.
- * xscreensaver, Copyright (c) 1993-1998 Jamie Zawinski <jwz@jwz.org>
+ * 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
@@ -28,13 +28,8 @@
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
-#ifndef VMS
-# include <pwd.h>
-# include <grp.h>
-#else /* VMS */
-# include "vms-pwd.h"
-#endif /* VMS */
-
+#include <pwd.h>
+#include <grp.h>
#ifdef __bsdi__
# include <sys/param.h>
@@ -47,6 +42,7 @@
#if defined(HAVE_SHADOW_PASSWD) /* passwds live in /etc/shadow */
# include <shadow.h>
+# define PWNAME "spnam"
# define PWTYPE struct spwd *
# define PWPSLOT sp_pwdp
# define GETPW getspnam
@@ -56,6 +52,7 @@
# include <sys/security.h>
# include <prot.h>
+# define PWNAME "prpwnam"
# define PWTYPE struct pr_passwd *
# define PWPSLOT ufld.fd_encrypt
# define GETPW getprpwnam
@@ -66,6 +63,7 @@
# include <sys/audit.h>
# include <pwdadj.h>
+# define PWNAME "pwanam"
# define PWTYPE struct passwd_adjunct *
# define PWPSLOT pwa_passwd
# define GETPW getpwanam
@@ -75,43 +73,33 @@
# include <hpsecurity.h>
# include <prot.h>
+# define PWNAME "spwnam"
# define PWTYPE struct s_passwd *
# define PWPSLOT pw_passwd
# define GETPW getspwnam
# define HAVE_BIGCRYPT
-#endif
+#elif defined(HAVE_PWNAM_SHADOW_PASSWD)
+# define PWNAME "pwnam_shadow"
+# define PWTYPE struct passwd *
+# define PWPSLOT pw_passwd
+# define GETPW getpwnam_shadow
-/* blargh */
-#undef Bool
-#undef True
-#undef False
-#define Bool int
-#define True 1
-#define False 0
+#endif
+#include "blurb.h"
+#include "auth.h"
-extern const char *blurb(void);
+#ifdef ALLOW_ROOT_PASSWD
static char *encrypted_root_passwd = 0;
-static char *encrypted_user_passwd = 0;
-
-#ifdef VMS
-# define ROOT "SYSTEM"
-#else
-# define ROOT "root"
-#endif
-
-#ifndef VMS
-Bool pwent_priv_init (int argc, char **argv, Bool verbose_p);
-Bool pwent_lock_init (int argc, char **argv, Bool verbose_p);
-Bool pwent_passwd_valid_p (const char *typed_passwd, Bool verbose_p);
#endif
+static char *encrypted_user_passwd = 0;
+#define ROOT "root"
-#ifndef VMS
static char *
user_name (void)
@@ -140,17 +128,6 @@ user_name (void)
return (u ? strdup(u) : 0);
}
-#else /* VMS */
-
-static char *
-user_name (void)
-{
- char *u = getenv("USER");
- return (u ? strdup(u) : 0);
-}
-
-#endif /* VMS */
-
static Bool
passwd_known_p (const char *pw)
@@ -162,24 +139,28 @@ passwd_known_p (const char *pw)
static char *
-get_encrypted_passwd(const char *user)
+get_encrypted_passwd (const char *user)
{
char *result = 0;
+ const char *pwtype = "pwnam";
-#ifdef PWTYPE
+# ifdef PWTYPE
if (user && *user && !result)
{ /* First check the shadow passwords. */
PWTYPE p = GETPW((char *) user);
if (p && passwd_known_p (p->PWPSLOT))
- result = strdup(p->PWPSLOT);
+ {
+ result = strdup(p->PWPSLOT);
+ pwtype = PWNAME;
+ }
}
-#endif /* PWTYPE */
+# endif /* PWTYPE */
if (user && *user && !result)
{ /* Check non-shadow passwords too. */
struct passwd *p = getpwnam(user);
if (p && passwd_known_p (p->pw_passwd))
- result = strdup(p->pw_passwd);
+ result = strdup(p->pw_passwd);
}
/* The manual for passwd(4) on HPUX 10.10 says:
@@ -200,15 +181,20 @@ get_encrypted_passwd(const char *user)
*s = 0;
}
-#ifndef HAVE_PAM
- /* We only issue this warning if not compiled with support for PAM.
- If we're using PAM, it's not unheard of that normal pwent passwords
- would be unavailable. */
+ /* We only issue this warning in non-verbose mode if not compiled with
+ support for PAM. If we're using PAM, it's common for pwent passwords
+ to be unavailable. */
- if (!result)
- fprintf (stderr, "%s: couldn't get password of \"%s\"\n",
- blurb(), (user ? user : "(null)"));
-#endif /* !HAVE_PAM */
+ if (!result &&
+ (verbose_p
+# ifdef HAVE_PAM
+ || 0
+# else
+ || 1
+# endif
+ ))
+ fprintf (stderr, "%s: %s: couldn't get password of \"%s\"\n",
+ blurb(), pwtype, (user ? user : "(null)"));
return result;
}
@@ -221,10 +207,8 @@ get_encrypted_passwd(const char *user)
locking isn't possible. (It will also have written to stderr.)
*/
-#ifndef VMS
-
Bool
-pwent_priv_init (int argc, char **argv, Bool verbose_p)
+pwent_priv_init (void)
{
char *u;
@@ -235,7 +219,9 @@ pwent_priv_init (int argc, char **argv, Bool verbose_p)
u = user_name();
encrypted_user_passwd = get_encrypted_passwd(u);
+#ifdef ALLOW_ROOT_PASSWD
encrypted_root_passwd = get_encrypted_passwd(ROOT);
+#endif
if (u) free (u);
if (encrypted_user_passwd)
@@ -246,7 +232,7 @@ pwent_priv_init (int argc, char **argv, Bool verbose_p)
Bool
-pwent_lock_init (int argc, char **argv, Bool verbose_p)
+pwent_lock_init (void)
{
if (encrypted_user_passwd)
return True;
@@ -287,7 +273,7 @@ passwds_match_p (const char *cleartext, const char *ciphertext)
to root.
*/
Bool
-pwent_passwd_valid_p (const char *typed_passwd, Bool verbose_p)
+pwent_passwd_valid_p (void *closure, const char *typed_passwd)
{
if (encrypted_user_passwd &&
passwds_match_p (typed_passwd, encrypted_user_passwd))
@@ -305,8 +291,6 @@ pwent_passwd_valid_p (const char *typed_passwd, Bool verbose_p)
return False;
}
-#else /* VMS */
-Bool pwent_lock_init (int argc, char **argv, Bool verbose_p) { return True; }
-#endif /* VMS */
-
+#else /* NO_LOCKING */
+int _ignore_;
#endif /* NO_LOCKING -- whole file */
diff --git a/driver/passwd.c b/driver/passwd.c
index 68e1a0b..11ca134 100644
--- a/driver/passwd.c
+++ b/driver/passwd.c
@@ -1,5 +1,5 @@
/* passwd.c --- verifying typed passwords with the OS.
- * xscreensaver, Copyright (c) 1993-2019 Jamie Zawinski <jwz@jwz.org>
+ * 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
@@ -14,8 +14,6 @@
# include "config.h"
#endif
-#ifndef NO_LOCKING /* whole file */
-
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -26,11 +24,7 @@
#include <sys/time.h>
#include <sys/stat.h>
-#ifndef VMS
-# include <pwd.h> /* for getpwuid() */
-#else /* VMS */
-# include "vms-pwd.h"
-#endif /* VMS */
+#include <pwd.h> /* for getpwuid() */
#ifdef HAVE_SYSLOG
# include <syslog.h>
@@ -41,8 +35,24 @@
#include "xscreensaver.h"
#include "auth.h"
+
+#ifdef NO_LOCKING
+
+Bool lock_init (void) { return 0; }
+Bool lock_priv_init (void) { return 0; }
+Bool xscreensaver_auth (void *closure,
+ Bool (*conv_fn) (void *closure,
+ int nmsgs,
+ const auth_message *msg,
+ auth_response **resp),
+ void (*finished_fn) (void *closure, Bool status))
+{
+ return False;
+}
+
+#else /* NO_LOCKING -- whole file */
+
extern const char *blurb(void);
-extern void check_for_leaks (const char *where);
/* blargh */
@@ -58,100 +68,39 @@ extern void check_for_leaks (const char *where);
struct auth_methods {
const char *name;
- Bool (*init) (int argc, char **argv, Bool verbose_p);
- Bool (*priv_init) (int argc, char **argv, Bool verbose_p);
- Bool (*valid_p) (const char *typed_passwd, Bool verbose_p);
- void (*try_unlock) (saver_info *si, Bool verbose_p,
- Bool (*valid_p)(const char *typed_passwd, Bool verbose_p));
+ Bool (*init) (void);
+ Bool (*priv_init) (void);
+ Bool (*valid_p) (void *closure, const char *plaintext);
+ Bool (*try_unlock) (void *closure,
+ Bool (*conv_fn) (void *closure,
+ int nmsgs,
+ const auth_message *msg,
+ auth_response **resp));
Bool initted_p;
Bool priv_initted_p;
};
-#ifdef HAVE_KERBEROS
-extern Bool kerberos_lock_init (int argc, char **argv, Bool verbose_p);
-extern Bool kerberos_passwd_valid_p (const char *typed_passwd, Bool verbose_p);
-#endif
-#ifdef HAVE_PAM
-extern Bool pam_priv_init (int argc, char **argv, Bool verbose_p);
-extern void pam_try_unlock (saver_info *si, Bool verbose_p,
- Bool (*valid_p)(const char *typed_passwd, Bool verbose_p));
-#endif
-#ifdef PASSWD_HELPER_PROGRAM
-extern Bool ext_priv_init (int argc, char **argv, Bool verbose_p);
-extern Bool ext_passwd_valid_p (const char *typed_passwd, Bool verbose_p);
-#endif
-extern Bool pwent_lock_init (int argc, char **argv, Bool verbose_p);
-extern Bool pwent_priv_init (int argc, char **argv, Bool verbose_p);
-extern Bool pwent_passwd_valid_p (const char *typed_passwd, Bool verbose_p);
-
-Bool lock_priv_init (int argc, char **argv, Bool verbose_p);
-Bool lock_init (int argc, char **argv, Bool verbose_p);
-Bool passwd_valid_p (const char *typed_passwd, Bool verbose_p);
-
-/* The authorization methods to try, in order.
- Note that the last one (the pwent version) is actually two auth methods,
- since that code tries shadow passwords, and then non-shadow passwords.
- (It's all in the same file since the APIs are randomly nearly-identical.)
+
+/* The authorization methods to try, in order of preference.
+ The first that initializes successfully is used and others are ignored.
*/
struct auth_methods methods[] = {
# ifdef HAVE_PAM
- { "PAM", 0, pam_priv_init, 0, pam_try_unlock,
- False, False },
+ { "PAM", 0, pam_priv_init, 0, pam_try_unlock, 0, },
# endif
# ifdef HAVE_KERBEROS
- { "Kerberos", kerberos_lock_init, 0, kerberos_passwd_valid_p, 0,
- False, False },
+ { "KRB", kerberos_lock_init, 0, kerberos_passwd_valid_p, 0, },
# endif
# ifdef PASSWD_HELPER_PROGRAM
- { "external", 0, ext_priv_init, ext_passwd_valid_p, 0,
- False, False },
+ { "EXT", 0, ext_priv_init, ext_passwd_valid_p, 0, },
# endif
- { "normal", pwent_lock_init, pwent_priv_init, pwent_passwd_valid_p, 0,
- False, False }
+ { "pwnam", pwent_lock_init, pwent_priv_init, pwent_passwd_valid_p, 0, }
};
-# ifdef HAVE_PROC_OOM
-/* On some recent Linux systems you can tell the kernel's OOM-killer to
- consider the possibility of maybe sometimes not killing you in low-memory
- situations. Because that would unlock the screen. And that would be bad.
-
- Linux >= 2.6.11: echo -17 > /proc/$$/oom_adj <-- ignoring this.
- Linux >= 2.6.37: echo -1000 > /proc/$$/oom_score_adj <-- trying this.
- */
-static void
-oom_assassin_immunity (Bool verbose_p)
-{
- char fn[1024];
- struct stat st;
- FILE *out;
- sprintf (fn, "/proc/%d/oom_score_adj", getpid());
- if (stat(fn, &st) != 0)
- {
- if (verbose_p)
- fprintf (stderr, "%s: OOM: %s does not exist\n", blurb(), fn);
- return;
- }
- out = fopen (fn, "w");
- if (!out)
- {
- if (verbose_p)
- {
- char b[2048];
- sprintf (b, "%s: OOM: unable to write %s\n", blurb(), fn);
- perror(b);
- }
- return;
- }
- fputs ("-1000\n", out);
- fclose (out);
-}
-# endif /* HAVE_PROC_OOM */
-
-
Bool
-lock_priv_init (int argc, char **argv, Bool verbose_p)
+lock_priv_init (void)
{
int i;
Bool any_ok = False;
@@ -160,26 +109,17 @@ lock_priv_init (int argc, char **argv, Bool verbose_p)
if (!methods[i].priv_init)
methods[i].priv_initted_p = True;
else
- methods[i].priv_initted_p = methods[i].priv_init (argc, argv,
- verbose_p);
+ methods[i].priv_initted_p = methods[i].priv_init();
if (methods[i].priv_initted_p)
any_ok = True;
- else if (verbose_p)
- fprintf (stderr, "%s: initialization of %s passwords failed.\n",
- blurb(), methods[i].name);
}
-
-# ifdef HAVE_PROC_OOM
- oom_assassin_immunity (verbose_p);
-# endif
-
return any_ok;
}
Bool
-lock_init (int argc, char **argv, Bool verbose_p)
+lock_init (void)
{
int i;
Bool any_ok = False;
@@ -191,70 +131,75 @@ lock_init (int argc, char **argv, Bool verbose_p)
if (!methods[i].init)
methods[i].initted_p = True;
else
- methods[i].initted_p = methods[i].init (argc, argv, verbose_p);
+ methods[i].initted_p = methods[i].init();
if (methods[i].initted_p)
any_ok = True;
else if (verbose_p)
- fprintf (stderr, "%s: initialization of %s passwords failed.\n",
+ fprintf (stderr, "%s: %s: passwords initialization failed\n",
blurb(), methods[i].name);
}
return any_ok;
}
-/* A basic auth driver that simply prompts for a password then runs it through
- * valid_p to determine whether the password is correct.
+/* For those auth methods that have a 'valid_p' function instead of a
+ 'try_unlock' function, this does a PAM-like conversation that first
+ prompts for a password and then tests it with the 'valid_p' function.
*/
-static void
-try_unlock_password(saver_info *si,
- Bool verbose_p,
- Bool (*valid_p)(const char *typed_passwd, Bool verbose_p))
+static Bool
+try_valid_p (void *closure,
+ const char *name,
+ Bool (*valid_p) (void *closure, const char *typed_passwd),
+ Bool (*conv_fn) (void *closure,
+ int nmsgs,
+ const auth_message *msg,
+ auth_response **resp))
{
- struct auth_message message;
- struct auth_response *response = NULL;
+ auth_message message;
+ auth_response *response = NULL;
+ Bool ok = False;
- memset(&message, 0, sizeof(message));
+ memset (&message, 0, sizeof(message));
if (verbose_p)
- fprintf(stderr, "%s: non-PAM password auth.\n", blurb());
+ fprintf (stderr, "%s: %s: non-PAM password auth\n", blurb(), name);
- /* Call the auth_conv function with "Password:", then feed
- * the result into valid_p()
- */
+ /* Call the auth_conv function with "Password:", then feed the result
+ into valid_p() */
message.type = AUTH_MSGTYPE_PROMPT_NOECHO;
message.msg = "Password:";
- si->unlock_cb(1, &message, &response, si);
+ ok = conv_fn (closure, 1, &message, &response);
+ if (!response || !response->response)
+ ok = False;
- if (!response)
- return;
+ if (ok)
+ ok = valid_p (closure, response->response);
- if (valid_p (response->response, verbose_p))
- si->unlock_state = ul_success; /* yay */
- else if (si->unlock_state == ul_cancel ||
- si->unlock_state == ul_time)
- ; /* more specific failures ok */
- else
- si->unlock_state = ul_fail; /* generic failure */
+ if (response)
+ {
+ if (response->response)
+ free (response->response);
+ free (response);
+ }
- if (response->response)
- free(response->response);
- free(response);
+ return ok;
}
/* Write a password failure to the system log.
*/
static void
-do_syslog (saver_info *si, Bool verbose_p)
+do_syslog (void)
{
# ifdef HAVE_SYSLOG
struct passwd *pw = getpwuid (getuid ());
- char *d = (si->dpy ? DisplayString (si->dpy) : 0);
+ char *d = getenv ("DISPLAY");
char *u = (pw && pw->pw_name ? pw->pw_name : "???");
int opt = 0;
int fac = 0;
+ int pri = LOG_NOTICE;
# ifdef LOG_PID
opt = LOG_PID;
@@ -270,115 +215,57 @@ do_syslog (saver_info *si, Bool verbose_p)
if (!d) d = "";
-# undef FMT
-# define FMT "FAILED LOGIN %d ON DISPLAY \"%s\", FOR \"%s\""
-
- if (verbose_p)
- fprintf (stderr, "%s: syslog: " FMT "\n", blurb(),
- si->unlock_failures, d, u);
-
openlog (progname, opt, fac);
- syslog (LOG_NOTICE, FMT, si->unlock_failures, d, u);
+ syslog (pri, "Failed login on display \"%s\" for \"%s\"", d, u);
closelog ();
# endif /* HAVE_SYSLOG */
}
-
-/**
- * Runs through each authentication driver calling its try_unlock function.
- * Called xss_authenticate() because AIX beat us to the name authenticate().
+/* Runs through each authentication driver calling its try_unlock function.
*/
-void
-xss_authenticate(saver_info *si, Bool verbose_p)
+Bool
+xscreensaver_auth (void *closure,
+ Bool (*conv_fn) (void *closure,
+ int nmsgs,
+ const auth_message *msg,
+ auth_response **resp),
+ void (*finished_fn) (void *closure, Bool status))
{
- int i, j;
-
- si->unlock_state = ul_read;
+ int i;
+ Bool ok = False;
for (i = 0; i < countof(methods); i++)
{
if (!methods[i].initted_p)
continue;
- if (si->cached_passwd != NULL && methods[i].valid_p)
- si->unlock_state = (methods[i].valid_p(si->cached_passwd, verbose_p) == True)
- ? ul_success : ul_fail;
- else if (methods[i].try_unlock != NULL)
- methods[i].try_unlock(si, verbose_p, methods[i].valid_p);
+ if (methods[i].try_unlock)
+ ok = methods[i].try_unlock (closure, conv_fn);
else if (methods[i].valid_p)
- try_unlock_password(si, verbose_p, methods[i].valid_p);
- else /* Ze goggles, zey do nozing! */
- fprintf(stderr, "%s: authentication method %s does nothing.\n",
- blurb(), methods[i].name);
-
- check_for_leaks (methods[i].name);
-
- /* If password authentication failed, but the password was NULL
- (meaning the user just hit RET) then treat that as "cancel".
- This means that if the password is literally NULL, it will
- work; but if not, then NULL passwords are treated as cancel.
- */
- if (si->unlock_state == ul_fail &&
- si->cached_passwd &&
- !*si->cached_passwd)
- {
- if (verbose_p)
- fprintf (stderr, "%s: assuming null password means cancel.\n",
- blurb());
- si->unlock_state = ul_cancel;
- }
-
- if (si->unlock_state == ul_success)
- {
- /* If we successfully authenticated by method N, but attempting
- to authenticate by method N-1 failed, mention that (since if
- an earlier authentication method fails and a later one succeeds,
- something screwy is probably going on.)
- */
- if (verbose_p && i > 0)
- {
- for (j = 0; j < i; j++)
- if (methods[j].initted_p)
- fprintf (stderr,
- "%s: authentication via %s failed.\n",
- blurb(), methods[j].name);
- fprintf (stderr,
- "%s: authentication via %s succeeded.\n",
- blurb(), methods[i].name);
- }
- goto DONE; /* Successfully authenticated! */
- }
- else if (si->unlock_state == ul_cancel ||
- si->unlock_state == ul_time)
- {
- /* If any auth method gets a cancel or timeout, don't try the
- next auth method! We're done! */
- if (verbose_p)
- fprintf (stderr, "%s: authentication via %s %s.\n",
- blurb(), methods[i].name,
- (si->unlock_state == ul_cancel
- ? "cancelled" : "timed out"));
- goto DONE;
- }
+ ok = try_valid_p (closure, methods[i].name, methods[i].valid_p,
+ conv_fn);
+ else
+ abort(); /* method must have one or the other function */
+
+ /* Only try the first method that initialized properly. That means that
+ if PAM initialized correctly, we will never try pwent or Kerberos.
+ If we did, then typing an incorrect password at PAM would result in a
+ second password prompt that would only go to pwent. There's no
+ sensible way to re-use the password typed the first time, if there
+ even was one. With fingerprint readers or OTP fobs, there might have
+ been 0, 2, or more passwords entered. */
+ break;
}
- if (verbose_p)
- fprintf(stderr, "%s: All authentication mechanisms failed.\n", blurb());
+ if (!ok)
+ do_syslog ();
- if (si->unlock_state == ul_fail)
- {
- /* Note the time of the first failure */
- if (si->unlock_failures == 0)
- si->unlock_failure_time = time((time_t *) 0);
- si->unlock_failures++;
- do_syslog (si, verbose_p);
- }
+ if (finished_fn)
+ finished_fn (closure, ok);
-DONE:
- if (si->auth_finished_cb)
- si->auth_finished_cb (si);
+ return ok;
}
#endif /* NO_LOCKING -- whole file */
diff --git a/driver/prefs.c b/driver/prefs.c
index 94e7a0c..a65b679 100644
--- a/driver/prefs.c
+++ b/driver/prefs.c
@@ -1,5 +1,5 @@
-/* dotfile.c --- management of the ~/.xscreensaver file.
- * xscreensaver, Copyright (c) 1998-2020 Jamie Zawinski <jwz@jwz.org>
+/* prefs.c --- reading and writing the ~/.xscreensaver file.
+ * xscreensaver, Copyright © 1998-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
@@ -14,309 +14,20 @@
# include "config.h"
#endif
-#include <stdlib.h>
-
-#ifdef HAVE_UNISTD_H
-# include <unistd.h>
-#endif
-
#include <stdio.h>
-#include <ctype.h>
+#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/time.h>
-#include <sys/param.h> /* for PATH_MAX */
-#include <X11/Xlib.h>
-#include <X11/Xresource.h>
-
-#ifndef VMS
-# include <pwd.h>
-#else /* VMS */
-# include "vms-pwd.h"
-#endif /* VMS */
-
-
-/* This file doesn't need the Xt headers, so stub these types out... */
-#undef XtPointer
-#define XtAppContext void*
-#define XtIntervalId void*
-#define XtPointer void*
-#define Widget void*
-
-
-/* Just in case there's something pathological about stat.h... */
-#ifndef S_IRUSR
-# define S_IRUSR 00400
-#endif
-#ifndef S_IWUSR
-# define S_IWUSR 00200
-#endif
-#ifndef S_IXUSR
-# define S_IXUSR 00100
-#endif
-#ifndef S_IXGRP
-# define S_IXGRP 00010
-#endif
-#ifndef S_IXOTH
-# define S_IXOTH 00001
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
#endif
-
-#include "version.h"
+#include "blurb.h"
#include "prefs.h"
-#include "resources.h"
-
-/* don't use realpath() on fedora system */
-#ifdef _FORTIFY_SOURCE
-#undef HAVE_REALPATH
-#endif
-
-
-extern char *progname;
-extern char *progclass;
-extern const char *blurb (void);
-
-
-
-static void get_screenhacks (Display *, saver_preferences *);
-static char *format_command (const char *cmd, Bool wrap_p);
-static void merge_system_screenhacks (Display *, saver_preferences *,
- screenhack **system_list, int count);
-static void stop_the_insanity (saver_preferences *p);
-
-
-static char *
-chase_symlinks (const char *file)
-{
-# ifdef HAVE_REALPATH
- if (file)
- {
-# ifndef PATH_MAX
-# ifdef MAXPATHLEN
-# define PATH_MAX MAXPATHLEN
-# else
-# define PATH_MAX 2048
-# endif
-# endif
- char buf[PATH_MAX];
- if (realpath (file, buf))
- return strdup (buf);
-
-/* sprintf (buf, "%.100s: realpath %.200s", blurb(), file);
- perror(buf);*/
- }
-# endif /* HAVE_REALPATH */
- return 0;
-}
-
-
-static Bool
-i_am_a_nobody (uid_t uid)
-{
- struct passwd *p;
-
- p = getpwnam ("nobody");
- if (! p) p = getpwnam ("noaccess");
- if (! p) p = getpwnam ("daemon");
-
- if (! p) /* There is no nobody? */
- return False;
-
- return (uid == p->pw_uid);
-}
-
-
-const char *
-init_file_name (void)
-{
- static char *file = 0;
-
- if (!file)
- {
- uid_t uid = getuid ();
- const char *home = getenv("HOME");
-
- if (i_am_a_nobody (uid) || !home || !*home)
- {
- /* If we're running as nobody, then use root's .xscreensaver file
- (since ~root/.xscreensaver and ~nobody/.xscreensaver are likely
- to be different -- if we didn't do this, then xscreensaver-demo
- would appear to have no effect when the luser is running as root.)
- */
- struct passwd *p = getpwuid (uid);
- uid = 0;
- if (!p || !p->pw_name || !*p->pw_name)
- {
- fprintf (stderr, "%s: couldn't get user info of uid %d\n",
- blurb(), getuid ());
- }
- else if (!p->pw_dir || !*p->pw_dir)
- {
- fprintf (stderr, "%s: couldn't get home directory of \"%s\"\n",
- blurb(), (p->pw_name ? p->pw_name : "???"));
- }
- else
- {
- home = p->pw_dir;
- }
- }
- if (home && *home)
- {
- const char *name = ".xscreensaver";
- file = (char *) malloc(strlen(home) + strlen(name) + 2);
- strcpy(file, home);
- if (!*home || home[strlen(home)-1] != '/')
- strcat(file, "/");
- strcat(file, name);
- }
- else
- {
- file = "";
- }
- }
-
- if (file && *file)
- return file;
- else
- return 0;
-}
-
-
-static const char *
-init_file_tmp_name (void)
-{
- static char *file = 0;
- if (!file)
- {
- const char *name = init_file_name();
- const char *suffix = ".tmp";
-
- char *n2 = chase_symlinks (name);
- if (n2) name = n2;
- if (!name || !*name)
- file = "";
- else
- {
- file = (char *) malloc(strlen(name) + strlen(suffix) + 2);
- strcpy(file, name);
- strcat(file, suffix);
- }
-
- if (n2) free (n2);
- }
-
- if (file && *file)
- return file;
- else
- return 0;
-}
-
-static int
-get_byte_resource (Display *dpy, char *name, char *class)
-{
- char *s = get_string_resource (dpy, name, class);
- char *s2 = s;
- int n = 0;
- if (!s) return 0;
-
- while (isspace(*s2)) s2++;
- while (*s2 >= '0' && *s2 <= '9')
- {
- n = (n * 10) + (*s2 - '0');
- s2++;
- }
- while (isspace(*s2)) s2++;
- if (*s2 == 'k' || *s2 == 'K') n <<= 10;
- else if (*s2 == 'm' || *s2 == 'M') n <<= 20;
- else if (*s2 == 'g' || *s2 == 'G') n <<= 30;
- else if (*s2)
- {
- LOSE:
- fprintf (stderr, "%s: %s must be a number of bytes, not \"%s\".\n",
- progname, name, s);
- free (s);
- return 0;
- }
- s2++;
- if (*s2 == 'b' || *s2 == 'B') s2++;
- while (isspace(*s2)) s2++;
- if (*s2) goto LOSE;
-
- free (s);
- return n;
-}
-
-
-static const char * const prefs[] = {
- "timeout",
- "cycle",
- "lock",
- "lockVTs", /* not saved */
- "lockTimeout",
- "passwdTimeout",
- "visualID",
- "installColormap",
- "verbose",
- "timestamp",
- "splash",
- "splashDuration",
- "quad",
- "demoCommand",
- "prefsCommand",
- "newLoginCommand",
- "helpURL", /* not saved */
- "loadURL", /* not saved */
- "newLoginCommand", /* not saved */
- "nice",
- "memoryLimit",
- "fade",
- "unfade",
- "fadeSeconds",
- "fadeTicks",
- "captureStderr",
- "captureStdout", /* not saved -- obsolete */
- "logFile", /* not saved */
- "ignoreUninstalledPrograms",
- "font",
- "dpmsEnabled",
- "dpmsQuickOff",
- "dpmsStandby",
- "dpmsSuspend",
- "dpmsOff",
- "grabDesktopImages",
- "grabVideoFrames",
- "chooseRandomImages",
- "imageDirectory",
- "mode",
- "selected",
- "textMode",
- "textLiteral",
- "textFile",
- "textProgram",
- "textURL",
- "",
- "programs",
- "",
- "pointerPollTime",
- "pointerHysteresis",
- "windowCreationTimeout",
- "initialDelay",
- "sgiSaverExtension", /* not saved -- obsolete */
- "mitSaverExtension", /* not saved -- obsolete */
- "xidleExtension", /* not saved -- obsolete */
- "GetViewPortIsFullOfLies",
- "procInterrupts",
- "xinputExtensionDev",
- "overlayStderr",
- "overlayTextBackground", /* not saved -- X resources only */
- "overlayTextForeground", /* not saved -- X resources only */
- "bourneShell", /* not saved -- X resources only */
- "authWarningSlack",
- 0
-};
static char *
strip (char *s)
@@ -334,85 +45,49 @@ strip (char *s)
return s;
}
-
-/* Reading
- */
-
-static int
-handle_entry (XrmDatabase *db, const char *key, const char *value,
- const char *filename, int line)
-{
- int i;
- for (i = 0; prefs[i]; i++)
- if (*prefs[i] && !strcasecmp(key, prefs[i]))
- {
- char *val = strdup(value);
- char *spec = (char *) malloc(strlen(progclass) + strlen(prefs[i]) +10);
- strcpy(spec, progclass);
- strcat(spec, ".");
- strcat(spec, prefs[i]);
-
- XrmPutStringResource (db, spec, val);
-
- free(spec);
- free(val);
- return 0;
- }
-
- fprintf(stderr, "%s: %s:%d: unknown option \"%s\"\n",
- blurb(), filename, line, key);
- return 1;
-}
-
-static int
-parse_init_file (saver_preferences *p)
+/* Parse the .xscreensaver or XScreenSaver.ad file and run the callback
+ for each key-value pair.
+*/
+int
+parse_init_file (const char *name,
+ void (*handler) (int lineno,
+ const char *key, const char *val,
+ void *closure),
+ void *closure)
{
- time_t write_date = 0;
- const char *name = init_file_name();
int line = 0;
struct stat st;
FILE *in;
int buf_size = 1024;
- char *buf;
+ char *buf = 0;
if (!name) return 0;
+ if (stat (name, &st) != 0) goto FAIL;
- if (stat(name, &st) != 0)
- {
- p->init_file_date = 0;
- return 0;
- }
+ buf = (char *) malloc (buf_size);
+ if (!buf) goto FAIL;
in = fopen(name, "r");
if (!in)
{
- char *buf = (char *) malloc(1024 + strlen(name));
- sprintf(buf, "%s: error reading \"%s\"", blurb(), name);
- perror(buf);
- free(buf);
- return -1;
+ sprintf (buf, "%s: error reading \"%.100s\"", blurb(), name);
+ perror (buf);
+ goto FAIL;
}
- if (fstat (fileno(in), &st) == 0)
+ if (fstat (fileno (in), &st) != 0)
{
- write_date = st.st_mtime;
- }
- else
- {
- char *buf = (char *) malloc(1024 + strlen(name));
- sprintf(buf, "%s: couldn't re-stat \"%s\"", blurb(), name);
- perror(buf);
- free(buf);
+ sprintf (buf, "%s: couldn't re-stat \"%.100s\"", blurb(), name);
+ perror (buf);
+ free (buf);
return -1;
}
- buf = (char *) malloc(buf_size);
-
while (fgets (buf, buf_size-1, in))
{
char *key, *value;
- int L = strlen(buf);
+ int L = strlen (buf);
line++;
while (L > 2 &&
@@ -425,13 +100,13 @@ parse_init_file (saver_preferences *p)
L -= 2;
}
buf_size += 1024;
- buf = (char *) realloc(buf, buf_size);
- if (!buf) exit(1);
+ buf = (char *) realloc (buf, buf_size);
+ if (!buf) goto FAIL;
line++;
if (!fgets (buf + L, buf_size-L-1, in))
break;
- L = strlen(buf);
+ L = strlen (buf);
}
/* Now handle other backslash escapes. */
@@ -453,7 +128,7 @@ parse_init_file (saver_preferences *p)
}
}
- key = strip(buf);
+ key = strip (buf);
if (*key == '#' || *key == '!' || *key == ';' ||
*key == '\n' || *key == 0)
@@ -462,1314 +137,23 @@ parse_init_file (saver_preferences *p)
value = strchr (key, ':');
if (!value)
{
- fprintf(stderr, "%s: %s:%d: unparsable line: %s\n", blurb(),
- name, line, key);
+ fprintf (stderr, "%s: %s:%d: unparsable line: %s\n", blurb(),
+ name, line, key);
continue;
}
else
{
*value++ = 0;
- value = strip(value);
+ value = strip (value);
}
- if (!p->db) abort();
- handle_entry (&p->db, key, value, name, line);
+ handler (line, key, value, closure);
}
fclose (in);
- free(buf);
-
- p->init_file_date = write_date;
+ free (buf);
return 0;
-}
-
-
-Bool
-init_file_changed_p (saver_preferences *p)
-{
- const char *name = init_file_name();
- struct stat st;
-
- if (!name) return False;
-
- if (stat(name, &st) != 0)
- return False;
-
- if (p->init_file_date == st.st_mtime)
- return False;
-
- return True;
-}
-
-
-/* Writing
- */
-
-static int
-tab_to (FILE *out, int from, int to)
-{
- int tab_width = 8;
- int to_mod = (to / tab_width) * tab_width;
- while (from < to_mod)
- {
- fprintf(out, "\t");
- from = (((from / tab_width) + 1) * tab_width);
- }
- while (from < to)
- {
- fprintf(out, " ");
- from++;
- }
- return from;
-}
-
-static char *
-stab_to (char *out, int from, int to)
-{
- int tab_width = 8;
- int to_mod = (to / tab_width) * tab_width;
- while (from < to_mod)
- {
- *out++ = '\t';
- from = (((from / tab_width) + 1) * tab_width);
- }
- while (from < to)
- {
- *out++ = ' ';
- from++;
- }
- return out;
-}
-
-static int
-string_columns (const char *string, int length, int start)
-{
- int tab_width = 8;
- int col = start;
- const char *end = string + length;
- while (string < end)
- {
- if (*string == '\n')
- col = 0;
- else if (*string == '\t')
- col = (((col / tab_width) + 1) * tab_width);
- else
- col++;
- string++;
- }
- return col;
-}
-
-
-static void
-write_entry (FILE *out, const char *key, const char *value)
-{
- char *v = strdup(value ? value : "");
- char *v2 = v;
- char *nl = 0;
- int col;
- Bool programs_p = (!strcmp(key, "programs"));
- int tab = (programs_p ? 32 : 16);
- Bool first = True;
-
- fprintf(out, "%s:", key);
- col = strlen(key) + 1;
-
- if (strlen(key) > 14)
- col = tab_to (out, col, 20);
-
- while (1)
- {
- if (!programs_p)
- v2 = strip(v2);
- nl = strchr(v2, '\n');
- if (nl)
- *nl = 0;
-
- if (first && programs_p)
- {
- col = tab_to (out, col, 77);
- fprintf (out, " \\\n");
- col = 0;
- }
-
- if (first)
- first = False;
- else
- {
- col = tab_to (out, col, 75);
- fprintf (out, " \\n\\\n");
- col = 0;
- }
-
- if (!programs_p)
- col = tab_to (out, col, tab);
-
- if (programs_p &&
- string_columns(v2, strlen (v2), col) + col > 75)
- {
- int L = strlen (v2);
- int start = 0;
- int end = start;
- while (start < L)
- {
- while (v2[end] == ' ' || v2[end] == '\t')
- end++;
- while (v2[end] != ' ' && v2[end] != '\t' &&
- v2[end] != '\n' && v2[end] != 0)
- end++;
- if (string_columns (v2 + start, (end - start), col) >= 74)
- {
- col = tab_to (out, col, 75);
- fprintf(out, " \\\n");
- col = tab_to (out, 0, tab + 2);
- while (v2[start] == ' ' || v2[start] == '\t')
- start++;
- }
-
- col = string_columns (v2 + start, (end - start), col);
- while (start < end)
- fputc(v2[start++], out);
- }
- }
- else
- {
- fprintf (out, "%s", v2);
- col += string_columns(v2, strlen (v2), col);
- }
-
- if (nl)
- v2 = nl + 1;
- else
- break;
- }
-
- fprintf(out, "\n");
- free(v);
-}
-
-int
-write_init_file (Display *dpy,
- saver_preferences *p, const char *version_string,
- Bool verbose_p)
-{
- int status = -1;
- const char *name = init_file_name();
- const char *tmp_name = init_file_tmp_name();
- char *n2 = chase_symlinks (name);
- struct stat st;
- int i, j;
-
- /* Kludge, since these aren't in the saver_preferences struct as strings...
- */
- char *visual_name;
- char *programs;
- Bool overlay_stderr_p;
- char *stderr_font;
- FILE *out;
-
- if (!name) goto END;
-
- if (n2) name = n2;
-
- /* Throttle the various timeouts to reasonable values before writing
- the file to disk. */
- stop_the_insanity (p);
-
-
- if (verbose_p)
- fprintf (stderr, "%s: writing \"%s\".\n", blurb(), name);
-
- unlink (tmp_name);
- out = fopen(tmp_name, "w");
- if (!out)
- {
- char *buf = (char *) malloc(1024 + strlen(name));
- sprintf(buf, "%s: error writing \"%s\"", blurb(), name);
- perror(buf);
- free(buf);
- goto END;
- }
-
- /* Give the new .xscreensaver file the same permissions as the old one;
- except ensure that it is readable and writable by owner, and not
- executable. Extra hack: if we're running as root, make the file
- be world-readable (so that the daemon, running as "nobody", will
- still be able to read it.)
- */
- if (stat(name, &st) == 0)
- {
- mode_t mode = st.st_mode;
- mode |= S_IRUSR | S_IWUSR; /* read/write by user */
- mode &= ~(S_IXUSR | S_IXGRP | S_IXOTH); /* executable by none */
-
- if (getuid() == (uid_t) 0) /* read by group/other */
- mode |= S_IRGRP | S_IROTH;
-
- if (fchmod (fileno(out), mode) != 0)
- {
- char *buf = (char *) malloc(1024 + strlen(name));
- sprintf (buf, "%s: error fchmodding \"%s\" to 0%o", blurb(),
- tmp_name, (unsigned int) mode);
- perror(buf);
- free(buf);
- goto END;
- }
- }
-
- /* Kludge, since these aren't in the saver_preferences struct... */
- visual_name = get_string_resource (dpy, "visualID", "VisualID");
- programs = 0;
- overlay_stderr_p = get_boolean_resource (dpy, "overlayStderr", "Boolean");
- stderr_font = get_string_resource (dpy, "font", "Font");
-
- i = 0;
- {
- char *ss;
- char **hack_strings = (char **)
- calloc (p->screenhacks_count, sizeof(char *));
-
- for (j = 0; j < p->screenhacks_count; j++)
- {
- hack_strings[j] = format_hack (dpy, p->screenhacks[j], True);
- i += strlen (hack_strings[j]);
- i += 2;
- }
-
- ss = programs = (char *) malloc(i + 10);
- *ss = 0;
- for (j = 0; j < p->screenhacks_count; j++)
- {
- strcat (ss, hack_strings[j]);
- free (hack_strings[j]);
- ss += strlen(ss);
- *ss++ = '\n';
- *ss = 0;
- }
- free (hack_strings);
- }
-
- {
- struct passwd *pw = getpwuid (getuid ());
- char *whoami = (pw && pw->pw_name && *pw->pw_name
- ? pw->pw_name
- : "<unknown>");
- time_t now = time ((time_t *) 0);
- char *timestr = (char *) ctime (&now);
- char *nl = (char *) strchr (timestr, '\n');
- if (nl) *nl = 0;
- fprintf (out,
- "# %s Preferences File\n"
- "# Written by %s %s for %s on %s.\n"
- "# https://www.jwz.org/xscreensaver/\n"
- "\n",
- progclass, progname, version_string, whoami, timestr);
- }
-
- for (j = 0; prefs[j]; j++)
- {
- char buf[255];
- const char *pr = prefs[j];
- enum pref_type { pref_str, pref_int, pref_bool, pref_byte, pref_time
- } type = pref_str;
- const char *s = 0;
- int i = 0;
- Bool b = False;
- Time t = 0;
-
- if (pr && !*pr)
- {
- fprintf(out, "\n");
- continue;
- }
-
-# undef CHECK
-# define CHECK(X) else if (!strcmp(pr, X))
- if (!pr || !*pr) ;
- CHECK("timeout") type = pref_time, t = p->timeout;
- CHECK("cycle") type = pref_time, t = p->cycle;
- CHECK("lock") type = pref_bool, b = p->lock_p;
- CHECK("lockVTs") continue; /* don't save, unused */
- CHECK("lockTimeout") type = pref_time, t = p->lock_timeout;
- CHECK("passwdTimeout") type = pref_time, t = p->passwd_timeout;
- CHECK("visualID") type = pref_str, s = visual_name;
- CHECK("installColormap") type = pref_bool, b = p->install_cmap_p;
- CHECK("verbose") type = pref_bool, b = p->verbose_p;
- CHECK("timestamp") type = pref_bool, b = p->timestamp_p;
- CHECK("splash") type = pref_bool, b = p->splash_p;
- CHECK("splashDuration") type = pref_time, t = p->splash_duration;
-# ifdef QUAD_MODE
- CHECK("quad") type = pref_bool, b = p->quad_p;
-# else /* !QUAD_MODE */
- CHECK("quad") continue; /* don't save */
-# endif /* !QUAD_MODE */
- CHECK("demoCommand") type = pref_str, s = p->demo_command;
- CHECK("prefsCommand") type = pref_str, s = p->prefs_command;
-/* CHECK("helpURL") type = pref_str, s = p->help_url; */
- CHECK("helpURL") continue; /* don't save */
-/* CHECK("loadURL") type = pref_str, s = p->load_url_command; */
- CHECK("loadURL") continue; /* don't save */
-/* CHECK("newLoginCommand") type = pref_str, s = p->new_login_command; */
- CHECK("newLoginCommand") continue; /* don't save */
- CHECK("nice") type = pref_int, i = p->nice_inferior;
- CHECK("memoryLimit") type = pref_byte, i = p->inferior_memory_limit;
- CHECK("fade") type = pref_bool, b = p->fade_p;
- CHECK("unfade") type = pref_bool, b = p->unfade_p;
- CHECK("fadeSeconds") type = pref_time, t = p->fade_seconds;
- CHECK("fadeTicks") type = pref_int, i = p->fade_ticks;
- CHECK("captureStderr") type = pref_bool, b = p->capture_stderr_p;
- CHECK("captureStdout") continue; /* don't save */
- CHECK("logFile") continue; /* don't save */
- CHECK("ignoreUninstalledPrograms")
- type = pref_bool, b = p->ignore_uninstalled_p;
-
- CHECK("font") type = pref_str, s = stderr_font;
-
- CHECK("dpmsEnabled") type = pref_bool, b = p->dpms_enabled_p;
- CHECK("dpmsQuickOff") type = pref_bool, b = p->dpms_quickoff_p;
- CHECK("dpmsStandby") type = pref_time, t = p->dpms_standby;
- CHECK("dpmsSuspend") type = pref_time, t = p->dpms_suspend;
- CHECK("dpmsOff") type = pref_time, t = p->dpms_off;
-
- CHECK("grabDesktopImages") type =pref_bool, b = p->grab_desktop_p;
- CHECK("grabVideoFrames") type =pref_bool, b = p->grab_video_p;
- CHECK("chooseRandomImages")type =pref_bool, b = p->random_image_p;
- CHECK("imageDirectory") type =pref_str, s = p->image_directory;
-
- CHECK("mode") type = pref_str,
- s = (p->mode == ONE_HACK ? "one" :
- p->mode == BLANK_ONLY ? "blank" :
- p->mode == DONT_BLANK ? "off" :
- p->mode == RANDOM_HACKS_SAME
- ? "random-same"
- : "random");
- CHECK("selected") type = pref_int, i = p->selected_hack;
-
- CHECK("textMode") type = pref_str,
- s = (p->tmode == TEXT_URL ? "url" :
- p->tmode == TEXT_LITERAL ? "literal" :
- p->tmode == TEXT_FILE ? "file" :
- p->tmode == TEXT_PROGRAM ? "program" :
- "date");
- CHECK("textLiteral") type = pref_str, s = p->text_literal;
- CHECK("textFile") type = pref_str, s = p->text_file;
- CHECK("textProgram") type = pref_str, s = p->text_program;
- CHECK("textURL") type = pref_str, s = p->text_url;
-
- CHECK("programs") type = pref_str, s = programs;
- CHECK("pointerPollTime") type = pref_time, t = p->pointer_timeout;
- CHECK("pointerHysteresis")type = pref_int, i = p->pointer_hysteresis;
- CHECK("windowCreationTimeout")type=pref_time,t= p->notice_events_timeout;
- CHECK("initialDelay") type = pref_time, t = p->initial_delay;
- CHECK("sgiSaverExtension") continue; /* don't save */
- CHECK("mitSaverExtension") continue; /* don't save */
- CHECK("xidleExtension") continue; /* don't save */
- CHECK("procInterrupts") type = pref_bool, b = p->use_proc_interrupts;
- CHECK("xinputExtensionDev") type = pref_bool, b = p->use_xinput_extension;
- CHECK("GetViewPortIsFullOfLies") type = pref_bool,
- b = p->getviewport_full_of_lies_p;
- CHECK("overlayStderr") type = pref_bool, b = overlay_stderr_p;
- CHECK("overlayTextBackground") continue; /* don't save */
- CHECK("overlayTextForeground") continue; /* don't save */
- CHECK("bourneShell") continue; /* don't save */
- CHECK("authWarningSlack") type = pref_int, i = p->auth_warning_slack;
- else abort();
-# undef CHECK
-
- switch (type)
- {
- case pref_str:
- break;
- case pref_int:
- sprintf(buf, "%d", i);
- s = buf;
- break;
- case pref_bool:
- s = b ? "True" : "False";
- break;
- case pref_time:
- {
- unsigned int hour = 0, min = 0, sec = (unsigned int) (t/1000);
- if (sec >= 60)
- {
- min += (sec / 60);
- sec %= 60;
- }
- if (min >= 60)
- {
- hour += (min / 60);
- min %= 60;
- }
- sprintf (buf, "%u:%02u:%02u", hour, min, sec);
- s = buf;
- }
- break;
- case pref_byte:
- {
- if (i >= (1<<30) && i == ((i >> 30) << 30))
- sprintf(buf, "%dG", i >> 30);
- else if (i >= (1<<20) && i == ((i >> 20) << 20))
- sprintf(buf, "%dM", i >> 20);
- else if (i >= (1<<10) && i == ((i >> 10) << 10))
- sprintf(buf, "%dK", i >> 10);
- else
- sprintf(buf, "%d", i);
- s = buf;
- }
- break;
- default:
- abort();
- break;
- }
-
- if (pr && (!strcmp(pr, "mode") || !strcmp(pr, "textMode")))
- fprintf(out, "\n");
-
- write_entry (out, pr, s);
- }
-
- fprintf(out, "\n");
-
- if (visual_name) free(visual_name);
- if (stderr_font) free(stderr_font);
- if (programs) free(programs);
-
- if (fclose(out) == 0)
- {
- time_t write_date = 0;
-
- if (stat(tmp_name, &st) == 0)
- {
- write_date = st.st_mtime;
- }
- else
- {
- char *buf = (char *) malloc(1024 + strlen(tmp_name) + strlen(name));
- sprintf(buf, "%s: couldn't stat \"%s\"", blurb(), tmp_name);
- perror(buf);
- unlink (tmp_name);
- free(buf);
- goto END;
- }
-
- if (rename (tmp_name, name) != 0)
- {
- char *buf = (char *) malloc(1024 + strlen(tmp_name) + strlen(name));
- sprintf(buf, "%s: error renaming \"%s\" to \"%s\"",
- blurb(), tmp_name, name);
- perror(buf);
- unlink (tmp_name);
- free(buf);
- goto END;
- }
- else
- {
- p->init_file_date = write_date;
-
- /* Since the .xscreensaver file is used for IPC, let's try and make
- sure that the bits actually land on the disk right away. */
- sync ();
-
- status = 0; /* wrote and renamed successfully! */
- }
- }
- else
- {
- char *buf = (char *) malloc(1024 + strlen(name));
- sprintf(buf, "%s: error closing \"%s\"", blurb(), name);
- perror(buf);
- free(buf);
- unlink (tmp_name);
- goto END;
- }
-
- END:
- if (n2) free (n2);
- return status;
-}
-
-
-/* Parsing the resource database
- */
-
-void
-free_screenhack (screenhack *hack)
-{
- if (hack->visual) free (hack->visual);
- if (hack->name) free (hack->name);
- free (hack->command);
- memset (hack, 0, sizeof(*hack));
- free (hack);
-}
-
-static void
-free_screenhack_list (screenhack **list, int count)
-{
- int i;
- if (!list) return;
- for (i = 0; i < count; i++)
- if (list[i])
- free_screenhack (list[i]);
- free (list);
-}
-
-
-
-/* Populate `saver_preferences' with the contents of the resource database.
- Note that this may be called multiple times -- it is re-run each time
- the ~/.xscreensaver file is reloaded.
-
- This function can be very noisy, since it issues resource syntax errors
- and so on.
- */
-void
-load_init_file (Display *dpy, saver_preferences *p)
-{
- static Bool first_time = True;
-
- screenhack **system_default_screenhacks = 0;
- int system_default_screenhack_count = 0;
-
- if (first_time)
- {
- /* Get the programs resource before the .xscreensaver file has been
- parsed and merged into the resource database for the first time:
- this is the value of *programs from the app-defaults file.
- Then clear it out so that it will be parsed again later, after
- the init file has been read.
- */
- get_screenhacks (dpy, p);
- system_default_screenhacks = p->screenhacks;
- system_default_screenhack_count = p->screenhacks_count;
- p->screenhacks = 0;
- p->screenhacks_count = 0;
- }
-
- if (parse_init_file (p) != 0) /* file might have gone away */
- if (!first_time) return;
-
- first_time = False;
-
- p->xsync_p = get_boolean_resource (dpy, "synchronous", "Synchronous");
- p->verbose_p = get_boolean_resource (dpy, "verbose", "Boolean");
- p->timestamp_p = get_boolean_resource (dpy, "timestamp", "Boolean");
- p->lock_p = get_boolean_resource (dpy, "lock", "Boolean");
- p->fade_p = get_boolean_resource (dpy, "fade", "Boolean");
- p->unfade_p = get_boolean_resource (dpy, "unfade", "Boolean");
- p->fade_seconds = 1000 * get_seconds_resource (dpy, "fadeSeconds", "Time");
- p->fade_ticks = get_integer_resource (dpy, "fadeTicks", "Integer");
- p->install_cmap_p = get_boolean_resource (dpy, "installColormap", "Boolean");
- p->nice_inferior = get_integer_resource (dpy, "nice", "Nice");
- p->inferior_memory_limit = get_byte_resource (dpy, "memoryLimit",
- "MemoryLimit");
- p->splash_p = get_boolean_resource (dpy, "splash", "Boolean");
-# ifdef QUAD_MODE
- p->quad_p = get_boolean_resource (dpy, "quad", "Boolean");
-# endif
- p->capture_stderr_p = get_boolean_resource (dpy, "captureStderr", "Boolean");
- p->ignore_uninstalled_p = get_boolean_resource (dpy,
- "ignoreUninstalledPrograms",
- "Boolean");
-
- p->initial_delay = 1000 * get_seconds_resource (dpy, "initialDelay", "Time");
- p->splash_duration = 1000 * get_seconds_resource (dpy, "splashDuration", "Time");
- p->timeout = 1000 * get_minutes_resource (dpy, "timeout", "Time");
- p->lock_timeout = 1000 * get_minutes_resource (dpy, "lockTimeout", "Time");
- p->cycle = 1000 * get_minutes_resource (dpy, "cycle", "Time");
- p->passwd_timeout = 1000 * get_seconds_resource (dpy, "passwdTimeout", "Time");
- p->pointer_timeout = 1000 * get_seconds_resource (dpy, "pointerPollTime", "Time");
- p->pointer_hysteresis = get_integer_resource (dpy, "pointerHysteresis","Integer");
- p->notice_events_timeout = 1000*get_seconds_resource(dpy,
- "windowCreationTimeout",
- "Time");
-
- p->dpms_enabled_p = get_boolean_resource (dpy, "dpmsEnabled", "Boolean");
- p->dpms_quickoff_p = get_boolean_resource (dpy, "dpmsQuickOff", "Boolean");
- p->dpms_standby = 1000 * get_minutes_resource (dpy, "dpmsStandby", "Time");
- p->dpms_suspend = 1000 * get_minutes_resource (dpy, "dpmsSuspend", "Time");
- p->dpms_off = 1000 * get_minutes_resource (dpy, "dpmsOff", "Time");
-
- p->grab_desktop_p = get_boolean_resource (dpy, "grabDesktopImages", "Boolean");
- p->grab_video_p = get_boolean_resource (dpy, "grabVideoFrames", "Boolean");
- p->random_image_p = get_boolean_resource (dpy, "chooseRandomImages", "Boolean");
- p->image_directory = get_string_resource (dpy,
- "imageDirectory",
- "ImageDirectory");
-
- p->text_literal = get_string_resource (dpy, "textLiteral", "TextLiteral");
- p->text_file = get_string_resource (dpy, "textFile", "TextFile");
- p->text_program = get_string_resource (dpy, "textProgram", "TextProgram");
- p->text_url = get_string_resource (dpy, "textURL", "TextURL");
-
- p->shell = get_string_resource (dpy, "bourneShell", "BourneShell");
-
- p->demo_command = get_string_resource(dpy, "demoCommand", "URL");
- p->prefs_command = get_string_resource(dpy, "prefsCommand", "URL");
- p->help_url = get_string_resource(dpy, "helpURL", "URL");
- p->load_url_command = get_string_resource(dpy, "loadURL", "LoadURL");
- p->new_login_command = get_string_resource(dpy,
- "newLoginCommand",
- "NewLoginCommand");
- p->auth_warning_slack = get_integer_resource(dpy, "authWarningSlack",
- "Integer");
-
- /* If "*splash" is unset, default to true. */
- {
- char *s = get_string_resource (dpy, "splash", "Boolean");
- if (s)
- free (s);
- else
- p->splash_p = True;
- }
-
- /* If "*grabDesktopImages" is unset, default to true. */
- {
- char *s = get_string_resource (dpy, "grabDesktopImages", "Boolean");
- if (s)
- free (s);
- else
- p->grab_desktop_p = True;
- }
-
- p->use_xidle_extension = get_boolean_resource (dpy, "xidleExtension","Boolean");
-#if 0 /* obsolete. */
- p->use_sgi_saver_extension = get_boolean_resource (dpy,
- "sgiSaverExtension",
- "Boolean");
-#endif
-#ifdef HAVE_XINPUT
- p->use_xinput_extension = get_boolean_resource (dpy, "xinputExtensionDev",
- "Boolean");
-#endif
-#if 0 /* broken and evil. */
- p->use_mit_saver_extension = get_boolean_resource (dpy,
- "mitSaverExtension",
- "Boolean");
-#endif
-
- p->use_proc_interrupts = get_boolean_resource (dpy,
- "procInterrupts", "Boolean");
-
- p->getviewport_full_of_lies_p =
- get_boolean_resource (dpy, "GetViewPortIsFullOfLies", "Boolean");
-
- get_screenhacks (dpy, p); /* Parse the "programs" resource. */
-
- {
- char *s = get_string_resource (dpy, "selected", "Integer");
- if (!s || !*s)
- p->selected_hack = -1;
- else
- p->selected_hack = get_integer_resource (dpy, "selected", "Integer");
- if (s) free (s);
- if (p->selected_hack < 0 || p->selected_hack >= p->screenhacks_count)
- p->selected_hack = -1;
- }
-
- {
- char *s = get_string_resource (dpy, "mode", "Mode");
- if (s && !strcasecmp (s, "one")) p->mode = ONE_HACK;
- else if (s && !strcasecmp (s, "blank")) p->mode = BLANK_ONLY;
- else if (s && !strcasecmp (s, "off")) p->mode = DONT_BLANK;
- else if (s && !strcasecmp (s, "random-same")) p->mode = RANDOM_HACKS_SAME;
- else p->mode = RANDOM_HACKS;
- if (s) free (s);
- }
-
- {
- char *s = get_string_resource (dpy, "textMode", "TextMode");
- if (s && !strcasecmp (s, "url")) p->tmode = TEXT_URL;
- else if (s && !strcasecmp (s, "literal")) p->tmode = TEXT_LITERAL;
- else if (s && !strcasecmp (s, "file")) p->tmode = TEXT_FILE;
- else if (s && !strcasecmp (s, "program")) p->tmode = TEXT_PROGRAM;
- else p->tmode = TEXT_DATE;
- if (s) free (s);
- }
-
- if (system_default_screenhack_count) /* note: first_time is also true */
- {
- merge_system_screenhacks (dpy, p, system_default_screenhacks,
- system_default_screenhack_count);
- free_screenhack_list (system_default_screenhacks,
- system_default_screenhack_count);
- system_default_screenhacks = 0;
- system_default_screenhack_count = 0;
- }
-
- if (p->debug_p)
- {
- p->xsync_p = True;
- p->verbose_p = True;
- p->timestamp_p = True;
- p->initial_delay = 0;
- }
-
- /* Throttle the various timeouts to reasonable values after reading the
- disk file. */
- stop_the_insanity (p);
-}
-
-
-/* If there are any hacks in the system-wide defaults that are not in
- the ~/.xscreensaver file, add the new ones to the end of the list.
- This does *not* actually save the file.
- */
-static void
-merge_system_screenhacks (Display *dpy, saver_preferences *p,
- screenhack **system_list, int system_count)
-{
- /* Yeah yeah, this is an N^2 operation, but I don't have hashtables handy,
- so fuck it. */
-
- int made_space = 0;
- int i;
- for (i = 0; i < system_count; i++)
- {
- int j;
- Bool matched_p = False;
-
- for (j = 0; j < p->screenhacks_count; j++)
- {
- char *name;
- if (!system_list[i]->name)
- system_list[i]->name = make_hack_name (dpy,
- system_list[i]->command);
-
- name = p->screenhacks[j]->name;
- if (!name)
- name = make_hack_name (dpy, p->screenhacks[j]->command);
-
- matched_p = !strcasecmp (name, system_list[i]->name);
-
- if (name != p->screenhacks[j]->name)
- free (name);
-
- if (matched_p)
- break;
- }
-
- if (!matched_p)
- {
- /* We have an entry in the system-wide list that is not in the
- user's .xscreensaver file. Add it to the end.
- Note that p->screenhacks is a single malloc block, not a
- linked list, so we have to realloc it.
- */
- screenhack *oh = system_list[i];
- screenhack *nh = (screenhack *) malloc (sizeof(screenhack));
-
- if (made_space == 0)
- {
- made_space = 10;
- p->screenhacks = (screenhack **)
- realloc (p->screenhacks,
- (p->screenhacks_count + made_space + 1)
- * sizeof(screenhack));
- if (!p->screenhacks) abort();
- }
-
- nh->enabled_p = oh->enabled_p;
- nh->visual = oh->visual ? strdup(oh->visual) : 0;
- nh->name = oh->name ? strdup(oh->name) : 0;
- nh->command = oh->command ? strdup(oh->command) : 0;
-
- p->screenhacks[p->screenhacks_count++] = nh;
- p->screenhacks[p->screenhacks_count] = 0;
- made_space--;
-
-#if 0
- fprintf (stderr, "%s: noticed new hack: %s\n", blurb(),
- (nh->name ? nh->name : make_hack_name (dpy, nh->command)));
-#endif
- }
- }
-}
-
-
-
-/* Parsing the programs resource.
- */
-
-screenhack *
-parse_screenhack (const char *line)
-{
- screenhack *h = (screenhack *) calloc (1, sizeof(*h));
- const char *s;
-
- h->enabled_p = True;
-
- while (isspace(*line)) line++; /* skip whitespace */
- if (*line == '-') /* handle "-" */
- {
- h->enabled_p = False;
- line++;
- while (isspace(*line)) line++; /* skip whitespace */
- }
-
- s = line; /* handle "visual:" */
- while (*line && *line != ':' && *line != '"' && !isspace(*line))
- line++;
- if (*line != ':')
- line = s;
- else
- {
- h->visual = (char *) malloc (line-s+1);
- strncpy (h->visual, s, line-s);
- h->visual[line-s] = 0;
- if (*line == ':') line++; /* skip ":" */
- while (isspace(*line)) line++; /* skip whitespace */
- }
-
- if (*line == '"') /* handle "name" */
- {
- line++;
- s = line;
- while (*line && *line != '"')
- line++;
- h->name = (char *) malloc (line-s+1);
- strncpy (h->name, s, line-s);
- h->name[line-s] = 0;
- if (*line == '"') line++; /* skip "\"" */
- while (isspace(*line)) line++; /* skip whitespace */
- }
-
- h->command = format_command (line, False); /* handle command */
- return h;
-}
-
-
-static char *
-format_command (const char *cmd, Bool wrap_p)
-{
- int tab = 30;
- int col = tab;
- char *cmd2 = (char *) calloc (1, 2 * (strlen (cmd) + 1));
- const char *in = cmd;
- char *out = cmd2;
- while (*in)
- {
- /* shrink all whitespace to one space, for the benefit of the "demo"
- mode display. We only do this when we can easily tell that the
- whitespace is not significant (no shell metachars).
- */
- switch (*in)
- {
- case '\'': case '"': case '`': case '\\':
- /* Metachars are scary. Copy the rest of the line unchanged. */
- while (*in)
- *out++ = *in++, col++;
- break;
-
- case ' ': case '\t':
- /* Squeeze all other whitespace down to one space. */
- while (*in == ' ' || *in == '\t')
- in++;
- *out++ = ' ', col++;
- break;
-
- default:
- /* Copy other chars unchanged. */
- *out++ = *in++, col++;
- break;
- }
- }
-
- *out = 0;
-
- /* Strip trailing whitespace */
- while (out > cmd2 && isspace (out[-1]))
- *(--out) = 0;
-
- return cmd2;
-}
-
-
-/* Returns a new string describing the shell command.
- This may be just the name of the program, capitalized.
- It also may be something from the resource database (gotten
- by looking for "hacks.XYZ.name", where XYZ is the program.)
- */
-char *
-make_hack_name (Display *dpy, const char *shell_command)
-{
- char *s = strdup (shell_command);
- char *s2;
- char res_name[255];
-
- for (s2 = s; *s2; s2++) /* truncate at first whitespace */
- if (isspace (*s2))
- {
- *s2 = 0;
- break;
- }
-
- s2 = strrchr (s, '/'); /* if pathname, take last component */
- if (s2)
- {
- s2 = strdup (s2+1);
- free (s);
- s = s2;
- }
-
- if (strlen (s) > 50) /* 51 is hereby defined as "unreasonable" */
- s[50] = 0;
-
- sprintf (res_name, "hacks.%s.name", s); /* resource? */
- s2 = get_string_resource (dpy, res_name, res_name);
- if (s2)
- {
- free (s);
- return s2;
- }
-
- for (s2 = s; *s2; s2++) /* if it has any capitals, return it */
- if (*s2 >= 'A' && *s2 <= 'Z')
- return s;
-
- if (s[0] >= 'a' && s[0] <= 'z') /* else cap it */
- s[0] -= 'a'-'A';
- if (s[0] == 'X' && s[1] >= 'a' && s[1] <= 'z') /* (magic leading X) */
- s[1] -= 'a'-'A';
- if (s[0] == 'G' && s[1] == 'l' &&
- s[2] >= 'a' && s[2] <= 'z') /* (magic leading GL) */
- s[1] -= 'a'-'A',
- s[2] -= 'a'-'A';
- return s;
-}
-
-
-char *
-format_hack (Display *dpy, screenhack *hack, Bool wrap_p)
-{
- int tab = 32;
- int size;
- char *h2, *out, *s;
- int col = 0;
-
- char *def_name = make_hack_name (dpy, hack->command);
-
- /* Don't ever write out a name for a hack if it's the same as the default.
- */
- if (hack->name && !strcmp (hack->name, def_name))
- {
- free (hack->name);
- hack->name = 0;
- }
- free (def_name);
-
- size = (2 * (strlen(hack->command) +
- (hack->visual ? strlen(hack->visual) : 0) +
- (hack->name ? strlen(hack->name) : 0) +
- tab));
- h2 = (char *) malloc (size);
- out = h2;
-
- if (!hack->enabled_p) *out++ = '-'; /* write disabled flag */
-
- if (hack->visual && *hack->visual) /* write visual name */
- {
- if (hack->enabled_p) *out++ = ' ';
- *out++ = ' ';
- strcpy (out, hack->visual);
- out += strlen (hack->visual);
- *out++ = ':';
- *out++ = ' ';
- }
-
- *out = 0;
- col = string_columns (h2, strlen (h2), 0);
-
- if (hack->name && *hack->name) /* write pretty name */
- {
- int L = (strlen (hack->name) + 2);
- if (L + col < tab)
- out = stab_to (out, col, tab - L - 2);
- else
- *out++ = ' ';
- *out++ = '"';
- strcpy (out, hack->name);
- out += strlen (hack->name);
- *out++ = '"';
- *out = 0;
-
- col = string_columns (h2, strlen (h2), 0);
- if (wrap_p && col >= tab)
- out = stab_to (out, col, 77);
- else
- *out++ = ' ';
-
- if (out >= h2+size) abort();
- }
-
- *out = 0;
- col = string_columns (h2, strlen (h2), 0);
- out = stab_to (out, col, tab); /* indent */
-
- if (out >= h2+size) abort();
- s = format_command (hack->command, wrap_p);
- strcpy (out, s);
- out += strlen (s);
- free (s);
- *out = 0;
-
- return h2;
-}
-
-
-static void
-get_screenhacks (Display *dpy, saver_preferences *p)
-{
- int i, j;
- int start = 0;
- int end = 0;
- int size;
- char *d;
-
- d = get_string_resource (dpy, "monoPrograms", "MonoPrograms");
- if (d && !*d) { free(d); d = 0; }
- if (!d)
- d = get_string_resource (dpy, "colorPrograms", "ColorPrograms");
- if (d && !*d) { free(d); d = 0; }
-
- if (d)
- {
- fprintf (stderr,
- "%s: the `monoPrograms' and `colorPrograms' resources are obsolete;\n\
- see the manual for details.\n", blurb());
- free(d);
- }
-
- d = get_string_resource (dpy, "programs", "Programs");
-
- free_screenhack_list (p->screenhacks, p->screenhacks_count);
- p->screenhacks = 0;
- p->screenhacks_count = 0;
-
- if (!d || !*d)
- return;
-
- size = strlen (d);
-
-
- /* Count up the number of newlines (which will be equal to or larger than
- one less than the number of hacks.)
- */
- for (i = j = 0; d[i]; i++)
- if (d[i] == '\n')
- j++;
- j++;
-
- p->screenhacks = (screenhack **) calloc (j + 1, sizeof (screenhack *));
-
- /* Iterate over the lines in `d' (the string with newlines)
- and make new strings to stuff into the `screenhacks' array.
- */
- p->screenhacks_count = 0;
- while (start < size)
- {
- /* skip forward over whitespace. */
- while (d[start] == ' ' || d[start] == '\t' || d[start] == '\n')
- start++;
-
- /* skip forward to newline or end of string. */
- end = start;
- while (d[end] != 0 && d[end] != '\n')
- end++;
-
- /* null terminate. */
- d[end] = 0;
-
- p->screenhacks[p->screenhacks_count++] = parse_screenhack (d + start);
- if (p->screenhacks_count >= i)
- abort();
-
- start = end+1;
- }
-
- free (d);
-
- if (p->screenhacks_count == 0)
- {
- free (p->screenhacks);
- p->screenhacks = 0;
- }
-}
-
-
-/* Make sure all the values in the preferences struct are sane.
- */
-static void
-stop_the_insanity (saver_preferences *p)
-{
- if (p->passwd_timeout <= 0) p->passwd_timeout = 30000; /* 30 secs */
- if (p->timeout < 15000) p->timeout = 15000; /* 15 secs */
- if (p->cycle != 0 && p->cycle < 2000) p->cycle = 2000; /* 2 secs */
- if (p->pointer_timeout <= 0) p->pointer_timeout = 5000; /* 5 secs */
- if (p->notice_events_timeout <= 0)
- p->notice_events_timeout = 10000; /* 10 secs */
- if (p->fade_seconds <= 0 || p->fade_ticks <= 0)
- p->fade_p = False;
- if (! p->fade_p) p->unfade_p = False;
-
- /* The DPMS settings may have the value 0.
- But if they are negative, or are a range less than 10 seconds,
- reset them to sensible defaults. (Since that must be a mistake.)
- */
- if (p->dpms_standby != 0 &&
- p->dpms_standby < 10 * 1000)
- p->dpms_standby = 2 * 60 * 60 * 1000; /* 2 hours */
- if (p->dpms_suspend != 0 &&
- p->dpms_suspend < 10 * 1000)
- p->dpms_suspend = 2 * 60 * 60 * 1000; /* 2 hours */
- if (p->dpms_off != 0 &&
- p->dpms_off < 10 * 1000)
- p->dpms_off = 4 * 60 * 60 * 1000; /* 4 hours */
-
- /* suspend may not be greater than off, unless off is 0.
- standby may not be greater than suspend, unless suspend is 0.
- */
- if (p->dpms_off != 0 &&
- p->dpms_suspend > p->dpms_off)
- p->dpms_suspend = p->dpms_off;
- if (p->dpms_suspend != 0 &&
- p->dpms_standby > p->dpms_suspend)
- p->dpms_standby = p->dpms_suspend;
-
- /* These fixes above ignores the case
- suspend = 0 and standby > off ...
- */
- if (p->dpms_off != 0 &&
- p->dpms_standby > p->dpms_off)
- p->dpms_standby = p->dpms_off;
-
-
- if (p->dpms_standby == 0 && /* if *all* are 0, then DPMS is disabled */
- p->dpms_suspend == 0 &&
- p->dpms_off == 0 &&
- !(p->dpms_quickoff_p) /* ... but we want to do DPMS quick off */
- )
- p->dpms_enabled_p = False;
-
-
- /* Set watchdog timeout to about half of the cycle timeout, but
- don't let it be faster than 1/2 minute or slower than 1 minute.
- */
- p->watchdog_timeout = p->cycle * 0.6;
- if (p->watchdog_timeout < 27000) p->watchdog_timeout = 27000; /* 27 secs */
- if (p->watchdog_timeout > 57000) p->watchdog_timeout = 57000; /* 57 secs */
-
- if (p->pointer_hysteresis < 0) p->pointer_hysteresis = 0;
-/* if (p->pointer_hysteresis > 100) p->pointer_hysteresis = 100; */
-
- if (p->auth_warning_slack < 0) p->auth_warning_slack = 0;
- if (p->auth_warning_slack > 300) p->auth_warning_slack = 300;
-}
-
-
-Bool
-senesculent_p (void)
-{
- /* If you are in here because you're planning on disabling this warning
- before redistributing my software, please don't.
-
- I sincerely request that you do one of the following:
-
- 1: leave this code intact and this warning in place, -OR-
-
- 2: Remove xscreensaver from your distribution.
-
- I would seriously prefer that you not distribute my software at all
- than that you distribute one version and then never update it for
- years.
-
- I am *constantly* getting email from users reporting bugs that have
- been fixed for literally years who have no idea that the software
- they are running is years out of date. Yes, it would be great if we
- lived in the ideal world where people checked that they were running
- the latest release before they report a bug, but we don't. To most
- people, "running the latest release" is synonymous with "running the
- latest release that my distro packages for me."
-
- When they even bother to tell me what version they're running, I
- say, "That version is three years old!", and they say "But this is
- the latest version my distro ships". Then I say, "your distro
- sucks", and they say "but I don't know how to compile from source,
- herp derp I eat paste", and *everybody* goes away unhappy.
-
- It wastes an enormous amount of my time, but worse than that, it
- does a grave disservice to the users, who are stuck experiencing
- bugs that are already fixed! These users think they are running the
- latest release, and they are not. They would like to be running the
- actual latest release, but they don't know how, because their distro
- makes that very difficult for them. It's terrible for everyone, and
- kind of makes me regret ever having released this software in the
- first place.
-
- So seriously. I ask that if you're planning on disabling this
- obsolescence warning, that you instead just remove xscreensaver from
- your distro entirely. Everybody will be happier that way. Check
- out gnome-screensaver instead, I understand it's really nice.
-
- Of course, my license allows you to ignore me and do whatever the
- fuck you want, but as the author, I hope you will have the common
- courtesy of complying with my request.
-
- Thank you!
-
- jwz, 2014, 2016, 2018.
-
- PS: In particular, since Debian refuses to upgrade software on any
- kind of rational timeline, I have asked that they stop shipping
- xscreensaver at all. They have refused. Instead of upgrading the
- software, they simply patched out this warning.
-
- If you want to witness the sad state of the open source peanut
- gallery, look no farther than the comments on my blog:
- http://jwz.org/b/yiYo
-
- Many of these people fall back on their go-to argument of, "If it is
- legal, it must be right." If you believe in that rhetorical device
- then you are a terrible person, and possibly a sociopath.
-
- There are also the armchair lawyers who say "Well, instead of
- *asking* people to do the right thing out of common courtesy, you
- should just change your license to prohibit them from acting
- amorally." Again, this is the answer of a sociopath, but that aside,
- if you devote even a second's thought to this you will realize that
- the end result of this would be for distros like Debian to just keep
- shipping the last version with the old license and then never
- upgrading it again -- which would be the worst possible outcome for
- everyone involved, most especially the users.
- */
- time_t now = time ((time_t *) 0); /* d */
- struct tm *tm = localtime (&now); /* o */
- const char *s = screensaver_id; /* n */
- char mon[4], year[5]; /* ' */
- int m, y, mrnths; /* t */
- s = strchr (s, ' '); if (!s) abort(); s++; /* */
- s = strchr (s, '('); if (!s) abort(); s++; /* d */
- s = strchr (s, '-'); if (!s) abort(); s++; /* o */
- strncpy (mon, s, 3); /* o */
- mon[3] = 0; /* */
- s = strchr (s, '-'); if (!s) abort(); s++; /* e */
- strncpy (year, s, 4); /* e */
- year[4] = 0; /* t */
- y = atoi (year); /* , */
- if (!strcmp(mon, "Jan")) m = 0; /* */
- else if (!strcmp(mon, "Feb")) m = 1; /* s */
- else if (!strcmp(mon, "Mar")) m = 2; /* t */
- else if (!strcmp(mon, "Apr")) m = 3; /* o */
- else if (!strcmp(mon, "May")) m = 4; /* p */
- else if (!strcmp(mon, "Jun")) m = 5; /* , */
- else if (!strcmp(mon, "Jul")) m = 6; /* */
- else if (!strcmp(mon, "Aug")) m = 7; /* s */
- else if (!strcmp(mon, "Sep")) m = 8; /* t */
- else if (!strcmp(mon, "Oct")) m = 9; /* a */
- else if (!strcmp(mon, "Nov")) m = 10; /* a */
- else if (!strcmp(mon, "Dec")) m = 11; /* a */
- else abort(); /* h */
- mrnths = ((((tm->tm_year + 1900) * 12) + tm->tm_mon) - /* h */
- (y * 12 + m)); /* h */
- /* p */
- return (mrnths >= 17); /* . */
+ FAIL:
+ if (buf) free (buf);
+ return -1;
}
diff --git a/driver/prefs.h b/driver/prefs.h
index cd1016d..14d5542 100644
--- a/driver/prefs.h
+++ b/driver/prefs.h
@@ -1,4 +1,4 @@
-/* xscreensaver, Copyright (c) 1993-2018 Jamie Zawinski <jwz@jwz.org>
+/* 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
@@ -12,26 +12,10 @@
#ifndef __XSCREENSAVER_PREFS_H__
#define __XSCREENSAVER_PREFS_H__
-#include "types.h"
-
-extern void load_init_file (Display *, saver_preferences *);
-extern Bool init_file_changed_p (saver_preferences *);
-extern int write_init_file (Display *,
- saver_preferences *, const char *version_string,
- Bool verbose_p);
-const char *init_file_name (void);
-extern Bool senesculent_p (void);
-
-extern screenhack *parse_screenhack (const char *line);
-extern void free_screenhack (screenhack *);
-extern char *format_hack (Display *, screenhack *, Bool wrap_p);
-char *make_hack_name (Display *, const char *shell_command);
-
-/* From dpms.c */
-extern void sync_server_dpms_settings (Display *, Bool enabled_p,
- Bool dpms_quickoff_p,
- int standby_secs, int suspend_secs,
- int off_secs,
- Bool verbose_p);
+extern int parse_init_file (const char *name,
+ void (*handler) (int lineno,
+ const char *key, const char *val,
+ void *closure),
+ void *closure);
#endif /* __XSCREENSAVER_PREFS_H__ */
diff --git a/driver/prefsw.c b/driver/prefsw.c
new file mode 100644
index 0000000..2bbf94f
--- /dev/null
+++ b/driver/prefsw.c
@@ -0,0 +1,1555 @@
+/* prefs.c --- reading and writing the ~/.xscreensaver file.
+ * xscreensaver, Copyright © 1998-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.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdlib.h>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/param.h> /* for PATH_MAX */
+#include <pwd.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xresource.h>
+#include <X11/Intrinsic.h>
+
+#include "version.h"
+#include "types.h"
+#include "prefs.h"
+#include "resources.h"
+#include "blurb.h"
+
+/* don't use realpath() on fedora system */
+#ifdef _FORTIFY_SOURCE
+# undef HAVE_REALPATH
+#endif
+
+
+extern char *progclass;
+
+static void get_screenhacks (Display *, saver_preferences *);
+static char *format_command (const char *cmd, Bool wrap_p);
+static void merge_system_screenhacks (Display *, saver_preferences *,
+ screenhack **system_list, int count);
+static void stop_the_insanity (saver_preferences *p);
+
+static char *format_hack (Display *, screenhack *, Bool wrap_p);
+
+static char *
+chase_symlinks (const char *file)
+{
+# ifdef HAVE_REALPATH
+ if (file)
+ {
+# ifndef PATH_MAX
+# ifdef MAXPATHLEN
+# define PATH_MAX MAXPATHLEN
+# else
+# define PATH_MAX 2048
+# endif
+# endif
+ char buf[PATH_MAX];
+ if (realpath (file, buf))
+ return strdup (buf);
+
+/* sprintf (buf, "%.100s: realpath %.200s", blurb(), file);
+ perror(buf);*/
+ }
+# endif /* HAVE_REALPATH */
+ return 0;
+}
+
+
+static Bool
+i_am_a_nobody (uid_t uid)
+{
+ struct passwd *p;
+
+ p = getpwnam ("nobody");
+ if (! p) p = getpwnam ("noaccess");
+ if (! p) p = getpwnam ("daemon");
+
+ if (! p) /* There is no nobody? */
+ return False;
+
+ return (uid == p->pw_uid);
+}
+
+
+const char *
+init_file_name (void)
+{
+ static char *file = 0;
+
+ if (!file)
+ {
+ uid_t uid = getuid ();
+ const char *home = getenv("HOME");
+
+ if (i_am_a_nobody (uid) || !home || !*home)
+ {
+ /* If we're running as nobody, then use root's .xscreensaver file
+ (since ~root/.xscreensaver and ~nobody/.xscreensaver are likely
+ to be different -- without this, xscreensaver-settings would
+ appear to have no effect when the luser is running as root.)
+ */
+ struct passwd *p = getpwuid (uid);
+ if (!p || !p->pw_name || !*p->pw_name)
+ {
+ fprintf (stderr, "%s: couldn't get user info of uid %d\n",
+ blurb(), getuid ());
+ }
+ else if (!p->pw_dir || !*p->pw_dir)
+ {
+ fprintf (stderr, "%s: couldn't get home directory of \"%s\"\n",
+ blurb(), (p->pw_name ? p->pw_name : "???"));
+ }
+ else
+ {
+ home = p->pw_dir;
+ }
+ }
+ if (home && *home)
+ {
+ const char *name = ".xscreensaver";
+ file = (char *) malloc(strlen(home) + strlen(name) + 2);
+ strcpy(file, home);
+ if (!*home || home[strlen(home)-1] != '/')
+ strcat(file, "/");
+ strcat(file, name);
+ }
+ else
+ {
+ file = "";
+ }
+ }
+
+ if (file && *file)
+ return file;
+ else
+ return 0;
+}
+
+
+static const char *
+init_file_tmp_name (void)
+{
+ static char *file = 0;
+ if (!file)
+ {
+ const char *name = init_file_name();
+ const char *suffix = ".tmp";
+
+ char *n2 = chase_symlinks (name);
+ if (n2) name = n2;
+
+ if (!name || !*name)
+ file = "";
+ else
+ {
+ file = (char *) malloc(strlen(name) + strlen(suffix) + 2);
+ strcpy(file, name);
+ strcat(file, suffix);
+ }
+
+ if (n2) free (n2);
+ }
+
+ if (file && *file)
+ return file;
+ else
+ return 0;
+}
+
+static const char * const prefs[] = {
+ "timeout",
+ "cycle",
+ "lock",
+ "lockVTs", /* not saved */
+ "lockTimeout",
+ "passwdTimeout",
+ "visualID",
+ "installColormap",
+ "verbose",
+ "splash",
+ "splashDuration",
+ "quad",
+ "demoCommand",
+ "prefsCommand",
+ "newLoginCommand",
+ "helpURL", /* not saved */
+ "loadURL", /* not saved */
+ "newLoginCommand", /* not saved */
+ "nice",
+ "memoryLimit", /* not saved */
+ "fade",
+ "unfade",
+ "fadeSeconds",
+ "fadeTicks", /* not saved */
+ "logFile", /* not saved */
+ "ignoreUninstalledPrograms",
+ "font",
+ "dpmsEnabled",
+ "dpmsQuickOff",
+ "dpmsStandby",
+ "dpmsSuspend",
+ "dpmsOff",
+ "grabDesktopImages",
+ "grabVideoFrames",
+ "chooseRandomImages",
+ "imageDirectory",
+ "mode",
+ "selected",
+ "textMode",
+ "textLiteral",
+ "textFile",
+ "textProgram",
+ "textURL",
+ "dialogTheme",
+ "",
+ "programs",
+ "",
+ "pointerHysteresis",
+ "bourneShell", /* not saved -- X resources only */
+ "authWarningSlack",
+ 0
+};
+
+static char *
+strip (char *s)
+{
+ char *s2;
+ while (*s == '\t' || *s == ' ' || *s == '\r' || *s == '\n')
+ s++;
+ for (s2 = s; *s2; s2++)
+ ;
+ for (s2--; s2 >= s; s2--)
+ if (*s2 == '\t' || *s2 == ' ' || *s2 == '\r' || *s2 =='\n')
+ *s2 = 0;
+ else
+ break;
+ return s;
+}
+
+
+/* Reading
+ */
+
+static int
+handle_entry (XrmDatabase *db, const char *key, const char *value,
+ const char *filename, int line)
+{
+ int i;
+ for (i = 0; prefs[i]; i++)
+ if (*prefs[i] && !strcasecmp(key, prefs[i]))
+ {
+ char *val = strdup(value);
+ char *spec = (char *) malloc(strlen(progclass) + strlen(prefs[i]) +10);
+ strcpy(spec, progclass);
+ strcat(spec, ".");
+ strcat(spec, prefs[i]);
+
+ XrmPutStringResource (db, spec, val);
+
+ free(spec);
+ free(val);
+ return 0;
+ }
+
+ /* fprintf(stderr, "%s: %s:%d: unknown option \"%s\"\n",
+ blurb(), filename, line, key); */
+ return 1;
+}
+
+
+struct parser_closure {
+ const char *file;
+ saver_preferences *prefs;
+};
+
+static void line_handler (int lineno,
+ const char *key, const char *val,
+ void *closure)
+{
+ struct parser_closure *c = (struct parser_closure *) closure;
+ saver_preferences *p = c->prefs;
+ if (!p->db) abort();
+ handle_entry (&p->db, key, val, c->file, lineno);
+}
+
+static int
+parse_init_file_1 (saver_preferences *p)
+{
+ time_t write_date = 0;
+ const char *name = init_file_name();
+ struct parser_closure C;
+ struct stat st;
+ int status;
+
+ if (!name) return 0;
+
+ C.file = name;
+ C.prefs = p;
+
+ if (stat(name, &st) == 0)
+ write_date = st.st_mtime;
+ else
+ {
+ p->init_file_date = 0;
+ return 0;
+ }
+
+ status = parse_init_file (name, line_handler, &C);
+
+ p->init_file_date = write_date;
+ return status;
+}
+
+
+Bool
+init_file_changed_p (saver_preferences *p)
+{
+ const char *name = init_file_name();
+ struct stat st;
+
+ if (!name) return False;
+
+ if (stat(name, &st) != 0)
+ return False;
+
+ if (p->init_file_date == st.st_mtime)
+ return False;
+
+ return True;
+}
+
+
+/* Writing
+ */
+
+static int
+tab_to (FILE *out, int from, int to)
+{
+ int tab_width = 8;
+ int to_mod = (to / tab_width) * tab_width;
+ while (from < to_mod)
+ {
+ fprintf(out, "\t");
+ from = (((from / tab_width) + 1) * tab_width);
+ }
+ while (from < to)
+ {
+ fprintf(out, " ");
+ from++;
+ }
+ return from;
+}
+
+static char *
+stab_to (char *out, int from, int to)
+{
+ int tab_width = 8;
+ int to_mod = (to / tab_width) * tab_width;
+ while (from < to_mod)
+ {
+ *out++ = '\t';
+ from = (((from / tab_width) + 1) * tab_width);
+ }
+ while (from < to)
+ {
+ *out++ = ' ';
+ from++;
+ }
+ return out;
+}
+
+static int
+string_columns (const char *string, int length, int start)
+{
+ int tab_width = 8;
+ int col = start;
+ const char *end = string + length;
+ while (string < end)
+ {
+ if (*string == '\n')
+ col = 0;
+ else if (*string == '\t')
+ col = (((col / tab_width) + 1) * tab_width);
+ else
+ col++;
+ string++;
+ }
+ return col;
+}
+
+
+static void
+write_entry (FILE *out, const char *key, const char *value)
+{
+ char *v = strdup(value ? value : "");
+ char *v2 = v;
+ char *nl = 0;
+ int col;
+ Bool programs_p = (!strcmp(key, "programs"));
+ int tab = (programs_p ? 32 : 16);
+ Bool first = True;
+
+ fprintf(out, "%s:", key);
+ col = strlen(key) + 1;
+
+ if (strlen(key) > 14)
+ col = tab_to (out, col, 20);
+
+ while (1)
+ {
+ if (!programs_p)
+ v2 = strip(v2);
+ nl = strchr(v2, '\n');
+ if (nl)
+ *nl = 0;
+
+ if (first && programs_p)
+ {
+ col = tab_to (out, col, 77);
+ fprintf (out, " \\\n");
+ col = 0;
+ }
+
+ if (first)
+ first = False;
+ else
+ {
+ col = tab_to (out, col, 75);
+ fprintf (out, " \\n\\\n");
+ col = 0;
+ }
+
+ if (!programs_p)
+ col = tab_to (out, col, tab);
+
+ if (programs_p &&
+ string_columns(v2, strlen (v2), col) + col > 75)
+ {
+ int L = strlen (v2);
+ int start = 0;
+ int end = start;
+ while (start < L)
+ {
+ while (v2[end] == ' ' || v2[end] == '\t')
+ end++;
+ while (v2[end] != ' ' && v2[end] != '\t' &&
+ v2[end] != '\n' && v2[end] != 0)
+ end++;
+ if (string_columns (v2 + start, (end - start), col) >= 74)
+ {
+ col = tab_to (out, col, 75);
+ fprintf(out, " \\\n");
+ col = tab_to (out, 0, tab + 2);
+ while (v2[start] == ' ' || v2[start] == '\t')
+ start++;
+ }
+
+ col = string_columns (v2 + start, (end - start), col);
+ while (start < end)
+ fputc(v2[start++], out);
+ }
+ }
+ else
+ {
+ fprintf (out, "%s", v2);
+ col += string_columns(v2, strlen (v2), col);
+ }
+
+ if (nl)
+ v2 = nl + 1;
+ else
+ break;
+ }
+
+ fprintf(out, "\n");
+ free(v);
+}
+
+int
+write_init_file (Display *dpy,
+ saver_preferences *p, const char *version_string,
+ Bool verbose_p)
+{
+ int status = -1;
+ const char *name = init_file_name();
+ const char *tmp_name = init_file_tmp_name();
+ char *n2 = chase_symlinks (name);
+ struct stat st;
+ int i, j;
+
+ /* Kludge, since these aren't in the saver_preferences struct as strings...
+ */
+ char *visual_name;
+ char *programs;
+ Bool overlay_stderr_p;
+ char *stderr_font;
+ FILE *out;
+
+ if (!name) goto END;
+
+ if (n2) name = n2;
+
+ /* Throttle the various timeouts to reasonable values before writing
+ the file to disk. */
+ stop_the_insanity (p);
+
+
+ if (verbose_p)
+ fprintf (stderr, "%s: writing \"%s\"\n", blurb(), name);
+
+ unlink (tmp_name);
+ out = fopen(tmp_name, "w");
+ if (!out)
+ {
+ char *buf = (char *) malloc(1024 + strlen(name));
+ sprintf(buf, "%s: error writing \"%s\"", blurb(), name);
+ perror(buf);
+ free(buf);
+ goto END;
+ }
+
+ /* Give the new .xscreensaver file the same permissions as the old one;
+ except ensure that it is readable and writable by owner, and not
+ executable. Extra hack: if we're running as root, make the file
+ be world-readable (so that the daemon, running as "nobody", will
+ still be able to read it.)
+ */
+ if (stat(name, &st) == 0)
+ {
+ mode_t mode = st.st_mode;
+ mode |= S_IRUSR | S_IWUSR; /* read/write by user */
+ mode &= ~(S_IXUSR | S_IXGRP | S_IXOTH); /* executable by none */
+
+ if (getuid() == (uid_t) 0) /* read by group/other */
+ mode |= S_IRGRP | S_IROTH;
+
+ if (fchmod (fileno(out), mode) != 0)
+ {
+ char *buf = (char *) malloc(1024 + strlen(name));
+ sprintf (buf, "%s: error fchmodding \"%s\" to 0%o", blurb(),
+ tmp_name, (unsigned int) mode);
+ perror(buf);
+ free(buf);
+ goto END;
+ }
+ }
+
+ /* Kludge, since these aren't in the saver_preferences struct... */
+ visual_name = get_string_resource (dpy, "visualID", "VisualID");
+ programs = 0;
+ overlay_stderr_p = get_boolean_resource (dpy, "overlayStderr", "Boolean");
+ stderr_font = get_string_resource (dpy, "font", "Font");
+
+ i = 0;
+ {
+ char *ss;
+ char **hack_strings = (char **)
+ calloc (p->screenhacks_count, sizeof(char *));
+
+ for (j = 0; j < p->screenhacks_count; j++)
+ {
+ hack_strings[j] = format_hack (dpy, p->screenhacks[j], True);
+ i += strlen (hack_strings[j]);
+ i += 2;
+ }
+
+ ss = programs = (char *) malloc(i + 10);
+ *ss = 0;
+ for (j = 0; j < p->screenhacks_count; j++)
+ {
+ strcat (ss, hack_strings[j]);
+ free (hack_strings[j]);
+ ss += strlen(ss);
+ *ss++ = '\n';
+ *ss = 0;
+ }
+ free (hack_strings);
+ }
+
+ {
+ struct passwd *pw = getpwuid (getuid ());
+ char *whoami = (pw && pw->pw_name && *pw->pw_name
+ ? pw->pw_name
+ : "<unknown>");
+ time_t now = time ((time_t *) 0);
+ char *timestr = (char *) ctime (&now);
+ char *nl = (char *) strchr (timestr, '\n');
+ if (nl) *nl = 0;
+ fprintf (out,
+ "# %s Preferences File\n"
+ "# Written by %s %s for %s on %s.\n"
+ "# https://www.jwz.org/xscreensaver/\n"
+ "\n",
+ progclass, progname, version_string, whoami, timestr);
+ }
+
+ for (j = 0; prefs[j]; j++)
+ {
+ char buf[255];
+ const char *pr = prefs[j];
+ enum pref_type { pref_str, pref_int, pref_bool, pref_byte, pref_time
+ } type = pref_str;
+ const char *s = 0;
+ int i = 0;
+ Bool b = False;
+ Time t = 0;
+
+ if (pr && !*pr)
+ {
+ fprintf(out, "\n");
+ continue;
+ }
+
+# undef CHECK
+# define CHECK(X) else if (!strcmp(pr, X))
+ if (!pr || !*pr) ;
+ CHECK("timeout") type = pref_time, t = p->timeout;
+ CHECK("cycle") type = pref_time, t = p->cycle;
+ CHECK("lock") type = pref_bool, b = p->lock_p;
+ CHECK("lockVTs") continue; /* don't save, unused */
+ CHECK("lockTimeout") type = pref_time, t = p->lock_timeout;
+ CHECK("passwdTimeout") type = pref_time, t = p->passwd_timeout;
+ CHECK("visualID") type = pref_str, s = visual_name;
+ CHECK("installColormap") type = pref_bool, b = p->install_cmap_p;
+ CHECK("verbose") type = pref_bool, b = p->verbose_p;
+ CHECK("splash") type = pref_bool, b = p->splash_p;
+ CHECK("splashDuration") type = pref_time, t = p->splash_duration;
+ CHECK("quad") continue; /* don't save */
+ CHECK("demoCommand") type = pref_str, s = p->demo_command;
+ CHECK("prefsCommand") continue; /* don't save, unused */
+ CHECK("helpURL") continue; /* don't save */
+ CHECK("loadURL") continue; /* don't save */
+ CHECK("newLoginCommand") continue; /* don't save */
+ CHECK("dialogTheme") type = pref_str, s = p->dialog_theme;
+ CHECK("nice") type = pref_int, i = p->nice_inferior;
+ CHECK("memoryLimit") continue; /* don't save */
+ CHECK("fade") type = pref_bool, b = p->fade_p;
+ CHECK("unfade") type = pref_bool, b = p->unfade_p;
+ CHECK("fadeSeconds") type = pref_time, t = p->fade_seconds;
+ CHECK("fadeTicks") continue; /* don't save */
+ CHECK("captureStdout") continue; /* don't save */
+ CHECK("logFile") continue; /* don't save */
+ CHECK("ignoreUninstalledPrograms")
+ type = pref_bool, b = p->ignore_uninstalled_p;
+
+ CHECK("font") type = pref_str, s = stderr_font;
+
+ CHECK("dpmsEnabled") type = pref_bool, b = p->dpms_enabled_p;
+ CHECK("dpmsQuickOff") type = pref_bool, b = p->dpms_quickoff_p;
+ CHECK("dpmsStandby") type = pref_time, t = p->dpms_standby;
+ CHECK("dpmsSuspend") type = pref_time, t = p->dpms_suspend;
+ CHECK("dpmsOff") type = pref_time, t = p->dpms_off;
+
+ CHECK("grabDesktopImages") type =pref_bool, b = p->grab_desktop_p;
+ CHECK("grabVideoFrames") type =pref_bool, b = p->grab_video_p;
+ CHECK("chooseRandomImages")type =pref_bool, b = p->random_image_p;
+ CHECK("imageDirectory") type =pref_str, s = p->image_directory;
+
+ CHECK("mode") type = pref_str,
+ s = (p->mode == ONE_HACK ? "one" :
+ p->mode == BLANK_ONLY ? "blank" :
+ p->mode == DONT_BLANK ? "off" :
+ p->mode == RANDOM_HACKS_SAME
+ ? "random-same"
+ : "random");
+ CHECK("selected") type = pref_int, i = p->selected_hack;
+
+ CHECK("textMode") type = pref_str,
+ s = (p->tmode == TEXT_URL ? "url" :
+ p->tmode == TEXT_LITERAL ? "literal" :
+ p->tmode == TEXT_FILE ? "file" :
+ p->tmode == TEXT_PROGRAM ? "program" :
+ "date");
+ CHECK("textLiteral") type = pref_str, s = p->text_literal;
+ CHECK("textFile") type = pref_str, s = p->text_file;
+ CHECK("textProgram") type = pref_str, s = p->text_program;
+ CHECK("textURL") type = pref_str, s = p->text_url;
+
+ CHECK("programs") type = pref_str, s = programs;
+ CHECK("pointerHysteresis")type = pref_int, i = p->pointer_hysteresis;
+ CHECK("overlayStderr") type = pref_bool, b = overlay_stderr_p;
+ CHECK("overlayTextBackground") continue; /* don't save */
+ CHECK("overlayTextForeground") continue; /* don't save */
+ CHECK("bourneShell") continue; /* don't save */
+ CHECK("authWarningSlack") type = pref_int, i = p->auth_warning_slack;
+ else
+ {
+ fprintf (stderr, "%s: internal error: key %s\n", blurb(), pr);
+ abort();
+ }
+# undef CHECK
+
+ switch (type)
+ {
+ case pref_str:
+ break;
+ case pref_int:
+ sprintf(buf, "%d", i);
+ s = buf;
+ break;
+ case pref_bool:
+ s = b ? "True" : "False";
+ break;
+ case pref_time:
+ {
+ unsigned int hour = 0, min = 0, sec = (unsigned int) (t/1000);
+ if (sec >= 60)
+ {
+ min += (sec / 60);
+ sec %= 60;
+ }
+ if (min >= 60)
+ {
+ hour += (min / 60);
+ min %= 60;
+ }
+ sprintf (buf, "%u:%02u:%02u", hour, min, sec);
+ s = buf;
+ }
+ break;
+ case pref_byte:
+ {
+ if (i >= (1<<30) && i == ((i >> 30) << 30))
+ sprintf(buf, "%dG", i >> 30);
+ else if (i >= (1<<20) && i == ((i >> 20) << 20))
+ sprintf(buf, "%dM", i >> 20);
+ else if (i >= (1<<10) && i == ((i >> 10) << 10))
+ sprintf(buf, "%dK", i >> 10);
+ else
+ sprintf(buf, "%d", i);
+ s = buf;
+ }
+ break;
+ default:
+ abort();
+ break;
+ }
+
+ if (pr && (!strcmp(pr, "mode") || !strcmp(pr, "textMode")))
+ fprintf(out, "\n");
+
+ write_entry (out, pr, s);
+ }
+
+ fprintf(out, "\n");
+
+ if (visual_name) free(visual_name);
+ if (stderr_font) free(stderr_font);
+ if (programs) free(programs);
+
+ if (fclose(out) == 0)
+ {
+ time_t write_date = 0;
+
+ if (stat(tmp_name, &st) == 0)
+ {
+ write_date = st.st_mtime;
+ }
+ else
+ {
+ char *buf = (char *) malloc(1024 + strlen(tmp_name) + strlen(name));
+ sprintf(buf, "%s: couldn't stat \"%s\"", blurb(), tmp_name);
+ perror(buf);
+ unlink (tmp_name);
+ free(buf);
+ goto END;
+ }
+
+ if (rename (tmp_name, name) != 0)
+ {
+ char *buf = (char *) malloc(1024 + strlen(tmp_name) + strlen(name));
+ sprintf(buf, "%s: error renaming \"%s\" to \"%s\"",
+ blurb(), tmp_name, name);
+ perror(buf);
+ unlink (tmp_name);
+ free(buf);
+ goto END;
+ }
+ else
+ {
+ p->init_file_date = write_date;
+
+ /* Since the .xscreensaver file is used for IPC, let's try and make
+ sure that the bits actually land on the disk right away. */
+ /* Update 2020: Apparently here in the future, this sometimes takes
+ 3+ seconds, so let's not. */
+ /* sync(); */
+
+ status = 0; /* wrote and renamed successfully! */
+ }
+ }
+ else
+ {
+ char *buf = (char *) malloc(1024 + strlen(name));
+ sprintf(buf, "%s: error closing \"%s\"", blurb(), name);
+ perror(buf);
+ free(buf);
+ unlink (tmp_name);
+ goto END;
+ }
+
+ END:
+ if (n2) free (n2);
+ return status;
+}
+
+
+/* Parsing the resource database
+ */
+
+static void
+free_screenhack (screenhack *hack)
+{
+ if (hack->visual) free (hack->visual);
+ if (hack->name) free (hack->name);
+ free (hack->command);
+ memset (hack, 0, sizeof(*hack));
+ free (hack);
+}
+
+static void
+free_screenhack_list (screenhack **list, int count)
+{
+ int i;
+ if (!list) return;
+ for (i = 0; i < count; i++)
+ if (list[i])
+ free_screenhack (list[i]);
+ free (list);
+}
+
+
+
+/* Populate `saver_preferences' with the contents of the resource database.
+ Note that this may be called multiple times -- it is re-run each time
+ the ~/.xscreensaver file is reloaded.
+
+ This function can be very noisy, since it issues resource syntax errors
+ and so on.
+ */
+void
+load_init_file (Display *dpy, saver_preferences *p)
+{
+ static Bool first_time = True;
+
+ screenhack **system_default_screenhacks = 0;
+ int system_default_screenhack_count = 0;
+
+ if (first_time)
+ {
+ /* Get the programs resource before the .xscreensaver file has been
+ parsed and merged into the resource database for the first time:
+ this is the value of *programs from the app-defaults file.
+ Then clear it out so that it will be parsed again later, after
+ the init file has been read.
+ */
+ get_screenhacks (dpy, p);
+ system_default_screenhacks = p->screenhacks;
+ system_default_screenhack_count = p->screenhacks_count;
+ p->screenhacks = 0;
+ p->screenhacks_count = 0;
+ }
+
+ if (parse_init_file_1 (p) != 0) /* file might have gone away */
+ if (!first_time) return;
+
+ first_time = False;
+
+ p->xsync_p = get_boolean_resource (dpy, "synchronous", "Synchronous");
+ p->verbose_p = get_boolean_resource (dpy, "verbose", "Boolean");
+ p->lock_p = get_boolean_resource (dpy, "lock", "Boolean");
+ p->fade_p = get_boolean_resource (dpy, "fade", "Boolean");
+ p->unfade_p = get_boolean_resource (dpy, "unfade", "Boolean");
+ p->fade_seconds = 1000 * get_seconds_resource (dpy, "fadeSeconds", "Time");
+ p->install_cmap_p = get_boolean_resource (dpy, "installColormap", "Boolean");
+ p->nice_inferior = get_integer_resource (dpy, "nice", "Nice");
+ p->splash_p = get_boolean_resource (dpy, "splash", "Boolean");
+# ifdef QUAD_MODE
+ p->quad_p = get_boolean_resource (dpy, "quad", "Boolean");
+# endif
+ p->ignore_uninstalled_p = get_boolean_resource (dpy,
+ "ignoreUninstalledPrograms",
+ "Boolean");
+
+ p->splash_duration = 1000 * get_seconds_resource (dpy, "splashDuration", "Time");
+ p->timeout = 1000 * get_minutes_resource (dpy, "timeout", "Time");
+ p->lock_timeout = 1000 * get_minutes_resource (dpy, "lockTimeout", "Time");
+ p->cycle = 1000 * get_minutes_resource (dpy, "cycle", "Time");
+ p->passwd_timeout = 1000 * get_seconds_resource (dpy, "passwdTimeout", "Time");
+ p->pointer_hysteresis = get_integer_resource (dpy, "pointerHysteresis","Integer");
+
+ p->dpms_enabled_p = get_boolean_resource (dpy, "dpmsEnabled", "Boolean");
+ p->dpms_quickoff_p = get_boolean_resource (dpy, "dpmsQuickOff", "Boolean");
+ p->dpms_standby = 1000 * get_minutes_resource (dpy, "dpmsStandby", "Time");
+ p->dpms_suspend = 1000 * get_minutes_resource (dpy, "dpmsSuspend", "Time");
+ p->dpms_off = 1000 * get_minutes_resource (dpy, "dpmsOff", "Time");
+
+ p->grab_desktop_p = get_boolean_resource (dpy, "grabDesktopImages", "Boolean");
+ p->grab_video_p = get_boolean_resource (dpy, "grabVideoFrames", "Boolean");
+ p->random_image_p = get_boolean_resource (dpy, "chooseRandomImages", "Boolean");
+ p->image_directory = get_string_resource (dpy,
+ "imageDirectory",
+ "ImageDirectory");
+
+ p->text_literal = get_string_resource (dpy, "textLiteral", "TextLiteral");
+ p->text_file = get_string_resource (dpy, "textFile", "TextFile");
+ p->text_program = get_string_resource (dpy, "textProgram", "TextProgram");
+ p->text_url = get_string_resource (dpy, "textURL", "TextURL");
+
+ p->shell = get_string_resource (dpy, "bourneShell", "BourneShell");
+
+ p->demo_command = get_string_resource(dpy, "demoCommand", "Command");
+ p->help_url = get_string_resource(dpy, "helpURL", "URL");
+ p->load_url_command = get_string_resource(dpy, "loadURL", "Command");
+ p->new_login_command = get_string_resource(dpy, "newLoginCommand",
+ "Command");
+ p->dialog_theme = get_string_resource(dpy, "dialogTheme", "String");
+ p->auth_warning_slack = get_integer_resource(dpy, "authWarningSlack",
+ "Integer");
+
+ /* If "*splash" is unset, default to true. */
+ {
+ char *s = get_string_resource (dpy, "splash", "Boolean");
+ if (s)
+ free (s);
+ else
+ p->splash_p = True;
+ }
+
+ /* If "*grabDesktopImages" is unset, default to true. */
+ {
+ char *s = get_string_resource (dpy, "grabDesktopImages", "Boolean");
+ if (s)
+ free (s);
+ else
+ p->grab_desktop_p = True;
+ }
+
+ get_screenhacks (dpy, p); /* Parse the "programs" resource. */
+
+ {
+ char *s = get_string_resource (dpy, "selected", "Integer");
+ if (!s || !*s)
+ p->selected_hack = -1;
+ else
+ p->selected_hack = get_integer_resource (dpy, "selected", "Integer");
+ if (s) free (s);
+ if (p->selected_hack < 0 || p->selected_hack >= p->screenhacks_count)
+ p->selected_hack = -1;
+ }
+
+ {
+ char *s = get_string_resource (dpy, "mode", "Mode");
+ if (s && !strcasecmp (s, "one")) p->mode = ONE_HACK;
+ else if (s && !strcasecmp (s, "blank")) p->mode = BLANK_ONLY;
+ else if (s && !strcasecmp (s, "off")) p->mode = DONT_BLANK;
+ else if (s && !strcasecmp (s, "random-same")) p->mode = RANDOM_HACKS_SAME;
+ else p->mode = RANDOM_HACKS;
+ if (s) free (s);
+ }
+
+ {
+ char *s = get_string_resource (dpy, "textMode", "TextMode");
+ if (s && !strcasecmp (s, "url")) p->tmode = TEXT_URL;
+ else if (s && !strcasecmp (s, "literal")) p->tmode = TEXT_LITERAL;
+ else if (s && !strcasecmp (s, "file")) p->tmode = TEXT_FILE;
+ else if (s && !strcasecmp (s, "program")) p->tmode = TEXT_PROGRAM;
+ else p->tmode = TEXT_DATE;
+ if (s) free (s);
+ }
+
+ if (system_default_screenhack_count) /* note: first_time is also true */
+ {
+ merge_system_screenhacks (dpy, p, system_default_screenhacks,
+ system_default_screenhack_count);
+ free_screenhack_list (system_default_screenhacks,
+ system_default_screenhack_count);
+ system_default_screenhacks = 0;
+ system_default_screenhack_count = 0;
+ }
+
+ if (p->debug_p)
+ {
+ p->xsync_p = True;
+ p->verbose_p = True;
+ }
+
+ /* Throttle the various timeouts to reasonable values after reading the
+ disk file. */
+ stop_the_insanity (p);
+}
+
+
+/* If there are any hacks in the system-wide defaults that are not in
+ the ~/.xscreensaver file, add the new ones to the end of the list.
+ This does *not* actually save the file.
+ */
+static void
+merge_system_screenhacks (Display *dpy, saver_preferences *p,
+ screenhack **system_list, int system_count)
+{
+ /* Yeah yeah, this is an N^2 operation, but I don't have hashtables handy,
+ so fuck it. */
+
+ int made_space = 0;
+ int i;
+ for (i = 0; i < system_count; i++)
+ {
+ int j;
+ Bool matched_p = False;
+
+ for (j = 0; j < p->screenhacks_count; j++)
+ {
+ char *name;
+ if (!system_list[i]->name)
+ system_list[i]->name = make_hack_name (dpy,
+ system_list[i]->command);
+
+ name = p->screenhacks[j]->name;
+ if (!name)
+ name = make_hack_name (dpy, p->screenhacks[j]->command);
+
+ matched_p = !strcasecmp (name, system_list[i]->name);
+
+ if (name != p->screenhacks[j]->name)
+ free (name);
+
+ if (matched_p)
+ break;
+ }
+
+ if (!matched_p)
+ {
+ /* We have an entry in the system-wide list that is not in the
+ user's .xscreensaver file. Add it to the end.
+ Note that p->screenhacks is a single malloc block, not a
+ linked list, so we have to realloc it.
+ */
+ screenhack *oh = system_list[i];
+ screenhack *nh = (screenhack *) malloc (sizeof(screenhack));
+
+ if (made_space == 0)
+ {
+ made_space = 10;
+ p->screenhacks = (screenhack **)
+ realloc (p->screenhacks,
+ (p->screenhacks_count + made_space + 1)
+ * sizeof(*p->screenhacks));
+ if (!p->screenhacks) abort();
+ }
+
+ nh->enabled_p = oh->enabled_p;
+ nh->visual = oh->visual ? strdup(oh->visual) : 0;
+ nh->name = oh->name ? strdup(oh->name) : 0;
+ nh->command = oh->command ? strdup(oh->command) : 0;
+
+ p->screenhacks[p->screenhacks_count++] = nh;
+ p->screenhacks[p->screenhacks_count] = 0;
+ made_space--;
+
+#if 0
+ fprintf (stderr, "%s: noticed new hack: %s\n", blurb(),
+ (nh->name ? nh->name : make_hack_name (dpy, nh->command)));
+#endif
+ }
+ }
+}
+
+
+
+/* Parsing the programs resource.
+ */
+
+static screenhack *
+parse_screenhack (const char *line)
+{
+ screenhack *h = (screenhack *) calloc (1, sizeof(*h));
+ const char *s;
+
+ h->enabled_p = True;
+
+ while (isspace(*line)) line++; /* skip whitespace */
+ if (*line == '-') /* handle "-" */
+ {
+ h->enabled_p = False;
+ line++;
+ while (isspace(*line)) line++; /* skip whitespace */
+ }
+
+ s = line; /* handle "visual:" */
+ while (*line && *line != ':' && *line != '"' && !isspace(*line))
+ line++;
+ if (*line != ':')
+ line = s;
+ else
+ {
+ h->visual = (char *) malloc (line-s+1);
+ strncpy (h->visual, s, line-s);
+ h->visual[line-s] = 0;
+ if (*line == ':') line++; /* skip ":" */
+ while (isspace(*line)) line++; /* skip whitespace */
+ }
+
+ if (*line == '"') /* handle "name" */
+ {
+ line++;
+ s = line;
+ while (*line && *line != '"')
+ line++;
+ h->name = (char *) malloc (line-s+1);
+ strncpy (h->name, s, line-s);
+ h->name[line-s] = 0;
+ if (*line == '"') line++; /* skip "\"" */
+ while (isspace(*line)) line++; /* skip whitespace */
+ }
+
+ h->command = format_command (line, False); /* handle command */
+ return h;
+}
+
+
+static char *
+format_command (const char *cmd, Bool wrap_p)
+{
+ int tab = 30;
+ int col = tab;
+ char *cmd2 = (char *) calloc (1, 2 * (strlen (cmd) + 1));
+ const char *in = cmd;
+ char *out = cmd2;
+ while (*in)
+ {
+ /* shrink all whitespace to one space, for the benefit of the "demo"
+ mode display. We only do this when we can easily tell that the
+ whitespace is not significant (no shell metachars).
+ */
+ switch (*in)
+ {
+ case '\'': case '"': case '`': case '\\':
+ /* Metachars are scary. Copy the rest of the line unchanged. */
+ while (*in)
+ *out++ = *in++, col++;
+ break;
+
+ case ' ': case '\t':
+ /* Squeeze all other whitespace down to one space. */
+ while (*in == ' ' || *in == '\t')
+ in++;
+ *out++ = ' ', col++;
+ break;
+
+ default:
+ /* Copy other chars unchanged. */
+ *out++ = *in++, col++;
+ break;
+ }
+ }
+
+ *out = 0;
+
+ /* Strip trailing whitespace */
+ while (out > cmd2 && isspace (out[-1]))
+ *(--out) = 0;
+
+ return cmd2;
+}
+
+
+/* Returns a new string describing the shell command.
+ This may be just the name of the program, capitalized.
+ It also may be something from the resource database (gotten
+ by looking for "hacks.XYZ.name", where XYZ is the program.)
+ */
+char *
+make_hack_name (Display *dpy, const char *shell_command)
+{
+ char *s = strdup (shell_command);
+ char *s2;
+ char res_name[255];
+
+ for (s2 = s; *s2; s2++) /* truncate at first whitespace */
+ if (isspace (*s2))
+ {
+ *s2 = 0;
+ break;
+ }
+
+ s2 = strrchr (s, '/'); /* if pathname, take last component */
+ if (s2)
+ {
+ s2 = strdup (s2+1);
+ free (s);
+ s = s2;
+ }
+
+ if (strlen (s) > 50) /* 51 is hereby defined as "unreasonable" */
+ s[50] = 0;
+
+ sprintf (res_name, "hacks.%s.name", s); /* resource? */
+ s2 = get_string_resource (dpy, res_name, res_name);
+ if (s2)
+ {
+ free (s);
+ return s2;
+ }
+
+ for (s2 = s; *s2; s2++) /* if it has any capitals, return it */
+ if (*s2 >= 'A' && *s2 <= 'Z')
+ return s;
+
+ if (s[0] >= 'a' && s[0] <= 'z') /* else cap it */
+ s[0] -= 'a'-'A';
+ if (s[0] == 'X' && s[1] >= 'a' && s[1] <= 'z') /* (magic leading X) */
+ s[1] -= 'a'-'A';
+ if (s[0] == 'G' && s[1] == 'l' &&
+ s[2] >= 'a' && s[2] <= 'z') /* (magic leading GL) */
+ s[1] -= 'a'-'A',
+ s[2] -= 'a'-'A';
+ return s;
+}
+
+
+static char *
+format_hack (Display *dpy, screenhack *hack, Bool wrap_p)
+{
+ int tab = 32;
+ int size;
+ char *h2, *out, *s;
+ int col = 0;
+
+ char *def_name = make_hack_name (dpy, hack->command);
+
+ /* Don't ever write out a name for a hack if it's the same as the default.
+ */
+ if (hack->name && !strcmp (hack->name, def_name))
+ {
+ free (hack->name);
+ hack->name = 0;
+ }
+ free (def_name);
+
+ size = (2 * (strlen(hack->command) +
+ (hack->visual ? strlen(hack->visual) : 0) +
+ (hack->name ? strlen(hack->name) : 0) +
+ tab));
+ h2 = (char *) malloc (size);
+ out = h2;
+
+ if (!hack->enabled_p) *out++ = '-'; /* write disabled flag */
+
+ if (hack->visual && *hack->visual) /* write visual name */
+ {
+ if (hack->enabled_p) *out++ = ' ';
+ *out++ = ' ';
+ strcpy (out, hack->visual);
+ out += strlen (hack->visual);
+ *out++ = ':';
+ *out++ = ' ';
+ }
+
+ *out = 0;
+ col = string_columns (h2, strlen (h2), 0);
+
+ if (hack->name && *hack->name) /* write pretty name */
+ {
+ int L = (strlen (hack->name) + 2);
+ if (L + col < tab)
+ out = stab_to (out, col, tab - L - 2);
+ else
+ *out++ = ' ';
+ *out++ = '"';
+ strcpy (out, hack->name);
+ out += strlen (hack->name);
+ *out++ = '"';
+ *out = 0;
+
+ col = string_columns (h2, strlen (h2), 0);
+ if (wrap_p && col >= tab)
+ out = stab_to (out, col, 77);
+ else
+ *out++ = ' ';
+
+ if (out >= h2+size) abort();
+ }
+
+ *out = 0;
+ col = string_columns (h2, strlen (h2), 0);
+ out = stab_to (out, col, tab); /* indent */
+
+ if (out >= h2+size) abort();
+ s = format_command (hack->command, wrap_p);
+ strcpy (out, s);
+ out += strlen (s);
+ free (s);
+ *out = 0;
+
+ return h2;
+}
+
+
+static void
+get_screenhacks (Display *dpy, saver_preferences *p)
+{
+ int i, j;
+ int start = 0;
+ int end = 0;
+ int size;
+ char *d = get_string_resource (dpy, "programs", "Programs");
+
+ free_screenhack_list (p->screenhacks, p->screenhacks_count);
+ p->screenhacks = 0;
+ p->screenhacks_count = 0;
+
+ if (!d || !*d)
+ return;
+
+ size = strlen (d);
+
+
+ /* Count up the number of newlines (which will be equal to or larger than
+ one less than the number of hacks.)
+ */
+ for (i = j = 0; d[i]; i++)
+ if (d[i] == '\n')
+ j++;
+ j++;
+
+ p->screenhacks = (screenhack **) calloc (j + 1, sizeof (screenhack *));
+
+ /* Iterate over the lines in `d' (the string with newlines)
+ and make new strings to stuff into the `screenhacks' array.
+ */
+ p->screenhacks_count = 0;
+ while (start < size)
+ {
+ /* skip forward over whitespace. */
+ while (d[start] == ' ' || d[start] == '\t' || d[start] == '\n')
+ start++;
+
+ /* skip forward to newline or end of string. */
+ end = start;
+ while (d[end] != 0 && d[end] != '\n')
+ end++;
+
+ /* null terminate. */
+ d[end] = 0;
+
+ p->screenhacks[p->screenhacks_count++] = parse_screenhack (d + start);
+ if (p->screenhacks_count >= i)
+ abort();
+
+ start = end+1;
+ }
+
+ free (d);
+
+ if (p->screenhacks_count == 0)
+ {
+ free (p->screenhacks);
+ p->screenhacks = 0;
+ }
+}
+
+
+/* Make sure all the values in the preferences struct are sane.
+ */
+static void
+stop_the_insanity (saver_preferences *p)
+{
+ if (p->passwd_timeout <= 0) p->passwd_timeout = 30000; /* 30 secs */
+ if (p->timeout < 15000) p->timeout = 15000; /* 15 secs */
+ if (p->cycle != 0 && p->cycle < 2000) p->cycle = 2000; /* 2 secs */
+ if (p->fade_seconds <= 0)
+ p->fade_p = False;
+ if (! p->fade_p) p->unfade_p = False;
+
+ /* The DPMS settings may have the value 0.
+ But if they are negative, or are a range less than 10 seconds,
+ reset them to sensible defaults. (Since that must be a mistake.)
+ */
+ if (p->dpms_standby != 0 &&
+ p->dpms_standby < 10 * 1000)
+ p->dpms_standby = 2 * 60 * 60 * 1000; /* 2 hours */
+ if (p->dpms_suspend != 0 &&
+ p->dpms_suspend < 10 * 1000)
+ p->dpms_suspend = 2 * 60 * 60 * 1000; /* 2 hours */
+ if (p->dpms_off != 0 &&
+ p->dpms_off < 10 * 1000)
+ p->dpms_off = 4 * 60 * 60 * 1000; /* 4 hours */
+
+ /* suspend may not be greater than off, unless off is 0.
+ standby may not be greater than suspend, unless suspend is 0.
+ */
+ if (p->dpms_off != 0 &&
+ p->dpms_suspend > p->dpms_off)
+ p->dpms_suspend = p->dpms_off;
+ if (p->dpms_suspend != 0 &&
+ p->dpms_standby > p->dpms_suspend)
+ p->dpms_standby = p->dpms_suspend;
+
+ /* These fixes above ignores the case
+ suspend = 0 and standby > off ...
+ */
+ if (p->dpms_off != 0 &&
+ p->dpms_standby > p->dpms_off)
+ p->dpms_standby = p->dpms_off;
+
+ if (p->dpms_standby == 0 && /* if *all* are 0, then DPMS is disabled */
+ p->dpms_suspend == 0 &&
+ p->dpms_off == 0 &&
+ !p->dpms_quickoff_p) /* ... but we want to do DPMS quick off */
+ p->dpms_enabled_p = False;
+
+
+ /* Set watchdog timeout to about half of the cycle timeout, but
+ don't let it be faster than 1/2 minute or slower than 1 minute.
+ */
+ p->watchdog_timeout = p->cycle * 0.6;
+ if (p->watchdog_timeout < 27000) p->watchdog_timeout = 27000; /* 27 secs */
+ if (p->watchdog_timeout > 57000) p->watchdog_timeout = 57000; /* 57 secs */
+
+ if (p->pointer_hysteresis < 0) p->pointer_hysteresis = 0;
+
+ if (p->auth_warning_slack < 0) p->auth_warning_slack = 0;
+ if (p->auth_warning_slack > 300) p->auth_warning_slack = 300;
+}
+
+
+Bool
+senescent_p (void)
+{
+ /* If you are in here because you're planning on disabling this warning
+ before redistributing my software, please don't.
+
+ I sincerely request that you do one of the following:
+
+ 1: leave this code intact and this warning in place, -OR-
+
+ 2: Remove xscreensaver from your distribution.
+
+ I would seriously prefer that you not distribute my software at all
+ than that you distribute one version and then never update it for
+ years.
+
+ I am *constantly* getting email from users reporting bugs that have
+ been fixed for literally years who have no idea that the software
+ they are running is years out of date. Yes, it would be great if we
+ lived in the ideal world where people checked that they were running
+ the latest release before they report a bug, but we don't. To most
+ people, "running the latest release" is synonymous with "running the
+ latest release that my distro packages for me."
+
+ When they even bother to tell me what version they're running, I
+ say, "That version is three years old!", and they say "But this is
+ the latest version my distro ships". Then I say, "your distro
+ sucks", and they say "but I don't know how to compile from source,
+ herp derp I eat paste", and *everybody* goes away unhappy.
+
+ It wastes an enormous amount of my time, but worse than that, it
+ does a grave disservice to the users, who are stuck experiencing
+ bugs that are already fixed! These users think they are running the
+ latest release, and they are not. They would like to be running the
+ actual latest release, but they don't know how, because their distro
+ makes that very difficult for them. It's terrible for everyone, and
+ kind of makes me regret ever having released this software in the
+ first place.
+
+ So seriously. I ask that if you're planning on disabling this
+ obsolescence warning, that you instead just remove xscreensaver from
+ your distro entirely. Everybody will be happier that way. Check
+ out gnome-screensaver instead, I understand it's really nice.
+
+ Of course, my license allows you to ignore me and do whatever the
+ fuck you want, but as the author, I hope you will have the common
+ courtesy of complying with my request.
+
+ Thank you!
+
+ jwz, 2014, 2016, 2018, 2021.
+
+ PS: In particular, since Debian refuses to upgrade software on any
+ kind of rational timeline, I have asked that they stop shipping
+ xscreensaver at all. They have refused. Instead of upgrading the
+ software, they simply patched out this warning.
+
+ If you want to witness the sad state of the open source peanut
+ gallery, look no farther than the comments on my blog:
+ http://jwz.org/b/yiYo
+
+ Many of these people fall back on their go-to argument of, "If it is
+ legal, it must be right." If you believe in that rhetorical device
+ then you are a terrible person, and possibly a sociopath.
+
+ There are also the armchair lawyers who say "Well, instead of
+ *asking* people to do the right thing out of common courtesy, you
+ should just change your license to prohibit them from acting
+ amorally." Again, this is the answer of a sociopath, but that aside,
+ if you devote even a second's thought to this you will realize that
+ the end result of this would be for distros like Debian to just keep
+ shipping the last version with the old license and then never
+ upgrading it again -- which would be the worst possible outcome for
+ everyone involved, most especially the users.
+
+ Also, some have incorrectly characterized this as a "time bomb".
+ It is a software update notification, nothing more. A "time bomb"
+ makes software stop working. This merely alerts the user that the
+ security-critical software that they are running is dangerously out
+ of date.
+ */
+
+ time_t now = time ((time_t *) 0); /* d */
+ struct tm *tm = localtime (&now); /* o */
+ const char *s = screensaver_id; /* n */
+ char mon[4], year[5]; /* ' */
+ int m, y, mrnths; /* t */
+ s = strchr (s, ' '); if (!s) abort(); s++; /* */
+ s = strchr (s, '('); if (!s) abort(); s++; /* d */
+ s = strchr (s, '-'); if (!s) abort(); s++; /* o */
+ strncpy (mon, s, 3); /* o */
+ mon[3] = 0; /* */
+ s = strchr (s, '-'); if (!s) abort(); s++; /* e */
+ strncpy (year, s, 4); /* e */
+ year[4] = 0; /* t */
+ y = atoi (year); /* , */
+ if (!strcmp(mon, "Jan")) m = 0; /* */
+ else if (!strcmp(mon, "Feb")) m = 1; /* s */
+ else if (!strcmp(mon, "Mar")) m = 2; /* t */
+ else if (!strcmp(mon, "Apr")) m = 3; /* o */
+ else if (!strcmp(mon, "May")) m = 4; /* p */
+ else if (!strcmp(mon, "Jun")) m = 5; /* , */
+ else if (!strcmp(mon, "Jul")) m = 6; /* */
+ else if (!strcmp(mon, "Aug")) m = 7; /* s */
+ else if (!strcmp(mon, "Sep")) m = 8; /* t */
+ else if (!strcmp(mon, "Oct")) m = 9; /* a */
+ else if (!strcmp(mon, "Nov")) m = 10; /* a */
+ else if (!strcmp(mon, "Dec")) m = 11; /* a */
+ else abort(); /* h */
+ mrnths = ((((tm->tm_year + 1900) * 12) + tm->tm_mon) - /* h */
+ (y * 12 + m)); /* h */
+ /* p */
+ return (mrnths >= 17); /* . */
+}
diff --git a/driver/remote.c b/driver/remote.c
index 83254e0..123bc28 100644
--- a/driver/remote.c
+++ b/driver/remote.c
@@ -1,4 +1,4 @@
-/* xscreensaver-command, Copyright (c) 1991-2019 Jamie Zawinski <jwz@jwz.org>
+/* xscreensaver-command, Copyright © 1991-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
@@ -33,102 +33,61 @@
#include <X11/Xutil.h> /* for XGetClassHint() */
#include <X11/Xos.h>
+#ifdef HAVE_DPMS_EXTENSION
+# include <X11/extensions/dpms.h>
+#endif
+
+#include "blurb.h"
+#include "atoms.h"
#include "remote.h"
+#include "clientmsg.h"
#ifdef _VROOT_H_
ERROR! you must not include vroot.h in this file
#endif
-extern char *progname;
-extern Atom XA_SCREENSAVER, XA_SCREENSAVER_VERSION, XA_SCREENSAVER_RESPONSE;
-extern Atom XA_SCREENSAVER_ID, XA_SCREENSAVER_STATUS, XA_EXIT;
-extern Atom XA_VROOT, XA_SELECT, XA_DEMO, XA_BLANK, XA_LOCK;
-extern Atom XA_ACTIVATE, XA_SUSPEND, XA_NEXT, XA_PREV, XA_EXIT;
-
-
-static XErrorHandler old_handler = 0;
-static Bool got_badwindow = False;
+static Bool error_handler_hit_p = False;
static int
-BadWindow_ehandler (Display *dpy, XErrorEvent *error)
+ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
{
- if (error->error_code == BadWindow)
- {
- got_badwindow = True;
- return 0;
- }
- else
- {
- fprintf (stderr, "%s: ", progname);
- if (!old_handler) abort();
- return (*old_handler) (dpy, error);
- }
+ error_handler_hit_p = True;
+ return 0;
}
-
-static Window
-find_screensaver_window (Display *dpy, char **version)
+/* See comment in xscreensaver.c for why this is here instead of there.
+ */
+static void
+reset_dpms_timer (Display *dpy)
{
- int i;
- Window root = RootWindowOfScreen (DefaultScreenOfDisplay (dpy));
- Window root2, parent, *kids;
- unsigned int nkids;
-
- if (version) *version = 0;
-
- if (! XQueryTree (dpy, root, &root2, &parent, &kids, &nkids))
- abort ();
- if (root != root2)
- abort ();
- if (parent)
- abort ();
- if (! (kids && nkids))
- return 0;
- for (i = 0; i < nkids; i++)
- {
- Atom type;
- int format;
- unsigned long nitems, bytesafter;
- unsigned char *v;
- int status;
-
- /* We're walking the list of root-level windows and trying to find
- the one that has a particular property on it. We need to trap
- BadWindows errors while doing this, because it's possible that
- some random window might get deleted in the meantime. (That
- window won't have been the one we're looking for.)
- */
- XSync (dpy, False);
- if (old_handler) abort();
- got_badwindow = False;
- old_handler = XSetErrorHandler (BadWindow_ehandler);
- status = XGetWindowProperty (dpy, kids[i],
- XA_SCREENSAVER_VERSION,
- 0, 200, False, XA_STRING,
- &type, &format, &nitems, &bytesafter,
- &v);
- XSync (dpy, False);
- XSetErrorHandler (old_handler);
- old_handler = 0;
+# ifdef HAVE_DPMS_EXTENSION
- if (got_badwindow)
- {
- status = BadWindow;
- got_badwindow = False;
- }
+ XErrorHandler old_handler;
+ int event_number, error_number;
+ BOOL enabled = False;
+ CARD16 power = 0;
- if (status == Success && type != None)
- {
- Window ret = kids[i];
- if (version)
- *version = (char *) v;
- XFree (kids);
- return ret;
- }
- }
+ XSync (dpy, False);
+ error_handler_hit_p = False;
+ old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
- if (kids) XFree (kids);
- return 0;
+ if (! DPMSQueryExtension (dpy, &event_number, &error_number))
+ goto DONE;
+ if (! DPMSCapable (dpy))
+ goto DONE;
+ if (! DPMSInfo (dpy, &power, &enabled))
+ goto DONE;
+ if (!enabled)
+ goto DONE;
+
+ /* Do this even if power == DPMSModeOn to reset the timer */
+ DPMSForceLevel (dpy, DPMSModeOn);
+
+ DONE:
+ XSync (dpy, False);
+ XSetErrorHandler (old_handler);
+
+# endif /* HAVE_DPMS_EXTENSION */
}
@@ -239,7 +198,7 @@ send_xscreensaver_command (Display *dpy, Atom command, long arg,
if (data) free (data);
fprintf (stdout, "\n");
fflush (stdout);
- fprintf (stderr, "bad status format on root window.\n");
+ fprintf (stderr, "bad status format on root window\n");
status = -1;
goto DONE;
}
@@ -301,7 +260,7 @@ send_xscreensaver_command (Display *dpy, Atom command, long arg,
if (dataP) XFree (dataP);
fprintf (stdout, "\n");
fflush (stdout);
- fprintf (stderr, "no saver status on root window.\n");
+ fprintf (stderr, "no saver status on root window\n");
status = -1;
goto DONE;
}
@@ -316,7 +275,6 @@ send_xscreensaver_command (Display *dpy, Atom command, long arg,
XEvent event;
long arg1 = arg;
long arg2 = 0;
-
if (arg < 0)
abort();
else if (arg == 0 && command == XA_SELECT)
@@ -327,6 +285,9 @@ send_xscreensaver_command (Display *dpy, Atom command, long arg,
arg2 = arg; /* since it didn't use to take an argument. */
}
+ if (command == XA_DEACTIVATE)
+ reset_dpms_timer (dpy);
+
event.xany.type = ClientMessage;
event.xclient.display = dpy;
event.xclient.window = window;
@@ -336,10 +297,11 @@ send_xscreensaver_command (Display *dpy, Atom command, long arg,
event.xclient.data.l[0] = (long) command;
event.xclient.data.l[1] = arg1;
event.xclient.data.l[2] = arg2;
- if (! XSendEvent (dpy, window, False, 0L, &event))
+
+ if (! XSendEvent (dpy, window, False, PropertyChangeMask, &event))
{
- sprintf (err, "XSendEvent(dpy, 0x%x ...) failed.\n",
- (unsigned int) window);
+ sprintf (err, "XSendEvent(dpy, 0x%x ...) failed\n",
+ (unsigned int) window);
if (error_ret)
*error_ret = strdup (err);
else
@@ -416,10 +378,11 @@ xscreensaver_command_response (Display *dpy, Window window,
int format;
unsigned long nitems, bytesafter;
unsigned char *msg = 0;
+ XErrorHandler old_handler;
XSync (dpy, False);
- if (old_handler) abort();
- old_handler = XSetErrorHandler (BadWindow_ehandler);
+ error_handler_hit_p = False;
+ old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
st2 = XGetWindowProperty (dpy, window,
XA_SCREENSAVER_RESPONSE,
0, 1024, True,
@@ -428,9 +391,8 @@ xscreensaver_command_response (Display *dpy, Window window,
&msg);
XSync (dpy, False);
XSetErrorHandler (old_handler);
- old_handler = 0;
- if (got_badwindow)
+ if (error_handler_hit_p)
{
if (exiting_p)
return 0;
@@ -491,109 +453,87 @@ xscreensaver_command_response (Display *dpy, Window window,
}
-/* Wait until the window has been mapped, blanking the screen.
+/* Wait until xscreensaver says the screen is blanked.
Catches errors, times out after a few seconds.
*/
static int
-xscreensaver_command_wait_for_blank (Display *dpy, Window window,
+xscreensaver_command_wait_for_blank (Display *dpy,
Bool verbose_p, char **error_ret)
{
+ Window w = RootWindow (dpy, 0); /* always screen 0 */
time_t start = time((time_t*)0);
int max = 10;
char err[2048];
- Status status = -1;
-
while (1)
{
- XWindowAttributes xgwa;
- xgwa.map_state = IsUnmapped;
+ Atom type;
+ int format;
+ unsigned long nitems, bytesafter;
+ unsigned char *dataP = 0;
+ time_t now;
+ struct timeval tv;
- if (!window)
- got_badwindow = True;
- else
+ /* Wait until the status property on the root window changes to
+ BLANK or LOCKED. */
+ if (XGetWindowProperty (dpy, w,
+ XA_SCREENSAVER_STATUS,
+ 0, 999, False, XA_INTEGER,
+ &type, &format, &nitems, &bytesafter,
+ &dataP)
+ == Success
+ && type == XA_INTEGER
+ && nitems >= 3
+ && dataP)
{
- XSync (dpy, False);
- if (old_handler) abort();
- got_badwindow = False;
- old_handler = XSetErrorHandler (BadWindow_ehandler);
- status = XGetWindowAttributes (dpy, window, &xgwa);
- XSync (dpy, False);
- XSetErrorHandler (old_handler);
- old_handler = 0;
- }
+ Atom state = ((Atom *) dataP)[0];
- if (got_badwindow)
- {
- /* If we got a BadWindow, it might be that in the course of
- activating, xscreensaver had to destroy and re-create the
- window to get one with the proper Visual. So wait for a
- new window to come into existence.
- */
- if (window && verbose_p > 1)
- fprintf (stderr,
- "%s: BadWindow 0x%08x waiting for screen to blank\n",
- progname, (unsigned int) window);
- window = find_screensaver_window (dpy, 0);
- if (window && verbose_p > 1)
- fprintf (stderr, "%s: new window is 0x%08x.\n",
- progname, (unsigned int) window);
- got_badwindow = False;
+ if (verbose_p > 1)
+ {
+ PROP32 *status = (PROP32 *) dataP;
+ int i;
+ fprintf (stderr, "%s: read status property: 0x%lx: %s", progname,
+ (unsigned long) w,
+ (status[0] == XA_LOCK ? "LOCK" :
+ status[0] == XA_BLANK ? "BLANK" :
+ status[0] == 0 ? "0" : "???"));
+ for (i = 1; i < nitems; i++)
+ fprintf (stderr, ", %lu", status[i]);
+ fprintf (stderr, "\n");
+ }
+
+ if (state == XA_BLANK || state == XA_LOCK)
+ {
+ if (verbose_p > 1)
+ fprintf (stderr, "%s: screen blanked\n", progname);
+ break;
+ }
}
- else if (status == 0)
+
+ now = time ((time_t *) 0);
+ if (now >= start + max)
{
- sprintf (err, "error on 0x%08x waiting for screen to blank",
- (unsigned int) window);
+ strcpy (err, "Timed out waiting for screen to blank");
if (error_ret)
*error_ret = strdup (err);
else
fprintf (stderr, "%s: %s\n", progname, err);
return -1;
}
- else if (xgwa.map_state == IsViewable)
+ else if (verbose_p == 1 && now > start + 3)
{
- if (verbose_p)
- fprintf (stderr, "%s: window 0x%08x mapped.\n",
- progname, (unsigned int) window);
- return 0;
+ fprintf (stderr, "%s: waiting for status change\n", progname);
+ verbose_p++;
}
- else
- {
- time_t now = time((time_t*)0);
- if (now >= start + max)
- {
- sprintf (err, "Timed out waiting for screen to blank on 0x%08x",
- (unsigned int) window);
- if (error_ret)
- *error_ret = strdup (err);
- else
- fprintf (stderr, "%s: %s\n", progname, err);
- return -1;
- }
- else if (verbose_p && now > start+1)
- {
- fprintf (stderr, "%s: waiting for window 0x%08x to map\n",
- progname, (unsigned int) window);
- }
- }
-
-# if defined(HAVE_SELECT)
- {
- struct timeval tv;
- tv.tv_sec = 0;
- tv.tv_usec = 1000000L / 10;
- select (0, 0, 0, 0, &tv);
- }
-# else
- sleep (1);
-# endif
+ tv.tv_sec = 0;
+ tv.tv_usec = 1000000L / 10;
+ select (0, 0, 0, 0, &tv);
}
return 0;
}
-
int
xscreensaver_command (Display *dpy, Atom command, long arg, Bool verbose_p,
char **error_ret)
@@ -614,8 +554,7 @@ xscreensaver_command (Display *dpy, Atom command, long arg, Bool verbose_p,
command == XA_NEXT ||
command == XA_PREV ||
command == XA_SELECT))
- status = xscreensaver_command_wait_for_blank (dpy, w, verbose_p,
- error_ret);
+ status = xscreensaver_command_wait_for_blank (dpy, verbose_p, error_ret);
fflush (stdout);
fflush (stderr);
@@ -648,7 +587,7 @@ server_xscreensaver_version (Display *dpy,
if (version_ret)
{
unsigned char *v = 0;
- XGetWindowProperty (dpy, window, XA_SCREENSAVER_VERSION, 0, 1,
+ XGetWindowProperty (dpy, window, XA_SCREENSAVER_VERSION, 0, 100,
False, XA_STRING, &type, &format, &nitems,
&bytesafter, &v);
if (v)
@@ -681,11 +620,12 @@ server_xscreensaver_version (Display *dpy,
{
char *o = 0, *p = 0, *c = 0;
o = strchr ((char *) id, '(');
- if (o) p = strchr (o, '@');
+ if (o) p = strrchr (o, '@');
if (p) c = strchr (p, ')');
if (c)
{
- /* found ID of the form "1234 (user@host)". */
+ /* found ID of the form "1234 (user@host)"
+ or the weirder "1234 (user@crap@host)". */
user = o+1;
host = p+1;
*p = 0;
@@ -695,12 +635,16 @@ server_xscreensaver_version (Display *dpy,
}
- if (user && *user && *user != '?')
+ if (!user_ret)
+ ;
+ else if (user && *user && *user != '?')
*user_ret = strdup (user);
else
*user_ret = 0;
- if (host && *host && *host != '?')
+ if (!host_ret)
+ ;
+ else if (host && *host && *host != '?')
*host_ret = strdup (host);
else
*host_ret = 0;
diff --git a/driver/remote.h b/driver/remote.h
index e1db351..6b1763f 100644
--- a/driver/remote.h
+++ b/driver/remote.h
@@ -1,5 +1,4 @@
-/* xscreensaver-command, Copyright (c) 1991-1998
- * by Jamie Zawinski <jwz@jwz.org>
+/* xscreensaver-command, Copyright © 1991-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
diff --git a/driver/screens.c b/driver/screens.c
index f319c96..dc87e51 100644
--- a/driver/screens.c
+++ b/driver/screens.c
@@ -1,5 +1,5 @@
/* screens.c --- dealing with RANDR, Xinerama, and VidMode Viewports.
- * xscreensaver, Copyright (c) 1991-2008 Jamie Zawinski <jwz@jwz.org>
+ * xscreensaver, Copyright © 1991-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
@@ -11,115 +11,114 @@
*/
/* There are a bunch of different mechanisms for multiple monitors
- * available in X. XScreenSaver needs to care about this for two
- * reasons: first, to ensure that all visible areas go black; and
- * second, so that the windows of screen savers exactly fill the
- * glass of each monitor (instead of one saver spanning multiple
- * monitors, or a monitor displaying only a sub-rectangle of the
- * screen saver.)
+ * available in X. XScreenSaver needs to care about this for two reasons:
+ * first, to ensure that all visible areas go black; and second, so that
+ * the windows of screen savers exactly fill the glass of each monitor
+ * (instead of one saver spanning multiple monitors, or a monitor
+ * displaying only a sub-rectangle of the screen saver.)
*
* 1) Multi-screen:
*
- * This is the original way. Each monitor gets its own display
- * number. :0.0 is the first one, :0.1 is the next, etc. The
- * value of $DISPLAY determines which screen windows open on by
- * default. A single app can open windows on multiple screens
- * with the same display connection, but windows cannot be moved
- * from one screen to another. The mouse can be moved from one
- * screen to another, though. Screens may be different depths
- * (e.g., one can be TrueColor and one can be PseudoColor.)
- * Screens cannot be resized or moved without restarting X.
+ * This is the original way. Each monitor gets its own display number.
+ * ":0.0" is the first one, ":0.1" is the next, and so on. The value
+ * of $DISPLAY determines which screen windows open on by default. A
+ * single app can open windows on multiple screens with the same
+ * display connection, but windows cannot be moved from one screen to
+ * another. The mouse can be moved from one screen to another, though.
+ * Screens may be different depths (e.g., one can be TrueColor and one
+ * can be PseudoColor.) Screens cannot be resized or moved without
+ * restarting X.
*
- * Everyone hates this way of doing things because of the
- * inability to move a window from one screen to another without
- * restarting the application.
+ * Everyone hates this way of doing things because of the inability to
+ * move a window from one screen to another without restarting the
+ * application.
*
* 2) Xinerama:
*
- * There is a single giant root window that spans all the
- * monitors. All monitors are the same depth, and windows can be
- * moved around. Applications can learn which rectangles are
- * actually visible on monitors by querying the Xinerama server
- * extension. (If you don't do that, you end up with dialog
- * boxes that try to appear in the middle of the screen actually
- * spanning the gap between two monitors.)
+ * There is a single giant root window that spans all the monitors.
+ * All monitors are the same depth, and windows can be moved around.
+ * Applications can learn which rectangles are actually visible on
+ * monitors by querying the Xinerama server extension. (If you don't
+ * do that, you end up with dialog boxes that try to appear in the
+ * middle of the screen actually spanning the gap between two
+ * monitors.)
*
- * Xinerama doesn't work with DRI, which means that if you use
- * it, you lose hardware acceleration on OpenGL programs. Also,
- * screens can't be resized or moved without restarting X.
+ * Xinerama didn't? work with DRI, which means that Xinerama precluded
+ * hardware acceleration in OpenGL programs. Also, screens couldn't
+ * be resized or moved without restarting X.
*
* 3) Vidmode Viewports:
*
+ * No longer supported as of XScreenSaver 6.
+ *
* With this extension, the root window can be bigger than the
- * monitor. Moving the mouse near the edges of the screen
- * scrolls around, like a pan-and-scan movie. There can also be
- * a hot key for changing the monitor's resolution (zooming
- * in/out).
+ * monitor. Moving the mouse near the edges of the screen scrolls
+ * around, like a pan-and-scan movie. There was also a hot-key for
+ * changing the monitor's resolution (zooming in/out).
*
- * Trying to combine this with Xinerama crashes the server, so
- * you can only use this if you have only a single screen, or are
- * in old-multi-screen mode.
+ * Trying to combine this with Xinerama crashes the server, so you
+ * could only use this if you had only a single screen, or were in old
+ * multi-screen mode.
*
- * Also, half the time it doesn't work at all: it tends to lie
- * about the size of the rectangle in use.
+ * Also, half the time it didn't work at all: it tended to lie about
+ * the size of the rectangle in use.
*
* 4) RANDR 1.0:
*
* The first version of the "Resize and Rotate" extension let you
* change the resolution of a screen on the fly. The root window
* would actually resize. However, it was also incompatible with
- * Xinerama (did it crash, or just do nothing? I can't remember)
- * so you needed to be in single-screen or old multi-screen mode.
- * I believe RANDR could co-exist with Vidmode Viewports, but I'm
- * not sure.
+ * Xinerama (did it crash, or just do nothing? I can't remember) so
+ * you needed to be in single-screen or old multi-screen mode. I
+ * believe RANDR could co-exist with Vidmode Viewports, but I'm not
+ * sure.
*
* 5) RANDR 1.2:
*
* Finally, RANDR added the functionality of Xinerama, plus some.
* Each X screen (in the sense of #1, "multi-screen") can have a
- * number of sub-rectangles that are displayed on monitors, and
- * each of those sub-rectangles can be displayed on more than one
- * monitor. So it's possible (I think) to have a hybrid of
- * multi-screen and Xinerama (e.g., to have two monitors running
- * in one depth, and three monitors running in another?)
- * Typically though, there will be a single X screen, with
- * Xinerama-like division of that large root window onto multiple
- * monitors. Also everything's dynamic: monitors can be added,
- * removed, and resized at runtime.
- *
- * I believe that as of RANDR 1.2, the Xinerama extension still
- * exists but only as a compatiblity layer: it's actually
- * returning data from the RANDR extension.
- *
- * Though RANDR 1.2 allows the same image to be cloned onto more
- * than one monitor, and also allows one monitor to show a
- * subsection of something on another monitor (e.g., the
- * rectangles can be enclosed or overlap). Since there's no way
- * to put seperate savers on those duplicated-or-overlapping
- * monitors, xscreensaver just ignores them (which allows them to
- * display duplicates or overlaps).
+ * number of sub-rectangles that are displayed on monitors, and each
+ * of those sub-rectangles can be displayed on more than one monitor.
+ * So it's possible (I think) to have a hybrid of multi-screen and
+ * Xinerama (e.g., to have two monitors running in one depth, and
+ * three monitors running in another?) Typically though, there will
+ * be a single X screen with one giant root window underlying the
+ * rectangles of multiple monitors. Also everything is dynamic:
+ * monitors can be added, removed, and resized at runtime, with
+ * notification events.
*
- * 5a) Nvidia fucks it up:
+ * RANDR rectangles can overlap, meaning one monitor can mirror
+ * another, or show a sub-rectangle of another, or just overlap in
+ * strange ways. The proper way to respond to weird layouts is... not
+ * always obvious.
*
- * Nvidia drivers as of Aug 2008 running in "TwinView" mode
- * apparently report correct screen geometry via Xinerama, but
- * report one giant screen via RANDR. The response from the
- * nvidia developers is, "we don't support RANDR, use Xinerama
- * instead." Which is a seriously lame answer. So, xscreensaver
- * has to query *both* extensions, and make a guess as to which
- * is to be believed.
+ * Also sometimes RANDR says stupid shit like, "You have one screen,
+ * and it has no available sizes or orientations."
*
- * 5b) Also sometimes RANDR says stupid shit like, "You have one
- * screen, and it has no available orientations or sizes."
+ * Sometimes RANDR and Xinerama report the same info, and sometimes
+ * not, so we look at both and see which looks most plausible.
*
+ * Also, Nvidia fucked it up: their drivers that were popular in 2008,
+ * when running in "TwinView" mode, reported correct sizes via
+ * Xinerama, but reported one giant screen via RANDR. Nvidia's
+ * response was, "We don't support RANDR, use Xinerama instead", which
+ * is another reason that XScreenSaver historically had to query both
+ * extensions and make a guess. Maybe this is no longer necessary.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
#include <X11/Xlib.h>
+#ifndef HAVE_RANDR_12
+# undef HAVE_RANDR /* RANDR 1.1 is no longer supported */
+#endif
+
#ifdef HAVE_RANDR
# include <X11/extensions/Xrandr.h>
#endif /* HAVE_RANDR */
@@ -132,37 +131,14 @@
# include <X11/extensions/xf86vmode.h>
#endif /* HAVE_XF86VMODE */
-/* This file doesn't need the Xt headers, so stub these types out... */
-#undef XtPointer
-#define XtAppContext void*
-#define XrmDatabase void*
-#define XtIntervalId void*
-#define XtPointer void*
-#define Widget void*
-
-#include "xscreensaver.h"
-#include "visual.h"
-
-
-typedef enum { S_SANE, S_ENCLOSED, S_DUPLICATE, S_OVERLAP,
- S_OFFSCREEN, S_DISABLED } monitor_sanity;
-
-/* 'typedef monitor' is in types.h */
-struct _monitor {
- int id;
- char *desc;
- Screen *screen;
- int x, y, width, height;
- monitor_sanity sanity; /* I'm not crazy you're the one who's crazy */
- int enemy; /* which monitor it overlaps or duplicates */
- char *err; /* msg to print at appropriate later time;
- exists only on monitor #0. */
-};
+#include "blurb.h"
+#include "screens.h"
-static Bool layouts_differ_p (monitor **a, monitor **b);
+#undef countof
+#define countof(x) (sizeof((x))/sizeof((*x)))
-static void
+void
free_monitors (monitor **monitors)
{
monitor **m2 = monitors;
@@ -231,122 +207,6 @@ xinerama_scan_monitors (Display *dpy, char **errP)
#endif /* HAVE_XINERAMA */
-#ifdef HAVE_XF86VMODE
-
-static monitor **
-vidmode_scan_monitors (Display *dpy, char **errP)
-{
- int event, error, nscreens, i;
- monitor **monitors;
-
- /* Note that XF86VidModeGetViewPort() tends to be full of lies on laptops
- that have a docking station or external monitor that runs in a different
- resolution than the laptop's screen:
-
- http://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=81593
- http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=208417
- http://bugs.xfree86.org/show_bug.cgi?id=421
-
- Presumably this is fixed by using RANDR instead of VidMode.
- */
-
-# ifdef HAVE_XINERAMA
- /* Attempts to use the VidMode extension when the Xinerama extension is
- active can result in a server crash! Yay! */
- if (XQueryExtension (dpy, "XINERAMA", &error, &event, &error))
- return 0;
-# endif /* !HAVE_XINERAMA */
-
- if (! XF86VidModeQueryExtension (dpy, &event, &error))
- return 0;
-
- nscreens = ScreenCount (dpy);
- monitors = (monitor **) calloc (nscreens + 1, sizeof(*monitors));
- if (!monitors) return 0;
-
- for (i = 0; i < nscreens; i++)
- {
- monitor *m = (monitor *) calloc (1, sizeof (monitor));
- XF86VidModeModeLine ml;
- int dot;
- Screen *screen = ScreenOfDisplay (dpy, i);
-
- monitors[i] = m;
- m->id = i;
- m->screen = screen;
-
- if (! safe_XF86VidModeGetViewPort (dpy, i, &m->x, &m->y))
- m->x = m->y = -1;
-
- if (XF86VidModeGetModeLine (dpy, i, &dot, &ml))
- {
- m->width = ml.hdisplay;
- m->height = ml.vdisplay;
- }
-
- /* On a system that has VidMode but does not have RANDR, and that has
- "Option Rotate" set, WidthOfScreen/HeightOfScreen are the rotated
- size, but XF86VidModeModeLine contains the unrotated size.
- Maybe there's something in 'flags' that indicates this?
- Or, we can just notice that the aspect ratios are inverted:
- */
- if (m->width > 0 &&
- m->height > 0 &&
- ((m->width > m->height) !=
- (WidthOfScreen(screen) > HeightOfScreen(screen))))
- {
- int swap = m->width;
- m->width = m->height;
- m->height = swap;
- }
-
-
- /* Apparently, though the server stores the X position in increments of
- 1 pixel, it will only make changes to the *display* in some other
- increment. With XF86_SVGA on a Thinkpad, the display only updates
- in multiples of 8 pixels when in 8-bit mode, and in multiples of 4
- pixels in 16-bit mode. I don't know what it does in 24- and 32-bit
- mode, because I don't have enough video memory to find out.
-
- I consider it a bug that XF86VidModeGetViewPort() is telling me the
- server's *target* scroll position rather than the server's *actual*
- scroll position. David Dawes agrees, and says they may fix this in
- XFree86 4.0, but it's nontrivial.
-
- He also confirms that this behavior is server-dependent, so the
- actual scroll position cannot be reliably determined by the client.
- So... that means the only solution is to provide a ``sandbox''
- around the blackout window -- we make the window be up to N pixels
- larger than the viewport on both the left and right sides. That
- means some part of the outer edges of each hack might not be
- visible, but screw it.
-
- I'm going to guess that 16 pixels is enough, and that the Y dimension
- doesn't have this problem.
-
- The drawback of doing this, of course, is that some of the screenhacks
- will still look pretty stupid -- for example, "slidescreen" will cut
- off the left and right edges of the grid, etc.
- */
-# define FUDGE 16
- if (m->x > 0 && m->x < m->width - ml.hdisplay)
- {
- /* Not at left edge or right edge:
- Round X position down to next lower multiple of FUDGE.
- Increase width by 2*FUDGE in case some server rounds up.
- */
- m->x = ((m->x - 1) / FUDGE) * FUDGE;
- m->width += (FUDGE * 2);
- }
-# undef FUDGE
- }
-
- return monitors;
-}
-
-#endif /* HAVE_XF86VMODE */
-
-
#ifdef HAVE_RANDR
static monitor **
@@ -354,7 +214,6 @@ randr_scan_monitors (Display *dpy, char **errP)
{
int event, error, major, minor, nscreens, i, j;
monitor **monitors;
- Bool new_randr_p = False;
if (! XRRQueryExtension (dpy, &event, &error))
return 0;
@@ -362,30 +221,17 @@ randr_scan_monitors (Display *dpy, char **errP)
if (! XRRQueryVersion (dpy, &major, &minor))
return 0;
- if (major <= 0) /* Protocol was still in flux back then -- fuck it. */
- return 0;
-
-# ifdef HAVE_RANDR_12
- new_randr_p = (major > 1 || (major == 1 && minor >= 2));
-# endif
+ if (! (major > 1 || (major == 1 && minor >= 2)))
+ return 0; /* 1.2 ir newer is required */
- if (! new_randr_p)
- /* RANDR 1.0 -- no Xinerama-like virtual screens. */
- nscreens = ScreenCount (dpy);
- else /* RANDR 1.2 or newer -- built-in Xinerama */
+ /* Add up the virtual screens on each X screen. */
+ nscreens = 0;
+ for (i = 0; i < ScreenCount (dpy); i++)
{
-# ifdef HAVE_RANDR_12
- int xsc = ScreenCount (dpy);
- nscreens = 0;
- /* Add up the virtual screens on each X screen. */
- for (i = 0; i < xsc; i++)
- {
- XRRScreenResources *res =
- XRRGetScreenResources (dpy, RootWindow (dpy, i));
- nscreens += res->noutput;
- XRRFreeScreenResources (res);
- }
-# endif /* HAVE_RANDR_12 */
+ XRRScreenResources *res =
+ XRRGetScreenResources (dpy, RootWindow (dpy, i));
+ nscreens += res->noutput;
+ XRRFreeScreenResources (res);
}
if (nscreens <= 0)
@@ -401,88 +247,43 @@ randr_scan_monitors (Display *dpy, char **errP)
for (i = 0, j = 0; i < ScreenCount (dpy); i++)
{
Screen *screen = ScreenOfDisplay (dpy, i);
-
- if (! new_randr_p) /* RANDR 1.0 */
+ int k;
+ XRRScreenResources *res =
+ XRRGetScreenResources (dpy, RootWindowOfScreen (screen));
+ for (k = 0; k < res->noutput; k++, j++)
{
- XRRScreenConfiguration *rrc;
monitor *m = (monitor *) calloc (1, sizeof (monitor));
- monitors[i] = m;
+ XRROutputInfo *rroi = XRRGetOutputInfo (dpy, res,
+ res->outputs[k]);
+ RRCrtc crtc = (rroi->crtc ? rroi->crtc :
+ rroi->ncrtc ? rroi->crtcs[0] : 0);
+ XRRCrtcInfo *crtci = (crtc ? XRRGetCrtcInfo(dpy, res, crtc) : 0);
+
+ monitors[j] = m;
m->screen = screen;
- m->id = i;
+ m->id = (i * 1000) + j;
+ m->desc = (rroi->name ? strdup (rroi->name) : 0);
- rrc = XRRGetScreenInfo (dpy, RootWindowOfScreen (screen));
- if (rrc)
+ if (crtci)
{
- SizeID size = -1;
- Rotation rot = ~0;
- XRRScreenSize *rrsizes;
- int nsizes = 0;
-
- size = XRRConfigCurrentConfiguration (rrc, &rot);
- rrsizes = XRRConfigSizes (rrc, &nsizes);
-
- if (nsizes <= 0) /* WTF? Shouldn't happen but does. */
- {
- m->width = DisplayWidth (dpy, i);
- m->height = DisplayHeight (dpy, i);
- }
- else if (rot & (RR_Rotate_90|RR_Rotate_270))
- {
- m->width = rrsizes[size].height;
- m->height = rrsizes[size].width;
- }
- else
- {
- m->width = rrsizes[size].width;
- m->height = rrsizes[size].height;
- }
-
- /* don't free 'rrsizes' */
- XRRFreeScreenConfigInfo (rrc);
+ /* Note: if the screen is rotated, XRRConfigSizes contains
+ the unrotated WxH, but XRRCrtcInfo contains rotated HxW.
+ */
+ m->x = crtci->x;
+ m->y = crtci->y;
+ m->width = crtci->width;
+ m->height = crtci->height;
}
- }
- else /* RANDR 1.2 or newer */
- {
-# ifdef HAVE_RANDR_12
- int k;
- XRRScreenResources *res =
- XRRGetScreenResources (dpy, RootWindowOfScreen (screen));
- for (k = 0; k < res->noutput; k++, j++)
- {
- monitor *m = (monitor *) calloc (1, sizeof (monitor));
- XRROutputInfo *rroi = XRRGetOutputInfo (dpy, res,
- res->outputs[k]);
- RRCrtc crtc = (rroi->crtc ? rroi->crtc :
- rroi->ncrtc ? rroi->crtcs[0] : 0);
- XRRCrtcInfo *crtci = (crtc ? XRRGetCrtcInfo(dpy, res, crtc) : 0);
-
- monitors[j] = m;
- m->screen = screen;
- m->id = (i * 1000) + j;
- m->desc = (rroi->name ? strdup (rroi->name) : 0);
-
- if (crtci)
- {
- /* Note: if the screen is rotated, XRRConfigSizes contains
- the unrotated WxH, but XRRCrtcInfo contains rotated HxW.
- */
- m->x = crtci->x;
- m->y = crtci->y;
- m->width = crtci->width;
- m->height = crtci->height;
- }
- if (rroi->connection == RR_Disconnected)
- m->sanity = S_DISABLED;
- /* #### do the same for RR_UnknownConnection? */
+ if (rroi->connection == RR_Disconnected)
+ m->sanity = S_DISABLED;
+ /* #### do the same for RR_UnknownConnection? */
- if (crtci)
- XRRFreeCrtcInfo (crtci);
- XRRFreeOutputInfo (rroi);
- }
- XRRFreeScreenResources (res);
-# endif /* HAVE_RANDR_12 */
+ if (crtci)
+ XRRFreeCrtcInfo (crtci);
+ XRRFreeOutputInfo (rroi);
}
+ XRRFreeScreenResources (res);
}
/* Work around more fucking brain damage. */
@@ -566,7 +367,7 @@ randr_versus_xinerama_fight (Display *dpy, monitor **randr_monitors,
if (!xinerama_monitors)
return randr_monitors;
- if (! layouts_differ_p (randr_monitors, xinerama_monitors))
+ if (! monitor_layouts_differ_p (randr_monitors, xinerama_monitors))
{
free_monitors (xinerama_monitors);
return randr_monitors;
@@ -582,9 +383,8 @@ randr_versus_xinerama_fight (Display *dpy, monitor **randr_monitors,
}
else
{
- *errP = append (*errP,
- "WARNING: RANDR and Xinerama report different\n"
- " screen layouts! Believing RANDR.");
+ *errP = append (*errP, /* This is "normal" now, I guess. */
+ "RANDR and Xinerama report different screen layouts");
free_monitors (xinerama_monitors);
return randr_monitors;
}
@@ -642,7 +442,6 @@ debug_scan_monitors (Display *dpy, char **errP)
index = (index+1) % countof(geoms);
return monitors;
}
-
#endif /* DEBUG_MULTISCREEN */
@@ -683,42 +482,36 @@ quadruple (monitor **monitors, Bool debug_p, char **errP)
#endif /* QUAD_MODE */
-static monitor **
-scan_monitors (saver_info *si)
+monitor **
+scan_monitors (Display *dpy)
{
- saver_preferences *p = &si->prefs;
monitor **monitors = 0;
char *err = 0;
# ifdef DEBUG_MULTISCREEN
- if (! monitors) monitors = debug_scan_monitors (si->dpy, &err);
+ if (! monitors) monitors = debug_scan_monitors (dpy, &err);
# endif
# ifdef HAVE_RANDR
- if (! p->getviewport_full_of_lies_p)
- if (! monitors) monitors = randr_scan_monitors (si->dpy, &err);
+ if (! monitors) monitors = randr_scan_monitors (dpy, &err);
# ifdef HAVE_XINERAMA
- monitors = randr_versus_xinerama_fight (si->dpy, monitors, &err);
+ monitors = randr_versus_xinerama_fight (dpy, monitors, &err);
# endif
# endif /* HAVE_RANDR */
-# ifdef HAVE_XF86VMODE
- if (! monitors) monitors = vidmode_scan_monitors (si->dpy, &err);
-# endif
-
# ifdef HAVE_XINERAMA
- if (! monitors) monitors = xinerama_scan_monitors (si->dpy, &err);
+ if (! monitors) monitors = xinerama_scan_monitors (dpy, &err);
# endif
- if (! monitors) monitors = basic_scan_monitors (si->dpy, &err);
+ if (! monitors) monitors = basic_scan_monitors (dpy, &err);
# ifdef QUAD_MODE
if (p->quad_p)
monitors = quadruple (monitors, p->debug_p, &err);
# endif
- if (monitors && err) monitors[0]->err = err;
+ if (monitors && *monitors && err) monitors[0]->err = err;
return monitors;
}
@@ -778,7 +571,7 @@ plausible_aspect_ratio_p (monitor **monitors)
/* Mark the ones that overlap, etc.
*/
-static void
+void
check_monitor_sanity (monitor **monitors)
{
int i, j, count = 0;
@@ -862,8 +655,8 @@ check_monitor_sanity (monitor **monitors)
}
-static Bool
-layouts_differ_p (monitor **a, monitor **b)
+Bool
+monitor_layouts_differ_p (monitor **a, monitor **b)
{
if (!a || !b) return True;
while (1)
@@ -886,10 +679,21 @@ layouts_differ_p (monitor **a, monitor **b)
}
+static int
+screen_number (Screen *screen)
+{
+ Display *dpy = DisplayOfScreen (screen);
+ int i;
+ for (i = 0; i < ScreenCount (dpy); i++)
+ if (ScreenOfDisplay (dpy, i) == screen)
+ return i;
+ return 0;
+}
+
+
void
-describe_monitor_layout (saver_info *si)
+describe_monitor_layout (monitor **monitors)
{
- monitor **monitors = si->monitor_layout;
int count = 0;
int good_count = 0;
int bad_count = 0;
@@ -904,7 +708,7 @@ describe_monitor_layout (saver_info *si)
count++;
}
- if (monitors[0]->err) /* deferred error msg */
+ if (monitors && *monitors && monitors[0]->err) /* deferred error msg */
{
char *token = strtok (monitors[0]->err, "\n");
while (token)
@@ -980,115 +784,3 @@ describe_monitor_layout (saver_info *si)
blurb());
}
}
-
-
-/* Synchronize the contents of si->ssi to the current state of the monitors.
- Doesn't change anything if nothing has changed; otherwise, alters and
- reuses existing saver_screen_info structs as much as possible.
- Returns True if anything changed.
- */
-Bool
-update_screen_layout (saver_info *si)
-{
- monitor **monitors = scan_monitors (si);
- int count = 0;
- int good_count = 0;
- int i, j;
- int seen_screens[100] = { 0, };
-
- if (! layouts_differ_p (monitors, si->monitor_layout))
- {
- free_monitors (monitors);
- return False;
- }
-
- free_monitors (si->monitor_layout);
- si->monitor_layout = monitors;
- check_monitor_sanity (si->monitor_layout);
-
- while (monitors[count])
- {
- if (monitors[count]->sanity == S_SANE)
- good_count++;
- count++;
- }
-
- if (si->ssi_count == 0)
- {
- si->ssi_count = 10;
- si->screens = (saver_screen_info *)
- calloc (sizeof(*si->screens), si->ssi_count);
- }
-
- if (si->ssi_count <= good_count)
- {
- si->ssi_count = good_count + 10;
- si->screens = (saver_screen_info *)
- realloc (si->screens, sizeof(*si->screens) * si->ssi_count);
- memset (si->screens + si->nscreens, 0,
- sizeof(*si->screens) * (si->ssi_count - si->nscreens));
- }
-
- if (! si->screens) abort();
-
- si->nscreens = good_count;
-
- /* Regenerate the list of GL visuals as needed. */
- if (si->best_gl_visuals)
- free (si->best_gl_visuals);
- si->best_gl_visuals = 0;
-
- for (i = 0, j = 0; i < count; i++)
- {
- monitor *m = monitors[i];
- saver_screen_info *ssi = &si->screens[j];
- Screen *old_screen = ssi->screen;
- int sn;
- if (monitors[i]->sanity != S_SANE) continue;
-
- ssi->global = si;
- ssi->number = j;
-
- sn = screen_number (m->screen);
- ssi->screen = m->screen;
- ssi->real_screen_number = sn;
- ssi->real_screen_p = (seen_screens[sn] == 0);
- seen_screens[sn]++;
-
- ssi->default_visual =
- get_visual_resource (ssi->screen, "visualID", "VisualID", False);
- ssi->current_visual = ssi->default_visual;
- ssi->current_depth = visual_depth (ssi->screen, ssi->current_visual);
-
- /* If the screen changed (or if this is the first time) we need
- a new toplevel shell for this screen's depth.
- */
- if (ssi->screen != old_screen)
- initialize_screen_root_widget (ssi);
-
- ssi->last_poll_mouse.root_x = -1;
- ssi->last_poll_mouse.root_y = -1;
-
- ssi->x = m->x;
- ssi->y = m->y;
- ssi->width = m->width;
- ssi->height = m->height;
-
-# ifndef DEBUG_MULTISCREEN
- {
- saver_preferences *p = &si->prefs;
- if (p->debug_p
-# ifdef QUAD_MODE
- && !p->quad_p
-# endif
- )
- ssi->width /= 2;
- }
-# endif
-
- j++;
- }
-
- si->default_screen = &si->screens[0];
- return True;
-}
diff --git a/driver/screens.h b/driver/screens.h
new file mode 100644
index 0000000..1e3501c
--- /dev/null
+++ b/driver/screens.h
@@ -0,0 +1,37 @@
+/* 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.
+ */
+
+#ifndef __XSCREENSAVER_SCREENS_H__
+#define __XSCREENSAVER_SCREENS_H__
+
+typedef struct _monitor monitor;
+
+typedef enum { S_SANE, S_ENCLOSED, S_DUPLICATE, S_OVERLAP,
+ S_OFFSCREEN, S_DISABLED } monitor_sanity;
+
+struct _monitor {
+ int id;
+ char *desc;
+ Screen *screen;
+ int x, y, width, height;
+ monitor_sanity sanity; /* I'm not crazy you're the one who's crazy */
+ int enemy; /* which monitor it overlaps or duplicates */
+ char *err; /* msg to print at appropriate later time;
+ exists only on monitor #0. */
+};
+
+extern monitor **scan_monitors (Display *);
+extern Bool monitor_layouts_differ_p (monitor **a, monitor **b);
+extern void free_monitors (monitor **monitors);
+extern void describe_monitor_layout (monitor **monitors);
+extern void check_monitor_sanity (monitor **monitors);
+
+#endif /* __XSCREENSAVER_SCREENS_H__ */
diff --git a/driver/screensaver-properties.desktop.in b/driver/screensaver-properties.desktop.in
index de42527..9cc17e6 100644
--- a/driver/screensaver-properties.desktop.in
+++ b/driver/screensaver-properties.desktop.in
@@ -1,5 +1,5 @@
[Desktop Entry]
-Exec=xscreensaver-demo
+Exec=xscreensaver-settings
Icon=xscreensaver
Terminal=false
_Name=Screensaver
diff --git a/driver/setuid.c b/driver/setuid.c
index 3ac78e4..164576f 100644
--- a/driver/setuid.c
+++ b/driver/setuid.c
@@ -1,5 +1,5 @@
/* setuid.c --- management of runtime privileges.
- * xscreensaver, Copyright (c) 1993-1998, 2005 Jamie Zawinski <jwz@jwz.org>
+ * 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
@@ -14,24 +14,20 @@
# include "config.h"
#endif
-#include <X11/Xlib.h> /* not used for much... */
-
-/* This file doesn't need the Xt headers, so stub these types out... */
-#undef XtPointer
-#define XtAppContext void*
-#define XrmDatabase void*
-#define XtIntervalId void*
-#define XtPointer void*
-#define Widget void*
-
-#include "xscreensaver.h"
-
-#ifndef EPERM
-#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
#endif
+#include <string.h>
#include <pwd.h> /* for getpwnam() and struct passwd */
#include <grp.h> /* for getgrgid() and struct group */
+#include <errno.h>
+
+#include "blurb.h"
+#include "auth.h"
+
static const char *
uid_gid_string (uid_t uid, gid_t gid)
@@ -49,31 +45,6 @@ uid_gid_string (uid_t uid, gid_t gid)
}
-void
-describe_uids (saver_info *si, FILE *out)
-{
- uid_t uid = getuid();
- gid_t gid = getgid();
- uid_t euid = geteuid();
- gid_t egid = getegid();
- char *s1 = strdup (uid_gid_string (uid, gid));
- char *s2 = strdup (uid_gid_string (euid, egid));
-
- if (si->orig_uid && *si->orig_uid &&
- (!!strcmp (si->orig_uid, s1) ||
- !!strcmp (si->orig_uid, s2)))
- fprintf (out, "%s: initial effective uid/gid was %s\n", blurb(),
- si->orig_uid);
-
- fprintf (out, "%s: running as %s", blurb(), s1);
- if (uid != euid || gid != egid)
- fprintf (out, "; effectively %s", s2);
- fprintf(out, "\n");
- free(s1);
- free(s2);
-}
-
-
/* Returns true if we need to call setgroups().
Without calling setgroups(), the process will retain any supplementary
@@ -113,7 +84,7 @@ setgroups_needed_p (uid_t target_group)
static int
-set_ids_by_number (uid_t uid, gid_t gid, char **message_ret)
+set_ids_by_number (uid_t uid, gid_t gid)
{
int uid_errno = 0;
int gid_errno = 0;
@@ -121,9 +92,6 @@ set_ids_by_number (uid_t uid, gid_t gid, char **message_ret)
struct passwd *p = getpwuid (uid);
struct group *g = getgrgid (gid);
- if (message_ret)
- *message_ret = 0;
-
/* Rumor has it that some implementations of of setuid() do nothing
when called with -1; therefore, if the "nobody" user has a uid of
-1, then that would be Really Bad. Rumor further has it that such
@@ -150,13 +118,12 @@ set_ids_by_number (uid_t uid, gid_t gid, char **message_ret)
if (uid_errno == 0 && gid_errno == 0 && sgs_errno == 0)
{
- static char buf [1024];
- sprintf (buf, "changed uid/gid to %.100s/%.100s (%ld/%ld).",
- (p && p->pw_name ? p->pw_name : "???"),
- (g && g->gr_name ? g->gr_name : "???"),
- (long) uid, (long) gid);
- if (message_ret)
- *message_ret = buf;
+ if (verbose_p)
+ fprintf (stderr, "%s: changed uid/gid to %.100s/%.100s (%ld/%ld)\n",
+ blurb(),
+ (p && p->pw_name ? p->pw_name : "???"),
+ (g && g->gr_name ? g->gr_name : "???"),
+ (long) uid, (long) gid);
return 0;
}
else
@@ -176,7 +143,7 @@ set_ids_by_number (uid_t uid, gid_t gid, char **message_ret)
else
{
errno = sgs_errno;
- perror(buf);
+ perror (buf);
}
fprintf (stderr, "%s: effective group list: ", blurb());
@@ -206,11 +173,11 @@ set_ids_by_number (uid_t uid, gid_t gid, char **message_ret)
(g && g->gr_name ? g->gr_name : "???"),
(long) gid);
if (gid_errno == -1)
- fprintf(stderr, "%s: unknown error\n", buf);
+ fprintf (stderr, "%s: unknown error\n", buf);
else
{
errno = gid_errno;
- perror(buf);
+ perror (buf);
}
}
@@ -241,121 +208,63 @@ set_ids_by_number (uid_t uid, gid_t gid, char **message_ret)
lock-mode.)
*** WARNING: DO NOT DISABLE ANY OF THE FOLLOWING CODE!
- If you do so, you will open a security hole. See the sections
- of the xscreensaver manual titled "LOCKING AND ROOT LOGINS",
- and "USING XDM".
+ If you do so, you will open a security hole.
+ Do not log in to an X11 desktop as root.
+ Log in as a normal users and 'sudo' as needed.
*/
void
-hack_uid (saver_info *si)
+disavow_privileges (void)
{
+ uid_t euid = geteuid();
+ gid_t egid = getegid();
+ uid_t uid = getuid();
+ gid_t gid = getgid();
+ struct passwd *p;
- /* Discard privileges, and set the effective user/group ids to the
- real user/group ids. That is, give up our "chmod +s" rights.
- */
- {
- uid_t euid = geteuid();
- gid_t egid = getegid();
- uid_t uid = getuid();
- gid_t gid = getgid();
-
- si->orig_uid = strdup (uid_gid_string (euid, egid));
-
- if (uid != euid || gid != egid)
- if (set_ids_by_number (uid, gid, &si->uid_message) != 0)
- saver_exit (si, 1, 0);
- }
-
-
- /* Locking can't work when running as root, because we have no way of
- knowing what the user id of the logged in user is (so we don't know
- whose password to prompt for.)
-
- *** WARNING: DO NOT DISABLE THIS CODE!
- If you do so, you will open a security hole. See the sections
- of the xscreensaver manual titled "LOCKING AND ROOT LOGINS",
- and "USING XDM".
- */
- if (getuid() == (uid_t) 0)
+ if (uid != euid || gid != egid)
{
- si->locking_disabled_p = True;
- si->nolock_reason = "running as root";
+ if (verbose_p)
+ fprintf (stderr, "%s: initial effective uid/gid was %s\n", blurb(),
+ uid_gid_string (euid, egid));
+ if (set_ids_by_number (uid, gid) != 0)
+ exit (1); /* already printed error */
}
-
- /* If we're running as root, switch to a safer user. This is above and
- beyond the fact that we've disabling locking, above -- the theory is
- that running graphics demos as root is just always a stupid thing
- to do, since they have probably never been security reviewed and are
- more likely to be buggy than just about any other kind of program.
- (And that assumes non-malicious code. There are also attacks here.)
-
- *** WARNING: DO NOT DISABLE THIS CODE!
- If you do so, you will open a security hole. See the sections
- of the xscreensaver manual titled "LOCKING AND ROOT LOGINS",
- and "USING XDM".
+ /* If we're still running as root, or if the user we are running at seems
+ to be in any way hinky, exit and do not allow password authentication
+ to continue.
*/
- if (getuid() == (uid_t) 0)
- {
- struct passwd *p;
-
- p = getpwnam ("nobody");
- if (! p) p = getpwnam ("noaccess");
- if (! p) p = getpwnam ("daemon");
- if (! p)
- {
- fprintf (stderr,
- "%s: running as root, and couldn't find a safer uid.\n",
- blurb());
- saver_exit(si, 1, 0);
- }
+ uid = getuid (); /* get it again */
+ p = getpwuid (uid);
- if (set_ids_by_number (p->pw_uid, p->pw_gid, &si->uid_message) != 0)
- saver_exit (si, -1, 0);
+ if (!p ||
+ uid == (uid_t) 0 ||
+ uid == (uid_t) -1 ||
+ uid == (uid_t) -2 ||
+ p->pw_uid == (uid_t) 0 ||
+ p->pw_uid == (uid_t) -1 ||
+ p->pw_uid == (uid_t) -2 ||
+ !p->pw_name ||
+ !*p->pw_name ||
+ !strcmp (p->pw_name, "root") ||
+ !strcmp (p->pw_name, "nobody") ||
+ !strcmp (p->pw_name, "noaccess") ||
+ !strcmp (p->pw_name, "operator") ||
+ !strcmp (p->pw_name, "daemon") ||
+ !strcmp (p->pw_name, "bin") ||
+ !strcmp (p->pw_name, "adm") ||
+ !strcmp (p->pw_name, "sys") ||
+ !strcmp (p->pw_name, "games"))
+ {
+ fprintf (stderr,
+ "%s: running as user \"%s\" -- authentication disallowed\n",
+ blurb(),
+ (p && p->pw_name && *p->pw_name
+ ? p->pw_name
+ : "<unknown>"));
+ exit (1);
}
-
- /* If there's anything even remotely funny looking about the passwd struct,
- or if we're running as some other user from the list below (a
- non-comprehensive selection of users known to be privileged in some way,
- and not normal end-users) then disable locking. If it was possible,
- switching to "nobody" would be the thing to do, but only root itself has
- the privs to do that.
-
- *** WARNING: DO NOT DISABLE THIS CODE!
- If you do so, you will open a security hole. See the sections
- of the xscreensaver manual titled "LOCKING AND ROOT LOGINS",
- and "USING XDM".
- */
- {
- uid_t uid = getuid (); /* get it again */
- struct passwd *p = getpwuid (uid); /* get it again */
-
- if (!p ||
- uid == (uid_t) 0 ||
- uid == (uid_t) -1 ||
- uid == (uid_t) -2 ||
- p->pw_uid == (uid_t) 0 ||
- p->pw_uid == (uid_t) -1 ||
- p->pw_uid == (uid_t) -2 ||
- !p->pw_name ||
- !*p->pw_name ||
- !strcmp (p->pw_name, "root") ||
- !strcmp (p->pw_name, "nobody") ||
- !strcmp (p->pw_name, "noaccess") ||
- !strcmp (p->pw_name, "operator") ||
- !strcmp (p->pw_name, "daemon") ||
- !strcmp (p->pw_name, "bin") ||
- !strcmp (p->pw_name, "adm") ||
- !strcmp (p->pw_name, "sys") ||
- !strcmp (p->pw_name, "games"))
- {
- static char buf [1024];
- sprintf (buf, "running as %.100s",
- (p && p->pw_name && *p->pw_name
- ? p->pw_name : "<unknown>"));
- si->nolock_reason = buf;
- si->locking_disabled_p = True;
- si->dangerous_uid_p = True;
- }
- }
+ if (verbose_p)
+ fprintf (stderr, "%s: running as user \"%s\"\n", blurb(), p->pw_name);
}
diff --git a/driver/subprocs.c b/driver/subprocs.c
index e251842..2b3453d 100644
--- a/driver/subprocs.c
+++ b/driver/subprocs.c
@@ -1,5 +1,5 @@
/* subprocs.c --- choosing, spawning, and killing screenhacks.
- * xscreensaver, Copyright (c) 1991-2019 Jamie Zawinski <jwz@jwz.org>
+ * xscreensaver, Copyright © 1991-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
@@ -19,6 +19,8 @@
#include <string.h>
#include <X11/Xlib.h> /* not used for much... */
+#include <X11/Xatom.h>
+#include <X11/Intrinsic.h> /* For XtAppAddSignal */
#ifndef ESRCH
# include <errno.h>
@@ -35,169 +37,23 @@
# include <sys/resource.h> /* for setrlimit() and RLIMIT_AS */
#endif
-#ifdef VMS
-# include <processes.h>
-# include <unixio.h> /* for close */
-# include <unixlib.h> /* for getpid */
-# define pid_t int
-# define fork vfork
-#endif /* VMS */
-
#include <signal.h> /* for the signal names */
#include <time.h>
-#if !defined(SIGCHLD) && defined(SIGCLD)
-# define SIGCHLD SIGCLD
-#endif
-
-#if 0 /* putenv() is declared in stdlib.h on modern linux systems. */
-#ifdef HAVE_PUTENV
-extern int putenv (/* const char * */); /* getenv() is in stdlib.h... */
-#endif
+#ifdef ENABLE_NLS
+# include <locale.h>
+# include <libintl.h>
+# define _(S) gettext(S)
+#else
+# define _(S) (S)
#endif
-extern int kill (pid_t, int); /* signal() is in sys/signal.h... */
-
-/* This file doesn't need the Xt headers, so stub these types out... */
-#undef XtPointer
-#define XtAppContext void*
-#define XrmDatabase void*
-#define XtIntervalId void*
-#define XtPointer void*
-#define Widget void*
-
#include "xscreensaver.h"
#include "exec.h"
#include "yarandom.h"
-#include "visual.h" /* for id_to_visual() */
-
-extern saver_info *global_si_kludge; /* I hate C so much... */
-
-
-/* Used when printing error/debugging messages from signal handlers.
- */
-static const char *
-no_malloc_number_to_string (long num)
-{
- static char string[128] = "";
- int num_digits;
- Bool negative_p = False;
-
- num_digits = 0;
-
- if (num == 0)
- return "0";
-
- if (num < 0)
- {
- negative_p = True;
- num = -num;
- }
-
- while ((num > 0) && (num_digits < sizeof(string) - 1))
- {
- int digit;
- digit = (int) num % 10;
- num_digits++;
- string[sizeof(string) - 1 - num_digits] = digit + '0';
- num /= 10;
- }
-
- if (negative_p)
- {
- num_digits++;
- string[sizeof(string) - 1 - num_digits] = '-';
- }
-
- return string + sizeof(string) - 1 - num_digits;
-}
-
-/* Like write(), but runs strlen() on the arg to get the length. */
-static int
-write_string (int fd, const char *str)
-{
- return write (fd, str, strlen (str));
-}
-
-static int
-write_long (int fd, long n)
-{
- const char *str = no_malloc_number_to_string (n);
- return write_string (fd, str);
-}
-
-
-/* RLIMIT_AS (called RLIMIT_VMEM on some systems) controls the maximum size
- of a process's address space, i.e., the maximal brk(2) and mmap(2) values.
- Setting this lets you put a cap on how much memory a process can allocate.
-
- Except the "and mmap()" part kinda makes this useless, since many GL
- implementations end up using mmap() to pull the whole frame buffer into
- memory (or something along those lines) making it appear processes are
- using hundreds of megabytes when in fact they're using very little, and
- we end up capping their mallocs prematurely. YAY!
- */
-#if defined(RLIMIT_VMEM) && !defined(RLIMIT_AS)
-# define RLIMIT_AS RLIMIT_VMEM
-#endif
-
-static void
-limit_subproc_memory (int address_space_limit, Bool verbose_p)
-{
-
-/* This has caused way more problems than it has solved...
- Let's just completely ignore the "memoryLimit" option now.
- */
-#undef HAVE_SETRLIMIT
-
-#if defined(HAVE_SETRLIMIT) && defined(RLIMIT_AS)
- struct rlimit r;
-
- if (address_space_limit < 10 * 1024) /* let's not be crazy */
- return;
-
- if (getrlimit (RLIMIT_AS, &r) != 0)
- {
- char buf [512];
- sprintf (buf, "%s: getrlimit(RLIMIT_AS) failed", blurb());
- perror (buf);
- return;
- }
-
- r.rlim_cur = address_space_limit;
-
- if (setrlimit (RLIMIT_AS, &r) != 0)
- {
- char buf [512];
- sprintf (buf, "%s: setrlimit(RLIMIT_AS, {%lu, %lu}) failed",
- blurb(), r.rlim_cur, r.rlim_max);
- perror (buf);
- return;
- }
-
- if (verbose_p)
- {
- int i = address_space_limit;
- char buf[100];
- if (i >= (1<<30) && i == ((i >> 30) << 30))
- sprintf(buf, "%dG", i >> 30);
- else if (i >= (1<<20) && i == ((i >> 20) << 20))
- sprintf(buf, "%dM", i >> 20);
- else if (i >= (1<<10) && i == ((i >> 10) << 10))
- sprintf(buf, "%dK", i >> 10);
- else
- sprintf(buf, "%d bytes", i);
-
- fprintf (stderr, "%s: limited pid %lu address space to %s.\n",
- blurb(), (unsigned long) getpid (), buf);
- }
+#include "visual.h" /* for id_to_visual() */
+#include "atoms.h"
-#endif /* HAVE_SETRLIMIT && RLIMIT_AS */
-}
-
-
-/* Management of child processes, and de-zombification.
- */
enum job_status {
job_running, /* the process is still alive */
@@ -220,10 +76,107 @@ struct screenhack_job {
static struct screenhack_job *jobs = 0;
-/* for debugging -- nothing calls this, but it's useful to invoke from gdb.
+static void clean_job_list (void);
+static void await_dying_children (saver_info *si);
+static void describe_dead_child (saver_info *, pid_t, int wait_status,
+ struct rusage);
+
+static XtSignalId xt_sigterm_id = 0;
+static int sigterm_received = 0;
+
+static XtSignalId xt_sigchld_id = 0;
+static int sigchld_received = 0;
+
+
+const char *
+signal_name (int signal)
+{
+ /* sys_signame[], sys_siglist[], strsignal() and sigabbrev_np()
+ are an unportable mess. */
+ switch (signal) {
+ case SIGHUP: return "SIGHUP";
+ case SIGINT: return "SIGINT";
+ case SIGQUIT: return "SIGQUIT";
+ case SIGILL: return "SIGILL";
+ case SIGTRAP: return "SIGTRAP";
+#ifdef SIGABRT
+ case SIGABRT: return "SIGABRT";
+#endif
+ case SIGFPE: return "SIGFPE";
+ case SIGKILL: return "SIGKILL";
+ case SIGBUS: return "SIGBUS";
+ case SIGSEGV: return "SIGSEGV";
+ case SIGPIPE: return "SIGPIPE";
+ case SIGALRM: return "SIGALRM";
+ case SIGTERM: return "SIGTERM";
+#ifdef SIGSTOP
+ case SIGSTOP: return "SIGSTOP";
+#endif
+#ifdef SIGCONT
+ case SIGCONT: return "SIGCONT";
+#endif
+#ifdef SIGUSR1
+ case SIGUSR1: return "SIGUSR1";
+#endif
+#ifdef SIGUSR2
+ case SIGUSR2: return "SIGUSR2";
+#endif
+#ifdef SIGEMT
+ case SIGEMT: return "SIGEMT";
+#endif
+#ifdef SIGSYS
+ case SIGSYS: return "SIGSYS";
+#endif
+ case SIGCHLD: return "SIGCHLD";
+#ifdef SIGPWR
+ case SIGPWR: return "SIGPWR";
+#endif
+#ifdef SIGWINCH
+ case SIGWINCH: return "SIGWINCH";
+#endif
+#ifdef SIGURG
+ case SIGURG: return "SIGURG";
+#endif
+#ifdef SIGIO
+ case SIGIO: return "SIGIO";
+#endif
+#ifdef SIGVTALRM
+ case SIGVTALRM: return "SIGVTALRM";
+#endif
+#ifdef SIGXCPU
+ case SIGXCPU: return "SIGXCPU";
+#endif
+#ifdef SIGXFSZ
+ case SIGXFSZ: return "SIGXFSZ";
+#endif
+#ifdef SIGDANGER
+ case SIGDANGER: return "SIGDANGER";
+#endif
+ default:
+ {
+ static char buf[50];
+ sprintf(buf, "signal %d\n", signal);
+ return buf;
+ }
+ }
+}
+
+
+/* Management of child processes, and de-zombification.
*/
-void show_job_list (void);
+static char *
+timestring (time_t when)
+{
+ static char buf[255] = { 0 };
+ struct tm tm;
+ localtime_r (&when, &tm);
+ sprintf (buf, "%02d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec);
+ return buf;
+}
+
+#ifdef DEBUG
+void show_job_list (void);
void
show_job_list (void)
{
@@ -231,27 +184,24 @@ show_job_list (void)
fprintf(stderr, "%s: job list:\n", blurb());
for (job = jobs; job; job = job->next)
{
- char b[] = " ??:??:?? ";
- char *t = (job->killed ? timestring (job->killed) :
- job->launched ? timestring (job->launched) : b);
- t += 11;
- t[8] = 0;
- fprintf (stderr, " %5ld: %2d: (%s) %s %s\n",
- (long) job->pid,
- job->screen,
- (job->status == job_running ? "running" :
- job->status == job_stopped ? "stopped" :
- job->status == job_killed ? " killed" :
- job->status == job_dead ? " dead" : " ???"),
- t, job->name);
+ fprintf (stderr, " %5ld: %2d: (%s) %s %s\n",
+ (long) job->pid,
+ job->screen,
+ (job->status == job_running ? "running" :
+ job->status == job_stopped ? "stopped" :
+ job->status == job_killed ? " killed" :
+ job->status == job_dead ? " dead" : " ???"),
+ (job->killed ? timestring (job->killed) :
+ job->launched ? timestring (job->launched) :
+ "??:??:??"),
+ job->name);
}
fprintf (stderr, "\n");
}
+#endif /* DEBUG */
-static void clean_job_list (void);
-
-static struct screenhack_job *
+static void
make_job (pid_t pid, int screen, const char *cmd)
{
struct screenhack_job *job = (struct screenhack_job *) malloc (sizeof(*job));
@@ -288,8 +238,6 @@ make_job (pid_t pid, int screen, const char *cmd)
job->killed = 0;
job->next = jobs;
jobs = job;
-
- return jobs;
}
@@ -317,8 +265,7 @@ free_job (struct screenhack_job *job)
}
-/* Cleans out dead jobs from the jobs list -- this must only be called
- from the main thread, not from a signal handler.
+/* Cleans out dead jobs from the jobs list.
*/
static void
clean_job_list (void)
@@ -367,79 +314,8 @@ find_job (pid_t pid)
return 0;
}
-static void await_dying_children (saver_info *si);
-#ifndef VMS
-static void describe_dead_child (saver_info *, pid_t, int wait_status);
-#endif
-
-
-/* Semaphore to temporarily turn the SIGCHLD handler into a no-op.
- Don't alter this directly -- use block_sigchld() / unblock_sigchld().
- */
-static int block_sigchld_handler = 0;
-
-
-#ifdef HAVE_SIGACTION
- sigset_t
-#else /* !HAVE_SIGACTION */
- int
-#endif /* !HAVE_SIGACTION */
-block_sigchld (void)
-{
-#ifdef HAVE_SIGACTION
- struct sigaction sa;
- sigset_t child_set;
-
- memset (&sa, 0, sizeof (sa));
- sa.sa_handler = SIG_IGN;
- sigaction (SIGPIPE, &sa, NULL);
-
- sigemptyset (&child_set);
- sigaddset (&child_set, SIGCHLD);
- sigprocmask (SIG_BLOCK, &child_set, 0);
-
-#else /* !HAVE_SIGACTION */
- signal (SIGPIPE, SIG_IGN);
-#endif /* !HAVE_SIGACTION */
-
- block_sigchld_handler++;
-
-#ifdef HAVE_SIGACTION
- return child_set;
-#else /* !HAVE_SIGACTION */
- return 0;
-#endif /* !HAVE_SIGACTION */
-}
-
-void
-unblock_sigchld (void)
-{
- if (block_sigchld_handler <= 0)
- abort();
-
- if (block_sigchld_handler <= 1) /* only unblock if count going to 0 */
- {
-#ifdef HAVE_SIGACTION
- struct sigaction sa;
- sigset_t child_set;
-
- memset(&sa, 0, sizeof (sa));
- sa.sa_handler = SIG_DFL;
- sigaction(SIGPIPE, &sa, NULL);
-
- sigemptyset(&child_set);
- sigaddset(&child_set, SIGCHLD);
- sigprocmask(SIG_UNBLOCK, &child_set, 0);
-#else /* !HAVE_SIGACTION */
- signal(SIGPIPE, SIG_DFL);
-#endif /* !HAVE_SIGACTION */
- }
-
- block_sigchld_handler--;
-}
-
-int
+static int
kill_job (saver_info *si, pid_t pid, int signal)
{
saver_preferences *p = &si->prefs;
@@ -448,12 +324,6 @@ kill_job (saver_info *si, pid_t pid, int signal)
clean_job_list();
- if (in_signal_handler_p)
- /* This function should not be called from the signal handler. */
- abort();
-
- block_sigchld(); /* we control the horizontal... */
-
job = find_job (pid);
if (!job ||
!job->pid ||
@@ -471,7 +341,6 @@ kill_job (saver_info *si, pid_t pid, int signal)
job->killed = time ((time_t *) 0);
break;
#ifdef SIGSTOP
- /* #### there must be a way to do this on VMS... */
case SIGSTOP: job->status = job_stopped; break;
case SIGCONT: job->status = job_running; break;
#endif /* SIGSTOP */
@@ -492,7 +361,7 @@ kill_job (saver_info *si, pid_t pid, int signal)
{
if (errno == ESRCH)
fprintf (stderr,
- "%s: %d: child process %lu (%s) was already dead.\n",
+ "%s: %d: child process %lu (%s) was already dead\n",
blurb(), job->screen, (unsigned long) job->pid, job->name);
else
{
@@ -506,53 +375,148 @@ kill_job (saver_info *si, pid_t pid, int signal)
await_dying_children (si);
DONE:
- unblock_sigchld();
- if (block_sigchld_handler < 0)
- abort();
-
clean_job_list();
return status;
}
-#ifdef SIGCHLD
-static RETSIGTYPE
-sigchld_handler (int sig)
+/* We use Xt-style signal handling. A Unix signal fires, and we inform Xt of
+ that. Then after we return to the top-level command loop on the main
+ stack, Xt runs our callback function for that signal. Just like Xt timers.
+ */
+static void
+catch_signal (int sig, RETSIGTYPE (*handler) (int))
{
- saver_info *si = global_si_kludge; /* I hate C so much... */
- in_signal_handler_p++;
-
- if (si->prefs.debug_p)
+# ifdef HAVE_SIGACTION
+ struct sigaction a;
+ a.sa_handler = handler;
+ sigemptyset (&a.sa_mask);
+ a.sa_flags = 0;
+ if (sigaction (sig, &a, 0) < 0)
+# else /* !HAVE_SIGACTION */
+ if (((long) signal (sig, handler)) == -1L)
+# endif /* !HAVE_SIGACTION */
{
- /* Don't call fprintf() from signal handlers, as it might malloc.
- fprintf(stderr, "%s: got SIGCHLD%s\n", blurb(),
- (block_sigchld_handler ? " (blocked)" : ""));
- */
- write_string (STDERR_FILENO, blurb());
- write_string (STDERR_FILENO, ": got SIGCHLD");
-
- if (block_sigchld_handler)
- write_string (STDERR_FILENO, " (blocked)\n");
- else
- write_string (STDERR_FILENO, "\n");
+ char buf [255];
+ sprintf (buf, "%s: couldn't catch signal %s", blurb(),
+ signal_name (sig));
+ perror (buf);
+ abort();
}
+}
+
+
+/* Exiting gracefully.
- if (block_sigchld_handler < 0)
- abort();
- else if (block_sigchld_handler == 0)
+ When xscreensaver sends a SIGTERM signal to xscreensaver-gfx, rather
+ than exiting immediately, we want it to do two things:
+
+ - Send a SIGTERM to each running screenhack. They would *probably*
+ die of a BadWindow X error once their window was deleted, but this
+ is cleaner and more immediate.
+
+ - Fade the screens in from black. This might take several seconds.
+
+ Should another signal come in while that is ongoing, we should just
+ die immediately.
+ */
+
+static RETSIGTYPE
+saver_sigterm_handler (int sig)
+{
+ /* This is the actual signal handler, running on the signal stack.
+ After firing once, set this signal back to the default behavior. */
+ sigterm_received = sig;
+ catch_signal (sig, SIG_DFL);
+
+ /* The first time a signal fires, inform Xt of that so that it will run
+ xt_signal_handler(). "XtNoticeSignal is the only Intrinsics function
+ that can safely be called from a signal handler". */
+ if (xt_sigterm_id)
+ XtNoticeSignal (xt_sigterm_id);
+}
+
+
+static void
+xt_sigterm_handler (XtPointer data, XtSignalId *id)
+{
+ /* This runs from the Xt event loop on the main stack, some time after
+ the signal fired. */
+ saver_info *si = (saver_info *) data;
+ saver_preferences *p = &si->prefs;
+ static Bool hit_p = False;
+ int i;
+
+ if (xt_sigterm_id)
+ XtRemoveSignal (xt_sigterm_id);
+ xt_sigterm_id = 0;
+
+ if (hit_p)
+ fprintf (stderr, "%s: second signal: %s: exiting\n", blurb(),
+ signal_name (sigterm_received));
+ else
{
- block_sigchld();
- await_dying_children (si);
- unblock_sigchld();
+ hit_p = True;
+ if (p->verbose_p)
+ fprintf (stderr, "%s: %s: unblanking\n", blurb(),
+ signal_name (sigterm_received));
+
+ /* Kill before unblanking, to stop drawing as soon as possible. */
+ for (i = 0; i < si->nscreens; i++)
+ {
+ saver_screen_info *ssi = &si->screens[i];
+ if (ssi->cycle_id)
+ {
+ XtRemoveTimeOut (ssi->cycle_id);
+ ssi->cycle_id = 0;
+ ssi->cycle_at = 0;
+ }
+ kill_screenhack (ssi);
+ }
+ unblank_screen (si);
+
+ if (p->verbose_p)
+ fprintf (stderr, "%s: %s: exiting\n", blurb(),
+ signal_name (sigterm_received));
}
- init_sigchld();
- in_signal_handler_p--;
+ /* Exit with the original signal received. */
+ kill (getpid(), sigterm_received);
+ abort();
+}
+
+
+/* SIGCHLD handling. Basically the same deal as SIGTERM.
+ */
+
+static RETSIGTYPE
+sigchld_handler (int sig)
+{
+ /* This is the actual signal handler, running on the signal stack.
+ After firing once, set this signal to fire again. */
+ sigchld_received = sig;
+# ifndef HAVE_SIGACTION
+ catch_signal (SIGCHLD, sigchld_handler);
+# endif
+
+ if (xt_sigchld_id)
+ XtNoticeSignal (xt_sigchld_id);
}
-#endif /* SIGCHLD */
-#ifndef VMS
+static void
+xt_sigchld_handler (XtPointer data, XtSignalId *id)
+{
+ /* This runs from the Xt event loop on the main stack, some time after
+ the signal fired. */
+ saver_info *si = (saver_info *) data;
+
+ if (si->prefs.debug_p)
+ fprintf(stderr, "%s: got SIGCHLD\n", blurb());
+
+ await_dying_children (si); /* Their first album was better */
+}
+
static void
await_dying_children (saver_info *si)
@@ -561,36 +525,19 @@ await_dying_children (saver_info *si)
{
int wait_status = 0;
pid_t kid;
+ struct rusage rus;
errno = 0;
- kid = waitpid (-1, &wait_status, WNOHANG|WUNTRACED);
+ kid = wait4 (-1, &wait_status, WNOHANG|WUNTRACED, &rus);
if (si->prefs.debug_p)
{
if (kid < 0 && errno)
- {
- /* Don't call fprintf() from signal handlers, as it might malloc.
- fprintf (stderr, "%s: waitpid(-1) ==> %ld (%d)\n", blurb(),
- (long) kid, errno);
- */
- write_string (STDERR_FILENO, blurb());
- write_string (STDERR_FILENO, ": waitpid(-1) ==> ");
- write_long (STDERR_FILENO, (long) kid);
- write_string (STDERR_FILENO, " (");
- write_long (STDERR_FILENO, (long) errno);
- write_string (STDERR_FILENO, ")\n");
- }
+ fprintf (stderr, "%s: waitpid(-1) ==> %ld (%d)\n", blurb(),
+ (long) kid, errno);
else
- {
- /* Don't call fprintf() from signal handlers, as it might malloc.
- fprintf (stderr, "%s: waitpid(-1) ==> %ld\n", blurb(),
- (long) kid);
- */
- write_string (STDERR_FILENO, blurb());
- write_string (STDERR_FILENO, ": waitpid(-1) ==> ");
- write_long (STDERR_FILENO, (long) kid);
- write_string (STDERR_FILENO, "\n");
- }
+ fprintf (stderr, "%s: waitpid(-1) ==> %ld\n", blurb(),
+ (long) kid);
}
/* 0 means no more children to reap.
@@ -600,272 +547,193 @@ await_dying_children (saver_info *si)
(kid < 0 && errno != EINTR))
break;
- describe_dead_child (si, kid, wait_status);
+ describe_dead_child (si, kid, wait_status, rus);
}
}
static void
-describe_dead_child (saver_info *si, pid_t kid, int wait_status)
+describe_dead_child (saver_info *si, pid_t kid, int wait_status,
+ struct rusage rus)
{
int i;
saver_preferences *p = &si->prefs;
struct screenhack_job *job = find_job (kid);
const char *name = job ? job->name : "<unknown>";
int screen_no = job ? job->screen : 0;
+ char msg[1024];
+ *msg = 0;
if (WIFEXITED (wait_status))
{
int exit_status = WEXITSTATUS (wait_status);
-
/* Treat exit code as a signed 8-bit quantity. */
if (exit_status & 0x80) exit_status |= ~0xFF;
- /* One might assume that exiting with non-0 means something went wrong.
- But that loser xswarm exits with the code that it was killed with, so
- it *always* exits abnormally. Treat abnormal exits as "normal" (don't
- mention them) if we've just killed the subprocess. But mention them
- if they happen on their own.
- */
- if (!job ||
- (exit_status != 0 &&
- (p->verbose_p || job->status != job_killed)))
- {
- /* Don't call fprintf() from signal handlers, as it might malloc.
- fprintf (stderr,
- "%s: %d: child pid %lu (%s) exited abnormally (code %d).\n",
- blurb(), screen_no, (unsigned long) kid, name, exit_status);
- */
- write_string (STDERR_FILENO, blurb());
- write_string (STDERR_FILENO, ": ");
- write_long (STDERR_FILENO, (long) screen_no);
- write_string (STDERR_FILENO, ": child pid ");
- write_long (STDERR_FILENO, (long) kid);
- write_string (STDERR_FILENO, " (");
- write_string (STDERR_FILENO, name);
- write_string (STDERR_FILENO, ") exited abnormally (code ");
- write_long (STDERR_FILENO, (long) exit_status);
- write_string (STDERR_FILENO, ").\n");
- }
- else if (p->verbose_p)
- {
- /* Don't call fprintf() from signal handlers, as it might malloc.
- fprintf (stderr, "%s: %d: child pid %lu (%s) exited normally.\n",
- blurb(), screen_no, (unsigned long) kid, name);
- */
- write_string (STDERR_FILENO, blurb());
- write_string (STDERR_FILENO, ": ");
- write_long (STDERR_FILENO, (long) screen_no);
- write_string (STDERR_FILENO, ": child pid ");
- write_long (STDERR_FILENO, (long) kid);
- write_string (STDERR_FILENO, " (");
- write_string (STDERR_FILENO, name);
- write_string (STDERR_FILENO, ") exited normally.\n");
- }
-
+ sprintf (msg, _("crashed with status %d"), exit_status);
+ if (p->verbose_p)
+ fprintf (stderr,
+ "%s: %d: child pid %lu (%s) exited abnormally"
+ " with status %d\n",
+ blurb(), screen_no, (unsigned long) kid, name, exit_status);
if (job)
job->status = job_dead;
}
else if (WIFSIGNALED (wait_status))
{
- if (p->verbose_p ||
- !job ||
- job->status != job_killed ||
- WTERMSIG (wait_status) != SIGTERM)
+ const char *sig = signal_name (WTERMSIG (wait_status));
+ if (job &&
+ job->status == job_killed &&
+ WTERMSIG (wait_status) == SIGTERM)
{
- /* Don't call fprintf() from signal handlers, as it might malloc.
- fprintf (stderr, "%s: %d: child pid %lu (%s) terminated with %s.\n",
- blurb(), screen_no, (unsigned long) kid, name,
- signal_name (WTERMSIG(wait_status)));
- */
- write_string (STDERR_FILENO, blurb());
- write_string (STDERR_FILENO, ": ");
- write_long (STDERR_FILENO, (long) screen_no);
- write_string (STDERR_FILENO, ": child pid ");
- write_long (STDERR_FILENO, (long) kid);
- write_string (STDERR_FILENO, " (");
- write_string (STDERR_FILENO, name);
- write_string (STDERR_FILENO, ") terminated with signal ");
- write_long (STDERR_FILENO, WTERMSIG(wait_status));
- write_string (STDERR_FILENO, ".\n");
+ /* Expected notification after we killed it. */
+ sprintf (msg, _("exited normally with %.100s"), sig);
+ if (p->verbose_p)
+ fprintf (stderr, "%s: %d: child pid %lu (%s)"
+ " exited normally with %s\n",
+ blurb(), screen_no, (unsigned long) kid, name, sig);
+ }
+ else
+ {
+ /* Unexpected signal. */
+ sprintf (msg, _("crashed with %.100s"), sig);
+ if (p->verbose_p)
+ fprintf (stderr, "%s: %d: child pid %lu (%s)"
+ " unexpectedly terminated with %s\n",
+ blurb(), screen_no, (unsigned long) kid, name, sig);
}
-
if (job)
job->status = job_dead;
}
else if (WIFSTOPPED (wait_status))
{
if (p->verbose_p)
- {
- /* Don't call fprintf() from signal handlers, as it might malloc.
- fprintf (stderr, "%s: child pid %lu (%s) stopped with %s.\n",
- blurb(), (unsigned long) kid, name,
- signal_name (WSTOPSIG (wait_status)));
- */
- write_string (STDERR_FILENO, blurb());
- write_string (STDERR_FILENO, ": ");
- write_long (STDERR_FILENO, (long) screen_no);
- write_string (STDERR_FILENO, ": child pid ");
- write_long (STDERR_FILENO, (long) kid);
- write_string (STDERR_FILENO, " (");
- write_string (STDERR_FILENO, name);
- write_string (STDERR_FILENO, ") stopped with signal ");
- write_long (STDERR_FILENO, WSTOPSIG(wait_status));
- write_string (STDERR_FILENO, ".\n");
- }
-
+ fprintf (stderr, "%s: child pid %lu (%s) stopped with %s\n",
+ blurb(), (unsigned long) kid, name,
+ signal_name (WSTOPSIG (wait_status)));
if (job)
job->status = job_stopped;
}
else
{
- /* Don't call fprintf() from signal handlers, as it might malloc.
+ /* Didn't exit, signal or stop; is this even possible? */
+ sprintf (msg, _("crashed mysteriously"));
fprintf (stderr, "%s: child pid %lu (%s) died in a mysterious way!",
blurb(), (unsigned long) kid, name);
- */
- write_string (STDERR_FILENO, blurb());
- write_string (STDERR_FILENO, ": ");
- write_long (STDERR_FILENO, (long) screen_no);
- write_string (STDERR_FILENO, ": child pid ");
- write_long (STDERR_FILENO, (long) kid);
- write_string (STDERR_FILENO, " (");
- write_string (STDERR_FILENO, name);
- write_string (STDERR_FILENO, ") died in a mysterious way!");
if (job)
job->status = job_dead;
}
- /* Clear out the pid so that screenhack_running_p() knows it's dead.
+# ifdef LOG_CPU_TIME
+ if (p->verbose_p && job && job->status == job_dead)
+ {
+ long u = rus.ru_utime.tv_usec / 1000 + rus.ru_utime.tv_sec * 1000;
+ long s = rus.ru_stime.tv_usec / 1000 + rus.ru_stime.tv_sec * 1000;
+ fprintf (stderr, "%s: %d: CPU used: %.1fu, %.1fs\n",
+ blurb(), screen_no, u / 1000.0, s / 1000.0);
+ }
+# endif /* LOG_CPU_TIME */
+
+ /* Clear out the pid so that any_screenhacks_running_p() knows it's dead.
*/
if (!job || job->status == job_dead)
{
- for (i = 0; i < si->nscreens; i++)
- {
- saver_screen_info *ssi = &si->screens[i];
- if (kid == ssi->pid)
- ssi->pid = 0;
- }
-# ifdef HAVE_LIBSYSTEMD
- if (kid == si->systemd_pid)
- si->systemd_pid = 0;
-# endif
+ for (i = 0; i < si->nscreens; i++)
+ {
+ saver_screen_info *ssi = &si->screens[i];
+ if (kid == ssi->pid)
+ {
+ ssi->pid = 0;
+ if (*msg)
+ screenhack_obituary (ssi, name, msg);
+ }
+ }
}
}
-#else /* VMS */
-static void await_dying_children (saver_info *si) { return; }
-#endif /* VMS */
-
void
-init_sigchld (void)
+init_sigchld (saver_info *si)
{
-#ifdef SIGCHLD
-
-# ifdef HAVE_SIGACTION /* Thanks to Tom Kelly <tom@ancilla.toronto.on.ca> */
-
- static Bool sigchld_initialized_p = 0;
- if (!sigchld_initialized_p)
- {
- struct sigaction action, old;
+ static Bool signals_initialized_p = 0;
+ if (signals_initialized_p) return;
+ signals_initialized_p = True;
- action.sa_handler = sigchld_handler;
- sigemptyset(&action.sa_mask);
- action.sa_flags = 0;
+ catch_signal (SIGTERM, saver_sigterm_handler); /* kill */
+ catch_signal (SIGINT, saver_sigterm_handler); /* shell ^C */
+ catch_signal (SIGQUIT, saver_sigterm_handler); /* shell ^| */
+ catch_signal (SIGCHLD, sigchld_handler);
- if (sigaction(SIGCHLD, &action, &old) < 0)
- {
- char buf [255];
- sprintf (buf, "%s: couldn't catch SIGCHLD", blurb());
- perror (buf);
- }
- sigchld_initialized_p = True;
- }
-
-# else /* !HAVE_SIGACTION */
-
- if (((long) signal (SIGCHLD, sigchld_handler)) == -1L)
- {
- char buf [255];
- sprintf (buf, "%s: couldn't catch SIGCHLD", blurb());
- perror (buf);
- }
-# endif /* !HAVE_SIGACTION */
-#endif /* SIGCHLD */
+ xt_sigchld_id = XtAppAddSignal (si->app, xt_sigchld_handler, si);
+ xt_sigterm_id = XtAppAddSignal (si->app, xt_sigterm_handler, si);
}
+static void
+hack_subproc_environment (Screen *screen, Window saver_window)
+{
+ /* Store $DISPLAY into the environment, so that the $DISPLAY variable that
+ the spawned processes inherit is correct. First, it must be on the same
+ host and display as the value of -display passed in on our command line
+ (which is not necessarily the same as what our $DISPLAY variable is.)
+ Second, the screen number in the $DISPLAY passed to the subprocess should
+ be the screen on which this particular hack is running -- not the display
+ specification which the driver itself is using, since the driver ignores
+ its screen number and manages all existing screens.
-
+ Likewise, store a window ID in $XSCREENSAVER_WINDOW -- this is necessary
+ in a Xinerama or RANDR world where a single X11 'Screen' spans multiple
+ monitors, and we want to run a hack on each piece of glass, not spanning
+ them. In that case, multiple hacks have the same $DISPLAY, screen and
+ root window.
+ */
+ Display *dpy = DisplayOfScreen (screen);
+ const char *odpy = DisplayString (dpy);
+ char *ndpy = (char *) malloc (strlen(odpy) + 20);
+ char *nssw = (char *) malloc (40);
+ char *s, *c;
-static Bool
-select_visual_of_hack (saver_screen_info *ssi, screenhack *hack)
-{
- saver_info *si = ssi->global;
- saver_preferences *p = &si->prefs;
- Bool selected;
+ strcpy (ndpy, "DISPLAY=");
+ s = ndpy + strlen(ndpy);
+ strcpy (s, odpy);
- if (hack->visual && *hack->visual)
- selected = select_visual(ssi, hack->visual);
- else
- selected = select_visual(ssi, 0);
+ /* We have to find the last colon since it is the boundary between
+ hostname & screen - IPv6 numeric format addresses may have many
+ colons before that point, and DECnet addresses always have two colons */
+ c = strrchr(s,':'); /* skip to last colon */
+ if (c != NULL) s = c+1;
+ while (isdigit(*s)) s++; /* skip over dpy number */
+ while (*s == '.') s++; /* skip over dot */
+ if (s[-1] != '.') *s++ = '.'; /* put on a dot */
+ sprintf(s, "%d", screen_number (screen)); /* put on screen number */
- if (!selected && (p->verbose_p || si->demoing_p))
- fprintf (stderr,
- (si->demoing_p
- ? "%s: warning, no \"%s\" visual for \"%s\".\n"
- : "%s: no \"%s\" visual; skipping \"%s\".\n"),
- blurb(),
- (hack->visual && *hack->visual ? hack->visual : "???"),
- hack->command);
+ sprintf (nssw, "XSCREENSAVER_WINDOW=0x%lX", (unsigned long) saver_window);
- return selected;
+ if (putenv (ndpy))
+ abort ();
+ if (putenv (nssw))
+ abort ();
+
+ /* don't free ndpy/nssw -- some implementations of putenv (BSD 4.4,
+ glibc 2.0) copy the argument, but some (libc4,5, glibc 2.1.2)
+ do not. So we must leak it (and/or the previous setting). Yay.
+ */
}
+#ifdef ABORT_TESTER /* Shoot down processes after a bit, for debugging */
static void
-print_path_error (const char *program)
+abort_debug_timer (XtPointer closure, XtIntervalId *id)
{
- char buf [512];
- char *cmd = strdup (program);
- char *token = strchr (cmd, ' ');
-
- if (token) *token = 0;
- sprintf (buf, "%s: could not execute \"%.100s\"", blurb(), cmd);
- free (cmd);
- perror (buf);
-
- if (errno == ENOENT &&
- (token = getenv("PATH")))
+ saver_screen_info *ssi = (saver_screen_info *) closure;
+ if (ssi->pid)
{
-# ifndef PATH_MAX
-# ifdef MAXPATHLEN
-# define PATH_MAX MAXPATHLEN
-# else
-# define PATH_MAX 2048
-# endif
-# endif
- char path[PATH_MAX];
- fprintf (stderr, "\n");
- *path = 0;
-# if defined(HAVE_GETCWD)
- if (! getcwd (path, sizeof(path)))
- *path = 0;
-# elif defined(HAVE_GETWD)
- getwd (path);
-# endif
- if (*path)
- fprintf (stderr, " Current directory is: %s\n", path);
- fprintf (stderr, " PATH is:\n");
- token = strtok (strdup(token), ":");
- while (token)
- {
- fprintf (stderr, " %s\n", token);
- token = strtok(0, ":");
- }
- fprintf (stderr, "\n");
+ fprintf (stderr, "%s: %d: %ld: born to ill\n", blurb(), ssi->number,
+ (unsigned long) ssi->pid);
+ kill (ssi->pid, SIGILL);
}
}
+#endif /* ABORT_TESTER */
/* Executes the command in another process.
@@ -875,15 +743,10 @@ print_path_error (const char *program)
Otherwise, -1 is returned and an error may have been
printed to stderr.
*/
-pid_t
+static pid_t
fork_and_exec (saver_screen_info *ssi, const char *command)
{
- return fork_and_exec_1 (ssi->global, ssi, command);
-}
-
-pid_t
-fork_and_exec_1 (saver_info *si, saver_screen_info *ssi, const char *command)
-{
+ saver_info *si = ssi->global;
saver_preferences *p = &si->prefs;
pid_t forked;
@@ -899,35 +762,60 @@ fork_and_exec_1 (saver_info *si, saver_screen_info *ssi, const char *command)
case 0:
close (ConnectionNumber (si->dpy)); /* close display fd */
- limit_subproc_memory (p->inferior_memory_limit, p->verbose_p);
if (ssi)
hack_subproc_environment (ssi->screen, ssi->screensaver_window);
- if (p->verbose_p)
- fprintf (stderr, "%s: %d: spawning \"%s\" in pid %lu.\n",
- blurb(), (ssi ? ssi->number : 0), command,
- (unsigned long) getpid ());
-
exec_command (p->shell, command, p->nice_inferior);
-
- /* If that returned, we were unable to exec the subprocess.
- Print an error message, if desired.
- */
- if (! p->ignore_uninstalled_p)
- print_path_error (command);
-
+ /* If that returned, we were unable to exec the subprocess. */
exit (1); /* exits child fork */
break;
default: /* parent */
- (void) make_job (forked, (ssi ? ssi->number : 0), command);
+ make_job (forked, (ssi ? ssi->number : 0), command);
+ if (p->verbose_p)
+ fprintf (stderr, "%s: %d: forked \"%s\" in pid %lu"
+ " on window 0x%lx\n",
+ blurb(), (ssi ? ssi->number : 0), command,
+ (unsigned long) forked,
+ (unsigned long) ssi->screensaver_window);
break;
}
+# ifdef ABORT_TESTER
+ if (forked)
+ XtAppAddTimeOut (si->app, 1000 * (5 + (5 * ssi->number)),
+ abort_debug_timer, (XtPointer) ssi);
+# endif
+
return forked;
}
+static Bool
+select_visual_of_hack (saver_screen_info *ssi, screenhack *hack)
+{
+ saver_info *si = ssi->global;
+ saver_preferences *p = &si->prefs;
+ Bool selected;
+
+ if (hack->visual && *hack->visual)
+ selected = select_visual(ssi, hack->visual);
+ else
+ selected = select_visual(ssi, 0);
+
+ if (!selected && (p->verbose_p || si->demoing_p))
+ fprintf (stderr,
+ (si->demoing_p
+ ? "%s: warning, no \"%s\" visual for \"%s\"\n"
+ : "%s: no \"%s\" visual; skipping \"%s\"\n"),
+ blurb(),
+ (hack->visual && *hack->visual ? hack->visual : "???"),
+ hack->command);
+
+ return selected;
+}
+
+
void
spawn_screenhack (saver_screen_info *ssi)
{
@@ -935,13 +823,21 @@ spawn_screenhack (saver_screen_info *ssi)
saver_preferences *p = &si->prefs;
XFlush (si->dpy);
- if (!monitor_powered_on_p (si))
+ if (!monitor_powered_on_p (si->dpy))
{
if (si->prefs.verbose_p)
fprintf (stderr,
"%s: %d: X says monitor has powered down; "
- "not launching a hack.\n", blurb(), ssi->number);
- return;
+ "not launching a hack\n", blurb(), ssi->number);
+ ssi->current_hack = -1;
+
+ /* Hooray, this doesn't actually clear the window if it was OpenGL. */
+ XClearWindow (si->dpy, ssi->screensaver_window);
+
+ /* Even though we aren't launching a hack, do launch the cycle timer,
+ in case the monitor powers back up at some point without us having
+ un-blanked. */
+ goto DONE;
}
if (p->screenhacks_count)
@@ -1015,9 +911,7 @@ spawn_screenhack (saver_screen_info *ssi)
if (new_hack < 0) /* don't run a hack */
{
ssi->current_hack = -1;
- if (si->selection_mode < 0)
- si->selection_mode = 0;
- return;
+ goto DONE;
}
ssi->current_hack = new_hack;
@@ -1045,7 +939,7 @@ spawn_screenhack (saver_screen_info *ssi)
*/
if (p->verbose_p)
fprintf(stderr,
- "%s: %d: no programs enabled, or no suitable visuals.\n",
+ "%s: %d: no programs enabled, or no suitable visuals\n",
blurb(), ssi->number);
return;
}
@@ -1053,12 +947,6 @@ spawn_screenhack (saver_screen_info *ssi)
goto AGAIN;
}
- /* Turn off "next" and "prev" modes now, but "demo" mode is only
- turned off by explicit action.
- */
- if (si->selection_mode < 0)
- si->selection_mode = 0;
-
forked = fork_and_exec (ssi, hack->command);
switch ((int) forked)
{
@@ -1066,17 +954,95 @@ spawn_screenhack (saver_screen_info *ssi)
case 0: /* child fork (can't happen) */
sprintf (buf, "%s: couldn't fork", blurb());
perror (buf);
- restore_real_vroot (si);
- saver_exit (si, 1, "couldn't fork");
+ exit (1);
break;
default:
ssi->pid = forked;
break;
}
+
+ XChangeProperty (si->dpy, ssi->screensaver_window, XA_WM_COMMAND,
+ XA_STRING, 8, PropModeReplace,
+ (unsigned char *) hack->command,
+ strlen (hack->command));
+ XChangeProperty (si->dpy, ssi->screensaver_window, XA_NET_WM_PID,
+ XA_CARDINAL, 32, PropModeReplace,
+ (unsigned char *) &ssi->pid, 1);
}
- store_saver_status (si); /* store current hack number */
+ DONE:
+
+ if (ssi->current_hack < 0)
+ XDeleteProperty (si->dpy, ssi->screensaver_window, XA_WM_COMMAND);
+
+ store_saver_status (si); /* store current hack numbers */
+
+ /* Now that the hack has launched, queue a timer to cycle it. */
+ if (!si->demoing_p && p->cycle)
+ {
+ time_t now = time ((time_t *) 0);
+ Time how_long = p->cycle;
+
+ /* If we're in "SELECT n" mode, the cycle timer going off will just
+ restart this same hack again. There's not much point in doing this
+ every 5 or 10 minutes, but on the other hand, leaving one hack
+ running for days is probably not a great idea, since they tend to
+ leak and/or crash. So, restart the thing once an hour.
+ */
+ if (si->selection_mode > 0 && ssi->pid)
+ how_long = 1000 * 60 * 60;
+
+ /* If there are multiple screens, stagger the restart time of subsequent
+ screens: they will all change every N minutes, but not at the same
+ time. But don't let that offset be more than about 5 minutes.
+
+ I originally did this by just adding an offset to the very first
+ cycle only, but after a few days, the cycles would synchronize again!
+ Are Xt timers implemented with Huygens pendulums?? So compare this
+ screen's target time against the previous screen's, and offset it as
+ needed.
+ */
+ if (ssi->number > 0 &&
+ p->mode != RANDOM_HACKS_SAME)
+ {
+ saver_screen_info *prev = &si->screens[ssi->number-1];
+ time_t cycle_at = now + how_long / 1000;
+ time_t prev_at = prev->cycle_at;
+
+ Time max = 1000 * 60 * 60 * 10;
+ Time off = (how_long > max ? max : how_long) / si->nscreens;
+
+ if (cycle_at < prev_at + off / 1000)
+ {
+ time_t old = cycle_at;
+ cycle_at = prev_at + off / 1000;
+ how_long = 1000 * (cycle_at - now);
+
+ if (p->verbose_p && cycle_at - old > 2)
+ fprintf (stderr, "%s: %d: offsetting cycle time by %ld sec\n",
+ blurb(), ssi->number,
+ cycle_at - old);
+ }
+ }
+
+ if (p->debug_p)
+ fprintf (stderr, "%s: %d: starting cycle_timer (%ld)\n",
+ blurb(), ssi->number, how_long);
+
+ if (ssi->cycle_id)
+ XtRemoveTimeOut (ssi->cycle_id);
+ ssi->cycle_id =
+ XtAppAddTimeOut (si->app, how_long, cycle_timer, (XtPointer) ssi);
+ ssi->cycle_at = now + how_long / 1000;
+
+ if (p->verbose_p)
+ {
+ time_t t = time((time_t *) 0) + how_long/1000;
+ fprintf (stderr, "%s: %d: next cycle in %lu sec at %s\n",
+ blurb(), ssi->number, how_long/1000, timestring(t));
+ }
+ }
}
@@ -1087,42 +1053,14 @@ kill_screenhack (saver_screen_info *ssi)
if (ssi->pid)
kill_job (si, ssi->pid, SIGTERM);
ssi->pid = 0;
-}
-
-void
-suspend_screenhack (saver_screen_info *ssi, Bool suspend_p)
-{
-#ifdef SIGSTOP /* older VMS doesn't have it... */
- saver_info *si = ssi->global;
- if (ssi->pid)
- kill_job (si, ssi->pid, (suspend_p ? SIGSTOP : SIGCONT));
-#endif /* SIGSTOP */
+ /* Hooray, this doesn't actually clear the window if it was OpenGL. */
+ XClearWindow (si->dpy, ssi->screensaver_window);
}
-/* Called when we're exiting abnormally, to kill off the subproc. */
-void
-emergency_kill_subproc (saver_info *si)
-{
- int i;
-#ifdef SIGCHLD
- signal (SIGCHLD, SIG_IGN);
-#endif /* SIGCHLD */
-
- for (i = 0; i < si->nscreens; i++)
- {
- saver_screen_info *ssi = &si->screens[i];
- if (ssi->pid)
- {
- kill_job (si, ssi->pid, SIGTERM);
- ssi->pid = 0;
- }
- }
-}
-
Bool
-screenhack_running_p (saver_info *si)
+any_screenhacks_running_p (saver_info *si)
{
Bool any_running_p = False;
int i;
@@ -1130,104 +1068,21 @@ screenhack_running_p (saver_info *si)
{
saver_screen_info *ssi = &si->screens[i];
if (ssi->pid) any_running_p = True;
+ /* Consider it running if an error dialog is posted, so that we
+ don't prematurely clear the window. */
+ if (ssi->error_dialog) any_running_p = True;
}
return any_running_p;
}
-
-/* Environment variables. */
-
-/* Modifies $PATH in the current environment, so that if DEFAULT_PATH_PREFIX
- is defined, the xscreensaver daemon will search that directory for hacks.
+/* Fork "xscreensaver-gl-visual" and wait for it to print the IDs of
+ the GL visual that should be used on this screen.
*/
-void
-hack_environment (saver_info *si)
-{
-#if defined(HAVE_PUTENV) && defined(DEFAULT_PATH_PREFIX)
- static const char *def_path = DEFAULT_PATH_PREFIX;
- if (def_path && *def_path)
- {
- const char *opath = getenv("PATH");
- char *npath;
- if (! opath) opath = "/bin:/usr/bin"; /* WTF */
- npath = (char *) malloc(strlen(def_path) + strlen(opath) + 20);
- strcpy (npath, "PATH=");
- strcat (npath, def_path);
- strcat (npath, ":");
- strcat (npath, opath);
-
- if (putenv (npath))
- abort ();
-
- /* don't free (npath) -- some implementations of putenv (BSD 4.4,
- glibc 2.0) copy the argument, but some (libc4,5, glibc 2.1.2)
- do not. So we must leak it (and/or the previous setting). Yay.
- */
- }
-#endif /* HAVE_PUTENV && DEFAULT_PATH_PREFIX */
-}
-
-
-void
-hack_subproc_environment (Screen *screen, Window saver_window)
-{
- /* Store $DISPLAY into the environment, so that the $DISPLAY variable that
- the spawned processes inherit is correct. First, it must be on the same
- host and display as the value of -display passed in on our command line
- (which is not necessarily the same as what our $DISPLAY variable is.)
- Second, the screen number in the $DISPLAY passed to the subprocess should
- be the screen on which this particular hack is running -- not the display
- specification which the driver itself is using, since the driver ignores
- its screen number and manages all existing screens.
-
- Likewise, store a window ID in $XSCREENSAVER_WINDOW -- this will allow
- us to (eventually) run multiple hacks in Xinerama mode, where each hack
- has the same $DISPLAY but a different piece of glass.
- */
- Display *dpy = DisplayOfScreen (screen);
- const char *odpy = DisplayString (dpy);
- char *ndpy = (char *) malloc (strlen(odpy) + 20);
- char *nssw = (char *) malloc (40);
- char *s, *c;
-
- strcpy (ndpy, "DISPLAY=");
- s = ndpy + strlen(ndpy);
- strcpy (s, odpy);
-
- /* We have to find the last colon since it is the boundary between
- hostname & screen - IPv6 numeric format addresses may have many
- colons before that point, and DECnet addresses always have two colons */
- c = strrchr(s,':'); /* skip to last colon */
- if (c != NULL) s = c+1;
- while (isdigit(*s)) s++; /* skip over dpy number */
- while (*s == '.') s++; /* skip over dot */
- if (s[-1] != '.') *s++ = '.'; /* put on a dot */
- sprintf(s, "%d", screen_number (screen)); /* put on screen number */
-
- sprintf (nssw, "XSCREENSAVER_WINDOW=0x%lX", (unsigned long) saver_window);
-
- /* Allegedly, BSD 4.3 didn't have putenv(), but nobody runs such systems
- any more, right? It's not Posix, but everyone seems to have it. */
-#ifdef HAVE_PUTENV
- if (putenv (ndpy))
- abort ();
- if (putenv (nssw))
- abort ();
-
- /* don't free ndpy/nssw -- some implementations of putenv (BSD 4.4,
- glibc 2.0) copy the argument, but some (libc4,5, glibc 2.1.2)
- do not. So we must leak it (and/or the previous setting). Yay.
- */
-#endif /* HAVE_PUTENV */
-}
-
-
-/* GL crap */
-
Visual *
get_best_gl_visual (saver_info *si, Screen *screen)
{
+ saver_preferences *p = &si->prefs;
pid_t forked;
int fds [2];
int in, out;
@@ -1238,7 +1093,7 @@ get_best_gl_visual (saver_info *si, Screen *screen)
char *av[10];
int ac = 0;
- av[ac++] = "xscreensaver-gl-helper";
+ av[ac++] = "xscreensaver-gl-visual";
av[ac] = 0;
if (pipe (fds))
@@ -1262,18 +1117,13 @@ get_best_gl_visual (saver_info *si, Screen *screen)
errout = errfds [1];
}
- block_sigchld(); /* This blocks it in the parent and child, to avoid
- racing. It is never unblocked in the child before
- the child exits, but that doesn't matter.
- */
-
switch ((int) (forked = fork ()))
{
case -1:
{
sprintf (buf, "%s: couldn't fork", blurb());
perror (buf);
- saver_exit (si, 1, 0);
+ exit (1);
}
case 0:
{
@@ -1315,11 +1165,17 @@ get_best_gl_visual (saver_info *si, Screen *screen)
int result = 0;
int wait_status = 0;
pid_t pid = -1;
-
- FILE *f = fdopen (in, "r");
+ FILE *f;
unsigned long v = 0;
char c;
+ make_job (forked, 0, av[0]); /* Bookkeeping for SIGCHLD */
+
+ if (p->verbose_p)
+ fprintf (stderr, "%s: %d: forked \"%s\" in pid %lu\n",
+ blurb(), 0, av[0], (unsigned long) forked);
+
+ f = fdopen (in, "r");
close (out); /* don't need this one */
*buf = 0;
@@ -1336,16 +1192,8 @@ get_best_gl_visual (saver_info *si, Screen *screen)
/* Wait for the child to die - wait for this pid only, not others. */
pid = waitpid (forked, &wait_status, 0);
if (si->prefs.debug_p)
- {
- write_string (STDERR_FILENO, blurb());
- write_string (STDERR_FILENO, ": waitpid(");
- write_long (STDERR_FILENO, (long) forked);
- write_string (STDERR_FILENO, ") ==> ");
- write_long (STDERR_FILENO, (long) pid);
- write_string (STDERR_FILENO, "\n");
- }
-
- unblock_sigchld(); /* child is dead and waited, unblock now. */
+ fprintf (stderr, "%s: waitpid(%ld) => %ld\n", blurb(),
+ (long) forked, (long) pid);
if (1 == sscanf (buf, "0x%lx %c", &v, &c))
result = (int) v;
@@ -1370,7 +1218,7 @@ get_best_gl_visual (saver_info *si, Screen *screen)
{
Visual *v = id_to_visual (screen, result);
if (si->prefs.verbose_p)
- fprintf (stderr, "%s: %d: %s: GL visual is 0x%X%s.\n",
+ fprintf (stderr, "%s: %d: %s: GL visual is 0x%X%s\n",
blurb(), screen_number (screen),
av[0], result,
(v == DefaultVisualOfScreen (screen)
@@ -1382,54 +1230,3 @@ get_best_gl_visual (saver_info *si, Screen *screen)
abort();
}
-
-
-
-/* Restarting the xscreensaver process from scratch. */
-
-static char **saved_argv;
-
-void
-save_argv (int argc, char **argv)
-{
- saved_argv = (char **) calloc (argc+2, sizeof (char *));
- saved_argv [argc] = 0;
- while (argc--)
- {
- int i = strlen (argv [argc]) + 1;
- saved_argv [argc] = (char *) malloc (i);
- memcpy (saved_argv [argc], argv [argc], i);
- }
-}
-
-
-/* Re-execs the process with the arguments in saved_argv. Does not return.
- */
-void
-restart_process (saver_info *si)
-{
- fflush (stdout);
- fflush (stderr);
- shutdown_stderr (si);
- if (si->prefs.verbose_p)
- {
- int i;
- fprintf (stderr, "%s: re-executing", blurb());
- for (i = 0; saved_argv[i]; i++)
- fprintf (stderr, " %s", saved_argv[i]);
- fprintf (stderr, "\n");
- }
- describe_uids (si, stderr);
- fprintf (stderr, "\n");
-
- fflush (stdout);
- fflush (stderr);
- execvp (saved_argv [0], saved_argv); /* shouldn't return */
- {
- char buf [512];
- sprintf (buf, "%s: could not restart process", blurb());
- perror(buf);
- fflush(stderr);
- abort();
- }
-}
diff --git a/driver/test-fade.c b/driver/test-fade.c
index 9db773d..31852ef 100644
--- a/driver/test-fade.c
+++ b/driver/test-fade.c
@@ -1,5 +1,5 @@
/* test-fade.c --- playing with colormap and/or gamma fading.
- * xscreensaver, Copyright (c) 2001, 2004 Jamie Zawinski <jwz@jwz.org>
+ * xscreensaver, Copyright © 2001-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
@@ -21,30 +21,35 @@
#include <stdio.h>
+#include <X11/Xatom.h>
#include <X11/Intrinsic.h>
+
#include "xscreensaver.h"
+#include "resources.h"
+#include "screens.h"
#include "fade.h"
+#include "atoms.h"
-#ifdef HAVE_SGI_VC_EXTENSION
-# include <X11/extensions/XSGIvc.h>
-#endif
#ifdef HAVE_XF86VMODE_GAMMA
# include <X11/extensions/xf86vmode.h>
#endif
+#ifdef HAVE_RANDR
+# include <X11/extensions/Xrandr.h>
+#endif
XrmDatabase db = 0;
-char *progname = 0;
char *progclass = "XScreenSaver";
+Bool debug_p = True;
-#define SGI_VC_NAME "SGI-VIDEO-CONTROL"
#define XF86_VIDMODE_NAME "XFree86-VidModeExtension"
+#define RANDR_NAME "RANDR"
int
main (int argc, char **argv)
{
- int seconds = 3;
- int ticks = 20;
- int delay = 1;
+ double seconds = 3;
+ double ratio = 1/3.0;
+ int delay = 2;
int op, event, error, major, minor;
@@ -52,70 +57,151 @@ main (int argc, char **argv)
Widget toplevel_shell = XtAppInitialize (&app, progclass, 0, 0,
&argc, argv, 0, 0, 0);
Display *dpy = XtDisplay (toplevel_shell);
- Colormap *current_maps;
- int i;
-
- XtGetApplicationNameAndClass (dpy, &progname, &progclass);
+ Screen *screen = ScreenOfDisplay (dpy, 0);
+ int nwindows, i;
+ Window windows[100];
+
+ int x, y;
+ unsigned int bw, d;
+ Window root = RootWindow (dpy, 0);
+ Visual *visual = DefaultVisual (dpy, 0);
+ Pixmap logo, logo_clipmask;
+ int logo_npixels;
+ unsigned long *logo_pixels;
+ unsigned int logo_width, logo_height;
+ XSetWindowAttributes attrs;
+ unsigned long attrmask = 0;
+ void *state = 0;
+
+ verbose_p += 2;
+
+ progname = argv[0];
+ progclass = "XScreenSaver";
db = XtDatabase (dpy);
- current_maps = (Colormap *) calloc(sizeof(Colormap), ScreenCount(dpy));
- for (i = 0; i < ScreenCount(dpy); i++)
- current_maps[i] = DefaultColormap (dpy, i);
+ init_xscreensaver_atoms (dpy);
+
+ {
+ const char * version_number = "test-fade";
+ Window daemon_window =
+ XCreateWindow (dpy, RootWindow (dpy, 0),
+ 0, 0, 1, 1, 0,
+ DefaultDepth (dpy, 0), InputOutput,
+ DefaultVisual (dpy, 0), attrmask, &attrs);
+ XChangeProperty (dpy, daemon_window, XA_SCREENSAVER_VERSION, XA_STRING,
+ 8, PropModeReplace, (unsigned char *) version_number,
+ strlen (version_number));
+ }
- if (!XQueryExtension (dpy, SGI_VC_NAME, &op, &event, &error))
- fprintf(stderr, "%s: no " SGI_VC_NAME " extension\n", progname);
- else
- {
-# ifdef HAVE_SGI_VC_EXTENSION
- if (!XSGIvcQueryVersion (dpy, &major, &minor))
- fprintf(stderr, "%s: unable to get " SGI_VC_NAME " version\n",
- progname);
- else
- fprintf(stderr, "%s: " SGI_VC_NAME " version %d.%d\n",
- progname, major, minor);
-# else /* !HAVE_SGI_VC_EXTENSION */
- fprintf(stderr, "%s: no support for display's " SGI_VC_NAME
- " extension\n", progname);
-# endif /* !HAVE_SGI_VC_EXTENSION */
- }
if (!XQueryExtension (dpy, XF86_VIDMODE_NAME, &op, &event, &error))
- fprintf(stderr, "%s: no " XF86_VIDMODE_NAME " extension\n", progname);
+ fprintf(stderr, "%s: no " XF86_VIDMODE_NAME " extension\n", blurb());
else
{
# ifdef HAVE_XF86VMODE_GAMMA
if (!XF86VidModeQueryVersion (dpy, &major, &minor))
fprintf(stderr, "%s: unable to get " XF86_VIDMODE_NAME " version\n",
- progname);
+ blurb());
else
fprintf(stderr, "%s: " XF86_VIDMODE_NAME " version %d.%d\n",
- progname, major, minor);
+ blurb(), major, minor);
# else /* !HAVE_XF86VMODE_GAMMA */
fprintf(stderr, "%s: no support for display's " XF86_VIDMODE_NAME
- " extension\n", progname);
+ " extension\n", blurb());
# endif /* !HAVE_XF86VMODE_GAMMA */
}
+ if (!XQueryExtension (dpy, RANDR_NAME, &op, &event, &error))
+ fprintf(stderr, "%s: no " RANDR_NAME " extension\n", blurb());
+ else
+ {
+# ifdef HAVE_RANDR
+ if (!XRRQueryVersion (dpy, &major, &minor))
+ fprintf(stderr, "%s: unable to get " RANDR_NAME " version\n",
+ blurb());
+ else
+ fprintf(stderr, "%s: " RANDR_NAME " version %d.%d\n",
+ blurb(), major, minor);
+# else /* !HAVE_RANDR */
+ fprintf(stderr, "%s: no support for display's " RANDR_NAME
+ " extension\n", blurb());
+# endif /* !HAVE_RANDR */
+ }
+
+ logo = xscreensaver_logo (screen, visual, root, DefaultColormap (dpy, 0),
+ WhitePixel (dpy, 0),
+ &logo_pixels, &logo_npixels,
+ &logo_clipmask, True);
+ XGetGeometry (dpy, logo, &root, &x, &y, &logo_width, &logo_height, &bw, &d);
+
+ nwindows = 0;
+ {
+ int x, y;
+ for (y = 0; y < 2; y++)
+ for (x = 0; x < 2; x++)
+ {
+ int win_width = 250;
+ int win_height = 200;
+
+ attrmask = CWOverrideRedirect;
+ attrs.override_redirect = True;
+ windows[nwindows] =
+ XCreateWindow (dpy, root,
+ 200 + x * win_width * 1.5,
+ 200 + y * win_height * 1.5,
+ win_width, win_height, 0, DefaultDepth (dpy, 0),
+ InputOutput, visual,
+ attrmask, &attrs);
+ XSetWindowBackground (dpy, windows[nwindows], BlackPixel (dpy, 0));
+ XClearWindow (dpy, windows[nwindows]);
+ nwindows++;
+ }
+ }
+
fprintf (stderr, "%s: fading %d screen%s\n",
- progname, ScreenCount(dpy), ScreenCount(dpy) == 1 ? "" : "s");
+ blurb(), ScreenCount(dpy), ScreenCount(dpy) == 1 ? "" : "s");
while (1)
{
XSync (dpy, False);
- fprintf(stderr, "%s: out...", progname);
+ fprintf(stderr, "%s: fading out\n\n", blurb());
fflush(stderr);
- fade_screens (dpy, current_maps, 0, 0, seconds, ticks, True, False);
- fprintf(stderr, "done.\n");
+ fade_screens (app, dpy, windows, nwindows, seconds,
+ True, /* out_p */
+ True, /* from_desktop_p */
+ &state);
+ for (i = 0; i < nwindows; i++)
+ XMapRaised (dpy, windows[i]);
+ XSync (dpy, False);
+ fprintf(stderr, "%s: out done\n\n", blurb());
fflush(stderr);
+ for (i = 0; i < nwindows; i++)
+ {
+ XSetWindowBackgroundPixmap (dpy, windows[i], logo);
+ XClearWindow (dpy, windows[i]);
+ XSetWindowBackground (dpy, windows[i], BlackPixel (dpy, 0));
+ }
+ XSync (dpy, False);
+
if (delay) sleep (delay);
- fprintf(stderr,"%s: in...", progname);
+ fprintf(stderr, "%s: fading in\n\n", blurb());
fflush(stderr);
- fade_screens (dpy, current_maps, 0, 0, seconds, ticks, False, False);
- fprintf(stderr, "done.\n");
+ fade_screens (app, dpy, windows, nwindows,
+ seconds * ratio,
+ True, /* out_p */
+ False, /* from_desktop_p */
+ &state);
+ fade_screens (app, dpy, windows, nwindows,
+ seconds * ratio,
+ False, /* out_p */
+ False, /* from_desktop_p */
+ &state);
+ XSync (dpy, False);
+ fprintf(stderr, "%s: in done\n\n", blurb());
fflush(stderr);
if (delay) sleep (delay);
diff --git a/driver/test-grab.c b/driver/test-grab.c
index 03018eb..65313a1 100644
--- a/driver/test-grab.c
+++ b/driver/test-grab.c
@@ -1,5 +1,5 @@
/* test-uid.c --- playing with grabs.
- * xscreensaver, Copyright (c) 1999, 2004 Jamie Zawinski <jwz@jwz.org>
+ * xscreensaver, Copyright © 1999-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
@@ -24,8 +24,9 @@
#include <X11/Xlib.h>
#include <X11/Intrinsic.h>
+#include <X11/cursorfont.h>
-char *progname = 0;
+#include "blurb.h"
char *progclass = "XScreenSaver";
#define ALL_POINTER_EVENTS \
@@ -38,38 +39,89 @@ int
main (int argc, char **argv)
{
XtAppContext app;
- int kstatus, mstatus;
- Cursor cursor = 0;
- int delay = 60 * 15;
+ int kstatus = AlreadyGrabbed, mstatus = AlreadyGrabbed;
+ Cursor cursor1, cursor2;
+ int delay1 = 15;
+ int delay2 = 60 * 15 - delay1;
Widget toplevel_shell = XtAppInitialize (&app, progclass, 0, 0,
&argc, argv, 0, 0, 0);
Display *dpy = XtDisplay (toplevel_shell);
Window w = RootWindow (dpy, DefaultScreen(dpy));
- XtGetApplicationNameAndClass (dpy, &progname, &progclass);
-
- kstatus = XGrabKeyboard (dpy, w, True,
- GrabModeSync, GrabModeAsync,
- CurrentTime);
- fprintf (stderr, "%s: grabbing keyboard on 0x%lx... %s.\n",
- progname, (unsigned long) w,
- (kstatus == GrabSuccess ? "GrabSuccess" :
- kstatus == AlreadyGrabbed ? "AlreadyGrabbed" :
- kstatus == GrabInvalidTime ? "GrabInvalidTime" :
- kstatus == GrabNotViewable ? "GrabNotViewable" :
- kstatus == GrabFrozen ? "GrabFrozen" :
- "???"));
-
- mstatus = XGrabPointer (dpy, w, True, ALL_POINTER_EVENTS,
- GrabModeAsync, GrabModeAsync, None,
- cursor, CurrentTime);
- fprintf (stderr, "%s: grabbing mouse on 0x%lx... %s.\n",
- progname, (unsigned long) w,
- (mstatus == GrabSuccess ? "GrabSuccess" :
- mstatus == AlreadyGrabbed ? "AlreadyGrabbed" :
- mstatus == GrabInvalidTime ? "GrabInvalidTime" :
- mstatus == GrabNotViewable ? "GrabNotViewable" :
- mstatus == GrabFrozen ? "GrabFrozen" :
- "???"));
+ int i;
+
+ Bool grab_kbd_p = True;
+ Bool grab_mouse_p = True;
+ Bool mouse_sync_p = True;
+ Bool kbd_sync_p = True;
+
+ progname = argv[0];
+
+ cursor1 = XCreateFontCursor (dpy, XC_hand1);
+ cursor2 = XCreateFontCursor (dpy, XC_gumby);
+
+ for (i = 1; i < argc; i++)
+ {
+ const char *oa = argv[i];
+ if (argv[i][0] == '-' && argv[i][1] == '-')
+ argv[i]++;
+ if (!strcmp (argv[i], "-kbd") || !strcmp (argv[i], "-keyboard"))
+ grab_mouse_p = False;
+ else if (!strcmp (argv[i], "-mouse") || !strcmp (argv[i], "-pointer"))
+ grab_kbd_p = False;
+ else if (!strcmp (argv[i], "-kbd-sync") ||
+ !strcmp (argv[i], "-keyboard-sync"))
+ kbd_sync_p = True;
+ else if (!strcmp (argv[i], "-kbd-async") ||
+ !strcmp (argv[i], "-keyboard-async"))
+ kbd_sync_p = False;
+ else if (!strcmp (argv[i], "-mouse-sync") ||
+ !strcmp (argv[i], "-pointer-sync"))
+ mouse_sync_p = True;
+ else if (!strcmp (argv[i], "-mouse-async") ||
+ !strcmp (argv[i], "-pointer-async"))
+ mouse_sync_p = False;
+ else
+ {
+ fprintf (stderr, "%s: unknown option: %s\n", blurb(), oa);
+ exit (1);
+ }
+ }
+
+ if (grab_kbd_p)
+ {
+ kstatus = XGrabKeyboard (dpy, w, True,
+ (mouse_sync_p ? GrabModeSync : GrabModeAsync),
+ (kbd_sync_p ? GrabModeSync : GrabModeAsync),
+ CurrentTime);
+ fprintf (stderr, "%s: grabbing keyboard on 0x%lx (%s, %s)... %s\n",
+ progname, (unsigned long) w,
+ (mouse_sync_p ? "sync" : "async"),
+ (kbd_sync_p ? "sync" : "async"),
+ (kstatus == GrabSuccess ? "GrabSuccess" :
+ kstatus == AlreadyGrabbed ? "AlreadyGrabbed" :
+ kstatus == GrabInvalidTime ? "GrabInvalidTime" :
+ kstatus == GrabNotViewable ? "GrabNotViewable" :
+ kstatus == GrabFrozen ? "GrabFrozen" :
+ "???"));
+ }
+
+ if (grab_mouse_p)
+ {
+ mstatus = XGrabPointer (dpy, w, True, ALL_POINTER_EVENTS,
+ (mouse_sync_p ? GrabModeSync : GrabModeAsync),
+ (kbd_sync_p ? GrabModeSync : GrabModeAsync),
+ None, cursor1, CurrentTime);
+ fprintf (stderr, "%s: grabbing mouse on 0x%lx (%s, %s)... %s\n",
+ progname, (unsigned long) w,
+ (mouse_sync_p ? "sync" : "async"),
+ (kbd_sync_p ? "sync" : "async"),
+ (mstatus == GrabSuccess ? "GrabSuccess" :
+ mstatus == AlreadyGrabbed ? "AlreadyGrabbed" :
+ mstatus == GrabInvalidTime ? "GrabInvalidTime" :
+ mstatus == GrabNotViewable ? "GrabNotViewable" :
+ mstatus == GrabFrozen ? "GrabFrozen" :
+ "???"));
+ }
XSync(dpy, False);
@@ -77,12 +129,34 @@ main (int argc, char **argv)
{
fprintf (stderr, "%s: sleeping for %d:%02d:%02d...\n",
progname,
- delay / (60 * 60),
- (delay % (60 * 60)) / 60,
- delay % 60);
- fflush(stderr);
- sleep (delay);
- XSync(dpy, False);
+ delay1 / (60 * 60),
+ (delay1 % (60 * 60)) / 60,
+ delay1 % 60);
+ sleep (delay1);
+ fprintf (stderr, "%s: changing mouse cursor...\n", progname);
+
+ mstatus = XGrabPointer (dpy, w, True, ALL_POINTER_EVENTS,
+ (mouse_sync_p ? GrabModeSync : GrabModeAsync),
+ (kbd_sync_p ? GrabModeSync : GrabModeAsync),
+ None, cursor2, CurrentTime);
+ XSync (dpy, False);
+ if (mstatus != GrabSuccess)
+ fprintf (stderr, "%s: failed: %s\n", progname,
+ (mstatus == GrabSuccess ? "GrabSuccess" :
+ mstatus == AlreadyGrabbed ? "AlreadyGrabbed" :
+ mstatus == GrabInvalidTime ? "GrabInvalidTime" :
+ mstatus == GrabNotViewable ? "GrabNotViewable" :
+ mstatus == GrabFrozen ? "GrabFrozen" :
+ "???"));
+
+ fprintf (stderr, "%s: sleeping for %d:%02d:%02d...\n",
+ progname,
+ delay2 / (60 * 60),
+ (delay2 % (60 * 60)) / 60,
+ delay2 % 60);
+ fflush (stderr);
+ sleep (delay2);
+ XSync (dpy, False);
}
exit (0);
diff --git a/driver/test-passwd.c b/driver/test-passwd.c
index 9b4f98e..2c894ac 100644
--- a/driver/test-passwd.c
+++ b/driver/test-passwd.c
@@ -1,4 +1,4 @@
-/* xscreensaver, Copyright (c) 1998-2017 Jamie Zawinski <jwz@jwz.org>
+/* xscreensaver, Copyright © 1998-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
@@ -10,8 +10,6 @@
*/
/* This is a kludgy test harness for debugging the password dialog box.
- It's somewhat easier to debug it here than in the xscreensaver executable
- itself.
*/
#ifdef HAVE_CONFIG_H
@@ -19,288 +17,101 @@
#endif
#include <stdio.h>
-#include <ctype.h>
-#include <pwd.h>
-
-#include <X11/Xlib.h>
-#include <X11/Xatom.h>
-#include <X11/Intrinsic.h>
-#include <X11/StringDefs.h>
-#include <X11/Shell.h>
-#include <X11/Xlocale.h>
-
-#include "xscreensaver.h"
-#include "resources.h"
-#include "version.h"
-#include "visual.h"
+#include <stdlib.h>
+#include "blurb.h"
#include "auth.h"
-char *progname = 0;
-char *progclass = 0;
-XrmDatabase db = 0;
-saver_info *global_si_kludge;
-
-FILE *real_stderr, *real_stdout;
-
-void monitor_power_on (saver_info *si, Bool on_p) {}
-Bool monitor_powered_on_p (saver_info *si) { return True; }
-void initialize_screensaver_window (saver_info *si) {}
-void raise_window (saver_info *si, Bool i, Bool b, Bool d) {}
-Bool blank_screen (saver_info *si) {return False;}
-void unblank_screen (saver_info *si) {}
-void reset_watchdog_timer(saver_info *si, Bool on_p) {}
-Bool select_visual (saver_screen_info *ssi, const char *v) { return False; }
-Bool window_exists_p (Display *dpy, Window window) {return True;}
-void start_notice_events_timer (saver_info *si, Window w, Bool b) {}
-Bool handle_clientmessage (saver_info *si, XEvent *e, Bool u) { return False; }
-int BadWindow_ehandler (Display *dpy, XErrorEvent *error) { exit(1); }
-const char *signal_name(int signal) { return "???"; }
-Bool restore_real_vroot (saver_info *si) { return False; }
-void store_saver_status (saver_info *si) {}
-void saver_exit (saver_info *si, int status, const char *core) { exit(status);}
-int move_mouse_grab (saver_info *si, Window to, Cursor c, int ts) { return 0; }
-int mouse_screen (saver_info *si) { return 0; }
-void check_for_leaks (const char *where) { }
-void shutdown_stderr (saver_info *si) { }
-void resize_screensaver_window (saver_info *si) { }
-void describe_monitor_layout (saver_info *si) { }
-Bool update_screen_layout (saver_info *si) { return 0; }
-Bool in_signal_handler_p = 0;
-char *timestring (time_t when) { return ""; }
-
-const char *blurb(void) { return progname; }
-Atom XA_SCREENSAVER, XA_DEMO, XA_PREFS;
+extern Bool test_auth_conv (void *, int, auth_message *, auth_response **);
-void
-idle_timer (XtPointer closure, XtIntervalId *id)
+Bool
+test_auth_conv (void *closure,
+ int nmsgs,
+ auth_message *msg,
+ auth_response **resp)
{
- saver_info *si = (saver_info *) closure;
- XEvent fake_event;
- fake_event.type = 0; /* XAnyEvent type, ignored. */
- fake_event.xany.display = si->dpy;
- fake_event.xany.window = 0;
- XPutBackEvent (si->dpy, &fake_event);
-}
-
-static int
-text_auth_conv (
- int num_msg,
- const struct auth_message *auth_msgs,
- struct auth_response **resp,
- saver_info *si)
-{
- char *input;
- char buf[255];
- struct auth_response *responses;
+ int page = 1;
int i;
+ nmsgs = 0;
+ msg = (auth_message *) calloc (100, sizeof(*msg));
- responses = calloc(num_msg, sizeof(struct auth_response));
- if (!responses)
- return -1;
-
- /* The unlock state won't actually be used until this function returns and
- * the auth module processes the response, but set it anyway for consistency
- */
- si->unlock_state = ul_read;
-
- for (i = 0; i < num_msg; ++i)
- {
- printf ("\n%s: %s", progname, auth_msgs[i].msg);
- if ( auth_msgs[i].type == AUTH_MSGTYPE_PROMPT_NOECHO
- || auth_msgs[i].type == AUTH_MSGTYPE_PROMPT_ECHO)
- {
- input = fgets (buf, sizeof(buf)-1, stdin);
- if (!input || !*input)
- exit (0);
- if (input[strlen(input)-1] == '\n')
- input[strlen(input)-1] = 0;
-
- responses[i].response = strdup(input);
- }
- }
-
- *resp = responses;
-
- si->unlock_state = ul_finished;
-
- return 0;
-}
-
-
-#ifdef __GNUC__
- __extension__ /* shut up about "string length is greater than the length
- ISO C89 compilers are required to support" when including
- the .ad file... */
-#endif
-
-static char *fallback[] = {
-#include "XScreenSaver_ad.h"
- 0
-};
+# define DIALOG() \
+ fprintf (stderr, "\n%s: page %d\n", blurb(), page++); \
+ xscreensaver_auth_conv (closure, nmsgs, msg, resp); \
+ if (*resp) \
+ for (i = 0; i < nmsgs; i++) \
+ fprintf (stderr, "%s: resp %d = \"%s\"\n", blurb(), i, \
+ ((*resp)[i].response ? (*resp)[i].response : "")); \
+ nmsgs = 0
-extern Bool debug_passwd_window_p; /* lock.c kludge */
-int
-main (int argc, char **argv)
-{
- enum { PASS, SPLASH, TTY } which;
- Widget toplevel_shell = 0;
- saver_screen_info ssip;
- saver_info sip;
- saver_info *si = &sip;
- saver_preferences *p = &si->prefs;
- struct passwd *pw;
+ msg[nmsgs].type = AUTH_MSGTYPE_INFO;
+ msg[nmsgs].msg = "1/4 Page One";
+ nmsgs++;
- memset(&sip, 0, sizeof(sip));
- memset(&ssip, 0, sizeof(ssip));
+ msg[nmsgs].type = AUTH_MSGTYPE_INFO;
+ msg[nmsgs].msg = "2/4 All work and no play makes Jack a dull boy. "
+ "All work and no play makes Jack a dull boy. ";
+ nmsgs++;
- si->nscreens = 1;
- si->screens = si->default_screen = &ssip;
- ssip.global = si;
+ msg[nmsgs].type = AUTH_MSGTYPE_ERROR;
+ msg[nmsgs].msg = "3/4 Red";
+ nmsgs++;
- global_si_kludge = si;
- real_stderr = stderr;
- real_stdout = stdout;
+ msg[nmsgs].type = AUTH_MSGTYPE_INFO;
+ msg[nmsgs].msg = "4/4 Greets to Crash Override.";
+ nmsgs++;
+ DIALOG();
- si->version = (char *) malloc (5);
- memcpy (si->version, screensaver_id + 17, 4);
- si->version[4] = 0;
- progname = argv[0];
- {
- char *s = strrchr(progname, '/');
- if (*s) progname = s+1;
- }
- if (argc != 2) goto USAGE;
- else if (!strcmp (argv[1], "pass")) which = PASS;
- else if (!strcmp (argv[1], "splash")) which = SPLASH;
- else if (!strcmp (argv[1], "tty")) which = TTY;
- else
- {
- USAGE:
- fprintf (stderr, "usage: %s [ pass | splash | tty ]\n", progname);
- exit (1);
- }
-
-#ifdef NO_LOCKING
- if (which == PASS || which == TTY)
- {
- fprintf (stderr, "%s: compiled with NO_LOCKING\n", progname);
- exit (1);
- }
-#endif
-
-#ifndef NO_LOCKING
- /* before hack_uid() for proper permissions */
- lock_priv_init (argc, argv, True);
-
- hack_uid (si);
-
- if (! lock_init (argc, argv, True))
- {
- si->locking_disabled_p = True;
- si->nolock_reason = "error getting password";
- }
-#endif
+ msg[nmsgs].type = AUTH_MSGTYPE_INFO;
+ msg[nmsgs].msg = "1/1 Page Two";
+ nmsgs++;
+ DIALOG();
- progclass = "XScreenSaver";
- if (!setlocale (LC_CTYPE, ""))
- fprintf (stderr, "%s: warning: could not set default locale\n",
- progname);
+ msg[nmsgs].type = AUTH_MSGTYPE_INFO;
+ msg[nmsgs].msg = "1/2 Page Three";
+ nmsgs++;
+ msg[nmsgs].type = AUTH_MSGTYPE_PROMPT_NOECHO;
+ msg[nmsgs].msg = "2/2 Greets to Crash Override and also Joey";
+ nmsgs++;
- if (which != TTY)
- {
- toplevel_shell = XtAppInitialize (&si->app, progclass, 0, 0,
- &argc, argv, fallback,
- 0, 0);
- si->dpy = XtDisplay (toplevel_shell);
- p->db = XtDatabase (si->dpy);
- si->default_screen->toplevel_shell = toplevel_shell;
- si->default_screen->screen = XtScreen(toplevel_shell);
- si->default_screen->default_visual =
- si->default_screen->current_visual =
- DefaultVisualOfScreen(si->default_screen->screen);
- si->default_screen->screensaver_window =
- RootWindowOfScreen(si->default_screen->screen);
- si->default_screen->current_depth =
- visual_depth(si->default_screen->screen,
- si->default_screen->current_visual);
+ msg[nmsgs].type = AUTH_MSGTYPE_PROMPT_NOECHO;
+ msg[nmsgs].msg = "1/3 Page Four";
+ nmsgs++;
- ssip.width = WidthOfScreen(ssip.screen);
- ssip.height = HeightOfScreen(ssip.screen);
+ msg[nmsgs].type = AUTH_MSGTYPE_INFO;
+ msg[nmsgs].msg = "2/3 Hack the planet.";
+ nmsgs++;
- db = p->db;
- XtGetApplicationNameAndClass (si->dpy, &progname, &progclass);
+ msg[nmsgs].type = AUTH_MSGTYPE_INFO;
+ msg[nmsgs].msg = "3/3 Hack the planet.";
+ nmsgs++;
+ DIALOG();
- load_init_file (si->dpy, &si->prefs);
- }
- p->verbose_p = True;
+ msg[nmsgs].type = AUTH_MSGTYPE_PROMPT_ECHO;
+ msg[nmsgs].msg = "1/1 Page Five visible text";
+ nmsgs++;
- pw = getpwuid (getuid ());
- si->user = strdup (pw->pw_name);
+ msg[nmsgs].type = AUTH_MSGTYPE_PROMPT_ECHO;
+ msg[nmsgs].msg = "1/3 Page six visible text";
+ nmsgs++;
-/* si->nscreens = 0;
- si->screens = si->default_screen = 0; */
+ msg[nmsgs].type = AUTH_MSGTYPE_INFO;
+ msg[nmsgs].msg = "2/3 Poop.";
+ nmsgs++;
- while (1)
- {
-#ifndef NO_LOCKING
- if (which == PASS)
- {
- si->unlock_cb = gui_auth_conv;
- si->auth_finished_cb = auth_finished_cb;
+ msg[nmsgs].type = AUTH_MSGTYPE_INFO;
+ msg[nmsgs].msg = "3/3 \xF0\x9F\x92\xA9 \xF0\x9F\x92\xA9 \xF0\x9F\x92\xA9"
+ " \xE2\x80\x9C"
+ " \xE2\xAC\xA4 \xE2\x80\xA2 \xE2\xAD\x98 "
+ "\xE2\x80\x9D";
+ nmsgs++;
+ DIALOG();
- debug_passwd_window_p = True;
- xss_authenticate(si, True);
-
- if (si->unlock_state == ul_success)
- fprintf (stderr, "%s: authentication succeeded\n", progname);
- else
- fprintf (stderr, "%s: authentication FAILED!\n", progname);
-
- XSync(si->dpy, False);
- fprintf (stderr, "\n######################################\n\n");
- sleep (3);
- }
- else
-#endif
- if (which == SPLASH)
- {
- XEvent event;
- make_splash_dialog (si);
- XtAppAddTimeOut (si->app, p->splash_duration + 1000,
- idle_timer, (XtPointer) si);
- while (si->splash_dialog)
- {
- XtAppNextEvent (si->app, &event);
- if (event.xany.window == si->splash_dialog)
- handle_splash_event (si, &event);
- XtDispatchEvent (&event);
- }
- XSync (si->dpy, False);
- sleep (1);
- }
-#ifndef NO_LOCKING
- else if (which == TTY)
- {
- si->unlock_cb = text_auth_conv;
-
- printf ("%s: Authenticating user %s\n", progname, si->user);
- xss_authenticate(si, True);
-
- if (si->unlock_state == ul_success)
- printf ("%s: Ok!\n", progname);
- else
- printf ("%s: Wrong!\n", progname);
- }
-#endif
- else
- abort();
- }
-
- free(si->user);
+ exit(0);
}
+
diff --git a/driver/test-randr.c b/driver/test-randr.c
index 74ead37..1f88d99 100644
--- a/driver/test-randr.c
+++ b/driver/test-randr.c
@@ -1,5 +1,5 @@
/* test-randr.c --- playing with the Resize And Rotate extension.
- * xscreensaver, Copyright (c) 2004-2008 Jamie Zawinski <jwz@jwz.org>
+ * xscreensaver, Copyright © 2004-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
@@ -30,26 +30,9 @@
#include <X11/Xproto.h>
#include <X11/extensions/Xrandr.h>
-char *progname = 0;
+#include "blurb.h"
char *progclass = "XScreenSaver";
-static const char *
-blurb (void)
-{
- static char buf[255];
- time_t now = time ((time_t *) 0);
- char *ct = (char *) ctime (&now);
- int n = strlen(progname);
- if (n > 100) n = 99;
- strncpy(buf, progname, n);
- buf[n++] = ':';
- buf[n++] = ' ';
- strncpy(buf+n, ct+11, 8);
- strcpy(buf+n+9, ": ");
- return buf;
-}
-
-
static Bool error_handler_hit_p = False;
static int
@@ -72,15 +55,15 @@ main (int argc, char **argv)
Widget toplevel_shell = XtAppInitialize (&app, progclass, 0, 0,
&argc, argv, 0, 0, 0);
Display *dpy = XtDisplay (toplevel_shell);
- XtGetApplicationNameAndClass (dpy, &progname, &progclass);
+ progname = argv[0];
nscreens = ScreenCount(dpy);
if (!XRRQueryExtension(dpy, &event_number, &error_number))
{
fprintf(stderr, "%s: XRRQueryExtension(dpy, ...) ==> False\n",
blurb());
- fprintf(stderr, "%s: server does not support the RANDR extension.\n",
+ fprintf(stderr, "%s: server does not support the RANDR extension\n",
blurb());
major = -1;
}
diff --git a/driver/test-screens.c b/driver/test-screens.c
index 2fb3e35..141b7ad 100644
--- a/driver/test-screens.c
+++ b/driver/test-screens.c
@@ -1,5 +1,5 @@
/* test-screens.c --- some test cases for the "monitor sanity" checks.
- * xscreensaver, Copyright (c) 2008 Jamie Zawinski <jwz@jwz.org>
+ * xscreensaver, Copyright © 2008-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
@@ -14,39 +14,20 @@
# include "config.h"
#endif
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
#include <X11/Xlib.h>
-/* This file doesn't need the Xt headers, so stub these types out... */
-#undef XtPointer
-#define XtAppContext void*
-#define XrmDatabase void*
-#define XtIntervalId void*
-#define XtPointer void*
-#define Widget void*
-
-#include "xscreensaver.h"
+#include "blurb.h"
#include "visual.h"
+#include "screens.h"
#undef WidthOfScreen
#undef HeightOfScreen
#define WidthOfScreen(s) 10240
#define HeightOfScreen(s) 10240
-#undef screen_number
-#define screen_number(s) ((int) s)
-
-#include "screens.c" /* to get at static void check_monitor_sanity() */
-
-char *progname = 0;
-char *progclass = "XScreenSaver";
-
-const char *blurb(void) { return progname; }
-
-Bool safe_XF86VidModeGetViewPort(Display *d, int i, int *x, int *y) { abort(); }
-void initialize_screen_root_widget(saver_screen_info *ssi) { abort(); }
-Visual *get_best_gl_visual (saver_info *si, Screen *sc) { abort(); }
-
-
static const char *
failstr (monitor_sanity san)
{
diff --git a/driver/test-uid.c b/driver/test-uid.c
index 6a1f9cc..713a3fd 100644
--- a/driver/test-uid.c
+++ b/driver/test-uid.c
@@ -124,12 +124,12 @@ main (int argc, char **argv)
{
if (was_numeric)
{
- fprintf(stderr, "no group numbered %s.\n", group);
+ fprintf(stderr, "no group numbered %s\n", group);
group = "";
}
else
{
- fprintf(stderr, "no group named %s.\n", group);
+ fprintf(stderr, "no group named %s\n", group);
goto NOGROUP;
}
}
@@ -138,14 +138,14 @@ main (int argc, char **argv)
{
gid_t g2 = gid;
if (setgroups(1, &g2) == 0)
- fprintf(stderr, " succeeded.\n");
+ fprintf(stderr, " succeeded\n");
else
perror(" failed");
}
fprintf(stderr, "setgid(%ld) \"%s\"", gid, group);
if (setgid(gid) == 0)
- fprintf(stderr, " succeeded.\n");
+ fprintf(stderr, " succeeded\n");
else
perror(" failed");
@@ -177,19 +177,19 @@ main (int argc, char **argv)
{
if (was_numeric)
{
- fprintf(stderr, "no user numbered \"%s\".\n", user);
+ fprintf(stderr, "no user numbered \"%s\"\n", user);
user = "";
}
else
{
- fprintf(stderr, "no user named %s.\n", user);
+ fprintf(stderr, "no user named %s\n", user);
goto NOUSER;
}
}
fprintf(stderr, "setuid(%ld) \"%s\"", uid, user);
if (setuid(uid) == 0)
- fprintf(stderr, " succeeded.\n");
+ fprintf(stderr, " succeeded\n");
else
perror(" failed");
NOUSER: ;
diff --git a/driver/test-vp.c b/driver/test-vp.c
index bf1a0b1..ded5aca 100644
--- a/driver/test-vp.c
+++ b/driver/test-vp.c
@@ -1,5 +1,5 @@
/* test-xinerama.c --- playing with XF86VidModeGetViewPort
- * xscreensaver, Copyright (c) 2004 Jamie Zawinski <jwz@jwz.org>
+ * xscreensaver, Copyright © 2004-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
@@ -31,26 +31,9 @@
#include <X11/extensions/xf86vmode.h>
#include <X11/extensions/Xinerama.h>
-char *progname = 0;
+#include "blurb.h"
char *progclass = "XScreenSaver";
-static const char *
-blurb (void)
-{
- static char buf[255];
- time_t now = time ((time_t *) 0);
- char *ct = (char *) ctime (&now);
- int n = strlen(progname);
- if (n > 100) n = 99;
- strncpy(buf, progname, n);
- buf[n++] = ':';
- buf[n++] = ' ';
- strncpy(buf+n, ct+11, 8);
- strcpy(buf+n+9, ": ");
- return buf;
-}
-
-
static Bool error_handler_hit_p = False;
static int
@@ -129,14 +112,13 @@ main (int argc, char **argv)
Widget toplevel_shell = XtAppInitialize (&app, progclass, 0, 0,
&argc, argv, 0, 0, 0);
Display *dpy = XtDisplay (toplevel_shell);
- XtGetApplicationNameAndClass (dpy, &progname, &progclass);
if (!XF86VidModeQueryExtension(dpy, &event_number, &error_number))
{
fprintf(stderr, "%s: XF86VidModeQueryExtension(dpy, ...) ==> False\n",
blurb());
fprintf(stderr,
- "%s: server does not support the XF86VidMode extension.\n",
+ "%s: server does not support the XF86VidMode extension\n",
blurb());
exit(1);
}
diff --git a/driver/test-xdpms.c b/driver/test-xdpms.c
index b86aed3..e837d8d 100644
--- a/driver/test-xdpms.c
+++ b/driver/test-xdpms.c
@@ -1,5 +1,5 @@
/* test-xdpms.c --- playing with the XDPMS extension.
- * xscreensaver, Copyright (c) 1998-2011 Jamie Zawinski <jwz@jwz.org>
+ * xscreensaver, Copyright © 1998-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
@@ -29,42 +29,11 @@
#include <X11/Xproto.h>
#include <X11/extensions/dpms.h>
-#include <X11/extensions/dpmsstr.h>
+/*#include <X11/extensions/dpmsstr.h>*/
-extern Bool DPMSQueryExtension (Display *dpy, int *event_ret, int *error_ret);
-extern Bool DPMSCapable (Display *dpy);
-extern Status DPMSForceLevel (Display *dpy, CARD16 level);
-extern Status DPMSInfo (Display *dpy, CARD16 *power_level, BOOL *state);
-
-extern Status DPMSGetVersion (Display *dpy, int *major_ret, int *minor_ret);
-extern Status DPMSSetTimeouts (Display *dpy,
- CARD16 standby, CARD16 suspend, CARD16 off);
-extern Bool DPMSGetTimeouts (Display *dpy,
- CARD16 *standby, CARD16 *suspend, CARD16 *off);
-extern Status DPMSEnable (Display *dpy);
-extern Status DPMSDisable (Display *dpy);
-
-
-char *progname = 0;
+#include "blurb.h"
char *progclass = "XScreenSaver";
-static const char *
-blurb (void)
-{
- static char buf[255];
- time_t now = time ((time_t *) 0);
- char *ct = (char *) ctime (&now);
- int n = strlen(progname);
- if (n > 100) n = 99;
- strncpy(buf, progname, n);
- buf[n++] = ':';
- buf[n++] = ' ';
- strncpy(buf+n, ct+11, 8);
- strcpy(buf+n+9, ": ");
- return buf;
-}
-
-
static Bool error_handler_hit_p = False;
static int
@@ -90,13 +59,12 @@ main (int argc, char **argv)
Widget toplevel_shell = XtAppInitialize (&app, progclass, 0, 0,
&argc, argv, 0, 0, 0);
Display *dpy = XtDisplay (toplevel_shell);
- XtGetApplicationNameAndClass (dpy, &progname, &progclass);
if (!DPMSQueryExtension(dpy, &event_number, &error_number))
{
fprintf(stderr, "%s: DPMSQueryExtension(dpy, ...) ==> False\n",
blurb());
- fprintf(stderr, "%s: server does not support the XDPMS extension.\n",
+ fprintf(stderr, "%s: server does not support the XDPMS extension\n",
blurb());
exit(1);
}
@@ -107,7 +75,7 @@ main (int argc, char **argv)
if (!DPMSCapable(dpy))
{
fprintf(stderr, "%s: DPMSCapable(dpy) ==> False\n", blurb());
- fprintf(stderr, "%s: server says hardware doesn't support DPMS.\n",
+ fprintf(stderr, "%s: server says hardware doesn't support DPMS\n",
blurb());
exit(1);
}
@@ -158,13 +126,12 @@ main (int argc, char **argv)
state == DPMSModeSuspend ||
state == DPMSModeOff)
{
- XErrorHandler old_handler;
int st;
- fprintf(stderr, "%s: monitor is off; turning it on.\n", blurb());
+ fprintf(stderr, "%s: monitor is off; turning it on\n", blurb());
XSync (dpy, False);
error_handler_hit_p = False;
- old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
+ XSetErrorHandler (ignore_all_errors_ehandler);
XSync (dpy, False);
st = DPMSForceLevel (dpy, DPMSModeOn);
XSync (dpy, False);
diff --git a/driver/test-xinerama.c b/driver/test-xinerama.c
index 8bafbb0..cd8add4 100644
--- a/driver/test-xinerama.c
+++ b/driver/test-xinerama.c
@@ -1,5 +1,5 @@
/* test-xinerama.c --- playing with the Xinerama extension.
- * xscreensaver, Copyright (c) 2003 Jamie Zawinski <jwz@jwz.org>
+ * xscreensaver, Copyright © 2003-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
@@ -30,26 +30,9 @@
#include <X11/Xproto.h>
#include <X11/extensions/Xinerama.h>
-char *progname = 0;
+#include "blurb.h"
char *progclass = "XScreenSaver";
-static const char *
-blurb (void)
-{
- static char buf[255];
- time_t now = time ((time_t *) 0);
- char *ct = (char *) ctime (&now);
- int n = strlen(progname);
- if (n > 100) n = 99;
- strncpy(buf, progname, n);
- buf[n++] = ':';
- buf[n++] = ' ';
- strncpy(buf+n, ct+11, 8);
- strcpy(buf+n+9, ": ");
- return buf;
-}
-
-
int
main (int argc, char **argv)
{
@@ -63,13 +46,12 @@ main (int argc, char **argv)
Widget toplevel_shell = XtAppInitialize (&app, progclass, 0, 0,
&argc, argv, 0, 0, 0);
Display *dpy = XtDisplay (toplevel_shell);
- XtGetApplicationNameAndClass (dpy, &progname, &progclass);
if (!XineramaQueryExtension(dpy, &event_number, &error_number))
{
fprintf(stderr, "%s: XineramaQueryExtension(dpy, ...) ==> False\n",
blurb());
- fprintf(stderr, "%s: server does not support the Xinerama extension.\n",
+ fprintf(stderr, "%s: server does not support the Xinerama extension\n",
blurb());
exit(1);
}
@@ -80,7 +62,7 @@ main (int argc, char **argv)
if (!XineramaIsActive(dpy))
{
fprintf(stderr, "%s: XineramaIsActive(dpy) ==> False\n", blurb());
- fprintf(stderr, "%s: server says Xinerama is turned off.\n", blurb());
+ fprintf(stderr, "%s: server says Xinerama is turned off\n", blurb());
exit(1);
}
else
diff --git a/driver/test-xinput.c b/driver/test-xinput.c
new file mode 100644
index 0000000..e3a6487
--- /dev/null
+++ b/driver/test-xinput.c
@@ -0,0 +1,306 @@
+/* test-xinput.c --- playing with the XInput2 extension.
+ * xscreensaver, Copyright © 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include <time.h>
+#include <sys/time.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/Intrinsic.h>
+#include <X11/Xproto.h>
+#include <X11/extensions/XInput2.h>
+
+#include "blurb.h"
+#include "xinput.h"
+
+char *progclass = "XScreenSaver";
+Bool debug_p = True;
+
+static void
+ungrab_timer (XtPointer closure, XtIntervalId *id)
+{
+ Display *dpy = (Display *) closure;
+ fprintf (stderr, "\n%s: ungrabbing\n\n", blurb());
+ XUngrabKeyboard (dpy, CurrentTime);
+ XUngrabPointer (dpy, CurrentTime);
+}
+
+
+static const char *
+grab_string (int status)
+{
+ switch (status) {
+ case GrabSuccess: return "GrabSuccess";
+ case AlreadyGrabbed: return "AlreadyGrabbed";
+ case GrabInvalidTime: return "GrabInvalidTime";
+ case GrabNotViewable: return "GrabNotViewable";
+ case GrabFrozen: return "GrabFrozen";
+ default:
+ {
+ static char buf[255];
+ sprintf(buf, "unknown status: %d", status);
+ return buf;
+ }
+ }
+}
+
+
+int
+main (int argc, char **argv)
+{
+ XtAppContext app;
+ Widget toplevel_shell;
+ Display *dpy;
+ int xi_opcode;
+ Bool grab_kbd_p = False;
+ Bool grab_mouse_p = False;
+ Bool mouse_sync_p = True;
+ Bool kbd_sync_p = True;
+ int i;
+
+ progname = argv[0];
+
+ for (i = 1; i < argc; i++)
+ {
+ const char *oa = argv[i];
+ if (argv[i][0] == '-' && argv[i][1] == '-')
+ argv[i]++;
+ if (!strcmp (argv[i], "-grab"))
+ grab_kbd_p = grab_mouse_p = True;
+ else if (!strcmp (argv[i], "-grab-kbd") ||
+ !strcmp (argv[i], "-grab-keyboard"))
+ grab_kbd_p = True;
+ else if (!strcmp (argv[i], "-grab-mouse") ||
+ !strcmp (argv[i], "-grab-pointer"))
+ grab_mouse_p = True;
+ else if (!strcmp (argv[i], "-kbd-sync") ||
+ !strcmp (argv[i], "-keyboard-sync"))
+ kbd_sync_p = True;
+ else if (!strcmp (argv[i], "-kbd-async") ||
+ !strcmp (argv[i], "-keyboard-async"))
+ kbd_sync_p = False;
+ else if (!strcmp (argv[i], "-mouse-sync") ||
+ !strcmp (argv[i], "-pointer-sync"))
+ mouse_sync_p = True;
+ else if (!strcmp (argv[i], "-mouse-async") ||
+ !strcmp (argv[i], "-pointer-async"))
+ mouse_sync_p = False;
+ else
+ {
+ fprintf (stderr, "%s: unknown option: %s\n", blurb(), oa);
+ exit (1);
+ }
+ }
+
+ toplevel_shell = XtAppInitialize (&app, progclass, 0, 0,
+ &argc, argv, 0, 0, 0);
+ dpy = XtDisplay (toplevel_shell);
+ if (!dpy) exit(1);
+
+ if (! init_xinput (dpy, &xi_opcode))
+ exit (1);
+
+ if (grab_kbd_p || grab_mouse_p)
+ {
+ int timeout = 15;
+ Window w = RootWindow (dpy, 0);
+ int status;
+ XColor black = { 0, };
+ Pixmap bit = XCreateBitmapFromData (dpy, w, "\000", 1, 1);
+ Cursor cursor = XCreatePixmapCursor (dpy, bit, bit, &black, &black, 0, 0);
+
+ if (grab_kbd_p)
+ {
+ status = XGrabKeyboard (dpy, w, True,
+ (mouse_sync_p ? GrabModeSync : GrabModeAsync),
+ (kbd_sync_p ? GrabModeSync : GrabModeAsync),
+ CurrentTime);
+ if (status == GrabSuccess)
+ fprintf (stderr, "%s: grabbed keyboard (%s, %s)\n", blurb(),
+ (mouse_sync_p ? "sync" : "async"),
+ (kbd_sync_p ? "sync" : "async"));
+ else
+ {
+ fprintf (stderr, "%s: failed to grab keyboard (%s, %s): %s\n",
+ blurb(),
+ (mouse_sync_p ? "sync" : "async"),
+ (kbd_sync_p ? "sync" : "async"),
+ grab_string (status));
+ exit(1);
+ }
+ }
+
+ if (grab_mouse_p)
+ {
+ status = XGrabPointer (dpy, w, True,
+ (ButtonPressMask | ButtonReleaseMask |
+ EnterWindowMask | LeaveWindowMask |
+ PointerMotionMask | PointerMotionHintMask |
+ Button1MotionMask | Button2MotionMask |
+ Button3MotionMask | Button4MotionMask |
+ Button5MotionMask | ButtonMotionMask),
+ (mouse_sync_p ? GrabModeSync : GrabModeAsync),
+ (kbd_sync_p ? GrabModeSync : GrabModeAsync),
+ w, cursor, CurrentTime);
+ if (status == GrabSuccess)
+ fprintf (stderr, "%s: grabbed mouse (%s, %s)\n", blurb(),
+ (mouse_sync_p ? "sync" : "async"),
+ (kbd_sync_p ? "sync" : "async"));
+ else
+ {
+ fprintf (stderr, "%s: failed to grab mouse (%s, %s): %s\n",
+ blurb(),
+ (mouse_sync_p ? "sync" : "async"),
+ (kbd_sync_p ? "sync" : "async"),
+ grab_string (status));
+ exit(1);
+ }
+ }
+
+ fprintf (stderr, "%s: ungrabbing in %d seconds\n", blurb(), timeout);
+ XtAppAddTimeOut (app, 1000 * timeout, ungrab_timer, (XtPointer) dpy);
+ }
+
+ while (1)
+ {
+ XEvent xev;
+ XIRawEvent *re;
+
+ XtAppNextEvent (app, &xev);
+ XtDispatchEvent (&xev);
+
+ switch (xev.xany.type) {
+ case KeyPress:
+ case KeyRelease:
+ {
+ static XComposeStatus compose = { 0, };
+ KeySym keysym = 0;
+ char c[100];
+ int n;
+ n = XLookupString (&xev.xkey, c, sizeof(c)-1, &keysym, &compose);
+ c[n] = 0;
+ fprintf (stderr, "%s: X11 Key%s %02x %02x %s \"%s\"\n", blurb(),
+ (xev.xkey.type == KeyPress ? "Press " : "Release"),
+ xev.xkey.keycode, xev.xkey.state,
+ XKeysymToString (keysym), c);
+ }
+ break;
+ case ButtonPress:
+ case ButtonRelease:
+ fprintf (stderr, "%s: X11 Button%s %d %d\n", blurb(),
+ (xev.xany.type == ButtonPress ? "Press " : "Release"),
+ xev.xbutton.button, xev.xbutton.state);
+ break;
+ case MotionNotify:
+ fprintf (stderr, "%s: X11 MotionNotify %4d, %-4d\n",
+ blurb(), xev.xmotion.x_root, xev.xmotion.y_root);
+ break;
+ case GenericEvent:
+ break;
+ case EnterNotify:
+ case LeaveNotify:
+ break;
+ default:
+ fprintf (stderr, "%s: X11 event %d on 0x%lx\n",
+ blurb(), xev.xany.type, xev.xany.window);
+ break;
+ }
+
+ if (xev.xcookie.type != GenericEvent ||
+ xev.xcookie.extension != xi_opcode)
+ continue; /* not an XInput event */
+ if (!xev.xcookie.data)
+ XGetEventData (dpy, &xev.xcookie);
+ if (!xev.xcookie.data)
+ continue; /* Bogus XInput event */
+
+ re = xev.xcookie.data;
+ switch (xev.xcookie.evtype) {
+ case XI_RawKeyPress:
+ case XI_RawKeyRelease:
+ {
+ /* Fake up an XKeyEvent in order to call XKeysymToString(). */
+ XEvent ev2;
+ Bool ok = xinput_event_to_xlib (xev.xcookie.evtype,
+ (XIDeviceEvent *) re,
+ &ev2);
+ if (!ok)
+ fprintf (stderr, "%s: unable to translate XInput2 event\n",
+ blurb());
+ else
+ {
+ static XComposeStatus compose = { 0, };
+ KeySym keysym = 0;
+ char c[100];
+ int n;
+ n = XLookupString (&ev2.xkey, c, sizeof(c)-1, &keysym, &compose);
+ c[n] = 0;
+ fprintf (stderr, "%s: XI_RawKey%s %02x %02x %s \"%s\"\n",
+ blurb(),
+ (ev2.xkey.type == KeyPress ? "Press " : "Release"),
+ ev2.xkey.keycode, ev2.xkey.state,
+ XKeysymToString (keysym), c);
+ }
+ }
+ break;
+ case XI_RawButtonPress:
+ case XI_RawButtonRelease:
+ fprintf (stderr, "%s: XI_RawButton%s %d\n", blurb(),
+ (re->evtype == XI_RawButtonPress ? "Press " : "Release"),
+ re->detail);
+ break;
+ case XI_RawMotion:
+ {
+ Window root_ret, child_ret;
+ int root_x, root_y;
+ int win_x, win_y;
+ unsigned int mask;
+ XQueryPointer (dpy, DefaultRootWindow (dpy),
+ &root_ret, &child_ret, &root_x, &root_y,
+ &win_x, &win_y, &mask);
+ fprintf (stderr, "%s: XI_RawMotion %4d, %-4d %7.02f, %-7.02f\n",
+ blurb(),
+ root_x, root_y,
+ re->raw_values[0], re->raw_values[1]);
+ }
+ break;
+ case XI_RawTouchBegin:
+ fprintf (stderr, "%s: XI_RawTouchBegin\n", blurb());
+ break;
+ case XI_RawTouchEnd:
+ fprintf (stderr, "%s: XI_RawTouchEnd", blurb());
+ break;
+ case XI_RawTouchUpdate:
+ fprintf (stderr, "%s: XI_RawTouchUpdate", blurb());
+ break;
+
+ default:
+ fprintf (stderr, "%s: XInput unknown event %d\n", blurb(),
+ xev.xcookie.evtype);
+ break;
+ }
+
+ XFreeEventData (dpy, &xev.xcookie);
+ }
+
+ exit (0);
+}
diff --git a/driver/test-xkb.c b/driver/test-xkb.c
new file mode 100644
index 0000000..8d26b73
--- /dev/null
+++ b/driver/test-xkb.c
@@ -0,0 +1,89 @@
+/* test-xkb.c --- playing with the X Keyboard extension.
+ * xscreensaver, Copyright © 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include <time.h>
+#include <sys/time.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/Intrinsic.h>
+#include <X11/Xproto.h>
+#include <X11/XKBlib.h>
+
+#include "blurb.h"
+
+char *progclass = "XScreenSaver";
+
+int
+main (int argc, char **argv)
+{
+ XtAppContext app;
+ Widget toplevel_shell;
+ Display *dpy;
+ int i;
+
+ progname = argv[0];
+
+ for (i = 1; i < argc; i++)
+ {
+ const char *oa = argv[i];
+ if (argv[i][0] == '-' && argv[i][1] == '-')
+ argv[i]++;
+ if (0) ;
+ else
+ {
+ fprintf (stderr, "%s: unknown option: %s\n", blurb(), oa);
+ exit (1);
+ }
+ }
+
+ toplevel_shell = XtAppInitialize (&app, progclass, 0, 0,
+ &argc, argv, 0, 0, 0);
+ dpy = XtDisplay (toplevel_shell);
+ if (!dpy) exit(1);
+
+ {
+ XkbStateRec state;
+ XkbDescPtr desc;
+ char *group;
+ Atom name;
+
+ if (XkbGetState (dpy, XkbUseCoreKbd, &state))
+ {
+ fprintf (stderr, "%s: XkbGetState failed\n", progname);
+ exit (1);
+ }
+ desc = XkbGetKeyboard (dpy, XkbAllComponentsMask, XkbUseCoreKbd);
+ if (!desc || !desc->names)
+ {
+ fprintf (stderr, "%s: XkbGetKeyboard failed\n", progname);
+ exit (1);
+ }
+
+ name = desc->names->groups[state.group];
+ group = (name ? XGetAtomName (dpy, name) : strdup("NULL"));
+ fprintf (stderr, "%s: kbd name: %s\n", progname, group);
+ free (group);
+ }
+
+ exit (0);
+}
diff --git a/driver/test-yarandom.c b/driver/test-yarandom.c
index 6701ff8..e434377 100644
--- a/driver/test-yarandom.c
+++ b/driver/test-yarandom.c
@@ -21,10 +21,9 @@
#include <ctype.h>
#include <stdio.h>
+#include "blurb.h"
#include "yarandom.h"
-char *progname = 0;
-
int
main (int argc, char **argv)
{
diff --git a/driver/types.h b/driver/types.h
index ebea9e0..d8e4880 100644
--- a/driver/types.h
+++ b/driver/types.h
@@ -1,4 +1,4 @@
-/* xscreensaver, Copyright (c) 1993-2020 Jamie Zawinski <jwz@jwz.org>
+/* 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
@@ -14,15 +14,6 @@
typedef struct saver_info saver_info;
-typedef enum {
- ul_read, /* reading input or ready to do so */
- ul_success, /* auth success, unlock */
- ul_fail, /* auth fail */
- ul_cancel, /* user cancelled auth (pw_cancel or pw_null) */
- ul_time, /* timed out */
- ul_finished /* user pressed enter */
-} unlock_state;
-
typedef struct screenhack screenhack;
struct screenhack {
Bool enabled_p;
@@ -39,44 +30,13 @@ typedef enum {
TEXT_DATE, TEXT_LITERAL, TEXT_FILE, TEXT_PROGRAM, TEXT_URL
} text_mode;
-struct auth_message;
-struct auth_response;
-
-typedef int (*auth_conv_cb_t) (
- int num_msg,
- const struct auth_message *msg,
- struct auth_response **resp,
- saver_info *si);
-
typedef struct saver_preferences saver_preferences;
typedef struct saver_screen_info saver_screen_info;
-typedef struct passwd_dialog_data passwd_dialog_data;
-typedef struct splash_dialog_data splash_dialog_data;
-typedef struct _monitor monitor;
-
-typedef struct poll_mouse_data poll_mouse_data;
-struct poll_mouse_data {
- int root_x;
- int root_y;
- Window child;
- unsigned int mask;
- time_t time;
-};
-
-#ifdef HAVE_XINPUT
-/* XInputExtension device support */
-#include <X11/extensions/XInput.h>
-typedef struct xinput_dev_info xinput_dev_info;
-struct xinput_dev_info {
- XDevice *device;
- XEventClass press, release, valuator;
- poll_mouse_data last_poll_mouse;
-};
-#endif
/* This structure holds all the user-specified parameters, read from the
command line, the resource database, or entered through a dialog box.
+ It is used by xscreensaver-gfx and xscreensaver-settings;
*/
struct saver_preferences {
@@ -89,23 +49,20 @@ struct saver_preferences {
wrote it. */
Bool verbose_p; /* whether to print out lots of status info */
- Bool timestamp_p; /* whether to mark messages with a timestamp */
- Bool capture_stderr_p; /* whether to redirect stdout/stderr */
Bool ignore_uninstalled_p; /* whether to avoid displaying or complaining
about hacks that are not on $PATH */
Bool debug_p; /* pay no mind to the man behind the curtain */
- Bool xsync_p; /* whether XSynchronize has been called */
+ Bool xsync_p; /* whether to XSynchronize */
Bool lock_p; /* whether to lock as well as save */
Bool fade_p; /* whether to fade to black, if possible */
Bool unfade_p; /* whether to fade from black, if possible */
Time fade_seconds; /* how long that should take */
- int fade_ticks; /* how many ticks should be used */
Bool splash_p; /* whether to do a splash screen at startup */
Bool install_cmap_p; /* whether we should use our own colormap
- when using the screen's default visual. */
+ when using the screen's default visual */
# ifdef QUAD_MODE
Bool quad_p; /* whether to run four savers per monitor */
@@ -118,21 +75,17 @@ struct saver_preferences {
int selected_hack; /* in one_hack mode, this is the one */
int nice_inferior; /* nice value for subprocs */
- int inferior_memory_limit; /* setrlimit(LIMIT_AS) value for subprocs */
- Time initial_delay; /* how long to sleep after launch */
Time splash_duration; /* how long the splash screen stays up */
Time timeout; /* how much idle time before activation */
Time lock_timeout; /* how long after activation locking starts */
Time cycle; /* how long each hack should run */
- Time passwd_timeout; /* how much time before pw dialog goes down */
- Time pointer_timeout; /* how often to check mouse position */
- Time notice_events_timeout; /* how long after window creation to select */
+ Time passwd_timeout; /* how long before pw dialog goes down */
Time watchdog_timeout; /* how often to re-raise and re-blank screen */
int pointer_hysteresis; /* mouse motions less than N/sec are ignored */
- Bool dpms_enabled_p; /* Whether to power down the monitor */
- Bool dpms_quickoff_p; /* Whether to power down monitor immediately
+ Bool dpms_enabled_p; /* whether to power down the monitor */
+ Bool dpms_quickoff_p; /* whether to power down monitor immediately
in "Blank Only" mode */
Time dpms_standby; /* how long until monitor goes black */
Time dpms_suspend; /* how long until monitor power-saves */
@@ -141,7 +94,7 @@ struct saver_preferences {
Bool grab_desktop_p; /* These are not used by "xscreensaver" */
Bool grab_video_p; /* itself: they are used by the external */
Bool random_image_p; /* "xscreensaver-getimage" program, and set */
- char *image_directory; /* by the "xscreensaver-demo" configurator. */
+ char *image_directory; /* by "xscreensaver-settings". */
text_mode tmode; /* How we generate text to display. */
char *text_literal; /* used when tmode is TEXT_LITERAL. */
@@ -149,21 +102,13 @@ struct saver_preferences {
char *text_program; /* used when tmode is TEXT_PROGRAM. */
char *text_url; /* used when tmode is TEXT_URL. */
- Bool use_xidle_extension; /* which extension to use, if possible */
- Bool use_mit_saver_extension;
- Bool use_sgi_saver_extension;
- Bool use_proc_interrupts;
- Bool use_xinput_extension;
-
- Bool getviewport_full_of_lies_p; /* XFree86 bug #421 */
-
char *shell; /* where to find /bin/sh */
char *demo_command; /* How to enter demo mode. */
- char *prefs_command; /* How to edit preferences. */
char *help_url; /* Where the help document resides. */
char *load_url_command; /* How one loads URLs. */
char *new_login_command; /* Command for the "New Login" button. */
+ char *dialog_theme; /* Color scheme on the unlock dialog */
int auth_warning_slack; /* Don't warn about login failures if they
all happen within this many seconds of
@@ -172,188 +117,46 @@ struct saver_preferences {
/* This structure holds all the data that applies to the program as a whole,
or to the non-screen-specific parts of the display connection.
-
- The saver_preferences structure (prefs.h) holds all the user-specified
- parameters, read from the command line, the resource database, or entered
- through a dialog box.
+ It is used only by xscreensaver-gfx.
*/
struct saver_info {
+
+ XtAppContext app;
+ Display *dpy;
+
char *version;
saver_preferences prefs;
int nscreens;
int ssi_count;
saver_screen_info *screens;
- saver_screen_info *default_screen; /* ...on which dialogs will appear. */
- monitor **monitor_layout; /* private to screens.c */
+ struct _monitor **monitor_layout; /* private to screens.c */
Visual **best_gl_visuals; /* visuals for GL hacks on screen N */
+ void *fade_state; /* fade.c private data */
- /* =======================================================================
- global connection info
- ======================================================================= */
-
- XtAppContext app;
- Display *dpy;
-
- /* =======================================================================
- server extension info
- ======================================================================= */
-
- Bool using_xidle_extension; /* which extension is being used. */
- Bool using_mit_saver_extension; /* Note that `p->use_*' is the *request*, */
- Bool using_sgi_saver_extension; /* and `si->using_*' is the *reality*. */
- Bool using_proc_interrupts;
-
-# ifdef HAVE_MIT_SAVER_EXTENSION
- int mit_saver_ext_event_number;
- int mit_saver_ext_error_number;
-# endif
-# ifdef HAVE_SGI_SAVER_EXTENSION
- int sgi_saver_ext_event_number;
- int sgi_saver_ext_error_number;
-# endif
# ifdef HAVE_RANDR
int randr_event_number;
int randr_error_number;
Bool using_randr_extension;
# endif
- Bool using_xinput_extension; /* Note that `p->use_*' is the *request*, */
- /* and `si->using_*' is the *reality*. */
-#ifdef HAVE_XINPUT
- int xinput_ext_event_number; /* may not be used */
- int xinput_ext_error_number;
- int xinput_DeviceButtonPress; /* Extension device event codes. */
- int xinput_DeviceButtonRelease; /* Assigned by server at runtime */
- int xinput_DeviceMotionNotify;
- xinput_dev_info *xinput_devices;
- int num_xinput_devices;
-# endif
-
- /* =======================================================================
- blanking
- ======================================================================= */
-
- Bool screen_blanked_p; /* Whether the saver is currently active. */
- Window mouse_grab_window; /* Window holding our mouse grab */
- Window keyboard_grab_window; /* Window holding our keyboard grab */
- int mouse_grab_screen; /* The screen number the mouse grab is on */
- int keyboard_grab_screen; /* The screen number the keyboard grab is on */
- Bool fading_possible_p; /* Whether fading to/from black is possible. */
- Bool throttled_p; /* Whether we should temporarily just blank
- the screen, not run hacks. (Deprecated:
- users should use "xset dpms force off"
- instead.) */
- time_t blank_time; /* The time at which the screen was blanked
- (if currently blanked) or unblanked (if
- not blanked.) */
-
-
- /* =======================================================================
- locking and runtime privileges
- ======================================================================= */
-
- Bool locked_p; /* Whether the screen is currently locked. */
- Bool dbox_up_p; /* Whether the demo-mode or passwd dialogs
- are currently visible */
-
- Bool locking_disabled_p; /* Sometimes locking is impossible. */
- char *nolock_reason; /* This is why. */
-
- char *orig_uid; /* What uid/gid we had at startup, before
- discarding privileges. */
- char *uid_message; /* Any diagnostics from our attempt to
- discard privileges (printed only in
- -verbose mode.) */
- Bool dangerous_uid_p; /* Set to true if we're running as a user id
- which is known to not be a normal, non-
- privileged user. */
-
- Window passwd_dialog; /* The password dialog, if it's up. */
- passwd_dialog_data *pw_data; /* Other info necessary to draw it. */
-
- int unlock_failures; /* Counts failed login attempts while the
- screen is locked. */
- time_t unlock_failure_time; /* Time of first failed login attempt. */
- time_t unlock_dismiss_time; /* Time lock dialog most recently dismissed. */
-
- char *unlock_typeahead; /* If the screen is locked, and the user types
- a character, we assume that it is the first
- character of the password. It's stored here
- for the password dialog to use to populate
- itself. */
-
- char *user; /* The user whose session is locked. */
- char *cached_passwd; /* Cached password, used to avoid multiple
- prompts for password-only auth mechanisms.*/
- unlock_state unlock_state;
-
- auth_conv_cb_t unlock_cb; /* The function used to prompt for creds. */
- void (*auth_finished_cb) (saver_info *si);
- /* Called when authentication has finished,
- regardless of success or failure.
- May be NULL. */
-
-
- /* =======================================================================
- demoing
- ======================================================================= */
-
Bool demoing_p; /* Whether we are demoing a single hack
(without UI.) */
-
- Window splash_dialog; /* The splash dialog, if its up. */
- splash_dialog_data *sp_data; /* Other info necessary to draw it. */
-
-
- /* =======================================================================
- timers
- ======================================================================= */
-
- XtIntervalId lock_id; /* Timer to implement `prefs.lock_timeout' */
- XtIntervalId cycle_id; /* Timer to implement `prefs.cycle' */
- XtIntervalId timer_id; /* Timer to implement `prefs.timeout' */
+ Bool emergency_p; /* Restarted because of a crash */
XtIntervalId watchdog_id; /* Timer to implement `prefs.watchdog */
- XtIntervalId check_pointer_timer_id; /* `prefs.pointer_timeout' */
-
- XtIntervalId de_race_id; /* Timer to make sure screen un-blanks */
- int de_race_ticks;
-
- time_t last_activity_time; /* Used only when no server exts. */
- time_t last_wall_clock_time; /* Used to detect laptop suspend. */
- saver_screen_info *last_activity_screen;
-
- Bool emergency_lock_p; /* Set when the wall clock has jumped
- (presumably due to laptop suspend) and we
- need to lock down right away instead of
- waiting for the lock timer to go off. */
-
-
- /* =======================================================================
- remote control
- ======================================================================= */
int selection_mode; /* Set to -1 if the NEXT ClientMessage has just
been received; set to -2 if PREV has just
been received; set to N if SELECT or DEMO N
has been received. (This is kind of nasty.)
*/
-
- /* =======================================================================
- subprocs
- ======================================================================= */
-
- XtIntervalId stderr_popup_timer;
-
-# ifdef HAVE_LIBSYSTEMD
- pid_t systemd_pid;
-# endif
};
+
/* This structure holds all the data that applies to the screen-specific parts
of the display connection; if the display has multiple screens, there will
- be one of these for each screen.
- */
+ be one of these for each screen. It is used only by xscreensaver-gfx.
+*/
struct saver_screen_info {
saver_info *global;
@@ -370,17 +173,10 @@ struct saver_screen_info {
Bool real_screen_p; /* This will be true of exactly one ssi per
X screen. */
- Widget toplevel_shell;
-
- /* =======================================================================
- blanking
- ======================================================================= */
- Window screensaver_window; /* The window that will impersonate the root,
- when the screensaver activates. Note that
- the window stored here may change, as we
- destroy and recreate it on different
- visuals. */
+ Window screensaver_window; /* The window on which hacks are drawn. This
+ window might be destroyed and re-created as
+ hacks cycle. */
Colormap cmap; /* The colormap that goes with the window. */
Bool install_cmap_p; /* Whether this screen should have its own
colormap installed, for whichever of several
@@ -392,55 +188,31 @@ struct saver_screen_info {
Visual *default_visual; /* visual to use when none other specified */
- Window real_vroot; /* The original virtual-root window. */
- Window real_vroot_value; /* What was in the __SWM_VROOT property. */
-
Cursor cursor; /* A blank cursor that goes with the
real root window. */
unsigned long black_pixel; /* Black, allocated from `cmap'. */
-
- int blank_vp_x, blank_vp_y; /* Where the virtual-scrolling viewport was
- when the screen went blank. We need to
- prevent the X server from letting the mouse
- bump the edges to scroll while the screen
- is locked, so we reset to this when it has
- moved, and the lock dialog is up... */
-
-# ifdef HAVE_MIT_SAVER_EXTENSION
- Window server_mit_saver_window;
-# endif
+ Window error_dialog; /* Error message about crashed savers */
- /* =======================================================================
- demoing
- ======================================================================= */
-
- Colormap demo_cmap; /* The colormap that goes with the dialogs:
- this might be the same as `cmap' so care
- must be taken not to free it while it's
- still in use. */
-
- /* =======================================================================
- timers
- ======================================================================= */
+ XtIntervalId cycle_id; /* Timer to implement `prefs.cycle' */
+ time_t cycle_at; /* When cycle_id will fire */
+ int current_hack; /* Index into `prefs.screenhacks' */
+ pid_t pid;
+};
- poll_mouse_data last_poll_mouse; /* Used only when no server exts. */
- /* =======================================================================
- subprocs
- ======================================================================= */
+/* From dpms.c */
+extern void sync_server_dpms_settings (Display *, struct saver_preferences *);
- int current_hack; /* Index into `prefs.screenhacks' */
- pid_t pid;
- int stderr_text_x;
- int stderr_text_y;
- int stderr_line_height;
- XFontStruct *stderr_font;
- GC stderr_gc;
- Window stderr_overlay_window; /* Used if the server has overlay planes */
- Colormap stderr_cmap;
-};
+const char *init_file_name (void);
+extern Bool init_file_changed_p (saver_preferences *);
+extern void load_init_file (Display *, saver_preferences *);
+extern int write_init_file (Display *,
+ saver_preferences *, const char *version_string,
+ Bool verbose_p);
+extern Bool senescent_p (void);
+char *make_hack_name (Display *, const char *shell_command);
#endif /* __XSCREENSAVER_TYPES_H__ */
diff --git a/driver/windows.c b/driver/windows.c
index 9e47c45..c9cdf9d 100644
--- a/driver/windows.c
+++ b/driver/windows.c
@@ -1,5 +1,5 @@
/* windows.c --- turning the screen black; dealing with visuals, virtual roots.
- * xscreensaver, Copyright (c) 1991-2020 Jamie Zawinski <jwz@jwz.org>
+ * xscreensaver, Copyright © 1991-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
@@ -14,41 +14,17 @@
# include "config.h"
#endif
-#ifdef VMS
-# include <unixlib.h> /* for getpid() */
-# include "vms-gtod.h" /* for gettimeofday() */
-#endif /* VMS */
-
-#ifndef VMS
-# include <pwd.h> /* for getpwuid() */
-#else /* VMS */
-# include "vms-pwd.h"
-#endif /* VMS */
-
#ifdef HAVE_UNAME
# include <sys/utsname.h> /* for uname() */
#endif /* HAVE_UNAME */
#include <stdio.h>
-/* #include <X11/Xproto.h> / * for CARD32 */
+#include <pwd.h> /* for getpwuid() */
#include <X11/Xlib.h>
#include <X11/Xutil.h> /* for XSetClassHint() */
#include <X11/Xatom.h>
-#include <X11/Xos.h> /* for time() */
-#include <signal.h> /* for the signal names */
+#include <X11/Intrinsic.h>
#include <time.h>
-#include <sys/time.h>
-
-/* You might think that to store an array of 32-bit quantities onto a
- server-side property, you would pass an array of 32-bit data quantities
- into XChangeProperty(). You would be wrong. You have to use an array
- of longs, even if long is 64 bits (using 32 of each 64.)
- */
-typedef long PROP32;
-
-#ifdef HAVE_MIT_SAVER_EXTENSION
-# include <X11/extensions/scrnsaver.h>
-#endif /* HAVE_MIT_SAVER_EXTENSION */
#ifdef HAVE_XF86VMODE
# include <X11/extensions/xf86vmode.h>
@@ -58,1118 +34,100 @@ typedef long PROP32;
# include <X11/extensions/Xinerama.h>
#endif /* HAVE_XINERAMA */
-/* This file doesn't need the Xt headers, so stub these types out... */
-#undef XtPointer
-#define XtAppContext void*
-#define XrmDatabase void*
-#define XtIntervalId void*
-#define XtPointer void*
-#define Widget void*
-
#include "xscreensaver.h"
+#include "atoms.h"
#include "visual.h"
+#include "screens.h"
#include "fade.h"
+#include "resources.h"
+#include "xft.h"
+#include "font-retry.h"
extern int kill (pid_t, int); /* signal() is in sys/signal.h... */
-Atom XA_VROOT, XA_XSETROOT_ID, XA_ESETROOT_PMAP_ID, XA_XROOTPMAP_ID;
-Atom XA_NET_WM_USER_TIME;
-Atom XA_SCREENSAVER, XA_SCREENSAVER_VERSION, XA_SCREENSAVER_ID;
-Atom XA_SCREENSAVER_STATUS;
-
-extern saver_info *global_si_kludge; /* I hate C so much... */
-
-static void maybe_transfer_grabs (saver_screen_info *ssi,
- Window old_w, Window new_w, int new_screen);
-
-#define ALL_POINTER_EVENTS \
- (ButtonPressMask | ButtonReleaseMask | EnterWindowMask | \
- LeaveWindowMask | PointerMotionMask | PointerMotionHintMask | \
- Button1MotionMask | Button2MotionMask | Button3MotionMask | \
- Button4MotionMask | Button5MotionMask | ButtonMotionMask)
-
-
-static const char *
-grab_string(int status)
-{
- switch (status)
- {
- case GrabSuccess: return "GrabSuccess";
- case AlreadyGrabbed: return "AlreadyGrabbed";
- case GrabInvalidTime: return "GrabInvalidTime";
- case GrabNotViewable: return "GrabNotViewable";
- case GrabFrozen: return "GrabFrozen";
- default:
- {
- static char foo[255];
- sprintf(foo, "unknown status: %d", status);
- return foo;
- }
- }
-}
-
-static int
-grab_kbd(saver_info *si, Window w, int screen_no)
-{
- saver_preferences *p = &si->prefs;
- int status = XGrabKeyboard (si->dpy, w, True,
- /* I don't really understand Sync vs Async,
- but these seem to work... */
- GrabModeSync, GrabModeAsync,
- CurrentTime);
- if (status == GrabSuccess)
- {
- si->keyboard_grab_window = w;
- si->keyboard_grab_screen = screen_no;
- }
-
- if (p->verbose_p)
- fprintf(stderr, "%s: %d: grabbing keyboard on 0x%lx... %s.\n",
- blurb(), screen_no, (unsigned long) w, grab_string(status));
- return status;
-}
-
-
-static int
-grab_mouse (saver_info *si, Window w, Cursor cursor, int screen_no)
-{
- saver_preferences *p = &si->prefs;
- int status = XGrabPointer (si->dpy, w, True, ALL_POINTER_EVENTS,
- GrabModeAsync, GrabModeAsync, w,
- cursor, CurrentTime);
- if (status == GrabSuccess)
- {
- si->mouse_grab_window = w;
- si->mouse_grab_screen = screen_no;
- }
-
- if (p->verbose_p)
- fprintf(stderr, "%s: %d: grabbing mouse on 0x%lx... %s.\n",
- blurb(), screen_no, (unsigned long) w, grab_string(status));
- return status;
-}
-
-
-static void
-ungrab_kbd(saver_info *si)
-{
- saver_preferences *p = &si->prefs;
- XUngrabKeyboard(si->dpy, CurrentTime);
- if (p->verbose_p)
- fprintf(stderr, "%s: %d: ungrabbing keyboard (was 0x%lx).\n",
- blurb(), si->keyboard_grab_screen,
- (unsigned long) si->keyboard_grab_window);
- si->keyboard_grab_window = 0;
-}
-
-
-static void
-ungrab_mouse(saver_info *si)
-{
- saver_preferences *p = &si->prefs;
- XUngrabPointer(si->dpy, CurrentTime);
- if (p->verbose_p)
- fprintf(stderr, "%s: %d: ungrabbing mouse (was 0x%lx).\n",
- blurb(), si->mouse_grab_screen,
- (unsigned long) si->mouse_grab_window);
- si->mouse_grab_window = 0;
-}
-
-
-/* Apparently there is this program called "rdesktop" which is a windows
- terminal server client for Unix. It would seem that this program holds
- the keyboard GRABBED the whole time it has focus! This is, of course,
- completely idiotic: the whole point of grabbing is to get events when
- you do *not* have focus, so grabbing *only when* you have focus is
- completely redundant -- unless your goal is to make xscreensaver not
- able to ever lock the screen when your program is running.
-
- If xscreensaver blanks while rdesktop still has a keyboard grab, then
- when we try to prompt for the password, we won't get the characters:
- they'll be typed into rdesktop.
-
- Perhaps rdesktop will release its keyboard grab if it loses focus?
- What the hell, let's give it a try. If we fail to grab the keyboard
- four times in a row, we forcibly set focus to "None" and try four
- more times. (We don't touch focus unless we're already having a hard
- time getting a grab.)
- */
-static void
-nuke_focus (saver_info *si, int screen_no)
-{
- saver_preferences *p = &si->prefs;
- Window focus = 0;
- int rev = 0;
-
- XGetInputFocus (si->dpy, &focus, &rev);
-
- if (p->verbose_p)
- {
- char w[255], r[255];
-
- if (focus == PointerRoot) strcpy (w, "PointerRoot");
- else if (focus == None) strcpy (w, "None");
- else sprintf (w, "0x%lx", (unsigned long) focus);
-
- if (rev == RevertToParent) strcpy (r, "RevertToParent");
- else if (rev == RevertToPointerRoot) strcpy (r, "RevertToPointerRoot");
- else if (rev == RevertToNone) strcpy (r, "RevertToNone");
- else sprintf (r, "0x%x", rev);
-
- fprintf (stderr, "%s: %d: removing focus from %s / %s.\n",
- blurb(), screen_no, w, r);
- }
-
- XSetInputFocus (si->dpy, None, RevertToNone, CurrentTime);
- XSync (si->dpy, False);
-}
-
-
-static void
-ungrab_keyboard_and_mouse (saver_info *si)
-{
- ungrab_mouse (si);
- ungrab_kbd (si);
-}
-
-
-static Bool
-grab_keyboard_and_mouse (saver_info *si, Window window, Cursor cursor,
- int screen_no)
-{
- Status mstatus = 0, kstatus = 0;
- int i;
- int retries = 4;
- Bool focus_fuckus = False;
-
- AGAIN:
-
- for (i = 0; i < retries; i++)
- {
- XSync (si->dpy, False);
- kstatus = grab_kbd (si, window, screen_no);
- if (kstatus == GrabSuccess)
- break;
-
- /* else, wait a second and try to grab again. */
- sleep (1);
- }
-
- if (kstatus != GrabSuccess)
- {
- fprintf (stderr, "%s: couldn't grab keyboard! (%s)\n",
- blurb(), grab_string(kstatus));
-
- if (! focus_fuckus)
- {
- focus_fuckus = True;
- nuke_focus (si, screen_no);
- goto AGAIN;
- }
- }
-
- for (i = 0; i < retries; i++)
- {
- XSync (si->dpy, False);
- mstatus = grab_mouse (si, window, cursor, screen_no);
- if (mstatus == GrabSuccess)
- break;
-
- /* else, wait a second and try to grab again. */
- sleep (1);
- }
-
- if (mstatus != GrabSuccess)
- fprintf (stderr, "%s: couldn't grab pointer! (%s)\n",
- blurb(), grab_string(mstatus));
-
-
- /* When should we allow blanking to proceed? The current theory
- is that a keyboard grab is mandatory; a mouse grab is optional.
-
- - If we don't have a keyboard grab, then we won't be able to
- read a password to unlock, so the kbd grab is mandatory.
- (We can't conditionalize this on locked_p, because someone
- might run "xscreensaver-command -lock" at any time.)
-
- - If we don't have a mouse grab, then we might not see mouse
- clicks as a signal to unblank -- but we will still see kbd
- activity, so that's not a disaster.
-
- It has been suggested that we should allow blanking if locking
- is disabled, and we have a mouse grab but no keyboard grab
- (that is: kstatus != GrabSuccess &&
- mstatus == GrabSuccess &&
- si->locking_disabled_p)
- That would allow screen blanking (but not locking) while the gdm
- login screen had the keyboard grabbed, but one would have to use
- the mouse to unblank. Keyboard characters would go to the gdm
- login field without unblanking. I have not made this change
- because I'm not completely convinced it is a safe thing to do.
- */
-
- if (kstatus != GrabSuccess) /* Do not blank without a kbd grab. */
- {
- /* If we didn't get both grabs, release the one we did get. */
- ungrab_keyboard_and_mouse (si);
- return False;
- }
-
- return True; /* Grab is good, go ahead and blank. */
-}
-
-
-int
-move_mouse_grab (saver_info *si, Window to, Cursor cursor, int to_screen_no)
-{
- Window old = si->mouse_grab_window;
-
- if (old == 0)
- return grab_mouse (si, to, cursor, to_screen_no);
- else
- {
- saver_preferences *p = &si->prefs;
- int status;
-
- XSync (si->dpy, False);
- XGrabServer (si->dpy); /* ############ DANGER! */
- XSync (si->dpy, False);
-
- if (p->verbose_p)
- fprintf(stderr, "%s: grabbing server...\n", blurb());
-
- ungrab_mouse (si);
- status = grab_mouse (si, to, cursor, to_screen_no);
-
- if (status != GrabSuccess) /* Augh! */
- {
- sleep (1); /* Note dramatic evil of sleeping
- with server grabbed. */
- XSync (si->dpy, False);
- status = grab_mouse (si, to, cursor, to_screen_no);
- }
-
- if (status != GrabSuccess) /* Augh! Try to get the old one back... */
- grab_mouse (si, old, cursor, to_screen_no);
-
- XUngrabServer (si->dpy);
- XSync (si->dpy, False); /* ###### (danger over) */
-
- if (p->verbose_p)
- fprintf(stderr, "%s: ungrabbing server.\n", blurb());
-
- return status;
- }
-}
-
-
-/* Prints an error message to stderr and returns True if there is another
- xscreensaver running already. Silently returns False otherwise. */
-Bool
-ensure_no_screensaver_running (Display *dpy, Screen *screen)
-{
- Bool status = 0;
- int i;
- Window root = RootWindowOfScreen (screen);
- Window root2, parent, *kids;
- unsigned int nkids;
- XErrorHandler old_handler = XSetErrorHandler (BadWindow_ehandler);
-
- if (! XQueryTree (dpy, root, &root2, &parent, &kids, &nkids))
- abort ();
- if (root != root2)
- abort ();
- if (parent)
- abort ();
- for (i = 0; i < nkids; i++)
- {
- Atom type;
- int format;
- unsigned long nitems, bytesafter;
- unsigned char *version;
-
- if (XGetWindowProperty (dpy, kids[i], XA_SCREENSAVER_VERSION, 0, 1,
- False, XA_STRING, &type, &format, &nitems,
- &bytesafter, &version)
- == Success
- && type != None)
- {
- unsigned char *id;
- if (XGetWindowProperty (dpy, kids[i], XA_SCREENSAVER_ID, 0, 512,
- False, XA_STRING, &type, &format, &nitems,
- &bytesafter, &id)
- != Success
- || type == None)
- id = (unsigned char *) "???";
-
- fprintf (stderr,
- "%s: already running on display %s (window 0x%x)\n from process %s.\n",
- blurb(), DisplayString (dpy), (int) kids [i],
- (char *) id);
- status = True;
- }
-
- else if (XGetWindowProperty (dpy, kids[i], XA_WM_COMMAND, 0, 128,
- False, XA_STRING, &type, &format, &nitems,
- &bytesafter, &version)
- == Success
- && type != None
- && !strcmp ((char *) version, "gnome-screensaver"))
- {
- fprintf (stderr,
- "%s: \"%s\" is already running on display %s (window 0x%x)\n",
- blurb(), (char *) version,
- DisplayString (dpy), (int) kids [i]);
- status = True;
- break;
- }
- }
-
- if (kids) XFree ((char *) kids);
- XSync (dpy, False);
- XSetErrorHandler (old_handler);
- return status;
-}
-
-
-
-/* Virtual-root hackery */
-
#ifdef _VROOT_H_
ERROR! You must not include vroot.h in this file.
#endif
-static void
-store_vroot_property (Display *dpy, Window win, Window value)
-{
-#if 0
- if (p->verbose_p)
- fprintf (stderr,
- "%s: storing XA_VROOT = 0x%x (%s) = 0x%x (%s)\n", blurb(),
- win,
- (win == screensaver_window ? "ScreenSaver" :
- (win == real_vroot ? "VRoot" :
- (win == real_vroot_value ? "Vroot_value" : "???"))),
- value,
- (value == screensaver_window ? "ScreenSaver" :
- (value == real_vroot ? "VRoot" :
- (value == real_vroot_value ? "Vroot_value" : "???"))));
-#endif
- XChangeProperty (dpy, win, XA_VROOT, XA_WINDOW, 32, PropModeReplace,
- (unsigned char *) &value, 1);
-}
-
-static void
-remove_vroot_property (Display *dpy, Window win)
-{
-#if 0
- if (p->verbose_p)
- fprintf (stderr, "%s: removing XA_VROOT from 0x%x (%s)\n", blurb(), win,
- (win == screensaver_window ? "ScreenSaver" :
- (win == real_vroot ? "VRoot" :
- (win == real_vroot_value ? "Vroot_value" : "???"))));
-#endif
- XDeleteProperty (dpy, win, XA_VROOT);
-}
-
-static Bool safe_XKillClient (Display *dpy, XID id);
+static void reset_watchdog_timer (saver_info *si);
-static void
-kill_xsetroot_data_1 (Display *dpy, Window window,
- Atom prop, const char *atom_name,
- Bool verbose_p)
+void
+store_saver_status (saver_info *si)
{
- Atom type;
- int format;
- unsigned long nitems, bytesafter;
- unsigned char *dataP = 0;
-
- /* If the user has been using xv or xsetroot as a screensaver (to display
- an image on the screensaver window, as a kind of slideshow) then the
- pixmap and its associated color cells have been put in RetainPermanent
- CloseDown mode. Since we're not destroying the xscreensaver window,
- but merely unmapping it, we need to free these resources or those
- colormap cells will stay allocated while the screensaver is off. (We
- could just delete the screensaver window and recreate it later, but
- that could cause other problems.) This code does an atomic read-and-
- delete of the _XSETROOT_ID property, and if it held a pixmap, then we
- cause the RetainPermanent resources of the client which created it
- (and which no longer exists) to be freed.
-
- Update: it seems that Gnome and KDE do this same trick, but with the
- properties "ESETROOT_PMAP_ID" and/or "_XROOTPMAP_ID" instead of
- "_XSETROOT_ID". So, we'll kill those too.
- */
- if (XGetWindowProperty (dpy, window, prop, 0, 1,
- True, AnyPropertyType, &type, &format, &nitems,
- &bytesafter, &dataP)
- == Success
- && type != None)
- {
- Pixmap *pixP = (Pixmap *) dataP;
- if (pixP && *pixP && type == XA_PIXMAP && format == 32 &&
- nitems == 1 && bytesafter == 0)
- {
- if (verbose_p)
- fprintf (stderr, "%s: destroying %s data (0x%lX).\n",
- blurb(), atom_name, *pixP);
- safe_XKillClient (dpy, *pixP);
- }
- else
- fprintf (stderr,
- "%s: deleted unrecognised %s property: \n"
- "\t%lu, %lu; type: %lu, format: %d, "
- "nitems: %lu, bytesafter %ld\n",
- blurb(), atom_name,
- (unsigned long) pixP, (pixP ? *pixP : 0), type,
- format, nitems, bytesafter);
- }
-}
+ /* The contents of XA_SCREENSAVER_STATUS has LOCK/BLANK/0 in the first slot,
+ the time at which that state began in the second slot, and the ordinal of
+ the running hacks on each screen (1-based) in subsequent slots. Since
+ we don't know the blank-versus-lock status here, we leave whatever was
+ there before unchanged: it will be updated by "xscreensaver".
+ XA_SCREENSAVER_STATUS is stored on the (real) root window of screen 0.
-static void
-kill_xsetroot_data (Display *dpy, Window w, Bool verbose_p)
-{
- kill_xsetroot_data_1 (dpy, w, XA_XSETROOT_ID, "_XSETROOT_ID", verbose_p);
- kill_xsetroot_data_1 (dpy, w, XA_ESETROOT_PMAP_ID, "ESETROOT_PMAP_ID",
- verbose_p);
- kill_xsetroot_data_1 (dpy, w, XA_XROOTPMAP_ID, "_XROOTPMAP_ID", verbose_p);
-}
+ XA_SCREENSAVER_VERSION and XA_SCREENSAVER_ID are stored on the unmapped
+ window created by the "xscreensaver" process. ClientMessage events are
+ sent to that window, and the responses are sent via the
+ XA_SCREENSAVER_RESPONSE property on it.
+ These properties are not used on the windows created by "xscreensaver-gfx"
+ for use by the display hacks.
-static void
-save_real_vroot (saver_screen_info *ssi)
-{
- saver_info *si = ssi->global;
+ See the different version of this function in xscreensaver.c.
+ */
Display *dpy = si->dpy;
- Screen *screen = ssi->screen;
+ Window w = RootWindow (dpy, 0); /* always screen 0 */
+ Atom type;
+ unsigned char *dataP = 0;
+ PROP32 *status = 0;
+ int format;
+ unsigned long nitems, bytesafter;
+ int nitems2 = si->nscreens + 2;
int i;
- Window root = RootWindowOfScreen (screen);
- Window root2, parent, *kids;
- unsigned int nkids;
- XErrorHandler old_handler;
-
- /* It's possible that a window might be deleted between our call to
- XQueryTree() and our call to XGetWindowProperty(). Don't die if
- that happens (but just ignore that window, it's not the one we're
- interested in anyway.)
- */
- XSync (dpy, False);
- old_handler = XSetErrorHandler (BadWindow_ehandler);
- XSync (dpy, False);
-
- ssi->real_vroot = 0;
- ssi->real_vroot_value = 0;
- if (! XQueryTree (dpy, root, &root2, &parent, &kids, &nkids))
- abort ();
- if (root != root2)
- abort ();
- if (parent)
- abort ();
- for (i = 0; i < nkids; i++)
- {
- Atom type;
- int format;
- unsigned long nitems, bytesafter;
- unsigned char *dataP = 0;
- Window *vrootP;
- int j;
-
- /* Skip this window if it is the xscreensaver window of any other
- screen (this can happen in the Xinerama case.)
- */
- for (j = 0; j < si->nscreens; j++)
- {
- saver_screen_info *ssi2 = &si->screens[j];
- if (kids[i] == ssi2->screensaver_window)
- goto SKIP;
- }
-
- if (XGetWindowProperty (dpy, kids[i], XA_VROOT, 0, 1, False, XA_WINDOW,
- &type, &format, &nitems, &bytesafter,
- &dataP)
- != Success)
- continue;
- if (! dataP)
- continue;
- vrootP = (Window *) dataP;
- if (ssi->real_vroot)
- {
- if (*vrootP == ssi->screensaver_window) abort ();
- fprintf (stderr,
- "%s: more than one virtual root window found (0x%x and 0x%x).\n",
- blurb(), (int) ssi->real_vroot, (int) kids [i]);
- exit (1);
- }
- ssi->real_vroot = kids [i];
- ssi->real_vroot_value = *vrootP;
- SKIP:
- ;
- }
+ /* Read the old property, so we can change just our parts. */
+ XGetWindowProperty (dpy, w,
+ XA_SCREENSAVER_STATUS,
+ 0, 999, False, XA_INTEGER,
+ &type, &format, &nitems, &bytesafter,
+ &dataP);
- XSync (dpy, False);
- XSetErrorHandler (old_handler);
- XSync (dpy, False);
+ status = (PROP32 *) calloc (nitems2, sizeof(PROP32));
- if (ssi->real_vroot)
+ if (dataP && type == XA_INTEGER && nitems >= 3)
{
- remove_vroot_property (si->dpy, ssi->real_vroot);
- XSync (dpy, False);
+ status[0] = ((PROP32 *) dataP)[0];
+ status[1] = ((PROP32 *) dataP)[1];
}
- XFree ((char *) kids);
-}
-
-
-static Bool
-restore_real_vroot_1 (saver_screen_info *ssi)
-{
- saver_info *si = ssi->global;
- saver_preferences *p = &si->prefs;
- if (p->verbose_p && ssi->real_vroot)
- fprintf (stderr,
- "%s: restoring __SWM_VROOT property on the real vroot (0x%lx).\n",
- blurb(), (unsigned long) ssi->real_vroot);
- if (ssi->screensaver_window)
- remove_vroot_property (si->dpy, ssi->screensaver_window);
- if (ssi->real_vroot)
- {
- store_vroot_property (si->dpy, ssi->real_vroot, ssi->real_vroot_value);
- ssi->real_vroot = 0;
- ssi->real_vroot_value = 0;
- /* make sure the property change gets there before this process
- terminates! We might be doing this because we have intercepted
- SIGTERM or something. */
- XSync (si->dpy, False);
- return True;
- }
- return False;
-}
-
-Bool
-restore_real_vroot (saver_info *si)
-{
- int i;
- Bool did_any = False;
for (i = 0; i < si->nscreens; i++)
{
saver_screen_info *ssi = &si->screens[i];
- if (restore_real_vroot_1 (ssi))
- did_any = True;
- }
- return did_any;
-}
-
-
-/* Signal hackery to ensure that the vroot doesn't get left in an
- inconsistent state
- */
-
-const char *
-signal_name(int signal)
-{
- switch (signal) {
- case SIGHUP: return "SIGHUP";
- case SIGINT: return "SIGINT";
- case SIGQUIT: return "SIGQUIT";
- case SIGILL: return "SIGILL";
- case SIGTRAP: return "SIGTRAP";
-#ifdef SIGABRT
- case SIGABRT: return "SIGABRT";
-#endif
- case SIGFPE: return "SIGFPE";
- case SIGKILL: return "SIGKILL";
- case SIGBUS: return "SIGBUS";
- case SIGSEGV: return "SIGSEGV";
- case SIGPIPE: return "SIGPIPE";
- case SIGALRM: return "SIGALRM";
- case SIGTERM: return "SIGTERM";
-#ifdef SIGSTOP
- case SIGSTOP: return "SIGSTOP";
-#endif
-#ifdef SIGCONT
- case SIGCONT: return "SIGCONT";
-#endif
-#ifdef SIGUSR1
- case SIGUSR1: return "SIGUSR1";
-#endif
-#ifdef SIGUSR2
- case SIGUSR2: return "SIGUSR2";
-#endif
-#ifdef SIGEMT
- case SIGEMT: return "SIGEMT";
-#endif
-#ifdef SIGSYS
- case SIGSYS: return "SIGSYS";
-#endif
-#ifdef SIGCHLD
- case SIGCHLD: return "SIGCHLD";
-#endif
-#ifdef SIGPWR
- case SIGPWR: return "SIGPWR";
-#endif
-#ifdef SIGWINCH
- case SIGWINCH: return "SIGWINCH";
-#endif
-#ifdef SIGURG
- case SIGURG: return "SIGURG";
-#endif
-#ifdef SIGIO
- case SIGIO: return "SIGIO";
-#endif
-#ifdef SIGVTALRM
- case SIGVTALRM: return "SIGVTALRM";
-#endif
-#ifdef SIGXCPU
- case SIGXCPU: return "SIGXCPU";
-#endif
-#ifdef SIGXFSZ
- case SIGXFSZ: return "SIGXFSZ";
-#endif
-#ifdef SIGDANGER
- case SIGDANGER: return "SIGDANGER";
-#endif
- default:
- {
- static char buf[50];
- sprintf(buf, "signal %d\n", signal);
- return buf;
+ status[2 + i] = ssi->current_hack + 1; /* 1-based */
}
- }
-}
-
-
-
-static RETSIGTYPE
-restore_real_vroot_handler (int sig)
-{
- saver_info *si = global_si_kludge; /* I hate C so much... */
-
- signal (sig, SIG_DFL);
- if (restore_real_vroot (si))
- fprintf (real_stderr, "\n%s: %s intercepted, vroot restored.\n",
- blurb(), signal_name(sig));
-# ifdef HAVE_LIBSYSTEMD
- if (si->systemd_pid) /* Kill background xscreensaver-systemd process */
- {
- /* We're exiting, so there's no need to do a full kill_job() here,
- which will waitpid(). */
- /* kill_job (si, si->systemd_pid, SIGTERM); */
- kill (si->systemd_pid, SIGTERM);
- si->systemd_pid = 0;
- }
-# endif
- kill (getpid (), sig);
-}
-static void
-catch_signal (saver_info *si, int sig, RETSIGTYPE (*handler) (int))
-{
-# ifdef HAVE_SIGACTION
-
- struct sigaction a;
- a.sa_handler = handler;
- sigemptyset (&a.sa_mask);
- a.sa_flags = 0;
-
- /* On Linux 2.4.9 (at least) we need to tell the kernel to not mask delivery
- of this signal from inside its handler, or else when we execvp() the
- process again, it starts up with SIGHUP blocked, meaning that killing
- it with -HUP only works *once*. You'd think that execvp() would reset
- all the signal masks, but it doesn't.
- */
-# if defined(SA_NOMASK)
- a.sa_flags |= SA_NOMASK;
-# elif defined(SA_NODEFER)
- a.sa_flags |= SA_NODEFER;
-# endif
-
- if (sigaction (sig, &a, 0) < 0)
-# else /* !HAVE_SIGACTION */
- if (((long) signal (sig, handler)) == -1L)
-# endif /* !HAVE_SIGACTION */
- {
- char buf [255];
- sprintf (buf, "%s: couldn't catch %s", blurb(), signal_name(sig));
- perror (buf);
- saver_exit (si, 1, 0);
- }
-}
-
-static RETSIGTYPE saver_sighup_handler (int sig);
-
-void
-handle_signals (saver_info *si)
-{
- catch_signal (si, SIGHUP, saver_sighup_handler);
-
- catch_signal (si, SIGINT, restore_real_vroot_handler);
- catch_signal (si, SIGQUIT, restore_real_vroot_handler);
- catch_signal (si, SIGILL, restore_real_vroot_handler);
- catch_signal (si, SIGTRAP, restore_real_vroot_handler);
-#ifdef SIGIOT
- catch_signal (si, SIGIOT, restore_real_vroot_handler);
-#endif
- catch_signal (si, SIGABRT, restore_real_vroot_handler);
-#ifdef SIGEMT
- catch_signal (si, SIGEMT, restore_real_vroot_handler);
-#endif
- catch_signal (si, SIGFPE, restore_real_vroot_handler);
- catch_signal (si, SIGBUS, restore_real_vroot_handler);
- catch_signal (si, SIGSEGV, restore_real_vroot_handler);
-#ifdef SIGSYS
- catch_signal (si, SIGSYS, restore_real_vroot_handler);
-#endif
- catch_signal (si, SIGTERM, restore_real_vroot_handler);
-#ifdef SIGXCPU
- catch_signal (si, SIGXCPU, restore_real_vroot_handler);
-#endif
-#ifdef SIGXFSZ
- catch_signal (si, SIGXFSZ, restore_real_vroot_handler);
-#endif
-#ifdef SIGDANGER
- catch_signal (si, SIGDANGER, restore_real_vroot_handler);
-#endif
-}
-
-
-static RETSIGTYPE
-saver_sighup_handler (int sig)
-{
- saver_info *si = global_si_kludge; /* I hate C so much... */
-
- /* Re-establish SIGHUP handler */
- catch_signal (si, SIGHUP, saver_sighup_handler);
-
- fprintf (stderr, "%s: %s received: restarting...\n",
- blurb(), signal_name(sig));
-
- if (si->screen_blanked_p)
- {
- int i;
- for (i = 0; i < si->nscreens; i++)
- kill_screenhack (&si->screens[i]);
- unblank_screen (si);
- XSync (si->dpy, False);
- }
-
- restart_process (si); /* Does not return */
- abort ();
-}
-
-
-
-void
-saver_exit (saver_info *si, int status, const char *dump_core_reason)
-{
- saver_preferences *p = &si->prefs;
- static Bool exiting = False;
- Bool bugp;
- Bool vrs;
-
- if (exiting)
- exit(status);
-
- exiting = True;
-
- vrs = restore_real_vroot (si);
- emergency_kill_subproc (si);
- shutdown_stderr (si);
-
- if (p->verbose_p && vrs)
- fprintf (real_stderr, "%s: old vroot restored.\n", blurb());
-
-# ifdef HAVE_LIBSYSTEMD
- if (si->systemd_pid) /* Kill background xscreensaver-systemd process */
- {
- kill_job (si, si->systemd_pid, SIGTERM);
- si->systemd_pid = 0;
- }
-# endif
-
- fflush(real_stdout);
-
-#ifdef VMS /* on VMS, 1 is the "normal" exit code instead of 0. */
- if (status == 0) status = 1;
- else if (status == 1) status = -1;
-#endif
-
- bugp = !!dump_core_reason;
-
- if (si->prefs.debug_p && !dump_core_reason)
- dump_core_reason = "because of -debug";
-
- if (dump_core_reason)
- {
- /* Note that the Linux man page for setuid() says If uid is
- different from the old effective uid, the process will be
- forbidden from leaving core dumps.
- */
- char cwd[4096]; /* should really be PATH_MAX, but who cares. */
- cwd[0] = 0;
- fprintf(real_stderr, "%s: dumping core (%s)\n", blurb(),
- dump_core_reason);
-
- if (bugp)
- fprintf(real_stderr,
- "%s: see https://www.jwz.org/xscreensaver/bugs.html\n"
- "\t\t\tfor bug reporting information.\n\n",
- blurb());
-
-# if defined(HAVE_GETCWD)
- if (!getcwd (cwd, sizeof(cwd)))
-# elif defined(HAVE_GETWD)
- if (!getwd (cwd))
-# endif
- strcpy(cwd, "unknown.");
-
- fprintf (real_stderr, "%s: current directory is %s\n", blurb(), cwd);
- describe_uids (si, real_stderr);
-
- /* Do this to drop a core file, so that we can get a stack trace. */
- abort();
- }
-
- exit (status);
-}
-
-
-/* Managing the actual screensaver window */
-
-Bool
-window_exists_p (Display *dpy, Window window)
-{
- XErrorHandler old_handler;
- XWindowAttributes xgwa;
- xgwa.screen = 0;
- old_handler = XSetErrorHandler (BadWindow_ehandler);
- XGetWindowAttributes (dpy, window, &xgwa);
+ XChangeProperty (si->dpy, w, XA_SCREENSAVER_STATUS, XA_INTEGER, 32,
+ PropModeReplace, (unsigned char *) status, nitems2);
XSync (dpy, False);
- XSetErrorHandler (old_handler);
- return (xgwa.screen != 0);
-}
-
-static void
-store_saver_id (saver_screen_info *ssi)
-{
- XClassHint class_hints;
- saver_info *si = ssi->global;
- unsigned long pid = (unsigned long) getpid ();
- char buf[20];
- struct passwd *p = getpwuid (getuid ());
- const char *name, *host;
- char *id;
-# if defined(HAVE_UNAME)
- struct utsname uts;
-# endif /* UNAME */
-
- /* First store the name and class on the window.
- */
- class_hints.res_name = progname;
- class_hints.res_class = progclass;
- XSetClassHint (si->dpy, ssi->screensaver_window, &class_hints);
- XStoreName (si->dpy, ssi->screensaver_window, "screensaver");
-
- /* Then store the xscreensaver version number.
- */
- XChangeProperty (si->dpy, ssi->screensaver_window,
- XA_SCREENSAVER_VERSION,
- XA_STRING, 8, PropModeReplace,
- (unsigned char *) si->version,
- strlen (si->version));
-
- /* Now store the XSCREENSAVER_ID property, that says what user and host
- xscreensaver is running as.
- */
-
- if (p && p->pw_name && *p->pw_name)
- name = p->pw_name;
- else if (p)
- {
- sprintf (buf, "%lu", (unsigned long) p->pw_uid);
- name = buf;
- }
- else
- name = "???";
-
-# if defined(HAVE_UNAME)
- {
- if (uname (&uts) < 0)
- host = "???";
- else
- host = uts.nodename;
- }
-# elif defined(VMS)
- host = getenv("SYS$NODE");
-# else /* !HAVE_UNAME && !VMS */
- host = "???";
-# endif /* !HAVE_UNAME && !VMS */
-
- id = (char *) malloc (strlen(name) + strlen(host) + 50);
- sprintf (id, "%lu (%s@%s)", pid, name, host);
-
- XChangeProperty (si->dpy, ssi->screensaver_window,
- XA_SCREENSAVER_ID, XA_STRING,
- 8, PropModeReplace,
- (unsigned char *) id, strlen (id));
- free (id);
-}
-
-
-void
-store_saver_status (saver_info *si)
-{
- PROP32 *status;
- int size = si->nscreens + 2;
- int i;
- status = (PROP32 *) calloc (size, sizeof(PROP32));
-
- status[0] = (PROP32) (si->screen_blanked_p || si->locked_p
- ? (si->locked_p ? XA_LOCK : XA_BLANK)
- : 0);
- status[1] = (PROP32) si->blank_time;
-
- for (i = 0; i < si->nscreens; i++)
+ if (si->prefs.debug_p && si->prefs.verbose_p)
{
- saver_screen_info *ssi = &si->screens[i];
- status [2 + i] = ssi->current_hack + 1;
+ int i;
+ fprintf (stderr, "%s: wrote status property: 0x%lx: %s", blurb(),
+ (unsigned long) w,
+ (status[0] == XA_LOCK ? "LOCK" :
+ status[0] == XA_BLANK ? "BLANK" :
+ status[0] == 0 ? "0" : "???"));
+ for (i = 1; i < nitems; i++)
+ fprintf (stderr, ", %lu", status[i]);
+ fprintf (stderr, "\n");
}
- XChangeProperty (si->dpy,
- RootWindow (si->dpy, 0), /* always screen #0 */
- XA_SCREENSAVER_STATUS,
- XA_INTEGER, 32, PropModeReplace,
- (unsigned char *) status, size);
free (status);
+ if (dataP)
+ XFree (dataP);
}
-static Bool error_handler_hit_p = False;
-
-static int
-ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
-{
- error_handler_hit_p = True;
- return 0;
-}
-
-
-/* Returns True if successful, False if an X error occurred.
- We need this because other programs might have done things to
- our window that will cause XChangeWindowAttributes() to fail:
- if that happens, we give up, destroy the window, and re-create
- it.
- */
-static Bool
-safe_XChangeWindowAttributes (Display *dpy, Window window,
- unsigned long mask,
- XSetWindowAttributes *attrs)
-{
- XErrorHandler old_handler;
- XSync (dpy, False);
- error_handler_hit_p = False;
- old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
-
- XChangeWindowAttributes (dpy, window, mask, attrs);
-
- XSync (dpy, False);
- XSetErrorHandler (old_handler);
- XSync (dpy, False);
-
- return (!error_handler_hit_p);
-}
-
-
-/* This might not be necessary, but just in case. */
-static Bool
-safe_XConfigureWindow (Display *dpy, Window window,
- unsigned long mask, XWindowChanges *changes)
-{
- XErrorHandler old_handler;
- XSync (dpy, False);
- error_handler_hit_p = False;
- old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
-
- XConfigureWindow (dpy, window, mask, changes);
-
- XSync (dpy, False);
- XSetErrorHandler (old_handler);
- XSync (dpy, False);
-
- return (!error_handler_hit_p);
-}
-
-/* This might not be necessary, but just in case. */
-static Bool
-safe_XDestroyWindow (Display *dpy, Window window)
-{
- XErrorHandler old_handler;
- XSync (dpy, False);
- error_handler_hit_p = False;
- old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
-
- XDestroyWindow (dpy, window);
-
- XSync (dpy, False);
- XSetErrorHandler (old_handler);
- XSync (dpy, False);
-
- return (!error_handler_hit_p);
-}
-
-
-static Bool
-safe_XKillClient (Display *dpy, XID id)
-{
- XErrorHandler old_handler;
- XSync (dpy, False);
- error_handler_hit_p = False;
- old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
-
- XKillClient (dpy, id);
-
- XSync (dpy, False);
- XSetErrorHandler (old_handler);
- XSync (dpy, False);
-
- return (!error_handler_hit_p);
-}
-
-
-#ifdef HAVE_XF86VMODE
-Bool
-safe_XF86VidModeGetViewPort (Display *dpy, int screen, int *xP, int *yP)
-{
- Bool result;
- XErrorHandler old_handler;
- XSync (dpy, False);
- error_handler_hit_p = False;
- old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
-
- result = XF86VidModeGetViewPort (dpy, screen, xP, yP);
-
- XSync (dpy, False);
- XSetErrorHandler (old_handler);
- XSync (dpy, False);
-
- return (error_handler_hit_p
- ? False
- : result);
-}
-
-/* There is no "safe_XF86VidModeGetModeLine" because it fails with an
- untrappable I/O error instead of an X error -- so one must call
- safe_XF86VidModeGetViewPort first, and assume that both have the
- same error condition. Thank you XFree, may I have another.
- */
-
-#endif /* HAVE_XF86VMODE */
-
-
static void
initialize_screensaver_window_1 (saver_screen_info *ssi)
{
@@ -1180,7 +138,8 @@ initialize_screensaver_window_1 (saver_screen_info *ssi)
/* This resets the screensaver window as fully as possible, since there's
no way of knowing what some random client may have done to us in the
meantime. We could just destroy and recreate the window, but that has
- its own set of problems...
+ its own set of problems. (Update: that's exactly what we're doing
+ these days.)
*/
XColor black;
XSetWindowAttributes attrs;
@@ -1214,8 +173,7 @@ initialize_screensaver_window_1 (saver_screen_info *ssi)
if (ssi->cmap)
{
XFreeColors (si->dpy, ssi->cmap, &ssi->black_pixel, 1, 0);
- if (ssi->cmap != ssi->demo_cmap &&
- ssi->cmap != def_cmap)
+ if (ssi->cmap != def_cmap)
XFreeColormap (si->dpy, ssi->cmap);
}
ssi->cmap = def_cmap;
@@ -1226,20 +184,18 @@ initialize_screensaver_window_1 (saver_screen_info *ssi)
CWBackPixel | CWBackingPixel | CWBorderPixel);
attrs.override_redirect = True;
- /* When use_mit_saver_extension or use_sgi_saver_extension is true, we won't
- actually be reading these events during normal operation; but we still
- need to see Button events for demo-mode to work properly.
- */
attrs.event_mask = (KeyPressMask | KeyReleaseMask |
ButtonPressMask | ButtonReleaseMask |
PointerMotionMask);
- attrs.backing_store = NotUseful;
+ attrs.backing_store = Always;
attrs.colormap = ssi->cmap;
attrs.background_pixel = ssi->black_pixel;
attrs.backing_pixel = ssi->black_pixel;
attrs.border_pixel = ssi->black_pixel;
+ printed_visual_info = True; /* Too noisy */
+
if (!p->verbose_p || printed_visual_info)
;
else if (ssi->current_visual == DefaultVisualOfScreen (ssi->screen))
@@ -1260,56 +216,11 @@ initialize_screensaver_window_1 (saver_screen_info *ssi)
}
printed_visual_info = True;
-#ifdef HAVE_MIT_SAVER_EXTENSION
- if (si->using_mit_saver_extension)
+ if (ssi->error_dialog)
{
- XScreenSaverInfo *info;
- Window root = RootWindowOfScreen (ssi->screen);
-
-#if 0
- /* This call sets the server screensaver timeouts to what we think
- they should be (based on the resources and args xscreensaver was
- started with.) It's important that we do this to sync back up
- with the server - if we have turned on prematurely, as by an
- ACTIVATE ClientMessage, then the server may decide to activate
- the screensaver while it's already active. That's ok for us,
- since we would know to ignore that ScreenSaverActivate event,
- but a side effect of this would be that the server would map its
- saver window (which we then hide again right away) meaning that
- the bits currently on the screen get blown away. Ugly. */
-
- /* #### Ok, that doesn't work - when we tell the server that the
- screensaver is "off" it sends us a Deactivate event, which is
- sensible... but causes the saver to never come on. Hmm. */
- disable_builtin_screensaver (si, True);
-#endif /* 0 */
-
-#if 0
- /* #### The MIT-SCREEN-SAVER extension gives us access to the
- window that the server itself uses for saving the screen.
- However, using this window in any way, in particular, calling
- XScreenSaverSetAttributes() as below, tends to make the X server
- crash. So fuck it, let's try and get along without using it...
-
- It's also inconvenient to use this window because it doesn't
- always exist (though the ID is constant.) So to use this
- window, we'd have to reimplement the ACTIVATE ClientMessage to
- tell the *server* to tell *us* to turn on, to cause the window
- to get created at the right time. Gag. */
- XScreenSaverSetAttributes (si->dpy, root,
- 0, 0, width, height, 0,
- current_depth, InputOutput, visual,
- attrmask, &attrs);
- XSync (si->dpy, False);
-#endif /* 0 */
-
- info = XScreenSaverAllocInfo ();
- XScreenSaverQueryInfo (si->dpy, root, info);
- ssi->server_mit_saver_window = info->window;
- if (! ssi->server_mit_saver_window) abort ();
- XFree (info);
+ XDestroyWindow (si->dpy, ssi->error_dialog);
+ ssi->error_dialog = 0;
}
-#endif /* HAVE_MIT_SAVER_EXTENSION */
if (ssi->screensaver_window)
{
@@ -1321,10 +232,13 @@ initialize_screensaver_window_1 (saver_screen_info *ssi)
changes.height = ssi->height;
changes.border_width = 0;
- if (! (safe_XConfigureWindow (si->dpy, ssi->screensaver_window,
- changesmask, &changes) &&
- safe_XChangeWindowAttributes (si->dpy, ssi->screensaver_window,
- attrmask, &attrs)))
+ /* XConfigureWindow and XChangeWindowAttributes can fail if a hack did
+ something weird to the window. In that case, we must destroy and
+ re-create it. */
+ if (! (XConfigureWindow (si->dpy, ssi->screensaver_window,
+ changesmask, &changes) &&
+ XChangeWindowAttributes (si->dpy, ssi->screensaver_window,
+ attrmask, &attrs)))
{
horked_window = ssi->screensaver_window;
ssi->screensaver_window = 0;
@@ -1338,35 +252,31 @@ initialize_screensaver_window_1 (saver_screen_info *ssi)
ssi->x, ssi->y, ssi->width, ssi->height,
0, ssi->current_depth, InputOutput,
ssi->current_visual, attrmask, &attrs);
- reset_stderr (ssi);
+ xscreensaver_set_wm_atoms (si->dpy, ssi->screensaver_window,
+ ssi->width, ssi->height, 0);
if (horked_window)
{
fprintf (stderr,
"%s: someone horked our saver window (0x%lx)! Recreating it...\n",
blurb(), (unsigned long) horked_window);
- maybe_transfer_grabs (ssi, horked_window, ssi->screensaver_window,
- ssi->number);
- safe_XDestroyWindow (si->dpy, horked_window);
- horked_window = 0;
+ XDestroyWindow (si->dpy, horked_window);
}
- if (p->verbose_p)
- fprintf (stderr, "%s: %d: saver window is 0x%lx.\n",
+ if (p->verbose_p > 1)
+ fprintf (stderr, "%s: %d: saver window is 0x%lx\n",
blurb(), ssi->number,
(unsigned long) ssi->screensaver_window);
}
- store_saver_id (ssi); /* store window name and IDs */
-
if (!ssi->cursor)
{
- Pixmap bit;
- bit = XCreatePixmapFromBitmapData (si->dpy, ssi->screensaver_window,
- "\000", 1, 1,
- BlackPixelOfScreen (ssi->screen),
- BlackPixelOfScreen (ssi->screen),
- 1);
+ Pixmap bit =
+ XCreatePixmapFromBitmapData (si->dpy, ssi->screensaver_window,
+ "\000", 1, 1,
+ BlackPixelOfScreen (ssi->screen),
+ BlackPixelOfScreen (ssi->screen),
+ 1);
ssi->cursor = XCreatePixmapCursor (si->dpy, bit, bit, &black, &black,
0, 0);
XFreePixmap (si->dpy, bit);
@@ -1380,6 +290,7 @@ initialize_screensaver_window_1 (saver_screen_info *ssi)
XDefineCursor (si->dpy, ssi->screensaver_window, ssi->cursor);
}
+
void
initialize_screensaver_window (saver_info *si)
{
@@ -1389,6 +300,27 @@ initialize_screensaver_window (saver_info *si)
}
+static void
+raise_window (saver_screen_info *ssi)
+{
+ saver_info *si = ssi->global;
+ if (ssi->error_dialog)
+ {
+ /* Make the error be topmost, and the saver be directly below it. */
+ Window windows[2];
+ windows[0] = ssi->error_dialog;
+ windows[1] = ssi->screensaver_window;
+ XMapRaised (si->dpy, windows[0]);
+ XRestackWindows (si->dpy, windows, countof(windows));
+ }
+ else
+ XMapRaised (si->dpy, ssi->screensaver_window);
+
+ if (ssi->cmap)
+ XInstallColormap (si->dpy, ssi->cmap);
+}
+
+
/* Called when the RANDR (Resize and Rotate) extension tells us that
the size of the screen has changed while the screen was blanked.
Call update_screen_layout() first, then call this to synchronize
@@ -1443,8 +375,8 @@ resize_screensaver_window (saver_info *si)
xgwa.width, xgwa.height, xgwa.x, xgwa.y,
ssi->width, ssi->height, ssi->x, ssi->y);
- if (! safe_XConfigureWindow (si->dpy, ssi->screensaver_window,
- changesmask, &changes))
+ if (! XConfigureWindow (si->dpy, ssi->screensaver_window,
+ changesmask, &changes))
fprintf (stderr, "%s: %d: someone horked our saver window"
" (0x%lx)! Unable to resize it!\n",
blurb(), i, (unsigned long) ssi->screensaver_window);
@@ -1458,27 +390,10 @@ resize_screensaver_window (saver_info *si)
Note that spawn_screenhack() calls select_visual() which may destroy
and re-create the window via initialize_screensaver_window_1().
*/
- if (si->screen_blanked_p)
- {
- if (ssi->cmap)
- XInstallColormap (si->dpy, ssi->cmap);
- XMapRaised (si->dpy, ssi->screensaver_window);
- XSync (si->dpy, False);
- if (! ssi->pid)
- spawn_screenhack (ssi);
-
- /* Make sure the act of adding a screen doesn't present as
- pointer motion (and thus cause an unblank). */
- {
- Window root, child;
- int x, y;
- unsigned int mask;
- XQueryPointer (si->dpy, ssi->screensaver_window, &root, &child,
- &ssi->last_poll_mouse.root_x,
- &ssi->last_poll_mouse.root_y,
- &x, &y, &mask);
- }
- }
+ raise_window (ssi);
+ XSync (si->dpy, False);
+ if (! ssi->pid)
+ spawn_screenhack (ssi);
}
/* Kill off any savers running on no-longer-extant monitors.
@@ -1491,44 +406,47 @@ resize_screensaver_window (saver_info *si)
if (ssi->screensaver_window)
{
XUnmapWindow (si->dpy, ssi->screensaver_window);
- restore_real_vroot_1 (ssi);
}
}
}
-void
-raise_window (saver_info *si,
- Bool inhibit_fade, Bool between_hacks_p, Bool dont_clear)
+static void
+raise_windows (saver_info *si)
{
- saver_preferences *p = &si->prefs;
int i;
+ for (i = 0; i < si->nscreens; i++)
+ {
+ saver_screen_info *ssi = &si->screens[i];
+ raise_window (ssi);
+ }
+}
- if (si->demoing_p)
- inhibit_fade = True;
-
- if (si->emergency_lock_p)
- inhibit_fade = True;
- if (!dont_clear)
- initialize_screensaver_window (si);
+/* Called only once, before the main loop begins.
+ */
+void
+blank_screen (saver_info *si)
+{
+ saver_preferences *p = &si->prefs;
+ Bool user_active_p = False;
+ int i;
- reset_watchdog_timer (si, True);
+ initialize_screensaver_window (si);
+ sync_server_dpms_settings (si->dpy, p);
- if (p->fade_p && si->fading_possible_p && !inhibit_fade)
+ if (p->fade_p &&
+ !si->demoing_p &&
+ !si->emergency_p)
{
Window *current_windows = (Window *)
- calloc(sizeof(Window), si->nscreens);
- Colormap *current_maps = (Colormap *)
- calloc(sizeof(Colormap), si->nscreens);
+ malloc (si->nscreens * sizeof(*current_windows));
+ if (!current_windows) abort();
for (i = 0; i < si->nscreens; i++)
{
saver_screen_info *ssi = &si->screens[i];
current_windows[i] = ssi->screensaver_window;
- current_maps[i] = (between_hacks_p
- ? ssi->cmap
- : DefaultColormapOfScreen (ssi->screen));
/* Ensure that the default background of the window is really black,
not a pixmap or something. (This does not clear the window.) */
XSetWindowBackground (si->dpy, ssi->screensaver_window,
@@ -1537,236 +455,98 @@ raise_window (saver_info *si,
if (p->verbose_p) fprintf (stderr, "%s: fading...\n", blurb());
- XGrabServer (si->dpy); /* ############ DANGER! */
-
- /* Clear the stderr layer on each screen.
- */
- if (!dont_clear)
- for (i = 0; i < si->nscreens; i++)
- {
- saver_screen_info *ssi = &si->screens[i];
- if (ssi->stderr_overlay_window)
- /* Do this before the fade, since the stderr cmap won't fade
- even if we uninstall it (beats me...) */
- clear_stderr (ssi);
- }
-
- /* Note! The server is grabbed, and this will take several seconds
- to complete! */
- fade_screens (si->dpy, current_maps,
- current_windows, si->nscreens,
- p->fade_seconds/1000, p->fade_ticks, True, !dont_clear);
-
- free(current_maps);
- free(current_windows);
- current_maps = 0;
- current_windows = 0;
-
- if (p->verbose_p) fprintf (stderr, "%s: fading done.\n", blurb());
-
-#ifdef HAVE_MIT_SAVER_EXTENSION
- for (i = 0; i < si->nscreens; i++)
- {
- saver_screen_info *ssi = &si->screens[i];
- if (ssi->server_mit_saver_window &&
- window_exists_p (si->dpy, ssi->server_mit_saver_window))
- XUnmapWindow (si->dpy, ssi->server_mit_saver_window);
- }
-#endif /* HAVE_MIT_SAVER_EXTENSION */
-
- XUngrabServer (si->dpy);
- XSync (si->dpy, False); /* ###### (danger over) */
- }
- else
- {
- for (i = 0; i < si->nscreens; i++)
- {
- saver_screen_info *ssi = &si->screens[i];
- if (!dont_clear)
- XClearWindow (si->dpy, ssi->screensaver_window);
- if (!dont_clear || ssi->stderr_overlay_window)
- clear_stderr (ssi);
- XMapRaised (si->dpy, ssi->screensaver_window);
- XSync (si->dpy, False);
-#ifdef HAVE_MIT_SAVER_EXTENSION
- if (ssi->server_mit_saver_window &&
- window_exists_p (si->dpy, ssi->server_mit_saver_window))
- XUnmapWindow (si->dpy, ssi->server_mit_saver_window);
-#endif /* HAVE_MIT_SAVER_EXTENSION */
- }
- }
-
- for (i = 0; i < si->nscreens; i++)
- {
- saver_screen_info *ssi = &si->screens[i];
- if (ssi->cmap)
- XInstallColormap (si->dpy, ssi->cmap);
- }
-}
-
-
-int
-mouse_screen (saver_info *si)
-{
- saver_preferences *p = &si->prefs;
- Window pointer_root, pointer_child;
- int root_x, root_y, win_x, win_y;
- unsigned int mask;
- int i;
-
- if (si->nscreens == 1)
- return 0;
-
- for (i = 0; i < si->nscreens; i++)
- {
- saver_screen_info *ssi = &si->screens[i];
- if (XQueryPointer (si->dpy, RootWindowOfScreen (ssi->screen),
- &pointer_root, &pointer_child,
- &root_x, &root_y, &win_x, &win_y, &mask) &&
- root_x >= ssi->x &&
- root_y >= ssi->y &&
- root_x < ssi->x + ssi->width &&
- root_y < ssi->y + ssi->height)
- {
- if (p->verbose_p)
- fprintf (stderr, "%s: mouse is on screen %d of %d\n",
- blurb(), i, si->nscreens);
- return i;
- }
+ /* This will take several seconds to complete. */
+ user_active_p = fade_screens (si->app, si->dpy,
+ current_windows, si->nscreens,
+ p->fade_seconds / 1000.0,
+ True, /* out_p */
+ True, /* from_desktop_p */
+ &si->fade_state);
+ free (current_windows);
+
+ if (!p->verbose_p)
+ ;
+ else if (user_active_p)
+ fprintf (stderr, "%s: fading aborted\n", blurb());
+ else
+ fprintf (stderr, "%s: fading done\n", blurb());
}
- /* couldn't figure out where the mouse is? Oh well. */
- return 0;
-}
-
+ raise_windows (si);
+ reset_watchdog_timer (si);
-Bool
-blank_screen (saver_info *si)
-{
- int i;
- Bool ok;
- Window w;
- int mscreen;
-
- /* Note: we do our grabs on the root window, not on the screensaver window.
- If we grabbed on the saver window, then the demo mode and lock dialog
- boxes wouldn't get any events.
-
- By "the root window", we mean "the root window that contains the mouse."
- We use to always grab the mouse on screen 0, but that has the effect of
- moving the mouse to screen 0 from whichever screen it was on, on
- multi-head systems.
- */
- mscreen = mouse_screen (si);
- w = RootWindowOfScreen(si->screens[mscreen].screen);
- ok = grab_keyboard_and_mouse (si, w,
- (si->demoing_p ? 0 : si->screens[0].cursor),
- mscreen);
-
-
-# if 0
- if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
- /* If we're using a server extension, then failure to get a grab is
- not a big deal -- even without the grab, we will still be able
- to un-blank when there is user activity, since the server will
- tell us. */
- /* #### No, that's not true: if we don't have a keyboard grab,
- then we can't read passwords to unlock.
- */
- ok = True;
-# endif /* 0 */
-
- if (!ok)
- return False;
+ /* user_active_p means that the user aborted the fade-out -- but that does
+ not mean that we are necessarily about to exit. If we are locking, then
+ the user activity will cause the unlock dialog to appear, but
+ authentication might not succeed. */
for (i = 0; i < si->nscreens; i++)
- {
- saver_screen_info *ssi = &si->screens[i];
- if (ssi->real_screen_p)
- save_real_vroot (ssi);
- store_vroot_property (si->dpy,
- ssi->screensaver_window,
- ssi->screensaver_window);
-
-#ifdef HAVE_XF86VMODE
- {
- int ev, er;
- if (!XF86VidModeQueryExtension (si->dpy, &ev, &er) ||
- !safe_XF86VidModeGetViewPort (si->dpy, i,
- &ssi->blank_vp_x,
- &ssi->blank_vp_y))
- ssi->blank_vp_x = ssi->blank_vp_y = -1;
- }
-#endif /* HAVE_XF86VMODE */
- }
-
- raise_window (si, False, False, False);
-
- si->screen_blanked_p = True;
- si->blank_time = time ((time_t *) 0);
- si->last_wall_clock_time = 0;
+ /* This also queues each screen's cycle_timer. */
+ spawn_screenhack (&si->screens[i]);
- store_saver_status (si); /* store blank time */
+ /* Turn off "next" and "prev" modes after they have happened once. */
+ if (si->selection_mode < 0)
+ si->selection_mode = 0;
- return True;
+ /* If we are blanking only, optionally power down monitor right now. */
+ if (p->mode == BLANK_ONLY &&
+ p->dpms_quickoff_p)
+ monitor_power_on (si, False);
}
+/* Called only once, upon receipt of SIGTERM, just before exiting.
+ */
void
unblank_screen (saver_info *si)
{
saver_preferences *p = &si->prefs;
- Bool unfade_p = (si->fading_possible_p && p->unfade_p);
+ Bool unfade_p = p->unfade_p;
int i;
monitor_power_on (si, True);
- reset_watchdog_timer (si, False);
if (si->demoing_p)
unfade_p = False;
if (unfade_p)
{
+ double seconds = p->fade_seconds / 1000.0;
+ double ratio = 1/3.0;
Window *current_windows = (Window *)
calloc(sizeof(Window), si->nscreens);
+ Bool interrupted_p = False;
for (i = 0; i < si->nscreens; i++)
{
saver_screen_info *ssi = &si->screens[i];
current_windows[i] = ssi->screensaver_window;
- /* Ensure that the default background of the window is really black,
- not a pixmap or something. (This does not clear the window.) */
- XSetWindowBackground (si->dpy, ssi->screensaver_window,
- ssi->black_pixel);
}
if (p->verbose_p) fprintf (stderr, "%s: unfading...\n", blurb());
+ monitor_power_on (si, True);
+
+ /* When we fade in to the desktop, first fade out from the saver to
+ black, then fade in from black to the desktop. */
+ interrupted_p = fade_screens (si->app, si->dpy,
+ current_windows, si->nscreens,
+ seconds * ratio,
+ True, /* out_p */
+ False, /* from_desktop_p */
+ &si->fade_state);
+ if (! interrupted_p)
+ interrupted_p = fade_screens (si->app, si->dpy,
+ current_windows, si->nscreens,
+ seconds * (1-ratio),
+ False, /* out_p */
+ False, /* from_desktop_p */
+ &si->fade_state);
+ free (current_windows);
- XSync (si->dpy, False);
- XGrabServer (si->dpy); /* ############ DANGER! */
- XSync (si->dpy, False);
-
- /* Clear the stderr layer on each screen.
- */
- for (i = 0; i < si->nscreens; i++)
- {
- saver_screen_info *ssi = &si->screens[i];
- clear_stderr (ssi);
- }
-
- XUngrabServer (si->dpy);
- XSync (si->dpy, False); /* ###### (danger over) */
-
- fade_screens (si->dpy, 0,
- current_windows, si->nscreens,
- p->fade_seconds/1000, p->fade_ticks,
- False, False);
-
- free(current_windows);
- current_windows = 0;
-
- if (p->verbose_p) fprintf (stderr, "%s: unfading done.\n", blurb());
+ if (p->verbose_p)
+ fprintf (stderr, "%s: unfading done%s\n", blurb(),
+ (interrupted_p ? " (interrupted)" : ""));
}
else
{
@@ -1777,118 +557,33 @@ unblank_screen (saver_info *si)
{
Colormap c = DefaultColormapOfScreen (ssi->screen);
/* avoid technicolor */
+ XSetWindowBackground (si->dpy, ssi->screensaver_window,
+ BlackPixelOfScreen (ssi->screen));
XClearWindow (si->dpy, ssi->screensaver_window);
if (c) XInstallColormap (si->dpy, c);
}
XUnmapWindow (si->dpy, ssi->screensaver_window);
}
}
-
-
- /* If the focus window does has a non-default colormap, then install
- that colormap as well. (On SGIs, this will cause both the root map
- and the focus map to be installed simultaneously. It'd be nice to
- pick up the other colormaps that had been installed, too; perhaps
- XListInstalledColormaps could be used for that?)
- */
- {
- Window focus = 0;
- int revert_to;
- XSync (si->dpy, False);
- XGetInputFocus (si->dpy, &focus, &revert_to);
- if (focus && focus != PointerRoot && focus != None)
- {
- XErrorHandler old_handler = XSetErrorHandler (BadWindow_ehandler);
- XWindowAttributes xgwa;
- xgwa.colormap = 0;
- if (XGetWindowAttributes (si->dpy, focus, &xgwa) &&
- xgwa.colormap &&
- xgwa.colormap != DefaultColormapOfScreen (xgwa.screen))
- XInstallColormap (si->dpy, xgwa.colormap);
- XSetErrorHandler (old_handler);
- }
- }
-
-
- for (i = 0; i < si->nscreens; i++)
- {
- saver_screen_info *ssi = &si->screens[i];
- kill_xsetroot_data (si->dpy, ssi->screensaver_window, p->verbose_p);
- }
-
- store_saver_status (si); /* store unblank time */
- ungrab_keyboard_and_mouse (si);
- restore_real_vroot (si);
-
- /* Unmap the windows a second time, dammit -- just to avoid a race
- with the screen-grabbing hacks. (I'm not sure if this is really
- necessary; I'm stabbing in the dark now.)
- */
- for (i = 0; i < si->nscreens; i++)
- XUnmapWindow (si->dpy, si->screens[i].screensaver_window);
-
- si->screen_blanked_p = False;
- si->blank_time = time ((time_t *) 0);
- si->last_wall_clock_time = 0;
-
- store_saver_status (si); /* store unblank time */
-}
-
-
-/* Transfer any grabs from the old window to the new.
- Actually I think none of this is necessary, since we always
- hold our grabs on the root window, but I wrote this before
- re-discovering that...
- */
-static void
-maybe_transfer_grabs (saver_screen_info *ssi,
- Window old_w, Window new_w,
- int new_screen_no)
-{
- saver_info *si = ssi->global;
-
- /* If the old window held our mouse grab, transfer the grab to the new
- window. (Grab the server while so doing, to avoid a race condition.)
- */
- if (old_w == si->mouse_grab_window)
- {
- XGrabServer (si->dpy); /* ############ DANGER! */
- ungrab_mouse (si);
- grab_mouse (si, ssi->screensaver_window,
- (si->demoing_p ? 0 : ssi->cursor),
- new_screen_no);
- XUngrabServer (si->dpy);
- XSync (si->dpy, False); /* ###### (danger over) */
- }
-
- /* If the old window held our keyboard grab, transfer the grab to the new
- window. (Grab the server while so doing, to avoid a race condition.)
- */
- if (old_w == si->keyboard_grab_window)
- {
- XGrabServer (si->dpy); /* ############ DANGER! */
- ungrab_kbd(si);
- grab_kbd(si, ssi->screensaver_window, ssi->number);
- XUngrabServer (si->dpy);
- XSync (si->dpy, False); /* ###### (danger over) */
- }
}
static Visual *
get_screen_gl_visual (saver_info *si, int real_screen_number)
{
- int i;
int nscreens = ScreenCount (si->dpy);
if (! si->best_gl_visuals)
- si->best_gl_visuals = (Visual **)
- calloc (nscreens + 1, sizeof (*si->best_gl_visuals));
+ {
+ int i;
+ si->best_gl_visuals = (Visual **)
+ calloc (nscreens + 1, sizeof (*si->best_gl_visuals));
- for (i = 0; i < nscreens; i++)
- if (! si->best_gl_visuals[i])
- si->best_gl_visuals[i] =
- get_best_gl_visual (si, ScreenOfDisplay (si->dpy, i));
+ for (i = 0; i < nscreens; i++)
+ if (! si->best_gl_visuals[i])
+ si->best_gl_visuals[i] =
+ get_best_gl_visual (si, ScreenOfDisplay (si->dpy, i));
+ }
if (real_screen_number < 0 || real_screen_number >= nscreens) abort();
return si->best_gl_visuals[real_screen_number];
@@ -1926,28 +621,21 @@ select_visual (saver_screen_info *ssi, const char *visual_name)
if (visual_name && *visual_name)
{
- if (!strcmp(visual_name, "default-i") ||
- !strcmp(visual_name, "Default-i") ||
- !strcmp(visual_name, "Default-I")
- )
+ if (!strcasecmp(visual_name, "default-i"))
{
visual_name = "default";
install_cmap_p = True;
}
- else if (!strcmp(visual_name, "default-n") ||
- !strcmp(visual_name, "Default-n") ||
- !strcmp(visual_name, "Default-N"))
+ else if (!strcasecmp(visual_name, "default-n"))
{
visual_name = "default";
install_cmap_p = False;
}
- else if (!strcmp(visual_name, "gl") ||
- !strcmp(visual_name, "Gl") ||
- !strcmp(visual_name, "GL"))
+ else if (!strcasecmp(visual_name, "GL"))
{
new_v = get_screen_gl_visual (si, ssi->real_screen_number);
if (!new_v && p->verbose_p)
- fprintf (stderr, "%s: no GL visuals.\n", progname);
+ fprintf (stderr, "%s: no GL visuals\n", blurb());
}
if (!new_v)
@@ -1979,8 +667,10 @@ select_visual (saver_screen_info *ssi, const char *visual_name)
if (p->verbose_p)
{
+#if 0
fprintf (stderr, "%s: %d: visual ", blurb(), ssi->number);
describe_visual (stderr, ssi->screen, new_v, install_cmap_p);
+#endif
#if 0
fprintf (stderr, "%s: from ", blurb());
describe_visual (stderr, ssi->screen, ssi->current_visual,
@@ -1988,39 +678,334 @@ select_visual (saver_screen_info *ssi, const char *visual_name)
#endif
}
- reset_stderr (ssi);
ssi->current_visual = new_v;
ssi->current_depth = visual_depth(ssi->screen, new_v);
ssi->cmap = 0;
ssi->screensaver_window = 0;
initialize_screensaver_window_1 (ssi);
-
- /* stderr_overlay_window is a child of screensaver_window, so we need
- to destroy that as well (actually, we just need to invalidate and
- drop our pointers to it, but this will destroy it, which is ok so
- long as it happens before old_w itself is destroyed.) */
- reset_stderr (ssi);
-
- raise_window (si, True, True, False);
- store_vroot_property (si->dpy,
- ssi->screensaver_window, ssi->screensaver_window);
-
- /* Transfer any grabs from the old window to the new. */
- maybe_transfer_grabs (ssi, old_w, ssi->screensaver_window, ssi->number);
+ raise_window (ssi);
/* Now we can destroy the old window without horking our grabs. */
XDestroyWindow (si->dpy, old_w);
- if (p->verbose_p)
- fprintf (stderr, "%s: %d: destroyed old saver window 0x%lx.\n",
+ if (p->verbose_p > 1)
+ fprintf (stderr, "%s: %d: destroyed old saver window 0x%lx\n",
blurb(), ssi->number, (unsigned long) old_w);
if (old_c &&
- old_c != DefaultColormapOfScreen (ssi->screen) &&
- old_c != ssi->demo_cmap)
+ old_c != DefaultColormapOfScreen (ssi->screen))
XFreeColormap (si->dpy, old_c);
}
return got_it;
}
+
+
+/* Synchronize the contents of si->ssi to the current state of the monitors.
+ Doesn't change anything if nothing has changed; otherwise, alters and
+ reuses existing saver_screen_info structs as much as possible.
+ Returns True if anything changed.
+ */
+Bool
+update_screen_layout (saver_info *si)
+{
+ monitor **monitors = scan_monitors (si->dpy);
+ int count = 0;
+ int good_count = 0;
+ int i, j;
+ int seen_screens[100] = { 0, };
+
+ if (! monitor_layouts_differ_p (monitors, si->monitor_layout))
+ {
+ free_monitors (monitors);
+ return False;
+ }
+
+ free_monitors (si->monitor_layout);
+ si->monitor_layout = monitors;
+ check_monitor_sanity (si->monitor_layout);
+
+ while (monitors[count])
+ {
+ if (monitors[count]->sanity == S_SANE)
+ good_count++;
+ count++;
+ }
+
+ if (si->ssi_count == 0)
+ {
+ si->ssi_count = 10;
+ si->screens = (saver_screen_info *)
+ calloc (sizeof(*si->screens), si->ssi_count);
+ }
+
+ if (si->ssi_count <= good_count)
+ {
+ si->ssi_count = good_count + 10;
+ si->screens = (saver_screen_info *)
+ realloc (si->screens, sizeof(*si->screens) * si->ssi_count);
+ memset (si->screens + si->nscreens, 0,
+ sizeof(*si->screens) * (si->ssi_count - si->nscreens));
+ }
+
+ if (! si->screens) abort();
+
+ si->nscreens = good_count;
+
+ /* Regenerate the list of GL visuals as needed. */
+ if (si->best_gl_visuals)
+ free (si->best_gl_visuals);
+ si->best_gl_visuals = 0;
+
+ for (i = 0, j = 0; i < count; i++)
+ {
+ monitor *m = monitors[i];
+ saver_screen_info *ssi = &si->screens[j];
+ int sn;
+ if (monitors[i]->sanity != S_SANE) continue;
+
+ ssi->global = si;
+ ssi->number = j;
+
+ sn = screen_number (m->screen);
+ ssi->screen = m->screen;
+ ssi->real_screen_number = sn;
+ ssi->real_screen_p = (seen_screens[sn] == 0);
+ seen_screens[sn]++;
+
+ ssi->default_visual =
+ get_visual_resource (ssi->screen, "visualID", "VisualID", False);
+ ssi->current_visual = ssi->default_visual;
+ ssi->current_depth = visual_depth (ssi->screen, ssi->current_visual);
+
+ ssi->x = m->x;
+ ssi->y = m->y;
+ ssi->width = m->width;
+ ssi->height = m->height;
+
+# ifndef DEBUG_MULTISCREEN
+ {
+ saver_preferences *p = &si->prefs;
+ if (p->debug_p
+# ifdef QUAD_MODE
+ && !p->quad_p
+# endif
+ )
+ ssi->width /= 2;
+ }
+# endif
+
+ j++;
+ }
+
+ return True;
+}
+
+
+/* When the screensaver is active, this timer will periodically change
+ the running program. Each screen has its own timer.
+ */
+void
+cycle_timer (XtPointer closure, XtIntervalId *id)
+{
+ saver_screen_info *ssi = (saver_screen_info *) closure;
+ saver_info *si = ssi->global;
+
+ if (ssi->error_dialog)
+ {
+ XDestroyWindow (si->dpy, ssi->error_dialog);
+ ssi->error_dialog = 0;
+ }
+
+ maybe_reload_init_file (si);
+ kill_screenhack (ssi);
+ raise_window (ssi);
+
+ /* We could do a fade-out of just this screen here; but that would only work
+ if the fade method is SHM, not gamma or colormap. It would also only
+ look right if the cycle timers never fire at the same time, which is
+ currently the case. */
+
+ XSync (si->dpy, False);
+ spawn_screenhack (ssi); /* This also re-adds the cycle_id timer */
+}
+
+
+/* Called when a screenhack has exited unexpectedly.
+ We print a notification on the window, and in a little while, launch
+ a new hack (rather than waiting for the cycle timer to fire).
+ */
+void
+screenhack_obituary (saver_screen_info *ssi,
+ const char *name, const char *error)
+{
+ saver_info *si = ssi->global;
+ saver_preferences *p = &si->prefs;
+ Time how_long = p->cycle;
+ Time max = 1000 * 60; /* Message stays up no longer than this */
+ Window window;
+ Visual *visual;
+ XSetWindowAttributes attrs;
+ XWindowChanges changes;
+ unsigned long attrmask;
+ XftFont *font;
+ XftColor fg;
+ XftDraw *xftdraw;
+ XGlyphInfo overall;
+ XGCValues gcv;
+ GC gc;
+ char *fn, *cn;
+ char buf[255];
+ int x, y, pad;
+ int bw = 4;
+ Colormap cmap;
+
+ /* Restart the cycle timer, to take down the error dialog and launch
+ a new hack.
+ */
+ if (how_long > max)
+ how_long = max;
+ if (ssi->cycle_id)
+ XtRemoveTimeOut (ssi->cycle_id);
+ ssi->cycle_id =
+ XtAppAddTimeOut (si->app, how_long, cycle_timer, (XtPointer) ssi);
+ ssi->cycle_at = time ((time_t *) 0) + how_long / 1000;
+ if (p->verbose_p)
+ fprintf (stderr, "%s: %d: cycling in %lu sec\n", blurb(), ssi->number,
+ how_long / 1000);
+
+ /* Render an error message while we wait.
+
+ We can't just render text on top of ssi->screensaver_window because
+ if there was an OpenGL hack running on it, Xlib graphics might not
+ show up at all. Likewise, creating a sub-window doesn't work.
+ So it must be a top-level override-redirect window atop the saver.
+ */
+ cmap = ssi->cmap ? ssi->cmap : DefaultColormapOfScreen (ssi->screen);
+ window = ssi->error_dialog;
+ if (window) XDestroyWindow (si->dpy, window);
+ attrs.override_redirect = True;
+ attrs.background_pixel = ssi->black_pixel;
+ attrs.border_pixel = ssi->black_pixel;
+ attrs.backing_store = Always;
+ attrs.colormap = cmap;
+ attrmask = (CWOverrideRedirect | CWBackPixel | CWBorderPixel |
+ CWBackingStore | CWColormap);
+ visual = ssi->current_visual;
+ window = ssi->error_dialog =
+ XCreateWindow (si->dpy, RootWindowOfScreen (ssi->screen),
+ 0, 0, 1, 1, 0, ssi->current_depth, InputOutput, visual,
+ attrmask, &attrs);
+
+ fn = get_string_resource (si->dpy, "errorFont", "Font");
+ cn = get_string_resource (si->dpy, "errorColor", "Color");
+ if (!fn || !*fn) fn = strdup ("monospace bold 16");
+ if (!cn || !*cn) cn = strdup ("#FF0000");
+
+ font = load_xft_font_retry (si->dpy, screen_number (ssi->screen), fn);
+ XftColorAllocName (si->dpy, visual, cmap, cn, &fg);
+ xftdraw = XftDrawCreate (si->dpy, window, visual, cmap);
+
+ gcv.foreground =
+ get_pixel_resource (si->dpy, cmap, "errorColor", "Color");
+ gcv.line_width = bw;
+ gc = XCreateGC (si->dpy, window, GCForeground | GCLineWidth, &gcv);
+
+ sprintf (buf, "\"%.100s\" %.100s", name, error);
+
+ XftTextExtentsUtf8 (si->dpy, font, (FcChar8 *) buf, strlen(buf), &overall);
+ x = (ssi->width - overall.width) / 2;
+ y = (ssi->height - overall.height) / 2 + font->ascent;
+ pad = bw + font->ascent * 2;
+
+ attrmask = CWX | CWY | CWWidth | CWHeight;
+ changes.x = ssi->x + x - pad;
+ changes.y = ssi->y + y - (font->ascent + pad);
+ changes.width = overall.width + pad * 2;
+ changes.height = font->ascent + font->descent + pad * 2;
+ XConfigureWindow (si->dpy, window, attrmask, &changes);
+ xscreensaver_set_wm_atoms (si->dpy, window, changes.width, changes.height,
+ ssi->screensaver_window);
+ XMapRaised (si->dpy, window);
+ XClearWindow (si->dpy, window);
+
+ XDrawRectangle (si->dpy, window, gc, gcv.line_width/2, gcv.line_width/2,
+ changes.width - gcv.line_width,
+ changes.height - gcv.line_width);
+
+ x = pad;
+ y = font->ascent + pad;
+ XftDrawStringUtf8 (xftdraw, &fg, font, x, y, (FcChar8 *) buf, strlen (buf));
+ XSync (si->dpy, False);
+
+ XFreeGC (si->dpy, gc);
+ XftDrawDestroy (xftdraw);
+ /* XftColorFree (si->dpy, visual, cmap, &fg); */
+ XftFontClose (si->dpy, font);
+ free (fn);
+ free (cn);
+}
+
+
+/* This timer goes off every few minutes to try and clean up anything that has
+ gone wrong. It raises the windows, in case some other window has been
+ mapped on top of them, and re-sets the server's DPMS settings.
+
+ Maybe we should respond to Expose events to detect when another window has
+ raised above us and re-raise ourselves sooner. But that would result in us
+ fighting against "xscreensaver-auth" which tries very hard to be on top.
+ */
+static void
+watchdog_timer (XtPointer closure, XtIntervalId *id)
+{
+ saver_info *si = (saver_info *) closure;
+ saver_preferences *p = &si->prefs;
+
+ /* If the DPMS settings on the server have changed, change them back to
+ what ~/.xscreensaver says they should be. */
+ sync_server_dpms_settings (si->dpy, p);
+
+ if (si->prefs.debug_p)
+ fprintf (stderr, "%s: watchdog timer raising screen\n", blurb());
+
+ raise_windows (si);
+
+ if (any_screenhacks_running_p (si) &&
+ !monitor_powered_on_p (si->dpy))
+ {
+ int i;
+ if (si->prefs.verbose_p)
+ fprintf (stderr,
+ "%s: monitor has powered down; killing running hacks\n",
+ blurb());
+ for (i = 0; i < si->nscreens; i++)
+ kill_screenhack (&si->screens[i]);
+ }
+
+ /* Re-schedule this timer. The watchdog timer defaults to a bit less
+ than the hack cycle period, but is never longer than one hour.
+ */
+ si->watchdog_id = 0;
+ reset_watchdog_timer (si);
+}
+
+
+static void
+reset_watchdog_timer (saver_info *si)
+{
+ saver_preferences *p = &si->prefs;
+
+ if (si->watchdog_id)
+ {
+ XtRemoveTimeOut (si->watchdog_id);
+ si->watchdog_id = 0;
+ }
+
+ if (p->watchdog_timeout <= 0) return;
+ si->watchdog_id = XtAppAddTimeOut (si->app, p->watchdog_timeout,
+ watchdog_timer, (XtPointer) si);
+ if (p->debug_p)
+ fprintf (stderr, "%s: restarting watchdog_timer (%ld, %ld)\n",
+ blurb(), p->watchdog_timeout, si->watchdog_id);
+}
diff --git a/driver/xdpyinfo.c b/driver/xdpyinfo.c
index 9f67966..4bb071c 100644
--- a/driver/xdpyinfo.c
+++ b/driver/xdpyinfo.c
@@ -34,9 +34,14 @@ in this Software without prior written authorization from The Open Group.
* -DHAVE_XRECORD
*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xproto.h> /* for CARD32 */
+#include <X11/extensions/shape.h>
#include <X11/extensions/multibuf.h>
#ifdef HAVE_XIE
#include <X11/extensions/XIElib.h>
@@ -69,18 +74,16 @@ in this Software without prior written authorization from The Open Group.
char *ProgramName;
Bool queryExtensions = False;
-static int StrCmp(a, b)
- char **a, **b;
+static int StrCmp(const void *a, const void *b)
{
- return strcmp(*a, *b);
+ return strcmp(*((char **)a), *((char **)b));
}
#ifdef HAVE_GLX /* Added by jwz, 11-Nov-99 */
static void
-print_glx_versions (dpy)
- Display *dpy;
+print_glx_versions (Display *dpy)
{
/* Note: with Mesa 3.0, this lies: it prints the info from the
client's GL library, rather than the info from the GLX server.
@@ -100,9 +103,7 @@ print_glx_versions (dpy)
}
static void
-print_glx_visual_info (dpy, vip)
- Display *dpy;
- XVisualInfo *vip;
+print_glx_visual_info (Display *dpy, XVisualInfo *vip)
{
int status, value = False;
@@ -271,8 +272,7 @@ struct overlay_list
static struct overlay_list *overlays = 0;
static void
-find_overlay_info (dpy)
- Display *dpy;
+find_overlay_info (Display *dpy)
{
int screen;
Atom OVERLAY = XInternAtom (dpy, "SERVER_OVERLAY_VISUALS", False);
@@ -307,8 +307,7 @@ find_overlay_info (dpy)
}
static void
-print_overlay_visual_info (vip)
- XVisualInfo *vip;
+print_overlay_visual_info (XVisualInfo *vip)
{
int i;
int vis = vip->visualid;
@@ -327,7 +326,7 @@ print_overlay_visual_info (vip)
if (ov->transparency == 1)
printf ("transparent pixel %lu\n", (unsigned long) ov->value);
else if (ov->transparency == 2)
- printf ("transparent mask 0x%x\n", (unsigned long) ov->value);
+ printf ("transparent mask 0x%lx\n", (unsigned long) ov->value);
else
printf ("opaque\n");
}
@@ -335,9 +334,8 @@ print_overlay_visual_info (vip)
#endif /* HAVE_OVERLAY */
-void
-print_extension_info (dpy)
- Display *dpy;
+static void
+print_extension_info (Display *dpy)
{
int n = 0;
char **extlist = XListExtensions (dpy, &n);
@@ -367,9 +365,8 @@ print_extension_info (dpy)
}
}
-void
-print_display_info (dpy)
- Display *dpy;
+static void
+print_display_info (Display *dpy)
{
char dummybuf[40];
char *cp;
@@ -393,7 +390,7 @@ print_display_info (dpy)
req_size = XExtendedMaxRequestSize (dpy);
if (!req_size) req_size = XMaxRequestSize (dpy);
printf ("maximum request size: %ld bytes\n", req_size * 4);
- printf ("motion buffer size: %d\n", XDisplayMotionBufferSize (dpy));
+ printf ("motion buffer size: %ld\n", XDisplayMotionBufferSize (dpy));
switch (BitmapBitOrder (dpy)) {
case LSBFirst: cp = "LSBFirst"; break;
@@ -470,9 +467,8 @@ print_display_info (dpy)
printf ("number of screens: %d\n", ScreenCount (dpy));
}
-void
-print_visual_info (vip)
- XVisualInfo *vip;
+static void
+print_visual_info (XVisualInfo *vip)
{
char errorbuf[40]; /* for sprintfing into */
char *class = NULL; /* for printing */
@@ -507,10 +503,15 @@ print_visual_info (vip)
vip->bits_per_rgb);
}
-void
-print_screen_info (dpy, scr)
- Display *dpy;
- int scr;
+static int print_event_mask (
+ char *buf, /* string to write into */
+ int lastcol, /* strlen(buf)+1 */
+ int indent, /* amount by which to indent */
+ long mask); /* event mask */
+
+
+static void
+print_screen_info (Display *dpy, int scr)
{
Screen *s = ScreenOfDisplay (dpy, scr); /* opaque structure */
XVisualInfo viproto; /* fill in for getting info */
@@ -565,7 +566,7 @@ print_screen_info (dpy, scr)
printf (" default colormap: 0x%lx\n", DefaultColormap (dpy, scr));
printf (" default number of colormap cells: %d\n",
DisplayCells (dpy, scr));
- printf (" preallocated pixels: black %d, white %d\n",
+ printf (" preallocated pixels: black %lu, white %lu\n",
BlackPixel (dpy, scr), WhitePixel (dpy, scr));
printf (" options: backing-store %s, save-unders %s\n",
(DoesBackingStore (s) == NotUseful) ? no :
@@ -637,11 +638,11 @@ static struct _event_table {
{ "OwnerGrabButtonMask ", OwnerGrabButtonMask },
{ NULL, 0 }};
-int print_event_mask (buf, lastcol, indent, mask)
- char *buf; /* string to write into */
- int lastcol; /* strlen(buf)+1 */
- int indent; /* amount by which to indent */
- long mask; /* event mask */
+static int print_event_mask (
+ char *buf, /* string to write into */
+ int lastcol, /* strlen(buf)+1 */
+ int indent, /* amount by which to indent */
+ long mask) /* event mask */
{
struct _event_table *etp;
int len;
@@ -673,11 +674,9 @@ int print_event_mask (buf, lastcol, indent, mask)
return (bitsfound);
}
-void
-print_standard_extension_info(dpy, extname, majorrev, minorrev)
- Display *dpy;
- char *extname;
- int majorrev, minorrev;
+static void
+print_standard_extension_info(Display *dpy, char *extname,
+ int majorrev, int minorrev)
{
int opcode, event, error;
@@ -692,10 +691,8 @@ print_standard_extension_info(dpy, extname, majorrev, minorrev)
printf("\n");
}
-int
-print_multibuf_info(dpy, extname)
- Display *dpy;
- char *extname;
+static int
+print_multibuf_info(Display *dpy, char *extname)
{
int i, j; /* temp variable: iterator */
int nmono, nstereo; /* count */
@@ -850,10 +847,8 @@ print_sync_info(dpy, extname)
}
#endif /* HAVE_XSYNC */
-int
-print_shape_info(dpy, extname)
- Display *dpy;
- char *extname;
+static int
+print_shape_info(Display *dpy, char *extname)
{
int majorrev, minorrev;
@@ -979,9 +974,8 @@ ExtensionPrintInfo known_extensions[] =
int num_known_extensions = sizeof known_extensions / sizeof known_extensions[0];
-void
-print_known_extensions(f)
- FILE *f;
+static void
+print_known_extensions(FILE *f)
{
int i;
for (i = 0; i < num_known_extensions; i++)
@@ -990,9 +984,8 @@ print_known_extensions(f)
}
}
-void
-mark_extension_for_printing(extname)
- char *extname;
+static void
+mark_extension_for_printing(char *extname)
{
int i;
@@ -1015,9 +1008,8 @@ mark_extension_for_printing(extname)
}
}
-void
-print_marked_extensions(dpy)
- Display *dpy;
+static void
+print_marked_extensions(Display *dpy)
{
int i;
for (i = 0; i < num_known_extensions; i++)
@@ -1035,7 +1027,7 @@ print_marked_extensions(dpy)
}
}
-static void usage ()
+static void usage (void)
{
fprintf (stderr, "usage: %s [options]\n", ProgramName);
fprintf (stderr, "-display displayname\tserver to query\n");
@@ -1047,15 +1039,11 @@ static void usage ()
exit (1);
}
-int main (argc, argv)
- int argc;
- char *argv[];
+int main (int argc, char *argv[])
{
Display *dpy; /* X connection */
char *displayname = NULL; /* server to contact */
int i; /* temp variable: iterator */
- Bool multibuf = False;
- int mbuf_event_base, mbuf_error_base;
ProgramName = argv[0];
@@ -1077,7 +1065,7 @@ int main (argc, argv)
dpy = XOpenDisplay (displayname);
if (!dpy) {
- fprintf (stderr, "%s: unable to open display \"%s\".\n",
+ fprintf (stderr, "%s: unable to open display \"%s\"\n",
ProgramName, XDisplayName (displayname));
exit (1);
}
diff --git a/driver/xinput.c b/driver/xinput.c
new file mode 100644
index 0000000..3b21db0
--- /dev/null
+++ b/driver/xinput.c
@@ -0,0 +1,381 @@
+/* xscreensaver, Copyright © 1991-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.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/extensions/XInput2.h>
+
+#include "blurb.h"
+#include "xinput.h"
+
+extern Bool debug_p;
+
+#undef countof
+#define countof(x) (sizeof((x))/sizeof((*x)))
+
+
+/* Initialize the XInput2 extension. Returns True on success.
+ */
+Bool
+init_xinput (Display *dpy, int *opcode_ret)
+{
+ int nscreens = ScreenCount (dpy);
+ XIEventMask evmasks[1];
+ unsigned char mask1[(XI_LASTEVENT + 7)/8];
+ int major, minor;
+ int xi_opcode, ev, err;
+ int i;
+ int ndevs = 0;
+ XIDeviceInfo *devs;
+
+ if (!XQueryExtension (dpy, "XInputExtension", &xi_opcode, &ev, &err))
+ {
+ fprintf (stderr, "%s: XInput extension missing\n", blurb());
+ return False;
+ }
+
+ major = 2; /* Desired version */
+ minor = 2;
+ if (XIQueryVersion (dpy, &major, &minor) != Success)
+ {
+ fprintf (stderr, "%s: server only supports XInput %d.%d\n",
+ blurb(), major, minor);
+ return False;
+ }
+
+ if (verbose_p)
+ fprintf (stderr, "%s: XInput version %d.%d\n", blurb(), major, minor);
+
+ memset (mask1, 0, sizeof(mask1));
+
+ XISetMask (mask1, XI_RawMotion);
+ XISetMask (mask1, XI_RawKeyPress);
+ XISetMask (mask1, XI_RawKeyRelease);
+ XISetMask (mask1, XI_RawButtonPress);
+ XISetMask (mask1, XI_RawButtonRelease);
+ XISetMask (mask1, XI_RawTouchBegin);
+ XISetMask (mask1, XI_RawTouchUpdate);
+ XISetMask (mask1, XI_RawTouchEnd);
+
+ /* If we use XIAllDevices instead, we get double events. */
+ evmasks[0].deviceid = XIAllMasterDevices;
+ evmasks[0].mask_len = sizeof(mask1);
+ evmasks[0].mask = mask1;
+
+ for (i = 0; i < nscreens; i++)
+ {
+ Window root = RootWindow (dpy, i);
+ if (XISelectEvents (dpy, root, evmasks, countof(evmasks)) != Success)
+ {
+ fprintf (stderr, "%s: XISelectEvents failed\n", blurb());
+ return False;
+ }
+ }
+
+ XFlush (dpy);
+
+ devs = XIQueryDevice (dpy, XIAllDevices, &ndevs);
+ if (!ndevs)
+ {
+ fprintf (stderr, "%s: XInput: no devices\n", blurb());
+ return False;
+ }
+
+ if (verbose_p)
+ for (i = 0; i < ndevs; i++)
+ {
+ XIDeviceInfo *d = &devs[i];
+ fprintf (stderr, "%s: device %2d/%d: %s: %s\n",
+ blurb(), d->deviceid, d->attachment,
+ (d->use == XIMasterPointer ? "MP" :
+ d->use == XIMasterKeyboard ? "MK" :
+ d->use == XISlavePointer ? "SP" :
+ d->use == XISlaveKeyboard ? "SK" :
+ d->use == XIFloatingSlave ? "FS" : "??"),
+ d->name);
+ }
+
+ XIFreeDeviceInfo (devs);
+ *opcode_ret = xi_opcode;
+ return True;
+}
+
+
+/* Convert an XInput2 event to corresponding old-school Xlib event.
+ Returns true on success.
+ */
+Bool
+xinput_event_to_xlib (int evtype, XIDeviceEvent *in, XEvent *out)
+{
+ Display *dpy = in->display;
+ Bool ok = False;
+
+ int root_x = 0, root_y = 0;
+ unsigned int mods = 0;
+
+ /* The closest thing to actual documentation on XInput2 seems to be a series
+ of blog posts by Peter Hutterer. There's basically nothing about it on
+ www.x.org. In http://who-t.blogspot.com/2009/07/xi2-recipes-part-4.html
+ he says:
+
+ "XIDeviceEvent [...] contains the state of the modifier keys [...]
+ The base modifiers are the ones currently pressed, latched the ones
+ pressed until a key is pressed that's configured to unlatch it (e.g.
+ some shift-capslock interactions have this behaviour) and finally
+ locked modifiers are the ones permanently active until unlocked
+ (default capslock behaviour in the US layout). The effective modifiers
+ are a bitwise OR of the three above - which is essentially equivalent
+ to the modifiers state supplied in the core protocol events."
+
+ However, I'm seeing random noise in the various XIDeviceEvent.mods fields.
+ Nonsensical values like base = 0x6045FB3D. So, let's poll the actual
+ modifiers from XQueryPointer. This can race: maybe the modifier state
+ changed between when the server generated the keyboard event, and when
+ we receive it and poll. However, if an actual human is typing and
+ releasing their modifier keys on such a tight timeframe... that's
+ probably already not going well.
+
+ I'm also seeing random noise in the event_xy and root_xy fields in
+ motion events. So just always use XQueryPointer.
+ */
+ switch (evtype) {
+ case XI_RawKeyPress:
+ case XI_RawKeyRelease:
+ case XI_RawButtonPress:
+ case XI_RawButtonRelease:
+ case XI_RawMotion:
+ {
+ Window root_ret, child_ret;
+ int win_x, win_y;
+ int i;
+ for (i = 0; i < ScreenCount (dpy); i++) /* query on correct screen */
+ if (XQueryPointer (dpy, RootWindow (dpy, i),
+ &root_ret, &child_ret, &root_x, &root_y,
+ &win_x, &win_y, &mods))
+ break;
+ }
+ default: break;
+ }
+
+ switch (evtype) {
+ case XI_RawKeyPress:
+ case XI_RawKeyRelease:
+ out->xkey.type = (evtype == XI_RawKeyPress ? KeyPress : KeyRelease);
+ out->xkey.display = in->display;
+ out->xkey.window = in->event;
+ out->xkey.root = in->root;
+ out->xkey.subwindow = in->child;
+ out->xkey.time = in->time;
+ out->xkey.x = root_x;
+ out->xkey.y = root_y;
+ out->xkey.x_root = root_x;
+ out->xkey.y_root = root_y;
+ out->xkey.state = mods;
+ out->xkey.keycode = in->detail;
+ ok = True;
+ break;
+ case XI_RawButtonPress:
+ case XI_RawButtonRelease:
+ out->xbutton.type = (evtype == XI_RawButtonPress
+ ? ButtonPress : ButtonRelease);
+ out->xbutton.display = in->display;
+ out->xbutton.window = in->event;
+ out->xbutton.root = in->root;
+ out->xbutton.subwindow = in->child;
+ out->xbutton.time = in->time;
+ out->xbutton.x = root_x;
+ out->xbutton.y = root_y;
+ out->xbutton.x_root = root_x;
+ out->xbutton.y_root = root_y;
+ out->xbutton.state = mods;
+ out->xbutton.button = in->detail;
+ ok = True;
+ break;
+ case XI_RawMotion:
+ out->xmotion.type = MotionNotify;
+ out->xmotion.display = in->display;
+ out->xmotion.window = in->event;
+ out->xmotion.root = in->root;
+ out->xmotion.subwindow = in->child;
+ out->xmotion.time = in->time;
+ out->xmotion.x = root_x;
+ out->xmotion.y = root_y;
+ out->xmotion.x_root = root_x;
+ out->xmotion.y_root = root_y;
+ out->xmotion.state = mods;
+ ok = True;
+ break;
+ default:
+ break;
+ }
+
+ return ok;
+}
+
+
+static void
+print_kbd_event (XEvent *xev, XComposeStatus *compose, Bool x11_p)
+{
+ if (debug_p) /* Passwords show up in plaintext! */
+ {
+ KeySym keysym = 0;
+ char c[100];
+ char M[100], *mods = M;
+ int n = XLookupString (&xev->xkey, c, sizeof(c)-1, &keysym, compose);
+ const char *ks = keysym ? XKeysymToString (keysym) : "NULL";
+ c[n] = 0;
+ if (*c == '\n') strcpy (c, "\\n");
+ else if (*c == '\r') strcpy (c, "\\r");
+ else if (*c == '\t') strcpy (c, "\\t");
+
+ *mods = 0;
+ if (xev->xkey.state & ShiftMask) strcat (mods, "-Sh");
+ if (xev->xkey.state & LockMask) strcat (mods, "-Lk");
+ if (xev->xkey.state & ControlMask) strcat (mods, "-C");
+ if (xev->xkey.state & Mod1Mask) strcat (mods, "-M1");
+ if (xev->xkey.state & Mod2Mask) strcat (mods, "-M2");
+ if (xev->xkey.state & Mod3Mask) strcat (mods, "-M3");
+ if (xev->xkey.state & Mod4Mask) strcat (mods, "-M4");
+ if (xev->xkey.state & Mod5Mask) strcat (mods, "-M5");
+ if (*mods) mods++;
+ if (!*mods) strcat (mods, "0");
+
+ fprintf (stderr, "%s: %s 0x%02X %s %s \"%s\"\n", blurb(),
+ (x11_p
+ ? (xev->xkey.type == KeyPress
+ ? "X11 KeyPress "
+ : "X11 KeyRelease ")
+ : (xev->xkey.type == KeyPress
+ ? "XI_RawKeyPress "
+ : "XI_RawKeyRelease")),
+ xev->xkey.keycode, mods, ks, c);
+ }
+ else /* Log only that the KeyPress happened. */
+ {
+ fprintf (stderr, "%s: X11 Key%s\n", blurb(),
+ (xev->xkey.type == KeyPress ? "Press " : "Release"));
+ }
+}
+
+
+void
+print_xinput_event (Display *dpy, XEvent *xev, const char *desc)
+{
+ XIRawEvent *re;
+
+ switch (xev->xany.type) {
+ case KeyPress:
+ case KeyRelease:
+ {
+ static XComposeStatus compose = { 0, };
+ print_kbd_event (xev, &compose, True);
+ }
+ break;
+
+ case ButtonPress:
+ case ButtonRelease:
+ fprintf (stderr, "%s: X11 Button%s %d %d\n", blurb(),
+ (xev->xany.type == ButtonPress ? "Press " : "Release"),
+ xev->xbutton.button, xev->xbutton.state);
+ break;
+
+ case MotionNotify:
+ fprintf (stderr, "%s: X11 MotionNotify %4d, %-4d"
+ " %s\n",
+ blurb(), xev->xmotion.x_root, xev->xmotion.y_root,
+ (desc ? desc : ""));
+ break;
+ default:
+ break;
+ }
+
+ if (xev->xany.type != GenericEvent)
+ return; /* not an XInput event */
+
+ if (!xev->xcookie.data)
+ XGetEventData (dpy, &xev->xcookie);
+
+ re = xev->xcookie.data;
+ if (!re) return; /* Bogus XInput event */
+
+ switch (re->evtype) {
+ case XI_RawKeyPress:
+ case XI_RawKeyRelease:
+ if (debug_p)
+ {
+ /* Fake up an XKeyEvent in order to call XKeysymToString(). */
+ XEvent ev2;
+ Bool ok = xinput_event_to_xlib (xev->xcookie.evtype,
+ (XIDeviceEvent *) re,
+ &ev2);
+ if (!ok)
+ fprintf (stderr, "%s: unable to translate XInput2 event\n", blurb());
+ else
+ {
+ static XComposeStatus compose = { 0, };
+ print_kbd_event (&ev2, &compose, False);
+ }
+ break;
+ }
+ else
+ fprintf (stderr, "%s: XI RawKey%s\n", blurb(),
+ (re->evtype == XI_RawKeyPress ? "Press " : "Release"));
+ break;
+
+ case XI_RawButtonPress:
+ case XI_RawButtonRelease:
+ fprintf (stderr, "%s: XI RawButton%s %d\n", blurb(),
+ (re->evtype == XI_RawButtonPress ? "Press " : "Release"),
+ re->detail);
+ break;
+
+ case XI_RawMotion:
+ if (verbose_p > 1)
+ {
+ Window root_ret, child_ret;
+ int root_x, root_y;
+ int win_x, win_y;
+ unsigned int mask;
+ XQueryPointer (dpy, DefaultRootWindow (dpy),
+ &root_ret, &child_ret, &root_x, &root_y,
+ &win_x, &win_y, &mask);
+ fprintf (stderr,
+ "%s: XI_RawMotion %4d, %-4d %7.02f, %-7.02f%s\n",
+ blurb(), root_x, root_y, re->raw_values[0], re->raw_values[1],
+ (desc ? desc : ""));
+ }
+ break;
+
+ /* Touch-screens, possibly trackpads or tablets. */
+ case XI_RawTouchBegin:
+ fprintf (stderr, "%s: XI RawTouchBegin\n", blurb());
+ break;
+ case XI_RawTouchEnd:
+ fprintf (stderr, "%s: XI RawTouchEnd\n", blurb());
+ break;
+ case XI_RawTouchUpdate:
+ if (verbose_p > 1)
+ fprintf (stderr, "%s: XI RawTouchUpdate\n", blurb());
+ break;
+
+ default:
+ fprintf (stderr, "%s: unknown XInput event %d\n", blurb(), re->type);
+ break;
+ }
+}
diff --git a/driver/xinput.h b/driver/xinput.h
new file mode 100644
index 0000000..2ee20ee
--- /dev/null
+++ b/driver/xinput.h
@@ -0,0 +1,19 @@
+/* xscreensaver, Copyright © 1991-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.
+ */
+
+#ifndef __XSCREENSAVER_XINPUT_H__
+#define __XSCREENSAVER_XINPUT_H__
+
+extern Bool init_xinput (Display *dpy, int *opcode_ret);
+extern Bool xinput_event_to_xlib (int evtype, XIDeviceEvent *in, XEvent *out);
+extern void print_xinput_event (Display *, XEvent *, const char *desc);
+
+#endif /* __XSCREENSAVER_XINPUT_H__ */
diff --git a/driver/xscreensaver-auth.c b/driver/xscreensaver-auth.c
new file mode 100644
index 0000000..4344d20
--- /dev/null
+++ b/driver/xscreensaver-auth.c
@@ -0,0 +1,340 @@
+/* xscreensaver, Copyright © 1991-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.
+ *
+ * XScreenSaver Daemon, version 6.
+ *
+ * This is "xscreensaver-auth" -- When the screen is locked and there is
+ * user activity, this program is launched to pop up a dialog, authenticate
+ * the user, and eventually exit with a status code indicating success or
+ * failure. See the comment atop xscreensaver.c for details of the division
+ * of powers.
+ *
+ * Flow of control within xscreensaver-auth:
+ *
+ * - Privileged password initialization (e.g. read /etc/shadow as root)
+ * - Disavow privileges
+ * - Unprivileged password initialization
+ * - Connect to X server
+ * - xss_authenticate (passwd.c)
+ * - Tries PAM, Kerberos, pwent, shadow passwords (passwd-*.c) until
+ * one of them works. Non-PAM methods are wrapped to act like PAM.
+ * - pam_conv calls our "conversation" function zero or more times.
+ * That function is expected to present messages to the user and/or
+ * to prompt the user to answer a question, wait for the answer, and
+ * return it. There might be only one question (the password) or
+ * there might be others, even multiple passwords.
+ * - xscreensaver_auth_conv (dialog.c) is our conversation function.
+ * - First time it is called, it creates the window.
+ * - Subsequent times, it reuses that window.
+ * - Runs an X11 event loop waiting for the user to complete
+ * or timeout, then returns the entered strings.
+ * - pam_conv takes the user input and returns success/failure.
+ * - xscreensaver_auth_finished is called to pop up a final dialog to
+ * present any error messages.
+ * - Exit with appropriate code.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdio.h>
+#include <ctype.h>
+#include <X11/Xlib.h>
+#include <X11/Intrinsic.h>
+#include <X11/StringDefs.h>
+#include <X11/Shell.h>
+#include <X11/Xos.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <pwd.h>
+
+#ifdef ENABLE_NLS
+# include <locale.h>
+# include <libintl.h>
+#endif
+
+#include "xscreensaver.h"
+#include "version.h"
+#include "atoms.h"
+#include "yarandom.h"
+#include "resources.h"
+#include "visual.h"
+#include "auth.h"
+
+#ifdef __GNUC__
+ __extension__ /* shut up about "string length is greater than the length
+ ISO C89 compilers are required to support" when including
+ the .ad file... */
+#endif
+
+static char *defaults[] = {
+#include "XScreenSaver_ad.h"
+ 0
+};
+
+
+char *progclass = 0;
+Bool debug_p = False;
+
+
+#ifdef HAVE_PROC_OOM
+/* Linux systems have an "out-of-memory killer" that will nuke random procs in
+ low-memory situations. You'd think it would pick the process using the
+ most memory, but most of the time it seems to pick the process that would
+ be most comically inconvenient, such as your screen locker, or crond.
+
+ Since killing "xscreensaver" unlocks the screen... that would be bad.
+
+ This program, "xscreensaver-auth", is the part of the XScreenSaver daemon
+ that might need to be setuid for other reasons, so we handle the OOM killer
+ here. We could instead handle OOM in the "xscreensaver" program, but then
+ that program would *also* need to be setuid.
+
+ So instead, "xscreensaver-auth" sets OOM immunity on its *parent* process.
+ That means that if you run it by hand, it will apply that immunity to the
+ parent shell. Maybe that's bad? I think I don't care.
+
+ Linux >= 2.6.11: echo -17 > /proc/$$/oom_adj
+ Linux >= 2.6.37: echo -1000 > /proc/$$/oom_score_adj
+
+ "An aircraft company discovered that it was cheaper to fly its planes with
+ less fuel on board. On rare occasions, however, the amount of fuel was
+ insufficient, and the plane would crash. In emergency cases, a passenger
+ was selected and thrown out of the plane. When necessary, the procedure
+ was repeated."
+
+ https://lwn.net/Articles/104179/
+
+ The OOM killer preferentially kills processes whose *children* use a lot of
+ memory, and processes that are niced. So if a display mode uses a lot of
+ memory, the OOM-killer is more likely to shoot down the XScreenSaver
+ *daemon* than just that screenhack!
+
+ To disable the OOM-killer entirely:
+
+ echo 2 > /proc/sys/vm/overcommit_memory
+ echo vm.overcommit_memory = 2 >> /etc/sysctl.conf
+ */
+static void
+oom_assassin_immunity (void)
+{
+# define OOM_VAL "-1000"
+ char fn[1024];
+ struct stat st;
+ FILE *fd;
+ pid_t pid = getppid(); /* our parent, not us */
+
+ sprintf (fn, "/proc/%d/oom_score_adj", pid);
+ if (stat(fn, &st) != 0)
+ {
+ if (verbose_p)
+ fprintf (stderr, "%s: OOM: %s does not exist\n", blurb(), fn);
+ return;
+ }
+ fd = fopen (fn, "w");
+ if (!fd) goto FAIL;
+ if (fputs (OOM_VAL "\n", fd) <= 0) goto FAIL;
+ if (fclose (fd) != 0) goto FAIL;
+
+ if (verbose_p)
+ fprintf (stderr, "%s: OOM: echo " OOM_VAL " > %s\n", blurb(), fn);
+ return;
+
+ FAIL:
+ {
+ char buf[1024];
+ const char *b = blurb();
+ sprintf (buf, "%.40s: OOM: %.200s", b, fn);
+ perror (buf);
+ if (getuid() == geteuid())
+ fprintf (stderr,
+ "%s: To prevent the kernel from randomly unlocking\n"
+ "%s: your screen via the out-of-memory killer,\n"
+ "%s: \"%s\" must be setuid root.\n",
+ b, b, b, progname);
+ }
+}
+#endif /* HAVE_PROC_OOM */
+
+
+int
+main (int argc, char **argv)
+{
+ Display *dpy;
+ XtAppContext app;
+ Widget root_widget;
+ char *dpy_str = getenv ("DISPLAY");
+ Bool xsync_p = False;
+ Bool splash_p = False;
+ Bool init_p = False;
+ int i;
+
+# undef ya_rand_init
+ ya_rand_init (0);
+
+ /* For Xt and X resource database purposes, this program is
+ "xscreensaver", not "xscreensaver-auth".
+ */
+ {
+ char *s = strrchr(argv[0], '/');
+ if (s) s++;
+ else s = argv[0];
+ if (strlen(s) > 20) /* keep it short. */
+ s[20] = 0;
+ progname = s;
+ }
+
+ progclass = "XScreenSaver";
+ argv[0] = "xscreensaver";
+
+ if (! dpy_str) dpy_str = ":0";
+
+ for (i = 1; i < argc; i++)
+ {
+ const char *oa = argv[i];
+ /* XScreenSaver predates the "--arg" convention. */
+ if (argv[i][0] == '-' && argv[i][1] == '-')
+ argv[i]++;
+
+ if (!strcmp (argv[i], "-v") || !strcmp (argv[i], "-verbose"))
+ verbose_p++;
+ else if (!strcmp (argv[i], "-vv")) verbose_p += 2;
+ else if (!strcmp (argv[i], "-vvv")) verbose_p += 3;
+ else if (!strcmp (argv[i], "-vvvv")) verbose_p += 4;
+ else if (!strcmp (argv[i], "-q") || !strcmp (argv[i], "-quiet"))
+ verbose_p = False;
+ else if (!strcmp (argv[i], "-debug"))
+ /* Does nothing else at the moment but warn that "xscreensaver"
+ is logging keystrokes to stderr. */
+ debug_p = True;
+ else if (!strcmp (argv[i], "-d") ||
+ !strcmp (argv[i], "-dpy") ||
+ !strcmp (argv[i], "-disp") ||
+ !strcmp (argv[i], "-display"))
+ {
+ dpy_str = argv[++i];
+ if (!dpy_str) goto HELP;
+ }
+ else if (!strcmp (argv[i], "-sync") ||
+ !strcmp (argv[i], "-synch") ||
+ !strcmp (argv[i], "-synchronize") ||
+ !strcmp (argv[i], "-synchronise"))
+ xsync_p = True;
+ else if (!strcmp (argv[i], "-splash"))
+ splash_p = True;
+ else if (!strcmp (argv[i], "-init"))
+ init_p = True;
+ else if (!strcmp (argv[i], "-h") || !strcmp (argv[i], "-help"))
+ {
+ HELP:
+ fprintf (stderr,
+ "\n"
+ "\txscreensaver-auth is launched by the xscreensaver daemon\n"
+ "\tto authenticate the user by prompting for a password.\n"
+ "\tDo not run this directly.\n"
+ "\n"
+ "\tOptions:\n"
+ "\t\t--dpy host:display.screen\n"
+ "\t\t--verbose --sync --splash --init\n"
+ "\n"
+ "\tRun 'xscreensaver-settings' to configure.\n"
+ "\n");
+ exit (1);
+ }
+ else
+ {
+ fprintf (stderr, "\n%s: unknown option: %s\n", blurb(), oa);
+ goto HELP;
+ }
+ }
+
+ if (!splash_p && init_p)
+ {
+ const char *v = XSCREENSAVER_VERSION;
+ if (strstr (v, "a") || strstr (v, "b"))
+ splash_p = True; /* Not optional for alpha and beta releases */
+ }
+
+# ifdef HAVE_PROC_OOM
+ if (splash_p || init_p)
+ oom_assassin_immunity ();
+# endif
+
+ if (!splash_p && !init_p)
+ lock_priv_init ();
+
+ if (!splash_p && init_p)
+ exit (0);
+
+ disavow_privileges ();
+
+ if (!splash_p)
+ lock_init ();
+
+ /* Setting the locale is necessary for XLookupString to return multi-byte
+ characters, enabling their use in passwords.
+ */
+# ifdef ENABLE_NLS
+ {
+ bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+ textdomain (GETTEXT_PACKAGE);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ if (!setlocale (LC_ALL, ""))
+ fprintf (stderr, "%s: warning: could not set default locale\n",
+ progname);
+ }
+# endif /* ENABLE_NLS */
+
+
+ /* Copy the -dpy arg to $DISPLAY for subprocesses. */
+ {
+ char *s = (char *) malloc (strlen(dpy_str) + 20);
+ sprintf (s, "DISPLAY=%s", dpy_str);
+ putenv (s);
+ /* free (s); */ /* some versions of putenv do not copy */
+ }
+
+ /* Open the display */
+ {
+ XrmOptionDescRec options;
+ argc = 1; /* Xt does not receive any of our command-line options. */
+ root_widget = XtAppInitialize (&app, progclass, &options, 0,
+ &argc, argv, defaults, 0, 0);
+ }
+
+ dpy = XtDisplay (root_widget);
+ if (xsync_p) XSynchronize (dpy, True);
+ init_xscreensaver_atoms (dpy);
+
+ if (splash_p)
+ {
+ xscreensaver_splash (root_widget);
+ exit (0);
+ }
+ else if (xscreensaver_auth ((void *) root_widget,
+ xscreensaver_auth_conv,
+ xscreensaver_auth_finished))
+ {
+ if (verbose_p)
+ fprintf (stderr, "%s: authentication succeeded\n", blurb());
+ exit (200);
+ }
+ else
+ {
+ if (verbose_p)
+ fprintf (stderr, "%s: authentication failed\n", blurb());
+ exit (-1);
+ }
+
+ /* On timeout, xscreensaver_auth did exit(0) */
+}
diff --git a/driver/xscreensaver-auth.man b/driver/xscreensaver-auth.man
new file mode 100644
index 0000000..1655602
--- /dev/null
+++ b/driver/xscreensaver-auth.man
@@ -0,0 +1,27 @@
+.TH XScreenSaver 1 "06-Jan-2021 (6.00)" "X Version 11"
+.SH NAME
+xscreensaver - extensible screen saver and screen locking framework
+.SH SYNOPSIS
+.B xscreensaver-auth
+[\-display \fIhost:display.screen\fP]
+.SH DESCRIPTION
+The
+.BR xscreensaver (1)
+daemon launches this program to prompt the user for their password.
+Do not run this program directly.
+.SH SEE ALSO
+.BR xscreensaver (1),
+.BR xscreensaver\-settings (1),
+.BR xscreensaver\-gfx (MANSUFFIX),
+.BR xscreensaver\-systemd (MANSUFFIX).
+.SH COPYRIGHT
+Copyright \(co 2021 by Jamie Zawinski.
+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.
+.SH AUTHOR
+Jamie Zawinski <jwz@jwz.org>
diff --git a/driver/xscreensaver-command.c b/driver/xscreensaver-command.c
index 4a4f0b4..0dab564 100644
--- a/driver/xscreensaver-command.c
+++ b/driver/xscreensaver-command.c
@@ -1,4 +1,4 @@
-/* xscreensaver-command, Copyright (c) 1991-2019 Jamie Zawinski <jwz@jwz.org>
+/* xscreensaver-command, Copyright © 1991-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
@@ -31,30 +31,15 @@
#include <X11/Intrinsic.h> /* only needed to get through xscreensaver.h */
-
-/* You might think that to read an array of 32-bit quantities out of a
- server-side property, you would pass an array of 32-bit data quantities
- into XGetWindowProperty(). You would be wrong. You have to use an array
- of longs, even if long is 64 bits (using 32 of each 64.)
- */
-typedef long PROP32;
-
+#include "blurb.h"
#include "remote.h"
#include "version.h"
+#include "atoms.h"
#ifdef _VROOT_H_
ERROR! you must not include vroot.h in this file
#endif
-char *progname;
-
-Atom XA_VROOT;
-Atom XA_SCREENSAVER, XA_SCREENSAVER_VERSION, XA_SCREENSAVER_RESPONSE;
-Atom XA_SCREENSAVER_ID, XA_SCREENSAVER_STATUS, XA_SELECT, XA_DEMO, XA_EXIT;
-Atom XA_BLANK, XA_LOCK, XA_ACTIVATE, XA_SUSPEND, XA_NEXT, XA_PREV;
-static Atom XA_DEACTIVATE, XA_CYCLE;
-static Atom XA_RESTART, XA_PREFS, XA_THROTTLE, XA_UNTHROTTLE;
-
static char *screensaver_version;
# ifdef __GNUC__
__extension__ /* don't warn about "string length is greater than the
@@ -72,10 +57,8 @@ usage: %s -<option>\n\
with this program, xscreensaver-command. See the man pages for\n\
details. These are the arguments understood by xscreensaver-command:\n\
\n\
- -demo Ask the xscreensaver process to enter interactive demo mode.\n\
-\n\
- -prefs Ask the xscreensaver process to bring up the preferences\n\
- panel.\n\
+ -quiet Only print output if an error occurs.\n\
+ -verbose Opposite of -quiet. Default.\n\
\n\
-activate Turn on the screensaver (blank the screen), as if the user\n\
had been idle for long enough.\n\
@@ -92,8 +75,7 @@ usage: %s -<option>\n\
is the next one in the list, instead of a randomly-chosen\n\
one. In other words, repeatedly executing -next will cause\n\
the xscreensaver process to invoke each graphics demo\n\
- sequentially. (Though using the -demo option is probably\n\
- an easier way to accomplish that.)\n\
+ sequentially.\n\
\n\
-prev Like -next, but goes in the other direction.\n\
\n\
@@ -103,15 +85,6 @@ usage: %s -<option>\n\
with a particular graphics demo. (The first element in the\n\
list is numbered 1, not 0.)\n\
\n\
- -exit Causes the xscreensaver process to exit gracefully.\n\
- This does nothing if the display is currently locked.\n\
- (Note that one must *never* kill xscreensaver with -9!)\n\
-\n\
- -restart Causes the screensaver process to exit and then restart with\n\
- the same command line arguments as last time. You shouldn't\n\
- really need to do this, since xscreensaver notices when the\n\
- .xscreensaver file has changed and re-reads it as needed.\n\
-\n\
-lock Tells the running xscreensaver process to lock the screen\n\
immediately. This is like -activate, but forces locking as\n\
well, even if locking is not the default. If the saver is\n\
@@ -122,10 +95,14 @@ usage: %s -<option>\n\
This is intended to be run just after your laptop's lid\n\
is closed, and just before the CPU halts.\n\
\n\
- -version Prints the version of xscreensaver that is currently running\n\
- on the display -- that is, the actual version number of the\n\
- running xscreensaver background process, rather than the\n\
- version number of xscreensaver-command.\n\
+ -exit Causes the xscreensaver process to exit gracefully.\n\
+ This does nothing if the display is currently locked.\n\
+ (Note that one must *never* kill xscreensaver with -9!)\n\
+\n\
+ -restart Causes the screensaver process to exit and then restart with\n\
+ the same command line arguments as last time. You shouldn't\n\
+ really need to do this, since xscreensaver notices when the\n\
+ .xscreensaver file has changed and re-reads it as needed.\n\
\n\
-time Prints the time at which the screensaver last activated or\n\
deactivated (roughly, how long the user has been idle or\n\
@@ -138,19 +115,19 @@ usage: %s -<option>\n\
use by shell scripts that want to react to the screensaver\n\
in some way.\n\
\n\
+ -version Prints the version of xscreensaver that is currently running\n\
+ on the display -- that is, the actual version number of the\n\
+ running xscreensaver background process, rather than the\n\
+ version number of xscreensaver-command.\n\
+\n\
See the man page for more details.\n\
For updates, check https://www.jwz.org/xscreensaver/\n\
\n";
-/* Note: The "-throttle" command is deprecated -- it predates the XDPMS
- extension. Instead of using -throttle, users should instead just
- power off the monitor (e.g., "xset dpms force off".) In a few
- minutes, the xscreensaver daemon will notice that the monitor is
- off, and cease running hacks.
- */
-
-#define USAGE() do { \
- fprintf (stderr, usage, progname, screensaver_version, year); exit (1); \
+#define USAGE(A,B) do { \
+ fprintf (stderr, "%s: %s: %s\n%s: try --help\n", \
+ progname, (A), ((B) ? (B) : ""), progname); \
+ exit (1); \
} while(0)
static int watch (Display *);
@@ -162,18 +139,20 @@ main (int argc, char **argv)
int i;
char *dpyname = 0;
Atom *cmd = 0;
+ const char *cmdstr = 0;
long arg = 0L;
char *s;
Atom XA_WATCH = 0; /* kludge: not really an atom */
char year[5];
+ Bool verbose_p = TRUE;
progname = argv[0];
s = strrchr (progname, '/');
if (s) progname = s+1;
- screensaver_version = (char *) malloc (5);
- memcpy (screensaver_version, screensaver_id + 17, 4);
- screensaver_version [4] = 0;
+ screensaver_version = strdup (screensaver_id + 17);
+ s = strchr (screensaver_version, ' ');
+ *s = 0;
s = strchr (screensaver_id, '-');
s = strrchr (s, '-');
@@ -187,9 +166,16 @@ main (int argc, char **argv)
int L;
if (s[0] == '-' && s[1] == '-') s++;
L = strlen (s);
- if (L < 2) USAGE ();
- if (!strncmp (s, "-display", L)) dpyname = argv [++i];
- else if (cmd) USAGE();
+ if (L < 2) USAGE ("unrecognized", argv[i]);
+ if (!strncmp (s, "-display", L)) dpyname = argv [++i];
+ else if (!strncmp (s, "-dpy", L)) dpyname = argv [++i];
+ else if (!strncmp (s, "-quiet", L)) verbose_p = FALSE;
+ else if (!strcmp (s, "-verbose")) verbose_p = TRUE;
+ else if (!strcmp (s, "-v")) verbose_p = TRUE;
+ else if (!strcmp (s, "-vv")) verbose_p = 2;
+ else if (!strcmp (s, "-vvv")) verbose_p = 3;
+ else if (!strcmp (s, "-vvvv")) verbose_p = 4;
+ else if (cmd) USAGE("extraneous", argv[i]);
else if (!strncmp (s, "-activate", L)) cmd = &XA_ACTIVATE;
else if (!strncmp (s, "-deactivate", L)) cmd = &XA_DEACTIVATE;
else if (!strncmp (s, "-suspend", L)) cmd = &XA_SUSPEND;
@@ -203,12 +189,16 @@ main (int argc, char **argv)
else if (!strncmp (s, "-preferences",L)) cmd = &XA_PREFS;
else if (!strncmp (s, "-prefs",L)) cmd = &XA_PREFS;
else if (!strncmp (s, "-lock", L)) cmd = &XA_LOCK;
- else if (!strncmp (s, "-throttle", L)) cmd = &XA_THROTTLE;
- else if (!strncmp (s, "-unthrottle", L)) cmd = &XA_UNTHROTTLE;
else if (!strncmp (s, "-version", L)) cmd = &XA_SCREENSAVER_VERSION;
else if (!strncmp (s, "-time", L)) cmd = &XA_SCREENSAVER_STATUS;
else if (!strncmp (s, "-watch", L)) cmd = &XA_WATCH;
- else USAGE ();
+ else if (!strncmp (s, "-help", L))
+ {
+ fprintf (stderr, usage, progname, screensaver_version, year);
+ exit (1);
+ }
+ else USAGE ("unrecognized", argv[i]);
+ cmdstr = argv[i];
if (cmd == &XA_SELECT || cmd == &XA_DEMO)
{
@@ -216,6 +206,7 @@ main (int argc, char **argv)
char c;
if (i+1 < argc && (1 == sscanf(argv[i+1], " %ld %c", &a, &c)))
{
+ cmdstr = argv[i+1];
arg = a;
i++;
}
@@ -223,39 +214,34 @@ main (int argc, char **argv)
}
if (!cmd)
- USAGE ();
+ USAGE("no commands", "");
if (arg < 0)
/* no command may have a negative argument. */
- USAGE();
+ USAGE("bad option", cmdstr);
else if (arg == 0)
{
/* SELECT must have a non-zero argument. */
if (cmd == &XA_SELECT)
- USAGE();
+ USAGE("bad option", cmdstr);
}
else /* arg > 0 */
{
/* no command other than SELECT and DEMO may have a non-zero argument. */
if (cmd != &XA_DEMO && cmd != &XA_SELECT)
- USAGE();
+ USAGE("bad option", cmdstr);
}
- /* For backward compatibility: -demo with no arguments used to send a
- "DEMO 0" ClientMessage to the xscreensaver process, which brought up
- the built-in demo mode dialog. Now that the demo mode dialog is no
- longer built in, we bring it up by just running the "xscreensaver-demo"
- program.
-
- Note that "-DEMO <n>" still sends a ClientMessage.
+ /* -demo with no arguments launches xscreensaver-settings.
+ -demo N sends a message to the daemon demo that hack number.
*/
if (cmd == &XA_PREFS ||
(cmd == &XA_DEMO && arg == 0))
{
char buf [512];
- char *new_argv[] = { "xscreensaver-demo", 0, 0, 0, 0, 0 };
+ char *new_argv[] = { "xscreensaver-settings", 0, 0, 0, 0, 0 };
int ac = 1;
if (dpyname)
@@ -286,7 +272,7 @@ main (int argc, char **argv)
{
dpyname = ":0.0";
fprintf (stderr,
- "%s: warning: $DISPLAY is not set: defaulting to \"%s\".\n",
+ "%s: warning: $DISPLAY is not set: defaulting to \"%s\"\n",
progname, dpyname);
}
@@ -298,29 +284,7 @@ main (int argc, char **argv)
exit (1);
}
- XA_VROOT = XInternAtom (dpy, "__SWM_VROOT", False);
- XA_SCREENSAVER = XInternAtom (dpy, "SCREENSAVER", False);
- XA_SCREENSAVER_ID = XInternAtom (dpy, "_SCREENSAVER_ID", False);
- XA_SCREENSAVER_VERSION = XInternAtom (dpy, "_SCREENSAVER_VERSION",False);
- XA_SCREENSAVER_STATUS = XInternAtom (dpy, "_SCREENSAVER_STATUS", False);
- XA_SCREENSAVER_RESPONSE = XInternAtom (dpy, "_SCREENSAVER_RESPONSE", False);
- XA_ACTIVATE = XInternAtom (dpy, "ACTIVATE", False);
- XA_DEACTIVATE = XInternAtom (dpy, "DEACTIVATE", False);
- XA_SUSPEND = XInternAtom (dpy, "SUSPEND", False);
- XA_RESTART = XInternAtom (dpy, "RESTART", False);
- XA_CYCLE = XInternAtom (dpy, "CYCLE", False);
- XA_NEXT = XInternAtom (dpy, "NEXT", False);
- XA_PREV = XInternAtom (dpy, "PREV", False);
- XA_SELECT = XInternAtom (dpy, "SELECT", False);
- XA_EXIT = XInternAtom (dpy, "EXIT", False);
- XA_DEMO = XInternAtom (dpy, "DEMO", False);
- XA_PREFS = XInternAtom (dpy, "PREFS", False);
- XA_LOCK = XInternAtom (dpy, "LOCK", False);
- XA_BLANK = XInternAtom (dpy, "BLANK", False);
- XA_THROTTLE = XInternAtom (dpy, "THROTTLE", False);
- XA_UNTHROTTLE = XInternAtom (dpy, "UNTHROTTLE", False);
-
- XSync (dpy, 0);
+ init_xscreensaver_atoms (dpy);
if (cmd == &XA_WATCH)
{
@@ -338,7 +302,7 @@ main (int argc, char **argv)
if (isatty(0))
sleep (1);
- i = xscreensaver_command (dpy, *cmd, arg, True, NULL);
+ i = xscreensaver_command (dpy, *cmd, arg, verbose_p, NULL);
if (i < 0) exit (i);
else exit (0);
}
@@ -390,7 +354,7 @@ watch (Display *dpy)
STATUS_LOSE:
if (last) XFree (last);
if (data) XFree (data);
- fprintf (stderr, "%s: bad status format on root window.\n",
+ fprintf (stderr, "%s: bad status format on root window\n",
progname);
return -1;
}
@@ -448,7 +412,7 @@ watch (Display *dpy)
{
if (last) XFree (last);
if (dataP) XFree (dataP);
- fprintf (stderr, "%s: no saver status on root window.\n",
+ fprintf (stderr, "%s: no saver status on root window\n",
progname);
return -1;
}
diff --git a/driver/xscreensaver-command.man b/driver/xscreensaver-command.man
index 5837e5d..6d52d2a 100644
--- a/driver/xscreensaver-command.man
+++ b/driver/xscreensaver-command.man
@@ -1,135 +1,102 @@
-.de EX \"Begin example
-.ne 5
-.if n .sp 1
-.if t .sp .5
-.nf
-.in +.5i
-..
-.de EE
-.fi
-.in -.5i
-.if n .sp 1
-.if t .sp .5
-..
-.TH XScreenSaver 1 "03-Jun-2019 (5.42)" "X Version 11"
+.TH XScreenSaver 1 "6-Jan-2021 (6.00)" "X Version 11"
.SH NAME
xscreensaver-command - control a running xscreensaver process
.SH SYNOPSIS
.B xscreensaver-command
-[\-display \fIhost:display.screen\fP] \
-[\-help | \
-\-demo | \
-\-prefs | \
-\-activate | \
-\-deactivate | \
-\-cycle | \
-\-next | \
-\-prev | \
-\-select \fIn\fP | \
-\-exit | \
-\-restart | \
-\-lock | \
-\-suspend | \
-\-version | \
-\-time | \
-\-watch]
+[\-\-display \fIhost:display.screen\fP] \
+[\-\-help | \
+\-\-quiet | \
+\-\-verbose | \
+\-\-activate | \
+\-\-deactivate | \
+\-\-cycle | \
+\-\-next | \
+\-\-prev | \
+\-\-select \fIn\fP | \
+\-\-lock | \
+\-\-suspend | \
+\-\-exit | \
+\-\-restart | \
+\-\-time | \
+\-\-watch | \
+\-\-version]
.SH DESCRIPTION
-The \fIxscreensaver\-command\fP program controls a running \fIxscreensaver\fP
-process by sending it client-messages.
-
+The \fIxscreensaver\-command\fP program controls a running
.BR xscreensaver (1)
-has a client-server model: the xscreensaver process is a
-daemon that runs in the background; it is controlled by other
-foreground programs such as \fIxscreensaver-command\fP and
-.BR xscreensaver\-demo (1).
+daemon.
-This program, \fIxscreensaver-command\fP, is a command-line-oriented tool; the
-.BR xscreensaver\-demo (1).
-program is a graphical tool.
.SH OPTIONS
.I xscreensaver-command
accepts the following command-line options:
+
.TP 8
-.B \-help
+.B \-\-help
Prints a brief summary of command-line options.
+
.TP 8
-.B \-demo
-This just launches the
-.BR xscreensaver\-demo (1)
-program, in which one can experiment with the various graphics hacks
-available, and edit parameters.
-.TP 8
-.B \-demo \fP\fInumber\fP
-When the \fI\-demo\fP option is followed by an integer, it instructs
-the \fIxscreensaver\fP daemon to run that hack, and wait for the user
-to click the mouse before deactivating (i.e., mouse motion does not
-deactivate.) This is the mechanism by which
-.BR xscreensaver\-demo (1)
-communicates with the
-.BR xscreensaver (1)
-daemon. (The first hack in the list is numbered 1, not 0.)
+.B \-\-quiet
+Only print output if an error occurs.
+
.TP 8
-.B \-prefs
-Like the no-argument form of \fI\-demo\fP, but brings up that program's
-Preferences panel by default.
+.B \-\-verbose
+Opposite of \-\-quiet. Default.
+
.TP 8
-.B \-activate
+.B \-\-activate
Tell xscreensaver to turn on immediately (that is, blank the screen, as if
the user had been idle for long enough.) The screensaver will deactivate as
soon as there is any user activity, as usual.
It is useful to run this from a menu; you may wish to run it as
-.EX
-sleep 5 ; xscreensaver-command -activate
-.EE
+.nf
+.sp
+ sleep 5 ; xscreensaver-command -activate
+.sp
+.fi
to be sure that you have time to take your hand off the mouse before
the screensaver comes on. (Because if you jiggle the mouse, xscreensaver
will notice, and deactivate.)
+
.TP 8
-.B \-deactivate
+.B \-\-deactivate
This tells xscreensaver to pretend that there has just been user activity.
This means that if the screensaver is active (the screen is blanked),
then this command will cause the screen to un-blank as if there had been
keyboard or mouse activity. If the screen is locked, then the password
dialog will pop up first, as usual. If the screen is not blanked, then
this simulated user activity will re-start the countdown (so, issuing
-the \fI\-deactivate\fP command periodically is \fIone\fP way to prevent
+the \fI\-\-deactivate\fP command periodically is \fIone\fP way to prevent
the screen from blanking.)
+
.TP 8
-.B \-cycle
+.B \-\-cycle
If the screensaver is active (the screen is blanked), then stop the current
graphics demo and run a new one (chosen randomly.)
+
.TP 8
-.B \-next
-This is like either \fI\-activate\fP or \fI\-cycle\fP, depending on which is
-more appropriate, except that the graphics hack that will be run is the next
-one in the list, instead of a randomly-chosen one. In other words,
+.B \-\-next
+This is like either \fI\-\-activate\fP or \fI\-\-cycle\fP, depending on which
+is more appropriate, except that the graphics hack that will be run is the
+next one in the list, instead of a randomly-chosen one. In other words,
repeatedly executing -next will cause the xscreensaver process to invoke each
-graphics demo sequentially. (Though using the \fI\-demo\fP option is probably
-an easier way to accomplish that.)
+graphics demo sequentially. (Though using the \fI\-\-settings\fP option is
+probably an easier way to accomplish that.)
+
.TP 8
-.B \-prev
-This is like \fI\-next\fP, but cycles in the other direction.
+.B \-\-prev
+This is like \fI\-\-next\fP, but cycles in the other direction.
+
.TP 8
-.B \-select \fInumber\fP
-Like \fI\-activate\fP, but runs the \fIN\fPth element in the list of hacks.
+.B \-\-select \fInumber\fP
+Like \fI\-\-activate\fP, but runs the \fIN\fPth element in the list of hacks.
By knowing what is in the \fIprograms\fP list, and in what order, you can use
this to activate the screensaver with a particular graphics demo. (The first
element in the list is numbered 1, not 0.)
-.TP 8
-.B \-exit
-Causes the xscreensaver process to exit gracefully.
-This does nothing if the display is currently locked.
-.B Warning:
-never use \fIkill -9\fP with \fIxscreensaver\fP while the screensaver is
-active. If you are using a virtual root window manager, that can leave
-things in an inconsistent state, and you may need to restart your window
-manager to repair the damage.
.TP 8
-.B \-lock
+.B \-\-lock
Tells the running xscreensaver process to lock the screen immediately.
-This is like \fI\-activate\fP, but forces locking as well, even if locking
+This is like \fI\-\-activate\fP, but forces locking as well, even if locking
is not the default (that is, even if xscreensaver's \fIlock\fP resource is
false, and even if the \fIlockTimeout\fP resource is non-zero.)
@@ -137,78 +104,99 @@ Note that locking doesn't work unless the \fIxscreensaver\fP process is
running as you. See
.BR xscreensaver (1)
for details.
+
.TP 8
-.B \-suspend
-Like \fI\-activate\fP, but ignores \fIlockTimeout\fP and immediately
+.B \-\-suspend
+Like \fI\-\-activate\fP, but ignores \fIlockTimeout\fP and immediately
powers off the screen without fading out. This is intended to be run
just after your laptop's lid is closed, and just before the CPU halts,
to lock things down quickly.
+
.TP 8
-.B \-version
-Prints the version of xscreensaver that is currently running on the display:
-that is, the actual version number of the running xscreensaver background
-process, rather than the version number of xscreensaver-command. (To see
-the version number of \fIxscreensaver-command\fP itself, use
-the \fI\-help\fP option.)
-.TP 8
-.B \-time
-Prints the time at which the screensaver last activated or
-deactivated (roughly, how long the user has been idle or non-idle: but
-not quite, since it only tells you when the screen became blanked or
-un-blanked.)
+.B \-\-exit
+Causes the xscreensaver process to exit gracefully.
+This does nothing if the display is currently locked.
+
+.B Warning:
+never use \fIkill -9\fP with \fIxscreensaver\fP while the screensaver is
+active. If you are using a virtual root window manager, that can leave
+things in an inconsistent state, and you may need to restart your window
+manager to repair the damage.
+
.TP 8
-.B \-restart
+.B \-\-restart
Causes the screensaver process to exit and then restart with the same command
line arguments as last time. You shouldn't really need to do this,
since xscreensaver notices when the \fI.xscreensaver\fP file has
changed and re-reads it as needed.
+
+.TP 8
+.B \-\-time
+Prints the time at which the screensaver last activated or
+deactivated (roughly, how long the user has been idle or non-idle: but
+not quite, since it only tells you when the screen became blanked or
+un-blanked.)
+
.TP 8
-.B \-watch
+.B \-\-watch
Prints a line each time the screensaver changes state: when the screen
blanks, locks, unblanks, or when the running hack is changed. This option
never returns; it is intended for use by shell scripts that want to react to
the screensaver in some way. An example of its output would be:
-.EX
-BLANK Fri Nov 5 01:57:22 1999
-RUN 34
-RUN 79
-RUN 16
-LOCK Fri Nov 5 01:57:22 1999
-RUN 76
-RUN 12
-UNBLANK Fri Nov 5 02:05:59 1999
-.EE
+.nf
+.sp
+ BLANK Fri Nov 5 01:57:22 1999
+ RUN 34
+ RUN 79
+ RUN 16
+ LOCK Fri Nov 5 01:57:22 1999
+ RUN 76
+ RUN 12
+ UNBLANK Fri Nov 5 02:05:59 1999
+.sp
+.fi
The above shows the screensaver activating, running three different
hacks, then locking (perhaps because the lock-timeout went off) then
unblanking (because the user became active, and typed the correct
password.) The hack numbers are their index in the `programs'
-list (starting with 1, not 0, as for the \fI\-select\fP command.)
+list (starting with 1, not 0, as for the \fI\-\-select\fP command.)
For example, suppose you want to run a program that turns down the volume
on your machine when the screen blanks, and turns it back up when the screen
un-blanks. You could do that by running a Perl program like the following
in the background. The following program tracks the output of
-the \fI\-watch\fP command and reacts accordingly:
-.EX
-#!/usr/bin/perl
+the \fI\-\-watch\fP command and reacts accordingly:
+.nf
+.sp
+ #!/usr/bin/perl
-my $blanked = 0;
-open (IN, "xscreensaver-command -watch |");
-while (<IN>) {
- if (m/^(BLANK|LOCK)/) {
- if (!$blanked) {
- system "sound-off";
- $blanked = 1;
- }
- } elsif (m/^UNBLANK/) {
- system "sound-on";
- $blanked = 0;
- }
-}
-.EE
+ my $blanked = 0;
+ open (my $in, "xscreensaver-command -watch |") || die;
+ while (<$in>) {
+ if (m/^(BLANK|LOCK)/) {
+ if (!$blanked) {
+ system ("sound-off");
+ $blanked = 1;
+ }
+ } elsif (m/^UNBLANK/) {
+ system ("sound-on");
+ $blanked = 0;
+ }
+ }
+.sp
+.fi
Note that LOCK might come either with or without a preceding BLANK
(depending on whether the lock-timeout is non-zero), so the above program
keeps track of both of them.
+
+.TP 8
+.B \-\-version
+Prints the version of xscreensaver that is currently running on the display:
+that is, the actual version number of the running xscreensaver background
+process, rather than the version number of xscreensaver-command. (To see
+the version number of \fIxscreensaver-command\fP itself, use
+the \fI\-\-help\fP option.)
+
.SH STOPPING GRAPHICS
If xscreensaver is running, but you want it to stop running screen hacks
(e.g., if you are logged in remotely, and you want the console to remain
@@ -216,15 +204,17 @@ locked but just be black, with no graphics processes running) you can
accomplish that by simply powering down the monitor remotely. In a
minute or so, xscreensaver will notice that the monitor is off, and
will stop running screen hacks. You can power off the monitor like so:
-.EX
-xset dpms force off
-.EE
+.nf
+.sp
+ xset dpms force off
+.sp
+.fi
See the
.BR xset (1)
manual for more info.
You can also use
-.BR xscreensaver-demo (1)
+.BR xscreensaver\-settings (1)
to make the monitor power down after a few hours, meaning that xscreensaver
will run graphics until it has been idle for the length of time you
specified; and after that, the monitor will power off, and screen hacks
@@ -243,7 +233,7 @@ to get the host and display number of the screen whose saver is
to be manipulated.
.TP 8
.B PATH
-to find the executable to restart (for the \fI\-restart\fP command).
+to find the executable to restart (for the \fI\-\-restart\fP command).
Note that this variable is consulted in the environment of
the \fIxscreensaver\fP process, not the \fIxscreensaver-command\fP process.
.SH UPGRADES
@@ -253,10 +243,10 @@ and related tools can always be found at https://www.jwz.org/xscreensaver/
.SH "SEE ALSO"
.BR X (1),
.BR xscreensaver (1),
-.BR xscreensaver\-demo (1),
+.BR xscreensaver\-settings (1),
.BR xset (1)
.SH COPYRIGHT
-Copyright \(co 1992-2019 by Jamie Zawinski.
+Copyright \(co 1992-2021 by Jamie Zawinski.
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
diff --git a/driver/xscreensaver-gfx.c b/driver/xscreensaver-gfx.c
new file mode 100644
index 0000000..5a543a4
--- /dev/null
+++ b/driver/xscreensaver-gfx.c
@@ -0,0 +1,595 @@
+/* xscreensaver, Copyright © 1991-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.
+ *
+ * XScreenSaver Daemon, version 6.
+ *
+ * This is "xscreensaver-gfx" -- When the time comes for the screen to blank,
+ * this process is launched by "xscreensaver" to fade out, black out every
+ * monitor on the system, launch graphics demos to render on those blanked
+ * screens, and cycle them from time to time. See the comment atop
+ * xscreensaver.c for details of the division of powers.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdio.h>
+#include <ctype.h>
+#include <X11/Xlib.h>
+#include <X11/Xlibint.h>
+#include <X11/Xatom.h>
+#include <X11/Intrinsic.h>
+#include <X11/StringDefs.h>
+#include <X11/Shell.h>
+#include <X11/Xos.h>
+#include <time.h>
+#include <sys/time.h>
+#include <netdb.h> /* for gethostbyname() */
+#include <sys/types.h>
+#include <pwd.h>
+
+#include <X11/extensions/XInput2.h>
+
+#ifdef HAVE_RANDR
+# include <X11/extensions/Xrandr.h>
+#endif /* HAVE_RANDR */
+
+#ifdef ENABLE_NLS
+# include <locale.h>
+# include <libintl.h>
+#endif
+
+#include "xscreensaver.h"
+#include "version.h"
+#include "atoms.h"
+#include "yarandom.h"
+#include "resources.h"
+#include "visual.h"
+#include "screens.h"
+#include "clientmsg.h"
+#include "xmu.h"
+
+saver_info *global_si_kludge = 0; /* I hate C so much... */
+
+char *progclass = 0;
+XrmDatabase db = 0;
+Bool debug_p = False;
+
+
+#ifdef __GNUC__
+ __extension__ /* shut up about "string length is greater than the length
+ ISO C89 compilers are required to support" when including
+ the .ad file... */
+#endif
+
+static char *defaults[] = {
+#include "XScreenSaver_ad.h"
+ 0
+};
+
+#ifdef _VROOT_H_
+ERROR! You must not include vroot.h in this file.
+#endif
+
+
+void
+maybe_reload_init_file (saver_info *si)
+{
+ saver_preferences *p = &si->prefs;
+ if (init_file_changed_p (p))
+ {
+ if (p->verbose_p)
+ fprintf (stderr, "%s: file \"%s\" has changed, reloading\n",
+ blurb(), init_file_name());
+
+ load_init_file (si->dpy, p);
+
+ /* If the DPMS settings in the init file have changed, change the
+ settings on the server to match. This also would have happened at
+ the watchdog timer. */
+ sync_server_dpms_settings (si->dpy, p);
+ }
+}
+
+
+static int
+saver_ehandler (Display *dpy, XErrorEvent *error)
+{
+ saver_info *si = global_si_kludge; /* I hate C so much... */
+ int i;
+
+ fprintf (stderr, "\n"
+ "#######################################"
+ "#######################################\n\n"
+ "%s: X Error! PLEASE REPORT THIS BUG.\n",
+ blurb());
+
+ for (i = 0; i < si->nscreens; i++)
+ {
+ saver_screen_info *ssi = &si->screens[i];
+ fprintf (stderr, "%s: screen %d/%d: 0x%x, 0x%x\n",
+ blurb(), ssi->real_screen_number, ssi->number,
+ (unsigned int) RootWindowOfScreen (si->screens[i].screen),
+ (unsigned int) si->screens[i].screensaver_window);
+ }
+
+ fprintf (stderr, "\n"
+ "#######################################"
+ "#######################################\n\n");
+
+ XmuPrintDefaultErrorMessage (dpy, error, stderr);
+ exit (1);
+}
+
+
+static void
+connect_to_server (saver_info *si)
+{
+ Widget toplevel_shell;
+ Window daemon_window;
+ XrmOptionDescRec options;
+ char *p;
+ int ac = 1;
+ char *av[] = { "xscreensaver" }; /* For Xt and Xrm purposes */
+
+ XSetErrorHandler (saver_ehandler);
+
+ toplevel_shell = XtAppInitialize (&si->app, progclass, &options, 0,
+ &ac, av, defaults, 0, 0);
+
+ si->dpy = XtDisplay (toplevel_shell);
+ si->prefs.db = XtDatabase (si->dpy);
+ XtGetApplicationNameAndClass (si->dpy, &p, &progclass);
+
+ db = si->prefs.db; /* resources.c needs this */
+
+ init_xscreensaver_atoms (si->dpy);
+
+ /* Select property changes on the window created by our parent xscreensaver
+ process: this is where ClientMessage events are sent, and response
+ properties are written.
+ */
+ daemon_window = find_screensaver_window (si->dpy, 0);
+ if (daemon_window)
+ {
+ XWindowAttributes xgwa;
+ XGetWindowAttributes (si->dpy, daemon_window, &xgwa);
+ XSelectInput (si->dpy, daemon_window,
+ xgwa.your_event_mask | PropertyChangeMask);
+ }
+ else
+ {
+ fprintf (stderr, "%s: xscreensaver does not seem to be running!\n",
+ blurb());
+ }
+}
+
+
+static void
+initialize_randr (saver_info *si)
+{
+ saver_preferences *p = &si->prefs;
+
+#ifdef HAVE_RANDR
+ if (XRRQueryExtension (si->dpy,
+ &si->randr_event_number, &si->randr_error_number))
+ {
+ int nscreens = ScreenCount (si->dpy); /* number of *real* screens */
+ int i;
+
+ si->using_randr_extension = TRUE;
+
+ if (p->verbose_p)
+ fprintf (stderr, "%s: selecting RANDR events\n", blurb());
+ for (i = 0; i < nscreens; i++)
+# ifdef RRScreenChangeNotifyMask /* randr.h 1.5, 2002/09/29 */
+ XRRSelectInput (si->dpy, RootWindow (si->dpy, i),
+ RRScreenChangeNotifyMask);
+# else /* !RRScreenChangeNotifyMask */ /* Xrandr.h 1.4, 2001/06/07 */
+ XRRScreenChangeSelectInput (si->dpy, RootWindow (si->dpy, i), True);
+# endif /* !RRScreenChangeNotifyMask */
+ }
+# endif /* HAVE_RANDR */
+}
+
+
+/* Read the XA_SCREENSAVER_STATUS propery from the root window of screen 0
+ to see what hacks were selected the last time we ran, so that -next and
+ -prev can work.
+ */
+static void
+read_status_prop (saver_info *si)
+{
+ Window w = RootWindow (si->dpy, 0); /* always screen 0 */
+ Atom type;
+ unsigned char *dataP = 0;
+ int format, i;
+ unsigned long nitems, bytesafter;
+
+ for (i = 0; i < si->nscreens; i++)
+ {
+ saver_screen_info *ssi = &si->screens[i];
+ ssi->current_hack = -1;
+ }
+
+ XGetWindowProperty (si->dpy, w, XA_SCREENSAVER_STATUS,
+ 0, 999, False, XA_INTEGER, &type, &format, &nitems,
+ &bytesafter, &dataP);
+ if (dataP && type == XA_INTEGER && nitems >= 3)
+ {
+ for (i = 2; i < nitems; i++)
+ {
+ int j = i - 2;
+ if (j < si->nscreens)
+ {
+ saver_screen_info *ssi = &si->screens[j];
+ int n = ((PROP32 *) dataP)[i];
+ ssi->current_hack = n-1; /* 1-based */
+ }
+ }
+ }
+ if (dataP)
+ XFree (dataP);
+}
+
+
+/* Processing ClientMessage events.
+ Both xscreensaver and xscreensaver-gfx handle these; some are handled
+ exclusively by one program or another, and a couple (next, prev) are
+ handled by xscreensaver only if xscreensaver-gfx is not already running.
+ */
+static void
+handle_clientmessage (saver_info *si, XEvent *xev)
+{
+ Display *dpy = si->dpy;
+ saver_preferences *p = &si->prefs;
+ Atom type = 0;
+
+ if (xev->xclient.message_type != XA_SCREENSAVER ||
+ xev->xclient.format != 32)
+ return;
+
+ /* Preferences might affect our handling of client messages. */
+ maybe_reload_init_file (si);
+
+ type = xev->xclient.data.l[0];
+
+ if (type == XA_CYCLE)
+ {
+ int i;
+ clientmessage_response (dpy, xev, True, "cycling");
+ si->selection_mode = 0; /* 0 means randomize when its time. */
+ CYCLE:
+ si->demoing_p = False;
+ monitor_power_on (si, True);
+
+ for (i = 0; i < si->nscreens; i++)
+ {
+ saver_screen_info *ssi = &si->screens[i];
+ cycle_timer ((XtPointer) ssi, 0);
+ }
+ }
+ else if (type == XA_NEXT || type == XA_PREV)
+ {
+ /* If xscreensaver-gfx was not running, these were handled by
+ xscreensaver. If xscreensaver-gfx is running, then xscreensaver
+ ignored these and we reply instead.
+ */
+ clientmessage_response (dpy, xev, True, "cycling");
+ si->selection_mode = (type == XA_NEXT ? -1 : -2);
+ goto CYCLE;
+ }
+ else if (type == XA_SELECT)
+ {
+ /* Same xscreensaver/xscreensaver-gfx division of labor as next/prev. */
+ long which = xev->xclient.data.l[1];
+
+ if (p->mode == DONT_BLANK)
+ {
+ clientmessage_response (dpy, xev, False, "blanking disabled");
+ return;
+ }
+
+ clientmessage_response (dpy, xev, True, "selecting");
+
+ if (which < 0) which = 0; /* 0 == "random" */
+ si->selection_mode = which;
+ goto CYCLE;
+ }
+ else
+ {
+ /* All other ClientMessages are handled by xscreensaver rather than
+ xscreensaver-gfx, and it will return an error message for unknown
+ ones. */
+ }
+}
+
+
+#ifdef DEBUG_MULTISCREEN
+/* For monitor hot-swap stress-testing. See screens.c. */
+static void
+debug_multiscreen_timer (XtPointer closure, XtIntervalId *id)
+{
+ saver_info *si = (saver_info *) closure;
+ saver_preferences *p = &si->prefs;
+ if (update_screen_layout (si))
+ {
+ if (p->verbose_p)
+ {
+ fprintf (stderr, "%s: new layout:\n", blurb());
+ describe_monitor_layout (si->monitor_layout);
+ }
+ resize_screensaver_window (si);
+ }
+ XtAppAddTimeOut (si->app, 1000*4, debug_multiscreen_timer, (XtPointer) si);
+}
+#endif /* DEBUG_MULTISCREEN */
+
+
+static void
+main_loop (saver_info *si, Bool init_p)
+{
+ saver_preferences *p = &si->prefs;
+
+ if (init_p && p->verbose_p)
+ print_available_extensions (si);
+
+ initialize_randr (si);
+ update_screen_layout (si);
+ describe_monitor_layout (si->monitor_layout);
+
+ initialize_screensaver_window (si);
+ init_sigchld (si);
+ read_status_prop (si);
+
+ if (! p->verbose_p)
+ ;
+ else if (si->demoing_p)
+ fprintf (stderr, "%s: demoing %d\n", blurb(), si->selection_mode);
+ else
+ fprintf (stderr, "%s: blanking\n", blurb());
+
+ blank_screen (si);
+
+# ifdef DEBUG_MULTISCREEN
+ if (p->debug_p)
+ debug_multiscreen_timer ((XtPointer) si, 0);
+# endif
+
+ while (1)
+ {
+ /* We have to go through this union bullshit because gcc-4.4.0 has
+ stricter struct-aliasing rules. Without this, the optimizer
+ can fuck things up. */
+ union {
+ XEvent x_event;
+# ifdef HAVE_RANDR
+ XRRScreenChangeNotifyEvent xrr_event;
+# endif /* HAVE_RANDR */
+ } event;
+
+ /* Block waiting for the next X event, while running timers. */
+ XtAppNextEvent (si->app, &event.x_event);
+
+ if (event.x_event.xany.type == ClientMessage)
+ handle_clientmessage (si, &event.x_event);
+# ifdef HAVE_RANDR
+ else if (si->using_randr_extension &&
+ (event.x_event.type ==
+ (si->randr_event_number + RRScreenChangeNotify)))
+ {
+ /* The Resize and Rotate extension sends an event when the
+ size, rotation, or refresh rate of any screen has changed.
+ */
+ if (p->verbose_p)
+ {
+ int screen = XRRRootToScreen (si->dpy, event.xrr_event.window);
+ fprintf (stderr, "%s: %d: screen change event received\n",
+ blurb(), screen);
+ }
+
+ /* Inform Xlib that it's ok to update its data structures. */
+ XRRUpdateConfiguration (&event.x_event);
+
+ /* Resize the existing xscreensaver windows and cached ssi data. */
+ if (update_screen_layout (si))
+ {
+ if (p->verbose_p)
+ {
+ fprintf (stderr, "%s: new layout:\n", blurb());
+ describe_monitor_layout (si->monitor_layout);
+ }
+ resize_screensaver_window (si);
+ }
+ }
+# endif /* HAVE_RANDR */
+
+ XtDispatchEvent (&event.x_event);
+ }
+}
+
+
+int
+main (int argc, char **argv)
+{
+ char *dpy_str = getenv ("DISPLAY");
+ char *s;
+ saver_info the_si;
+ saver_info *si = &the_si;
+ saver_preferences *p = &si->prefs;
+ Bool init_p = False;
+ Bool cmdline_verbose_val = False, cmdline_verbose_p = False;
+ int i;
+
+ memset(si, 0, sizeof(*si));
+ global_si_kludge = si; /* I hate C so much... */
+
+# undef ya_rand_init
+ ya_rand_init (0);
+
+ /* For Xt and X resource database purposes, this program is
+ "xscreensaver", not "xscreensaver-gfx".
+ */
+ s = strrchr(argv[0], '/');
+ if (s) s++;
+ else s = argv[0];
+ if (strlen(s) > 20) /* keep it short. */
+ s[20] = 0;
+ progname = s;
+
+ progclass = "XScreenSaver";
+
+ si->version = strdup (screensaver_id + 17);
+ s = strchr (si->version, ' ');
+ *s = 0;
+
+ for (i = 1; i < argc; i++)
+ {
+ const char *oa = argv[i];
+ /* XScreenSaver predates the "--arg" convention. */
+ if (argv[i][0] == '-' && argv[i][1] == '-')
+ argv[i]++;
+
+ if (!strcmp (argv[i], "-debug"))
+ p->debug_p = True;
+ else if (!strcmp (argv[i], "-v") || !strcmp (argv[i], "-verbose"))
+ {
+ p->verbose_p++;
+ cmdline_verbose_p = True, cmdline_verbose_val = p->verbose_p;
+ }
+ else if (!strcmp (argv[i], "-vv"))
+ {
+ p->verbose_p += 2;
+ cmdline_verbose_p = True, cmdline_verbose_val = p->verbose_p;
+ }
+ else if (!strcmp (argv[i], "-vvv"))
+ {
+ p->verbose_p += 3;
+ cmdline_verbose_p = True, cmdline_verbose_val = p->verbose_p;
+ }
+ else if (!strcmp (argv[i], "-vvvv"))
+ {
+ p->verbose_p += 4;
+ cmdline_verbose_p = True, cmdline_verbose_val = p->verbose_p;
+ }
+ else if (!strcmp (argv[i], "-q") || !strcmp (argv[i], "-quiet"))
+ {
+ p->verbose_p = False;
+ cmdline_verbose_p = True, cmdline_verbose_val = p->verbose_p;
+ }
+ else if (!strcmp (argv[i], "-init"))
+ init_p = True;
+ else if (!strcmp (argv[i], "-d") ||
+ !strcmp (argv[i], "-dpy") ||
+ !strcmp (argv[i], "-disp") ||
+ !strcmp (argv[i], "-display"))
+ {
+ dpy_str = argv[++i];
+ if (!dpy_str) goto HELP;
+ }
+ else if (!strcmp (argv[i], "-sync") ||
+ !strcmp (argv[i], "-synch") ||
+ !strcmp (argv[i], "-synchronize") ||
+ !strcmp (argv[i], "-synchronise"))
+ p->xsync_p = True;
+ else if (!strcmp (argv[i], "-h") || !strcmp (argv[i], "-help"))
+ {
+ HELP:
+ fprintf (stderr,
+ "\n"
+ "\txscreensaver-gfx is launched by the xscreensaver daemon\n"
+ "\tto manage the graphical display modes as subprocesses.\n"
+ "\tDo not run this directly.\n"
+ "\n"
+ "\tOptions:\n"
+ "\t\t--dpy host:display.screen\n"
+ "\t\t--verbose --debug --sync\n"
+ "\t\t--init --emergency\n"
+ "\t\t--next --prev --select N --demo N\n"
+ "\n"
+ "\tRun 'xscreensaver-settings' to configure.\n"
+ "\n");
+ exit (1);
+ }
+ else if (!strcmp (argv[i], "-emergency"))
+ {
+ /* This means we should start up without a fade-out, blanking the
+ screen as quickly as possible. This is used when suspending,
+ and also if xscreensaver-gfx has crashed and is being restarted.
+ */
+ si->emergency_p = True;
+ }
+ else if (!strcmp (argv[i], "-next"))
+ {
+ /* "xscreensaver-command -next" sent a ClientMessage to the daemon
+ while it was idle, so we should start with this hack. */
+ si->selection_mode = -1; /* -1 means next */
+ }
+ else if (!strcmp (argv[i], "-prev"))
+ {
+ si->selection_mode = -2; /* -2 means prev */
+ }
+ else if (!strcmp (argv[i], "-select") ||
+ !strcmp (argv[i], "-demo"))
+ {
+ /* "xscreensaver-command -select N" sent a ClientMessage to the
+ daemon while it was idle, so we should start with this hack. */
+ int n;
+ char c;
+ if (!strcmp (argv[i], "-demo"))
+ si->demoing_p = True; /* Just means "don't auto-cycle" */
+
+ if (! argv[++i]) goto FAIL;
+ if (1 != sscanf (argv[i], " %d %c", &n, &c)) goto HELP;
+ if (n <= 0) goto FAIL;
+ si->selection_mode = n; /* hack number is 1-based */
+ }
+ else
+ {
+ FAIL:
+ fprintf (stderr, "\n%s: unknown option: %s\n", blurb(), oa);
+ goto HELP;
+ }
+ }
+
+ /* Copy the -dpy arg to $DISPLAY for subprocesses. */
+ s = (char *) malloc (strlen(dpy_str) + 20);
+ sprintf (s, "DISPLAY=%s", dpy_str);
+ putenv (s);
+ /* free (s); */ /* some versions of putenv do not copy */
+
+# ifdef ENABLE_NLS
+ {
+ bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+ textdomain (GETTEXT_PACKAGE);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ if (!setlocale (LC_ALL, ""))
+ fprintf (stderr, "%s: warning: could not set default locale\n",
+ progname);
+ }
+# endif /* ENABLE_NLS */
+
+ connect_to_server (si);
+
+ if (p->xsync_p)
+ XSynchronize (si->dpy, True);
+
+ load_init_file (si->dpy, p);
+
+ /* Command line overrides init file */
+ if (cmdline_verbose_p)
+ p->verbose_p = cmdline_verbose_val;
+ verbose_p = p->verbose_p;
+ debug_p = p->debug_p;
+
+ main_loop (si, init_p); /* doesn't return */
+ exit (-1);
+}
diff --git a/driver/xscreensaver-gfx.man b/driver/xscreensaver-gfx.man
new file mode 100644
index 0000000..7b209ac
--- /dev/null
+++ b/driver/xscreensaver-gfx.man
@@ -0,0 +1,28 @@
+.TH XScreenSaver 1 "6-Jan-2021 (6.00)" "X Version 11"
+.SH NAME
+xscreensaver - extensible screen saver and screen locking framework
+.SH SYNOPSIS
+.B xscreensaver-gfx
+[\-display \fIhost:display.screen\fP]
+.SH DESCRIPTION
+The
+.BR xscreensaver (1)
+daemon launches this program to blank the screen and render graphics.
+It is responsible for launching graphics demos as sub-processes.
+Do not run this program directly.
+.SH SEE ALSO
+.BR xscreensaver (1),
+.BR xscreensaver\-settings (1),
+.BR xscreensaver\-auth (MANSUFFIX),
+.BR xscreensaver\-systemd (MANSUFFIX).
+.SH COPYRIGHT
+Copyright \(co 2021 by Jamie Zawinski.
+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.
+.SH AUTHOR
+Jamie Zawinski <jwz@jwz.org>
diff --git a/driver/xscreensaver-settings.man b/driver/xscreensaver-settings.man
new file mode 100644
index 0000000..6b9657d
--- /dev/null
+++ b/driver/xscreensaver-settings.man
@@ -0,0 +1,420 @@
+.TH XScreenSaver 1 "6-Jan-2021 (6.00)" "X Version 11"
+.SH NAME
+xscreensaver-settings - configure and control the xscreensaver daemon
+.SH SYNOPSIS
+.B xscreensaver\-settings
+[\-display \fIhost:display.screen\fP]
+[\-prefs]
+[\-debug]
+.SH DESCRIPTION
+The \fIxscreensaver\-settings\fP program is a graphical front-end for
+setting the parameters used by the
+.BR xscreensaver (1)
+daemon. It is a tool for editing the \fI~/.xscreensaver\fP file, and for
+demoing the various display modes.
+
+The main window consists of a menu bar and two tabbed pages. The first page
+is for editing the list of demos, and the second is for editing various other
+parameters of the screensaver.
+.SH MENU COMMANDS
+All of these commands are on either the \fBFile\fP or \fBHelp\fP menus:
+
+.TP 4
+.B Blank Screen Now
+Activates the background \fIxscreensaver\fP daemon, which will then run
+a demo at random. This is the same as running
+.BR xscreensaver\-command (1)
+with the \fI\-activate\fP option.
+
+.TP 4
+.B Lock Screen Now
+Just like \fBBlank Screen Now\fP, except the screen will be locked as
+well (even if it is not configured to lock all the time.) This is the
+same as running
+.BR xscreensaver\-command (1)
+with the \fI\-lock\fP option.
+
+.TP 4
+.B Kill Daemon
+If the xscreensaver daemon is running on this screen, kill it.
+This is the same as running
+.BR xscreensaver\-command (1)
+with the \fI\-exit\fP option.
+
+.TP 4
+.B Restart Daemon
+If the xscreensaver daemon is running on this screen, kill it.
+Then launch it again. This is the same as doing
+"\fIxscreensaver-command --exit\fP" followed by "\fIxscreensaver\fP".
+
+Note that it is \fInot\fP the same as doing
+"\fIxscreensaver-command --restart\fP".
+
+.TP 4
+.B Exit
+Exits the \fIxscreensaver-settings\fP program (this program) without
+affecting the background \fIxscreensaver\fP daemon, if any.
+
+.TP 4
+.B About...
+Displays the version number of this program, \fIxscreensaver-settings\fP.
+
+.TP 4
+.B Documentation...
+Opens up a web browser looking at the XScreenSaver web page, where you
+can find online copies of the
+.BR xscreensaver (1),
+.BR xscreensaver\-settings (1),
+and
+.BR xscreensaver\-command (1)
+manuals.
+.SH DISPLAY MODES TAB
+This page contains a list of the names of the various display modes, a
+preview area, and some fields that let you configure screen saver behavior.
+
+.TP 4
+.B Mode
+This option menu controls the activation behavior of the screen saver.
+The options are:
+.RS 4
+
+.TP 4
+.B Disable Screen Saver
+Don't ever blank the screen, and don't ever allow the monitor to power down.
+
+.TP 4
+.B Blank Screen Only
+When blanking the screen, just go black: don't run any graphics.
+
+.TP 4
+.B Only One Screen Saver
+When blanking the screen, only ever use one particular display mode (the
+one selected in the list.)
+
+.TP 4
+.B Random Screen Saver
+When blanking the screen, select a random display mode from among those
+that are enabled and applicable. If there are multiple monitors
+connected, run a different display mode on each one. This is the default.
+
+.TP 4
+.B Random Same Saver
+This is just like \fBRandom Screen Saver\fP, except that the \fIsame\fP
+randomly-chosen display mode will be run on all monitors, instead of
+different ones on each.
+.RE
+
+.TP 4
+.B Demo List
+Double-clicking in the list on the left will let you try out the indicated
+demo. The screen will go black, and the program will run in full-screen
+mode, just as it would if the \fIxscreensaver\fP daemon had launched it.
+Clicking the mouse again will stop the demo and un-blank the screen.
+
+Single-clicking in the list will run it in the small preview pane on the
+right. (But beware: many of the display modes behave somewhat differently
+when running in full-screen mode, so the scaled-down view might not give
+an accurate impression.)
+
+When \fBMode\fP is set to \fBRandom Screen Saver\fP, each name in the list
+has a checkbox next to it: this controls whether this display mode is
+enabled. If it is unchecked, then that mode will not be chosen. (Though
+you can still run it explicitly by double-clicking on its name.)
+
+.TP 4
+.B Arrow Buttons
+Beneath the list are a pair of up and down arrows. Clicking on the down
+arrow will select the next item in the list, and then run it in full-screen
+mode, just as if you had double-clicked on it. The up arrow goes the other
+way. This is just a shortcut for trying out all of the display modes in turn.
+
+.TP 4
+.B Blank After
+After the user has been idle this long, the \fIxscreensaver\fP daemon
+will blank the screen.
+
+.TP 4
+.B Cycle After
+After the screensaver has been running for this long, the currently
+running graphics demo will be killed, and a new one started.
+If this is 0, then the graphics demo will never be changed:
+only one demo will run until the screensaver is deactivated by user
+activity.
+
+If there are multiple screens, the savers are staggered slightly so
+that while they all change every \fIcycle\fP minutes, they don't all
+change at the same time.
+
+.TP 4
+.B Lock Screen
+When this is checked, the screen will be locked when it activates.
+
+.TP 4
+.B Lock Screen After
+This controls the length of the "grace period" between when the
+screensaver activates, and when the screen becomes locked. For
+example, if this is 5 minutes, and \fIBlank After\fP is 10 minutes,
+then after 10 minutes, the screen would blank. If there was user
+activity at 12 minutes, no password would be required to un-blank the
+screen. But, if there was user activity at 15 minutes or later (that
+is, \fILock Screen After\fP minutes after activation) then a password
+would be required. The default is 0, meaning that if locking is
+enabled, then a password will be required as soon as the screen blanks.
+
+.TP 4
+.B Preview
+This button, below the small preview window, runs the demo in full-screen
+mode so that you can try it out. This is the same thing that happens when
+you double-click an element in the list. Click the mouse to dismiss the
+full-screen preview.
+
+.TP 4
+.B Settings
+This button will pop up a dialog where you can configure settings specific
+to the display mode selected in the list.
+
+.SH SETTINGS DIALOG
+When you click on the \fISettings\fP button on the \fIDisplay Modes\fP
+tab, a configuration dialog will pop up that lets you customize settings
+of the selected display mode. Each display mode has its own custom
+configuration controls on the left side.
+
+On the right side is a paragraph or two describing the display mode.
+Below that is a \fBDocumentation\fP button that will display the display
+mode's manual page in a new window.
+
+The \fBAdvanced\fP button reconfigures the dialog box so that you can
+edit the display mode's command line directly, instead of using the
+graphical controls.
+.SH ADVANCED TAB
+This tab lets you change various settings used by the xscreensaver daemon
+itself, as well as some global options shared by all of the display modes.
+
+.B Image Manipulation
+
+Some of the graphics hacks manipulate images. These settings control
+where those source images come from. The savers load images by running the
+.BR xscreensaver\-getimage (MANSUFFIX)
+and
+.BR xscreensaver\-getimage\-file (MANSUFFIX)
+programs.
+.RS 4
+
+.TP 4
+.B Grab Desktop Images
+If this option is selected, then savers are allowed to manipulate the
+desktop image, that is, a display mode might draw a picture of your
+desktop melting, or being distorted in some way. The
+security-paranoid might want to disable this option, because if it is
+set, it means that the windows on your desktop will occasionally be
+visible while your screen is locked. Others will not be able to
+\fIdo\fP anything, but they may be able to \fIsee\fP whatever you left
+on your screen.
+
+.TP 4
+.B Grab Video Frames
+If your system has a video capture device, selecting this option may allow
+the image-manipulating modes to grab a still-frame of video to operate on.
+
+.TP 4
+.B Choose Random Image
+If this option is set, then the image-manipulating modes will select a
+random image file to operate on, from the specified source. That
+source may be a local directory, which will be recursively searched
+for images. Or, it may be the URL of an RSS or Atom feed (e.g., a
+Flickr gallery), in which case a random image from that feed will be
+selected instead. The contents of the feed will be cached locally and
+refreshed periodically as needed.
+.PP
+If more than one of the above image-related options are selected, then
+one will be chosen at random. If none of them are selected, then an
+image of video colorbars will be used instead.
+.RE
+.PP
+.B Text Manipulation
+
+Some of the display modes display and manipulate text. The following
+options control how that text is generated. The savers load text by
+running the
+.BR xscreensaver\-text (MANSUFFIX)
+program.
+.RS 4
+
+.TP 4
+.B Host Name and Time
+If this checkbox is selected, then the text used by the screen savers
+will be the local host name, OS version, date, time, and system load.
+
+.TP 4
+.B Text
+If this checkbox is selected, then the literal text typed in the
+field to its right will be used. If it contains % escape sequences,
+they will be expanded as per
+.BR strftime (2).
+
+.TP 4
+.B Text File
+If this checkbox is selected, then the contents of the corresponding
+file will be displayed.
+
+.TP 4
+.B Program
+If this checkbox is selected, then the given program will be run,
+repeatedly, and its output will be displayed.
+
+.TP 4
+.B URL
+If this checkbox is selected, then the given web page will be downloaded
+and displayed repeatedly. If the document contains HTML, RSS, or Atom,
+it will be converted to plain-text first.
+
+Note: this re-downloads the document every time the screen saver
+runs out of text, so it will probably be hitting that web server multiple
+times a minute.
+.RE
+.PP
+.B Power Management Settings
+
+These settings control whether, and when, your monitor powers down.
+.RS 4
+
+.TP 4
+.B Power Management Enabled
+Whether the monitor should be powered down after a period of inactivity.
+
+If this option is grayed out, it means your X server does not support
+the XDPMS extension, and so control over the monitor's power state is
+not available.
+
+.TP 4
+.B Standby After
+If \fIPower Management Enabled\fP is selected, the monitor will go black
+after this much idle time. (Graphics demos will stop running, also.)
+
+.TP 4
+.B Suspend After
+If \fIPower Management Enabled\fP is selected, the monitor will go
+into power-saving mode after this much idle time. This duration should
+be greater than or equal to \fIStandby\fP.
+
+.TP 4
+.B Off After
+If \fIPower Management Enabled\fP is selected, the monitor will fully
+power down after this much idle time. This duration should be greater
+than or equal to \fISuspend\fP.
+
+.TP 4
+.B Quick Power-off in "Blank Only" Mode
+If the display mode is set to \fIBlank Screen Only\fP and this is
+checked, then the monitor will be powered off immediately upon
+blanking, regardless of the other power-management settings. In this
+way, the power management idle-timers can be completely disabled, but
+the screen will be powered off when black.
+.RE
+.PP
+.B Blanking
+
+These options control how the screen fades to or from black when
+a screen saver begins or ends. Note: fading doesn't work with all
+video drivers.
+In particular, it does not work on the 2020-vintage Raspberry Pi.
+.RS 4
+
+.TP 4
+.B Fade To Black When Blanking
+If selected, then when the screensaver activates, the current contents
+of the screen will fade to black instead of simply winking out. A fade
+will also be done when switching from one display mode to another.
+
+.TP 4
+.B Unfade From Black When Unblanking
+The opposite: if selected, then when the screensaver deactivates, the original
+contents of the screen will fade in from black instead of appearing
+immediately. This is only done if \fIFade To Black\fP is also selected.
+
+.TP 4
+.B Fade Duration
+When fading or unfading are selected, this controls how long the fade will
+take.
+
+.RE
+.B Theme
+.RS 4
+This option menu lists the color schemes available for use on the
+unlock dialog.
+.RE
+
+There are more settings than these available, but these are the most
+commonly used ones; see the manual for
+.BR xscreensaver (1)
+for other parameters that can be set by editing the \fI~/.xscreensaver\fP
+file, or the X resource database.
+.SH COMMAND-LINE OPTIONS
+.I xscreensaver\-settings
+accepts the following command line options.
+.TP 8
+.B \-display \fIhost:display.screen\fP
+The X display to use. The \fIxscreensaver\-settings\fP program will open its
+window on that display, and also control the \fIxscreensaver\fP daemon that
+is managing that same display.
+
+.TP 8
+.B \-prefs
+Start up with the \fBAdvanced\fP tab selected by default
+instead of the \fBDisplay Modes\fP tab.
+
+.TP 8
+.B \-debug
+Causes lots of diagnostics to be printed on stderr.
+
+.P
+The \fIxscreensaver\fP and \fIxscreensaver\-settings\fP processes must run
+on the same machine, or at least, on two machines that share a file system.
+When \fIxscreensaver\-settings\fP writes a new version of
+the \fI~/.xscreensaver\fP file, \fIxscreensaver\fP needs to see that same
+file, or it won't work.
+.SH ENVIRONMENT
+.PP
+.TP 8
+.B DISPLAY
+to get the default host and display number.
+.TP 8
+.B PATH
+to find the sub-programs to run. However, note that the sub-programs
+are actually launched by the \fIxscreensaver\fP daemon, not
+by \fIxscreensaver-settings\fP itself. So, what matters is what \fB$PATH\fP
+that the \fIxscreensaver\fP program sees.
+.TP 8
+.B HOME
+for the directory in which to read and write the \fI.xscreensaver\fP file.
+.TP 8
+.B XENVIRONMENT
+to get the name of a resource file that overrides the global resources
+stored in the RESOURCE_MANAGER property.
+.TP 8
+.B HTTP_PROXY, HTTPS_PROXY, http_proxy, or https_proxy
+to get the default proxy host and port.
+.SH UPGRADES
+The latest version of xscreensaver, an online version of this manual,
+and a FAQ can always be found at https://www.jwz.org/xscreensaver/
+.SH SEE ALSO
+.BR X (1),
+.BR xscreensaver (1),
+.BR xscreensaver\-command (1),
+.BR xscreensaver\-getimage (MANSUFFIX),
+.BR xscreensaver\-getimage\-file (MANSUFFIX),
+.BR xscreensaver\-getimage\-video (MANSUFFIX),
+.BR xscreensaver\-text (MANSUFFIX)
+.SH COPYRIGHT
+Copyright \(co 1992-2021 by Jamie Zawinski.
+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.
+.SH AUTHOR
+Jamie Zawinski <jwz@jwz.org>, 13-aug-1992.
+
+Please let me know if you find any bugs or make any improvements.
diff --git a/driver/xscreensaver-systemd.c b/driver/xscreensaver-systemd.c
index a46ed4d..d06174a 100644
--- a/driver/xscreensaver-systemd.c
+++ b/driver/xscreensaver-systemd.c
@@ -1,4 +1,5 @@
-/* xscreensaver-systemd, Copyright (c) 2019 Martin Lucina <martin@lucina.net>
+/* xscreensaver-systemd, Copyright (c) 2019-2021
+ * Martin Lucina <martin@lucina.net> and Jamie Zawinski <jwz@jwz.org>
*
* ISC License
*
@@ -16,217 +17,966 @@
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
- * This is a small utility providing systemd integration for XScreenSaver.
*
- * When run from ~/.xsession or equivalent, this will:
+ * This utility provides systemd integration for XScreenSaver.
+ * It does two things:
*
- * - Lock the screen before the system goes to sleep (using
- * xscreensaver-command -suspend).
+ * - When the system is about to go to sleep (e.g., laptop lid closing)
+ * it locks the screen *before* the system goes to sleep, by running
+ * "xscreensaver-command -suspend". And then when the system wakes
+ * up again, it runs "xscreensaver-command -deactivate" to force the
+ * unlock dialog to appear immediately.
*
- * - Ensure the XScreenSaver password dialog is shown after the system
- * is resumed (using xscreensaver-command -deactivate).
+ * - When another process on the system makes asks for the screen saver
+ * to be inhibited (e.g. because a video is playing) this program
+ * periodically runs "xscreensaver-command -deactivate" to keep the
+ * display un-blanked. It does this until the other program asks for
+ * it to stop.
*
- * This is implemented using the recommended way to do these things
- * nowadays, namely inhibitor locks. sd-bus is used for DBUS communication,
- * so the only dependency is libsystemd (which you already have if you
- * want this).
+ * For this to work at all, you must prevent Gnome and KDE from usurping
+ * the "org.freedesktop.ScreenSaver" messages, or else this program can't
+ * receive them. The "xscreensaver" man page contains the (complicated)
+ * installation instructions.
+ *
+ * Background:
+ *
+ * For decades, the traditional way for a video player to temporarily
+ * inhibit the screen saver was to have a heartbeat command that ran
+ * "xscreensaver-command -deactivate" once a minute while the video was
+ * playing, and ceased when the video was paused or stopped. The reason
+ * to do it as a heartbeat rather than a toggle is so that the player
+ * fails SAFE -- if the player exits abnormally, the heart stops beating,
+ * and screen saving and locking resumes.
+ *
+ * These days, the popular apps do this by using systemd. The design of
+ * the systemd method easily and trivially allows an app to inhibit the
+ * screen saver, crash, and then never un-inhibit it, so now your screen
+ * will never blank again.
+ *
+ * Furthermore, since the systemd method uses cookies to ensure that only
+ * the app that sent "inhibit" can send the matching "uninhibit", simply
+ * re-launching the crashed video player does not fix the problem.
+ *
+ * "Did IQs just drop sharply while I was away?" -- Ellen Ripley
+ *
+ * We can sometimes detect that the inhibiting app has exited abnormally
+ * by using "tracking peers" but I'm not sure how reliable that is.
+ *
+ * Furthermore, we can't listen for these "inhibit blanking" requests
+ * if some other program is already listening for them -- which Gnome and
+ * KDE do by default, even if their screen savers are otherwise disabled.
+ * That makes it far more complicated for the user to install XScreenSaver
+ * in such a way that "xscreensaver-systemd" can even launch at all.
+ *
+ * To recap: because the existing video players decided to delete the
+ * single line of code that they already had -- the heartbeat call to
+ * "xscreensaver-command -deactivate" -- we had to respond by adding a
+ * THOUSAND LINES of complicated code that talks to a server that may
+ * not be running, and that may not allow us to connect, and that may
+ * not work properly anyway, and that makes installation hellaciously
+ * difficult and confusing for the end user.
+ *
+ * This is what is laughingly referred to as "progress".
+ *
+ * So here's what we're dealing with now, with the various apps that
+ * you might use to play video on Linux at the end of 2020:
+ *
+ *
+ *****************************************************************************
+ *
+ * Firefox (version 78.5)
+ *
+ * When playing media, Firefox will send "inhibit" to one of these
+ * targets: "org.freedesktop.ScreenSaver" or "org.gnome.SessionManager".
+ *
+ * However, Firefox decides which, if any, of those to use at launch time,
+ * and does not revisit that decision. So if xscreensaver-systemd has not
+ * been launched before Firefox, it won't work. Fortunately, in most use
+ * cases, xscreensaver will have been launched earlier in the startup
+ * sequence than the web browser.
+ *
+ * If you close the tab or exit while playing, Firefox sends "uninhibit".
+ *
+ * Critical Firefox Bug:
+ *
+ * If Firefox crashes or is killed while playing, it never sends
+ * "uninhibit", leaving the screen saver permanently inhibited. Once
+ * that happens, the only way to un-fuck things is to kill and restart
+ * the "xscreensaver-systemd" program.
+ *
+ * Annoying Firefox Bug:
+ *
+ * Firefox sends an "inhibit" message when it is merely playing audio.
+ * That's horrible. Playing audio should prevent your machine from going
+ * to sleep, but it should NOT prevent your screen from blanking or
+ * locking.
+ *
+ * However at least it sends it with the reason "audio-playing" instead
+ * of "video-playing", meaning we can (and do) special-case Firefox and
+ * ignore that one.
+ *
+ *
+ *****************************************************************************
+ *
+ * Chrome (version 87)
+ *
+ * Sends "inhibit" to "org.freedesktop.ScreenSaver" (though it uses a
+ * a different object path than Firefox does). Unlike Firefox, Chrome
+ * does not send an "inhibit" message when only audio is playing.
+ *
+ * Critical Chrome Bug:
+ *
+ * If Chrome crashes or is killed while playing, it never sends
+ * "uninhibit", leaving the screen saver permanently inhibited.
+ *
+ *
+ *****************************************************************************
+ *
+ * Chromium (version 78, Raspbian 10.4)
+ *
+ * Does not use "org.freedesktop.ScreenSaver" or "xdg-screensaver".
+ * It appears to make no attempt to inhibit the screen saver while
+ * video is playing.
+ *
+ *
+ *****************************************************************************
+ *
+ * Chromium (version 84.0.4147.141, Raspbian 10.6)
+ *
+ * Sends "inhibit" to "org.freedesktop.ScreenSaver" (though it uses a
+ * a different object path than Firefox does). Unlike Firefox, Chrome
+ * does not send an "inhibit" message when only audio is playing.
+ *
+ * If you close the tab or exit while playing, Chromium sends "uninhibit".
+ *
+ * Critical Chromium Bug:
+ *
+ * If Chromium crashes or is killed while playing, it never sends
+ * "uninhibit", leaving the screen saver permanently inhibited.
+ *
+ * Annoying Chromium Bug:
+ *
+ * Like Firefox, Chromium sends an "inhibit" message when it is merely
+ * playing audio. Unlike Firefox, it sends exactly the same "reason"
+ * string as it does when playing video, so we can't tell them apart.
+ *
+ *
+ *****************************************************************************
+ *
+ * MPV (version 0.29.1)
+ *
+ * While playing, it runs "xdg-screensaver reset" every 10 seconds as a
+ * heartbeat. That program is a super-complicated shell script that will
+ * eventually run "xscreensaver-command -reset". So MPV talks to the
+ * xscreensaver daemon directly rather than going through systemd.
+ * That's fine.
+ *
+ * On Debian 10.4 and 10.6, MPV does not have a dependency on the
+ * "xdg-utils" package, so "xdg-screensaver" might not be installed.
+ * Oddly, Chromium *does* have a dependency on "xdg-utils", even though
+ * Chromium doesn't run "xdg-screensaver".
+ *
+ * The source code suggests that MPlayer and MPV call XResetScreenSaver()
+ * as well, but only affects the X11 server's built-in screen saver, not
+ * a userspace screen locker like xscreensaver.
+ *
+ * They also call XScreenSaverSuspend() which is part of the MIT
+ * SCREEN-SAVER server extension. XScreenSaver does make use of that
+ * extension because it is worse than useless. See the commentary at
+ * the top of xscreensaver.c for details.
+ *
+ * Annoying MPV Bug:
+ *
+ * Like Firefox and Chromium, MPV inhibits screen blanking when only
+ * audio is playing.
+ *
+ *
+ *****************************************************************************
+ *
+ * MPlayer (version mplayer-gui 2:1.3.0)
+ *
+ * I can't get this thing to play video at all. It only plays the audio
+ * of MP4 files, so I can't guess what it might or might not do with video.
+ * It appears to make no attempt to inhibit the screen saver.
+ *
+ *
+ *****************************************************************************
+ *
+ * VLC (version 3.0.11-0+deb10u1+rpt3)
+ *
+ * VLC sends "inhibit" to "org.freedesktop.ScreenSaver" when playing
+ * video. It does not send "inhibit" when playing audio only, and it
+ * sends "uninhibit" under all the right circumstances.
+ *
+ * NOTE: that's what I saw when I tested it on Raspbian 10.6. However,
+ * the version that came with Raspbian 10.4 -- which also called itself
+ * "VLC 3.0.11" -- did not send "uninhibit" when using the window
+ * manager's "close" button! Or when killed with "kill".
+ *
+ * NOTE ALSO: The VLC source code suggests that under some circumstances
+ * it might be talking to these instead: "org.freedesktop.ScreenSaver",
+ * "org.freedesktop.PowerManagement.Inhibit", "org.mate.SessionManager",
+ * and/or "org.gnome.SessionManager". It also contains code to run
+ * "xdg-screensaver reset" as a heartbeat. I can't tell how it decides
+ * which system to use. I have never seen it run "xdg-screensaver".
+ *
+ *
+ *****************************************************************************
+ *
+ * Zoom
+ *
+ * I'm told that the proprietary Zoom executable for Linux sends "inhibit"
+ * to "org.freedesktop.ScreenSaver", but I don't have any further details.
+ *
+ *****************************************************************************
+ *
+ * TO DO:
+ *
+ * - What precisely does the standalone Zoom executable do on Linux?
+ * There doesn't seem to be a Raspbian build, so I can't test it.
+ *
+ * - xscreensaver_method_uninhibit() does not actually send a reply, are
+ * we doing the right thing when registering it?
+ *
+ * - Currently this code is only listening to "org.freedesktop.ScreenSaver".
+ * Perhaps it should listen to "org.mate.SessionManager" and
+ * "org.gnome.SessionManager"? Where are those documented?
+ *
+ * - Do we need to call sd_bus_release_name() explicitly on exit?
+ *
+ * - Run under valgrind to check for any memory leaks.
+ *
+ * - Apparently the two different desktops have managed to come up with
+ * *three* different ways for dbus clients to ask the question, "is the
+ * screen currently blanked?" We should probably also respond to these:
+ *
+ * qdbus org.freedesktop.ScreenSaver /ScreenSaver org.freedesktop.ScreenSaver.GetActive
+ * qdbus org.kde.screensaver /ScreenSaver org.freedesktop.ScreenSaver.GetActive
+ * qdbus org.gnome.ScreenSaver /ScreenSaver org.gnome.ScreenSaver.GetActive
+ *
+ *
+ *
+ * TESTING:
+ *
+ * To call the D-BUS methods manually, you can use "busctl":
+ *
+ * busctl --user call org.freedesktop.ScreenSaver \
+ * /ScreenSaver org.freedesktop.ScreenSaver \
+ * Inhibit ss test-application test-reason
+ *
+ * This will hand out a cookie, which you can pass back to UnInhibit:
+ *
+ * u 1792821391
+ *
+ * busctl --user call org.freedesktop.ScreenSaver \
+ * /ScreenSaver org.freedesktop.ScreenSaver \
+ * UnInhibit u 1792821391
*
* https://github.com/mato/xscreensaver-systemd
*/
-#include <assert.h>
-#include <err.h>
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <poll.h>
#include <errno.h>
+#include <stdint.h>
#include <stdlib.h>
+#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <X11/Xlib.h>
#include <systemd/sd-bus.h>
+#include "version.h"
+#include "blurb.h"
+#include "yarandom.h"
+#include "queue.h"
+
+static char *screensaver_version;
+
+#define DBUS_CLIENT_NAME "org.jwz.XScreenSaver"
+#define DBUS_SD_SERVICE_NAME "org.freedesktop.login1"
+#define DBUS_SD_OBJECT_PATH "/org/freedesktop/login1"
+#define DBUS_SD_INTERFACE "org.freedesktop.login1.Manager"
+#define DBUS_SD_METHOD "Inhibit"
+#define DBUS_SD_METHOD_ARGS "ssss"
+#define DBUS_SD_METHOD_WHAT "sleep"
+#define DBUS_SD_METHOD_WHO "xscreensaver"
+#define DBUS_SD_METHOD_WHY "lock screen on suspend"
+#define DBUS_SD_METHOD_MODE "delay"
+
+#define DBUS_SD_MATCH "type='signal'," \
+ "interface='" DBUS_SD_INTERFACE "'," \
+ "member='PrepareForSleep'"
+
+#define DBUS_FDO_NAME "org.freedesktop.ScreenSaver"
+#define DBUS_FDO_OBJECT_PATH "/ScreenSaver" /* Firefox */
+#define DBUS_FDO_OBJECT_PATH_2 "/org/freedesktop/ScreenSaver" /* Chrome */
+#define DBUS_FDO_INTERFACE "org.freedesktop.ScreenSaver"
+
+#define HEARTBEAT_INTERVAL 50 /* seconds */
+
+#undef countof
+#define countof(x) (sizeof((x))/sizeof((*x)))
+
+
struct handler_ctx {
- sd_bus *bus;
- sd_bus_message *lock;
+ sd_bus *system_bus;
+ sd_bus_message *lock_message;
+ int lock_fd;
+ int is_inhibited;
+ sd_bus_track *track;
+};
+
+static struct handler_ctx global_ctx = { NULL, NULL, -1, 0, NULL };
+
+SLIST_HEAD(inhibit_head, inhibit_entry) inhibit_head =
+ SLIST_HEAD_INITIALIZER(inhibit_head);
+
+struct inhibit_entry {
+ uint32_t cookie;
+ time_t start_time;
+ char *appname;
+ char *peer;
+ SLIST_ENTRY(inhibit_entry) entries;
};
-static struct handler_ctx global_ctx = { NULL, NULL };
-static int handler(sd_bus_message *m, void *arg,
- sd_bus_error *ret_error)
+
+static void
+xscreensaver_command (const char *cmd)
{
- struct handler_ctx *ctx = arg;
- int before_sleep;
- int rc;
- sd_bus_error error = SD_BUS_ERROR_NULL;
- sd_bus_message *reply = NULL;
- int fd;
-
- rc = sd_bus_message_read(m, "b", &before_sleep);
- if (rc < 0) {
- warnx("Failed to read message: %s", strerror(-rc));
- return 0;
- }
+ char buf[1024];
+ int rc;
+ sprintf (buf, "xscreensaver-command %.100s -%.100s",
+ (verbose_p ? "-verbose" : "-quiet"),
+ cmd);
+ if (verbose_p)
+ fprintf (stderr, "%s: exec: %s\n", blurb(), buf);
+ rc = system (buf);
+ if (rc == -1)
+ fprintf (stderr, "%s: exec failed: %s\n", blurb(), buf);
+ else if (WEXITSTATUS(rc) != 0)
+ fprintf (stderr, "%s: exec: \"%s\" exited with status %d\n",
+ blurb(), buf, WEXITSTATUS(rc));
+}
- /* Use the scheme described at
- * https://www.freedesktop.org/wiki/Software/systemd/inhibit/
- * under "Taking Delay Locks".
- */
- if (before_sleep) {
- rc = system("xscreensaver-command -suspend");
- if (rc == -1) {
- warnx("Failed to run xscreensaver-command");
- }
- else if (WEXITSTATUS(rc) != 0) {
- warnx("xscreensaver-command failed with %d", WEXITSTATUS(rc));
- }
- if (ctx->lock) {
- /*
- * This will release the lock, since we hold the only ref to the
- * message, and sd_bus_message_unref() will close the underlying
- * fd.
- */
- sd_bus_message_unref(ctx->lock);
- ctx->lock = NULL;
- }
- else {
- warnx("Warning: ctx->lock is NULL, this should not happen?");
- }
+static int
+xscreensaver_register_sleep_lock (struct handler_ctx *ctx)
+{
+ sd_bus_error error = SD_BUS_ERROR_NULL;
+ sd_bus_message *reply = NULL;
+ int fd = -1;
+ int rc = sd_bus_call_method (ctx->system_bus,
+ DBUS_SD_SERVICE_NAME, DBUS_SD_OBJECT_PATH,
+ DBUS_SD_INTERFACE, DBUS_SD_METHOD,
+ &error, &reply,
+ DBUS_SD_METHOD_ARGS,
+ DBUS_SD_METHOD_WHAT, DBUS_SD_METHOD_WHO,
+ DBUS_SD_METHOD_WHY, DBUS_SD_METHOD_MODE);
+ if (rc < 0) {
+ fprintf (stderr, "%s: inhibit sleep failed: %s\n",
+ blurb(), error.message);
+ goto DONE;
+ }
+
+ /* Save the lock fd and explicitly take a ref to the lock message. */
+ rc = sd_bus_message_read (reply, "h", &fd);
+ if (rc < 0 || fd < 0) {
+ fprintf (stderr, "%s: inhibit sleep failed: no lock fd: %s\n",
+ blurb(), strerror(-rc));
+ goto DONE;
+ }
+ sd_bus_message_ref(reply);
+ ctx->lock_message = reply;
+ ctx->lock_fd = fd;
+
+ DONE:
+ sd_bus_error_free (&error);
+
+ return rc;
+}
+
+
+/* Called when DBUS_SD_INTERFACE sends a "PrepareForSleep" signal.
+ The event is sent twice: before sleep, and after.
+ */
+static int
+xscreensaver_systemd_handler (sd_bus_message *m, void *arg,
+ sd_bus_error *ret_error)
+{
+ struct handler_ctx *ctx = arg;
+ int before_sleep;
+ int rc;
+
+ rc = sd_bus_message_read (m, "b", &before_sleep);
+ if (rc < 0) {
+ fprintf (stderr, "%s: message read failed: %s\n",
+ blurb(), strerror(-rc));
+ return 1; /* >= 0 means success */
+ }
+
+ /* Use the scheme described at
+ https://www.freedesktop.org/wiki/Software/systemd/inhibit/
+ under "Taking Delay Locks".
+ */
+
+ if (before_sleep) {
+ /* Tell xscreensaver that we are suspending, and to lock if desired. */
+ xscreensaver_command ("suspend");
+
+ if (ctx->lock_message) {
+ /* Release the lock, meaning we are done and it's ok to sleep now.
+ Don't rely on unref'ing the message to close the fd, do that
+ explicitly here.
+ */
+ close(ctx->lock_fd);
+ sd_bus_message_unref (ctx->lock_message);
+ ctx->lock_message = NULL;
+ ctx->lock_fd = -1;
+ } else {
+ fprintf (stderr, "%s: no context lock\n", blurb());
}
- else {
- rc = system("xscreensaver-command -deactivate");
- if (rc == -1) {
- warnx("Failed to run xscreensaver-command");
- }
- else if (WEXITSTATUS(rc) != 0) {
- warnx("xscreensaver-command exited with %d", WEXITSTATUS(rc));
- }
+ } else {
+ /* Tell xscreensaver to present the unlock dialog right now. */
+ xscreensaver_command ("deactivate");
- rc = sd_bus_call_method(ctx->bus,
- "org.freedesktop.login1",
- "/org/freedesktop/login1",
- "org.freedesktop.login1.Manager",
- "Inhibit",
- &error,
- &reply,
- "ssss",
- "sleep",
- "xscreensaver",
- "lock screen on suspend",
- "delay");
- if (rc < 0) {
- warnx("Failed to call Inhibit(): %s", error.message);
- goto out;
- }
- /*
- * Verify that the reply actually contains a lock fd.
- */
- rc = sd_bus_message_read(reply, "h", &fd);
+ /* We woke from sleep, so we need to re-register for the next sleep. */
+ rc = xscreensaver_register_sleep_lock (ctx);
+ if (rc < 0)
+ fprintf (stderr, "%s: could not re-register sleep lock\n", blurb());
+ }
+
+ return 1; /* >= 0 means success */
+}
+
+
+/* Called from the vtable when another process sends a request to systemd
+ to inhibit the screen saver. We return to them a cookie which they must
+ present with their "uninhibit" request.
+ */
+static int
+xscreensaver_method_inhibit (sd_bus_message *m, void *arg,
+ sd_bus_error *ret_error)
+{
+ struct handler_ctx *ctx = arg;
+ const char *application_name = 0, *inhibit_reason = 0;
+ struct inhibit_entry *entry = 0;
+ const char *s;
+ const char *sender;
+
+ int rc = sd_bus_message_read(m, "ss", &application_name, &inhibit_reason);
+ if (rc < 0) {
+ fprintf (stderr, "%s: failed to parse method call: %s\n",
+ blurb(), strerror(-rc));
+ return rc;
+ }
+
+ if (!application_name || !*application_name) {
+ fprintf (stderr, "%s: no app name in method call\n", blurb());
+ return -1;
+ }
+
+ if (!inhibit_reason || !*inhibit_reason) {
+ fprintf (stderr, "%s: no reason in method call from \"%s\"\n",
+ blurb(), application_name);
+ return -1;
+ }
+
+ sender = sd_bus_message_get_sender (m);
+
+ /* Omit directory (Chrome does this shit) */
+ s = strrchr (application_name, '/');
+ if (s && s[1]) application_name = s+1;
+
+ if (strcasestr (inhibit_reason, "audio") &&
+ !strcasestr (inhibit_reason, "video")) {
+ /* Firefox 78 sends an inhibit when playing audio only, with reason
+ "audio-playing". This is horrible. Ignore it. (But perhaps it
+ would be better to accept it, issue them a cookie, and then just
+ ignore that entry?) */
+ if (verbose_p)
+ fprintf (stderr, "%s: inhibited by \"%s\" (%s) with \"%s\", ignored\n",
+ blurb(), application_name, sender, inhibit_reason);
+ return -1;
+ }
+
+ /* Tell the global tracker object to monitor when this peer exits. */
+ rc = sd_bus_track_add_name(ctx->track, sender);
+ if (rc < 0) {
+ fprintf (stderr, "%s: failed to track peer \"%s\": %s\n",
+ blurb(), sender, strerror(-rc));
+ sender = NULL;
+ }
+
+ entry = malloc(sizeof (struct inhibit_entry));
+ entry->cookie = ya_random();
+ entry->appname = strdup(application_name);
+ entry->peer = sender ? strdup(sender) : NULL;
+ entry->start_time = time ((time_t *)0);
+ SLIST_INSERT_HEAD(&inhibit_head, entry, entries);
+ ctx->is_inhibited++;
+ if (verbose_p)
+ fprintf (stderr, "%s: inhibited by \"%s\" (%s) with \"%s\""
+ " -> cookie %08X\n",
+ blurb(), application_name, sender, inhibit_reason, entry->cookie);
+
+ return sd_bus_reply_method_return (m, "u", entry->cookie);
+}
+
+
+/* Called from the vtable when another process sends a request to systemd
+ to uninhibit the screen saver. The cookie must match an earlier "inhibit"
+ request.
+ */
+static int
+xscreensaver_method_uninhibit (sd_bus_message *m, void *arg,
+ sd_bus_error *ret_error)
+{
+ struct handler_ctx *ctx = arg;
+ uint32_t cookie;
+ struct inhibit_entry *entry;
+ int found = 0;
+ const char *sender;
+
+ int rc = sd_bus_message_read (m, "u", &cookie);
+ if (rc < 0) {
+ fprintf (stderr, "%s: failed to parse method call: %s\n",
+ blurb(), strerror(-rc));
+ return rc;
+ }
+
+ sender = sd_bus_message_get_sender (m);
+
+ SLIST_FOREACH(entry, &inhibit_head, entries) {
+ if (entry->cookie == cookie) {
+ if (verbose_p)
+ fprintf (stderr, "%s: uninhibited by \"%s\" (%s) with cookie %08X\n",
+ blurb(), entry->appname, sender, cookie);
+ SLIST_REMOVE (&inhibit_head, entry, inhibit_entry, entries);
+ if (entry->appname) free (entry->appname);
+ if (entry->peer) {
+ rc = sd_bus_track_remove_name(ctx->track, entry->peer);
if (rc < 0) {
- warnx("Failed to read message: %s", strerror(-rc));
- goto out;
+ fprintf (stderr, "%s: failed to stop tracking peer \"%s\": %s\n",
+ blurb(), entry->peer, strerror(-rc));
}
- assert(fd >= 0);
- ctx->lock = reply;
-
-out:
- sd_bus_error_free(&error);
+ free(entry->peer);
+ }
+ free(entry);
+ ctx->is_inhibited--;
+ if (ctx->is_inhibited < 0)
+ ctx->is_inhibited = 0;
+ found = 1;
+ break;
}
+ }
- return 0;
+ if (! found)
+ fprintf (stderr, "%s: uninhibit: no match for cookie %08X\n",
+ blurb(), cookie);
+
+ return sd_bus_reply_method_return (m, "");
}
-int main(int argc, char *argv[])
+/*
+ * This vtable defines the service interface we implement.
+ */
+static const sd_bus_vtable
+xscreensaver_dbus_vtable[] = {
+ SD_BUS_VTABLE_START(0),
+ SD_BUS_METHOD("Inhibit", "ss", "u", xscreensaver_method_inhibit,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("UnInhibit", "u", "", xscreensaver_method_uninhibit,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_VTABLE_END
+};
+
+
+/* The only reason this program connects to X at all is so that it dies
+ right away when the X server shuts down. Otherwise the process might
+ linger, still connected to systemd but unable to connect to xscreensaver.
+ */
+static Display *
+open_dpy (void)
{
- sd_bus *bus = NULL, *user_bus = NULL;
- sd_bus_slot *slot = NULL;
- struct handler_ctx *ctx = &global_ctx;
- sd_bus_error error = SD_BUS_ERROR_NULL;
- sd_bus_message *reply = NULL;
- int rc;
- int fd;
- const char *match =
- "type='signal',interface='org.freedesktop.login1.Manager'"
- ",member='PrepareForSleep'";
-
- rc = sd_bus_open_user(&user_bus);
- if (rc < 0) {
- warnx("Failed to connect to user bus: %s", strerror(-rc));
- goto out;
- }
- rc = sd_bus_request_name(user_bus, "org.jwz.XScreenSaver", 0);
- if (rc < 0) {
- warnx("Failed to acquire well-known name: %s", strerror(-rc));
- warnx("Is another copy of xscreensaver-systemd running?");
- goto out;
+ Display *d;
+ const char *s = getenv("DISPLAY");
+ if (!s || !*s) {
+ fprintf (stderr, "%s: $DISPLAY unset\n", progname);
+ exit (1);
+ }
+
+ d = XOpenDisplay (s);
+ if (!d) {
+ fprintf (stderr, "%s: can't open display %s\n", progname, s);
+ exit (1);
+ }
+
+ return d;
+}
+
+
+static pid_t
+get_bus_name_pid (sd_bus *bus, const char *name)
+{
+ int rc;
+ sd_bus_creds *creds;
+ pid_t pid;
+
+ rc = sd_bus_get_name_creds (bus, name, SD_BUS_CREDS_PID, &creds);
+ if (rc == 0) {
+ rc = sd_bus_creds_get_pid (creds, &pid);
+ sd_bus_creds_unref (creds);
+ if (rc == 0)
+ return pid;
+ }
+
+ return -1;
+}
+
+
+/* This only works on Linux, but it's useful for the error message.
+ */
+static char *
+process_name (pid_t pid)
+{
+ char fn[100], buf[100], *s;
+ FILE *fd = 0;
+ if (pid <= 0) goto FAIL;
+ /* "comm" truncates at 16 characters. "cmdline" has nulls between args. */
+ sprintf (fn, "/proc/%lu/cmdline", (unsigned long) pid);
+ fd = fopen (fn, "r");
+ if (!fd) goto FAIL;
+ if (!fgets (buf, sizeof(buf)-1, fd)) goto FAIL;
+ if (fclose (fd) != 0) goto FAIL;
+ s = strchr (buf, '\n');
+ if (s) *s = 0;
+ return strdup (buf);
+ FAIL:
+ if (fd) fclose (fd);
+ return 0;
+}
+
+
+static int
+xscreensaver_systemd_loop (void)
+{
+ sd_bus *system_bus = NULL, *user_bus = NULL;
+ struct handler_ctx *ctx = &global_ctx;
+ sd_bus_error error = SD_BUS_ERROR_NULL;
+ int rc;
+ time_t last_deactivate_time = 0;
+ Display *dpy = open_dpy();
+
+ /* 'user_bus' is where we receive messages from other programs sending
+ inhibit/uninhibit to org.freedesktop.ScreenSaver, etc.
+ */
+
+ rc = sd_bus_open_user (&user_bus);
+ if (rc < 0) {
+ fprintf (stderr, "%s: user bus connection failed: %s\n",
+ blurb(), strerror(-rc));
+ goto FAIL;
+ }
+
+ /* Create a single tracking object so that we can later ask it,
+ "is the peer with this name still around?" This is how we tell
+ that Firefox has exited without calling 'uninhibit'.
+ */
+ rc = sd_bus_track_new (user_bus,
+ &global_ctx.track,
+ NULL,
+ NULL);
+ if (rc < 0) {
+ fprintf (stderr, "%s: cannot create user bus tracker: %s\n",
+ blurb(), strerror(-rc));
+ goto FAIL;
+ }
+
+ rc = sd_bus_add_object_vtable (user_bus,
+ NULL,
+ DBUS_FDO_OBJECT_PATH,
+ DBUS_FDO_INTERFACE,
+ xscreensaver_dbus_vtable,
+ &global_ctx);
+ if (rc < 0) {
+ fprintf (stderr, "%s: vtable registration failed: %s\n",
+ blurb(), strerror(-rc));
+ goto FAIL;
+ }
+
+ rc = sd_bus_add_object_vtable (user_bus,
+ NULL,
+ DBUS_FDO_OBJECT_PATH_2,
+ DBUS_FDO_INTERFACE,
+ xscreensaver_dbus_vtable,
+ &global_ctx);
+ if (rc < 0) {
+ fprintf (stderr, "%s: vtable registration failed: %s\n",
+ blurb(), strerror(-rc));
+ goto FAIL;
+ }
+
+ {
+ const char * const names[] = { DBUS_FDO_NAME, DBUS_CLIENT_NAME };
+ int i = 0;
+ for (i = 0; i < countof(names); i++) {
+ rc = sd_bus_request_name (user_bus, names[i], 0);
+ if (rc < 0) {
+ pid_t pid = get_bus_name_pid (user_bus, names[i]);
+ if (pid != -1) {
+ char *pname = process_name (pid);
+ if (pname) {
+ fprintf (stderr,
+ "%s: connection failed: \"%s\" in use by pid %lu (%s)\n",
+ blurb(), names[i], (unsigned long) pid, pname);
+ free (pname);
+ } else {
+ fprintf (stderr,
+ "%s: connection failed: \"%s\" in use by pid %lu\n",
+ blurb(), names[i], (unsigned long) pid);
+ }
+ } else if (-rc == EEXIST || -rc == EALREADY) {
+ fprintf (stderr, "%s: connection failed: \"%s\" already in use\n",
+ blurb(), names[i]);
+ } else {
+ fprintf (stderr, "%s: connection failed for \"%s\": %s\n",
+ blurb(), names[i], strerror(-rc));
+ }
+ goto FAIL;
+ }
}
+ }
- rc = sd_bus_open_system(&bus);
- if (rc < 0) {
- warnx("Failed to connect to system bus: %s", strerror(-rc));
- goto out;
+ /* 'system_bus' is where we hold a lock on org.freedesktop.login1, meaning
+ that the system will send us a PrepareForSleep message when the system is
+ about to suspend.
+ */
+
+ rc = sd_bus_open_system (&system_bus);
+ if (rc < 0) {
+ fprintf (stderr, "%s: system bus connection failed: %s\n",
+ blurb(), strerror(-rc));
+ goto FAIL;
+ }
+
+ /* Obtain a lock fd from the "Inhibit" method, so that we can delay
+ sleep when a "PrepareForSleep" signal is posted. */
+
+ ctx->system_bus = system_bus;
+ rc = xscreensaver_register_sleep_lock (ctx);
+ if (rc < 0)
+ goto FAIL;
+
+ /* This is basically an event mask, saying that we are interested in
+ "PrepareForSleep", and to run our callback when that signal is thrown.
+ */
+ rc = sd_bus_add_match (system_bus, NULL, DBUS_SD_MATCH,
+ xscreensaver_systemd_handler,
+ &global_ctx);
+ if (rc < 0) {
+ fprintf (stderr, "%s: add match failed: %s\n", blurb(), strerror(-rc));
+ goto FAIL;
+ }
+
+ if (verbose_p)
+ fprintf (stderr, "%s: connected\n", blurb());
+
+
+ /* Run an event loop forever, and wait for our callback to run.
+ */
+ while (1) {
+ struct pollfd fds[3];
+ uint64_t poll_timeout, system_timeout, user_timeout;
+ struct inhibit_entry *entry;
+
+ /* We MUST call sd_bus_process() on each bus at least once before calling
+ sd_bus_get_events(), so just always start the event loop by processing
+ all outstanding requests on both busses. */
+ do {
+ rc = sd_bus_process (system_bus, NULL);
+ if (rc < 0) {
+ fprintf (stderr, "%s: failed to process system bus: %s\n",
+ blurb(), strerror(-rc));
+ goto FAIL;
+ }
+ } while (rc > 0);
+
+ do {
+ rc = sd_bus_process (user_bus, NULL);
+ if (rc < 0) {
+ fprintf (stderr, "%s: failed to process user bus: %s\n",
+ blurb(), strerror(-rc));
+ goto FAIL;
+ }
+ } while (rc > 0);
+
+ fds[0].fd = sd_bus_get_fd (system_bus);
+ fds[0].events = sd_bus_get_events (system_bus);
+ fds[0].revents = 0;
+
+ fds[1].fd = sd_bus_get_fd (user_bus);
+ fds[1].events = sd_bus_get_events (user_bus);
+ fds[1].revents = 0;
+
+ fds[2].fd = XConnectionNumber (dpy);
+ fds[2].events = POLLIN;
+ fds[2].revents = 0;
+
+
+ sd_bus_get_timeout (system_bus, &system_timeout);
+ sd_bus_get_timeout (user_bus, &user_timeout);
+
+ if (system_timeout == 0 && user_timeout == 0)
+ poll_timeout = 0;
+ else if (system_timeout == UINT64_MAX && user_timeout == UINT64_MAX)
+ poll_timeout = -1;
+ else {
+ poll_timeout = (system_timeout < user_timeout
+ ? system_timeout : user_timeout);
+ poll_timeout /= 1000000;
}
- ctx->bus = bus;
-
- rc = sd_bus_call_method(bus,
- "org.freedesktop.login1",
- "/org/freedesktop/login1",
- "org.freedesktop.login1.Manager",
- "Inhibit",
- &error,
- &reply,
- "ssss",
- "sleep",
- "xscreensaver",
- "lock screen on suspend",
- "delay");
- if (rc < 0) {
- warnx("Failed to call Inhibit(): %s", error.message);
- goto out;
+
+ /* Prune any entries whose original sender has gone away: this happens
+ if a program inhibits, then exits without having called uninhibit.
+ That would have left us inhibited forever, even if the inhibiting
+ program was re-launched, since the new instance won't have the
+ same cookie. */
+ SLIST_FOREACH (entry, &inhibit_head, entries) {
+ if (entry->peer &&
+ !sd_bus_track_count_name (ctx->track, entry->peer)) {
+ if (verbose_p)
+ fprintf (stderr,
+ "%s: peer %s for inhibiting app \"%s\" has died:"
+ " uninhibiting %08X\n",
+ blurb(),
+ entry->peer,
+ entry->appname,
+ entry->cookie);
+ SLIST_REMOVE (&inhibit_head, entry, inhibit_entry, entries);
+ if (entry->appname) free (entry->appname);
+ free(entry->peer);
+ free (entry);
+ ctx->is_inhibited--;
+ if (ctx->is_inhibited < 0)
+ ctx->is_inhibited = 0;
+ }
}
- /*
- * Verify that the reply actually contains a lock fd.
+
+
+ /* We want to wake up at least once every N seconds to de-activate
+ the screensaver if we have been inhibited.
*/
- rc = sd_bus_message_read(reply, "h", &fd);
+ if (poll_timeout > HEARTBEAT_INTERVAL * 1000)
+ poll_timeout = HEARTBEAT_INTERVAL * 1000;
+
+ rc = poll (fds, 3, poll_timeout);
if (rc < 0) {
- warnx("Failed to read message: %s", strerror(-rc));
- goto out;
+ fprintf (stderr, "%s: poll failed: %s\n", blurb(), strerror(-rc));
+ exit (EXIT_FAILURE);
}
- assert(fd >= 0);
- ctx->lock = reply;
- rc = sd_bus_add_match(bus, &slot, match, handler, &global_ctx);
- if (rc < 0) {
- warnx("Failed to add match: %s", strerror(-rc));
- goto out;
+ if (fds[2].revents & (POLLERR | POLLHUP | POLLNVAL)) {
+ fprintf (stderr, "%s: X connection closed\n", blurb());
+ goto FAIL;
}
- for (;;) {
- rc = sd_bus_process(bus, NULL);
- if (rc < 0) {
- warnx("Failed to process bus: %s", strerror(-rc));
- goto out;
+ if (ctx->is_inhibited) {
+ time_t now = time ((time_t *) 0);
+ if (now - last_deactivate_time >= HEARTBEAT_INTERVAL) {
+ if (verbose_p) {
+ SLIST_FOREACH (entry, &inhibit_head, entries) {
+ char ct[100];
+ ctime_r (&entry->start_time, ct);
+ fprintf (stderr, "%s: inhibited by \"%s\" since %s",
+ blurb(), entry->appname, ct);
+ }
}
- if (rc > 0)
- /* we processed a request, try to process another one, right-away */
- continue;
+ xscreensaver_command ("deactivate");
+ last_deactivate_time = now;
+ }
+ }
+ }
- /* Wait for the next request to process */
- rc = sd_bus_wait(bus, (uint64_t) -1);
- if (rc < 0) {
- warnx("Failed to wait on bus: %s", strerror(-rc));
- goto out;
- }
+ FAIL:
+
+ XCloseDisplay(dpy);
+
+ if (system_bus)
+ sd_bus_flush_close_unref (system_bus);
+
+ if (ctx->track)
+ sd_bus_track_unref (ctx->track);
+
+ if (user_bus)
+ sd_bus_flush_close_unref (user_bus);
+
+ sd_bus_error_free (&error);
+
+ return EXIT_FAILURE;
+}
+
+
+static char *usage = "\n\
+usage: %s [-verbose]\n\
+\n\
+This program is launched by the xscreensaver daemon to monitor DBus.\n\
+It invokes 'xscreensaver-command' to tell the xscreensaver daemon to lock\n\
+the screen before the system suspends, e.g., when a laptop's lid is closed.\n\
+\n\
+It also responds to certain messages sent by media players allowing them to\n\
+request that the screen not be blanked during playback.\n\
+\n\
+From XScreenSaver %s, (c) 1991-%s Jamie Zawinski <jwz@jwz.org>.\n";
+
+
+#define USAGE() do { \
+ fprintf (stderr, usage, progname, screensaver_version, year); exit (1); \
+ } while(0)
+
+
+int
+main (int argc, char **argv)
+{
+ int i;
+ 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;
+
+ screensaver_version = version;
+
+ progname = argv[0];
+ s = strrchr (progname, '/');
+ if (s) progname = s+1;
+
+ for (i = 1; i < argc; i++)
+ {
+ const char *s = argv [i];
+ int L;
+ if (s[0] == '-' && s[1] == '-') s++;
+ L = strlen (s);
+ if (L < 2) USAGE ();
+ else if (!strncmp (s, "-verbose", L)) verbose_p = 1;
+ else if (!strncmp (s, "-quiet", L)) verbose_p = 0;
+ else USAGE ();
}
-out:
- if (reply)
- sd_bus_message_unref(reply);
- if (slot)
- sd_bus_slot_unref(slot);
- if (bus)
- sd_bus_flush_close_unref(bus);
- if (user_bus)
- sd_bus_flush_close_unref(user_bus);
- sd_bus_error_free(&error);
-
- return EXIT_FAILURE;
+# undef ya_rand_init
+ ya_rand_init (0);
+
+ exit (xscreensaver_systemd_loop());
}
diff --git a/driver/xscreensaver-systemd.man b/driver/xscreensaver-systemd.man
index 082cac7..beb23e7 100644
--- a/driver/xscreensaver-systemd.man
+++ b/driver/xscreensaver-systemd.man
@@ -1,49 +1,67 @@
-.TH XScreenSaver 1 "4-Jun-2019 (5.43)" "X Version 11"
+.TH XScreenSaver 1 "6-Jan-2021 (6.00)" "X Version 11"
.SH NAME
-xscreensaver-systemd - lock the screen when the machine suspends.
+xscreensaver-systemd - lock the screen upon suspend, and inhibit
+screen-blanking during video playback.
.SH SYNOPSIS
-.B xscreensaver-systemd
+.B xscreensaver-systemd [-verbose]
.SH DESCRIPTION
-The \fIxscreensaver\-systemd\fP program is a helper program to
-integrate xscreensaver with
-.BR systemd (1).
-When run from \fI~/.xsession\fP or equivalent, this will:
-.TP 3
-\fB1:
-Lock the screen before the system goes to sleep
-(using \fIxscreensaver-command -suspend\fP).
-.TP 3
-\fB2:
-Ensure the XScreenSaver password dialog is shown after the system
-is resumed (using \fIxscreensaver-command -deactivate\fP).
+The \fIxscreensaver\-systemd\fP program is a helper program launched by
+.BR xscreensaver (1)
+for
+.BR systemd (1)
+or
+.BR elogind (8)
+integration. It does two things:
+.TP 2
+\fB*
+When the system is about to go to sleep (e.g., the laptop lid has just been
+closed) it locks the screen just \fIbefore\fP the system sleeps, by running
+\fIxscreensaver-command -suspend\fP. When the system wakes up again, it
+runs \fIxscreensaver-command -deactivate\fP to make the unlock dialog appear
+immediately. It does this through the
+.BR org.freedesktop.login1 (5)
+D-Bus interface.
+.TP 2
+\fB*
+When another process on the system asks for the screen saver to be
+inhibited (e.g. because a video is playing) this program periodically runs
+\fIxscreensaver-command -deactivate\fP to keep the display un-blanked.
+It does this until the other program asks for it to stop, or exits.
+It does this through the
+.BR org.freedesktop.ScreenSaver (5)
+D-Bus interface.
.RS 0
-
-This is implemented using inhibitor locks;
-.BR sd-bus (3)
-is used for DBUS communication.
.SH BUGS
-Uses libsystemd.
+Only one program at a time can register with
+.BR systemd (1)
+for screen-saver-related events, so if another screen saver is running as a
+part of the desktop environment, \fIxscreensaver-systemd\fP will be unable to
+launch. Likely adversaries include
+.BR ksmserver (1)
+and
+.BR gsd\-screensaver\-proxy (1).
.SH SEE ALSO
.BR X (1),
.BR xscreensaver (1),
-.BR xscreensaver\-demo (1),
+.BR xscreensaver\-settings (1),
.BR xscreensaver\-command (1),
.BR systemd (1),
-.BR sd-bus (3)
+.BR sd-bus (3),
+.BR elogind (8)
.SH COPYRIGHT
-Copyright \(co 2019 by Martin Lucina.
+Copyright \(co 2019-2021 by Martin Lucina and Jamie Zawinski.
Permission to use, copy, modify, and/or distribute this software
for any purpose with or without fee is hereby granted, provided
that the above copyright notice and this permission notice appear
in all copies.
-THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
-WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
-AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
-CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
-OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
-CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+The software is provided "as is" and the author disclaims all
+warranties with regard to this software including all implied
+warranties of merchantability and fitness. in no event shall the
+author be liable for any special, direct, indirect, or
+consequential damages or any damages whatsoever resulting from loss
+of use, data or profits, whether in an action of contract,
+negligence or other tortious action, arising out of or in
+connection with the use or performance of this software.
.SH AUTHOR
Martin Lucina <martin@lucina.net>, 4-Jun-2019
diff --git a/driver/xscreensaver.c b/driver/xscreensaver.c
index 40f8207..5eb7167 100644
--- a/driver/xscreensaver.c
+++ b/driver/xscreensaver.c
@@ -1,4 +1,4 @@
-/* xscreensaver, Copyright (c) 1991-2020 Jamie Zawinski <jwz@jwz.org>
+/* xscreensaver, Copyright © 1991-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
@@ -7,2527 +7,2334 @@
* documentation. No representations are made about the suitability of this
* software for any purpose. It is provided "as is" without express or
* implied warranty.
- */
-
-/* ========================================================================
- * First we wait until the keyboard and mouse become idle for the specified
- * amount of time. We do this in one of three different ways: periodically
- * checking with the XIdle server extension; selecting key and mouse events
- * on (nearly) all windows; or by waiting for the MIT-SCREEN-SAVER extension
- * to send us a "you are idle" event.
*
- * Then, we map a full screen black window.
+ * XScreenSaver Daemon, version 6.
+ *
+ * Having started its life in 1991, XScreenSaver acquired a lot of optional
+ * features to allow it to detect user activity, lock the screen and
+ * authenticate on systems as varied as VMS, SunOS, HPUX, SGI, and of course
+ * that young upstart, Linux. In such a heterogeneous environment, many
+ * features that would have simplified things were not universally available.
+ * Though I made an effort to follow a principle of minimal library usage in
+ * the XScreenSaver daemon, as described in my 2004 article "On Toolkits"
+ * <https://www.jwz.org/xscreensaver/toolkits.html>, there was still quite a
+ * lot of code in the daemon, more than is strictly necessary when we consider
+ * only modern, still-maintained operating systems.
*
- * We place a __SWM_VROOT property on this window, so that newly-started
- * clients will think that this window is a "virtual root" window (as per
- * the logic in the historical "vroot.h" header.)
+ * This 2021 refactor had one goal: to minimize the amount of code in which a
+ * crash will cause the screen to unlock. In service of that goal, the process
+ * which holds the keyboard an mouse grabbed should:
*
- * If there is an existing "virtual root" window (one that already had
- * an __SWM_VROOT property) then we remove that property from that window.
- * Otherwise, clients would see that window (the real virtual root) instead
- * of ours (the impostor.)
+ * - Be as small as technically possible, for ease of auditing;
*
- * Then we pick a random program to run, and start it. Two assumptions
- * are made about this program: that it has been specified with whatever
- * command-line options are necessary to make it run on the root window;
- * and that it has been compiled with vroot.h, so that it is able to find
- * the root window when a virtual-root window manager (or this program) is
- * running.
+ * - Link against as few libraries as possible, to bring in as little
+ * third-party code as possible;
*
- * Then, we wait for keyboard or mouse events to be generated on the window.
- * When they are, we kill the inferior process, unmap the window, and restore
- * the __SWM_VROOT property to the real virtual root window if there was one.
+ * - Delegate other tasks to helper programs running in other processes,
+ * in such a way that if those other processes should crash, the grabs
+ * remain intact and the screen remains locked.
*
- * On multi-screen systems, we do the above on each screen, and start
- * multiple programs, each with a different value of $DISPLAY.
+ * In the XScreenSaver 6 design, the division of labor is as follows:
*
- * On Xinerama systems, we do a similar thing, but instead create multiple
- * windows on the (only) display, and tell the subprocess which one to use
- * via the $XSCREENSAVER_WINDOW environment variable -- this trick requires
- * a recent (Aug 2003) revision of vroot.h.
+ * A) "xscreensaver" -- This is the main screen saver daemon. If this
+ * process crashes, the screen unlocks, so this is the one where
+ * minimizing the attack surface is critical.
*
- * (See comments in screens.c for more details about Xinerama/RANDR stuff.)
+ * - It reads events using the XInput extension version 2, which is
+ * required. This means that X11R7 is also required, which was
+ * released in 2009, and is ubiquitous as of 2021.
*
- * While we are waiting for user activity, we also set up timers so that,
- * after a certain amount of time has passed, we can start a different
- * screenhack. We do this by killing the running child process with
- * SIGTERM, and then starting a new one in the same way.
+ * - It links against only libX11 and libXi.
*
- * If there was a real virtual root, meaning that we removed the __SWM_VROOT
- * property from it, meaning we must (absolutely must) restore it before we
- * exit, then we set up signal handlers for most signals (SIGINT, SIGTERM,
- * etc.) that do this. Most Xlib and Xt routines are not reentrant, so it
- * is not generally safe to call them from signal handlers; however, this
- * program spends most of its time waiting, so the window of opportunity
- * when code could be called reentrantly is fairly small; and also, the worst
- * that could happen is that the call would fail. If we've gotten one of
- * these signals, then we're on our way out anyway. If we didn't restore the
- * __SWM_VROOT property, that would be very bad, so it's worth a shot. Note
- * that this means that, if you're using a virtual-root window manager, you
- * can really fuck up the world by killing this process with "kill -9".
+ * - It maps no windows and renders no graphics or text. It only
+ * acquires keyboard and mouse grabs, handles timer logic, and
+ * launches and manages several sub-processes.
*
- * This program accepts ClientMessages of type SCREENSAVER; these messages
- * may contain the atoms ACTIVATE, DEACTIVATE, etc, meaning to turn the
- * screensaver on or off now, regardless of the idleness of the user,
- * and a few other things. The included "xscreensaver-command" program
- * sends these messsages.
+ * B) "xscreensaver-gfx" -- When the time comes for the screen to blank,
+ * this process is launched to fade out, black out every monitor on
+ * the system, launch graphics demos to render on those blanked screens,
+ * and cycle them from time to time.
*
- * If we don't have the XIdle, MIT-SCREEN-SAVER, or SGI SCREEN_SAVER
- * extensions, then we do the XAutoLock trick: notice every window that
- * gets created, and wait 30 seconds or so until its creating process has
- * settled down, and then select KeyPress events on those windows which
- * already select for KeyPress events. It's important that we not select
- * KeyPress on windows which don't select them, because that would
- * interfere with event propagation. This will break if any program
- * changes its event mask to contain KeyRelease or PointerMotion more than
- * 30 seconds after creating the window, but such programs do not seem to
- * occur in nature (I've never seen it happen in all these years.)
+ * - If this program crashes, the screen does not unlock. The keyboard
+ * and mouse remain grabbed by the "xscreensaver" process, which will
+ * re-launch this process as necessary.
*
- * The reason that we can't select KeyPresses on windows that don't have
- * them already is that, when dispatching a KeyPress event, X finds the
- * lowest (leafmost) window in the hierarchy on which *any* client selects
- * for KeyPress, and sends the event to that window. This means that if a
- * client had a window with subwindows, and expected to receive KeyPress
- * events on the parent window instead of the subwindows, then that client
- * would malfunction if some other client selected KeyPress events on the
- * subwindows. It is an incredible misdesign that one client can make
- * another client malfunction in this way.
+ * - If it does crash, the logged in user's desktop may be momentarily
+ * visible before it re-launches, but it will be impossible to interact
+ * with it.
*
- * But here's a new kink that started showing up in late 2014: GNOME programs
- * don't actually select for or receive KeyPress events! They do it behind
- * the scenes through some kind of Input Method magic, even when running in
- * an en_US locale. However, in that case, those applications *do* seem to
- * update the _NET_WM_USER_TIME on their own windows every time they have
- * received a secret KeyPress, so we *also* monitor that property on every
- * window, and treat changes to it as identical to KeyPress.
+ * - This process must react to hot-swapping of monitors in order to
+ * keep the screen blanked and the desktop obscured, and manages the
+ * life cycle of the graphics demos, each running in their own
+ * sub-process of "xscreensaver-gfx".
*
- * To detect mouse motion, we periodically wake up and poll the mouse
- * position and button/modifier state, and notice when something has
- * changed. We make this check every five seconds by default, and since the
- * screensaver timeout has a granularity of one minute, this makes the
- * chance of a false positive very small. We could detect mouse motion in
- * the same way as keyboard activity, but that would suffer from the same
- * "client changing event mask" problem that the KeyPress events hack does.
- * I think polling is more reliable.
+ * C) The graphics demos themselves. Launched by "xscreensaver-gfx" to run
+ * on the windows provided, there is one of these processes for each
+ * screen. These can use X11 or OpenGL, and have no impact on security;
+ * if it breaks, you can keep both pieces.
*
- * On systems with /proc/interrupts (Linux) we poll that file and note when
- * the interrupt counter numbers on the "keyboard" and "PS/2" lines change.
- * (There is no reliable way, using /proc/interrupts, to detect non-PS/2
- * mice, so it doesn't help for serial or USB mice.)
+ * D) "xscreensaver-auth" -- When the time comes to prompt the user for their
+ * password to unlock the screen, the "xscreensaver" daemon launches this
+ * process. If it exits with a "success" error code, the screen unlocks,
+ * otherwise it does not. This means that if it crashes, the screen
+ * remains locked.
*
- * None of this crap happens if we're using one of the extensions. Sadly,
- * the XIdle extension hasn't been available for many years; the SGI
- * extension only exists on SGIs; and the MIT extension, while widely
- * deployed, is garbage in several ways.
+ * - It turns out that programs using the XInput 2 extension are able to
+ * snoop the keyboard even when the keyboard is grabbed by another
+ * process! So that's how this program reads user input, even while
+ * the "xscreensaver" process has the keyboard grabbed.
*
- * A third idle-detection option could be implemented (but is not): when
- * running on the console display ($DISPLAY is `localhost`:0) and we're on a
- * machine where /dev/tty and /dev/mouse have reasonable last-modification
- * times, we could just stat() those. But the incremental benefit of
- * implementing this is really small, so forget I said anything.
+ * - This program runs PAM, or whatever other authorization mechanisms
+ * are configured. On some systems, it may need to be setuid in order
+ * to read /etc/shadow, meaning it must disavow privileges after
+ * initialization but before connecting to the X server.
*
- * Debugging hints:
- * - Have a second terminal handy.
- * - Be careful where you set your breakpoints, you don't want this to
- * stop under the debugger with the keyboard grabbed or the blackout
- * window exposed.
- * - If you run your debugger under XEmacs, try M-ESC (x-grab-keyboard)
- * to keep your emacs window alive even when xscreensaver has grabbed.
- * - Go read the code related to `debug_p'.
- * - You probably can't set breakpoints in functions that are called on
- * the other side of a call to fork() -- if your subprocesses are
- * dying with signal 5, Trace/BPT Trap, you're losing in this way.
- * - If you aren't using a server extension, don't leave this stopped
- * under the debugger for very long, or the X input buffer will get
- * huge because of the keypress events it's selecting for. This can
- * make your X server wedge with "no more input buffers."
+ * - It gets to use libXft, so the fonts don't suck.
*
- * ======================================================================== */
+ * - In theory, this program could be implemented using any GUI toolkit,
+ * and thus take advantage of input methods, on-screen keyboards,
+ * screen readers, etc. Again, this is permissible because this
+ * program fails SAFE: if it crashes, the screen remains locked.
+ *
+ * E) "xscreensaver-systemd" -- This runs in the background and monitors
+ * requests on the systemd dbus. This is how closing the laptop lid
+ * causes the screen to lock, and how video players request that blanking
+ * be inhibited. This program invokes "xscreensaver-command" as needed
+ * to pass those requests along to "xscreensaver" via ClientMessages.
+ */
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
-#include <stdio.h>
-#include <ctype.h>
-#include <X11/Xlib.h>
-
-#ifdef ENABLE_NLS
-# include <locale.h>
-# include <libintl.h>
-#endif /* ENABLE_NLS */
+#include "version.h"
-#include <X11/Xlibint.h>
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
-#include <X11/Xatom.h>
-#include <X11/Intrinsic.h>
-#include <X11/StringDefs.h>
-#include <X11/Shell.h>
-#include <X11/Xos.h>
+#include <stdio.h>
+#include <ctype.h>
#include <time.h>
#include <sys/time.h>
-#include <netdb.h> /* for gethostbyname() */
#include <sys/types.h>
-#include <pwd.h>
-#ifdef HAVE_XMU
-# ifndef VMS
-# include <X11/Xmu/Error.h>
-# else /* !VMS */
-# include <Xmu/Error.h>
-# endif /* !VMS */
-#else /* !HAVE_XMU */
-# include "xmu.h"
-#endif /* !HAVE_XMU */
-
-#ifdef HAVE_MIT_SAVER_EXTENSION
-#include <X11/extensions/scrnsaver.h>
-#endif /* HAVE_MIT_SAVER_EXTENSION */
-
-#ifdef HAVE_XIDLE_EXTENSION
-# include <X11/extensions/xidle.h>
-#endif /* HAVE_XIDLE_EXTENSION */
-
-#ifdef HAVE_SGI_VC_EXTENSION
-# include <X11/extensions/XSGIvc.h>
-#endif /* HAVE_SGI_VC_EXTENSION */
+#include <sys/stat.h>
+#include <signal.h>
+#include <errno.h>
-#ifdef HAVE_READ_DISPLAY_EXTENSION
-# include <X11/extensions/readdisplay.h>
-#endif /* HAVE_READ_DISPLAY_EXTENSION */
+#include <pwd.h> /* for getpwuid() */
+#include <grp.h> /* for getgrgid() */
-#ifdef HAVE_XSHM_EXTENSION
-# include <X11/extensions/XShm.h>
-#endif /* HAVE_XSHM_EXTENSION */
+#ifdef HAVE_UNAME
+# include <sys/utsname.h> /* for uname() */
+#endif /* HAVE_UNAME */
-#ifdef HAVE_DPMS_EXTENSION
-# include <X11/extensions/dpms.h>
-#endif /* HAVE_DPMS_EXTENSION */
-
-
-#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
-# include <X11/extensions/Xdbe.h>
-#endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
-
-#ifdef HAVE_XF86VMODE
-# include <X11/extensions/xf86vmode.h>
-#endif /* HAVE_XF86VMODE */
+#ifdef HAVE_SYS_WAIT_H
+# include <sys/wait.h> /* for waitpid() and associated macros */
+#endif
-#ifdef HAVE_XF86MISCSETGRABKEYSSTATE
-# include <X11/extensions/xf86misc.h>
-#endif /* HAVE_XF86MISCSETGRABKEYSSTATE */
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#include <X11/cursorfont.h>
+#include <X11/Xos.h>
+#include <X11/extensions/XInput2.h>
-#ifdef HAVE_XINERAMA
-# include <X11/extensions/Xinerama.h>
-#endif /* HAVE_XINERAMA */
+#include "xmu.h"
+#include "blurb.h"
+#include "atoms.h"
+#include "clientmsg.h"
+#include "xinput.h"
+#include "prefs.h"
-#ifdef HAVE_RANDR
-# include <X11/extensions/Xrandr.h>
-#endif /* HAVE_RANDR */
+#undef countof
+#define countof(x) (sizeof((x))/sizeof((*x)))
-#include "xscreensaver.h"
-#include "version.h"
-#include "yarandom.h"
-#include "resources.h"
-#include "visual.h"
-#include "usleep.h"
-#include "auth.h"
+#undef ABS
+#define ABS(x)((x)<0?-(x):(x))
-saver_info *global_si_kludge = 0; /* I hate C so much... */
+#undef MAX
+#define MAX(x,y)((x)>(y)?(x):(y))
-char *progname = 0;
-char *progclass = 0;
-XrmDatabase db = 0;
+/* Globals used in this file.
+ */
+Bool debug_p = False;
+static Bool splash_p = True;
+static const char *version_number = 0;
+
+/* Preferences. */
+static Bool lock_p = False;
+static Bool locking_disabled_p = False;
+static unsigned int blank_timeout = 0;
+static unsigned int lock_timeout = 0;
+static unsigned int pointer_hysteresis = 0;
+
+/* Subprocesses. */
+#define SAVER_GFX_PROGRAM "xscreensaver-gfx"
+#define SAVER_AUTH_PROGRAM "xscreensaver-auth"
+#define SAVER_SYSTEMD_PROGRAM "xscreensaver-systemd"
+static pid_t saver_gfx_pid = 0;
+static pid_t saver_auth_pid = 0;
+static pid_t saver_systemd_pid = 0;
+static int sighup_received = 0;
+static int sigterm_received = 0;
+static int sigchld_received = 0;
+static Bool gfx_stopped_p = False;
+
+Window daemon_window = 0;
+Cursor blank_cursor = None;
+Cursor auth_cursor = None;
+
+
+/* for the --restart command.
+ */
+static char **saved_argv;
-static Atom XA_SCREENSAVER_RESPONSE;
-static Atom XA_ACTIVATE, XA_DEACTIVATE, XA_SUSPEND, XA_CYCLE, XA_NEXT, XA_PREV;
-static Atom XA_RESTART, XA_SELECT;
-static Atom XA_THROTTLE, XA_UNTHROTTLE;
-Atom XA_DEMO, XA_PREFS, XA_EXIT, XA_LOCK, XA_BLANK;
+static void
+save_argv (int ac, char **av)
+{
+ saved_argv = (char **) calloc (ac+2, sizeof (char *));
+ saved_argv [ac] = 0;
+ while (ac--)
+ {
+ int i = strlen (av[ac]) + 1;
+ saved_argv[ac] = (char *) malloc (i);
+ memcpy (saved_argv[ac], av[ac], i);
+ }
+}
-
-static XrmOptionDescRec options [] = {
- { "-verbose", ".verbose", XrmoptionNoArg, "on" },
- { "-silent", ".verbose", XrmoptionNoArg, "off" },
+static void
+kill_all_subprocs (void)
+{
+ if (saver_gfx_pid)
+ {
+ if (verbose_p)
+ fprintf (stderr, "%s: pid %lu: killing " SAVER_GFX_PROGRAM "\n",
+ blurb(), (unsigned long) saver_gfx_pid);
+ kill (saver_gfx_pid, SIGTERM);
- /* xscreensaver-demo uses this one */
- { "-nosplash", ".splash", XrmoptionNoArg, "off" },
- { "-no-splash", ".splash", XrmoptionNoArg, "off" },
+ if (gfx_stopped_p) /* SIGCONT to allow SIGTERM to proceed */
+ {
+ if (verbose_p)
+ fprintf (stderr, "%s: pid %lu: sending " SAVER_GFX_PROGRAM
+ " SIGCONT\n",
+ blurb(), (unsigned long) saver_gfx_pid);
+ gfx_stopped_p = False;
+ kill (-saver_gfx_pid, SIGCONT); /* send to process group */
+ }
+ }
- /* useful for debugging */
- { "-no-capture-stderr", ".captureStderr", XrmoptionNoArg, "off" },
- { "-log", ".logFile", XrmoptionSepArg, 0 },
-};
+ if (saver_auth_pid)
+ {
+ if (verbose_p)
+ fprintf (stderr, "%s: pid %lu: killing " SAVER_AUTH_PROGRAM "\n",
+ blurb(), (unsigned long) saver_auth_pid);
+ kill (saver_auth_pid, SIGTERM);
+ }
-#ifdef __GNUC__
- __extension__ /* shut up about "string length is greater than the length
- ISO C89 compilers are required to support" when including
- the .ad file... */
-#endif
+ if (saver_systemd_pid)
+ {
+ if (verbose_p)
+ fprintf (stderr, "%s: pid %lu: killing " SAVER_SYSTEMD_PROGRAM "\n",
+ blurb(), (unsigned long) saver_systemd_pid);
+ kill (saver_systemd_pid, SIGTERM);
+ }
+}
-static char *defaults[] = {
-#include "XScreenSaver_ad.h"
- 0
-};
-
-#ifdef _VROOT_H_
-ERROR! You must not include vroot.h in this file.
-#endif
static void
-do_help (saver_info *si)
+saver_exit (int status)
{
- char *s, year[5];
- s = strchr (screensaver_id, '-');
- s = strrchr (s, '-');
- s++;
- strncpy (year, s, 4);
- year[4] = 0;
-
- fflush (stdout);
- fflush (stderr);
- fprintf (stdout, "\
-xscreensaver %s, copyright (c) 1991-%s by Jamie Zawinski <jwz@jwz.org>\n\
-\n\
- All xscreensaver configuration is via the `~/.xscreensaver' file.\n\
- Rather than editing that file by hand, just run `xscreensaver-demo':\n\
- that program lets you configure the screen saver graphically,\n\
- including timeouts, locking, and display modes.\n\
-\n",
- si->version, year);
- fprintf (stdout, "\
- Just getting started? Try this:\n\
-\n\
- xscreensaver &\n\
- xscreensaver-demo\n\
-\n\
- For updates, online manual, and FAQ, please see the web page:\n\
-\n\
- https://www.jwz.org/xscreensaver/\n\
-\n");
-
- fflush (stdout);
- fflush (stderr);
- exit (1);
+ kill_all_subprocs();
+ exit (status);
}
-Bool in_signal_handler_p = 0; /* I hate C so much... */
-
-char *
-timestring (time_t when)
+static void
+catch_signal (int sig, RETSIGTYPE (*handler) (int))
{
- if (in_signal_handler_p)
- {
- /* Turns out that ctime() and even localtime_r() call malloc() on Linux!
- So we can't call them from inside SIGCHLD. WTF.
- */
- static char buf[30];
- strcpy (buf, "... ... .. signal ....");
- return buf;
- }
- else
+# ifdef HAVE_SIGACTION
+
+ struct sigaction a;
+ a.sa_handler = handler;
+ sigemptyset (&a.sa_mask);
+ a.sa_flags = 0;
+
+ /* On Linux 2.4.9 (at least) we need to tell the kernel to not mask delivery
+ of this signal from inside its handler, or else when we execvp() the
+ process again, it starts up with SIGHUP blocked, meaning that killing
+ it with -HUP only works *once*. You'd think that execvp() would reset
+ all the signal masks, but it doesn't.
+ */
+# if defined(SA_NOMASK)
+ a.sa_flags |= SA_NOMASK;
+# elif defined(SA_NODEFER)
+ a.sa_flags |= SA_NODEFER;
+# endif
+
+ if (sigaction (sig, &a, 0) < 0)
+# else /* !HAVE_SIGACTION */
+ if (((long) signal (sig, handler)) == -1L)
+# endif /* !HAVE_SIGACTION */
{
- char *str, *nl;
- if (! when) when = time ((time_t *) 0);
- str = (char *) ctime (&when);
- nl = (char *) strchr (str, '\n');
- if (nl) *nl = 0; /* take off that dang newline */
- return str;
+ char buf [255];
+ sprintf (buf, "%s: couldn't catch signal %d", blurb(), sig);
+ perror (buf);
+ saver_exit (1);
}
}
-static Bool blurb_timestamp_p = True; /* kludge */
-const char *
-blurb (void)
+/* Re-execs the process with the arguments in saved_argv. Does not return.
+ Do not call this while the screen is locked: user must unlock first.
+ */
+static void
+restart_process (void)
{
- if (!blurb_timestamp_p)
- return progname;
- else
- {
- static char buf[255];
- char *ct = timestring(0);
- int n = strlen(progname);
- if (n > 100) n = 99;
- strncpy(buf, progname, n);
- buf[n++] = ':';
- buf[n++] = ' ';
- strncpy(buf+n, ct+11, 8);
- strcpy(buf+n+9, ": ");
- return buf;
- }
+ kill_all_subprocs();
+ execvp (saved_argv [0], saved_argv); /* shouldn't return */
+ {
+ char buf [512];
+ sprintf (buf, "%s: could not restart process", blurb());
+ perror(buf);
+ fflush(stderr);
+ abort();
+ }
}
+static RETSIGTYPE saver_sighup_handler (int sig) { sighup_received = sig; }
+static RETSIGTYPE saver_sigchld_handler (int sig) { sigchld_received = sig; }
+static RETSIGTYPE saver_sigterm_handler (int sig) { sigterm_received = sig; }
-int
-saver_ehandler (Display *dpy, XErrorEvent *error)
+static void
+handle_signals (void)
{
- saver_info *si = global_si_kludge; /* I hate C so much... */
- int i;
- Bool fatal_p;
+ catch_signal (SIGHUP, saver_sighup_handler);
+ catch_signal (SIGCHLD, saver_sigchld_handler);
+ catch_signal (SIGTERM, saver_sigterm_handler); /* kill */
+ catch_signal (SIGINT, saver_sigterm_handler); /* shell ^C */
+ catch_signal (SIGQUIT, saver_sigterm_handler); /* shell ^| */
+}
- if (!real_stderr) real_stderr = stderr;
- fprintf (real_stderr, "\n"
- "#######################################"
- "#######################################\n\n"
- "%s: X Error! PLEASE REPORT THIS BUG.\n",
- blurb());
+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");
+ }
- for (i = 0; i < si->nscreens; i++)
- {
- saver_screen_info *ssi = &si->screens[i];
- fprintf (real_stderr, "%s: screen %d/%d: 0x%x, 0x%x, 0x%x\n",
- blurb(), ssi->real_screen_number, ssi->number,
- (unsigned int) RootWindowOfScreen (si->screens[i].screen),
- (unsigned int) si->screens[i].real_vroot,
- (unsigned int) si->screens[i].screensaver_window);
- }
+ /* Put each launched process in its own process group so that
+ SIGSTOP will affect all of its inferior processes as well.
+ */
+ if (setpgid (forked, 0))
+ {
+ char buf [255];
+ sprintf (buf, "%s: setpgid %d", blurb(), forked);
+ perror (buf);
+ }
+ break;
+ }
- fprintf (real_stderr, "\n"
- "#######################################"
- "#######################################\n\n");
+ return forked;
+}
- fatal_p = XmuPrintDefaultErrorMessage (dpy, error, real_stderr);
- fatal_p = True; /* The only time I've ever seen a supposedly nonfatal error,
- it has been BadImplementation / Xlib sequence lost, which
- are in truth pretty damned fatal.
- */
+static int respawn_thrashing_count = 0;
+
+/* Called from the main loop some time after the SIGCHLD signal has fired.
+ Returns true if:
+ - the process that died was "xscreensaver-auth", and
+ - it exited with a "success" status meaning "user is authenticated".
+ */
+static Bool
+handle_sigchld (Display *dpy, Bool blanked_p)
+{
+ Bool authenticated_p = False;
- fprintf (real_stderr, "\n");
+ sigchld_received = 0;
+ if (debug_p)
+ fprintf (stderr, "%s: SIGCHLD received\n", blurb());
- if (! fatal_p)
- fprintf (real_stderr, "%s: nonfatal error.\n\n", blurb());
- else
+ /* Reap every now-dead inferior without blocking. */
+ while (1)
{
- if (si->prefs.xsync_p)
- {
- saver_exit (si, -1, "because of synchronous X Error");
- }
+ int wait_status = 0;
+ pid_t kid = waitpid (-1, &wait_status, WNOHANG | WUNTRACED);
+ char how[100];
+
+ /* 0 means no more children to reap.
+ -1 means error -- except "interrupted system call"
+ isn't a "real" error, so if we get that, try again.
+ */
+ if (kid == 0 ||
+ (kid < 0 && errno != EINTR))
+ break;
+
+ /* We get SIGCHLD after sending SIGSTOP, but no action is required. */
+ if (WIFSTOPPED (wait_status))
+ {
+ if (verbose_p)
+ fprintf (stderr, "%s: pid %lu: %s stopped\n", blurb(),
+ (unsigned long) kid,
+ (kid == saver_gfx_pid ? SAVER_GFX_PROGRAM :
+ kid == saver_auth_pid ? SAVER_AUTH_PROGRAM :
+ kid == saver_systemd_pid ? SAVER_SYSTEMD_PROGRAM :
+ "unknown"));
+ continue;
+ }
+
+ if (WIFSIGNALED (wait_status))
+ {
+ if (WTERMSIG (wait_status) == SIGTERM)
+ strcpy (how, "with SIGTERM");
+ else
+ sprintf (how, "with signal %d", WTERMSIG (wait_status));
+ }
+ else if (WIFEXITED (wait_status))
+ {
+ int exit_status = WEXITSTATUS (wait_status);
+ /* Treat exit code as a signed 8-bit quantity. */
+ if (exit_status & 0x80) exit_status |= ~0xFF;
+ if (exit_status)
+ sprintf (how, "with status %d", exit_status);
+ else
+ strcpy (how, "normally");
+ }
else
- {
-#ifdef __GNUC__
- __extension__ /* don't warn about "string length is greater than the
- length ISO C89 compilers are required to support". */
-#endif
- fprintf (real_stderr,
- "#######################################################################\n"
- "\n"
- " If at all possible, please re-run xscreensaver with the command\n"
- " line arguments `-sync -verbose -log log.txt', and reproduce this\n"
- " bug. That will cause xscreensaver to dump a `core' file to the\n"
- " current directory. Please include the stack trace from that core\n"
- " file in your bug report. *DO NOT* mail the core file itself! That\n"
- " won't work. A \"log.txt\" file will also be written. Please *do*\n"
- " include the complete \"log.txt\" file with your bug report.\n"
- "\n"
- " https://www.jwz.org/xscreensaver/bugs.html explains how to create\n"
- " the most useful bug reports, and how to examine core files.\n"
- "\n"
- " The more information you can provide, the better. But please\n"
- " report this bug, regardless!\n"
- "\n"
- "#######################################################################\n"
- "\n"
- "\n");
+ sprintf (how, "for unknown reason %d", wait_status);
+
+ if (kid == saver_gfx_pid)
+ {
+ saver_gfx_pid = 0;
+ gfx_stopped_p = False;
+ if (blanked_p)
+ {
+ if (respawn_thrashing_count > 5)
+ {
+ /* If we have tried to re-launch this pid N times in a row
+ without unblanking, give up instead of forking it in a
+ tight loop. Screen will remain grabbed, but desktop will
+ be visible.
+ */
+ fprintf (stderr, "%s: pid %lu: " SAVER_GFX_PROGRAM
+ " won't re-launch!\n",
+ blurb(), (unsigned long) kid);
+ }
+ else
+ {
+ char *av[10];
+ int ac = 0;
+ av[ac++] = SAVER_GFX_PROGRAM;
+ av[ac++] = "--emergency";
+ if (verbose_p) av[ac++] = "--verbose";
+ if (debug_p) av[ac++] = "--debug";
+ av[ac] = 0;
+ fprintf (stderr, "%s: pid %lu: " SAVER_GFX_PROGRAM
+ " exited unexpectedly %s: re-launching\n",
+ blurb(), (unsigned long) kid, how);
+ gfx_stopped_p = False;
+ saver_gfx_pid = fork_and_exec (dpy, ac, av);
+ respawn_thrashing_count++;
+ }
+ }
+ else
+ {
+ /* Should not have been running anyway. */
+ if (verbose_p)
+ fprintf (stderr, "%s: pid %lu: " SAVER_GFX_PROGRAM
+ " exited %s\n", blurb(), (unsigned long) kid, how);
+ }
+ }
+ else if (kid == saver_systemd_pid)
+ {
+ /* xscreensaver-systemd might fail if systemd isn't running, or if
+ xscreensaver-systemd was already running, or if some other
+ program has bound to our targets. Or if it doesn't exist because
+ this system doesn't use systemd. So don't re-launch it if it
+ failed to start, or dies.
+ */
+ saver_systemd_pid = 0;
+ fprintf (stderr, "%s: pid %lu: " SAVER_SYSTEMD_PROGRAM
+ " exited unexpectedly %s\n",
+ blurb(), (unsigned long) kid, how);
+ }
+ else if (kid == saver_auth_pid)
+ {
+ saver_auth_pid = 0;
- saver_exit (si, -1, 0);
- }
+ /* xscreensaver-auth exits with status 200 to mean "ok to unlock".
+ Any other exit code, or dying with a signal, means "nope".
+ */
+ if (!WIFSIGNALED (wait_status) &&
+ WIFEXITED (wait_status))
+ {
+ unsigned int status = (unsigned int) WEXITSTATUS (wait_status);
+ if (status == 200)
+ {
+ authenticated_p = True;
+ strcpy (how, "and authenticated");
+ }
+ else if (status == 0 && !blanked_p)
+ strcpy (how, "normally"); /* This was the splash dialog */
+ else if (status == 0)
+ strcpy (how, "and authentication canceled");
+ else if (status == 255) /* which is -1 */
+ strcpy (how, "and authentication failed");
+ }
+
+ if (verbose_p)
+ fprintf (stderr, "%s: pid %lu: " SAVER_AUTH_PROGRAM " exited %s\n",
+ blurb(), (unsigned long) kid, how);
+ }
+ else if (verbose_p)
+ {
+ fprintf (stderr, "%s: pid %lu: unknown child"
+ " exited unexpectedly %s\n",
+ blurb(), (unsigned long) kid, how);
+ }
}
- return 0;
+ return authenticated_p;
}
-#ifdef __GNUC__ /* Silence warning */
-static void startup_ehandler (String, String, String, String, String *,
- Cardinal *) __attribute__((noreturn));
-#endif /* __GNUC__ */
-
-/* This error handler is used only while the X connection is being set up;
- after we've got a connection, we don't use this handler again. The only
- reason for having this is so that we can present a more idiot-proof error
- message than "cannot open display."
+/* Add DEFAULT_PATH_PREFIX to the front of $PATH.
+ Typically "/usr/libexec/xscreensaver".
*/
-static void
-startup_ehandler (String name, String type, String class,
- String defalt, /* one can't even spel properly
- in this joke of a language */
- String *av, Cardinal *ac)
+static void
+hack_environment (void)
{
- char fmt[512];
- String p[10];
- saver_info *si = global_si_kludge; /* I hate C so much... */
- XrmDatabase *db = XtAppGetErrorDatabase(si->app);
- *fmt = 0;
- XtAppGetErrorDatabaseText(si->app, name, type, class, defalt,
- fmt, sizeof(fmt)-1, *db);
-
- fprintf (stderr, "%s: ", blurb());
-
- memset (p, 0, sizeof(p));
- if (*ac > countof (p)) *ac = countof (p);
- memcpy ((char *) p, (char *) av, (*ac) * sizeof(*av));
- fprintf (stderr, fmt, /* Did I mention that I hate C? */
- p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9]);
- fprintf (stderr, "\n");
-
- describe_uids (si, stderr);
-
- if (si->orig_uid && !strncmp (si->orig_uid, "root/", 5))
- {
- fprintf (stderr, "\n"
- "%s: This is probably because you're logging in as root. You\n"
-" shouldn't log in as root: you should log in as a normal user,\n"
-" and then `su' as needed. If you insist on logging in as\n"
-" root, you will have to turn off X's security features before\n"
-" xscreensaver will work.\n"
- "\n"
-" Please read the manual and FAQ for more information:\n",
- blurb());
- }
- else
+ static const char *def_path = DEFAULT_PATH_PREFIX;
+ const char *opath = getenv("PATH");
+ char *npath;
+ if (! opath) opath = "/bin:/usr/bin"; /* WTF */
+ npath = (char *) malloc(strlen(def_path) + strlen(opath) + 20);
+ strcpy (npath, "PATH=");
+ strcat (npath, def_path);
+ strcat (npath, ":");
+ strcat (npath, opath);
+
+ /* Can fail if out of memory, I guess. Ignore errors. */
+ putenv (npath);
+
+ /* don't free (npath) -- some implementations of putenv (BSD 4.4,
+ glibc 2.0) copy the argument, but some (libc4,5, glibc 2.1.2)
+ do not. So we must leak it (and/or the previous setting). Yay.
+ */
+}
+
+
+static void
+print_banner(void)
+{
+ const time_t rel = XSCREENSAVER_RELEASED;
+ struct tm *tm = localtime (&rel);
+ char buf[100];
+ int months = (time ((time_t *) 0) - XSCREENSAVER_RELEASED) / (60*60*24*30);
+ int years = months / 12.0 + 0.7;
+
+ strftime (buf, sizeof(buf), "%b %Y", tm);
+
+ if (months > 18)
+ sprintf (buf + strlen(buf), " -- %d years ago", years);
+ else if (months > 1)
+ sprintf (buf + strlen(buf), " -- %d months ago", months);
+
+ if (verbose_p || debug_p)
{
- fprintf (stderr, "\n"
- "%s: Errors at startup are usually authorization problems.\n"
-" But you're not logging in as root (good!) so something\n"
-" else must be wrong. Did you read the manual and the FAQ?\n",
- blurb());
+ fprintf (stderr,
+ "\tXScreenSaver " XSCREENSAVER_VERSION ", released %s\n"
+ "\tCopyright \302\251 1991-%d by"
+ " Jamie Zawinski <jwz@jwz.org>\n\n",
+ buf, tm->tm_year + 1900);
+ if (months > 18)
+ fprintf (stderr,
+ /* Hey jerks, the only time someone will see this particular
+ message is if they are running xscreensaver with '-log' in
+ order to send me a bug report, and they had damned well
+ better try the latest release before they do that. */
+ "\t ###################################################\n"
+ "\t ### ###\n"
+ "\t ### THIS VERSION IS VERY OLD! PLEASE UPGRADE! ###\n"
+ "\t ### ###\n"
+ "\t ###################################################\n"
+ "\n");
}
- fprintf (stderr, "\n"
- " https://www.jwz.org/xscreensaver/faq.html\n"
- " https://www.jwz.org/xscreensaver/man.html\n"
- "\n");
+ if (debug_p)
+ fprintf (stderr,
+ "#####################################"
+ "#####################################\n"
+ "\n"
+ "\t\t\t DEBUG MODE IS NOT SECURE\n"
+ "\n"
+ "\tThe XScreenSaver window will only cover the left half of\n"
+ "\tthe screen. Position your terminal window on the right.\n"
+ "\tWARNING: stderr and the log file will include every\n"
+ "\tcharacter that you type, including passwords.\n"
+ "\n"
+ "#####################################"
+ "#####################################\n"
+ "\n");
- fflush (stderr);
- fflush (stdout);
- exit (1);
+ version_number = XSCREENSAVER_VERSION;
}
-
-/* The zillions of initializations.
- */
-/* Set progname, version, etc. This is done very early.
- */
static void
-set_version_string (saver_info *si, int *argc, char **argv)
+fix_fds (void)
{
- progclass = "XScreenSaver";
+ /* Bad Things Happen if stdin, stdout, and stderr have been closed
+ (as by the `sh incantation "xscreensaver >&- 2>&-"). When you do
+ that, the X connection gets allocated to one of these fds, and
+ then some random library writes to stderr, and random bits get
+ stuffed down the X pipe, causing "Xlib: sequence lost" errors.
+ So, we cause the first three file descriptors to be open to
+ /dev/null if they aren't open to something else already. This
+ must be done before any other files are opened (or the closing
+ of that other file will again free up one of the "magic" first
+ three FDs.)
- /* progname is reset later, after we connect to X. */
- progname = strrchr(argv[0], '/');
- if (progname) progname++;
- else progname = argv[0];
+ We do this by opening /dev/null three times, and then closing
+ those fds, *unless* any of them got allocated as #0, #1, or #2,
+ in which case we leave them open. Gag.
- if (strlen(progname) > 100) /* keep it short. */
- progname[99] = 0;
+ Really, this crap is technically required of *every* X program,
+ if you want it to be robust in the face of "2>&-".
+ */
+ int fd0 = open ("/dev/null", O_RDWR);
+ int fd1 = open ("/dev/null", O_RDWR);
+ int fd2 = open ("/dev/null", O_RDWR);
+ if (fd0 > 2) close (fd0);
+ if (fd1 > 2) close (fd1);
+ if (fd2 > 2) close (fd2);
+}
- /* The X resource database blows up if argv[0] has a "." in it. */
- {
- char *s = argv[0];
- while ((s = strchr (s, '.')))
- *s = '_';
- }
- si->version = (char *) malloc (32);
- memcpy (si->version, screensaver_id + 17, 4);
- si->version [4] = 0;
+/* Mostly duplicated in resources.c.
+ */
+static int
+parse_time (const char *string)
+{
+ unsigned int h, m, s;
+ char c;
+ if (3 == sscanf (string, " %u : %2u : %2u %c", &h, &m, &s, &c))
+ ;
+ else if (2 == sscanf (string, " : %2u : %2u %c", &m, &s, &c) ||
+ 2 == sscanf (string, " %u : %2u %c", &m, &s, &c))
+ h = 0;
+ else if (1 == sscanf (string, " : %2u %c", &s, &c))
+ h = m = 0;
+ else if (1 == sscanf (string, " %u %c", &m, &c))
+ h = s = 0;
+ else
+ {
+ fprintf (stderr, "%s: unparsable duration \"%s\"\n", blurb(), string);
+ return -1;
+ }
+ if (s >= 60 && (h != 0 || m != 0))
+ {
+ fprintf (stderr, "%s: seconds > 59 in \"%s\"\n", blurb(), string);
+ return -1;
+ }
+ if (m >= 60 && h > 0)
+ {
+ fprintf (stderr, "%s: minutes > 59 in \"%s\"\n", blurb(), string);
+ return -1;
+ }
+ return ((h * 60 * 60) + (m * 60) + s);
}
-/* Initializations that potentially take place as a priveleged user:
- If the xscreensaver executable is setuid root, then these initializations
- are run as root, before discarding privileges.
+/* This program only needs a very few options 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
-privileged_initialization (saver_info *si, int *argc, char **argv)
+static void init_line_handler (int lineno,
+ const char *key, const char *val,
+ void *closure)
{
-#ifndef NO_LOCKING
- /* before hack_uid() for proper permissions */
- lock_priv_init (*argc, argv, si->prefs.verbose_p);
-#endif /* NO_LOCKING */
+ if (!strcmp (key, "verbose")) verbose_p = !strcasecmp (val, "true");
+ else if (!strcmp (key, "splash")) splash_p = !strcasecmp (val, "true");
+ else if (!strcmp (key, "lock")) lock_p = !strcasecmp (val, "true");
+ else if (!strcmp (key, "timeout"))
+ {
+ int t = parse_time (val);
+ if (t > 0) blank_timeout = t;
+ }
+ if (!strcmp (key, "lockTimeout"))
+ {
+ int t = parse_time (val);
+ if (t >= 0) lock_timeout = t;
+ }
+ if (!strcmp (key, "pointerHysteresis"))
+ {
+ int i = atoi (val);
+ if (i >= 0)
+ pointer_hysteresis = i;
+ }
+}
- hack_uid (si);
+static void
+read_init_file_simple (const char *filename)
+{
+ if (debug_p)
+ fprintf (stderr, "%s: reading %s\n", blurb(), filename);
+ parse_init_file (filename, init_line_handler, 0);
}
-/* Figure out what locking mechanisms are supported.
- */
+static time_t init_file_time = 0;
+
static void
-lock_initialization (saver_info *si, int *argc, char **argv)
+read_init_files (Bool both_p)
{
-#ifdef NO_LOCKING
- si->locking_disabled_p = True;
- si->nolock_reason = "not compiled with locking support";
-#else /* !NO_LOCKING */
+ static const char *home = 0;
+ char *fn;
+ struct stat st;
+ Bool read_p = False;
- /* Finish initializing locking, now that we're out of privileged code. */
- if (! lock_init (*argc, argv, si->prefs.verbose_p))
+ if (!home)
{
- si->locking_disabled_p = True;
- si->nolock_reason = "error getting password";
+ home = getenv("HOME");
+ if (!home) home = "";
}
- /* If locking is currently enabled, but the environment indicates that
- we have been launched as GDM's "Background" program, then disable
- locking just in case.
- */
- if (!si->locking_disabled_p && getenv ("RUNNING_UNDER_GDM"))
+ if (both_p)
{
- si->locking_disabled_p = True;
- si->nolock_reason = "running under GDM";
+ read_init_file_simple (AD_DIR "/XScreenSaver");
+ read_p = True;
}
- /* If the server is XDarwin (MacOS X) then disable locking.
- (X grabs only affect X programs, so you can use Command-Tab
- to bring any other Mac program to the front, e.g., Terminal.)
- */
- if (!si->locking_disabled_p)
- {
- int op = 0, event = 0, error = 0;
- Bool macos_p = False;
-
-#ifdef __APPLE__
- /* Disable locking if *running* on Apple hardware, since we have no
- reliable way to determine whether the server is running on MacOS.
- Hopefully __APPLE__ means "MacOS" and not "Linux on Mac hardware"
- but I'm not really sure about that.
- */
- macos_p = True;
-#endif
-
- if (!macos_p)
- /* This extension exists on the Apple X11 server, but not
- on earlier versions of the XDarwin server. */
- macos_p = XQueryExtension (si->dpy, "Apple-DRI", &op, &event, &error);
+ fn = (char *) malloc (strlen(home) + 40);
+ sprintf (fn, "%s/.xscreensaver", home);
- if (macos_p)
+ if (!stat (fn, &st))
+ {
+ if (both_p || st.st_mtime != init_file_time)
{
- si->locking_disabled_p = True;
- si->nolock_reason = "Cannot lock securely on MacOS X";
+ Bool ov = verbose_p;
+ init_file_time = st.st_mtime;
+ read_init_file_simple (fn);
+ read_p = True;
+
+ /* Changes to verbose in .xscreenaver after startup are ignored; else
+ running xscreensaver-settings would turn off cmd line -verbose. */
+ if (!both_p) verbose_p = ov;
}
}
- /* Like MacOS, locking under Wayland's embedded X11 server does not work.
- (X11 grabs don't work because the Wayland window manager lives at a
- higher level than the X11 emulation layer.)
- */
- if (!si->locking_disabled_p && getenv ("WAYLAND_DISPLAY"))
+ if (blank_timeout < 5)
+ blank_timeout = 60 * 60 * 10;
+
+ free (fn);
+
+ if (read_p && verbose_p)
{
- si->locking_disabled_p = True;
- si->nolock_reason = "Cannot lock securely under Wayland";
+ fprintf (stderr, "%s: blank after: %d\n", blurb(), blank_timeout);
+ if (lock_p)
+ fprintf (stderr, "%s: lock after: %d\n", blurb(), lock_timeout);
}
+}
- if (si->prefs.debug_p) /* But allow locking anyway in debug mode. */
- si->locking_disabled_p = False;
-#endif /* NO_LOCKING */
-}
-/* Open the connection to the X server, and intern our Atoms.
+/* We trap and ignore ALL protocol errors that happen after initialization.
+ By default, Xlib would exit. Ignoring an X error is less bad than
+ crashing and unlocking.
*/
-static Widget
-connect_to_server (saver_info *si, int *argc, char **argv)
+static Bool ignore_x11_error_p = False;
+static Bool error_handler_hit_p = False;
+static Bool print_x11_error_p = True;
+
+static int
+error_handler (Display *dpy, XErrorEvent *event)
{
- Widget toplevel_shell;
+ error_handler_hit_p = True;
-#ifdef HAVE_PUTENV
- char *d = getenv ("DISPLAY");
- if (!d || !*d)
+ if (print_x11_error_p)
{
- char *ndpy = strdup("DISPLAY=:0.0");
- /* if (si->prefs.verbose_p) */ /* sigh, too early to test this... */
- fprintf (stderr,
- "%s: warning: $DISPLAY is not set: defaulting to \"%s\".\n",
- blurb(), ndpy+8);
- if (putenv (ndpy))
- abort ();
- /* don't free (ndpy) -- some implementations of putenv (BSD 4.4,
- glibc 2.0) copy the argument, but some (libc4,5, glibc 2.1.2)
- do not. So we must leak it (and/or the previous setting). Yay.
- */
+ const char *b = blurb();
+ const char *p = progname;
+ fprintf (stderr, "\n%s: X ERROR! PLEASE REPORT THIS BUG!\n\n", b);
+ progname = b;
+ XmuPrintDefaultErrorMessage (dpy, event, stderr);
+ progname = p;
+
+# ifdef __GNUC__
+ __extension__ /* don't warn about "string length is greater than the
+ length ISO C89 compilers are required to support". */
+# endif
+ fprintf (stderr, "\n"
+ "#######################################################################\n"
+ "\n"
+ " If at all possible, please re-run xscreensaver with the command\n"
+ " line arguments \"-sync -log log.txt\", and reproduce this bug.\n"
+ " Please include the complete \"log.txt\" file with your bug report.\n"
+ "\n"
+ " https://www.jwz.org/xscreensaver/bugs.html explains how to create\n"
+ " the most useful bug reports.\n"
+ "\n"
+ " The more information you can provide, the better. But please\n"
+ " report this bug, regardless!\n"
+ "\n"
+ "#######################################################################\n"
+ "\n"
+ "\n");
+
+ fflush (stderr);
+
+ if (! ignore_x11_error_p)
+ abort();
}
-#endif /* HAVE_PUTENV */
-
- XSetErrorHandler (saver_ehandler);
-
- XtAppSetErrorMsgHandler (si->app, startup_ehandler);
- toplevel_shell = XtAppInitialize (&si->app, progclass,
- options, XtNumber (options),
- argc, argv, defaults, 0, 0);
- XtAppSetErrorMsgHandler (si->app, 0);
-
- si->dpy = XtDisplay (toplevel_shell);
- si->prefs.db = XtDatabase (si->dpy);
- XtGetApplicationNameAndClass (si->dpy, &progname, &progclass);
-
- if(strlen(progname) > 100) /* keep it short. */
- progname [99] = 0;
-
- db = si->prefs.db; /* resources.c needs this */
-
- XA_VROOT = XInternAtom (si->dpy, "__SWM_VROOT", False);
- XA_SCREENSAVER = XInternAtom (si->dpy, "SCREENSAVER", False);
- XA_SCREENSAVER_VERSION = XInternAtom (si->dpy, "_SCREENSAVER_VERSION",False);
- XA_SCREENSAVER_ID = XInternAtom (si->dpy, "_SCREENSAVER_ID", False);
- XA_SCREENSAVER_STATUS = XInternAtom (si->dpy, "_SCREENSAVER_STATUS", False);
- XA_SCREENSAVER_RESPONSE = XInternAtom (si->dpy, "_SCREENSAVER_RESPONSE",
- False);
- XA_XSETROOT_ID = XInternAtom (si->dpy, "_XSETROOT_ID", False);
- XA_ESETROOT_PMAP_ID = XInternAtom (si->dpy, "ESETROOT_PMAP_ID", False);
- XA_XROOTPMAP_ID = XInternAtom (si->dpy, "_XROOTPMAP_ID", False);
- XA_NET_WM_USER_TIME = XInternAtom (si->dpy, "_NET_WM_USER_TIME", False);
- XA_ACTIVATE = XInternAtom (si->dpy, "ACTIVATE", False);
- XA_DEACTIVATE = XInternAtom (si->dpy, "DEACTIVATE", False);
- XA_SUSPEND = XInternAtom (si->dpy, "SUSPEND", False);
- XA_RESTART = XInternAtom (si->dpy, "RESTART", False);
- XA_CYCLE = XInternAtom (si->dpy, "CYCLE", False);
- XA_NEXT = XInternAtom (si->dpy, "NEXT", False);
- XA_PREV = XInternAtom (si->dpy, "PREV", False);
- XA_SELECT = XInternAtom (si->dpy, "SELECT", False);
- XA_EXIT = XInternAtom (si->dpy, "EXIT", False);
- XA_DEMO = XInternAtom (si->dpy, "DEMO", False);
- XA_PREFS = XInternAtom (si->dpy, "PREFS", False);
- XA_LOCK = XInternAtom (si->dpy, "LOCK", False);
- XA_BLANK = XInternAtom (si->dpy, "BLANK", False);
- XA_THROTTLE = XInternAtom (si->dpy, "THROTTLE", False);
- XA_UNTHROTTLE = XInternAtom (si->dpy, "UNTHROTTLE", False);
-
- return toplevel_shell;
+
+ return 0;
}
-/* Handle the command-line arguments that were not handled for us by Xt.
- Issue an error message and exit if there are unknown options.
+/* Check for other running instances of XScreenSaver, gnome-screensaver, etc.
*/
static void
-process_command_line (saver_info *si, int *argc, char **argv)
+ensure_no_screensaver_running (Display *dpy)
{
- int i;
- for (i = 1; i < *argc; i++)
+ int screen, nscreens = ScreenCount (dpy);
+
+ /* Silently ignore BadWindow race conditions. */
+ Bool op = print_x11_error_p;
+ print_x11_error_p = False;
+
+ for (screen = 0; screen < nscreens; screen++)
{
- if (!strcmp (argv[i], "-debug"))
- /* no resource for this one, out of paranoia. */
- si->prefs.debug_p = True;
+ int i;
+ Window root = RootWindow (dpy, screen);
+ Window root2 = 0, parent = 0, *kids = 0;
+ unsigned int nkids = 0;
- else if (!strcmp (argv[i], "-h") ||
- !strcmp (argv[i], "-help") ||
- !strcmp (argv[i], "--help"))
- do_help (si);
+ if (! XQueryTree (dpy, root, &root2, &parent, &kids, &nkids))
+ continue;
+ if (root != root2)
+ continue;
+ if (parent)
+ continue;
+ for (i = 0; i < nkids; i++)
+ {
+ Atom type;
+ int format;
+ unsigned long nitems, bytesafter;
+ unsigned char *version;
+
+ if (XGetWindowProperty (dpy, kids[i], XA_SCREENSAVER_VERSION, 0, 1,
+ False, XA_STRING, &type, &format, &nitems,
+ &bytesafter, &version)
+ == Success
+ && type != None)
+ {
+ unsigned char *id;
+ if (XGetWindowProperty (dpy, kids[i], XA_SCREENSAVER_ID, 0, 512,
+ False, XA_STRING, &type, &format,
+ &nitems, &bytesafter, &id)
+ != Success
+ || type == None)
+ id = (unsigned char *) "???";
- else
- {
- const char *s = argv[i];
- fprintf (stderr, "%s: unknown option \"%s\". Try \"-help\".\n",
- blurb(), s);
-
- if (s[0] == '-' && s[1] == '-') s++;
- if (!strcmp (s, "-activate") ||
- !strcmp (s, "-deactivate") ||
- !strcmp (s, "-cycle") ||
- !strcmp (s, "-next") ||
- !strcmp (s, "-prev") ||
- !strcmp (s, "-exit") ||
- !strcmp (s, "-restart") ||
- !strcmp (s, "-demo") ||
- !strcmp (s, "-prefs") ||
- !strcmp (s, "-preferences") ||
- !strcmp (s, "-lock") ||
- !strcmp (s, "-version") ||
- !strcmp (s, "-time"))
- {
-
- if (!strcmp (s, "-demo") || !strcmp (s, "-prefs"))
- fprintf (stderr, "\n\
- Perhaps you meant to run the `xscreensaver-demo' program instead?\n");
- else
- fprintf (stderr, "\n\
- However, `%s' is an option to the `xscreensaver-command' program.\n", s);
-
- fprintf (stderr, "\
- The `xscreensaver' program is a daemon that runs in the background.\n\
- You control a running xscreensaver process by sending it messages\n\
- with `xscreensaver-demo' or `xscreensaver-command'.\n\
-. See the man pages for details, or check the web page:\n\
- https://www.jwz.org/xscreensaver/\n\n");
- }
-
- exit (1);
- }
+ fprintf (stderr,
+ "%s: already running on display %s"
+ " (window 0x%x)\n from process %s\n",
+ blurb(), DisplayString (dpy), (int) kids [i],
+ (char *) id);
+ saver_exit (1);
+ }
+ else if (XGetWindowProperty (dpy, kids[i], XA_WM_COMMAND, 0, 128,
+ False, XA_STRING, &type, &format,
+ &nitems, &bytesafter, &version)
+ == Success
+ && type != None
+ && (!strcmp ((char *) version, "gnome-screensaver") ||
+ !strcmp ((char *) version, "mate-screensaver") ||
+ !strcmp ((char *) version, "cinnamon-screensaver")))
+ {
+ fprintf (stderr,
+ "%s: \"%s\" is already running on display %s"
+ " (window 0x%x)\n",
+ blurb(), (char *) version,
+ DisplayString (dpy), (int) kids [i]);
+ saver_exit (1);
+ }
+ }
+ if (kids) XFree ((char *) kids);
}
+
+ print_x11_error_p = op;
}
-/* Print out the xscreensaver banner to the tty if applicable;
- Issue any other warnings that are called for at this point.
+/* Store a property on the root window indicating that xscreensaver is
+ running, and whether it is blanked or locked. This property is read
+ by "xscreensaver-command" and by ensure_no_screensaver_running().
+ This property is also overwritten by "xscreensaver-gfx" to indicate
+ which screenhacks are running.
*/
static void
-print_banner (saver_info *si)
+store_saver_status (Display *dpy,
+ Bool blanked_p, Bool locked_p, time_t blank_time)
{
- saver_preferences *p = &si->prefs;
-
- char *s, year[5];
- s = strchr (screensaver_id, '-');
- s = strrchr (s, '-');
- s++;
- strncpy (year, s, 4);
- year[4] = 0;
-
- /* This resource gets set some time before the others, so that we know
- whether to print the banner (and so that the banner gets printed before
- any resource-database-related error messages.)
- */
- p->verbose_p = (p->debug_p ||
- get_boolean_resource (si->dpy, "verbose", "Boolean"));
-
- /* Ditto, for the locking_disabled_p message. */
- p->lock_p = get_boolean_resource (si->dpy, "lock", "Boolean");
+ /* The contents of XA_SCREENSAVER_STATUS has LOCK/BLANK/0 in the first slot,
+ the time at which that state began in the second slot, and the ordinal of
+ the running hacks on each screen (1-based) in subsequent slots. Since
+ we don't know the hacks here (or even how many monitors are attached) we
+ leave whatever was there before unchanged: it will be updated by
+ "xscreensaver-gfx".
- if (p->verbose_p)
- fprintf (stderr,
- "%s %s, copyright (c) 1991-%s "
- "by Jamie Zawinski <jwz@jwz.org>.\n",
- progname, si->version, year);
+ XA_SCREENSAVER_STATUS is stored on the (real) root window of screen 0.
- if (p->debug_p)
- fprintf (stderr, "\n"
- "%s: Warning: running in DEBUG MODE. Be afraid.\n"
- "\n"
- "\tNote that in debug mode, the xscreensaver window will only\n"
- "\tcover the left half of the screen. (The idea is that you\n"
- "\tcan still see debugging output in a shell, if you position\n"
- "\tit on the right side of the screen.)\n"
- "\n"
- "\tDebug mode is NOT SECURE. Do not run with -debug in\n"
- "\tuntrusted environments.\n"
- "\n",
- blurb());
-
- if (p->verbose_p && senesculent_p ())
- fprintf (stderr, "\n"
- "*************************************"
- "**************************************\n"
- "%s: Warning: this version of xscreensaver is VERY OLD!\n"
- "%s: Please upgrade! https://www.jwz.org/xscreensaver/\n"
- "*************************************"
- "**************************************\n"
- "\n",
- blurb(), blurb());
-
- if (p->verbose_p)
- {
- if (!si->uid_message || !*si->uid_message)
- describe_uids (si, stderr);
- else
- {
- if (si->orig_uid && *si->orig_uid)
- fprintf (stderr, "%s: initial effective uid/gid was %s.\n",
- blurb(), si->orig_uid);
- fprintf (stderr, "%s: %s\n", blurb(), si->uid_message);
- }
-
- fprintf (stderr, "%s: in process %lu.\n", blurb(),
- (unsigned long) getpid());
- }
-}
+ XA_SCREENSAVER_VERSION and XA_SCREENSAVER_ID are stored on the unmapped
+ window created by the "xscreensaver" process. ClientMessage events are
+ sent to that window, and the responses are sent via the
+ XA_SCREENSAVER_RESPONSE property on it.
-static void
-print_lock_failure_banner (saver_info *si)
-{
- saver_preferences *p = &si->prefs;
+ These properties are not used on the windows created by "xscreensaver-gfx"
+ for use by the display hacks.
- /* If locking was not able to be initalized for some reason, explain why.
- (This has to be done after we've read the lock_p resource.)
+ See the different version of this function in windows.c.
*/
- if (si->locking_disabled_p)
+ Window w = RootWindow (dpy, 0); /* always screen 0 */
+ Atom type;
+ unsigned char *dataP = 0;
+ PROP32 *status = 0;
+ int format;
+ unsigned long nitems, bytesafter;
+
+ /* Read the old property, so we can change just parts. */
+ if (XGetWindowProperty (dpy, w,
+ XA_SCREENSAVER_STATUS,
+ 0, 999, False, XA_INTEGER,
+ &type, &format, &nitems, &bytesafter,
+ &dataP)
+ == Success
+ && type == XA_INTEGER
+ && nitems >= 3
+ && dataP)
+ status = (PROP32 *) dataP;
+
+ if (!status) /* There was no existing property */
{
- p->lock_p = False;
- fprintf (stderr, "%s: locking is disabled (%s).\n", blurb(),
- si->nolock_reason);
- if (strstr (si->nolock_reason, "passw"))
- fprintf (stderr, "%s: does xscreensaver need to be setuid? "
- "consult the manual.\n", blurb());
- else if (strstr (si->nolock_reason, "running as "))
- fprintf (stderr,
- "%s: locking only works when xscreensaver is launched\n"
- "\t by a normal, non-privileged user (e.g., not \"root\".)\n"
- "\t See the manual for details.\n",
- blurb());
+ nitems = 3;
+ status = (PROP32 *) malloc (nitems * sizeof(*status));
}
-}
+ status[0] = (PROP32) (locked_p ? XA_LOCK : blanked_p ? XA_BLANK : 0);
+ status[1] = (PROP32) blank_time; /* Y2038 bug: unsigned 32 bit time_t */
+ XChangeProperty (dpy, w, XA_SCREENSAVER_STATUS, XA_INTEGER, 32,
+ PropModeReplace, (unsigned char *) status, nitems);
+ XSync (dpy, False);
+# if 0
+ if (debug_p && verbose_p)
+ {
+ int i;
+ fprintf (stderr, "%s: wrote status property: 0x%lx: %s", blurb(),
+ (unsigned long) w,
+ (status[0] == XA_LOCK ? "LOCK" :
+ status[0] == XA_BLANK ? "BLANK" :
+ status[0] == 0 ? "0" : "???"));
+ for (i = 1; i < nitems; i++)
+ fprintf (stderr, ", %lu", status[i]);
+ fprintf (stderr, "\n");
+ if (system ("xprop -root _SCREENSAVER_STATUS") <= 0)
+ fprintf (stderr, "%s: xprop exec failed\n", blurb());
+ }
+# endif /* 0 */
-/* called from screens.c so that all the Xt crud is here. */
-void
-initialize_screen_root_widget (saver_screen_info *ssi)
-{
- saver_info *si = ssi->global;
- if (ssi->toplevel_shell)
- XtDestroyWidget (ssi->toplevel_shell);
- ssi->toplevel_shell =
- XtVaAppCreateShell (progname, progclass,
- applicationShellWidgetClass,
- si->dpy,
- XtNscreen, ssi->screen,
- XtNvisual, ssi->current_visual,
- XtNdepth, visual_depth (ssi->screen,
- ssi->current_visual),
- NULL);
+ if (status != (PROP32 *) dataP)
+ free (status);
+ if (dataP)
+ XFree (dataP);
}
-/* Examine all of the display's screens, and populate the `saver_screen_info'
- structures. Make sure this is called after hack_environment() sets $PATH.
+/* This process does not map any windows on the screen. However, it creates
+ one hidden window on screen 0, which is the rendezvous point for
+ communication with xscreensaver-command: that window is how it can tell
+ that XScreenSaver is running, what the version number is, and it is where
+ bidirectional ClientMessage communication takes place. Since there are
+ no "blanking" windows around at all when xscreensaver-gfx is not running,
+ this window is needed. We could have instead re-tooled xscreensaver-command
+ to do all of its communication through the root window instead, but this
+ seemed easier.
*/
static void
-initialize_per_screen_info (saver_info *si, Widget toplevel_shell)
+create_daemon_window (Display *dpy)
{
- int i;
-
- update_screen_layout (si);
+ XClassHint class_hints;
+ XSetWindowAttributes attrs;
+ unsigned long attrmask = 0;
+ const char *name = "???";
+ const char *host = "???";
+ char buf[20];
+ pid_t pid = getpid();
+ struct passwd *p = getpwuid (getuid ());
+ time_t now = time ((time_t *) 0);
+ char *id;
+# ifdef HAVE_UNAME
+ struct utsname uts;
+# endif
- /* Check to see whether fading is ever possible -- if any of the
- screens on the display has a PseudoColor visual, then fading can
- work (on at least some screens.) If no screen has a PseudoColor
- visual, then don't bother ever trying to fade, because it will
- just cause a delay without causing any visible effect.
- */
- for (i = 0; i < si->nscreens; i++)
+ if (p && p->pw_name && *p->pw_name)
+ name = p->pw_name;
+ else if (p)
{
- saver_screen_info *ssi = &si->screens[i];
- if (has_writable_cells (ssi->screen, ssi->current_visual) ||
- get_visual (ssi->screen, "PseudoColor", True, False) ||
- get_visual (ssi->screen, "GrayScale", True, False))
- {
- si->fading_possible_p = True;
- break;
- }
+ sprintf (buf, "%lu", (unsigned long) p->pw_uid);
+ name = buf;
}
+ else
+ name = "???";
-#ifdef HAVE_XF86VMODE_GAMMA
- si->fading_possible_p = True; /* if we can gamma fade, go for it */
-#endif
-}
-
-
-/* If any server extensions have been requested, try and initialize them.
- Issue warnings if requests can't be honored.
- */
-static void
-initialize_server_extensions (saver_info *si)
-{
- saver_preferences *p = &si->prefs;
-
- Bool server_has_xidle_extension_p = False;
- Bool server_has_sgi_saver_extension_p = False;
- Bool server_has_mit_saver_extension_p = False;
- Bool system_has_proc_interrupts_p = False;
- Bool server_has_xinput_extension_p = False;
- const char *piwhy = 0;
-
- si->using_xidle_extension = p->use_xidle_extension;
- si->using_sgi_saver_extension = p->use_sgi_saver_extension;
- si->using_mit_saver_extension = p->use_mit_saver_extension;
- si->using_proc_interrupts = p->use_proc_interrupts;
- si->using_xinput_extension = p->use_xinput_extension;
-
-#ifdef HAVE_XIDLE_EXTENSION
+# ifdef HAVE_UNAME
{
- int ev, er;
- server_has_xidle_extension_p = XidleQueryExtension (si->dpy, &ev, &er);
+ if (! uname (&uts))
+ host = uts.nodename;
}
-#endif
-#ifdef HAVE_SGI_SAVER_EXTENSION
- server_has_sgi_saver_extension_p =
- XScreenSaverQueryExtension (si->dpy,
- &si->sgi_saver_ext_event_number,
- &si->sgi_saver_ext_error_number);
-#endif
-#ifdef HAVE_MIT_SAVER_EXTENSION
- server_has_mit_saver_extension_p =
- XScreenSaverQueryExtension (si->dpy,
- &si->mit_saver_ext_event_number,
- &si->mit_saver_ext_error_number);
-#endif
-#ifdef HAVE_PROC_INTERRUPTS
- system_has_proc_interrupts_p = query_proc_interrupts_available (si, &piwhy);
-#endif
-
-#ifdef HAVE_XINPUT
- server_has_xinput_extension_p = query_xinput_extension (si);
-#endif
+# endif
- if (!server_has_xidle_extension_p)
- si->using_xidle_extension = False;
- else if (p->verbose_p)
- {
- if (si->using_xidle_extension)
- fprintf (stderr, "%s: using XIDLE extension.\n", blurb());
- else
- fprintf (stderr, "%s: not using server's XIDLE extension.\n", blurb());
- }
+ class_hints.res_name = (char *) progname; /* not const? */
+ class_hints.res_class = "XScreenSaver";
+ id = (char *) malloc (strlen(name) + strlen(host) + 50);
+ sprintf (id, "%lu (%s@%s)", (unsigned long) pid, name, host);
+
+ attrmask = CWOverrideRedirect | CWEventMask;
+ attrs.override_redirect = True;
+ attrs.event_mask = PropertyChangeMask;
+
+ daemon_window = XCreateWindow (dpy, RootWindow (dpy, 0),
+ 0, 0, 1, 1, 0,
+ DefaultDepth (dpy, 0), InputOutput,
+ DefaultVisual (dpy, 0), attrmask, &attrs);
+ XStoreName (dpy, daemon_window, "XScreenSaver Daemon");
+ XSetClassHint (dpy, daemon_window, &class_hints);
+ XChangeProperty (dpy, daemon_window, XA_WM_COMMAND, XA_STRING,
+ 8, PropModeReplace, (unsigned char *) progname,
+ strlen (progname));
+ XChangeProperty (dpy, daemon_window, XA_SCREENSAVER_VERSION, XA_STRING,
+ 8, PropModeReplace, (unsigned char *) version_number,
+ strlen (version_number));
+ XChangeProperty (dpy, daemon_window, XA_SCREENSAVER_ID, XA_STRING,
+ 8, PropModeReplace, (unsigned char *) id, strlen (id));
+
+ store_saver_status (dpy, False, False, now);
+ free (id);
+}
- if (!server_has_sgi_saver_extension_p)
- si->using_sgi_saver_extension = False;
- else if (p->verbose_p)
- {
- if (si->using_sgi_saver_extension)
- fprintf (stderr, "%s: using SGI SCREEN_SAVER extension.\n", blurb());
- else
- fprintf (stderr,
- "%s: not using server's SGI SCREEN_SAVER extension.\n",
- blurb());
- }
- if (!server_has_mit_saver_extension_p)
- si->using_mit_saver_extension = False;
- else if (p->verbose_p)
+static const char *
+grab_string (int status)
+{
+ switch (status) {
+ case GrabSuccess: return "GrabSuccess";
+ case AlreadyGrabbed: return "AlreadyGrabbed";
+ case GrabInvalidTime: return "GrabInvalidTime";
+ case GrabNotViewable: return "GrabNotViewable";
+ case GrabFrozen: return "GrabFrozen";
+ default:
{
- if (si->using_mit_saver_extension)
- fprintf (stderr, "%s: using lame MIT-SCREEN-SAVER extension.\n",
- blurb());
- else
- fprintf (stderr,
- "%s: not using server's lame MIT-SCREEN-SAVER extension.\n",
- blurb());
+ static char buf[255];
+ sprintf(buf, "unknown status: %d", status);
+ return buf;
}
+ }
+}
-#ifdef HAVE_RANDR
- if (XRRQueryExtension (si->dpy,
- &si->randr_event_number, &si->randr_error_number))
- {
- int nscreens = ScreenCount (si->dpy); /* number of *real* screens */
- int i;
+static int
+grab_kbd (Screen *screen)
+{
+ Display *dpy = DisplayOfScreen (screen);
+ Window w = RootWindowOfScreen (screen);
+ int status = XGrabKeyboard (dpy, w, True, GrabModeAsync, GrabModeAsync,
+ CurrentTime);
+ if (verbose_p)
+ fprintf (stderr, "%s: grabbing keyboard on 0x%lx: %s\n",
+ blurb(), (unsigned long) w, grab_string (status));
+ return status;
+}
- si->using_randr_extension = TRUE;
-
- if (p->verbose_p)
- fprintf (stderr, "%s: selecting RANDR events\n", blurb());
- for (i = 0; i < nscreens; i++)
-# ifdef RRScreenChangeNotifyMask /* randr.h 1.5, 2002/09/29 */
- XRRSelectInput (si->dpy, RootWindow (si->dpy, i),
- RRScreenChangeNotifyMask);
-# else /* !RRScreenChangeNotifyMask */ /* Xrandr.h 1.4, 2001/06/07 */
- XRRScreenChangeSelectInput (si->dpy, RootWindow (si->dpy, i), True);
-# endif /* !RRScreenChangeNotifyMask */
- }
-# endif /* HAVE_RANDR */
-#ifdef HAVE_XINPUT
- if (!server_has_xinput_extension_p)
- si->using_xinput_extension = False;
- else
- {
- if (si->using_xinput_extension)
- init_xinput_extension(si);
+static int
+grab_mouse (Screen *screen, Cursor cursor)
+{
+ Display *dpy = DisplayOfScreen (screen);
+ Window w = RootWindowOfScreen (screen);
+ int status = XGrabPointer (dpy, w, True,
+ (ButtonPressMask | ButtonReleaseMask |
+ EnterWindowMask | LeaveWindowMask |
+ PointerMotionMask | PointerMotionHintMask |
+ Button1MotionMask | Button2MotionMask |
+ Button3MotionMask | Button4MotionMask |
+ Button5MotionMask | ButtonMotionMask),
+ GrabModeAsync, GrabModeAsync, w,
+ cursor, CurrentTime);
+ if (verbose_p)
+ fprintf (stderr, "%s: grabbing mouse on 0x%lx... %s\n",
+ blurb(), (unsigned long) w, grab_string (status));
+ return status;
+}
- if (p->verbose_p)
- {
- if (si->using_xinput_extension)
- fprintf (stderr,
- "%s: selecting events from %d XInputExtension devices.\n",
- blurb(), si->num_xinput_devices);
- else
- fprintf (stderr,
- "%s: not using XInputExtension.\n",
- blurb());
- }
- }
-#endif
- if (!system_has_proc_interrupts_p)
- {
- si->using_proc_interrupts = False;
- if (p->verbose_p && piwhy)
- fprintf (stderr, "%s: not using /proc/interrupts: %s.\n", blurb(),
- piwhy);
- }
- else if (p->verbose_p)
- {
- if (si->using_proc_interrupts)
- fprintf (stderr,
- "%s: consulting /proc/interrupts for keyboard activity.\n",
- blurb());
- else
- fprintf (stderr,
- "%s: not consulting /proc/interrupts for keyboard activity.\n",
- blurb());
- }
+static void
+ungrab_kbd (Display *dpy)
+{
+ if (verbose_p)
+ fprintf (stderr, "%s: ungrabbing keyboard\n", blurb());
+ XUngrabKeyboard (dpy, CurrentTime);
}
-#ifdef DEBUG_MULTISCREEN
static void
-debug_multiscreen_timer (XtPointer closure, XtIntervalId *id)
+ungrab_mouse (Display *dpy)
{
- saver_info *si = (saver_info *) closure;
- saver_preferences *p = &si->prefs;
- if (update_screen_layout (si))
- {
- if (p->verbose_p)
- {
- fprintf (stderr, "%s: new layout:\n", blurb());
- describe_monitor_layout (si);
- }
- resize_screensaver_window (si);
- }
- XtAppAddTimeOut (si->app, 1000*4, debug_multiscreen_timer, (XtPointer) si);
+ if (verbose_p)
+ fprintf (stderr, "%s: ungrabbing mouse\n", blurb());
+ XUngrabPointer (dpy, CurrentTime);
}
-#endif /* DEBUG_MULTISCREEN */
-/* For the case where we aren't using an server extensions, select user events
- on all the existing windows, and launch timers to select events on
- newly-created windows as well.
+/* Some remote desktop clients (e.g., "rdesktop") hold the keyboard GRABBED the
+ whole time they have focus! This is idiotic because the whole point of
+ grabbing is to get events when you do *not* have focus, so grabbing only
+ when* you have focus is redundant. Anyway, that prevents us from getting a
+ keyboard grab. It turns out that for some of these apps, de-focusing them
+ forces them to release their grab.
- If a server extension is being used, this does nothing.
+ So if we fail to grab the keyboard four times in a row, we forcibly set
+ focus to "None" and try four more times. We don't touch focus unless we're
+ already having a hard time getting a grab.
*/
static void
-select_events (saver_info *si)
+nuke_focus (Screen *screen)
{
- saver_preferences *p = &si->prefs;
- int i;
+ Display *dpy = DisplayOfScreen (screen);
+ Window focus = 0;
+ int rev = 0;
- if (si->using_xidle_extension ||
- si->using_mit_saver_extension ||
- si->using_sgi_saver_extension)
- return;
+ XGetInputFocus (dpy, &focus, &rev);
- if (p->initial_delay)
+ if (verbose_p)
{
- if (p->verbose_p)
- {
- fprintf (stderr, "%s: waiting for %d second%s...", blurb(),
- (int) p->initial_delay/1000,
- (p->initial_delay == 1000 ? "" : "s"));
- fflush (stderr);
- fflush (stdout);
- }
- usleep (p->initial_delay);
- if (p->verbose_p)
- fprintf (stderr, " done.\n");
- }
+ char w[255], r[255];
- if (p->verbose_p)
- {
- fprintf (stderr, "%s: selecting events on extant windows...", blurb());
- fflush (stderr);
- fflush (stdout);
- }
+ if (focus == PointerRoot) strcpy (w, "PointerRoot");
+ else if (focus == None) strcpy (w, "None");
+ else sprintf (w, "0x%lx", (unsigned long) focus);
- /* Select events on the root windows of every screen. This also selects
- for window creation events, so that new subwindows will be noticed.
- */
- for (i = 0; i < si->nscreens; i++)
- {
- saver_screen_info *ssi = &si->screens[i];
- if (ssi->real_screen_p)
- start_notice_events_timer (si,
- RootWindowOfScreen (si->screens[i].screen), False);
- }
+ if (rev == RevertToParent) strcpy (r, "RevertToParent");
+ else if (rev == RevertToPointerRoot) strcpy (r, "RevertToPointerRoot");
+ else if (rev == RevertToNone) strcpy (r, "RevertToNone");
+ else sprintf (r, "0x%x", rev);
- if (p->verbose_p)
- fprintf (stderr, " done.\n");
+ fprintf (stderr, "%s: removing focus from %s / %s\n",
+ blurb(), w, r);
+ }
-# ifdef DEBUG_MULTISCREEN
- if (p->debug_p) debug_multiscreen_timer ((XtPointer) si, 0);
-# endif
+ XSetInputFocus (dpy, None, RevertToNone, CurrentTime);
}
-void
-maybe_reload_init_file (saver_info *si)
+static void
+ungrab_keyboard_and_mouse (Display *dpy)
{
- saver_preferences *p = &si->prefs;
- if (init_file_changed_p (p))
- {
- if (p->verbose_p)
- fprintf (stderr, "%s: file \"%s\" has changed, reloading.\n",
- blurb(), init_file_name());
-
- load_init_file (si->dpy, p);
-
- /* If a server extension is in use, and p->timeout has changed,
- we need to inform the server of the new timeout. */
- disable_builtin_screensaver (si, False);
-
- /* If the DPMS settings in the init file have changed,
- change the settings on the server to match. */
- sync_server_dpms_settings (si->dpy,
- (p->dpms_enabled_p &&
- p->mode != DONT_BLANK),
- p->dpms_quickoff_p,
- p->dpms_standby / 1000,
- p->dpms_suspend / 1000,
- p->dpms_off / 1000,
- False);
- }
+ ungrab_mouse (dpy);
+ ungrab_kbd (dpy);
}
-/* Loop forever:
-
- - wait until the user is idle;
- - blank the screen;
- - wait until the user is active;
- - unblank the screen;
- - repeat.
-
+/* Returns true if it succeeds.
*/
-static void
-main_loop (saver_info *si)
+static Bool
+grab_keyboard_and_mouse (Screen *screen)
{
- saver_preferences *p = &si->prefs;
- Bool ok_to_unblank;
+ Display *dpy = DisplayOfScreen (screen);
+ Status mstatus = 0, kstatus = 0;
int i;
+ int retries = 4;
+ Bool focus_fuckus = False;
- while (1)
+ AGAIN:
+
+ for (i = 0; i < retries; i++)
{
- Bool was_locked = False;
-
- if (p->verbose_p)
- fprintf (stderr, "%s: awaiting idleness.\n", blurb());
-
- check_for_leaks ("unblanked A");
- sleep_until_idle (si, True);
- check_for_leaks ("unblanked B");
-
- if (p->verbose_p)
- {
- if (si->demoing_p)
- fprintf (stderr, "%s: demoing %d at %s.\n", blurb(),
- si->selection_mode, timestring(0));
- else
- fprintf (stderr, "%s: blanking screen at %s.\n", blurb(),
- timestring(0));
- }
-
- maybe_reload_init_file (si);
-
- /* Treat DONT_BLANK as BLANK_ONLY in emergency-lock when locking
- is enabled. */
-
- if (p->mode == DONT_BLANK &&
- (!si->emergency_lock_p ||
- !p->lock_p ||
- si->locking_disabled_p))
- {
- if (p->verbose_p)
- fprintf (stderr, "%s: idle with blanking disabled at %s.\n",
- blurb(), timestring(0));
+ XSync (dpy, False);
+ kstatus = grab_kbd (screen);
+ if (kstatus == GrabSuccess)
+ break;
- /* Go around the loop and wait for the next bout of idleness,
- or for the init file to change, or for a remote command to
- come in, or something.
+ /* else, wait a second and try to grab again. */
+ sleep (1);
+ }
- But, if locked_p is true, go ahead. This can only happen
- if we're in "disabled" mode but a "lock" clientmessage came
- in: in that case, we should go ahead and blank/lock the screen.
- */
- if (!si->locked_p)
- continue;
- }
+ if (kstatus != GrabSuccess)
+ {
+ fprintf (stderr, "%s: couldn't grab keyboard: %s\n",
+ blurb(), grab_string (kstatus));
- /* Since we're about to blank the screen, kill the de-race timer,
- if any. It might still be running if we have unblanked and then
- re-blanked in a short period (e.g., when using the "next" button
- in xscreensaver-demo.)
- */
- if (si->de_race_id)
+ if (! focus_fuckus)
{
- if (p->verbose_p)
- fprintf (stderr, "%s: stopping de-race timer (%d remaining.)\n",
- blurb(), si->de_race_ticks);
- XtRemoveTimeOut (si->de_race_id);
- si->de_race_id = 0;
+ focus_fuckus = True;
+ nuke_focus (screen);
+ goto AGAIN;
}
+ }
+ for (i = 0; i < retries; i++)
+ {
+ XSync (dpy, False);
+ mstatus = grab_mouse (screen, blank_cursor);
+ if (mstatus == GrabSuccess)
+ break;
- /* Now, try to blank.
- */
-
- if (! blank_screen (si))
- {
- /* We were unable to grab either the keyboard or mouse.
- This means we did not (and must not) blank the screen.
- If we were to blank the screen while some other program
- is holding both the mouse and keyboard grabbed, then
- we would never be able to un-blank it! We would never
- see any events, and the display would be wedged.
-
- In particular, without that keyboard grab, we will be
- unable to ever read keypresses on the unlock dialog.
- You can't unlock if you can't type your password.
-
- So, just go around the loop again and wait for the
- next bout of idleness. (If the user remains idle, we
- will next try to blank the screen again in no more than
- 60 seconds.)
- */
- Time retry = 60 * 1000;
- if (p->timeout < retry)
- retry = p->timeout;
-
- if (p->debug_p)
- {
- fprintf (stderr,
- "%s: DEBUG MODE: unable to grab -- BLANKING ANYWAY.\n",
- blurb());
- }
- else
- {
- fprintf (stderr,
- "%s: unable to grab keyboard or mouse! Blanking aborted.\n",
- blurb());
-
- /* Since we were unable to blank, clearly we're not locked,
- but we might have been prematurely marked as locked by
- the LOCK ClientMessage. */
- if (si->locked_p)
- set_locked_p (si, False);
-
- schedule_wakeup_event (si, retry, p->debug_p);
- continue;
- }
- }
-
- for (i = 0; i < si->nscreens; i++)
- kill_screenhack (&si->screens[i]);
+ /* else, wait a second and try to grab again. */
+ sleep (1);
+ }
- raise_window (si, True, True, False);
- if (si->throttled_p)
- fprintf (stderr, "%s: not launching hack (throttled.)\n", blurb());
- else
- for (i = 0; i < si->nscreens; i++)
- spawn_screenhack (&si->screens[i]);
+ if (mstatus != GrabSuccess)
+ fprintf (stderr, "%s: couldn't grab pointer: %s\n",
+ blurb(), grab_string (mstatus));
+
+
+ /* When should we allow blanking to proceed? The current theory
+ is that a keyboard grab is mandatory; a mouse grab is optional.
+
+ - If we don't have a keyboard grab, then we won't be able to
+ read a password to unlock, so the kbd grab is mandatory.
+ (We can't conditionalize this on locked_p, because someone
+ might run "xscreensaver-command -lock" at any time.)
+
+ - If we don't have a mouse grab, then we might not see mouse
+ clicks as a signal to unblank -- but we will still see kbd
+ activity, so that's not a disaster.
+
+ If the mouse grab failed with AlreadyGrabbed, then I *think*
+ that means that we will still see the mouse events via XInput2.
+ But if it failed with GrabFrozen, that means that the grabber
+ used GrabModeSync, and we will only receive those mouse events
+ as a replay after they release the grab, which doesn't help us.
+
+ If the keyboard grab failed with AlreadyGrabbed rather than
+ GrabFrozen then we may still get those keypresses -- but so will
+ the program holding the grab, so that's unacceptable for our
+ purpose of reading passwords.
+
+ It has been suggested that we should allow blanking if locking
+ is disabled, and we have a mouse grab but no keyboard grab.
+ That would allow screen blanking (but not locking) while the gdm
+ login screen had the keyboard grabbed, but one would have to use
+ the mouse to unblank. Keyboard characters would go to the gdm
+ login field without unblanking. I have not made this change
+ because I'm not completely convinced it is a safe thing to do.
+ */
- /* If we are blanking only, optionally power down monitor right now. */
- if (p->mode == BLANK_ONLY &&
- p->dpms_enabled_p &&
- p->dpms_quickoff_p)
- {
- sync_server_dpms_settings (si->dpy, True,
- p->dpms_quickoff_p,
- p->dpms_standby / 1000,
- p->dpms_suspend / 1000,
- p->dpms_off / 1000,
- False);
- monitor_power_on (si, False);
- }
+ if (kstatus != GrabSuccess) /* Do not blank without a kbd grab. */
+ {
+ /* If we didn't get both grabs, release the one we did get. */
+ ungrab_keyboard_and_mouse (dpy);
+ return False;
+ }
- /* Don't start the cycle timer in demo mode. */
- if (!si->demoing_p && p->cycle)
- si->cycle_id = XtAppAddTimeOut (si->app,
- (si->selection_mode
- /* see comment in cycle_timer() */
- ? 1000 * 60 * 60
- : p->cycle),
- cycle_timer,
- (XtPointer) si);
+ return True; /* Grab is good, go ahead and blank. */
+}
-#ifndef NO_LOCKING
- /* Maybe start locking the screen.
- */
+/* Which screen is the mouse on?
+ */
+static Screen *
+mouse_screen (Display *dpy)
+{
+ int i, nscreens = ScreenCount (dpy);
+ if (nscreens > 1)
+ for (i = 0; i < nscreens; i++)
{
- Time lock_timeout = p->lock_timeout;
-
- /* If we're fading, don't lock until the fade finishes. */
- if (si->fading_possible_p && p->fade_p)
- lock_timeout += p->fade_seconds / 1000;
-
- if (si->emergency_lock_p)
- lock_timeout = 0;
-
- if (si->emergency_lock_p && p->lock_p && lock_timeout)
+ Window pointer_root, pointer_child;
+ int root_x, root_y, win_x, win_y;
+ unsigned int mask;
+ int status = XQueryPointer (dpy, RootWindow (dpy, i),
+ &pointer_root, &pointer_child,
+ &root_x, &root_y, &win_x, &win_y, &mask);
+ if (status != None)
{
- int secs = p->lock_timeout / 1000;
- if (p->verbose_p)
- fprintf (stderr,
- "%s: locking now, instead of waiting for %d:%02d:%02d.\n",
- blurb(),
- (secs / (60 * 60)), ((secs / 60) % 60), (secs % 60));
- lock_timeout = 0;
+ if (verbose_p)
+ fprintf (stderr, "%s: mouse is on screen %d of %d\n",
+ blurb(), i, nscreens);
+ return ScreenOfDisplay (dpy, i);
}
-
- si->emergency_lock_p = False;
-
- if (!si->demoing_p && /* if not going into demo mode */
- p->lock_p && /* and locking is enabled */
- !si->locking_disabled_p && /* and locking is possible */
- lock_timeout == 0) /* and locking is not timer-deferred */
- set_locked_p (si, True); /* then lock right now. */
-
- /* locked_p might be true already because of the above, or because of
- the LOCK ClientMessage. But if not, and if we're supposed to lock
- after some time, set up a timer to do so.
- */
- if (p->lock_p &&
- !si->locked_p &&
- lock_timeout > 0)
- si->lock_id = XtAppAddTimeOut (si->app, lock_timeout,
- activate_lock_timer,
- (XtPointer) si);
}
-#endif /* !NO_LOCKING */
-
-
- ok_to_unblank = True;
- do {
- check_for_leaks ("blanked A");
- sleep_until_idle (si, False); /* until not idle */
- check_for_leaks ("blanked B");
-
- maybe_reload_init_file (si);
-
-#ifndef NO_LOCKING
- /* Maybe unlock the screen.
- */
- if (si->locked_p)
- {
- saver_screen_info *ssi = si->default_screen;
- if (si->locking_disabled_p) abort ();
-
- was_locked = True;
- si->dbox_up_p = True;
- for (i = 0; i < si->nscreens; i++)
- suspend_screenhack (&si->screens[i], True); /* suspend */
- XUndefineCursor (si->dpy, ssi->screensaver_window);
-
- ok_to_unblank = unlock_p (si);
+ return ScreenOfDisplay (dpy, 0);
+}
- si->dbox_up_p = False;
- XDefineCursor (si->dpy, ssi->screensaver_window, ssi->cursor);
- for (i = 0; i < si->nscreens; i++)
- suspend_screenhack (&si->screens[i], False); /* resume */
- if (!ok_to_unblank &&
- !screenhack_running_p (si))
- {
- /* If the lock dialog has been dismissed and we're not about to
- unlock the screen, and there is currently no hack running,
- then launch one. (There might be no hack running if DPMS
- had kicked in. But DPMS is off now, so bring back the hack)
- */
- if (si->cycle_id)
- XtRemoveTimeOut (si->cycle_id);
- si->cycle_id = 0;
- cycle_timer ((XtPointer) si, 0);
- }
- }
-#endif /* !NO_LOCKING */
+static void
+maybe_disable_locking (Display *dpy)
+{
+ const char *why = 0;
- } while (!ok_to_unblank);
+# ifdef NO_LOCKING
+ why = "locking disabled at compile time";
+# endif
+ if (!why)
+ {
+ uid_t u = getuid();
+ if (u == 0 || u == (uid_t) -1 || u == (uid_t) -2)
+ why = "cannot lock when running as root";
+ }
- if (p->verbose_p)
- fprintf (stderr, "%s: unblanking screen at %s.\n",
- blurb(), timestring (0));
+ if (!why && getenv ("RUNNING_UNDER_GDM"))
+ /* Launched as GDM's "Background" program */
+ why = "cannot lock when running under GDM";
- /* Kill before unblanking, to stop drawing as soon as possible. */
- for (i = 0; i < si->nscreens; i++)
- kill_screenhack (&si->screens[i]);
- unblank_screen (si);
+ /* X11 grabs don't work under Wayland's embedded X11 server. The Wayland
+ window manager lives at a higher level than the X11 emulation layer. */
+ if (!why && getenv ("WAYLAND_DISPLAY"))
+ why = "cannot lock securely under Wayland";
- set_locked_p (si, False);
- si->emergency_lock_p = False;
- si->demoing_p = 0;
- si->selection_mode = 0;
+ if (!why)
+ {
+ /* Grabs under the macOS XDarwin server only affect other X11 programs:
+ you can Cmd-Tab to "Terminal". These extensions exist on later
+ releases XQuartz. */
+ int op = 0, event = 0, error = 0;
+ if (XQueryExtension (dpy, "Apple-DRI", &op, &event, &error) ||
+ XQueryExtension (dpy, "Apple-WM", &op, &event, &error))
+ why = "cannot lock securely under macOS X11";
+ }
- /* If we're throttled, and the user has explicitly unlocked the screen,
- then unthrottle. If we weren't locked, then don't unthrottle
- automatically, because someone might have just bumped the desk... */
- if (was_locked)
+ if (why)
+ {
+ if (debug_p)
{
- if (si->throttled_p && p->verbose_p)
- fprintf (stderr, "%s: unthrottled.\n", blurb());
- si->throttled_p = False;
+ fprintf (stderr, "%s: %s\n", blurb(), why);
+ fprintf (stderr, "%s: DEBUG MODE: allowing locking anyway!\n",
+ blurb());
+ }
+ else
+ {
+ locking_disabled_p = True;
+ if (lock_p || verbose_p)
+ fprintf (stderr, "%s: locking disabled: %s\n", blurb(), why);
}
-
- if (si->cycle_id)
- {
- XtRemoveTimeOut (si->cycle_id);
- si->cycle_id = 0;
- }
-
- if (si->lock_id)
- {
- XtRemoveTimeOut (si->lock_id);
- si->lock_id = 0;
- }
-
-# ifdef HAVE_LIBSYSTEMD
- /* This might be a good spot to re-launch si->systemd_pid
- if it has died unexpectedly. Which shouldn't happen. */
-# endif
-
- /* Since we're unblanked now, break race conditions and make
- sure we stay that way (see comment in timers.c.) */
- if (! si->de_race_id)
- de_race_timer ((XtPointer) si, 0);
}
}
-static void analyze_display (saver_info *si);
-static void fix_fds (void);
-int
-main (int argc, char **argv)
+static void
+main_loop (Display *dpy)
{
- Widget shell;
- saver_info the_si;
- saver_info *si = &the_si;
- saver_preferences *p = &si->prefs;
- struct passwd *spasswd;
- int i;
+ int xi_opcode;
+ time_t now = time ((time_t *) 0);
+ time_t active_at = now;
+ time_t blanked_at = 0;
+ time_t ignore_activity_before = now;
+ time_t last_checked_init_file = now;
+ Bool authenticated_p = False;
+ Bool ignore_motion_p = False;
- /* It turns out that if we do setlocale (LC_ALL, "") here, people
- running in Japanese locales get font craziness on the password
- dialog, presumably because it is displaying Japanese characters
- in a non-Japanese font. However, if we don't call setlocale()
- at all, then XLookupString() never returns multi-byte UTF-8
- characters when people type non-Latin1 characters on the
- keyboard.
+ enum { UNBLANKED, BLANKED, LOCKED, AUTH } current_state = UNBLANKED;
- The current theory (and at this point, I'm really guessing!) is
- that using LC_CTYPE instead of LC_ALL will make XLookupString()
- behave usefully, without having the side-effect of screwing up
- the fonts on the unlock dialog.
+ struct { time_t time; int x, y; } last_mouse = { 0, 0, 0 };
- See https://bugs.launchpad.net/ubuntu/+source/xscreensaver/+bug/671923
- from comment #20 onward.
+ maybe_disable_locking (dpy);
+ init_xscreensaver_atoms (dpy);
+ ensure_no_screensaver_running (dpy);
- -- jwz, 24-Sep-2011
- */
-#ifdef ENABLE_NLS
- if (!setlocale (LC_CTYPE, ""))
- fprintf (stderr, "%s: warning: could not set default locale\n",
- progname);
+ if (! init_xinput (dpy, &xi_opcode))
+ saver_exit (1);
- bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
- textdomain (GETTEXT_PACKAGE);
-#endif /* ENABLE_NLS */
+ create_daemon_window (dpy);
- memset(si, 0, sizeof(*si));
- global_si_kludge = si; /* I hate C so much... */
+ handle_signals();
- fix_fds();
+ blank_cursor = None; /* Cursor of window under mouse (which is blank). */
+ auth_cursor = XCreateFontCursor (dpy, XC_top_left_arrow);
-# undef ya_rand_init
- ya_rand_init (0);
+ if (strchr (version_number, 'a') || strchr (version_number, 'b'))
+ splash_p = True; /* alpha and beta releases */
- save_argv (argc, argv);
- set_version_string (si, &argc, argv);
- privileged_initialization (si, &argc, argv);
- hack_environment (si);
+ /* Launch "xscreensaver-auth" once at startup with either --splash or --init,
+ The latter to handle the OOM-killer while setuid. */
+ {
+ char *av[10];
+ int ac = 0;
+ av[ac++] = SAVER_AUTH_PROGRAM;
+ av[ac++] = (splash_p ? "--splash" : "--init");
+ if (verbose_p) av[ac++] = "--verbose";
+ if (verbose_p > 1) av[ac++] = "--verbose";
+ if (verbose_p > 2) av[ac++] = "--verbose";
+ if (debug_p) av[ac++] = "--debug";
+ av[ac] = 0;
+ saver_auth_pid = fork_and_exec (dpy, ac, av);
+ }
- spasswd = getpwuid(getuid());
- if (!spasswd)
- {
- fprintf(stderr, "Could not figure out who the current user is!\n");
- return 1;
- }
+# ifdef HAVE_LIBSYSTEMD
+ /* Launch xscreensaver-systemd at startup. */
+ {
+ char *av[10];
+ int ac = 0;
+ av[ac++] = SAVER_SYSTEMD_PROGRAM;
+ if (verbose_p || debug_p)
+ av[ac++] = "--verbose";
+ av[ac] = 0;
+ saver_systemd_pid = fork_and_exec (dpy, ac, av);
+ }
+# endif /* HAVE_LIBSYSTEMD */
- si->user = strdup(spasswd->pw_name ? spasswd->pw_name : "(unknown)");
-# ifndef NO_LOCKING
- si->unlock_cb = gui_auth_conv;
- si->auth_finished_cb = auth_finished_cb;
-# endif /* !NO_LOCKING */
+ /* X11 errors during startup initialization were fatal.
+ Once we enter the main loop, they are printed but ignored.
+ */
+ XSync (dpy, False);
+ ignore_x11_error_p = True;
- shell = connect_to_server (si, &argc, argv);
- process_command_line (si, &argc, argv);
- stderr_log_file (si);
- print_banner (si);
+ /************************************************************************
+ Main loop
+ ************************************************************************/
- load_init_file(si->dpy, p); /* must be before initialize_per_screen_info() */
- blurb_timestamp_p = p->timestamp_p; /* kludge */
- initialize_per_screen_info (si, shell); /* also sets si->fading_possible_p */
+ while (1)
+ {
+ Bool force_blank_p = False;
+ Bool force_lock_p = False;
+ Atom blank_mode = 0;
+ char blank_mode_arg[20] = { 0 };
- /* We can only issue this warning now. */
- if (p->verbose_p && !si->fading_possible_p && (p->fade_p || p->unfade_p))
- fprintf (stderr,
- "%s: there are no PseudoColor or GrayScale visuals.\n"
- "%s: ignoring the request for fading/unfading.\n",
- blurb(), blurb());
+ /* Wait until an event comes in, or a timeout. */
+ {
+ int xfd = ConnectionNumber (dpy);
+ fd_set in_fds;
+ struct timeval tv;
+ time_t until;
+ switch (current_state) {
+ case UNBLANKED: until = active_at + blank_timeout; break;
+ case BLANKED: until = blanked_at + lock_timeout; break;
+ default: until = 0;
+ }
- for (i = 0; i < si->nscreens; i++)
- {
- saver_screen_info *ssi = &si->screens[i];
- if (ssi->real_screen_p)
- if (ensure_no_screensaver_running (si->dpy, si->screens[i].screen))
- exit (1);
- ssi->current_hack = -1;
- }
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ if (until >= now)
+ tv.tv_sec = until - now;
+
+ if (verbose_p > 3)
+ {
+ if (!tv.tv_sec)
+ fprintf (stderr, "%s: block until input\n", blurb());
+ else
+ {
+ struct tm tm;
+ time_t t = now + tv.tv_sec;
+ localtime_r (&t, &tm);
+ fprintf (stderr,
+ "%s: block for %ld sec until %02d:%02d:%02d\n",
+ blurb(), tv.tv_sec, tm.tm_hour, tm.tm_min, tm.tm_sec);
+ }
+ }
- lock_initialization (si, &argc, argv);
- print_lock_failure_banner (si);
+ FD_ZERO (&in_fds);
+ FD_SET (xfd, &in_fds);
+ select (xfd + 1, &in_fds, NULL, NULL, (tv.tv_sec ? &tv : NULL));
+ }
- if (p->xsync_p) XSynchronize (si->dpy, True);
+ now = time ((time_t *) 0);
- if (p->verbose_p) analyze_display (si);
- initialize_server_extensions (si);
- si->blank_time = time ((time_t *) 0); /* must be before ..._window */
- initialize_screensaver_window (si);
+ /********************************************************************
+ Signal handlers
+ ********************************************************************/
- select_events (si);
- init_sigchld ();
- disable_builtin_screensaver (si, True);
- sync_server_dpms_settings (si->dpy,
- (p->dpms_enabled_p &&
- p->mode != DONT_BLANK),
- p->dpms_quickoff_p,
- p->dpms_standby / 1000,
- p->dpms_suspend / 1000,
- p->dpms_off / 1000,
- False);
+ /* If a signal handler fired, it set a flag, and that caused select()
+ to return. Now that we are back on the program stack, handle
+ those signals. */
- initialize_stderr (si);
- handle_signals (si);
+ /* SIGHUP is the same as "xscreensaver-command -restart". */
+ if (sighup_received)
+ {
+ sighup_received = 0;
+ if (current_state == LOCKED)
+ {
+ fprintf (stderr,
+ "%s: SIGHUP received while locked: ignoring\n",
+ blurb());
+ }
+ else
+ {
+ fprintf (stderr, "%s: SIGHUP received: restarting\n", blurb());
+ restart_process(); /* Does not return */
+ fprintf (stderr, "%s: SIGHUP RESTART FAILED!\n", blurb());
+ }
+ }
- store_saver_status (si); /* for xscreensaver-command -status */
+ /* Upon SIGTERM, SIGINT or SIGQUIT we must kill our subprocesses
+ before exiting.
+ */
+ if (sigterm_received)
+ {
+ int sig = sigterm_received; /* Same handler for all 3 signals */
+ const char *sn = (sig == SIGINT ? "SIGINT" :
+ sig == SIGQUIT ? "SIGQUIT" : "SIGTERM");
+ sigterm_received = 0;
+ fprintf (stderr, "%s: %s received%s: exiting\n", blurb(), sn,
+ (current_state == LOCKED ? " while locked" : ""));
+
+ /* Rather than calling saver_exit(), set our SIGTERM handler back to
+ the default and re-signal it so that this process actually dies
+ with a signal. */
+ signal (sig, SIG_DFL);
+ kill_all_subprocs();
+ kill (getpid(), sig);
+ abort();
+ }
-# ifdef HAVE_LIBSYSTEMD /* Launch it in the background */
- si->systemd_pid = fork_and_exec_1 (si, 0, "xscreensaver-systemd");
-# endif
+ /* SIGCHLD is fired any time one of our subprocesses dies.
+ When "xscreensaver-auth" dies, it analyzes its exit code.
+ */
+ if (sigchld_received)
+ authenticated_p = handle_sigchld (dpy, current_state != UNBLANKED);
- make_splash_dialog (si);
+ /* Now process any outstanding X11 events on the queue: user activity
+ from XInput, and ClientMessages from xscreensaver-command.
+ */
+ while (XPending (dpy))
+ {
+ XEvent xev;
+ XNextEvent (dpy, &xev);
+ now = time ((time_t *) 0);
+
+ /****************************************************************
+ Client Messages
+ Both xscreensaver and xscreensaver-gfx handle these; some are
+ handled exclusively by one program or another, and some of them
+ (select, next, prev) are handled by xscreensaver only if
+ xscreensaver-gfx is not already running.
+ ****************************************************************/
+
+ switch (xev.xany.type) {
+ case ClientMessage:
+ {
+ Atom msg = xev.xclient.data.l[0];
- main_loop (si); /* doesn't return */
- return 0;
-}
+ /* Re-load the .xscreensaver file before processing the results
+ of any ClientMessage, in case they just changed the timeouts.
+ */
+ last_checked_init_file = 0;
-static void
-fix_fds (void)
-{
- /* Bad Things Happen if stdin, stdout, and stderr have been closed
- (as by the `sh incantation "xscreensaver >&- 2>&-"). When you do
- that, the X connection gets allocated to one of these fds, and
- then some random library writes to stderr, and random bits get
- stuffed down the X pipe, causing "Xlib: sequence lost" errors.
- So, we cause the first three file descriptors to be open to
- /dev/null if they aren't open to something else already. This
- must be done before any other files are opened (or the closing
- of that other file will again free up one of the "magic" first
- three FDs.)
+ if (! (xev.xclient.message_type == XA_SCREENSAVER &&
+ xev.xclient.format == 32 &&
+ xev.xclient.data.l[0]))
+ {
+ goto BAD_CM;
+ }
+ else if (msg == XA_ACTIVATE ||
+ msg == XA_SELECT ||
+ msg == XA_DEMO ||
+ msg == XA_NEXT ||
+ msg == XA_PREV)
+ {
+ /* The others are the same as -activate except that they
+ cause some extra args to be added to the xscreensaver-gfx
+ command line.
+ */
+ if (msg != XA_ACTIVATE)
+ blank_mode = msg;
+ if (msg == XA_SELECT || msg == XA_DEMO)
+ { /* see remote.c */
+ unsigned long n = xev.xclient.data.l[1];
+ if (n == 5000) n = xev.xclient.data.l[2];
+ sprintf (blank_mode_arg, "%lu", n);
+ }
+
+ if (msg == XA_DEMO) ignore_motion_p = True;
+
+ if (current_state == UNBLANKED)
+ {
+ force_blank_p = True;
+ ignore_activity_before = now + 2;
+ clientmessage_response (dpy, &xev, True, "blanking");
+ }
+ else if (msg == XA_SELECT ||
+ msg == XA_NEXT ||
+ msg == XA_PREV)
+ {
+ /* When active, these are handled by xscreensaver-gfx
+ instead of xscreensaver, so silently ignore them,
+ and allow xscreensaver-gfx to reply instead. */
+ }
+ else
+ clientmessage_response (dpy, &xev, False,
+ "already active");
+ }
+ else if (msg == XA_CYCLE)
+ {
+ if (current_state == UNBLANKED)
+ /* Only allowed when screen already blanked */
+ clientmessage_response (dpy, &xev, False, "not blanked");
+ /* else xscreensaver-gfx will respond to this. */
+ }
+ else if (msg == XA_DEACTIVATE)
+ {
+ if (current_state == UNBLANKED)
+ {
+ clientmessage_response (dpy, &xev, True,
+ "already inactive, resetting activity time");
+ active_at = now;
+ ignore_activity_before = now;
+ }
+ else
+ {
+ /* This behaves just like user input: if state is
+ LOCKED, it will advance to AUTH. */
+ active_at = now;
+ ignore_activity_before = now;
+ clientmessage_response (dpy, &xev, True, "deactivating");
+ }
+
+ /* DEACTIVATE while inactive also needs to reset the
+ server's DPMS time, but doing that here would mean
+ linking with additional libraries, doing additional X
+ protocol, and also some finicky error handling, since
+ the DPMS extension is a pain in the ass. So instead,
+ I made xscreensaver-command do that instead. This
+ somewhat breaks the abstraction of ClientMessage
+ handling, but it's more robust. */
+ }
+ else if (msg == XA_LOCK)
+ {
+ if (locking_disabled_p)
+ clientmessage_response (dpy, &xev, False,
+ "locking disabled");
+ else if (current_state == UNBLANKED ||
+ current_state == BLANKED)
+ {
+ force_lock_p = True;
+ ignore_activity_before = now + 2;
+ clientmessage_response (dpy, &xev, True, "locking");
+ }
+ else
+ clientmessage_response (dpy, &xev, False,
+ "already locked");
+ }
+ else if (msg == XA_SUSPEND)
+ {
+ force_blank_p = True;
+ if (lock_p) force_lock_p = True;
+ ignore_activity_before = now + 2;
+ blank_mode = msg;
+ clientmessage_response (dpy, &xev, True, "suspending");
+ }
+ else if (msg == XA_EXIT)
+ {
+ if (current_state == UNBLANKED ||
+ current_state == BLANKED)
+ {
+ clientmessage_response (dpy, &xev, True, "exiting");
+ XSync (dpy, False);
+ saver_exit (0);
+ }
+ else
+ clientmessage_response (dpy, &xev, False,
+ "screen is locked");
+ }
+ else if (msg == XA_RESTART)
+ {
+ if (current_state == UNBLANKED ||
+ current_state == BLANKED)
+ {
+ clientmessage_response (dpy, &xev, True, "restarting");
+ XSync (dpy, False);
+ restart_process(); /* Does not return */
+ fprintf (stderr, "%s: RESTART FAILED!\n", blurb());
+ }
+ else
+ clientmessage_response (dpy, &xev, False,
+ "screen is locked");
+ }
+ else
+ {
+ BAD_CM:
+ if (verbose_p)
+ {
+ Atom type;
+ char *tstr, *name;
+ Bool op = print_x11_error_p;
+ print_x11_error_p = False; /* Ignore BadAtom */
+ type = xev.xclient.message_type;
+ tstr = type ? XGetAtomName (dpy, type) : 0;
+ name = msg ? XGetAtomName (dpy, msg) : 0;
+ fprintf (stderr,
+ "%s: unrecognized ClientMessage %s %s\n",
+ blurb(),
+ (tstr ? tstr : "???"),
+ (name ? name : "???"));
+ if (tstr) XFree (tstr);
+ if (name) XFree (name);
+ print_x11_error_p = op;
+ }
+ }
+ continue;
+ }
- We do this by opening /dev/null three times, and then closing
- those fds, *unless* any of them got allocated as #0, #1, or #2,
- in which case we leave them open. Gag.
- Really, this crap is technically required of *every* X program,
- if you want it to be robust in the face of "2>&-".
- */
- int fd0 = open ("/dev/null", O_RDWR);
- int fd1 = open ("/dev/null", O_RDWR);
- int fd2 = open ("/dev/null", O_RDWR);
- if (fd0 > 2) close (fd0);
- if (fd1 > 2) close (fd1);
- if (fd2 > 2) close (fd2);
-}
+ /****************************************************************
+ Normal X11 keyboard and mouse events
+ ****************************************************************/
+
+ /* XInput2 "raw" events bypass X11 grabs, but grabs take priority.
+ If this process has the keyboard and mouse grabbed, it receives
+ the following events:
+
+ - X11 KeyPress, KeyRelease
+ - X11 ButtonPress, ButtonRelease
+ - X11 MotionNotify
+ - XInput XI_RawButtonPress, XI_RawButtonRelease
+ - XInput XI_RawMotion
+
+ Note that button and motion events are doubly reported, but
+ keyboard events are not.
+
+ If this process does *not* have the keyboard and mouse grabbed,
+ it receives the following events, regardless of the window in
+ which they occur:
+
+ - XInput XI_RawKeyPress, XI_RawKeyRelease
+ - XInput XI_RawButtonPress, XI_RawButtonRelease
+ - XInput XI_RawMotion
+
+ But here's an irritating kink: though XInput2 generally allows
+ snooping of everything, it respects GrabModeSync. What this
+ means is that if some other process has the keyboard grabbed with
+ "Sync" instead of "Async", then this process will not see any of
+ the events until that process releases its grab, and then the
+ events come in late, all at once. Verify this by running:
+
+ test-xinput &
+ Note that keyboard and mouse events are detected.
+ test-grab --mouse --mouse-async --kbd-async
+ Same.
+ test-grab --mouse --mouse-sync --kbd-async
+ Keyboard events are detected.
+ No motion or button events until "test-grab" is killed.
+ test-grab --mouse --mouse-async --kbd-sync
+ Vice versa.
+ */
+ case KeyPress:
+ case KeyRelease:
+ if (current_state != AUTH && /* logged by xscreensaver-auth */
+ (verbose_p > 1 ||
+ (verbose_p && now - active_at > 1)))
+ print_xinput_event (dpy, &xev, "");
+ active_at = now;
+ continue;
+ break;
+ case ButtonPress:
+ case ButtonRelease:
+ active_at = now;
+ if (verbose_p)
+ print_xinput_event (dpy, &xev, "");
+ continue;
+ break;
+ case MotionNotify:
+ /* Since we always get XI_RawMotion, but only get MotionNotify
+ when grabbed, we can just ignore MotionNotify and let the
+ XI_RawMotion clause handle hysteresis. */
+ if (verbose_p > 1)
+ print_xinput_event (dpy, &xev, "ignored");
+ continue;
+ break;
+ default:
+ break;
+ }
-
-/* Processing ClientMessage events.
- */
+ /****************************************************************
+ XInput keyboard and mouse events
+ ****************************************************************/
+
+ if (xev.xcookie.type != GenericEvent ||
+ xev.xcookie.extension != xi_opcode)
+ continue; /* not an XInput event */
+
+ if (!xev.xcookie.data)
+ XGetEventData (dpy, &xev.xcookie);
+ if (!xev.xcookie.data)
+ continue; /* Bogus XInput event */
+
+ switch (xev.xcookie.evtype) {
+ case XI_RawKeyPress:
+ case XI_RawKeyRelease:
+ case XI_RawButtonPress:
+ case XI_RawButtonRelease:
+ if (current_state != AUTH && /* logged by xscreensaver-auth */
+ (verbose_p > 1 ||
+ (verbose_p && now - active_at > 1)))
+ print_xinput_event (dpy, &xev, "");
+ active_at = now;
+ break;
+
+ case XI_RawMotion:
+ {
+ /* Mouse wheel scrolling sends Button4 and Button5 events as well
+ as motion, so we handled those above, in XI_RawButtonPress.
+ The raw_values in the motion event for a mouse wheel reflect
+ the position of the wheel sensor.
+
+ On a trackpad where two-finger-swipe is a scroll gesture, I
+ saw behavior identical to a mouse wheel -- it does not send
+ RawTouch events.
+ */
+
+ /* Don't poll the mouse position more frequently than once
+ a second. The motion only counts as activity if it has
+ moved farther than N pixels per second.
+ */
+ int secs = now - last_mouse.time;
+ if (secs >= 1)
+ {
+ Window root_ret, child_ret;
+ int root_x, root_y;
+ int win_x, win_y;
+ unsigned int mask;
+ int dist;
+ Bool ignored_p = False;
+
+ XQueryPointer (dpy, DefaultRootWindow (dpy),
+ &root_ret, &child_ret, &root_x, &root_y,
+ &win_x, &win_y, &mask);
+ dist = MAX (ABS (last_mouse.x - root_x),
+ ABS (last_mouse.y - root_y));
+
+ ignored_p = (ignore_motion_p || dist < pointer_hysteresis);
+
+ if (! ignored_p)
+ {
+ active_at = now;
+ last_mouse.time = now;
+ last_mouse.x = root_x;
+ last_mouse.y = root_y;
+ }
+
+ if (verbose_p > 1 ||
+ (verbose_p && now - active_at > 5))
+ print_xinput_event (dpy, &xev,
+ (ignored_p ? " ignored" : ""));
+ }
+ }
+ break;
-static Bool error_handler_hit_p = False;
+ default:
+ if (verbose_p)
+ print_xinput_event (dpy, &xev, "");
+ break;
+ }
-static int
-ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
-{
- error_handler_hit_p = True;
- return 0;
-}
+ XFreeEventData (dpy, &xev.xcookie);
+ }
-/* Sometimes some systems send us ClientMessage events with bogus atoms in
- them. We only look up the atom names for printing warning messages,
- so don't bomb out when it happens...
- */
-static char *
-XGetAtomName_safe (Display *dpy, Atom atom)
-{
- char *result;
- XErrorHandler old_handler;
- if (!atom) return 0;
- XSync (dpy, False);
- error_handler_hit_p = False;
- old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
- result = XGetAtomName (dpy, atom);
- XSync (dpy, False);
- XSetErrorHandler (old_handler);
- XSync (dpy, False);
- if (error_handler_hit_p) result = 0;
+ /********************************************************************
+ Advancing the state machine
+ ********************************************************************/
- if (result)
- return result;
- else
- {
- char buf[100];
- sprintf (buf, "<<undefined atom 0x%04X>>", (unsigned int) atom);
- return strdup (buf);
- }
-}
+ /* If it's time, see if the .xscreensaver file has changed, since that
+ might change the blank and lock timeouts.
+ */
+ if (now >= last_checked_init_file + 60)
+ {
+ last_checked_init_file = now;
+ if (verbose_p)
+ fprintf(stderr,"%s: checking init file\n", blurb());
+ read_init_files (False);
+ }
+ /* Now that events have been processed, see if the state should change,
+ based on any events received and the current time.
+ */
+ switch (current_state) {
+ case UNBLANKED:
+ if (!locking_disabled_p &&
+ (force_lock_p ||
+ (lock_p &&
+ now >= active_at + blank_timeout + lock_timeout)))
+ {
+ fprintf (stderr, "%s: locking\n", blurb());
+ if (grab_keyboard_and_mouse (mouse_screen (dpy)))
+ {
+ current_state = LOCKED;
+ blanked_at = now;
+ authenticated_p = False;
+ store_saver_status (dpy, True, True, now);
+ }
+ else
+ fprintf (stderr, "%s: unable to grab -- locking aborted!\n",
+ blurb());
-static void
-clientmessage_response (saver_info *si, Window w, Bool error,
- const char *stderr_msg,
- const char *protocol_msg)
-{
- char *proto;
- int L;
- saver_preferences *p = &si->prefs;
- XErrorHandler old_handler;
-
- if (error || p->verbose_p)
- fprintf (stderr, "%s: %s\n", blurb(), stderr_msg);
-
- L = strlen(protocol_msg);
- proto = (char *) malloc (L + 2);
- proto[0] = (error ? '-' : '+');
- strcpy (proto+1, protocol_msg);
- L++;
-
- /* Ignore all X errors while sending a response to a ClientMessage.
- Pretty much the only way we could get an error here is if the
- window we're trying to send the reply on has been deleted, in
- which case, the sender of the ClientMessage won't see our response
- anyway.
- */
- XSync (si->dpy, False);
- error_handler_hit_p = False;
- old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
+ force_lock_p = False; /* Single shot */
+ }
+ else if (force_blank_p ||
+ now >= active_at + blank_timeout)
+ {
+ fprintf (stderr, "%s: blanking\n", blurb());
+ if (grab_keyboard_and_mouse (mouse_screen (dpy)))
+ {
+ current_state = BLANKED;
+ blanked_at = now;
+ store_saver_status (dpy, True, False, now);
+ }
+ else
+ fprintf (stderr, "%s: unable to grab -- blanking aborted!\n",
+ blurb());
+ }
- XChangeProperty (si->dpy, w, XA_SCREENSAVER_RESPONSE, XA_STRING, 8,
- PropModeReplace, (unsigned char *) proto, L);
+ if (current_state == BLANKED || current_state == LOCKED)
+ {
+ /* Grab succeeded and state changed: launch graphics. */
+ if (! saver_gfx_pid)
+ {
+ static Bool first_time_p = True;
+ char *av[20];
+ int ac = 0;
+ av[ac++] = SAVER_GFX_PROGRAM;
+ if (first_time_p) av[ac++] = "--init";
+ if (verbose_p) av[ac++] = "--verbose";
+ if (debug_p) av[ac++] = "--debug";
+
+ if (blank_mode == XA_NEXT)
+ av[ac++] = "--next";
+ else if (blank_mode == XA_PREV)
+ av[ac++] = "--prev";
+ else if (blank_mode == XA_SELECT)
+ av[ac++] = "--select";
+ else if (blank_mode == XA_DEMO)
+ av[ac++] = "--demo";
+ else if (blank_mode == XA_SUSPEND)
+ av[ac++] = "--emergency";
+
+ if (blank_mode == XA_SELECT || blank_mode == XA_DEMO)
+ av[ac++] = blank_mode_arg;
+
+ av[ac] = 0;
+ gfx_stopped_p = False;
+ saver_gfx_pid = fork_and_exec (dpy, ac, av);
+ respawn_thrashing_count = 0;
+ first_time_p = False;
+ }
+ }
+ break;
- XSync (si->dpy, False);
- XSetErrorHandler (old_handler);
- XSync (si->dpy, False);
+ case BLANKED:
+ if (!locking_disabled_p &&
+ (force_lock_p ||
+ (lock_p &&
+ now >= blanked_at + lock_timeout)))
+ {
+ if (verbose_p)
+ fprintf (stderr, "%s: locking%s\n", blurb(),
+ (force_lock_p ? "" : " after timeout"));
+ current_state = LOCKED;
+ authenticated_p = False;
+ store_saver_status (dpy, True, True, now);
+ force_lock_p = False; /* Single shot */
+ }
+ else if (active_at >= now &&
+ active_at >= ignore_activity_before)
+ {
+ UNBLANK:
+ if (verbose_p)
+ fprintf (stderr, "%s: unblanking\n", blurb());
+ current_state = UNBLANKED;
+ ignore_motion_p = False;
+ store_saver_status (dpy, False, False, now);
+
+ if (saver_gfx_pid)
+ {
+ if (verbose_p)
+ fprintf (stderr,
+ "%s: pid %lu: killing " SAVER_GFX_PROGRAM "\n",
+ blurb(), (unsigned long) saver_gfx_pid);
+ kill (saver_gfx_pid, SIGTERM);
+ respawn_thrashing_count = 0;
+
+ if (gfx_stopped_p) /* SIGCONT to allow SIGTERM to proceed */
+ {
+ if (verbose_p)
+ fprintf (stderr, "%s: pid %lu: sending "
+ SAVER_GFX_PROGRAM " SIGCONT\n",
+ blurb(), (unsigned long) saver_gfx_pid);
+ gfx_stopped_p = False;
+ kill (-saver_gfx_pid, SIGCONT); /* send to process group */
+ }
+ }
- free (proto);
-}
+ ungrab_keyboard_and_mouse (dpy);
+ }
+ break;
+ case LOCKED:
+ if (active_at >= now &&
+ active_at >= ignore_activity_before)
+ {
+ char *av[10];
+ int ac = 0;
-static void
-bogus_clientmessage_warning (saver_info *si, XEvent *event)
-{
-#if 0 /* Oh, fuck it. GNOME likes to spew random ClientMessages at us
- all the time. This is presumably indicative of an error in
- the sender of that ClientMessage: if we're getting it and
- ignoring it, then it's not reaching the intended recipient.
- But people complain to me about this all the time ("waaah!
- xscreensaver is printing to it's stderr and that gets my
- panties all in a bunch!") And I'm sick of hearing about it.
- So we'll just ignore these messages and let GNOME go right
- ahead and continue to stumble along in its malfunction.
- */
-
- saver_preferences *p = &si->prefs;
- char *str = XGetAtomName_safe (si->dpy, event->xclient.message_type);
- Window w = event->xclient.window;
- char wdesc[255];
- int screen = 0;
- Bool root_p = False;
-
- *wdesc = 0;
- for (screen = 0; screen < si->nscreens; screen++)
- if (w == si->screens[screen].screensaver_window)
- {
- strcpy (wdesc, "xscreensaver");
+ if (saver_gfx_pid)
+ {
+ /* To suspend or not suspend? If we don't suspend, then a
+ misbehaving or heavily-loaded screenhack might make it more
+ difficult to type in the password. Though that seems pretty
+ unlikely.
+
+ But if we do suspend, then attaching a new monitor while
+ the unlock dialog is up will cause a new desktop to be
+ visible, since "xscreensaver-gfx" is the process that
+ handles RANDR. You can't interact with that new desktop,
+ but you can see it, possibly including (depending on the
+ window manager) the names of file icons on the desktop, or
+ other things. */
+# if 0
+ if (verbose_p)
+ fprintf (stderr, "%s: pid %lu: sending " SAVER_GFX_PROGRAM
+ " SIGSTOP\n", blurb(),
+ (unsigned long) saver_gfx_pid);
+ gfx_stopped_p = True;
+ kill (-saver_gfx_pid, SIGSTOP); /* send to process group */
+# endif
+ }
+
+ if (verbose_p)
+ fprintf (stderr, "%s: authorizing\n", blurb());
+ current_state = AUTH;
+
+ /* We already hold the mouse grab, but try to re-grab it with
+ a different mouse pointer, so that the pointer shows up while
+ the auth dialog is raised. We can ignore failures here. */
+ grab_mouse (mouse_screen (dpy), auth_cursor);
+
+ av[ac++] = SAVER_AUTH_PROGRAM;
+ if (verbose_p) av[ac++] = "--verbose";
+ if (verbose_p > 1) av[ac++] = "--verbose";
+ if (verbose_p > 2) av[ac++] = "--verbose";
+ if (debug_p) av[ac++] = "--debug";
+ av[ac] = 0;
+ saver_auth_pid = fork_and_exec (dpy, ac, av);
+ }
break;
- }
- else if (w == RootWindow (si->dpy, screen))
- {
- strcpy (wdesc, "root");
- root_p = True;
+
+ case AUTH:
+ if (saver_auth_pid)
+ {
+ /* xscreensaver-auth still running -- wait for it to exit. */
+ }
+ else if (authenticated_p)
+ {
+ /* xscreensaver-auth exited with "success" status */
+ if (verbose_p)
+ fprintf (stderr, "%s: unlocking\n", blurb());
+ authenticated_p = False;
+ goto UNBLANK;
+ }
+ else
+ {
+ /* xscreensaver-auth exited with non-success, or with signal. */
+ if (verbose_p)
+ fprintf (stderr, "%s: authorization failed\n", blurb());
+ current_state = LOCKED;
+ authenticated_p = False;
+
+ /* We already hold the mouse grab, but try to re-grab it with
+ a different mouse pointer, to hide the pointer again now that
+ the auth dialog is gone. We can ignore failures here. */
+ grab_mouse (mouse_screen (dpy), blank_cursor);
+
+ /* When the unlock dialog is dismissed, ignore any input for a
+ second to give the user time to take their hands off of the
+ keyboard and mouse, so that it doesn't pop up again
+ immediately. */
+ ignore_activity_before = now + 1;
+
+ if (gfx_stopped_p) /* SIGCONT to resume savers */
+ {
+ if (verbose_p)
+ fprintf (stderr, "%s: pid %lu: sending " SAVER_GFX_PROGRAM
+ " SIGCONT\n",
+ blurb(), (unsigned long) saver_gfx_pid);
+ gfx_stopped_p = False;
+ kill (-saver_gfx_pid, SIGCONT); /* send to process group */
+ }
+ }
+ break;
+
+ default:
+ /* abort(); */
break;
}
+ }
+}
+
- /* If this ClientMessage was sent to the real root window instead of to the
- xscreensaver window, then it might be intended for someone else who is
- listening on the root window (e.g., the window manager). So only print
- the warning if: we are in debug mode; or if the bogus message was
- actually sent to one of the xscreensaver-created windows.
+/* There is no good reason for this executable to be setuid, but in case
+ it is, turn that off.
+ */
+static const char *
+disavow_privileges (void)
+{
+ static char msg[255];
+ uid_t euid = geteuid();
+ gid_t egid = getegid();
+ uid_t uid = getuid();
+ gid_t gid = getgid();
+
+ if (uid == euid && gid == egid)
+ return 0;
+
+ /* Without setgroups(), the process will retain any supplementary groups
+ associated with the uid, e.g. the default groups of the "root" user.
+ But setgroups() can only be called by root, and returns EPERM for other
+ users even if the call would be a no-op. So ignore its error status.
*/
- if (root_p && !p->debug_p)
- return;
+ setgroups (1, &gid);
- if (!*wdesc)
+ if (gid != egid && setgid (gid) != 0)
{
- XErrorHandler old_handler;
- XClassHint hint;
- XWindowAttributes xgwa;
- memset (&hint, 0, sizeof(hint));
- memset (&xgwa, 0, sizeof(xgwa));
-
- XSync (si->dpy, False);
- old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
- XGetClassHint (si->dpy, w, &hint);
- XGetWindowAttributes (si->dpy, w, &xgwa);
- XSync (si->dpy, False);
- XSetErrorHandler (old_handler);
- XSync (si->dpy, False);
-
- screen = (xgwa.screen ? screen_number (xgwa.screen) : -1);
-
- sprintf (wdesc, "%.20s / %.20s",
- (hint.res_name ? hint.res_name : "(null)"),
- (hint.res_class ? hint.res_class : "(null)"));
- if (hint.res_name) XFree (hint.res_name);
- if (hint.res_class) XFree (hint.res_class);
+ fprintf (stderr, "%s: setgid %d -> %d failed\n", blurb(), egid, gid);
+ exit (1);
}
- fprintf (stderr, "%s: %d: unrecognised ClientMessage \"%s\" received\n",
- blurb(), screen, (str ? str : "(null)"));
- fprintf (stderr, "%s: %d: for window 0x%lx (%s)\n",
- blurb(), screen, (unsigned long) w, wdesc);
- if (str) XFree (str);
+ if (uid != euid && setgid (gid) != 0)
+ {
+ fprintf (stderr, "%s: setuid %d -> %d failed\n", blurb(), euid, uid);
+ exit (1);
+ }
-#endif /* 0 */
+ /* Return the message since this is before verbose_p is set or log opened. */
+ sprintf (msg, "setuid %d:%d -> %d:%d", euid, egid, uid, gid);
+ return msg;
}
-Bool
-handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p)
+int
+main (int argc, char **argv)
{
- saver_preferences *p = &si->prefs;
- Atom type = 0;
- Window window = event->xclient.window;
+ Display *dpy = 0;
+ char *dpy_str = getenv ("DISPLAY");
+ char *logfile = 0;
+ Bool sync_p = False;
+ Bool cmdline_verbose_val = False, cmdline_verbose_p = False;
+ Bool cmdline_splash_val = False, cmdline_splash_p = False;
+ const char *s;
+ const char *pmsg;
+ int i;
- /* Preferences might affect our handling of client messages. */
- maybe_reload_init_file (si);
+ progname = argv[0];
+ s = strrchr (progname, '/');
+ if (s) progname = s+1;
- if (event->xclient.message_type != XA_SCREENSAVER ||
- event->xclient.format != 32)
- {
- bogus_clientmessage_warning (si, event);
- return False;
- }
+ pmsg = disavow_privileges();
- type = event->xclient.data.l[0];
- if (type == XA_ACTIVATE)
- {
- if (until_idle_p)
- {
- if (p->mode == DONT_BLANK)
- {
- clientmessage_response(si, window, True,
- "ACTIVATE ClientMessage received in DONT_BLANK mode.",
- "screen blanking is currently disabled.");
- return False;
- }
+ fclose (stdin);
+ fix_fds();
- clientmessage_response(si, window, False,
- "ACTIVATE ClientMessage received.",
- "activating.");
- si->selection_mode = 0;
- si->demoing_p = False;
-
- if (si->throttled_p && p->verbose_p)
- fprintf (stderr, "%s: unthrottled.\n", blurb());
- si->throttled_p = False;
-
- if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
- {
- XForceScreenSaver (si->dpy, ScreenSaverActive);
- return False;
- }
- else
- {
- return True;
- }
- }
- clientmessage_response(si, window, True,
- "ClientMessage ACTIVATE received while already active.",
- "already active.");
- }
- else if (type == XA_DEACTIVATE)
+ for (i = 1; i < argc; i++)
{
+ const char *oa = argv[i];
+ /* XScreenSaver predates the "--arg" convention. */
+ if (argv[i][0] == '-' && argv[i][1] == '-')
+ argv[i]++;
- /* Regardless of whether the screen saver is active, a DEACTIVATE
- message should cause the monitor to become powered on. */
- monitor_power_on (si, True);
-
-# if 0
- /* When -deactivate is received while locked, pop up the dialog box
- instead of just ignoring it. Some people depend on this behavior
- to be able to unlock by using e.g. a fingerprint reader without
- also having to click the mouse first.
- */
- if (si->locked_p)
+ if (!strcmp (argv[i], "-debug"))
+ debug_p = True;
+ else if (!strcmp (argv[i], "-v") || !strcmp (argv[i], "-verbose"))
{
- clientmessage_response(si, window, False,
- "DEACTIVATE ClientMessage received while locked: ignored.",
- "screen is locked.");
+ verbose_p++;
+ cmdline_verbose_p = True, cmdline_verbose_val = verbose_p;
}
- else
-# endif /* 0 */
+ else if (!strcmp (argv[i], "-vv"))
{
- if (! until_idle_p)
- {
- if (si->throttled_p && p->verbose_p)
- fprintf (stderr, "%s: unthrottled.\n", blurb());
- si->throttled_p = False;
-
- clientmessage_response(si, window, False,
- "DEACTIVATE ClientMessage received.",
- "deactivating.");
- if (si->using_mit_saver_extension ||
- si->using_sgi_saver_extension)
- {
- XForceScreenSaver (si->dpy, ScreenSaverReset);
- return False;
- }
- else
- {
- return True;
- }
- }
- clientmessage_response(si, window, False,
- "ClientMessage DEACTIVATE received while inactive: "
- "resetting idle timer.",
- "not active: idle timer reset.");
- reset_timers (si);
+ verbose_p += 2;
+ cmdline_verbose_p = True, cmdline_verbose_val = verbose_p;
}
- }
- else if (type == XA_SUSPEND)
- {
- clientmessage_response(si, window, False,
- "SUSPEND ClientMessage received.",
- "suspending.");
- si->selection_mode = 0;
- si->demoing_p = False;
- si->throttled_p = True;
-
- /* When suspending, immediately lock, if locking enabled. */
-# ifndef NO_LOCKING
- if (p->lock_p && !si->locked_p && !si->locking_disabled_p)
+ else if (!strcmp (argv[i], "-vvv"))
{
- si->emergency_lock_p = True;
- if (p->verbose_p)
- fprintf (stderr, "%s: locking.\n", blurb());
- set_locked_p (si, True);
+ verbose_p += 3;
+ cmdline_verbose_p = True, cmdline_verbose_val = verbose_p;
}
-# endif
-
- /* When suspending, immediately power off the display. */
- monitor_power_on (si, False);
-
- if (until_idle_p)
- return True; /* Blank now */
- else
- return False; /* Do not unblank now */
- }
- else if (type == XA_CYCLE)
- {
- if (! until_idle_p)
- {
- clientmessage_response(si, window, False,
- "CYCLE ClientMessage received.",
- "cycling.");
- si->selection_mode = 0; /* 0 means randomize when its time. */
- si->demoing_p = False;
-
- if (si->throttled_p && p->verbose_p)
- fprintf (stderr, "%s: unthrottled.\n", blurb());
- si->throttled_p = False;
-
- if (si->cycle_id)
- XtRemoveTimeOut (si->cycle_id);
- si->cycle_id = 0;
- cycle_timer ((XtPointer) si, 0);
- return False;
- }
- clientmessage_response(si, window, True,
- "ClientMessage CYCLE received while inactive.",
- "not active.");
- }
- else if (type == XA_NEXT || type == XA_PREV)
- {
- clientmessage_response(si, window, False,
- (type == XA_NEXT
- ? "NEXT ClientMessage received."
- : "PREV ClientMessage received."),
- "cycling.");
- si->selection_mode = (type == XA_NEXT ? -1 : -2);
- si->demoing_p = False;
-
- if (si->throttled_p && p->verbose_p)
- fprintf (stderr, "%s: unthrottled.\n", blurb());
- si->throttled_p = False;
-
- if (! until_idle_p)
- {
- if (si->cycle_id)
- XtRemoveTimeOut (si->cycle_id);
- si->cycle_id = 0;
- cycle_timer ((XtPointer) si, 0);
- }
- else
- return True;
- }
- else if (type == XA_SELECT)
- {
- char buf [255];
- char buf2 [255];
- long which = event->xclient.data.l[1];
-
- if (p->mode == DONT_BLANK)
+ else if (!strcmp (argv[i], "-vvvv"))
{
- clientmessage_response(si, window, True,
- "SELECT ClientMessage received in DONT_BLANK mode.",
- "screen blanking is currently disabled.");
- return False;
+ verbose_p += 4;
+ cmdline_verbose_p = True, cmdline_verbose_val = verbose_p;
+ }
+ else if (!strcmp (argv[i], "-q") || !strcmp (argv[i], "-quiet"))
+ {
+ verbose_p = 0;
+ cmdline_verbose_p = True, cmdline_verbose_val = verbose_p;
+ }
+ else if (!strcmp (argv[i], "-splash"))
+ {
+ splash_p = True;
+ cmdline_splash_p = True, cmdline_splash_val = splash_p;
+ }
+ else if (!strcmp (argv[i], "-no-splash") ||
+ !strcmp (argv[i], "-nosplash"))
+ {
+ splash_p = False;
+ cmdline_splash_p = True, cmdline_splash_val = splash_p;
+ }
+ else if (!strcmp (argv[i], "-log"))
+ {
+ logfile = argv[++i];
+ if (!logfile) goto HELP;
+ }
+ else if (!strcmp (argv[i], "-d") ||
+ !strcmp (argv[i], "-dpy") ||
+ !strcmp (argv[i], "-disp") ||
+ !strcmp (argv[i], "-display"))
+ {
+ dpy_str = argv[++i];
+ if (!dpy_str) goto HELP;
+ }
+ else if (!strcmp (argv[i], "-sync") ||
+ !strcmp (argv[i], "-synch") ||
+ !strcmp (argv[i], "-synchronize") ||
+ !strcmp (argv[i], "-synchronise"))
+ sync_p = True;
+ else if (!strcmp (argv[i], "-h") || !strcmp (argv[i], "-help"))
+ {
+ HELP:
+ print_banner();
+ fprintf (stderr,
+ "\t\thttps://www.jwz.org/xscreensaver/\n"
+ "\n"
+ "\tOptions:\n\n"
+ "\t\t--dpy host:display.screen\n"
+ "\t\t--verbose\n"
+ "\t\t--no-splash\n"
+ "\t\t--log logfile\n"
+ "\n"
+ "\tRun 'xscreensaver-settings' to configure.\n"
+ "\n");
+ saver_exit (1);
}
-
- sprintf (buf, "SELECT %ld ClientMessage received.", which);
- sprintf (buf2, "activating (%ld).", which);
- clientmessage_response (si, window, False, buf, buf2);
-
- if (which < 0) which = 0; /* 0 == "random" */
- si->selection_mode = which;
- si->demoing_p = False;
-
- if (si->throttled_p && p->verbose_p)
- fprintf (stderr, "%s: unthrottled.\n", blurb());
- si->throttled_p = False;
-
- if (! until_idle_p)
- {
- if (si->cycle_id)
- XtRemoveTimeOut (si->cycle_id);
- si->cycle_id = 0;
- cycle_timer ((XtPointer) si, 0);
- }
- else
- return True;
- }
- else if (type == XA_EXIT)
- {
- /* Ignore EXIT message if the screen is locked. */
- if (until_idle_p || !si->locked_p)
- {
- clientmessage_response (si, window, False,
- "EXIT ClientMessage received.",
- "exiting.");
- if (! until_idle_p)
- {
- int i;
- for (i = 0; i < si->nscreens; i++)
- kill_screenhack (&si->screens[i]);
- unblank_screen (si);
- XSync (si->dpy, False);
- }
- saver_exit (si, 0, 0);
- }
- else
- clientmessage_response (si, window, True,
- "EXIT ClientMessage received while locked.",
- "screen is locked.");
- }
- else if (type == XA_RESTART)
- {
- /* The RESTART message works whether the screensaver is active or not,
- unless the screen is locked, in which case it doesn't work.
- */
- if (until_idle_p || !si->locked_p)
- {
- clientmessage_response (si, window, False,
- "RESTART ClientMessage received.",
- "restarting.");
- if (! until_idle_p)
- {
- int i;
- for (i = 0; i < si->nscreens; i++)
- kill_screenhack (&si->screens[i]);
- unblank_screen (si);
- XSync (si->dpy, False);
- }
-
- restart_process (si); /* does not return */
- abort();
- }
- else
- clientmessage_response (si, window, True,
- "RESTART ClientMessage received while locked.",
- "screen is locked.");
- }
- else if (type == XA_DEMO)
- {
- long arg = event->xclient.data.l[1];
- Bool demo_one_hack_p = (arg == 5000);
-
- if (demo_one_hack_p)
- {
- if (until_idle_p)
- {
- long which = event->xclient.data.l[2];
- char buf [255];
- char buf2 [255];
- sprintf (buf, "DEMO %ld ClientMessage received.", which);
- sprintf (buf2, "demoing (%ld).", which);
- clientmessage_response (si, window, False, buf, buf2);
-
- if (which < 0) which = 0; /* 0 == "random" */
- si->selection_mode = which;
- si->demoing_p = True;
-
- if (si->throttled_p && p->verbose_p)
- fprintf (stderr, "%s: unthrottled.\n", blurb());
- si->throttled_p = False;
-
- return True;
- }
-
- clientmessage_response (si, window, True,
- "DEMO ClientMessage received while active.",
- "already active.");
- }
- else
- {
- clientmessage_response (si, window, True,
- "obsolete form of DEMO ClientMessage.",
- "obsolete form of DEMO ClientMessage.");
- }
- }
- else if (type == XA_PREFS)
- {
- clientmessage_response (si, window, True,
- "the PREFS client-message is obsolete.",
- "the PREFS client-message is obsolete.");
- }
- else if (type == XA_LOCK)
- {
-#ifdef NO_LOCKING
- clientmessage_response (si, window, True,
- "not compiled with support for locking.",
- "locking not enabled.");
-#else /* !NO_LOCKING */
- if (si->locking_disabled_p)
- clientmessage_response (si, window, True,
- "LOCK ClientMessage received, but locking is disabled.",
- "locking not enabled.");
- else if (si->locked_p)
- clientmessage_response (si, window, True,
- "LOCK ClientMessage received while already locked.",
- "already locked.");
- else
- {
- char buf [255];
- char *response = (until_idle_p
- ? "activating and locking."
- : "locking.");
- sprintf (buf, "LOCK ClientMessage received; %s", response);
- clientmessage_response (si, window, False, buf, response);
-
- /* Have to set the time or xscreensaver-command doesn't report
- the LOCK state change. Must come before set_locked_p(). */
- si->blank_time = time ((time_t *) 0);
-
- /* Note that this leaves things in a slightly inconsistent state:
- we are blanked but not locked. And blanking might actually
- fail if we can't get the grab. */
- set_locked_p (si, True);
-
- si->selection_mode = 0;
- si->demoing_p = False;
-
- if (si->lock_id) /* we're doing it now, so lose the timeout */
- {
- XtRemoveTimeOut (si->lock_id);
- si->lock_id = 0;
- }
-
- if (until_idle_p)
- {
- if (si->using_mit_saver_extension ||
- si->using_sgi_saver_extension)
- {
- XForceScreenSaver (si->dpy, ScreenSaverActive);
- return False;
- }
- else
- {
- return True;
- }
- }
- }
-#endif /* !NO_LOCKING */
- }
- else if (type == XA_THROTTLE)
- {
- /* The THROTTLE command is deprecated -- it predates the XDPMS
- extension. Instead of using -throttle, users should instead
- just power off the monitor (e.g., "xset dpms force off".)
- In a few minutes, xscreensaver will notice that the monitor
- is off, and cease running hacks.
- */
- if (si->throttled_p)
- clientmessage_response (si, window, True,
- "THROTTLE ClientMessage received, but "
- "already throttled.",
- "already throttled.");
- else
- {
- char buf [255];
- char *response = "throttled.";
- si->throttled_p = True;
- si->selection_mode = 0;
- si->demoing_p = False;
- sprintf (buf, "THROTTLE ClientMessage received; %s", response);
- clientmessage_response (si, window, False, buf, response);
-
- if (! until_idle_p)
- {
- if (si->cycle_id)
- XtRemoveTimeOut (si->cycle_id);
- si->cycle_id = 0;
- cycle_timer ((XtPointer) si, 0);
- }
- }
- }
- else if (type == XA_UNTHROTTLE)
- {
- if (! si->throttled_p)
- clientmessage_response (si, window, True,
- "UNTHROTTLE ClientMessage received, but "
- "not throttled.",
- "not throttled.");
else
- {
- char buf [255];
- char *response = "unthrottled.";
- si->throttled_p = False;
- si->selection_mode = 0;
- si->demoing_p = False;
- sprintf (buf, "UNTHROTTLE ClientMessage received; %s", response);
- clientmessage_response (si, window, False, buf, response);
-
- if (! until_idle_p)
- {
- if (si->cycle_id)
- XtRemoveTimeOut (si->cycle_id);
- si->cycle_id = 0;
- cycle_timer ((XtPointer) si, 0);
- }
- }
+ {
+ fprintf (stderr, "\n%s: unknown option: %s\n\n", blurb(), oa);
+ goto HELP;
+ }
}
- else
+
+ if (logfile)
{
- char buf [1024];
- char *str;
- str = XGetAtomName_safe (si->dpy, type);
-
- if (str)
- {
- if (strlen (str) > 80)
- strcpy (str+70, "...");
- sprintf (buf, "unrecognised screensaver ClientMessage %s received.",
- str);
- free (str);
- }
- else
- {
- sprintf (buf,
- "unrecognised screensaver ClientMessage 0x%x received.",
- (unsigned int) event->xclient.data.l[0]);
- }
+ int stdout_fd = 1;
+ int stderr_fd = 2;
+ int fd = open (logfile, O_WRONLY | O_APPEND | O_CREAT, 0666);
+ if (fd < 0)
+ {
+ char buf[255];
+ FAIL:
+ sprintf (buf, "%.100s: %.100s", blurb(), logfile);
+ perror (buf);
+ fflush (stderr);
+ fflush (stdout);
+ saver_exit (1);
+ }
- clientmessage_response (si, window, True, buf, buf);
- }
- return False;
-}
+ fprintf (stderr, "%s: logging to file %s\n", blurb(), logfile);
-
-/* Some random diagnostics printed in -verbose mode.
- */
+ if (dup2 (fd, stdout_fd) < 0) goto FAIL;
+ if (dup2 (fd, stderr_fd) < 0) goto FAIL;
-static void
-analyze_display (saver_info *si)
-{
- int i, j;
- static struct {
- const char *name; const char *desc;
- Bool useful_p;
- Status (*version_fn) (Display *, int *majP, int *minP);
- } exts[] = {
-
- { "SCREEN_SAVER", /* underscore */ "SGI Screen-Saver",
-# ifdef HAVE_SGI_SAVER_EXTENSION
- True, 0
-# else
- False, 0
-# endif
- }, { "SCREEN-SAVER", /* dash */ "SGI Screen-Saver",
-# ifdef HAVE_SGI_SAVER_EXTENSION
- True, 0
-# else
- False, 0
-# endif
- }, { "MIT-SCREEN-SAVER", "MIT Screen-Saver",
-# ifdef HAVE_MIT_SAVER_EXTENSION
- True, XScreenSaverQueryVersion
-# else
- False, 0
-# endif
- }, { "XIDLE", "XIdle",
-# ifdef HAVE_XIDLE_EXTENSION
- True, 0
-# else
- False, 0
-# endif
- }, { "SGI-VIDEO-CONTROL", "SGI Video-Control",
-# ifdef HAVE_SGI_VC_EXTENSION
- True, XSGIvcQueryVersion
-# else
- False, 0
-# endif
- }, { "READDISPLAY", "SGI Read-Display",
-# ifdef HAVE_READ_DISPLAY_EXTENSION
- True, XReadDisplayQueryVersion
-# else
- False, 0
-# endif
- }, { "MIT-SHM", "Shared Memory",
-# ifdef HAVE_XSHM_EXTENSION
- True, (Status (*) (Display*,int*,int*)) XShmQueryVersion /* 4 args */
-# else
- False, 0
-# endif
- }, { "DOUBLE-BUFFER", "Double-Buffering",
-# ifdef HAVE_DOUBLE_BUFFER_EXTENSION
- True, XdbeQueryExtension
-# else
- False, 0
-# endif
- }, { "DPMS", "Power Management",
-# ifdef HAVE_DPMS_EXTENSION
- True, DPMSGetVersion
-# else
- False, 0
-# endif
- }, { "GLX", "GLX",
-# ifdef HAVE_GL
- True, 0
-# else
- False, 0
-# endif
- }, { "XFree86-VidModeExtension", "XF86 Video-Mode",
-# ifdef HAVE_XF86VMODE
- True, XF86VidModeQueryVersion
-# else
- False, 0
-# endif
- }, { "XC-VidModeExtension", "XC Video-Mode",
-# ifdef HAVE_XF86VMODE
- True, XF86VidModeQueryVersion
-# else
- False, 0
-# endif
- }, { "XFree86-MISC", "XF86 Misc",
-# ifdef HAVE_XF86MISCSETGRABKEYSSTATE
- True, XF86MiscQueryVersion
-# else
- False, 0
-# endif
- }, { "XC-MISC", "XC Misc",
-# ifdef HAVE_XF86MISCSETGRABKEYSSTATE
- True, XF86MiscQueryVersion
-# else
- False, 0
-# endif
- }, { "XINERAMA", "Xinerama",
-# ifdef HAVE_XINERAMA
- True, XineramaQueryVersion
-# else
- False, 0
-# endif
- }, { "RANDR", "Resize-and-Rotate",
-# ifdef HAVE_RANDR
- True, XRRQueryVersion
-# else
- False, 0
-# endif
- }, { "DRI", "DRI",
- True, 0
- }, { "NV-CONTROL", "NVidia",
- True, 0
- }, { "NV-GLX", "NVidia GLX",
- True, 0
- }, { "Apple-DRI", "Apple-DRI (XDarwin)",
- True, 0
- }, { "XInputExtension", "XInput",
- True, 0
- },
- };
-
- fprintf (stderr, "%s: running on display \"%s\"\n", blurb(),
- DisplayString(si->dpy));
- fprintf (stderr, "%s: vendor is %s, %d.\n", blurb(),
- ServerVendor(si->dpy), VendorRelease(si->dpy));
-
- fprintf (stderr, "%s: useful extensions:\n", blurb());
- for (i = 0; i < countof(exts); i++)
- {
- int op = 0, event = 0, error = 0;
- char buf [255];
- int maj = 0, min = 0;
- int dummy1, dummy2, dummy3;
+ fprintf (stderr, "\n\n"
+ "#####################################"
+ "#####################################\n"
+ "%s: logging to \"%s\"\n"
+ "#####################################"
+ "#####################################\n"
+ "\n",
+ blurb(), logfile);
- /* Most of the extension version functions take 3 args,
- writing results into args 2 and 3, but some take more.
- We only ever care about the first two results, but we
- pass in three extra pointers just in case.
- */
- Status (*version_fn_2) (Display*,int*,int*,int*,int*,int*) =
- (Status (*) (Display*,int*,int*,int*,int*,int*)) exts[i].version_fn;
+ if (!verbose_p)
+ verbose_p = True;
+ cmdline_verbose_val = verbose_p;
+ }
- if (!XQueryExtension (si->dpy, exts[i].name, &op, &event, &error))
- continue;
- sprintf (buf, "%s: ", blurb());
- strcat (buf, exts[i].desc);
+ save_argv (argc, argv);
+ hack_environment();
+ print_banner();
+ read_init_files (True);
- if (!version_fn_2)
- ;
- else if (version_fn_2 (si->dpy, &maj, &min, &dummy1, &dummy2, &dummy3))
- sprintf (buf+strlen(buf), " (%d.%d)", maj, min);
- else
- strcat (buf, " (unavailable)");
+ /* Command line overrides init file */
+ if (cmdline_verbose_p) verbose_p = cmdline_verbose_val;
+ if (cmdline_splash_p) splash_p = cmdline_splash_val;
- if (!exts[i].useful_p)
- strcat (buf, " (disabled at compile time)");
- fprintf (stderr, "%s\n", buf);
- }
+ if (verbose_p)
+ fprintf (stderr, "%s: running in process %lu\n", blurb(),
+ (unsigned long) getpid());
-# ifdef HAVE_LIBSYSTEMD
- fprintf (stderr, "%s: libsystemd\n", blurb());
-# else
- fprintf (stderr, "%s: libsystemd (disabled at compile time)\n", blurb());
-# endif
+ if (verbose_p && pmsg)
+ fprintf (stderr, "%s: %s\n", blurb(), pmsg);
- for (i = 0; i < si->nscreens; i++)
+ if (! dpy_str)
{
- saver_screen_info *ssi = &si->screens[i];
- unsigned long colormapped_depths = 0;
- unsigned long non_mapped_depths = 0;
- XVisualInfo vi_in, *vi_out;
- int out_count;
-
- if (!ssi->real_screen_p) continue;
-
- vi_in.screen = ssi->real_screen_number;
- vi_out = XGetVisualInfo (si->dpy, VisualScreenMask, &vi_in, &out_count);
- if (!vi_out) continue;
- for (j = 0; j < out_count; j++) {
- if (vi_out[j].depth >= 32) continue;
- if (vi_out[j].class == PseudoColor)
- colormapped_depths |= (1 << vi_out[j].depth);
- else
- non_mapped_depths |= (1 << vi_out[j].depth);
- }
- XFree ((char *) vi_out);
-
- if (colormapped_depths)
- {
- fprintf (stderr, "%s: screen %d colormapped depths:", blurb(),
- ssi->real_screen_number);
- for (j = 0; j < 32; j++)
- if (colormapped_depths & (1 << j))
- fprintf (stderr, " %d", j);
- fprintf (stderr, ".\n");
- }
- if (non_mapped_depths)
- {
- fprintf (stderr, "%s: screen %d non-colormapped depths:",
- blurb(), ssi->real_screen_number);
- for (j = 0; j < 32; j++)
- if (non_mapped_depths & (1 << j))
- fprintf (stderr, " %d", j);
- fprintf (stderr, ".\n");
- }
+ dpy_str = ":0.0";
+ fprintf (stderr,
+ "%s: warning: $DISPLAY is not set: defaulting to \"%s\"\n",
+ blurb(), dpy_str);
}
- describe_monitor_layout (si);
-}
+ /* Copy the -dpy arg to $DISPLAY for subprocesses. */
+ {
+ char *s = (char *) malloc (strlen(dpy_str) + 20);
+ sprintf (s, "DISPLAY=%s", dpy_str);
+ putenv (s);
+ /* free (s); */ /* some versions of putenv do not copy */
+ }
+ dpy = XOpenDisplay (dpy_str);
+ if (!dpy) saver_exit (1);
-Bool
-display_is_on_console_p (saver_info *si)
-{
- Bool not_on_console = True;
- char *dpystr = DisplayString (si->dpy);
- char *tail = (char *) strchr (dpystr, ':');
- if (! tail || strncmp (tail, ":0", 2))
- not_on_console = True;
- else
+ if (sync_p)
{
- char dpyname[255], localname[255];
- strncpy (dpyname, dpystr, tail-dpystr);
- dpyname [tail-dpystr] = 0;
- if (!*dpyname ||
- !strcmp(dpyname, "unix") ||
- !strcmp(dpyname, "localhost"))
- not_on_console = False;
- else if (gethostname (localname, sizeof (localname)))
- not_on_console = True; /* can't find hostname? */
- else if (!strncmp (dpyname, "/tmp/launch-", 12)) /* MacOS X launchd */
- not_on_console = False;
- else
- {
- /* We have to call gethostbyname() on the result of gethostname()
- because the two aren't guarenteed to be the same name for the
- same host: on some losing systems, one is a FQDN and the other
- is not. Here in the wide wonderful world of Unix it's rocket
- science to obtain the local hostname in a portable fashion.
-
- And don't forget, gethostbyname() reuses the structure it
- returns, so we have to copy the fucker before calling it again.
- Thank you master, may I have another.
- */
- struct hostent *h = gethostbyname (dpyname);
- if (!h)
- not_on_console = True;
- else
- {
- char hn [255];
- struct hostent *l;
- strcpy (hn, h->h_name);
- l = gethostbyname (localname);
- not_on_console = (!l || !!(strcmp (l->h_name, hn)));
- }
- }
+ XSynchronize (dpy, True);
+ XSync (dpy, False);
}
- return !not_on_console;
-}
+ XSetErrorHandler (error_handler);
-/* Do a little bit of heap introspection...
- */
-void
-check_for_leaks (const char *where)
-{
-#if defined(HAVE_SBRK) && defined(LEAK_PARANOIA)
- static unsigned long last_brk = 0;
- int b = (unsigned long) sbrk(0);
- if (last_brk && last_brk < b)
- fprintf (stderr, "%s: %s: brk grew by %luK.\n",
- blurb(), where,
- (((b - last_brk) + 1023) / 1024));
- last_brk = b;
-#endif /* HAVE_SBRK */
+ main_loop (dpy);
+ saver_exit (0);
+ return (1);
}
diff --git a/driver/xscreensaver.h b/driver/xscreensaver.h
index bed6537..4bd84b4 100644
--- a/driver/xscreensaver.h
+++ b/driver/xscreensaver.h
@@ -1,4 +1,4 @@
-/* xscreensaver, Copyright (c) 1993-2019 Jamie Zawinski <jwz@jwz.org>
+/* 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
@@ -24,35 +24,22 @@
# include <signal.h> /* for sigset_t */
#endif
-#include "prefs.h"
+#include "blurb.h"
+#include "types.h"
-extern char *progname;
extern char *progclass;
#undef countof
#define countof(x) (sizeof((x))/sizeof((*x)))
-
-
/* =======================================================================
server extensions and virtual roots
======================================================================= */
-extern Bool restore_real_vroot (saver_info *si);
-extern void disable_builtin_screensaver (saver_info *, Bool unblank_screen_p);
extern Bool ensure_no_screensaver_running (Display *, Screen *);
-#ifdef HAVE_PROC_INTERRUPTS
-extern Bool query_proc_interrupts_available (saver_info *, const char **why);
-#endif
-
-#ifdef HAVE_XINPUT
-extern Bool query_xinput_extension (saver_info *);
-extern void init_xinput_extension (saver_info *si);
-#endif
-
/* Display Power Management System (DPMS) interface. */
-extern Bool monitor_powered_on_p (saver_info *si);
+extern Bool monitor_powered_on_p (Display *);
extern void monitor_power_on (saver_info *si, Bool on_p);
@@ -62,12 +49,8 @@ extern void monitor_power_on (saver_info *si, Bool on_p);
extern Bool update_screen_layout (saver_info *si);
extern void initialize_screensaver_window (saver_info *si);
-extern void initialize_screen_root_widget (saver_screen_info *ssi);
-extern void raise_window (saver_info *si,
- Bool inhibit_fade, Bool between_hacks_p,
- Bool dont_clear);
-extern Bool blank_screen (saver_info *si);
+extern void blank_screen (saver_info *si);
extern void unblank_screen (saver_info *si);
extern void resize_screensaver_window (saver_info *si);
@@ -79,73 +62,17 @@ extern void get_screen_viewport (saver_screen_info *ssi,
/* =======================================================================
- locking
- ======================================================================= */
-
-#ifndef NO_LOCKING
-extern Bool unlock_p (saver_info *si);
-extern Bool lock_priv_init (int argc, char **argv, Bool verbose_p);
-extern Bool lock_init (int argc, char **argv, Bool verbose_p);
-extern Bool passwd_valid_p (const char *typed_passwd, Bool verbose_p);
-#endif /* NO_LOCKING */
-
-extern void set_locked_p (saver_info *si, Bool locked_p);
-extern int move_mouse_grab (saver_info *si, Window to, Cursor cursor,
- int to_screen_no);
-extern int mouse_screen (saver_info *si);
-
-
-/* =======================================================================
- runtime privileges
- ======================================================================= */
-
-extern void hack_uid (saver_info *si);
-extern void describe_uids (saver_info *si, FILE *out);
-
-/* =======================================================================
- demoing
- ======================================================================= */
-
-extern 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);
-extern int string_width (XFontStruct *font, char *s);
-
-extern void make_splash_dialog (saver_info *si);
-extern void handle_splash_event (saver_info *si, XEvent *e);
-extern XFontStruct *splash_load_font (Display *, char *name, char *class);
-
-
-/* =======================================================================
timers
======================================================================= */
-extern void start_notice_events_timer (saver_info *, Window, Bool verbose_p);
extern void cycle_timer (XtPointer si, XtIntervalId *id);
-extern void activate_lock_timer (XtPointer si, XtIntervalId *id);
-extern void reset_watchdog_timer (saver_info *si, Bool on_p);
-extern void idle_timer (XtPointer si, XtIntervalId *id);
-extern void de_race_timer (XtPointer si, XtIntervalId *id);
extern void sleep_until_idle (saver_info *si, Bool until_idle_p);
-extern void reset_timers (saver_info *si);
-extern void schedule_wakeup_event (saver_info *si, Time when, Bool verbose_p);
-
-
-/* =======================================================================
- remote control
- ======================================================================= */
-extern Bool handle_clientmessage (saver_info *, XEvent *, Bool);
-extern void maybe_reload_init_file (saver_info *);
/* =======================================================================
subprocs
======================================================================= */
-extern void handle_signals (saver_info *si);
#ifdef HAVE_SIGACTION
extern sigset_t block_sigchld (void);
#else /* !HAVE_SIGACTION */
@@ -153,61 +80,22 @@ extern void handle_signals (saver_info *si);
#endif /* !HAVE_SIGACTION */
extern void unblock_sigchld (void);
extern void hack_environment (saver_info *si);
-extern void hack_subproc_environment (Screen *, Window saver_window);
-extern void init_sigchld (void);
+extern void init_sigchld (saver_info *si);
extern void spawn_screenhack (saver_screen_info *ssi);
-extern pid_t fork_and_exec (saver_screen_info *ssi, const char *command);
-extern pid_t fork_and_exec_1 (saver_info *si, saver_screen_info *ssi,
- const char *command);
extern void kill_screenhack (saver_screen_info *ssi);
-int kill_job (saver_info *si, pid_t pid, int signal);
-extern void suspend_screenhack (saver_screen_info *ssi, Bool suspend_p);
-extern Bool screenhack_running_p (saver_info *si);
-extern void emergency_kill_subproc (saver_info *si);
+extern Bool any_screenhacks_running_p (saver_info *si);
extern Bool select_visual (saver_screen_info *ssi, const char *visual_name);
extern void store_saver_status (saver_info *si);
extern const char *signal_name (int signal);
-
-/* =======================================================================
- subprocs diagnostics
- ======================================================================= */
-
-extern FILE *real_stderr;
-extern FILE *real_stdout;
-extern void stderr_log_file (saver_info *si);
-extern void initialize_stderr (saver_info *si);
-extern void reset_stderr (saver_screen_info *ssi);
-extern void clear_stderr (saver_screen_info *ssi);
-extern void shutdown_stderr (saver_info *si);
-
+extern void screenhack_obituary (saver_screen_info *,
+ const char *name, const char *error);
/* =======================================================================
misc
======================================================================= */
-extern const char *blurb (void);
-extern void save_argv (int argc, char **argv);
-extern void saver_exit (saver_info *si, int status, const char *core_reason);
-extern void restart_process (saver_info *si);
-
-extern int saver_ehandler (Display *dpy, XErrorEvent *error);
-extern int BadWindow_ehandler (Display *dpy, XErrorEvent *error);
-extern Bool window_exists_p (Display *dpy, Window window);
-extern Bool in_signal_handler_p;
-extern char *timestring (time_t);
-extern Bool display_is_on_console_p (saver_info *si);
extern Visual *get_best_gl_visual (saver_info *si, Screen *screen);
-extern void check_for_leaks (const char *where);
-extern void describe_monitor_layout (saver_info *si);
-
-#ifdef HAVE_XF86VMODE
-Bool safe_XF86VidModeGetViewPort (Display *, int, int *, int *);
-#endif /* HAVE_XF86VMODE */
-
-extern Atom XA_VROOT, XA_XSETROOT_ID, XA_ESETROOT_PMAP_ID, XA_XROOTPMAP_ID;
-extern Atom XA_NET_WM_USER_TIME;
-extern Atom XA_SCREENSAVER, XA_SCREENSAVER_VERSION, XA_SCREENSAVER_ID;
-extern Atom XA_SCREENSAVER_STATUS, XA_LOCK, XA_BLANK;
-extern Atom XA_DEMO, XA_PREFS;
+extern void maybe_reload_init_file (saver_info *);
+void print_available_extensions (saver_info *);
#endif /* __XSCREENSAVER_H__ */
diff --git a/driver/xscreensaver.man b/driver/xscreensaver.man
index 0679ff4..9c3bdd0 100644
--- a/driver/xscreensaver.man
+++ b/driver/xscreensaver.man
@@ -1,208 +1,149 @@
-.de EX \"Begin example
-.ne 5
-.if n .sp 1
-.if t .sp .5
-.nf
-.in +.5i
-..
-.de EE
-.fi
-.in -.5i
-.if n .sp 1
-.if t .sp .5
-..
-.TH XScreenSaver 1 "6-Jun-2019 (5.43)" "X Version 11"
+.TH XScreenSaver 1 "6-Jan-2021 (6.00)" "X Version 11"
.SH NAME
xscreensaver - extensible screen saver and screen locking framework
.SH SYNOPSIS
.B xscreensaver
-[\-display \fIhost:display.screen\fP] \
-[\-verbose] \
-[\-no\-splash] \
-[\-no\-capture\-stderr] \
-[\-log \fIfilename\fP]
+[\-\-display \fIhost:display.screen\fP] \
+[\-\-verbose] \
+[\-\-no\-splash] \
+[\-\-log \fIfilename\fP]
.SH DESCRIPTION
-The \fIxscreensaver\fP program waits until the keyboard and mouse have been
-idle for a period, and then runs a graphics demo chosen at random. It
-turns off as soon as there is any mouse or keyboard activity.
-
-This program can lock your terminal in order to prevent others from using it,
-though its default mode of operation is merely to display pretty pictures on
-your screen when it is not in use.
+XScreenSaver waits until the user is idle, and then runs graphics demos chosen
+at random. It can also lock your screen, and provides configuration and
+control of display power management.
-It also provides configuration and control of your monitor's power-saving
-features.
+XScreenSaver is also available on macOS, iOS and Android.
.SH GETTING STARTED
-For the impatient, try this:
-.EX
-xscreensaver &
-xscreensaver-demo
-.EE
-The
-.BR xscreensaver-demo (1)
-program pops up a dialog box that lets you configure the screen saver,
-and experiment with the various display modes.
-
-.B Note that xscreensaver has a client-server model:
-the \fIxscreensaver\fP program is a daemon that runs in the background;
-it is controlled by the foreground
-.BR xscreensaver-demo (1)
-and
-.BR xscreensaver-command (1)
-programs.
-.SH CONFIGURATION
-The easiest way to configure \fIxscreensaver\fP is to simply run the
-.BR xscreensaver-demo (1)
-program, and change the settings through the GUI. The rest of this
-manual page describes lower level ways of changing settings.
-
-I'll repeat that because it's important:
+XScreenSaver is a daemon that runs in the background. You configure it
+with the
+.BR xscreensaver\-settings (1)
+program.
+.nf
+.sp
+ xscreensaver &
+ xscreensaver-settings
+.sp
+.fi
-.RS 4
-The easy way to configure xscreensaver is to run the
-.BR xscreensaver-demo (1)
-program. You shouldn't need to know any of the stuff described
-in \fIthis\fP manual unless you are trying to do something tricky,
-like customize xscreensaver for site-wide use or something.
-.RE
+.SH HOW IT WORKS
+When it is time to activate the screensaver, a full-screen black window is
+created that covers each monitor. A sub-process is launched for each one
+running a graphics demo, pointed at the appropriate window. Because of this,
+any program which can draw on a provided window can be used as a screensaver.
+The various graphics demos are, in fact, just standalone programs that do
+that.
-Options to \fIxscreensaver\fP are stored in one of two places: in
-a \fI.xscreensaver\fP file in your home directory; or in the X resource
-database. If the \fI.xscreensaver\fP file exists, it overrides any settings
-in the resource database.
+When the user becomes active again, the screensaver windows are unmapped, and
+the running subprocesses are killed.
-The syntax of the \fI.xscreensaver\fP file is similar to that of
-the \fI.Xdefaults\fP file; for example, to set the \fItimeout\fP parameter
-in the \fI.xscreensaver\fP file, you would write the following:
-.EX
-timeout: 5
-.EE
-whereas, in the \fI.Xdefaults\fP file, you would write
-.EX
-xscreensaver.timeout: 5
-.EE
-If you change a setting in the \fI.xscreensaver\fP file while xscreensaver
-is already running, it will notice this, and reload the file. (The file will
-be reloaded the next time the screen saver needs to take some action, such as
-blanking or unblanking the screen, or picking a new graphics mode.)
+The display modes are run at a low process priority, and spend most of their
+time sleeping/idle by default, so they should not consume significant system
+resources.
-If you change a setting in your X resource database, or if you want
-xscreensaver to notice your changes immediately instead of the next time
-it wakes up, then you will need to reload your \fI.Xdefaults\fP file,
-and then tell the running xscreensaver process to restart itself, like so:
-.EX
-xrdb < ~/.Xdefaults
-xscreensaver-command -restart
-.EE
-If you want to set the system-wide defaults, then make your edits to
-the xscreensaver app-defaults file, which should have been installed
-when xscreensaver itself was installed. The app-defaults file will
-usually be named /usr/lib/X11/app-defaults/XScreenSaver, but different
-systems might keep it in a different place (for example,
-/usr/openwin/lib/app-defaults/XScreenSaver on Solaris).
-
-When settings are changed in the Preferences dialog box (see above)
-the current settings will be written to the \fI.xscreensaver\fP file.
-(The \fI.Xdefaults\fP file and the app-defaults file will never be
-written by xscreensaver itself.)
.SH COMMAND-LINE OPTIONS
-.I xscreensaver
-also accepts a few command-line options, mostly for use when debugging:
-for normal operation, you should configure things via the \fI~/.xscreensaver\fP
-file.
.TP 8
-.B \-display \fIhost:display.screen\fP
+.B \-\-display \fIhost:display.screen\fP
The X display to use. For displays with multiple screens, XScreenSaver
-will manage all screens on the display simultaniously.
+will manage all screens on the display simultaneously.
.TP 8
-.B \-verbose
-Same as setting the \fIverbose\fP resource to \fItrue\fP: print diagnostics
-on stderr and on the xscreensaver window.
+.B \-\-verbose
+Print diagnostics to stderr.
.TP 8
-.B \-no-capture-stderr
-Do not redirect the stdout and stderr streams to the xscreensaver window
-itself. If xscreensaver is crashing, you might need to do this in order
-to see the error message.
+.B \-\-log \fIfilename\fP
+Append all diagnostic output to the given file. This also
+implies \fI\-\-verbose\fP. Use this when reporting bugs.
.TP 8
-.B \-log \fIfilename\fP
-This is exactly the same as redirecting stdout and stderr to the given
-file (for append). This is useful when reporting bugs.
-.SH HOW IT WORKS
-When it is time to activate the screensaver, a full-screen black window is
-created on each screen of the display. Each window is created in such a way
-that, to any subsequently-created programs, it will appear to be a "virtual
-root" window. Because of this, any program which draws on the root
-window (and which understands virtual roots) can be used as a screensaver.
-The various graphics demos are, in fact, just standalone programs that
-know how to draw on the provided window.
-
-When the user becomes active again, the screensaver windows are unmapped, and
-the running subprocesses are killed by sending them \fBSIGTERM\fP. This is
-also how the subprocesses are killed when the screensaver decides that it's
-time to run a different demo: the old one is killed and a new one is launched.
+.B \-\-no\-splash
+Don't display the splash screen at startup.
-You can control a running screensaver process by using the
-.BR xscreensaver\-command (1)
-program (which see).
.SH POWER MANAGEMENT
-Modern X servers contain support to power down the monitor after an idle
-period. If the monitor has powered down, then \fIxscreensaver\fP will
-notice this (after a few minutes), and will not waste CPU by drawing
-graphics demos on a black screen. An attempt will also be made to
-explicitly power the monitor back up as soon as user activity is detected.
-
-The \fI~/.xscreensaver\fP file controls the configuration of your
-display's power management settings: if you have used
-.BR xset (1)
-to change your power management settings, then xscreensaver will
-override those changes with the values specified
-in \fI~/.xscreensaver\fP (or with its built-in defaults, if there
-is no \fI~/.xscreensaver\fP file yet).
-
-To change your power management settings, run
-.BR xscreensaver\-demo (1)
-and change the various timeouts through the user interface.
-Alternatively, you can edit the \fI~/.xscreensaver\fP file directly.
+The
+.BR xscreensaver\-settings (1)
+program is where you configure if and when your monitor should power off.
+It saves the settings in your \fI~/.xscreensaver\fP file.
If the power management section is grayed out in the
-.BR xscreensaver\-demo (1)
+.BR xscreensaver\-settings (1)
window, then that means that your X server does not support
the XDPMS extension, and so control over the monitor's power state
is not available.
-If you're using a laptop, don't be surprised if changing the DPMS
-settings has no effect: many laptops have monitor power-saving behavior
-built in at a very low level that is invisible to Unix and X. On such
-systems, you can typically adjust the power-saving delays only by
-changing settings in the BIOS in some hardware-specific way.
+When the monitor is powered down, the display hacks are stopped
+(though it may take a minute or two for XScreenSaver to notice).
-If DPMS seems not to be working with XFree86, make sure the "DPMS"
-option is set in your \fI/etc/X11/XF86Config\fP file. See the
-.BR XF86Config (5)
-manual for details.
-.SH USING GNOME OR UNITY
-For the better part of a decade, GNOME shipped xscreensaver as-is,
-and everything just worked out of the box. In 2005, however, they
-decided to re-invent the wheel and ship their own replacement for
-the \fIxscreensaver\fP daemon called "\fIgnome-screensaver\fP",
-rather than improving xscreensaver and contributing their changes
-back. As a result, the "\fIgnome-screensaver\fP" program is insecure,
-bug-ridden, and missing many features of xscreensaver. You shouldn't
-use it.
-
-To replace gnome-screensaver with xscreensaver:
+Note: if you use
+.BR xset (1)
+to change the power management settings, XScreenSaver will override those
+changes. Whatever is in the \fI~/.xscreensaver\fP file takes precedence.
+
+.SH LAPTOP LIDS
+If your system has
+.BR systemd (1)
+221 or newer, or
+.BR elogind (8),
+then closing the lid of your laptop will cause the screen to lock immediately.
+
+If not, then the screen might not lock until a few seconds \fIafter\fP you
+re-open the lid. Which is less than ideal. So if you don't
+use \fIsystemd\fP, you might want to get in the habit of
+doing \fIxscreensaver-command --lock\fP before closing the lid.
+
+.SH PLAYING VIDEOS
+Likewise, if you have
+.BR systemd (1)
+221 or newer, or
+.BR elogind (8),
+then all of the popular video players and web browsers will
+prevent XScreenSaver from blanking the screen while video is playing.
+
+Both of these features require that
+.BR xscreensaver\-systemd (MANSUFFIX)
+be able connect to the systemd bus. Parts of KDE and GNOME may need to be
+disabled first for that to work; see below.
+
+.SH INSTALLATION
+Each desktop environment has its own system for launching long-running
+daemons like XScreenSaver, and since many of them come bundled with
+their own (buggy, insecure, inferior) screen-locking frameworks, it is
+also necessary to disable those other frameworks before XScreenSaver
+can work.
+
+.SS INSTALLING XSCREENSAVER ON GNOME OR UNITY
+For many years, GNOME shipped XScreenSaver as-is, and everything just worked.
+In 2005, however, they decided to needlessly re-invent the wheel and ship
+their own replacement for the \fIxscreensaver\fP daemon called
+.BR gnome-screensaver (1)
+rather than improving XScreenSaver and contributing their changes back. As a
+result, the \fIgnome-screensaver\fP program is insecure, bug-ridden, and
+missing many features of XScreenSaver. In fact, in 2011 it lost the ability
+to run display modes at all.
+
+In 2012 some distros forked and renamed it as both
+.BR mate-screensaver (1)
+and
+.BR cinnamon-screensaver (1),
+which seem to be basically the same.
+
+To replace gnome-screensaver with XScreenSaver:
.RS 4
.TP 3
-\fB1: Fully uninstall the gnome-screensaver package.\fP
-.EX
-sudo apt-get remove gnome-screensaver
-.EE
-or possibly
-.EX
-sudo dpkg -P gnome-screensaver
-.EE
+\fB1: Fully uninstall the other screen saver packages:\fP
+.nf
+.sp
+ sudo apt-get remove gnome-screensaver
+ sudo apt-get remove mate-screensaver
+ sudo apt-get remove cinnamon-screensaver
+or
+ sudo rpm -e gnome-screensaver
+ sudo rpm -e mate-screensaver
+ sudo rpm -e cinnamon-screensaver
+.sp
+.fi
+Be careful that it doesn't try to uninstall all of GNOME.
+
.TP 3
-\fB2: Launch xscreensaver at login.\fP
+\fB2: Launch XScreenSaver at login.\fP
+
Select "\fIStartup Applications\fP" from the menu (or manually
launch "\fIgnome-session-properties\fP") and add "\fIxscreensaver\fP".
@@ -210,27 +151,49 @@ Do this as your normal user account, not as root.
(This should go without saying, because you should never, ever, ever
be logged in to the graphical desktop as user "root".)
.TP 3
-\fB3: Make GNOME's "Lock Screen" use xscreensaver.\fP
-.EX
-sudo ln -sf /usr/bin/xscreensaver-command \\
+\fB3: Make GNOME's "Lock Screen" use XScreenSaver.\fP
+.nf
+.sp
+ sudo ln -sf /usr/bin/xscreensaver-command \\
/usr/bin/gnome-screensaver-command
-.EE
+.sp
+.fi
That doesn't work under Unity, though. Apparently it has its own
built-in screen locker which is not gnome-screensaver, and cannot be
removed, and yet still manages to be bug-addled and insecure.
Keep reinventing that wheel, guys! (If you have figured out how to
-replace Unity's locking "feature" with xscreensaver, let me know.)
+replace Unity's locking "feature" with XScreenSaver, let me know.)
+
.TP 3
\fB4: Turn off Unity's built-in blanking.\fP
+
Open "\fISystem Settings / Brightness & Lock\fP";
.br
Un-check "\fIStart Automatically\fP";
.br
Set \fI"Turn screen off when inactive for"\fP to \fI"Never".\fP
-.SH USING KDE
+.br
+Or possibly that has been randomly renamed again:
+.br
+Set "\fISettings / Power / Power Settings\fP" to \fI"Never".\fP
+.TP 3
+\fB5: Stop GNOME from blocking XScreenSaver's "systemd" integration:\fP
+.nf
+.sp
+ sudo systemctl \-\-user mask gsd\-screensaver\-proxy.service
+.sp
+.fi
+Without the above, video players will not be able to tell XScreenSaver
+not to blank the screen while videos are playing, and the screen will not
+auto-lock when you close your laptop's lid.
+
+After running that command, reboot. Yes, you have to reboot; it won't let
+you simply stop the service. Logging out won't do it either.
+
+.SS INSTALLING XSCREENSAVER ON KDE
Like GNOME, KDE also decided to invent their own screen saver framework
-from scratch instead of simply using xscreensaver. To replace the KDE
-screen saver with xscreensaver, do the following:
+from scratch instead of simply using XScreenSaver. To replace the KDE
+screen saver with XScreenSaver, do the following:
.RS 4
.TP 3
\fB1: Turn off KDE's screen saver.\fP
@@ -257,19 +220,21 @@ add "\fI/usr/bin/xscreensaver\fP".
If you are lucky, that will create a \fI"xscreensaver.desktop"\fP file
for you in \fI~/.config/autostart/\fP or \fI~/.kde/Autostart/\fP.
.TP 3
-\fB3: Make xscreensaver be an Autostart program.\fP
+\fB3: Make XScreenSaver be an Autostart program.\fP
If it does not already exist, create a file in your autostart directory
called \fIxscreensaver.desktop\fP that contains the following six lines:
-.EX
-[Desktop Entry]
-Exec=xscreensaver
-Name=XScreenSaver
-Type=Application
-StartupNotify=false
-X-KDE-StartupNotify=false
-.EE
+.nf
+.sp
+ [Desktop Entry]
+ Exec=xscreensaver
+ Name=XScreenSaver
+ Type=Application
+ StartupNotify=false
+ X-KDE-StartupNotify=false
+.sp
+.fi
.TP 3
-\fB4: Make the various "lock session" buttons call xscreensaver.\fP
+\fB4: Make the various "lock session" buttons call XScreenSaver.\fP
The file you want to replace next has moved around over the years. It
might be called \fI/usr/libexec/kde4/kscreenlocker\fP,
or it might be called "\fIkdesktop_lock\fP" or "\fIkrunner_lock\fP"
@@ -278,52 +243,96 @@ it might be in \fI/usr/lib/kde4/libexec/\fP
or in \fI/usr/kde/3.5/bin/\fP or even in \fI/usr/bin/\fP,
depending on the distro and phase of the moon. Replace the contents
of that file with these two lines:
-.EX
-#!/bin/sh
-xscreensaver-command -lock
-.EE
+.nf
+.sp
+ #!/bin/sh
+ xscreensaver-command \-\-lock
+.sp
+.fi
Make sure the file is executable (chmod a+x).
-.RE
-.PP
-Now use xscreensaver normally, controlling it via the usual
-.BR xscreensaver-demo (1)
-and
-.BR xscreensaver-command (1)
-mechanisms.
-.SH USING SYSTEMD
+.TP 3
+\fB5: Stop KDE from blocking XScreenSaver's "systemd" integration:\fP
+You must arrange for KDE's
+.BR ksmserver (1)
+daemon to be launched with the command line switch \fI\-\-no\-lockscreen\fP.
+
+One way to accomplish that is to edit the
+.BR startkde (1)
+script in \fI/usr/bin/\fP by hand, then log out and log back in. Another
+way would be to wrap the \fIksmserver\fP program:
+.nf
+.sp
+ mv /usr/bin/ksmserver /usr/bin/ksmserver-orig
+.sp
+.fi
+and replace \fI/usr/bin/ksmserver\fP with:
+.nf
+.sp
+ #!/bin/sh
+ ksmserver-orig \-\-no\-lockscreen
+.sp
+.fi
+Either change will, of course, get blown away the next time your system
+upgrades KDE.
+
+Instead of being in \fI/usr/bin/\fP, the \fIksmserver\fP program might be
+in \fI/usr/lib/\fP or \fIusr/lib*/libexec/\fP or \fIusr/lib/*/libexec/\fP
+or somewhere else, depending on your distro.
+
+But without this, video players will not be able to tell XScreenSaver not to
+blank the screen while videos are playing, and the screen will not auto-lock
+when you close your laptop's lid.
+
+It seems that KDE 5.17 replaced \fIstartkde\fP with \fIstartplasma-x11\fP,
+and I don't know how to change how \fIthat\fP launches \fIksmserver\fP.
+Let me know if you figure it out.
+
+.SS LAUNCHING XSCREENSAVER FROM SYSTEMD
If the above didn't do it, and your system has
.BR systemd (1),
-then give this a try:
+maybe this is how it works:
+.RS 4
.TP 3
\fB1: Create a service.\fP
Create the file \fI~/.config/systemd/user/xscreensaver.service\fP
containing:
-.EX
-[Unit]
-Description=XScreenSaver
-[Service]
-ExecStart=/usr/bin/xscreensaver
-[Install]
-WantedBy=default.target
-.EE
+.nf
+.sp
+ [Unit]
+ Description=XScreenSaver
+ [Service]
+ ExecStart=/usr/bin/xscreensaver
+ Restart=on-failure
+ [Install]
+ WantedBy=default.target
+.sp
+.fi
.TP 3
\fB2. Enable it.\fP
-.EX
-systemctl --user enable xscreensaver
-.EE
+.nf
+.sp
+ systemctl \-\-user enable xscreensaver
+.sp
+.fi
+.RE
Then restart X11.
-.SH USING UPSTART
-If it's still not working, but on your distro, that newfangled
-.BR systemd (1)
-nonsense has already fallen out of favor? Then maybe this will work:
-launch the \fI"Startup Applications"\fP applet, click \fI"Add"\fP,
-enter these lines, then restart X11:
-.EX
-Name: XScreenSaver
-Command: xscreensaver
-Comment: xscreensaver
-.EE
-.SH USING GDM
+
+.SS LAUNCHING XSCREENAVER FROM UPSTART
+If your system has
+.BR upstart (7)
+instead of
+.BR systemd (1),
+maybe this will work: launch the \fI"Startup Applications"\fP applet,
+click \fI"Add"\fP, enter these lines, then restart X11:
+.nf
+.sp
+ Name: XScreenSaver
+ Command: xscreensaver
+ Comment: XScreenSaver
+.sp
+.fi
+
+.SS LAUNCHING XSCREENSAVER FROM GDM
You can run \fIxscreensaver\fP from your
.BR gdm (1)
session, so that the screensaver will run even when nobody is logged
@@ -334,20 +343,22 @@ On the \fIGeneral\fP page set the \fILocal Greeter\fP to
\fIStandard Greeter\fP.
On the \fIBackground\fP page, type the
-command \fB"xscreensaver -nosplash"\fP into the \fIBackground Program\fP
-field. That will cause gdm to run xscreensaver while nobody is logged
+command \fB"xscreensaver \-\-nosplash"\fP into the \fIBackground Program\fP
+field. That will cause gdm to run XScreenSaver while nobody is logged
in, and kill it as soon as someone does log in. (The user will then
-be responsible for starting xscreensaver on their own, if they want.)
+be responsible for starting XScreenSaver on their own, if they want.)
If that doesn't work, you can edit the config file directly. Edit
\fI/etc/X11/gdm/gdm.conf\fP to include:
-.EX
-Greeter=/usr/bin/gdmlogin
-BackgroundProgram=xscreensaver -nosplash
-RunBackgroundProgramAlways=true
-.EE
+.nf
+.sp
+ Greeter=/usr/bin/gdmlogin
+ BackgroundProgram=xscreensaver \-\-nosplash
+ RunBackgroundProgramAlways=true
+.sp
+.fi
In this situation, the \fIxscreensaver\fP process will probably be running
-as user \fIgdm\fP instead of \fIroot\fP. You can configure the settings
+as user \fIgdm\fP instead of as \fIroot\fP. You can configure the settings
for this nobody-logged-in state (timeouts, DPMS, etc.) by editing
the \fI~gdm/.xscreensaver\fP file.
@@ -369,127 +380,26 @@ X server's access control mechanisms, see the man pages for
.BR xauth (1),
and
.BR xhost (1).
-.SH LAPTOP LIDS
-If you are running a system with
-.BR systemd (1)
-221 or newer, and if xscreensaver was compiled with \fIlibsystemd\fP
-support, then closing the lid of your laptop will cause the screen to
-lock immediately.
-If not, then the screen might not lock until a few seconds \fIafter\fP you
-re-open the lid. Which is less than ideal. So if you don't
-use \fIsystemd\fP, you might want to get in the habit of
-doing \fIxscreensaver-command -lock\fP before closing the lid.
-.SH BUGS
-Bugs? There are no bugs. Ok, well, maybe. If you find one, please let
-me know. https://www.jwz.org/xscreensaver/bugs.html explains how to
-construct the most useful bug reports.
-.PP
-.TP 4
-.B Locking and root logins
-In order for it to be safe for xscreensaver to be launched by \fIxdm\fP,
-certain precautions had to be taken, among them that xscreensaver never
-runs as \fIroot\fP. In particular, if it is launched as root (as \fIxdm\fP
-is likely to do), xscreensaver will disavow its privileges, and switch
-itself to a safe user id (such as \fInobody\fP).
+.SS LAPTOP LIDS WITHOUT SYSTEMD
+BSD systems or other systems without
+.BR systemd (1)
+or
+.BR elogind (8)
+might have luck by adding \fIxscreensaver\-command \-\-suspend\fP to
+some appropriate spot in \fI/etc/acpi/events/anything\fP or in
+\fI/etc/acpi/handler.sh\fP, if those files exist.
-An implication of this is that if you log in as \fIroot\fP on the console,
-xscreensaver will refuse to lock the screen (because it can't tell
-the difference between \fIroot\fP being logged in on the console, and a
-normal user being logged in on the console but xscreensaver having been
-launched by the
-.BR xdm (1)
-.I Xsetup
-file).
+.SH SECURITY CONCERNS
+XScreenSaver has a decades-long track record of securely locking your screen.
+However, there are many things that can go wrong. X11 is a very old system,
+and has a number of design flaws that make it susceptible to foot-shooting.
-The solution to this is simple: you shouldn't be logging in on the console
-as \fIroot\fP in the first place! (What, are you crazy or something?)
+.SS MAGIC BACKDOOR KEYSTROKES
+The XFree86 and Xorg X servers, as well as the Linux kernel, both trap
+certain magic keystrokes before X11 client programs ever see them.
+If you care about keeping your screen locked, this is a big problem.
-Proper Unix hygiene dictates that you should log in as yourself, and
-.BR su (1)
-to \fIroot\fP as necessary. People who spend their day logged in
-as \fIroot\fP are just begging for disaster.
-.TP 4
-.B XAUTH and XDM
-For xscreensaver to work when launched by
-.BR xdm (1)
-or
-.BR gdm (1),
-programs running on the local machine as user \fI"nobody"\fP must be
-able to connect to the X server. This means that if you want to run
-xscreensaver on the console while nobody is logged in, you may need
-to disable cookie-based access control (and allow all users who can log
-in to the local machine to connect to the display).
-
-You should be sure that this is an acceptable thing to do in your
-environment before doing it. See the "\fIUsing GDM\fP" section,
-above, for more details.
-.TP 4
-.B Passwords
-If you get an error message at startup like "couldn't get password
-of \fIuser\fP" then this probably means that you're on a system in which
-the
-.BR getpwent (3)
-library routine can only be effectively used by root. If this is the case,
-then \fIxscreensaver\fP must be installed as setuid to root in order for
-locking to work. Care has been taken to make this a safe thing to do.
-
-It also may mean that your system uses shadow passwords instead of the standard
-.BR getpwent (3)
-interface; in that case, you may need to change some options
-with \fIconfigure\fP and recompile.
-
-If you change your password after xscreensaver has been launched, it will
-continue using your old password to unlock the screen until xscreensaver
-is restarted. On some systems, it may accept \fIboth\fP your old and new
-passwords. So, after you change your password, you'll have to do
-.EX
-xscreensaver-command -restart
-.EE
-to make \fIxscreensaver\fP notice.
-.TP 4
-.B PAM Passwords
-If your system uses PAM (Pluggable Authentication Modules), then in order
-for xscreensaver to use PAM properly, PAM must be told about xscreensaver.
-The xscreensaver installation process should update the PAM data (on Linux,
-by creating the file \fI/etc/pam.d/xscreensaver\fP for you, and on Solaris,
-by telling you what lines to add to the \fI/etc/pam.conf\fP file).
-
-If the PAM configuration files do not know about xscreensaver, then
-you \fImight\fP be in a situation where xscreensaver will refuse to ever
-unlock the screen.
-
-This is a design flaw in PAM (there is no way for a client to tell the
-difference between PAM responding "I have never heard of your module",
-and responding, "you typed the wrong password"). As far as I can tell,
-there is no way for xscreensaver to automatically work around this, or
-detect the problem in advance, so if you have PAM, make sure it is
-configured correctly!
-.TP 4
-.B Machine Load
-Although this program "nices" the subprocesses that it starts,
-graphics-intensive subprograms can still overload the machine by causing
-the X server process itself (which is not "niced") to consume many
-cycles. Care has been taken in all the modules shipped with xscreensaver
-to sleep periodically, and not run full tilt, so as not to cause
-appreciable load.
-
-However, if you are running the OpenGL-based screen savers on a machine
-that does not have a video card with 3D acceleration, they \fIwill\fP
-make your machine slow, despite
-.BR nice (1).
-
-Your options are: don't use the OpenGL display modes; or, collect the
-spare change hidden under the cushions of your couch, and use it to
-buy a video card manufactured after 1998. (It doesn't even need to be
-\fIfast\fP 3D hardware: the problem will be fixed if there is any
-3D hardware \fIat all.\fP)
-.TP 4
-.B Magic Backdoor Keystrokes
-The XFree86 X server and the Linux kernel both trap certain magic
-keystrokes before X11 client programs ever see them. If you care
-about keeping your screen locked, this is a big problem.
-.RS 4
.TP 3
.B Ctrl+Alt+Backspace
This keystroke kills the X server, and on some systems, leaves you at
@@ -500,6 +410,7 @@ permanently, you need to set the \fBDontZap\fP flag in your
depending which is in use on your system. See
.BR XF86Config (5)
for details.
+
.TP 3
.B Ctrl-Alt-F1, Ctrl-Alt-F2, etc.
These keystrokes will switch to a different virtual console, while
@@ -512,48 +423,147 @@ since VT switching is an actual useful feature.
There is no way to disable VT switching only when the screen is
locked. It's all or nothing.
+
.TP 3
.B Ctrl-Alt-KP_Multiply
This keystroke kills any X11 app that holds a lock, so typing this
-will kill xscreensaver and unlock the screen. This so-called
-"feature" showed up in the X server in 2008, and as of 2011, some
-vendors are shipping it turned on by default. How nice. You can
-disable it by turning off
-\fBAllowClosedownGrabs\fP in \fIxorg.conf\fP.
+will kill XScreenSaver and unlock the screen. You can disable it by
+turning off \fBAllowClosedownGrabs\fP in \fIxorg.conf\fP.
+
.TP 3
.B Alt-SysRq-F
-This is the Linux kernel "OOM-killer" keystroke. It shoots down
-random long-running programs of its choosing, and so might might
-target and kill xscreensaver, and there's no way for xscreensaver to
-protect itself from that. You can disable it globally with:
-.EX
-echo 176 > /proc/sys/kernel/sysrq
-.EE
-.RE
+This is the Linux kernel "OOM-killer" keystroke. It shoots down random
+long-running programs of its choosing, and so might target and kill
+XScreenSaver. You can disable this keystroke globally with:
+.nf
+.sp
+ echo 176 > /proc/sys/kernel/sysrq
+.sp
+.fi
There's little that I can do to make the screen locker be secure so long
as the kernel and X11 developers are \fIactively\fP working against
security like this. The strength of the lock on your front door
doesn't matter much so long as someone else in the house insists on
leaving a key under the welcome mat.
-.TP 4
-.B Dangerous Backdoor Server Extensions
-Many distros enable by default several X11 server extensions that can
-be used to bypass grabs, and thus snoop on you while you're typing
-your password. These extensions are nominally for debugging and
-automation, but they are also security-circumventing keystroke
-loggers. If your server is configured to load the \fBRECORD, XTRAP\fP
-or \fBXTEST\fP extensions, you absolutely should disable those, 100%
-of the time. Look for them in \fIxorg.conf\fP or whatever it is
-called.
+.SS THE OOM-KILLER
+Even if you have disabled the \fBAlt-SysRq-F\fP OOM-killer keystroke, the
+OOM-killer might still decide to assassinate XScreenSaver at random, which
+will unlock your screen. If the
+.BR xscreensaver\-auth (MANSUFFIX)
+program is installed setuid, it attempts to tell the OOM-killer to leave
+the XScreenSaver daemon alone, but that may or may not work.
+
+You would think that the OOM-killer would pick the process using the most
+memory, but most of the time it seems to pick the process that would be most
+comically inconvenient, such as your screen locker, or
+.BR crond (8).
+You can disable the OOM-killer entirely with:
+.nf
+.sp
+ echo 2 > /proc/sys/vm/overcommit_memory
+ echo vm.overcommit_memory = 2 >> /etc/sysctl.conf
+.sp
+.fi
+
+.SS X SERVER ACCESS IS GAME OVER
+X11's security model is all-or-nothing. If a program can connect to your X
+server at all, either locally or over the network, it can log all of your
+keystrokes, simulate keystrokes, launch arbitrary programs, and change the
+settings of other programs. Assume that anything that can connect to your X
+server can execute arbitrary code as the logged-in user. See
+.BR Xsecurity (1)
+and
+.BR xauth (1).
+
+.SS PAM PASSWORDS
+If your system uses PAM (Pluggable Authentication Modules), then PAM must be
+configured for XScreenSaver. If it is not, then you \fImight\fP be in a
+situation where you can't unlock. Probably the file you need
+is \fI/etc/pam.d/xscreensaver\fP.
+
+.SS DON'T LOG IN AS ROOT
+In order for it to be safe for XScreenSaver to be launched by \fIxdm\fP,
+certain precautions had to be taken, among them that XScreenSaver never
+runs as \fIroot\fP. In particular, if it is launched as root (as \fIxdm\fP
+is likely to do), XScreenSaver will disavow its privileges, and switch
+itself to a safe user id (such as \fInobody\fP).
+
+An implication of this is that if you log in as \fIroot\fP on the console,
+XScreenSaver will refuse to lock the screen (because it can't tell
+the difference between \fIroot\fP being logged in on the console, and a
+normal user being logged in on the console but XScreenSaver having been
+launched by the
+.BR xdm (1)
+.I Xsetup
+file).
+
+Proper Unix hygiene dictates that you should log in as yourself, and
+.BR sudo (1)
+to \fIroot\fP as necessary. People who spend their day logged in
+as \fIroot\fP are just begging for disaster.
+
+.SH MULTI-USER OR SITE-WIDE CONFIGURATION
+For a single user, the proper way to configure XScreenSaver is to simply
+run the
+.BR xscreensaver\-settings (1)
+program, and change the settings through the GUI. The rest of this manual
+describes lower-level ways of changing settings. You shouldn't need to
+know any of the stuff described below unless you are trying to do something
+complicated.
+
+Options to XScreenSaver are stored in one of two places: in a file
+called \fI.xscreensaver\fP in your home directory; or in the X resource
+database. If the \fI.xscreensaver\fP file exists, it overrides any settings
+in the resource database.
+
+The syntax of the \fI.xscreensaver\fP file is similar to that of
+the \fI.Xdefaults\fP file; for example, to set the \fItimeout\fP parameter
+n the \fI.xscreensaver\fP file, you would write the following:
+.nf
+.sp
+ timeout: 5
+.sp
+.fi
+whereas, in the \fI.Xdefaults\fP file, you would write
+.nf
+.sp
+ xscreensaver.timeout: 5
+.sp
+.fi
+If you change a setting in the \fI.xscreensaver\fP file while XScreenSaver
+is already running, it will notice this, and reload the file as needed.
+
+If you change a setting in your X resource database, or if you want
+XScreenSaver to notice your changes immediately instead of the next time it
+wakes up, then you will need to reload your \fI.Xdefaults\fP file, and then
+tell the running \fIxscreensaver\fP process to restart itself, like so:
+.nf
+.sp
+ xrdb < ~/.Xdefaults
+ xscreensaver-command \-\-restart
+.sp
+.fi
+If you want to set the system-wide defaults, then make your edits to
+the XScreenSaver app-defaults file, which should have been installed
+when XScreenSaver itself was installed. The app-defaults file will
+usually be named /etc/X11/app-defaults/XScreenSaver, but different
+systems might keep it in a different place.
+
+When settings are changed in the Preferences dialog box, those settings are
+written to the \fI.xscreensaver\fP file. The \fI.Xdefaults\fP file and the
+app-defaults file will never be written by XScreenSaver itself.
+
.SH X RESOURCES
-These are the X resources use by the \fIxscreensaver\fP program.
-You probably won't need to change these manually (that's what the
-.BR xscreensaver\-demo (1)
-program is for).
+These are the X resources use by XScreenSaver program. You probably won't
+need to change these manually: that's what the
+.BR xscreensaver\-settings (1)
+program is for.
+
.TP 8
.B timeout\fP (class \fBTime\fP)
The screensaver will activate (blank the screen) after the keyboard and
mouse have been idle for this many minutes. Default 10 minutes.
+
.TP 8
.B cycle\fP (class \fBTime\fP)
After the screensaver has been running for this many minutes, the currently
@@ -562,46 +572,46 @@ new one started. If this is 0, then the graphics hack will never be changed:
only one demo will run until the screensaver is deactivated by user activity.
Default 10 minutes.
-The running saver will be restarted every \fIcycle\fP minutes even when
-\fImode\fP is \fIone\fP, since some savers tend to converge on a steady
-state.
+If there are multiple screens, the savers are staggered slightly so
+that while they all change every \fIcycle\fP minutes, they don't all
+change at the same time.
+
.TP 8
.B lock\fP (class \fBBoolean\fP)
Enable locking: before the screensaver will turn off, it will require you
-to type the password of the logged-in user (really, the person who ran
-xscreensaver), or the root password. (\fBNote:\fP this doesn't work if the
-screensaver is launched by
-.BR xdm (1)
-because it can't know the user-id of the logged-in user. See
-the "\fIUsing XDM(1)\fP" section, below.
+to type the password of the logged-in user.
+
.TP 8
.B lockTimeout\fP (class \fBTime\fP)
If locking is enabled, this controls the length of the "grace period"
between when the screensaver activates, and when the screen becomes locked.
-For example, if this is 5, and \fI\-timeout\fP is 10, then after 10 minutes,
+For example, if this is 5, and \fItimeout\fP is 10, then after 10 minutes,
the screen would blank. If there was user activity at 12 minutes, no password
would be required to un-blank the screen. But, if there was user activity
-at 15 minutes or later (that is, \fI\-lock\-timeout\fP minutes after
+at 15 minutes or later (that is, \fIlockTimeout\fP minutes after
activation) then a password would be required. The default is 0, meaning
that if locking is enabled, then a password will be required as soon as the
screen blanks.
+
.TP 8
.B passwdTimeout\fP (class \fBTime\fP)
If the screen is locked, then this is how many seconds the password dialog box
-should be left on the screen before giving up (default 30 seconds). This
-should not be too large: the X server is grabbed for the duration that the
-password dialog box is up (for security purposes) and leaving the server
-grabbed for too long can cause problems.
+should be left on the screen before giving up (default 30 seconds). A few
+seconds are added each time you type a character.
+
.TP 8
.B dpmsEnabled\fP (class \fBBoolean\fP)
Whether power management is enabled.
+
.TP 8
.B dpmsStandby\fP (class \fBTime\fP)
If power management is enabled, how long until the monitor goes solid black.
+
.TP 8
.B dpmsSuspend\fP (class \fBTime\fP)
If power management is enabled, how long until the monitor goes into
power-saving mode.
+
.TP 8
.B dpmsOff\fP (class \fBTime\fP)
If power management is enabled, how long until the monitor powers down
@@ -609,109 +619,42 @@ completely. Note that these settings will have no effect unless both
the X server and the display hardware support power management; not
all do. See the \fIPower Management\fP section, below, for more
information.
+
.TP 8
.B dpmsQuickOff\fP (class \fBBoolean\fP)
If \fImode\fP is \fIblank\fP and this is true, then the screen will be
powered down immediately upon blanking, regardless of other
power-management settings.
-.TP 8
-.B visualID\fP (class \fBVisualID\fP)
-This is an historical artifacts left over from when 8-bit
-displays were still common. You should probably ignore this.
-Specify which X visual to use by default. (Note carefully that this resource
-is called \fBvisualID\fP, not merely \fBvisual\fP; if you set the \fBvisual\fP
-resource instead, things will malfunction in obscure ways for obscure reasons.)
-
-Legal values for the \fBVisualID\fP resource are:
-.RS 8
-.TP 8
-.B default
-Use the screen's default visual (the visual of the root window).
-This is the default.
-.TP 8
-.B best
-Use the visual which supports the most colors. Note, however, that the
-visual with the most colors might be a TrueColor visual, which does not
-support colormap animation. Some programs have more interesting behavior
-when run on PseudoColor visuals than on TrueColor.
-.TP 8
-.B mono
-Use a monochrome visual, if there is one.
-.TP 8
-.B gray
-Use a grayscale or staticgray visual, if there is one and it has more than
-one plane (that is, it's not monochrome).
-.TP 8
-.B color
-Use the best of the color visuals, if there are any.
-.TP 8
-.B GL
-Use the visual that is best for OpenGL programs. (OpenGL programs have
-somewhat different requirements than other X programs.)
-.TP 8
-.I class
-where \fIclass\fP is one of \fBStaticGray\fP, \fBStaticColor\fP,
-\fBTrueColor\fP, \fBGrayScale\fP, \fBPseudoColor\fP, or \fBDirectColor\fP.
-Selects the deepest visual of the given class.
-.TP 8
-.I number
-where \fInumber\fP (decimal or hex) is interpreted as a visual id number,
-as reported by the
-.BR xdpyinfo (1)
-program; in this way you can have finer control over exactly which visual
-gets used, for example, to select a shallower one than would otherwise
-have been chosen.
-
-.RE
-.RS 8
-Note that this option specifies only the \fIdefault\fP visual that will
-be used: the visual used may be overridden on a program-by-program basis.
-See the description of the \fBprograms\fP resource, below.
-.RE
-.TP 8
-.B installColormap\fP (class \fBBoolean\fP)
-On PseudoColor (8-bit) displays, install a private colormap while the
-screensaver is active, so that the graphics hacks can get as many
-colors as possible. This is the default. (This only applies when the
-screen's default visual is being used, since non-default visuals get
-their own colormaps automatically.) This can also be overridden on a
-per-hack basis: see the discussion of the \fBdefault\-n\fP name in the
-section about the \fBprograms\fP resource.
-
-This does nothing if you have a TrueColor (16-bit or deeper) display.
-(Which, in this century, you do.)
.TP 8
.B verbose\fP (class \fBBoolean\fP)
Whether to print diagnostics. Default false.
-.TP 8
-.B timestamp\fP (class \fBBoolean\fP)
-Whether to print the time of day along with any other diagnostic messages.
-Default true.
+
.TP 8
.B splash\fP (class \fBBoolean\fP)
Whether to display a splash screen at startup. Default true.
+
.TP 8
.B splashDuration\fP (class \fBTime\fP)
How long the splash screen should remain visible; default 5 seconds.
+
.TP 8
.B helpURL\fP (class \fBURL\fP)
The splash screen has a \fIHelp\fP button on it. When you press it, it will
display the web page indicated here in your web browser.
+
.TP 8
.B loadURL\fP (class \fBLoadURL\fP)
This is the shell command used to load a URL into your web browser.
The default setting will load it into Mozilla/Netscape if it is already
running, otherwise, will launch a new browser looking at the \fIhelpURL\fP.
+
.TP 8
.B demoCommand\fP (class \fBDemoCommand\fP)
This is the shell command run when the \fIDemo\fP button on the splash window
is pressed. It defaults to
-.BR xscreensaver\-demo (1).
-.TP 8
-.B prefsCommand\fP (class \fBPrefsCommand\fP)
-This is the shell command run when the \fIPrefs\fP button on the splash window
-is pressed. It defaults to \fIxscreensaver\-demo\ \-prefs\fP.
+.BR xscreensaver\-settings (1).
+
.TP 8
.B newLoginCommand\fP (class \fBNewLoginCommand\fP)
If set, this is the shell command that is run when the "New Login" button
@@ -723,104 +666,92 @@ Typically this will be some variant of
.BR lxdm (1)
or
.BR dm-tool (1).
+
.TP 8
.B nice\fP (class \fBNice\fP)
-The sub-processes created by \fIxscreensaver\fP will be "niced" to this
-level, so that they are given lower priority than other processes on the
-system, and don't increase the load unnecessarily. The default is 10.
-(Higher numbers mean lower priority; see
+The sub-processes launched by XScreenSaver will be "niced" to this level, so
+that they are given lower priority than other processes on the system, and
+don't increase the load unnecessarily. The default is 10. (Higher numbers
+mean lower priority; see
.BR nice (1)
for details.)
+
.TP 8
.B fade\fP (class \fBBoolean\fP)
If this is true, then when the screensaver activates, the current contents
-of the screen will fade to black instead of simply winking out. This only
-works on certain systems. A fade will also be done when switching graphics
-hacks (when the \fIcycle\fP timer expires). Default: true.
+of the screen will fade to black instead of simply winking out.
+Default: true.
+
.TP 8
.B unfade\fP (class \fBBoolean\fP)
If this is true, then when the screensaver deactivates, the original contents
of the screen will fade in from black instead of appearing immediately. This
-only works on certain systems, and if \fIfade\fP is true as well.
-Default false.
+is only done if \fIfade\fP is true as well. Default: true.
+
.TP 8
.B fadeSeconds\fP (class \fBTime\fP)
If \fIfade\fP is true, this is how long the fade will be in
-seconds (default 3 seconds).
-.TP 8
-.B fadeTicks\fP (class \fBInteger\fP)
-If \fIfade\fP is true, this is how many times a second the colormap will
-be changed to effect a fade. Higher numbers yield smoother fades, but
-may make the fades take longer than the specified \fIfadeSeconds\fP if
-your server isn't fast enough to keep up. Default 20.
-.TP 8
-.B captureStderr\fP (class \fBBoolean\fP)
-Whether \fIxscreensaver\fP should redirect its stdout and stderr streams to
-the window itself. Since its nature is to take over the screen, you would not
-normally see error messages generated by xscreensaver or the sub-programs it
-runs; this resource will cause the output of all relevant programs to be
-drawn on the screensaver window itself, as well as being written to the
-controlling terminal of the screensaver driver process. Default true.
+seconds. Default 3 seconds.
+
.TP 8
.B ignoreUninstalledPrograms\fP (class \fBBoolean\fP)
There may be programs in the list that are not installed on the system,
yet are marked as "enabled". If this preference is true, then such
programs will simply be ignored. If false, then a warning will be printed
if an attempt is made to run the nonexistent program. Also, the
-.BR xscreensaver-demo (1)
+.BR xscreensaver\-settings (1)
program will suppress the non-existent programs from the list if this
is true. Default: false.
+
.TP 8
.B authWarningSlack\fP (class \fBInteger\fP)
-If \fIall\fP failed unlock attempts (incorrect password entered) were
-made within this period of time, the usual dialog that warns about such
-attempts after a successful login will be suppressed. The assumption
+After you successfully unlock the screen, a dialog may pop up informing
+you of previous failed login attempts. If all of those login attemps
+were within this amount of time, they are ignored. The assumption
is that incorrect passwords entered within a few seconds of a correct
one are user error, rather than hostile action. Default 20 seconds.
-.TP 8
-.B GetViewPortIsFullOfLies\fP (class \fBBoolean\fP)
-Set this to true if the xscreensaver window doesn't cover the whole screen.
-This works around a longstanding XFree86 bug #421. See the
-xscreensaver FAQ for details.
-.TP 8
-.B font\fP (class \fBFont\fP)
-The font used for the stdout/stderr text, if \fBcaptureStderr\fP is true.
-Default \fB*\-medium\-r\-*\-140\-*\-m\-*\fP (a 14 point fixed-width font).
+
.TP 8
.B mode\fP (class \fBMode\fP)
-Controls the behavior of xscreensaver. Legal values are:
+Controls the screen-saving behavior. Valid values are:
.RS 8
+
.TP 8
.B random
When blanking the screen, select a random display mode from among those
that are enabled and applicable. This is the default.
+
.TP 8
.B random-same
Like \fIrandom\fP, but if there are multiple screens, each screen
will run the \fIsame\fP random display mode, instead of each screen
running a different one.
+
.TP 8
.B one
When blanking the screen, only ever use one particular display mode (the
one indicated by the \fIselected\fP setting).
+
.TP 8
.B blank
When blanking the screen, just go black: don't run any graphics hacks.
+
.TP 8
.B off
Don't ever blank the screen, and don't ever allow the monitor to power down.
-
.RE
+
.TP 8
.B selected\fP (class \fBInteger\fP)
When \fImode\fP is set to \fIone\fP, this is the one, indicated by its
index in the \fIprograms\fP list. You're crazy if you count them and
set this number by hand: let
-.BR xscreensaver\-demo (1)
+.BR xscreensaver\-settings (1)
do it for you!
+
.TP 8
.B programs\fP (class \fBPrograms\fP)
-The graphics hacks which \fIxscreensaver\fP runs when the user is idle.
+The graphics hacks which XScreenSaver runs when the user is idle.
The value of this resource is a multi-line string, one \fIsh\fP-syntax
command per line. Each line must contain exactly one command: no
semicolons, no ampersands.
@@ -832,7 +763,7 @@ expires, it is killed, and another is selected and run.
If a line begins with a dash (-) then that particular program is
disabled: it won't be selected at random (though you can still select
it explicitly using the
-.BR xscreensaver\-demo (1)
+.BR xscreensaver\-settings (1)
program).
If all programs are disabled, then the screen will just be made blank,
@@ -850,32 +781,28 @@ for each screen. (All screens are blanked and unblanked simultaneously.)
Note that you must escape the newlines; here is an example of how you
might set this in your \fI~/.xscreensaver\fP file:
-
-.RS 8
-.EX
-programs: \\
- qix -root \\n\\
- ico -r -faces -sleep 1 -obj ico \\n\\
- xdaliclock -builtin2 -root \\n\\
- xv -root -rmode 5 image.gif -quit \\n
-.EE
-.RE
+.nf
+.sp
+ programs: \\
+ qix -root \\n\\
+ ico -r -faces -sleep 1 -obj ico \\n\\
+ xdaliclock -builtin2 -root \\n\\
+ xv -root -rmode 5 image.gif -quit \\n
+.sp
+.fi
.RS 8
Make sure your \fB$PATH\fP environment variable is set up correctly
-\fIbefore\fP xscreensaver is launched, or it won't be able to find the
+\fIbefore\fP XScreenSaver is launched, or it won't be able to find the
programs listed in the \fIprograms\fP resource.
-To use a program as a screensaver, two things are required: that that
-program draw on the root window (or be able to be configured to draw on
-the root window); and that that program understand "virtual root"
-windows, as used by virtual window managers such as
-.BR tvtwm (1).
-(Generally, this is accomplished by just including the \fI"vroot.h"\fP
-header file in the program's source.)
+To use a program as a screensaver, it must be able to render onto
+the window provided to it in the \fB$XSCREENSAVER_WINDOW\fP environment
+variable. If it creates and maps its own window instead, it won't work.
+It must render onto the provided window.
.B Visuals:
-Because xscreensaver was created back when dinosaurs roamed the earth,
+Because XScreenSaver was created back when dinosaurs roamed the earth,
it still contains support for some things you've probably never seen,
such as 1-bit monochrome monitors, grayscale monitors, and monitors
capable of displaying only 8-bit colormapped images.
@@ -883,22 +810,22 @@ capable of displaying only 8-bit colormapped images.
If there are some programs that you want to run only when using a color
display, and others that you want to run only when using a monochrome
display, you can specify that like this:
-.EX
- mono: mono-program -root \\n\\
- color: color-program -root \\n\\
-.EE
-.RE
-.RS 8
+.nf
+.sp
+ mono: mono-program -root \\n\\
+ color: color-program -root \\n\\
+.sp
+.fi
More generally, you can specify the kind of visual that should be used for
the window on which the program will be drawing. For example, if one
program works best if it has a colormap, but another works best if it has
a 24-bit visual, both can be accommodated:
-.EX
- PseudoColor: cmap-program -root \\n\\
- TrueColor: 24bit-program -root \\n\\
-.EE
-.RE
-.RS 8
+.nf
+.sp
+ PseudoColor: cmap-program -root \\n\\
+ TrueColor: 24bit-program -root \\n\\
+.sp
+.fi
In addition to the symbolic visual names described above (in the discussion
of the \fIvisualID\fP resource) one other visual name is supported in
the \fIprograms\fP list:
@@ -906,15 +833,7 @@ the \fIprograms\fP list:
.TP 4
.B default-n
This is like \fBdefault\fP, but also requests the use of the default colormap,
-instead of a private colormap. (That is, it behaves as if
-the \fI\-no\-install\fP command-line option was specified, but only for
-this particular hack.) This is provided because some third-party programs
-that draw on the root window (notably:
-.BR xv (1),
-and
-.BR xearth (1))
-make assumptions about the visual and colormap of the root window:
-assumptions which xscreensaver can violate.
+instead of a private colormap.
.RE
If you specify a particular visual for a program, and that visual does not
@@ -924,72 +843,88 @@ arrange for appropriate hacks to be run on each. For example, if one screen
is color and the other is monochrome, hacks that look good in mono can be
run on one, and hacks that only look good in color will show up on the other.
.RE
-.PP
-.PP
-You shouldn't ever need to change the following resources:
-.PP
+
.TP 8
-.B pointerPollTime\fP (class \fBTime\fP)
-When server extensions are not in use, this controls how
-frequently \fIxscreensaver\fP checks to see if the mouse position or buttons
-have changed. Default 5 seconds.
+.B visualID\fP (class \fBVisualID\fP)
+This is an historical artifact left over from when 8-bit
+displays were still common. You should probably ignore this.
+
+Specify which X visual to use by default. (Note carefully that this resource
+is called \fBvisualID\fP, not merely \fBvisual\fP; if you set the \fBvisual\fP
+resource instead, things will malfunction in obscure ways for obscure reasons.)
+
+Valid values for the \fBVisualID\fP resource are:
+.RS 8
.TP 8
-.B pointerHysteresis\fP (class \fBInteger\fP)
-If the mouse moves less than this-many pixels in a second, ignore it
-(do not consider that to be "activity"). This is so that the screen
-doesn't un-blank (or fail to blank) just because you bumped the desk.
-Default: 10 pixels.
+.B default
+Use the screen's default visual (the visual of the root window).
+This is the default.
+.TP 8
+.B best
+Use the visual which supports the most colors. Note, however, that the
+visual with the most colors might be a TrueColor visual, which does not
+support colormap animation. Some programs have more interesting behavior
+when run on PseudoColor visuals than on TrueColor.
+.TP 8
+.B mono
+Use a monochrome visual, if there is one.
.TP 8
-.B windowCreationTimeout\fP (class \fBTime\fP)
-When server extensions are not in use, this controls the delay between when
-windows are created and when \fIxscreensaver\fP selects events on them.
-Default 30 seconds.
+.B gray
+Use a grayscale or staticgray visual, if there is one and it has more than
+one plane (that is, it's not monochrome).
.TP 8
-.B initialDelay\fP (class \fBTime\fP)
-When server extensions are not in use, \fIxscreensaver\fP will wait this many
-seconds before selecting events on existing windows, under the assumption that
-\fIxscreensaver\fP is started during your login procedure, and the window
-state may be in flux. Default 0. (This used to default to 30, but that was
-back in the days when slow machines and X terminals were more common...)
+.B color
+Use the best of the color visuals, if there are any.
.TP 8
-.B procInterrupts\fP (class \fBBoolean\fP)
-This resource controls whether the \fB/proc/interrupts\fP file should be
-consulted to decide whether the user is idle. This is the default
-if \fIxscreensaver\fP has been compiled on a system which supports this
-mechanism (i.e., Linux systems).
-
-The benefit to doing this is that \fIxscreensaver\fP can note that the user
-is active even when the X console is not the active one: if the user is
-typing in another virtual console, xscreensaver will notice that and will
-fail to activate. For example, if you're playing Quake in VGA-mode,
-xscreensaver won't wake up in the middle of your game and start competing
-for CPU.
-
-The drawback to doing this is that perhaps you \fIreally do\fP want idleness
-on the X console to cause the X display to lock, even if there is activity
-on other virtual consoles. If you want that, then set this option to False.
-(Or just lock the X console manually.)
-
-The default value for this resource is True, on systems where it works.
+.B GL
+Use the visual that is best for OpenGL programs. (OpenGL programs have
+somewhat different requirements than other X programs.)
.TP 8
-.B overlayStderr\fP (class \fBBoolean\fP)
-If \fBcaptureStderr\fP is True, and your server supports "overlay" visuals,
-then the text will be written into one of the higher layers instead of into
-the same layer as the running screenhack. Set this to False to disable
-that (though you shouldn't need to).
+.I class
+where \fIclass\fP is one of \fBStaticGray\fP, \fBStaticColor\fP,
+\fBTrueColor\fP, \fBGrayScale\fP, \fBPseudoColor\fP, or \fBDirectColor\fP.
+Selects the deepest visual of the given class.
.TP 8
-.B overlayTextForeground\fP (class \fBForeground\fP)
-The foreground color used for the stdout/stderr text, if \fBcaptureStderr\fP
-is true. Default: Yellow.
+.I N
+where \fInumber\fP (decimal or hex) is interpreted as a visual id number,
+as reported by the
+.BR xdpyinfo (1)
+program; in this way you can have finer control over exactly which visual
+gets used, for example, to select a shallower one than would otherwise
+have been chosen.
+
+.RE
+.RS 8
+Note that this option specifies only the \fIdefault\fP visual that will
+be used: the visual used may be overridden on a program-by-program basis.
+See the description of the \fBprograms\fP resource, above.
+.RE
+
.TP 8
-.B overlayTextBackground\fP (class \fBBackground\fP)
-The background color used for the stdout/stderr text, if \fBcaptureStderr\fP
-is true. Default: Black.
+.B installColormap\fP (class \fBBoolean\fP)
+This is an historical artifact left over from when 8-bit displays were still
+common. On PseudoColor (8-bit) displays, install a private colormap while the
+screensaver is active, so that the graphics hacks can get as many colors as
+possible. This is the default. (This only applies when the screen's default
+visual is being used, since non-default visuals get their own colormaps
+automatically.) This can also be overridden on a per-hack basis: see the
+discussion of the \fBdefault\-n\fP name in the section about the
+\fBprograms\fP resource.
+
+This does nothing if you have a TrueColor (16-bit or deeper) display.
+(Which, in this century, you do.)
+
.TP 8
-.B bourneShell\fP (class \fBBourneShell\fP)
-The pathname of the shell that \fIxscreensaver\fP uses to start subprocesses.
-This must be whatever your local variant of \fB/bin/sh\fP is: in particular,
-it must not be \fBcsh\fP.
+.B pointerHysteresis\fP (class \fBInteger\fP)
+If the mouse moves less than this-many pixels in a second, ignore it
+(do not consider that to be "activity"). This is so that the screen
+doesn't un-blank (or fail to blank) just because you bumped the desk.
+Default: 10 pixels.
+
+.SH BUGS
+https://www.jwz.org/xscreensaver/bugs.html explains how to write the most
+useful bug reports. If you find a bug, please let me know!
+
.SH ENVIRONMENT
.PP
.TP 8
@@ -1003,7 +938,7 @@ should draw. This is necessary on Xinerama/RANDR systems where
multiple physical monitors share a single X11 "Screen".
.TP 8
.B PATH
-to find the sub-programs to run.
+to find the sub-programs to run, including the display modes.
.TP 8
.B HOME
for the directory in which to read the \fI.xscreensaver\fP file.
@@ -1012,7 +947,7 @@ for the directory in which to read the \fI.xscreensaver\fP file.
to get the name of a resource file that overrides the global resources
stored in the RESOURCE_MANAGER property.
.SH UPGRADES
-The latest version of xscreensaver, an online version of this manual,
+The latest version of XScreenSaver, an online version of this manual,
and a FAQ can always be found at https://www.jwz.org/xscreensaver/
.SH SEE ALSO
.BR X (1),
@@ -1021,13 +956,16 @@ and a FAQ can always be found at https://www.jwz.org/xscreensaver/
.BR xdm (1),
.BR gdm (1),
.BR xhost (1),
-.BR xscreensaver\-demo (1),
+.BR systemd (1),
+.BR elogind (8),
+.BR xscreensaver\-settings (1),
.BR xscreensaver\-command (1),
-.BR xscreensaver\-gl\-helper (1),
-.BR xscreensaver\-getimage (1),
-.BR xscreensaver\-text (1).
+.BR xscreensaver\-systemd (MANSUFFIX),
+.BR xscreensaver\-gl\-helper (MANSUFFIX),
+.BR xscreensaver\-getimage (MANSUFFIX),
+.BR xscreensaver\-text (MANSUFFIX).
.SH COPYRIGHT
-Copyright \(co 1991-2019 by Jamie Zawinski.
+Copyright \(co 1991-2021 by Jamie Zawinski.
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
@@ -1042,5 +980,5 @@ to comp.sources.x on 17-Aug-1992.
Please let me know if you find any bugs or make any improvements.
And a huge thank you to the hundreds of people who have contributed, in
-large ways and small, to the xscreensaver collection over the past
-two decades!
+large ways and small, to the XScreenSaver collection over the past
+three decades!
diff --git a/driver/xscreensaver.ui b/driver/xscreensaver.ui
new file mode 100644
index 0000000..e9e4522
--- /dev/null
+++ b/driver/xscreensaver.ui
@@ -0,0 +1,2994 @@
+<?xml version="1.0"?>
+<!--*- mode: xml -*-->
+<interface>
+ <object class="GtkAdjustment" id="adjustment1">
+ <property name="upper">720</property>
+ <property name="lower">1</property>
+ <property name="page_increment">15</property>
+ <property name="step_increment">1</property>
+ <property name="page_size">0</property>
+ <property name="value">1</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment2">
+ <property name="upper">720</property>
+ <property name="lower">0</property>
+ <property name="page_increment">15</property>
+ <property name="step_increment">1</property>
+ <property name="page_size">0</property>
+ <property name="value">0</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment3">
+ <property name="upper">720</property>
+ <property name="lower">0</property>
+ <property name="page_increment">15</property>
+ <property name="step_increment">1</property>
+ <property name="page_size">0</property>
+ <property name="value">0</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment4">
+ <property name="upper">1440</property>
+ <property name="lower">0</property>
+ <property name="page_increment">15</property>
+ <property name="step_increment">1</property>
+ <property name="page_size">0</property>
+ <property name="value">0</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment5">
+ <property name="upper">1440</property>
+ <property name="lower">0</property>
+ <property name="page_increment">15</property>
+ <property name="step_increment">1</property>
+ <property name="page_size">0</property>
+ <property name="value">0</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment6">
+ <property name="upper">1440</property>
+ <property name="lower">0</property>
+ <property name="page_increment">15</property>
+ <property name="step_increment">1</property>
+ <property name="page_size">0</property>
+ <property name="value">0</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment7">
+ <property name="upper">10</property>
+ <property name="lower">0</property>
+ <property name="page_increment">1</property>
+ <property name="step_increment">1</property>
+ <property name="page_size">0</property>
+ <property name="value">0</property>
+ </object>
+ <object class="GtkListStore" id="mode_menu_model">
+ <columns>
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Disable Screen Saver</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Blank Screen Only</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Only One Screen Saver</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Random Screen Saver</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Same Random Savers</col>
+ </row>
+ </data>
+ </object>
+
+ <object class="GtkListStore" id="theme_menu_model">
+ <columns>
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Default</col>
+ </row>
+ </data>
+ </object>
+
+
+
+
+
+ <object class="GtkListStore" id="visual_combo_model">
+ <columns>
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Any</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Best</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Default</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Default-N</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">GL</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">TrueColor</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">PseudoColor</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">StaticGray</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">GrayScale</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">DirectColor</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Color</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Gray</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Mono</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkUIManager" id="uimanager1">
+ <child>
+ <object class="GtkActionGroup" id="actiongroup1">
+ <child>
+ <object class="GtkAction" id="file">
+ <property name="name">file</property>
+ <property name="label" translatable="yes">_File</property>
+ <signal handler="file_menu_cb" last_modification_time="Sun, 06 Mar 2005 21:41:13 GMT" name="activate"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkAction" id="activate_action">
+ <property name="name">activate_action</property>
+ <property name="label" translatable="yes">_Blank Screen Now</property>
+ <signal handler="activate_menu_cb" name="activate"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkAction" id="lock_action">
+ <property name="name">lock_action</property>
+ <property name="label" translatable="yes">_Lock Screen Now</property>
+ <signal handler="lock_menu_cb" name="activate"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkAction" id="kill_action">
+ <property name="name">kill_action</property>
+ <property name="label" translatable="yes">_Kill Daemon</property>
+ <signal handler="kill_menu_cb" name="activate"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkAction" id="restart_action">
+ <property name="name">restart_action</property>
+ <property name="label" translatable="yes">_Restart Daemon</property>
+ <signal handler="restart_menu_cb" name="activate"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkAction" id="exit_action">
+ <property name="name">exit_action</property>
+ <property name="label" translatable="yes">_Quit</property>
+ <signal handler="exit_menu_cb" name="activate"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkAction" id="help">
+ <property name="name">help</property>
+ <property name="label" translatable="yes">_Help</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkAction" id="about_action">
+ <property name="name">about_action</property>
+ <property name="label" translatable="yes">_About...</property>
+ <signal handler="about_menu_cb" name="activate"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkAction" id="doc_action">
+ <property name="name">doc_action</property>
+ <property name="label" translatable="yes">_Documentation...</property>
+ <signal handler="doc_menu_cb" name="activate"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ <ui>
+ <menubar name="menubar">
+ <menu action="file">
+ <menuitem name="activate_menu" action="activate_action"/>
+ <menuitem name="lock_menu" action="lock_action"/>
+ <menuitem name="kill_menu" action="kill_action"/>
+ <menuitem name="restart_menu" action="restart_action"/>
+ <separator/>
+ <menuitem name="exit_menu" action="exit_action"/>
+ </menu>
+ <menu action="help">
+ <menuitem name="about_menu" action="about_action"/>
+ <menuitem name="doc_menu" action="doc_action"/>
+ </menu>
+ </menubar>
+ </ui>
+ </object>
+ <object class="GtkWindow" id="xscreensaver_demo">
+ <property name="title" translatable="yes">XScreenSaver</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+ <child>
+ <object class="GtkVBox" id="outer_vbox">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkMenuBar" constructor="uimanager1" id="menubar">
+ <property name="visible">True</property>
+ <property name="pack_direction">GTK_PACK_DIRECTION_LTR</property>
+ <property name="child_pack_direction">GTK_PACK_DIRECTION_LTR</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="spacer_hbox">
+ <property name="border_width">8</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkNotebook" id="notebook">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="show_tabs">True</property>
+ <property name="show_border">True</property>
+ <property name="tab_pos">GTK_POS_TOP</property>
+ <property name="scrollable">False</property>
+ <property name="enable_popup">False</property>
+ <signal handler="switch_page_cb" name="switch_page"/>
+ <child>
+ <object class="GtkTable" id="demos_table">
+ <property name="border_width">10</property>
+ <property name="visible">True</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">0</property>
+ <property name="column_spacing">0</property>
+ <child>
+ <object class="GtkTable" id="blanking_table">
+ <property name="visible">True</property>
+ <property name="n_rows">3</property>
+ <property name="n_columns">4</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">2</property>
+ <property name="column_spacing">0</property>
+ <child>
+ <object class="GtkLabel" id="cycle_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Cycle After</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_RIGHT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">1</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">8</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">cycle_spinbutton</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ <accessibility>
+ <relation target="cycle_spinbutton" type="label-for"/>
+ <relation target="cycle_spinbutton" type="flows-to"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEventBox" id="lock_button_eventbox">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">Whether a password should be required to un-blank the screen.</property>
+ <property name="visible_window">False</property>
+ <property name="above_child">False</property>
+ <child>
+ <object class="GtkCheckButton" id="lock_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Lock Screen After </property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <accessibility>
+ <relation target="lock_spinbutton" type="controller-for"/>
+ <relation target="lock_spinbutton" type="label-for"/>
+ <relation target="lock_spinbutton" type="flows-to"/>
+ </accessibility>
+ <signal handler="pref_changed_cb" name="toggled"/>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="a11y-lock_button1">
+ <property name="AtkObject::accessible_name" translatable="yes">Lock Screen</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="timeout_spinbutton">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">How long before the screen saver activates.</property>
+ <property name="can_focus">True</property>
+ <property name="climb_rate">15</property>
+ <property name="digits">0</property>
+ <property name="numeric">True</property>
+ <property name="update_policy">GTK_UPDATE_ALWAYS</property>
+ <property name="snap_to_ticks">True</property>
+ <property name="wrap">False</property>
+ <property name="adjustment">adjustment1</property>
+ <accessibility>
+ <relation target="timeout_label" type="labelled-by"/>
+ <relation target="timeout_mlabel" type="labelled-by"/>
+ <relation target="timeout_label" type="flows-from"/>
+ <relation target="timeout_mlabel" type="flows-to"/>
+ </accessibility>
+ <signal handler="pref_changed_cb" name="activate"/>
+ <signal handler="pref_changed_event_cb" name="focus_out_event"/>
+ <signal handler="pref_changed_cb" name="value_changed"/>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="lock_spinbutton">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">How long after the screen blanks until a password will be required.</property>
+ <property name="can_focus">True</property>
+ <property name="climb_rate">15</property>
+ <property name="digits">0</property>
+ <property name="numeric">True</property>
+ <property name="update_policy">GTK_UPDATE_ALWAYS</property>
+ <property name="snap_to_ticks">True</property>
+ <property name="wrap">False</property>
+ <property name="adjustment">adjustment2</property>
+ <accessibility>
+ <relation target="lock_button" type="controlled-by"/>
+ <relation target="lock_button" type="labelled-by"/>
+ <relation target="lock_mlabel" type="labelled-by"/>
+ <relation target="lock_button" type="flows-from"/>
+ <relation target="lock_mlabel" type="flows-to"/>
+ </accessibility>
+ <signal handler="pref_changed_cb" name="activate"/>
+ <signal handler="pref_changed_event_cb" name="focus_out_event"/>
+ <signal handler="pref_changed_cb" name="value_changed"/>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="a11y-lock_spinbutton1">
+ <property name="AtkObject::accessible_name" translatable="yes">Lock Screen After</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_padding">10</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="cycle_spinbutton">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">How long each display mode should run before choosing a new one (in Random mode.)</property>
+ <property name="can_focus">True</property>
+ <property name="climb_rate">15</property>
+ <property name="digits">0</property>
+ <property name="numeric">True</property>
+ <property name="update_policy">GTK_UPDATE_ALWAYS</property>
+ <property name="snap_to_ticks">True</property>
+ <property name="wrap">False</property>
+ <property name="adjustment">adjustment3</property>
+ <accessibility>
+ <relation target="cycle_label" type="labelled-by"/>
+ <relation target="cycle_mlabel" type="labelled-by"/>
+ <relation target="cycle_label" type="flows-from"/>
+ <relation target="cycle_mlabel" type="flows-to"/>
+ </accessibility>
+ <signal handler="pref_changed_cb" name="activate"/>
+ <signal handler="pref_changed_event_cb" name="focus_out_event"/>
+ <signal handler="pref_changed_cb" name="value_changed"/>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="lock_mlabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">minutes</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">8</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ <accessibility>
+ <relation target="lock_spinbutton" type="label-for"/>
+ <relation target="lock_spinbutton" type="flows-to"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="left_attach">3</property>
+ <property name="right_attach">4</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="cycle_mlabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">minutes</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">8</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ <accessibility>
+ <relation target="cycle_spinbutton" type="label-for"/>
+ <relation target="cycle_spinbutton" type="flows-from"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="left_attach">3</property>
+ <property name="right_attach">4</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="timeout_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Blank After</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_RIGHT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">1</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">8</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">timeout_spinbutton</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ <accessibility>
+ <relation target="timeout_spinbutton" type="label-for"/>
+ <relation target="timeout_spinbutton" type="flows-to"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="timeout_mlabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">minutes</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">8</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ <accessibility>
+ <relation target="timeout_spinbutton" type="label-for"/>
+ <relation target="timeout_spinbutton" type="flows-from"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="left_attach">3</property>
+ <property name="right_attach">4</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHButtonBox" id="demo_manual_hbbox">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_SPREAD</property>
+ <property name="spacing">30</property>
+ <child>
+ <object class="GtkButton" id="demo">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">Demo the selected screen saver in full-screen mode (click the mouse to return.)</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Preview</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <signal handler="run_this_cb" name="clicked"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="settings">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">Customization and explanation of the selected screen saver.</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Settings...</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <signal handler="settings_cb" name="clicked"/>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="list_vbox">
+ <property name="border_width">10</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkHBox" id="mode_hbox">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkLabel" id="mode_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Mode:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">mode_menu</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ <accessibility>
+ <relation target="mode_menu" type="label-for"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="mode_menu">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="has_entry">False</property>
+ <property name="model">mode_menu_model</property>
+ <accessibility>
+ <relation target="mode_label" type="labelled-by"/>
+ </accessibility>
+ <child>
+ <object class="GtkCellRendererText" id="renderer1"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">4</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">10</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scroller">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
+ <property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+ <child>
+ <object class="GtkTreeView" id="list">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <property name="rules_hint">True</property>
+ <property name="reorderable">False</property>
+ <property name="enable_search">True</property>
+ <property name="fixed_height_mode">False</property>
+ <property name="hover_selection">False</property>
+ <property name="hover_expand">False</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="centering_hbox">
+ <property name="visible">True</property>
+ <property name="homogeneous">True</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkHBox" id="next_prev_hbox">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkButton" id="next">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">Run the next screen saver in the list in full-screen mode (click the mouse to return.)</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <signal handler="run_next_cb" name="clicked"/>
+ <child>
+ <object class="GtkArrow" id="arrow1">
+ <property name="visible">True</property>
+ <property name="arrow_type">GTK_ARROW_DOWN</property>
+ <property name="shadow_type">GTK_SHADOW_OUT</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="prev">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">Run the previous screen saver in the list in full-screen mode (click the mouse to return.)</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <signal handler="run_prev_cb" name="clicked"/>
+ <child>
+ <object class="GtkArrow" id="arrow2">
+ <property name="visible">True</property>
+ <property name="arrow_type">GTK_ARROW_UP</property>
+ <property name="shadow_type">GTK_SHADOW_OUT</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="preview_frame">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="label_yalign">0.5</property>
+ <property name="shadow_type">GTK_SHADOW_ETCHED_IN</property>
+ <accessibility>
+ <relation target="label1" type="labelled-by"/>
+ </accessibility>
+ <child>
+ <object class="GtkNotebook" id="preview_notebook">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="show_tabs">True</property>
+ <property name="show_border">False</property>
+ <property name="tab_pos">GTK_POS_BOTTOM</property>
+ <property name="scrollable">False</property>
+ <property name="enable_popup">False</property>
+ <child>
+ <object class="GtkAspectFrame" id="preview_aspectframe">
+ <property name="border_width">8</property>
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="label_yalign">0.5</property>
+ <property name="shadow_type">GTK_SHADOW_ETCHED_IN</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="ratio">1.33000004292</property>
+ <property name="obey_child">False</property>
+ <child>
+ <object class="GtkDrawingArea" id="preview">
+ <property name="visible">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="preview_tab">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">preview</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="no_preview_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">No Preview
+Available</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_CENTER</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="no_preview_tab">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">no preview</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="not_installed_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Not
+Installed</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_CENTER</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="not_installed_tab">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">not installed</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="nothing_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Very few (or no) screen savers appear to be available.
+
+This probably means that the "xscreensaver-extras" and
+"xscreensaver-gl-extras" packages are not installed.</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_CENTER</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="nothing_tab">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">nothing</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Description</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ <accessibility>
+ <relation target="preview_frame" type="label-for"/>
+ </accessibility>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="y_padding">6</property>
+ <property name="x_options">expand|shrink|fill</property>
+ <property name="y_options">expand|shrink|fill</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="demo_tab">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Display Modes</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_CENTER</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">notebook</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkTable" id="options_table">
+ <property name="visible">True</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">True</property>
+ <property name="row_spacing">0</property>
+ <property name="column_spacing">0</property>
+ <child>
+ <object class="GtkFrame" id="grab_frame">
+ <property name="border_width">10</property>
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="label_yalign">0.5</property>
+ <property name="shadow_type">GTK_SHADOW_ETCHED_IN</property>
+ <accessibility>
+ <relation target="label2" type="labelled-by"/>
+ </accessibility>
+ <child>
+ <object class="GtkHBox" id="grab_hbox">
+ <property name="border_width">8</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">8</property>
+ <child>
+ <object class="GtkImage" id="image2">
+ <property name="visible">True</property>
+ <property name="pixbuf">screensaver-snap.png</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="xpad">4</property>
+ <property name="ypad">8</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="grab_vbox">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkCheckButton" id="grab_desk_button">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">Whether the image-manipulating modes should be allowed to operate on an image of your desktop.</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Grab Desktop _Images</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <signal handler="pref_changed_cb" name="toggled"/>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="grab_video_button">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">Whether the image-manipulating modes should operate on images captured from the system's video input (if there is one.)</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Grab _Video Frames</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <signal handler="pref_changed_cb" name="toggled"/>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="grab_image_button">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">Whether the image-manipulating modes should load image files.</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Choose _Random Image:</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <accessibility>
+ <relation target="image_text" type="controller-for"/>
+ <relation target="image_browse_button" type="controller-for"/>
+ </accessibility>
+ <signal handler="pref_changed_cb" name="toggled"/>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="image_hbox">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkLabel" id="grab_dummy">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"/>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">8</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="image_text">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">The local directory, RSS feed or Atom feed from which images will be randomly chosen.</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"/>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ <accessibility>
+ <relation target="grab_image_button" type="labelled-by"/>
+ <relation target="grab_image_button" type="controlled-by"/>
+ </accessibility>
+ <signal handler="pref_changed_cb" name="activate"/>
+ <signal handler="pref_changed_event_cb" name="focus_out_event"/>
+ </object>
+ <packing>
+ <property name="padding">2</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="image_browse_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Browse</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <signal handler="browse_image_dir_cb" name="clicked"/>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label8">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Local directory, or RSS feed URL.</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">20</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Image Manipulation</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ <accessibility>
+ <relation target="grab_frame" type="label-for"/>
+ </accessibility>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="diag_frame">
+ <property name="border_width">10</property>
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="label_yalign">0.5</property>
+ <property name="shadow_type">GTK_SHADOW_ETCHED_IN</property>
+ <accessibility>
+ <relation target="label3" type="labelled-by"/>
+ </accessibility>
+ <child>
+ <object class="GtkHBox" id="diag_hbox">
+ <property name="border_width">8</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">8</property>
+ <child>
+ <object class="GtkImage" id="diag_logo">
+ <property name="visible">True</property>
+ <property name="pixbuf">screensaver-diagnostic.png</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkTable" id="text_table">
+ <property name="visible">True</property>
+ <property name="n_rows">5</property>
+ <property name="n_columns">3</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">2</property>
+ <property name="column_spacing">2</property>
+ <child>
+ <object class="GtkRadioButton" id="text_radio">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">Text-displaying modes will display the text typed here.</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Text</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <accessibility>
+ <relation target="text_entry" type="controller-for"/>
+ <relation target="text_entry" type="label-for"/>
+ </accessibility>
+ <signal handler="pref_changed_cb" last_modification_time="Sun, 20 Mar 2005 21:31:44 GMT" name="toggled"/>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="text_file_radio">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">Text-displaying modes will display the contents of this file.</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Text _file</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">text_radio</property>
+ <accessibility>
+ <relation target="text_file_entry" type="label-for"/>
+ <relation target="text_file_entry" type="controller-for"/>
+ </accessibility>
+ <signal handler="pref_changed_cb" last_modification_time="Sun, 20 Mar 2005 21:31:55 GMT" name="toggled"/>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="text_program_radio">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">Text-displaying modes will display the output of this program.</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Program</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">text_radio</property>
+ <accessibility>
+ <relation target="text_program_entry" type="label-for"/>
+ <relation target="text_program_entry" type="controller-for"/>
+ <relation target="text_program_browse" type="controller-for"/>
+ </accessibility>
+ <signal handler="pref_changed_cb" last_modification_time="Sun, 20 Mar 2005 21:32:07 GMT" name="toggled"/>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="text_url_radio">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">Text-displaying modes will display the contents of this URL (HTML or RSS).</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_URL</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">text_radio</property>
+ <accessibility>
+ <relation target="text_url_entry" type="label-for"/>
+ <relation target="text_url_entry" type="controller-for"/>
+ </accessibility>
+ <signal handler="pref_changed_cb" last_modification_time="Sun, 20 Mar 2005 21:32:17 GMT" name="toggled"/>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="text_host_radio">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">Text-displaying modes will display the local host name, date, and time.</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Host Name and Time</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">True</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">text_radio</property>
+ <signal handler="pref_changed_cb" last_modification_time="Sun, 20 Mar 2005 21:31:32 GMT" name="toggled"/>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="text_url_entry">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">Text-displaying modes will display the contents of this URL (HTML or RSS).</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"/>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ <accessibility>
+ <relation target="text_url_radio" type="controlled-by"/>
+ </accessibility>
+ <signal handler="pref_changed_cb" last_modification_time="Sun, 20 Mar 2005 21:33:10 GMT" name="activate"/>
+ <signal handler="pref_changed_event_cb" last_modification_time="Sun, 20 Mar 2005 21:34:26 GMT" name="focus_out_event"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="text_file_browse">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Browse</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <accessibility>
+ <relation target="text_file_radio" type="controlled-by"/>
+ </accessibility>
+ <signal handler="browse_text_file_cb" last_modification_time="Sun, 20 Mar 2005 01:24:38 GMT" name="clicked"/>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="text_entry">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">Text-displaying modes will display the text typed here.</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"/>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ <accessibility>
+ <relation target="text_program_radio" type="labelled-by"/>
+ <relation target="text_program_radio" type="controlled-by"/>
+ </accessibility>
+ <signal handler="pref_changed_cb" last_modification_time="Sun, 20 Mar 2005 21:32:42 GMT" name="activate"/>
+ <signal handler="pref_changed_event_cb" last_modification_time="Sun, 20 Mar 2005 21:33:43 GMT" name="focus_out_event"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="text_program_entry">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">Text-displaying modes will display the output of this program.</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"/>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ <accessibility>
+ <relation target="text_program_radio" type="labelled-by"/>
+ <relation target="text_program_radio" type="controlled-by"/>
+ </accessibility>
+ <signal handler="pref_changed_cb" last_modification_time="Sun, 20 Mar 2005 21:33:02 GMT" name="activate"/>
+ <signal handler="pref_changed_event_cb" last_modification_time="Sun, 20 Mar 2005 21:34:15 GMT" name="focus_out_event"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="text_program_browse">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Browse</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <accessibility>
+ <relation target="text_program_radio" type="controller-for"/>
+ </accessibility>
+ <signal handler="browse_text_program_cb" last_modification_time="Sun, 20 Mar 2005 01:24:51 GMT" name="clicked"/>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="text_file_entry">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">Text-displaying modes will display the contents of this file.</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"/>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ <accessibility>
+ <relation target="text_file_radio" type="labelled-by"/>
+ <relation target="text_file_radio" type="controlled-by"/>
+ </accessibility>
+ <signal handler="pref_changed_cb" last_modification_time="Sun, 20 Mar 2005 21:32:53 GMT" name="activate"/>
+ <signal handler="pref_changed_event_cb" last_modification_time="Sun, 20 Mar 2005 21:33:55 GMT" name="focus_out_event"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Text Manipulation</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ <accessibility>
+ <relation target="diag_frame" type="label-for"/>
+ </accessibility>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="dpms_frame">
+ <property name="border_width">10</property>
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="label_yalign">0.5</property>
+ <property name="shadow_type">GTK_SHADOW_ETCHED_IN</property>
+ <child>
+ <object class="GtkHBox" id="dpms_hbox">
+ <property name="border_width">8</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">8</property>
+ <child>
+ <object class="GtkImage" id="dpms_logo">
+ <property name="visible">True</property>
+ <property name="pixbuf">screensaver-power.png</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox6">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkCheckButton" id="dpms_button">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">Whether the monitor should be powered down after a while.</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Power Management Enabled</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">True</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <accessibility>
+ <relation target="dpms_suspend_spinbutton" type="controller-for"/>
+ <relation target="dpms_standby_spinbutton" type="controller-for"/>
+ <relation target="dpms_off_spinbutton" type="controller-for"/>
+ </accessibility>
+ <signal handler="pref_changed_cb" name="toggled"/>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkTable" id="dpms_table">
+ <property name="visible">True</property>
+ <property name="n_rows">3</property>
+ <property name="n_columns">3</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">2</property>
+ <property name="column_spacing">4</property>
+ <child>
+ <object class="GtkLabel" id="dpms_standby_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Stand_by After</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">1</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">10</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">dpms_standby_spinbutton</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ <accessibility>
+ <relation target="dpms_standby_spinbutton" type="label-for"/>
+ <relation target="dpms_standby_spinbutton" type="flows-to"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="dpms_suspend_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Sus_pend After</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">1</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">10</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">dpms_suspend_spinbutton</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ <accessibility>
+ <relation target="dpms_suspend_spinbutton" type="label-for"/>
+ <relation target="dpms_suspend_spinbutton" type="flows-to"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="dpms_off_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Off After</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">1</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">10</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">dpms_off_spinbutton</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ <accessibility>
+ <relation target="dpms_off_spinbutton" type="label-for"/>
+ <relation target="dpms_off_spinbutton" type="flows-to"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="dpms_standby_mlabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">minutes</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ <accessibility>
+ <relation target="dpms_standby_spinbutton" type="label-for"/>
+ <relation target="dpms_standby_spinbutton" type="flows-from"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="dpms_suspend_mlabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">minutes</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ <accessibility>
+ <relation target="dpms_suspend_spinbutton" type="label-for"/>
+ <relation target="dpms_suspend_spinbutton" type="flows-from"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="dpms_off_mlabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">minutes</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ <accessibility>
+ <relation target="dpms_off_spinbutton" type="label-for"/>
+ <relation target="dpms_off_spinbutton" type="flows-from"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="dpms_off_spinbutton">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">How long until the monitor powers down.</property>
+ <property name="can_focus">True</property>
+ <property name="climb_rate">15</property>
+ <property name="digits">0</property>
+ <property name="numeric">True</property>
+ <property name="update_policy">GTK_UPDATE_ALWAYS</property>
+ <property name="snap_to_ticks">True</property>
+ <property name="wrap">False</property>
+ <property name="adjustment">adjustment4</property>
+ <accessibility>
+ <relation target="dpms_button" type="controlled-by"/>
+ <relation target="dpms_off_label" type="labelled-by"/>
+ <relation target="dpms_off_mlabel" type="labelled-by"/>
+ <relation target="dpms_off_label" type="flows-from"/>
+ <relation target="dpms_off_mlabel" type="flows-to"/>
+ </accessibility>
+ <signal handler="pref_changed_cb" name="activate"/>
+ <signal handler="pref_changed_event_cb" name="focus_out_event"/>
+ <signal handler="pref_changed_cb" name="value_changed"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options"/>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="dpms_suspend_spinbutton">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">How long until the monitor goes into power-saving mode.</property>
+ <property name="can_focus">True</property>
+ <property name="climb_rate">15</property>
+ <property name="digits">0</property>
+ <property name="numeric">True</property>
+ <property name="update_policy">GTK_UPDATE_ALWAYS</property>
+ <property name="snap_to_ticks">True</property>
+ <property name="wrap">False</property>
+ <property name="adjustment">adjustment5</property>
+ <accessibility>
+ <relation target="dpms_button" type="controlled-by"/>
+ <relation target="dpms_suspend_label" type="labelled-by"/>
+ <relation target="dpms_suspend_mlabel" type="labelled-by"/>
+ <relation target="dpms_suspend_label" type="flows-from"/>
+ <relation target="dpms_suspend_mlabel" type="flows-to"/>
+ </accessibility>
+ <signal handler="pref_changed_cb" name="activate"/>
+ <signal handler="pref_changed_event_cb" name="focus_out_event"/>
+ <signal handler="pref_changed_cb" name="value_changed"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options"/>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="dpms_standby_spinbutton">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">How long until the monitor goes completely black.</property>
+ <property name="can_focus">True</property>
+ <property name="climb_rate">15</property>
+ <property name="digits">0</property>
+ <property name="numeric">True</property>
+ <property name="update_policy">GTK_UPDATE_ALWAYS</property>
+ <property name="snap_to_ticks">True</property>
+ <property name="wrap">False</property>
+ <property name="adjustment">adjustment6</property>
+ <accessibility>
+ <relation target="dpms_button" type="controlled-by"/>
+ <relation target="dpms_standby_label" type="labelled-by"/>
+ <relation target="dpms_standby_mlabel" type="labelled-by"/>
+ <relation target="dpms_standby_label" type="flows-from"/>
+ <relation target="dpms_standby_mlabel" type="flows-to"/>
+ </accessibility>
+ <signal handler="pref_changed_cb" name="activate"/>
+ <signal handler="pref_changed_event_cb" name="focus_out_event"/>
+ <signal handler="pref_changed_cb" name="value_changed"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options"/>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="dpms_quickoff_button">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">Whether the monitor should be powered off immediately in "Blank Screen Only" mode, regardless of the above power-management timeouts.</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Quick Power-off in Blank Only Mode</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <signal handler="pref_changed_cb" name="toggled"/>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Display Power Management</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ <accessibility>
+ <relation target="dpms_frame" type="label-for"/>
+ </accessibility>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="blanking_frame">
+ <property name="border_width">10</property>
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="label_yalign">0.5</property>
+ <property name="shadow_type">GTK_SHADOW_ETCHED_IN</property>
+ <accessibility>
+ <relation target="label5" type="labelled-by"/>
+ </accessibility>
+ <child>
+ <object class="GtkHBox" id="fading_hbox">
+ <property name="border_width">8</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">8</property>
+ <child>
+ <object class="GtkImage" id="image5">
+ <property name="visible">True</property>
+ <property name="pixbuf">screensaver-colorselector.png</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox7">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkCheckButton" id="fade_button">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">Whether the screen should slowly fade to black when the screen saver activates.</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Fade to Black when _Blanking</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <accessibility>
+ <relation target="fade_spinbutton" type="controller-for"/>
+ </accessibility>
+ <signal handler="pref_changed_cb" name="toggled"/>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="unfade_button">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">Whether the screen should slowly fade in from black when the screen saver deactivates.</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Fade from Black When _Unblanking</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <accessibility>
+ <relation target="fade_spinbutton" type="controller-for"/>
+ </accessibility>
+ <signal handler="pref_changed_cb" name="toggled"/>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="fade_hbox">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkLabel" id="fade_dummy">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"/>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">3</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="fade_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">F_ade Duration</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">fade_spinbutton</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ <accessibility>
+ <relation target="fade_spinbutton" type="label-for"/>
+ <relation target="fade_spinbutton" type="flows-to"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="padding">14</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="fade_spinbutton">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">How long it should take for the screen to fade in and out.</property>
+ <property name="can_focus">True</property>
+ <property name="climb_rate">1</property>
+ <property name="digits">0</property>
+ <property name="numeric">True</property>
+ <property name="update_policy">GTK_UPDATE_ALWAYS</property>
+ <property name="snap_to_ticks">True</property>
+ <property name="wrap">False</property>
+ <property name="adjustment">adjustment7</property>
+ <accessibility>
+ <relation target="unfade_button" type="controlled-by"/>
+ <relation target="fade_button" type="controlled-by"/>
+ <relation target="fade_label" type="labelled-by"/>
+ <relation target="fade_sec_label" type="labelled-by"/>
+ <relation target="fade_label" type="flows-from"/>
+ <relation target="fade_sec_label" type="flows-to"/>
+ </accessibility>
+ <signal handler="pref_changed_cb" name="activate"/>
+ <signal handler="pref_changed_event_cb" name="focus_out_event"/>
+ <signal handler="pref_changed_cb" name="value_changed"/>
+ </object>
+ <packing>
+ <property name="padding">4</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="fade_sec_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">seconds</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ <accessibility>
+ <relation target="fade_spinbutton" type="label-for"/>
+ <relation target="fade_spinbutton" type="flows-from"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="padding">2</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHSeparator" id="blanking_hr">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="padding">8</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+
+ <child>
+ <object class="GtkHBox" id="theme_hbox">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkLabel" id="theme_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Theme:</property>
+ <property name="tooltip-text" translatable="yes">The color scheme to use on the unlock dialog.</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">theme_menu</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ <accessibility>
+ <relation target="theme_menu" type="label-for"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="theme_menu">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="has_entry">False</property>
+ <property name="model">theme_menu_model</property>
+ <accessibility>
+ <relation target="theme_label" type="labelled-by"/>
+ </accessibility>
+ <child>
+ <object class="GtkCellRendererText" id="renderer2"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">4</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="theme_preview">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">Show the what the unlock dialog will look like.</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Preview</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <signal handler="preview_theme_cb" name="clicked"/>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">10</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+
+
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Blanking</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ <accessibility>
+ <relation target="blanking_frame" type="label-for"/>
+ </accessibility>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="options_tab">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Advanced</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">notebook</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHButtonBox" id="hbuttonbox2">
+ <property name="border_width">5</property>
+ <property name="layout_style">GTK_BUTTONBOX_EDGE</property>
+ <property name="spacing">10</property>
+ <child>
+ <object class="GtkButton" id="helpbutton">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-help</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <signal handler="doc_menu_cb" name="clicked"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="closebutton">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-close</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <signal handler="exit_menu_cb" name="clicked"/>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkDialog" id="xscreensaver_settings_dialog">
+ <property name="title" translatable="yes">dialog1</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog_vbox">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog_action_area">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <object class="GtkButton" id="adv_button">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Advanced &gt;&gt;</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <signal handler="settings_adv_cb" name="clicked"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="std_button">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Standard &lt;&lt;</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <signal handler="settings_std_cb" name="clicked"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="reset_button">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Reset to Defaults</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <signal handler="settings_reset_cb" name="clicked"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="cancel_button">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <signal handler="settings_cancel_cb" name="clicked"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="ok_button">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-ok</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <signal handler="settings_ok_cb" name="clicked"/>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkFrame" id="opt_frame">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="label_yalign">0</property>
+ <property name="shadow_type">GTK_SHADOW_ETCHED_IN</property>
+ <accessibility>
+ <relation target="label6" type="labelled-by"/>
+ </accessibility>
+ <child>
+ <object class="GtkNotebook" id="opt_notebook">
+ <property name="border_width">12</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="show_tabs">True</property>
+ <property name="show_border">False</property>
+ <property name="tab_pos">GTK_POS_BOTTOM</property>
+ <property name="scrollable">False</property>
+ <property name="enable_popup">False</property>
+ <signal handler="settings_switch_page_cb" name="switch_page"/>
+ <child>
+ <object class="GtkVBox" id="settings_vbox">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="tab_expand">True</property>
+ <property name="tab_fill">True</property>
+ <property name="tab_pack">GTK_PACK_END</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="std_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Standard</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkTable" id="adv_table">
+ <property name="visible">True</property>
+ <property name="n_rows">4</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">0</property>
+ <property name="column_spacing">0</property>
+ <child>
+ <object class="GtkImage" id="cmd_logo">
+ <property name="visible">True</property>
+ <property name="pixbuf">screensaver-cmndln.png</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">4</property>
+ <property name="ypad">8</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="cmd_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Command Line:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">cmd_text</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ <accessibility>
+ <relation target="cmd_text" type="label-for"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="cmd_text">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"/>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ <accessibility>
+ <relation target="cmd_label" type="labelled-by"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="visual_hbox">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkLabel" id="visual">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Visual:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">1</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">3</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">visual_combo</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ <accessibility>
+ <relation target="visual_combo" type="label-for"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxEntry" id="visual_combo">
+ <property name="has_entry">False</property>
+ <property name="visible">True</property>
+ <property name="model">visual_combo_model</property>
+ <property name="text-column">0</property>
+ <accessibility>
+ <relation target="visual" type="labelled-by"/>
+ </accessibility>
+ <child internal-child="entry">
+ <object class="GtkEntry" id="visual_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"/>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">fill</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="adv_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Advanced</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Settings</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ <accessibility>
+ <relation target="opt_frame" type="label-for"/>
+ </accessibility>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="doc_frame">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="label_yalign">0</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+ <child>
+ <object class="GtkVBox" id="doc_vbox">
+ <property name="border_width">5</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="doc">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes"/>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">True</property>
+ <property name="selectable">True</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkButton" id="manual">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Documentation...</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <signal handler="manual_cb" name="clicked"/>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"/>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">adv_button</action-widget>
+ <action-widget response="0">std_button</action-widget>
+ <action-widget response="0">reset_button</action-widget>
+ <action-widget response="-6">cancel_button</action-widget>
+ <action-widget response="-5">ok_button</action-widget>
+ </action-widgets>
+ </object>
+</interface>