diff options
author | Simon Rettberg | 2024-09-06 14:42:37 +0200 |
---|---|---|
committer | Simon Rettberg | 2024-09-06 14:42:37 +0200 |
commit | badef32037f52f79abc1f1440b786cd71afdf270 (patch) | |
tree | 412b792d4cab4a7a110db82fcf74fe8a1ac55ec1 /driver | |
parent | Delete pre-6.00 files (diff) | |
download | xscreensaver-badef32037f52f79abc1f1440b786cd71afdf270.tar.gz xscreensaver-badef32037f52f79abc1f1440b786cd71afdf270.tar.xz xscreensaver-badef32037f52f79abc1f1440b786cd71afdf270.zip |
Diffstat (limited to 'driver')
80 files changed, 11209 insertions, 18381 deletions
diff --git a/driver/.gdbinit b/driver/.gdbinit deleted file mode 100644 index a585259..0000000 --- a/driver/.gdbinit +++ /dev/null @@ -1,27 +0,0 @@ -# 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 index 0e986a9..4b3c1cb 100644 --- a/driver/Makefile.in +++ b/driver/Makefile.in @@ -1,9 +1,9 @@ -# driver/Makefile.in --- xscreensaver, Copyright © 1997-2021 Jamie Zawinski. +# driver/Makefile.in --- xscreensaver, Copyright © 1997-2023 Jamie Zawinski. # the `../configure' script generates `driver/Makefile' from this file. @SET_MAKE@ .SUFFIXES: -.SUFFIXES: .c .m .o .desktop .desktop.in +.SUFFIXES: .c .m .o .desktop .desktop.in .service .service.in srcdir = @srcdir@ VPATH = @srcdir@ @@ -29,7 +29,9 @@ mansuffixB = 6 GTK_DATADIR = @GTK_DATADIR@ GTK_APPDIR = $(GTK_DATADIR)/applications GTK_ICONDIR = $(GTK_DATADIR)/pixmaps -GTK_UIDIR = $(GTK_DATADIR)/xscreensaver/ui +GTK_SHAREDIR = $(GTK_DATADIR)/xscreensaver +UPDATE_ICON_CACHE = gtk-update-icon-cache +GLIB_COMPILE_RESOURCES = @GLIB_COMPILE_RESOURCES@ HACKDIR = @HACKDIR@ HACK_CONF_DIR = @HACK_CONF_DIR@ @@ -51,6 +53,7 @@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SETUID = $(INSTALL_PROGRAM) $(SUID_FLAGS) INSTALL_DATA = @INSTALL_DATA@ INSTALL_DIRS = @INSTALL_DIRS@ +HACKDIR = @HACKDIR@ X_CFLAGS = @X_CFLAGS@ X_LIBS = @X_LIBS@ @@ -81,8 +84,7 @@ PAM_DIR = $(PAM_ROOT)/pam.d PAM_CONF = $(PAM_ROOT)/pam.conf ICON_SRC = $(UTILS_SRC)/images -LOGO = $(ICON_SRC)/logo-50.xpm -GTK_ICONS = $(ICON_SRC)/screensaver-*.png +LOGO = $(ICON_SRC)/logo-512.png UTILS_SRC = $(srcdir)/../utils UTILS_BIN = ../utils @@ -94,7 +96,7 @@ LIBS_PRE = $(LIBS) $(X_LIBS) $(X_PRE_LIBS) LIBS_POST = $(X_EXTRA_LIBS) XDPMS_LIBS = @XDPMS_LIBS@ -XINERAMA_LIBS = @XINERAMA_LIBS@ $(FIXME) +XINERAMA_LIBS = @XINERAMA_LIBS@ XINPUT_LIBS = -lXi XML_LIBS = @XML_LIBS@ @@ -118,6 +120,7 @@ GFX_OBJS = xscreensaver-gfx.o screens.o windows.o subprocs.o \ $(UTILS_BIN)/font-retry.o \ $(UTILS_BIN)/logo.o \ $(UTILS_BIN)/minixpm.o \ + $(UTILS_BIN)/screenshot.o \ $(UTILS_BIN)/xft.o \ $(UTILS_BIN)/utf8wc.o \ $(UTILS_BIN)/xshm.o \ @@ -134,9 +137,6 @@ 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 - PASSWD_SRCS = @PASSWD_SRCS@ PASSWD_OBJS = @PASSWD_OBJS@ @@ -160,7 +160,7 @@ AUTH_OBJS_1 = dialog.o passwd.o setuid.o \ $(UTILS_BIN)/logo.o \ $(UTILS_BIN)/minixpm.o AUTH_LIBS = $(LIBS_PRE) $(XFT_LIBS) $(XINPUT_LIBS) $(XINERAMA_LIBS) \ - @SAVER_LIBS@ -lXt -lX11 -lXext -lXi \ + $(XDPMS_LIBS) @SAVER_LIBS@ -lXt -lX11 -lXext -lXi \ @PASSWD_LIBS@ $(LIBS_POST) $(INTL_LIBS) SYSTEMD_DEFS = @@ -175,16 +175,16 @@ CMD_LIBS = $(LIBS_PRE) $(XINPUT_LIBS) -lX11 -lXext $(LIBS_POST) GTK_DEFS = -DHACK_CONFIGURATION_PATH='"$(HACK_CONF_DIR)"' \ -DDEFAULT_PATH_PREFIX='"@HACKDIR@"' \ - -DDEFAULT_ICONDIR='"$(GTK_UIDIR)"' \ -DLOCALEDIR=\"$(localedir)\" \ -I$(ICON_SRC) GTK_SRCS = demo-Gtk.c demo-Gtk-conf.c -GTK_OBJS = demo-Gtk.o demo-Gtk-conf.o \ - blurb.o exec.o prefs.o prefsw.o dpms.o remote.o \ +GTK_OBJS = demo-Gtk.o demo-Gtk-conf.o demo-Gtk-resources.o \ + blurb.o exec.o prefs.o prefsw.o dpms.o remote.o screens.o \ clientmsg.o atoms.o \ $(UTILS_BIN)/xmu.o \ $(UTILS_BIN)/resources.o \ $(UTILS_BIN)/visual.o \ + $(UTILS_BIN)/screenshot.o \ $(UTILS_BIN)/usleep.o GTK_LIBS = $(LIBS_PRE) $(INTL_LIBS) $(XDPMS_LIBS) \ $(XINERAMA_LIBS) $(XML_LIBS) @GTK_LIBS@ \ @@ -217,6 +217,7 @@ UTIL_EXES = xscreensaver-gfx @EXES_SYSTEMD@ SETUID_EXES = xscreensaver-auth DEMO_EXES = @ALL_DEMO_PROGRAMS@ EXES_SYSTEMD = xscreensaver-systemd +DESKS = xscreensaver.desktop xscreensaver.service HDRS = XScreenSaver_ad.h XScreenSaver_Xm_ad.h \ xscreensaver.h prefs.h remote.h exec.h \ @@ -225,22 +226,25 @@ HDRS = XScreenSaver_ad.h XScreenSaver_Xm_ad.h \ MENA = xscreensaver.man xscreensaver-settings.man \ xscreensaver-command.man MENB = xscreensaver-gfx.man xscreensaver-auth.man \ - xscreensaver-command.man xscreensaver-systemd.man + xscreensaver-systemd.man EXTRAS = README Makefile.in \ XScreenSaver.ad.in XScreenSaver-Xm.ad xscreensaver.pam.in \ - xscreensaver.ui screensaver-properties.desktop.in \ - .gdbinit + demo.ui prefs.ui gresource.xml xscreensaver.desktop.in \ + xscreensaver-settings.desktop.in xscreensaver.service.in TARFILES = $(DAEMON_SRCS) $(GFX_SRCS) $(AUTH_SRCS) $(SYSTEMD_SRCS) \ $(CMD_SRCS) $(GTK_SRCS) $(MOTIF_SRCS) $(PWENT_SRCS) \ - $(PWHELPER_SRCS) $(KERBEROS_SRCS) $(PAM_SRCS) \ + $(KERBEROS_SRCS) $(PAM_SRCS) \ $(HDRS) $(MENA) $(MENB) $(TEST_SRCS) $(EXTRAS) -default: $(EXES) $(UTIL_EXES) $(SETUID_EXES) -all: $(EXES) $(UTIL_EXES) $(SETUID_EXES) $(DEMO_EXES) -tests: $(TEST_EXES) +# Using $(MAKE) directly means the shell executes things even with "make -n" +MAKE2 = $(MAKE) +MAKE2CC = $(MAKE2) CC="$(CC)" CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" +default:: $(EXES) $(UTIL_EXES) $(SETUID_EXES) $(DESKS) +all:: default $(DEMO_EXES) +tests:: $(TEST_EXES) ############################################################################## # @@ -254,7 +258,7 @@ uninstall: uninstall-program uninstall-ad uninstall-man uninstall-xml \ uninstall-gnome install-strip: - $(MAKE) INSTALL_PROGRAM='$(INSTALL_PROGRAM) -s' install + $(MAKE2) INSTALL_PROGRAM='$(INSTALL_PROGRAM) -s' install install-program:: $(EXES) @if [ ! -d $(install_prefix)$(bindir) ]; then \ @@ -389,8 +393,8 @@ install-man:: echo ln -sf $$F1 $$D/$$F2 ; \ ln -sf $$F1 $$D/$$F2 -# These used to be in driver/ and installed into $(bindir) -# Now they are in hacks/ and are installed into ${libexecdir}/xscreensaver/ +# These used to be in driver/ and installed into /usr/bin/ +# Now they are in hacks/ and are installed into /usr/libexec/xscreensaver/ OLD_EXES = xscreensaver-getimage xscreensaver-getimage-file \ xscreensaver-getimage-video xscreensaver-text \ xscreensaver-systemd @@ -511,23 +515,45 @@ install-pam: xscreensaver.pam $$e "" ;\ fi -# screensaver-properties.desktop + +# xscreensaver.desktop +# xscreensaver-settings.desktop # into /usr/share/applications/ -install-gnome:: screensaver-properties.desktop +install-gnome:: xscreensaver.desktop xscreensaver-settings.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 ;\ + old=xscreensaver-properties.desktop ;\ + if [ -f "$(install_prefix)$(GTK_APPDIR)/$$old" ]; then \ + echo rm -f "$(install_prefix)$(GTK_APPDIR)/$$old" ;\ + rm -f "$(install_prefix)$(GTK_APPDIR)/$$old" ;\ + fi ;\ + for name in xscreensaver.desktop xscreensaver-settings.desktop ; do\ + echo $(INSTALL_DATA) $$name $(install_prefix)$(GTK_APPDIR)/$$name;\ + $(INSTALL_DATA) $$name $(install_prefix)$(GTK_APPDIR)/$$name;\ + echo chmod a+x $(install_prefix)$(GTK_APPDIR)/$$name ;\ + chmod a+x $(install_prefix)$(GTK_APPDIR)/$$name ;\ + done ;\ fi - -# xscreensaver.xpm +# xscreensaver.service +# into /usr/share/xscreensaver/ +# But maybe it should instead be installed as: +# $(prefix)/share/dbus-1/system-services/org.jwz.xscreensaver.service +# Or would that make it be enabled by default? +# +install-gnome:: xscreensaver.service + @if [ ! -d "$(install_prefix)$(GTK_SHAREDIR)" ]; then \ + echo $(INSTALL_DIRS) "$(install_prefix)$(GTK_SHAREDIR)" ;\ + $(INSTALL_DIRS) "$(install_prefix)$(GTK_SHAREDIR)" ;\ + fi ;\ + name=xscreensaver.service ;\ + echo $(INSTALL_DATA) $$name $(install_prefix)$(GTK_SHAREDIR)/$$name ;\ + $(INSTALL_DATA) $$name $(install_prefix)$(GTK_SHAREDIR)/$$name + +# xscreensaver.png # into /usr/share/pixmaps/ install-gnome:: $(LOGO) @if [ "$(GTK_DATADIR)" != "" ]; then \ @@ -535,70 +561,47 @@ install-gnome:: $(LOGO) echo $(INSTALL_DIRS) "$(install_prefix)$(GTK_ICONDIR)" ;\ $(INSTALL_DIRS) "$(install_prefix)$(GTK_ICONDIR)" ;\ fi ;\ - target=xscreensaver.xpm ;\ + target=xscreensaver.png ;\ 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/ui/ -install-gnome:: - @if [ "$(GTK_DATADIR)" != "" ]; then \ - if [ ! -d "$(install_prefix)$(GTK_UIDIR)" ]; then \ - echo $(INSTALL_DIRS) "$(install_prefix)$(GTK_UIDIR)" ;\ - $(INSTALL_DIRS) "$(install_prefix)$(GTK_UIDIR)" ;\ - fi ;\ - for target in $(GTK_ICONS) ; do \ - dest=`echo $$target | sed 's@^.*/@@'` ;\ - echo $(INSTALL_DATA) $$target \ - $(install_prefix)$(GTK_UIDIR)/$$dest ;\ - $(INSTALL_DATA) $$target \ - $(install_prefix)$(GTK_UIDIR)/$$dest ;\ - done ;\ - fi - -# xscreensaver.ui -# into /usr/share/xscreensaver/ui/ -install-gnome:: xscreensaver.ui - @if [ "$(GTK_DATADIR)" != "" ]; then \ - if [ ! -d "$(install_prefix)$(GTK_UIDIR)" ]; then \ - echo $(INSTALL_DIRS) "$(install_prefix)$(GTK_UIDIR)" ;\ - $(INSTALL_DIRS) "$(install_prefix)$(GTK_UIDIR)" ;\ - fi ;\ - target=xscreensaver.ui ;\ - echo $(INSTALL_DATA) $(srcdir)/$$target \ - $(install_prefix)$(GTK_UIDIR)/$$target ;\ - if $(INSTALL_DATA) $(srcdir)/$$target \ - $(install_prefix)$(GTK_UIDIR)/$$target ;\ - then true ;\ - else \ - e=echo ; \ - $$e "" ;\ - $$e " ####################################################################";\ - $$e " Warning: unable to install $$target into" ;\ - $$e " $(install_prefix)$(GTK_UIDIR)/." ;\ - $$e " Without this file, xscreensaver-settings will not" ;\ - $$e " be able to run properly." ;\ - $$e " ####################################################################";\ - $$e "" ;\ - exit 1 ; \ - fi ; \ - fi - +install-gnome:: uninstall-old-gnome-icons +install-gnome:: update-icon-caches + +update-icon-caches:: + @for f in /usr/share/icons/index.theme \ + /usr/share/icons/*/index.theme \ + /usr/share/pixmaps/index.theme \ + /usr/share/pixmaps/*/index.theme \ + ; do \ + if [ -f $$f ]; then \ + f=`dirname $$f` ;\ + echo $(UPDATE_ICON_CACHE) --force --quiet $$f ;\ + $(UPDATE_ICON_CACHE) --force --quiet $$f ;\ + fi ;\ + done -# screensaver-properties.desktop +# xscreensaver.desktop +# xscreensaver-settings.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 + old=xscreensaver-properties.desktop ;\ + if [ -f "$old" ]; then \ + echo rm -f $(install_prefix)$(GTK_APPDIR)/$$old ;\ + rm -f $(install_prefix)$(GTK_APPDIR)/$$old ;\ + fi ;\ + for f in xscreensaver.desktop xscreensaver-settings.desktop; do \ + echo rm -f $(install_prefix)$(GTK_APPDIR)/$$f ;\ + rm -f $(install_prefix)$(GTK_APPDIR)/$$f ;\ + done ;\ + fi -# xscreensaver.xpm -# into /usr/share/pixmaps/ +# xscreensaver.xpm (pre-2022) +# from /usr/share/pixmaps/ uninstall-gnome:: @if [ "$(GTK_ICONDIR)" != "" ]; then \ target=xscreensaver.xpm ;\ @@ -606,31 +609,31 @@ uninstall-gnome:: rm -f $(install_prefix)$(GTK_ICONDIR)/$$target ;\ fi -# ../utils/images/screensaver-*.png -# into /usr/share/xscreensaver/ui/ -uninstall-gnome:: - @if [ "$(GTK_DATADIR)" != "" ]; then \ - for target in $(GTK_ICONS) ; do \ - dest=`echo $$target | sed 's@^.*/@@'` ;\ - echo rm -f $(install_prefix)$(GTK_UIDIR)/$$dest ;\ - rm -f $(install_prefix)$(GTK_UIDIR)/$$dest ;\ - done ;\ - fi - -# xscreensaver.ui -# into /usr/share/xscreensaver/ui/ +# /usr/share/xscreensaver/ui/*.png and *.ui no longer used as of 2022, 6.05. uninstall-gnome:: - @if [ "$(GTK_DATADIR)" != "" ]; then \ - for target in xscreensaver.ui xscreensaver-demo.ui ; do \ - echo rm -f $(install_prefix)$(GTK_UIDIR)/$$target ;\ - rm -f $(install_prefix)$(GTK_UIDIR)/$$target ;\ - done ;\ - rmdir "$(GTK_UIDIR)" ;\ - rmdir "$(GTK_DATADIR)/xscreensaver" ;\ - exit 0 ;\ + @if [ "$(GTK_ICONDIR)" != "" ]; then \ + echo rm -rf $(install_prefix)$(GTK_SHAREDIR)/ui ;\ + rm -rf $(install_prefix)$(GTK_SHAREDIR)/ui ;\ fi -# /usr/share/xscreensaver/glade/ no longer used +# What is all this crap with the wrong logo? +# /usr/share/icons/nuoveXT2/16x16/apps/xscreensaver.png through +# /usr/share/icons/gnome-colors-common/32x32/apps/xscreensaver.png +uninstall-gnome:: uninstall-old-gnome-icons +uninstall-old-gnome-icons:: + @for f in \ + "$(install_prefix)$(GTK_DATADIR)/icons"/*/*/apps/xscreensaver.png \ + "$(install_prefix)$(GTK_DATADIR)/icons"/*/*/apps/xscreensaver.svg \ + "$(install_prefix)$(GTK_ICONDIR)/xscreensaver.xpm" \ + "$(install_prefix)$(GTK_ICONDIR)/xscreensaver.svg" ;\ + do \ + if [ -f "$$f" ]; then \ + echo rm -f "$$f" ;\ + rm -f "$$f" ;\ + fi ;\ + done + +# /usr/share/xscreensaver/glade/ no longer used as of 2022. uninstall-gnome:: -rm -rf $(GTK_DATADIR)/xscreensaver/glade @@ -667,7 +670,6 @@ distclean: clean TAGS *~ "#"* *.rej *.orig \ Makefile \ XScreenSaver.ad \ - screensaver-properties.desktop \ xscreensaver.pam # Adds all current dependencies to Makefile @@ -687,7 +689,7 @@ distdepend: check_men update_ad_version XScreenSaver_ad.h XScreenSaver_Xm_ad.h -s '# DO NOT DELETE: updated by make distdepend' $(DEPEND_FLAGS) -- \ $(INCLUDES_1) $(DEFS) $(DEPEND_DEFINES) $(CFLAGS) $(X_CFLAGS) -- \ $(SAVER_SRCS_1) $(SYSTEMD_SRCS) $(MOTIF_SRCS) $(GTK_SRCS) \ - $(PWENT_SRCS) $(PWHELPER_SRCS) $(KERBEROS_SRCS) $(PAM_SRCS) \ + $(PWENT_SRCS) $(KERBEROS_SRCS) $(PAM_SRCS) \ $(LOCK_SRCS_1) $(DEMO_SRCS_1) $(CMD_SRCS) \ $(TEST_SRCS) 2>/dev/null | \ sort -d | \ @@ -735,7 +737,7 @@ tags: find $(srcdir) -name '*.[chly]' -print | xargs etags -a list_tarfiles: - @$(MAKE) XScreenSaver_ad.h XScreenSaver_Xm_ad.h 2>&1 >/dev/null + @$(MAKE2) XScreenSaver_ad.h XScreenSaver_Xm_ad.h 2>&1 >/dev/null @find $(TARFILES) -type f -print | sort check_men: @@ -773,7 +775,7 @@ $(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)/grabclient.o: $(UTILS_SRC)/grabclient.c $(UTILS_BIN)/utf8wc.o: $(UTILS_SRC)/utf8wc.c $(UTILS_BIN)/xftwrap.o: $(UTILS_SRC)/xftwrap.c $(UTILS_BIN)/font-retry.o: $(UTILS_SRC)/font-retry.c @@ -792,7 +794,7 @@ UTIL_OBJS = $(UTILS_BIN)/overlay.o \ $(UTILS_BIN)/colorbars.o \ $(UTILS_BIN)/hsv.o \ $(UTILS_BIN)/colors.o \ - $(UTILS_BIN)/grabscreen.o \ + $(UTILS_BIN)/grabclient.o \ $(UTILS_BIN)/xft.o \ $(UTILS_BIN)/xftwrap.o \ $(UTILS_BIN)/utf8wc.o \ @@ -801,8 +803,7 @@ UTIL_OBJS = $(UTILS_BIN)/overlay.o \ $(UTILS_BIN)/aligned_malloc.o $(UTIL_OBJS): - cd $(UTILS_BIN) ; \ - $(MAKE) $(@F) CC="$(CC)" CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" + $(MAKE2CC) -C $(UTILS_BIN) $(@F) ############################################################################## @@ -862,6 +863,14 @@ demo-Gtk.o: demo-Gtk.c demo-Gtk-conf.o: demo-Gtk-conf.c $(CC) -c $(CC_ALL) $(GTK_DEFS) $< +GCRARGS = --sourcedir=$(srcdir) --sourcedir=$(ICON_SRC) --generate-source +demo-Gtk-resources.c: gresource.xml demo.ui prefs.ui + $(GLIB_COMPILE_RESOURCES) $(srcdir)/gresource.xml --target=$@ $(GCRARGS) + +GTK_WARNINGS = -Wno-long-long -Wno-c99-extensions -Wno-pedantic +demo-Gtk-resources.o: demo-Gtk-resources.c + $(CC) -c $(CC_ALL) $(GTK_DEFS) $(GTK_WARNINGS) $< + xscreensaver-settings-Gtk: $(GTK_OBJS) $(CC) $(LDFLAGS) -o $@ $(GTK_OBJS) $(GTK_LIBS) @@ -912,13 +921,8 @@ XScreenSaver_Xm_ad.h:: mv $$TMP $$OUT ; \ fi - -# Replace this with @INTLTOOL_DESKTOP_RULE@ once -# https://bugs.launchpad.net/intltool/+bug/1749904 is fixed. -.desktop.in.desktop: $(INTLTOOL_MERGE) $(top_srcdir)/po/*.po - $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) \ - $(INTLTOOL_V_MERGE_OPTIONS) -d -u \ - -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@ +@INTLTOOL_DESKTOP_RULE@ +@INTLTOOL_SERVICE_RULE@ ############################################################################## @@ -992,6 +996,7 @@ xdpyinfo: xdpyinfo.o # # DO NOT DELETE: updated by make distdepend +demo-Gtk-conf.o: $(srcdir)/blurb.h demo-Gtk-conf.o: ../config.h demo-Gtk-conf.o: $(srcdir)/demo-Gtk-conf.h demo-Gtk-conf.o: $(UTILS_SRC)/xscreensaver-intl.h @@ -1001,28 +1006,17 @@ demo-Gtk.o: $(srcdir)/blurb.h demo-Gtk.o: ../config.h demo-Gtk.o: $(srcdir)/demo-Gtk-conf.h demo-Gtk.o: $(srcdir)/remote.h +demo-Gtk.o: $(srcdir)/screens.h demo-Gtk.o: $(srcdir)/types.h demo-Gtk.o: $(UTILS_SRC)/resources.h +demo-Gtk.o: $(UTILS_SRC)/screenshot.h demo-Gtk.o: $(UTILS_SRC)/usleep.h demo-Gtk.o: $(UTILS_SRC)/version.h demo-Gtk.o: $(UTILS_SRC)/visual.h demo-Gtk.o: $(UTILS_SRC)/xmu.h demo-Gtk.o: $(UTILS_SRC)/xscreensaver-intl.h -demo-Xm.o: XScreenSaver_Xm_ad.h -demo-Xm.o: XScreenSaver_ad.h -demo-Xm.o: $(srcdir)/atoms.h -demo-Xm.o: $(srcdir)/blurb.h demo-Xm.o: ../config.h -demo-Xm.o: $(srcdir)/remote.h -demo-Xm.o: $(srcdir)/types.h -demo-Xm.o: $(UTILS_SRC)/resources.h -demo-Xm.o: $(UTILS_SRC)/version.h -demo-Xm.o: $(UTILS_SRC)/visual.h -demo-Xm.o: $(UTILS_SRC)/xmu.h demo-Xm-widgets.o: ../config.h -passwd-helper.o: $(srcdir)/auth.h -passwd-helper.o: $(srcdir)/blurb.h -passwd-helper.o: ../config.h passwd-kerberos.o: $(srcdir)/auth.h passwd-kerberos.o: $(srcdir)/blurb.h passwd-kerberos.o: ../config.h @@ -1063,7 +1057,6 @@ test-xdpms.o: $(srcdir)/blurb.h test-xdpms.o: ../config.h test-xinerama.o: $(srcdir)/blurb.h test-xinerama.o: ../config.h -test-xinput.o: $(srcdir)/blurb.h test-xinput.o: ../config.h test-xinput.o: $(srcdir)/xinput.h test-xkb.o: $(srcdir)/blurb.h diff --git a/driver/XScreenSaver.ad.in b/driver/XScreenSaver.ad.in index bfc6414..d3e89f5 100644 --- a/driver/XScreenSaver.ad.in +++ b/driver/XScreenSaver.ad.in @@ -4,8 +4,8 @@ ! a screen saver and locker for the X window system ! by Jamie Zawinski ! -! version 6.00 -! 01-Apr-2021 +! version 6.09 +! 07-Jun-2024 ! ! See "man xscreensaver" for more info. The latest version is always ! available at https://www.jwz.org/xscreensaver/ @@ -58,7 +58,7 @@ *ignoreUninstalledPrograms: False *authWarningSlack: 20 -*textMode: file +*textMode: url *textLiteral: XScreenSaver *textFile: @DEFAULT_TEXT_FILE@ *textProgram: fortune @@ -98,7 +98,7 @@ ! non-Gnome systems: ! @NOGNOME@*loadURL: x-www-browser '%s' || firefox '%s' || chromium-browser '%s' -@NOGNOME@*manualCommand: xterm -sb -fg black -bg gray75 -T '%s manual' \ +@NOGNOME@*manualCommand: lxterminal -T '%s manual' \ @NOGNOME@ -e /bin/sh -c 'man "%s" ; read foo' @@ -324,248 +324,260 @@ XScreenSaver.bourneShell: /bin/sh !============================================================================= *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\ - scooter -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: handsy -root \n\ -@GL_KLUDGE@ GL: maze3d -root \n\ -@GL_KLUDGE@ GL: peepers -root \n\ -@GL_KLUDGE@ GL: razzledazzle -root \n\ - vfeedback -root \n\ -@GL_KLUDGE@ GL: deepstars -root \n\ -@GL_KLUDGE@ GL: gravitywell -root \n\ -@GL_KLUDGE@ GL: beats -root \n\ -@GL_KLUDGE@ GL: covid19 -root \n\ -@GL_KLUDGE@ GL: etruscanvenus -root \n\ -@GL_KLUDGE@ GL: gibson -root \n\ -@GL_KLUDGE@ GL: headroom -root \n\ -@GL_KLUDGE@ GL: sphereeversion -root \n + 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\ + rdbomb --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\ +- webcollage --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\ + 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\ + scooter --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\ + 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: handsy --root \n\ +@GL_KLUDGE@ GL: maze3d --root \n\ +@GL_KLUDGE@ GL: peepers --root \n\ +@GL_KLUDGE@ GL: razzledazzle --root \n\ + vfeedback --root \n\ +@GL_KLUDGE@ GL: deepstars --root \n\ +@GL_KLUDGE@ GL: gravitywell --root \n\ +@GL_KLUDGE@ GL: beats --root \n\ +@GL_KLUDGE@ GL: covid19 --root \n\ +@GL_KLUDGE@ GL: etruscanvenus --root \n\ +@GL_KLUDGE@ GL: gibson --root \n\ +@GL_KLUDGE@ GL: headroom --root \n\ +@GL_KLUDGE@ GL: sphereeversion --root \n\ + binaryhorizon --root \n\ + marbling --root \n\ +@GL_KLUDGE@ GL: chompytower --root \n\ +@GL_KLUDGE@ GL: hextrail --root \n\ +@GL_KLUDGE@ GL: mapscroller --root \n\ +@GL_KLUDGE@ GL: nakagin --root \n\ +@GL_KLUDGE@ GL: squirtorus --root \n\ +@GL_KLUDGE@ GL: cubocteversion --root \n\ + droste --root \n\ +@GL_KLUDGE@ GL: papercube --root \n\ +@GL_KLUDGE@ GL: skulloop --root \n\ +@GL_KLUDGE@ GL: highvoltage --root \n\ +@GL_KLUDGE@ GL: kallisti --root \n @@ -580,6 +592,7 @@ XScreenSaver.bourneShell: /bin/sh *hacks.antmaze.name: Ant Maze *hacks.antspotlight.name: Ant Spotlight *hacks.apple2.name: Apple ][ +*hacks.binaryhorizon.name: Binary Horizon *hacks.binaryring.name: Binary Ring *hacks.blinkbox.name: Blink Box *hacks.blitspin.name: Blit Spin @@ -589,6 +602,7 @@ XScreenSaver.bourneShell: /bin/sh *hacks.bsod.name: BSOD *hacks.bubble3d.name: Bubble 3D *hacks.ccurve.name: C Curve +*hacks.chompytower.name: Chompy Tower *hacks.cityflow.name: City Flow *hacks.cloudlife.name: Cloud Life *hacks.companioncube.name: Companion Cube @@ -598,6 +612,7 @@ XScreenSaver.bourneShell: /bin/sh *hacks.cubestorm.name: Cube Storm *hacks.cubetwist.name: Cube Twist *hacks.cubicgrid.name: Cubic Grid +*hacks.cubocteversion.name: Cuboctahedron Eversion *hacks.cwaves.name: C Waves *hacks.dangerball.name: Danger Ball *hacks.decayscreen.name: Decay Screen @@ -634,6 +649,8 @@ XScreenSaver.bourneShell: /bin/sh *hacks.gltext.name: GL Text *hacks.gravitywell.name: Gravity Well *hacks.hexstrut.name: Hex Strut +*hacks.hextrail.name: Hex Trail +*hacks.highvoltage.name: High Voltage *hacks.ifs.name: IFS *hacks.imsmap.name: IMS Map *hacks.jigglypuff.name: Jiggly Puff @@ -641,6 +658,7 @@ XScreenSaver.bourneShell: /bin/sh *hacks.lcdscrub.name: LCD Scrub *hacks.lmorph.name: LMorph *hacks.m6502.name: m6502 +*hacks.mapscroller.name: Map Scroller *hacks.maze3d.name: Maze 3D *hacks.memscroller.name: Mem Scroller *hacks.metaballs.name: Meta Balls @@ -653,14 +671,14 @@ XScreenSaver.bourneShell: /bin/sh *hacks.nerverot.name: Nerve Rot *hacks.noseguy.name: Nose Guy *hacks.pacman.name: Pac-Man +*hacks.papercube.name: Paper Cube *hacks.photopile.name: Photo Pile *hacks.popsquares.name: Pop Squares *hacks.projectiveplane.name:Projective Plane *hacks.quasicrystal.name: Quasi-Crystal *hacks.raverhoop.name: Raver Hoop *hacks.razzledazzle.name: Razzle Dazzle -*hacks.rd-bomb.name: RD-Bomb -*hacks.rd-bomb.name: RD-Bomb +*hacks.rdbomb.name: RD-Bomb *hacks.romanboy.name: Roman Boy *hacks.rotzoomer.name: Rot Zoomer *hacks.rubikblocks.name: Rubik Blocks 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..c469a3e --- /dev/null +++ b/driver/XScreenSaver_ad.h @@ -0,0 +1,551 @@ +"#error Do not run app-defaults files through xrdb!", +"#error That does not do what you might expect.", +"#error Put this file in /usr/lib/X11/app-defaults/XScreenSaver instead.", +"*mode: random", +"*timeout: 0:10:00", +"*cycle: 0:10:00", +"*lockTimeout: 0:00:00", +"*passwdTimeout: 0:00:30", +"*dpmsEnabled: False", +"*dpmsQuickoffEnabled: False", +"*dpmsStandby: 2:00:00", +"*dpmsSuspend: 2:00:00", +"*dpmsOff: 4:00:00", +"*grabDesktopImages: True", +"*grabVideoFrames: False", +"*chooseRandomImages: True", +"*imageDirectory: /Library/Desktop Pictures/", +"*nice: 10", +"*memoryLimit: 0", +"*lock: False", +"*verbose: False", +"*fade: True", +"*unfade: True", +"*fadeSeconds: 0:00:03", +"*fadeTicks: 20", +"*splash: True", +"*splashDuration: 0:00:05", +"*visualID: default", +"*installColormap: True", +"*ignoreUninstalledPrograms: False", +"*authWarningSlack: 20", +"*textMode: url", +"*textLiteral: XScreenSaver", +"*textFile: ", +"*textProgram: fortune", +"*textURL: https://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss", +"*demoCommand: xscreensaver-settings", +"*helpURL: https://www.jwz.org/xscreensaver/man.html", +"*loadURL: x-www-browser '%s' || firefox '%s' || chromium-browser '%s'", +"*manualCommand: lxterminal -T '%s manual' \ + -e /bin/sh -c 'man \"%s\" ; read foo'", +"*dateFormat: %I:%M %p, %a %b %e", +"*newLoginCommand: no-such-login-manager", +"XScreenSaver.pointerHysteresis: 10", +"XScreenSaver.bourneShell: /bin/sh", +"*dialogTheme: default", +"*themeNames: Default, Borderless, Dark Gray, Borderless Black, \ + Green Black, White, Blue, Aqua Black, Wine", +"*Dialog.headingFont: sans-serif bold 16", +"*Dialog.bodyFont: sans-serif 14", +"*Dialog.errorFont: sans-serif bold 14", +"*Dialog.labelFont: sans-serif bold 14", +"*Dialog.unameFont: sans-serif 12", +"*Dialog.buttonFont: sans-serif bold 14", +"*Dialog.dateFont: sans-serif 9", +"*passwd.uname: True", +"*passwd.asterisks: True", +"*default.Dialog.foreground: #000000", +"*default.Dialog.background: #E6E6E6", +"*default.Dialog.button.foreground: #000000", +"*default.Dialog.button.background: #F5F5F5", +"*default.Dialog.logo.background: #BBBBBB", +"*default.Dialog.text.foreground: #000000", +"*default.Dialog.error.foreground: #FF0000", +"*default.Dialog.text.background: #FFFFFF", +"*default.Dialog.topShadowColor: #FFFFFF", +"*default.Dialog.bottomShadowColor: #CECECE", +"*default.Dialog.shadowWidth: 2", +"*default.Dialog.logo.width: 210", +"*default.Dialog.logo.height: 210", +"*default.Dialog.thermometer.foreground: #4464AC", +"*default.Dialog.thermometer.background: #FFFFFF", +"*default.Dialog.thermometer.width: 8", +"*default.Dialog.borderColor: #CECECE", +"*default.Dialog.borderWidth: 0", +"*default.Dialog.internalPadding: 24", +"*borderless.Dialog.topShadowColor: #E6E6E6", +"*borderless.Dialog.button.background: #FFFFFF", +"*borderless.Dialog.bottomShadowColor: #E6E6E6", +"*borderless.Dialog.logo.background: #E6E6E6", +"*borderless.Dialog.borderColor: #888888", +"*borderless.Dialog.thermometer.width: 6", +"*borderless.Dialog.borderWidth: 1", +"*darkgray.Dialog.foreground: #CCCCCC", +"*darkgray.Dialog.background: #333333", +"*darkgray.Dialog.topShadowColor: #444444", +"*darkgray.Dialog.bottomShadowColor: #111111", +"*darkgray.Dialog.borderColor: #111111", +"*darkgray.Dialog.text.foreground: #DDDDDD", +"*darkgray.Dialog.text.background: #666666", +"*darkgray.Dialog.button.foreground: #CCCCCC", +"*darkgray.Dialog.button.background: #666666", +"*darkgray.Dialog.logo.background: #444444", +"*darkgray.Dialog.thermometer.foreground: #4464AC", +"*darkgray.Dialog.thermometer.background: #666666", +"*darkgray.Dialog.borderWidth: 0", +"*borderlessblack.Dialog.foreground: #CCCCCC", +"*borderlessblack.Dialog.background: #000000", +"*borderlessblack.Dialog.topShadowColor: #000000", +"*borderlessblack.Dialog.bottomShadowColor: #000000", +"*borderlessblack.Dialog.text.foreground: #CCCCCC", +"*borderlessblack.Dialog.text.background: #000000", +"*borderlessblack.Dialog.button.foreground: #CCCCCC", +"*borderlessblack.Dialog.button.background: #333333", +"*borderlessblack.Dialog.logo.background: #000000", +"*borderlessblack.Dialog.thermometer.foreground: #CCCCCC", +"*borderlessblack.Dialog.thermometer.background: #333333", +"*borderlessblack.Dialog.thermometer.width: 3", +"*borderlessblack.Dialog.borderColor: #333333", +"*borderlessblack.Dialog.borderWidth: 1", +"*greenblack.Dialog.foreground: #00FF00", +"*greenblack.Dialog.background: #000000", +"*greenblack.Dialog.topShadowColor: #000000", +"*greenblack.Dialog.bottomShadowColor: #000000", +"*greenblack.Dialog.shadowWidth: 1", +"*greenblack.Dialog.text.foreground: #00FF00", +"*greenblack.Dialog.text.background: #006600", +"*greenblack.Dialog.button.foreground: #00FF00", +"*greenblack.Dialog.button.background: #006600", +"*greenblack.Dialog.logo.background: #000000", +"*greenblack.Dialog.thermometer.foreground: #00CC00", +"*greenblack.Dialog.thermometer.background: #006600", +"*greenblack.Dialog.borderColor: #006600", +"*greenblack.Dialog.borderWidth: 1", +"*white.Dialog.foreground: #000000", +"*white.Dialog.background: #FFFFFF", +"*white.Dialog.topShadowColor: #CCCCCC", +"*white.Dialog.bottomShadowColor: #CCCCCC", +"*white.Dialog.shadowWidth: 1", +"*white.Dialog.borderColor: #CCCCCC", +"*white.Dialog.text.foreground: #000000", +"*white.Dialog.text.background: #FFFFFF", +"*white.Dialog.button.foreground: #000000", +"*white.Dialog.button.background: #FFFFFF", +"*white.Dialog.logo.background: #FFFFFF", +"*white.Dialog.borderWidth: 0", +"*blue.Dialog.foreground: #000000", +"*blue.Dialog.background: #BBCCDD", +"*blue.Dialog.topShadowColor: #CCDDEE", +"*blue.Dialog.bottomShadowColor: #AABBCC", +"*blue.Dialog.borderColor: #AABBCC", +"*blue.Dialog.text.foreground: #000000", +"*blue.Dialog.text.background: #DDEEFF", +"*blue.Dialog.button.foreground: #000000", +"*blue.Dialog.button.background: #DDEEFF", +"*blue.Dialog.logo.background: #BBCCDD", +"*blue.Dialog.thermometer.foreground: #5566AA", +"*blue.Dialog.thermometer.background: #BBCCDD", +"*blue.Dialog.borderWidth: 0", +"*aquablack.Dialog.foreground: #00EFEF", +"*aquablack.Dialog.background: #000000", +"*aquablack.Dialog.topShadowColor: #000000", +"*aquablack.Dialog.bottomShadowColor: #000000", +"*aquablack.Dialog.shadowWidth: 2", +"*aquablack.Dialog.button.foreground: #000000", +"*aquablack.Dialog.button.background: #2244EE", +"*aquablack.Dialog.logo.background: #000000", +"*aquablack.Dialog.text.foreground: #2244EE", +"*aquablack.Dialog.text.background: #EEEEEE", +"*aquablack.Dialog.thermometer.foreground: #2244EE", +"*aquablack.Dialog.thermometer.background: #000088", +"*aquablack.Dialog.borderColor: #000066", +"*aquablack.Dialog.borderWidth: 1", +"*aquablack.Dialog.internalPadding: 36", +"*wine.Dialog.foreground: #AD8FA6", +"*wine.Dialog.background: #2C041E", +"*wine.Dialog.topShadowColor: #2C041E", +"*wine.Dialog.bottomShadowColor: #2C041E", +"*wine.Dialog.text.foreground: #706B70", +"*wine.Dialog.text.background: #F9F9F8", +"*wine.Dialog.button.foreground: #5F585B", +"*wine.Dialog.logo.background: #2C041E", +"*wine.Dialog.thermometer.foreground: #AD8FA6", +"*wine.Dialog.thermometer.background: #4D2946", +"*wine.Dialog.borderColor: #4D2946", +"*wine.Dialog.thermometer.width: 6", +"*wine.Dialog.borderWidth: 1", +"*errorFont: sans-serif bold 18", +"*errorColor: #FF0000", +"*programs: \ + maze --root \\n\ + GL: superquadrics --root \\n\ + attraction --root \\n\ + blitspin --root \\n\ + greynetic --root \\n\ + helix --root \\n\ + hopalong --root \\n\ + imsmap --root \\n\ +- noseguy --root \\n\ +- pyro --root \\n\ + qix --root \\n\ +- rocks --root \\n\ + rorschach --root \\n\ + decayscreen --root \\n\ + flame --root \\n\ + halo --root \\n\ + slidescreen --root \\n\ + pedal --root \\n\ + bouboule --root \\n\ +- braid --root \\n\ + coral --root \\n\ + deco --root \\n\ + drift --root \\n\ +- fadeplot --root \\n\ + galaxy --root \\n\ + goop --root \\n\ + grav --root \\n\ + ifs --root \\n\ + GL: jigsaw --root \\n\ + julia --root \\n\ +- kaleidescope --root \\n\ + GL: moebius --root \\n\ + moire --root \\n\ + GL: morph3d --root \\n\ + mountain --root \\n\ + munch --root \\n\ + penrose --root \\n\ + GL: pipes --root \\n\ + rdbomb --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\ +- webcollage --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\ + xflame --root \\n\ + xmatrix --root \\n\ + GL: gflux --root \\n\ +- nerverot --root \\n\ + xrayswarm --root \\n\ + xspirograph --root \\n\ + GL: circuit --root \\n\ + GL: dangerball --root \\n\ +- GL: dnalogo --root \\n\ + GL: engine --root \\n\ + GL: flipscreen3d --root \\n\ + GL: gltext --root \\n\ + GL: menger --root \\n\ + GL: molecule --root \\n\ + rotzoomer --root \\n\ + scooter --root \\n\ + speedmine --root \\n\ + GL: starwars --root \\n\ + GL: stonerview --root \\n\ + vermiculate --root \\n\ + whirlwindwarp --root \\n\ + zoom --root \\n\ + anemone --root \\n\ + apollonian --root \\n\ + GL: boxed --root \\n\ + GL: cubenetic --root \\n\ + GL: endgame --root \\n\ + euler2d --root \\n\ + fluidballs --root \\n\ + GL: flurry --root \\n\ +- GL: glblur --root \\n\ + GL: glsnake --root \\n\ + halftone --root \\n\ + GL: juggler3d --root \\n\ + GL: lavalite --root \\n\ +- polyominoes --root \\n\ + GL: queens --root \\n\ +- GL: sballs --root \\n\ + GL: spheremonics --root \\n\ + twang --root \\n\ +- GL: antspotlight --root \\n\ + apple2 --root \\n\ + GL: atunnel --root \\n\ + barcode --root \\n\ + GL: blinkbox --root \\n\ + GL: blocktube --root \\n\ + GL: bouncingcow --root \\n\ + cloudlife --root \\n\ + GL: cubestorm --root \\n\ + eruption --root \\n\ + GL: flipflop --root \\n\ + GL: flyingtoasters --root \\n\ + fontglide --root \\n\ + GL: gleidescope --root \\n\ + GL: glknots --root \\n\ + GL: glmatrix --root \\n\ +- GL: glslideshow --root \\n\ + GL: hypertorus --root \\n\ +- GL: jigglypuff --root \\n\ + metaballs --root \\n\ + GL: mirrorblob --root \\n\ + piecewise --root \\n\ + GL: polytopes --root \\n\ + pong --root \\n\ + popsquares --root \\n\ + GL: surfaces --root \\n\ + xanalogtv --root \\n\ + abstractile --root \\n\ + anemotaxis --root \\n\ +- GL: antinspect --root \\n\ + fireworkx --root \\n\ + fuzzyflakes --root \\n\ + interaggregate --root \\n\ + intermomentary --root \\n\ + memscroller --root \\n\ + GL: noof --root \\n\ + pacman --root \\n\ + GL: pinion --root \\n\ + GL: polyhedra --root \\n\ +- GL: providence --root \\n\ + substrate --root \\n\ + wormhole --root \\n\ +- GL: antmaze --root \\n\ + GL: boing --root \\n\ + boxfit --root \\n\ + GL: carousel --root \\n\ + celtic --root \\n\ + GL: crackberg --root \\n\ + GL: cube21 --root \\n\ + fiberlamp --root \\n\ + GL: fliptext --root \\n\ + GL: glhanoi --root \\n\ + GL: tangram --root \\n\ + GL: timetunnel --root \\n\ + GL: glschool --root \\n\ + GL: topblock --root \\n\ + GL: cubicgrid --root \\n\ + cwaves --root \\n\ + GL: gears --root \\n\ + GL: glcells --root \\n\ + GL: lockward --root \\n\ + m6502 --root \\n\ + GL: moebiusgears --root \\n\ + GL: voronoi --root \\n\ + GL: hypnowheel --root \\n\ + GL: klein --root \\n\ +- lcdscrub --root \\n\ + GL: photopile --root \\n\ + GL: skytentacles --root \\n\ + GL: rubikblocks --root \\n\ + GL: companioncube --root \\n\ + GL: hilbert --root \\n\ + GL: tronbit --root \\n\ + GL: geodesic --root \\n\ + hexadrop --root \\n\ + GL: kaleidocycle --root \\n\ + GL: quasicrystal --root \\n\ + GL: unknownpleasures --root \\n\ + binaryring --root \\n\ + GL: cityflow --root \\n\ + GL: geodesicgears --root \\n\ + GL: projectiveplane --root \\n\ + GL: romanboy --root \\n\ + tessellimage --root \\n\ + GL: winduprobot --root \\n\ + GL: splitflap --root \\n\ + GL: cubestack --root \\n\ + GL: cubetwist --root \\n\ + GL: discoball --root \\n\ + GL: dymaxionmap --root \\n\ + GL: energystream --root \\n\ + GL: hexstrut --root \\n\ + GL: hydrostat --root \\n\ + GL: raverhoop --root \\n\ + GL: splodesic --root \\n\ + GL: unicrud --root \\n\ + GL: esper --root \\n\ + GL: vigilance --root \\n\ + GL: crumbler --root \\n\ + filmleader --root \\n\ + glitchpeg --root \\n\ + GL: handsy --root \\n\ + GL: maze3d --root \\n\ + GL: peepers --root \\n\ + GL: razzledazzle --root \\n\ + vfeedback --root \\n\ + GL: deepstars --root \\n\ + GL: gravitywell --root \\n\ + GL: beats --root \\n\ + GL: covid19 --root \\n\ + GL: etruscanvenus --root \\n\ + GL: gibson --root \\n\ + GL: headroom --root \\n\ + GL: sphereeversion --root \\n\ + binaryhorizon --root \\n\ + marbling --root \\n\ + GL: chompytower --root \\n\ + GL: hextrail --root \\n\ + GL: mapscroller --root \\n\ + GL: nakagin --root \\n\ + GL: squirtorus --root \\n\ + GL: cubocteversion --root \\n\ + droste --root \\n\ + GL: papercube --root \\n\ + GL: skulloop --root \\n\ + GL: highvoltage --root \\n\ + GL: kallisti --root \\n", +"*hacks.antinspect.name: Ant Inspect", +"*hacks.antmaze.name: Ant Maze", +"*hacks.antspotlight.name: Ant Spotlight", +"*hacks.apple2.name: Apple ][", +"*hacks.binaryhorizon.name: Binary Horizon", +"*hacks.binaryring.name: Binary Ring", +"*hacks.blinkbox.name: Blink Box", +"*hacks.blitspin.name: Blit Spin", +"*hacks.blocktube.name: Block Tube", +"*hacks.bouncingcow.name: Bouncing Cow", +"*hacks.boxfit.name: Box Fit", +"*hacks.bsod.name: BSOD", +"*hacks.bubble3d.name: Bubble 3D", +"*hacks.ccurve.name: C Curve", +"*hacks.chompytower.name: Chompy Tower", +"*hacks.cityflow.name: City Flow", +"*hacks.cloudlife.name: Cloud Life", +"*hacks.companioncube.name: Companion Cube", +"*hacks.covid19.name: COVID19", +"*hacks.cube21.name: Cube 21", +"*hacks.cubestack.name: Cube Stack", +"*hacks.cubestorm.name: Cube Storm", +"*hacks.cubetwist.name: Cube Twist", +"*hacks.cubicgrid.name: Cubic Grid", +"*hacks.cubocteversion.name: Cuboctahedron Eversion", +"*hacks.cwaves.name: C Waves", +"*hacks.dangerball.name: Danger Ball", +"*hacks.decayscreen.name: Decay Screen", +"*hacks.deepstars.name: Deep Stars", +"*hacks.dnalogo.name: DNA Logo", +"*hacks.dymaxionmap.name: Dymaxion Map", +"*hacks.energystream.name: Energy Stream", +"*hacks.etruscanvenus.name: Etruscan Venus", +"*hacks.euler2d.name: Euler 2D", +"*hacks.fadeplot.name: Fade Plot", +"*hacks.fiberlamp.name: Fiber Lamp", +"*hacks.filmleader.name: Film Leader", +"*hacks.flipflop.name: Flip Flop", +"*hacks.flipscreen3d.name: Flip Screen 3D", +"*hacks.fliptext.name: Flip Text", +"*hacks.fluidballs.name: Fluid Balls", +"*hacks.flyingtoasters.name: Flying Toasters", +"*hacks.fontglide.name: Font Glide", +"*hacks.fuzzyflakes.name: Fuzzy Flakes", +"*hacks.geodesicgears.name: Geodesic Gears", +"*hacks.gflux.name: GFlux", +"*hacks.glblur.name: GL Blur", +"*hacks.glcells.name: GL Cells", +"*hacks.gleidescope.name: Gleidescope", +"*hacks.glforestfire.name: GL Forest Fire", +"*hacks.glhanoi.name: GL Hanoi", +"*hacks.glitchpeg.name: GlitchPEG", +"*hacks.glknots.name: GL Knots", +"*hacks.glmatrix.name: GL Matrix", +"*hacks.glplanet.name: GL Planet", +"*hacks.glschool.name: GL School", +"*hacks.glslideshow.name: GL Slideshow", +"*hacks.glsnake.name: GL Snake", +"*hacks.gltext.name: GL Text", +"*hacks.gravitywell.name: Gravity Well", +"*hacks.hexstrut.name: Hex Strut", +"*hacks.hextrail.name: Hex Trail", +"*hacks.highvoltage.name: High Voltage", +"*hacks.ifs.name: IFS", +"*hacks.imsmap.name: IMS Map", +"*hacks.jigglypuff.name: Jiggly Puff", +"*hacks.juggler3d.name: Juggler 3D", +"*hacks.lcdscrub.name: LCD Scrub", +"*hacks.lmorph.name: LMorph", +"*hacks.m6502.name: m6502", +"*hacks.mapscroller.name: Map Scroller", +"*hacks.maze3d.name: Maze 3D", +"*hacks.memscroller.name: Mem Scroller", +"*hacks.metaballs.name: Meta Balls", +"*hacks.mirrorblob.name: Mirror Blob", +"*hacks.moebius.name: Möbius", +"*hacks.moebiusgears.name: Möbius Gears", +"*hacks.moire.name: Moiré", +"*hacks.moire2.name: Moiré 2", +"*hacks.morph3d.name: Morph 3D", +"*hacks.nerverot.name: Nerve Rot", +"*hacks.noseguy.name: Nose Guy", +"*hacks.pacman.name: Pac-Man", +"*hacks.papercube.name: Paper Cube", +"*hacks.photopile.name: Photo Pile", +"*hacks.popsquares.name: Pop Squares", +"*hacks.projectiveplane.name:Projective Plane", +"*hacks.quasicrystal.name: Quasi-Crystal", +"*hacks.raverhoop.name: Raver Hoop", +"*hacks.razzledazzle.name: Razzle Dazzle", +"*hacks.rdbomb.name: RD-Bomb", +"*hacks.romanboy.name: Roman Boy", +"*hacks.rotzoomer.name: Rot Zoomer", +"*hacks.rubikblocks.name: Rubik Blocks", +"*hacks.sballs.name: SBalls", +"*hacks.shadebobs.name: Shade Bobs", +"*hacks.sierpinski3d.name: Sierpinski 3D", +"*hacks.skytentacles.name: Sky Tentacles", +"*hacks.slidescreen.name: Slide Screen", +"*hacks.speedmine.name: Speed Mine", +"*hacks.sphereeversion.name: Sphere Eversion", +"*hacks.splitflap.name: Split-Flap", +"*hacks.starwars.name: Star Wars", +"*hacks.stonerview.name: Stoner View", +"*hacks.t3d.name: T3D", +"*hacks.testx11.name: Test X11", +"*hacks.timetunnel.name: Time Tunnel", +"*hacks.topblock.name: Top Block", +"*hacks.tronbit.name: Tron Bit", +"*hacks.unknownpleasures.name:Unknown Pleasures", +"*hacks.vfeedback.name: VFeedback", +"*hacks.vidwhacker.name: Vid Whacker", +"*hacks.webcollage.name: Web Collage", +"*hacks.whirlwindwarp.name: Whirlwind Warp", +"*hacks.winduprobot.name: Windup Robot", +"*hacks.xanalogtv.name: XAnalogTV", +"*hacks.xrayswarm.name: XRaySwarm", diff --git a/driver/atoms.c b/driver/atoms.c index e8b6add..fc34707 100644 --- a/driver/atoms.c +++ b/driver/atoms.c @@ -1,4 +1,4 @@ -/* xscreensaver-command, Copyright © 1991-2021 Jamie Zawinski <jwz@jwz.org> +/* xscreensaver-command, Copyright © 1991-2023 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 @@ -25,9 +25,10 @@ Atom XA_SCREENSAVER, XA_SCREENSAVER_VERSION, XA_SCREENSAVER_RESPONSE, XA_SCREENSAVER_ID, XA_SCREENSAVER_STATUS, XA_SELECT, XA_DEMO, XA_EXIT, XA_BLANK, XA_LOCK, XA_ACTIVATE, XA_SUSPEND, XA_NEXT, XA_PREV, - XA_DEACTIVATE, XA_CYCLE, XA_RESTART, XA_PREFS, + XA_DEACTIVATE, XA_CYCLE, XA_RESTART, XA_NET_WM_PID, XA_NET_WM_STATE, XA_NET_WM_STATE_ABOVE, XA_NET_WM_STATE_FULLSCREEN, XA_NET_WM_BYPASS_COMPOSITOR, + XA_NET_WM_STATE_STAYS_ON_TOP, XA_KDE_NET_WM_WINDOW_TYPE_OVERRIDE, XA_NET_WM_WINDOW_TYPE, XA_NET_WM_WINDOW_TYPE_SPLASH, XA_NET_WM_WINDOW_TYPE_DIALOG, XA_NET_WM_WINDOW_TYPE_NOTIFICATION, XA_NET_WM_WINDOW_TYPE_NORMAL; @@ -52,7 +53,6 @@ init_xscreensaver_atoms (Display *dpy) XA_SELECT = A("SELECT"); XA_EXIT = A("EXIT"); XA_DEMO = A("DEMO"); - XA_PREFS = A("PREFS"); XA_LOCK = A("LOCK"); XA_BLANK = A("BLANK"); @@ -66,5 +66,8 @@ init_xscreensaver_atoms (Display *dpy) XA_NET_WM_WINDOW_TYPE_DIALOG = A("_NET_WM_WINDOW_TYPE_DIALOG"); XA_NET_WM_WINDOW_TYPE_NOTIFICATION = A("_NET_WM_WINDOW_TYPE_NOTIFICATION"); XA_NET_WM_WINDOW_TYPE_NORMAL = A("_NET_WM_WINDOW_TYPE_NORMAL"); + XA_NET_WM_STATE_STAYS_ON_TOP = A("_NET_WM_STATE_STAYS_ON_TOP"); + XA_KDE_NET_WM_WINDOW_TYPE_OVERRIDE = A("_KDE_NET_WM_WINDOW_TYPE_OVERRIDE"); + # undef A } diff --git a/driver/atoms.h b/driver/atoms.h index b5a7b7c..3f65d81 100644 --- a/driver/atoms.h +++ b/driver/atoms.h @@ -1,4 +1,4 @@ -/* xscreensaver-command, Copyright © 1991-2021 Jamie Zawinski <jwz@jwz.org> +/* xscreensaver-command, Copyright © 1991-2023 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 @@ -15,9 +15,10 @@ extern Atom XA_SCREENSAVER, XA_SCREENSAVER_VERSION, XA_SCREENSAVER_RESPONSE, XA_SCREENSAVER_ID, XA_SCREENSAVER_STATUS, XA_SELECT, XA_DEMO, XA_EXIT, XA_BLANK, XA_LOCK, XA_ACTIVATE, XA_SUSPEND, XA_NEXT, XA_PREV, - XA_DEACTIVATE, XA_CYCLE, XA_RESTART, XA_PREFS, + XA_DEACTIVATE, XA_CYCLE, XA_RESTART, XA_NET_WM_PID, XA_NET_WM_STATE, XA_NET_WM_STATE_ABOVE, XA_NET_WM_STATE_FULLSCREEN, XA_NET_WM_BYPASS_COMPOSITOR, + XA_NET_WM_STATE_STAYS_ON_TOP, XA_KDE_NET_WM_WINDOW_TYPE_OVERRIDE, XA_NET_WM_WINDOW_TYPE, XA_NET_WM_WINDOW_TYPE_SPLASH, XA_NET_WM_WINDOW_TYPE_DIALOG, XA_NET_WM_WINDOW_TYPE_NOTIFICATION, XA_NET_WM_WINDOW_TYPE_NORMAL; diff --git a/driver/atomswm.c b/driver/atomswm.c index 887dc2c..1ddf47a 100644 --- a/driver/atomswm.c +++ b/driver/atomswm.c @@ -43,6 +43,7 @@ xscreensaver_set_wm_atoms (Display *dpy, Window window, int width, int height, # endif Atom va[10]; long vl[10]; + int i; class_hints.res_name = "xscreensaver"; /* not progname */ class_hints.res_class = "XScreenSaver"; size_hints.flags = PMinSize | PMaxSize; @@ -73,22 +74,27 @@ xscreensaver_set_wm_atoms (Display *dpy, Window window, int width, int height, grubby paws off of our windows. */ - vl[0] = 1; /* _NET_WM_BYPASS_COMPOSITOR = 1 */ + i = 0; + vl[i++] = 1; /* _NET_WM_BYPASS_COMPOSITOR = 1 */ XChangeProperty (dpy, window, XA_NET_WM_BYPASS_COMPOSITOR, XA_CARDINAL, 32, - PropModeReplace, (unsigned char *) vl, 1); + PropModeReplace, (unsigned char *) vl, i); /* _NET_WM_STATE = [ _NET_WM_STATE_ABOVE, _NET_WM_STATE_FULLSCREEN ] */ - va[0] = XA_NET_WM_STATE_ABOVE; - va[1] = XA_NET_WM_STATE_FULLSCREEN; + i = 0; + va[i++] = XA_NET_WM_STATE_ABOVE; + va[i++] = XA_NET_WM_STATE_FULLSCREEN; + va[i++] = XA_NET_WM_STATE_STAYS_ON_TOP; /* Does this do anything? */ XChangeProperty (dpy, window, XA_NET_WM_STATE, XA_ATOM, 32, - PropModeReplace, (unsigned char *) va, 2); + PropModeReplace, (unsigned char *) va, i); /* As there is no _NET_WM_WINDOW_TYPE_SCREENSAVER, which property is most likely to effectively communicate "on top always" to the WM? _NET_WM_WINDOW_TYPE = NORMAL, SPLASH, DIALOG or NOTIFICATION? */ - va[0] = XA_NET_WM_WINDOW_TYPE_NOTIFICATION; + i = 0; + va[i++] = XA_NET_WM_WINDOW_TYPE_NOTIFICATION; + va[i++] = XA_KDE_NET_WM_WINDOW_TYPE_OVERRIDE; /* Does this do anything? */ XChangeProperty (dpy, window, XA_NET_WM_WINDOW_TYPE, XA_ATOM, 32, - PropModeReplace, (unsigned char *) va, 1); + PropModeReplace, (unsigned char *) va, i); if (for_window) /* This is the error dialog for a saver window */ { diff --git a/driver/auth.h b/driver/auth.h index 40594ea..80a49ba 100644 --- a/driver/auth.h +++ b/driver/auth.h @@ -1,5 +1,5 @@ /* auth.h --- Providing authentication mechanisms. - * Copyright © 1993-2021 Jamie Zawinski <jwz@jwz.org> + * Copyright © 1993-2022 Jamie Zawinski <jwz@jwz.org> * (c) 2007, Quest Software, Inc. All rights reserved. * This file is part of XScreenSaver. * @@ -85,7 +85,7 @@ extern Bool xscreensaver_auth_conv (void *closure, const auth_message *msg, auth_response **resp); extern void xscreensaver_auth_finished (void *closure, Bool authenticated_p); -extern void xscreensaver_splash (void *root_widget); +extern void xscreensaver_splash (void *root_widget, Bool disable_settings_p); #endif /* __XSCREENSAVER_AUTH_H__ */ diff --git a/driver/blurb.c b/driver/blurb.c index d732a9b..ddb8b18 100644 --- a/driver/blurb.c +++ b/driver/blurb.c @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright © 1991-2021 Jamie Zawinski <jwz@jwz.org> +/* xscreensaver, Copyright © 1991-2022 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 @@ -17,20 +17,29 @@ #include <string.h> #include <time.h> +#include <sys/time.h> const char *progname = ""; int verbose_p = 0; +/* #define BLURB_CENTISECONDS */ + const char * blurb (void) { static char buf[255] = { 0 }; struct tm tm; - time_t now; + struct timeval now; int i; - now = time ((time_t *) 0); - localtime_r (&now, &tm); +# ifdef GETTIMEOFDAY_TWO_ARGS + struct timezone tzp; + gettimeofday (&now, &tzp); +# else + gettimeofday (&now); +# endif + + localtime_r (&now.tv_sec, &tm); i = strlen (progname); if (i > 40) i = 40; memcpy (buf, progname, i); @@ -44,6 +53,16 @@ blurb (void) buf[i++] = ':'; buf[i++] = '0' + (tm.tm_sec >= 10 ? tm.tm_sec/10 : 0); buf[i++] = '0' + (tm.tm_sec % 10); + +# ifdef BLURB_CENTISECONDS + { + int c = now.tv_usec / 10000; + buf[i++] = '.'; + buf[i++] = '0' + (c >= 10 ? c/10 : 0); + buf[i++] = '0' + (c % 10); + } +# endif + buf[i] = 0; return buf; } diff --git a/driver/clientmsg.c b/driver/clientmsg.c index f3d9d71..475d72b 100644 --- a/driver/clientmsg.c +++ b/driver/clientmsg.c @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright © 1991-2021 Jamie Zawinski <jwz@jwz.org> +/* xscreensaver, Copyright © 1991-2023 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 @@ -36,59 +36,68 @@ error_handler (Display *dpy, XErrorEvent *error) Window find_screensaver_window (Display *dpy, char **version) { - int i; - Window root = RootWindowOfScreen (DefaultScreenOfDisplay (dpy)); - Window root2, parent, *kids; - unsigned int nkids; - XErrorHandler old_handler; + int nscreens = ScreenCount (dpy); + int screen; Window ret = 0; + XErrorHandler old_handler; XSync (dpy, False); old_handler = XSetErrorHandler (error_handler); if (version) *version = 0; - if (! XQueryTree (dpy, root, &root2, &parent, &kids, &nkids)) - abort (); - if (root != root2) - abort (); - if (parent) - abort (); - if (! (kids && nkids)) - goto DONE; - for (i = 0; i < nkids; i++) + for (screen = 0; screen < nscreens; screen++) { - Atom type; - int format; - unsigned long nitems, bytesafter; - unsigned char *v = 0; - int status; - - /* We're walking the list of root-level windows and trying to find - the one that has a particular property on it. We need to trap - BadWindows errors while doing this, because it's possible that - some random window might get deleted in the meantime. (That - window won't have been the one we're looking for.) - */ - status = XGetWindowProperty (dpy, kids[i], - XA_SCREENSAVER_VERSION, - 0, 200, False, XA_STRING, - &type, &format, &nitems, &bytesafter, - &v); - if (status == Success && type != None) - { - ret = kids[i]; - if (version) - *version = (char *) v; - else - XFree (v); - goto DONE; - } - if (v) XFree (v); + Window root = RootWindow (dpy, screen); + Window root2, parent, *kids; + unsigned int nkids; + int i = 0; + + if (! XQueryTree (dpy, root, &root2, &parent, &kids, &nkids)) + abort (); + if (root != root2) + abort (); + if (parent) + abort (); + if (! (kids && nkids)) + goto DONE; + for (i = 0; i < nkids; i++) + { + Atom type; + int format; + unsigned long nitems, bytesafter; + unsigned char *v = 0; + int status; + + /* We're walking the list of root-level windows and trying to find + the one that has a particular property on it. We need to trap + BadWindows errors while doing this, because it's possible that + some random window might get deleted in the meantime. (That + window won't have been the one we're looking for.) + */ + status = XGetWindowProperty (dpy, kids[i], + XA_SCREENSAVER_VERSION, + 0, 200, False, XA_STRING, + &type, &format, &nitems, &bytesafter, + &v); + if (status == Success && type != None) + { + ret = kids[i]; + if (version) + *version = (char *) v; + else + XFree (v); + goto DONE; + } + if (v) XFree (v); + + DONE: + if (ret) break; + } + if (kids) XFree (kids); + if (ret) break; } - DONE: - if (kids) XFree (kids); XSetErrorHandler (old_handler); return ret; } @@ -104,8 +113,15 @@ clientmessage_response (Display *dpy, XEvent *xev, Bool ok, const char *msg) { Atom cmd = xev->xclient.data.l[0]; char *name = XGetAtomName (dpy, cmd); - fprintf (stderr, "%s: ClientMessage %s: %s\n", blurb(), - (name ? name : "???"), msg); + pid_t caller = (cmd == XA_DEACTIVATE + ? (pid_t) xev->xclient.data.l[1] + : 0); + if (caller) + fprintf (stderr, "%s: ClientMessage %s: %s (from pid %lu)\n", blurb(), + (name ? name : "???"), msg, (unsigned long) caller); + else + fprintf (stderr, "%s: ClientMessage %s: %s\n", blurb(), + (name ? name : "???"), msg); } L = strlen (msg); diff --git a/driver/compile_axp.com b/driver/compile_axp.com deleted file mode 100644 index d6ed0e8..0000000 --- a/driver/compile_axp.com +++ /dev/null @@ -1,15 +0,0 @@ -$ 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 deleted file mode 100644 index d6ed0e8..0000000 --- a/driver/compile_decc.com +++ /dev/null @@ -1,15 +0,0 @@ -$ 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 index 6d04215..86acea2 100644 --- a/driver/demo-Gtk-conf.c +++ b/driver/demo-Gtk-conf.c @@ -1,5 +1,5 @@ /* demo-Gtk-conf.c --- implements the dynamic configuration dialogs. - * xscreensaver, Copyright (c) 2001-2020 Jamie Zawinski <jwz@jwz.org> + * xscreensaver, Copyright © 2001-2022 Jamie Zawinski <jwz@jwz.org> * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that @@ -14,7 +14,7 @@ # include "config.h" #endif -#if defined(HAVE_GTK) && defined(HAVE_XML) /* whole file */ +#ifdef HAVE_GTK /* whole file */ #include <xscreensaver-intl.h> @@ -28,36 +28,7 @@ #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 <libxml/parser.h> #if (__GNUC__ >= 4) /* Ignore useless warnings generated by gtk.h */ # undef inline @@ -66,6 +37,7 @@ # pragma GCC diagnostic ignored "-Wlong-long" # pragma GCC diagnostic ignored "-Wvariadic-macros" # pragma GCC diagnostic ignored "-Wpedantic" +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif #include <gtk/gtk.h> @@ -74,6 +46,7 @@ # pragma GCC diagnostic pop #endif +#include "blurb.h" #include "demo-Gtk-conf.h" /* Deal with deprecation of direct access to struct fields on the way to GTK3 @@ -92,12 +65,7 @@ #endif -extern const char *blurb (void); - - -const char *hack_configuration_path = HACK_CONFIGURATION_PATH; - -static gboolean debug_p = FALSE; +static gboolean debug_p = FALSE; /* Copied from s->debug_p in parent */ #define MIN_SLIDER_WIDTH 150 @@ -120,6 +88,11 @@ typedef enum { } parameter_type; +typedef enum { + NONE, INVERT, RATIO +} convert_t; + + typedef struct { parameter_type type; @@ -140,8 +113,9 @@ typedef struct { 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. */ + convert_t convert; /* invert: whether to flip the value and pretend the + range goes from hi-low instead of low-hi. + ratio: position 1.0 at the center. */ /* boolean, select-option */ @@ -161,7 +135,9 @@ typedef struct { static parameter *make_select_option (const char *file, xmlNodePtr); static void make_parameter_widget (const char *filename, - parameter *, GtkWidget *); + parameter *, GtkWidget *, + void (*changed_cb) (GtkWidget *, gpointer), + gpointer changed_data); static void browse_button_cb (GtkButton *button, gpointer user_data); @@ -221,7 +197,14 @@ describe_parameter (FILE *out, parameter *p) 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\""); + + switch (p->convert) { + case NONE: break; + case INVERT: fprintf (out, " convert=\"invert\""); break; + case RATIO: fprintf (out, " convert=\"ratio\""); break; + default: abort(); break; + } + 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"); @@ -366,7 +349,11 @@ make_parameter (const char *filename, xmlNodePtr node) 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")); + + if (convert && !strcmp (convert, "invert")) p->convert = INVERT; + else if (convert && !strcmp (convert, "ratio")) p->convert = RATIO; + else p->convert = NONE; + p->arg = xmlGetProp (node, (xmlChar *) "arg"); p->arg_set = xmlGetProp (node, (xmlChar *) "arg-set"); p->arg_unset = xmlGetProp (node, (xmlChar *) "arg-unset"); @@ -461,7 +448,7 @@ sanity_check_parameter (const char *filename, const xmlChar *node_name, gboolean high; gboolean value; gboolean arg; - gboolean invert_p; + gboolean convert; gboolean arg_set; gboolean arg_unset; } allowed, require; @@ -511,7 +498,7 @@ sanity_check_parameter (const char *filename, const xmlChar *node_name, /* require.high = TRUE; -- may be 0 */ allowed.value = TRUE; /* require.value = TRUE; -- may be 0 */ - allowed.invert_p = TRUE; + allowed.convert = TRUE; break; case SPINBUTTON: allowed.id = TRUE; @@ -525,7 +512,7 @@ sanity_check_parameter (const char *filename, const xmlChar *node_name, /* require.high = TRUE; -- may be 0 */ allowed.value = TRUE; /* require.value = TRUE; -- may be 0 */ - allowed.invert_p = TRUE; + allowed.convert = FALSE; break; case BOOLEAN: allowed.id = TRUE; @@ -571,9 +558,15 @@ sanity_check_parameter (const char *filename, const xmlChar *node_name, CHECK (high, "high"); CHECK (value, "default"); CHECK (arg, "arg"); - CHECK (invert_p, "convert"); + CHECK (convert, "convert"); CHECK (arg_set, "arg-set"); CHECK (arg_unset, "arg-unset"); + + if (p->convert == RATIO) + { + if (p->low <= 0) WARN ("low must be > 0"); + if (p->high <= 1) WARN ("high must be > 1"); + } # undef CHECK # undef WARN @@ -587,16 +580,13 @@ 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) { @@ -605,10 +595,12 @@ sanity_check_menu_options (const char *filename, const xmlChar *node_name, if (spc) *spc = 0; if (prefix) { +# if 0 if (strcmp (a, prefix)) fprintf (stderr, "%s: %s: both \"%s\" and \"%s\" used in <select id=\"%s\">\n", blurb(), filename, prefix, a, p->id); +# endif free (prefix); } prefix = a; @@ -703,7 +695,9 @@ sanity_check_parameters (const char *filename, GList *parms) /* Helper for make_parameters() */ static GList * -make_parameters_1 (const char *filename, xmlNodePtr node, GtkWidget *parent) +make_parameters_1 (const char *filename, xmlNodePtr node, GtkWidget *parent, + void (*changed_cb) (GtkWidget *, gpointer), + gpointer changed_data) { GList *list = 0; @@ -713,14 +707,16 @@ make_parameters_1 (const char *filename, xmlNodePtr node, GtkWidget *parent) if (!strcmp (name, "hgroup") || !strcmp (name, "vgroup")) { - GtkWidget *box = (*name == 'h' - ? gtk_hbox_new (FALSE, 0) - : gtk_vbox_new (FALSE, 0)); + GtkWidget *box = gtk_box_new (*name == 'h' + ? GTK_ORIENTATION_HORIZONTAL + : GTK_ORIENTATION_VERTICAL, + 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); + list2 = make_parameters_1 (filename, node->xmlChildrenNode, box, + changed_cb, changed_data); if (list2) list = g_list_concat (list, list2); } @@ -730,7 +726,8 @@ make_parameters_1 (const char *filename, xmlNodePtr node, GtkWidget *parent) if (p) { list = g_list_append (list, p); - make_parameter_widget (filename, p, parent); + make_parameter_widget (filename, p, parent, + changed_cb, changed_data); } } } @@ -743,13 +740,16 @@ make_parameters_1 (const char *filename, xmlNodePtr node, GtkWidget *parent) Returns a GList of `parameter' objects. */ static GList * -make_parameters (const char *filename, xmlNodePtr node, GtkWidget *parent) +make_parameters (const char *filename, xmlNodePtr node, GtkWidget *parent, + void (*changed_cb) (GtkWidget *, gpointer), + gpointer changed_data) { 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 make_parameters_1 (filename, node->xmlChildrenNode, parent, + changed_cb, changed_data); } return 0; } @@ -763,42 +763,62 @@ invert_range (gfloat low, gfloat high, gfloat value) return (low + (range - off)); } +/* In: 0-1; Out: low-high. */ +static gfloat +ratio_to_range (gfloat low, gfloat high, gfloat ratio) +{ + return (ratio > 0.5 + ? (1 + (2 * (ratio - 0.5) * (high - 1))) + : (low + (2 * ratio * (1 - low)))); +} + +/* In: low-high; Out: 0-1. */ +static gfloat +range_to_ratio (gfloat low, gfloat high, gfloat value) +{ + return (value > 1 + ? ((value - 1) / (2 * (high - 1))) + 0.5 + : ((value - low) / (2 * (1 - low)))); +} + 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) + gfloat low = p->low; + gfloat high = p->high; + float range = (high - low); + float value = (p->convert == INVERT + ? invert_range (low, high, p->value) : p->value); - gfloat si = (p->high - p->low) / 100; - gfloat pi = (p->high - p->low) / 10; + gfloat si = (high - low) / 100; + gfloat pi = (high - low) / 10; gfloat page_size = ((p->type == SLIDER) ? 1 : 0); - if (p->value < p->low || p->value > p->high) + if (p->value < low || p->value > 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); + (int) p->value, (int) low, (int) 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); + blurb(), filename, p->value, low, high); + value = (value < low ? low : high); } #if 0 - else if (debug_p && p->value < 1000 && p->high >= 10000) + else if (debug_p && p->value < 1000 && 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); + (int) p->value, (int) low, (int) high); else fprintf (stderr, "%s: WARNING: %s: %.2f is suspicious for range [%.2f, %.2f]\n", - blurb(), filename, p->value, p->low, p->high); + blurb(), filename, p->value, low, high); } #endif /* 0 */ @@ -809,20 +829,26 @@ make_adjustment (const char *filename, parameter *p) if (range <= 500) si = 1; - return GTK_ADJUSTMENT (gtk_adjustment_new (value, - p->low, - p->high + page_size, + /* For simplicity, "ratio" sliders in the UI all run from 0.0 to 1.0. */ + if (p->convert == RATIO) + { + value = range_to_ratio (low, high, value); + low = 0; + high = 1; + page_size = 0; + } + + return GTK_ADJUSTMENT (gtk_adjustment_new (value, low, 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) + GtkRequisition min, nat; + gtk_widget_get_preferred_size (w, &min, &nat); + if (min.width < width) gtk_widget_set_size_request (GTK_WIDGET (w), width, -1); } @@ -834,9 +860,10 @@ set_widget_min_width (GtkWidget *w, int width) static GtkWidget * insert_fake_hbox (GtkWidget *parent) { - if (GTK_IS_VBOX (parent)) + if (gtk_orientable_get_orientation (GTK_ORIENTABLE (parent)) == + GTK_ORIENTATION_VERTICAL) { - GtkWidget *hbox = gtk_hbox_new (FALSE, 0); + GtkWidget *hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_box_pack_start (GTK_BOX (parent), hbox, FALSE, FALSE, 4); gtk_widget_show (hbox); return hbox; @@ -846,15 +873,15 @@ insert_fake_hbox (GtkWidget *parent) static void -link_atk_label_to_widget(GtkWidget *label, GtkWidget *widget) +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); + 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); + 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, @@ -862,7 +889,9 @@ link_atk_label_to_widget(GtkWidget *label, GtkWidget *widget) `parent' must be a GtkBox. */ static void -make_parameter_widget (const char *filename, parameter *p, GtkWidget *parent) +make_parameter_widget (const char *filename, parameter *p, GtkWidget *parent, + void (*changed_cb) (GtkWidget *, gpointer data), + gpointer changed_data) { const char *label = (char *) p->label; if (p->widget) return; @@ -878,7 +907,8 @@ make_parameter_widget (const char *filename, parameter *p, GtkWidget *parent) 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); + gtk_label_set_xalign (GTK_LABEL (w), 1.0); + gtk_label_set_yalign (GTK_LABEL (w), 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); @@ -888,6 +918,8 @@ make_parameter_widget (const char *filename, parameter *p, GtkWidget *parent) 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); + g_signal_connect (p->widget, "changed", G_CALLBACK (changed_cb), + changed_data); break; } case FILENAME: @@ -900,12 +932,12 @@ make_parameter_widget (const char *filename, parameter *p, GtkWidget *parent) gtk_widget_show (button); p->widget = entry; - gtk_signal_connect (GTK_OBJECT (button), - "clicked", GTK_SIGNAL_FUNC (browse_button_cb), - (gpointer) entry); + g_signal_connect (button, "clicked", G_CALLBACK (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); + gtk_label_set_xalign (GTK_LABEL (L), 1.0); + gtk_label_set_yalign (GTK_LABEL (L), 0.5); set_widget_min_width (GTK_WIDGET (L), MIN_LABEL_WIDTH); gtk_widget_show (L); @@ -916,12 +948,14 @@ make_parameter_widget (const char *filename, parameter *p, GtkWidget *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); + g_signal_connect (p->widget, "changed", + G_CALLBACK (changed_cb), changed_data); break; } case SLIDER: { GtkAdjustment *adj = make_adjustment (filename, p); - GtkWidget *scale = gtk_hscale_new (adj); + GtkWidget *scale = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, adj); GtkWidget *labelw = 0; if (label) @@ -929,7 +963,8 @@ make_parameter_widget (const char *filename, parameter *p, GtkWidget *parent) 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); + gtk_label_set_xalign (GTK_LABEL (labelw), 0.0); + gtk_label_set_yalign (GTK_LABEL (labelw), 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); @@ -943,17 +978,21 @@ make_parameter_widget (const char *filename, parameter *p, GtkWidget *parent) 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); + gtk_label_set_xalign (GTK_LABEL (w), 1.0); + gtk_label_set_yalign (GTK_LABEL (w), 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); + /* We can't show the value as it is wrong for ratio and inverted + sliders. */ + gtk_scale_set_draw_value (GTK_SCALE (scale), FALSE); + gtk_box_pack_start (GTK_BOX (parent), scale, FALSE, FALSE, 4); gtk_widget_show (scale); @@ -963,13 +1002,16 @@ make_parameter_widget (const char *filename, parameter *p, GtkWidget *parent) 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); + gtk_label_set_xalign (GTK_LABEL (w), 0.0); + gtk_label_set_yalign (GTK_LABEL (w), 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; + g_signal_connect (adj, "value-changed", + G_CALLBACK (changed_cb), changed_data); break; } case SPINBUTTON: @@ -986,7 +1028,8 @@ make_parameter_widget (const char *filename, parameter *p, GtkWidget *parent) 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); + gtk_label_set_xalign (GTK_LABEL (w), 1.0); + gtk_label_set_yalign (GTK_LABEL (w), 0.5); set_widget_min_width (GTK_WIDGET (w), MIN_LABEL_WIDTH); gtk_widget_show (w); parent = insert_fake_hbox (parent); @@ -997,6 +1040,8 @@ make_parameter_widget (const char *filename, parameter *p, GtkWidget *parent) gtk_box_pack_start (GTK_BOX (parent), spin, FALSE, FALSE, 4); p->widget = spin; + g_signal_connect (adj, "value-changed", + G_CALLBACK (changed_cb), changed_data); break; } case BOOLEAN: @@ -1006,26 +1051,37 @@ make_parameter_widget (const char *filename, parameter *p, GtkWidget *parent) parent = insert_fake_hbox (parent); */ gtk_box_pack_start (GTK_BOX (parent), p->widget, FALSE, FALSE, 4); + g_signal_connect (p->widget, "clicked", + G_CALLBACK (changed_cb), changed_data); break; } case SELECT: { - GtkWidget *opt = gtk_option_menu_new (); - GtkWidget *menu = gtk_menu_new (); + GtkComboBox *cbox = GTK_COMBO_BOX (gtk_combo_box_new()); + GtkListStore *model = gtk_list_store_new (1, G_TYPE_STRING); + GtkCellRenderer *view = gtk_cell_renderer_text_new(); + GtkTreeIter iter; GList *opts; + g_object_set (G_OBJECT (cbox), "model", model, NULL); + g_object_unref (model); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (cbox), view, TRUE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (cbox), view, + "text", 0, NULL); + 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); + const char *name = _((char *) s->label); + gtk_list_store_append (model, &iter); + gtk_list_store_set (model, &iter, 0, name, -1); } - gtk_option_menu_set_menu (GTK_OPTION_MENU (opt), menu); - p->widget = opt; + p->widget = GTK_WIDGET (cbox); parent = insert_fake_hbox (parent); gtk_box_pack_start (GTK_BOX (parent), p->widget, FALSE, FALSE, 4); + g_signal_connect (p->widget, "changed", + G_CALLBACK (changed_cb), changed_data); break; } @@ -1045,76 +1101,28 @@ make_parameter_widget (const char *filename, parameter *p, GtkWidget *parent) } } - -/* 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)); + GtkEntry *entry = GTK_ENTRY (user_data); + GtkWindow *win = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (button))); + const char *ofile = gtk_entry_get_text (entry); + char *file = strdup (ofile); + + if (debug_p) fprintf (stderr, "%s: settings browse button\n", blurb()); + if (file_chooser (win, entry, &file, _("Select file."), + debug_p, FALSE, FALSE)) + { + if (debug_p) + fprintf (stderr, "%s: file: \"%s\" -> \"%s\n", blurb(), ofile, file); + gtk_entry_set_text (entry, file); + } + free (file); } - + /* Converting to and from command-lines */ @@ -1223,6 +1231,23 @@ format_switch (parameter *p, const char *value) } +static void +pretty_float (char *out, gfloat val) +{ + char *s1; + sprintf (out, "%.3f", val); + s1 = strchr (out, '.'); + 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; + } +} + + /* Maps a `parameter' to a command-line switch. Returns 0 if it can't, or if the parameter has the default value. */ @@ -1261,11 +1286,19 @@ parameter_to_switch (parameter *p) ? 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)); + float value; + switch (p->convert) { + case INVERT: + value = invert_range (GET_ADJ_LOWER(adj), GET_ADJ_UPPER(adj), + GET_ADJ_VALUE(adj)) - 1; + break; + case RATIO: + value = ratio_to_range (p->low, p->high, GET_ADJ_VALUE(adj)); + break; + default: + value = GET_ADJ_VALUE(adj); + break; + } if (value == p->value) /* same as default */ return 0; @@ -1273,17 +1306,8 @@ parameter_to_switch (parameter *p) 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; - } + pretty_float (buf, value); + return format_switch (p, buf); } case BOOLEAN: @@ -1301,21 +1325,19 @@ parameter_to_switch (parameter *p) 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); + GtkComboBox *cbox = GTK_COMBO_BOX (p->widget); + int menu_elt = gtk_combo_box_get_active (cbox); + GList *ol = (menu_elt >= 0 ? g_list_nth (p->options, menu_elt) : NULL); 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; + if (o) + { + if (o->type != SELECT_OPTION) abort(); + s = (char *) o->arg_set; + if (s) + return strdup (s); + } + return 0; } default: if (p->widget) @@ -1454,10 +1476,12 @@ parse_command_line_into_parameters (const char *filename, 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]". + /* the next token is the next switch if it matches ^-[-a-z] (To avoid losing on "-x -3.1".) */ - if (s && (s[0] != '-' || !isalpha(s[1]))) + if (! (s && + s[0] == '-' && + (s[1] == '-' || isalpha(s[1])))) { value = s; rest->next->data = 0; @@ -1479,9 +1503,14 @@ static gboolean compare_opts (const char *option, const char *value, const char *template) { - int ol = strlen (option); + int ol; char *c; + /* -arg and --arg are the same. */ + if (option[0] == '-' && option[1] == '-') option++; + if (template[0] == '-' && template[1] == '-') template++; + ol = strlen (option); + if (strncmp (option, template, ol)) return FALSE; @@ -1625,8 +1654,15 @@ parameter_set_switch (parameter *p, gpointer value) if (1 == sscanf ((char *) value, "%f %c", &f, &c)) { - if (p->invert_p) + switch (p->convert) { + case INVERT: f = invert_range (GET_ADJ_LOWER(adj), GET_ADJ_UPPER(adj), f) - 1; + break; + case RATIO: + f = range_to_ratio (p->low, p->high, f); + break; + default: break; + } gtk_adjustment_set_value (adj, f); } break; @@ -1639,8 +1675,8 @@ parameter_set_switch (parameter *p, gpointer value) } case SELECT: { - gtk_option_menu_set_history (GTK_OPTION_MENU (p->widget), - GPOINTER_TO_INT(value)); + GtkComboBox *cbox = GTK_COMBO_BOX (p->widget); + gtk_combo_box_set_active (cbox, GPOINTER_TO_INT(value)); break; } default: @@ -1666,18 +1702,33 @@ restore_defaults (const char *progname, GList *parms) 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); + gfloat value; + switch (p->convert) { + case INVERT: + value = invert_range (p->low, p->high, p->value); + break; + case RATIO: + value = range_to_ratio (p->low, p->high, p->value); + break; + default: + value = p->value; + break; + } gtk_adjustment_set_value (adj, value); break; } + case SPINBUTTON: + { + GtkAdjustment *adj = + gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (p->widget)); + gtk_adjustment_set_value (adj, p->value); + break; + } case BOOLEAN: { /* A toggle button should be on by default if it inserts @@ -1690,7 +1741,7 @@ restore_defaults (const char *progname, GList *parms) } case SELECT: { - GtkOptionMenu *opt = GTK_OPTION_MENU (p->widget); + GtkComboBox *cbox = GTK_COMBO_BOX (p->widget); GList *opts; int selected = 0; int index; @@ -1708,7 +1759,7 @@ restore_defaults (const char *progname, GList *parms) } } - gtk_option_menu_set_history (GTK_OPTION_MENU (opt), selected); + gtk_combo_box_set_active (cbox, selected); break; } default: @@ -1718,7 +1769,7 @@ restore_defaults (const char *progname, GList *parms) } - + /* Documentation strings */ @@ -1796,15 +1847,49 @@ get_description (GList *parms, gboolean verbose_p) } } - +static int +get_year (const char *desc) +{ + /* Find the last 4-digit year-like number on the last line of the string. */ + const char *s = strrchr (desc, '\n'); + int year = 0; + if (!s) return 0; + while (*s) + { + if ((s[0] == ' ' || s[0] == '\t' || s[0] == '\r' || s[0] == '\n' || + s[0] == '-' || s[0] == '.') && + s[1] >= '0' && s[1] <= '9' && + s[2] >= '0' && s[2] <= '9' && + s[3] >= '0' && s[3] <= '9' && + s[4] >= '0' && s[4] <= '9' && + (s[5] == ' ' || s[5] == '\t' || s[5] == '\r' || s[5] == '\n' || + s[5] == '-' || s[5] == '.')) + { + int n = (((s[1]-'0') * 1000) + + ((s[2]-'0') * 100) + + ((s[3]-'0') * 10) + + ((s[4]-'0') * 1)); + if (n > 1970 && n < 2100) + year = n; + s += 4; + } + s++; + } + return year; +} + + + /* External interface. */ static conf_data * load_configurator_1 (const char *program, const char *arguments, + void (*changed_cb) (GtkWidget *, gpointer), + gpointer changed_data, gboolean verbose_p) { - const char *dir = hack_configuration_path; + const char *dir = HACK_CONFIGURATION_PATH; char *base_program; int L = strlen (dir); char *file; @@ -1865,10 +1950,11 @@ load_configurator_1 (const char *program, const char *arguments, /* Parsed the XML file. Now make some widgets. */ - vbox0 = gtk_vbox_new (FALSE, 0); + vbox0 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); gtk_widget_show (vbox0); - parms = make_parameters (file, doc->xmlRootNode, vbox0); + parms = make_parameters (file, doc->xmlRootNode, vbox0, + changed_cb, changed_data); sanity_check_parameters (file, parms); xmlFreeDoc (doc); @@ -1880,6 +1966,7 @@ load_configurator_1 (const char *program, const char *arguments, data->widget = vbox0; data->parameters = parms; data->description = get_description (parms, verbose_p); + data->year = get_year (data->description); } else { @@ -1939,14 +2026,17 @@ split_command_line (const char *full_command_line, conf_data * -load_configurator (const char *full_command_line, gboolean verbose_p) +load_configurator (const char *full_command_line, + void (*changed_cb) (GtkWidget *, gpointer), + gpointer changed_data, + 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); + cd = load_configurator_1 (prog, args, changed_cb, changed_data, verbose_p); free (prog); free (args); return cd; @@ -2009,4 +2099,4 @@ free_conf_data (conf_data *data) } -#endif /* HAVE_GTK && HAVE_XML -- whole file */ +#endif /* HAVE_GTK -- whole file */ diff --git a/driver/demo-Gtk-conf.h b/driver/demo-Gtk-conf.h index f462152..ad94ee3 100644 --- a/driver/demo-Gtk-conf.h +++ b/driver/demo-Gtk-conf.h @@ -1,5 +1,5 @@ /* demo-Gtk-conf.c --- implements the dynamic configuration dialogs. - * xscreensaver, Copyright (c) 2001-2008 Jamie Zawinski <jwz@jwz.org> + * xscreensaver, Copyright © 2001-2022 Jamie Zawinski <jwz@jwz.org> * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that @@ -19,13 +19,61 @@ typedef struct { char *progname; char *progclass; char *description; + int year; } conf_data; -extern conf_data *load_configurator (const char *cmd_line, gboolean verbose_p); +/* Referenced by demo-Gtk.c; defined in demo-Gtk-conf.c. + */ +extern conf_data *load_configurator (const char *cmd_line, + void (*changed_cb) (GtkWidget *, gpointer), + gpointer changed_data, + 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; +/* Referenced from demo.ui and prefs.ui; defined in demo-Gtk.c. + */ +extern void quit_menu_cb (GtkAction *, gpointer user_data); +extern void about_menu_cb (GtkAction *, gpointer user_data); +extern void doc_menu_cb (GtkAction *, gpointer user_data); +extern void file_menu_cb (GtkAction *, gpointer user_data); +extern void activate_menu_cb (GtkAction *, gpointer user_data); +extern void lock_menu_cb (GtkAction *, gpointer user_data); +extern void kill_menu_cb (GtkAction *, gpointer user_data); +extern void restart_menu_cb (GtkWidget *, gpointer user_data); +extern void run_this_cb (GtkButton *, gpointer user_data); +extern void manual_cb (GtkButton *, gpointer user_data); +extern void run_next_cb (GtkButton *, gpointer user_data); +extern void run_prev_cb (GtkButton *, gpointer user_data); +extern gboolean pref_changed_cb (GtkWidget *, gpointer user_data); +extern gboolean pref_changed_event_cb (GtkWidget *, GdkEvent *, gpointer data); +extern gboolean dpms_sanity_cb (GtkWidget *, gpointer user_data); +extern gboolean dpms_sanity_event_cb (GtkWidget *, GdkEvent *, gpointer data); +extern gboolean image_text_pref_changed_event_cb (GtkWidget *, GdkEvent *, + gpointer user_data); +extern void mode_menu_item_cb (GtkWidget *, gpointer user_data); +extern void switch_page_cb (GtkNotebook *, GtkWidget *, + gint page_num, gpointer user_data); +extern void browse_image_dir_cb (GtkButton *, gpointer user_data); +extern void browse_text_file_cb (GtkButton *, gpointer user_data); +extern void browse_text_program_cb (GtkButton *, gpointer user_data); +extern void settings_cb (GtkButton *, gpointer user_data); +extern void settings_adv_cb (GtkButton *, gpointer user_data); +extern void settings_std_cb (GtkButton *, gpointer user_data); +extern void settings_reset_cb (GtkButton *, gpointer user_data); +extern void settings_switch_page_cb (GtkNotebook *, GtkWidget *, + gint page_num, gpointer user_data); +extern void settings_cancel_cb (GtkWidget *, gpointer user_data); +extern void settings_ok_cb (GtkWidget *, gpointer user_data); +extern void preview_theme_cb (GtkWidget *, gpointer user_data); + +/* Referenced by demo-Gtk-conf.c; defined in demo-Gtk.c. + */ +extern void warning_dialog (GtkWindow *, const char *title, const char *msg); +extern gboolean file_chooser (GtkWindow *, GtkEntry *, char **retP, + const char *title, + gboolean verbose_p, + gboolean dir_p, gboolean program_p); #endif /* _DEMO_GTK_CONF_H_ */ diff --git a/driver/demo-Gtk.c b/driver/demo-Gtk.c index b5e82e2..4950369 100644 --- a/driver/demo-Gtk.c +++ b/driver/demo-Gtk.c @@ -1,5 +1,5 @@ /* demo-Gtk.c --- implements the interactive demo-mode and options dialogs. - * xscreensaver, Copyright © 1993-2021 Jamie Zawinski <jwz@jwz.org> + * xscreensaver, Copyright © 1993-2024 Jamie Zawinski <jwz@jwz.org> * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that @@ -16,23 +16,6 @@ #ifdef HAVE_GTK /* whole file */ -#include "blurb.h" - -#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 */ @@ -42,32 +25,22 @@ #endif /* HAVE_UNAME */ #include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <ctype.h> #include <time.h> #include <pwd.h> /* for getpwuid() */ #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_XINERAMA -# include <X11/extensions/Xinerama.h> -#endif /* HAVE_XINERAMA */ +#include <X11/Shell.h> #if (__GNUC__ >= 4) /* Ignore useless warnings generated by gtk.h */ # undef inline @@ -76,130 +49,78 @@ # pragma GCC diagnostic ignored "-Wlong-long" # pragma GCC diagnostic ignored "-Wvariadic-macros" # pragma GCC diagnostic ignored "-Wpedantic" +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif #include <gtk/gtk.h> - -#ifdef HAVE_CRAPPLET -# include <gnome.h> -# include <capplet-widget.h> -#endif - -#include <gdk/gdkx.h> - -#ifdef HAVE_GTK2 -# include <gmodule.h> -#else /* !HAVE_GTK2 */ -# define G_MODULE_EXPORT /**/ -#endif /* !HAVE_GTK2 */ - -#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 <gdk/gdkx.h> /* For gdk_x11_get_default_xdisplay(), etc. */ #if (__GNUC__ >= 4) # pragma GCC diagnostic pop #endif - +#include "blurb.h" +#include "xscreensaver-intl.h" #include "version.h" #include "types.h" #include "resources.h" /* for parse_time() */ #include "remote.h" /* for xscreensaver_command() */ +#include "screens.h" #include "visual.h" #include "atoms.h" #include "usleep.h" +#include "atoms.h" +#include "screenshot.h" #include "xmu.h" -#include "logo-50.xpm" -#include "logo-180.xpm" - #include "demo-Gtk-conf.h" -#include "atoms.h" - -#include <stdio.h> -#include <string.h> -#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))) +const char *progclass = "XScreenSaver"; -char *progclass = "XScreenSaver"; -XrmDatabase db; +#ifdef __GNUC__ + __extension__ /* don't warn about "string length is greater than the + length ISO C89 compilers are required to support". */ +#endif +static char *defaults[] = { +#include "XScreenSaver_ad.h" + 0 +}; /* 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 }; +enum { COL_ENABLED, COL_NAME, COL_LAST }; +typedef enum { D_NONE, D_LAUNCH, D_GNOME, D_KDE } dialog_button; 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 */ + GtkWindow *window; + GtkWindow *dialog; + + Display *dpy; + Bool wayland_p; + Pixmap screenshot; + Visual *gl_visual; -#ifdef HAVE_GTK2 - GtkBuilder *gtk_ui; /* UI file */ -#endif /* HAVE_GTK2 */ + conf_data *cdata; /* private data for per-hack configuration */ Bool debug_p; /* whether to print diagnostics */ Bool initializing_p; /* flag for breaking recursion loops */ + Bool flushing_p; /* flag for breaking recursion loops */ Bool saving_p; /* flag for breaking recursion loops */ + Bool dpms_supported_p; /* Whether XDPMS is available */ char *desired_preview_cmd; /* subprocess we intend to run */ char *running_preview_cmd; /* subprocess we are currently running */ @@ -222,16 +143,149 @@ typedef struct { int _selected_list_element; /* don't use this: call selected_list_element() instead */ - int nscreens; /* How many X or Xinerama screens there are */ + Bool multi_screen_p; /* Is there more than one monitor */ 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; +/* Class definitions for the application and two windows. The classes are: + + XScreenSaverApp -- The invisible GtkApplication main-loop framework. + XScreenSaverWindow -- The main window with the scrolling list of hacks. + XScreenSaverDialog -- The per-hack settings window. + */ +#define XSCREENSAVER_APP_TYPE (xscreensaver_app_get_type()) +G_DECLARE_FINAL_TYPE (XScreenSaverApp, xscreensaver_app, XSCREENSAVER, APP, + GtkApplication) + +struct _XScreenSaverApp { + GtkApplication parent; + Bool cmdline_debug_p; +}; + + +G_DEFINE_TYPE (XScreenSaverApp, xscreensaver_app, GTK_TYPE_APPLICATION) + +/* The widgets we reference from the demo.ui file. + */ +#define ALL_WINDOW_WIDGETS \ + W(activate_menuitem) \ + W(lock_menuitem) \ + W(kill_menuitem) \ + W(restart_menuitem) \ + W(list) \ + W(scroller) \ + W(preview_frame) \ + W(short_preview_label) \ + W(preview_author_label) \ + W(timeout_spinbutton) \ + W(cycle_spinbutton) \ + W(lock_spinbutton) \ + W(dpms_standby_spinbutton) \ + W(dpms_suspend_spinbutton) \ + W(dpms_off_spinbutton) \ + W(fade_spinbutton) \ + W(lock_button) \ + W(dpms_button) \ + W(dpms_quickoff_button) \ + W(grab_desk_button) \ + W(grab_video_button) \ + W(grab_image_button) \ + W(fade_button) \ + W(unfade_button) \ + W(preview) \ + W(preview_notebook) \ + W(text_radio) \ + W(text_file_radio) \ + W(text_file_browse) \ + W(text_program_radio) \ + W(text_url_radio) \ + W(text_host_radio) \ + W(image_text) \ + W(image_browse_button) \ + W(text_entry) \ + W(text_file_entry) \ + W(text_program_entry) \ + W(text_url_entry) \ + W(text_program_browse) \ + W(theme_menu) \ + W(mode_menu) \ + W(next_prev_hbox) \ + W(blanking_table) \ + W(lock_mlabel) \ + W(dpms_frame) \ + W(dpms_standby_label) \ + W(dpms_standby_mlabel) \ + W(dpms_suspend_label) \ + W(dpms_suspend_mlabel) \ + W(dpms_off_label) \ + W(dpms_off_mlabel) \ + W(fade_label) \ + W(demo) \ + W(settings) \ + +/* The widgets we reference from the prefs.ui file. + */ +#define ALL_DIALOG_WIDGETS \ + W(opt_notebook) \ + W(doc) \ + W(settings_vbox) \ + W(cmd_text) \ + W(opt_frame) \ + W(dialog_vbox) \ + W(adv_button) \ + W(std_button) \ + W(cmd_label) \ + W(manual) \ + W(visual) \ + W(visual_combo) \ + W(reset_button) \ + W(ok_button) \ + +#define XSCREENSAVER_WINDOW_TYPE (xscreensaver_window_get_type()) +G_DECLARE_FINAL_TYPE (XScreenSaverWindow, xscreensaver_window, + XSCREENSAVER, WINDOW, GtkApplicationWindow) + +struct _XScreenSaverWindow { + GtkApplicationWindow parent; + state state; + + GtkWidget +# undef W +# define W(NAME) * NAME, + ALL_WINDOW_WIDGETS + *_dummy; +# undef W +}; + +G_DEFINE_TYPE (XScreenSaverWindow, xscreensaver_window, + GTK_TYPE_APPLICATION_WINDOW) + + +#define XSCREENSAVER_DIALOG_TYPE (xscreensaver_dialog_get_type()) +G_DECLARE_FINAL_TYPE (XScreenSaverDialog, xscreensaver_dialog, + XSCREENSAVER, DIALOG, GtkDialog) + +struct _XScreenSaverDialog { + GtkApplicationWindow parent; + XScreenSaverWindow *main; + char *unedited_cmdline; /* Current hack command line before saving */ + + GtkWidget +# undef W +# define W(NAME) * NAME, + ALL_DIALOG_WIDGETS + *_dummy; +# undef W +}; + +G_DEFINE_TYPE (XScreenSaverDialog, xscreensaver_dialog, + GTK_TYPE_DIALOG) + + +static void hack_subproc_environment (Window preview_window_id, Bool debug_p); static void populate_demo_window (state *, int list_elt); static void populate_prefs_page (state *); @@ -239,418 +293,379 @@ static void populate_popup_window (state *); static Bool flush_dialog_changes_and_save (state *); static Bool flush_popup_changes_and_save (state *); +static Bool validate_image_directory (state *, const char *path); 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 *); +static void sensitize_demo_widgets (state *, Bool sensitive_p); +static void kill_gnome_screensaver (state *); +static void kill_kde_screensaver (state *); - -/* Prototypes of functions used by the Gtk-generated code, to avoid warnings. - */ -void exit_menu_cb (GtkAction *, gpointer user_data); -void about_menu_cb (GtkAction *, gpointer user_data); -void doc_menu_cb (GtkAction *, gpointer user_data); -void file_menu_cb (GtkAction *, gpointer user_data); -void activate_menu_cb (GtkAction *, gpointer user_data); -void lock_menu_cb (GtkAction *, gpointer user_data); -void kill_menu_cb (GtkAction *, gpointer user_data); -void restart_menu_cb (GtkWidget *, gpointer user_data); -void run_this_cb (GtkButton *, gpointer user_data); -void manual_cb (GtkButton *, gpointer user_data); -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); -void preview_theme_cb (GtkWidget *, gpointer user_data); - -static void kill_gnome_screensaver (void); -static void kill_kde_screensaver (void); +/* Some pathname utilities */ -/* Some random utility functions +/* Removed redundant . and .. components from the pathname. + Strip leading and trailing spaces. + Make it have a trailing slash if it should be a directory. */ - -static GtkWidget * -name_to_widget (state *s, const char *name) +static char * +normalize_pathname (const char *path, gboolean dir_p) { - GtkWidget *w; - if (!s) abort(); - if (!name) abort(); - if (!*name) abort(); + int L; + char *p2, *s; + if (!path) return 0; + if (!*path) return strdup (""); -#ifdef HAVE_GTK2 - if (!s->gtk_ui) - { - /* First try to load the UI file from the current directory; - if there isn't one there, check the installed directory. - */ -# define UI_FILE "xscreensaver.ui" - const char * const files[] = { UI_FILE, - DEFAULT_ICONDIR "/" UI_FILE }; - int i; + /* Strip leading spaces */ + while (isspace (*path)) path++; - s->gtk_ui = gtk_builder_new (); + L = strlen (path); + p2 = (char *) malloc (L + 3); + strcpy (p2, path); - for (i = 0; i < countof (files); i++) + /* Strip trailing spaces and slashes */ + while (L > 0 && (isspace (p2[L-1]) || p2[L-1] == '/')) + p2[--L] = 0; + + for (s = p2; s && *s; s++) + { + if (*s == '/' && + (!strncmp (s, "/../", 4) || /* delete "XYZ/../" */ + !strncmp (s, "/..\000", 4))) /* delete "XYZ/..$" */ { - struct stat st; - if (!stat (files[i], &st)) + char *s0 = s; + while (s0 > p2 && s0[-1] != '/') + s0--; + if (s0 > p2) { - GError* error = NULL; - - if (gtk_builder_add_from_file (s->gtk_ui, files[i], &error)) - break; - else - { - g_warning ("Couldn't load builder file %s: %s", - files[i], error->message); - g_error_free (error); - } + s0--; + s += 3; + /* strcpy (s0, s); */ + memmove(s0, s, strlen(s) + 1); + s = s0-1; } } - if (i >= countof (files)) - { - fprintf (stderr, - "%s: could not load \"" UI_FILE "\"\n" - "\tfrom " DEFAULT_ICONDIR "/ or current directory.\n", - blurb()); - exit (-1); - } -# undef UI_FILE - - gtk_builder_connect_signals (s->gtk_ui, NULL); + 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--; } - w = GTK_WIDGET (gtk_builder_get_object (s->gtk_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 UI 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); + /* + Normalize consecutive slashes. + Ignore doubled slashes after ":" to avoid mangling URLs. + */ -#else /* !HAVE_GTK2 */ + 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); + } - 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; + /* and strip trailing whitespace for good measure. */ + L = strlen(p2); + while (isspace(p2[L-1])) + p2[--L] = 0; - 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)) + if (dir_p) { - list_widget = GTK_LIST (widget); - vp = GTK_VIEWPORT (GTK_WIDGET (list_widget)->parent); - scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent); + p2[L++] = '/'; /* Add trailing slash */ + p2[L] = 0; } - 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)); + return p2; +} - 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; +/* Expand or un-expand ~/ to $HOME in a pathname, as requested. + Strip leading and trailing spaces. + Make it have a trailing slash if it should be a directory. + */ +static char * +pathname_tilde (const char *p, gboolean add_p, gboolean dir_p) +{ + char *p2; + if (!p) return 0; - ratio_t = ((double) child_y) / ((double) children_h); - ratio_b = ((double) child_y + child_h) / ((double) children_h); + if (dir_p && + (!strncasecmp (p, "http://", 7) || + !strncasecmp (p, "https://", 8))) + dir_p = FALSE; - if (adj->upper == 0.0) /* no items in list */ - return; + p2 = normalize_pathname (p, dir_p); + p = p2; - if (ratio_t < (adj->value / adj->upper) || - ratio_b > ((adj->value + adj->page_size) / adj->upper)) + if (add_p) { - double target; - int slop = parent_h * 0.75; /* how much to overshoot by */ - - if (ratio_t < (adj->value / adj->upper)) + const char *home = getenv("HOME"); + int L = strlen(home); + if (!strncmp (home, p, L) && p[L] == '/') { - 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; + char *p3 = (char *) malloc (strlen (p) + 4); + strcpy (p3, "~"); + strcat (p3, p + L); + free (p2); + p2 = p3; } - 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 */ -} + else if (!strncmp (p, "~/", 2)) + { + const char *home = getenv("HOME"); + char *p3 = (char *) malloc (strlen (p) + strlen (home) + 4); + strcpy (p3, home); + strcat (p3, p + 1); + free (p2); + p2 = p3; + } -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)); + return p2; } -void restart_menu_cb (GtkWidget *widget, gpointer user_data); - -static void warning_dialog_restart_cb (GtkWidget *widget, gpointer user_data) +/* Is the path a directory that exists? */ +static gboolean +directory_p (const char *path) { - restart_menu_cb (widget, user_data); - warning_dialog_dismiss_cb (widget, user_data); -} + char *p2 = pathname_tilde (path, FALSE, FALSE); /* no slash on dir */ + struct stat st; + gboolean ok = FALSE; -static void warning_dialog_killg_cb (GtkWidget *widget, gpointer user_data) -{ - kill_gnome_screensaver (); - warning_dialog_dismiss_cb (widget, user_data); + if (!p2 || !*p2) + ok = FALSE; + else if (stat (p2, &st)) + ok = FALSE; + else if (!S_ISDIR (st.st_mode)) + ok = FALSE; + else + ok = TRUE; + free (p2); + return ok; } -static void warning_dialog_killk_cb (GtkWidget *widget, gpointer user_data) + +/* Is the path a file (not directory) that exists? */ +static gboolean +file_p (const char *path) { - kill_kde_screensaver (); - warning_dialog_dismiss_cb (widget, user_data); + char *p2 = pathname_tilde (path, FALSE, FALSE); + struct stat st; + gboolean ok = FALSE; + if (!p2 || !*p2) + ok = FALSE; + else if (stat (p2, &st)) + ok = FALSE; + else if (S_ISDIR (st.st_mode)) + ok = FALSE; + else + ok = TRUE; + free (p2); + return ok; } -typedef enum { D_NONE, D_LAUNCH, D_GNOME, D_KDE } dialog_button; + +/* See if the directory has at least one image file under it. + Recurses to at most the given depth, chasing symlinks. + To do this properly would mean running "xscreensaver-getimage-file" + and seeing if it found anything, but that might take a long time to + build the cache the first time, so this is close enough. + */ static Bool -warning_dialog (GtkWidget *parent, const char *message, - dialog_button button_type, int center) +image_files_p (const char *path, int max_depth) { - char *msg = strdup (message); - char *head; + const char * const exts[] = { + "jpg", "jpeg", "pjpeg", "pjpg", "png", "gif", + "tif", "tiff", "xbm", "xpm", "svg", + }; + struct dirent *de; + Bool ok = FALSE; + char *p2 = pathname_tilde (path, FALSE, FALSE); /* no slash on dir */ + DIR *dir = opendir (p2); + if (!dir) goto DONE; + + while (!ok && (de = readdir (dir))) + { + struct stat st; + const char *f = de->d_name; + char *f2; + if (*f == '.') continue; - GtkWidget *dialog = gtk_dialog_new (); - GtkWidget *label = 0; - GtkWidget *ok = 0; - GtkWidget *cancel = 0; - int i = 0; + f2 = (char *) malloc (strlen(p2) + strlen(f) + 10); + strcpy (f2, p2); + strcat (f2, "/"); + strcat (f2, f); - while (parent && !GET_WINDOW (parent)) - parent = GET_PARENT (parent); + if (!stat (f2, &st)) + { + if (S_ISDIR (st.st_mode)) + { + if (max_depth > 0 && image_files_p (f2, max_depth - 1)) + ok = TRUE; + } + else + { + int i; + const char *ext = strrchr (f, '.'); + if (ext) + for (i = 0; i < countof(exts); i++) + if (!strcasecmp (ext+1, exts[i])) + { + /* fprintf (stderr, "%s: found %s\n", blurb(), f2); */ + ok = TRUE; + break; + } + } + } - if (!parent || - !GET_WINDOW (parent)) /* too early to pop up transient dialogs */ - { - fprintf (stderr, - "%s: too early for warning dialog?" - "\n\n\t%s\n\n", - progname, message); - free(msg); - return False; + free (f2); } - head = msg; - while (head) - { - char name[20]; - char *s = strchr (head, '\n'); - if (s) *s = 0; + closedir (dir); + DONE: + free (p2); + return ok; +} - sprintf (name, "label%d", i++); - { - label = gtk_label_new (head); -#ifdef HAVE_GTK2 - gtk_label_set_selectable (GTK_LABEL (label), TRUE); -#endif /* HAVE_GTK2 */ +/* Some random utility functions + */ -#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); - } +/* Why this behavior isn't automatic, I'll never understand. + */ +static void +ensure_selected_item_visible (state *s, GtkWidget *widget) +{ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); - if (s) - head = s+1; - else - head = 0; + /* Find the path of the selected row in the list. + */ + GtkTreeView *list_widget = GTK_TREE_VIEW (win->list); + GtkTreeSelection *selection = gtk_tree_view_get_selection (list_widget); + GtkTreeModel *model; + GtkTreeIter iter; + GtkTreePath *path; - center--; - } + if (!gtk_tree_selection_get_selected (selection, &model, &iter)) + path = gtk_tree_path_new_first (); + else + path = gtk_tree_model_get_path (model, &iter); - label = gtk_label_new (""); - gtk_box_pack_start (GTK_BOX (GET_CONTENT_AREA (GTK_DIALOG (dialog))), - label, TRUE, TRUE, 0); - gtk_widget_show (label); + /* Make this item be visible and selected. */ + gtk_tree_view_set_cursor (list_widget, path, NULL, FALSE); - label = gtk_hbutton_box_new (); - gtk_box_pack_start (GTK_BOX (GET_ACTION_AREA (GTK_DIALOG (dialog))), - label, TRUE, TRUE, 0); + /* Make the scroller show that item at the center of the viewport. + The set_cursor() call, above, makes the item be visible, but it hugs + the top or bottom edge of the viewport, instead of providing more + surrounding context. -#ifdef HAVE_GTK2 - if (button_type != D_NONE) + Doing the following in list_select_changed_cb() instead of here makes + the list vertically re-center when using the cursor keys instead of + hugging the top or bottom (good) but also makes it re-center when + clicking on a new item with the mouse (bad). + */ + if (gtk_widget_get_realized (GTK_WIDGET (list_widget))) { - cancel = gtk_button_new_from_stock (GTK_STOCK_CANCEL); - gtk_container_add (GTK_CONTAINER (label), cancel); + GdkWindow *bin = gtk_tree_view_get_bin_window (list_widget); + int binh = gdk_window_get_height (bin); + GdkRectangle r; + gtk_tree_view_get_cell_area (list_widget, path, NULL, &r); + gtk_tree_view_convert_widget_to_tree_coords (list_widget, + r.x, r.y, &r.x, &r.y); + gtk_tree_view_scroll_to_point (list_widget, r.x, r.y - binh / 2); } - 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); - } + gtk_tree_path_free (path); +} -#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); +static void +warning_dialog_cb (GtkDialog *dialog, gint response_id, gpointer user_data) +{ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; + if (s->debug_p) + fprintf (stderr, "%s: dialog response %d\n", blurb(), response_id); + switch (response_id) { + case D_LAUNCH: restart_menu_cb (GTK_WIDGET (dialog), user_data); break; + case D_GNOME: kill_gnome_screensaver (s); break; + case D_KDE: kill_kde_screensaver (s); break; + default: /* D_NONE or GTK_RESPONSE_DELETE_EVENT */ + break; + } + gtk_widget_destroy (GTK_WIDGET (dialog)); +} - 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); - } +static Bool +warning_dialog_1 (GtkWindow *win, + const char *title, + const char *message, + dialog_button button_type) +{ + GtkWidget *dialog = + (button_type == D_NONE + ? gtk_dialog_new_with_buttons (title, win, + GTK_DIALOG_DESTROY_WITH_PARENT, + _("_OK"), D_NONE, + NULL) + : gtk_dialog_new_with_buttons (title, win, + GTK_DIALOG_DESTROY_WITH_PARENT, + (button_type == D_LAUNCH ? _("Launch") : + button_type == D_GNOME ? _("Kill") : + button_type == D_KDE ? _("Kill") : + _("_OK")), + button_type, + _("_Cancel"), D_NONE, + NULL)); + GtkWidget *content_area = + gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + GtkWidget *hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + GtkWidget *vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + GtkWidget *im = gtk_image_new_from_icon_name ("dialog-warning", + GTK_ICON_SIZE_DIALOG); + GtkWidget *label = gtk_label_new (message); + int margin = 32; + + gtk_box_pack_start (GTK_BOX (hbox), im, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + gtk_container_add (GTK_CONTAINER (content_area), hbox); + + gtk_widget_set_margin_start (hbox, margin); + gtk_widget_set_margin_end (hbox, margin); + gtk_widget_set_margin_top (hbox, margin); + gtk_widget_set_margin_bottom (hbox, margin / 2); + + gtk_widget_set_margin_start (label, margin / 2); + gtk_widget_set_valign (im, GTK_ALIGN_START); + + g_signal_connect (dialog, "response", + G_CALLBACK (warning_dialog_cb), + win); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), button_type); + gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); + gtk_window_set_transient_for (GTK_WINDOW (dialog), win); + gtk_widget_show_all (dialog); - gdk_window_set_transient_for (GET_WINDOW (GTK_WIDGET (dialog)), - GET_WINDOW (GTK_WIDGET (parent))); + return TRUE; +} -#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; +void +warning_dialog (GtkWindow *win, const char *title, const char *message) +{ + warning_dialog_1 (win, title, message, D_NONE); } @@ -660,8 +675,13 @@ run_cmd (state *s, Atom command, int arg) char *err = 0; int status; + if (!s->dpy) return; flush_dialog_changes_and_save (s); - status = xscreensaver_command (GDK_DISPLAY(), command, arg, False, &err); + + if (s->debug_p) + fprintf (stderr, "%s: command: %s %d\n", blurb(), + XGetAtomName (s->dpy, command), arg); + status = xscreensaver_command (s->dpy, command, arg, FALSE, &err); /* Kludge: ignore the spurious "window unexpectedly deleted" errors... */ if (status < 0 && err && strstr (err, "unexpectedly deleted")) @@ -670,16 +690,12 @@ run_cmd (state *s, Atom command, int arg) 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); + sprintf (buf, "%.100s", (err ? err : _("Unknown error!"))); + warning_dialog (s->window, _("Error"), buf); } if (err) free (err); - sensitize_menu_items (s, True); - force_dialog_repaint (s); + sensitize_menu_items (s, TRUE); } @@ -690,14 +706,17 @@ run_hack (state *s, int list_elt, Bool report_errors_p) char *err = 0; int status; + if (!s->dpy) return; 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 (s->debug_p) + fprintf (stderr, "%s: command: DEMO %d\n", blurb(), hack_number + 1); + status = xscreensaver_command (s->dpy, XA_DEMO, hack_number + 1, + FALSE, &err); if (status < 0 && report_errors_p) { @@ -711,11 +730,8 @@ run_hack (state *s, int list_elt, Bool report_errors_p) 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); + sprintf (buf, "%.100s", err ? err : _("Unknown error!")); + warning_dialog (s->window, _("Error"), buf); } } else @@ -723,283 +739,232 @@ run_hack (state *s, int list_elt, Bool report_errors_p) /* The error is that the daemon isn't running; offer to restart it. */ - const char *d = DisplayString (GDK_DISPLAY()); + const char *d = DisplayString (s->dpy); char msg [1024]; sprintf (msg, - _("Warning:\n\n" - "The XScreenSaver daemon doesn't seem to be running\n" - "on display \"%s\". Launch it now?"), + _("The XScreenSaver daemon doesn't seem to be running\n" + "on display \"%.25s\". Launch it now?"), d); - warning_dialog (s->toplevel_widget, msg, D_LAUNCH, 1); + warning_dialog_1 (s->window, _("Warning"), msg, D_LAUNCH); } } if (err) free (err); - sensitize_menu_items (s, False); + sensitize_menu_items (s, FALSE); } - -/* Button callbacks +static pid_t +fork_and_exec (state *s, int argc, char **argv) +{ + char buf [255]; + pid_t forked = fork(); + switch ((int) forked) { + case -1: + sprintf (buf, "%s: couldn't fork", blurb()); + perror (buf); + break; + + case 0: + if (s->dpy) close (ConnectionNumber (s->dpy)); + execvp (argv[0], argv); /* shouldn't return. */ + + sprintf (buf, "%s: pid %lu: couldn't exec %s", blurb(), + (unsigned long) getpid(), argv[0]); + perror (buf); + exit (1); /* exits child fork */ + break; + + default: /* parent fork */ + + /* Put it in its own process group so that this process getting SIGTERM + does not propagate to the forked process. */ + if (setpgid (forked, 0)) + { + char buf [255]; + sprintf (buf, "%s: setpgid %d", blurb(), forked); + perror (buf); + } + + if (s->debug_p) + { + int i; + fprintf (stderr, "%s: pid %lu: forked:", blurb(), + (unsigned long) forked); + for (i = 0; i < argc; i++) + if (strchr (argv[i], ' ')) + fprintf (stderr, " \"%s\"", argv[i]); + else + fprintf (stderr, " %s", argv[i]); + fprintf (stderr, "\n"); + } - According to Eric Lassauge, this G_MODULE_EXPORT crud is needed to make - GTK work on Cygwin; apparently all GTK callbacks need this magic extra - declaration. I do not pretend to understand. - */ + break; + } -G_MODULE_EXPORT void -exit_menu_cb (GtkAction *menu_action, gpointer user_data) -{ - state *s = global_state_kludge; /* I hate C so much... */ - flush_dialog_changes_and_save (s); - kill_preview_subproc (s, False); - gtk_main_quit (); + return forked; } -static gboolean -wm_toplevel_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data) + + +/**************************************************************************** + + XScreenSaverWindow callbacks, referenced by demo.ui. + + ****************************************************************************/ + +/* File menu / Quit */ +G_MODULE_EXPORT void +quit_menu_cb (GtkAction *menu_action, gpointer user_data) { - state *s = (state *) data; + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; + if (s->debug_p) fprintf (stderr, "%s: quit menu\n", blurb()); flush_dialog_changes_and_save (s); - gtk_main_quit (); - return TRUE; + kill_preview_subproc (s, FALSE); + g_application_quit (G_APPLICATION ( + gtk_window_get_application (GTK_WINDOW (win)))); } +/* Help menu / About */ G_MODULE_EXPORT void about_menu_cb (GtkAction *menu_action, gpointer user_data) { -#if 1 - /* Let's just pop up the splash dialog instead. */ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; + if (s->debug_p) fprintf (stderr, "%s: about menu\n", blurb()); preview_theme_cb (NULL, user_data); -#else - char msg [2048]; - char copy[1024]; - char *desc = _("For updates, check https://www.jwz.org/xscreensaver/"); - - char *version = strdup (screensaver_id + 17); - char *year = strchr (version, '-'); - char *s = strchr (version, ' '); - *s = 0; - year = strchr (year+1, '-') + 1; - s = strchr (year, ')'); - *s = 0; - - /* 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, version, "", 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; - GSList *proxies = gtk_action_get_proxies (menu_action); - GtkWidget *parent = GTK_WIDGET (proxies->data); - 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 (version); - gtk_box_pack_start (GTK_BOX (vbox), label1, TRUE, TRUE, 0); - gtk_label_set_justify (GTK_LABEL (label1), GTK_JUSTIFY_LEFT); - gtk_misc_set_alignment (GTK_MISC (label1), 0.0, 0.75); - -#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))); - } -#endif /* 0 */ } +/* Help menu / Documentation */ G_MODULE_EXPORT void doc_menu_cb (GtkAction *menu_action, gpointer user_data) { - state *s = global_state_kludge; /* I hate C so much... */ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; saver_preferences *p = &s->prefs; char *help_command; + int ac = 0; + char *av[10]; + + if (s->debug_p) fprintf (stderr, "%s: doc menu\n", blurb()); 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); + warning_dialog (GTK_WINDOW (win), _("Error"), + _("No Help URL has been specified.\n")); return; } help_command = (char *) malloc (strlen (p->load_url_command) + (strlen (p->help_url) * 5) + 20); - strcpy (help_command, "( "); - sprintf (help_command + strlen(help_command), - p->load_url_command, + sprintf (help_command, p->load_url_command, p->help_url, p->help_url, p->help_url, p->help_url, p->help_url); - strcat (help_command, " ) &"); - if (system (help_command) < 0) - fprintf (stderr, "%s: fork error\n", blurb()); + + av[ac++] = "/bin/sh"; + av[ac++] = "-c"; + av[ac++] = help_command; + av[ac] = 0; + fork_and_exec (s, ac, av); free (help_command); } +/* File menu opened */ G_MODULE_EXPORT void file_menu_cb (GtkAction *menu_action, gpointer user_data) { - state *s = global_state_kludge; /* I hate C so much... */ - sensitize_menu_items (s, False); + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; + if (s->debug_p) fprintf (stderr, "%s: file menu post\n", blurb()); + sensitize_menu_items (s, FALSE); } +/* File menu / Activate */ G_MODULE_EXPORT void activate_menu_cb (GtkAction *menu_action, gpointer user_data) { - state *s = global_state_kludge; /* I hate C so much... */ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; + if (s->debug_p) fprintf (stderr, "%s: activate menu\n", blurb()); run_cmd (s, XA_ACTIVATE, 0); } +/* File menu / Lock */ G_MODULE_EXPORT void lock_menu_cb (GtkAction *menu_action, gpointer user_data) { - state *s = global_state_kludge; /* I hate C so much... */ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; + if (s->debug_p) fprintf (stderr, "%s: lock menu\n", blurb()); run_cmd (s, XA_LOCK, 0); } +/* File menu / Kill daemon */ G_MODULE_EXPORT void kill_menu_cb (GtkAction *menu_action, gpointer user_data) { - state *s = global_state_kludge; /* I hate C so much... */ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; + if (s->debug_p) fprintf (stderr, "%s: kill menu\n", blurb()); run_cmd (s, XA_EXIT, 0); } +/* File menu / Restart */ G_MODULE_EXPORT void restart_menu_cb (GtkWidget *widget, gpointer user_data) { - state *s = global_state_kludge; /* I hate C so much... */ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; + int ac = 0; + char *av[10]; + if (s->debug_p) fprintf (stderr, "%s: restart menu\n", blurb()); + if (!s->dpy) return; flush_dialog_changes_and_save (s); - xscreensaver_command (GDK_DISPLAY(), XA_EXIT, 0, False, NULL); + xscreensaver_command (s->dpy, XA_EXIT, 0, FALSE, NULL); sleep (1); - if (system ("xscreensaver -splash &") < 0) - fprintf (stderr, "%s: fork error\n", blurb()); + av[ac++] = "xscreensaver"; + av[ac++] = "--splash"; + if (s->debug_p) av[ac++] = "--verbose"; + av[ac] = 0; + fork_and_exec (s, ac, av); 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 (!s->dpy) return FALSE; + server_xscreensaver_version (s->dpy, &rversion, 0, 0); if (!rversion) - return False; + return FALSE; free (rversion); - return True; + return TRUE; } static void await_xscreensaver (state *s) { int countdown = 5; - Bool ok = False; + Bool ok = FALSE; while (!ok && (--countdown > 0)) if (xscreensaver_running_p (s)) - ok = True; + ok = TRUE; else sleep (1); /* If it's not there yet, wait a second... */ - sensitize_menu_items (s, True); + sensitize_menu_items (s, TRUE); if (! ok) { @@ -1009,37 +974,19 @@ await_xscreensaver (state *s) Bool root_p = (geteuid () == 0); strcpy (buf, - _("Error:\n\n" - "The xscreensaver daemon did not start up properly.\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 `sudo' as necessary.")); + strcat (buf, + _("You are running as root. Don't do that. Instead, you should\n" + "log in as a normal user and use `sudo' as necessary.") + ); else strcat (buf, _("Please check your $PATH and permissions.")); - warning_dialog (s->toplevel_widget, buf, D_NONE, 1); + warning_dialog (s->window, _("Error"), buf); } - - force_dialog_repaint (s); } @@ -1050,12 +997,14 @@ selected_list_element (state *s) } +/* Write the settings to disk; call this only when changes have been made. + */ static int demo_write_init_file (state *s, saver_preferences *p) { - Display *dpy = GDK_DISPLAY(); + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); - if (!write_init_file (dpy, p, s->short_version, False)) + if (!write_init_file (s->dpy, p, s->short_version, FALSE)) { if (s->debug_p) fprintf (stderr, "%s: wrote %s\n", blurb(), init_file_name()); @@ -1065,14 +1014,13 @@ demo_write_init_file (state *s, saver_preferences *p) { 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); + warning_dialog (GTK_WINDOW (win), _("Error"), + _("Couldn't determine init file name!\n")); 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); + sprintf (b, _("Couldn't write %s\n"), f); + warning_dialog (GTK_WINDOW (win), _("Error"), b); free (b); } return -1; @@ -1080,106 +1028,57 @@ demo_write_init_file (state *s, saver_preferences *p) } +/* The "Preview" button on the main page. */ G_MODULE_EXPORT void run_this_cb (GtkButton *button, gpointer user_data) { - state *s = global_state_kludge; /* I hate C so much... */ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; 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]; - + if (s->debug_p) fprintf (stderr, "%s: preview button\n", blurb()); 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); + run_hack (s, list_elt, TRUE); } 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 + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); + GtkWidget *parent = win->scroller; + gboolean was = gtk_widget_get_sensitive (parent); GtkTreeIter iter; GtkTreeModel *model; GtkTreeSelection *selection; -#endif /* HAVE_GTK2 */ - if (!was) gtk_widget_set_sensitive (parent, True); -#ifdef HAVE_GTK2 + if (!was) gtk_widget_set_sensitive (parent, TRUE); model = gtk_tree_view_get_model (GTK_TREE_VIEW (list)); if (!model) abort(); if (gtk_tree_model_iter_nth_child (model, &iter, NULL, list_elt)) { selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list)); gtk_tree_selection_select_iter (selection, &iter); + if (s->debug_p) + fprintf (stderr, "%s: select list elt %d\n", blurb(), list_elt); } -#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); + if (scroll_p) ensure_selected_item_visible (s, GTK_WIDGET (list)); + if (!was) gtk_widget_set_sensitive (parent, FALSE); } +/* The down arrow */ 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; */ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; Bool ops = s->preview_suppressed_p; - - GtkWidget *list_widget = name_to_widget (s, "list"); + GtkWidget *list_widget = win->list; int list_elt = selected_list_element (s); + if (s->debug_p) fprintf (stderr, "%s: down arrow\n", blurb()); + if (list_elt < 0) list_elt = 0; else @@ -1188,27 +1087,30 @@ run_next_cb (GtkButton *button, gpointer user_data) if (list_elt >= s->list_count) list_elt = 0; - s->preview_suppressed_p = True; + s->preview_suppressed_p = TRUE; flush_dialog_changes_and_save (s); - force_list_select_item (s, list_widget, list_elt, True); + force_list_select_item (s, list_widget, list_elt, TRUE); populate_demo_window (s, list_elt); - run_hack (s, list_elt, False); + populate_popup_window (s); + run_hack (s, list_elt, FALSE); s->preview_suppressed_p = ops; } +/* The up arrow */ 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; */ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; Bool ops = s->preview_suppressed_p; - - GtkWidget *list_widget = name_to_widget (s, "list"); + GtkWidget *list_widget = win->list; int list_elt = selected_list_element (s); + if (s->debug_p) fprintf (stderr, "%s: up arrow\n", blurb()); + if (list_elt < 0) list_elt = s->list_count - 1; else @@ -1217,19 +1119,20 @@ run_prev_cb (GtkButton *button, gpointer user_data) if (list_elt < 0) list_elt = s->list_count - 1; - s->preview_suppressed_p = True; + s->preview_suppressed_p = TRUE; flush_dialog_changes_and_save (s); - force_list_select_item (s, list_widget, list_elt, True); + force_list_select_item (s, list_widget, list_elt, TRUE); populate_demo_window (s, list_elt); - run_hack (s, list_elt, False); + populate_popup_window (s); + 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. +/* Writes the settings of the given hack 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 @@ -1240,7 +1143,7 @@ flush_changes (state *s, const char *visual) { saver_preferences *p = &s->prefs; - Bool changed = False; + Bool changed = FALSE; screenhack *hack; int hack_number; if (list_elt < 0 || list_elt >= s->list_count) @@ -1253,7 +1156,7 @@ flush_changes (state *s, enabled_p != hack->enabled_p) { hack->enabled_p = enabled_p; - changed = True; + changed = TRUE; if (s->debug_p) fprintf (stderr, "%s: \"%s\": enabled => %d\n", blurb(), hack->name, enabled_p); @@ -1265,7 +1168,7 @@ flush_changes (state *s, { if (hack->command) free (hack->command); hack->command = strdup (command); - changed = True; + changed = TRUE; if (s->debug_p) fprintf (stderr, "%s: \"%s\": command => \"%s\"\n", blurb(), hack->name, command); @@ -1281,7 +1184,7 @@ flush_changes (state *s, { if (hack->visual) free (hack->visual); hack->visual = strdup (visual); - changed = True; + changed = TRUE; if (s->debug_p) fprintf (stderr, "%s: \"%s\": visual => \"%s\"\n", blurb(), hack->name, visual); @@ -1302,7 +1205,7 @@ hack_time_text (state *s, const char *line, Time *store, Bool sec_p) { int value; if (!sec_p || strchr (line, ':')) - value = parse_time ((char *) line, sec_p, True); + value = parse_time ((char *) line, sec_p, TRUE); else { char c; @@ -1316,11 +1219,9 @@ hack_time_text (state *s, const char *line, Time *store, Bool sec_p) if (value < 0) { char b[255]; - sprintf (b, - _("Error:\n\n" - "Unparsable time format: \"%s\"\n"), + sprintf (b, _("Unparsable time format: \"%.100s\"\n"), line); - warning_dialog (s->toplevel_widget, b, D_NONE, 100); + warning_dialog (s->window, _("Error"), b); } else *store = value; @@ -1328,97 +1229,6 @@ hack_time_text (state *s, const char *line, Time *store, Bool sec_p) } -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; @@ -1426,10 +1236,10 @@ typedef struct { } FlushForeachClosure; static gboolean -flush_checkbox (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer data) +flush_checkbox (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) { FlushForeachClosure *closure = data; gboolean checked; @@ -1440,7 +1250,7 @@ flush_checkbox (GtkTreeModel *model, if (flush_changes (closure->s, closure->i, checked, 0, 0)) - *closure->changed = True; + *closure->changed = TRUE; closure->i++; @@ -1448,8 +1258,6 @@ flush_checkbox (GtkTreeModel *model, return FALSE; } -#endif /* HAVE_GTK2 */ - static char * theme_name_strip (const char *s) @@ -1476,113 +1284,80 @@ theme_name_strip (const char *s) static Bool flush_dialog_changes_and_save (state *s) { - Display *dpy = GDK_DISPLAY(); saver_preferences *p = &s->prefs; saver_preferences P2, *p2 = &P2; -#ifdef HAVE_GTK2 - GtkTreeView *list_widget = GTK_TREE_VIEW (name_to_widget (s, "list")); + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); + GtkTreeView *list_widget = GTK_TREE_VIEW (win->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; - Bool changed = False; - GtkWidget *w; - - if (s->saving_p) return False; - s->saving_p = True; + if (s->initializing_p) return FALSE; + if (s->saving_p) return FALSE; + s->saving_p = TRUE; *p2 = *p; - /* Flush any checkbox changes in the list down into the prefs struct. + /* Flush any checkbox changes in the list down into the s2 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->splash_p, "splash_button"); -#endif +# define SECONDS(PREF,WIDGET) \ + hack_time_text (s, gtk_entry_get_text (GTK_ENTRY (win->WIDGET)), \ + &(PREF), TRUE) +# define MINUTES(PREF,WIDGET) \ + hack_time_text (s, gtk_entry_get_text (GTK_ENTRY (win->WIDGET)), \ + &(PREF), FALSE) +# define CHECKBOX(PREF,WIDGET) \ + (PREF) = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (win->WIDGET)) +# define PATHNAME(PREF,WIDGET,DIRP) \ + (PREF) = pathname_tilde ( \ + gtk_entry_get_text (GTK_ENTRY (win->WIDGET)), TRUE, (DIRP)) +# define TEXT(PREF,WIDGET) \ + (PREF) = (char *) g_strdup (gtk_entry_get_text (GTK_ENTRY (win->WIDGET))) + + 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, TRUE); { - 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"); + Bool v = FALSE; + Bool od = s->debug_p; + s->debug_p = 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; + s->debug_p = od; } - CHECKBOX (p2->fade_p, "fade_button"); - CHECKBOX (p2->unfade_p, "unfade_button"); - SECONDS (&p2->fade_seconds, "fade_spinbutton"); + TEXT (p2->text_literal, text_entry); + PATHNAME (p2->text_file, text_file_entry, FALSE); + PATHNAME (p2->text_program, text_program_entry, FALSE); + TEXT (p2->text_url, text_url_entry); + + CHECKBOX (p2->fade_p, fade_button); + CHECKBOX (p2->unfade_p, unfade_button); + SECONDS (p2->fade_seconds, fade_spinbutton); # undef SECONDS # undef MINUTES @@ -1590,35 +1365,9 @@ flush_dialog_changes_and_save (state *s) # 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 is not a URL - */ - if (p2->image_directory && - *p2->image_directory && - !directory_p (p2->image_directory) && - strncmp(p2->image_directory, "http://", 7) && - strncmp(p2->image_directory, "https://", 8) && - ( !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. */ { - GtkComboBox *opt = GTK_COMBO_BOX (name_to_widget (s, "mode_menu")); + GtkComboBox *opt = GTK_COMBO_BOX (win->mode_menu); int menu_elt = gtk_combo_box_get_active (opt); if (menu_elt < 0 || menu_elt >= countof(mode_menu_order)) abort(); p2->mode = mode_menu_order[menu_elt]; @@ -1634,10 +1383,10 @@ flush_dialog_changes_and_save (state *s) /* Theme menu. */ { - GtkComboBox *cbox = GTK_COMBO_BOX (name_to_widget (s, "theme_menu")); - char *themes = get_string_resource (dpy, "themeNames", "ThemeNames"); + GtkComboBox *cbox = GTK_COMBO_BOX (win->theme_menu); + char *themes = get_string_resource (s->dpy, "themeNames", "ThemeNames"); int menu_index = gtk_combo_box_get_active (cbox); - char *token = themes; + char *token = themes ? themes : "default"; char *name, *last; int i = 0; while ((name = strtok_r (token, ",", &last))) @@ -1646,199 +1395,238 @@ flush_dialog_changes_and_save (state *s) if (i == menu_index) { char *name2 = theme_name_strip (name); - if (p->dialog_theme) free (p->dialog_theme); - p2->dialog_theme = name2; + if (p->dialog_theme && !!strcmp (p->dialog_theme, name2)) + { + free (p->dialog_theme); + p2->dialog_theme = name2; + if (s->debug_p) + fprintf (stderr, "%s: theme => \"%s\"\n", blurb(), + p2->dialog_theme); + } + else + { + free (name2); + } } i++; } } -# define COPY(field, name) \ - if (p->field != p2->field) { \ - changed = True; \ - if (s->debug_p) \ - fprintf (stderr, "%s: %s => %ld\n", blurb(), \ - name, (unsigned long) 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(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"); + /* It is difficult to get "editing completed" events out of GtkEntry. + I want something that fires on RET or focus-out, but I can't seem + to find a consistent way to get that. So let's fake it here. + */ + if (!s->initializing_p && + !!strcmp (p->image_directory, p2->image_directory)) + { + if (s->debug_p) + fprintf (stderr, "%s: imagedir validating \"%s\" -> \"%s\"\n", + blurb(), p->image_directory, p2->image_directory); + if (! validate_image_directory (s, p2->image_directory)) + { + /* Don't save the bad new value into the preferences. */ + if (p2->image_directory != p->image_directory) + free (p2->image_directory); + p2->image_directory = strdup (p->image_directory); + } + } - COPY(grab_desktop_p, "grab_desktop_p"); - COPY(grab_video_p, "grab_video_p"); - COPY(random_image_p, "random_image_p"); - COPY(dialog_theme, "dialog_theme"); + /* Copy any changes from p2 into p, and log them. + */ +# undef STR +# define STR(S) #S +# define COPY(FIELD) \ + if (p->FIELD != p2->FIELD) { \ + changed = TRUE; \ + if (s->debug_p) \ + fprintf (stderr, "%s: %s: %ld => %ld\n", blurb(),\ + STR(FIELD), (unsigned long) p->FIELD, \ + (unsigned long) p2->FIELD); \ + } \ + p->FIELD = p2->FIELD + + COPY(mode); + COPY(selected_hack); + + COPY(timeout); + COPY(cycle); + COPY(lock_p); + COPY(lock_timeout); + + COPY(dpms_enabled_p); + COPY(dpms_quickoff_p); + COPY(dpms_standby); + COPY(dpms_suspend); + COPY(dpms_off); + + COPY(tmode); + + COPY(install_cmap_p); + COPY(fade_p); + COPY(unfade_p); + COPY(fade_seconds); + + COPY(grab_desktop_p); + COPY(grab_video_p); + COPY(random_image_p); + + COPY(dialog_theme); # 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; \ +# define COPYSTR(FIELD) \ + if (!p->FIELD || \ + !p2->FIELD || \ + strcmp(p->FIELD, p2->FIELD)) \ + { \ + changed = TRUE; \ + if (s->debug_p) \ + fprintf (stderr, "%s: %s => \"%s\"\n", blurb(), \ + STR(FIELD), 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"); + COPYSTR(image_directory); + COPYSTR(text_literal); + COPYSTR(text_file); + COPYSTR(text_program); + COPYSTR(text_url); # undef COPYSTR populate_prefs_page (s); if (changed) { - sync_server_dpms_settings (GDK_DISPLAY(), p); - changed = demo_write_init_file (s, p); + if (s->dpy) + sync_server_dpms_settings (s->dpy, p); + demo_write_init_file (s, p); + + /* Tell the xscreensaver daemon to wake up and reload the init file, + in case the timeout has changed. Without this, it would wait + until the *old* timeout had expired before reloading. */ + if (s->debug_p) + fprintf (stderr, "%s: command: DEACTIVATE\n", blurb()); + if (s->dpy) + xscreensaver_command (s->dpy, XA_DEACTIVATE, 0, 0, 0); } - s->saving_p = False; + 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) +/* Called when any field in the prefs dialog may have been changed. + Referenced by many items in demo.ui. */ +G_MODULE_EXPORT gboolean +pref_changed_cb (GtkWidget *widget, gpointer user_data) { - 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")); - GtkComboBoxEntry *vis = GTK_COMBO_BOX_ENTRY (name_to_widget (s, "visual_combo")); - GtkEntry *visent = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (vis))); - - const char *visual = gtk_entry_get_text (visent); - const char *command = gtk_entry_get_text (cmd); - - char c; - unsigned long id; + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; - if (s->saving_p) return False; - s->saving_p = True; - - if (list_elt < 0) - goto DONE; - - if (maybe_reload_init_file (s) != 0) + if (s->debug_p) { - changed = True; - goto DONE; + if (s->flushing_p) + fprintf (stderr, "%s: (pref changed: %s)\n", blurb(), + gtk_widget_get_name (widget)); + else + fprintf (stderr, "%s: pref changed: %s\n", blurb(), + gtk_widget_get_name (widget)); } - /* 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 + if (! s->flushing_p) { - gdk_beep (); /* unparsable */ - visual = ""; - gtk_entry_set_text (visent, _("Any")); + s->flushing_p = TRUE; + flush_dialog_changes_and_save (s); + s->flushing_p = FALSE; } + return GDK_EVENT_PROPAGATE; +} - 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; +/* Same as pref_changed_cb but different. */ +G_MODULE_EXPORT gboolean +pref_changed_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) +{ + pref_changed_cb (widget, user_data); + return GDK_EVENT_PROPAGATE; } -G_MODULE_EXPORT void -pref_changed_cb (GtkWidget *widget, gpointer user_data) +/* Called when the timeout or DPMS spinbuttons are changed, by demo.ui. + */ +G_MODULE_EXPORT gboolean +dpms_sanity_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; - } + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; + Time timeout, standby, suspend, off; + + if (s->flushing_p) return GDK_EVENT_PROPAGATE; + if (s->initializing_p) return GDK_EVENT_PROPAGATE; + if (! s->dpms_supported_p) return GDK_EVENT_PROPAGATE; + + /* Read the current values from the four spinbuttons. */ +# define MINUTES(V,WIDGET) \ + hack_time_text (s, gtk_entry_get_text (GTK_ENTRY (win->WIDGET)), \ + &(V), FALSE) + MINUTES (timeout, timeout_spinbutton); + MINUTES (standby, dpms_standby_spinbutton); + MINUTES (suspend, dpms_suspend_spinbutton); + MINUTES (off, dpms_off_spinbutton); +# undef MINUTES + + /* If the DPMS settings are non-zero, they must not go backwards: + standby >= timeout (screen saver activation) + suspend >= standby + off >= suspend + */ +# define MINUTES(V,LOWER,WIDGET) \ + if ((V) != 0 && (V) < LOWER) \ + gtk_spin_button_set_value (GTK_SPIN_BUTTON (win->WIDGET), \ + (double) ((LOWER) + 59) / (60 * 1000)) + MINUTES (standby, timeout, dpms_standby_spinbutton); + MINUTES (suspend, standby, dpms_suspend_spinbutton); + MINUTES (off, standby, dpms_off_spinbutton); + MINUTES (off, suspend, dpms_off_spinbutton); +# undef MINUTES + + return GDK_EVENT_PROPAGATE; } + +/* Same as dpms_sanity_cb but different. */ G_MODULE_EXPORT gboolean -pref_changed_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) +dpms_sanity_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) { - pref_changed_cb (widget, user_data); - return FALSE; + dpms_sanity_cb (widget, user_data); + return GDK_EVENT_PROPAGATE; } + + /* 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; + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; saver_preferences *p = &s->prefs; - GtkWidget *list = name_to_widget (s, "list"); + GtkWidget *list = win->list; int list_elt; int menu_index = gtk_combo_box_get_active (GTK_COMBO_BOX (widget)); saver_mode new_mode = mode_menu_order[menu_index]; + if (s->flushing_p) return; /* Called as a spurious side-effect */ + if (s->initializing_p) return; + + if (s->debug_p) fprintf (stderr, "%s: mode menu\n", blurb()); + /* 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. @@ -1851,7 +1639,8 @@ mode_menu_item_cb (GtkWidget *widget, gpointer user_data) 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); + populate_popup_window (s); + force_list_select_item (s, list, list_elt, TRUE); p->mode = old_mode; /* put it back, so the init file gets written */ } @@ -1859,52 +1648,94 @@ mode_menu_item_cb (GtkWidget *widget, gpointer user_data) } +/* Remove the "random-same" item from the screen saver mode menu + (we don't display that unless there are multiple screens.) + */ +static void +hide_mode_menu_random_same (state *s) +{ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); + GtkComboBox *opt = GTK_COMBO_BOX (win->mode_menu); + GtkTreeModel *list = gtk_combo_box_get_model (opt); + unsigned int i; + for (i = 0; i < countof(mode_menu_order); i++) + { + if (mode_menu_order[i] == RANDOM_HACKS_SAME) + { + GtkTreeIter iter; + gtk_tree_model_iter_nth_child (list, &iter, NULL, i); + gtk_list_store_remove (GTK_LIST_STORE (list), &iter); + break; + } + } + + /* recompute option-menu size */ + gtk_widget_unrealize (GTK_WIDGET (opt)); + gtk_widget_realize (GTK_WIDGET (opt)); +} + + +/* Called when a new tab is selected. */ G_MODULE_EXPORT void -switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page, +switch_page_cb (GtkNotebook *notebook, GtkWidget *page, gint page_num, gpointer user_data) { - state *s = global_state_kludge; /* I hate C so much... */ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; + + if (s->debug_p) fprintf (stderr, "%s: tab changed\n", blurb()); + populate_prefs_page (s); 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)); + { + populate_demo_window (s, selected_list_element (s)); + populate_popup_window (s); + } else schedule_preview (s, 0); } -#ifdef HAVE_GTK2 + +/* Called when a line is double-clicked in the saver list. */ static void -list_activated_cb (GtkTreeView *list, - GtkTreePath *path, - GtkTreeViewColumn *column, - gpointer data) +list_activated_cb (GtkTreeView *list, GtkTreePath *path, + GtkTreeViewColumn *column, gpointer data) { state *s = data; char *str; int list_elt; - if (gdk_pointer_is_grabbed()) return; + if (s->debug_p) fprintf (stderr, "%s: list activated\n", blurb()); + + /* I did this in Gtk 2 and I don't remember why: + if (gdk_pointer_is_grabbed()) return; + I don't understand how to use gdk_display_device_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); + run_hack (s, list_elt, TRUE); } +/* Called when a line is selected/highlighted in the saver list. */ static void list_select_changed_cb (GtkTreeSelection *selection, gpointer data) { - state *s = (state *)data; + state *s = (state *) data; GtkTreeModel *model; GtkTreeIter iter; GtkTreePath *path; char *str; int list_elt; + if (s->debug_p) fprintf (stderr, "%s: list selection changed\n", blurb()); + if (!gtk_tree_selection_get_selected (selection, &model, &iter)) return; @@ -1916,65 +1747,14 @@ list_select_changed_cb (GtkTreeSelection *selection, gpointer data) 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. - */ + 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 @@ -1982,39 +1762,25 @@ list_unselect_cb (GtkList *list, GtkWidget *child, gpointer data) 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) +list_checkbox_cb (GtkCellRendererToggle *toggle, + gchar *path_string, 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")); + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); + GtkScrolledWindow *scroller = GTK_SCROLLED_WINDOW (win->scroller); + GtkTreeView *list = GTK_TREE_VIEW (win->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 (s->debug_p) fprintf (stderr, "%s: list checkbox\n", blurb()); + if (!gtk_tree_model_get_iter (model, &iter, path)) { g_warning ("bad path: %s", path_string); @@ -2031,17 +1797,15 @@ list_checkbox_cb ( -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); + scroll_top = gtk_adjustment_get_value (adj); flush_dialog_changes_and_save (s); - force_list_select_item (s, GTK_WIDGET (list), list_elt, False); + force_list_select_item (s, GTK_WIDGET (list), list_elt, FALSE); populate_demo_window (s, list_elt); + populate_popup_window (s); /* restore the previous scroll position of the top of the list. this is weak, but I don't really know why it's moving... */ @@ -2049,373 +1813,538 @@ list_checkbox_cb ( } -typedef struct { - state *state; - GtkFileSelection *widget; -} file_selection_data; - - - +/* If the directory or URL does not have images in it, pop up a warning + dialog. This happens at startup, and is fast. + */ static void -store_image_directory (GtkWidget *button, gpointer user_data) +validate_image_directory_quick (state *s) { - 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); + char *warn = 0; + char buf[10240]; + + if (!p->random_image_p) return; + + if (!p->image_directory || !*p->image_directory) + warn = _("Image directory is unset"); + else if (!strncmp (p->image_directory, "http://", 7) || + !strncmp (p->image_directory, "https://", 8)) + warn = 0; + else if (!directory_p (p->image_directory)) + warn = _("Image directory does not exist"); + else if (!image_files_p (p->image_directory, 10)) + warn = _("Image directory is empty"); + + if (!warn) return; + + sprintf (buf, + _("%.100s:\n\n" + " %.100s\n\n" + "Select the 'Advanced' tab and choose a directory with some\n" + "pictures in it, or you're going to see a lot of boring colorbars!"), + warn, + (p->image_directory ? p->image_directory : "")); + warning_dialog (s->window, _("Warning"), buf); +} - 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; - } +/* "Cancel" button on the validate image directory progress dialog. */ +static void +validate_image_directory_cancel_cb (GtkDialog *dialog, gint response_id, + gpointer user_data) +{ + Bool *closed = (Bool *) user_data; + *closed = TRUE; + gtk_widget_destroy (GTK_WIDGET (dialog)); +} - 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); +typedef struct { + GtkWidget *dialog; + int timer_id; +} validate_timer_closure; + +static int +validate_timer_show (gpointer data) +{ + validate_timer_closure *vtc = (validate_timer_closure *) data; + gtk_widget_show_all (vtc->dialog); + vtc->timer_id = 0; + return FALSE; } -static void -store_text_file (GtkWidget *button, gpointer user_data) +/* If the directory or URL does not have images in it, pop up a warning + dialog and return false. This happens when the imageDirectory preference + is edited, and might be slow. + + It does this by running "xscreensaver-getimage-file", which has the side + effect of populating the image cache for that directory. Since that will + take a while if there are a lot of files, this also pops up a progress + dialog with a spinner in it, and a cancel button. That progress dialog + only pops up if the validation has already been running for a little + while, so that it doesn't flicker for small or pre-cached directories. + */ +static Bool +validate_image_directory (state *s, const char *path) { - 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); + validate_timer_closure vtc; + char buf[1024]; + char err[1024]; + GtkWidget *dialog, *content_area, *label, *spinner; + int margin = 32; + Bool closed_p = FALSE; + + dialog = gtk_dialog_new_with_buttons (_("XScreenSaver Image Cache"), + s->window, + GTK_DIALOG_DESTROY_WITH_PARENT, + _("_Cancel"), GTK_RESPONSE_CLOSE, + NULL); + + sprintf (buf, _("Populating image cache for \"%.100s\"..."), path); + content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + label = gtk_label_new (buf); + + gtk_widget_set_margin_start (label, margin); + gtk_widget_set_margin_end (label, margin); + gtk_widget_set_margin_top (label, margin); + gtk_widget_set_margin_bottom (label, margin / 2); + gtk_container_add (GTK_CONTAINER (content_area), label); + + spinner = gtk_spinner_new(); + gtk_spinner_start (GTK_SPINNER (spinner)); + gtk_container_add (GTK_CONTAINER (content_area), spinner); + gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); + + g_signal_connect (dialog, "response", + G_CALLBACK (validate_image_directory_cancel_cb), + &closed_p); + g_signal_connect (dialog, "close", + G_CALLBACK (validate_image_directory_cancel_cb), + &closed_p); + + /* Only pop up the dialog box with the spinner if this has already taken + a little while, so that if it completes immediately, we don't flicker. + */ + vtc.dialog = dialog; + vtc.timer_id = g_timeout_add (1000, validate_timer_show, &vtc); - if (p->text_file && !strcmp(p->text_file, path)) - return; /* no change */ + while (gtk_events_pending ()) /* Paint the window now. */ + gtk_main_iteration (); - 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; - } + { + pid_t forked; + int fds [2]; + int in, out; - if (p->text_file) free (p->text_file); - p->text_file = normalize_directory (path); + char *av[10]; + int ac = 0; - 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); -} + *err = 0; + av[ac++] = "xscreensaver-getimage-file"; + av[ac++] = (char *) path; + av[ac] = 0; + if (pipe (fds)) + { + strcpy (err, "error creating pipe"); + goto FAIL; + } -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); + in = fds [0]; + out = fds [1]; - if (p->text_program && !strcmp(p->text_program, path)) - return; /* no change */ + switch ((int) (forked = fork ())) + { + case -1: + { + strcpy (err, "couldn't fork"); + goto FAIL; + } + case 0: /* Child fork */ + { + int stderr_fd = 2; -# 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 + close (in); /* don't need this one */ + if (! s->debug_p) + close (fileno (stdout)); + close (ConnectionNumber (s->dpy)); /* close display fd */ - if (p->text_program) free (p->text_program); - p->text_program = normalize_directory (path); + if (dup2 (out, stderr_fd) < 0) /* pipe stdout */ + { + perror ("could not dup() a new stderr:"); + exit (1); + } - 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); -} + execvp (av[0], av); /* shouldn't return. */ + 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: /* Parent fork */ + { + char *ss = err; + int bufsiz = sizeof(err); -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)); -} + close (out); /* don't need this one */ -static void -browse_image_dir_ok (GtkWidget *button, gpointer user_data) -{ - browse_image_dir_cancel (button, user_data); - store_image_directory (button, user_data); -} + if (s->debug_p) + fprintf (stderr, "%s: forked %s\n", blurb(), av[0]); -static void -browse_text_file_ok (GtkWidget *button, gpointer user_data) -{ - browse_image_dir_cancel (button, user_data); - store_text_file (button, user_data); -} + while (1) + { + fd_set rset; + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 1000000 / 10; /* Repaint widgets at 10 fps */ + FD_ZERO (&rset); + FD_SET (in, &rset); + if (0 < select (in+1, &rset, 0, 0, &tv)) + { + int n = read (in, (void *) ss, bufsiz); + if (n <= 0) + { + if (s->debug_p) + fprintf (stderr, "%s: %s: read EOF\n", blurb(), av[0]); + break; + } + else + { + ss += n; + bufsiz -= n; + *ss = 0; + + if (s->debug_p) + fprintf (stderr, "%s: %s: read: \"%s\"\n", blurb(), + av[0], ss - n); + } + } -static void -browse_text_program_ok (GtkWidget *button, gpointer user_data) -{ - browse_image_dir_cancel (button, user_data); - store_text_program (button, user_data); -} + /* Service Gtk events and timers */ + while (gtk_events_pending ()) + gtk_main_iteration (); -static void -browse_image_dir_close (GtkWidget *widget, GdkEvent *event, gpointer user_data) -{ - browse_image_dir_cancel (widget, user_data); -} + if (closed_p) + { + kill (forked, SIGTERM); + if (s->debug_p) + fprintf (stderr, "%s: cancel\n", blurb()); + break; + } + } -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; + *ss = 0; + close (in); - GtkFileSelection *selector = GTK_FILE_SELECTION( - gtk_file_selection_new ("Please select the image directory.")); + if (s->debug_p) + fprintf (stderr, "%s: %s exited\n", blurb(), av[0]); - if (!fsd) - fsd = (file_selection_data *) malloc (sizeof (*fsd)); + /* Wait for the child to die. */ + { + int wait_status = 0; + waitpid (-1, &wait_status, 0); + } + } + } + } - fsd->widget = selector; - fsd->state = s; + if (vtc.timer_id) /* Remove the popup timer if it hasn't fired. */ + g_source_remove (vtc.timer_id); - if (p->image_directory && *p->image_directory) - gtk_file_selection_set_filename (selector, p->image_directory); + if (s->debug_p) + fprintf (stderr, "%s: dismiss\n", blurb()); - 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); + if (! closed_p) + gtk_widget_destroy (dialog); - gtk_widget_set_sensitive (GTK_WIDGET (selector->file_list), False); + FAIL: + if (*err) + { + warning_dialog (s->window, _("Warning"), err); + return FALSE; + } - gtk_window_set_modal (GTK_WINDOW (selector), True); - gtk_widget_show (GTK_WIDGET (selector)); + return TRUE; } -G_MODULE_EXPORT void -browse_text_file_cb (GtkButton *button, gpointer user_data) +/* Called when the imageDirectory text field is edited directly (focus-out). + */ +G_MODULE_EXPORT gboolean +image_text_pref_changed_event_cb (GtkWidget *widget, GdkEvent *event, + gpointer user_data) { - state *s = global_state_kludge; /* I hate C so much... */ +#if 0 /* This is handled in flush_dialog_changes_and_save now */ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; 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; + GtkEntry *w = GTK_ENTRY (win->image_text); + const char *str = gtk_entry_get_text (w); + char *path = pathname_tilde (str, TRUE, TRUE); - if (p->text_file && *p->text_file) - gtk_file_selection_set_filename (selector, p->text_file); + if (s->debug_p) fprintf (stderr, "%s: imagedir text edited\n", blurb()); - 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); + if (p->image_directory && strcmp(p->image_directory, path)) + { + if (s->debug_p) + fprintf (stderr, "%s: imagedir validating \"%s\" -> \"%s\"\n", blurb(), + p->image_directory, path); + if (! validate_image_directory (s, path)) + { + /* Don't save the bad new value into the preferences. */ + free (path); + return GDK_EVENT_PROPAGATE; + } + } - gtk_window_set_modal (GTK_WINDOW (selector), True); - gtk_widget_show (GTK_WIDGET (selector)); + free (path); +# endif + pref_changed_event_cb (widget, event, user_data); + return GDK_EVENT_PROPAGATE; } -G_MODULE_EXPORT void -browse_text_program_cb (GtkButton *button, gpointer user_data) +/* Run a modal file selector dialog. + Select a file, directory, or program. + Normalize the resultant path and store it into the string pointer. + Also update the text field with the new path. + Returns true if any changes made. + */ +gboolean +file_chooser (GtkWindow *parent, GtkEntry *entry, char **retP, + const char *title, gboolean verbose_p, + gboolean dir_p, gboolean program_p) { - 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.")); + gint res; + gboolean changed_p = FALSE; + GtkWidget *dialog = + gtk_file_chooser_dialog_new (title, parent, + (dir_p + ? GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER + : GTK_FILE_CHOOSER_ACTION_OPEN), + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Select"), GTK_RESPONSE_ACCEPT, + NULL); + const char *old = gtk_entry_get_text (entry); /* not *retP */ + + if (*old) + { + char *p2 = pathname_tilde (old, FALSE, dir_p); + GFile *gf; - if (!fsd) - fsd = (file_selection_data *) malloc (sizeof (*fsd)); + /* If it's a command line and it begins with an absolute path, + default to that file and its directory. */ + if (program_p && (*p2 == '/' || *p2 == '~')) + { + char *s = strpbrk (p2, " \t\r\n"); + if (s) *s = 0; + program_p = FALSE; + } - fsd->widget = selector; - fsd->state = s; + gf = g_file_new_for_path (p2); + if (! program_p) + { + gtk_file_chooser_set_file (GTK_FILE_CHOOSER (dialog), gf, NULL); + if (verbose_p) + fprintf (stderr, "%s: chooser: default \"%s\"\n", blurb(), p2); + } + free (p2); + g_object_unref (gf); + } - if (p->text_program && *p->text_program) - gtk_file_selection_set_filename (selector, p->text_program); + res = gtk_dialog_run (GTK_DIALOG (dialog)); + if (res == GTK_RESPONSE_ACCEPT) + { + GtkFileChooser *chooser = GTK_FILE_CHOOSER (dialog); + char *str = gtk_file_chooser_get_filename (chooser); + char *path = pathname_tilde (str, TRUE, dir_p); + g_free (str); - 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); + if (*retP && !strcmp (*retP, path)) + { + if (verbose_p) + fprintf (stderr, "%s: chooser: unchanged\n", blurb()); + free (path); /* no change */ + } + else if (dir_p && !directory_p (path)) + { + char b[255]; + sprintf (b, _("Directory does not exist: \"%.100s\"\n"), path); + warning_dialog (parent, _("Error"), b); + free (path); /* no change */ + } + else if (!dir_p && !file_p (path)) + { + char b[255]; + sprintf (b, _("File does not exist: \"%.100s\"\n"), path); + warning_dialog (parent, _("Error"), b); + free (path); /* no change */ + } + else + { + if (verbose_p) + fprintf (stderr, "%s: chooser: \"%s\" -> \"%s\n", + blurb(), *retP, path); + if (*retP) free (*retP); + *retP = path; + gtk_entry_set_text (entry, path); + changed_p = TRUE; + } + } + else if (verbose_p) + fprintf (stderr, "%s: chooser: cancelled\n", blurb()); - gtk_window_set_modal (GTK_WINDOW (selector), True); - gtk_widget_show (GTK_WIDGET (selector)); + gtk_widget_destroy (dialog); + return changed_p; } +/* The "Browse" button next to the imageDirectory text field. */ G_MODULE_EXPORT void -preview_theme_cb (GtkWidget *w, gpointer user_data) +browse_image_dir_cb (GtkButton *button, gpointer user_data) { - if (system ("xscreensaver-auth --splash &") < 0) - fprintf (stderr, "%s: splash exec failed\n", blurb()); + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; + saver_preferences *p = &s->prefs; + char *old = strdup (p->image_directory); + + if (s->debug_p) fprintf (stderr, "%s: imagedir browse button\n", blurb()); + if (file_chooser (GTK_WINDOW (win), + GTK_ENTRY (win->image_text), + &p->image_directory, + _("Please select the image directory."), + s->debug_p, TRUE, FALSE)) + { + if (validate_image_directory (s, p->image_directory)) + demo_write_init_file (s, p); + else + { + /* Don't save the bad new value into the preferences. */ + free (p->image_directory); + p->image_directory = old; + old = 0; + } + } + + if (old) free (old); } +/* The "Browse" button next to the textFile text field. */ G_MODULE_EXPORT void -settings_cb (GtkButton *button, gpointer user_data) +browse_text_file_cb (GtkButton *button, gpointer user_data) { - state *s = global_state_kludge; /* I hate C so much... */ - int list_elt = selected_list_element (s); + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; + saver_preferences *p = &s->prefs; - populate_demo_window (s, list_elt); /* reset the widget */ - populate_popup_window (s); /* create UI on popup window */ - gtk_widget_show (s->popup_widget); + if (s->debug_p) fprintf (stderr, "%s: textfile browse button\n", blurb()); + if (file_chooser (GTK_WINDOW (win), + GTK_ENTRY (win->text_file_entry), + &p->text_file, + _("Please select a text file."), + s->debug_p, FALSE, FALSE)) + demo_write_init_file (s, p); } -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 */ -} +/* The "Browse" button next to the textProgram text field. */ G_MODULE_EXPORT void -settings_adv_cb (GtkButton *button, gpointer user_data) +browse_text_program_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")); + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; + saver_preferences *p = &s->prefs; - settings_sync_cmd_text (s); - gtk_notebook_set_page (notebook, 1); + if (s->debug_p) fprintf (stderr, "%s: textprogram browse button\n", blurb()); + if (file_chooser (GTK_WINDOW (win), + GTK_ENTRY (win->text_program_entry), + &p->text_program, + _("Please select a text-generating program."), + s->debug_p, FALSE, TRUE)) + demo_write_init_file (s, p); } -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); -} +/* The "Preview" button next to the Theme option menu. */ G_MODULE_EXPORT void -settings_reset_cb (GtkButton *button, gpointer user_data) +preview_theme_cb (GtkWidget *w, 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 */ -} + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; + int ac = 0; + char *av[10]; -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 (s->debug_p) fprintf (stderr, "%s: preview theme button\n", blurb()); - 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(); + /* Settings button is disabled with --splash --splash so that we don't + end up with two copies of xscreensaver-settings running. */ + av[ac++] = "xscreensaver-auth"; + av[ac++] = "--splash"; + av[ac++] = "--splash"; + av[ac] = 0; + fork_and_exec (s, ac, av); } - +/* The "Settings" button on the main page. */ G_MODULE_EXPORT void -settings_cancel_cb (GtkButton *button, gpointer user_data) +settings_cb (GtkButton *button, gpointer user_data) { - state *s = global_state_kludge; /* I hate C so much... */ - gtk_widget_hide (s->popup_widget); -} + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; + saver_preferences *p = &s->prefs; + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (s->dialog); + int list_elt = selected_list_element (s); -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 (s->debug_p) fprintf (stderr, "%s: settings button\n", blurb()); - 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); + populate_demo_window (s, list_elt); /* reset the widget */ + populate_popup_window (s); /* create UI on popup window */ - flush_popup_changes_and_save (s); - gtk_widget_hide (s->popup_widget); -} + /* Pre-select the "Standard" page. */ + settings_std_cb (NULL, s->dialog); + settings_switch_page_cb (GTK_NOTEBOOK (dialog->opt_notebook), NULL, 0, + s->dialog); -static gboolean -wm_popup_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data) -{ - state *s = (state *) data; - settings_cancel_cb (0, (gpointer) s); - return TRUE; + /* If there is no saved position for the dialog, position it to the + right of the main window. See also restore_window_position(), + which already ran at startup. */ + { + int win_x, win_y, dialog_x, dialog_y; + char dummy; + char *old = p->settings_geom; + + if (!old || !*old || + 4 != sscanf (old, " %d , %d %d , %d %c", + &win_x, &win_y, &dialog_x, &dialog_y, &dummy)) + win_x = win_y = dialog_x = dialog_y = -1; + + if (dialog_x <= 0 && dialog_y <= 0) + { + int win_w, win_h; + gtk_window_get_position (GTK_WINDOW (s->window), &win_x, &win_y); + gtk_window_get_size (GTK_WINDOW (s->window), &win_w, &win_h); + dialog_x = win_x + win_w + 8; + dialog_y = win_y; + gtk_window_move (GTK_WINDOW (s->dialog), dialog_x, dialog_y); + } + } + + gtk_widget_show (GTK_WIDGET (s->dialog)); } - /* Populating the various widgets */ @@ -2423,18 +2352,18 @@ wm_popup_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data) /* Returns the number of the last hack run by the server. */ static int -server_current_hack (void) +server_current_hack (state *s) { 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 */ + if (!s->dpy) return hack_number; + if (XGetWindowProperty (s->dpy, RootWindow(s->dpy, 0), /* always screen #0 */ XA_SCREENSAVER_STATUS, - 0, 3, False, XA_INTEGER, + 0, 3, FALSE, XA_INTEGER, &type, &format, &nitems, &bytesafter, &dataP) == Success @@ -2458,13 +2387,14 @@ server_current_hack (void) static void scroll_to_current_hack (state *s) { + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); 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 (); + hack_number = server_current_hack (s); if (hack_number < 0) /* failing that, last "one mode" */ hack_number = p->selected_hack; if (hack_number < 0) /* failing that, newest hack. */ @@ -2489,9 +2419,10 @@ scroll_to_current_hack (state *s) 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); + GtkWidget *list = win->list; + force_list_select_item (s, list, list_elt, TRUE); populate_demo_window (s, list_elt); + populate_popup_window (s); } } @@ -2499,10 +2430,8 @@ scroll_to_current_hack (state *s) 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")); + GtkTreeView *list = GTK_TREE_VIEW (XSCREENSAVER_WINDOW (s->window)->list); GtkListStore *model; GtkTreeSelection *selection; GtkCellRenderer *ren; @@ -2560,7 +2489,7 @@ populate_hack_list (state *s) pretty_name = (hack->name ? strdup (hack->name) - : make_hack_name (dpy, hack->command)); + : make_hack_name (s->dpy, hack->command)); if (!available_p) { @@ -2568,16 +2497,15 @@ populate_hack_list (state *s) (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]; */ + GtkStyleContext *c = + gtk_widget_get_style_context (GTK_WIDGET (list)); + GdkRGBA fg; 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, */ + gtk_style_context_get_color (c, GTK_STATE_FLAG_INSENSITIVE, &fg); + sprintf (buf, "<span foreground=\"#%02X%02X%02X\">%s</span>", + (unsigned int) (0xFF * fg.red), + (unsigned int) (0xFF * fg.green), + (unsigned int) (0xFF * fg.blue), pretty_name); free (pretty_name); pretty_name = buf; @@ -2590,107 +2518,13 @@ populate_hack_list (state *s) -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) { + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); saver_preferences *p = &s->prefs; Bool sensitive = (p->mode == RANDOM_HACKS || p->mode == RANDOM_HACKS_SAME || @@ -2699,73 +2533,32 @@ update_list_sensitivity (state *s) 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")); + GtkTreeView *list = GTK_TREE_VIEW (win->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_widget_set_sensitive (GTK_WIDGET (win->scroller), sensitive); + gtk_widget_set_sensitive (GTK_WIDGET (win->next_prev_hbox), sensitive); + gtk_widget_set_sensitive (GTK_WIDGET (win->blanking_table), blankable); 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) { - Display *dpy = GDK_DISPLAY(); + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); saver_preferences *p = &s->prefs; - Bool can_lock_p = True; + 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; +# ifdef NO_LOCKING + 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) + if (!s->multi_screen_p && p->mode == RANDOM_HACKS_SAME) p->mode = RANDOM_HACKS; @@ -2780,108 +2573,100 @@ populate_prefs_page (state *s) # 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)) - + gtk_spin_button_set_value (GTK_SPIN_BUTTON (win->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); - + gtk_spin_button_set_value (GTK_SPIN_BUTTON (win->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 ("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 ("fade_button", p->fade_p); - TOGGLE_ACTIVE ("unfade_button", p->unfade_p); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (win->NAME), (ACTIVEP)) + + TOGGLE_ACTIVE (lock_button, p->lock_p); + TOGGLE_ACTIVE (dpms_button, p->dpms_enabled_p && s->dpms_supported_p); + TOGGLE_ACTIVE (dpms_quickoff_button, (p->dpms_quickoff_p && + s->dpms_supported_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 (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; + 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")), + gtk_entry_set_text (GTK_ENTRY (win->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"), + gtk_widget_set_sensitive (win->image_text, p->random_image_p); + gtk_widget_set_sensitive (win->image_browse_button, p->random_image_p); - gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_entry")), + gtk_entry_set_text (GTK_ENTRY (win->text_entry), (p->text_literal ? p->text_literal : "")); - gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_file_entry")), + gtk_entry_set_text (GTK_ENTRY (win->text_file_entry), (p->text_file ? p->text_file : "")); - gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_program_entry")), + gtk_entry_set_text (GTK_ENTRY (win->text_program_entry), (p->text_program ? p->text_program : "")); - gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_url_entry")), + gtk_entry_set_text (GTK_ENTRY (win->text_url_entry), (p->text_url ? p->text_url : "")); - gtk_widget_set_sensitive (name_to_widget (s, "text_entry"), + gtk_widget_set_sensitive (win->text_entry, p->tmode == TEXT_LITERAL); - gtk_widget_set_sensitive (name_to_widget (s, "text_file_entry"), + gtk_widget_set_sensitive (win->text_file_entry, p->tmode == TEXT_FILE); - gtk_widget_set_sensitive (name_to_widget (s, "text_file_browse"), + gtk_widget_set_sensitive (win->text_file_browse, p->tmode == TEXT_FILE); - gtk_widget_set_sensitive (name_to_widget (s, "text_program_entry"), + gtk_widget_set_sensitive (win->text_program_entry, p->tmode == TEXT_PROGRAM); - gtk_widget_set_sensitive (name_to_widget (s, "text_program_browse"), + gtk_widget_set_sensitive (win->text_program_browse, p->tmode == TEXT_PROGRAM); - gtk_widget_set_sensitive (name_to_widget (s, "text_url_entry"), + gtk_widget_set_sensitive (win->text_url_entry, p->tmode == TEXT_URL); - /* Theme menu */ { - GtkComboBox *cbox = GTK_COMBO_BOX (name_to_widget (s, "theme_menu")); - - /* Without this, pref_changed_cb gets called an exponentially-increasing - number of times on the themes menu, despite the call to - gtk_list_store_clear(). */ - static Bool done_once = False; + GtkComboBox *cbox = GTK_COMBO_BOX (win->theme_menu); - if (cbox && !done_once) + if (cbox) { - char *themes = get_string_resource (dpy, "themeNames", "ThemeNames"); - char *token = themes; - char *name, *name2, *last; + char *themes = get_string_resource(s->dpy, "themeNames", "ThemeNames"); + char *token = themes ? themes : strdup ("default"); + char *name, *last = 0; GtkListStore *model; GtkTreeIter iter; int i = 0; - done_once = True; - - g_object_get (G_OBJECT (cbox), "model", &model, NULL); - if (!model) abort(); - gtk_list_store_clear (model); + /* Bad things happen if we do these things more than once. */ + static Bool model_built_p = FALSE; + static Bool signal_connected_p = FALSE; - gtk_signal_connect (GTK_OBJECT (cbox), "changed", - GTK_SIGNAL_FUNC (pref_changed_cb), (gpointer) s); + if (! model_built_p) + { + g_object_get (G_OBJECT (cbox), "model", &model, NULL); + if (!model) abort(); + gtk_list_store_clear (model); + } while ((name = strtok_r (token, ",", &last))) { + char *name2; int L; token = 0; @@ -2893,22 +2678,33 @@ populate_prefs_page (state *s) name[L-1] == '\n')) name[--L] = 0; - gtk_list_store_append (model, &iter); - gtk_list_store_set (model, &iter, 0, name, -1); + if (! model_built_p) + { + gtk_list_store_append (model, &iter); + gtk_list_store_set (model, &iter, 0, name, -1); + } name2 = theme_name_strip (name); - if (!strcmp (p->dialog_theme, name2)) + if (p->dialog_theme && name2 && !strcmp (p->dialog_theme, name2)) gtk_combo_box_set_active (cbox, i); free (name2); i++; } + + model_built_p = TRUE; + + if (! signal_connected_p) + { + g_signal_connect (G_OBJECT (cbox), "changed", + G_CALLBACK (pref_changed_cb), (gpointer) win); + signal_connected_p = TRUE; + } } } - /* Map the `saver_mode' enum to mode menu to values. */ { - GtkComboBox *opt = GTK_COMBO_BOX (name_to_widget (s, "mode_menu")); + GtkComboBox *opt = GTK_COMBO_BOX (win->mode_menu); int i; for (i = 0; i < countof(mode_menu_order); i++) @@ -2918,146 +2714,183 @@ populate_prefs_page (state *s) update_list_sensitivity (s); } - { - Bool dpms_supported = False; - Display *dpy = GDK_DISPLAY(); - -#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 (win->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); -# 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_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); - SENSITIZE ("dpms_quickoff_button", dpms_supported); - - SENSITIZE ("fade_label", (p->fade_p || p->unfade_p)); - SENSITIZE ("fade_spinbutton", (p->fade_p || p->unfade_p)); + /* DPMS + */ + SENSITIZE (dpms_button, s->dpms_supported_p); + SENSITIZE (dpms_standby_label, s->dpms_supported_p && p->dpms_enabled_p); + SENSITIZE (dpms_standby_mlabel, s->dpms_supported_p && p->dpms_enabled_p); + SENSITIZE (dpms_standby_spinbutton,s->dpms_supported_p && p->dpms_enabled_p); + SENSITIZE (dpms_suspend_label, s->dpms_supported_p && p->dpms_enabled_p); + SENSITIZE (dpms_suspend_mlabel, s->dpms_supported_p && p->dpms_enabled_p); + SENSITIZE (dpms_suspend_spinbutton,s->dpms_supported_p && p->dpms_enabled_p); + SENSITIZE (dpms_off_label, s->dpms_supported_p && p->dpms_enabled_p); + SENSITIZE (dpms_off_mlabel, s->dpms_supported_p && p->dpms_enabled_p); + SENSITIZE (dpms_off_spinbutton, s->dpms_supported_p && p->dpms_enabled_p); + SENSITIZE (dpms_quickoff_button, s->dpms_supported_p); + + SENSITIZE (fade_label, (p->fade_p || p->unfade_p)); + SENSITIZE (fade_spinbutton, (p->fade_p || p->unfade_p)); # undef SENSITIZE - } -} - -/* Allow the documentation label to re-flow when the text is changed. - http://blog.borovsak.si/2009/05/wrapping-adn-resizing-gtklabel.html - */ -static void -cb_allocate (GtkWidget *label, GtkAllocation *allocation, gpointer data) -{ - gtk_widget_set_size_request (label, allocation->width - 8, -1); + if (!s->dpms_supported_p) + gtk_frame_set_label (GTK_FRAME (win->dpms_frame), + _("Display Power Management (not supported by this display)")); } -static void -populate_popup_window (state *s) +/* Creates a human-readable anchor to put on a URL. + */ +static char * +anchorize (const char *url) { - GtkLabel *doc = GTK_LABEL (name_to_widget (s, "doc")); - char *doc_string = 0; - - /* #### not in Gtk 1.2 - gtk_label_set_selectable (doc); - */ - - g_signal_connect (G_OBJECT (doc), "size-allocate", - G_CALLBACK (cb_allocate), NULL); - -# ifdef HAVE_XML - if (s->cdata) - { - free_conf_data (s->cdata); - s->cdata = 0; + const char *wiki1 = "http://en.wikipedia.org/wiki/"; + const char *wiki2 = "https://en.wikipedia.org/wiki/"; + const char *math1 = "http://mathworld.wolfram.com/"; + const char *math2 = "https://mathworld.wolfram.com/"; + if (!strncmp (wiki1, url, strlen(wiki1)) || + !strncmp (wiki2, url, strlen(wiki2))) { + char *anchor = (char *) malloc (strlen(url) * 3 + 10); + const char *in; + char *out; + strcpy (anchor, "Wikipedia: \""); + in = url + strlen(!strncmp (wiki1, url, strlen(wiki1)) ? wiki1 : wiki2); + out = anchor + strlen(anchor); + while (*in) { + if (*in == '_') { + *out++ = ' '; + } else if (*in == '#') { + *out++ = ':'; + *out++ = ' '; + } else if (*in == '%') { + char hex[3]; + unsigned int n = 0; + hex[0] = in[1]; + hex[1] = in[2]; + hex[2] = 0; + sscanf (hex, "%x", &n); + *out++ = (char) n; + in += 2; + } else { + *out++ = *in; + } + in++; } - - { - 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); + *out++ = '"'; + *out = 0; + return anchor; + + } else if (!strncmp (math1, url, strlen(math1)) || + !strncmp (math2, url, strlen(math2))) { + char *anchor = (char *) malloc (strlen(url) * 3 + 10); + const char *start, *in; + char *out; + strcpy (anchor, "MathWorld: \""); + start = url + strlen(!strncmp (math1, url, strlen(math1)) ? math1 : math2); + in = start; + out = anchor + strlen(anchor); + while (*in) { + if (*in == '_') { + *out++ = ' '; + } else if (in != start && *in >= 'A' && *in <= 'Z') { + *out++ = ' '; + *out++ = *in; + } else if (!strncmp (in, ".htm", 4)) { + break; + } else { + *out++ = *in; } - } - - 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."))); + in++; + } + *out++ = '"'; + *out = 0; + return anchor; - { - GtkWidget *w = name_to_widget (s, "dialog_vbox"); - gtk_widget_hide (w); - gtk_widget_unrealize (w); - gtk_widget_realize (w); - gtk_widget_show (w); + } else { + return strdup (url); } } -static void -sensitize_demo_widgets (state *s, Bool sensitive_p) +/* Quote the text as HTML and make URLs be clickable links. + */ +static char * +hreffify (const char *in) { - const char *names[] = { "demo", "settings", - "cmd_label", "cmd_text", "manual", - "visual", "visual_combo" }; - int i; - for (i = 0; i < countof(names); i++) + char *ret, *out; + if (!in) return 0; + + ret = out = malloc (strlen(in) * 3); + while (*in) { - GtkWidget *w = name_to_widget (s, names[i]); - gtk_widget_set_sensitive (GTK_WIDGET(w), sensitive_p); + if (!strncmp (in, "http://", 7) || + !strncmp (in, "https://", 8)) + { + char *url, *anchor; + const char *end = in; + while (*end && + *end != ' ' && *end != '\t' && *end != '\r' && *end != '\n') + end++; + + url = (char *) malloc (end - in + 1); + strncpy (url, in, end-in); + url [end-in] = 0; + + anchor = anchorize (url); + + strcpy (out, "<a href=\""); out += strlen (out); + strcpy (out, url); out += strlen (out); + strcpy (out, "\">"); out += strlen (out); + strcpy (out, anchor); out += strlen (out); + strcpy (out, "</a>"); out += strlen (out); + free (url); + free (anchor); + in = end; + } + else if (*in == '<') + { + strcpy (out, "<"); + out += strlen (out); + in++; + } + else if (*in == '>') + { + strcpy (out, ">"); + out += strlen (out); + in++; + } + else if (*in == '&') + { + strcpy (out, "&"); + out += strlen (out); + in++; + } + else + { + *out++ = *in++; + } } + *out = 0; + return ret; } static void sensitize_menu_items (state *s, Bool force_p) { - static Bool running_p = False; + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); + static Bool running_p = FALSE; static time_t last_checked = 0; time_t now = time ((time_t *) 0); - const char *names[] = { "activate_action", "lock_action", "kill_action", - /* "demo" */ }; - int i; if (force_p || now > last_checked + 10) /* check every 10 seconds */ { @@ -3065,289 +2898,52 @@ sensitize_menu_items (state *s, Bool force_p) last_checked = time ((time_t *) 0); } - for (i = 0; i < countof(names); i++) - { - GtkAction *a = GTK_ACTION (gtk_builder_get_object (s->gtk_ui, names[i])); - gtk_action_set_sensitive (a, 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_BOX_ENTRY (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" -}; + gtk_widget_set_sensitive (win->activate_menuitem, running_p); + gtk_widget_set_sensitive (win->lock_menuitem, running_p); + gtk_widget_set_sensitive (win->kill_menuitem, running_p); -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); + gtk_menu_item_set_label (GTK_MENU_ITEM (win->restart_menuitem), + (running_p + ? _("Restart Daemon") + : _("Launch Daemon"))); } -#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. +/* Fill in the contents of the main window, and a few things on the + settings dialog. */ 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(); + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (s->dialog); 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")); - GtkComboBoxEntry *vis = GTK_COMBO_BOX_ENTRY (name_to_widget (s, "visual_combo")); - GtkWidget *list = GTK_WIDGET (name_to_widget (s, "list")); + GtkFrame *frame1 = GTK_FRAME (win->preview_frame); + GtkFrame *frame2 = dialog ? GTK_FRAME (dialog->opt_frame) : 0; + GtkEntry *cmd = dialog ? GTK_ENTRY (dialog->cmd_text) : 0; + GtkComboBox *vis = dialog ? GTK_COMBO_BOX (dialog->visual_combo) : 0; + + /* Enforce a minimum size on the preview pane. */ + if (s->dpy) + { + int dw = DisplayWidth (s->dpy, 0); + int dh = DisplayHeight (s->dpy, 0); + int minw, minh; + # define TRY(W) do { \ + minw = (W); minh = minw * 9/16 + 48; \ + if (dw > minw * 1.5 && dh > minh * 1.5) \ + gtk_widget_set_size_request (GTK_WIDGET (frame1), minw, minh); \ + } while(0) + TRY (300); + TRY (400); + TRY (480); + TRY (640); + TRY (800); + /* TRY (960); */ +# undef TRY + } if (p->mode == BLANK_ONLY) { @@ -3371,7 +2967,7 @@ populate_demo_window (state *s, int list_elt) pretty_name = (hack ? (hack->name ? strdup (hack->name) - : make_hack_name (dpy, hack->command)) + : make_hack_name (s->dpy, hack->command)) : 0); if (hack) @@ -3383,31 +2979,40 @@ populate_demo_window (state *s, int list_elt) if (!pretty_name) pretty_name = strdup (_("Preview")); - gtk_frame_set_label (frame1, _(pretty_name)); - gtk_frame_set_label (frame2, _(pretty_name)); + if (dialog->unedited_cmdline) free (dialog->unedited_cmdline); + dialog->unedited_cmdline = strdup (hack ? hack->command : ""); - gtk_entry_set_text (cmd, (hack ? hack->command : "")); - gtk_entry_set_position (cmd, 0); + gtk_frame_set_label (frame1, _(pretty_name)); + if (frame2) + gtk_frame_set_label (frame2, _(pretty_name)); + if (cmd) + gtk_entry_set_text (cmd, dialog->unedited_cmdline); { char title[255]; sprintf (title, _("%s: %.100s Settings"), progclass, (pretty_name ? pretty_name : "???")); - gtk_window_set_title (GTK_WINDOW (s->popup_widget), title); + gtk_window_set_title (GTK_WINDOW (s->window), title); + if (s->dialog) + gtk_window_set_title (GTK_WINDOW (s->dialog), title); } - gtk_entry_set_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (vis))), - (hack - ? (hack->visual && *hack->visual - ? hack->visual - : _("Any")) - : "")); + /* Fill in the Visual combo-box */ + if (vis) + gtk_entry_set_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (vis))), + (hack + ? (hack->visual && *hack->visual + ? hack->visual + : _("Any")) + : "")); - sensitize_demo_widgets (s, (hack ? True : False)); + sensitize_demo_widgets (s, (hack ? TRUE : FALSE)); if (pretty_name) free (pretty_name); - ensure_selected_item_visible (list); + /* This causes the window to scroll out from under the mouse when + clicking on an item, vertically centering it. That's annoying. */ + /* ensure_selected_item_visible (s, list); */ s->_selected_list_element = list_elt; } @@ -3416,19 +3021,7 @@ populate_demo_window (state *s, int 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 } @@ -3452,7 +3045,6 @@ sort_hack_cmp (const void *a, const void *b) static void initialize_sort_map (state *s) { - Display *dpy = GDK_DISPLAY(); saver_preferences *p = &s->prefs; int i, j; @@ -3503,7 +3095,7 @@ initialize_sort_map (state *s) screenhack *hack = p->screenhacks[i]; char *name = (hack->name && *hack->name ? strdup (hack->name) - : make_hack_name (dpy, hack->command)); + : make_hack_name (s->dpy, hack->command)); gchar *s2 = g_str_to_ascii (name, 0); /* Sort "Möbius" properly */ gchar *s3 = g_ascii_strdown (s2, -1); free (name); @@ -3538,13 +3130,13 @@ initialize_sort_map (state *s) static int maybe_reload_init_file (state *s) { - Display *dpy = GDK_DISPLAY(); saver_preferences *p = &s->prefs; + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); int status = 0; - static Bool reentrant_lock = False; + static Bool reentrant_lock = FALSE; if (reentrant_lock) return 0; - reentrant_lock = True; + reentrant_lock = TRUE; if (init_file_changed_p (p)) { @@ -3555,120 +3147,106 @@ maybe_reload_init_file (state *s) 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); + sprintf (b, _("file \"%s\" has changed, reloading.\n"), f); + warning_dialog (s->window, _("Warning"), b); free (b); - load_init_file (dpy, p); + load_init_file (s->dpy, p); initialize_sort_map (s); list_elt = selected_list_element (s); - list = name_to_widget (s, "list"); + list = win->list; gtk_container_foreach (GTK_CONTAINER (list), widget_deleter, NULL); populate_hack_list (s); - force_list_select_item (s, list, list_elt, True); + force_list_select_item (s, list, list_elt, TRUE); populate_prefs_page (s); populate_demo_window (s, list_elt); - ensure_selected_item_visible (list); + populate_popup_window (s); + ensure_selected_item_visible (s, list); status = 1; } - reentrant_lock = False; + 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) +x_visual_to_gdk_visual (GdkWindow *win, Visual *xv) { - GList *gvs = gdk_list_visuals(); - if (!xv) return gdk_visual_get_system(); - for (; gvs; gvs = gvs->next) + if (xv) { - GdkVisual *gv = (GdkVisual *) gvs->data; - if (xv == GDK_VISUAL_XVISUAL (gv)) - return gv; + GdkScreen *screen = gdk_window_get_screen (win); + GList *gvs = gdk_screen_list_visuals (screen); + /* This list is sometimes NULL, not even the default visual! */ + 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(); + return 0; } + 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; + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->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); + + GtkWidget *notebook = win->preview_notebook; + gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), + (!s->running_preview_error_p ? 0 : /* ok */ + nothing_p ? 3 : /* no hacks installed */ + !available_p ? 2 : /* hack not installed */ + s->wayland_p ? 4 : /* fucking wayland */ + 1)); /* preview failed */ +} - /* 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 */ - } +static gboolean +preview_resize_cb (GtkWidget *self, GdkEvent *event, gpointer data) +{ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (data); + state *s = &win->state; - gdk_flush (); + /* If a subproc is running, clear the window to black when we resize. + Without this, sometimes turds get left behind. */ + if (s->dpy && !s->wayland_p && s->running_preview_cmd) + { + GdkWindow *window = gtk_widget_get_window (self); + Window id; + XWindowAttributes xgwa; + XGCValues gcv; + GC gc; + + if (! window) return TRUE; + id = gdk_x11_window_get_xid (window); + if (! id) return TRUE; + + /* Not sure why XClearWindow is insufficient here, but it is. */ + XGetWindowAttributes (s->dpy, id, &xgwa); + gcv.foreground = BlackPixelOfScreen (xgwa.screen); + gc = XCreateGC (s->dpy, id, GCForeground, &gcv); + XFillRectangle (s->dpy, id, gc, 0, 0, xgwa.width, xgwa.height); + XFreeGC (s->dpy, gc); + } + return FALSE; } @@ -3680,18 +3258,20 @@ reset_preview_window (state *s) 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)) + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); + GtkWidget *pr = win->preview; + if (s->dpy && !s->wayland_p && gtk_widget_get_realized (pr)) { - GdkWindow *window = GET_WINDOW (pr); - Window oid = (window ? GDK_WINDOW_XWINDOW (window) : 0); + GdkWindow *window = gtk_widget_get_window (pr); + Window oid = (window ? gdk_x11_window_get_xid (window) : 0); Window id; gtk_widget_hide (pr); gtk_widget_unrealize (pr); + gtk_widget_set_has_window (pr, TRUE); gtk_widget_realize (pr); gtk_widget_show (pr); - id = (window ? GDK_WINDOW_XWINDOW (window) : 0); - if (s->debug_p) + id = (window ? gdk_x11_window_get_xid (window) : 0); + if (s->debug_p && oid != id) fprintf (stderr, "%s: window id 0x%X -> 0x%X\n", blurb(), (unsigned int) oid, (unsigned int) id); @@ -3699,60 +3279,47 @@ reset_preview_window (state *s) } +/* Make the preview widget use the best GL visual. + We just always use that one rather than switching. + */ 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)); + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); + Visual *xvisual = s->gl_visual; + GtkWidget *widget = win->preview; + GdkWindow *gwindow = gtk_widget_get_window (GTK_WIDGET (win)); + GdkScreen *gscreen = gdk_window_get_screen (gwindow); + GdkVisual *gvisual1 = gdk_screen_get_system_visual (gscreen); + GdkVisual *gvisual2 = x_visual_to_gdk_visual (gwindow, xvisual); + + if (! gvisual2) + { + gvisual2 = gvisual1; + if (s->debug_p) + fprintf (stderr, "%s: couldn't convert X Visual 0x%lx to a GdkVisual;" + " winging it.\n", blurb(), + (xvisual ? (unsigned long) xvisual->visualid : 0L)); + } if (s->debug_p) fprintf (stderr, "%s: using %s visual 0x%lx\n", blurb(), - (visual == dvisual ? "default" : "non-default"), + (gvisual1 == gvisual2 ? "default" : "non-default"), (xvisual ? (unsigned long) xvisual->visualid : 0L)); - if (!GET_REALIZED (widget) || - gtk_widget_get_visual (widget) != visual) + if (!gtk_widget_get_realized (widget) || + gtk_widget_get_visual (widget) != gvisual2) { gtk_widget_unrealize (widget); - gtk_widget_set_visual (widget, visual); - gtk_widget_set_colormap (widget, cmap); + gtk_widget_set_has_window (widget, TRUE); + gtk_widget_set_visual (widget, gvisual2); 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 */ @@ -3803,11 +3370,12 @@ reap_zombies (state *s) } +#define EXEC_FAILED_EXIT_STATUS -33 + /* 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; @@ -3838,12 +3406,10 @@ get_best_gl_visual (state *s) } case 0: { - int stdout_fd = 1; - close (in); /* don't need this one */ - close (ConnectionNumber (dpy)); /* close display fd */ + close (ConnectionNumber (s->dpy)); /* close display fd */ - if (dup2 (out, stdout_fd) < 0) /* pipe stdout */ + if (dup2 (out, STDOUT_FILENO) < 0) /* pipe stdout */ { perror ("could not dup() a new stdout:"); return 0; @@ -3865,27 +3431,44 @@ get_best_gl_visual (state *s) 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 */ + _exit (EXEC_FAILED_EXIT_STATUS); /* exits fork */ break; } default: { int result = 0; int wait_status = 0; + int exit_status = EXEC_FAILED_EXIT_STATUS; FILE *f = fdopen (in, "r"); unsigned int v = 0; char c; + int i = 0; close (out); /* don't need this one */ *buf = 0; - if (!fgets (buf, sizeof(buf)-1, f)) - *buf = 0; + do { + errno = 0; + if (! fgets (buf, sizeof(buf)-1, f)) + *buf = 0; + } while (errno == EINTR && /* fgets might fail due to SIGCHLD. */ + i++ < 1000); /* And just in case. */ + fclose (f); /* Wait for the child to die. */ - waitpid (-1, &wait_status, 0); + waitpid (forked, &wait_status, 0); + + exit_status = WEXITSTATUS (wait_status); + /* Treat exit code as a signed 8-bit quantity. */ + if (exit_status & 0x80) exit_status |= ~0xFF; + + if (exit_status == EXEC_FAILED_EXIT_STATUS) + { + fprintf (stderr, "%s: %s is not installed\n", blurb(), av[0]); + return 0; + } if (1 == sscanf (buf, "0x%x %c", &v, &c)) result = (int) v; @@ -3899,10 +3482,9 @@ get_best_gl_visual (state *s) } else { - Visual *v = id_to_visual (DefaultScreenOfDisplay (dpy), result); + Visual *v = id_to_visual (DefaultScreenOfDisplay (s->dpy), result); if (s->debug_p) - fprintf (stderr, "%s: %s says the GL visual is 0x%X.\n", - blurb(), av[0], result); + fprintf (stderr, "%s: GL visual is 0x%X\n", blurb(), result); if (!v) abort(); return v; } @@ -3916,14 +3498,14 @@ get_best_gl_visual (state *s) static void kill_preview_subproc (state *s, Bool reset_p) { - s->running_preview_error_p = False; + 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); + g_source_remove (s->subproc_check_timer_id); s->subproc_check_timer_id = 0; s->subproc_check_countdown = 0; } @@ -3979,30 +3561,33 @@ kill_preview_subproc (state *s, Bool reset_p) static void launch_preview_subproc (state *s) { + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); 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"); + GtkWidget *pr = win->preview; GdkWindow *window; reset_preview_window (s); - window = GET_WINDOW (pr); + window = gtk_widget_get_window (pr); - s->running_preview_error_p = False; + s->running_preview_error_p = FALSE; - if (s->preview_suppressed_p) + if (s->preview_suppressed_p || !s->gl_visual) { - kill_preview_subproc (s, False); + kill_preview_subproc (s, FALSE); goto DONE; } new_cmd = malloc (strlen (cmd) + 40); - id = (window ? GDK_WINDOW_XWINDOW (window) : 0); + id = (window && !s->wayland_p + ? gdk_x11_window_get_xid (window) + : 0); if (id == 0) { /* No window id? No command to run. */ @@ -4012,19 +3597,22 @@ launch_preview_subproc (state *s) else { /* We do this instead of relying on $XSCREENSAVER_WINDOW specifically - so that third-party savers that don't implement -window-id will fail: + so that third-party savers that don't implement --window-id will fail: otherwise we might have full-screen windows popping up when we were just trying to get a preview thumbnail. */ strcpy (new_cmd, cmd); - sprintf (new_cmd + strlen (new_cmd), " -window-id 0x%X", + sprintf (new_cmd + strlen (new_cmd), " --window-id 0x%X", (unsigned int) id); } - kill_preview_subproc (s, False); + if (id && s->screenshot) + screenshot_save (s->dpy, id, s->screenshot); + + kill_preview_subproc (s, FALSE); if (! new_cmd) { - s->running_preview_error_p = True; + s->running_preview_error_p = TRUE; clear_preview_window (s); goto DONE; } @@ -4036,13 +3624,13 @@ launch_preview_subproc (state *s) char buf[255]; sprintf (buf, "%s: couldn't fork", blurb()); perror (buf); - s->running_preview_error_p = True; + s->running_preview_error_p = TRUE; goto DONE; break; } case 0: { - close (ConnectionNumber (GDK_DISPLAY())); + close (ConnectionNumber (s->dpy)); hack_subproc_environment (id, s->debug_p); @@ -4081,6 +3669,16 @@ launch_preview_subproc (state *s) } } + /* Put some props on the embedded preview window, for debugging. */ + XStoreName (s->dpy, id, "XScreenSaver Settings Preview"); + XChangeProperty (s->dpy, id, XA_WM_COMMAND, + XA_STRING, 8, PropModeReplace, + (unsigned char *) new_cmd, + strlen (new_cmd)); + XChangeProperty (s->dpy, id, XA_NET_WM_PID, + XA_CARDINAL, 32, PropModeReplace, + (unsigned char *) &forked, 1); + schedule_preview_check (s); DONE: @@ -4101,8 +3699,7 @@ hack_environment (state *s) ""; # endif - Display *dpy = GDK_DISPLAY(); - const char *odpy = DisplayString (dpy); + const char *odpy = s->dpy ? DisplayString (s->dpy) : ":0.0"; char *ndpy = (char *) malloc(strlen(odpy) + 20); strcpy (ndpy, "DISPLAY="); strcat (ndpy, odpy); @@ -4141,7 +3738,7 @@ 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, + them with "--root" and not with "--window-id" -- which, of course, doesn't happen. */ char *nssw = (char *) malloc (40); @@ -4159,6 +3756,7 @@ hack_subproc_environment (Window preview_window_id, Bool debug_p) } + /* Called from a timer: Launches the currently-chosen subprocess, if it's not already running. If there's a different process running, kills it. @@ -4168,7 +3766,7 @@ update_subproc_timer (gpointer data) { state *s = (state *) data; if (! s->desired_preview_cmd) - kill_preview_subproc (s, True); + kill_preview_subproc (s, TRUE); else if (!s->running_preview_cmd || !!strcmp (s->desired_preview_cmd, s->running_preview_cmd)) launch_preview_subproc (s); @@ -4177,13 +3775,6 @@ update_subproc_timer (gpointer data) 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 @@ -4207,8 +3798,8 @@ schedule_preview (state *s, const char *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); + g_source_remove (s->subproc_timer_id); + s->subproc_timer_id = g_timeout_add (delay, update_subproc_timer, s); } @@ -4219,12 +3810,12 @@ static int check_subproc_timer (gpointer data) { state *s = (state *) data; - Bool again_p = True; + Bool again_p = TRUE; if (s->running_preview_error_p || /* already dead */ s->running_preview_pid <= 0) { - again_p = False; + again_p = FALSE; } else { @@ -4232,9 +3823,9 @@ check_subproc_timer (gpointer data) reap_zombies (s); status = kill (s->running_preview_pid, 0); if (status < 0 && errno == ESRCH) - s->running_preview_error_p = True; + s->running_preview_error_p = TRUE; - if (s->debug_p) + if (s->debug_p && s->running_preview_error_p) { char *ss = subproc_pretty_name (s); fprintf (stderr, "%s: timer: pid %lu (%s) is %s\n", blurb(), @@ -4246,7 +3837,7 @@ check_subproc_timer (gpointer data) if (s->running_preview_error_p) { clear_preview_window (s); - again_p = False; + again_p = FALSE; } } @@ -4254,7 +3845,7 @@ check_subproc_timer (gpointer data) might be satisfied. */ if (--s->subproc_check_countdown <= 0) - again_p = False; + again_p = FALSE; if (again_p) return TRUE; /* re-execute timer */ @@ -4272,7 +3863,7 @@ check_subproc_timer (gpointer data) 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. + take a --window-id argument. */ static void schedule_preview_check (state *s) @@ -4284,27 +3875,27 @@ schedule_preview_check (state *s) fprintf (stderr, "%s: scheduling check\n", blurb()); if (s->subproc_check_timer_id) - gtk_timeout_remove (s->subproc_check_timer_id); + g_source_remove (s->subproc_check_timer_id); s->subproc_check_timer_id = - gtk_timeout_add (1000 / ticks, - check_subproc_timer, (gpointer) s); + g_timeout_add (1000 / ticks, + check_subproc_timer, (gpointer) s); s->subproc_check_countdown = ticks * seconds; } static Bool -screen_blanked_p (void) +screen_blanked_p (state *s) { Atom type; int format; unsigned long nitems, bytesafter; unsigned char *dataP = 0; - Display *dpy = GDK_DISPLAY(); - Bool blanked_p = False; + Bool blanked_p = FALSE; - if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */ + if (!s->dpy) return FALSE; + if (XGetWindowProperty (s->dpy, RootWindow (s->dpy, 0), /* always screen 0 */ XA_SCREENSAVER_STATUS, - 0, 3, False, XA_INTEGER, + 0, 3, FALSE, XA_INTEGER, &type, &format, &nitems, &bytesafter, &dataP) == Success @@ -4329,63 +3920,45 @@ static int check_blanked_timer (gpointer data) { state *s = (state *) data; - Bool blanked_p = screen_blanked_p (); + Bool blanked_p = screen_blanked_p (s); 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); + kill_preview_subproc (s, TRUE); } - return True; /* re-execute timer */ + return TRUE; /* re-execute timer */ } -/* How many screens are there (including Xinerama.) - */ -static int -screen_count (Display *dpy) +/* Is there more than one active monitor? */ +static Bool +multi_screen_p (Display *dpy) { - int nscreens = ScreenCount(dpy); -# ifdef HAVE_XINERAMA - if (nscreens <= 1) + monitor **monitors = dpy ? scan_monitors (dpy) : NULL; + Bool ret = FALSE; + if (monitors) { - int event_number, error_number; - if (XineramaQueryExtension (dpy, &event_number, &error_number) && - XineramaIsActive (dpy)) + int count = 0; + int good_count = 0; + while (monitors[count]) { - XineramaScreenInfo *xsi = XineramaQueryScreens (dpy, &nscreens); - if (xsi) XFree (xsi); + if (monitors[count]->sanity == S_SANE) + good_count++; + count++; } + free_monitors (monitors); + ret = (good_count > 1); } -# 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); + return ret; } - -/* The main demo-mode command loop. - */ #if 0 static Bool -mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks, - XrmRepresentation *type, XrmValue *value, XPointer closure) +xrm_mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks, + XrmRepresentation *type, XrmValue *value, XPointer closure) { int i; for (i = 0; quarks[i]; i++) @@ -4401,98 +3974,146 @@ mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks, fprintf (stderr, ": %s\n", (char *) value->addr); - return False; + return FALSE; +} +#endif /* 0 */ + + +static int +ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error) +{ + return 0; } -#endif static Window -gnome_screensaver_window (Screen *screen) +gnome_screensaver_window (Display *dpy, char **name_ret) { - Display *dpy = DisplayOfScreen (screen); - Window root = RootWindowOfScreen (screen); - Window parent, *kids; - unsigned int nkids; + int nscreens; + int i, screen; Window gnome_window = 0; - int i; + XErrorHandler old_handler; - if (! XQueryTree (dpy, root, &root, &parent, &kids, &nkids)) - abort (); - for (i = 0; i < nkids; i++) + if (!dpy) return 0; + XSync (dpy, FALSE); + old_handler = XSetErrorHandler (ignore_all_errors_ehandler); + + nscreens = ScreenCount (dpy); + for (screen = 0; screen < nscreens; screen++) { - 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") || - !strcmp ((char *) name, "mate-screensaver") || - !strcmp ((char *) name, "cinnamon-screensaver"))) - { - gnome_window = kids[i]; - break; - } + Window root = RootWindow (dpy, screen); + Window parent, *kids; + unsigned int nkids; + + if (! XQueryTree (dpy, root, &root, &parent, &kids, &nkids)) + abort (); + if (name_ret) + *name_ret = 0; + 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") || + !strcmp ((char *) name, "mate-screensaver") || + !strcmp ((char *) name, "cinnamon-screensaver") || + !strcmp ((char *) name, "xfce4-screensaver") || + !strcmp ((char *) name, "light-locker"))) + { + gnome_window = kids[i]; + if (name_ret) + *name_ret = strdup ((char *) name); + break; + } + } + if (kids) XFree ((char *) kids); + if (gnome_window) + break; } - if (kids) XFree ((char *) kids); + XSync (dpy, FALSE); + XSetErrorHandler (old_handler); return gnome_window; } + static Bool -gnome_screensaver_active_p (void) +gnome_screensaver_active_p (state *s, char **name_ret) { - Display *dpy = GDK_DISPLAY(); - Window w = gnome_screensaver_window (DefaultScreenOfDisplay (dpy)); - return (w ? True : False); + Window w = gnome_screensaver_window (s->dpy, name_ret); + return (w ? TRUE : FALSE); } + static void -kill_gnome_screensaver (void) +kill_gnome_screensaver (state *s) { - Display *dpy = GDK_DISPLAY(); - Window w = gnome_screensaver_window (DefaultScreenOfDisplay (dpy)); - if (w) XKillClient (dpy, (XID) w); + Window w = gnome_screensaver_window (s->dpy, NULL); + if (w) XKillClient (s->dpy, (XID) w); } + static Bool kde_screensaver_active_p (void) { + /* Apparently this worked in KDE 3, but not 4 or 5. + Maybe parsing the output of this would work in KDE 5: + kreadconfig5 --file kscreenlockerrc --group Daemon --key Autolock + but there's probably no way to kill the KDE saver. + Fuck it. */ FILE *p = popen ("dcop kdesktop KScreensaverIface isEnabled 2>/dev/null", "r"); char buf[255]; - if (!p) return False; - if (!fgets (buf, sizeof(buf)-1, p)) return False; + int i = 0; + if (!p) return FALSE; + + *buf = 0; + do { + errno = 0; + if (! fgets (buf, sizeof(buf)-1, p)) + *buf = 0; + } while (errno == EINTR && /* fgets might fail due to SIGCHLD. */ + i++ < 1000); /* And just in case. */ + pclose (p); if (!strcmp (buf, "true\n")) - return True; + return TRUE; else - return False; + return FALSE; } + static void -kill_kde_screensaver (void) +kill_kde_screensaver (state *s) { - /* Use empty body to kill warning from gcc -Wall with - "warning: ignoring return value of 'system', - declared with attribute warn_unused_result" - */ - if (system ("dcop kdesktop KScreensaverIface enable false")) {} + int ac = 0; + char *av[10]; + av[ac++] = "dcop"; + av[ac++] = "kdesktop"; + av[ac++] = "KScreensaverIface"; + av[ac++] = "enable"; + av[ac++] = "false"; + av[ac] = 0; + fork_and_exec (s, ac, av); } -static void -the_network_is_not_the_computer (state *s) +static int +the_network_is_not_the_computer (gpointer data) { - Display *dpy = GDK_DISPLAY(); + state *s = (state *) data; char *rversion = 0, *ruser = 0, *rhost = 0; char *luser, *lhost; char *msg = 0; + char *oname = 0; struct passwd *p = getpwuid (getuid ()); - const char *d = DisplayString (dpy); + const char *d = s->dpy ? DisplayString (s->dpy) : ":0.0"; # if defined(HAVE_UNAME) struct utsname uts; @@ -4509,7 +4130,8 @@ the_network_is_not_the_computer (state *s) else luser = "???"; - server_xscreensaver_version (dpy, &rversion, &ruser, &rhost); + if (s->dpy) + server_xscreensaver_version (s->dpy, &rversion, &ruser, &rhost); /* Make a buffer that's big enough for a number of copies of all the strings, plus some. */ @@ -4522,22 +4144,22 @@ the_network_is_not_the_computer (state *s) 1024)); *msg = 0; - if (!rversion || !*rversion) + if ((!rversion || !*rversion) && !s->debug_p) { +# ifndef __APPLE__ sprintf (msg, - _("Warning:\n\n" - "The XScreenSaver daemon doesn't seem to be running\n" - "on display \"%s\". Launch it now?"), + _("The XScreenSaver daemon doesn't seem to be running\n" + "on display \"%.25s\". Launch it now?"), d); +# endif } 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" + _("%s is running as user \"%s\" on host \"%s\".\n" + "But the xscreensaver managing display \"%.25s\"\n" "is running as user \"%s\" on host \"%s\".\n" "\n" "Since they are different users, they won't be reading/writing\n" @@ -4560,8 +4182,7 @@ the_network_is_not_the_computer (state *s) /* 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" + _("%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" @@ -4577,13 +4198,12 @@ the_network_is_not_the_computer (state *s) progname, lhost, luser); } - else if (!!strcmp (rversion, s->short_version)) + else if (rversion && *rversion && !!strcmp (rversion, s->short_version)) { /* Warn that the version numbers don't match. */ sprintf (msg, - _("Warning:\n\n" - "This is %s version %s.\n" + _("This is %s version %s.\n" "But the xscreensaver managing display \"%s\"\n" "is version %s. This could cause problems.\n" "\n" @@ -4593,9 +4213,10 @@ the_network_is_not_the_computer (state *s) rversion); } + validate_image_directory_quick (s); if (*msg) - warning_dialog (s->toplevel_widget, msg, D_LAUNCH, 1); + warning_dialog_1 (s->window, _("Warning"), msg, D_LAUNCH); if (rversion) free (rversion); if (ruser) free (ruser); @@ -4608,23 +4229,42 @@ the_network_is_not_the_computer (state *s) 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 (gnome_screensaver_active_p (s, &oname)) + { + char msg [1024]; + sprintf (msg, + _("The GNOME screen saver daemon (%s) appears to be running.\n" + "It must be stopped for XScreenSaver to work properly.\n" + "\n" + "Stop the \"%s\" daemon now?\n"), + oname, oname); + warning_dialog_1 (s->window, _("Warning"), msg, D_GNOME); + } - if (kde_screensaver_active_p ()) - warning_dialog (s->toplevel_widget, - _("Warning:\n\n" - "The KDE screen saver daemon appears to be running.\n" + if (kde_screensaver_active_p()) + warning_dialog_1 (s->window, _("Warning"), + _("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); + D_KDE); + + if (! s->gl_visual) + warning_dialog (s->window, _("Error"), + _("No GL visuals: the xscreensaver-gl* packages are required.")); + + if (s->wayland_p) + warning_dialog (s->window, _("Warning"), + _("You are running Wayland rather than the X Window System.\n" + "\n" + "Under Wayland, idle-detection fails when non-X11 programs\n" + "are selected, meaning the screen may blank prematurely.\n" + "Also, locking is impossible.\n" + "\n" + "See the XScreenSaver manual for instructions on\n" + "configuring your system to use X11 instead of Wayland.\n")); + + return FALSE; /* Only run timer once */ } @@ -4632,739 +4272,1155 @@ the_network_is_not_the_computer (state *s) of the program that generated them. */ static int -demo_ehandler (Display *dpy, XErrorEvent *error) +x_error (Display *dpy, XErrorEvent *error) { - state *s = global_state_kludge; /* I hate C so much... */ - fprintf (stderr, "\nX error in %s:\n", blurb()); + fprintf (stderr, "\n%s: X error:\n", blurb()); XmuPrintDefaultErrorMessage (dpy, error, stderr); - kill_preview_subproc (s, False); - exit (-1); + /* No way to get 'state' in here... */ + /* kill_preview_subproc (s, FALSE); */ + exit (-1); /* Likewise, no way to call g_application_quit(). */ 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; +g_logger (const gchar *domain, GLogLevelFlags log_level, + const gchar *message, gpointer data) +{ + if (log_level & G_LOG_LEVEL_DEBUG) return; + if (log_level & G_LOG_LEVEL_INFO) return; + fprintf (stderr, "%s: %s: %s\n", blurb(), domain, message); + if (log_level & G_LOG_LEVEL_CRITICAL) exit (-1); +} - 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" : "")); +/* Why are there two of these hooks and why does this one suck so hard?? */ +static GLogWriterOutput +g_other_logger (GLogLevelFlags log_level, const GLogField *fields, + gsize n_fields, gpointer data) +{ + int i; + GLogWriterOutput ret = G_LOG_WRITER_UNHANDLED; + if (log_level & G_LOG_LEVEL_DEBUG) return ret; + if (log_level & G_LOG_LEVEL_INFO) return ret; + for (i = 0; i < n_fields; i++) + { + const GLogField *field = &fields[i]; + if (strcmp (field->key, "MESSAGE")) continue; + fprintf (stderr, "%s: %s\n", blurb(), (char *) field->value); + ret = G_LOG_WRITER_HANDLED; + } + if (log_level & G_LOG_LEVEL_CRITICAL) exit (-1); + return ret; } -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 */ + XScreenSaverDialog callbacks, referenced by prefs.ui. -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) +/* The "Documentation" button on the Settings dialog */ +G_MODULE_EXPORT void +manual_cb (GtkButton *button, 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; -} + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (user_data); + XScreenSaverWindow *win = dialog->main; + state *s = &win->state; + saver_preferences *p = &s->prefs; + GtkWidget *list_widget = win->list; + int list_elt = selected_list_element (s); + int hack_number; + char *name, *name2, *cmd, *str; + char *oname = 0; + if (s->debug_p) fprintf (stderr, "%s: documentation button\n", blurb()); + 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 (s, list_widget); -#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)); + 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; - if (GTK_IS_LIST (w)) + cmd = get_string_resource (s->dpy, "manualCommand", "ManualCommand"); + if (cmd) { - for (i = 0; i < depth+1; i++) - fprintf (stderr, " "); - fprintf (stderr, "...list kids...\n"); + int ac = 0; + char *av[10]; + char *cmd2 = (char *) malloc (strlen (cmd) + (strlen (name2) * 4) + 100); + sprintf (cmd2, cmd, name2, name2, name2, name2); + av[ac++] = "/bin/sh"; + av[ac++] = "-c"; + av[ac++] = cmd2; + av[ac] = 0; + fork_and_exec (s, ac, av); + free (cmd2); } - else if (GTK_IS_CONTAINER (w)) + else { - GList *kids = gtk_container_children (GTK_CONTAINER (w)); - while (kids) - { - print_widget_tree (GTK_WIDGET (kids->data), depth+1); - kids = kids->next; - } + warning_dialog (s->window, _("Error"), + _("no `manualCommand' resource set.")); } + + free (oname); } -#endif /* 0 */ -static int -delayed_scroll_kludge (gpointer data) + +static void +settings_sync_cmd_text (state *s) { - state *s = (state *) data; - GtkWidget *w = GTK_WIDGET (name_to_widget (s, "list")); - ensure_selected_item_visible (w); + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (s->dialog); + GtkWidget *cmd = GTK_WIDGET (dialog->cmd_text); + char *cmd_line; + if (! s->cdata) return; + cmd_line = get_configurator_command_line (s->cdata, FALSE); + gtk_entry_set_text (GTK_ENTRY (cmd), cmd_line); + free (cmd_line); +} - /* 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 */ +/* The "Advanced" button on the settings dialog. */ +G_MODULE_EXPORT void +settings_adv_cb (GtkButton *button, gpointer user_data) +{ + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (user_data); + XScreenSaverWindow *win = dialog->main; + state *s = &win->state; + GtkNotebook *notebook = GTK_NOTEBOOK (dialog->opt_notebook); + if (s->debug_p) fprintf (stderr, "%s: settings advanced button\n", blurb()); + settings_sync_cmd_text (s); + gtk_notebook_set_current_page (notebook, 1); } -#ifdef HAVE_GTK2 -static GtkWidget * -create_xscreensaver_demo (void) +/* The "Standard" button on the settings dialog. */ +G_MODULE_EXPORT void +settings_std_cb (GtkButton *button, gpointer user_data) { - GtkWidget *nb; + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (user_data); + XScreenSaverWindow *win = dialog->main; + state *s = &win->state; + GtkNotebook *notebook = GTK_NOTEBOOK (dialog->opt_notebook); + if (s->debug_p) fprintf (stderr, "%s: settings standard button\n", blurb()); + settings_sync_cmd_text (s); + gtk_notebook_set_current_page (notebook, 0); +} - 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"); +/* The "Reset to Defaults" button on the settings dialog. */ +G_MODULE_EXPORT void +settings_reset_cb (GtkButton *button, gpointer user_data) +{ + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (user_data); + XScreenSaverWindow *win = dialog->main; + GtkWidget *cmd = GTK_WIDGET (dialog->cmd_text); + state *s = &win->state; + char *cmd_line; + if (s->debug_p) fprintf (stderr, "%s: settings reset button\n", blurb()); + if (! s->cdata) return; + cmd_line = get_configurator_command_line (s->cdata, TRUE); + gtk_entry_set_text (GTK_ENTRY (cmd), cmd_line); + free (cmd_line); + populate_popup_window (s); } -static GtkWidget * -create_xscreensaver_settings_dialog (void) + +/* Called when "Advanced/Standard" buttons change the displayed page. */ +G_MODULE_EXPORT void +settings_switch_page_cb (GtkNotebook *notebook, GtkWidget *page, + gint page_num, gpointer user_data) { - GtkWidget *w, *box; + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (user_data); + XScreenSaverWindow *win = dialog->main; + state *s = &win->state; + GtkWidget *adv = dialog->adv_button; + GtkWidget *std = dialog->std_button; + if (s->debug_p) fprintf (stderr, "%s: settings page changed\n", blurb()); - box = name_to_widget (global_state_kludge, "dialog_action_area"); + 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(); - w = name_to_widget (global_state_kludge, "adv_button"); - gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (box), w, TRUE); + /* Nobody uses the "Advanced" tab. Let's just hide it. + (The tab still needs to be there, since the 'cmd_text' widget is + what gets stored into the .xscreensaver file.) */ + gtk_widget_hide (adv); + gtk_widget_hide (std); +} - 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"); +/* The "Cancel" button on the Settings dialog. */ +G_MODULE_EXPORT void +settings_cancel_cb (GtkWidget *button, gpointer user_data) +{ + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (user_data); + XScreenSaverWindow *win = dialog->main; + state *s = &win->state; + if (s->debug_p) fprintf (stderr, "%s: settings cancel button\n", blurb()); + gtk_widget_hide (GTK_WIDGET (dialog)); + gtk_widget_unrealize (GTK_WIDGET (dialog)); + + /* Restart the hack running in the Preview window with the reset options. */ + schedule_preview (s, dialog->unedited_cmdline); } -#endif /* HAVE_GTK2 */ -int -main (int argc, char **argv) +/* The "Ok" button on the Settings dialog. */ +G_MODULE_EXPORT void +settings_ok_cb (GtkWidget *button, gpointer user_data) { - 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; + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (user_data); + XScreenSaverWindow *win = dialog->main; + state *s = &win->state; + GtkNotebook *notebook = GTK_NOTEBOOK (dialog->opt_notebook); + int page = gtk_notebook_get_current_page (notebook); + if (s->debug_p) fprintf (stderr, "%s: settings ok button\n", blurb()); -#ifdef ENABLE_NLS - bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); - textdomain (GETTEXT_PACKAGE); + 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); -# 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 */ + flush_popup_changes_and_save (s); + gtk_widget_hide (GTK_WIDGET (dialog)); + gtk_widget_unrealize (GTK_WIDGET (dialog)); +} -#endif /* ENABLE_NLS */ - str = strrchr (real_progname, '/'); - if (str) real_progname = str+1; +/* Called when any widget value is changed in the Settings dialog. */ +static void +dialog_change_cb (GtkWidget *widget, gpointer user_data) +{ + state *s = (state *) user_data; + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (s->dialog); + char *cur_cmd, *def_cmd; + const char *prev_cmd; + if (!dialog || !s->cdata) return; + settings_sync_cmd_text (s); + cur_cmd = get_configurator_command_line (s->cdata, FALSE); + def_cmd = get_configurator_command_line (s->cdata, TRUE); + prev_cmd = dialog->unedited_cmdline; - s = &S; - memset (s, 0, sizeof(*s)); - s->initializing_p = True; - p = &s->prefs; + /* "Reset to Defaults" button enabled only if current cmd is not default. */ + gtk_widget_set_sensitive (dialog->reset_button, + !!strcmp (cur_cmd, def_cmd)); - global_state_kludge = s; /* I hate C so much... */ + /* "Save" button enabled only if current cmd is edited. */ + gtk_widget_set_sensitive (dialog->ok_button, + !!strcmp (cur_cmd, prev_cmd)); - progname = real_progname; + /* Restart the hack running in the Preview window with the prevailing, + un-saved set of options, for a realtime preview of what they do. */ + schedule_preview (s, cur_cmd); - s->short_version = XSCREENSAVER_VERSION; + free (cur_cmd); + free (def_cmd); +} + + +/* Fill in the contents of the "Settings" dialog for the current hack. + It may or may not currently be visible. + */ +static void +populate_popup_window (state *s) +{ + saver_preferences *p = &s->prefs; + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (s->dialog); + + GtkLabel *doc = dialog ? GTK_LABEL (dialog->doc) : 0; + char *doc_string = 0; + + if (s->cdata) + { + free_conf_data (s->cdata); + s->cdata = 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); + 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 (p->mode == BLANK_ONLY || p->mode == DONT_BLANK) + hack = 0; + + if (hack && dialog) + { + GtkWidget *parent = dialog->settings_vbox; + GtkWidget *cmd = GTK_WIDGET (dialog->cmd_text); + const char *cmd_line = gtk_entry_get_text (GTK_ENTRY (cmd)); + if (!cmd_line) abort(); + s->cdata = load_configurator (cmd_line, dialog_change_cb, s, + s->debug_p); + dialog_change_cb (NULL, s); + if (s->cdata && s->cdata->widget) + gtk_box_pack_start (GTK_BOX (parent), s->cdata->widget, + TRUE, TRUE, 0); + + /* Make the pretty name on the tab boxes include the year and be bold. + */ + if (s->cdata && s->cdata->year) + { + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); + GtkFrame *frame1 = GTK_FRAME (win->preview_frame); + GtkFrame *frame2 = GTK_FRAME (dialog->opt_frame); + char *pretty_name = (hack->name + ? strdup (hack->name) + : make_hack_name (s->dpy, hack->command)); + char *s2 = (char *) malloc (strlen (pretty_name) + 20); + sprintf (s2, "<b>%s (%d)</b>", pretty_name, s->cdata->year); + free (pretty_name); + pretty_name = s2; + + gtk_frame_set_label (frame1, _(pretty_name)); + gtk_frame_set_label (frame2, _(pretty_name)); + gtk_label_set_use_markup ( /* Must be after set_label */ + GTK_LABEL (gtk_frame_get_label_widget (frame1)), TRUE); + gtk_label_set_use_markup ( + GTK_LABEL (gtk_frame_get_label_widget (frame2)), TRUE); + free (pretty_name); + } + } } -#ifdef DEFAULT_ICONDIR /* from -D on compile line */ -# ifndef HAVE_GTK2 + doc_string = (s->cdata && s->cdata->description && *s->cdata->description + ? _(s->cdata->description) + : 0); + doc_string = hreffify (doc_string); + if (doc) + { + GtkWidget *w = dialog->dialog_vbox; + gtk_label_set_text (doc, (doc_string + ? doc_string + : _("No description available."))); + gtk_label_set_use_markup (doc, TRUE); + + /* Shrink the dialog to the minimum viable size. */ + gtk_window_resize (GTK_WINDOW (dialog), 1, 1); + + gtk_widget_hide (w); + gtk_widget_unrealize (w); + gtk_widget_realize (w); + gtk_widget_show (w); + } + + /* Also set the documentation on the main window, below the preview. */ { - const char *dir = DEFAULT_ICONDIR; - if (*dir) add_pixmap_directory (dir); + GtkLabel *doc2 = GTK_LABEL (win->short_preview_label); + GtkLabel *doc3 = GTK_LABEL (win->preview_author_label); + char *s2 = 0; + char *s3 = 0; + + if (doc_string) + { + /* Keep only the first paragraph, and the last line. + Omit everything in between. */ + const char *second_para = strstr (doc_string, "\n\n"); + const char *last_line = strrchr (doc_string, '\n'); + s2 = strdup (doc_string); + if (second_para) + s2[second_para - doc_string] = 0; + if (last_line) + s3 = strdup (last_line + 1); + } + + gtk_label_set_text (doc2, + (s2 + ? _(s2) + : (p->mode == BLANK_ONLY || p->mode == DONT_BLANK) + ? "" + : _("No description available."))); + gtk_label_set_text (doc3, (s3 ? _(s3) : "")); + if (s2) free (s2); + if (s3) free (s3); } -# 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"; + if (doc_string) + free (doc_string); +} - /* We need to parse this arg really early... Sigh. */ - for (i = 1; i < argc; i++) +static void +sensitize_demo_widgets (state *s, Bool sensitive_p) +{ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (s->dialog); + gtk_widget_set_sensitive (win->demo, sensitive_p); + gtk_widget_set_sensitive (win->settings, sensitive_p); + if (dialog) { - 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); - } - } + gtk_widget_set_sensitive (dialog->cmd_label, sensitive_p); + gtk_widget_set_sensitive (dialog->cmd_text, sensitive_p); + gtk_widget_set_sensitive (dialog->manual, sensitive_p); + gtk_widget_set_sensitive (dialog->visual, sensitive_p); + gtk_widget_set_sensitive (dialog->visual_combo, sensitive_p); } +} - if (s->debug_p) - fprintf (stderr, "%s: using config directory \"%s\"\n", - progname, hack_configuration_path); +/* 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) +{ + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (s->dialog); + Bool changed = FALSE; + saver_preferences *p = &s->prefs; + int list_elt = selected_list_element (s); + + GtkEntry *cmd = GTK_ENTRY (dialog->cmd_text); + GtkWidget *vis = GTK_WIDGET (dialog->visual_combo); + GtkEntry *visent = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (vis))); + + const char *visual = gtk_entry_get_text (visent); + 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; + } - /* Let Gtk open the X connection, then initialize Xt to use that - same connection. Doctor Frankenstein would be proud. + /* Sanity-check and canonicalize whatever the user typed into the combo box. */ -# ifdef HAVE_CRAPPLET - if (crapplet_p) + 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 visual = ""; + + changed = flush_changes (s, list_elt, -1, command, visual); + if (changed) { - 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.) - */ + demo_write_init_file (s, p); - 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 */ - } + /* Do this to re-launch the hack if (and only if) the command line + has changed. */ + populate_demo_window (s, selected_list_element (s)); + } - client = gnome_master_client (); + DONE: + s->saving_p = FALSE; + return changed; +} - 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); - } + XScreenSaverWindow - if (init_results == 1) - { - system ("xscreensaver -nosplash &"); - return 0; - } + ****************************************************************************/ - } + +static void +xscreensaver_window_destroy (GObject *object) +{ + /* Called by WM close box, but not by File / Quit */ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (object); + quit_menu_cb (NULL, win); /* Shouldn't return? */ + G_OBJECT_CLASS (xscreensaver_window_parent_class)->dispose (object); +} + + +/* gtk_window_move() sets the origin of the window's WM decorations, but + GTK's "configure-event" returns the root-relative origin of the window + within the decorations, so the "configure-event" numbers are too large by + the size of the decorations (title bar and border). Without compensating + for this, the window would move down and slightly to the right every time + we saved and restored. GDK provides no way to find those numbers, so we + have to hack it out X11 style... + */ +static void +wm_decoration_origin (GtkWindow *gtkw, int *x, int *y) +{ + Display *dpy = gdk_x11_get_default_xdisplay(); + GdkWindow *gdkw = gtk_widget_get_window (GTK_WIDGET (gtkw)); + Window xw = gdk_x11_window_get_xid (gdkw); + + Window root, parent, *kids; + unsigned int nkids; + + Atom type = None; + int format; + unsigned long nitems, bytesafter; + unsigned char *data; + + static Atom swm_vroot = 0; + XWindowAttributes xgwa; + + if (!dpy || !xw) return; + if (! XQueryTree (dpy, xw, &root, &parent, &kids, &nkids)) + abort (); + if (kids) XFree ((char *) kids); + + if (parent == root) /* No window above us at all */ + return; + + if (! swm_vroot) + swm_vroot = XInternAtom (dpy, "__SWM_VROOT", FALSE); + + /* If parent is a virtual root, there is no intervening WM decoration. */ + if (XGetWindowProperty (dpy, parent, swm_vroot, + 0, 0, FALSE, AnyPropertyType, + &type, &format, &nitems, &bytesafter, + (unsigned char **) &data) + == Success + && type != None) + return; + + /* If we have a parent, it is the WM decoration, so use its origin. */ + if (! XGetWindowAttributes (dpy, parent, &xgwa)) + abort(); + *x = xgwa.x; + *y = xgwa.y; +} + + +static void +save_window_position (state *s, GtkWindow *win, int x, int y, Bool dialog_p) +{ + saver_preferences *p = &s->prefs; + int win_x, win_y, dialog_x, dialog_y; + char dummy; + char *old = p->settings_geom; + char str[100]; + + if (!s->dpy || s->wayland_p) return; + wm_decoration_origin (win, &x, &y); + + if (!old || !*old || + 4 != sscanf (old, " %d , %d %d , %d %c", + &win_x, &win_y, &dialog_x, &dialog_y, &dummy)) + win_x = win_y = dialog_x = dialog_y = -1; + + if (dialog_p) + dialog_x = x, dialog_y = y; else -# endif /* HAVE_CRAPPLET */ + win_x = x, win_y = y; + + sprintf (str, "%d,%d %d,%d", win_x, win_y, dialog_x, dialog_y); + + if (old && !strcmp (old, str)) return; /* unchanged */ + + p->settings_geom = strdup (str); + + if (s->debug_p) + fprintf (stderr, "%s: geom: %s => %s\n", blurb(), + (old ? old : "null"), str); + + /* This writes the .xscreensaver file repeatedly as the window is dragged, + which is too much. We could defer it with a timer. But instead let's + just not save it upon resize, and only save the positions once the + file is written due to some other change. + */ + /* demo_write_init_file (s, p); */ + if (old) free (old); +} + + +static void +restore_window_position (state *s, GtkWindow *window, Bool dialog_p) +{ + saver_preferences *p = &s->prefs; + int win_x, win_y, dialog_x, dialog_y, x, y; + char dummy; + char *old = p->settings_geom; + + if (!old || !*old || + 4 != sscanf (old, " %d , %d %d , %d %c", + &win_x, &win_y, &dialog_x, &dialog_y, &dummy)) + win_x = win_y = dialog_x = dialog_y = -1; + + if (dialog_p) + x = dialog_x, y = dialog_y; + else + x = win_x, y = win_y; + + if (x <= 0 || y <= 0) return; + + if (s->debug_p) + fprintf (stderr, "%s: restore origin: %d,%d\n", blurb(), x, y); + gtk_window_move (window, x, y); +} + + +/* When the window is moved, save the new origin in .xscreensaver. */ +static gboolean +xscreensaver_window_resize_cb (GtkWindow *window, GdkEvent *event, + gpointer data) +{ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (window); + state *s = &win->state; + save_window_position (s, GTK_WINDOW (win), + event->configure.x, event->configure.y, FALSE); + return FALSE; +} + + +static int +delayed_scroll_kludge (gpointer data) +{ + state *s = (state *) data; + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); + GtkWidget *list_widget = win->list; + ensure_selected_item_visible (s, list_widget); + return FALSE; /* do not re-execute timer */ +} + + +static void +quit_action_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data) +{ + quit_menu_cb (NULL, user_data); +} + + +static GActionEntry app_entries[] = { + { "quit", quit_action_cb, NULL, NULL, NULL }, +/* + { "undo", undo_action_cb, NULL, NULL, NULL }, + { "redo", redo_action_cb, NULL, NULL, NULL }, + { "cut", cut_action_cb, NULL, NULL, NULL }, + { "copy", copy_action_cb, NULL, NULL, NULL }, + { "paste", paste_action_cb, NULL, NULL, NULL }, + { "delete", delete_action_cb, NULL, NULL, NULL }, +*/ +}; + +/* #### I don't know how to make the accelerators show up on the menu items, + and I don't understand the difference between "callbacks" and "actions". + I see examples in other .ui files that do things like: + + <ui> + <menubar name="menubar"> + <menu action="file"> + <menuitem name="activate_menu" action="activate_action"/> + or + <menu id="menubar"> + <submenu> + <attribute name="label">File</attribute> + <section> + <item> + <attribute name="label">Activate</attribute> + <attribute name="action">app.activate</attribute> + + but when I put variants of that in demo.ui, nothing shows up. + + It would be nice to have an "Edit" menu with working text-editing commands + on it, for use with our various text fields. One would think that a GUI + toolkit would provide boilerplate for such things, but nooooo... + */ +const gchar *accels[][2] = { + { "app.quit", "<Ctrl>Q" }, +/* + { "app.undo", "<Ctrl>Z" }, + { "app.redo", "<Ctrl>Y" }, + { "app.cut", "<Ctrl>X" }, + { "app.copy", "<Ctrl>C" }, + { "app.paste", "<Ctrl>V" }, +*/ +}; + + +static void +xscreensaver_window_realize (GtkWidget *self, gpointer user_data) +{ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (self); + state *s = &win->state; + saver_preferences *p = &s->prefs; + + s->initializing_p = TRUE; + s->short_version = XSCREENSAVER_VERSION; + s->window = GTK_WINDOW (win); + + s->dpy = gdk_x11_get_default_xdisplay(); + + /* Debian 11.4, Gtk 3.24.24, 2022: under Wayland, get_default_xdisplay is + returning uninitialized data! However, gdk_x11_window_get_xid prints a + warning and returns NULL. So let's try that, and as a fallback, also try + and sanity check the contents of the Display structure... + */ + if (! gdk_x11_window_get_xid (gtk_widget_get_window (self))) { - gtk_init (&argc, &argv); + s->dpy = NULL; + s->wayland_p = TRUE; } + if (s->dpy) + { + if (ProtocolVersion (s->dpy) != 11 || + ProtocolRevision (s->dpy) != 0) + { + fprintf (stderr, "%s: uninitialized data in Display: " + "protocol version %d.%d!\n", blurb(), + ProtocolVersion(s->dpy), ProtocolRevision(s->dpy)); + s->dpy = NULL; + s->wayland_p = TRUE; + } + } - /* 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. + /* If we don't have a display connection, then we are surely under Wayland + even if the environment variable is not set. */ - argv[0] = "xscreensaver"; - progname = argv[0]; + if (!s->dpy && + !getenv ("WAYLAND_DISPLAY") && + !getenv ("WAYLAND_SOCKET")) + putenv ("WAYLAND_DISPLAY=probably"); + if (getenv ("WAYLAND_DISPLAY") || + getenv ("WAYLAND_SOCKET")) + s->wayland_p = TRUE; - /* 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, (char **) &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. + /* If GTK is running directly under Wayland, try to open an X11 connection + to XWayland anyway, so that get_string_resource and load_init_file work. */ - for (i = 1; i < argc; i++) + if (! s->dpy) { - 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); - } + s->dpy = XOpenDisplay (NULL); + if (s->debug_p) + { + if (s->dpy) + fprintf (stderr, "%s: opened secondary XWayland connection\n", + blurb()); + else + fprintf (stderr, "%s: failed to open XWayland connection\n", + blurb()); + } } - /* 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]. + /* Teach Xt to use the Display that Gtk/Gdk have already opened. */ - p->db = db; - s->nscreens = screen_count (dpy); + if (s->dpy) + { + XtAppContext app; + int argc = 0; + char *argv[2]; + const char *oprogname = progname; - init_xscreensaver_atoms (dpy); - hack_environment (s); /* must be before initialize_sort_map() */ + progname = "xscreensaver"; /* For X resources */ + argv[argc++] = (char *) progname; + argv[argc] = 0; - load_init_file (dpy, p); - initialize_sort_map (s); + XtToolkitInitialize (); + app = XtCreateApplicationContext (); + XtAppSetFallbackResources (app, defaults); + XtDisplayInitialize (app, s->dpy, progname, progclass, 0, 0, &argc, argv); - /* 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 (s->debug_p) + { + XSync (s->dpy, False); + XSynchronize (s->dpy, True); /* Must be after XtDisplayInitialize */ + } + /* Result discarded and leaked */ + XtAppCreateShell (progname, progclass, applicationShellWidgetClass, + s->dpy, 0, 0); + p->db = XtDatabase (s->dpy); + XSetErrorHandler (x_error); -#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 + signal (SIGPIPE, SIG_IGN); /* Is this necessary? */ - init_xscreensaver_atoms (dpy); + progname = oprogname; - /* 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; +# if 0 + /* Print out all the Xrm resources we read. */ + { + XrmName name = { 0 }; + XrmClass class = { 0 }; + int count = 0; + XrmEnumerateDatabase (db, &name, &class, XrmEnumAllLevels, xrm_mapper, + (void *) &count); + } +# endif + } + s->multi_screen_p = multi_screen_p (s->dpy); - /* Set the main window's title. */ + /* Let's see if the server supports DPMS. + */ + s->dpms_supported_p = FALSE; +# ifdef HAVE_DPMS_EXTENSION { - 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); + int op = 0, event = 0, error = 0; + if (s->dpy && XQueryExtension (s->dpy, "DPMS", &op, &event, &error)) + /* Technically this should also check DPMSCapable(), but this is + almost certainly close enough. */ + s->dpms_supported_p = TRUE; + else if (s->debug_p) + fprintf (stderr, "%s: server does not support power management\n", + blurb()); } +# else /* !HAVE_DPMS_EXTENSION */ + if (s->debug_p) + fprintf (stderr, "%s: DPMS not supported at compile time\n", blurb()); +# endif /* !HAVE_DPMS_EXTENSION */ - /* 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); - } +# if defined(__APPLE__) && !defined(__OPTIMIZE__) + s->dpms_supported_p = TRUE; /* macOS X11: debugging kludge */ +# endif - /* 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); + if (s->dpy) + init_xscreensaver_atoms (s->dpy); + + hack_environment (s); /* must be before initialize_sort_map() */ + load_init_file (s->dpy, p); + initialize_sort_map (s); + + s->gl_visual = get_best_gl_visual (s); + s->dialog = g_object_new (XSCREENSAVER_DIALOG_TYPE, NULL); + XSCREENSAVER_DIALOG (s->dialog)->main = win; + gtk_window_set_transient_for (GTK_WINDOW (s->dialog), GTK_WINDOW (win)); + + sensitize_menu_items (s, TRUE); populate_hack_list (s); populate_prefs_page (s); - sensitize_demo_widgets (s, False); - fix_text_entry_sizes (s); + sensitize_demo_widgets (s, FALSE); scroll_to_current_hack (s); + if (s->dpy && !s->wayland_p) + fix_preview_visual (s); + if (! s->multi_screen_p) + hide_mode_menu_random_same (s); + + restore_window_position (s, GTK_WINDOW (self), FALSE); - 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. */ - gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "mode_menu")), - "changed", GTK_SIGNAL_FUNC (mode_menu_item_cb), - (gpointer) s); - if (s->nscreens <= 1) + g_timeout_add (60 * 1000, check_blanked_timer, s); + + /* Attach the actions and their keybindings. */ + { + int i; + GtkApplication *app = gtk_window_get_application (GTK_WINDOW (win)); + g_action_map_add_action_entries (G_ACTION_MAP (app), + app_entries, countof (app_entries), + win); + for (i = 0; i < countof (accels); i++) + { + const gchar *a[2]; + a[0] = accels[i][1]; + a[1] = 0; + gtk_application_set_accels_for_action (GTK_APPLICATION (app), + accels[i][0], a); + } + } + +# if 0 + /* Load every configurator in turn, to scan them for errors all at once. */ + if (s->debug_p) { - GtkComboBox *opt = GTK_COMBO_BOX (name_to_widget (s, "mode_menu")); - GtkTreeModel *list = gtk_combo_box_get_model (opt); - unsigned int i; - for (i = 0; i < countof(mode_menu_order); i++) + int i; + for (i = 0; i < p->screenhacks_count; i++) { - /* The "random-same" mode menu item does not appear unless - there are multiple screens. - */ - if (mode_menu_order[i] == RANDOM_HACKS_SAME) - { - GtkTreeIter iter; - gtk_tree_model_iter_nth_child (list, &iter, NULL, i); - gtk_list_store_remove (GTK_LIST_STORE (list), &iter); - break; - } + screenhack *hack = p->screenhacks[i]; + conf_data *d = load_configurator (hack->command, s->debug_p); + if (d) free_conf_data (d); } - - /* recompute option-menu size */ - gtk_widget_unrealize (GTK_WIDGET (opt)); - gtk_widget_realize (GTK_WIDGET (opt)); } +# endif - - /* Handle the -prefs command-line argument. */ - if (prefs_p) + /* Grab the screenshot pixmap before mapping the window. */ + if (s->dpy && !s->wayland_p) { - GtkNotebook *notebook = - GTK_NOTEBOOK (name_to_widget (s, "notebook")); - gtk_notebook_set_page (notebook, 1); + GdkWindow *gw = gtk_widget_get_window (win->preview); + Window xw = gdk_x11_window_get_xid (gw); + s->screenshot = screenshot_grab (s->dpy, xw, TRUE, s->debug_p); } -# ifdef HAVE_CRAPPLET - if (crapplet_p) - { - GtkWidget *capplet; - GtkWidget *outer_vbox; + /* Issue any warnings about the running xscreensaver daemon. + Wait a few seconds, in case things are still starting up. */ + g_timeout_add (5 * 1000, the_network_is_not_the_computer, s); - gtk_widget_hide (s->toplevel_widget); + /* 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. */ + g_timeout_add (500, delayed_scroll_kludge, s); - capplet = capplet_widget_new (); + s->initializing_p = FALSE; +} - /* 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); - } - } +static void +xscreensaver_window_init (XScreenSaverWindow *win) +{ + gtk_widget_init_template (GTK_WIDGET (win)); + g_signal_connect (win, "destroy", + G_CALLBACK (xscreensaver_window_destroy), win); + g_signal_connect (win, "realize", + G_CALLBACK (xscreensaver_window_realize), win); + g_signal_connect (win, "configure-event", + G_CALLBACK (xscreensaver_window_resize_cb),win); + g_signal_connect (win->preview, "configure-event", + G_CALLBACK (preview_resize_cb),win); +} - s->toplevel_widget = capplet; - } -# endif /* HAVE_CRAPPLET */ +static void +xscreensaver_window_class_init (XScreenSaverWindowClass *class) +{ + gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class), + "/org/jwz/xscreensaver/demo.ui"); + + /* Fill in the widget fields in XScreenSaverWindow with the corresponding + objects created from demo.ui. */ +# undef W +# define W(NAME) \ + gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), \ + XScreenSaverWindow, NAME); + ALL_WINDOW_WIDGETS +# undef W +} - /* 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 + XScreenSaverDialog - 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.) +static void +xscreensaver_dialog_destroy (GObject *object) +{ + /* Called by WM close box, but not by File / Quit */ + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (object); + XScreenSaverWindow *win = dialog->main; + flush_dialog_changes_and_save (&win->state); + G_OBJECT_CLASS (xscreensaver_dialog_parent_class)->dispose (object); +} - #### 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")); +static void +xscreensaver_dialog_realize (GtkWidget *self, gpointer user_data) +{ + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (self); + XScreenSaverWindow *win = dialog->main; + state *s = &win->state; + restore_window_position (s, GTK_WINDOW (self), TRUE); +} - gtk_timeout_add (60 * 1000, check_blanked_timer, s); +/* When the window is moved, save the new origin in .xscreensaver. */ +static gboolean +xscreensaver_dialog_resize_cb (GtkWindow *window, GdkEvent *event, + gpointer data) +{ + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (window); + XScreenSaverWindow *win = dialog->main; + state *s = &win->state; + save_window_position (s, GTK_WINDOW (dialog), + event->configure.x, event->configure.y, TRUE); + return FALSE; +} - /* Handle the --settings command-line argument. */ - if (settings_p) - gtk_timeout_add (500, settings_timer, 0); +/* The WM close box. */ +static gboolean +xscreensaver_dialog_delete_cb (GtkWidget *self, GdkEvent *event, + gpointer user_data) +{ + settings_cancel_cb (GTK_WIDGET (self), user_data); + return TRUE; /* Do not run other handlers */ +} - /* Issue any warnings about the running xscreensaver daemon. */ - if (! s->debug_p) - the_network_is_not_the_computer (s); +static void +xscreensaver_dialog_init (XScreenSaverDialog *win) +{ + gtk_widget_init_template (GTK_WIDGET (win)); + g_signal_connect (win, "destroy", + G_CALLBACK (xscreensaver_dialog_destroy), win); + g_signal_connect (win, "realize", + G_CALLBACK (xscreensaver_dialog_realize), win); + g_signal_connect (win, "configure-event", + G_CALLBACK (xscreensaver_dialog_resize_cb), win); + g_signal_connect (win, "delete-event", + G_CALLBACK (xscreensaver_dialog_delete_cb), win); +} - if (time ((time_t *) 0) - XSCREENSAVER_RELEASED > 60*60*24*30*17) - warning_dialog (s->toplevel_widget, - _("Warning:\n\n" - "This version of xscreensaver is VERY OLD!\n" - "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; +static void +xscreensaver_dialog_class_init (XScreenSaverDialogClass *class) +{ + gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class), + "/org/jwz/xscreensaver/prefs.ui"); + + /* Fill in the widget fields in XScreenSaverDialog with the corresponding + objects created from prefs.ui. */ +# undef W +# define W(NAME) \ + gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), \ + XScreenSaverDialog, NAME); + ALL_DIALOG_WIDGETS +# undef W +} - /* 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); +/**************************************************************************** + + XScreenSaverApp + + ****************************************************************************/ -#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 +static void +xscreensaver_app_init (XScreenSaverApp *app) +{ +} -# ifdef HAVE_CRAPPLET - if (crapplet_p) - capplet_gtk_main (); + +static void +xscreensaver_app_startup (GApplication *app) +{ + G_APPLICATION_CLASS (xscreensaver_app_parent_class)->startup (app); + + /* Without this, the floating point numbers in the XML files are not + parsed properly in locales that use commas instead of periods in + floats: sscanf %f expects "1.0" to be "1,0" and returns 0. + + This must be called later than main() because something beneath + g_application_run() calls setlocale(LC_ALL, "") and would override it. + */ +# ifdef ENABLE_NLS + if (!setlocale (LC_NUMERIC, "C")) + fprintf (stderr, "%s: warning: could not set LC_NUMERIC=C\n", blurb()); +# endif /* ENABLE_NLS */ +} + + +static void +xscreensaver_app_activate (GApplication *app) +{ + XScreenSaverWindow *win = + g_object_new (XSCREENSAVER_WINDOW_TYPE, "application", app, NULL); + win->state.debug_p = XSCREENSAVER_APP (app)->cmdline_debug_p; + gtk_widget_show_all (GTK_WIDGET (win)); + gtk_window_present (GTK_WINDOW (win)); +} + + +static void +xscreensaver_app_open (GApplication *app, + GFile **files, gint n_files, + const gchar *hint) +{ + GList *windows = gtk_application_get_windows (GTK_APPLICATION (app)); + if (windows) + gtk_window_present (GTK_WINDOW (windows->data)); else -# endif /* HAVE_CRAPPLET */ - gtk_main (); + xscreensaver_app_activate (app); +} + - kill_preview_subproc (s, False); - exit (0); +static int +opts_cb (GApplication *app, GVariantDict *opts, gpointer data) +{ + if (g_variant_dict_contains (opts, "version")) { + fprintf (stderr, "%s\n", screensaver_id+4); + return 0; + } else if (g_variant_dict_contains (opts, "debug")) { + XSCREENSAVER_APP (app)->cmdline_debug_p = TRUE; + return -1; + } else { + return -1; + } } + +static void +xscreensaver_app_class_init (XScreenSaverAppClass *class) +{ + G_APPLICATION_CLASS (class)->startup = xscreensaver_app_startup; + G_APPLICATION_CLASS (class)->activate = xscreensaver_app_activate; + G_APPLICATION_CLASS (class)->open = xscreensaver_app_open; +} + +static XScreenSaverApp * +xscreensaver_app_new (void) +{ + XScreenSaverApp *app = g_object_new (XSCREENSAVER_APP_TYPE, + "application-id", + "org.jwz.xscreensaver.settings", + NULL); + + g_application_add_main_option (G_APPLICATION (app), "version", 'v', + G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, + "Print the version number", + NULL); + g_application_add_main_option (G_APPLICATION (app), "debug", 0, + G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, + "Print diagnostics to stderr", + NULL); + g_signal_connect (app, "handle-local-options", G_CALLBACK (opts_cb), app); + return app; +} + + +int +main (int argc, char *argv[]) +{ + char *s; + progname = argv[0]; + s = strrchr (progname, '/'); + if (s) progname = s+1; + g_log_set_default_handler (g_logger, NULL); + g_log_set_writer_func (g_other_logger, NULL, NULL); + +# ifdef ENABLE_NLS + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + textdomain (GETTEXT_PACKAGE); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); +# endif /* ENABLE_NLS */ + + return g_application_run (G_APPLICATION (xscreensaver_app_new()), + argc, argv); +} + + + #endif /* HAVE_GTK -- whole file */ diff --git a/driver/demo-Xm.c b/driver/demo-Xm.c index 547bbe9..2a95bff 100644 --- a/driver/demo-Xm.c +++ b/driver/demo-Xm.c @@ -451,6 +451,7 @@ await_xscreensaver (Widget widget) 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" @@ -465,7 +466,11 @@ await_xscreensaver (Widget widget) "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."); + "normal user, and `su' as necessary." + */ + "You are running as root. Don't do that. Instead, you should\n" + "log in as a normal user and use `sudo' as necessary." + ); else strcat (buf, "Please check your $PATH and permissions."); @@ -605,13 +610,21 @@ apply_changes_and_save (Widget widget) /* Something was changed -- store results into the struct, and write the file. */ + int status; 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); + status = demo_write_init_file (widget, p); + + /* Tell the xscreensaver daemon to wake up and reload the init file, + in case the timeout has changed. Without this, it would wait + until the *old* timeout had expired before reloading. */ + xscreensaver_command (XtDisplay (widget), XA_DEACTIVATE, 0, 0, 0); + + return status; } /* No changes made */ @@ -1530,7 +1543,7 @@ the_network_is_not_the_computer (Widget parent) 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" + "on display \"%.25s\". You can launch it by selecting\n" "`Restart Daemon' from the File menu, or by typing\n" "\"xscreensaver &\" in a shell.", d); diff --git a/driver/demo.ui b/driver/demo.ui new file mode 100644 index 0000000..1dd8403 --- /dev/null +++ b/driver/demo.ui @@ -0,0 +1,2630 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="XScreenSaverWindow" parent="GtkApplicationWindow"> + <property name="title" translatable="yes">XScreenSaver Settings</property> + <property name="type">GTK_WINDOW_TOPLEVEL</property> + <property name="window-position">GTK_WIN_POS_NONE</property> + <property name="modal">False</property> + <property name="resizable">True</property> + <property name="destroy-with-parent">False</property> + <property name="decorated">True</property> + <property name="skip-taskbar-hint">False</property> + <property name="skip-pager-hint">False</property> + <property name="type-hint">GDK_WINDOW_TYPE_HINT_NORMAL</property> + <property name="gravity">GDK_GRAVITY_NORTH_WEST</property> + <property name="focus-on-map">True</property> + <property name="urgency-hint">False</property> + <child> + <object class="GtkAdjustment" id="adjustment1"> + <property name="upper">720</property> + <property name="lower">1</property> + <property name="page-increment">15</property> + <property name="step-increment">1</property> + <property name="page-size">0</property> + <property name="value">1</property> + </object> + <object class="GtkAdjustment" id="adjustment2"> + <property name="upper">720</property> + <property name="lower">0</property> + <property name="page-increment">15</property> + <property name="step-increment">1</property> + <property name="page-size">0</property> + <property name="value">0</property> + </object> + <object class="GtkAdjustment" id="adjustment3"> + <property name="upper">720</property> + <property name="lower">0</property> + <property name="page-increment">15</property> + <property name="step-increment">1</property> + <property name="page-size">0</property> + <property name="value">0</property> + </object> + <object class="GtkAdjustment" id="adjustment4"> + <property name="upper">1440</property> + <property name="lower">0</property> + <property name="page-increment">15</property> + <property name="step-increment">1</property> + <property name="page-size">0</property> + <property name="value">0</property> + </object> + <object class="GtkAdjustment" id="adjustment5"> + <property name="upper">1440</property> + <property name="lower">0</property> + <property name="page-increment">15</property> + <property name="step-increment">1</property> + <property name="page-size">0</property> + <property name="value">0</property> + </object> + <object class="GtkAdjustment" id="adjustment6"> + <property name="upper">1440</property> + <property name="lower">0</property> + <property name="page-increment">15</property> + <property name="step-increment">1</property> + <property name="page-size">0</property> + <property name="value">0</property> + </object> + <object class="GtkAdjustment" id="adjustment7"> + <property name="upper">10</property> + <property name="lower">0</property> + <property name="page-increment">1</property> + <property name="step-increment">1</property> + <property name="page-size">0</property> + <property name="value">0</property> + </object> + <object class="GtkListStore" id="mode_menu_model"> + <columns> + <column type="gchararray"/> + </columns> + <data> + <row> + <col id="0" translatable="yes">Disable Screen Saver</col> + </row> + <row> + <col id="0" translatable="yes">Blank Screen Only</col> + </row> + <row> + <col id="0" translatable="yes">Only One Screen Saver</col> + </row> + <row> + <col id="0" translatable="yes">Random Screen Saver</col> + </row> + <row> + <col id="0" translatable="yes">Same Random Savers</col> + </row> + </data> + </object> + <object class="GtkListStore" id="theme_menu_model"> + <columns> + <column type="gchararray"/> + </columns> + <data> + <row> + <col id="0" translatable="yes">Default</col> + </row> + </data> + </object> + <object class="GtkBox" id="content_box"> + <property name="name">content_box</property> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="outer_vbox"> + <property name="name">outer_vbox</property> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">5</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkMenuBar" id="menubar"> + <property name="name">menubar</property> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkImageMenuItem"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label">gtk-file</property> + <property name="use-stock">True</property> + <property name="use-underline">True</property> + <signal handler="file_menu_cb" name="activate"/> + <child type="submenu"> + <object class="GtkMenu"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkImageMenuItem" id="activate_menuitem"> + <property name="name">activate_menuitem</property> + <property name="label" translatable="yes">_Blank Screen Now</property> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="use-underline">True</property> + <signal handler="activate_menu_cb" name="activate"/> + </object> + </child> + <child> + <object class="GtkImageMenuItem" id="lock_menuitem"> + <property name="name">lock_menuitem</property> + <property name="label" translatable="yes">_Lock Screen Now</property> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="use-underline">True</property> + <signal handler="lock_menu_cb" name="activate"/> + </object> + </child> + <child> + <object class="GtkImageMenuItem" id="kill_menuitem"> + <property name="name">kill_menuitem</property> + <property name="label" translatable="yes">_Kill Daemon</property> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="use-underline">True</property> + <signal handler="kill_menu_cb" name="activate"/> + </object> + </child> + <child> + <object class="GtkImageMenuItem" id="restart_menuitem"> + <property name="name">restart_menuitem</property> + <property name="label" translatable="yes">_Restart Daemon</property> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="use-underline">True</property> + <signal handler="restart_menu_cb" name="activate"/> + </object> + </child> + <child> + <object class="GtkImageMenuItem"> + <property name="label">gtk-quit</property> + <property name="use-stock">True</property> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="use-underline">True</property> + <signal handler="quit_menu_cb" name="activate"/> + </object> + </child> + </object> + </child> + </object> + </child> + +<!-- + <child> + <object class="GtkImageMenuItem"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label">gtk-edit</property> + <property name="use-stock">True</property> + <property name="use-underline">True</property> + <child type="submenu"> + <object class="GtkMenu"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkImageMenuItem"> + <property name="label">gtk-undo</property> + <property name="use-stock">True</property> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="use-underline">True</property> + <signal handler="undo_menu_cb" name="activate"/> + </object> + </child> + <child> + <object class="GtkImageMenuItem"> + <property name="label">gtk-redo</property> + <property name="use-stock">True</property> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="use-underline">True</property> + <signal handler="redo_menu_cb" name="activate"/> + </object> + </child> + <child> + <object class="GtkSeparatorMenuItem"> + <property name="visible">True</property> + </object> + </child> + <child> + <object class="GtkImageMenuItem"> + <property name="label">gtk-cut</property> + <property name="use-stock">True</property> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="use-underline">True</property> + <signal handler="cut_menu_cb" name="activate"/> + </object> + </child> + <child> + <object class="GtkImageMenuItem"> + <property name="label">gtk-copy</property> + <property name="use-stock">True</property> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="use-underline">True</property> + <signal handler="copy_menu_cb" name="activate"/> + </object> + </child> + <child> + <object class="GtkImageMenuItem"> + <property name="label">gtk-paste</property> + <property name="use-stock">True</property> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="use-underline">True</property> + <signal handler="paste_menu_cb" name="activate"/> + </object> + </child> + <child> + <object class="GtkImageMenuItem"> + <property name="label">gtk-delete</property> + <property name="use-stock">True</property> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="use-underline">True</property> + <signal handler="delete_menu_cb" name="activate"/> + </object> + </child> + </object> + </child> + </object> + </child> +--> + + <child> + <object class="GtkImageMenuItem"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label">gtk-help</property> + <property name="use-stock">True</property> + <property name="use-underline">True</property> + <child type="submenu"> + <object class="GtkMenu"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkImageMenuItem"> + <property name="label">gtk-about</property> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="use-underline">True</property> + <property name="use-stock">True</property> + <signal handler="about_menu_cb" name="activate"/> + </object> + </child> + <child> + <object class="GtkImageMenuItem"> + <property name="label" translatable="yes">Documentation...</property> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="use-underline">True</property> + <signal handler="doc_menu_cb" name="activate"/> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <object class="GtkNotebook" id="notebook"> + <property name="name">notebook</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="show-tabs">True</property> + <property name="show-border">True</property> + <property name="tab-pos">GTK_POS_TOP</property> + <property name="scrollable">False</property> + <property name="enable-popup">False</property> + <signal handler="switch_page_cb" name="switch_page"/> + + <!-- Display Modes page --> + <child> + <object class="GtkGrid" id="demos_table"> + <property name="name">demos_table</property> + <property name="visible">True</property> + <property name="margin-top">12</property> + <property name="margin-bottom">12</property> + <property name="margin-start">12</property> + <property name="margin-end">12</property> + <property name="column-homogeneous">False</property> + <child> + <object class="GtkBox" id="list_vbox"> + <property name="name">list_vbox</property> + <property name="border-width">10</property> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">4</property> + <property name="orientation">vertical</property> + <property name="hexpand">False</property> + <property name="vexpand">True</property> + <child> + <object class="GtkBox" id="mode_hbox"> + <property name="name">mode_hbox</property> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + <property name="orientation">horizontal</property> + <child> + <object class="GtkLabel" id="mode_label"> + <property name="name">mode_label</property> + <property name="visible">True</property> + <property name="label" translatable="yes">_Mode:</property> + <property name="use-underline">True</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="mnemonic-widget">mode_menu</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + <accessibility> + <relation target="mode_menu" type="label-for"/> + </accessibility> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkComboBox" id="mode_menu"> + <property name="name">mode_menu</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="has-entry">False</property> + <property name="model">mode_menu_model</property> + <signal handler="mode_menu_item_cb" name="changed"/> + <accessibility> + <relation target="mode_label" type="labelled-by"/> + </accessibility> + <child> + <object class="GtkCellRendererText" id="renderer1"/> + <attributes> + <attribute name="text">0</attribute> + </attributes> + </child> + </object> + <packing> + <property name="padding">4</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </object> + <packing> + <property name="padding">10</property> + <property name="expand">False</property> + <property name="fill">True</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="scroller"> + <property name="name">scroller</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="hscrollbar-policy">GTK_POLICY_NEVER</property> + <property name="vscrollbar-policy">GTK_POLICY_ALWAYS</property> + <property name="shadow-type">GTK_SHADOW_IN</property> + <property name="window-placement">GTK_CORNER_TOP_LEFT</property> + <child> + <object class="GtkTreeView" id="list"> + <property name="name">list</property> + <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> + <property name="search-column">1</property> + </object> + </child> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + <child> + <object class="GtkBox" id="centering_hbox"> + <property name="name">centering_hbox</property> + <property name="visible">True</property> + <property name="homogeneous">True</property> + <property name="spacing">0</property> + <property name="orientation">horizontal</property> + <property name="margin-top">4</property> + <property name="margin-bottom">8</property> + <child> + <object class="GtkBox" id="next_prev_hbox"> + <property name="name">next_prev_hbox</property> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">8</property> + <property name="orientation">horizontal</property> + <child> + <object class="GtkButton" id="next"> + <property name="name">next</property> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Run the next screen saver in the list in full-screen mode (click the mouse to return).</property> + <property name="can-focus">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus-on-click">True</property> + <signal handler="run_next_cb" name="clicked"/> + <child> + <object class="GtkArrow" id="arrow1"> + <property name="name">arrow1</property> + <property name="visible">True</property> + <property name="arrow-type">GTK_ARROW_DOWN</property> + <property name="shadow-type">GTK_SHADOW_OUT</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </object> + </child> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkButton" id="prev"> + <property name="name">prev</property> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Run the previous screen saver in the list in full-screen mode.</property> + <property name="can-focus">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus-on-click">True</property> + <signal handler="run_prev_cb" name="clicked"/> + <child> + <object class="GtkArrow" id="arrow2"> + <property name="name">arrow2</property> + <property name="visible">True</property> + <property name="arrow-type">GTK_ARROW_UP</property> + <property name="shadow-type">GTK_SHADOW_OUT</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </object> + </child> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack-type">GTK_PACK_END</property> + </packing> + </child> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">0</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="blanking_table"> + <property name="name">blanking_table</property> + <property name="visible">True</property> + <property name="row-spacing">2</property> + <property name="margin-start">8</property> + <property name="margin-end">8</property> + <child> + <object class="GtkLabel" id="timeout_label"> + <property name="name">timeout_label</property> + <property name="visible">True</property> + <property name="label" translatable="yes">_Blank After</property> + <property name="use-underline">True</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_RIGHT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">1</property> + <property name="yalign">0.5</property> + <property name="xpad">8</property> + <property name="ypad">0</property> + <property name="mnemonic-widget">timeout_spinbutton</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + <accessibility> + <relation target="timeout_spinbutton" type="label-for"/> + <relation target="timeout_spinbutton" type="flows-to"/> + </accessibility> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">0</property> + </packing> + </child> + <child> + <object class="GtkSpinButton" id="timeout_spinbutton"> + <property name="name">timeout_spinbutton</property> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">How long before the screen saver activates.</property> + <property name="can-focus">True</property> + <property name="climb-rate">15</property> + <property name="digits">0</property> + <property name="numeric">True</property> + <property name="update-policy">GTK_UPDATE_ALWAYS</property> + <property name="snap-to-ticks">True</property> + <property name="wrap">False</property> + <property name="adjustment">adjustment1</property> + <accessibility> + <relation target="timeout_label" type="labelled-by"/> + <relation target="timeout_mlabel" type="labelled-by"/> + <relation target="timeout_label" type="flows-from"/> + <relation target="timeout_mlabel" type="flows-to"/> + </accessibility> + <signal handler="pref_changed_cb" name="activate"/> + <signal handler="pref_changed_event_cb" name="focus_out_event"/> + <signal handler="pref_changed_cb" name="value_changed"/> + <signal handler="dpms_sanity_cb" name="activate"/> + <signal handler="dpms_sanity_cb" name="value_changed"/> + <signal handler="dpms_sanity_event_cb" name="focus_out_event"/> + </object> + <packing> + <property name="left-attach">1</property> + <property name="top-attach">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="timeout_mlabel"> + <property name="name">timeout_mlabel</property> + <property name="visible">True</property> + <property name="label" translatable="yes">minutes</property> + <property name="use-underline">False</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">8</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + <accessibility> + <relation target="timeout_spinbutton" type="label-for"/> + <relation target="timeout_spinbutton" type="flows-from"/> + </accessibility> + </object> + <packing> + <property name="left-attach">2</property> + <property name="top-attach">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="cycle_label"> + <property name="name">cycle_label</property> + <property name="visible">True</property> + <property name="label" translatable="yes">_Cycle After</property> + <property name="use-underline">True</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_RIGHT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">1</property> + <property name="yalign">0.5</property> + <property name="xpad">8</property> + <property name="ypad">0</property> + <property name="mnemonic-widget">cycle_spinbutton</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + <accessibility> + <relation target="cycle_spinbutton" type="label-for"/> + <relation target="cycle_spinbutton" type="flows-to"/> + </accessibility> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">1</property> + </packing> + </child> + <child> + <object class="GtkSpinButton" id="cycle_spinbutton"> + <property name="name">cycle_spinbutton</property> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">How long each display mode should run before choosing a new one (in Random mode).</property> + <property name="can-focus">True</property> + <property name="climb-rate">15</property> + <property name="digits">0</property> + <property name="numeric">True</property> + <property name="update-policy">GTK_UPDATE_ALWAYS</property> + <property name="snap-to-ticks">True</property> + <property name="wrap">False</property> + <property name="adjustment">adjustment3</property> + <accessibility> + <relation target="cycle_label" type="labelled-by"/> + <relation target="cycle_mlabel" type="labelled-by"/> + <relation target="cycle_label" type="flows-from"/> + <relation target="cycle_mlabel" type="flows-to"/> + </accessibility> + <signal handler="pref_changed_cb" name="activate"/> + <signal handler="pref_changed_event_cb" name="focus_out_event"/> + <signal handler="pref_changed_cb" name="value_changed"/> + </object> + <packing> + <property name="left-attach">1</property> + <property name="top-attach">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="cycle_mlabel"> + <property name="name">cycle_mlabel</property> + <property name="visible">True</property> + <property name="label" translatable="yes">minutes</property> + <property name="use-underline">False</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">8</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + <accessibility> + <relation target="cycle_spinbutton" type="label-for"/> + <relation target="cycle_spinbutton" type="flows-from"/> + </accessibility> + </object> + <packing> + <property name="left-attach">2</property> + <property name="top-attach">1</property> + </packing> + </child> + <child> + <object class="GtkEventBox" id="lock_button_eventbox"> + <property name="name">lock_button_eventbox</property> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Whether a password should be required to un-blank the screen.</property> + <property name="visible-window">False</property> + <property name="above-child">False</property> + <child> + <object class="GtkCheckButton" id="lock_button"> + <property name="name">lock_button</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="label" translatable="yes">_Lock Screen After </property> + <property name="use-underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus-on-click">True</property> + <property name="active">False</property> + <property name="inconsistent">False</property> + <property name="draw-indicator">True</property> + <accessibility> + <relation target="lock_spinbutton" type="controller-for"/> + <relation target="lock_spinbutton" type="label-for"/> + <relation target="lock_spinbutton" type="flows-to"/> + </accessibility> + <signal handler="pref_changed_cb" name="toggled"/> + <child internal-child="accessible"> + <object class="AtkObject" id="a11y-lock_button1"> + <property name="AtkObject::accessible-name" translatable="yes">Lock Screen</property> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">2</property> + </packing> + </child> + <child> + <object class="GtkSpinButton" id="lock_spinbutton"> + <property name="name">lock_spinbutton</property> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">How long after the screen blanks until a password will be required.</property> + <property name="can-focus">True</property> + <property name="climb-rate">15</property> + <property name="digits">0</property> + <property name="numeric">True</property> + <property name="update-policy">GTK_UPDATE_ALWAYS</property> + <property name="snap-to-ticks">True</property> + <property name="wrap">False</property> + <property name="adjustment">adjustment2</property> + <accessibility> + <relation target="lock_button" type="controlled-by"/> + <relation target="lock_button" type="labelled-by"/> + <relation target="lock_mlabel" type="labelled-by"/> + <relation target="lock_button" type="flows-from"/> + <relation target="lock_mlabel" type="flows-to"/> + </accessibility> + <signal handler="pref_changed_cb" name="activate"/> + <signal handler="pref_changed_event_cb" name="focus_out_event"/> + <signal handler="pref_changed_cb" name="value_changed"/> + <child internal-child="accessible"> + <object class="AtkObject" id="a11y-lock_spinbutton1"> + <property name="AtkObject::accessible-name" translatable="yes">Lock Screen After</property> + </object> + </child> + </object> + <packing> + <property name="left-attach">1</property> + <property name="top-attach">2</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="lock_mlabel"> + <property name="name">lock_mlabel</property> + <property name="visible">True</property> + <property name="label" translatable="yes">minutes</property> + <property name="use-underline">False</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">8</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + <accessibility> + <relation target="lock_spinbutton" type="label-for"/> + <relation target="lock_spinbutton" type="flows-to"/> + </accessibility> + </object> + <packing> + <property name="left-attach">2</property> + <property name="top-attach">2</property> + </packing> + </child> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">1</property> + </packing> + </child> + <child> + <object class="GtkFrame" id="preview_frame"> + <property name="name">preview_frame</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="hexpand">True</property> + <property name="vexpand">True</property> + <accessibility> + <relation target="label1" type="labelled-by"/> + </accessibility> + <child> + <object class="GtkBox" id="preview_vbox"> + <property name="name">preview_vbox</property> + <property name="border-width">5</property> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">5</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkNotebook" id="preview_notebook"> + <property name="name">preview_notebook</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="show-tabs">False</property> + <property name="show-border">False</property> + <property name="tab-pos">GTK_POS_BOTTOM</property> + <property name="scrollable">False</property> + <property name="enable-popup">False</property> + + <child> + <object class="GtkAspectFrame" id="preview_aspectframe"> + <property name="name">preview_aspectframe</property> + <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.777777</property> + <property name="obey-child">False</property> + <child> + <object class="GtkDrawingArea" id="preview"> + <property name="name">preview</property> + <property name="visible">True</property> + </object> + </child> + </object> + <packing> + <property name="tab-expand">False</property> + <property name="tab-fill">False</property> + </packing> + </child> + <child type="tab"> + <object class="GtkLabel" id="preview_tab"> + <property name="name">preview_tab</property> + <property name="visible">True</property> + <property name="label" translatable="yes">Preview</property> + <property name="use-underline">False</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + </object> + <packing> + <property name="tab-expand">False</property> + <property name="tab-fill">False</property> + </packing> + </child> + + <child> + <object class="GtkLabel" id="no_preview_label"> + <property name="name">no_preview_label</property> + <property name="visible">True</property> + <property name="label" translatable="yes">No Preview +Available</property> + <property name="use-underline">False</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_CENTER</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + </object> + <packing> + <property name="tab-expand">False</property> + <property name="tab-fill">False</property> + </packing> + </child> + <child type="tab"> + <object class="GtkLabel" id="no_preview_tab"> + <property name="name">no_preview_tab</property> + <property name="visible">True</property> + <property name="label" translatable="yes">No Preview</property> + <property name="use-underline">False</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + </object> + <packing> + <property name="tab-expand">False</property> + <property name="tab-fill">False</property> + </packing> + </child> + + <child> + <object class="GtkLabel" id="not_installed_label"> + <property name="name">not_installed_label</property> + <property name="visible">True</property> + <property name="label" translatable="yes">Not +Installed</property> + <property name="use-underline">False</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_CENTER</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + </object> + <packing> + <property name="tab-expand">False</property> + <property name="tab-fill">False</property> + </packing> + </child> + <child type="tab"> + <object class="GtkLabel" id="not_installed_tab"> + <property name="name">not_installed_tab</property> + <property name="visible">True</property> + <property name="label" translatable="yes">Not Installed</property> + <property name="use-underline">False</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + </object> + <packing> + <property name="tab-expand">False</property> + <property name="tab-fill">False</property> + </packing> + </child> + + <child> + <object class="GtkLabel" id="nothing_label"> + <property name="name">nothing_label</property> + <property name="visible">True</property> + <property name="label" translatable="yes">Very few (or no) screen savers appear to be available. + +This probably means that the "xscreensaver-extras" and +"xscreensaver-gl-extras" packages are not installed.</property> + <property name="use-underline">False</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_CENTER</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + </object> + <packing> + <property name="tab-expand">False</property> + <property name="tab-fill">False</property> + </packing> + </child> + <child type="tab"> + <object class="GtkLabel" id="nothing_tab"> + <property name="name">nothing_tab</property> + <property name="visible">True</property> + <property name="label" translatable="yes">Nothing Installed</property> + <property name="use-underline">False</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + </object> + <packing> + <property name="tab-expand">False</property> + <property name="tab-fill">False</property> + </packing> + <packing> + <property name="tab-expand">False</property> + <property name="tab-fill">False</property> + </packing> + </child> + + <child> + <object class="GtkLabel" id="wayland_label"> + <property name="name">wayland_label</property> + <property name="visible">True</property> + <property name="label" translatable="yes">No Previews Available +Under Wayland.</property> + <property name="use-underline">False</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_CENTER</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + </object> + <packing> + <property name="tab-expand">False</property> + <property name="tab-fill">False</property> + </packing> + </child> + <child type="tab"> + <object class="GtkLabel" id="wayland_tab"> + <property name="name">wayland_tab</property> + <property name="visible">True</property> + <property name="label" translatable="yes">No Previews Under Wayland</property> + <property name="use-underline">False</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + </object> + <packing> + <property name="tab-expand">False</property> + <property name="tab-fill">False</property> + </packing> + <packing> + <property name="tab-expand">False</property> + <property name="tab-fill">False</property> + </packing> + </child> + + </object> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + + <child> + <object class="GtkLabel" id="short_preview_label"> + <property name="name">short_preview_label</property> + <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">True</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.0</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">72</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + <accessibility> + <relation target="preview_aspectframe" type="label-for"/> + </accessibility> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <object class="GtkLabel" id="preview_author_label"> + <property name="name">preview_author_label</property> + <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_CENTER</property> + <property name="wrap">True</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.0</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">72</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + <accessibility> + <relation target="preview_aspectframe" type="label-for"/> + </accessibility> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">True</property> + </packing> + </child> + </object> + </child> + <child type="label"> + <object class="GtkLabel" id="label1"> + <property name="name">label1</property> + <property name="visible">True</property> + <property name="label" translatable="yes">Description</property> + <property name="use-underline">False</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + <accessibility> + <relation target="preview_frame" type="label-for"/> + </accessibility> + </object> + </child> + </object> + <packing> + <property name="left-attach">1</property> + <property name="top-attach">0</property> + </packing> + </child> + <child> + <object class="GtkHButtonBox" id="demo_manual_hbbox"> + <property name="name">demo_manual_hbbox</property> + <property name="visible">True</property> + <property name="layout-style">GTK_BUTTONBOX_SPREAD</property> + <property name="spacing">30</property> + <child> + <object class="GtkButton" id="demo"> + <property name="name">demo</property> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Demo the selected screen saver in full-screen mode.</property> + <property name="can-default">True</property> + <property name="can-focus">True</property> + <property name="label" translatable="yes">_Preview</property> + <property name="use-underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus-on-click">True</property> + <signal handler="run_this_cb" name="clicked"/> + </object> + </child> + <child> + <object class="GtkButton" id="settings"> + <property name="name">settings</property> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Customization and explanation of the selected screen saver.</property> + <property name="can-default">True</property> + <property name="can-focus">True</property> + <property name="label" translatable="yes">_Settings...</property> + <property name="use-underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus-on-click">True</property> + <signal handler="settings_cb" name="clicked"/> + </object> + </child> + </object> + <packing> + <property name="left-attach">1</property> + <property name="top-attach">1</property> + </packing> + </child> + </object> + <packing> + <property name="tab-expand">False</property> + <property name="tab-fill">False</property> + </packing> + </child> + <child type="tab"> + <object class="GtkLabel" id="demo_tab"> + <property name="name">demo_tab</property> + <property name="visible">True</property> + <property name="label" translatable="yes">_Display Modes</property> + <property name="use-underline">True</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_CENTER</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="mnemonic-widget">notebook</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + </object> + <packing> + <property name="tab-expand">False</property> + <property name="tab-fill">False</property> + </packing> + </child> + + <!-- Advanced page --> + <child> + <object class="GtkGrid" id="options_table"> + <property name="name">options_table</property> + <property name="visible">True</property> + <property name="margin-start">8</property> + <property name="margin-end">8</property> + <property name="column-homogeneous">True</property> + <child> + <object class="GtkFrame" id="grab_frame"> + <property name="name">grab_frame</property> + <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> + <property name="hexpand">True</property> + <accessibility> + <relation target="label2" type="labelled-by"/> + </accessibility> + <child> + <object class="GtkBox" id="grab_vbox"> + <property name="name">grab_vbox</property> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="orientation">vertical</property> + <property name="spacing">4</property> + <property name="margin-start">12</property> + <property name="margin-end">12</property> + <property name="margin-top">12</property> + <property name="margin-bottom">12</property> + <child> + <object class="GtkCheckButton" id="grab_desk_button"> + <property name="name">grab_desk_button</property> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Whether the image-manipulating modes should be allowed to operate on an image of your desktop.</property> + <property name="can-focus">True</property> + <property name="label" translatable="yes">Grab Desktop _Images</property> + <property name="use-underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus-on-click">True</property> + <property name="active">False</property> + <property name="inconsistent">False</property> + <property name="draw-indicator">True</property> + <signal handler="pref_changed_cb" name="toggled"/> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="grab_video_button"> + <property name="name">grab_video_button</property> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Whether the image-manipulating modes should operate on images captured from the camera (if there is one).</property> + <property name="can-focus">True</property> + <property name="label" translatable="yes">Grab _Video Frames</property> + <property name="use-underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus-on-click">True</property> + <property name="active">False</property> + <property name="inconsistent">False</property> + <property name="draw-indicator">True</property> + <signal handler="pref_changed_cb" name="toggled"/> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="grab_image_button"> + <property name="name">grab_image_button</property> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Whether the image-manipulating modes should load image files.</property> + <property name="can-focus">True</property> + <property name="label" translatable="yes">Choose _Random Image:</property> + <property name="use-underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus-on-click">True</property> + <property name="active">False</property> + <property name="inconsistent">False</property> + <property name="draw-indicator">True</property> + <accessibility> + <relation target="image_text" type="controller-for"/> + <relation target="image_browse_button" type="controller-for"/> + </accessibility> + <signal handler="pref_changed_cb" name="toggled"/> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkBox" id="image_hbox"> + <property name="name">image_hbox</property> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + <property name="orientation">horizontal</property> + <child> + <object class="GtkLabel" id="grab_dummy"> + <property name="name">grab_dummy</property> + <property name="visible">True</property> + <property name="label" translatable="yes"/> + <property name="use-underline">False</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">8</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="image_text"> + <property name="name">image_text</property> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">The local directory, RSS feed or Atom feed from which images will be randomly chosen.</property> + <property name="can-focus">True</property> + <property name="editable">True</property> + <property name="visibility">True</property> + <property name="max-length">0</property> + <property name="text" translatable="yes"/> + <property name="has-frame">True</property> + <property name="invisible-char">*</property> + <property name="activates-default">False</property> + <accessibility> + <relation target="grab_image_button" type="labelled-by"/> + <relation target="grab_image_button" type="controlled-by"/> + </accessibility> + <signal handler="pref_changed_cb" name="activate"/> + <signal handler="image_text_pref_changed_event_cb" name="focus_out_event"/> + </object> + <packing> + <property name="padding">2</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + <child> + <object class="GtkButton" id="image_browse_button"> + <property name="name">image_browse_button</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="label" translatable="yes">_Browse</property> + <property name="use-underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus-on-click">True</property> + <signal handler="browse_image_dir_cb" name="clicked"/> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label8"> + <property name="name">label8</property> + <property name="visible">True</property> + <property name="label" translatable="yes">Local directory, or URL of RSS or Atom feed.</property> + <property name="use-underline">False</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">20</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </object> + </child> + <child type="label"> + <object class="GtkLabel" id="label2"> + <property name="name">label2</property> + <property name="visible">True</property> + <property name="label" translatable="yes">Image Manipulation</property> + <property name="use-underline">False</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + <accessibility> + <relation target="grab_frame" type="label-for"/> + </accessibility> + </object> + </child> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">0</property> + </packing> + </child> + <child> + <object class="GtkFrame" id="text_frame"> + <property name="name">text_frame</property> + <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> + <property name="expand">False</property> + <property name="hexpand">True</property> + <accessibility> + <relation target="label3" type="labelled-by"/> + </accessibility> + <child type="label"> + <object class="GtkLabel" id="label3"> + <property name="name">label3</property> + <property name="visible">True</property> + <property name="label" translatable="yes">Text Manipulation</property> + <property name="use-underline">False</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + <accessibility> + <relation target="text_frame" type="label-for"/> + </accessibility> + </object> + </child> + <child> + <object class="GtkGrid" id="text_table"> + <property name="name">text_table</property> + <property name="visible">True</property> + <property name="row-spacing">4</property> + <property name="column-spacing">4</property> + <property name="margin-start">12</property> + <property name="margin-end">12</property> + <property name="margin-top">12</property> + <property name="margin-bottom">12</property> + <child> + <object class="GtkRadioButton" id="text_host_radio"> + <property name="name">text_host_radio</property> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Text-displaying modes will display the local host name, date, and time.</property> + <property name="can-focus">True</property> + <property name="label" translatable="yes">_Host Name and Time</property> + <property name="use-underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus-on-click">True</property> + <property name="active">True</property> + <property name="inconsistent">False</property> + <property name="draw-indicator">True</property> + <property name="group">text_radio</property> + <signal handler="pref_changed_cb" name="toggled"/> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">0</property> + <property name="width">3</property> + </packing> + </child> + <child> + <object class="GtkRadioButton" id="text_radio"> + <property name="name">text_radio</property> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Text-displaying modes will display the text typed here.</property> + <property name="can-focus">True</property> + <property name="label" translatable="yes">_Text</property> + <property name="use-underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus-on-click">True</property> + <property name="active">False</property> + <property name="inconsistent">False</property> + <property name="draw-indicator">True</property> + <accessibility> + <relation target="text_entry" type="controller-for"/> + <relation target="text_entry" type="label-for"/> + </accessibility> + <signal handler="pref_changed_cb" name="toggled"/> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="text_entry"> + <property name="name">text_entry</property> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Text-displaying modes will display the text typed here.</property> + <property name="can-focus">True</property> + <property name="editable">True</property> + <property name="visibility">True</property> + <property name="max-length">0</property> + <property name="text" translatable="yes"/> + <property name="has-frame">True</property> + <property name="invisible-char">*</property> + <property name="activates-default">False</property> + <property name="expand">True</property> + <accessibility> + <relation target="text_program_radio" type="labelled-by"/> + <relation target="text_program_radio" type="controlled-by"/> + </accessibility> + <signal handler="pref_changed_cb" name="activate"/> + <signal handler="pref_changed_event_cb" name="focus_out_event"/> + </object> + <packing> + <property name="left-attach">1</property> + <property name="top-attach">1</property> + <property name="width">2</property> + </packing> + </child> + <child> + <object class="GtkRadioButton" id="text_file_radio"> + <property name="name">text_file_radio</property> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Text-displaying modes will display the contents of this file.</property> + <property name="can-focus">True</property> + <property name="label" translatable="yes">Text _file</property> + <property name="use-underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus-on-click">True</property> + <property name="active">False</property> + <property name="inconsistent">False</property> + <property name="draw-indicator">True</property> + <property name="group">text_radio</property> + <accessibility> + <relation target="text_file_entry" type="label-for"/> + <relation target="text_file_entry" type="controller-for"/> + </accessibility> + <signal handler="pref_changed_cb" name="toggled"/> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">2</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="text_file_entry"> + <property name="name">text_file_entry</property> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Text-displaying modes will display the contents of this file.</property> + <property name="can-focus">True</property> + <property name="editable">True</property> + <property name="visibility">True</property> + <property name="max-length">0</property> + <property name="text" translatable="yes"/> + <property name="has-frame">True</property> + <property name="invisible-char">*</property> + <property name="activates-default">False</property> + <property name="expand">True</property> + <accessibility> + <relation target="text_file_radio" type="labelled-by"/> + <relation target="text_file_radio" type="controlled-by"/> + </accessibility> + <signal handler="pref_changed_cb" name="activate"/> + <signal handler="pref_changed_event_cb" name="focus_out_event"/> + </object> + <packing> + <property name="left-attach">1</property> + <property name="top-attach">2</property> + </packing> + </child> + <child> + <object class="GtkButton" id="text_file_browse"> + <property name="name">text_file_browse</property> + <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> + <property name="expand">False</property> + <accessibility> + <relation target="text_file_radio" type="controlled-by"/> + </accessibility> + <signal handler="browse_text_file_cb" name="clicked"/> + </object> + <packing> + <property name="left-attach">2</property> + <property name="top-attach">2</property> + </packing> + </child> + <child> + <object class="GtkRadioButton" id="text_program_radio"> + <property name="name">text_program_radio</property> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Text-displaying modes will display the output of this program.</property> + <property name="can-focus">True</property> + <property name="label" translatable="yes">_Program</property> + <property name="use-underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus-on-click">True</property> + <property name="active">False</property> + <property name="inconsistent">False</property> + <property name="draw-indicator">True</property> + <property name="group">text_radio</property> + <accessibility> + <relation target="text_program_entry" type="label-for"/> + <relation target="text_program_entry" type="controller-for"/> + <relation target="text_program_browse" type="controller-for"/> + </accessibility> + <signal handler="pref_changed_cb" name="toggled"/> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">3</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="text_program_entry"> + <property name="name">text_program_entry</property> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Text-displaying modes will display the output of this program.</property> + <property name="can-focus">True</property> + <property name="editable">True</property> + <property name="visibility">True</property> + <property name="max-length">0</property> + <property name="text" translatable="yes"/> + <property name="has-frame">True</property> + <property name="invisible-char">*</property> + <property name="activates-default">False</property> + <property name="expand">True</property> + <accessibility> + <relation target="text_program_radio" type="labelled-by"/> + <relation target="text_program_radio" type="controlled-by"/> + </accessibility> + <signal handler="pref_changed_cb" name="activate"/> + <signal handler="pref_changed_event_cb" name="focus_out_event"/> + </object> + <packing> + <property name="left-attach">1</property> + <property name="top-attach">3</property> + </packing> + </child> + <child> + <object class="GtkButton" id="text_program_browse"> + <property name="name">text_program_browse</property> + <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> + <property name="expand">False</property> + <accessibility> + <relation target="text_program_radio" type="controller-for"/> + </accessibility> + <signal handler="browse_text_program_cb" name="clicked"/> + </object> + <packing> + <property name="left-attach">2</property> + <property name="top-attach">3</property> + </packing> + </child> + <child> + <object class="GtkRadioButton" id="text_url_radio"> + <property name="name">text_url_radio</property> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Text-displaying modes will display the contents of this URL (HTML or RSS).</property> + <property name="can-focus">True</property> + <property name="label" translatable="yes">_URL</property> + <property name="use-underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus-on-click">True</property> + <property name="active">False</property> + <property name="inconsistent">False</property> + <property name="draw-indicator">True</property> + <property name="group">text_radio</property> + <accessibility> + <relation target="text_url_entry" type="label-for"/> + <relation target="text_url_entry" type="controller-for"/> + </accessibility> + <signal handler="pref_changed_cb" name="toggled"/> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">4</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="text_url_entry"> + <property name="name">text_url_entry</property> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Text-displaying modes will display the contents of this URL (HTML or RSS).</property> + <property name="can-focus">True</property> + <property name="editable">True</property> + <property name="visibility">True</property> + <property name="max-length">0</property> + <property name="text" translatable="yes"/> + <property name="has-frame">True</property> + <property name="invisible-char">*</property> + <property name="activates-default">False</property> + <property name="expand">True</property> + <accessibility> + <relation target="text_url_radio" type="controlled-by"/> + </accessibility> + <signal handler="pref_changed_cb" name="activate"/> + <signal handler="pref_changed_event_cb" name="focus_out_event"/> + </object> + <packing> + <property name="left-attach">1</property> + <property name="top-attach">4</property> + <property name="width">2</property> + </packing> + </child> + </object> + </child> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">1</property> + </packing> + </child> + <child> + <object class="GtkFrame" id="dpms_frame"> + <property name="name">dpms_frame</property> + <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> + <property name="expand">False</property> + <child> + <object class="GtkBox" id="vbox6"> + <property name="name">vbox6</property> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">4</property> + <property name="margin-start">12</property> + <property name="margin-end">12</property> + <property name="margin-top">12</property> + <property name="margin-bottom">12</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkCheckButton" id="dpms_button"> + <property name="name">dpms_button</property> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Whether the monitor should be powered down after a while.</property> + <property name="can-focus">True</property> + <property name="label" translatable="yes">_Power Management Enabled</property> + <property name="use-underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus-on-click">True</property> + <property name="active">True</property> + <property name="inconsistent">False</property> + <property name="draw-indicator">True</property> + <accessibility> + <relation target="dpms_suspend_spinbutton" type="controller-for"/> + <relation target="dpms_standby_spinbutton" type="controller-for"/> + <relation target="dpms_off_spinbutton" type="controller-for"/> + </accessibility> + <signal handler="pref_changed_cb" name="toggled"/> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="dpms_table"> + <property name="name">dpms_table</property> + <property name="visible">True</property> + <property name="row-spacing">2</property> + <property name="column-spacing">4</property> + <child> + <object class="GtkLabel" id="dpms_standby_label"> + <property name="name">dpms_standby_label</property> + <property name="visible">True</property> + <property name="label" translatable="yes">Stand_by After</property> + <property name="use-underline">True</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">1</property> + <property name="yalign">0.5</property> + <property name="xpad">10</property> + <property name="ypad">0</property> + <property name="mnemonic-widget">dpms_standby_spinbutton</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + <accessibility> + <relation target="dpms_standby_spinbutton" type="label-for"/> + <relation target="dpms_standby_spinbutton" type="flows-to"/> + </accessibility> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="dpms_suspend_label"> + <property name="name">dpms_suspend_label</property> + <property name="visible">True</property> + <property name="label" translatable="yes">Sus_pend After</property> + <property name="use-underline">True</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">1</property> + <property name="yalign">0.5</property> + <property name="xpad">10</property> + <property name="ypad">0</property> + <property name="mnemonic-widget">dpms_suspend_spinbutton</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + <accessibility> + <relation target="dpms_suspend_spinbutton" type="label-for"/> + <relation target="dpms_suspend_spinbutton" type="flows-to"/> + </accessibility> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="dpms_off_label"> + <property name="name">dpms_off_label</property> + <property name="visible">True</property> + <property name="label" translatable="yes">_Off After</property> + <property name="use-underline">True</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">1</property> + <property name="yalign">0.5</property> + <property name="xpad">10</property> + <property name="ypad">0</property> + <property name="mnemonic-widget">dpms_off_spinbutton</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + <accessibility> + <relation target="dpms_off_spinbutton" type="label-for"/> + <relation target="dpms_off_spinbutton" type="flows-to"/> + </accessibility> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">2</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="dpms_standby_mlabel"> + <property name="name">dpms_standby_mlabel</property> + <property name="visible">True</property> + <property name="label" translatable="yes">minutes</property> + <property name="use-underline">False</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + <accessibility> + <relation target="dpms_standby_spinbutton" type="label-for"/> + <relation target="dpms_standby_spinbutton" type="flows-from"/> + </accessibility> + </object> + <packing> + <property name="left-attach">2</property> + <property name="top-attach">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="dpms_suspend_mlabel"> + <property name="name">dpms_suspend_mlabel</property> + <property name="visible">True</property> + <property name="label" translatable="yes">minutes</property> + <property name="use-underline">False</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + <accessibility> + <relation target="dpms_suspend_spinbutton" type="label-for"/> + <relation target="dpms_suspend_spinbutton" type="flows-from"/> + </accessibility> + </object> + <packing> + <property name="left-attach">2</property> + <property name="top-attach">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="dpms_off_mlabel"> + <property name="name">dpms_off_mlabel</property> + <property name="visible">True</property> + <property name="label" translatable="yes">minutes</property> + <property name="use-underline">False</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + <accessibility> + <relation target="dpms_off_spinbutton" type="label-for"/> + <relation target="dpms_off_spinbutton" type="flows-from"/> + </accessibility> + </object> + <packing> + <property name="left-attach">2</property> + <property name="top-attach">2</property> + </packing> + </child> + <child> + <object class="GtkSpinButton" id="dpms_off_spinbutton"> + <property name="name">dpms_off_spinbutton</property> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">How long until the monitor powers down.</property> + <property name="can-focus">True</property> + <property name="climb-rate">15</property> + <property name="digits">0</property> + <property name="numeric">True</property> + <property name="update-policy">GTK_UPDATE_ALWAYS</property> + <property name="snap-to-ticks">True</property> + <property name="wrap">False</property> + <property name="adjustment">adjustment4</property> + <accessibility> + <relation target="dpms_button" type="controlled-by"/> + <relation target="dpms_off_label" type="labelled-by"/> + <relation target="dpms_off_mlabel" type="labelled-by"/> + <relation target="dpms_off_label" type="flows-from"/> + <relation target="dpms_off_mlabel" type="flows-to"/> + </accessibility> + <signal handler="pref_changed_cb" name="activate"/> + <signal handler="pref_changed_event_cb" name="focus_out_event"/> + <signal handler="pref_changed_cb" name="value_changed"/> + <signal handler="dpms_sanity_cb" name="activate"/> + <signal handler="dpms_sanity_cb" name="value_changed"/> + <signal handler="dpms_sanity_event_cb" name="focus_out_event"/> + </object> + <packing> + <property name="left-attach">1</property> + <property name="top-attach">2</property> + </packing> + </child> + <child> + <object class="GtkSpinButton" id="dpms_suspend_spinbutton"> + <property name="name">dpms_suspend_spinbutton</property> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">How long until the monitor goes into power-saving mode.</property> + <property name="can-focus">True</property> + <property name="climb-rate">15</property> + <property name="digits">0</property> + <property name="numeric">True</property> + <property name="update-policy">GTK_UPDATE_ALWAYS</property> + <property name="snap-to-ticks">True</property> + <property name="wrap">False</property> + <property name="adjustment">adjustment5</property> + <accessibility> + <relation target="dpms_button" type="controlled-by"/> + <relation target="dpms_suspend_label" type="labelled-by"/> + <relation target="dpms_suspend_mlabel" type="labelled-by"/> + <relation target="dpms_suspend_label" type="flows-from"/> + <relation target="dpms_suspend_mlabel" type="flows-to"/> + </accessibility> + <signal handler="pref_changed_cb" name="activate"/> + <signal handler="pref_changed_event_cb" name="focus_out_event"/> + <signal handler="pref_changed_cb" name="value_changed"/> + <signal handler="dpms_sanity_cb" name="activate"/> + <signal handler="dpms_sanity_cb" name="value_changed"/> + <signal handler="dpms_sanity_event_cb" name="focus_out_event"/> + </object> + <packing> + <property name="left-attach">1</property> + <property name="top-attach">1</property> + </packing> + </child> + <child> + <object class="GtkSpinButton" id="dpms_standby_spinbutton"> + <property name="name">dpms_standby_spinbutton</property> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">How long until the monitor goes completely black.</property> + <property name="can-focus">True</property> + <property name="climb-rate">15</property> + <property name="digits">0</property> + <property name="numeric">True</property> + <property name="update-policy">GTK_UPDATE_ALWAYS</property> + <property name="snap-to-ticks">True</property> + <property name="wrap">False</property> + <property name="adjustment">adjustment6</property> + <accessibility> + <relation target="dpms_button" type="controlled-by"/> + <relation target="dpms_standby_label" type="labelled-by"/> + <relation target="dpms_standby_mlabel" type="labelled-by"/> + <relation target="dpms_standby_label" type="flows-from"/> + <relation target="dpms_standby_mlabel" type="flows-to"/> + </accessibility> + <signal handler="pref_changed_cb" name="activate"/> + <signal handler="pref_changed_event_cb" name="focus_out_event"/> + <signal handler="pref_changed_cb" name="value_changed"/> + <signal handler="dpms_sanity_cb" name="activate"/> + <signal handler="dpms_sanity_cb" name="value_changed"/> + <signal handler="dpms_sanity_event_cb" name="focus_out_event"/> + </object> + <packing> + <property name="left-attach">1</property> + <property name="top-attach">0</property> + </packing> + </child> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">True</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="dpms_quickoff_button"> + <property name="name">dpms_quickoff_button</property> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Whether the monitor should be powered off immediately in "Blank Screen Only" mode, regardless of the above power-management timeouts.</property> + <property name="can-focus">True</property> + <property name="label" translatable="yes">_Quick Power-off in Blank Only Mode</property> + <property name="use-underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus-on-click">True</property> + <property name="active">False</property> + <property name="inconsistent">False</property> + <property name="draw-indicator">True</property> + <signal handler="pref_changed_cb" name="toggled"/> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </object> + </child> + <child type="label"> + <object class="GtkLabel" id="label4"> + <property name="name">label4</property> + <property name="visible">True</property> + <property name="label" translatable="yes">Display Power Management</property> + <property name="use-underline">False</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + <accessibility> + <relation target="dpms_frame" type="label-for"/> + </accessibility> + </object> + </child> + </object> + <packing> + <property name="left-attach">1</property> + <property name="top-attach">0</property> + </packing> + </child> + <child> + <object class="GtkFrame" id="blanking_frame"> + <property name="name">blanking_frame</property> + <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> + <property name="expand">False</property> + <accessibility> + <relation target="label5" type="labelled-by"/> + </accessibility> + <child> + <object class="GtkBox" id="vbox7"> + <property name="name">vbox7</property> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="orientation">vertical</property> + <property name="spacing">4</property> + <property name="margin-start">12</property> + <property name="margin-end">12</property> + <property name="margin-top">12</property> + <property name="margin-bottom">12</property> + <child> + <object class="GtkCheckButton" id="fade_button"> + <property name="name">fade_button</property> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Whether the screen should slowly fade to black when the screen saver activates.</property> + <property name="can-focus">True</property> + <property name="label" translatable="yes">Fade to Black when _Blanking</property> + <property name="use-underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus-on-click">True</property> + <property name="active">False</property> + <property name="inconsistent">False</property> + <property name="draw-indicator">True</property> + <accessibility> + <relation target="fade_spinbutton" type="controller-for"/> + </accessibility> + <signal handler="pref_changed_cb" name="toggled"/> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="unfade_button"> + <property name="name">unfade_button</property> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Whether the screen should slowly fade in from black when the screen saver deactivates.</property> + <property name="can-focus">True</property> + <property name="label" translatable="yes">Fade from Black When _Unblanking</property> + <property name="use-underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus-on-click">True</property> + <property name="active">False</property> + <property name="inconsistent">False</property> + <property name="draw-indicator">True</property> + <accessibility> + <relation target="fade_spinbutton" type="controller-for"/> + </accessibility> + <signal handler="pref_changed_cb" name="toggled"/> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkBox" id="fade_hbox"> + <property name="name">fade_hbox</property> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + <property name="orientation">horizontal</property> + <child> + <object class="GtkLabel" id="fade_dummy"> + <property name="name">fade_dummy</property> + <property name="visible">True</property> + <property name="label" translatable="yes"/> + <property name="use-underline">False</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">3</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="fade_label"> + <property name="name">fade_label</property> + <property name="visible">True</property> + <property name="label" translatable="yes">F_ade Duration</property> + <property name="use-underline">True</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="mnemonic-widget">fade_spinbutton</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + <accessibility> + <relation target="fade_spinbutton" type="label-for"/> + <relation target="fade_spinbutton" type="flows-to"/> + </accessibility> + </object> + <packing> + <property name="padding">14</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkSpinButton" id="fade_spinbutton"> + <property name="name">fade_spinbutton</property> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">How long it should take for the screen to fade in and out.</property> + <property name="can-focus">True</property> + <property name="climb-rate">1</property> + <property name="digits">0</property> + <property name="numeric">True</property> + <property name="update-policy">GTK_UPDATE_ALWAYS</property> + <property name="snap-to-ticks">True</property> + <property name="wrap">False</property> + <property name="adjustment">adjustment7</property> + <accessibility> + <relation target="unfade_button" type="controlled-by"/> + <relation target="fade_button" type="controlled-by"/> + <relation target="fade_label" type="labelled-by"/> + <relation target="fade_sec_label" type="labelled-by"/> + <relation target="fade_label" type="flows-from"/> + <relation target="fade_sec_label" type="flows-to"/> + </accessibility> + <signal handler="pref_changed_cb" name="activate"/> + <signal handler="pref_changed_event_cb" name="focus_out_event"/> + <signal handler="pref_changed_cb" name="value_changed"/> + </object> + <packing> + <property name="padding">4</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="fade_sec_label"> + <property name="name">fade_sec_label</property> + <property name="visible">True</property> + <property name="label" translatable="yes">seconds</property> + <property name="use-underline">False</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + <accessibility> + <relation target="fade_spinbutton" type="label-for"/> + <relation target="fade_spinbutton" type="flows-from"/> + </accessibility> + </object> + <packing> + <property name="padding">2</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkHSeparator" id="blanking_hr"> + <property name="name">blanking_hr</property> + <property name="visible">True</property> + </object> + <packing> + <property name="padding">8</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkBox" id="theme_hbox"> + <property name="name">theme_hbox</property> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + <property name="orientation">horizontal</property> + <child> + <object class="GtkLabel" id="theme_label"> + <property name="name">theme_label</property> + <property name="visible">True</property> + <property name="label" translatable="yes">_Theme:</property> + <property name="tooltip-text" translatable="yes">The color scheme to use on the unlock dialog.</property> + <property name="use-underline">True</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="mnemonic-widget">theme_menu</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + <accessibility> + <relation target="theme_menu" type="label-for"/> + </accessibility> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkComboBox" id="theme_menu"> + <property name="name">theme_menu</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="has-entry">False</property> + <property name="model">theme_menu_model</property> + <accessibility> + <relation target="theme_label" type="labelled-by"/> + </accessibility> + <child> + <object class="GtkCellRendererText" id="renderer2"/> + <attributes> + <attribute name="text">0</attribute> + </attributes> + </child> + </object> + <packing> + <property name="padding">4</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkButton" id="theme_preview"> + <property name="name">theme_preview</property> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Show the what the unlock dialog will look like.</property> + <property name="can-default">True</property> + <property name="can-focus">True</property> + <property name="label" translatable="yes">_Preview</property> + <property name="use-underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus-on-click">True</property> + <signal handler="preview_theme_cb" name="clicked"/> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </object> + <packing> + <property name="padding">10</property> + <property name="expand">False</property> + <property name="fill">True</property> + </packing> + </child> + </object> + </child> + <child type="label"> + <object class="GtkLabel" id="label5"> + <property name="name">label5</property> + <property name="visible">True</property> + <property name="label" translatable="yes">Blanking</property> + <property name="use-underline">False</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + <accessibility> + <relation target="blanking_frame" type="label-for"/> + </accessibility> + </object> + </child> + </object> + <packing> + <property name="left-attach">1</property> + <property name="top-attach">1</property> + </packing> + </child> + </object> + <packing> + <property name="tab-expand">False</property> + <property name="tab-fill">False</property> + </packing> + </child> + <child type="tab"> + <object class="GtkLabel" id="options_tab"> + <property name="name">options_tab</property> + <property name="visible">True</property> + <property name="label" translatable="yes">_Advanced</property> + <property name="use-underline">True</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="mnemonic-widget">notebook</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + </object> + <packing> + <property name="tab-expand">False</property> + <property name="tab-fill">False</property> + </packing> + </child> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + <child> + <object class="GtkHButtonBox" id="hbuttonbox2"> + <property name="name">hbuttonbox2</property> + <property name="border-width">5</property> + <property name="layout-style">GTK_BUTTONBOX_EDGE</property> + <property name="spacing">10</property> + <child> + <object class="GtkButton" id="helpbutton"> + <property name="name">helpbutton</property> + <property name="visible">True</property> + <property name="can-default">True</property> + <property name="can-focus">True</property> + <property name="label">gtk-help</property> + <property name="use-stock">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus-on-click">True</property> + <signal handler="doc_menu_cb" name="clicked"/> + </object> + </child> + <child> + <object class="GtkButton" id="closebutton"> + <property name="name">closebutton</property> + <property name="visible">True</property> + <property name="can-default">True</property> + <property name="can-focus">True</property> + <property name="label">gtk-close</property> + <property name="use-stock">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus-on-click">True</property> + <signal handler="quit_menu_cb" name="clicked"/> + </object> + </child> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack-type">GTK_PACK_END</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </object> + </child> + </template> +</interface> diff --git a/driver/dialog.c b/driver/dialog.c index fce74c4..417f862 100644 --- a/driver/dialog.c +++ b/driver/dialog.c @@ -1,5 +1,5 @@ /* dialog.c --- the password dialog and splash screen. - * xscreensaver, Copyright © 1993-2021 Jamie Zawinski <jwz@jwz.org> + * xscreensaver, Copyright © 1993-2023 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 @@ -47,6 +47,10 @@ #include <ctype.h> #include <pwd.h> +#ifndef HAVE_XINPUT +# error The XInput2 extension is required +#endif + #include <X11/Xproto.h> /* for CARD32 */ #include <X11/Xlib.h> #include <X11/Xatom.h> @@ -79,6 +83,10 @@ #include "font-retry.h" #include "prefs.h" #include "usleep.h" +#include "utf8wc.h" + +#undef countof +#define countof(x) (sizeof((x))/sizeof((*x))) extern Bool debug_p; @@ -125,7 +133,7 @@ typedef struct { typedef struct { char *text; XftFont *font; - XftColor fg; + XftColor fg, fg2; Pixel bg; enum { LABEL, BUTTON, TEXT, TEXT_RO } type; line_align align; @@ -146,7 +154,7 @@ struct window_state { Window window; Colormap cmap; - Bool splash_p; + int splash_p; auth_state auth_state; int xi_opcode; int xkb_opcode; @@ -177,9 +185,11 @@ struct window_state { XtIntervalId timer; XtIntervalId cursor_timer; /* Blink the I-beam */ + XtIntervalId bs_timer; /* Auto-repeat Backspace */ int i_beam; double start_time, end_time; + int passwd_timeout; Bool show_stars_p; /* "I regret that I have but one asterisk for my country." -- Nathan Hale, 1776. */ @@ -192,6 +202,7 @@ struct window_state { char *date_format; char *kbd_layout_label; char *newlogin_cmd; + const char *asterisk_utf8; /* Resources for fonts and colors */ XftDraw *xftdraw; @@ -208,6 +219,7 @@ struct window_state { XftColor xft_foreground; XftColor xft_text_foreground; XftColor xft_button_foreground; + XftColor xft_button_disabled; XftColor xft_error_foreground; Pixel passwd_background; Pixel thermo_foreground; @@ -483,10 +495,13 @@ draw_dialog_line (window_state *ws, Drawable d, dialog_line *line, if (line->type == BUTTON && line->button && - (line->button->down_p || line->button->disabled_p)) + line->button->down_p) xoff2 = yoff2 = MIN (ws->shadow_width, line->font->ascent/2); - XftDrawStringUtf8_multi (ws->xftdraw, &line->fg, line->font, + XftDrawStringUtf8_multi (ws->xftdraw, + (line->button && line->button->disabled_p + ? &line->fg2 : &line->fg), + line->font, left + xoff2, y + ypad + yoff2 + line->font->ascent, (FcChar8 *) text, strlen (text), @@ -666,6 +681,31 @@ get_xft_color (window_state *ws, XftColor *ret, s, ret); } +static void +dim_xft_color (window_state *ws, const XftColor *in, Pixel bg, XftColor *out) +{ + double dim = 0.6; +# if 0 /* Turns out Xft alpha doesn't work. How very. */ + XRenderColor rc = in->color; + rc.alpha *= dim; +# else + XRenderColor rc; + XColor xc; + xc.pixel = bg; + XQueryColor (ws->dpy, DefaultColormapOfScreen (ws->screen), &xc); + rc.red = dim * in->color.red + (1-dim) * xc.red; + rc.green = dim * in->color.green + (1-dim) * xc.green; + rc.blue = dim * in->color.blue + (1-dim) * xc.blue; + rc.alpha = in->color.alpha; +# endif + if (! XftColorAllocValue (ws->dpy, + DefaultVisualOfScreen(ws->screen), + DefaultColormapOfScreen (ws->screen), + &rc, out)) + abort(); +} + + static int get_int (window_state *ws, const char *name, const char *rclass) { @@ -674,32 +714,74 @@ get_int (window_state *ws, const char *name, const char *rclass) } +static const char * +choose_asterisk (window_state *ws) +{ + static char picked[8]; + const unsigned long candidates[] = { 0x25CF, /* Black Circle */ + 0x2022, /* Bullet */ + 0x2731, /* Heavy Asterisk */ + '*' }; /* Ἀστερίσκος */ + const unsigned long *uc = candidates; + int i, L; + for (i = 0; i < countof (candidates) - 1; i++) + { +# ifdef HAVE_XFT + if (XftCharExists (ws->dpy, ws->label_font, (FcChar32) *uc)) + break; + if (debug_p) + fprintf (stderr, "%s: char U+%0lX does not exist\n", blurb(), *uc); +# endif + uc++; + } + + L = utf8_encode (*uc, picked, sizeof (picked) - 1); + picked[L] = 0; + + return picked; +} + + /* Decide where on the X11 screen to place the dialog. This is complicated because, in the face of RANDR and Xinerama, we want to center it on a *monitor*, not on what X calls a 'Screen'. So get the monitor state, then figure out which one of those the mouse is in. */ static void -splash_pick_window_position (Display *dpy, Position *xP, Position *yP) +splash_pick_window_position (Display *dpy, Position *xP, Position *yP, + Screen **screenP) { - Window pointer_root, pointer_child; - int root_x = 0, root_y = 0, win_x, win_y; - unsigned int mask; + Screen *mouse_screen = DefaultScreenOfDisplay (dpy); + int root_x = 0, root_y = 0; + int nscreens = ScreenCount (dpy); + int i, screen; monitor **monitors; monitor *m = 0; - int i; - XQueryPointer (dpy, RootWindow (dpy, 0), - &pointer_root, &pointer_child, - &root_x, &root_y, &win_x, &win_y, &mask); + /* Find the mouse screen, and position on it. */ + for (screen = 0; screen < nscreens; screen++) + { + Window pointer_root, pointer_child; + int win_x, win_y; + unsigned int mask; + int status = XQueryPointer (dpy, RootWindow (dpy, screen), + &pointer_root, &pointer_child, + &root_x, &root_y, &win_x, &win_y, &mask); + if (status != None) + { + mouse_screen = ScreenOfDisplay (dpy, screen); + break; + } + } - monitors = scan_monitors (dpy); + monitors = scan_monitors (dpy); /* This scans all Screens */ if (!monitors || !*monitors) abort(); for (i = 0; monitors[i]; i++) { monitor *m0 = monitors[i]; if (m0->sanity == S_SANE && + mouse_screen == m0->screen && root_x >= m0->x && root_y >= m0->y && root_x < m0->x + m0->width && @@ -725,6 +807,7 @@ splash_pick_window_position (Display *dpy, Position *xP, Position *yP) *xP = m->x + m->width/2; *yP = m->y + m->height/2; + *screenP = mouse_screen; free_monitors (monitors); } @@ -733,43 +816,44 @@ splash_pick_window_position (Display *dpy, Position *xP, Position *yP) static void unlock_cb (window_state *ws); -/* This program only needs one option from the init file, so it - just reads the .ad file and the .xscreensaver file directly rather - than going through Xt and Xrm. +/* This program only needs a few options from .xscreensaver. + Read that file directly and store those into the Xrm database. */ -static void init_line_handler (int lineno, +static void init_line_handler (int lineno, const char *key, const char *val, void *closure) { window_state *ws = (window_state *) closure; - if (val && *val && !strcmp (key, "dialogTheme")) + if (!val || !*val) + ; + else if (!strcmp (key, "dialogTheme") || + !strcmp (key, "passwdTimeout")) { - if (ws->dialog_theme) free (ws->dialog_theme); - ws->dialog_theme = strdup (val); + XrmDatabase db = XtDatabase (ws->dpy); + char *key2 = (char *) malloc (strlen (progname) + strlen (val) + 10); + sprintf (key2, "%s.%s", progname, key); + XrmPutStringResource (&db, key2, val); + free (key2); } + /* We read additional resources, such as "PROGCLASS.THEME.Dialog.foreground", + but those are from the .ad file only, not from .xscreensaver, so they + don't need a clause here in the file parser. They have already been + read into the DB by Xt's Xrm initialization. */ } + static void read_init_file_simple (window_state *ws) { const char *home = getenv("HOME"); - const char *fn1 = AD_DIR "/XScreenSaver"; - char *fn2; + char *fn; if (!home || !*home) return; - fn2 = (char *) malloc (strlen(home) + 40); - sprintf (fn2, "%s/.xscreensaver", home); - + fn = (char *) malloc (strlen(home) + 40); + sprintf (fn, "%s/.xscreensaver", home); if (debug_p) - fprintf (stderr, "%s: reading %s\n", blurb(), fn1); - parse_init_file (fn1, init_line_handler, ws); - - if (debug_p) - fprintf (stderr, "%s: reading %s\n", blurb(), fn2); - parse_init_file (fn2, init_line_handler, ws); - - if (verbose_p) - fprintf (stderr, "%s: theme: %s\n", blurb(), - (ws->dialog_theme ? ws->dialog_theme : "none")); + fprintf (stderr, "%s: reading %s\n", blurb(), fn); + parse_init_file (fn, init_line_handler, ws); + free (fn); } @@ -907,10 +991,9 @@ create_window (window_state *ws, int w, int h) /* Loads resources and creates and returns the global window state. */ static window_state * -window_init (Widget root_widget, Bool splash_p) +window_init (Widget root_widget, int splash_p) { Display *dpy = XtDisplay (root_widget); - Screen *screen = XtScreen (root_widget); window_state *ws; Bool resource_error_p = False; @@ -919,17 +1002,14 @@ window_init (Widget root_widget, Bool splash_p) ws->splash_p = splash_p; ws->dpy = dpy; - ws->screen = screen; ws->app = XtWidgetToApplicationContext (root_widget); - /* Read default theme from resources before the init file. */ - ws->dialog_theme = - get_string_resource (ws->dpy, "dialogTheme", "DialogTheme"); - if (!ws->dialog_theme || !*ws->dialog_theme) - ws->dialog_theme = strdup ("default"); + splash_pick_window_position (ws->dpy, &ws->cx, &ws->cy, &ws->screen); - /* Read theme from init file before any other resources. */ - read_init_file_simple (ws); + ws->cmap = XCreateColormap (dpy, + RootWindowOfScreen (ws->screen), /* Old skool */ + DefaultVisualOfScreen (ws->screen), + AllocNone); { struct passwd *p = getpwuid (getuid()); @@ -937,15 +1017,25 @@ window_init (Widget root_widget, Bool splash_p) ws->user = p->pw_name; } - ws->cmap = XCreateColormap (dpy, RootWindowOfScreen (screen), /* Old skool */ - DefaultVisualOfScreen (screen), - AllocNone); + /* Read resources and .xscreensaver file settings. + */ + read_init_file_simple (ws); + + ws->dialog_theme = /* must be first */ + get_string_resource (ws->dpy, "dialogTheme", "DialogTheme"); + if (!ws->dialog_theme || !*ws->dialog_theme) + ws->dialog_theme = strdup ("default"); + if (verbose_p) + fprintf (stderr, "%s: theme: %s\n", blurb(), ws->dialog_theme); ws->newlogin_cmd = get_str (ws, "newLoginCommand", "NewLoginCommand"); ws->date_format = get_str (ws, "dateFormat", "DateFormat"); ws->show_stars_p = get_boolean_resource (ws->dpy, "passwd.asterisks", "Passwd.Boolean"); + ws->passwd_timeout = get_seconds_resource (ws->dpy, "passwdTimeout", "Time"); + if (ws->passwd_timeout <= 5) ws->passwd_timeout = 5; + /* Put the version number in the label. */ { char *version = strdup (screensaver_id + 17); @@ -975,6 +1065,8 @@ window_init (Widget root_widget, Bool splash_p) ws->label_font = get_font (ws, "labelFont"); ws->date_font = get_font (ws, "dateFont"); ws->hostname_font = get_font (ws, "unameFont"); + + ws->asterisk_utf8 = choose_asterisk (ws); ws->foreground = get_color (ws, "foreground", "Foreground"); ws->background = get_color (ws, "background", "Background"); @@ -986,6 +1078,8 @@ window_init (Widget root_widget, Bool splash_p) "error.foreground", "Error.Foreground"); get_xft_color (ws, &ws->xft_button_foreground, "button.foreground", "Button.Foreground"); + dim_xft_color (ws, &ws->xft_button_foreground, ws->background, + &ws->xft_button_disabled); ws->shadow_top = get_color (ws, "topShadowColor", "Foreground"); ws->shadow_bottom = get_color (ws, "bottomShadowColor", "Background"); @@ -1002,8 +1096,8 @@ window_init (Widget root_widget, Bool splash_p) if (resource_error_p) { /* Make sure the error messages show up. */ - ws->foreground = BlackPixelOfScreen (screen); - ws->background = WhitePixelOfScreen (screen); + ws->foreground = BlackPixelOfScreen (ws->screen); + ws->background = WhitePixelOfScreen (ws->screen); } ws->preferred_logo_width = get_int (ws, "logo.width", "Logo.Width"); @@ -1040,7 +1134,7 @@ window_init (Widget root_widget, Bool splash_p) Window root = RootWindowOfScreen(ws->screen); Visual *visual = DefaultVisualOfScreen (ws->screen); int logo_size = (ws->heading_font->ascent > 24 ? 2 : 1); - ws->logo_pixmap = xscreensaver_logo (screen, visual, root, ws->cmap, + ws->logo_pixmap = xscreensaver_logo (ws->screen, visual, root, ws->cmap, ws->background, &ws->logo_pixels, &ws->logo_npixels, &ws->logo_clipmask, logo_size); @@ -1049,8 +1143,6 @@ window_init (Widget root_widget, Bool splash_p) &ws->logo_width, &ws->logo_height, &bw, &d); } - splash_pick_window_position (ws->dpy, &ws->cx, &ws->cy); - ws->x = ws->y = 0; create_window (ws, 1, 1); @@ -1212,7 +1304,7 @@ static void window_draw (window_state *ws) { Display *dpy = ws->dpy; - Screen *screen = DefaultScreenOfDisplay (dpy); + Screen *screen = ws->screen; Window root = RootWindowOfScreen (screen); Visual *visual = DefaultVisualOfScreen(screen); int depth = DefaultDepthOfScreen (screen); @@ -1234,8 +1326,6 @@ window_draw (window_state *ws) char date_text[100]; time_t now = time ((time_t *) 0); struct tm *tm = localtime (&now); - double ratio = 1 - ((double_time() - ws->start_time) / - (ws->end_time - ws->start_time)); dialog_line *lines = (dialog_line *) calloc (ws->nmsgs + 40, sizeof(*lines)); Bool emitted_user_p = False; @@ -1276,27 +1366,111 @@ window_draw (window_state *ws) lines[i].text = ws->heading_label; /* XScreenSaver */ lines[i].font = ws->heading_font; lines[i].fg = ws->xft_foreground; + lines[i].fg2 = lines[i].fg; lines[i].bg = ws->background; lines[i].type = LABEL; lines[i].align = CENTER; i++; + /* If you are in here because you're planning on disabling this notice + before redistributing my software, please don't. + + I sincerely request that you do one of the following: + + 1: leave this code intact and this warning in place, -OR- + + 2: Remove xscreensaver from your distribution. + + I would seriously prefer that you not distribute my software at all + than that you distribute one version and then never update it for + years. + + I am *constantly* getting email from users reporting bugs that have + been fixed for literally years who have no idea that the software + they are running is years out of date. Yes, it would be great if we + lived in the ideal world where people checked that they were running + the latest release before they report a bug, but we don't. To most + people, "running the latest release" is synonymous with "running the + latest release that my distro packages for me." + + When they even bother to tell me what version they're running, I + say, "That version is three years old!", and they say "But this is + the latest version my distro ships". Then I say, "your distro + sucks", and they say "but I don't know how to compile from source, + herp derp I eat paste", and *everybody* goes away unhappy. + + It wastes an enormous amount of my time, but worse than that, it + does a grave disservice to the users, who are stuck experiencing + bugs that are already fixed! These users think they are running the + latest release, and they are not. They would like to be running the + actual latest release, but they don't know how, because their distro + makes that very difficult for them. It's terrible for everyone, and + kind of makes me regret ever having released this software in the + first place. + + So seriously. I ask that if you're planning on disabling this + obsolescence warning, that you instead just remove xscreensaver from + your distro entirely. Everybody will be happier that way. Check + out gnome-screensaver instead, I understand it's really nice. + + Of course, my license allows you to ignore me and do whatever the + fuck you want, but as the author, I hope you will have the common + courtesy of complying with my request. + + Thank you! + + jwz, 2014, 2016, 2018, 2021. + + PS: In particular, since Debian refuses to upgrade software on any + kind of rational timeline, I have asked that they stop shipping + xscreensaver at all. They have refused. Instead of upgrading the + software, they simply patched out this warning. + + If you want to witness the sad state of the open source peanut + gallery, look no farther than the comments on my blog: + http://jwz.org/b/yiYo + + Many of these people fall back on their go-to argument of, "If it is + legal, it must be right." If you believe in that rhetorical device + then you are a terrible person, and possibly a sociopath. + + There are also the armchair lawyers who say "Well, instead of + *asking* people to do the right thing out of common courtesy, you + should just change your license to prohibit them from acting + amorally." Again, this is the answer of a sociopath, but that aside, + if you devote even a second's thought to this you will realize that + the end result of this would be for distros like Debian to just keep + shipping the last version with the old license and then never + upgrading it again -- which would be the worst possible outcome for + everyone involved, most especially the users. + + Also, some have incorrectly characterized this as a "time bomb". + It is a software update notification, nothing more. A "time bomb" + makes software stop working. This merely alerts the user that the + security-critical software that they are running is dangerously out + of date. + + If you have read all of the above, and still decide to intentionally + disrespect the wishes of the person who wrote all of this software for + you -- you are a terrible person. Kindly go fuck yourself. + */ if (time ((time_t *) 0) - XSCREENSAVER_RELEASED > 60*60*24*30*17) { lines[i].text = _("Update available!\nThis version is very old.\n"); lines[i].font = ws->error_font; lines[i].fg = ws->xft_error_foreground; + lines[i].fg2 = lines[i].fg; lines[i].bg = ws->background; lines[i].type = LABEL; lines[i].align = CENTER; i++; } - else if (strstr (ws->version, "a") || - strstr (ws->version, "b")) + else if (strstr (ws->version, "a") || strstr (ws->version, "b")) { lines[i].text = _("PRE-RELEASE VERSION"); lines[i].font = ws->error_font; lines[i].fg = ws->xft_error_foreground; + lines[i].fg2 = lines[i].fg; lines[i].bg = ws->background; lines[i].type = LABEL; lines[i].align = CENTER; @@ -1308,6 +1482,7 @@ window_draw (window_state *ws) lines[i].text = ws->hostname_label; lines[i].font = ws->hostname_font; lines[i].fg = ws->xft_foreground; + lines[i].fg2 = lines[i].fg; lines[i].bg = ws->background; lines[i].type = LABEL; lines[i].align = CENTER; @@ -1318,6 +1493,7 @@ window_draw (window_state *ws) lines[i].text = ""; \ lines[i].font = ws->body_font; \ lines[i].fg = ws->xft_foreground; \ + lines[i].fg2 = lines[i].fg; \ lines[i].bg = ws->background; \ lines[i].type = LABEL; \ lines[i].align = CENTER; \ @@ -1331,6 +1507,7 @@ window_draw (window_state *ws) _("DEBUG MODE:\nAll keystrokes are being logged to stderr.\n"); lines[i].font = ws->error_font; lines[i].fg = ws->xft_error_foreground; + lines[i].fg2 = lines[i].fg; lines[i].bg = ws->background; lines[i].type = LABEL; lines[i].align = CENTER; @@ -1342,6 +1519,7 @@ window_draw (window_state *ws) lines[i].text = ws->body_label; /* Copyright or error message */ lines[i].font = ws->body_font; lines[i].fg = ws->xft_foreground; + lines[i].fg2 = lines[i].fg; lines[i].bg = ws->background; lines[i].type = LABEL; lines[i].align = CENTER; @@ -1362,6 +1540,7 @@ window_draw (window_state *ws) lines[i].fg = (ws->msgs[j].type == AUTH_MSGTYPE_ERROR ? ws->xft_error_foreground : ws->xft_foreground); + lines[i].fg2 = lines[i].fg; lines[i].bg = ws->background; lines[i].type = LABEL; lines[i].align = CENTER; @@ -1377,6 +1556,7 @@ window_draw (window_state *ws) lines[i].text = _("Username:"); lines[i].font = ws->label_font; lines[i].fg = ws->xft_foreground; + lines[i].fg2 = lines[i].fg; lines[i].bg = ws->background; lines[i].type = LABEL; lines[i].align = LEFT; @@ -1386,6 +1566,7 @@ window_draw (window_state *ws) lines[i].text = ws->user; /* $USER */ lines[i].font = ws->label_font; lines[i].fg = ws->xft_text_foreground; + lines[i].fg2 = lines[i].fg; lines[i].bg = ws->passwd_background; lines[i].type = TEXT_RO; lines[i].align = RIGHT; @@ -1395,6 +1576,7 @@ window_draw (window_state *ws) lines[i].text = trim (ws->msgs[j].msg); /* PAM prompt text */ lines[i].font = ws->label_font; lines[i].fg = ws->xft_foreground; + lines[i].fg2 = lines[i].fg; lines[i].bg = ws->background; lines[i].type = LABEL; lines[i].align = LEFT; @@ -1410,6 +1592,7 @@ window_draw (window_state *ws) : ""); lines[i].font = ws->label_font; lines[i].fg = ws->xft_text_foreground; + lines[i].fg2 = lines[i].fg; lines[i].bg = ws->passwd_background; lines[i].type = TEXT; lines[i].align = RIGHT; @@ -1422,6 +1605,7 @@ window_draw (window_state *ws) lines[i].text = date_text; lines[i].font = ws->date_font; lines[i].fg = ws->xft_foreground; + lines[i].fg2 = lines[i].fg; lines[i].bg = ws->background; lines[i].type = LABEL; lines[i].align = RIGHT; @@ -1434,6 +1618,7 @@ window_draw (window_state *ws) lines[i].text = ws->kbd_layout_label; lines[i].font = ws->date_font; lines[i].fg = ws->xft_foreground; + lines[i].fg2 = lines[i].fg; lines[i].bg = ws->background; lines[i].type = LABEL; lines[i].align = RIGHT; @@ -1482,6 +1667,7 @@ window_draw (window_state *ws) lines[i].text = _("Settings"); lines[i].font = ws->button_font; lines[i].fg = ws->xft_button_foreground; + lines[i].fg2 = ws->xft_button_disabled; lines[i].bg = ws->button_background; lines[i].type = BUTTON; lines[i].align = LEFT; @@ -1489,9 +1675,14 @@ window_draw (window_state *ws) lines[i].button = &ws->demo_button_state; i++; + if (ws->splash_p > 1) + /* Settings button is disabled with --splash --splash */ + ws->demo_button_state.disabled_p = True; + lines[i].text = _("Help"); lines[i].font = ws->button_font; lines[i].fg = ws->xft_button_foreground; + lines[i].fg2 = ws->xft_button_disabled; lines[i].bg = ws->button_background; lines[i].type = BUTTON; lines[i].align = RIGHT; @@ -1505,6 +1696,7 @@ window_draw (window_state *ws) lines[i].text = _("New Login"); lines[i].font = ws->button_font; lines[i].fg = ws->xft_button_foreground; + lines[i].fg2 = ws->xft_button_disabled; lines[i].bg = ws->button_background; lines[i].type = BUTTON; lines[i].align = LEFT; @@ -1516,6 +1708,7 @@ window_draw (window_state *ws) lines[i].text = _("OK"); lines[i].font = ws->button_font; lines[i].fg = ws->xft_button_foreground; + lines[i].fg2 = ws->xft_button_disabled; lines[i].bg = ws->button_background; lines[i].type = BUTTON; lines[i].align = RIGHT; @@ -1536,10 +1729,12 @@ window_draw (window_state *ws) { if (ws->auth_state != AUTH_NOTIFY) { + double remain = ws->end_time - double_time(); + double ratio = remain / ws->passwd_timeout; int thermo_w = ws->thermo_width; int thermo_h = window_height - ext_border * 2; int thermo_h2 = thermo_h - ws->shadow_width * 2; - int thermo_h3 = thermo_h2 * (1.0 - ratio); + int thermo_h3 = thermo_h2 * (1.0 - (ratio > 1 ? 1 : ratio)); XSetForeground (dpy, gc, ws->thermo_foreground); XFillRectangle (dpy, dbuf, gc, @@ -1718,6 +1913,11 @@ destroy_window (window_state *ws) XtRemoveTimeOut (ws->cursor_timer); ws->cursor_timer = 0; } + if (ws->bs_timer) + { + XtRemoveTimeOut (ws->bs_timer); + ws->bs_timer = 0; + } while (XCheckMaskEvent (ws->dpy, PointerMotionMask, &event)) if (verbose_p) @@ -1753,7 +1953,7 @@ destroy_window (window_state *ws) XftColorFree (ws->dpy, DefaultVisualOfScreen (ws->screen), DefaultColormapOfScreen (ws->screen), &ws->xft_error_foreground); - XftDrawDestroy (ws->xftdraw); + if (ws->xftdraw) XftDrawDestroy (ws->xftdraw); # if 0 /* screw this, we're exiting anyway */ if (ws->foreground != black && ws->foreground != white) @@ -1878,6 +2078,7 @@ persistent_auth_status_failure (window_state *ws, } +static void bs_timer (XtPointer, XtIntervalId *); static void handle_keypress (window_state *ws, XKeyEvent *event) @@ -1914,15 +2115,13 @@ handle_keypress (window_state *ws, XKeyEvent *event) /* Add 10% to the time remaining every time a key is pressed, but don't go past the max duration. */ { - time_t now = time ((time_t *) 0); - int max = get_seconds_resource (ws->dpy, "passwdTimeout", "Time"); - int remain = ws->end_time - now; + double now = double_time(); + double remain = ws->end_time - now; remain *= 1.1; - if (remain > max) remain = max; + if (remain > ws->passwd_timeout) remain = ws->passwd_timeout; if (remain < 3) remain = 3; ws->end_time = now + remain; } - if (decoded_size == 1) /* Handle single-char commands */ { @@ -1945,6 +2144,15 @@ handle_keypress (window_state *ws, XKeyEvent *event) } ws->plaintext_passwd_char_size[nchars-1] = 0; } + + /* The XInput2 extension does not send auto-repeat KeyPress + events, and it annoys people that you can't hold down the + Backspace key to clear the line. So clear the whole line + if the key is held down for a little while. */ + if (ws->bs_timer) + XtRemoveTimeOut (ws->bs_timer); + ws->bs_timer = + XtAppAddTimeOut (ws->app, 1000 * 0.6, bs_timer, (XtPointer) ws); } break; @@ -1956,7 +2164,7 @@ handle_keypress (window_state *ws, XKeyEvent *event) ws->auth_state = AUTH_CANCEL; break; - case '\025': case '\030': /* Erase line */ + case '\025': case '\030': /* Erase line ^U ^X */ memset (ws->plaintext_passwd, 0, sizeof (ws->plaintext_passwd)); memset (ws->plaintext_passwd_char_size, 0, sizeof (ws->plaintext_passwd_char_size)); @@ -1997,9 +2205,7 @@ handle_keypress (window_state *ws, XKeyEvent *event) *out = 0; for (i = 0; i < MAX_PASSWD_CHARS && ws->plaintext_passwd_char_size[i]; i++) { - const char *b = /* "\xE2\x80\xA2"; */ /* U+2022 Bullet */ - "\xe2\x97\x8f"; /* U+25CF Black Circle */ - strcat (out, b); + strcat (out, ws->asterisk_utf8); out += strlen(out); } } @@ -2059,6 +2265,14 @@ handle_event (window_state *ws, XEvent *xev) refresh_p = True; break; + case KeyRelease: + if (ws->bs_timer) + { + XtRemoveTimeOut (ws->bs_timer); + ws->bs_timer = 0; + } + break; + case ButtonPress: case ButtonRelease: { @@ -2091,6 +2305,23 @@ cursor_timer (XtPointer closure, XtIntervalId *id) } +/* Auto-repeat Backspace, since XInput2 doesn't do autorepeat. */ +static void +bs_timer (XtPointer closure, XtIntervalId *id) +{ + window_state *ws = (window_state *) closure; + if (ws->bs_timer) + XtRemoveTimeOut (ws->bs_timer); + ws->bs_timer = 0; + /* Erase line */ + memset (ws->plaintext_passwd, 0, sizeof (ws->plaintext_passwd)); + memset (ws->plaintext_passwd_char_size, 0, + sizeof (ws->plaintext_passwd_char_size)); + memset (ws->censored_passwd, 0, sizeof(ws->censored_passwd)); + window_draw (ws); +} + + /* Redraw the window for the thermometer, and exit if the time has expired. */ static void @@ -2121,8 +2352,7 @@ gui_main_loop (window_state *ws, Bool splash_p, Bool notification_p) timeout = 5; else { - timeout = get_seconds_resource (ws->dpy, "passwdTimeout", "Time"); - if (timeout <= 5) timeout = 5; + timeout = ws->passwd_timeout; cursor_timer (ws, 0); } @@ -2236,7 +2466,12 @@ gui_main_loop (window_state *ws, Bool splash_p, Bool notification_p) break; } - /* Since MappingNotify doesn't work, we have to do this crap instead. */ + /* Since MappingNotify doesn't work, we have to do this crap instead. + Probably some of these events could be ignored, as it seems that + any.xkb_type == XkbStateNotify comes in every time a modifier key is + touched. What event comes in when there is a keyboard layout change, + the only thing we actually care about? + */ if (xev.xany.type == ws->xkb_opcode) { XkbEvent *xkb = (XkbEvent *) &xev; @@ -2496,10 +2731,10 @@ xscreensaver_auth_finished (void *closure, Bool authenticated_p) void -xscreensaver_splash (void *closure) +xscreensaver_splash (void *closure, Bool disable_settings_p) { Widget root_widget = (Widget) closure; - window_state *ws = window_init (root_widget, True); + window_state *ws = window_init (root_widget, disable_settings_p ? 2 : 1); ws->auth_state = AUTH_READ; gui_main_loop (ws, True, False); destroy_window (ws); diff --git a/driver/dpms.c b/driver/dpms.c index 15721ea..fe5e245 100644 --- a/driver/dpms.c +++ b/driver/dpms.c @@ -1,5 +1,5 @@ /* dpms.c --- syncing the X Display Power Management System values - * xscreensaver, Copyright © 2001-2021 Jamie Zawinski <jwz@jwz.org> + * xscreensaver, Copyright © 2001-2022 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 @@ -20,17 +20,59 @@ #include "xscreensaver.h" +/* Disable the X11 built-in screen saver. This is not directly related + to DPMS, but it does need to be prevented from fighting with us. + */ +static void +disable_builtin_saver (Display *dpy) +{ + int otimeout = -1; + int ointerval = -1; + int oblanking = -1; + int oexposures = -1; + XGetScreenSaver (dpy, &otimeout, &ointerval, &oblanking, &oexposures); + if (otimeout == 0 && ointerval == 0 && oblanking == 0 && oexposures == 0) + { + if (verbose_p > 1) + fprintf (stderr, "%s: builtin saver already disabled\n", blurb()); + } + else + { + if (verbose_p) + fprintf (stderr, "%s: disabling server's builtin saver\n", blurb()); + XSetScreenSaver (dpy, 0, 0, 0, 0); + XForceScreenSaver (dpy, ScreenSaverReset); + } +} + + #ifndef HAVE_DPMS_EXTENSION /* almost the whole file */ void sync_server_dpms_settings (Display *dpy, struct saver_preferences *p) { + disable_builtin_saver (dpy); if (p->verbose_p) fprintf (stderr, "%s: DPMS not supported at compile time\n", blurb()); } -Bool monitor_powered_on_p (Display *dpy) { return True; } -void monitor_power_on (saver_info *si, Bool on_p) { return; } +Bool monitor_powered_on_p (Display *dpy) +{ + if (verbose_p > 1) + fprintf (stderr, + "%s: DPMS disabled at compile time, assuming monitor on\n", + blurb()); + return True; +} + +void monitor_power_on (saver_info *si, Bool on_p) +{ + if (verbose_p > 1) + fprintf (stderr, + "%s: DPMS disabled at compile time, not turning monitor %s\n", + blurb(), (on ? "on" : "off")); + return; +} #else /* HAVE_DPMS_EXTENSION -- whole file */ @@ -65,15 +107,18 @@ sync_server_dpms_settings (Display *dpy, struct saver_preferences *p) Bool verbose_p = p->verbose_p; static Bool warned_p = False; - /* If the monitor is currently powered off, defer any changes until - we are next called while it is powered on. */ + /* If the monitor is currently powered off, defer any changes until we are + next called while it is powered on. Making changes to the DPMS settings + can have the side-effect of powering the monitor back on. + */ if (! monitor_powered_on_p (dpy)) - return; + { + if (verbose_p > 1) + fprintf (stderr, "%s: DPMS: monitor off, skipping sync\n", blurb()); + return; + } - /* Why did I do this? It makes DPMS never happen. - XSetScreenSaver (dpy, 0, 0, 0, 0); - XForceScreenSaver (dpy, ScreenSaverReset); - */ + disable_builtin_saver (dpy); if (dpms_quickoff_p && !off_secs) { @@ -102,7 +147,7 @@ sync_server_dpms_settings (Display *dpy, struct saver_preferences *p) if (! DPMSQueryExtension (dpy, &event, &error)) { - if (verbose_p && !warned_p) + if (verbose_p > 1 || (verbose_p && !warned_p)) fprintf (stderr, "%s: XDPMS extension not supported\n", blurb()); warned_p = True; return; @@ -110,7 +155,7 @@ sync_server_dpms_settings (Display *dpy, struct saver_preferences *p) if (! DPMSCapable (dpy)) { - if (verbose_p && !warned_p) + if (verbose_p > 1 || (verbose_p && !warned_p)) fprintf (stderr, "%s: DPMS not supported\n", blurb()); warned_p = True; return; @@ -118,7 +163,7 @@ sync_server_dpms_settings (Display *dpy, struct saver_preferences *p) if (! DPMSInfo (dpy, &o_power, &o_enabled)) { - if (verbose_p && !warned_p) + if (verbose_p > 1 || (verbose_p && !warned_p)) fprintf (stderr, "%s: unable to get DPMS state\n", blurb()); warned_p = True; return; @@ -167,6 +212,9 @@ sync_server_dpms_settings (Display *dpy, struct saver_preferences *p) fprintf (stderr, "%s: set DPMS timeouts: %d %d %d\n", blurb(), standby_secs, suspend_secs, off_secs); } + else if (verbose_p > 1) + fprintf (stderr, "%s: DPMS timeouts already %d %d %d\n", blurb(), + o_standby, o_suspend, o_off); } Bool @@ -178,19 +226,34 @@ monitor_powered_on_p (Display *dpy) CARD16 state; if (!DPMSQueryExtension(dpy, &event_number, &error_number)) - /* Server doesn't know -- assume the monitor is on. */ - result = True; + { + /* Server doesn't know -- assume the monitor is on. */ + if (verbose_p > 1) + fprintf (stderr, "%s: DPMSQueryExtension failed, assuming monitor on\n", + blurb()); + result = True; + } else if (!DPMSCapable(dpy)) - /* Server says the monitor doesn't do power management -- so it's on. */ - result = True; + { + /* Server says the monitor doesn't do power management -- so it's on. */ + if (verbose_p > 1) + fprintf (stderr, "%s: DPMSCapable false; assuming monitor on\n", + blurb()); + result = True; + } else { DPMSInfo(dpy, &state, &onoff); if (!onoff) - /* Server says DPMS is disabled -- so the monitor is on. */ - result = True; + { + /* Server says DPMS is disabled -- so the monitor is on. */ + if (verbose_p > 1) + fprintf (stderr, "%s: DPMSInfo disabled; assuming monitor on\n", + blurb()); + result = True; + } else switch (state) { case DPMSModeOn: result = True; break; /* really on */ @@ -199,6 +262,13 @@ monitor_powered_on_p (Display *dpy) case DPMSModeOff: result = False; break; /* really off */ default: result = True; break; /* protocol error? */ } + if (verbose_p > 1) + fprintf (stderr, "%s: DPMSInfo = %s %s\n", blurb(), + (state == DPMSModeOn ? "DPMSModeOn" : + state == DPMSModeStandby ? "DPMSModeStandby" : + state == DPMSModeSuspend ? "DPMSModeSuspend" : + state == DPMSModeOff ? "DPMSModeOff" : "???"), + (result ? "True" : "False")); } return result; @@ -207,6 +277,7 @@ monitor_powered_on_p (Display *dpy) void monitor_power_on (saver_info *si, Bool on_p) { + Bool verbose_p = si->prefs.verbose_p; if ((!!on_p) != monitor_powered_on_p (si->dpy)) { XErrorHandler old_handler; @@ -215,7 +286,7 @@ monitor_power_on (saver_info *si, Bool on_p) if (!DPMSQueryExtension(si->dpy, &event_number, &error_number) || !DPMSCapable(si->dpy)) { - if (si->prefs.verbose_p && !warned_p) + if (verbose_p > 1 || (verbose_p && !warned_p)) fprintf (stderr, "%s: unable to power %s monitor: no DPMS extension\n", blurb(), (on_p ? "on" : "off")); @@ -243,12 +314,18 @@ monitor_power_on (saver_info *si, Bool on_p) XSetErrorHandler (old_handler); /* Ignore error_handler_hit_p, just probe monitor instead */ + if (verbose_p > 1 && error_handler_hit_p) + fprintf (stderr, "%s: DPMSForceLevel got an X11 error\n", blurb()); + if ((!!on_p) != monitor_powered_on_p (si->dpy)) /* double-check */ fprintf (stderr, "%s: DPMSForceLevel(dpy, %s) did not change monitor power state\n", blurb(), (on_p ? "DPMSModeOn" : "DPMSModeOff")); } + else if (verbose_p > 1) + fprintf (stderr, "%s: monitor is already %s\n", blurb(), + on_p ? "on" : "off"); } #endif /* HAVE_DPMS_EXTENSION -- whole file */ diff --git a/driver/exts.c b/driver/exts.c index 641325d..af5c536 100644 --- a/driver/exts.c +++ b/driver/exts.c @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright © 1991-2021 Jamie Zawinski <jwz@jwz.org> +/* xscreensaver, Copyright © 1991-2022 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 @@ -17,6 +17,10 @@ # include "config.h" #endif +#ifndef HAVE_XINPUT +# error The XInput2 extension is required +#endif + #include <stdio.h> #include <X11/Xlib.h> #include <X11/Xlibint.h> @@ -190,8 +194,13 @@ print_available_extensions (saver_info *si) # ifdef HAVE_LIBSYSTEMD fprintf (stderr, "%s: libsystemd\n", blurb()); -# else - fprintf (stderr, "%s: libsystemd (disabled at compile time)\n", blurb()); +# endif +# ifdef HAVE_LIBELOGIND + fprintf (stderr, "%s: libelogind\n", blurb()); +# endif +# if !defined(HAVE_LIBSYSTEMD) && !defined(HAVE_LIBELOGIND) + fprintf (stderr, "%s: libsystemd/libelogind (disabled at compile time)\n", + blurb()); # endif for (i = 0; i < si->nscreens; i++) diff --git a/driver/fade.c b/driver/fade.c index 9451582..46a56ef 100644 --- a/driver/fade.c +++ b/driver/fade.c @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright © 1992-2021 Jamie Zawinski <jwz@jwz.org> +/* xscreensaver, Copyright © 1992-2022 Jamie Zawinski <jwz@jwz.org> * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that @@ -19,7 +19,7 @@ - XF86 Gamma or RANDR Gamma: These do the fade by altering the brightness settings of the screen. This works on any system that has the "XF86 Video-Mode" extension (which is every modern system) AND ALSO has gamma - support in the video driver. But it turns out that as of 2021, the + support in the video driver. But it turns out that as of 2022, the Raspberry Pi HDMI video driver still does not support gamma. And there's no way to determine that the video driver lacks gamma support even though the extension exists. Since the Pi is probably the single most popular @@ -105,14 +105,13 @@ */ #undef HAVE_RANDR_12 -#define HAVE_XINPUT2 1 /* Mandatory */ - - -#ifdef HAVE_XINPUT2 -# include <X11/extensions/XInput2.h> -# include "xinput.h" +#ifndef HAVE_XINPUT +# error The XInput2 extension is required #endif +#include <X11/extensions/XInput2.h> +#include "xinput.h" + typedef struct { int nscreens; @@ -157,7 +156,7 @@ double_time (void) } -#ifdef HAVE_XINPUT2 +#ifdef HAVE_XINPUT static int xi_opcode = -1; #endif @@ -176,7 +175,7 @@ user_event_p (Display *dpy, XEvent *event, XPointer arg) case MotionNotify: if (motion_p) return True; break; -# ifdef HAVE_XINPUT2 +# ifdef HAVE_XINPUT case GenericEvent: { XIRawEvent *re; @@ -186,18 +185,31 @@ user_event_p (Display *dpy, XEvent *event, XPointer arg) XGetEventData (dpy, &event->xcookie); if (! event->xcookie.data) return False; - re = event->xcookie.data; - if (re->evtype == XI_RawKeyPress || - re->evtype == XI_RawButtonPress) - return True; - else if (motion_p && re->evtype == XI_RawMotion) + re = event->xcookie.data; + switch (re->evtype) { + case XI_RawKeyPress: + case XI_RawButtonPress: + case XI_RawTouchBegin: + case XI_KeyPress: + case XI_ButtonPress: + case XI_TouchBegin: return True; + break; + case XI_RawMotion: + case XI_RawTouchUpdate: + case XI_Motion: + case XI_TouchUpdate: + if (motion_p) return True; + break; + default: + break; + } /* Calling XFreeEventData here is bad news */ } break; -# endif /* HAVE_XINPUT2 */ +# endif /* HAVE_XINPUT */ default: break; } @@ -213,7 +225,7 @@ user_active_p (XtAppContext app, Display *dpy, Bool fade_out_p) Bool motion_p = fade_out_p; /* Motion aborts fade-out, not fade-in. */ motion_p = False; /* Naah, never abort on motion only */ -# ifdef HAVE_XINPUT2 +# ifdef HAVE_XINPUT if (xi_opcode == -1) { Bool ov = verbose_p; @@ -294,6 +306,46 @@ ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error) } +/* Like XDestroyWindow, but destroys the window later, on a timer. This is + necessary to work around a KDE 5 compositor bug. Without this, destroying + an old window causes the desktop to briefly become visible, even though a + new window has already been mapped that is obscuring both of them! + */ +typedef struct { + XtAppContext app; + Display *dpy; + Window window; +} defer_destroy_closure; + +static void +defer_destroy_handler (XtPointer closure, XtIntervalId *id) +{ + defer_destroy_closure *c = (defer_destroy_closure *) closure; + XErrorHandler old_handler; + XSync (c->dpy, False); + error_handler_hit_p = False; + old_handler = XSetErrorHandler (ignore_all_errors_ehandler); + XDestroyWindow (c->dpy, c->window); + XSync (c->dpy, False); + XSetErrorHandler (old_handler); + if (verbose_p > 1 && !error_handler_hit_p) + fprintf (stderr, "%s: destroyed old window 0x%lx\n", + blurb(), (unsigned long) c->window); + free (c); +} + +/* Used here and in windows.c */ +void +defer_XDestroyWindow (XtAppContext app, Display *dpy, Window w) +{ + defer_destroy_closure *c = (defer_destroy_closure *) malloc (sizeof (*c)); + c->app = app; + c->dpy = dpy; + c->window = w; + XtAppAddTimeOut (app, 5 * 1000, defer_destroy_handler, (XtPointer) c); +} + + /* Returns true if canceled by user activity. */ Bool fade_screens (XtAppContext app, Display *dpy, @@ -304,8 +356,8 @@ fade_screens (XtAppContext app, Display *dpy, int status = False; fade_state *state = 0; - if (nwindows <= 0) abort(); - if (!saver_windows) abort(); + if (nwindows <= 0) return False; + if (!saver_windows) return False; if (!closureP) abort(); state = (fade_state *) *closureP; @@ -398,14 +450,14 @@ colormap_fade (XtAppContext app, Display *dpy, int status = -1; Colormap *window_cmaps = 0; int i, j, k; - int cmaps_per_screen = 5; - int nscreens = ScreenCount(dpy); - int ncmaps = nscreens * cmaps_per_screen; + unsigned int cmaps_per_screen = 5; + unsigned int nscreens = ScreenCount(dpy); + unsigned int ncmaps = nscreens * cmaps_per_screen; Colormap *fade_cmaps = 0; Bool installed = False; - int total_ncolors; + unsigned int total_ncolors; XColor *orig_colors, *current_colors, *screen_colors, *orig_screen_colors; - int screen; + unsigned int screen; window_cmaps = (Colormap *) calloc(sizeof(Colormap), nwindows); if (!window_cmaps) abort(); @@ -1659,8 +1711,10 @@ xshm_fade (XtAppContext app, Display *dpy, { XClearWindow (dpy, saver_windows[screen]); XMapRaised (dpy, saver_windows[screen]); - if (info[screen].window) - XUnmapWindow (dpy, info[screen].window); + /* Doing this here triggers the same KDE 5 compositor bug that + defer_XDestroyWindow is to work around. */ + /* if (info[screen].window) + XUnmapWindow (dpy, info[screen].window); */ } } @@ -1688,7 +1742,7 @@ xshm_fade (XtAppContext app, Display *dpy, if (info[screen].intermediate) destroy_xshm_image (dpy, info[screen].intermediate, &shm_info); if (info[screen].window) - XDestroyWindow (dpy, info[screen].window); + defer_XDestroyWindow (app, dpy, info[screen].window); if (info[screen].gc) XFreeGC (dpy, info[screen].gc); } diff --git a/driver/fade.h b/driver/fade.h index 56725b5..6c62d33 100644 --- a/driver/fade.h +++ b/driver/fade.h @@ -17,4 +17,12 @@ extern Bool fade_screens (XtAppContext app, Display *dpy, Window *black_windows, int nwindows, double seconds, Bool out_p, Bool from_desktop_p, void **closureP); + +/* Like XDestroyWindow, but destroys the window later, on a timer. This is + necessary to work around a KDE 5 compositor bug. Without this, destroying + an old window causes the desktop to briefly become visible, even though a + new window has already been mapped that is obscuring both of them! + */ +extern void defer_XDestroyWindow (XtAppContext, Display *, Window); + #endif /* __FADE_H__ */ diff --git a/driver/gresource.xml b/driver/gresource.xml new file mode 100644 index 0000000..345fadd --- /dev/null +++ b/driver/gresource.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/jwz/xscreensaver"> + <file preprocess="xml-stripblanks">demo.ui</file> + <file preprocess="xml-stripblanks">prefs.ui</file> + </gresource> +</gresources> diff --git a/driver/link_axp.com b/driver/link_axp.com deleted file mode 100644 index a141892..0000000 --- a/driver/link_axp.com +++ /dev/null @@ -1,15 +0,0 @@ -$! 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 deleted file mode 100644 index d1de0d0..0000000 --- a/driver/link_decc.com +++ /dev/null @@ -1,15 +0,0 @@ -$! 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 deleted file mode 100644 index d36481e..0000000 --- a/driver/lock.c +++ /dev/null @@ -1,2276 +0,0 @@ -/* 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); - - XSetForeground (si->dpy, gc1, pw->foreground); - 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) - { - XSetForeground (si->dpy, gc1, pw->thermo_background); - XFillRectangle (si->dpy, si->passwd_dialog, gc1, - 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; - - si->unlock_dismiss_time = time((time_t *) 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; - time_t now = time ((time_t *) 0); - - if (!si->unlock_cb) - { - fprintf(stderr, "%s: Error: no unlock function specified!\n", blurb()); - return False; - } - - raise_window (si, True, True, True); - - /* If your cat is sitting on the return key, don't thrash the window. - Only one failed/cancelled unlock per 2 seconds. - */ - if (si->unlock_dismiss_time >= now - 1) - { - if (p->verbose_p) - fprintf (stderr, "%s: unlock: thrashing: RET held down?\n", blurb()); - XSync (si->dpy, False); -# undef sleep - sleep (2); /* This is less than ideal, but fine */ - } - - 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 deleted file mode 100644 index fdba1ee..0000000 --- a/driver/mlstring.c +++ /dev/null @@ -1,229 +0,0 @@ -/* - * (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 deleted file mode 100644 index ce36205..0000000 --- a/driver/mlstring.h +++ /dev/null @@ -1,57 +0,0 @@ -/* 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 deleted file mode 100644 index 4b17c63..0000000 --- a/driver/passwd-helper.c +++ /dev/null @@ -1,158 +0,0 @@ -/* passwd-helper.c --- verifying typed passwords with external helper program - * xscreensaver, Copyright © 1993-2021 Jamie Zawinski <jwz@jwz.org> - * written by Olaf Kirch <okir@suse.de> - * - * 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 strongly suspect that this code has not been used in decades, and I - am considering removing it. These details should be hidden behind PAM. - If you are using this code, email me and tell me why. -- jwz, Feb 2021 - - *****************************************************************************/ - -#error "email jwz@jwz.org about passwd-helper.c" - - -/* The idea here is to be able to run xscreensaver without any setuid bits. - * Password verification happens through an external program that you feed - * your password to on stdin. The external command is invoked with a user - * 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 <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> - -#include "blurb.h" -#include "auth.h" - - -static int -ext_run (const char *user, const char *typed_passwd) -{ - int pfd[2], status; - pid_t pid; - - if (pipe(pfd) < 0) - return 0; - - if (verbose_p) - fprintf (stderr, "%s: EXT: %s\n", blurb(), PASSWD_HELPER_PROGRAM); - - 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: EXT: %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: waitpid failed: %s\n", - blurb(), strerror(errno)); - return 0; - } - - 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 (void *closure, const char *typed_passwd) -{ - struct passwd *pw; - int res = 0; - - if ((pw = getpwuid(getuid())) != NULL) - res = ext_run (pw->pw_name, typed_passwd); - endpwent(); - -#ifdef ALLOW_ROOT_PASSWD - if (!res) - res = ext_run ("root", typed_passwd); -#endif /* ALLOW_ROOT_PASSWD */ - - return res; -} - - -Bool -ext_priv_init (void) -{ - /* Make sure the passwd helper exists */ - if (access(PASSWD_HELPER_PROGRAM, X_OK) < 0) { - fprintf(stderr, - "%s: EXT: warning: %s does not exist.\n" - "%s: EXT password authentication will not work.\n", - blurb(), PASSWD_HELPER_PROGRAM, blurb()); - return False; - } - return True; -} - -#endif /* NO_LOCKING -- whole file */ diff --git a/driver/passwd-pam.c b/driver/passwd-pam.c index 87942ab..4d434db 100644 --- a/driver/passwd-pam.c +++ b/driver/passwd-pam.c @@ -1,5 +1,5 @@ /* passwd-pam.c --- verifying typed passwords with PAM - * xscreensaver, Copyright © 1993-2021 Jamie Zawinski <jwz@jwz.org> + * xscreensaver, Copyright © 1993-2022 Jamie Zawinski <jwz@jwz.org> * By Bill Nottingham <notting@redhat.com> and jwz. * * Permission to use, copy, modify, distribute, and sell this software and its @@ -34,12 +34,16 @@ those files are not a part of PAM's C API, but it's how real-world systems actually work. - Also note that FreeBSD's implementation of PAM requires the calling process - to be running as root during the entire interactive PAM conversation: it - can't ever disavow privileges. Linux's PAM implementation uses a setuid - helper so that a non-root process can still authenticate, as is right and - proper. Consequently, XScreenSaver does not support PAM on FreeBSD. - Dear FreeBSD, get your shit together. + Also note that FreeBSD's PAM configuration requires the calling process to + be running as root during the *entire* interactive PAM conversation: it + can't ever disavow privileges. Whereas Linux's PAM configurations use a + setuid helper within the PAM stack so that a non-root process can still + authenticate, as is right and proper. Consequently, PAM does not work with + XScreenSaver on FreeBSD by default. Dear FreeBSD, get your shit together. + + I am told that only *some* of the FreeBSD PAM modules have this bug, e.g., + "pam_unix" fails, but "pam_winbind" works. So this problem is demonstrably + fixable entirely within the PAM stack, and that's where it should be fixed. */ #ifdef HAVE_CONFIG_H diff --git a/driver/passwd-pwent.c b/driver/passwd-pwent.c index b6c74de..c568063 100644 --- a/driver/passwd-pwent.c +++ b/driver/passwd-pwent.c @@ -1,5 +1,5 @@ /* passwd-pwent.c --- verifying typed passwords with the OS. - * xscreensaver, Copyright © 1993-2021 Jamie Zawinski <jwz@jwz.org> + * xscreensaver, Copyright © 1993-2022 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 @@ -163,6 +163,7 @@ get_encrypted_passwd (const char *user) result = strdup(p->pw_passwd); } +# ifdef HAVE_HPUX_PASSWD /* The manual for passwd(4) on HPUX 10.10 says: Password aging is put in effect for a particular user if his @@ -173,6 +174,9 @@ get_encrypted_passwd (const char *user) 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. + + This behavior can break other systems where comma separated data is + significant, such as argon2 passwords on NetBSD. */ if (result && strlen(result) > 13) { @@ -180,6 +184,7 @@ get_encrypted_passwd (const char *user) if (s) *s = 0; } +# endif /* HAVE_HPUX_PASSWD */ /* We only issue this warning in non-verbose mode if not compiled with support for PAM. If we're using PAM, it's common for pwent passwords diff --git a/driver/pdf2jpeg.m b/driver/pdf2jpeg.m deleted file mode 100644 index d681b4a..0000000 --- a/driver/pdf2jpeg.m +++ /dev/null @@ -1,152 +0,0 @@ -/* 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 deleted file mode 100644 index 9d80dd7..0000000 --- a/driver/pdf2jpeg.man +++ /dev/null @@ -1,43 +0,0 @@ -.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.ui b/driver/prefs.ui new file mode 100644 index 0000000..a6e4e2e --- /dev/null +++ b/driver/prefs.ui @@ -0,0 +1,518 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="XScreenSaverDialog" parent="GtkDialog"> + <property name="title" translatable="yes">XScreenSaver Settings</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 internal-child="vbox"> + <object class="GtkBox" id="dialog_vbox"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + <property name="orientation">vertical</property> + <property name="margin-start">12</property> + <property name="margin-end">12</property> + <property name="margin-top">0</property> + <property name="margin-bottom">0</property> + <child internal-child="action_area"> + <object class="GtkHButtonBox" id="dialog_action_area"> + <property name="visible">True</property> + <property name="layout-style">GTK_BUTTONBOX_END</property> + <child> + <object class="GtkButton" id="adv_button"> + <property name="visible">True</property> + <property name="can-default">True</property> + <property name="can-focus">True</property> + <property name="label" translatable="yes">_Advanced >></property> + <property name="use-underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus-on-click">True</property> + <signal handler="settings_adv_cb" name="clicked"/> + </object> + </child> + <child> + <object class="GtkButton" id="std_button"> + <property name="visible">True</property> + <property name="can-default">True</property> + <property name="can-focus">True</property> + <property name="label" translatable="yes">_Standard <<</property> + <property name="use-underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus-on-click">True</property> + <signal handler="settings_std_cb" name="clicked"/> + </object> + </child> + <child> + <object class="GtkButton" id="reset_button"> + <property name="visible">True</property> + <property name="can-default">True</property> + <property name="can-focus">True</property> + <property name="label" translatable="yes">_Reset to Defaults</property> + <property name="use-underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus-on-click">True</property> + <signal handler="settings_reset_cb" name="clicked"/> + </object> + </child> + <child> + <object class="GtkButton" id="cancel_button"> + <property name="visible">True</property> + <property name="can-default">True</property> + <property name="can-focus">True</property> + <property name="label">gtk-cancel</property> + <property name="use-stock">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus-on-click">True</property> + <signal handler="settings_cancel_cb" name="clicked"/> + </object> + </child> + <child> + <object class="GtkButton" id="ok_button"> + <property name="visible">True</property> + <property name="can-default">True</property> + <property name="can-focus">True</property> + <property name="label" translatable="yes">Save</property> + <property name="use-stock">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus-on-click">True</property> + <signal handler="settings_ok_cb" name="clicked"/> + </object> + </child> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack-type">GTK_PACK_END</property> + </packing> + </child> + <child> + <object class="GtkBox" id="vbox1"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkFrame" id="opt_frame"> + <property name="visible">True</property> + <property name="label-xalign">0</property> + <property name="label-yalign">0</property> + <property name="shadow-type">GTK_SHADOW_NONE</property> + <accessibility> + <relation target="label6" type="labelled-by"/> + </accessibility> + <child> + <object class="GtkNotebook" id="opt_notebook"> + <property name="border-width">12</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="show-tabs">False</property> + <property name="show-border">False</property> + <property name="tab-pos">GTK_POS_BOTTOM</property> + <property name="scrollable">False</property> + <property name="enable-popup">False</property> + <signal handler="settings_switch_page_cb" name="switch_page"/> + <child> + <object class="GtkBox" id="settings_vbox"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + <property name="orientation">vertical</property> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="tab-expand">False</property> + <property name="tab-fill">False</property> + </packing> + </child> + <child type="tab"> + <object class="GtkLabel" id="std_label"> + <property name="visible">True</property> + <property name="label" translatable="yes">Standard</property> + <property name="use-underline">False</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + </object> + <packing> + <property name="tab-expand">False</property> + <property name="tab-fill">False</property> + </packing> + </child> + <child> + <object class="GtkTable" id="adv_table"> + <property name="visible">True</property> + <property name="n-rows">4</property> + <property name="n-columns">2</property> + <property name="homogeneous">False</property> + <property name="row-spacing">20</property> + <property name="column-spacing">0</property> + <property name="expand">True</property> + <child> + <object class="GtkLabel" id="cmd_label"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Command Line:</property> + <property name="use-underline">True</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="mnemonic-widget">cmd_text</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + <accessibility> + <relation target="cmd_text" type="label-for"/> + </accessibility> + </object> + <packing> + <property name="left-attach">1</property> + <property name="right-attach">2</property> + <property name="top-attach">1</property> + <property name="bottom-attach">2</property> + <property name="x-options">fill</property> + <property name="y-options"/> + </packing> + </child> + <child> + <object class="GtkEntry" id="cmd_text"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="editable">True</property> + <property name="visibility">True</property> + <property name="max-length">0</property> + <property name="text" translatable="yes"/> + <property name="has-frame">True</property> + <property name="invisible-char">*</property> + <property name="activates-default">False</property> + <accessibility> + <relation target="cmd_label" type="labelled-by"/> + </accessibility> + </object> + <packing> + <property name="left-attach">1</property> + <property name="right-attach">2</property> + <property name="top-attach">2</property> + <property name="bottom-attach">3</property> + <property name="y-options"/> + </packing> + </child> + <child> + <object class="GtkBox" id="visual_hbox"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + <property name="orientation">horizontal</property> + <child> + <object class="GtkLabel" id="visual"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Visual:</property> + <property name="use-underline">True</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">1</property> + <property name="yalign">0.5</property> + <property name="xpad">3</property> + <property name="ypad">0</property> + <property name="mnemonic-widget">visual_combo</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + <accessibility> + <relation target="visual_combo" type="label-for"/> + </accessibility> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <object class="GtkListStore" id="visual_combo_model"> + <columns> + <column type="gchararray"/> + </columns> + <data> + <row> + <col id="0" translatable="yes">Any</col> + </row> + <row> + <col id="0" translatable="yes">Best</col> + </row> + <row> + <col id="0" translatable="yes">Default</col> + </row> + <row> + <col id="0" translatable="yes">Default-N</col> + </row> + <row> + <col id="0" translatable="yes">GL</col> + </row> + <row> + <col id="0" translatable="yes">TrueColor</col> + </row> + <row> + <col id="0" translatable="yes">PseudoColor</col> + </row> + <row> + <col id="0" translatable="yes">StaticGray</col> + </row> + <row> + <col id="0" translatable="yes">GrayScale</col> + </row> + <row> + <col id="0" translatable="yes">DirectColor</col> + </row> + <row> + <col id="0" translatable="yes">Color</col> + </row> + <row> + <col id="0" translatable="yes">Gray</col> + </row> + <row> + <col id="0" translatable="yes">Mono</col> + </row> + </data> + </object> + <object class="GtkComboBox" id="visual_combo"> + <property name="has-entry">False</property> + <property name="visible">True</property> + <property name="model">visual_combo_model</property> + <accessibility> + <relation target="visual" type="labelled-by"/> + </accessibility> + <child> + <object class="GtkEntry" id="visual_entry"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="editable">True</property> + <property name="visibility">True</property> + <property name="max-length">0</property> + <property name="text" translatable="yes"/> + <property name="has-frame">True</property> + <property name="invisible-char">*</property> + <property name="activates-default">False</property> + </object> + </child> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </object> + <packing> + <property name="left-attach">1</property> + <property name="right-attach">2</property> + <property name="top-attach">3</property> + <property name="bottom-attach">4</property> + <property name="x-options">fill</property> + <property name="y-options">fill</property> + </packing> + </child> + </object> + <packing> + <property name="tab-expand">False</property> + <property name="tab-fill">False</property> + </packing> + </child> + <child type="tab"> + <object class="GtkLabel" id="adv_label"> + <property name="visible">True</property> + <property name="label" translatable="yes">Advanced</property> + <property name="use-underline">False</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + </object> + <packing> + <property name="tab-expand">False</property> + <property name="tab-fill">False</property> + </packing> + </child> + </object> + </child> + <child type="label"> + <object class="GtkLabel" id="label6"> + <property name="visible">True</property> + <property name="label" translatable="yes">Settings</property> + <property name="use-underline">False</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + <accessibility> + <relation target="opt_frame" type="label-for"/> + </accessibility> + </object> + </child> + </object> + <packing> + <property name="padding">10</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + <child> + <object class="GtkFrame" id="doc_frame"> + <property name="visible">True</property> + <property name="label-xalign">0</property> + <property name="label-yalign">0</property> + <property name="shadow-type">GTK_SHADOW_NONE</property> + <child> + <object class="GtkBox" id="doc_vbox"> + <property name="border-width">5</property> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">5</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel" id="doc"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="label" translatable="yes"/> + <property name="use-underline">False</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">True</property> + <property name="selectable">True</property> + <property name="xalign">0</property> + <property name="yalign">0</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + <child> + <object class="GtkBox" id="hbox1"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + <property name="orientation">horizontal</property> + <property name="margin-top">12</property> + <child> + <object class="GtkButton" id="manual"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="label" translatable="yes">_Documentation...</property> + <property name="use-underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus-on-click">True</property> + <signal handler="manual_cb" name="clicked"/> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="pack-type">GTK_PACK_END</property> + </packing> + </child> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </object> + </child> + <child type="label"> + <object class="GtkLabel" id="label7"> + <property name="visible">True</property> + <property name="label" translatable="yes"/> + <property name="use-underline">False</property> + <property name="use-markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width-chars">-1</property> + <property name="single-line-mode">False</property> + <property name="angle">0</property> + </object> + </child> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="0">adv_button</action-widget> + <action-widget response="0">std_button</action-widget> + <action-widget response="0">reset_button</action-widget> + <action-widget response="-6">cancel_button</action-widget> + <action-widget response="-5">ok_button</action-widget> + </action-widgets> + </template> +</interface> diff --git a/driver/prefsw.c b/driver/prefsw.c index 2bbf94f..c5b4b2d 100644 --- a/driver/prefsw.c +++ b/driver/prefsw.c @@ -1,5 +1,5 @@ /* prefs.c --- reading and writing the ~/.xscreensaver file. - * xscreensaver, Copyright © 1998-2021 Jamie Zawinski <jwz@jwz.org> + * xscreensaver, Copyright © 1998-2023 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 @@ -80,22 +80,6 @@ chase_symlinks (const char *file) } -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) { @@ -103,32 +87,7 @@ init_file_name (void) if (!file) { - uid_t uid = getuid (); const char *home = getenv("HOME"); - - if (i_am_a_nobody (uid) || !home || !*home) - { - /* If we're running as nobody, then use root's .xscreensaver file - (since ~root/.xscreensaver and ~nobody/.xscreensaver are likely - to be different -- without this, xscreensaver-settings would - appear to have no effect when the luser is running as root.) - */ - struct passwd *p = getpwuid (uid); - if (!p || !p->pw_name || !*p->pw_name) - { - fprintf (stderr, "%s: couldn't get user info of uid %d\n", - blurb(), getuid ()); - } - else if (!p->pw_dir || !*p->pw_dir) - { - fprintf (stderr, "%s: couldn't get home directory of \"%s\"\n", - blurb(), (p->pw_name ? p->pw_name : "???")); - } - else - { - home = p->pw_dir; - } - } if (home && *home) { const char *name = ".xscreensaver"; @@ -155,32 +114,29 @@ 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; + const char *name = init_file_name(); + 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 (file) free (file); - if (n2) free (n2); + if (!name || !*name) + file = 0; + else + { + file = (char *) malloc(strlen(name) + 20); + sprintf (file, "%s.%08lX", name, (random() % 0xFFFFFFFF)); } + if (n2) free (n2); + if (file && *file) return file; else return 0; } + static const char * const prefs[] = { "timeout", "cycle", @@ -226,6 +182,7 @@ static const char * const prefs[] = { "textProgram", "textURL", "dialogTheme", + "settingsGeom", "", "programs", "", @@ -293,7 +250,7 @@ static void line_handler (int lineno, { struct parser_closure *c = (struct parser_closure *) closure; saver_preferences *p = c->prefs; - if (!p->db) abort(); + if (!p->db) return; /* Not X11, Wayland */ handle_entry (&p->db, key, val, c->file, lineno); } @@ -506,8 +463,6 @@ write_init_file (Display *dpy, */ char *visual_name; char *programs; - Bool overlay_stderr_p; - char *stderr_font; FILE *out; if (!name) goto END; @@ -535,9 +490,7 @@ write_init_file (Display *dpy, /* 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.) + executable. */ if (stat(name, &st) == 0) { @@ -545,9 +498,6 @@ write_init_file (Display *dpy, 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)); @@ -559,11 +509,9 @@ write_init_file (Display *dpy, } } - /* Kludge, since these aren't in the saver_preferences struct... */ + /* Kludge, since this isn'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; { @@ -646,6 +594,7 @@ write_init_file (Display *dpy, CHECK("loadURL") continue; /* don't save */ CHECK("newLoginCommand") continue; /* don't save */ CHECK("dialogTheme") type = pref_str, s = p->dialog_theme; + CHECK("settingsGeom") type = pref_str, s = p->settings_geom; CHECK("nice") type = pref_int, i = p->nice_inferior; CHECK("memoryLimit") continue; /* don't save */ CHECK("fade") type = pref_bool, b = p->fade_p; @@ -657,8 +606,6 @@ write_init_file (Display *dpy, 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; @@ -692,7 +639,8 @@ write_init_file (Display *dpy, CHECK("programs") type = pref_str, s = programs; CHECK("pointerHysteresis")type = pref_int, i = p->pointer_hysteresis; - CHECK("overlayStderr") type = pref_bool, b = overlay_stderr_p; + CHECK("font") continue; /* don't save */ + CHECK("overlayStderr") continue; /* don't save */ CHECK("overlayTextBackground") continue; /* don't save */ CHECK("overlayTextForeground") continue; /* don't save */ CHECK("bourneShell") continue; /* don't save */ @@ -759,7 +707,6 @@ write_init_file (Display *dpy, fprintf(out, "\n"); if (visual_name) free(visual_name); - if (stderr_font) free(stderr_font); if (programs) free(programs); if (fclose(out) == 0) @@ -889,9 +836,6 @@ load_init_file (Display *dpy, saver_preferences *p) p->install_cmap_p = get_boolean_resource (dpy, "installColormap", "Boolean"); p->nice_inferior = get_integer_resource (dpy, "nice", "Nice"); p->splash_p = get_boolean_resource (dpy, "splash", "Boolean"); -# ifdef QUAD_MODE - p->quad_p = get_boolean_resource (dpy, "quad", "Boolean"); -# endif p->ignore_uninstalled_p = get_boolean_resource (dpy, "ignoreUninstalledPrograms", "Boolean"); @@ -929,6 +873,7 @@ load_init_file (Display *dpy, saver_preferences *p) p->new_login_command = get_string_resource(dpy, "newLoginCommand", "Command"); p->dialog_theme = get_string_resource(dpy, "dialogTheme", "String"); + p->settings_geom = get_string_resource(dpy, "settingsGeom", "String"); p->auth_warning_slack = get_integer_resource(dpy, "authWarningSlack", "Integer"); @@ -1138,7 +1083,7 @@ format_command (const char *cmd, Bool wrap_p) { int tab = 30; int col = tab; - char *cmd2 = (char *) calloc (1, 2 * (strlen (cmd) + 1)); + char *cmd2 = (char *) calloc (1, 2 * (strlen (cmd) + 10)); const char *in = cmd; char *out = cmd2; while (*in) @@ -1175,6 +1120,14 @@ format_command (const char *cmd, Bool wrap_p) while (out > cmd2 && isspace (out[-1])) *(--out) = 0; + /* In version 6.05, the defaults were changed from "-root" to "--root". + If anything in .xscreensaver still ends with "-root", silently change + it, so that the "Reset to Defaults" button is enabled/disabled as + appropriate, and doesn't think the command differs from the default. + */ + if (out > cmd2+7 && !strcmp (out-6, " -root")) + strcpy (out-6, " --root"); /* malloc had enough slack */ + return cmd2; } @@ -1387,36 +1340,29 @@ stop_the_insanity (saver_preferences *p) 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 ... + /* DPMS settings may be zero, but otherwise, if they < 10 sec or negative, + set them to 2 minutes. */ +# define THROTTLE(FIELD) \ + if (p->FIELD != 0 && ((long) p->FIELD) < 10 * 1000) \ + p->FIELD = 2 * 60 * 60 * 1000 + THROTTLE (dpms_standby); + THROTTLE (dpms_suspend); + THROTTLE (dpms_off); +# undef THROTTLE + + /* If the DPMS settings are non-zero, they must not go backwards: + standby >= timeout (screen saver activation) + suspend >= standby + off >= suspend */ - if (p->dpms_off != 0 && - p->dpms_standby > p->dpms_off) - p->dpms_standby = p->dpms_off; +# define THROTTLE(FIELD,LOWER) \ + if (p->FIELD != 0 && ((long) p->FIELD) < ((long) p->LOWER)) \ + p->FIELD = p->LOWER + THROTTLE (dpms_standby, timeout); + THROTTLE (dpms_suspend, dpms_standby); + THROTTLE (dpms_off, dpms_standby); + THROTTLE (dpms_off, dpms_suspend); +#undef THROTTLE if (p->dpms_standby == 0 && /* if *all* are 0, then DPMS is disabled */ p->dpms_suspend == 0 && @@ -1437,119 +1383,3 @@ stop_the_insanity (saver_preferences *p) if (p->auth_warning_slack < 0) p->auth_warning_slack = 0; if (p->auth_warning_slack > 300) p->auth_warning_slack = 300; } - - -Bool -senescent_p (void) -{ - /* If you are in here because you're planning on disabling this warning - before redistributing my software, please don't. - - I sincerely request that you do one of the following: - - 1: leave this code intact and this warning in place, -OR- - - 2: Remove xscreensaver from your distribution. - - I would seriously prefer that you not distribute my software at all - than that you distribute one version and then never update it for - years. - - I am *constantly* getting email from users reporting bugs that have - been fixed for literally years who have no idea that the software - they are running is years out of date. Yes, it would be great if we - lived in the ideal world where people checked that they were running - the latest release before they report a bug, but we don't. To most - people, "running the latest release" is synonymous with "running the - latest release that my distro packages for me." - - When they even bother to tell me what version they're running, I - say, "That version is three years old!", and they say "But this is - the latest version my distro ships". Then I say, "your distro - sucks", and they say "but I don't know how to compile from source, - herp derp I eat paste", and *everybody* goes away unhappy. - - It wastes an enormous amount of my time, but worse than that, it - does a grave disservice to the users, who are stuck experiencing - bugs that are already fixed! These users think they are running the - latest release, and they are not. They would like to be running the - actual latest release, but they don't know how, because their distro - makes that very difficult for them. It's terrible for everyone, and - kind of makes me regret ever having released this software in the - first place. - - So seriously. I ask that if you're planning on disabling this - obsolescence warning, that you instead just remove xscreensaver from - your distro entirely. Everybody will be happier that way. Check - out gnome-screensaver instead, I understand it's really nice. - - Of course, my license allows you to ignore me and do whatever the - fuck you want, but as the author, I hope you will have the common - courtesy of complying with my request. - - Thank you! - - jwz, 2014, 2016, 2018, 2021. - - PS: In particular, since Debian refuses to upgrade software on any - kind of rational timeline, I have asked that they stop shipping - xscreensaver at all. They have refused. Instead of upgrading the - software, they simply patched out this warning. - - If you want to witness the sad state of the open source peanut - gallery, look no farther than the comments on my blog: - http://jwz.org/b/yiYo - - Many of these people fall back on their go-to argument of, "If it is - legal, it must be right." If you believe in that rhetorical device - then you are a terrible person, and possibly a sociopath. - - There are also the armchair lawyers who say "Well, instead of - *asking* people to do the right thing out of common courtesy, you - should just change your license to prohibit them from acting - amorally." Again, this is the answer of a sociopath, but that aside, - if you devote even a second's thought to this you will realize that - the end result of this would be for distros like Debian to just keep - shipping the last version with the old license and then never - upgrading it again -- which would be the worst possible outcome for - everyone involved, most especially the users. - - Also, some have incorrectly characterized this as a "time bomb". - It is a software update notification, nothing more. A "time bomb" - makes software stop working. This merely alerts the user that the - security-critical software that they are running is dangerously out - of date. - */ - - time_t now = time ((time_t *) 0); /* d */ - struct tm *tm = localtime (&now); /* o */ - const char *s = screensaver_id; /* n */ - char mon[4], year[5]; /* ' */ - int m, y, mrnths; /* t */ - s = strchr (s, ' '); if (!s) abort(); s++; /* */ - s = strchr (s, '('); if (!s) abort(); s++; /* d */ - s = strchr (s, '-'); if (!s) abort(); s++; /* o */ - strncpy (mon, s, 3); /* o */ - mon[3] = 0; /* */ - s = strchr (s, '-'); if (!s) abort(); s++; /* e */ - strncpy (year, s, 4); /* e */ - year[4] = 0; /* t */ - y = atoi (year); /* , */ - if (!strcmp(mon, "Jan")) m = 0; /* */ - else if (!strcmp(mon, "Feb")) m = 1; /* s */ - else if (!strcmp(mon, "Mar")) m = 2; /* t */ - else if (!strcmp(mon, "Apr")) m = 3; /* o */ - else if (!strcmp(mon, "May")) m = 4; /* p */ - else if (!strcmp(mon, "Jun")) m = 5; /* , */ - else if (!strcmp(mon, "Jul")) m = 6; /* */ - else if (!strcmp(mon, "Aug")) m = 7; /* s */ - else if (!strcmp(mon, "Sep")) m = 8; /* t */ - else if (!strcmp(mon, "Oct")) m = 9; /* a */ - else if (!strcmp(mon, "Nov")) m = 10; /* a */ - else if (!strcmp(mon, "Dec")) m = 11; /* a */ - else abort(); /* h */ - mrnths = ((((tm->tm_year + 1900) * 12) + tm->tm_mon) - /* h */ - (y * 12 + m)); /* h */ - /* p */ - return (mrnths >= 17); /* . */ -} diff --git a/driver/remote.c b/driver/remote.c index 123bc28..ee1e6f2 100644 --- a/driver/remote.c +++ b/driver/remote.c @@ -1,4 +1,4 @@ -/* xscreensaver-command, Copyright © 1991-2021 Jamie Zawinski <jwz@jwz.org> +/* xscreensaver-command, Copyright © 1991-2024 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 @@ -80,9 +80,14 @@ reset_dpms_timer (Display *dpy) if (!enabled) goto DONE; - /* Do this even if power == DPMSModeOn to reset the timer */ DPMSForceLevel (dpy, DPMSModeOn); + /* We want to reset the timer inside the X server. DPMSForceLevel doesn't + do that if the monitor was already powered on: it keeps counting down. + But disabling and re-enabling seems to do the trick. */ + DPMSDisable (dpy); + DPMSEnable (dpy); + DONE: XSync (dpy, False); XSetErrorHandler (old_handler); diff --git a/driver/screens.c b/driver/screens.c index dc87e51..7252e5e 100644 --- a/driver/screens.c +++ b/driver/screens.c @@ -1,5 +1,5 @@ /* screens.c --- dealing with RANDR, Xinerama, and VidMode Viewports. - * xscreensaver, Copyright © 1991-2021 Jamie Zawinski <jwz@jwz.org> + * xscreensaver, Copyright © 1991-2022 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 @@ -27,7 +27,7 @@ * 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. + * restarting X. Sometimes this mode is referred to as "ZaphodHeads". * * Everyone hates this way of doing things because of the inability to * move a window from one screen to another without restarting the @@ -43,7 +43,7 @@ * middle of the screen actually spanning the gap between two * monitors.) * - * Xinerama didn't? work with DRI, which means that Xinerama precluded + * Xinerama didn't work with DRI, which means that Xinerama precluded * hardware acceleration in OpenGL programs. Also, screens couldn't * be resized or moved without restarting X. * @@ -51,14 +51,14 @@ * * No longer supported as of XScreenSaver 6. * - * With this extension, the root window can be bigger than the - * monitor. Moving the mouse near the edges of the screen scrolls - * around, like a pan-and-scan movie. There was also a hot-key for - * changing the monitor's resolution (zooming in/out). + * With this extension, the root window could be bigger than the + * monitor. Moving the mouse near the edges of the screen would + * scroll around, like a pan-and-scan movie. There was also a + * hot-key for changing the monitor's resolution (zooming in/out). * - * Trying to combine this with Xinerama crashes the server, so you - * could only use this if you had only a single screen, or were in old - * multi-screen mode. + * Trying to combine this with Xinerama crashed the server, so you + * could ONLY use this if you had only a single screen, or were in + * old multi-screen mode. * * Also, half the time it didn't work at all: it tended to lie about * the size of the rectangle in use. @@ -154,6 +154,27 @@ free_monitors (monitor **monitors) } +static monitor ** +append_monitors (monitor **a, monitor **b) +{ + int na = 0, nb = 0, i; + if (!b) return a; + if (!a) return b; + + while (a[na]) na++; + while (b[nb]) nb++; + + a = (monitor **) realloc (a, sizeof(*a) * (na + nb + 1)); + if (!a) return b; /* If this happens, the end is nigh */ + + for (i = 0; i < nb; i++) + a[na++] = b[i]; + a[na] = 0; + free (b); + return a; +} + + static char * append (char *s1, const char *s2) { @@ -187,12 +208,34 @@ xinerama_scan_monitors (Display *dpy, char **errP) xsi = XineramaQueryScreens (dpy, &nscreens); if (!xsi) return 0; + if (nscreens <= 0) + { + *errP = append (*errP, + "WARNING: Xinerama reported no screens! Ignoring it."); + /* Fall back to basic X11. */ + return 0; + } + monitors = (monitor **) calloc (nscreens + 1, sizeof(*monitors)); - if (!monitors) return 0; + if (!monitors) + { + XFree (xsi); + return 0; + } for (i = 0; i < nscreens; i++) { monitor *m = (monitor *) calloc (1, sizeof (monitor)); + char buf[255]; + if (!m) + { + free_monitors (monitors); + XFree (xsi); + return 0; + } + + sprintf (buf, "Xinerama-%d", i); + monitors[i] = m; m->id = i; m->screen = screen; @@ -200,7 +243,10 @@ xinerama_scan_monitors (Display *dpy, char **errP) m->y = xsi[i].y_org; m->width = xsi[i].width; m->height = xsi[i].height; + m->desc = strdup (buf); } + + XFree (xsi); return monitors; } @@ -222,12 +268,16 @@ randr_scan_monitors (Display *dpy, char **errP) return 0; if (! (major > 1 || (major == 1 && minor >= 2))) - return 0; /* 1.2 ir newer is required */ + return 0; /* 1.2 or newer is required */ /* Add up the virtual screens on each X screen. */ nscreens = 0; for (i = 0; i < ScreenCount (dpy); i++) { + /* This first time around, use XRRGetScreenResources and not + XRRGetScreenResourcesCurrent to make sure we are polling the + actual hardware and populating the server's cache. + */ XRRScreenResources *res = XRRGetScreenResources (dpy, RootWindow (dpy, i)); nscreens += res->noutput; @@ -238,6 +288,7 @@ randr_scan_monitors (Display *dpy, char **errP) { *errP = append (*errP, "WARNING: RANDR reported no screens! Ignoring it."); + /* Fall back to Xinerama or basic X11. */ return 0; } @@ -246,23 +297,50 @@ randr_scan_monitors (Display *dpy, char **errP) for (i = 0, j = 0; i < ScreenCount (dpy); i++) { + /* This second time around, we can use use XRRGetScreenResourcesCurrent + instead of XRRGetScreenResources, since we just polled the hardware + and populated the cache, above. Sometimes XRRGetScreenResources is + slow, and can delay the mapping of the unlock dialog by a full second + or longer. Cutting the number of calls to it in half helps. + */ Screen *screen = ScreenOfDisplay (dpy, i); int k; XRRScreenResources *res = - XRRGetScreenResources (dpy, RootWindowOfScreen (screen)); + XRRGetScreenResourcesCurrent (dpy, RootWindowOfScreen (screen)); + if (!res) + { + free_monitors (monitors); + return 0; + } + 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); + XRROutputInfo *rroi = XRRGetOutputInfo (dpy, res, res->outputs[k]); + RRCrtc crtc = (rroi && rroi->crtc ? rroi->crtc : + rroi && rroi->ncrtc ? rroi->crtcs[0] : 0); XRRCrtcInfo *crtci = (crtc ? XRRGetCrtcInfo(dpy, res, crtc) : 0); + char buf[255]; + + if (!m) + { + free_monitors (monitors); + if (crtci) + XRRFreeCrtcInfo (crtci); + if (rroi) + XRRFreeOutputInfo (rroi); + XRRFreeScreenResources (res); + return 0; + } + + sprintf (buf, "RANDR-%d.%d", i, k); monitors[j] = m; m->screen = screen; m->id = (i * 1000) + j; - m->desc = (rroi->name ? strdup (rroi->name) : 0); + m->desc = (rroi && rroi->name + ? strdup (rroi->name) + : strdup (buf)); if (crtci) { @@ -275,13 +353,14 @@ randr_scan_monitors (Display *dpy, char **errP) m->height = crtci->height; } - if (rroi->connection == RR_Disconnected) + if (!rroi || rroi->connection == RR_Disconnected) m->sanity = S_DISABLED; /* #### do the same for RR_UnknownConnection? */ if (crtci) XRRFreeCrtcInfo (crtci); - XRRFreeOutputInfo (rroi); + if (rroi) + XRRFreeOutputInfo (rroi); } XRRFreeScreenResources (res); } @@ -299,7 +378,9 @@ randr_scan_monitors (Display *dpy, char **errP) if (! ok) { *errP = append (*errP, - "WARNING: RANDR says all screens are 0x0! Ignoring it."); + (i == 0 + ? "WARNING: RANDR reports no screens" + : "WARNING: RANDR says all screens are 0x0")); free_monitors (monitors); monitors = 0; } @@ -323,6 +404,15 @@ basic_scan_monitors (Display *dpy, char **errP) { Screen *screen = ScreenOfDisplay (dpy, i); monitor *m = (monitor *) calloc (1, sizeof (monitor)); + char buf[255]; + if (!m) + { + free_monitors (monitors); + return 0; + } + + sprintf (buf, "Xlib-%d", i); + monitors[i] = m; m->id = i; m->screen = screen; @@ -330,6 +420,7 @@ basic_scan_monitors (Display *dpy, char **errP) m->y = 0; m->width = WidthOfScreen (screen); m->height = HeightOfScreen (screen); + m->desc = strdup (buf); } return monitors; } @@ -359,6 +450,9 @@ randr_versus_xinerama_fight (Display *dpy, monitor **randr_monitors, char **errP) { monitor **xinerama_monitors; + int randr_count = 0; + int xinerama_count = 0; + char buf[1024]; if (!randr_monitors) return 0; @@ -369,22 +463,33 @@ randr_versus_xinerama_fight (Display *dpy, monitor **randr_monitors, if (! monitor_layouts_differ_p (randr_monitors, xinerama_monitors)) { + *errP = append (*errP, "RANDR and Xinerama agree"); 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 */ + + while (randr_monitors[randr_count]) + randr_count++; + while (xinerama_monitors[xinerama_count]) + xinerama_count++; + + if (randr_count == xinerama_count) + strcpy (buf, "RANDR and Xinerama differ"); + else + sprintf (buf, "RANDR screens: %d, Xinerama: %d", + randr_count, xinerama_count); + + if (xinerama_count > randr_count) { - *errP = append (*errP, - "WARNING: RANDR reports 1 screen but Xinerama\n" - " reports multiple. Believing Xinerama."); + strcat (buf, "; believing Xinerama"); + *errP = append (*errP, buf); free_monitors (randr_monitors); return xinerama_monitors; } else { - *errP = append (*errP, /* This is "normal" now, I guess. */ - "RANDR and Xinerama report different screen layouts"); + strcat (buf, "; believing RANDR"); + *errP = append (*errP, buf); free_monitors (xinerama_monitors); return randr_monitors; } @@ -445,43 +550,6 @@ debug_scan_monitors (Display *dpy, char **errP) #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 */ - - monitor ** scan_monitors (Display *dpy) { @@ -506,10 +574,31 @@ scan_monitors (Display *dpy) if (! monitors) monitors = basic_scan_monitors (dpy, &err); -# ifdef QUAD_MODE - if (p->quad_p) - monitors = quadruple (monitors, p->debug_p, &err); -# endif + if (monitors) + { + int count = 0; + int good_count = 0; + + check_monitor_sanity (monitors); + while (monitors[count]) + { + if (monitors[count]->sanity == S_SANE) + good_count++; + count++; + } + + if (good_count == 0) + { + monitor **m2 = basic_scan_monitors (dpy, &err); + err = append(err, "No usable screens! Falling back to root window"); + /* Append the fallback monitors to the old, bad ones so that + describe_monitor_layout() still describes the monitors that + were rejected. Don't call check_monitor_sanity(m2) to leave + the new ones marked as sane. + */ + monitors = append_monitors (monitors, m2); + } + } if (monitors && *monitors && err) monitors[0]->err = err; @@ -667,7 +756,8 @@ monitor_layouts_differ_p (monitor **a, monitor **b) (*a)->x != (*b)->x || (*a)->y != (*b)->y || (*a)->width != (*b)->width || - (*a)->height != (*b)->height) + (*a)->height != (*b)->height || + (*a)->sanity != (*b)->sanity) return True; a++; b++; @@ -711,9 +801,10 @@ describe_monitor_layout (monitor **monitors) if (monitors && *monitors && monitors[0]->err) /* deferred error msg */ { char *token = strtok (monitors[0]->err, "\n"); + const char *b = blurb(); while (token) { - fprintf (stderr, "%s: %s\n", blurb(), token); + fprintf (stderr, "%s: %s\n", b, token); token = strtok (0, "\n"); } free (monitors[0]->err); @@ -725,12 +816,12 @@ describe_monitor_layout (monitor **monitors) else { int i; - fprintf (stderr, "%s: screens in use: %d\n", blurb(), good_count); + 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", + 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); @@ -738,13 +829,13 @@ describe_monitor_layout (monitor **monitors) } if (bad_count > 0) { - fprintf (stderr, "%s: rejected screens: %d\n", blurb(), bad_count); + 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", + 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); @@ -777,10 +868,8 @@ describe_monitor_layout (monitor **monitors) 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()); + "%s: implausible single screen aspect ratio %.2f\n", + blurb(), + monitors[0]->width / (double) monitors[0]->height); } } diff --git a/driver/screensaver-properties.desktop.in b/driver/screensaver-properties.desktop.in deleted file mode 100644 index 9cc17e6..0000000 --- a/driver/screensaver-properties.desktop.in +++ /dev/null @@ -1,8 +0,0 @@ -[Desktop Entry] -Exec=xscreensaver-settings -Icon=xscreensaver -Terminal=false -_Name=Screensaver -_Comment=Change screensaver properties -Type=Application -Categories=Settings;DesktopSettings;Security;X-XFCE; diff --git a/driver/stderr.c b/driver/stderr.c deleted file mode 100644 index 84fa697..0000000 --- a/driver/stderr.c +++ /dev/null @@ -1,560 +0,0 @@ -/* 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 index 2b3453d..39b1ddd 100644 --- a/driver/subprocs.c +++ b/driver/subprocs.c @@ -1,5 +1,5 @@ /* subprocs.c --- choosing, spawning, and killing screenhacks. - * xscreensaver, Copyright © 1991-2021 Jamie Zawinski <jwz@jwz.org> + * xscreensaver, Copyright © 1991-2024 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 @@ -53,6 +53,7 @@ #include "yarandom.h" #include "visual.h" /* for id_to_visual() */ #include "atoms.h" +#include "screenshot.h" enum job_status { @@ -65,6 +66,8 @@ enum job_status { element will be removed. */ }; +#define EXEC_FAILED_EXIT_STATUS -33 + struct screenhack_job { char *name; pid_t pid; @@ -461,6 +464,16 @@ xt_sigterm_handler (XtPointer data, XtSignalId *id) fprintf (stderr, "%s: %s: unblanking\n", blurb(), signal_name (sigterm_received)); + /* We are in the process of shutting down and are about to exit, + so don't accidentally re-launch hacks. */ + si->terminating_p = True; + + if (si->watchdog_id) + { + XtRemoveTimeOut (si->watchdog_id); + si->watchdog_id = 0; + } + /* Kill before unblanking, to stop drawing as soon as possible. */ for (i = 0; i < si->nscreens; i++) { @@ -570,7 +583,11 @@ describe_dead_child (saver_info *si, pid_t kid, int wait_status, /* Treat exit code as a signed 8-bit quantity. */ if (exit_status & 0x80) exit_status |= ~0xFF; - sprintf (msg, _("crashed with status %d"), exit_status); + if (exit_status == EXEC_FAILED_EXIT_STATUS) + strcpy (msg, _("is not installed")); + else + sprintf (msg, _("crashed with status %d"), exit_status); + if (p->verbose_p) fprintf (stderr, "%s: %d: child pid %lu (%s) exited abnormally" @@ -767,7 +784,7 @@ fork_and_exec (saver_screen_info *ssi, const char *command) exec_command (p->shell, command, p->nice_inferior); /* If that returned, we were unable to exec the subprocess. */ - exit (1); /* exits child fork */ + exit (EXEC_FAILED_EXIT_STATUS); /* exits child fork */ break; default: /* parent */ @@ -831,7 +848,11 @@ spawn_screenhack (saver_screen_info *ssi) "not launching a hack\n", blurb(), ssi->number); ssi->current_hack = -1; - /* Hooray, this doesn't actually clear the window if it was OpenGL. */ + /* Hooray, this doesn't actually clear the window if it was OpenGL. + And some X servers apparently ignore XClearWindow if the monitor is + powered off, meaning when the monitor powers back on, the stale bits + that we just tried to erase are still in the frame buffer. + */ XClearWindow (si->dpy, ssi->screensaver_window); /* Even though we aren't launching a hack, do launch the cycle timer, @@ -947,6 +968,44 @@ spawn_screenhack (saver_screen_info *ssi) goto AGAIN; } + /* Install screenshot property on window. Must be after + select_visual_of_hack() which might replace the window. */ + if (ssi->screenshot) + screenshot_save (si->dpy, ssi->screensaver_window, ssi->screenshot); + + if (getuid() == (uid_t) 0 || geteuid() == (uid_t) 0) + /* Prior to XScreenSaver 6, if running as root, we would change the + effective uid to the user "nobody" or "daemon" or "noaccess", + but even that was just encouraging bad behavior. Don't log in + as root. */ + { + fprintf (stderr, "%s: %d: running as root: not launching hacks.\n", + blurb(), ssi->number); + screenhack_obituary (ssi, "", "XScreenSaver: Don't log in as root."); + goto DONE; + } + + if (! si->best_gl_visuals || + ! si->best_gl_visuals[ssi->real_screen_number]) + { + /* Lots of things malfunction in mysterious ways if only *part* of + the XScreenSaver application is installed. That some distros + still insist on dividing XScreenSaver into multiple + bafflingly-named sub-packages that can be omitted willy nilly + causes repeated, predicted, time-wasting and extremely irritating + problems for everybody, while solving no problems whatsoever. + + Here's your car, but let's make it trivially easy for everyone to + accidentally omit the seat belts and distributor cap, because + some weirdo once wanted that in 2002. + + Install all of XScreenSaver or none. + */ + screenhack_obituary (ssi, "", + "No GL visuals: the xscreensaver-gl* packages are required."); + goto DONE; + } + forked = fork_and_exec (ssi, hack->command); switch ((int) forked) { @@ -978,6 +1037,13 @@ spawn_screenhack (saver_screen_info *ssi) store_saver_status (si); /* store current hack numbers */ + /* If there is no hack running, clear the window, in case there are + stale bits left over because the server chose to ignore our earlier + call to XClearWindow while the monitor was powered down. + */ + if (!ssi->pid && !ssi->error_dialog) + XClearWindow (si->dpy, ssi->screensaver_window); + /* Now that the hack has launched, queue a timer to cycle it. */ if (!si->demoing_p && p->cycle) { @@ -1054,7 +1120,13 @@ kill_screenhack (saver_screen_info *ssi) kill_job (si, ssi->pid, SIGTERM); ssi->pid = 0; - /* Hooray, this doesn't actually clear the window if it was OpenGL. */ + /* Do not clear ssi->current_hack here, see watchdog_timer(). */ + + /* Hooray, this doesn't actually clear the window if it was OpenGL. + And some X servers apparently ignore XClearWindow if the monitor is + powered off, meaning when the monitor powers back on, the stale bits + that we just tried to erase are still in the frame buffer. + */ XClearWindow (si->dpy, ssi->screensaver_window); } @@ -1157,17 +1229,19 @@ get_best_gl_visual (saver_info *si, Screen *screen) sprintf (buf, "%s: running %s", blurb(), av[0]); perror (buf); } - exit (1); /* exits fork */ + exit (EXEC_FAILED_EXIT_STATUS); /* exits fork */ break; } default: { int result = 0; int wait_status = 0; + int exit_status = EXEC_FAILED_EXIT_STATUS; pid_t pid = -1; FILE *f; unsigned long v = 0; char c; + int i = 0; make_job (forked, 0, av[0]); /* Bookkeeping for SIGCHLD */ @@ -1179,8 +1253,13 @@ get_best_gl_visual (saver_info *si, Screen *screen) close (out); /* don't need this one */ *buf = 0; - if (! fgets (buf, sizeof(buf)-1, f)) - *buf = 0; + do { + errno = 0; + if (! fgets (buf, sizeof(buf)-1, f)) + *buf = 0; + } while (errno == EINTR && /* fgets might fail due to SIGCHLD. */ + i++ < 1000); /* And just in case. */ + fclose (f); if (! si->prefs.verbose_p) @@ -1195,6 +1274,16 @@ get_best_gl_visual (saver_info *si, Screen *screen) fprintf (stderr, "%s: waitpid(%ld) => %ld\n", blurb(), (long) forked, (long) pid); + exit_status = WEXITSTATUS (wait_status); + /* Treat exit code as a signed 8-bit quantity. */ + if (exit_status & 0x80) exit_status |= ~0xFF; + + if (exit_status == EXEC_FAILED_EXIT_STATUS) + { + fprintf (stderr, "%s: %s is not installed\n", blurb(), av[0]); + return 0; + } + if (1 == sscanf (buf, "0x%lx %c", &v, &c)) result = (int) v; diff --git a/driver/test-grab.c b/driver/test-grab.c index 65313a1..fe7b0e2 100644 --- a/driver/test-grab.c +++ b/driver/test-grab.c @@ -1,5 +1,5 @@ /* test-uid.c --- playing with grabs. - * xscreensaver, Copyright © 1999-2021 Jamie Zawinski <jwz@jwz.org> + * xscreensaver, Copyright © 1999-2022 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 @@ -80,9 +80,18 @@ main (int argc, char **argv) else if (!strcmp (argv[i], "-mouse-async") || !strcmp (argv[i], "-pointer-async")) mouse_sync_p = False; + else if (!strcmp (argv[i], "-sync")) + kbd_sync_p = mouse_sync_p = True; + else if (!strcmp (argv[i], "-async")) + kbd_sync_p = mouse_sync_p = False; else { fprintf (stderr, "%s: unknown option: %s\n", blurb(), oa); + fprintf (stderr, "usage: %s " + "[--sync | --async]" + "\n\t\t [--kbd] [--kbd-sync | --kbd-async]" + "\n\t\t [--mouse] [--mouse-sync | --mouse-async]\n", + progname); exit (1); } } diff --git a/driver/test-randr.c b/driver/test-randr.c index 1f88d99..8acfb98 100644 --- a/driver/test-randr.c +++ b/driver/test-randr.c @@ -1,5 +1,5 @@ /* test-randr.c --- playing with the Resize And Rotate extension. - * xscreensaver, Copyright © 2004-2021 Jamie Zawinski <jwz@jwz.org> + * xscreensaver, Copyright © 2004-2022 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 @@ -43,6 +43,51 @@ ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error) } +static void +query_outputs (Display *dpy, int screen) +{ +# ifdef HAVE_RANDR_12 + int major = -1, minor = -1; + + if (!XRRQueryVersion(dpy, &major, &minor)) + abort(); + + if (major > 1 || (major == 1 && minor >= 2)) + { + int j; + XRRScreenResources *res = + XRRGetScreenResources (dpy, RootWindow (dpy, screen)); + 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_Connected ? "connected" : + rroi->connection == RR_Disconnected ? "disconnected" : + rroi->connection == RR_UnknownConnection ? "unknown" : + "ERROR"), + (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 */ +} + + int main (int argc, char **argv) { @@ -217,42 +262,7 @@ main (int argc, char **argv) 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 */ + query_outputs (dpy, i); } if (major > 0) @@ -272,7 +282,7 @@ main (int argc, char **argv) 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); + blurb()); while (1) { XEvent event; @@ -285,34 +295,38 @@ main (int argc, char **argv) int screen = XRRRootToScreen (dpy, xrr_event->window); fprintf (stderr, "%s: screen %d: RRScreenChangeNotify event\n", - progname, screen); + blurb(), screen); fprintf (stderr, "%s: screen %d: old size: \t%d x %d\n", - progname, screen, + blurb(), 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); + fprintf (stderr, "%s: screen %d: old root: \t%d x %d\t0x%lx\n", + blurb(), screen, + xgwa[screen].width, xgwa[screen].height, + (unsigned long) w[screen]); XRRUpdateConfiguration (&event); XSync (dpy, False); fprintf (stderr, "%s: screen %d: new size: \t%d x %d\n", - progname, screen, + blurb(), 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"); + fprintf (stderr, "%s: screen %d: new root:\t%d x %d\t0x%lx\n", + blurb(), screen, + xgwa[screen].width, xgwa[screen].height, + (unsigned long) w[screen]); + + for (i = 0; i < nscreens; i++) + query_outputs (dpy, i); } else { - fprintf (stderr, "%s: event %d\n", progname, event.type); + fprintf (stderr, "%s: event %d\n", blurb(), event.type); } } } diff --git a/driver/test-screens.c b/driver/test-screens.c index 141b7ad..1e0189a 100644 --- a/driver/test-screens.c +++ b/driver/test-screens.c @@ -83,7 +83,7 @@ test (int testnum, const char *screens, const char *desired) { 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); + sprintf (out + strlen(out), "@%ld", (long) m->screen); } else strcpy (out, failstr (m->sanity)); diff --git a/driver/test-xinput.c b/driver/test-xinput.c index e3a6487..93336ec 100644 --- a/driver/test-xinput.c +++ b/driver/test-xinput.c @@ -1,5 +1,5 @@ /* test-xinput.c --- playing with the XInput2 extension. - * xscreensaver, Copyright © 2021 Jamie Zawinski <jwz@jwz.org> + * xscreensaver, Copyright © 2021-2022 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 @@ -8,6 +8,26 @@ * 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 verbosely prints out all events received from Xlib and the XInput2 + * extension. + * + * --grab, --grab-kbd, --grab-mouse + * Read events while grabbed. The grab is released after 15 seconds. + * + * --sync, --async + * Async grabs queue up events and deliver them all at release. + * + * Real X11 multi-head ("Zaphod Heads") has different behaviors than + * single-screen displays with multiple monitors (Xinerama, XRANDR). + * To test X11 multi-head or different visual depths on Raspberry Pi + * you have to do it inside a nested server: + * + * apt install xserver-xephyr + * sudo rm /tmp/.X1-lock /tmp/.X11-unix/X1 + * export DISPLAY=:0 + * Xephyr :1 -ac -screen 1280x720 -screen 640x480x8 & + * export DISPLAY=:1 */ #ifdef HAVE_CONFIG_H @@ -29,17 +49,54 @@ #include <X11/Xproto.h> #include <X11/extensions/XInput2.h> -#include "blurb.h" +/* #include "blurb.h" */ +extern const char *progname; +extern int verbose_p; + #include "xinput.h" char *progclass = "XScreenSaver"; Bool debug_p = True; +#undef countof +#define countof(x) (sizeof((x))/sizeof((*x))) + +#define RST "\x1B[0m" +#define RED "\x1B[31m" +#define GRN "\x1B[32m" +#define YEL "\x1B[33m" +#define BLU "\x1B[34m" +#define MAG "\x1B[35m" +#define CYN "\x1B[36m" +#define WHT "\x1B[37m" +#define BLD "\x1B[1m" +#define UL "\x1B[4m" + +static const char * +blurb(void) +{ + static char buf[255] = { 0 }; + struct tm *tm; + struct timeval tv; +# ifdef GETTIMEOFDAY_TWO_ARGS + struct timezone tzp; + gettimeofday (&tv, &tzp); +# else + gettimeofday (&tv); +# endif + tm = localtime (&tv.tv_sec); + sprintf (buf, "%s: %02d:%02d:%02d.%03lu", progname, + tm->tm_hour, tm->tm_min, tm->tm_sec, + (unsigned long) (tv.tv_usec / 1000)); + return buf; +} + + static void ungrab_timer (XtPointer closure, XtIntervalId *id) { Display *dpy = (Display *) closure; - fprintf (stderr, "\n%s: ungrabbing\n\n", blurb()); + fprintf (stdout, "\n%s: ungrabbing\n\n", blurb()); XUngrabKeyboard (dpy, CurrentTime); XUngrabPointer (dpy, CurrentTime); } @@ -64,6 +121,503 @@ grab_string (int status) } +typedef enum { + ETYPE, ETIME, ESERIAL, EROOT, EWIN, ESUB, EX, EY, EXR, EYR, + EFLAGS, ESTATE, EKEYCODE, EKEY, EHINT, ESSCR, + EEND +} coltype; + +typedef struct { + const char *name; + int width; + enum { TSTR, TSTRL, TINT, TUINT, THEX, TTIME } type; +} columns; + + +static const columns cols[] = { + /* ETYPE */ { "Type", 17, TSTRL }, + /* ETIME */ { "Timestamp", 11, TTIME }, + /* ESERIAL */ { "Serial", 9, TUINT }, + /* EROOT */ { "Root", 8, THEX }, + /* EWIN */ { "Window", 8, THEX }, + /* ESUB */ { "Subwin", 8, THEX }, + /* EX */ { "X", 11, TINT }, + /* EY */ { "Y", 11, TINT }, + /* EXR */ { "X Root", 11, TINT }, + /* EYR */ { "Y Root", 11, TINT }, + /* EFLAGS */ { "Flags", 8, THEX }, + /* ESTATE */ { "State", 8, THEX }, + /* EKEYCODE */ { "Code", 5, THEX }, + /* EKEY */ { "Key", 3, TSTR }, + /* EHINT */ { "Hint", 9, TINT }, + /* ESSCR */ { "Same", 4, TINT }, +}; + +static struct { Time t; XEvent e; } history[100] = { 0, }; + +static void +print_header (void) +{ + char buf[1024] = { 0 }; + char *s = buf; + coltype t; + + if (countof(cols) != EEND) abort(); + for (t = 0; t < EEND; t++) + { + if (t > 0) *s++ = ' '; + if (cols[t].type == TSTRL) + sprintf (s, "%-*s", cols[t].width, cols[t].name); + else + sprintf (s, "%*s", cols[t].width, cols[t].name); + s += strlen(s); + } + *s++ = '\n'; + for (t = 0; t < EEND; t++) + { + int i; + if (t > 0) *s++ = ' '; + for (i = 0; i < cols[t].width; i++) + *s++ = '='; + } + fprintf (stdout, "\n%s\n", buf); +} + + +static void +print_field (char *out, coltype t, void *val) +{ + const columns *col = &cols[t]; + if (! val) + { + sprintf (out, "%*s", col->width, "-"); + return; + } + + switch (col->type) { + case TSTRL: sprintf (out, "%-*s", col->width, (char *) val); break; + case TSTR: sprintf (out, "%*s", col->width, (char *) val); break; + case TINT: sprintf (out, "%*d", col->width, *((int *) val)); break; + case TUINT: sprintf (out, "%*u", col->width, *((unsigned int *) val)); break; + case THEX: sprintf (out, "%*X", col->width, *((unsigned int *) val)); break; + case TTIME: sprintf (out, "%*.3f", col->width, + (double) (*((unsigned int *) val)) / 1000.0); break; + default: abort(); break; + } +} + + +static void +push_history (Time t, XEvent *xev) +{ + memmove (history + 1, history, sizeof(history) - sizeof(*history)); + history[0].t = t; + history[0].e = *xev; +} + + +static void +asciify (char *c, int L) +{ + if (!strcmp(c,"\n")) strcpy(c, "\\n"); + else if (!strcmp(c,"\r")) strcpy(c, "\\r"); + else if (!strcmp(c,"\t")) strcpy(c, "\\t"); + else if (!strcmp(c," ")) strcpy(c, "SPC"); + else if (L == 1 && c[0] < ' ') + sprintf (c, "^%c", c[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; +} + + +/* Whether the field's value is sane. */ +static Bool +validate_field (Display *dpy, coltype t, void *val) +{ + const char *err = 0; + if (!val) return True; + + switch (t) { + case ETIME: + { + Time t = *(Time *) val; + int i; + for (i = 0; i < countof(history); i++) + { + if (t && t == history[i].t) + { + err = "DUP TIME"; + break; + } + else if (t && t < history[i].t) + { + err = "TIME TRAVEL"; + break; + } + } + } + break; + + case ESERIAL: + { + unsigned long s = *(unsigned long *) val; + int i; + for (i = 0; i < countof(history); i++) + { + if (s && s == history[i].e.xany.serial) + { + err = "DUP SERIAL"; + break; + } + else if (s && s < history[i].e.xany.serial) + { + err = "TIME TRAVEL"; + break; + } + } + } + break; + + case EROOT: case EWIN: case ESUB: + { + Window w = *(Window *) val; + if (w) + { + XWindowAttributes xgwa; + error_handler_hit_p = False; + XSync (dpy, False); + XSetErrorHandler (ignore_all_errors_ehandler); + XSync (dpy, False); + XGetWindowAttributes (dpy, w, &xgwa); + XSync (dpy, False); + if (error_handler_hit_p) + { + err = "BAD WINDOW"; + break; + } + } + } + + case EX: case EY: case EXR: case EYR: + { + int i = *(int *) val; + if (i < 0 || i > 0xFFFF) + { + err = "BAD COORD"; + break; + } + } + break; + + case EFLAGS: + { + /* "The only defined flag is XIKeyRepeat for XI_KeyPress events." */ + int i = *(int *) val; + if (i != 0 && 1 != XIKeyRepeat) + { + err = "BAD BOOL"; + break; + } + } + break; + + case ESTATE: + { + unsigned int i = *(int *) val; + + if (i & ~(ShiftMask | LockMask | ControlMask | Mod1Mask | + Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask | + Button1Mask | Button2Mask | Button3Mask | + Button4Mask | Button5Mask)) + { + err = "BAD MODS"; + break; + } + } + break; + + case EKEYCODE: + { + int i = *(int *) val; + if (i < 0 || i > 0xFFFF) + { + err = "BAD KEYCODE"; + break; + } + } + break; + + case EHINT: case ESSCR: + { + int i = *(int *) val; + if (i != 0 && 1 != 1) + { + err = "BAD BOOL"; + break; + } + } + break; + + default: + break; + } + + if (err) return False; + return True; +} + + +static void +print_event (Display *dpy, XEvent *xev, int xi_opcode) +{ + XIRawEvent *re = 0; + XIDeviceEvent *de = 0; + void *fields[EEND] = { 0, }; + + switch (xev->xany.type) { + case KeyPress: + case KeyRelease: + { + static XComposeStatus compose = { 0, }; + KeySym keysym = 0; + static char c[100]; + int n; + + fields[ETYPE] = (void *) + (xev->xany.type == KeyPress ? "KeyPress" : "KeyRelease"); + fields[ETIME] = &xev->xkey.time; + fields[ESERIAL] = &xev->xkey.serial; + fields[EWIN] = &xev->xkey.window; + fields[EROOT] = &xev->xkey.root; + fields[ESUB] = &xev->xkey.subwindow; + fields[EX] = &xev->xkey.x; + fields[EY] = &xev->xkey.y; + fields[EXR] = &xev->xkey.x_root; + fields[EYR] = &xev->xkey.y_root; + fields[ESTATE] = &xev->xkey.state; + fields[EKEYCODE] = &xev->xkey.keycode; + + n = XLookupString (&xev->xkey, c, sizeof(c)-1, &keysym, &compose); + c[n] = 0; + asciify (c, n); + fields[EKEY] = c; + } + break; + case ButtonPress: + case ButtonRelease: + { + fields[ETYPE] = (void *) + (xev->xany.type == ButtonPress ? "ButtonPress" : "ButtonRelease"); + fields[ETIME] = &xev->xbutton.time; + fields[ESERIAL] = &xev->xbutton.serial; + fields[EWIN] = &xev->xbutton.window; + fields[EROOT] = &xev->xbutton.root; + fields[ESUB] = &xev->xbutton.subwindow; + fields[EX] = &xev->xbutton.x; + fields[EY] = &xev->xbutton.y; + fields[EXR] = &xev->xbutton.x_root; + fields[EYR] = &xev->xbutton.y_root; + fields[ESTATE] = &xev->xbutton.state; + fields[EKEYCODE] = &xev->xbutton.button; + } + break; + case MotionNotify: + { + fields[ETYPE] = (void *) "MotionNotify"; + fields[ETIME] = &xev->xmotion.time; + fields[ESERIAL] = &xev->xmotion.serial; + fields[EWIN] = &xev->xmotion.window; + fields[EROOT] = &xev->xmotion.root; + fields[ESUB] = &xev->xmotion.subwindow; + fields[EX] = &xev->xmotion.x; + fields[EY] = &xev->xmotion.y; + fields[EXR] = &xev->xmotion.x_root; + fields[EYR] = &xev->xmotion.y_root; + fields[ESTATE] = &xev->xmotion.state; + fields[EHINT] = &xev->xmotion.is_hint; + } + break; + case GenericEvent: + break; + case EnterNotify: + case LeaveNotify: + break; + default: + { + static char ee[100]; + sprintf (ee, "EVENT %2d", xev->xany.type); + fields[ETYPE] = ⅇ + fields[ESERIAL] = &xev->xany.serial; + fields[EWIN] = &xev->xany.window; + } + break; + } + + if (xev->xcookie.type != GenericEvent || + xev->xcookie.extension != xi_opcode) + goto DONE; /* not an XInput event */ + if (!xev->xcookie.data) + XGetEventData (dpy, &xev->xcookie); + if (!xev->xcookie.data) + { + fields[ETYPE] = "BAD XIINPUT"; + goto DONE; + } + + re = xev->xcookie.data; + + if (xev->xany.serial != re->serial) abort(); + + switch (xev->xcookie.evtype) { + case XI_RawKeyPress: fields[ETYPE] = "XI_RawKeyPress"; break; + case XI_RawKeyRelease: fields[ETYPE] = "XI_RawKeyRelease"; break; + case XI_RawButtonPress: fields[ETYPE] = "XI_RawBtnPress"; break; + case XI_RawButtonRelease: fields[ETYPE] = "XI_RawBtnRelease"; break; + case XI_RawMotion: fields[ETYPE] = "XI_RawMotion"; break; + case XI_RawTouchBegin: fields[ETYPE] = "XI_RawTouchBegin"; break; + case XI_RawTouchEnd: fields[ETYPE] = "XI_RawTouchEnd"; break; + case XI_RawTouchUpdate: fields[ETYPE] = "XI_RawTouchUpd"; break; + case XI_KeyPress: fields[ETYPE] = "XI_KeyPress"; break; + case XI_KeyRelease: fields[ETYPE] = "XI_KeyRelease"; break; + case XI_ButtonPress: fields[ETYPE] = "XI_BtnPress"; break; + case XI_ButtonRelease: fields[ETYPE] = "XI_BtnRelease"; break; + case XI_Motion: fields[ETYPE] = "XI_Motion"; break; + case XI_TouchBegin: fields[ETYPE] = "XI_TouchBegin"; break; + case XI_TouchEnd: fields[ETYPE] = "XI_TouchEnd"; break; + case XI_TouchUpdate: fields[ETYPE] = "XI_TouchUpd"; break; + default: + { + static char ee[100]; + sprintf (ee, "XI EVENT %2d", xev->xany.type); + fields[ETYPE] = ⅇ + } + break; + } + + fields[ESERIAL] = &xev->xany.serial; + fields[ETIME] = &re->time; + fields[EFLAGS] = &re->flags; + fields[EKEYCODE] = &re->detail; + + /* Only these events are XIDeviceEvents. The "XI_Raw" variants are not. + */ + switch (xev->xcookie.evtype) { + case XI_KeyPress: + case XI_KeyRelease: + case XI_ButtonPress: + case XI_ButtonRelease: + case XI_Motion: + case XI_TouchBegin: + case XI_TouchEnd: + case XI_TouchUpdate: + de = (XIDeviceEvent *) re; + fields[EROOT] = &de->root; + fields[EWIN] = &de->event; + fields[ESUB] = &de->child; + fields[EX] = &de->event_x; + fields[EY] = &de->event_y; + fields[EXR] = &de->root_x; + fields[EYR] = &de->root_y; + fields[ESTATE] = &de->mods.effective; + break; + default: break; + } + + switch (xev->xcookie.evtype) { + case XI_RawKeyPress: + case XI_RawKeyRelease: + case XI_KeyPress: + case XI_KeyRelease: + { + XKeyEvent xkey = { 0, }; + static XComposeStatus compose = { 0, }; + KeySym keysym = 0; + static char c[100]; + int n; + xkey.type = ((xev->xcookie.evtype == XI_RawKeyPress || + xev->xcookie.evtype == XI_KeyPress) + ? KeyPress : KeyRelease); + xkey.serial = xev->xany.serial; + xkey.display = xev->xany.display; + xkey.window = 0; /* xev->xany.window; */ + xkey.keycode = re->detail; + + if (de) /* Available for non-raw events only */ + { + xkey.root = de->root; + xkey.subwindow = 0; /* de->child; */ + xkey.time = de->time; + xkey.state = de->mods.effective; + } + + n = XLookupString (&xkey, c, sizeof(c)-1, &keysym, &compose); + c[n] = 0; + asciify (c, n); + fields[EKEY] = c; + } + break; + case XI_RawButtonPress: + case XI_RawButtonRelease: + case XI_ButtonPress: + case XI_ButtonRelease: + { + static char c[10]; + sprintf (c, "b%d", re->detail); + fields[EKEY] = &c; + } + break; + default: + break; + } + + DONE: + + if (fields[ETYPE]) + { + char buf[10240]; + char *s = buf; + coltype t; + *s = 0; + for (t = 0; t < EEND; t++) + { + Bool ok = validate_field (dpy, t, fields[t]); + + if (t > 0) { *s++ = ' '; *s = 0; } + + if (!ok) + { + strcat (s, BLD); + strcat (s, RED); + s += strlen(s); + } + + print_field (s, t, fields[t]); + s += strlen(s); + + if (!ok) + { + strcat (s, RST); + s += strlen(s); + } + } + fprintf (stdout, "%s\n", buf); + } + + { + Time t = (fields[ETIME] ? * (Time *) fields[ETIME] : 0); + push_history (t, xev); + } +} + + int main (int argc, char **argv) { @@ -73,11 +627,14 @@ main (int argc, char **argv) int xi_opcode; Bool grab_kbd_p = False; Bool grab_mouse_p = False; - Bool mouse_sync_p = True; - Bool kbd_sync_p = True; + Bool mouse_sync_p = False; + Bool kbd_sync_p = False; int i; + char *s; progname = argv[0]; + s = strrchr (progname, '/'); + if (s) progname = s+1; for (i = 1; i < argc; i++) { @@ -104,9 +661,18 @@ main (int argc, char **argv) else if (!strcmp (argv[i], "-mouse-async") || !strcmp (argv[i], "-pointer-async")) mouse_sync_p = False; + else if (!strcmp (argv[i], "-sync")) + kbd_sync_p = mouse_sync_p = True; + else if (!strcmp (argv[i], "-async")) + kbd_sync_p = mouse_sync_p = False; else { fprintf (stderr, "%s: unknown option: %s\n", blurb(), oa); + fprintf (stderr, "usage: %s " + "[--grab] [--sync | --async]" + "\n\t\t [--grab-kbd] [--kbd-sync | --kbd-async]" + "\n\t\t [--grab-mouse] [--mouse-sync | --mouse-async]\n", + progname); exit (1); } } @@ -119,6 +685,10 @@ main (int argc, char **argv) if (! init_xinput (dpy, &xi_opcode)) exit (1); + fprintf (stdout, "\n%s: Make your window wide. " + "Bogus values are " BLD RED "RED" RST ".\n", + blurb()); + if (grab_kbd_p || grab_mouse_p) { int timeout = 15; @@ -135,12 +705,12 @@ main (int argc, char **argv) (kbd_sync_p ? GrabModeSync : GrabModeAsync), CurrentTime); if (status == GrabSuccess) - fprintf (stderr, "%s: grabbed keyboard (%s, %s)\n", blurb(), + fprintf (stdout, "%s: grabbed keyboard (%s, %s)\n", blurb(), (mouse_sync_p ? "sync" : "async"), (kbd_sync_p ? "sync" : "async")); else { - fprintf (stderr, "%s: failed to grab keyboard (%s, %s): %s\n", + fprintf (stdout, "%s: failed to grab keyboard (%s, %s): %s\n", blurb(), (mouse_sync_p ? "sync" : "async"), (kbd_sync_p ? "sync" : "async"), @@ -162,12 +732,12 @@ main (int argc, char **argv) (kbd_sync_p ? GrabModeSync : GrabModeAsync), w, cursor, CurrentTime); if (status == GrabSuccess) - fprintf (stderr, "%s: grabbed mouse (%s, %s)\n", blurb(), + fprintf (stdout, "%s: grabbed mouse (%s, %s)\n", blurb(), (mouse_sync_p ? "sync" : "async"), (kbd_sync_p ? "sync" : "async")); else { - fprintf (stderr, "%s: failed to grab mouse (%s, %s): %s\n", + fprintf (stdout, "%s: failed to grab mouse (%s, %s): %s\n", blurb(), (mouse_sync_p ? "sync" : "async"), (kbd_sync_p ? "sync" : "async"), @@ -176,129 +746,17 @@ main (int argc, char **argv) } } - fprintf (stderr, "%s: ungrabbing in %d seconds\n", blurb(), timeout); + fprintf (stdout, "%s: ungrabbing in %d seconds\n", blurb(), timeout); XtAppAddTimeOut (app, 1000 * timeout, ungrab_timer, (XtPointer) dpy); } + print_header(); while (1) { - XEvent xev; - XIRawEvent *re; - + XEvent xev = { 0, }; XtAppNextEvent (app, &xev); XtDispatchEvent (&xev); - - switch (xev.xany.type) { - case KeyPress: - case KeyRelease: - { - static XComposeStatus compose = { 0, }; - KeySym keysym = 0; - char c[100]; - int n; - n = XLookupString (&xev.xkey, c, sizeof(c)-1, &keysym, &compose); - c[n] = 0; - fprintf (stderr, "%s: X11 Key%s %02x %02x %s \"%s\"\n", blurb(), - (xev.xkey.type == KeyPress ? "Press " : "Release"), - xev.xkey.keycode, xev.xkey.state, - XKeysymToString (keysym), c); - } - break; - case ButtonPress: - case ButtonRelease: - fprintf (stderr, "%s: X11 Button%s %d %d\n", blurb(), - (xev.xany.type == ButtonPress ? "Press " : "Release"), - xev.xbutton.button, xev.xbutton.state); - break; - case MotionNotify: - fprintf (stderr, "%s: X11 MotionNotify %4d, %-4d\n", - blurb(), xev.xmotion.x_root, xev.xmotion.y_root); - break; - case GenericEvent: - break; - case EnterNotify: - case LeaveNotify: - break; - default: - fprintf (stderr, "%s: X11 event %d on 0x%lx\n", - blurb(), xev.xany.type, xev.xany.window); - break; - } - - if (xev.xcookie.type != GenericEvent || - xev.xcookie.extension != xi_opcode) - continue; /* not an XInput event */ - if (!xev.xcookie.data) - XGetEventData (dpy, &xev.xcookie); - if (!xev.xcookie.data) - continue; /* Bogus XInput event */ - - re = xev.xcookie.data; - switch (xev.xcookie.evtype) { - case XI_RawKeyPress: - case XI_RawKeyRelease: - { - /* Fake up an XKeyEvent in order to call XKeysymToString(). */ - XEvent ev2; - Bool ok = xinput_event_to_xlib (xev.xcookie.evtype, - (XIDeviceEvent *) re, - &ev2); - if (!ok) - fprintf (stderr, "%s: unable to translate XInput2 event\n", - blurb()); - else - { - static XComposeStatus compose = { 0, }; - KeySym keysym = 0; - char c[100]; - int n; - n = XLookupString (&ev2.xkey, c, sizeof(c)-1, &keysym, &compose); - c[n] = 0; - fprintf (stderr, "%s: XI_RawKey%s %02x %02x %s \"%s\"\n", - blurb(), - (ev2.xkey.type == KeyPress ? "Press " : "Release"), - ev2.xkey.keycode, ev2.xkey.state, - XKeysymToString (keysym), c); - } - } - break; - case XI_RawButtonPress: - case XI_RawButtonRelease: - fprintf (stderr, "%s: XI_RawButton%s %d\n", blurb(), - (re->evtype == XI_RawButtonPress ? "Press " : "Release"), - re->detail); - break; - case XI_RawMotion: - { - Window root_ret, child_ret; - int root_x, root_y; - int win_x, win_y; - unsigned int mask; - XQueryPointer (dpy, DefaultRootWindow (dpy), - &root_ret, &child_ret, &root_x, &root_y, - &win_x, &win_y, &mask); - fprintf (stderr, "%s: XI_RawMotion %4d, %-4d %7.02f, %-7.02f\n", - blurb(), - root_x, root_y, - re->raw_values[0], re->raw_values[1]); - } - break; - case XI_RawTouchBegin: - fprintf (stderr, "%s: XI_RawTouchBegin\n", blurb()); - break; - case XI_RawTouchEnd: - fprintf (stderr, "%s: XI_RawTouchEnd", blurb()); - break; - case XI_RawTouchUpdate: - fprintf (stderr, "%s: XI_RawTouchUpdate", blurb()); - break; - - default: - fprintf (stderr, "%s: XInput unknown event %d\n", blurb(), - xev.xcookie.evtype); - break; - } - + print_event (dpy, &xev, xi_opcode); XFreeEventData (dpy, &xev.xcookie); } diff --git a/driver/types.h b/driver/types.h index d8e4880..f11ad3b 100644 --- a/driver/types.h +++ b/driver/types.h @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright © 1993-2021 Jamie Zawinski <jwz@jwz.org> +/* xscreensaver, Copyright © 1993-2023 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 @@ -64,10 +64,6 @@ struct saver_preferences { 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; @@ -109,6 +105,7 @@ struct saver_preferences { char *load_url_command; /* How one loads URLs. */ char *new_login_command; /* Command for the "New Login" button. */ char *dialog_theme; /* Color scheme on the unlock dialog */ + char *settings_geom; /* Saved positions of the settings windows */ int auth_warning_slack; /* Don't warn about login failures if they all happen within this many seconds of @@ -143,6 +140,8 @@ struct saver_info { Bool demoing_p; /* Whether we are demoing a single hack (without UI.) */ Bool emergency_p; /* Restarted because of a crash */ + Bool terminating_p; /* In the process of shutting down */ + XtIntervalId watchdog_id; /* Timer to implement `prefs.watchdog */ int selection_mode; /* Set to -1 if the NEXT ClientMessage has just @@ -192,7 +191,7 @@ struct saver_screen_info { real root window. */ unsigned long black_pixel; /* Black, allocated from `cmap'. */ Window error_dialog; /* Error message about crashed savers */ - + Pixmap screenshot; /* Saved screen image before activation */ XtIntervalId cycle_id; /* Timer to implement `prefs.cycle' */ time_t cycle_at; /* When cycle_id will fire */ @@ -211,7 +210,6 @@ extern void load_init_file (Display *, saver_preferences *); extern int write_init_file (Display *, saver_preferences *, const char *version_string, Bool verbose_p); -extern Bool senescent_p (void); char *make_hack_name (Display *, const char *shell_command); diff --git a/driver/vms_axp.opt b/driver/vms_axp.opt deleted file mode 100644 index 04d465d..0000000 --- a/driver/vms_axp.opt +++ /dev/null @@ -1,5 +0,0 @@ -[-.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 deleted file mode 100644 index 25dd1f1..0000000 --- a/driver/vms_axp_12.opt +++ /dev/null @@ -1,5 +0,0 @@ -[-.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 deleted file mode 100644 index 65bec03..0000000 --- a/driver/vms_decc.opt +++ /dev/null @@ -1,5 +0,0 @@ -[-.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 deleted file mode 100644 index fdd9a80..0000000 --- a/driver/vms_decc_12.opt +++ /dev/null @@ -1,5 +0,0 @@ -[-.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 index c9cdf9d..6f67e13 100644 --- a/driver/windows.c +++ b/driver/windows.c @@ -1,5 +1,5 @@ /* windows.c --- turning the screen black; dealing with visuals, virtual roots. - * xscreensaver, Copyright © 1991-2021 Jamie Zawinski <jwz@jwz.org> + * xscreensaver, Copyright © 1991-2022 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 @@ -38,6 +38,7 @@ #include "atoms.h" #include "visual.h" #include "screens.h" +#include "screenshot.h" #include "fade.h" #include "resources.h" #include "xft.h" @@ -218,7 +219,7 @@ initialize_screensaver_window_1 (saver_screen_info *ssi) if (ssi->error_dialog) { - XDestroyWindow (si->dpy, ssi->error_dialog); + defer_XDestroyWindow (si->app, si->dpy, ssi->error_dialog); ssi->error_dialog = 0; } @@ -260,7 +261,7 @@ initialize_screensaver_window_1 (saver_screen_info *ssi) fprintf (stderr, "%s: someone horked our saver window (0x%lx)! Recreating it...\n", blurb(), (unsigned long) horked_window); - XDestroyWindow (si->dpy, horked_window); + defer_XDestroyWindow (si->app, si->dpy, horked_window); } if (p->verbose_p > 1) @@ -435,6 +436,16 @@ blank_screen (saver_info *si) initialize_screensaver_window (si); sync_server_dpms_settings (si->dpy, p); + /* Save a screenshot. Must be before fade-out. */ + for (i = 0; i < si->nscreens; i++) + { + saver_screen_info *ssi = &si->screens[i]; + if (ssi->screenshot) + XFreePixmap (si->dpy, ssi->screenshot); + ssi->screenshot = + screenshot_grab (si->dpy, ssi->screensaver_window, False, p->verbose_p); + } + if (p->fade_p && !si->demoing_p && !si->emergency_p) @@ -687,7 +698,7 @@ select_visual (saver_screen_info *ssi, const char *visual_name) raise_window (ssi); /* Now we can destroy the old window without horking our grabs. */ - XDestroyWindow (si->dpy, old_w); + defer_XDestroyWindow (si->app, si->dpy, old_w); if (p->verbose_p > 1) fprintf (stderr, "%s: %d: destroyed old saver window 0x%lx\n", @@ -724,7 +735,6 @@ update_screen_layout (saver_info *si) free_monitors (si->monitor_layout); si->monitor_layout = monitors; - check_monitor_sanity (si->monitor_layout); while (monitors[count]) { @@ -740,13 +750,13 @@ update_screen_layout (saver_info *si) calloc (sizeof(*si->screens), si->ssi_count); } - if (si->ssi_count <= good_count) + if (si->ssi_count < 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)); + realloc (si->screens, sizeof(*si->screens) * count); + memset (si->screens + si->ssi_count, 0, + sizeof(*si->screens) * (count - si->ssi_count)); + si->ssi_count = count; } if (! si->screens) abort(); @@ -787,11 +797,7 @@ update_screen_layout (saver_info *si) # ifndef DEBUG_MULTISCREEN { saver_preferences *p = &si->prefs; - if (p->debug_p -# ifdef QUAD_MODE - && !p->quad_p -# endif - ) + if (p->debug_p) ssi->width /= 2; } # endif @@ -814,7 +820,7 @@ cycle_timer (XtPointer closure, XtIntervalId *id) if (ssi->error_dialog) { - XDestroyWindow (si->dpy, ssi->error_dialog); + defer_XDestroyWindow (si->app, si->dpy, ssi->error_dialog); ssi->error_dialog = 0; } @@ -884,7 +890,7 @@ screenhack_obituary (saver_screen_info *ssi, */ cmap = ssi->cmap ? ssi->cmap : DefaultColormapOfScreen (ssi->screen); window = ssi->error_dialog; - if (window) XDestroyWindow (si->dpy, window); + if (window) defer_XDestroyWindow (si->app, si->dpy, window); attrs.override_redirect = True; attrs.background_pixel = ssi->black_pixel; attrs.border_pixel = ssi->black_pixel; @@ -912,7 +918,10 @@ screenhack_obituary (saver_screen_info *ssi, gcv.line_width = bw; gc = XCreateGC (si->dpy, window, GCForeground | GCLineWidth, &gcv); - sprintf (buf, "\"%.100s\" %.100s", name, error); + if (name && *name) + sprintf (buf, "\"%.100s\" %.100s", name, error); + else + sprintf (buf, "%.100s", error); XftTextExtentsUtf8 (si->dpy, font, (FcChar8 *) buf, strlen(buf), &overall); x = (ssi->width - overall.width) / 2; @@ -961,6 +970,7 @@ watchdog_timer (XtPointer closure, XtIntervalId *id) { saver_info *si = (saver_info *) closure; saver_preferences *p = &si->prefs; + Bool running_p, on_p, terminating_p; /* If the DPMS settings on the server have changed, change them back to what ~/.xscreensaver says they should be. */ @@ -971,8 +981,10 @@ watchdog_timer (XtPointer closure, XtIntervalId *id) raise_windows (si); - if (any_screenhacks_running_p (si) && - !monitor_powered_on_p (si->dpy)) + running_p = any_screenhacks_running_p (si); + on_p = monitor_powered_on_p (si->dpy); + terminating_p = si->terminating_p; + if (running_p && !on_p) { int i; if (si->prefs.verbose_p) @@ -981,6 +993,31 @@ watchdog_timer (XtPointer closure, XtIntervalId *id) blurb()); for (i = 0; i < si->nscreens; i++) kill_screenhack (&si->screens[i]); + /* Do not clear current_hack here. */ + } + else if (terminating_p) + { + /* If we are in the process of shutting down and are about to exit, + don't re-launch anything just because the monitor came back on. */ + } + else if (!running_p && on_p) + { + /* If the hack number is set but no hack is running, it is because the + hack was killed when the monitor powered off, above. This assumes + that kill_screenhack() clears pid but not current_hack. Start the + hack going again. The cycle_timer will also do this (unless "cycle" + is 0) but watchdog_timer runs more frequently. + */ + if (si->nscreens > 0 && si->screens[0].current_hack >= 0) + { + int i; + if (si->prefs.verbose_p) + fprintf (stderr, + "%s: monitor has powered back on; re-launching hacks\n", + blurb()); + for (i = 0; i < si->nscreens; i++) + spawn_screenhack (&si->screens[i]); + } } /* Re-schedule this timer. The watchdog timer defaults to a bit less diff --git a/driver/xinput.c b/driver/xinput.c index 3b21db0..f865f42 100644 --- a/driver/xinput.c +++ b/driver/xinput.c @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright © 1991-2021 Jamie Zawinski <jwz@jwz.org> +/* xscreensaver, Copyright © 1991-2022 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 @@ -9,6 +9,49 @@ * implied warranty. */ +/* For our purposes, here are the salient facts about the XInput2 extension: + + - Available by default as of X11R7 in 2009. + + - We receive events from the XIRawEvent family while other processes + have the keyboard and mouse grabbed (XI_RawKeyPress, etc.) + The entire xscreensaver-auth security model hinges on this fact. + + - We cannot receive events from the XIDeviceEvent family (XI_KeyPress, + etc.) so while XIDeviceEvents contain many fields that we would like + to have, don't be fooled, those are not available. XIRawEvents such + as XI_RawKeyPress cannot be cast to XIDeviceEvent. + + - XI_RawButtonPress, XI_RawButtonRelease and XI_RawMotion contain no + usable position information. The non-raw versions do, but we don't + receive those, so we have to read that info via XQueryPointer. + + - XI_RawKeyPress and XI_RawKeyRelease contain the keycode (in 'detail') + but no modifier info, so, again, to be able to tell "a" from "A" we + need to call XQueryPointer. + + - XI_RawKeyPress does not auto-repeat the way Xlib KeyPress does. + + - Raw events have no window, subwindow, etc. + + - Event serial numbers are not unique. + + - If *this* process has the keyboard and mouse grabbed, it receives: + + - KeyPress, KeyRelease + - ButtonPress, ButtonRelease, MotionNotify + - XI_RawButtonPress, XI_RawButtonRelease, XI_RawMotion (doubly reported) + + - If this process *does not* have keyboard and mouse grabbed, it receives: + + - XI_RawKeyPress, XI_RawKeyRelease + - XI_RawButtonPress, XI_RawButtonRelease, XI_RawMotion + + The closest thing to actual documentation on XInput2 seems to be a series + of blog posts by Peter Hutterer. There's basically nothing about it on + www.x.org. https://who-t.blogspot.com/2009/07/xi2-recipes-part-4.html + */ + #ifdef HAVE_CONFIG_H # include "config.h" #endif @@ -35,7 +78,6 @@ extern Bool debug_p; Bool init_xinput (Display *dpy, int *opcode_ret) { - int nscreens = ScreenCount (dpy); XIEventMask evmasks[1]; unsigned char mask1[(XI_LASTEVENT + 7)/8]; int major, minor; @@ -64,6 +106,10 @@ init_xinput (Display *dpy, int *opcode_ret) memset (mask1, 0, sizeof(mask1)); + /* The XIRawEvent family of events are delivered to us while someone else + holds a grab, but the non-raw versions are not. In fact, attempting to + select XI_KeyPress, etc. results in a BadAccess X error. + */ XISetMask (mask1, XI_RawMotion); XISetMask (mask1, XI_RawKeyPress); XISetMask (mask1, XI_RawKeyRelease); @@ -73,19 +119,18 @@ init_xinput (Display *dpy, int *opcode_ret) XISetMask (mask1, XI_RawTouchUpdate); XISetMask (mask1, XI_RawTouchEnd); - /* If we use XIAllDevices instead, we get double events. */ + /* If we use XIAllDevices instead, we get duplicate events. */ evmasks[0].deviceid = XIAllMasterDevices; evmasks[0].mask_len = sizeof(mask1); evmasks[0].mask = mask1; - for (i = 0; i < nscreens; i++) + /* Only select events on screen 0 -- if we select on each screen, + we get duplicate events. */ + if (XISelectEvents (dpy, RootWindow (dpy, 0), evmasks, countof(evmasks)) + != Success) { - Window root = RootWindow (dpy, i); - if (XISelectEvents (dpy, root, evmasks, countof(evmasks)) != Success) - { - fprintf (stderr, "%s: XISelectEvents failed\n", blurb()); - return False; - } + fprintf (stderr, "%s: XISelectEvents failed\n", blurb()); + return False; } XFlush (dpy); @@ -127,33 +172,10 @@ xinput_event_to_xlib (int evtype, XIDeviceEvent *in, XEvent *out) Bool ok = False; int root_x = 0, root_y = 0; + int win_x = 0, win_y = 0; unsigned int mods = 0; + Window subw = 0; - /* The closest thing to actual documentation on XInput2 seems to be a series - of blog posts by Peter Hutterer. There's basically nothing about it on - www.x.org. In http://who-t.blogspot.com/2009/07/xi2-recipes-part-4.html - he says: - - "XIDeviceEvent [...] contains the state of the modifier keys [...] - The base modifiers are the ones currently pressed, latched the ones - pressed until a key is pressed that's configured to unlatch it (e.g. - some shift-capslock interactions have this behaviour) and finally - locked modifiers are the ones permanently active until unlocked - (default capslock behaviour in the US layout). The effective modifiers - are a bitwise OR of the three above - which is essentially equivalent - to the modifiers state supplied in the core protocol events." - - However, I'm seeing random noise in the various XIDeviceEvent.mods fields. - Nonsensical values like base = 0x6045FB3D. So, let's poll the actual - modifiers from XQueryPointer. This can race: maybe the modifier state - changed between when the server generated the keyboard event, and when - we receive it and poll. However, if an actual human is typing and - releasing their modifier keys on such a tight timeframe... that's - probably already not going well. - - I'm also seeing random noise in the event_xy and root_xy fields in - motion events. So just always use XQueryPointer. - */ switch (evtype) { case XI_RawKeyPress: case XI_RawKeyRelease: @@ -161,12 +183,12 @@ xinput_event_to_xlib (int evtype, XIDeviceEvent *in, XEvent *out) case XI_RawButtonRelease: case XI_RawMotion: { - Window root_ret, child_ret; + Window root_ret; int win_x, win_y; int i; for (i = 0; i < ScreenCount (dpy); i++) /* query on correct screen */ if (XQueryPointer (dpy, RootWindow (dpy, i), - &root_ret, &child_ret, &root_x, &root_y, + &root_ret, &subw, &root_x, &root_y, &win_x, &win_y, &mods)) break; } @@ -178,12 +200,12 @@ xinput_event_to_xlib (int evtype, XIDeviceEvent *in, XEvent *out) case XI_RawKeyRelease: out->xkey.type = (evtype == XI_RawKeyPress ? KeyPress : KeyRelease); out->xkey.display = in->display; - out->xkey.window = in->event; out->xkey.root = in->root; - out->xkey.subwindow = in->child; + out->xkey.window = subw; + out->xkey.subwindow = subw; out->xkey.time = in->time; - out->xkey.x = root_x; - out->xkey.y = root_y; + out->xkey.x = win_x; + out->xkey.y = win_y; out->xkey.x_root = root_x; out->xkey.y_root = root_y; out->xkey.state = mods; @@ -195,12 +217,12 @@ xinput_event_to_xlib (int evtype, XIDeviceEvent *in, XEvent *out) out->xbutton.type = (evtype == XI_RawButtonPress ? ButtonPress : ButtonRelease); out->xbutton.display = in->display; - out->xbutton.window = in->event; out->xbutton.root = in->root; - out->xbutton.subwindow = in->child; + out->xbutton.window = subw; + out->xbutton.subwindow = subw; out->xbutton.time = in->time; - out->xbutton.x = root_x; - out->xbutton.y = root_y; + out->xbutton.x = win_x; + out->xbutton.y = win_y; out->xbutton.x_root = root_x; out->xbutton.y_root = root_y; out->xbutton.state = mods; @@ -210,17 +232,34 @@ xinput_event_to_xlib (int evtype, XIDeviceEvent *in, XEvent *out) case XI_RawMotion: out->xmotion.type = MotionNotify; out->xmotion.display = in->display; - out->xmotion.window = in->event; out->xmotion.root = in->root; - out->xmotion.subwindow = in->child; + out->xmotion.window = subw; + out->xmotion.subwindow = subw; out->xmotion.time = in->time; - out->xmotion.x = root_x; - out->xmotion.y = root_y; + out->xmotion.x = win_x; + out->xmotion.y = win_y; out->xmotion.x_root = root_x; out->xmotion.y_root = root_y; out->xmotion.state = mods; ok = True; break; + case XI_RawTouchBegin: + case XI_RawTouchEnd: + out->xbutton.type = (evtype == XI_RawTouchBegin + ? ButtonPress : ButtonRelease); + out->xbutton.display = in->display; + out->xbutton.root = in->root; + out->xbutton.window = subw; + out->xbutton.subwindow = subw; + out->xbutton.time = in->time; + out->xbutton.x = win_x; + out->xbutton.y = win_y; + out->xbutton.x_root = root_x; + out->xbutton.y_root = root_y; + out->xbutton.state = mods; + out->xbutton.button = in->detail; + ok = True; + break; default: break; } @@ -230,14 +269,14 @@ xinput_event_to_xlib (int evtype, XIDeviceEvent *in, XEvent *out) static void -print_kbd_event (XEvent *xev, XComposeStatus *compose, Bool x11_p) +print_kbd_event (XKeyEvent *xkey, XComposeStatus *compose, Bool x11_p) { if (debug_p) /* Passwords show up in plaintext! */ { KeySym keysym = 0; char c[100]; char M[100], *mods = M; - int n = XLookupString (&xev->xkey, c, sizeof(c)-1, &keysym, compose); + int n = XLookupString (xkey, c, sizeof(c)-1, &keysym, compose); const char *ks = keysym ? XKeysymToString (keysym) : "NULL"; c[n] = 0; if (*c == '\n') strcpy (c, "\\n"); @@ -245,31 +284,31 @@ print_kbd_event (XEvent *xev, XComposeStatus *compose, Bool x11_p) else if (*c == '\t') strcpy (c, "\\t"); *mods = 0; - if (xev->xkey.state & ShiftMask) strcat (mods, "-Sh"); - if (xev->xkey.state & LockMask) strcat (mods, "-Lk"); - if (xev->xkey.state & ControlMask) strcat (mods, "-C"); - if (xev->xkey.state & Mod1Mask) strcat (mods, "-M1"); - if (xev->xkey.state & Mod2Mask) strcat (mods, "-M2"); - if (xev->xkey.state & Mod3Mask) strcat (mods, "-M3"); - if (xev->xkey.state & Mod4Mask) strcat (mods, "-M4"); - if (xev->xkey.state & Mod5Mask) strcat (mods, "-M5"); + if (xkey->state & ShiftMask) strcat (mods, "-Sh"); + if (xkey->state & LockMask) strcat (mods, "-Lk"); + if (xkey->state & ControlMask) strcat (mods, "-C"); + if (xkey->state & Mod1Mask) strcat (mods, "-M1"); + if (xkey->state & Mod2Mask) strcat (mods, "-M2"); + if (xkey->state & Mod3Mask) strcat (mods, "-M3"); + if (xkey->state & Mod4Mask) strcat (mods, "-M4"); + if (xkey->state & Mod5Mask) strcat (mods, "-M5"); if (*mods) mods++; if (!*mods) strcat (mods, "0"); fprintf (stderr, "%s: %s 0x%02X %s %s \"%s\"\n", blurb(), (x11_p - ? (xev->xkey.type == KeyPress + ? (xkey->type == KeyPress ? "X11 KeyPress " : "X11 KeyRelease ") - : (xev->xkey.type == KeyPress + : (xkey->type == KeyPress ? "XI_RawKeyPress " : "XI_RawKeyRelease")), - xev->xkey.keycode, mods, ks, c); + xkey->keycode, mods, ks, c); } else /* Log only that the KeyPress happened. */ { fprintf (stderr, "%s: X11 Key%s\n", blurb(), - (xev->xkey.type == KeyPress ? "Press " : "Release")); + (xkey->type == KeyPress ? "Press " : "Release")); } } @@ -284,7 +323,7 @@ print_xinput_event (Display *dpy, XEvent *xev, const char *desc) case KeyRelease: { static XComposeStatus compose = { 0, }; - print_kbd_event (xev, &compose, True); + print_kbd_event (&xev->xkey, &compose, True); } break; @@ -321,7 +360,7 @@ print_xinput_event (Display *dpy, XEvent *xev, const char *desc) { /* Fake up an XKeyEvent in order to call XKeysymToString(). */ XEvent ev2; - Bool ok = xinput_event_to_xlib (xev->xcookie.evtype, + Bool ok = xinput_event_to_xlib (re->evtype, (XIDeviceEvent *) re, &ev2); if (!ok) @@ -329,7 +368,7 @@ print_xinput_event (Display *dpy, XEvent *xev, const char *desc) else { static XComposeStatus compose = { 0, }; - print_kbd_event (&ev2, &compose, False); + print_kbd_event (&ev2.xkey, &compose, False); } break; } @@ -340,9 +379,18 @@ print_xinput_event (Display *dpy, XEvent *xev, const char *desc) case XI_RawButtonPress: case XI_RawButtonRelease: - fprintf (stderr, "%s: XI RawButton%s %d\n", blurb(), - (re->evtype == XI_RawButtonPress ? "Press " : "Release"), - re->detail); + { + Window root_ret, child_ret; + int root_x, root_y; + int win_x, win_y; + unsigned int mask; + XQueryPointer (dpy, DefaultRootWindow (dpy), + &root_ret, &child_ret, &root_x, &root_y, + &win_x, &win_y, &mask); + fprintf (stderr, "%s: XI _RawButton%s %4d, %-4d\n", blurb(), + (re->evtype == XI_RawButtonPress ? "Press " : "Release"), + root_x, root_y); + } break; case XI_RawMotion: diff --git a/driver/xinput.h b/driver/xinput.h index 2ee20ee..75ef275 100644 --- a/driver/xinput.h +++ b/driver/xinput.h @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright © 1991-2021 Jamie Zawinski <jwz@jwz.org> +/* xscreensaver, Copyright © 1991-2022 Jamie Zawinski <jwz@jwz.org> * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that diff --git a/driver/xscreensaver-auth.c b/driver/xscreensaver-auth.c index 4344d20..a65cc0d 100644 --- a/driver/xscreensaver-auth.c +++ b/driver/xscreensaver-auth.c @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright © 1991-2021 Jamie Zawinski <jwz@jwz.org> +/* xscreensaver, Copyright © 1991-2022 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 @@ -167,6 +167,66 @@ oom_assassin_immunity (void) #endif /* HAVE_PROC_OOM */ +#ifndef HAVE_DPMS_EXTENSION +# define dpms_init (dpy) /** */ + +#else /* HAVE_DPMS_EXTENSION */ + +# include <X11/Xproto.h> +# include <X11/extensions/dpms.h> + +static int +ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error) +{ + return 0; +} + +/* When XScreenSaver first launches, power on the monitor and disable DPMS. + The DPMS settings will be re-written when 'xscreensaver-gfx' launches for + the first time to blank the screen, and will be re-enabled if that is what + is configured in ~/.xscreensaver. + + Without this, if the server's default DPMS timeouts are different than, and + less than, the .xscreensaver file's 'timeout' and 'dpmsStandby' the monitor + will already be powered down when 'xscreensaver-gfx' launches for the first + time, and it will never change them, as it can't touch those settings if + the monitor is already powered off. + + It would be better for us to call sync_server_dpms_settings() here, but + that requires a full 'saver_preferences' struct. Once 'xscreensaver-gfx' + is launched, it will correct things. + + The only way this behaves oddly is if the user has set their 'dpmsStandby' + to less than their 'timeout', but that would be weird, right? That + probably shouldn't even be permitted (though currently it is). + + This is in 'xscreensaver-auth' because that's run at startup with either + --splash or --init, long before 'xscreensaver-gfx' is run for the first + time; and putting this code into 'xscreensaver' would violate the principle + of linking only the bare minimum into the daemon itself. + */ +static void +dpms_init (Display *dpy) +{ + int ev, err; + XErrorHandler old_handler; + XSync (dpy, False); + old_handler = XSetErrorHandler (ignore_all_errors_ehandler); + XSync (dpy, False); + + if (DPMSQueryExtension (dpy, &ev, &err)) + { + DPMSForceLevel (dpy, DPMSModeOn); + DPMSDisable (dpy); + } + XSetScreenSaver (dpy, 0, 0, 0, 0); + + XSync (dpy, False); + XSetErrorHandler (old_handler); +} +#endif /* HAVE_DPMS_EXTENSION */ + + int main (int argc, char **argv) { @@ -175,7 +235,7 @@ main (int argc, char **argv) Widget root_widget; char *dpy_str = getenv ("DISPLAY"); Bool xsync_p = False; - Bool splash_p = False; + int splash_p = 0; Bool init_p = False; int i; @@ -225,13 +285,20 @@ main (int argc, char **argv) dpy_str = argv[++i]; if (!dpy_str) goto HELP; } + else if (!strcmp (argv[i], "-ver") || + !strcmp (argv[i], "-vers") || + !strcmp (argv[i], "-version")) + { + fprintf (stderr, "%s\n", screensaver_id+4); + exit (1); + } else if (!strcmp (argv[i], "-sync") || !strcmp (argv[i], "-synch") || !strcmp (argv[i], "-synchronize") || !strcmp (argv[i], "-synchronise")) xsync_p = True; else if (!strcmp (argv[i], "-splash")) - splash_p = True; + splash_p++; /* 0, 1 or 2 */ else if (!strcmp (argv[i], "-init")) init_p = True; else if (!strcmp (argv[i], "-h") || !strcmp (argv[i], "-help")) @@ -266,19 +333,16 @@ main (int argc, char **argv) } # ifdef HAVE_PROC_OOM - if (splash_p || init_p) + if (splash_p == 1 || init_p) oom_assassin_immunity (); # endif if (!splash_p && !init_p) lock_priv_init (); - if (!splash_p && init_p) - exit (0); - disavow_privileges (); - if (!splash_p) + if (!splash_p && !init_p) lock_init (); /* Setting the locale is necessary for XLookupString to return multi-byte @@ -316,9 +380,17 @@ main (int argc, char **argv) if (xsync_p) XSynchronize (dpy, True); init_xscreensaver_atoms (dpy); - if (splash_p) + if (splash_p == 1 || init_p) + dpms_init (dpy); + + if (!splash_p && init_p) + { + exit (0); + } + else if (splash_p) { - xscreensaver_splash (root_widget); + /* Settings button is disabled with --splash --splash */ + xscreensaver_splash (root_widget, splash_p > 1); exit (0); } else if (xscreensaver_auth ((void *) root_widget, diff --git a/driver/xscreensaver-auth.man b/driver/xscreensaver-auth.man index 1655602..6da3740 100644 --- a/driver/xscreensaver-auth.man +++ b/driver/xscreensaver-auth.man @@ -3,7 +3,7 @@ xscreensaver - extensible screen saver and screen locking framework .SH SYNOPSIS .B xscreensaver-auth -[\-display \fIhost:display.screen\fP] +[\-\-display \fIhost:display.screen\fP] [\-\-version] .SH DESCRIPTION The .BR xscreensaver (1) @@ -15,7 +15,7 @@ Do not run this program directly. .BR xscreensaver\-gfx (MANSUFFIX), .BR xscreensaver\-systemd (MANSUFFIX). .SH COPYRIGHT -Copyright \(co 2021 by Jamie Zawinski. +Copyright \(co 2021-2022 by Jamie Zawinski. Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that diff --git a/driver/xscreensaver-command.c b/driver/xscreensaver-command.c index 0dab564..1e09ba8 100644 --- a/driver/xscreensaver-command.c +++ b/driver/xscreensaver-command.c @@ -1,4 +1,4 @@ -/* xscreensaver-command, Copyright © 1991-2021 Jamie Zawinski <jwz@jwz.org> +/* xscreensaver-command, Copyright © 1991-2023 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 @@ -186,8 +186,6 @@ main (int argc, char **argv) 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, "-version", L)) cmd = &XA_SCREENSAVER_VERSION; else if (!strncmp (s, "-time", L)) cmd = &XA_SCREENSAVER_STATUS; @@ -221,9 +219,9 @@ main (int argc, char **argv) USAGE("bad option", cmdstr); else if (arg == 0) { - /* SELECT must have a non-zero argument. */ - if (cmd == &XA_SELECT) - USAGE("bad option", cmdstr); + /* SELECT and DEMO must have a non-zero argument. */ + if (cmd == &XA_SELECT || cmd == &XA_DEMO) + USAGE("missing hack number", cmdstr); } else /* arg > 0 */ { @@ -232,39 +230,10 @@ main (int argc, char **argv) USAGE("bad option", cmdstr); } - - - /* -demo with no arguments launches xscreensaver-settings. - -demo N sends a message to the daemon demo that hack number. - */ - if (cmd == &XA_PREFS || - (cmd == &XA_DEMO && arg == 0)) - { - char buf [512]; - char *new_argv[] = { "xscreensaver-settings", 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); - } - - + /* Include the parent's pid with --deactivate so that the logs can give a + hint as to what other process is preventing the screen from blanking. */ + if (cmd == &XA_DEACTIVATE) + arg = (long) getppid(); if (!dpyname) dpyname = (char *) getenv ("DISPLAY"); diff --git a/driver/xscreensaver-command.man b/driver/xscreensaver-command.man index 6d52d2a..c0301d4 100644 --- a/driver/xscreensaver-command.man +++ b/driver/xscreensaver-command.man @@ -24,39 +24,23 @@ xscreensaver-command - control a running xscreensaver process The \fIxscreensaver\-command\fP program controls a running .BR xscreensaver (1) daemon. - .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 \-\-quiet Only print output if an error occurs. - .TP 8 .B \-\-verbose Opposite of \-\-quiet. 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 -.nf -.sp - sleep 5 ; xscreensaver-command -activate -.sp -.fi -to be sure that you have time to take your hand off the mouse before -the screensaver comes on. (Because if you jiggle the mouse, xscreensaver -will notice, and deactivate.) - .TP 8 .B \-\-deactivate This tells xscreensaver to pretend that there has just been user activity. @@ -67,32 +51,27 @@ 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\-\-settings\fP option is -probably an easier way to accomplish that.) - +repeatedly executing \-\-next will cause the xscreensaver process to invoke +each graphics demo sequentially. (Though using the \fI\-\-settings\fP option +is probably an easier way to accomplish that.) .TP 8 .B \-\-prev This is like \fI\-\-next\fP, but cycles in the other direction. - .TP 8 -.B \-\-select \fInumber\fP +.B \-\-select\fP \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 \-\-lock Tells the running xscreensaver process to lock the screen immediately. @@ -104,39 +83,31 @@ Note that locking doesn't work unless the \fIxscreensaver\fP process is running as you. See .BR xscreensaver (1) for details. - .TP 8 .B \-\-suspend Like \fI\-\-activate\fP, but ignores \fIlockTimeout\fP and immediately powers off the screen without fading out. This is intended to be run just after your laptop's lid is closed, and just before the CPU halts, to lock things down quickly. - .TP 8 .B \-\-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. - +never use \fIkill -9\fP with \fIxscreensaver\fP. That can leave things in an +inconsistent state, and you may need to log out to repair the damage. .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 \-\-time Prints the time at which the screensaver last activated or deactivated (roughly, how long the user has been idle or non-idle: but not quite, since it only tells you when the screen became blanked or un-blanked.) - .TP 8 .B \-\-watch Prints a line each time the screensaver changes state: when the screen @@ -188,7 +159,6 @@ the \fI\-\-watch\fP command and reacts accordingly: Note that LOCK might come either with or without a preceding BLANK (depending on whether the lock-timeout is non-zero), so the above program keeps track of both of them. - .TP 8 .B \-\-version Prints the version of xscreensaver that is currently running on the display: @@ -196,7 +166,6 @@ that is, the actual version number of the running xscreensaver background process, rather than the version number of xscreensaver-command. (To see the version number of \fIxscreensaver-command\fP itself, use the \fI\-\-help\fP option.) - .SH STOPPING GRAPHICS If xscreensaver is running, but you want it to stop running screen hacks (e.g., if you are logged in remotely, and you want the console to remain @@ -246,7 +215,7 @@ and related tools can always be found at https://www.jwz.org/xscreensaver/ .BR xscreensaver\-settings (1), .BR xset (1) .SH COPYRIGHT -Copyright \(co 1992-2021 by Jamie Zawinski. +Copyright \(co 1992-2022 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 @@ -255,6 +224,6 @@ 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. +Jamie Zawinski <jwz@jwz.org>. 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 deleted file mode 100644 index ad0095d..0000000 --- a/driver/xscreensaver-demo.glade2.in +++ /dev/null @@ -1,3136 +0,0 @@ -<?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 "xscreensaver-extras" and -"xscreensaver-gl-extras" packages are not installed.</property> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_CENTER</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - </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 >></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 <<</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 deleted file mode 100644 index 3dfe894..0000000 --- a/driver/xscreensaver-demo.glade2p +++ /dev/null @@ -1,19 +0,0 @@ -<?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 deleted file mode 100644 index 7da5fea..0000000 --- a/driver/xscreensaver-demo.man +++ /dev/null @@ -1,402 +0,0 @@ -.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 deleted file mode 100755 index 2a6d345..0000000 --- a/driver/xscreensaver-getimage-desktop +++ /dev/null @@ -1,174 +0,0 @@ -#!/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 deleted file mode 100644 index 1974525..0000000 --- a/driver/xscreensaver-getimage-desktop.man +++ /dev/null @@ -1,55 +0,0 @@ -.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 deleted file mode 100755 index 3a5c7f4..0000000 --- a/driver/xscreensaver-getimage-file +++ /dev/null @@ -1,1317 +0,0 @@ -#!/usr/bin/perl -w -# Copyright © 2001-2020 Jamie Zawinski <jwz@jwz.org>. -# -# Permission to use, copy, modify, distribute, and sell this software and its -# documentation for any purpose is hereby granted without fee, provided that -# 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.53 $' =~ 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 < - } else { - if ($c =~ m@^x([\dA-F]+)$@si) { # for A - $c = chr(hex($1)); - } elsif ($c =~ m@^\d+$@si) { # for A - $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/^--?flush-?cache$/s) { $feed_max_age = $cache_max_age = 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 deleted file mode 100644 index 778ad85..0000000 --- a/driver/xscreensaver-getimage-file.man +++ /dev/null @@ -1,66 +0,0 @@ -.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 deleted file mode 100755 index dbc8986..0000000 --- a/driver/xscreensaver-getimage-video +++ /dev/null @@ -1,144 +0,0 @@ -#!/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 deleted file mode 100644 index d19f34e..0000000 --- a/driver/xscreensaver-getimage-video.man +++ /dev/null @@ -1,51 +0,0 @@ -.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.man b/driver/xscreensaver-getimage.man deleted file mode 100644 index ae68014..0000000 --- a/driver/xscreensaver-getimage.man +++ /dev/null @@ -1,73 +0,0 @@ -.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-gfx.c b/driver/xscreensaver-gfx.c index 5a543a4..870423c 100644 --- a/driver/xscreensaver-gfx.c +++ b/driver/xscreensaver-gfx.c @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright © 1991-2021 Jamie Zawinski <jwz@jwz.org> +/* xscreensaver, Copyright © 1991-2022 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 @@ -36,6 +36,10 @@ #include <sys/types.h> #include <pwd.h> +#ifndef HAVE_XINPUT +# error The XInput2 extension is required +#endif + #include <X11/extensions/XInput2.h> #ifdef HAVE_RANDR @@ -86,12 +90,16 @@ maybe_reload_init_file (saver_info *si) saver_preferences *p = &si->prefs; if (init_file_changed_p (p)) { + Bool ov = p->verbose_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 (ov) + p->verbose_p = True; + /* If the DPMS settings in the init file have changed, change the settings on the server to match. This also would have happened at the watchdog timer. */ @@ -344,7 +352,9 @@ main_loop (saver_info *si, Bool init_p) initialize_randr (si); update_screen_layout (si); - describe_monitor_layout (si->monitor_layout); + + if (p->verbose_p) + describe_monitor_layout (si->monitor_layout); initialize_screensaver_window (si); init_sigchld (si); @@ -389,26 +399,26 @@ main_loop (saver_info *si, Bool init_p) /* The Resize and Rotate extension sends an event when the size, rotation, or refresh rate of any screen has changed. */ - if (p->verbose_p) - { - int screen = XRRRootToScreen (si->dpy, event.xrr_event.window); - fprintf (stderr, "%s: %d: screen change event received\n", - blurb(), screen); - } + Bool changed_p; /* Inform Xlib that it's ok to update its data structures. */ XRRUpdateConfiguration (&event.x_event); /* Resize the existing xscreensaver windows and cached ssi data. */ - if (update_screen_layout (si)) + changed_p = update_screen_layout (si); + + if (p->verbose_p) { - if (p->verbose_p) - { - fprintf (stderr, "%s: new layout:\n", blurb()); - describe_monitor_layout (si->monitor_layout); - } - resize_screensaver_window (si); + int screen = XRRRootToScreen (si->dpy, event.xrr_event.window); + fprintf (stderr, "%s: %d: screen change event: %s\n", + blurb(), screen, + (changed_p ? "new layout:" : "layout unchanged")); + if (changed_p) + describe_monitor_layout (si->monitor_layout); } + + if (changed_p) + resize_screensaver_window (si); } # endif /* HAVE_RANDR */ @@ -495,6 +505,13 @@ main (int argc, char **argv) dpy_str = argv[++i]; if (!dpy_str) goto HELP; } + else if (!strcmp (argv[i], "-ver") || + !strcmp (argv[i], "-vers") || + !strcmp (argv[i], "-version")) + { + fprintf (stderr, "%s\n", screensaver_id+4); + exit (1); + } else if (!strcmp (argv[i], "-sync") || !strcmp (argv[i], "-synch") || !strcmp (argv[i], "-synchronize") || diff --git a/driver/xscreensaver-gfx.man b/driver/xscreensaver-gfx.man index 7b209ac..f14fccd 100644 --- a/driver/xscreensaver-gfx.man +++ b/driver/xscreensaver-gfx.man @@ -3,7 +3,8 @@ xscreensaver - extensible screen saver and screen locking framework .SH SYNOPSIS .B xscreensaver-gfx -[\-display \fIhost:display.screen\fP] +[\-\-display \fIhost:display.screen\fP] +[\-\-version] .SH DESCRIPTION The .BR xscreensaver (1) @@ -16,7 +17,7 @@ Do not run this program directly. .BR xscreensaver\-auth (MANSUFFIX), .BR xscreensaver\-systemd (MANSUFFIX). .SH COPYRIGHT -Copyright \(co 2021 by Jamie Zawinski. +Copyright \(co 2021-2022 by Jamie Zawinski. Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that diff --git a/driver/xscreensaver-settings.desktop.in b/driver/xscreensaver-settings.desktop.in new file mode 100755 index 0000000..e50f569 --- /dev/null +++ b/driver/xscreensaver-settings.desktop.in @@ -0,0 +1,9 @@ +#!/usr/bin/env xdg-open +[Desktop Entry] +Exec=xscreensaver-settings +Icon=xscreensaver +_Name=XScreenSaver Settings +_Comment=Change screensaver properties +Type=Application +Categories=Settings;DesktopSettings;Screensaver;Security;GNOME;GTK;KDE;Motif;Qt;X-GNOME-Settings-Panel;X-GNOME-SystemSettings;X-Unity-Settings-Panel;X-XFCE-SettingsDialog;X-XFCE-SystemSettings +Terminal=false diff --git a/driver/xscreensaver-settings.man b/driver/xscreensaver-settings.man index 6b9657d..6ecaa90 100644 --- a/driver/xscreensaver-settings.man +++ b/driver/xscreensaver-settings.man @@ -3,9 +3,8 @@ xscreensaver-settings - configure and control the xscreensaver daemon .SH SYNOPSIS .B xscreensaver\-settings -[\-display \fIhost:display.screen\fP] -[\-prefs] -[\-debug] +[\-\-display \fIhost:display.screen\fP] +[\-\-debug] .SH DESCRIPTION The \fIxscreensaver\-settings\fP program is a graphical front-end for setting the parameters used by the @@ -18,29 +17,25 @@ 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. - +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. - +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. - +with the \fI\-\-exit\fP option. .TP 4 .B Restart Daemon If the xscreensaver daemon is running on this screen, kill it. @@ -49,16 +44,13 @@ Then launch it again. This is the same as doing Note that it is \fInot\fP the same as doing "\fIxscreensaver-command --restart\fP". - .TP 4 .B Exit Exits the \fIxscreensaver-settings\fP program (this program) without affecting the background \fIxscreensaver\fP daemon, if any. - .TP 4 .B About... Displays the version number of this program, \fIxscreensaver-settings\fP. - .TP 4 .B Documentation... Opens up a web browser looking at the XScreenSaver web page, where you @@ -71,41 +63,32 @@ 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 +.SS 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 +.SS 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. @@ -121,20 +104,16 @@ 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 +If the list has focus, you can type any character to search within it. +.SS 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 +.SS Blank After After the user has been idle this long, the \fIxscreensaver\fP daemon will blank the screen. - -.TP 4 -.B Cycle After +.SS 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: @@ -144,13 +123,9 @@ activity. If there are multiple screens, the savers are staggered slightly so that while they all change every \fIcycle\fP minutes, they don't all change at the same time. - -.TP 4 -.B Lock Screen +.SS Lock Screen When this is checked, the screen will be locked when it activates. - -.TP 4 -.B Lock Screen After +.SS 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, @@ -160,38 +135,23 @@ 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 +.SS 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 +.SS Settings This button will pop up a dialog where you can configure settings specific to the display mode selected in the list. - .SH SETTINGS DIALOG When you click on the \fISettings\fP button on the \fIDisplay Modes\fP tab, a configuration dialog will pop up that lets you customize settings -of the selected display mode. Each display mode has its own custom -configuration controls on the left side. - -On the right side is a paragraph or two describing the display mode. -Below that is a \fBDocumentation\fP button that will display the display -mode's manual page in a new window. - -The \fBAdvanced\fP button reconfigures the dialog box so that you can -edit the display mode's command line directly, instead of using the -graphical controls. +of the selected display mode. Each display mode has its own custom set +of configuration 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 - +.SS Image Manipulation Some of the graphics hacks manipulate images. These settings control where those source images come from. The savers load images by running the .BR xscreensaver\-getimage (MANSUFFIX) @@ -199,7 +159,6 @@ and .BR xscreensaver\-getimage\-file (MANSUFFIX) programs. .RS 4 - .TP 4 .B Grab Desktop Images If this option is selected, then savers are allowed to manipulate the @@ -210,12 +169,11 @@ set, it means that the windows on your desktop will occasionally be visible while your screen is locked. Others will not be able to \fIdo\fP anything, but they may be able to \fIsee\fP whatever you left on your screen. - .TP 4 .B Grab Video Frames -If your system has a video capture device, selecting this option may allow -the image-manipulating modes to grab a still-frame of video to operate on. - +If your system has a camera or other video input, selecting this option may +allow the image-manipulating modes to grab a still-frame of video to operate +on. .TP 4 .B Choose Random Image If this option is set, then the image-manipulating modes will select a @@ -230,38 +188,31 @@ 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 - +.SS Text Manipulation Some of the display modes display and manipulate text. The following options control how that text is generated. The savers load text by running the .BR xscreensaver\-text (MANSUFFIX) program. .RS 4 - .TP 4 .B Host Name and Time If this checkbox is selected, then the text used by the screen savers will be the local host name, OS version, date, time, and system load. - .TP 4 .B Text If this checkbox is selected, then the literal text typed in the field to its right will be used. If it contains % escape sequences, they will be expanded as per .BR strftime (2). - .TP 4 .B Text File If this checkbox is selected, then the contents of the corresponding file will be displayed. - .TP 4 .B Program If this checkbox is selected, then the given program will be run, repeatedly, and its output will be displayed. - .TP 4 .B URL If this checkbox is selected, then the given web page will be downloaded @@ -273,11 +224,9 @@ runs out of text, so it will probably be hitting that web server multiple times a minute. .RE .PP -.B Power Management Settings - +.SS 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. @@ -285,24 +234,20 @@ Whether the monitor should be powered down after a period of inactivity. If this option is grayed out, it means your X server does not support the XDPMS extension, and so control over the monitor's power state is not available. - .TP 4 .B Standby After If \fIPower Management Enabled\fP is selected, the monitor will go black after this much idle time. (Graphics demos will stop running, also.) - .TP 4 .B Suspend After If \fIPower Management Enabled\fP is selected, the monitor will go into power-saving mode after this much idle time. This duration should be greater than or equal to \fIStandby\fP. - .TP 4 .B Off After If \fIPower Management Enabled\fP is selected, the monitor will fully power down after this much idle time. This duration should be greater than or equal to \fISuspend\fP. - .TP 4 .B Quick Power-off in "Blank Only" Mode If the display mode is set to \fIBlank Screen Only\fP and this is @@ -311,35 +256,24 @@ blanking, regardless of the other power-management settings. In this way, the power management idle-timers can be completely disabled, but the screen will be powered off when black. .RE -.PP -.B Blanking - +.SS Blanking These options control how the screen fades to or from black when -a screen saver begins or ends. Note: fading doesn't work with all -video drivers. -In particular, it does not work on the 2020-vintage Raspberry Pi. +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. A fade -will also be done when switching from one display mode to another. - +of the screen will fade to black instead of simply winking out. .TP 4 .B Unfade From Black When Unblanking The opposite: if selected, then when the screensaver deactivates, the original contents of the screen will fade in from black instead of appearing immediately. This is only done if \fIFade To Black\fP is also selected. - .TP 4 .B Fade Duration When fading or unfading are selected, this controls how long the fade will take. - -.RE -.B Theme -.RS 4 +.SS Theme This option menu lists the color schemes available for use on the unlock dialog. .RE @@ -353,21 +287,14 @@ file, or the X resource database. .I xscreensaver\-settings accepts the following command line options. .TP 8 -.B \-display \fIhost:display.screen\fP +.B \-\-display \fIhost:display.screen\fP The X display to use. The \fIxscreensaver\-settings\fP program will open its window on that display, and also control the \fIxscreensaver\fP daemon that is managing that same display. - .TP 8 -.B \-prefs -Start up with the \fBAdvanced\fP tab selected by default -instead of the \fBDisplay Modes\fP tab. - -.TP 8 -.B \-debug +.B \-\-debug Causes lots of diagnostics to be printed on stderr. - -.P +.PP The \fIxscreensaver\fP and \fIxscreensaver\-settings\fP processes must run on the same machine, or at least, on two machines that share a file system. When \fIxscreensaver\-settings\fP writes a new version of @@ -380,10 +307,10 @@ file, or it won't work. to get the default host and display number. .TP 8 .B PATH -to find the sub-programs to run. However, note that the sub-programs -are actually launched by the \fIxscreensaver\fP daemon, not -by \fIxscreensaver-settings\fP itself. So, what matters is what \fB$PATH\fP -that the \fIxscreensaver\fP program sees. +to find the sub-programs to run. However, note that the sub-programs +actually launched by \fIxscreensaver-settings\fP for display in the +inline preview pane, but are launched by the \fIxscreensaver\fP daemon +when run full screen, so the \fB$PATH\fP setting in both processes matters. .TP 8 .B HOME for the directory in which to read and write the \fI.xscreensaver\fP file. @@ -406,7 +333,7 @@ and a FAQ can always be found at https://www.jwz.org/xscreensaver/ .BR xscreensaver\-getimage\-video (MANSUFFIX), .BR xscreensaver\-text (MANSUFFIX) .SH COPYRIGHT -Copyright \(co 1992-2021 by Jamie Zawinski. +Copyright \(co 1992-2022 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 @@ -415,6 +342,6 @@ 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. +Jamie Zawinski <jwz@jwz.org>. Please let me know if you find any bugs or make any improvements. diff --git a/driver/xscreensaver-systemd.c b/driver/xscreensaver-systemd.c index d06174a..2e174b2 100644 --- a/driver/xscreensaver-systemd.c +++ b/driver/xscreensaver-systemd.c @@ -1,4 +1,4 @@ -/* xscreensaver-systemd, Copyright (c) 2019-2021 +/* xscreensaver-systemd, Copyright (c) 2019-2023 * Martin Lucina <martin@lucina.net> and Jamie Zawinski <jwz@jwz.org> * * ISC License @@ -17,6 +17,7 @@ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * + ***************************************************************************** * * This utility provides systemd integration for XScreenSaver. * It does two things: @@ -27,16 +28,20 @@ * up again, it runs "xscreensaver-command -deactivate" to force the * unlock dialog to appear immediately. * - * - When another process on the system makes asks for the screen saver - * to be inhibited (e.g. because a video is playing) this program - * periodically runs "xscreensaver-command -deactivate" to keep the - * display un-blanked. It does this until the other program asks for - * it to stop. + * - When another process on the system asks for the screen saver to be + * inhibited (e.g. because a video is playing) this program periodically + * runs "xscreensaver-command -deactivate" to keep the display un-blanked. + * It does this until the other program asks for it to stop. + * + * For this to work at all, you must either: * - * For this to work at all, you must prevent Gnome and KDE from usurping - * the "org.freedesktop.ScreenSaver" messages, or else this program can't - * receive them. The "xscreensaver" man page contains the (complicated) - * installation instructions. + * A: Be running GNOME's "org.gnome.SessionManager" D-Bus service; or + * B: Be running KDE's "org.kde.Solid.PowerManagement.PolicyAgent" svc; or + * C: Prevent your desktop environment from running the + * "org.freedesktop.ScreenSaver "service. + * + * + ***************************************************************************** * * Background: * @@ -60,21 +65,29 @@ * "Did IQs just drop sharply while I was away?" -- Ellen Ripley * * We can sometimes detect that the inhibiting app has exited abnormally - * by using "tracking peers" but I'm not sure how reliable that is. + * by using "tracking peers" but that seems to not work on all systems. * * Furthermore, we can't listen for these "inhibit blanking" requests - * if some other program is already listening for them -- which Gnome and + * if some other program is already listening for them -- which GNOME and * KDE do by default, even if their screen savers are otherwise disabled. - * That makes it far more complicated for the user to install XScreenSaver - * in such a way that "xscreensaver-systemd" can even launch at all. + * + * Both GNOME and KDE bind to "org.freedesktop.ScreenSaver" by default, + * meaning that we can't listen for events sent there. However, after + * receiving events at "org.freedesktop.ScreenSaver" they both *also* + * sends out a secondary set of notifications that we *are* able to + * receive. Naturally GNOME and KDE do this in differently idiosyncratic + * ways. + * + * If you use some third desktop system that registers itself as + * "org.freedesktop.ScreenSaver", you will need to convince it to stop + * doing that. * * To recap: because the existing video players decided to delete the * single line of code that they already had -- the heartbeat call to - * "xscreensaver-command -deactivate" -- we had to respond by adding a - * THOUSAND LINES of complicated code that talks to a server that may - * not be running, and that may not allow us to connect, and that may - * not work properly anyway, and that makes installation hellaciously - * difficult and confusing for the end user. + * "xscreensaver-command -deactivate" -- we had to respond by adding + * TWELVE HUNDRED LINES of complicated code that talks to a server that + * may not be running, and that may not allow us to connect, and that may + * not work properly anyway. * * This is what is laughingly referred to as "progress". * @@ -84,86 +97,90 @@ * ***************************************************************************** * - * Firefox (version 78.5) + * Firefox 91.9.0esr, Raspbian 11.1: * - * When playing media, Firefox will send "inhibit" to one of these - * targets: "org.freedesktop.ScreenSaver" or "org.gnome.SessionManager". + * Sends "Inhibit" to the first of these targets that exists at launch: + * "org.freedesktop.ScreenSaver /ScreenSaver" or + * "org.gnome.SessionManager /org/gnome/SessionManager". + * In the source, "WakeLockListener.cpp". * - * However, Firefox decides which, if any, of those to use at launch time, - * and does not revisit that decision. So if xscreensaver-systemd has not - * been launched before Firefox, it won't work. Fortunately, in most use - * cases, xscreensaver will have been launched earlier in the startup - * sequence than the web browser. + * Inhibit: Kind: Reason: * - * If you close the tab or exit while playing, Firefox sends "uninhibit". - * - * Critical Firefox Bug: - * - * If Firefox crashes or is killed while playing, it never sends - * "uninhibit", leaving the screen saver permanently inhibited. Once - * that happens, the only way to un-fuck things is to kill and restart - * the "xscreensaver-systemd" program. - * - * Annoying Firefox Bug: - * - * Firefox sends an "inhibit" message when it is merely playing audio. - * That's horrible. Playing audio should prevent your machine from going - * to sleep, but it should NOT prevent your screen from blanking or - * locking. - * - * However at least it sends it with the reason "audio-playing" instead - * of "video-playing", meaning we can (and do) special-case Firefox and - * ignore that one. + * Yes MP4 URL "video-playing" + * Yes MP3 URL "audio-playing" + * Yes <VIDEO> "video-playing" + * No <VIDEO AUTOPLAY> - + * No <VIDEO AUTOPLAY LOOP> - + * Yes <AUDIO> "audio-playing" + * No <AUDIO AUTOPLAY> - + * Yes YouTube IFRAME "video-playing" + * Yes YouTube IFRAME, autoplay "video-playing" + * Yes Uninhibit on quit + * No Uninhibit on kill * * ***************************************************************************** * - * Chrome (version 87) + * Chromium 98.0.4758.106-rpt1, Raspbian 11.1: * - * Sends "inhibit" to "org.freedesktop.ScreenSaver" (though it uses a - * a different object path than Firefox does). Unlike Firefox, Chrome - * does not send an "inhibit" message when only audio is playing. + * Sends "Inhibit" to the first of these targets that exists at launch: + * "org.gnome.SessionManager /org/gnome/SessionManager" or + * "org.freedesktop.ScreenSaver /org/freedesktop/ScreenSaver". + * In the source, "power_save_blocker_linux.cc". * - * Critical Chrome Bug: + * Inhibit: Kind: Reason: * - * If Chrome crashes or is killed while playing, it never sends - * "uninhibit", leaving the screen saver permanently inhibited. + * Yes MP4 URL "chromium-browser-v7" + * Yes MP3 URL "chromium-browser-v7" <-- BAD + * Yes <VIDEO> "chromium-browser-v7" + * Yes <VIDEO AUTOPLAY> "chromium-browser-v7" + * Yes <VIDEO AUTOPLAY LOOP> "chromium-browser-v7" <-- BAD + * No <AUDIO> - + * No <AUDIO AUTOPLAY> - + * Yes YouTube IFRAME "chromium-browser-v7" + * Yes YouTube IFRAME, autoplay "chromium-browser-v7" + * Yes Uninhibit on quit + * No Uninhibit on kill * + * Bad Chromium bug #1: * - ***************************************************************************** + * It inhibits when only audio is playing, and does so with the same + * message as for audio, so we can't tell them apart. This means that, + * unlike Firefox, playing audio-only in Chromium will prevent your + * screen from blanking. * - * Chromium (version 78, Raspbian 10.4) + * Bad Chromium bug #2: * - * Does not use "org.freedesktop.ScreenSaver" or "xdg-screensaver". - * It appears to make no attempt to inhibit the screen saver while - * video is playing. + * It inhibits when short looping videos are playing. Many sites + * (including Twitter) auto-convert GIFs to looping MP4s to save + * bandwidth, so Chromium will inhibit any time such a GIF is visible. * + * The proper way for Chrome to fix this would be to stop inhibiting once + * the video loops. That way your multi-hour movie inhibits properly, but + * your looping GIF only inhibits for the first few seconds. + * * ***************************************************************************** * - * Chromium (version 84.0.4147.141, Raspbian 10.6) + * Chromium 101.0.4951.64, Debian 11.3: * - * Sends "inhibit" to "org.freedesktop.ScreenSaver" (though it uses a - * a different object path than Firefox does). Unlike Firefox, Chrome - * does not send an "inhibit" message when only audio is playing. + * Same as the above, except that "chromium-browser-v7" has changed to + * "Video Wake Lock". * - * If you close the tab or exit while playing, Chromium sends "uninhibit". + * When playing videos, it sends two "Inhibit" requests: "Video Wake Lock" + * and "Playing audio". When playing audio, it also sends those same two + * requests. Closer! Still wrong. * - * Critical Chromium Bug: * - * If Chromium crashes or is killed while playing, it never sends - * "uninhibit", leaving the screen saver permanently inhibited. - * - * Annoying Chromium Bug: + ***************************************************************************** * - * Like Firefox, Chromium sends an "inhibit" message when it is merely - * playing audio. Unlike Firefox, it sends exactly the same "reason" - * string as it does when playing video, so we can't tell them apart. + * Chrome 101.0.4951.64 (Official Google Build), Debian 11.3: * + * Same. * ***************************************************************************** * - * MPV (version 0.29.1) + * MPV 0.32.0-3, Raspbian 11.1: * * While playing, it runs "xdg-screensaver reset" every 10 seconds as a * heartbeat. That program is a super-complicated shell script that will @@ -171,7 +188,7 @@ * xscreensaver daemon directly rather than going through systemd. * That's fine. * - * On Debian 10.4 and 10.6, MPV does not have a dependency on the + * On Debian 10.4 through 11.1, MPV does not have a dependency on the * "xdg-utils" package, so "xdg-screensaver" might not be installed. * Oddly, Chromium *does* have a dependency on "xdg-utils", even though * Chromium doesn't run "xdg-screensaver". @@ -185,64 +202,96 @@ * extension because it is worse than useless. See the commentary at * the top of xscreensaver.c for details. * - * Annoying MPV Bug: * - * Like Firefox and Chromium, MPV inhibits screen blanking when only - * audio is playing. + ***************************************************************************** + * + * MPV 0.33+, I think: + * + * The developer had a hissy fit and removed "xdg-screensaver" support: + * + * https://github.com/mpv-player/mpv/commit/c498b2846af0ee8835b9144c9f6893568a4e49c6 + * + * So now I guess you're back to figuring out how to add a "heartbeat" + * command to have MPV periodically call "xscreensaver-command -reset". + * Good luck with that. Maybe you should just use VLC instead. * * ***************************************************************************** * - * MPlayer (version mplayer-gui 2:1.3.0) + * VLC 3.0.16-1, Raspbian 11.1 & Debian 11.3: + * + * Sends "Inhibit" to the first of these targets that exists at launch: + * "org.freedesktop.ScreenSaver /ScreenSaver" or + * "org.mate.SessionManager /org/mate/SessionManager" or + * "org.gnome.SessionManager /org/gnome/SessionManager". + * + * For video, it sends the reason "Playing some media." It does not send + * "Inhibit" when playing audio only. Also it is the only program to + * properly send "Uninhibit" when killed. * - * I can't get this thing to play video at all. It only plays the audio - * of MP4 files, so I can't guess what it might or might not do with video. - * It appears to make no attempt to inhibit the screen saver. + * It also contains code to run "xdg-screensaver reset" as a heartbeat, + * but I have never seen that happen. + * In the source, "vlc/modules/misc/inhibit/dbus.c" and "xdg.c". * * ***************************************************************************** * - * VLC (version 3.0.11-0+deb10u1+rpt3) + * MPlayer 2:1.4, Raspbian 11.1: * - * VLC sends "inhibit" to "org.freedesktop.ScreenSaver" when playing - * video. It does not send "inhibit" when playing audio only, and it - * sends "uninhibit" under all the right circumstances. + * Apparently makes no attempt to inhibit the screen saver. * - * NOTE: that's what I saw when I tested it on Raspbian 10.6. However, - * the version that came with Raspbian 10.4 -- which also called itself - * "VLC 3.0.11" -- did not send "uninhibit" when using the window - * manager's "close" button! Or when killed with "kill". + * Update, Sep 2023: rumor has it that this works with some versions. + * I have not verified this: * - * NOTE ALSO: The VLC source code suggests that under some circumstances - * it might be talking to these instead: "org.freedesktop.ScreenSaver", - * "org.freedesktop.PowerManagement.Inhibit", "org.mate.SessionManager", - * and/or "org.gnome.SessionManager". It also contains code to run - * "xdg-screensaver reset" as a heartbeat. I can't tell how it decides - * which system to use. I have never seen it run "xdg-screensaver". + * ~/.mplayer/config: + * heartbeat-cmd="xscreensaver-command -deactivate >&- 2>&- &" * * ***************************************************************************** * - * Zoom + * Zoom 5.10.4.2845, Debian 11.3: + * + * Sends "Inhibit" to "org.freedesktop.ScreenSaver" with "in a meeting". + * I think it does this for both video and audio-only meetings, but that + * seems reasonable. * - * I'm told that the proprietary Zoom executable for Linux sends "inhibit" - * to "org.freedesktop.ScreenSaver", but I don't have any further details. * ***************************************************************************** * - * TO DO: + * Steam 1:1.0.0.74, Debian 11.3: + * + * Inhibits, then uninhibits and immediately reinhibits every 30 seconds + * forever. Sometimes it identifies as "Steam", sometimes as "My SDL + * application", AKA "Baby's First Hello World". Perfect, no notes. * - * - What precisely does the standalone Zoom executable do on Linux? - * There doesn't seem to be a Raspbian build, so I can't test it. * - * - xscreensaver_method_uninhibit() does not actually send a reply, are - * we doing the right thing when registering it? + ***************************************************************************** + * + * XFCE Power Manager Presentation Mode: + * + * This setting prevents the screen from blanking, and has a long history + * of becoming turned on accidentally. Tries org.freedesktop.ScreenSaver + * and others before falling back to "xscreensaver-command -deactivate" + * as a heartbeat. + * + * + ***************************************************************************** + * + * Kodi / XMBC: * - * - Currently this code is only listening to "org.freedesktop.ScreenSaver". - * Perhaps it should listen to "org.mate.SessionManager" and - * "org.gnome.SessionManager"? Where are those documented? + * Apparently makes no attempt to inhibit the screen saver. + * There are some third party extensions that fix that, e.g. + * "kodi-prevent-xscreensaver". + * + * + ***************************************************************************** + * + * + * TO DO: * - * - Do we need to call sd_bus_release_name() explicitly on exit? + * - What is "org.mate.SessionManager"? If it is just a re-branded + * "org.gnome.SessionManager" with the same behavior, we should probably + * listen to "InhibitorAdded" on that as well. * * - Run under valgrind to check for any memory leaks. * @@ -250,29 +299,38 @@ * *three* different ways for dbus clients to ask the question, "is the * screen currently blanked?" We should probably also respond to these: * - * qdbus org.freedesktop.ScreenSaver /ScreenSaver org.freedesktop.ScreenSaver.GetActive - * qdbus org.kde.screensaver /ScreenSaver org.freedesktop.ScreenSaver.GetActive - * qdbus org.gnome.ScreenSaver /ScreenSaver org.gnome.ScreenSaver.GetActive + * qdbus org.freedesktop.ScreenSaver /ScreenSaver + * org.freedesktop.ScreenSaver.GetActive + * qdbus org.kde.screensaver /ScreenSaver + * org.freedesktop.ScreenSaver.GetActive + * qdbus org.gnome.ScreenSaver /ScreenSaver + * org.gnome.ScreenSaver.GetActive * * + ***************************************************************************** * * TESTING: * + * To snoop the bus, "dbus-monitor" or "dbus-monitor --system". + * * To call the D-BUS methods manually, you can use "busctl": * - * busctl --user call org.freedesktop.ScreenSaver \ - * /ScreenSaver org.freedesktop.ScreenSaver \ - * Inhibit ss test-application test-reason + * busctl --user call org.freedesktop.ScreenSaver \ + * /ScreenSaver org.freedesktop.ScreenSaver \ + * Inhibit ss test-application test-reason * * This will hand out a cookie, which you can pass back to UnInhibit: * - * u 1792821391 + * u 1792821391 * - * busctl --user call org.freedesktop.ScreenSaver \ - * /ScreenSaver org.freedesktop.ScreenSaver \ - * UnInhibit u 1792821391 + * busctl --user call org.freedesktop.ScreenSaver \ + * /ScreenSaver org.freedesktop.ScreenSaver \ + * UnInhibit u 1792821391 * * https://github.com/mato/xscreensaver-systemd + * + * + ***************************************************************************** */ #ifdef HAVE_CONFIG_H @@ -294,7 +352,13 @@ #include <signal.h> #include <X11/Xlib.h> -#include <systemd/sd-bus.h> +#if defined (HAVE_LIBSYSTEMD) +# include <systemd/sd-bus.h> +#elif defined (HAVE_LIBELOGIND) +# include <elogind/sd-bus.h> +#else +# error Neither HAVE_LIBSYSTEMD nor HAVE_LIBELOGIND is defined. +#endif #include "version.h" #include "blurb.h" @@ -303,6 +367,8 @@ static char *screensaver_version; +/* This is for power management, on the system bus. + */ #define DBUS_CLIENT_NAME "org.jwz.XScreenSaver" #define DBUS_SD_SERVICE_NAME "org.freedesktop.login1" #define DBUS_SD_OBJECT_PATH "/org/freedesktop/login1" @@ -313,15 +379,54 @@ static char *screensaver_version; #define DBUS_SD_METHOD_WHO "xscreensaver" #define DBUS_SD_METHOD_WHY "lock screen on suspend" #define DBUS_SD_METHOD_MODE "delay" - -#define DBUS_SD_MATCH "type='signal'," \ - "interface='" DBUS_SD_INTERFACE "'," \ - "member='PrepareForSleep'" - +#define DBUS_SD_MATCH "type='signal'," \ + "interface='" DBUS_SD_INTERFACE "'," \ + "member='PrepareForSleep'" + +/* This is for handling requests to lock or unlock, on the system bus. + This is sent by "loginctl lock-session", which in turn is run if someone + has added "HandleLidSwitch=lock" to /etc/systemd/logind.conf in order to + make closing the lid lock the screen *without* suspending. Note that it + is sent on login1 *Session*, not *Manager*. + */ +#define DBUS_SD_LOCK_INTERFACE "org.freedesktop.login1.Session" +#define DBUS_SD_LOCK_MATCH "type='signal'," \ + "interface='" DBUS_SD_LOCK_INTERFACE "'," \ + "member='Lock'" +#define DBUS_SD_UNLOCK_MATCH "type='signal'," \ + "interface='" DBUS_SD_LOCK_INTERFACE "'," \ + "member='Unlock'" + +/* This is for blanking inhibition, on the user bus. + See the large comment above for the absolute mess that apps make of this. + */ #define DBUS_FDO_NAME "org.freedesktop.ScreenSaver" -#define DBUS_FDO_OBJECT_PATH "/ScreenSaver" /* Firefox */ +#define DBUS_FDO_INTERFACE DBUS_FDO_NAME +#define DBUS_FDO_OBJECT_PATH_1 "/ScreenSaver" /* Firefox, VLC */ #define DBUS_FDO_OBJECT_PATH_2 "/org/freedesktop/ScreenSaver" /* Chrome */ -#define DBUS_FDO_INTERFACE "org.freedesktop.ScreenSaver" + +#define DBUS_GSN_INTERFACE "org.gnome.SessionManager" +#define DBUS_GSN_PATH "/org/gnome/SessionManager" +#define DBUS_GSN_MATCH_1 "type='signal'," \ + "interface='" DBUS_GSN_INTERFACE "'," \ + "member='InhibitorAdded'" +#define DBUS_GSN_MATCH_2 "type='signal'," \ + "interface='" DBUS_GSN_INTERFACE "'," \ + "member='InhibitorRemoved'" + +#define DBUS_KDE_INTERFACE "org.kde.Solid.PowerManagement.PolicyAgent" +#define DBUS_KDE_PATH "/org/kde/Solid/PowerManagement/PolicyAgent" +#define DBUS_KDE_MATCH "type='signal'," \ + "interface='" DBUS_KDE_INTERFACE "'," \ + "member='InhibitionsChanged'" +#define INTERNAL_KDE_COOKIE "_KDE_" + +/* This is for metadata about D-Bus itself. + */ +#define DBUS_SERVICE_DBUS "org.freedesktop.DBus" +#define DBUS_PATH_DBUS "/org/freedesktop/DBus" +#define DBUS_INTERFACE_DBUS DBUS_SERVICE_DBUS + #define HEARTBEAT_INTERVAL 50 /* seconds */ @@ -333,7 +438,7 @@ struct handler_ctx { sd_bus *system_bus; sd_bus_message *lock_message; int lock_fd; - int is_inhibited; + int inhibit_count; sd_bus_track *track; }; @@ -343,10 +448,13 @@ SLIST_HEAD(inhibit_head, inhibit_entry) inhibit_head = SLIST_HEAD_INITIALIZER(inhibit_head); struct inhibit_entry { - uint32_t cookie; + char *cookie; time_t start_time; char *appname; char *peer; + char *reason; + Bool ignored_p; + Bool seen_p; SLIST_ENTRY(inhibit_entry) entries; }; @@ -357,7 +465,7 @@ xscreensaver_command (const char *cmd) char buf[1024]; int rc; sprintf (buf, "xscreensaver-command %.100s -%.100s", - (verbose_p ? "-verbose" : "-quiet"), + (verbose_p ? "--verbose" : "--quiet"), cmd); if (verbose_p) fprintf (stderr, "%s: exec: %s\n", blurb(), buf); @@ -370,6 +478,47 @@ xscreensaver_command (const char *cmd) } +/* Make a method call and read a single string reply. + Like "dbus-send --print-reply --dest=$dest $path $interface.$msg" + */ +static const char * +dbus_send (sd_bus *bus, const char *dest, const char *path, + const char *interface, const char *msg) +{ + int rc; + sd_bus_message *m = 0; + sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus_message *reply = 0; + const char *ret = 0; + + rc = sd_bus_message_new_method_call (bus, &m, dest, path, interface, msg); + if (rc < 0) { + fprintf (stderr, "%s: dbus_send: failed to create message: %s%s: %s\n", + blurb(), interface, msg, strerror(-rc)); + return 0; + } + + sd_bus_message_set_auto_start (m, 1); + sd_bus_message_set_expect_reply (m, 1); + + rc = sd_bus_call (bus, m, -1, &error, &reply); + if (rc < 0) { + fprintf (stderr, "%s: dbus_send: call failed: %s.%s: %s\n", + blurb(), interface, msg, strerror(-rc)); + return 0; + } + + rc = sd_bus_message_read (reply, "s", &ret); + if (rc < 0) { + fprintf (stderr, "%s: dbus_send: failed to read reply: %s.%s: %s\n", + blurb(), interface, msg, strerror(-rc)); + return 0; + } + + return ret; +} + + static int xscreensaver_register_sleep_lock (struct handler_ctx *ctx) { @@ -407,12 +556,12 @@ xscreensaver_register_sleep_lock (struct handler_ctx *ctx) } -/* Called when DBUS_SD_INTERFACE sends a "PrepareForSleep" signal. - The event is sent twice: before sleep, and after. +/* Called when "org.freedesktop.login1.Manager" sends a "PrepareForSleep" + signal. The event is sent twice: before sleep, and after. */ static int -xscreensaver_systemd_handler (sd_bus_message *m, void *arg, - sd_bus_error *ret_error) +xscreensaver_prepare_for_sleep_cb (sd_bus_message *m, void *arg, + sd_bus_error *ret_error) { struct handler_ctx *ctx = arg; int before_sleep; @@ -460,19 +609,194 @@ xscreensaver_systemd_handler (sd_bus_message *m, void *arg, } -/* Called from the vtable when another process sends a request to systemd - to inhibit the screen saver. We return to them a cookie which they must +/* Called when "org.freedesktop.login1.Session" sends a "Lock" signal. + The event is sent several times in quick succession. + https://www.freedesktop.org/software/systemd/man/org.freedesktop.login1.html + */ +static int +xscreensaver_lock_cb (sd_bus_message *m, void *arg, sd_bus_error *ret_error) +{ + /* Tell xscreensaver that we are suspending, and to lock if desired. + Maybe sending "lock" here would be more appropriate, but that might + do a screen-fade first. */ + xscreensaver_command ("suspend"); + return 1; /* >= 0 means success */ +} + +/* Called when "org.freedesktop.login1.Session" sends an "Unlock" signal. + I'm not sure if anything ever sends this. + */ +static int +xscreensaver_unlock_cb (sd_bus_message *m, void *arg, sd_bus_error *ret_error) +{ + /* Tell xscreensaver to present the unlock dialog right now. */ + xscreensaver_command ("deactivate"); + return 1; /* >= 0 means success */ +} + + +/* Is this "reason" string one that means we should inhibit blanking? + If the string is e.g. "audio-playing", (Firefox) the answer is no. + If the string is "Download in progress", (Chromium) the answer is no. + Likewise, GEdit sends "There are unsaved documents", which is apparently + an attempt for it to inhibit *logout*, which is reasonable, rather than + an attempt to inhibit *blanking*, which is not. I don't know how to tell + them apart. + */ +static Bool +good_reason_p (const char *s) +{ + if (!s || !*s) return True; + if (strcasestr (s, "video")) return True; + if (strcasestr (s, "audio")) return False; + if (strcasestr (s, "download")) return False; + if (strcasestr (s, "unsaved")) return False; + return True; +} + + +static const char * +remove_dir (const char *s) +{ + if (s) { + const char *s2 = strrchr (s, '/'); + if (s2 && s2[1]) return s2+1; + } + return s; +} + + +static struct inhibit_entry * +add_new_entry (struct handler_ctx *ctx, + const char *cookie, + const char *application_name, + const char *sender, + const char *via, + const char *inhibit_reason) +{ + struct inhibit_entry *entry = calloc (1, sizeof (*entry)); + entry->cookie = strdup(cookie); + entry->appname = strdup(application_name); + entry->peer = sender ? strdup(sender) : NULL; + entry->reason = strdup(inhibit_reason ? inhibit_reason : ""); + entry->start_time = time ((time_t *)0); + entry->ignored_p = ! good_reason_p (inhibit_reason); + SLIST_INSERT_HEAD (&inhibit_head, entry, entries); + + if (! entry->ignored_p) + ctx->inhibit_count++; + + if (verbose_p) { + char *c2 = (char *) + malloc ((entry->cookie ? strlen(entry->cookie) : 0) + 20); + if (cookie && !!strcmp (cookie, INTERNAL_KDE_COOKIE)) + sprintf (c2, " cookie \"%s\"", remove_dir (entry->cookie)); + else + *c2 = 0; + fprintf (stderr, + "%s: inhibited by \"%s\" (%s%s%s) with \"%s\"%s%s\n", + blurb(), remove_dir (application_name), + (via ? "via " : ""), + (via ? via : ""), + (sender ? sender : ""), + inhibit_reason, + c2, + (entry->ignored_p ? " (ignored)" : "")); + } + + return entry; +} + + +static void +free_entry (struct handler_ctx *ctx, struct inhibit_entry *entry) +{ + if (entry->peer) { + int rc = sd_bus_track_remove_name (ctx->track, entry->peer); + if (rc < 0) { + fprintf (stderr, "%s: failed to stop tracking peer \"%s\": %s\n", + blurb(), entry->peer, strerror(-rc)); + } + free (entry->peer); + } + + if (entry->appname) free (entry->appname); + if (entry->cookie) free (entry->cookie); + if (entry->reason) free (entry->reason); + + if (! entry->ignored_p) + ctx->inhibit_count--; + if (ctx->inhibit_count < 0) + ctx->inhibit_count = 0; + + free (entry); +} + + +/* Remove any entries with the given cookie. + If cookie is NULL, instead remove any entries whose peer is dead. + */ +static Bool +remove_matching_entry (struct handler_ctx *ctx, + const char *matching_cookie, + const char *sender, + const char *via) +{ + struct inhibit_entry *entry, *entry_next; + Bool found = False; + + SLIST_FOREACH_SAFE (entry, &inhibit_head, entries, entry_next) { + if (matching_cookie + ? !strcmp (entry->cookie, matching_cookie) + : !sd_bus_track_count_name (ctx->track, entry->peer)) { + if (verbose_p) { + if (matching_cookie) + fprintf (stderr, + "%s: uninhibited by \"%s\" (%s%s%s) with \"%s\"" + " cookie \"%s\"%s\n", + blurb(), remove_dir (entry->appname), + (via ? "via " : ""), + (via ? via : ""), + (sender ? sender : ""), + entry->reason, + remove_dir (entry->cookie), + (entry->ignored_p ? " (ignored)" : "")); + else + fprintf (stderr, "%s: peer %s for inhibiting app \"%s\" has died:" + " uninhibiting %s%s\n", + blurb(), entry->peer, entry->appname, + remove_dir (entry->cookie), + (entry->ignored_p ? " (ignored)" : "")); + } + SLIST_REMOVE (&inhibit_head, entry, inhibit_entry, entries); + free_entry (ctx, entry); + found = True; + break; + } + } + + if (!found && matching_cookie && verbose_p) + fprintf (stderr, "%s: uninhibit: no match for cookie \"%s\"\n", + blurb(), remove_dir (matching_cookie)); + + return found; +} + + +/* Called from the vtable when another process sends a request to + "org.freedesktop.ScreenSaver" (on which we are authoritative) to + inhibit the screen saver. We return to them a cookie which they must present with their "uninhibit" request. */ static int -xscreensaver_method_inhibit (sd_bus_message *m, void *arg, - sd_bus_error *ret_error) +xscreensaver_inhibit_cb (sd_bus_message *m, void *arg, + sd_bus_error *ret_error) { struct handler_ctx *ctx = arg; const char *application_name = 0, *inhibit_reason = 0; - struct inhibit_entry *entry = 0; - const char *s; const char *sender; + uint32_t cookie; + char cookie_str[20]; int rc = sd_bus_message_read(m, "ss", &application_name, &inhibit_reason); if (rc < 0) { @@ -494,22 +818,8 @@ xscreensaver_method_inhibit (sd_bus_message *m, void *arg, sender = sd_bus_message_get_sender (m); - /* Omit directory (Chrome does this shit) */ - s = strrchr (application_name, '/'); - if (s && s[1]) application_name = s+1; - - if (strcasestr (inhibit_reason, "audio") && - !strcasestr (inhibit_reason, "video")) { - /* Firefox 78 sends an inhibit when playing audio only, with reason - "audio-playing". This is horrible. Ignore it. (But perhaps it - would be better to accept it, issue them a cookie, and then just - ignore that entry?) */ - if (verbose_p) - fprintf (stderr, "%s: inhibited by \"%s\" (%s) with \"%s\", ignored\n", - blurb(), application_name, sender, inhibit_reason); - return -1; - } - + application_name = remove_dir (application_name); + /* Tell the global tracker object to monitor when this peer exits. */ rc = sd_bus_track_add_name(ctx->track, sender); if (rc < 0) { @@ -518,34 +828,26 @@ xscreensaver_method_inhibit (sd_bus_message *m, void *arg, sender = NULL; } - entry = malloc(sizeof (struct inhibit_entry)); - entry->cookie = ya_random(); - entry->appname = strdup(application_name); - entry->peer = sender ? strdup(sender) : NULL; - entry->start_time = time ((time_t *)0); - SLIST_INSERT_HEAD(&inhibit_head, entry, entries); - ctx->is_inhibited++; - if (verbose_p) - fprintf (stderr, "%s: inhibited by \"%s\" (%s) with \"%s\"" - " -> cookie %08X\n", - blurb(), application_name, sender, inhibit_reason, entry->cookie); + cookie = ya_random(); + sprintf (cookie_str, "%08X", cookie); - return sd_bus_reply_method_return (m, "u", entry->cookie); + add_new_entry (ctx, cookie_str, application_name, sender, 0, inhibit_reason); + return sd_bus_reply_method_return (m, "u", cookie); } -/* Called from the vtable when another process sends a request to systemd - to uninhibit the screen saver. The cookie must match an earlier "inhibit" +/* Called from the vtable when another process sends a request to + "org.freedesktop.ScreenSaver" (on which we are authoritative) to + uninhibit the screen saver. The cookie must match an earlier "inhibit" request. */ static int -xscreensaver_method_uninhibit (sd_bus_message *m, void *arg, - sd_bus_error *ret_error) +xscreensaver_uninhibit_cb (sd_bus_message *m, void *arg, + sd_bus_error *ret_error) { struct handler_ctx *ctx = arg; uint32_t cookie; - struct inhibit_entry *entry; - int found = 0; + char cookie_str[20]; const char *sender; int rc = sd_bus_message_read (m, "u", &cookie); @@ -555,50 +857,228 @@ xscreensaver_method_uninhibit (sd_bus_message *m, void *arg, return rc; } + sprintf (cookie_str, "%08X", cookie); sender = sd_bus_message_get_sender (m); + remove_matching_entry (ctx, cookie_str, sender, 0); - SLIST_FOREACH(entry, &inhibit_head, entries) { - if (entry->cookie == cookie) { - if (verbose_p) - fprintf (stderr, "%s: uninhibited by \"%s\" (%s) with cookie %08X\n", - blurb(), entry->appname, sender, cookie); - SLIST_REMOVE (&inhibit_head, entry, inhibit_entry, entries); - if (entry->appname) free (entry->appname); - if (entry->peer) { - rc = sd_bus_track_remove_name(ctx->track, entry->peer); - if (rc < 0) { - fprintf (stderr, "%s: failed to stop tracking peer \"%s\": %s\n", - blurb(), entry->peer, strerror(-rc)); - } - free(entry->peer); + return sd_bus_reply_method_return (m, ""); +} + + +/* Called when "org.gnome.SessionManager" (gnome-shell) sends out a broadcast + announcement that some other process has received an inhibitor lock. + Anyone can receive this signal: it is non-exclusive. + */ +static int +xscreensaver_gnome_inhibitor_added_cb (sd_bus_message *m, void *arg, + sd_bus_error *err) +{ + struct handler_ctx *ctx = arg; + sd_bus *bus = sd_bus_message_get_bus (m); + const char *path = 0; + const char *iface = 0; + const char *sender = 0; + const char *appid = 0; + const char *reason = 0; + char *via; + int rc; + + rc = sd_bus_message_read (m, "o", &path); + if (rc < 0) { + fprintf (stderr, "%s: failed to parse method call: %s\n", + blurb(), strerror(-rc)); + return rc; + } + + sender = sd_bus_message_get_sender (m); + iface = sd_bus_message_get_interface (m); + + appid = dbus_send (bus, DBUS_GSN_INTERFACE, path, + DBUS_GSN_INTERFACE ".Inhibitor", "GetAppId"); + if (!appid) return 0; + + appid = remove_dir (appid); + reason = dbus_send (bus, DBUS_GSN_INTERFACE, path, + DBUS_GSN_INTERFACE ".Inhibitor", "GetReason"); + if (!reason) return 0; + + /* We can't get the original peer sender of this message: this is + a rebroadcast from gnome-shell. */ + + via = (char *) malloc (strlen(iface) + strlen(sender) + 10); + sprintf (via, "%s%s", iface, sender); + add_new_entry (ctx, path, appid, 0, via, reason); + free (via); + + return 0; +} + + +/* Called when "org.gnome.SessionManager" (gnome-shell) sends out a broadcast + announcement that some other process has relinquished an inhibitor lock. + Anyone can receive this signal: it is non-exclusive. + */ +static int +xscreensaver_gnome_inhibitor_removed_cb (sd_bus_message *m, void *arg, + sd_bus_error *err) +{ + struct handler_ctx *ctx = arg; + const char *path = 0; + const char *sender = 0; + const char *iface = 0; + int rc; + + rc = sd_bus_message_read (m, "o", &path); + if (rc < 0) { + fprintf (stderr, "%s: failed to parse method call: %s\n", + blurb(), strerror(-rc)); + return rc; + } + + iface = sd_bus_message_get_interface (m); + sender = sd_bus_message_get_sender (m); + remove_matching_entry (ctx, path, sender, iface); + return 0; +} + + +/* Called when "org.kde.Solid.PowerManagement.PolicyAgent" (powerdevil) + sends out a broadcast announcement that some other process has received + or relinquished an inhibitor lock. Anyone can receive this signal: it + is non-exclusive. + */ +static int +xscreensaver_kde_inhibitor_changed_cb (sd_bus_message *m, void *arg, + sd_bus_error *err) +{ + /* When an inhibitor is being added, this message contains the appname + and reason as an array of two strings. When one is being removed, the + message contains an empty array, followed by an array of one element, + the appname. So one can tell "inhibit" and "uninhibit" apart, but + can't tell *which* inhibition was being removed, since the uninhibit + message doesn't contain the reason or a cookie. So that's pretty + worthless. + + Instead of parsing the contents of this message, any time we get + "InhibitionsChanged", we send a "ListInhibitions" message and parse + the result of that instead: the current list of inhibitions. + */ + struct handler_ctx *ctx = arg; + sd_bus *bus = sd_bus_message_get_bus (m); + sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus_message *reply = 0; + const char *dest = DBUS_KDE_INTERFACE; + const char *path = DBUS_KDE_PATH; + const char *interface = dest; + const char *msg = "ListInhibitions"; + struct inhibit_entry *entry, *entry_next; + int rc; + + rc = sd_bus_message_new_method_call (bus, &m, dest, path, interface, msg); + if (rc < 0) { + fprintf (stderr, "%s: KDE: failed to create message: %s%s: %s\n", + blurb(), interface, msg, strerror(-rc)); + return rc; + } + + sd_bus_message_set_auto_start (m, 1); + sd_bus_message_set_expect_reply (m, 1); + + rc = sd_bus_call (bus, m, -1, &error, &reply); + if (rc < 0) { + fprintf (stderr, "%s: KDE: call failed: %s.%s: %s\n", + blurb(), interface, msg, strerror(-rc)); + return rc; + } + + m = reply; + + /* It's an array of an arbitrary number of structs of 2 strings each. */ + rc = sd_bus_message_enter_container (m, 'a', "(ss)"); + if (rc < 0) { + fprintf (stderr, "%s: KDE: enter container failed: %s.%s: %s\n", + blurb(), interface, msg, strerror(-rc)); + return rc; + } + + /* Since they don't give us the original inhibiting cookie, we have to + assume that the appname/reason pair is unique: that means that if + someone sends two identical inhibits, that counts as one. No nesting. + */ + interface = "KDE"; /* That string is so long */ + + /* Clear the "seen" flags for our internal tracking. */ + SLIST_FOREACH_SAFE (entry, &inhibit_head, entries, entry_next) { + if (!strcmp (entry->cookie, INTERNAL_KDE_COOKIE)) + entry->seen_p = False; + } + + /* Iterate over each entry in this message reply. + */ + while (1) { + const char *appname = 0, *reason = 0; + Bool seen_p = False; + + rc = sd_bus_message_read (m, "(ss)", &appname, &reason); + if (rc < 0) { + fprintf (stderr, "%s: KDE: message read failed: %s.%s: %s\n", + blurb(), interface, msg, strerror(-rc)); + return rc; + } + + if (rc == 0) break; + + /* Tag any existing entries that match an entry in the new list. + */ + SLIST_FOREACH_SAFE (entry, &inhibit_head, entries, entry_next) { + if (!strcmp (entry->cookie, INTERNAL_KDE_COOKIE) && + !strcmp (entry->appname, appname) && + !strcmp (entry->reason, reason)) { + entry->seen_p = True; + seen_p = True; } - free(entry); - ctx->is_inhibited--; - if (ctx->is_inhibited < 0) - ctx->is_inhibited = 0; - found = 1; - break; + } + + /* Add a new entry if this one is not already in the list. + */ + if (! seen_p) { + entry = add_new_entry (ctx, INTERNAL_KDE_COOKIE, appname, 0, + interface, reason); + entry->seen_p = True; } } - if (! found) - fprintf (stderr, "%s: uninhibit: no match for cookie %08X\n", - blurb(), cookie); + /* Remove any existing entries that were not mentioned. + */ + SLIST_FOREACH_SAFE (entry, &inhibit_head, entries, entry_next) { + if (!strcmp (entry->cookie, INTERNAL_KDE_COOKIE) && + !entry->seen_p) { + if (verbose_p) + fprintf (stderr, + "%s: uninhibited by \"%s\" (via %s) with \"%s\"%s\n", + blurb(), remove_dir (entry->appname), + interface, + entry->reason, + (entry->ignored_p ? " (ignored)" : "")); + SLIST_REMOVE (&inhibit_head, entry, inhibit_entry, entries); + free_entry (ctx, entry); + } + } - return sd_bus_reply_method_return (m, ""); + return 0; } -/* - * This vtable defines the service interface we implement. + +/* This vtable defines the services we implement on the + "org.freedesktop.ScreenSaver" endpoint. */ -static const sd_bus_vtable -xscreensaver_dbus_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_METHOD("Inhibit", "ss", "u", xscreensaver_method_inhibit, - SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("UnInhibit", "u", "", xscreensaver_method_uninhibit, - SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_VTABLE_END +static const sd_bus_vtable xscreensaver_dbus_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD ("Inhibit", "ss", "u", xscreensaver_inhibit_cb, + SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD ("UnInhibit", "u", "", xscreensaver_uninhibit_cb, + SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_VTABLE_END }; @@ -668,6 +1148,55 @@ process_name (pid_t pid) } +/* Whether the given service name is currently registered on the bus. + */ +static Bool +service_exists_p (sd_bus *bus, const char *name) +{ + int rc; + sd_bus_message *m = 0; + sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus_message *reply = 0; + int ret = 0; + const char *dest = DBUS_SERVICE_DBUS; + const char *path = DBUS_PATH_DBUS; + const char *interface = DBUS_INTERFACE_DBUS; + const char *msg = "NameHasOwner"; + + rc = sd_bus_message_new_method_call (bus, &m, dest, path, interface, msg); + if (rc < 0) { + fprintf (stderr, "%s: dbus_send: failed to create message: %s%s: %s\n", + blurb(), interface, msg, strerror(-rc)); + return 0; + } + + rc = sd_bus_message_append (m, "s", name); + if (rc < 0) { + fprintf (stderr, "%s: dbus_send: failed append arg: %s%s: %s\n", + blurb(), interface, msg, strerror(-rc)); + return 0; + } + + sd_bus_message_set_auto_start (m, 1); + + rc = sd_bus_call (bus, m, -1, &error, &reply); + if (rc < 0) { + fprintf (stderr, "%s: dbus_send: call failed: %s.%s: %s\n", + blurb(), interface, msg, strerror(-rc)); + return 0; + } + + rc = sd_bus_message_read (reply, "b", &ret); + if (rc < 0) { + fprintf (stderr, "%s: dbus_send: failed to read reply: %s.%s: %s\n", + blurb(), interface, msg, strerror(-rc)); + return 0; + } + + return ret; +} + + static int xscreensaver_systemd_loop (void) { @@ -678,10 +1207,23 @@ xscreensaver_systemd_loop (void) time_t last_deactivate_time = 0; Display *dpy = open_dpy(); - /* 'user_bus' is where we receive messages from other programs sending - inhibit/uninhibit to org.freedesktop.ScreenSaver, etc. + /* 'system_bus' is where we hold a lock on "org.freedesktop.login1", meaning + that we will receive a "PrepareForSleep" message when the system is about + to suspend. + + 'user_bus' is where we receive messages from other programs sending + "Inhibit" and "Uninhibit" requests to "org.freedesktop.ScreenSaver", and + notifications of same from "org.gnome.SessionManager" and + "org.kde.Solid.PowerManagement.PolicyAgent". */ + rc = sd_bus_open_system (&system_bus); + if (rc < 0) { + fprintf (stderr, "%s: system bus connection failed: %s\n", + blurb(), strerror(-rc)); + goto FAIL; + } + rc = sd_bus_open_user (&user_bus); if (rc < 0) { fprintf (stderr, "%s: user bus connection failed: %s\n", @@ -703,106 +1245,239 @@ xscreensaver_systemd_loop (void) goto FAIL; } - rc = sd_bus_add_object_vtable (user_bus, - NULL, - DBUS_FDO_OBJECT_PATH, - DBUS_FDO_INTERFACE, - xscreensaver_dbus_vtable, - &global_ctx); - if (rc < 0) { - fprintf (stderr, "%s: vtable registration failed: %s\n", - blurb(), strerror(-rc)); - goto FAIL; - } - - rc = sd_bus_add_object_vtable (user_bus, - NULL, - DBUS_FDO_OBJECT_PATH_2, - DBUS_FDO_INTERFACE, - xscreensaver_dbus_vtable, - &global_ctx); - if (rc < 0) { - fprintf (stderr, "%s: vtable registration failed: %s\n", - blurb(), strerror(-rc)); - goto FAIL; + /* Exit if "org.jwz.XScreenSaver" is already registered on the user bus. + We don't receive any events on that endpoint, but this is a good mutex + to prevent more than one copy of "xscreensaver-systemd" from running. + */ + { + const char *name = DBUS_CLIENT_NAME; + rc = sd_bus_request_name (user_bus, name, 0); + if (rc < 0) { + pid_t pid = get_bus_name_pid (user_bus, name); + if (pid != -1) { + char *pname = process_name (pid); + if (pname) { + fprintf (stderr, "%s: \"%s\" in use by pid %lu (%s)\n", + blurb(), name, (unsigned long) pid, remove_dir (pname)); + free (pname); + } else { + fprintf (stderr, "%s: \"%s\" in use by pid %lu\n", + blurb(), name, (unsigned long) pid); + } + } else if (-rc == EEXIST || -rc == EALREADY) { + fprintf (stderr, "%s: \"%s\" already in use\n", blurb(), name); + } else { + fprintf (stderr, "%s: unknown error: \"%s\": %s\n", + blurb(), name, strerror(-rc)); + } + goto FAIL; + } else if (verbose_p) { + fprintf (stderr, "%s: registered as \"%s\"\n", blurb(), name); + } } + + /* Register ourselves as "org.freedesktop.ScreenSaver" if possible. + If "org.gnome.SessionManager" or "org.kde.Solid.PowerManagement. + PolicyAgent" are registered, this is optional; otherwise it is + mandatory. + */ { - const char * const names[] = { DBUS_FDO_NAME, DBUS_CLIENT_NAME }; - int i = 0; - for (i = 0; i < countof(names); i++) { - rc = sd_bus_request_name (user_bus, names[i], 0); - if (rc < 0) { - pid_t pid = get_bus_name_pid (user_bus, names[i]); - if (pid != -1) { - char *pname = process_name (pid); - if (pname) { - fprintf (stderr, - "%s: connection failed: \"%s\" in use by pid %lu (%s)\n", - blurb(), names[i], (unsigned long) pid, pname); + const char *gname = DBUS_GSN_INTERFACE; + const char *kname = DBUS_KDE_INTERFACE; + const char *name = DBUS_FDO_NAME; + Bool fd_p = False; + Bool gnome_p = False; + Bool kde_p = False; + time_t start = time ((time_t *) 0); + time_t now = start; + int timeout = 30; + int retries = 0; + + rc = sd_bus_request_name (user_bus, name, 0); + if (rc >= 0) { + fd_p = True; + if (verbose_p) + fprintf (stderr, "%s: registered as \"%s\"\n", blurb(), name); + } else { + pid_t pid = get_bus_name_pid (user_bus, name); + if (pid != -1) { + char *pname = process_name (pid); + if (verbose_p) { + fprintf (stderr, "%s: \"%s\" in use by pid %lu (%s)\n", + blurb(), name, (unsigned long) pid, remove_dir (pname)); + free (pname); + } else { + if (verbose_p) + fprintf (stderr, "%s: \"%s\" in use by pid %lu\n", + blurb(), name, (unsigned long) pid); + } + } else if (-rc == EEXIST || -rc == EALREADY) { + if (verbose_p) + fprintf (stderr, "%s: \"%s\" already in use\n", blurb(), name); + } else { + fprintf (stderr, "%s: unknown error for \"%s\": %s\n", + blurb(), name, strerror(-rc)); + } + } + + /* If XScreenSaver was launched at login, it's possible that + "org.freedesktop.ScreenSaver" has been registered by "ksmserver" but + "org.kde.Solid.PowerManagement.PolicyAgent" hasn't yet been registered + by "org_kde_powerdevil". So give it 30 seconds to see if things + settle down. + */ + while (1) { + gnome_p = service_exists_p (user_bus, gname); + kde_p = service_exists_p (user_bus, kname); + if (fd_p || gnome_p || kde_p) + break; + now = time ((time_t *) 0); + if (now >= start + timeout) + break; + + retries++; + sleep (3); + } + + if (verbose_p) { + int i = 0; + for (i = 0; i < 2; i++) { + Bool exists_p = (i == 0 ? gnome_p : kde_p); + const char *name = (i == 0 ? gname : kname); + char rr[20]; + if (now == start) + *rr = 0; + else + sprintf (rr, " after %lu seconds", now - start); + + if (exists_p) { + pid_t pid = get_bus_name_pid (user_bus, name); + if (pid != -1) { + char *pname = process_name (pid); + fprintf (stderr, "%s: \"%s\" in use by pid %lu (%s)%s\n", + blurb(), name, (unsigned long) pid, remove_dir (pname), + rr); free (pname); } else { - fprintf (stderr, - "%s: connection failed: \"%s\" in use by pid %lu\n", - blurb(), names[i], (unsigned long) pid); + fprintf (stderr, "%s: \"%s\" in use%s\n", blurb(), name, rr); } - } else if (-rc == EEXIST || -rc == EALREADY) { - fprintf (stderr, "%s: connection failed: \"%s\" already in use\n", - blurb(), names[i]); } else { - fprintf (stderr, "%s: connection failed for \"%s\": %s\n", + fprintf (stderr, "%s: \"%s\" not in use%s\n", blurb(), name, rr); + } + } + } + + if (! (fd_p || gnome_p || kde_p)) /* Must have at least one */ + goto FAIL; + + /* Register our callbacks for things sent to + "org.freedesktop.ScreenSaver /ScreenSaver" and + "org.freedesktop.ScreenSaver /org/freedesktop/ScreenSaver" + */ + if (fd_p) { + const char * const names[] = { DBUS_FDO_OBJECT_PATH_1, + DBUS_FDO_OBJECT_PATH_2 }; + int i = 0; + for (i = 0; i < countof(names); i++) { + rc = sd_bus_add_object_vtable (user_bus, NULL, names[i], + DBUS_FDO_INTERFACE, + xscreensaver_dbus_vtable, + &global_ctx); + if (rc < 0) { + fprintf (stderr, "%s: vtable registration failed for %s: %s\n", blurb(), names[i], strerror(-rc)); + goto FAIL; } - goto FAIL; } } } - /* 'system_bus' is where we hold a lock on org.freedesktop.login1, meaning - that the system will send us a PrepareForSleep message when the system is - about to suspend. - */ - rc = sd_bus_open_system (&system_bus); + /* Obtain a lock fd from the "Inhibit" method, so that we can delay + sleep when a "PrepareForSleep" signal is posted. + */ + ctx->system_bus = system_bus; + rc = xscreensaver_register_sleep_lock (ctx); + if (rc < 0) + goto FAIL; + + /* Register a callback for "org.freedesktop.login1.Manager.PrepareForSleep". + System bus. + */ + rc = sd_bus_add_match (system_bus, NULL, DBUS_SD_MATCH, + xscreensaver_prepare_for_sleep_cb, &global_ctx); if (rc < 0) { - fprintf (stderr, "%s: system bus connection failed: %s\n", + fprintf (stderr, "%s: registering sleep callback failed: %s\n", blurb(), strerror(-rc)); goto FAIL; } - /* Obtain a lock fd from the "Inhibit" method, so that we can delay - sleep when a "PrepareForSleep" signal is posted. */ + /* Register a callback for "org.freedesktop.login1.Session.Lock". + System bus. + */ + rc = sd_bus_add_match (system_bus, NULL, DBUS_SD_LOCK_MATCH, + xscreensaver_lock_cb, &global_ctx); + if (rc < 0) { + fprintf (stderr, "%s: registering lock-session callback failed: %s\n", + blurb(), strerror(-rc)); + goto FAIL; + } - ctx->system_bus = system_bus; - rc = xscreensaver_register_sleep_lock (ctx); - if (rc < 0) + /* And for Unlock. */ + rc = sd_bus_add_match (system_bus, NULL, DBUS_SD_UNLOCK_MATCH, + xscreensaver_unlock_cb, &global_ctx); + if (rc < 0) { + fprintf (stderr, "%s: registering lock-session callback failed: %s\n", + blurb(), strerror(-rc)); goto FAIL; + } - /* This is basically an event mask, saying that we are interested in - "PrepareForSleep", and to run our callback when that signal is thrown. - */ - rc = sd_bus_add_match (system_bus, NULL, DBUS_SD_MATCH, - xscreensaver_systemd_handler, - &global_ctx); + + /* Register a callback for "org.gnome.SessionManager.InhibitorAdded". + User bus. + */ + rc = sd_bus_add_match (user_bus, NULL, DBUS_GSN_MATCH_1, + xscreensaver_gnome_inhibitor_added_cb, &global_ctx); if (rc < 0) { - fprintf (stderr, "%s: add match failed: %s\n", blurb(), strerror(-rc)); + fprintf (stderr, "%s: registering GNOME inhibitor callback failed: %s\n", + blurb(), strerror(-rc)); goto FAIL; } - if (verbose_p) - fprintf (stderr, "%s: connected\n", blurb()); + /* Register a callback for "org.gnome.SessionManager.InhibitorRemoved". + User bus. + */ + rc = sd_bus_add_match (user_bus, NULL, DBUS_GSN_MATCH_2, + xscreensaver_gnome_inhibitor_removed_cb, &global_ctx); + if (rc < 0) { + fprintf (stderr, "%s: registering GNOME de-inhibitor callback failed: %s\n", + blurb(), strerror(-rc)); + goto FAIL; + } + + /* Register a callback for "org.kde.Solid.PowerManagement.PolicyAgent. + InhibitionsChanged". User bus. + */ + rc = sd_bus_add_match (user_bus, NULL, DBUS_KDE_MATCH, + xscreensaver_kde_inhibitor_changed_cb, &global_ctx); + if (rc < 0) { + fprintf (stderr, "%s: registering KDE inhibitor callback failed: %s\n", + blurb(), strerror(-rc)); + goto FAIL; + } - /* Run an event loop forever, and wait for our callback to run. + /* Run an event loop forever, and wait for our callbacks to run. */ while (1) { struct pollfd fds[3]; - uint64_t poll_timeout, system_timeout, user_timeout; - struct inhibit_entry *entry; + uint64_t poll_timeout_msec, system_timeout_usec, user_timeout_usec; /* We MUST call sd_bus_process() on each bus at least once before calling sd_bus_get_events(), so just always start the event loop by processing - all outstanding requests on both busses. */ + all outstanding requests on both busses. + */ do { rc = sd_bus_process (system_bus, NULL); if (rc < 0) { @@ -821,97 +1496,128 @@ xscreensaver_systemd_loop (void) } } while (rc > 0); - fds[0].fd = sd_bus_get_fd (system_bus); - fds[0].events = sd_bus_get_events (system_bus); + /* Prune any entries whose original sender has gone away: this happens + if a program inhibits, then exits without having called uninhibit. + That would have left us inhibited forever, even if the inhibiting + program was re-launched, since the new instance won't have the + same cookie. + */ + remove_matching_entry (ctx, NULL, 0, 0); + + /* If we are inhibited and HEARTBEAT_INTERVAL has passed, de-activate the + screensaver. + */ + if (ctx->inhibit_count > 0) { + time_t now = time ((time_t *) 0); + if (now - last_deactivate_time >= HEARTBEAT_INTERVAL) { + if (verbose_p) { + struct inhibit_entry *entry; + SLIST_FOREACH (entry, &inhibit_head, entries) { + char ct[100]; + ctime_r (&entry->start_time, ct); + fprintf (stderr, "%s: inhibited by \"%s\" since %s", + blurb(), remove_dir (entry->appname), ct); + } + } + xscreensaver_command ("deactivate"); + last_deactivate_time = now; + } + } + + /* The remainder of the code that follows is concerned solely with + determining how long we should wait until the next iteration of the + event loop. + */ + rc = sd_bus_get_fd (system_bus); + if (rc < 0) { + fprintf (stderr, "%s: sd_bus_get_fd failed for system bus: %s\n", + blurb(), strerror(-rc)); + goto FAIL; + } + fds[0].fd = rc; + rc = sd_bus_get_events (system_bus); + if (rc < 0) { + fprintf (stderr, "%s: sd_bus_get_events failed for system bus: %s\n", + blurb(), strerror(-rc)); + goto FAIL; + } + fds[0].events = rc; fds[0].revents = 0; - fds[1].fd = sd_bus_get_fd (user_bus); - fds[1].events = sd_bus_get_events (user_bus); + rc = sd_bus_get_fd (user_bus); + if (rc < 0) { + fprintf (stderr, "%s: sd_bus_get_fd failed for user bus: %s\n", + blurb(), strerror(-rc)); + goto FAIL; + } + fds[1].fd = rc; + rc = sd_bus_get_events (user_bus); + if (rc < 0) { + fprintf (stderr, "%s: sd_bus_get_events failed for user bus: %s\n", + blurb(), strerror(-rc)); + goto FAIL; + } + fds[1].events = rc; fds[1].revents = 0; + /* Activity on the X server connection will wake us from the poll(). */ fds[2].fd = XConnectionNumber (dpy); fds[2].events = POLLIN; fds[2].revents = 0; - - sd_bus_get_timeout (system_bus, &system_timeout); - sd_bus_get_timeout (user_bus, &user_timeout); - - if (system_timeout == 0 && user_timeout == 0) - poll_timeout = 0; - else if (system_timeout == UINT64_MAX && user_timeout == UINT64_MAX) - poll_timeout = -1; - else { - poll_timeout = (system_timeout < user_timeout - ? system_timeout : user_timeout); - poll_timeout /= 1000000; + rc = sd_bus_get_timeout (system_bus, &system_timeout_usec); + if (rc < 0) { + fprintf (stderr, "%s: sd_bus_get_timeout failed for system bus: %s\n", + blurb(), strerror(-rc)); + goto FAIL; } - - /* Prune any entries whose original sender has gone away: this happens - if a program inhibits, then exits without having called uninhibit. - That would have left us inhibited forever, even if the inhibiting - program was re-launched, since the new instance won't have the - same cookie. */ - SLIST_FOREACH (entry, &inhibit_head, entries) { - if (entry->peer && - !sd_bus_track_count_name (ctx->track, entry->peer)) { - if (verbose_p) - fprintf (stderr, - "%s: peer %s for inhibiting app \"%s\" has died:" - " uninhibiting %08X\n", - blurb(), - entry->peer, - entry->appname, - entry->cookie); - SLIST_REMOVE (&inhibit_head, entry, inhibit_entry, entries); - if (entry->appname) free (entry->appname); - free(entry->peer); - free (entry); - ctx->is_inhibited--; - if (ctx->is_inhibited < 0) - ctx->is_inhibited = 0; - } + sd_bus_get_timeout (user_bus, &user_timeout_usec); + if (rc < 0) { + fprintf (stderr, "%s: sd_bus_get_timeout failed for user bus: %s\n", + blurb(), strerror(-rc)); + goto FAIL; } + /* Pick the smaller of the two bus timeouts and convert from microseconds + to milliseconds expected by poll(). + */ + poll_timeout_msec = ((system_timeout_usec < user_timeout_usec + ? system_timeout_usec : user_timeout_usec) + / 1000); - /* We want to wake up at least once every N seconds to de-activate - the screensaver if we have been inhibited. + /* If we have been inhibited, we want to wake up at least once every N + seconds to de-activate the screensaver. */ - if (poll_timeout > HEARTBEAT_INTERVAL * 1000) - poll_timeout = HEARTBEAT_INTERVAL * 1000; + if (ctx->inhibit_count > 0 && + poll_timeout_msec > HEARTBEAT_INTERVAL * 1000) + poll_timeout_msec = HEARTBEAT_INTERVAL * 1000; + + if (poll_timeout_msec < 1000) + poll_timeout_msec = 1000; - rc = poll (fds, 3, poll_timeout); + rc = poll (fds, 3, poll_timeout_msec); if (rc < 0) { - fprintf (stderr, "%s: poll failed: %s\n", blurb(), strerror(-rc)); - exit (EXIT_FAILURE); + fprintf (stderr, "%s: poll failed: %s\n", blurb(), strerror(errno)); + goto FAIL; } if (fds[2].revents & (POLLERR | POLLHUP | POLLNVAL)) { fprintf (stderr, "%s: X connection closed\n", blurb()); + dpy = 0; goto FAIL; + } else if (fds[2].revents & POLLIN) { + /* Even though we have requested no events, there are some events that + the X server sends anyway, e.g. MappingNotify, and if we don't flush + the fd, it will constantly be ready for reading, and we busy-loop. */ + char buf[1024]; + while (read (fds[2].fd, buf, sizeof(buf)) > 0) + ; } - if (ctx->is_inhibited) { - time_t now = time ((time_t *) 0); - if (now - last_deactivate_time >= HEARTBEAT_INTERVAL) { - if (verbose_p) { - SLIST_FOREACH (entry, &inhibit_head, entries) { - char ct[100]; - ctime_r (&entry->start_time, ct); - fprintf (stderr, "%s: inhibited by \"%s\" since %s", - blurb(), entry->appname, ct); - } - } - xscreensaver_command ("deactivate"); - last_deactivate_time = now; - } - } - } + } /* Event loop end */ FAIL: - XCloseDisplay(dpy); - if (system_bus) sd_bus_flush_close_unref (system_bus); @@ -923,6 +1629,9 @@ xscreensaver_systemd_loop (void) sd_bus_error_free (&error); + if (dpy) + XCloseDisplay (dpy); + return EXIT_FAILURE; } @@ -959,9 +1668,7 @@ main (int argc, char **argv) screensaver_version = version; - progname = argv[0]; - s = strrchr (progname, '/'); - if (s) progname = s+1; + progname = remove_dir (argv[0]); for (i = 1; i < argc; i++) { @@ -970,8 +1677,21 @@ main (int argc, char **argv) if (s[0] == '-' && s[1] == '-') s++; L = strlen (s); if (L < 2) USAGE (); - else if (!strncmp (s, "-verbose", L)) verbose_p = 1; + else if (!strncmp (s, "-q", L)) verbose_p = 0; else if (!strncmp (s, "-quiet", L)) verbose_p = 0; + else if (!strncmp (s, "-verbose", L)) verbose_p++; + else if (!strncmp (s, "-v", L)) verbose_p++; + else if (!strncmp (s, "-vv", L)) verbose_p += 2; + else if (!strncmp (s, "-vvv", L)) verbose_p += 3; + else if (!strncmp (s, "-vvvv", L)) verbose_p += 4; + else if (!strcmp (s, "-ver") || + !strcmp (s, "-vers") || + !strcmp (s, "-version")) + { + fprintf (stderr, "%s\n", screensaver_id+4); + exit (1); + } + else USAGE (); } diff --git a/driver/xscreensaver-systemd.man b/driver/xscreensaver-systemd.man index beb23e7..def72a5 100644 --- a/driver/xscreensaver-systemd.man +++ b/driver/xscreensaver-systemd.man @@ -1,11 +1,11 @@ -.TH XScreenSaver 1 "6-Jan-2021 (6.00)" "X Version 11" +.TH XScreenSaver 1 "6-Jan-2022 (6.00)" "X Version 11" .SH NAME xscreensaver-systemd - lock the screen upon suspend, and inhibit screen-blanking during video playback. .SH SYNOPSIS -.B xscreensaver-systemd [-verbose] +.B xscreensaver-systemd [\-\-verbose] [\-\-version] .SH DESCRIPTION -The \fIxscreensaver\-systemd\fP program is a helper program launched by +The \fIxscreensaver\-systemd\fP program is a helper daemon launched by .BR xscreensaver (1) for .BR systemd (1) @@ -16,30 +16,63 @@ integration. It does two things: \fB* When the system is about to go to sleep (e.g., the laptop lid has just been closed) it locks the screen just \fIbefore\fP the system sleeps, by running -\fIxscreensaver-command -suspend\fP. When the system wakes up again, it -runs \fIxscreensaver-command -deactivate\fP to make the unlock dialog appear +\fIxscreensaver-command \-\-suspend\fP. When the system wakes up again, it +runs \fIxscreensaver\-command \-\-deactivate\fP to make the unlock dialog appear immediately. It does this through the .BR org.freedesktop.login1 (5) D-Bus interface. .TP 2 \fB* -When another process on the system asks for the screen saver to be -inhibited (e.g. because a video is playing) this program periodically runs -\fIxscreensaver-command -deactivate\fP to keep the display un-blanked. -It does this until the other program asks for it to stop, or exits. -It does this through the -.BR org.freedesktop.ScreenSaver (5) -D-Bus interface. +When another process asks for the screen saver to be inhibited (e.g. because +a video is playing) this program periodically runs \fIxscreensaver\-command +\-\-deactivate\fP to keep the display un-blanked. It does this until that +other program asks for it to stop, or exits. It does this through the +.BR org.freedesktop.ScreenSaver (5), +.BR org.gnome.SessionManager (5) +and +.BR org.kde.Solid.PowerManagement.PolicyAgent (5) +D-Bus interfaces. .RS 0 .SH BUGS +Blanking-related problems take two forms: failing to connect to D-Bus; and +other programs requesting stupid things. +.SS D-BUS Only one program at a time can register with .BR systemd (1) +or +.BR elogind (8) for screen-saver-related events, so if another screen saver is running as a -part of the desktop environment, \fIxscreensaver-systemd\fP will be unable to -launch. Likely adversaries include -.BR ksmserver (1) -and -.BR gsd\-screensaver\-proxy (1). +part of the desktop environment, \fIxscreensaver\-systemd\fP will be unable +to launch. This program has workarounds for Gnome and KDE, but if you are +using something else, that might be a problem. +.SS CHROME & CHROMIUM (101.0) +.TP 2 +\fB* +Playing only audio in Chrome will prevent your screen from blanking. Chrome +sends the exact same "inhibit" reason for audio as for video, so we can't tell +them apart. +.TP 2 +\fB* +Chrome prevents your screen from blanking when playing short, looping videos. +This means that your screen won't blank when Chrome is showing a Twitter page +(because those animated GIFs aren't really GIFs, they are MP4s). This affects +Tweetdeck as well. + +The proper way to fix this would be for Chrome to stop inhibiting once a video +loops. That way your multi-hour movie inhibits properly, but your looping GIF +only inhibits for the first few seconds. +.PP +Firefox does not have either of these problems. +.SS MPLAYER (2:1.4) +Makes no attempt to inhibit the screen saver. Use VLC instead. +.SS MPV (0.33) +Makes no attempt to inhibit the screen saver. Use VLC instead. +.SS VARIOUS +Most programs fail to re-enable screen blanking if they crash or are killed +while playing. We try to detect when this has happened, but that might not +work with all versions of systemd, resulting in screen blanking remaining +permanently disabled. If that happens, killing and restarting +\fIxscreensaver\-systemd\fP is a workaround. .SH SEE ALSO .BR X (1), .BR xscreensaver (1), @@ -49,7 +82,7 @@ and .BR sd-bus (3), .BR elogind (8) .SH COPYRIGHT -Copyright \(co 2019-2021 by Martin Lucina and Jamie Zawinski. +Copyright \(co 2019-2022 by Martin Lucina and Jamie Zawinski. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear diff --git a/driver/xscreensaver-text b/driver/xscreensaver-text deleted file mode 100755 index eca1fbf..0000000 --- a/driver/xscreensaver-text +++ /dev/null @@ -1,899 +0,0 @@ -#!/usr/bin/perl -w -# Copyright © 2005-2019 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.48 $' =~ 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 - # "á" 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/•/gs; - $text =~ s/\xE2\x80\xA6/.../gs; - $text =~ s/\xE2\x80\xB2/'/gs; - $text =~ s/\xE2\x84\xA2/™/gs; - $text =~ s/\xE2\x86\x90/ ← /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; - - # I don't understand why we must do this here, but must not do this - # in the 'file' branch above, which reads the file with :raw... - utf8::decode ($body); # Pack multi-byte UTF-8 back into wide chars. - - $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, $author, $body1, $body2, $body3); - - $title = $3 if (m@<((TITLE) [^<>\s]*)[^<>]*>\s*(.*?)\s*</\1>@xsi); - $author= $3 if (m@<((DC:CREATOR) [^<>\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 || ''); - $author= rss_field_to_html ($author || ''); - $body1 = rss_field_to_html ($body1 || ''); - - $title = '' if ($body1 eq $title); # Identical in Twitter's atom feed. - - # Omit author if it's in the title or body - $author = '' if ($author && - ($title =~ m/\Q$author\E/si || - $body1 =~ m/\Q$author\E/si)); - - $title = $author if ($author && !$title); - $title = "$author: $title" if ($author && $title); - - $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 deleted file mode 100644 index dcedc3b..0000000 --- a/driver/xscreensaver-text.man +++ /dev/null @@ -1,85 +0,0 @@ -.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 index 5eb7167..b5e85a6 100644 --- a/driver/xscreensaver.c +++ b/driver/xscreensaver.c @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright © 1991-2021 Jamie Zawinski <jwz@jwz.org> +/* xscreensaver, Copyright © 1991-2023 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 @@ -8,7 +8,9 @@ * software for any purpose. It is provided "as is" without express or * implied warranty. * + * ========================================================================== * XScreenSaver Daemon, version 6. + * ========================================================================== * * Having started its life in 1991, XScreenSaver acquired a lot of optional * features to allow it to detect user activity, lock the screen and @@ -101,6 +103,95 @@ * causes the screen to lock, and how video players request that blanking * be inhibited. This program invokes "xscreensaver-command" as needed * to pass those requests along to "xscreensaver" via ClientMessages. + * + * + * ========================================================================== + * Ancient History + * ========================================================================== + * + * As mentioned above, XScreenSaver version 6 (released in 2021) requires + * the XInput 2 server extension for idle detection and password input. + * In XScreenSaver 5 and earlier (1991 through 2020), idle-detection was + * far more convoluted. Basically, we selected input events on every window, + * and noticed when new windows were created so that we could track them too. + * Over the decades, there have also been three optional X11 server extensions + * that were applicable to screen saving: + * + * - XIdle + * + * This extension provided a function to poll the user's idle time. + * It was simple and direct and worked great. Therefore, it was + * removed from the X11 distribution in 1994, with the release of + * X11R6. https://bugs.freedesktop.org/show_bug.cgi?id=1419 + * + * - SGI SCREEN_SAVER + * + * This extension sent two new events: "user is idle", and "user is no + * longer idle". It was simple and direct and worked great. But as + * the name implies, it only ever worked on Silicon Graphics machines. + * SGI became irrelevant around 1998 and went out of business in 2009. + * + * - MIT-SCREEN-SAVER + * + * This extension still exists, but it is useless to us. Since it still + * exists, people sometimes ask why XScreenSaver no longer makes use of it. + * The MIT-SCREEN-SAVER extension took the following approach: + * + * - When the user is idle, immediately map full screen black windows + * on each screen. + * + * - Inform the screen saver client that the screen is now black. + * + * - When user activity occurs, unmap the windows and then inform the + * screen saver client. + * + * The screen saver client was able to specify a few parameters of that + * window, like visual and depth, but that was all. + * + * The extension was designed with the assumption that a screen saver would + * render onto the provided window. However: + * + * - OpenGL programs may require different visuals than 2D X11 programs, + * and you can't change the visual of a window after it has been + * created. + * + * - The extension mapped one window per X11 "Screen", which, in this + * modern world, tend to span the entire virtual desktop; whereas + * XScreenSaver runs savers full screen on each *monitor* instead. + * In other words, it was incompatible with Xinerama / RANDR. + * + * - Since this extension mapped its own full-screen black windows and + * informed us of that after the fact, it precluded "fade" and "unfade" + * animations from being implemented. + * + * - Since it only told us when the user was idle or non-idle, it + * precluded the "hysteresis" option from being implemented (ignoring + * tiny mouse motions). + * + * In summary, it created its windows too early, removed them too late, + * created windows of the wrong quantity and wrong shape, could not create + * them with the proper visuals, and delivered too little information about + * what caused the user activity. + * + * Also, the MIT-SCREEN-SAVER extension was flaky, and using it at all led + * to frequent server crashes. Worse, those server crashes were highly + * dependent on your particular video driver. + * + * So that's why, even if the server supports the MIT-SCREEN-SAVER + * extension, we don't use it. + * + * Some video players attempt to inhibit blanking during playback by + * calling XResetScreenSaver and/or XScreenSaverSuspend, which affect + * only the X server's *built-in* screen saver, and the window created + * by the MIT-SCREEN-SAVER extension. To rely upon those calls alone + * and then call it a day would betray a willful ignorance of why the + * MIT-SCREEN-SAVER extension is a useless foundation upon which to + * build a screen saver or screen locker. + * + * Fortunately, every modern video player and web browser also makes + * use of systemd / logind / dbus to request blanking inhibition, and + * we receive those requests via our systemd integration in + * xscreensaver-systemd (which see). */ #ifdef HAVE_CONFIG_H @@ -134,6 +225,10 @@ # include <sys/wait.h> /* for waitpid() and associated macros */ #endif +#ifndef HAVE_XINPUT +# error The XInput2 extension is required +#endif + #include <X11/Xlib.h> #include <X11/Xutil.h> #include <X11/Xatom.h> @@ -168,6 +263,7 @@ static const char *version_number = 0; /* Preferences. */ static Bool lock_p = False; static Bool locking_disabled_p = False; +static Bool blanking_disabled_p = False; static unsigned int blank_timeout = 0; static unsigned int lock_timeout = 0; static unsigned int pointer_hysteresis = 0; @@ -456,8 +552,10 @@ handle_sigchld (Display *dpy, Bool blanked_p) int ac = 0; av[ac++] = SAVER_GFX_PROGRAM; av[ac++] = "--emergency"; - if (verbose_p) av[ac++] = "--verbose"; - if (debug_p) av[ac++] = "--debug"; + if (verbose_p) av[ac++] = "--verbose"; + if (verbose_p > 1) av[ac++] = "--verbose"; + if (verbose_p > 2) av[ac++] = "--verbose"; + if (debug_p) av[ac++] = "--debug"; av[ac] = 0; fprintf (stderr, "%s: pid %lu: " SAVER_GFX_PROGRAM " exited unexpectedly %s: re-launching\n", @@ -582,7 +680,9 @@ print_banner(void) /* Hey jerks, the only time someone will see this particular message is if they are running xscreensaver with '-log' in order to send me a bug report, and they had damned well - better try the latest release before they do that. */ + better try the latest release before they do that -- + even if your perma-out-of-date distro does not make that + easily available to them. */ "\t ###################################################\n" "\t ### ###\n" "\t ### THIS VERSION IS VERY OLD! PLEASE UPGRADE! ###\n" @@ -684,20 +784,24 @@ static void init_line_handler (int lineno, const char *key, const char *val, void *closure) { + if (*key == '*' || *key == '.') key++; /* Xrm wildcards */ + if (!strcmp (key, "verbose")) verbose_p = !strcasecmp (val, "true"); else if (!strcmp (key, "splash")) splash_p = !strcasecmp (val, "true"); else if (!strcmp (key, "lock")) lock_p = !strcasecmp (val, "true"); + else if (!strcmp (key, "mode")) blanking_disabled_p = + !strcasecmp (val, "off"); else if (!strcmp (key, "timeout")) { int t = parse_time (val); if (t > 0) blank_timeout = t; } - if (!strcmp (key, "lockTimeout")) + else if (!strcmp (key, "lockTimeout")) { int t = parse_time (val); if (t >= 0) lock_timeout = t; } - if (!strcmp (key, "pointerHysteresis")) + else if (!strcmp (key, "pointerHysteresis")) { int i = atoi (val); if (i >= 0) @@ -882,7 +986,9 @@ ensure_no_screensaver_running (Display *dpy) && type != None && (!strcmp ((char *) version, "gnome-screensaver") || !strcmp ((char *) version, "mate-screensaver") || - !strcmp ((char *) version, "cinnamon-screensaver"))) + !strcmp ((char *) version, "cinnamon-screensaver") || + !strcmp ((char *) version, "xfce4-screensaver") || + !strcmp ((char *) version, "light-locker"))) { fprintf (stderr, "%s: \"%s\" is already running on display %s" @@ -1130,7 +1236,7 @@ ungrab_mouse (Display *dpy) /* Some remote desktop clients (e.g., "rdesktop") hold the keyboard GRABBED the whole time they have focus! This is idiotic because the whole point of grabbing is to get events when you do *not* have focus, so grabbing only - when* you have focus is redundant. Anyway, that prevents us from getting a + *when* you have focus is redundant. Anyway, that prevents us from getting a keyboard grab. It turns out that for some of these apps, de-focusing them forces them to release their grab. @@ -1261,14 +1367,14 @@ grab_keyboard_and_mouse (Screen *screen) because I'm not completely convinced it is a safe thing to do. */ - if (kstatus != GrabSuccess) /* Do not blank without a kbd grab. */ + if (kstatus != GrabSuccess) /* Do not blank without a kbd grab. */ { /* If we didn't get both grabs, release the one we did get. */ ungrab_keyboard_and_mouse (dpy); return False; } - return True; /* Grab is good, go ahead and blank. */ + return True; /* Grab is good, go ahead and blank. */ } @@ -1304,6 +1410,8 @@ static void maybe_disable_locking (Display *dpy) { const char *why = 0; + Bool wayland_p = (getenv ("WAYLAND_DISPLAY") || + getenv ("WAYLAND_SOCKET")); # ifdef NO_LOCKING why = "locking disabled at compile time"; @@ -1322,7 +1430,7 @@ maybe_disable_locking (Display *dpy) /* X11 grabs don't work under Wayland's embedded X11 server. The Wayland window manager lives at a higher level than the X11 emulation layer. */ - if (!why && getenv ("WAYLAND_DISPLAY")) + if (!why && wayland_p) why = "cannot lock securely under Wayland"; if (!why) @@ -1344,6 +1452,25 @@ maybe_disable_locking (Display *dpy) fprintf (stderr, "%s: DEBUG MODE: allowing locking anyway!\n", blurb()); } + else if (wayland_p) + { + const char *s = blurb(); + locking_disabled_p = True; + + /* Maybe we should just refuse to launch instead? We can operate + properly only if the user uses only X11 programs, and doesn't + want to lock the screen. + */ + fprintf (stderr, "\n" + "%s: WARNING: Wayland is not supported.\n" + "\n" + "%s: Under Wayland, idle-detection fails when non-X11\n" + "%s: programs are selected, meaning the screen may\n" + "%s: blank prematurely. Also, locking is impossible.\n" + "%s: See the manual for instructions on configuring\n" + "%s: your system to use X11 instead of Wayland.\n\n", + s, s, s, s, s, s); + } else { locking_disabled_p = True; @@ -1377,6 +1504,14 @@ main_loop (Display *dpy) if (! init_xinput (dpy, &xi_opcode)) saver_exit (1); + /* Disable server built-in screen saver. */ + XSetScreenSaver (dpy, 0, 0, 0, 0); + XForceScreenSaver (dpy, ScreenSaverReset); + + /* It would be nice to sync the server's DPMS settings here to what is + specified in the .xscreensaver file, but xscreensaver-gfx handles that, + so that won't happen until the first time the screen blanks. */ + create_daemon_window (dpy); handle_signals(); @@ -1402,7 +1537,7 @@ main_loop (Display *dpy) saver_auth_pid = fork_and_exec (dpy, ac, av); } -# ifdef HAVE_LIBSYSTEMD +# if defined(HAVE_LIBSYSTEMD) || defined(HAVE_LIBELOGIND) /* Launch xscreensaver-systemd at startup. */ { char *av[10]; @@ -1413,7 +1548,7 @@ main_loop (Display *dpy) av[ac] = 0; saver_systemd_pid = fork_and_exec (dpy, ac, av); } -# endif /* HAVE_LIBSYSTEMD */ +# endif /* HAVE_LIBSYSTEMD || HAVE_LIBELOGIND */ /* X11 errors during startup initialization were fatal. @@ -1629,9 +1764,9 @@ main_loop (Display *dpy) linking with additional libraries, doing additional X protocol, and also some finicky error handling, since the DPMS extension is a pain in the ass. So instead, - I made xscreensaver-command do that instead. This - somewhat breaks the abstraction of ClientMessage - handling, but it's more robust. */ + I made xscreensaver-command:reset_dpms_timer() do that + instead. This somewhat breaks the abstraction of + ClientMessage handling, but it's more robust. */ } else if (msg == XA_LOCK) { @@ -1800,6 +1935,9 @@ main_loop (Display *dpy) case XI_RawKeyRelease: case XI_RawButtonPress: case XI_RawButtonRelease: + case XI_RawTouchBegin: + case XI_RawTouchEnd: + case XI_RawTouchUpdate: if (current_state != AUTH && /* logged by xscreensaver-auth */ (verbose_p > 1 || (verbose_p && now - active_at > 1))) @@ -1892,7 +2030,8 @@ main_loop (Display *dpy) (lock_p && now >= active_at + blank_timeout + lock_timeout))) { - fprintf (stderr, "%s: locking\n", blurb()); + if (verbose_p) + fprintf (stderr, "%s: locking\n", blurb()); if (grab_keyboard_and_mouse (mouse_screen (dpy))) { current_state = LOCKED; @@ -1909,16 +2048,25 @@ main_loop (Display *dpy) else if (force_blank_p || now >= active_at + blank_timeout) { - fprintf (stderr, "%s: blanking\n", blurb()); - if (grab_keyboard_and_mouse (mouse_screen (dpy))) + if (blanking_disabled_p && !force_blank_p) { - current_state = BLANKED; - blanked_at = now; - store_saver_status (dpy, True, False, now); + if (verbose_p) + fprintf (stderr, "%s: not blanking: disabled\n", blurb()); } else - fprintf (stderr, "%s: unable to grab -- blanking aborted!\n", - blurb()); + { + if (verbose_p) + fprintf (stderr, "%s: blanking\n", blurb()); + if (grab_keyboard_and_mouse (mouse_screen (dpy))) + { + current_state = BLANKED; + blanked_at = now; + store_saver_status (dpy, True, False, now); + } + else + fprintf (stderr, "%s: unable to grab -- blanking aborted!\n", + blurb()); + } } if (current_state == BLANKED || current_state == LOCKED) @@ -1930,9 +2078,11 @@ main_loop (Display *dpy) char *av[20]; int ac = 0; av[ac++] = SAVER_GFX_PROGRAM; - if (first_time_p) av[ac++] = "--init"; - if (verbose_p) av[ac++] = "--verbose"; - if (debug_p) av[ac++] = "--debug"; + if (first_time_p) av[ac++] = "--init"; + if (verbose_p) av[ac++] = "--verbose"; + if (verbose_p > 1) av[ac++] = "--verbose"; + if (verbose_p > 2) av[ac++] = "--verbose"; + if (debug_p) av[ac++] = "--debug"; if (blank_mode == XA_NEXT) av[ac++] = "--next"; @@ -2218,6 +2368,15 @@ main (int argc, char **argv) { logfile = argv[++i]; if (!logfile) goto HELP; + if (! verbose_p) /* might already be -vv */ + verbose_p = cmdline_verbose_p = cmdline_verbose_val = True; + } + else if (!strcmp (argv[i], "-ver") || + !strcmp (argv[i], "-vers") || + !strcmp (argv[i], "-version")) + { + fprintf (stderr, "%s\n", screensaver_id+4); + exit (1); } else if (!strcmp (argv[i], "-d") || !strcmp (argv[i], "-dpy") || @@ -2244,6 +2403,7 @@ main (int argc, char **argv) "\t\t--verbose\n" "\t\t--no-splash\n" "\t\t--log logfile\n" + "\t\t--version\n" "\n" "\tRun 'xscreensaver-settings' to configure.\n" "\n"); diff --git a/driver/xscreensaver.desktop.in b/driver/xscreensaver.desktop.in new file mode 100755 index 0000000..6fcb30a --- /dev/null +++ b/driver/xscreensaver.desktop.in @@ -0,0 +1,9 @@ +#!/usr/bin/env xdg-open +[Desktop Entry] +Exec=xscreensaver +Icon=xscreensaver +_Name=XScreenSaver +_Comment=XScreenSaver daemon: screen saver and locker +Type=Application +Categories=Screensaver;Security;GNOME;GTK;KDE;Motif;Qt +Terminal=false diff --git a/driver/xscreensaver.man b/driver/xscreensaver.man index 9c3bdd0..572c727 100644 --- a/driver/xscreensaver.man +++ b/driver/xscreensaver.man @@ -1,10 +1,11 @@ -.TH XScreenSaver 1 "6-Jan-2021 (6.00)" "X Version 11" +.TH XScreenSaver 1 "6-Jan-2022 (6.00)" "X Version 11" .SH NAME xscreensaver - extensible screen saver and screen locking framework .SH SYNOPSIS .B xscreensaver [\-\-display \fIhost:display.screen\fP] \ [\-\-verbose] \ +[\-\-version] \ [\-\-no\-splash] \ [\-\-log \fIfilename\fP] .SH DESCRIPTION @@ -24,7 +25,6 @@ program. xscreensaver-settings .sp .fi - .SH HOW IT WORKS When it is time to activate the screensaver, a full-screen black window is created that covers each monitor. A sub-process is launched for each one @@ -39,291 +39,223 @@ the running subprocesses are killed. The display modes are run at a low process priority, and spend most of their time sleeping/idle by default, so they should not consume significant system resources. - .SH COMMAND-LINE OPTIONS .TP 8 -.B \-\-display \fIhost:display.screen\fP +.B \-\-display\fP \fIhost:display.screen\fP The X display to use. For displays with multiple screens, XScreenSaver will manage all screens on the display simultaneously. .TP 8 .B \-\-verbose Print diagnostics to stderr. .TP 8 -.B \-\-log \fIfilename\fP +.B \-\-version\fP +Print the version number and exit. +.TP 8 +.B \-\-log\fP \fIfilename\fP Append all diagnostic output to the given file. This also implies \fI\-\-verbose\fP. Use this when reporting bugs. .TP 8 .B \-\-no\-splash Don't display the splash screen at startup. - .SH POWER MANAGEMENT The .BR xscreensaver\-settings (1) program is where you configure if and when your monitor should power off. It saves the settings in your \fI~/.xscreensaver\fP file. - -If the power management section is grayed out in the -.BR xscreensaver\-settings (1) -window, then that means that your X server does not support -the XDPMS extension, and so control over the monitor's power state -is not available. - -When the monitor is powered down, the display hacks are stopped -(though it may take a minute or two for XScreenSaver to notice). - -Note: if you use +Do not use .BR xset (1) -to change the power management settings, XScreenSaver will override those -changes. Whatever is in the \fI~/.xscreensaver\fP file takes precedence. +to manually change the power management settings, that won't work. +When the monitor is powered down, the display hacks will stop running +(though it may take a minute or two for XScreenSaver to notice). .SH LAPTOP LIDS -If your system has +If your system uses .BR systemd (1) -221 or newer, or +or .BR elogind (8), then closing the lid of your laptop will cause the screen to lock immediately. If not, then the screen might not lock until a few seconds \fIafter\fP you -re-open the lid. Which is less than ideal. So if you don't -use \fIsystemd\fP, you might want to get in the habit of -doing \fIxscreensaver-command --lock\fP before closing the lid. - +re-open the lid. Which is less than ideal. So if you do not have +.BR systemd (1), +you might want to get in the habit of manually locking your screen +\fIbefore\fP closing the lid (\fIxscreensaver\-command\ \-\-lock\fP). .SH PLAYING VIDEOS Likewise, if you have .BR systemd (1) -221 or newer, or +or .BR elogind (8), then all of the popular video players and web browsers will prevent XScreenSaver from blanking the screen while video is playing. Both of these features require that .BR xscreensaver\-systemd (MANSUFFIX) -be able connect to the systemd bus. Parts of KDE and GNOME may need to be -disabled first for that to work; see below. - +be able connect to D-Bus. .SH INSTALLATION Each desktop environment has its own system for launching long-running daemons like XScreenSaver, and since many of them come bundled with their own (buggy, insecure, inferior) screen-locking frameworks, it is also necessary to disable those other frameworks before XScreenSaver can work. - .SS INSTALLING XSCREENSAVER ON GNOME OR UNITY -For many years, GNOME shipped XScreenSaver as-is, and everything just worked. -In 2005, however, they decided to needlessly re-invent the wheel and ship -their own replacement for the \fIxscreensaver\fP daemon called -.BR gnome-screensaver (1) -rather than improving XScreenSaver and contributing their changes back. As a -result, the \fIgnome-screensaver\fP program is insecure, bug-ridden, and -missing many features of XScreenSaver. In fact, in 2011 it lost the ability -to run display modes at all. - -In 2012 some distros forked and renamed it as both -.BR mate-screensaver (1) -and -.BR cinnamon-screensaver (1), -which seem to be basically the same. - -To replace gnome-screensaver with XScreenSaver: +For many years, GNOME included XScreenSaver as-is, and everything just worked. +Not any more! .RS 4 .TP 3 \fB1: Fully uninstall the other screen saver packages:\fP .nf .sp - sudo apt-get remove gnome-screensaver - sudo apt-get remove mate-screensaver - sudo apt-get remove cinnamon-screensaver + sudo apt remove gnome-screensaver + sudo apt remove mate-screensaver + sudo apt remove cinnamon-screensaver + sudo apt remove light-locker or sudo rpm -e gnome-screensaver sudo rpm -e mate-screensaver sudo rpm -e cinnamon-screensaver + sudo rpm -e light-locker .sp .fi Be careful that it doesn't try to uninstall all of GNOME. - -.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 -.nf -.sp - sudo ln -sf /usr/bin/xscreensaver-command \\ - /usr/bin/gnome-screensaver-command -.sp -.fi -That doesn't work under Unity, though. Apparently it has its own -built-in screen locker which is not gnome-screensaver, and cannot be -removed, and yet still manages to be bug-addled and insecure. -Keep reinventing that wheel, guys! (If you have figured out how to -replace Unity's locking "feature" with XScreenSaver, let me know.) - -.TP 3 -\fB4: Turn off Unity's built-in blanking.\fP +\fB2: Turn off GNOME's built-in blanking.\fP +Set all of the following settings to "\fINever\fP" or "\fIOff\fP", as +these are all controlled by +.BR xscreensaver\-settings (1) +now: -Open "\fISystem Settings / Brightness & Lock\fP"; -.br -Un-check "\fIStart Automatically\fP"; +"\fISettings / Privacy / Screen Lock / Blank Screen Delay\fP" .br -Set \fI"Turn screen off when inactive for"\fP to \fI"Never".\fP +"\fISettings / Privacy / Screen Lock / Automatic Screen Lock\fP" .br -Or possibly that has been randomly renamed again: +"\fISettings / Power / Blank Screen\fP" .br -Set "\fISettings / Power / Power Settings\fP" to \fI"Never".\fP +"\fISettings / Power / Automatic Suspend\fP" +.TP 3 +\fB3: Launch XScreenSaver at login.\fP +Launch "\fITweaks\fP", select "\fIStartup Applications\fP", click the plus +sign, and select "\fIXScreenSaver\fP" (not "\fIXScreenSaver Settings\fP") +from the (very long) menu. + +Or, see the "\fILAUNCHING XSCREENSAVER FROM SYSTEMD\fP" section below. +That works too. .TP 3 -\fB5: Stop GNOME from blocking XScreenSaver's "systemd" integration:\fP +\fB4: Make GNOME's "Lock" icon use XScreenSaver.\fP +This used to work, but no longer does with GNOME 3.38. If you figure it out, +let me know! This still works for Cinnamon 4.8 and MATE 1.24: .nf .sp - sudo systemctl \-\-user mask gsd\-screensaver\-proxy.service + sudo ln -sf /usr/bin/xscreensaver-command \\ + /usr/bin/gnome-screensaver-command + sudo ln -sf /usr/bin/xscreensaver-command \\ + /usr/bin/cinnamon-screensaver-command + sudo ln -sf /usr/bin/xscreensaver-command \\ + /usr/bin/mate-screensaver-command + sudo ln -sf /usr/bin/xscreensaver-command \\ + /usr/bin/xfce4-screensaver-command + sudo ln -sf /usr/bin/xscreensaver-command \\ + /usr/bin/light-locker-command .sp .fi -Without the above, video players will not be able to tell XScreenSaver -not to blank the screen while videos are playing, and the screen will not -auto-lock when you close your laptop's lid. - -After running that command, reboot. Yes, you have to reboot; it won't let -you simply stop the service. Logging out won't do it either. - +This change will get blown away when you upgrade. .SS INSTALLING XSCREENSAVER ON KDE -Like GNOME, KDE also decided to invent their own screen saver framework -from scratch instead of simply using XScreenSaver. To replace the KDE +Like GNOME, KDE also decided to re-invent the wheel. 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: +\fB1: Turn off KDE's built-in blanking.\fP +In \fISystem Settings\fP, un-check the following items, as these are +controlled by +.BR xscreensaver\-settings (1) +now: -Open "\fISystem Settings / Startup/Shutdown / Autostart\fP", and then -add "\fI/usr/bin/xscreensaver\fP". +"\fIWorkspace Behavior / Screen Locking / Lock automatically\fP" +.br +"\fIWorkspace Behavior / Screen Locking / After waking from sleep\fP" +.br +"\fIWorkspace Behavior / Screen Locking / Keyboard shortcut\fP" +.br +"\fIHardware / Power Management / Screen Energy Saving\fP" +.br +"\fIHardware / Power Management / Suspend session\fP" +.br +"\fIHardware / Power Management / Laptop lid closed" = Do Nothing\fP -If you are lucky, that will create a \fI"xscreensaver.desktop"\fP file -for you in \fI~/.config/autostart/\fP or \fI~/.kde/Autostart/\fP. +If there are multiple tabs, you may need to change these settings on all +three of them: "On AC power", "Battery" and "Low Battery". .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: -.nf -.sp - [Desktop Entry] - Exec=xscreensaver - Name=XScreenSaver - Type=Application - StartupNotify=false - X-KDE-StartupNotify=false -.sp -.fi +\fB2: Launch XScreenSaver at login.\fP +Copy the file \fI/usr/share/applications/xscreensaver.desktop\fP into +the directory \fI~/.config/autostart/\fP .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: +\fB3: Make KDE's "Lock" icon use XScreenSaver.\fP +Find the "\fIkscreenlocker_greet\fP" program. It might be in +"\fI/usr/lib/*/libexec/\fP", or it might be somewhere else. +Delete that file and replace it with a file containing these two lines. +Make it executable (chmod a+x). .nf .sp #!/bin/sh - xscreensaver-command \-\-lock + xscreensaver-command \-\-lock & .sp .fi -Make sure the file is executable (chmod a+x). +This change will get blown away when you upgrade. .TP 3 -\fB5: Stop KDE from blocking XScreenSaver's "systemd" integration:\fP -You must arrange for KDE's -.BR ksmserver (1) -daemon to be launched with the command line switch \fI\-\-no\-lockscreen\fP. - -One way to accomplish that is to edit the -.BR startkde (1) -script in \fI/usr/bin/\fP by hand, then log out and log back in. Another -way would be to wrap the \fIksmserver\fP program: +\fB4: Turn off KDE's built-in locking on suspend, even harder.\fP +Even after disabling KDE's screen locking, above, it is \fIpossible\fP that +KDE will still use its built-in locker when you close your laptop's lid. If +that is happening, double-check the settings above, but if those are correct, +try the following. First, ensure you are running KDE 5.21 or newer. Next, +enable \fI"systemd user sessions"\fP for KDE so that you can edit the +parameters for \fIksmserver:\fP .nf .sp - mv /usr/bin/ksmserver /usr/bin/ksmserver-orig + kwriteconfig5 \-\-file startkderc \-\-group General \\ + \-\-key systemdBoot true .sp .fi -and replace \fI/usr/bin/ksmserver\fP with: +Log out and back in. + +Next, edit the \fIplasma-ksmserver\fP service to change how \fIksmserver\fP +is launched: .nf .sp - #!/bin/sh - ksmserver-orig \-\-no\-lockscreen + systemctl edit --user plasma-ksmserver.service .sp .fi -Either change will, of course, get blown away the next time your system -upgrades KDE. - -Instead of being in \fI/usr/bin/\fP, the \fIksmserver\fP program might be -in \fI/usr/lib/\fP or \fIusr/lib*/libexec/\fP or \fIusr/lib/*/libexec/\fP -or somewhere else, depending on your distro. - -But without this, video players will not be able to tell XScreenSaver not to -blank the screen while videos are playing, and the screen will not auto-lock -when you close your laptop's lid. - -It seems that KDE 5.17 replaced \fIstartkde\fP with \fIstartplasma-x11\fP, -and I don't know how to change how \fIthat\fP launches \fIksmserver\fP. -Let me know if you figure it out. - -.SS LAUNCHING XSCREENSAVER FROM SYSTEMD -If the above didn't do it, and your system has -.BR systemd (1), -maybe this is how it works: -.RS 4 -.TP 3 -\fB1: Create a service.\fP -Create the file \fI~/.config/systemd/user/xscreensaver.service\fP -containing: +Replace the contents of the file that lets you edit with this: .nf .sp - [Unit] - Description=XScreenSaver [Service] - ExecStart=/usr/bin/xscreensaver - Restart=on-failure - [Install] - WantedBy=default.target + ExecStart= + ExecStart=/usr/bin/ksmserver \-\-no\-lockscreen .sp .fi -.TP 3 -\fB2. Enable it.\fP +Then log out and back in \fIagain\fP. +.SS LAUNCHING XSCREENSAVER FROM LXDE +Add the line \fI@xscreensaver\fP to +\fI/etc/xdg/lxsession/LXDE/autostart\fP or +\fI/etc/xdg/lxsession/LXDE-pi/autostart\fP. +.SS LAUNCHING XSCREENSAVER FROM SYSTEMD +If you are not using GNOME, KDE or LXDE, the way to launch XScreenSaver +at login is probably +.BR systemd (1). + +Copy the file \fI/usr/share/xscreensaver/xscreensaver.service\fP into +the directory \fI~/.config/systemd/user/\fP. Create that directory first +if it doesn't exist. Then enable it with: .nf .sp systemctl \-\-user enable xscreensaver .sp .fi -.RE -Then restart X11. - .SS LAUNCHING XSCREENAVER FROM UPSTART -If your system has +If you are not using GNOME, KDE or LXDE, and your system uses .BR upstart (7) instead of .BR systemd (1), -maybe this will work: launch the \fI"Startup Applications"\fP applet, -click \fI"Add"\fP, enter these lines, then restart X11: +launch the \fI"Startup Applications"\fP applet, click \fI"Add"\fP, and enter +these lines: .nf .sp Name: XScreenSaver @@ -331,7 +263,14 @@ click \fI"Add"\fP, enter these lines, then restart X11: Comment: XScreenSaver .sp .fi - +.SS LAPTOP LIDS WITHOUT SYSTEMD +BSD systems or other systems without +.BR systemd (1) +or +.BR elogind (8) +might have luck by adding "\fIxscreensaver\-command \-\-suspend\fP" to +some appropriate spot in \fI/etc/acpi/events/anything\fP or in +\fI/etc/acpi/handler.sh\fP, if those files exist. .SS LAUNCHING XSCREENSAVER FROM GDM You can run \fIxscreensaver\fP from your .BR gdm (1) @@ -343,7 +282,7 @@ 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 +command \fI"xscreensaver \-\-no\-splash"\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.) @@ -353,83 +292,111 @@ If that doesn't work, you can edit the config file directly. Edit .nf .sp Greeter=/usr/bin/gdmlogin - BackgroundProgram=xscreensaver \-\-nosplash + BackgroundProgram=xscreensaver \-\-no\-splash RunBackgroundProgramAlways=true .sp .fi -In this situation, the \fIxscreensaver\fP process will probably be running -as user \fIgdm\fP instead of as \fIroot\fP. You can configure the settings -for this nobody-logged-in state (timeouts, DPMS, etc.) by editing -the \fI~gdm/.xscreensaver\fP file. - -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. +In this situation, the \fIxscreensaver\fP process will be running as user +\fIgdm\fP. You can configure the settings for this nobody-logged-in +state (timeouts, DPMS, etc.) by editing the \fI~gdm/.xscreensaver\fP file. If you get "connection refused" errors when running \fIxscreensaver\fP -from \fIgdm\fP, then this probably means that you have +from \fIgdm\fP, then this probably means that you are having .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 +problems. 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). -.SS LAPTOP LIDS WITHOUT SYSTEMD -BSD systems or other systems without -.BR systemd (1) -or -.BR elogind (8) -might have luck by adding \fIxscreensaver\-command \-\-suspend\fP to -some appropriate spot in \fI/etc/acpi/events/anything\fP or in -\fI/etc/acpi/handler.sh\fP, if those files exist. +There might be a way to accomplish this with other display managers. +It's a mystery! +.SH THE WAYLAND PROBLEM +Wayland is a completely different window system that is intended to replace +X11. After 14+ years of trying, some Linux distros have finally begun +enabling it by default. Most deployments of it also include XWayland, which +is a compatibility layer that allows \fIsome\fP X11 programs to continue to +work within a Wayland environment. + +Unfortunately, XScreenSaver is not one of those programs. + +If your system is running XWayland, XScreenSaver will malfunction in two +ways: +.RS 0 +.TP 3 +\fB1:\fP It will be unable to detect user activity in non-X11 programs. +This means that while a native Wayland program is selected, XScreenSaver will +think that you are idle, and may blank the screen prematurely. +.TP 3 +\fB2:\fP It will be unable to lock the screen. + +This is because X11 grabs don't work properly under XWayland, so there is no +way for XScreenSaver to prevent the user from switching away from the screen +locker to another application. +.RE + +In short, for XScreenSaver to work properly, you will need to switch off +Wayland and use the X Window System like in the "good old days". +.SS TO DISABLE WAYLAND UNDER GNOME +The login screen should have a gear-icon menu that lets you change the session +type from "GNOME" (the Wayland session) to "GNOME on Xorg" (the X11 session). + +Alternately, edit \fI/etc/gdm/custom.conf\fP and make sure it includes this +line: +.nf +.sp + WaylandEnable=false +.fi +.SS TO DISABLE WAYLAND UNDER KDE +The login screen should have a menu that lets you change the session type to +"Plasma (X11)". + +Alternately, edit \fI/etc/sddm.conf\fP and change the \fISessionDir\fP line +under the \fI[Wayland]\fP section to say: +.nf +.sp + SessionDir=/dev/null +.fi .SH SECURITY CONCERNS XScreenSaver has a decades-long track record of securely locking your screen. However, there are many things that can go wrong. X11 is a very old system, and has a number of design flaws that make it susceptible to foot-shooting. - .SS MAGIC BACKDOOR KEYSTROKES -The XFree86 and Xorg X servers, as well as the Linux kernel, both trap +The Xorg and XFree86 X servers, as well as the Linux kernel, both trap certain magic keystrokes before X11 client programs ever see them. If you care about keeping your screen locked, this is a big problem. - .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 xorg.conf (5) +or .BR XF86Config (5) -for details. - +file. .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. +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 +.BR xorg.conf (5), +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. You can disable it by -turning off \fBAllowClosedownGrabs\fP in \fIxorg.conf\fP. - +This keystroke kills any X11 app that holds a lock, so typing this will kill +XScreenSaver and unlock the screen. You can disable it by turning off +\fBAllowClosedownGrabs\fP in +.BR xorg.conf (5). .TP 3 .B Alt-SysRq-F This is the Linux kernel "OOM-killer" keystroke. It shoots down random @@ -464,7 +431,16 @@ You can disable the OOM-killer entirely with: echo vm.overcommit_memory = 2 >> /etc/sysctl.conf .sp .fi - +In addition to the kernel's OOM-killer, +.BR systemd (1) +has its own. The included \fIxscreensaver.service\fP file attempts to +evade it, but you may want to just turn it off anyway: +.nf +.sp + sudo systemctl disable --now systemd-oomd + sudo systemctl mask systemd-oomd +.sp +.fi .SS X SERVER ACCESS IS GAME OVER X11's security model is all-or-nothing. If a program can connect to your X server at all, either locally or over the network, it can log all of your @@ -474,96 +450,53 @@ server can execute arbitrary code as the logged-in user. See .BR Xsecurity (1) and .BR xauth (1). - .SS PAM PASSWORDS If your system uses PAM (Pluggable Authentication Modules), then PAM must be configured for XScreenSaver. If it is not, then you \fImight\fP be in a situation where you can't unlock. Probably the file you need is \fI/etc/pam.d/xscreensaver\fP. - .SS DON'T LOG IN AS ROOT -In order for it to be safe for XScreenSaver to be launched by \fIxdm\fP, -certain precautions had to be taken, among them that XScreenSaver never -runs as \fIroot\fP. In particular, if it is launched as root (as \fIxdm\fP -is likely to do), XScreenSaver will disavow its privileges, and switch -itself to a safe user id (such as \fInobody\fP). - -An implication of this is that if you log in as \fIroot\fP on the console, -XScreenSaver will refuse to lock the screen (because it can't tell -the difference between \fIroot\fP being logged in on the console, and a -normal user being logged in on the console but XScreenSaver having been -launched by the -.BR xdm (1) -.I Xsetup -file). - -Proper Unix hygiene dictates that you should log in as yourself, and +Never log in as root. Log in as a normal user and use .BR sudo (1) -to \fIroot\fP as necessary. People who spend their day logged in -as \fIroot\fP are just begging for disaster. - +as necessary. If you are logged in as root, XScreenSaver will not lock your +screen or run display modes, for numerous good and proper reasons. .SH MULTI-USER OR SITE-WIDE CONFIGURATION For a single user, the proper way to configure XScreenSaver is to simply run the .BR xscreensaver\-settings (1) -program, and change the settings through the GUI. The rest of this manual -describes lower-level ways of changing settings. You shouldn't need to -know any of the stuff described below unless you are trying to do something -complicated. - -Options to XScreenSaver are stored in one of two places: in a file -called \fI.xscreensaver\fP in your home directory; or in the X resource -database. If the \fI.xscreensaver\fP file exists, it overrides any settings -in the resource database. - -The syntax of the \fI.xscreensaver\fP file is similar to that of -the \fI.Xdefaults\fP file; for example, to set the \fItimeout\fP parameter -n the \fI.xscreensaver\fP file, you would write the following: -.nf -.sp - timeout: 5 -.sp -.fi -whereas, in the \fI.Xdefaults\fP file, you would write -.nf -.sp - xscreensaver.timeout: 5 -.sp -.fi +program, and change the settings through the GUI. Changes are written +to the \fI~/.xscreensaver\fP file. + +If you want to set the system-wide defaults, then make your edits to +\fI/etc/X11/app-defaults/XScreenSaver\fP instead. The two files have +similar (but not identical) syntax. + +You can also make changes via the X Resource Database and +.BR xrdb (1), +but that can be very confusing and is not really recommended. + +Options in \fI~/.xscreensaver\fP override any settings in the resource +database or app-defaults file. + If you change a setting in the \fI.xscreensaver\fP file while XScreenSaver is already running, it will notice this, and reload the file as needed. - -If you change a setting in your X resource database, or if you want -XScreenSaver to notice your changes immediately instead of the next time it -wakes up, then you will need to reload your \fI.Xdefaults\fP file, and then -tell the running \fIxscreensaver\fP process to restart itself, like so: +But if you change a setting in the X Resource Database, you will need to +restart XScreenSaver for those changes to take effect: .nf .sp xrdb < ~/.Xdefaults xscreensaver-command \-\-restart .sp .fi -If you want to set the system-wide defaults, then make your edits to -the XScreenSaver app-defaults file, which should have been installed -when XScreenSaver itself was installed. The app-defaults file will -usually be named /etc/X11/app-defaults/XScreenSaver, but different -systems might keep it in a different place. - -When settings are changed in the Preferences dialog box, those settings are -written to the \fI.xscreensaver\fP file. The \fI.Xdefaults\fP file and the -app-defaults file will never be written by XScreenSaver itself. - .SH X RESOURCES These are the X resources use by XScreenSaver program. You probably won't need to change these manually: that's what the .BR xscreensaver\-settings (1) program is for. - .TP 8 .B timeout\fP (class \fBTime\fP) The screensaver will activate (blank the screen) after the keyboard and mouse have been idle for this many minutes. Default 10 minutes. - .TP 8 .B cycle\fP (class \fBTime\fP) After the screensaver has been running for this many minutes, the currently @@ -575,12 +508,10 @@ Default 10 minutes. If there are multiple screens, the savers are staggered slightly so that while they all change every \fIcycle\fP minutes, they don't all change at the same time. - .TP 8 .B lock\fP (class \fBBoolean\fP) Enable locking: before the screensaver will turn off, it will require you to type the password of the logged-in user. - .TP 8 .B lockTimeout\fP (class \fBTime\fP) If locking is enabled, this controls the length of the "grace period" @@ -592,26 +523,21 @@ at 15 minutes or later (that is, \fIlockTimeout\fP minutes after activation) then a password would be required. The default is 0, meaning that if locking is enabled, then a password will be required as soon as the screen blanks. - .TP 8 .B passwdTimeout\fP (class \fBTime\fP) If the screen is locked, then this is how many seconds the password dialog box should be left on the screen before giving up (default 30 seconds). A few seconds are added each time you type a character. - .TP 8 .B dpmsEnabled\fP (class \fBBoolean\fP) Whether power management is enabled. - .TP 8 .B dpmsStandby\fP (class \fBTime\fP) If power management is enabled, how long until the monitor goes solid black. - .TP 8 .B dpmsSuspend\fP (class \fBTime\fP) If power management is enabled, how long until the monitor goes into power-saving mode. - .TP 8 .B dpmsOff\fP (class \fBTime\fP) If power management is enabled, how long until the monitor powers down @@ -619,42 +545,32 @@ 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 verbose\fP (class \fBBoolean\fP) Whether to print diagnostics. Default false. - .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\-settings (1). - .TP 8 .B newLoginCommand\fP (class \fBNewLoginCommand\fP) If set, this is the shell command that is run when the "New Login" button @@ -666,7 +582,6 @@ Typically this will be some variant of .BR lxdm (1) or .BR dm-tool (1). - .TP 8 .B nice\fP (class \fBNice\fP) The sub-processes launched by XScreenSaver will be "niced" to this level, so @@ -675,24 +590,19 @@ 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. -Default: true. - +If this is true, then when the screensaver activates, the desktop will fade to +black instead of simply winking out. Default: true. .TP 8 .B unfade\fP (class \fBBoolean\fP) -If this is true, then when the screensaver deactivates, the original contents -of the screen will fade in from black instead of appearing immediately. This -is only done if \fIfade\fP is true as well. Default: true. - +If this is true, then when the screensaver deactivates, desktop will fade back +ininstead of appearing immediately. This is only done if \fIfade\fP is true +as well. Default: true. .TP 8 .B fadeSeconds\fP (class \fBTime\fP) If \fIfade\fP is true, this is how long the fade will be in seconds. Default 3 seconds. - .TP 8 .B ignoreUninstalledPrograms\fP (class \fBBoolean\fP) There may be programs in the list that are not installed on the system, @@ -702,7 +612,6 @@ if an attempt is made to run the nonexistent program. Also, the .BR xscreensaver\-settings (1) program will suppress the non-existent programs from the list if this is true. Default: false. - .TP 8 .B authWarningSlack\fP (class \fBInteger\fP) After you successfully unlock the screen, a dialog may pop up informing @@ -710,37 +619,30 @@ you of previous failed login attempts. If all of those login attemps were within this amount of time, they are ignored. The assumption is that incorrect passwords entered within a few seconds of a correct one are user error, rather than hostile action. Default 20 seconds. - .TP 8 .B mode\fP (class \fBMode\fP) Controls the screen-saving behavior. Valid values are: .RS 8 - .TP 8 .B random When blanking the screen, select a random display mode from among those that are enabled and applicable. This is the default. - .TP 8 .B random-same Like \fIrandom\fP, but if there are multiple screens, each screen will run the \fIsame\fP random display mode, instead of each screen running a different one. - .TP 8 .B one When blanking the screen, only ever use one particular display mode (the one indicated by the \fIselected\fP setting). - .TP 8 .B blank When blanking the screen, just go black: don't run any graphics hacks. - .TP 8 .B off Don't ever blank the screen, and don't ever allow the monitor to power down. .RE - .TP 8 .B selected\fP (class \fBInteger\fP) When \fImode\fP is set to \fIone\fP, this is the one, indicated by its @@ -748,7 +650,6 @@ index in the \fIprograms\fP list. You're crazy if you count them and set this number by hand: let .BR xscreensaver\-settings (1) do it for you! - .TP 8 .B programs\fP (class \fBPrograms\fP) The graphics hacks which XScreenSaver runs when the user is idle. @@ -776,24 +677,9 @@ 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: -.nf -.sp - programs: \\ - qix -root \\n\\ - ico -r -faces -sleep 1 -obj ico \\n\\ - xdaliclock -builtin2 -root \\n\\ - xv -root -rmode 5 image.gif -quit \\n -.sp -.fi -.RS 8 -Make sure your \fB$PATH\fP environment variable is set up correctly -\fIbefore\fP XScreenSaver is launched, or it won't be able to find the -programs listed in the \fIprograms\fP resource. +The default XScreenSaver hacks directory (typically +\fI/usr/libexec/xscreensaver/\fP) is prepended to \fB$PATH\fP +before searching for these programs. To use a program as a screensaver, it must be able to render onto the window provided to it in the \fB$XSCREENSAVER_WINDOW\fP environment @@ -834,8 +720,8 @@ the \fIprograms\fP list: .B default-n This is like \fBdefault\fP, but also requests the use of the default colormap, instead of a private colormap. - .RE +.PP 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 @@ -843,7 +729,6 @@ 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 - .TP 8 .B visualID\fP (class \fBVisualID\fP) This is an historical artifact left over from when 8-bit @@ -892,14 +777,13 @@ as reported by the 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 +.PP Note that this option specifies only the \fIdefault\fP visual that will be used: the visual used may be overridden on a program-by-program basis. See the description of the \fBprograms\fP resource, above. .RE - .TP 8 .B installColormap\fP (class \fBBoolean\fP) This is an historical artifact left over from when 8-bit displays were still @@ -913,7 +797,6 @@ discussion of the \fBdefault\-n\fP name in the section about the This does nothing if you have a TrueColor (16-bit or deeper) display. (Which, in this century, you do.) - .TP 8 .B pointerHysteresis\fP (class \fBInteger\fP) If the mouse moves less than this-many pixels in a second, ignore it @@ -921,10 +804,12 @@ If the mouse moves less than this-many pixels in a second, ignore it doesn't un-blank (or fail to blank) just because you bumped the desk. Default: 10 pixels. +A single pixel of motion will still cause the monitor to power back on, +but not un-blank. This is because the X11 server itself unfortunately handles +power-management-related activity detection rather than XScreenSaver. .SH BUGS https://www.jwz.org/xscreensaver/bugs.html explains how to write the most useful bug reports. If you find a bug, please let me know! - .SH ENVIRONMENT .PP .TP 8 @@ -934,8 +819,7 @@ 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". +should draw. .TP 8 .B PATH to find the sub-programs to run, including the display modes. @@ -965,7 +849,7 @@ and a FAQ can always be found at https://www.jwz.org/xscreensaver/ .BR xscreensaver\-getimage (MANSUFFIX), .BR xscreensaver\-text (MANSUFFIX). .SH COPYRIGHT -Copyright \(co 1991-2021 by Jamie Zawinski. +Copyright \(co 1991-2022 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 @@ -974,8 +858,7 @@ 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. +Jamie Zawinski <jwz@jwz.org> Please let me know if you find any bugs or make any improvements. diff --git a/driver/xscreensaver.service.in b/driver/xscreensaver.service.in new file mode 100644 index 0000000..efad06c --- /dev/null +++ b/driver/xscreensaver.service.in @@ -0,0 +1,18 @@ +[Unit] +Description=XScreenSaver +Documentation=man:xscreensaver +Documentation=man:xscreensaver-settings +Documentation=https://www.jwz.org/xscreensaver/ +After=graphical-session-pre.target +PartOf=graphical-session.target +ConditionUser=!@system +Conflicts=org.gnome.ScreenSaver org.cinnamon.ScreenSaver org.mate.ScreenSaver org.xfce.ScreenSaver light-locker + +[Service] +ExecStart=/usr/bin/xscreensaver +Restart=on-failure +OOMScoreAdjust=-1000 + +[Install] +Alias=org.jwz.xscreensaver.service +WantedBy=graphical-session.target diff --git a/driver/xscreensaver.ui b/driver/xscreensaver.ui deleted file mode 100644 index e9e4522..0000000 --- a/driver/xscreensaver.ui +++ /dev/null @@ -1,2994 +0,0 @@ -<?xml version="1.0"?> -<!--*- mode: xml -*--> -<interface> - <object class="GtkAdjustment" id="adjustment1"> - <property name="upper">720</property> - <property name="lower">1</property> - <property name="page_increment">15</property> - <property name="step_increment">1</property> - <property name="page_size">0</property> - <property name="value">1</property> - </object> - <object class="GtkAdjustment" id="adjustment2"> - <property name="upper">720</property> - <property name="lower">0</property> - <property name="page_increment">15</property> - <property name="step_increment">1</property> - <property name="page_size">0</property> - <property name="value">0</property> - </object> - <object class="GtkAdjustment" id="adjustment3"> - <property name="upper">720</property> - <property name="lower">0</property> - <property name="page_increment">15</property> - <property name="step_increment">1</property> - <property name="page_size">0</property> - <property name="value">0</property> - </object> - <object class="GtkAdjustment" id="adjustment4"> - <property name="upper">1440</property> - <property name="lower">0</property> - <property name="page_increment">15</property> - <property name="step_increment">1</property> - <property name="page_size">0</property> - <property name="value">0</property> - </object> - <object class="GtkAdjustment" id="adjustment5"> - <property name="upper">1440</property> - <property name="lower">0</property> - <property name="page_increment">15</property> - <property name="step_increment">1</property> - <property name="page_size">0</property> - <property name="value">0</property> - </object> - <object class="GtkAdjustment" id="adjustment6"> - <property name="upper">1440</property> - <property name="lower">0</property> - <property name="page_increment">15</property> - <property name="step_increment">1</property> - <property name="page_size">0</property> - <property name="value">0</property> - </object> - <object class="GtkAdjustment" id="adjustment7"> - <property name="upper">10</property> - <property name="lower">0</property> - <property name="page_increment">1</property> - <property name="step_increment">1</property> - <property name="page_size">0</property> - <property name="value">0</property> - </object> - <object class="GtkListStore" id="mode_menu_model"> - <columns> - <column type="gchararray"/> - </columns> - <data> - <row> - <col id="0" translatable="yes">Disable Screen Saver</col> - </row> - <row> - <col id="0" translatable="yes">Blank Screen Only</col> - </row> - <row> - <col id="0" translatable="yes">Only One Screen Saver</col> - </row> - <row> - <col id="0" translatable="yes">Random Screen Saver</col> - </row> - <row> - <col id="0" translatable="yes">Same Random Savers</col> - </row> - </data> - </object> - - <object class="GtkListStore" id="theme_menu_model"> - <columns> - <column type="gchararray"/> - </columns> - <data> - <row> - <col id="0" translatable="yes">Default</col> - </row> - </data> - </object> - - - - - - <object class="GtkListStore" id="visual_combo_model"> - <columns> - <column type="gchararray"/> - </columns> - <data> - <row> - <col id="0" translatable="yes">Any</col> - </row> - <row> - <col id="0" translatable="yes">Best</col> - </row> - <row> - <col id="0" translatable="yes">Default</col> - </row> - <row> - <col id="0" translatable="yes">Default-N</col> - </row> - <row> - <col id="0" translatable="yes">GL</col> - </row> - <row> - <col id="0" translatable="yes">TrueColor</col> - </row> - <row> - <col id="0" translatable="yes">PseudoColor</col> - </row> - <row> - <col id="0" translatable="yes">StaticGray</col> - </row> - <row> - <col id="0" translatable="yes">GrayScale</col> - </row> - <row> - <col id="0" translatable="yes">DirectColor</col> - </row> - <row> - <col id="0" translatable="yes">Color</col> - </row> - <row> - <col id="0" translatable="yes">Gray</col> - </row> - <row> - <col id="0" translatable="yes">Mono</col> - </row> - </data> - </object> - <object class="GtkUIManager" id="uimanager1"> - <child> - <object class="GtkActionGroup" id="actiongroup1"> - <child> - <object class="GtkAction" id="file"> - <property name="name">file</property> - <property name="label" translatable="yes">_File</property> - <signal handler="file_menu_cb" last_modification_time="Sun, 06 Mar 2005 21:41:13 GMT" name="activate"/> - </object> - </child> - <child> - <object class="GtkAction" id="activate_action"> - <property name="name">activate_action</property> - <property name="label" translatable="yes">_Blank Screen Now</property> - <signal handler="activate_menu_cb" name="activate"/> - </object> - </child> - <child> - <object class="GtkAction" id="lock_action"> - <property name="name">lock_action</property> - <property name="label" translatable="yes">_Lock Screen Now</property> - <signal handler="lock_menu_cb" name="activate"/> - </object> - </child> - <child> - <object class="GtkAction" id="kill_action"> - <property name="name">kill_action</property> - <property name="label" translatable="yes">_Kill Daemon</property> - <signal handler="kill_menu_cb" name="activate"/> - </object> - </child> - <child> - <object class="GtkAction" id="restart_action"> - <property name="name">restart_action</property> - <property name="label" translatable="yes">_Restart Daemon</property> - <signal handler="restart_menu_cb" name="activate"/> - </object> - </child> - <child> - <object class="GtkAction" id="exit_action"> - <property name="name">exit_action</property> - <property name="label" translatable="yes">_Quit</property> - <signal handler="exit_menu_cb" name="activate"/> - </object> - </child> - <child> - <object class="GtkAction" id="help"> - <property name="name">help</property> - <property name="label" translatable="yes">_Help</property> - </object> - </child> - <child> - <object class="GtkAction" id="about_action"> - <property name="name">about_action</property> - <property name="label" translatable="yes">_About...</property> - <signal handler="about_menu_cb" name="activate"/> - </object> - </child> - <child> - <object class="GtkAction" id="doc_action"> - <property name="name">doc_action</property> - <property name="label" translatable="yes">_Documentation...</property> - <signal handler="doc_menu_cb" name="activate"/> - </object> - </child> - </object> - </child> - <ui> - <menubar name="menubar"> - <menu action="file"> - <menuitem name="activate_menu" action="activate_action"/> - <menuitem name="lock_menu" action="lock_action"/> - <menuitem name="kill_menu" action="kill_action"/> - <menuitem name="restart_menu" action="restart_action"/> - <separator/> - <menuitem name="exit_menu" action="exit_action"/> - </menu> - <menu action="help"> - <menuitem name="about_menu" action="about_action"/> - <menuitem name="doc_menu" action="doc_action"/> - </menu> - </menubar> - </ui> - </object> - <object class="GtkWindow" id="xscreensaver_demo"> - <property name="title" translatable="yes">XScreenSaver</property> - <property name="type">GTK_WINDOW_TOPLEVEL</property> - <property name="window_position">GTK_WIN_POS_NONE</property> - <property name="modal">False</property> - <property name="resizable">True</property> - <property name="destroy_with_parent">False</property> - <property name="decorated">True</property> - <property name="skip_taskbar_hint">False</property> - <property name="skip_pager_hint">False</property> - <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property> - <property name="gravity">GDK_GRAVITY_NORTH_WEST</property> - <property name="focus_on_map">True</property> - <property name="urgency_hint">False</property> - <child> - <object class="GtkVBox" id="outer_vbox"> - <property name="visible">True</property> - <property name="homogeneous">False</property> - <property name="spacing">5</property> - <child> - <object class="GtkMenuBar" constructor="uimanager1" id="menubar"> - <property name="visible">True</property> - <property name="pack_direction">GTK_PACK_DIRECTION_LTR</property> - <property name="child_pack_direction">GTK_PACK_DIRECTION_LTR</property> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - <child> - <object class="GtkHBox" id="spacer_hbox"> - <property name="border_width">8</property> - <property name="visible">True</property> - <property name="homogeneous">False</property> - <property name="spacing">0</property> - <child> - <object class="GtkNotebook" id="notebook"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="show_tabs">True</property> - <property name="show_border">True</property> - <property name="tab_pos">GTK_POS_TOP</property> - <property name="scrollable">False</property> - <property name="enable_popup">False</property> - <signal handler="switch_page_cb" name="switch_page"/> - <child> - <object class="GtkTable" id="demos_table"> - <property name="border_width">10</property> - <property name="visible">True</property> - <property name="n_rows">2</property> - <property name="n_columns">2</property> - <property name="homogeneous">False</property> - <property name="row_spacing">0</property> - <property name="column_spacing">0</property> - <child> - <object class="GtkTable" id="blanking_table"> - <property name="visible">True</property> - <property name="n_rows">3</property> - <property name="n_columns">4</property> - <property name="homogeneous">False</property> - <property name="row_spacing">2</property> - <property name="column_spacing">0</property> - <child> - <object class="GtkLabel" id="cycle_label"> - <property name="visible">True</property> - <property name="label" translatable="yes">_Cycle After</property> - <property name="use_underline">True</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_RIGHT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">1</property> - <property name="yalign">0.5</property> - <property name="xpad">8</property> - <property name="ypad">0</property> - <property name="mnemonic_widget">cycle_spinbutton</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - <accessibility> - <relation target="cycle_spinbutton" type="label-for"/> - <relation target="cycle_spinbutton" type="flows-to"/> - </accessibility> - </object> - <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> - <property name="x_options">fill</property> - <property name="y_options"/> - </packing> - </child> - <child> - <object class="GtkEventBox" id="lock_button_eventbox"> - <property name="visible">True</property> - <property name="tooltip-text" translatable="yes">Whether a password should be required to un-blank the screen.</property> - <property name="visible_window">False</property> - <property name="above_child">False</property> - <child> - <object class="GtkCheckButton" id="lock_button"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="label" translatable="yes">_Lock Screen After </property> - <property name="use_underline">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> - <property name="focus_on_click">True</property> - <property name="active">False</property> - <property name="inconsistent">False</property> - <property name="draw_indicator">True</property> - <accessibility> - <relation target="lock_spinbutton" type="controller-for"/> - <relation target="lock_spinbutton" type="label-for"/> - <relation target="lock_spinbutton" type="flows-to"/> - </accessibility> - <signal handler="pref_changed_cb" name="toggled"/> - <child internal-child="accessible"> - <object class="AtkObject" id="a11y-lock_button1"> - <property name="AtkObject::accessible_name" translatable="yes">Lock Screen</property> - </object> - </child> - </object> - </child> - </object> - <packing> - <property name="left_attach">0</property> - <property name="right_attach">2</property> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> - <property name="x_options">fill</property> - <property name="y_options"/> - </packing> - </child> - <child> - <object class="GtkSpinButton" id="timeout_spinbutton"> - <property name="visible">True</property> - <property name="tooltip-text" translatable="yes">How long before the screen saver activates.</property> - <property name="can_focus">True</property> - <property name="climb_rate">15</property> - <property name="digits">0</property> - <property name="numeric">True</property> - <property name="update_policy">GTK_UPDATE_ALWAYS</property> - <property name="snap_to_ticks">True</property> - <property name="wrap">False</property> - <property name="adjustment">adjustment1</property> - <accessibility> - <relation target="timeout_label" type="labelled-by"/> - <relation target="timeout_mlabel" type="labelled-by"/> - <relation target="timeout_label" type="flows-from"/> - <relation target="timeout_mlabel" type="flows-to"/> - </accessibility> - <signal handler="pref_changed_cb" name="activate"/> - <signal handler="pref_changed_event_cb" name="focus_out_event"/> - <signal handler="pref_changed_cb" name="value_changed"/> - </object> - <packing> - <property name="left_attach">2</property> - <property name="right_attach">3</property> - <property name="top_attach">0</property> - <property name="bottom_attach">1</property> - <property name="x_options">fill</property> - <property name="y_options"/> - </packing> - </child> - <child> - <object class="GtkSpinButton" id="lock_spinbutton"> - <property name="visible">True</property> - <property name="tooltip-text" translatable="yes">How long after the screen blanks until a password will be required.</property> - <property name="can_focus">True</property> - <property name="climb_rate">15</property> - <property name="digits">0</property> - <property name="numeric">True</property> - <property name="update_policy">GTK_UPDATE_ALWAYS</property> - <property name="snap_to_ticks">True</property> - <property name="wrap">False</property> - <property name="adjustment">adjustment2</property> - <accessibility> - <relation target="lock_button" type="controlled-by"/> - <relation target="lock_button" type="labelled-by"/> - <relation target="lock_mlabel" type="labelled-by"/> - <relation target="lock_button" type="flows-from"/> - <relation target="lock_mlabel" type="flows-to"/> - </accessibility> - <signal handler="pref_changed_cb" name="activate"/> - <signal handler="pref_changed_event_cb" name="focus_out_event"/> - <signal handler="pref_changed_cb" name="value_changed"/> - <child internal-child="accessible"> - <object class="AtkObject" id="a11y-lock_spinbutton1"> - <property name="AtkObject::accessible_name" translatable="yes">Lock Screen After</property> - </object> - </child> - </object> - <packing> - <property name="left_attach">2</property> - <property name="right_attach">3</property> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> - <property name="y_padding">10</property> - <property name="x_options">fill</property> - <property name="y_options"/> - </packing> - </child> - <child> - <object class="GtkSpinButton" id="cycle_spinbutton"> - <property name="visible">True</property> - <property name="tooltip-text" translatable="yes">How long each display mode should run before choosing a new one (in Random mode.)</property> - <property name="can_focus">True</property> - <property name="climb_rate">15</property> - <property name="digits">0</property> - <property name="numeric">True</property> - <property name="update_policy">GTK_UPDATE_ALWAYS</property> - <property name="snap_to_ticks">True</property> - <property name="wrap">False</property> - <property name="adjustment">adjustment3</property> - <accessibility> - <relation target="cycle_label" type="labelled-by"/> - <relation target="cycle_mlabel" type="labelled-by"/> - <relation target="cycle_label" type="flows-from"/> - <relation target="cycle_mlabel" type="flows-to"/> - </accessibility> - <signal handler="pref_changed_cb" name="activate"/> - <signal handler="pref_changed_event_cb" name="focus_out_event"/> - <signal handler="pref_changed_cb" name="value_changed"/> - </object> - <packing> - <property name="left_attach">2</property> - <property name="right_attach">3</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> - <property name="x_options">fill</property> - <property name="y_options"/> - </packing> - </child> - <child> - <object class="GtkLabel" id="lock_mlabel"> - <property name="visible">True</property> - <property name="label" translatable="yes">minutes</property> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0</property> - <property name="yalign">0.5</property> - <property name="xpad">8</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - <accessibility> - <relation target="lock_spinbutton" type="label-for"/> - <relation target="lock_spinbutton" type="flows-to"/> - </accessibility> - </object> - <packing> - <property name="left_attach">3</property> - <property name="right_attach">4</property> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> - <property name="y_options"/> - </packing> - </child> - <child> - <object class="GtkLabel" id="cycle_mlabel"> - <property name="visible">True</property> - <property name="label" translatable="yes">minutes</property> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0</property> - <property name="yalign">0.5</property> - <property name="xpad">8</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - <accessibility> - <relation target="cycle_spinbutton" type="label-for"/> - <relation target="cycle_spinbutton" type="flows-from"/> - </accessibility> - </object> - <packing> - <property name="left_attach">3</property> - <property name="right_attach">4</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> - <property name="y_options"/> - </packing> - </child> - <child> - <object class="GtkLabel" id="timeout_label"> - <property name="visible">True</property> - <property name="label" translatable="yes">_Blank After</property> - <property name="use_underline">True</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_RIGHT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">1</property> - <property name="yalign">0.5</property> - <property name="xpad">8</property> - <property name="ypad">0</property> - <property name="mnemonic_widget">timeout_spinbutton</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - <accessibility> - <relation target="timeout_spinbutton" type="label-for"/> - <relation target="timeout_spinbutton" type="flows-to"/> - </accessibility> - </object> - <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">0</property> - <property name="bottom_attach">1</property> - <property name="x_options">fill</property> - <property name="y_options"/> - </packing> - </child> - <child> - <object class="GtkLabel" id="timeout_mlabel"> - <property name="visible">True</property> - <property name="label" translatable="yes">minutes</property> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0</property> - <property name="yalign">0.5</property> - <property name="xpad">8</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - <accessibility> - <relation target="timeout_spinbutton" type="label-for"/> - <relation target="timeout_spinbutton" type="flows-from"/> - </accessibility> - </object> - <packing> - <property name="left_attach">3</property> - <property name="right_attach">4</property> - <property name="top_attach">0</property> - <property name="bottom_attach">1</property> - <property name="y_options"/> - </packing> - </child> - </object> - <packing> - <property name="left_attach">0</property> - <property name="right_attach">1</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> - <property name="x_options">fill</property> - <property name="y_options">fill</property> - </packing> - </child> - <child> - <object class="GtkHButtonBox" id="demo_manual_hbbox"> - <property name="visible">True</property> - <property name="layout_style">GTK_BUTTONBOX_SPREAD</property> - <property name="spacing">30</property> - <child> - <object class="GtkButton" id="demo"> - <property name="visible">True</property> - <property name="tooltip-text" translatable="yes">Demo the selected screen saver in full-screen mode (click the mouse to return.)</property> - <property name="can_default">True</property> - <property name="can_focus">True</property> - <property name="label" translatable="yes">_Preview</property> - <property name="use_underline">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> - <property name="focus_on_click">True</property> - <signal handler="run_this_cb" name="clicked"/> - </object> - </child> - <child> - <object class="GtkButton" id="settings"> - <property name="visible">True</property> - <property name="tooltip-text" translatable="yes">Customization and explanation of the selected screen saver.</property> - <property name="can_default">True</property> - <property name="can_focus">True</property> - <property name="label" translatable="yes">_Settings...</property> - <property name="use_underline">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> - <property name="focus_on_click">True</property> - <signal handler="settings_cb" name="clicked"/> - </object> - </child> - </object> - <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> - <property name="x_options">fill</property> - <property name="y_options">fill</property> - </packing> - </child> - <child> - <object class="GtkVBox" id="list_vbox"> - <property name="border_width">10</property> - <property name="visible">True</property> - <property name="homogeneous">False</property> - <property name="spacing">0</property> - <child> - <object class="GtkHBox" id="mode_hbox"> - <property name="visible">True</property> - <property name="homogeneous">False</property> - <property name="spacing">0</property> - <child> - <object class="GtkLabel" id="mode_label"> - <property name="visible">True</property> - <property name="label" translatable="yes">_Mode:</property> - <property name="use_underline">True</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="mnemonic_widget">mode_menu</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - <accessibility> - <relation target="mode_menu" type="label-for"/> - </accessibility> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - <child> - <object class="GtkComboBox" id="mode_menu"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="has_entry">False</property> - <property name="model">mode_menu_model</property> - <accessibility> - <relation target="mode_label" type="labelled-by"/> - </accessibility> - <child> - <object class="GtkCellRendererText" id="renderer1"/> - <attributes> - <attribute name="text">0</attribute> - </attributes> - </child> - </object> - <packing> - <property name="padding">4</property> - <property name="expand">True</property> - <property name="fill">True</property> - </packing> - </child> - </object> - <packing> - <property name="padding">10</property> - <property name="expand">False</property> - <property name="fill">True</property> - </packing> - </child> - <child> - <object class="GtkScrolledWindow" id="scroller"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hscrollbar_policy">GTK_POLICY_NEVER</property> - <property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property> - <property name="shadow_type">GTK_SHADOW_IN</property> - <property name="window_placement">GTK_CORNER_TOP_LEFT</property> - <child> - <object class="GtkTreeView" id="list"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="headers_visible">False</property> - <property name="rules_hint">True</property> - <property name="reorderable">False</property> - <property name="enable_search">True</property> - <property name="fixed_height_mode">False</property> - <property name="hover_selection">False</property> - <property name="hover_expand">False</property> - </object> - </child> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">True</property> - <property name="fill">True</property> - </packing> - </child> - <child> - <object class="GtkHBox" id="centering_hbox"> - <property name="visible">True</property> - <property name="homogeneous">True</property> - <property name="spacing">0</property> - <child> - <object class="GtkHBox" id="next_prev_hbox"> - <property name="visible">True</property> - <property name="homogeneous">False</property> - <property name="spacing">0</property> - <child> - <object class="GtkButton" id="next"> - <property name="visible">True</property> - <property name="tooltip-text" translatable="yes">Run the next screen saver in the list in full-screen mode (click the mouse to return.)</property> - <property name="can_focus">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> - <property name="focus_on_click">True</property> - <signal handler="run_next_cb" name="clicked"/> - <child> - <object class="GtkArrow" id="arrow1"> - <property name="visible">True</property> - <property name="arrow_type">GTK_ARROW_DOWN</property> - <property name="shadow_type">GTK_SHADOW_OUT</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - </object> - </child> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - <child> - <object class="GtkButton" id="prev"> - <property name="visible">True</property> - <property name="tooltip-text" translatable="yes">Run the previous screen saver in the list in full-screen mode (click the mouse to return.)</property> - <property name="can_focus">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> - <property name="focus_on_click">True</property> - <signal handler="run_prev_cb" name="clicked"/> - <child> - <object class="GtkArrow" id="arrow2"> - <property name="visible">True</property> - <property name="arrow_type">GTK_ARROW_UP</property> - <property name="shadow_type">GTK_SHADOW_OUT</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - </object> - </child> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="pack_type">GTK_PACK_END</property> - </packing> - </child> - </object> - <packing> - <property name="left_attach">0</property> - <property name="right_attach">1</property> - <property name="top_attach">0</property> - <property name="bottom_attach">1</property> - <property name="x_options">fill</property> - </packing> - </child> - <child> - <object class="GtkFrame" id="preview_frame"> - <property name="visible">True</property> - <property name="label_xalign">0</property> - <property name="label_yalign">0.5</property> - <property name="shadow_type">GTK_SHADOW_ETCHED_IN</property> - <accessibility> - <relation target="label1" type="labelled-by"/> - </accessibility> - <child> - <object class="GtkNotebook" id="preview_notebook"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="show_tabs">True</property> - <property name="show_border">False</property> - <property name="tab_pos">GTK_POS_BOTTOM</property> - <property name="scrollable">False</property> - <property name="enable_popup">False</property> - <child> - <object class="GtkAspectFrame" id="preview_aspectframe"> - <property name="border_width">8</property> - <property name="visible">True</property> - <property name="label_xalign">0</property> - <property name="label_yalign">0.5</property> - <property name="shadow_type">GTK_SHADOW_ETCHED_IN</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="ratio">1.33000004292</property> - <property name="obey_child">False</property> - <child> - <object class="GtkDrawingArea" id="preview"> - <property name="visible">True</property> - </object> - </child> - </object> - <packing> - <property name="tab_expand">False</property> - <property name="tab_fill">True</property> - </packing> - </child> - <child type="tab"> - <object class="GtkLabel" id="preview_tab"> - <property name="visible">True</property> - <property name="label" translatable="yes">preview</property> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - </object> - </child> - <child> - <object class="GtkLabel" id="no_preview_label"> - <property name="visible">True</property> - <property name="label" translatable="yes">No Preview -Available</property> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_CENTER</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - </object> - <packing> - <property name="tab_expand">False</property> - <property name="tab_fill">True</property> - </packing> - </child> - <child type="tab"> - <object class="GtkLabel" id="no_preview_tab"> - <property name="visible">True</property> - <property name="label" translatable="yes">no preview</property> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - </object> - </child> - <child> - <object class="GtkLabel" id="not_installed_label"> - <property name="visible">True</property> - <property name="label" translatable="yes">Not -Installed</property> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_CENTER</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - </object> - <packing> - <property name="tab_expand">False</property> - <property name="tab_fill">True</property> - </packing> - </child> - <child type="tab"> - <object class="GtkLabel" id="not_installed_tab"> - <property name="visible">True</property> - <property name="label" translatable="yes">not installed</property> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - </object> - </child> - <child> - <object class="GtkLabel" id="nothing_label"> - <property name="visible">True</property> - <property name="label" translatable="yes">Very few (or no) screen savers appear to be available. - -This probably means that the "xscreensaver-extras" and -"xscreensaver-gl-extras" packages are not installed.</property> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_CENTER</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - </object> - <packing> - <property name="tab_expand">False</property> - <property name="tab_fill">True</property> - </packing> - </child> - <child type="tab"> - <object class="GtkLabel" id="nothing_tab"> - <property name="visible">True</property> - <property name="label" translatable="yes">nothing</property> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - </object> - </child> - </object> - </child> - <child type="label"> - <object class="GtkLabel" id="label1"> - <property name="visible">True</property> - <property name="label" translatable="yes">Description</property> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - <accessibility> - <relation target="preview_frame" type="label-for"/> - </accessibility> - </object> - </child> - </object> - <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">0</property> - <property name="bottom_attach">1</property> - <property name="y_padding">6</property> - <property name="x_options">expand|shrink|fill</property> - <property name="y_options">expand|shrink|fill</property> - </packing> - </child> - </object> - <packing> - <property name="tab_expand">False</property> - <property name="tab_fill">True</property> - </packing> - </child> - <child type="tab"> - <object class="GtkLabel" id="demo_tab"> - <property name="visible">True</property> - <property name="label" translatable="yes">_Display Modes</property> - <property name="use_underline">True</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_CENTER</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="mnemonic_widget">notebook</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - </object> - </child> - <child> - <object class="GtkTable" id="options_table"> - <property name="visible">True</property> - <property name="n_rows">2</property> - <property name="n_columns">2</property> - <property name="homogeneous">True</property> - <property name="row_spacing">0</property> - <property name="column_spacing">0</property> - <child> - <object class="GtkFrame" id="grab_frame"> - <property name="border_width">10</property> - <property name="visible">True</property> - <property name="label_xalign">0</property> - <property name="label_yalign">0.5</property> - <property name="shadow_type">GTK_SHADOW_ETCHED_IN</property> - <accessibility> - <relation target="label2" type="labelled-by"/> - </accessibility> - <child> - <object class="GtkHBox" id="grab_hbox"> - <property name="border_width">8</property> - <property name="visible">True</property> - <property name="homogeneous">False</property> - <property name="spacing">8</property> - <child> - <object class="GtkImage" id="image2"> - <property name="visible">True</property> - <property name="pixbuf">screensaver-snap.png</property> - <property name="xalign">0</property> - <property name="yalign">0</property> - <property name="xpad">4</property> - <property name="ypad">8</property> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - <child> - <object class="GtkVBox" id="grab_vbox"> - <property name="visible">True</property> - <property name="homogeneous">False</property> - <property name="spacing">0</property> - <child> - <object class="GtkCheckButton" id="grab_desk_button"> - <property name="visible">True</property> - <property name="tooltip-text" translatable="yes">Whether the image-manipulating modes should be allowed to operate on an image of your desktop.</property> - <property name="can_focus">True</property> - <property name="label" translatable="yes">Grab Desktop _Images</property> - <property name="use_underline">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> - <property name="focus_on_click">True</property> - <property name="active">False</property> - <property name="inconsistent">False</property> - <property name="draw_indicator">True</property> - <signal handler="pref_changed_cb" name="toggled"/> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="grab_video_button"> - <property name="visible">True</property> - <property name="tooltip-text" translatable="yes">Whether the image-manipulating modes should operate on images captured from the system's video input (if there is one.)</property> - <property name="can_focus">True</property> - <property name="label" translatable="yes">Grab _Video Frames</property> - <property name="use_underline">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> - <property name="focus_on_click">True</property> - <property name="active">False</property> - <property name="inconsistent">False</property> - <property name="draw_indicator">True</property> - <signal handler="pref_changed_cb" name="toggled"/> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="grab_image_button"> - <property name="visible">True</property> - <property name="tooltip-text" translatable="yes">Whether the image-manipulating modes should load image files.</property> - <property name="can_focus">True</property> - <property name="label" translatable="yes">Choose _Random Image:</property> - <property name="use_underline">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> - <property name="focus_on_click">True</property> - <property name="active">False</property> - <property name="inconsistent">False</property> - <property name="draw_indicator">True</property> - <accessibility> - <relation target="image_text" type="controller-for"/> - <relation target="image_browse_button" type="controller-for"/> - </accessibility> - <signal handler="pref_changed_cb" name="toggled"/> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - <child> - <object class="GtkHBox" id="image_hbox"> - <property name="visible">True</property> - <property name="homogeneous">False</property> - <property name="spacing">0</property> - <child> - <object class="GtkLabel" id="grab_dummy"> - <property name="visible">True</property> - <property name="label" translatable="yes"/> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="xpad">8</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="image_text"> - <property name="visible">True</property> - <property name="tooltip-text" translatable="yes">The local directory, RSS feed or Atom feed from which images will be randomly chosen.</property> - <property name="can_focus">True</property> - <property name="editable">True</property> - <property name="visibility">True</property> - <property name="max_length">0</property> - <property name="text" translatable="yes"/> - <property name="has_frame">True</property> - <property name="invisible_char">*</property> - <property name="activates_default">False</property> - <accessibility> - <relation target="grab_image_button" type="labelled-by"/> - <relation target="grab_image_button" type="controlled-by"/> - </accessibility> - <signal handler="pref_changed_cb" name="activate"/> - <signal handler="pref_changed_event_cb" name="focus_out_event"/> - </object> - <packing> - <property name="padding">2</property> - <property name="expand">True</property> - <property name="fill">True</property> - </packing> - </child> - <child> - <object class="GtkButton" id="image_browse_button"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="label" translatable="yes">_Browse</property> - <property name="use_underline">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> - <property name="focus_on_click">True</property> - <signal handler="browse_image_dir_cb" name="clicked"/> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label8"> - <property name="visible">True</property> - <property name="label" translatable="yes">Local directory, or RSS feed URL.</property> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0</property> - <property name="yalign">0.5</property> - <property name="xpad">20</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">True</property> - <property name="fill">True</property> - </packing> - </child> - </object> - </child> - <child type="label"> - <object class="GtkLabel" id="label2"> - <property name="visible">True</property> - <property name="label" translatable="yes">Image Manipulation</property> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - <accessibility> - <relation target="grab_frame" type="label-for"/> - </accessibility> - </object> - </child> - </object> - <packing> - <property name="left_attach">0</property> - <property name="right_attach">1</property> - <property name="top_attach">0</property> - <property name="bottom_attach">1</property> - </packing> - </child> - <child> - <object class="GtkFrame" id="diag_frame"> - <property name="border_width">10</property> - <property name="visible">True</property> - <property name="label_xalign">0</property> - <property name="label_yalign">0.5</property> - <property name="shadow_type">GTK_SHADOW_ETCHED_IN</property> - <accessibility> - <relation target="label3" type="labelled-by"/> - </accessibility> - <child> - <object class="GtkHBox" id="diag_hbox"> - <property name="border_width">8</property> - <property name="visible">True</property> - <property name="homogeneous">False</property> - <property name="spacing">8</property> - <child> - <object class="GtkImage" id="diag_logo"> - <property name="visible">True</property> - <property name="pixbuf">screensaver-diagnostic.png</property> - <property name="xalign">0.5</property> - <property name="yalign">0</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - <child> - <object class="GtkTable" id="text_table"> - <property name="visible">True</property> - <property name="n_rows">5</property> - <property name="n_columns">3</property> - <property name="homogeneous">False</property> - <property name="row_spacing">2</property> - <property name="column_spacing">2</property> - <child> - <object class="GtkRadioButton" id="text_radio"> - <property name="visible">True</property> - <property name="tooltip-text" translatable="yes">Text-displaying modes will display the text typed here.</property> - <property name="can_focus">True</property> - <property name="label" translatable="yes">_Text</property> - <property name="use_underline">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> - <property name="focus_on_click">True</property> - <property name="active">False</property> - <property name="inconsistent">False</property> - <property name="draw_indicator">True</property> - <accessibility> - <relation target="text_entry" type="controller-for"/> - <relation target="text_entry" type="label-for"/> - </accessibility> - <signal handler="pref_changed_cb" last_modification_time="Sun, 20 Mar 2005 21:31:44 GMT" name="toggled"/> - </object> - <packing> - <property name="left_attach">0</property> - <property name="right_attach">1</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> - <property name="x_options">fill</property> - <property name="y_options"/> - </packing> - </child> - <child> - <object class="GtkRadioButton" id="text_file_radio"> - <property name="visible">True</property> - <property name="tooltip-text" translatable="yes">Text-displaying modes will display the contents of this file.</property> - <property name="can_focus">True</property> - <property name="label" translatable="yes">Text _file</property> - <property name="use_underline">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> - <property name="focus_on_click">True</property> - <property name="active">False</property> - <property name="inconsistent">False</property> - <property name="draw_indicator">True</property> - <property name="group">text_radio</property> - <accessibility> - <relation target="text_file_entry" type="label-for"/> - <relation target="text_file_entry" type="controller-for"/> - </accessibility> - <signal handler="pref_changed_cb" last_modification_time="Sun, 20 Mar 2005 21:31:55 GMT" name="toggled"/> - </object> - <packing> - <property name="left_attach">0</property> - <property name="right_attach">1</property> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> - <property name="x_options">fill</property> - <property name="y_options"/> - </packing> - </child> - <child> - <object class="GtkRadioButton" id="text_program_radio"> - <property name="visible">True</property> - <property name="tooltip-text" translatable="yes">Text-displaying modes will display the output of this program.</property> - <property name="can_focus">True</property> - <property name="label" translatable="yes">_Program</property> - <property name="use_underline">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> - <property name="focus_on_click">True</property> - <property name="active">False</property> - <property name="inconsistent">False</property> - <property name="draw_indicator">True</property> - <property name="group">text_radio</property> - <accessibility> - <relation target="text_program_entry" type="label-for"/> - <relation target="text_program_entry" type="controller-for"/> - <relation target="text_program_browse" type="controller-for"/> - </accessibility> - <signal handler="pref_changed_cb" last_modification_time="Sun, 20 Mar 2005 21:32:07 GMT" name="toggled"/> - </object> - <packing> - <property name="left_attach">0</property> - <property name="right_attach">1</property> - <property name="top_attach">3</property> - <property name="bottom_attach">4</property> - <property name="x_options">fill</property> - <property name="y_options"/> - </packing> - </child> - <child> - <object class="GtkRadioButton" id="text_url_radio"> - <property name="visible">True</property> - <property name="tooltip-text" translatable="yes">Text-displaying modes will display the contents of this URL (HTML or RSS).</property> - <property name="can_focus">True</property> - <property name="label" translatable="yes">_URL</property> - <property name="use_underline">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> - <property name="focus_on_click">True</property> - <property name="active">False</property> - <property name="inconsistent">False</property> - <property name="draw_indicator">True</property> - <property name="group">text_radio</property> - <accessibility> - <relation target="text_url_entry" type="label-for"/> - <relation target="text_url_entry" type="controller-for"/> - </accessibility> - <signal handler="pref_changed_cb" last_modification_time="Sun, 20 Mar 2005 21:32:17 GMT" name="toggled"/> - </object> - <packing> - <property name="left_attach">0</property> - <property name="right_attach">1</property> - <property name="top_attach">4</property> - <property name="bottom_attach">5</property> - <property name="x_options">fill</property> - <property name="y_options"/> - </packing> - </child> - <child> - <object class="GtkRadioButton" id="text_host_radio"> - <property name="visible">True</property> - <property name="tooltip-text" translatable="yes">Text-displaying modes will display the local host name, date, and time.</property> - <property name="can_focus">True</property> - <property name="label" translatable="yes">_Host Name and Time</property> - <property name="use_underline">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> - <property name="focus_on_click">True</property> - <property name="active">True</property> - <property name="inconsistent">False</property> - <property name="draw_indicator">True</property> - <property name="group">text_radio</property> - <signal handler="pref_changed_cb" last_modification_time="Sun, 20 Mar 2005 21:31:32 GMT" name="toggled"/> - </object> - <packing> - <property name="left_attach">0</property> - <property name="right_attach">3</property> - <property name="top_attach">0</property> - <property name="bottom_attach">1</property> - <property name="x_options">fill</property> - <property name="y_options"/> - </packing> - </child> - <child> - <object class="GtkEntry" id="text_url_entry"> - <property name="visible">True</property> - <property name="tooltip-text" translatable="yes">Text-displaying modes will display the contents of this URL (HTML or RSS).</property> - <property name="can_focus">True</property> - <property name="editable">True</property> - <property name="visibility">True</property> - <property name="max_length">0</property> - <property name="text" translatable="yes"/> - <property name="has_frame">True</property> - <property name="invisible_char">*</property> - <property name="activates_default">False</property> - <accessibility> - <relation target="text_url_radio" type="controlled-by"/> - </accessibility> - <signal handler="pref_changed_cb" last_modification_time="Sun, 20 Mar 2005 21:33:10 GMT" name="activate"/> - <signal handler="pref_changed_event_cb" last_modification_time="Sun, 20 Mar 2005 21:34:26 GMT" name="focus_out_event"/> - </object> - <packing> - <property name="left_attach">1</property> - <property name="right_attach">3</property> - <property name="top_attach">4</property> - <property name="bottom_attach">5</property> - <property name="y_options"/> - </packing> - </child> - <child> - <object class="GtkButton" id="text_file_browse"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="label" translatable="yes">_Browse</property> - <property name="use_underline">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> - <property name="focus_on_click">True</property> - <accessibility> - <relation target="text_file_radio" type="controlled-by"/> - </accessibility> - <signal handler="browse_text_file_cb" last_modification_time="Sun, 20 Mar 2005 01:24:38 GMT" name="clicked"/> - </object> - <packing> - <property name="left_attach">2</property> - <property name="right_attach">3</property> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> - <property name="x_options">fill</property> - <property name="y_options"/> - </packing> - </child> - <child> - <object class="GtkEntry" id="text_entry"> - <property name="visible">True</property> - <property name="tooltip-text" translatable="yes">Text-displaying modes will display the text typed here.</property> - <property name="can_focus">True</property> - <property name="editable">True</property> - <property name="visibility">True</property> - <property name="max_length">0</property> - <property name="text" translatable="yes"/> - <property name="has_frame">True</property> - <property name="invisible_char">*</property> - <property name="activates_default">False</property> - <accessibility> - <relation target="text_program_radio" type="labelled-by"/> - <relation target="text_program_radio" type="controlled-by"/> - </accessibility> - <signal handler="pref_changed_cb" last_modification_time="Sun, 20 Mar 2005 21:32:42 GMT" name="activate"/> - <signal handler="pref_changed_event_cb" last_modification_time="Sun, 20 Mar 2005 21:33:43 GMT" name="focus_out_event"/> - </object> - <packing> - <property name="left_attach">1</property> - <property name="right_attach">3</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> - <property name="y_options"/> - </packing> - </child> - <child> - <object class="GtkEntry" id="text_program_entry"> - <property name="visible">True</property> - <property name="tooltip-text" translatable="yes">Text-displaying modes will display the output of this program.</property> - <property name="can_focus">True</property> - <property name="editable">True</property> - <property name="visibility">True</property> - <property name="max_length">0</property> - <property name="text" translatable="yes"/> - <property name="has_frame">True</property> - <property name="invisible_char">*</property> - <property name="activates_default">False</property> - <accessibility> - <relation target="text_program_radio" type="labelled-by"/> - <relation target="text_program_radio" type="controlled-by"/> - </accessibility> - <signal handler="pref_changed_cb" last_modification_time="Sun, 20 Mar 2005 21:33:02 GMT" name="activate"/> - <signal handler="pref_changed_event_cb" last_modification_time="Sun, 20 Mar 2005 21:34:15 GMT" name="focus_out_event"/> - </object> - <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">3</property> - <property name="bottom_attach">4</property> - <property name="y_options"/> - </packing> - </child> - <child> - <object class="GtkButton" id="text_program_browse"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="label" translatable="yes">_Browse</property> - <property name="use_underline">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> - <property name="focus_on_click">True</property> - <accessibility> - <relation target="text_program_radio" type="controller-for"/> - </accessibility> - <signal handler="browse_text_program_cb" last_modification_time="Sun, 20 Mar 2005 01:24:51 GMT" name="clicked"/> - </object> - <packing> - <property name="left_attach">2</property> - <property name="right_attach">3</property> - <property name="top_attach">3</property> - <property name="bottom_attach">4</property> - <property name="x_options">fill</property> - <property name="y_options"/> - </packing> - </child> - <child> - <object class="GtkEntry" id="text_file_entry"> - <property name="visible">True</property> - <property name="tooltip-text" translatable="yes">Text-displaying modes will display the contents of this file.</property> - <property name="can_focus">True</property> - <property name="editable">True</property> - <property name="visibility">True</property> - <property name="max_length">0</property> - <property name="text" translatable="yes"/> - <property name="has_frame">True</property> - <property name="invisible_char">*</property> - <property name="activates_default">False</property> - <accessibility> - <relation target="text_file_radio" type="labelled-by"/> - <relation target="text_file_radio" type="controlled-by"/> - </accessibility> - <signal handler="pref_changed_cb" last_modification_time="Sun, 20 Mar 2005 21:32:53 GMT" name="activate"/> - <signal handler="pref_changed_event_cb" last_modification_time="Sun, 20 Mar 2005 21:33:55 GMT" name="focus_out_event"/> - </object> - <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> - <property name="y_options"/> - </packing> - </child> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">True</property> - <property name="fill">True</property> - </packing> - </child> - </object> - </child> - <child type="label"> - <object class="GtkLabel" id="label3"> - <property name="visible">True</property> - <property name="label" translatable="yes">Text Manipulation</property> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - <accessibility> - <relation target="diag_frame" type="label-for"/> - </accessibility> - </object> - </child> - </object> - <packing> - <property name="left_attach">0</property> - <property name="right_attach">1</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> - <property name="x_options">fill</property> - </packing> - </child> - <child> - <object class="GtkFrame" id="dpms_frame"> - <property name="border_width">10</property> - <property name="visible">True</property> - <property name="label_xalign">0</property> - <property name="label_yalign">0.5</property> - <property name="shadow_type">GTK_SHADOW_ETCHED_IN</property> - <child> - <object class="GtkHBox" id="dpms_hbox"> - <property name="border_width">8</property> - <property name="visible">True</property> - <property name="homogeneous">False</property> - <property name="spacing">8</property> - <child> - <object class="GtkImage" id="dpms_logo"> - <property name="visible">True</property> - <property name="pixbuf">screensaver-power.png</property> - <property name="xalign">0.5</property> - <property name="yalign">0</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - <child> - <object class="GtkVBox" id="vbox6"> - <property name="visible">True</property> - <property name="homogeneous">False</property> - <property name="spacing">0</property> - <child> - <object class="GtkCheckButton" id="dpms_button"> - <property name="visible">True</property> - <property name="tooltip-text" translatable="yes">Whether the monitor should be powered down after a while.</property> - <property name="can_focus">True</property> - <property name="label" translatable="yes">_Power Management Enabled</property> - <property name="use_underline">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> - <property name="focus_on_click">True</property> - <property name="active">True</property> - <property name="inconsistent">False</property> - <property name="draw_indicator">True</property> - <accessibility> - <relation target="dpms_suspend_spinbutton" type="controller-for"/> - <relation target="dpms_standby_spinbutton" type="controller-for"/> - <relation target="dpms_off_spinbutton" type="controller-for"/> - </accessibility> - <signal handler="pref_changed_cb" name="toggled"/> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - <child> - <object class="GtkTable" id="dpms_table"> - <property name="visible">True</property> - <property name="n_rows">3</property> - <property name="n_columns">3</property> - <property name="homogeneous">False</property> - <property name="row_spacing">2</property> - <property name="column_spacing">4</property> - <child> - <object class="GtkLabel" id="dpms_standby_label"> - <property name="visible">True</property> - <property name="label" translatable="yes">Stand_by After</property> - <property name="use_underline">True</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">1</property> - <property name="yalign">0.5</property> - <property name="xpad">10</property> - <property name="ypad">0</property> - <property name="mnemonic_widget">dpms_standby_spinbutton</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - <accessibility> - <relation target="dpms_standby_spinbutton" type="label-for"/> - <relation target="dpms_standby_spinbutton" type="flows-to"/> - </accessibility> - </object> - <packing> - <property name="left_attach">0</property> - <property name="right_attach">1</property> - <property name="top_attach">0</property> - <property name="bottom_attach">1</property> - <property name="x_options">fill</property> - <property name="y_options"/> - </packing> - </child> - <child> - <object class="GtkLabel" id="dpms_suspend_label"> - <property name="visible">True</property> - <property name="label" translatable="yes">Sus_pend After</property> - <property name="use_underline">True</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">1</property> - <property name="yalign">0.5</property> - <property name="xpad">10</property> - <property name="ypad">0</property> - <property name="mnemonic_widget">dpms_suspend_spinbutton</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - <accessibility> - <relation target="dpms_suspend_spinbutton" type="label-for"/> - <relation target="dpms_suspend_spinbutton" type="flows-to"/> - </accessibility> - </object> - <packing> - <property name="left_attach">0</property> - <property name="right_attach">1</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> - <property name="x_options">fill</property> - <property name="y_options"/> - </packing> - </child> - <child> - <object class="GtkLabel" id="dpms_off_label"> - <property name="visible">True</property> - <property name="label" translatable="yes">_Off After</property> - <property name="use_underline">True</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">1</property> - <property name="yalign">0.5</property> - <property name="xpad">10</property> - <property name="ypad">0</property> - <property name="mnemonic_widget">dpms_off_spinbutton</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - <accessibility> - <relation target="dpms_off_spinbutton" type="label-for"/> - <relation target="dpms_off_spinbutton" type="flows-to"/> - </accessibility> - </object> - <packing> - <property name="left_attach">0</property> - <property name="right_attach">1</property> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> - <property name="x_options">fill</property> - <property name="y_options"/> - </packing> - </child> - <child> - <object class="GtkLabel" id="dpms_standby_mlabel"> - <property name="visible">True</property> - <property name="label" translatable="yes">minutes</property> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - <accessibility> - <relation target="dpms_standby_spinbutton" type="label-for"/> - <relation target="dpms_standby_spinbutton" type="flows-from"/> - </accessibility> - </object> - <packing> - <property name="left_attach">2</property> - <property name="right_attach">3</property> - <property name="top_attach">0</property> - <property name="bottom_attach">1</property> - <property name="x_options">fill</property> - <property name="y_options"/> - </packing> - </child> - <child> - <object class="GtkLabel" id="dpms_suspend_mlabel"> - <property name="visible">True</property> - <property name="label" translatable="yes">minutes</property> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - <accessibility> - <relation target="dpms_suspend_spinbutton" type="label-for"/> - <relation target="dpms_suspend_spinbutton" type="flows-from"/> - </accessibility> - </object> - <packing> - <property name="left_attach">2</property> - <property name="right_attach">3</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> - <property name="x_options">fill</property> - <property name="y_options"/> - </packing> - </child> - <child> - <object class="GtkLabel" id="dpms_off_mlabel"> - <property name="visible">True</property> - <property name="label" translatable="yes">minutes</property> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - <accessibility> - <relation target="dpms_off_spinbutton" type="label-for"/> - <relation target="dpms_off_spinbutton" type="flows-from"/> - </accessibility> - </object> - <packing> - <property name="left_attach">2</property> - <property name="right_attach">3</property> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> - <property name="x_options">fill</property> - <property name="y_options"/> - </packing> - </child> - <child> - <object class="GtkSpinButton" id="dpms_off_spinbutton"> - <property name="visible">True</property> - <property name="tooltip-text" translatable="yes">How long until the monitor powers down.</property> - <property name="can_focus">True</property> - <property name="climb_rate">15</property> - <property name="digits">0</property> - <property name="numeric">True</property> - <property name="update_policy">GTK_UPDATE_ALWAYS</property> - <property name="snap_to_ticks">True</property> - <property name="wrap">False</property> - <property name="adjustment">adjustment4</property> - <accessibility> - <relation target="dpms_button" type="controlled-by"/> - <relation target="dpms_off_label" type="labelled-by"/> - <relation target="dpms_off_mlabel" type="labelled-by"/> - <relation target="dpms_off_label" type="flows-from"/> - <relation target="dpms_off_mlabel" type="flows-to"/> - </accessibility> - <signal handler="pref_changed_cb" name="activate"/> - <signal handler="pref_changed_event_cb" name="focus_out_event"/> - <signal handler="pref_changed_cb" name="value_changed"/> - </object> - <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> - <property name="x_options"/> - <property name="y_options"/> - </packing> - </child> - <child> - <object class="GtkSpinButton" id="dpms_suspend_spinbutton"> - <property name="visible">True</property> - <property name="tooltip-text" translatable="yes">How long until the monitor goes into power-saving mode.</property> - <property name="can_focus">True</property> - <property name="climb_rate">15</property> - <property name="digits">0</property> - <property name="numeric">True</property> - <property name="update_policy">GTK_UPDATE_ALWAYS</property> - <property name="snap_to_ticks">True</property> - <property name="wrap">False</property> - <property name="adjustment">adjustment5</property> - <accessibility> - <relation target="dpms_button" type="controlled-by"/> - <relation target="dpms_suspend_label" type="labelled-by"/> - <relation target="dpms_suspend_mlabel" type="labelled-by"/> - <relation target="dpms_suspend_label" type="flows-from"/> - <relation target="dpms_suspend_mlabel" type="flows-to"/> - </accessibility> - <signal handler="pref_changed_cb" name="activate"/> - <signal handler="pref_changed_event_cb" name="focus_out_event"/> - <signal handler="pref_changed_cb" name="value_changed"/> - </object> - <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> - <property name="x_options"/> - <property name="y_options"/> - </packing> - </child> - <child> - <object class="GtkSpinButton" id="dpms_standby_spinbutton"> - <property name="visible">True</property> - <property name="tooltip-text" translatable="yes">How long until the monitor goes completely black.</property> - <property name="can_focus">True</property> - <property name="climb_rate">15</property> - <property name="digits">0</property> - <property name="numeric">True</property> - <property name="update_policy">GTK_UPDATE_ALWAYS</property> - <property name="snap_to_ticks">True</property> - <property name="wrap">False</property> - <property name="adjustment">adjustment6</property> - <accessibility> - <relation target="dpms_button" type="controlled-by"/> - <relation target="dpms_standby_label" type="labelled-by"/> - <relation target="dpms_standby_mlabel" type="labelled-by"/> - <relation target="dpms_standby_label" type="flows-from"/> - <relation target="dpms_standby_mlabel" type="flows-to"/> - </accessibility> - <signal handler="pref_changed_cb" name="activate"/> - <signal handler="pref_changed_event_cb" name="focus_out_event"/> - <signal handler="pref_changed_cb" name="value_changed"/> - </object> - <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">0</property> - <property name="bottom_attach">1</property> - <property name="x_options"/> - <property name="y_options"/> - </packing> - </child> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">True</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="dpms_quickoff_button"> - <property name="visible">True</property> - <property name="tooltip-text" translatable="yes">Whether the monitor should be powered off immediately in "Blank Screen Only" mode, regardless of the above power-management timeouts.</property> - <property name="can_focus">True</property> - <property name="label" translatable="yes">_Quick Power-off in Blank Only Mode</property> - <property name="use_underline">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> - <property name="focus_on_click">True</property> - <property name="active">False</property> - <property name="inconsistent">False</property> - <property name="draw_indicator">True</property> - <signal handler="pref_changed_cb" name="toggled"/> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">True</property> - <property name="fill">True</property> - </packing> - </child> - </object> - </child> - <child type="label"> - <object class="GtkLabel" id="label4"> - <property name="visible">True</property> - <property name="label" translatable="yes">Display Power Management</property> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - <accessibility> - <relation target="dpms_frame" type="label-for"/> - </accessibility> - </object> - </child> - </object> - <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">0</property> - <property name="bottom_attach">1</property> - <property name="y_options">fill</property> - </packing> - </child> - <child> - <object class="GtkFrame" id="blanking_frame"> - <property name="border_width">10</property> - <property name="visible">True</property> - <property name="label_xalign">0</property> - <property name="label_yalign">0.5</property> - <property name="shadow_type">GTK_SHADOW_ETCHED_IN</property> - <accessibility> - <relation target="label5" type="labelled-by"/> - </accessibility> - <child> - <object class="GtkHBox" id="fading_hbox"> - <property name="border_width">8</property> - <property name="visible">True</property> - <property name="homogeneous">False</property> - <property name="spacing">8</property> - <child> - <object class="GtkImage" id="image5"> - <property name="visible">True</property> - <property name="pixbuf">screensaver-colorselector.png</property> - <property name="xalign">0.5</property> - <property name="yalign">0</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - <child> - <object class="GtkVBox" id="vbox7"> - <property name="visible">True</property> - <property name="homogeneous">False</property> - <property name="spacing">0</property> - <child> - <object class="GtkCheckButton" id="fade_button"> - <property name="visible">True</property> - <property name="tooltip-text" translatable="yes">Whether the screen should slowly fade to black when the screen saver activates.</property> - <property name="can_focus">True</property> - <property name="label" translatable="yes">Fade to Black when _Blanking</property> - <property name="use_underline">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> - <property name="focus_on_click">True</property> - <property name="active">False</property> - <property name="inconsistent">False</property> - <property name="draw_indicator">True</property> - <accessibility> - <relation target="fade_spinbutton" type="controller-for"/> - </accessibility> - <signal handler="pref_changed_cb" name="toggled"/> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="unfade_button"> - <property name="visible">True</property> - <property name="tooltip-text" translatable="yes">Whether the screen should slowly fade in from black when the screen saver deactivates.</property> - <property name="can_focus">True</property> - <property name="label" translatable="yes">Fade from Black When _Unblanking</property> - <property name="use_underline">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> - <property name="focus_on_click">True</property> - <property name="active">False</property> - <property name="inconsistent">False</property> - <property name="draw_indicator">True</property> - <accessibility> - <relation target="fade_spinbutton" type="controller-for"/> - </accessibility> - <signal handler="pref_changed_cb" name="toggled"/> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - <child> - <object class="GtkHBox" id="fade_hbox"> - <property name="visible">True</property> - <property name="homogeneous">False</property> - <property name="spacing">0</property> - <child> - <object class="GtkLabel" id="fade_dummy"> - <property name="visible">True</property> - <property name="label" translatable="yes"/> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="xpad">3</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="fade_label"> - <property name="visible">True</property> - <property name="label" translatable="yes">F_ade Duration</property> - <property name="use_underline">True</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="mnemonic_widget">fade_spinbutton</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - <accessibility> - <relation target="fade_spinbutton" type="label-for"/> - <relation target="fade_spinbutton" type="flows-to"/> - </accessibility> - </object> - <packing> - <property name="padding">14</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - <child> - <object class="GtkSpinButton" id="fade_spinbutton"> - <property name="visible">True</property> - <property name="tooltip-text" translatable="yes">How long it should take for the screen to fade in and out.</property> - <property name="can_focus">True</property> - <property name="climb_rate">1</property> - <property name="digits">0</property> - <property name="numeric">True</property> - <property name="update_policy">GTK_UPDATE_ALWAYS</property> - <property name="snap_to_ticks">True</property> - <property name="wrap">False</property> - <property name="adjustment">adjustment7</property> - <accessibility> - <relation target="unfade_button" type="controlled-by"/> - <relation target="fade_button" type="controlled-by"/> - <relation target="fade_label" type="labelled-by"/> - <relation target="fade_sec_label" type="labelled-by"/> - <relation target="fade_label" type="flows-from"/> - <relation target="fade_sec_label" type="flows-to"/> - </accessibility> - <signal handler="pref_changed_cb" name="activate"/> - <signal handler="pref_changed_event_cb" name="focus_out_event"/> - <signal handler="pref_changed_cb" name="value_changed"/> - </object> - <packing> - <property name="padding">4</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="fade_sec_label"> - <property name="visible">True</property> - <property name="label" translatable="yes">seconds</property> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - <accessibility> - <relation target="fade_spinbutton" type="label-for"/> - <relation target="fade_spinbutton" type="flows-from"/> - </accessibility> - </object> - <packing> - <property name="padding">2</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - <child> - <object class="GtkHSeparator" id="blanking_hr"> - <property name="visible">True</property> - </object> - <packing> - <property name="padding">8</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - - - <child> - <object class="GtkHBox" id="theme_hbox"> - <property name="visible">True</property> - <property name="homogeneous">False</property> - <property name="spacing">0</property> - <child> - <object class="GtkLabel" id="theme_label"> - <property name="visible">True</property> - <property name="label" translatable="yes">_Theme:</property> - <property name="tooltip-text" translatable="yes">The color scheme to use on the unlock dialog.</property> - <property name="use_underline">True</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="mnemonic_widget">theme_menu</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - <accessibility> - <relation target="theme_menu" type="label-for"/> - </accessibility> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - <child> - <object class="GtkComboBox" id="theme_menu"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="has_entry">False</property> - <property name="model">theme_menu_model</property> - <accessibility> - <relation target="theme_label" type="labelled-by"/> - </accessibility> - <child> - <object class="GtkCellRendererText" id="renderer2"/> - <attributes> - <attribute name="text">0</attribute> - </attributes> - </child> - </object> - <packing> - <property name="padding">4</property> - <property name="expand">True</property> - <property name="fill">True</property> - </packing> - </child> - <child> - <object class="GtkButton" id="theme_preview"> - <property name="visible">True</property> - <property name="tooltip-text" translatable="yes">Show the what the unlock dialog will look like.</property> - <property name="can_default">True</property> - <property name="can_focus">True</property> - <property name="label" translatable="yes">_Preview</property> - <property name="use_underline">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> - <property name="focus_on_click">True</property> - <signal handler="preview_theme_cb" name="clicked"/> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - </object> - <packing> - <property name="padding">10</property> - <property name="expand">False</property> - <property name="fill">True</property> - </packing> - </child> - - - - </object> - <packing> - <property name="padding">0</property> - <property name="expand">True</property> - <property name="fill">True</property> - </packing> - </child> - </object> - </child> - <child type="label"> - <object class="GtkLabel" id="label5"> - <property name="visible">True</property> - <property name="label" translatable="yes">Blanking</property> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - <accessibility> - <relation target="blanking_frame" type="label-for"/> - </accessibility> - </object> - </child> - </object> - <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> - <property name="x_options">fill</property> - <property name="y_options">fill</property> - </packing> - </child> - </object> - <packing> - <property name="tab_expand">False</property> - <property name="tab_fill">True</property> - </packing> - </child> - <child type="tab"> - <object class="GtkLabel" id="options_tab"> - <property name="visible">True</property> - <property name="label" translatable="yes">_Advanced</property> - <property name="use_underline">True</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="mnemonic_widget">notebook</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - </object> - </child> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">True</property> - <property name="fill">True</property> - </packing> - </child> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">True</property> - <property name="fill">True</property> - </packing> - </child> - <child> - <object class="GtkHButtonBox" id="hbuttonbox2"> - <property name="border_width">5</property> - <property name="layout_style">GTK_BUTTONBOX_EDGE</property> - <property name="spacing">10</property> - <child> - <object class="GtkButton" id="helpbutton"> - <property name="visible">True</property> - <property name="can_default">True</property> - <property name="can_focus">True</property> - <property name="label">gtk-help</property> - <property name="use_stock">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> - <property name="focus_on_click">True</property> - <signal handler="doc_menu_cb" name="clicked"/> - </object> - </child> - <child> - <object class="GtkButton" id="closebutton"> - <property name="visible">True</property> - <property name="can_default">True</property> - <property name="can_focus">True</property> - <property name="label">gtk-close</property> - <property name="use_stock">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> - <property name="focus_on_click">True</property> - <signal handler="exit_menu_cb" name="clicked"/> - </object> - </child> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="pack_type">GTK_PACK_END</property> - </packing> - </child> - </object> - </child> - </object> - <object class="GtkDialog" id="xscreensaver_settings_dialog"> - <property name="title" translatable="yes">dialog1</property> - <property name="type">GTK_WINDOW_TOPLEVEL</property> - <property name="window_position">GTK_WIN_POS_NONE</property> - <property name="modal">False</property> - <property name="resizable">True</property> - <property name="destroy_with_parent">False</property> - <property name="decorated">True</property> - <property name="skip_taskbar_hint">False</property> - <property name="skip_pager_hint">False</property> - <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property> - <property name="gravity">GDK_GRAVITY_NORTH_WEST</property> - <property name="focus_on_map">True</property> - <property name="urgency_hint">False</property> - <property name="has_separator">False</property> - <child internal-child="vbox"> - <object class="GtkVBox" id="dialog_vbox"> - <property name="visible">True</property> - <property name="homogeneous">False</property> - <property name="spacing">0</property> - <child internal-child="action_area"> - <object class="GtkHButtonBox" id="dialog_action_area"> - <property name="visible">True</property> - <property name="layout_style">GTK_BUTTONBOX_END</property> - <child> - <object class="GtkButton" id="adv_button"> - <property name="visible">True</property> - <property name="can_default">True</property> - <property name="can_focus">True</property> - <property name="label" translatable="yes">_Advanced >></property> - <property name="use_underline">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> - <property name="focus_on_click">True</property> - <signal handler="settings_adv_cb" name="clicked"/> - </object> - </child> - <child> - <object class="GtkButton" id="std_button"> - <property name="visible">True</property> - <property name="can_default">True</property> - <property name="can_focus">True</property> - <property name="label" translatable="yes">_Standard <<</property> - <property name="use_underline">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> - <property name="focus_on_click">True</property> - <signal handler="settings_std_cb" name="clicked"/> - </object> - </child> - <child> - <object class="GtkButton" id="reset_button"> - <property name="visible">True</property> - <property name="can_default">True</property> - <property name="can_focus">True</property> - <property name="label" translatable="yes">_Reset to Defaults</property> - <property name="use_underline">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> - <property name="focus_on_click">True</property> - <signal handler="settings_reset_cb" name="clicked"/> - </object> - </child> - <child> - <object class="GtkButton" id="cancel_button"> - <property name="visible">True</property> - <property name="can_default">True</property> - <property name="can_focus">True</property> - <property name="label">gtk-cancel</property> - <property name="use_stock">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> - <property name="focus_on_click">True</property> - <signal handler="settings_cancel_cb" name="clicked"/> - </object> - </child> - <child> - <object class="GtkButton" id="ok_button"> - <property name="visible">True</property> - <property name="can_default">True</property> - <property name="can_focus">True</property> - <property name="label">gtk-ok</property> - <property name="use_stock">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> - <property name="focus_on_click">True</property> - <signal handler="settings_ok_cb" name="clicked"/> - </object> - </child> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="pack_type">GTK_PACK_END</property> - </packing> - </child> - <child> - <object class="GtkVBox" id="vbox1"> - <property name="visible">True</property> - <property name="homogeneous">False</property> - <property name="spacing">0</property> - <child> - <object class="GtkFrame" id="opt_frame"> - <property name="visible">True</property> - <property name="label_xalign">0</property> - <property name="label_yalign">0</property> - <property name="shadow_type">GTK_SHADOW_ETCHED_IN</property> - <accessibility> - <relation target="label6" type="labelled-by"/> - </accessibility> - <child> - <object class="GtkNotebook" id="opt_notebook"> - <property name="border_width">12</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="show_tabs">True</property> - <property name="show_border">False</property> - <property name="tab_pos">GTK_POS_BOTTOM</property> - <property name="scrollable">False</property> - <property name="enable_popup">False</property> - <signal handler="settings_switch_page_cb" name="switch_page"/> - <child> - <object class="GtkVBox" id="settings_vbox"> - <property name="visible">True</property> - <property name="homogeneous">False</property> - <property name="spacing">0</property> - <child> - <placeholder/> - </child> - </object> - <packing> - <property name="tab_expand">True</property> - <property name="tab_fill">True</property> - <property name="tab_pack">GTK_PACK_END</property> - </packing> - </child> - <child type="tab"> - <object class="GtkLabel" id="std_label"> - <property name="visible">True</property> - <property name="label" translatable="yes">Standard</property> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - </object> - </child> - <child> - <object class="GtkTable" id="adv_table"> - <property name="visible">True</property> - <property name="n_rows">4</property> - <property name="n_columns">2</property> - <property name="homogeneous">False</property> - <property name="row_spacing">0</property> - <property name="column_spacing">0</property> - <child> - <object class="GtkImage" id="cmd_logo"> - <property name="visible">True</property> - <property name="pixbuf">screensaver-cmndln.png</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="xpad">4</property> - <property name="ypad">8</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="right_attach">1</property> - <property name="top_attach">0</property> - <property name="bottom_attach">1</property> - <property name="x_options">fill</property> - <property name="y_options">fill</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="cmd_label"> - <property name="visible">True</property> - <property name="label" translatable="yes">_Command Line:</property> - <property name="use_underline">True</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="mnemonic_widget">cmd_text</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - <accessibility> - <relation target="cmd_text" type="label-for"/> - </accessibility> - </object> - <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> - <property name="x_options">fill</property> - <property name="y_options"/> - </packing> - </child> - <child> - <object class="GtkEntry" id="cmd_text"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="editable">True</property> - <property name="visibility">True</property> - <property name="max_length">0</property> - <property name="text" translatable="yes"/> - <property name="has_frame">True</property> - <property name="invisible_char">*</property> - <property name="activates_default">False</property> - <accessibility> - <relation target="cmd_label" type="labelled-by"/> - </accessibility> - </object> - <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> - <property name="y_options"/> - </packing> - </child> - <child> - <object class="GtkHBox" id="visual_hbox"> - <property name="visible">True</property> - <property name="homogeneous">False</property> - <property name="spacing">0</property> - <child> - <object class="GtkLabel" id="visual"> - <property name="visible">True</property> - <property name="label" translatable="yes">_Visual:</property> - <property name="use_underline">True</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">1</property> - <property name="yalign">0.5</property> - <property name="xpad">3</property> - <property name="ypad">0</property> - <property name="mnemonic_widget">visual_combo</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - <accessibility> - <relation target="visual_combo" type="label-for"/> - </accessibility> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - <child> - <object class="GtkComboBoxEntry" id="visual_combo"> - <property name="has_entry">False</property> - <property name="visible">True</property> - <property name="model">visual_combo_model</property> - <property name="text-column">0</property> - <accessibility> - <relation target="visual" type="labelled-by"/> - </accessibility> - <child internal-child="entry"> - <object class="GtkEntry" id="visual_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="editable">True</property> - <property name="visibility">True</property> - <property name="max_length">0</property> - <property name="text" translatable="yes"/> - <property name="has_frame">True</property> - <property name="invisible_char">*</property> - <property name="activates_default">False</property> - </object> - </child> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - </object> - <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">3</property> - <property name="bottom_attach">4</property> - <property name="x_options">fill</property> - <property name="y_options">fill</property> - </packing> - </child> - </object> - <packing> - <property name="tab_expand">False</property> - <property name="tab_fill">True</property> - </packing> - </child> - <child type="tab"> - <object class="GtkLabel" id="adv_label"> - <property name="visible">True</property> - <property name="label" translatable="yes">Advanced</property> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - </object> - </child> - </object> - </child> - <child type="label"> - <object class="GtkLabel" id="label6"> - <property name="visible">True</property> - <property name="label" translatable="yes">Settings</property> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - <accessibility> - <relation target="opt_frame" type="label-for"/> - </accessibility> - </object> - </child> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">True</property> - <property name="fill">True</property> - </packing> - </child> - <child> - <object class="GtkFrame" id="doc_frame"> - <property name="visible">True</property> - <property name="label_xalign">0</property> - <property name="label_yalign">0</property> - <property name="shadow_type">GTK_SHADOW_NONE</property> - <child> - <object class="GtkVBox" id="doc_vbox"> - <property name="border_width">5</property> - <property name="visible">True</property> - <property name="homogeneous">False</property> - <property name="spacing">5</property> - <child> - <object class="GtkLabel" id="doc"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="label" translatable="yes"/> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">True</property> - <property name="selectable">True</property> - <property name="xalign">0</property> - <property name="yalign">0</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">True</property> - <property name="fill">True</property> - </packing> - </child> - <child> - <object class="GtkHBox" id="hbox1"> - <property name="visible">True</property> - <property name="homogeneous">False</property> - <property name="spacing">0</property> - <child> - <object class="GtkButton" id="manual"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="label" translatable="yes">_Documentation...</property> - <property name="use_underline">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> - <property name="focus_on_click">True</property> - <signal handler="manual_cb" name="clicked"/> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="pack_type">GTK_PACK_END</property> - </packing> - </child> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - </object> - </child> - <child type="label"> - <object class="GtkLabel" id="label7"> - <property name="visible">True</property> - <property name="label" translatable="yes"/> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - </object> - </child> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - </object> - <packing> - <property name="padding">0</property> - <property name="expand">True</property> - <property name="fill">True</property> - </packing> - </child> - </object> - </child> - <action-widgets> - <action-widget response="0">adv_button</action-widget> - <action-widget response="0">std_button</action-widget> - <action-widget response="0">reset_button</action-widget> - <action-widget response="-6">cancel_button</action-widget> - <action-widget response="-5">ok_button</action-widget> - </action-widgets> - </object> -</interface> |