summaryrefslogtreecommitdiffstats
path: root/driver
diff options
context:
space:
mode:
authorSimon Rettberg2018-10-16 10:08:48 +0200
committerSimon Rettberg2018-10-16 10:08:48 +0200
commitd3a98cf6cbc3bd0b9efc570f58e8812c03931c18 (patch)
treecbddf8e50f35a9c6e878a5bfe3c6d625d99e12ba /driver
downloadxscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.tar.gz
xscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.tar.xz
xscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.zip
Original 5.40
Diffstat (limited to 'driver')
-rw-r--r--driver/.gdbinit27
-rw-r--r--driver/Makefile.in1023
-rw-r--r--driver/README6
-rw-r--r--driver/XScreenSaver-Xm.ad126
-rw-r--r--driver/XScreenSaver.ad.in556
-rw-r--r--driver/XScreenSaver_Xm_ad.h108
-rw-r--r--driver/XScreenSaver_ad.h416
-rw-r--r--driver/auth.h54
-rw-r--r--driver/compile_axp.com15
-rw-r--r--driver/compile_decc.com15
-rw-r--r--driver/demo-Gtk-conf.c1998
-rw-r--r--driver/demo-Gtk-conf.h31
-rw-r--r--driver/demo-Gtk.c5337
-rw-r--r--driver/demo-Xm-widgets.c907
-rw-r--r--driver/demo-Xm.c1875
-rw-r--r--driver/dpms.c304
-rw-r--r--driver/exec.c300
-rw-r--r--driver/exec.h21
-rw-r--r--driver/link_axp.com15
-rw-r--r--driver/link_decc.com15
-rw-r--r--driver/lock.c2259
-rw-r--r--driver/mlstring.c229
-rw-r--r--driver/mlstring.h57
-rw-r--r--driver/passwd-helper.c162
-rw-r--r--driver/passwd-kerberos.c251
-rw-r--r--driver/passwd-pam.c526
-rw-r--r--driver/passwd-pwent.c312
-rw-r--r--driver/passwd.c339
-rw-r--r--driver/pdf2jpeg.m152
-rw-r--r--driver/pdf2jpeg.man43
-rw-r--r--driver/prefs.c1770
-rw-r--r--driver/prefs.h37
-rw-r--r--driver/remote.c595
-rw-r--r--driver/remote.h24
-rw-r--r--driver/screens.c1094
-rw-r--r--driver/screensaver-properties.desktop.in8
-rw-r--r--driver/setuid.c361
-rw-r--r--driver/splash.c917
-rw-r--r--driver/stderr.c560
-rw-r--r--driver/subprocs.c1423
-rw-r--r--driver/test-apm.c101
-rw-r--r--driver/test-fade.c123
-rw-r--r--driver/test-grab.c89
-rw-r--r--driver/test-mlstring.c312
-rw-r--r--driver/test-passwd.c306
-rw-r--r--driver/test-randr.c339
-rw-r--r--driver/test-screens.c208
-rw-r--r--driver/test-uid.c209
-rw-r--r--driver/test-vp.c213
-rw-r--r--driver/test-xdpms.c179
-rw-r--r--driver/test-xinerama.c112
-rw-r--r--driver/timers.c1788
-rw-r--r--driver/types.h442
-rw-r--r--driver/vms-getpwnam.c129
-rw-r--r--driver/vms-hpwd.c75
-rw-r--r--driver/vms-pwd.h48
-rw-r--r--driver/vms-validate.c75
-rw-r--r--driver/vms_axp.opt5
-rw-r--r--driver/vms_axp_12.opt5
-rw-r--r--driver/vms_decc.opt5
-rw-r--r--driver/vms_decc_12.opt5
-rw-r--r--driver/windows.c2003
-rw-r--r--driver/xdpyinfo.c1098
-rw-r--r--driver/xscreensaver-command.c450
-rw-r--r--driver/xscreensaver-command.man263
-rw-r--r--driver/xscreensaver-demo.glade2.in3136
-rw-r--r--driver/xscreensaver-demo.glade2p19
-rw-r--r--driver/xscreensaver-demo.man402
-rwxr-xr-xdriver/xscreensaver-getimage-desktop174
-rw-r--r--driver/xscreensaver-getimage-desktop.man55
-rwxr-xr-xdriver/xscreensaver-getimage-file1316
-rw-r--r--driver/xscreensaver-getimage-file.man66
-rwxr-xr-xdriver/xscreensaver-getimage-video144
-rw-r--r--driver/xscreensaver-getimage-video.man51
-rw-r--r--driver/xscreensaver-getimage.c2000
-rw-r--r--driver/xscreensaver-getimage.man73
-rwxr-xr-xdriver/xscreensaver-text884
-rw-r--r--driver/xscreensaver-text.man85
-rw-r--r--driver/xscreensaver.c2463
-rw-r--r--driver/xscreensaver.h210
-rw-r--r--driver/xscreensaver.man1035
-rw-r--r--driver/xscreensaver.pam.in13
-rw-r--r--driver/xset.c389
83 files changed, 45365 insertions, 0 deletions
diff --git a/driver/.gdbinit b/driver/.gdbinit
new file mode 100644
index 0000000..a585259
--- /dev/null
+++ b/driver/.gdbinit
@@ -0,0 +1,27 @@
+# If you're debugging xscreensaver and you are running a virtual root window
+# manager, you'd better let the process handle these signals: it remaps the
+# virtual root window when they arrive. If you don't do this, your window
+# manager will be hosed.
+#
+# Also, gdb copes badly with breakpoints in functions that are called on the
+# other side of a fork(). The Trace/BPT traps cause the spawned process to
+# die.
+#
+#handle 1 pass nostop
+#handle 3 pass nostop
+#handle 4 pass nostop
+#handle 6 pass nostop
+#handle 7 pass nostop
+#handle 8 pass nostop
+#handle 9 pass nostop
+#handle 10 pass nostop
+#handle 11 pass nostop
+#handle 12 pass nostop
+#handle 13 pass nostop
+#handle 15 pass nostop
+#handle 19 pass nostop
+set env MallocGuardEdges 1
+set env MallocPreScribble 1
+set env MallocScribble 1
+b exit
+set args -debug
diff --git a/driver/Makefile.in b/driver/Makefile.in
new file mode 100644
index 0000000..55effec
--- /dev/null
+++ b/driver/Makefile.in
@@ -0,0 +1,1023 @@
+# driver/Makefile.in --- xscreensaver, Copyright (c) 1997-2010 Jamie Zawinski.
+# the `../configure' script generates `driver/Makefile' from this file.
+
+@SET_MAKE@
+.SUFFIXES:
+.SUFFIXES: .c .m .o
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+top_srcdir = @top_srcdir@
+top_builddir = ..
+
+install_prefix =
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+bindir = @bindir@
+datarootdir = @datarootdir@
+datadir = @datadir@
+localedir = @PO_DATADIR@/locale
+mandir = @mandir@
+libexecdir = @libexecdir@
+mansuffix = 1
+manNdir = $(mandir)/man$(mansuffix)
+
+INTLTOOL_MERGE = @INTLTOOL_MERGE@
+
+GTK_DATADIR = @GTK_DATADIR@
+GTK_APPDIR = $(GTK_DATADIR)/applications
+GTK_ICONDIR = $(GTK_DATADIR)/pixmaps
+GTK_GLADEDIR = $(GTK_DATADIR)/xscreensaver/glade
+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_DATA = @INSTALL_DATA@
+INSTALL_DIRS = @INSTALL_DIRS@
+
+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@
+
+# Note:
+#
+# X_LIBS would more properly be called X_LDFLAGS (it contains the -L args.)
+# X_PRE_LIBS contains extra libraries you have to link against on some systems,
+# and that must come before -lX11. (e.g., -lSM and -lICE.)
+# X_EXTRA_LIBS contains extra libraries needed by X that aren't a part of X.
+# (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
+
+UTILS_SRC = $(srcdir)/../utils
+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
+
+GTK_SRCS = demo-Gtk.c demo-Gtk-conf.c
+GTK_OBJS = demo-Gtk.o demo-Gtk-conf.o @GTK_EXTRA_OBJS@
+
+PWENT_SRCS = passwd-pwent.c
+PWENT_OBJS = passwd-pwent.o
+
+KERBEROS_SRCS = passwd-kerberos.c
+KERBEROS_OBJS = passwd-kerberos.o
+
+PAM_SRCS = passwd-pam.c
+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
+
+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_EXES = test-passwd test-uid test-xdpms test-grab \
+ test-apm test-fade test-xinerama test-vp \
+ test-randr xdpyinfo test-mlstring test-screens
+
+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)
+
+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@
+EXES2 = @ALL_DEMO_PROGRAMS@
+EXES_OSX = pdf2jpeg
+
+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
+MEN_OSX = xscreensaver-getimage-desktop.man pdf2jpeg.man
+MEN = $(MEN_1) @MEN_OSX@
+
+EXTRAS = README Makefile.in \
+ XScreenSaver.ad.in XScreenSaver-Xm.ad xscreensaver.pam.in \
+ xscreensaver-demo.glade2.in xscreensaver-demo.glade2p \
+ 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) \
+ $(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)
+
+
+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
+
+install-strip:
+ $(MAKE) INSTALL_PROGRAM='$(INSTALL_PROGRAM) -s' \
+ install
+
+install-program: $(EXES)
+ @if [ ! -d $(install_prefix)$(bindir) ]; then \
+ $(INSTALL_DIRS) $(install_prefix)$(bindir) ; \
+ 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@ ; do \
+ echo $(INSTALL_PROGRAM) $$exe $(install_prefix)$(bindir)/$$exe ; \
+ $(INSTALL_PROGRAM) $$exe $(install_prefix)$(bindir)/$$exe ; \
+ done
+
+install-ad: XScreenSaver.ad
+ @if [ ! -d $(install_prefix)$(AD_DIR) ]; then \
+ $(INSTALL_DIRS) $(install_prefix)$(AD_DIR) ; \
+ fi
+ @-echo $(INSTALL_DATA) XScreenSaver.ad \
+ $(install_prefix)$(AD_DIR)/XScreenSaver ; \
+ if $(INSTALL_DATA) XScreenSaver.ad \
+ $(install_prefix)$(AD_DIR)/XScreenSaver ; then \
+ true ; \
+ else \
+ e=echo ; \
+ if [ -f $(install_prefix)$(AD_DIR)/XScreenSaver ]; then \
+ $$e "" ;\
+ $$e " ####################################################################";\
+ $$e " Warning: unable to install $(install_prefix)$(AD_DIR)/XScreenSaver" ;\
+ $$e " That file exists, and is unwritable. It is probably from" ;\
+ $$e " an older version of xscreensaver, and could cause things" ;\
+ $$e " to malfunction. Please delete it!" ;\
+ $$e " ####################################################################";\
+ $$e "" ;\
+ exit 1 ; \
+ else \
+ $$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 " ####################################################################";\
+ $$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.
+#
+install-man: $(MEN)
+ @men="$(MEN)" ; \
+ 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" ; \
+ \
+ if [ ! -d $(install_prefix)$(manNdir) ]; then \
+ $(INSTALL_DIRS) $(install_prefix)$(manNdir) ; \
+ 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' \
+ < $(srcdir)/$$man > $$T ; \
+ echo $(INSTALL_DATA) $(srcdir)/$$man \
+ $(install_prefix)$(manNdir)/$$instname ; \
+ $(INSTALL_DATA) $$T \
+ $(install_prefix)$(manNdir)/$$instname ; \
+ done ; \
+ rm -f $$T
+
+uninstall-program:
+ @for program in $(EXES) $(SCRIPTS); do \
+ echo rm -f $(install_prefix)$(bindir)/$$program ; \
+ rm -f $(install_prefix)$(bindir)/$$program ; \
+ done
+
+uninstall-ad:
+ rm -f $(install_prefix)$(AD_DIR)/XScreenSaver
+
+uninstall-man:
+ @men="$(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* ; \
+ done
+
+install-pam: xscreensaver.pam
+ @src="xscreensaver.pam" ; \
+ dest=`sed -n 's/.*PAM_SERVICE_NAME[ ]*"\([^"]*\)".*$$/\1/p' \
+ < ../config.h` ; \
+ dir="$(install_prefix)$(PAM_DIR)" ; \
+ conf="$(PAM_CONF)" ; \
+ \
+ if [ -d $$dir ] ; then \
+ \
+ if [ -f $$dir/xdm ]; then \
+ src2=$$dir/xdm ; \
+ elif [ -f $$dir/login ]; then \
+ src2=$$dir/login ; \
+ fi ; \
+ \
+ if [ -z "$$src2" ]; then \
+ echo $(INSTALL_DATA) $$src $$dir/$$dest ; \
+ $(INSTALL_DATA) $$src $$dir/$$dest ; \
+ 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 ; \
+ fi ; \
+ \
+ if [ ! -f $$dir/$$dest ]; 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 " ####################################################################";\
+ $$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" ; \
+ 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 ; \
+ if [ "x$$extras" = "x" ]; then \
+ echo "Error: no login rules in $$conf?" >&2 ; \
+ else \
+ echo "adding $$dest rules to $$src:" ; \
+ echo "$$extras" | sed 's/^/ /' ; \
+ fi ; \
+ echo $(INSTALL_DATA) $$src $$conf ; \
+ $(INSTALL_DATA) $$src $$conf ; \
+ echo rm -f $$src ; \
+ rm -f $$src ; \
+ fi ; \
+ if ( grep $$dest $$conf >/dev/null ); then \
+ echo ; \
+ else \
+ 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 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 " ####################################################################";\
+ $$e "" ;\
+ fi ; \
+ fi
+
+# screensaver-properties.desktop
+# into /usr/share/applications/
+install-gnome:: screensaver-properties.desktop
+ @if [ "$(GTK_DATADIR)" != "" ]; then \
+ if [ ! -d "$(install_prefix)$(GTK_APPDIR)" ]; then \
+ echo $(INSTALL_DIRS) "$(install_prefix)$(GTK_APPDIR)" ;\
+ $(INSTALL_DIRS) "$(install_prefix)$(GTK_APPDIR)" ;\
+ fi ;\
+ name2=xscreensaver-properties.desktop ;\
+ echo $(INSTALL_DATA) screensaver-properties.desktop \
+ $(install_prefix)$(GTK_APPDIR)/$$name2 ;\
+ $(INSTALL_DATA) screensaver-properties.desktop \
+ $(install_prefix)$(GTK_APPDIR)/$$name2 ;\
+ fi
+
+
+# xscreensaver.xpm
+# into /usr/share/pixmaps/
+install-gnome:: $(LOGO)
+ @if [ "$(GTK_DATADIR)" != "" ]; then \
+ if [ ! -d "$(install_prefix)$(GTK_ICONDIR)" ]; then \
+ echo $(INSTALL_DIRS) "$(install_prefix)$(GTK_ICONDIR)" ;\
+ $(INSTALL_DIRS) "$(install_prefix)$(GTK_ICONDIR)" ;\
+ fi ;\
+ target=xscreensaver.xpm ;\
+ echo $(INSTALL_DATA) $(LOGO) \
+ $(install_prefix)$(GTK_ICONDIR)/$$target ;\
+ $(INSTALL_DATA) $(LOGO) \
+ $(install_prefix)$(GTK_ICONDIR)/$$target ;\
+ fi
+
+# ../utils/images/screensaver-*.png
+# into /usr/share/xscreensaver/glade/
+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)" ;\
+ fi ;\
+ for target in $(GTK_ICONS) ; do \
+ dest=`echo $$target | sed 's@^.*/@@'` ;\
+ echo $(INSTALL_DATA) $$target \
+ $(install_prefix)$(GTK_GLADEDIR)/$$dest ;\
+ $(INSTALL_DATA) $$target \
+ $(install_prefix)$(GTK_GLADEDIR)/$$dest ;\
+ done ;\
+ fi
+
+# xscreensaver-demo.glade2
+# into /usr/share/xscreensaver/glade/
+install-gnome:: xscreensaver-demo.glade2
+ @if [ "$(GTK_DATADIR)" != "" ]; then \
+ if [ ! -d "$(install_prefix)$(GTK_GLADEDIR)" ]; then \
+ echo $(INSTALL_DIRS) "$(install_prefix)$(GTK_GLADEDIR)" ;\
+ $(INSTALL_DIRS) "$(install_prefix)$(GTK_GLADEDIR)" ;\
+ fi ;\
+ target=xscreensaver-demo.glade2 ;\
+ echo $(INSTALL_DATA) $$target \
+ $(install_prefix)$(GTK_GLADEDIR)/$$target ;\
+ if $(INSTALL_DATA) $$target \
+ $(install_prefix)$(GTK_GLADEDIR)/$$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 " be able to run properly." ;\
+ $$e " ####################################################################";\
+ $$e "" ;\
+ exit 1 ; \
+ fi ; \
+ fi
+
+
+# screensaver-properties.desktop
+# into /usr/share/applications/
+uninstall-gnome::
+ @if [ "$(GTK_DATADIR)" != "" ]; then \
+ f=xscreensaver-properties.desktop ;\
+ echo rm -f $(install_prefix)$(GTK_APPDIR)/$$f ;\
+ rm -f $(install_prefix)$(GTK_APPDIR)/$$f ;\
+ fi
+
+# xscreensaver.xpm
+# into /usr/share/pixmaps/
+uninstall-gnome::
+ @if [ "$(GTK_ICONDIR)" != "" ]; then \
+ target=xscreensaver.xpm ;\
+ echo rm -f $(install_prefix)$(GTK_ICONDIR)/$$target ;\
+ rm -f $(install_prefix)$(GTK_ICONDIR)/$$target ;\
+ fi
+
+# ../utils/images/screensaver-*.png
+# into /usr/share/xscreensaver/glade/
+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 ;\
+ done ;\
+ fi
+
+# xscreensaver-demo.glade2
+# into /usr/share/xscreensaver/glade/
+uninstall-gnome:: xscreensaver-demo.glade2
+ @if [ "$(GTK_DATADIR)" != "" ]; then \
+ target=xscreensaver-demo.glade2 ;\
+ echo rm -f $(install_prefix)$(GTK_GLADEDIR)/$$target ;\
+ rm -f $(install_prefix)$(GTK_GLADEDIR)/$$target ;\
+ fi
+
+# /usr/share/xscreensaver/config/README
+install-xml:
+ @dest=$(install_prefix)$(HACK_CONF_DIR) ; \
+ if [ ! -d $$dest ]; then \
+ $(INSTALL_DIRS) $$dest ; \
+ fi ; \
+ src=$(srcdir)/../hacks/config ; \
+ echo $(INSTALL_DATA) $$src/README $$dest/README ; \
+ $(INSTALL_DATA) $$src/README $$dest/README
+
+
+# /usr/share/xscreensaver/config/README
+uninstall-xml:
+ rm -f $(install_prefix)$(HACK_CONF_DIR)/README
+
+clean:
+ -rm -f *.o a.out core $(EXES) $(EXES2) $(TEST_EXES) \
+ XScreenSaver_ad.h XScreenSaver_Xm_ad.h
+
+distclean: clean
+ -rm -f Makefile XScreenSaver.ad \
+ TAGS *~ "#"* screensaver-properties.desktop
+
+# Adds all current dependencies to Makefile
+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)
+
+# Adds some dependencies to Makefile.in -- not totally accurate, but pretty
+# close. This excludes dependencies on files in /usr/include, etc. It tries
+# to include only dependencies on files which are themselves a part of this
+# package.
+distdepend: check_men update_ad_version XScreenSaver_ad.h XScreenSaver_Xm_ad.h
+ @echo updating dependencies in `pwd`/Makefile.in... ; \
+ $(DEPEND) -w 0 -f - \
+ -s '# DO NOT DELETE: updated by make distdepend' $(DEPEND_FLAGS) -- \
+ $(INCLUDES_1) $(DEFS) $(DEPEND_DEFINES) $(CFLAGS) $(X_CFLAGS) -- \
+ $(SAVER_SRCS_1) $(MOTIF_SRCS) $(GTK_SRCS) $(GETIMG_SRCS_1) \
+ $(PWENT_SRCS) $(LOCK_SRCS_1) $(DEMO_SRCS_1) $(CMD_SRCS) \
+ $(TEST_SRCS) 2>/dev/null | \
+ sort -d | \
+ ( \
+ awk '/^# .*Makefile.in ---/,/^# DO .*distdepend/' < Makefile.in ; \
+ sed -e '/^#.*/d' \
+ -e 's@ \./@ @g;s@ /[^ ]*@@g;/^.*:$$/d' \
+ -e 's@\.\./utils@$$(UTILS_SRC)@g' \
+ -e 's@ \([^$$]\)@ $$(srcdir)/\1@g' \
+ -e 's@$$.*\(XScreenSaver_ad\)@\1@g' \
+ -e 's@$$.*\(XScreenSaver_Xm_ad\)@\1@g' \
+ -e 's@ $$(srcdir)/\(.*config\.h\)@ \1@g' ; \
+ echo '' \
+ ) > /tmp/distdepend.$$$$ && \
+ mv /tmp/distdepend.$$$$ Makefile.in
+
+# Updates the version number in the app-defaults file to be in sync with
+# the version number in version.h. This is so people can tell when they
+# have a version skew between the app-defaults file and the executable.
+# Also update hacks/config/README in the same way.
+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` ; \
+ 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/" \
+ -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 ; \
+ if cmp -s $$S $$T ; then \
+ true ; \
+ else \
+ cat $$T > $$S ; \
+ echo "updated $$S to $$V $$D" ; \
+ fi ; \
+ done ; \
+ rm $$T
+
+TAGS: tags
+tags:
+ find $(srcdir) -name '*.[chly]' -print | xargs etags -a
+
+echo_tarfiles:
+ @$(MAKE) XScreenSaver_ad.h XScreenSaver_Xm_ad.h 2>&1 >/dev/null
+ @echo $(TARFILES)
+
+check_men:
+ @badmen="" ; \
+ for exe in $(EXES); do \
+ if ! [ -f $(srcdir)/$$exe.man ]; then \
+ badmen="$$badmen $$exe" ; \
+ fi ; \
+ done ; \
+ if [ -n "$$badmen" ]; then \
+ echo "" ; \
+ echo "Warning: The following programs have no manuals:" ; \
+ echo "" ; \
+ for m in $$badmen ; do \
+ echo " $$m" ; \
+ done ; \
+ echo "" ; \
+ 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...
+#
+$(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
+$(UTILS_BIN)/visual.o: $(UTILS_SRC)/visual.c
+$(UTILS_BIN)/xmu.o: $(UTILS_SRC)/xmu.c
+$(UTILS_BIN)/logo.o: $(UTILS_SRC)/logo.c
+$(UTILS_BIN)/minixpm.o: $(UTILS_SRC)/minixpm.c
+$(UTILS_BIN)/yarandom.o: $(UTILS_SRC)/yarandom.c
+$(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)/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
+
+$(UTIL_OBJS):
+ cd $(UTILS_BIN) ; \
+ $(MAKE) $(@F) CC="$(CC)" CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)"
+
+# How we build object files in this directory.
+.c.o:
+ $(CC) -c $(INCLUDES) $(DEFS) $(CPPFLAGS) $(CFLAGS) $(X_CFLAGS) $<
+
+.m.o:
+ $(OBJCC) -c $(INCLUDES) $(DEFS) $(CPPFLAGS) $(CFLAGS) $(X_CFLAGS) $<
+
+# subprocs takes an extra -D option.
+subprocs.o: subprocs.c
+ $(CC) -c $(INCLUDES) $(SUBP_DEFS) $(CPPFLAGS) $(CFLAGS) $(X_CFLAGS) \
+ $(srcdir)/subprocs.c
+
+# xscreensaver takes an extra -D option.
+xscreensaver.o: xscreensaver.c
+ $(CC) -c $(INCLUDES) $(DEFS) $(INTL_DEFS) $(CPPFLAGS) $(CFLAGS) $(X_CFLAGS) \
+ $(srcdir)/xscreensaver.c
+
+# 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
+
+# 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
+
+
+# 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
+
+XScreenSaver_Xm_ad.h: XScreenSaver-Xm.ad
+ $(SHELL) $(UTILS_SRC)/ad2c XScreenSaver-Xm.ad > XScreenSaver_Xm_ad.h
+
+@INTLTOOL_DESKTOP_RULE@
+
+# The executables linked in this directory.
+#
+xscreensaver: $(SAVER_OBJS)
+ $(CC) $(LDFLAGS) -o $@ $(SAVER_OBJS) $(SAVER_LIBS) $(INTL_LIBS)
+
+xscreensaver-command: $(CMD_OBJS)
+ $(CC) $(LDFLAGS) -o $@ $(CMD_OBJS) $(CMD_LIBS)
+
+
+xscreensaver-demo: @PREFERRED_DEMO_PROGRAM@
+ @if [ "@PREFERRED_DEMO_PROGRAM@" = "" ]; then \
+ echo "WARNING: neither GTK nor Motif are available," \
+ "therefore no xscreensaver-demo!" ; \
+ 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)
+
+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-getimage: $(GETIMG_OBJS)
+ $(CC) $(LDFLAGS) -o $@ $(GETIMG_OBJS) $(GETIMG_LIBS) -lm
+
+pdf2jpeg: $(PDF2JPEG_OBJS)
+ $(OBJCC) $(LDFLAGS) -o $@ $(PDF2JPEG_OBJS) $(PDF2JPEG_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
+
+test-passwd: $(TEST_PASSWD_OBJS) XScreenSaver_ad.h
+ $(CC) $(LDFLAGS) -o $@ $(TEST_PASSWD_OBJS) $(SAVER_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)
+
+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)
+
+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)
+
+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)
+
+test-grab: test-grab.o
+ $(CC) $(LDFLAGS) -o $@ test-grab.o $(SAVER_LIBS)
+
+test-apm: test-apm.o
+ $(CC) $(LDFLAGS) -o $@ test-apm.o $(SAVER_LIBS) -lapm
+
+test-mlstring.o: mlstring.c
+test-mlstring: test-mlstring.o
+ $(CC) -DTEST $(LDFLAGS) -o $@ test-mlstring.o $(SAVER_LIBS)
+
+TEST_FADE_OBJS = test-fade.o $(UTILS_SRC)/fade.o $(DEMO_UTIL_OBJS)
+test-fade: test-fade.o $(UTILS_BIN)/fade.o
+ $(CC) $(LDFLAGS) -o $@ $(TEST_FADE_OBJS) $(SAVER_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)
+
+
+xdpyinfo.o: xdpyinfo.c
+ $(CC) -c $(INCLUDES) -DHAVE_GLX $(CPPFLAGS) $(CFLAGS) $(X_CFLAGS) \
+ $(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
+
+
+##############################################################################
+#
+# DO NOT DELETE: updated by make distdepend
+
+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: ../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)/xscreensaver-intl.h
+demo-Xm.o: ../config.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.o: $(srcdir)/auth.h
+passwd.o: ../config.h
+passwd.o: $(srcdir)/prefs.h
+passwd.o: $(srcdir)/types.h
+passwd.o: $(srcdir)/xscreensaver.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: ../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: ../config.h
+test-fade.o: $(srcdir)/prefs.h
+test-fade.o: $(srcdir)/types.h
+test-fade.o: $(UTILS_SRC)/fade.h
+test-fade.o: $(srcdir)/xscreensaver.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: ../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: ../config.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: $(UTILS_SRC)/visual.h
+test-screens.o: $(srcdir)/xscreensaver.h
+test-uid.o: ../config.h
+test-vp.o: ../config.h
+test-xdpms.o: ../config.h
+test-xinerama.o: ../config.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: ../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
+
diff --git a/driver/README b/driver/README
new file mode 100644
index 0000000..df64793
--- /dev/null
+++ b/driver/README
@@ -0,0 +1,6 @@
+
+This directory contains the source for xscreensaver and xscreensaver-command,
+the screensaver driver, and the program for externally controlling it. Some
+stuff from the ../utils/ directory is used here as well.
+
+If you have compilation problems, check the parameters in ../config.h.
diff --git a/driver/XScreenSaver-Xm.ad b/driver/XScreenSaver-Xm.ad
new file mode 100644
index 0000000..6b04ae9
--- /dev/null
+++ b/driver/XScreenSaver-Xm.ad
@@ -0,0 +1,126 @@
+! Resources for the Motif dialog boxes of the "xscreensaver-demo" program.
+!
+*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
+! above must be fully qualified to get around *sgiMode.
+
+*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.in b/driver/XScreenSaver.ad.in
new file mode 100644
index 0000000..f4e29d4
--- /dev/null
+++ b/driver/XScreenSaver.ad.in
@@ -0,0 +1,556 @@
+!
+! XScreenSaver
+!
+! a screen saver and locker for the X window system
+! by Jamie Zawinski
+!
+! version 5.40
+! 12-Aug-2018
+!
+! See "man xscreensaver" for more info. The latest version is always
+! available at https://www.jwz.org/xscreensaver/
+
+
+! These resources, when placed in the system-wide app-defaults directory
+! (e.g., /usr/lib/X11/app-defaults/XScreenSaver) will provide the default
+! settings for new users. However, if you have a ".xscreensaver" file in
+! your home directory, the settings in that file take precedence.
+
+
+! Don't hand this file to "xrdb" -- that isn't how app-defaults files work.
+! Though app-defaults files have (mostly) the same syntax as your ~/.Xdefaults
+! file, they are used differently, and if you run this file through xrdb,
+! you will probably mess things up.
+
+#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.
+
+! /* (xrdb prevention kludge: whole file)
+
+*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: @DEFAULT_IMAGES_P@
+! This can be a local directory name, or the URL of an RSS or Atom feed.
+*imageDirectory: @DEFAULT_IMAGE_DIRECTORY@
+*nice: 10
+*memoryLimit: 0
+*lock: False
+*verbose: False
+*timestamp: True
+*fade: True
+*unfade: False
+*fadeSeconds: 0:00:03
+*fadeTicks: 20
+*splash: True
+*splashDuration: 0:00:05
+*visualID: default
+*captureStderr: True
+*ignoreUninstalledPrograms: False
+*authWarningSlack: 20
+
+*textMode: file
+*textLiteral: XScreenSaver
+*textFile: @DEFAULT_TEXT_FILE@
+*textProgram: fortune
+*textURL: https://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss
+
+*overlayTextForeground: #FFFF00
+*overlayTextBackground: #000000
+*overlayStderr: True
+*font: *-medium-r-*-140-*-m-*
+
+! The default is to use these 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 "Demo" button on the splash screen runs (/bin/sh syntax.)
+*demoCommand: xscreensaver-demo
+
+! This is what the "Prefs" button on the splash screen runs (/bin/sh syntax.)
+*prefsCommand: xscreensaver-demo -prefs
+
+! This is the URL loaded by the "Help" button on the splash screen,
+! and by the "Documentation" menu item in xscreensaver-demo.
+*helpURL: https://www.jwz.org/xscreensaver/man.html
+
+! loadURL -- how the "Help" buttons load the helpURL (/bin/sh syntax.)
+! manualCommand -- how the "Documentation" buttons display man pages.
+!
+! 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)
+!
+@GNOME24@*loadURL: @WITH_BROWSER@ '%s'
+@GNOME24@*manualCommand: gnome-terminal --title '%s manual' \
+@GNOME24@ --command '/bin/sh -c "man %s; read foo"'
+!
+! Gnome 2.2:
+!
+@GNOME22@*loadURL: gnome-url-show '%s'
+@GNOME22@*manualCommand: gnome-terminal --title '%s manual' \
+@GNOME22@ --command '/bin/sh -c "man %s; read foo"'
+!
+! Gnome 1.4:
+!
+! *loadURL: gnome-moz-remote --newwin '%s'
+! *manualCommand: gnome-help-browser 'man:%s'
+!
+! non-Gnome systems:
+!
+@NOGNOME@*loadURL: firefox '%s' || mozilla '%s' || netscape '%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
+! To show the time only:
+! *dateFormat: %I:%M %p
+! For 24 hour time:
+! *dateFormat: %H:%M
+
+
+! 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.)
+! For Gnome: probably "gdmflexiserver -ls". KDE, probably "kdmctl reserve".
+! Or maybe yet another wheel-reinvention, "lxdm -c USER_SWITCH".
+! Oh wait, this wheel just keeps getting better: "dm-tool switch-to-greeter".
+!
+@NEW_LOGIN_COMMAND_P@*newLoginCommand: @NEW_LOGIN_COMMAND@
+
+
+! Turning on "installColormap" on 8-bit systems interacts erratically with
+! certain jurassic window managers. If your screen turns some color other
+! than black, the window manager is buggy, and you need to set this resource
+! to false. Or switch WMs. Or join the 21st century and get a 24-bit
+! graphics card.
+!
+*installColormap: True
+
+
+! This is the list of installed screen saver modes. See "man xscreensaver"
+! for the syntax used here.
+!
+! 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
+! screen savers interactively.
+!
+*programs: \
+ maze -root \n\
+@GL_KLUDGE@ 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_KLUDGE@ GL: jigsaw -root \n\
+ julia -root \n\
+- kaleidescope -root \n\
+@GL_KLUDGE@ GL: moebius -root \n\
+ moire -root \n\
+@GL_KLUDGE@ GL: morph3d -root \n\
+ mountain -root \n\
+ munch -root \n\
+ penrose -root \n\
+@GL_KLUDGE@ GL: pipes -root \n\
+ rd-bomb -root \n\
+@GL_KLUDGE@ GL: rubik -root \n\
+- sierpinski -root \n\
+ slip -root \n\
+@GL_KLUDGE@ GL: sproingies -root \n\
+ starfish -root \n\
+ strange -root \n\
+ swirl -root \n\
+ triangle -root \n\
+ xjack -root \n\
+ xlyap -root \n\
+@GL_KLUDGE@ GL: atlantis -root \n\
+ bsod -root \n\
+@GL_KLUDGE@ GL: bubble3d -root \n\
+@GL_KLUDGE@ GL: cage -root \n\
+- crystal -root \n\
+ cynosure -root \n\
+ discrete -root \n\
+ distort -root \n\
+ epicycle -root \n\
+ flow -root \n\
+@GL_KLUDGE@ GL: glplanet -root \n\
+ interference -root \n\
+ kumppa -root \n\
+@GL_KLUDGE@ GL: lament -root \n\
+ moire2 -root \n\
+@GL_KLUDGE@ GL: sonar -root \n\
+@GL_KLUDGE@ 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\
+@GLE_KLUDGE@ GL: extrusion -root \n\
+- loop -root \n\
+ penetrate -root \n\
+ petri -root \n\
+ phosphor -root \n\
+@GL_KLUDGE@ GL: pulsar -root \n\
+ ripples -root \n\
+ shadebobs -root \n\
+@GL_KLUDGE@ GL: sierpinski3d -root \n\
+ spotlight -root \n\
+ squiral -root \n\
+ wander -root \n\
+- webcollage -root \n\
+ xflame -root \n\
+ xmatrix -root \n\
+@GL_KLUDGE@ GL: gflux -root \n\
+- nerverot -root \n\
+ xrayswarm -root \n\
+ xspirograph -root \n\
+@GL_KLUDGE@ GL: circuit -root \n\
+@GL_KLUDGE@ GL: dangerball -root \n\
+- GL: dnalogo -root \n\
+@GL_KLUDGE@ GL: engine -root \n\
+@GL_KLUDGE@ GL: flipscreen3d -root \n\
+@GL_KLUDGE@ GL: gltext -root \n\
+@GL_KLUDGE@ GL: menger -root \n\
+@GL_KLUDGE@ GL: molecule -root \n\
+ rotzoomer -root \n\
+ speedmine -root \n\
+@GL_KLUDGE@ GL: starwars -root \n\
+@GL_KLUDGE@ GL: stonerview -root \n\
+ vermiculate -root \n\
+ whirlwindwarp -root \n\
+ zoom -root \n\
+ anemone -root \n\
+ apollonian -root \n\
+@GL_KLUDGE@ GL: boxed -root \n\
+@GL_KLUDGE@ GL: cubenetic -root \n\
+@GL_KLUDGE@ GL: endgame -root \n\
+ euler2d -root \n\
+ fluidballs -root \n\
+@GL_KLUDGE@ GL: flurry -root \n\
+- GL: glblur -root \n\
+@GL_KLUDGE@ GL: glsnake -root \n\
+ halftone -root \n\
+@GL_KLUDGE@ GL: juggler3d -root \n\
+@GL_KLUDGE@ GL: lavalite -root \n\
+- polyominoes -root \n\
+@GL_KLUDGE@ GL: queens -root \n\
+- GL: sballs -root \n\
+@GL_KLUDGE@ GL: spheremonics -root \n\
+- thornbird -root \n\
+ twang -root \n\
+- GL: antspotlight -root \n\
+ apple2 -root \n\
+@GL_KLUDGE@ GL: atunnel -root \n\
+ barcode -root \n\
+@GL_KLUDGE@ GL: blinkbox -root \n\
+@GL_KLUDGE@ GL: blocktube -root \n\
+@GL_KLUDGE@ GL: bouncingcow -root \n\
+ cloudlife -root \n\
+@GL_KLUDGE@ GL: cubestorm -root \n\
+ eruption -root \n\
+@GL_KLUDGE@ GL: flipflop -root \n\
+@GL_KLUDGE@ GL: flyingtoasters -root \n\
+ fontglide -root \n\
+@GL_KLUDGE@ GL: gleidescope -root \n\
+@GL_KLUDGE@ GL: glknots -root \n\
+@GL_KLUDGE@ GL: glmatrix -root \n\
+- GL: glslideshow -root \n\
+@GL_KLUDGE@ GL: hypertorus -root \n\
+- GL: jigglypuff -root \n\
+ metaballs -root \n\
+@GL_KLUDGE@ GL: mirrorblob -root \n\
+ piecewise -root \n\
+@GL_KLUDGE@ GL: polytopes -root \n\
+ pong -root \n\
+ popsquares -root \n\
+@GL_KLUDGE@ 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_KLUDGE@ GL: noof -root \n\
+ pacman -root \n\
+@GL_KLUDGE@ GL: pinion -root \n\
+@GL_KLUDGE@ GL: polyhedra -root \n\
+- GL: providence -root \n\
+ substrate -root \n\
+ wormhole -root \n\
+- GL: antmaze -root \n\
+@GL_KLUDGE@ GL: boing -root \n\
+ boxfit -root \n\
+@GL_KLUDGE@ GL: carousel -root \n\
+ celtic -root \n\
+@GL_KLUDGE@ GL: crackberg -root \n\
+@GL_KLUDGE@ GL: cube21 -root \n\
+ fiberlamp -root \n\
+@GL_KLUDGE@ GL: fliptext -root \n\
+@GL_KLUDGE@ GL: glhanoi -root \n\
+@GL_KLUDGE@ GL: tangram -root \n\
+@GL_KLUDGE@ GL: timetunnel -root \n\
+@GL_KLUDGE@ GL: glschool -root \n\
+@GL_KLUDGE@ GL: topblock -root \n\
+@GL_KLUDGE@ GL: cubicgrid -root \n\
+ cwaves -root \n\
+@GL_KLUDGE@ GL: gears -root \n\
+@GL_KLUDGE@ GL: glcells -root \n\
+@GL_KLUDGE@ GL: lockward -root \n\
+ m6502 -root \n\
+@GL_KLUDGE@ GL: moebiusgears -root \n\
+@GL_KLUDGE@ GL: voronoi -root \n\
+@GL_KLUDGE@ GL: hypnowheel -root \n\
+@GL_KLUDGE@ GL: klein -root \n\
+- lcdscrub -root \n\
+@GL_KLUDGE@ GL: photopile -root \n\
+@GL_KLUDGE@ GL: skytentacles -root \n\
+@GL_KLUDGE@ GL: rubikblocks -root \n\
+@GL_KLUDGE@ GL: companioncube -root \n\
+@GL_KLUDGE@ GL: hilbert -root \n\
+@GL_KLUDGE@ GL: tronbit -root \n\
+@GL_KLUDGE@ GL: geodesic -root \n\
+ hexadrop -root \n\
+@GL_KLUDGE@ GL: kaleidocycle -root \n\
+@GL_KLUDGE@ GL: quasicrystal -root \n\
+@GL_KLUDGE@ GL: unknownpleasures -root \n\
+ binaryring -root \n\
+@GL_KLUDGE@ GL: cityflow -root \n\
+@GL_KLUDGE@ GL: geodesicgears -root \n\
+@GL_KLUDGE@ GL: projectiveplane -root \n\
+@GL_KLUDGE@ GL: romanboy -root \n\
+ tessellimage -root \n\
+@GL_KLUDGE@ GL: winduprobot -root \n\
+@GL_KLUDGE@ GL: splitflap -root \n\
+@GL_KLUDGE@ GL: cubestack -root \n\
+@GL_KLUDGE@ GL: cubetwist -root \n\
+@GL_KLUDGE@ GL: discoball -root \n\
+@GL_KLUDGE@ GL: dymaxionmap -root \n\
+@GL_KLUDGE@ GL: energystream -root \n\
+@GL_KLUDGE@ GL: hexstrut -root \n\
+@GL_KLUDGE@ GL: hydrostat -root \n\
+@GL_KLUDGE@ GL: raverhoop -root \n\
+@GL_KLUDGE@ GL: splodesic -root \n\
+@GL_KLUDGE@ GL: unicrud -root \n\
+@GL_KLUDGE@ GL: esper -root \n\
+@GL_KLUDGE@ GL: vigilance -root \n\
+@GL_KLUDGE@ GL: crumbler -root \n\
+ filmleader -root \n\
+ glitchpeg -root \n\
+@GL_KLUDGE@ GL: maze3d -root \n\
+@GL_KLUDGE@ GL: peepers -root \n\
+@GL_KLUDGE@ GL: razzledazzle -root \n\
+ vfeedback -root \n
+
+
+
+!=============================================================================
+!
+! You probably don't want to change anything after this point.
+!
+!=============================================================================
+
+
+XScreenSaver.pointerPollTime: 0:00:05
+XScreenSaver.pointerHysteresis: 10
+XScreenSaver.initialDelay: 0:00:00
+XScreenSaver.windowCreationTimeout: 0:00:30
+XScreenSaver.bourneShell: /bin/sh
+
+
+! Resources for the password and splash-screen dialog boxes of
+! the "xscreensaver" daemon.
+!
+*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-medium-r-*-*-*-140-*-*-*-iso8859-1
+
+
+*Dialog.foreground: #000000
+*Dialog.background: #E6E6E6
+*Dialog.Button.foreground: #000000
+*Dialog.Button.background: #F5F5F5
+!*Dialog.Button.pointBackground: #EAEAEA
+!*Dialog.Button.clickBackground: #C3C3C3
+*Dialog.text.foreground: #000000
+*Dialog.text.background: #FFFFFF
+*passwd.thermometer.foreground: #4464AC
+*passwd.thermometer.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.heading.label: XScreenSaver %s
+*passwd.body.label: This screen is locked.
+*passwd.unlock.label: OK
+*passwd.login.label: New Login
+*passwd.user.label: Username:
+*passwd.thermometer.width: 8
+*passwd.asterisks: True
+*passwd.uname: True
+
+*splash.heading.label: XScreenSaver %s
+*splash.body.label: Copyright \251 1991-2018 by
+*splash.body2.label: Jamie Zawinski <jwz@jwz.org>
+*splash.demo.label: Settings
+*splash.help.label: Help
+
+
+!=============================================================================
+!
+! Pretty names for the hacks that have unusual capitalization.
+!
+!=============================================================================
+
+*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.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.dnalogo.name: DNA Logo
+*hacks.dymaxionmap.name: DymaxionMap
+*hacks.energystream.name: EnergyStream
+*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.gflux.name: GFlux
+*hacks.gleidescope.name: Gleidescope
+*hacks.glforestfire.name: GLForestFire
+*hacks.glitchpeg.name: GlitchPEG
+*hacks.hyperball.name: HyperBall
+*hacks.hypercube.name: HyperCube
+*hacks.ifs.name: IFS
+*hacks.imsmap.name: IMSMap
+*hacks.jigglypuff.name: JigglyPuff
+*hacks.juggler3d.name: Juggler3D
+*hacks.lcdscrub.name: LCDscrub
+*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.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.t3d.name: T3D
+*hacks.testx11.name: TestX11
+*hacks.timetunnel.name: TimeTunnel
+*hacks.topblock.name: TopBlock
+*hacks.tronbit.name: TronBit
+*hacks.unknownpleasures.name:UnknownPleasures
+*hacks.vfeedback.name: VFeedback
+*hacks.vidwhacker.name: VidWhacker
+*hacks.webcollage.name: WebCollage
+*hacks.whirlwindwarp.name: WhirlWindWarp
+*hacks.winduprobot.name: WindupRobot
+*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..cd4976e
--- /dev/null
+++ b/driver/XScreenSaver_ad.h
@@ -0,0 +1,416 @@
+"#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",
+"*timestamp: True",
+"*fade: True",
+"*unfade: False",
+"*fadeSeconds: 0:00:03",
+"*fadeTicks: 20",
+"*splash: True",
+"*splashDuration: 0:00:05",
+"*visualID: default",
+"*captureStderr: 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",
+"*overlayTextForeground: #FFFF00",
+"*overlayTextBackground: #000000",
+"*overlayStderr: True",
+"*font: *-medium-r-*-140-*-m-*",
+"*sgiSaverExtension: True",
+"*xidleExtension: True",
+"*procInterrupts: True",
+"*xinputExtensionDev: False",
+"GetViewPortIsFullOfLies: False",
+"*demoCommand: xscreensaver-demo",
+"*prefsCommand: xscreensaver-demo -prefs",
+"*helpURL: https://www.jwz.org/xscreensaver/man.html",
+"*loadURL: firefox '%s' || mozilla '%s' || netscape '%s'",
+"*manualCommand: xterm -sb -fg black -bg gray75 -T '%s manual' \
+ -e /bin/sh -c 'man \"%s\" ; read foo'",
+"*dateFormat: %d-%b-%y (%a); %I:%M %p",
+"*installColormap: True",
+"*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\
+ 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: maze3d -root \\n\
+ GL: peepers -root \\n\
+ GL: razzledazzle -root \\n\
+ vfeedback -root \\n",
+"XScreenSaver.pointerPollTime: 0:00:05",
+"XScreenSaver.pointerHysteresis: 10",
+"XScreenSaver.initialDelay: 0:00:00",
+"XScreenSaver.windowCreationTimeout: 0:00:30",
+"XScreenSaver.bourneShell: /bin/sh",
+"*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",
+"*passwd.passwdFont: -*-courier-medium-r-*-*-*-140-*-*-*-iso8859-1",
+"*Dialog.foreground: #000000",
+"*Dialog.background: #E6E6E6",
+"*Dialog.Button.foreground: #000000",
+"*Dialog.Button.background: #F5F5F5",
+"*Dialog.text.foreground: #000000",
+"*Dialog.text.background: #FFFFFF",
+"*passwd.thermometer.foreground: #4464AC",
+"*passwd.thermometer.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.heading.label: XScreenSaver %s",
+"*passwd.body.label: This screen is locked.",
+"*passwd.unlock.label: OK",
+"*passwd.login.label: New Login",
+"*passwd.user.label: Username:",
+"*passwd.thermometer.width: 8",
+"*passwd.asterisks: True",
+"*passwd.uname: True",
+"*splash.heading.label: XScreenSaver %s",
+"*splash.body.label: Copyright \\251 1991-2018 by",
+"*splash.body2.label: Jamie Zawinski <jwz@jwz.org>",
+"*splash.demo.label: Settings",
+"*splash.help.label: Help",
+"*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.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.dnalogo.name: DNA Logo",
+"*hacks.dymaxionmap.name: DymaxionMap",
+"*hacks.energystream.name: EnergyStream",
+"*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.gflux.name: GFlux",
+"*hacks.gleidescope.name: Gleidescope",
+"*hacks.glforestfire.name: GLForestFire",
+"*hacks.glitchpeg.name: GlitchPEG",
+"*hacks.hyperball.name: HyperBall",
+"*hacks.hypercube.name: HyperCube",
+"*hacks.ifs.name: IFS",
+"*hacks.imsmap.name: IMSMap",
+"*hacks.jigglypuff.name: JigglyPuff",
+"*hacks.juggler3d.name: Juggler3D",
+"*hacks.lcdscrub.name: LCDscrub",
+"*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.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.t3d.name: T3D",
+"*hacks.testx11.name: TestX11",
+"*hacks.timetunnel.name: TimeTunnel",
+"*hacks.topblock.name: TopBlock",
+"*hacks.tronbit.name: TronBit",
+"*hacks.unknownpleasures.name:UnknownPleasures",
+"*hacks.vfeedback.name: VFeedback",
+"*hacks.vidwhacker.name: VidWhacker",
+"*hacks.webcollage.name: WebCollage",
+"*hacks.whirlwindwarp.name: WhirlWindWarp",
+"*hacks.winduprobot.name: WindupRobot",
+"*hacks.xanalogtv.name: XAnalogTV",
+"*hacks.xrayswarm.name: XRaySwarm",
+"*hacks.documentation.isInstalled: True",
diff --git a/driver/auth.h b/driver/auth.h
new file mode 100644
index 0000000..65e00f3
--- /dev/null
+++ b/driver/auth.h
@@ -0,0 +1,54 @@
+/* auth.h --- Providing authentication mechanisms.
+ *
+ * (c) 2007, Quest Software, Inc. All rights reserved.
+ *
+ * This file is part of XScreenSaver,
+ * Copyright (c) 1993-2004 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 XSS_AUTH_H
+#define XSS_AUTH_H
+
+#include "types.h"
+
+#undef Bool
+#undef True
+#undef False
+#define Bool int
+#define True 1
+#define False 0
+
+struct auth_message {
+ enum {
+ AUTH_MSGTYPE_INFO,
+ AUTH_MSGTYPE_ERROR,
+ AUTH_MSGTYPE_PROMPT_NOECHO,
+ AUTH_MSGTYPE_PROMPT_ECHO
+ } type;
+ const char *msg;
+};
+
+struct auth_response {
+ char *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);
+
+void
+auth_finished_cb (saver_info *si);
+
+#endif
diff --git a/driver/compile_axp.com b/driver/compile_axp.com
new file mode 100644
index 0000000..d6ed0e8
--- /dev/null
+++ b/driver/compile_axp.com
@@ -0,0 +1,15 @@
+$ CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H)/INCL=([],[-],[-.UTILS]) DEMO.C
+$ CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H)/INCL=([],[-],[-.UTILS]) DIALOGS-XM.C
+$ CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H)/INCL=([],[-],[-.UTILS]) LOCK.C
+$ CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H)/INCL=([],[-],[-.UTILS]) PASSWD.C
+$ CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H)/INCL=([],[-],[-.UTILS]) STDERR.C
+$ CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H,NO_SETUID)/INCL=([],[-],[-.UTILS]) SUBPROCS.C
+$ CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H)/INCL=([],[-],[-.UTILS]) TIMERS.C
+$ CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H)/INCL=([],[-],[-.UTILS]) WINDOWS.C
+$ CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H)/INCL=([],[-],[-.UTILS]) XSCREENSAVER-COMMAND.C
+$ CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H,NO_SETUID)/INCL=([],[-],[-.UTILS]) XSCREENSAVER.C
+$ CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H)/INCL=([],[-],[-.UTILS]) XSET.C
+$ CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H)/INCL=([],[-],[-.UTILS]) VMS-GETPWNAM.C
+$!!! CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H)/INCL=([],[-],[-.UTILS]) GETPWUID.C
+$ CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H)/INCL=([],[-],[-.UTILS]) VMS-HPWD.C
+$ CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H)/INCL=([],[-],[-.UTILS]) VMS-VALIDATE.C
diff --git a/driver/compile_decc.com b/driver/compile_decc.com
new file mode 100644
index 0000000..d6ed0e8
--- /dev/null
+++ b/driver/compile_decc.com
@@ -0,0 +1,15 @@
+$ CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H)/INCL=([],[-],[-.UTILS]) DEMO.C
+$ CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H)/INCL=([],[-],[-.UTILS]) DIALOGS-XM.C
+$ CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H)/INCL=([],[-],[-.UTILS]) LOCK.C
+$ CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H)/INCL=([],[-],[-.UTILS]) PASSWD.C
+$ CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H)/INCL=([],[-],[-.UTILS]) STDERR.C
+$ CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H,NO_SETUID)/INCL=([],[-],[-.UTILS]) SUBPROCS.C
+$ CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H)/INCL=([],[-],[-.UTILS]) TIMERS.C
+$ CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H)/INCL=([],[-],[-.UTILS]) WINDOWS.C
+$ CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H)/INCL=([],[-],[-.UTILS]) XSCREENSAVER-COMMAND.C
+$ CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H,NO_SETUID)/INCL=([],[-],[-.UTILS]) XSCREENSAVER.C
+$ CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H)/INCL=([],[-],[-.UTILS]) XSET.C
+$ CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H)/INCL=([],[-],[-.UTILS]) VMS-GETPWNAM.C
+$!!! CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H)/INCL=([],[-],[-.UTILS]) GETPWUID.C
+$ CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H)/INCL=([],[-],[-.UTILS]) VMS-HPWD.C
+$ CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H)/INCL=([],[-],[-.UTILS]) VMS-VALIDATE.C
diff --git a/driver/demo-Gtk-conf.c b/driver/demo-Gtk-conf.c
new file mode 100644
index 0000000..bac6ecc
--- /dev/null
+++ b/driver/demo-Gtk-conf.c
@@ -0,0 +1,1998 @@
+/* demo-Gtk-conf.c --- implements the dynamic configuration dialogs.
+ * xscreensaver, Copyright (c) 2001-2014 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
+
+#if defined(HAVE_GTK) && defined(HAVE_XML) /* whole file */
+
+#include <xscreensaver-intl.h>
+
+#include <stdlib.h>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+/*
+ * Both of these workarounds can be removed when support for ancient
+ * libxml versions is dropped. versions 1.8.11 and 2.3.4 provide the
+ * correct fixes.
+ */
+
+/*
+ * Older libxml polluted the global headerspace, while libxml2 fixed
+ * this. To support both old and recent libxmls, we have this
+ * workaround.
+ */
+#ifdef HAVE_OLD_XML_HEADERS
+# include <parser.h>
+#else /* ! HAVE_OLD_XML_HEADERS */
+# include <libxml/parser.h>
+#endif /* HAVE_OLD_XML_HEADERS */
+
+/*
+ * handle non-native spelling mistakes in earlier versions and provide
+ * the source-compat fix for this that may not be in older versions.
+ */
+#ifndef xmlChildrenNode
+# if LIBXML_VERSION >= 20000
+# define xmlChildrenNode children
+# define xmlRootNode children
+# else
+# define xmlChildrenNode childs
+# define xmlRootNode root
+# endif /* LIBXML_VERSION */
+#endif /* xmlChildrenNode */
+
+#include <gtk/gtk.h>
+
+#include "demo-Gtk-conf.h"
+
+/* Deal with deprecation of direct access to struct fields on the way to GTK3
+ See http://live.gnome.org/GnomeGoals/UseGseal
+ */
+#if GTK_CHECK_VERSION(2,14,0)
+# define GET_PARENT(w) gtk_widget_get_parent (w)
+# define GET_ADJ_VALUE(a) gtk_adjustment_get_value (a)
+# define GET_ADJ_UPPER(a) gtk_adjustment_get_upper (a)
+# define GET_ADJ_LOWER(a) gtk_adjustment_get_lower (a)
+#else
+# define GET_PARENT(w) ((w)->parent)
+# define GET_ADJ_VALUE(a) ((a)->value)
+# define GET_ADJ_UPPER(a) ((a)->upper)
+# define GET_ADJ_LOWER(a) ((a)->lower)
+#endif
+
+
+extern const char *blurb (void);
+
+
+const char *hack_configuration_path = HACK_CONFIGURATION_PATH;
+
+static gboolean debug_p = FALSE;
+
+
+#define MIN_SLIDER_WIDTH 150
+#define MIN_SPINBUTTON_WIDTH 48
+#define MIN_LABEL_WIDTH 70
+
+
+typedef enum {
+ COMMAND,
+ FAKE,
+ DESCRIPTION,
+ FAKEPREVIEW,
+ STRING,
+ FILENAME,
+ SLIDER,
+ SPINBUTTON,
+ BOOLEAN,
+ SELECT,
+ SELECT_OPTION
+} parameter_type;
+
+
+typedef struct {
+
+ parameter_type type;
+
+ xmlChar *id; /* widget name */
+ xmlChar *label; /* heading label, or null */
+
+ /* command, fake, description, fakepreview, string, file
+ */
+ xmlChar *string; /* file name, description, whatever. */
+
+ /* slider, spinbutton
+ */
+ xmlChar *low_label; /* label for the left side */
+ xmlChar *high_label; /* label for the right side */
+ float low; /* minimum value */
+ float high; /* maximum value */
+ float value; /* default value */
+ gboolean integer_p; /* whether the range is integral, or real */
+ xmlChar *arg; /* command-line option to set (substitute "%") */
+ gboolean invert_p; /* whether to flip the value and pretend the
+ range goes from hi-low instead of low-hi. */
+
+ /* boolean, select-option
+ */
+ xmlChar *arg_set; /* command-line option to set for "yes", or null */
+ xmlChar *arg_unset; /* command-line option to set for "no", or null */
+
+ /* select
+ */
+ GList *options;
+
+ /* select_option
+ */
+ GtkWidget *widget;
+
+} parameter;
+
+
+static parameter *make_select_option (const char *file, xmlNodePtr);
+static void make_parameter_widget (const char *filename,
+ parameter *, GtkWidget *);
+static void browse_button_cb (GtkButton *button, gpointer user_data);
+
+
+/* Frees the parameter object and all strings and sub-parameters.
+ Does not destroy the widget, if any.
+ */
+static void
+free_parameter (parameter *p)
+{
+ GList *rest;
+ if (p->id) free (p->id);
+ if (p->label) free (p->label);
+ if (p->string) free (p->string);
+ if (p->low_label) free (p->low_label);
+ if (p->high_label) free (p->high_label);
+ if (p->arg) free (p->arg);
+ if (p->arg_set) free (p->arg_set);
+ if (p->arg_unset) free (p->arg_unset);
+
+ for (rest = p->options; rest; rest = rest->next)
+ if (rest->data)
+ free_parameter ((parameter *) rest->data);
+
+ memset (p, ~0, sizeof(*p));
+ free (p);
+}
+
+
+/* Debugging: dumps out a `parameter' structure.
+ */
+#if 0
+void
+describe_parameter (FILE *out, parameter *p)
+{
+ fprintf (out, "<");
+ switch (p->type)
+ {
+ case COMMAND: fprintf (out, "command"); break;
+ case FAKE: fprintf (out, "fake"); break;
+ case DESCRIPTION: fprintf (out, "_description"); break;
+ case FAKEPREVIEW: fprintf (out, "fakepreview"); break;
+ case STRING: fprintf (out, "string"); break;
+ case FILENAME: fprintf (out, "filename"); break;
+ case SLIDER: fprintf (out, "number type=\"slider\""); break;
+ case SPINBUTTON: fprintf (out, "number type=\"spinbutton\""); break;
+ case BOOLEAN: fprintf (out, "boolean"); break;
+ case SELECT: fprintf (out, "select"); break;
+ default: abort(); break;
+ }
+ if (p->id) fprintf (out, " id=\"%s\"", p->id);
+ if (p->label) fprintf (out, " _label=\"%s\"", p->label);
+ if (p->string && p->type != DESCRIPTION)
+ fprintf (out, " string=\"%s\"", p->string);
+ if (p->low_label) fprintf (out, " _low-label=\"%s\"", p->low_label);
+ if (p->high_label) fprintf (out, " _high-label=\"%s\"", p->high_label);
+ if (p->low) fprintf (out, " low=\"%.2f\"", p->low);
+ if (p->high) fprintf (out, " high=\"%.2f\"", p->high);
+ if (p->value) fprintf (out, " default=\"%.2f\"", p->value);
+ if (p->arg) fprintf (out, " arg=\"%s\"", p->arg);
+ if (p->invert_p) fprintf (out, " convert=\"invert\"");
+ if (p->arg_set) fprintf (out, " arg-set=\"%s\"", p->arg_set);
+ if (p->arg_unset) fprintf (out, " arg-unset=\"%s\"", p->arg_unset);
+ fprintf (out, ">\n");
+
+ if (p->type == SELECT)
+ {
+ GList *opt;
+ for (opt = p->options; opt; opt = opt->next)
+ {
+ parameter *o = (parameter *) opt->data;
+ if (o->type != SELECT_OPTION) abort();
+ fprintf (out, " <option");
+ if (o->id) fprintf (out, " id=\"%s\"", o->id);
+ if (o->label) fprintf (out, " _label=\"%s\"", o->label);
+ if (o->arg_set) fprintf (out, " arg-set=\"%s\"", o->arg_set);
+ if (o->arg_unset) fprintf (out, " arg-unset=\"%s\"", o->arg_unset);
+ fprintf (out, ">\n");
+ }
+ fprintf (out, "</select>\n");
+ }
+ else if (p->type == DESCRIPTION)
+ {
+ if (p->string)
+ fprintf (out, " %s\n", p->string);
+ fprintf (out, "</_description>\n");
+ }
+}
+#endif /* 0 */
+
+
+/* Like xmlGetProp() but parses a float out of the string.
+ If the number was expressed as a float and not an integer
+ (that is, the string contained a decimal point) then
+ `floatp' is set to TRUE. Otherwise, it is unchanged.
+ */
+static float
+xml_get_float (xmlNodePtr node, const xmlChar *name, gboolean *floatpP)
+{
+ const char *s = (char *) xmlGetProp (node, name);
+ float f;
+ char c;
+ if (!s || 1 != sscanf (s, "%f %c", &f, &c))
+ return 0;
+ else
+ {
+ if (strchr (s, '.')) *floatpP = TRUE;
+ return f;
+ }
+}
+
+
+static void sanity_check_parameter (const char *filename,
+ const xmlChar *node_name,
+ parameter *p);
+static void sanity_check_text_node (const char *filename,
+ const xmlNodePtr node);
+static void sanity_check_menu_options (const char *filename,
+ const xmlChar *node_name,
+ parameter *p);
+
+/* Allocates and returns a new `parameter' object based on the
+ properties in the given XML node. Returns 0 if there's nothing
+ to create (comment, or unknown tag.)
+ */
+static parameter *
+make_parameter (const char *filename, xmlNodePtr node)
+{
+ parameter *p;
+ const char *name = (char *) node->name;
+ const char *convert;
+ gboolean floatp = FALSE;
+
+ if (node->type == XML_COMMENT_NODE)
+ return 0;
+
+ p = calloc (1, sizeof(*p));
+
+ if (!name) abort();
+ else if (!strcmp (name, "command")) p->type = COMMAND;
+ else if (!strcmp (name, "fullcommand")) p->type = COMMAND;
+ else if (!strcmp (name, "_description")) p->type = DESCRIPTION;
+ else if (!strcmp (name, "fakepreview")) p->type = FAKEPREVIEW;
+ else if (!strcmp (name, "fake")) p->type = FAKE;
+ else if (!strcmp (name, "boolean")) p->type = BOOLEAN;
+ else if (!strcmp (name, "string")) p->type = STRING;
+ else if (!strcmp (name, "file")) p->type = FILENAME;
+ else if (!strcmp (name, "number")) p->type = SPINBUTTON;
+ else if (!strcmp (name, "select")) p->type = SELECT;
+
+ else if (!strcmp (name, "xscreensaver-text") || /* ignored in X11; */
+ !strcmp (name, "xscreensaver-image") || /* used in Cocoa. */
+ !strcmp (name, "xscreensaver-updater") ||
+ !strcmp (name, "video"))
+ {
+ free (p);
+ return 0;
+ }
+ else if (node->type == XML_TEXT_NODE)
+ {
+ sanity_check_text_node (filename, node);
+ free (p);
+ return 0;
+ }
+ else
+ {
+ if (debug_p)
+ fprintf (stderr, "%s: WARNING: %s: unknown tag: \"%s\"\n",
+ blurb(), filename, name);
+ free (p);
+ return 0;
+ }
+
+ if (p->type == SPINBUTTON)
+ {
+ const char *type = (char *) xmlGetProp (node, (xmlChar *) "type");
+ if (!type || !strcmp (type, "spinbutton")) p->type = SPINBUTTON;
+ else if (!strcmp (type, "slider")) p->type = SLIDER;
+ else
+ {
+ if (debug_p)
+ fprintf (stderr, "%s: WARNING: %s: unknown %s type: \"%s\"\n",
+ blurb(), filename, name, type);
+ free (p);
+ return 0;
+ }
+ }
+ else if (p->type == DESCRIPTION)
+ {
+ if (node->xmlChildrenNode &&
+ node->xmlChildrenNode->type == XML_TEXT_NODE &&
+ !node->xmlChildrenNode->next)
+ p->string = (xmlChar *)
+ strdup ((char *) node->xmlChildrenNode->content);
+ }
+
+ p->id = xmlGetProp (node, (xmlChar *) "id");
+ p->label = xmlGetProp (node, (xmlChar *) "_label");
+ p->low_label = xmlGetProp (node, (xmlChar *) "_low-label");
+ p->high_label = xmlGetProp (node, (xmlChar *) "_high-label");
+ p->low = xml_get_float (node, (xmlChar *) "low", &floatp);
+ p->high = xml_get_float (node, (xmlChar *) "high", &floatp);
+ p->value = xml_get_float (node, (xmlChar *) "default", &floatp);
+ p->integer_p = !floatp;
+ convert = (char *) xmlGetProp (node, (xmlChar *) "convert");
+ p->invert_p = (convert && !strcmp (convert, "invert"));
+ p->arg = xmlGetProp (node, (xmlChar *) "arg");
+ p->arg_set = xmlGetProp (node, (xmlChar *) "arg-set");
+ p->arg_unset = xmlGetProp (node, (xmlChar *) "arg-unset");
+
+ /* Check for missing decimal point */
+ if (debug_p &&
+ p->integer_p &&
+ (p->high != p->low) &&
+ (p->high - p->low) <= 1)
+ fprintf (stderr,
+ "%s: WARNING: %s: %s: range [%.1f, %.1f] shouldn't be integral!\n",
+ blurb(), filename, p->id,
+ p->low, p->high);
+
+ if (p->type == SELECT)
+ {
+ xmlNodePtr kids;
+ for (kids = node->xmlChildrenNode; kids; kids = kids->next)
+ {
+ parameter *s = make_select_option (filename, kids);
+ if (s)
+ p->options = g_list_append (p->options, s);
+ }
+ }
+
+ sanity_check_parameter (filename, (const xmlChar *) name, p);
+
+ return p;
+}
+
+
+/* Allocates and returns a new SELECT_OPTION `parameter' object based
+ on the properties in the given XML node. Returns 0 if there's nothing
+ to create (comment, or unknown tag.)
+ */
+static parameter *
+make_select_option (const char *filename, xmlNodePtr node)
+{
+ if (node->type == XML_COMMENT_NODE)
+ return 0;
+ else if (node->type == XML_TEXT_NODE)
+ {
+ sanity_check_text_node (filename, node);
+ return 0;
+ }
+ else if (node->type != XML_ELEMENT_NODE)
+ {
+ if (debug_p)
+ fprintf (stderr,
+ "%s: WARNING: %s: %s: unexpected child tag type %d\n",
+ blurb(), filename, node->name, (int)node->type);
+ return 0;
+ }
+ else if (strcmp ((char *) node->name, "option"))
+ {
+ if (debug_p)
+ fprintf (stderr,
+ "%s: WARNING: %s: %s: child not an option tag: \"%s\"\n",
+ blurb(), filename, node->name, node->name);
+ return 0;
+ }
+ else
+ {
+ parameter *s = calloc (1, sizeof(*s));
+
+ s->type = SELECT_OPTION;
+ s->id = xmlGetProp (node, (xmlChar *) "id");
+ s->label = xmlGetProp (node, (xmlChar *) "_label");
+ s->arg_set = xmlGetProp (node, (xmlChar *) "arg-set");
+ s->arg_unset = xmlGetProp (node, (xmlChar *) "arg-unset");
+
+ sanity_check_parameter (filename, node->name, s);
+ return s;
+ }
+}
+
+
+/* Rudimentary check to make sure someone hasn't typed "arg-set="
+ when they should have typed "arg=", etc.
+ */
+static void
+sanity_check_parameter (const char *filename, const xmlChar *node_name,
+ parameter *p)
+{
+ struct {
+ gboolean id;
+ gboolean label;
+ gboolean string;
+ gboolean low_label;
+ gboolean high_label;
+ gboolean low;
+ gboolean high;
+ gboolean value;
+ gboolean arg;
+ gboolean invert_p;
+ gboolean arg_set;
+ gboolean arg_unset;
+ } allowed, require;
+
+ memset (&allowed, 0, sizeof (allowed));
+ memset (&require, 0, sizeof (require));
+
+ switch (p->type)
+ {
+ case COMMAND:
+ allowed.arg = TRUE;
+ require.arg = TRUE;
+ break;
+ case FAKE:
+ break;
+ case DESCRIPTION:
+ allowed.string = TRUE;
+ break;
+ case FAKEPREVIEW:
+ break;
+ case STRING:
+ allowed.id = TRUE;
+ require.id = TRUE;
+ allowed.label = TRUE;
+ require.label = TRUE;
+ allowed.arg = TRUE;
+ require.arg = TRUE;
+ break;
+ case FILENAME:
+ allowed.id = TRUE;
+ require.id = TRUE;
+ allowed.label = TRUE;
+ allowed.arg = TRUE;
+ require.arg = TRUE;
+ break;
+ case SLIDER:
+ allowed.id = TRUE;
+ require.id = TRUE;
+ allowed.label = TRUE;
+ allowed.low_label = TRUE;
+ allowed.high_label = TRUE;
+ allowed.arg = TRUE;
+ require.arg = TRUE;
+ allowed.low = TRUE;
+ /* require.low = TRUE; -- may be 0 */
+ allowed.high = TRUE;
+ /* require.high = TRUE; -- may be 0 */
+ allowed.value = TRUE;
+ /* require.value = TRUE; -- may be 0 */
+ allowed.invert_p = TRUE;
+ break;
+ case SPINBUTTON:
+ allowed.id = TRUE;
+ require.id = TRUE;
+ allowed.label = TRUE;
+ allowed.arg = TRUE;
+ require.arg = TRUE;
+ allowed.low = TRUE;
+ /* require.low = TRUE; -- may be 0 */
+ allowed.high = TRUE;
+ /* require.high = TRUE; -- may be 0 */
+ allowed.value = TRUE;
+ /* require.value = TRUE; -- may be 0 */
+ allowed.invert_p = TRUE;
+ break;
+ case BOOLEAN:
+ allowed.id = TRUE;
+ require.id = TRUE;
+ allowed.label = TRUE;
+ allowed.arg_set = TRUE;
+ allowed.arg_unset = TRUE;
+ break;
+ case SELECT:
+ allowed.id = TRUE;
+ require.id = TRUE;
+ break;
+ case SELECT_OPTION:
+ allowed.id = TRUE;
+ allowed.label = TRUE;
+ require.label = TRUE;
+ allowed.arg_set = TRUE;
+ break;
+ default:
+ abort();
+ break;
+ }
+
+# define WARN(STR) \
+ fprintf (stderr, "%s: %s: " STR " in <%s%s id=\"%s\">\n", \
+ blurb(), filename, node_name, \
+ (!strcmp((char *) node_name, "number") \
+ ? (p->type == SPINBUTTON ? " type=spinbutton" : " type=slider")\
+ : ""), \
+ (p->id ? (char *) p->id : ""))
+# define CHECK(SLOT,NAME) \
+ if (p->SLOT && !allowed.SLOT) \
+ WARN ("\"" NAME "\" is not a valid option"); \
+ if (!p->SLOT && require.SLOT) \
+ WARN ("\"" NAME "\" is required")
+
+ CHECK (id, "id");
+ CHECK (label, "_label");
+ CHECK (string, "(body text)");
+ CHECK (low_label, "_low-label");
+ CHECK (high_label, "_high-label");
+ CHECK (low, "low");
+ CHECK (high, "high");
+ CHECK (value, "default");
+ CHECK (arg, "arg");
+ CHECK (invert_p, "convert");
+ CHECK (arg_set, "arg-set");
+ CHECK (arg_unset, "arg-unset");
+# undef CHECK
+# undef WARN
+
+ if (p->type == SELECT)
+ sanity_check_menu_options (filename, node_name, p);
+}
+
+
+static void
+sanity_check_menu_options (const char *filename, const xmlChar *node_name,
+ parameter *p)
+{
+ GList *opts;
+ int noptions = 0;
+ int nulls = 0;
+ char *prefix = 0;
+
+/* fprintf (stderr, "\n## %s\n", p->id);*/
+ for (opts = p->options; opts; opts = opts->next)
+ {
+ parameter *s = (parameter *) opts->data;
+ if (!s->arg_set) nulls++;
+ noptions++;
+
+ if (s->arg_set)
+ {
+ char *a = strdup ((char *) s->arg_set);
+ char *spc = strchr (a, ' ');
+ if (spc) *spc = 0;
+ if (prefix)
+ {
+ if (strcmp (a, prefix))
+ fprintf (stderr,
+ "%s: %s: both \"%s\" and \"%s\" used in <select id=\"%s\">\n",
+ blurb(), filename, prefix, a, p->id);
+ free (prefix);
+ }
+ prefix = a;
+ }
+
+/* fprintf (stderr, "\n %s\n", s->arg_set);*/
+ }
+
+ if (prefix) free (prefix);
+ prefix = 0;
+ if (nulls > 1)
+ fprintf (stderr,
+ "%s: %s: more than one menu with no arg-set in <select id=\"%s\">\n",
+ blurb(), filename, p->id);
+}
+
+
+/* "text" nodes show up for all the non-tag text in the file, including
+ all the newlines between tags. Warn if there is text there that
+ is not whitespace.
+ */
+static void
+sanity_check_text_node (const char *filename, const xmlNodePtr node)
+{
+ const char *body = (const char *) node->content;
+ if (node->type != XML_TEXT_NODE) abort();
+ while (isspace (*body)) body++;
+ if (*body)
+ fprintf (stderr, "%s: WARNING: %s: random text present: \"%s\"\n",
+ blurb(), filename, body);
+}
+
+
+/* Returns a list of strings, every switch mentioned in the parameters.
+ The strings must be freed.
+ */
+static GList *
+get_all_switches (const char *filename, GList *parms)
+{
+ GList *switches = 0;
+ GList *p;
+ for (p = parms; p; p = p->next)
+ {
+ parameter *pp = (parameter *) p->data;
+
+ if (pp->type == SELECT)
+ {
+ GList *list2 = get_all_switches (filename, pp->options);
+ switches = g_list_concat (switches, list2);
+ }
+ if (pp->arg && *pp->arg)
+ switches = g_list_append (switches, strdup ((char *) pp->arg));
+ if (pp->arg_set && *pp->arg_set)
+ switches = g_list_append (switches, strdup ((char *) pp->arg_set));
+ if (pp->arg_unset && *pp->arg_unset)
+ switches = g_list_append (switches, strdup ((char *) pp->arg_unset));
+ }
+ return switches;
+}
+
+
+/* Ensures that no switch is mentioned more than once.
+ */
+static void
+sanity_check_parameters (const char *filename, GList *parms)
+{
+ GList *list = get_all_switches (filename, parms);
+ GList *p;
+ for (p = list; p; p = p->next)
+ {
+ char *sw = (char *) p->data;
+ GList *p2;
+
+ if (*sw != '-' && *sw != '+')
+ fprintf (stderr, "%s: %s: switch does not begin with hyphen \"%s\"\n",
+ blurb(), filename, sw);
+
+ for (p2 = p->next; p2; p2 = p2->next)
+ {
+ const char *sw2 = (const char *) p2->data;
+ if (!strcmp (sw, sw2))
+ fprintf (stderr, "%s: %s: duplicate switch \"%s\"\n",
+ blurb(), filename, sw);
+ }
+
+ free (sw);
+ }
+ g_list_free (list);
+}
+
+
+/* Helper for make_parameters()
+ */
+static GList *
+make_parameters_1 (const char *filename, xmlNodePtr node, GtkWidget *parent)
+{
+ GList *list = 0;
+
+ for (; node; node = node->next)
+ {
+ const char *name = (char *) node->name;
+ if (!strcmp (name, "hgroup") ||
+ !strcmp (name, "vgroup"))
+ {
+ GtkWidget *box = (*name == 'h'
+ ? gtk_hbox_new (FALSE, 0)
+ : gtk_vbox_new (FALSE, 0));
+ GList *list2;
+ gtk_widget_show (box);
+ gtk_box_pack_start (GTK_BOX (parent), box, FALSE, FALSE, 0);
+
+ list2 = make_parameters_1 (filename, node->xmlChildrenNode, box);
+ if (list2)
+ list = g_list_concat (list, list2);
+ }
+ else
+ {
+ parameter *p = make_parameter (filename, node);
+ if (p)
+ {
+ list = g_list_append (list, p);
+ make_parameter_widget (filename, p, parent);
+ }
+ }
+ }
+ return list;
+}
+
+
+/* Calls make_parameter() and make_parameter_widget() on each relevant
+ tag in the XML tree. Also handles the "hgroup" and "vgroup" flags.
+ Returns a GList of `parameter' objects.
+ */
+static GList *
+make_parameters (const char *filename, xmlNodePtr node, GtkWidget *parent)
+{
+ for (; node; node = node->next)
+ {
+ if (node->type == XML_ELEMENT_NODE &&
+ !strcmp ((char *) node->name, "screensaver"))
+ return make_parameters_1 (filename, node->xmlChildrenNode, parent);
+ }
+ return 0;
+}
+
+
+static gfloat
+invert_range (gfloat low, gfloat high, gfloat value)
+{
+ gfloat range = high-low;
+ gfloat off = value-low;
+ return (low + (range - off));
+}
+
+
+static GtkAdjustment *
+make_adjustment (const char *filename, parameter *p)
+{
+ float range = (p->high - p->low);
+ float value = (p->invert_p
+ ? invert_range (p->low, p->high, p->value)
+ : p->value);
+ gfloat si = (p->high - p->low) / 100;
+ gfloat pi = (p->high - p->low) / 10;
+ gfloat page_size = ((p->type == SLIDER) ? 1 : 0);
+
+ if (p->value < p->low || p->value > p->high)
+ {
+ if (debug_p && p->integer_p)
+ fprintf (stderr, "%s: WARNING: %s: %d is not in range [%d, %d]\n",
+ blurb(), filename,
+ (int) p->value, (int) p->low, (int) p->high);
+ else if (debug_p)
+ fprintf (stderr,
+ "%s: WARNING: %s: %.2f is not in range [%.2f, %.2f]\n",
+ blurb(), filename, p->value, p->low, p->high);
+ value = (value < p->low ? p->low : p->high);
+ }
+#if 0
+ else if (debug_p && p->value < 1000 && p->high >= 10000)
+ {
+ if (p->integer_p)
+ fprintf (stderr,
+ "%s: WARNING: %s: %d is suspicious for range [%d, %d]\n",
+ blurb(), filename,
+ (int) p->value, (int) p->low, (int) p->high);
+ else
+ fprintf (stderr,
+ "%s: WARNING: %s: %.2f is suspicious for range [%.2f, %.2f]\n",
+ blurb(), filename, p->value, p->low, p->high);
+ }
+#endif /* 0 */
+
+ si = (int) (si + 0.5);
+ pi = (int) (pi + 0.5);
+ if (si < 1) si = 1;
+ if (pi < 1) pi = 1;
+
+ if (range <= 500) si = 1;
+
+ return GTK_ADJUSTMENT (gtk_adjustment_new (value,
+ p->low,
+ p->high + page_size,
+ si, pi, page_size));
+}
+
+
+
+static void
+set_widget_min_width (GtkWidget *w, int width)
+{
+ GtkRequisition req;
+ gtk_widget_size_request (GTK_WIDGET (w), &req);
+ if (req.width < width)
+ gtk_widget_set_size_request (GTK_WIDGET (w), width, -1);
+}
+
+
+/* If we're inside a vbox, we need to put an hbox in it, or labels appear
+ on top instead of to the left, and things stretch to the full width of
+ the window.
+ */
+static GtkWidget *
+insert_fake_hbox (GtkWidget *parent)
+{
+ if (GTK_IS_VBOX (parent))
+ {
+ GtkWidget *hbox = gtk_hbox_new (FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (parent), hbox, FALSE, FALSE, 4);
+ gtk_widget_show (hbox);
+ return hbox;
+ }
+ return parent;
+}
+
+
+static void
+link_atk_label_to_widget(GtkWidget *label, GtkWidget *widget)
+{
+ AtkObject *atk_label = gtk_widget_get_accessible (label);
+ AtkObject *atk_widget = gtk_widget_get_accessible (widget);
+
+ atk_object_add_relationship (atk_label, ATK_RELATION_LABEL_FOR,
+ atk_widget);
+ atk_object_add_relationship (atk_widget, ATK_RELATION_LABELLED_BY,
+ atk_label);
+}
+
+/* Given a `parameter' struct, allocates an appropriate GtkWidget for it,
+ and stores it in `p->widget'.
+ `parent' must be a GtkBox.
+ */
+static void
+make_parameter_widget (const char *filename, parameter *p, GtkWidget *parent)
+{
+ const char *label = (char *) p->label;
+ if (p->widget) return;
+
+ switch (p->type)
+ {
+ case STRING:
+ {
+ GtkWidget *entry = gtk_entry_new ();
+ parent = insert_fake_hbox (parent);
+ if (label)
+ {
+ GtkWidget *w = gtk_label_new (_(label));
+ link_atk_label_to_widget (w, entry);
+ gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_RIGHT);
+ gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
+ set_widget_min_width (GTK_WIDGET (w), MIN_LABEL_WIDTH);
+ gtk_widget_show (w);
+ gtk_box_pack_start (GTK_BOX (parent), w, FALSE, FALSE, 4);
+ }
+
+ p->widget = entry;
+ if (p->string)
+ gtk_entry_set_text (GTK_ENTRY (p->widget), (char *) p->string);
+ gtk_box_pack_start (GTK_BOX (parent), p->widget, FALSE, FALSE, 4);
+ break;
+ }
+ case FILENAME:
+ {
+ GtkWidget *L = gtk_label_new (label ? _(label) : "");
+ GtkWidget *entry = gtk_entry_new ();
+ GtkWidget *button = gtk_button_new_with_label (_("Browse..."));
+ link_atk_label_to_widget (L, entry);
+ gtk_widget_show (entry);
+ gtk_widget_show (button);
+ p->widget = entry;
+
+ gtk_signal_connect (GTK_OBJECT (button),
+ "clicked", GTK_SIGNAL_FUNC (browse_button_cb),
+ (gpointer) entry);
+
+ gtk_label_set_justify (GTK_LABEL (L), GTK_JUSTIFY_RIGHT);
+ gtk_misc_set_alignment (GTK_MISC (L), 1.0, 0.5);
+ set_widget_min_width (GTK_WIDGET (L), MIN_LABEL_WIDTH);
+ gtk_widget_show (L);
+
+ if (p->string)
+ gtk_entry_set_text (GTK_ENTRY (entry), (char *) p->string);
+
+ parent = insert_fake_hbox (parent);
+ gtk_box_pack_start (GTK_BOX (parent), L, FALSE, FALSE, 4);
+ gtk_box_pack_start (GTK_BOX (parent), entry, TRUE, TRUE, 4);
+ gtk_box_pack_start (GTK_BOX (parent), button, FALSE, FALSE, 4);
+ break;
+ }
+ case SLIDER:
+ {
+ GtkAdjustment *adj = make_adjustment (filename, p);
+ GtkWidget *scale = gtk_hscale_new (adj);
+ GtkWidget *labelw = 0;
+
+ if (label)
+ {
+ labelw = gtk_label_new (_(label));
+ link_atk_label_to_widget (labelw, scale);
+ gtk_label_set_justify (GTK_LABEL (labelw), GTK_JUSTIFY_LEFT);
+ gtk_misc_set_alignment (GTK_MISC (labelw), 0.0, 0.5);
+ set_widget_min_width (GTK_WIDGET (labelw), MIN_LABEL_WIDTH);
+ gtk_widget_show (labelw);
+ gtk_box_pack_start (GTK_BOX (parent), labelw, FALSE, FALSE, 2);
+ }
+
+ /* Do this after 'labelw' so that it appears above, not to left. */
+ parent = insert_fake_hbox (parent);
+
+ if (p->low_label)
+ {
+ GtkWidget *w = gtk_label_new (_((char *) p->low_label));
+ link_atk_label_to_widget (w, scale);
+ gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_RIGHT);
+ gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
+ set_widget_min_width (GTK_WIDGET (w), MIN_LABEL_WIDTH);
+ gtk_widget_show (w);
+ gtk_box_pack_start (GTK_BOX (parent), w, FALSE, FALSE, 4);
+ }
+
+ gtk_scale_set_value_pos (GTK_SCALE (scale), GTK_POS_BOTTOM);
+ gtk_scale_set_draw_value (GTK_SCALE (scale), debug_p);
+ gtk_scale_set_digits (GTK_SCALE (scale), (p->integer_p ? 0 : 2));
+ set_widget_min_width (GTK_WIDGET (scale), MIN_SLIDER_WIDTH);
+
+ gtk_box_pack_start (GTK_BOX (parent), scale, FALSE, FALSE, 4);
+
+ gtk_widget_show (scale);
+
+ if (p->high_label)
+ {
+ GtkWidget *w = gtk_label_new (_((char *) p->high_label));
+ link_atk_label_to_widget (w, scale);
+ gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_LEFT);
+ gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5);
+ set_widget_min_width (GTK_WIDGET (w), MIN_LABEL_WIDTH);
+ gtk_widget_show (w);
+ gtk_box_pack_start (GTK_BOX (parent), w, FALSE, FALSE, 4);
+ }
+
+ p->widget = scale;
+ break;
+ }
+ case SPINBUTTON:
+ {
+ GtkAdjustment *adj = make_adjustment (filename, p);
+ GtkWidget *spin = gtk_spin_button_new (adj, 15, 0);
+ gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spin), TRUE);
+ gtk_spin_button_set_snap_to_ticks (GTK_SPIN_BUTTON (spin), TRUE);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin), GET_ADJ_VALUE(adj));
+ set_widget_min_width (GTK_WIDGET (spin), MIN_SPINBUTTON_WIDTH);
+
+ if (label)
+ {
+ GtkWidget *w = gtk_label_new (_(label));
+ link_atk_label_to_widget (w, spin);
+ gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_RIGHT);
+ gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
+ set_widget_min_width (GTK_WIDGET (w), MIN_LABEL_WIDTH);
+ gtk_widget_show (w);
+ parent = insert_fake_hbox (parent);
+ gtk_box_pack_start (GTK_BOX (parent), w, FALSE, FALSE, 4);
+ }
+
+ gtk_widget_show (spin);
+ gtk_box_pack_start (GTK_BOX (parent), spin, FALSE, FALSE, 4);
+
+ p->widget = spin;
+ break;
+ }
+ case BOOLEAN:
+ {
+ p->widget = gtk_check_button_new_with_label (_(label));
+ /* Let these stretch -- doesn't hurt.
+ parent = insert_fake_hbox (parent);
+ */
+ gtk_box_pack_start (GTK_BOX (parent), p->widget, FALSE, FALSE, 4);
+ break;
+ }
+ case SELECT:
+ {
+ GtkWidget *opt = gtk_option_menu_new ();
+ GtkWidget *menu = gtk_menu_new ();
+ GList *opts;
+
+ for (opts = p->options; opts; opts = opts->next)
+ {
+ parameter *s = (parameter *) opts->data;
+ GtkWidget *i = gtk_menu_item_new_with_label (_((char *) s->label));
+ gtk_widget_show (i);
+ gtk_menu_append (GTK_MENU (menu), i);
+ }
+
+ gtk_option_menu_set_menu (GTK_OPTION_MENU (opt), menu);
+ p->widget = opt;
+ parent = insert_fake_hbox (parent);
+ gtk_box_pack_start (GTK_BOX (parent), p->widget, FALSE, FALSE, 4);
+ break;
+ }
+
+ case COMMAND:
+ case FAKE:
+ case DESCRIPTION:
+ case FAKEPREVIEW:
+ break;
+ default:
+ abort();
+ }
+
+ if (p->widget)
+ {
+ gtk_widget_set_name (p->widget, (char *) p->id);
+ gtk_widget_show (p->widget);
+ }
+}
+
+
+/* File selection.
+ Absurdly, there is no GTK file entry widget, only a GNOME one,
+ so in order to avoid depending on GNOME in this code, we have
+ to do it ourselves.
+ */
+
+/* cancel button on GtkFileSelection: user_data unused */
+static void
+file_sel_cancel (GtkWidget *button, gpointer user_data)
+{
+ GtkWidget *dialog = button;
+ while (GET_PARENT (dialog))
+ dialog = GET_PARENT (dialog);
+ gtk_widget_destroy (dialog);
+}
+
+/* ok button on GtkFileSelection: user_data is the corresponding GtkEntry */
+static void
+file_sel_ok (GtkWidget *button, gpointer user_data)
+{
+ GtkWidget *entry = GTK_WIDGET (user_data);
+ GtkWidget *dialog = button;
+ const char *path;
+
+ while (GET_PARENT (dialog))
+ dialog = GET_PARENT (dialog);
+ gtk_widget_hide (dialog);
+
+ path = gtk_file_selection_get_filename (GTK_FILE_SELECTION (dialog));
+ /* apparently one doesn't free `path' */
+
+ gtk_entry_set_text (GTK_ENTRY (entry), path);
+ gtk_entry_set_position (GTK_ENTRY (entry), strlen (path));
+
+ gtk_widget_destroy (dialog);
+}
+
+/* WM close on GtkFileSelection: user_data unused */
+static void
+file_sel_close (GtkWidget *widget, GdkEvent *event, gpointer user_data)
+{
+ file_sel_cancel (widget, user_data);
+}
+
+/* "Browse" button: user_data is the corresponding GtkEntry */
+static void
+browse_button_cb (GtkButton *button, gpointer user_data)
+{
+ GtkWidget *entry = GTK_WIDGET (user_data);
+ const char *text = gtk_entry_get_text (GTK_ENTRY (entry));
+ GtkFileSelection *selector =
+ GTK_FILE_SELECTION (gtk_file_selection_new (_("Select file.")));
+
+ gtk_file_selection_set_filename (selector, text);
+ gtk_signal_connect (GTK_OBJECT (selector->ok_button),
+ "clicked", GTK_SIGNAL_FUNC (file_sel_ok),
+ (gpointer) entry);
+ gtk_signal_connect (GTK_OBJECT (selector->cancel_button),
+ "clicked", GTK_SIGNAL_FUNC (file_sel_cancel),
+ (gpointer) entry);
+ gtk_signal_connect (GTK_OBJECT (selector), "delete_event",
+ GTK_SIGNAL_FUNC (file_sel_close),
+ (gpointer) entry);
+
+ gtk_window_set_modal (GTK_WINDOW (selector), TRUE);
+ gtk_widget_show (GTK_WIDGET (selector));
+}
+
+
+/* Converting to and from command-lines
+ */
+
+
+/* Returns a copy of string that has been quoted according to shell rules:
+ it may have been wrapped in "" and had some characters backslashed; or
+ it may be unchanged.
+ */
+static char *
+shell_quotify (const char *string)
+{
+ char *string2 = (char *) malloc ((strlen (string) * 2) + 10);
+ const char *in;
+ char *out;
+ int need_quotes = 0;
+ int in_length = 0;
+
+ out = string2;
+ *out++ = '"';
+ for (in = string; *in; in++)
+ {
+ in_length++;
+ if (*in == '!' ||
+ *in == '"' ||
+ *in == '$')
+ {
+ need_quotes = 1;
+ *out++ = '\\';
+ *out++ = *in;
+ }
+ else if (*in <= ' ' ||
+ *in >= 127 ||
+ *in == '\'' ||
+ *in == '#' ||
+ *in == '%' ||
+ *in == '&' ||
+ *in == '(' ||
+ *in == ')' ||
+ *in == '*')
+ {
+ need_quotes = 1;
+ *out++ = *in;
+ }
+ else
+ *out++ = *in;
+ }
+ *out++ = '"';
+ *out = 0;
+
+ if (in_length == 0)
+ need_quotes = 1;
+
+ if (need_quotes)
+ return (string2);
+
+ free (string2);
+ return strdup (string);
+}
+
+/* Modify the string in place to remove wrapping double-quotes
+ and interior backslashes.
+ */
+static void
+de_stringify (char *s)
+{
+ char q = s[0];
+ if (q != '\'' && q != '\"' && q != '`')
+ abort();
+ memmove (s, s+1, strlen (s));
+ while (*s && *s != q)
+ {
+ if (*s == '\\')
+ memmove (s, s+1, strlen (s)+1);
+ s++;
+ }
+ if (*s != q) abort();
+ *s = 0;
+}
+
+
+/* Substitutes a shell-quotified version of `value' into `p->arg' at
+ the place where the `%' character appeared.
+ */
+static char *
+format_switch (parameter *p, const char *value)
+{
+ char *fmt = (char *) p->arg;
+ char *v2;
+ char *result, *s;
+ if (!fmt || !value) return 0;
+ v2 = shell_quotify (value);
+ result = (char *) malloc (strlen (fmt) + strlen (v2) + 10);
+ s = result;
+ for (; *fmt; fmt++)
+ if (*fmt != '%')
+ *s++ = *fmt;
+ else
+ {
+ strcpy (s, v2);
+ s += strlen (s);
+ }
+ *s = 0;
+
+ free (v2);
+ return result;
+}
+
+
+/* Maps a `parameter' to a command-line switch.
+ Returns 0 if it can't, or if the parameter has the default value.
+ */
+static char *
+parameter_to_switch (parameter *p)
+{
+ switch (p->type)
+ {
+ case COMMAND:
+ if (p->arg)
+ return strdup ((char *) p->arg);
+ else
+ return 0;
+ break;
+ case STRING:
+ case FILENAME:
+ if (!p->widget) return 0;
+ {
+ const char *s = gtk_entry_get_text (GTK_ENTRY (p->widget));
+ char *v;
+ if (!strcmp ((s ? s : ""),
+ (p->string ? (char *) p->string : "")))
+ v = 0; /* same as default */
+ else
+ v = format_switch (p, s);
+
+ /* don't free `s' */
+ return v;
+ }
+ case SLIDER:
+ case SPINBUTTON:
+ if (!p->widget) return 0;
+ {
+ GtkAdjustment *adj =
+ (p->type == SLIDER
+ ? gtk_range_get_adjustment (GTK_RANGE (p->widget))
+ : gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (p->widget)));
+ char buf[255];
+ char *s1;
+ float value = (p->invert_p
+ ? invert_range (GET_ADJ_LOWER(adj), GET_ADJ_UPPER(adj),
+ GET_ADJ_VALUE(adj)) - 1
+ : GET_ADJ_VALUE(adj));
+
+ if (value == p->value) /* same as default */
+ return 0;
+
+ if (p->integer_p)
+ sprintf (buf, "%d", (int) (value + (value > 0 ? 0.5 : -0.5)));
+ else
+ sprintf (buf, "%.4f", value);
+
+ s1 = strchr (buf, '.');
+ if (s1)
+ {
+ char *s2 = s1 + strlen(s1) - 1;
+ while (s2 > s1 && *s2 == '0') /* lose trailing zeroes */
+ *s2-- = 0;
+ if (s2 >= s1 && *s2 == '.') /* lose trailing decimal */
+ *s2-- = 0;
+ }
+ return format_switch (p, buf);
+ }
+ case BOOLEAN:
+ if (!p->widget) return 0;
+ {
+ GtkToggleButton *b = GTK_TOGGLE_BUTTON (p->widget);
+ const char *s = (gtk_toggle_button_get_active (b)
+ ? (char *) p->arg_set
+ : (char *) p->arg_unset);
+ if (s)
+ return strdup (s);
+ else
+ return 0;
+ }
+ case SELECT:
+ if (!p->widget) return 0;
+ {
+ GtkOptionMenu *opt = GTK_OPTION_MENU (p->widget);
+ 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);
+ GList *ol = g_list_nth (p->options, menu_elt);
+ parameter *o = (ol ? (parameter *) ol->data : 0);
+ const char *s;
+ if (!o) abort();
+ if (o->type != SELECT_OPTION) abort();
+ s = (char *) o->arg_set;
+ if (s)
+ return strdup (s);
+ else
+ return 0;
+ }
+ default:
+ if (p->widget)
+ abort();
+ else
+ return 0;
+ }
+}
+
+/* Maps a GList of `parameter' objects to a complete command-line string.
+ All arguments will be properly quoted.
+ */
+static char *
+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 *result;
+ char *out;
+ int i, j;
+
+ for (i = 0, j = 0; parms; parms = parms->next, i++)
+ {
+ parameter *p = (parameter *) parms->data;
+ if (!default_p || p->type == COMMAND)
+ {
+ char *s = parameter_to_switch (p);
+ strs[j++] = s;
+ LL += (s ? strlen(s) : 0) + 1;
+ }
+ }
+
+ result = (char *) malloc (LL + 10);
+ out = result;
+ for (i = 0; i < j; i++)
+ if (strs[i])
+ {
+ strcpy (out, strs[i]);
+ out += strlen (out);
+ *out++ = ' ';
+ free (strs[i]);
+ }
+ *out = 0;
+ while (out > result && out[-1] == ' ') /* strip trailing spaces */
+ *(--out) = 0;
+ free (strs);
+
+ return result;
+}
+
+
+/* Returns a GList of the tokens the string, using shell syntax;
+ Quoted strings are handled as a single token.
+ */
+static GList *
+tokenize_command_line (const char *cmd)
+{
+ GList *result = 0;
+ const char *s = cmd;
+ while (*s)
+ {
+ const char *start;
+ char *ss;
+ for (; isspace(*s); s++); /* skip whitespace */
+
+ start = s;
+ if (*s == '\'' || *s == '\"' || *s == '`')
+ {
+ char q = *s;
+ s++;
+ while (*s && *s != q) /* skip to matching quote */
+ {
+ if (*s == '\\' && s[1]) /* allowing backslash quoting */
+ s++;
+ s++;
+ }
+ s++;
+ }
+ else
+ {
+ while (*s &&
+ (! (isspace(*s) ||
+ *s == '\'' ||
+ *s == '\"' ||
+ *s == '`')))
+ s++;
+ }
+
+ if (s > start)
+ {
+ ss = (char *) malloc ((s - start) + 1);
+ strncpy (ss, start, s-start);
+ ss[s-start] = 0;
+ if (*ss == '\'' || *ss == '\"' || *ss == '`')
+ de_stringify (ss);
+ result = g_list_append (result, ss);
+ }
+ }
+
+ return result;
+}
+
+static void parameter_set_switch (parameter *, gpointer value);
+static gboolean parse_command_line_into_parameters_1 (const char *filename,
+ GList *parms,
+ const char *option,
+ const char *value,
+ parameter *parent);
+
+
+/* Parses the command line, and flushes those options down into
+ the `parameter' structs in the list.
+ */
+static void
+parse_command_line_into_parameters (const char *filename,
+ const char *cmd, GList *parms)
+{
+ GList *tokens = tokenize_command_line (cmd);
+ GList *rest;
+ for (rest = tokens; rest; rest = rest->next)
+ {
+ char *option = rest->data;
+ rest->data = 0;
+
+ if (option[0] != '-' && option[0] != '+')
+ {
+ if (debug_p)
+ fprintf (stderr, "%s: WARNING: %s: not a switch: \"%s\"\n",
+ blurb(), filename, option);
+ }
+ else
+ {
+ char *value = 0;
+
+ if (rest->next) /* pop off the arg to this option */
+ {
+ char *s = (char *) rest->next->data;
+ /* the next token is the next switch iff it matches "-[a-z]".
+ (To avoid losing on "-x -3.1".)
+ */
+ if (s && (s[0] != '-' || !isalpha(s[1])))
+ {
+ value = s;
+ rest->next->data = 0;
+ rest = rest->next;
+ }
+ }
+
+ parse_command_line_into_parameters_1 (filename, parms,
+ option, value, 0);
+ if (value) free (value);
+ free (option);
+ }
+ }
+ g_list_free (tokens);
+}
+
+
+static gboolean
+compare_opts (const char *option, const char *value,
+ const char *template)
+{
+ int ol = strlen (option);
+ char *c;
+
+ if (strncmp (option, template, ol))
+ return FALSE;
+
+ if (template[ol] != (value ? ' ' : 0))
+ return FALSE;
+
+ /* At this point, we have a match against "option".
+ If template contains a %, we're done.
+ Else, compare against "value" too.
+ */
+ c = strchr (template, '%');
+ if (c)
+ return TRUE;
+
+ if (!value)
+ return (template[ol] == 0);
+ if (strcmp (template + ol + 1, value))
+ return FALSE;
+
+ return TRUE;
+}
+
+
+static gboolean
+parse_command_line_into_parameters_1 (const char *filename,
+ GList *parms,
+ const char *option,
+ const char *value,
+ parameter *parent)
+{
+ GList *p;
+ parameter *match = 0;
+ gint which = -1;
+ gint index = 0;
+
+ for (p = parms; p; p = p->next)
+ {
+ parameter *pp = (parameter *) p->data;
+ which = -99;
+
+ if (pp->type == SELECT)
+ {
+ if (parse_command_line_into_parameters_1 (filename,
+ pp->options,
+ option, value,
+ pp))
+ {
+ which = -2;
+ match = pp;
+ }
+ }
+ else if (pp->arg)
+ {
+ if (compare_opts (option, value, (char *) pp->arg))
+ {
+ which = -1;
+ match = pp;
+ }
+ }
+ else if (pp->arg_set)
+ {
+ if (compare_opts (option, value, (char *) pp->arg_set))
+ {
+ which = 1;
+ match = pp;
+ }
+ }
+ else if (pp->arg_unset)
+ {
+ if (compare_opts (option, value, (char *) pp->arg_unset))
+ {
+ which = 0;
+ match = pp;
+ }
+ }
+
+ if (match)
+ break;
+
+ index++;
+ }
+
+ if (!match)
+ {
+ if (debug_p && !parent)
+ fprintf (stderr, "%s: WARNING: %s: no match for %s %s\n",
+ blurb(), filename, option, (value ? value : ""));
+ return FALSE;
+ }
+
+ switch (match->type)
+ {
+ case STRING:
+ case FILENAME:
+ case SLIDER:
+ case SPINBUTTON:
+ if (which != -1) abort();
+ parameter_set_switch (match, (gpointer) value);
+ break;
+ case BOOLEAN:
+ if (which != 0 && which != 1) abort();
+ parameter_set_switch (match, GINT_TO_POINTER(which));
+ break;
+ case SELECT_OPTION:
+ if (which != 1) abort();
+ parameter_set_switch (parent, GINT_TO_POINTER(index));
+ break;
+ default:
+ break;
+ }
+ return TRUE;
+}
+
+
+/* Set the parameter's value.
+ For STRING, FILENAME, SLIDER, and SPINBUTTON, `value' is a char*.
+ For BOOLEAN and SELECT, `value' is an int.
+ */
+static void
+parameter_set_switch (parameter *p, gpointer value)
+{
+ if (p->type == SELECT_OPTION) abort();
+ if (!p->widget) return;
+ switch (p->type)
+ {
+ case STRING:
+ case FILENAME:
+ {
+ gtk_entry_set_text (GTK_ENTRY (p->widget), (char *) value);
+ break;
+ }
+ case SLIDER:
+ case SPINBUTTON:
+ {
+ GtkAdjustment *adj =
+ (p->type == SLIDER
+ ? gtk_range_get_adjustment (GTK_RANGE (p->widget))
+ : gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (p->widget)));
+ float f;
+ char c;
+
+ if (1 == sscanf ((char *) value, "%f %c", &f, &c))
+ {
+ if (p->invert_p)
+ f = invert_range (GET_ADJ_LOWER(adj), GET_ADJ_UPPER(adj), f) - 1;
+ gtk_adjustment_set_value (adj, f);
+ }
+ break;
+ }
+ case BOOLEAN:
+ {
+ GtkToggleButton *b = GTK_TOGGLE_BUTTON (p->widget);
+ gtk_toggle_button_set_active (b, GPOINTER_TO_INT(value));
+ break;
+ }
+ case SELECT:
+ {
+ gtk_option_menu_set_history (GTK_OPTION_MENU (p->widget),
+ GPOINTER_TO_INT(value));
+ break;
+ }
+ default:
+ abort();
+ }
+}
+
+
+static void
+restore_defaults (const char *progname, GList *parms)
+{
+ for (; parms; parms = parms->next)
+ {
+ parameter *p = (parameter *) parms->data;
+ if (!p->widget) continue;
+ switch (p->type)
+ {
+ case STRING:
+ case FILENAME:
+ {
+ gtk_entry_set_text (GTK_ENTRY (p->widget),
+ (p->string ? (char *) p->string : ""));
+ break;
+ }
+ case SLIDER:
+ case SPINBUTTON:
+ {
+ GtkAdjustment *adj =
+ (p->type == SLIDER
+ ? gtk_range_get_adjustment (GTK_RANGE (p->widget))
+ : gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (p->widget)));
+ float value = (p->invert_p
+ ? invert_range (p->low, p->high, p->value)
+ : p->value);
+ gtk_adjustment_set_value (adj, value);
+ break;
+ }
+ case BOOLEAN:
+ {
+ /* A toggle button should be on by default if it inserts
+ nothing into the command line when on. E.g., it should
+ be on if `arg_set' is null.
+ */
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (p->widget),
+ (!p->arg_set || !*p->arg_set));
+ break;
+ }
+ case SELECT:
+ {
+ GtkOptionMenu *opt = GTK_OPTION_MENU (p->widget);
+ GList *opts;
+ int selected = 0;
+ int index;
+
+ for (opts = p->options, index = 0; opts;
+ opts = opts->next, index++)
+ {
+ parameter *s = (parameter *) opts->data;
+ /* The default menu item is the first one with
+ no `arg_set' field. */
+ if (!s->arg_set)
+ {
+ selected = index;
+ break;
+ }
+ }
+
+ gtk_option_menu_set_history (GTK_OPTION_MENU (opt), selected);
+ break;
+ }
+ default:
+ abort();
+ }
+ }
+}
+
+
+
+/* Documentation strings
+ */
+
+static char *
+get_description (GList *parms, gboolean verbose_p)
+{
+ parameter *doc = 0;
+ for (; parms; parms = parms->next)
+ {
+ parameter *p = (parameter *) parms->data;
+ if (p->type == DESCRIPTION)
+ {
+ doc = p;
+ break;
+ }
+ }
+
+ if (!doc || !doc->string)
+ return 0;
+ else
+ {
+ char *d = strdup ((char *) doc->string);
+ char *s;
+ char *p;
+ for (s = d; *s; s++)
+ if (s[0] == '\n')
+ {
+ if (s[1] == '\n') /* blank line: leave it */
+ s++;
+ else if (s[1] == ' ' || s[1] == '\t')
+ s++; /* next line is indented: leave newline */
+ else if (!strncmp(s+1, "http:", 5))
+ s++; /* next line begins a URL: leave newline */
+ else
+ s[0] = ' '; /* delete newline to un-fold this line */
+ }
+
+ /* strip off leading whitespace on first line only */
+ for (s = d; *s && (*s == ' ' || *s == '\t'); s++)
+ ;
+ while (*s == '\n') /* strip leading newlines */
+ s++;
+ if (s != d)
+ memmove (d, s, strlen(s)+1);
+
+ /* strip off trailing whitespace and newlines */
+ {
+ int L = strlen(d);
+ while (L && isspace(d[L-1]))
+ d[--L] = 0;
+ }
+
+ /* strip off duplicated whitespaces */
+ for (s = d; *s; s++)
+ if (s[0] == ' ')
+ {
+ p = s+1;
+ while (*s == ' ')
+ s++;
+ if (*p && (s != p))
+ memmove (p, s, strlen(s)+1);
+ }
+
+#if 0
+ if (verbose_p)
+ {
+ fprintf (stderr, "%s: text read is \"%s\"\n", blurb(),doc->string);
+ fprintf (stderr, "%s: description is \"%s\"\n", blurb(), d);
+ fprintf (stderr, "%s: translation is \"%s\"\n", blurb(), _(d));
+ }
+#endif /* 0 */
+
+ return (d);
+ }
+}
+
+
+/* External interface.
+ */
+
+static conf_data *
+load_configurator_1 (const char *program, const char *arguments,
+ gboolean verbose_p)
+{
+ const char *dir = hack_configuration_path;
+ char *base_program;
+ int L = strlen (dir);
+ char *file;
+ char *s;
+ FILE *f;
+ conf_data *data;
+
+ if (L == 0) return 0;
+
+ base_program = strrchr(program, '/');
+ if (base_program) base_program++;
+ if (!base_program) base_program = (char *) program;
+
+ file = (char *) malloc (L + strlen (base_program) + 10);
+ data = (conf_data *) calloc (1, sizeof(*data));
+
+ strcpy (file, dir);
+ if (file[L-1] != '/')
+ file[L++] = '/';
+ strcpy (file+L, base_program);
+
+ for (s = file+L; *s; s++)
+ if (*s == '/' || *s == ' ')
+ *s = '_';
+ else if (isupper (*s))
+ *s = tolower (*s);
+
+ strcat (file+L, ".xml");
+
+ f = fopen (file, "r");
+ if (f)
+ {
+ int res, size = 1024;
+ char chars[1024];
+ xmlParserCtxtPtr ctxt;
+ xmlDocPtr doc = 0;
+ GtkWidget *vbox0;
+ GList *parms;
+
+ if (verbose_p)
+ fprintf (stderr, "%s: reading %s...\n", blurb(), file);
+
+ res = fread (chars, 1, 4, f);
+ if (res <= 0)
+ {
+ free (data);
+ data = 0;
+ goto DONE;
+ }
+
+ ctxt = xmlCreatePushParserCtxt(NULL, NULL, chars, res, file);
+ while ((res = fread(chars, 1, size, f)) > 0)
+ xmlParseChunk (ctxt, chars, res, 0);
+ xmlParseChunk (ctxt, chars, 0, 1);
+ doc = ctxt->myDoc;
+ xmlFreeParserCtxt (ctxt);
+ fclose (f);
+
+ /* Parsed the XML file. Now make some widgets. */
+
+ vbox0 = gtk_vbox_new (FALSE, 0);
+ gtk_widget_show (vbox0);
+
+ parms = make_parameters (file, doc->xmlRootNode, vbox0);
+ sanity_check_parameters (file, parms);
+
+ xmlFreeDoc (doc);
+
+ restore_defaults (program, parms);
+ if (arguments && *arguments)
+ parse_command_line_into_parameters (program, arguments, parms);
+
+ data->widget = vbox0;
+ data->parameters = parms;
+ data->description = get_description (parms, verbose_p);
+ }
+ else
+ {
+ parameter *p;
+
+ if (verbose_p)
+ fprintf (stderr, "%s: %s does not exist.\n", blurb(), file);
+
+ p = calloc (1, sizeof(*p));
+ p->type = COMMAND;
+ p->arg = (xmlChar *) strdup (arguments);
+
+ data->parameters = g_list_append (0, (gpointer) p);
+ }
+
+ data->progname = strdup (program);
+
+ DONE:
+ free (file);
+ return data;
+}
+
+static void
+split_command_line (const char *full_command_line,
+ char **prog_ret, char **args_ret)
+{
+ char *line = strdup (full_command_line);
+ char *prog;
+ char *args;
+ char *s;
+
+ prog = line;
+ s = line;
+ while (*s)
+ {
+ if (isspace (*s))
+ {
+ *s = 0;
+ s++;
+ while (isspace (*s)) s++;
+ break;
+ }
+ else if (*s == '=') /* if the leading word contains an "=", skip it. */
+ {
+ while (*s && !isspace (*s)) s++;
+ while (isspace (*s)) s++;
+ prog = s;
+ }
+ s++;
+ }
+ args = s;
+
+ *prog_ret = strdup (prog);
+ *args_ret = strdup (args);
+ free (line);
+}
+
+
+conf_data *
+load_configurator (const char *full_command_line, gboolean verbose_p)
+{
+ char *prog;
+ char *args;
+ conf_data *cd;
+ debug_p = verbose_p;
+ split_command_line (full_command_line, &prog, &args);
+ cd = load_configurator_1 (prog, args, verbose_p);
+ free (prog);
+ free (args);
+ return cd;
+}
+
+
+
+char *
+get_configurator_command_line (conf_data *data, gboolean default_p)
+{
+ char *args = parameters_to_cmd_line (data->parameters, default_p);
+ char *result = (char *) malloc (strlen (data->progname) +
+ strlen (args) + 2);
+ strcpy (result, data->progname);
+ strcat (result, " ");
+ strcat (result, args);
+ free (args);
+ return result;
+}
+
+
+void
+set_configurator_command_line (conf_data *data, const char *full_command_line)
+{
+ char *prog;
+ char *args;
+ split_command_line (full_command_line, &prog, &args);
+ if (data->progname) free (data->progname);
+ data->progname = prog;
+ restore_defaults (prog, data->parameters);
+ parse_command_line_into_parameters (prog, args, data->parameters);
+ free (args);
+}
+
+void
+free_conf_data (conf_data *data)
+{
+ if (data->parameters)
+ {
+ GList *rest;
+ for (rest = data->parameters; rest; rest = rest->next)
+ {
+ free_parameter ((parameter *) rest->data);
+ rest->data = 0;
+ }
+ g_list_free (data->parameters);
+ data->parameters = 0;
+ }
+
+ if (data->widget)
+ gtk_widget_destroy (data->widget);
+
+ if (data->progname)
+ free (data->progname);
+ if (data->description)
+ free (data->description);
+
+ memset (data, ~0, sizeof(*data));
+ free (data);
+}
+
+
+#endif /* HAVE_GTK && HAVE_XML -- whole file */
diff --git a/driver/demo-Gtk-conf.h b/driver/demo-Gtk-conf.h
new file mode 100644
index 0000000..f462152
--- /dev/null
+++ b/driver/demo-Gtk-conf.h
@@ -0,0 +1,31 @@
+/* demo-Gtk-conf.c --- implements the dynamic configuration dialogs.
+ * xscreensaver, Copyright (c) 2001-2008 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 _DEMO_GTK_CONF_H_
+#define _DEMO_GTK_CONF_H_
+
+typedef struct {
+ GtkWidget *widget; /* the container widget with the sliders and stuff. */
+ GList *parameters; /* internal data -- hands off */
+ char *progname;
+ char *progclass;
+ char *description;
+} conf_data;
+
+extern conf_data *load_configurator (const char *cmd_line, gboolean verbose_p);
+extern char *get_configurator_command_line (conf_data *, gboolean default_p);
+extern void set_configurator_command_line (conf_data *, const char *cmd_line);
+extern void free_conf_data (conf_data *);
+
+extern const char *hack_configuration_path;
+
+#endif /* _DEMO_GTK_CONF_H_ */
diff --git a/driver/demo-Gtk.c b/driver/demo-Gtk.c
new file mode 100644
index 0000000..0dfc387
--- /dev/null
+++ b/driver/demo-Gtk.c
@@ -0,0 +1,5337 @@
+/* demo-Gtk.c --- implements the interactive demo-mode and options dialogs.
+ * xscreensaver, Copyright (c) 1993-2018 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
+
+#ifdef HAVE_GTK /* whole file */
+
+#include <xscreensaver-intl.h>
+
+#include <stdlib.h>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+# ifdef __GNUC__
+# define STFU __extension__ /* ignore gcc -pendantic warnings in next sexp */
+# else
+# define STFU /* */
+# endif
+
+
+#ifdef ENABLE_NLS
+# 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 <sys/stat.h>
+#include <sys/time.h>
+
+
+#include <signal.h>
+#include <errno.h>
+#ifdef HAVE_SYS_WAIT_H
+# include <sys/wait.h> /* for waitpid() and associated macros */
+#endif
+
+
+#include <X11/Xproto.h> /* for CARD32 */
+#include <X11/Xatom.h> /* for XA_INTEGER */
+#include <X11/Intrinsic.h>
+#include <X11/StringDefs.h>
+
+/* We don't actually use any widget internals, but these are included
+ so that gdb will have debug info for the widgets... */
+#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 */
+
+#include <gtk/gtk.h>
+
+#ifdef HAVE_CRAPPLET
+# include <gnome.h>
+# include <capplet-widget.h>
+#endif
+
+#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
+
+
+
+#include "version.h"
+#include "prefs.h"
+#include "resources.h" /* for parse_time() */
+#include "visual.h" /* for has_writable_cells() */
+#include "remote.h" /* for xscreensaver_command() */
+#include "usleep.h"
+
+#include "logo-50.xpm"
+#include "logo-180.xpm"
+
+#include "demo-Gtk-conf.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#ifdef HAVE_GTK2
+enum {
+ COL_ENABLED,
+ COL_NAME,
+ COL_LAST
+};
+#endif /* HAVE_GTK2 */
+
+/* Deal with deprecation of direct access to struct fields on the way to GTK3
+ See http://live.gnome.org/GnomeGoals/UseGseal
+ */
+#if GTK_CHECK_VERSION(2,14,0)
+# define GET_PARENT(w) gtk_widget_get_parent (w)
+# define GET_WINDOW(w) gtk_widget_get_window (w)
+# define GET_ACTION_AREA(d) gtk_dialog_get_action_area (d)
+# define GET_CONTENT_AREA(d) gtk_dialog_get_content_area (d)
+# define GET_ADJ_VALUE(a) gtk_adjustment_get_value (a)
+# define SET_ADJ_VALUE(a,v) gtk_adjustment_set_value (a, v)
+# define SET_ADJ_UPPER(a,v) gtk_adjustment_set_upper (a, v)
+#else
+# define GET_PARENT(w) ((w)->parent)
+# define GET_WINDOW(w) ((w)->window)
+# define GET_ACTION_AREA(d) ((d)->action_area)
+# define GET_CONTENT_AREA(d) ((d)->vbox)
+# define GET_ADJ_VALUE(a) ((a)->value)
+# define SET_ADJ_VALUE(a,v) (a)->value = v
+# define SET_ADJ_UPPER(a,v) (a)->upper = v
+#endif
+
+#if GTK_CHECK_VERSION(2,18,0)
+# define SET_CAN_DEFAULT(w) gtk_widget_set_can_default ((w), TRUE)
+# define GET_SENSITIVE(w) gtk_widget_get_sensitive (w)
+#else
+# define SET_CAN_DEFAULT(w) GTK_WIDGET_SET_FLAGS ((w), GTK_CAN_DEFAULT)
+# define GET_SENSITIVE(w) GTK_WIDGET_IS_SENSITIVE (w)
+#endif
+
+#if GTK_CHECK_VERSION(2,20,0)
+# define GET_REALIZED(w) gtk_widget_get_realized (w)
+#else
+# define GET_REALIZED(w) GTK_WIDGET_REALIZED (w)
+#endif
+
+/* from exec.c */
+extern void exec_command (const char *shell, const char *command, int nice);
+extern int on_path_p (const char *program);
+
+static void hack_subproc_environment (Window preview_window_id, Bool debug_p);
+
+#undef countof
+#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;
+
+/* The order of the items in the mode menu. */
+static int mode_menu_order[] = {
+ DONT_BLANK, BLANK_ONLY, ONE_HACK, RANDOM_HACKS, RANDOM_HACKS_SAME };
+
+
+typedef struct {
+
+ char *short_version; /* version number of this xscreensaver build */
+
+ GtkWidget *toplevel_widget; /* the main window */
+ GtkWidget *base_widget; /* root of our hierarchy (for name lookups) */
+ GtkWidget *popup_widget; /* the "Settings" dialog */
+ conf_data *cdata; /* private data for per-hack configuration */
+
+#ifdef HAVE_GTK2
+ GladeXML *glade_ui; /* Glade UI file */
+#endif /* HAVE_GTK2 */
+
+ Bool debug_p; /* whether to print diagnostics */
+ Bool initializing_p; /* flag for breaking recursion loops */
+ Bool saving_p; /* flag for breaking recursion loops */
+
+ char *desired_preview_cmd; /* subprocess we intend to run */
+ char *running_preview_cmd; /* subprocess we are currently running */
+ pid_t running_preview_pid; /* pid of forked subproc (might be dead) */
+ Bool running_preview_error_p; /* whether the pid died abnormally */
+
+ Bool preview_suppressed_p; /* flag meaning "don't launch subproc" */
+ int subproc_timer_id; /* timer to delay subproc launch */
+ int subproc_check_timer_id; /* timer to check whether it started up */
+ int subproc_check_countdown; /* how many more checks left */
+
+ int *list_elt_to_hack_number; /* table for sorting the hack list */
+ int *hack_number_to_list_elt; /* the inverse table */
+ Bool *hacks_available_p; /* whether hacks are on $PATH */
+ int total_available; /* how many are on $PATH */
+ int list_count; /* how many items are in the list: this may be
+ less than p->screenhacks_count, if some are
+ suppressed. */
+
+ int _selected_list_element; /* don't use this: call
+ selected_list_element() instead */
+
+ int nscreens; /* How many X or Xinerama screens there are */
+
+ saver_preferences prefs;
+
+} state;
+
+
+/* Total fucking evilness due to the fact that it's rocket science to get
+ 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_BLANK, XA_LOCK, XA_RESTART, XA_EXIT;
+
+
+static void populate_demo_window (state *, int list_elt);
+static void populate_prefs_page (state *);
+static void populate_popup_window (state *);
+
+static Bool flush_dialog_changes_and_save (state *);
+static Bool flush_popup_changes_and_save (state *);
+
+static int maybe_reload_init_file (state *);
+static void await_xscreensaver (state *);
+static Bool xscreensaver_running_p (state *);
+static void sensitize_menu_items (state *s, Bool force_p);
+static void force_dialog_repaint (state *s);
+
+static void schedule_preview (state *, const char *cmd);
+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.
+ */
+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 restart_menu_cb (GtkWidget *, gpointer user_data);
+void run_this_cb (GtkButton *, gpointer user_data);
+void manual_cb (GtkButton *, gpointer user_data);
+void run_next_cb (GtkButton *, gpointer user_data);
+void run_prev_cb (GtkButton *, gpointer user_data);
+void pref_changed_cb (GtkWidget *, gpointer user_data);
+gboolean pref_changed_event_cb (GtkWidget *, GdkEvent *, gpointer user_data);
+void mode_menu_item_cb (GtkWidget *, gpointer user_data);
+void switch_page_cb (GtkNotebook *, GtkNotebookPage *,
+ gint page_num, gpointer user_data);
+void browse_image_dir_cb (GtkButton *, gpointer user_data);
+void browse_text_file_cb (GtkButton *, gpointer user_data);
+void browse_text_program_cb (GtkButton *, gpointer user_data);
+void settings_cb (GtkButton *, gpointer user_data);
+void settings_adv_cb (GtkButton *, gpointer user_data);
+void settings_std_cb (GtkButton *, gpointer user_data);
+void settings_reset_cb (GtkButton *, gpointer user_data);
+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);
+
+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)
+{
+ GtkWidget *w;
+ if (!s) abort();
+ if (!name) abort();
+ if (!*name) abort();
+
+#ifdef HAVE_GTK2
+ if (!s->glade_ui)
+ {
+ /* First try to load the Glade 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 };
+ int i;
+ 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;
+ }
+ }
+ if (!s->glade_ui)
+ {
+ fprintf (stderr,
+ "%s: could not load \"" GLADE_FILE_NAME "\"\n"
+ "\tfrom " GLADE_DIR "/ or current directory.\n",
+ blurb());
+ exit (-1);
+ }
+# undef GLADE_FILE_NAME
+
+ glade_xml_signal_autoconnect (s->glade_ui);
+ }
+
+ w = glade_xml_get_widget (s->glade_ui, name);
+
+#else /* !HAVE_GTK2 */
+
+ w = (GtkWidget *) gtk_object_get_data (GTK_OBJECT (s->base_widget),
+ name);
+ if (w) return w;
+ w = (GtkWidget *) gtk_object_get_data (GTK_OBJECT (s->popup_widget),
+ name);
+#endif /* HAVE_GTK2 */
+ if (w) return w;
+
+ fprintf (stderr, "%s: no widget \"%s\" (wrong Glade file?)\n",
+ blurb(), name);
+ abort();
+}
+
+
+/* Why this behavior isn't automatic in *either* toolkit, I'll never know.
+ Takes a scroller, viewport, or list as an argument.
+ */
+static void
+ensure_selected_item_visible (GtkWidget *widget)
+{
+#ifdef HAVE_GTK2
+ GtkTreePath *path;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+ path = gtk_tree_path_new_first ();
+ else
+ path = gtk_tree_model_get_path (model, &iter);
+
+ gtk_tree_view_set_cursor (GTK_TREE_VIEW (widget), path, NULL, FALSE);
+
+ gtk_tree_path_free (path);
+
+#else /* !HAVE_GTK2 */
+
+ GtkScrolledWindow *scroller = 0;
+ GtkViewport *vp = 0;
+ GtkList *list_widget = 0;
+ GList *slist;
+ GList *kids;
+ int nkids = 0;
+ GtkWidget *selected = 0;
+ int list_elt = -1;
+ GtkAdjustment *adj;
+ gint parent_h, child_y, child_h, children_h, ignore;
+ double ratio_t, ratio_b;
+
+ if (GTK_IS_SCROLLED_WINDOW (widget))
+ {
+ scroller = GTK_SCROLLED_WINDOW (widget);
+ vp = GTK_VIEWPORT (GTK_BIN (scroller)->child);
+ list_widget = GTK_LIST (GTK_BIN(vp)->child);
+ }
+ else if (GTK_IS_VIEWPORT (widget))
+ {
+ vp = GTK_VIEWPORT (widget);
+ scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent);
+ list_widget = GTK_LIST (GTK_BIN(vp)->child);
+ }
+ else if (GTK_IS_LIST (widget))
+ {
+ list_widget = GTK_LIST (widget);
+ vp = GTK_VIEWPORT (GTK_WIDGET (list_widget)->parent);
+ scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent);
+ }
+ else
+ abort();
+
+ slist = list_widget->selection;
+ selected = (slist ? GTK_WIDGET (slist->data) : 0);
+ if (!selected)
+ return;
+
+ list_elt = gtk_list_child_position (list_widget, GTK_WIDGET (selected));
+
+ for (kids = gtk_container_children (GTK_CONTAINER (list_widget));
+ kids; kids = kids->next)
+ nkids++;
+
+ adj = gtk_scrolled_window_get_vadjustment (scroller);
+
+ gdk_window_get_geometry (GET_WINDOW (GTK_WIDGET (vp)),
+ &ignore, &ignore, &ignore, &parent_h, &ignore);
+ gdk_window_get_geometry (GET_WINDOW (GTK_WIDGET (selected)),
+ &ignore, &child_y, &ignore, &child_h, &ignore);
+ children_h = nkids * child_h;
+
+ ratio_t = ((double) child_y) / ((double) children_h);
+ ratio_b = ((double) child_y + child_h) / ((double) children_h);
+
+ if (adj->upper == 0.0) /* no items in list */
+ return;
+
+ if (ratio_t < (adj->value / adj->upper) ||
+ ratio_b > ((adj->value + adj->page_size) / adj->upper))
+ {
+ double target;
+ int slop = parent_h * 0.75; /* how much to overshoot by */
+
+ if (ratio_t < (adj->value / adj->upper))
+ {
+ double ratio_w = ((double) parent_h) / ((double) children_h);
+ double ratio_l = (ratio_b - ratio_t);
+ target = ((ratio_t - ratio_w + ratio_l) * adj->upper);
+ target += slop;
+ }
+ else /* if (ratio_b > ((adj->value + adj->page_size) / adj->upper))*/
+ {
+ target = ratio_t * adj->upper;
+ target -= slop;
+ }
+
+ if (target > adj->upper - adj->page_size)
+ target = adj->upper - adj->page_size;
+ if (target < 0)
+ target = 0;
+
+ gtk_adjustment_set_value (adj, target);
+ }
+#endif /* !HAVE_GTK2 */
+}
+
+static void
+warning_dialog_dismiss_cb (GtkWidget *widget, gpointer user_data)
+{
+ GtkWidget *shell = GTK_WIDGET (user_data);
+ while (GET_PARENT (shell))
+ shell = GET_PARENT (shell);
+ gtk_widget_destroy (GTK_WIDGET (shell));
+}
+
+
+void restart_menu_cb (GtkWidget *widget, gpointer user_data);
+
+static void warning_dialog_restart_cb (GtkWidget *widget, gpointer user_data)
+{
+ restart_menu_cb (widget, user_data);
+ warning_dialog_dismiss_cb (widget, user_data);
+}
+
+static void warning_dialog_killg_cb (GtkWidget *widget, gpointer user_data)
+{
+ kill_gnome_screensaver ();
+ warning_dialog_dismiss_cb (widget, user_data);
+}
+
+static void warning_dialog_killk_cb (GtkWidget *widget, gpointer user_data)
+{
+ kill_kde_screensaver ();
+ warning_dialog_dismiss_cb (widget, user_data);
+}
+
+typedef enum { D_NONE, D_LAUNCH, D_GNOME, D_KDE } dialog_button;
+
+static Bool
+warning_dialog (GtkWidget *parent, const char *message,
+ dialog_button button_type, int center)
+{
+ char *msg = strdup (message);
+ char *head;
+
+ GtkWidget *dialog = gtk_dialog_new ();
+ GtkWidget *label = 0;
+ GtkWidget *ok = 0;
+ GtkWidget *cancel = 0;
+ int i = 0;
+
+ while (parent && !GET_WINDOW (parent))
+ parent = GET_PARENT (parent);
+
+ if (!parent ||
+ !GET_WINDOW (parent)) /* too early to pop up transient dialogs */
+ {
+ fprintf (stderr, "%s: too early for dialog?\n", progname);
+ free(msg);
+ return False;
+ }
+
+ head = msg;
+ while (head)
+ {
+ char name[20];
+ char *s = strchr (head, '\n');
+ if (s) *s = 0;
+
+ sprintf (name, "label%d", i++);
+
+ {
+ label = gtk_label_new (head);
+#ifdef HAVE_GTK2
+ gtk_label_set_selectable (GTK_LABEL (label), TRUE);
+#endif /* HAVE_GTK2 */
+
+#ifndef HAVE_GTK2
+ if (i == 1)
+ {
+ GTK_WIDGET (label)->style =
+ gtk_style_copy (GTK_WIDGET (label)->style);
+ GTK_WIDGET (label)->style->font =
+ gdk_font_load (get_string_resource("warning_dialog.headingFont",
+ "Dialog.Font"));
+ gtk_widget_set_style (GTK_WIDGET (label),
+ GTK_WIDGET (label)->style);
+ }
+#endif /* !HAVE_GTK2 */
+ if (center <= 0)
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_box_pack_start (GTK_BOX (GET_CONTENT_AREA (GTK_DIALOG (dialog))),
+ label, TRUE, TRUE, 0);
+ gtk_widget_show (label);
+ }
+
+ if (s)
+ head = s+1;
+ else
+ head = 0;
+
+ center--;
+ }
+
+ label = gtk_label_new ("");
+ gtk_box_pack_start (GTK_BOX (GET_CONTENT_AREA (GTK_DIALOG (dialog))),
+ label, TRUE, TRUE, 0);
+ gtk_widget_show (label);
+
+ label = gtk_hbutton_box_new ();
+ gtk_box_pack_start (GTK_BOX (GET_ACTION_AREA (GTK_DIALOG (dialog))),
+ label, TRUE, TRUE, 0);
+
+#ifdef HAVE_GTK2
+ if (button_type != D_NONE)
+ {
+ cancel = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
+ gtk_container_add (GTK_CONTAINER (label), cancel);
+ }
+
+ ok = gtk_button_new_from_stock (GTK_STOCK_OK);
+ gtk_container_add (GTK_CONTAINER (label), ok);
+
+#else /* !HAVE_GTK2 */
+
+ ok = gtk_button_new_with_label ("OK");
+ gtk_container_add (GTK_CONTAINER (label), ok);
+
+ if (button_type != D_NONE)
+ {
+ cancel = gtk_button_new_with_label ("Cancel");
+ gtk_container_add (GTK_CONTAINER (label), cancel);
+ }
+
+#endif /* !HAVE_GTK2 */
+
+ gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
+ gtk_container_set_border_width (GTK_CONTAINER (dialog), 10);
+ gtk_window_set_title (GTK_WINDOW (dialog), progclass);
+ SET_CAN_DEFAULT (ok);
+ gtk_widget_show (ok);
+ gtk_widget_grab_focus (ok);
+
+ if (cancel)
+ {
+ SET_CAN_DEFAULT (cancel);
+ gtk_widget_show (cancel);
+ }
+ gtk_widget_show (label);
+ gtk_widget_show (dialog);
+
+ if (button_type != D_NONE)
+ {
+ GtkSignalFunc fn;
+ switch (button_type) {
+ case D_LAUNCH: fn = GTK_SIGNAL_FUNC (warning_dialog_restart_cb); break;
+ case D_GNOME: fn = GTK_SIGNAL_FUNC (warning_dialog_killg_cb); break;
+ case D_KDE: fn = GTK_SIGNAL_FUNC (warning_dialog_killk_cb); break;
+ default: abort(); break;
+ }
+ gtk_signal_connect_object (GTK_OBJECT (ok), "clicked", fn,
+ (gpointer) dialog);
+ gtk_signal_connect_object (GTK_OBJECT (cancel), "clicked",
+ GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb),
+ (gpointer) dialog);
+ }
+ else
+ {
+ gtk_signal_connect_object (GTK_OBJECT (ok), "clicked",
+ GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb),
+ (gpointer) dialog);
+ }
+
+ gdk_window_set_transient_for (GET_WINDOW (GTK_WIDGET (dialog)),
+ GET_WINDOW (GTK_WIDGET (parent)));
+
+#ifdef HAVE_GTK2
+ gtk_window_present (GTK_WINDOW (dialog));
+#else /* !HAVE_GTK2 */
+ gdk_window_show (GTK_WIDGET (dialog)->window);
+ gdk_window_raise (GTK_WIDGET (dialog)->window);
+#endif /* !HAVE_GTK2 */
+
+ free (msg);
+ return True;
+}
+
+
+static void
+run_cmd (state *s, Atom command, int arg)
+{
+ char *err = 0;
+ int status;
+
+ flush_dialog_changes_and_save (s);
+ status = xscreensaver_command (GDK_DISPLAY(), command, arg, False, &err);
+
+ /* Kludge: ignore the spurious "window unexpectedly deleted" errors... */
+ if (status < 0 && err && strstr (err, "unexpectedly deleted"))
+ status = 0;
+
+ if (status < 0)
+ {
+ char buf [255];
+ if (err)
+ sprintf (buf, "Error:\n\n%s", err);
+ else
+ strcpy (buf, "Unknown error!");
+ warning_dialog (s->toplevel_widget, buf, D_NONE, 100);
+ }
+ if (err) free (err);
+
+ sensitize_menu_items (s, True);
+ force_dialog_repaint (s);
+}
+
+
+static void
+run_hack (state *s, int list_elt, Bool report_errors_p)
+{
+ int hack_number;
+ char *err = 0;
+ int status;
+
+ if (list_elt < 0) return;
+ hack_number = s->list_elt_to_hack_number[list_elt];
+
+ flush_dialog_changes_and_save (s);
+ schedule_preview (s, 0);
+
+ status = xscreensaver_command (GDK_DISPLAY(), XA_DEMO, hack_number + 1,
+ False, &err);
+
+ if (status < 0 && report_errors_p)
+ {
+ if (xscreensaver_running_p (s))
+ {
+ /* Kludge: ignore the spurious "window unexpectedly deleted"
+ errors... */
+ if (err && strstr (err, "unexpectedly deleted"))
+ status = 0;
+
+ if (status < 0)
+ {
+ char buf [255];
+ if (err)
+ sprintf (buf, "Error:\n\n%s", err);
+ else
+ strcpy (buf, "Unknown error!");
+ warning_dialog (s->toplevel_widget, buf, D_NONE, 100);
+ }
+ }
+ else
+ {
+ /* The error is that the daemon isn't running;
+ offer to restart it.
+ */
+ const char *d = DisplayString (GDK_DISPLAY());
+ char msg [1024];
+ sprintf (msg,
+ _("Warning:\n\n"
+ "The XScreenSaver daemon doesn't seem to be running\n"
+ "on display \"%s\". Launch it now?"),
+ d);
+ warning_dialog (s->toplevel_widget, msg, D_LAUNCH, 1);
+ }
+ }
+
+ if (err) free (err);
+
+ sensitize_menu_items (s, False);
+}
+
+
+
+/* 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.
+ */
+
+G_MODULE_EXPORT void
+exit_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ flush_dialog_changes_and_save (s);
+ kill_preview_subproc (s, False);
+ gtk_main_quit ();
+}
+
+static gboolean
+wm_toplevel_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data)
+{
+ state *s = (state *) data;
+ flush_dialog_changes_and_save (s);
+ gtk_main_quit ();
+ return TRUE;
+}
+
+
+G_MODULE_EXPORT void
+about_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
+{
+ 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, ',');
+ *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
+ look as good in the plain-old default Latin1 "C" locale.)
+ */
+#ifdef HAVE_GTK2
+ sprintf(copy, ("Copyright \xC2\xA9 1991-%s %s"), year, s);
+#else /* !HAVE_GTK2 */
+ sprintf(copy, ("Copyright \251 1991-%s %s"), year, s);
+#endif /* !HAVE_GTK2 */
+
+ sprintf (msg, "%s\n\n%s", copy, desc);
+
+ /* I can't make gnome_about_new() work here -- it starts dying in
+ gdk_imlib_get_visual() under gnome_about_new(). If this worked,
+ then this might be the thing to do:
+
+ #ifdef HAVE_CRAPPLET
+ {
+ const gchar *auth[] = { 0 };
+ GtkWidget *about = gnome_about_new (progclass, vers, "", auth, desc,
+ "xscreensaver.xpm");
+ gtk_widget_show (about);
+ }
+ #else / * GTK but not GNOME * /
+ ...
+ */
+ {
+ GdkColormap *colormap;
+ GdkPixmap *gdkpixmap;
+ GdkBitmap *mask;
+
+ GtkWidget *dialog = gtk_dialog_new ();
+ GtkWidget *hbox, *icon, *vbox, *label1, *label2, *hb, *ok;
+ GtkWidget *parent = GTK_WIDGET (menuitem);
+ while (GET_PARENT (parent))
+ parent = GET_PARENT (parent);
+
+ hbox = gtk_hbox_new (FALSE, 20);
+ gtk_box_pack_start (GTK_BOX (GET_CONTENT_AREA (GTK_DIALOG (dialog))),
+ hbox, TRUE, TRUE, 0);
+
+ colormap = gtk_widget_get_colormap (parent);
+ gdkpixmap =
+ gdk_pixmap_colormap_create_from_xpm_d (NULL, colormap, &mask, NULL,
+ (gchar **) logo_180_xpm);
+ icon = gtk_pixmap_new (gdkpixmap, mask);
+ gtk_misc_set_padding (GTK_MISC (icon), 10, 10);
+
+ gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0);
+
+ vbox = gtk_vbox_new (FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
+
+ label1 = gtk_label_new (vers);
+ 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);
+
+#ifndef HAVE_GTK2
+ GTK_WIDGET (label1)->style = gtk_style_copy (GTK_WIDGET (label1)->style);
+ GTK_WIDGET (label1)->style->font =
+ gdk_font_load (get_string_resource ("about.headingFont","Dialog.Font"));
+ gtk_widget_set_style (GTK_WIDGET (label1), GTK_WIDGET (label1)->style);
+#endif /* HAVE_GTK2 */
+
+ label2 = gtk_label_new (msg);
+ gtk_box_pack_start (GTK_BOX (vbox), label2, TRUE, TRUE, 0);
+ gtk_label_set_justify (GTK_LABEL (label2), GTK_JUSTIFY_LEFT);
+ gtk_misc_set_alignment (GTK_MISC (label2), 0.0, 0.25);
+
+#ifndef HAVE_GTK2
+ GTK_WIDGET (label2)->style = gtk_style_copy (GTK_WIDGET (label2)->style);
+ GTK_WIDGET (label2)->style->font =
+ gdk_font_load (get_string_resource ("about.bodyFont","Dialog.Font"));
+ gtk_widget_set_style (GTK_WIDGET (label2), GTK_WIDGET (label2)->style);
+#endif /* HAVE_GTK2 */
+
+ hb = gtk_hbutton_box_new ();
+
+ gtk_box_pack_start (GTK_BOX (GET_ACTION_AREA (GTK_DIALOG (dialog))),
+ hb, TRUE, TRUE, 0);
+
+#ifdef HAVE_GTK2
+ ok = gtk_button_new_from_stock (GTK_STOCK_OK);
+#else /* !HAVE_GTK2 */
+ ok = gtk_button_new_with_label (_("OK"));
+#endif /* !HAVE_GTK2 */
+ gtk_container_add (GTK_CONTAINER (hb), ok);
+
+ gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
+ gtk_container_set_border_width (GTK_CONTAINER (dialog), 10);
+ gtk_window_set_title (GTK_WINDOW (dialog), progclass);
+
+ gtk_widget_show (hbox);
+ gtk_widget_show (icon);
+ gtk_widget_show (vbox);
+ gtk_widget_show (label1);
+ gtk_widget_show (label2);
+ gtk_widget_show (hb);
+ gtk_widget_show (ok);
+ gtk_widget_show (dialog);
+
+ gtk_signal_connect_object (GTK_OBJECT (ok), "clicked",
+ GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb),
+ (gpointer) dialog);
+ gdk_window_set_transient_for (GET_WINDOW (GTK_WIDGET (dialog)),
+ GET_WINDOW (GTK_WIDGET (parent)));
+ gdk_window_show (GET_WINDOW (GTK_WIDGET (dialog)));
+ gdk_window_raise (GET_WINDOW (GTK_WIDGET (dialog)));
+ }
+}
+
+
+G_MODULE_EXPORT void
+doc_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ saver_preferences *p = &s->prefs;
+ char *help_command;
+
+ if (!p->help_url || !*p->help_url)
+ {
+ warning_dialog (s->toplevel_widget,
+ _("Error:\n\n"
+ "No Help URL has been specified.\n"), D_NONE, 100);
+ return;
+ }
+
+ help_command = (char *) malloc (strlen (p->load_url_command) +
+ (strlen (p->help_url) * 4) + 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);
+ strcat (help_command, " ) &");
+ if (system (help_command) < 0)
+ fprintf (stderr, "%s: fork error\n", blurb());
+ free (help_command);
+}
+
+
+G_MODULE_EXPORT void
+file_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ sensitize_menu_items (s, False);
+}
+
+
+G_MODULE_EXPORT void
+activate_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ run_cmd (s, XA_ACTIVATE, 0);
+}
+
+
+G_MODULE_EXPORT void
+lock_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ run_cmd (s, XA_LOCK, 0);
+}
+
+
+G_MODULE_EXPORT void
+kill_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ run_cmd (s, XA_EXIT, 0);
+}
+
+
+G_MODULE_EXPORT void
+restart_menu_cb (GtkWidget *widget, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ flush_dialog_changes_and_save (s);
+ xscreensaver_command (GDK_DISPLAY(), XA_EXIT, 0, False, NULL);
+ sleep (1);
+ if (system ("xscreensaver -nosplash &") < 0)
+ fprintf (stderr, "%s: fork error\n", blurb());
+
+ await_xscreensaver (s);
+}
+
+static Bool
+xscreensaver_running_p (state *s)
+{
+ Display *dpy = GDK_DISPLAY();
+ char *rversion = 0;
+ server_xscreensaver_version (dpy, &rversion, 0, 0);
+ if (!rversion)
+ return False;
+ free (rversion);
+ return True;
+}
+
+static void
+await_xscreensaver (state *s)
+{
+ int countdown = 5;
+ Bool ok = False;
+
+ while (!ok && (--countdown > 0))
+ if (xscreensaver_running_p (s))
+ ok = True;
+ else
+ sleep (1); /* If it's not there yet, wait a second... */
+
+ sensitize_menu_items (s, True);
+
+ if (! ok)
+ {
+ /* Timed out, no screensaver running. */
+
+ char buf [1024];
+ Bool root_p = (geteuid () == 0);
+
+ strcpy (buf,
+ _("Error:\n\n"
+ "The xscreensaver daemon did not start up properly.\n"
+ "\n"));
+
+ if (root_p)
+ strcat (buf, STFU
+ _("You are running as root. This usually means that xscreensaver\n"
+ "was unable to contact your X server because access control is\n"
+ "turned on. Try running this command:\n"
+ "\n"
+ " xhost +localhost\n"
+ "\n"
+ "and then selecting `File / Restart Daemon'.\n"
+ "\n"
+ "Note that turning off access control will allow anyone logged\n"
+ "on to this machine to access your screen, which might be\n"
+ "considered a security problem. Please read the xscreensaver\n"
+ "manual and FAQ for more information.\n"
+ "\n"
+ "You shouldn't run X as root. Instead, you should log in as a\n"
+ "normal user, and `su' as necessary."));
+ else
+ strcat (buf, _("Please check your $PATH and permissions."));
+
+ warning_dialog (s->toplevel_widget, buf, D_NONE, 1);
+ }
+
+ force_dialog_repaint (s);
+}
+
+
+static int
+selected_list_element (state *s)
+{
+ return s->_selected_list_element;
+}
+
+
+static int
+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)
+ fprintf (stderr, "%s: wrote %s\n", blurb(), init_file_name());
+ return 0;
+ }
+ else
+ {
+ const char *f = init_file_name();
+ if (!f || !*f)
+ warning_dialog (s->toplevel_widget,
+ _("Error:\n\nCouldn't determine init file name!\n"),
+ D_NONE, 100);
+ else
+ {
+ char *b = (char *) malloc (strlen(f) + 1024);
+ sprintf (b, _("Error:\n\nCouldn't write %s\n"), f);
+ warning_dialog (s->toplevel_widget, b, D_NONE, 100);
+ free (b);
+ }
+ return -1;
+ }
+}
+
+
+G_MODULE_EXPORT void
+run_this_cb (GtkButton *button, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ int list_elt = selected_list_element (s);
+ if (list_elt < 0) return;
+ if (!flush_dialog_changes_and_save (s))
+ run_hack (s, list_elt, True);
+}
+
+
+G_MODULE_EXPORT void
+manual_cb (GtkButton *button, gpointer user_data)
+{
+ Display *dpy = GDK_DISPLAY();
+ state *s = global_state_kludge; /* I hate C so much... */
+ saver_preferences *p = &s->prefs;
+ GtkWidget *list_widget = name_to_widget (s, "list");
+ int list_elt = selected_list_element (s);
+ int hack_number;
+ char *name, *name2, *cmd, *str;
+ char *oname = 0;
+ if (list_elt < 0) return;
+ hack_number = s->list_elt_to_hack_number[list_elt];
+
+ flush_dialog_changes_and_save (s);
+ ensure_selected_item_visible (list_widget);
+
+ name = strdup (p->screenhacks[hack_number]->command);
+ name2 = name;
+ oname = name;
+ while (isspace (*name2)) name2++;
+ str = name2;
+ while (*str && !isspace (*str)) str++;
+ *str = 0;
+ str = strrchr (name2, '/');
+ if (str) name2 = str+1;
+
+ cmd = get_string_resource (dpy, "manualCommand", "ManualCommand");
+ if (cmd)
+ {
+ char *cmd2 = (char *) malloc (strlen (cmd) + (strlen (name2) * 4) + 100);
+ strcpy (cmd2, "( ");
+ sprintf (cmd2 + strlen (cmd2),
+ cmd,
+ name2, name2, name2, name2);
+ strcat (cmd2, " ) &");
+ if (system (cmd2) < 0)
+ fprintf (stderr, "%s: fork error\n", blurb());
+ free (cmd2);
+ }
+ else
+ {
+ warning_dialog (GTK_WIDGET (button),
+ _("Error:\n\nno `manualCommand' resource set."),
+ D_NONE, 100);
+ }
+
+ free (oname);
+}
+
+
+static void
+force_list_select_item (state *s, GtkWidget *list, int list_elt, Bool scroll_p)
+{
+ GtkWidget *parent = name_to_widget (s, "scroller");
+ gboolean was = GET_SENSITIVE (parent);
+#ifdef HAVE_GTK2
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+#endif /* HAVE_GTK2 */
+
+ 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 (gtk_tree_model_iter_nth_child (model, &iter, NULL, list_elt))
+ {
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
+ gtk_tree_selection_select_iter (selection, &iter);
+ }
+#else /* !HAVE_GTK2 */
+ gtk_list_select_item (GTK_LIST (list), list_elt);
+#endif /* !HAVE_GTK2 */
+ if (scroll_p) ensure_selected_item_visible (GTK_WIDGET (list));
+ if (!was) gtk_widget_set_sensitive (parent, False);
+}
+
+
+G_MODULE_EXPORT void
+run_next_cb (GtkButton *button, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ /* saver_preferences *p = &s->prefs; */
+ Bool ops = s->preview_suppressed_p;
+
+ GtkWidget *list_widget = name_to_widget (s, "list");
+ int list_elt = selected_list_element (s);
+
+ if (list_elt < 0)
+ list_elt = 0;
+ else
+ list_elt++;
+
+ if (list_elt >= s->list_count)
+ list_elt = 0;
+
+ s->preview_suppressed_p = True;
+
+ flush_dialog_changes_and_save (s);
+ force_list_select_item (s, list_widget, list_elt, True);
+ populate_demo_window (s, list_elt);
+ run_hack (s, list_elt, False);
+
+ s->preview_suppressed_p = ops;
+}
+
+
+G_MODULE_EXPORT void
+run_prev_cb (GtkButton *button, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ /* saver_preferences *p = &s->prefs; */
+ Bool ops = s->preview_suppressed_p;
+
+ GtkWidget *list_widget = name_to_widget (s, "list");
+ int list_elt = selected_list_element (s);
+
+ if (list_elt < 0)
+ list_elt = s->list_count - 1;
+ else
+ list_elt--;
+
+ if (list_elt < 0)
+ list_elt = s->list_count - 1;
+
+ s->preview_suppressed_p = True;
+
+ flush_dialog_changes_and_save (s);
+ force_list_select_item (s, list_widget, list_elt, True);
+ populate_demo_window (s, list_elt);
+ run_hack (s, list_elt, False);
+
+ s->preview_suppressed_p = ops;
+}
+
+
+/* Writes the given settings into prefs.
+ Returns true if there was a change, False otherwise.
+ command and/or visual may be 0, or enabled_p may be -1, meaning "no change".
+ */
+static Bool
+flush_changes (state *s,
+ int list_elt,
+ int enabled_p,
+ const char *command,
+ const char *visual)
+{
+ saver_preferences *p = &s->prefs;
+ Bool changed = False;
+ screenhack *hack;
+ int hack_number;
+ if (list_elt < 0 || list_elt >= s->list_count)
+ abort();
+
+ hack_number = s->list_elt_to_hack_number[list_elt];
+ hack = p->screenhacks[hack_number];
+
+ if (enabled_p != -1 &&
+ enabled_p != hack->enabled_p)
+ {
+ hack->enabled_p = enabled_p;
+ changed = True;
+ if (s->debug_p)
+ fprintf (stderr, "%s: \"%s\": enabled => %d\n",
+ blurb(), hack->name, enabled_p);
+ }
+
+ if (command)
+ {
+ if (!hack->command || !!strcmp (command, hack->command))
+ {
+ if (hack->command) free (hack->command);
+ hack->command = strdup (command);
+ changed = True;
+ if (s->debug_p)
+ fprintf (stderr, "%s: \"%s\": command => \"%s\"\n",
+ blurb(), hack->name, command);
+ }
+ }
+
+ if (visual)
+ {
+ const char *ov = hack->visual;
+ if (!ov || !*ov) ov = "any";
+ if (!*visual) visual = "any";
+ if (!!strcasecmp (visual, ov))
+ {
+ if (hack->visual) free (hack->visual);
+ hack->visual = strdup (visual);
+ changed = True;
+ if (s->debug_p)
+ fprintf (stderr, "%s: \"%s\": visual => \"%s\"\n",
+ blurb(), hack->name, visual);
+ }
+ }
+
+ return changed;
+}
+
+
+/* Helper for the text fields that contain time specifications:
+ this parses the text, and does error checking.
+ */
+static void
+hack_time_text (state *s, const char *line, Time *store, Bool sec_p)
+{
+ if (*line)
+ {
+ int value;
+ if (!sec_p || strchr (line, ':'))
+ value = parse_time ((char *) line, sec_p, True);
+ else
+ {
+ char c;
+ if (sscanf (line, "%d%c", &value, &c) != 1)
+ value = -1;
+ if (!sec_p)
+ value *= 60;
+ }
+
+ value *= 1000; /* Time measures in microseconds */
+ if (value < 0)
+ {
+ char b[255];
+ sprintf (b,
+ _("Error:\n\n"
+ "Unparsable time format: \"%s\"\n"),
+ line);
+ warning_dialog (s->toplevel_widget, b, D_NONE, 100);
+ }
+ else
+ *store = value;
+ }
+}
+
+
+static Bool
+directory_p (const char *path)
+{
+ struct stat st;
+ if (!path || !*path)
+ return False;
+ else if (stat (path, &st))
+ return False;
+ else if (!S_ISDIR (st.st_mode))
+ return False;
+ else
+ return True;
+}
+
+static Bool
+file_p (const char *path)
+{
+ struct stat st;
+ if (!path || !*path)
+ return False;
+ else if (stat (path, &st))
+ return False;
+ else if (S_ISDIR (st.st_mode))
+ return False;
+ else
+ return True;
+}
+
+static char *
+normalize_directory (const char *path)
+{
+ int L;
+ char *p2, *s;
+ if (!path || !*path) return 0;
+ L = strlen (path);
+ p2 = (char *) malloc (L + 2);
+ strcpy (p2, path);
+ if (p2[L-1] == '/') /* remove trailing slash */
+ p2[--L] = 0;
+
+ for (s = p2; s && *s; s++)
+ {
+ if (*s == '/' &&
+ (!strncmp (s, "/../", 4) || /* delete "XYZ/../" */
+ !strncmp (s, "/..\000", 4))) /* delete "XYZ/..$" */
+ {
+ char *s0 = s;
+ while (s0 > p2 && s0[-1] != '/')
+ s0--;
+ if (s0 > p2)
+ {
+ s0--;
+ s += 3;
+ /* strcpy (s0, s); */
+ memmove(s0, s, strlen(s) + 1);
+ s = s0-1;
+ }
+ }
+ else if (*s == '/' && !strncmp (s, "/./", 3)) { /* delete "/./" */
+ /* strcpy (s, s+2), s--; */
+ memmove(s, s+2, strlen(s+2) + 1);
+ s--;
+ }
+ else if (*s == '/' && !strncmp (s, "/.\000", 3)) /* delete "/.$" */
+ *s = 0, s--;
+ }
+
+ /*
+ Normalize consecutive slashes.
+ Ignore doubled slashes after ":" to avoid mangling URLs.
+ */
+
+ for (s = p2; s && *s; s++){
+ if (*s == ':') continue;
+ if (!s[1] || !s[2]) continue;
+ while (s[1] == '/' && s[2] == '/')
+ /* strcpy (s+1, s+2); */
+ memmove (s+1, s+2, strlen(s+2) + 1);
+ }
+
+ /* and strip trailing whitespace for good measure. */
+ L = strlen(p2);
+ while (isspace(p2[L-1]))
+ p2[--L] = 0;
+
+ return p2;
+}
+
+
+#ifdef HAVE_GTK2
+
+typedef struct {
+ state *s;
+ int i;
+ Bool *changed;
+} FlushForeachClosure;
+
+static gboolean
+flush_checkbox (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ FlushForeachClosure *closure = data;
+ gboolean checked;
+
+ gtk_tree_model_get (model, iter,
+ COL_ENABLED, &checked,
+ -1);
+
+ if (flush_changes (closure->s, closure->i,
+ checked, 0, 0))
+ *closure->changed = True;
+
+ closure->i++;
+
+ /* don't remove row */
+ return FALSE;
+}
+
+#endif /* HAVE_GTK2 */
+
+/* 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.)
+ */
+static Bool
+flush_dialog_changes_and_save (state *s)
+{
+ saver_preferences *p = &s->prefs;
+ saver_preferences P2, *p2 = &P2;
+#ifdef HAVE_GTK2
+ GtkTreeView *list_widget = GTK_TREE_VIEW (name_to_widget (s, "list"));
+ GtkTreeModel *model = gtk_tree_view_get_model (list_widget);
+ FlushForeachClosure closure;
+#else /* !HAVE_GTK2 */
+ GtkList *list_widget = GTK_LIST (name_to_widget (s, "list"));
+ GList *kids = gtk_container_children (GTK_CONTAINER (list_widget));
+ int i;
+#endif /* !HAVE_GTK2 */
+ static Bool already_warned_about_missing_image_directory = False; /* very long name... */
+
+ Bool changed = False;
+ GtkWidget *w;
+
+ if (s->saving_p) return False;
+ s->saving_p = True;
+
+ *p2 = *p;
+
+ /* Flush any checkbox changes in the list down into the prefs struct.
+ */
+#ifdef HAVE_GTK2
+ closure.s = s;
+ closure.changed = &changed;
+ closure.i = 0;
+ gtk_tree_model_foreach (model, flush_checkbox, &closure);
+
+#else /* !HAVE_GTK2 */
+
+ for (i = 0; kids; kids = kids->next, i++)
+ {
+ GtkWidget *line = GTK_WIDGET (kids->data);
+ GtkWidget *line_hbox = GTK_WIDGET (GTK_BIN (line)->child);
+ GtkWidget *line_check =
+ GTK_WIDGET (gtk_container_children (GTK_CONTAINER (line_hbox))->data);
+ Bool checked =
+ gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (line_check));
+
+ if (flush_changes (s, i, (checked ? 1 : 0), 0, 0))
+ changed = True;
+ }
+#endif /* ~HAVE_GTK2 */
+
+ /* Flush the non-hack-specific settings down into the prefs struct.
+ */
+
+# define SECONDS(FIELD,NAME) \
+ w = name_to_widget (s, (NAME)); \
+ hack_time_text (s, gtk_entry_get_text (GTK_ENTRY (w)), (FIELD), True)
+
+# define MINUTES(FIELD,NAME) \
+ w = name_to_widget (s, (NAME)); \
+ hack_time_text (s, gtk_entry_get_text (GTK_ENTRY (w)), (FIELD), False)
+
+# define CHECKBOX(FIELD,NAME) \
+ w = name_to_widget (s, (NAME)); \
+ (FIELD) = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (w))
+
+# define PATHNAME(FIELD,NAME) \
+ w = name_to_widget (s, (NAME)); \
+ (FIELD) = normalize_directory (gtk_entry_get_text (GTK_ENTRY (w)))
+
+# define TEXT(FIELD,NAME) \
+ w = name_to_widget (s, (NAME)); \
+ (FIELD) = (char *) g_strdup(gtk_entry_get_text (GTK_ENTRY (w)))
+
+ MINUTES (&p2->timeout, "timeout_spinbutton");
+ MINUTES (&p2->cycle, "cycle_spinbutton");
+ CHECKBOX (p2->lock_p, "lock_button");
+ MINUTES (&p2->lock_timeout, "lock_spinbutton");
+
+ CHECKBOX (p2->dpms_enabled_p, "dpms_button");
+ CHECKBOX (p2->dpms_quickoff_p, "dpms_quickoff_button");
+ MINUTES (&p2->dpms_standby, "dpms_standby_spinbutton");
+ MINUTES (&p2->dpms_suspend, "dpms_suspend_spinbutton");
+ MINUTES (&p2->dpms_off, "dpms_off_spinbutton");
+
+ CHECKBOX (p2->grab_desktop_p, "grab_desk_button");
+ CHECKBOX (p2->grab_video_p, "grab_video_button");
+ CHECKBOX (p2->random_image_p, "grab_image_button");
+ PATHNAME (p2->image_directory, "image_text");
+
+#if 0
+ CHECKBOX (p2->verbose_p, "verbose_button");
+ CHECKBOX (p2->capture_stderr_p, "capture_button");
+ CHECKBOX (p2->splash_p, "splash_button");
+#endif
+
+ {
+ Bool v = False;
+ CHECKBOX (v, "text_host_radio"); if (v) p2->tmode = TEXT_DATE;
+ CHECKBOX (v, "text_radio"); if (v) p2->tmode = TEXT_LITERAL;
+ CHECKBOX (v, "text_file_radio"); if (v) p2->tmode = TEXT_FILE;
+ CHECKBOX (v, "text_program_radio"); if (v) p2->tmode = TEXT_PROGRAM;
+ CHECKBOX (v, "text_url_radio"); if (v) p2->tmode = TEXT_URL;
+ TEXT (p2->text_literal, "text_entry");
+ PATHNAME (p2->text_file, "text_file_entry");
+ PATHNAME (p2->text_program, "text_program_entry");
+ PATHNAME (p2->text_program, "text_program_entry");
+ 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");
+
+# undef SECONDS
+# undef MINUTES
+# undef CHECKBOX
+# undef PATHNAME
+# undef TEXT
+
+ /* 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://
+ */
+ if (p2->image_directory &&
+ *p2->image_directory &&
+ !directory_p (p2->image_directory) &&
+ strncmp(p2->image_directory, "http://", 6) &&
+ ( !already_warned_about_missing_image_directory ||
+ ( p->image_directory &&
+ *p->image_directory &&
+ strcmp(p->image_directory, p2->image_directory)
+ )
+ )
+ )
+ {
+ char b[255];
+ sprintf (b, "Warning:\n\n" "Directory does not exist: \"%s\"\n",
+ p2->image_directory);
+ if (warning_dialog (s->toplevel_widget, b, D_NONE, 100))
+ already_warned_about_missing_image_directory = True;
+ }
+
+
+ /* 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);
+ if (menu_elt < 0 || menu_elt >= countof(mode_menu_order)) abort();
+ p2->mode = mode_menu_order[menu_elt];
+ }
+
+ if (p2->mode == ONE_HACK)
+ {
+ int list_elt = selected_list_element (s);
+ p2->selected_hack = (list_elt >= 0
+ ? s->list_elt_to_hack_number[list_elt]
+ : -1);
+ }
+
+# 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); \
+ } \
+ p->field = p2->field
+
+ COPY(mode, "mode");
+ COPY(selected_hack, "selected_hack");
+
+ COPY(timeout, "timeout");
+ COPY(cycle, "cycle");
+ COPY(lock_p, "lock_p");
+ COPY(lock_timeout, "lock_timeout");
+
+ COPY(dpms_enabled_p, "dpms_enabled_p");
+ COPY(dpms_quickoff_p, "dpms_quickoff_enabled_p");
+ COPY(dpms_standby, "dpms_standby");
+ COPY(dpms_suspend, "dpms_suspend");
+ COPY(dpms_off, "dpms_off");
+
+#if 0
+ COPY(verbose_p, "verbose_p");
+ COPY(capture_stderr_p, "capture_stderr_p");
+ COPY(splash_p, "splash_p");
+#endif
+
+ COPY(tmode, "tmode");
+
+ COPY(install_cmap_p, "install_cmap_p");
+ COPY(fade_p, "fade_p");
+ COPY(unfade_p, "unfade_p");
+ COPY(fade_seconds, "fade_seconds");
+
+ COPY(grab_desktop_p, "grab_desktop_p");
+ COPY(grab_video_p, "grab_video_p");
+ COPY(random_image_p, "random_image_p");
+
+# undef COPY
+
+# define COPYSTR(FIELD,NAME) \
+ if (!p->FIELD || \
+ !p2->FIELD || \
+ strcmp(p->FIELD, p2->FIELD)) \
+ { \
+ changed = True; \
+ if (s->debug_p) \
+ fprintf (stderr, "%s: %s => \"%s\"\n", blurb(), NAME, p2->FIELD); \
+ } \
+ if (p->FIELD && p->FIELD != p2->FIELD) \
+ free (p->FIELD); \
+ p->FIELD = p2->FIELD; \
+ p2->FIELD = 0
+
+ COPYSTR(image_directory, "image_directory");
+ COPYSTR(text_literal, "text_literal");
+ COPYSTR(text_file, "text_file");
+ COPYSTR(text_program, "text_program");
+ COPYSTR(text_url, "text_url");
+# undef COPYSTR
+
+ populate_prefs_page (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);
+
+ changed = demo_write_init_file (s, p);
+ }
+
+ s->saving_p = False;
+ return changed;
+}
+
+
+/* Flush out any changes made in the popup dialog box (where changes
+ take place only when the OK button is clicked.)
+ */
+static Bool
+flush_popup_changes_and_save (state *s)
+{
+ Bool changed = False;
+ saver_preferences *p = &s->prefs;
+ 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"));
+
+ const char *visual = gtk_entry_get_text (GTK_ENTRY (GTK_COMBO (vis)->entry));
+ const char *command = gtk_entry_get_text (cmd);
+
+ char c;
+ unsigned long id;
+
+ if (s->saving_p) return False;
+ s->saving_p = True;
+
+ if (list_elt < 0)
+ goto DONE;
+
+ if (maybe_reload_init_file (s) != 0)
+ {
+ changed = True;
+ goto DONE;
+ }
+
+ /* Sanity-check and canonicalize whatever the user typed into the combo box.
+ */
+ if (!strcasecmp (visual, "")) visual = "";
+ else if (!strcasecmp (visual, "any")) visual = "";
+ else if (!strcasecmp (visual, "default")) visual = "Default";
+ else if (!strcasecmp (visual, "default-n")) visual = "Default-N";
+ else if (!strcasecmp (visual, "default-i")) visual = "Default-I";
+ else if (!strcasecmp (visual, "best")) visual = "Best";
+ else if (!strcasecmp (visual, "mono")) visual = "Mono";
+ else if (!strcasecmp (visual, "monochrome")) visual = "Mono";
+ else if (!strcasecmp (visual, "gray")) visual = "Gray";
+ else if (!strcasecmp (visual, "grey")) visual = "Gray";
+ else if (!strcasecmp (visual, "color")) visual = "Color";
+ else if (!strcasecmp (visual, "gl")) visual = "GL";
+ else if (!strcasecmp (visual, "staticgray")) visual = "StaticGray";
+ else if (!strcasecmp (visual, "staticcolor")) visual = "StaticColor";
+ else if (!strcasecmp (visual, "truecolor")) visual = "TrueColor";
+ else if (!strcasecmp (visual, "grayscale")) visual = "GrayScale";
+ else if (!strcasecmp (visual, "greyscale")) visual = "GrayScale";
+ else if (!strcasecmp (visual, "pseudocolor")) visual = "PseudoColor";
+ else if (!strcasecmp (visual, "directcolor")) visual = "DirectColor";
+ else if (1 == sscanf (visual, " %lu %c", &id, &c)) ;
+ else if (1 == sscanf (visual, " 0x%lx %c", &id, &c)) ;
+ else
+ {
+ gdk_beep (); /* unparsable */
+ visual = "";
+ gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry), _("Any"));
+ }
+
+ changed = flush_changes (s, list_elt, -1, command, visual);
+ if (changed)
+ {
+ changed = demo_write_init_file (s, p);
+
+ /* Do this to re-launch the hack if (and only if) the command line
+ has changed. */
+ populate_demo_window (s, selected_list_element (s));
+ }
+
+ DONE:
+ s->saving_p = False;
+ return changed;
+}
+
+
+G_MODULE_EXPORT void
+pref_changed_cb (GtkWidget *widget, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ if (! s->initializing_p)
+ {
+ s->initializing_p = True;
+ flush_dialog_changes_and_save (s);
+ s->initializing_p = False;
+ }
+}
+
+G_MODULE_EXPORT gboolean
+pref_changed_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data)
+{
+ pref_changed_cb (widget, user_data);
+ return FALSE;
+}
+
+/* Callback on menu items in the "mode" options menu.
+ */
+G_MODULE_EXPORT void
+mode_menu_item_cb (GtkWidget *widget, gpointer user_data)
+{
+ state *s = (state *) user_data;
+ saver_preferences *p = &s->prefs;
+ 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];
+
+ /* Keep the same list element displayed as before; except if we're
+ switching *to* "one screensaver" mode from any other mode, set
+ "the one" to be that which is currently selected.
+ */
+ list_elt = selected_list_element (s);
+ if (new_mode == ONE_HACK)
+ p->selected_hack = s->list_elt_to_hack_number[list_elt];
+
+ {
+ saver_mode old_mode = p->mode;
+ p->mode = new_mode;
+ populate_demo_window (s, list_elt);
+ force_list_select_item (s, list, list_elt, True);
+ p->mode = old_mode; /* put it back, so the init file gets written */
+ }
+
+ pref_changed_cb (widget, user_data);
+}
+
+
+G_MODULE_EXPORT void
+switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page,
+ gint page_num, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ pref_changed_cb (GTK_WIDGET (notebook), user_data);
+
+ /* If we're switching to page 0, schedule the current hack to be run.
+ Otherwise, schedule it to stop. */
+ if (page_num == 0)
+ populate_demo_window (s, selected_list_element (s));
+ else
+ schedule_preview (s, 0);
+}
+
+#ifdef HAVE_GTK2
+static void
+list_activated_cb (GtkTreeView *list,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ gpointer data)
+{
+ state *s = data;
+ char *str;
+ int list_elt;
+
+ g_return_if_fail (!gdk_pointer_is_grabbed ());
+
+ str = gtk_tree_path_to_string (path);
+ list_elt = strtol (str, NULL, 10);
+ g_free (str);
+
+ if (list_elt >= 0)
+ run_hack (s, list_elt, True);
+}
+
+static void
+list_select_changed_cb (GtkTreeSelection *selection, gpointer data)
+{
+ state *s = (state *)data;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ char *str;
+ int list_elt;
+
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+ return;
+
+ path = gtk_tree_model_get_path (model, &iter);
+ str = gtk_tree_path_to_string (path);
+ list_elt = strtol (str, NULL, 10);
+
+ gtk_tree_path_free (path);
+ g_free (str);
+
+ populate_demo_window (s, list_elt);
+ flush_dialog_changes_and_save (s);
+
+ /* Re-populate the Settings window any time a new item is selected
+ in the list, in case both windows are currently visible.
+ */
+ populate_popup_window (s);
+}
+
+#else /* !HAVE_GTK2 */
+
+static time_t last_doubleclick_time = 0; /* FMH! This is to suppress the
+ list_select_cb that comes in
+ *after* we've double-clicked.
+ */
+
+static gint
+list_doubleclick_cb (GtkWidget *button, GdkEventButton *event,
+ gpointer data)
+{
+ state *s = (state *) data;
+ if (event->type == GDK_2BUTTON_PRESS)
+ {
+ GtkList *list = GTK_LIST (name_to_widget (s, "list"));
+ int list_elt = gtk_list_child_position (list, GTK_WIDGET (button));
+
+ last_doubleclick_time = time ((time_t *) 0);
+
+ if (list_elt >= 0)
+ run_hack (s, list_elt, True);
+ }
+
+ return FALSE;
+}
+
+
+static void
+list_select_cb (GtkList *list, GtkWidget *child, gpointer data)
+{
+ state *s = (state *) data;
+ time_t now = time ((time_t *) 0);
+
+ if (now >= last_doubleclick_time + 2)
+ {
+ int list_elt = gtk_list_child_position (list, GTK_WIDGET (child));
+ populate_demo_window (s, list_elt);
+ flush_dialog_changes_and_save (s);
+ }
+}
+
+static void
+list_unselect_cb (GtkList *list, GtkWidget *child, gpointer data)
+{
+ state *s = (state *) data;
+ populate_demo_window (s, -1);
+ flush_dialog_changes_and_save (s);
+}
+
+#endif /* !HAVE_GTK2 */
+
+
+/* Called when the checkboxes that are in the left column of the
+ scrolling list are clicked. This both populates the right pane
+ (just as clicking on the label (really, listitem) does) and
+ also syncs this checkbox with the right pane Enabled checkbox.
+ */
+static void
+list_checkbox_cb (
+#ifdef HAVE_GTK2
+ GtkCellRendererToggle *toggle,
+ gchar *path_string,
+#else /* !HAVE_GTK2 */
+ GtkWidget *cb,
+#endif /* !HAVE_GTK2 */
+ gpointer data)
+{
+ state *s = (state *) data;
+
+#ifdef HAVE_GTK2
+ GtkScrolledWindow *scroller =
+ GTK_SCROLLED_WINDOW (name_to_widget (s, "scroller"));
+ GtkTreeView *list = GTK_TREE_VIEW (name_to_widget (s, "list"));
+ GtkTreeModel *model = gtk_tree_view_get_model (list);
+ GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
+ GtkTreeIter iter;
+ gboolean active;
+#else /* !HAVE_GTK2 */
+ GtkWidget *line_hbox = GTK_WIDGET (cb)->parent;
+ GtkWidget *line = GTK_WIDGET (line_hbox)->parent;
+
+ GtkList *list = GTK_LIST (GTK_WIDGET (line)->parent);
+ GtkViewport *vp = GTK_VIEWPORT (GTK_WIDGET (list)->parent);
+ GtkScrolledWindow *scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent);
+#endif /* !HAVE_GTK2 */
+ GtkAdjustment *adj;
+ double scroll_top;
+
+ int list_elt;
+
+#ifdef HAVE_GTK2
+ if (!gtk_tree_model_get_iter (model, &iter, path))
+ {
+ g_warning ("bad path: %s", path_string);
+ return;
+ }
+ gtk_tree_path_free (path);
+
+ gtk_tree_model_get (model, &iter,
+ COL_ENABLED, &active,
+ -1);
+
+ gtk_list_store_set (GTK_LIST_STORE (model), &iter,
+ COL_ENABLED, !active,
+ -1);
+
+ list_elt = strtol (path_string, NULL, 10);
+#else /* !HAVE_GTK2 */
+ list_elt = gtk_list_child_position (list, line);
+#endif /* !HAVE_GTK2 */
+
+ /* remember previous scroll position of the top of the list */
+ adj = gtk_scrolled_window_get_vadjustment (scroller);
+ scroll_top = GET_ADJ_VALUE (adj);
+
+ flush_dialog_changes_and_save (s);
+ force_list_select_item (s, GTK_WIDGET (list), list_elt, False);
+ populate_demo_window (s, list_elt);
+
+ /* restore the previous scroll position of the top of the list.
+ this is weak, but I don't really know why it's moving... */
+ gtk_adjustment_set_value (adj, scroll_top);
+}
+
+
+typedef struct {
+ state *state;
+ GtkFileSelection *widget;
+} file_selection_data;
+
+
+
+static void
+store_image_directory (GtkWidget *button, gpointer user_data)
+{
+ file_selection_data *fsd = (file_selection_data *) user_data;
+ state *s = fsd->state;
+ GtkFileSelection *selector = fsd->widget;
+ GtkWidget *top = s->toplevel_widget;
+ saver_preferences *p = &s->prefs;
+ const char *path = gtk_file_selection_get_filename (selector);
+
+ if (p->image_directory && !strcmp(p->image_directory, path))
+ return; /* no change */
+
+ /* No warning for URLs. */
+ if ((!directory_p (path)) && strncmp(path, "http://", 6))
+ {
+ char b[255];
+ sprintf (b, _("Error:\n\n" "Directory does not exist: \"%s\"\n"), path);
+ warning_dialog (GTK_WIDGET (top), b, D_NONE, 100);
+ return;
+ }
+
+ if (p->image_directory) free (p->image_directory);
+ p->image_directory = normalize_directory (path);
+
+ gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "image_text")),
+ (p->image_directory ? p->image_directory : ""));
+ demo_write_init_file (s, p);
+}
+
+
+static void
+store_text_file (GtkWidget *button, gpointer user_data)
+{
+ file_selection_data *fsd = (file_selection_data *) user_data;
+ state *s = fsd->state;
+ GtkFileSelection *selector = fsd->widget;
+ GtkWidget *top = s->toplevel_widget;
+ saver_preferences *p = &s->prefs;
+ const char *path = gtk_file_selection_get_filename (selector);
+
+ if (p->text_file && !strcmp(p->text_file, path))
+ return; /* no change */
+
+ if (!file_p (path))
+ {
+ char b[255];
+ sprintf (b, _("Error:\n\n" "File does not exist: \"%s\"\n"), path);
+ warning_dialog (GTK_WIDGET (top), b, D_NONE, 100);
+ return;
+ }
+
+ if (p->text_file) free (p->text_file);
+ p->text_file = normalize_directory (path);
+
+ gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_file_entry")),
+ (p->text_file ? p->text_file : ""));
+ demo_write_init_file (s, p);
+}
+
+
+static void
+store_text_program (GtkWidget *button, gpointer user_data)
+{
+ file_selection_data *fsd = (file_selection_data *) user_data;
+ state *s = fsd->state;
+ GtkFileSelection *selector = fsd->widget;
+ /*GtkWidget *top = s->toplevel_widget;*/
+ saver_preferences *p = &s->prefs;
+ const char *path = gtk_file_selection_get_filename (selector);
+
+ if (p->text_program && !strcmp(p->text_program, path))
+ return; /* no change */
+
+# if 0
+ if (!file_p (path))
+ {
+ char b[255];
+ sprintf (b, _("Error:\n\n" "File does not exist: \"%s\"\n"), path);
+ warning_dialog (GTK_WIDGET (top), b, D_NONE, 100);
+ return;
+ }
+# endif
+
+ if (p->text_program) free (p->text_program);
+ p->text_program = normalize_directory (path);
+
+ gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_program_entry")),
+ (p->text_program ? p->text_program : ""));
+ demo_write_init_file (s, p);
+}
+
+
+
+static void
+browse_image_dir_cancel (GtkWidget *button, gpointer user_data)
+{
+ file_selection_data *fsd = (file_selection_data *) user_data;
+ gtk_widget_hide (GTK_WIDGET (fsd->widget));
+}
+
+static void
+browse_image_dir_ok (GtkWidget *button, gpointer user_data)
+{
+ browse_image_dir_cancel (button, user_data);
+ store_image_directory (button, user_data);
+}
+
+static void
+browse_text_file_ok (GtkWidget *button, gpointer user_data)
+{
+ browse_image_dir_cancel (button, user_data);
+ store_text_file (button, user_data);
+}
+
+static void
+browse_text_program_ok (GtkWidget *button, gpointer user_data)
+{
+ browse_image_dir_cancel (button, user_data);
+ store_text_program (button, user_data);
+}
+
+static void
+browse_image_dir_close (GtkWidget *widget, GdkEvent *event, gpointer user_data)
+{
+ browse_image_dir_cancel (widget, user_data);
+}
+
+
+G_MODULE_EXPORT void
+browse_image_dir_cb (GtkButton *button, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ saver_preferences *p = &s->prefs;
+ static file_selection_data *fsd = 0;
+
+ GtkFileSelection *selector = GTK_FILE_SELECTION(
+ gtk_file_selection_new ("Please select the image directory."));
+
+ if (!fsd)
+ fsd = (file_selection_data *) malloc (sizeof (*fsd));
+
+ fsd->widget = selector;
+ fsd->state = s;
+
+ if (p->image_directory && *p->image_directory)
+ gtk_file_selection_set_filename (selector, p->image_directory);
+
+ gtk_signal_connect (GTK_OBJECT (selector->ok_button),
+ "clicked", GTK_SIGNAL_FUNC (browse_image_dir_ok),
+ (gpointer *) fsd);
+ gtk_signal_connect (GTK_OBJECT (selector->cancel_button),
+ "clicked", GTK_SIGNAL_FUNC (browse_image_dir_cancel),
+ (gpointer *) fsd);
+ gtk_signal_connect (GTK_OBJECT (selector), "delete_event",
+ GTK_SIGNAL_FUNC (browse_image_dir_close),
+ (gpointer *) fsd);
+
+ gtk_widget_set_sensitive (GTK_WIDGET (selector->file_list), False);
+
+ gtk_window_set_modal (GTK_WINDOW (selector), True);
+ gtk_widget_show (GTK_WIDGET (selector));
+}
+
+
+G_MODULE_EXPORT void
+browse_text_file_cb (GtkButton *button, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ saver_preferences *p = &s->prefs;
+ static file_selection_data *fsd = 0;
+
+ GtkFileSelection *selector = GTK_FILE_SELECTION(
+ gtk_file_selection_new ("Please select a text file."));
+
+ if (!fsd)
+ fsd = (file_selection_data *) malloc (sizeof (*fsd));
+
+ fsd->widget = selector;
+ fsd->state = s;
+
+ if (p->text_file && *p->text_file)
+ gtk_file_selection_set_filename (selector, p->text_file);
+
+ gtk_signal_connect (GTK_OBJECT (selector->ok_button),
+ "clicked", GTK_SIGNAL_FUNC (browse_text_file_ok),
+ (gpointer *) fsd);
+ gtk_signal_connect (GTK_OBJECT (selector->cancel_button),
+ "clicked", GTK_SIGNAL_FUNC (browse_image_dir_cancel),
+ (gpointer *) fsd);
+ gtk_signal_connect (GTK_OBJECT (selector), "delete_event",
+ GTK_SIGNAL_FUNC (browse_image_dir_close),
+ (gpointer *) fsd);
+
+ gtk_window_set_modal (GTK_WINDOW (selector), True);
+ gtk_widget_show (GTK_WIDGET (selector));
+}
+
+
+G_MODULE_EXPORT void
+browse_text_program_cb (GtkButton *button, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ saver_preferences *p = &s->prefs;
+ static file_selection_data *fsd = 0;
+
+ GtkFileSelection *selector = GTK_FILE_SELECTION(
+ gtk_file_selection_new ("Please select a text-generating program."));
+
+ if (!fsd)
+ fsd = (file_selection_data *) malloc (sizeof (*fsd));
+
+ fsd->widget = selector;
+ fsd->state = s;
+
+ if (p->text_program && *p->text_program)
+ gtk_file_selection_set_filename (selector, p->text_program);
+
+ gtk_signal_connect (GTK_OBJECT (selector->ok_button),
+ "clicked", GTK_SIGNAL_FUNC (browse_text_program_ok),
+ (gpointer *) fsd);
+ gtk_signal_connect (GTK_OBJECT (selector->cancel_button),
+ "clicked", GTK_SIGNAL_FUNC (browse_image_dir_cancel),
+ (gpointer *) fsd);
+ gtk_signal_connect (GTK_OBJECT (selector), "delete_event",
+ GTK_SIGNAL_FUNC (browse_image_dir_close),
+ (gpointer *) fsd);
+
+ gtk_window_set_modal (GTK_WINDOW (selector), True);
+ gtk_widget_show (GTK_WIDGET (selector));
+}
+
+
+
+
+
+G_MODULE_EXPORT void
+settings_cb (GtkButton *button, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ int list_elt = selected_list_element (s);
+
+ populate_demo_window (s, list_elt); /* reset the widget */
+ populate_popup_window (s); /* create UI on popup window */
+ gtk_widget_show (s->popup_widget);
+}
+
+static void
+settings_sync_cmd_text (state *s)
+{
+# ifdef HAVE_XML
+ GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text"));
+ char *cmd_line = get_configurator_command_line (s->cdata, False);
+ gtk_entry_set_text (GTK_ENTRY (cmd), cmd_line);
+ gtk_entry_set_position (GTK_ENTRY (cmd), strlen (cmd_line));
+ free (cmd_line);
+# endif /* HAVE_XML */
+}
+
+G_MODULE_EXPORT void
+settings_adv_cb (GtkButton *button, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ GtkNotebook *notebook =
+ GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
+
+ settings_sync_cmd_text (s);
+ gtk_notebook_set_page (notebook, 1);
+}
+
+G_MODULE_EXPORT void
+settings_std_cb (GtkButton *button, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ GtkNotebook *notebook =
+ GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
+
+ /* Re-create UI to reflect the in-progress command-line settings. */
+ populate_popup_window (s);
+
+ gtk_notebook_set_page (notebook, 0);
+}
+
+G_MODULE_EXPORT void
+settings_reset_cb (GtkButton *button, gpointer user_data)
+{
+# ifdef HAVE_XML
+ state *s = global_state_kludge; /* I hate C so much... */
+ GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text"));
+ char *cmd_line = get_configurator_command_line (s->cdata, True);
+ gtk_entry_set_text (GTK_ENTRY (cmd), cmd_line);
+ gtk_entry_set_position (GTK_ENTRY (cmd), strlen (cmd_line));
+ free (cmd_line);
+ populate_popup_window (s);
+# endif /* HAVE_XML */
+}
+
+G_MODULE_EXPORT void
+settings_switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page,
+ gint page_num, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ GtkWidget *adv = name_to_widget (s, "adv_button");
+ GtkWidget *std = name_to_widget (s, "std_button");
+
+ if (page_num == 0)
+ {
+ gtk_widget_show (adv);
+ gtk_widget_hide (std);
+ }
+ else if (page_num == 1)
+ {
+ gtk_widget_hide (adv);
+ gtk_widget_show (std);
+ }
+ else
+ abort();
+}
+
+
+
+G_MODULE_EXPORT void
+settings_cancel_cb (GtkButton *button, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ gtk_widget_hide (s->popup_widget);
+}
+
+G_MODULE_EXPORT void
+settings_ok_cb (GtkButton *button, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ GtkNotebook *notebook = GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
+ int page = gtk_notebook_get_current_page (notebook);
+
+ if (page == 0)
+ /* Regenerate the command-line from the widget contents before saving.
+ But don't do this if we're looking at the command-line page already,
+ or we will blow away what they typed... */
+ settings_sync_cmd_text (s);
+
+ flush_popup_changes_and_save (s);
+ gtk_widget_hide (s->popup_widget);
+}
+
+static gboolean
+wm_popup_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data)
+{
+ state *s = (state *) data;
+ settings_cancel_cb (0, (gpointer) s);
+ return TRUE;
+}
+
+
+
+/* Populating the various widgets
+ */
+
+
+/* Returns the number of the last hack run by the server.
+ */
+static int
+server_current_hack (void)
+{
+ Atom type;
+ int format;
+ unsigned long nitems, bytesafter;
+ unsigned char *dataP = 0;
+ Display *dpy = GDK_DISPLAY();
+ int hack_number = -1;
+
+ if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */
+ XA_SCREENSAVER_STATUS,
+ 0, 3, False, XA_INTEGER,
+ &type, &format, &nitems, &bytesafter,
+ &dataP)
+ == Success
+ && type == XA_INTEGER
+ && nitems >= 3
+ && dataP)
+ {
+ PROP32 *data = (PROP32 *) dataP;
+ hack_number = (int) data[2] - 1;
+ }
+
+ if (dataP) XFree (dataP);
+
+ return hack_number;
+}
+
+
+/* Finds the number of the last hack that was run, and makes that item be
+ selected by default.
+ */
+static void
+scroll_to_current_hack (state *s)
+{
+ saver_preferences *p = &s->prefs;
+ int hack_number = -1;
+
+ if (p->mode == ONE_HACK) /* in "one" mode, use the one */
+ hack_number = p->selected_hack;
+ if (hack_number < 0) /* otherwise, use the last-run */
+ hack_number = server_current_hack ();
+ if (hack_number < 0) /* failing that, last "one mode" */
+ hack_number = p->selected_hack;
+ if (hack_number < 0) /* failing that, newest hack. */
+ {
+ /* We should only get here if the user does not have a .xscreensaver
+ file, and the screen has not been blanked with a hack since X
+ started up: in other words, this is probably a fresh install.
+
+ Instead of just defaulting to hack #0 (in either "programs" or
+ "alphabetical" order) let's try to default to the last runnable
+ hack in the "programs" list: this is probably the hack that was
+ most recently added to the xscreensaver distribution (and so
+ it's probably the currently-coolest one!)
+ */
+ hack_number = p->screenhacks_count-1;
+ while (hack_number > 0 &&
+ ! (s->hacks_available_p[hack_number] &&
+ p->screenhacks[hack_number]->enabled_p))
+ hack_number--;
+ }
+
+ if (hack_number >= 0 && hack_number < p->screenhacks_count)
+ {
+ int list_elt = s->hack_number_to_list_elt[hack_number];
+ GtkWidget *list = name_to_widget (s, "list");
+ force_list_select_item (s, list, list_elt, True);
+ populate_demo_window (s, list_elt);
+ }
+}
+
+
+static void
+populate_hack_list (state *s)
+{
+ Display *dpy = GDK_DISPLAY();
+#ifdef HAVE_GTK2
+ saver_preferences *p = &s->prefs;
+ GtkTreeView *list = GTK_TREE_VIEW (name_to_widget (s, "list"));
+ GtkListStore *model;
+ GtkTreeSelection *selection;
+ GtkCellRenderer *ren;
+ GtkTreeIter iter;
+ int i;
+
+ g_object_get (G_OBJECT (list),
+ "model", &model,
+ NULL);
+ if (!model)
+ {
+ model = gtk_list_store_new (COL_LAST, G_TYPE_BOOLEAN, G_TYPE_STRING);
+ g_object_set (G_OBJECT (list), "model", model, NULL);
+ g_object_unref (model);
+
+ ren = gtk_cell_renderer_toggle_new ();
+ gtk_tree_view_insert_column_with_attributes (list, COL_ENABLED,
+ _("Use"), ren,
+ "active", COL_ENABLED,
+ NULL);
+
+ g_signal_connect (ren, "toggled",
+ G_CALLBACK (list_checkbox_cb),
+ s);
+
+ ren = gtk_cell_renderer_text_new ();
+ gtk_tree_view_insert_column_with_attributes (list, COL_NAME,
+ _("Screen Saver"), ren,
+ "markup", COL_NAME,
+ NULL);
+
+ g_signal_connect_after (list, "row_activated",
+ G_CALLBACK (list_activated_cb),
+ s);
+
+ selection = gtk_tree_view_get_selection (list);
+ g_signal_connect (selection, "changed",
+ G_CALLBACK (list_select_changed_cb),
+ s);
+
+ }
+
+ for (i = 0; i < s->list_count; i++)
+ {
+ int hack_number = s->list_elt_to_hack_number[i];
+ screenhack *hack = (hack_number < 0 ? 0 : p->screenhacks[hack_number]);
+ char *pretty_name;
+ Bool available_p = (hack && s->hacks_available_p [hack_number]);
+
+ if (!hack) continue;
+
+ /* If we're to suppress uninstalled hacks, check $PATH now. */
+ if (p->ignore_uninstalled_p && !available_p)
+ continue;
+
+ pretty_name = (hack->name
+ ? strdup (hack->name)
+ : make_hack_name (dpy, hack->command));
+
+ if (!available_p)
+ {
+ /* Make the text foreground be the color of insensitive widgets
+ (but don't actually make it be insensitive, since we still
+ want to be able to click on it.)
+ */
+ GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (list));
+ GdkColor *fg = &style->fg[GTK_STATE_INSENSITIVE];
+ /* GdkColor *bg = &style->bg[GTK_STATE_INSENSITIVE]; */
+ char *buf = (char *) malloc (strlen (pretty_name) + 100);
+
+ sprintf (buf, "<span foreground=\"#%02X%02X%02X\""
+ /* " background=\"#%02X%02X%02X\"" */
+ ">%s</span>",
+ fg->red >> 8, fg->green >> 8, fg->blue >> 8,
+ /* bg->red >> 8, bg->green >> 8, bg->blue >> 8, */
+ pretty_name);
+ free (pretty_name);
+ pretty_name = buf;
+ }
+
+ gtk_list_store_append (model, &iter);
+ gtk_list_store_set (model, &iter,
+ COL_ENABLED, hack->enabled_p,
+ COL_NAME, pretty_name,
+ -1);
+ free (pretty_name);
+ }
+
+#else /* !HAVE_GTK2 */
+
+ saver_preferences *p = &s->prefs;
+ GtkList *list = GTK_LIST (name_to_widget (s, "list"));
+ int i;
+ for (i = 0; i < s->list_count; i++)
+ {
+ int hack_number = s->list_elt_to_hack_number[i];
+ screenhack *hack = (hack_number < 0 ? 0 : p->screenhacks[hack_number]);
+
+ /* A GtkList must contain only GtkListItems, but those can contain
+ an arbitrary widget. We add an Hbox, and inside that, a Checkbox
+ and a Label. We handle single and double click events on the
+ line itself, for clicking on the text, but the interior checkbox
+ also handles its own events.
+ */
+ GtkWidget *line;
+ GtkWidget *line_hbox;
+ GtkWidget *line_check;
+ GtkWidget *line_label;
+ char *pretty_name;
+ Bool available_p = (hack && s->hacks_available_p [hack_number]);
+
+ if (!hack) continue;
+
+ /* If we're to suppress uninstalled hacks, check $PATH now. */
+ if (p->ignore_uninstalled_p && !available_p)
+ continue;
+
+ pretty_name = (hack->name
+ ? strdup (hack->name)
+ : make_hack_name (hack->command));
+
+ line = gtk_list_item_new ();
+ line_hbox = gtk_hbox_new (FALSE, 0);
+ line_check = gtk_check_button_new ();
+ line_label = gtk_label_new (pretty_name);
+
+ gtk_container_add (GTK_CONTAINER (line), line_hbox);
+ gtk_box_pack_start (GTK_BOX (line_hbox), line_check, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (line_hbox), line_label, FALSE, FALSE, 0);
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (line_check),
+ hack->enabled_p);
+ gtk_label_set_justify (GTK_LABEL (line_label), GTK_JUSTIFY_LEFT);
+
+ gtk_widget_show (line_check);
+ gtk_widget_show (line_label);
+ gtk_widget_show (line_hbox);
+ gtk_widget_show (line);
+
+ free (pretty_name);
+
+ gtk_container_add (GTK_CONTAINER (list), line);
+ gtk_signal_connect (GTK_OBJECT (line), "button_press_event",
+ GTK_SIGNAL_FUNC (list_doubleclick_cb),
+ (gpointer) s);
+
+ gtk_signal_connect (GTK_OBJECT (line_check), "toggled",
+ GTK_SIGNAL_FUNC (list_checkbox_cb),
+ (gpointer) s);
+
+ gtk_widget_show (line);
+
+ if (!available_p)
+ {
+ /* Make the widget be colored like insensitive widgets
+ (but don't actually make it be insensitive, since we
+ still want to be able to click on it.)
+ */
+ GtkRcStyle *rc_style;
+ GdkColor fg, bg;
+
+ gtk_widget_realize (GTK_WIDGET (line_label));
+
+ fg = GTK_WIDGET (line_label)->style->fg[GTK_STATE_INSENSITIVE];
+ bg = GTK_WIDGET (line_label)->style->bg[GTK_STATE_INSENSITIVE];
+
+ rc_style = gtk_rc_style_new ();
+ rc_style->fg[GTK_STATE_NORMAL] = fg;
+ rc_style->bg[GTK_STATE_NORMAL] = bg;
+ rc_style->color_flags[GTK_STATE_NORMAL] |= GTK_RC_FG|GTK_RC_BG;
+
+ gtk_widget_modify_style (GTK_WIDGET (line_label), rc_style);
+ gtk_rc_style_unref (rc_style);
+ }
+ }
+
+ gtk_signal_connect (GTK_OBJECT (list), "select_child",
+ GTK_SIGNAL_FUNC (list_select_cb),
+ (gpointer) s);
+ gtk_signal_connect (GTK_OBJECT (list), "unselect_child",
+ GTK_SIGNAL_FUNC (list_unselect_cb),
+ (gpointer) s);
+#endif /* !HAVE_GTK2 */
+}
+
+static void
+update_list_sensitivity (state *s)
+{
+ saver_preferences *p = &s->prefs;
+ Bool sensitive = (p->mode == RANDOM_HACKS ||
+ p->mode == RANDOM_HACKS_SAME ||
+ p->mode == ONE_HACK);
+ Bool checkable = (p->mode == RANDOM_HACKS ||
+ p->mode == RANDOM_HACKS_SAME);
+ Bool blankable = (p->mode != DONT_BLANK);
+
+#ifndef HAVE_GTK2
+ GtkWidget *head = name_to_widget (s, "col_head_hbox");
+ GtkWidget *use = name_to_widget (s, "use_col_frame");
+#endif /* HAVE_GTK2 */
+ GtkWidget *scroller = name_to_widget (s, "scroller");
+ GtkWidget *buttons = name_to_widget (s, "next_prev_hbox");
+ GtkWidget *blanker = name_to_widget (s, "blanking_table");
+
+#ifdef HAVE_GTK2
+ GtkTreeView *list = GTK_TREE_VIEW (name_to_widget (s, "list"));
+ GtkTreeViewColumn *use = gtk_tree_view_get_column (list, COL_ENABLED);
+#else /* !HAVE_GTK2 */
+ GtkList *list = GTK_LIST (name_to_widget (s, "list"));
+ GList *kids = gtk_container_children (GTK_CONTAINER (list));
+
+ gtk_widget_set_sensitive (GTK_WIDGET (head), sensitive);
+#endif /* !HAVE_GTK2 */
+ gtk_widget_set_sensitive (GTK_WIDGET (scroller), sensitive);
+ gtk_widget_set_sensitive (GTK_WIDGET (buttons), sensitive);
+
+ gtk_widget_set_sensitive (GTK_WIDGET (blanker), blankable);
+
+#ifdef HAVE_GTK2
+ gtk_tree_view_column_set_visible (use, checkable);
+#else /* !HAVE_GTK2 */
+ if (checkable)
+ gtk_widget_show (use); /* the "Use" column header */
+ else
+ gtk_widget_hide (use);
+
+ while (kids)
+ {
+ GtkBin *line = GTK_BIN (kids->data);
+ GtkContainer *line_hbox = GTK_CONTAINER (line->child);
+ GtkWidget *line_check =
+ GTK_WIDGET (gtk_container_children (line_hbox)->data);
+
+ if (checkable)
+ gtk_widget_show (line_check);
+ else
+ gtk_widget_hide (line_check);
+
+ kids = kids->next;
+ }
+#endif /* !HAVE_GTK2 */
+}
+
+
+static void
+populate_prefs_page (state *s)
+{
+ saver_preferences *p = &s->prefs;
+
+ Bool can_lock_p = True;
+
+ /* Disable all the "lock" controls if locking support was not provided
+ at compile-time, or if running on MacOS. */
+# if defined(NO_LOCKING) || defined(__APPLE__)
+ can_lock_p = False;
+# endif
+
+
+ /* If there is only one screen, the mode menu contains
+ "random" but not "random-same".
+ */
+ if (s->nscreens <= 1 && p->mode == RANDOM_HACKS_SAME)
+ p->mode = RANDOM_HACKS;
+
+
+ /* The file supports timeouts of less than a minute, but the GUI does
+ not, so throttle the values to be at least one minute (since "0" is
+ a bad rounding choice...)
+ */
+# define THROTTLE(NAME) if (p->NAME != 0 && p->NAME < 60000) p->NAME = 60000
+ THROTTLE (timeout);
+ THROTTLE (cycle);
+ /* THROTTLE (passwd_timeout); */ /* GUI doesn't set this; leave it alone */
+# undef THROTTLE
+
+# define FMT_MINUTES(NAME,N) \
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (name_to_widget (s, (NAME))), (double)((N) + 59) / (60 * 1000))
+
+# define FMT_SECONDS(NAME,N) \
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (name_to_widget (s, (NAME))), (double)((N) / 1000))
+
+ FMT_MINUTES ("timeout_spinbutton", p->timeout);
+ FMT_MINUTES ("cycle_spinbutton", p->cycle);
+ FMT_MINUTES ("lock_spinbutton", p->lock_timeout);
+ FMT_MINUTES ("dpms_standby_spinbutton", p->dpms_standby);
+ FMT_MINUTES ("dpms_suspend_spinbutton", p->dpms_suspend);
+ FMT_MINUTES ("dpms_off_spinbutton", p->dpms_off);
+ FMT_SECONDS ("fade_spinbutton", p->fade_seconds);
+
+# undef FMT_MINUTES
+# undef FMT_SECONDS
+
+# define TOGGLE_ACTIVE(NAME,ACTIVEP) \
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (name_to_widget (s,(NAME))),\
+ (ACTIVEP))
+
+ 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);
+ TOGGLE_ACTIVE ("dpms_quickoff_button", p->dpms_quickoff_p);
+ 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);
+
+ switch (p->tmode)
+ {
+ case TEXT_LITERAL: TOGGLE_ACTIVE ("text_radio", True); break;
+ case TEXT_FILE: TOGGLE_ACTIVE ("text_file_radio", True); break;
+ case TEXT_PROGRAM: TOGGLE_ACTIVE ("text_program_radio", True); break;
+ case TEXT_URL: TOGGLE_ACTIVE ("text_url_radio", True); break;
+ default: TOGGLE_ACTIVE ("text_host_radio", True); break;
+ }
+
+# undef TOGGLE_ACTIVE
+
+ gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "image_text")),
+ (p->image_directory ? p->image_directory : ""));
+ gtk_widget_set_sensitive (name_to_widget (s, "image_text"),
+ p->random_image_p);
+ gtk_widget_set_sensitive (name_to_widget (s, "image_browse_button"),
+ p->random_image_p);
+
+ gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_entry")),
+ (p->text_literal ? p->text_literal : ""));
+ gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_file_entry")),
+ (p->text_file ? p->text_file : ""));
+ gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_program_entry")),
+ (p->text_program ? p->text_program : ""));
+ gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_url_entry")),
+ (p->text_url ? p->text_url : ""));
+
+ gtk_widget_set_sensitive (name_to_widget (s, "text_entry"),
+ p->tmode == TEXT_LITERAL);
+ gtk_widget_set_sensitive (name_to_widget (s, "text_file_entry"),
+ p->tmode == TEXT_FILE);
+ gtk_widget_set_sensitive (name_to_widget (s, "text_file_browse"),
+ p->tmode == TEXT_FILE);
+ gtk_widget_set_sensitive (name_to_widget (s, "text_program_entry"),
+ p->tmode == TEXT_PROGRAM);
+ gtk_widget_set_sensitive (name_to_widget (s, "text_program_browse"),
+ p->tmode == TEXT_PROGRAM);
+ gtk_widget_set_sensitive (name_to_widget (s, "text_url_entry"),
+ p->tmode == TEXT_URL);
+
+
+ /* Map the `saver_mode' enum to mode menu to values. */
+ {
+ GtkOptionMenu *opt = GTK_OPTION_MENU (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);
+ 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
+ {
+ int op = 0, event = 0, error = 0;
+ if (XQueryExtension (dpy, "DPMS", &op, &event, &error))
+ dpms_supported = True;
+ }
+#endif /* HAVE_DPMS_EXTENSION */
+
+
+# define SENSITIZE(NAME,SENSITIVEP) \
+ gtk_widget_set_sensitive (name_to_widget (s, (NAME)), (SENSITIVEP))
+
+ /* Blanking and Locking
+ */
+ SENSITIZE ("lock_button", can_lock_p);
+ SENSITIZE ("lock_spinbutton", can_lock_p && p->lock_p);
+ SENSITIZE ("lock_mlabel", can_lock_p && p->lock_p);
+
+ /* DPMS
+ */
+ 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);
+ SENSITIZE ("dpms_standby_spinbutton", dpms_supported && p->dpms_enabled_p);
+ SENSITIZE ("dpms_suspend_label", dpms_supported && p->dpms_enabled_p);
+ SENSITIZE ("dpms_suspend_mlabel", dpms_supported && p->dpms_enabled_p);
+ SENSITIZE ("dpms_suspend_spinbutton", dpms_supported && p->dpms_enabled_p);
+ 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);
+
+ /* 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)));
+
+# undef SENSITIZE
+ }
+}
+
+
+static void
+populate_popup_window (state *s)
+{
+ GtkLabel *doc = GTK_LABEL (name_to_widget (s, "doc"));
+ char *doc_string = 0;
+
+ /* #### not in Gtk 1.2
+ gtk_label_set_selectable (doc);
+ */
+
+# ifdef HAVE_XML
+ if (s->cdata)
+ {
+ free_conf_data (s->cdata);
+ s->cdata = 0;
+ }
+
+ {
+ saver_preferences *p = &s->prefs;
+ int list_elt = selected_list_element (s);
+ int hack_number = (list_elt >= 0 && list_elt < s->list_count
+ ? s->list_elt_to_hack_number[list_elt]
+ : -1);
+ screenhack *hack = (hack_number >= 0 ? p->screenhacks[hack_number] : 0);
+ if (hack)
+ {
+ GtkWidget *parent = name_to_widget (s, "settings_vbox");
+ GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text"));
+ const char *cmd_line = gtk_entry_get_text (GTK_ENTRY (cmd));
+ s->cdata = load_configurator (cmd_line, s->debug_p);
+ if (s->cdata && s->cdata->widget)
+ gtk_box_pack_start (GTK_BOX (parent), s->cdata->widget,
+ TRUE, TRUE, 0);
+ }
+ }
+
+ doc_string = (s->cdata
+ ? s->cdata->description
+ : 0);
+# else /* !HAVE_XML */
+ doc_string = _("Descriptions not available: no XML support compiled in.");
+# endif /* !HAVE_XML */
+
+ gtk_label_set_text (doc, (doc_string
+ ? _(doc_string)
+ : _("No description available.")));
+}
+
+
+static void
+sensitize_demo_widgets (state *s, Bool sensitive_p)
+{
+ const char *names[] = { "demo", "settings",
+ "cmd_label", "cmd_text", "manual",
+ "visual", "visual_combo" };
+ int i;
+ for (i = 0; i < countof(names); i++)
+ {
+ GtkWidget *w = name_to_widget (s, names[i]);
+ gtk_widget_set_sensitive (GTK_WIDGET(w), sensitive_p);
+ }
+}
+
+
+static void
+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",
+ /* "demo" */ };
+ int i;
+
+ if (force_p || now > last_checked + 10) /* check every 10 seconds */
+ {
+ running_p = xscreensaver_running_p (s);
+ last_checked = time ((time_t *) 0);
+ }
+
+ for (i = 0; i < countof(names); i++)
+ {
+ GtkWidget *w = name_to_widget (s, names[i]);
+ gtk_widget_set_sensitive (GTK_WIDGET(w), running_p);
+ }
+}
+
+
+/* When the File menu is de-posted after a "Restart Daemon" command,
+ the window underneath doesn't repaint for some reason. I guess this
+ is a bug in exposure handling in GTK or GDK. This works around it.
+ */
+static void
+force_dialog_repaint (state *s)
+{
+#if 1
+ /* Tell GDK to invalidate and repaint the whole window.
+ */
+ GdkWindow *w = GET_WINDOW (s->toplevel_widget);
+ GdkRegion *region = gdk_region_new ();
+ GdkRectangle rect;
+ rect.x = rect.y = 0;
+ rect.width = rect.height = 32767;
+ gdk_region_union_with_rect (region, &rect);
+ gdk_window_invalidate_region (w, region, True);
+ gdk_region_destroy (region);
+ gdk_window_process_updates (w, True);
+#else
+ /* Force the server to send an exposure event by creating and then
+ destroying a window as a child of the top level shell.
+ */
+ Display *dpy = GDK_DISPLAY();
+ Window parent = GDK_WINDOW_XWINDOW (s->toplevel_widget->window);
+ Window w;
+ XWindowAttributes xgwa;
+ XGetWindowAttributes (dpy, parent, &xgwa);
+ w = XCreateSimpleWindow (dpy, parent, 0, 0, xgwa.width, xgwa.height, 0,0,0);
+ XMapRaised (dpy, w);
+ XDestroyWindow (dpy, w);
+ XSync (dpy, False);
+#endif
+}
+
+
+/* Even though we've given these text fields a maximum number of characters,
+ their default size is still about 30 characters wide -- so measure out
+ a string in their font, and resize them to just fit that.
+ */
+static void
+fix_text_entry_sizes (state *s)
+{
+ GtkWidget *w;
+
+# if 0 /* appears no longer necessary with Gtk 1.2.10 */
+ const char * const spinbuttons[] = {
+ "timeout_spinbutton", "cycle_spinbutton", "lock_spinbutton",
+ "dpms_standby_spinbutton", "dpms_suspend_spinbutton",
+ "dpms_off_spinbutton",
+ "-fade_spinbutton" };
+ int i;
+ int width = 0;
+
+ for (i = 0; i < countof(spinbuttons); i++)
+ {
+ const char *n = spinbuttons[i];
+ int cols = 4;
+ while (*n == '-') n++, cols--;
+ w = GTK_WIDGET (name_to_widget (s, n));
+ width = gdk_text_width (w->style->font, "MMMMMMMM", cols);
+ gtk_widget_set_usize (w, width, -2);
+ }
+
+ /* Now fix the width of the combo box.
+ */
+ w = GTK_WIDGET (name_to_widget (s, "visual_combo"));
+ w = GTK_COMBO (w)->entry;
+ width = gdk_string_width (w->style->font, "PseudoColor___");
+ gtk_widget_set_usize (w, width, -2);
+
+ /* Now fix the width of the file entry text.
+ */
+ w = GTK_WIDGET (name_to_widget (s, "image_text"));
+ width = gdk_string_width (w->style->font, "mmmmmmmmmmmmmm");
+ gtk_widget_set_usize (w, width, -2);
+
+ /* Now fix the width of the command line text.
+ */
+ w = GTK_WIDGET (name_to_widget (s, "cmd_text"));
+ width = gdk_string_width (w->style->font, "mmmmmmmmmmmmmmmmmmmm");
+ gtk_widget_set_usize (w, width, -2);
+
+# endif /* 0 */
+
+ /* Now fix the height of the list widget:
+ make it default to being around 10 text-lines high instead of 4.
+ */
+ w = GTK_WIDGET (name_to_widget (s, "list"));
+ {
+ int lines = 10;
+ int height;
+ int leading = 3; /* approximate is ok... */
+ int border = 2;
+
+#ifdef HAVE_GTK2
+ PangoFontMetrics *pain =
+ pango_context_get_metrics (gtk_widget_get_pango_context (w),
+ gtk_widget_get_style (w)->font_desc,
+ gtk_get_default_language ());
+ height = PANGO_PIXELS (pango_font_metrics_get_ascent (pain) +
+ pango_font_metrics_get_descent (pain));
+#else /* !HAVE_GTK2 */
+ height = w->style->font->ascent + w->style->font->descent;
+#endif /* !HAVE_GTK2 */
+
+ height += leading;
+ height *= lines;
+ height += border * 2;
+ w = GTK_WIDGET (name_to_widget (s, "scroller"));
+ gtk_widget_set_usize (w, -2, height);
+ }
+}
+
+
+#ifndef HAVE_GTK2
+
+/* Pixmaps for the up and down arrow buttons (yeah, this is sleazy...)
+ */
+
+static char *up_arrow_xpm[] = {
+ "15 15 4 1",
+ " c None",
+ "- c #FFFFFF",
+ "+ c #D6D6D6",
+ "@ c #000000",
+
+ " @ ",
+ " @ ",
+ " -+@ ",
+ " -+@ ",
+ " -+++@ ",
+ " -+++@ ",
+ " -+++++@ ",
+ " -+++++@ ",
+ " -+++++++@ ",
+ " -+++++++@ ",
+ " -+++++++++@ ",
+ " -+++++++++@ ",
+ " -+++++++++++@ ",
+ " @@@@@@@@@@@@@ ",
+ " ",
+
+ /* Need these here because gdk_pixmap_create_from_xpm_d() walks off
+ the end of the array (Gtk 1.2.5.) */
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+};
+
+static char *down_arrow_xpm[] = {
+ "15 15 4 1",
+ " c None",
+ "- c #FFFFFF",
+ "+ c #D6D6D6",
+ "@ c #000000",
+
+ " ",
+ " ------------- ",
+ " -+++++++++++@ ",
+ " -+++++++++@ ",
+ " -+++++++++@ ",
+ " -+++++++@ ",
+ " -+++++++@ ",
+ " -+++++@ ",
+ " -+++++@ ",
+ " -+++@ ",
+ " -+++@ ",
+ " -+@ ",
+ " -+@ ",
+ " @ ",
+ " @ ",
+
+ /* Need these here because gdk_pixmap_create_from_xpm_d() walks off
+ the end of the array (Gtk 1.2.5.) */
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+};
+
+static void
+pixmapify_button (state *s, int down_p)
+{
+ GdkPixmap *pixmap;
+ GdkBitmap *mask;
+ GtkWidget *pixmapwid;
+ GtkStyle *style;
+ GtkWidget *w;
+
+ w = GTK_WIDGET (name_to_widget (s, (down_p ? "next" : "prev")));
+ style = gtk_widget_get_style (w);
+ mask = 0;
+ pixmap = gdk_pixmap_create_from_xpm_d (w->window, &mask,
+ &style->bg[GTK_STATE_NORMAL],
+ (down_p
+ ? (gchar **) down_arrow_xpm
+ : (gchar **) up_arrow_xpm));
+ pixmapwid = gtk_pixmap_new (pixmap, mask);
+ gtk_widget_show (pixmapwid);
+ gtk_container_remove (GTK_CONTAINER (w), GTK_BIN (w)->child);
+ gtk_container_add (GTK_CONTAINER (w), pixmapwid);
+}
+
+static void
+map_next_button_cb (GtkWidget *w, gpointer user_data)
+{
+ state *s = (state *) user_data;
+ pixmapify_button (s, 1);
+}
+
+static void
+map_prev_button_cb (GtkWidget *w, gpointer user_data)
+{
+ state *s = (state *) user_data;
+ pixmapify_button (s, 0);
+}
+#endif /* !HAVE_GTK2 */
+
+
+#ifndef HAVE_GTK2
+/* Work around a Gtk bug that causes label widgets to wrap text too early.
+ */
+
+static void
+you_are_not_a_unique_or_beautiful_snowflake (GtkWidget *label,
+ GtkAllocation *allocation,
+ void *foo)
+{
+ GtkRequisition req;
+ GtkWidgetAuxInfo *aux_info;
+
+ aux_info = gtk_object_get_data (GTK_OBJECT (label), "gtk-aux-info");
+
+ aux_info->width = allocation->width;
+ aux_info->height = -2;
+ aux_info->x = -1;
+ aux_info->y = -1;
+
+ gtk_widget_size_request (label, &req);
+}
+
+/* Feel the love. Thanks to Nat Friedman for finding this workaround.
+ */
+static void
+eschew_gtk_lossage (GtkLabel *label)
+{
+ GtkWidgetAuxInfo *aux_info = g_new0 (GtkWidgetAuxInfo, 1);
+ aux_info->width = GTK_WIDGET (label)->allocation.width;
+ aux_info->height = -2;
+ aux_info->x = -1;
+ aux_info->y = -1;
+
+ gtk_object_set_data (GTK_OBJECT (label), "gtk-aux-info", aux_info);
+
+ gtk_signal_connect (GTK_OBJECT (label), "size_allocate",
+ GTK_SIGNAL_FUNC (you_are_not_a_unique_or_beautiful_snowflake),
+ 0);
+
+ gtk_widget_set_usize (GTK_WIDGET (label), -2, -2);
+
+ gtk_widget_queue_resize (GTK_WIDGET (label));
+}
+#endif /* !HAVE_GTK2 */
+
+
+static void
+populate_demo_window (state *s, int list_elt)
+{
+ Display *dpy = GDK_DISPLAY();
+ saver_preferences *p = &s->prefs;
+ screenhack *hack;
+ char *pretty_name;
+ 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"));
+ GtkWidget *list = GTK_WIDGET (name_to_widget (s, "list"));
+
+ if (p->mode == BLANK_ONLY)
+ {
+ hack = 0;
+ pretty_name = strdup (_("Blank Screen"));
+ schedule_preview (s, 0);
+ }
+ else if (p->mode == DONT_BLANK)
+ {
+ hack = 0;
+ pretty_name = strdup (_("Screen Saver Disabled"));
+ schedule_preview (s, 0);
+ }
+ else
+ {
+ int hack_number = (list_elt >= 0 && list_elt < s->list_count
+ ? s->list_elt_to_hack_number[list_elt]
+ : -1);
+ hack = (hack_number >= 0 ? p->screenhacks[hack_number] : 0);
+
+ pretty_name = (hack
+ ? (hack->name
+ ? strdup (hack->name)
+ : make_hack_name (dpy, hack->command))
+ : 0);
+
+ if (hack)
+ schedule_preview (s, hack->command);
+ else
+ schedule_preview (s, 0);
+ }
+
+ if (!pretty_name)
+ pretty_name = strdup (_("Preview"));
+
+ gtk_frame_set_label (frame1, _(pretty_name));
+ gtk_frame_set_label (frame2, _(pretty_name));
+
+ gtk_entry_set_text (cmd, (hack ? hack->command : ""));
+ gtk_entry_set_position (cmd, 0);
+
+ {
+ char title[255];
+ sprintf (title, _("%s: %.100s Settings"),
+ progclass, (pretty_name ? pretty_name : "???"));
+ gtk_window_set_title (GTK_WINDOW (s->popup_widget), title);
+ }
+
+ gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry),
+ (hack
+ ? (hack->visual && *hack->visual
+ ? hack->visual
+ : _("Any"))
+ : ""));
+
+ sensitize_demo_widgets (s, (hack ? True : False));
+
+ if (pretty_name) free (pretty_name);
+
+ ensure_selected_item_visible (list);
+
+ s->_selected_list_element = list_elt;
+}
+
+
+static void
+widget_deleter (GtkWidget *widget, gpointer data)
+{
+ /* #### Well, I want to destroy these widgets, but if I do that, they get
+ referenced again, and eventually I get a SEGV. So instead of
+ destroying them, I'll just hide them, and leak a bunch of memory
+ every time the disk file changes. Go go go Gtk!
+
+ #### Ok, that's a lie, I get a crash even if I just hide the widget
+ and don't ever delete it. Fuck!
+ */
+#if 0
+ gtk_widget_destroy (widget);
+#else
+ gtk_widget_hide (widget);
+#endif
+}
+
+
+static char **sort_hack_cmp_names_kludge;
+static int
+sort_hack_cmp (const void *a, const void *b)
+{
+ if (a == b)
+ return 0;
+ else
+ {
+ int aa = *(int *) a;
+ int bb = *(int *) b;
+ const char last[] = "\377\377\377\377\377\377\377\377\377\377\377";
+ return strcmp ((aa < 0 ? last : sort_hack_cmp_names_kludge[aa]),
+ (bb < 0 ? last : sort_hack_cmp_names_kludge[bb]));
+ }
+}
+
+
+static void
+initialize_sort_map (state *s)
+{
+ Display *dpy = GDK_DISPLAY();
+ saver_preferences *p = &s->prefs;
+ int i, j;
+
+ if (s->list_elt_to_hack_number) free (s->list_elt_to_hack_number);
+ if (s->hack_number_to_list_elt) free (s->hack_number_to_list_elt);
+ if (s->hacks_available_p) free (s->hacks_available_p);
+
+ s->list_elt_to_hack_number = (int *)
+ calloc (sizeof(int), p->screenhacks_count + 1);
+ s->hack_number_to_list_elt = (int *)
+ calloc (sizeof(int), p->screenhacks_count + 1);
+ s->hacks_available_p = (Bool *)
+ calloc (sizeof(Bool), p->screenhacks_count + 1);
+ s->total_available = 0;
+
+ /* Check which hacks actually exist on $PATH
+ */
+ for (i = 0; i < p->screenhacks_count; i++)
+ {
+ screenhack *hack = p->screenhacks[i];
+ int on = on_path_p (hack->command) ? 1 : 0;
+ s->hacks_available_p[i] = on;
+ s->total_available += on;
+ }
+
+ /* Initialize list->hack table to unsorted mapping, omitting nonexistent
+ hacks, if desired.
+ */
+ j = 0;
+ for (i = 0; i < p->screenhacks_count; i++)
+ {
+ if (!p->ignore_uninstalled_p ||
+ s->hacks_available_p[i])
+ s->list_elt_to_hack_number[j++] = i;
+ }
+ s->list_count = j;
+
+ for (; j < p->screenhacks_count; j++)
+ s->list_elt_to_hack_number[j] = -1;
+
+
+ /* Generate list of sortable names (once)
+ */
+ sort_hack_cmp_names_kludge = (char **)
+ calloc (sizeof(char *), p->screenhacks_count);
+ for (i = 0; i < p->screenhacks_count; i++)
+ {
+ screenhack *hack = p->screenhacks[i];
+ 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;
+ }
+
+ /* Sort list->hack map alphabetically
+ */
+ qsort (s->list_elt_to_hack_number,
+ p->screenhacks_count,
+ sizeof(*s->list_elt_to_hack_number),
+ sort_hack_cmp);
+
+ /* Free names
+ */
+ for (i = 0; i < p->screenhacks_count; i++)
+ free (sort_hack_cmp_names_kludge[i]);
+ free (sort_hack_cmp_names_kludge);
+ sort_hack_cmp_names_kludge = 0;
+
+ /* Build inverse table */
+ for (i = 0; i < p->screenhacks_count; i++)
+ {
+ int n = s->list_elt_to_hack_number[i];
+ if (n != -1)
+ s->hack_number_to_list_elt[n] = i;
+ }
+}
+
+
+static int
+maybe_reload_init_file (state *s)
+{
+ Display *dpy = GDK_DISPLAY();
+ saver_preferences *p = &s->prefs;
+ int status = 0;
+
+ static Bool reentrant_lock = False;
+ if (reentrant_lock) return 0;
+ reentrant_lock = True;
+
+ if (init_file_changed_p (p))
+ {
+ const char *f = init_file_name();
+ char *b;
+ int list_elt;
+ GtkWidget *list;
+
+ if (!f || !*f) return 0;
+ b = (char *) malloc (strlen(f) + 1024);
+ sprintf (b,
+ _("Warning:\n\n"
+ "file \"%s\" has changed, reloading.\n"),
+ f);
+ warning_dialog (s->toplevel_widget, b, D_NONE, 100);
+ free (b);
+
+ load_init_file (dpy, p);
+ initialize_sort_map (s);
+
+ list_elt = selected_list_element (s);
+ list = name_to_widget (s, "list");
+ gtk_container_foreach (GTK_CONTAINER (list), widget_deleter, NULL);
+ populate_hack_list (s);
+ force_list_select_item (s, list, list_elt, True);
+ populate_prefs_page (s);
+ populate_demo_window (s, list_elt);
+ ensure_selected_item_visible (list);
+
+ status = 1;
+ }
+
+ reentrant_lock = False;
+ return status;
+}
+
+
+
+/* Making the preview window have the right X visual (so that GL works.)
+ */
+
+static Visual *get_best_gl_visual (state *);
+
+static GdkVisual *
+x_visual_to_gdk_visual (Visual *xv)
+{
+ GList *gvs = gdk_list_visuals();
+ if (!xv) return gdk_visual_get_system();
+ for (; gvs; gvs = gvs->next)
+ {
+ GdkVisual *gv = (GdkVisual *) gvs->data;
+ if (xv == GDK_VISUAL_XVISUAL (gv))
+ return gv;
+ }
+ fprintf (stderr, "%s: couldn't convert X Visual 0x%lx to a GdkVisual\n",
+ blurb(), (unsigned long) xv->visualid);
+ abort();
+}
+
+static void
+clear_preview_window (state *s)
+{
+ GtkWidget *p;
+ GdkWindow *window;
+ GtkStyle *style;
+
+ if (!s->toplevel_widget) return; /* very early */
+ p = name_to_widget (s, "preview");
+ window = GET_WINDOW (p);
+
+ if (!window) return;
+
+ /* Flush the widget background down into the window, in case a subproc
+ has changed it. */
+ style = gtk_widget_get_style (p);
+ gdk_window_set_background (window, &style->bg[GTK_STATE_NORMAL]);
+ gdk_window_clear (window);
+
+ {
+ int list_elt = selected_list_element (s);
+ int hack_number = (list_elt >= 0
+ ? s->list_elt_to_hack_number[list_elt]
+ : -1);
+ Bool available_p = (hack_number >= 0
+ ? s->hacks_available_p [hack_number]
+ : True);
+ Bool nothing_p = (s->total_available < 5);
+
+#ifdef HAVE_GTK2
+ GtkWidget *notebook = name_to_widget (s, "preview_notebook");
+ gtk_notebook_set_page (GTK_NOTEBOOK (notebook),
+ (s->running_preview_error_p
+ ? (available_p ? 1 :
+ nothing_p ? 3 : 2)
+ : 0));
+#else /* !HAVE_GTK2 */
+ if (s->running_preview_error_p)
+ {
+ const char * const lines1[] = { N_("No Preview"), N_("Available") };
+ const char * const lines2[] = { N_("Not"), N_("Installed") };
+ int nlines = countof(lines1);
+ int lh = p->style->font->ascent + p->style->font->descent;
+ int y, i;
+ gint w, h;
+
+ const char * const *lines = (available_p ? lines1 : lines2);
+
+ gdk_window_get_size (window, &w, &h);
+ y = (h - (lh * nlines)) / 2;
+ y += p->style->font->ascent;
+ for (i = 0; i < nlines; i++)
+ {
+ int sw = gdk_string_width (p->style->font, _(lines[i]));
+ int x = (w - sw) / 2;
+ gdk_draw_string (window, p->style->font,
+ p->style->fg_gc[GTK_STATE_NORMAL],
+ x, y, _(lines[i]));
+ y += lh;
+ }
+ }
+#endif /* !HAVE_GTK2 */
+ }
+
+ gdk_flush ();
+}
+
+
+static void
+reset_preview_window (state *s)
+{
+ /* On some systems (most recently, MacOS X) OpenGL programs get confused
+ when you kill one and re-start another on the same window. So maybe
+ it's best to just always destroy and recreate the preview window
+ when changing hacks, instead of always trying to reuse the same one?
+ */
+ GtkWidget *pr = name_to_widget (s, "preview");
+ if (GET_REALIZED (pr))
+ {
+ GdkWindow *window = GET_WINDOW (pr);
+ Window oid = (window ? GDK_WINDOW_XWINDOW (window) : 0);
+ Window id;
+ gtk_widget_hide (pr);
+ gtk_widget_unrealize (pr);
+ gtk_widget_realize (pr);
+ gtk_widget_show (pr);
+ id = (window ? GDK_WINDOW_XWINDOW (window) : 0);
+ if (s->debug_p)
+ fprintf (stderr, "%s: window id 0x%X -> 0x%X\n", blurb(),
+ (unsigned int) oid,
+ (unsigned int) id);
+ }
+}
+
+
+static void
+fix_preview_visual (state *s)
+{
+ GtkWidget *widget = name_to_widget (s, "preview");
+ Visual *xvisual = get_best_gl_visual (s);
+ GdkVisual *visual = x_visual_to_gdk_visual (xvisual);
+ GdkVisual *dvisual = gdk_visual_get_system();
+ GdkColormap *cmap = (visual == dvisual
+ ? gdk_colormap_get_system ()
+ : gdk_colormap_new (visual, False));
+
+ if (s->debug_p)
+ fprintf (stderr, "%s: using %s visual 0x%lx\n", blurb(),
+ (visual == dvisual ? "default" : "non-default"),
+ (xvisual ? (unsigned long) xvisual->visualid : 0L));
+
+ if (!GET_REALIZED (widget) ||
+ gtk_widget_get_visual (widget) != visual)
+ {
+ gtk_widget_unrealize (widget);
+ gtk_widget_set_visual (widget, visual);
+ gtk_widget_set_colormap (widget, cmap);
+ gtk_widget_realize (widget);
+ }
+
+ /* Set the Widget colors to be white-on-black. */
+ {
+ GdkWindow *window = GET_WINDOW (widget);
+ GtkStyle *style = gtk_style_copy (gtk_widget_get_style (widget));
+ GdkColormap *cmap = gtk_widget_get_colormap (widget);
+ GdkColor *fg = &style->fg[GTK_STATE_NORMAL];
+ GdkColor *bg = &style->bg[GTK_STATE_NORMAL];
+ GdkGC *fgc = gdk_gc_new(window);
+ GdkGC *bgc = gdk_gc_new(window);
+ if (!gdk_color_white (cmap, fg)) abort();
+ if (!gdk_color_black (cmap, bg)) abort();
+ gdk_gc_set_foreground (fgc, fg);
+ gdk_gc_set_background (fgc, bg);
+ gdk_gc_set_foreground (bgc, bg);
+ gdk_gc_set_background (bgc, fg);
+ style->fg_gc[GTK_STATE_NORMAL] = fgc;
+ style->bg_gc[GTK_STATE_NORMAL] = fgc;
+ gtk_widget_set_style (widget, style);
+
+ /* For debugging purposes, put a title on the window (so that
+ it can be easily found in the output of "xwininfo -tree".)
+ */
+ gdk_window_set_title (window, "Preview");
+ }
+
+ gtk_widget_show (widget);
+}
+
+
+/* Subprocesses
+ */
+
+static char *
+subproc_pretty_name (state *s)
+{
+ if (s->running_preview_cmd)
+ {
+ char *ps = strdup (s->running_preview_cmd);
+ char *ss = strchr (ps, ' ');
+ if (ss) *ss = 0;
+ ss = strrchr (ps, '/');
+ if (!ss)
+ ss = ps;
+ else
+ {
+ ss = strdup (ss+1);
+ free (ps);
+ }
+ return ss;
+ }
+ else
+ return strdup ("???");
+}
+
+
+static void
+reap_zombies (state *s)
+{
+ int wait_status = 0;
+ pid_t pid;
+ while ((pid = waitpid (-1, &wait_status, WNOHANG|WUNTRACED)) > 0)
+ {
+ if (s->debug_p)
+ {
+ if (pid == s->running_preview_pid)
+ {
+ char *ss = subproc_pretty_name (s);
+ fprintf (stderr, "%s: pid %lu (%s) died\n", blurb(),
+ (unsigned long) pid, ss);
+ free (ss);
+ }
+ else
+ fprintf (stderr, "%s: pid %lu died\n", blurb(),
+ (unsigned long) pid);
+ }
+ }
+}
+
+
+/* Mostly lifted from driver/subprocs.c */
+static Visual *
+get_best_gl_visual (state *s)
+{
+ Display *dpy = GDK_DISPLAY();
+ pid_t forked;
+ int fds [2];
+ int in, out;
+ char buf[1024];
+
+ char *av[10];
+ int ac = 0;
+
+ av[ac++] = "xscreensaver-gl-helper";
+ av[ac] = 0;
+
+ if (pipe (fds))
+ {
+ perror ("error creating pipe:");
+ return 0;
+ }
+
+ in = fds [0];
+ out = fds [1];
+
+ switch ((int) (forked = fork ()))
+ {
+ case -1:
+ {
+ sprintf (buf, "%s: couldn't fork", blurb());
+ perror (buf);
+ exit (1);
+ }
+ case 0:
+ {
+ int stdout_fd = 1;
+
+ close (in); /* don't need this one */
+ close (ConnectionNumber (dpy)); /* close display fd */
+
+ if (dup2 (out, stdout_fd) < 0) /* pipe stdout */
+ {
+ perror ("could not dup() a new stdout:");
+ return 0;
+ }
+
+ execvp (av[0], av); /* shouldn't return. */
+
+ if (errno != ENOENT)
+ {
+ /* Ignore "no such file or directory" errors, unless verbose.
+ Issue all other exec errors, though. */
+ sprintf (buf, "%s: running %s", blurb(), av[0]);
+ perror (buf);
+ }
+
+ /* Note that one must use _exit() instead of exit() in procs forked
+ off of Gtk programs -- Gtk installs an atexit handler that has a
+ copy of the X connection (which we've already closed, for safety.)
+ If one uses exit() instead of _exit(), then one sometimes gets a
+ spurious "Gdk-ERROR: Fatal IO error on X server" error message.
+ */
+ _exit (1); /* exits fork */
+ break;
+ }
+ default:
+ {
+ int result = 0;
+ int wait_status = 0;
+
+ FILE *f = fdopen (in, "r");
+ unsigned int v = 0;
+ char c;
+
+ close (out); /* don't need this one */
+
+ *buf = 0;
+ if (!fgets (buf, sizeof(buf)-1, f))
+ *buf = 0;
+ fclose (f);
+
+ /* Wait for the child to die. */
+ waitpid (-1, &wait_status, 0);
+
+ if (1 == sscanf (buf, "0x%x %c", &v, &c))
+ result = (int) v;
+
+ if (result == 0)
+ {
+ if (s->debug_p)
+ fprintf (stderr, "%s: %s did not report a GL visual!\n",
+ blurb(), av[0]);
+ return 0;
+ }
+ else
+ {
+ Visual *v = id_to_visual (DefaultScreenOfDisplay (dpy), result);
+ if (s->debug_p)
+ fprintf (stderr, "%s: %s says the GL visual is 0x%X.\n",
+ blurb(), av[0], result);
+ if (!v) abort();
+ return v;
+ }
+ }
+ }
+
+ abort();
+}
+
+
+static void
+kill_preview_subproc (state *s, Bool reset_p)
+{
+ s->running_preview_error_p = False;
+
+ reap_zombies (s);
+ clear_preview_window (s);
+
+ if (s->subproc_check_timer_id)
+ {
+ gtk_timeout_remove (s->subproc_check_timer_id);
+ s->subproc_check_timer_id = 0;
+ s->subproc_check_countdown = 0;
+ }
+
+ if (s->running_preview_pid)
+ {
+ int status = kill (s->running_preview_pid, SIGTERM);
+ char *ss = subproc_pretty_name (s);
+
+ if (status < 0)
+ {
+ if (errno == ESRCH)
+ {
+ if (s->debug_p)
+ fprintf (stderr, "%s: pid %lu (%s) was already dead.\n",
+ blurb(), (unsigned long) s->running_preview_pid, ss);
+ }
+ else
+ {
+ char buf [1024];
+ sprintf (buf, "%s: couldn't kill pid %lu (%s)",
+ blurb(), (unsigned long) s->running_preview_pid, ss);
+ perror (buf);
+ }
+ }
+ else {
+ int endstatus;
+ waitpid(s->running_preview_pid, &endstatus, 0);
+ if (s->debug_p)
+ fprintf (stderr, "%s: killed pid %lu (%s)\n", blurb(),
+ (unsigned long) s->running_preview_pid, ss);
+ }
+
+ free (ss);
+ s->running_preview_pid = 0;
+ if (s->running_preview_cmd) free (s->running_preview_cmd);
+ s->running_preview_cmd = 0;
+ }
+
+ reap_zombies (s);
+
+ if (reset_p)
+ {
+ reset_preview_window (s);
+ clear_preview_window (s);
+ }
+}
+
+
+/* Immediately and unconditionally launches the given process,
+ after appending the -window-id option; sets running_preview_pid.
+ */
+static void
+launch_preview_subproc (state *s)
+{
+ saver_preferences *p = &s->prefs;
+ Window id;
+ char *new_cmd = 0;
+ pid_t forked;
+ const char *cmd = s->desired_preview_cmd;
+
+ GtkWidget *pr = name_to_widget (s, "preview");
+ GdkWindow *window;
+
+ reset_preview_window (s);
+
+ window = GET_WINDOW (pr);
+
+ s->running_preview_error_p = False;
+
+ if (s->preview_suppressed_p)
+ {
+ kill_preview_subproc (s, False);
+ goto DONE;
+ }
+
+ new_cmd = malloc (strlen (cmd) + 40);
+
+ id = (window ? GDK_WINDOW_XWINDOW (window) : 0);
+ if (id == 0)
+ {
+ /* No window id? No command to run. */
+ free (new_cmd);
+ new_cmd = 0;
+ }
+ else
+ {
+ strcpy (new_cmd, cmd);
+ sprintf (new_cmd + strlen (new_cmd), " -window-id 0x%X",
+ (unsigned int) id);
+ }
+
+ kill_preview_subproc (s, False);
+ if (! new_cmd)
+ {
+ s->running_preview_error_p = True;
+ clear_preview_window (s);
+ goto DONE;
+ }
+
+ switch ((int) (forked = fork ()))
+ {
+ case -1:
+ {
+ char buf[255];
+ sprintf (buf, "%s: couldn't fork", blurb());
+ perror (buf);
+ s->running_preview_error_p = True;
+ goto DONE;
+ break;
+ }
+ case 0:
+ {
+ close (ConnectionNumber (GDK_DISPLAY()));
+
+ hack_subproc_environment (id, s->debug_p);
+
+ usleep (250000); /* pause for 1/4th second before launching, to give
+ the previous program time to die and flush its X
+ buffer, so we don't get leftover turds on the
+ window. */
+
+ exec_command (p->shell, new_cmd, p->nice_inferior);
+ /* Don't bother printing an error message when we are unable to
+ exec subprocesses; we handle that by polling the pid later.
+
+ Note that one must use _exit() instead of exit() in procs forked
+ off of Gtk programs -- Gtk installs an atexit handler that has a
+ copy of the X connection (which we've already closed, for safety.)
+ If one uses exit() instead of _exit(), then one sometimes gets a
+ spurious "Gdk-ERROR: Fatal IO error on X server" error message.
+ */
+ _exit (1); /* exits child fork */
+ break;
+
+ default:
+
+ if (s->running_preview_cmd) free (s->running_preview_cmd);
+ s->running_preview_cmd = strdup (s->desired_preview_cmd);
+ s->running_preview_pid = forked;
+
+ if (s->debug_p)
+ {
+ char *ss = subproc_pretty_name (s);
+ fprintf (stderr, "%s: forked %lu (%s)\n", blurb(),
+ (unsigned long) forked, ss);
+ free (ss);
+ }
+ break;
+ }
+ }
+
+ schedule_preview_check (s);
+
+ DONE:
+ if (new_cmd) free (new_cmd);
+ new_cmd = 0;
+}
+
+
+/* Modify $DISPLAY and $PATH for the benefit of subprocesses.
+ */
+static void
+hack_environment (state *s)
+{
+ static const char *def_path =
+# ifdef DEFAULT_PATH_PREFIX
+ DEFAULT_PATH_PREFIX;
+# else
+ "";
+# endif
+
+ Display *dpy = GDK_DISPLAY();
+ const char *odpy = DisplayString (dpy);
+ char *ndpy = (char *) malloc(strlen(odpy) + 20);
+ strcpy (ndpy, "DISPLAY=");
+ strcat (ndpy, odpy);
+ if (putenv (ndpy))
+ abort ();
+
+ if (s->debug_p)
+ fprintf (stderr, "%s: %s\n", blurb(), ndpy);
+
+ /* 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.
+ */
+
+ if (def_path && *def_path)
+ {
+ const char *opath = getenv("PATH");
+ char *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 ();
+ /* do not free(npath) -- see above */
+
+ if (s->debug_p)
+ fprintf (stderr, "%s: added \"%s\" to $PATH\n", blurb(), def_path);
+ }
+}
+
+
+static void
+hack_subproc_environment (Window preview_window_id, Bool debug_p)
+{
+ /* Store a window ID in $XSCREENSAVER_WINDOW -- this isn't strictly
+ necessary yet, but it will make programs work if we had invoked
+ them with "-root" and not with "-window-id" -- which, of course,
+ doesn't happen.
+ */
+ char *nssw = (char *) malloc (40);
+ sprintf (nssw, "XSCREENSAVER_WINDOW=0x%X", (unsigned int) preview_window_id);
+
+ /* 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. */
+ if (putenv (nssw))
+ abort ();
+
+ if (debug_p)
+ fprintf (stderr, "%s: %s\n", blurb(), nssw);
+
+ /* do not free(nssw) -- see above */
+}
+
+
+/* Called from a timer:
+ Launches the currently-chosen subprocess, if it's not already running.
+ If there's a different process running, kills it.
+ */
+static int
+update_subproc_timer (gpointer data)
+{
+ state *s = (state *) data;
+ if (! s->desired_preview_cmd)
+ kill_preview_subproc (s, True);
+ else if (!s->running_preview_cmd ||
+ !!strcmp (s->desired_preview_cmd, s->running_preview_cmd))
+ launch_preview_subproc (s);
+
+ s->subproc_timer_id = 0;
+ return FALSE; /* do not re-execute timer */
+}
+
+static int
+settings_timer (gpointer data)
+{
+ settings_cb (0, 0);
+ return FALSE;
+}
+
+
+/* Call this when you think you might want a preview process running.
+ It will set a timer that will actually launch that program a second
+ from now, if you haven't changed your mind (to avoid double-click
+ spazzing, etc.) `cmd' may be null meaning "no process".
+ */
+static void
+schedule_preview (state *s, const char *cmd)
+{
+ int delay = 1000 * 0.5; /* 1/2 second hysteresis */
+
+ if (s->debug_p)
+ {
+ if (cmd)
+ fprintf (stderr, "%s: scheduling preview \"%s\"\n", blurb(), cmd);
+ else
+ fprintf (stderr, "%s: scheduling preview death\n", blurb());
+ }
+
+ if (s->desired_preview_cmd) free (s->desired_preview_cmd);
+ s->desired_preview_cmd = (cmd ? strdup (cmd) : 0);
+
+ if (s->subproc_timer_id)
+ gtk_timeout_remove (s->subproc_timer_id);
+ s->subproc_timer_id = gtk_timeout_add (delay, update_subproc_timer, s);
+}
+
+
+/* Called from a timer:
+ Checks to see if the subproc that should be running, actually is.
+ */
+static int
+check_subproc_timer (gpointer data)
+{
+ state *s = (state *) data;
+ Bool again_p = True;
+
+ if (s->running_preview_error_p || /* already dead */
+ s->running_preview_pid <= 0)
+ {
+ again_p = False;
+ }
+ else
+ {
+ int status;
+ reap_zombies (s);
+ status = kill (s->running_preview_pid, 0);
+ if (status < 0 && errno == ESRCH)
+ s->running_preview_error_p = True;
+
+ if (s->debug_p)
+ {
+ char *ss = subproc_pretty_name (s);
+ fprintf (stderr, "%s: timer: pid %lu (%s) is %s\n", blurb(),
+ (unsigned long) s->running_preview_pid, ss,
+ (s->running_preview_error_p ? "dead" : "alive"));
+ free (ss);
+ }
+
+ if (s->running_preview_error_p)
+ {
+ clear_preview_window (s);
+ again_p = False;
+ }
+ }
+
+ /* Otherwise, it's currently alive. We might be checking again, or we
+ might be satisfied. */
+
+ if (--s->subproc_check_countdown <= 0)
+ again_p = False;
+
+ if (again_p)
+ return TRUE; /* re-execute timer */
+ else
+ {
+ s->subproc_check_timer_id = 0;
+ s->subproc_check_countdown = 0;
+ return FALSE; /* do not re-execute timer */
+ }
+}
+
+
+/* Call this just after launching a subprocess.
+ This sets a timer that will, five times a second for two seconds,
+ check whether the program is still running. The assumption here
+ is that if the process didn't stay up for more than a couple of
+ seconds, then either the program doesn't exist, or it doesn't
+ take a -window-id argument.
+ */
+static void
+schedule_preview_check (state *s)
+{
+ int seconds = 2;
+ int ticks = 5;
+
+ if (s->debug_p)
+ fprintf (stderr, "%s: scheduling check\n", blurb());
+
+ if (s->subproc_check_timer_id)
+ gtk_timeout_remove (s->subproc_check_timer_id);
+ s->subproc_check_timer_id =
+ gtk_timeout_add (1000 / ticks,
+ check_subproc_timer, (gpointer) s);
+ s->subproc_check_countdown = ticks * seconds;
+}
+
+
+static Bool
+screen_blanked_p (void)
+{
+ Atom type;
+ int format;
+ unsigned long nitems, bytesafter;
+ unsigned char *dataP = 0;
+ Display *dpy = GDK_DISPLAY();
+ Bool blanked_p = False;
+
+ if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */
+ XA_SCREENSAVER_STATUS,
+ 0, 3, False, XA_INTEGER,
+ &type, &format, &nitems, &bytesafter,
+ &dataP)
+ == Success
+ && type == XA_INTEGER
+ && nitems >= 3
+ && dataP)
+ {
+ Atom *data = (Atom *) dataP;
+ blanked_p = (data[0] == XA_BLANK || data[0] == XA_LOCK);
+ }
+
+ if (dataP) XFree (dataP);
+
+ return blanked_p;
+}
+
+/* Wake up every now and then and see if the screen is blanked.
+ If it is, kill off the small-window demo -- no point in wasting
+ cycles by running two screensavers at once...
+ */
+static int
+check_blanked_timer (gpointer data)
+{
+ state *s = (state *) data;
+ Bool blanked_p = screen_blanked_p ();
+ if (blanked_p && s->running_preview_pid)
+ {
+ if (s->debug_p)
+ fprintf (stderr, "%s: screen is blanked: killing preview\n", blurb());
+ kill_preview_subproc (s, True);
+ }
+
+ return True; /* re-execute timer */
+}
+
+
+/* How many screens are there (including Xinerama.)
+ */
+static int
+screen_count (Display *dpy)
+{
+ int nscreens = ScreenCount(dpy);
+# ifdef HAVE_XINERAMA
+ if (nscreens <= 1)
+ {
+ int event_number, error_number;
+ if (XineramaQueryExtension (dpy, &event_number, &error_number) &&
+ XineramaIsActive (dpy))
+ {
+ XineramaScreenInfo *xsi = XineramaQueryScreens (dpy, &nscreens);
+ if (xsi) XFree (xsi);
+ }
+ }
+# endif /* HAVE_XINERAMA */
+
+ return nscreens;
+}
+
+
+/* Setting window manager icon
+ */
+
+static void
+init_icon (GdkWindow *window)
+{
+ GdkBitmap *mask = 0;
+ GdkPixmap *pixmap =
+ gdk_pixmap_create_from_xpm_d (window, &mask, 0,
+ (gchar **) logo_50_xpm);
+ if (pixmap)
+ gdk_window_set_icon (window, 0, pixmap, mask);
+}
+
+
+/* The main demo-mode command loop.
+ */
+
+#if 0
+static Bool
+mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks,
+ XrmRepresentation *type, XrmValue *value, XPointer closure)
+{
+ int i;
+ for (i = 0; quarks[i]; i++)
+ {
+ if (bindings[i] == XrmBindTightly)
+ fprintf (stderr, (i == 0 ? "" : "."));
+ else if (bindings[i] == XrmBindLoosely)
+ fprintf (stderr, "*");
+ else
+ fprintf (stderr, " ??? ");
+ fprintf(stderr, "%s", XrmQuarkToString (quarks[i]));
+ }
+
+ fprintf (stderr, ": %s\n", (char *) value->addr);
+
+ return False;
+}
+#endif
+
+
+static Window
+gnome_screensaver_window (Screen *screen)
+{
+ Display *dpy = DisplayOfScreen (screen);
+ Window root = RootWindowOfScreen (screen);
+ Window parent, *kids;
+ unsigned int nkids;
+ Window gnome_window = 0;
+ int i;
+
+ if (! XQueryTree (dpy, root, &root, &parent, &kids, &nkids))
+ abort ();
+ for (i = 0; i < nkids; i++)
+ {
+ Atom type;
+ int format;
+ unsigned long nitems, bytesafter;
+ unsigned char *name;
+ if (XGetWindowProperty (dpy, kids[i], XA_WM_COMMAND, 0, 128,
+ False, XA_STRING, &type, &format, &nitems,
+ &bytesafter, &name)
+ == Success
+ && type != None
+ && !strcmp ((char *) name, "gnome-screensaver"))
+ {
+ gnome_window = kids[i];
+ break;
+ }
+ }
+
+ if (kids) XFree ((char *) kids);
+ return gnome_window;
+}
+
+static Bool
+gnome_screensaver_active_p (void)
+{
+ Display *dpy = GDK_DISPLAY();
+ Window w = gnome_screensaver_window (DefaultScreenOfDisplay (dpy));
+ return (w ? True : False);
+}
+
+static void
+kill_gnome_screensaver (void)
+{
+ Display *dpy = GDK_DISPLAY();
+ Window w = gnome_screensaver_window (DefaultScreenOfDisplay (dpy));
+ if (w) XKillClient (dpy, (XID) w);
+}
+
+static Bool
+kde_screensaver_active_p (void)
+{
+ FILE *p = popen ("dcop kdesktop KScreensaverIface isEnabled 2>/dev/null",
+ "r");
+ char buf[255];
+ fgets (buf, sizeof(buf)-1, p);
+ pclose (p);
+ if (!strcmp (buf, "true\n"))
+ return True;
+ else
+ return False;
+}
+
+static void
+kill_kde_screensaver (void)
+{
+ system ("dcop kdesktop KScreensaverIface enable false");
+}
+
+
+static void
+the_network_is_not_the_computer (state *s)
+{
+ Display *dpy = GDK_DISPLAY();
+ char *rversion = 0, *ruser = 0, *rhost = 0;
+ char *luser, *lhost;
+ char *msg = 0;
+ struct passwd *p = getpwuid (getuid ());
+ const char *d = DisplayString (dpy);
+
+# if defined(HAVE_UNAME)
+ struct utsname uts;
+ if (uname (&uts) < 0)
+ lhost = "<UNKNOWN>";
+ else
+ lhost = uts.nodename;
+# elif defined(VMS)
+ strcpy (lhost, getenv("SYS$NODE"));
+# else /* !HAVE_UNAME && !VMS */
+ strcat (lhost, "<UNKNOWN>");
+# endif /* !HAVE_UNAME && !VMS */
+
+ if (p && p->pw_name)
+ luser = p->pw_name;
+ else
+ luser = "???";
+
+ server_xscreensaver_version (dpy, &rversion, &ruser, &rhost);
+
+ /* Make a buffer that's big enough for a number of copies of all the
+ strings, plus some. */
+ msg = (char *) malloc (10 * ((rversion ? strlen(rversion) : 0) +
+ (ruser ? strlen(ruser) : 0) +
+ (rhost ? strlen(rhost) : 0) +
+ strlen(lhost) +
+ strlen(luser) +
+ strlen(d) +
+ 1024));
+ *msg = 0;
+
+ if (!rversion || !*rversion)
+ {
+ sprintf (msg,
+ _("Warning:\n\n"
+ "The XScreenSaver daemon doesn't seem to be running\n"
+ "on display \"%s\". Launch it now?"),
+ d);
+ }
+ else if (p && ruser && *ruser && !!strcmp (ruser, p->pw_name))
+ {
+ /* Warn that the two processes are running as different users.
+ */
+ sprintf(msg,
+ _("Warning:\n\n"
+ "%s is running as user \"%s\" on host \"%s\".\n"
+ "But the xscreensaver managing display \"%s\"\n"
+ "is running as user \"%s\" on host \"%s\".\n"
+ "\n"
+ "Since they are different users, they won't be reading/writing\n"
+ "the same ~/.xscreensaver file, so %s isn't\n"
+ "going to work right.\n"
+ "\n"
+ "You should either re-run %s as \"%s\", or re-run\n"
+ "xscreensaver as \"%s\".\n"
+ "\n"
+ "Restart the xscreensaver daemon now?\n"),
+ progname, luser, lhost,
+ d,
+ (ruser ? ruser : "???"), (rhost ? rhost : "???"),
+ progname,
+ progname, (ruser ? ruser : "???"),
+ luser);
+ }
+ else if (rhost && *rhost && !!strcmp (rhost, lhost))
+ {
+ /* Warn that the two processes are running on different hosts.
+ */
+ sprintf (msg,
+ _("Warning:\n\n"
+ "%s is running as user \"%s\" on host \"%s\".\n"
+ "But the xscreensaver managing display \"%s\"\n"
+ "is running as user \"%s\" on host \"%s\".\n"
+ "\n"
+ "If those two machines don't share a file system (that is,\n"
+ "if they don't see the same ~%s/.xscreensaver file) then\n"
+ "%s won't work right.\n"
+ "\n"
+ "Restart the daemon on \"%s\" as \"%s\" now?\n"),
+ progname, luser, lhost,
+ d,
+ (ruser ? ruser : "???"), (rhost ? rhost : "???"),
+ luser,
+ progname,
+ lhost, luser);
+ }
+ else if (!!strcmp (rversion, s->short_version))
+ {
+ /* Warn that the version numbers don't match.
+ */
+ sprintf (msg,
+ _("Warning:\n\n"
+ "This is %s version %s.\n"
+ "But the xscreensaver managing display \"%s\"\n"
+ "is version %s. This could cause problems.\n"
+ "\n"
+ "Restart the xscreensaver daemon now?\n"),
+ progname, s->short_version,
+ d,
+ rversion);
+ }
+
+
+ if (*msg)
+ warning_dialog (s->toplevel_widget, msg, D_LAUNCH, 1);
+
+ if (rversion) free (rversion);
+ if (ruser) free (ruser);
+ if (rhost) free (rhost);
+ free (msg);
+ msg = 0;
+
+ /* Note: since these dialogs are not modal, they will stack up.
+ So we do this check *after* popping up the "xscreensaver is not
+ running" dialog so that these are on top. Good enough.
+ */
+
+ if (gnome_screensaver_active_p ())
+ warning_dialog (s->toplevel_widget,
+ _("Warning:\n\n"
+ "The GNOME screensaver daemon appears to be running.\n"
+ "It must be stopped for XScreenSaver to work properly.\n"
+ "\n"
+ "Stop the GNOME screen saver daemon now?\n"),
+ D_GNOME, 1);
+
+ if (kde_screensaver_active_p ())
+ warning_dialog (s->toplevel_widget,
+ _("Warning:\n\n"
+ "The KDE screen saver daemon appears to be running.\n"
+ "It must be stopped for XScreenSaver to work properly.\n"
+ "\n"
+ "Stop the KDE screen saver daemon now?\n"),
+ D_KDE, 1);
+}
+
+
+/* We use this error handler so that X errors are preceeded by the name
+ of the program that generated them.
+ */
+static int
+demo_ehandler (Display *dpy, XErrorEvent *error)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ fprintf (stderr, "\nX error in %s:\n", blurb());
+ XmuPrintDefaultErrorMessage (dpy, error, stderr);
+ kill_preview_subproc (s, False);
+ exit (-1);
+ return 0;
+}
+
+
+/* We use this error handler so that Gtk/Gdk errors are preceeded by the name
+ of the program that generated them; and also that we can ignore one
+ particular bogus error message that Gdk madly spews.
+ */
+static void
+g_log_handler (const gchar *log_domain, GLogLevelFlags log_level,
+ const gchar *message, gpointer user_data)
+{
+ /* Ignore the message "Got event for unknown window: 0x...".
+ Apparently some events are coming in for the xscreensaver window
+ (presumably reply events related to the ClientMessage) and Gdk
+ feels the need to complain about them. So, just suppress any
+ messages that look like that one.
+ */
+ if (strstr (message, "unknown window"))
+ return;
+
+ fprintf (stderr, "%s: %s-%s: %s%s", blurb(),
+ (log_domain ? log_domain : progclass),
+ (log_level == G_LOG_LEVEL_ERROR ? "error" :
+ log_level == G_LOG_LEVEL_CRITICAL ? "critical" :
+ log_level == G_LOG_LEVEL_WARNING ? "warning" :
+ log_level == G_LOG_LEVEL_MESSAGE ? "message" :
+ log_level == G_LOG_LEVEL_INFO ? "info" :
+ log_level == G_LOG_LEVEL_DEBUG ? "debug" : "???"),
+ message,
+ ((!*message || message[strlen(message)-1] != '\n')
+ ? "\n" : ""));
+}
+
+
+STFU
+static char *defaults[] = {
+#include "XScreenSaver_ad.h"
+ 0
+};
+
+#if 0
+#ifdef HAVE_CRAPPLET
+static struct poptOption crapplet_options[] = {
+ {NULL, '\0', 0, NULL, 0}
+};
+#endif /* HAVE_CRAPPLET */
+#endif /* 0 */
+
+const char *usage = "[--display dpy] [--prefs | --settings]"
+# ifdef HAVE_CRAPPLET
+ " [--crapplet]"
+# endif
+ "\n\t\t [--debug] [--sync] [--no-xshm] [--configdir dir]";
+
+static void
+map_popup_window_cb (GtkWidget *w, gpointer user_data)
+{
+ state *s = (state *) user_data;
+ Boolean oi = s->initializing_p;
+#ifndef HAVE_GTK2
+ GtkLabel *label = GTK_LABEL (name_to_widget (s, "doc"));
+#endif
+ s->initializing_p = True;
+#ifndef HAVE_GTK2
+ eschew_gtk_lossage (label);
+#endif
+ s->initializing_p = oi;
+}
+
+
+#if 0
+static void
+print_widget_tree (GtkWidget *w, int depth)
+{
+ int i;
+ for (i = 0; i < depth; i++)
+ fprintf (stderr, " ");
+ fprintf (stderr, "%s\n", gtk_widget_get_name (w));
+
+ if (GTK_IS_LIST (w))
+ {
+ for (i = 0; i < depth+1; i++)
+ fprintf (stderr, " ");
+ fprintf (stderr, "...list kids...\n");
+ }
+ else if (GTK_IS_CONTAINER (w))
+ {
+ GList *kids = gtk_container_children (GTK_CONTAINER (w));
+ while (kids)
+ {
+ print_widget_tree (GTK_WIDGET (kids->data), depth+1);
+ kids = kids->next;
+ }
+ }
+}
+#endif /* 0 */
+
+static int
+delayed_scroll_kludge (gpointer data)
+{
+ state *s = (state *) data;
+ GtkWidget *w = GTK_WIDGET (name_to_widget (s, "list"));
+ ensure_selected_item_visible (w);
+
+ /* Oh, this is just fucking lovely, too. */
+ w = GTK_WIDGET (name_to_widget (s, "preview"));
+ gtk_widget_hide (w);
+ gtk_widget_show (w);
+
+ return FALSE; /* do not re-execute timer */
+}
+
+#ifdef HAVE_GTK2
+
+static GtkWidget *
+create_xscreensaver_demo (void)
+{
+ GtkWidget *nb;
+
+ nb = name_to_widget (global_state_kludge, "preview_notebook");
+ gtk_notebook_set_show_tabs (GTK_NOTEBOOK (nb), FALSE);
+
+ return name_to_widget (global_state_kludge, "xscreensaver_demo");
+}
+
+static GtkWidget *
+create_xscreensaver_settings_dialog (void)
+{
+ GtkWidget *w, *box;
+
+ box = name_to_widget (global_state_kludge, "dialog_action_area");
+
+ w = name_to_widget (global_state_kludge, "adv_button");
+ gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (box), w, TRUE);
+
+ w = name_to_widget (global_state_kludge, "std_button");
+ gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (box), w, TRUE);
+
+ return name_to_widget (global_state_kludge, "xscreensaver_settings_dialog");
+}
+
+#endif /* HAVE_GTK2 */
+
+int
+main (int argc, char **argv)
+{
+ XtAppContext app;
+ state S, *s;
+ saver_preferences *p;
+ Bool prefs_p = False;
+ Bool settings_p = False;
+ int i;
+ Display *dpy;
+ Widget toplevel_shell;
+ char *real_progname = argv[0];
+ char *window_title;
+ char *geom = 0;
+ Bool crapplet_p = False;
+ char *str;
+
+#ifdef ENABLE_NLS
+ bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+ textdomain (GETTEXT_PACKAGE);
+
+# ifdef HAVE_GTK2
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+# else /* !HAVE_GTK2 */
+ if (!setlocale (LC_ALL, ""))
+ fprintf (stderr, "%s: locale not supported by C library\n", real_progname);
+# endif /* !HAVE_GTK2 */
+
+#endif /* ENABLE_NLS */
+
+ str = strrchr (real_progname, '/');
+ if (str) real_progname = str+1;
+
+ s = &S;
+ memset (s, 0, sizeof(*s));
+ s->initializing_p = True;
+ p = &s->prefs;
+
+ global_state_kludge = s; /* I hate C so much... */
+
+ progname = real_progname;
+
+ s->short_version = (char *) malloc (5);
+ memcpy (s->short_version, screensaver_id + 17, 4);
+ s->short_version [4] = 0;
+
+
+ /* 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
+ for all of the domains that seem to be in use.
+ */
+ {
+ const char * const domains[] = { 0,
+ "Gtk", "Gdk", "GLib", "GModule",
+ "GThread", "Gnome", "GnomeUI" };
+ for (i = 0; i < countof(domains); i++)
+ g_log_set_handler (domains[i], G_LOG_LEVEL_MASK, g_log_handler, 0);
+ }
+
+#ifdef DEFAULT_ICONDIR /* from -D on compile line */
+# ifndef HAVE_GTK2
+ {
+ const char *dir = DEFAULT_ICONDIR;
+ if (*dir) add_pixmap_directory (dir);
+ }
+# endif /* !HAVE_GTK2 */
+#endif /* DEFAULT_ICONDIR */
+
+ /* This is gross, but Gtk understands --display and not -display...
+ */
+ for (i = 1; i < argc; i++)
+ if (argv[i][0] && argv[i][1] &&
+ !strncmp(argv[i], "-display", strlen(argv[i])))
+ argv[i] = "--display";
+
+
+ /* We need to parse this arg really early... Sigh. */
+ for (i = 1; i < argc; i++)
+ {
+ if (argv[i] &&
+ (!strcmp(argv[i], "--crapplet") ||
+ !strcmp(argv[i], "--capplet")))
+ {
+# if defined(HAVE_CRAPPLET) || defined(HAVE_GTK2)
+ int j;
+ crapplet_p = True;
+ for (j = i; j < argc; j++) /* remove it from the list */
+ argv[j] = argv[j+1];
+ argc--;
+# else /* !HAVE_CRAPPLET && !HAVE_GTK2 */
+ fprintf (stderr, "%s: not compiled with --crapplet support\n",
+ real_progname);
+ fprintf (stderr, "%s: %s\n", real_progname, usage);
+ exit (1);
+# endif /* !HAVE_CRAPPLET && !HAVE_GTK2 */
+ }
+ else if (argv[i] &&
+ (!strcmp(argv[i], "--debug") ||
+ !strcmp(argv[i], "-debug") ||
+ !strcmp(argv[i], "-d")))
+ {
+ int j;
+ s->debug_p = True;
+ for (j = i; j < argc; j++) /* remove it from the list */
+ argv[j] = argv[j+1];
+ argc--;
+ i--;
+ }
+ else if (argv[i] &&
+ argc > i+1 &&
+ *argv[i+1] &&
+ (!strcmp(argv[i], "-geometry") ||
+ !strcmp(argv[i], "-geom") ||
+ !strcmp(argv[i], "-geo") ||
+ !strcmp(argv[i], "-g")))
+ {
+ int j;
+ geom = argv[i+1];
+ for (j = i; j < argc; j++) /* remove them from the list */
+ argv[j] = argv[j+2];
+ argc -= 2;
+ i -= 2;
+ }
+ else if (argv[i] &&
+ argc > i+1 &&
+ *argv[i+1] &&
+ (!strcmp(argv[i], "--configdir")))
+ {
+ int j;
+ struct stat st;
+ hack_configuration_path = argv[i+1];
+ for (j = i; j < argc; j++) /* remove them from the list */
+ argv[j] = argv[j+2];
+ argc -= 2;
+ i -= 2;
+
+ if (0 != stat (hack_configuration_path, &st))
+ {
+ char buf[255];
+ sprintf (buf, "%s: %.200s", blurb(), hack_configuration_path);
+ perror (buf);
+ exit (1);
+ }
+ else if (!S_ISDIR (st.st_mode))
+ {
+ fprintf (stderr, "%s: not a directory: %s\n",
+ blurb(), hack_configuration_path);
+ exit (1);
+ }
+ }
+ }
+
+
+ if (s->debug_p)
+ fprintf (stderr, "%s: using config directory \"%s\"\n",
+ progname, hack_configuration_path);
+
+
+ /* Let Gtk open the X connection, then initialize Xt to use that
+ same connection. Doctor Frankenstein would be proud.
+ */
+# ifdef HAVE_CRAPPLET
+ if (crapplet_p)
+ {
+ GnomeClient *client;
+ GnomeClientFlags flags = 0;
+
+ int init_results = gnome_capplet_init ("screensaver-properties",
+ s->short_version,
+ argc, argv, NULL, 0, NULL);
+ /* init_results is:
+ 0 upon successful initialization;
+ 1 if --init-session-settings was passed on the cmdline;
+ 2 if --ignore was passed on the cmdline;
+ -1 on error.
+
+ So the 1 signifies just to init the settings, and quit, basically.
+ (Meaning launch the xscreensaver daemon.)
+ */
+
+ if (init_results < 0)
+ {
+# if 0
+ g_error ("An initialization error occurred while "
+ "starting xscreensaver-capplet.\n");
+# else /* !0 */
+ fprintf (stderr, "%s: gnome_capplet_init failed: %d\n",
+ real_progname, init_results);
+ exit (1);
+# endif /* !0 */
+ }
+
+ client = gnome_master_client ();
+
+ if (client)
+ flags = gnome_client_get_flags (client);
+
+ if (flags & GNOME_CLIENT_IS_CONNECTED)
+ {
+ int token =
+ gnome_startup_acquire_token ("GNOME_SCREENSAVER_PROPERTIES",
+ gnome_client_get_id (client));
+ if (token)
+ {
+ char *session_args[20];
+ int i = 0;
+ session_args[i++] = real_progname;
+ session_args[i++] = "--capplet";
+ session_args[i++] = "--init-session-settings";
+ session_args[i] = 0;
+ gnome_client_set_priority (client, 20);
+ gnome_client_set_restart_style (client, GNOME_RESTART_ANYWAY);
+ gnome_client_set_restart_command (client, i, session_args);
+ }
+ else
+ {
+ gnome_client_set_restart_style (client, GNOME_RESTART_NEVER);
+ }
+
+ gnome_client_flush (client);
+ }
+
+ if (init_results == 1)
+ {
+ system ("xscreensaver -nosplash &");
+ return 0;
+ }
+
+ }
+ else
+# endif /* HAVE_CRAPPLET */
+ {
+ gtk_init (&argc, &argv);
+ }
+
+
+ /* We must read exactly the same resources as xscreensaver.
+ That means we must have both the same progclass *and* progname,
+ at least as far as the resource database is concerned. So,
+ put "xscreensaver" in argv[0] while initializing Xt.
+ */
+ argv[0] = "xscreensaver";
+ progname = argv[0];
+
+
+ /* Teach Xt to use the Display that Gtk/Gdk have already opened.
+ */
+ XtToolkitInitialize ();
+ app = XtCreateApplicationContext ();
+ dpy = GDK_DISPLAY();
+ XtAppSetFallbackResources (app, defaults);
+ XtDisplayInitialize (app, dpy, progname, progclass, 0, 0, &argc, argv);
+ toplevel_shell = XtAppCreateShell (progname, progclass,
+ applicationShellWidgetClass,
+ dpy, 0, 0);
+
+ dpy = XtDisplay (toplevel_shell);
+ db = XtDatabase (dpy);
+ XtGetApplicationNameAndClass (dpy, &progname, &progclass);
+ XSetErrorHandler (demo_ehandler);
+
+ /* Let's just ignore these. They seem to confuse Irix Gtk... */
+ signal (SIGPIPE, SIG_IGN);
+
+ /* After doing Xt-style command-line processing, complain about any
+ unrecognized command-line arguments.
+ */
+ for (i = 1; i < argc; i++)
+ {
+ char *str = argv[i];
+ if (str[0] == '-' && str[1] == '-')
+ str++;
+ if (!strcmp (str, "-prefs"))
+ prefs_p = True;
+ else if (!strcmp (str, "-settings"))
+ settings_p = True;
+ else if (crapplet_p)
+ /* There are lots of random args that we don't care about when we're
+ started as a crapplet, so just ignore unknown args in that case. */
+ ;
+ else
+ {
+ fprintf (stderr, _("%s: unknown option: %s\n"), real_progname,
+ argv[i]);
+ fprintf (stderr, "%s: %s\n", real_progname, usage);
+ exit (1);
+ }
+ }
+
+ /* 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
+ important that `progname' be "xscreensaver", rather than whatever
+ was in argv[0].
+ */
+ p->db = db;
+ s->nscreens = screen_count (dpy);
+
+ hack_environment (s); /* must be before initialize_sort_map() */
+
+ load_init_file (dpy, p);
+ initialize_sort_map (s);
+
+ /* Now that Xt has been initialized, and the resources have been read,
+ we can set our `progname' variable to something more in line with
+ reality.
+ */
+ progname = real_progname;
+
+
+#if 0
+ /* Print out all the resources we read. */
+ {
+ XrmName name = { 0 };
+ XrmClass class = { 0 };
+ int count = 0;
+ XrmEnumerateDatabase (db, &name, &class, XrmEnumAllLevels, mapper,
+ (POINTER) &count);
+ }
+#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_BLANK = XInternAtom (dpy, "BLANK", False);
+ XA_LOCK = XInternAtom (dpy, "LOCK", False);
+ XA_EXIT = XInternAtom (dpy, "EXIT", False);
+ XA_RESTART = XInternAtom (dpy, "RESTART", False);
+
+
+ /* Create the window and all its widgets.
+ */
+ s->base_widget = create_xscreensaver_demo ();
+ s->popup_widget = create_xscreensaver_settings_dialog ();
+ s->toplevel_widget = s->base_widget;
+
+
+ /* Set the main window's title. */
+ {
+ char *base_title = _("Screensaver Preferences");
+ char *v = (char *) strdup(strchr(screensaver_id, ' '));
+ char *s1, *s2, *s3, *s4;
+ s1 = (char *) strchr(v, ' '); s1++;
+ s2 = (char *) strchr(s1, ' ');
+ s3 = (char *) strchr(v, '('); s3++;
+ s4 = (char *) strchr(s3, ')');
+ *s2 = 0;
+ *s4 = 0;
+
+ window_title = (char *) malloc (strlen (base_title) +
+ strlen (progclass) +
+ strlen (s1) + strlen (s3) +
+ 100);
+ sprintf (window_title, "%s (%s %s, %s)", base_title, progclass, s1, s3);
+ gtk_window_set_title (GTK_WINDOW (s->toplevel_widget), window_title);
+ gtk_window_set_title (GTK_WINDOW (s->popup_widget), window_title);
+ free (v);
+ }
+
+ /* Adjust the (invisible) notebooks on the popup dialog... */
+ {
+ GtkNotebook *notebook =
+ GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
+ GtkWidget *std = GTK_WIDGET (name_to_widget (s, "std_button"));
+ int page = 0;
+
+# ifdef HAVE_XML
+ gtk_widget_hide (std);
+# else /* !HAVE_XML */
+ /* Make the advanced page be the only one available. */
+ gtk_widget_set_sensitive (std, False);
+ std = GTK_WIDGET (name_to_widget (s, "adv_button"));
+ gtk_widget_hide (std);
+ std = GTK_WIDGET (name_to_widget (s, "reset_button"));
+ gtk_widget_hide (std);
+ page = 1;
+# endif /* !HAVE_XML */
+
+ gtk_notebook_set_page (notebook, page);
+ gtk_notebook_set_show_tabs (notebook, False);
+ }
+
+ /* Various other widget initializations...
+ */
+ gtk_signal_connect (GTK_OBJECT (s->toplevel_widget), "delete_event",
+ GTK_SIGNAL_FUNC (wm_toplevel_close_cb),
+ (gpointer) s);
+ gtk_signal_connect (GTK_OBJECT (s->popup_widget), "delete_event",
+ GTK_SIGNAL_FUNC (wm_popup_close_cb),
+ (gpointer) s);
+
+ populate_hack_list (s);
+ populate_prefs_page (s);
+ sensitize_demo_widgets (s, False);
+ fix_text_entry_sizes (s);
+ scroll_to_current_hack (s);
+
+ gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "cancel_button")),
+ "map", GTK_SIGNAL_FUNC(map_popup_window_cb),
+ (gpointer) s);
+
+#ifndef HAVE_GTK2
+ gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "prev")),
+ "map", GTK_SIGNAL_FUNC(map_prev_button_cb),
+ (gpointer) s);
+ gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "next")),
+ "map", GTK_SIGNAL_FUNC(map_next_button_cb),
+ (gpointer) s);
+#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));
+ }
+
+ if (s->nscreens <= 1) /* recompute option-menu size */
+ {
+ gtk_widget_unrealize (GTK_WIDGET (menu));
+ gtk_widget_realize (GTK_WIDGET (menu));
+ }
+ }
+
+
+ /* Handle the -prefs command-line argument. */
+ if (prefs_p)
+ {
+ GtkNotebook *notebook =
+ GTK_NOTEBOOK (name_to_widget (s, "notebook"));
+ gtk_notebook_set_page (notebook, 1);
+ }
+
+# ifdef HAVE_CRAPPLET
+ if (crapplet_p)
+ {
+ GtkWidget *capplet;
+ GtkWidget *outer_vbox;
+
+ gtk_widget_hide (s->toplevel_widget);
+
+ capplet = capplet_widget_new ();
+
+ /* Make there be a "Close" button instead of "OK" and "Cancel" */
+# ifdef HAVE_CRAPPLET_IMMEDIATE
+ capplet_widget_changes_are_immediate (CAPPLET_WIDGET (capplet));
+# endif /* HAVE_CRAPPLET_IMMEDIATE */
+ /* In crapplet-mode, take off the menubar. */
+ gtk_widget_hide (name_to_widget (s, "menubar"));
+
+ /* Reparent our top-level container to be a child of the capplet
+ window.
+ */
+ outer_vbox = GTK_BIN (s->toplevel_widget)->child;
+ gtk_widget_ref (outer_vbox);
+ gtk_container_remove (GTK_CONTAINER (s->toplevel_widget),
+ outer_vbox);
+ STFU GTK_OBJECT_SET_FLAGS (outer_vbox, GTK_FLOATING);
+ gtk_container_add (GTK_CONTAINER (capplet), outer_vbox);
+
+ /* Find the window above us, and set the title and close handler. */
+ {
+ GtkWidget *window = capplet;
+ while (window && !GTK_IS_WINDOW (window))
+ window = GET_PARENT (window);
+ if (window)
+ {
+ gtk_window_set_title (GTK_WINDOW (window), window_title);
+ gtk_signal_connect (GTK_OBJECT (window), "delete_event",
+ GTK_SIGNAL_FUNC (wm_toplevel_close_cb),
+ (gpointer) s);
+ }
+ }
+
+ s->toplevel_widget = capplet;
+ }
+# endif /* HAVE_CRAPPLET */
+
+
+ /* The Gnome folks hate the menubar. I think it's important to have access
+ to the commands on the File menu (Restart Daemon, etc.) and to the
+ About and Documentation commands on the Help menu.
+ */
+#if 0
+#ifdef HAVE_GTK2
+ gtk_widget_hide (name_to_widget (s, "menubar"));
+#endif
+#endif
+
+ free (window_title);
+ window_title = 0;
+
+#ifdef HAVE_GTK2
+ /* After picking the default size, allow -geometry to override it. */
+ if (geom)
+ gtk_window_parse_geometry (GTK_WINDOW (s->toplevel_widget), geom);
+#endif
+
+ gtk_widget_show (s->toplevel_widget);
+ init_icon (GET_WINDOW (GTK_WIDGET (s->toplevel_widget))); /* after `show' */
+ fix_preview_visual (s);
+
+ /* Realize page zero, so that we can diddle the scrollbar when the
+ user tabs back to it -- otherwise, the current hack isn't scrolled
+ to the first time they tab back there, when started with "-prefs".
+ (Though it is if they then tab away, and back again.)
+
+ #### Bah! This doesn't work. Gtk eats my ass! Someone who
+ #### understands this crap, explain to me how to make this work.
+ */
+ gtk_widget_realize (name_to_widget (s, "demos_table"));
+
+
+ gtk_timeout_add (60 * 1000, check_blanked_timer, s);
+
+
+ /* Handle the --settings command-line argument. */
+ if (settings_p)
+ gtk_timeout_add (500, settings_timer, 0);
+
+
+ /* Issue any warnings about the running xscreensaver daemon. */
+ if (! s->debug_p)
+ the_network_is_not_the_computer (s);
+
+
+ if (senesculent_p())
+ warning_dialog (s->toplevel_widget,
+ _("Warning:\n\n"
+ "This version of xscreensaver is VERY OLD!\n"
+ "Please upgrade!\n"
+ "\n"
+ "https://www.jwz.org/xscreensaver/\n"
+ "\n"
+ "(If this is the latest version that your distro ships, then\n"
+ "your distro is doing you a disservice. Build from source.)\n"
+ ),
+ 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.
+ Fortunately, we're using Gtk for all of the UI, and only initialized
+ Xt so that we could process the command line and use the X resource
+ manager.
+ */
+ s->initializing_p = False;
+
+ /* This totally sucks -- set a timer that whacks the scrollbar 0.5 seconds
+ after we start up. Otherwise, it always appears scrolled to the top
+ when in crapplet-mode. */
+ gtk_timeout_add (500, delayed_scroll_kludge, s);
+
+
+#if 1
+ /* Load every configurator in turn, to scan them for errors all at once. */
+ if (s->debug_p)
+ {
+ int i;
+ for (i = 0; i < p->screenhacks_count; i++)
+ {
+ screenhack *hack = p->screenhacks[i];
+ conf_data *d = load_configurator (hack->command, s->debug_p);
+ if (d) free_conf_data (d);
+ }
+ }
+#endif
+
+
+# ifdef HAVE_CRAPPLET
+ if (crapplet_p)
+ capplet_gtk_main ();
+ else
+# endif /* HAVE_CRAPPLET */
+ gtk_main ();
+
+ kill_preview_subproc (s, False);
+ exit (0);
+}
+
+#endif /* HAVE_GTK -- whole file */
diff --git a/driver/demo-Xm-widgets.c b/driver/demo-Xm-widgets.c
new file mode 100644
index 0000000..cbe3393
--- /dev/null
+++ b/driver/demo-Xm-widgets.c
@@ -0,0 +1,907 @@
+/* demo-Xm.c --- implements the interactive demo-mode and options dialogs.
+ * xscreensaver, Copyright (c) 1999, 2003 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 <X11/Xatom.h>
+#include <X11/Intrinsic.h>
+
+#include <X11/IntrinsicP.h> /* just for debug info */
+#include <X11/ShellP.h>
+
+#include <X11/Shell.h>
+#include <Xm/Xm.h>
+#include <Xm/MainW.h>
+#include <Xm/Form.h>
+#include <Xm/Frame.h>
+#include <Xm/ScrolledW.h>
+#include <Xm/List.h>
+#include <Xm/PushB.h>
+#include <Xm/PushBG.h>
+#include <Xm/Text.h>
+#include <Xm/TextF.h>
+#include <Xm/ToggleBG.h>
+#include <Xm/CascadeBG.h>
+#include <Xm/RowColumn.h>
+#include <Xm/LabelG.h>
+#include <Xm/SeparatoG.h>
+#include <Xm/SelectioB.h>
+
+#ifdef HAVE_XMCOMBOBOX /* a Motif 2.0 widget */
+# include <Xm/ComboBox.h>
+# ifndef XmNtextField /* Lesstif 0.89.4 bug */
+# undef HAVE_XMCOMBOBOX
+# endif
+#endif /* HAVE_XMCOMBOBOX */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+
+
+const char *visual_menu[] = {
+ "Any", "Best", "Default", "Default-N", "GL", "TrueColor", "PseudoColor",
+ "StaticGray", "GrayScale", "DirectColor", "Color", "Gray", "Mono", 0
+};
+
+
+
+static Widget create_demos_page (Widget parent);
+static Widget create_options_page (Widget parent);
+
+static void
+tab_cb (Widget button, XtPointer client_data, XtPointer ignored)
+{
+ Widget parent = XtParent(button);
+ Widget tabber = XtNameToWidget (parent, "*folder");
+ Widget this_tab = (Widget) client_data;
+ Widget *kids = 0;
+ Cardinal nkids = 0;
+ if (!tabber) abort();
+
+ XtVaGetValues (tabber, XmNnumChildren, &nkids, XmNchildren, &kids, NULL);
+ if (!kids) abort();
+ if (nkids > 0)
+ XtUnmanageChildren (kids, nkids);
+
+ XtManageChild (this_tab);
+}
+
+
+Widget
+create_xscreensaver_demo (Widget parent)
+{
+ /* MainWindow
+ Form
+ Menubar
+ DemoTab
+ OptionsTab
+ HR
+ Tabber
+ (demo page)
+ (options page)
+ */
+
+ Widget mainw, form, menubar;
+ Widget demo_tab, options_tab, hr, tabber, demos, options;
+ Arg av[100];
+ int ac = 0;
+
+ mainw = XmCreateMainWindow (parent, "demoForm", av, ac);
+ form = XmCreateForm (mainw, "form", av, ac);
+ menubar = XmCreateSimpleMenuBar (form, "menubar", av, ac);
+ XtVaSetValues (menubar,
+ XmNtopAttachment, XmATTACH_FORM,
+ XmNleftAttachment, XmATTACH_FORM,
+ XmNrightAttachment, XmATTACH_FORM,
+ NULL);
+
+ {
+ Widget menu = 0, item = 0;
+ char *menus[] = {
+ "*file", "blank", "lock", "kill", "restart", "-", "exit",
+ "*edit", "cut", "copy", "paste",
+ "*help", "about", "docMenu" };
+ int i;
+ for (i = 0; i < sizeof(menus)/sizeof(*menus); i++)
+ {
+ ac = 0;
+ if (menus[i][0] == '-')
+ item = XmCreateSeparatorGadget (menu, "separator", av, ac);
+ else if (menus[i][0] != '*')
+ item = XmCreatePushButtonGadget (menu, menus[i], av, ac);
+ else
+ {
+ menu = XmCreatePulldownMenu (parent, menus[i]+1, av, ac);
+ XtSetArg (av [ac], XmNsubMenuId, menu); ac++;
+ item = XmCreateCascadeButtonGadget (menubar, menus[i]+1, av, ac);
+
+ if (!strcmp (menus[i]+1, "help"))
+ XtVaSetValues(menubar, XmNmenuHelpWidget, item, NULL);
+ }
+ XtManageChild (item);
+ }
+ ac = 0;
+ }
+
+ demo_tab = XmCreatePushButtonGadget (form, "demoTab", av, ac);
+ XtVaSetValues (demo_tab,
+ XmNleftAttachment, XmATTACH_FORM,
+ XmNtopAttachment, XmATTACH_WIDGET,
+ XmNtopWidget, menubar,
+ NULL);
+
+ options_tab = XmCreatePushButtonGadget (form, "optionsTab", av, ac);
+ XtVaSetValues (options_tab,
+ XmNleftAttachment, XmATTACH_WIDGET,
+ XmNleftWidget, demo_tab,
+ XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET,
+ XmNtopWidget, demo_tab,
+ XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET,
+ XmNbottomWidget, demo_tab,
+ NULL);
+
+ hr = XmCreateSeparatorGadget (form, "hr", av, ac);
+ XtVaSetValues (hr,
+ XmNleftAttachment, XmATTACH_FORM,
+ XmNrightAttachment, XmATTACH_FORM,
+ XmNtopAttachment, XmATTACH_WIDGET,
+ XmNtopWidget, demo_tab,
+ NULL);
+
+ tabber = XmCreateForm (form, "folder", av, ac);
+ XtVaSetValues (tabber,
+ XmNtopAttachment, XmATTACH_WIDGET,
+ XmNtopWidget, hr,
+ XmNleftAttachment, XmATTACH_FORM,
+ XmNrightAttachment, XmATTACH_FORM,
+ XmNbottomAttachment, XmATTACH_FORM,
+ NULL);
+
+ demos = create_demos_page (tabber);
+ options = create_options_page (tabber);
+
+ XtAddCallback (demo_tab, XmNactivateCallback, tab_cb, demos);
+ XtAddCallback (options_tab, XmNactivateCallback, tab_cb, options);
+
+ XtManageChild (demos);
+ XtManageChild (options);
+
+ XtManageChild (demo_tab);
+ XtManageChild (options_tab);
+ XtManageChild (hr);
+ XtManageChild (menubar);
+ XtManageChild (tabber);
+ XtManageChild (form);
+
+#if 1
+ XtUnmanageChild (options);
+ XtManageChild (demos);
+#endif
+
+ return mainw;
+}
+
+
+static Widget
+create_demos_page (Widget parent)
+{
+ /* Form1 (horizontal)
+ Form2 (vertical)
+ Scroller
+ List
+ ButtonBox1 (vertical)
+ Button ("Down")
+ Button ("Up")
+ Form3 (vertical)
+ Frame
+ Label
+ TextArea (doc)
+ Label
+ Text ("Command Line")
+ Form4 (horizontal)
+ Checkbox ("Enabled")
+ Label ("Visual")
+ ComboBox
+ HR
+ ButtonBox2 (vertical)
+ Button ("Demo")
+ Button ("Documentation")
+ */
+ Widget form1, form2, form3, form4;
+ Widget scroller, list, buttonbox1, down, up;
+ Widget frame, frame_label, doc, cmd_label, cmd_text, enabled, vis_label;
+ Widget combo;
+ Widget hr, buttonbox2, demo, man;
+ Arg av[100];
+ int ac = 0;
+ int i;
+
+ form1 = XmCreateForm (parent, "form1", av, ac);
+ form2 = XmCreateForm (form1, "form2", av, ac);
+ XtVaSetValues (form2,
+ XmNtopAttachment, XmATTACH_FORM,
+ XmNleftAttachment, XmATTACH_FORM,
+ XmNbottomAttachment, XmATTACH_FORM,
+ NULL);
+
+ scroller = XmCreateScrolledWindow (form2, "scroller", av, ac);
+ XtVaSetValues (scroller,
+ XmNtopAttachment, XmATTACH_FORM,
+ XmNleftAttachment, XmATTACH_FORM,
+ XmNrightAttachment, XmATTACH_FORM,
+ XmNbottomAttachment, XmATTACH_WIDGET,
+ NULL);
+ list = XmCreateList (scroller, "list", av, ac);
+
+ buttonbox1 = XmCreateForm (form2, "buttonbox1", av, ac);
+ XtVaSetValues (buttonbox1,
+ XmNleftAttachment, XmATTACH_FORM,
+ XmNrightAttachment, XmATTACH_FORM,
+ XmNbottomAttachment, XmATTACH_FORM,
+ NULL);
+ XtVaSetValues (scroller, XmNbottomWidget, buttonbox1, NULL);
+
+ down = XmCreatePushButton (buttonbox1, "down", av, ac);
+ XtVaSetValues (down,
+ XmNleftAttachment, XmATTACH_FORM,
+ XmNtopAttachment, XmATTACH_FORM,
+ XmNbottomAttachment, XmATTACH_FORM,
+ NULL);
+ up = XmCreatePushButton (buttonbox1, "up", av, ac);
+ XtVaSetValues (up,
+ XmNleftAttachment, XmATTACH_WIDGET,
+ XmNleftWidget, down,
+ XmNtopAttachment, XmATTACH_FORM,
+ XmNbottomAttachment, XmATTACH_FORM,
+ NULL);
+
+ form3 = XmCreateForm (form1, "form3", av, ac);
+ XtVaSetValues (form3,
+ XmNtopAttachment, XmATTACH_FORM,
+ XmNleftAttachment, XmATTACH_WIDGET,
+ XmNleftWidget, form2,
+ XmNrightAttachment, XmATTACH_FORM,
+ XmNbottomAttachment, XmATTACH_FORM,
+ NULL);
+ frame = XmCreateFrame (form3, "frame", av, ac);
+
+ ac = 0;
+ XtSetArg (av [ac], XmNchildType, XmFRAME_TITLE_CHILD); ac++;
+ frame_label = XmCreateLabelGadget (frame, "frameLabel", av, ac);
+
+ ac = 0;
+ XtVaSetValues (frame,
+ XmNtopAttachment, XmATTACH_FORM,
+ XmNleftAttachment, XmATTACH_FORM,
+ XmNrightAttachment, XmATTACH_FORM,
+ XmNbottomAttachment, XmATTACH_WIDGET,
+ NULL);
+
+ ac = 0;
+ XtSetArg (av [ac], XmNchildType, XmFRAME_WORKAREA_CHILD); ac++;
+ doc = XmCreateText (frame, "doc", av, ac);
+
+ ac = 0;
+ XtVaSetValues (doc,
+ XmNeditable, FALSE,
+ XmNcursorPositionVisible, FALSE,
+ XmNwordWrap, TRUE,
+ XmNeditMode, XmMULTI_LINE_EDIT,
+ XmNshadowThickness, 0,
+ NULL);
+
+ cmd_label = XmCreateLabelGadget (form3, "cmdLabel", av, ac);
+ XtVaSetValues (cmd_label,
+ XmNleftAttachment, XmATTACH_FORM,
+ XmNrightAttachment, XmATTACH_FORM,
+ XmNbottomAttachment, XmATTACH_WIDGET,
+ NULL);
+ XtVaSetValues (frame, XmNbottomWidget, cmd_label, NULL);
+
+ cmd_text = XmCreateTextField (form3, "cmdText", av, ac);
+ XtVaSetValues (cmd_text,
+ XmNleftAttachment, XmATTACH_FORM,
+ XmNrightAttachment, XmATTACH_FORM,
+ XmNbottomAttachment, XmATTACH_WIDGET,
+ NULL);
+ XtVaSetValues (cmd_label, XmNbottomWidget, cmd_text, NULL);
+
+ form4 = XmCreateForm (form3, "form4", av, ac);
+ XtVaSetValues (form4,
+ XmNleftAttachment, XmATTACH_FORM,
+ XmNrightAttachment, XmATTACH_FORM,
+ XmNbottomAttachment, XmATTACH_WIDGET,
+ NULL);
+ XtVaSetValues (cmd_text, XmNbottomWidget, form4, NULL);
+
+ enabled = XmCreateToggleButtonGadget (form4, "enabled", av, ac);
+ XtVaSetValues (enabled,
+ XmNtopAttachment, XmATTACH_FORM,
+ XmNleftAttachment, XmATTACH_FORM,
+ XmNbottomAttachment, XmATTACH_FORM,
+ NULL);
+ vis_label = XmCreateLabelGadget (form4, "visLabel", av, ac);
+ XtVaSetValues (vis_label,
+ XmNtopAttachment, XmATTACH_FORM,
+ XmNleftAttachment, XmATTACH_WIDGET,
+ XmNleftWidget, enabled,
+ XmNbottomAttachment, XmATTACH_FORM,
+ NULL);
+#ifdef HAVE_XMCOMBOBOX
+ {
+ Widget list;
+ ac = 0;
+ XtSetArg (av [ac], XmNcomboBoxType, XmDROP_DOWN_COMBO_BOX); ac++;
+ combo = XmCreateComboBox (form4, "combo", av, ac);
+ for (i = 0; visual_menu[i]; i++)
+ {
+ XmString xs = XmStringCreate ((char *) visual_menu[i],
+ XmSTRING_DEFAULT_CHARSET);
+ XmComboBoxAddItem (combo, xs, 0, False);
+ XmStringFree (xs);
+ }
+ XtVaGetValues (combo, XmNlist, &list, NULL);
+ XtVaSetValues (list, XmNvisibleItemCount, i, NULL);
+ }
+#else /* !HAVE_XMCOMBOBOX */
+ {
+ Widget popup_menu = XmCreatePulldownMenu (parent, "menu", av, ac);
+ Widget kids[100];
+ for (i = 0; visual_menu[i]; i++)
+ {
+ XmString xs = XmStringCreate ((char *) visual_menu[i],
+ XmSTRING_DEFAULT_CHARSET);
+ ac = 0;
+ XtSetArg (av [ac], XmNlabelString, xs); ac++;
+ kids[i] = XmCreatePushButtonGadget (popup_menu, "button", av, ac);
+ /* XtAddCallback (combo, XmNactivateCallback, visual_popup_cb,
+ combo); */
+ XmStringFree (xs);
+ }
+ XtManageChildren (kids, i);
+
+ ac = 0;
+ XtSetArg (av [ac], XmNsubMenuId, popup_menu); ac++;
+ combo = XmCreateOptionMenu (form4, "combo", av, ac);
+ ac = 0;
+ }
+#endif /* !HAVE_XMCOMBOBOX */
+
+ XtVaSetValues (combo,
+ XmNtopAttachment, XmATTACH_FORM,
+ XmNleftAttachment, XmATTACH_WIDGET,
+ XmNleftWidget, vis_label,
+ XmNbottomAttachment, XmATTACH_FORM,
+ NULL);
+
+ hr = XmCreateSeparatorGadget (form3, "hr", av, ac);
+ XtVaSetValues (hr,
+ XmNleftAttachment, XmATTACH_FORM,
+ XmNrightAttachment, XmATTACH_FORM,
+ XmNbottomAttachment, XmATTACH_WIDGET,
+ NULL);
+ XtVaSetValues (form4, XmNbottomWidget, hr, NULL);
+
+ buttonbox2 = XmCreateForm (form3, "buttonbox2", av, ac);
+ XtVaSetValues (buttonbox2,
+ XmNleftAttachment, XmATTACH_FORM,
+ XmNrightAttachment, XmATTACH_FORM,
+ XmNbottomAttachment, XmATTACH_FORM,
+ NULL);
+ XtVaSetValues (hr, XmNbottomWidget, buttonbox2, NULL);
+
+ demo = XmCreatePushButtonGadget (buttonbox2, "demo", av, ac);
+ XtVaSetValues (demo,
+ XmNleftAttachment, XmATTACH_FORM,
+ XmNtopAttachment, XmATTACH_FORM,
+ XmNbottomAttachment, XmATTACH_FORM,
+ NULL);
+ man = XmCreatePushButtonGadget (buttonbox2, "man", av, ac);
+ XtVaSetValues (man,
+ XmNrightAttachment, XmATTACH_FORM,
+ XmNtopAttachment, XmATTACH_FORM,
+ XmNbottomAttachment, XmATTACH_FORM,
+ NULL);
+
+ XtManageChild (demo);
+ XtManageChild (man);
+ XtManageChild (buttonbox2);
+ XtManageChild (hr);
+
+ XtManageChild (combo);
+ XtManageChild (vis_label);
+ XtManageChild (enabled);
+ XtManageChild (form4);
+
+ XtManageChild (cmd_text);
+ XtManageChild (cmd_label);
+
+ XtManageChild (doc);
+ XtManageChild (frame_label);
+ XtManageChild (frame);
+ XtManageChild (form3);
+
+ XtManageChild (up);
+ XtManageChild (down);
+ XtManageChild (buttonbox1);
+
+ XtManageChild (list);
+ XtManageChild (scroller);
+ XtManageChild (form2);
+
+ XtManageChild (form1);
+
+ XtVaSetValues (form1,
+ XmNleftAttachment, XmATTACH_FORM,
+ XmNrightAttachment, XmATTACH_FORM,
+ XmNtopAttachment, XmATTACH_FORM,
+ XmNbottomAttachment, XmATTACH_FORM,
+ NULL);
+
+ return form1;
+}
+
+
+
+static Widget
+create_options_page (Widget parent)
+{
+ /* This is what the layout is today:
+
+ Form (horizontal)
+ Label ("Saver Timeout")
+ Label ("Cycle Timeout")
+ Label ("Fade Duration")
+ Label ("Fade Ticks")
+ Label ("Lock Timeout")
+ Label ("Password Timeout")
+
+ Text (timeout)
+ Text (cycle)
+ Text (fade seconds)
+ Text (fade ticks)
+ Text (lock)
+ Text (passwd)
+
+ Toggle ("Verbose")
+ Toggle ("Install Colormap")
+ Toggle ("Fade Colormap")
+ Toggle ("Unfade Colormap")
+ Toggle ("Require Password")
+
+ HR
+ Button ("OK")
+ Button ("Cancel")
+ */
+
+ /* This is what it should be:
+
+ Form (horizontal)
+ Form (vertical) ("column1")
+ Frame
+ Label ("Blanking and Locking")
+ Form
+ Label ("Blank After")
+ Label ("Cycle After")
+ Text ("Blank After")
+ Text ("Cycle After")
+ HR
+ Checkbox ("Require Password")
+ Label ("Lock After")
+ Text ("Lock After")
+ Frame
+ Label ("Image Manipulation")
+ Form
+ Checkbox ("Grab Desktop Images")
+ Checkbox ("Grab Video Frames")
+ Checkbox ("Choose Random Image")
+ Text (pathname)
+ Button ("Browse")
+ Frame
+ Label ("Diagnostics")
+ Form
+ Checkbox ("Verbose Diagnostics")
+ Checkbox ("Display Subprocess Errors")
+ Checkbox ("Display Splash Screen at Startup")
+ Form (vertical) ("column2")
+ Frame
+ Label ("Display Power Management")
+ Form
+ Checkbox ("Power Management Enabled")
+ Label ("Standby After")
+ Label ("Suspend After")
+ Label ("Off After")
+ Text ("Standby After")
+ Text ("Suspend After")
+ Text ("Off After")
+ Frame
+ Label ("Colormaps")
+ Form
+ Checkbox ("Install Colormap")
+ HR
+ Checkbox ("Fade To Black When Blanking")
+ Checkbox ("Fade From Black When Unblanking")
+ Label ("Fade Duration")
+ Text ("Fade Duration")
+
+ timeoutLabel
+ cycleLabel
+ fadeSecondsLabel
+ fadeTicksLabel
+ lockLabel
+ passwdLabel
+
+ timeoutText
+ cycleText
+ fadeSecondsText
+ fadeTicksText
+ lockText
+ passwdText
+
+ verboseToggle
+ cmapToggle
+ fadeToggle
+ unfadeToggle
+ lockToggle
+
+ separator
+ OK
+ Cancel
+ */
+
+
+
+ Arg av[64];
+ int ac = 0;
+ Widget children[100];
+ Widget timeout_label, cycle_label, fade_seconds_label, fade_ticks_label;
+ Widget lock_label, passwd_label, hr;
+ Widget preferences_form;
+
+ Widget timeout_text, cycle_text, fade_text, fade_ticks_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;
+
+ ac = 0;
+ XtSetArg (av [ac], XmNdialogType, XmDIALOG_PROMPT); ac++;
+
+ ac = 0;
+ XtSetArg (av [ac], XmNtopAttachment, XmATTACH_FORM); ac++;
+ XtSetArg (av [ac], XmNbottomAttachment, XmATTACH_FORM); ac++;
+ XtSetArg (av [ac], XmNleftAttachment, XmATTACH_FORM); ac++;
+ XtSetArg (av [ac], XmNrightAttachment, XmATTACH_FORM); ac++;
+ preferences_form = XmCreateForm (parent, "preferencesForm", av, ac);
+ XtManageChild (preferences_form);
+
+ ac = 0;
+
+ XtSetArg(av[ac], XmNalignment, XmALIGNMENT_END); ac++;
+ timeout_label = XmCreateLabelGadget (preferences_form, "timeoutLabel",
+ av, ac);
+ ac = 0;
+ XtSetArg(av[ac], XmNalignment, XmALIGNMENT_END); ac++;
+ cycle_label = XmCreateLabelGadget (preferences_form, "cycleLabel",
+ av, ac);
+ ac = 0;
+ XtSetArg(av[ac], XmNalignment, XmALIGNMENT_END); ac++;
+ fade_seconds_label = XmCreateLabelGadget (preferences_form,
+ "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++;
+ passwd_label = XmCreateLabelGadget (preferences_form, "passwdLabel", av, ac);
+ ac = 0;
+ 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",
+ av, ac);
+ XtSetArg(av[ac], XmNalignment, XmALIGNMENT_BEGINNING); ac++;
+ verbose_toggle = XmCreateToggleButtonGadget (preferences_form,
+ "verboseToggle", av, ac);
+ ac = 0;
+ XtSetArg(av[ac], XmNalignment, XmALIGNMENT_BEGINNING); ac++;
+ install_cmap_toggle = XmCreateToggleButtonGadget (preferences_form,
+ "cmapToggle", av, ac);
+ ac = 0;
+ XtSetArg(av[ac], XmNalignment, XmALIGNMENT_BEGINNING); ac++;
+ fade_toggle = XmCreateToggleButtonGadget (preferences_form, "fadeToggle",
+ av, ac);
+ ac = 0;
+ XtSetArg(av[ac], XmNalignment, XmALIGNMENT_BEGINNING); ac++;
+ unfade_toggle = XmCreateToggleButtonGadget (preferences_form, "unfadeToggle",
+ av,ac);
+ ac = 0;
+ XtSetArg(av[ac], XmNalignment, XmALIGNMENT_BEGINNING); ac++;
+ lock_toggle = XmCreateToggleButtonGadget (preferences_form, "lockToggle",
+ av, ac);
+ ac = 0;
+ hr = XmCreateSeparatorGadget (preferences_form, "separator", av, ac);
+
+ prefs_done = XmCreatePushButtonGadget (preferences_form, "OK", av, ac);
+ prefs_cancel = XmCreatePushButtonGadget (preferences_form, "Cancel", av, ac);
+
+ XtVaSetValues (timeout_label,
+ XmNtopAttachment, XmATTACH_FORM,
+ XmNtopOffset, 4,
+ XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET,
+ XmNbottomWidget, timeout_text,
+ XmNleftAttachment, XmATTACH_FORM,
+ XmNleftOffset, 20,
+ XmNrightAttachment, XmATTACH_WIDGET,
+ XmNrightOffset, 4,
+ XmNrightWidget, timeout_text,
+ NULL);
+
+ XtVaSetValues (cycle_label,
+ XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET,
+ XmNtopOffset, 0,
+ XmNtopWidget, cycle_text,
+ XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET,
+ XmNbottomOffset, 0,
+ XmNbottomWidget, cycle_text,
+ XmNleftAttachment, XmATTACH_FORM,
+ XmNleftOffset, 20,
+ XmNrightAttachment, XmATTACH_WIDGET,
+ XmNrightOffset, 4,
+ XmNrightWidget, cycle_text,
+ NULL);
+
+ XtVaSetValues (fade_seconds_label,
+ XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET,
+ XmNtopOffset, 0,
+ XmNtopWidget, fade_text,
+ XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET,
+ XmNbottomOffset, 0,
+ XmNbottomWidget, fade_text,
+ XmNleftAttachment, XmATTACH_FORM,
+ XmNleftOffset, 20,
+ XmNrightAttachment, XmATTACH_WIDGET,
+ XmNrightOffset, 4,
+ 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,
+ XmNtopWidget, lock_timeout_text,
+ XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET,
+ XmNbottomOffset, 0,
+ XmNbottomWidget, lock_timeout_text,
+ XmNleftAttachment, XmATTACH_FORM,
+ XmNleftOffset, 19,
+ XmNrightAttachment, XmATTACH_WIDGET,
+ XmNrightOffset, 4,
+ XmNrightWidget, lock_timeout_text,
+ NULL);
+
+ XtVaSetValues (passwd_label,
+ XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET,
+ XmNtopOffset, 0,
+ XmNtopWidget, passwd_timeout_text,
+ XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET,
+ XmNbottomOffset, 0,
+ XmNbottomWidget, passwd_timeout_text,
+ XmNleftAttachment, XmATTACH_FORM,
+ XmNleftOffset, 14,
+ XmNrightAttachment, XmATTACH_WIDGET,
+ XmNrightOffset, 4,
+ XmNrightWidget, passwd_timeout_text,
+ NULL);
+
+ XtVaSetValues (timeout_text,
+ XmNtopAttachment, XmATTACH_FORM,
+ XmNtopOffset, 4,
+ XmNleftAttachment, XmATTACH_FORM,
+ XmNleftOffset, 141,
+ NULL);
+
+ XtVaSetValues (cycle_text,
+ XmNtopAttachment, XmATTACH_WIDGET,
+ XmNtopOffset, 2,
+ XmNtopWidget, timeout_text,
+ XmNleftAttachment, XmATTACH_OPPOSITE_WIDGET,
+ XmNleftOffset, 0,
+ XmNleftWidget, timeout_text,
+ NULL);
+
+ XtVaSetValues (fade_text,
+ XmNtopAttachment, XmATTACH_WIDGET,
+ XmNtopOffset, 2,
+ XmNtopWidget, cycle_text,
+ XmNleftAttachment, XmATTACH_OPPOSITE_WIDGET,
+ XmNleftOffset, 0,
+ XmNleftWidget, cycle_text,
+ NULL);
+
+ XtVaSetValues (fade_ticks_text,
+ XmNtopAttachment, XmATTACH_WIDGET,
+ XmNtopOffset, 2,
+ XmNtopWidget, fade_text,
+ XmNleftAttachment, XmATTACH_OPPOSITE_WIDGET,
+ XmNleftOffset, 0,
+ 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,
+ XmNtopWidget, lock_timeout_text,
+ XmNleftAttachment, XmATTACH_OPPOSITE_WIDGET,
+ XmNleftOffset, 0,
+ XmNleftWidget, lock_timeout_text,
+ NULL);
+
+ XtVaSetValues (verbose_toggle,
+ XmNtopAttachment, XmATTACH_FORM,
+ XmNtopOffset, 4,
+ XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET,
+ XmNbottomOffset, 0,
+ XmNbottomWidget, timeout_text,
+ XmNleftAttachment, XmATTACH_WIDGET,
+ XmNleftOffset, 20,
+ XmNleftWidget, timeout_text,
+ XmNrightAttachment, XmATTACH_FORM,
+ XmNrightOffset, 20,
+ NULL);
+
+ XtVaSetValues (install_cmap_toggle,
+ XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET,
+ XmNtopOffset, 0,
+ XmNtopWidget, cycle_text,
+ XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET,
+ XmNbottomOffset, 0,
+ XmNbottomWidget, cycle_text,
+ XmNleftAttachment, XmATTACH_OPPOSITE_WIDGET,
+ XmNleftOffset, 0,
+ XmNleftWidget, verbose_toggle,
+ XmNrightAttachment, XmATTACH_FORM,
+ XmNrightOffset, 20,
+ NULL);
+
+ XtVaSetValues (fade_toggle,
+ XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET,
+ XmNtopOffset, 0,
+ XmNtopWidget, fade_text,
+ XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET,
+ XmNbottomOffset, 0,
+ XmNbottomWidget, fade_text,
+ XmNleftAttachment, XmATTACH_OPPOSITE_WIDGET,
+ XmNleftOffset, 0,
+ XmNleftWidget, install_cmap_toggle,
+ XmNrightAttachment, XmATTACH_FORM,
+ XmNrightOffset, 20,
+ NULL);
+
+ XtVaSetValues (unfade_toggle,
+ XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET,
+ XmNtopOffset, 0,
+ XmNtopWidget, fade_ticks_text,
+ XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET,
+ XmNbottomOffset, 0,
+ XmNbottomWidget, fade_ticks_text,
+ XmNleftAttachment, XmATTACH_OPPOSITE_WIDGET,
+ XmNleftOffset, 0,
+ XmNleftWidget, fade_toggle,
+ XmNrightAttachment, XmATTACH_FORM,
+ XmNrightOffset, 20,
+ NULL);
+
+ XtVaSetValues (lock_toggle,
+ XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET,
+ XmNtopOffset, 0,
+ XmNtopWidget, lock_timeout_text,
+ XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET,
+ XmNbottomOffset, 0,
+ XmNbottomWidget, lock_timeout_text,
+ XmNleftAttachment, XmATTACH_OPPOSITE_WIDGET,
+ XmNleftOffset, 0,
+ XmNleftWidget, unfade_toggle,
+ XmNrightAttachment, XmATTACH_FORM,
+ XmNrightOffset, 20,
+ NULL);
+
+ XtVaSetValues (hr,
+ XmNtopWidget, passwd_timeout_text,
+ XmNbottomAttachment, XmATTACH_FORM,
+ XmNbottomOffset, 4,
+ XmNleftAttachment, XmATTACH_FORM,
+ XmNrightAttachment, XmATTACH_FORM,
+ NULL);
+
+ XtVaSetValues (prefs_done,
+ XmNleftAttachment, XmATTACH_FORM,
+ XmNbottomAttachment, XmATTACH_FORM,
+ NULL);
+ XtVaSetValues (prefs_cancel,
+ XmNrightAttachment, XmATTACH_FORM,
+ XmNbottomAttachment, XmATTACH_FORM,
+ NULL);
+ XtVaSetValues (hr,
+ XmNbottomAttachment, XmATTACH_WIDGET,
+ XmNbottomWidget, prefs_done,
+ NULL);
+
+ ac = 0;
+ 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;
+ children[ac++] = install_cmap_toggle;
+ children[ac++] = fade_toggle;
+ children[ac++] = unfade_toggle;
+ children[ac++] = lock_toggle;
+ children[ac++] = hr;
+
+ XtManageChildren(children, ac);
+ ac = 0;
+
+ XtManageChild (prefs_done);
+ XtManageChild (prefs_cancel);
+
+ XtManageChild (preferences_form);
+
+ XtVaSetValues (preferences_form,
+ XmNleftAttachment, XmATTACH_FORM,
+ XmNrightAttachment, XmATTACH_FORM,
+ XmNtopAttachment, XmATTACH_FORM,
+ XmNbottomAttachment, XmATTACH_FORM,
+ NULL);
+
+ return preferences_form;
+}
diff --git a/driver/demo-Xm.c b/driver/demo-Xm.c
new file mode 100644
index 0000000..149e7c5
--- /dev/null
+++ b/driver/demo-Xm.c
@@ -0,0 +1,1875 @@
+/* demo-Xm.c --- implements the interactive demo-mode and options dialogs.
+ * xscreensaver, Copyright (c) 1993-2003, 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
+ * 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
+
+#ifdef HAVE_MOTIF /* whole file */
+
+#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 <X11/Xproto.h> /* for CARD32 */
+#include <X11/Xatom.h> /* for XA_INTEGER */
+#include <X11/Intrinsic.h>
+#include <X11/StringDefs.h>
+
+/* We don't actually use any widget internals, but these are included
+ so that gdb will have debug info for the widgets... */
+#include <X11/IntrinsicP.h>
+#include <X11/ShellP.h>
+
+#ifdef HAVE_XPM
+# 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>
+#include <Xm/LabelG.h>
+#include <Xm/RowColumn.h>
+#include <Xm/MessageB.h>
+
+#ifdef HAVE_XMCOMBOBOX /* a Motif 2.0 widget */
+# include <Xm/ComboBox.h>
+# ifndef XmNtextField /* Lesstif 0.89.4 bug */
+# undef HAVE_XMCOMBOBOX
+# endif
+# if (XmVersion < 2001) /* Lesstif has two personalities these days */
+# undef HAVE_XMCOMBOBOX
+# endif
+#endif /* HAVE_XMCOMBOBOX */
+
+#include "version.h"
+#include "prefs.h"
+#include "resources.h" /* for parse_time() */
+#include "visual.h" /* for has_writable_cells() */
+#include "remote.h" /* for xscreensaver_command() */
+#include "usleep.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#undef countof
+#define countof(x) (sizeof((x))/sizeof((*x)))
+
+
+char *progname = 0;
+char *progclass = "XScreenSaver";
+XrmDatabase db;
+
+typedef struct {
+ saver_preferences *a, *b;
+} prefs_pair;
+
+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_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);
+static int apply_changes_and_save (Widget widget);
+static int maybe_reload_init_file (Widget widget, prefs_pair *pair);
+static void await_xscreensaver (Widget widget);
+
+
+/* Some random utility functions
+ */
+
+static Widget
+name_to_widget (Widget widget, const char *name)
+{
+ Widget parent;
+ char name2[255];
+ name2[0] = '*';
+ strcpy (name2+1, name);
+
+ while ((parent = XtParent (widget)))
+ widget = parent;
+ return XtNameToWidget (widget, name2);
+}
+
+
+
+/* Why this behavior isn't automatic in *either* toolkit, I'll never know.
+ Takes a scroller, viewport, or list as an argument.
+ */
+static void
+ensure_selected_item_visible (Widget list)
+{
+ int *pos_list = 0;
+ int pos_count = 0;
+ if (XmListGetSelectedPos (list, &pos_list, &pos_count) && pos_count > 0)
+ {
+ int top = -2;
+ int visible = 0;
+ XtVaGetValues (list,
+ XmNtopItemPosition, &top,
+ XmNvisibleItemCount, &visible,
+ NULL);
+ if (pos_list[0] >= top + visible)
+ {
+ int pos = pos_list[0] - visible + 1;
+ if (pos < 0) pos = 0;
+ XmListSetPos (list, pos);
+ }
+ else if (pos_list[0] < top)
+ {
+ XmListSetPos (list, pos_list[0]);
+ }
+ }
+ if (pos_list)
+ XtFree ((char *) pos_list);
+}
+
+
+static void
+warning_dialog_dismiss_cb (Widget button, XtPointer client_data,
+ XtPointer user_data)
+{
+ Widget shell = (Widget) client_data;
+ XtDestroyWidget (shell);
+}
+
+
+static void
+warning_dialog (Widget parent, const char *message, int center)
+{
+ char *msg = strdup (message);
+ char *head;
+
+ Widget dialog = 0;
+ Widget label = 0;
+ Widget ok = 0;
+ int i = 0;
+
+ Widget w;
+ Widget container;
+ XmString xmstr;
+ Arg av[10];
+ int ac = 0;
+
+ ac = 0;
+ dialog = XmCreateWarningDialog (parent, "warning", av, ac);
+
+ w = XmMessageBoxGetChild (dialog, XmDIALOG_MESSAGE_LABEL);
+ if (w) XtUnmanageChild (w);
+ w = XmMessageBoxGetChild (dialog, XmDIALOG_CANCEL_BUTTON);
+ if (w) XtUnmanageChild (w);
+ w = XmMessageBoxGetChild (dialog, XmDIALOG_HELP_BUTTON);
+ if (w) XtUnmanageChild (w);
+
+ ok = XmMessageBoxGetChild (dialog, XmDIALOG_OK_BUTTON);
+
+ ac = 0;
+ XtSetArg (av[ac], XmNnumColumns, 1); ac++;
+ XtSetArg (av[ac], XmNorientation, XmVERTICAL); ac++;
+ XtSetArg (av[ac], XmNpacking, XmPACK_COLUMN); ac++;
+ XtSetArg (av[ac], XmNrowColumnType, XmWORK_AREA); ac++;
+ XtSetArg (av[ac], XmNspacing, 0); ac++;
+ container = XmCreateRowColumn (dialog, "container", av, ac);
+
+ head = msg;
+ while (head)
+ {
+ char name[20];
+ char *s = strchr (head, '\n');
+ if (s) *s = 0;
+
+ sprintf (name, "label%d", i++);
+
+ xmstr = XmStringCreate (head, XmSTRING_DEFAULT_CHARSET);
+ ac = 0;
+ XtSetArg (av[ac], XmNlabelString, xmstr); ac++;
+ XtSetArg (av[ac], XmNmarginHeight, 0); ac++;
+ label = XmCreateLabelGadget (container, name, av, ac);
+ XtManageChild (label);
+ XmStringFree (xmstr);
+
+ if (s)
+ head = s+1;
+ else
+ head = 0;
+
+ center--;
+ }
+
+ XtManageChild (container);
+ XtRealizeWidget (dialog);
+ XtManageChild (dialog);
+
+ XtAddCallback (ok, XmNactivateCallback, warning_dialog_dismiss_cb, dialog);
+
+ free (msg);
+}
+
+
+static void
+run_cmd (Widget widget, Atom command, int arg)
+{
+ char *err = 0;
+ int status;
+
+ apply_changes_and_save (widget);
+ status = xscreensaver_command (XtDisplay (widget),
+ command, arg, False, &err);
+ if (status < 0)
+ {
+ char buf [255];
+ if (err)
+ sprintf (buf, "Error:\n\n%s", err);
+ else
+ strcpy (buf, "Unknown error!");
+ warning_dialog (widget, buf, 100);
+ }
+ if (err) free (err);
+}
+
+
+static void
+run_hack (Widget widget, int which, Bool report_errors_p)
+{
+ if (which < 0) return;
+ apply_changes_and_save (widget);
+ if (report_errors_p)
+ run_cmd (widget, XA_DEMO, which + 1);
+ else
+ {
+ char *s = 0;
+ xscreensaver_command (XtDisplay (widget), XA_DEMO, which + 1, False, &s);
+ if (s) free (s);
+ }
+}
+
+
+
+/* Button callbacks
+ */
+
+void
+exit_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
+{
+ apply_changes_and_save (XtParent (button));
+ exit (0);
+}
+
+#if 0
+static void
+wm_close_cb (Widget widget, GdkEvent *event, XtPointer data)
+{
+ apply_changes_and_save (XtParent (button));
+ exit (0);
+}
+#endif
+
+void
+cut_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
+{
+ /* #### */
+ warning_dialog (XtParent (button),
+ "Error:\n\n"
+ "cut unimplemented\n", 1);
+}
+
+
+void
+copy_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
+{
+ /* #### */
+ warning_dialog (XtParent (button),
+ "Error:\n\n"
+ "copy unimplemented\n", 1);
+}
+
+
+void
+paste_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
+{
+ /* #### */
+ warning_dialog (XtParent (button),
+ "Error:\n\n"
+ "paste unimplemented\n", 1);
+}
+
+
+void
+about_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
+{
+ char buf [2048];
+ char *s = strdup (screensaver_id + 4);
+ char *s2;
+
+ s2 = strchr (s, ',');
+ *s2 = 0;
+ s2 += 2;
+
+ 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"
+ "instead, which has many more features.\n"
+ "\n"
+ "For xscreensaver updates, check https://www.jwz.org/xscreensaver/",
+ s, s2);
+ free (s);
+
+ warning_dialog (XtParent (button), buf, 100);
+}
+
+
+void
+doc_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
+{
+ prefs_pair *pair = (prefs_pair *) client_data;
+
+ saver_preferences *p = pair->a;
+ char *help_command;
+
+ if (!p->help_url || !*p->help_url)
+ {
+ warning_dialog (XtParent (button),
+ "Error:\n\n"
+ "No Help URL has been specified.\n", 100);
+ return;
+ }
+
+ help_command = (char *) malloc (strlen (p->load_url_command) +
+ (strlen (p->help_url) * 4) + 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);
+ strcat (help_command, " ) &");
+ system (help_command);
+ free (help_command);
+}
+
+
+void
+activate_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
+{
+ run_cmd (XtParent (button), XA_ACTIVATE, 0);
+}
+
+
+void
+lock_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
+{
+ run_cmd (XtParent (button), XA_LOCK, 0);
+}
+
+
+void
+kill_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
+{
+ run_cmd (XtParent (button), XA_EXIT, 0);
+}
+
+
+void
+restart_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
+{
+#if 0
+ run_cmd (XtParent (button), XA_RESTART, 0);
+#else
+ button = XtParent (button);
+ apply_changes_and_save (button);
+ xscreensaver_command (XtDisplay (button), XA_EXIT, 0, False, NULL);
+ sleep (1);
+ system ("xscreensaver -nosplash &");
+#endif
+
+ await_xscreensaver (button);
+}
+
+static void
+await_xscreensaver (Widget widget)
+{
+ int countdown = 5;
+
+ Display *dpy = XtDisplay (widget);
+ char *rversion = 0;
+
+ while (!rversion && (--countdown > 0))
+ {
+ /* Check for the version of the running xscreensaver... */
+ server_xscreensaver_version (dpy, &rversion, 0, 0);
+
+ /* If it's not there yet, wait a second... */
+ sleep (1);
+ }
+
+ if (rversion)
+ {
+ /* Got it. */
+ free (rversion);
+ }
+ else
+ {
+ /* Timed out, no screensaver running. */
+
+ char buf [1024];
+ Bool root_p = (geteuid () == 0);
+
+ strcpy (buf,
+ "Error:\n\n"
+ "The xscreensaver daemon did not start up properly.\n"
+ "\n");
+
+ if (root_p)
+# ifdef __GNUC__
+ __extension__ /* don't warn about "string length is greater than
+ the length ISO C89 compilers are required to
+ support" in the following expression... */
+# endif
+ strcat (buf,
+ "You are running as root. This usually means that xscreensaver\n"
+ "was unable to contact your X server because access control is\n"
+ "turned on. Try running this command:\n"
+ "\n"
+ " xhost +localhost\n"
+ "\n"
+ "and then selecting `File / Restart Daemon'.\n"
+ "\n"
+ "Note that turning off access control will allow anyone logged\n"
+ "on to this machine to access your screen, which might be\n"
+ "considered a security problem. Please read the xscreensaver\n"
+ "manual and FAQ for more information.\n"
+ "\n"
+ "You shouldn't run X as root. Instead, you should log in as a\n"
+ "normal user, and `su' as necessary.");
+ else
+ strcat (buf, "Please check your $PATH and permissions.");
+
+ warning_dialog (XtParent (widget), buf, 1);
+ }
+}
+
+
+static int _selected_hack_number = -1;
+
+static int
+selected_hack_number (Widget toplevel)
+{
+ return _selected_hack_number;
+}
+
+
+static int
+demo_write_init_file (Widget widget, saver_preferences *p)
+{
+ if (!write_init_file (XtDisplay (widget), p, short_version, False))
+ return 0;
+ else
+ {
+ const char *f = init_file_name();
+ if (!f || !*f)
+ warning_dialog (widget,
+ "Error:\n\nCouldn't determine init file name!\n",
+ 100);
+ else
+ {
+ char *b = (char *) malloc (strlen(f) + 1024);
+ sprintf (b, "Error:\n\nCouldn't write %s\n", f);
+ warning_dialog (widget, b, 100);
+ free (b);
+ }
+ return -1;
+ }
+}
+
+
+static int
+apply_changes_and_save (Widget widget)
+{
+ prefs_pair *pair = global_prefs_pair;
+ saver_preferences *p = pair->a;
+ Widget list_widget = name_to_widget (widget, "list");
+ int which = selected_hack_number (widget);
+
+ Widget cmd = name_to_widget (widget, "cmdText");
+ Widget enabled = name_to_widget (widget, "enabled");
+
+ Widget vis = name_to_widget (widget, "combo");
+# ifdef HAVE_XMCOMBOBOX
+ Widget text = 0;
+# else /* !HAVE_XMCOMBOBOX */
+ Widget menu = 0, *kids = 0, selected_item = 0;
+ Cardinal nkids = 0;
+ int i = 0;
+# endif /* !HAVE_XMCOMBOBOX */
+
+ Bool enabled_p = False;
+ const char *visual = 0;
+ const char *command = 0;
+
+ char c;
+ unsigned long id;
+
+ if (which < 0) return -1;
+
+# ifdef HAVE_XMCOMBOBOX
+ XtVaGetValues (vis, XmNtextField, &text, NULL);
+ if (!text)
+ /* If we can't get at the text field of this combo box, we're screwed. */
+ abort();
+ XtVaGetValues (text, XmNvalue, &visual, NULL);
+
+# else /* !HAVE_XMCOMBOBOX */
+ XtVaGetValues (vis, XmNsubMenuId, &menu, NULL);
+ XtVaGetValues (menu, XmNnumChildren, &nkids, XmNchildren, &kids, NULL);
+ XtVaGetValues (menu, XmNmenuHistory, &selected_item, NULL);
+ if (selected_item)
+ for (i = 0; i < nkids; i++)
+ if (kids[i] == selected_item)
+ break;
+
+ visual = visual_menu[i];
+# endif /* !HAVE_XMCOMBOBOX */
+
+ XtVaGetValues (enabled, XmNset, &enabled_p, NULL);
+ XtVaGetValues (cmd, XtNvalue, &command, NULL);
+
+ if (maybe_reload_init_file (widget, pair) != 0)
+ return 1;
+
+ /* Sanity-check and canonicalize whatever the user typed into the combo box.
+ */
+ if (!strcasecmp (visual, "")) visual = "";
+ else if (!strcasecmp (visual, "any")) visual = "";
+ else if (!strcasecmp (visual, "default")) visual = "Default";
+ else if (!strcasecmp (visual, "default-n")) visual = "Default-N";
+ else if (!strcasecmp (visual, "default-i")) visual = "Default-I";
+ else if (!strcasecmp (visual, "best")) visual = "Best";
+ else if (!strcasecmp (visual, "mono")) visual = "Mono";
+ else if (!strcasecmp (visual, "monochrome")) visual = "Mono";
+ else if (!strcasecmp (visual, "gray")) visual = "Gray";
+ else if (!strcasecmp (visual, "grey")) visual = "Gray";
+ else if (!strcasecmp (visual, "color")) visual = "Color";
+ else if (!strcasecmp (visual, "gl")) visual = "GL";
+ else if (!strcasecmp (visual, "staticgray")) visual = "StaticGray";
+ else if (!strcasecmp (visual, "staticcolor")) visual = "StaticColor";
+ else if (!strcasecmp (visual, "truecolor")) visual = "TrueColor";
+ else if (!strcasecmp (visual, "grayscale")) visual = "GrayScale";
+ else if (!strcasecmp (visual, "greyscale")) visual = "GrayScale";
+ else if (!strcasecmp (visual, "pseudocolor")) visual = "PseudoColor";
+ else if (!strcasecmp (visual, "directcolor")) visual = "DirectColor";
+ else if (1 == sscanf (visual, " %lu %c", &id, &c)) ;
+ else if (1 == sscanf (visual, " 0x%lx %c", &id, &c)) ;
+ else
+ {
+ XBell (XtDisplay (widget), 0); /* unparsable */
+ visual = "";
+ /* #### gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry), "Any");*/
+ }
+
+ ensure_selected_item_visible (list_widget);
+
+ if (!p->screenhacks[which]->visual)
+ p->screenhacks[which]->visual = strdup ("");
+ if (!p->screenhacks[which]->command)
+ p->screenhacks[which]->command = strdup ("");
+
+ if (p->screenhacks[which]->enabled_p != enabled_p ||
+ !!strcasecmp (p->screenhacks[which]->visual, visual) ||
+ !!strcasecmp (p->screenhacks[which]->command, command))
+ {
+ /* Something was changed -- store results into the struct,
+ and write the file.
+ */
+ free (p->screenhacks[which]->visual);
+ free (p->screenhacks[which]->command);
+ p->screenhacks[which]->visual = strdup (visual);
+ p->screenhacks[which]->command = strdup (command);
+ p->screenhacks[which]->enabled_p = enabled_p;
+
+ return demo_write_init_file (widget, p);
+ }
+
+ /* No changes made */
+ return 0;
+}
+
+void
+run_this_cb (Widget button, XtPointer client_data, XtPointer ignored)
+{
+ int which = selected_hack_number (XtParent (button));
+ if (which < 0) return;
+ if (0 == apply_changes_and_save (XtParent (button)))
+ run_hack (XtParent (button), which, True);
+}
+
+
+void
+manual_cb (Widget button, XtPointer client_data, XtPointer ignored)
+{
+ prefs_pair *pair = (prefs_pair *) client_data;
+ saver_preferences *p = pair->a;
+ Widget list_widget = name_to_widget (button, "list");
+ int which = selected_hack_number (button);
+ char *name, *name2, *cmd, *s;
+ if (which < 0) return;
+ apply_changes_and_save (button);
+ ensure_selected_item_visible (list_widget);
+
+ name = strdup (p->screenhacks[which]->command);
+ name2 = name;
+ while (isspace (*name2)) name2++;
+ s = name2;
+ while (*s && !isspace (*s)) s++;
+ *s = 0;
+ s = strrchr (name2, '/');
+ if (s) name = s+1;
+
+ cmd = get_string_resource (XtDisplay (button), "manualCommand", "ManualCommand");
+ if (cmd)
+ {
+ char *cmd2 = (char *) malloc (strlen (cmd) + (strlen (name2) * 4) + 100);
+ strcpy (cmd2, "( ");
+ sprintf (cmd2 + strlen (cmd2),
+ cmd,
+ name2, name2, name2, name2);
+ strcat (cmd2, " ) &");
+ system (cmd2);
+ free (cmd2);
+ }
+ else
+ {
+ warning_dialog (XtParent (button),
+ "Error:\n\nno `manualCommand' resource set.",
+ 100);
+ }
+
+ free (name);
+}
+
+
+void
+run_next_cb (Widget button, XtPointer client_data, XtPointer ignored)
+{
+ prefs_pair *pair = (prefs_pair *) client_data;
+ saver_preferences *p = pair->a;
+
+ Widget list_widget = name_to_widget (button, "list");
+ int which = selected_hack_number (button);
+
+ button = XtParent (button);
+
+ if (which < 0)
+ which = 0;
+ else
+ which++;
+
+ if (which >= p->screenhacks_count)
+ which = 0;
+
+ apply_changes_and_save (button);
+
+ XmListDeselectAllItems (list_widget); /* LessTif lossage */
+ XmListSelectPos (list_widget, which+1, True);
+
+ ensure_selected_item_visible (list_widget);
+ populate_demo_window (button, which, pair);
+ run_hack (button, which, False);
+}
+
+
+void
+run_prev_cb (Widget button, XtPointer client_data, XtPointer ignored)
+{
+ prefs_pair *pair = (prefs_pair *) client_data;
+ saver_preferences *p = pair->a;
+
+ Widget list_widget = name_to_widget (button, "list");
+ int which = selected_hack_number (button);
+
+ button = XtParent (button);
+
+ if (which < 0)
+ which = p->screenhacks_count - 1;
+ else
+ which--;
+
+ if (which < 0)
+ which = p->screenhacks_count - 1;
+
+ apply_changes_and_save (button);
+
+ XmListDeselectAllItems (list_widget); /* LessTif lossage */
+ XmListSelectPos (list_widget, which+1, True);
+
+ ensure_selected_item_visible (list_widget);
+ populate_demo_window (button, which, pair);
+ run_hack (button, which, False);
+}
+
+
+/* Helper for the text fields that contain time specifications:
+ this parses the text, and does error checking.
+ */
+static void
+hack_time_text (Widget button, const char *line, Time *store, Bool sec_p)
+{
+ if (*line)
+ {
+ int value;
+ value = parse_time ((char *) line, sec_p, True);
+ value *= 1000; /* Time measures in microseconds */
+ if (value < 0)
+ {
+ char b[255];
+ sprintf (b,
+ "Error:\n\n"
+ "Unparsable time format: \"%s\"\n",
+ line);
+ warning_dialog (XtParent (button), b, 100);
+ }
+ else
+ *store = value;
+ }
+}
+
+
+void
+prefs_ok_cb (Widget button, XtPointer client_data, XtPointer ignored)
+{
+ prefs_pair *pair = (prefs_pair *) client_data;
+
+ saver_preferences *p = pair->a;
+ saver_preferences *p2 = pair->b;
+ Bool changed = False;
+ char *v = 0;
+
+ button = XtParent (button);
+
+# define SECONDS(field, name) \
+ v = 0; \
+ XtVaGetValues (name_to_widget (button, (name)), XtNvalue, &v, NULL); \
+ hack_time_text (button, v, (field), True)
+
+# define MINUTES(field, name) \
+ v = 0; \
+ XtVaGetValues (name_to_widget (button, (name)), XtNvalue, &v, NULL); \
+ hack_time_text (button, v, (field), False)
+
+# define INTEGER(field, name) do { \
+ unsigned int value; \
+ char c; \
+ XtVaGetValues (name_to_widget (button, (name)), XtNvalue, &v, NULL); \
+ if (! *v) \
+ ; \
+ else if (sscanf (v, "%u%c", &value, &c) != 1) \
+ { \
+ char b[255]; \
+ sprintf (b, "Error:\n\n" "Not an integer: \"%s\"\n", v); \
+ warning_dialog (XtParent (button), b, 100); \
+ } \
+ else \
+ *(field) = value; \
+ } while(0)
+
+# define CHECKBOX(field, name) \
+ XtVaGetValues (name_to_widget (button, (name)), XmNset, &field, NULL)
+
+ 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");
+ CHECKBOX (p2->install_cmap_p, "cmapToggle");
+ CHECKBOX (p2->fade_p, "fadeToggle");
+ CHECKBOX (p2->unfade_p, "unfadeToggle");
+ CHECKBOX (p2->lock_p, "lockToggle");
+
+# undef SECONDS
+# undef MINUTES
+# undef INTEGER
+# undef CHECKBOX
+
+# define COPY(field) \
+ if (p->field != p2->field) changed = True; \
+ p->field = p2->field
+
+ COPY(timeout);
+ COPY(cycle);
+ COPY(lock_timeout);
+ COPY(passwd_timeout);
+ COPY(fade_seconds);
+ COPY(fade_ticks);
+ COPY(verbose_p);
+ COPY(install_cmap_p);
+ COPY(fade_p);
+ COPY(unfade_p);
+ COPY(lock_p);
+# undef COPY
+
+ populate_prefs_page (button, pair);
+
+ if (changed)
+ demo_write_init_file (button, p);
+}
+
+
+void
+prefs_cancel_cb (Widget button, XtPointer client_data, XtPointer ignored)
+{
+ prefs_pair *pair = (prefs_pair *) client_data;
+
+ *pair->b = *pair->a;
+ populate_prefs_page (XtParent (button), pair);
+}
+
+
+static void
+list_select_cb (Widget list, XtPointer client_data, XtPointer call_data)
+{
+ prefs_pair *pair = (prefs_pair *) client_data;
+
+ XmListCallbackStruct *lcb = (XmListCallbackStruct *) call_data;
+ int which = lcb->item_position - 1;
+
+ apply_changes_and_save (list);
+ populate_demo_window (list, which, pair);
+
+ if (lcb->reason == XmCR_DEFAULT_ACTION && which >= 0)
+ run_hack (list, which, True);
+}
+
+
+/* Populating the various widgets
+ */
+
+
+/* Formats a `Time' into "H:MM:SS". (Time is microseconds.)
+ */
+static void
+format_time (char *buf, Time time)
+{
+ int s = time / 1000;
+ unsigned int h = 0, m = 0;
+ if (s >= 60)
+ {
+ m += (s / 60);
+ s %= 60;
+ }
+ if (m >= 60)
+ {
+ h += (m / 60);
+ m %= 60;
+ }
+ sprintf (buf, "%u:%02u:%02u", h, m, s);
+}
+
+
+/* Finds the number of the last hack to run, and makes that item be
+ selected by default.
+ */
+static void
+scroll_to_current_hack (Widget toplevel, prefs_pair *pair)
+{
+ Atom type;
+ int format;
+ unsigned long nitems, bytesafter;
+ unsigned char *data = 0;
+ Display *dpy = XtDisplay (toplevel);
+ int which = 0;
+ Widget list;
+
+ if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */
+ XA_SCREENSAVER_STATUS,
+ 0, 3, False, XA_INTEGER,
+ &type, &format, &nitems, &bytesafter,
+ &data)
+ == Success
+ && type == XA_INTEGER
+ && nitems >= 3
+ && data)
+ which = (int) data[2] - 1;
+
+ if (data) free (data);
+
+ if (which < 0)
+ return;
+
+ list = name_to_widget (toplevel, "list");
+ apply_changes_and_save (toplevel);
+
+ XmListDeselectAllItems (list); /* LessTif lossage */
+ XmListSelectPos (list, which+1, True);
+
+ ensure_selected_item_visible (list);
+ populate_demo_window (toplevel, which, pair);
+}
+
+
+
+static void
+populate_hack_list (Widget toplevel, prefs_pair *pair)
+{
+ saver_preferences *p = pair->a;
+ Widget list = name_to_widget (toplevel, "list");
+ screenhack **hacks = p->screenhacks;
+ screenhack **h;
+
+ for (h = hacks; *h; h++)
+ {
+ char *pretty_name = (h[0]->name
+ ? strdup (h[0]->name)
+ : make_hack_name (XtDisplay (toplevel), h[0]->command));
+
+ XmString xmstr = XmStringCreate (pretty_name, XmSTRING_DEFAULT_CHARSET);
+ XmListAddItem (list, xmstr, 0);
+ XmStringFree (xmstr);
+ }
+
+ XtAddCallback (list, XmNbrowseSelectionCallback, list_select_cb, pair);
+ XtAddCallback (list, XmNdefaultActionCallback, list_select_cb, pair);
+}
+
+
+static void
+populate_prefs_page (Widget top, prefs_pair *pair)
+{
+ saver_preferences *p = pair->a;
+ char s[100];
+
+ format_time (s, p->timeout);
+ XtVaSetValues (name_to_widget (top, "timeoutText"), XmNvalue, s, NULL);
+ format_time (s, p->cycle);
+ XtVaSetValues (name_to_widget (top, "cycleText"), XmNvalue, s, NULL);
+ format_time (s, p->lock_timeout);
+ XtVaSetValues (name_to_widget (top, "lockText"), XmNvalue, s, NULL);
+ format_time (s, p->passwd_timeout);
+ 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);
+ XtVaSetValues (name_to_widget (top, "cmapToggle"),
+ XmNset, p->install_cmap_p, NULL);
+ XtVaSetValues (name_to_widget (top, "fadeToggle"),
+ XmNset, p->fade_p, NULL);
+ XtVaSetValues (name_to_widget (top, "unfadeToggle"),
+ 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);
+ }
+}
+
+
+static void
+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++)
+ {
+ Widget w = name_to_widget (toplevel, names[i]);
+ XtVaSetValues (w, XtNsensitive, sensitive_p, NULL);
+ }
+
+ /* I don't know how to handle these yet... */
+ {
+ const char *names2[] = { "cut", "copy", "paste" };
+ for (i = 0; i < sizeof(names2)/countof(*names2); i++)
+ {
+ Widget w = name_to_widget (toplevel, names2[i]);
+ XtVaSetValues (w, XtNsensitive, FALSE, NULL);
+ }
+ }
+}
+
+
+
+/* Pixmaps for the up and down arrow buttons (yeah, this is sleazy...)
+ */
+
+#ifdef HAVE_XPM
+
+static char *up_arrow_xpm[] = {
+ "15 15 4 1",
+ " c None s background",
+ "- c #FFFFFF",
+ "+ c #D6D6D6",
+ "@ c #000000",
+
+ " @ ",
+ " @ ",
+ " -+@ ",
+ " -+@ ",
+ " -+++@ ",
+ " -+++@ ",
+ " -+++++@ ",
+ " -+++++@ ",
+ " -+++++++@ ",
+ " -+++++++@ ",
+ " -+++++++++@ ",
+ " -+++++++++@ ",
+ " -+++++++++++@ ",
+ " @@@@@@@@@@@@@ ",
+ " "
+};
+
+static char *down_arrow_xpm[] = {
+ "15 15 4 1",
+ " c None s background",
+ "- c #FFFFFF",
+ "+ c #D6D6D6",
+ "@ c #000000",
+
+ " ",
+ " ------------- ",
+ " -+++++++++++@ ",
+ " -+++++++++@ ",
+ " -+++++++++@ ",
+ " -+++++++@ ",
+ " -+++++++@ ",
+ " -+++++@ ",
+ " -+++++@ ",
+ " -+++@ ",
+ " -+++@ ",
+ " -+@ ",
+ " -+@ ",
+ " @ ",
+ " @ "
+};
+
+#endif /* HAVE_XPM */
+
+
+static void
+pixmapify_buttons (Widget toplevel)
+{
+#ifdef HAVE_XPM
+
+ Display *dpy = XtDisplay (toplevel);
+ Window window = XtWindow (toplevel);
+ XWindowAttributes xgwa;
+ XpmAttributes xpmattrs;
+ Pixmap up_pixmap = 0, down_pixmap = 0;
+ int result;
+ Widget up = name_to_widget (toplevel, "up");
+ Widget dn = name_to_widget (toplevel, "down");
+# ifdef XpmColorSymbols
+ XColor xc;
+ XpmColorSymbol symbols[2];
+ char color[20];
+# endif
+
+ XGetWindowAttributes (dpy, window, &xgwa);
+
+ xpmattrs.valuemask = 0;
+
+# ifdef XpmColorSymbols
+ symbols[0].name = "background";
+ symbols[0].pixel = 0;
+ symbols[1].name = 0;
+ XtVaGetValues (up, XmNbackground, &xc, NULL);
+ XQueryColor (dpy, xgwa.colormap, &xc);
+ sprintf (color, "#%04X%04X%04X", xc.red, xc.green, xc.blue);
+ symbols[0].value = color;
+ symbols[0].pixel = xc.pixel;
+
+ xpmattrs.valuemask |= XpmColorSymbols;
+ xpmattrs.colorsymbols = symbols;
+ xpmattrs.numsymbols = 1;
+# endif
+
+# ifdef XpmCloseness
+ xpmattrs.valuemask |= XpmCloseness;
+ xpmattrs.closeness = 40000;
+# endif
+# ifdef XpmVisual
+ xpmattrs.valuemask |= XpmVisual;
+ xpmattrs.visual = xgwa.visual;
+# endif
+# ifdef XpmDepth
+ xpmattrs.valuemask |= XpmDepth;
+ xpmattrs.depth = xgwa.depth;
+# endif
+# ifdef XpmColormap
+ xpmattrs.valuemask |= XpmColormap;
+ xpmattrs.colormap = xgwa.colormap;
+# endif
+
+ result = XpmCreatePixmapFromData(dpy, window, up_arrow_xpm,
+ &up_pixmap, 0 /* mask */, &xpmattrs);
+ if (!up_pixmap || (result != XpmSuccess && result != XpmColorError))
+ {
+ fprintf (stderr, "%s: Can't load pixmaps\n", progname);
+ return;
+ }
+
+ result = XpmCreatePixmapFromData(dpy, window, down_arrow_xpm,
+ &down_pixmap, 0 /* mask */, &xpmattrs);
+ if (!down_pixmap || (result != XpmSuccess && result != XpmColorError))
+ {
+ fprintf (stderr, "%s: Can't load pixmaps\n", progname);
+ return;
+ }
+
+ XtVaSetValues (up, XmNlabelType, XmPIXMAP, XmNlabelPixmap, up_pixmap, NULL);
+ XtVaSetValues (dn, XmNlabelType, XmPIXMAP, XmNlabelPixmap, down_pixmap,NULL);
+
+#endif /* HAVE_XPM */
+}
+
+
+
+char *
+get_hack_blurb (Display *dpy, screenhack *hack)
+{
+ char *doc_string;
+ char *prog_name = strdup (hack->command);
+ char *pretty_name = (hack->name
+ ? strdup (hack->name)
+ : make_hack_name (dpy, hack->command));
+ char doc_name[255], doc_class[255];
+ char *s, *s2;
+
+ for (s = prog_name; *s && !isspace(*s); s++)
+ ;
+ *s = 0;
+ s = strrchr (prog_name, '/');
+ if (s) strcpy (prog_name, s+1);
+
+ sprintf (doc_name, "hacks.%s.documentation", pretty_name);
+ sprintf (doc_class, "hacks.%s.documentation", prog_name);
+ free (prog_name);
+ free (pretty_name);
+
+ doc_string = get_string_resource (dpy, doc_name, doc_class);
+ if (doc_string)
+ {
+ for (s = doc_string; *s; s++)
+ {
+ if (*s == '\n')
+ {
+ /* skip over whitespace at beginning of line */
+ s++;
+ while (*s && (*s == ' ' || *s == '\t'))
+ s++;
+ }
+ else if (*s == ' ' || *s == '\t')
+ {
+ /* compress all other horizontal whitespace. */
+ *s = ' ';
+ s++;
+ for (s2 = s; *s2 && (*s2 == ' ' || *s2 == '\t'); s2++)
+ ;
+ if (s2 > s) strcpy (s, s2);
+ s--;
+ }
+ }
+
+ while (*s && isspace (*s)) /* Strip trailing whitespace */
+ *(--s) = 0;
+
+ /* Delete whitespace at end of each line. */
+ for (; s > doc_string; s--)
+ if (*s == '\n' && (s[-1] == ' ' || s[-1] == '\t'))
+ {
+ for (s2 = s-1;
+ s2 > doc_string && (*s2 == ' ' || *s2 == '\t');
+ s2--)
+ ;
+ s2++;
+ if (s2 < s) strcpy (s2, s);
+ s = s2;
+ }
+
+ /* Delete leading blank lines. */
+ for (s = doc_string; *s == '\n'; s++)
+ ;
+ if (s > doc_string) strcpy (doc_string, s);
+ }
+ else
+ {
+# if 0
+ static int doc_installed = 0;
+ if (doc_installed == 0)
+ {
+ if (get_boolean_resource ("hacks.documentation.isInstalled",
+ "hacks.documentation.isInstalled"))
+ doc_installed = 1;
+ else
+ doc_installed = -1;
+ }
+
+ if (doc_installed < 0)
+ doc_string =
+ strdup ("Error:\n\n"
+ "The documentation strings do not appear to be "
+ "installed. This is probably because there is "
+ "an \"XScreenSaver\" app-defaults file installed "
+ "that is from an older version of the program. "
+ "To fix this problem, delete that file, or "
+ "install a current version (either will work.)");
+ else
+# 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 "
+ "instead, which has many more features."
+ "\n\n"
+ "If you were running the GTK version, there would be a preview "
+ "of this screen saver mode displayed here, along with graphical "
+ "configuration options.");
+ }
+
+ return doc_string;
+}
+
+
+static void
+populate_demo_window (Widget toplevel, int which, prefs_pair *pair)
+{
+ saver_preferences *p = pair->a;
+ screenhack *hack = (which >= 0 ? p->screenhacks[which] : 0);
+ Widget frameL = name_to_widget (toplevel, "frameLabel");
+ Widget doc = name_to_widget (toplevel, "doc");
+ Widget cmd = name_to_widget (toplevel, "cmdText");
+ Widget enabled = name_to_widget (toplevel, "enabled");
+ Widget vis = name_to_widget (toplevel, "combo");
+ int i = 0;
+
+ char *pretty_name = (hack
+ ? (hack->name
+ ? strdup (hack->name)
+ : make_hack_name (XtDisplay (toplevel), hack->command))
+ : 0);
+ char *doc_string = hack ? get_hack_blurb (XtDisplay (toplevel), hack) : 0;
+
+ XmString xmstr;
+
+ xmstr = XmStringCreate (pretty_name, XmSTRING_DEFAULT_CHARSET);
+ XtVaSetValues (frameL, XmNlabelString, xmstr, NULL);
+ XmStringFree (xmstr);
+
+ XtVaSetValues (doc, XmNvalue, doc_string, NULL);
+ XtVaSetValues (cmd, XmNvalue, (hack ? hack->command : ""), NULL);
+
+ XtVaSetValues (enabled, XmNset, (hack ? hack->enabled_p : False), NULL);
+
+ i = 0;
+ if (hack && hack->visual && *hack->visual)
+ for (i = 0; visual_menu[i]; i++)
+ if (!strcasecmp (hack->visual, visual_menu[i]))
+ break;
+ if (!visual_menu[i]) i = -1;
+
+ {
+# ifdef HAVE_XMCOMBOBOX
+ Widget text = 0;
+ XtVaGetValues (vis, XmNtextField, &text, NULL);
+ XtVaSetValues (vis, XmNselectedPosition, i, NULL);
+ if (i < 0)
+ XtVaSetValues (text, XmNvalue, hack->visual, NULL);
+# else /* !HAVE_XMCOMBOBOX */
+ Cardinal nkids;
+ Widget *kids;
+ Widget menu;
+
+ XtVaGetValues (vis, XmNsubMenuId, &menu, NULL);
+ if (!menu) abort ();
+ XtVaGetValues (menu, XmNnumChildren, &nkids, XmNchildren, &kids, NULL);
+ if (!kids) abort();
+ if (i < nkids)
+ XtVaSetValues (vis, XmNmenuHistory, kids[i], NULL);
+# endif /* !HAVE_XMCOMBOBOX */
+ }
+
+ sensitize_demo_widgets (toplevel, (hack ? True : False));
+
+ if (pretty_name) free (pretty_name);
+ if (doc_string) free (doc_string);
+
+ _selected_hack_number = which;
+}
+
+
+
+static int
+maybe_reload_init_file (Widget widget, prefs_pair *pair)
+{
+ int status = 0;
+ saver_preferences *p = pair->a;
+
+ static Bool reentrant_lock = False;
+ if (reentrant_lock) return 0;
+ reentrant_lock = True;
+
+ if (init_file_changed_p (p))
+ {
+ const char *f = init_file_name();
+ char *b;
+ int which;
+ Widget list;
+
+ if (!f || !*f) return 0;
+ b = (char *) malloc (strlen(f) + 1024);
+ sprintf (b,
+ "Warning:\n\n"
+ "file \"%s\" has changed, reloading.\n",
+ f);
+ warning_dialog (widget, b, 100);
+ free (b);
+
+ load_init_file (XtDisplay (widget), p);
+
+ which = selected_hack_number (widget);
+ list = name_to_widget (widget, "list");
+
+ XtVaSetValues (list, XmNitemCount, 0, NULL);
+
+ populate_hack_list (widget, pair);
+
+ XmListDeselectAllItems (list); /* LessTif lossage */
+ XmListSelectPos (list, which+1, True);
+
+ populate_prefs_page (widget, pair);
+ populate_demo_window (widget, which, pair);
+ ensure_selected_item_visible (list);
+
+ status = 1;
+ }
+
+ reentrant_lock = False;
+ return status;
+}
+
+
+
+/* Attach all callback functions to widgets
+ */
+
+static void
+add_callbacks (Widget toplevel, prefs_pair *pair)
+{
+ Widget w;
+
+# define CB(NAME,FN) \
+ w = name_to_widget (toplevel, (NAME)); \
+ XtAddCallback (w, XmNactivateCallback, (FN), pair)
+
+ CB ("blank", activate_menu_cb);
+ CB ("lock", lock_menu_cb);
+ CB ("kill", kill_menu_cb);
+ CB ("restart", restart_menu_cb);
+ CB ("exit", exit_menu_cb);
+
+ CB ("cut", cut_menu_cb);
+ CB ("copy", copy_menu_cb);
+ CB ("paste", paste_menu_cb);
+
+ CB ("about", about_menu_cb);
+ CB ("docMenu", doc_menu_cb);
+
+ CB ("down", run_next_cb);
+ CB ("up", run_prev_cb);
+ CB ("demo", run_this_cb);
+ CB ("man", manual_cb);
+
+ CB ("preferencesForm.Cancel", prefs_cancel_cb);
+ CB ("preferencesForm.OK", prefs_ok_cb);
+
+# undef CB
+}
+
+
+static void
+sanity_check_resources (Widget toplevel)
+{
+ const char *names[] = { "demoTab", "optionsTab", "cmdLabel", "visLabel",
+ "enabled", "demo", "man", "timeoutLabel",
+ "cycleLabel", "fadeSecondsLabel", "fadeTicksLabel",
+ "lockLabel", "passwdLabel" };
+ int i;
+ for (i = 0; i < sizeof(names)/countof(*names); i++)
+ {
+ Widget w = name_to_widget (toplevel, names[i]);
+ const char *name = XtName(w);
+ XmString xm = 0;
+ char *label = 0;
+ XtVaGetValues (w, XmNlabelString, &xm, NULL);
+ if (xm) XmStringGetLtoR (xm, XmSTRING_DEFAULT_CHARSET, &label);
+ if (w && (!label || !strcmp (name, label)))
+ {
+ xm = XmStringCreate ("ERROR", XmSTRING_DEFAULT_CHARSET);
+ XtVaSetValues (w, XmNlabelString, xm, NULL);
+ }
+ }
+}
+
+/* Set certain buttons to be the same size (the max of the set.)
+ */
+static void
+hack_button_sizes (Widget toplevel)
+{
+ Widget demo = name_to_widget (toplevel, "demo");
+ Widget man = name_to_widget (toplevel, "man");
+ Widget ok = name_to_widget (toplevel, "OK");
+ Widget can = name_to_widget (toplevel, "Cancel");
+ Widget up = name_to_widget (toplevel, "up");
+ Widget down = name_to_widget (toplevel, "down");
+ Dimension w1, w2;
+
+ XtVaGetValues (demo, XmNwidth, &w1, NULL);
+ XtVaGetValues (man, XmNwidth, &w2, NULL);
+ XtVaSetValues ((w1 > w2 ? man : demo), XmNwidth, (w1 > w2 ? w1 : w2), NULL);
+
+ XtVaGetValues (ok, XmNwidth, &w1, NULL);
+ XtVaGetValues (can, XmNwidth, &w2, NULL);
+ XtVaSetValues ((w1 > w2 ? can : ok), XmNwidth, (w1 > w2 ? w1 : w2), NULL);
+
+ XtVaGetValues (up, XmNwidth, &w1, NULL);
+ XtVaGetValues (down, XmNwidth, &w2, NULL);
+ XtVaSetValues ((w1 > w2 ? down : up), XmNwidth, (w1 > w2 ? w1 : w2), NULL);
+}
+
+
+
+
+/* The main demo-mode command loop.
+ */
+
+#if 0
+static Bool
+mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks,
+ XrmRepresentation *type, XrmValue *value, XPointer closure)
+{
+ int i;
+ for (i = 0; quarks[i]; i++)
+ {
+ if (bindings[i] == XrmBindTightly)
+ fprintf (stderr, (i == 0 ? "" : "."));
+ else if (bindings[i] == XrmBindLoosely)
+ fprintf (stderr, "*");
+ else
+ fprintf (stderr, " ??? ");
+ fprintf(stderr, "%s", XrmQuarkToString (quarks[i]));
+ }
+
+ fprintf (stderr, ": %s\n", (char *) value->addr);
+
+ return False;
+}
+#endif
+
+
+static void
+the_network_is_not_the_computer (Widget parent)
+{
+ Display *dpy = XtDisplay (parent);
+ char *rversion, *ruser, *rhost;
+ char *luser, *lhost;
+ char *msg = 0;
+ struct passwd *p = getpwuid (getuid ());
+ const char *d = DisplayString (dpy);
+
+# if defined(HAVE_UNAME)
+ struct utsname uts;
+ if (uname (&uts) < 0)
+ lhost = "<UNKNOWN>";
+ else
+ lhost = uts.nodename;
+# elif defined(VMS)
+ strcpy (lhost, getenv("SYS$NODE"));
+# else /* !HAVE_UNAME && !VMS */
+ strcat (lhost, "<UNKNOWN>");
+# endif /* !HAVE_UNAME && !VMS */
+
+ if (p && p->pw_name)
+ luser = p->pw_name;
+ else
+ luser = "???";
+
+ server_xscreensaver_version (dpy, &rversion, &ruser, &rhost);
+
+ /* Make a buffer that's big enough for a number of copies of all the
+ strings, plus some. */
+ msg = (char *) malloc (10 * ((rversion ? strlen(rversion) : 0) +
+ (ruser ? strlen(ruser) : 0) +
+ (rhost ? strlen(rhost) : 0) +
+ strlen(lhost) +
+ strlen(luser) +
+ strlen(d) +
+ 1024));
+ *msg = 0;
+
+ if (!rversion || !*rversion)
+ {
+ sprintf (msg,
+ "Warning:\n\n"
+ "The XScreenSaver daemon doesn't seem to be running\n"
+ "on display \"%s\". You can launch it by selecting\n"
+ "`Restart Daemon' from the File menu, or by typing\n"
+ "\"xscreensaver &\" in a shell.",
+ d);
+ }
+ else if (p && ruser && *ruser && !!strcmp (ruser, p->pw_name))
+ {
+ /* Warn that the two processes are running as different users.
+ */
+ sprintf(msg,
+ "Warning:\n\n"
+ "%s is running as user \"%s\" on host \"%s\".\n"
+ "But the xscreensaver managing display \"%s\"\n"
+ "is running as user \"%s\" on host \"%s\".\n"
+ "\n"
+ "Since they are different users, they won't be reading/writing\n"
+ "the same ~/.xscreensaver file, so %s isn't\n"
+ "going to work right.\n"
+ "\n"
+ "Either re-run %s as \"%s\", or re-run\n"
+ "xscreensaver as \"%s\" (which you can do by\n"
+ "selecting `Restart Daemon' from the File menu.)\n",
+ progname, luser, lhost,
+ d,
+ (ruser ? ruser : "???"), (rhost ? rhost : "???"),
+ progname,
+ progname, (ruser ? ruser : "???"),
+ luser);
+ }
+ else if (rhost && *rhost && !!strcmp (rhost, lhost))
+ {
+ /* Warn that the two processes are running on different hosts.
+ */
+ sprintf (msg,
+ "Warning:\n\n"
+ "%s is running as user \"%s\" on host \"%s\".\n"
+ "But the xscreensaver managing display \"%s\"\n"
+ "is running as user \"%s\" on host \"%s\".\n"
+ "\n"
+ "If those two machines don't share a file system (that is,\n"
+ "if they don't see the same ~%s/.xscreensaver file) then\n"
+ "%s won't work right.\n"
+ "\n"
+ "You can restart the daemon on \"%s\" as \"%s\" by\n"
+ "selecting `Restart Daemon' from the File menu.)",
+ progname, luser, lhost,
+ d,
+ (ruser ? ruser : "???"), (rhost ? rhost : "???"),
+ luser,
+ progname,
+ lhost, luser);
+ }
+ else if (!!strcmp (rversion, short_version))
+ {
+ /* Warn that the version numbers don't match.
+ */
+ sprintf (msg,
+ "Warning:\n\n"
+ "This is %s version %s.\n"
+ "But the xscreensaver managing display \"%s\"\n"
+ "is version %s. This could cause problems.",
+ progname, short_version,
+ d,
+ rversion);
+ }
+
+
+ if (*msg)
+ warning_dialog (parent, msg, 1);
+
+ free (msg);
+}
+
+
+/* We use this error handler so that X errors are preceeded by the name
+ of the program that generated them.
+ */
+static int
+demo_ehandler (Display *dpy, XErrorEvent *error)
+{
+ fprintf (stderr, "\nX error in %s:\n", progname);
+ XmuPrintDefaultErrorMessage (dpy, error, stderr);
+ exit (-1);
+ 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 *defaults[] = {
+#include "XScreenSaver_ad.h"
+#include "XScreenSaver_Xm_ad.h"
+ 0
+};
+
+
+int
+main (int argc, char **argv)
+{
+ XtAppContext app;
+ prefs_pair Pair, *pair;
+ saver_preferences P, P2, *p, *p2;
+ Bool prefs = False;
+ int i;
+ Display *dpy;
+ Widget toplevel_shell, dialog;
+ char *real_progname = argv[0];
+ char *s;
+
+ s = strrchr (real_progname, '/');
+ if (s) real_progname = s+1;
+
+ p = &P;
+ p2 = &P2;
+ pair = &Pair;
+ pair->a = p;
+ pair->b = p2;
+ memset (p, 0, sizeof (*p));
+ memset (p2, 0, sizeof (*p2));
+
+ global_prefs_pair = pair;
+
+ progname = real_progname;
+
+ /* We must read exactly the same resources as xscreensaver.
+ That means we must have both the same progclass *and* progname,
+ at least as far as the resource database is concerned. So,
+ put "xscreensaver" in argv[0] while initializing Xt.
+ */
+ argv[0] = "xscreensaver";
+ progname = argv[0];
+
+
+ toplevel_shell = XtAppInitialize (&app, progclass, 0, 0, &argc, argv,
+ defaults, 0, 0);
+
+ dpy = XtDisplay (toplevel_shell);
+ db = XtDatabase (dpy);
+ XtGetApplicationNameAndClass (dpy, &progname, &progclass);
+ XSetErrorHandler (demo_ehandler);
+
+ /* Complain about unrecognized command-line arguments.
+ */
+ for (i = 1; i < argc; i++)
+ {
+ char *s = argv[i];
+ if (s[0] == '-' && s[1] == '-')
+ s++;
+ if (!strcmp (s, "-prefs"))
+ prefs = True;
+ else
+ {
+ fprintf (stderr, "usage: %s [ -display dpy-string ] [ -prefs ]\n",
+ real_progname);
+ exit (1);
+ }
+ }
+
+ short_version = (char *) malloc (5);
+ memcpy (short_version, screensaver_id + 17, 4);
+ short_version [4] = 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
+ important that `progname' be "xscreensaver", rather than whatever
+ was in argv[0].
+ */
+ p->db = db;
+ load_init_file (dpy, p);
+ *p2 = *p;
+
+ /* Now that Xt has been initialized, and the resources have been read,
+ we can set our `progname' variable to something more in line with
+ reality.
+ */
+ progname = real_progname;
+
+
+#if 0
+ {
+ XrmName name = { 0 };
+ XrmClass class = { 0 };
+ int count = 0;
+ XrmEnumerateDatabase (db, &name, &class, XrmEnumAllLevels, mapper,
+ (POINTER) &count);
+ }
+#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_BLANK = XInternAtom (dpy, "BLANK", False);
+ XA_LOCK = XInternAtom (dpy, "LOCK", False);
+ XA_EXIT = XInternAtom (dpy, "EXIT", False);
+ XA_RESTART = XInternAtom (dpy, "RESTART", False);
+
+ /* Create the window and all its widgets.
+ */
+ dialog = create_xscreensaver_demo (toplevel_shell);
+
+ /* Set the window's title. */
+ {
+ char title[255];
+ char *v = (char *) strdup(strchr(screensaver_id, ' '));
+ char *s1, *s2, *s3, *s4;
+ s1 = (char *) strchr(v, ' '); s1++;
+ s2 = (char *) strchr(s1, ' ');
+ s3 = (char *) strchr(v, '('); s3++;
+ s4 = (char *) strchr(s3, ')');
+ *s2 = 0;
+ *s4 = 0;
+ sprintf (title, "%.50s %.50s, %.50s", progclass, s1, s3);
+ XtVaSetValues (toplevel_shell, XtNtitle, title, NULL);
+ free (v);
+ }
+
+ sanity_check_resources (toplevel_shell);
+ add_callbacks (toplevel_shell, pair);
+ populate_hack_list (toplevel_shell, pair);
+ populate_prefs_page (toplevel_shell, pair);
+ sensitize_demo_widgets (toplevel_shell, False);
+ scroll_to_current_hack (toplevel_shell, pair);
+
+ XtManageChild (dialog);
+ XtRealizeWidget(toplevel_shell);
+
+ /* The next few calls must come after XtRealizeWidget(). */
+ pixmapify_buttons (toplevel_shell);
+ hack_button_sizes (toplevel_shell);
+ ensure_selected_item_visible (name_to_widget (toplevel_shell, "list"));
+
+ XSync (dpy, False);
+ XtVaSetValues (toplevel_shell, XmNallowShellResize, False, NULL);
+
+
+ /* Handle the -prefs command-line argument. */
+ if (prefs)
+ {
+ Widget tabber = name_to_widget (toplevel_shell, "folder");
+ Widget this_tab = name_to_widget (toplevel_shell, "optionsTab");
+ Widget this_page = name_to_widget (toplevel_shell, "preferencesForm");
+ Widget *kids = 0;
+ Cardinal nkids = 0;
+ if (!tabber) abort();
+
+ XtVaGetValues (tabber, XmNnumChildren, &nkids, XmNchildren, &kids, NULL);
+ if (!kids) abort();
+ if (nkids > 0)
+ XtUnmanageChildren (kids, nkids);
+
+ XtManageChild (this_page);
+
+ XmProcessTraversal (this_tab, XmTRAVERSE_CURRENT);
+ }
+
+ /* Issue any warnings about the running xscreensaver daemon. */
+ the_network_is_not_the_computer (toplevel_shell);
+
+
+ XtAppMainLoop (app);
+ exit (0);
+}
+
+#endif /* HAVE_MOTIF -- whole file */
diff --git a/driver/dpms.c b/driver/dpms.c
new file mode 100644
index 0000000..a0dd7b8
--- /dev/null
+++ b/driver/dpms.c
@@ -0,0 +1,304 @@
+/* dpms.c --- syncing the X Display Power Management values
+ * xscreensaver, Copyright (c) 2001-2017 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.
+ */
+
+/* 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>
+
+#ifdef HAVE_DPMS_EXTENSION /* almost the whole file */
+
+# include <X11/Xproto.h>
+# include <X11/extensions/dpms.h>
+/*# include <X11/extensions/dpmsstr.h>*/
+
+ /* 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*
+
+#include "xscreensaver.h"
+
+#ifdef HAVE_DPMS_EXTENSION
+
+static Bool error_handler_hit_p = False;
+
+static int
+ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
+{
+ error_handler_hit_p = True;
+ return 0;
+}
+
+
+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)
+{
+ int event = 0, error = 0;
+ BOOL o_enabled = False;
+ CARD16 o_power = 0;
+ CARD16 o_standby = 0, o_suspend = 0, o_off = 0;
+ Bool bogus_p = False;
+
+ if (dpms_quickoff_p && !off_secs)
+ {
+ /* 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" */
+ enabled_p = False;
+
+ else if ((standby_secs != 0 && standby_secs < 10) ||
+ (suspend_secs != 0 && suspend_secs < 10) ||
+ (off_secs != 0 && off_secs < 10))
+ /* any negative, or any positive-and-less-than-10-seconds, is crazy. */
+ bogus_p = True;
+
+ if (bogus_p) enabled_p = False;
+
+ /* X protocol sends these values in a CARD16, so truncate them to 16 bits.
+ This means that the maximum timeout is 18:12:15.
+ */
+ if (standby_secs > 0xFFFF) standby_secs = 0xFFFF;
+ if (suspend_secs > 0xFFFF) suspend_secs = 0xFFFF;
+ if (off_secs > 0xFFFF) off_secs = 0xFFFF;
+
+ if (! DPMSQueryExtension (dpy, &event, &error))
+ {
+ if (verbose_p)
+ fprintf (stderr, "%s: XDPMS extension not supported.\n", blurb());
+ return;
+ }
+
+ if (! DPMSCapable (dpy))
+ {
+ if (verbose_p)
+ fprintf (stderr, "%s: DPMS not supported.\n", blurb());
+ return;
+ }
+
+ if (! DPMSInfo (dpy, &o_power, &o_enabled))
+ {
+ if (verbose_p)
+ fprintf (stderr, "%s: unable to get DPMS state.\n", blurb());
+ return;
+ }
+
+ if (o_enabled != enabled_p)
+ {
+ if (! (enabled_p ? DPMSEnable (dpy) : DPMSDisable (dpy)))
+ {
+ if (verbose_p)
+ fprintf (stderr, "%s: unable to set DPMS state.\n", blurb());
+ return;
+ }
+ else if (verbose_p)
+ 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",
+ blurb(), standby_secs, suspend_secs, off_secs);
+ return;
+ }
+
+ if (!DPMSGetTimeouts (dpy, &o_standby, &o_suspend, &o_off))
+ {
+ if (verbose_p)
+ fprintf (stderr, "%s: unable to get DPMS timeouts.\n", blurb());
+ return;
+ }
+
+ if (o_standby != standby_secs ||
+ o_suspend != suspend_secs ||
+ o_off != off_secs)
+ {
+ if (!DPMSSetTimeouts (dpy, standby_secs, suspend_secs, off_secs))
+ {
+ if (verbose_p)
+ 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(),
+ standby_secs, suspend_secs, off_secs);
+ }
+}
+
+Bool
+monitor_powered_on_p (saver_info *si)
+{
+ Bool result;
+ int event_number, error_number;
+ BOOL onoff = False;
+ CARD16 state;
+
+ if (!DPMSQueryExtension(si->dpy, &event_number, &error_number))
+ /* Server doesn't know -- assume the monitor is on. */
+ result = True;
+
+ else if (!DPMSCapable(si->dpy))
+ /* Server says the monitor doesn't do power management -- so it's on. */
+ result = True;
+
+ else
+ {
+ DPMSInfo(si->dpy, &state, &onoff);
+ if (!onoff)
+ /* Server says DPMS is disabled -- so the monitor is on. */
+ result = True;
+ else
+ switch (state) {
+ case DPMSModeOn: result = True; break; /* really on */
+ case DPMSModeStandby: result = False; break; /* kinda off */
+ case DPMSModeSuspend: result = False; break; /* pretty off */
+ case DPMSModeOff: result = False; break; /* really off */
+ default: result = True; break; /* protocol error? */
+ }
+ }
+
+ return result;
+}
+
+void
+monitor_power_on (saver_info *si, Bool on_p)
+{
+ if ((!!on_p) != monitor_powered_on_p (si))
+ {
+ XErrorHandler old_handler;
+ int event_number, error_number;
+ if (!DPMSQueryExtension(si->dpy, &event_number, &error_number) ||
+ !DPMSCapable(si->dpy))
+ {
+ if (si->prefs.verbose_p)
+ fprintf (stderr,
+ "%s: unable to power %s monitor: no DPMS extension.\n",
+ blurb(), (on_p ? "on" : "off"));
+ return;
+ }
+
+ /* The manual for DPMSForceLevel() says that it throws BadMatch if
+ "DPMS is disabled on the specified display."
+
+ The manual for DPMSCapable() says that it "returns True if the X
+ server is capable of DPMS."
+
+ Apparently they consider "capable of DPMS" and "DPMS is enabled"
+ to be different things, and so even if DPMSCapable() returns
+ True, DPMSForceLevel() *might* throw an X Error. Isn't that
+ just fucking special.
+ */
+ XSync (si->dpy, False);
+ error_handler_hit_p = False;
+ old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
+ XSync (si->dpy, False);
+ DPMSForceLevel(si->dpy, (on_p ? DPMSModeOn : DPMSModeOff));
+ XSync (si->dpy, False);
+ XSetErrorHandler (old_handler);
+ /* Ignore error_handler_hit_p, just probe monitor instead */
+
+ if ((!!on_p) != monitor_powered_on_p (si)) /* double-check */
+ fprintf (stderr,
+ "%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 */
diff --git a/driver/exec.c b/driver/exec.c
new file mode 100644
index 0000000..38ca88a
--- /dev/null
+++ b/driver/exec.c
@@ -0,0 +1,300 @@
+/* exec.c --- executes a program in *this* pid, without an intervening process.
+ * xscreensaver, Copyright (c) 1991-2013 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.
+ */
+
+
+/* I don't believe what a sorry excuse for an operating system UNIX is!
+
+ - I want to spawn a process.
+ - I want to know it's pid so that I can kill it.
+ - I would like to receive a message when it dies of natural causes.
+ - I want the spawned process to have user-specified arguments.
+
+ If shell metacharacters are present (wildcards, backquotes, etc), the
+ only way to parse those arguments is to run a shell to do the parsing
+ for you.
+
+ And the only way to know the pid of the process is to fork() and exec()
+ it in the spawned side of the fork.
+
+ But if you're running a shell to parse your arguments, this gives you
+ the pid of the *shell*, not the pid of the *process* that you're
+ actually interested in, which is an *inferior* of the shell. This also
+ means that the SIGCHLD you get applies to the shell, not its inferior.
+ (Why isn't that sufficient? I don't remember any more, but it turns
+ out that it isn't.)
+
+ So, the only solution, when metacharacters are present, is to force the
+ shell to exec() its inferior. What a fucking hack! We prepend "exec "
+ to the command string, and hope it doesn't contain unquoted semicolons
+ or ampersands (we don't search for them, because we don't want to
+ prohibit their use in quoted strings (messages, for example) and parsing
+ out the various quote characters is too much of a pain.)
+
+ (Actually, Clint Wong <clint@jts.com> points out that process groups
+ might be used to take care of this problem; this may be worth considering
+ some day, except that, 1: this code works now, so why fix it, and 2: from
+ what I've seen in Emacs, dealing with process groups isn't especially
+ portable.)
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/stat.h>
+
+#ifndef ESRCH
+# include <errno.h>
+#endif
+
+#if defined(HAVE_SETPRIORITY) && defined(PRIO_PROCESS)
+# include <sys/resource.h> /* for setpriority() and PRIO_PROCESS */
+ /* 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);
+
+static void nice_process (int nice_level);
+
+
+#ifndef VMS
+
+static void
+exec_simple_command (const char *command)
+{
+ char *av[1024];
+ int ac = 0;
+ char *token = strtok (strdup(command), " \t");
+ while (token)
+ {
+ av[ac++] = token;
+ token = strtok(0, " \t");
+ }
+ av[ac] = 0;
+
+ execvp (av[0], av); /* shouldn't return. */
+}
+
+
+static void
+exec_complex_command (const char *shell, const char *command)
+{
+ char *av[5];
+ int ac = 0;
+ char *command2 = (char *) malloc (strlen (command) + 10);
+ const char *s;
+ int got_eq = 0;
+ const char *after_vars;
+
+ /* Skip leading whitespace.
+ */
+ while (*command == ' ' || *command == '\t')
+ command++;
+
+ /* If the string has a series of tokens with "=" in them at them, set
+ `after_vars' to point into the string after those tokens and any
+ trailing whitespace. Otherwise, after_vars == command.
+ */
+ after_vars = command;
+ for (s = command; *s; s++)
+ {
+ if (*s == '=') got_eq = 1;
+ else if (*s == ' ')
+ {
+ if (got_eq)
+ {
+ while (*s == ' ' || *s == '\t')
+ s++;
+ after_vars = s;
+ got_eq = 0;
+ }
+ else
+ break;
+ }
+ }
+
+ *command2 = 0;
+ strncat (command2, command, after_vars - command);
+ strcat (command2, "exec ");
+ strcat (command2, after_vars);
+
+ /* We have now done these transformations:
+ "foo -x -y" ==> "exec foo -x -y"
+ "BLAT=foop foo -x" ==> "BLAT=foop exec foo -x"
+ "BLAT=foop A=b foo -x" ==> "BLAT=foop A=b exec foo -x"
+ */
+
+
+ /* Invoke the shell as "/bin/sh -c 'exec prog -arg -arg ...'" */
+ av [ac++] = (char *) shell;
+ av [ac++] = "-c";
+ av [ac++] = command2;
+ av [ac] = 0;
+
+ 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, "*?$&!<>[];`'\\\"=");
+ /* note: = is in the above because of the sh syntax "FOO=bar cmd". */
+
+ if (getuid() == (uid_t) 0 || geteuid() == (uid_t) 0)
+ {
+ /* If you're thinking of commenting this out, think again.
+ If you do so, you will open a security hole. Mail jwz
+ so that he may enlighten you as to the error of your ways.
+ */
+ fprintf (stderr, "%s: we're still running as root! Disaster!\n",
+ blurb());
+ exit (-1);
+ }
+
+ if (hairy_p)
+ /* If it contains any shell metacharacters, do it the hard way,
+ and fork a shell to parse the arguments for us. */
+ exec_complex_command (shell, command);
+ else
+ /* Otherwise, we can just exec the program directly. */
+ exec_simple_command (command);
+
+#else /* VMS */
+ exec_vms_command (command);
+#endif /* VMS */
+}
+
+
+/* Setting process priority
+ */
+
+static void
+nice_process (int nice_level)
+{
+ if (nice_level == 0)
+ return;
+
+#if defined(HAVE_NICE)
+ {
+ int old_nice = nice (0);
+ int n = nice_level - old_nice;
+ errno = 0;
+ if (nice (n) == -1 && errno != 0)
+ {
+ char buf [512];
+ sprintf (buf, "%s: nice(%d) failed", blurb(), n);
+ perror (buf);
+ }
+ }
+#elif defined(HAVE_SETPRIORITY) && defined(PRIO_PROCESS)
+ if (setpriority (PRIO_PROCESS, getpid(), nice_level) != 0)
+ {
+ char buf [512];
+ sprintf (buf, "%s: setpriority(PRIO_PROCESS, %lu, %d) failed",
+ blurb(), (unsigned long) getpid(), nice_level);
+ perror (buf);
+ }
+#else
+ fprintf (stderr,
+ "%s: don't know how to change process priority on this system.\n",
+ blurb());
+
+#endif
+}
+
+
+/* Whether the given command exists on $PATH.
+ (Anything before the first space is considered to be the program name.)
+ */
+int
+on_path_p (const char *program)
+{
+ int result = 0;
+ struct stat st;
+ char *cmd = strdup (program);
+ char *token = strchr (cmd, ' ');
+ char *path = 0;
+ int L;
+
+ if (token) *token = 0;
+ token = 0;
+
+ if (strchr (cmd, '/'))
+ {
+ result = (0 == stat (cmd, &st));
+ goto DONE;
+ }
+
+ path = getenv("PATH");
+ if (!path || !*path)
+ goto DONE;
+
+ L = strlen (cmd);
+ path = strdup (path);
+ token = strtok (path, ":");
+
+ while (token)
+ {
+ char *p2 = (char *) malloc (strlen (token) + L + 3);
+ strcpy (p2, token);
+ strcat (p2, "/");
+ strcat (p2, cmd);
+ result = (0 == stat (p2, &st));
+ free (p2);
+ if (result)
+ goto DONE;
+ token = strtok (0, ":");
+ }
+
+ DONE:
+ free (cmd);
+ if (path) free (path);
+ return result;
+}
+
diff --git a/driver/exec.h b/driver/exec.h
new file mode 100644
index 0000000..318410b
--- /dev/null
+++ b/driver/exec.h
@@ -0,0 +1,21 @@
+/* exec.c --- executes a program in *this* pid, without an intervening process.
+ * xscreensaver, Copyright (c) 1991-2006 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_EXEC_H__
+#define __XSCREENSAVER_EXEC_H__
+
+extern void exec_command (const char *shell, const char *command,
+ int nice_level);
+
+extern int on_path_p (const char *program);
+
+#endif /* __XSCREENSAVER_EXEC_H__ */
diff --git a/driver/link_axp.com b/driver/link_axp.com
new file mode 100644
index 0000000..a141892
--- /dev/null
+++ b/driver/link_axp.com
@@ -0,0 +1,15 @@
+$! We fisrt test the version of DECW/Motif ; if 1.2 we need to link with new
+$! X11R5 libraries
+$@sys$update:decw$get_image_version sys$share:decw$xlibshr.exe decw$version
+$ if f$extract(4,3,decw$version).eqs."1.2"
+$ then
+$! DECW/Motif 1.2 : link with X11R5 libraries
+$ link xscreensaver-command,vms_axp_12.opt/opt
+$ link xscreensaver,demo,dialogs-xm,lock,passwd,stderr,subprocs,timers, -
+ windows,xset,vms-getpwnam,vms-hpwd,vms-validate,vms_axp_12.opt/opt
+$ else
+$! Else, link with X11R4 libraries
+$ link xscreensaver-command,vms_axp.opt/opt
+$ link xscreensaver,demo,dialogs-xm,lock,passwd,stderr,subprocs,timers, -
+ windows,xset,vms-getpwnam,vms-hpwd,vms-validate,vms_axp.opt/opt
+$ endif
diff --git a/driver/link_decc.com b/driver/link_decc.com
new file mode 100644
index 0000000..d1de0d0
--- /dev/null
+++ b/driver/link_decc.com
@@ -0,0 +1,15 @@
+$! We fisrt test the version of DECW/Motif ; if 1.2 we need to link with new
+$! X11R5 libraries
+$@sys$update:decw$get_image_version sys$share:decw$xlibshr.exe decw$version
+$ if f$extract(4,3,decw$version).eqs."1.2"
+$ then
+$! DECW/Motif 1.2 : link with X11R5 libraries
+$ link xscreensaver-command,vms_decc_12.opt/opt
+$ link xscreensaver,demo,dialogs-xm,lock,passwd,stderr,subprocs,timers, -
+ windows,xset,vms-getpwnam,vms-hpwd,vms-validate,vms_decc_12.opt/opt
+$ else
+$! Else, link with X11R4 libraries
+$ link xscreensaver-command,vms_decc.opt/opt
+$ link xscreensaver,demo,dialogs-xm,lock,passwd,stderr,subprocs,timers, -
+ windows,xset,vms-getpwnam,vms-hpwd,vms-validate,vms_decc.opt/opt
+$ endif
diff --git a/driver/lock.c b/driver/lock.c
new file mode 100644
index 0000000..10b879e
--- /dev/null
+++ b/driver/lock.c
@@ -0,0 +1,2259 @@
+/* lock.c --- handling the password dialog for locking-mode.
+ * xscreensaver, Copyright (c) 1993-2018 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.
+ */
+
+/* Athena locking code contributed by Jon A. Christopher <jac8782@tamu.edu> */
+/* Copyright 1997, with the same permissions as above. */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <ctype.h>
+#include <X11/Intrinsic.h>
+#include <X11/cursorfont.h>
+#include <X11/Xos.h> /* for time() */
+#include <time.h>
+#include <sys/time.h>
+#include "xscreensaver.h"
+#include "resources.h"
+#include "mlstring.h"
+#include "auth.h"
+
+#ifndef NO_LOCKING /* (mostly) whole file */
+
+#ifdef HAVE_XHPDISABLERESET
+# include <X11/XHPlib.h>
+ static void hp_lock_reset (saver_info *si, Bool lock_p);
+#endif /* HAVE_XHPDISABLERESET */
+
+#ifdef HAVE_XF86VMODE
+# include <X11/extensions/xf86vmode.h>
+ static void xfree_lock_mode_switch (saver_info *si, Bool lock_p);
+#endif /* HAVE_XF86VMODE */
+
+#ifdef HAVE_XF86MISCSETGRABKEYSSTATE
+# include <X11/extensions/xf86misc.h>
+ static void xfree_lock_grab_smasher (saver_info *si, Bool lock_p);
+#endif /* HAVE_XF86MISCSETGRABKEYSSTATE */
+
+#ifdef HAVE_RANDR
+# include <X11/extensions/Xrandr.h>
+#endif /* HAVE_RANDR */
+
+#ifdef _VROOT_H_
+ERROR! You must not include vroot.h in this file.
+#endif
+
+#ifdef HAVE_UNAME
+# include <sys/utsname.h> /* for hostname info */
+#endif /* HAVE_UNAME */
+#include <ctype.h>
+
+#ifndef VMS
+# include <pwd.h>
+#else /* VMS */
+
+extern char *getenv(const char *name);
+extern int validate_user(char *name, char *password);
+
+static Bool
+vms_passwd_valid_p(char *pw, Bool verbose_p)
+{
+ return (validate_user (getenv("USER"), typed_passwd) == 1);
+}
+# undef passwd_valid_p
+# define passwd_valid_p vms_passwd_valid_p
+
+#endif /* VMS */
+
+#define SAMPLE_INPUT "MMMMMMMMMMMM"
+
+
+#undef MAX
+#define MAX(a,b) ((a)>(b)?(a):(b))
+
+typedef struct info_dialog_data info_dialog_data;
+
+
+#define MAX_BYTES_PER_CHAR 8 /* UTF-8 uses no more than 3, I think */
+#define MAX_PASSWD_CHARS 128 /* Longest possible passphrase */
+
+struct passwd_dialog_data {
+
+ saver_screen_info *prompt_screen;
+ int previous_mouse_x, previous_mouse_y;
+
+ /* "Characters" in the password may be a variable number of bytes long.
+ typed_passwd contains the raw bytes.
+ typed_passwd_char_size indicates the size in bytes of each character,
+ so that we can make backspace work.
+ */
+ char typed_passwd [MAX_PASSWD_CHARS * MAX_BYTES_PER_CHAR];
+ char typed_passwd_char_size [MAX_PASSWD_CHARS];
+
+ XtIntervalId timer;
+ int i_beam;
+
+ float ratio;
+ Position x, y;
+ Dimension width;
+ Dimension height;
+ Dimension border_width;
+
+ Bool echo_input;
+ Bool show_stars_p; /* "I regret that I have but one asterisk for my country."
+ -- Nathan Hale, 1776. */
+
+ char *heading_label;
+ char *body_label;
+ char *user_label;
+ mlstring *info_label;
+ /* The entry field shall only be displayed if prompt_label is not NULL */
+ mlstring *prompt_label;
+ char *date_label;
+ char *passwd_string;
+ Bool passwd_changed_p; /* Whether the user entry field needs redrawing */
+ Bool caps_p; /* Whether we saw a keypress with caps-lock on */
+ char *unlock_label;
+ char *login_label;
+ char *uname_label;
+
+ Bool show_uname_p;
+
+ XFontStruct *heading_font;
+ XFontStruct *body_font;
+ XFontStruct *label_font;
+ XFontStruct *passwd_font;
+ XFontStruct *date_font;
+ XFontStruct *button_font;
+ XFontStruct *uname_font;
+
+ Pixel foreground;
+ Pixel background;
+ Pixel border;
+ Pixel passwd_foreground;
+ Pixel passwd_background;
+ Pixel thermo_foreground;
+ Pixel thermo_background;
+ Pixel shadow_top;
+ Pixel shadow_bottom;
+ Pixel button_foreground;
+ Pixel button_background;
+
+ Dimension preferred_logo_width, logo_width;
+ Dimension preferred_logo_height, logo_height;
+ Dimension thermo_width;
+ Dimension internal_border;
+ Dimension shadow_width;
+
+ Dimension passwd_field_x, passwd_field_y;
+ Dimension passwd_field_width, passwd_field_height;
+
+ Dimension unlock_button_x, unlock_button_y;
+ Dimension unlock_button_width, unlock_button_height;
+
+ Dimension login_button_x, login_button_y;
+ Dimension login_button_width, login_button_height;
+
+ Dimension thermo_field_x, thermo_field_y;
+ Dimension thermo_field_height;
+
+ Pixmap logo_pixmap;
+ Pixmap logo_clipmask;
+ int logo_npixels;
+ unsigned long *logo_pixels;
+
+ Cursor passwd_cursor;
+ Bool unlock_button_down_p;
+ Bool login_button_down_p;
+ Bool login_button_p;
+ Bool login_button_enabled_p;
+ Bool button_state_changed_p; /* Refers to both buttons */
+
+ Pixmap save_under;
+ Pixmap user_entry_pixmap;
+};
+
+static void draw_passwd_window (saver_info *si);
+static void update_passwd_window (saver_info *si, const char *printed_passwd,
+ float ratio);
+static void destroy_passwd_window (saver_info *si);
+static void undo_vp_motion (saver_info *si);
+static void finished_typing_passwd (saver_info *si, passwd_dialog_data *pw);
+static void cleanup_passwd_window (saver_info *si);
+static void restore_background (saver_info *si);
+
+extern void xss_authenticate(saver_info *si, Bool verbose_p);
+
+static int
+new_passwd_window (saver_info *si)
+{
+ passwd_dialog_data *pw;
+ Screen *screen;
+ Colormap cmap;
+ saver_screen_info *ssi = &si->screens [mouse_screen (si)];
+
+ pw = (passwd_dialog_data *) calloc (1, sizeof(*pw));
+ if (!pw)
+ return -1;
+
+ /* Display the button only if the "newLoginCommand" pref is non-null.
+ */
+ pw->login_button_p = (si->prefs.new_login_command &&
+ *si->prefs.new_login_command);
+
+ pw->passwd_cursor = XCreateFontCursor (si->dpy, XC_top_left_arrow);
+
+ pw->prompt_screen = ssi;
+
+ screen = pw->prompt_screen->screen;
+ cmap = DefaultColormapOfScreen (screen);
+
+ pw->show_stars_p = get_boolean_resource(si->dpy, "passwd.asterisks",
+ "Boolean");
+
+ pw->heading_label = get_string_resource (si->dpy, "passwd.heading.label",
+ "Dialog.Label.Label");
+ pw->body_label = get_string_resource (si->dpy, "passwd.body.label",
+ "Dialog.Label.Label");
+ pw->user_label = get_string_resource (si->dpy, "passwd.user.label",
+ "Dialog.Label.Label");
+ pw->unlock_label = get_string_resource (si->dpy, "passwd.unlock.label",
+ "Dialog.Button.Label");
+ pw->login_label = get_string_resource (si->dpy, "passwd.login.label",
+ "Dialog.Button.Label");
+
+ pw->date_label = get_string_resource (si->dpy, "dateFormat", "DateFormat");
+
+ if (!pw->heading_label)
+ pw->heading_label = strdup("ERROR: RESOURCES NOT INSTALLED CORRECTLY");
+ if (!pw->body_label)
+ pw->body_label = strdup("ERROR: RESOURCES NOT INSTALLED CORRECTLY");
+ if (!pw->user_label) pw->user_label = strdup("ERROR");
+ if (!pw->date_label) pw->date_label = strdup("ERROR");
+ if (!pw->unlock_label) pw->unlock_label = strdup("ERROR (UNLOCK)");
+ if (!pw->login_label) pw->login_label = strdup ("ERROR (LOGIN)") ;
+
+ /* Put the version number in the label. */
+ {
+ char *s = (char *) malloc (strlen(pw->heading_label) + 20);
+ sprintf(s, pw->heading_label, si->version);
+ free (pw->heading_label);
+ pw->heading_label = s;
+ }
+
+ /* Get hostname info */
+ pw->uname_label = strdup(""); /* Initialy, write nothing */
+
+# ifdef HAVE_UNAME
+ {
+ struct utsname uts;
+
+ if (uname (&uts) == 0)
+ {
+#if 0 /* Get the full hostname */
+ {
+ char *s;
+ if ((s = strchr(uts.nodename, '.')))
+ *s = 0;
+ }
+#endif
+ char *s = strdup (uts.nodename);
+ free (pw->uname_label);
+ pw->uname_label = s;
+ }
+ }
+# endif
+
+ pw->passwd_string = strdup("");
+
+ pw->heading_font =
+ splash_load_font (si->dpy, "passwd.headingFont", "Dialog.Font");
+ pw->button_font =
+ splash_load_font (si->dpy, "passwd.buttonFont", "Dialog.Font");
+ pw->body_font =
+ splash_load_font (si->dpy, "passwd.bodyFont", "Dialog.Font");
+ pw->label_font =
+ splash_load_font (si->dpy, "passwd.labelFont", "Dialog.Font");
+ pw->passwd_font =
+ splash_load_font (si->dpy, "passwd.passwdFont", "Dialog.Font");
+ pw->date_font =
+ splash_load_font (si->dpy, "passwd.dateFont", "Dialog.Font");
+ pw->uname_font =
+ splash_load_font (si->dpy, "passwd.unameFont", "Dialog.Font");
+
+ pw->show_uname_p = get_boolean_resource(si->dpy, "passwd.uname", "Boolean");
+
+ pw->foreground = get_pixel_resource (si->dpy, cmap,
+ "passwd.foreground",
+ "Dialog.Foreground" );
+ pw->background = get_pixel_resource (si->dpy, cmap,
+ "passwd.background",
+ "Dialog.Background" );
+ pw->border = get_pixel_resource (si->dpy, cmap,
+ "passwd.borderColor",
+ "Dialog.borderColor");
+
+ if (pw->foreground == pw->background)
+ {
+ /* Make sure the error messages show up. */
+ pw->foreground = BlackPixelOfScreen (screen);
+ pw->background = WhitePixelOfScreen (screen);
+ }
+
+ pw->passwd_foreground = get_pixel_resource (si->dpy, cmap,
+ "passwd.text.foreground",
+ "Dialog.Text.Foreground" );
+ pw->passwd_background = get_pixel_resource (si->dpy, cmap,
+ "passwd.text.background",
+ "Dialog.Text.Background" );
+ pw->button_foreground = get_pixel_resource (si->dpy, cmap,
+ "splash.Button.foreground",
+ "Dialog.Button.Foreground" );
+ pw->button_background = get_pixel_resource (si->dpy, cmap,
+ "splash.Button.background",
+ "Dialog.Button.Background" );
+ pw->thermo_foreground = get_pixel_resource (si->dpy, cmap,
+ "passwd.thermometer.foreground",
+ "Dialog.Thermometer.Foreground");
+ pw->thermo_background = get_pixel_resource ( si->dpy, cmap,
+ "passwd.thermometer.background",
+ "Dialog.Thermometer.Background");
+ pw->shadow_top = get_pixel_resource ( si->dpy, cmap,
+ "passwd.topShadowColor",
+ "Dialog.Foreground" );
+ pw->shadow_bottom = get_pixel_resource (si->dpy, cmap,
+ "passwd.bottomShadowColor",
+ "Dialog.Background" );
+
+ pw->preferred_logo_width = get_integer_resource (si->dpy,
+ "passwd.logo.width",
+ "Dialog.Logo.Width");
+ pw->preferred_logo_height = get_integer_resource (si->dpy,
+ "passwd.logo.height",
+ "Dialog.Logo.Height");
+ pw->thermo_width = get_integer_resource (si->dpy, "passwd.thermometer.width",
+ "Dialog.Thermometer.Width");
+ pw->internal_border = get_integer_resource (si->dpy,
+ "passwd.internalBorderWidth",
+ "Dialog.InternalBorderWidth");
+ pw->shadow_width = get_integer_resource (si->dpy, "passwd.shadowThickness",
+ "Dialog.ShadowThickness");
+
+ if (pw->preferred_logo_width == 0) pw->preferred_logo_width = 150;
+ if (pw->preferred_logo_height == 0) pw->preferred_logo_height = 150;
+ if (pw->internal_border == 0) pw->internal_border = 15;
+ if (pw->shadow_width == 0) pw->shadow_width = 4;
+ if (pw->thermo_width == 0) pw->thermo_width = pw->shadow_width;
+
+
+ /* We need to remember the mouse position and restore it afterward, or
+ sometimes (perhaps only with Xinerama?) the mouse gets warped to
+ inside the bounds of the lock dialog window.
+ */
+ {
+ Window pointer_root, pointer_child;
+ int root_x, root_y, win_x, win_y;
+ unsigned int mask;
+ pw->previous_mouse_x = 0;
+ pw->previous_mouse_y = 0;
+ if (XQueryPointer (si->dpy, RootWindowOfScreen (pw->prompt_screen->screen),
+ &pointer_root, &pointer_child,
+ &root_x, &root_y, &win_x, &win_y, &mask))
+ {
+ pw->previous_mouse_x = root_x;
+ pw->previous_mouse_y = root_y;
+ if (si->prefs.verbose_p)
+ fprintf (stderr, "%s: %d: mouse is at %d,%d.\n",
+ blurb(), pw->prompt_screen->number,
+ pw->previous_mouse_x, pw->previous_mouse_y);
+ }
+ else if (si->prefs.verbose_p)
+ fprintf (stderr, "%s: %d: unable to determine mouse position?\n",
+ blurb(), pw->prompt_screen->number);
+ }
+
+ /* Before mapping the window, save a pixmap of the current screen.
+ When we lower the window, we restore these bits. This works,
+ because the running screenhack has already been sent SIGSTOP, so
+ we know nothing else is drawing right now! */
+ {
+ XGCValues gcv;
+ GC gc;
+ pw->save_under = XCreatePixmap (si->dpy,
+ pw->prompt_screen->screensaver_window,
+ pw->prompt_screen->width,
+ pw->prompt_screen->height,
+ pw->prompt_screen->current_depth);
+ gcv.function = GXcopy;
+ gc = XCreateGC (si->dpy, pw->save_under, GCFunction, &gcv);
+ XCopyArea (si->dpy, pw->prompt_screen->screensaver_window,
+ pw->save_under, gc,
+ 0, 0,
+ pw->prompt_screen->width, pw->prompt_screen->height,
+ 0, 0);
+ XFreeGC (si->dpy, gc);
+ }
+
+ si->pw_data = pw;
+ return 0;
+}
+
+
+Bool debug_passwd_window_p = False; /* used only by test-passwd.c */
+
+
+/**
+ * info_msg and prompt may be NULL.
+ */
+static int
+make_passwd_window (saver_info *si,
+ const char *info_msg,
+ const char *prompt,
+ Bool echo)
+{
+ XSetWindowAttributes attrs;
+ unsigned long attrmask = 0;
+ passwd_dialog_data *pw;
+ Screen *screen;
+ Colormap cmap;
+ Dimension max_string_width_px;
+ saver_screen_info *ssi = &si->screens [mouse_screen (si)];
+
+ cleanup_passwd_window (si);
+
+ if (! ssi) /* WTF? Trying to prompt while no screens connected? */
+ return -1;
+
+ if (!si->pw_data)
+ if (new_passwd_window (si) < 0)
+ return -1;
+
+ if (!(pw = si->pw_data))
+ return -1;
+
+ pw->ratio = 1.0;
+
+ pw->prompt_screen = ssi;
+ if (si->prefs.verbose_p)
+ fprintf (stderr, "%s: %d: creating password dialog (\"%s\")\n",
+ blurb(), pw->prompt_screen->number,
+ info_msg ? info_msg : "");
+
+ screen = pw->prompt_screen->screen;
+ cmap = DefaultColormapOfScreen (screen);
+
+ pw->echo_input = echo;
+
+ max_string_width_px = ssi->width
+ - pw->shadow_width * 4
+ - pw->border_width * 2
+ - pw->thermo_width
+ - pw->preferred_logo_width
+ - pw->internal_border * 2;
+ /* As the string wraps it makes the window taller which makes the logo wider
+ * which leaves less room for the text which makes the string wrap. Uh-oh, a
+ * loop. By wrapping at a bit less than the available width, there's some
+ * room for the dialog to grow without going off the edge of the screen. */
+ max_string_width_px *= 0.75;
+
+ if (!info_msg && senesculent_p())
+ info_msg = ("\n"
+ "This version of XScreenSaver\n"
+ "is very old! Please upgrade!\n");
+
+ pw->info_label = mlstring_new(info_msg ? info_msg : pw->body_label,
+ pw->label_font, max_string_width_px);
+
+ {
+ int direction, ascent, descent;
+ XCharStruct overall;
+
+ pw->width = 0;
+ pw->height = 0;
+
+ /* Measure the heading_label. */
+ XTextExtents (pw->heading_font,
+ pw->heading_label, strlen(pw->heading_label),
+ &direction, &ascent, &descent, &overall);
+ if (overall.width > pw->width) pw->width = overall.width;
+ pw->height += ascent + descent;
+
+ /* Measure the uname_label. */
+ if ((strlen(pw->uname_label)) && pw->show_uname_p)
+ {
+ XTextExtents (pw->uname_font,
+ pw->uname_label, strlen(pw->uname_label),
+ &direction, &ascent, &descent, &overall);
+ if (overall.width > pw->width) pw->width = overall.width;
+ pw->height += ascent + descent;
+ }
+
+ {
+ Dimension w2 = 0, w3 = 0, button_w = 0;
+ Dimension h2 = 0, h3 = 0, button_h = 0;
+ const char *passwd_string = SAMPLE_INPUT;
+
+ /* Measure the user_label. */
+ XTextExtents (pw->label_font,
+ pw->user_label, strlen(pw->user_label),
+ &direction, &ascent, &descent, &overall);
+ if (overall.width > w2) w2 = overall.width;
+ h2 += ascent + descent;
+
+ /* Measure the info_label. */
+ if (pw->info_label->overall_width > pw->width)
+ pw->width = pw->info_label->overall_width;
+ h2 += pw->info_label->overall_height;
+
+ /* Measure the user string. */
+ XTextExtents (pw->passwd_font,
+ si->user, strlen(si->user),
+ &direction, &ascent, &descent, &overall);
+ overall.width += (pw->shadow_width * 4);
+ ascent += (pw->shadow_width * 4);
+ if (overall.width > w3) w3 = overall.width;
+ h3 += ascent + descent;
+
+ /* Measure the (dummy) passwd_string. */
+ if (prompt)
+ {
+ XTextExtents (pw->passwd_font,
+ passwd_string, strlen(passwd_string),
+ &direction, &ascent, &descent, &overall);
+ overall.width += (pw->shadow_width * 4);
+ ascent += (pw->shadow_width * 4);
+ if (overall.width > w3) w3 = overall.width;
+ h3 += ascent + descent;
+
+ /* Measure the prompt_label. */
+ max_string_width_px -= w3;
+ pw->prompt_label = mlstring_new (prompt, pw->label_font,
+ max_string_width_px);
+
+ if (pw->prompt_label->overall_width > w2)
+ w2 = pw->prompt_label->overall_width;
+
+ h2 += pw->prompt_label->overall_height;
+
+ w2 = w2 + w3 + (pw->shadow_width * 2);
+ h2 = MAX (h2, h3);
+ }
+
+ /* The "Unlock" button. */
+ XTextExtents (pw->label_font,
+ pw->unlock_label, strlen(pw->unlock_label),
+ &direction, &ascent, &descent, &overall);
+ button_w = overall.width;
+ button_h = ascent + descent;
+
+ /* Add some horizontal padding inside the button. */
+ button_w += ascent;
+
+ button_w += ((ascent + descent) / 2) + (pw->shadow_width * 2);
+ button_h += ((ascent + descent) / 2) + (pw->shadow_width * 2);
+
+ pw->unlock_button_width = button_w;
+ pw->unlock_button_height = button_h;
+
+ w2 = MAX (w2, button_w);
+ h2 += button_h * 1.5;
+
+ /* The "New Login" button */
+ pw->login_button_width = 0;
+ pw->login_button_height = 0;
+
+ if (pw->login_button_p)
+ {
+ pw->login_button_enabled_p = True;
+
+ /* Measure the "New Login" button */
+ XTextExtents (pw->button_font, pw->login_label,
+ strlen (pw->login_label),
+ &direction, &ascent, &descent, &overall);
+ button_w = overall.width;
+ button_h = ascent + descent;
+
+ /* Add some horizontal padding inside the buttons. */
+ button_w += ascent;
+
+ button_w += ((ascent + descent) / 2) + (pw->shadow_width * 2);
+ button_h += ((ascent + descent) / 2) + (pw->shadow_width * 2);
+
+ pw->login_button_width = button_w;
+ pw->login_button_height = button_h;
+
+ if (button_h > pw->unlock_button_height)
+ h2 += (button_h * 1.5 - pw->unlock_button_height * 1.5);
+
+ /* Use (2 * shadow_width) spacing between the buttons. Another
+ (2 * shadow_width) is required to account for button shadows. */
+ w2 = MAX (w2,
+ button_w + pw->unlock_button_width +
+ (pw->shadow_width * 4));
+ }
+
+ if (w2 > pw->width) pw->width = w2;
+ pw->height += h2;
+ }
+
+ pw->width += (pw->internal_border * 2);
+ pw->height += (pw->internal_border * 4);
+
+ pw->width += pw->thermo_width + (pw->shadow_width * 3);
+
+ if (pw->preferred_logo_height > pw->height)
+ pw->height = pw->logo_height = pw->preferred_logo_height;
+ else if (pw->height > pw->preferred_logo_height)
+ pw->logo_height = pw->height;
+
+ pw->logo_width = pw->logo_height;
+
+ pw->width += pw->logo_width;
+ }
+
+ attrmask |= CWOverrideRedirect; attrs.override_redirect = True;
+
+ if (debug_passwd_window_p)
+ attrs.override_redirect = False; /* kludge for test-passwd.c */
+
+ attrmask |= CWEventMask;
+ attrs.event_mask = (ExposureMask | KeyPressMask |
+ ButtonPressMask | ButtonReleaseMask);
+
+ /* Figure out where on the desktop to place the window so that it will
+ actually be visible; this takes into account virtual viewports as
+ well as Xinerama. */
+ {
+ saver_screen_info *ssi = &si->screens [mouse_screen (si)];
+ int x = ssi->x;
+ int y = ssi->y;
+ int w = ssi->width;
+ int h = ssi->height;
+ if (si->prefs.debug_p) w /= 2;
+ pw->x = x + ((w + pw->width) / 2) - pw->width;
+ pw->y = y + ((h + pw->height) / 2) - pw->height;
+ if (pw->x < x) pw->x = x;
+ if (pw->y < y) pw->y = y;
+ }
+
+ pw->border_width = get_integer_resource (si->dpy, "passwd.borderWidth",
+ "Dialog.BorderWidth");
+
+ /* Only create the window the first time around */
+ if (!si->passwd_dialog)
+ {
+ si->passwd_dialog =
+ XCreateWindow (si->dpy,
+ RootWindowOfScreen(screen),
+ pw->x, pw->y, pw->width, pw->height, pw->border_width,
+ DefaultDepthOfScreen (screen), InputOutput,
+ DefaultVisualOfScreen(screen),
+ attrmask, &attrs);
+ XSetWindowBackground (si->dpy, si->passwd_dialog, pw->background);
+ XSetWindowBorder (si->dpy, si->passwd_dialog, pw->border);
+
+ /* We use the default visual, not ssi->visual, so that the logo pixmap's
+ visual matches that of the si->passwd_dialog window. */
+ pw->logo_pixmap = xscreensaver_logo (ssi->screen,
+ /* ssi->current_visual, */
+ DefaultVisualOfScreen(screen),
+ si->passwd_dialog, cmap,
+ pw->background,
+ &pw->logo_pixels, &pw->logo_npixels,
+ &pw->logo_clipmask, True);
+ }
+ else /* On successive prompts, just resize the window */
+ {
+ XWindowChanges wc;
+ unsigned int mask = CWX | CWY | CWWidth | CWHeight;
+
+ wc.x = pw->x;
+ wc.y = pw->y;
+ wc.width = pw->width;
+ wc.height = pw->height;
+
+ XConfigureWindow (si->dpy, si->passwd_dialog, mask, &wc);
+ }
+
+ restore_background(si);
+
+ XMapRaised (si->dpy, si->passwd_dialog);
+ XSync (si->dpy, False);
+
+ move_mouse_grab (si, si->passwd_dialog,
+ pw->passwd_cursor,
+ pw->prompt_screen->number);
+ undo_vp_motion (si);
+
+ si->pw_data = pw;
+
+ if (cmap)
+ XInstallColormap (si->dpy, cmap);
+ draw_passwd_window (si);
+
+ return 0;
+}
+
+
+static void
+draw_passwd_window (saver_info *si)
+{
+ passwd_dialog_data *pw = si->pw_data;
+ XGCValues gcv;
+ GC gc1, gc2;
+ int spacing, height;
+ int x1, x2, x3, y1, y2;
+ int sw;
+ int tb_height;
+
+ /* Force redraw */
+ pw->passwd_changed_p = True;
+ pw->button_state_changed_p = True;
+
+ /* This height is the height of all the elements, not to be confused with
+ * the overall window height which is pw->height. It is used to compute
+ * the amount of spacing (padding) between elements. */
+ height = (pw->heading_font->ascent + pw->heading_font->descent +
+ pw->info_label->overall_height +
+ MAX (((pw->label_font->ascent + pw->label_font->descent) +
+ (pw->prompt_label ? pw->prompt_label->overall_height : 0)),
+ ((pw->passwd_font->ascent + pw->passwd_font->descent) +
+ (pw->shadow_width * 2)) * (pw->prompt_label ? 2 : 1)) +
+ pw->date_font->ascent + pw->date_font->descent);
+
+ if ((strlen(pw->uname_label)) && pw->show_uname_p)
+ height += (pw->uname_font->ascent + pw->uname_font->descent);
+
+ height += ((pw->button_font->ascent + pw->button_font->descent) * 2 +
+ 2 * pw->shadow_width);
+
+ spacing = ((pw->height - 2 * pw->shadow_width
+ - pw->internal_border - height)
+ / 10);
+
+ if (spacing < 0) spacing = 0;
+
+ gcv.foreground = pw->foreground;
+ gc1 = XCreateGC (si->dpy, si->passwd_dialog, GCForeground, &gcv);
+ gc2 = XCreateGC (si->dpy, si->passwd_dialog, GCForeground, &gcv);
+ x1 = pw->logo_width + pw->thermo_width + (pw->shadow_width * 3);
+ x3 = pw->width - (pw->shadow_width * 2);
+ y1 = (pw->shadow_width * 2) + spacing + spacing;
+
+ /* top heading
+ */
+ XSetFont (si->dpy, gc1, pw->heading_font->fid);
+ sw = string_width (pw->heading_font, pw->heading_label);
+ x2 = (x1 + ((x3 - x1 - sw) / 2));
+ y1 += spacing + pw->heading_font->ascent + pw->heading_font->descent;
+ XDrawString (si->dpy, si->passwd_dialog, gc1, x2, y1,
+ pw->heading_label, strlen(pw->heading_label));
+
+ /* uname below top heading
+ */
+ if ((strlen(pw->uname_label)) && pw->show_uname_p)
+ {
+ XSetFont (si->dpy, gc1, pw->uname_font->fid);
+ y1 += spacing + pw->uname_font->ascent + pw->uname_font->descent;
+ sw = string_width (pw->uname_font, pw->uname_label);
+ x2 = (x1 + ((x3 - x1 - sw) / 2));
+ XDrawString (si->dpy, si->passwd_dialog, gc1, x2, y1,
+ pw->uname_label, strlen(pw->uname_label));
+ }
+
+ /* the info_label (below uname)
+ */
+ x2 = (x1 + ((x3 - x1 - pw->info_label->overall_width) / 2));
+ y1 += spacing + pw->info_label->font_height / 2;
+ mlstring_draw(si->dpy, si->passwd_dialog, gc1, pw->info_label,
+ x2, y1);
+ y1 += pw->info_label->overall_height;
+
+
+ tb_height = (pw->passwd_font->ascent + pw->passwd_font->descent +
+ (pw->shadow_width * 4));
+
+ /* the "User:" prompt
+ */
+ y2 = y1;
+ XSetForeground (si->dpy, gc1, pw->foreground);
+ XSetFont (si->dpy, gc1, pw->label_font->fid);
+ y1 += (spacing + tb_height + pw->shadow_width);
+ x2 = (x1 + pw->internal_border +
+ MAX(string_width (pw->label_font, pw->user_label),
+ pw->prompt_label ? pw->prompt_label->overall_width : 0));
+ XDrawString (si->dpy, si->passwd_dialog, gc1,
+ x2 - string_width (pw->label_font, pw->user_label),
+ y1 - pw->passwd_font->descent,
+ pw->user_label, strlen(pw->user_label));
+
+ /* the prompt_label prompt
+ */
+ if (pw->prompt_label)
+ {
+ y1 += tb_height - pw->label_font->ascent + pw->shadow_width;
+ mlstring_draw(si->dpy, si->passwd_dialog, gc1, pw->prompt_label,
+ x2 - pw->prompt_label->overall_width, y1);
+ }
+
+ /* the "user name" text field
+ */
+ y1 = y2;
+ XSetForeground (si->dpy, gc1, pw->passwd_foreground);
+ XSetForeground (si->dpy, gc2, pw->passwd_background);
+ XSetFont (si->dpy, gc1, pw->passwd_font->fid);
+ y1 += (spacing + tb_height);
+ x2 += (pw->shadow_width * 4);
+
+ pw->passwd_field_width = x3 - x2 - pw->internal_border;
+ pw->passwd_field_height = (pw->passwd_font->ascent +
+ pw->passwd_font->descent +
+ pw->shadow_width);
+
+ XFillRectangle (si->dpy, si->passwd_dialog, gc2,
+ x2 - pw->shadow_width,
+ y1 - (pw->passwd_font->ascent + pw->passwd_font->descent),
+ pw->passwd_field_width, pw->passwd_field_height);
+ XDrawString (si->dpy, si->passwd_dialog, gc1,
+ x2,
+ y1 - pw->passwd_font->descent,
+ si->user, strlen(si->user));
+
+ /* the password/prompt text field
+ */
+ if (pw->prompt_label)
+ {
+ y1 += (spacing + pw->prompt_label->overall_height + pw->shadow_width*2);
+
+ pw->passwd_field_x = x2 - pw->shadow_width;
+ pw->passwd_field_y = y1 - (pw->passwd_font->ascent +
+ pw->passwd_font->descent);
+ }
+
+ /* The shadow around the text fields
+ */
+ y1 = y2;
+ y1 += (spacing + (pw->shadow_width * 3));
+ x1 = x2 - (pw->shadow_width * 2);
+ x2 = pw->passwd_field_width + (pw->shadow_width * 2);
+ y2 = pw->passwd_field_height + (pw->shadow_width * 2);
+
+ draw_shaded_rectangle (si->dpy, si->passwd_dialog,
+ x1, y1, x2, y2,
+ pw->shadow_width,
+ pw->shadow_bottom, pw->shadow_top);
+
+ if (pw->prompt_label)
+ {
+ y1 += (spacing + pw->prompt_label->overall_height + pw->shadow_width*2);
+ draw_shaded_rectangle (si->dpy, si->passwd_dialog,
+ x1, y1, x2, y2,
+ pw->shadow_width,
+ pw->shadow_bottom, pw->shadow_top);
+ }
+
+
+ /* The date, below the text fields
+ */
+ {
+ char buf[100];
+ time_t now = time ((time_t *) 0);
+ struct tm *tm = localtime (&now);
+ memset (buf, 0, sizeof(buf));
+ strftime (buf, sizeof(buf)-1, pw->date_label, tm);
+
+ XSetFont (si->dpy, gc1, pw->date_font->fid);
+ y1 += pw->shadow_width;
+ y1 += (spacing + tb_height);
+ y1 += spacing/2;
+ sw = string_width (pw->date_font, buf);
+ x2 = x1 + x2 - sw;
+ XDrawString (si->dpy, si->passwd_dialog, gc1, x2, y1, buf, strlen(buf));
+ }
+
+ /* Set up the GCs for the "New Login" and "Unlock" buttons.
+ */
+ XSetForeground(si->dpy, gc1, pw->button_foreground);
+ XSetForeground(si->dpy, gc2, pw->button_background);
+ XSetFont(si->dpy, gc1, pw->button_font->fid);
+
+ /* The "Unlock" button */
+ x2 = pw->width - pw->internal_border - (pw->shadow_width * 2);
+
+ /* right aligned button */
+ x1 = x2 - pw->unlock_button_width;
+
+ /* Add half the difference between y1 and the internal edge.
+ * It actually looks better if the internal border is ignored. */
+ y1 += ((pw->height - MAX (pw->unlock_button_height, pw->login_button_height)
+ - spacing - y1)
+ / 2);
+
+ pw->unlock_button_x = x1;
+ pw->unlock_button_y = y1;
+
+ /* The "New Login" button
+ */
+ if (pw->login_button_p)
+ {
+ /* Using the same GC as for the Unlock button */
+
+ sw = string_width (pw->button_font, pw->login_label);
+
+ /* left aligned button */
+ x1 = (pw->logo_width + pw->thermo_width + (pw->shadow_width * 3) +
+ pw->internal_border);
+
+ pw->login_button_x = x1;
+ pw->login_button_y = y1;
+ }
+
+ /* The logo
+ */
+ x1 = pw->shadow_width * 6;
+ y1 = pw->shadow_width * 6;
+ x2 = pw->logo_width - (pw->shadow_width * 12);
+ y2 = pw->logo_height - (pw->shadow_width * 12);
+
+ if (pw->logo_pixmap)
+ {
+ Window root;
+ int x, y;
+ unsigned int w, h, bw, d;
+ XGetGeometry (si->dpy, pw->logo_pixmap, &root, &x, &y, &w, &h, &bw, &d);
+ XSetForeground (si->dpy, gc1, pw->foreground);
+ XSetBackground (si->dpy, gc1, pw->background);
+ XSetClipMask (si->dpy, gc1, pw->logo_clipmask);
+ XSetClipOrigin (si->dpy, gc1,
+ x1 + ((x2 - (int)w) / 2),
+ y1 + ((y2 - (int)h) / 2));
+ if (d == 1)
+ XCopyPlane (si->dpy, pw->logo_pixmap, si->passwd_dialog, gc1,
+ 0, 0, w, h,
+ x1 + ((x2 - (int)w) / 2),
+ y1 + ((y2 - (int)h) / 2),
+ 1);
+ else
+ XCopyArea (si->dpy, pw->logo_pixmap, si->passwd_dialog, gc1,
+ 0, 0, w, h,
+ x1 + ((x2 - (int)w) / 2),
+ y1 + ((y2 - (int)h) / 2));
+ }
+
+ /* The thermometer
+ */
+ XSetForeground (si->dpy, gc1, pw->thermo_foreground);
+ XSetForeground (si->dpy, gc2, pw->thermo_background);
+
+ pw->thermo_field_x = pw->logo_width + pw->shadow_width;
+ pw->thermo_field_y = pw->shadow_width * 5;
+ pw->thermo_field_height = pw->height - (pw->shadow_width * 10);
+
+#if 0
+ /* Solid border inside the logo box. */
+ XSetForeground (si->dpy, gc1, pw->foreground);
+ XDrawRectangle (si->dpy, si->passwd_dialog, gc1, x1, y1, x2-1, y2-1);
+#endif
+
+ /* The shadow around the logo
+ */
+ draw_shaded_rectangle (si->dpy, si->passwd_dialog,
+ pw->shadow_width * 4,
+ pw->shadow_width * 4,
+ pw->logo_width - (pw->shadow_width * 8),
+ pw->logo_height - (pw->shadow_width * 8),
+ pw->shadow_width,
+ pw->shadow_bottom, pw->shadow_top);
+
+ /* The shadow around the thermometer
+ */
+ draw_shaded_rectangle (si->dpy, si->passwd_dialog,
+ pw->logo_width,
+ pw->shadow_width * 4,
+ pw->thermo_width + (pw->shadow_width * 2),
+ pw->height - (pw->shadow_width * 8),
+ pw->shadow_width,
+ pw->shadow_bottom, pw->shadow_top);
+
+#if 1
+ /* Solid border inside the thermometer. */
+ XSetForeground (si->dpy, gc1, pw->foreground);
+ XDrawRectangle (si->dpy, si->passwd_dialog, gc1,
+ pw->thermo_field_x, pw->thermo_field_y,
+ pw->thermo_width - 1, pw->thermo_field_height - 1);
+#endif
+
+ /* The shadow around the whole window
+ */
+ draw_shaded_rectangle (si->dpy, si->passwd_dialog,
+ 0, 0, pw->width, pw->height, pw->shadow_width,
+ pw->shadow_top, pw->shadow_bottom);
+
+ XFreeGC (si->dpy, gc1);
+ XFreeGC (si->dpy, gc2);
+
+ update_passwd_window (si, pw->passwd_string, pw->ratio);
+}
+
+static void
+draw_button(Display *dpy,
+ Drawable dialog,
+ XFontStruct *font,
+ unsigned long foreground, unsigned long background,
+ char *label,
+ int x, int y,
+ int width, int height,
+ int shadow_width,
+ Pixel shadow_light, Pixel shadow_dark,
+ Bool button_down)
+{
+ XGCValues gcv;
+ GC gc1, gc2;
+ int sw;
+ int label_x, label_y;
+
+ gcv.foreground = foreground;
+ gcv.font = font->fid;
+ gc1 = XCreateGC(dpy, dialog, GCForeground|GCFont, &gcv);
+ gcv.foreground = background;
+ gc2 = XCreateGC(dpy, dialog, GCForeground, &gcv);
+
+ XFillRectangle(dpy, dialog, gc2,
+ x, y, width, height);
+
+ sw = string_width(font, label);
+
+ label_x = x + ((width - sw) / 2);
+ label_y = (y + (height - (font->ascent + font->descent)) / 2 + font->ascent);
+
+ if (button_down)
+ {
+ label_x += 2;
+ label_y += 2;
+ }
+
+ XDrawString(dpy, dialog, gc1, label_x, label_y, label, strlen(label));
+
+ XFreeGC(dpy, gc1);
+ XFreeGC(dpy, gc2);
+
+ draw_shaded_rectangle(dpy, dialog, x, y, width, height,
+ shadow_width, shadow_light, shadow_dark);
+}
+
+static void
+update_passwd_window (saver_info *si, const char *printed_passwd, float ratio)
+{
+ passwd_dialog_data *pw = si->pw_data;
+ XGCValues gcv;
+ GC gc1, gc2;
+ int x, y;
+ XRectangle rects[1];
+
+ pw->ratio = ratio;
+ gcv.foreground = pw->passwd_foreground;
+ gcv.font = pw->passwd_font->fid;
+ gc1 = XCreateGC (si->dpy, si->passwd_dialog, GCForeground|GCFont, &gcv);
+ gcv.foreground = pw->passwd_background;
+ gc2 = XCreateGC (si->dpy, si->passwd_dialog, GCForeground, &gcv);
+
+ if (printed_passwd)
+ {
+ char *s = strdup (printed_passwd);
+ if (pw->passwd_string) free (pw->passwd_string);
+ pw->passwd_string = s;
+ }
+
+ if (pw->prompt_label)
+ {
+
+ /* the "password" text field
+ */
+ rects[0].x = pw->passwd_field_x;
+ rects[0].y = pw->passwd_field_y;
+ rects[0].width = pw->passwd_field_width;
+ rects[0].height = pw->passwd_field_height;
+
+ /* The user entry (password) field is double buffered.
+ * This avoids flickering, particularly in synchronous mode. */
+
+ if (pw->passwd_changed_p)
+ {
+ pw->passwd_changed_p = False;
+
+ if (pw->user_entry_pixmap)
+ {
+ XFreePixmap(si->dpy, pw->user_entry_pixmap);
+ pw->user_entry_pixmap = 0;
+ }
+
+ pw->user_entry_pixmap =
+ XCreatePixmap (si->dpy, si->passwd_dialog,
+ rects[0].width, rects[0].height,
+ DefaultDepthOfScreen (pw->prompt_screen->screen));
+
+ XFillRectangle (si->dpy, pw->user_entry_pixmap, gc2,
+ 0, 0, rects[0].width, rects[0].height);
+
+ XDrawString (si->dpy, pw->user_entry_pixmap, gc1,
+ pw->shadow_width,
+ pw->passwd_font->ascent,
+ pw->passwd_string, strlen(pw->passwd_string));
+
+ /* Ensure the new pixmap gets copied to the window */
+ pw->i_beam = 0;
+
+ }
+
+ /* The I-beam
+ */
+ if (pw->i_beam == 0)
+ {
+ /* Make the I-beam disappear */
+ XCopyArea(si->dpy, pw->user_entry_pixmap, si->passwd_dialog, gc2,
+ 0, 0, rects[0].width, rects[0].height,
+ rects[0].x, rects[0].y);
+ }
+ else if (pw->i_beam == 1)
+ {
+ /* Make the I-beam appear */
+ x = (rects[0].x + pw->shadow_width +
+ string_width (pw->passwd_font, pw->passwd_string));
+ y = rects[0].y + pw->shadow_width;
+
+ if (x > rects[0].x + rects[0].width - 1)
+ x = rects[0].x + rects[0].width - 1;
+ XDrawLine (si->dpy, si->passwd_dialog, gc1,
+ x, y,
+ x, y + pw->passwd_font->ascent +
+ pw->passwd_font->descent-1);
+ }
+
+ pw->i_beam = (pw->i_beam + 1) % 4;
+
+ }
+
+ /* the thermometer
+ */
+ y = (pw->thermo_field_height - 2) * (1.0 - pw->ratio);
+ if (y > 0)
+ {
+ XFillRectangle (si->dpy, si->passwd_dialog, gc2,
+ pw->thermo_field_x + 1,
+ pw->thermo_field_y + 1,
+ pw->thermo_width-2,
+ y);
+ XSetForeground (si->dpy, gc1, pw->thermo_foreground);
+ XFillRectangle (si->dpy, si->passwd_dialog, gc1,
+ pw->thermo_field_x + 1,
+ pw->thermo_field_y + 1 + y,
+ pw->thermo_width-2,
+ MAX (0, pw->thermo_field_height - y - 2));
+ }
+
+ if (pw->button_state_changed_p)
+ {
+ pw->button_state_changed_p = False;
+
+ /* The "Unlock" button
+ */
+ draw_button(si->dpy, si->passwd_dialog, pw->button_font,
+ pw->button_foreground, pw->button_background,
+ pw->unlock_label,
+ pw->unlock_button_x, pw->unlock_button_y,
+ pw->unlock_button_width, pw->unlock_button_height,
+ pw->shadow_width,
+ (pw->unlock_button_down_p ? pw->shadow_bottom :
+ pw->shadow_top),
+ (pw->unlock_button_down_p ? pw->shadow_top :
+ pw->shadow_bottom),
+ pw->unlock_button_down_p);
+
+ /* The "New Login" button
+ */
+ if (pw->login_button_p)
+ {
+ draw_button(si->dpy, si->passwd_dialog, pw->button_font,
+ (pw->login_button_enabled_p
+ ? pw->passwd_foreground
+ : pw->shadow_bottom),
+ pw->button_background,
+ pw->login_label,
+ pw->login_button_x, pw->login_button_y,
+ pw->login_button_width, pw->login_button_height,
+ pw->shadow_width,
+ (pw->login_button_down_p
+ ? pw->shadow_bottom
+ : pw->shadow_top),
+ (pw->login_button_down_p
+ ? pw->shadow_top
+ : pw->shadow_bottom),
+ pw->login_button_down_p);
+ }
+ }
+
+ XFreeGC (si->dpy, gc1);
+ XFreeGC (si->dpy, gc2);
+ XSync (si->dpy, False);
+}
+
+
+void
+restore_background (saver_info *si)
+{
+ passwd_dialog_data *pw = si->pw_data;
+ saver_screen_info *ssi = pw->prompt_screen;
+ XGCValues gcv;
+ GC gc;
+
+ gcv.function = GXcopy;
+
+ gc = XCreateGC (si->dpy, ssi->screensaver_window, GCFunction, &gcv);
+
+ XCopyArea (si->dpy, pw->save_under,
+ ssi->screensaver_window, gc,
+ 0, 0,
+ ssi->width, ssi->height,
+ 0, 0);
+
+ XFreeGC (si->dpy, gc);
+}
+
+
+/* Frees anything created by make_passwd_window */
+static void
+cleanup_passwd_window (saver_info *si)
+{
+ passwd_dialog_data *pw;
+
+ if (!(pw = si->pw_data))
+ return;
+
+ if (pw->info_label)
+ {
+ mlstring_free(pw->info_label);
+ pw->info_label = 0;
+ }
+
+ if (pw->prompt_label)
+ {
+ mlstring_free(pw->prompt_label);
+ pw->prompt_label = 0;
+ }
+
+ memset (pw->typed_passwd, 0, sizeof(pw->typed_passwd));
+ memset (pw->typed_passwd_char_size, 0, sizeof(pw->typed_passwd_char_size));
+ memset (pw->passwd_string, 0, strlen(pw->passwd_string));
+
+ if (pw->timer)
+ {
+ XtRemoveTimeOut (pw->timer);
+ pw->timer = 0;
+ }
+
+ if (pw->user_entry_pixmap)
+ {
+ XFreePixmap(si->dpy, pw->user_entry_pixmap);
+ pw->user_entry_pixmap = 0;
+ }
+}
+
+
+static void
+destroy_passwd_window (saver_info *si)
+{
+ saver_preferences *p = &si->prefs;
+ passwd_dialog_data *pw = si->pw_data;
+ saver_screen_info *ssi = pw->prompt_screen;
+ Colormap cmap = DefaultColormapOfScreen (ssi->screen);
+ Pixel black = BlackPixelOfScreen (ssi->screen);
+ Pixel white = WhitePixelOfScreen (ssi->screen);
+ XEvent event;
+
+ cleanup_passwd_window (si);
+
+ if (si->cached_passwd)
+ {
+ char *wipe = si->cached_passwd;
+
+ while (*wipe)
+ *wipe++ = '\0';
+
+ free(si->cached_passwd);
+ si->cached_passwd = NULL;
+ }
+
+ move_mouse_grab (si, RootWindowOfScreen (ssi->screen),
+ ssi->cursor, ssi->number);
+
+ if (pw->passwd_cursor)
+ XFreeCursor (si->dpy, pw->passwd_cursor);
+
+ if (p->verbose_p)
+ fprintf (stderr, "%s: %d: moving mouse back to %d,%d.\n",
+ blurb(), ssi->number,
+ pw->previous_mouse_x, pw->previous_mouse_y);
+
+ XWarpPointer (si->dpy, None, RootWindowOfScreen (ssi->screen),
+ 0, 0, 0, 0,
+ pw->previous_mouse_x, pw->previous_mouse_y);
+ XSync (si->dpy, False);
+
+ while (XCheckMaskEvent (si->dpy, PointerMotionMask, &event))
+ if (p->verbose_p)
+ fprintf (stderr, "%s: discarding MotionNotify event.\n", blurb());
+
+#ifdef HAVE_XINPUT
+ if (si->using_xinput_extension && si->xinput_DeviceMotionNotify)
+ while (XCheckTypedEvent (si->dpy, si->xinput_DeviceMotionNotify, &event))
+ if (p->verbose_p)
+ fprintf (stderr, "%s: discarding DeviceMotionNotify event.\n",
+ blurb());
+#endif
+
+ if (si->passwd_dialog)
+ {
+ if (si->prefs.verbose_p)
+ fprintf (stderr, "%s: %d: destroying password dialog.\n",
+ blurb(), pw->prompt_screen->number);
+
+ XDestroyWindow (si->dpy, si->passwd_dialog);
+ si->passwd_dialog = 0;
+ }
+
+ if (pw->save_under)
+ {
+ restore_background(si);
+ XFreePixmap (si->dpy, pw->save_under);
+ pw->save_under = 0;
+ }
+
+ if (pw->heading_label) free (pw->heading_label);
+ if (pw->body_label) free (pw->body_label);
+ if (pw->user_label) free (pw->user_label);
+ if (pw->date_label) free (pw->date_label);
+ if (pw->login_label) free (pw->login_label);
+ if (pw->unlock_label) free (pw->unlock_label);
+ if (pw->passwd_string) free (pw->passwd_string);
+ if (pw->uname_label) free (pw->uname_label);
+
+ if (pw->heading_font) XFreeFont (si->dpy, pw->heading_font);
+ if (pw->body_font) XFreeFont (si->dpy, pw->body_font);
+ if (pw->label_font) XFreeFont (si->dpy, pw->label_font);
+ if (pw->passwd_font) XFreeFont (si->dpy, pw->passwd_font);
+ if (pw->date_font) XFreeFont (si->dpy, pw->date_font);
+ if (pw->button_font) XFreeFont (si->dpy, pw->button_font);
+ if (pw->uname_font) XFreeFont (si->dpy, pw->uname_font);
+
+ if (pw->foreground != black && pw->foreground != white)
+ XFreeColors (si->dpy, cmap, &pw->foreground, 1, 0L);
+ if (pw->background != black && pw->background != white)
+ XFreeColors (si->dpy, cmap, &pw->background, 1, 0L);
+ if (!(pw->button_foreground == black || pw->button_foreground == white))
+ XFreeColors (si->dpy, cmap, &pw->button_foreground, 1, 0L);
+ if (!(pw->button_background == black || pw->button_background == white))
+ XFreeColors (si->dpy, cmap, &pw->button_background, 1, 0L);
+ if (pw->passwd_foreground != black && pw->passwd_foreground != white)
+ XFreeColors (si->dpy, cmap, &pw->passwd_foreground, 1, 0L);
+ if (pw->passwd_background != black && pw->passwd_background != white)
+ XFreeColors (si->dpy, cmap, &pw->passwd_background, 1, 0L);
+ if (pw->thermo_foreground != black && pw->thermo_foreground != white)
+ XFreeColors (si->dpy, cmap, &pw->thermo_foreground, 1, 0L);
+ if (pw->thermo_background != black && pw->thermo_background != white)
+ XFreeColors (si->dpy, cmap, &pw->thermo_background, 1, 0L);
+ if (pw->shadow_top != black && pw->shadow_top != white)
+ XFreeColors (si->dpy, cmap, &pw->shadow_top, 1, 0L);
+ if (pw->shadow_bottom != black && pw->shadow_bottom != white)
+ XFreeColors (si->dpy, cmap, &pw->shadow_bottom, 1, 0L);
+
+ if (pw->logo_pixmap)
+ XFreePixmap (si->dpy, pw->logo_pixmap);
+ if (pw-> logo_clipmask)
+ XFreePixmap (si->dpy, pw->logo_clipmask);
+ if (pw->logo_pixels)
+ {
+ if (pw->logo_npixels)
+ XFreeColors (si->dpy, cmap, pw->logo_pixels, pw->logo_npixels, 0L);
+ free (pw->logo_pixels);
+ pw->logo_pixels = 0;
+ pw->logo_npixels = 0;
+ }
+
+ if (pw->save_under)
+ XFreePixmap (si->dpy, pw->save_under);
+
+ if (cmap)
+ XInstallColormap (si->dpy, cmap);
+
+ memset (pw, 0, sizeof(*pw));
+ free (pw);
+ si->pw_data = 0;
+}
+
+
+#if defined(HAVE_XF86MISCSETGRABKEYSSTATE) || defined(HAVE_XF86VMODE)
+
+static Bool error_handler_hit_p = False;
+
+static int
+ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
+{
+ error_handler_hit_p = True;
+ return 0;
+}
+
+#endif /* HAVE_XF86MISCSETGRABKEYSSTATE || HAVE_XF86VMODE */
+
+
+#ifdef HAVE_XHPDISABLERESET
+/* This function enables and disables the C-Sh-Reset hot-key, which
+ normally resets the X server (logging out the logged-in user.)
+ We don't want random people to be able to do that while the
+ screen is locked.
+ */
+static void
+hp_lock_reset (saver_info *si, Bool lock_p)
+{
+ static Bool hp_locked_p = False;
+
+ /* Calls to XHPDisableReset and XHPEnableReset must be balanced,
+ or BadAccess errors occur. (It's ok for this to be global,
+ since it affects the whole machine, not just the current screen.)
+ */
+ if (hp_locked_p == lock_p)
+ return;
+
+ if (lock_p)
+ XHPDisableReset (si->dpy);
+ else
+ XHPEnableReset (si->dpy);
+ hp_locked_p = lock_p;
+}
+#endif /* HAVE_XHPDISABLERESET */
+
+
+#ifdef HAVE_XF86MISCSETGRABKEYSSTATE
+
+/* This function enables and disables the Ctrl-Alt-KP_star and
+ Ctrl-Alt-KP_slash hot-keys, which (in XFree86 4.2) break any
+ grabs and/or kill the grabbing client. That would effectively
+ unlock the screen, so we don't like that.
+
+ The Ctrl-Alt-KP_star and Ctrl-Alt-KP_slash hot-keys only exist
+ if AllowDeactivateGrabs and/or AllowClosedownGrabs are turned on
+ in XF86Config. I believe they are disabled by default.
+
+ This does not affect any other keys (specifically Ctrl-Alt-BS or
+ Ctrl-Alt-F1) but I wish it did. Maybe it will someday.
+ */
+static void
+xfree_lock_grab_smasher (saver_info *si, Bool lock_p)
+{
+ saver_preferences *p = &si->prefs;
+ int status;
+ int event, error;
+ XErrorHandler old_handler;
+
+ if (!XF86MiscQueryExtension(si->dpy, &event, &error))
+ return;
+
+ XSync (si->dpy, False);
+ error_handler_hit_p = False;
+ old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
+ XSync (si->dpy, False);
+ status = XF86MiscSetGrabKeysState (si->dpy, !lock_p);
+ XSync (si->dpy, False);
+ if (error_handler_hit_p) status = 666;
+
+ if (!lock_p && status == MiscExtGrabStateAlready)
+ status = MiscExtGrabStateSuccess; /* shut up, consider this success */
+
+ if (p->verbose_p && status != MiscExtGrabStateSuccess)
+ fprintf (stderr, "%s: error: XF86MiscSetGrabKeysState(%d) returned %s\n",
+ blurb(), !lock_p,
+ (status == MiscExtGrabStateSuccess ? "MiscExtGrabStateSuccess" :
+ status == MiscExtGrabStateLocked ? "MiscExtGrabStateLocked" :
+ status == MiscExtGrabStateAlready ? "MiscExtGrabStateAlready" :
+ status == 666 ? "an X error" :
+ "unknown value"));
+
+ XSync (si->dpy, False);
+ XSetErrorHandler (old_handler);
+ XSync (si->dpy, False);
+}
+#endif /* HAVE_XF86MISCSETGRABKEYSSTATE */
+
+
+
+/* This function enables and disables the C-Alt-Plus and C-Alt-Minus
+ hot-keys, which normally change the resolution of the X server.
+ We don't want people to be able to switch the server resolution
+ while the screen is locked, because if they switch to a higher
+ resolution, it could cause part of the underlying desktop to become
+ exposed.
+ */
+#ifdef HAVE_XF86VMODE
+
+static void
+xfree_lock_mode_switch (saver_info *si, Bool lock_p)
+{
+ static Bool any_mode_locked_p = False;
+ saver_preferences *p = &si->prefs;
+ int screen;
+ int real_nscreens = ScreenCount (si->dpy);
+ int event, error;
+ Bool status;
+ XErrorHandler old_handler;
+
+ if (any_mode_locked_p == lock_p)
+ return;
+ if (!XF86VidModeQueryExtension (si->dpy, &event, &error))
+ return;
+
+ for (screen = 0; screen < real_nscreens; screen++)
+ {
+ XSync (si->dpy, False);
+ old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
+ error_handler_hit_p = False;
+ status = XF86VidModeLockModeSwitch (si->dpy, screen, lock_p);
+ XSync (si->dpy, False);
+ XSetErrorHandler (old_handler);
+ if (error_handler_hit_p) status = False;
+
+ if (status)
+ any_mode_locked_p = lock_p;
+
+ if (!status && (p->verbose_p || !lock_p))
+ /* Only print this when verbose, or when we locked but can't unlock.
+ I tried printing this message whenever it comes up, but
+ mode-locking always fails if DontZoom is set in XF86Config. */
+ fprintf (stderr, "%s: %d: unable to %s mode switching!\n",
+ blurb(), screen, (lock_p ? "lock" : "unlock"));
+ else if (p->verbose_p)
+ fprintf (stderr, "%s: %d: %s mode switching.\n",
+ blurb(), screen, (lock_p ? "locked" : "unlocked"));
+ }
+}
+#endif /* HAVE_XF86VMODE */
+
+
+/* If the viewport has been scrolled since the screen was blanked,
+ then scroll it back to where it belongs. This function only exists
+ to patch over a very brief race condition.
+ */
+static void
+undo_vp_motion (saver_info *si)
+{
+#ifdef HAVE_XF86VMODE
+ saver_preferences *p = &si->prefs;
+ int screen;
+ int real_nscreens = ScreenCount (si->dpy);
+ int event, error;
+
+ if (!XF86VidModeQueryExtension (si->dpy, &event, &error))
+ return;
+
+ for (screen = 0; screen < real_nscreens; screen++)
+ {
+ saver_screen_info *ssi = &si->screens[screen];
+ int x, y;
+ Bool status;
+
+ if (ssi->blank_vp_x == -1 && ssi->blank_vp_y == -1)
+ break;
+ if (!XF86VidModeGetViewPort (si->dpy, screen, &x, &y))
+ return;
+ if (ssi->blank_vp_x == x && ssi->blank_vp_y == y)
+ return;
+
+ /* We're going to move the viewport. The mouse has just been grabbed on
+ (and constrained to, thus warped to) the password window, so it is no
+ longer near the edge of the screen. However, wait a bit anyway, just
+ to make sure the server drains its last motion event, so that the
+ screen doesn't continue to scroll after we've reset the viewport.
+ */
+ XSync (si->dpy, False);
+ usleep (250000); /* 1/4 second */
+ XSync (si->dpy, False);
+
+ status = XF86VidModeSetViewPort (si->dpy, screen,
+ ssi->blank_vp_x, ssi->blank_vp_y);
+
+ if (!status)
+ fprintf (stderr,
+ "%s: %d: unable to move vp from (%d,%d) back to (%d,%d)!\n",
+ blurb(), screen, x, y, ssi->blank_vp_x, ssi->blank_vp_y);
+ else if (p->verbose_p)
+ fprintf (stderr,
+ "%s: %d: vp moved to (%d,%d); moved it back to (%d,%d).\n",
+ blurb(), screen, x, y, ssi->blank_vp_x, ssi->blank_vp_y);
+ }
+#endif /* HAVE_XF86VMODE */
+}
+
+
+
+/* Interactions
+ */
+
+static void
+passwd_animate_timer (XtPointer closure, XtIntervalId *id)
+{
+ saver_info *si = (saver_info *) closure;
+ int tick = 166;
+ passwd_dialog_data *pw = si->pw_data;
+
+ if (!pw) return;
+
+ pw->ratio -= (1.0 / ((double) si->prefs.passwd_timeout / (double) tick));
+ if (pw->ratio < 0)
+ {
+ pw->ratio = 0;
+ if (si->unlock_state == ul_read)
+ si->unlock_state = ul_time;
+ }
+
+ update_passwd_window (si, 0, pw->ratio);
+
+ if (si->unlock_state == ul_read)
+ pw->timer = XtAppAddTimeOut (si->app, tick, passwd_animate_timer,
+ (XtPointer) si);
+ else
+ pw->timer = 0;
+
+ idle_timer ((XtPointer) si, 0);
+}
+
+
+static XComposeStatus *compose_status;
+
+static void
+handle_login_button (saver_info *si, XEvent *event)
+{
+ saver_preferences *p = &si->prefs;
+ Bool mouse_in_box = False;
+ Bool hit_p = False;
+ passwd_dialog_data *pw = si->pw_data;
+ saver_screen_info *ssi = pw->prompt_screen;
+
+ if (! pw->login_button_enabled_p)
+ return;
+
+ mouse_in_box =
+ (event->xbutton.x >= pw->login_button_x &&
+ event->xbutton.x <= pw->login_button_x + pw->login_button_width &&
+ event->xbutton.y >= pw->login_button_y &&
+ event->xbutton.y <= pw->login_button_y + pw->login_button_height);
+
+ if (ButtonRelease == event->xany.type &&
+ pw->login_button_down_p &&
+ mouse_in_box)
+ {
+ /* Only allow them to press the button once: don't want to
+ accidentally launch a dozen gdm choosers if the machine
+ is being slow.
+ */
+ hit_p = True;
+ pw->login_button_enabled_p = False;
+ }
+
+ pw->login_button_down_p = (mouse_in_box &&
+ ButtonRelease != event->xany.type);
+
+ update_passwd_window (si, 0, pw->ratio);
+
+ if (hit_p)
+ fork_and_exec (ssi, p->new_login_command);
+}
+
+
+static void
+handle_unlock_button (saver_info *si, XEvent *event)
+{
+ Bool mouse_in_box = False;
+ passwd_dialog_data *pw = si->pw_data;
+
+ mouse_in_box =
+ (event->xbutton.x >= pw->unlock_button_x &&
+ event->xbutton.x <= pw->unlock_button_x + pw->unlock_button_width &&
+ event->xbutton.y >= pw->unlock_button_y &&
+ event->xbutton.y <= pw->unlock_button_y + pw->unlock_button_height);
+
+ if (ButtonRelease == event->xany.type &&
+ pw->unlock_button_down_p &&
+ mouse_in_box)
+ finished_typing_passwd (si, pw);
+
+ pw->unlock_button_down_p = (mouse_in_box &&
+ ButtonRelease != event->xany.type);
+}
+
+
+static void
+finished_typing_passwd (saver_info *si, passwd_dialog_data *pw)
+{
+ if (si->unlock_state == ul_read)
+ {
+ update_passwd_window (si, "Checking...", pw->ratio);
+ XSync (si->dpy, False);
+
+ si->unlock_state = ul_finished;
+ update_passwd_window (si, "", pw->ratio);
+ }
+}
+
+static void
+handle_passwd_key (saver_info *si, XKeyEvent *event)
+{
+ passwd_dialog_data *pw = si->pw_data;
+ 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.c.
+ */
+ int decoded_size = XLookupString (event, (char *)decoded, sizeof(decoded),
+ &keysym, compose_status);
+
+#if 0
+ {
+ const char *ks = XKeysymToString (keysym);
+ int i;
+ fprintf(stderr, "## %-12s\t=> %d\t", (ks ? ks : "(null)"), decoded_size);
+ for (i = 0; i < decoded_size; i++)
+ fprintf(stderr, "%c", decoded[i]);
+ fprintf(stderr, "\t");
+ for (i = 0; i < decoded_size; i++)
+ fprintf(stderr, "\\%03o", ((unsigned char *)decoded)[i]);
+ fprintf(stderr, "\n");
+ }
+#endif
+
+ if (decoded_size > MAX_BYTES_PER_CHAR)
+ {
+ /* The multi-byte character returned is too large. */
+ XBell (si->dpy, 0);
+ return;
+ }
+
+ decoded[decoded_size] = 0;
+ pw->passwd_changed_p = True;
+
+ /* Add 10% to the time remaining every time a key is pressed. */
+ pw->ratio += 0.1;
+ if (pw->ratio > 1) pw->ratio = 1;
+
+ 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 (pw->typed_passwd_char_size);
+ int nbytes = strlen (pw->typed_passwd);
+ if (nbytes <= 0)
+ XBell (si->dpy, 0);
+ else
+ {
+ int i;
+ for (i = pw->typed_passwd_char_size[nchars-1]; i >= 0; i--)
+ {
+ if (nbytes < 0) abort();
+ pw->typed_passwd[nbytes--] = 0;
+ }
+ pw->typed_passwd_char_size[nchars-1] = 0;
+ }
+ }
+ break;
+
+ case '\012': case '\015': /* Enter */
+ finished_typing_passwd (si, pw);
+ break;
+
+ case '\033': /* Escape */
+ si->unlock_state = ul_cancel;
+ break;
+
+ case '\025': case '\030': /* Erase line */
+ memset (pw->typed_passwd, 0, sizeof (pw->typed_passwd));
+ memset (pw->typed_passwd_char_size, 0,
+ sizeof (pw->typed_passwd_char_size));
+ break;
+
+ default:
+ if (*decoded < ' ' && *decoded != '\t') /* Other ctrl char */
+ XBell (si->dpy, 0);
+ else
+ goto SELF_INSERT;
+ break;
+ }
+ }
+ else
+ {
+ int nbytes, nchars;
+ SELF_INSERT:
+ nbytes = strlen (pw->typed_passwd);
+ nchars = strlen (pw->typed_passwd_char_size);
+ if (nchars + 1 >= sizeof (pw->typed_passwd_char_size)-1 ||
+ nbytes + decoded_size >= sizeof (pw->typed_passwd)-1) /* overflow */
+ XBell (si->dpy, 0);
+ else
+ {
+ pw->typed_passwd_char_size[nchars] = decoded_size;
+ pw->typed_passwd_char_size[nchars+1] = 0;
+ memcpy (pw->typed_passwd + nbytes, decoded, decoded_size);
+ pw->typed_passwd[nbytes + decoded_size] = 0;
+ }
+ }
+
+ if (pw->echo_input)
+ {
+ /* If the input is wider than the text box, only show the last portion,
+ to simulate a horizontally-scrolling text field. */
+ int chars_in_pwfield = (pw->passwd_field_width /
+ pw->passwd_font->max_bounds.width);
+ const char *output = pw->typed_passwd;
+ if (strlen(output) > chars_in_pwfield)
+ output += (strlen(output) - chars_in_pwfield);
+ update_passwd_window (si, output, pw->ratio);
+ }
+ else if (pw->show_stars_p)
+ {
+ int nchars = strlen (pw->typed_passwd_char_size);
+ char *stars = 0;
+ stars = (char *) malloc(nchars + 1);
+ memset (stars, '*', nchars);
+ stars[nchars] = 0;
+ update_passwd_window (si, stars, pw->ratio);
+ free (stars);
+ }
+ else
+ {
+ update_passwd_window (si, "", pw->ratio);
+ }
+}
+
+
+static void
+passwd_event_loop (saver_info *si)
+{
+ saver_preferences *p = &si->prefs;
+ char *msg = 0;
+
+ /* 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;
+
+ passwd_animate_timer ((XtPointer) si, 0);
+ reset_watchdog_timer (si, False); /* Disable watchdog while dialog up */
+
+ while (si->unlock_state == ul_read)
+ {
+ XtAppNextEvent (si->app, &event.x_event);
+
+#ifdef HAVE_RANDR
+ 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)
+ {
+ /* XRRRootToScreen is in Xrandr.h 1.4, 2001/06/07 */
+ int screen = XRRRootToScreen(si->dpy, event.xrr_event.window);
+ fprintf (stderr, "%s: %d: screen change event received\n",
+ blurb(), screen);
+ }
+
+#ifdef RRScreenChangeNotifyMask
+ /* Inform Xlib that it's ok to update its data structures. */
+ XRRUpdateConfiguration(&event.x_event); /* Xrandr.h 1.9, 2002/09/29*/
+#endif /* RRScreenChangeNotifyMask */
+
+ /* 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);
+ }
+ resize_screensaver_window (si);
+ }
+ }
+ else
+#endif /* HAVE_RANDR */
+
+ if (event.x_event.xany.window == si->passwd_dialog &&
+ event.x_event.xany.type == Expose)
+ draw_passwd_window (si);
+ else if (event.x_event.xany.type == KeyPress)
+ {
+ handle_passwd_key (si, &event.x_event.xkey);
+ si->pw_data->caps_p = (event.x_event.xkey.state & LockMask);
+ }
+ else if (event.x_event.xany.type == ButtonPress ||
+ event.x_event.xany.type == ButtonRelease)
+ {
+ si->pw_data->button_state_changed_p = True;
+ handle_unlock_button (si, &event.x_event);
+ if (si->pw_data->login_button_p)
+ handle_login_button (si, &event.x_event);
+ }
+ else
+ XtDispatchEvent (&event.x_event);
+ }
+
+ switch (si->unlock_state)
+ {
+ case ul_cancel: msg = ""; break;
+ case ul_time: msg = "Timed out!"; break;
+ case ul_finished: msg = "Checking..."; break;
+ default: msg = 0; break;
+ }
+
+ if (p->verbose_p)
+ switch (si->unlock_state) {
+ case ul_cancel:
+ fprintf (stderr, "%s: input cancelled.\n", blurb()); break;
+ case ul_time:
+ fprintf (stderr, "%s: input timed out.\n", blurb()); break;
+ case ul_finished:
+ fprintf (stderr, "%s: input finished.\n", blurb()); break;
+ default: break;
+ }
+
+ if (msg)
+ {
+ si->pw_data->i_beam = 0;
+ update_passwd_window (si, msg, 0.0);
+ XSync (si->dpy, False);
+
+ /* Swallow all pending KeyPress/KeyRelease events. */
+ {
+ XEvent e;
+ while (XCheckMaskEvent (si->dpy, KeyPressMask|KeyReleaseMask, &e))
+ ;
+ }
+ }
+
+ reset_watchdog_timer (si, True); /* Re-enable watchdog */
+}
+
+
+static void
+handle_typeahead (saver_info *si)
+{
+ passwd_dialog_data *pw = si->pw_data;
+ int i;
+ if (!si->unlock_typeahead)
+ return;
+
+ pw->passwd_changed_p = True;
+
+ i = strlen (si->unlock_typeahead);
+ if (i >= sizeof(pw->typed_passwd) - 1)
+ i = sizeof(pw->typed_passwd) - 1;
+
+ memcpy (pw->typed_passwd, si->unlock_typeahead, i);
+ pw->typed_passwd [i] = 0;
+ {
+ int j;
+ char *c = pw->typed_passwd_char_size;
+ for (j = 0; j < i; j++)
+ *c++ = 1;
+ *c = 0;
+ }
+
+ memset (si->unlock_typeahead, '*', strlen(si->unlock_typeahead));
+ si->unlock_typeahead[i] = 0;
+ update_passwd_window (si, si->unlock_typeahead, pw->ratio);
+
+ free (si->unlock_typeahead);
+ si->unlock_typeahead = 0;
+}
+
+
+/**
+ * Returns a copy of the input string with trailing whitespace removed.
+ * Whitespace is anything considered so by isspace().
+ * It is safe to call this with NULL, in which case NULL will be returned.
+ * The returned string (if not NULL) should be freed by the caller with free().
+ */
+static char *
+remove_trailing_whitespace(const char *str)
+{
+ size_t len;
+ char *newstr, *chr;
+
+ if (!str)
+ return NULL;
+
+ len = strlen(str);
+
+ newstr = malloc(len + 1);
+ if (!newstr)
+ return NULL;
+
+ (void) strcpy(newstr, str);
+ chr = newstr + len;
+ while (isspace(*--chr) && chr >= newstr)
+ *chr = '\0';
+
+ return newstr;
+}
+
+
+/*
+ * The authentication conversation function.
+ * Like a PAM conversation function, this accepts multiple messages in a single
+ * round. It then splits them into individual messages for display on the
+ * passwd dialog. A message sequence of info or error followed by a prompt will
+ * be reduced into a single dialog window.
+ *
+ * Returns 0 on success or -1 if some problem occurred (cancelled, OOM, etc.)
+ */
+int
+gui_auth_conv(int num_msg,
+ const struct auth_message auth_msgs[],
+ struct auth_response **resp,
+ saver_info *si)
+{
+ int i;
+ const char *info_msg, *prompt;
+ struct auth_response *responses;
+
+ if (si->unlock_state == ul_cancel ||
+ si->unlock_state == ul_time)
+ /* If we've already cancelled or timed out in this PAM conversation,
+ don't prompt again even if PAM asks us to! */
+ return -1;
+
+ if (!(responses = calloc(num_msg, sizeof(struct auth_response))))
+ goto fail;
+
+ for (i = 0; i < num_msg; ++i)
+ {
+ info_msg = prompt = NULL;
+
+ /* See if there is a following message that can be shown at the same
+ * time */
+ if (auth_msgs[i].type == AUTH_MSGTYPE_INFO
+ && i+1 < num_msg
+ && ( auth_msgs[i+1].type == AUTH_MSGTYPE_PROMPT_NOECHO
+ || auth_msgs[i+1].type == AUTH_MSGTYPE_PROMPT_ECHO)
+ )
+ {
+ info_msg = auth_msgs[i].msg;
+ prompt = auth_msgs[++i].msg;
+ }
+ else
+ {
+ if ( auth_msgs[i].type == AUTH_MSGTYPE_INFO
+ || auth_msgs[i].type == AUTH_MSGTYPE_ERROR)
+ info_msg = auth_msgs[i].msg;
+ else
+ prompt = auth_msgs[i].msg;
+ }
+
+ {
+ char *info_msg_trimmed, *prompt_trimmed;
+
+ /* Trailing whitespace looks bad in a GUI */
+ info_msg_trimmed = remove_trailing_whitespace(info_msg);
+ prompt_trimmed = remove_trailing_whitespace(prompt);
+
+ if (make_passwd_window(si, info_msg_trimmed, prompt_trimmed,
+ auth_msgs[i].type == AUTH_MSGTYPE_PROMPT_ECHO
+ ? True : False)
+ < 0)
+ goto fail;
+
+ if (info_msg_trimmed)
+ free(info_msg_trimmed);
+
+ if (prompt_trimmed)
+ free(prompt_trimmed);
+ }
+
+ compose_status = calloc (1, sizeof (*compose_status));
+ if (!compose_status)
+ goto fail;
+
+ si->unlock_state = ul_read;
+
+ handle_typeahead (si);
+ passwd_event_loop (si);
+
+ if (si->unlock_state == ul_cancel)
+ goto fail;
+
+ responses[i].response = strdup(si->pw_data->typed_passwd);
+
+ /* Cache the first response to a PROMPT_NOECHO to save prompting for
+ * each auth mechanism. */
+ if (si->cached_passwd == NULL &&
+ auth_msgs[i].type == AUTH_MSGTYPE_PROMPT_NOECHO)
+ si->cached_passwd = strdup(responses[i].response);
+
+ free (compose_status);
+ compose_status = 0;
+ }
+
+ *resp = responses;
+
+ return (si->unlock_state == ul_finished) ? 0 : -1;
+
+fail:
+ if (compose_status)
+ free (compose_status);
+ compose_status = 0;
+
+ if (responses)
+ {
+ for (i = 0; i < num_msg; ++i)
+ if (responses[i].response)
+ free (responses[i].response);
+ free (responses);
+ }
+
+ return -1;
+}
+
+
+void
+auth_finished_cb (saver_info *si)
+{
+ char buf[1024];
+ const char *s;
+
+ /* If we have something to say, put the dialog back up for a few seconds
+ to display it. Otherwise, don't bother.
+ */
+
+ if (si->unlock_state == ul_fail && /* failed with caps lock on */
+ si->pw_data && si->pw_data->caps_p)
+ s = "Authentication failed (Caps Lock?)";
+ else if (si->unlock_state == ul_fail) /* failed without caps lock */
+ s = "Authentication failed!";
+ else if (si->unlock_state == ul_success && /* good, but report failures */
+ si->unlock_failures > 0)
+ {
+ if (si->unlock_failures == 1)
+ s = "There has been\n1 failed login attempt.";
+ else
+ {
+ sprintf (buf, "There have been\n%d failed login attempts.",
+ si->unlock_failures);
+ s = buf;
+ }
+ si->unlock_failures = 0;
+
+ /* ignore failures if they all were too recent */
+ if (time((time_t *) 0) - si->unlock_failure_time
+ < si->prefs.auth_warning_slack)
+ goto END;
+ }
+ else /* good, with no failures, */
+ goto END; /* or timeout, or cancel. */
+
+ make_passwd_window (si, s, NULL, True);
+ XSync (si->dpy, False);
+
+ {
+ int secs = 4;
+ time_t start = time ((time_t *) 0);
+ XEvent event;
+ while (time ((time_t *) 0) < start + secs)
+ if (XPending (si->dpy))
+ {
+ XNextEvent (si->dpy, &event);
+ if (event.xany.window == si->passwd_dialog &&
+ event.xany.type == Expose)
+ draw_passwd_window (si);
+ else if (event.xany.type == ButtonPress ||
+ event.xany.type == KeyPress)
+ break;
+ XSync (si->dpy, False);
+ }
+ else
+ usleep (250000); /* 1/4 second */
+ }
+
+ END:
+ if (si->pw_data)
+ destroy_passwd_window (si);
+}
+
+
+Bool
+unlock_p (saver_info *si)
+{
+ saver_preferences *p = &si->prefs;
+
+ if (!si->unlock_cb)
+ {
+ fprintf(stderr, "%s: Error: no unlock function specified!\n", blurb());
+ return False;
+ }
+
+ raise_window (si, True, True, True);
+
+ xss_authenticate(si, p->verbose_p);
+
+ return (si->unlock_state == ul_success);
+}
+
+
+void
+set_locked_p (saver_info *si, Bool locked_p)
+{
+ si->locked_p = locked_p;
+
+#ifdef HAVE_XHPDISABLERESET
+ hp_lock_reset (si, locked_p); /* turn off/on C-Sh-Reset */
+#endif
+#ifdef HAVE_XF86VMODE
+ xfree_lock_mode_switch (si, locked_p); /* turn off/on C-Alt-Plus */
+#endif
+#ifdef HAVE_XF86MISCSETGRABKEYSSTATE
+ xfree_lock_grab_smasher (si, locked_p); /* turn off/on C-Alt-KP-*,/ */
+#endif
+
+ store_saver_status (si); /* store locked-p */
+}
+
+
+#else /* NO_LOCKING -- whole file */
+
+void
+set_locked_p (saver_info *si, Bool locked_p)
+{
+ if (locked_p) abort();
+}
+
+#endif /* !NO_LOCKING */
diff --git a/driver/mlstring.c b/driver/mlstring.c
new file mode 100644
index 0000000..fdba1ee
--- /dev/null
+++ b/driver/mlstring.c
@@ -0,0 +1,229 @@
+/*
+ * (c) 2007, Quest Software, Inc. All rights reserved.
+ *
+ * This file is part of XScreenSaver,
+ * Copyright (c) 1993-2009 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.
+ */
+
+#include <stdlib.h>
+#include <ctype.h>
+
+#include <X11/Xlib.h>
+
+#include "mlstring.h"
+
+#define LINE_SPACING 1.2
+
+static mlstring *
+mlstring_allocate(const char *msg);
+
+static void
+mlstring_calculate(mlstring *str, XFontStruct *font);
+
+mlstring*
+mlstring_new(const char *msg, XFontStruct *font, Dimension wrap_width)
+{
+ mlstring *newstr;
+
+ if (!(newstr = mlstring_allocate(msg)))
+ return NULL;
+
+ newstr->font_id = font->fid;
+
+ mlstring_wrap(newstr, font, wrap_width);
+
+ return newstr;
+}
+
+mlstring *
+mlstring_allocate(const char *msg)
+{
+ const char *s;
+ mlstring *ml;
+ struct mlstr_line *cur, *prev = NULL;
+ size_t linelength;
+ int the_end = 0;
+
+ if (!msg)
+ return NULL;
+
+ ml = calloc(1, sizeof(mlstring));
+
+ if (!ml)
+ return NULL;
+
+ for (s = msg; !the_end; msg = ++s)
+ {
+ /* New string struct */
+ cur = calloc(1, sizeof(struct mlstr_line));
+ if (!cur)
+ goto fail;
+
+ if (!ml->lines)
+ ml->lines = cur;
+
+ /* Find the \n or end of string */
+ while (*s != '\n')
+ {
+ if (*s == '\0')
+ {
+ the_end = 1;
+ break;
+ }
+
+ ++s;
+ }
+
+ linelength = s - msg;
+
+ /* Duplicate the string */
+ cur->line = malloc(linelength + 1);
+ if (!cur->line)
+ goto fail;
+
+ strncpy(cur->line, msg, linelength);
+ cur->line[linelength] = '\0';
+
+ if (prev)
+ prev->next_line = cur;
+ prev = cur;
+ }
+
+ return ml;
+
+fail:
+
+ if (ml)
+ mlstring_free(ml);
+
+ return NULL;
+}
+
+
+/*
+ * Frees an mlstring.
+ * This function does not have any unit tests.
+ */
+void
+mlstring_free(mlstring *str) {
+ struct mlstr_line *cur, *next;
+
+ for (cur = str->lines; cur; cur = next) {
+ next = cur->next_line;
+ free(cur->line);
+ free(cur);
+ }
+
+ free(str);
+}
+
+
+void
+mlstring_wrap(mlstring *mstring, XFontStruct *font, Dimension width)
+{
+ short char_width = font->max_bounds.width;
+ int line_length, wrap_at;
+ struct mlstr_line *mstr, *newml;
+
+ /* An alternative implementation of this function would be to keep trying
+ * XTextWidth() on space-delimited substrings until the longest one less
+ * than 'width' is found, however there shouldn't be much difference
+ * between that, and this implementation.
+ */
+
+ for (mstr = mstring->lines; mstr; mstr = mstr->next_line)
+ {
+ if (XTextWidth(font, mstr->line, strlen(mstr->line)) > width)
+ {
+ /* Wrap it */
+ line_length = width / char_width;
+ if (line_length == 0)
+ line_length = 1;
+
+ /* First try to soft wrap by finding a space */
+ for (wrap_at = line_length; wrap_at >= 0 && !isspace(mstr->line[wrap_at]); --wrap_at);
+
+ if (wrap_at == -1) /* No space found, hard wrap */
+ wrap_at = line_length;
+ else
+ wrap_at++; /* Leave the space at the end of the line. */
+
+ newml = calloc(1, sizeof(*newml));
+ if (!newml) /* OOM, don't bother trying to wrap */
+ break;
+
+ if (NULL == (newml->line = strdup(mstr->line + wrap_at)))
+ {
+ /* OOM, jump ship */
+ free(newml);
+ break;
+ }
+
+ /* Terminate the existing string at its end */
+ mstr->line[wrap_at] = '\0';
+
+ newml->next_line = mstr->next_line;
+ mstr->next_line = newml;
+ }
+ }
+
+ mlstring_calculate(mstring, font);
+}
+
+#undef MAX
+#define MAX(x, y) ((x) > (y) ? (x) : (y))
+
+/*
+ * Calculates the overall extents (width + height of the multi-line string).
+ * This function is called as part of mlstring_new().
+ * It does not have any unit testing.
+ */
+void
+mlstring_calculate(mlstring *str, XFontStruct *font) {
+ struct mlstr_line *line;
+
+ str->font_height = font->ascent + font->descent;
+ str->overall_height = 0;
+ str->overall_width = 0;
+
+ /* XXX: Should there be some baseline calculations to help XDrawString later on? */
+ str->font_ascent = font->ascent;
+
+ for (line = str->lines; line; line = line->next_line)
+ {
+ line->line_width = XTextWidth(font, line->line, strlen(line->line));
+ str->overall_width = MAX(str->overall_width, line->line_width);
+ /* Don't add line spacing for the first line */
+ str->overall_height += (font->ascent + font->descent) *
+ (line == str->lines ? 1 : LINE_SPACING);
+ }
+}
+
+void
+mlstring_draw(Display *dpy, Drawable dialog, GC gc, mlstring *string, int x, int y) {
+ struct mlstr_line *line;
+
+ if (!string)
+ return;
+
+ y += string->font_ascent;
+
+ XSetFont(dpy, gc, string->font_id);
+
+ for (line = string->lines; line; line = line->next_line)
+ {
+ XDrawString(dpy, dialog, gc, x, y, line->line, strlen(line->line));
+ y += string->font_height * LINE_SPACING;
+ }
+}
+
+/* vim:ts=8:sw=2:noet
+ */
diff --git a/driver/mlstring.h b/driver/mlstring.h
new file mode 100644
index 0000000..ce36205
--- /dev/null
+++ b/driver/mlstring.h
@@ -0,0 +1,57 @@
+/* mlstring.h --- Multi-line strings for use with Xlib
+ *
+ * (c) 2007, Quest Software, Inc. All rights reserved.
+ *
+ * This file is part of XScreenSaver,
+ * Copyright (c) 1993-2004 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 MLSTRING_H
+#define MLSTRING_H
+
+#include <X11/Intrinsic.h>
+
+/* mlstring means multi-line string */
+
+struct mlstr_line;
+
+typedef struct mlstring mlstring;
+struct mlstring {
+ struct mlstr_line *lines; /* linked list */
+ Dimension overall_height;
+ Dimension overall_width;
+ /* XXX: Perhaps it is simpler to keep a reference to the XFontStruct */
+ int font_ascent;
+ int font_height;
+ Font font_id;
+};
+
+struct mlstr_line {
+ char *line;
+ Dimension line_width;
+ struct mlstr_line *next_line;
+};
+
+mlstring *
+mlstring_new(const char *str, XFontStruct *font, Dimension wrap_width);
+
+/* Does not have to be called manually */
+void
+mlstring_wrap(mlstring *mstr, XFontStruct *font, Dimension width);
+
+void
+mlstring_free(mlstring *str);
+
+void
+mlstring_draw(Display *dpy, Drawable dialog, GC gc, mlstring *string, int x, int y);
+
+#endif
+/* vim:ts=8:sw=2:noet
+ */
diff --git a/driver/passwd-helper.c b/driver/passwd-helper.c
new file mode 100644
index 0000000..a3a6b92
--- /dev/null
+++ b/driver/passwd-helper.c
@@ -0,0 +1,162 @@
+/* passwd-helper.c --- verifying typed passwords with external helper program
+ * 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
+ * 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.
+ */
+
+/* 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
+ * name argument.
+ *
+ * The external helper does whatever authentication is necessary. Currently,
+ * SuSE uses "unix2_chkpwd", which is a variation of "unix_chkpwd" from the
+ * PAM distribution.
+ *
+ * Normally, the password helper should just authenticate the calling user
+ * (i.e. based on the caller's real uid). This is in order to prevent
+ * brute-forcing passwords in a shadow environment. A less restrictive
+ * approach would be to allow verifying other passwords as well, but always
+ * with a 2 second delay or so. (Not sure what SuSE's "unix2_chkpwd"
+ * currently does.)
+ * -- Olaf Kirch <okir@suse.de>, 16-Dec-2003
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#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>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <errno.h>
+
+#include <sys/wait.h>
+
+static int
+ext_run (const char *user, const char *typed_passwd, int verbose_p)
+{
+ int pfd[2], status;
+ pid_t pid;
+
+ if (pipe(pfd) < 0)
+ return 0;
+
+ if (verbose_p)
+ fprintf (stderr, "%s: ext_run (%s, %s)\n",
+ blurb(), PASSWD_HELPER_PROGRAM, user);
+
+ block_sigchld();
+
+ if ((pid = fork()) < 0) {
+ close(pfd[0]);
+ close(pfd[1]);
+ return 0;
+ }
+
+ if (pid == 0) {
+ close(pfd[1]);
+ if (pfd[0] != 0)
+ dup2(pfd[0], 0);
+
+ /* 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,
+ strerror(errno));
+ exit(1);
+ }
+
+ close(pfd[0]);
+
+ /* Write out password to helper process */
+ if (!typed_passwd)
+ typed_passwd = "";
+ write(pfd[1], typed_passwd, strlen(typed_passwd));
+ close(pfd[1]);
+
+ while (waitpid(pid, &status, 0) < 0) {
+ if (errno == EINTR)
+ continue;
+ if (verbose_p)
+ fprintf(stderr, "%s: ext_run: waitpid failed: %s\n",
+ blurb(), strerror(errno));
+ unblock_sigchld();
+ return 0;
+ }
+
+ unblock_sigchld();
+
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
+ return 0;
+ return 1;
+}
+
+
+
+/* This can be called at any time, and says whether the typed password
+ belongs to either the logged in user (real uid, not effective); or
+ to root.
+ */
+int
+ext_passwd_valid_p (const char *typed_passwd, int verbose_p)
+{
+ struct passwd *pw;
+ int res = 0;
+
+ if ((pw = getpwuid(getuid())) != NULL)
+ res = ext_run (pw->pw_name, typed_passwd, verbose_p);
+ endpwent();
+
+#ifdef ALLOW_ROOT_PASSWD
+ if (!res)
+ res = ext_run ("root", typed_passwd, verbose_p);
+#endif /* ALLOW_ROOT_PASSWD */
+
+ return res;
+}
+
+
+int
+ext_priv_init (int argc, char **argv, int verbose_p)
+{
+ /* 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",
+ blurb(), PASSWD_HELPER_PROGRAM, blurb());
+ return 0;
+ }
+ return 1;
+}
+
+#endif /* NO_LOCKING -- whole file */
diff --git a/driver/passwd-kerberos.c b/driver/passwd-kerberos.c
new file mode 100644
index 0000000..202e0eb
--- /dev/null
+++ b/driver/passwd-kerberos.c
@@ -0,0 +1,251 @@
+/* kpasswd.c --- verify kerberos passwords.
+ * written by Nat Lanza (magus@cs.cmu.edu) for
+ * xscreensaver, Copyright (c) 1993-2004 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
+
+#ifndef NO_LOCKING /* whole file */
+
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+/* I'm not sure if this is exactly the right test...
+ Might __APPLE__ be defined if this is apple hardware, but not
+ an Apple OS?
+
+ Thanks to Alexei Kosut <akosut@stanford.edu> for the MacOS X code.
+ */
+#ifdef __APPLE__
+# define HAVE_DARWIN
+#endif
+
+
+#if defined(HAVE_DARWIN)
+# include <Kerberos/Kerberos.h>
+#elif defined(HAVE_KERBEROS5)
+# include <kerberosIV/krb.h>
+# include <kerberosIV/des.h>
+#else /* !HAVE_KERBEROS5 (meaning Kerberos 4) */
+# include <krb.h>
+# include <des.h>
+#endif /* !HAVE_KERBEROS5 */
+
+#if !defined(VMS) && !defined(HAVE_ADJUNCT_PASSWD)
+# include <pwd.h>
+#endif
+
+
+#ifdef __bsdi__
+# include <sys/param.h>
+# if _BSDI_VERSION >= 199608
+# define BSD_AUTH
+# endif
+#endif /* __bsdi__ */
+
+/* blargh */
+#undef Bool
+#undef True
+#undef False
+#define Bool int
+#define True 1
+#define False 0
+
+/* The user information we need to store */
+#ifdef HAVE_DARWIN
+ static KLPrincipal princ;
+#else /* !HAVE_DARWIN */
+ static char realm[REALM_SZ];
+ static char name[ANAME_SZ];
+ static char inst[INST_SZ];
+ 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
+ using tf_get_pname(), this should work even if your kerberos username
+ isn't the same as your local username. We grab the ticket at startup
+ time so that even if your ticketfile dies while the screen's locked
+ we'll still have the information to unlock it.
+
+ Problems: the password dialog currently displays local username, so if
+ you have some non-standard name/instance when you run xscreensaver,
+ you'll need to remember what it was when unlocking, or else you lose.
+
+ Also, we use des_string_to_key(), so if you have an AFS password
+ (encrypted with ka_StringToKey()), you'll lose. Get a kerberos password;
+ it isn't that hard.
+
+ Like the original lock_init, we return false if something went wrong.
+ We don't use the arguments we're given, though.
+ */
+Bool
+kerberos_lock_init (int argc, char **argv, Bool verbose_p)
+{
+# ifdef HAVE_DARWIN
+
+ KLBoolean found;
+ return ((klNoErr == (KLCacheHasValidTickets (NULL, kerberosVersion_Any,
+ &found, &princ, NULL)))
+ && found);
+
+# else /* !HAVE_DARWIN */
+
+ /* Perhaps we should be doing it the Mac way (above) all the time?
+ The following code assumes Unix-style file-based Kerberos credentials
+ cache, which Mac OS X doesn't use. But is there any real reason to
+ do it this way at all, even on other Unixen?
+ */
+ int k_errno;
+
+ memset(name, 0, sizeof(name));
+ memset(inst, 0, sizeof(inst));
+
+ /* find out where the user's keeping his tickets.
+ squirrel it away for later use. */
+ tk_file = tkt_string();
+
+ /* open ticket file or die trying. */
+ if ((k_errno = tf_init(tk_file, R_TKT_FIL))) {
+ return False;
+ }
+
+ /* same with principal and instance names */
+ if ((k_errno = tf_get_pname(name)) ||
+ (k_errno = tf_get_pinst(inst))) {
+ return False;
+ }
+
+ /* close the ticketfile to release the lock on it. */
+ tf_close();
+
+ /* figure out what realm we're authenticated to. this ought
+ to be the local realm, but it pays to be sure. */
+ if ((k_errno = krb_get_tf_realm(tk_file, realm))) {
+ return False;
+ }
+
+ /* last-minute sanity check on what we got. */
+ if ((strlen(name)+strlen(inst)+strlen(realm)+3) >
+ (REALM_SZ + ANAME_SZ + INST_SZ + 3)) {
+ return False;
+ }
+
+ /* success */
+ return True;
+
+# endif /* !HAVE_DARWIN */
+}
+
+
+/* des_string_to_key() wants this. If C didn't suck, we could have an
+ anonymous function do this. Even a local one. But it does, so here
+ we are. Calling it ive_got_your_local_function_right_here_buddy()
+ would have been rude.
+ */
+#ifndef HAVE_DARWIN
+static int
+key_to_key(char *user, char *instance, char *realm, char *passwd, C_Block key)
+{
+ memcpy(key, passwd, sizeof(des_cblock));
+ return (0);
+}
+#endif /* !HAVE_DARWIN */
+
+/* Called to see if the user's typed password is valid. We do this by asking
+ the kerberos server for a ticket and checking to see if it gave us one.
+ We need to move the ticketfile first, or otherwise we end up updating the
+ user's tkfile with new tickets. This would break services like zephyr that
+ like to stay authenticated, and it would screw with AFS authentication at
+ some sites. So, we do a quick, painful hack with a tmpfile.
+ */
+Bool
+kerberos_passwd_valid_p (const char *typed_passwd, Bool verbose_p)
+{
+# ifdef HAVE_DARWIN
+ return (klNoErr ==
+ KLAcquireNewInitialTicketsWithPassword (princ, NULL,
+ typed_passwd, NULL));
+# else /* !HAVE_DARWIN */
+
+ /* See comments in kerberos_lock_init -- should we do it the Mac Way
+ on all systems?
+ */
+ C_Block mitkey;
+ Bool success;
+ char *newtkfile;
+ int fh = -1;
+
+ /* 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());
+
+ if( (fh = mkstemp(newtkfile)) < 0)
+ {
+ free(newtkfile);
+ return(False);
+ }
+ if( fchmod(fh, 0600) < 0)
+ {
+ free(newtkfile);
+ return(False);
+ }
+
+
+ krb_set_tkt_string(newtkfile);
+
+ /* encrypt the typed password. if you have an AFS password instead
+ of a kerberos one, you lose *right here*. If you want to use AFS
+ passwords, you can use ka_StringToKey() instead. As always, ymmv. */
+ des_string_to_key(typed_passwd, mitkey);
+
+ if (krb_get_in_tkt(name, inst, realm, "krbtgt", realm, DEFAULT_TKT_LIFE,
+ key_to_key, NULL, (char *) mitkey) != 0) {
+ success = False;
+ } else {
+ success = True;
+ }
+
+ /* quickly block out the tempfile and password to prevent snooping,
+ then restore the old ticketfile and cleean up a bit. */
+
+ dest_tkt();
+ krb_set_tkt_string(tk_file);
+ free(newtkfile);
+ memset(mitkey, 0, sizeof(mitkey));
+ close(fh); /* #### tom: should the file be removed? */
+
+
+ /* Did we verify successfully? */
+ return success;
+
+# endif /* !HAVE_DARWIN */
+}
+
+#endif /* NO_LOCKING -- whole file */
diff --git a/driver/passwd-pam.c b/driver/passwd-pam.c
new file mode 100644
index 0000000..d463bc2
--- /dev/null
+++ b/driver/passwd-pam.c
@@ -0,0 +1,526 @@
+/* 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>
+ *
+ * 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 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
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifndef NO_LOCKING /* whole file */
+
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+extern char *blurb(void);
+
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.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 "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!
+ */
+#if !defined(PAM_REFRESH_CRED) && defined(PAM_CRED_REFRESH)
+# define PAM_REFRESH_CRED PAM_CRED_REFRESH
+#endif
+#if !defined(PAM_REINITIALIZE_CRED) && defined(PAM_CRED_REINITIALIZE)
+# define PAM_REINITIALIZE_CRED PAM_CRED_REINITIALIZE
+#endif
+
+static int pam_conversation (int nmsgs,
+ const struct pam_message **msg,
+ 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));
+
+Bool pam_priv_init (int argc, char **argv, Bool verbose_p);
+
+#ifdef HAVE_PAM_FAIL_DELAY
+ /* We handle delays ourself.*/
+ /* Don't set this to 0 (Linux bug workaround.) */
+# define PAM_NO_DELAY(pamh) pam_fail_delay ((pamh), 1)
+#else /* !HAVE_PAM_FAIL_DELAY */
+# define PAM_NO_DELAY(pamh) /* */
+#endif /* !HAVE_PAM_FAIL_DELAY */
+
+
+/* On SunOS 5.6, and on Linux with PAM 0.64, pam_strerror() takes two args.
+ On some other Linux systems with some other version of PAM (e.g.,
+ whichever Debian release comes with a 2.2.5 kernel) it takes one arg.
+ I can't tell which is more "recent" or "correct" behavior, so configure
+ figures out which is in use for us. Shoot me!
+ */
+#ifdef PAM_STRERROR_TWO_ARGS
+# define PAM_STRERROR(pamh, status) pam_strerror((pamh), (status))
+#else /* !PAM_STRERROR_TWO_ARGS */
+# define PAM_STRERROR(pamh, status) pam_strerror((status))
+#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!
+
+ (I've been told this is bug 4092227, and is fixed in Solaris 7.)
+ (I've also been told that it's fixed in Solaris 2.6 by patch 106257-05.)
+ */
+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.
+ */
+void
+pam_try_unlock(saver_info *si, Bool verbose_p,
+ Bool (*valid_p)(const char *typed_passwd, Bool verbose_p))
+{
+ 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 */
+
+ 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;
+
+
+ /* Initialize PAM.
+ */
+ status = pam_start (service, si->user, &pc, &pamh);
+ if (verbose_p)
+ fprintf (stderr, "%s: pam_start (\"%s\", \"%s\", ...) ==> %d (%s)\n",
+ blurb(), service, si->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");
+ 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! */
+ {
+ int status2;
+
+ /* On most systems, it doesn't matter whether the account modules
+ are run, or whether they fail or succeed.
+
+ On some systems, the account modules fail, because they were
+ never configured properly, but it's necessary to run them anyway
+ because certain PAM modules depend on side effects of the account
+ modules having been run.
+
+ And on still other systems, the account modules are actually
+ used, and failures in them should be considered to be true!
+
+ So:
+ - We run the account modules on all systems.
+ - Whether we ignore them is a configure option.
+
+ It's all kind of a mess.
+ */
+ status2 = pam_acct_mgmt (pamh, 0);
+
+ if (verbose_p)
+ fprintf (stderr, "%s: pam_acct_mgmt (...) ==> %d (%s)\n",
+ blurb(), status2, PAM_STRERROR(pamh, status2));
+
+ /* HPUX for some reason likes to make PAM defines different from
+ * everyone else's. */
+#ifdef PAM_AUTHTOKEN_REQD
+ if (status2 == PAM_AUTHTOKEN_REQD)
+#else
+ if (status2 == PAM_NEW_AUTHTOK_REQD)
+#endif
+ {
+ status2 = pam_chauthtok (pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
+ if (verbose_p)
+ fprintf (stderr, "%s: pam_chauthtok (...) ==> %d (%s)\n",
+ blurb(), status2, PAM_STRERROR(pamh, status2));
+ }
+
+ /* If 'configure' requested that we believe the results of PAM
+ account module failures, then obey that status code.
+ Otherwise ignore it.
+ */
+#ifdef PAM_CHECK_ACCOUNT_TYPE
+ status = status2;
+#endif
+
+ /* Each time we successfully authenticate, refresh credentials,
+ for Kerberos/AFS/DCE/etc. If this fails, just ignore that
+ failure and blunder along; it shouldn't matter.
+ */
+
+#if defined(__linux__)
+ /* Apparently the Linux PAM library ignores PAM_REFRESH_CRED and only
+ refreshes credentials when using PAM_REINITIALIZE_CRED. */
+ status2 = pam_setcred (pamh, PAM_REINITIALIZE_CRED);
+#else
+ /* But Solaris requires PAM_REFRESH_CRED or extra prompts appear. */
+ status2 = pam_setcred (pamh, PAM_REFRESH_CRED);
+#endif
+
+ if (verbose_p)
+ fprintf (stderr, "%s: pam_setcred (...) ==> %d (%s)\n",
+ blurb(), status2, PAM_STRERROR(pamh, status2));
+ }
+
+ DONE:
+ if (pamh)
+ {
+ int status2 = pam_end (pamh, status);
+ pamh = 0;
+ if (verbose_p)
+ fprintf (stderr, "%s: pam_end (...) ==> %d (%s)\n",
+ blurb(), status2,
+ (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 */
+}
+
+
+Bool
+pam_priv_init (int argc, char **argv, Bool verbose_p)
+{
+ /* 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"
+ 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
+ is unreadable or something (don't know if that actually happens.)
+ */
+ const char dir[] = "/etc/pam.d";
+ const char file[] = "/etc/pam.d/" PAM_SERVICE_NAME;
+ const char file2[] = "/etc/pam.conf";
+ struct stat st;
+
+# ifndef S_ISDIR
+# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
+# endif
+
+ if (stat (dir, &st) == 0 && S_ISDIR(st.st_mode))
+ {
+ if (stat (file, &st) != 0)
+ fprintf (stderr,
+ "%s: warning: %s does not exist.\n"
+ "%s: password authentication via PAM is unlikely to work.\n",
+ blurb(), file, blurb());
+ }
+ else if (stat (file2, &st) == 0)
+ {
+ FILE *f = fopen (file2, "r");
+ if (f)
+ {
+ Bool ok = False;
+ char buf[255];
+ while (fgets (buf, sizeof(buf), f))
+ if (strstr (buf, PAM_SERVICE_NAME))
+ {
+ ok = True;
+ break;
+ }
+ fclose (f);
+ if (!ok)
+ {
+ fprintf (stderr,
+ "%s: warning: %s does not list the `%s' service.\n"
+ "%s: password authentication via PAM is unlikely to work.\n",
+ blurb(), file2, PAM_SERVICE_NAME, blurb());
+ }
+ }
+ /* else warn about file2 existing but being unreadable? */
+ }
+ else
+ {
+ fprintf (stderr,
+ "%s: warning: neither %s nor %s exist.\n"
+ "%s: password authentication via PAM is unlikely to work.\n",
+ blurb(), file2, file, blurb());
+ }
+
+ /* Return true anyway, just in case. */
+ return True;
+}
+
+
+static int
+pam_conversation (int nmsgs,
+ const struct pam_message **msg,
+ struct pam_response **resp,
+ void *vsaver_info)
+{
+ int i, ret = -1;
+ struct auth_message *messages = 0;
+ struct auth_response *authresp = 0;
+ struct pam_response *pam_responses;
+ saver_info *si = (saver_info *) vsaver_info;
+ Bool verbose_p;
+
+ /* 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;
+
+ /* Converting the PAM prompts into the XScreenSaver native format.
+ * It was a design goal to collapse (INFO,PROMPT) pairs from PAM
+ * into a single call to the unlock_cb function. The unlock_cb function
+ * does that, but only if it is passed several prompts at a time. Most PAM
+ * modules only send a single prompt at a time, but because there is no way
+ * of telling whether there will be more prompts to follow, we can only ever
+ * 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;
+
+ if (verbose_p)
+ fprintf (stderr, "%s: pam_conversation (", blurb());
+
+ for (i = 0; i < nmsgs; ++i)
+ {
+ if (verbose_p && i > 0) fprintf (stderr, ", ");
+
+ messages[i].msg = msg[i]->msg;
+
+ switch (msg[i]->msg_style) {
+ case PAM_PROMPT_ECHO_OFF: messages[i].type = AUTH_MSGTYPE_PROMPT_NOECHO;
+ if (verbose_p) fprintf (stderr, "ECHO_OFF");
+ break;
+ case PAM_PROMPT_ECHO_ON: messages[i].type = AUTH_MSGTYPE_PROMPT_ECHO;
+ if (verbose_p) fprintf (stderr, "ECHO_ON");
+ break;
+ case PAM_ERROR_MSG: messages[i].type = AUTH_MSGTYPE_ERROR;
+ if (verbose_p) fprintf (stderr, "ERROR_MSG");
+ break;
+ case PAM_TEXT_INFO: messages[i].type = AUTH_MSGTYPE_INFO;
+ if (verbose_p) fprintf (stderr, "TEXT_INFO");
+ break;
+ default: messages[i].type = AUTH_MSGTYPE_PROMPT_ECHO;
+ if (verbose_p) fprintf (stderr, "PROMPT_ECHO");
+ break;
+ }
+
+ if (verbose_p)
+ fprintf (stderr, "=\"%s\"", msg[i]->msg ? msg[i]->msg : "(null)");
+ }
+
+ 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;
+ }
+
+end:
+ 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);
+
+ return PAM_CONV_ERR;
+}
+
+#endif /* NO_LOCKING -- whole file */
diff --git a/driver/passwd-pwent.c b/driver/passwd-pwent.c
new file mode 100644
index 0000000..bb0edfc
--- /dev/null
+++ b/driver/passwd-pwent.c
@@ -0,0 +1,312 @@
+/* passwd-pwent.c --- verifying typed passwords with the OS.
+ * xscreensaver, Copyright (c) 1993-1998 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
+
+#ifndef NO_LOCKING /* whole file */
+
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#ifdef HAVE_CRYPT_H
+# include <crypt.h>
+#endif
+
+#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 */
+
+
+#ifdef __bsdi__
+# include <sys/param.h>
+# if _BSDI_VERSION >= 199608
+# define BSD_AUTH
+# endif
+#endif /* __bsdi__ */
+
+
+#if defined(HAVE_SHADOW_PASSWD) /* passwds live in /etc/shadow */
+
+# include <shadow.h>
+# define PWTYPE struct spwd *
+# define PWPSLOT sp_pwdp
+# define GETPW getspnam
+
+#elif defined(HAVE_ENHANCED_PASSWD) /* passwds live in /tcb/files/auth/ */
+ /* M.Matsumoto <matsu@yao.sharp.co.jp> */
+# include <sys/security.h>
+# include <prot.h>
+
+# define PWTYPE struct pr_passwd *
+# define PWPSLOT ufld.fd_encrypt
+# define GETPW getprpwnam
+
+#elif defined(HAVE_ADJUNCT_PASSWD)
+
+# include <sys/label.h>
+# include <sys/audit.h>
+# include <pwdadj.h>
+
+# define PWTYPE struct passwd_adjunct *
+# define PWPSLOT pwa_passwd
+# define GETPW getpwanam
+
+#elif defined(HAVE_HPUX_PASSWD)
+
+# include <hpsecurity.h>
+# include <prot.h>
+
+# define PWTYPE struct s_passwd *
+# define PWPSLOT pw_passwd
+# define GETPW getspwnam
+
+# define HAVE_BIGCRYPT
+
+#endif
+
+
+/* blargh */
+#undef Bool
+#undef True
+#undef False
+#define Bool int
+#define True 1
+#define False 0
+
+
+extern const char *blurb(void);
+
+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
+
+
+#ifndef VMS
+
+static char *
+user_name (void)
+{
+ /* I think that just checking $USER here is not the best idea. */
+
+ const char *u = 0;
+
+ /* It has been reported that getlogin() returns the wrong user id on some
+ very old SGI systems... And I've seen it return the string "rlogin"
+ sometimes! Screw it, using getpwuid() should be enough...
+ */
+/* u = (char *) getlogin ();
+ */
+
+ /* getlogin() fails if not attached to a terminal; in that case, use
+ getpwuid(). (Note that in this case, we're not doing shadow stuff, since
+ all we're interested in is the name, not the password. So that should
+ still work. Right?) */
+ if (!u || !*u)
+ {
+ struct passwd *p = getpwuid (getuid ());
+ u = (p ? p->pw_name : 0);
+ }
+
+ 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)
+{
+ return (pw &&
+ pw[0] != '*' && /* This would be sensible... */
+ strlen(pw) > 4); /* ...but this is what Solaris does. */
+}
+
+
+static char *
+get_encrypted_passwd(const char *user)
+{
+ char *result = 0;
+
+#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);
+ }
+#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);
+ }
+
+ /* The manual for passwd(4) on HPUX 10.10 says:
+
+ Password aging is put in effect for a particular user if his
+ encrypted password in the password file is followed by a comma and
+ a nonnull string of characters from the above alphabet. This
+ string defines the "age" needed to implement password aging.
+
+ So this means that passwd->pw_passwd isn't simply a string of cyphertext,
+ it might have trailing junk. So, if there is a comma in the string, and
+ that comma is beyond position 13, terminate the string before the comma.
+ */
+ if (result && strlen(result) > 13)
+ {
+ char *s = strchr (result+13, ',');
+ if (s)
+ *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. */
+
+ if (!result)
+ fprintf (stderr, "%s: couldn't get password of \"%s\"\n",
+ blurb(), (user ? user : "(null)"));
+#endif /* !HAVE_PAM */
+
+ return result;
+}
+
+
+
+/* This has to be called before we've changed our effective user ID,
+ because it might need privileges to get at the encrypted passwords.
+ Returns false if we weren't able to get any passwords, and therefore,
+ locking isn't possible. (It will also have written to stderr.)
+ */
+
+#ifndef VMS
+
+Bool
+pwent_priv_init (int argc, char **argv, Bool verbose_p)
+{
+ char *u;
+
+#ifdef HAVE_ENHANCED_PASSWD
+ set_auth_parameters(argc, argv);
+ check_auth_parameters();
+#endif /* HAVE_DEC_ENHANCED */
+
+ u = user_name();
+ encrypted_user_passwd = get_encrypted_passwd(u);
+ encrypted_root_passwd = get_encrypted_passwd(ROOT);
+ if (u) free (u);
+
+ if (encrypted_user_passwd)
+ return True;
+ else
+ return False;
+}
+
+
+Bool
+pwent_lock_init (int argc, char **argv, Bool verbose_p)
+{
+ if (encrypted_user_passwd)
+ return True;
+ else
+ return False;
+}
+
+
+
+static Bool
+passwds_match_p (const char *cleartext, const char *ciphertext)
+{
+ char *s = 0; /* note that on some systems, crypt() may return null */
+
+ s = (char *) crypt (cleartext, ciphertext);
+ if (s && !strcmp (s, ciphertext))
+ return True;
+
+#ifdef HAVE_BIGCRYPT
+ /* There seems to be no way to tell at runtime if an HP machine is in
+ "trusted" mode, and thereby, which of crypt() or bigcrypt() we should
+ be calling to compare passwords. So call them both, and see which
+ one works. */
+
+ s = (char *) bigcrypt (cleartext, ciphertext);
+ if (s && !strcmp (s, ciphertext))
+ return True;
+
+#endif /* HAVE_BIGCRYPT */
+
+ return False;
+}
+
+
+
+/* This can be called at any time, and says whether the typed password
+ belongs to either the logged in user (real uid, not effective); or
+ to root.
+ */
+Bool
+pwent_passwd_valid_p (const char *typed_passwd, Bool verbose_p)
+{
+ if (encrypted_user_passwd &&
+ passwds_match_p (typed_passwd, encrypted_user_passwd))
+ return True;
+
+#ifdef ALLOW_ROOT_PASSWD
+ /* do not allow root to have a null password. */
+ else if (typed_passwd[0] &&
+ encrypted_root_passwd &&
+ passwds_match_p (typed_passwd, encrypted_root_passwd))
+ return True;
+#endif /* ALLOW_ROOT_PASSWD */
+
+ else
+ return False;
+}
+
+#else /* VMS */
+Bool pwent_lock_init (int argc, char **argv, Bool verbose_p) { return True; }
+#endif /* VMS */
+
+#endif /* NO_LOCKING -- whole file */
diff --git a/driver/passwd.c b/driver/passwd.c
new file mode 100644
index 0000000..ac5a3f0
--- /dev/null
+++ b/driver/passwd.c
@@ -0,0 +1,339 @@
+/* passwd.c --- verifying typed passwords with the OS.
+ * xscreensaver, Copyright (c) 1993-2014 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
+
+#ifndef NO_LOCKING /* whole file */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+#include <time.h>
+#include <sys/time.h>
+
+#ifndef VMS
+# include <pwd.h> /* for getpwuid() */
+#else /* VMS */
+# include "vms-pwd.h"
+#endif /* VMS */
+
+#ifdef HAVE_SYSLOG
+# include <syslog.h>
+#endif /* HAVE_SYSLOG */
+
+#include <X11/Intrinsic.h>
+
+#include "xscreensaver.h"
+#include "auth.h"
+
+extern const char *blurb(void);
+extern void check_for_leaks (const char *where);
+
+
+/* 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)))
+
+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 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.)
+ */
+struct auth_methods methods[] = {
+# ifdef HAVE_PAM
+ { "PAM", 0, pam_priv_init, 0, pam_try_unlock,
+ False, False },
+# endif
+# ifdef HAVE_KERBEROS
+ { "Kerberos", kerberos_lock_init, 0, kerberos_passwd_valid_p, 0,
+ False, False },
+# endif
+# ifdef PASSWD_HELPER_PROGRAM
+ { "external", 0, ext_priv_init, ext_passwd_valid_p, 0,
+ False, False },
+# endif
+ { "normal", pwent_lock_init, pwent_priv_init, pwent_passwd_valid_p, 0,
+ False, False }
+};
+
+
+Bool
+lock_priv_init (int argc, char **argv, Bool verbose_p)
+{
+ int i;
+ Bool any_ok = False;
+ for (i = 0; i < countof(methods); i++)
+ {
+ 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);
+
+ 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);
+ }
+ return any_ok;
+}
+
+
+Bool
+lock_init (int argc, char **argv, Bool verbose_p)
+{
+ int i;
+ Bool any_ok = False;
+ for (i = 0; i < countof(methods); i++)
+ {
+ if (!methods[i].priv_initted_p) /* Bail if lock_priv_init failed. */
+ continue;
+
+ if (!methods[i].init)
+ methods[i].initted_p = True;
+ else
+ methods[i].initted_p = methods[i].init (argc, argv, verbose_p);
+
+ if (methods[i].initted_p)
+ any_ok = True;
+ else if (verbose_p)
+ fprintf (stderr, "%s: initialization of %s passwords 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.
+ */
+static void
+try_unlock_password(saver_info *si,
+ Bool verbose_p,
+ Bool (*valid_p)(const char *typed_passwd, Bool verbose_p))
+{
+ struct auth_message message;
+ struct auth_response *response = NULL;
+
+ memset(&message, 0, sizeof(message));
+
+ if (verbose_p)
+ fprintf(stderr, "%s: non-PAM password auth.\n", blurb());
+
+ /* 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);
+
+ if (!response)
+ return;
+
+ 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->response)
+ free(response->response);
+ free(response);
+}
+
+
+/* Write a password failure to the system log.
+ */
+static void
+do_syslog (saver_info *si, Bool verbose_p)
+{
+# ifdef HAVE_SYSLOG
+ struct passwd *pw = getpwuid (getuid ());
+ char *d = DisplayString (si->dpy);
+ char *u = (pw && pw->pw_name ? pw->pw_name : "???");
+ int opt = 0;
+ int fac = 0;
+
+# ifdef LOG_PID
+ opt = LOG_PID;
+# endif
+
+# if defined(LOG_AUTHPRIV)
+ fac = LOG_AUTHPRIV;
+# elif defined(LOG_AUTH)
+ fac = LOG_AUTH;
+# else
+ fac = LOG_DAEMON;
+# endif
+
+ 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);
+ 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().
+ */
+void
+xss_authenticate(saver_info *si, Bool verbose_p)
+{
+ int i, j;
+
+ si->unlock_state = ul_read;
+
+ 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);
+ 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)
+ {
+ 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! */
+ fprintf (stderr,
+ "%s: authentication via %s %s.\n",
+ blurb(), methods[i].name,
+ (si->unlock_state == ul_cancel
+ ? "cancelled" : "timed out"));
+ goto DONE;
+ }
+ }
+
+ if (verbose_p)
+ fprintf(stderr, "%s: All authentication mechanisms failed.\n", blurb());
+
+ 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);
+ }
+
+DONE:
+ if (si->auth_finished_cb)
+ si->auth_finished_cb (si);
+}
+
+#endif /* NO_LOCKING -- whole file */
diff --git a/driver/pdf2jpeg.m b/driver/pdf2jpeg.m
new file mode 100644
index 0000000..d681b4a
--- /dev/null
+++ b/driver/pdf2jpeg.m
@@ -0,0 +1,152 @@
+/* pdf2jpeg -- converts a PDF file to a JPEG file, using Cocoa
+ *
+ * Copyright (c) 2003, 2008 by 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.
+ *
+ * Inspired by clues provided by Jan Kujawa and Jonathan Hendry.
+ */
+
+#import <Cocoa/Cocoa.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+int
+main (int argc, char** argv)
+{
+ const char *progname = argv[0];
+ const char *infile = 0, *outfile = 0;
+ double compression = 0.85;
+ double scale = 1.0;
+ int verbose = 0;
+ int i;
+
+ for (i = 1; i < argc; i++)
+ {
+ char c;
+ if (argv[i][0] == '-' && argv[i][1] == '-')
+ argv[i]++;
+ if (!strcmp (argv[i], "-q") ||
+ !strcmp (argv[i], "-qual") ||
+ !strcmp (argv[i], "-quality"))
+ {
+ int q;
+ if (1 != sscanf (argv[++i], " %d %c", &q, &c) ||
+ q < 5 || q > 100)
+ {
+ fprintf (stderr, "%s: quality must be 5 - 100 (%d)\n",
+ progname, q);
+ goto USAGE;
+ }
+ compression = q / 100.0;
+ }
+ else if (!strcmp (argv[i], "-scale"))
+ {
+ float s;
+ if (1 != sscanf (argv[++i], " %f %c", &s, &c) ||
+ s <= 0 || s > 50)
+ {
+ fprintf (stderr, "%s: scale must be 0.0 - 50.0 (%f)\n",
+ progname, s);
+ goto USAGE;
+ }
+ scale = s;
+ }
+ else if (!strcmp (argv[i], "-verbose"))
+ verbose++;
+ else if (!strcmp (argv[i], "-v") ||
+ !strcmp (argv[i], "-vv") ||
+ !strcmp (argv[i], "-vvv"))
+ verbose += strlen(argv[i])-1;
+ else if (argv[i][0] == '-')
+ {
+ fprintf (stderr, "%s: unknown option %s\n", progname, argv[i]);
+ goto USAGE;
+ }
+ else if (!infile)
+ infile = argv[i];
+ else if (!outfile)
+ outfile = argv[i];
+ else
+ {
+ USAGE:
+ fprintf (stderr,
+ "usage: %s [-verbose] [-scale N] [-quality NN] "
+ "infile.pdf outfile.jpg\n",
+ progname);
+ exit (1);
+ }
+ }
+
+ if (!infile || !outfile)
+ goto USAGE;
+
+
+ // Much of Cocoa needs one of these to be available.
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ //Need an NSApp instance to make [NSImage TIFFRepresentation] work
+ NSApp = [NSApplication sharedApplication];
+ [NSApp autorelease];
+
+ if (verbose)
+ fprintf (stderr, "%s: reading %s...\n", progname, infile);
+
+ // Load the PDF file into an NSData object:
+ NSData *pdf_data = [NSData dataWithContentsOfFile:
+ [NSString stringWithCString:infile
+ encoding:NSUTF8StringEncoding]];
+
+ // Create an NSPDFImageRep from the data:
+ NSPDFImageRep *pdf_rep = [NSPDFImageRep imageRepWithData:pdf_data];
+
+ // Create an NSImage instance
+ NSRect rect;
+ rect.size = [pdf_rep size];
+ rect.size.width *= scale;
+ rect.size.height *= scale;
+ rect.origin.x = rect.origin.y = 0;
+ NSImage *image = [[NSImage alloc] initWithSize:rect.size];
+
+ // Draw the PDFImageRep in the NSImage
+ [image lockFocus];
+ [pdf_rep drawInRect:rect];
+ [image unlockFocus];
+
+ // Load the NSImage's contents into an NSBitmapImageRep:
+ NSBitmapImageRep *bit_rep = [NSBitmapImageRep
+ imageRepWithData:[image TIFFRepresentation]];
+
+ // Write the bitmapImageRep to a JPEG file:
+ if (bit_rep == nil)
+ {
+ fprintf (stderr, "%s: error converting image?\n", argv[0]);
+ exit (1);
+ }
+
+ if (verbose)
+ fprintf (stderr, "%s: writing %s (%d%% quality)...\n",
+ progname, outfile, (int) (compression * 100));
+
+ NSDictionary *props = [NSDictionary
+ dictionaryWithObject:
+ [NSNumber numberWithFloat:compression]
+ forKey:NSImageCompressionFactor];
+ NSData *jpeg_data = [bit_rep representationUsingType:NSJPEGFileType
+ properties:props];
+
+ [jpeg_data writeToFile:
+ [NSString stringWithCString:outfile
+ encoding:NSUTF8StringEncoding]
+ atomically:YES];
+ [image release];
+
+ [pool release];
+ exit (0);
+}
diff --git a/driver/pdf2jpeg.man b/driver/pdf2jpeg.man
new file mode 100644
index 0000000..9d80dd7
--- /dev/null
+++ b/driver/pdf2jpeg.man
@@ -0,0 +1,43 @@
+.TH XScreenSaver 1 "07-Sep-2003 (4.13)" "X Version 11"
+.SH NAME
+pdf2jpeg - converts a PDF file to a JPEG file using Cocoa
+.SH SYNOPSIS
+.B pdf2jpeg
+[\--verbose] [\--quality \fINN\fP] infile.pdf outfile.jpg
+.SH DESCRIPTION
+This reads a PDF file (for example, as written by the
+.BR screencapture (1)
+program) and writes a JPEG file.
+.SH OPTIONS
+.I pdf2jpeg
+accepts the following options:
+.TP 4
+.B --verbose
+Print diagnostics.
+.TP 4
+.B --quality \fINN\fP
+JPEG compression factor. Default 85%.
+.SH BUGS
+The input and output files must be files: pipes don't work.
+
+This program is Cocoa-specific, so it won't work on non-MacOS systems.
+
+This shouldn't need to be a part of the XScreenSaver distribution at
+all, but Apple is COMPLETELY INSANE and made
+.BR screencapture (1)
+only write PDFs, with no simple way to convert that to something
+less crazy.
+.SH SEE ALSO
+.BR screencapture (1),
+.BR xscreensaver\-getimage\-desktop (1)
+.SH COPYRIGHT
+Copyright \(co 2003 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>, 20-Oct-03.
diff --git a/driver/prefs.c b/driver/prefs.c
new file mode 100644
index 0000000..8fb029e
--- /dev/null
+++ b/driver/prefs.c
@@ -0,0 +1,1770 @@
+/* dotfile.c --- management of the ~/.xscreensaver file.
+ * xscreensaver, Copyright (c) 1998-2018 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 <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
+#endif
+
+
+#include "version.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 ();
+ struct passwd *p = getpwuid (uid);
+
+ if (i_am_a_nobody (uid))
+ /* 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.)
+ */
+ uid = 0;
+
+ 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 ());
+ file = "";
+ }
+ 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 : "???"));
+ file = "";
+ }
+ else
+ {
+ const char *home = p->pw_dir;
+ 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);
+ }
+ }
+
+ 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)
+{
+ 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;
+}
+
+
+static int
+parse_init_file (saver_preferences *p)
+{
+ 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;
+
+ if (!name) return 0;
+
+ if (stat(name, &st) != 0)
+ {
+ p->init_file_date = 0;
+ return 0;
+ }
+
+ 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;
+ }
+
+ 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);
+ return -1;
+ }
+
+ buf = (char *) malloc(buf_size);
+
+ while (fgets (buf, buf_size-1, in))
+ {
+ char *key, *value;
+ int L = strlen(buf);
+
+ line++;
+ while (L > 2 &&
+ (buf[L-1] != '\n' || /* whole line didn't fit in buffer */
+ buf[L-2] == '\\')) /* or line ended with backslash */
+ {
+ if (buf[L-2] == '\\') /* backslash-newline gets swallowed */
+ {
+ buf[L-2] = 0;
+ L -= 2;
+ }
+ buf_size += 1024;
+ buf = (char *) realloc(buf, buf_size);
+ if (!buf) exit(1);
+
+ line++;
+ if (!fgets (buf + L, buf_size-L-1, in))
+ break;
+ L = strlen(buf);
+ }
+
+ /* Now handle other backslash escapes. */
+ {
+ int i, j;
+ for (i = 0; buf[i]; i++)
+ if (buf[i] == '\\')
+ {
+ switch (buf[i+1])
+ {
+ case 'n': buf[i] = '\n'; break;
+ case 'r': buf[i] = '\r'; break;
+ case 't': buf[i] = '\t'; break;
+ default: buf[i] = buf[i+1]; break;
+ }
+ for (j = i+2; buf[j]; j++)
+ buf[j-1] = buf[j];
+ buf[j-1] = 0;
+ }
+ }
+
+ key = strip(buf);
+
+ if (*key == '#' || *key == '!' || *key == ';' ||
+ *key == '\n' || *key == 0)
+ continue;
+
+ value = strchr (key, ':');
+ if (!value)
+ {
+ fprintf(stderr, "%s: %s:%d: unparsable line: %s\n", blurb(),
+ name, line, key);
+ continue;
+ }
+ else
+ {
+ *value++ = 0;
+ value = strip(value);
+ }
+
+ if (!p->db) abort();
+ handle_entry (&p->db, key, value, name, line);
+ }
+ fclose (in);
+ free(buf);
+
+ p->init_file_date = write_date;
+ 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); /* . */
+}
diff --git a/driver/prefs.h b/driver/prefs.h
new file mode 100644
index 0000000..cd1016d
--- /dev/null
+++ b/driver/prefs.h
@@ -0,0 +1,37 @@
+/* xscreensaver, Copyright (c) 1993-2018 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_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);
+
+#endif /* __XSCREENSAVER_PREFS_H__ */
diff --git a/driver/remote.c b/driver/remote.c
new file mode 100644
index 0000000..775036a
--- /dev/null
+++ b/driver/remote.c
@@ -0,0 +1,595 @@
+/* xscreensaver-command, Copyright (c) 1991-2009 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 <time.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#ifdef HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#endif /* HAVE_SYS_SELECT_H */
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include <X11/Xproto.h> /* for CARD32 */
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/Xutil.h> /* for XGetClassHint() */
+#include <X11/Xos.h>
+
+#include "remote.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;
+
+
+static XErrorHandler old_handler = 0;
+static Bool got_badwindow = False;
+static int
+BadWindow_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);
+ }
+}
+
+
+
+static Window
+find_screensaver_window (Display *dpy, char **version)
+{
+ 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;
+
+ if (got_badwindow)
+ {
+ status = BadWindow;
+ got_badwindow = False;
+ }
+
+ if (status == Success && type != None)
+ {
+ Window ret = kids[i];
+ if (version)
+ *version = (char *) v;
+ XFree (kids);
+ return ret;
+ }
+ }
+
+ if (kids) XFree (kids);
+ return 0;
+}
+
+
+static int
+send_xscreensaver_command (Display *dpy, Atom command, long arg,
+ Window *window_ret, char **error_ret)
+{
+ int status = -1;
+ char *v = 0;
+ Window window = find_screensaver_window (dpy, &v);
+ XWindowAttributes xgwa;
+ char err[2048];
+
+ if (window_ret)
+ *window_ret = window;
+
+ if (!window)
+ {
+ sprintf (err, "no screensaver is running on display %s",
+ DisplayString (dpy));
+
+ if (error_ret)
+ {
+ *error_ret = strdup (err);
+ status = -1;
+ goto DONE;
+ }
+
+ if (command == XA_EXIT)
+ {
+ /* Don't print an error if xscreensaver is already dead. */
+ status = 1;
+ goto DONE;
+ }
+
+ fprintf (stderr, "%s: %s\n", progname, err);
+ status = -1;
+ goto DONE;
+ }
+
+ /* Select for property change events, so that we can read the response. */
+ XGetWindowAttributes (dpy, window, &xgwa);
+ XSelectInput (dpy, window, xgwa.your_event_mask | PropertyChangeMask);
+
+ if (command == XA_SCREENSAVER_STATUS ||
+ command == XA_SCREENSAVER_VERSION)
+ {
+ XClassHint hint;
+ memset (&hint, 0, sizeof(hint));
+ if (!v || !*v)
+ {
+ sprintf (err, "version property not set on window 0x%x?",
+ (unsigned int) window);
+ if (error_ret)
+ *error_ret = strdup (err);
+ else
+ fprintf (stderr, "%s: %s\n", progname, err);
+
+ status = -1;
+ goto DONE;
+ }
+
+ XGetClassHint(dpy, window, &hint);
+ if (!hint.res_class)
+ {
+ sprintf (err, "class hints not set on window 0x%x?",
+ (unsigned int) window);
+ if (error_ret)
+ *error_ret = strdup (err);
+ else
+ fprintf (stderr, "%s: %s\n", progname, err);
+
+ status = -1;
+ goto DONE;
+ }
+
+ fprintf (stdout, "%s %s", hint.res_class, v);
+
+ if (command != XA_SCREENSAVER_STATUS)
+ {
+ fprintf (stdout, "\n");
+ }
+ else
+ {
+ Atom type;
+ int format;
+ unsigned long nitems, bytesafter;
+ unsigned char *dataP = 0;
+
+ if (XGetWindowProperty (dpy,
+ RootWindow (dpy, 0),
+ XA_SCREENSAVER_STATUS,
+ 0, 999, False, XA_INTEGER,
+ &type, &format, &nitems, &bytesafter,
+ &dataP)
+ == Success
+ && type
+ && dataP)
+ {
+ Atom blanked;
+ time_t tt;
+ char *s;
+ Atom *data = (Atom *) dataP;
+
+ if (type != XA_INTEGER || nitems < 3)
+ {
+ STATUS_LOSE:
+ if (data) free (data);
+ fprintf (stdout, "\n");
+ fflush (stdout);
+ fprintf (stderr, "bad status format on root window.\n");
+ status = -1;
+ goto DONE;
+ }
+
+ blanked = (Atom) data[0];
+ tt = (time_t) data[1];
+
+ if (tt <= (time_t) 666000000L) /* early 1991 */
+ goto STATUS_LOSE;
+
+ if (blanked == XA_BLANK)
+ fputs (": screen blanked since ", stdout);
+ else if (blanked == XA_LOCK)
+ fputs (": screen locked since ", stdout);
+ else if (blanked == 0)
+ /* suggestions for a better way to phrase this are welcome. */
+ fputs (": screen non-blanked since ", stdout);
+ else
+ /* `blanked' has an unknown value - fail. */
+ goto STATUS_LOSE;
+
+ s = ctime(&tt);
+ if (s[strlen(s)-1] == '\n')
+ s[strlen(s)-1] = 0;
+ fputs (s, stdout);
+
+ {
+ int nhacks = nitems - 2;
+ Bool any = False;
+ int i;
+ for (i = 0; i < nhacks; i++)
+ if (data[i + 2] > 0)
+ {
+ any = True;
+ break;
+ }
+
+ if (any && nhacks == 1)
+ fprintf (stdout, " (hack #%d)\n", (int) data[2]);
+ else if (any)
+ {
+ fprintf (stdout, " (hacks: ");
+ for (i = 0; i < nhacks; i++)
+ {
+ fprintf (stdout, "#%d", (int) data[2 + i]);
+ if (i != nhacks-1)
+ fputs (", ", stdout);
+ }
+ fputs (")\n", stdout);
+ }
+ else
+ fputs ("\n", stdout);
+ }
+
+ if (data) free (data);
+ }
+ else
+ {
+ if (dataP) XFree (dataP);
+ fprintf (stdout, "\n");
+ fflush (stdout);
+ fprintf (stderr, "no saver status on root window.\n");
+ status = -1;
+ goto DONE;
+ }
+ }
+
+ /* No need to read a response for these commands. */
+ status = 1;
+ goto DONE;
+ }
+ else
+ {
+ XEvent event;
+ long arg1 = arg;
+ long arg2 = 0;
+
+ if (arg < 0)
+ abort();
+ else if (arg == 0 && command == XA_SELECT)
+ abort();
+ else if (arg != 0 && command == XA_DEMO)
+ {
+ arg1 = 5000; /* version number of the XA_DEMO protocol, */
+ arg2 = arg; /* since it didn't use to take an argument. */
+ }
+
+ event.xany.type = ClientMessage;
+ event.xclient.display = dpy;
+ event.xclient.window = window;
+ event.xclient.message_type = XA_SCREENSAVER;
+ event.xclient.format = 32;
+ memset (&event.xclient.data, 0, sizeof(event.xclient.data));
+ 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))
+ {
+ sprintf (err, "XSendEvent(dpy, 0x%x ...) failed.\n",
+ (unsigned int) window);
+ if (error_ret)
+ *error_ret = strdup (err);
+ else
+ fprintf (stderr, "%s: %s\n", progname, err);
+ status = -1;
+ goto DONE;
+ }
+ }
+
+ status = 0;
+
+ DONE:
+ if (v) free (v);
+ XSync (dpy, 0);
+ return status;
+}
+
+
+static Bool
+xscreensaver_command_event_p (Display *dpy, XEvent *event, XPointer arg)
+{
+ return (event->xany.type == PropertyNotify &&
+ event->xproperty.state == PropertyNewValue &&
+ event->xproperty.atom == XA_SCREENSAVER_RESPONSE);
+}
+
+
+static int
+xscreensaver_command_response (Display *dpy, Window window,
+ Bool verbose_p, Bool exiting_p,
+ char **error_ret)
+{
+ int sleep_count = 0;
+ char err[2048];
+ XEvent event;
+ Bool got_event = False;
+
+ while (!(got_event = XCheckIfEvent(dpy, &event,
+ &xscreensaver_command_event_p, 0)) &&
+ sleep_count++ < 10)
+ {
+# if defined(HAVE_SELECT)
+ /* Wait for an event, but don't wait longer than 1 sec. Note that we
+ might do this multiple times if an event comes in, but it wasn't
+ the event we're waiting for.
+ */
+ int fd = XConnectionNumber(dpy);
+ fd_set rset;
+ struct timeval tv;
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ FD_ZERO (&rset);
+ FD_SET (fd, &rset);
+ select (fd+1, &rset, 0, 0, &tv);
+# else /* !HAVE_SELECT */
+ sleep(1);
+# endif /* !HAVE_SELECT */
+ }
+
+ if (!got_event)
+ {
+ sprintf (err, "no response to command.");
+ if (error_ret)
+ *error_ret = strdup (err);
+ else
+ fprintf (stderr, "%s: %s\n", progname, err);
+
+ return -1;
+ }
+ else
+ {
+ Status st2;
+ Atom type;
+ int format;
+ unsigned long nitems, bytesafter;
+ unsigned char *msg = 0;
+
+ XSync (dpy, False);
+ if (old_handler) abort();
+ old_handler = XSetErrorHandler (BadWindow_ehandler);
+ st2 = XGetWindowProperty (dpy, window,
+ XA_SCREENSAVER_RESPONSE,
+ 0, 1024, True,
+ AnyPropertyType,
+ &type, &format, &nitems, &bytesafter,
+ &msg);
+ XSync (dpy, False);
+ XSetErrorHandler (old_handler);
+ old_handler = 0;
+
+ if (got_badwindow)
+ {
+ if (exiting_p)
+ return 0;
+
+ sprintf (err, "xscreensaver window unexpectedly deleted.");
+
+ if (error_ret)
+ *error_ret = strdup (err);
+ else
+ fprintf (stderr, "%s: %s\n", progname, err);
+
+ return -1;
+ }
+
+ if (st2 == Success && type != None)
+ {
+ if (type != XA_STRING || format != 8)
+ {
+ sprintf (err, "unrecognized response property.");
+
+ if (error_ret)
+ *error_ret = strdup (err);
+ else
+ fprintf (stderr, "%s: %s\n", progname, err);
+
+ if (msg) XFree (msg);
+ return -1;
+ }
+ else if (!msg || (msg[0] != '+' && msg[0] != '-'))
+ {
+ sprintf (err, "unrecognized response message.");
+
+ if (error_ret)
+ *error_ret = strdup (err);
+ else
+ fprintf (stderr, "%s: %s\n", progname, err);
+
+ if (msg) XFree (msg);
+ return -1;
+ }
+ else
+ {
+ int ret = (msg[0] == '+' ? 0 : -1);
+ sprintf (err, "%s: %s\n", progname, (char *) msg+1);
+
+ if (error_ret)
+ *error_ret = strdup (err);
+ else if (verbose_p || ret != 0)
+ fprintf ((ret < 0 ? stderr : stdout), "%s\n", err);
+
+ XFree (msg);
+ return ret;
+ }
+ }
+ }
+
+ return -1; /* warning suppression: not actually reached */
+}
+
+
+int
+xscreensaver_command (Display *dpy, Atom command, long arg, Bool verbose_p,
+ char **error_ret)
+{
+ Window w = 0;
+ int status = send_xscreensaver_command (dpy, command, arg, &w, error_ret);
+ if (status == 0)
+ status = xscreensaver_command_response (dpy, w, verbose_p,
+ (command == XA_EXIT),
+ error_ret);
+
+ fflush (stdout);
+ fflush (stderr);
+ return (status < 0 ? status : 0);
+}
+
+
+void
+server_xscreensaver_version (Display *dpy,
+ char **version_ret,
+ char **user_ret,
+ char **host_ret)
+{
+ Window window = find_screensaver_window (dpy, 0);
+
+ Atom type;
+ int format;
+ unsigned long nitems, bytesafter;
+
+ if (version_ret)
+ *version_ret = 0;
+ if (user_ret)
+ *user_ret = 0;
+ if (host_ret)
+ *host_ret = 0;
+
+ if (!window)
+ return;
+
+ if (version_ret)
+ {
+ unsigned char *v = 0;
+ XGetWindowProperty (dpy, window, XA_SCREENSAVER_VERSION, 0, 1,
+ False, XA_STRING, &type, &format, &nitems,
+ &bytesafter, &v);
+ if (v)
+ {
+ *version_ret = strdup ((char *) v);
+ XFree (v);
+ }
+ }
+
+ if (user_ret || host_ret)
+ {
+ unsigned char *id = 0;
+ const char *user = 0;
+ const char *host = 0;
+
+ XGetWindowProperty (dpy, window, XA_SCREENSAVER_ID, 0, 512,
+ False, XA_STRING, &type, &format, &nitems,
+ &bytesafter, &id);
+ if (id && *id)
+ {
+ const char *old_tag = " on host ";
+ const char *s = strstr ((char *) id, old_tag);
+ if (s)
+ {
+ /* found ID of the form "1234 on host xyz". */
+ user = 0;
+ host = s + strlen (old_tag);
+ }
+ else
+ {
+ char *o = 0, *p = 0, *c = 0;
+ o = strchr ((char *) id, '(');
+ if (o) p = strchr (o, '@');
+ if (p) c = strchr (p, ')');
+ if (c)
+ {
+ /* found ID of the form "1234 (user@host)". */
+ user = o+1;
+ host = p+1;
+ *p = 0;
+ *c = 0;
+ }
+ }
+
+ }
+
+ if (user && *user && *user != '?')
+ *user_ret = strdup (user);
+ else
+ *user_ret = 0;
+
+ if (host && *host && *host != '?')
+ *host_ret = strdup (host);
+ else
+ *host_ret = 0;
+
+ if (id)
+ XFree (id);
+ }
+}
diff --git a/driver/remote.h b/driver/remote.h
new file mode 100644
index 0000000..e1db351
--- /dev/null
+++ b/driver/remote.h
@@ -0,0 +1,24 @@
+/* xscreensaver-command, Copyright (c) 1991-1998
+ * by 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_REMOTE_H_
+#define _XSCREENSAVER_REMOTE_H_
+
+extern int xscreensaver_command (Display *dpy, Atom command, long arg,
+ Bool verbose_p, char **error_ret);
+
+extern void server_xscreensaver_version (Display *dpy,
+ char **version_ret,
+ char **user_ret,
+ char **host_ret);
+
+#endif /* _XSCREENSAVER_REMOTE_H_ */
diff --git a/driver/screens.c b/driver/screens.c
new file mode 100644
index 0000000..1a2f41d
--- /dev/null
+++ b/driver/screens.c
@@ -0,0 +1,1094 @@
+/* screens.c --- dealing with RANDR, Xinerama, and VidMode Viewports.
+ * xscreensaver, Copyright (c) 1991-2008 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 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.)
+ *
+ * 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.
+ *
+ * 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.)
+ *
+ * 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.
+ *
+ * 3) Vidmode Viewports:
+ *
+ * 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).
+ *
+ * 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.
+ *
+ * Also, half the time it doesn't work at all: it tends 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.
+ *
+ * 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).
+ *
+ * 5a) Nvidia fucks it up:
+ *
+ * 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.
+ *
+ * 5b) Also sometimes RANDR says stupid shit like, "You have one
+ * screen, and it has no available orientations or sizes."
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <X11/Xlib.h>
+
+#ifdef HAVE_RANDR
+# include <X11/extensions/Xrandr.h>
+#endif /* HAVE_RANDR */
+
+#ifdef HAVE_XINERAMA
+# include <X11/extensions/Xinerama.h>
+#endif /* HAVE_XINERAMA */
+
+#ifdef HAVE_XF86VMODE
+# 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. */
+};
+
+static Bool layouts_differ_p (monitor **a, monitor **b);
+
+
+static void
+free_monitors (monitor **monitors)
+{
+ monitor **m2 = monitors;
+ if (! monitors) return;
+ while (*m2)
+ {
+ if ((*m2)->desc) free ((*m2)->desc);
+ if ((*m2)->err) free ((*m2)->err);
+ free (*m2);
+ m2++;
+ }
+ free (monitors);
+}
+
+
+static char *
+append (char *s1, const char *s2)
+{
+ char *s = (char *) malloc ((s1 ? strlen(s1) : 0) +
+ (s2 ? strlen(s2) : 0) + 3);
+ *s = 0;
+ if (s1) strcat (s, s1);
+ if (s1 && s2) strcat (s, "\n");
+ if (s2) strcat (s, s2);
+ if (s1) free (s1);
+ return s;
+}
+
+
+#ifdef HAVE_XINERAMA
+
+static monitor **
+xinerama_scan_monitors (Display *dpy, char **errP)
+{
+ Screen *screen = DefaultScreenOfDisplay (dpy);
+ int event, error, nscreens, i;
+ XineramaScreenInfo *xsi;
+ monitor **monitors;
+
+ if (! XineramaQueryExtension (dpy, &event, &error))
+ return 0;
+
+ if (! XineramaIsActive (dpy))
+ return 0;
+
+ xsi = XineramaQueryScreens (dpy, &nscreens);
+ if (!xsi) return 0;
+
+ monitors = (monitor **) calloc (nscreens + 1, sizeof(*monitors));
+ if (!monitors) return 0;
+
+ for (i = 0; i < nscreens; i++)
+ {
+ monitor *m = (monitor *) calloc (1, sizeof (monitor));
+ monitors[i] = m;
+ m->id = i;
+ m->screen = screen;
+ m->x = xsi[i].x_org;
+ m->y = xsi[i].y_org;
+ m->width = xsi[i].width;
+ m->height = xsi[i].height;
+ }
+ return monitors;
+}
+
+#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 **
+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;
+
+ 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 (! new_randr_p)
+ /* RANDR 1.0 -- no Xinerama-like virtual screens. */
+ nscreens = ScreenCount (dpy);
+ else /* RANDR 1.2 or newer -- built-in Xinerama */
+ {
+# 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 */
+ }
+
+ if (nscreens <= 0)
+ {
+ *errP = append (*errP,
+ "WARNING: RANDR reported no screens! Ignoring it.");
+ return 0;
+ }
+
+ monitors = (monitor **) calloc (nscreens + 1, sizeof(*monitors));
+ if (!monitors) return 0;
+
+ for (i = 0, j = 0; i < ScreenCount (dpy); i++)
+ {
+ Screen *screen = ScreenOfDisplay (dpy, i);
+
+ if (! new_randr_p) /* RANDR 1.0 */
+ {
+ XRRScreenConfiguration *rrc;
+ monitor *m = (monitor *) calloc (1, sizeof (monitor));
+ monitors[i] = m;
+ m->screen = screen;
+ m->id = i;
+
+ rrc = XRRGetScreenInfo (dpy, RootWindowOfScreen (screen));
+ if (rrc)
+ {
+ 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);
+ }
+ }
+ 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 (crtci)
+ XRRFreeCrtcInfo (crtci);
+ XRRFreeOutputInfo (rroi);
+ }
+ XRRFreeScreenResources (res);
+# endif /* HAVE_RANDR_12 */
+ }
+ }
+
+ /* Work around more fucking brain damage. */
+ {
+ int ok = 0;
+ int i = 0;
+ while (monitors[i])
+ {
+ if (monitors[i]->width != 0 && monitors[i]->height != 0)
+ ok++;
+ i++;
+ }
+ if (! ok)
+ {
+ *errP = append (*errP,
+ "WARNING: RANDR says all screens are 0x0! Ignoring it.");
+ free_monitors (monitors);
+ monitors = 0;
+ }
+ }
+
+ return monitors;
+}
+
+#endif /* HAVE_RANDR */
+
+
+static monitor **
+basic_scan_monitors (Display *dpy, char **errP)
+{
+ int nscreens = ScreenCount (dpy);
+ int i;
+ monitor **monitors = (monitor **) calloc (nscreens + 1, sizeof(*monitors));
+ if (!monitors) return 0;
+
+ for (i = 0; i < nscreens; i++)
+ {
+ Screen *screen = ScreenOfDisplay (dpy, i);
+ monitor *m = (monitor *) calloc (1, sizeof (monitor));
+ monitors[i] = m;
+ m->id = i;
+ m->screen = screen;
+ m->x = 0;
+ m->y = 0;
+ m->width = WidthOfScreen (screen);
+ m->height = HeightOfScreen (screen);
+ }
+ return monitors;
+}
+
+
+#if defined(HAVE_RANDR) && defined(HAVE_XINERAMA)
+
+/* From: Aaron Plattner <aplattner@nvidia.com>
+ Date: August 7, 2008 10:21:25 AM PDT
+ To: linux-bugs@nvidia.com
+
+ The NVIDIA X driver does not yet support RandR 1.2. The X server has
+ a compatibility layer in it that allows RandR 1.2 clients to talk to
+ RandR 1.1 drivers through an RandR 1.2 pseudo-output called "default".
+ This reports the total combined resolution of the TwinView display,
+ since it doesn't have any visibility into TwinView metamodes. There
+ is no way for the driver to prevent the server from turning on this
+ compatibility layer.
+
+ The intention is for X client applications to continue to use the
+ Xinerama extension to query the screen geometry. RandR 1.2 reports
+ its own Xinerama info for this purpose. I would recommend against
+ modifying xscreensaver to try to get this information from RandR.
+ */
+static monitor **
+randr_versus_xinerama_fight (Display *dpy, monitor **randr_monitors,
+ char **errP)
+{
+ monitor **xinerama_monitors;
+
+ if (!randr_monitors)
+ return 0;
+
+ xinerama_monitors = xinerama_scan_monitors (dpy, errP);
+ if (!xinerama_monitors)
+ return randr_monitors;
+
+ if (! layouts_differ_p (randr_monitors, xinerama_monitors))
+ {
+ free_monitors (xinerama_monitors);
+ return randr_monitors;
+ }
+ else if ( randr_monitors[0] && !randr_monitors[1] && /* 1 monitor */
+ xinerama_monitors[0] && xinerama_monitors[1]) /* >1 monitor */
+ {
+ *errP = append (*errP,
+ "WARNING: RANDR reports 1 screen but Xinerama\n"
+ "\t\treports multiple. Believing Xinerama.");
+ free_monitors (randr_monitors);
+ return xinerama_monitors;
+ }
+ else
+ {
+ *errP = append (*errP,
+ "WARNING: RANDR and Xinerama report different\n"
+ "\t\tscreen layouts! Believing RANDR.");
+ free_monitors (xinerama_monitors);
+ return randr_monitors;
+ }
+}
+
+#endif /* HAVE_RANDR && HAVE_XINERAMA */
+
+
+#ifdef DEBUG_MULTISCREEN
+
+/* If DEBUG_MULTISCREEN is defined, then in "-debug" mode, xscreensaver
+ will pretend that it is changing the number of connected monitors
+ every few seconds, using the geometries in the following list,
+ for stress-testing purposes.
+ */
+static monitor **
+debug_scan_monitors (Display *dpy, char **errP)
+{
+ static const char * const geoms[] = {
+ "1600x1028+0+22",
+ "1024x768+0+22",
+ "800x600+0+22",
+ "800x600+0+22,800x600+800+22",
+ "800x600+0+22,800x600+800+22,800x600+300+622",
+ "800x600+0+22,800x600+800+22,800x600+0+622,800x600+800+622",
+ "640x480+0+22,640x480+640+22,640x480+0+502,640x480+640+502",
+ "640x480+240+22,640x480+0+502,640x480+640+502",
+ "640x480+0+200,640x480+640+200",
+ "800x600+400+22",
+ "320x200+0+22,320x200+320+22,320x200+640+22,320x200+960+22,320x200+0+222,320x200+320+222,320x200+640+222,320x200+960+222,320x200+0+422,320x200+320+422,320x200+640+422,320x200+960+422,320x200+0+622,320x200+320+622,320x200+640+622,320x200+960+622,320x200+0+822,320x200+320+822,320x200+640+822,320x200+960+822"
+ };
+ static int index = 0;
+ monitor **monitors = (monitor **) calloc (100, sizeof(*monitors));
+ int nscreens = 0;
+ Screen *screen = DefaultScreenOfDisplay (dpy);
+
+ char *s = strdup (geoms[index]);
+ char *token = strtok (s, ",");
+ while (token)
+ {
+ monitor *m = calloc (1, sizeof (monitor));
+ char c;
+ m->id = nscreens;
+ m->screen = screen;
+ if (4 != sscanf (token, "%dx%d+%d+%d%c",
+ &m->width, &m->height, &m->x, &m->y, &c))
+ abort();
+ m->width -= 2;
+ m->height -= 2;
+ monitors[nscreens++] = m;
+ token = strtok (0, ",");
+ }
+ free (s);
+
+ index = (index+1) % countof(geoms);
+ return monitors;
+}
+
+#endif /* DEBUG_MULTISCREEN */
+
+
+#ifdef QUAD_MODE
+static monitor **
+quadruple (monitor **monitors, Bool debug_p, char **errP)
+{
+ int i, j, count = 0;
+ monitor **monitors2;
+ while (monitors[count])
+ count++;
+ monitors2 = (monitor **) calloc (count * 4 + 1, sizeof(*monitors));
+ if (!monitors2) abort();
+
+ for (i = 0, j = 0; i < count; i++)
+ {
+ int k;
+ for (k = 0; k < 4; k++)
+ {
+ monitors2[j+k] = (monitor *) calloc (1, sizeof (monitor));
+ *monitors2[j+k] = *monitors[i];
+ monitors2[j+k]->width /= (debug_p ? 4 : 2);
+ monitors2[j+k]->height /= 2;
+ monitors2[j+k]->id = (monitors[i]->id * 4) + k;
+ monitors2[j+k]->name = (monitors[i]->name
+ ? strdup (monitors[i]->name) : 0);
+ }
+ monitors2[j+1]->x += monitors2[j]->width;
+ monitors2[j+2]->y += monitors2[j]->height;
+ monitors2[j+3]->x += monitors2[j]->width;
+ monitors2[j+3]->y += monitors2[j]->height;
+ j += 4;
+ }
+
+ free_monitors (monitors);
+ return monitors2;
+}
+#endif /* QUAD_MODE */
+
+
+static monitor **
+scan_monitors (saver_info *si)
+{
+ saver_preferences *p = &si->prefs;
+ monitor **monitors = 0;
+ char *err = 0;
+
+# ifdef DEBUG_MULTISCREEN
+ if (! monitors) monitors = debug_scan_monitors (si->dpy, &err);
+# endif
+
+# ifdef HAVE_RANDR
+ if (! p->getviewport_full_of_lies_p)
+ if (! monitors) monitors = randr_scan_monitors (si->dpy, &err);
+
+# ifdef HAVE_XINERAMA
+ monitors = randr_versus_xinerama_fight (si->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);
+# endif
+
+ if (! monitors) monitors = basic_scan_monitors (si->dpy, &err);
+
+# ifdef QUAD_MODE
+ if (p->quad_p)
+ monitors = quadruple (monitors, p->debug_p, &err);
+# endif
+
+ if (monitors && err) monitors[0]->err = err;
+
+ return monitors;
+}
+
+
+static Bool
+monitors_overlap_p (monitor *a, monitor *b)
+{
+ /* Two rectangles overlap if the max of the tops is less than the
+ min of the bottoms and the max of the lefts is less than the min
+ of the rights.
+ */
+# undef MAX
+# undef MIN
+# define MAX(A,B) ((A)>(B)?(A):(B))
+# define MIN(A,B) ((A)<(B)?(A):(B))
+
+ int maxleft = MAX(a->x, b->x);
+ int maxtop = MAX(a->y, b->y);
+ int minright = MIN(a->x + a->width - 1, b->x + b->width);
+ int minbot = MIN(a->y + a->height - 1, b->y + b->height);
+ return (maxtop < minbot && maxleft < minright);
+}
+
+
+static Bool
+plausible_aspect_ratio_p (monitor **monitors)
+{
+ /* Modern wide-screen monitors come in the following aspect ratios:
+
+ One monitor: If you tack a 640x480 monitor
+ onto the right, the ratio is:
+ 16 x 9 --> 1.78
+ 852 x 480 --> 1.77 852+640 x 480 --> 3.11 "SD 480p"
+ 1280 x 720 --> 1.78 1280+640 x 720 --> 2.67 "HD 720p"
+ 1280 x 920 --> 1.39 1280+640 x 920 --> 2.09
+ 1366 x 768 --> 1.78 1366+640 x 768 --> 2.61 "HD 768p"
+ 1440 x 900 --> 1.60 1440+640 x 900 --> 2.31
+ 1680 x 1050 --> 1.60 1680+640 x 1050 --> 2.21
+ 1690 x 1050 --> 1.61 1690+640 x 1050 --> 2.22
+ 1920 x 1080 --> 1.78 1920+640 x 1080 --> 2.37 "HD 1080p"
+ 1920 x 1200 --> 1.60 1920+640 x 1200 --> 2.13
+ 2560 x 1600 --> 1.60 2560+640 x 1600 --> 2.00
+
+ So that implies that if we ever see an aspect ratio >= 2.0,
+ we can be pretty sure that the X server is lying to us, and
+ that's actually two monitors, not one.
+ */
+ if (monitors[0] && !monitors[1] && /* exactly 1 monitor */
+ monitors[0]->height &&
+ monitors[0]->width / (double) monitors[0]->height >= 1.9)
+ return False;
+ else
+ return True;
+}
+
+
+/* Mark the ones that overlap, etc.
+ */
+static void
+check_monitor_sanity (monitor **monitors)
+{
+ int i, j, count = 0;
+
+ while (monitors[count])
+ count++;
+
+# define X1 monitors[i]->x
+# define X2 monitors[j]->x
+# define Y1 monitors[i]->y
+# define Y2 monitors[j]->y
+# define W1 monitors[i]->width
+# define W2 monitors[j]->width
+# define H1 monitors[i]->height
+# define H2 monitors[j]->height
+
+ /* If a monitor is enclosed by any other monitor, that's insane.
+ */
+ for (i = 0; i < count; i++)
+ for (j = 0; j < count; j++)
+ if (i != j &&
+ monitors[i]->sanity == S_SANE &&
+ monitors[j]->sanity == S_SANE &&
+ monitors[i]->screen == monitors[j]->screen &&
+ X2 >= X1 &&
+ Y2 >= Y1 &&
+ (X2+W2) <= (X1+W1) &&
+ (Y2+H2) <= (Y1+H1))
+ {
+ if (X1 == X2 &&
+ Y1 == Y2 &&
+ W1 == W2 &&
+ H1 == H2)
+ monitors[j]->sanity = S_DUPLICATE;
+ else
+ monitors[j]->sanity = S_ENCLOSED;
+ monitors[j]->enemy = i;
+ }
+
+ /* After checking for enclosure, check for other lossage against earlier
+ monitors. We do enclosure first so that we make sure to pick the
+ larger one.
+ */
+ for (i = 0; i < count; i++)
+ for (j = 0; j < i; j++)
+ {
+ if (monitors[i]->sanity != S_SANE) continue; /* already marked */
+ if (monitors[j]->sanity != S_SANE) continue;
+ if (monitors[i]->screen != monitors[j]->screen) continue;
+
+ if (monitors_overlap_p (monitors[i], monitors[j]))
+ {
+ monitors[i]->sanity = S_OVERLAP;
+ monitors[i]->enemy = j;
+ }
+ }
+
+ /* Finally, make sure all monitors have sane positions and sizes.
+ Xinerama sometimes reports 1024x768 VPs at -1936862040, -1953705044.
+ */
+ for (i = 0; i < count; i++)
+ {
+ if (monitors[i]->sanity != S_SANE) continue; /* already marked */
+ if (X1 < 0 || Y1 < 0 ||
+ W1 <= 0 || H1 <= 0 ||
+ X1+W1 >= 0x7FFF || Y1+H1 >= 0x7FFF)
+ {
+ monitors[i]->sanity = S_OFFSCREEN;
+ monitors[i]->enemy = 0;
+ }
+ }
+
+# undef X1
+# undef X2
+# undef Y1
+# undef Y2
+# undef W1
+# undef W2
+# undef H1
+# undef H2
+}
+
+
+static Bool
+layouts_differ_p (monitor **a, monitor **b)
+{
+ if (!a || !b) return True;
+ while (1)
+ {
+ if (!*a) break;
+ if (!*b) break;
+ if ((*a)->screen != (*b)->screen ||
+ (*a)->x != (*b)->x ||
+ (*a)->y != (*b)->y ||
+ (*a)->width != (*b)->width ||
+ (*a)->height != (*b)->height)
+ return True;
+ a++;
+ b++;
+ }
+ if (*a) return True;
+ if (*b) return True;
+
+ return False;
+}
+
+
+void
+describe_monitor_layout (saver_info *si)
+{
+ monitor **monitors = si->monitor_layout;
+ int count = 0;
+ int good_count = 0;
+ int bad_count = 0;
+ int implausible_p = !plausible_aspect_ratio_p (monitors);
+
+ while (monitors[count])
+ {
+ if (monitors[count]->sanity == S_SANE)
+ good_count++;
+ else
+ bad_count++;
+ count++;
+ }
+
+ if (monitors[0]->err) /* deferred error msg */
+ {
+ char *token = strtok (monitors[0]->err, "\n");
+ while (token)
+ {
+ fprintf (stderr, "%s: %s\n", blurb(), token);
+ token = strtok (0, "\n");
+ }
+ free (monitors[0]->err);
+ monitors[0]->err = 0;
+ }
+
+ if (count == 0)
+ fprintf (stderr, "%s: no screens!\n", blurb());
+ else
+ {
+ int i;
+ fprintf (stderr, "%s: screens in use: %d\n", blurb(), good_count);
+ for (i = 0; i < count; i++)
+ {
+ monitor *m = monitors[i];
+ if (m->sanity != S_SANE) continue;
+ fprintf (stderr, "%s: %3d/%d: %dx%d+%d+%d",
+ blurb(), m->id, screen_number (m->screen),
+ m->width, m->height, m->x, m->y);
+ if (m->desc && *m->desc) fprintf (stderr, " (%s)", m->desc);
+ fprintf (stderr, "\n");
+ }
+ if (bad_count > 0)
+ {
+ fprintf (stderr, "%s: rejected screens: %d\n", blurb(), bad_count);
+ for (i = 0; i < count; i++)
+ {
+ monitor *m = monitors[i];
+ monitor *e = monitors[m->enemy];
+ if (m->sanity == S_SANE) continue;
+ fprintf (stderr, "%s: %3d/%d: %dx%d+%d+%d",
+ blurb(), m->id, screen_number (m->screen),
+ m->width, m->height, m->x, m->y);
+ if (m->desc && *m->desc) fprintf (stderr, " (%s)", m->desc);
+ fprintf (stderr, " -- ");
+ switch (m->sanity)
+ {
+ case S_SANE: abort(); break;
+ case S_ENCLOSED:
+ fprintf (stderr, "enclosed by %d (%dx%d+%d+%d)\n",
+ e->id, e->width, e->height, e->x, e->y);
+ break;
+ case S_DUPLICATE:
+ fprintf (stderr, "duplicate of %d\n", e->id);
+ break;
+ case S_OVERLAP:
+ fprintf (stderr, "overlaps %d (%dx%d+%d+%d)\n",
+ e->id, e->width, e->height, e->x, e->y);
+ break;
+ case S_OFFSCREEN:
+ fprintf (stderr, "off screen (%dx%d)\n",
+ WidthOfScreen (e->screen),
+ HeightOfScreen (e->screen));
+ break;
+ case S_DISABLED:
+ fprintf (stderr, "output disabled\n");
+ break;
+ }
+ }
+ }
+
+ if (implausible_p)
+ fprintf (stderr,
+ "%s: WARNING: single screen aspect ratio is %dx%d = %.2f\n"
+ "%s: probable X server bug in Xinerama/RANDR!\n",
+ blurb(), monitors[0]->width, monitors[0]->height,
+ monitors[0]->width / (double) monitors[0]->height,
+ 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/screensaver-properties.desktop.in b/driver/screensaver-properties.desktop.in
new file mode 100644
index 0000000..de42527
--- /dev/null
+++ b/driver/screensaver-properties.desktop.in
@@ -0,0 +1,8 @@
+[Desktop Entry]
+Exec=xscreensaver-demo
+Icon=xscreensaver
+Terminal=false
+_Name=Screensaver
+_Comment=Change screensaver properties
+Type=Application
+Categories=Settings;DesktopSettings;Security;X-XFCE;
diff --git a/driver/setuid.c b/driver/setuid.c
new file mode 100644
index 0000000..3ac78e4
--- /dev/null
+++ b/driver/setuid.c
@@ -0,0 +1,361 @@
+/* setuid.c --- management of runtime privileges.
+ * xscreensaver, Copyright (c) 1993-1998, 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
+ * 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 <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>
+#endif
+
+#include <pwd.h> /* for getpwnam() and struct passwd */
+#include <grp.h> /* for getgrgid() and struct group */
+
+static const char *
+uid_gid_string (uid_t uid, gid_t gid)
+{
+ static char buf[255];
+ struct passwd *p = 0;
+ struct group *g = 0;
+ p = getpwuid (uid);
+ g = getgrgid (gid);
+ sprintf (buf, "%.100s/%.100s (%ld/%ld)",
+ (p && p->pw_name ? p->pw_name : "???"),
+ (g && g->gr_name ? g->gr_name : "???"),
+ (long) uid, (long) gid);
+ return buf;
+}
+
+
+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
+ gids associated with the uid, e.g.:
+
+ % groups root
+ root : root bin daemon sys adm disk wheel
+
+ However, setgroups() can only be called by root, and returns EPERM
+ for other users even if the call would be a no-op (e.g., setting the
+ group list to the current list.) So, to avoid that spurious error,
+ before calling setgroups() we first check whether the current list
+ of groups contains only one element, our target group. If so, we
+ don't need to call setgroups().
+ */
+static int
+setgroups_needed_p (uid_t target_group)
+{
+ gid_t groups[1024];
+ int n, size;
+ size = sizeof(groups) / sizeof(gid_t);
+ n = getgroups (size - 1, groups);
+ if (n < 0)
+ {
+ char buf [1024];
+ sprintf (buf, "%s: getgroups(%ld, ...)", blurb(), (long int)(size - 1));
+ perror (buf);
+ return 1;
+ }
+ else if (n == 0) /* an empty list means only egid is in effect. */
+ return 0;
+ else if (n == 1 && groups[0] == target_group) /* one element, the target */
+ return 0;
+ else /* more than one, or the wrong one. */
+ return 1;
+}
+
+
+static int
+set_ids_by_number (uid_t uid, gid_t gid, char **message_ret)
+{
+ int uid_errno = 0;
+ int gid_errno = 0;
+ int sgs_errno = 0;
+ 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
+ systems really ought to be using -2 for "nobody", since that works.
+ So, if we get a uid (or gid, for good measure) of -1, switch to -2
+ instead. Note that this must be done after we've looked up the
+ user/group names with getpwuid(-1) and/or getgrgid(-1).
+ */
+ if (gid == (gid_t) -1) gid = (gid_t) -2;
+ if (uid == (uid_t) -1) uid = (uid_t) -2;
+
+ errno = 0;
+ if (setgroups_needed_p (gid) &&
+ setgroups (1, &gid) < 0)
+ sgs_errno = errno ? errno : -1;
+
+ errno = 0;
+ if (setgid (gid) != 0)
+ gid_errno = errno ? errno : -1;
+
+ errno = 0;
+ if (setuid (uid) != 0)
+ uid_errno = errno ? errno : -1;
+
+ 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;
+ return 0;
+ }
+ else
+ {
+ char buf [1024];
+ gid_t groups[1024];
+ int n, size;
+
+ if (sgs_errno)
+ {
+ sprintf (buf, "%s: couldn't setgroups to %.100s (%ld)",
+ blurb(),
+ (g && g->gr_name ? g->gr_name : "???"),
+ (long) gid);
+ if (sgs_errno == -1)
+ fprintf(stderr, "%s: unknown error\n", buf);
+ else
+ {
+ errno = sgs_errno;
+ perror(buf);
+ }
+
+ fprintf (stderr, "%s: effective group list: ", blurb());
+ size = sizeof(groups) / sizeof(gid_t);
+ n = getgroups (size - 1, groups);
+ if (n < 0)
+ fprintf (stderr, "unknown!\n");
+ else
+ {
+ int i;
+ fprintf (stderr, "[");
+ for (i = 0; i < n; i++)
+ {
+ g = getgrgid (groups[i]);
+ if (i > 0) fprintf (stderr, ", ");
+ if (g && g->gr_name) fprintf (stderr, "%s", g->gr_name);
+ else fprintf (stderr, "%ld", (long) groups[i]);
+ }
+ fprintf (stderr, "]\n");
+ }
+ }
+
+ if (gid_errno)
+ {
+ sprintf (buf, "%s: couldn't set gid to %.100s (%ld)",
+ blurb(),
+ (g && g->gr_name ? g->gr_name : "???"),
+ (long) gid);
+ if (gid_errno == -1)
+ fprintf(stderr, "%s: unknown error\n", buf);
+ else
+ {
+ errno = gid_errno;
+ perror(buf);
+ }
+ }
+
+ if (uid_errno)
+ {
+ sprintf (buf, "%s: couldn't set uid to %.100s (%ld)",
+ blurb(),
+ (p && p->pw_name ? p->pw_name : "???"),
+ (long) uid);
+ if (uid_errno == -1)
+ fprintf(stderr, "%s: unknown error\n", buf);
+ else
+ {
+ errno = uid_errno;
+ perror(buf);
+ }
+ }
+
+ return -1;
+ }
+}
+
+
+/* If we've been run as setuid or setgid to someone else (most likely root)
+ turn off the extra permissions so that random user-specified programs
+ don't get special privileges. (On some systems it is necessary to install
+ this program as setuid root in order to read the passwd file to implement
+ 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".
+ */
+void
+hack_uid (saver_info *si)
+{
+
+ /* 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)
+ {
+ si->locking_disabled_p = True;
+ si->nolock_reason = "running as root";
+ }
+
+
+ /* 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 (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);
+ }
+
+ if (set_ids_by_number (p->pw_uid, p->pw_gid, &si->uid_message) != 0)
+ saver_exit (si, -1, 0);
+ }
+
+
+ /* 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;
+ }
+ }
+}
diff --git a/driver/splash.c b/driver/splash.c
new file mode 100644
index 0000000..a4f1761
--- /dev/null
+++ b/driver/splash.c
@@ -0,0 +1,917 @@
+/* xscreensaver, Copyright (c) 1991-2018 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 <X11/Intrinsic.h>
+
+#include "xscreensaver.h"
+#include "resources.h"
+#include "font-retry.h"
+
+#undef MAX
+#define MAX(a,b) ((a)>(b)?(a):(b))
+
+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);
+}
+
+
+int
+string_width (XFontStruct *font, char *s)
+{
+ return XTextWidth(font, s, strlen(s));
+}
+
+
+static void update_splash_window (saver_info *si);
+static void draw_splash_window (saver_info *si);
+static void destroy_splash_window (saver_info *si);
+static void unsplash_timer (XtPointer closure, XtIntervalId *id);
+
+static void do_demo (saver_screen_info *ssi);
+#ifdef PREFS_BUTTON
+static void do_prefs (saver_screen_info *ssi);
+#endif /* PREFS_BUTTON */
+static void do_help (saver_screen_info *ssi);
+
+
+XFontStruct *
+splash_load_font (Display *dpy, char *name, char *class)
+{
+ char *s = get_string_resource (dpy, name, class);
+ XFontStruct *f;
+ if (!s || !*s)
+ s = "-*-helvetica-bold-r-*-*-*-140-*-*-*-*-*-*";
+ f = load_font_retry (dpy, s);
+ if (!f) abort();
+ return f;
+}
+
+
+struct splash_dialog_data {
+
+ saver_screen_info *prompt_screen;
+ XtIntervalId timer;
+
+ Dimension width;
+ Dimension height;
+
+ char *heading_label;
+ char *body_label;
+ char *body2_label;
+ char *body3_label;
+ char *body4_label;
+ char *demo_label;
+#ifdef PREFS_BUTTON
+ char *prefs_label;
+#endif /* PREFS_BUTTON */
+ char *help_label;
+
+ XFontStruct *heading_font;
+ XFontStruct *body_font;
+ XFontStruct *button_font;
+
+ Pixel foreground;
+ Pixel background;
+ Pixel border;
+ Pixel button_foreground;
+ Pixel button_background;
+ Pixel shadow_top;
+ Pixel shadow_bottom;
+
+ Dimension logo_width;
+ Dimension logo_height;
+ Dimension internal_border;
+ Dimension shadow_width;
+
+ Dimension button_width, button_height;
+ Dimension demo_button_x, demo_button_y;
+#ifdef PREFS_BUTTON
+ Dimension prefs_button_x, prefs_button_y;
+#endif /* PREFS_BUTTON */
+ Dimension help_button_x, help_button_y;
+
+ Pixmap logo_pixmap;
+ Pixmap logo_clipmask;
+ int logo_npixels;
+ unsigned long *logo_pixels;
+
+ int pressed;
+};
+
+
+void
+make_splash_dialog (saver_info *si)
+{
+ saver_preferences *p = &si->prefs;
+ int x, y, bw;
+ XSetWindowAttributes attrs;
+ unsigned long attrmask = 0;
+ splash_dialog_data *sp;
+ saver_screen_info *ssi;
+ Colormap cmap;
+
+ Bool whyne = senesculent_p ();
+
+ if (whyne)
+ {
+ /* If locking is not enabled, make sure they see the message. */
+ if (!p->lock_p)
+ {
+ si->prefs.splash_p = True;
+ if (si->prefs.splash_duration < 5000)
+ si->prefs.splash_duration = 5000;
+ }
+ si->prefs.splash_duration += 3000;
+ }
+
+ if (si->sp_data)
+ return;
+ if (!si->prefs.splash_p ||
+ si->prefs.splash_duration <= 0)
+ return;
+
+ ssi = &si->screens[mouse_screen (si)];
+
+ if (!ssi || !ssi->screen)
+ return; /* WTF? Trying to splash while no screens connected? */
+
+ cmap = DefaultColormapOfScreen (ssi->screen);
+
+ sp = (splash_dialog_data *) calloc (1, sizeof(*sp));
+ sp->prompt_screen = ssi;
+
+ sp->heading_label = get_string_resource (si->dpy,
+ "splash.heading.label",
+ "Dialog.Label.Label");
+ sp->body_label = get_string_resource (si->dpy,
+ "splash.body.label",
+ "Dialog.Label.Label");
+ sp->body2_label = get_string_resource (si->dpy,
+ "splash.body2.label",
+ "Dialog.Label.Label");
+ sp->demo_label = get_string_resource (si->dpy,
+ "splash.demo.label",
+ "Dialog.Button.Label");
+#ifdef PREFS_BUTTON
+ sp->prefs_label = get_string_resource (si->dpy,
+ "splash.prefs.label",
+ "Dialog.Button.Label");
+#endif /* PREFS_BUTTON */
+ sp->help_label = get_string_resource (si->dpy,
+ "splash.help.label",
+ "Dialog.Button.Label");
+
+
+
+ if (whyne)
+ {
+ sp->body3_label = strdup("WARNING: This version is very old!");
+ sp->body4_label = strdup("Please upgrade!");
+ }
+
+ if (!sp->heading_label)
+ sp->heading_label = strdup("ERROR: REESOURCES NOT INSTALLED CORRECTLY");
+ if (!sp->body_label)
+ sp->body_label = strdup("ERROR: REESOURCES NOT INSTALLED CORRECTLY");
+ if (!sp->body2_label)
+ sp->body2_label = strdup("ERROR: REESOURCES NOT INSTALLED CORRECTLY");
+ if (!sp->demo_label) sp->demo_label = strdup("ERROR");
+#ifdef PREFS_BUTTON
+ if (!sp->prefs_label) sp->prefs_label = strdup("ERROR");
+#endif /* PREFS_BUTTON */
+ if (!sp->help_label) sp->help_label = strdup("ERROR");
+
+ /* Put the version number in the label. */
+ {
+ char *s = (char *) malloc (strlen(sp->heading_label) + 20);
+ sprintf(s, sp->heading_label, si->version);
+ free (sp->heading_label);
+ sp->heading_label = s;
+ }
+
+ sp->heading_font =
+ splash_load_font (si->dpy, "splash.headingFont", "Dialog.Font");
+ sp->body_font =
+ splash_load_font (si->dpy, "splash.bodyFont", "Dialog.Font");
+ sp->button_font =
+ splash_load_font (si->dpy, "splash.buttonFont", "Dialog.Font");
+
+ sp->foreground = get_pixel_resource (si->dpy, cmap,
+ "splash.foreground",
+ "Dialog.Foreground");
+ sp->background = get_pixel_resource (si->dpy, cmap,
+ "splash.background",
+ "Dialog.Background");
+ sp->border = get_pixel_resource (si->dpy, cmap,
+ "splash.borderColor",
+ "Dialog.borderColor");
+
+ if (sp->foreground == sp->background)
+ {
+ /* Make sure the error messages show up. */
+ sp->foreground = BlackPixelOfScreen (ssi->screen);
+ sp->background = WhitePixelOfScreen (ssi->screen);
+ }
+
+ sp->button_foreground = get_pixel_resource (si->dpy, cmap,
+ "splash.Button.foreground",
+ "Dialog.Button.Foreground");
+ sp->button_background = get_pixel_resource (si->dpy, cmap,
+ "splash.Button.background",
+ "Dialog.Button.Background");
+ sp->shadow_top = get_pixel_resource (si->dpy, cmap,
+ "splash.topShadowColor",
+ "Dialog.Foreground");
+ sp->shadow_bottom = get_pixel_resource (si->dpy, cmap,
+ "splash.bottomShadowColor",
+ "Dialog.Background");
+
+ sp->logo_width = get_integer_resource (si->dpy,
+ "splash.logo.width",
+ "Dialog.Logo.Width");
+ sp->logo_height = get_integer_resource (si->dpy,
+ "splash.logo.height",
+ "Dialog.Logo.Height");
+ sp->internal_border = get_integer_resource (si->dpy,
+ "splash.internalBorderWidth",
+ "Dialog.InternalBorderWidth");
+ sp->shadow_width = get_integer_resource (si->dpy,
+ "splash.shadowThickness",
+ "Dialog.ShadowThickness");
+
+ if (sp->logo_width == 0) sp->logo_width = 150;
+ if (sp->logo_height == 0) sp->logo_height = 150;
+ if (sp->internal_border == 0) sp->internal_border = 15;
+ if (sp->shadow_width == 0) sp->shadow_width = 4;
+
+ {
+ int direction, ascent, descent;
+ XCharStruct overall;
+
+ sp->width = 0;
+ sp->height = 0;
+
+ /* Measure the heading_label. */
+ XTextExtents (sp->heading_font,
+ sp->heading_label, strlen(sp->heading_label),
+ &direction, &ascent, &descent, &overall);
+ if (overall.width > sp->width) sp->width = overall.width;
+ sp->height += ascent + descent;
+
+ /* Measure the body_label. */
+ XTextExtents (sp->body_font,
+ sp->body_label, strlen(sp->body_label),
+ &direction, &ascent, &descent, &overall);
+ if (overall.width > sp->width) sp->width = overall.width;
+ sp->height += ascent + descent;
+
+ /* Measure the body2_label. */
+ XTextExtents (sp->body_font,
+ sp->body2_label, strlen(sp->body2_label),
+ &direction, &ascent, &descent, &overall);
+ if (overall.width > sp->width) sp->width = overall.width;
+ sp->height += ascent + descent;
+
+ /* Measure the optional body3_label. */
+ if (sp->body3_label)
+ {
+ XTextExtents (sp->heading_font,
+ sp->body3_label, strlen(sp->body3_label),
+ &direction, &ascent, &descent, &overall);
+ if (overall.width > sp->width) sp->width = overall.width;
+ XTextExtents (sp->heading_font,
+ sp->body4_label, strlen(sp->body4_label),
+ &direction, &ascent, &descent, &overall);
+ if (overall.width > sp->width) sp->width = overall.width;
+ sp->height += (ascent + descent) * 5;
+ }
+
+ {
+ Dimension w2 = 0, w3 = 0, w4 = 0;
+ Dimension h2 = 0, h3 = 0, h4 = 0;
+
+ /* Measure the Demo button. */
+ XTextExtents (sp->button_font,
+ sp->demo_label, strlen(sp->demo_label),
+ &direction, &ascent, &descent, &overall);
+ w2 = overall.width;
+ h2 = ascent + descent;
+
+#ifdef PREFS_BUTTON
+ /* Measure the Prefs button. */
+ XTextExtents (sp->button_font,
+ sp->prefs_label, strlen(sp->prefs_label),
+ &direction, &ascent, &descent, &overall);
+ w3 = overall.width;
+ h3 = ascent + descent;
+#else /* !PREFS_BUTTON */
+ w3 = 0;
+ h3 = 0;
+#endif /* !PREFS_BUTTON */
+
+ /* Measure the Help button. */
+ XTextExtents (sp->button_font,
+ sp->help_label, strlen(sp->help_label),
+ &direction, &ascent, &descent, &overall);
+ w4 = overall.width;
+ h4 = ascent + descent;
+
+ w2 = MAX(w2, w3); w2 = MAX(w2, w4);
+ h2 = MAX(h2, h3); h2 = MAX(h2, h4);
+
+ /* Add some horizontal padding inside the buttons. */
+ w2 += ascent;
+
+ w2 += ((ascent + descent) / 2) + (sp->shadow_width * 2);
+ h2 += ((ascent + descent) / 2) + (sp->shadow_width * 2);
+
+ sp->button_width = w2;
+ sp->button_height = h2;
+
+#ifdef PREFS_BUTTON
+ w2 *= 3;
+#else /* !PREFS_BUTTON */
+ w2 *= 2;
+#endif /* !PREFS_BUTTON */
+
+ w2 += ((ascent + descent) * 2); /* for space between buttons */
+
+ if (w2 > sp->width) sp->width = w2;
+ sp->height += h2;
+ }
+
+ sp->width += (sp->internal_border * 2);
+ sp->height += (sp->internal_border * 3);
+
+ if (sp->logo_height > sp->height)
+ sp->height = sp->logo_height;
+ else if (sp->height > sp->logo_height)
+ sp->logo_height = sp->height;
+
+ sp->logo_width = sp->logo_height;
+
+ sp->width += sp->logo_width;
+ }
+
+ attrmask |= CWOverrideRedirect; attrs.override_redirect = True;
+ attrmask |= CWEventMask;
+ attrs.event_mask = (ExposureMask | ButtonPressMask | ButtonReleaseMask);
+
+ {
+ int sx = 0, sy = 0, w, h;
+
+ x = ssi->x;
+ y = ssi->y;
+ w = ssi->width;
+ h = ssi->height;
+ if (si->prefs.debug_p) w /= 2;
+ x = sx + (((w + sp->width) / 2) - sp->width);
+ y = sy + (((h + sp->height) / 2) - sp->height);
+ if (x < sx) x = sx;
+ if (y < sy) y = sy;
+ }
+
+ bw = get_integer_resource (si->dpy,
+ "splash.borderWidth",
+ "Dialog.BorderWidth");
+
+ si->splash_dialog =
+ XCreateWindow (si->dpy,
+ RootWindowOfScreen(ssi->screen),
+ x, y, sp->width, sp->height, bw,
+ DefaultDepthOfScreen (ssi->screen), InputOutput,
+ DefaultVisualOfScreen(ssi->screen),
+ attrmask, &attrs);
+ XSetWindowBackground (si->dpy, si->splash_dialog, sp->background);
+ XSetWindowBorder (si->dpy, si->splash_dialog, sp->border);
+
+
+ sp->logo_pixmap = xscreensaver_logo (ssi->screen,
+ /* same visual as si->splash_dialog */
+ DefaultVisualOfScreen (ssi->screen),
+ si->splash_dialog, cmap,
+ sp->background,
+ &sp->logo_pixels, &sp->logo_npixels,
+ &sp->logo_clipmask, True);
+
+ XMapRaised (si->dpy, si->splash_dialog);
+ XSync (si->dpy, False);
+
+ si->sp_data = sp;
+
+ sp->timer = XtAppAddTimeOut (si->app, si->prefs.splash_duration,
+ unsplash_timer, (XtPointer) si);
+
+ draw_splash_window (si);
+ XSync (si->dpy, False);
+}
+
+
+static void
+draw_splash_window (saver_info *si)
+{
+ splash_dialog_data *sp = si->sp_data;
+ XGCValues gcv;
+ GC gc1, gc2;
+ int vspacing, height;
+ int x1, x2, x3, y1, y2;
+ int sw;
+
+#ifdef PREFS_BUTTON
+ int hspacing;
+ int nbuttons = 3;
+#endif /* !PREFS_BUTTON */
+
+ height = (sp->heading_font->ascent + sp->heading_font->descent +
+ sp->body_font->ascent + sp->body_font->descent +
+ sp->body_font->ascent + sp->body_font->descent +
+ sp->button_font->ascent + sp->button_font->descent);
+ vspacing = ((sp->height
+ - (4 * sp->shadow_width)
+ - (2 * sp->internal_border)
+ - height) / 5);
+ if (vspacing < 0) vspacing = 0;
+ if (vspacing > (sp->heading_font->ascent * 2))
+ vspacing = (sp->heading_font->ascent * 2);
+
+ gcv.foreground = sp->foreground;
+ gc1 = XCreateGC (si->dpy, si->splash_dialog, GCForeground, &gcv);
+ gc2 = XCreateGC (si->dpy, si->splash_dialog, GCForeground, &gcv);
+ x1 = sp->logo_width;
+ x3 = sp->width - (sp->shadow_width * 2);
+ y1 = sp->internal_border;
+
+ /* top heading
+ */
+ XSetFont (si->dpy, gc1, sp->heading_font->fid);
+ sw = string_width (sp->heading_font, sp->heading_label);
+ x2 = (x1 + ((x3 - x1 - sw) / 2));
+ y1 += sp->heading_font->ascent;
+ XDrawString (si->dpy, si->splash_dialog, gc1, x2, y1,
+ sp->heading_label, strlen(sp->heading_label));
+ y1 += sp->heading_font->descent;
+
+ /* text below top heading
+ */
+ XSetFont (si->dpy, gc1, sp->body_font->fid);
+ y1 += vspacing + sp->body_font->ascent;
+ sw = string_width (sp->body_font, sp->body_label);
+ x2 = (x1 + ((x3 - x1 - sw) / 2));
+ XDrawString (si->dpy, si->splash_dialog, gc1, x2, y1,
+ sp->body_label, strlen(sp->body_label));
+ y1 += sp->body_font->descent;
+
+ y1 += sp->body_font->ascent;
+ sw = string_width (sp->body_font, sp->body2_label);
+ x2 = (x1 + ((x3 - x1 - sw) / 2));
+ XDrawString (si->dpy, si->splash_dialog, gc1, x2, y1,
+ sp->body2_label, strlen(sp->body2_label));
+ y1 += sp->body_font->descent;
+
+ if (sp->body3_label)
+ {
+ XSetFont (si->dpy, gc1, sp->heading_font->fid);
+ y1 += sp->heading_font->ascent + sp->heading_font->descent;
+ y1 += sp->heading_font->ascent;
+ sw = string_width (sp->heading_font, sp->body3_label);
+ x2 = (x1 + ((x3 - x1 - sw) / 2));
+ XDrawString (si->dpy, si->splash_dialog, gc1, x2, y1,
+ sp->body3_label, strlen(sp->body3_label));
+ y1 += sp->heading_font->descent + sp->heading_font->ascent;
+ sw = string_width (sp->heading_font, sp->body4_label);
+ x2 = (x1 + ((x3 - x1 - sw) / 2));
+ XDrawString (si->dpy, si->splash_dialog, gc1, x2, y1,
+ sp->body4_label, strlen(sp->body4_label));
+ y1 += sp->heading_font->descent;
+ XSetFont (si->dpy, gc1, sp->body_font->fid);
+ }
+
+ /* The buttons
+ */
+ XSetForeground (si->dpy, gc1, sp->button_foreground);
+ XSetForeground (si->dpy, gc2, sp->button_background);
+
+/* y1 += (vspacing * 2);*/
+ y1 = sp->height - sp->internal_border - sp->button_height;
+
+ x1 += sp->internal_border;
+ y2 = (y1 + ((sp->button_height -
+ (sp->button_font->ascent + sp->button_font->descent))
+ / 2)
+ + sp->button_font->ascent);
+#ifdef PREFS_BUTTON
+ hspacing = ((sp->width - x1 - (sp->shadow_width * 2) -
+ sp->internal_border - (sp->button_width * nbuttons))
+ / 2);
+#endif
+
+ x2 = x1 + ((sp->button_width - string_width(sp->button_font, sp->demo_label))
+ / 2);
+ XFillRectangle (si->dpy, si->splash_dialog, gc2, x1, y1,
+ sp->button_width, sp->button_height);
+ XDrawString (si->dpy, si->splash_dialog, gc1, x2, y2,
+ sp->demo_label, strlen(sp->demo_label));
+ sp->demo_button_x = x1;
+ sp->demo_button_y = y1;
+
+#ifdef PREFS_BUTTON
+ x1 += hspacing + sp->button_width;
+ x2 = x1 + ((sp->button_width - string_width(sp->button_font,sp->prefs_label))
+ / 2);
+ XFillRectangle (si->dpy, si->splash_dialog, gc2, x1, y1,
+ sp->button_width, sp->button_height);
+ XDrawString (si->dpy, si->splash_dialog, gc1, x2, y2,
+ sp->prefs_label, strlen(sp->prefs_label));
+ sp->prefs_button_x = x1;
+ sp->prefs_button_y = y1;
+#endif /* PREFS_BUTTON */
+
+#ifdef PREFS_BUTTON
+ x1 += hspacing + sp->button_width;
+#else /* !PREFS_BUTTON */
+ x1 = (sp->width - sp->button_width -
+ sp->internal_border - (sp->shadow_width * 2));
+#endif /* !PREFS_BUTTON */
+
+ x2 = x1 + ((sp->button_width - string_width(sp->button_font,sp->help_label))
+ / 2);
+ XFillRectangle (si->dpy, si->splash_dialog, gc2, x1, y1,
+ sp->button_width, sp->button_height);
+ XDrawString (si->dpy, si->splash_dialog, gc1, x2, y2,
+ sp->help_label, strlen(sp->help_label));
+ sp->help_button_x = x1;
+ sp->help_button_y = y1;
+
+
+ /* The logo
+ */
+ x1 = sp->shadow_width * 6;
+ y1 = sp->shadow_width * 6;
+ x2 = sp->logo_width - (sp->shadow_width * 12);
+ y2 = sp->logo_height - (sp->shadow_width * 12);
+
+ if (sp->logo_pixmap)
+ {
+ Window root;
+ int x, y;
+ unsigned int w, h, bw, d;
+ XGetGeometry (si->dpy, sp->logo_pixmap, &root, &x, &y, &w, &h, &bw, &d);
+ XSetForeground (si->dpy, gc1, sp->foreground);
+ XSetBackground (si->dpy, gc1, sp->background);
+ XSetClipMask (si->dpy, gc1, sp->logo_clipmask);
+ XSetClipOrigin (si->dpy, gc1, x1 + ((x2 - (int)w) /2), y1 + ((y2 - (int)h) / 2));
+ if (d == 1)
+ XCopyPlane (si->dpy, sp->logo_pixmap, si->splash_dialog, gc1,
+ 0, 0, w, h,
+ x1 + ((x2 - (int)w) / 2),
+ y1 + ((y2 - (int)h) / 2),
+ 1);
+ else
+ XCopyArea (si->dpy, sp->logo_pixmap, si->splash_dialog, gc1,
+ 0, 0, w, h,
+ x1 + ((x2 - (int)w) / 2),
+ y1 + ((y2 - (int)h) / 2));
+ }
+
+ /* Solid border inside the logo box. */
+#if 0
+ XSetForeground (si->dpy, gc1, sp->foreground);
+ XDrawRectangle (si->dpy, si->splash_dialog, gc1, x1, y1, x2-1, y2-1);
+#endif
+
+ /* The shadow around the logo
+ */
+ draw_shaded_rectangle (si->dpy, si->splash_dialog,
+ sp->shadow_width * 4,
+ sp->shadow_width * 4,
+ sp->logo_width - (sp->shadow_width * 8),
+ sp->logo_height - (sp->shadow_width * 8),
+ sp->shadow_width,
+ sp->shadow_bottom, sp->shadow_top);
+
+ /* The shadow around the whole window
+ */
+ draw_shaded_rectangle (si->dpy, si->splash_dialog,
+ 0, 0, sp->width, sp->height, sp->shadow_width,
+ sp->shadow_top, sp->shadow_bottom);
+
+ XFreeGC (si->dpy, gc1);
+ XFreeGC (si->dpy, gc2);
+
+ update_splash_window (si);
+}
+
+
+static void
+update_splash_window (saver_info *si)
+{
+ splash_dialog_data *sp = si->sp_data;
+ int pressed;
+ if (!sp) return;
+ pressed = sp->pressed;
+
+ /* The shadows around the buttons
+ */
+ draw_shaded_rectangle (si->dpy, si->splash_dialog,
+ sp->demo_button_x, sp->demo_button_y,
+ sp->button_width, sp->button_height, sp->shadow_width,
+ (pressed == 1 ? sp->shadow_bottom : sp->shadow_top),
+ (pressed == 1 ? sp->shadow_top : sp->shadow_bottom));
+#ifdef PREFS_BUTTON
+ draw_shaded_rectangle (si->dpy, si->splash_dialog,
+ sp->prefs_button_x, sp->prefs_button_y,
+ sp->button_width, sp->button_height, sp->shadow_width,
+ (pressed == 2 ? sp->shadow_bottom : sp->shadow_top),
+ (pressed == 2 ? sp->shadow_top : sp->shadow_bottom));
+#endif /* PREFS_BUTTON */
+ draw_shaded_rectangle (si->dpy, si->splash_dialog,
+ sp->help_button_x, sp->help_button_y,
+ sp->button_width, sp->button_height, sp->shadow_width,
+ (pressed == 3 ? sp->shadow_bottom : sp->shadow_top),
+ (pressed == 3 ? sp->shadow_top : sp->shadow_bottom));
+}
+
+static void
+destroy_splash_window (saver_info *si)
+{
+ splash_dialog_data *sp = si->sp_data;
+ saver_screen_info *ssi = sp->prompt_screen;
+ Colormap cmap = DefaultColormapOfScreen (ssi->screen);
+ Pixel black = BlackPixelOfScreen (ssi->screen);
+ Pixel white = WhitePixelOfScreen (ssi->screen);
+
+ if (sp->timer)
+ XtRemoveTimeOut (sp->timer);
+
+ if (si->splash_dialog)
+ {
+ XDestroyWindow (si->dpy, si->splash_dialog);
+ si->splash_dialog = 0;
+ }
+
+ if (sp->heading_label) free (sp->heading_label);
+ if (sp->body_label) free (sp->body_label);
+ if (sp->body2_label) free (sp->body2_label);
+ if (sp->body3_label) free (sp->body3_label);
+ if (sp->body4_label) free (sp->body4_label);
+ if (sp->demo_label) free (sp->demo_label);
+#ifdef PREFS_BUTTON
+ if (sp->prefs_label) free (sp->prefs_label);
+#endif /* PREFS_BUTTON */
+ if (sp->help_label) free (sp->help_label);
+
+ if (sp->heading_font) XFreeFont (si->dpy, sp->heading_font);
+ if (sp->body_font) XFreeFont (si->dpy, sp->body_font);
+ if (sp->button_font) XFreeFont (si->dpy, sp->button_font);
+
+ if (sp->foreground != black && sp->foreground != white)
+ XFreeColors (si->dpy, cmap, &sp->foreground, 1, 0L);
+ if (sp->background != black && sp->background != white)
+ XFreeColors (si->dpy, cmap, &sp->background, 1, 0L);
+ if (sp->button_foreground != black && sp->button_foreground != white)
+ XFreeColors (si->dpy, cmap, &sp->button_foreground, 1, 0L);
+ if (sp->button_background != black && sp->button_background != white)
+ XFreeColors (si->dpy, cmap, &sp->button_background, 1, 0L);
+ if (sp->shadow_top != black && sp->shadow_top != white)
+ XFreeColors (si->dpy, cmap, &sp->shadow_top, 1, 0L);
+ if (sp->shadow_bottom != black && sp->shadow_bottom != white)
+ XFreeColors (si->dpy, cmap, &sp->shadow_bottom, 1, 0L);
+
+ if (sp->logo_pixmap)
+ XFreePixmap (si->dpy, sp->logo_pixmap);
+ if (sp->logo_clipmask)
+ XFreePixmap (si->dpy, sp->logo_clipmask);
+ if (sp->logo_pixels)
+ {
+ if (sp->logo_npixels)
+ XFreeColors (si->dpy, cmap, sp->logo_pixels, sp->logo_npixels, 0L);
+ free (sp->logo_pixels);
+ sp->logo_pixels = 0;
+ sp->logo_npixels = 0;
+ }
+
+ memset (sp, 0, sizeof(*sp));
+ free (sp);
+ si->sp_data = 0;
+}
+
+void
+handle_splash_event (saver_info *si, XEvent *event)
+{
+ splash_dialog_data *sp = si->sp_data;
+ saver_screen_info *ssi;
+ int which = 0;
+ if (!sp) return;
+ ssi = sp->prompt_screen;
+
+ switch (event->xany.type)
+ {
+ case Expose:
+ draw_splash_window (si);
+ break;
+
+ case ButtonPress: case ButtonRelease:
+
+ if (event->xbutton.x >= sp->demo_button_x &&
+ event->xbutton.x < sp->demo_button_x + sp->button_width &&
+ event->xbutton.y >= sp->demo_button_y &&
+ event->xbutton.y < sp->demo_button_y + sp->button_height)
+ which = 1;
+
+#ifdef PREFS_BUTTON
+ else if (event->xbutton.x >= sp->prefs_button_x &&
+ event->xbutton.x < sp->prefs_button_x + sp->button_width &&
+ event->xbutton.y >= sp->prefs_button_y &&
+ event->xbutton.y < sp->prefs_button_y + sp->button_height)
+ which = 2;
+#endif /* PREFS_BUTTON */
+
+ else if (event->xbutton.x >= sp->help_button_x &&
+ event->xbutton.x < sp->help_button_x + sp->button_width &&
+ event->xbutton.y >= sp->help_button_y &&
+ event->xbutton.y < sp->help_button_y + sp->button_height)
+ which = 3;
+
+ if (event->xany.type == ButtonPress)
+ {
+ sp->pressed = which;
+ update_splash_window (si);
+ if (which == 0)
+ XBell (si->dpy, False);
+ }
+ else if (event->xany.type == ButtonRelease)
+ {
+ if (which && sp->pressed == which)
+ {
+ destroy_splash_window (si);
+ sp = si->sp_data;
+ switch (which)
+ {
+ case 1: do_demo (ssi); break;
+#ifdef PREFS_BUTTON
+ case 2: do_prefs (ssi); break;
+#endif /* PREFS_BUTTON */
+ case 3: do_help (ssi); break;
+ default: abort();
+ }
+ }
+ else if (which == 0 && sp->pressed == 0)
+ {
+ /* click and release on the window but not in a button:
+ treat that as "dismiss the splash dialog." */
+ destroy_splash_window (si);
+ sp = si->sp_data;
+ }
+ if (sp) sp->pressed = 0;
+ update_splash_window (si);
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+unsplash_timer (XtPointer closure, XtIntervalId *id)
+{
+ saver_info *si = (saver_info *) closure;
+ if (si && si->sp_data)
+ destroy_splash_window (si);
+}
+
+
+/* Button callbacks */
+
+#ifdef VMS
+# define pid_t int
+# define fork vfork
+#endif /* VMS */
+
+
+static void
+do_demo (saver_screen_info *ssi)
+{
+ saver_info *si = ssi->global;
+ saver_preferences *p = &si->prefs;
+ const char *cmd = p->demo_command;
+
+ if (cmd && *cmd)
+ fork_and_exec (ssi, cmd);
+ else
+ fprintf (stderr, "%s: no demo-mode command has been specified.\n",
+ blurb());
+}
+
+#ifdef PREFS_BUTTON
+static void
+do_prefs (saver_screen_info *ssi)
+{
+ saver_info *si = ssi->global;
+ saver_preferences *p = &si->prefs;
+ const char *cmd = p->prefs_command;
+
+ if (command && *command)
+ fork_and_exec (ssi, cmd);
+ else
+ fprintf (stderr, "%s: no preferences command has been specified.\n",
+ blurb());
+}
+#endif /* PREFS_BUTTON */
+
+static void
+do_help (saver_screen_info *ssi)
+{
+ saver_info *si = ssi->global;
+ saver_preferences *p = &si->prefs;
+ char *help_command = 0;
+
+ if (!p->load_url_command || !*p->load_url_command)
+ {
+ fprintf (stderr, "%s: no URL command has been specified.\n", blurb());
+ return;
+ }
+ if (!p->help_url || !*p->help_url)
+ {
+ fprintf (stderr, "%s: no Help URL has been specified.\n", blurb());
+ return;
+ }
+
+ help_command = (char *) malloc (strlen (p->load_url_command) +
+ (strlen (p->help_url) * 4) + 10);
+ sprintf (help_command, p->load_url_command,
+ p->help_url, p->help_url, p->help_url, p->help_url);
+
+ fork_and_exec (ssi, help_command);
+ free (help_command);
+}
diff --git a/driver/stderr.c b/driver/stderr.c
new file mode 100644
index 0000000..84fa697
--- /dev/null
+++ b/driver/stderr.c
@@ -0,0 +1,560 @@
+/* stderr.c --- capturing stdout/stderr output onto the screensaver window.
+ * xscreensaver, Copyright (c) 1991-2016 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.
+ */
+
+/* stderr hackery - Why Unix Sucks, reason number 32767.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdlib.h>
+
+#include <stdio.h>
+#include <time.h>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#ifdef HAVE_FCNTL
+# include <fcntl.h>
+#endif
+
+#include <X11/Intrinsic.h>
+
+#include "xscreensaver.h"
+#include "resources.h"
+#include "visual.h"
+
+FILE *real_stderr = 0;
+FILE *real_stdout = 0;
+
+
+/* It's ok for these to be global, since they refer to the one and only
+ stderr stream, not to a particular screen or window or visual.
+ */
+static char stderr_buffer [4096];
+static char *stderr_tail = 0;
+static time_t stderr_last_read = 0;
+
+static int stderr_stdout_read_fd = -1;
+
+static void make_stderr_overlay_window (saver_screen_info *);
+
+
+/* Recreates the stderr window or GCs: do this when the xscreensaver window
+ on a screen has been re-created.
+ */
+void
+reset_stderr (saver_screen_info *ssi)
+{
+ saver_info *si = ssi->global;
+
+ if (si->prefs.debug_p)
+ fprintf ((real_stderr ? real_stderr : stderr),
+ "%s: resetting stderr\n", blurb());
+
+ ssi->stderr_text_x = 0;
+ ssi->stderr_text_y = 0;
+
+ if (ssi->stderr_gc)
+ XFreeGC (si->dpy, ssi->stderr_gc);
+ ssi->stderr_gc = 0;
+
+ if (ssi->stderr_overlay_window)
+ XDestroyWindow(si->dpy, ssi->stderr_overlay_window);
+ ssi->stderr_overlay_window = 0;
+
+ if (ssi->stderr_cmap)
+ XFreeColormap(si->dpy, ssi->stderr_cmap);
+ ssi->stderr_cmap = 0;
+}
+
+/* Erases any stderr text overlaying the screen (if possible) and resets
+ the stderr output cursor to the upper left. Do this when the xscreensaver
+ window is cleared.
+ */
+void
+clear_stderr (saver_screen_info *ssi)
+{
+ saver_info *si = ssi->global;
+ ssi->stderr_text_x = 0;
+ ssi->stderr_text_y = 0;
+ if (ssi->stderr_overlay_window)
+ XClearWindow (si->dpy, ssi->stderr_overlay_window);
+}
+
+
+/* Draws the string on the screen's window.
+ */
+static void
+print_stderr_1 (saver_screen_info *ssi, char *string)
+{
+ saver_info *si = ssi->global;
+ Display *dpy = si->dpy;
+ Screen *screen = ssi->screen;
+ Window window = (ssi->stderr_overlay_window ?
+ ssi->stderr_overlay_window :
+ ssi->screensaver_window);
+ int h_border = 20;
+ int v_border = 20;
+ char *head = string;
+ char *tail;
+
+ if (! ssi->stderr_font)
+ {
+ char *font_name = get_string_resource (dpy, "font", "Font");
+ if (!font_name) font_name = strdup ("fixed");
+ ssi->stderr_font = XLoadQueryFont (dpy, font_name);
+ if (! ssi->stderr_font) ssi->stderr_font = XLoadQueryFont (dpy, "fixed");
+ ssi->stderr_line_height = (ssi->stderr_font->ascent +
+ ssi->stderr_font->descent);
+ free (font_name);
+ }
+
+ if (! ssi->stderr_gc)
+ {
+ XGCValues gcv;
+ Pixel fg, bg;
+ Colormap cmap = ssi->cmap;
+
+ if (!ssi->stderr_overlay_window &&
+ get_boolean_resource(dpy, "overlayStderr", "Boolean"))
+ {
+ make_stderr_overlay_window (ssi);
+ if (ssi->stderr_overlay_window)
+ window = ssi->stderr_overlay_window;
+ if (ssi->stderr_cmap)
+ cmap = ssi->stderr_cmap;
+ }
+
+ fg = get_pixel_resource (dpy,cmap,"overlayTextForeground","Foreground");
+ bg = get_pixel_resource (dpy,cmap,"overlayTextBackground","Background");
+ gcv.font = ssi->stderr_font->fid;
+ gcv.foreground = fg;
+ gcv.background = bg;
+ ssi->stderr_gc = XCreateGC (dpy, window,
+ (GCFont | GCForeground | GCBackground),
+ &gcv);
+ }
+
+
+ if (ssi->stderr_cmap)
+ XInstallColormap(si->dpy, ssi->stderr_cmap);
+
+ for (tail = string; *tail; tail++)
+ {
+ if (*tail == '\n' || *tail == '\r')
+ {
+ int maxy = HeightOfScreen (screen) - v_border - v_border;
+ if (tail != head)
+ XDrawImageString (dpy, window, ssi->stderr_gc,
+ ssi->stderr_text_x + h_border,
+ ssi->stderr_text_y + v_border +
+ ssi->stderr_font->ascent,
+ head, tail - head);
+ ssi->stderr_text_x = 0;
+ ssi->stderr_text_y += ssi->stderr_line_height;
+ head = tail + 1;
+ if (*tail == '\r' && *head == '\n')
+ head++, tail++;
+
+ if (ssi->stderr_text_y > maxy - ssi->stderr_line_height)
+ {
+#if 0
+ ssi->stderr_text_y = 0;
+#else
+ int offset = ssi->stderr_line_height * 5;
+ XWindowAttributes xgwa;
+ XGetWindowAttributes (dpy, window, &xgwa);
+
+ XCopyArea (dpy, window, window, ssi->stderr_gc,
+ 0, v_border + offset,
+ xgwa.width,
+ (xgwa.height - v_border - v_border - offset),
+ 0, v_border);
+ XClearArea (dpy, window,
+ 0, xgwa.height - v_border - offset,
+ xgwa.width, offset, False);
+ ssi->stderr_text_y -= offset;
+#endif
+ }
+ }
+ }
+ if (tail != head)
+ {
+ int direction, ascent, descent;
+ XCharStruct overall;
+ XDrawImageString (dpy, window, ssi->stderr_gc,
+ ssi->stderr_text_x + h_border,
+ ssi->stderr_text_y + v_border
+ + ssi->stderr_font->ascent,
+ head, tail - head);
+ XTextExtents (ssi->stderr_font, tail, tail - head,
+ &direction, &ascent, &descent, &overall);
+ ssi->stderr_text_x += overall.width;
+ }
+}
+
+static void
+make_stderr_overlay_window (saver_screen_info *ssi)
+{
+ saver_info *si = ssi->global;
+ unsigned long transparent_pixel = 0;
+ Visual *visual = get_overlay_visual (ssi->screen, &transparent_pixel);
+ if (visual)
+ {
+ int depth = visual_depth (ssi->screen, visual);
+ XSetWindowAttributes attrs;
+ XWindowAttributes xgwa;
+ unsigned long attrmask;
+ XGetWindowAttributes (si->dpy, ssi->screensaver_window, &xgwa);
+
+ if (si->prefs.debug_p)
+ fprintf(real_stderr,
+ "%s: using overlay visual 0x%0x for stderr text layer.\n",
+ blurb(), (int) XVisualIDFromVisual (visual));
+
+ ssi->stderr_cmap = XCreateColormap(si->dpy,
+ RootWindowOfScreen(ssi->screen),
+ visual, AllocNone);
+
+ attrmask = (CWColormap | CWBackPixel | CWBackingPixel | CWBorderPixel |
+ CWBackingStore | CWSaveUnder);
+ attrs.colormap = ssi->stderr_cmap;
+ attrs.background_pixel = transparent_pixel;
+ attrs.backing_pixel = transparent_pixel;
+ attrs.border_pixel = transparent_pixel;
+ attrs.backing_store = NotUseful;
+ attrs.save_under = False;
+
+ ssi->stderr_overlay_window =
+ XCreateWindow(si->dpy, ssi->screensaver_window, 0, 0,
+ xgwa.width, xgwa.height,
+ 0, depth, InputOutput, visual, attrmask, &attrs);
+ XMapRaised(si->dpy, ssi->stderr_overlay_window);
+ }
+}
+
+
+/* Draws the string on each screen's window as error text.
+ */
+static void
+print_stderr (saver_info *si, char *string)
+{
+ saver_preferences *p = &si->prefs;
+ int i;
+
+ /* In verbose mode, copy it to stderr as well. */
+ if (p->verbose_p)
+ fprintf (real_stderr, "%s", string);
+
+ for (i = 0; i < si->nscreens; i++)
+ print_stderr_1 (&si->screens[i], string);
+}
+
+
+/* Polls the stderr buffer every few seconds and if it finds any text,
+ writes it on all screens.
+ */
+static void
+stderr_popup_timer_fn (XtPointer closure, XtIntervalId *id)
+{
+ saver_info *si = (saver_info *) closure;
+ char *s = stderr_buffer;
+ if (*s)
+ {
+ /* If too much data was printed, then something has gone haywire,
+ so truncate it. */
+ char *trailer = "\n\n<< stderr diagnostics have been truncated >>\n\n";
+ int max = sizeof (stderr_buffer) - strlen (trailer) - 5;
+ if (strlen (s) > max)
+ strcpy (s + max, trailer);
+ /* Now show the user. */
+ print_stderr (si, s);
+ }
+
+ stderr_tail = stderr_buffer;
+ si->stderr_popup_timer = 0;
+}
+
+
+/* Called when data becomes available on the stderr pipe. Copies it into
+ stderr_buffer where stderr_popup_timer_fn() can find it later.
+ */
+static void
+stderr_callback (XtPointer closure, int *fd, XtIntervalId *id)
+{
+ saver_info *si = (saver_info *) closure;
+ char *s;
+ int left;
+ int size;
+ int read_this_time = 0;
+
+ if (!fd || *fd < 0 || *fd != stderr_stdout_read_fd)
+ abort();
+
+ if (stderr_tail == 0)
+ stderr_tail = stderr_buffer;
+
+ left = ((sizeof (stderr_buffer) - 2) - (stderr_tail - stderr_buffer));
+
+ s = stderr_tail;
+ *s = 0;
+
+ /* Read as much data from the fd as we can, up to our buffer size. */
+ if (left > 0)
+ {
+ while ((size = read (*fd, (void *) s, left)) > 0)
+ {
+ left -= size;
+ s += size;
+ read_this_time += size;
+ }
+ *s = 0;
+ }
+ else
+ {
+ char buf2 [1024];
+ /* The buffer is full; flush the rest of it. */
+ while (read (*fd, (void *) buf2, sizeof (buf2)) > 0)
+ ;
+ }
+
+ stderr_tail = s;
+ stderr_last_read = time ((time_t *) 0);
+
+ /* Now we have read some data that we would like to put up in a dialog
+ box. But more data may still be coming in - so don't pop up the
+ dialog right now, but instead, start a timer that will pop it up
+ a second from now. Should more data come in in the meantime, we
+ will be called again, and will reset that timer again. So the
+ dialog will only pop up when a second has elapsed with no new data
+ being written to stderr.
+
+ However, if the buffer is full (meaning lots of data has been written)
+ then we don't reset the timer.
+ */
+ if (read_this_time > 0)
+ {
+ if (si->stderr_popup_timer)
+ XtRemoveTimeOut (si->stderr_popup_timer);
+
+ si->stderr_popup_timer =
+ XtAppAddTimeOut (si->app, 1 * 1000, stderr_popup_timer_fn,
+ (XtPointer) si);
+ }
+}
+
+/* If stderr capturing is desired, this replaces `stdout' and `stderr'
+ with a pipe, so that any output written to them will show up on the
+ screen as well as on the original value of those streams.
+ */
+void
+initialize_stderr (saver_info *si)
+{
+ static Boolean done = False;
+ int fds [2];
+ int in, out;
+ int new_stdout, new_stderr;
+ int stdout_fd = 1;
+ int stderr_fd = 2;
+ int flags = 0;
+ Boolean stderr_dialog_p;
+
+ if (done) return;
+ done = True;
+
+ real_stderr = stderr;
+ real_stdout = stdout;
+
+ stderr_dialog_p = get_boolean_resource (si->dpy, "captureStderr", "Boolean");
+
+ if (!stderr_dialog_p)
+ return;
+
+ if (pipe (fds))
+ {
+ perror ("error creating pipe:");
+ return;
+ }
+
+ in = fds [0];
+ out = fds [1];
+
+# ifdef HAVE_FCNTL
+
+# if defined(O_NONBLOCK)
+ flags = O_NONBLOCK;
+# elif defined(O_NDELAY)
+ flags = O_NDELAY;
+# else
+ ERROR!! neither O_NONBLOCK nor O_NDELAY are defined.
+# endif
+
+ /* Set both sides of the pipe to nonblocking - this is so that
+ our reads (in stderr_callback) will terminate, and so that
+ out writes (in the client programs) will silently fail when
+ the pipe is full, instead of hosing the program. */
+ if (fcntl (in, F_SETFL, flags) != 0)
+ {
+ perror ("fcntl:");
+ return;
+ }
+ if (fcntl (out, F_SETFL, flags) != 0)
+ {
+ perror ("fcntl:");
+ return;
+ }
+
+# endif /* !HAVE_FCNTL */
+
+ if (stderr_dialog_p)
+ {
+ FILE *new_stderr_file;
+ FILE *new_stdout_file;
+
+ new_stderr = dup (stderr_fd);
+ if (new_stderr < 0)
+ {
+ perror ("could not dup() a stderr:");
+ return;
+ }
+ if (! (new_stderr_file = fdopen (new_stderr, "w")))
+ {
+ perror ("could not fdopen() the new stderr:");
+ return;
+ }
+ real_stderr = new_stderr_file;
+
+ close (stderr_fd);
+ if (dup2 (out, stderr_fd) < 0)
+ {
+ perror ("could not dup() a new stderr:");
+ return;
+ }
+
+
+ new_stdout = dup (stdout_fd);
+ if (new_stdout < 0)
+ {
+ perror ("could not dup() a stdout:");
+ return;
+ }
+ if (! (new_stdout_file = fdopen (new_stdout, "w")))
+ {
+ perror ("could not fdopen() the new stdout:");
+ return;
+ }
+ real_stdout = new_stdout_file;
+
+ close (stdout_fd);
+ if (dup2 (out, stdout_fd) < 0)
+ {
+ perror ("could not dup() a new stdout:");
+ return;
+ }
+ close (out);
+ }
+
+ stderr_stdout_read_fd = in;
+ XtAppAddInput (si->app, in, (XtPointer) XtInputReadMask, stderr_callback,
+ (XtPointer) si);
+}
+
+
+/* If the "-log file" command-line option has been specified,
+ open the file for append, and redirect stdout/stderr there.
+ This is called very early, before initialize_stderr().
+ */
+void
+stderr_log_file (saver_info *si)
+{
+ int stdout_fd = 1;
+ int stderr_fd = 2;
+ const char *filename = get_string_resource (si->dpy, "logFile", "LogFile");
+ int fd;
+
+ if (!filename || !*filename) return;
+
+ fd = open (filename, O_WRONLY | O_APPEND | O_CREAT, 0666);
+
+ if (fd < 0)
+ {
+ char buf[255];
+ FAIL:
+ sprintf (buf, "%.100s: %.100s", blurb(), filename);
+ perror (buf);
+ fflush (stderr);
+ fflush (stdout);
+ exit (1);
+ }
+
+ fprintf (stderr, "%s: logging to file %s\n", blurb(), filename);
+
+ if (dup2 (fd, stdout_fd) < 0) goto FAIL;
+ if (dup2 (fd, stderr_fd) < 0) goto FAIL;
+
+ fprintf (stderr, "\n\n"
+ "##########################################################################\n"
+ "%s: logging to \"%s\" at %s\n"
+ "##########################################################################\n"
+ "\n",
+ blurb(), filename, timestring(0));
+}
+
+
+/* If there is anything in the stderr buffer, flush it to the real stderr.
+ This does no X operations. Call this when exiting to make sure any
+ last words actually show up.
+ */
+void
+shutdown_stderr (saver_info *si)
+{
+ fflush (stdout);
+ fflush (stderr);
+
+ if (!real_stderr || stderr_stdout_read_fd < 0)
+ return;
+
+ stderr_callback ((XtPointer) si, &stderr_stdout_read_fd, 0);
+
+ if (stderr_tail &&
+ stderr_buffer < stderr_tail)
+ {
+ *stderr_tail = 0;
+ fprintf (real_stderr, "%s", stderr_buffer);
+ stderr_tail = stderr_buffer;
+ }
+
+ if (real_stdout) fflush (real_stdout);
+ if (real_stderr) fflush (real_stderr);
+
+ if (stdout != real_stdout)
+ {
+ dup2 (fileno(real_stdout), fileno(stdout));
+ fclose (real_stdout);
+ real_stdout = stdout;
+ }
+ if (stderr != real_stderr)
+ {
+ dup2 (fileno(real_stderr), fileno(stderr));
+ fclose (real_stderr);
+ real_stderr = stderr;
+ }
+ if (stderr_stdout_read_fd != -1)
+ {
+ close (stderr_stdout_read_fd);
+ stderr_stdout_read_fd = -1;
+ }
+}
diff --git a/driver/subprocs.c b/driver/subprocs.c
new file mode 100644
index 0000000..4f327e9
--- /dev/null
+++ b/driver/subprocs.c
@@ -0,0 +1,1423 @@
+/* subprocs.c --- choosing, spawning, and killing screenhacks.
+ * xscreensaver, Copyright (c) 1991-2017 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 <ctype.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <X11/Xlib.h> /* not used for much... */
+
+#ifndef ESRCH
+# include <errno.h>
+#endif
+
+#include <sys/time.h> /* sys/resource.h needs this for timeval */
+#include <sys/param.h> /* for PATH_MAX */
+
+#ifdef HAVE_SYS_WAIT_H
+# include <sys/wait.h> /* for waitpid() and associated macros */
+#endif
+
+#ifdef HAVE_SETRLIMIT
+# 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
+#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);
+ }
+
+#endif /* HAVE_SETRLIMIT && RLIMIT_AS */
+}
+
+
+/* Management of child processes, and de-zombification.
+ */
+
+enum job_status {
+ job_running, /* the process is still alive */
+ job_stopped, /* we have sent it a STOP signal */
+ job_killed, /* we have sent it a TERM signal */
+ job_dead /* we have wait()ed for it, and it's dead -- this state only
+ occurs so that we can avoid calling free() from a signal
+ handler. Shortly after going into this state, the list
+ element will be removed. */
+};
+
+struct screenhack_job {
+ char *name;
+ pid_t pid;
+ int screen;
+ enum job_status status;
+ time_t launched, killed;
+ struct screenhack_job *next;
+};
+
+static struct screenhack_job *jobs = 0;
+
+/* for debugging -- nothing calls this, but it's useful to invoke from gdb.
+ */
+void show_job_list (void);
+
+void
+show_job_list (void)
+{
+ struct screenhack_job *job;
+ 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, "\n");
+}
+
+
+static void clean_job_list (void);
+
+static struct screenhack_job *
+make_job (pid_t pid, int screen, const char *cmd)
+{
+ struct screenhack_job *job = (struct screenhack_job *) malloc (sizeof(*job));
+
+ static char name [1024];
+ const char *in = cmd;
+ char *out = name;
+ int got_eq = 0;
+
+ clean_job_list();
+
+ AGAIN:
+ while (isspace(*in)) in++; /* skip whitespace */
+ while (!isspace(*in) && *in != ':') {
+ if (*in == '=') got_eq = 1;
+ *out++ = *in++; /* snarf first token */
+ }
+
+ if (got_eq) /* if the first token was FOO=bar */
+ { /* then get the next token instead. */
+ got_eq = 0;
+ out = name;
+ goto AGAIN;
+ }
+
+ while (isspace(*in)) in++; /* skip whitespace */
+ *out = 0;
+
+ job->name = strdup(name);
+ job->pid = pid;
+ job->screen = screen;
+ job->status = job_running;
+ job->launched = time ((time_t *) 0);
+ job->killed = 0;
+ job->next = jobs;
+ jobs = job;
+
+ return jobs;
+}
+
+
+static void
+free_job (struct screenhack_job *job)
+{
+ if (!job)
+ return;
+ else if (job == jobs)
+ jobs = jobs->next;
+ else
+ {
+ struct screenhack_job *job2, *prev;
+ for (prev = 0, job2 = jobs;
+ job2;
+ prev = job2, job2 = job2->next)
+ if (job2 == job)
+ {
+ prev->next = job->next;
+ break;
+ }
+ }
+ free(job->name);
+ free(job);
+}
+
+
+/* Cleans out dead jobs from the jobs list -- this must only be called
+ from the main thread, not from a signal handler.
+ */
+static void
+clean_job_list (void)
+{
+ struct screenhack_job *job, *prev, *next;
+ time_t now = time ((time_t *) 0);
+ static time_t last_warn = 0;
+ Bool warnedp = False;
+
+ for (prev = 0, job = jobs, next = (job ? job->next : 0);
+ job;
+ prev = job, job = next, next = (job ? job->next : 0))
+ {
+ if (job->status == job_dead)
+ {
+ if (prev)
+ prev->next = next;
+ free_job (job);
+ job = prev;
+ }
+ else if (job->status == job_killed &&
+ now - job->killed > 10 &&
+ now - last_warn > 10)
+ {
+ fprintf (stderr,
+ "%s: WARNING: pid %ld (%s) sent SIGTERM %ld seconds ago"
+ " and did not die!\n",
+ blurb(),
+ (long) job->pid,
+ job->name,
+ (long) (now - job->killed));
+ warnedp = True;
+ }
+ }
+ if (warnedp) last_warn = now;
+}
+
+
+static struct screenhack_job *
+find_job (pid_t pid)
+{
+ struct screenhack_job *job;
+ for (job = jobs; job; job = job->next)
+ if (job->pid == pid)
+ return job;
+ 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--;
+}
+
+static int
+kill_job (saver_info *si, pid_t pid, int signal)
+{
+ saver_preferences *p = &si->prefs;
+ struct screenhack_job *job;
+ int status = -1;
+
+ 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 ||
+ job->status == job_killed)
+ {
+ if (p->verbose_p)
+ fprintf (stderr, "%s: no child %ld to signal!\n",
+ blurb(), (long) pid);
+ goto DONE;
+ }
+
+ switch (signal) {
+ case SIGTERM:
+ job->status = job_killed;
+ 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 */
+ default: abort();
+ }
+
+ if (p->verbose_p)
+ fprintf (stderr, "%s: %d: %s pid %lu (%s)\n",
+ blurb(), job->screen,
+ (job->status == job_killed ? "killing" :
+ job->status == job_stopped ? "suspending" : "resuming"),
+ (unsigned long) job->pid,
+ job->name);
+
+ status = kill (job->pid, signal);
+
+ if (p->verbose_p && status < 0)
+ {
+ if (errno == ESRCH)
+ fprintf (stderr,
+ "%s: %d: child process %lu (%s) was already dead.\n",
+ blurb(), job->screen, (unsigned long) job->pid, job->name);
+ else
+ {
+ char buf [1024];
+ sprintf (buf, "%s: %d: couldn't kill child process %lu (%s)",
+ blurb(), job->screen, (unsigned long) job->pid, job->name);
+ perror (buf);
+ }
+ }
+
+ 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)
+{
+ saver_info *si = global_si_kludge; /* I hate C so much... */
+ in_signal_handler_p++;
+
+ if (si->prefs.debug_p)
+ {
+ /* 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");
+ }
+
+ if (block_sigchld_handler < 0)
+ abort();
+ else if (block_sigchld_handler == 0)
+ {
+ block_sigchld();
+ await_dying_children (si);
+ unblock_sigchld();
+ }
+
+ init_sigchld();
+ in_signal_handler_p--;
+}
+#endif /* SIGCHLD */
+
+
+#ifndef VMS
+
+static void
+await_dying_children (saver_info *si)
+{
+ while (1)
+ {
+ int wait_status = 0;
+ pid_t kid;
+
+ errno = 0;
+ kid = waitpid (-1, &wait_status, WNOHANG|WUNTRACED);
+
+ 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");
+ }
+ 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");
+ }
+ }
+
+ /* 0 means no more children to reap.
+ -1 means error -- except "interrupted system call" isn't a "real"
+ error, so if we get that, we should just try again. */
+ if (kid == 0 ||
+ (kid < 0 && errno != EINTR))
+ break;
+
+ describe_dead_child (si, kid, wait_status);
+ }
+}
+
+
+static void
+describe_dead_child (saver_info *si, pid_t kid, int wait_status)
+{
+ 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;
+
+ 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");
+ }
+
+ if (job)
+ job->status = job_dead;
+ }
+ else if (WIFSIGNALED (wait_status))
+ {
+ if (p->verbose_p ||
+ !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");
+ }
+
+ 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");
+ }
+
+ if (job)
+ job->status = job_stopped;
+ }
+ else
+ {
+ /* Don't call fprintf() from signal handlers, as it might malloc.
+ 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.
+ */
+ 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;
+ }
+}
+
+#else /* VMS */
+static void await_dying_children (saver_info *si) { return; }
+#endif /* VMS */
+
+
+void
+init_sigchld (void)
+{
+#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;
+
+ action.sa_handler = sigchld_handler;
+ sigemptyset(&action.sa_mask);
+ action.sa_flags = 0;
+
+ 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 */
+}
+
+
+
+
+
+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;
+}
+
+
+static void
+print_path_error (const char *program)
+{
+ 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")))
+ {
+# 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");
+ }
+}
+
+
+/* Executes the command in another process.
+ Command may be any single command acceptable to /bin/sh.
+ It may include wildcards, but no semicolons.
+ If successful, the pid of the other process is returned.
+ Otherwise, -1 is returned and an error may have been
+ printed to stderr.
+ */
+pid_t
+fork_and_exec (saver_screen_info *ssi, const char *command)
+{
+ saver_info *si = ssi->global;
+ saver_preferences *p = &si->prefs;
+ pid_t forked;
+
+ switch ((int) (forked = fork ()))
+ {
+ case -1:
+ {
+ char buf [255];
+ sprintf (buf, "%s: couldn't fork", blurb());
+ perror (buf);
+ break;
+ }
+
+ case 0:
+ close (ConnectionNumber (si->dpy)); /* close display fd */
+ limit_subproc_memory (p->inferior_memory_limit, p->verbose_p);
+ hack_subproc_environment (ssi->screen, ssi->screensaver_window);
+
+ if (p->verbose_p)
+ fprintf (stderr, "%s: %d: spawning \"%s\" in pid %lu.\n",
+ blurb(), ssi->number, 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);
+
+ exit (1); /* exits child fork */
+ break;
+
+ default: /* parent */
+ (void) make_job (forked, ssi->number, command);
+ break;
+ }
+
+ return forked;
+}
+
+
+void
+spawn_screenhack (saver_screen_info *ssi)
+{
+ saver_info *si = ssi->global;
+ saver_preferences *p = &si->prefs;
+ XFlush (si->dpy);
+
+ if (!monitor_powered_on_p (si))
+ {
+ if (si->prefs.verbose_p)
+ fprintf (stderr,
+ "%s: %d: X says monitor has powered down; "
+ "not launching a hack.\n", blurb(), ssi->number);
+ return;
+ }
+
+ if (p->screenhacks_count)
+ {
+ screenhack *hack;
+ pid_t forked;
+ char buf [255];
+ int new_hack = -1;
+ int retry_count = 0;
+ Bool force = False;
+
+ AGAIN:
+
+ if (p->screenhacks_count < 1)
+ {
+ /* No hacks at all */
+ new_hack = -1;
+ }
+ else if (p->screenhacks_count == 1)
+ {
+ /* Exactly one hack in the list */
+ new_hack = 0;
+ }
+ else if (si->selection_mode == -1)
+ {
+ /* Select the next hack, wrapping. */
+ new_hack = (ssi->current_hack + 1) % p->screenhacks_count;
+ }
+ else if (si->selection_mode == -2)
+ {
+ /* Select the previous hack, wrapping. */
+ if (ssi->current_hack < 0)
+ new_hack = p->screenhacks_count - 1;
+ else
+ new_hack = ((ssi->current_hack + p->screenhacks_count - 1)
+ % p->screenhacks_count);
+ }
+ else if (si->selection_mode > 0)
+ {
+ /* Select a specific hack, by number (via the ACTIVATE command.) */
+ new_hack = ((si->selection_mode - 1) % p->screenhacks_count);
+ force = True;
+ }
+ else if (p->mode == ONE_HACK &&
+ p->selected_hack >= 0)
+ {
+ /* Select a specific hack, by number (via "One Saver" mode.) */
+ new_hack = p->selected_hack;
+ force = True;
+ }
+ else if (p->mode == BLANK_ONLY || p->mode == DONT_BLANK)
+ {
+ new_hack = -1;
+ }
+ else if (p->mode == RANDOM_HACKS_SAME &&
+ ssi->number != 0)
+ {
+ /* Use the same hack that's running on screen 0.
+ (Assumes this function was called on screen 0 first.)
+ */
+ new_hack = si->screens[0].current_hack;
+ }
+ else /* (p->mode == RANDOM_HACKS) */
+ {
+ /* Select a random hack (but not the one we just ran.) */
+ while ((new_hack = random () % p->screenhacks_count)
+ == ssi->current_hack)
+ ;
+ }
+
+ if (new_hack < 0) /* don't run a hack */
+ {
+ ssi->current_hack = -1;
+ if (si->selection_mode < 0)
+ si->selection_mode = 0;
+ return;
+ }
+
+ ssi->current_hack = new_hack;
+ hack = p->screenhacks[ssi->current_hack];
+
+ /* If the hack is disabled, or there is no visual for this hack,
+ then try again (move forward, or backward, or re-randomize.)
+ Unless this hack was specified explicitly, in which case,
+ use it regardless.
+ */
+ if (force)
+ select_visual_of_hack (ssi, hack);
+
+ if (!force &&
+ (!hack->enabled_p ||
+ !on_path_p (hack->command) ||
+ !select_visual_of_hack (ssi, hack)))
+ {
+ if (++retry_count > (p->screenhacks_count*4))
+ {
+ /* Uh, oops. Odds are, there are no suitable visuals,
+ and we're looping. Give up. (This is totally lame,
+ what we should do is make a list of suitable hacks at
+ the beginning, then only loop over them.)
+ */
+ if (p->verbose_p)
+ fprintf(stderr,
+ "%s: %d: no programs enabled, or no suitable visuals.\n",
+ blurb(), ssi->number);
+ return;
+ }
+ else
+ 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)
+ {
+ case -1: /* fork failed */
+ 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");
+ break;
+
+ default:
+ ssi->pid = forked;
+ break;
+ }
+ }
+
+ store_saver_status (si); /* store current hack number */
+}
+
+
+void
+kill_screenhack (saver_screen_info *ssi)
+{
+ saver_info *si = ssi->global;
+ 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 */
+}
+
+
+/* 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)
+{
+ Bool any_running_p = False;
+ int i;
+ for (i = 0; i < si->nscreens; i++)
+ {
+ saver_screen_info *ssi = &si->screens[i];
+ if (ssi->pid) 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.
+ */
+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)
+{
+ pid_t forked;
+ int fds [2];
+ int in, out;
+ int errfds[2];
+ int errin = -1, errout = -1;
+ char buf[1024];
+
+ char *av[10];
+ int ac = 0;
+
+ av[ac++] = "xscreensaver-gl-helper";
+ av[ac] = 0;
+
+ if (pipe (fds))
+ {
+ perror ("error creating pipe:");
+ return 0;
+ }
+
+ in = fds [0];
+ out = fds [1];
+
+ if (!si->prefs.verbose_p)
+ {
+ if (pipe (errfds))
+ {
+ perror ("error creating pipe:");
+ return 0;
+ }
+
+ errin = errfds [0];
+ 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);
+ }
+ case 0:
+ {
+ close (in); /* don't need this one */
+ close (ConnectionNumber (si->dpy)); /* close display fd */
+
+ if (dup2 (out, STDOUT_FILENO) < 0) /* pipe stdout */
+ {
+ perror ("could not dup() a new stdout:");
+ return 0;
+ }
+
+ if (! si->prefs.verbose_p)
+ {
+ close(errin);
+ if (dup2 (errout, STDERR_FILENO) < 0)
+ {
+ perror ("could not dup() a new stderr:");
+ return 0;
+ }
+ }
+
+ hack_subproc_environment (screen, 0); /* set $DISPLAY */
+
+ execvp (av[0], av); /* shouldn't return. */
+
+ if (errno != ENOENT /* || si->prefs.verbose_p */ )
+ {
+ /* Ignore "no such file or directory" errors.
+ Issue all other exec errors, though. */
+ sprintf (buf, "%s: running %s", blurb(), av[0]);
+ perror (buf);
+ }
+ exit (1); /* exits fork */
+ break;
+ }
+ default:
+ {
+ int result = 0;
+ int wait_status = 0;
+ pid_t pid = -1;
+
+ FILE *f = fdopen (in, "r");
+ unsigned long v = 0;
+ char c;
+
+ close (out); /* don't need this one */
+
+ *buf = 0;
+ if (! fgets (buf, sizeof(buf)-1, f))
+ *buf = 0;
+ fclose (f);
+
+ if (! si->prefs.verbose_p)
+ {
+ close (errout);
+ close (errin);
+ }
+
+ /* 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. */
+
+ if (1 == sscanf (buf, "0x%lx %c", &v, &c))
+ result = (int) v;
+
+ if (result == 0)
+ {
+ if (si->prefs.verbose_p)
+ {
+ int L = strlen(buf);
+ fprintf (stderr, "%s: %s did not report a GL visual!\n",
+ blurb(), av[0]);
+
+ if (L && buf[L-1] == '\n')
+ buf[--L] = 0;
+ if (*buf)
+ fprintf (stderr, "%s: %s said: \"%s\"\n",
+ blurb(), av[0], buf);
+ }
+ return 0;
+ }
+ else
+ {
+ Visual *v = id_to_visual (screen, result);
+ if (si->prefs.verbose_p)
+ fprintf (stderr, "%s: %d: %s: GL visual is 0x%X%s.\n",
+ blurb(), screen_number (screen),
+ av[0], result,
+ (v == DefaultVisualOfScreen (screen)
+ ? " (default)" : ""));
+ return v;
+ }
+ }
+ }
+
+ 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-apm.c b/driver/test-apm.c
new file mode 100644
index 0000000..6b87c7e
--- /dev/null
+++ b/driver/test-apm.c
@@ -0,0 +1,101 @@
+/* test-apm.c --- playing with the APM library.
+ * xscreensaver, Copyright (c) 1999 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/Intrinsic.h>
+
+#include <apm.h>
+
+#define countof(x) (sizeof((x))/sizeof(*(x)))
+
+
+char *progname = 0;
+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 void
+apm_cb (XtPointer closure, int *fd, XtInputId *id)
+{
+ apm_event_t events[100];
+ int n, i;
+ while ((n = apm_get_events (*fd, 0, events, countof(events)))
+ > 0)
+ for (i = 0; i < n; i++)
+ {
+ fprintf (stderr, "%s: APM event 0x%x: %s.\n", blurb(),
+ events[i], apm_event_name (events[i]));
+#if 0
+ switch (events[i])
+ {
+ case APM_SYS_STANDBY:
+ case APM_USER_STANDBY:
+ case APM_SYS_SUSPEND:
+ case APM_USER_SUSPEND:
+ case APM_CRITICAL_SUSPEND:
+ break;
+ }
+#endif
+ }
+}
+
+int
+main (int argc, char **argv)
+{
+ XtAppContext app;
+ Widget toplevel_shell = XtAppInitialize (&app, progclass, 0, 0,
+ &argc, argv, 0, 0, 0);
+ Display *dpy = XtDisplay (toplevel_shell);
+ int fd;
+ XtInputId id;
+ XtGetApplicationNameAndClass (dpy, &progname, &progclass);
+
+ fd = apm_open ();
+ if (fd <= 0)
+ {
+ fprintf (stderr, "%s: couldn't initialize APM.\n", blurb());
+ exit (1);
+ }
+
+ id = XtAppAddInput(app, fd,
+ (XtPointer) (XtInputReadMask | XtInputWriteMask),
+ apm_cb, 0);
+ XtAppMainLoop (app);
+ exit (0);
+}
diff --git a/driver/test-fade.c b/driver/test-fade.c
new file mode 100644
index 0000000..9db773d
--- /dev/null
+++ b/driver/test-fade.c
@@ -0,0 +1,123 @@
+/* test-fade.c --- playing with colormap and/or gamma fading.
+ * xscreensaver, Copyright (c) 2001, 2004 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 <X11/Intrinsic.h>
+#include "xscreensaver.h"
+#include "fade.h"
+
+#ifdef HAVE_SGI_VC_EXTENSION
+# include <X11/extensions/XSGIvc.h>
+#endif
+#ifdef HAVE_XF86VMODE_GAMMA
+# include <X11/extensions/xf86vmode.h>
+#endif
+
+XrmDatabase db = 0;
+char *progname = 0;
+char *progclass = "XScreenSaver";
+
+#define SGI_VC_NAME "SGI-VIDEO-CONTROL"
+#define XF86_VIDMODE_NAME "XFree86-VidModeExtension"
+
+int
+main (int argc, char **argv)
+{
+ int seconds = 3;
+ int ticks = 20;
+ int delay = 1;
+
+ int op, event, error, major, minor;
+
+ XtAppContext app;
+ 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);
+ db = XtDatabase (dpy);
+
+ current_maps = (Colormap *) calloc(sizeof(Colormap), ScreenCount(dpy));
+ for (i = 0; i < ScreenCount(dpy); i++)
+ current_maps[i] = DefaultColormap (dpy, i);
+
+ 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);
+ else
+ {
+# ifdef HAVE_XF86VMODE_GAMMA
+ if (!XF86VidModeQueryVersion (dpy, &major, &minor))
+ fprintf(stderr, "%s: unable to get " XF86_VIDMODE_NAME " version\n",
+ progname);
+ else
+ fprintf(stderr, "%s: " XF86_VIDMODE_NAME " version %d.%d\n",
+ progname, major, minor);
+# else /* !HAVE_XF86VMODE_GAMMA */
+ fprintf(stderr, "%s: no support for display's " XF86_VIDMODE_NAME
+ " extension\n", progname);
+# endif /* !HAVE_XF86VMODE_GAMMA */
+ }
+
+ fprintf (stderr, "%s: fading %d screen%s\n",
+ progname, ScreenCount(dpy), ScreenCount(dpy) == 1 ? "" : "s");
+
+ while (1)
+ {
+ XSync (dpy, False);
+
+ fprintf(stderr, "%s: out...", progname);
+ fflush(stderr);
+ fade_screens (dpy, current_maps, 0, 0, seconds, ticks, True, False);
+ fprintf(stderr, "done.\n");
+ fflush(stderr);
+
+ if (delay) sleep (delay);
+
+ fprintf(stderr,"%s: in...", progname);
+ fflush(stderr);
+ fade_screens (dpy, current_maps, 0, 0, seconds, ticks, False, False);
+ fprintf(stderr, "done.\n");
+ fflush(stderr);
+
+ if (delay) sleep (delay);
+ }
+}
diff --git a/driver/test-grab.c b/driver/test-grab.c
new file mode 100644
index 0000000..03018eb
--- /dev/null
+++ b/driver/test-grab.c
@@ -0,0 +1,89 @@
+/* test-uid.c --- playing with grabs.
+ * xscreensaver, Copyright (c) 1999, 2004 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 <sys/time.h>
+
+#include <X11/Xlib.h>
+#include <X11/Intrinsic.h>
+
+char *progname = 0;
+char *progclass = "XScreenSaver";
+
+#define ALL_POINTER_EVENTS \
+ (ButtonPressMask | ButtonReleaseMask | EnterWindowMask | \
+ LeaveWindowMask | PointerMotionMask | PointerMotionHintMask | \
+ Button1MotionMask | Button2MotionMask | Button3MotionMask | \
+ Button4MotionMask | Button5MotionMask | ButtonMotionMask)
+
+int
+main (int argc, char **argv)
+{
+ XtAppContext app;
+ int kstatus, mstatus;
+ Cursor cursor = 0;
+ int delay = 60 * 15;
+ 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" :
+ "???"));
+
+ XSync(dpy, False);
+
+ if (kstatus == GrabSuccess || mstatus == GrabSuccess)
+ {
+ 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);
+ }
+
+ exit (0);
+}
diff --git a/driver/test-mlstring.c b/driver/test-mlstring.c
new file mode 100644
index 0000000..e269a00
--- /dev/null
+++ b/driver/test-mlstring.c
@@ -0,0 +1,312 @@
+/*
+ * (c) 2007, Quest Software, Inc. All rights reserved.
+ *
+ * This file is part of XScreenSaver,
+ * Copyright (c) 1993-2004 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.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "mlstring.c" /* hokey, but whatever */
+
+#define WRAP_WIDTH_PX 100
+
+#undef Bool
+#undef True
+#undef False
+typedef int Bool;
+#define True 1
+#define False 0
+
+#define SKIPPED -1
+#define SUCCESS 0
+#define FAILURE 1
+
+#define FAIL(msg, ...) \
+ do { \
+ ++failcount; \
+ fprintf(stderr, "[FAIL] "); \
+ fprintf(stderr, msg, __VA_ARGS__); \
+ putc('\n', stderr); \
+ return FAILURE; \
+ } while (0)
+
+#define SUCCEED(testname) \
+ do { \
+ fprintf(stderr, "[SUCCESS] %s\n", (testname)); \
+ } while (0)
+
+#define SKIP(testname) \
+ do { \
+ fprintf(stderr, "[SKIPPED] %s\n", (testname)); \
+ } while (0)
+
+extern mlstring* mlstring_allocate(const char *msg);
+extern void mlstring_wrap(mlstring *mstr, XFontStruct *font, Dimension width);
+
+static int failcount = 0;
+
+static char *mlstring_to_cstr(const mlstring *mlstr) {
+ char *cstr;
+ size_t cstrlen = 0, alloclen = 1024;
+ const struct mlstr_line *line;
+
+ cstr = malloc(alloclen);
+ if (!cstr)
+ return NULL;
+ cstr[0] = '\0';
+
+ for (line = mlstr->lines; line; line = line->next_line) {
+ /* Extend the buffer if necessary. */
+ if (cstrlen + strlen(line->line) + 1 > alloclen) {
+ cstr = realloc(cstr, alloclen *= 2);
+ if (!cstr)
+ return NULL;
+ }
+
+ /* If this is not the first line */
+ if (line != mlstr->lines) {
+ /* Append a newline character */
+ cstr[cstrlen] = '\n';
+ ++cstrlen;
+ cstr[cstrlen] = '\0';
+ }
+
+ strcat(cstr, line->line);
+ cstrlen += strlen(line->line);
+ }
+ return cstr;
+}
+
+/* Pass -1 for expect_min or expect_exact to not check that value.
+ * expect_empty_p means an empty line is expected at some point in the string.
+ * Also ensures that the string was not too wide after wrapping. */
+static int mlstring_expect_lines(const mlstring *mlstr, int expect_min, int expect_exact, Bool expect_empty_p)
+{
+ int count;
+ Bool got_empty_line = False;
+ const struct mlstr_line *line = mlstr->lines;
+
+ for (count = 0; line; line = line->next_line) {
+ if (line->line[0] == '\0') {
+ if (!expect_empty_p)
+ FAIL("Not expecting empty lines, but got one on line %d of [%s]", count + 1, mlstring_to_cstr(mlstr));
+ got_empty_line = True;
+ }
+ ++count;
+ }
+
+ if (expect_empty_p && !got_empty_line)
+ FAIL("Expecting an empty line, but none found in [%s]", mlstring_to_cstr(mlstr));
+
+ if (expect_exact != -1 && expect_exact != count)
+ FAIL("Expected %d lines, got %d", expect_exact, count);
+
+ if (expect_min != -1 && count < expect_min)
+ FAIL("Expected at least %d lines, got %d", expect_min, count);
+
+ return SUCCESS;
+}
+
+static int mlstring_expect(const char *msg, int expect_lines, const mlstring *mlstr, Bool expect_empty_p)
+{
+ char *str, *str_top;
+ const struct mlstr_line *cur;
+ int linecount = 0;
+
+ /* Duplicate msg so we can chop it up */
+ str_top = strdup(msg);
+ if (!str_top)
+ return SKIPPED;
+
+ /* Replace all newlines with NUL */
+ str = str_top;
+ while ((str = strchr(str, '\n')))
+ *str++ = '\0';
+
+ /* str is now used to point to the expected string */
+ str = str_top;
+
+ for (cur = mlstr->lines; cur; cur = cur->next_line)
+ {
+ ++linecount;
+ if (strcmp(cur->line, str))
+ FAIL("lines didn't match; expected [%s], got [%s]", str, cur->line);
+
+ str += strlen(str) + 1; /* Point to the next expected string */
+ }
+
+ free(str_top);
+
+ return mlstring_expect_lines(mlstr, -1, expect_lines, expect_empty_p);
+}
+
+/* Ensures that the width has been set properly after wrapping */
+static int check_width(const char *msg, const mlstring *mlstr) {
+ if (mlstr->overall_width == 0)
+ FAIL("Overall width was zero for string [%s]", msg);
+
+ if (mlstr->overall_width > WRAP_WIDTH_PX)
+ FAIL("Overall width was %hu but the maximum wrap width was %d", mlstr->overall_width, WRAP_WIDTH_PX);
+
+ return SUCCESS;
+}
+
+/* FAIL() actually returns the wrong return codes in main, but it
+ * prints a message which is what we want. */
+
+#define TRY_NEW(str, numl, expect_empty) \
+ do { \
+ mlstr = mlstring_allocate((str)); \
+ if (!mlstr) \
+ FAIL("%s", #str); \
+ if (SUCCESS == mlstring_expect((str), (numl), mlstr, (expect_empty))) \
+ SUCCEED(#str); \
+ free(mlstr); \
+ } while (0)
+
+/* Expects an XFontStruct* font, and tries to wrap to 100px */
+#define TRY_WRAP(str, minl, expect_empty) \
+ do { \
+ mltest = mlstring_allocate((str)); \
+ if (!mltest) \
+ SKIP(#str); \
+ else { \
+ mlstring_wrap(mltest, font, WRAP_WIDTH_PX); \
+ check_width((str), mltest); \
+ if (SUCCESS == mlstring_expect_lines(mltest, (minl), -1, (expect_empty))) \
+ SUCCEED(#str); \
+ free(mltest); \
+ mltest = NULL; \
+ } \
+ } while (0)
+
+
+/* Ideally this function would use stub functions rather than real Xlib.
+ * Then it would be possible to test for exact line counts, which would be
+ * more reliable.
+ * It also doesn't handle Xlib errors.
+ *
+ * Don't print anything based on the return value of this function, it only
+ * returns a value so that I can use the FAIL() macro without warning.
+ *
+ * Anyone who understands this function wins a cookie ;)
+ */
+static int test_wrapping(void)
+{
+ Display *dpy = NULL;
+ XFontStruct *font = NULL;
+ mlstring *mltest = NULL;
+ int ok = 0;
+ int chars_per_line, chars_first_word, i;
+
+ const char *test_short = "a";
+ const char *test_hardwrap = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+ const char *test_withnewlines = "a\nb";
+ char *test_softwrap = NULL;
+
+ dpy = XOpenDisplay(NULL);
+ if (!dpy)
+ goto end;
+
+ font = XLoadQueryFont(dpy, "fixed");
+ if (!font)
+ goto end;
+
+ TRY_WRAP(test_short, 1, False);
+ TRY_WRAP(test_hardwrap, 2, False);
+ TRY_WRAP(test_withnewlines, 2, False);
+
+ /* See if wrapping splits on word boundaries like it should */
+ chars_per_line = WRAP_WIDTH_PX / font->max_bounds.width;
+ if (chars_per_line < 3)
+ goto end;
+
+ /* Allocate for 2 lines + \0 */
+ test_softwrap = malloc(chars_per_line * 2 + 1);
+ if (!test_softwrap)
+ goto end;
+
+ /* 2 = strlen(' a'); that is, the minimum space required to start a new word
+ * on the same line. */
+ chars_first_word = chars_per_line - 2;
+
+ for (i = 0; i < chars_first_word; ++i) {
+ test_softwrap[i] = 'a'; /* first word */
+ test_softwrap[i + chars_per_line] = 'b'; /* second word */
+ }
+ /* space between first & second words */
+ test_softwrap[chars_first_word] = ' ';
+ /* first char of second word (last char of first line) */
+ test_softwrap[chars_first_word + 1] = 'b';
+ /* after second word */
+ test_softwrap[chars_per_line * 2] = '\0';
+
+ mltest = mlstring_allocate(test_softwrap);
+ mlstring_wrap(mltest, font, WRAP_WIDTH_PX);
+
+ /* reusing 'i' for a moment here to make freeing mltest easier */
+ i = strlen(mltest->lines->line);
+ free(mltest);
+
+ if (i != chars_first_word)
+ FAIL("Soft wrap failed, expected the first line to be %d chars, but it was %d.", chars_first_word, i);
+ SUCCEED("Soft wrap");
+
+ ok = 1;
+
+end:
+ if (test_softwrap)
+ free(test_softwrap);
+
+ if (font)
+ XFreeFont(dpy, font);
+
+ if (dpy)
+ XCloseDisplay(dpy);
+
+ if (!ok)
+ SKIP("wrapping");
+
+ return ok ? SUCCESS : SKIPPED; /* Unused, actually */
+}
+
+
+int main(int argc, char *argv[])
+{
+ const char *oneline = "1Foo";
+ const char *twolines = "2Foo\nBar";
+ const char *threelines = "3Foo\nBar\nWhippet";
+ const char *trailnewline = "4Foo\n";
+ const char *trailnewlines = "5Foo\n\n";
+ const char *embeddednewlines = "6Foo\n\nBar";
+ mlstring *mlstr;
+
+ TRY_NEW(oneline, 1, False);
+ TRY_NEW(twolines, 2, False);
+ TRY_NEW(threelines, 3, False);
+ TRY_NEW(trailnewline, 2, True);
+ TRY_NEW(trailnewlines, 3, True);
+ TRY_NEW(embeddednewlines, 3, True);
+
+ (void) test_wrapping();
+
+ fprintf(stdout, "%d test failures.\n", failcount);
+
+ return !!failcount;
+}
+
+/* vim:ts=8:sw=2:noet
+ */
diff --git a/driver/test-passwd.c b/driver/test-passwd.c
new file mode 100644
index 0000000..9b4f98e
--- /dev/null
+++ b/driver/test-passwd.c
@@ -0,0 +1,306 @@
+/* xscreensaver, Copyright (c) 1998-2017 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 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
+# include "config.h"
+#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 "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;
+
+void
+idle_timer (XtPointer closure, XtIntervalId *id)
+{
+ 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 i;
+
+ 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
+};
+
+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;
+
+ memset(&sip, 0, sizeof(sip));
+ memset(&ssip, 0, sizeof(ssip));
+
+ si->nscreens = 1;
+ si->screens = si->default_screen = &ssip;
+ ssip.global = si;
+
+ global_si_kludge = si;
+ real_stderr = stderr;
+ real_stdout = stdout;
+
+ 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
+
+ progclass = "XScreenSaver";
+
+ if (!setlocale (LC_CTYPE, ""))
+ fprintf (stderr, "%s: warning: could not set default locale\n",
+ progname);
+
+
+ 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);
+
+ ssip.width = WidthOfScreen(ssip.screen);
+ ssip.height = HeightOfScreen(ssip.screen);
+
+ db = p->db;
+ XtGetApplicationNameAndClass (si->dpy, &progname, &progclass);
+
+ load_init_file (si->dpy, &si->prefs);
+ }
+
+ p->verbose_p = True;
+
+ pw = getpwuid (getuid ());
+ si->user = strdup (pw->pw_name);
+
+/* si->nscreens = 0;
+ si->screens = si->default_screen = 0; */
+
+ while (1)
+ {
+#ifndef NO_LOCKING
+ if (which == PASS)
+ {
+ si->unlock_cb = gui_auth_conv;
+ si->auth_finished_cb = auth_finished_cb;
+
+ 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);
+}
diff --git a/driver/test-randr.c b/driver/test-randr.c
new file mode 100644
index 0000000..74ead37
--- /dev/null
+++ b/driver/test-randr.c
@@ -0,0 +1,339 @@
+/* test-randr.c --- playing with the Resize And Rotate extension.
+ * xscreensaver, Copyright (c) 2004-2008 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/Xrandr.h>
+
+char *progname = 0;
+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
+ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
+{
+ error_handler_hit_p = True;
+ return 0;
+}
+
+
+int
+main (int argc, char **argv)
+{
+ int event_number = -1, error_number = -1;
+ int major = -1, minor = -1;
+ int nscreens = 0;
+ int i;
+
+ XtAppContext app;
+ Widget toplevel_shell = XtAppInitialize (&app, progclass, 0, 0,
+ &argc, argv, 0, 0, 0);
+ Display *dpy = XtDisplay (toplevel_shell);
+ XtGetApplicationNameAndClass (dpy, &progname, &progclass);
+
+ 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",
+ blurb());
+ major = -1;
+ }
+ else
+ {
+ fprintf(stderr, "%s: XRRQueryExtension(dpy, ...) ==> %d, %d\n",
+ blurb(), event_number, error_number);
+
+ if (!XRRQueryVersion(dpy, &major, &minor))
+ {
+ fprintf(stderr, "%s: XRRQueryVersion(dpy, ...) ==> False\n",
+ blurb());
+ fprintf(stderr, "%s: server didn't report RANDR version numbers?\n",
+ blurb());
+ }
+ else
+ fprintf(stderr, "%s: XRRQueryVersion(dpy, ...) ==> %d, %d\n", blurb(),
+ major, minor);
+ }
+
+ for (i = 0; i < nscreens; i++)
+ {
+ XRRScreenConfiguration *rrc;
+ XErrorHandler old_handler;
+
+ XSync (dpy, False);
+ error_handler_hit_p = False;
+ old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
+
+ rrc = (major >= 0 ? XRRGetScreenInfo (dpy, RootWindow (dpy, i)) : 0);
+
+ XSync (dpy, False);
+ XSetErrorHandler (old_handler);
+ XSync (dpy, False);
+
+ if (error_handler_hit_p)
+ {
+ fprintf(stderr, "%s: XRRGetScreenInfo(dpy, %d) ==> X error:\n",
+ blurb(), i);
+ /* do it again without the error handler to print the error */
+ rrc = XRRGetScreenInfo (dpy, RootWindow (dpy, i));
+ }
+ else if (rrc)
+ {
+ SizeID current_size = -1;
+ Rotation current_rotation = ~0;
+
+ fprintf (stderr, "\n%s: Screen %d\n", blurb(), i);
+
+ current_size =
+ XRRConfigCurrentConfiguration (rrc, &current_rotation);
+
+ /* Times */
+# if 0 /* #### This is wrong -- I don't understand what these two
+ timestamp numbers represent, or how they correlate
+ to the wall clock or to each other. */
+ {
+ Time server_time, config_time;
+ server_time = XRRConfigTimes (rrc, &config_time);
+ if (config_time == 0 || server_time == 0)
+ fprintf (stderr, "%s: config has never been changed\n",
+ blurb());
+ else
+ fprintf (stderr, "%s: config changed %lu seconds ago\n",
+ blurb(), (unsigned long) (server_time - config_time));
+ }
+# endif
+
+ /* Rotations */
+ {
+ Rotation available, current;
+ available = XRRConfigRotations (rrc, &current);
+
+ fprintf (stderr, "%s: Available Rotations:\t", blurb());
+ if (available & RR_Rotate_0) fprintf (stderr, " 0");
+ if (available & RR_Rotate_90) fprintf (stderr, " 90");
+ if (available & RR_Rotate_180) fprintf (stderr, " 180");
+ if (available & RR_Rotate_270) fprintf (stderr, " 270");
+ if (! (available & (RR_Rotate_0 | RR_Rotate_90 |
+ RR_Rotate_180 | RR_Rotate_270)))
+ fprintf (stderr, " none");
+ fprintf (stderr, "\n");
+
+ if (current_rotation != current)
+ fprintf (stderr,
+ "%s: WARNING: rotation inconsistency: 0x%X vs 0x%X\n",
+ blurb(), current_rotation, current);
+
+ fprintf (stderr, "%s: Current Rotation:\t", blurb());
+ if (current & RR_Rotate_0) fprintf (stderr, " 0");
+ if (current & RR_Rotate_90) fprintf (stderr, " 90");
+ if (current & RR_Rotate_180) fprintf (stderr, " 180");
+ if (current & RR_Rotate_270) fprintf (stderr, " 270");
+ if (! (current & (RR_Rotate_0 | RR_Rotate_90 |
+ RR_Rotate_180 | RR_Rotate_270)))
+ fprintf (stderr, " none");
+ fprintf (stderr, "\n");
+
+ fprintf (stderr, "%s: Available Reflections:\t", blurb());
+ if (available & RR_Reflect_X) fprintf (stderr, " X");
+ if (available & RR_Reflect_Y) fprintf (stderr, " Y");
+ if (! (available & (RR_Reflect_X | RR_Reflect_Y)))
+ fprintf (stderr, " none");
+ fprintf (stderr, "\n");
+
+ fprintf (stderr, "%s: Current Reflections:\t", blurb());
+ if (current & RR_Reflect_X) fprintf (stderr, " X");
+ if (current & RR_Reflect_Y) fprintf (stderr, " Y");
+ if (! (current & (RR_Reflect_X | RR_Reflect_Y)))
+ fprintf (stderr, " none");
+ fprintf (stderr, "\n");
+ }
+
+ /* Sizes */
+ {
+ int nsizes, j;
+ XRRScreenSize *rrsizes;
+
+ rrsizes = XRRConfigSizes (rrc, &nsizes);
+ if (nsizes <= 0)
+ fprintf (stderr, "%s: sizes:\t none\n", blurb());
+ else
+ for (j = 0; j < nsizes; j++)
+ {
+ short *rates;
+ int nrates, k;
+ fprintf (stderr,
+ "%s: %c size %d: %d x %d\t rates:",
+ blurb(),
+ (j == current_size ? '+' : ' '),
+ j,
+ rrsizes[j].width, rrsizes[j].height);
+
+ rates = XRRConfigRates (rrc, j, &nrates);
+ if (nrates == 0)
+ fprintf (stderr, " none?");
+ else
+ for (k = 0; k < nrates; k++)
+ fprintf (stderr, " %d", rates[k]);
+ fprintf (stderr, "\n");
+ /* don't free 'rates' */
+ }
+ /* don't free 'rrsizes' */
+ }
+
+ XRRFreeScreenConfigInfo (rrc);
+ }
+ else if (major >= 0)
+ {
+ fprintf(stderr, "%s: XRRGetScreenInfo(dpy, %d) ==> NULL\n",
+ blurb(), i);
+ }
+
+
+# ifdef HAVE_RANDR_12
+ if (major > 1 || (major == 1 && minor >= 2))
+ {
+ int j;
+ XRRScreenResources *res =
+ XRRGetScreenResources (dpy, RootWindow (dpy, i));
+ fprintf (stderr, "\n");
+ for (j = 0; j < res->noutput; j++)
+ {
+ int k;
+ XRROutputInfo *rroi =
+ XRRGetOutputInfo (dpy, res, res->outputs[j]);
+ fprintf (stderr, "%s: Output %d: %s: %s (%d)\n", blurb(), j,
+ rroi->name,
+ (rroi->connection == RR_Disconnected ? "disconnected" :
+ rroi->connection == RR_UnknownConnection ? "unknown" :
+ "connected"),
+ (int) rroi->crtc);
+ for (k = 0; k < rroi->ncrtc; k++)
+ {
+ XRRCrtcInfo *crtci = XRRGetCrtcInfo (dpy, res,
+ rroi->crtcs[k]);
+ fprintf(stderr, "%s: %c CRTC %d (%d): %dx%d+%d+%d\n",
+ blurb(),
+ (rroi->crtc == rroi->crtcs[k] ? '+' : ' '),
+ k, (int) rroi->crtcs[k],
+ crtci->width, crtci->height, crtci->x, crtci->y);
+ XRRFreeCrtcInfo (crtci);
+ }
+ XRRFreeOutputInfo (rroi);
+ fprintf (stderr, "\n");
+ }
+ XRRFreeScreenResources (res);
+ }
+# endif /* HAVE_RANDR_12 */
+ }
+
+ if (major > 0)
+ {
+ Window w[20];
+ XWindowAttributes xgwa[20];
+
+ for (i = 0; i < nscreens; i++)
+ {
+ XRRSelectInput (dpy, RootWindow (dpy, i), RRScreenChangeNotifyMask);
+ w[i] = RootWindow (dpy, i);
+ XGetWindowAttributes (dpy, w[i], &xgwa[i]);
+ }
+
+ XSync (dpy, False);
+
+ fprintf (stderr, "\n%s: awaiting events...\n\n"
+ "\t(If you resize the screen or add/remove monitors, this should\n"
+ "\tnotice that and print stuff. Otherwise, hit ^C.)\n\n",
+ progname);
+ while (1)
+ {
+ XEvent event;
+ XNextEvent (dpy, &event);
+
+ if (event.type == event_number + RRScreenChangeNotify)
+ {
+ XRRScreenChangeNotifyEvent *xrr_event =
+ (XRRScreenChangeNotifyEvent *) &event;
+ int screen = XRRRootToScreen (dpy, xrr_event->window);
+
+ fprintf (stderr, "%s: screen %d: RRScreenChangeNotify event\n",
+ progname, screen);
+
+ fprintf (stderr, "%s: screen %d: old size: \t%d x %d\n",
+ progname, screen,
+ DisplayWidth (dpy, screen),
+ DisplayHeight (dpy, screen));
+ fprintf (stderr, "%s: screen %d: old root 0x%lx:\t%d x %d\n",
+ progname, screen, (unsigned long) w[screen],
+ xgwa[screen].width, xgwa[screen].height);
+
+ XRRUpdateConfiguration (&event);
+ XSync (dpy, False);
+
+ fprintf (stderr, "%s: screen %d: new size: \t%d x %d\n",
+ progname, screen,
+ DisplayWidth (dpy, screen),
+ DisplayHeight (dpy, screen));
+
+ w[screen] = RootWindow (dpy, screen);
+ XGetWindowAttributes (dpy, w[screen], &xgwa[screen]);
+ fprintf (stderr, "%s: screen %d: new root 0x%lx:\t%d x %d\n",
+ progname, screen, (unsigned long) w[screen],
+ xgwa[screen].width, xgwa[screen].height);
+ fprintf (stderr, "\n");
+ }
+ else
+ {
+ fprintf (stderr, "%s: event %d\n", progname, event.type);
+ }
+ }
+ }
+
+ XSync (dpy, False);
+ exit (0);
+}
diff --git a/driver/test-screens.c b/driver/test-screens.c
new file mode 100644
index 0000000..2fb3e35
--- /dev/null
+++ b/driver/test-screens.c
@@ -0,0 +1,208 @@
+/* test-screens.c --- some test cases for the "monitor sanity" checks.
+ * xscreensaver, Copyright (c) 2008 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 <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 "visual.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)
+{
+ switch (san) {
+ case S_SANE: return "OK";
+ case S_ENCLOSED: return "ENC";
+ case S_DUPLICATE: return "DUP";
+ case S_OVERLAP: return "OVR";
+ case S_OFFSCREEN: return "OFF";
+ case S_DISABLED: return "DIS";
+ default: abort(); break;
+ }
+}
+
+
+static void
+test (int testnum, const char *screens, const char *desired)
+{
+ monitor *monitors[100];
+ char result[2048];
+ char *out = result;
+ int i, nscreens = 0;
+ char *token = strtok (strdup(screens), ",");
+ while (token)
+ {
+ monitor *m = calloc (1, sizeof (monitor));
+ char c;
+ m->id = (testnum * 1000) + nscreens;
+ if (5 == sscanf (token, "%dx%d+%d+%d@%d%c",
+ &m->width, &m->height, &m->x, &m->y,
+ (int *) &m->screen, &c))
+ ;
+ else if (4 != sscanf (token, "%dx%d+%d+%d%c",
+ &m->width, &m->height, &m->x, &m->y, &c))
+ {
+ fprintf (stderr, "%s: unparsable geometry: %s\n", blurb(), token);
+ exit (1);
+ }
+ monitors[nscreens] = m;
+ nscreens++;
+ token = strtok (0, ",");
+ }
+ monitors[nscreens] = 0;
+
+ check_monitor_sanity (monitors);
+
+ *out = 0;
+ for (i = 0; i < nscreens; i++)
+ {
+ monitor *m = monitors[i];
+ if (out != result) *out++ = ',';
+ if (m->sanity == S_SANE)
+ {
+ sprintf (out, "%dx%d+%d+%d", m->width, m->height, m->x, m->y);
+ if (m->screen)
+ sprintf (out + strlen(out), "@%d", (int) m->screen);
+ }
+ else
+ strcpy (out, failstr (m->sanity));
+ out += strlen(out);
+ }
+ *out = 0;
+
+ if (!strcmp (result, desired))
+ fprintf (stderr, "%s: test %2d OK\n", blurb(), testnum);
+ else
+ fprintf (stderr, "%s: test %2d FAILED:\n"
+ "%s: given: %s\n"
+ "%s: wanted: %s\n"
+ "%s: got: %s\n",
+ blurb(), testnum,
+ blurb(), screens,
+ blurb(), desired,
+ blurb(), result);
+
+# if 0
+ {
+ saver_info SI;
+ SI.monitor_layout = monitors;
+ describe_monitor_layout (&SI);
+ }
+# endif
+
+}
+
+static void
+run_tests(void)
+{
+ int i = 1;
+# define A(a) test (i++, a, a);
+# define B(a,b) test (i++, a, b)
+
+ A("");
+ A("1024x768+0+0");
+ A("1024x768+0+0,1024x768+1024+0");
+ A("1024x768+0+0,1024x768+0+768");
+ A("1024x768+0+0,1024x768+0+768,1024x768+1024+0");
+ A("800x600+0+0,800x600+0+0@1,800x600+10+0@2");
+
+ B("1024x768+999999+0",
+ "OFF");
+ B("1024x768+-999999+-999999",
+ "OFF");
+ B("1024x768+0+0,1024x768+0+0",
+ "1024x768+0+0,DUP");
+ B("1024x768+0+0,1024x768+0+0,1024x768+0+0",
+ "1024x768+0+0,DUP,DUP");
+ B("1024x768+0+0,1024x768+1024+0,1024x768+0+0",
+ "1024x768+0+0,1024x768+1024+0,DUP");
+ B("1280x1024+0+0,1024x768+0+64,800x600+0+0,640x480+0+0,720x400+0+0",
+ "1280x1024+0+0,ENC,ENC,ENC,ENC");
+ B("1024x768+0+64,1280x1024+0+0,800x600+0+0,640x480+0+0,800x600+0+0,720x400+0+0",
+ "ENC,1280x1024+0+0,ENC,ENC,ENC,ENC");
+ B("1024x768+0+64,1280x1024+0+0,800x600+0+0,640x480+0+0,1280x1024+0+0,720x400+0+0",
+ "ENC,1280x1024+0+0,ENC,ENC,DUP,ENC");
+ B("720x400+0+0,640x480+0+0,800x600+0+0,1024x768+0+64,1280x1024+0+0",
+ "ENC,ENC,ENC,ENC,1280x1024+0+0");
+ B("1280x1024+0+0,800x600+1280+0,800x600+1300+0",
+ "1280x1024+0+0,800x600+1280+0,OVR");
+ B("1280x1024+0+0,800x600+1280+0,800x600+1300+0,1280x1024+0+0,800x600+1280+0",
+ "1280x1024+0+0,800x600+1280+0,OVR,DUP,DUP");
+
+ /* +-------------+----+ +------+---+ 1: 1440x900, widescreen display
+ | : | | 3+4 : | 2: 1280x1024, conventional display
+ | 1+2 : 1 | +......+ | 3: 1024x768, laptop
+ | : | | 3 | 4: 800x600, external projector
+ +.............+----+ +----------+
+ | 2 |
+ | |
+ +-------------+
+ */
+ B("1440x900+0+0,1280x1024+0+0,1024x768+1440+0,800x600+1440+0",
+ "1440x900+0+0,OVR,1024x768+1440+0,ENC");
+ B("800x600+0+0,800x600+0+0,800x600+800+0",
+ "800x600+0+0,DUP,800x600+800+0");
+ B("1600x1200+0+0,1360x768+0+0",
+ "1600x1200+0+0,ENC");
+ B("1600x1200+0+0,1360x768+0+0,1600x1200+0+0@1,1360x768+0+0@1",
+ "1600x1200+0+0,ENC,1600x1200+0+0@1,ENC");
+}
+
+
+int
+main (int argc, char **argv)
+{
+ char *s;
+ progname = argv[0];
+ s = strrchr(progname, '/');
+ if (s) progname = s+1;
+ if (argc != 1)
+ {
+ fprintf (stderr, "usage: %s\n", argv[0]);
+ exit (1);
+ }
+
+ run_tests();
+
+ exit (0);
+}
diff --git a/driver/test-uid.c b/driver/test-uid.c
new file mode 100644
index 0000000..6a1f9cc
--- /dev/null
+++ b/driver/test-uid.c
@@ -0,0 +1,209 @@
+/* test-uid.c --- playing with setuid.
+ * xscreensaver, Copyright (c) 1998, 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
+ * 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 <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <pwd.h>
+#include <grp.h>
+
+static void
+print(void)
+{
+ int uid = getuid();
+ int gid = getgid();
+ int euid = geteuid();
+ int egid = getegid();
+ struct passwd *p = 0;
+ struct group *g = 0;
+ gid_t groups[1024];
+ int n, size;
+
+ p = getpwuid (uid);
+ g = getgrgid (gid);
+ fprintf(stderr, "real user/group: %ld/%ld (%s/%s)\n", (long) uid, (long) gid,
+ (p && p->pw_name ? p->pw_name : "???"),
+ (g && g->gr_name ? g->gr_name : "???"));
+
+ p = getpwuid (euid);
+ g = getgrgid (egid);
+ fprintf(stderr, "eff. user/group: %ld/%ld (%s/%s)\n", (long)euid, (long)egid,
+ (p && p->pw_name ? p->pw_name : "???"),
+ (g && g->gr_name ? g->gr_name : "???"));
+
+ size = sizeof(groups) / sizeof(gid_t);
+ n = getgroups(size - 1, groups);
+ if (n < 0)
+ perror("getgroups failed");
+ else
+ {
+ int i;
+ fprintf (stderr, "eff. group list: [");
+ for (i = 0; i < n; i++)
+ {
+ g = getgrgid (groups[i]);
+ fprintf(stderr, "%s%s=%ld", (i == 0 ? "" : ", "),
+ (g->gr_name ? g->gr_name : "???"),
+ (long) groups[i]);
+ }
+ fprintf (stderr, "]\n");
+ }
+}
+
+int
+main (int argc, char **argv)
+{
+ int i;
+ struct passwd *p = 0;
+ struct group *g = 0;
+
+ if (argc <= 1)
+ {
+ fprintf(stderr,
+ "usage: %s [ user/group ... ]\n"
+ "\tEach argument may be a user name, or user/group.\n"
+ "\tThis program will attempt to setuid/setgid to each\n"
+ "\tin turn, and report the results. The user and group\n"
+ "\tnames may be strings, or numeric.\n",
+ argv[0]);
+ exit(1);
+ }
+
+ print();
+ for (i = 1; i < argc; i++)
+ {
+ char *user = argv[i];
+ char *group = strchr(user, '/');
+ if (!group)
+ group = strchr(user, '.');
+ if (group)
+ *group++ = 0;
+
+ if (group && *group)
+ {
+ long gid = 0;
+ int was_numeric = 0;
+
+ g = 0;
+ if (*group == '-' || (*group >= '0' && *group <= '9'))
+ if (1 == sscanf(group, "%ld", &gid))
+ {
+ g = getgrgid (gid);
+ was_numeric = 1;
+ }
+
+ if (!g)
+ g = getgrnam(group);
+
+ if (g)
+ {
+ gid = g->gr_gid;
+ group = g->gr_name;
+ }
+ else
+ {
+ if (was_numeric)
+ {
+ fprintf(stderr, "no group numbered %s.\n", group);
+ group = "";
+ }
+ else
+ {
+ fprintf(stderr, "no group named %s.\n", group);
+ goto NOGROUP;
+ }
+ }
+
+ fprintf(stderr, "setgroups(1, [%ld]) \"%s\"", gid, group);
+ {
+ gid_t g2 = gid;
+ if (setgroups(1, &g2) == 0)
+ fprintf(stderr, " succeeded.\n");
+ else
+ perror(" failed");
+ }
+
+ fprintf(stderr, "setgid(%ld) \"%s\"", gid, group);
+ if (setgid(gid) == 0)
+ fprintf(stderr, " succeeded.\n");
+ else
+ perror(" failed");
+
+ NOGROUP: ;
+ }
+
+ if (user && *user)
+ {
+ long uid = 0;
+ int was_numeric = 0;
+
+ p = 0;
+ if (*user == '-' || (*user >= '0' && *user <= '9'))
+ if (1 == sscanf(user, "%ld", &uid))
+ {
+ p = getpwuid (uid);
+ was_numeric = 1;
+ }
+
+ if (!p)
+ p = getpwnam(user);
+
+ if (p)
+ {
+ uid = p->pw_uid;
+ user = p->pw_name;
+ }
+ else
+ {
+ if (was_numeric)
+ {
+ fprintf(stderr, "no user numbered \"%s\".\n", user);
+ user = "";
+ }
+ else
+ {
+ 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");
+ else
+ perror(" failed");
+ NOUSER: ;
+ }
+ print();
+ }
+
+ fprintf(stderr,
+ "running \"whoami\" and \"groups\" in a sub-process reports:\n");
+ fflush(stdout);
+ fflush(stderr);
+ system ("/bin/sh -c 'echo \"`whoami` / `groups`\"'");
+
+ fflush(stdout);
+ fflush(stderr);
+ exit(0);
+}
diff --git a/driver/test-vp.c b/driver/test-vp.c
new file mode 100644
index 0000000..bf1a0b1
--- /dev/null
+++ b/driver/test-vp.c
@@ -0,0 +1,213 @@
+/* test-xinerama.c --- playing with XF86VidModeGetViewPort
+ * xscreensaver, Copyright (c) 2004 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/xf86vmode.h>
+#include <X11/extensions/Xinerama.h>
+
+char *progname = 0;
+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
+ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
+{
+ error_handler_hit_p = True;
+ return 0;
+}
+
+
+static int
+screen_count (Display *dpy)
+{
+ int n = ScreenCount(dpy);
+ int xn = 0;
+ int event_number, error_number;
+
+ if (!XineramaQueryExtension (dpy, &event_number, &error_number))
+ {
+ fprintf(stderr, "%s: XineramaQueryExtension(dpy, ...) ==> False\n",
+ blurb());
+ goto DONE;
+ }
+ else
+ fprintf(stderr, "%s: XineramaQueryExtension(dpy, ...) ==> %d, %d\n",
+ blurb(), event_number, error_number);
+
+ if (!XineramaIsActive(dpy))
+ {
+ fprintf(stderr, "%s: XineramaIsActive(dpy) ==> False\n",
+ blurb());
+ goto DONE;
+ }
+ else
+ {
+ int major, minor;
+ XineramaScreenInfo *xsi;
+ fprintf(stderr, "%s: XineramaIsActive(dpy) ==> True\n",
+ blurb());
+ if (!XineramaQueryVersion(dpy, &major, &minor))
+ {
+ fprintf(stderr,
+ "%s: XineramaQueryVersion(dpy, ...) ==> False\n",
+ blurb());
+ goto DONE;
+ }
+ else
+ fprintf(stderr,
+ "%s: XineramaQueryVersion(dpy, ...) ==> %d, %d\n",
+ blurb(), major, minor);
+
+ xsi = XineramaQueryScreens (dpy, &xn);
+ if (xsi) XFree (xsi);
+ }
+
+ DONE:
+ fprintf (stderr, "\n");
+ fprintf (stderr, "%s: X client screens: %d\n", blurb(), n);
+ fprintf (stderr, "%s: Xinerama screens: %d\n", blurb(), xn);
+ fprintf (stderr, "\n");
+
+ if (xn > n) return xn;
+ else return n;
+}
+
+
+int
+main (int argc, char **argv)
+{
+ int event_number, error_number;
+ int major, minor;
+ int nscreens = 0;
+ int i;
+
+ XtAppContext app;
+ 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",
+ blurb());
+ exit(1);
+ }
+ else
+ fprintf(stderr, "%s: XF86VidModeQueryExtension(dpy, ...) ==> %d, %d\n",
+ blurb(), event_number, error_number);
+
+ if (!XF86VidModeQueryVersion(dpy, &major, &minor))
+ {
+ fprintf(stderr, "%s: XF86VidModeQueryVersion(dpy, ...) ==> False\n",
+ blurb());
+ fprintf(stderr,
+ "%s: server didn't report XF86VidMode version numbers?\n",
+ blurb());
+ }
+ else
+ fprintf(stderr, "%s: XF86VidModeQueryVersion(dpy, ...) ==> %d, %d\n",
+ blurb(), major, minor);
+
+ nscreens = screen_count (dpy);
+
+ for (i = 0; i < nscreens; i++)
+ {
+ int result = 0;
+ int x = 0, y = 0, dot = 0;
+ XF86VidModeModeLine ml = { 0, };
+ XErrorHandler old_handler;
+
+ XSync (dpy, False);
+ error_handler_hit_p = False;
+ old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
+
+ result = XF86VidModeGetViewPort (dpy, i, &x, &y);
+
+ XSync (dpy, False);
+ XSetErrorHandler (old_handler);
+ XSync (dpy, False);
+
+ if (error_handler_hit_p)
+ {
+ fprintf(stderr,
+ "%s: XF86VidModeGetViewPort(dpy, %d, ...) ==> X error\n",
+ blurb(), i);
+ continue;
+ }
+
+ if (! result)
+ fprintf(stderr, "%s: XF86VidModeGetViewPort(dpy, %d, ...) ==> %d\n",
+ blurb(), i, result);
+
+ result = XF86VidModeGetModeLine (dpy, i, &dot, &ml);
+ if (! result)
+ fprintf(stderr, "%s: XF86VidModeGetModeLine(dpy, %d, ...) ==> %d\n",
+ blurb(), i, result);
+
+ fprintf (stderr, "%s: screen %d: %dx%d; viewport: %dx%d+%d+%d\n",
+ blurb(), i,
+ DisplayWidth (dpy, i), DisplayHeight (dpy, i),
+ ml.hdisplay, ml.vdisplay, x, y
+ );
+
+ fprintf (stderr,
+ "%s: hsync start %d; end %d; total %d; skew %d;\n",
+ blurb(),
+ ml.hsyncstart, ml.hsyncend, ml.htotal, ml.hskew);
+ fprintf (stderr,
+ "%s: vsync start %d; end %d; total %d; flags 0x%04x;\n",
+ blurb(),
+ ml.vsyncstart, ml.vsyncend, ml.vtotal, ml.flags);
+ fprintf (stderr, "\n");
+ }
+ XSync (dpy, False);
+ exit (0);
+}
diff --git a/driver/test-xdpms.c b/driver/test-xdpms.c
new file mode 100644
index 0000000..b86aed3
--- /dev/null
+++ b/driver/test-xdpms.c
@@ -0,0 +1,179 @@
+/* test-xdpms.c --- playing with the XDPMS extension.
+ * xscreensaver, Copyright (c) 1998-2011 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/dpms.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;
+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
+ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
+{
+ error_handler_hit_p = True;
+ return 0;
+}
+
+
+int
+main (int argc, char **argv)
+{
+ int delay = 10;
+
+ int event_number, error_number;
+ int major, minor;
+ CARD16 standby, suspend, off;
+ CARD16 state;
+ BOOL onoff;
+
+ XtAppContext app;
+ 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",
+ blurb());
+ exit(1);
+ }
+ else
+ fprintf(stderr, "%s: DPMSQueryExtension(dpy, ...) ==> %d, %d\n", blurb(),
+ event_number, error_number);
+
+ if (!DPMSCapable(dpy))
+ {
+ fprintf(stderr, "%s: DPMSCapable(dpy) ==> False\n", blurb());
+ fprintf(stderr, "%s: server says hardware doesn't support DPMS.\n",
+ blurb());
+ exit(1);
+ }
+ else
+ fprintf(stderr, "%s: DPMSCapable(dpy) ==> True\n", blurb());
+
+ if (!DPMSGetVersion(dpy, &major, &minor))
+ {
+ fprintf(stderr, "%s: DPMSGetVersion(dpy, ...) ==> False\n", blurb());
+ fprintf(stderr, "%s: server didn't report XDPMS version numbers?\n",
+ blurb());
+ }
+ else
+ fprintf(stderr, "%s: DPMSGetVersion(dpy, ...) ==> %d, %d\n", blurb(),
+ major, minor);
+
+ if (!DPMSGetTimeouts(dpy, &standby, &suspend, &off))
+ {
+ fprintf(stderr, "%s: DPMSGetTimeouts(dpy, ...) ==> False\n", blurb());
+ fprintf(stderr, "%s: server didn't report DPMS timeouts?\n", blurb());
+ }
+ else
+ fprintf(stderr,
+ "%s: DPMSGetTimeouts(dpy, ...)\n"
+ "\t ==> standby = %d, suspend = %d, off = %d\n",
+ blurb(), standby, suspend, off);
+
+ while (1)
+ {
+ if (!DPMSInfo(dpy, &state, &onoff))
+ {
+ fprintf(stderr, "%s: DPMSInfo(dpy, ...) ==> False\n", blurb());
+ fprintf(stderr, "%s: couldn't read DPMS state?\n", blurb());
+ onoff = 0;
+ state = -1;
+ }
+ else
+ {
+ fprintf(stderr, "%s: DPMSInfo(dpy, ...) ==> %s, %s\n", blurb(),
+ (state == DPMSModeOn ? "DPMSModeOn" :
+ state == DPMSModeStandby ? "DPMSModeStandby" :
+ state == DPMSModeSuspend ? "DPMSModeSuspend" :
+ state == DPMSModeOff ? "DPMSModeOff" : "???"),
+ (onoff == 1 ? "On" : onoff == 0 ? "Off" : "???"));
+ }
+
+ if (state == DPMSModeStandby ||
+ state == DPMSModeSuspend ||
+ state == DPMSModeOff)
+ {
+ XErrorHandler old_handler;
+ int st;
+ 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);
+ XSync (dpy, False);
+ st = DPMSForceLevel (dpy, DPMSModeOn);
+ XSync (dpy, False);
+ if (error_handler_hit_p) st = -666;
+
+ fprintf (stderr, "%s: DPMSForceLevel (dpy, DPMSModeOn) ==> %s\n",
+ blurb(), (st == -666 ? "X Error" : st ? "Ok" : "Error"));
+ }
+
+ sleep (delay);
+ }
+}
diff --git a/driver/test-xinerama.c b/driver/test-xinerama.c
new file mode 100644
index 0000000..8bafbb0
--- /dev/null
+++ b/driver/test-xinerama.c
@@ -0,0 +1,112 @@
+/* test-xinerama.c --- playing with the Xinerama extension.
+ * xscreensaver, Copyright (c) 2003 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/Xinerama.h>
+
+char *progname = 0;
+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)
+{
+ int event_number, error_number;
+ int major, minor;
+ int nscreens = 0;
+ XineramaScreenInfo *xsi;
+ int i;
+
+ XtAppContext app;
+ 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",
+ blurb());
+ exit(1);
+ }
+ else
+ fprintf(stderr, "%s: XineramaQueryExtension(dpy, ...) ==> %d, %d\n",
+ blurb(), event_number, error_number);
+
+ if (!XineramaIsActive(dpy))
+ {
+ fprintf(stderr, "%s: XineramaIsActive(dpy) ==> False\n", blurb());
+ fprintf(stderr, "%s: server says Xinerama is turned off.\n", blurb());
+ exit(1);
+ }
+ else
+ fprintf(stderr, "%s: XineramaIsActive(dpy) ==> True\n", blurb());
+
+ if (!XineramaQueryVersion(dpy, &major, &minor))
+ {
+ fprintf(stderr, "%s: XineramaQueryVersion(dpy, ...) ==> False\n",
+ blurb());
+ fprintf(stderr, "%s: server didn't report Xinerama version numbers?\n",
+ blurb());
+ }
+ else
+ fprintf(stderr, "%s: XineramaQueryVersion(dpy, ...) ==> %d, %d\n", blurb(),
+ major, minor);
+
+ xsi = XineramaQueryScreens (dpy, &nscreens);
+ fprintf(stderr, "%s: %d Xinerama screens\n", blurb(), nscreens);
+
+ for (i = 0; i < nscreens; i++)
+ fprintf (stderr, "%s: screen %d: %dx%d+%d+%d\n",
+ blurb(),
+ xsi[i].screen_number,
+ xsi[i].width, xsi[i].height,
+ xsi[i].x_org, xsi[i].y_org);
+ XFree (xsi);
+ XSync (dpy, False);
+ exit (0);
+}
diff --git a/driver/timers.c b/driver/timers.c
new file mode 100644
index 0000000..ea97f34
--- /dev/null
+++ b/driver/timers.c
@@ -0,0 +1,1788 @@
+/* timers.c --- detecting when the user is idle, and other timer-related tasks.
+ * xscreensaver, Copyright (c) 1991-2017 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 <X11/Xlib.h>
+#include <X11/Intrinsic.h>
+#include <X11/Xos.h>
+#include <X11/Xatom.h>
+#include <time.h>
+#include <sys/time.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_XIDLE_EXTENSION
+#include <X11/extensions/xidle.h>
+#endif /* HAVE_XIDLE_EXTENSION */
+
+#ifdef HAVE_MIT_SAVER_EXTENSION
+#include <X11/extensions/scrnsaver.h>
+#endif /* HAVE_MIT_SAVER_EXTENSION */
+
+#ifdef HAVE_SGI_SAVER_EXTENSION
+#include <X11/extensions/XScreenSaver.h>
+#endif /* HAVE_SGI_SAVER_EXTENSION */
+
+#ifdef HAVE_RANDR
+#include <X11/extensions/Xrandr.h>
+#endif /* HAVE_RANDR */
+
+#include "xscreensaver.h"
+
+#undef ABS
+#define ABS(x)((x)<0?-(x):(x))
+
+#undef MAX
+#define MAX(x,y)((x)>(y)?(x):(y))
+
+
+#ifdef HAVE_PROC_INTERRUPTS
+static Bool proc_interrupts_activity_p (saver_info *si);
+#endif /* HAVE_PROC_INTERRUPTS */
+
+static void check_for_clock_skew (saver_info *si);
+
+
+void
+idle_timer (XtPointer closure, XtIntervalId *id)
+{
+ saver_info *si = (saver_info *) closure;
+
+ /* What an amazingly shitty design. Not only does Xt execute timeout
+ events from XtAppNextEvent() instead of from XtDispatchEvent(), but
+ there is no way to tell Xt to block until there is an X event OR a
+ timeout happens. Once your timeout proc is called, XtAppNextEvent()
+ still won't return until a "real" X event comes in.
+
+ So this function pushes a stupid, gratuitous, unnecessary event back
+ on the event queue to force XtAppNextEvent to return Right Fucking Now.
+ When the code in sleep_until_idle() sees an event of type XAnyEvent,
+ which the server never generates, it knows that a timeout has occurred.
+ */
+ 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);
+
+ /* If we are the timer that just went off, clear the pointer to the id. */
+ if (id)
+ {
+ if (si->timer_id && *id != si->timer_id)
+ abort(); /* oops, scheduled timer twice?? */
+ si->timer_id = 0;
+ }
+}
+
+
+void
+schedule_wakeup_event (saver_info *si, Time when, Bool verbose_p)
+{
+ if (si->timer_id)
+ {
+ if (verbose_p)
+ fprintf (stderr, "%s: idle_timer already running\n", blurb());
+ return;
+ }
+
+ /* Wake up periodically to ask the server if we are idle. */
+ si->timer_id = XtAppAddTimeOut (si->app, when, idle_timer,
+ (XtPointer) si);
+
+ if (verbose_p)
+ fprintf (stderr, "%s: starting idle_timer (%ld, %ld)\n",
+ blurb(), when, si->timer_id);
+}
+
+
+static void
+notice_events (saver_info *si, Window window, Bool top_p)
+{
+ saver_preferences *p = &si->prefs;
+ XWindowAttributes attrs;
+ unsigned long events;
+ Window root, parent, *kids;
+ unsigned int nkids;
+ int screen_no;
+
+ if (XtWindowToWidget (si->dpy, window))
+ /* If it's one of ours, don't mess up its event mask. */
+ return;
+
+ if (!XQueryTree (si->dpy, window, &root, &parent, &kids, &nkids))
+ return;
+ if (window == root)
+ top_p = False;
+
+ /* Figure out which screen this window is on, for the diagnostics. */
+ for (screen_no = 0; screen_no < si->nscreens; screen_no++)
+ if (root == RootWindowOfScreen (si->screens[screen_no].screen))
+ break;
+
+ XGetWindowAttributes (si->dpy, window, &attrs);
+ events = ((attrs.all_event_masks | attrs.do_not_propagate_mask)
+ & (KeyPressMask | PropertyChangeMask));
+
+ /* Select for SubstructureNotify on all windows.
+ Select for PropertyNotify on all windows.
+ Select for KeyPress on all windows that already have it selected.
+
+ Note that we can't select for ButtonPress, because of X braindamage:
+ only one client at a time may select for ButtonPress on a given
+ window, though any number can select for KeyPress. Someone explain
+ *that* to me.
+
+ So, if the user spends a while clicking the mouse without ever moving
+ the mouse or touching the keyboard, we won't know that they've been
+ active, and the screensaver will come on. That sucks, but I don't
+ know how to get around it.
+
+ Since X presents mouse wheels as clicks, this applies to those, too:
+ scrolling through a document using only the mouse wheel doesn't
+ count as activity... Fortunately, /proc/interrupts helps, on
+ systems that have it. Oh, if it's a PS/2 mouse, not serial or USB.
+ This sucks!
+ */
+ XSelectInput (si->dpy, window,
+ SubstructureNotifyMask | PropertyChangeMask | events);
+
+ if (top_p && p->debug_p && (events & KeyPressMask))
+ {
+ /* Only mention one window per tree (hack hack). */
+ fprintf (stderr, "%s: %d: selected KeyPress on 0x%lX\n",
+ blurb(), screen_no, (unsigned long) window);
+ top_p = False;
+ }
+
+ if (kids)
+ {
+ while (nkids)
+ notice_events (si, kids [--nkids], top_p);
+ XFree ((char *) kids);
+ }
+}
+
+
+int
+BadWindow_ehandler (Display *dpy, XErrorEvent *error)
+{
+ /* When we notice a window being created, we spawn a timer that waits
+ 30 seconds or so, and then selects events on that window. This error
+ handler is used so that we can cope with the fact that the window
+ may have been destroyed <30 seconds after it was created.
+ */
+ if (error->error_code == BadWindow ||
+ error->error_code == BadMatch ||
+ error->error_code == BadDrawable)
+ return 0;
+ else
+ return saver_ehandler (dpy, error);
+}
+
+
+struct notice_events_timer_arg {
+ saver_info *si;
+ Window w;
+};
+
+static void
+notice_events_timer (XtPointer closure, XtIntervalId *id)
+{
+ struct notice_events_timer_arg *arg =
+ (struct notice_events_timer_arg *) closure;
+
+ XErrorHandler old_handler = XSetErrorHandler (BadWindow_ehandler);
+
+ saver_info *si = arg->si;
+ Window window = arg->w;
+
+ free(arg);
+ notice_events (si, window, True);
+ XSync (si->dpy, False);
+ XSetErrorHandler (old_handler);
+}
+
+void
+start_notice_events_timer (saver_info *si, Window w, Bool verbose_p)
+{
+ saver_preferences *p = &si->prefs;
+ struct notice_events_timer_arg *arg =
+ (struct notice_events_timer_arg *) malloc(sizeof(*arg));
+ arg->si = si;
+ arg->w = w;
+ XtAppAddTimeOut (si->app, p->notice_events_timeout, notice_events_timer,
+ (XtPointer) arg);
+
+ if (verbose_p)
+ fprintf (stderr, "%s: starting notice_events_timer for 0x%X (%lu)\n",
+ blurb(), (unsigned int) w, p->notice_events_timeout);
+}
+
+
+/* When the screensaver is active, this timer will periodically change
+ the running program.
+ */
+void
+cycle_timer (XtPointer closure, XtIntervalId *id)
+{
+ saver_info *si = (saver_info *) closure;
+ saver_preferences *p = &si->prefs;
+ Time how_long = p->cycle;
+
+ if (si->selection_mode > 0 &&
+ screenhack_running_p (si))
+ /* 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. */
+ how_long = 1000 * 60 * 60;
+
+ if (si->dbox_up_p)
+ {
+ if (p->verbose_p)
+ fprintf (stderr, "%s: dialog box up; delaying hack change.\n",
+ blurb());
+ how_long = 30000; /* 30 secs */
+ }
+ else
+ {
+ int i;
+ maybe_reload_init_file (si);
+ for (i = 0; i < si->nscreens; i++)
+ kill_screenhack (&si->screens[i]);
+
+ raise_window (si, True, True, False);
+
+ if (!si->throttled_p)
+ for (i = 0; i < si->nscreens; i++)
+ spawn_screenhack (&si->screens[i]);
+ else
+ {
+ if (p->verbose_p)
+ fprintf (stderr, "%s: not launching new hack (throttled.)\n",
+ blurb());
+ }
+ }
+
+ if (how_long > 0)
+ {
+ si->cycle_id = XtAppAddTimeOut (si->app, how_long, cycle_timer,
+ (XtPointer) si);
+
+ if (p->debug_p)
+ fprintf (stderr, "%s: starting cycle_timer (%ld, %ld)\n",
+ blurb(), how_long, si->cycle_id);
+ }
+ else
+ {
+ if (p->debug_p)
+ fprintf (stderr, "%s: not starting cycle_timer: how_long == %ld\n",
+ blurb(), (unsigned long) how_long);
+ }
+}
+
+
+void
+activate_lock_timer (XtPointer closure, XtIntervalId *id)
+{
+ saver_info *si = (saver_info *) closure;
+ saver_preferences *p = &si->prefs;
+
+ if (p->verbose_p)
+ fprintf (stderr, "%s: timed out; activating lock.\n", blurb());
+ set_locked_p (si, True);
+}
+
+
+/* Call this when user activity (or "simulated" activity) has been noticed.
+ */
+void
+reset_timers (saver_info *si)
+{
+ saver_preferences *p = &si->prefs;
+ if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
+ return;
+
+ if (si->timer_id)
+ {
+ if (p->debug_p)
+ fprintf (stderr, "%s: killing idle_timer (%ld, %ld)\n",
+ blurb(), p->timeout, si->timer_id);
+ XtRemoveTimeOut (si->timer_id);
+ si->timer_id = 0;
+ }
+
+ schedule_wakeup_event (si, p->timeout, p->debug_p); /* sets si->timer_id */
+
+ if (si->cycle_id) abort (); /* no cycle timer when inactive */
+
+ si->last_activity_time = time ((time_t *) 0);
+
+ /* This will (hopefully, supposedly) tell the server to re-set its
+ DPMS timer. Without this, the -deactivate clientmessage would
+ prevent xscreensaver from blanking, but would not prevent the
+ monitor from powering down. */
+#if 0
+ /* #### With some servers, this causes the screen to flicker every
+ time a key is pressed! Ok, I surrender. I give up on ever
+ having DPMS work properly.
+ */
+ XForceScreenSaver (si->dpy, ScreenSaverReset);
+
+ /* And if the monitor is already powered off, turn it on.
+ You'd think the above would do that, but apparently not? */
+ monitor_power_on (si, True);
+#endif
+
+}
+
+
+/* Returns true if a mouse has moved since the last time we checked.
+ Small motions (of less than "hysteresis" pixels/second) are ignored.
+ */
+static Bool
+device_pointer_moved_p (saver_info *si, poll_mouse_data *last_poll_mouse,
+ poll_mouse_data *this_poll_mouse, Bool mods_p,
+ const char *debug_type, int debug_id)
+{
+ saver_preferences *p = &si->prefs;
+
+ unsigned int distance, dps;
+ unsigned long seconds = 0;
+ Bool moved_p = False;
+
+ distance = MAX (ABS (last_poll_mouse->root_x - this_poll_mouse->root_x),
+ ABS (last_poll_mouse->root_y - this_poll_mouse->root_y));
+ seconds = (this_poll_mouse->time - last_poll_mouse->time);
+
+
+ /* When the screen is blanked, we get MotionNotify events, but when not
+ blanked, we poll only every 5 seconds, and that's not enough resolution
+ to do hysteresis based on a 1 second interval. So, assume that any
+ motion we've seen during the 5 seconds when our eyes were closed happened
+ in the last 1 second instead.
+ */
+ if (seconds > 1) seconds = 1;
+
+ dps = (seconds <= 0 ? distance : (distance / seconds));
+
+ /* Motion only counts if the rate is more than N pixels per second.
+ */
+ if (dps >= p->pointer_hysteresis &&
+ distance > 0)
+ moved_p = True;
+
+ /* If the mouse is not on this screen but used to be, that's motion.
+ If the mouse was not on this screen, but is now, that's motion.
+ */
+ {
+ Bool on_screen_p = (this_poll_mouse->root_x != -1 &&
+ this_poll_mouse->root_y != -1);
+ Bool was_on_screen_p = (last_poll_mouse->root_x != -1 &&
+ last_poll_mouse->root_y != -1);
+
+ if (on_screen_p != was_on_screen_p)
+ moved_p = True;
+ }
+
+ if (p->debug_p && (distance != 0 || moved_p))
+ {
+ fprintf (stderr, "%s: %s %d: pointer %s", blurb(), debug_type, debug_id,
+ (moved_p ? "moved: " : "ignored:"));
+ if (last_poll_mouse->root_x == -1)
+ fprintf (stderr, "off screen");
+ else
+ fprintf (stderr, "%d,%d",
+ last_poll_mouse->root_x,
+ last_poll_mouse->root_y);
+ fprintf (stderr, " -> ");
+ if (this_poll_mouse->root_x == -1)
+ fprintf (stderr, "off screen");
+ else
+ fprintf (stderr, "%d,%d", this_poll_mouse->root_x,
+ this_poll_mouse->root_y);
+ if (last_poll_mouse->root_x != -1 && this_poll_mouse->root_x != -1)
+ fprintf (stderr, " (%d,%d; %d/%lu=%d)",
+ ABS(last_poll_mouse->root_x - this_poll_mouse->root_x),
+ ABS(last_poll_mouse->root_y - this_poll_mouse->root_y),
+ distance, seconds, dps);
+
+ fprintf (stderr, ".\n");
+ }
+
+ if (!moved_p &&
+ mods_p &&
+ this_poll_mouse->mask != last_poll_mouse->mask)
+ {
+ moved_p = True;
+
+ if (p->debug_p)
+ fprintf (stderr, "%s: %s %d: modifiers changed: 0x%04x -> 0x%04x.\n",
+ blurb(), debug_type, debug_id,
+ last_poll_mouse->mask, this_poll_mouse->mask);
+ }
+
+ last_poll_mouse->child = this_poll_mouse->child;
+ last_poll_mouse->mask = this_poll_mouse->mask;
+
+ if (moved_p || seconds > 0)
+ {
+ last_poll_mouse->time = this_poll_mouse->time;
+ last_poll_mouse->root_x = this_poll_mouse->root_x;
+ last_poll_mouse->root_y = this_poll_mouse->root_y;
+ }
+
+ return moved_p;
+}
+
+/* Returns true if core mouse pointer has moved since the last time we checked.
+ */
+static Bool
+pointer_moved_p (saver_screen_info *ssi, Bool mods_p)
+{
+ saver_info *si = ssi->global;
+
+ Window root;
+ poll_mouse_data this_poll_mouse;
+ int x, y;
+
+ /* don't check xinerama pseudo-screens. */
+ if (!ssi->real_screen_p) return False;
+
+ this_poll_mouse.time = time ((time_t *) 0);
+
+ if (!XQueryPointer (si->dpy, ssi->screensaver_window, &root,
+ &this_poll_mouse.child,
+ &this_poll_mouse.root_x, &this_poll_mouse.root_y,
+ &x, &y, &this_poll_mouse.mask))
+ {
+ /* If XQueryPointer() returns false, the mouse is not on this screen.
+ */
+ this_poll_mouse.root_x = -1;
+ this_poll_mouse.root_y = -1;
+ this_poll_mouse.child = 0;
+ this_poll_mouse.mask = 0;
+ }
+ else
+ si->last_activity_screen = ssi;
+
+ return device_pointer_moved_p(si, &(ssi->last_poll_mouse), &this_poll_mouse,
+ mods_p, "screen", ssi->number);
+}
+
+
+/* When we aren't using a server extension, this timer is used to periodically
+ wake up and poll the mouse position, which is possibly more reliable than
+ selecting motion events on every window.
+ */
+static void
+check_pointer_timer (XtPointer closure, XtIntervalId *id)
+{
+ int i;
+ saver_info *si = (saver_info *) closure;
+ saver_preferences *p = &si->prefs;
+ Bool active_p = False;
+
+ if (!si->using_proc_interrupts &&
+ (si->using_xidle_extension ||
+ si->using_mit_saver_extension ||
+ si->using_sgi_saver_extension))
+ /* If an extension is in use, we should not be polling the mouse.
+ Unless we're also checking /proc/interrupts, in which case, we should.
+ */
+ abort ();
+
+ if (id && *id == si->check_pointer_timer_id) /* this is us - it's expired */
+ si->check_pointer_timer_id = 0;
+
+ if (si->check_pointer_timer_id) /* only queue one at a time */
+ XtRemoveTimeOut (si->check_pointer_timer_id);
+
+ si->check_pointer_timer_id = /* now re-queue */
+ XtAppAddTimeOut (si->app, p->pointer_timeout, check_pointer_timer,
+ (XtPointer) si);
+
+ for (i = 0; i < si->nscreens; i++)
+ {
+ saver_screen_info *ssi = &si->screens[i];
+ if (pointer_moved_p (ssi, True))
+ active_p = True;
+ }
+
+#ifdef HAVE_PROC_INTERRUPTS
+ if (!active_p &&
+ si->using_proc_interrupts &&
+ proc_interrupts_activity_p (si))
+ {
+ active_p = True;
+ }
+#endif /* HAVE_PROC_INTERRUPTS */
+
+ if (active_p)
+ reset_timers (si);
+
+ check_for_clock_skew (si);
+}
+
+
+/* An unfortunate situation is this: the saver is not active, because the
+ user has been typing. The machine is a laptop. The user closes the lid
+ and suspends it. The CPU halts. Some hours later, the user opens the
+ lid. At this point, Xt's timers will fire, and xscreensaver will blank
+ the screen.
+
+ So far so good -- well, not really, but it's the best that we can do,
+ since the OS doesn't send us a signal *before* shutdown -- but if the
+ user had delayed locking (lockTimeout > 0) then we should start off
+ in the locked state, rather than only locking N minutes from when the
+ lid was opened. Also, eschewing fading is probably a good idea, to
+ clamp down as soon as possible.
+
+ We only do this when we'd be polling the mouse position anyway.
+ This amounts to an assumption that machines with APM support also
+ have /proc/interrupts.
+
+ Now here's a thing that sucks about this: if the user actually changes
+ the time of the machine, it will either trigger or delay the triggering
+ of a lock. On most systems, that requires root, but I'll bet at least
+ some GUI configs let non-root do it. Also, NTP attacks.
+
+ On Linux 2.6.39+ systems, there exists clock_gettime(CLOCK_BOOTTIME)
+ which would allow us to detect the "laptop CPU had been halted" state
+ independently of changes in wall-clock time. But of course that's not
+ portable.
+
+ When the wall clock changes, what do Xt timers do, anyway? If I have
+ a timer set for 30 seconds from now, and adjust the wall clock +15 seconds,
+ does the timer fire 30 seconds from now or 15? I actually have no idea.
+ It does not appear to be specified.
+ */
+static void
+check_for_clock_skew (saver_info *si)
+{
+ saver_preferences *p = &si->prefs;
+ time_t now = time ((time_t *) 0);
+ long shift = now - si->last_wall_clock_time;
+
+ if (p->debug_p)
+ {
+ int i = (si->last_wall_clock_time == 0 ? 0 : shift);
+ fprintf (stderr,
+ "%s: checking wall clock for hibernation (%d:%02d:%02d).\n",
+ blurb(),
+ (i / (60 * 60)), ((i / 60) % 60), (i % 60));
+ }
+
+ if (si->last_wall_clock_time != 0 &&
+ shift > (p->timeout / 1000))
+ {
+ if (p->verbose_p)
+ fprintf (stderr, "%s: wall clock has jumped by %ld:%02ld:%02ld%s\n",
+ blurb(),
+ (shift / (60 * 60)), ((shift / 60) % 60), (shift % 60),
+ (p->mode == DONT_BLANK ? " while saver disabled" : ""));
+
+ /* If the saver is entirely disabled, there's no need to do the
+ emergency-blank-and-lock thing.
+ */
+ if (p->mode != DONT_BLANK)
+ {
+ si->emergency_lock_p = True;
+ idle_timer ((XtPointer) si, 0);
+ }
+ }
+
+ si->last_wall_clock_time = now;
+}
+
+
+
+static void
+dispatch_event (saver_info *si, XEvent *event)
+{
+ /* If this is for the splash dialog, pass it along.
+ Note that the password dialog is handled with its own event loop,
+ so events for that window will never come through here.
+ */
+ if (si->splash_dialog && event->xany.window == si->splash_dialog)
+ handle_splash_event (si, event);
+
+ XtDispatchEvent (event);
+}
+
+
+static void
+swallow_unlock_typeahead_events (saver_info *si, XEvent *e)
+{
+ XEvent event;
+ char buf [100];
+ int i = 0;
+
+ memset (buf, 0, sizeof(buf));
+
+ event = *e;
+
+ do
+ {
+ if (event.xany.type == KeyPress)
+ {
+ char s[2];
+ int size = XLookupString ((XKeyEvent *) &event, s, 1, 0, 0);
+ if (size != 1) continue;
+ switch (*s)
+ {
+ case '\010': case '\177': /* Backspace */
+ if (i > 0) i--;
+ break;
+ case '\025': case '\030': /* Erase line */
+ case '\012': case '\015': /* Enter */
+ case '\033': /* ESC */
+ i = 0;
+ break;
+ case '\040': /* Space */
+ if (i == 0)
+ break; /* ignore space at beginning of line */
+ /* else, fall through */
+ default:
+ buf [i++] = *s;
+ break;
+ }
+ }
+
+ } while (i < sizeof(buf)-1 &&
+ XCheckMaskEvent (si->dpy, KeyPressMask, &event));
+
+ buf[i] = 0;
+
+ if (si->unlock_typeahead)
+ {
+ memset (si->unlock_typeahead, 0, strlen(si->unlock_typeahead));
+ free (si->unlock_typeahead);
+ }
+
+ if (i > 0)
+ si->unlock_typeahead = strdup (buf);
+ else
+ si->unlock_typeahead = 0;
+
+ memset (buf, 0, sizeof(buf));
+}
+
+
+/* methods of detecting idleness:
+
+ explicitly informed by SGI SCREEN_SAVER server event;
+ explicitly informed by MIT-SCREEN-SAVER server event;
+ poll server idle time with XIDLE extension;
+ select events on all windows, and note absence of recent events;
+ note that /proc/interrupts has not changed in a while;
+ activated by clientmessage.
+
+ methods of detecting non-idleness:
+
+ read events on the xscreensaver window;
+ explicitly informed by SGI SCREEN_SAVER server event;
+ explicitly informed by MIT-SCREEN-SAVER server event;
+ select events on all windows, and note events on any of them;
+ note that a client updated their window's _NET_WM_USER_TIME property;
+ note that /proc/interrupts has changed;
+ deactivated by clientmessage.
+
+ I trust that explains why this function is a big hairy mess.
+ */
+void
+sleep_until_idle (saver_info *si, Bool until_idle_p)
+{
+ saver_preferences *p = &si->prefs;
+
+ /* 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 */
+# ifdef HAVE_MIT_SAVER_EXTENSION
+ XScreenSaverNotifyEvent sevent;
+# endif /* HAVE_MIT_SAVER_EXTENSION */
+ } event;
+
+ /* We need to select events on all windows if we're not using any extensions.
+ Otherwise, we don't need to. */
+ Bool scanning_all_windows = !(si->using_xidle_extension ||
+ si->using_mit_saver_extension ||
+ si->using_sgi_saver_extension);
+
+ /* We need to periodically wake up and check for idleness if we're not using
+ any extensions, or if we're using the XIDLE extension. The other two
+ extensions explicitly deliver events when we go idle/non-idle, so we
+ don't need to poll. */
+ Bool polling_for_idleness = !(si->using_mit_saver_extension ||
+ si->using_sgi_saver_extension);
+
+ /* Whether we need to periodically wake up and check to see if the mouse has
+ moved. We only need to do this when not using any extensions. The reason
+ this isn't the same as `polling_for_idleness' is that the "idleness" poll
+ can happen (for example) 5 minutes from now, whereas the mouse-position
+ poll should happen with low periodicity. We don't need to poll the mouse
+ position with the XIDLE extension, but we do need to periodically wake up
+ and query the server with that extension. For our purposes, polling
+ /proc/interrupts is just like polling the mouse position. It has to
+ happen on the same kind of schedule. */
+ Bool polling_mouse_position = (si->using_proc_interrupts ||
+ !(si->using_xidle_extension ||
+ si->using_mit_saver_extension ||
+ si->using_sgi_saver_extension) ||
+ si->using_xinput_extension);
+
+ const char *why = 0; /* What caused the idle-state to change? */
+
+ if (until_idle_p)
+ {
+ if (polling_for_idleness)
+ /* This causes a no-op event to be delivered to us in a while, so that
+ we come back around through the event loop again. */
+ schedule_wakeup_event (si, p->timeout, p->debug_p);
+
+ if (polling_mouse_position)
+ /* Check to see if the mouse has moved, and set up a repeating timer
+ to do so periodically (typically, every 5 seconds.) */
+ check_pointer_timer ((XtPointer) si, 0);
+ }
+
+ while (1)
+ {
+ XtAppNextEvent (si->app, &event.x_event);
+
+ switch (event.x_event.xany.type) {
+ case 0: /* our synthetic "timeout" event has been signalled */
+ if (until_idle_p)
+ {
+ Time idle;
+
+ /* We may be idle; check one last time to see if the mouse has
+ moved, just in case the idle-timer went off within the 5 second
+ window between mouse polling. If the mouse has moved, then
+ check_pointer_timer() will reset last_activity_time.
+ */
+ if (polling_mouse_position)
+ check_pointer_timer ((XtPointer) si, 0);
+
+#ifdef HAVE_XIDLE_EXTENSION
+ if (si->using_xidle_extension)
+ {
+ /* The XIDLE extension uses the synthetic event to prod us into
+ re-asking the server how long the user has been idle. */
+ if (! XGetIdleTime (si->dpy, &idle))
+ {
+ fprintf (stderr, "%s: XGetIdleTime() failed.\n", blurb());
+ saver_exit (si, 1, 0);
+ }
+ }
+ else
+#endif /* HAVE_XIDLE_EXTENSION */
+#ifdef HAVE_MIT_SAVER_EXTENSION
+ if (si->using_mit_saver_extension)
+ {
+ /* We don't need to do anything in this case - the synthetic
+ event isn't necessary, as we get sent specific events
+ to wake us up. In fact, this event generally shouldn't
+ be being delivered when the MIT extension is in use. */
+ idle = 0;
+ }
+ else
+#endif /* HAVE_MIT_SAVER_EXTENSION */
+#ifdef HAVE_SGI_SAVER_EXTENSION
+ if (si->using_sgi_saver_extension)
+ {
+ /* We don't need to do anything in this case - the synthetic
+ event isn't necessary, as we get sent specific events
+ to wake us up. In fact, this event generally shouldn't
+ be being delivered when the SGI extension is in use. */
+ idle = 0;
+ }
+ else
+#endif /* HAVE_SGI_SAVER_EXTENSION */
+ {
+ /* Otherwise, no server extension is in use. The synthetic
+ event was to tell us to wake up and see if the user is now
+ idle. Compute the amount of idle time by comparing the
+ `last_activity_time' to the wall clock. The l_a_t was set
+ by calling `reset_timers()', which is called only in only
+ two situations: when polling the mouse position has revealed
+ the the mouse has moved (user activity) or when we have read
+ an event (again, user activity.)
+ */
+ idle = 1000 * (si->last_activity_time - time ((time_t *) 0));
+ }
+
+ if (idle >= p->timeout)
+ {
+ /* Look, we've been idle long enough. We're done. */
+ why = "timeout";
+ goto DONE;
+ }
+ else if (si->emergency_lock_p)
+ {
+ /* Oops, the wall clock has jumped far into the future, so
+ we need to lock down in a hurry! */
+ why = "large wall clock change";
+ goto DONE;
+ }
+ else
+ {
+ /* The event went off, but it turns out that the user has not
+ yet been idle for long enough. So re-signal the event.
+ Be economical: if we should blank after 5 minutes, and the
+ user has been idle for 2 minutes, then set this timer to
+ go off in 3 minutes.
+ */
+ if (polling_for_idleness)
+ schedule_wakeup_event (si, p->timeout - idle, p->debug_p);
+ }
+ }
+ break;
+
+ case ClientMessage:
+ if (handle_clientmessage (si, &event.x_event, until_idle_p))
+ {
+ why = "ClientMessage";
+ goto DONE;
+ }
+ break;
+
+ case CreateNotify:
+ /* A window has been created on the screen somewhere. If we're
+ supposed to scan all windows for events, prepare this window. */
+ if (scanning_all_windows)
+ {
+ Window w = event.x_event.xcreatewindow.window;
+ start_notice_events_timer (si, w, p->debug_p);
+ }
+ break;
+
+ case KeyPress:
+ case ButtonPress:
+ /* Ignore release events so that hitting ESC at the password dialog
+ doesn't result in the password dialog coming right back again when
+ the fucking release key is seen! */
+ /* case KeyRelease:*/
+ /* case ButtonRelease:*/
+ case MotionNotify:
+
+ if (p->debug_p)
+ {
+ Window root=0, window=0;
+ int x=-1, y=-1;
+ const char *type = 0;
+ if (event.x_event.xany.type == MotionNotify)
+ {
+ /*type = "MotionNotify";*/
+ root = event.x_event.xmotion.root;
+ window = event.x_event.xmotion.window;
+ x = event.x_event.xmotion.x_root;
+ y = event.x_event.xmotion.y_root;
+ }
+ else if (event.x_event.xany.type == KeyPress)
+ {
+ type = "KeyPress";
+ root = event.x_event.xkey.root;
+ window = event.x_event.xkey.window;
+ x = y = -1;
+ }
+ else if (event.x_event.xany.type == ButtonPress)
+ {
+ type = "ButtonPress";
+ root = event.x_event.xkey.root;
+ window = event.x_event.xkey.window;
+ x = event.x_event.xmotion.x_root;
+ y = event.x_event.xmotion.y_root;
+ }
+
+ if (type)
+ {
+ int i;
+ for (i = 0; i < si->nscreens; i++)
+ if (root == RootWindowOfScreen (si->screens[i].screen))
+ break;
+ fprintf (stderr,"%s: %d: %s on 0x%lx",
+ blurb(), i, type, (unsigned long) window);
+
+ /* Be careful never to do this unless in -debug mode, as
+ this could expose characters from the unlock password. */
+ if (p->debug_p && event.x_event.xany.type == KeyPress)
+ {
+ KeySym keysym;
+ char c = 0;
+ XLookupString (&event.x_event.xkey, &c, 1, &keysym, 0);
+ fprintf (stderr, " (%s%s)",
+ (event.x_event.xkey.send_event ? "synthetic " : ""),
+ XKeysymToString (keysym));
+ }
+
+ if (x == -1)
+ fprintf (stderr, "\n");
+ else
+ fprintf (stderr, " at %d,%d.\n", x, y);
+ }
+ }
+
+ /* If any widgets want to handle this event, let them. */
+ dispatch_event (si, &event.x_event);
+
+
+ /* If we got a MotionNotify event, figure out what screen it
+ was on and poll the mouse there: if the mouse hasn't moved
+ far enough to count as "real" motion, then ignore this
+ event.
+ */
+ if (event.x_event.xany.type == MotionNotify)
+ {
+ int i;
+ for (i = 0; i < si->nscreens; i++)
+ if (event.x_event.xmotion.root ==
+ RootWindowOfScreen (si->screens[i].screen))
+ break;
+ if (i < si->nscreens)
+ {
+ if (!pointer_moved_p (&si->screens[i], False))
+ continue;
+ }
+ }
+
+
+ /* We got a user event.
+ If we're waiting for the user to become active, this is it.
+ If we're waiting until the user becomes idle, reset the timers
+ (since now we have longer to wait.)
+ */
+ if (!until_idle_p)
+ {
+ if (si->demoing_p &&
+ (event.x_event.xany.type == MotionNotify ||
+ event.x_event.xany.type == KeyRelease))
+ /* When we're demoing a single hack, mouse motion doesn't
+ cause deactivation. Only clicks and keypresses do. */
+ ;
+ else
+ {
+ /* If we're not demoing, then any activity causes deactivation.
+ */
+ why = (event.x_event.xany.type == MotionNotify ?"mouse motion":
+ event.x_event.xany.type == KeyPress?"keyboard activity":
+ event.x_event.xany.type == ButtonPress ? "mouse click" :
+ "unknown user activity");
+ goto DONE;
+ }
+ }
+ else
+ reset_timers (si);
+
+ break;
+
+ case PropertyNotify:
+
+ /* Starting 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, those applications *do* update the WM_USER_TIME
+ property on their own windows every time they recieve a secret
+ KeyPress, so we must *also* monitor that property on every
+ window, and treat changes to it as identical to KeyPress.
+
+ _NET_WM_USER_TIME is documented (such as it is) here:
+
+ http://standards.freedesktop.org/wm-spec/latest/ar01s05.html
+ #idm139870829932528
+
+ Specifically:
+
+ "Contains the XServer time at which last user activity in this
+ window took place. [...] A client [...] might, for example,
+ use the timestamp of the last KeyPress or ButtonPress event."
+
+ As of early 2016, KDE4 does something really stupid, though: some
+ hidden power management thing reduces the display brightness 150
+ seconds after the screen is blanked -- and sets a WM_USER_TIME
+ property on a hidden "kded4" window whose time is in the distant
+ past (the time at which the X server launched).
+
+ So we ignore any WM_USER_TIME whose timestamp is more than a
+ couple seconds old.
+ */
+ if (event.x_event.xproperty.state == PropertyNewValue &&
+ event.x_event.xproperty.atom == XA_NET_WM_USER_TIME)
+ {
+ int threshold = 2; /* seconds */
+ Bool bogus_p = True;
+ Window w = event.x_event.xproperty.window;
+
+ Atom type;
+ int format;
+ unsigned long nitems, bytesafter;
+ unsigned char *data = 0;
+ Cardinal user_time = 0;
+ XErrorHandler old_handler = XSetErrorHandler (BadWindow_ehandler);
+
+ if (XGetWindowProperty (si->dpy, w,
+ XA_NET_WM_USER_TIME, 0L, 1L, False,
+ XA_CARDINAL, &type, &format, &nitems,
+ &bytesafter, &data)
+ == Success &&
+ data &&
+ type == XA_CARDINAL &&
+ format == 32 &&
+ nitems == 1)
+ {
+ long diff;
+ user_time = ((Cardinal *) data)[0];
+ diff = event.x_event.xproperty.time - user_time;
+ if (diff >= 0 && diff < threshold)
+ bogus_p = False;
+ }
+
+ if (data) XFree (data);
+
+ why = "WM_USER_TIME";
+
+ if (p->debug_p)
+ {
+ XWindowAttributes xgwa;
+ int i;
+
+ XGetWindowAttributes (si->dpy, w, &xgwa);
+ for (i = 0; i < si->nscreens; i++)
+ if (xgwa.root == RootWindowOfScreen (si->screens[i].screen))
+ break;
+ fprintf (stderr,"%s: %d: %s = %ld%s on 0x%lx\n",
+ blurb(), i, why, (unsigned long) user_time,
+ (bogus_p ? " (bad)" : ""),
+ (unsigned long) w);
+ }
+
+ XSync (si->dpy, False);
+ XSetErrorHandler (old_handler);
+
+ if (bogus_p)
+ break;
+ else if (until_idle_p)
+ reset_timers (si);
+ else
+ goto DONE;
+ }
+ break;
+
+ default:
+
+#ifdef HAVE_MIT_SAVER_EXTENSION
+ if (event.x_event.type == si->mit_saver_ext_event_number)
+ {
+ /* This event's number is that of the MIT-SCREEN-SAVER server
+ extension. This extension has one event number, and the event
+ itself contains sub-codes that say what kind of event it was
+ (an "idle" or "not-idle" event.)
+ */
+ if (event.sevent.state == ScreenSaverOn)
+ {
+ int i = 0;
+ if (p->verbose_p)
+ fprintf (stderr, "%s: MIT ScreenSaverOn event received.\n",
+ blurb());
+
+ /* Get the "real" server window(s) out of the way as soon
+ as possible. */
+ 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);
+ }
+
+ if (event.sevent.kind != ScreenSaverExternal)
+ {
+ fprintf (stderr,
+ "%s: ScreenSaverOn event wasn't of type External!\n",
+ blurb());
+ }
+
+ if (until_idle_p)
+ {
+ why = "MIT ScreenSaverOn";
+ goto DONE;
+ }
+ }
+ else if (event.sevent.state == ScreenSaverOff)
+ {
+ if (p->verbose_p)
+ fprintf (stderr, "%s: MIT ScreenSaverOff event received.\n",
+ blurb());
+ if (!until_idle_p)
+ {
+ why = "MIT ScreenSaverOff";
+ goto DONE;
+ }
+ }
+ else
+ fprintf (stderr,
+ "%s: unknown MIT-SCREEN-SAVER event %d received!\n",
+ blurb(), event.sevent.state);
+ }
+ else
+
+#endif /* HAVE_MIT_SAVER_EXTENSION */
+
+
+#ifdef HAVE_SGI_SAVER_EXTENSION
+ if (event.x_event.type == (si->sgi_saver_ext_event_number + ScreenSaverStart))
+ {
+ /* The SGI SCREEN_SAVER server extension has two event numbers,
+ and this event matches the "idle" event. */
+ if (p->verbose_p)
+ fprintf (stderr, "%s: SGI ScreenSaverStart event received.\n",
+ blurb());
+
+ if (until_idle_p)
+ {
+ why = "SGI ScreenSaverStart";
+ goto DONE;
+ }
+ }
+ else if (event.x_event.type == (si->sgi_saver_ext_event_number +
+ ScreenSaverEnd))
+ {
+ /* The SGI SCREEN_SAVER server extension has two event numbers,
+ and this event matches the "idle" event. */
+ if (p->verbose_p)
+ fprintf (stderr, "%s: SGI ScreenSaverEnd event received.\n",
+ blurb());
+ if (!until_idle_p)
+ {
+ why = "SGI ScreenSaverEnd";
+ goto DONE;
+ }
+ }
+ else
+#endif /* HAVE_SGI_SAVER_EXTENSION */
+
+#ifdef HAVE_XINPUT
+ /* If we got a MotionNotify event, check to see if the mouse has
+ moved far enough to count as "real" motion, if not, then ignore
+ this event.
+ */
+ if ((si->num_xinput_devices > 0) &&
+ (event.x_event.type == si->xinput_DeviceMotionNotify))
+ {
+ XDeviceMotionEvent *dme = (XDeviceMotionEvent *) &event;
+ poll_mouse_data *last_poll_mouse = NULL;
+ int d;
+
+ for (d = 0; d < si->num_xinput_devices; d++)
+ {
+ if (si->xinput_devices[d].device->device_id == dme->deviceid)
+ {
+ last_poll_mouse = &(si->xinput_devices[d].last_poll_mouse);
+ break;
+ }
+ }
+
+ if (last_poll_mouse)
+ {
+ poll_mouse_data this_poll_mouse;
+ this_poll_mouse.root_x = dme->x_root;
+ this_poll_mouse.root_y = dme->y_root;
+ this_poll_mouse.child = dme->subwindow;
+ this_poll_mouse.mask = dme->device_state;
+ this_poll_mouse.time = dme->time / 1000; /* milliseconds */
+
+ if (!device_pointer_moved_p (si, last_poll_mouse,
+ &this_poll_mouse, False,
+ "device", dme->deviceid))
+ continue;
+ }
+ else if (p->debug_p)
+ fprintf (stderr,
+ "%s: received MotionNotify from unknown device %d\n",
+ blurb(), (int) dme->deviceid);
+ }
+
+ if ((!until_idle_p) &&
+ (si->num_xinput_devices > 0) &&
+ (event.x_event.type == si->xinput_DeviceMotionNotify ||
+ event.x_event.type == si->xinput_DeviceButtonPress))
+ /* Ignore DeviceButtonRelease, see ButtonRelease comment above. */
+ {
+
+ dispatch_event (si, &event.x_event);
+ if (si->demoing_p &&
+ event.x_event.type == si->xinput_DeviceMotionNotify)
+ /* When we're demoing a single hack, mouse motion doesn't
+ cause deactivation. Only clicks and keypresses do. */
+ ;
+ else
+ /* If we're not demoing, then any activity causes deactivation.
+ */
+ {
+ why = (event.x_event.type == si->xinput_DeviceMotionNotify
+ ? "XI mouse motion" :
+ event.x_event.type == si->xinput_DeviceButtonPress
+ ? "XI mouse click" : "unknown XINPUT event");
+ goto DONE;
+ }
+ }
+ else
+#endif /* HAVE_XINPUT */
+
+#ifdef HAVE_RANDR
+ 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)
+ {
+ /* XRRRootToScreen is in Xrandr.h 1.4, 2001/06/07 */
+ int screen = XRRRootToScreen (si->dpy, event.xrr_event.window);
+ fprintf (stderr, "%s: %d: screen change event received\n",
+ blurb(), screen);
+ }
+
+# ifdef RRScreenChangeNotifyMask
+ /* Inform Xlib that it's ok to update its data structures. */
+ XRRUpdateConfiguration (&event.x_event); /* Xrandr.h 1.9, 2002/09/29 */
+# endif /* RRScreenChangeNotifyMask */
+
+ /* 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);
+ }
+ resize_screensaver_window (si);
+ }
+ }
+ else
+#endif /* HAVE_RANDR */
+
+ /* Just some random event. Let the Widgets handle it, if desired. */
+ dispatch_event (si, &event.x_event);
+ }
+ }
+ DONE:
+
+ if (p->verbose_p)
+ {
+ if (! why) why = "unknown reason";
+ fprintf (stderr, "%s: %s (%s)\n", blurb(),
+ (until_idle_p ? "user is idle" : "user is active"),
+ why);
+ }
+
+ /* If there's a user event on the queue, swallow it.
+ If we're using a server extension, and the user becomes active, we
+ get the extension event before the user event -- so the keypress or
+ motion or whatever is still on the queue. This makes "unfade" not
+ work, because it sees that event, and bugs out. (This problem
+ doesn't exhibit itself without an extension, because in that case,
+ there's only one event generated by user activity, not two.)
+ */
+ if (!until_idle_p && si->locked_p)
+ swallow_unlock_typeahead_events (si, &event.x_event);
+ else
+ while (XCheckMaskEvent (si->dpy,
+ (KeyPressMask|ButtonPressMask|PointerMotionMask),
+ &event.x_event))
+ ;
+
+
+ if (si->check_pointer_timer_id)
+ {
+ XtRemoveTimeOut (si->check_pointer_timer_id);
+ si->check_pointer_timer_id = 0;
+ }
+ if (si->timer_id)
+ {
+ XtRemoveTimeOut (si->timer_id);
+ si->timer_id = 0;
+ }
+
+ if (until_idle_p && si->cycle_id) /* no cycle timer when inactive */
+ abort ();
+}
+
+
+
+/* Some crap for dealing with /proc/interrupts.
+
+ On Linux systems, it's possible to see the hardware interrupt count
+ associated with the keyboard. We can therefore use that as another method
+ of detecting idleness.
+
+ Why is it a good idea to do this? Because it lets us detect keyboard
+ activity that is not associated with X events. For example, if the user
+ has switched to another virtual console, it's good for xscreensaver to not
+ be running graphics hacks on the (non-visible) X display. The common
+ complaint that checking /proc/interrupts addresses is that the user is
+ playing Quake on a non-X console, and the GL hacks are perceptibly slowing
+ the game...
+
+ This is tricky for a number of reasons.
+
+ * First, we must be sure to only do this when running on an X server that
+ is on the local machine (because otherwise, we'd be reacting to the
+ wrong keyboard.) The way we do this is by noting that the $DISPLAY is
+ pointing to display 0 on the local machine. It *could* be that display
+ 1 is also on the local machine (e.g., two X servers, each on a different
+ virtual-terminal) but it's also possible that screen 1 is an X terminal,
+ using this machine as the host. So we can't take that chance.
+
+ * Second, one can only access these interrupt numbers in a completely
+ and utterly brain-damaged way. You would think that one would use an
+ ioctl for this. But no. The ONLY way to get this information is to
+ open the pseudo-file /proc/interrupts AS A FILE, and read the numbers
+ out of it TEXTUALLY. Because this is Unix, and all the world's a file,
+ and the only real data type is the short-line sequence of ASCII bytes.
+
+ Now it's all well and good that the /proc/interrupts pseudo-file
+ exists; that's a clever idea, and a useful API for things that are
+ already textually oriented, like shell scripts, and users doing
+ interactive debugging sessions. But to make a *C PROGRAM* open a file
+ and parse the textual representation of integers out of it is just
+ insane.
+
+ * Third, you can't just hold the file open, and fseek() back to the
+ beginning to get updated data! If you do that, the data never changes.
+ And I don't want to call open() every five seconds, because I don't want
+ to risk going to disk for any inodes. It turns out that if you dup()
+ it early, then each copy gets fresh data, so we can get around that in
+ this way (but for how many releases, one might wonder?)
+
+ * Fourth, the format of the output of the /proc/interrupts file is
+ undocumented, and has changed several times already! In Linux 2.0.33,
+ even on a multiprocessor machine, it looks like this:
+
+ 0: 309453991 timer
+ 1: 4771729 keyboard
+
+ but in Linux 2.2 and 2.4 kernels with MP machines, it looks like this:
+
+ CPU0 CPU1
+ 0: 1671450 1672618 IO-APIC-edge timer
+ 1: 13037 13495 IO-APIC-edge keyboard
+
+ and in Linux 2.6, it's gotten even goofier: now there are two lines
+ labelled "i8042". One of them is the keyboard, and one of them is
+ the PS/2 mouse -- and of course, you can't tell them apart, except
+ by wiggling the mouse and noting which one changes:
+
+ CPU0 CPU1
+ 1: 32051 30864 IO-APIC-edge i8042
+ 12: 476577 479913 IO-APIC-edge i8042
+
+ Joy! So how are we expected to parse that? Well, this code doesn't
+ parse it: it saves the first line with the string "keyboard" (or
+ "i8042") in it, and does a string-comparison to note when it has
+ changed. If there are two "i8042" lines, we assume the first is
+ the keyboard and the second is the mouse (doesn't matter which is
+ which, really, as long as we don't compare them against each other.)
+
+ Thanks to Nat Friedman <nat@nat.org> for figuring out most of this crap.
+
+ Note that if you have a serial or USB mouse, or a USB keyboard, it won't
+ detect it. That's because there's no way to tell the difference between a
+ serial mouse and a general serial port, and all USB devices look the same
+ from here. It would be somewhat unfortunate to have the screensaver turn
+ off when the modem on COM1 burped, or when a USB disk was accessed.
+ */
+
+
+#ifdef HAVE_PROC_INTERRUPTS
+
+#define PROC_INTERRUPTS "/proc/interrupts"
+
+Bool
+query_proc_interrupts_available (saver_info *si, const char **why)
+{
+ /* We can use /proc/interrupts if $DISPLAY points to :0, and if the
+ "/proc/interrupts" file exists and is readable.
+ */
+ FILE *f;
+ if (why) *why = 0;
+
+ if (!display_is_on_console_p (si))
+ {
+ if (why) *why = "not on primary console";
+ return False;
+ }
+
+ f = fopen (PROC_INTERRUPTS, "r");
+ if (!f)
+ {
+ if (why) *why = "does not exist";
+ return False;
+ }
+
+ fclose (f);
+ return True;
+}
+
+
+static Bool
+proc_interrupts_activity_p (saver_info *si)
+{
+ static FILE *f0 = 0;
+ FILE *f1 = 0;
+ int fd;
+ static char last_kbd_line[255] = { 0, };
+ static char last_ptr_line[255] = { 0, };
+ char new_line[sizeof(last_kbd_line)];
+ Bool checked_kbd = False, kbd_changed = False;
+ Bool checked_ptr = False, ptr_changed = False;
+ int i8042_count = 0;
+
+ if (!f0)
+ {
+ /* First time -- open the file. */
+ f0 = fopen (PROC_INTERRUPTS, "r");
+ if (!f0)
+ {
+ char buf[255];
+ sprintf(buf, "%s: error opening %s", blurb(), PROC_INTERRUPTS);
+ perror (buf);
+ goto FAIL;
+ }
+
+# if defined(HAVE_FCNTL) && defined(FD_CLOEXEC)
+ /* Close this fd upon exec instead of inheriting / leaking it. */
+ if (fcntl (fileno (f0), F_SETFD, FD_CLOEXEC) != 0)
+ perror ("fcntl: CLOEXEC:");
+# endif
+ }
+
+ if (f0 == (FILE *) -1) /* means we got an error initializing. */
+ return False;
+
+ fd = dup (fileno (f0));
+ if (fd < 0)
+ {
+ char buf[255];
+ sprintf(buf, "%s: could not dup() the %s fd", blurb(), PROC_INTERRUPTS);
+ perror (buf);
+ goto FAIL;
+ }
+
+ f1 = fdopen (fd, "r");
+ if (!f1)
+ {
+ char buf[255];
+ sprintf(buf, "%s: could not fdopen() the %s fd", blurb(),
+ PROC_INTERRUPTS);
+ perror (buf);
+ goto FAIL;
+ }
+
+ /* Actually, I'm unclear on why this fseek() is necessary, given the timing
+ of the dup() above, but it is. */
+ if (fseek (f1, 0, SEEK_SET) != 0)
+ {
+ char buf[255];
+ sprintf(buf, "%s: error rewinding %s", blurb(), PROC_INTERRUPTS);
+ perror (buf);
+ goto FAIL;
+ }
+
+ /* Now read through the pseudo-file until we find the "keyboard",
+ "PS/2 mouse", or "i8042" lines. */
+
+ while (fgets (new_line, sizeof(new_line)-1, f1))
+ {
+ Bool i8042_p = !!strstr (new_line, "i8042");
+ if (i8042_p) i8042_count++;
+
+ if (strchr (new_line, ','))
+ {
+ /* Ignore any line that has a comma on it: this is because
+ a setup like this:
+
+ 12: 930935 XT-PIC usb-uhci, PS/2 Mouse
+
+ is really bad news. It *looks* like we can note mouse
+ activity from that line, but really, that interrupt gets
+ fired any time any USB device has activity! So we have
+ to ignore any shared IRQs.
+ */
+ }
+ else if (!checked_kbd &&
+ (strstr (new_line, "keyboard") ||
+ (i8042_p && i8042_count == 1)))
+ {
+ /* Assume the keyboard interrupt is the line that says "keyboard",
+ or the *first* line that says "i8042".
+ */
+ kbd_changed = (*last_kbd_line && !!strcmp (new_line, last_kbd_line));
+ strcpy (last_kbd_line, new_line);
+ checked_kbd = True;
+ }
+ else if (!checked_ptr &&
+ (strstr (new_line, "PS/2 Mouse") ||
+ (i8042_p && i8042_count == 2)))
+ {
+ /* Assume the mouse interrupt is the line that says "PS/2 mouse",
+ or the *second* line that says "i8042".
+ */
+ ptr_changed = (*last_ptr_line && !!strcmp (new_line, last_ptr_line));
+ strcpy (last_ptr_line, new_line);
+ checked_ptr = True;
+ }
+
+ if (checked_kbd && checked_ptr)
+ break;
+ }
+
+ if (checked_kbd || checked_ptr)
+ {
+ fclose (f1);
+
+ if (si->prefs.debug_p && (kbd_changed || ptr_changed))
+ fprintf (stderr, "%s: /proc/interrupts activity: %s\n",
+ blurb(),
+ ((kbd_changed && ptr_changed) ? "mouse and kbd" :
+ kbd_changed ? "kbd" :
+ ptr_changed ? "mouse" : "ERR"));
+
+ return (kbd_changed || ptr_changed);
+ }
+
+
+ /* If we got here, we didn't find either a "keyboard" or a "PS/2 Mouse"
+ line in the file at all. */
+ fprintf (stderr, "%s: no keyboard or mouse data in %s?\n",
+ blurb(), PROC_INTERRUPTS);
+
+ FAIL:
+ if (f1)
+ fclose (f1);
+
+ if (f0 && f0 != (FILE *) -1)
+ fclose (f0);
+
+ f0 = (FILE *) -1;
+ return False;
+}
+
+#endif /* HAVE_PROC_INTERRUPTS */
+
+
+/* This timer goes off every few minutes, whether the user is idle or not,
+ to try and clean up anything that has gone wrong.
+
+ It calls disable_builtin_screensaver() so that if xset has been used,
+ or some other program (like xlock) has messed with the XSetScreenSaver()
+ settings, they will be set back to sensible values (if a server extension
+ is in use, messing with xlock can cause xscreensaver to never get a wakeup
+ event, and could cause monitor power-saving to occur, and all manner of
+ heinousness.)
+
+ If the screen is currently blanked, it raises the window, in case some
+ other window has been mapped on top of it.
+
+ If the screen is currently blanked, and there is no hack running, it
+ clears the window, in case there is an error message printed on it (we
+ don't want the error message to burn in.)
+ */
+
+static void
+watchdog_timer (XtPointer closure, XtIntervalId *id)
+{
+ saver_info *si = (saver_info *) closure;
+ saver_preferences *p = &si->prefs;
+
+ disable_builtin_screensaver (si, False);
+
+ /* 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->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 (si->screen_blanked_p)
+ {
+ Bool running_p = screenhack_running_p (si);
+
+ if (si->dbox_up_p)
+ {
+ if (si->prefs.debug_p)
+ fprintf (stderr, "%s: dialog box is up: not raising screen.\n",
+ blurb());
+ }
+ else
+ {
+ if (si->prefs.debug_p)
+ fprintf (stderr, "%s: watchdog timer raising %sscreen.\n",
+ blurb(), (running_p ? "" : "and clearing "));
+
+ raise_window (si, True, True, running_p);
+ }
+
+ if (screenhack_running_p (si) &&
+ !monitor_powered_on_p (si))
+ {
+ int i;
+ if (si->prefs.verbose_p)
+ fprintf (stderr,
+ "%s: X says 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, True);
+ }
+}
+
+
+void
+reset_watchdog_timer (saver_info *si, Bool on_p)
+{
+ saver_preferences *p = &si->prefs;
+
+ if (si->watchdog_id)
+ {
+ XtRemoveTimeOut (si->watchdog_id);
+ si->watchdog_id = 0;
+ }
+
+ if (on_p && p->watchdog_timeout)
+ {
+ 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);
+ }
+}
+
+
+/* It's possible that a race condition could have led to the saver
+ window being unexpectedly still mapped. This can happen like so:
+
+ - screen is blanked
+ - hack is launched
+ - that hack tries to grab a screen image (it does this by
+ first unmapping the saver window, then remapping it.)
+ - hack unmaps window
+ - hack waits
+ - user becomes active
+ - hack re-maps window (*)
+ - driver kills subprocess
+ - driver unmaps window (**)
+
+ The race is that (*) might have been sent to the server before
+ the client process was killed, but, due to scheduling randomness,
+ might not have been received by the server until after (**).
+ In other words, (*) and (**) might happen out of order, meaning
+ the driver will unmap the window, and then after that, the
+ recently-dead client will re-map it. This leaves the user
+ locked out (it looks like a desktop, but it's not!)
+
+ To avoid this: after un-blanking the screen, we launch a timer
+ that wakes up once a second for ten seconds, and makes damned
+ sure that the window is still unmapped.
+ */
+
+void
+de_race_timer (XtPointer closure, XtIntervalId *id)
+{
+ saver_info *si = (saver_info *) closure;
+ saver_preferences *p = &si->prefs;
+ int secs = 1;
+
+ if (id == 0) /* if id is 0, this is the initialization call. */
+ {
+ si->de_race_ticks = 10;
+ if (p->verbose_p)
+ fprintf (stderr, "%s: starting de-race timer (%d seconds.)\n",
+ blurb(), si->de_race_ticks);
+ }
+ else
+ {
+ int i;
+ XSync (si->dpy, False);
+ for (i = 0; i < si->nscreens; i++)
+ {
+ saver_screen_info *ssi = &si->screens[i];
+ Window w = ssi->screensaver_window;
+ XWindowAttributes xgwa;
+ XGetWindowAttributes (si->dpy, w, &xgwa);
+ if (xgwa.map_state != IsUnmapped)
+ {
+ if (p->verbose_p)
+ fprintf (stderr,
+ "%s: %d: client race! emergency unmap 0x%lx.\n",
+ blurb(), i, (unsigned long) w);
+ XUnmapWindow (si->dpy, w);
+ }
+ else if (p->debug_p)
+ fprintf (stderr, "%s: %d: (de-race of 0x%lx is cool.)\n",
+ blurb(), i, (unsigned long) w);
+ }
+ XSync (si->dpy, False);
+
+ si->de_race_ticks--;
+ }
+
+ if (id && *id == si->de_race_id)
+ si->de_race_id = 0;
+
+ if (si->de_race_id) abort();
+
+ if (si->de_race_ticks <= 0)
+ {
+ si->de_race_id = 0;
+ if (p->verbose_p)
+ fprintf (stderr, "%s: de-race completed.\n", blurb());
+ }
+ else
+ {
+ si->de_race_id = XtAppAddTimeOut (si->app, secs * 1000,
+ de_race_timer, closure);
+ }
+}
diff --git a/driver/types.h b/driver/types.h
new file mode 100644
index 0000000..f1630b0
--- /dev/null
+++ b/driver/types.h
@@ -0,0 +1,442 @@
+/* xscreensaver, Copyright (c) 1993-2014 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_TYPES_H__
+#define __XSCREENSAVER_TYPES_H__
+
+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;
+ char *visual;
+ char *name;
+ char *command;
+};
+
+typedef enum {
+ RANDOM_HACKS, ONE_HACK, BLANK_ONLY, DONT_BLANK, RANDOM_HACKS_SAME
+} saver_mode;
+
+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.
+ */
+struct saver_preferences {
+
+ XrmDatabase db; /* The resource database into which the
+ init file is merged, and out of which the
+ preferences are parsed. */
+
+ time_t init_file_date; /* The date (from stat()) of the .xscreensaver
+ file the last time this process read or
+ 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 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. */
+
+# ifdef QUAD_MODE
+ Bool quad_p; /* whether to run four savers per monitor */
+# endif
+
+ screenhack **screenhacks; /* the programs to run */
+ int screenhacks_count;
+
+ saver_mode mode; /* hack-selection mode */
+ 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 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
+ in "Blank Only" mode */
+ Time dpms_standby; /* how long until monitor goes black */
+ Time dpms_suspend; /* how long until monitor power-saves */
+ Time dpms_off; /* how long until monitor powers down */
+
+ 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. */
+
+ text_mode tmode; /* How we generate text to display. */
+ char *text_literal; /* used when tmode is TEXT_LITERAL. */
+ char *text_file; /* used when tmode is TEXT_FILE. */
+ 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. */
+
+ int auth_warning_slack; /* Don't warn about login failures if they
+ all happen within this many seconds of
+ a successful login. */
+};
+
+/* 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.
+ */
+struct saver_info {
+ 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 */
+ Visual **best_gl_visuals; /* visuals for GL hacks on screen N */
+
+ /* =======================================================================
+ 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. */
+
+ 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' */
+ 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;
+
+};
+
+/* 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.
+ */
+struct saver_screen_info {
+ saver_info *global;
+
+ int number; /* The internal ordinal of this screen,
+ counting Xinerama rectangles as separate
+ screens. */
+ int real_screen_number; /* The number of the underlying X screen on
+ which this rectangle lies. */
+ Screen *screen; /* The X screen in question. */
+
+ int x, y, width, height; /* The size and position of this rectangle
+ on its underlying X screen. */
+
+ 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. */
+ 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
+ reasons. This is definitive (even a false
+ value here overrides prefs->install_cmap_p.)
+ */
+ Visual *current_visual; /* The visual of the window. */
+ int current_depth; /* How deep the visual (and the window) are. */
+
+ 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
+
+
+ /* =======================================================================
+ 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
+ ======================================================================= */
+
+ poll_mouse_data last_poll_mouse; /* Used only when no server exts. */
+
+ /* =======================================================================
+ subprocs
+ ======================================================================= */
+
+ 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;
+};
+
+
+#endif /* __XSCREENSAVER_TYPES_H__ */
diff --git a/driver/vms-getpwnam.c b/driver/vms-getpwnam.c
new file mode 100644
index 0000000..ec0650c
--- /dev/null
+++ b/driver/vms-getpwnam.c
@@ -0,0 +1,129 @@
+/*
+ * getpwnam(name) - retrieves a UAF entry
+ *
+ * Author: Patrick L. Mahan
+ * Location: TGV, Inc
+ * Date: 15-Nov-1991
+ *
+ * Purpose: Provides emulation for the UNIX getpwname routine.
+ *
+ * Modification History
+ *
+ * Date | Who | Version | Reason
+ * ------------+-----------+---------------+---------------------------
+ * 15-Nov-1991 | PLM | 1.0 | First Write
+ */
+
+#define PASSWDROUTINES
+
+#include <stdio.h>
+#include <descrip.h>
+#include <uaidef.h>
+#include <string.h>
+#include <stdlib.h>
+#include <starlet.h>
+#include "vms-pwd.h"
+
+struct uic {
+ unsigned short uid;
+ unsigned short gid;
+};
+
+#define TEST(ptr, str) { if (ptr == NULL) { \
+ fprintf(stderr, "getpwnam: memory allocation failure for \"%s\"\n", \
+ str); \
+ return ((struct passwd *)(NULL)); \
+ } }
+
+struct passwd *getpwnam(name)
+char *name;
+{
+ int istatus;
+ int UserNameLen;
+ int UserOwnerLen;
+ int UserDeviceLen;
+ int UserDirLen;
+ static char UserName[13];
+ static char UserOwner[32];
+ static char UserDevice[32];
+ static char UserDir[64];
+ char *cptr, *sptr;
+ unsigned long int UserPwd[2];
+ unsigned short int UserSalt;
+ unsigned long int UserEncrypt;
+ struct uic UicValue;
+ struct passwd *entry;
+
+ struct dsc$descriptor_s VMSNAME =
+ {strlen(name), DSC$K_DTYPE_T, DSC$K_CLASS_S, name};
+
+ struct itmlist3 {
+ unsigned short int length;
+ unsigned short int item;
+ unsigned long int addr;
+ unsigned long int retaddr;
+ } ItemList[] = {
+ {12, UAI$_USERNAME, (unsigned long)&UserName, (unsigned long)&UserNameLen},
+ {8, UAI$_PWD, (unsigned long)&UserPwd, 0},
+ {4, UAI$_UIC, (unsigned long)&UicValue, 0},
+ {32, UAI$_OWNER, (unsigned long)&UserOwner, (unsigned long)&UserOwnerLen},
+ {32, UAI$_DEFDEV, (unsigned long)&UserDevice, (unsigned long)&UserDeviceLen},
+ {64, UAI$_DEFDIR, (unsigned long)&UserDir, (unsigned long)&UserDirLen},
+ {2, UAI$_SALT, (unsigned long)&UserSalt, 0},
+ {4, UAI$_ENCRYPT, (unsigned long)&UserEncrypt, 0},
+ {0, 0, 0, 0}
+ };
+
+ UserNameLen = 0;
+ istatus = sys$getuai (0, 0, &VMSNAME, &ItemList, 0, 0, 0);
+
+ if (!(istatus & 1)) {
+ fprintf (stderr, "getpwnam: unable to retrieve passwd entry for %s\n",
+ name);
+ fprintf (stderr, "getpwnam: vms error number is 0x%x\n", istatus);
+ return ((struct passwd *)NULL);
+ }
+
+ entry = (struct passwd *) calloc (1, sizeof(struct passwd));
+ TEST(entry, "PASSWD_ENTRY");
+
+ entry->pw_uid = UicValue.uid;
+ entry->pw_gid = UicValue.gid;
+ entry->pw_salt = UserSalt;
+ entry->pw_encrypt = UserEncrypt;
+
+ sptr = UserName;
+ cptr = calloc (UserNameLen+1, sizeof(char));
+ TEST(cptr, "USERNAME");
+ strncpy (cptr, sptr, UserNameLen);
+ cptr[UserNameLen] = '\0';
+ entry->pw_name = cptr;
+
+ cptr = calloc(8, sizeof(char));
+ TEST(cptr, "PASSWORD");
+ memcpy(cptr, UserPwd, 8);
+ entry->pw_passwd = cptr;
+
+ sptr = UserOwner; sptr++;
+ cptr = calloc ((int)UserOwner[0]+1, sizeof(char));
+ TEST(cptr, "FULLNAME");
+ strncpy (cptr, sptr, (int)UserOwner[0]);
+ cptr[(int)UserOwner[0]] = '\0';
+ entry->pw_gecos = cptr;
+
+ cptr = calloc ((int)UserDevice[0]+(int)UserDir[0]+1, sizeof(char));
+ TEST(cptr, "HOME");
+ sptr = UserDevice; sptr++;
+ strncpy (cptr, sptr, (int)UserDevice[0]);
+ sptr = UserDir; sptr++;
+ strncat (cptr, sptr, (int)UserDir[0]);
+ cptr[(int)UserDevice[0]+(int)UserDir[0]] = '\0';
+ entry->pw_dir = cptr;
+
+ cptr = calloc (strlen("SYS$SYSTEM:LOGINOUT.EXE")+1, sizeof(char));
+ TEST(cptr,"SHELL");
+ strcpy (cptr, "SYS$SYSTEM:LOGINOUT.EXE");
+ entry->pw_shell = cptr;
+
+ return (entry);
+}
diff --git a/driver/vms-hpwd.c b/driver/vms-hpwd.c
new file mode 100644
index 0000000..707e3ea
--- /dev/null
+++ b/driver/vms-hpwd.c
@@ -0,0 +1,75 @@
+/*
+ * VAX/VMS Password hashing routines:
+ *
+ * uses the System Service SYS$HASH_PASSWORD
+ *
+ * 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.
+ *
+ */
+
+#include <syidef.h>
+#include <descrip.h>
+#include <string.h>
+#include <starlet.h>
+/*
+ * Hashing routine
+ */
+hash_vms_password(output_buf,input_buf,input_length,username,encryption_type,salt)
+char *output_buf;
+char *input_buf;
+int input_length;
+char *username;
+int encryption_type;
+unsigned short salt;
+{
+ struct dsc$descriptor_s password;
+ struct dsc$descriptor_s user;
+
+ /*
+ * Check the VMS Version. If this is V5.4 or later, then
+ * we can use the new system service SYS$HASH_PASSWORD. Else
+ * fail and return garbage.
+ */
+
+ static char VMS_Version[32];
+ struct {
+ unsigned short int Size;
+ unsigned short int Code;
+ char *Buffer;
+ unsigned short int *Resultant_Size;
+ } Item_List[2]={32, SYI$_VERSION, VMS_Version, 0, 0, 0};
+ struct {int Size; char *Ptr;} Descr1;
+
+ /*
+ * Get the information
+ */
+ sys$getsyiw(0,0,0,Item_List,0,0,0);
+ /*
+ * Call the old routine if this isn't V5.4 or later...
+ */
+#ifndef __DECC
+ if ((VMS_Version[1] < '5') ||
+ ((VMS_Version[1] == '5') && (VMS_Version[3] < '4'))) {
+ printf("Unsupported OS version\n");
+ return(1);
+ }
+#endif /* !__DECC */
+ /*
+ * Call the SYS$HASH_PASSWORD system service...
+ */
+ password.dsc$b_dtype = DSC$K_DTYPE_T;
+ password.dsc$b_class = DSC$K_CLASS_S;
+ password.dsc$w_length = input_length;
+ password.dsc$a_pointer = input_buf;
+ user.dsc$b_dtype = DSC$K_DTYPE_T;
+ user.dsc$b_class = DSC$K_CLASS_S;
+ user.dsc$w_length = strlen(username);
+ user.dsc$a_pointer = username;
+ sys$hash_password (&password, encryption_type, salt, &user, output_buf);
+}
diff --git a/driver/vms-pwd.h b/driver/vms-pwd.h
new file mode 100644
index 0000000..6cb73d3
--- /dev/null
+++ b/driver/vms-pwd.h
@@ -0,0 +1,48 @@
+/* @(#)pwd.h 1.7 89/08/24 SMI; from S5R2 1.1 */
+
+#ifndef __pwd_h
+#define __pwd_h
+
+#ifdef vax11c
+#include <types.h>
+#else
+#include <sys/types.h>
+#endif /* vax11c */
+
+#ifdef PASSWDROUTINES
+#define EXTERN
+#else
+#define EXTERN extern
+#endif /* PASSWDROUTINES */
+
+struct passwd {
+ char *pw_name;
+ char *pw_passwd;
+ int pw_uid;
+ int pw_gid;
+ short pw_salt;
+ int pw_encrypt;
+ char *pw_age;
+ char *pw_comment;
+ char *pw_gecos;
+ char *pw_dir;
+ char *pw_shell;
+};
+
+
+#ifndef _POSIX_SOURCE
+extern struct passwd *getpwent();
+
+struct comment {
+ char *c_dept;
+ char *c_name;
+ char *c_acct;
+ char *c_bin;
+};
+
+#endif
+
+EXTERN struct passwd *getpwuid(/* uid_t uid */);
+EXTERN struct passwd *getpwnam(/* char *name */);
+
+#endif /* !__pwd_h */
diff --git a/driver/vms-validate.c b/driver/vms-validate.c
new file mode 100644
index 0000000..8f7141d
--- /dev/null
+++ b/driver/vms-validate.c
@@ -0,0 +1,75 @@
+/*
+ * validate a password for a user
+ *
+ * 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.
+ */
+
+/*
+ * Includes
+ */
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "vms-pwd.h"
+int hash_vms_password(char *output_buf,char *input_buf,int input_length,
+ char *username,int encryption_type,unsigned short salt);
+
+/*
+ *
+ * Validate a VMS UserName/Password pair.
+ *
+ */
+
+int validate_user(name,password)
+char *name;
+char *password;
+{
+ char password_buf[64];
+ char username_buf[31];
+ char encrypt_buf[8];
+ register int i;
+ register char *cp,*cp1;
+ struct passwd *user_entry;
+
+ /*
+ * Get the users UAF entry
+ */
+ user_entry = getpwnam(name);
+
+ /*
+ * If user_entry == NULL then we got a bad error
+ * return -1 to indicate a bad error
+ */
+ if (user_entry == NULL) return (-1);
+
+ /*
+ * Uppercase the password
+ */
+ cp = password;
+ cp1 = password_buf;
+ while (*cp)
+ if (islower(*cp))
+ *cp1++ = toupper(*cp++);
+ else
+ *cp1++ = *cp++;
+ /*
+ * Get the length of the password
+ */
+ i = strlen(password);
+ /*
+ * Encrypt the password
+ */
+ hash_vms_password(encrypt_buf,password_buf,i,user_entry->pw_name,
+ user_entry->pw_encrypt, user_entry->pw_salt);
+ if (memcmp(encrypt_buf,user_entry->pw_passwd,8) == 0)
+ return(1);
+ else return(0);
+}
+
diff --git a/driver/vms_axp.opt b/driver/vms_axp.opt
new file mode 100644
index 0000000..04d465d
--- /dev/null
+++ b/driver/vms_axp.opt
@@ -0,0 +1,5 @@
+[-.UTILS]UTILS.OLB_AXP/LIB
+SYS$SHARE:DECW$XMLIBSHR.EXE/SHARE
+SYS$SHARE:DECW$XMULIBSHR.EXE/SHARE
+SYS$SHARE:DECW$XTSHR.EXE/SHARE
+SYS$SHARE:DECW$XLIBSHR.EXE/SHARE
diff --git a/driver/vms_axp_12.opt b/driver/vms_axp_12.opt
new file mode 100644
index 0000000..25dd1f1
--- /dev/null
+++ b/driver/vms_axp_12.opt
@@ -0,0 +1,5 @@
+[-.UTILS]UTILS.OLB_AXP/LIB
+SYS$SHARE:DECW$XMLIBSHR12.EXE/SHARE
+SYS$SHARE:DECW$XMULIBSHRR5.EXE/SHARE
+SYS$SHARE:DECW$XTLIBSHRR5.EXE/SHARE
+SYS$SHARE:DECW$XLIBSHR.EXE/SHARE
diff --git a/driver/vms_decc.opt b/driver/vms_decc.opt
new file mode 100644
index 0000000..65bec03
--- /dev/null
+++ b/driver/vms_decc.opt
@@ -0,0 +1,5 @@
+[-.UTILS]UTILS.OLB_DECC/LIB
+SYS$SHARE:DECW$XMLIBSHR.EXE/SHARE
+SYS$SHARE:DECW$XMULIBSHR.EXE/SHARE
+SYS$SHARE:DECW$XTSHR.EXE/SHARE
+SYS$SHARE:DECW$XLIBSHR.EXE/SHARE
diff --git a/driver/vms_decc_12.opt b/driver/vms_decc_12.opt
new file mode 100644
index 0000000..fdd9a80
--- /dev/null
+++ b/driver/vms_decc_12.opt
@@ -0,0 +1,5 @@
+[-.UTILS]UTILS.OLB_DECC/LIB
+SYS$SHARE:DECW$XMLIBSHR12.EXE/SHARE
+SYS$SHARE:DECW$XMULIBSHRR5.EXE/SHARE
+SYS$SHARE:DECW$XTLIBSHRR5.EXE/SHARE
+SYS$SHARE:DECW$XLIBSHR.EXE/SHARE
diff --git a/driver/windows.c b/driver/windows.c
new file mode 100644
index 0000000..9b2bf84
--- /dev/null
+++ b/driver/windows.c
@@ -0,0 +1,2003 @@
+/* windows.c --- turning the screen black; dealing with visuals, virtual roots.
+ * xscreensaver, Copyright (c) 1991-2014 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
+
+#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 <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 <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>
+#endif /* HAVE_XF86VMODE */
+
+#ifdef HAVE_XINERAMA
+# 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 "visual.h"
+#include "fade.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
+kill_xsetroot_data_1 (Display *dpy, Window window,
+ Atom prop, const char *atom_name,
+ Bool verbose_p)
+{
+ 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);
+ }
+}
+
+
+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);
+}
+
+
+static void
+save_real_vroot (saver_screen_info *ssi)
+{
+ saver_info *si = ssi->global;
+ Display *dpy = si->dpy;
+ Screen *screen = ssi->screen;
+ 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:
+ ;
+ }
+
+ XSync (dpy, False);
+ XSetErrorHandler (old_handler);
+ XSync (dpy, False);
+
+ if (ssi->real_vroot)
+ {
+ remove_vroot_property (si->dpy, ssi->real_vroot);
+ XSync (dpy, False);
+ }
+
+ 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;
+ }
+ }
+}
+
+
+
+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));
+ 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());
+
+ 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);
+ 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++)
+ {
+ saver_screen_info *ssi = &si->screens[i];
+ status [2 + i] = ssi->current_hack + 1;
+ }
+
+ XChangeProperty (si->dpy,
+ RootWindow (si->dpy, 0), /* always screen #0 */
+ XA_SCREENSAVER_STATUS,
+ XA_INTEGER, 32, PropModeReplace,
+ (unsigned char *) status, size);
+ free (status);
+}
+
+
+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)
+{
+ saver_info *si = ssi->global;
+ saver_preferences *p = &si->prefs;
+ Bool install_cmap_p = ssi->install_cmap_p; /* not p->install_cmap_p */
+
+ /* 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...
+ */
+ XColor black;
+ XSetWindowAttributes attrs;
+ unsigned long attrmask;
+ static Bool printed_visual_info = False; /* only print the message once. */
+ Window horked_window = 0;
+
+ black.red = black.green = black.blue = 0;
+
+ if (ssi->cmap == DefaultColormapOfScreen (ssi->screen))
+ ssi->cmap = 0;
+
+ if (ssi->current_visual != DefaultVisualOfScreen (ssi->screen))
+ /* It's not the default visual, so we have no choice but to install. */
+ install_cmap_p = True;
+
+ if (install_cmap_p)
+ {
+ if (! ssi->cmap)
+ {
+ ssi->cmap = XCreateColormap (si->dpy,
+ RootWindowOfScreen (ssi->screen),
+ ssi->current_visual, AllocNone);
+ if (! XAllocColor (si->dpy, ssi->cmap, &black)) abort ();
+ ssi->black_pixel = black.pixel;
+ }
+ }
+ else
+ {
+ Colormap def_cmap = DefaultColormapOfScreen (ssi->screen);
+ if (ssi->cmap)
+ {
+ XFreeColors (si->dpy, ssi->cmap, &ssi->black_pixel, 1, 0);
+ if (ssi->cmap != ssi->demo_cmap &&
+ ssi->cmap != def_cmap)
+ XFreeColormap (si->dpy, ssi->cmap);
+ }
+ ssi->cmap = def_cmap;
+ ssi->black_pixel = BlackPixelOfScreen (ssi->screen);
+ }
+
+ attrmask = (CWOverrideRedirect | CWEventMask | CWBackingStore | CWColormap |
+ 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.colormap = ssi->cmap;
+ attrs.background_pixel = ssi->black_pixel;
+ attrs.backing_pixel = ssi->black_pixel;
+ attrs.border_pixel = ssi->black_pixel;
+
+ if (!p->verbose_p || printed_visual_info)
+ ;
+ else if (ssi->current_visual == DefaultVisualOfScreen (ssi->screen))
+ {
+ fprintf (stderr, "%s: %d: visual ", blurb(), ssi->number);
+ describe_visual (stderr, ssi->screen, ssi->current_visual,
+ install_cmap_p);
+ }
+ else
+ {
+ fprintf (stderr, "%s: using visual: ", blurb());
+ describe_visual (stderr, ssi->screen, ssi->current_visual,
+ install_cmap_p);
+ fprintf (stderr, "%s: default visual: ", blurb());
+ describe_visual (stderr, ssi->screen,
+ DefaultVisualOfScreen (ssi->screen),
+ ssi->install_cmap_p);
+ }
+ printed_visual_info = True;
+
+#ifdef HAVE_MIT_SAVER_EXTENSION
+ if (si->using_mit_saver_extension)
+ {
+ 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);
+ }
+#endif /* HAVE_MIT_SAVER_EXTENSION */
+
+ if (ssi->screensaver_window)
+ {
+ XWindowChanges changes;
+ unsigned int changesmask = CWX|CWY|CWWidth|CWHeight|CWBorderWidth;
+ changes.x = ssi->x;
+ changes.y = ssi->y;
+ changes.width = ssi->width;
+ 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)))
+ {
+ horked_window = ssi->screensaver_window;
+ ssi->screensaver_window = 0;
+ }
+ }
+
+ if (!ssi->screensaver_window)
+ {
+ ssi->screensaver_window =
+ XCreateWindow (si->dpy, RootWindowOfScreen (ssi->screen),
+ ssi->x, ssi->y, ssi->width, ssi->height,
+ 0, ssi->current_depth, InputOutput,
+ ssi->current_visual, attrmask, &attrs);
+ reset_stderr (ssi);
+
+ 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;
+ }
+
+ if (p->verbose_p)
+ 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);
+ ssi->cursor = XCreatePixmapCursor (si->dpy, bit, bit, &black, &black,
+ 0, 0);
+ XFreePixmap (si->dpy, bit);
+ }
+
+ XSetWindowBackground (si->dpy, ssi->screensaver_window, ssi->black_pixel);
+
+ if (si->demoing_p)
+ XUndefineCursor (si->dpy, ssi->screensaver_window);
+ else
+ XDefineCursor (si->dpy, ssi->screensaver_window, ssi->cursor);
+}
+
+void
+initialize_screensaver_window (saver_info *si)
+{
+ int i;
+ for (i = 0; i < si->nscreens; i++)
+ initialize_screensaver_window_1 (&si->screens[i]);
+}
+
+
+/* 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
+ the size of the saver windows to the new sizes of the screens.
+ */
+void
+resize_screensaver_window (saver_info *si)
+{
+ saver_preferences *p = &si->prefs;
+ int i;
+
+ for (i = 0; i < si->nscreens; i++)
+ {
+ saver_screen_info *ssi = &si->screens[i];
+ XWindowAttributes xgwa;
+
+ /* Make sure a window exists -- it might not if a monitor was just
+ added for the first time.
+ */
+ if (! ssi->screensaver_window)
+ {
+ initialize_screensaver_window_1 (ssi);
+ if (p->verbose_p)
+ fprintf (stderr,
+ "%s: %d: newly added window 0x%lx %dx%d+%d+%d\n",
+ blurb(), i, (unsigned long) ssi->screensaver_window,
+ ssi->width, ssi->height, ssi->x, ssi->y);
+ }
+
+ /* Make sure the window is the right size -- it might not be if
+ the monitor changed resolution, or if a badly-behaved hack
+ screwed with it.
+ */
+ XGetWindowAttributes (si->dpy, ssi->screensaver_window, &xgwa);
+ if (xgwa.x != ssi->x ||
+ xgwa.y != ssi->y ||
+ xgwa.width != ssi->width ||
+ xgwa.height != ssi->height)
+ {
+ XWindowChanges changes;
+ unsigned int changesmask = CWX|CWY|CWWidth|CWHeight|CWBorderWidth;
+ changes.x = ssi->x;
+ changes.y = ssi->y;
+ changes.width = ssi->width;
+ changes.height = ssi->height;
+ changes.border_width = 0;
+
+ if (p->verbose_p)
+ fprintf (stderr,
+ "%s: %d: resize 0x%lx from %dx%d+%d+%d to %dx%d+%d+%d\n",
+ blurb(), i, (unsigned long) ssi->screensaver_window,
+ 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))
+ fprintf (stderr, "%s: %d: someone horked our saver window"
+ " (0x%lx)! Unable to resize it!\n",
+ blurb(), i, (unsigned long) ssi->screensaver_window);
+ }
+
+ /* Now (if blanked) make sure that it's mapped and running a hack --
+ it might not be if we just added it. (We also might be re-using
+ an old window that existed for a previous monitor that was
+ removed and re-added.)
+
+ 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);
+ 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);
+ }
+ }
+ }
+
+ /* Kill off any savers running on no-longer-extant monitors.
+ */
+ for (; i < si->ssi_count; i++)
+ {
+ saver_screen_info *ssi = &si->screens[i];
+ if (ssi->pid)
+ kill_screenhack (ssi);
+ 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)
+{
+ saver_preferences *p = &si->prefs;
+ int i;
+
+ if (si->demoing_p)
+ inhibit_fade = True;
+
+ if (si->emergency_lock_p)
+ inhibit_fade = True;
+
+ if (!dont_clear)
+ initialize_screensaver_window (si);
+
+ reset_watchdog_timer (si, True);
+
+ if (p->fade_p && si->fading_possible_p && !inhibit_fade)
+ {
+ Window *current_windows = (Window *)
+ calloc(sizeof(Window), si->nscreens);
+ Colormap *current_maps = (Colormap *)
+ calloc(sizeof(Colormap), si->nscreens);
+
+ 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,
+ ssi->black_pixel);
+ }
+
+ 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);
+#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;
+ }
+ }
+
+ /* couldn't figure out where the mouse is? Oh well. */
+ return 0;
+}
+
+
+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;
+
+ 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;
+
+ store_saver_status (si); /* store blank time */
+
+ return True;
+}
+
+
+void
+unblank_screen (saver_info *si)
+{
+ saver_preferences *p = &si->prefs;
+ Bool unfade_p = (si->fading_possible_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)
+ {
+ Window *current_windows = (Window *)
+ calloc(sizeof(Window), si->nscreens);
+
+ 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());
+
+
+ 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());
+ }
+ else
+ {
+ for (i = 0; i < si->nscreens; i++)
+ {
+ saver_screen_info *ssi = &si->screens[i];
+ if (ssi->cmap)
+ {
+ Colormap c = DefaultColormapOfScreen (ssi->screen);
+ /* avoid technicolor */
+ 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;
+ XGetInputFocus (si->dpy, &focus, &revert_to);
+ if (focus && focus != PointerRoot && focus != None)
+ {
+ XWindowAttributes xgwa;
+ xgwa.colormap = 0;
+ XGetWindowAttributes (si->dpy, focus, &xgwa);
+ if (xgwa.colormap &&
+ xgwa.colormap != DefaultColormapOfScreen (xgwa.screen))
+ XInstallColormap (si->dpy, xgwa.colormap);
+ }
+ }
+
+
+ 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));
+
+ 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];
+}
+
+
+Bool
+select_visual (saver_screen_info *ssi, const char *visual_name)
+{
+ XWindowAttributes xgwa;
+ saver_info *si = ssi->global;
+ saver_preferences *p = &si->prefs;
+ Bool install_cmap_p = p->install_cmap_p;
+ Bool was_installed_p = (ssi->cmap != DefaultColormapOfScreen(ssi->screen));
+ Visual *new_v = 0;
+ Bool got_it;
+
+ /* On some systems (most recently, MacOS X) OpenGL programs get confused
+ when you kill one and re-start another on the same window. So maybe
+ it's best to just always destroy and recreate the xscreensaver window
+ when changing hacks, instead of trying to reuse the old one?
+ */
+ Bool always_recreate_window_p = True;
+
+ get_screen_gl_visual (si, 0); /* let's probe all the GL visuals early */
+
+ /* We make sure the existing window is actually on ssi->screen before
+ trying to use it, in case things moved around radically when monitors
+ were added or deleted. If we don't do this we could get a BadMatch
+ even though the depths match. I think.
+ */
+ memset (&xgwa, 0, sizeof(xgwa));
+ if (ssi->screensaver_window)
+ XGetWindowAttributes (si->dpy, ssi->screensaver_window, &xgwa);
+
+ if (visual_name && *visual_name)
+ {
+ if (!strcmp(visual_name, "default-i") ||
+ !strcmp(visual_name, "Default-i") ||
+ !strcmp(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"))
+ {
+ visual_name = "default";
+ install_cmap_p = False;
+ }
+ else if (!strcmp(visual_name, "gl") ||
+ !strcmp(visual_name, "Gl") ||
+ !strcmp(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);
+ }
+
+ if (!new_v)
+ new_v = get_visual (ssi->screen, visual_name, True, False);
+ }
+ else
+ {
+ new_v = ssi->default_visual;
+ }
+
+ got_it = !!new_v;
+
+ if (new_v && new_v != DefaultVisualOfScreen(ssi->screen))
+ /* It's not the default visual, so we have no choice but to install. */
+ install_cmap_p = True;
+
+ ssi->install_cmap_p = install_cmap_p;
+
+ if ((ssi->screen != xgwa.screen) ||
+ (new_v &&
+ (always_recreate_window_p ||
+ (ssi->current_visual != new_v) ||
+ (install_cmap_p != was_installed_p))))
+ {
+ Colormap old_c = ssi->cmap;
+ Window old_w = ssi->screensaver_window;
+ if (! new_v)
+ new_v = ssi->current_visual;
+
+ if (p->verbose_p)
+ {
+ fprintf (stderr, "%s: %d: visual ", blurb(), ssi->number);
+ describe_visual (stderr, ssi->screen, new_v, install_cmap_p);
+#if 0
+ fprintf (stderr, "%s: from ", blurb());
+ describe_visual (stderr, ssi->screen, ssi->current_visual,
+ was_installed_p);
+#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);
+
+ /* 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",
+ blurb(), ssi->number, (unsigned long) old_w);
+
+ if (old_c &&
+ old_c != DefaultColormapOfScreen (ssi->screen) &&
+ old_c != ssi->demo_cmap)
+ XFreeColormap (si->dpy, old_c);
+ }
+
+ return got_it;
+}
diff --git a/driver/xdpyinfo.c b/driver/xdpyinfo.c
new file mode 100644
index 0000000..9f67966
--- /dev/null
+++ b/driver/xdpyinfo.c
@@ -0,0 +1,1098 @@
+/*
+ * $ TOG: xdpyinfo.c /main/35 1998/02/09 13:57:05 kaleb $
+ *
+ * xdpyinfo - print information about X display connecton
+ *
+ *
+Copyright 1988, 1998 The Open Group
+
+All Rights Reserved.
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+Except as contained in this notice, the name of The Open Group shall not be
+used in advertising or otherwise to promote the sale, use or other dealings
+in this Software without prior written authorization from The Open Group.
+ *
+ * Author: Jim Fulton, MIT X Consortium
+ *
+ * GLX and Overlay support added by Jamie Zawinski <jwz@jwz.org>, 11-Nov-99
+ *
+ * To compile:
+ * cc -DHAVE_GLX xdpyinfo.c -o xdpyinfo -lGL -lX11 -lXext [-lXtst] -lm
+ *
+ * Other defines to consider:
+ * -DMITSHM -DHAVE_XDBE -DHAVE_XIE -DHAVE_XTEST -DHAVE_SYNC
+ * -DHAVE_XRECORD
+ */
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xproto.h> /* for CARD32 */
+#include <X11/extensions/multibuf.h>
+#ifdef HAVE_XIE
+#include <X11/extensions/XIElib.h>
+#endif /* HAVE_XIE */
+#ifdef HAVE_XTEST
+#include <X11/extensions/XTest.h>
+#endif /* HAVE_XTEST */
+#ifdef HAVE_XSYNC
+#include <X11/extensions/sync.h>
+#endif /* HAVE_XSYNC */
+#ifdef HAVE_XDBE
+#include <X11/extensions/Xdbe.h>
+#endif /* HAVE_XDBE */
+#ifdef HAVE_XRECORD
+#include <X11/extensions/record.h>
+#endif /* HAVE_XRECORD */
+#ifdef MITSHM
+#include <X11/extensions/XShm.h>
+#endif
+#include <X11/Xos.h>
+#include <stdio.h>
+
+#ifdef HAVE_GLX
+# include <GL/gl.h>
+# include <GL/glx.h>
+#endif /* HAVE_GLX */
+
+#define HAVE_OVERLAY /* jwz: no compile-time deps, so do this all the time */
+
+char *ProgramName;
+Bool queryExtensions = False;
+
+static int StrCmp(a, b)
+ char **a, **b;
+{
+ return strcmp(*a, *b);
+}
+
+
+#ifdef HAVE_GLX /* Added by jwz, 11-Nov-99 */
+
+static void
+print_glx_versions (dpy)
+ 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.
+
+ Note also that we can't protect these calls by only doing
+ them when the GLX extension is present, because with Mesa,
+ the server doesn't have that extension (but the GL library
+ works anyway.)
+ */
+ int scr = DefaultScreen (dpy);
+ const char *vend, *vers;
+ vend = glXQueryServerString (dpy, scr, GLX_VENDOR);
+ if (!vend) return;
+ vers = glXQueryServerString (dpy, scr, GLX_VERSION);
+ printf ("GLX vendor: %s (%s)\n",
+ vend, (vers ? vers : "unknown version"));
+}
+
+static void
+print_glx_visual_info (dpy, vip)
+ Display *dpy;
+ XVisualInfo *vip;
+{
+ int status, value = False;
+
+ status = glXGetConfig (dpy, vip, GLX_USE_GL, &value);
+ if (status == GLX_NO_EXTENSION)
+ /* dpy does not support the GLX extension. */
+ return;
+
+ if (status == GLX_BAD_VISUAL || value == False)
+ {
+ printf (" GLX supported: no\n");
+ return;
+ }
+ else
+ {
+ printf (" GLX supported: yes\n");
+ }
+
+ if (!glXGetConfig (dpy, vip, GLX_LEVEL, &value) &&
+ value != 0)
+ printf (" GLX level: %d\n", value);
+
+ if (!glXGetConfig (dpy, vip, GLX_RGBA, &value) && value)
+ {
+ int r=0, g=0, b=0, a=0;
+ glXGetConfig (dpy, vip, GLX_RED_SIZE, &r);
+ glXGetConfig (dpy, vip, GLX_GREEN_SIZE, &g);
+ glXGetConfig (dpy, vip, GLX_BLUE_SIZE, &b);
+ glXGetConfig (dpy, vip, GLX_ALPHA_SIZE, &a);
+ printf (" GLX type: RGBA (%2d, %2d, %2d, %2d)\n",
+ r, g, b, a);
+
+ r=0, g=0, b=0, a=0;
+ glXGetConfig (dpy, vip, GLX_ACCUM_RED_SIZE, &r);
+ glXGetConfig (dpy, vip, GLX_ACCUM_GREEN_SIZE, &g);
+ glXGetConfig (dpy, vip, GLX_ACCUM_BLUE_SIZE, &b);
+ glXGetConfig (dpy, vip, GLX_ACCUM_ALPHA_SIZE, &a);
+ printf (" GLX accum: RGBA (%2d, %2d, %2d, %2d)\n",
+ r, g, b, a);
+ }
+ else
+ {
+ value = 0;
+ glXGetConfig (dpy, vip, GLX_BUFFER_SIZE, &value);
+ printf (" GLX type: indexed (%d)\n", value);
+ }
+
+# if 0 /* redundant */
+ if (!glXGetConfig (dpy, vip, GLX_X_VISUAL_TYPE_EXT, &value))
+ printf (" GLX class: %s\n",
+ (value == GLX_TRUE_COLOR_EXT ? "TrueColor" :
+ value == GLX_DIRECT_COLOR_EXT ? "DirectColor" :
+ value == GLX_PSEUDO_COLOR_EXT ? "PseudoColor" :
+ value == GLX_STATIC_COLOR_EXT ? "StaticColor" :
+ value == GLX_GRAY_SCALE_EXT ? "Grayscale" :
+ value == GLX_STATIC_GRAY_EXT ? "StaticGray" : "???"));
+# endif
+
+# ifdef GLX_VISUAL_CAVEAT_EXT
+ if (!glXGetConfig (dpy, vip, GLX_VISUAL_CAVEAT_EXT, &value) &&
+ value != GLX_NONE_EXT)
+ printf (" GLX rating: %s\n",
+ (value == GLX_NONE_EXT ? "none" :
+ value == GLX_SLOW_VISUAL_EXT ? "slow" :
+# ifdef GLX_NON_CONFORMANT_EXT
+ value == GLX_NON_CONFORMANT_EXT ? "non-conformant" :
+# endif
+ "???"));
+# endif
+
+ if (!glXGetConfig (dpy, vip, GLX_DOUBLEBUFFER, &value))
+ printf (" GLX double-buffer: %s\n", (value ? "yes" : "no"));
+
+ if (!glXGetConfig (dpy, vip, GLX_STEREO, &value) &&
+ value)
+ printf (" GLX stereo: %s\n", (value ? "yes" : "no"));
+
+ if (!glXGetConfig (dpy, vip, GLX_AUX_BUFFERS, &value) &&
+ value != 0)
+ printf (" GLX aux buffers: %d\n", value);
+
+ if (!glXGetConfig (dpy, vip, GLX_DEPTH_SIZE, &value))
+ printf (" GLX depth size: %d\n", value);
+
+ if (!glXGetConfig (dpy, vip, GLX_STENCIL_SIZE, &value) &&
+ value != 0)
+ printf (" GLX stencil size: %d\n", value);
+
+# if defined(GL_SAMPLE_BUFFERS)
+# define SB GL_SAMPLE_BUFFERS
+# define SM GL_SAMPLES
+# elif defined(GLX_SAMPLE_BUFFERS)
+# define SB GLX_SAMPLE_BUFFERS
+# define SM GLX_SAMPLES
+# elif defined(GLX_SAMPLE_BUFFERS_ARB)
+# define SB GLX_SAMPLE_BUFFERS_ARB
+# define SM GLX_SAMPLES_ARB
+# elif defined(GLX_SAMPLE_BUFFERS_SGIS)
+# define SB GLX_SAMPLE_BUFFERS_SGIS
+# define SM GLX_SAMPLES_SGIS
+# endif
+
+# ifdef SB
+ if (!glXGetConfig (dpy, vip, SB, &value) && value != 0)
+ {
+ int bufs = value;
+ if (!glXGetConfig (dpy, vip, SM, &value))
+ printf (" GLX multisample: %d, %d\n", bufs, value);
+ }
+# endif /* SB */
+
+ if (!glXGetConfig (dpy, vip, GLX_TRANSPARENT_TYPE_EXT, &value) &&
+ value != GLX_NONE_EXT)
+ {
+ if (value == GLX_NONE_EXT)
+ printf (" GLX transparency: none\n");
+ else if (value == GLX_TRANSPARENT_INDEX_EXT)
+ {
+ if (!glXGetConfig (dpy, vip, GLX_TRANSPARENT_INDEX_VALUE_EXT,&value))
+ printf (" GLX transparency: indexed (%d)\n", value);
+ }
+ else if (value == GLX_TRANSPARENT_RGB_EXT)
+ {
+ int r=0, g=0, b=0, a=0;
+ glXGetConfig (dpy, vip, GLX_TRANSPARENT_RED_VALUE_EXT, &r);
+ glXGetConfig (dpy, vip, GLX_TRANSPARENT_GREEN_VALUE_EXT, &g);
+ glXGetConfig (dpy, vip, GLX_TRANSPARENT_BLUE_VALUE_EXT, &b);
+ glXGetConfig (dpy, vip, GLX_TRANSPARENT_ALPHA_VALUE_EXT, &a);
+ printf (" GLX transparency: RGBA (%2d, %2d, %2d, %2d)\n",
+ r, g, b, a);
+ }
+ }
+}
+#endif /* HAVE_GLX */
+
+
+#ifdef HAVE_OVERLAY /* Added by jwz, 11-Nov-99 */
+
+ /* If the server's root window contains a SERVER_OVERLAY_VISUALS property,
+ then that identifies the visuals which correspond to the video hardware's
+ overlay planes. Windows created in these kinds of visuals may have
+ transparent pixels that let other layers shine through.
+
+ This might not be an X Consortium standard, but it turns out that
+ SGI, HP, DEC, and IBM all use this same mechanism. So that's close
+ enough for me.
+
+ Documentation on the SERVER_OVERLAY_VISUALS property can be found at:
+ http://www.hp.com/xwindow/sharedInfo/Whitepapers/Visuals/server_overlay_visuals.html
+ */
+
+struct overlay
+{
+ CARD32 visual_id;
+ CARD32 transparency; /* 0: none; 1: pixel; 2: mask */
+ CARD32 value; /* the transparent pixel */
+ CARD32 layer; /* -1: underlay; 0: normal; 1: popup; 2: overlay */
+};
+
+struct overlay_list
+{
+ int count;
+ struct overlay *list;
+};
+
+static struct overlay_list *overlays = 0;
+
+static void
+find_overlay_info (dpy)
+ Display *dpy;
+{
+ int screen;
+ Atom OVERLAY = XInternAtom (dpy, "SERVER_OVERLAY_VISUALS", False);
+
+ overlays = (struct overlay_list *) calloc (sizeof (struct overlay_list),
+ ScreenCount (dpy));
+
+ for (screen = 0; screen < ScreenCount (dpy); screen++)
+ {
+ Window window = RootWindow (dpy, screen);
+ Atom actual_type;
+ int actual_format;
+ unsigned long nitems, bytes_after;
+ struct overlay *data = 0;
+ int result = XGetWindowProperty (dpy, window, OVERLAY,
+ 0, (65536 / sizeof (long)), False,
+ OVERLAY, &actual_type, &actual_format,
+ &nitems, &bytes_after,
+ (unsigned char **) &data);
+ if (result == Success &&
+ actual_type == OVERLAY &&
+ actual_format == 32 &&
+ nitems > 0)
+ {
+ overlays[screen].count = (nitems /
+ (sizeof(struct overlay) / sizeof(CARD32)));
+ overlays[screen].list = data;
+ }
+ else if (data)
+ XFree((char *) data);
+ }
+}
+
+static void
+print_overlay_visual_info (vip)
+ XVisualInfo *vip;
+{
+ int i;
+ int vis = vip->visualid;
+ int scr = vip->screen;
+ if (!overlays) return;
+ for (i = 0; i < overlays[scr].count; i++)
+ if (vis == overlays[scr].list[i].visual_id)
+ {
+ struct overlay *ov = &overlays[scr].list[i];
+ printf (" Overlay info: layer %ld (%s), ",
+ (long) ov->layer,
+ (ov->layer == -1 ? "underlay" :
+ ov->layer == 0 ? "normal" :
+ ov->layer == 1 ? "popup" :
+ ov->layer == 2 ? "overlay" : "???"));
+ 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);
+ else
+ printf ("opaque\n");
+ }
+}
+#endif /* HAVE_OVERLAY */
+
+
+void
+print_extension_info (dpy)
+ Display *dpy;
+{
+ int n = 0;
+ char **extlist = XListExtensions (dpy, &n);
+
+ printf ("number of extensions: %d\n", n);
+
+ if (extlist) {
+ register int i;
+ int opcode, event, error;
+
+ qsort(extlist, n, sizeof(char *), StrCmp);
+ for (i = 0; i < n; i++) {
+ if (!queryExtensions) {
+ printf (" %s\n", extlist[i]);
+ continue;
+ }
+ XQueryExtension(dpy, extlist[i], &opcode, &event, &error);
+ printf (" %s (opcode: %d", extlist[i], opcode);
+ if (event)
+ printf (", base event: %d", event);
+ if (error)
+ printf (", base error: %d", error);
+ printf(")\n");
+ }
+ /* do not free, Xlib can depend on contents being unaltered */
+ /* XFreeExtensionList (extlist); */
+ }
+}
+
+void
+print_display_info (dpy)
+ Display *dpy;
+{
+ char dummybuf[40];
+ char *cp;
+ int minkeycode, maxkeycode;
+ int i, n;
+ long req_size;
+ XPixmapFormatValues *pmf;
+ Window focuswin;
+ int focusrevert;
+
+ printf ("name of display: %s\n", DisplayString (dpy));
+ printf ("version number: %d.%d\n",
+ ProtocolVersion (dpy), ProtocolRevision (dpy));
+ printf ("vendor string: %s\n", ServerVendor (dpy));
+ printf ("vendor release number: %d\n", VendorRelease (dpy));
+
+#ifdef HAVE_GLX
+ print_glx_versions (dpy);
+#endif /* HAVE_GLX */
+
+ 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));
+
+ switch (BitmapBitOrder (dpy)) {
+ case LSBFirst: cp = "LSBFirst"; break;
+ case MSBFirst: cp = "MSBFirst"; break;
+ default:
+ sprintf (dummybuf, "unknown order %d", BitmapBitOrder (dpy));
+ cp = dummybuf;
+ break;
+ }
+ printf ("bitmap unit, bit order, padding: %d, %s, %d\n",
+ BitmapUnit (dpy), cp, BitmapPad (dpy));
+
+ switch (ImageByteOrder (dpy)) {
+ case LSBFirst: cp = "LSBFirst"; break;
+ case MSBFirst: cp = "MSBFirst"; break;
+ default:
+ sprintf (dummybuf, "unknown order %d", ImageByteOrder (dpy));
+ cp = dummybuf;
+ break;
+ }
+ printf ("image byte order: %s\n", cp);
+
+ pmf = XListPixmapFormats (dpy, &n);
+ printf ("number of supported pixmap formats: %d\n", n);
+ if (pmf) {
+ printf ("supported pixmap formats:\n");
+ for (i = 0; i < n; i++) {
+ printf (" depth %d, bits_per_pixel %d, scanline_pad %d\n",
+ pmf[i].depth, pmf[i].bits_per_pixel, pmf[i].scanline_pad);
+ }
+ XFree ((char *) pmf);
+ }
+
+
+ /*
+ * when we get interfaces to the PixmapFormat stuff, insert code here
+ */
+
+ XDisplayKeycodes (dpy, &minkeycode, &maxkeycode);
+ printf ("keycode range: minimum %d, maximum %d\n",
+ minkeycode, maxkeycode);
+
+ XGetInputFocus (dpy, &focuswin, &focusrevert);
+ printf ("focus: ");
+ switch (focuswin) {
+ case PointerRoot:
+ printf ("PointerRoot\n");
+ break;
+ case None:
+ printf ("None\n");
+ break;
+ default:
+ printf("window 0x%lx, revert to ", focuswin);
+ switch (focusrevert) {
+ case RevertToParent:
+ printf ("Parent\n");
+ break;
+ case RevertToNone:
+ printf ("None\n");
+ break;
+ case RevertToPointerRoot:
+ printf ("PointerRoot\n");
+ break;
+ default: /* should not happen */
+ printf ("%d\n", focusrevert);
+ break;
+ }
+ break;
+ }
+
+ print_extension_info (dpy);
+
+ printf ("default screen number: %d\n", DefaultScreen (dpy));
+ printf ("number of screens: %d\n", ScreenCount (dpy));
+}
+
+void
+print_visual_info (vip)
+ XVisualInfo *vip;
+{
+ char errorbuf[40]; /* for sprintfing into */
+ char *class = NULL; /* for printing */
+
+ switch (vip->class) {
+ case StaticGray: class = "StaticGray"; break;
+ case GrayScale: class = "GrayScale"; break;
+ case StaticColor: class = "StaticColor"; break;
+ case PseudoColor: class = "PseudoColor"; break;
+ case TrueColor: class = "TrueColor"; break;
+ case DirectColor: class = "DirectColor"; break;
+ default:
+ sprintf (errorbuf, "unknown class %d", vip->class);
+ class = errorbuf;
+ break;
+ }
+
+ printf (" visual:\n");
+ printf (" visual id: 0x%lx\n", vip->visualid);
+ printf (" class: %s\n", class);
+ printf (" depth: %d plane%s\n", vip->depth,
+ vip->depth == 1 ? "" : "s");
+ if (vip->class == TrueColor || vip->class == DirectColor)
+ printf (" available colormap entries: %d per subfield\n",
+ vip->colormap_size);
+ else
+ printf (" available colormap entries: %d\n",
+ vip->colormap_size);
+ printf (" red, green, blue masks: 0x%lx, 0x%lx, 0x%lx\n",
+ vip->red_mask, vip->green_mask, vip->blue_mask);
+ printf (" significant bits in color specification: %d bits\n",
+ vip->bits_per_rgb);
+}
+
+void
+print_screen_info (dpy, scr)
+ Display *dpy;
+ int scr;
+{
+ Screen *s = ScreenOfDisplay (dpy, scr); /* opaque structure */
+ XVisualInfo viproto; /* fill in for getting info */
+ XVisualInfo *vip; /* retured info */
+ int nvi; /* number of elements returned */
+ int i; /* temp variable: iterator */
+ char eventbuf[80]; /* want 79 chars per line + nul */
+ static char *yes = "YES", *no = "NO", *when = "WHEN MAPPED";
+ double xres, yres;
+ int ndepths = 0, *depths = NULL;
+ unsigned int width, height;
+
+
+ /*
+ * there are 2.54 centimeters to an inch; so there are 25.4 millimeters.
+ *
+ * dpi = N pixels / (M millimeters / (25.4 millimeters / 1 inch))
+ * = N pixels / (M inch / 25.4)
+ * = N * 25.4 pixels / M inch
+ */
+
+ xres = ((((double) DisplayWidth(dpy,scr)) * 25.4) /
+ ((double) DisplayWidthMM(dpy,scr)));
+ yres = ((((double) DisplayHeight(dpy,scr)) * 25.4) /
+ ((double) DisplayHeightMM(dpy,scr)));
+
+ printf ("\n");
+ printf ("screen #%d:\n", scr);
+ printf (" dimensions: %dx%d pixels (%dx%d millimeters)\n",
+ DisplayWidth (dpy, scr), DisplayHeight (dpy, scr),
+ DisplayWidthMM(dpy, scr), DisplayHeightMM (dpy, scr));
+ printf (" resolution: %dx%d dots per inch\n",
+ (int) (xres + 0.5), (int) (yres + 0.5));
+ depths = XListDepths (dpy, scr, &ndepths);
+ if (!depths) ndepths = 0;
+ printf (" depths (%d): ", ndepths);
+ for (i = 0; i < ndepths; i++) {
+ printf ("%d", depths[i]);
+ if (i < ndepths - 1) {
+ putchar (',');
+ putchar (' ');
+ }
+ }
+ putchar ('\n');
+ if (depths) XFree ((char *) depths);
+ printf (" root window id: 0x%lx\n", RootWindow (dpy, scr));
+ printf (" depth of root window: %d plane%s\n",
+ DisplayPlanes (dpy, scr),
+ DisplayPlanes (dpy, scr) == 1 ? "" : "s");
+ printf (" number of colormaps: minimum %d, maximum %d\n",
+ MinCmapsOfScreen(s), MaxCmapsOfScreen(s));
+ 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",
+ BlackPixel (dpy, scr), WhitePixel (dpy, scr));
+ printf (" options: backing-store %s, save-unders %s\n",
+ (DoesBackingStore (s) == NotUseful) ? no :
+ ((DoesBackingStore (s) == Always) ? yes : when),
+ DoesSaveUnders (s) ? yes : no);
+ XQueryBestSize (dpy, CursorShape, RootWindow (dpy, scr), 65535, 65535,
+ &width, &height);
+ if (width == 65535 && height == 65535)
+ printf (" largest cursor: unlimited\n");
+ else
+ printf (" largest cursor: %dx%d\n", width, height);
+ printf (" current input event mask: 0x%lx\n", EventMaskOfScreen (s));
+ (void) print_event_mask (eventbuf, 79, 4, EventMaskOfScreen (s));
+
+
+ nvi = 0;
+ viproto.screen = scr;
+ vip = XGetVisualInfo (dpy, VisualScreenMask, &viproto, &nvi);
+ printf (" number of visuals: %d\n", nvi);
+ printf (" default visual id: 0x%lx\n",
+ XVisualIDFromVisual (DefaultVisual (dpy, scr)));
+ for (i = 0; i < nvi; i++) {
+ print_visual_info (vip+i);
+#ifdef HAVE_OVERLAY
+ print_overlay_visual_info (vip+i);
+#endif /* HAVE_OVERLAY */
+#ifdef HAVE_GLX
+ print_glx_visual_info (dpy, vip+i);
+#endif /* HAVE_GLX */
+ }
+ if (vip) XFree ((char *) vip);
+}
+
+/*
+ * The following routine prints out an event mask, wrapping events at nice
+ * boundaries.
+ */
+
+#define MASK_NAME_WIDTH 25
+
+static struct _event_table {
+ char *name;
+ long value;
+} event_table[] = {
+ { "KeyPressMask ", KeyPressMask },
+ { "KeyReleaseMask ", KeyReleaseMask },
+ { "ButtonPressMask ", ButtonPressMask },
+ { "ButtonReleaseMask ", ButtonReleaseMask },
+ { "EnterWindowMask ", EnterWindowMask },
+ { "LeaveWindowMask ", LeaveWindowMask },
+ { "PointerMotionMask ", PointerMotionMask },
+ { "PointerMotionHintMask ", PointerMotionHintMask },
+ { "Button1MotionMask ", Button1MotionMask },
+ { "Button2MotionMask ", Button2MotionMask },
+ { "Button3MotionMask ", Button3MotionMask },
+ { "Button4MotionMask ", Button4MotionMask },
+ { "Button5MotionMask ", Button5MotionMask },
+ { "ButtonMotionMask ", ButtonMotionMask },
+ { "KeymapStateMask ", KeymapStateMask },
+ { "ExposureMask ", ExposureMask },
+ { "VisibilityChangeMask ", VisibilityChangeMask },
+ { "StructureNotifyMask ", StructureNotifyMask },
+ { "ResizeRedirectMask ", ResizeRedirectMask },
+ { "SubstructureNotifyMask ", SubstructureNotifyMask },
+ { "SubstructureRedirectMask ", SubstructureRedirectMask },
+ { "FocusChangeMask ", FocusChangeMask },
+ { "PropertyChangeMask ", PropertyChangeMask },
+ { "ColormapChangeMask ", ColormapChangeMask },
+ { "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 */
+{
+ struct _event_table *etp;
+ int len;
+ int bitsfound = 0;
+
+ buf[0] = buf[lastcol] = '\0'; /* just in case */
+
+#define INDENT() { register int i; len = indent; \
+ for (i = 0; i < indent; i++) buf[i] = ' '; }
+
+ INDENT ();
+
+ for (etp = event_table; etp->name; etp++) {
+ if (mask & etp->value) {
+ if (len + MASK_NAME_WIDTH > lastcol) {
+ puts (buf);
+ INDENT ();
+ }
+ strcpy (buf+len, etp->name);
+ len += MASK_NAME_WIDTH;
+ bitsfound++;
+ }
+ }
+
+ if (bitsfound) puts (buf);
+
+#undef INDENT
+
+ return (bitsfound);
+}
+
+void
+print_standard_extension_info(dpy, extname, majorrev, minorrev)
+ Display *dpy;
+ char *extname;
+ int majorrev, minorrev;
+{
+ int opcode, event, error;
+
+ printf("%s version %d.%d ", extname, majorrev, minorrev);
+
+ XQueryExtension(dpy, extname, &opcode, &event, &error);
+ printf ("opcode: %d", opcode);
+ if (event)
+ printf (", base event: %d", event);
+ if (error)
+ printf (", base error: %d", error);
+ printf("\n");
+}
+
+int
+print_multibuf_info(dpy, extname)
+ Display *dpy;
+ char *extname;
+{
+ int i, j; /* temp variable: iterator */
+ int nmono, nstereo; /* count */
+ XmbufBufferInfo *mono_info = NULL, *stereo_info = NULL; /* arrays */
+ static char *fmt =
+ " visual id, max buffers, depth: 0x%lx, %d, %d\n";
+ int scr = 0;
+ int majorrev, minorrev;
+
+ if (!XmbufGetVersion(dpy, &majorrev, &minorrev))
+ return 0;
+
+ print_standard_extension_info(dpy, extname, majorrev, minorrev);
+
+ for (i = 0; i < ScreenCount (dpy); i++)
+ {
+ if (!XmbufGetScreenInfo (dpy, RootWindow(dpy, scr), &nmono, &mono_info,
+ &nstereo, &stereo_info)) {
+ fprintf (stderr,
+ "%s: unable to get multibuffer info for screen %d\n",
+ ProgramName, scr);
+ } else {
+ printf (" screen %d number of mono multibuffer types: %d\n", i, nmono);
+ for (j = 0; j < nmono; j++) {
+ printf (fmt, mono_info[j].visualid, mono_info[j].max_buffers,
+ mono_info[j].depth);
+ }
+ printf (" number of stereo multibuffer types: %d\n", nstereo);
+ for (j = 0; j < nstereo; j++) {
+ printf (fmt, stereo_info[j].visualid,
+ stereo_info[j].max_buffers, stereo_info[j].depth);
+ }
+ if (mono_info) XFree ((char *) mono_info);
+ if (stereo_info) XFree ((char *) stereo_info);
+ }
+ }
+ return 1;
+} /* end print_multibuf_info */
+
+
+/* XIE stuff */
+
+#ifdef HAVE_XIE
+
+char *subset_names[] = { NULL, "FULL", "DIS" };
+char *align_names[] = { NULL, "Alignable", "Arbitrary" };
+char *group_names[] = { /* 0 */ "Default",
+ /* 2 */ "ColorAlloc",
+ /* 4 */ "Constrain",
+ /* 6 */ "ConvertFromRGB",
+ /* 8 */ "ConvertToRGB",
+ /* 10 */ "Convolve",
+ /* 12 */ "Decode",
+ /* 14 */ "Dither",
+ /* 16 */ "Encode",
+ /* 18 */ "Gamut",
+ /* 20 */ "Geometry",
+ /* 22 */ "Histogram",
+ /* 24 */ "WhiteAdjust"
+ };
+
+int
+print_xie_info(dpy, extname)
+ Display *dpy;
+ char *extname;
+{
+ XieExtensionInfo *xieInfo;
+ int i;
+ int ntechs;
+ XieTechnique *techs;
+ XieTechniqueGroup prevGroup;
+
+ if (!XieInitialize(dpy, &xieInfo ))
+ return 0;
+
+ print_standard_extension_info(dpy, extname,
+ xieInfo->server_major_rev, xieInfo->server_minor_rev);
+
+ printf(" service class: %s\n", subset_names[xieInfo->service_class]);
+ printf(" alignment: %s\n", align_names[xieInfo->alignment]);
+ printf(" uncnst_mantissa: %d\n", xieInfo->uncnst_mantissa);
+ printf(" uncnst_min_exp: %d\n", xieInfo->uncnst_min_exp);
+ printf(" uncnst_max_exp: %d\n", xieInfo->uncnst_max_exp);
+ printf(" cnst_levels:");
+ for (i = 0; i < xieInfo->n_cnst_levels; i++)
+ printf(" %d", xieInfo->cnst_levels[i]);
+ printf("\n");
+
+ if (!XieQueryTechniques(dpy, xieValAll, &ntechs, &techs))
+ return 1;
+
+ prevGroup = -1;
+
+ for (i = 0; i < ntechs; i++)
+ {
+ if (techs[i].group != prevGroup)
+ {
+ printf(" technique group: %s\n", group_names[techs[i].group >> 1]);
+ prevGroup = techs[i].group;
+ }
+ printf(" %s\tspeed: %d needs_param: %s number: %d\n",
+ techs[i].name,
+ techs[i].speed, (techs[i].needs_param ? "True " : "False"),
+ techs[i].number);
+ }
+ return 1;
+} /* end print_xie_info */
+
+#endif /* HAVE_XIE */
+
+
+#ifdef HAVE_XTEST
+int
+print_xtest_info(dpy, extname)
+ Display *dpy;
+ char *extname;
+{
+ int majorrev, minorrev, foo;
+
+ if (!XTestQueryExtension(dpy, &foo, &foo, &majorrev, &minorrev))
+ return 0;
+ print_standard_extension_info(dpy, extname, majorrev, minorrev);
+ return 1;
+}
+#endif /* HAVE_XTEST */
+
+#ifdef HAVE_XSYNC
+int
+print_sync_info(dpy, extname)
+ Display *dpy;
+ char *extname;
+{
+ int majorrev, minorrev;
+ XSyncSystemCounter *syscounters;
+ int ncounters, i;
+
+ if (!XSyncInitialize(dpy, &majorrev, &minorrev))
+ return 0;
+ print_standard_extension_info(dpy, extname, majorrev, minorrev);
+
+ syscounters = XSyncListSystemCounters(dpy, &ncounters);
+ printf(" system counters: %d\n", ncounters);
+ for (i = 0; i < ncounters; i++)
+ {
+ printf(" %s id: 0x%08x resolution_lo: %d resolution_hi: %d\n",
+ syscounters[i].name, syscounters[i].counter,
+ XSyncValueLow32(syscounters[i].resolution),
+ XSyncValueHigh32(syscounters[i].resolution));
+ }
+ XSyncFreeSystemCounterList(syscounters);
+ return 1;
+}
+#endif /* HAVE_XSYNC */
+
+int
+print_shape_info(dpy, extname)
+ Display *dpy;
+ char *extname;
+{
+ int majorrev, minorrev;
+
+ if (!XShapeQueryVersion(dpy, &majorrev, &minorrev))
+ return 0;
+ print_standard_extension_info(dpy, extname, majorrev, minorrev);
+ return 1;
+}
+
+#ifdef MITSHM
+int
+print_mitshm_info(dpy, extname)
+ Display *dpy;
+ char *extname;
+{
+ int majorrev, minorrev;
+ Bool sharedPixmaps;
+
+ if (!XShmQueryVersion(dpy, &majorrev, &minorrev, &sharedPixmaps))
+ return 0;
+ print_standard_extension_info(dpy, extname, majorrev, minorrev);
+ printf(" shared pixmaps: ");
+ if (sharedPixmaps)
+ {
+ int format = XShmPixmapFormat(dpy);
+ printf("yes, format: %d\n", format);
+ }
+ else
+ {
+ printf("no\n");
+ }
+ return 1;
+}
+#endif /* MITSHM */
+
+#ifdef HAVE_XDBE
+int
+print_dbe_info(dpy, extname)
+ Display *dpy;
+ char *extname;
+{
+ int majorrev, minorrev;
+ XdbeScreenVisualInfo *svi;
+ int numscreens = 0;
+ int iscrn, ivis;
+
+ if (!XdbeQueryExtension(dpy, &majorrev, &minorrev))
+ return 0;
+
+ print_standard_extension_info(dpy, extname, majorrev, minorrev);
+ svi = XdbeGetVisualInfo(dpy, (Drawable *)NULL, &numscreens);
+ for (iscrn = 0; iscrn < numscreens; iscrn++)
+ {
+ printf(" Double-buffered visuals on screen %d\n", iscrn);
+ for (ivis = 0; ivis < svi[iscrn].count; ivis++)
+ {
+ printf(" visual id 0x%lx depth %d perflevel %d\n",
+ svi[iscrn].visinfo[ivis].visual,
+ svi[iscrn].visinfo[ivis].depth,
+ svi[iscrn].visinfo[ivis].perflevel);
+ }
+ }
+ XdbeFreeVisualInfo(svi);
+ return 1;
+}
+#endif /* HAVE_XDBE */
+
+#ifdef HAVE_XRECORD
+int
+print_record_info(dpy, extname)
+ Display *dpy;
+ char *extname;
+{
+ int majorrev, minorrev;
+
+ if (!XRecordQueryVersion(dpy, &majorrev, &minorrev))
+ return 0;
+ print_standard_extension_info(dpy, extname, majorrev, minorrev);
+ return 1;
+}
+#endif /* HAVE_XRECORD */
+
+/* utilities to manage the list of recognized extensions */
+
+
+typedef int (*ExtensionPrintFunc)(
+#if NeedFunctionPrototypes
+ Display *, char *
+#endif
+);
+
+typedef struct {
+ char *extname;
+ ExtensionPrintFunc printfunc;
+ Bool printit;
+} ExtensionPrintInfo;
+
+ExtensionPrintInfo known_extensions[] =
+{
+#ifdef MITSHM
+ {"MIT-SHM", print_mitshm_info, False},
+#endif /* MITSHM */
+ {MULTIBUFFER_PROTOCOL_NAME, print_multibuf_info, False},
+ {"SHAPE", print_shape_info, False},
+#ifdef HAVE_XSYNC
+ {SYNC_NAME, print_sync_info, False},
+#endif /* HAVE_XSYNC */
+#ifdef HAVE_XIE
+ {xieExtName, print_xie_info, False},
+#endif /* HAVE_XIE */
+#ifdef HAVE_XTEST
+ {XTestExtensionName, print_xtest_info, False},
+#endif /* HAVE_XTEST */
+#ifdef HAVE_XDBE
+ {"DOUBLE-BUFFER", print_dbe_info, False},
+#endif /* HAVE_XDBE */
+#ifdef HAVE_XRECORD
+ {"RECORD", print_record_info, False}
+#endif /* HAVE_XRECORD */
+ /* add new extensions here */
+ /* wish list: PEX XKB LBX */
+};
+
+int num_known_extensions = sizeof known_extensions / sizeof known_extensions[0];
+
+void
+print_known_extensions(f)
+ FILE *f;
+{
+ int i;
+ for (i = 0; i < num_known_extensions; i++)
+ {
+ fprintf(f, "%s ", known_extensions[i].extname);
+ }
+}
+
+void
+mark_extension_for_printing(extname)
+ char *extname;
+{
+ int i;
+
+ if (strcmp(extname, "all") == 0)
+ {
+ for (i = 0; i < num_known_extensions; i++)
+ known_extensions[i].printit = True;
+ }
+ else
+ {
+ for (i = 0; i < num_known_extensions; i++)
+ {
+ if (strcmp(extname, known_extensions[i].extname) == 0)
+ {
+ known_extensions[i].printit = True;
+ return;
+ }
+ }
+ printf("%s extension not supported by %s\n", extname, ProgramName);
+ }
+}
+
+void
+print_marked_extensions(dpy)
+ Display *dpy;
+{
+ int i;
+ for (i = 0; i < num_known_extensions; i++)
+ {
+ if (known_extensions[i].printit)
+ {
+ printf("\n");
+ if (! (*known_extensions[i].printfunc)(dpy,
+ known_extensions[i].extname))
+ {
+ printf("%s extension not supported by server\n",
+ known_extensions[i].extname);
+ }
+ }
+ }
+}
+
+static void usage ()
+{
+ fprintf (stderr, "usage: %s [options]\n", ProgramName);
+ fprintf (stderr, "-display displayname\tserver to query\n");
+ fprintf (stderr, "-queryExtensions\tprint info returned by XQueryExtension\n");
+ fprintf (stderr, "-ext all\t\tprint detailed info for all supported extensions\n");
+ fprintf (stderr, "-ext extension-name\tprint detailed info for extension-name if one of:\n ");
+ print_known_extensions(stderr);
+ fprintf (stderr, "\n");
+ exit (1);
+}
+
+int main (argc, argv)
+ 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];
+
+ for (i = 1; i < argc; i++) {
+ char *arg = argv[i];
+ int len = strlen(arg);
+
+ if (!strncmp("-display", arg, len)) {
+ if (++i >= argc) usage ();
+ displayname = argv[i];
+ } else if (!strncmp("-queryExtensions", arg, len)) {
+ queryExtensions = True;
+ } else if (!strncmp("-ext", arg, len)) {
+ if (++i >= argc) usage ();
+ mark_extension_for_printing(argv[i]);
+ } else
+ usage ();
+ }
+
+ dpy = XOpenDisplay (displayname);
+ if (!dpy) {
+ fprintf (stderr, "%s: unable to open display \"%s\".\n",
+ ProgramName, XDisplayName (displayname));
+ exit (1);
+ }
+
+#ifdef HAVE_OVERLAY
+ find_overlay_info (dpy);
+#endif /* HAVE_OVERLAY */
+
+ print_display_info (dpy);
+ for (i = 0; i < ScreenCount (dpy); i++) {
+ print_screen_info (dpy, i);
+ }
+
+ print_marked_extensions(dpy);
+
+ XCloseDisplay (dpy);
+ exit (0);
+}
diff --git a/driver/xscreensaver-command.c b/driver/xscreensaver-command.c
new file mode 100644
index 0000000..f4a855d
--- /dev/null
+++ b/driver/xscreensaver-command.c
@@ -0,0 +1,450 @@
+/* xscreensaver-command, Copyright (c) 1991-2013 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 <time.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+/* #include <X11/Xproto.h> / * for CARD32 */
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/Xutil.h> /* for XGetClassHint() */
+#include <X11/Xos.h>
+
+#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 "remote.h"
+#include "version.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;
+static Atom XA_ACTIVATE, XA_DEACTIVATE, XA_CYCLE, XA_NEXT, XA_PREV;
+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
+ length ISO C89 compilers are required to support" in the
+ usage string... */
+# endif
+static char *usage = "\n\
+usage: %s -<option>\n\
+\n\
+ This program provides external control of a running xscreensaver process.\n\
+ Version %s, copyright (c) 1991-%s Jamie Zawinski <jwz@jwz.org>.\n\
+\n\
+ The xscreensaver program is a daemon that runs in the background.\n\
+ You control a running xscreensaver process by sending it messages\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\
+\n\
+ -activate Turn on the screensaver (blank the screen), as if the user\n\
+ had been idle for long enough.\n\
+\n\
+ -deactivate Turns off the screensaver (un-blank the screen), as if user\n\
+ activity had been detected.\n\
+\n\
+ -cycle If the screensaver is active (the screen is blanked), then\n\
+ stop the current graphics demo and run a new one (chosen\n\
+ randomly.)\n\
+\n\
+ -next Like either -activate or -cycle, depending on which is more\n\
+ appropriate, except that the graphics hack that will be run\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\
+\n\
+ -prev Like -next, but goes in the other direction.\n\
+\n\
+ -select <N> Like -activate, but runs the Nth element in the list of\n\
+ hacks. By knowing what is in the `programs' list, and in\n\
+ what order, you can use this to activate the screensaver\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\
+ already active, this causes it to be locked as well.\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\
+ -time Prints the time at which the screensaver last activated or\n\
+ deactivated (roughly, how long the user has been idle or\n\
+ non-idle -- but not quite, since it only tells you when the\n\
+ screen became blanked or un-blanked.)\n\
+\n\
+ -watch Prints a line each time the screensaver changes state: when\n\
+ the screen blanks, locks, unblanks, or when the running hack\n\
+ is changed. This option never returns; it is intended for\n\
+ use by shell scripts that want to react to the screensaver\n\
+ in some way.\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); \
+ } while(0)
+
+static int watch (Display *);
+
+int
+main (int argc, char **argv)
+{
+ Display *dpy;
+ int i;
+ char *dpyname = 0;
+ Atom *cmd = 0;
+ long arg = 0L;
+ char *s;
+ Atom XA_WATCH = 0; /* kludge: not really an atom */
+ char year[5];
+
+ 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;
+
+ s = strchr (screensaver_id, '-');
+ s = strrchr (s, '-');
+ s++;
+ strncpy (year, s, 4);
+ year[4] = 0;
+
+ 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 ();
+ if (!strncmp (s, "-display", L)) dpyname = argv [++i];
+ else if (cmd) USAGE();
+ else if (!strncmp (s, "-activate", L)) cmd = &XA_ACTIVATE;
+ else if (!strncmp (s, "-deactivate", L)) cmd = &XA_DEACTIVATE;
+ else if (!strncmp (s, "-cycle", L)) cmd = &XA_CYCLE;
+ else if (!strncmp (s, "-next", L)) cmd = &XA_NEXT;
+ else if (!strncmp (s, "-prev", L)) cmd = &XA_PREV;
+ else if (!strncmp (s, "-select", L)) cmd = &XA_SELECT;
+ else if (!strncmp (s, "-exit", L)) cmd = &XA_EXIT;
+ else if (!strncmp (s, "-restart", L)) cmd = &XA_RESTART;
+ else if (!strncmp (s, "-demo", L)) cmd = &XA_DEMO;
+ 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 ();
+
+ if (cmd == &XA_SELECT || cmd == &XA_DEMO)
+ {
+ long a;
+ char c;
+ if (i+1 < argc && (1 == sscanf(argv[i+1], " %ld %c", &a, &c)))
+ {
+ arg = a;
+ i++;
+ }
+ }
+ }
+
+ if (!cmd)
+ USAGE ();
+
+ if (arg < 0)
+ /* no command may have a negative argument. */
+ USAGE();
+ else if (arg == 0)
+ {
+ /* SELECT must have a non-zero argument. */
+ if (cmd == &XA_SELECT)
+ USAGE();
+ }
+ else /* arg > 0 */
+ {
+ /* no command other than SELECT and DEMO may have a non-zero argument. */
+ if (cmd != &XA_DEMO && cmd != &XA_SELECT)
+ USAGE();
+ }
+
+
+
+ /* 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.
+ */
+ if (cmd == &XA_PREFS ||
+ (cmd == &XA_DEMO && arg == 0))
+ {
+ char buf [512];
+ char *new_argv[] = { "xscreensaver-demo", 0, 0, 0, 0, 0 };
+ int ac = 1;
+
+ if (dpyname)
+ {
+ new_argv[ac++] = "-display";
+ new_argv[ac++] = dpyname;
+ }
+
+ if (cmd == &XA_PREFS)
+ new_argv[ac++] = "-prefs";
+
+ fflush(stdout);
+ fflush(stderr);
+ execvp (new_argv[0], new_argv); /* shouldn't return */
+
+ sprintf (buf, "%s: could not exec %s", progname, new_argv[0]);
+ perror(buf);
+ fflush(stdout);
+ fflush(stderr);
+ exit (-1);
+ }
+
+
+
+ if (!dpyname) dpyname = (char *) getenv ("DISPLAY");
+
+ if (!dpyname)
+ {
+ dpyname = ":0.0";
+ fprintf (stderr,
+ "%s: warning: $DISPLAY is not set: defaulting to \"%s\".\n",
+ progname, dpyname);
+ }
+
+ dpy = XOpenDisplay (dpyname);
+ if (!dpy)
+ {
+ fprintf (stderr, "%s: can't open display %s\n", progname,
+ (dpyname ? dpyname : "(null)"));
+ 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_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);
+
+ if (cmd == &XA_WATCH)
+ {
+ i = watch (dpy);
+ exit (i);
+ }
+
+ if (*cmd == XA_ACTIVATE || *cmd == XA_LOCK ||
+ *cmd == XA_NEXT || *cmd == XA_PREV || *cmd == XA_SELECT)
+ /* People never guess that KeyRelease deactivates the screen saver too,
+ so if we're issuing an activation command, wait a second.
+ No need to do this if stdin is not a tty, meaning we're not being
+ run from the command line.
+ */
+ if (isatty(0))
+ sleep (1);
+
+ i = xscreensaver_command (dpy, *cmd, arg, True, NULL);
+ if (i < 0) exit (i);
+ else exit (0);
+}
+
+
+static int
+watch (Display *dpy)
+{
+ char *v = 0;
+ Window window = RootWindow (dpy, 0);
+ XWindowAttributes xgwa;
+ XEvent event;
+ PROP32 *last = 0;
+
+ if (v) free (v);
+ XGetWindowAttributes (dpy, window, &xgwa);
+ XSelectInput (dpy, window, xgwa.your_event_mask | PropertyChangeMask);
+
+ while (1)
+ {
+ XNextEvent (dpy, &event);
+ if (event.xany.type == PropertyNotify &&
+ event.xproperty.state == PropertyNewValue &&
+ event.xproperty.atom == XA_SCREENSAVER_STATUS)
+ {
+ Atom type;
+ int format;
+ unsigned long nitems, bytesafter;
+ unsigned char *dataP = 0;
+
+ if (XGetWindowProperty (dpy,
+ RootWindow (dpy, 0), /* always screen #0 */
+ XA_SCREENSAVER_STATUS,
+ 0, 999, False, XA_INTEGER,
+ &type, &format, &nitems, &bytesafter,
+ &dataP)
+ == Success
+ && type
+ && dataP)
+ {
+ time_t tt;
+ char *s;
+ Bool changed = False;
+ Bool running = False;
+ PROP32 *data = (PROP32 *) dataP;
+
+ if (type != XA_INTEGER || nitems < 3)
+ {
+ STATUS_LOSE:
+ if (last) XFree (last);
+ if (data) XFree (data);
+ fprintf (stderr, "%s: bad status format on root window.\n",
+ progname);
+ return -1;
+ }
+
+ tt = (time_t) data[1];
+ if (tt <= (time_t) 666000000L) /* early 1991 */
+ goto STATUS_LOSE;
+
+ s = ctime(&tt);
+ if (s[strlen(s)-1] == '\n')
+ s[strlen(s)-1] = 0;
+
+ if (!last || data[0] != last[0])
+ {
+ /* State changed. */
+ if (data[0] == XA_BLANK)
+ printf ("BLANK %s\n", s);
+ else if (data[0] == XA_LOCK)
+ printf ("LOCK %s\n", s);
+ else if (data[0] == 0)
+ printf ("UNBLANK %s\n", s);
+ else
+ goto STATUS_LOSE;
+ }
+
+ if (!last)
+ changed = True;
+ else
+ {
+ int i;
+ for (i = 2; i < nitems; i++)
+ {
+ if (data[i] != last[i])
+ changed = True;
+ if (data[i])
+ running = True;
+ }
+ }
+
+ if (running && changed)
+ {
+ int i;
+ fprintf (stdout, "RUN");
+ for (i = 2; i < nitems; i++)
+ fprintf (stdout, " %d", (int) data[i]);
+ fprintf (stdout, "\n");
+ }
+
+ fflush (stdout);
+
+ if (last) XFree (last);
+ last = data;
+ }
+ else
+ {
+ if (last) XFree (last);
+ if (dataP) XFree (dataP);
+ 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
new file mode 100644
index 0000000..040a183
--- /dev/null
+++ b/driver/xscreensaver-command.man
@@ -0,0 +1,263 @@
+.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 "09-Nov-2013 (5.23)" "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 | \
+\-version | \
+\-time | \
+\-watch]
+.SH DESCRIPTION
+The \fIxscreensaver\-command\fP program controls a running \fIxscreensaver\fP
+process by sending it client-messages.
+
+.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).
+
+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
+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.)
+.TP 8
+.B \-prefs
+Like the no-argument form of \fI\-demo\fP, but brings up that program's
+Preferences panel by default.
+.TP 8
+.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
+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
+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 screen from blanking.)
+.TP 8
+.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,
+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.)
+.TP 8
+.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.
+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
+Tells the running xscreensaver process to lock the screen immediately.
+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.)
+
+Note that locking doesn't work unless the \fIxscreensaver\fP process is
+running as you. See
+.BR xscreensaver (1)
+for details.
+.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.)
+.TP 8
+.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 \-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
+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.)
+
+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
+
+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
+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.
+.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
+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
+See the
+.BR xset (1)
+manual for more info.
+
+You can also use
+.BR xscreensaver-demo (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
+will stop being run.
+.SH DIAGNOSTICS
+If an error occurs while communicating with the \fIxscreensaver\fP daemon, or
+if the daemon reports an error, a diagnostic message will be printed to
+stderr, and \fIxscreensaver-command\fP will exit with a non-zero value. If
+the command is accepted, an indication of this will be printed to stdout, and
+the exit value will be zero.
+.SH ENVIRONMENT
+.PP
+.TP 8
+.B DISPLAY
+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).
+Note that this variable is consulted in the environment of
+the \fIxscreensaver\fP process, not the \fIxscreensaver-command\fP process.
+.SH UPGRADES
+The latest version of
+.BR xscreensaver (1)
+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 xset (1)
+.SH COPYRIGHT
+Copyright \(co 1992-2013 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-demo.glade2.in b/driver/xscreensaver-demo.glade2.in
new file mode 100644
index 0000000..ad0095d
--- /dev/null
+++ b/driver/xscreensaver-demo.glade2.in
@@ -0,0 +1,3136 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+<requires lib="gnome"/>
+
+<widget 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>
+ <widget class="GtkVBox" id="outer_vbox">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">5</property>
+
+ <child>
+ <widget class="GtkMenuBar" 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>
+
+ <child>
+ <widget class="GtkMenuItem" id="file">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_File</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="file_menu_cb" last_modification_time="Sun, 06 Mar 2005 21:41:13 GMT"/>
+
+ <child>
+ <widget class="GtkMenu" id="file_menu">
+
+ <child>
+ <widget class="GtkMenuItem" id="activate_menu">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Blank Screen Now</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="activate_menu_cb"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="lock_menu">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Lock Screen Now</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="lock_menu_cb"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="kill_menu">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Kill Daemon</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="kill_menu_cb"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="restart">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Restart Daemon</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="restart_menu_cb"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="separator1">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="exit_menu">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Quit</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="exit_menu_cb"/>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="help">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Help</property>
+ <property name="use_underline">True</property>
+
+ <child>
+ <widget class="GtkMenu" id="help_menu">
+
+ <child>
+ <widget class="GtkMenuItem" id="about_menu">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_About...</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="about_menu_cb"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="doc_menu">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Documentation...</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="doc_menu_cb"/>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ <widget 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 name="switch_page" handler="switch_page_cb"/>
+
+ <child>
+ <widget 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>
+ <widget 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>
+ <widget 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>
+ <atkrelation target="cycle_spinbutton" type="label-for"/>
+ <atkrelation target="cycle_spinbutton" type="flows-to"/>
+ </accessibility>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEventBox" id="lock_button_eventbox">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">Whether a password should be required to un-blank the screen.</property>
+ <property name="visible_window">True</property>
+ <property name="above_child">False</property>
+
+ <child>
+ <widget 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>
+ <atkproperty name="AtkObject::accessible_name" translatable="yes">Lock Screen</atkproperty>
+ <atkrelation target="lock_spinbutton" type="controller-for"/>
+ <atkrelation target="lock_spinbutton" type="label-for"/>
+ <atkrelation target="lock_spinbutton" type="flows-to"/>
+ </accessibility>
+ <signal name="toggled" handler="pref_changed_cb"/>
+ </widget>
+ </child>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkSpinButton" id="timeout_spinbutton">
+ <property name="visible">True</property>
+ <property name="tooltip" 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">1 1 720 1 15 0</property>
+ <accessibility>
+ <atkrelation target="timeout_label" type="labelled-by"/>
+ <atkrelation target="timeout_mlabel" type="labelled-by"/>
+ <atkrelation target="timeout_label" type="flows-from"/>
+ <atkrelation target="timeout_mlabel" type="flows-to"/>
+ </accessibility>
+ <signal name="activate" handler="pref_changed_cb"/>
+ <signal name="focus_out_event" handler="pref_changed_event_cb"/>
+ <signal name="value_changed" handler="pref_changed_cb"/>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkSpinButton" id="lock_spinbutton">
+ <property name="visible">True</property>
+ <property name="tooltip" 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">0 0 720 1 15 0</property>
+ <accessibility>
+ <atkproperty name="AtkObject::accessible_name" translatable="yes">Lock Screen After</atkproperty>
+ <atkrelation target="lock_button" type="controlled-by"/>
+ <atkrelation target="lock_button" type="labelled-by"/>
+ <atkrelation target="lock_mlabel" type="labelled-by"/>
+ <atkrelation target="lock_button" type="flows-from"/>
+ <atkrelation target="lock_mlabel" type="flows-to"/>
+ </accessibility>
+ <signal name="activate" handler="pref_changed_cb"/>
+ <signal name="focus_out_event" handler="pref_changed_event_cb"/>
+ <signal name="value_changed" handler="pref_changed_cb"/>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkSpinButton" id="cycle_spinbutton">
+ <property name="visible">True</property>
+ <property name="tooltip" 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">0 0 720 1 15 0</property>
+ <accessibility>
+ <atkrelation target="cycle_label" type="labelled-by"/>
+ <atkrelation target="cycle_mlabel" type="labelled-by"/>
+ <atkrelation target="cycle_label" type="flows-from"/>
+ <atkrelation target="cycle_mlabel" type="flows-to"/>
+ </accessibility>
+ <signal name="activate" handler="pref_changed_cb"/>
+ <signal name="focus_out_event" handler="pref_changed_event_cb"/>
+ <signal name="value_changed" handler="pref_changed_cb"/>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ <atkrelation target="lock_spinbutton" type="label-for"/>
+ <atkrelation target="lock_spinbutton" type="flows-to"/>
+ </accessibility>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ <atkrelation target="cycle_spinbutton" type="label-for"/>
+ <atkrelation target="cycle_spinbutton" type="flows-from"/>
+ </accessibility>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ <atkrelation target="timeout_spinbutton" type="label-for"/>
+ <atkrelation target="timeout_spinbutton" type="flows-to"/>
+ </accessibility>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ <atkrelation target="timeout_spinbutton" type="label-for"/>
+ <atkrelation target="timeout_spinbutton" type="flows-from"/>
+ </accessibility>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+ </widget>
+ <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>
+ <widget 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>
+ <widget class="GtkButton" id="demo">
+ <property name="visible">True</property>
+ <property name="tooltip" 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 name="clicked" handler="run_this_cb"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="settings">
+ <property name="visible">True</property>
+ <property name="tooltip" 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 name="clicked" handler="settings_cb"/>
+ </widget>
+ </child>
+ </widget>
+ <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>
+ <widget 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>
+ <widget class="GtkHBox" id="mode_hbox">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget 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>
+ <atkrelation target="mode_menu_popup" type="label-for"/>
+ </accessibility>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkOptionMenu" id="mode_menu">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="history">0</property>
+ <accessibility>
+ <atkrelation target="mode_label" type="labelled-by"/>
+ </accessibility>
+
+ <child internal-child="menu">
+ <widget class="GtkMenu" id="mode_menu_popup">
+ <property name="visible">True</property>
+
+ <child>
+ <widget class="GtkMenuItem" id="disable_menuitem">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">Never blank the screen or power down the monitor.</property>
+ <property name="label" translatable="yes">_Disable Screen Saver</property>
+ <property name="use_underline">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="blank_menuitem">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">When idle or locked, blacken the screen only.</property>
+ <property name="label" translatable="yes">_Blank Screen Only</property>
+ <property name="use_underline">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="one_menuitem">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">When idle or locked, run the display mode selected below.</property>
+ <property name="label" translatable="yes">_Only One Screen Saver</property>
+ <property name="use_underline">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="random_menuitem">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">When idle or locked, choose a random display mode from among the checked items in the list below.</property>
+ <property name="label" translatable="yes">_Random Screen Saver</property>
+ <property name="use_underline">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="random_same_menuitem">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">When idle or locked, choose a random display mode from among the checked items in the list below. Run that same mode on each monitor.</property>
+ <property name="label" translatable="yes">_Same Random Savers</property>
+ <property name="use_underline">True</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">4</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">10</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ <widget 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>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHBox" id="centering_hbox">
+ <property name="visible">True</property>
+ <property name="homogeneous">True</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkHBox" id="next_prev_hbox">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkButton" id="next">
+ <property name="visible">True</property>
+ <property name="tooltip" 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 name="clicked" handler="run_next_cb"/>
+
+ <child>
+ <widget 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>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="prev">
+ <property name="visible">True</property>
+ <property name="tooltip" 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 name="clicked" handler="run_prev_cb"/>
+
+ <child>
+ <widget 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>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ <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>
+ </widget>
+ <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>
+ <widget 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>
+ <atkrelation target="label1" type="labelled-by"/>
+ </accessibility>
+
+ <child>
+ <widget 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>
+ <widget 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>
+ <widget class="GtkDrawingArea" id="preview">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ </widget>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ </widget>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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 &quot;xscreensaver-extras&quot; and
+&quot;xscreensaver-gl-extras&quot; 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>
+ </widget>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget 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>
+ <atkrelation target="preview_frame" type="label-for"/>
+ </accessibility>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <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>
+ </widget>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ <widget 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>
+ <atkrelation target="label2" type="labelled-by"/>
+ </accessibility>
+
+ <child>
+ <widget 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>
+ <widget 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>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkVBox" id="grab_vbox">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkCheckButton" id="grab_desk_button">
+ <property name="visible">True</property>
+ <property name="tooltip" 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 name="toggled" handler="pref_changed_cb"/>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkCheckButton" id="grab_video_button">
+ <property name="visible">True</property>
+ <property name="tooltip" 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 name="toggled" handler="pref_changed_cb"/>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkCheckButton" id="grab_image_button">
+ <property name="visible">True</property>
+ <property name="tooltip" 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>
+ <atkrelation target="image_text" type="controller-for"/>
+ <atkrelation target="image_browse_button" type="controller-for"/>
+ </accessibility>
+ <signal name="toggled" handler="pref_changed_cb"/>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHBox" id="image_hbox">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkLabel" id="grab_dummy">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"></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">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>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="image_text">
+ <property name="visible">True</property>
+ <property name="tooltip" 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>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ <accessibility>
+ <atkrelation target="grab_image_button" type="labelled-by"/>
+ <atkrelation target="grab_image_button" type="controlled-by"/>
+ </accessibility>
+ <signal name="activate" handler="pref_changed_cb"/>
+ <signal name="focus_out_event" handler="pref_changed_event_cb"/>
+ </widget>
+ <packing>
+ <property name="padding">2</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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 name="clicked" handler="browse_image_dir_cb"/>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget 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>
+ <atkrelation target="grab_frame" type="label-for"/>
+ </accessibility>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <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>
+ <widget 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>
+ <atkrelation target="label3" type="labelled-by"/>
+ </accessibility>
+
+ <child>
+ <widget 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>
+ <widget 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>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ <widget class="GtkRadioButton" id="text_radio">
+ <property name="visible">True</property>
+ <property name="tooltip" 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>
+ <atkrelation target="text_entry" type="controller-for"/>
+ <atkrelation target="text_entry" type="label-for"/>
+ </accessibility>
+ <signal name="toggled" handler="pref_changed_cb" last_modification_time="Sun, 20 Mar 2005 21:31:44 GMT"/>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkRadioButton" id="text_file_radio">
+ <property name="visible">True</property>
+ <property name="tooltip" 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>
+ <atkrelation target="text_file_entry" type="label-for"/>
+ <atkrelation target="text_file_entry" type="controller-for"/>
+ </accessibility>
+ <signal name="toggled" handler="pref_changed_cb" last_modification_time="Sun, 20 Mar 2005 21:31:55 GMT"/>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkRadioButton" id="text_program_radio">
+ <property name="visible">True</property>
+ <property name="tooltip" 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>
+ <atkrelation target="text_program_entry" type="label-for"/>
+ <atkrelation target="text_program_entry" type="controller-for"/>
+ <atkrelation target="text_program_browse" type="controller-for"/>
+ </accessibility>
+ <signal name="toggled" handler="pref_changed_cb" last_modification_time="Sun, 20 Mar 2005 21:32:07 GMT"/>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkRadioButton" id="text_url_radio">
+ <property name="visible">True</property>
+ <property name="tooltip" 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>
+ <atkrelation target="text_url_entry" type="label-for"/>
+ <atkrelation target="text_url_entry" type="controller-for"/>
+ </accessibility>
+ <signal name="toggled" handler="pref_changed_cb" last_modification_time="Sun, 20 Mar 2005 21:32:17 GMT"/>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkRadioButton" id="text_host_radio">
+ <property name="visible">True</property>
+ <property name="tooltip" 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 name="toggled" handler="pref_changed_cb" last_modification_time="Sun, 20 Mar 2005 21:31:32 GMT"/>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="text_url_entry">
+ <property name="visible">True</property>
+ <property name="tooltip" 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>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ <accessibility>
+ <atkrelation target="text_url_radio" type="controlled-by"/>
+ </accessibility>
+ <signal name="activate" handler="pref_changed_cb" last_modification_time="Sun, 20 Mar 2005 21:33:10 GMT"/>
+ <signal name="focus_out_event" handler="pref_changed_event_cb" last_modification_time="Sun, 20 Mar 2005 21:34:26 GMT"/>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ <atkrelation target="text_file_radio" type="controlled-by"/>
+ </accessibility>
+ <signal name="clicked" handler="browse_text_file_cb" last_modification_time="Sun, 20 Mar 2005 01:24:38 GMT"/>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="text_entry">
+ <property name="visible">True</property>
+ <property name="tooltip" 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>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ <accessibility>
+ <atkrelation target="text_program_radio" type="labelled-by"/>
+ <atkrelation target="text_program_radio" type="controlled-by"/>
+ </accessibility>
+ <signal name="activate" handler="pref_changed_cb" last_modification_time="Sun, 20 Mar 2005 21:32:42 GMT"/>
+ <signal name="focus_out_event" handler="pref_changed_event_cb" last_modification_time="Sun, 20 Mar 2005 21:33:43 GMT"/>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="text_program_entry">
+ <property name="visible">True</property>
+ <property name="tooltip" 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>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ <accessibility>
+ <atkrelation target="text_program_radio" type="labelled-by"/>
+ <atkrelation target="text_program_radio" type="controlled-by"/>
+ </accessibility>
+ <signal name="activate" handler="pref_changed_cb" last_modification_time="Sun, 20 Mar 2005 21:33:02 GMT"/>
+ <signal name="focus_out_event" handler="pref_changed_event_cb" last_modification_time="Sun, 20 Mar 2005 21:34:15 GMT"/>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ <atkrelation target="text_program_radio" type="controller-for"/>
+ </accessibility>
+ <signal name="clicked" handler="browse_text_program_cb" last_modification_time="Sun, 20 Mar 2005 01:24:51 GMT"/>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="text_file_entry">
+ <property name="visible">True</property>
+ <property name="tooltip" 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>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ <accessibility>
+ <atkrelation target="text_file_radio" type="labelled-by"/>
+ <atkrelation target="text_file_radio" type="controlled-by"/>
+ </accessibility>
+ <signal name="activate" handler="pref_changed_cb" last_modification_time="Sun, 20 Mar 2005 21:32:53 GMT"/>
+ <signal name="focus_out_event" handler="pref_changed_event_cb" last_modification_time="Sun, 20 Mar 2005 21:33:55 GMT"/>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget 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>
+ <atkrelation target="diag_frame" type="label-for"/>
+ </accessibility>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <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>
+ <widget 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>
+ <widget 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>
+ <widget 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>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkVBox" id="vbox6">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkCheckButton" id="dpms_button">
+ <property name="visible">True</property>
+ <property name="tooltip" 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>
+ <atkrelation target="dpms_suspend_spinbutton" type="controller-for"/>
+ <atkrelation target="dpms_standby_spinbutton" type="controller-for"/>
+ <atkrelation target="dpms_off_spinbutton" type="controller-for"/>
+ </accessibility>
+ <signal name="toggled" handler="pref_changed_cb"/>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ <widget 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>
+ <atkrelation target="dpms_standby_spinbutton" type="label-for"/>
+ <atkrelation target="dpms_standby_spinbutton" type="flows-to"/>
+ </accessibility>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ <atkrelation target="dpms_suspend_spinbutton" type="label-for"/>
+ <atkrelation target="dpms_suspend_spinbutton" type="flows-to"/>
+ </accessibility>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ <atkrelation target="dpms_off_spinbutton" type="label-for"/>
+ <atkrelation target="dpms_off_spinbutton" type="flows-to"/>
+ </accessibility>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ <atkrelation target="dpms_standby_spinbutton" type="label-for"/>
+ <atkrelation target="dpms_standby_spinbutton" type="flows-from"/>
+ </accessibility>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ <atkrelation target="dpms_suspend_spinbutton" type="label-for"/>
+ <atkrelation target="dpms_suspend_spinbutton" type="flows-from"/>
+ </accessibility>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ <atkrelation target="dpms_off_spinbutton" type="label-for"/>
+ <atkrelation target="dpms_off_spinbutton" type="flows-from"/>
+ </accessibility>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkSpinButton" id="dpms_off_spinbutton">
+ <property name="visible">True</property>
+ <property name="tooltip" 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">0 0 1440 1 15 0</property>
+ <accessibility>
+ <atkrelation target="dpms_button" type="controlled-by"/>
+ <atkrelation target="dpms_off_label" type="labelled-by"/>
+ <atkrelation target="dpms_off_mlabel" type="labelled-by"/>
+ <atkrelation target="dpms_off_label" type="flows-from"/>
+ <atkrelation target="dpms_off_mlabel" type="flows-to"/>
+ </accessibility>
+ <signal name="activate" handler="pref_changed_cb"/>
+ <signal name="focus_out_event" handler="pref_changed_event_cb"/>
+ <signal name="value_changed" handler="pref_changed_cb"/>
+ </widget>
+ <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>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkSpinButton" id="dpms_suspend_spinbutton">
+ <property name="visible">True</property>
+ <property name="tooltip" 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">0 0 1440 1 15 0</property>
+ <accessibility>
+ <atkrelation target="dpms_button" type="controlled-by"/>
+ <atkrelation target="dpms_suspend_label" type="labelled-by"/>
+ <atkrelation target="dpms_suspend_mlabel" type="labelled-by"/>
+ <atkrelation target="dpms_suspend_label" type="flows-from"/>
+ <atkrelation target="dpms_suspend_mlabel" type="flows-to"/>
+ </accessibility>
+ <signal name="activate" handler="pref_changed_cb"/>
+ <signal name="focus_out_event" handler="pref_changed_event_cb"/>
+ <signal name="value_changed" handler="pref_changed_cb"/>
+ </widget>
+ <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>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkSpinButton" id="dpms_standby_spinbutton">
+ <property name="visible">True</property>
+ <property name="tooltip" 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">0 0 1440 1 15 0</property>
+ <accessibility>
+ <atkrelation target="dpms_button" type="controlled-by"/>
+ <atkrelation target="dpms_standby_label" type="labelled-by"/>
+ <atkrelation target="dpms_standby_mlabel" type="labelled-by"/>
+ <atkrelation target="dpms_standby_label" type="flows-from"/>
+ <atkrelation target="dpms_standby_mlabel" type="flows-to"/>
+ </accessibility>
+ <signal name="activate" handler="pref_changed_cb"/>
+ <signal name="focus_out_event" handler="pref_changed_event_cb"/>
+ <signal name="value_changed" handler="pref_changed_cb"/>
+ </widget>
+ <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>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkCheckButton" id="dpms_quickoff_button">
+ <property name="visible">True</property>
+ <property name="tooltip" 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 name="toggled" handler="pref_changed_cb"/>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget 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>
+ <atkrelation target="dpms_frame" type="label-for"/>
+ </accessibility>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <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>
+ <widget class="GtkFrame" id="cmap_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>
+ <atkrelation target="label5" type="labelled-by"/>
+ </accessibility>
+
+ <child>
+ <widget class="GtkHBox" id="cmap_hbox">
+ <property name="border_width">8</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">8</property>
+
+ <child>
+ <widget 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>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkVBox" id="vbox7">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkCheckButton" id="fade_button">
+ <property name="visible">True</property>
+ <property name="tooltip" 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>
+ <atkrelation target="fade_spinbutton" type="controller-for"/>
+ </accessibility>
+ <signal name="toggled" handler="pref_changed_cb"/>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkCheckButton" id="unfade_button">
+ <property name="visible">True</property>
+ <property name="tooltip" 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>
+ <atkrelation target="fade_spinbutton" type="controller-for"/>
+ </accessibility>
+ <signal name="toggled" handler="pref_changed_cb"/>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHBox" id="fade_hbox">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkLabel" id="fade_dummy">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"></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">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>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ <atkrelation target="fade_spinbutton" type="label-for"/>
+ <atkrelation target="fade_spinbutton" type="flows-to"/>
+ </accessibility>
+ </widget>
+ <packing>
+ <property name="padding">14</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkSpinButton" id="fade_spinbutton">
+ <property name="visible">True</property>
+ <property name="tooltip" 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">0 0 10 1 1 0</property>
+ <accessibility>
+ <atkrelation target="unfade_button" type="controlled-by"/>
+ <atkrelation target="fade_button" type="controlled-by"/>
+ <atkrelation target="fade_label" type="labelled-by"/>
+ <atkrelation target="fade_sec_label" type="labelled-by"/>
+ <atkrelation target="fade_label" type="flows-from"/>
+ <atkrelation target="fade_sec_label" type="flows-to"/>
+ </accessibility>
+ <signal name="activate" handler="pref_changed_cb"/>
+ <signal name="focus_out_event" handler="pref_changed_event_cb"/>
+ <signal name="value_changed" handler="pref_changed_cb"/>
+ </widget>
+ <packing>
+ <property name="padding">4</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ <atkrelation target="fade_spinbutton" type="label-for"/>
+ <atkrelation target="fade_spinbutton" type="flows-from"/>
+ </accessibility>
+ </widget>
+ <packing>
+ <property name="padding">2</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHSeparator" id="cmap_hr">
+ <property name="visible">True</property>
+ </widget>
+ <packing>
+ <property name="padding">8</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkCheckButton" id="install_button">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">Whether to install a private colormap when running in 8-bit mode on the default Visual.</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Install _Colormap</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 name="toggled" handler="pref_changed_cb"/>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Fading and Colormaps</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>
+ <atkrelation target="cmap_frame" type="label-for"/>
+ </accessibility>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <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>
+ </widget>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHButtonBox" id="hbuttonbox2">
+ <property name="border_width">5</property>
+ <property name="layout_style">GTK_BUTTONBOX_EDGE</property>
+ <property name="spacing">10</property>
+
+ <child>
+ <widget 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 name="clicked" handler="doc_menu_cb"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget 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 name="clicked" handler="exit_menu_cb"/>
+ </widget>
+ </child>
+ </widget>
+ <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>
+ </widget>
+ </child>
+</widget>
+
+<widget 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>
+ @COMMENT_DEMO_GLADE2_GTK_2_22_HEAD@<property name="has_separator">False</property>@COMMENT_DEMO_GLADE2_GTK_2_22_TAIL@
+
+ <child internal-child="vbox">
+ <widget 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">
+ <widget class="GtkHButtonBox" id="dialog_action_area">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+
+ <child>
+ <widget 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>
+ <property name="response_id">0</property>
+ <signal name="clicked" handler="settings_adv_cb"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget 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>
+ <property name="response_id">0</property>
+ <signal name="clicked" handler="settings_std_cb"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget 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>
+ <property name="response_id">0</property>
+ <signal name="clicked" handler="settings_reset_cb"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget 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>
+ <property name="response_id">-6</property>
+ <signal name="clicked" handler="settings_cancel_cb"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget 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>
+ <property name="response_id">-5</property>
+ <signal name="clicked" handler="settings_ok_cb"/>
+ </widget>
+ </child>
+ </widget>
+ <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>
+ <widget class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget 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>
+ <atkrelation target="label6" type="labelled-by"/>
+ </accessibility>
+
+ <child>
+ <widget 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 name="switch_page" handler="settings_switch_page_cb"/>
+
+ <child>
+ <widget class="GtkVBox" id="settings_vbox">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="tab_expand">True</property>
+ <property name="tab_fill">True</property>
+ <property name="tab_pack">GTK_PACK_END</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ <widget 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>
+ </widget>
+ <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>
+ <widget 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>
+ <atkrelation target="cmd_text" type="label-for"/>
+ </accessibility>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ <accessibility>
+ <atkrelation target="cmd_label" type="labelled-by"/>
+ </accessibility>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHBox" id="visual_hbox">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget 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_entry</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>
+ <atkrelation target="visual_combo" type="label-for"/>
+ </accessibility>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkCombo" id="visual_combo">
+ <property name="visible">True</property>
+ <property name="value_in_list">False</property>
+ <property name="allow_empty">True</property>
+ <property name="case_sensitive">False</property>
+ <property name="enable_arrow_keys">True</property>
+ <property name="enable_arrows_always">False</property>
+ <accessibility>
+ <atkrelation target="visual" type="labelled-by"/>
+ </accessibility>
+
+ <child internal-child="entry">
+ <widget 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>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ </widget>
+ </child>
+
+ <child internal-child="list">
+ <widget class="GtkList" id="combo-list1">
+ <property name="visible">True</property>
+ <property name="selection_mode">GTK_SELECTION_BROWSE</property>
+
+ <child>
+ <widget class="GtkListItem" id="listitem25">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Any</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkListItem" id="listitem26">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Best</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkListItem" id="listitem27">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Default</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkListItem" id="listitem28">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Default-N</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkListItem" id="listitem29">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">GL</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkListItem" id="listitem30">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">TrueColor</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkListItem" id="listitem31">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">PseudoColor</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkListItem" id="listitem32">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">StaticGray</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkListItem" id="listitem33">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">GrayScale</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkListItem" id="listitem34">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">DirectColor</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkListItem" id="listitem35">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Color</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkListItem" id="listitem36">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Gray</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkListItem" id="listitem37">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Mono</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ <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>
+ </widget>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget 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>
+ <atkrelation target="opt_frame" type="label-for"/>
+ </accessibility>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget 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>
+ <widget 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>
+ <widget class="GtkLabel" id="doc">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes"></property>
+ <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>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget 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 name="clicked" handler="manual_cb"/>
+ </widget>
+ <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>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"></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>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
diff --git a/driver/xscreensaver-demo.glade2p b/driver/xscreensaver-demo.glade2p
new file mode 100644
index 0000000..3dfe894
--- /dev/null
+++ b/driver/xscreensaver-demo.glade2p
@@ -0,0 +1,19 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-project SYSTEM "http://glade.gnome.org/glade-project-2.0.dtd">
+
+<glade-project>
+ <name>XScreenSaver Demo</name>
+ <program_name>xscreensaver-demo</program_name>
+ <source_directory></source_directory>
+ <pixmaps_directory>../utils/images</pixmaps_directory>
+ <use_widget_names>TRUE</use_widget_names>
+ <output_main_file>FALSE</output_main_file>
+ <output_build_files>FALSE</output_build_files>
+ <backup_source_files>FALSE</backup_source_files>
+ <main_source_file>demo-Gtk2-widgets.c</main_source_file>
+ <main_header_file>demo-Gtk2-widgets.h</main_header_file>
+ <handler_source_file>demo-Gtk2-stubs.c</handler_source_file>
+ <handler_header_file>demo-Gtk2-stubs.h</handler_header_file>
+ <support_source_file>demo-Gtk2-support.c</support_source_file>
+ <support_header_file>demo-Gtk2-support.h</support_header_file>
+</glade-project>
diff --git a/driver/xscreensaver-demo.man b/driver/xscreensaver-demo.man
new file mode 100644
index 0000000..7da5fea
--- /dev/null
+++ b/driver/xscreensaver-demo.man
@@ -0,0 +1,402 @@
+.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 "09-Nov-2013 (5.23)" "X Version 11"
+.SH NAME
+xscreensaver-demo - interactively control the background xscreensaver daemon
+.SH SYNOPSIS
+.B xscreensaver\-demo
+[\-display \fIhost:display.screen\fP]
+[\-prefs]
+[--debug]
+.SH DESCRIPTION
+The \fIxscreensaver\-demo\fP program is a graphical front-end for
+setting the parameters used by the background
+.BR xscreensaver (1)
+daemon.
+It is essentially two things: a tool for editing the \fI~/.xscreensaver\fP
+file; and a tool for demoing the various graphics hacks that
+the \fIxscreensaver\fP daemon will launch.
+
+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-demo\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-demo\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\-demo (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.
+
+The running saver will be restarted every this-many minutes even in
+\fIOnly One Screen Saver\fP mode, since some savers tend to converge
+on a steady state.
+.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, if it has one, in a new window (since each of the
+display modes is actually a separate program, they each have their
+own manual.)
+
+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.
+(All of these options work by invoking the
+.BR xscreensaver\-getimage (1)
+program, which is what actually does the work.)
+.RS 4
+.TP 4
+.B Grab Desktop Images
+If this option is selected, then they 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 card, selecting this option will allow
+the image-manipulating modes to capture a 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. (These parameters control
+the behavior of the
+.BR xscreensaver\-text (1)
+program, which is what actually does the work.)
+.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 HTTP URL 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. Be careful that the owner of that server doesn't
+consider that to be abusive.
+.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.
+
+If you're using a laptop, don't be surprised if this 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 only adjust the power-saving delays by changing settings
+in the BIOS in some hardware-specific way.
+.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. (This might be preferable
+on laptops.)
+.RE
+.PP
+.B Fading and Colormaps
+
+These options control how the screen fades to or from black when
+a screen saver begins or ends.
+.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. (Note:
+this doesn't work with all X servers.) A fade will also be done when
+switching graphics hacks (when the \fICycle After\fP expires.)
+.TP 4
+.B Unfade From Black When Unblanking
+The complement to \fIFade Colormap\fP: 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 Colormap\fP
+is also selected.
+.TP 4
+.B Fade Duration
+When fading or unfading are selected, this controls how long the fade will
+take.
+.TP 4
+.B Install Colormap
+On 8-bit screens, whether to install a private colormap while the
+screensaver is active, so that the graphics hacks can get as many
+colors as possible. This does nothing if you are running in 16-bit
+or better.
+.PP
+.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\-demo
+accepts the following command line options.
+.TP 8
+.B \-display \fIhost:display.screen\fP
+The X display to use. The \fIxscreensaver\-demo\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
+It is important that the \fIxscreensaver\fP and \fIxscreensaver\-demo\fP
+processes be running on the same machine, or at least, on two machines
+that share a file system. When \fIxscreensaver\-demo\fP writes a new version
+of the \fI~/.xscreensaver\fP file, it's important that the \fIxscreensaver\fP
+see that same file. If the two processes are seeing
+different \fI~/.xscreensaver\fP files, things will malfunction.
+.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-demo\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\fR or \fPhttp_proxy
+to get the default HTTP 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 (1),
+.BR xscreensaver\-text (1)
+.SH COPYRIGHT
+Copyright \(co 1992-2015 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-92.
+
+Please let me know if you find any bugs or make any improvements.
diff --git a/driver/xscreensaver-getimage-desktop b/driver/xscreensaver-getimage-desktop
new file mode 100755
index 0000000..2a6d345
--- /dev/null
+++ b/driver/xscreensaver-getimage-desktop
@@ -0,0 +1,174 @@
+#!/usr/bin/perl -w
+# Copyright © 2003-2013 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 script is invoked by "xscreensaver-getimage" on X11 MacOS systems
+# to grab an image of the desktop, and then load it on to the given X11
+# Drawable using the "xscreensaver-getimage-file" program.
+#
+# This script is only used in an *X11* build on MacOS systems.
+#
+# When running on non-Mac X11 systems, utils/grabscreen.c is used.
+#
+# However, when running under X11 on MacOS, that usual X11-based
+# screen-grabbing mechanism doesn't work, so we need to invoke the
+# "/usr/bin/screencapture" program to do it instead. (This script).
+#
+# However again, for the MacOS-native (Cocoa) build of the screen savers,
+# "utils/grabclient.c" instead links against "OSX/osxgrabscreen.m", which
+# grabs screen images directly without invoking a sub-process to do it.
+#
+# Created: 20-Oct-2003.
+
+
+require 5;
+#use diagnostics; # Fails on some MacOS 10.5 systems
+use strict;
+
+my $progname = $0; $progname =~ s@.*/@@g;
+my $version = q{ $Revision: 1.6 $ }; $version =~ s/^[^0-9]+([0-9.]+).*$/$1/;
+
+my @grabber = ("screencapture", "-x");
+my @converter = ("pdf2jpeg");
+
+my $verbose = 0;
+
+
+sub error($) {
+ ($_) = @_;
+ print STDERR "$progname: $_\n";
+ exit 1;
+}
+
+# returns the full path of the named program, or undef.
+#
+sub which($) {
+ my ($prog) = @_;
+ foreach (split (/:/, $ENV{PATH})) {
+ if (-x "$_/$prog") {
+ return $prog;
+ }
+ }
+ return undef;
+}
+
+sub check_path() {
+ my $ok = 1;
+ foreach ($grabber[0], $converter[0]) {
+ if (! which ($_)) {
+ print STDERR "$progname: \"$_\" not found on \$PATH.\n";
+ $ok = 0;
+ }
+ }
+ exit (1) unless $ok;
+}
+
+
+sub grab_image() {
+
+ check_path();
+
+ my $tmpdir = $ENV{TMPDIR};
+ $tmpdir = "/tmp" unless $tmpdir;
+
+ my $tmpfile = sprintf ("%s/xssgrab.%08x.pdf", $tmpdir, rand(0xffffffff));
+ my @cmd = (@grabber, $tmpfile);
+
+ unlink $tmpfile;
+
+ print STDERR "$progname: executing \"" . join(' ', @cmd) . "\"\n"
+ if ($verbose);
+ system (join(' ', @cmd) . ' 2>/dev/null');
+
+ my @st = stat($tmpfile);
+ my $size = (@st ? $st[7] : 0);
+ if ($size <= 2048) {
+ unlink $tmpfile;
+ if ($size == 0) {
+ error "\"" . join(' ', @cmd) . "\" produced no data.";
+ } else {
+ error "\"" . join(' ', @cmd) . "\" produced only $size bytes.";
+ }
+ }
+
+ # On MacOS 10.3, "screencapture -x" always wrote a PDF.
+ # On 10.4.2, it writes a PNG by default, and the output format can be
+ # changed with the new "-t" argument.
+ #
+ # So, for maximal compatibility, we run it without "-t", but look at
+ # the first few bytes to see if it's a PDF, and if it is, convert it
+ # to a JPEG first. Otherwise, we assume that whatever screencapture
+ # wrote is a file format that xscreensaver-getimage-file can already
+ # cope with (though it will have the extension ".pdf", regardless of
+ # what is actually in the file).
+ #
+ my $pdf_p = 0;
+ {
+ open (my $in, '<:raw', $tmpfile) || error ("$tmpfile: $!");
+ my $buf = '';
+ read ($in, $buf, 10);
+ close $in;
+ $pdf_p = ($buf =~ m/^%PDF-/s);
+ }
+
+ # If it's a PDF, convert it to a JPEG.
+ #
+ if ($pdf_p)
+ {
+ my $jpgfile = $tmpfile;
+ $jpgfile =~ s/\.[^.]+$//;
+ $jpgfile .= ".jpg";
+
+ @cmd = (@converter, $tmpfile, $jpgfile);
+ push @cmd, "--verbose" if ($verbose);
+
+ print STDERR "$progname: executing \"" . join(' ', @cmd) . "\"\n"
+ if ($verbose);
+ system (@cmd);
+ unlink $tmpfile;
+ $tmpfile = $jpgfile;
+ }
+
+ @st = stat($tmpfile);
+ $size = (@st ? $st[7] : 0);
+ if ($size <= 2048) {
+ unlink $tmpfile;
+ if ($size == 0) {
+ error "\"" . join(' ', @cmd) . "\" produced no data.";
+ } else {
+ error "\"" . join(' ', @cmd) . "\" produced only $size bytes.";
+ }
+ }
+
+ print STDERR "$progname: wrote \"$tmpfile\"\n" if ($verbose);
+ print STDOUT "$tmpfile\n";
+}
+
+
+sub usage() {
+ print STDERR "usage: $progname [--verbose]\n";
+ exit 1;
+}
+
+sub main() {
+ while ($_ = $ARGV[0]) {
+ shift @ARGV;
+ if (m/^--?verbose$/s) { $verbose++; }
+ elsif (m/^-v+$/s) { $verbose += length($_)-1; }
+ elsif (m/^--?name$/s) { } # ignored, for compatibility
+ elsif (m/^-./) { usage; }
+ else { usage; }
+ }
+ grab_image();
+}
+
+main;
+exit 0;
diff --git a/driver/xscreensaver-getimage-desktop.man b/driver/xscreensaver-getimage-desktop.man
new file mode 100644
index 0000000..1974525
--- /dev/null
+++ b/driver/xscreensaver-getimage-desktop.man
@@ -0,0 +1,55 @@
+.TH XScreenSaver 1 "07-Sep-2003 (4.13)" "X Version 11"
+.SH NAME
+xscreensaver-getimage-desktop - put a desktop image on the root window
+.SH SYNOPSIS
+.B xscreensaver-getimage-desktop
+[\-display \fIhost:display.screen\fP] [\--verbose] [\--stdout]
+.SH DESCRIPTION
+The \fIxscreensaver\-getimage\-desktop\fP program is a helper program
+for the xscreensaver hacks that manipulate images. Specifically, it
+is invoked by
+.BR xscreensaver\-getimage (1)
+as needed. This is not a user-level command.
+
+This program is only used on MacOS X / XDarwin systems, because
+on those systems, it's necessary to use the
+.BR screencapture (1)
+program to get an image of the desktop -- the usual X11
+mechanism for grabbing the screen doesn't work on OSX.
+
+This script works by running
+.BR screencapture (1)
+to get a PDF, then converting it to a JPEG with
+.BR pdf2jpeg (1),
+then loading it onto the window with
+.BR xscreensaver\-getimage\-file (1).
+.SH OPTIONS
+.I xscreensaver-getimage-desktop
+accepts the following options:
+.TP 4
+.B --verbose
+Print diagnostics.
+.TP 4
+.B --stdout
+Instead of loading the image onto the root window, write it to stdout
+as a PBM file.
+.SH SEE ALSO
+.BR screencapture (1),
+.BR pdf2jpeg (1),
+.BR X (1),
+.BR xscreensaver (1),
+.BR xscreensaver\-demo (1),
+.BR xscreensaver\-getimage (1),
+.BR xscreensaver\-getimage\-file (1),
+.BR xscreensaver\-getimage\-video (1),
+.SH COPYRIGHT
+Copyright \(co 2003 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>, 20-Oct-03.
diff --git a/driver/xscreensaver-getimage-file b/driver/xscreensaver-getimage-file
new file mode 100755
index 0000000..ba1ef30
--- /dev/null
+++ b/driver/xscreensaver-getimage-file
@@ -0,0 +1,1316 @@
+#!/usr/bin/perl -w
+# Copyright © 2001-2018 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 program chooses a random file from under the given directory, and
+# prints its name. The file will be an image file whose dimensions are
+# larger than a certain minimum size.
+#
+# If the directory is a URL, it is assumed to be an RSS or Atom feed.
+# The images from that feed will be downloaded, cached, and selected from
+# at random. The feed will be re-polled periodically, as needed.
+#
+# The various xscreensaver hacks that manipulate images ("jigsaw", etc.) get
+# the image to manipulate by running the "xscreensaver-getimage" program.
+#
+# Under X11, the "xscreensaver-getimage" program invokes this script,
+# depending on the value of the "chooseRandomImages" and "imageDirectory"
+# settings in the ~/.xscreensaver file (or .../app-defaults/XScreenSaver).
+# The screen savers invoke "xscreensaver-getimage" via utils/grabclient.c,
+# which then invokes this script.
+#
+# Under Cocoa, this script lives inside the .saver bundle, and is invoked
+# directly from utils/grabclient.c.
+#
+# Created: 12-Apr-01.
+
+require 5;
+#use diagnostics; # Fails on some MacOS 10.5 systems
+use strict;
+
+use POSIX;
+use Fcntl;
+
+use Fcntl ':flock'; # import LOCK_* constants
+
+use POSIX ':fcntl_h'; # S_ISDIR was here in Perl 5.6
+import Fcntl ':mode' unless defined &S_ISUID; # but it is here in Perl 5.8
+ # but in Perl 5.10, both of these load, and cause errors!
+ # So we have to check for S_ISUID instead of S_ISDIR? WTF?
+
+use Digest::MD5 qw(md5_base64);
+
+# Some Linux systems don't install LWP by default!
+# Only error out if we're actually loading a URL instead of local data.
+BEGIN { eval 'use LWP::Simple;' }
+
+
+my $progname = $0; $progname =~ s@.*/@@g;
+my ($version) = ('$Revision: 1.52 $' =~ m/\s(\d[.\d]+)\s/s);
+
+my $verbose = 0;
+
+# Whether to use MacOS X's Spotlight to generate the list of files.
+# When set to -1, uses Spotlight if "mdfind" exists.
+#
+# (In my experience, this isn't actually any faster, and might not find
+# everything if your Spotlight index is out of date, which happens often.)
+#
+my $use_spotlight_p = 0;
+
+# Whether to cache the results of the last run.
+#
+my $cache_p = 1;
+
+# Regenerate the cache if it is older than this many seconds.
+#
+my $cache_max_age = 60 * 60 * 3; # 3 hours
+
+# Re-poll RSS/Atom feeds when local copy is older than this many seconds.
+#
+my $feed_max_age = $cache_max_age;
+
+
+# This matches files that we are allowed to use as images (case-insensitive.)
+# Anything not matching this is ignored. This is so you can point your
+# imageDirectory at directory trees that have things other than images in
+# them, but it assumes that you gave your images sensible file extensions.
+#
+my @good_extensions = ('jpg', 'jpeg', 'pjpeg', 'pjpg', 'png', 'gif',
+ 'tif', 'tiff', 'xbm', 'xpm');
+my $good_file_re = '\.(' . join("|", @good_extensions) . ')$';
+
+# This matches file extensions that might occur in an image directory,
+# and that are never used in the name of a subdirectory. This is an
+# optimization that prevents us from having to stat() those files to
+# tell whether they are directories or not. (It speeds things up a
+# lot. Don't give your directories stupid names.)
+#
+my @nondir_extensions = ('ai', 'bmp', 'bz2', 'cr2', 'crw', 'db',
+ 'dmg', 'eps', 'gz', 'hqx', 'htm', 'html', 'icns', 'ilbm', 'mov',
+ 'nef', 'pbm', 'pdf', 'php', 'pl', 'ppm', 'ps', 'psd', 'sea', 'sh',
+ 'shtml', 'tar', 'tgz', 'thb', 'txt', 'xcf', 'xmp', 'Z', 'zip' );
+my $nondir_re = '\.(' . join("|", @nondir_extensions) . ')$';
+
+
+# JPEG, GIF, and PNG files that are are smaller than this are rejected:
+# this is so that you can use an image directory that contains both big
+# images and thumbnails, and have it only select the big versions.
+# But, if all of your images are smaller than this, all will be rejected.
+#
+my $min_image_width = 500;
+my $min_image_height = 500;
+
+my @all_files = (); # list of "good" files we've collected
+my %seen_inodes; # for breaking recursive symlink loops
+
+# For diagnostic messages:
+#
+my $dir_count = 1; # number of directories seen
+my $stat_count = 0; # number of files/dirs stat'ed
+my $skip_count_unstat = 0; # number of files skipped without stat'ing
+my $skip_count_stat = 0; # number of files skipped after stat
+
+my $config_file = $ENV{HOME} . "/.xscreensaver";
+my $image_directory = undef;
+
+
+sub find_all_files($);
+sub find_all_files($) {
+ my ($dir) = @_;
+
+ print STDERR "$progname: + reading dir $dir/...\n" if ($verbose > 1);
+
+ my $dd;
+ if (! opendir ($dd, $dir)) {
+ print STDERR "$progname: couldn't open $dir: $!\n" if ($verbose);
+ return;
+ }
+ my @files = readdir ($dd);
+ closedir ($dd);
+
+ my @dirs = ();
+
+ foreach my $file (@files) {
+ next if ($file =~ m/^\./); # silently ignore dot files/dirs
+
+ if ($file =~ m/[~%\#]$/) { # ignore backup files (and dirs...)
+ $skip_count_unstat++;
+ print STDERR "$progname: - skip file $file\n" if ($verbose > 1);
+ }
+
+ $file = "$dir/$file";
+
+ if ($file =~ m/$good_file_re/io) {
+ #
+ # Assume that files ending in .jpg exist and are not directories.
+ #
+ push @all_files, $file;
+ print STDERR "$progname: - found file $file\n" if ($verbose > 1);
+
+ } elsif ($file =~ m/$nondir_re/io) {
+ #
+ # Assume that files ending in .html are not directories.
+ #
+ $skip_count_unstat++;
+ print STDERR "$progname: -- skip file $file\n" if ($verbose > 1);
+
+ } else {
+ #
+ # Now we need to stat the file to see if it's a subdirectory.
+ #
+ # Note: we could use the trick of checking "nlinks" on the parent
+ # directory to see if this directory contains any subdirectories,
+ # but that would exclude any symlinks to directories.
+ #
+ my @st = stat($file);
+ my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
+ $atime,$mtime,$ctime,$blksize,$blocks) = @st;
+
+ $stat_count++;
+
+ if ($#st == -1) {
+ if ($verbose) {
+ my $ll = readlink $file;
+ if (defined ($ll)) {
+ print STDERR "$progname: + dangling symlink: $file -> $ll\n";
+ } else {
+ print STDERR "$progname: + unreadable: $file\n";
+ }
+ }
+ next;
+ }
+
+ next if ($seen_inodes{"$dev:$ino"}); # break symlink loops
+ $seen_inodes{"$dev:$ino"} = 1;
+
+ if (S_ISDIR($mode)) {
+ push @dirs, $file;
+ $dir_count++;
+ print STDERR "$progname: + found dir $file\n" if ($verbose > 1);
+
+ } else {
+ $skip_count_stat++;
+ print STDERR "$progname: + skip file $file\n" if ($verbose > 1);
+ }
+ }
+ }
+
+ foreach (@dirs) {
+ find_all_files ($_);
+ }
+}
+
+
+sub spotlight_all_files($) {
+ my ($dir) = @_;
+
+ my @terms = ();
+ # "public.image" matches all (indexed) images, including Photoshop, etc.
+# push @terms, "kMDItemContentTypeTree == 'public.image'";
+ foreach (@good_extensions) {
+
+ # kMDItemFSName hits the file system every time: much worse than "find".
+# push @terms, "kMDItemFSName == '*.$_'";
+
+ # kMDItemDisplayName matches against the name in the Spotlight index,
+ # but won't find files that (for whatever reason) didn't get indexed.
+ push @terms, "kMDItemDisplayName == '*.$_'";
+ }
+
+ $dir =~ s@([^-_/a-z\d.,])@\\$1@gsi; # quote for sh
+ my $cmd = "mdfind -onlyin $dir \"" . join (' || ', @terms) . "\"";
+
+ print STDERR "$progname: executing: $cmd\n" if ($verbose > 1);
+ @all_files = split (/[\r\n]+/, `$cmd`);
+}
+
+
+# If we're using cacheing, read the cache file and return its contents,
+# if any. This also holds an exclusive lock on the cache file, which
+# has the additional benefit that if two copies of this program are
+# running at once, one will wait for the other, instead of both of
+# them spanking the same file system at the same time.
+#
+my $cache_fd = undef;
+my $cache_file_name = undef;
+my $read_cache_p = 0;
+
+sub read_cache($) {
+ my ($dir) = @_;
+
+ return () unless ($cache_p);
+
+ my $dd = "$ENV{HOME}/Library/Caches"; # MacOS location
+ if (-d $dd) {
+ $cache_file_name = "$dd/org.jwz.xscreensaver.getimage.cache";
+ } elsif (-d "$ENV{HOME}/.cache") { # Gnome "FreeDesktop XDG" location
+ $dd = "$ENV{HOME}/.cache/xscreensaver";
+ if (! -d $dd) { mkdir ($dd) || error ("mkdir $dd: $!"); }
+ $cache_file_name = "$dd/xscreensaver-getimage.cache"
+ } elsif (-d "$ENV{HOME}/tmp") { # If ~/tmp/ exists, use it.
+ $cache_file_name = "$ENV{HOME}/tmp/.xscreensaver-getimage.cache";
+ } else {
+ $cache_file_name = "$ENV{HOME}/.xscreensaver-getimage.cache";
+ }
+
+ print STDERR "$progname: awaiting lock: $cache_file_name\n"
+ if ($verbose > 1);
+
+ my $file = $cache_file_name;
+ open ($cache_fd, '+>>', $file) || error ("unable to write $file: $!");
+ flock ($cache_fd, LOCK_EX) || error ("unable to lock $file: $!");
+ seek ($cache_fd, 0, 0) || error ("unable to rewind $file: $!");
+
+ my $mtime = (stat($cache_fd))[9];
+
+ if ($mtime + $cache_max_age < time) {
+ print STDERR "$progname: cache is too old\n" if ($verbose);
+ return ();
+ }
+
+ my $odir = <$cache_fd>;
+ $odir =~ s/[\r\n]+$//s if defined ($odir);
+ if (!defined ($odir) || ($dir ne $odir)) {
+ print STDERR "$progname: cache is for $odir, not $dir\n"
+ if ($verbose && $odir);
+ return ();
+ }
+
+ my @files = ();
+ while (<$cache_fd>) {
+ s/[\r\n]+$//s;
+ push @files, "$odir/$_";
+ }
+
+ print STDERR "$progname: " . ($#files+1) . " files in cache\n"
+ if ($verbose);
+
+ $read_cache_p = 1;
+ return @files;
+}
+
+
+sub write_cache($) {
+ my ($dir) = @_;
+
+ return unless ($cache_p);
+
+ # If we read the cache, just close it without rewriting it.
+ # If we didn't read it, then write it now.
+
+ if (! $read_cache_p) {
+
+ truncate ($cache_fd, 0) ||
+ error ("unable to truncate $cache_file_name: $!");
+ seek ($cache_fd, 0, 0) ||
+ error ("unable to rewind $cache_file_name: $!");
+
+ if ($#all_files >= 0) {
+ print $cache_fd "$dir\n";
+ foreach (@all_files) {
+ my $f = $_; # stupid Perl. do this to avoid modifying @all_files!
+ $f =~ s@^\Q$dir/@@so || die; # remove $dir from front
+ print $cache_fd "$f\n";
+ }
+ }
+
+ print STDERR "$progname: cached " . ($#all_files+1) . " files\n"
+ if ($verbose);
+ }
+
+ flock ($cache_fd, LOCK_UN) ||
+ error ("unable to unlock $cache_file_name: $!");
+ close ($cache_fd);
+ $cache_fd = undef;
+}
+
+
+sub html_unquote($) {
+ my ($h) = @_;
+
+ # This only needs to handle entities that occur in RSS, not full HTML.
+ my %ent = ( 'amp' => '&', 'lt' => '<', 'gt' => '>',
+ 'quot' => '"', 'apos' => "'" );
+ $h =~ s/(&(\#)?([[:alpha:]\d]+);?)/
+ {
+ my ($o, $c) = ($1, $3);
+ if (! defined($2)) {
+ $c = $ent{$c}; # for &lt;
+ } else {
+ if ($c =~ m@^x([\dA-F]+)$@si) { # for &#x41;
+ $c = chr(hex($1));
+ } elsif ($c =~ m@^\d+$@si) { # for &#65;
+ $c = chr($c);
+ } else {
+ $c = undef;
+ }
+ }
+ ($c || $o);
+ }
+ /gexi;
+ return $h;
+}
+
+
+
+# Figure out what the proxy server should be, either from environment
+# variables or by parsing the output of the (MacOS) program "scutil",
+# which tells us what the system-wide proxy settings are.
+#
+sub set_proxy($) {
+ my ($ua) = @_;
+
+ my $proxy_data = `scutil --proxy 2>/dev/null`;
+ foreach my $proto ('http', 'https') {
+ my ($server) = ($proxy_data =~ m/\b${proto}Proxy\s*:\s*([^\s]+)/si);
+ my ($port) = ($proxy_data =~ m/\b${proto}Port\s*:\s*([^\s]+)/si);
+ my ($enable) = ($proxy_data =~ m/\b${proto}Enable\s*:\s*([^\s]+)/si);
+
+ if ($server && $enable) {
+ # Note: this ignores the "ExceptionsList".
+ my $proto2 = 'http';
+ $ENV{"${proto}_proxy"} = ("${proto2}://" . $server .
+ ($port ? ":$port" : "") . "/");
+ print STDERR "$progname: MacOS $proto proxy: " .
+ $ENV{"${proto}_proxy"} . "\n"
+ if ($verbose > 2);
+ }
+ }
+
+ $ua->env_proxy();
+}
+
+
+sub init_lwp() {
+ if (! defined ($LWP::Simple::ua)) {
+ error ("\n\n\tPerl is broken. Do this to repair it:\n" .
+ "\n\tsudo cpan LWP::Simple LWP::Protocol::https Mozilla::CA\n");
+ }
+ set_proxy ($LWP::Simple::ua);
+}
+
+
+sub sanity_check_lwp() {
+ my $url1 = 'https://www.mozilla.org/';
+ my $url2 = 'http://www.mozilla.org/';
+ my $body = (LWP::Simple::get($url1) || '');
+ if (length($body) < 10240) {
+ my $err = "";
+ $body = (LWP::Simple::get($url2) || '');
+ if (length($body) < 10240) {
+ $err = "Perl is broken: neither HTTP nor HTTPS URLs work.";
+ } else {
+ $err = "Perl is broken: HTTP URLs work but HTTPS URLs don't.";
+ }
+ $err .= "\nMaybe try: sudo cpan -f Mozilla::CA LWP::Protocol::https";
+ $err =~ s/^/\t/gm;
+ error ("\n\n$err\n");
+ }
+}
+
+
+# If the URL does not already end with an extension appropriate for the
+# content-type, add it after a "#" search.
+#
+# This is for when we know the content type of the URL, but the URL is
+# some crazy thing without an extension. The files on disk need to have
+# proper extensions.
+#
+sub force_extension($$) {
+ my ($url, $ct) = @_;
+ return $url unless (defined($url) && defined($ct));
+ my ($ext) = ($ct =~ m@^image/([-a-z\d]+)@si);
+ return $url unless $ext;
+ $ext = lc($ext);
+ $ext = 'jpg' if ($ext eq 'jpeg');
+ return $url if ($url =~ m/\.$ext$/si);
+ return "$url#.$ext";
+}
+
+
+# Returns a list of the image enclosures in the RSS or Atom feed.
+# Elements of the list are references, [ "url", "guid" ].
+#
+sub parse_feed($);
+sub parse_feed($) {
+ my ($url) = @_;
+
+ init_lwp();
+ $LWP::Simple::ua->agent ("$progname/$version");
+ $LWP::Simple::ua->timeout (10); # bail sooner than the default of 3 minutes
+
+
+ # Half the time, random Linux systems don't have Mozilla::CA installed,
+ # which results in "Can't verify SSL peers without knowning which
+ # Certificate Authorities to trust".
+ #
+ # In xscreensaver-text we just disabled certificate checks. However,
+ # malicious images really do exist, so for xscreensaver-getimage-file,
+ # let's actually require that SSL be installed properly.
+
+ print STDERR "$progname: loading $url\n" if ($verbose);
+ my $body = (LWP::Simple::get($url) || '');
+
+ if ($body !~ m@^\s*<(\?xml|rss)\b@si) {
+ # Not an RSS/Atom feed. Try RSS autodiscovery.
+
+ # (Great news, everybody: Flickr no longer provides RSS for "Sets",
+ # only for "Photostreams", and only the first 20 images of those.
+ # Thanks, assholes.)
+
+ if ($body =~ m/^\s*$/s) {
+ sanity_check_lwp();
+ error ("null response: $url");
+ }
+
+ error ("not an RSS or Atom feed, or HTML: $url")
+ unless ($body =~ m@<(HEAD|BODY|A|IMG)\b@si);
+
+ # Find the first <link> with RSS or Atom in it, and use that instead.
+
+ $body =~ s@<LINK\s+([^<>]*)>@{
+ my $p = $1;
+ if ($p =~ m! \b REL \s* = \s* ['"]? alternate \b!six &&
+ $p =~ m! \b TYPE \s* = \s* ['"]? application/(atom|rss) !six &&
+ $p =~ m! \b HREF \s* = \s* ['"] ( [^<>'"]+ ) !six
+ ) {
+ my $u2 = html_unquote ($1);
+ if ($u2 =~ m!^/!s) {
+ my ($h) = ($url =~ m!^([a-z]+://[^/]+)!si);
+ $u2 = "$h$u2";
+ }
+ print STDERR "$progname: found feed: $u2\n"
+ if ($verbose);
+ return parse_feed ($u2);
+ }
+ '';
+ }@gsexi;
+
+ error ("no RSS or Atom feed for HTML page: $url");
+ }
+
+
+ $body =~ s@(<ENTRY|<ITEM)@\001$1@gsi;
+ my @items = split(/\001/, $body);
+ shift @items;
+
+ my @imgs = ();
+ my %ids;
+
+ foreach my $item (@items) {
+ my $iurl = undef;
+ my $id = undef;
+
+ # First look for <link rel="enclosure" href="...">
+ #
+ if (! $iurl) {
+ foreach my $link ($item =~ m@<LINK[^<>]*>@gsi) {
+ last if $iurl;
+ my ($href) = ($link =~ m/\bHREF\s*=\s*[\"\']([^<>\'\"]+)/si);
+ my ($type) = ($link =~ m/\bTYPE\s*=\s*[\"\']?([^<>\'\"]+)/si);
+ my ($rel) = ($link =~ m/\bREL\s*=\s*[\"\']?([^<>\'\"]+)/si);
+ $href = undef unless (lc($rel || '') eq 'enclosure');
+ $href = undef if ($type && $type !~ m@^image/@si); # omit videos
+ $iurl = html_unquote($href) if $href;
+ $iurl = force_extension ($iurl, $type);
+ }
+ }
+
+ # Then look for <media:content url="...">
+ #
+ if (! $iurl) {
+ foreach my $link ($item =~ m@<MEDIA:CONTENT[^<>]*>@gsi) {
+ last if $iurl;
+ my ($href) = ($link =~ m/\bURL\s*=\s*[\"\']([^<>\'\"]+)/si);
+ my ($type) = ($link =~ m/\bTYPE\s*=\s*[\"\']?([^<>\'\"]+)/si);
+ my ($med) = ($link =~ m/\bMEDIUM\s*=\s*[\"\']?([^<>\'\"]+)/si);
+ $type = 'image/jpeg' if (!$type && lc($med || '') eq 'image');
+ $href = undef if ($type && $type !~ m@^image/@si); # omit videos
+ $iurl = html_unquote($href) if $href;
+ $iurl = force_extension ($iurl, $type);
+ }
+ }
+
+ # Then look for <enclosure url="..."/>
+ #
+ if (! $iurl) {
+ foreach my $link ($item =~ m@<ENCLOSURE[^<>]*>@gsi) {
+ last if $iurl;
+ my ($href) = ($link =~ m/\bURL\s*=\s*[\"\']([^<>\'\"]+)/si);
+ my ($type) = ($link =~ m/\bTYPE\s*=\s*[\"\']?([^<>\'\"]+)/si);
+ $href = undef if ($type && $type !~ m@^image/@si); # omit videos
+ $iurl = html_unquote($href) if ($href);
+ $iurl = force_extension ($iurl, $type);
+ }
+ }
+
+ # Ok, maybe there's an image in the <url> field?
+ #
+ if (! $iurl) {
+ foreach my $link ($item =~ m@<URL\b[^<>]*>([^<>]*)@gsi) {
+ last if $iurl;
+ my $u2 = $1;
+ $iurl = html_unquote($u2) if ($u2 =~ m/$good_file_re/io);
+ if (! $iurl) {
+ my $u3 = $u2;
+ $u3 =~ s/#.*$//gs;
+ $u3 =~ s/[?&].*$//gs;
+ $iurl = html_unquote($u2) if ($u3 =~ m/$good_file_re/io);
+ }
+ }
+ }
+
+ # Then look for <content:encoded> or <description>... with an
+ # <img src="..."> inside. If more than one image, take the first.
+ #
+ foreach my $t ('content:encoded', 'description') {
+ last if $iurl;
+ foreach my $link ($item =~ m@<$t[^<>]*>(.*?)</$t>@gsi) {
+ last if $iurl;
+ my $desc = $1;
+ if ($desc =~ m@<!\[CDATA\[\s*(.*?)\s*\]\]>@gs) {
+ $desc = $1;
+ } else {
+ $desc = html_unquote($desc);
+ }
+ my ($href) = ($desc =~ m@<IMG[^<>]*\bSRC=[\"\']?([^\"\'<>]+)@si);
+ $iurl = html_unquote($href) if ($href);
+ # If IMG SRC has a bogus extension, pretend it's a JPEG.
+ $iurl = force_extension ($iurl, 'image/jpeg')
+ if ($iurl && $iurl !~ m/$good_file_re/io);
+ }
+ }
+
+ # Find a unique ID for this image, to defeat image farms.
+ # First look for <id>...</id>
+ ($id) = ($item =~ m!<ID\b[^<>]*>\s*([^<>]+?)\s*</ID>!si) unless $id;
+
+ # Then look for <guid isPermaLink=...> ... </guid>
+ ($id) = ($item =~ m!<GUID\b[^<>]*>\s*([^<>]+?)\s*</GUID>!si) unless $id;
+
+ # Then look for <link> ... </link>
+ ($id) = ($item =~ m!<LINK\b[^<>]*>\s*([^<>]+?)\s*</LINK>!si) unless $id;
+
+ # If we only have a GUID or LINK, but it's an image, use that.
+ $iurl = $id if (!$iurl && $id && $id =~ m/$good_file_re/io);
+
+ if ($iurl) {
+ $id = $iurl unless $id;
+ my $o = $ids{$id};
+ if (! $o) {
+ $ids{$id} = $iurl;
+ my @P = ($iurl, $id);
+ push @imgs, \@P;
+ } elsif ($iurl ne $o) {
+ print STDERR "$progname: WARNING: dup ID \"$id\"" .
+ " for \"$o\" and \"$iurl\"\n";
+ }
+ }
+ }
+
+ return @imgs;
+}
+
+
+# Like md5_base64 but uses filename-safe characters.
+#
+sub md5_file($) {
+ my ($s) = @_;
+ $s = md5_base64($s);
+ $s =~ s@[/]@_@gs;
+ $s =~ s@[+]@-@gs;
+ return $s;
+}
+
+
+# expands the first URL relative to the second.
+#
+sub expand_url($$) {
+ my ($url, $base) = @_;
+
+ $url =~ s/^\s+//gs; # lose whitespace at front and back
+ $url =~ s/\s+$//gs;
+
+ if (! ($url =~ m/^[a-z]+:/)) {
+
+ $base =~ s@(\#.*)$@@; # strip anchors
+ $base =~ s@(\?.*)$@@; # strip arguments
+ $base =~ s@/[^/]*$@/@; # take off trailing file component
+
+ my $tail = '';
+ if ($url =~ s@(\#.*)$@@) { $tail = $1; } # save anchors
+ if ($url =~ s@(\?.*)$@@) { $tail = "$1$tail"; } # save arguments
+
+ my $base2 = $base;
+
+ $base2 =~ s@^([a-z]+:/+[^/]+)/.*@$1@ # if url is an absolute path
+ if ($url =~ m@^/@);
+
+ my $ourl = $url;
+
+ $url = $base2 . $url;
+ $url =~ s@/\./@/@g; # expand "."
+ 1 while ($url =~ s@/[^/]+/\.\./@/@s); # expand ".."
+
+ $url .= $tail; # put anchors/args back
+
+ print STDERR "$progname: relative URL: $ourl --> $url\n"
+ if ($verbose > 1);
+
+ } else {
+ print STDERR "$progname: absolute URL: $url\n"
+ if ($verbose > 2);
+ }
+
+ return $url;
+}
+
+
+# Given the URL of an image, download it into the given directory
+# and return the file name.
+#
+sub download_image($$$) {
+ my ($url, $uid, $dir) = @_;
+
+ my $url2 = $url;
+ $url2 =~ s/\#.*$//s; # Omit search terms after file extension
+ $url2 =~ s/\?.*$//s;
+ my ($ext) = ($url =~ m@\.([a-z\d]+)$@si);
+ ($ext) = ($url2 =~ m@\.([a-z\d]+)$@si) unless $ext;
+
+ # If the feed hasn't put a sane extension on their URLs, nothing's going
+ # to work. This code assumes that file names have extensions, even the
+ # ones in the cache directory.
+ #
+ if (! $ext) {
+ print STDERR "$progname: skipping extensionless URL: $url\n"
+ if ($verbose > 1);
+ return undef;
+ }
+
+ # Don't bother downloading files that we will reject anyway.
+ #
+ if (! ($url =~ m/$good_file_re/io ||
+ $url2 =~ m/$good_file_re/io)) {
+ print STDERR "$progname: skipping non-image URL: $url\n"
+ if ($verbose > 1);
+ return undef;
+ }
+
+ my $file = md5_file ($uid);
+ $file .= '.' . lc($ext) if $ext;
+
+ # Don't bother doing If-Modified-Since to see if the URL has changed.
+ # If we have already downloaded it, assume it's good.
+ if (-f "$dir/$file") {
+ print STDERR "$progname: exists: $dir/$file for $uid / $url\n"
+ if ($verbose > 1);
+ return $file;
+ }
+
+ # Special-case kludge for Flickr:
+ # Their RSS feeds sometimes include only the small versions of the images.
+ # So if the URL ends in one of the "small-size" letters, change it to "b".
+ #
+ # _o orig, 1600 +
+ # _k large, 2048 max
+ # _h large, 1600 max
+ # _b large, 1024 max
+ # _c medium, 800 max
+ # _z medium, 640 max
+ # "" medium, 500 max
+ # _n small, 320 max
+ # _m small, 240 max
+ # _t thumb, 100 max
+ # _q square, 150x150
+ # _s square, 75x75
+ #
+ # Note: if we wanted to get the _k or _o version instead of the _b or _h
+ # version, we'd need to crack the DRM -- which is easy: see crack_secret
+ # in "https://www.jwz.org/hacks/galdown".
+ #
+ $url =~ s@_[sqtmnzc](\.[a-z]+)$@_b$1@si
+ if ($url =~ m@^https?://[^/?#&]*?flickr\.com/@si);
+
+ print STDERR "$progname: downloading: $dir/$file for $uid / $url\n"
+ if ($verbose > 1);
+ init_lwp();
+ $LWP::Simple::ua->agent ("$progname/$version");
+
+ $url =~ s/\#.*$//s; # Omit search terms
+ my $status = LWP::Simple::mirror ($url, "$dir/$file");
+ if (!LWP::Simple::is_success ($status)) {
+ print STDERR "$progname: error $status: $url\n"; # keep going
+ }
+
+ return $file;
+}
+
+
+sub mirror_feed($) {
+ my ($url) = @_;
+
+ if ($url !~ m/^https?:/si) { # not a URL: local directory.
+ return (undef, $url);
+ }
+
+ my $dir = "$ENV{HOME}/Library/Caches"; # MacOS location
+ if (-d $dir) {
+ $dir = "$dir/org.jwz.xscreensaver.feeds";
+ } elsif (-d "$ENV{HOME}/.cache") { # Gnome "FreeDesktop XDG" location
+ $dir = "$ENV{HOME}/.cache/xscreensaver";
+ if (! -d $dir) { mkdir ($dir) || error ("mkdir $dir: $!"); }
+ $dir .= "/feeds";
+ if (! -d $dir) { mkdir ($dir) || error ("mkdir $dir: $!"); }
+ } elsif (-d "$ENV{HOME}/tmp") { # If ~/tmp/ exists, use it.
+ $dir = "$ENV{HOME}/tmp/.xscreensaver-feeds";
+ } else {
+ $dir = "$ENV{HOME}/.xscreensaver-feeds";
+ }
+
+ if (! -d $dir) {
+ mkdir ($dir) || error ("mkdir $dir: $!");
+ print STDERR "$progname: mkdir $dir/\n" if ($verbose);
+ }
+
+ # MD5 for directory name to use for cache of a feed URL.
+ $dir .= '/' . md5_file ($url);
+
+ if (! -d $dir) {
+ mkdir ($dir) || error ("mkdir $dir: $!");
+ print STDERR "$progname: mkdir $dir/ for $url\n" if ($verbose);
+ }
+
+ # At this point, we have the directory corresponding to this URL.
+ # Now check to see if the files in it are up to date, and download
+ # them if not.
+
+ my $stamp = '.timestamp';
+ my $lock = "$dir/$stamp";
+
+ print STDERR "$progname: awaiting lock: $lock\n"
+ if ($verbose > 1);
+
+ my $mtime = ((stat($lock))[9]) || 0;
+
+ my $lock_fd;
+ open ($lock_fd, '+>>', $lock) || error ("unable to write $lock: $!");
+ flock ($lock_fd, LOCK_EX) || error ("unable to lock $lock: $!");
+ seek ($lock_fd, 0, 0) || error ("unable to rewind $lock: $!");
+
+ my $poll_p = ($mtime + $feed_max_age < time);
+
+ # --no-cache cmd line arg means poll again right now.
+ $poll_p = 1 unless ($cache_p);
+
+ # Even if the cache is young, make sure there is at least one file,
+ # and re-check if not.
+ #
+ if (! $poll_p) {
+ my $count = 0;
+ opendir (my $dirh, $dir) || error ("$dir: $!");
+ foreach my $f (readdir ($dirh)) {
+ next if ($f =~ m/^\./s);
+ $count++;
+ last;
+ }
+ closedir $dirh;
+
+ if ($count <= 0) {
+ print STDERR "$progname: no image files in cache of $url\n"
+ if ($verbose);
+ $poll_p = 1;
+ }
+ }
+
+ if ($poll_p) {
+
+ print STDERR "$progname: loading $url\n" if ($verbose);
+
+ my %files;
+ opendir (my $dirh, $dir) || error ("$dir: $!");
+ foreach my $f (readdir ($dirh)) {
+ next if ($f eq '.' || $f eq '..');
+ $files{$f} = 0; # 0 means "file exists, should be deleted"
+ }
+ closedir $dirh;
+
+ $files{$stamp} = 1;
+
+ # Download each image currently in the feed.
+ #
+ my $count = 0;
+ my @urls = parse_feed ($url);
+ print STDERR "$progname: " . ($#urls + 1) . " images\n"
+ if ($verbose > 1);
+ my %seen_src_urls;
+ foreach my $p (@urls) {
+ my ($furl, $id) = @$p;
+ $furl = expand_url ($furl, $url);
+
+ # No need to download the same image twice, even if it was in the feed
+ # multiple times under different GUIDs.
+ next if ($seen_src_urls{$furl});
+ $seen_src_urls{$furl} = 1;
+
+ my $f = download_image ($furl, $id, $dir);
+ next unless $f;
+ $files{$f} = 1; # Got it, don't delete
+ $count++;
+ }
+
+ my $empty_p = ($count <= 0);
+
+ # Now delete any files that are no longer in the feed.
+ # But if there was nothing in the feed (network failure?)
+ # then don't blow away the old files.
+ #
+ my $kept = 0;
+ foreach my $f (keys(%files)) {
+ if ($count <= 0) {
+ $kept++;
+ } elsif ($files{$f}) {
+ $kept++;
+ } else {
+ if (unlink ("$dir/$f")) {
+ print STDERR "$progname: rm $dir/$f\n" if ($verbose > 1);
+ } else {
+ print STDERR "$progname: rm $dir/$f: $!\n"; # don't bail
+ }
+ }
+ }
+
+ # Both feed and cache are empty. No files at all. Bail.
+ error ("empty feed: $url") if ($kept <= 1);
+
+ # Feed is empty, but we have some files from last time. Warn.
+ print STDERR "$progname: empty feed: using cache: $url\n"
+ if ($empty_p);
+
+ $mtime = time(); # update the timestamp
+
+ } else {
+
+ # Not yet time to re-check the URL.
+ print STDERR "$progname: using cache: $url\n" if ($verbose);
+
+ }
+
+ # Unlock and update the write date on the .timestamp file.
+ #
+ truncate ($lock_fd, 0) || error ("unable to truncate $lock: $!");
+ seek ($lock_fd, 0, 0) || error ("unable to rewind $lock: $!");
+ utime ($mtime, $mtime, $lock_fd) || error ("unable to touch $lock: $!");
+ flock ($lock_fd, LOCK_UN) || error ("unable to unlock $lock: $!");
+ close ($lock_fd);
+ $lock_fd = undef;
+ print STDERR "$progname: unlocked $lock\n" if ($verbose > 1);
+
+ # Don't bother using the imageDirectory cache. We know that this directory
+ # is flat, and we can assume that an RSS feed doesn't contain 100,000 images
+ # like ~/Pictures/ might.
+ #
+ $cache_p = 0;
+
+ # Return the URL and directory name of the files of that URL's local cache.
+ #
+ return ($url, $dir);
+}
+
+
+sub find_random_file($) {
+ my ($dir) = @_;
+
+ if ($use_spotlight_p == -1) {
+ $use_spotlight_p = 0;
+ if (-x '/usr/bin/mdfind') {
+ $use_spotlight_p = 1;
+ }
+ }
+
+ my $url;
+ ($url, $dir) = mirror_feed ($dir);
+
+ if ($url) {
+ $use_spotlight_p = 0;
+ print STDERR "$progname: $dir is cache for $url\n" if ($verbose > 1);
+ }
+
+ @all_files = read_cache ($dir);
+
+ if ($#all_files >= 0) {
+ # got it from the cache...
+
+ } elsif ($use_spotlight_p) {
+ print STDERR "$progname: spotlighting $dir...\n" if ($verbose);
+ spotlight_all_files ($dir);
+ print STDERR "$progname: found " . ($#all_files+1) .
+ " file" . ($#all_files == 0 ? "" : "s") .
+ " via Spotlight\n"
+ if ($verbose);
+ } else {
+ print STDERR "$progname: recursively reading $dir...\n" if ($verbose);
+ find_all_files ($dir);
+ print STDERR "$progname: " .
+ "f=" . ($#all_files+1) . "; " .
+ "d=$dir_count; " .
+ "s=$stat_count; " .
+ "skip=${skip_count_unstat}+$skip_count_stat=" .
+ ($skip_count_unstat + $skip_count_stat) .
+ ".\n"
+ if ($verbose);
+ }
+
+ write_cache ($dir);
+
+ if ($#all_files < 0) {
+ print STDERR "$progname: no image files in $dir\n";
+ exit 1;
+ }
+
+ my $max_tries = 50;
+ my $total_files = @all_files;
+ my $sparse_p = ($total_files < 20);
+
+ # If the directory has a lot of files in it:
+ # Make a pass through looking for hirez files (assume some are thumbs);
+ # If we found none, then, select any other file at random.
+ # Otherwise if there are a small number of files:
+ # Just select one at random (in case there's like, just one hirez).
+
+ for (my $check_size_p = $sparse_p ? 0 : 1;
+ $check_size_p >= 0; $check_size_p--) {
+
+ for (my $i = 0; $i < $max_tries; $i++) {
+ my $n = int (rand ($total_files));
+ my $file = $all_files[$n];
+ if (!$check_size_p || large_enough_p ($file)) {
+ if (! $url) {
+ $file =~ s@^\Q$dir/@@so || die; # remove $dir from front
+ }
+ return $file;
+ }
+ }
+ }
+
+ print STDERR "$progname: no suitable images in " . ($url || $dir) . " -- " .
+ ($total_files <= $max_tries
+ ? "all $total_files images"
+ : "$max_tries of $total_files images") .
+ " are smaller than ${min_image_width}x${min_image_height}.\n";
+
+ # If we got here, blow away the cache. Maybe it's stale.
+ unlink $cache_file_name if $cache_file_name;
+
+ exit 1;
+}
+
+
+sub large_enough_p($) {
+ my ($file) = @_;
+
+ my ($w, $h) = image_file_size ($file);
+
+ if (!defined ($h)) {
+
+ # Nonexistent files are obviously too small!
+ # Already printed $verbose message about the file not existing.
+ return 0 unless -f $file;
+
+ print STDERR "$progname: $file: unable to determine image size\n"
+ if ($verbose);
+ # Assume that unknown files are of good sizes: this will happen if
+ # they matched $good_file_re, but we don't have code to parse them.
+ # (This will also happen if the file is junk...)
+ return 1;
+ }
+
+ if ($w < $min_image_width || $h < $min_image_height) {
+ print STDERR "$progname: $file: too small ($w x $h)\n" if ($verbose);
+ return 0;
+ }
+
+ print STDERR "$progname: $file: $w x $h\n" if ($verbose);
+ return 1;
+}
+
+
+
+# Given the raw body of a GIF document, returns the dimensions of the image.
+#
+sub gif_size($) {
+ my ($body) = @_;
+ my $type = substr($body, 0, 6);
+ my $s;
+ return () unless ($type =~ /GIF8[7,9]a/);
+ $s = substr ($body, 6, 10);
+ my ($a,$b,$c,$d) = unpack ("C"x4, $s);
+ return (($b<<8|$a), ($d<<8|$c));
+}
+
+# Given the raw body of a JPEG document, returns the dimensions of the image.
+#
+sub jpeg_size($) {
+ my ($body) = @_;
+ my $i = 0;
+ my $L = length($body);
+
+ my $c1 = substr($body, $i, 1); $i++;
+ my $c2 = substr($body, $i, 1); $i++;
+ return () unless (ord($c1) == 0xFF && ord($c2) == 0xD8);
+
+ my $ch = "0";
+ while (ord($ch) != 0xDA && $i < $L) {
+ # Find next marker, beginning with 0xFF.
+ while (ord($ch) != 0xFF) {
+ return () if (length($body) <= $i);
+ $ch = substr($body, $i, 1); $i++;
+ }
+ # markers can be padded with any number of 0xFF.
+ while (ord($ch) == 0xFF) {
+ return () if (length($body) <= $i);
+ $ch = substr($body, $i, 1); $i++;
+ }
+
+ # $ch contains the value of the marker.
+ my $marker = ord($ch);
+
+ if (($marker >= 0xC0) &&
+ ($marker <= 0xCF) &&
+ ($marker != 0xC4) &&
+ ($marker != 0xCC)) { # it's a SOFn marker
+ $i += 3;
+ return () if (length($body) <= $i);
+ my $s = substr($body, $i, 4); $i += 4;
+ my ($a,$b,$c,$d) = unpack("C"x4, $s);
+ return (($c<<8|$d), ($a<<8|$b));
+
+ } else {
+ # We must skip variables, since FFs in variable names aren't
+ # valid JPEG markers.
+ return () if (length($body) <= $i);
+ my $s = substr($body, $i, 2); $i += 2;
+ my ($c1, $c2) = unpack ("C"x2, $s);
+ my $length = ($c1 << 8) | $c2;
+ return () if ($length < 2);
+ $i += $length-2;
+ }
+ }
+ return ();
+}
+
+# Given the raw body of a PNG document, returns the dimensions of the image.
+#
+sub png_size($) {
+ my ($body) = @_;
+ return () unless ($body =~ m/^\211PNG\r/s);
+ my ($bits) = ($body =~ m/^.{12}(.{12})/s);
+ return () unless defined ($bits);
+ return () unless ($bits =~ /^IHDR/);
+ my ($ign, $w, $h) = unpack("a4N2", $bits);
+ return ($w, $h);
+}
+
+
+# Given the raw body of a GIF, JPEG, or PNG document, returns the dimensions
+# of the image.
+#
+sub image_size($) {
+ my ($body) = @_;
+ return () if (length($body) < 10);
+ my ($w, $h) = gif_size ($body);
+ if ($w && $h) { return ($w, $h); }
+ ($w, $h) = jpeg_size ($body);
+ if ($w && $h) { return ($w, $h); }
+ # #### TODO: need image parsers for TIFF, XPM, XBM.
+ return png_size ($body);
+}
+
+# Returns the dimensions of the image file.
+#
+sub image_file_size($) {
+ my ($file) = @_;
+ my $in;
+ if (! open ($in, '<:raw', $file)) {
+ print STDERR "$progname: $file: $!\n" if ($verbose);
+ return ();
+ }
+ my $body = '';
+ sysread ($in, $body, 1024 * 50); # The first 50k should be enough.
+ close $in; # (It's not for certain huge jpegs...
+ return image_size ($body); # but we know they're huge!)
+}
+
+
+# Reads the prefs we use from ~/.xscreensaver
+#
+sub get_x11_prefs() {
+ my $got_any_p = 0;
+
+ if (open (my $in, '<', $config_file)) {
+ print STDERR "$progname: reading $config_file\n" if ($verbose > 1);
+ local $/ = undef; # read entire file
+ my $body = <$in>;
+ close $in;
+ $got_any_p = get_x11_prefs_1 ($body);
+
+ } elsif ($verbose > 1) {
+ print STDERR "$progname: $config_file: $!\n";
+ }
+
+ if (! $got_any_p && defined ($ENV{DISPLAY})) {
+ # We weren't able to read settings from the .xscreensaver file.
+ # Fall back to any settings in the X resource database
+ # (/usr/X11R6/lib/X11/app-defaults/XScreenSaver)
+ #
+ print STDERR "$progname: reading X resources\n" if ($verbose > 1);
+ my $body = `appres XScreenSaver xscreensaver -1`;
+ $got_any_p = get_x11_prefs_1 ($body);
+ }
+}
+
+
+sub get_x11_prefs_1($) {
+ my ($body) = @_;
+
+ my $got_any_p = 0;
+ $body =~ s@\\\n@@gs;
+ $body =~ s@^[ \t]*#[^\n]*$@@gm;
+
+ if ($body =~ m/^[.*]*imageDirectory:[ \t]*([^\s]+)\s*$/im) {
+ $image_directory = $1;
+ $got_any_p = 1;
+ }
+ return $got_any_p;
+}
+
+
+sub get_cocoa_prefs($) {
+ my ($id) = @_;
+ print STDERR "$progname: reading Cocoa prefs: \"$id\"\n" if ($verbose > 1);
+ my $v = get_cocoa_pref_1 ($id, "imageDirectory");
+ $v = '~/Pictures' unless defined ($v); # Match default in XScreenSaverView
+ $image_directory = $v if defined ($v);
+}
+
+
+sub get_cocoa_pref_1($$) {
+ my ($id, $key) = @_;
+ # make sure there's nothing stupid/malicious in either string.
+ $id =~ s/[^-a-z\d. ]/_/gsi;
+ $key =~ s/[^-a-z\d. ]/_/gsi;
+ my $cmd = "defaults -currentHost read \"$id\" \"$key\"";
+
+ print STDERR "$progname: executing $cmd\n"
+ if ($verbose > 3);
+
+ my $val = `$cmd 2>/dev/null`;
+ $val =~ s/^\s+//s;
+ $val =~ s/\s+$//s;
+
+ print STDERR "$progname: Cocoa: $id $key = \"$val\"\n"
+ if ($verbose > 2);
+
+ $val = undef if ($val =~ m/^$/s);
+
+ return $val;
+}
+
+
+sub error($) {
+ my ($err) = @_;
+ print STDERR "$progname: $err\n";
+ exit 1;
+}
+
+sub usage() {
+ print STDERR "usage: $progname [--verbose] [ directory-or-feed-url ]\n\n" .
+ " Prints the name of a randomly-selected image file. The directory\n" .
+ " is searched recursively. Images smaller than " .
+ "${min_image_width}x${min_image_height} are excluded.\n" .
+ "\n" .
+ " The directory may also be the URL of an RSS/Atom feed. Enclosed\n" .
+ " images will be downloaded and cached locally.\n" .
+ "\n";
+ exit 1;
+}
+
+sub main() {
+ my $cocoa_id = undef;
+ my $abs_p = 0;
+
+ while ($_ = $ARGV[0]) {
+ shift @ARGV;
+ if (m/^--?verbose$/s) { $verbose++; }
+ elsif (m/^-v+$/s) { $verbose += length($_)-1; }
+ elsif (m/^--?name$/s) { } # ignored, for compatibility
+ elsif (m/^--?spotlight$/s) { $use_spotlight_p = 1; }
+ elsif (m/^--?no-spotlight$/s) { $use_spotlight_p = 0; }
+ elsif (m/^--?cache$/s) { $cache_p = 1; }
+ elsif (m/^--?no-?cache$/s) { $cache_p = 0; }
+ elsif (m/^--?cocoa$/) { $cocoa_id = shift @ARGV; }
+ elsif (m/^--?abs(olute)?$/) { $abs_p = 1; }
+ elsif (m/^-./) { usage; }
+ elsif (!defined($image_directory)) { $image_directory = $_; }
+ else { usage; }
+ }
+
+ # Most hacks (X11 and Cocoa) pass a --directory value on the command line,
+ # but if they don't, look it up from the resources. Currently this only
+ # happens with "glitchpeg" which invokes xscreensaver-getimage-file
+ # directly instead of going through the traditional path.
+ #
+ if (! $image_directory) {
+ if (!defined ($cocoa_id)) {
+ # see OSX/XScreenSaverView.m
+ $cocoa_id = $ENV{XSCREENSAVER_CLASSPATH};
+ }
+
+ if (defined ($cocoa_id)) {
+ get_cocoa_prefs($cocoa_id);
+ error ("no imageDirectory in $cocoa_id") unless $image_directory;
+ } else {
+ get_x11_prefs();
+ error ("no imageDirectory in X11 resources") unless $image_directory;
+ }
+ }
+
+ usage unless (defined($image_directory));
+
+ $image_directory =~ s@^feed:@http:@si;
+
+ if ($image_directory =~ m/^https?:/si) {
+ # ok
+ } else {
+ $image_directory =~ s@^~/@$ENV{HOME}/@s; # allow literal "~/"
+ $image_directory =~ s@/+$@@s; # omit trailing /
+
+ if (! -d $image_directory) {
+ print STDERR "$progname: $image_directory not a directory or URL\n";
+ usage;
+ }
+ }
+
+ my $file = find_random_file ($image_directory);
+
+ # With --absolute return fully qualified paths instead of relative to --dir.
+ if ($abs_p &&
+ $file !~ m@^/@ &&
+ $image_directory =~ m@^/@s) {
+ $file = "$image_directory/$file";
+ $file =~ s@//+@/@gs;
+ }
+
+ print STDOUT "$file\n";
+}
+
+main;
+exit 0;
diff --git a/driver/xscreensaver-getimage-file.man b/driver/xscreensaver-getimage-file.man
new file mode 100644
index 0000000..778ad85
--- /dev/null
+++ b/driver/xscreensaver-getimage-file.man
@@ -0,0 +1,66 @@
+.TH XScreenSaver 1 "20-Mar-2005 (4.21)" "X Version 11"
+.SH NAME
+xscreensaver-getimage-file - put a randomly-selected image on the root window
+.SH SYNOPSIS
+.B xscreensaver-getimage-file
+[\-display \fIhost:display.screen\fP]
+[\--verbose]
+[\--name]
+[\--no-cache]
+directory-or-URL
+.SH DESCRIPTION
+The \fIxscreensaver\-getimage\-file\fP program is a helper program
+for the xscreensaver hacks that manipulate images. Specifically, it
+is invoked by
+.BR xscreensaver\-getimage (1)
+as needed. This is not a user-level command.
+
+This program selects a random image from disk, and loads it on the root
+window. It does this by figuring out which image-loading programs are
+installed on the system, and invoking the first one it finds.
+.SH OPTIONS
+.I xscreensaver-getimage-file
+accepts the following options:
+.TP 4
+.B --verbose
+Print diagnostics.
+.TP 4
+.B --name
+Don't load an image: instead just print the file name to stdout.
+.TP 4
+.I directory-or-URL
+If a directory is specified, it will be searched recursively for
+images. Any images found will eligible for display. For efficiency,
+the contents of the directory are cached for a few hours before it
+is re-scanned.
+
+If a URL is specified, it should be the URL of an RSS or Atom feed
+containing images. The first time it is accessed, all of the images
+in the feed will be downloaded to a local cache directory. If a few
+hours have elapsed since last time, the URL will be polled again, and
+any new images will be cached, any images no longer in the feed
+will be expired.
+.TP 4
+.B --no-cache
+Update the cache immediately, even if it is not time yet. This
+will re-scan the directory, or re-poll the RSS feed.
+.SH SEE ALSO
+.BR X (1),
+.BR xscreensaver (1),
+.BR xscreensaver\-demo (1),
+.BR xscreensaver\-getimage (1),
+.BR xv (1),
+.BR xli (1),
+.BR xloadimage (1),
+.BR chbg (1)
+.SH COPYRIGHT
+Copyright \(co 2001-2012 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>, 14-Apr-01
diff --git a/driver/xscreensaver-getimage-video b/driver/xscreensaver-getimage-video
new file mode 100755
index 0000000..dbc8986
--- /dev/null
+++ b/driver/xscreensaver-getimage-video
@@ -0,0 +1,144 @@
+#!/usr/bin/perl -w
+# Copyright © 2001-2015 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 program attempts to grab a single frame of video from the system's
+# video capture card, and then load it on to the root window using the
+# "xscreensaver-getimage-file" program. Various frame-grabbing programs
+# are known, and the first one found is used.
+#
+# The various xscreensaver hacks that manipulate images ("slidescreen",
+# "jigsaw", etc.) get the image to manipulate by running the
+# "xscreensaver-getimage" program.
+#
+# The various screen savers invoke "xscreensaver-getimage", which will in
+# turn invoke this program, depending on the value of the "grabVideoFrames"
+# setting in the ~/.xscreensaver file (or in the app-defaults file, usually
+# /usr/lib/X11/app-defaults/XScreenSaver).
+#
+# Created: 13-Apr-2001.
+
+require 5;
+#use diagnostics; # Fails on some MacOS 10.5 systems
+use strict;
+
+my $progname = $0; $progname =~ s@.*/@@g;
+my $version = q{ $Revision: 1.23 $ }; $version =~ s/^[^0-9]+([0-9.]+).*$/$1/;
+
+my $tmpdir = $ENV{TMPDIR} || "/tmp";
+my $tmpfile = sprintf("%s/xssv.%08x.ppm", $tmpdir, rand(0xFFFFFFFF));
+
+my $verbose = 0;
+
+
+# These are programs that can be used to grab a video frame. The first one
+# of these programs that exists on $PATH will be used, and the image file
+# is assumed to be written to $tmpfile (in some image format acceptable to
+# "xscreensaver-getimage-file", e.g., PPM or JPEG.)
+#
+# If you add other programs to this list, please let me know!
+#
+my @programs = (
+
+ "bttvgrab -d q -Q -l 1 -o ppm -f $tmpfile", # BTTV
+ "qcam > $tmpfile", # Connectix Qcam
+ "gqcam -t PPM -d $tmpfile", # GTK+ Qcam clone
+
+ "v4lctl snap ppm full $tmpfile", # XawTV 3.94.
+
+ "streamer -a -s 768x576 -o $tmpfile", # XawTV
+ # the "-a" option ("mute audio") arrived with XawTV 3.76.
+
+ "atitv snap $tmpfile", # ATI video capture card
+
+ "grab -type ppm -format ntsc -source 1 " . # *BSD BT848 module
+ "-settle 0.75 -output $tmpfile",
+
+ "motioneye -j $tmpfile", # Sony Vaio MotionEye
+ # (hardware jpeg encoder)
+
+ "vidcat -b -f ppm -s 640x480 > $tmpfile 2>&-", # w3cam/ovcam
+
+ "vidtomem -f $tmpfile 2>&- " . # Silicon Graphics
+ "&& mv $tmpfile-00000.rgb $tmpfile",
+
+ # Maybe this works?
+ # "ffmpeg -i /dev/video0 -ss 00:00:01 -vframes 1 $tmpfile 2>&-",
+
+ # "mplayer -really-quiet tv://0 " . # Maybe works with some cams?
+ # "-ao null -vo pnm -frames 1 2>&- " .
+ # "&& mv 00000001.ppm $tmpfile",
+
+);
+
+
+sub error($) {
+ my ($e) = @_;
+ print STDERR "$progname: $e\n";
+ exit 1;
+}
+
+sub pick_grabber() {
+ my @names = ();
+
+ foreach my $cmd (@programs) {
+ $_ = $cmd;
+ my ($name) = m/^([^ ]+)/;
+ push @names, "\"$name\"";
+ print STDERR "$progname: looking for $name...\n" if ($verbose > 2);
+ foreach my $dir (split (/:/, $ENV{PATH})) {
+ print STDERR "$progname: checking $dir/$name\n" if ($verbose > 3);
+ if (-x "$dir/$name") {
+ return $cmd;
+ }
+ }
+ }
+
+ $names[$#names] = "or " . $names[$#names];
+ error ("none of: " . join (", ", @names) . " were found on \$PATH.");
+}
+
+
+sub grab_image() {
+ my $cmd = pick_grabber();
+ unlink $tmpfile;
+
+ print STDERR "$progname: executing \"$cmd\"\n" if ($verbose);
+ system ($cmd);
+
+ if (! -s $tmpfile) {
+ unlink $tmpfile;
+ error ("\"$cmd\" produced no data.");
+ }
+
+ print STDERR "$progname: wrote \"$tmpfile\"\n" if ($verbose);
+ print STDOUT "$tmpfile\n";
+}
+
+
+sub usage() {
+ print STDERR "usage: $progname [--verbose] [--name | --stdout]\n";
+ exit 1;
+}
+
+sub main() {
+ while ($_ = $ARGV[0]) {
+ shift @ARGV;
+ if (m/^--?verbose$/s) { $verbose++; }
+ elsif (m/^-v+$/s) { $verbose += length($_)-1; }
+ elsif (m/^--?name$/s) { } # ignored, for compatibility
+ elsif (m/^-./) { usage; }
+ else { usage; }
+ }
+ grab_image();
+}
+
+main;
+exit 0;
diff --git a/driver/xscreensaver-getimage-video.man b/driver/xscreensaver-getimage-video.man
new file mode 100644
index 0000000..d19f34e
--- /dev/null
+++ b/driver/xscreensaver-getimage-video.man
@@ -0,0 +1,51 @@
+.TH XScreenSaver 1 "20-Mar-2005 (4.21)" "X Version 11"
+.SH NAME
+xscreensaver-getimage-video - put a video frame on the root window
+.SH SYNOPSIS
+.B xscreensaver-getimage-video
+[\-display \fIhost:display.screen\fP] [\--verbose] [\--stdout]
+.SH DESCRIPTION
+The \fIxscreensaver\-getimage\-video\fP program is a helper program
+for the xscreensaver hacks that manipulate images. Specifically, it
+is invoked by
+.BR xscreensaver\-getimage (1)
+as needed. This is not a user-level command.
+
+This program grabs a random frame of video from the system's video input,
+and then loads it on the root window. It does this by figuring out which
+frame-grabbing programs are installed on the system, and invoking the
+first one it finds. Then it runs
+.BR xscreensaver\-getimage\-file (1)
+to load that image onto the root window.
+.SH OPTIONS
+.I xscreensaver-getimage-video
+accepts the following options:
+.TP 4
+.B --verbose
+Print diagnostics.
+.TP 4
+.B --stdout
+Instead of loading the image onto the root window, write it to stdout
+as a PBM file.
+.SH SEE ALSO
+.BR X (1),
+.BR xscreensaver (1),
+.BR xscreensaver\-demo (1),
+.BR xscreensaver\-getimage (1),
+.BR xscreensaver\-getimage\-file (1),
+.BR bttvgrab (1),
+.BR qcam (1),
+.BR streamer (1),
+.BR atitv (1),
+.BR vidtomem (1)
+.SH COPYRIGHT
+Copyright \(co 2001 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>, 14-Apr-01
diff --git a/driver/xscreensaver-getimage.c b/driver/xscreensaver-getimage.c
new file mode 100644
index 0000000..092540d
--- /dev/null
+++ b/driver/xscreensaver-getimage.c
@@ -0,0 +1,2000 @@
+/* xscreensaver, Copyright (c) 2001-2018 by 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-getimage -- helper program that puts a random image
+ onto the given window or pixmap. That image is either a screen-grab,
+ a file loaded from disk, or a frame grabbed from the system's video
+ input.
+ */
+
+#include "utils.h"
+
+#include <X11/Intrinsic.h>
+#include <ctype.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#ifdef HAVE_SYS_WAIT_H
+# include <sys/wait.h> /* for waitpid() and associated macros */
+#endif
+
+#ifdef HAVE_XMU
+# ifndef VMS
+# include <X11/Xmu/Error.h>
+# else /* VMS */
+# include <Xmu/Error.h>
+# endif
+#else
+# include "xmu.h"
+#endif
+
+#include "yarandom.h"
+#include "grabscreen.h"
+#include "resources.h"
+#include "colorbars.h"
+#include "visual.h"
+#include "prefs.h"
+#include "version.h"
+#include "vroot.h"
+
+#ifndef _XSCREENSAVER_VROOT_H_
+# error Error! You have an old version of vroot.h! Check -I args.
+#endif /* _XSCREENSAVER_VROOT_H_ */
+
+#ifdef HAVE_GDK_PIXBUF
+# undef HAVE_JPEGLIB
+# ifdef HAVE_GTK2
+# include <gdk-pixbuf-xlib/gdk-pixbuf-xlib.h>
+# else /* !HAVE_GTK2 */
+# include <gdk-pixbuf/gdk-pixbuf-xlib.h>
+# endif /* !HAVE_GTK2 */
+#endif /* HAVE_GDK_PIXBUF */
+
+#ifdef HAVE_JPEGLIB
+# undef HAVE_GDK_PIXBUF
+# include <jpeglib.h>
+#endif
+
+
+#ifdef __APPLE__
+ /* On MacOS under X11, the usual X11 mechanism of getting a screen shot
+ doesn't work, and we need to use an external program. This is only
+ used when running under X11 on MacOS. If it's a Cocoa build, this
+ path is not taken, and OSX/grabclient-osx.m is used instead.
+ */
+# define USE_EXTERNAL_SCREEN_GRABBER
+#endif
+
+
+#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 "../driver/XScreenSaver_ad.h"
+ 0
+};
+
+
+
+char *progname = 0;
+char *progclass = "XScreenSaver";
+XrmDatabase db;
+XtAppContext app;
+
+extern void grabscreen_verbose (void);
+
+typedef enum {
+ GRAB_DESK, GRAB_VIDEO, GRAB_FILE, GRAB_BARS
+} grab_type;
+
+
+#define GETIMAGE_VIDEO_PROGRAM "xscreensaver-getimage-video"
+#define GETIMAGE_FILE_PROGRAM "xscreensaver-getimage-file"
+#define GETIMAGE_SCREEN_PROGRAM "xscreensaver-getimage-desktop"
+
+extern const char *blurb (void);
+
+const char *
+blurb (void)
+{
+ return progname;
+}
+
+
+static int
+x_ehandler (Display *dpy, XErrorEvent *error)
+{
+ if (error->error_code == BadWindow || error->error_code == BadDrawable)
+ {
+ fprintf (stderr, "%s: target %s 0x%lx unexpectedly deleted\n", progname,
+ (error->error_code == BadWindow ? "window" : "pixmap"),
+ (unsigned long) error->resourceid);
+ }
+ else
+ {
+ fprintf (stderr, "\nX error in %s:\n", progname);
+ XmuPrintDefaultErrorMessage (dpy, error, stderr);
+ }
+ exit (-1);
+ return 0;
+}
+
+
+static Bool error_handler_hit_p = False;
+
+static int
+ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
+{
+ error_handler_hit_p = True;
+ return 0;
+}
+
+#ifndef USE_EXTERNAL_SCREEN_GRABBER
+static int
+ignore_badmatch_ehandler (Display *dpy, XErrorEvent *error)
+{
+ if (error->error_code == BadMatch)
+ return ignore_all_errors_ehandler (dpy, error);
+ else
+ return x_ehandler (dpy, error);
+}
+#endif /* ! USE_EXTERNAL_SCREEN_GRABBER */
+
+
+/* Returns True if the given Drawable is a Window; False if it's a Pixmap.
+ */
+static Bool
+drawable_window_p (Display *dpy, Drawable d)
+{
+ XErrorHandler old_handler;
+ XWindowAttributes xgwa;
+
+ XSync (dpy, False);
+ old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
+ error_handler_hit_p = False;
+ XGetWindowAttributes (dpy, d, &xgwa);
+ XSync (dpy, False);
+ XSetErrorHandler (old_handler);
+ XSync (dpy, False);
+
+ if (!error_handler_hit_p)
+ return True; /* It's a Window. */
+ else
+ return False; /* It's a Pixmap, or an invalid ID. */
+}
+
+
+/* Returns true if the window is the root window, or a virtual root window,
+ but *not* the xscreensaver window. That is, if it's a "real" desktop
+ root window of some kind.
+ */
+static Bool
+root_window_p (Screen *screen, Window window)
+{
+ Display *dpy = DisplayOfScreen (screen);
+ Atom type;
+ int format;
+ unsigned long nitems, bytesafter;
+ unsigned char *version;
+
+ if (window != RootWindowOfScreen (screen))
+ return False;
+
+ if (XGetWindowProperty (dpy, window,
+ XInternAtom (dpy, "_SCREENSAVER_VERSION", False),
+ 0, 1, False, XA_STRING,
+ &type, &format, &nitems, &bytesafter,
+ &version)
+ == Success
+ && type != None)
+ return False;
+
+ return True;
+}
+
+
+/* Clear the window or pixmap to black, or its background color.
+ */
+static void
+clear_drawable (Screen *screen, Drawable drawable)
+{
+ Display *dpy = DisplayOfScreen (screen);
+ XGCValues gcv;
+ GC gc;
+ Window root;
+ int x, y;
+ unsigned int w, h, bw, d;
+ XGetGeometry (dpy, drawable, &root, &x, &y, &w, &h, &bw, &d);
+
+ /* The window might have no-op background of None, so to clear it,
+ draw a black rectangle first, then do XClearWindow (in case the
+ actual background color is non-black...) */
+
+ /* #### really we should allocate "black" instead, but I'm lazy... */
+ gcv.foreground = BlackPixelOfScreen (screen);
+ gc = XCreateGC (dpy, drawable, GCForeground, &gcv);
+ XFillRectangle (dpy, drawable, gc, 0, 0, w, h);
+ XFreeGC (dpy, gc);
+ if (drawable_window_p (dpy, drawable))
+ XClearWindow (dpy, (Window) drawable);
+ XFlush (dpy);
+}
+
+
+/* Figure out what kind of scaling/positioning we ought to do to display
+ a src-sized image in a dest-sized window/pixmap. Returns the width
+ and height to which the image should be scaled, and the position where
+ it should be displayed to center it.
+ */
+static void
+compute_image_scaling (int src_w, int src_h,
+ int dest_w, int dest_h,
+ Bool verbose_p,
+ int *scaled_from_x_ret, int *scaled_from_y_ret,
+ int *scaled_to_x_ret, int *scaled_to_y_ret,
+ int *scaled_w_ret, int *scaled_h_ret)
+{
+ int srcx, srcy, destx, desty;
+
+ Bool exact_fit_p = ((src_w == dest_w && src_h <= dest_h) ||
+ (src_h == dest_h && src_w <= dest_w));
+
+ if (!exact_fit_p) /* scale the image up or down */
+ {
+ float rw = (float) dest_w / src_w;
+ float rh = (float) dest_h / src_h;
+ float r = (rw < rh ? rw : rh);
+ int tw, th, pct;
+
+ /* If the window is a goofy aspect ratio, take a middle slice of
+ the image instead. */
+ if (dest_w > dest_h * 5 || dest_h > dest_w * 5)
+ {
+ double r2 = (dest_w > dest_h
+ ? dest_w / (double) dest_h
+ : dest_h / (double) dest_w);
+ r *= r2;
+ if (verbose_p)
+ fprintf (stderr, "%s: weird aspect: scaling by %.1f\n",
+ progname, r2);
+ }
+
+ tw = src_w * r;
+ th = src_h * r;
+ pct = (r * 100);
+
+#if 0
+ /* this optimization breaks things */
+ if (pct < 95 || pct > 105) /* don't scale if it's close */
+#endif
+ {
+ if (verbose_p)
+ fprintf (stderr, "%s: scaling image by %d%% (%dx%d -> %dx%d)\n",
+ progname, pct, src_w, src_h, tw, th);
+ src_w = tw;
+ src_h = th;
+ }
+ }
+
+ /* Center the image on the window/pixmap. */
+ srcx = 0;
+ srcy = 0;
+ destx = (dest_w - src_w) / 2;
+ desty = (dest_h - src_h) / 2;
+ if (destx < 0) srcx = -destx, destx = 0;
+ if (desty < 0) srcy = -desty, desty = 0;
+
+ /* if (dest_w < src_w) src_w = dest_w;
+ if (dest_h < src_h) src_h = dest_h; */
+
+ *scaled_w_ret = src_w;
+ *scaled_h_ret = src_h;
+ *scaled_from_x_ret = srcx;
+ *scaled_from_y_ret = srcy;
+ *scaled_to_x_ret = destx;
+ *scaled_to_y_ret = desty;
+
+ if (verbose_p)
+ fprintf (stderr, "%s: displaying %dx%d+%d+%d image at %d,%d in %dx%d.\n",
+ progname, src_w, src_h, srcx, srcy, destx, desty, dest_w, dest_h);
+}
+
+
+static void
+colorbars (Screen *screen, Visual *visual, Drawable drawable, Colormap cmap)
+{
+ Pixmap mask = 0;
+ unsigned long *pixels; /* ignored - unfreed */
+ int npixels;
+ Pixmap logo = xscreensaver_logo (screen, visual, drawable, cmap,
+ BlackPixelOfScreen (screen),
+ &pixels, &npixels, &mask, True);
+ draw_colorbars (screen, visual, drawable, cmap, 0, 0, 0, 0, logo, mask);
+ XFreePixmap (DisplayOfScreen (screen), logo);
+ XFreePixmap (DisplayOfScreen (screen), mask);
+}
+
+
+/* Scales an XImage, modifying it in place.
+ This doesn't do dithering or smoothing, so it might have artifacts.
+ If out of memory, returns False, and the XImage will have been
+ destroyed and freed.
+ */
+#if !defined(USE_EXTERNAL_SCREEN_GRABBER) || defined(HAVE_JPEGLIB)
+static Bool
+scale_ximage (Screen *screen, Visual *visual,
+ XImage *ximage, int new_width, int new_height)
+{
+ Display *dpy = DisplayOfScreen (screen);
+ int depth = visual_depth (screen, visual);
+ int x, y;
+ double xscale, yscale;
+
+ XImage *ximage2 = XCreateImage (dpy, visual, depth,
+ ZPixmap, 0, 0,
+ new_width, new_height, 8, 0);
+ ximage2->data = (char *) calloc (ximage2->height, ximage2->bytes_per_line);
+
+ if (!ximage2->data)
+ {
+ fprintf (stderr, "%s: out of memory scaling %dx%d image to %dx%d\n",
+ progname,
+ ximage->width, ximage->height,
+ ximage2->width, ximage2->height);
+ if (ximage->data) free (ximage->data);
+ if (ximage2->data) free (ximage2->data);
+ ximage->data = 0;
+ ximage2->data = 0;
+ XDestroyImage (ximage);
+ XDestroyImage (ximage2);
+ return False;
+ }
+
+ /* Brute force scaling... */
+ xscale = (double) ximage->width / ximage2->width;
+ yscale = (double) ximage->height / ximage2->height;
+ for (y = 0; y < ximage2->height; y++)
+ for (x = 0; x < ximage2->width; x++)
+ XPutPixel (ximage2, x, y,
+ XGetPixel (ximage, x * xscale, y * yscale));
+
+ free (ximage->data);
+ ximage->data = 0;
+
+ (*ximage) = (*ximage2);
+
+ ximage2->data = 0;
+ XDestroyImage (ximage2);
+
+ return True;
+}
+#endif /* !USE_EXTERNAL_SCREEN_GRABBER || HAVE_JPEGLIB */
+
+
+#ifdef HAVE_GDK_PIXBUF
+
+/* Reads the given image file and renders it on the Drawable, using GDK.
+ Returns False if it fails.
+ */
+static Bool
+read_file_gdk (Screen *screen, Window window, Drawable drawable,
+ const char *filename, Bool verbose_p,
+ XRectangle *geom_ret)
+{
+ GdkPixbuf *pb;
+ Display *dpy = DisplayOfScreen (screen);
+ unsigned int win_width, win_height, win_depth;
+# ifdef HAVE_GTK2
+ GError *gerr = 0;
+# endif /* HAVE_GTK2 */
+
+ /* Find the size of the Drawable. */
+ {
+ Window root;
+ int x, y;
+ unsigned int bw;
+ XGetGeometry (dpy, drawable,
+ &root, &x, &y, &win_width, &win_height, &bw, &win_depth);
+ }
+
+ gdk_pixbuf_xlib_init_with_depth (dpy, screen_number (screen), win_depth);
+# ifdef HAVE_GTK2
+# if !GLIB_CHECK_VERSION(2, 36 ,0)
+ g_type_init();
+# endif
+# else /* !HAVE_GTK2 */
+ xlib_rgb_init (dpy, screen);
+# endif /* !HAVE_GTK2 */
+
+ pb = gdk_pixbuf_new_from_file (filename
+# ifdef HAVE_GTK2
+ , &gerr
+# endif /* HAVE_GTK2 */
+ );
+
+ if (!pb)
+ {
+ fprintf (stderr, "%s: unable to load \"%s\"\n", progname, filename);
+# ifdef HAVE_GTK2
+ if (gerr && gerr->message && *gerr->message)
+ fprintf (stderr, "%s: reason: %s\n", progname, gerr->message);
+# endif /* HAVE_GTK2 */
+ return False;
+ }
+ else
+ {
+ int w = gdk_pixbuf_get_width (pb);
+ int h = gdk_pixbuf_get_height (pb);
+ int srcx, srcy, destx, desty, w2, h2;
+ Bool bg_p = False;
+
+# ifdef HAVE_GDK_PIXBUF_APPLY_EMBEDDED_ORIENTATION
+ {
+ int ow = w, oh = h;
+ GdkPixbuf *opb = pb;
+ pb = gdk_pixbuf_apply_embedded_orientation (opb);
+ g_object_unref (opb);
+ w = gdk_pixbuf_get_width (pb);
+ h = gdk_pixbuf_get_height (pb);
+ if (verbose_p && (w != ow || h != oh))
+ fprintf (stderr, "%s: rotated %dx%d to %dx%d\n",
+ progname, ow, oh, w, h);
+ }
+# endif
+
+ compute_image_scaling (w, h, win_width, win_height, verbose_p,
+ &srcx, &srcy, &destx, &desty, &w2, &h2);
+ if (w != w2 || h != h2)
+ {
+ GdkPixbuf *pb2 = gdk_pixbuf_scale_simple (pb, w2, h2,
+ GDK_INTERP_BILINEAR);
+ if (pb2)
+ {
+ g_object_unref (pb);
+ pb = pb2;
+ w = w2;
+ h = h2;
+ }
+ else
+ fprintf (stderr, "%s: out of memory when scaling?\n", progname);
+ }
+
+ /* If we're rendering onto the root window (and it's not the
+ xscreensaver pseudo-root) then put the image in the window's
+ background. Otherwise, just paint the image onto the window.
+ */
+ bg_p = (window == drawable && root_window_p (screen, window));
+
+ if (bg_p)
+ {
+ XGCValues gcv;
+ GC gc;
+ drawable = XCreatePixmap (dpy, window,
+ win_width, win_height, win_depth);
+ gcv.foreground = BlackPixelOfScreen (screen);
+ gc = XCreateGC (dpy, drawable, GCForeground, &gcv);
+ XFillRectangle (dpy, drawable, gc, 0, 0, win_width, win_height);
+ XFreeGC (dpy, gc);
+ }
+ else
+ clear_drawable (screen, drawable);
+
+ /* #### Note that this always uses the default colormap! Morons!
+ Owen says that in Gnome 2.0, I should try using
+ gdk_pixbuf_render_pixmap_and_mask_for_colormap() instead.
+ But I haven't tried.
+ */
+ if (srcx > 0) w -= srcx;
+ if (srcy > 0) h -= srcy;
+ gdk_pixbuf_xlib_render_to_drawable_alpha (pb, drawable,
+ srcx, srcy, destx, desty,
+ w, h,
+ GDK_PIXBUF_ALPHA_FULL, 127,
+ XLIB_RGB_DITHER_NORMAL,
+ 0, 0);
+ if (bg_p)
+ {
+ XSetWindowBackgroundPixmap (dpy, window, drawable);
+ XClearWindow (dpy, window);
+ }
+
+ if (geom_ret)
+ {
+ geom_ret->x = destx;
+ geom_ret->y = desty;
+ geom_ret->width = w;
+ geom_ret->height = h;
+ }
+ }
+
+ XSync (dpy, False);
+ return True;
+}
+
+#endif /* HAVE_GDK_PIXBUF */
+
+
+
+#ifdef HAVE_JPEGLIB
+
+/* Allocates a colormap that makes a PseudoColor or DirectColor
+ visual behave like a TrueColor visual of the same depth.
+
+ #### Duplicated in utils/grabscreen.c
+ */
+static void
+allocate_cubic_colormap (Screen *screen, Visual *visual, Colormap cmap,
+ Bool verbose_p)
+{
+ Display *dpy = DisplayOfScreen (screen);
+ int nr, ng, nb, cells;
+ int r, g, b;
+ int depth;
+ XColor colors[4097];
+ int i;
+
+ depth = visual_depth (screen, visual);
+
+ switch (depth)
+ {
+ case 8: nr = 3; ng = 3; nb = 2; cells = 256; break;
+ case 12: nr = 4; ng = 4; nb = 4; cells = 4096; break;
+ default: abort(); break;
+ }
+
+ memset(colors, 0, sizeof(colors));
+ for (r = 0; r < (1 << nr); r++)
+ for (g = 0; g < (1 << ng); g++)
+ for (b = 0; b < (1 << nb); b++)
+ {
+ i = (r | (g << nr) | (b << (nr + ng)));
+ colors[i].pixel = i;
+ colors[i].flags = DoRed|DoGreen|DoBlue;
+ if (depth == 8)
+ {
+ colors[i].red = ((r << 13) | (r << 10) | (r << 7) |
+ (r << 4) | (r << 1));
+ colors[i].green = ((g << 13) | (g << 10) | (g << 7) |
+ (g << 4) | (g << 1));
+ colors[i].blue = ((b << 14) | (b << 12) | (b << 10) |
+ (b << 8) | (b << 6) | (b << 4) |
+ (b << 2) | b);
+ }
+ else
+ {
+ colors[i].red = (r << 12) | (r << 8) | (r << 4) | r;
+ colors[i].green = (g << 12) | (g << 8) | (g << 4) | g;
+ colors[i].blue = (b << 12) | (b << 8) | (b << 4) | b;
+ }
+ }
+
+ {
+ int j;
+ int allocated = 0;
+ int interleave = cells / 8; /* skip around, rather than allocating in
+ order, so that we get better coverage if
+ we can't allocated all of them. */
+ for (j = 0; j < interleave; j++)
+ for (i = 0; i < cells; i += interleave)
+ if (XAllocColor (dpy, cmap, &colors[i + j]))
+ allocated++;
+
+ if (verbose_p)
+ fprintf (stderr, "%s: allocated %d of %d colors for cubic map\n",
+ progname, allocated, cells);
+ }
+}
+
+/* Find the pixel index that is closest to the given color
+ (using linear distance in RGB space -- which is far from the best way.)
+
+ #### Duplicated in utils/grabscreen.c
+ */
+static unsigned long
+find_closest_pixel (XColor *colors, int ncolors,
+ unsigned long r, unsigned long g, unsigned long b)
+{
+ unsigned long distance = ~0;
+ int i, found = 0;
+
+ if (ncolors == 0)
+ abort();
+ for (i = 0; i < ncolors; i++)
+ {
+ unsigned long d;
+ int rd, gd, bd;
+
+ rd = r - colors[i].red;
+ gd = g - colors[i].green;
+ bd = b - colors[i].blue;
+ if (rd < 0) rd = -rd;
+ if (gd < 0) gd = -gd;
+ if (bd < 0) bd = -bd;
+ d = (rd << 1) + (gd << 2) + bd;
+
+ if (d < distance)
+ {
+ distance = d;
+ found = i;
+ if (distance == 0)
+ break;
+ }
+ }
+
+ return found;
+}
+
+
+/* Given an XImage with 8-bit or 12-bit RGB data, convert it to be
+ displayable with the given X colormap. The farther from a perfect
+ color cube the contents of the colormap are, the lossier the
+ transformation will be. No dithering is done.
+
+ #### Duplicated in utils/grabscreen.c
+ */
+static void
+remap_image (Screen *screen, Colormap cmap, XImage *image, Bool verbose_p)
+{
+ Display *dpy = DisplayOfScreen (screen);
+ unsigned long map[4097];
+ int x, y, i;
+ int cells;
+ XColor colors[4097];
+
+ if (image->depth == 8)
+ cells = 256;
+ else if (image->depth == 12)
+ cells = 4096;
+ else
+ abort();
+
+ memset(map, -1, sizeof(*map));
+ memset(colors, -1, sizeof(*colors));
+
+ for (i = 0; i < cells; i++)
+ colors[i].pixel = i;
+ XQueryColors (dpy, cmap, colors, cells);
+
+ if (verbose_p)
+ fprintf(stderr, "%s: building color cube for %d bit image\n",
+ progname, image->depth);
+
+ for (i = 0; i < cells; i++)
+ {
+ unsigned short r, g, b;
+
+ if (cells == 256)
+ {
+ /* "RRR GGG BB" In an 8 bit map. Convert that to
+ "RRR RRR RR" "GGG GGG GG" "BB BB BB BB" to give
+ an even spread. */
+ r = (i & 0x07);
+ g = (i & 0x38) >> 3;
+ b = (i & 0xC0) >> 6;
+
+ r = ((r << 13) | (r << 10) | (r << 7) | (r << 4) | (r << 1));
+ g = ((g << 13) | (g << 10) | (g << 7) | (g << 4) | (g << 1));
+ b = ((b << 14) | (b << 12) | (b << 10) | (b << 8) |
+ (b << 6) | (b << 4) | (b << 2) | b);
+ }
+ else
+ {
+ /* "RRRR GGGG BBBB" In a 12 bit map. Convert that to
+ "RRRR RRRR" "GGGG GGGG" "BBBB BBBB" to give an even
+ spread. */
+ r = (i & 0x00F);
+ g = (i & 0x0F0) >> 4;
+ b = (i & 0xF00) >> 8;
+
+ r = (r << 12) | (r << 8) | (r << 4) | r;
+ g = (g << 12) | (g << 8) | (g << 4) | g;
+ b = (b << 12) | (b << 8) | (b << 4) | b;
+ }
+
+ map[i] = find_closest_pixel (colors, cells, r, g, b);
+ }
+
+ if (verbose_p)
+ fprintf(stderr, "%s: remapping colors in %d bit image\n",
+ progname, image->depth);
+
+ for (y = 0; y < image->height; y++)
+ for (x = 0; x < image->width; x++)
+ {
+ unsigned long pixel = XGetPixel(image, x, y);
+ if (pixel >= cells) abort();
+ XPutPixel(image, x, y, map[pixel]);
+ }
+}
+
+
+/* If the file has a PPM (P6) on it, read it and return an XImage.
+ Otherwise, rewind the fd back to the beginning, and return 0.
+ */
+static XImage *
+maybe_read_ppm (Screen *screen, Visual *visual,
+ const char *filename, FILE *in, Bool verbose_p)
+{
+ Display *dpy = DisplayOfScreen (screen);
+ int depth = visual_depth (screen, visual);
+ struct stat st;
+ char *buf = 0;
+ int bufsiz = 0;
+ char *s, dummy;
+ int i, j;
+ int x, y, w, h, maxval;
+ XImage *ximage = 0;
+
+ if (fstat (fileno (in), &st))
+ goto FAIL;
+
+ bufsiz = st.st_size;
+ buf = (char *) malloc (bufsiz + 1);
+ if (!buf)
+ {
+ fprintf (stderr, "%s: out of memory loading %d byte PPM file %s\n",
+ progname, bufsiz, filename);
+ goto FAIL;
+ }
+
+ if (! (s = fgets (buf, bufsiz, in))) /* line 1 */
+ goto FAIL;
+
+ if (!strncmp (buf, "\107\111", 2))
+ {
+ fprintf (stderr, "%s: %s: sorry, GIF files not supported"
+ " when compiled with JPEGlib instead of GDK_Pixbuf.\n",
+ progname, filename);
+ goto FAIL;
+ }
+ else if (!strncmp (buf, "\211\120", 2))
+ {
+ fprintf (stderr, "%s: %s: sorry, PNG files not supported"
+ " when compiled with JPEGlib instead of GDK_Pixbuf.\n",
+ progname, filename);
+ goto FAIL;
+ }
+
+ if (strncmp (s, "P6", 2))
+ goto FAIL;
+
+ if (! (s = fgets (buf, bufsiz, in))) /* line 2 */
+ goto FAIL;
+ if (2 != sscanf (s, " %d %d %c", &w, &h, &dummy))
+ {
+ fprintf (stderr, "%s: %s: invalid PPM (line 2)\n", progname, filename);
+ goto FAIL;
+ }
+
+ if (! (s = fgets (buf, bufsiz, in))) /* line 3 */
+ goto FAIL;
+ if (1 != sscanf (s, " %d %c", &maxval, &dummy))
+ {
+ fprintf (stderr, "%s: %s: invalid PPM (line 3)\n", progname, filename);
+ goto FAIL;
+ }
+ if (maxval != 255)
+ {
+ fprintf (stderr, "%s: %s: unparsable PPM: maxval is %d\n",
+ progname, filename, maxval);
+ goto FAIL;
+ }
+
+ ximage = XCreateImage (dpy, visual, depth, ZPixmap, 0, 0,
+ w, h, 8, 0);
+ if (ximage)
+ ximage->data = (char *) calloc (ximage->height, ximage->bytes_per_line);
+ if (!ximage || !ximage->data)
+ {
+ fprintf (stderr, "%s: out of memory loading %dx%d PPM file %s\n",
+ progname, ximage->width, ximage->height, filename);
+ goto FAIL;
+ }
+
+ s = buf;
+ j = bufsiz;
+ while ((i = fread (s, 1, j, in)) > 0)
+ s += i, j -= i;
+
+ i = 0;
+ for (y = 0; y < ximage->height; y++)
+ for (x = 0; x < ximage->width; x++)
+ {
+ unsigned char r = buf[i++];
+ unsigned char g = buf[i++];
+ unsigned char b = buf[i++];
+ unsigned long pixel;
+
+ if (depth > 16)
+ pixel = (r << 16) | (g << 8) | b;
+ else if (depth == 8)
+ pixel = ((r >> 5) | ((g >> 5) << 3) | ((b >> 6) << 6));
+ else if (depth == 12)
+ pixel = ((r >> 4) | ((g >> 4) << 4) | ((b >> 4) << 8));
+ else if (depth == 16 || depth == 15)
+ pixel = (((r >> 3) << 10) | ((g >> 3) << 5) | ((b >> 3)));
+ else
+ abort();
+
+ XPutPixel (ximage, x, y, pixel);
+ }
+
+ free (buf);
+ return ximage;
+
+ FAIL:
+ if (buf) free (buf);
+ if (ximage && ximage->data)
+ {
+ free (ximage->data);
+ ximage->data = 0;
+ }
+ if (ximage) XDestroyImage (ximage);
+ fseek (in, 0, SEEK_SET);
+ return 0;
+}
+
+
+typedef struct {
+ struct jpeg_error_mgr pub; /* this is what passes for subclassing in C */
+ const char *filename;
+ Screen *screen;
+ Visual *visual;
+ Drawable drawable;
+ Colormap cmap;
+} getimg_jpg_error_mgr;
+
+
+static void
+jpg_output_message (j_common_ptr cinfo)
+{
+ getimg_jpg_error_mgr *err = (getimg_jpg_error_mgr *) cinfo->err;
+ char buf[JMSG_LENGTH_MAX];
+ cinfo->err->format_message (cinfo, buf);
+ fprintf (stderr, "%s: %s: %s\n", progname, err->filename, buf);
+}
+
+
+static void
+jpg_error_exit (j_common_ptr cinfo)
+{
+ getimg_jpg_error_mgr *err = (getimg_jpg_error_mgr *) cinfo->err;
+ cinfo->err->output_message (cinfo);
+ colorbars (err->screen, err->visual, err->drawable, err->cmap);
+ XSync (DisplayOfScreen (err->screen), False);
+ exit (1);
+}
+
+
+/* Reads a JPEG file, returns an RGB XImage of it.
+ */
+static XImage *
+read_jpeg_ximage (Screen *screen, Visual *visual, Drawable drawable,
+ Colormap cmap, const char *filename, Bool verbose_p)
+{
+ Display *dpy = DisplayOfScreen (screen);
+ int depth = visual_depth (screen, visual);
+
+ FILE *in = 0;
+ XImage *ximage = 0;
+ struct jpeg_decompress_struct cinfo;
+ getimg_jpg_error_mgr jerr;
+ JSAMPARRAY scanbuf = 0;
+ int y;
+
+ jerr.filename = filename;
+ jerr.screen = screen;
+ jerr.visual = visual;
+ jerr.drawable = drawable;
+ jerr.cmap = cmap;
+
+ if (! (depth >= 15 || depth == 12 || depth == 8))
+ {
+ fprintf (stderr, "%s: unsupported depth: %d\n", progname, depth);
+ goto FAIL;
+ }
+
+ in = fopen (filename, "rb");
+ if (!in)
+ {
+ fprintf (stderr, "%s: %s: unreadable\n", progname, filename);
+ goto FAIL;
+ }
+
+ /* Check to see if it's a PPM, and if so, read that instead of using
+ the JPEG library. Yeah, this is all modular and stuff.
+ */
+ if ((ximage = maybe_read_ppm (screen, visual, filename, in, verbose_p)))
+ {
+ fclose (in);
+ return ximage;
+ }
+
+ cinfo.err = jpeg_std_error (&jerr.pub);
+ jerr.pub.output_message = jpg_output_message;
+ jerr.pub.error_exit = jpg_error_exit;
+
+ jpeg_create_decompress (&cinfo);
+ jpeg_stdio_src (&cinfo, in);
+ jpeg_read_header (&cinfo, TRUE);
+
+ /* set some decode parameters */
+ cinfo.out_color_space = JCS_RGB;
+ cinfo.quantize_colors = FALSE;
+
+ jpeg_start_decompress (&cinfo);
+
+ ximage = XCreateImage (dpy, visual, depth, ZPixmap, 0, 0,
+ cinfo.output_width, cinfo.output_height,
+ 8, 0);
+ if (ximage)
+ ximage->data = (char *) calloc (ximage->height, ximage->bytes_per_line);
+
+ if (ximage && ximage->data)
+ scanbuf = (*cinfo.mem->alloc_sarray) ((j_common_ptr) &cinfo, JPOOL_IMAGE,
+ cinfo.rec_outbuf_height *
+ cinfo.output_width *
+ cinfo.output_components,
+ 1);
+ if (!ximage || !ximage->data || !scanbuf)
+ {
+ fprintf (stderr, "%s: out of memory loading %dx%d file %s\n",
+ progname, ximage->width, ximage->height, filename);
+ goto FAIL;
+ }
+
+ y = 0;
+ while (cinfo.output_scanline < cinfo.output_height)
+ {
+ int n = jpeg_read_scanlines (&cinfo, scanbuf, 1);
+ int i;
+ for (i = 0; i < n; i++)
+ {
+ int x;
+ for (x = 0; x < ximage->width; x++)
+ {
+ int j = x * cinfo.output_components;
+ unsigned char r = scanbuf[i][j];
+ unsigned char g = scanbuf[i][j+1];
+ unsigned char b = scanbuf[i][j+2];
+ unsigned long pixel;
+
+ if (depth > 16)
+ pixel = (r << 16) | (g << 8) | b;
+ else if (depth == 8)
+ pixel = ((r >> 5) | ((g >> 5) << 3) | ((b >> 6) << 6));
+ else if (depth == 12)
+ pixel = ((r >> 4) | ((g >> 4) << 4) | ((b >> 4) << 8));
+ else if (depth == 15)
+ /* Gah! I don't understand why these are in the other
+ order. */
+ pixel = (((r >> 3) << 10) | ((g >> 3) << 5) | ((b >> 3)));
+ else if (depth == 16)
+ pixel = (((r >> 3) << 11) | ((g >> 2) << 5) | ((b >> 3)));
+ else
+ abort();
+
+ XPutPixel (ximage, x, y, pixel);
+ }
+ y++;
+ }
+ }
+
+ if (cinfo.output_scanline < cinfo.output_height)
+ /* don't goto FAIL -- we might have viewable partial data. */
+ jpeg_abort_decompress (&cinfo);
+ else
+ jpeg_finish_decompress (&cinfo);
+
+ jpeg_destroy_decompress (&cinfo);
+ fclose (in);
+ in = 0;
+
+ return ximage;
+
+ FAIL:
+ if (in) fclose (in);
+ if (ximage && ximage->data)
+ {
+ free (ximage->data);
+ ximage->data = 0;
+ }
+ if (ximage) XDestroyImage (ximage);
+ if (scanbuf) free (scanbuf);
+ return 0;
+}
+
+
+/* Reads the given image file and renders it on the Drawable, using JPEG lib.
+ Returns False if it fails.
+ */
+static Bool
+read_file_jpeglib (Screen *screen, Window window, Drawable drawable,
+ const char *filename, Bool verbose_p,
+ XRectangle *geom_ret)
+{
+ Display *dpy = DisplayOfScreen (screen);
+ XImage *ximage;
+ Visual *visual;
+ int class, depth;
+ Colormap cmap;
+ unsigned int win_width, win_height, win_depth;
+ int srcx, srcy, destx, desty, w2, h2;
+
+ /* Find the size of the Drawable, and the Visual/Colormap of the Window. */
+ {
+ Window root;
+ int x, y;
+ unsigned int bw;
+ XWindowAttributes xgwa;
+ XGetWindowAttributes (dpy, window, &xgwa);
+ visual = xgwa.visual;
+ cmap = xgwa.colormap;
+ XGetGeometry (dpy, drawable,
+ &root, &x, &y, &win_width, &win_height, &bw, &win_depth);
+ }
+
+ /* Make sure we're not on some weirdo visual...
+ */
+ class = visual_class (screen, visual);
+ depth = visual_depth (screen, visual);
+ if ((class == PseudoColor || class == DirectColor) &&
+ (depth != 8 && depth != 12))
+ {
+ fprintf (stderr, "%s: Pseudo/DirectColor depth %d unsupported\n",
+ progname, depth);
+ return False;
+ }
+
+ /* Read the file...
+ */
+ ximage = read_jpeg_ximage (screen, visual, drawable, cmap,
+ filename, verbose_p);
+ if (!ximage) return False;
+
+ /* Scale it, if necessary...
+ */
+ compute_image_scaling (ximage->width, ximage->height,
+ win_width, win_height, verbose_p,
+ &srcx, &srcy, &destx, &desty, &w2, &h2);
+ if (ximage->width != w2 || ximage->height != h2)
+ if (! scale_ximage (screen, visual, ximage, w2, h2))
+ return False;
+
+ /* Allocate a colormap, if we need to...
+ */
+ if (class == PseudoColor || class == DirectColor)
+ {
+ allocate_cubic_colormap (screen, visual, cmap, verbose_p);
+ remap_image (screen, cmap, ximage, verbose_p);
+ }
+
+ /* Finally, put the resized image on the window.
+ */
+ {
+ GC gc;
+ XGCValues gcv;
+
+ /* If we're rendering onto the root window (and it's not the xscreensaver
+ pseudo-root) then put the image in the window's background. Otherwise,
+ just paint the image onto the window.
+ */
+ if (window == drawable && root_window_p (screen, window))
+ {
+ Pixmap bg = XCreatePixmap (dpy, window,
+ win_width, win_height, win_depth);
+ gcv.foreground = BlackPixelOfScreen (screen);
+ gc = XCreateGC (dpy, drawable, GCForeground, &gcv);
+ XFillRectangle (dpy, bg, gc, 0, 0, win_width, win_height);
+ XPutImage (dpy, bg, gc, ximage,
+ srcx, srcy, destx, desty, ximage->width, ximage->height);
+ XSetWindowBackgroundPixmap (dpy, window, bg);
+ XClearWindow (dpy, window);
+ }
+ else
+ {
+ gc = XCreateGC (dpy, drawable, 0, &gcv);
+ clear_drawable (screen, drawable);
+ XPutImage (dpy, drawable, gc, ximage,
+ srcx, srcy, destx, desty, ximage->width, ximage->height);
+ }
+
+ XFreeGC (dpy, gc);
+ }
+
+ if (geom_ret)
+ {
+ geom_ret->x = destx;
+ geom_ret->y = desty;
+ geom_ret->width = ximage->width;
+ geom_ret->height = ximage->height;
+ }
+
+ free (ximage->data);
+ ximage->data = 0;
+ XDestroyImage (ximage);
+ XSync (dpy, False);
+ return True;
+}
+
+#endif /* HAVE_JPEGLIB */
+
+
+/* Reads the given image file and renders it on the Drawable.
+ Returns False if it fails.
+ */
+static Bool
+display_file (Screen *screen, Window window, Drawable drawable,
+ const char *filename, Bool verbose_p,
+ XRectangle *geom_ret)
+{
+ if (verbose_p)
+ fprintf (stderr, "%s: loading \"%s\"\n", progname, filename);
+
+# if defined(HAVE_GDK_PIXBUF)
+ if (read_file_gdk (screen, window, drawable, filename, verbose_p, geom_ret))
+ return True;
+# elif defined(HAVE_JPEGLIB)
+ if (read_file_jpeglib (screen, window, drawable, filename, verbose_p,
+ geom_ret))
+ return True;
+# else /* !(HAVE_GDK_PIXBUF || HAVE_JPEGLIB) */
+ /* shouldn't get here if we have no image-loading methods available. */
+ abort();
+# endif /* !(HAVE_GDK_PIXBUF || HAVE_JPEGLIB) */
+
+ return False;
+}
+
+
+/* Invokes a sub-process and returns its output (presumably, a file to
+ load.) Free the string when done. 'grab_type' controls which program
+ to run. Returned pathname may be relative to 'directory', or absolute.
+ */
+static char *
+get_filename_1 (Screen *screen, const char *directory, grab_type type,
+ Bool verbose_p)
+{
+ Display *dpy = DisplayOfScreen (screen);
+ pid_t forked;
+ int fds [2];
+ int in, out;
+ char buf[10240];
+ char *av[20];
+ int ac = 0;
+
+ switch (type)
+ {
+ case GRAB_FILE:
+ av[ac++] = GETIMAGE_FILE_PROGRAM;
+ if (verbose_p)
+ av[ac++] = "--verbose";
+ av[ac++] = "--name";
+ av[ac++] = (char *) directory;
+ break;
+
+ case GRAB_VIDEO:
+ av[ac++] = GETIMAGE_VIDEO_PROGRAM;
+ if (verbose_p)
+ av[ac++] = "--verbose";
+ av[ac++] = "--name";
+ break;
+
+# ifdef USE_EXTERNAL_SCREEN_GRABBER
+ case GRAB_DESK:
+ av[ac++] = GETIMAGE_SCREEN_PROGRAM;
+ if (verbose_p)
+ av[ac++] = "--verbose";
+ av[ac++] = "--name";
+ break;
+# endif
+
+ default:
+ abort();
+ }
+ av[ac] = 0;
+
+ if (verbose_p)
+ {
+ int i;
+ fprintf (stderr, "%s: executing:", progname);
+ for (i = 0; i < ac; i++)
+ fprintf (stderr, " %s", av[i]);
+ fprintf (stderr, "\n");
+ }
+
+ if (pipe (fds))
+ {
+ sprintf (buf, "%s: error creating pipe", progname);
+ perror (buf);
+ return 0;
+ }
+
+ in = fds [0];
+ out = fds [1];
+
+ switch ((int) (forked = fork ()))
+ {
+ case -1:
+ {
+ sprintf (buf, "%s: couldn't fork", progname);
+ perror (buf);
+ return 0;
+ }
+ case 0:
+ {
+ int stdout_fd = 1;
+
+ close (in); /* don't need this one */
+ close (ConnectionNumber (dpy)); /* close display fd */
+
+ if (dup2 (out, stdout_fd) < 0) /* pipe stdout */
+ {
+ sprintf (buf, "%s: could not dup() a new stdout", progname);
+ exit (-1); /* exits fork */
+ }
+
+ execvp (av[0], av); /* shouldn't return. */
+ exit (-1); /* exits fork */
+ break;
+ }
+ default:
+ {
+ struct stat st;
+ int wait_status = 0;
+ FILE *f = fdopen (in, "r");
+ int L;
+ char *ret = 0;
+
+ close (out); /* don't need this one */
+ *buf = 0;
+ if (! fgets (buf, sizeof(buf)-1, f))
+ *buf = 0;
+ fclose (f);
+
+ /* Wait for the child to die. */
+ waitpid (-1, &wait_status, 0);
+
+ L = strlen (buf);
+ while (L && buf[L-1] == '\n')
+ buf[--L] = 0;
+
+ if (!*buf)
+ return 0;
+
+ ret = strdup (buf);
+
+ if (*ret != '/')
+ {
+ /* Program returned path relative to directory. Prepend dir
+ to buf so that we can properly stat it. */
+ strcpy (buf, directory);
+ if (directory[strlen(directory)-1] != '/')
+ strcat (buf, "/");
+ strcat (buf, ret);
+ }
+
+ if (stat(buf, &st))
+ {
+ fprintf (stderr, "%s: file does not exist: \"%s\"\n",
+ progname, buf);
+ free (ret);
+ return 0;
+ }
+ else
+ return ret;
+ }
+ }
+
+ abort();
+}
+
+
+/* Returns a pathname to an image file. Free the string when you're done.
+ */
+static char *
+get_filename (Screen *screen, const char *directory, Bool verbose_p)
+{
+ return get_filename_1 (screen, directory, GRAB_FILE, verbose_p);
+}
+
+
+/* Grabs a video frame to a file, and returns a pathname to that file.
+ Delete that file when you are done with it (and free the string.)
+ */
+static char *
+get_video_filename (Screen *screen, Bool verbose_p)
+{
+ return get_filename_1 (screen, 0, GRAB_VIDEO, verbose_p);
+}
+
+/* Grabs a desktop image to a file, and returns a pathname to that file.
+ Delete that file when you are done with it (and free the string.)
+ */
+# ifdef USE_EXTERNAL_SCREEN_GRABBER
+static char *
+get_desktop_filename (Screen *screen, Bool verbose_p)
+{
+ return get_filename_1 (screen, 0, GRAB_DESK, verbose_p);
+}
+#endif /* USE_EXTERNAL_SCREEN_GRABBER */
+
+
+/* Grabs a video frame, and renders it on the Drawable.
+ Returns False if it fails;
+ */
+static Bool
+display_video (Screen *screen, Window window, Drawable drawable,
+ Bool verbose_p, XRectangle *geom_ret)
+{
+ char *filename = get_video_filename (screen, verbose_p);
+ Bool status;
+
+ if (!filename)
+ {
+ if (verbose_p)
+ fprintf (stderr, "%s: video grab failed.\n", progname);
+ return False;
+ }
+
+ status = display_file (screen, window, drawable, filename, verbose_p,
+ geom_ret);
+
+ if (unlink (filename))
+ {
+ char buf[512];
+ sprintf (buf, "%s: rm %.100s", progname, filename);
+ perror (buf);
+ }
+ else if (verbose_p)
+ fprintf (stderr, "%s: rm %s\n", progname, filename);
+
+ if (filename) free (filename);
+ return status;
+}
+
+
+/* Grabs a desktop screen shot onto the window and the drawable.
+ If the window and drawable are not the same size, the image in
+ the drawable is scaled to fit.
+ Returns False if it fails.
+ */
+static Bool
+display_desktop (Screen *screen, Window window, Drawable drawable,
+ Bool verbose_p, XRectangle *geom_ret)
+{
+# ifdef USE_EXTERNAL_SCREEN_GRABBER
+
+ Display *dpy = DisplayOfScreen (screen);
+ Bool top_p = top_level_window_p (screen, window);
+ char *filename;
+ Bool status;
+
+ if (top_p)
+ {
+ if (verbose_p)
+ fprintf (stderr, "%s: unmapping 0x%lx.\n", progname,
+ (unsigned long) window);
+ XUnmapWindow (dpy, window);
+ XSync (dpy, False);
+ }
+
+ filename = get_desktop_filename (screen, verbose_p);
+
+ if (top_p)
+ {
+ if (verbose_p)
+ fprintf (stderr, "%s: mapping 0x%lx.\n", progname,
+ (unsigned long) window);
+ XMapRaised (dpy, window);
+ XSync (dpy, False);
+ }
+
+ if (!filename)
+ {
+ if (verbose_p)
+ fprintf (stderr, "%s: desktop grab failed.\n", progname);
+ return False;
+ }
+
+ status = display_file (screen, window, drawable, filename, verbose_p,
+ geom_ret);
+
+ if (unlink (filename))
+ {
+ char buf[512];
+ sprintf (buf, "%s: rm %.100s", progname, filename);
+ perror (buf);
+ }
+ else if (verbose_p)
+ fprintf (stderr, "%s: rm %s\n", progname, filename);
+
+ if (filename) free (filename);
+ return status;
+
+# else /* !USE_EXTERNAL_SCREEN_GRABBER */
+
+ Display *dpy = DisplayOfScreen (screen);
+ XGCValues gcv;
+ XWindowAttributes xgwa;
+ Window root;
+ int px, py;
+ unsigned int pw, ph, pbw, pd;
+ int srcx, srcy, destx, desty, w2, h2;
+
+ if (verbose_p)
+ {
+ fprintf (stderr, "%s: grabbing desktop image\n", progname);
+ grabscreen_verbose();
+ }
+
+ XGetWindowAttributes (dpy, window, &xgwa);
+ XGetGeometry (dpy, drawable, &root, &px, &py, &pw, &ph, &pbw, &pd);
+
+ grab_screen_image_internal (screen, window);
+
+ compute_image_scaling (xgwa.width, xgwa.height,
+ pw, ph, verbose_p,
+ &srcx, &srcy, &destx, &desty, &w2, &h2);
+
+ if (pw == w2 && ph == h2) /* it fits -- just copy server-side pixmaps */
+ {
+ GC gc = XCreateGC (dpy, drawable, 0, &gcv);
+ XCopyArea (dpy, window, drawable, gc,
+ 0, 0, xgwa.width, xgwa.height, 0, 0);
+ XFreeGC (dpy, gc);
+ }
+ else /* size mismatch -- must scale client-side images to fit drawable */
+ {
+ GC gc;
+ XImage *ximage = 0;
+ XErrorHandler old_handler;
+
+ XSync (dpy, False);
+ old_handler = XSetErrorHandler (ignore_badmatch_ehandler);
+ error_handler_hit_p = False;
+
+ /* This can return BadMatch if the window is not fully on screen.
+ Trap that error and return color bars in that case.
+ (Note that this only happens with XGetImage, not with XCopyArea:
+ yet another totally gratuitous inconsistency in X, thanks.)
+ */
+ ximage = XGetImage (dpy, window, 0, 0, xgwa.width, xgwa.height,
+ ~0L, ZPixmap);
+
+ XSync (dpy, False);
+ XSetErrorHandler (old_handler);
+ XSync (dpy, False);
+
+ if (error_handler_hit_p)
+ {
+ ximage = 0;
+ if (verbose_p)
+ fprintf (stderr, "%s: BadMatch reading window 0x%x contents!\n",
+ progname, (unsigned int) window);
+ }
+
+ if (!ximage ||
+ !scale_ximage (xgwa.screen, xgwa.visual, ximage, w2, h2))
+ return False;
+
+ gc = XCreateGC (dpy, drawable, 0, &gcv);
+ clear_drawable (screen, drawable);
+ XPutImage (dpy, drawable, gc, ximage,
+ srcx, srcy, destx, desty, ximage->width, ximage->height);
+ XDestroyImage (ximage);
+ XFreeGC (dpy, gc);
+ }
+
+ if (geom_ret)
+ {
+ geom_ret->x = destx;
+ geom_ret->y = desty;
+ geom_ret->width = w2;
+ geom_ret->height = h2;
+ }
+
+ XSync (dpy, False);
+ return True;
+
+# endif /* !USE_EXTERNAL_SCREEN_GRABBER */
+}
+
+
+/* Whether the given Drawable is unreasonably small.
+ */
+static Bool
+drawable_miniscule_p (Display *dpy, Drawable drawable)
+{
+ Window root;
+ int xx, yy;
+ unsigned int bw, d, w = 0, h = 0;
+ XGetGeometry (dpy, drawable, &root, &xx, &yy, &w, &h, &bw, &d);
+ return (w < 32 || h < 30);
+}
+
+
+/* Grabs an image (from a file, video, or the desktop) and renders it on
+ the Drawable. If `file' is specified, always use that file. Otherwise,
+ select randomly, based on the other arguments.
+ */
+static void
+get_image (Screen *screen,
+ Window window, Drawable drawable,
+ Bool verbose_p,
+ Bool desk_p,
+ Bool video_p,
+ Bool image_p,
+ const char *dir,
+ const char *file)
+{
+ Display *dpy = DisplayOfScreen (screen);
+ grab_type which = GRAB_BARS;
+ struct stat st;
+ const char *file_prop = 0;
+ char *absfile = 0;
+ XRectangle geom = { 0, 0, 0, 0 };
+
+ if (! drawable_window_p (dpy, window))
+ {
+ fprintf (stderr, "%s: 0x%lx is a pixmap, not a window!\n",
+ progname, (unsigned long) window);
+ exit (1);
+ }
+
+ /* Make sure the Screen and the Window correspond. */
+ {
+ XWindowAttributes xgwa;
+ XGetWindowAttributes (dpy, window, &xgwa);
+ screen = xgwa.screen;
+ }
+
+ if (file && stat (file, &st))
+ {
+ fprintf (stderr, "%s: file \"%s\" does not exist\n", progname, file);
+ file = 0;
+ }
+
+ if (verbose_p)
+ {
+ fprintf (stderr, "%s: grabDesktopImages: %s\n",
+ progname, desk_p ? "True" : "False");
+ fprintf (stderr, "%s: grabVideoFrames: %s\n",
+ progname, video_p ? "True" : "False");
+ fprintf (stderr, "%s: chooseRandomImages: %s\n",
+ progname, image_p ? "True" : "False");
+ fprintf (stderr, "%s: imageDirectory: %s\n",
+ progname, (file ? file : dir ? dir : ""));
+ }
+
+# if !(defined(HAVE_GDK_PIXBUF) || defined(HAVE_JPEGLIB))
+ image_p = False; /* can't load images from files... */
+# ifdef USE_EXTERNAL_SCREEN_GRABBER
+ desk_p = False; /* ...or from desktops grabbed to files. */
+# endif
+
+ if (file)
+ {
+ fprintf (stderr,
+ "%s: image file loading not available at compile-time\n",
+ progname);
+ fprintf (stderr, "%s: can't load \"%s\"\n", progname, file);
+ file = 0;
+ }
+# endif /* !(HAVE_GDK_PIXBUF || HAVE_JPEGLIB) */
+
+ if (file)
+ {
+ desk_p = False;
+ video_p = False;
+ image_p = True;
+ }
+ else if (!dir || !*dir)
+ {
+ if (verbose_p && image_p)
+ fprintf (stderr,
+ "%s: no imageDirectory: turning off chooseRandomImages.\n",
+ progname);
+ image_p = False;
+ }
+
+ /* If the target drawable is really small, no good can come of that.
+ Always do colorbars in that case.
+ */
+ if (drawable_miniscule_p (dpy, drawable))
+ {
+ desk_p = False;
+ video_p = False;
+ image_p = False;
+ }
+
+# ifndef _VROOT_H_
+# error Error! This file definitely needs vroot.h!
+# endif
+
+ /* We can grab desktop images (using the normal X11 method) if:
+ - the window is the real root window;
+ - the window is a toplevel window.
+ We cannot grab desktop images that way if:
+ - the window is a non-top-level window.
+
+ Under X11 on MacOS, desktops are just like loaded image files.
+ Under Cocoa on MacOS, this code is not used at all.
+ */
+# ifndef USE_EXTERNAL_SCREEN_GRABBER
+ if (desk_p)
+ {
+ if (!top_level_window_p (screen, window))
+ {
+ desk_p = False;
+ if (verbose_p)
+ fprintf (stderr,
+ "%s: 0x%x not top-level: turning off grabDesktopImages.\n",
+ progname, (unsigned int) window);
+ }
+ }
+# endif /* !USE_EXTERNAL_SCREEN_GRABBER */
+
+ if (! (desk_p || video_p || image_p))
+ which = GRAB_BARS;
+ else
+ {
+ int i = 0;
+ int n;
+ /* Loop until we get one that's permitted.
+ If files or video are permitted, do them more often
+ than desktop.
+
+ D+V+I: 10% + 45% + 45%.
+ V+I: 50% + 50%
+ D+V: 18% + 82%
+ D+I: 18% + 82%
+ */
+ AGAIN:
+ n = (random() % 100);
+ if (++i > 300) abort();
+ else if (desk_p && n < 10) which = GRAB_DESK; /* 10% */
+ else if (video_p && n < 55) which = GRAB_VIDEO; /* 45% */
+ else if (image_p) which = GRAB_FILE; /* 45% */
+ else goto AGAIN;
+ }
+
+
+ /* If we're to search a directory to find an image file, do so now.
+ */
+ if (which == GRAB_FILE && !file)
+ {
+ file = get_filename (screen, dir, verbose_p);
+ if (!file)
+ {
+ which = GRAB_BARS;
+ if (verbose_p)
+ fprintf (stderr, "%s: no image files found.\n", progname);
+ }
+ }
+
+ /* Now actually render something.
+ */
+ switch (which)
+ {
+ case GRAB_BARS:
+ {
+ XWindowAttributes xgwa;
+ COLORBARS:
+ if (verbose_p)
+ fprintf (stderr, "%s: drawing colorbars.\n", progname);
+ XGetWindowAttributes (dpy, window, &xgwa);
+ colorbars (screen, xgwa.visual, drawable, xgwa.colormap);
+ XSync (dpy, False);
+ if (! file_prop) file_prop = "";
+
+ }
+ break;
+
+ case GRAB_DESK:
+ if (! display_desktop (screen, window, drawable, verbose_p, &geom))
+ goto COLORBARS;
+ file_prop = "desktop";
+ break;
+
+ case GRAB_FILE:
+ if (*file && *file != '/') /* pathname is relative to dir. */
+ {
+ if (absfile) free (absfile);
+ absfile = malloc (strlen(dir) + strlen(file) + 10);
+ strcpy (absfile, dir);
+ if (dir[strlen(dir)-1] != '/')
+ strcat (absfile, "/");
+ strcat (absfile, file);
+ }
+ if (! display_file (screen, window, drawable,
+ (absfile ? absfile : file),
+ verbose_p, &geom))
+ goto COLORBARS;
+ file_prop = file;
+ break;
+
+ case GRAB_VIDEO:
+ if (! display_video (screen, window, drawable, verbose_p, &geom))
+ goto COLORBARS;
+ file_prop = "video";
+ break;
+
+ default:
+ abort();
+ break;
+ }
+
+ {
+ Atom a = XInternAtom (dpy, XA_XSCREENSAVER_IMAGE_FILENAME, False);
+ if (file_prop && *file_prop)
+ {
+ char *f2 = strdup (file_prop);
+
+ /* Take the extension off of the file name. */
+ /* Duplicated in utils/grabclient.c. */
+ char *slash = strrchr (f2, '/');
+ char *dot = strrchr ((slash ? slash : f2), '.');
+ if (dot) *dot = 0;
+ /* Replace slashes with newlines */
+ /* while ((dot = strchr(f2, '/'))) *dot = '\n'; */
+ /* Replace slashes with spaces */
+ /* while ((dot = strchr(f2, '/'))) *dot = ' '; */
+
+ XChangeProperty (dpy, window, a, XA_STRING, 8, PropModeReplace,
+ (unsigned char *) f2, strlen(f2));
+ free (f2);
+ }
+ else
+ XDeleteProperty (dpy, window, a);
+
+ a = XInternAtom (dpy, XA_XSCREENSAVER_IMAGE_GEOMETRY, False);
+ if (geom.width > 0)
+ {
+ char gstr[30];
+ sprintf (gstr, "%dx%d+%d+%d", geom.width, geom.height, geom.x, geom.y);
+ XChangeProperty (dpy, window, a, XA_STRING, 8, PropModeReplace,
+ (unsigned char *) gstr, strlen (gstr));
+ }
+ else
+ XDeleteProperty (dpy, window, a);
+ }
+
+ if (absfile) free (absfile);
+ XSync (dpy, False);
+}
+
+
+#ifdef DEBUG
+static Bool
+mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks,
+ XrmRepresentation *type, XrmValue *value, XPointer closure)
+{
+ int i;
+ for (i = 0; quarks[i]; i++)
+ {
+ if (bindings[i] == XrmBindTightly)
+ fprintf (stderr, (i == 0 ? "" : "."));
+ else if (bindings[i] == XrmBindLoosely)
+ fprintf (stderr, "*");
+ else
+ fprintf (stderr, " ??? ");
+ fprintf(stderr, "%s", XrmQuarkToString (quarks[i]));
+ }
+
+ fprintf (stderr, ": %s\n", (char *) value->addr);
+
+ return False;
+}
+#endif /* DEBUG */
+
+
+#define USAGE "usage: %s [ -options... ] window-id [pixmap-id]\n" \
+ "\n" \
+ " %s\n" \
+ "\n" \
+ " %s puts an image on the given window or pixmap.\n" \
+ "\n" \
+ " It is used by those xscreensaver demos that operate on images.\n" \
+ " The image may be a file loaded from disk, a frame grabbed from\n" \
+ " the system's video camera, or a screenshot of the desktop,\n" \
+ " depending on command-line options or the ~/.xscreensaver file.\n" \
+ "\n" \
+ " Options include:\n" \
+ "\n" \
+ " -display host:dpy.screen which display to use\n" \
+ " -root draw to the root window\n" \
+ " -verbose print diagnostics\n" \
+ " -images / -no-images whether to allow image file loading\n" \
+ " -video / -no-video whether to allow video grabs\n" \
+ " -desktop / -no-desktop whether to allow desktop screen grabs\n"\
+ " -directory <path> where to find image files to load\n" \
+ " -file <filename> load this image file\n" \
+ "\n" \
+ " The XScreenSaver Control Panel (xscreensaver-demo) lets you set the\n"\
+ " defaults for these options in your ~/.xscreensaver file.\n" \
+ "\n"
+
+int
+main (int argc, char **argv)
+{
+ saver_preferences P;
+ Widget toplevel;
+ Display *dpy;
+ Screen *screen;
+ char *oprogname = progname;
+ char *file = 0;
+ char version[255];
+
+ Window window = (Window) 0;
+ Drawable drawable = (Drawable) 0;
+ const char *window_str = 0;
+ const char *drawable_str = 0;
+ char *s;
+ int i;
+
+ progname = argv[0];
+ s = strrchr (progname, '/');
+ if (s) progname = s+1;
+ oprogname = progname;
+
+ /* half-assed way of avoiding buffer-overrun attacks. */
+ if (strlen (progname) >= 100) progname[100] = 0;
+
+# ifndef _VROOT_H_
+# error Error! This file definitely needs vroot.h!
+# endif
+
+ /* Get the version number, for error messages. */
+ {
+ char *v = (char *) strdup(strchr(screensaver_id, ' '));
+ char *s1, *s2, *s3, *s4;
+ s1 = (char *) strchr(v, ' '); s1++;
+ s2 = (char *) strchr(s1, ' ');
+ s3 = (char *) strchr(v, '('); s3++;
+ s4 = (char *) strchr(s3, ')');
+ *s2 = 0;
+ *s4 = 0;
+ sprintf (version, "Part of XScreenSaver %s -- %s.", s1, s3);
+ free(v);
+ }
+
+ /* We must read exactly the same resources as xscreensaver.
+ That means we must have both the same progclass *and* progname,
+ at least as far as the resource database is concerned. So,
+ put "xscreensaver" in argv[0] while initializing Xt.
+ */
+ progname = argv[0] = "xscreensaver";
+
+ /* allow one dash or two. */
+ for (i = 1; i < argc; i++)
+ if (argv[i][0] == '-' && argv[i][1] == '-') argv[i]++;
+
+ toplevel = XtAppInitialize (&app, progclass, 0, 0, &argc, argv,
+ defaults, 0, 0);
+ dpy = XtDisplay (toplevel);
+ screen = XtScreen (toplevel);
+ db = XtDatabase (dpy);
+ XtGetApplicationNameAndClass (dpy, &s, &progclass);
+ XSetErrorHandler (x_ehandler);
+ XSync (dpy, False);
+
+ /* Randomize -- only need to do this here because this program
+ doesn't use the `screenhack.h' or `lockmore.h' APIs. */
+# undef ya_rand_init
+ ya_rand_init (0);
+
+ memset (&P, 0, sizeof(P));
+ P.db = db;
+ load_init_file (dpy, &P);
+
+ progname = argv[0] = oprogname;
+
+ for (i = 1; i < argc; i++)
+ {
+ unsigned long w;
+ char dummy;
+
+ /* Have to re-process these, or else the .xscreensaver file
+ has priority over the command line...
+ */
+ if (!strcmp (argv[i], "-v") || !strcmp (argv[i], "-verbose"))
+ P.verbose_p = True;
+ else if (!strcmp (argv[i], "-desktop")) P.grab_desktop_p = True;
+ else if (!strcmp (argv[i], "-no-desktop")) P.grab_desktop_p = False;
+ else if (!strcmp (argv[i], "-video")) P.grab_video_p = True;
+ else if (!strcmp (argv[i], "-no-video")) P.grab_video_p = False;
+ else if (!strcmp (argv[i], "-images")) P.random_image_p = True;
+ else if (!strcmp (argv[i], "-no-images")) P.random_image_p = False;
+ else if (!strcmp (argv[i], "-file")) file = argv[++i];
+ else if (!strcmp (argv[i], "-directory") || !strcmp (argv[i], "-dir"))
+ P.image_directory = argv[++i];
+ else if (!strcmp (argv[i], "-root") || !strcmp (argv[i], "root"))
+ {
+ if (window)
+ {
+ fprintf (stderr, "%s: both %s and %s specified?\n",
+ progname, argv[i], window_str);
+ goto LOSE;
+ }
+ window_str = argv[i];
+ window = VirtualRootWindowOfScreen (screen);
+ }
+ else if ((1 == sscanf (argv[i], " 0x%lx %c", &w, &dummy) ||
+ 1 == sscanf (argv[i], " %lu %c", &w, &dummy)) &&
+ w != 0)
+ {
+ if (drawable)
+ {
+ fprintf (stderr, "%s: both %s and %s specified?\n",
+ progname, drawable_str, argv[i]);
+ goto LOSE;
+ }
+ else if (window)
+ {
+ drawable_str = argv[i];
+ drawable = (Drawable) w;
+ }
+ else
+ {
+ window_str = argv[i];
+ window = (Window) w;
+ }
+ }
+ else
+ {
+ if (argv[i][0] == '-')
+ fprintf (stderr, "\n%s: unknown option \"%s\"\n",
+ progname, argv[i]);
+ else
+ fprintf (stderr, "\n%s: unparsable window/pixmap ID: \"%s\"\n",
+ progname, argv[i]);
+ LOSE:
+# ifdef __GNUC__
+ __extension__ /* don't warn about "string length is greater than
+ the length ISO C89 compilers are required to
+ support" in the usage string... */
+# endif
+ fprintf (stderr, USAGE, progname, version, progname);
+ exit (1);
+ }
+ }
+
+ if (window == 0)
+ {
+ fprintf (stderr, "\n%s: no window ID specified!\n", progname);
+ goto LOSE;
+ }
+
+
+#ifdef DEBUG
+ if (P.verbose_p) /* Print out all the resources we can see. */
+ {
+ XrmName name = { 0 };
+ XrmClass class = { 0 };
+ int count = 0;
+ XrmEnumerateDatabase (db, &name, &class, XrmEnumAllLevels, mapper,
+ (XtPointer) &count);
+ }
+#endif /* DEBUG */
+
+ if (!window) abort();
+ if (!drawable) drawable = window;
+
+ get_image (screen, window, drawable, P.verbose_p,
+ P.grab_desktop_p, P.grab_video_p, P.random_image_p,
+ P.image_directory, file);
+ exit (0);
+}
diff --git a/driver/xscreensaver-getimage.man b/driver/xscreensaver-getimage.man
new file mode 100644
index 0000000..ae68014
--- /dev/null
+++ b/driver/xscreensaver-getimage.man
@@ -0,0 +1,73 @@
+.TH XScreenSaver 1 "20-Mar-2005 (4.21)" "X Version 11"
+.SH NAME
+xscreensaver-getimage - put some randomly-selected image on the root window
+.SH SYNOPSIS
+.B xscreensaver-getimage
+[\-display \fIhost:display.screen\fP] [\--verbose] window-id [pixmap-id]
+.SH DESCRIPTION
+The \fIxscreensaver\-getimage\fP program is a helper program for the
+xscreensaver hacks that manipulate images. This is not a user-level
+command.
+
+This program selects a random image, and puts it on the specified
+window or pixmap. This image might be a snapshot of the desktop; or
+a frame captured from the system's video input; or a randomly-selected
+image from disk.
+
+If only a window ID is specified, the image will be painted there.
+If both a window ID and a pixmap ID are specified, then the image will
+be painted on the pixmap; and the window \fImay\fP be modified as a
+side-effect.
+.SH OPTIONS
+.I xscreensaver-getimage
+reads the \fI~/.xscreensaver\fP file for configuration information.
+It uses these settings:
+.TP 4
+.B grabDesktopImages
+Whether it is acceptable to grab snapshots of the desktop.
+The security paranoid might want to turn this off, to avoid letting
+people see (but not touch!) your desktop while the screen is locked.
+.TP 4
+.B grabVideoFrames
+Whether it is acceptable to grab frames of video from the system's video
+input. Grabbing of video is done by invoking the
+.BR xscreensaver-getimage-video (1)
+program.
+.TP 4
+.B chooseRandomImages
+Whether it is acceptable to display random images found on disk.
+Selection and loading of images is done by invoking the
+.BR xscreensaver-getimage-file (1)
+program.
+.TP 4
+.B imageDirectory
+When loading images from disk, this is the directory to find them in.
+The directory will be searched recursively for images.
+
+It may also be the URL of an RSS or Atom feed, 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 none of the three options are set to True, then video
+colorbars will be displayed instead.
+.SH BUGS
+When grabbing desktop images, the \fIwindow\fP argument will be unmapped
+and have its contents modified, causing flicker. (This does not happen
+when loading image files or video frames.)
+.SH SEE ALSO
+.BR X (1),
+.BR xscreensaver (1)
+.BR xscreensaver\-demo (1)
+.BR xscreensaver\-getimage\-file (1)
+.BR xscreensaver\-getimage\-video (1)
+.SH COPYRIGHT
+Copyright \(co 2001-2011 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>, 14-Apr-01
diff --git a/driver/xscreensaver-text b/driver/xscreensaver-text
new file mode 100755
index 0000000..e965bed
--- /dev/null
+++ b/driver/xscreensaver-text
@@ -0,0 +1,884 @@
+#!/usr/bin/perl -w
+# Copyright © 2005-2017 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 program writes some text to stdout, based on preferences in the
+# .xscreensaver file. It may load a file, a URL, run a program, or just
+# print the date.
+#
+# In a native MacOS build of xscreensaver, this script is included in
+# the Contents/Resources/ directory of each screen saver .bundle that
+# uses it; and in that case, it looks up its resources using
+# /usr/bin/defaults instead.
+#
+# Created: 19-Mar-2005.
+
+require 5;
+#use diagnostics; # Fails on some MacOS 10.5 systems
+use strict;
+
+# Some Linux systems don't install LWP by default!
+# Only error out if we're actually loading a URL instead of local data.
+BEGIN { eval 'use LWP::UserAgent;' }
+
+# Not sure how prevalent this is. Hope it's part of the default install.
+BEGIN { eval 'use HTML::Entities;' }
+
+use Socket;
+use POSIX qw(strftime);
+use Text::Wrap qw(wrap);
+#use bytes; # This breaks shit.
+
+my $progname = $0; $progname =~ s@.*/@@g;
+my ($version) = ('$Revision: 1.46 $' =~ m/\s(\d[.\d]+)\s/s);
+
+my $verbose = 0;
+my $http_proxy = undef;
+
+my $config_file = $ENV{HOME} . "/.xscreensaver";
+my $text_mode = 'date';
+my $text_literal = '';
+my $text_file = '';
+my $text_program = '';
+my $text_url = 'https://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss';
+# Default URL needs to be set and match what's in OSX/XScreenSaverView.m
+
+my $wrap_columns = undef;
+my $truncate_lines = undef;
+my $latin1_p = 0;
+my $nyarlathotep_p = 0;
+
+
+# Convert any HTML entities to Latin1 characters.
+#
+sub de_entify($) {
+ my ($text) = @_;
+
+ return '' unless defined($text);
+ return $text unless ($text =~ m/&/s);
+
+ # Convert any HTML entities to Unicode characters,
+ # if the HTML::Entities module is installed.
+ eval {
+ my $t2 = $text;
+ $text = undef;
+ $text = HTML::Entities::decode_entities ($t2);
+ };
+ return $text if defined($text);
+
+ # If it's not installed, just complain instead of trying to halfass it.
+ print STDOUT ("\n\tPerl is broken. Do this to repair it:\n" .
+ "\n\tsudo cpan HTML::Entities\n\n");
+ exit (1);
+}
+
+
+# Convert any Unicode characters to Latin1 if possible.
+# Unconvertable bytes are left alone.
+#
+sub utf8_to_latin1($) {
+ my ($text) = @_;
+
+ utf8::encode ($text); # Unpack Unicode back to multi-byte UTF-8.
+
+ # Maybe it would be better to handle this in the Unicode domain
+ # by doing things like s/\x{2018}/\"/g, but without decoding the
+ # string back to UTF-8 first, I'm at a loss as to how to have
+ # "&aacute;" print as "\340" instead of as "\303\240".
+
+ $text =~ s/ \xC2 ( [\xA0-\xFF] ) / $1 /gsex;
+ $text =~ s/ \xC3 ( [\x80-\xFF] ) / chr (ord($1) | 0x40) /gsex;
+
+ # Handles a few 3-byte sequences too.
+ $text =~ s/\xE2\x80\x93/--/gs;
+ $text =~ s/\xE2\x80\x94/--/gs;
+ $text =~ s/\xE2\x80\x98/`/gs;
+ $text =~ s/\xE2\x80\x99/'/gs;
+ $text =~ s/\xE2\x80\x9C/``/gs;
+ $text =~ s/\xE2\x80\x9D/'/gs;
+ $text =~ s/\xE2\x80\xA2/&bull;/gs;
+ $text =~ s/\xE2\x80\xA6/.../gs;
+ $text =~ s/\xE2\x80\xB2/'/gs;
+ $text =~ s/\xE2\x84\xA2/&trade;/gs;
+ $text =~ s/\xE2\x86\x90/ &larr; /gs;
+
+ return $text;
+}
+
+
+# Reads the prefs we use from ~/.xscreensaver
+#
+sub get_x11_prefs() {
+ my $got_any_p = 0;
+
+ if (open (my $in, '<', $config_file)) {
+ print STDERR "$progname: reading $config_file\n" if ($verbose > 1);
+ local $/ = undef; # read entire file
+ my $body = <$in>;
+ close $in;
+ $got_any_p = get_x11_prefs_1 ($body);
+
+ } elsif ($verbose > 1) {
+ print STDERR "$progname: $config_file: $!\n";
+ }
+
+ if (! $got_any_p && defined ($ENV{DISPLAY})) {
+ # We weren't able to read settings from the .xscreensaver file.
+ # Fall back to any settings in the X resource database
+ # (/usr/X11R6/lib/X11/app-defaults/XScreenSaver)
+ #
+ print STDERR "$progname: reading X resources\n" if ($verbose > 1);
+ my $body = `appres XScreenSaver xscreensaver -1`;
+ $got_any_p = get_x11_prefs_1 ($body);
+ }
+
+ if ($verbose > 1) {
+ print STDERR "$progname: mode: $text_mode\n";
+ print STDERR "$progname: literal: $text_literal\n";
+ print STDERR "$progname: file: $text_file\n";
+ print STDERR "$progname: program: $text_program\n";
+ print STDERR "$progname: url: $text_url\n";
+ }
+
+ $text_mode =~ tr/A-Z/a-z/;
+ $text_literal =~ s@\\n@\n@gs;
+ $text_literal =~ s@\\\n@\n@gs;
+}
+
+
+sub get_x11_prefs_1($) {
+ my ($body) = @_;
+
+ my $got_any_p = 0;
+ $body =~ s@\\\n@@gs;
+ $body =~ s@^[ \t]*#[^\n]*$@@gm;
+
+ if ($body =~ m/^[.*]*textMode:[ \t]*([^\s]+)\s*$/im) {
+ $text_mode = $1;
+ $got_any_p = 1;
+ }
+ if ($body =~ m/^[.*]*textLiteral:[ \t]*(.*?)[ \t]*$/im) {
+ $text_literal = $1;
+ }
+ if ($body =~ m/^[.*]*textFile:[ \t]*(.*?)[ \t]*$/im) {
+ $text_file = $1;
+ }
+ if ($body =~ m/^[.*]*textProgram:[ \t]*(.*?)[ \t]*$/im) {
+ $text_program = $1;
+ }
+ if ($body =~ m/^[.*]*textURL:[ \t]*(.*?)[ \t]*$/im) {
+ $text_url = $1;
+ }
+
+ return $got_any_p;
+}
+
+
+sub get_cocoa_prefs($) {
+ my ($id) = @_;
+ my $v;
+
+ print STDERR "$progname: reading Cocoa prefs: \"$id\"\n" if ($verbose > 1);
+
+ $v = get_cocoa_pref_1 ($id, "textMode");
+ $text_mode = $v if defined ($v);
+
+ # The "textMode" pref is set to a number instead of a string because I
+ # couldn't figure out the black magic to make Cocoa bindings work right.
+ #
+ # Update: as of 5.33, Cocoa writes strings instead of numbers, but
+ # pre-existing saved preferences might still have numbers in them.
+ #
+ if ($text_mode eq '0') { $text_mode = 'date'; }
+ elsif ($text_mode eq '1') { $text_mode = 'literal'; }
+ elsif ($text_mode eq '2') { $text_mode = 'file'; }
+ elsif ($text_mode eq '3') { $text_mode = 'url'; }
+ elsif ($text_mode eq '4') { $text_mode = 'program'; }
+
+ $v = get_cocoa_pref_1 ($id, "textLiteral");
+ $text_literal = $v if defined ($v);
+ $text_literal =~ s@\\n@\n@gs;
+ $text_literal =~ s@\\\n@\n@gs;
+
+ $v = get_cocoa_pref_1 ($id, "textFile");
+ $text_file = $v if defined ($v);
+
+ $v = get_cocoa_pref_1 ($id, "textProgram");
+ $text_program = $v if defined ($v);
+
+ $v = get_cocoa_pref_1 ($id, "textURL");
+ $text_url = $v if defined ($v);
+}
+
+
+sub get_cocoa_pref_1($$) {
+ my ($id, $key) = @_;
+ # make sure there's nothing stupid/malicious in either string.
+ $id =~ s/[^-a-z\d. ]/_/gsi;
+ $key =~ s/[^-a-z\d. ]/_/gsi;
+ my $cmd = "defaults -currentHost read \"$id\" \"$key\"";
+
+ print STDERR "$progname: executing $cmd\n"
+ if ($verbose > 3);
+
+ my $val = `$cmd 2>/dev/null`;
+ $val =~ s/^\s+//s;
+ $val =~ s/\s+$//s;
+
+ print STDERR "$progname: Cocoa: $id $key = \"$val\"\n"
+ if ($verbose > 2);
+
+ $val = undef if ($val =~ m/^$/s);
+
+ return $val;
+}
+
+
+# like system() but checks errors.
+#
+sub safe_system(@) {
+ my (@cmd) = @_;
+
+ print STDERR "$progname: executing " . join(' ', @cmd) . "\n"
+ if ($verbose > 3);
+
+ system @cmd;
+ my $exit_value = $? >> 8;
+ my $signal_num = $? & 127;
+ my $dumped_core = $? & 128;
+ error ("$cmd[0]: core dumped!") if ($dumped_core);
+ error ("$cmd[0]: signal $signal_num!") if ($signal_num);
+ error ("$cmd[0]: exited with $exit_value!") if ($exit_value);
+}
+
+
+sub which($) {
+ my ($cmd) = @_;
+
+ if ($cmd =~ m@^\./|^/@) {
+ error ("cannot execute $cmd") unless (-x $cmd);
+ return $cmd;
+ }
+
+ foreach my $dir (split (/:/, $ENV{PATH})) {
+ my $cmd2 = "$dir/$cmd";
+ print STDERR "$progname: checking $cmd2\n" if ($verbose > 3);
+ return $cmd2 if (-x "$cmd2");
+ }
+ error ("$cmd not found on \$PATH");
+}
+
+
+sub output() {
+
+ binmode (STDOUT, ($latin1_p ? ':raw' : ':utf8'));
+ binmode (STDERR, ':utf8');
+
+ # Do some basic sanity checking (null text, null file names, etc.)
+ #
+ if (($text_mode eq 'literal' && $text_literal =~ m/^\s*$/i) ||
+ ($text_mode eq 'file' && $text_file =~ m/^\s*$/i) ||
+ ($text_mode eq 'program' && $text_program =~ m/^\s*$/i) ||
+ ($text_mode eq 'url' && $text_url =~ m/^\s*$/i)) {
+ print STDERR "$progname: falling back to 'date'\n" if ($verbose);
+ $text_mode = 'date';
+ }
+
+ if ($text_mode eq 'literal') {
+ $text_literal = strftime ($text_literal, localtime);
+ $text_literal = utf8_to_latin1($text_literal) if ($latin1_p);
+ $text_literal =~ y/A-Za-z/N-ZA-Mn-za-m/ if ($nyarlathotep_p);
+ print STDOUT $text_literal;
+ print STDOUT "\n" unless ($text_literal =~ m/\n$/s);
+
+ } elsif ($text_mode eq 'file') {
+
+ $text_file =~ s@^~/@$ENV{HOME}/@s; # allow literal "~/"
+
+ if (open (my $in, '<:raw', $text_file)) {
+ print STDERR "$progname: reading $text_file\n" if ($verbose);
+ binmode (STDOUT, ':raw');
+
+ if (($wrap_columns && $wrap_columns > 0) || $truncate_lines) {
+ # read it, then reformat it.
+ local $/ = undef; # read entire file
+ my $body = <$in>;
+ $body = reformat_text ($body);
+ print STDOUT $body;
+ } else {
+ # stream it by lines
+ while (<$in>) {
+ $_ = utf8_to_latin1($_) if ($latin1_p);
+ y/A-Za-z/N-ZA-Mn-za-m/ if ($nyarlathotep_p);
+ print STDOUT $_;
+ }
+ }
+ close $in;
+ } else {
+ error ("$text_file: $!");
+ }
+
+ } elsif ($text_mode eq 'program') {
+
+ my ($prog, $args) = ($text_program =~ m/^([^\s]+)(.*)$/);
+ $text_program = which ($prog) . $args;
+ print STDERR "$progname: running $text_program\n" if ($verbose);
+
+ if (($wrap_columns && $wrap_columns > 0) || $truncate_lines) {
+ # read it, then reformat it.
+ my $lines = 0;
+ my $body = "";
+ my $cmd = "( $text_program ) 2>&1";
+ # $cmd .= " | sed -l"; # line buffer instead of 4k pipe buffer
+ open (my $pipe, '-|:unix', $cmd);
+ while (my $line = <$pipe>) {
+ $body .= $line;
+ $lines++;
+ last if ($truncate_lines && $lines > $truncate_lines);
+ }
+ close $pipe;
+ $body = reformat_text ($body);
+ print STDOUT $body;
+ } else {
+ # stream it
+ safe_system ("$text_program");
+ }
+
+ } elsif ($text_mode eq 'url') {
+
+ get_url_text ($text_url);
+
+ } else { # $text_mode eq 'date'
+
+ my $n = `uname -n`;
+ $n =~ s/\.local\n/\n/s;
+ print $n;
+
+ my $unamep = 1;
+
+ if (-f "/etc/redhat-release") { # "Fedora Core release 4 (Stentz)"
+ safe_system ("cat", "/etc/redhat-release");
+ }
+
+ if (-f "/etc/release") { # "Solaris 10 3/05 s10_74L2a X86"
+ safe_system ("head", "-1", "/etc/release");
+ }
+
+ if (-f "/usr/sbin/system_profiler") { # "Mac OS X 10.4.5 (8H14)"
+ my $sp = # "iMac G5"
+ `/usr/sbin/system_profiler SPSoftwareDataType SPHardwareDataType 2>/dev/null`;
+ # system_profiler on OS X 10.10 generates spurious error messages.
+ my ($v) = ($sp =~ m/^\s*System Version:\s*(.*)$/mi);
+ my ($s) = ($sp =~ m/^\s*(?:CPU|Processor) Speed:\s*(.*)$/mi);
+ my ($t) = ($sp =~ m/^\s*(?:Machine|Model) Name:\s*(.*)$/mi);
+ print "$v\n" if ($v);
+ print "$s $t\n" if ($s && $t);
+ $unamep = !defined ($v);
+ }
+
+ if ($unamep) {
+ safe_system ("uname", "-sr"); # "Linux 2.6.15-1.1831_FC4"
+ }
+
+ print "\n";
+ safe_system ("date", "+%c");
+ print "\n";
+ my $ut = `uptime`;
+ $ut =~ s/^[ \d:]*(am|pm)?//i;
+ $ut =~ s/,\s*(load)/\n$1/;
+ print "$ut\n";
+ }
+
+}
+
+
+# Make an educated guess as to what's in this document.
+# We don't necessarily take the Content-Type header at face value.
+# Returns 'html', 'rss', or 'text';
+#
+sub guess_content_type($$) {
+ my ($ct, $body) = @_;
+
+ $body =~ s/^(.{512}).*/$1/s; # only look in first half K of file
+
+ if ($ct =~ m@^text/.*html@i) { return 'html'; }
+ if ($ct =~ m@\b(atom|rss|xml)\b@i) { return 'rss'; }
+
+ if ($body =~ m@^\s*<\?xml@is) { return 'rss'; }
+ if ($body =~ m@^\s*<!DOCTYPE RSS@is) { return 'rss'; }
+ if ($body =~ m@^\s*<!DOCTYPE HTML@is) { return 'html'; }
+
+ if ($body =~ m@<(BASE|HTML|HEAD|BODY|SCRIPT|STYLE|TABLE|A\s+HREF)\b@i) {
+ return 'html';
+ }
+
+ if ($body =~ m@<(RSS|CHANNEL|GENERATOR|DESCRIPTION|CONTENT|FEED|ENTRY)\b@i) {
+ return 'rss';
+ }
+
+ return 'text';
+}
+
+
+sub reformat_html($$) {
+ my ($body, $rss_p) = @_;
+ $_ = $body;
+
+ # In HTML, try to preserve newlines inside of PRE.
+ #
+ if (! $rss_p) {
+ s@(<PRE\b[^<>]*>\s*)(.*?)(</PRE)@{
+ my ($a, $b, $c) = ($1, $2, $3);
+ $b =~ s/[\r\n]/<BR>/gs;
+ $a . $b . $c;
+ }@gsexi;
+ }
+
+ if (! $rss_p) {
+ # In HTML, unfold lines.
+ # In RSS, assume \n means literal line break.
+ s@[\r\n]@ @gsi;
+ }
+
+ # This right here is the part where I doom us all to inhuman
+ # toil for the One whose Name cannot be expressed in the
+ # Basic Multilingual Plane. http://jwz.org/b/yhAT He comes.
+
+ s@<!--.*?-->@@gsi; # lose comments
+ s@<(STYLE|SCRIPT)\b[^<>]*>.*?</\1\s*>@@gsi; # lose css and js
+
+ s@</?(BR|TR|TD|LI|DIV)\b[^<>]*>@\n@gsi; # line break at BR, TD, DIV, etc
+ s@</?(P|UL|OL|BLOCKQUOTE)\b[^<>]*>@\n\n@gsi; # two line breaks
+
+ s@<lj\s+user=\"?([^<>\"]+)\"?[^<>]*>?@$1@gsi; # handle <LJ USER=>
+ s@</?[BI]>@*@gsi; # bold, italic => asterisks
+
+
+ s@<[^<>]*>?@@gs; # lose all other HTML tags
+ $_ = de_entify ($_); # convert HTML entities
+
+ # For Wikipedia: delete anything inside {{ }} and unwrap [[tags]],
+ # among other things.
+ #
+ if ($rss_p eq 'wiki') {
+
+ s@<!--.*?-->@@gsi; # lose HTML comments again
+
+ # Creation line is often truncated: screws up parsing with unbalanced {{.
+ s@(: +[^a-zA-Z ]* *Created page) with [^\n]+@$1@s;
+
+ s@/\*.*?\*/@@si; # /* ... */
+
+ # Try to omit all tables, since they're impossible to read as text.
+ #
+ 1 while (s/\{\{[^{}]*}}/ /gs); # {{ ... }}
+ 1 while (s/\{\|.*?\|\}/\n\n/gs); # {| ... |}
+ 1 while (s/\|-.*?\|/ /gs); # |- ... | (table cell)
+
+ # Convert anchors to something more readable.
+ #
+ s/\[\[([^\[\]\|]+)\|([^\[\]]+)\]\]/$2/gs; # [[link|anchor]]
+ s/\[\[([^:\[\]\|]+)\]\]/$1/gs; # [[anchor]]
+ s/\[https?:[^\[\]\s]+\s+([^\[\]]+)\]/$1/gs; # [url anchor]
+
+ # Convert all references to asterisks.
+ s@\s*<ref>\s*.*?</ref>@*@gs; # <ref> ... <ref> -> "*"
+ s@\n[ \t]*\d+\s*\^\s*http[^\s]+[ \t]*\n@\n@gs; # 1 ^ URL (a Reflist)
+
+ s@\[\[File:([^\|\]]+).*?\]\]@\n$1\n@gs; # [[File: X | ... ]]
+ s@\[\[Category:.*?\]\]@@gs; # omit categories
+
+ s/<[^<>]*>//gs; # Omit all remaining tags
+ s/\'{3,}//gs; # Omit ''' and ''''
+ s/\'\'/\"/gs; # '' -> "
+ s/\`\`/\"/gs; # `` -> "
+ s/\"\"+/\"/gs; # "" -> "
+
+ s/^[ \t]*[*#]+[ \t]*$//gm; # Omit lines with just * or # on them
+
+ # Omit trailing headlines with no text after them (e.g. == Notes ==)
+ 1 while (s/\n==+[ \t]*[^\n=]+[ \t]*==+\s*$/\n/s);
+
+ $_ = de_entify ($_); # convert HTML entities, again
+ }
+
+
+ # elide any remaining non-Latin1 binary data.
+ if ($latin1_p) {
+ utf8::encode ($_); # Unpack Unicode back to multi-byte UTF-8.
+ s/([^\000-\176]+(\s*[^\000-\176]+)[^a-z\d]*)/\xAB...\xBB /g;
+ }
+
+ $_ .= "\n";
+
+ s/[ \t]+$//gm; # lose whitespace at end of line
+ s@\n\n\n+@\n\n@gs; # compress blank lines
+
+ if (!defined($wrap_columns) || $wrap_columns > 0) {
+ $Text::Wrap::columns = ($wrap_columns || 72);
+ $Text::Wrap::break = '[\s/|]'; # wrap on slashes for URLs
+ $_ = wrap ("", " ", $_); # wrap the lines as a paragraph
+ s/[ \t]+$//gm; # lose whitespace at end of line again
+ }
+
+ s/^\n+//gs;
+
+ if ($truncate_lines) {
+ s/^(([^\n]*\n){$truncate_lines}).*$/$1/s;
+ }
+
+ $_ = utf8_to_latin1($_) if ($latin1_p);
+ y/A-Za-z/N-ZA-Mn-za-m/ if ($nyarlathotep_p);
+
+ return $_;
+}
+
+
+sub reformat_rss($) {
+ my ($body) = @_;
+
+ my $wiki_p = ($body =~ m@<generator>[^<>]*Wiki@si);
+
+ $body =~ s/(<(ITEM|ENTRY)\b)/\001\001$1/gsi;
+ my @items = split (/\001\001/, $body);
+
+ print STDERR "$progname: converting RSS ($#items items)...\n"
+ if ($verbose > 2);
+
+ shift @items;
+
+ # Let's skip forward in the stream by a random amount, so that if
+ # two copies of ljlatest are running at the same time (e.g., on a
+ # multi-headed machine), they get different text. (Put the items
+ # that we take off the front back on the back.)
+ #
+ if ($#items > 7) {
+ my $n = int (rand ($#items - 5));
+ print STDERR "$progname: rotating by $n items...\n" if ($verbose > 2);
+ while ($n-- > 0) {
+ push @items, (shift @items);
+ }
+ }
+
+ my $out = '';
+
+ my $i = -1;
+ foreach (@items) {
+ $i++;
+
+ my ($title, $body1, $body2, $body3);
+
+ $title = $3 if (m@<((TITLE) [^<>\s]*)[^<>]*>\s*(.*?)\s*</\1>@xsi);
+ $body1 = $3 if (m@<((DESCRIPTION) [^<>\s]*)[^<>]*>\s*(.*?)\s*</\1>@xsi);
+ $body2 = $3 if (m@<((CONTENT) [^<>\s]*)[^<>]*>\s*(.*?)\s*</\1>@xsi);
+ $body3 = $3 if (m@<((SUMMARY) [^<>\s]*)[^<>]*>\s*(.*?)\s*</\1>@xsi);
+
+ # If there are both <description> and <content> or <content:encoded>,
+ # use whichever one contains more text.
+ #
+ if ($body3 && length($body3) >= length($body2 || '')) {
+ $body2 = $body3;
+ }
+ if ($body2 && length($body2) >= length($body1 || '')) {
+ $body1 = $body2;
+ }
+
+ if (! $body1) {
+ if ($title) {
+ print STDERR "$progname: no body in item $i (\"$title\")\n"
+ if ($verbose > 2);
+ } else {
+ print STDERR "$progname: no body or title in item $i\n"
+ if ($verbose > 2);
+ next;
+ }
+ }
+
+ $title = rss_field_to_html ($title || '');
+ $body1 = rss_field_to_html ($body1 || '');
+
+ $title = '' if ($body1 eq $title); # Identical in Twitter's atom feed.
+
+ $out .= reformat_html ("$title<P>$body1", $wiki_p ? 'wiki' : 'rss');
+ $out .= "\n";
+ }
+
+ if ($truncate_lines) {
+ $out =~ s/^(([^\n]*\n){$truncate_lines}).*$/$1/s;
+ }
+
+ return $out;
+}
+
+
+sub rss_field_to_html($) {
+ my ($body) = @_;
+
+ # If <![CDATA[...]]> is present, everything inside that is HTML,
+ # and not double-encoded.
+ #
+ if ($body =~ m/^\s*<!\[CDATA\[(.*?)\]\s*\]/is) {
+ $body = $1;
+ } else {
+ $body = de_entify ($body); # convert entities to get HTML from XML
+ }
+
+ return $body;
+}
+
+
+sub reformat_text($) {
+ my ($body) = @_;
+
+ # only re-wrap if --cols was specified. Otherwise, dump it as is.
+ #
+ if ($wrap_columns && $wrap_columns > 0) {
+ print STDERR "$progname: wrapping at $wrap_columns...\n" if ($verbose > 2);
+ $Text::Wrap::columns = $wrap_columns;
+ $Text::Wrap::break = '[\s/]'; # wrap on slashes for URLs
+ $body = wrap ("", "", $body);
+ $body =~ s/[ \t]+$//gm;
+ }
+
+ if ($truncate_lines) {
+ $body =~ s/^(([^\n]*\n){$truncate_lines}).*$/$1/s;
+ }
+
+ $body = utf8_to_latin1($body) if ($latin1_p);
+ $body =~ y/A-Za-z/N-ZA-Mn-za-m/ if ($nyarlathotep_p);
+ return $body;
+}
+
+
+# Figure out what the proxy server should be, either from environment
+# variables or by parsing the output of the (MacOS) program "scutil",
+# which tells us what the system-wide proxy settings are.
+#
+sub set_proxy($) {
+ my ($ua) = @_;
+
+ my $proxy_data = `scutil --proxy 2>/dev/null`;
+ foreach my $proto ('http', 'https') {
+ my ($server) = ($proxy_data =~ m/\b${proto}Proxy\s*:\s*([^\s]+)/si);
+ my ($port) = ($proxy_data =~ m/\b${proto}Port\s*:\s*([^\s]+)/si);
+ my ($enable) = ($proxy_data =~ m/\b${proto}Enable\s*:\s*([^\s]+)/si);
+
+ if ($server && $enable) {
+ # Note: this ignores the "ExceptionsList".
+ my $proto2 = 'http';
+ $ENV{"${proto}_proxy"} = ("${proto2}://" . $server .
+ ($port ? ":$port" : "") . "/");
+ print STDERR "$progname: MacOS $proto proxy: " .
+ $ENV{"${proto}_proxy"} . "\n"
+ if ($verbose > 2);
+ }
+ }
+
+ $ua->env_proxy();
+}
+
+
+sub get_url_text($) {
+ my ($url) = @_;
+
+ my $ua = eval 'LWP::UserAgent->new';
+
+ if (! $ua) {
+ print STDOUT ("\n\tPerl is broken. Do this to repair it:\n" .
+ "\n\tsudo cpan LWP::UserAgent" .
+ " LWP::Protocol::https Mozilla::CA\n\n");
+ return;
+ }
+
+ # Half the time, random Linux systems don't have Mozilla::CA installed,
+ # which results in "Can't verify SSL peers without knowning which
+ # Certificate Authorities to trust".
+ #
+ # I'm going to take a controversial stand here and say that, for the
+ # purposes of plain-text being displayed in a screen saver via RSS,
+ # the chances of a certificate-based man-in-the-middle attack having
+ # a malicious effect on anyone anywhere at any time is so close to
+ # zero that it can be discounted. So, just don't bother validating
+ # SSL connections.
+ #
+ $ENV{'PERL_LWP_SSL_VERIFY_HOSTNAME'} = 0;
+ eval {
+ $ua->ssl_opts (verify_hostname => 0, SSL_verify_mode => 0);
+ };
+
+
+ set_proxy ($ua);
+ $ua->agent ("$progname/$version");
+ my $res = $ua->get ($url);
+ my $body;
+ my $ct;
+
+ if ($res && $res->is_success) {
+ $body = $res->decoded_content || '';
+ $ct = $res->header ('Content-Type') || 'text/plain';
+
+ } else {
+ my $err = ($res ? $res->status_line : '') || '';
+ $err = 'unknown error' unless $err;
+ $err = "$url: $err";
+ # error ($err);
+ $body = "Error loading URL $err\n\n";
+ $ct = 'text/plain';
+ }
+
+ # This is not necessary, since HTTP::Message::decoded_content() has
+ # already done 'decode (<charset-header>, $body)'.
+ # utf8::decode ($body); # Pack multi-byte UTF-8 back into wide chars.
+
+ $ct = guess_content_type ($ct, $body);
+ if ($ct eq 'html') {
+ print STDERR "$progname: converting HTML...\n" if ($verbose > 2);
+ $body = reformat_html ($body, 0);
+ } elsif ($ct eq 'rss') {
+ $body = reformat_rss ($body);
+ } else {
+ print STDERR "$progname: plain text...\n" if ($verbose > 2);
+ $body = reformat_text ($body);
+ }
+ print STDOUT $body;
+}
+
+
+
+sub error($) {
+ my ($err) = @_;
+ print STDERR "$progname: $err\n";
+ exit 1;
+}
+
+sub usage() {
+ print STDERR "usage: $progname [ --options ... ]\n" .
+ ("\n" .
+ " Prints out some text for use by various screensavers,\n" .
+ " according to the options in the ~/.xscreensaver file.\n" .
+ " This may dump the contents of a file, run a program,\n" .
+ " or load a URL.\n".
+ "\n" .
+ " Options:\n" .
+ "\n" .
+ " --date Print the host name and current time.\n" .
+ "\n" .
+ " --text STRING Print out the given text. It may contain %\n" .
+ " escape sequences as per strftime(2).\n" .
+ "\n" .
+ " --file PATH Print the contents of the given file.\n" .
+ " If --cols is specified, re-wrap the lines;\n" .
+ " otherwise, print them as-is.\n" .
+ "\n" .
+ " --program CMD Run the given program and print its output.\n" .
+ " If --cols is specified, re-wrap the output.\n" .
+ "\n" .
+ " --url HTTP-URL Download and print the contents of the HTTP\n" .
+ " document. If it contains HTML, RSS, or Atom,\n" .
+ " it will be converted to plain-text.\n" .
+ "\n" .
+ " --cols N Wrap lines at this column. Default 72.\n" .
+ "\n" .
+ " --lines N No more than N lines of output.\n" .
+ "\n" .
+ " --latin1 Emit Latin1 instead of UTF-8.\n" .
+ "\n");
+ exit 1;
+}
+
+sub main() {
+
+ my $load_p = 1;
+ my $cocoa_id = undef;
+
+ while ($#ARGV >= 0) {
+ $_ = shift @ARGV;
+ if ($_ eq "--verbose") { $verbose++; }
+ elsif (m/^-v+$/) { $verbose += length($_)-1; }
+ elsif (m/^--?date$/) { $text_mode = 'date';
+ $load_p = 0; }
+ elsif (m/^--?text$/) { $text_mode = 'literal';
+ $text_literal = shift @ARGV || '';
+ $text_literal =~ s@\\n@\n@gs;
+ $text_literal =~ s@\\\n@\n@gs;
+ $load_p = 0; }
+ elsif (m/^--?file$/) { $text_mode = 'file';
+ $text_file = shift @ARGV || '';
+ $load_p = 0; }
+ elsif (m/^--?program$/) { $text_mode = 'program';
+ $text_program = shift @ARGV || '';
+ $load_p = 0; }
+ elsif (m/^--?url$/) { $text_mode = 'url';
+ $text_url = shift @ARGV || '';
+ $load_p = 0; }
+ elsif (m/^--?col(umn)?s?$/) { $wrap_columns = 0 + shift @ARGV; }
+ elsif (m/^--?lines?$/) { $truncate_lines = 0 + shift @ARGV; }
+ elsif (m/^--?cocoa$/) { $cocoa_id = shift @ARGV; }
+ elsif (m/^--?latin1$/) { $latin1_p++; }
+ elsif (m/^--?nyarlathotep$/) { $nyarlathotep_p++; }
+ elsif (m/^-./) { usage; }
+ else { usage; }
+ }
+
+ if ($load_p) {
+
+ if (!defined ($cocoa_id)) {
+ # see OSX/XScreenSaverView.m
+ $cocoa_id = $ENV{XSCREENSAVER_CLASSPATH};
+ }
+
+ if (defined ($cocoa_id)) {
+ get_cocoa_prefs($cocoa_id);
+ } else {
+ get_x11_prefs();
+ }
+ }
+
+ output();
+
+
+ if (defined ($cocoa_id)) {
+ #
+ # On MacOS, sleep for 10 seconds between when the last output is
+ # printed, and when this process exits. This is because MacOS
+ # 10.5.0 and later broke ptys in a new and exciting way: basically,
+ # once the process at the end of the pty exits, you have exactly
+ # 1 second to read all the queued data off the pipe before it is
+ # summarily flushed.
+ #
+ # Many of the screen savers were written to depend on being able
+ # to read a small number of bytes, and continue reading until they
+ # reached EOF. This is no longer possible.
+ #
+ # Note that the current MacOS behavior has all four of these
+ # awesome properties: 1) Inconvenient; 2) Has no sane workaround;
+ # 3) Different behavior than MacOS 10.1 through 10.4; and 4)
+ # Different behavior than every other Unix in the world.
+ #
+ # See http://jwz.org/b/DHke, and for those of you inside Apple,
+ # "Problem ID 5606018".
+ #
+ # One workaround would be to rewrite the savers to have an
+ # internal buffer, and always read as much data as possible as
+ # soon as a pipe has input available. However, that's a lot more
+ # work, so instead, let's just not exit right away, and hope that
+ # 10 seconds is enough.
+ #
+ # This will solve the problem for invocations of xscreensaver-text
+ # that produce little output (e.g., date-mode); and won't solve it
+ # in cases where a large amount of text is generated in a short
+ # amount of time (e.g., url-mode.)
+ #
+ sleep (10);
+ }
+}
+
+main();
+exit 0;
diff --git a/driver/xscreensaver-text.man b/driver/xscreensaver-text.man
new file mode 100644
index 0000000..dcedc3b
--- /dev/null
+++ b/driver/xscreensaver-text.man
@@ -0,0 +1,85 @@
+.TH XScreenSaver 1 "20-Mar-2005 (4.21)" "X Version 11"
+.SH NAME
+xscreensaver\-text - prints some text to stdout, for use by screen savers.
+.SH SYNOPSIS
+.B xscreensaver\-text
+[\--verbose]
+[\--columns \fIN\fP]
+[\--text \fISTRING\fP]
+[\--file \fIPATH\fP]
+[\--program \fICMD\fP]
+[\--url \fIURL\fP]
+.SH DESCRIPTION
+The \fIxscreensaver\-text\fP script prints out some text for use by
+various screensavers, according to the options set in the ~/.xscreensaver
+file. This may dump the contents of a file, run a program, or load a URL.
+.SH OPTIONS
+.I xscreensaver\-text
+accepts the following options:
+.TP 8
+.B \-\-columns \fIN\fP or \-\-cols \fIN\fP
+Where to wrap lines; default 72 columns.
+.TP 8
+.B \-\-verbose \fRor\fP \-v
+Print diagnostics to stderr. Multiple \fI-v\fP switches increase the
+amount of output.
+.PP
+Command line options may be used to override the settings in the
+~/.xscreensaver file:
+.TP 8
+.B \-\-string \fISTRING\fP
+Print the given string. It may contain % escape sequences as per
+.BR strftime (2).
+.TP 8
+.B \-\-file \fIPATH\fP
+Print the contents of the given file. If --cols is specified, re-wrap
+the lines; otherwise, print them as-is.
+.TP 8
+.B \-\-program \fICMD\fP
+Run the given program and print its output. If --cols is specified,
+re-wrap the output.
+.TP 8
+.B \-\-url \fIHTTP-URL\fP
+Download and print the contents of the HTTP document. If it contains
+HTML, RSS, or Atom, it will be converted to plain-text.
+
+Note: this re-downloads the document every time it is run! It might
+be considered abusive for you to point this at a web server that you
+do not control!
+.SH ENVIRONMENT
+.PP
+.TP 4
+.B HTTP_PROXY\fR or \fPhttp_proxy
+to get the default HTTP proxy host and port.
+.SH BUGS
+The RSS and Atom output is always ISO-8859-1, regardless of locale.
+
+URLs should be cached, use "If-Modified-Since", and obey "Expires".
+.SH SEE ALSO
+.BR xscreensaver-demo (1),
+.BR xscreensaver (1),
+.BR fortune (1),
+.BR phosphor (MANSUFFIX),
+.BR apple2 (MANSUFFIX),
+.BR starwars (MANSUFFIX),
+.BR fontglide (MANSUFFIX),
+.BR dadadodo (1),
+.BR webcollage (MANSUFFIX),
+.RS 0
+.I http://www.livejournal.com/stats/latest-rss.bml,
+.RS 0
+.I https://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss,
+.RS 0
+.BR driftnet (1),
+.BR EtherPEG ,
+.BR EtherPeek
+.SH COPYRIGHT
+Copyright \(co 2005 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>, 20-Mar-2005.
diff --git a/driver/xscreensaver.c b/driver/xscreensaver.c
new file mode 100644
index 0000000..f5f65dc
--- /dev/null
+++ b/driver/xscreensaver.c
@@ -0,0 +1,2463 @@
+/* xscreensaver, Copyright (c) 1991-2018 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.
+ */
+
+/* ========================================================================
+ * 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.
+ *
+ * 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.)
+ *
+ * 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.)
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * On multi-screen systems, we do the above on each screen, and start
+ * multiple programs, each with a different value of $DISPLAY.
+ *
+ * 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.
+ *
+ * (See comments in screens.c for more details about Xinerama/RANDR stuff.)
+ *
+ * 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.
+ *
+ * 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".
+ *
+ * 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.
+ *
+ * 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.)
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.)
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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."
+ *
+ * ======================================================================== */
+
+#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 <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>
+#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 */
+
+#ifdef HAVE_READ_DISPLAY_EXTENSION
+# include <X11/extensions/readdisplay.h>
+#endif /* HAVE_READ_DISPLAY_EXTENSION */
+
+#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_XF86MISCSETGRABKEYSSTATE
+# include <X11/extensions/xf86misc.h>
+#endif /* HAVE_XF86MISCSETGRABKEYSSTATE */
+
+#ifdef HAVE_XINERAMA
+# include <X11/extensions/Xinerama.h>
+#endif /* HAVE_XINERAMA */
+
+#ifdef HAVE_RANDR
+# include <X11/extensions/Xrandr.h>
+#endif /* HAVE_RANDR */
+
+
+#include "xscreensaver.h"
+#include "version.h"
+#include "yarandom.h"
+#include "resources.h"
+#include "visual.h"
+#include "usleep.h"
+#include "auth.h"
+
+saver_info *global_si_kludge = 0; /* I hate C so much... */
+
+char *progname = 0;
+char *progclass = 0;
+XrmDatabase db = 0;
+
+
+static Atom XA_SCREENSAVER_RESPONSE;
+static Atom XA_ACTIVATE, XA_DEACTIVATE, 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 XrmOptionDescRec options [] = {
+
+ { "-verbose", ".verbose", XrmoptionNoArg, "on" },
+ { "-silent", ".verbose", XrmoptionNoArg, "off" },
+
+ /* xscreensaver-demo uses this one */
+ { "-nosplash", ".splash", XrmoptionNoArg, "off" },
+ { "-no-splash", ".splash", XrmoptionNoArg, "off" },
+
+ /* useful for debugging */
+ { "-no-capture-stderr", ".captureStderr", XrmoptionNoArg, "off" },
+ { "-log", ".logFile", XrmoptionSepArg, 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 *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)
+{
+ 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);
+}
+
+
+Bool in_signal_handler_p = 0; /* I hate C so much... */
+
+char *
+timestring (time_t when)
+{
+ 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
+ {
+ 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;
+ }
+}
+
+static Bool blurb_timestamp_p = True; /* kludge */
+
+const char *
+blurb (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;
+ }
+}
+
+
+int
+saver_ehandler (Display *dpy, XErrorEvent *error)
+{
+ saver_info *si = global_si_kludge; /* I hate C so much... */
+ int i;
+ Bool fatal_p;
+
+ if (!real_stderr) real_stderr = stderr;
+
+ fprintf (real_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 (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);
+ }
+
+ fprintf (real_stderr, "\n"
+ "#######################################"
+ "#######################################\n\n");
+
+ 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.
+ */
+
+ fprintf (real_stderr, "\n");
+
+ if (! fatal_p)
+ fprintf (real_stderr, "%s: nonfatal error.\n\n", blurb());
+ else
+ {
+ if (si->prefs.xsync_p)
+ {
+ saver_exit (si, -1, "because of synchronous X Error");
+ }
+ 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");
+
+ saver_exit (si, -1, 0);
+ }
+ }
+
+ return 0;
+}
+
+
+/* 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."
+ */
+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)
+{
+ 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
+ {
+ 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, "\n"
+ " https://www.jwz.org/xscreensaver/faq.html\n"
+ " https://www.jwz.org/xscreensaver/man.html\n"
+ "\n");
+
+ fflush (stderr);
+ fflush (stdout);
+ exit (1);
+}
+
+
+/* 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)
+{
+ progclass = "XScreenSaver";
+
+ /* progname is reset later, after we connect to X. */
+ progname = strrchr(argv[0], '/');
+ if (progname) progname++;
+ else progname = argv[0];
+
+ if (strlen(progname) > 100) /* keep it short. */
+ progname[99] = 0;
+
+ /* 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;
+}
+
+
+/* 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.
+ */
+static void
+privileged_initialization (saver_info *si, int *argc, char **argv)
+{
+#ifndef NO_LOCKING
+ /* before hack_uid() for proper permissions */
+ lock_priv_init (*argc, argv, si->prefs.verbose_p);
+#endif /* NO_LOCKING */
+
+ hack_uid (si);
+}
+
+
+/* Figure out what locking mechanisms are supported.
+ */
+static void
+lock_initialization (saver_info *si, int *argc, char **argv)
+{
+#ifdef NO_LOCKING
+ si->locking_disabled_p = True;
+ si->nolock_reason = "not compiled with locking support";
+#else /* !NO_LOCKING */
+
+ /* Finish initializing locking, now that we're out of privileged code. */
+ if (! lock_init (*argc, argv, si->prefs.verbose_p))
+ {
+ si->locking_disabled_p = True;
+ si->nolock_reason = "error getting password";
+ }
+
+ /* 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"))
+ {
+ si->locking_disabled_p = True;
+ si->nolock_reason = "running under GDM";
+ }
+
+ /* 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);
+
+ if (macos_p)
+ {
+ si->locking_disabled_p = True;
+ si->nolock_reason = "Cannot lock securely on MacOS X";
+ }
+ }
+
+ /* 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"))
+ {
+ si->locking_disabled_p = True;
+ si->nolock_reason = "Cannot lock securely under Wayland";
+ }
+
+ 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.
+ */
+static Widget
+connect_to_server (saver_info *si, int *argc, char **argv)
+{
+ Widget toplevel_shell;
+
+#ifdef HAVE_PUTENV
+ char *d = getenv ("DISPLAY");
+ if (!d || !*d)
+ {
+ 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.
+ */
+ }
+#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_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;
+}
+
+
+/* Handle the command-line arguments that were not handled for us by Xt.
+ Issue an error message and exit if there are unknown options.
+ */
+static void
+process_command_line (saver_info *si, int *argc, char **argv)
+{
+ int i;
+ for (i = 1; i < *argc; i++)
+ {
+ if (!strcmp (argv[i], "-debug"))
+ /* no resource for this one, out of paranoia. */
+ si->prefs.debug_p = True;
+
+ else if (!strcmp (argv[i], "-h") ||
+ !strcmp (argv[i], "-help") ||
+ !strcmp (argv[i], "--help"))
+ do_help (si);
+
+ 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);
+ }
+ }
+}
+
+
+/* Print out the xscreensaver banner to the tty if applicable;
+ Issue any other warnings that are called for at this point.
+ */
+static void
+print_banner (saver_info *si)
+{
+ 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");
+
+ if (p->verbose_p)
+ fprintf (stderr,
+ "%s %s, copyright (c) 1991-%s "
+ "by Jamie Zawinski <jwz@jwz.org>.\n",
+ progname, si->version, year);
+
+ 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());
+ }
+}
+
+static void
+print_lock_failure_banner (saver_info *si)
+{
+ saver_preferences *p = &si->prefs;
+
+ /* 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.)
+ */
+ if (si->locking_disabled_p)
+ {
+ 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());
+ }
+
+}
+
+
+/* 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);
+}
+
+
+/* Examine all of the display's screens, and populate the `saver_screen_info'
+ structures. Make sure this is called after hack_environment() sets $PATH.
+ */
+static void
+initialize_per_screen_info (saver_info *si, Widget toplevel_shell)
+{
+ int i;
+
+ update_screen_layout (si);
+
+ /* 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++)
+ {
+ 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;
+ }
+ }
+
+#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
+ {
+ int ev, er;
+ server_has_xidle_extension_p = XidleQueryExtension (si->dpy, &ev, &er);
+ }
+#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
+
+ 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());
+ }
+
+ 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)
+ {
+ 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());
+ }
+
+#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 */
+
+#ifdef HAVE_XINPUT
+ if (!server_has_xinput_extension_p)
+ si->using_xinput_extension = False;
+ else
+ {
+ if (si->using_xinput_extension)
+ init_xinput_extension(si);
+
+ 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());
+ }
+}
+
+
+#ifdef DEBUG_MULTISCREEN
+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);
+ }
+ resize_screensaver_window (si);
+ }
+ XtAppAddTimeOut (si->app, 1000*4, debug_multiscreen_timer, (XtPointer) si);
+}
+#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.
+
+ If a server extension is being used, this does nothing.
+ */
+static void
+select_events (saver_info *si)
+{
+ saver_preferences *p = &si->prefs;
+ int i;
+
+ if (si->using_xidle_extension ||
+ si->using_mit_saver_extension ||
+ si->using_sgi_saver_extension)
+ return;
+
+ if (p->initial_delay)
+ {
+ 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");
+ }
+
+ if (p->verbose_p)
+ {
+ fprintf (stderr, "%s: selecting events on extant windows...", blurb());
+ fflush (stderr);
+ fflush (stdout);
+ }
+
+ /* 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 (p->verbose_p)
+ fprintf (stderr, " done.\n");
+
+# ifdef DEBUG_MULTISCREEN
+ if (p->debug_p) debug_multiscreen_timer ((XtPointer) si, 0);
+# 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 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);
+ }
+}
+
+
+/* Loop forever:
+
+ - wait until the user is idle;
+ - blank the screen;
+ - wait until the user is active;
+ - unblank the screen;
+ - repeat.
+
+ */
+static void
+main_loop (saver_info *si)
+{
+ saver_preferences *p = &si->prefs;
+ Bool ok_to_unblank;
+ int i;
+
+ while (1)
+ {
+ 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);
+
+ if (p->mode == DONT_BLANK)
+ {
+ if (p->verbose_p)
+ fprintf (stderr, "%s: idle with blanking disabled at %s.\n",
+ blurb(), timestring(0));
+
+ /* 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.
+
+ 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;
+ }
+
+ /* 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 (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;
+ }
+
+
+ /* 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]);
+
+ 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 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);
+ }
+
+ /* 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);
+
+
+#ifndef NO_LOCKING
+ /* Maybe start locking the screen.
+ */
+ {
+ Time lock_timeout = p->lock_timeout;
+
+ if (si->emergency_lock_p && p->lock_p && lock_timeout)
+ {
+ 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;
+ }
+
+ 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);
+
+ 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 */
+
+ } while (!ok_to_unblank);
+
+
+ if (p->verbose_p)
+ fprintf (stderr, "%s: unblanking screen at %s.\n",
+ blurb(), timestring (0));
+
+ /* 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);
+
+ set_locked_p (si, False);
+ si->emergency_lock_p = False;
+ si->demoing_p = 0;
+ si->selection_mode = 0;
+
+ /* 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 (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;
+ }
+
+ if (si->lock_id)
+ {
+ XtRemoveTimeOut (si->lock_id);
+ si->lock_id = 0;
+ }
+
+ /* 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)
+{
+ Widget shell;
+ saver_info the_si;
+ saver_info *si = &the_si;
+ saver_preferences *p = &si->prefs;
+ struct passwd *spasswd;
+ int i;
+
+ /* 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.
+
+ 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.
+
+ See https://bugs.launchpad.net/ubuntu/+source/xscreensaver/+bug/671923
+ from comment #20 onward.
+
+ -- jwz, 24-Sep-2011
+ */
+#ifdef ENABLE_NLS
+ if (!setlocale (LC_CTYPE, ""))
+ fprintf (stderr, "%s: warning: could not set default locale\n",
+ progname);
+
+ bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+ textdomain (GETTEXT_PACKAGE);
+#endif /* ENABLE_NLS */
+
+ memset(si, 0, sizeof(*si));
+ global_si_kludge = si; /* I hate C so much... */
+
+ fix_fds();
+
+# undef ya_rand_init
+ ya_rand_init (0);
+
+ save_argv (argc, argv);
+ set_version_string (si, &argc, argv);
+ privileged_initialization (si, &argc, argv);
+ hack_environment (si);
+
+ spasswd = getpwuid(getuid());
+ if (!spasswd)
+ {
+ fprintf(stderr, "Could not figure out who the current user is!\n");
+ return 1;
+ }
+
+ 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 */
+
+ shell = connect_to_server (si, &argc, argv);
+ process_command_line (si, &argc, argv);
+ stderr_log_file (si);
+ print_banner (si);
+
+ 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 */
+
+ /* 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());
+
+ 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);
+ }
+
+ lock_initialization (si, &argc, argv);
+ print_lock_failure_banner (si);
+
+ if (p->xsync_p) XSynchronize (si->dpy, True);
+
+ 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);
+
+ 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);
+
+ initialize_stderr (si);
+ handle_signals (si);
+
+ make_splash_dialog (si);
+
+ main_loop (si); /* doesn't return */
+ return 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.)
+
+ 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);
+}
+
+
+
+/* Processing ClientMessage events.
+ */
+
+
+static Bool error_handler_hit_p = False;
+
+static int
+ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
+{
+ error_handler_hit_p = True;
+ return 0;
+}
+
+/* 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;
+
+ if (result)
+ return result;
+ else
+ {
+ char buf[100];
+ sprintf (buf, "<<undefined atom 0x%04X>>", (unsigned int) atom);
+ return strdup (buf);
+ }
+}
+
+
+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);
+
+ XChangeProperty (si->dpy, w, XA_SCREENSAVER_RESPONSE, XA_STRING, 8,
+ PropModeReplace, (unsigned char *) proto, L);
+
+ XSync (si->dpy, False);
+ XSetErrorHandler (old_handler);
+ XSync (si->dpy, False);
+
+ free (proto);
+}
+
+
+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");
+ break;
+ }
+ else if (w == RootWindow (si->dpy, screen))
+ {
+ strcpy (wdesc, "root");
+ root_p = True;
+ 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.
+ */
+ if (root_p && !p->debug_p)
+ return;
+
+ if (!*wdesc)
+ {
+ 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: %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);
+
+#endif /* 0 */
+}
+
+
+Bool
+handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p)
+{
+ saver_preferences *p = &si->prefs;
+ Atom type = 0;
+ Window window = event->xclient.window;
+
+ /* Preferences might affect our handling of client messages. */
+ maybe_reload_init_file (si);
+
+ if (event->xclient.message_type != XA_SCREENSAVER ||
+ event->xclient.format != 32)
+ {
+ bogus_clientmessage_warning (si, event);
+ return False;
+ }
+
+ 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;
+ }
+
+ 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)
+ {
+# 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)
+ {
+ clientmessage_response(si, window, False,
+ "DEACTIVATE ClientMessage received while locked: ignored.",
+ "screen is locked.");
+ }
+ else
+# endif /* 0 */
+ {
+ 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);
+ }
+ }
+ 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)
+ {
+ clientmessage_response(si, window, True,
+ "SELECT ClientMessage received in DONT_BLANK mode.",
+ "screen blanking is currently disabled.");
+ return False;
+ }
+
+ 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);
+
+ /* 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);
+
+ /* Have to set the time or xscreensaver-command doesn't
+ report the LOCK state change. */
+ si->blank_time = time ((time_t *) 0);
+
+ 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);
+ }
+ }
+ }
+ else
+ {
+ 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]);
+ }
+
+ clientmessage_response (si, window, True, buf, buf);
+ }
+ return False;
+}
+
+
+/* Some random diagnostics printed in -verbose mode.
+ */
+
+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;
+
+ /* 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 (!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);
+ }
+
+ 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");
+ }
+ }
+
+ describe_monitor_layout (si);
+}
+
+
+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
+ {
+ 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)));
+ }
+ }
+ }
+ return !not_on_console;
+}
+
+
+/* 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 */
+}
diff --git a/driver/xscreensaver.h b/driver/xscreensaver.h
new file mode 100644
index 0000000..42cf794
--- /dev/null
+++ b/driver/xscreensaver.h
@@ -0,0 +1,210 @@
+/* xscreensaver, Copyright (c) 1993-2017 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_H__
+#define __XSCREENSAVER_H__
+
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include <string.h>
+#include <stdio.h>
+
+#ifdef HAVE_SIGACTION
+# include <signal.h> /* for sigset_t */
+#endif
+
+#include "prefs.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 void monitor_power_on (saver_info *si, Bool on_p);
+
+
+/* =======================================================================
+ blanking
+ ======================================================================= */
+
+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 unblank_screen (saver_info *si);
+extern void resize_screensaver_window (saver_info *si);
+
+extern void get_screen_viewport (saver_screen_info *ssi,
+ int *x_ret, int *y_ret,
+ int *w_ret, int *h_ret,
+ int target_x, int target_y,
+ Bool verbose_p);
+
+
+/* =======================================================================
+ 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 */
+ extern int block_sigchld (void);
+#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 spawn_screenhack (saver_screen_info *ssi);
+extern pid_t fork_and_exec (saver_screen_info *ssi, const char *command);
+extern void kill_screenhack (saver_screen_info *ssi);
+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 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);
+
+
+/* =======================================================================
+ 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;
+
+#endif /* __XSCREENSAVER_H__ */
diff --git a/driver/xscreensaver.man b/driver/xscreensaver.man
new file mode 100644
index 0000000..a99845b
--- /dev/null
+++ b/driver/xscreensaver.man
@@ -0,0 +1,1035 @@
+.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 "09-Nov-2013 (5.23)" "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]
+.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.
+
+It also provides configuration and control of your monitor's power-saving
+features.
+.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:
+
+.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
+
+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.
+
+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.)
+
+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
+The X display to use. For displays with multiple screens, XScreenSaver
+will manage all screens on the display simultaniously.
+.TP 8
+.B \-verbose
+Same as setting the \fIverbose\fP resource to \fItrue\fP: print diagnostics
+on stderr and on the xscreensaver window.
+.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.
+.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.
+
+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.
+
+If the power management section is grayed out in the
+.BR xscreensaver\-demo (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.
+
+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:
+.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
+.TP 3
+\fB2: Launch xscreensaver at login.\fP
+Select "\fIStartup Applications\fP" from the menu (or manually
+launch "\fIgnome-session-properties\fP") and add "\fIxscreensaver\fP".
+
+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 \\
+ /usr/bin/gnome-screensaver-command
+.EE
+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.)
+.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
+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:
+.RS 4
+.TP 3
+\fB1: Turn off KDE's screen saver.\fP
+Open the "\fIControl Center\fP" and
+select the "\fIAppearance & Themes / Screensaver\fP" page.
+Un-check "\fIStart Automatically\fP".
+
+Or possibly:
+Open "\fISystem Settings\fP" and
+select "\fIScreen Locking\fP".
+Un-check "\fILock Screen Automatically\fP".
+.TP 3
+\fB2: Find your Autostart directory.\fP
+Open the "\fISystem Administration / Paths\fP" page,
+and see what your "Autostart path" is set to: it will
+probably be something like \fI~/.kde/Autostart/\fP
+or \fI~/.config/autostart/\fP
+
+If that doesn't work, then try this:
+
+Open "\fISystem Settings / Startup/Shutdown / Autostart\fP", and then
+add "\fI/usr/bin/xscreensaver\fP".
+
+If you are lucky, that will create a \fIxscreensaver.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
+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
+.TP 3
+\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"
+or "\fIkscreenlocker_greet\fP", and
+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
+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
+If the above didn't do it, and your system has
+.BR systemd (1),
+then give this a try:
+.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
+.TP 3
+\fB2. Enable it.\fP
+.EX
+systemctl --user enable xscreensaver
+.EE
+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
+You can run \fIxscreensaver\fP from your
+.BR gdm (1)
+session, so that the screensaver will run even when nobody is logged
+in on the console. To do this, run
+.BR gdmconfig (1).
+
+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
+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.)
+
+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
+In this situation, the \fIxscreensaver\fP process will probably be running
+as user \fIgdm\fP instead of \fIroot\fP. You can configure the settings
+for this nobody-logged-in state (timeouts, DPMS, etc.) by editing
+the \fI~gdm/.xscreensaver\fP file.
+
+It is safe to run \fIxscreensaver\fP as root (as \fIxdm\fP or \fIgdm\fP may do).
+If run as root, \fIxscreensaver\fP changes its effective user and group ids
+to something safe (like \fI"nobody"\fP) before connecting to the X server
+or launching user-specified programs.
+
+An unfortunate side effect of this (important) security precaution is that
+it may conflict with cookie-based authentication.
+
+If you get "connection refused" errors when running \fIxscreensaver\fP
+from \fIgdm\fP, then this probably means that you have
+.BR xauth (1)
+or some other security mechanism turned on. For information on the
+X server's access control mechanisms, see the man pages for
+.BR X (1),
+.BR Xsecurity (1),
+.BR xauth (1),
+and
+.BR xhost (1).
+.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).
+
+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).
+
+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?)
+
+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
+a text console. If the user launched X11 manually, that text console
+will still be logged in. To disable this keystroke globally and
+permanently, you need to set the \fBDontZap\fP flag in your
+\fIxorg.conf\fP or \fIXF86Config\fP or \fIXF86Config-4\fP file,
+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
+leaving the console that X11 is running on locked. If you left a
+shell logged in on another virtual console, it is unprotected. So
+don't leave yourself logged in on other consoles. You can disable VT
+switching globally and permanently by setting \fBDontVTSwitch\fP in
+your \fIxorg.conf\fP, but that might make your system harder to use,
+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.
+.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
+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.
+.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).
+.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
+running graphics-hack sub-process will be killed (with \fBSIGTERM\fP), and a
+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.
+.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.
+.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,
+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
+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.
+.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
+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.
+.TP 8
+.B newLoginCommand\fP (class \fBNewLoginCommand\fP)
+If set, this is the shell command that is run when the "New Login" button
+is pressed on the unlock dialog box, in order to create a new desktop
+session without logging out the user who has locked the screen.
+Typically this will be some variant of
+.BR gdmflexiserver (1),
+.BR kdmctl (1),
+.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
+.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.
+.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.
+.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.
+.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)
+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
+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:
+.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)
+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 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.
+
+When the screensaver starts up, one of these is selected (according to
+the \fBmode\fP setting), and run. After the \fIcycle\fP period
+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)
+program).
+
+If all programs are disabled, then the screen will just be made blank,
+as when \fImode\fP is set to \fIblank\fP.
+
+To disable a program, you must mark it as disabled with a dash instead
+of removing it from the list. This is because the system-wide (app-defaults)
+and per-user (.xscreensaver) settings are merged together, and if a user
+just \fIdeletes\fP an entry from their programs list, but that entry still
+exists in the system-wide list, then it will come back. However, if the
+user \fIdisables\fP it, then their setting takes precedence.
+
+If the display has multiple screens, then a different program will be run
+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
+.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
+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.)
+
+.B Visuals:
+
+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.
+
+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
+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
+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:
+.RS 1
+.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.
+
+.RE
+If you specify a particular visual for a program, and that visual does not
+exist on the screen, then that program will not be chosen to run. This
+means that on displays with multiple screens of different depths, you can
+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.
+.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.
+.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.
+.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...)
+.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.
+.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).
+.TP 8
+.B overlayTextForeground\fP (class \fBForeground\fP)
+The foreground color used for the stdout/stderr text, if \fBcaptureStderr\fP
+is true. Default: Yellow.
+.TP 8
+.B overlayTextBackground\fP (class \fBBackground\fP)
+The background color used for the stdout/stderr text, if \fBcaptureStderr\fP
+is true. Default: Black.
+.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.
+.SH ENVIRONMENT
+.PP
+.TP 8
+.B DISPLAY
+to get the default host and display number, and to inform the sub-programs
+of the screen on which to draw.
+.TP 8
+.B XSCREENSAVER_WINDOW
+Passed to sub-programs to indicate the ID of the window on which they
+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.
+.TP 8
+.B HOME
+for the directory in which to read 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.
+.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 Xsecurity (1),
+.BR xauth (1),
+.BR xdm (1),
+.BR gdm (1),
+.BR xhost (1),
+.BR xscreensaver\-demo (1),
+.BR xscreensaver\-command (1),
+.BR xscreensaver\-gl\-helper (1),
+.BR xscreensaver\-getimage (1),
+.BR xscreensaver\-text (1).
+.SH COPYRIGHT
+Copyright \(co 1991-2018 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>. Written in late 1991; version 1.0 posted
+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!
diff --git a/driver/xscreensaver.pam.in b/driver/xscreensaver.pam.in
new file mode 100644
index 0000000..c18fa9f
--- /dev/null
+++ b/driver/xscreensaver.pam.in
@@ -0,0 +1,13 @@
+#%PAM-1.0
+
+# Fedora Core 5:
+auth include system-auth
+
+# SuSE 9.0: (along with "configure --with-passwd-helper" and "unix2_chkpwd")
+# auth required pam_unix2.so nullok
+
+# Distant past:
+# auth required /lib/security/pam_pwdb.so shadow nullok
+
+# Account validation
+@COMMENT_PAM_CHECK_ACCOUNT@account include system-auth
diff --git a/driver/xset.c b/driver/xset.c
new file mode 100644
index 0000000..a381429
--- /dev/null
+++ b/driver/xset.c
@@ -0,0 +1,389 @@
+/* xset.c --- interacting with server extensions and the builtin screensaver.
+ * xscreensaver, Copyright (c) 1991-2008 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 <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#include <X11/Xos.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"
+
+#ifdef _VROOT_H_
+ERROR! You must not include vroot.h in this file.
+#endif
+
+
+/* MIT SCREEN-SAVER server extension hackery.
+ */
+
+#ifdef HAVE_MIT_SAVER_EXTENSION
+
+# include <X11/extensions/scrnsaver.h>
+
+static int
+ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
+{
+ return 0;
+}
+
+static void
+init_mit_saver_extension (saver_info *si)
+{
+ int i;
+ Pixmap *blank_pix = (Pixmap *) calloc (sizeof(Pixmap), si->nscreens);
+
+ for (i = 0; i < si->nscreens; i++)
+ {
+ saver_screen_info *ssi = &si->screens[i];
+ XID kill_id = 0;
+ Atom kill_type = 0;
+ Window root = RootWindowOfScreen (ssi->screen);
+ blank_pix[i] = XCreatePixmap (si->dpy, root, 1, 1, 1);
+
+ /* Kill off the old MIT-SCREEN-SAVER client if there is one.
+ This tends to generate X errors, though (possibly due to a bug
+ in the server extension itself?) so just ignore errors here. */
+ if (XScreenSaverGetRegistered (si->dpy,
+ XScreenNumberOfScreen (ssi->screen),
+ &kill_id, &kill_type)
+ && kill_id != blank_pix[i])
+ {
+ XErrorHandler old_handler =
+ XSetErrorHandler (ignore_all_errors_ehandler);
+ XKillClient (si->dpy, kill_id);
+ XSync (si->dpy, False);
+ XSetErrorHandler (old_handler);
+ }
+ XScreenSaverSelectInput (si->dpy, root, ScreenSaverNotifyMask);
+ XScreenSaverRegister (si->dpy,
+ XScreenNumberOfScreen (ssi->screen),
+ (XID) blank_pix[i], XA_PIXMAP);
+ }
+ free(blank_pix);
+}
+#endif /* HAVE_MIT_SAVER_EXTENSION */
+
+
+#ifdef HAVE_XINPUT
+/* XInputExtension device support */
+
+Bool
+query_xinput_extension (saver_info *si)
+{
+ XExtCodes codes;
+ return XQueryExtension (si->dpy, INAME, &codes.major_opcode,
+ &codes.first_event, &codes.first_error);
+}
+
+void
+init_xinput_extension (saver_info *si)
+{
+ int i, ndevices;
+ int class;
+ XDeviceInfo *list;
+ XDevice *dev;
+ XAnyClassPtr pClass;
+ XEventClass *event_list;
+ int nevents = 0;
+
+ /* skip if already initialized */
+ if (si->num_xinput_devices && si->xinput_devices)
+ return;
+
+ si->num_xinput_devices = 0;
+
+ list = XListInputDevices (si->dpy, &ndevices);
+ if (list == NULL)
+ {
+ si->xinput_devices = NULL;
+ return;
+ }
+
+ /* We only care about 3 event types per device (DeviceButtonPress,
+ DeviceButtonRelease, and DeviceMotionNotify), hence the "* 3"
+ for the event count. */
+ event_list = calloc(ndevices * 3, sizeof(XEventClass));
+ if (event_list == NULL)
+ return;
+
+ si->xinput_devices = calloc(ndevices, sizeof(struct xinput_dev_info));
+ if (si->xinput_devices == NULL)
+ {
+ free(event_list);
+ return;
+ }
+
+ for (i = 0; i < ndevices; i++)
+ {
+ if ((list[i].use == IsXExtensionDevice)
+#ifdef IsXExtensionPointer
+ || (list[i].use == IsXExtensionPointer)
+#endif
+ )
+ {
+ struct xinput_dev_info *dev_info =
+ &si->xinput_devices[si->num_xinput_devices];
+ Bool device_we_want = False;
+
+ if (si->prefs.debug_p)
+ fprintf(stderr,
+ "Extension device #%2d: XID=%2d type=%3d name=\"%s\"\n",
+ i, (int) list[i].id, (int) list[i].type, list[i].name);
+
+ dev = XOpenDevice (si->dpy, list[i].id);
+ if (!dev)
+ continue;
+ dev_info->device = dev;
+
+ pClass = list[i].inputclassinfo;
+ for (class = 0; class < list[i].num_classes; class++)
+ {
+ switch (pClass->class)
+ {
+ case ButtonClass:
+ if (((XButtonInfo *) pClass)->num_buttons > 0)
+ {
+ /* Macros set values in the second & third arguments */
+ DeviceButtonPress (dev, si->xinput_DeviceButtonPress,
+ dev_info->press);
+ event_list[nevents++] = dev_info->press;
+
+ DeviceButtonRelease (dev, si->xinput_DeviceButtonRelease,
+ dev_info->release);
+ event_list[nevents++] = dev_info->release;
+ device_we_want = True;
+ }
+ break;
+
+ case ValuatorClass:
+ if (((XValuatorInfo *) pClass)->num_axes > 0)
+ {
+ DeviceMotionNotify (dev, si->xinput_DeviceMotionNotify,
+ dev_info->valuator);
+ event_list[nevents++] = dev_info->valuator;
+ device_we_want = True;
+ }
+ break;
+
+ default:
+ /* ignore other classes of devices/events */
+ break;
+ }
+
+ pClass = (XAnyClassPtr) & ((char *) pClass)[pClass->length];
+ }
+
+ if (device_we_want)
+ si->num_xinput_devices++;
+ else
+ XCloseDevice (si->dpy, dev);
+ }
+ }
+
+ if (list)
+ XFreeDeviceList (list);
+
+ if ((nevents == 0) || (si->num_xinput_devices == 0))
+ {
+ free(event_list);
+ free(si->xinput_devices);
+ si->xinput_devices = NULL;
+ si->num_xinput_devices = 0;
+ return;
+ }
+
+ for (i = 0; i < si->nscreens; i++)
+ {
+ saver_screen_info *ssi = &si->screens[i];
+ Window root = RootWindowOfScreen (ssi->screen);
+ XSelectExtensionEvent (si->dpy, root, event_list, nevents);
+ }
+
+ free(event_list);
+}
+
+#if 0
+/* not used */
+static void
+close_xinput_extension (saver_info *si)
+{
+ int i;
+
+ for (i = 0; i < si->num_xinput_devices; i++)
+ XCloseDevice (si->dpy, si->xinput_devices[i].device);
+
+ free(si->xinput_devices);
+ si->xinput_devices = NULL;
+ si->num_xinput_devices = 0;
+}
+#endif
+#endif /* HAVE_XINPUT */
+
+
+/* SGI SCREEN_SAVER server extension hackery.
+ */
+
+#ifdef HAVE_SGI_SAVER_EXTENSION
+
+# include <X11/extensions/XScreenSaver.h>
+
+static void
+init_sgi_saver_extension (saver_info *si)
+{
+ saver_preferences *p = &si->prefs;
+ int i;
+ if (si->screen_blanked_p)
+ /* If you mess with this while the server thinks it's active,
+ the server crashes. */
+ return;
+
+ for (i = 0; i < si->nscreens; i++)
+ {
+ saver_screen_info *ssi = &si->screens[i];
+ XScreenSaverDisable (si->dpy, XScreenNumberOfScreen(ssi->screen));
+ if (! XScreenSaverEnable (si->dpy, XScreenNumberOfScreen(ssi->screen)))
+ {
+ fprintf (stderr,
+ "%s: SGI SCREEN_SAVER extension exists, but can't be initialized;\n\
+ perhaps some other screensaver program is already running?\n",
+ blurb());
+ si->using_sgi_saver_extension = False;
+ return;
+ }
+ }
+}
+
+#endif /* HAVE_SGI_SAVER_EXTENSION */
+
+
+
+/* Figuring out what the appropriate XSetScreenSaver() parameters are
+ (one wouldn't expect this to be rocket science.)
+ */
+
+void
+disable_builtin_screensaver (saver_info *si, Bool unblank_screen_p)
+{
+ saver_preferences *p = &si->prefs;
+ int current_server_timeout, current_server_interval;
+ int current_prefer_blank, current_allow_exp;
+ int desired_server_timeout, desired_server_interval;
+ int desired_prefer_blank, desired_allow_exp;
+
+ XGetScreenSaver (si->dpy, &current_server_timeout, &current_server_interval,
+ &current_prefer_blank, &current_allow_exp);
+
+ desired_server_timeout = current_server_timeout;
+ desired_server_interval = current_server_interval;
+ desired_prefer_blank = current_prefer_blank;
+ desired_allow_exp = current_allow_exp;
+
+ /* On SGIs, if interval is non-zero, it is the number of seconds after
+ screen saving starts at which the monitor should be powered down.
+ Obviously I don't want that, so set it to 0 (meaning "never".)
+
+ Power saving is disabled if DontPreferBlanking, but in that case,
+ we don't get extension events either. So we can't turn it off that way.
+
+ Note: if you're running Irix 6.3 (O2), you may find that your monitor is
+ powering down anyway, regardless of the xset settings. This is fixed by
+ installing SGI patches 2447 and 2537.
+ */
+ desired_server_interval = 0;
+
+ /* I suspect (but am not sure) that DontAllowExposures might have
+ something to do with powering off the monitor as well, at least
+ on some systems that don't support XDPMS? Who knows... */
+ desired_allow_exp = AllowExposures;
+
+ if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
+ {
+ desired_server_timeout = (p->timeout / 1000);
+
+ /* The SGI extension won't give us events unless blanking is on.
+ I think (unsure right now) that the MIT extension is the opposite. */
+ if (si->using_sgi_saver_extension)
+ desired_prefer_blank = PreferBlanking;
+ else
+ desired_prefer_blank = DontPreferBlanking;
+ }
+ else
+ {
+ /* When we're not using an extension, set the server-side timeout to 0,
+ so that the server never gets involved with screen blanking, and we
+ do it all ourselves. (However, when we *are* using an extension,
+ we tell the server when to notify us, and rather than blanking the
+ screen, the server will send us an X event telling us to blank.)
+ */
+ desired_server_timeout = 0;
+ }
+
+ /* XSetScreenSaver() generates BadValue if either timeout parameter
+ exceeds 15 bits (signed short.) That is 09:06:07.
+ */
+ if (desired_server_timeout > 0x7FFF) desired_server_timeout = 0x7FFF;
+ if (desired_server_interval > 0x7FFF) desired_server_interval = 0x7FFF;
+
+ if (desired_server_timeout != current_server_timeout ||
+ desired_server_interval != current_server_interval ||
+ desired_prefer_blank != current_prefer_blank ||
+ desired_allow_exp != current_allow_exp)
+ {
+ if (p->verbose_p)
+ fprintf (stderr,
+ "%s: disabling server builtin screensaver:\n"
+ "%s: (xset s %d %d; xset s %s; xset s %s)\n",
+ blurb(), blurb(),
+ desired_server_timeout, desired_server_interval,
+ (desired_prefer_blank ? "blank" : "noblank"),
+ (desired_allow_exp ? "expose" : "noexpose"));
+
+ XSetScreenSaver (si->dpy,
+ desired_server_timeout, desired_server_interval,
+ desired_prefer_blank, desired_allow_exp);
+ XSync(si->dpy, False);
+ }
+
+
+#if defined(HAVE_MIT_SAVER_EXTENSION) || defined(HAVE_SGI_SAVER_EXTENSION)
+ {
+ static Bool extension_initted = False;
+ if (!extension_initted)
+ {
+ extension_initted = True;
+# ifdef HAVE_MIT_SAVER_EXTENSION
+ if (si->using_mit_saver_extension) init_mit_saver_extension(si);
+# endif
+# ifdef HAVE_SGI_SAVER_EXTENSION
+ if (si->using_sgi_saver_extension) init_sgi_saver_extension(si);
+# endif
+ }
+ }
+#endif /* HAVE_MIT_SAVER_EXTENSION || HAVE_SGI_SAVER_EXTENSION */
+
+ if (unblank_screen_p)
+ /* Turn off the server builtin saver if it is now running. */
+ XForceScreenSaver (si->dpy, ScreenSaverReset);
+}