From: Zygo Blaxell Date: Wed, 2 Jul 2025 03:30:14 +0000 (-0400) Subject: From https://www.jwz.org/xscreensaver/xscreensaver-6.11.tar.gz X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8ff3d9398dd59219244dac4f8bcb02baf8b33594;p=xscreensaver From https://www.jwz.org/xscreensaver/xscreensaver-6.11.tar.gz -rw-rw-r-- 1 zblaxell zblaxell 25158311 Jul 1 21:54 xscreensaver-6.11.tar.gz 3b44aa3829dee9508fa0def1c7e82a4d03dc30e7 xscreensaver-6.11.tar.gz --- diff --git a/Makefile.in b/Makefile.in index e5a2af25..df7843b0 100644 --- a/Makefile.in +++ b/Makefile.in @@ -167,11 +167,11 @@ bump-version:: MAJOR="$$1"; MINOR="$$2"; SUF="$$3"; \ VERS="$$MAJOR.$$MINOR$$SUF" ; \ if [ -z "$$SUF" ]; then \ - MINOR=`echo $$MINOR + 1 | bc | sed 's/^\(.\)$$/0\1/'` ; \ + MINOR=`echo $$((MINOR + 1)) | sed 's/^\(.\)$$/0\1/'` ; \ else \ set - `echo $$SUF | sed 's/^\([^0-9]*\)/\1 /'` ; \ AA="$$1"; BB="$$2"; \ - BB=`echo $$BB + 1 | bc` ; \ + BB=$$((BB + 1)) ; \ SUF="$$AA$$BB" ; \ fi ; \ VERS2="$$MAJOR.$$MINOR$$SUF" ; \ @@ -267,9 +267,12 @@ rpm:: mkdir $$DIR ; \ ( cd $$DIR; mkdir BUILD RPMS RPMS/$$ARCH SOURCES SPECS SRPMS ) ; \ cp -p $${ADIR}$$TGZ $$DIR/SOURCES/ ; \ + set -x ; \ rpmbuild --define "_topdir $$DIR" \ --define "USE_GL yes" \ + --nodeps \ -v -ba xscreensaver.spec ; \ + set +x ; \ echo '' ; \ echo 'RPM build complete' ; \ echo '' ; \ @@ -425,7 +428,7 @@ count:: B=`cd hacks/glx ; ( make -s INSTALL=true install-man | \ grep true | grep -v helper | grep -v ljlatest | wc -l )` ; \ echo " GLX:" $$B ; \ - C=`echo $$A + $$B | bc` ; \ + C=$$((A + B)) ; \ echo " Total:" $$C ; \ diff --git a/OSX/Makefile b/OSX/Makefile index 2dab5d03..394ec5ad 100644 --- a/OSX/Makefile +++ b/OSX/Makefile @@ -1,4 +1,4 @@ -# XScreenSaver for MacOS X, Copyright © 2006-2023 Jamie Zawinski. +# XScreenSaver for MacOS X, Copyright © 2006-2025 Jamie Zawinski. XCODE_APP = /Applications/Xcode.app TARGETS = All Savers @@ -45,6 +45,7 @@ debug:: release:: $(MAKE2) _release > /dev/null + $(MAKE2) update_installer_size Sparkle.framework: @@ -299,6 +300,27 @@ build/Release/installer.pkg: installer.rtf installer.xml installer.sh installer. rm -rf "$$STAGE" ; \ +# Set the installer's REQUIRED_SPACE to the size in MB of the build/Release/ +# directory, plus 8%, rounded up to the nearest 10. That should be pretty +# close to reality. +# +update_installer_size:: + @ \ + SIZE=`du -ks build/Release/ | sed 's/[^0-9].*//'` ; \ + SIZE=`perl -e "print int($$SIZE / 10240 * 1.08 + 0.999) * 10;"` ; \ + FILE="installer.sh" ; \ + TMP="/tmp/installer.$$$$.sh" ; \ + sed -e "s/^\(REQUIRED_SPACE=\)[0-9]*\(.*\)/\1$$SIZE\2/" \ + < "$$FILE" > "$$TMP" ; \ + if cmp -s "$$FILE" "$$TMP" ; then \ + echo "$$FILE unchanged (required space $$SIZE MB)" ; \ + else \ + cat "$$TMP" > "$$FILE" ; \ + echo "$$FILE updated (required space $$SIZE MB)" ; \ + fi ; \ + rm -f "$$TMP" + + dmg:: distdepend check_versions dmg:: build/Release/installer.pkg dmg:: _dmg notarize staple updates.xml diff --git a/OSX/PrefsReader.m b/OSX/PrefsReader.m index 84c01f1c..a67cf9f2 100644 --- a/OSX/PrefsReader.m +++ b/OSX/PrefsReader.m @@ -488,7 +488,7 @@ // This happens when the .xml file says things like arg="-foo 'bar baz'" if (result[0] == '\'' && result[strlen(result)-1] == '\'') { result[strlen(result)-1] = 0; - strcpy (result, result+1); + memmove (result, result+1, strlen (result)); } // Kludge: assume that any string that begins with "~" and has a "/" diff --git a/OSX/Randomizer.plist b/OSX/Randomizer.plist index 27ade404..01e6eae9 100644 --- a/OSX/Randomizer.plist +++ b/OSX/Randomizer.plist @@ -17,7 +17,7 @@ CFBundleSignature ???? CFBundleVersion - 6.10 + 6.11 LSMinimumSystemVersion ${MACOSX_DEPLOYMENT_TARGET} NSPrincipalClass @@ -25,12 +25,12 @@ LSApplicationCategoryType public.app-category.entertainment CFBundleShortVersionString - 6.10 + 6.11 CFBundleLongVersionString - 6.10 + 6.11 CFBundleGetInfoString - 6.10 + 6.11 NSHumanReadableCopyright - 6.10 + 6.11 diff --git a/OSX/SaverRunner.m b/OSX/SaverRunner.m index 44da19f0..c2813963 100644 --- a/OSX/SaverRunner.m +++ b/OSX/SaverRunner.m @@ -1078,7 +1078,7 @@ relabel_menus (NSObject *v, NSString *old_str, NSString *new_str) p = [dir stringByAppendingPathComponent: p]; NSString *classname = [[p lastPathComponent] stringByDeletingPathExtension]; - NSString *title = classname; + NSString *title; // Get the title's capitalization right by reading the XML file. @@ -1095,6 +1095,8 @@ relabel_menus (NSObject *v, NSString *old_str, NSString *new_str) NSAssert1 (r.length, @"no name in %@", p); if (r.length) title = [xml substringToIndex: r.location]; + else + title = classname; } # else // !HAVE_IPHONE NSBundle *nsb = [NSBundle bundleWithPath:p]; diff --git a/OSX/SaverRunner.plist b/OSX/SaverRunner.plist index 97da3084..c9dbcf88 100644 --- a/OSX/SaverRunner.plist +++ b/OSX/SaverRunner.plist @@ -17,7 +17,7 @@ CFBundleSignature ???? CFBundleVersion - 6.10 + 6.11 LSMinimumSystemVersion ${MACOSX_DEPLOYMENT_TARGET} NSPrincipalClass @@ -25,13 +25,13 @@ LSApplicationCategoryType public.app-category.entertainment CFBundleShortVersionString - 6.10 + 6.11 CFBundleLongVersionString - 6.10 + 6.11 CFBundleGetInfoString - 6.10 + 6.11 NSHumanReadableCopyright - 6.10 + 6.11 NSMainNibFile SaverRunner CFBundleIconFile diff --git a/OSX/Updater.plist b/OSX/Updater.plist index 7d1071e5..f923b227 100644 --- a/OSX/Updater.plist +++ b/OSX/Updater.plist @@ -17,7 +17,7 @@ CFBundleSignature ???? CFBundleVersion - 6.10 + 6.11 LSMinimumSystemVersion ${MACOSX_DEPLOYMENT_TARGET} NSPrincipalClass @@ -25,13 +25,13 @@ LSApplicationCategoryType public.app-category.entertainment CFBundleShortVersionString - 6.10 + 6.11 CFBundleLongVersionString - 6.10 + 6.11 CFBundleGetInfoString - 6.10 + 6.11 NSHumanReadableCopyright - 6.10 + 6.11 NSMainNibFile Updater CFBundleIconFile diff --git a/OSX/XScreenSaver.plist b/OSX/XScreenSaver.plist index e86aaa0e..7b6dceb7 100644 --- a/OSX/XScreenSaver.plist +++ b/OSX/XScreenSaver.plist @@ -17,7 +17,7 @@ CFBundleSignature ???? CFBundleVersion - 6.10 + 6.11 LSMinimumSystemVersion ${MACOSX_DEPLOYMENT_TARGET} NSPrincipalClass @@ -25,13 +25,13 @@ LSApplicationCategoryType public.app-category.entertainment CFBundleShortVersionString - 6.10 + 6.11 CFBundleLongVersionString - 6.10 + 6.11 CFBundleGetInfoString - 6.10 + 6.11 NSHumanReadableCopyright - 6.10 + 6.11 NSMainNibFile SaverRunner diff --git a/OSX/bindist.rtf b/OSX/bindist.rtf index 03628968..383cf49c 100644 --- a/OSX/bindist.rtf +++ b/OSX/bindist.rtf @@ -16,8 +16,8 @@ \b0 by Jamie Zawinski\ and many others\ \ -version 6.10\ -27-Apr-2025\ +version 6.11\ +01-Jul-2025\ \ {\field{\*\fldinst{HYPERLINK "https://www.jwz.org/xscreensaver/"}}{\fldrslt \cf2 \ul \ulc2 https://www.jwz.org/xscreensaver/}}\ \pard\pardeftab720 diff --git a/OSX/bindist.webloc b/OSX/bindist.webloc index bb777090..6fcd10ca 100644 --- a/OSX/bindist.webloc +++ b/OSX/bindist.webloc @@ -3,6 +3,6 @@ URL - https://itunes.apple.com/app/xscreensaver/id539014593 + https://itunes.apple.com/app/xscreensaver/id539014593?mt=8 diff --git a/OSX/iSaverRunner.plist b/OSX/iSaverRunner.plist index 2495d6c2..b1f7bd23 100644 --- a/OSX/iSaverRunner.plist +++ b/OSX/iSaverRunner.plist @@ -17,17 +17,17 @@ CFBundleSignature ???? CFBundleVersion - 6.10 + 6.11 LSApplicationCategoryType public.app-category.entertainment CFBundleShortVersionString - 6.10 + 6.11 CFBundleLongVersionString - 6.10 + 6.11 CFBundleGetInfoString - 6.10 + 6.11 NSHumanReadableCopyright - 6.10 + 6.11 NSMainNibFile iSaverRunner CFBundleDisplayName diff --git a/OSX/installer.sh b/OSX/installer.sh index 62e15cc0..22991746 100755 --- a/OSX/installer.sh +++ b/OSX/installer.sh @@ -1,5 +1,5 @@ #!/bin/bash -# XScreenSaver, Copyright © 2013-2023 Jamie Zawinski +# XScreenSaver, Copyright © 2013-2025 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 @@ -20,7 +20,7 @@ #set -x DEBUG=0 -REQUIRED_SPACE=360 # MB. Highly approximate; updated 6.07. +REQUIRED_SPACE=260 # MB export PATH="/bin:/sbin:/usr/bin:/usr/sbin:$PATH" diff --git a/OSX/tvSaverRunner.plist b/OSX/tvSaverRunner.plist index 9bc514e0..0c650777 100644 --- a/OSX/tvSaverRunner.plist +++ b/OSX/tvSaverRunner.plist @@ -17,17 +17,17 @@ CFBundleSignature ???? CFBundleVersion - 6.10 + 6.11 LSApplicationCategoryType public.app-category.entertainment CFBundleShortVersionString - 6.10 + 6.11 CFBundleLongVersionString - 6.10 + 6.11 CFBundleGetInfoString - 6.10 + 6.11 NSHumanReadableCopyright - 6.10 + 6.11 CFBundleDisplayName ${PRODUCT_NAME} CFBundleIcons diff --git a/README b/README index 69137d64..82b1769a 100644 --- a/README +++ b/README @@ -69,6 +69,10 @@ Interested in writing a new screen saver? Version History =============================================================================== +6.11 * X11: Now supports Wayland (blanking only, not locking). + * X11: More reliable and timely DPMS activation. + * X11: Dead keys work in password input. + * X11: Fixed a couple of minor Y2038 bugs. 6.10 * New hacks, `dumpsterfire', `hopffibration', `platonicfolding' and `klondike'. * Rewrote the VT100 emulator for 'apple2' and 'phosphor'. diff --git a/README.hacking b/README.hacking index 39d8b166..2f65fbb2 100644 --- a/README.hacking +++ b/README.hacking @@ -182,6 +182,14 @@ Programming Tips - On modern machines, OpenGL will always run faster than Xlib. It's also more portable. Consider writing in OpenGL whenever possible. + - You must use OpenGL 1.3. This means "immedidate mode", no shaders. + (It is possible to use GLSL shader language if you do so *optionally* + but it is a huge pain because the savers must run when that fails a + runtime check.) + + - If you have static image assets, keep them PNG and small. See + hacks/images/ and "maze" or "peepers" for examples. + - Free any memory you allocate. While screen savers under X11 have their memory freed automatically when their process is killed by the XScreenSaver daemon, under iOS and Android screen savers exist diff --git a/config.h.in b/config.h.in index 88405d98..94f888e1 100644 --- a/config.h.in +++ b/config.h.in @@ -1,7 +1,7 @@ /* config.h.in. Generated from configure.ac by autoheader. */ -/* xscreensaver, Copyright © 1991-2022 Jamie Zawinski. +/* xscreensaver, Copyright © 1991-2025 Jamie Zawinski. * Generate this file by running 'configure' rather than editing it by hand. */ @@ -299,6 +299,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_UTIL_H +/* Define this if you have the Wayland libraries. */ +#undef HAVE_WAYLAND + /* Define this if you have XF86VidModeGetViewPort, for virtual desktops. */ #undef HAVE_XF86VMODE diff --git a/configure b/configure index af5e09ee..f52c29b5 100755 --- a/configure +++ b/configure @@ -687,6 +687,11 @@ SETCAP_HACKS SETUID_HACKS SETUID_AUTH PROG_SETCAP +WAYLAND_IDLE_OBJS +WAYLAND_GEN +WAYLAND_DATADIR +WAYLAND_LIBS +WAYLAND_CFLAGS LIBCAP_LIBS LIBCAP_CFLAGS PASSWD_LIBS @@ -847,8 +852,8 @@ enable_rpath with_libiconv_prefix with_libintl_prefix with_app_defaults +enable_dependency_tracking with_hackdir -enable_subdir with_configdir with_fontdir with_dpms_ext @@ -867,6 +872,7 @@ with_proc_interrupts with_proc_oom with_systemd with_elogind +with_wayland enable_locking enable_root_passwd with_pam @@ -1536,16 +1542,18 @@ Server Extension Options: --with-systemd Support systemd requests to lock on suspend, and to allow video players to inhibit the screen saver. --with-elogind Use elogind instead of systemd. + --with-wayland Use native Wayland when available. Screen Locking Options: --disable-locking Do not allow locking of the display at all. --with-pam Use Pluggable Authentication Modules. --with-pam-service-name Set the name of the xscreensaver PAM service. - --enable-pam-account Whether PAM should check the result of account - modules when authenticating. Only do this if you - have "account" modules configured on your system. - --enable-root-passwd Allow the root password to unlock, if not using PAM. + --enable-pam-check-account-type + Whether PAM should check the result of account + modules when authenticating. Only do this if you + have "account" modules configured on your system. + --enable-root-passwd Allow the root password to unlock, if not using PAM. --with-kerberos Include support for Kerberos authentication. --with-shadow Include support for shadow password authentication. @@ -10149,6 +10157,13 @@ fi printf "%s\n" "$ac_cv_x_app_defaults" >&6; } eval ac_x_app_defaults="$ac_cv_x_app_defaults" +# Ignore --disable-dependency-tracking, which RHEL8 rpmbuild adds by default. +# Check whether --enable-dependency-tracking was given. +if test ${enable_dependency_tracking+y} +then : + enableval=$enable_dependency_tracking; +fi + ############################################################################### # # Handle the --with-hackdir option @@ -10181,23 +10196,6 @@ HACKDIR=`echo "${HACKDIR}" | sed 's@/$@@;s@//*@/@g'` # Expand HACKDIR as HACKDIR_FULL HACKDIR_FULL=`eval eval eval eval eval eval eval eval eval echo $HACKDIR` -# This option used to be called --enable-subdir; make sure that is no longer -# used, since configure brain-damagedly ignores unknown --enable options. - -obsolete_enable= -# Check whether --enable-subdir was given. -if test ${enable_subdir+y} -then : - enableval=$enable_subdir; obsolete_enable=yes -fi - -if test -n "$obsolete_enable"; then - echo "error: the --enable-subdir option has been replaced with" - echo " the new --with-hackdir option; see \`configure --help'" - echo " for more information." - exit 1 -fi - ############################################################################### # # Handle the --with-configdir option @@ -13418,6 +13416,230 @@ if test "$have_elogind" = yes; then fi +############################################################################### +# +# Check for Wayland +# +############################################################################### + +have_wayland=no +with_wayland_req=unspecified + +# Check whether --with-wayland was given. +if test ${with_wayland+y} +then : + withval=$with_wayland; with_wayland="$withval"; with_wayland_req="$withval" +else case e in #( + e) with_wayland=yes ;; +esac +fi + + case "$with_wayland" in + yes) ;; + no) ;; + + /*) + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Wayland headers" >&5 +printf %s "checking for Wayland headers... " >&6; } + d=$with_wayland/include + if test -d $d; then + X_CFLAGS="-I$d $X_CFLAGS" + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $d" >&5 +printf "%s\n" "$d" >&6; } + else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: not found ($d: no such directory)" >&5 +printf "%s\n" "not found ($d: no such directory)" >&6; } + fi + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Wayland libs" >&5 +printf %s "checking for Wayland libs... " >&6; } + d=$with_wayland/lib + if test -d $d; then + X_LIBS="-L$d $X_LIBS" + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $d" >&5 +printf "%s\n" "$d" >&6; } + else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: not found ($d: no such directory)" >&5 +printf "%s\n" "not found ($d: no such directory)" >&6; } + fi + + # replace the directory string with "yes". + with_wayland_req="yes" + with_wayland=$with_wayland_req + ;; + + *) + echo "" + echo "error: argument to --with-wayland must be \"yes\", \"no\", or a directory." + echo " If it is a directory, then \`DIR/include' will be added to" + echo " the -I list, and \`DIR/lib' will be added to the -L list." + exit 1 + ;; + esac + +if test "$with_wayland" != yes -a "$with_wayland" != no ; then + echo "error: must be yes or no: --with-wayland=$with_wayland" + exit 1 +fi + +if test "$with_wayland" = yes; then + have_wayland=no + + pkgs='' + ok="yes" + pkg_check_version wayland-server 1.0 + pkg_check_version wayland-client 1.0 + pkg_check_version wayland-scanner 1.0 + ac_wayland_version_string="$vers" + have_wayland="$ok" + wayland_pkgs="$pkgs" + + if test "$have_wayland" = yes; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Wayland includes" >&5 +printf %s "checking for Wayland includes... " >&6; } +if test ${ac_cv_wayland_config_cflags+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_cv_wayland_config_cflags=`$pkg_config --cflags $pkgs` ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_wayland_config_cflags" >&5 +printf "%s\n" "$ac_cv_wayland_config_cflags" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Wayland libs" >&5 +printf %s "checking for Wayland libs... " >&6; } +if test ${ac_cv_wayland_config_libs+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_cv_wayland_config_libs=`$pkg_config --libs $pkgs` ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_wayland_config_libs" >&5 +printf "%s\n" "$ac_cv_wayland_config_libs" >&6; } + fi + + ac_wayland_config_cflags=$ac_cv_wayland_config_cflags + ac_wayland_config_libs=$ac_cv_wayland_config_libs + + WAYLAND_DATADIR="" + WAYLAND_GEN="" + WAYLAND_IDLE_OBJS="" + if test "$have_wayland" = yes; then + WAYLAND_DATADIR=`$pkg_config --variable=prefix wayland-client` + WAYLAND_DATADIR="$WAYLAND_DATADIR/share" + WAYLAND_GEN='$(WAYLAND_GEN_HDRS) $(WAYLAND_GEN_SRCS)' + WAYLAND_IDLE_OBJS='$(WAYLAND_IDLE_OBJS_1)' + fi + + if test "$have_wayland" = yes; then + INCLUDES="$INCLUDES $ac_wayland_config_cflags" + WAYLAND_LIBS="$WAYLAND_LIBS $ac_wayland_config_libs" + printf "%s\n" "#define HAVE_WAYLAND 1" >>confdefs.h + + fi +fi + +# Check for the various Gnome help and URL loading programs. +# +WITH_BROWSER=gnome-open +if test "$have_wayland" = yes; then + for ac_prog in gnome-open +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_gnome_open_program+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$gnome_open_program"; then + ac_cv_prog_gnome_open_program="$gnome_open_program" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_gnome_open_program="$ac_prog" + printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi ;; +esac +fi +gnome_open_program=$ac_cv_prog_gnome_open_program +if test -n "$gnome_open_program"; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $gnome_open_program" >&5 +printf "%s\n" "$gnome_open_program" >&6; } +else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } +fi + + test -n "$gnome_open_program" && break +done + + for ac_prog in gnome-url-show +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_gnome_url_show_program+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$gnome_url_show_program"; then + ac_cv_prog_gnome_url_show_program="$gnome_url_show_program" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_gnome_url_show_program="$ac_prog" + printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi ;; +esac +fi +gnome_url_show_program=$ac_cv_prog_gnome_url_show_program +if test -n "$gnome_url_show_program"; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $gnome_url_show_program" >&5 +printf "%s\n" "$gnome_url_show_program" >&6; } +else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } +fi + + test -n "$gnome_url_show_program" && break +done + +fi + ############################################################################### # # The --enable-locking option @@ -21167,14 +21389,6 @@ fi PASSWD_SRCS="$PASSWD_SRCS \$(PWENT_SRCS)" PASSWD_OBJS="$PASSWD_OBJS \$(PWENT_OBJS)" -if test "$enable_locking" = yes; then - LOCK_SRCS='$(LOCK_SRCS_1) $(PASSWD_SRCS)' - LOCK_OBJS='$(LOCK_OBJS_1) $(PASSWD_OBJS)' -else - LOCK_SRCS='$(NOLOCK_SRCS_1)' - LOCK_OBJS='$(NOLOCK_OBJS_1)' -fi - if test "$ac_macosx" = yes; then EXES_OSX='$(EXES_OSX)' SCRIPTS_OSX='$(SCRIPTS_OSX)' diff --git a/configure.ac b/configure.ac index 5e847061..914b7b4e 100644 --- a/configure.ac +++ b/configure.ac @@ -1,4 +1,4 @@ -# configure.in --- xscreensaver, Copyright © 1997-2023 Jamie Zawinski. +# configure.in --- xscreensaver, Copyright © 1997-2025 Jamie Zawinski. # # Note: upgrading past 2.69_5 breaks the world, mostly the po/ shitshow. @@ -32,7 +32,7 @@ fi ############################################################################### AH_TOP([ -/* xscreensaver, Copyright © 1991-2022 Jamie Zawinski. +/* xscreensaver, Copyright © 1991-2025 Jamie Zawinski. * Generate this file by running 'configure' rather than editing it by hand. */ ]) @@ -222,6 +222,9 @@ AH_TEMPLATE([HAVE_GLE], AH_TEMPLATE([HAVE_GLE3], [Define this if GL Extrusion is version 3.]) +AH_TEMPLATE([HAVE_WAYLAND], + [Define this if you have the Wayland libraries.]) + AH_TEMPLATE([HAVE_FFMPEG], [Define this if you have the ffmpeg libraries.]) @@ -1213,6 +1216,9 @@ Installation Options: [eval ac_x_app_defaults="$withval"]) AC_PATH_X_APP_DEFAULTS +# Ignore --disable-dependency-tracking, which RHEL8 rpmbuild adds by default. +AC_ARG_ENABLE(dependency-tracking,,) + ############################################################################### # @@ -1240,19 +1246,6 @@ HACKDIR=`echo "${HACKDIR}" | sed 's@/$@@;s@//*@/@g'` # Expand HACKDIR as HACKDIR_FULL HACKDIR_FULL=`eval eval eval eval eval eval eval eval eval echo $HACKDIR` -# This option used to be called --enable-subdir; make sure that is no longer -# used, since configure brain-damagedly ignores unknown --enable options. - -obsolete_enable= -AC_ARG_ENABLE(subdir,,[obsolete_enable=yes]) -if test -n "$obsolete_enable"; then - echo "error: the --enable-subdir option has been replaced with" - echo " the new --with-hackdir option; see \`configure --help'" - echo " for more information." - exit 1 -fi - - ############################################################################### # # Handle the --with-configdir option @@ -2210,6 +2203,75 @@ if test "$have_elogind" = yes; then fi +############################################################################### +# +# Check for Wayland +# +############################################################################### + +have_wayland=no +with_wayland_req=unspecified +AC_ARG_WITH(wayland, +[ --with-wayland Use native Wayland when available.], + [with_wayland="$withval"; with_wayland_req="$withval"],[with_wayland=yes]) + +HANDLE_X_PATH_ARG(with_wayland, --with-wayland, Wayland) + +if test "$with_wayland" != yes -a "$with_wayland" != no ; then + echo "error: must be yes or no: --with-wayland=$with_wayland" + exit 1 +fi + + +if test "$with_wayland" = yes; then + have_wayland=no + + pkgs='' + ok="yes" + pkg_check_version wayland-server 1.0 + pkg_check_version wayland-client 1.0 + pkg_check_version wayland-scanner 1.0 + ac_wayland_version_string="$vers" + have_wayland="$ok" + wayland_pkgs="$pkgs" + + if test "$have_wayland" = yes; then + AC_CACHE_CHECK([for Wayland includes], ac_cv_wayland_config_cflags, + [ac_cv_wayland_config_cflags=`$pkg_config --cflags $pkgs`]) + AC_CACHE_CHECK([for Wayland libs], ac_cv_wayland_config_libs, + [ac_cv_wayland_config_libs=`$pkg_config --libs $pkgs`]) + fi + + ac_wayland_config_cflags=$ac_cv_wayland_config_cflags + ac_wayland_config_libs=$ac_cv_wayland_config_libs + + WAYLAND_DATADIR="" + WAYLAND_GEN="" + WAYLAND_IDLE_OBJS="" + if test "$have_wayland" = yes; then + WAYLAND_DATADIR=`$pkg_config --variable=prefix wayland-client` + WAYLAND_DATADIR="$WAYLAND_DATADIR/share" + WAYLAND_GEN='$(WAYLAND_GEN_HDRS) $(WAYLAND_GEN_SRCS)' + WAYLAND_IDLE_OBJS='$(WAYLAND_IDLE_OBJS_1)' + fi + + if test "$have_wayland" = yes; then + INCLUDES="$INCLUDES $ac_wayland_config_cflags" + WAYLAND_LIBS="$WAYLAND_LIBS $ac_wayland_config_libs" + AC_DEFINE(HAVE_WAYLAND) + fi +fi + + +# Check for the various Gnome help and URL loading programs. +# +WITH_BROWSER=gnome-open +if test "$have_wayland" = yes; then + AC_CHECK_PROGS(gnome_open_program, gnome-open) + AC_CHECK_PROGS(gnome_url_show_program, gnome-url-show) +fi + + ############################################################################### # # The --enable-locking option @@ -2296,10 +2358,11 @@ Screen Locking Options: AC_ARG_WITH([pam_service_name], [ --with-pam-service-name Set the name of the xscreensaver PAM service. - --enable-pam-account Whether PAM should check the result of account - modules when authenticating. Only do this if you - have "account" modules configured on your system. - --enable-root-passwd Allow the root password to unlock, if not using PAM.], + --enable-pam-check-account-type + Whether PAM should check the result of account + modules when authenticating. Only do this if you + have "account" modules configured on your system. + --enable-root-passwd Allow the root password to unlock, if not using PAM.], [pam_service_name="$withval"],[pam_service_name="xscreensaver"]) AC_ARG_ENABLE(pam-check-account-type, @@ -4325,14 +4388,6 @@ PASSWD_SRCS="$PASSWD_SRCS \$(PWENT_SRCS)" PASSWD_OBJS="$PASSWD_OBJS \$(PWENT_OBJS)" -if test "$enable_locking" = yes; then - LOCK_SRCS='$(LOCK_SRCS_1) $(PASSWD_SRCS)' - LOCK_OBJS='$(LOCK_OBJS_1) $(PASSWD_OBJS)' -else - LOCK_SRCS='$(NOLOCK_SRCS_1)' - LOCK_OBJS='$(NOLOCK_OBJS_1)' -fi - if test "$ac_macosx" = yes; then EXES_OSX='$(EXES_OSX)' SCRIPTS_OSX='$(SCRIPTS_OSX)' @@ -4467,6 +4522,11 @@ AC_SUBST(XINERAMA_LIBS) AC_SUBST(PASSWD_LIBS) AC_SUBST(LIBCAP_CFLAGS) AC_SUBST(LIBCAP_LIBS) +AC_SUBST(WAYLAND_CFLAGS) +AC_SUBST(WAYLAND_LIBS) +AC_SUBST(WAYLAND_DATADIR) +AC_SUBST(WAYLAND_GEN) +AC_SUBST(WAYLAND_IDLE_OBJS) AC_SUBST(PROG_SETCAP) AC_SUBST(SETUID_AUTH) AC_SUBST(SETUID_HACKS) diff --git a/driver/Makefile.in b/driver/Makefile.in index 4b3c1cbb..2dccc60f 100644 --- a/driver/Makefile.in +++ b/driver/Makefile.in @@ -1,4 +1,4 @@ -# driver/Makefile.in --- xscreensaver, Copyright © 1997-2023 Jamie Zawinski. +# driver/Makefile.in --- xscreensaver, Copyright © 1997-2025 Jamie Zawinski. # the `../configure' script generates `driver/Makefile' from this file. @SET_MAKE@ @@ -33,6 +33,27 @@ GTK_SHAREDIR = $(GTK_DATADIR)/xscreensaver UPDATE_ICON_CACHE = gtk-update-icon-cache GLIB_COMPILE_RESOURCES = @GLIB_COMPILE_RESOURCES@ +WAYLAND_CFLAGS = @WAYLAND_CFLAGS@ +WAYLAND_LIBS = @WAYLAND_LIBS@ +WAYLAND_DATADIR = @WAYLAND_DATADIR@ +WAYLAND_GEN = @WAYLAND_GEN@ + +WAYLAND_GEN_XML = xdg-shell.xml \ + ext-idle-notify-v1.xml \ + idle.xml +WAYLAND_GEN_HDRS = xdg-shell-v1-client-protocol.h \ + ext-idle-notify-v1-client-protocol.h \ + idle-client-protocol.h +WAYLAND_GEN_SRCS = xdg-shell-v1-protocol.c \ + ext-idle-notify-v1-protocol.c \ + idle-protocol.c + +WAYLAND_IDLE_OBJS = @WAYLAND_IDLE_OBJS@ +WAYLAND_IDLE_SRCS = wayland-idle.c +WAYLAND_IDLE_OBJS_1 = wayland-idle.o \ + ext-idle-notify-v1-protocol.o \ + idle-protocol.o + HACKDIR = @HACKDIR@ HACK_CONF_DIR = @HACK_CONF_DIR@ @@ -101,10 +122,11 @@ XINPUT_LIBS = -lXi XML_LIBS = @XML_LIBS@ DAEMON_DEFS = -DDEFAULT_PATH_PREFIX='"@HACKDIR@"' -DAD_DIR='"$(AD_DIR)"' -DAEMON_SRCS = xscreensaver.c blurb.c atoms.c clientmsg.c xinput.c prefs.c +DAEMON_SRCS = xscreensaver.c blurb.c atoms.c clientmsg.c xinput.c prefs.c \ + $(WAYLAND_IDLE_SRCS) DAEMON_OBJS = xscreensaver.o blurb.o atoms.o clientmsg.o xinput.o prefs.o \ - $(UTILS_BIN)/xmu.o -DAEMON_LIBS = $(LIBS_PRE) $(XINPUT_LIBS) -lX11 $(LIBS_POST) + $(UTILS_BIN)/xmu.o $(WAYLAND_IDLE_OBJS) +DAEMON_LIBS = $(LIBS_PRE) $(XINPUT_LIBS) -lX11 $(WAYLAND_LIBS) $(LIBS_POST) GFX_DEFS = -DLOCALEDIR=\"$(localedir)\" GFX_SRCS = xscreensaver-gfx.c screens.c windows.c subprocs.c \ @@ -140,10 +162,6 @@ PAM_OBJS = passwd-pam.o PASSWD_SRCS = @PASSWD_SRCS@ PASSWD_OBJS = @PASSWD_OBJS@ -LOCK_SRCS = @LOCK_SRCS@ -LOCK_OBJS = @LOCK_OBJS@ - - AUTH_DEFS = -DLOCALEDIR=\"$(localedir)\" -DAD_DIR='"$(AD_DIR)"' AUTH_SRCS = xscreensaver-auth.c dialog.c passwd.c setuid.c AUTH_OBJS = xscreensaver-auth.o $(AUTH_OBJS_1) @@ -181,6 +199,7 @@ GTK_SRCS = demo-Gtk.c demo-Gtk-conf.c 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 \ + $(WAYLAND_IDLE_OBJS) \ $(UTILS_BIN)/xmu.o \ $(UTILS_BIN)/resources.o \ $(UTILS_BIN)/visual.o \ @@ -222,7 +241,8 @@ DESKS = xscreensaver.desktop xscreensaver.service HDRS = XScreenSaver_ad.h XScreenSaver_Xm_ad.h \ xscreensaver.h prefs.h remote.h exec.h \ demo-Gtk-conf.h auth.h types.h blurb.h atoms.h clientmsg.h \ - screens.h xinput.h fade.h + screens.h xinput.h fade.h wayland-idle.h \ + $(WAYLAND_GEN_HDRS) MENA = xscreensaver.man xscreensaver-settings.man \ xscreensaver-command.man MENB = xscreensaver-gfx.man xscreensaver-auth.man \ @@ -231,12 +251,14 @@ MENB = xscreensaver-gfx.man xscreensaver-auth.man \ EXTRAS = README Makefile.in \ XScreenSaver.ad.in XScreenSaver-Xm.ad xscreensaver.pam.in \ demo.ui prefs.ui gresource.xml xscreensaver.desktop.in \ - xscreensaver-settings.desktop.in xscreensaver.service.in + xscreensaver-settings.desktop.in xscreensaver.service.in \ + $(WAYLAND_GEN_XML) TARFILES = $(DAEMON_SRCS) $(GFX_SRCS) $(AUTH_SRCS) $(SYSTEMD_SRCS) \ $(CMD_SRCS) $(GTK_SRCS) $(MOTIF_SRCS) $(PWENT_SRCS) \ $(KERBEROS_SRCS) $(PAM_SRCS) \ - $(HDRS) $(MENA) $(MENB) $(TEST_SRCS) $(EXTRAS) + $(WAYLAND_GEN_SRCS) \ + $(HDRS) $(MENA) $(MENB) $(TEST_SRCS) $(EXTRAS) # Using $(MAKE) directly means the shell executes things even with "make -n" MAKE2 = $(MAKE) @@ -654,6 +676,32 @@ uninstall-xml: rm -f $(install_prefix)$(HACK_CONF_DIR)/README +# Generate .c and .h files for various Wayland APIs. +# +xdg-shell.xml: $(WAYLAND_DATADIR)/wayland-protocols/stable/xdg-shell/xdg-shell.xml + cp -p $< $@ +xdg-shell-v1-client-protocol.h: xdg-shell.xml + wayland-scanner client-header < $< > $@ +xdg-shell-v1-protocol.c: xdg-shell.xml + wayland-scanner private-code < $< > $@ + +ext-idle-notify-v1.xml: \ + $(WAYLAND_DATADIR)/wayland-protocols/staging/ext-idle-notify/ext-idle-notify-v1.xml + cp -p $< $@ +ext-idle-notify-v1-client-protocol.h: ext-idle-notify-v1.xml + wayland-scanner client-header < $< > $@ +ext-idle-notify-v1-protocol.c: ext-idle-notify-v1.xml + wayland-scanner private-code < $< > $@ + +# org_kde_kwin_idle -- I don't know why this isn't in WAYLAND_DATADIR +#idle.xml: $(WAYLAND_DATADIR)/wayland-protocols/stable/ext-idle-notify/idle.xml +# cp -p $< $@ +idle-client-protocol.h: idle.xml + wayland-scanner client-header < $< > $@ +idle-protocol.c: idle.xml + wayland-scanner private-code < $< > $@ + + ############################################################################## # # Clean and dependencies @@ -677,7 +725,9 @@ depend: XScreenSaver_ad.h XScreenSaver_Xm_ad.h $(DEPEND) -s '# DO NOT DELETE: updated by make depend' \ $(DEPEND_FLAGS) -- \ $(INCLUDES_1) $(DEFS) $(DEPEND_DEFINES) $(CFLAGS) $(X_CFLAGS) -- \ - $(SAVER_SRCS) $(CMD_SRCS) + $(DAEMON_SRCS) $(GFX_SRCS) $(AUTH_SRCS) $(SYSTEMD_SRCS) $(CMD_SRCS) \ + $(GTK_SRCS) $(MOTIF_SRCS) $(PWENT_SRCS) $(KERBEROS_SRCS) \ + $(PAM_SRCS) $(WAYLAND_GEN_SRCS) $(TEST_SRCS) # Adds some dependencies to Makefile.in -- not totally accurate, but pretty # close. This excludes dependencies on files in /usr/include, etc. It tries @@ -688,10 +738,10 @@ distdepend: check_men update_ad_version XScreenSaver_ad.h XScreenSaver_Xm_ad.h $(DEPEND) -w 0 -f - \ -s '# DO NOT DELETE: updated by make distdepend' $(DEPEND_FLAGS) -- \ $(INCLUDES_1) $(DEFS) $(DEPEND_DEFINES) $(CFLAGS) $(X_CFLAGS) -- \ - $(SAVER_SRCS_1) $(SYSTEMD_SRCS) $(MOTIF_SRCS) $(GTK_SRCS) \ - $(PWENT_SRCS) $(KERBEROS_SRCS) $(PAM_SRCS) \ - $(LOCK_SRCS_1) $(DEMO_SRCS_1) $(CMD_SRCS) \ - $(TEST_SRCS) 2>/dev/null | \ + $(DAEMON_SRCS) $(GFX_SRCS) $(AUTH_SRCS) $(SYSTEMD_SRCS) $(CMD_SRCS) \ + $(GTK_SRCS) $(MOTIF_SRCS) $(PWENT_SRCS) $(KERBEROS_SRCS) \ + $(PAM_SRCS) $(WAYLAND_GEN_SRCS) $(TEST_SRCS) \ + 2>/dev/null | \ sort -d | \ ( \ awk '/^# .*Makefile.in ---/,/^# DO .*distdepend/' < Makefile.in ; \ @@ -781,6 +831,7 @@ $(UTILS_BIN)/xftwrap.o: $(UTILS_SRC)/xftwrap.c $(UTILS_BIN)/font-retry.o: $(UTILS_SRC)/font-retry.c $(UTILS_BIN)/xshm.o: $(UTILS_SRC)/xshm.c $(UTILS_BIN)/aligned_malloc.o: $(UTILS_SRC)/aligned_malloc.c +$(UTILS_BIN)/screenshot.o: $(UTILS_SRC)/screenshot.c UTIL_OBJS = $(UTILS_BIN)/overlay.o \ @@ -800,7 +851,8 @@ UTIL_OBJS = $(UTILS_BIN)/overlay.o \ $(UTILS_BIN)/utf8wc.o \ $(UTILS_BIN)/font-retry.o \ $(UTILS_BIN)/xshm.o \ - $(UTILS_BIN)/aligned_malloc.o + $(UTILS_BIN)/aligned_malloc.o \ + $(UTILS_BIN)/screenshot.o $(UTIL_OBJS): $(MAKE2CC) -C $(UTILS_BIN) $(@F) @@ -826,6 +878,8 @@ CC_ALL=$(INCLUDES) $(DEFS) $(CPPFLAGS) $(CFLAGS) $(X_CFLAGS) xscreensaver.o: xscreensaver.c $(CC) -c $(CC_ALL) $(DAEMON_DEFS) $< +xscreensaver.o: $(WAYLAND_GEN) + xscreensaver-auth.o: XScreenSaver_ad.h xscreensaver-auth.o: xscreensaver-auth.c $(CC) -c $(CC_ALL) $(AUTH_DEFS) $< @@ -872,7 +926,7 @@ 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) + $(CC) $(LDFLAGS) -o $@ $(GTK_OBJS) $(GTK_LIBS) $(WAYLAND_LIBS) demo-Xm.o: XScreenSaver_ad.h @@ -973,7 +1027,8 @@ test-grab: test-grab.o blurb.o TEST_FADE_OBJS = test-fade.o fade.o blurb.o atoms.o clientmsg.o xinput.o \ $(UTILS_BIN)/visual.o $(UTILS_BIN)/resources.o $(UTILS_BIN)/usleep.o \ $(UTILS_BIN)/logo.o $(UTILS_BIN)/minixpm.o $(UTILS_BIN)/xshm.o \ - $(UTILS_BIN)/xmu.o $(UTILS_BIN)/aligned_malloc.o + $(UTILS_BIN)/xmu.o $(UTILS_BIN)/aligned_malloc.o \ + $(UTILS_BIN)/screenshot.o test-fade: $(TEST_FADE_OBJS) $(CC) $(LDFLAGS) -o $@ $(TEST_FADE_OBJS) $(GFX_LIBS) @@ -996,6 +1051,16 @@ xdpyinfo: xdpyinfo.o # # DO NOT DELETE: updated by make distdepend +atoms.o: $(srcdir)/atoms.h +atoms.o: ../config.h +atomswm.o: $(srcdir)/atoms.h +atomswm.o: ../config.h +blurb.o: $(srcdir)/blurb.h +blurb.o: ../config.h +clientmsg.o: $(srcdir)/atoms.h +clientmsg.o: $(srcdir)/blurb.h +clientmsg.o: $(srcdir)/clientmsg.h +clientmsg.o: ../config.h demo-Gtk-conf.o: $(srcdir)/blurb.h demo-Gtk-conf.o: ../config.h demo-Gtk-conf.o: $(srcdir)/demo-Gtk-conf.h @@ -1017,20 +1082,84 @@ demo-Gtk.o: $(UTILS_SRC)/xmu.h demo-Gtk.o: $(UTILS_SRC)/xscreensaver-intl.h demo-Xm.o: ../config.h demo-Xm-widgets.o: ../config.h +dialog.o: $(srcdir)/atoms.h +dialog.o: $(srcdir)/auth.h +dialog.o: $(srcdir)/blurb.h +dialog.o: ../config.h +dialog.o: $(srcdir)/prefs.h +dialog.o: $(srcdir)/screens.h +dialog.o: $(UTILS_SRC)/font-retry.h +dialog.o: $(UTILS_SRC)/resources.h +dialog.o: $(UTILS_SRC)/usleep.h +dialog.o: $(UTILS_SRC)/utf8wc.h +dialog.o: $(UTILS_SRC)/version.h +dialog.o: $(UTILS_SRC)/visual.h +dialog.o: $(UTILS_SRC)/xft.h +dialog.o: $(UTILS_SRC)/xftwrap.h +dialog.o: $(srcdir)/xinput.h +dpms.o: $(srcdir)/blurb.h +dpms.o: ../config.h +dpms.o: $(srcdir)/types.h +dpms.o: $(srcdir)/xscreensaver.h +exec.o: ../config.h +exec.o: $(srcdir)/exec.h +exts.o: $(srcdir)/blurb.h +exts.o: ../config.h +exts.o: $(srcdir)/types.h +exts.o: $(srcdir)/xscreensaver.h +fade.o: $(srcdir)/atoms.h +fade.o: $(srcdir)/blurb.h +fade.o: $(srcdir)/clientmsg.h +fade.o: ../config.h +fade.o: $(srcdir)/fade.h +fade.o: $(UTILS_SRC)/usleep.h +fade.o: $(UTILS_SRC)/visual.h +fade.o: $(UTILS_SRC)/xmu.h +fade.o: $(UTILS_SRC)/xshm.h +fade.o: $(srcdir)/xinput.h passwd-kerberos.o: $(srcdir)/auth.h passwd-kerberos.o: $(srcdir)/blurb.h passwd-kerberos.o: ../config.h +passwd.o: $(srcdir)/auth.h +passwd.o: $(srcdir)/blurb.h +passwd.o: ../config.h +passwd.o: $(srcdir)/types.h +passwd.o: $(srcdir)/xscreensaver.h passwd-pam.o: $(srcdir)/auth.h passwd-pam.o: $(srcdir)/blurb.h passwd-pam.o: ../config.h passwd-pwent.o: $(srcdir)/auth.h passwd-pwent.o: $(srcdir)/blurb.h passwd-pwent.o: ../config.h +prefs.o: $(srcdir)/blurb.h +prefs.o: ../config.h +prefs.o: $(srcdir)/prefs.h +prefsw.o: $(srcdir)/blurb.h +prefsw.o: ../config.h +prefsw.o: $(srcdir)/prefs.h +prefsw.o: $(srcdir)/types.h +prefsw.o: $(UTILS_SRC)/resources.h +prefsw.o: $(UTILS_SRC)/version.h remote.o: $(srcdir)/atoms.h remote.o: $(srcdir)/blurb.h remote.o: $(srcdir)/clientmsg.h remote.o: ../config.h remote.o: $(srcdir)/remote.h +screens.o: $(srcdir)/blurb.h +screens.o: ../config.h +screens.o: $(srcdir)/screens.h +setuid.o: $(srcdir)/auth.h +setuid.o: $(srcdir)/blurb.h +setuid.o: ../config.h +subprocs.o: $(srcdir)/atoms.h +subprocs.o: $(srcdir)/blurb.h +subprocs.o: ../config.h +subprocs.o: $(srcdir)/exec.h +subprocs.o: $(srcdir)/types.h +subprocs.o: $(UTILS_SRC)/screenshot.h +subprocs.o: $(UTILS_SRC)/visual.h +subprocs.o: $(UTILS_SRC)/yarandom.h +subprocs.o: $(srcdir)/xscreensaver.h test-fade.o: $(srcdir)/atoms.h test-fade.o: $(srcdir)/blurb.h test-fade.o: ../config.h @@ -1038,6 +1167,7 @@ test-fade.o: $(srcdir)/fade.h test-fade.o: $(srcdir)/screens.h test-fade.o: $(srcdir)/types.h test-fade.o: $(UTILS_SRC)/resources.h +test-fade.o: $(UTILS_SRC)/screenshot.h test-fade.o: $(srcdir)/xscreensaver.h test-grab.o: $(srcdir)/blurb.h test-grab.o: ../config.h @@ -1064,11 +1194,64 @@ test-xkb.o: ../config.h test-yarandom.o: $(srcdir)/blurb.h test-yarandom.o: ../config.h test-yarandom.o: $(UTILS_SRC)/yarandom.h +wayland-idle.o: $(srcdir)/blurb.h +wayland-idle.o: ../config.h +wayland-idle.o: $(srcdir)/ext-idle-notify-v1-client-protocol.h +wayland-idle.o: $(srcdir)/idle-client-protocol.h +wayland-idle.o: $(srcdir)/wayland-idle.h +windows.o: $(srcdir)/atoms.h +windows.o: $(srcdir)/blurb.h +windows.o: ../config.h +windows.o: $(srcdir)/exec.h +windows.o: $(srcdir)/fade.h +windows.o: $(srcdir)/screens.h +windows.o: $(srcdir)/types.h +windows.o: $(UTILS_SRC)/font-retry.h +windows.o: $(UTILS_SRC)/resources.h +windows.o: $(UTILS_SRC)/screenshot.h +windows.o: $(UTILS_SRC)/visual.h +windows.o: $(UTILS_SRC)/xft.h +windows.o: $(srcdir)/xscreensaver.h +xinput.o: $(srcdir)/blurb.h +xinput.o: ../config.h +xinput.o: $(srcdir)/xinput.h +xscreensaver-auth.o: XScreenSaver_ad.h +xscreensaver-auth.o: $(srcdir)/atoms.h +xscreensaver-auth.o: $(srcdir)/auth.h +xscreensaver-auth.o: $(srcdir)/blurb.h +xscreensaver-auth.o: ../config.h +xscreensaver-auth.o: $(srcdir)/types.h +xscreensaver-auth.o: $(UTILS_SRC)/resources.h +xscreensaver-auth.o: $(UTILS_SRC)/version.h +xscreensaver-auth.o: $(UTILS_SRC)/visual.h +xscreensaver-auth.o: $(UTILS_SRC)/yarandom.h +xscreensaver-auth.o: $(srcdir)/xscreensaver.h xscreensaver-command.o: $(srcdir)/atoms.h xscreensaver-command.o: $(srcdir)/blurb.h xscreensaver-command.o: ../config.h xscreensaver-command.o: $(srcdir)/remote.h xscreensaver-command.o: $(UTILS_SRC)/version.h +xscreensaver-gfx.o: XScreenSaver_ad.h +xscreensaver-gfx.o: $(srcdir)/atoms.h +xscreensaver-gfx.o: $(srcdir)/blurb.h +xscreensaver-gfx.o: $(srcdir)/clientmsg.h +xscreensaver-gfx.o: ../config.h +xscreensaver-gfx.o: $(srcdir)/screens.h +xscreensaver-gfx.o: $(srcdir)/types.h +xscreensaver-gfx.o: $(UTILS_SRC)/resources.h +xscreensaver-gfx.o: $(UTILS_SRC)/version.h +xscreensaver-gfx.o: $(UTILS_SRC)/visual.h +xscreensaver-gfx.o: $(UTILS_SRC)/xmu.h +xscreensaver-gfx.o: $(UTILS_SRC)/yarandom.h +xscreensaver-gfx.o: $(srcdir)/xscreensaver.h +xscreensaver.o: $(srcdir)/atoms.h +xscreensaver.o: $(srcdir)/blurb.h +xscreensaver.o: $(srcdir)/clientmsg.h +xscreensaver.o: ../config.h +xscreensaver.o: $(srcdir)/prefs.h +xscreensaver.o: $(UTILS_SRC)/version.h +xscreensaver.o: $(UTILS_SRC)/xmu.h +xscreensaver.o: $(srcdir)/xinput.h xscreensaver-systemd.o: $(srcdir)/blurb.h xscreensaver-systemd.o: ../config.h xscreensaver-systemd.o: $(UTILS_SRC)/queue.h diff --git a/driver/XScreenSaver.ad.in b/driver/XScreenSaver.ad.in index eca70448..08b9699e 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.10 -! 27-Apr-2025 +! version 6.11 +! 01-Jul-2025 ! ! See "man xscreensaver" for more info. The latest version is always ! available at https://www.jwz.org/xscreensaver/ diff --git a/driver/atoms.c b/driver/atoms.c index fc347074..828f266c 100644 --- a/driver/atoms.c +++ b/driver/atoms.c @@ -1,4 +1,4 @@ -/* xscreensaver-command, Copyright © 1991-2023 Jamie Zawinski +/* xscreensaver-command, Copyright © 1991-2025 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 @@ -25,7 +25,7 @@ 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_DEACTIVATE, XA_CYCLE, XA_RESTART, XA_AUTH, 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, @@ -55,6 +55,7 @@ init_xscreensaver_atoms (Display *dpy) XA_DEMO = A("DEMO"); XA_LOCK = A("LOCK"); XA_BLANK = A("BLANK"); + XA_AUTH = A("AUTH"); XA_NET_WM_PID = A("_NET_WM_PID"); XA_NET_WM_STATE = A("_NET_WM_STATE"); diff --git a/driver/atoms.h b/driver/atoms.h index 3f65d81f..34e4c8cd 100644 --- a/driver/atoms.h +++ b/driver/atoms.h @@ -1,4 +1,4 @@ -/* xscreensaver-command, Copyright © 1991-2023 Jamie Zawinski +/* xscreensaver-command, Copyright © 1991-2025 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 @@ -15,7 +15,7 @@ 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_DEACTIVATE, XA_CYCLE, XA_RESTART, XA_AUTH, 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, @@ -35,5 +35,4 @@ extern void xscreensaver_set_wm_atoms (Display *, Window, */ typedef long PROP32; - #endif /* _XSCREENSAVER_ATOMS_H_ */ diff --git a/driver/demo-Gtk.c b/driver/demo-Gtk.c index 49503696..6ff4ecd6 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-2024 Jamie Zawinski + * xscreensaver, Copyright © 1993-2025 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 @@ -16,6 +16,7 @@ #ifdef HAVE_GTK /* whole file */ +#define _GNU_SOURCE #ifdef ENABLE_NLS # include #endif /* ENABLE_NLS */ @@ -55,6 +56,12 @@ #include #include /* For gdk_x11_get_default_xdisplay(), etc. */ +#ifdef GDK_WINDOWING_WAYLAND +# include +#else +# define GDK_IS_WAYLAND_DISPLAY(dpy) False +#endif + #if (__GNUC__ >= 4) # pragma GCC diagnostic pop #endif @@ -73,6 +80,10 @@ #include "screenshot.h" #include "xmu.h" +#ifdef HAVE_WAYLAND +# include "wayland-idle.h" +#endif + #include "demo-Gtk-conf.h" @@ -110,7 +121,7 @@ typedef struct { GtkWindow *dialog; Display *dpy; - Bool wayland_p; + enum { X11_BACKEND, WAYLAND_BACKEND, XWAYLAND_BACKEND } backend; Pixmap screenshot; Visual *gl_visual; @@ -121,6 +132,7 @@ typedef struct { Bool flushing_p; /* flag for breaking recursion loops */ Bool saving_p; /* flag for breaking recursion loops */ Bool dpms_supported_p; /* Whether XDPMS is available */ + Bool grabbing_supported_p; /* Whether "Grab Desktop" and "Fade" work */ char *desired_preview_cmd; /* subprocess we intend to run */ char *running_preview_cmd; /* subprocess we are currently running */ @@ -974,7 +986,7 @@ await_xscreensaver (state *s) Bool root_p = (geteuid () == 0); strcpy (buf, - _("The xscreensaver daemon did not start up properly.\n" + _("The XScreenSaver daemon did not start up properly.\n" "\n")); if (root_p) @@ -2202,7 +2214,7 @@ file_chooser (GtkWindow *parent, GtkEntry *entry, char **retP, } } else if (verbose_p) - fprintf (stderr, "%s: chooser: cancelled\n", blurb()); + fprintf (stderr, "%s: chooser: canceled\n", blurb()); gtk_widget_destroy (dialog); return changed_p; @@ -2361,9 +2373,11 @@ server_current_hack (state *s) int hack_number = -1; if (!s->dpy) return hack_number; + + /* XA_SCREENSAVER_STATUS format documented in windows.c. */ if (XGetWindowProperty (s->dpy, RootWindow(s->dpy, 0), /* always screen #0 */ XA_SCREENSAVER_STATUS, - 0, 3, FALSE, XA_INTEGER, + 0, 999, FALSE, XA_INTEGER, &type, &format, &nitems, &bytesafter, &dataP) == Success @@ -2372,7 +2386,7 @@ server_current_hack (state *s) && dataP) { PROP32 *data = (PROP32 *) dataP; - hack_number = (int) data[2] - 1; + hack_number = (int) data[3] - 1; /* Hack running on the first screen */ } if (dataP) XFree (dataP); @@ -2555,6 +2569,10 @@ populate_prefs_page (state *s) can_lock_p = FALSE; # endif + if (s->backend == WAYLAND_BACKEND || + s->backend == XWAYLAND_BACKEND) + can_lock_p = FALSE; + /* If there is only one screen, the mode menu contains "random" but not "random-same". */ @@ -2596,11 +2614,12 @@ populate_prefs_page (state *s) 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_desk_button, (p->grab_desktop_p && + s->grabbing_supported_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); + TOGGLE_ACTIVE (fade_button, p->fade_p && s->grabbing_supported_p); + TOGGLE_ACTIVE (unfade_button, p->unfade_p && s->grabbing_supported_p); switch (p->tmode) { @@ -2737,8 +2756,14 @@ populate_prefs_page (state *s) 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)); + /* Fading + */ + SENSITIZE (fade_button, s->grabbing_supported_p); + SENSITIZE (unfade_button, s->grabbing_supported_p); + SENSITIZE (fade_label, ((p->fade_p || p->unfade_p) && + s->grabbing_supported_p)); + SENSITIZE (fade_spinbutton, ((p->fade_p || p->unfade_p) && + s->grabbing_supported_p)); # undef SENSITIZE @@ -3211,11 +3236,11 @@ clear_preview_window (state *s) 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 */ + (!s->running_preview_error_p ? 0 : /* ok */ + nothing_p ? 3 : /* no hacks installed */ + !available_p ? 2 : /* hack not installed */ + s->backend == WAYLAND_BACKEND ? 4 : /* no previews without X11 */ + 1)); /* preview failed */ } @@ -3227,7 +3252,9 @@ preview_resize_cb (GtkWidget *self, GdkEvent *event, gpointer data) /* 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) + if (s->dpy && + s->backend != WAYLAND_BACKEND && + s->running_preview_cmd) { GdkWindow *window = gtk_widget_get_window (self); Window id; @@ -3260,7 +3287,9 @@ reset_preview_window (state *s) */ XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); GtkWidget *pr = win->preview; - if (s->dpy && !s->wayland_p && gtk_widget_get_realized (pr)) + if (s->dpy && + s->backend != WAYLAND_BACKEND && + gtk_widget_get_realized (pr)) { GdkWindow *window = gtk_widget_get_window (pr); Window oid = (window ? gdk_x11_window_get_xid (window) : 0); @@ -3585,7 +3614,7 @@ launch_preview_subproc (state *s) new_cmd = malloc (strlen (cmd) + 40); - id = (window && !s->wayland_p + id = (window && s->backend != WAYLAND_BACKEND ? gdk_x11_window_get_xid (window) : 0); if (id == 0) @@ -3893,9 +3922,11 @@ screen_blanked_p (state *s) Bool blanked_p = FALSE; if (!s->dpy) return FALSE; + + /* XA_SCREENSAVER_STATUS format documented in windows.c. */ if (XGetWindowProperty (s->dpy, RootWindow (s->dpy, 0), /* always screen 0 */ XA_SCREENSAVER_STATUS, - 0, 3, FALSE, XA_INTEGER, + 0, 999, FALSE, XA_INTEGER, &type, &format, &nitems, &bytesafter, &dataP) == Success @@ -3904,7 +3935,7 @@ screen_blanked_p (state *s) && dataP) { Atom *data = (Atom *) dataP; - blanked_p = (data[0] == XA_BLANK || data[0] == XA_LOCK); + blanked_p = (data[0] != 0); } if (dataP) XFree (dataP); @@ -4159,7 +4190,7 @@ the_network_is_not_the_computer (gpointer data) */ sprintf(msg, _("%s is running as user \"%s\" on host \"%s\".\n" - "But the xscreensaver managing display \"%.25s\"\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" @@ -4169,7 +4200,7 @@ the_network_is_not_the_computer (gpointer data) "You should either re-run %s as \"%s\", or re-run\n" "xscreensaver as \"%s\".\n" "\n" - "Restart the xscreensaver daemon now?\n"), + "Restart the XScreenSaver daemon now?\n"), progname, luser, lhost, d, (ruser ? ruser : "???"), (rhost ? rhost : "???"), @@ -4183,7 +4214,7 @@ the_network_is_not_the_computer (gpointer data) */ sprintf (msg, _("%s is running as user \"%s\" on host \"%s\".\n" - "But the xscreensaver managing display \"%s\"\n" + "But the XScreenSaver managing display \"%s\"\n" "is running as user \"%s\" on host \"%s\".\n" "\n" "If those two machines don't share a file system (that is,\n" @@ -4204,10 +4235,10 @@ the_network_is_not_the_computer (gpointer data) */ sprintf (msg, _("This is %s version %s.\n" - "But the xscreensaver managing display \"%s\"\n" + "But the XScreenSaver managing display \"%s\"\n" "is version %s. This could cause problems.\n" "\n" - "Restart the xscreensaver daemon now?\n"), + "Restart the XScreenSaver daemon now?\n"), progname, s->short_version, d, rversion); @@ -4253,16 +4284,52 @@ the_network_is_not_the_computer (gpointer data) 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")); + if (s->backend != X11_BACKEND) + { +# ifdef HAVE_WAYLAND + /* Connect to the Wayland server in the same way that the XScreenSaver + daemon will, and if we are in a state where the daemon won't work + properly, pop up a dialog box explaining why. + */ + const char *wayland_err = 0; + wayland_state *wayland = wayland_idle_init (NULL, NULL, &wayland_err); + + if (s->debug_p && wayland_err) + fprintf (stderr, "%s: wayland: %s\n", blurb(), wayland_err); + + if (wayland) /* Connected to Wayland, have necessary extensions. */ + { + wayland_idle_free (wayland); + } + else if (wayland_err && !strcmp (wayland_err, "connection failed")) + { + if (getenv ("WAYLAND_DISPLAY") || getenv ("WAYLAND_SOCKET")) + { + warning_dialog (s->window, _("Warning"), + _("Unable to connect to the Wayland server. " + "The XScreenSaver daemon will not work.\n")); + } + /* Otherwise, presumably running under real X11. */ + } + else if (wayland_err) + { + /* Connected but the necessary extensions are missing. */ + char msg [1024]; + sprintf (msg, + _("Wayland error:\n\n" + "%.100s\n\n" + "The XScreenSaver daemon will not work.\n"), + wayland_err); + warning_dialog (s->window, _("Warning"), msg); + } +# else /* !HAVE_WAYLAND */ + if (s->debug_p) + fprintf (stderr, "%s: wayland: disabled at compile time\n", blurb()); + warning_dialog (s->window, _("Warning"), + _("Not compiled with support for Wayland. " + "The XScreenSaver daemon will not work.\n")); +# endif /* !HAVE_WAYLAND */ + } return FALSE; /* Only run timer once */ } @@ -4834,7 +4901,7 @@ save_window_position (state *s, GtkWindow *win, int x, int y, Bool dialog_p) char *old = p->settings_geom; char str[100]; - if (!s->dpy || s->wayland_p) return; + if (!s->dpy || s->backend == WAYLAND_BACKEND) return; wm_decoration_origin (win, &x, &y); if (!old || !*old || @@ -4974,6 +5041,7 @@ const gchar *accels[][2] = { static void xscreensaver_window_realize (GtkWidget *self, gpointer user_data) { + GdkDisplay *gdpy = gdk_display_get_default(); XScreenSaverWindow *win = XSCREENSAVER_WINDOW (self); state *s = &win->state; saver_preferences *p = &s->prefs; @@ -4982,48 +5050,31 @@ xscreensaver_window_realize (GtkWidget *self, gpointer user_data) 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))) - { - s->dpy = NULL; - s->wayland_p = TRUE; - } + if (GDK_IS_WAYLAND_DISPLAY (gdpy)) + s->backend = WAYLAND_BACKEND; /* Should not happen */ + else if (GDK_IS_X11_DISPLAY (gdpy) && + (getenv ("WAYLAND_DISPLAY") || + getenv ("WAYLAND_SOCKET"))) + s->backend = XWAYLAND_BACKEND; + else + s->backend = X11_BACKEND; - 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; - } - } + if (s->debug_p) + fprintf (stderr, "%s: GDK backend is %s\n", blurb(), + (s->backend == X11_BACKEND ? "X11" : + s->backend == XWAYLAND_BACKEND ? "XWayland" : + s->backend == WAYLAND_BACKEND ? "Wayland" : "???")); - /* If we don't have a display connection, then we are surely under Wayland - even if the environment variable is not set. - */ - if (!s->dpy && - !getenv ("WAYLAND_DISPLAY") && - !getenv ("WAYLAND_SOCKET")) - putenv ("WAYLAND_DISPLAY=probably"); + if (s->backend != WAYLAND_BACKEND) + s->dpy = gdk_x11_get_default_xdisplay(); - if (getenv ("WAYLAND_DISPLAY") || - getenv ("WAYLAND_SOCKET")) - s->wayland_p = TRUE; - /* 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. + /* If GDK is using native Wayland, try to open an X11 connection to + XWayland anyway, so that get_string_resource and load_init_file work. + This should never happen, since we should always be either X11_BACKEND + or XWAYLAND_BACKEND, never WAYLAND_BACKEND. */ - if (! s->dpy) + if (s->backend == WAYLAND_BACKEND) { s->dpy = XOpenDisplay (NULL); if (s->debug_p) @@ -5104,10 +5155,43 @@ xscreensaver_window_realize (GtkWidget *self, gpointer user_data) fprintf (stderr, "%s: DPMS not supported at compile time\n", blurb()); # endif /* !HAVE_DPMS_EXTENSION */ -# if defined(__APPLE__) && !defined(__OPTIMIZE__) +# if defined(__APPLE__) && !defined(HAVE_COCOA) && !defined(__OPTIMIZE__) s->dpms_supported_p = TRUE; /* macOS X11: debugging kludge */ # endif + /* Under Wayland, we can only grab screenshots if "grim" is installed, + and even so, there's no way to grab screenshots under GNOME or KDE. + See comment in xscreensaver-getimage.c, and discussion thread here: + https://www.jwz.org/blog/2025/06/wayland-screenshots/#comment-260326 + */ + s->grabbing_supported_p = True; + if (getenv ("WAYLAND_DISPLAY") || getenv ("WAYLAND_SOCKET")) + { + const char *prog = "grim"; + char *desk = getenv ("XDG_CURRENT_DESKTOP"); +fprintf(stderr,"##A %s\n", desk); + if (desk && + (strcasestr (desk, "GNOME") || + strcasestr (desk, "KDE"))) + { +fprintf(stderr,"##B %s\n", desk); + s->grabbing_supported_p = False; + if (s->debug_p) + fprintf (stderr, + "%s: screenshots and fading not supported on Wayland %s\n", + blurb(), desk); + } + else if (! on_path_p (prog)) + { +fprintf(stderr,"##C %s\n", desk); + s->grabbing_supported_p = False; + if (s->debug_p) + fprintf (stderr, + "%s: screenshots and fading on Wayland require \"%s\"\n", + blurb(), prog); + } + } + if (s->dpy) init_xscreensaver_atoms (s->dpy); @@ -5126,7 +5210,7 @@ xscreensaver_window_realize (GtkWidget *self, gpointer user_data) populate_prefs_page (s); sensitize_demo_widgets (s, FALSE); scroll_to_current_hack (s); - if (s->dpy && !s->wayland_p) + if (s->dpy && s->backend != WAYLAND_BACKEND) fix_preview_visual (s); if (! s->multi_screen_p) hide_mode_menu_random_same (s); @@ -5167,11 +5251,16 @@ xscreensaver_window_realize (GtkWidget *self, gpointer user_data) # endif /* Grab the screenshot pixmap before mapping the window. */ - if (s->dpy && !s->wayland_p) + if (s->dpy && s->backend != WAYLAND_BACKEND) { 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); + XWindowAttributes xgwa; + /* Make sure the grab is the size of the root window. + It would be better if it was the screen, but close enough. */ + XGetWindowAttributes (s->dpy, xw, &xgwa); + if (s->grabbing_supported_p) + s->screenshot = screenshot_grab (s->dpy, xgwa.root, TRUE, s->debug_p); } /* Issue any warnings about the running xscreensaver daemon. @@ -5397,6 +5486,14 @@ xscreensaver_app_new (void) "Print diagnostics to stderr", NULL); g_signal_connect (app, "handle-local-options", G_CALLBACK (opts_cb), app); + + /* If we are under Wayland, tell GDK to use XWayland as the backend rather + than native Wayland. This is the only way for hack previews to work. + If this setting is respected, we should always end up with 'backend' set + to either X11_BACKEND or XWAYLAND_BACKEND, and never to WAYLAND_BACKEND. + */ + gdk_set_allowed_backends ("x11"); + return app; } diff --git a/driver/demo-Xm.c b/driver/demo-Xm.c index 2a95bff7..08a9ae93 100644 --- a/driver/demo-Xm.c +++ b/driver/demo-Xm.c @@ -1,5 +1,5 @@ /* demo-Xm.c --- implements the interactive demo-mode and options dialogs. - * xscreensaver, Copyright © 1993-2021 Jamie Zawinski + * xscreensaver, Copyright © 1993-2025 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 @@ -911,35 +911,39 @@ scroll_to_current_hack (Widget toplevel, prefs_pair *pair) Atom type; int format; unsigned long nitems, bytesafter; - unsigned char *data = 0; + unsigned char *dataP = 0; Display *dpy = XtDisplay (toplevel); - int which = 0; + int hack_number = -1; Widget list; + /* XA_SCREENSAVER_STATUS format documented in windows.c. */ if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */ XA_SCREENSAVER_STATUS, - 0, 3, False, XA_INTEGER, + 0, 999, False, XA_INTEGER, &type, &format, &nitems, &bytesafter, - &data) + &dataP) == Success && type == XA_INTEGER && nitems >= 3 - && data) - which = (int) data[2] - 1; + && dataP) + { + PROP32 *data = (PROP32 *) dataP; + hack_number = (int) data[3] - 1; /* Hack running on the first screen */ + } - if (data) free (data); + if (dataP) XFree (dataP); - if (which < 0) + if (hack_number < 0) return; list = name_to_widget (toplevel, "list"); apply_changes_and_save (toplevel); XmListDeselectAllItems (list); /* LessTif lossage */ - XmListSelectPos (list, which+1, True); + XmListSelectPos (list, hack_number+1, True); ensure_selected_item_visible (list); - populate_demo_window (toplevel, which, pair); + populate_demo_window (toplevel, hack_number, pair); } @@ -1173,7 +1177,7 @@ get_hack_blurb (Display *dpy, screenhack *hack) ; *s = 0; s = strrchr (prog_name, '/'); - if (s) strcpy (prog_name, s+1); + if (s) memmove (prog_name, s+1, strlen (s)); sprintf (doc_name, "hacks.%s.documentation", pretty_name); sprintf (doc_class, "hacks.%s.documentation", prog_name); diff --git a/driver/dialog.c b/driver/dialog.c index 5b5e868b..4a258056 100644 --- a/driver/dialog.c +++ b/driver/dialog.c @@ -1,5 +1,5 @@ /* dialog.c --- the password dialog and splash screen. - * xscreensaver, Copyright © 1993-2023 Jamie Zawinski + * xscreensaver, Copyright © 1993-2025 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 @@ -158,6 +158,8 @@ struct window_state { auth_state auth_state; int xi_opcode; int xkb_opcode; + XIM im; + XIC ic; /* Variant strings */ @@ -880,6 +882,40 @@ grab_keyboard_and_mouse (window_state *ws) } +/* In the olden days, the way to have multiple keyboard layouts was: + + A) Run "xmodmap" to load a different layout, one at a time. Fun! + + B) Have a "Mode_switch" key somewhere on your keyboard (traditionally on + the key labeled "AltGr"), and attached to Mod5. When "Mode_switch" was + toggled on, the KeyPress event's 'state' bitmask would have the Mod5 bit + set, meaning that the client should use the keycode's 3rd and 4th + columns in the map instead of the 1st and 2nd. In this way you could + have exactly two layouts. + + But in This Modern World, the XKeyboard extension does... other magic. + + C) Under LXDE circa 2021-2025, the Keyboard Preferences dialog lets you + select one alternate keyboard, and a key combo, typically Ctrl + Alt. + Through some mechanism I do not understand, it configures XKB so that + pressing Ctrl + Alt causes XLookupString to map the keycode to a + different keysym. It does this without the 'state' field having Mod5 + set. + + Bit 13 is set in 'state', but I don't know what that is or where it is + defined. If I had to guess, I'd say that XKB.h:XkbBuildCoreState() + uses bits 13 and 14 to indicate up to 4 alternate keyboard layouts, but + I have't found any documentation to this effect, nor do I know how + XLookupString knows to interpret those bits. Or why LXDE limits you to + just 1 alternate layout. + + When you press Ctrl + Alt, an XkbEvent comes in, which is our clue that + the keyboard layout *may* have changed. When that happens, we call + this function to present the name of the current layout on the window. + + Pressing Ctrl + Alt does not send a MappingNotify event, because that + would make too much sense. + */ static void get_keyboard_layout (window_state *ws) { @@ -980,6 +1016,24 @@ create_window (window_state *ws, int w, int h) XSetWindowColormap (ws->dpy, ws->window, ws->cmap); xscreensaver_set_wm_atoms (ws->dpy, ws->window, w, h, 0); + /* An input method is necessary for dead keys to work. + */ + if (! ws->im) + ws->im = XOpenIM (ws->dpy, NULL, NULL, NULL); + if (ws->ic) + XDestroyIC (ws->ic), ws->ic = 0; + if (ws->im) + ws->ic = XCreateIC (ws->im, + XNInputStyle, XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, ws->window, + NULL); + if (ws->ic) + XSetICFocus (ws->ic); + + if (verbose_p) + fprintf (stderr, "%s: %s input method\n", blurb(), + ws->ic ? "attached" : "failed to attach"); + if (ow) { XMapRaised (ws->dpy, ws->window); @@ -2027,18 +2081,30 @@ persistent_auth_status_failure (window_state *ws, if (increment_p && clear_p) abort(); - /* Read the old property so that we can increment it. */ + /* Read the old property so that we can increment it. + Structure: + + XScreenSaver 6.00, 2021: XScreenSaver >= 6.11, 2025: + + 0: count 0: count + 1: 32 bit time_t 1: 64 bit time_t hi + 2: 64 bit time_t lo + */ if (XGetWindowProperty (dpy, w, prop, 0, 999, False, XA_INTEGER, &type, &format, &nitems, &bytesafter, &dataP) == Success && type == XA_INTEGER - && nitems >= 2 + && nitems >= 3 && dataP) { - count = ((PROP32 *) dataP) [0]; - tt = ((PROP32 *) dataP) [1]; /* Y2038 bug: unsigned 32 bit time_t */ + PROP32 *data = (PROP32 *) dataP; + count = data[0]; + tt = (time_t) /* 64 bit time_t */ + ((((unsigned long) data[1] & 0xFFFFFFFFL) << 32) | + ((unsigned long) data[2] & 0xFFFFFFFFL)); + if (verbose_p) fprintf (stderr, "%s: previous auth failures: %d @ %lu\n", blurb(), count, (unsigned long) tt); @@ -2055,7 +2121,7 @@ persistent_auth_status_failure (window_state *ws, } else if (increment_p) { - PROP32 vv[2]; + PROP32 vv[3]; count++; /* Remember the time of the *oldest* failed login. A failed login @@ -2065,9 +2131,11 @@ persistent_auth_status_failure (window_state *ws, if (tt <= 0) tt = time ((time_t *) 0); vv[0] = (PROP32) count; - vv[1] = (PROP32) tt; + vv[1] = (PROP32) (((unsigned long) tt) >> 32); + vv[2] = (PROP32) (((unsigned long) tt) & 0xFFFFFFFFL); + XChangeProperty (dpy, w, prop, XA_INTEGER, 32, - PropModeReplace, (unsigned char *) vv, 2); + PropModeReplace, (unsigned char *) vv, 3); if (verbose_p) fprintf (stderr, "%s: saved auth failure: %d @ %lu\n", blurb(), count, (unsigned long) tt); @@ -2081,7 +2149,7 @@ persistent_auth_status_failure (window_state *ws, static void bs_timer (XtPointer, XtIntervalId *); static void -handle_keypress (window_state *ws, XKeyEvent *event) +handle_keypress (window_state *ws, XKeyEvent *event, Bool filter_p) { unsigned char decoded [MAX_BYTES_PER_CHAR * 10]; /* leave some slack */ KeySym keysym = 0; @@ -2103,6 +2171,48 @@ handle_keypress (window_state *ws, XKeyEvent *event) int decoded_size = XLookupString (event, (char *)decoded, sizeof(decoded), &keysym, &ws->compose_status); + /* But if we have an input method, we can unambiguously get UTF8. + */ + if (ws->ic && event->type == KeyPress) + { + char decoded2[sizeof(decoded)]; + Status s = 0; + KeySym keysym2 = 0; + int size2 = Xutf8LookupString (ws->ic, (XKeyPressedEvent *) event, + decoded2, sizeof(decoded2)-1 + , &keysym2, &s); + decoded2[size2] = 0; + + switch (s) { + case XLookupChars: /* Set 'c2' to a UTF8 string */ + if (*decoded2) + { + strcpy ((char *) decoded, (char *) decoded2); + decoded_size = size2; + } + break; + case XLookupKeySym: /* Set 'keysym2' but not 'decoded2' */ + if (keysym2) + keysym = keysym2; + break; + case XLookupBoth: /* Set 'keysym2' and 'decoded2' */ + if (keysym2) + keysym = keysym2; + if (*decoded2) + { + strcpy ((char *) decoded, (char *) decoded2); + decoded_size = size2; + } + break; + case XLookupNone: /* No input yet */ + case XBufferOverflow: /* 'c2' was too small */ + break; + default: + abort(); + break; + } + } + if (decoded_size > MAX_BYTES_PER_CHAR) { /* The multi-byte character returned is too large. */ @@ -2184,7 +2294,10 @@ handle_keypress (window_state *ws, XKeyEvent *event) SELF_INSERT: nbytes = strlen (ws->plaintext_passwd); nchars = strlen (ws->plaintext_passwd_char_size); - if (nchars + 1 >= sizeof (ws->plaintext_passwd_char_size)-1 || + + if (filter_p) + ; /* Ignore: it is probably the start of a dead-key sequence. */ + else if (nchars + 1 >= sizeof (ws->plaintext_passwd_char_size)-1 || nbytes + decoded_size >= sizeof (ws->plaintext_passwd)-1) XBell (ws->dpy, 0); /* overflow */ else if (decoded_size == 0) @@ -2248,7 +2361,7 @@ handle_button (window_state *ws, XEvent *xev, line_button_state *bs) static Bool -handle_event (window_state *ws, XEvent *xev) +handle_event (window_state *ws, XEvent *xev, Bool filter_p) { Bool refresh_p = False; switch (xev->xany.type) { @@ -2257,7 +2370,7 @@ handle_event (window_state *ws, XEvent *xev) ws->auth_state = AUTH_CANCEL; else { - handle_keypress (ws, &xev->xkey); + handle_keypress (ws, &xev->xkey, filter_p); ws->caps_p = (xev->xkey.state & LockMask); if (ws->auth_state == AUTH_NOTIFY) ws->auth_state = AUTH_CANCEL; @@ -2393,6 +2506,7 @@ gui_main_loop (window_state *ws, Bool splash_p, Bool notification_p) { XEvent xev; XtInputMask m = XtAppPending (ws->app); + Bool filtered_p = False; if (m & XtIMXEvent) /* Process timers then block on an X event (which we know is there) */ @@ -2422,26 +2536,38 @@ gui_main_loop (window_state *ws, Bool splash_p, Bool notification_p) if ((m & ~XtIMXEvent) && !ws->splash_p) refresh_p = True; /* In auth mode, all timers refresh */ - if (verbose_p || debug_p) - print_xinput_event (ws->dpy, &xev, ""); - /* Convert XInput events to Xlib events, for simplicity and familiarity. */ - if (xev.xcookie.type == GenericEvent && - xev.xcookie.extension == ws->xi_opcode && - (xev.xcookie.data || XGetEventData (ws->dpy, &xev.xcookie))) - { - XEvent ev2; - Bool ok = - xinput_event_to_xlib (xev.xcookie.evtype, xev.xcookie.data, &ev2); - XFreeEventData (ws->dpy, &xev.xcookie); - if (test_mode_ignore_xi_events) - ok = False; - if (ok) + { + XEvent ev2; + Bool swap_p = False; + + if (xev.xcookie.type == GenericEvent && + xev.xcookie.extension == ws->xi_opcode && + (xev.xcookie.data || XGetEventData (ws->dpy, &xev.xcookie))) + { + swap_p = xinput_event_to_xlib (xev.xcookie.evtype, + xev.xcookie.data, &ev2); + if (test_mode_ignore_xi_events) + swap_p = False; + } + + /* XFilterEvent does not work on XInput events, so check the + newly-synthesized X11 event instead. */ + filtered_p = XFilterEvent ((swap_p ? &ev2 : &xev), ws->window); + + /* Log the original event, not the synthetic one. */ + if (verbose_p || debug_p) + print_xinput_event (ws->dpy, &xev, ws->ic, + (filtered_p ? " (filtered)" : "")); + if (swap_p) + { + XFreeEventData (ws->dpy, &xev.xcookie); xev = ev2; - } + } + } - if (handle_event (ws, &xev)) + if (handle_event (ws, &xev, filtered_p)) refresh_p = True; XtDispatchEvent (&xev); @@ -2473,6 +2599,7 @@ gui_main_loop (window_state *ws, Bool splash_p, Bool notification_p) keyboard layout would count as such. It does not. */ if (verbose_p) fprintf (stderr, "%s: MappingNotify\n", blurb()); + XRefreshKeyboardMapping (&xev.xmapping); get_keyboard_layout (ws); refresh_p = True; break; @@ -2492,6 +2619,7 @@ gui_main_loop (window_state *ws, Bool splash_p, Bool notification_p) XkbEvent *xkb = (XkbEvent *) &xev; if (verbose_p) fprintf (stderr, "%s: XKB event %d\n", blurb(), xkb->any.xkb_type); + /* Possibly the only event of interest here is XkbStateNotify = 2. */ get_keyboard_layout (ws); refresh_p = True; } @@ -2578,7 +2706,7 @@ window_state *global_ws = 0; to combine multiple messages onto a single dialog if PAM splits them between calls to this function. - Returns True on success. If the user timed out or cancelled, we just exit. + Returns True on success. If the user timed out or canceled, we just exit. */ Bool xscreensaver_auth_conv (void *closure, diff --git a/driver/dpms.c b/driver/dpms.c index fe5e2454..613ed06e 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-2022 Jamie Zawinski + * xscreensaver, Copyright © 2001-2025 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 @@ -15,6 +15,7 @@ #endif #include +#include #include #include @@ -23,7 +24,7 @@ /* 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 +static Bool disable_builtin_saver (Display *dpy) { int otimeout = -1; @@ -35,6 +36,7 @@ disable_builtin_saver (Display *dpy) { if (verbose_p > 1) fprintf (stderr, "%s: builtin saver already disabled\n", blurb()); + return False; } else { @@ -42,6 +44,7 @@ disable_builtin_saver (Display *dpy) fprintf (stderr, "%s: disabling server's builtin saver\n", blurb()); XSetScreenSaver (dpy, 0, 0, 0, 0); XForceScreenSaver (dpy, ScreenSaverReset); + return True; } } @@ -98,6 +101,8 @@ sync_server_dpms_settings (Display *dpy, struct saver_preferences *p) CARD16 o_power = 0; CARD16 o_standby = 0, o_suspend = 0, o_off = 0; Bool bogus_p = False; + Bool changed_p = False; + static int change_count = 0; Bool enabled_p = (p->dpms_enabled_p && p->mode != DONT_BLANK); Bool dpms_quickoff_p = p->dpms_quickoff_p; @@ -118,7 +123,7 @@ sync_server_dpms_settings (Display *dpy, struct saver_preferences *p) return; } - disable_builtin_saver (dpy); + changed_p = disable_builtin_saver (dpy); if (dpms_quickoff_p && !off_secs) { @@ -178,9 +183,13 @@ sync_server_dpms_settings (Display *dpy, struct saver_preferences *p) warned_p = True; return; } - else if (verbose_p) - fprintf (stderr, "%s: turned DPMS %s\n", blurb(), - enabled_p ? "on" : "off"); + else + { + if (verbose_p) + fprintf (stderr, "%s: turned DPMS %s\n", blurb(), + enabled_p ? "on" : "off"); + changed_p = True; + } } if (bogus_p) @@ -208,13 +217,27 @@ sync_server_dpms_settings (Display *dpy, struct saver_preferences *p) fprintf (stderr, "%s: unable to set DPMS timeouts\n", blurb()); return; } - else if (verbose_p) - fprintf (stderr, "%s: set DPMS timeouts: %d %d %d\n", blurb(), - standby_secs, suspend_secs, off_secs); + else + { + if (verbose_p) + fprintf (stderr, "%s: set DPMS timeouts: %d %d %d\n", blurb(), + standby_secs, suspend_secs, off_secs); + changed_p = True; + } } else if (verbose_p > 1) fprintf (stderr, "%s: DPMS timeouts already %d %d %d\n", blurb(), o_standby, o_suspend, o_off); + + if (changed_p) + change_count++; + + if (change_count > 3) + { + fprintf (stderr, "%s: WARNING: some other program keeps changing" + " the DPMS settings. That's bad.\n", blurb()); + change_count = 0; + } } Bool @@ -328,4 +351,75 @@ monitor_power_on (saver_info *si, Bool on_p) on_p ? "on" : "off"); } + +/* Force the server DPMS state to be what the wall clock says it should be. + */ +void +brute_force_dpms (Display *dpy, struct saver_preferences *p, + time_t activity_time) +{ + XErrorHandler old_handler; + int event_number, error_number; + BOOL onoff = False; + CARD16 state; + CARD16 target; + time_t age = time ((time_t *) 0) - activity_time; + + if (activity_time == 0) /* Auth dialog is visible */ + target = DPMSModeOn; + else if (p->mode == BLANK_ONLY && p->dpms_quickoff_p) + target = DPMSModeOff; + else if (p->dpms_off && age >= (p->dpms_off / 1000)) + target = DPMSModeOff; + else if (p->dpms_suspend && age >= (p->dpms_suspend / 1000)) + target = DPMSModeSuspend; + else if (p->dpms_standby && age >= (p->dpms_standby / 1000)) + target = DPMSModeStandby; + else + /* We have no opinion about the desired DPMS state, so if it is off, + leave it off. It may have been powered down manually with xset. */ + return; + + if (!DPMSQueryExtension (dpy, &event_number, &error_number) || + !DPMSCapable (dpy)) + return; + + DPMSInfo (dpy, &state, &onoff); + if (!onoff) + return; + + if (state == target) + return; + + XSync (dpy, False); + error_handler_hit_p = False; + old_handler = XSetErrorHandler (ignore_all_errors_ehandler); + XSync (dpy, False); + + DPMSForceLevel (dpy, target); + + XSync (dpy, False); + XSetErrorHandler (old_handler); + + if (p->verbose_p > 1 && error_handler_hit_p) + { + fprintf (stderr, "%s: DPMSForceLevel got an X11 error\n", blurb()); + return; + } + + DPMSInfo (dpy, &state, &onoff); + { + const char *s = (target == DPMSModeOff ? "Off" : + target == DPMSModeSuspend ? "Suspend" : + target == DPMSModeStandby ? "Standby" : + target == DPMSModeOn ? "On" : "???"); + if (state != target) + fprintf (stderr, + "%s: DPMSForceLevel(dpy, %s) did not change monitor power state\n", + blurb(), s); + else if (p->verbose_p) + fprintf (stderr, "%s: set monitor power to %s\n", blurb(), s); + } +} + #endif /* HAVE_DPMS_EXTENSION -- whole file */ diff --git a/driver/ext-idle-notify-v1-client-protocol.h b/driver/ext-idle-notify-v1-client-protocol.h new file mode 100644 index 00000000..429e7038 --- /dev/null +++ b/driver/ext-idle-notify-v1-client-protocol.h @@ -0,0 +1,323 @@ +/* Generated by wayland-scanner 1.23.1 */ + +#ifndef EXT_IDLE_NOTIFY_V1_CLIENT_PROTOCOL_H +#define EXT_IDLE_NOTIFY_V1_CLIENT_PROTOCOL_H + +#include +#include +#include "wayland-client.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @page page_ext_idle_notify_v1 The ext_idle_notify_v1 protocol + * @section page_ifaces_ext_idle_notify_v1 Interfaces + * - @subpage page_iface_ext_idle_notifier_v1 - idle notification manager + * - @subpage page_iface_ext_idle_notification_v1 - idle notification + * @section page_copyright_ext_idle_notify_v1 Copyright + *
+ *
+ * Copyright © 2015 Martin Gräßlin
+ * Copyright © 2022 Simon Ser
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * 
+ */ +struct ext_idle_notification_v1; +struct ext_idle_notifier_v1; +struct wl_seat; + +#ifndef EXT_IDLE_NOTIFIER_V1_INTERFACE +#define EXT_IDLE_NOTIFIER_V1_INTERFACE +/** + * @page page_iface_ext_idle_notifier_v1 ext_idle_notifier_v1 + * @section page_iface_ext_idle_notifier_v1_desc Description + * + * This interface allows clients to monitor user idle status. + * + * After binding to this global, clients can create ext_idle_notification_v1 + * objects to get notified when the user is idle for a given amount of time. + * @section page_iface_ext_idle_notifier_v1_api API + * See @ref iface_ext_idle_notifier_v1. + */ +/** + * @defgroup iface_ext_idle_notifier_v1 The ext_idle_notifier_v1 interface + * + * This interface allows clients to monitor user idle status. + * + * After binding to this global, clients can create ext_idle_notification_v1 + * objects to get notified when the user is idle for a given amount of time. + */ +extern const struct wl_interface ext_idle_notifier_v1_interface; +#endif +#ifndef EXT_IDLE_NOTIFICATION_V1_INTERFACE +#define EXT_IDLE_NOTIFICATION_V1_INTERFACE +/** + * @page page_iface_ext_idle_notification_v1 ext_idle_notification_v1 + * @section page_iface_ext_idle_notification_v1_desc Description + * + * This interface is used by the compositor to send idle notification events + * to clients. + * + * Initially the notification object is not idle. The notification object + * becomes idle when no user activity has happened for at least the timeout + * duration, starting from the creation of the notification object. User + * activity may include input events or a presence sensor, but is + * compositor-specific. + * + * How this notification responds to idle inhibitors depends on how + * it was constructed. If constructed from the + * get_idle_notification request, then if an idle inhibitor is + * active (e.g. another client has created a zwp_idle_inhibitor_v1 + * on a visible surface), the compositor must not make the + * notification object idle. However, if constructed from the + * get_input_idle_notification request, then idle inhibitors are + * ignored, and only input from the user, e.g. from a keyboard or + * mouse, counts as activity. + * + * When the notification object becomes idle, an idled event is sent. When + * user activity starts again, the notification object stops being idle, + * a resumed event is sent and the timeout is restarted. + * @section page_iface_ext_idle_notification_v1_api API + * See @ref iface_ext_idle_notification_v1. + */ +/** + * @defgroup iface_ext_idle_notification_v1 The ext_idle_notification_v1 interface + * + * This interface is used by the compositor to send idle notification events + * to clients. + * + * Initially the notification object is not idle. The notification object + * becomes idle when no user activity has happened for at least the timeout + * duration, starting from the creation of the notification object. User + * activity may include input events or a presence sensor, but is + * compositor-specific. + * + * How this notification responds to idle inhibitors depends on how + * it was constructed. If constructed from the + * get_idle_notification request, then if an idle inhibitor is + * active (e.g. another client has created a zwp_idle_inhibitor_v1 + * on a visible surface), the compositor must not make the + * notification object idle. However, if constructed from the + * get_input_idle_notification request, then idle inhibitors are + * ignored, and only input from the user, e.g. from a keyboard or + * mouse, counts as activity. + * + * When the notification object becomes idle, an idled event is sent. When + * user activity starts again, the notification object stops being idle, + * a resumed event is sent and the timeout is restarted. + */ +extern const struct wl_interface ext_idle_notification_v1_interface; +#endif + +#define EXT_IDLE_NOTIFIER_V1_DESTROY 0 +#define EXT_IDLE_NOTIFIER_V1_GET_IDLE_NOTIFICATION 1 +#define EXT_IDLE_NOTIFIER_V1_GET_INPUT_IDLE_NOTIFICATION 2 + + +/** + * @ingroup iface_ext_idle_notifier_v1 + */ +#define EXT_IDLE_NOTIFIER_V1_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_ext_idle_notifier_v1 + */ +#define EXT_IDLE_NOTIFIER_V1_GET_IDLE_NOTIFICATION_SINCE_VERSION 1 +/** + * @ingroup iface_ext_idle_notifier_v1 + */ +#define EXT_IDLE_NOTIFIER_V1_GET_INPUT_IDLE_NOTIFICATION_SINCE_VERSION 2 + +/** @ingroup iface_ext_idle_notifier_v1 */ +static inline void +ext_idle_notifier_v1_set_user_data(struct ext_idle_notifier_v1 *ext_idle_notifier_v1, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) ext_idle_notifier_v1, user_data); +} + +/** @ingroup iface_ext_idle_notifier_v1 */ +static inline void * +ext_idle_notifier_v1_get_user_data(struct ext_idle_notifier_v1 *ext_idle_notifier_v1) +{ + return wl_proxy_get_user_data((struct wl_proxy *) ext_idle_notifier_v1); +} + +static inline uint32_t +ext_idle_notifier_v1_get_version(struct ext_idle_notifier_v1 *ext_idle_notifier_v1) +{ + return wl_proxy_get_version((struct wl_proxy *) ext_idle_notifier_v1); +} + +/** + * @ingroup iface_ext_idle_notifier_v1 + * + * Destroy the manager object. All objects created via this interface + * remain valid. + */ +static inline void +ext_idle_notifier_v1_destroy(struct ext_idle_notifier_v1 *ext_idle_notifier_v1) +{ + wl_proxy_marshal_flags((struct wl_proxy *) ext_idle_notifier_v1, + EXT_IDLE_NOTIFIER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) ext_idle_notifier_v1), WL_MARSHAL_FLAG_DESTROY); +} + +/** + * @ingroup iface_ext_idle_notifier_v1 + * + * Create a new idle notification object. + * + * The notification object has a minimum timeout duration and is tied to a + * seat. The client will be notified if the seat is inactive for at least + * the provided timeout. See ext_idle_notification_v1 for more details. + * + * A zero timeout is valid and means the client wants to be notified as + * soon as possible when the seat is inactive. + */ +static inline struct ext_idle_notification_v1 * +ext_idle_notifier_v1_get_idle_notification(struct ext_idle_notifier_v1 *ext_idle_notifier_v1, uint32_t timeout, struct wl_seat *seat) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_flags((struct wl_proxy *) ext_idle_notifier_v1, + EXT_IDLE_NOTIFIER_V1_GET_IDLE_NOTIFICATION, &ext_idle_notification_v1_interface, wl_proxy_get_version((struct wl_proxy *) ext_idle_notifier_v1), 0, NULL, timeout, seat); + + return (struct ext_idle_notification_v1 *) id; +} + +/** + * @ingroup iface_ext_idle_notifier_v1 + * + * Create a new idle notification object to track input from the + * user, such as keyboard and mouse movement. Because this object is + * meant to track user input alone, it ignores idle inhibitors. + * + * The notification object has a minimum timeout duration and is tied to a + * seat. The client will be notified if the seat is inactive for at least + * the provided timeout. See ext_idle_notification_v1 for more details. + * + * A zero timeout is valid and means the client wants to be notified as + * soon as possible when the seat is inactive. + */ +static inline struct ext_idle_notification_v1 * +ext_idle_notifier_v1_get_input_idle_notification(struct ext_idle_notifier_v1 *ext_idle_notifier_v1, uint32_t timeout, struct wl_seat *seat) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_flags((struct wl_proxy *) ext_idle_notifier_v1, + EXT_IDLE_NOTIFIER_V1_GET_INPUT_IDLE_NOTIFICATION, &ext_idle_notification_v1_interface, wl_proxy_get_version((struct wl_proxy *) ext_idle_notifier_v1), 0, NULL, timeout, seat); + + return (struct ext_idle_notification_v1 *) id; +} + +/** + * @ingroup iface_ext_idle_notification_v1 + * @struct ext_idle_notification_v1_listener + */ +struct ext_idle_notification_v1_listener { + /** + * notification object is idle + * + * This event is sent when the notification object becomes idle. + * + * It's a compositor protocol error to send this event twice + * without a resumed event in-between. + */ + void (*idled)(void *data, + struct ext_idle_notification_v1 *ext_idle_notification_v1); + /** + * notification object is no longer idle + * + * This event is sent when the notification object stops being + * idle. + * + * It's a compositor protocol error to send this event twice + * without an idled event in-between. It's a compositor protocol + * error to send this event prior to any idled event. + */ + void (*resumed)(void *data, + struct ext_idle_notification_v1 *ext_idle_notification_v1); +}; + +/** + * @ingroup iface_ext_idle_notification_v1 + */ +static inline int +ext_idle_notification_v1_add_listener(struct ext_idle_notification_v1 *ext_idle_notification_v1, + const struct ext_idle_notification_v1_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) ext_idle_notification_v1, + (void (**)(void)) listener, data); +} + +#define EXT_IDLE_NOTIFICATION_V1_DESTROY 0 + +/** + * @ingroup iface_ext_idle_notification_v1 + */ +#define EXT_IDLE_NOTIFICATION_V1_IDLED_SINCE_VERSION 1 +/** + * @ingroup iface_ext_idle_notification_v1 + */ +#define EXT_IDLE_NOTIFICATION_V1_RESUMED_SINCE_VERSION 1 + +/** + * @ingroup iface_ext_idle_notification_v1 + */ +#define EXT_IDLE_NOTIFICATION_V1_DESTROY_SINCE_VERSION 1 + +/** @ingroup iface_ext_idle_notification_v1 */ +static inline void +ext_idle_notification_v1_set_user_data(struct ext_idle_notification_v1 *ext_idle_notification_v1, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) ext_idle_notification_v1, user_data); +} + +/** @ingroup iface_ext_idle_notification_v1 */ +static inline void * +ext_idle_notification_v1_get_user_data(struct ext_idle_notification_v1 *ext_idle_notification_v1) +{ + return wl_proxy_get_user_data((struct wl_proxy *) ext_idle_notification_v1); +} + +static inline uint32_t +ext_idle_notification_v1_get_version(struct ext_idle_notification_v1 *ext_idle_notification_v1) +{ + return wl_proxy_get_version((struct wl_proxy *) ext_idle_notification_v1); +} + +/** + * @ingroup iface_ext_idle_notification_v1 + * + * Destroy the notification object. + */ +static inline void +ext_idle_notification_v1_destroy(struct ext_idle_notification_v1 *ext_idle_notification_v1) +{ + wl_proxy_marshal_flags((struct wl_proxy *) ext_idle_notification_v1, + EXT_IDLE_NOTIFICATION_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) ext_idle_notification_v1), WL_MARSHAL_FLAG_DESTROY); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/driver/ext-idle-notify-v1-protocol.c b/driver/ext-idle-notify-v1-protocol.c new file mode 100644 index 00000000..4c042111 --- /dev/null +++ b/driver/ext-idle-notify-v1-protocol.c @@ -0,0 +1,80 @@ +/* Generated by wayland-scanner 1.23.1 */ + +/* + * Copyright © 2015 Martin Gräßlin + * Copyright © 2022 Simon Ser + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include "wayland-util.h" + +#ifndef __has_attribute +# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif + +#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) +#define WL_PRIVATE __attribute__ ((visibility("hidden"))) +#else +#define WL_PRIVATE +#endif + +extern const struct wl_interface ext_idle_notification_v1_interface; +extern const struct wl_interface wl_seat_interface; + +static const struct wl_interface *ext_idle_notify_v1_types[] = { + &ext_idle_notification_v1_interface, + NULL, + &wl_seat_interface, + &ext_idle_notification_v1_interface, + NULL, + &wl_seat_interface, +}; + +static const struct wl_message ext_idle_notifier_v1_requests[] = { + { "destroy", "", ext_idle_notify_v1_types + 0 }, + { "get_idle_notification", "nuo", ext_idle_notify_v1_types + 0 }, + { "get_input_idle_notification", "2nuo", ext_idle_notify_v1_types + 3 }, +}; + +WL_PRIVATE const struct wl_interface ext_idle_notifier_v1_interface = { + "ext_idle_notifier_v1", 2, + 3, ext_idle_notifier_v1_requests, + 0, NULL, +}; + +static const struct wl_message ext_idle_notification_v1_requests[] = { + { "destroy", "", ext_idle_notify_v1_types + 0 }, +}; + +static const struct wl_message ext_idle_notification_v1_events[] = { + { "idled", "", ext_idle_notify_v1_types + 0 }, + { "resumed", "", ext_idle_notify_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface ext_idle_notification_v1_interface = { + "ext_idle_notification_v1", 2, + 1, ext_idle_notification_v1_requests, + 2, ext_idle_notification_v1_events, +}; + diff --git a/driver/ext-idle-notify-v1.xml b/driver/ext-idle-notify-v1.xml new file mode 100644 index 00000000..db7d9c16 --- /dev/null +++ b/driver/ext-idle-notify-v1.xml @@ -0,0 +1,131 @@ + + + + Copyright © 2015 Martin Gräßlin + Copyright © 2022 Simon Ser + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + This interface allows clients to monitor user idle status. + + After binding to this global, clients can create ext_idle_notification_v1 + objects to get notified when the user is idle for a given amount of time. + + + + + Destroy the manager object. All objects created via this interface + remain valid. + + + + + + Create a new idle notification object. + + The notification object has a minimum timeout duration and is tied to a + seat. The client will be notified if the seat is inactive for at least + the provided timeout. See ext_idle_notification_v1 for more details. + + A zero timeout is valid and means the client wants to be notified as + soon as possible when the seat is inactive. + + + + + + + + + + + Create a new idle notification object to track input from the + user, such as keyboard and mouse movement. Because this object is + meant to track user input alone, it ignores idle inhibitors. + + The notification object has a minimum timeout duration and is tied to a + seat. The client will be notified if the seat is inactive for at least + the provided timeout. See ext_idle_notification_v1 for more details. + + A zero timeout is valid and means the client wants to be notified as + soon as possible when the seat is inactive. + + + + + + + + + + + This interface is used by the compositor to send idle notification events + to clients. + + Initially the notification object is not idle. The notification object + becomes idle when no user activity has happened for at least the timeout + duration, starting from the creation of the notification object. User + activity may include input events or a presence sensor, but is + compositor-specific. + + How this notification responds to idle inhibitors depends on how + it was constructed. If constructed from the + get_idle_notification request, then if an idle inhibitor is + active (e.g. another client has created a zwp_idle_inhibitor_v1 + on a visible surface), the compositor must not make the + notification object idle. However, if constructed from the + get_input_idle_notification request, then idle inhibitors are + ignored, and only input from the user, e.g. from a keyboard or + mouse, counts as activity. + + When the notification object becomes idle, an idled event is sent. When + user activity starts again, the notification object stops being idle, + a resumed event is sent and the timeout is restarted. + + + + + Destroy the notification object. + + + + + + This event is sent when the notification object becomes idle. + + It's a compositor protocol error to send this event twice without a + resumed event in-between. + + + + + + This event is sent when the notification object stops being idle. + + It's a compositor protocol error to send this event twice without an + idled event in-between. It's a compositor protocol error to send this + event prior to any idled event. + + + + diff --git a/driver/exts.c b/driver/exts.c index af5c536f..8e0d68c8 100644 --- a/driver/exts.c +++ b/driver/exts.c @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright © 1991-2022 Jamie Zawinski +/* xscreensaver, Copyright © 1991-2025 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 @@ -145,8 +145,18 @@ print_available_extensions (saver_info *si) }, { "Apple-DRI", "Apple-DRI", True, 0 }, { "Apple-WM", "Apple-WM", True, 0 }, { "XInputExtension", "XInput", True, 0 + }, { "XWAYLAND", "XWayland", True, 0 }, }; + static const char * const envs[] = { + "GDMSESSION", + "RUNNING_UNDER_GDM", + "WAYLAND_DISPLAY", + "WAYLAND_SOCKET", + "XDG_CURRENT_DESKTOP", + "XDG_SESSION_DESKTOP", + "XDG_SESSION_TYPE", + }; fprintf (stderr, "%s: running on display \"%s\"\n", blurb(), DisplayString(si->dpy)); @@ -192,6 +202,14 @@ print_available_extensions (saver_info *si) fprintf (stderr, "%s\n", buf); } +# if !defined(HAVE_GL) + fprintf (stderr, "%s: OpenGL disabled at compile time\n", blurb()); +# elif defined(HAVE_EGL) + fprintf (stderr, "%s: Using EGL\n", blurb()); +# else + fprintf (stderr, "%s: Using GLX\n", blurb()); +# endif + # ifdef HAVE_LIBSYSTEMD fprintf (stderr, "%s: libsystemd\n", blurb()); # endif @@ -199,10 +217,18 @@ print_available_extensions (saver_info *si) fprintf (stderr, "%s: libelogind\n", blurb()); # endif # if !defined(HAVE_LIBSYSTEMD) && !defined(HAVE_LIBELOGIND) - fprintf (stderr, "%s: libsystemd/libelogind (disabled at compile time)\n", + fprintf (stderr, "%s: libsystemd/libelogind disabled at compile time\n", blurb()); # endif + for (i = 0; i < countof(envs); i++) + { + const char *key = envs[i]; + char *val = getenv (key); + if (!val) continue; + fprintf (stderr, "%s: %s = \"%s\"\n", blurb(), key, val); + } + for (i = 0; i < si->nscreens; i++) { saver_screen_info *ssi = &si->screens[i]; diff --git a/driver/fade.c b/driver/fade.c index 46a56efa..c3d2df75 100644 --- a/driver/fade.c +++ b/driver/fade.c @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright © 1992-2022 Jamie Zawinski +/* xscreensaver, Copyright © 1992-2025 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 @@ -33,38 +33,42 @@ hours after it faded out) it has to retain that first screenshot of the desktop to fade back to. But if the desktop had changed in the meantime, there will be a glitch at the end as it snaps from the screenshot to the - new current reality. + new current reality. And under Wayland, it has to get that screenshot + by running "xscreensaver-getimage" which in turn runs "grim", if it is + installed, which it might not be. In summary, everything is terrible because X11 doesn't support alpha. + Another thing to consider is to implement fading with OpenGL: put the + screen shot in a textured quad and just change its color for fading. + That should be efficient on both X11 and Wayland. However it would + require linking the OpenGL libraries into "xscreensaver-gfx". + + The fade process goes like this: - Screen saver activates: - - Fade out: - - Desktop is visible - - Save screenshot for later - - Map invisible temp windows - - Fade from desktop to black - - Erase saver windows to black and raise them - - Destroy temp windows - - Screen saver deactivates: - - Fade out: - - Saver graphics are visible - - Map invisible temp windows - - Do not save a screenshot - - Fade from graphics to black - - Erase saver windows to black and raise them - - Destroy temp windows - - - Fade in: - - Screen is black - - Map invisible temp windows - - Do not save a screenshot - - Unmap saver windows - - Fade from black to saved screenshot - - Destroy temp windows + Screen saver activates: + + - Desktop is visible + - Save screenshot for later + - Fade out: + - Map invisible temp windows + - Fade from desktop to black + - Erase saver windows to black and raise them + - Destroy temp windows + + Screen saver deactivates: + + - Saver graphics are visible + - Fade out: + - Get a screenshot of the current screenhack + - Map invisible temp windows + - Fade from screenhack screenshot to black + - Fade in: + - Fade from black to saved screenshot of desktop + - Unmap obscured saver windows + - Destroy temp windows */ #ifdef HAVE_CONFIG_H @@ -76,6 +80,10 @@ #include #include +#ifdef HAVE_SYS_WAIT_H +# include /* for waitpid() and associated macros */ +#endif + #ifdef HAVE_JWXYZ # include "jwxyz.h" #else /* real X11 */ @@ -112,6 +120,12 @@ #include #include "xinput.h" +#if defined(__APPLE__) && !defined(HAVE_COCOA) +# define HAVE_MACOS_X11 +#elif !defined(HAVE_JWXYZ) /* Real X11, possibly Wayland */ +# define HAVE_REAL_X11 +#endif + typedef struct { int nscreens; @@ -1517,6 +1531,119 @@ typedef struct { static int xshm_whack (Display *, XShmSegmentInfo *, xshm_fade_info *, float ratio); + +/* Grab a screenshot and return it. + It will be the size and extent of the given window. + Or None if we failed. + Somewhat duplicated in screenshot.c:screenshot_grab(). + */ +static Pixmap +xshm_screenshot_grab (Display *dpy, Window window, + Bool from_desktop_p, Bool verbose_p) +{ + XWindowAttributes xgwa; + Pixmap pixmap = 0; + Bool external_p = False; + +# ifdef HAVE_MACOS_X11 + external_p = True; +# else /* X11, possibly Wayland */ + external_p = getenv ("WAYLAND_DISPLAY") || getenv ("WAYLAND_SOCKET"); +# endif + + XGetWindowAttributes (dpy, window, &xgwa); + pixmap = XCreatePixmap (dpy, window, xgwa.width, xgwa.height, xgwa.depth); + if (!pixmap) return None; + + XSync (dpy, False); /* So that the pixmap exists before we exec. */ + + /* Run xscreensaver-getimage to get a screenshot. It will, in turn, + run "screencapture" or "grim" to write the screenshot to a PNG file, + then will load that file onto our Pixmap. + */ + if (external_p) + { + pid_t forked; + char *av[20]; + int ac = 0; + char buf[1024]; + char rootstr[20], pixstr[20]; + + sprintf (rootstr, "0x%0lx", (unsigned long) window); + sprintf (pixstr, "0x%0lx", (unsigned long) pixmap); + av[ac++] = "xscreensaver-getimage"; + if (verbose_p) + av[ac++] = "--verbose"; + av[ac++] = "--desktop"; + if (!from_desktop_p) + av[ac++] = "--no-cache"; + av[ac++] = "--no-images"; + av[ac++] = "--no-video"; + av[ac++] = rootstr; + av[ac++] = pixstr; + av[ac] = 0; + + if (verbose_p) + { + int i; + fprintf (stderr, "%s: fade: executing:", blurb()); + for (i = 0; i < ac; i++) + fprintf (stderr, " %s", av[i]); + fprintf (stderr, "\n"); + } + + switch ((int) (forked = fork ())) { + case -1: + { + sprintf (buf, "%s: couldn't fork", blurb()); + perror (buf); + return 0; + } + case 0: + { + close (ConnectionNumber (dpy)); /* close display fd */ + execvp (av[0], av); /* shouldn't return. */ + exit (-1); /* exits fork */ + break; + } + default: + { + int wait_status = 0, exit_status = 0; + /* Wait for the child to die. */ + 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 != 0) + { + fprintf (stderr, "%s: fade: %s exited with %d\n", + blurb(), av[0], exit_status); + if (pixmap) XFreePixmap (dpy, pixmap); + return None; + } + } + } + } +# ifndef HAVE_MACOS_X11 + else + { + /* Grab a screenshot using XCopyArea. */ + + XGCValues gcv; + GC gc; + gcv.function = GXcopy; + gcv.subwindow_mode = IncludeInferiors; + gc = XCreateGC (dpy, pixmap, GCFunction | GCSubwindowMode, &gcv); + XCopyArea (dpy, xgwa.root, pixmap, gc, + xgwa.x, xgwa.y, xgwa.width, xgwa.height, 0, 0); + XFreeGC (dpy, gc); + } +# endif /* HAVE_MACOS_X11 */ + + return pixmap; +} + + /* Returns: 0: faded normally 1: canceled by user activity @@ -1558,7 +1685,6 @@ xshm_fade (XtAppContext app, Display *dpy, XWindowAttributes xgwa; Window root; XGCValues gcv; - GC gc; unsigned long attrmask = 0; XSetWindowAttributes attrs; @@ -1584,17 +1710,10 @@ xshm_fade (XtAppContext app, Display *dpy, } else { - /* Create a pixmap and grab a screenshot into it. */ info[screen].screenshot = - XCreatePixmap (dpy, root, xgwa.width, xgwa.height, xgwa.depth); + xshm_screenshot_grab (dpy, saver_windows[screen], from_desktop_p, + verbose_p); if (!info[screen].screenshot) goto FAIL; - - gcv.function = GXcopy; - gcv.subwindow_mode = IncludeInferiors; - gc = XCreateGC (dpy, root, GCFunction | GCSubwindowMode, &gcv); - XCopyArea (dpy, root, info[screen].screenshot, gc, - xgwa.x, xgwa.y, xgwa.width, xgwa.height, 0, 0); - XFreeGC (dpy, gc); } /* Create the fader window for the animation. */ @@ -1654,11 +1773,16 @@ xshm_fade (XtAppContext app, Display *dpy, /* Now that we have mapped the screenshot on the fader windows, take the saver windows off the screen. */ +# if 0 + /* Under macOS X11 and Wayland, this causes a single frame of flicker. + I don't see how that is possible, since we just did XMapRaised, + above. */ if (out_p) { XUnmapWindow (dpy, saver_windows[screen]); XClearWindow (dpy, saver_windows[screen]); } +# endif } /* Run the animation at the maximum frame rate in the time allotted. */ diff --git a/driver/idle-client-protocol.h b/driver/idle-client-protocol.h new file mode 100644 index 00000000..4d3bc29a --- /dev/null +++ b/driver/idle-client-protocol.h @@ -0,0 +1,237 @@ +/* Generated by wayland-scanner 1.23.1 */ + +#ifndef IDLE_CLIENT_PROTOCOL_H +#define IDLE_CLIENT_PROTOCOL_H + +#include +#include +#include "wayland-client.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @page page_idle The idle protocol + * @section page_ifaces_idle Interfaces + * - @subpage page_iface_org_kde_kwin_idle - User idle time manager + * - @subpage page_iface_org_kde_kwin_idle_timeout - + * @section page_copyright_idle Copyright + *
+ *
+ * Copyright (C) 2015 Martin Gräßlin
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see .
+ * 
+ */ +struct org_kde_kwin_idle; +struct org_kde_kwin_idle_timeout; +struct wl_seat; + +#ifndef ORG_KDE_KWIN_IDLE_INTERFACE +#define ORG_KDE_KWIN_IDLE_INTERFACE +/** + * @page page_iface_org_kde_kwin_idle org_kde_kwin_idle + * @section page_iface_org_kde_kwin_idle_desc Description + * + * This interface allows to monitor user idle time on a given seat. The interface + * allows to register timers which trigger after no user activity was registered + * on the seat for a given interval. It notifies when user activity resumes. + * + * This is useful for applications wanting to perform actions when the user is not + * interacting with the system, e.g. chat applications setting the user as away, power + * management features to dim screen, etc.. + * @section page_iface_org_kde_kwin_idle_api API + * See @ref iface_org_kde_kwin_idle. + */ +/** + * @defgroup iface_org_kde_kwin_idle The org_kde_kwin_idle interface + * + * This interface allows to monitor user idle time on a given seat. The interface + * allows to register timers which trigger after no user activity was registered + * on the seat for a given interval. It notifies when user activity resumes. + * + * This is useful for applications wanting to perform actions when the user is not + * interacting with the system, e.g. chat applications setting the user as away, power + * management features to dim screen, etc.. + */ +extern const struct wl_interface org_kde_kwin_idle_interface; +#endif +#ifndef ORG_KDE_KWIN_IDLE_TIMEOUT_INTERFACE +#define ORG_KDE_KWIN_IDLE_TIMEOUT_INTERFACE +/** + * @page page_iface_org_kde_kwin_idle_timeout org_kde_kwin_idle_timeout + * @section page_iface_org_kde_kwin_idle_timeout_api API + * See @ref iface_org_kde_kwin_idle_timeout. + */ +/** + * @defgroup iface_org_kde_kwin_idle_timeout The org_kde_kwin_idle_timeout interface + */ +extern const struct wl_interface org_kde_kwin_idle_timeout_interface; +#endif + +#define ORG_KDE_KWIN_IDLE_GET_IDLE_TIMEOUT 0 + + +/** + * @ingroup iface_org_kde_kwin_idle + */ +#define ORG_KDE_KWIN_IDLE_GET_IDLE_TIMEOUT_SINCE_VERSION 1 + +/** @ingroup iface_org_kde_kwin_idle */ +static inline void +org_kde_kwin_idle_set_user_data(struct org_kde_kwin_idle *org_kde_kwin_idle, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) org_kde_kwin_idle, user_data); +} + +/** @ingroup iface_org_kde_kwin_idle */ +static inline void * +org_kde_kwin_idle_get_user_data(struct org_kde_kwin_idle *org_kde_kwin_idle) +{ + return wl_proxy_get_user_data((struct wl_proxy *) org_kde_kwin_idle); +} + +static inline uint32_t +org_kde_kwin_idle_get_version(struct org_kde_kwin_idle *org_kde_kwin_idle) +{ + return wl_proxy_get_version((struct wl_proxy *) org_kde_kwin_idle); +} + +/** @ingroup iface_org_kde_kwin_idle */ +static inline void +org_kde_kwin_idle_destroy(struct org_kde_kwin_idle *org_kde_kwin_idle) +{ + wl_proxy_destroy((struct wl_proxy *) org_kde_kwin_idle); +} + +/** + * @ingroup iface_org_kde_kwin_idle + */ +static inline struct org_kde_kwin_idle_timeout * +org_kde_kwin_idle_get_idle_timeout(struct org_kde_kwin_idle *org_kde_kwin_idle, struct wl_seat *seat, uint32_t timeout) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_flags((struct wl_proxy *) org_kde_kwin_idle, + ORG_KDE_KWIN_IDLE_GET_IDLE_TIMEOUT, &org_kde_kwin_idle_timeout_interface, wl_proxy_get_version((struct wl_proxy *) org_kde_kwin_idle), 0, NULL, seat, timeout); + + return (struct org_kde_kwin_idle_timeout *) id; +} + +/** + * @ingroup iface_org_kde_kwin_idle_timeout + * @struct org_kde_kwin_idle_timeout_listener + */ +struct org_kde_kwin_idle_timeout_listener { + /** + * Triggered when there has not been any user activity in the requested idle time interval + * + * + */ + void (*idle)(void *data, + struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout); + /** + * Triggered on the first user activity after an idle event + * + * + */ + void (*resumed)(void *data, + struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout); +}; + +/** + * @ingroup iface_org_kde_kwin_idle_timeout + */ +static inline int +org_kde_kwin_idle_timeout_add_listener(struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout, + const struct org_kde_kwin_idle_timeout_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) org_kde_kwin_idle_timeout, + (void (**)(void)) listener, data); +} + +#define ORG_KDE_KWIN_IDLE_TIMEOUT_RELEASE 0 +#define ORG_KDE_KWIN_IDLE_TIMEOUT_SIMULATE_USER_ACTIVITY 1 + +/** + * @ingroup iface_org_kde_kwin_idle_timeout + */ +#define ORG_KDE_KWIN_IDLE_TIMEOUT_IDLE_SINCE_VERSION 1 +/** + * @ingroup iface_org_kde_kwin_idle_timeout + */ +#define ORG_KDE_KWIN_IDLE_TIMEOUT_RESUMED_SINCE_VERSION 1 + +/** + * @ingroup iface_org_kde_kwin_idle_timeout + */ +#define ORG_KDE_KWIN_IDLE_TIMEOUT_RELEASE_SINCE_VERSION 1 +/** + * @ingroup iface_org_kde_kwin_idle_timeout + */ +#define ORG_KDE_KWIN_IDLE_TIMEOUT_SIMULATE_USER_ACTIVITY_SINCE_VERSION 1 + +/** @ingroup iface_org_kde_kwin_idle_timeout */ +static inline void +org_kde_kwin_idle_timeout_set_user_data(struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) org_kde_kwin_idle_timeout, user_data); +} + +/** @ingroup iface_org_kde_kwin_idle_timeout */ +static inline void * +org_kde_kwin_idle_timeout_get_user_data(struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout) +{ + return wl_proxy_get_user_data((struct wl_proxy *) org_kde_kwin_idle_timeout); +} + +static inline uint32_t +org_kde_kwin_idle_timeout_get_version(struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout) +{ + return wl_proxy_get_version((struct wl_proxy *) org_kde_kwin_idle_timeout); +} + +/** @ingroup iface_org_kde_kwin_idle_timeout */ +static inline void +org_kde_kwin_idle_timeout_destroy(struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout) +{ + wl_proxy_destroy((struct wl_proxy *) org_kde_kwin_idle_timeout); +} + +/** + * @ingroup iface_org_kde_kwin_idle_timeout + */ +static inline void +org_kde_kwin_idle_timeout_release(struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout) +{ + wl_proxy_marshal_flags((struct wl_proxy *) org_kde_kwin_idle_timeout, + ORG_KDE_KWIN_IDLE_TIMEOUT_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) org_kde_kwin_idle_timeout), WL_MARSHAL_FLAG_DESTROY); +} + +/** + * @ingroup iface_org_kde_kwin_idle_timeout + */ +static inline void +org_kde_kwin_idle_timeout_simulate_user_activity(struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout) +{ + wl_proxy_marshal_flags((struct wl_proxy *) org_kde_kwin_idle_timeout, + ORG_KDE_KWIN_IDLE_TIMEOUT_SIMULATE_USER_ACTIVITY, NULL, wl_proxy_get_version((struct wl_proxy *) org_kde_kwin_idle_timeout), 0); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/driver/idle-protocol.c b/driver/idle-protocol.c new file mode 100644 index 00000000..d788b177 --- /dev/null +++ b/driver/idle-protocol.c @@ -0,0 +1,69 @@ +/* Generated by wayland-scanner 1.23.1 */ + +/* + * Copyright (C) 2015 Martin Gräßlin + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include "wayland-util.h" + +#ifndef __has_attribute +# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif + +#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) +#define WL_PRIVATE __attribute__ ((visibility("hidden"))) +#else +#define WL_PRIVATE +#endif + +extern const struct wl_interface org_kde_kwin_idle_timeout_interface; +extern const struct wl_interface wl_seat_interface; + +static const struct wl_interface *idle_types[] = { + &org_kde_kwin_idle_timeout_interface, + &wl_seat_interface, + NULL, +}; + +static const struct wl_message org_kde_kwin_idle_requests[] = { + { "get_idle_timeout", "nou", idle_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface org_kde_kwin_idle_interface = { + "org_kde_kwin_idle", 1, + 1, org_kde_kwin_idle_requests, + 0, NULL, +}; + +static const struct wl_message org_kde_kwin_idle_timeout_requests[] = { + { "release", "", idle_types + 0 }, + { "simulate_user_activity", "", idle_types + 0 }, +}; + +static const struct wl_message org_kde_kwin_idle_timeout_events[] = { + { "idle", "", idle_types + 0 }, + { "resumed", "", idle_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface org_kde_kwin_idle_timeout_interface = { + "org_kde_kwin_idle_timeout", 1, + 2, org_kde_kwin_idle_timeout_requests, + 2, org_kde_kwin_idle_timeout_events, +}; + diff --git a/driver/idle.xml b/driver/idle.xml new file mode 100644 index 00000000..92d9989c --- /dev/null +++ b/driver/idle.xml @@ -0,0 +1,49 @@ + + + . + ]]> + + + This interface allows to monitor user idle time on a given seat. The interface + allows to register timers which trigger after no user activity was registered + on the seat for a given interval. It notifies when user activity resumes. + + This is useful for applications wanting to perform actions when the user is not + interacting with the system, e.g. chat applications setting the user as away, power + management features to dim screen, etc.. + + + + + + + + + + + + + + + + + + + + + + diff --git a/driver/prefsw.c b/driver/prefsw.c index c5b4b2d0..7fd732bd 100644 --- a/driver/prefsw.c +++ b/driver/prefsw.c @@ -1,5 +1,5 @@ /* prefs.c --- reading and writing the ~/.xscreensaver file. - * xscreensaver, Copyright © 1998-2023 Jamie Zawinski + * xscreensaver, Copyright © 1998-2025 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 @@ -1371,13 +1371,6 @@ stop_the_insanity (saver_preferences *p) p->dpms_enabled_p = False; - /* Set watchdog timeout to about half of the cycle timeout, but - don't let it be faster than 1/2 minute or slower than 1 minute. - */ - p->watchdog_timeout = p->cycle * 0.6; - if (p->watchdog_timeout < 27000) p->watchdog_timeout = 27000; /* 27 secs */ - if (p->watchdog_timeout > 57000) p->watchdog_timeout = 57000; /* 57 secs */ - if (p->pointer_hysteresis < 0) p->pointer_hysteresis = 0; if (p->auth_warning_slack < 0) p->auth_warning_slack = 0; diff --git a/driver/remote.c b/driver/remote.c index ee1e6f23..04974ca1 100644 --- a/driver/remote.c +++ b/driver/remote.c @@ -1,4 +1,4 @@ -/* xscreensaver-command, Copyright © 1991-2024 Jamie Zawinski +/* xscreensaver-command, Copyright © 1991-2025 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 @@ -182,47 +182,32 @@ send_xscreensaver_command (Display *dpy, Atom command, long arg, unsigned long nitems, bytesafter; unsigned char *dataP = 0; + /* XA_SCREENSAVER_STATUS format documented in windows.c. */ if (XGetWindowProperty (dpy, - RootWindow (dpy, 0), + RootWindow (dpy, 0), /* always screen #0 */ XA_SCREENSAVER_STATUS, 0, 999, False, XA_INTEGER, &type, &format, &nitems, &bytesafter, &dataP) == Success - && type - && dataP) - { - Atom blanked; - time_t tt; + && type == XA_INTEGER + && nitems >= 3 + && dataP) + { + PROP32 *data = (PROP32 *) dataP; + Atom state = (Atom) data[0]; + time_t tt = (time_t) /* 64 bit time_t */ + ((((unsigned long) data[1] & 0xFFFFFFFFL) << 32) | + ((unsigned long) data[2] & 0xFFFFFFFFL)); char *s; - Atom *data = (Atom *) dataP; - - if (type != XA_INTEGER || nitems < 3) - { - STATUS_LOSE: - if (data) free (data); - fprintf (stdout, "\n"); - fflush (stdout); - fprintf (stderr, "bad status format on root window\n"); - status = -1; - goto DONE; - } - - blanked = (Atom) data[0]; - tt = (time_t) data[1]; - - if (tt <= (time_t) 666000000L) /* early 1991 */ - goto STATUS_LOSE; - if (blanked == XA_BLANK) + if (state == XA_BLANK) fputs (": screen blanked since ", stdout); - else if (blanked == XA_LOCK) + else if (state == XA_LOCK || state == XA_AUTH) fputs (": screen locked since ", stdout); - else if (blanked == 0) - /* suggestions for a better way to phrase this are welcome. */ + else if (state == 0) fputs (": screen non-blanked since ", stdout); else - /* `blanked' has an unknown value - fail. */ goto STATUS_LOSE; s = ctime(&tt); @@ -231,7 +216,8 @@ send_xscreensaver_command (Display *dpy, Atom command, long arg, fputs (s, stdout); { - int nhacks = nitems - 2; + int off = 3; + int nhacks = nitems - off; Bool any = False; int i; for (i = 0; i < nhacks; i++) @@ -242,13 +228,13 @@ send_xscreensaver_command (Display *dpy, Atom command, long arg, } if (any && nhacks == 1) - fprintf (stdout, " (hack #%d)\n", (int) data[2]); + fprintf (stdout, " (hack #%d)\n", (int) data[off]); else if (any) { fprintf (stdout, " (hacks: "); for (i = 0; i < nhacks; i++) { - fprintf (stdout, "#%d", (int) data[2 + i]); + fprintf (stdout, "#%d", (int) data[off + i]); if (i != nhacks-1) fputs (", ", stdout); } @@ -258,14 +244,20 @@ send_xscreensaver_command (Display *dpy, Atom command, long arg, fputs ("\n", stdout); } - if (data) free (data); + if (dataP) free (dataP); } else { - if (dataP) XFree (dataP); fprintf (stdout, "\n"); + STATUS_LOSE: fflush (stdout); - fprintf (stderr, "no saver status on root window\n"); + + if (dataP) + fprintf (stderr, "bad status format on root window\n"); + else + fprintf (stderr, "no saver status on root window\n"); + + if (dataP) XFree (dataP); status = -1; goto DONE; } @@ -286,8 +278,11 @@ send_xscreensaver_command (Display *dpy, Atom command, long arg, abort(); else if (arg != 0 && command == XA_DEMO) { - arg1 = 5000; /* version number of the XA_DEMO protocol, */ - arg2 = arg; /* since it didn't use to take an argument. */ + /* 1.09, 1992: DEMO took no arguments + 2.35, 1998: [ '300', hack-number ] 1 indexed, 0 means random + 5.00, 2006: [ '5000', hack-number ] same */ + arg1 = 5000; + arg2 = arg; } if (command == XA_DEACTIVATE) @@ -478,8 +473,8 @@ xscreensaver_command_wait_for_blank (Display *dpy, time_t now; struct timeval tv; - /* Wait until the status property on the root window changes to - BLANK or LOCKED. */ + /* Wait until the status property changes to BLANK or LOCKED. */ + /* XA_SCREENSAVER_STATUS format documented in windows.c. */ if (XGetWindowProperty (dpy, w, XA_SCREENSAVER_STATUS, 0, 999, False, XA_INTEGER, @@ -498,15 +493,16 @@ xscreensaver_command_wait_for_blank (Display *dpy, int i; fprintf (stderr, "%s: read status property: 0x%lx: %s", progname, (unsigned long) w, - (status[0] == XA_LOCK ? "LOCK" : - status[0] == XA_BLANK ? "BLANK" : + (status[0] == XA_BLANK ? "BLANK" : + status[0] == XA_LOCK ? "LOCK" : + status[0] == XA_AUTH ? "AUTH" : status[0] == 0 ? "0" : "???")); for (i = 1; i < nitems; i++) fprintf (stderr, ", %lu", status[i]); fprintf (stderr, "\n"); } - if (state == XA_BLANK || state == XA_LOCK) + if (state != 0) { if (verbose_p > 1) fprintf (stderr, "%s: screen blanked\n", progname); diff --git a/driver/subprocs.c b/driver/subprocs.c index 39b1ddd0..f6b70af9 100644 --- a/driver/subprocs.c +++ b/driver/subprocs.c @@ -1,5 +1,5 @@ /* subprocs.c --- choosing, spawning, and killing screenhacks. - * xscreensaver, Copyright © 1991-2024 Jamie Zawinski + * xscreensaver, Copyright © 1991-2025 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 @@ -704,10 +704,14 @@ hack_subproc_environment (Screen *screen, Window saver_window) them. In that case, multiple hacks have the same $DISPLAY, screen and root window. */ + char *nssw = (char *) malloc (40); + +# if !(defined(__APPLE__) && !defined(HAVE_COCOA)) /* not macOS X11 */ + /* XQuartz's weird $DISPLAY pathnames do not match DisplayString() */ + Display *dpy = DisplayOfScreen (screen); const char *odpy = DisplayString (dpy); char *ndpy = (char *) malloc (strlen(odpy) + 20); - char *nssw = (char *) malloc (40); char *s, *c; strcpy (ndpy, "DISPLAY="); @@ -724,10 +728,11 @@ hack_subproc_environment (Screen *screen, Window saver_window) if (s[-1] != '.') *s++ = '.'; /* put on a dot */ sprintf(s, "%d", screen_number (screen)); /* put on screen number */ - sprintf (nssw, "XSCREENSAVER_WINDOW=0x%lX", (unsigned long) saver_window); - if (putenv (ndpy)) abort (); +# endif /* not macOS X11 */ + + sprintf (nssw, "XSCREENSAVER_WINDOW=0x%lX", (unsigned long) saver_window); if (putenv (nssw)) abort (); @@ -1103,11 +1108,12 @@ spawn_screenhack (saver_screen_info *ssi) ssi->cycle_at = now + how_long / 1000; if (p->verbose_p) - { - time_t t = time((time_t *) 0) + how_long/1000; - fprintf (stderr, "%s: %d: next cycle in %lu sec at %s\n", - blurb(), ssi->number, how_long/1000, timestring(t)); - } + fprintf (stderr, "%s: %d: next cycle in %d:%02d:%02d at %s\n", + blurb(), ssi->number, + (int) (how_long / 1000) / (60 * 60), + (int) ((how_long / 1000) % (60 * 60)) / 60, + (int) (how_long / 1000) % 60, + timestring (ssi->cycle_at)); } } @@ -1126,8 +1132,13 @@ kill_screenhack (saver_screen_info *ssi) 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. + + Do not clear the window when exiting, as that would prevent the + unfade from fading the now-dead hack to black, before fading from + black to desktop. */ - XClearWindow (si->dpy, ssi->screensaver_window); + if (! si->terminating_p) + XClearWindow (si->dpy, ssi->screensaver_window); } diff --git a/driver/test-fade.c b/driver/test-fade.c index 31852ef8..f71e567b 100644 --- a/driver/test-fade.c +++ b/driver/test-fade.c @@ -1,5 +1,5 @@ /* test-fade.c --- playing with colormap and/or gamma fading. - * xscreensaver, Copyright © 2001-2021 Jamie Zawinski + * xscreensaver, Copyright © 2001-2025 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 @@ -27,6 +27,7 @@ #include "xscreensaver.h" #include "resources.h" #include "screens.h" +#include "screenshot.h" #include "fade.h" #include "atoms.h" @@ -66,6 +67,7 @@ main (int argc, char **argv) Window root = RootWindow (dpy, 0); Visual *visual = DefaultVisual (dpy, 0); Pixmap logo, logo_clipmask; + Pixmap screenshot; int logo_npixels; unsigned long *logo_pixels; unsigned int logo_width, logo_height; @@ -81,6 +83,20 @@ main (int argc, char **argv) init_xscreensaver_atoms (dpy); + fprintf (stderr, "\n%s:\n" + "\n" + "\tYou should see the following, with no flickering:\n" + "\n" + "\t- Plain desktop;\n" + "\t- Four black rectangles slowly fade in (about 4 seconds);\n" + "\t- Once they are solid black, XScreenSaver logos flash in;\n" + "\t- Those logos fade out to solid black (about 2 seconds);\n" + "\t- Those black squares fade to desktop (about 2 seconds);\n" + "\t- Plain desktop (about 1 second);\n" + "\t- Repeat.\n" + "\n", + blurb()); + { const char * version_number = "test-fade"; Window daemon_window = @@ -135,6 +151,10 @@ main (int argc, char **argv) &logo_clipmask, True); XGetGeometry (dpy, logo, &root, &x, &y, &logo_width, &logo_height, &bw, &d); + fprintf (stderr, "\n%s: grabbing shared screenshot\n", blurb()); + screenshot = screenshot_grab (dpy, root, True, True); + fprintf (stderr, "\n"); + nwindows = 0; { int x, y; @@ -155,11 +175,17 @@ main (int argc, char **argv) attrmask, &attrs); XSetWindowBackground (dpy, windows[nwindows], BlackPixel (dpy, 0)); XClearWindow (dpy, windows[nwindows]); + if (screenshot) + { + fprintf (stderr, "%s: saving screenshot 0x%0lX on 0x%lX\n", + blurb(), (unsigned long) screenshot, windows[nwindows]); + screenshot_save (dpy, windows[nwindows], screenshot); + } nwindows++; } } - fprintf (stderr, "%s: fading %d screen%s\n", + fprintf (stderr, "\n%s: fading %d screen%s\n\n", blurb(), ScreenCount(dpy), ScreenCount(dpy) == 1 ? "" : "s"); while (1) diff --git a/driver/types.h b/driver/types.h index f11ad3b0..3ddbed67 100644 --- a/driver/types.h +++ b/driver/types.h @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright © 1993-2023 Jamie Zawinski +/* xscreensaver, Copyright © 1993-2025 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 @@ -77,7 +77,6 @@ struct saver_preferences { Time lock_timeout; /* how long after activation locking starts */ Time cycle; /* how long each hack should run */ Time passwd_timeout; /* how long before pw dialog goes down */ - Time watchdog_timeout; /* how often to re-raise and re-blank screen */ int pointer_hysteresis; /* mouse motions less than N/sec are ignored */ Bool dpms_enabled_p; /* whether to power down the monitor */ @@ -137,6 +136,10 @@ struct saver_info { Bool using_randr_extension; # endif + time_t activity_time; /* Time at which user last active. */ + time_t blank_time; /* Time at which screen blanked. */ + Bool auth_p; /* Whether auth dialog currently visible. */ + Bool demoing_p; /* Whether we are demoing a single hack (without UI.) */ Bool emergency_p; /* Restarted because of a crash */ @@ -149,6 +152,8 @@ struct saver_info { been received; set to N if SELECT or DEMO N has been received. (This is kind of nasty.) */ + + Bool all_clientmessages_p; /* If the daemon isn't running... */ }; @@ -202,6 +207,7 @@ struct saver_screen_info { /* From dpms.c */ extern void sync_server_dpms_settings (Display *, struct saver_preferences *); +extern void brute_force_dpms (Display *, struct saver_preferences *, time_t); const char *init_file_name (void); diff --git a/driver/wayland-idle.c b/driver/wayland-idle.c new file mode 100644 index 00000000..39acd20d --- /dev/null +++ b/driver/wayland-idle.c @@ -0,0 +1,352 @@ +/* xscreensaver, Copyright © 2025 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. + * + * Detecting idleness via a connection to the Wayland server and the + * "idle-notify-v1" protocol: https://wayland.app/protocols/ext-idle-notify-v1 + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#ifdef HAVE_UNISTD_H +# include +#endif + +#include +#include +#include +/* #include */ +#include +#include +/* #include */ +#include "ext-idle-notify-v1-client-protocol.h" +#include "idle-client-protocol.h" +#include "wayland-idle.h" +#include "blurb.h" + + +struct wayland_state { + struct wl_display *dpy; + struct wl_registry *reg; + struct wl_event_loop *event_loop; + + void (*activity_cb) (void *closure); + void *closure; + + struct wl_seat *seat; + char *seat_name; + uint32_t seat_caps; + + struct ext_idle_notifier_v1 *ext_idle_notifier; + struct ext_idle_notification_v1 *ext_idle_notification; + + struct org_kde_kwin_idle *kde_idle_manager; + struct org_kde_kwin_idle_timeout *kde_idle_timer; +}; + + +static void +seat_handle_capabilities (void *data, struct wl_seat *seat, uint32_t caps) +{ + wayland_state *state = (wayland_state *) data; + state->seat_caps = caps; + if (verbose_p > 2) + fprintf (stderr, "%s: wayland: seat caps: 0x%02lX\n", blurb(), + (unsigned long) caps); +} + +static void +seat_handle_name (void *data, struct wl_seat *seat, const char *name) +{ + wayland_state *state = (wayland_state *) data; + state->seat_name = strdup (name); + if (verbose_p > 2) + fprintf (stderr, "%s: wayland: seat name: \"%s\"\n", blurb(), name); +} + + +/* This is an iterator for all of the extensions that Wayland provides, + so we can find the ones we are interested in. + */ +static void +handle_global (void *data, struct wl_registry *reg, + uint32_t name, const char *iface, uint32_t version) +{ + wayland_state *state = (wayland_state *) data; + + if (!strcmp (iface, wl_seat_interface.name)) + { + static const struct wl_seat_listener seat_listener = { + .name = seat_handle_name, + .capabilities = seat_handle_capabilities, + }; + if (verbose_p > 2) + fprintf (stderr, "%s: wayland: found: %s\n", blurb(), iface); + state->seat = wl_registry_bind (reg, name, &wl_seat_interface, version); + wl_seat_add_listener (state->seat, &seat_listener, state); + } + else if (!strcmp (iface, ext_idle_notifier_v1_interface.name)) + { + if (verbose_p) + fprintf (stderr, "%s: wayland: found: %s\n", blurb(), iface); + state->ext_idle_notifier = + wl_registry_bind (reg, name, &ext_idle_notifier_v1_interface, 1); + } + else if (!strcmp (iface, org_kde_kwin_idle_interface.name)) + { + if (verbose_p) + fprintf (stderr, "%s: wayland: found: %s\n", blurb(), iface); + state->kde_idle_manager = + wl_registry_bind (reg, name, &org_kde_kwin_idle_interface, 1); + } +} + +static void +handle_global_remove (void *data, struct wl_registry *registry, uint32_t name) +{ +} + + +/* Called when the user has been idle longer than the timeout. */ +static void +ext_handle_idled (void *data, struct ext_idle_notification_v1 *note) +{ + /* wayland_state *state = (wayland_state *) data; */ + if (verbose_p > 2) + fprintf (stderr, "%s: wayland: ext_idle: idle notification\n", blurb()); +} + +/* Called when the user is no longer idle. */ +static void +ext_handle_resumed (void *data, struct ext_idle_notification_v1 *note) +{ + wayland_state *state = (wayland_state *) data; + if (verbose_p > 2) + fprintf (stderr, "%s: wayland: ext_idle: active notification\n", blurb()); + (*state->activity_cb) (state->closure); +} + +static void +kde_handle_idle (void *data, struct org_kde_kwin_idle_timeout *timer) +{ + /* wayland_state *state = (wayland_state *) data; */ + if (verbose_p > 2) + fprintf (stderr, "%s: wayland: kde_idle: idle notification\n", blurb()); +} + +static void +kde_handle_resumed (void *data, struct org_kde_kwin_idle_timeout *timer) +{ + wayland_state *state = (wayland_state *) data; + if (verbose_p > 2) + fprintf (stderr, "%s: wayland: kde_idle: active notification\n", blurb()); + (*state->activity_cb) (state->closure); +} + + +static void +destroy_timer (wayland_state *state) +{ + if (state->ext_idle_notification) + { + ext_idle_notification_v1_destroy (state->ext_idle_notification); + state->ext_idle_notification = 0; + if (verbose_p > 2) + fprintf (stderr, "%s: wayland: destroyed kde_idle timer\n", blurb()); + } + if (state->kde_idle_timer) + { + org_kde_kwin_idle_timeout_release (state->kde_idle_timer); + state->kde_idle_timer = 0; + if (verbose_p > 2) + fprintf (stderr, "%s: wayland: destroyed ext_idle timer\n", blurb()); + } +} + +static void +register_timer (wayland_state *state, int timeout) +{ + destroy_timer (state); + if (state->ext_idle_notifier) + { + static const struct ext_idle_notification_v1_listener + ext_idle_notification_listener = { + .idled = ext_handle_idled, + .resumed = ext_handle_resumed, + }; + + state->ext_idle_notification = + ext_idle_notifier_v1_get_idle_notification (state->ext_idle_notifier, + timeout, + state->seat); + ext_idle_notification_v1_add_listener (state->ext_idle_notification, + &ext_idle_notification_listener, + state); + if (verbose_p > 2) + fprintf (stderr, "%s: wayland: registered ext_idle timer\n", blurb()); + } + else + { + static const struct org_kde_kwin_idle_timeout_listener + kde_idle_timer_listener = { + .idle = kde_handle_idle, + .resumed = kde_handle_resumed, + }; + + state->kde_idle_timer = + org_kde_kwin_idle_get_idle_timeout (state->kde_idle_manager, + state->seat, + timeout); + org_kde_kwin_idle_timeout_add_listener (state->kde_idle_timer, + &kde_idle_timer_listener, + state); + if (verbose_p > 2) + fprintf (stderr, "%s: wayland: registered kde_idle timer\n", blurb()); + } +} + + +/* Dispatch any events that have been read. */ +static int +dpy_handle_event (int fd, uint32_t mask, void *data) +{ + wayland_state *state = (wayland_state *) data; + int count = 0; + + if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) + { + /* Nothing good can come of this... */ + } + + if (mask & WL_EVENT_READABLE) + count = wl_display_dispatch (state->dpy); + + if (mask & WL_EVENT_WRITABLE) + wl_display_flush (state->dpy); + + if (mask == 0) + { + count = wl_display_dispatch_pending (state->dpy); + wl_display_flush (state->dpy); + } + + return count; +} + + +/* Connects to Wayland and returns an opaque state object on success. + When user activity is detected, the callback will be run with the + provided object as its argument. On failure, returns NULL and + an error message. + */ +wayland_state * +wayland_idle_init (void (*activity_cb) (void *closure), + void *closure, + const char **error_ret) +{ + wayland_state *state = (wayland_state *) calloc (sizeof (*state), 1); + state->activity_cb = activity_cb; + state->closure = closure; + + state->dpy = wl_display_connect (NULL); + if (!state->dpy) + { + free (state); + if (error_ret) *error_ret = "connection failed"; + return NULL; + } + + if (verbose_p) + fprintf (stderr, "%s: wayland: server connected\n", blurb()); + + state->reg = wl_display_get_registry (state->dpy); + + { + static const struct wl_registry_listener listener = { + .global = handle_global, + .global_remove = handle_global_remove, + }; + wl_registry_add_listener (state->reg, &listener, state); + } + + wl_display_roundtrip (state->dpy); /* First time to register seats */ + wl_display_roundtrip (state->dpy); /* Second time to register timers */ + + if (! state->seat) + { + wayland_idle_free (state); + if (error_ret) *error_ret = "server has no seats"; + return NULL; + } + + if (! (state->kde_idle_manager || + state->ext_idle_notifier)) + { + static char err[300]; + sprintf (err, "server doesn't implement \"%.100s\" or \"%.100s\"", + ext_idle_notifier_v1_interface.name, + org_kde_kwin_idle_interface.name); + wayland_idle_free (state); + if (error_ret) *error_ret = err; + return NULL; + } + + state->event_loop = wl_event_loop_create(); + + { /* Tell the event loop to select() on the display connection. */ + struct wl_event_source *source = + wl_event_loop_add_fd (state->event_loop, + wl_display_get_fd (state->dpy), + WL_EVENT_READABLE, + dpy_handle_event, + state); + wl_event_source_check (source); + /* #### Do I need to free 'source'? */ + } + + register_timer (state, 1000); /* milliseconds */ + + wayland_idle_process_events (state); + + if (error_ret) *error_ret = 0; + return state; +} + +int +wayland_idle_get_fd (wayland_state *state) +{ + return wl_display_get_fd (state->dpy); +} + + +/* Handle any notifications from the Wayland server and run callbacks. + */ +void +wayland_idle_process_events (wayland_state *state) +{ + /* Process all outstanding events without blocking. */ + wl_event_loop_dispatch (state->event_loop, 0); +} + + +void +wayland_idle_free (wayland_state *state) +{ + wl_display_disconnect (state->dpy); + if (state->event_loop) + wl_event_loop_destroy (state->event_loop); + if (state->seat_name) + free (state->seat_name); + free (state); + if (verbose_p) + fprintf (stderr, "%s: wayland: disconnected\n", blurb()); +} diff --git a/driver/wayland-idle.h b/driver/wayland-idle.h new file mode 100644 index 00000000..7fb1df56 --- /dev/null +++ b/driver/wayland-idle.h @@ -0,0 +1,37 @@ +/* xscreensaver, Copyright © 2025 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. + */ + +#ifndef __XSCREENSAVER_WAYLAND_IDLE_H__ +#define __XSCREENSAVER_WAYLAND_IDLE_H__ + +typedef struct wayland_state wayland_state; + +/* Connects to Wayland and returns an opaque state object on success. + When user activity is detected, the callback will be run with the + provided object as its argument. On failure, returns NULL and + an error message. + */ +extern wayland_state * +wayland_idle_init (void (*activity_cb) (void *closure), + void *closure, + const char **error_ret); + +/* Returns the file descriptor of the Wayland display connection. + You may select on this to see if it needs attention. */ +extern int wayland_idle_get_fd (wayland_state *); + +/* Handle any notifications from the Wayland server and run callbacks. */ +extern void wayland_idle_process_events (wayland_state *); + +/* Shut it all down. */ +extern void wayland_idle_free (wayland_state *); + +#endif /* __XSCREENSAVER_WAYLAND_H__ */ diff --git a/driver/windows.c b/driver/windows.c index 6f67e138..c30a0ea7 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-2022 Jamie Zawinski + * xscreensaver, Copyright © 1991-2025 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 @@ -14,11 +14,14 @@ # include "config.h" #endif +#define _GNU_SOURCE #ifdef HAVE_UNAME # include /* for uname() */ #endif /* HAVE_UNAME */ #include +#include +#include #include /* for getpwuid() */ #include #include /* for XSetClassHint() */ @@ -39,6 +42,7 @@ #include "visual.h" #include "screens.h" #include "screenshot.h" +#include "exec.h" #include "fade.h" #include "resources.h" #include "xft.h" @@ -52,16 +56,30 @@ ERROR! You must not include vroot.h in this file. #endif -static void reset_watchdog_timer (saver_info *si); - void store_saver_status (saver_info *si) { - /* The contents of XA_SCREENSAVER_STATUS has LOCK/BLANK/0 in the first slot, - the time at which that state began in the second slot, and the ordinal of - the running hacks on each screen (1-based) in subsequent slots. Since - we don't know the blank-versus-lock status here, we leave whatever was - there before unchanged: it will be updated by "xscreensaver". + /* Structure of the XA_SCREENSAVER_STATUS property: + + XScreenSaver 3.20, 1999: XScreenSaver 6.11, 2025: + + 0: BLANK | LOCK | 0 0: BLANK | LOCK | AUTH | 0 + 1: 32 bit time_t 1: 64 bit time_t hi + 2: screen 0 hack 2: 64 bit time_t lo + 3: screen 1 hack 3: screen 0 hack + ... 4: screen 1 hack + ... + + The first slot is the status: blanked, locked, locked with the password + dialog posted, or 0 for unblanked. + + The time_t is the time at which that status began. + + Following all of that is the list of the ordinals of the hacks running on + each screen, 1-based. + + Since we don't know the blank/lock/auth status here, we leave whatever + was there before unchanged: those will be updated by "xscreensaver". XA_SCREENSAVER_STATUS is stored on the (real) root window of screen 0. @@ -82,44 +100,70 @@ store_saver_status (saver_info *si) PROP32 *status = 0; int format; unsigned long nitems, bytesafter; - int nitems2 = si->nscreens + 2; - int i; - - /* Read the old property, so we can change just our parts. */ - XGetWindowProperty (dpy, w, - XA_SCREENSAVER_STATUS, - 0, 999, False, XA_INTEGER, - &type, &format, &nitems, &bytesafter, - &dataP); + int i, j; + PROP32 state = XA_BLANK; + time_t tt = 0; - status = (PROP32 *) calloc (nitems2, sizeof(PROP32)); + /* I hate using XGrabServer, but the calls to read and then alter the + property must be atomic, and there's no other way to do that. */ + XGrabServer (dpy); + XSync (dpy, False); - if (dataP && type == XA_INTEGER && nitems >= 3) + /* Read the old property, so we can change just our parts. */ + if (XGetWindowProperty (dpy, w, + XA_SCREENSAVER_STATUS, + 0, 999, False, XA_INTEGER, + &type, &format, &nitems, &bytesafter, + &dataP) + == Success + && type == XA_INTEGER + && nitems >= 3 + && dataP) { - status[0] = ((PROP32 *) dataP)[0]; - status[1] = ((PROP32 *) dataP)[1]; + PROP32 *data = (PROP32 *) dataP; + state = data[0]; + tt = (time_t) /* 64 bit time_t */ + ((((unsigned long) data[1] & 0xFFFFFFFFL) << 32) | + ((unsigned long) data[2] & 0xFFFFFFFFL)); } + nitems = 3 + si->nscreens; + status = (PROP32 *) calloc (nitems, sizeof(*status)); + + j = 0; + status[j++] = state; + status[j++] = (PROP32) (((unsigned long) tt) >> 32); + status[j++] = (PROP32) (((unsigned long) tt) & 0xFFFFFFFFL); + for (i = 0; i < si->nscreens; i++) { saver_screen_info *ssi = &si->screens[i]; - status[2 + i] = ssi->current_hack + 1; /* 1-based */ + status[j++] = ssi->current_hack + 1; /* 1-based */ } + if (j != nitems) abort(); XChangeProperty (si->dpy, w, XA_SCREENSAVER_STATUS, XA_INTEGER, 32, - PropModeReplace, (unsigned char *) status, nitems2); + PropModeReplace, (unsigned char *) status, nitems); + XUngrabServer (dpy); XSync (dpy, False); if (si->prefs.debug_p && si->prefs.verbose_p) { int i; - fprintf (stderr, "%s: wrote status property: 0x%lx: %s", blurb(), - (unsigned long) w, - (status[0] == XA_LOCK ? "LOCK" : - status[0] == XA_BLANK ? "BLANK" : - status[0] == 0 ? "0" : "???")); - for (i = 1; i < nitems; i++) - fprintf (stderr, ", %lu", status[i]); + fprintf (stderr, "%s: wrote status property: 0x%lx: ", blurb(), + (unsigned long) w); + for (i = 0; i < nitems; i++) + { + if (i > 0) fprintf (stderr, ", "); + if (i == 0 || i == 2) + fprintf (stderr, "%s", + (status[i] == XA_LOCK ? "LOCK" : + status[i] == XA_BLANK ? "BLANK" : + status[i] == XA_AUTH ? "AUTH" : + status[i] == 0 ? "0" : "???")); + else + fprintf (stderr, "%lu", status[i]); + } fprintf (stderr, "\n"); } @@ -432,18 +476,56 @@ blank_screen (saver_info *si) saver_preferences *p = &si->prefs; Bool user_active_p = False; int i; + Bool grabbing_supported_p = True; initialize_screensaver_window (si); sync_server_dpms_settings (si->dpy, p); + /* Under Wayland, we can only grab screenshots if "grim" is installed, + and even so, there's no way to grab screenshots under GNOME or KDE. + See comment in xscreensaver-getimage.c, and discussion thread here: + https://www.jwz.org/blog/2025/06/wayland-screenshots/#comment-260326 + */ + if (getenv ("WAYLAND_DISPLAY") || getenv ("WAYLAND_SOCKET")) + { + const char *prog = "grim"; + char *desk = getenv ("XDG_CURRENT_DESKTOP"); + if (desk && + (strcasestr (desk, "GNOME") || + strcasestr (desk, "KDE"))) + { + grabbing_supported_p = False; + if (p->verbose_p || p->fade_p || p->unfade_p || p->grab_desktop_p) + fprintf (stderr, + "%s: screenshots and fading not supported on Wayland %s\n", + blurb(), desk); + } + else if (! on_path_p (prog)) + { + grabbing_supported_p = False; + if (p->verbose_p || p->fade_p || p->unfade_p || p->grab_desktop_p) + fprintf (stderr, + "%s: screenshots and fading on Wayland require \"%s\"\n", + blurb(), prog); + } + } + + if (! grabbing_supported_p) + { + p->fade_p = False; + p->unfade_p = False; + } + /* 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 (grabbing_supported_p) + ssi->screenshot = + screenshot_grab (si->dpy, ssi->screensaver_window, False, + p->verbose_p); } if (p->fade_p && @@ -546,6 +628,13 @@ unblank_screen (saver_info *si) True, /* out_p */ False, /* from_desktop_p */ &si->fade_state); + + for (i = 0; i < si->nscreens; i++) + { + saver_screen_info *ssi = &si->screens[i]; + XClearWindow (si->dpy, ssi->screensaver_window); + } + if (! interrupted_p) interrupted_p = fade_screens (si->app, si->dpy, current_windows, si->nscreens, @@ -976,10 +1065,19 @@ watchdog_timer (XtPointer closure, XtIntervalId *id) what ~/.xscreensaver says they should be. */ sync_server_dpms_settings (si->dpy, p); + /* If the wall clock tells us that the monitor should be powered off now, + and the auth dialog is not currently on screen, make sure that the + monitor is off. */ + brute_force_dpms (si->dpy, p, + (si->auth_p + ? 0 + : si->activity_time)); + if (si->prefs.debug_p) fprintf (stderr, "%s: watchdog timer raising screen\n", blurb()); - raise_windows (si); + if (! si->auth_p) /* Do not race with the password dialog */ + raise_windows (si); running_p = any_screenhacks_running_p (si); on_p = monitor_powered_on_p (si->dpy); @@ -1021,28 +1119,58 @@ watchdog_timer (XtPointer closure, XtIntervalId *id) } /* Re-schedule this timer. The watchdog timer defaults to a bit less - than the hack cycle period, but is never longer than one hour. + than the hack cycle period, but is never longer than one minute. */ si->watchdog_id = 0; reset_watchdog_timer (si); } -static void +void reset_watchdog_timer (saver_info *si) { saver_preferences *p = &si->prefs; + time_t now = time ((time_t *) 0); + + /* When should the watchdog next run? + We have a few choices, pick the one that comes soonest. + - A fraction of the cycle time; + - A minute from now; + - The next DPMS event; + - Shortly after the password dialog goes away; + - Not sooner than half a minute, except for DPMS or password. + */ +# define WHEN(TT) do { \ + time_t tt = (TT); \ + if (tt > now && tt < when) when = tt; \ + } while (0) + + int max = 57; + int min = 27; + time_t when = now + max; + + WHEN (now + (p->cycle * 0.6) / 1000); + if (when < now + min) when = now + min; + + WHEN (si->activity_time + p->dpms_standby / 1000); + WHEN (si->activity_time + p->dpms_suspend / 1000); + WHEN (si->activity_time + p->dpms_off / 1000); + + /* So that we do DPMS promptly after auth dialog is canceled. */ + if (si->auth_p) WHEN (now + 2); + + min = 2; + if (when < now + min) when = now + min; if (si->watchdog_id) - { - XtRemoveTimeOut (si->watchdog_id); - si->watchdog_id = 0; - } + XtRemoveTimeOut (si->watchdog_id); - if (p->watchdog_timeout <= 0) return; - si->watchdog_id = XtAppAddTimeOut (si->app, p->watchdog_timeout, + si->watchdog_id = XtAppAddTimeOut (si->app, + (when - now) * 1000, watchdog_timer, (XtPointer) si); - if (p->debug_p) - fprintf (stderr, "%s: restarting watchdog_timer (%ld, %ld)\n", - blurb(), p->watchdog_timeout, si->watchdog_id); + if (p->verbose_p > 1) + fprintf (stderr, "%s: watchdog in %d:%02d:%02d\n", blurb(), + (int) (when - now) / (60 * 60), + (int) ((when - now) % (60 * 60)) / 60, + (int) (when - now) % 60); } diff --git a/driver/xdg-shell-v1-client-protocol.h b/driver/xdg-shell-v1-client-protocol.h new file mode 100644 index 00000000..db577563 --- /dev/null +++ b/driver/xdg-shell-v1-client-protocol.h @@ -0,0 +1,2381 @@ +/* Generated by wayland-scanner 1.23.1 */ + +#ifndef XDG_SHELL_CLIENT_PROTOCOL_H +#define XDG_SHELL_CLIENT_PROTOCOL_H + +#include +#include +#include "wayland-client.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @page page_xdg_shell The xdg_shell protocol + * @section page_ifaces_xdg_shell Interfaces + * - @subpage page_iface_xdg_wm_base - create desktop-style surfaces + * - @subpage page_iface_xdg_positioner - child surface positioner + * - @subpage page_iface_xdg_surface - desktop user interface surface base interface + * - @subpage page_iface_xdg_toplevel - toplevel surface + * - @subpage page_iface_xdg_popup - short-lived, popup surfaces for menus + * @section page_copyright_xdg_shell Copyright + *
+ *
+ * Copyright © 2008-2013 Kristian Høgsberg
+ * Copyright © 2013      Rafael Antognolli
+ * Copyright © 2013      Jasper St. Pierre
+ * Copyright © 2010-2013 Intel Corporation
+ * Copyright © 2015-2017 Samsung Electronics Co., Ltd
+ * Copyright © 2015-2017 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * 
+ */ +struct wl_output; +struct wl_seat; +struct wl_surface; +struct xdg_popup; +struct xdg_positioner; +struct xdg_surface; +struct xdg_toplevel; +struct xdg_wm_base; + +#ifndef XDG_WM_BASE_INTERFACE +#define XDG_WM_BASE_INTERFACE +/** + * @page page_iface_xdg_wm_base xdg_wm_base + * @section page_iface_xdg_wm_base_desc Description + * + * The xdg_wm_base interface is exposed as a global object enabling clients + * to turn their wl_surfaces into windows in a desktop environment. It + * defines the basic functionality needed for clients and the compositor to + * create windows that can be dragged, resized, maximized, etc, as well as + * creating transient windows such as popup menus. + * @section page_iface_xdg_wm_base_api API + * See @ref iface_xdg_wm_base. + */ +/** + * @defgroup iface_xdg_wm_base The xdg_wm_base interface + * + * The xdg_wm_base interface is exposed as a global object enabling clients + * to turn their wl_surfaces into windows in a desktop environment. It + * defines the basic functionality needed for clients and the compositor to + * create windows that can be dragged, resized, maximized, etc, as well as + * creating transient windows such as popup menus. + */ +extern const struct wl_interface xdg_wm_base_interface; +#endif +#ifndef XDG_POSITIONER_INTERFACE +#define XDG_POSITIONER_INTERFACE +/** + * @page page_iface_xdg_positioner xdg_positioner + * @section page_iface_xdg_positioner_desc Description + * + * The xdg_positioner provides a collection of rules for the placement of a + * child surface relative to a parent surface. Rules can be defined to ensure + * the child surface remains within the visible area's borders, and to + * specify how the child surface changes its position, such as sliding along + * an axis, or flipping around a rectangle. These positioner-created rules are + * constrained by the requirement that a child surface must intersect with or + * be at least partially adjacent to its parent surface. + * + * See the various requests for details about possible rules. + * + * At the time of the request, the compositor makes a copy of the rules + * specified by the xdg_positioner. Thus, after the request is complete the + * xdg_positioner object can be destroyed or reused; further changes to the + * object will have no effect on previous usages. + * + * For an xdg_positioner object to be considered complete, it must have a + * non-zero size set by set_size, and a non-zero anchor rectangle set by + * set_anchor_rect. Passing an incomplete xdg_positioner object when + * positioning a surface raises an invalid_positioner error. + * @section page_iface_xdg_positioner_api API + * See @ref iface_xdg_positioner. + */ +/** + * @defgroup iface_xdg_positioner The xdg_positioner interface + * + * The xdg_positioner provides a collection of rules for the placement of a + * child surface relative to a parent surface. Rules can be defined to ensure + * the child surface remains within the visible area's borders, and to + * specify how the child surface changes its position, such as sliding along + * an axis, or flipping around a rectangle. These positioner-created rules are + * constrained by the requirement that a child surface must intersect with or + * be at least partially adjacent to its parent surface. + * + * See the various requests for details about possible rules. + * + * At the time of the request, the compositor makes a copy of the rules + * specified by the xdg_positioner. Thus, after the request is complete the + * xdg_positioner object can be destroyed or reused; further changes to the + * object will have no effect on previous usages. + * + * For an xdg_positioner object to be considered complete, it must have a + * non-zero size set by set_size, and a non-zero anchor rectangle set by + * set_anchor_rect. Passing an incomplete xdg_positioner object when + * positioning a surface raises an invalid_positioner error. + */ +extern const struct wl_interface xdg_positioner_interface; +#endif +#ifndef XDG_SURFACE_INTERFACE +#define XDG_SURFACE_INTERFACE +/** + * @page page_iface_xdg_surface xdg_surface + * @section page_iface_xdg_surface_desc Description + * + * An interface that may be implemented by a wl_surface, for + * implementations that provide a desktop-style user interface. + * + * It provides a base set of functionality required to construct user + * interface elements requiring management by the compositor, such as + * toplevel windows, menus, etc. The types of functionality are split into + * xdg_surface roles. + * + * Creating an xdg_surface does not set the role for a wl_surface. In order + * to map an xdg_surface, the client must create a role-specific object + * using, e.g., get_toplevel, get_popup. The wl_surface for any given + * xdg_surface can have at most one role, and may not be assigned any role + * not based on xdg_surface. + * + * A role must be assigned before any other requests are made to the + * xdg_surface object. + * + * The client must call wl_surface.commit on the corresponding wl_surface + * for the xdg_surface state to take effect. + * + * Creating an xdg_surface from a wl_surface which has a buffer attached or + * committed is a client error, and any attempts by a client to attach or + * manipulate a buffer prior to the first xdg_surface.configure call must + * also be treated as errors. + * + * After creating a role-specific object and setting it up (e.g. by sending + * the title, app ID, size constraints, parent, etc), the client must + * perform an initial commit without any buffer attached. The compositor + * will reply with initial wl_surface state such as + * wl_surface.preferred_buffer_scale followed by an xdg_surface.configure + * event. The client must acknowledge it and is then allowed to attach a + * buffer to map the surface. + * + * Mapping an xdg_surface-based role surface is defined as making it + * possible for the surface to be shown by the compositor. Note that + * a mapped surface is not guaranteed to be visible once it is mapped. + * + * For an xdg_surface to be mapped by the compositor, the following + * conditions must be met: + * (1) the client has assigned an xdg_surface-based role to the surface + * (2) the client has set and committed the xdg_surface state and the + * role-dependent state to the surface + * (3) the client has committed a buffer to the surface + * + * A newly-unmapped surface is considered to have met condition (1) out + * of the 3 required conditions for mapping a surface if its role surface + * has not been destroyed, i.e. the client must perform the initial commit + * again before attaching a buffer. + * @section page_iface_xdg_surface_api API + * See @ref iface_xdg_surface. + */ +/** + * @defgroup iface_xdg_surface The xdg_surface interface + * + * An interface that may be implemented by a wl_surface, for + * implementations that provide a desktop-style user interface. + * + * It provides a base set of functionality required to construct user + * interface elements requiring management by the compositor, such as + * toplevel windows, menus, etc. The types of functionality are split into + * xdg_surface roles. + * + * Creating an xdg_surface does not set the role for a wl_surface. In order + * to map an xdg_surface, the client must create a role-specific object + * using, e.g., get_toplevel, get_popup. The wl_surface for any given + * xdg_surface can have at most one role, and may not be assigned any role + * not based on xdg_surface. + * + * A role must be assigned before any other requests are made to the + * xdg_surface object. + * + * The client must call wl_surface.commit on the corresponding wl_surface + * for the xdg_surface state to take effect. + * + * Creating an xdg_surface from a wl_surface which has a buffer attached or + * committed is a client error, and any attempts by a client to attach or + * manipulate a buffer prior to the first xdg_surface.configure call must + * also be treated as errors. + * + * After creating a role-specific object and setting it up (e.g. by sending + * the title, app ID, size constraints, parent, etc), the client must + * perform an initial commit without any buffer attached. The compositor + * will reply with initial wl_surface state such as + * wl_surface.preferred_buffer_scale followed by an xdg_surface.configure + * event. The client must acknowledge it and is then allowed to attach a + * buffer to map the surface. + * + * Mapping an xdg_surface-based role surface is defined as making it + * possible for the surface to be shown by the compositor. Note that + * a mapped surface is not guaranteed to be visible once it is mapped. + * + * For an xdg_surface to be mapped by the compositor, the following + * conditions must be met: + * (1) the client has assigned an xdg_surface-based role to the surface + * (2) the client has set and committed the xdg_surface state and the + * role-dependent state to the surface + * (3) the client has committed a buffer to the surface + * + * A newly-unmapped surface is considered to have met condition (1) out + * of the 3 required conditions for mapping a surface if its role surface + * has not been destroyed, i.e. the client must perform the initial commit + * again before attaching a buffer. + */ +extern const struct wl_interface xdg_surface_interface; +#endif +#ifndef XDG_TOPLEVEL_INTERFACE +#define XDG_TOPLEVEL_INTERFACE +/** + * @page page_iface_xdg_toplevel xdg_toplevel + * @section page_iface_xdg_toplevel_desc Description + * + * This interface defines an xdg_surface role which allows a surface to, + * among other things, set window-like properties such as maximize, + * fullscreen, and minimize, set application-specific metadata like title and + * id, and well as trigger user interactive operations such as interactive + * resize and move. + * + * A xdg_toplevel by default is responsible for providing the full intended + * visual representation of the toplevel, which depending on the window + * state, may mean things like a title bar, window controls and drop shadow. + * + * Unmapping an xdg_toplevel means that the surface cannot be shown + * by the compositor until it is explicitly mapped again. + * All active operations (e.g., move, resize) are canceled and all + * attributes (e.g. title, state, stacking, ...) are discarded for + * an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to + * the state it had right after xdg_surface.get_toplevel. The client + * can re-map the toplevel by performing a commit without any buffer + * attached, waiting for a configure event and handling it as usual (see + * xdg_surface description). + * + * Attaching a null buffer to a toplevel unmaps the surface. + * @section page_iface_xdg_toplevel_api API + * See @ref iface_xdg_toplevel. + */ +/** + * @defgroup iface_xdg_toplevel The xdg_toplevel interface + * + * This interface defines an xdg_surface role which allows a surface to, + * among other things, set window-like properties such as maximize, + * fullscreen, and minimize, set application-specific metadata like title and + * id, and well as trigger user interactive operations such as interactive + * resize and move. + * + * A xdg_toplevel by default is responsible for providing the full intended + * visual representation of the toplevel, which depending on the window + * state, may mean things like a title bar, window controls and drop shadow. + * + * Unmapping an xdg_toplevel means that the surface cannot be shown + * by the compositor until it is explicitly mapped again. + * All active operations (e.g., move, resize) are canceled and all + * attributes (e.g. title, state, stacking, ...) are discarded for + * an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to + * the state it had right after xdg_surface.get_toplevel. The client + * can re-map the toplevel by performing a commit without any buffer + * attached, waiting for a configure event and handling it as usual (see + * xdg_surface description). + * + * Attaching a null buffer to a toplevel unmaps the surface. + */ +extern const struct wl_interface xdg_toplevel_interface; +#endif +#ifndef XDG_POPUP_INTERFACE +#define XDG_POPUP_INTERFACE +/** + * @page page_iface_xdg_popup xdg_popup + * @section page_iface_xdg_popup_desc Description + * + * A popup surface is a short-lived, temporary surface. It can be used to + * implement for example menus, popovers, tooltips and other similar user + * interface concepts. + * + * A popup can be made to take an explicit grab. See xdg_popup.grab for + * details. + * + * When the popup is dismissed, a popup_done event will be sent out, and at + * the same time the surface will be unmapped. See the xdg_popup.popup_done + * event for details. + * + * Explicitly destroying the xdg_popup object will also dismiss the popup and + * unmap the surface. Clients that want to dismiss the popup when another + * surface of their own is clicked should dismiss the popup using the destroy + * request. + * + * A newly created xdg_popup will be stacked on top of all previously created + * xdg_popup surfaces associated with the same xdg_toplevel. + * + * The parent of an xdg_popup must be mapped (see the xdg_surface + * description) before the xdg_popup itself. + * + * The client must call wl_surface.commit on the corresponding wl_surface + * for the xdg_popup state to take effect. + * @section page_iface_xdg_popup_api API + * See @ref iface_xdg_popup. + */ +/** + * @defgroup iface_xdg_popup The xdg_popup interface + * + * A popup surface is a short-lived, temporary surface. It can be used to + * implement for example menus, popovers, tooltips and other similar user + * interface concepts. + * + * A popup can be made to take an explicit grab. See xdg_popup.grab for + * details. + * + * When the popup is dismissed, a popup_done event will be sent out, and at + * the same time the surface will be unmapped. See the xdg_popup.popup_done + * event for details. + * + * Explicitly destroying the xdg_popup object will also dismiss the popup and + * unmap the surface. Clients that want to dismiss the popup when another + * surface of their own is clicked should dismiss the popup using the destroy + * request. + * + * A newly created xdg_popup will be stacked on top of all previously created + * xdg_popup surfaces associated with the same xdg_toplevel. + * + * The parent of an xdg_popup must be mapped (see the xdg_surface + * description) before the xdg_popup itself. + * + * The client must call wl_surface.commit on the corresponding wl_surface + * for the xdg_popup state to take effect. + */ +extern const struct wl_interface xdg_popup_interface; +#endif + +#ifndef XDG_WM_BASE_ERROR_ENUM +#define XDG_WM_BASE_ERROR_ENUM +enum xdg_wm_base_error { + /** + * given wl_surface has another role + */ + XDG_WM_BASE_ERROR_ROLE = 0, + /** + * xdg_wm_base was destroyed before children + */ + XDG_WM_BASE_ERROR_DEFUNCT_SURFACES = 1, + /** + * the client tried to map or destroy a non-topmost popup + */ + XDG_WM_BASE_ERROR_NOT_THE_TOPMOST_POPUP = 2, + /** + * the client specified an invalid popup parent surface + */ + XDG_WM_BASE_ERROR_INVALID_POPUP_PARENT = 3, + /** + * the client provided an invalid surface state + */ + XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE = 4, + /** + * the client provided an invalid positioner + */ + XDG_WM_BASE_ERROR_INVALID_POSITIONER = 5, + /** + * the client didn’t respond to a ping event in time + */ + XDG_WM_BASE_ERROR_UNRESPONSIVE = 6, +}; +#endif /* XDG_WM_BASE_ERROR_ENUM */ + +/** + * @ingroup iface_xdg_wm_base + * @struct xdg_wm_base_listener + */ +struct xdg_wm_base_listener { + /** + * check if the client is alive + * + * The ping event asks the client if it's still alive. Pass the + * serial specified in the event back to the compositor by sending + * a "pong" request back with the specified serial. See + * xdg_wm_base.pong. + * + * Compositors can use this to determine if the client is still + * alive. It's unspecified what will happen if the client doesn't + * respond to the ping request, or in what timeframe. Clients + * should try to respond in a reasonable amount of time. The + * “unresponsive” error is provided for compositors that wish + * to disconnect unresponsive clients. + * + * A compositor is free to ping in any way it wants, but a client + * must always respond to any xdg_wm_base object it created. + * @param serial pass this to the pong request + */ + void (*ping)(void *data, + struct xdg_wm_base *xdg_wm_base, + uint32_t serial); +}; + +/** + * @ingroup iface_xdg_wm_base + */ +static inline int +xdg_wm_base_add_listener(struct xdg_wm_base *xdg_wm_base, + const struct xdg_wm_base_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) xdg_wm_base, + (void (**)(void)) listener, data); +} + +#define XDG_WM_BASE_DESTROY 0 +#define XDG_WM_BASE_CREATE_POSITIONER 1 +#define XDG_WM_BASE_GET_XDG_SURFACE 2 +#define XDG_WM_BASE_PONG 3 + +/** + * @ingroup iface_xdg_wm_base + */ +#define XDG_WM_BASE_PING_SINCE_VERSION 1 + +/** + * @ingroup iface_xdg_wm_base + */ +#define XDG_WM_BASE_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_wm_base + */ +#define XDG_WM_BASE_CREATE_POSITIONER_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_wm_base + */ +#define XDG_WM_BASE_GET_XDG_SURFACE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_wm_base + */ +#define XDG_WM_BASE_PONG_SINCE_VERSION 1 + +/** @ingroup iface_xdg_wm_base */ +static inline void +xdg_wm_base_set_user_data(struct xdg_wm_base *xdg_wm_base, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) xdg_wm_base, user_data); +} + +/** @ingroup iface_xdg_wm_base */ +static inline void * +xdg_wm_base_get_user_data(struct xdg_wm_base *xdg_wm_base) +{ + return wl_proxy_get_user_data((struct wl_proxy *) xdg_wm_base); +} + +static inline uint32_t +xdg_wm_base_get_version(struct xdg_wm_base *xdg_wm_base) +{ + return wl_proxy_get_version((struct wl_proxy *) xdg_wm_base); +} + +/** + * @ingroup iface_xdg_wm_base + * + * Destroy this xdg_wm_base object. + * + * Destroying a bound xdg_wm_base object while there are surfaces + * still alive created by this xdg_wm_base object instance is illegal + * and will result in a defunct_surfaces error. + */ +static inline void +xdg_wm_base_destroy(struct xdg_wm_base *xdg_wm_base) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_wm_base, + XDG_WM_BASE_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_wm_base), WL_MARSHAL_FLAG_DESTROY); +} + +/** + * @ingroup iface_xdg_wm_base + * + * Create a positioner object. A positioner object is used to position + * surfaces relative to some parent surface. See the interface description + * and xdg_surface.get_popup for details. + */ +static inline struct xdg_positioner * +xdg_wm_base_create_positioner(struct xdg_wm_base *xdg_wm_base) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_flags((struct wl_proxy *) xdg_wm_base, + XDG_WM_BASE_CREATE_POSITIONER, &xdg_positioner_interface, wl_proxy_get_version((struct wl_proxy *) xdg_wm_base), 0, NULL); + + return (struct xdg_positioner *) id; +} + +/** + * @ingroup iface_xdg_wm_base + * + * This creates an xdg_surface for the given surface. While xdg_surface + * itself is not a role, the corresponding surface may only be assigned + * a role extending xdg_surface, such as xdg_toplevel or xdg_popup. It is + * illegal to create an xdg_surface for a wl_surface which already has an + * assigned role and this will result in a role error. + * + * This creates an xdg_surface for the given surface. An xdg_surface is + * used as basis to define a role to a given surface, such as xdg_toplevel + * or xdg_popup. It also manages functionality shared between xdg_surface + * based surface roles. + * + * See the documentation of xdg_surface for more details about what an + * xdg_surface is and how it is used. + */ +static inline struct xdg_surface * +xdg_wm_base_get_xdg_surface(struct xdg_wm_base *xdg_wm_base, struct wl_surface *surface) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_flags((struct wl_proxy *) xdg_wm_base, + XDG_WM_BASE_GET_XDG_SURFACE, &xdg_surface_interface, wl_proxy_get_version((struct wl_proxy *) xdg_wm_base), 0, NULL, surface); + + return (struct xdg_surface *) id; +} + +/** + * @ingroup iface_xdg_wm_base + * + * A client must respond to a ping event with a pong request or + * the client may be deemed unresponsive. See xdg_wm_base.ping + * and xdg_wm_base.error.unresponsive. + */ +static inline void +xdg_wm_base_pong(struct xdg_wm_base *xdg_wm_base, uint32_t serial) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_wm_base, + XDG_WM_BASE_PONG, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_wm_base), 0, serial); +} + +#ifndef XDG_POSITIONER_ERROR_ENUM +#define XDG_POSITIONER_ERROR_ENUM +enum xdg_positioner_error { + /** + * invalid input provided + */ + XDG_POSITIONER_ERROR_INVALID_INPUT = 0, +}; +#endif /* XDG_POSITIONER_ERROR_ENUM */ + +#ifndef XDG_POSITIONER_ANCHOR_ENUM +#define XDG_POSITIONER_ANCHOR_ENUM +enum xdg_positioner_anchor { + XDG_POSITIONER_ANCHOR_NONE = 0, + XDG_POSITIONER_ANCHOR_TOP = 1, + XDG_POSITIONER_ANCHOR_BOTTOM = 2, + XDG_POSITIONER_ANCHOR_LEFT = 3, + XDG_POSITIONER_ANCHOR_RIGHT = 4, + XDG_POSITIONER_ANCHOR_TOP_LEFT = 5, + XDG_POSITIONER_ANCHOR_BOTTOM_LEFT = 6, + XDG_POSITIONER_ANCHOR_TOP_RIGHT = 7, + XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT = 8, +}; +#endif /* XDG_POSITIONER_ANCHOR_ENUM */ + +#ifndef XDG_POSITIONER_GRAVITY_ENUM +#define XDG_POSITIONER_GRAVITY_ENUM +enum xdg_positioner_gravity { + XDG_POSITIONER_GRAVITY_NONE = 0, + XDG_POSITIONER_GRAVITY_TOP = 1, + XDG_POSITIONER_GRAVITY_BOTTOM = 2, + XDG_POSITIONER_GRAVITY_LEFT = 3, + XDG_POSITIONER_GRAVITY_RIGHT = 4, + XDG_POSITIONER_GRAVITY_TOP_LEFT = 5, + XDG_POSITIONER_GRAVITY_BOTTOM_LEFT = 6, + XDG_POSITIONER_GRAVITY_TOP_RIGHT = 7, + XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT = 8, +}; +#endif /* XDG_POSITIONER_GRAVITY_ENUM */ + +#ifndef XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM +#define XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM +/** + * @ingroup iface_xdg_positioner + * constraint adjustments + * + * The constraint adjustment value define ways the compositor will adjust + * the position of the surface, if the unadjusted position would result + * in the surface being partly constrained. + * + * Whether a surface is considered 'constrained' is left to the compositor + * to determine. For example, the surface may be partly outside the + * compositor's defined 'work area', thus necessitating the child surface's + * position be adjusted until it is entirely inside the work area. + * + * The adjustments can be combined, according to a defined precedence: 1) + * Flip, 2) Slide, 3) Resize. + */ +enum xdg_positioner_constraint_adjustment { + /** + * don't move the child surface when constrained + * + * Don't alter the surface position even if it is constrained on + * some axis, for example partially outside the edge of an output. + */ + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE = 0, + /** + * move along the x axis until unconstrained + * + * Slide the surface along the x axis until it is no longer + * constrained. + * + * First try to slide towards the direction of the gravity on the x + * axis until either the edge in the opposite direction of the + * gravity is unconstrained or the edge in the direction of the + * gravity is constrained. + * + * Then try to slide towards the opposite direction of the gravity + * on the x axis until either the edge in the direction of the + * gravity is unconstrained or the edge in the opposite direction + * of the gravity is constrained. + */ + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X = 1, + /** + * move along the y axis until unconstrained + * + * Slide the surface along the y axis until it is no longer + * constrained. + * + * First try to slide towards the direction of the gravity on the y + * axis until either the edge in the opposite direction of the + * gravity is unconstrained or the edge in the direction of the + * gravity is constrained. + * + * Then try to slide towards the opposite direction of the gravity + * on the y axis until either the edge in the direction of the + * gravity is unconstrained or the edge in the opposite direction + * of the gravity is constrained. + */ + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y = 2, + /** + * invert the anchor and gravity on the x axis + * + * Invert the anchor and gravity on the x axis if the surface is + * constrained on the x axis. For example, if the left edge of the + * surface is constrained, the gravity is 'left' and the anchor is + * 'left', change the gravity to 'right' and the anchor to 'right'. + * + * If the adjusted position also ends up being constrained, the + * resulting position of the flip_x adjustment will be the one + * before the adjustment. + */ + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X = 4, + /** + * invert the anchor and gravity on the y axis + * + * Invert the anchor and gravity on the y axis if the surface is + * constrained on the y axis. For example, if the bottom edge of + * the surface is constrained, the gravity is 'bottom' and the + * anchor is 'bottom', change the gravity to 'top' and the anchor + * to 'top'. + * + * The adjusted position is calculated given the original anchor + * rectangle and offset, but with the new flipped anchor and + * gravity values. + * + * If the adjusted position also ends up being constrained, the + * resulting position of the flip_y adjustment will be the one + * before the adjustment. + */ + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y = 8, + /** + * horizontally resize the surface + * + * Resize the surface horizontally so that it is completely + * unconstrained. + */ + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X = 16, + /** + * vertically resize the surface + * + * Resize the surface vertically so that it is completely + * unconstrained. + */ + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y = 32, +}; +#endif /* XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM */ + +#define XDG_POSITIONER_DESTROY 0 +#define XDG_POSITIONER_SET_SIZE 1 +#define XDG_POSITIONER_SET_ANCHOR_RECT 2 +#define XDG_POSITIONER_SET_ANCHOR 3 +#define XDG_POSITIONER_SET_GRAVITY 4 +#define XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT 5 +#define XDG_POSITIONER_SET_OFFSET 6 +#define XDG_POSITIONER_SET_REACTIVE 7 +#define XDG_POSITIONER_SET_PARENT_SIZE 8 +#define XDG_POSITIONER_SET_PARENT_CONFIGURE 9 + + +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_SIZE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_ANCHOR_RECT_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_ANCHOR_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_GRAVITY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_OFFSET_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_REACTIVE_SINCE_VERSION 3 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_PARENT_SIZE_SINCE_VERSION 3 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_PARENT_CONFIGURE_SINCE_VERSION 3 + +/** @ingroup iface_xdg_positioner */ +static inline void +xdg_positioner_set_user_data(struct xdg_positioner *xdg_positioner, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) xdg_positioner, user_data); +} + +/** @ingroup iface_xdg_positioner */ +static inline void * +xdg_positioner_get_user_data(struct xdg_positioner *xdg_positioner) +{ + return wl_proxy_get_user_data((struct wl_proxy *) xdg_positioner); +} + +static inline uint32_t +xdg_positioner_get_version(struct xdg_positioner *xdg_positioner) +{ + return wl_proxy_get_version((struct wl_proxy *) xdg_positioner); +} + +/** + * @ingroup iface_xdg_positioner + * + * Notify the compositor that the xdg_positioner will no longer be used. + */ +static inline void +xdg_positioner_destroy(struct xdg_positioner *xdg_positioner) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, + XDG_POSITIONER_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), WL_MARSHAL_FLAG_DESTROY); +} + +/** + * @ingroup iface_xdg_positioner + * + * Set the size of the surface that is to be positioned with the positioner + * object. The size is in surface-local coordinates and corresponds to the + * window geometry. See xdg_surface.set_window_geometry. + * + * If a zero or negative size is set the invalid_input error is raised. + */ +static inline void +xdg_positioner_set_size(struct xdg_positioner *xdg_positioner, int32_t width, int32_t height) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, + XDG_POSITIONER_SET_SIZE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, width, height); +} + +/** + * @ingroup iface_xdg_positioner + * + * Specify the anchor rectangle within the parent surface that the child + * surface will be placed relative to. The rectangle is relative to the + * window geometry as defined by xdg_surface.set_window_geometry of the + * parent surface. + * + * When the xdg_positioner object is used to position a child surface, the + * anchor rectangle may not extend outside the window geometry of the + * positioned child's parent surface. + * + * If a negative size is set the invalid_input error is raised. + */ +static inline void +xdg_positioner_set_anchor_rect(struct xdg_positioner *xdg_positioner, int32_t x, int32_t y, int32_t width, int32_t height) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, + XDG_POSITIONER_SET_ANCHOR_RECT, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, x, y, width, height); +} + +/** + * @ingroup iface_xdg_positioner + * + * Defines the anchor point for the anchor rectangle. The specified anchor + * is used derive an anchor point that the child surface will be + * positioned relative to. If a corner anchor is set (e.g. 'top_left' or + * 'bottom_right'), the anchor point will be at the specified corner; + * otherwise, the derived anchor point will be centered on the specified + * edge, or in the center of the anchor rectangle if no edge is specified. + */ +static inline void +xdg_positioner_set_anchor(struct xdg_positioner *xdg_positioner, uint32_t anchor) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, + XDG_POSITIONER_SET_ANCHOR, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, anchor); +} + +/** + * @ingroup iface_xdg_positioner + * + * Defines in what direction a surface should be positioned, relative to + * the anchor point of the parent surface. If a corner gravity is + * specified (e.g. 'bottom_right' or 'top_left'), then the child surface + * will be placed towards the specified gravity; otherwise, the child + * surface will be centered over the anchor point on any axis that had no + * gravity specified. If the gravity is not in the ‘gravity’ enum, an + * invalid_input error is raised. + */ +static inline void +xdg_positioner_set_gravity(struct xdg_positioner *xdg_positioner, uint32_t gravity) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, + XDG_POSITIONER_SET_GRAVITY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, gravity); +} + +/** + * @ingroup iface_xdg_positioner + * + * Specify how the window should be positioned if the originally intended + * position caused the surface to be constrained, meaning at least + * partially outside positioning boundaries set by the compositor. The + * adjustment is set by constructing a bitmask describing the adjustment to + * be made when the surface is constrained on that axis. + * + * If no bit for one axis is set, the compositor will assume that the child + * surface should not change its position on that axis when constrained. + * + * If more than one bit for one axis is set, the order of how adjustments + * are applied is specified in the corresponding adjustment descriptions. + * + * The default adjustment is none. + */ +static inline void +xdg_positioner_set_constraint_adjustment(struct xdg_positioner *xdg_positioner, uint32_t constraint_adjustment) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, + XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, constraint_adjustment); +} + +/** + * @ingroup iface_xdg_positioner + * + * Specify the surface position offset relative to the position of the + * anchor on the anchor rectangle and the anchor on the surface. For + * example if the anchor of the anchor rectangle is at (x, y), the surface + * has the gravity bottom|right, and the offset is (ox, oy), the calculated + * surface position will be (x + ox, y + oy). The offset position of the + * surface is the one used for constraint testing. See + * set_constraint_adjustment. + * + * An example use case is placing a popup menu on top of a user interface + * element, while aligning the user interface element of the parent surface + * with some user interface element placed somewhere in the popup surface. + */ +static inline void +xdg_positioner_set_offset(struct xdg_positioner *xdg_positioner, int32_t x, int32_t y) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, + XDG_POSITIONER_SET_OFFSET, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, x, y); +} + +/** + * @ingroup iface_xdg_positioner + * + * When set reactive, the surface is reconstrained if the conditions used + * for constraining changed, e.g. the parent window moved. + * + * If the conditions changed and the popup was reconstrained, an + * xdg_popup.configure event is sent with updated geometry, followed by an + * xdg_surface.configure event. + */ +static inline void +xdg_positioner_set_reactive(struct xdg_positioner *xdg_positioner) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, + XDG_POSITIONER_SET_REACTIVE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0); +} + +/** + * @ingroup iface_xdg_positioner + * + * Set the parent window geometry the compositor should use when + * positioning the popup. The compositor may use this information to + * determine the future state the popup should be constrained using. If + * this doesn't match the dimension of the parent the popup is eventually + * positioned against, the behavior is undefined. + * + * The arguments are given in the surface-local coordinate space. + */ +static inline void +xdg_positioner_set_parent_size(struct xdg_positioner *xdg_positioner, int32_t parent_width, int32_t parent_height) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, + XDG_POSITIONER_SET_PARENT_SIZE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, parent_width, parent_height); +} + +/** + * @ingroup iface_xdg_positioner + * + * Set the serial of an xdg_surface.configure event this positioner will be + * used in response to. The compositor may use this information together + * with set_parent_size to determine what future state the popup should be + * constrained using. + */ +static inline void +xdg_positioner_set_parent_configure(struct xdg_positioner *xdg_positioner, uint32_t serial) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, + XDG_POSITIONER_SET_PARENT_CONFIGURE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, serial); +} + +#ifndef XDG_SURFACE_ERROR_ENUM +#define XDG_SURFACE_ERROR_ENUM +enum xdg_surface_error { + /** + * Surface was not fully constructed + */ + XDG_SURFACE_ERROR_NOT_CONSTRUCTED = 1, + /** + * Surface was already constructed + */ + XDG_SURFACE_ERROR_ALREADY_CONSTRUCTED = 2, + /** + * Attaching a buffer to an unconfigured surface + */ + XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER = 3, + /** + * Invalid serial number when acking a configure event + */ + XDG_SURFACE_ERROR_INVALID_SERIAL = 4, + /** + * Width or height was zero or negative + */ + XDG_SURFACE_ERROR_INVALID_SIZE = 5, + /** + * Surface was destroyed before its role object + */ + XDG_SURFACE_ERROR_DEFUNCT_ROLE_OBJECT = 6, +}; +#endif /* XDG_SURFACE_ERROR_ENUM */ + +/** + * @ingroup iface_xdg_surface + * @struct xdg_surface_listener + */ +struct xdg_surface_listener { + /** + * suggest a surface change + * + * The configure event marks the end of a configure sequence. A + * configure sequence is a set of one or more events configuring + * the state of the xdg_surface, including the final + * xdg_surface.configure event. + * + * Where applicable, xdg_surface surface roles will during a + * configure sequence extend this event as a latched state sent as + * events before the xdg_surface.configure event. Such events + * should be considered to make up a set of atomically applied + * configuration states, where the xdg_surface.configure commits + * the accumulated state. + * + * Clients should arrange their surface for the new states, and + * then send an ack_configure request with the serial sent in this + * configure event at some point before committing the new surface. + * + * If the client receives multiple configure events before it can + * respond to one, it is free to discard all but the last event it + * received. + * @param serial serial of the configure event + */ + void (*configure)(void *data, + struct xdg_surface *xdg_surface, + uint32_t serial); +}; + +/** + * @ingroup iface_xdg_surface + */ +static inline int +xdg_surface_add_listener(struct xdg_surface *xdg_surface, + const struct xdg_surface_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) xdg_surface, + (void (**)(void)) listener, data); +} + +#define XDG_SURFACE_DESTROY 0 +#define XDG_SURFACE_GET_TOPLEVEL 1 +#define XDG_SURFACE_GET_POPUP 2 +#define XDG_SURFACE_SET_WINDOW_GEOMETRY 3 +#define XDG_SURFACE_ACK_CONFIGURE 4 + +/** + * @ingroup iface_xdg_surface + */ +#define XDG_SURFACE_CONFIGURE_SINCE_VERSION 1 + +/** + * @ingroup iface_xdg_surface + */ +#define XDG_SURFACE_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_surface + */ +#define XDG_SURFACE_GET_TOPLEVEL_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_surface + */ +#define XDG_SURFACE_GET_POPUP_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_surface + */ +#define XDG_SURFACE_SET_WINDOW_GEOMETRY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_surface + */ +#define XDG_SURFACE_ACK_CONFIGURE_SINCE_VERSION 1 + +/** @ingroup iface_xdg_surface */ +static inline void +xdg_surface_set_user_data(struct xdg_surface *xdg_surface, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) xdg_surface, user_data); +} + +/** @ingroup iface_xdg_surface */ +static inline void * +xdg_surface_get_user_data(struct xdg_surface *xdg_surface) +{ + return wl_proxy_get_user_data((struct wl_proxy *) xdg_surface); +} + +static inline uint32_t +xdg_surface_get_version(struct xdg_surface *xdg_surface) +{ + return wl_proxy_get_version((struct wl_proxy *) xdg_surface); +} + +/** + * @ingroup iface_xdg_surface + * + * Destroy the xdg_surface object. An xdg_surface must only be destroyed + * after its role object has been destroyed, otherwise + * a defunct_role_object error is raised. + */ +static inline void +xdg_surface_destroy(struct xdg_surface *xdg_surface) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_surface, + XDG_SURFACE_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_surface), WL_MARSHAL_FLAG_DESTROY); +} + +/** + * @ingroup iface_xdg_surface + * + * This creates an xdg_toplevel object for the given xdg_surface and gives + * the associated wl_surface the xdg_toplevel role. + * + * See the documentation of xdg_toplevel for more details about what an + * xdg_toplevel is and how it is used. + */ +static inline struct xdg_toplevel * +xdg_surface_get_toplevel(struct xdg_surface *xdg_surface) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_flags((struct wl_proxy *) xdg_surface, + XDG_SURFACE_GET_TOPLEVEL, &xdg_toplevel_interface, wl_proxy_get_version((struct wl_proxy *) xdg_surface), 0, NULL); + + return (struct xdg_toplevel *) id; +} + +/** + * @ingroup iface_xdg_surface + * + * This creates an xdg_popup object for the given xdg_surface and gives + * the associated wl_surface the xdg_popup role. + * + * If null is passed as a parent, a parent surface must be specified using + * some other protocol, before committing the initial state. + * + * See the documentation of xdg_popup for more details about what an + * xdg_popup is and how it is used. + */ +static inline struct xdg_popup * +xdg_surface_get_popup(struct xdg_surface *xdg_surface, struct xdg_surface *parent, struct xdg_positioner *positioner) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_flags((struct wl_proxy *) xdg_surface, + XDG_SURFACE_GET_POPUP, &xdg_popup_interface, wl_proxy_get_version((struct wl_proxy *) xdg_surface), 0, NULL, parent, positioner); + + return (struct xdg_popup *) id; +} + +/** + * @ingroup iface_xdg_surface + * + * The window geometry of a surface is its "visible bounds" from the + * user's perspective. Client-side decorations often have invisible + * portions like drop-shadows which should be ignored for the + * purposes of aligning, placing and constraining windows. + * + * The window geometry is double-buffered state, see wl_surface.commit. + * + * When maintaining a position, the compositor should treat the (x, y) + * coordinate of the window geometry as the top left corner of the window. + * A client changing the (x, y) window geometry coordinate should in + * general not alter the position of the window. + * + * Once the window geometry of the surface is set, it is not possible to + * unset it, and it will remain the same until set_window_geometry is + * called again, even if a new subsurface or buffer is attached. + * + * If never set, the value is the full bounds of the surface, + * including any subsurfaces. This updates dynamically on every + * commit. This unset is meant for extremely simple clients. + * + * The arguments are given in the surface-local coordinate space of + * the wl_surface associated with this xdg_surface, and may extend outside + * of the wl_surface itself to mark parts of the subsurface tree as part of + * the window geometry. + * + * When applied, the effective window geometry will be the set window + * geometry clamped to the bounding rectangle of the combined + * geometry of the surface of the xdg_surface and the associated + * subsurfaces. + * + * The effective geometry will not be recalculated unless a new call to + * set_window_geometry is done and the new pending surface state is + * subsequently applied. + * + * The width and height of the effective window geometry must be + * greater than zero. Setting an invalid size will raise an + * invalid_size error. + */ +static inline void +xdg_surface_set_window_geometry(struct xdg_surface *xdg_surface, int32_t x, int32_t y, int32_t width, int32_t height) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_surface, + XDG_SURFACE_SET_WINDOW_GEOMETRY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_surface), 0, x, y, width, height); +} + +/** + * @ingroup iface_xdg_surface + * + * When a configure event is received, if a client commits the + * surface in response to the configure event, then the client + * must make an ack_configure request sometime before the commit + * request, passing along the serial of the configure event. + * + * For instance, for toplevel surfaces the compositor might use this + * information to move a surface to the top left only when the client has + * drawn itself for the maximized or fullscreen state. + * + * If the client receives multiple configure events before it + * can respond to one, it only has to ack the last configure event. + * Acking a configure event that was never sent raises an invalid_serial + * error. + * + * A client is not required to commit immediately after sending + * an ack_configure request - it may even ack_configure several times + * before its next surface commit. + * + * A client may send multiple ack_configure requests before committing, but + * only the last request sent before a commit indicates which configure + * event the client really is responding to. + * + * Sending an ack_configure request consumes the serial number sent with + * the request, as well as serial numbers sent by all configure events + * sent on this xdg_surface prior to the configure event referenced by + * the committed serial. + * + * It is an error to issue multiple ack_configure requests referencing a + * serial from the same configure event, or to issue an ack_configure + * request referencing a serial from a configure event issued before the + * event identified by the last ack_configure request for the same + * xdg_surface. Doing so will raise an invalid_serial error. + */ +static inline void +xdg_surface_ack_configure(struct xdg_surface *xdg_surface, uint32_t serial) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_surface, + XDG_SURFACE_ACK_CONFIGURE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_surface), 0, serial); +} + +#ifndef XDG_TOPLEVEL_ERROR_ENUM +#define XDG_TOPLEVEL_ERROR_ENUM +enum xdg_toplevel_error { + /** + * provided value is not a valid variant of the resize_edge enum + */ + XDG_TOPLEVEL_ERROR_INVALID_RESIZE_EDGE = 0, + /** + * invalid parent toplevel + */ + XDG_TOPLEVEL_ERROR_INVALID_PARENT = 1, + /** + * client provided an invalid min or max size + */ + XDG_TOPLEVEL_ERROR_INVALID_SIZE = 2, +}; +#endif /* XDG_TOPLEVEL_ERROR_ENUM */ + +#ifndef XDG_TOPLEVEL_RESIZE_EDGE_ENUM +#define XDG_TOPLEVEL_RESIZE_EDGE_ENUM +/** + * @ingroup iface_xdg_toplevel + * edge values for resizing + * + * These values are used to indicate which edge of a surface + * is being dragged in a resize operation. + */ +enum xdg_toplevel_resize_edge { + XDG_TOPLEVEL_RESIZE_EDGE_NONE = 0, + XDG_TOPLEVEL_RESIZE_EDGE_TOP = 1, + XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM = 2, + XDG_TOPLEVEL_RESIZE_EDGE_LEFT = 4, + XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT = 5, + XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT = 6, + XDG_TOPLEVEL_RESIZE_EDGE_RIGHT = 8, + XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT = 9, + XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT = 10, +}; +#endif /* XDG_TOPLEVEL_RESIZE_EDGE_ENUM */ + +#ifndef XDG_TOPLEVEL_STATE_ENUM +#define XDG_TOPLEVEL_STATE_ENUM +/** + * @ingroup iface_xdg_toplevel + * types of state on the surface + * + * The different state values used on the surface. This is designed for + * state values like maximized, fullscreen. It is paired with the + * configure event to ensure that both the client and the compositor + * setting the state can be synchronized. + * + * States set in this way are double-buffered, see wl_surface.commit. + */ +enum xdg_toplevel_state { + /** + * the surface is maximized + * the surface is maximized + * + * The surface is maximized. The window geometry specified in the + * configure event must be obeyed by the client, or the + * xdg_wm_base.invalid_surface_state error is raised. + * + * The client should draw without shadow or other decoration + * outside of the window geometry. + */ + XDG_TOPLEVEL_STATE_MAXIMIZED = 1, + /** + * the surface is fullscreen + * the surface is fullscreen + * + * The surface is fullscreen. The window geometry specified in + * the configure event is a maximum; the client cannot resize + * beyond it. For a surface to cover the whole fullscreened area, + * the geometry dimensions must be obeyed by the client. For more + * details, see xdg_toplevel.set_fullscreen. + */ + XDG_TOPLEVEL_STATE_FULLSCREEN = 2, + /** + * the surface is being resized + * the surface is being resized + * + * The surface is being resized. The window geometry specified in + * the configure event is a maximum; the client cannot resize + * beyond it. Clients that have aspect ratio or cell sizing + * configuration can use a smaller size, however. + */ + XDG_TOPLEVEL_STATE_RESIZING = 3, + /** + * the surface is now activated + * the surface is now activated + * + * Client window decorations should be painted as if the window + * is active. Do not assume this means that the window actually has + * keyboard or pointer focus. + */ + XDG_TOPLEVEL_STATE_ACTIVATED = 4, + /** + * the surface’s left edge is tiled + * + * The window is currently in a tiled layout and the left edge is + * considered to be adjacent to another part of the tiling grid. + * + * The client should draw without shadow or other decoration + * outside of the window geometry on the left edge. + * @since 2 + */ + XDG_TOPLEVEL_STATE_TILED_LEFT = 5, + /** + * the surface’s right edge is tiled + * + * The window is currently in a tiled layout and the right edge + * is considered to be adjacent to another part of the tiling grid. + * + * The client should draw without shadow or other decoration + * outside of the window geometry on the right edge. + * @since 2 + */ + XDG_TOPLEVEL_STATE_TILED_RIGHT = 6, + /** + * the surface’s top edge is tiled + * + * The window is currently in a tiled layout and the top edge is + * considered to be adjacent to another part of the tiling grid. + * + * The client should draw without shadow or other decoration + * outside of the window geometry on the top edge. + * @since 2 + */ + XDG_TOPLEVEL_STATE_TILED_TOP = 7, + /** + * the surface’s bottom edge is tiled + * + * The window is currently in a tiled layout and the bottom edge + * is considered to be adjacent to another part of the tiling grid. + * + * The client should draw without shadow or other decoration + * outside of the window geometry on the bottom edge. + * @since 2 + */ + XDG_TOPLEVEL_STATE_TILED_BOTTOM = 8, + /** + * surface repaint is suspended + * + * The surface is currently not ordinarily being repainted; for + * example because its content is occluded by another window, or + * its outputs are switched off due to screen locking. + * @since 6 + */ + XDG_TOPLEVEL_STATE_SUSPENDED = 9, + /** + * the surface’s left edge is constrained + * + * The left edge of the window is currently constrained, meaning + * it shouldn't attempt to resize from that edge. It can for + * example mean it's tiled next to a monitor edge on the + * constrained side of the window. + * @since 7 + */ + XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT = 10, + /** + * the surface’s right edge is constrained + * + * The right edge of the window is currently constrained, meaning + * it shouldn't attempt to resize from that edge. It can for + * example mean it's tiled next to a monitor edge on the + * constrained side of the window. + * @since 7 + */ + XDG_TOPLEVEL_STATE_CONSTRAINED_RIGHT = 11, + /** + * the surface’s top edge is constrained + * + * The top edge of the window is currently constrained, meaning + * it shouldn't attempt to resize from that edge. It can for + * example mean it's tiled next to a monitor edge on the + * constrained side of the window. + * @since 7 + */ + XDG_TOPLEVEL_STATE_CONSTRAINED_TOP = 12, + /** + * the surface’s bottom edge is tiled + * + * The bottom edge of the window is currently constrained, + * meaning it shouldn't attempt to resize from that edge. It can + * for example mean it's tiled next to a monitor edge on the + * constrained side of the window. + * @since 7 + */ + XDG_TOPLEVEL_STATE_CONSTRAINED_BOTTOM = 13, +}; +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_STATE_TILED_LEFT_SINCE_VERSION 2 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_STATE_TILED_RIGHT_SINCE_VERSION 2 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_STATE_TILED_TOP_SINCE_VERSION 2 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_STATE_TILED_BOTTOM_SINCE_VERSION 2 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION 6 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT_SINCE_VERSION 7 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_STATE_CONSTRAINED_RIGHT_SINCE_VERSION 7 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_STATE_CONSTRAINED_TOP_SINCE_VERSION 7 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_STATE_CONSTRAINED_BOTTOM_SINCE_VERSION 7 +#endif /* XDG_TOPLEVEL_STATE_ENUM */ + +#ifndef XDG_TOPLEVEL_WM_CAPABILITIES_ENUM +#define XDG_TOPLEVEL_WM_CAPABILITIES_ENUM +enum xdg_toplevel_wm_capabilities { + /** + * show_window_menu is available + */ + XDG_TOPLEVEL_WM_CAPABILITIES_WINDOW_MENU = 1, + /** + * set_maximized and unset_maximized are available + */ + XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE = 2, + /** + * set_fullscreen and unset_fullscreen are available + */ + XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN = 3, + /** + * set_minimized is available + */ + XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE = 4, +}; +#endif /* XDG_TOPLEVEL_WM_CAPABILITIES_ENUM */ + +/** + * @ingroup iface_xdg_toplevel + * @struct xdg_toplevel_listener + */ +struct xdg_toplevel_listener { + /** + * suggest a surface change + * + * This configure event asks the client to resize its toplevel + * surface or to change its state. The configured state should not + * be applied immediately. See xdg_surface.configure for details. + * + * The width and height arguments specify a hint to the window + * about how its surface should be resized in window geometry + * coordinates. See set_window_geometry. + * + * If the width or height arguments are zero, it means the client + * should decide its own window dimension. This may happen when the + * compositor needs to configure the state of the surface but + * doesn't have any information about any previous or expected + * dimension. + * + * The states listed in the event specify how the width/height + * arguments should be interpreted, and possibly how it should be + * drawn. + * + * Clients must send an ack_configure in response to this event. + * See xdg_surface.configure and xdg_surface.ack_configure for + * details. + */ + void (*configure)(void *data, + struct xdg_toplevel *xdg_toplevel, + int32_t width, + int32_t height, + struct wl_array *states); + /** + * surface wants to be closed + * + * The close event is sent by the compositor when the user wants + * the surface to be closed. This should be equivalent to the user + * clicking the close button in client-side decorations, if your + * application has any. + * + * This is only a request that the user intends to close the + * window. The client may choose to ignore this request, or show a + * dialog to ask the user to save their data, etc. + */ + void (*close)(void *data, + struct xdg_toplevel *xdg_toplevel); + /** + * recommended window geometry bounds + * + * The configure_bounds event may be sent prior to a + * xdg_toplevel.configure event to communicate the bounds a window + * geometry size is recommended to constrain to. + * + * The passed width and height are in surface coordinate space. If + * width and height are 0, it means bounds is unknown and + * equivalent to as if no configure_bounds event was ever sent for + * this surface. + * + * The bounds can for example correspond to the size of a monitor + * excluding any panels or other shell components, so that a + * surface isn't created in a way that it cannot fit. + * + * The bounds may change at any point, and in such a case, a new + * xdg_toplevel.configure_bounds will be sent, followed by + * xdg_toplevel.configure and xdg_surface.configure. + * @since 4 + */ + void (*configure_bounds)(void *data, + struct xdg_toplevel *xdg_toplevel, + int32_t width, + int32_t height); + /** + * compositor capabilities + * + * This event advertises the capabilities supported by the + * compositor. If a capability isn't supported, clients should hide + * or disable the UI elements that expose this functionality. For + * instance, if the compositor doesn't advertise support for + * minimized toplevels, a button triggering the set_minimized + * request should not be displayed. + * + * The compositor will ignore requests it doesn't support. For + * instance, a compositor which doesn't advertise support for + * minimized will ignore set_minimized requests. + * + * Compositors must send this event once before the first + * xdg_surface.configure event. When the capabilities change, + * compositors must send this event again and then send an + * xdg_surface.configure event. + * + * The configured state should not be applied immediately. See + * xdg_surface.configure for details. + * + * The capabilities are sent as an array of 32-bit unsigned + * integers in native endianness. + * @param capabilities array of 32-bit capabilities + * @since 5 + */ + void (*wm_capabilities)(void *data, + struct xdg_toplevel *xdg_toplevel, + struct wl_array *capabilities); +}; + +/** + * @ingroup iface_xdg_toplevel + */ +static inline int +xdg_toplevel_add_listener(struct xdg_toplevel *xdg_toplevel, + const struct xdg_toplevel_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) xdg_toplevel, + (void (**)(void)) listener, data); +} + +#define XDG_TOPLEVEL_DESTROY 0 +#define XDG_TOPLEVEL_SET_PARENT 1 +#define XDG_TOPLEVEL_SET_TITLE 2 +#define XDG_TOPLEVEL_SET_APP_ID 3 +#define XDG_TOPLEVEL_SHOW_WINDOW_MENU 4 +#define XDG_TOPLEVEL_MOVE 5 +#define XDG_TOPLEVEL_RESIZE 6 +#define XDG_TOPLEVEL_SET_MAX_SIZE 7 +#define XDG_TOPLEVEL_SET_MIN_SIZE 8 +#define XDG_TOPLEVEL_SET_MAXIMIZED 9 +#define XDG_TOPLEVEL_UNSET_MAXIMIZED 10 +#define XDG_TOPLEVEL_SET_FULLSCREEN 11 +#define XDG_TOPLEVEL_UNSET_FULLSCREEN 12 +#define XDG_TOPLEVEL_SET_MINIMIZED 13 + +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_CONFIGURE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_CLOSE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION 4 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION 5 + +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_PARENT_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_TITLE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_APP_ID_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SHOW_WINDOW_MENU_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_MOVE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_RESIZE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_MAX_SIZE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_MIN_SIZE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_MAXIMIZED_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_UNSET_MAXIMIZED_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_FULLSCREEN_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_UNSET_FULLSCREEN_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_MINIMIZED_SINCE_VERSION 1 + +/** @ingroup iface_xdg_toplevel */ +static inline void +xdg_toplevel_set_user_data(struct xdg_toplevel *xdg_toplevel, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) xdg_toplevel, user_data); +} + +/** @ingroup iface_xdg_toplevel */ +static inline void * +xdg_toplevel_get_user_data(struct xdg_toplevel *xdg_toplevel) +{ + return wl_proxy_get_user_data((struct wl_proxy *) xdg_toplevel); +} + +static inline uint32_t +xdg_toplevel_get_version(struct xdg_toplevel *xdg_toplevel) +{ + return wl_proxy_get_version((struct wl_proxy *) xdg_toplevel); +} + +/** + * @ingroup iface_xdg_toplevel + * + * This request destroys the role surface and unmaps the surface; + * see "Unmapping" behavior in interface section for details. + */ +static inline void +xdg_toplevel_destroy(struct xdg_toplevel *xdg_toplevel) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), WL_MARSHAL_FLAG_DESTROY); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Set the "parent" of this surface. This surface should be stacked + * above the parent surface and all other ancestor surfaces. + * + * Parent surfaces should be set on dialogs, toolboxes, or other + * "auxiliary" surfaces, so that the parent is raised when the dialog + * is raised. + * + * Setting a null parent for a child surface unsets its parent. Setting + * a null parent for a surface which currently has no parent is a no-op. + * + * Only mapped surfaces can have child surfaces. Setting a parent which + * is not mapped is equivalent to setting a null parent. If a surface + * becomes unmapped, its children's parent is set to the parent of + * the now-unmapped surface. If the now-unmapped surface has no parent, + * its children's parent is unset. If the now-unmapped surface becomes + * mapped again, its parent-child relationship is not restored. + * + * The parent toplevel must not be one of the child toplevel's + * descendants, and the parent must be different from the child toplevel, + * otherwise the invalid_parent protocol error is raised. + */ +static inline void +xdg_toplevel_set_parent(struct xdg_toplevel *xdg_toplevel, struct xdg_toplevel *parent) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_SET_PARENT, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, parent); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Set a short title for the surface. + * + * This string may be used to identify the surface in a task bar, + * window list, or other user interface elements provided by the + * compositor. + * + * The string must be encoded in UTF-8. + */ +static inline void +xdg_toplevel_set_title(struct xdg_toplevel *xdg_toplevel, const char *title) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_SET_TITLE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, title); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Set an application identifier for the surface. + * + * The app ID identifies the general class of applications to which + * the surface belongs. The compositor can use this to group multiple + * surfaces together, or to determine how to launch a new application. + * + * For D-Bus activatable applications, the app ID is used as the D-Bus + * service name. + * + * The compositor shell will try to group application surfaces together + * by their app ID. As a best practice, it is suggested to select app + * ID's that match the basename of the application's .desktop file. + * For example, "org.freedesktop.FooViewer" where the .desktop file is + * "org.freedesktop.FooViewer.desktop". + * + * Like other properties, a set_app_id request can be sent after the + * xdg_toplevel has been mapped to update the property. + * + * See the desktop-entry specification [0] for more details on + * application identifiers and how they relate to well-known D-Bus + * names and .desktop files. + * + * [0] https://standards.freedesktop.org/desktop-entry-spec/ + */ +static inline void +xdg_toplevel_set_app_id(struct xdg_toplevel *xdg_toplevel, const char *app_id) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_SET_APP_ID, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, app_id); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Clients implementing client-side decorations might want to show + * a context menu when right-clicking on the decorations, giving the + * user a menu that they can use to maximize or minimize the window. + * + * This request asks the compositor to pop up such a window menu at + * the given position, relative to the local surface coordinates of + * the parent surface. There are no guarantees as to what menu items + * the window menu contains, or even if a window menu will be drawn + * at all. + * + * This request must be used in response to some sort of user action + * like a button press, key press, or touch down event. + */ +static inline void +xdg_toplevel_show_window_menu(struct xdg_toplevel *xdg_toplevel, struct wl_seat *seat, uint32_t serial, int32_t x, int32_t y) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_SHOW_WINDOW_MENU, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, seat, serial, x, y); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Start an interactive, user-driven move of the surface. + * + * This request must be used in response to some sort of user action + * like a button press, key press, or touch down event. The passed + * serial is used to determine the type of interactive move (touch, + * pointer, etc). + * + * The server may ignore move requests depending on the state of + * the surface (e.g. fullscreen or maximized), or if the passed serial + * is no longer valid. + * + * If triggered, the surface will lose the focus of the device + * (wl_pointer, wl_touch, etc) used for the move. It is up to the + * compositor to visually indicate that the move is taking place, such as + * updating a pointer cursor, during the move. There is no guarantee + * that the device focus will return when the move is completed. + */ +static inline void +xdg_toplevel_move(struct xdg_toplevel *xdg_toplevel, struct wl_seat *seat, uint32_t serial) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_MOVE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, seat, serial); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Start a user-driven, interactive resize of the surface. + * + * This request must be used in response to some sort of user action + * like a button press, key press, or touch down event. The passed + * serial is used to determine the type of interactive resize (touch, + * pointer, etc). + * + * The server may ignore resize requests depending on the state of + * the surface (e.g. fullscreen or maximized). + * + * If triggered, the client will receive configure events with the + * "resize" state enum value and the expected sizes. See the "resize" + * enum value for more details about what is required. The client + * must also acknowledge configure events using "ack_configure". After + * the resize is completed, the client will receive another "configure" + * event without the resize state. + * + * If triggered, the surface also will lose the focus of the device + * (wl_pointer, wl_touch, etc) used for the resize. It is up to the + * compositor to visually indicate that the resize is taking place, + * such as updating a pointer cursor, during the resize. There is no + * guarantee that the device focus will return when the resize is + * completed. + * + * The edges parameter specifies how the surface should be resized, and + * is one of the values of the resize_edge enum. Values not matching + * a variant of the enum will cause the invalid_resize_edge protocol error. + * The compositor may use this information to update the surface position + * for example when dragging the top left corner. The compositor may also + * use this information to adapt its behavior, e.g. choose an appropriate + * cursor image. + */ +static inline void +xdg_toplevel_resize(struct xdg_toplevel *xdg_toplevel, struct wl_seat *seat, uint32_t serial, uint32_t edges) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_RESIZE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, seat, serial, edges); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Set a maximum size for the window. + * + * The client can specify a maximum size so that the compositor does + * not try to configure the window beyond this size. + * + * The width and height arguments are in window geometry coordinates. + * See xdg_surface.set_window_geometry. + * + * Values set in this way are double-buffered, see wl_surface.commit. + * + * The compositor can use this information to allow or disallow + * different states like maximize or fullscreen and draw accurate + * animations. + * + * Similarly, a tiling window manager may use this information to + * place and resize client windows in a more effective way. + * + * The client should not rely on the compositor to obey the maximum + * size. The compositor may decide to ignore the values set by the + * client and request a larger size. + * + * If never set, or a value of zero in the request, means that the + * client has no expected maximum size in the given dimension. + * As a result, a client wishing to reset the maximum size + * to an unspecified state can use zero for width and height in the + * request. + * + * Requesting a maximum size to be smaller than the minimum size of + * a surface is illegal and will result in an invalid_size error. + * + * The width and height must be greater than or equal to zero. Using + * strictly negative values for width or height will result in a + * invalid_size error. + */ +static inline void +xdg_toplevel_set_max_size(struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_SET_MAX_SIZE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, width, height); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Set a minimum size for the window. + * + * The client can specify a minimum size so that the compositor does + * not try to configure the window below this size. + * + * The width and height arguments are in window geometry coordinates. + * See xdg_surface.set_window_geometry. + * + * Values set in this way are double-buffered, see wl_surface.commit. + * + * The compositor can use this information to allow or disallow + * different states like maximize or fullscreen and draw accurate + * animations. + * + * Similarly, a tiling window manager may use this information to + * place and resize client windows in a more effective way. + * + * The client should not rely on the compositor to obey the minimum + * size. The compositor may decide to ignore the values set by the + * client and request a smaller size. + * + * If never set, or a value of zero in the request, means that the + * client has no expected minimum size in the given dimension. + * As a result, a client wishing to reset the minimum size + * to an unspecified state can use zero for width and height in the + * request. + * + * Requesting a minimum size to be larger than the maximum size of + * a surface is illegal and will result in an invalid_size error. + * + * The width and height must be greater than or equal to zero. Using + * strictly negative values for width and height will result in a + * invalid_size error. + */ +static inline void +xdg_toplevel_set_min_size(struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_SET_MIN_SIZE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, width, height); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Maximize the surface. + * + * After requesting that the surface should be maximized, the compositor + * will respond by emitting a configure event. Whether this configure + * actually sets the window maximized is subject to compositor policies. + * The client must then update its content, drawing in the configured + * state. The client must also acknowledge the configure when committing + * the new content (see ack_configure). + * + * It is up to the compositor to decide how and where to maximize the + * surface, for example which output and what region of the screen should + * be used. + * + * If the surface was already maximized, the compositor will still emit + * a configure event with the "maximized" state. + * + * If the surface is in a fullscreen state, this request has no direct + * effect. It may alter the state the surface is returned to when + * unmaximized unless overridden by the compositor. + */ +static inline void +xdg_toplevel_set_maximized(struct xdg_toplevel *xdg_toplevel) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_SET_MAXIMIZED, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Unmaximize the surface. + * + * After requesting that the surface should be unmaximized, the compositor + * will respond by emitting a configure event. Whether this actually + * un-maximizes the window is subject to compositor policies. + * If available and applicable, the compositor will include the window + * geometry dimensions the window had prior to being maximized in the + * configure event. The client must then update its content, drawing it in + * the configured state. The client must also acknowledge the configure + * when committing the new content (see ack_configure). + * + * It is up to the compositor to position the surface after it was + * unmaximized; usually the position the surface had before maximizing, if + * applicable. + * + * If the surface was already not maximized, the compositor will still + * emit a configure event without the "maximized" state. + * + * If the surface is in a fullscreen state, this request has no direct + * effect. It may alter the state the surface is returned to when + * unmaximized unless overridden by the compositor. + */ +static inline void +xdg_toplevel_unset_maximized(struct xdg_toplevel *xdg_toplevel) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_UNSET_MAXIMIZED, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Make the surface fullscreen. + * + * After requesting that the surface should be fullscreened, the + * compositor will respond by emitting a configure event. Whether the + * client is actually put into a fullscreen state is subject to compositor + * policies. The client must also acknowledge the configure when + * committing the new content (see ack_configure). + * + * The output passed by the request indicates the client's preference as + * to which display it should be set fullscreen on. If this value is NULL, + * it's up to the compositor to choose which display will be used to map + * this surface. + * + * If the surface doesn't cover the whole output, the compositor will + * position the surface in the center of the output and compensate with + * with border fill covering the rest of the output. The content of the + * border fill is undefined, but should be assumed to be in some way that + * attempts to blend into the surrounding area (e.g. solid black). + * + * If the fullscreened surface is not opaque, the compositor must make + * sure that other screen content not part of the same surface tree (made + * up of subsurfaces, popups or similarly coupled surfaces) are not + * visible below the fullscreened surface. + */ +static inline void +xdg_toplevel_set_fullscreen(struct xdg_toplevel *xdg_toplevel, struct wl_output *output) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_SET_FULLSCREEN, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, output); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Make the surface no longer fullscreen. + * + * After requesting that the surface should be unfullscreened, the + * compositor will respond by emitting a configure event. + * Whether this actually removes the fullscreen state of the client is + * subject to compositor policies. + * + * Making a surface unfullscreen sets states for the surface based on the following: + * * the state(s) it may have had before becoming fullscreen + * * any state(s) decided by the compositor + * * any state(s) requested by the client while the surface was fullscreen + * + * The compositor may include the previous window geometry dimensions in + * the configure event, if applicable. + * + * The client must also acknowledge the configure when committing the new + * content (see ack_configure). + */ +static inline void +xdg_toplevel_unset_fullscreen(struct xdg_toplevel *xdg_toplevel) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_UNSET_FULLSCREEN, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Request that the compositor minimize your surface. There is no + * way to know if the surface is currently minimized, nor is there + * any way to unset minimization on this surface. + * + * If you are looking to throttle redrawing when minimized, please + * instead use the wl_surface.frame event for this, as this will + * also work with live previews on windows in Alt-Tab, Expose or + * similar compositor features. + */ +static inline void +xdg_toplevel_set_minimized(struct xdg_toplevel *xdg_toplevel) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_SET_MINIMIZED, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0); +} + +#ifndef XDG_POPUP_ERROR_ENUM +#define XDG_POPUP_ERROR_ENUM +enum xdg_popup_error { + /** + * tried to grab after being mapped + */ + XDG_POPUP_ERROR_INVALID_GRAB = 0, +}; +#endif /* XDG_POPUP_ERROR_ENUM */ + +/** + * @ingroup iface_xdg_popup + * @struct xdg_popup_listener + */ +struct xdg_popup_listener { + /** + * configure the popup surface + * + * This event asks the popup surface to configure itself given + * the configuration. The configured state should not be applied + * immediately. See xdg_surface.configure for details. + * + * The x and y arguments represent the position the popup was + * placed at given the xdg_positioner rule, relative to the upper + * left corner of the window geometry of the parent surface. + * + * For version 2 or older, the configure event for an xdg_popup is + * only ever sent once for the initial configuration. Starting with + * version 3, it may be sent again if the popup is setup with an + * xdg_positioner with set_reactive requested, or in response to + * xdg_popup.reposition requests. + * @param x x position relative to parent surface window geometry + * @param y y position relative to parent surface window geometry + * @param width window geometry width + * @param height window geometry height + */ + void (*configure)(void *data, + struct xdg_popup *xdg_popup, + int32_t x, + int32_t y, + int32_t width, + int32_t height); + /** + * popup interaction is done + * + * The popup_done event is sent out when a popup is dismissed by + * the compositor. The client should destroy the xdg_popup object + * at this point. + */ + void (*popup_done)(void *data, + struct xdg_popup *xdg_popup); + /** + * signal the completion of a repositioned request + * + * The repositioned event is sent as part of a popup + * configuration sequence, together with xdg_popup.configure and + * lastly xdg_surface.configure to notify the completion of a + * reposition request. + * + * The repositioned event is to notify about the completion of a + * xdg_popup.reposition request. The token argument is the token + * passed in the xdg_popup.reposition request. + * + * Immediately after this event is emitted, xdg_popup.configure and + * xdg_surface.configure will be sent with the updated size and + * position, as well as a new configure serial. + * + * The client should optionally update the content of the popup, + * but must acknowledge the new popup configuration for the new + * position to take effect. See xdg_surface.ack_configure for + * details. + * @param token reposition request token + * @since 3 + */ + void (*repositioned)(void *data, + struct xdg_popup *xdg_popup, + uint32_t token); +}; + +/** + * @ingroup iface_xdg_popup + */ +static inline int +xdg_popup_add_listener(struct xdg_popup *xdg_popup, + const struct xdg_popup_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) xdg_popup, + (void (**)(void)) listener, data); +} + +#define XDG_POPUP_DESTROY 0 +#define XDG_POPUP_GRAB 1 +#define XDG_POPUP_REPOSITION 2 + +/** + * @ingroup iface_xdg_popup + */ +#define XDG_POPUP_CONFIGURE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_popup + */ +#define XDG_POPUP_POPUP_DONE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_popup + */ +#define XDG_POPUP_REPOSITIONED_SINCE_VERSION 3 + +/** + * @ingroup iface_xdg_popup + */ +#define XDG_POPUP_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_popup + */ +#define XDG_POPUP_GRAB_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_popup + */ +#define XDG_POPUP_REPOSITION_SINCE_VERSION 3 + +/** @ingroup iface_xdg_popup */ +static inline void +xdg_popup_set_user_data(struct xdg_popup *xdg_popup, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) xdg_popup, user_data); +} + +/** @ingroup iface_xdg_popup */ +static inline void * +xdg_popup_get_user_data(struct xdg_popup *xdg_popup) +{ + return wl_proxy_get_user_data((struct wl_proxy *) xdg_popup); +} + +static inline uint32_t +xdg_popup_get_version(struct xdg_popup *xdg_popup) +{ + return wl_proxy_get_version((struct wl_proxy *) xdg_popup); +} + +/** + * @ingroup iface_xdg_popup + * + * This destroys the popup. Explicitly destroying the xdg_popup + * object will also dismiss the popup, and unmap the surface. + * + * If this xdg_popup is not the "topmost" popup, the + * xdg_wm_base.not_the_topmost_popup protocol error will be sent. + */ +static inline void +xdg_popup_destroy(struct xdg_popup *xdg_popup) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_popup, + XDG_POPUP_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_popup), WL_MARSHAL_FLAG_DESTROY); +} + +/** + * @ingroup iface_xdg_popup + * + * This request makes the created popup take an explicit grab. An explicit + * grab will be dismissed when the user dismisses the popup, or when the + * client destroys the xdg_popup. This can be done by the user clicking + * outside the surface, using the keyboard, or even locking the screen + * through closing the lid or a timeout. + * + * If the compositor denies the grab, the popup will be immediately + * dismissed. + * + * This request must be used in response to some sort of user action like a + * button press, key press, or touch down event. The serial number of the + * event should be passed as 'serial'. + * + * The parent of a grabbing popup must either be an xdg_toplevel surface or + * another xdg_popup with an explicit grab. If the parent is another + * xdg_popup it means that the popups are nested, with this popup now being + * the topmost popup. + * + * Nested popups must be destroyed in the reverse order they were created + * in, e.g. the only popup you are allowed to destroy at all times is the + * topmost one. + * + * When compositors choose to dismiss a popup, they may dismiss every + * nested grabbing popup as well. When a compositor dismisses popups, it + * will follow the same dismissing order as required from the client. + * + * If the topmost grabbing popup is destroyed, the grab will be returned to + * the parent of the popup, if that parent previously had an explicit grab. + * + * If the parent is a grabbing popup which has already been dismissed, this + * popup will be immediately dismissed. If the parent is a popup that did + * not take an explicit grab, an error will be raised. + * + * During a popup grab, the client owning the grab will receive pointer + * and touch events for all their surfaces as normal (similar to an + * "owner-events" grab in X11 parlance), while the top most grabbing popup + * will always have keyboard focus. + */ +static inline void +xdg_popup_grab(struct xdg_popup *xdg_popup, struct wl_seat *seat, uint32_t serial) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_popup, + XDG_POPUP_GRAB, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_popup), 0, seat, serial); +} + +/** + * @ingroup iface_xdg_popup + * + * Reposition an already-mapped popup. The popup will be placed given the + * details in the passed xdg_positioner object, and a + * xdg_popup.repositioned followed by xdg_popup.configure and + * xdg_surface.configure will be emitted in response. Any parameters set + * by the previous positioner will be discarded. + * + * The passed token will be sent in the corresponding + * xdg_popup.repositioned event. The new popup position will not take + * effect until the corresponding configure event is acknowledged by the + * client. See xdg_popup.repositioned for details. The token itself is + * opaque, and has no other special meaning. + * + * If multiple reposition requests are sent, the compositor may skip all + * but the last one. + * + * If the popup is repositioned in response to a configure event for its + * parent, the client should send an xdg_positioner.set_parent_configure + * and possibly an xdg_positioner.set_parent_size request to allow the + * compositor to properly constrain the popup. + * + * If the popup is repositioned together with a parent that is being + * resized, but not in response to a configure event, the client should + * send an xdg_positioner.set_parent_size request. + */ +static inline void +xdg_popup_reposition(struct xdg_popup *xdg_popup, struct xdg_positioner *positioner, uint32_t token) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_popup, + XDG_POPUP_REPOSITION, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_popup), 0, positioner, token); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/driver/xdg-shell-v1-protocol.c b/driver/xdg-shell-v1-protocol.c new file mode 100644 index 00000000..4faed8df --- /dev/null +++ b/driver/xdg-shell-v1-protocol.c @@ -0,0 +1,184 @@ +/* Generated by wayland-scanner 1.23.1 */ + +/* + * Copyright © 2008-2013 Kristian Høgsberg + * Copyright © 2013 Rafael Antognolli + * Copyright © 2013 Jasper St. Pierre + * Copyright © 2010-2013 Intel Corporation + * Copyright © 2015-2017 Samsung Electronics Co., Ltd + * Copyright © 2015-2017 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include "wayland-util.h" + +#ifndef __has_attribute +# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif + +#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) +#define WL_PRIVATE __attribute__ ((visibility("hidden"))) +#else +#define WL_PRIVATE +#endif + +extern const struct wl_interface wl_output_interface; +extern const struct wl_interface wl_seat_interface; +extern const struct wl_interface wl_surface_interface; +extern const struct wl_interface xdg_popup_interface; +extern const struct wl_interface xdg_positioner_interface; +extern const struct wl_interface xdg_surface_interface; +extern const struct wl_interface xdg_toplevel_interface; + +static const struct wl_interface *xdg_shell_types[] = { + NULL, + NULL, + NULL, + NULL, + &xdg_positioner_interface, + &xdg_surface_interface, + &wl_surface_interface, + &xdg_toplevel_interface, + &xdg_popup_interface, + &xdg_surface_interface, + &xdg_positioner_interface, + &xdg_toplevel_interface, + &wl_seat_interface, + NULL, + NULL, + NULL, + &wl_seat_interface, + NULL, + &wl_seat_interface, + NULL, + NULL, + &wl_output_interface, + &wl_seat_interface, + NULL, + &xdg_positioner_interface, + NULL, +}; + +static const struct wl_message xdg_wm_base_requests[] = { + { "destroy", "", xdg_shell_types + 0 }, + { "create_positioner", "n", xdg_shell_types + 4 }, + { "get_xdg_surface", "no", xdg_shell_types + 5 }, + { "pong", "u", xdg_shell_types + 0 }, +}; + +static const struct wl_message xdg_wm_base_events[] = { + { "ping", "u", xdg_shell_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface xdg_wm_base_interface = { + "xdg_wm_base", 7, + 4, xdg_wm_base_requests, + 1, xdg_wm_base_events, +}; + +static const struct wl_message xdg_positioner_requests[] = { + { "destroy", "", xdg_shell_types + 0 }, + { "set_size", "ii", xdg_shell_types + 0 }, + { "set_anchor_rect", "iiii", xdg_shell_types + 0 }, + { "set_anchor", "u", xdg_shell_types + 0 }, + { "set_gravity", "u", xdg_shell_types + 0 }, + { "set_constraint_adjustment", "u", xdg_shell_types + 0 }, + { "set_offset", "ii", xdg_shell_types + 0 }, + { "set_reactive", "3", xdg_shell_types + 0 }, + { "set_parent_size", "3ii", xdg_shell_types + 0 }, + { "set_parent_configure", "3u", xdg_shell_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface xdg_positioner_interface = { + "xdg_positioner", 7, + 10, xdg_positioner_requests, + 0, NULL, +}; + +static const struct wl_message xdg_surface_requests[] = { + { "destroy", "", xdg_shell_types + 0 }, + { "get_toplevel", "n", xdg_shell_types + 7 }, + { "get_popup", "n?oo", xdg_shell_types + 8 }, + { "set_window_geometry", "iiii", xdg_shell_types + 0 }, + { "ack_configure", "u", xdg_shell_types + 0 }, +}; + +static const struct wl_message xdg_surface_events[] = { + { "configure", "u", xdg_shell_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface xdg_surface_interface = { + "xdg_surface", 7, + 5, xdg_surface_requests, + 1, xdg_surface_events, +}; + +static const struct wl_message xdg_toplevel_requests[] = { + { "destroy", "", xdg_shell_types + 0 }, + { "set_parent", "?o", xdg_shell_types + 11 }, + { "set_title", "s", xdg_shell_types + 0 }, + { "set_app_id", "s", xdg_shell_types + 0 }, + { "show_window_menu", "ouii", xdg_shell_types + 12 }, + { "move", "ou", xdg_shell_types + 16 }, + { "resize", "ouu", xdg_shell_types + 18 }, + { "set_max_size", "ii", xdg_shell_types + 0 }, + { "set_min_size", "ii", xdg_shell_types + 0 }, + { "set_maximized", "", xdg_shell_types + 0 }, + { "unset_maximized", "", xdg_shell_types + 0 }, + { "set_fullscreen", "?o", xdg_shell_types + 21 }, + { "unset_fullscreen", "", xdg_shell_types + 0 }, + { "set_minimized", "", xdg_shell_types + 0 }, +}; + +static const struct wl_message xdg_toplevel_events[] = { + { "configure", "iia", xdg_shell_types + 0 }, + { "close", "", xdg_shell_types + 0 }, + { "configure_bounds", "4ii", xdg_shell_types + 0 }, + { "wm_capabilities", "5a", xdg_shell_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface xdg_toplevel_interface = { + "xdg_toplevel", 7, + 14, xdg_toplevel_requests, + 4, xdg_toplevel_events, +}; + +static const struct wl_message xdg_popup_requests[] = { + { "destroy", "", xdg_shell_types + 0 }, + { "grab", "ou", xdg_shell_types + 22 }, + { "reposition", "3ou", xdg_shell_types + 24 }, +}; + +static const struct wl_message xdg_popup_events[] = { + { "configure", "iiii", xdg_shell_types + 0 }, + { "popup_done", "", xdg_shell_types + 0 }, + { "repositioned", "3u", xdg_shell_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface xdg_popup_interface = { + "xdg_popup", 7, + 3, xdg_popup_requests, + 3, xdg_popup_events, +}; + diff --git a/driver/xdg-shell.xml b/driver/xdg-shell.xml new file mode 100644 index 00000000..c4d4685a --- /dev/null +++ b/driver/xdg-shell.xml @@ -0,0 +1,1415 @@ + + + + + Copyright © 2008-2013 Kristian Høgsberg + Copyright © 2013 Rafael Antognolli + Copyright © 2013 Jasper St. Pierre + Copyright © 2010-2013 Intel Corporation + Copyright © 2015-2017 Samsung Electronics Co., Ltd + Copyright © 2015-2017 Red Hat Inc. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + The xdg_wm_base interface is exposed as a global object enabling clients + to turn their wl_surfaces into windows in a desktop environment. It + defines the basic functionality needed for clients and the compositor to + create windows that can be dragged, resized, maximized, etc, as well as + creating transient windows such as popup menus. + + + + + + + + + + + + + + + Destroy this xdg_wm_base object. + + Destroying a bound xdg_wm_base object while there are surfaces + still alive created by this xdg_wm_base object instance is illegal + and will result in a defunct_surfaces error. + + + + + + Create a positioner object. A positioner object is used to position + surfaces relative to some parent surface. See the interface description + and xdg_surface.get_popup for details. + + + + + + + This creates an xdg_surface for the given surface. While xdg_surface + itself is not a role, the corresponding surface may only be assigned + a role extending xdg_surface, such as xdg_toplevel or xdg_popup. It is + illegal to create an xdg_surface for a wl_surface which already has an + assigned role and this will result in a role error. + + This creates an xdg_surface for the given surface. An xdg_surface is + used as basis to define a role to a given surface, such as xdg_toplevel + or xdg_popup. It also manages functionality shared between xdg_surface + based surface roles. + + See the documentation of xdg_surface for more details about what an + xdg_surface is and how it is used. + + + + + + + + A client must respond to a ping event with a pong request or + the client may be deemed unresponsive. See xdg_wm_base.ping + and xdg_wm_base.error.unresponsive. + + + + + + + The ping event asks the client if it's still alive. Pass the + serial specified in the event back to the compositor by sending + a "pong" request back with the specified serial. See xdg_wm_base.pong. + + Compositors can use this to determine if the client is still + alive. It's unspecified what will happen if the client doesn't + respond to the ping request, or in what timeframe. Clients should + try to respond in a reasonable amount of time. The “unresponsive” + error is provided for compositors that wish to disconnect unresponsive + clients. + + A compositor is free to ping in any way it wants, but a client must + always respond to any xdg_wm_base object it created. + + + + + + + + The xdg_positioner provides a collection of rules for the placement of a + child surface relative to a parent surface. Rules can be defined to ensure + the child surface remains within the visible area's borders, and to + specify how the child surface changes its position, such as sliding along + an axis, or flipping around a rectangle. These positioner-created rules are + constrained by the requirement that a child surface must intersect with or + be at least partially adjacent to its parent surface. + + See the various requests for details about possible rules. + + At the time of the request, the compositor makes a copy of the rules + specified by the xdg_positioner. Thus, after the request is complete the + xdg_positioner object can be destroyed or reused; further changes to the + object will have no effect on previous usages. + + For an xdg_positioner object to be considered complete, it must have a + non-zero size set by set_size, and a non-zero anchor rectangle set by + set_anchor_rect. Passing an incomplete xdg_positioner object when + positioning a surface raises an invalid_positioner error. + + + + + + + + + Notify the compositor that the xdg_positioner will no longer be used. + + + + + + Set the size of the surface that is to be positioned with the positioner + object. The size is in surface-local coordinates and corresponds to the + window geometry. See xdg_surface.set_window_geometry. + + If a zero or negative size is set the invalid_input error is raised. + + + + + + + + Specify the anchor rectangle within the parent surface that the child + surface will be placed relative to. The rectangle is relative to the + window geometry as defined by xdg_surface.set_window_geometry of the + parent surface. + + When the xdg_positioner object is used to position a child surface, the + anchor rectangle may not extend outside the window geometry of the + positioned child's parent surface. + + If a negative size is set the invalid_input error is raised. + + + + + + + + + + + + + + + + + + + + + + Defines the anchor point for the anchor rectangle. The specified anchor + is used derive an anchor point that the child surface will be + positioned relative to. If a corner anchor is set (e.g. 'top_left' or + 'bottom_right'), the anchor point will be at the specified corner; + otherwise, the derived anchor point will be centered on the specified + edge, or in the center of the anchor rectangle if no edge is specified. + + + + + + + + + + + + + + + + + + + Defines in what direction a surface should be positioned, relative to + the anchor point of the parent surface. If a corner gravity is + specified (e.g. 'bottom_right' or 'top_left'), then the child surface + will be placed towards the specified gravity; otherwise, the child + surface will be centered over the anchor point on any axis that had no + gravity specified. If the gravity is not in the ‘gravity’ enum, an + invalid_input error is raised. + + + + + + + The constraint adjustment value define ways the compositor will adjust + the position of the surface, if the unadjusted position would result + in the surface being partly constrained. + + Whether a surface is considered 'constrained' is left to the compositor + to determine. For example, the surface may be partly outside the + compositor's defined 'work area', thus necessitating the child surface's + position be adjusted until it is entirely inside the work area. + + The adjustments can be combined, according to a defined precedence: 1) + Flip, 2) Slide, 3) Resize. + + + + Don't alter the surface position even if it is constrained on some + axis, for example partially outside the edge of an output. + + + + + Slide the surface along the x axis until it is no longer constrained. + + First try to slide towards the direction of the gravity on the x axis + until either the edge in the opposite direction of the gravity is + unconstrained or the edge in the direction of the gravity is + constrained. + + Then try to slide towards the opposite direction of the gravity on the + x axis until either the edge in the direction of the gravity is + unconstrained or the edge in the opposite direction of the gravity is + constrained. + + + + + Slide the surface along the y axis until it is no longer constrained. + + First try to slide towards the direction of the gravity on the y axis + until either the edge in the opposite direction of the gravity is + unconstrained or the edge in the direction of the gravity is + constrained. + + Then try to slide towards the opposite direction of the gravity on the + y axis until either the edge in the direction of the gravity is + unconstrained or the edge in the opposite direction of the gravity is + constrained. + + + + + Invert the anchor and gravity on the x axis if the surface is + constrained on the x axis. For example, if the left edge of the + surface is constrained, the gravity is 'left' and the anchor is + 'left', change the gravity to 'right' and the anchor to 'right'. + + If the adjusted position also ends up being constrained, the resulting + position of the flip_x adjustment will be the one before the + adjustment. + + + + + Invert the anchor and gravity on the y axis if the surface is + constrained on the y axis. For example, if the bottom edge of the + surface is constrained, the gravity is 'bottom' and the anchor is + 'bottom', change the gravity to 'top' and the anchor to 'top'. + + The adjusted position is calculated given the original anchor + rectangle and offset, but with the new flipped anchor and gravity + values. + + If the adjusted position also ends up being constrained, the resulting + position of the flip_y adjustment will be the one before the + adjustment. + + + + + Resize the surface horizontally so that it is completely + unconstrained. + + + + + Resize the surface vertically so that it is completely unconstrained. + + + + + + + Specify how the window should be positioned if the originally intended + position caused the surface to be constrained, meaning at least + partially outside positioning boundaries set by the compositor. The + adjustment is set by constructing a bitmask describing the adjustment to + be made when the surface is constrained on that axis. + + If no bit for one axis is set, the compositor will assume that the child + surface should not change its position on that axis when constrained. + + If more than one bit for one axis is set, the order of how adjustments + are applied is specified in the corresponding adjustment descriptions. + + The default adjustment is none. + + + + + + + Specify the surface position offset relative to the position of the + anchor on the anchor rectangle and the anchor on the surface. For + example if the anchor of the anchor rectangle is at (x, y), the surface + has the gravity bottom|right, and the offset is (ox, oy), the calculated + surface position will be (x + ox, y + oy). The offset position of the + surface is the one used for constraint testing. See + set_constraint_adjustment. + + An example use case is placing a popup menu on top of a user interface + element, while aligning the user interface element of the parent surface + with some user interface element placed somewhere in the popup surface. + + + + + + + + + + When set reactive, the surface is reconstrained if the conditions used + for constraining changed, e.g. the parent window moved. + + If the conditions changed and the popup was reconstrained, an + xdg_popup.configure event is sent with updated geometry, followed by an + xdg_surface.configure event. + + + + + + Set the parent window geometry the compositor should use when + positioning the popup. The compositor may use this information to + determine the future state the popup should be constrained using. If + this doesn't match the dimension of the parent the popup is eventually + positioned against, the behavior is undefined. + + The arguments are given in the surface-local coordinate space. + + + + + + + + Set the serial of an xdg_surface.configure event this positioner will be + used in response to. The compositor may use this information together + with set_parent_size to determine what future state the popup should be + constrained using. + + + + + + + + An interface that may be implemented by a wl_surface, for + implementations that provide a desktop-style user interface. + + It provides a base set of functionality required to construct user + interface elements requiring management by the compositor, such as + toplevel windows, menus, etc. The types of functionality are split into + xdg_surface roles. + + Creating an xdg_surface does not set the role for a wl_surface. In order + to map an xdg_surface, the client must create a role-specific object + using, e.g., get_toplevel, get_popup. The wl_surface for any given + xdg_surface can have at most one role, and may not be assigned any role + not based on xdg_surface. + + A role must be assigned before any other requests are made to the + xdg_surface object. + + The client must call wl_surface.commit on the corresponding wl_surface + for the xdg_surface state to take effect. + + Creating an xdg_surface from a wl_surface which has a buffer attached or + committed is a client error, and any attempts by a client to attach or + manipulate a buffer prior to the first xdg_surface.configure call must + also be treated as errors. + + After creating a role-specific object and setting it up (e.g. by sending + the title, app ID, size constraints, parent, etc), the client must + perform an initial commit without any buffer attached. The compositor + will reply with initial wl_surface state such as + wl_surface.preferred_buffer_scale followed by an xdg_surface.configure + event. The client must acknowledge it and is then allowed to attach a + buffer to map the surface. + + Mapping an xdg_surface-based role surface is defined as making it + possible for the surface to be shown by the compositor. Note that + a mapped surface is not guaranteed to be visible once it is mapped. + + For an xdg_surface to be mapped by the compositor, the following + conditions must be met: + (1) the client has assigned an xdg_surface-based role to the surface + (2) the client has set and committed the xdg_surface state and the + role-dependent state to the surface + (3) the client has committed a buffer to the surface + + A newly-unmapped surface is considered to have met condition (1) out + of the 3 required conditions for mapping a surface if its role surface + has not been destroyed, i.e. the client must perform the initial commit + again before attaching a buffer. + + + + + + + + + + + + + + Destroy the xdg_surface object. An xdg_surface must only be destroyed + after its role object has been destroyed, otherwise + a defunct_role_object error is raised. + + + + + + This creates an xdg_toplevel object for the given xdg_surface and gives + the associated wl_surface the xdg_toplevel role. + + See the documentation of xdg_toplevel for more details about what an + xdg_toplevel is and how it is used. + + + + + + + This creates an xdg_popup object for the given xdg_surface and gives + the associated wl_surface the xdg_popup role. + + If null is passed as a parent, a parent surface must be specified using + some other protocol, before committing the initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + + + + + + + + + The window geometry of a surface is its "visible bounds" from the + user's perspective. Client-side decorations often have invisible + portions like drop-shadows which should be ignored for the + purposes of aligning, placing and constraining windows. + + The window geometry is double-buffered state, see wl_surface.commit. + + When maintaining a position, the compositor should treat the (x, y) + coordinate of the window geometry as the top left corner of the window. + A client changing the (x, y) window geometry coordinate should in + general not alter the position of the window. + + Once the window geometry of the surface is set, it is not possible to + unset it, and it will remain the same until set_window_geometry is + called again, even if a new subsurface or buffer is attached. + + If never set, the value is the full bounds of the surface, + including any subsurfaces. This updates dynamically on every + commit. This unset is meant for extremely simple clients. + + The arguments are given in the surface-local coordinate space of + the wl_surface associated with this xdg_surface, and may extend outside + of the wl_surface itself to mark parts of the subsurface tree as part of + the window geometry. + + When applied, the effective window geometry will be the set window + geometry clamped to the bounding rectangle of the combined + geometry of the surface of the xdg_surface and the associated + subsurfaces. + + The effective geometry will not be recalculated unless a new call to + set_window_geometry is done and the new pending surface state is + subsequently applied. + + The width and height of the effective window geometry must be + greater than zero. Setting an invalid size will raise an + invalid_size error. + + + + + + + + + + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + For instance, for toplevel surfaces the compositor might use this + information to move a surface to the top left only when the client has + drawn itself for the maximized or fullscreen state. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + Acking a configure event that was never sent raises an invalid_serial + error. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + Sending an ack_configure request consumes the serial number sent with + the request, as well as serial numbers sent by all configure events + sent on this xdg_surface prior to the configure event referenced by + the committed serial. + + It is an error to issue multiple ack_configure requests referencing a + serial from the same configure event, or to issue an ack_configure + request referencing a serial from a configure event issued before the + event identified by the last ack_configure request for the same + xdg_surface. Doing so will raise an invalid_serial error. + + + + + + + The configure event marks the end of a configure sequence. A configure + sequence is a set of one or more events configuring the state of the + xdg_surface, including the final xdg_surface.configure event. + + Where applicable, xdg_surface surface roles will during a configure + sequence extend this event as a latched state sent as events before the + xdg_surface.configure event. Such events should be considered to make up + a set of atomically applied configuration states, where the + xdg_surface.configure commits the accumulated state. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + If the client receives multiple configure events before it can respond + to one, it is free to discard all but the last event it received. + + + + + + + + + This interface defines an xdg_surface role which allows a surface to, + among other things, set window-like properties such as maximize, + fullscreen, and minimize, set application-specific metadata like title and + id, and well as trigger user interactive operations such as interactive + resize and move. + + A xdg_toplevel by default is responsible for providing the full intended + visual representation of the toplevel, which depending on the window + state, may mean things like a title bar, window controls and drop shadow. + + Unmapping an xdg_toplevel means that the surface cannot be shown + by the compositor until it is explicitly mapped again. + All active operations (e.g., move, resize) are canceled and all + attributes (e.g. title, state, stacking, ...) are discarded for + an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to + the state it had right after xdg_surface.get_toplevel. The client + can re-map the toplevel by performing a commit without any buffer + attached, waiting for a configure event and handling it as usual (see + xdg_surface description). + + Attaching a null buffer to a toplevel unmaps the surface. + + + + + This request destroys the role surface and unmaps the surface; + see "Unmapping" behavior in interface section for details. + + + + + + + + + + + + Set the "parent" of this surface. This surface should be stacked + above the parent surface and all other ancestor surfaces. + + Parent surfaces should be set on dialogs, toolboxes, or other + "auxiliary" surfaces, so that the parent is raised when the dialog + is raised. + + Setting a null parent for a child surface unsets its parent. Setting + a null parent for a surface which currently has no parent is a no-op. + + Only mapped surfaces can have child surfaces. Setting a parent which + is not mapped is equivalent to setting a null parent. If a surface + becomes unmapped, its children's parent is set to the parent of + the now-unmapped surface. If the now-unmapped surface has no parent, + its children's parent is unset. If the now-unmapped surface becomes + mapped again, its parent-child relationship is not restored. + + The parent toplevel must not be one of the child toplevel's + descendants, and the parent must be different from the child toplevel, + otherwise the invalid_parent protocol error is raised. + + + + + + + Set a short title for the surface. + + This string may be used to identify the surface in a task bar, + window list, or other user interface elements provided by the + compositor. + + The string must be encoded in UTF-8. + + + + + + + Set an application identifier for the surface. + + The app ID identifies the general class of applications to which + the surface belongs. The compositor can use this to group multiple + surfaces together, or to determine how to launch a new application. + + For D-Bus activatable applications, the app ID is used as the D-Bus + service name. + + The compositor shell will try to group application surfaces together + by their app ID. As a best practice, it is suggested to select app + ID's that match the basename of the application's .desktop file. + For example, "org.freedesktop.FooViewer" where the .desktop file is + "org.freedesktop.FooViewer.desktop". + + Like other properties, a set_app_id request can be sent after the + xdg_toplevel has been mapped to update the property. + + See the desktop-entry specification [0] for more details on + application identifiers and how they relate to well-known D-Bus + names and .desktop files. + + [0] https://standards.freedesktop.org/desktop-entry-spec/ + + + + + + + Clients implementing client-side decorations might want to show + a context menu when right-clicking on the decorations, giving the + user a menu that they can use to maximize or minimize the window. + + This request asks the compositor to pop up such a window menu at + the given position, relative to the local surface coordinates of + the parent surface. There are no guarantees as to what menu items + the window menu contains, or even if a window menu will be drawn + at all. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. + + + + + + + + + + Start an interactive, user-driven move of the surface. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. The passed + serial is used to determine the type of interactive move (touch, + pointer, etc). + + The server may ignore move requests depending on the state of + the surface (e.g. fullscreen or maximized), or if the passed serial + is no longer valid. + + If triggered, the surface will lose the focus of the device + (wl_pointer, wl_touch, etc) used for the move. It is up to the + compositor to visually indicate that the move is taking place, such as + updating a pointer cursor, during the move. There is no guarantee + that the device focus will return when the move is completed. + + + + + + + + These values are used to indicate which edge of a surface + is being dragged in a resize operation. + + + + + + + + + + + + + + + Start a user-driven, interactive resize of the surface. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. The passed + serial is used to determine the type of interactive resize (touch, + pointer, etc). + + The server may ignore resize requests depending on the state of + the surface (e.g. fullscreen or maximized). + + If triggered, the client will receive configure events with the + "resize" state enum value and the expected sizes. See the "resize" + enum value for more details about what is required. The client + must also acknowledge configure events using "ack_configure". After + the resize is completed, the client will receive another "configure" + event without the resize state. + + If triggered, the surface also will lose the focus of the device + (wl_pointer, wl_touch, etc) used for the resize. It is up to the + compositor to visually indicate that the resize is taking place, + such as updating a pointer cursor, during the resize. There is no + guarantee that the device focus will return when the resize is + completed. + + The edges parameter specifies how the surface should be resized, and + is one of the values of the resize_edge enum. Values not matching + a variant of the enum will cause the invalid_resize_edge protocol error. + The compositor may use this information to update the surface position + for example when dragging the top left corner. The compositor may also + use this information to adapt its behavior, e.g. choose an appropriate + cursor image. + + + + + + + + + The different state values used on the surface. This is designed for + state values like maximized, fullscreen. It is paired with the + configure event to ensure that both the client and the compositor + setting the state can be synchronized. + + States set in this way are double-buffered, see wl_surface.commit. + + + + The surface is maximized. The window geometry specified in the configure + event must be obeyed by the client, or the xdg_wm_base.invalid_surface_state + error is raised. + + The client should draw without shadow or other + decoration outside of the window geometry. + + + + + The surface is fullscreen. The window geometry specified in the + configure event is a maximum; the client cannot resize beyond it. For + a surface to cover the whole fullscreened area, the geometry + dimensions must be obeyed by the client. For more details, see + xdg_toplevel.set_fullscreen. + + + + + The surface is being resized. The window geometry specified in the + configure event is a maximum; the client cannot resize beyond it. + Clients that have aspect ratio or cell sizing configuration can use + a smaller size, however. + + + + + Client window decorations should be painted as if the window is + active. Do not assume this means that the window actually has + keyboard or pointer focus. + + + + + The window is currently in a tiled layout and the left edge is + considered to be adjacent to another part of the tiling grid. + + The client should draw without shadow or other decoration outside of + the window geometry on the left edge. + + + + + The window is currently in a tiled layout and the right edge is + considered to be adjacent to another part of the tiling grid. + + The client should draw without shadow or other decoration outside of + the window geometry on the right edge. + + + + + The window is currently in a tiled layout and the top edge is + considered to be adjacent to another part of the tiling grid. + + The client should draw without shadow or other decoration outside of + the window geometry on the top edge. + + + + + The window is currently in a tiled layout and the bottom edge is + considered to be adjacent to another part of the tiling grid. + + The client should draw without shadow or other decoration outside of + the window geometry on the bottom edge. + + + + + The surface is currently not ordinarily being repainted; for + example because its content is occluded by another window, or its + outputs are switched off due to screen locking. + + + + + The left edge of the window is currently constrained, meaning it + shouldn't attempt to resize from that edge. It can for example mean + it's tiled next to a monitor edge on the constrained side of the + window. + + + + + The right edge of the window is currently constrained, meaning it + shouldn't attempt to resize from that edge. It can for example mean + it's tiled next to a monitor edge on the constrained side of the + window. + + + + + The top edge of the window is currently constrained, meaning it + shouldn't attempt to resize from that edge. It can for example mean + it's tiled next to a monitor edge on the constrained side of the + window. + + + + + The bottom edge of the window is currently constrained, meaning it + shouldn't attempt to resize from that edge. It can for example mean + it's tiled next to a monitor edge on the constrained side of the + window. + + + + + + + Set a maximum size for the window. + + The client can specify a maximum size so that the compositor does + not try to configure the window beyond this size. + + The width and height arguments are in window geometry coordinates. + See xdg_surface.set_window_geometry. + + Values set in this way are double-buffered, see wl_surface.commit. + + The compositor can use this information to allow or disallow + different states like maximize or fullscreen and draw accurate + animations. + + Similarly, a tiling window manager may use this information to + place and resize client windows in a more effective way. + + The client should not rely on the compositor to obey the maximum + size. The compositor may decide to ignore the values set by the + client and request a larger size. + + If never set, or a value of zero in the request, means that the + client has no expected maximum size in the given dimension. + As a result, a client wishing to reset the maximum size + to an unspecified state can use zero for width and height in the + request. + + Requesting a maximum size to be smaller than the minimum size of + a surface is illegal and will result in an invalid_size error. + + The width and height must be greater than or equal to zero. Using + strictly negative values for width or height will result in a + invalid_size error. + + + + + + + + Set a minimum size for the window. + + The client can specify a minimum size so that the compositor does + not try to configure the window below this size. + + The width and height arguments are in window geometry coordinates. + See xdg_surface.set_window_geometry. + + Values set in this way are double-buffered, see wl_surface.commit. + + The compositor can use this information to allow or disallow + different states like maximize or fullscreen and draw accurate + animations. + + Similarly, a tiling window manager may use this information to + place and resize client windows in a more effective way. + + The client should not rely on the compositor to obey the minimum + size. The compositor may decide to ignore the values set by the + client and request a smaller size. + + If never set, or a value of zero in the request, means that the + client has no expected minimum size in the given dimension. + As a result, a client wishing to reset the minimum size + to an unspecified state can use zero for width and height in the + request. + + Requesting a minimum size to be larger than the maximum size of + a surface is illegal and will result in an invalid_size error. + + The width and height must be greater than or equal to zero. Using + strictly negative values for width and height will result in a + invalid_size error. + + + + + + + + Maximize the surface. + + After requesting that the surface should be maximized, the compositor + will respond by emitting a configure event. Whether this configure + actually sets the window maximized is subject to compositor policies. + The client must then update its content, drawing in the configured + state. The client must also acknowledge the configure when committing + the new content (see ack_configure). + + It is up to the compositor to decide how and where to maximize the + surface, for example which output and what region of the screen should + be used. + + If the surface was already maximized, the compositor will still emit + a configure event with the "maximized" state. + + If the surface is in a fullscreen state, this request has no direct + effect. It may alter the state the surface is returned to when + unmaximized unless overridden by the compositor. + + + + + + Unmaximize the surface. + + After requesting that the surface should be unmaximized, the compositor + will respond by emitting a configure event. Whether this actually + un-maximizes the window is subject to compositor policies. + If available and applicable, the compositor will include the window + geometry dimensions the window had prior to being maximized in the + configure event. The client must then update its content, drawing it in + the configured state. The client must also acknowledge the configure + when committing the new content (see ack_configure). + + It is up to the compositor to position the surface after it was + unmaximized; usually the position the surface had before maximizing, if + applicable. + + If the surface was already not maximized, the compositor will still + emit a configure event without the "maximized" state. + + If the surface is in a fullscreen state, this request has no direct + effect. It may alter the state the surface is returned to when + unmaximized unless overridden by the compositor. + + + + + + Make the surface fullscreen. + + After requesting that the surface should be fullscreened, the + compositor will respond by emitting a configure event. Whether the + client is actually put into a fullscreen state is subject to compositor + policies. The client must also acknowledge the configure when + committing the new content (see ack_configure). + + The output passed by the request indicates the client's preference as + to which display it should be set fullscreen on. If this value is NULL, + it's up to the compositor to choose which display will be used to map + this surface. + + If the surface doesn't cover the whole output, the compositor will + position the surface in the center of the output and compensate with + with border fill covering the rest of the output. The content of the + border fill is undefined, but should be assumed to be in some way that + attempts to blend into the surrounding area (e.g. solid black). + + If the fullscreened surface is not opaque, the compositor must make + sure that other screen content not part of the same surface tree (made + up of subsurfaces, popups or similarly coupled surfaces) are not + visible below the fullscreened surface. + + + + + + + Make the surface no longer fullscreen. + + After requesting that the surface should be unfullscreened, the + compositor will respond by emitting a configure event. + Whether this actually removes the fullscreen state of the client is + subject to compositor policies. + + Making a surface unfullscreen sets states for the surface based on the following: + * the state(s) it may have had before becoming fullscreen + * any state(s) decided by the compositor + * any state(s) requested by the client while the surface was fullscreen + + The compositor may include the previous window geometry dimensions in + the configure event, if applicable. + + The client must also acknowledge the configure when committing the new + content (see ack_configure). + + + + + + Request that the compositor minimize your surface. There is no + way to know if the surface is currently minimized, nor is there + any way to unset minimization on this surface. + + If you are looking to throttle redrawing when minimized, please + instead use the wl_surface.frame event for this, as this will + also work with live previews on windows in Alt-Tab, Expose or + similar compositor features. + + + + + + This configure event asks the client to resize its toplevel surface or + to change its state. The configured state should not be applied + immediately. See xdg_surface.configure for details. + + The width and height arguments specify a hint to the window + about how its surface should be resized in window geometry + coordinates. See set_window_geometry. + + If the width or height arguments are zero, it means the client + should decide its own window dimension. This may happen when the + compositor needs to configure the state of the surface but doesn't + have any information about any previous or expected dimension. + + The states listed in the event specify how the width/height + arguments should be interpreted, and possibly how it should be + drawn. + + Clients must send an ack_configure in response to this event. See + xdg_surface.configure and xdg_surface.ack_configure for details. + + + + + + + + + The close event is sent by the compositor when the user + wants the surface to be closed. This should be equivalent to + the user clicking the close button in client-side decorations, + if your application has any. + + This is only a request that the user intends to close the + window. The client may choose to ignore this request, or show + a dialog to ask the user to save their data, etc. + + + + + + + + The configure_bounds event may be sent prior to a xdg_toplevel.configure + event to communicate the bounds a window geometry size is recommended + to constrain to. + + The passed width and height are in surface coordinate space. If width + and height are 0, it means bounds is unknown and equivalent to as if no + configure_bounds event was ever sent for this surface. + + The bounds can for example correspond to the size of a monitor excluding + any panels or other shell components, so that a surface isn't created in + a way that it cannot fit. + + The bounds may change at any point, and in such a case, a new + xdg_toplevel.configure_bounds will be sent, followed by + xdg_toplevel.configure and xdg_surface.configure. + + + + + + + + + + + + + + + + + This event advertises the capabilities supported by the compositor. If + a capability isn't supported, clients should hide or disable the UI + elements that expose this functionality. For instance, if the + compositor doesn't advertise support for minimized toplevels, a button + triggering the set_minimized request should not be displayed. + + The compositor will ignore requests it doesn't support. For instance, + a compositor which doesn't advertise support for minimized will ignore + set_minimized requests. + + Compositors must send this event once before the first + xdg_surface.configure event. When the capabilities change, compositors + must send this event again and then send an xdg_surface.configure + event. + + The configured state should not be applied immediately. See + xdg_surface.configure for details. + + The capabilities are sent as an array of 32-bit unsigned integers in + native endianness. + + + + + + + + A popup surface is a short-lived, temporary surface. It can be used to + implement for example menus, popovers, tooltips and other similar user + interface concepts. + + A popup can be made to take an explicit grab. See xdg_popup.grab for + details. + + When the popup is dismissed, a popup_done event will be sent out, and at + the same time the surface will be unmapped. See the xdg_popup.popup_done + event for details. + + Explicitly destroying the xdg_popup object will also dismiss the popup and + unmap the surface. Clients that want to dismiss the popup when another + surface of their own is clicked should dismiss the popup using the destroy + request. + + A newly created xdg_popup will be stacked on top of all previously created + xdg_popup surfaces associated with the same xdg_toplevel. + + The parent of an xdg_popup must be mapped (see the xdg_surface + description) before the xdg_popup itself. + + The client must call wl_surface.commit on the corresponding wl_surface + for the xdg_popup state to take effect. + + + + + + + + + This destroys the popup. Explicitly destroying the xdg_popup + object will also dismiss the popup, and unmap the surface. + + If this xdg_popup is not the "topmost" popup, the + xdg_wm_base.not_the_topmost_popup protocol error will be sent. + + + + + + This request makes the created popup take an explicit grab. An explicit + grab will be dismissed when the user dismisses the popup, or when the + client destroys the xdg_popup. This can be done by the user clicking + outside the surface, using the keyboard, or even locking the screen + through closing the lid or a timeout. + + If the compositor denies the grab, the popup will be immediately + dismissed. + + This request must be used in response to some sort of user action like a + button press, key press, or touch down event. The serial number of the + event should be passed as 'serial'. + + The parent of a grabbing popup must either be an xdg_toplevel surface or + another xdg_popup with an explicit grab. If the parent is another + xdg_popup it means that the popups are nested, with this popup now being + the topmost popup. + + Nested popups must be destroyed in the reverse order they were created + in, e.g. the only popup you are allowed to destroy at all times is the + topmost one. + + When compositors choose to dismiss a popup, they may dismiss every + nested grabbing popup as well. When a compositor dismisses popups, it + will follow the same dismissing order as required from the client. + + If the topmost grabbing popup is destroyed, the grab will be returned to + the parent of the popup, if that parent previously had an explicit grab. + + If the parent is a grabbing popup which has already been dismissed, this + popup will be immediately dismissed. If the parent is a popup that did + not take an explicit grab, an error will be raised. + + During a popup grab, the client owning the grab will receive pointer + and touch events for all their surfaces as normal (similar to an + "owner-events" grab in X11 parlance), while the top most grabbing popup + will always have keyboard focus. + + + + + + + + This event asks the popup surface to configure itself given the + configuration. The configured state should not be applied immediately. + See xdg_surface.configure for details. + + The x and y arguments represent the position the popup was placed at + given the xdg_positioner rule, relative to the upper left corner of the + window geometry of the parent surface. + + For version 2 or older, the configure event for an xdg_popup is only + ever sent once for the initial configuration. Starting with version 3, + it may be sent again if the popup is setup with an xdg_positioner with + set_reactive requested, or in response to xdg_popup.reposition requests. + + + + + + + + + + The popup_done event is sent out when a popup is dismissed by the + compositor. The client should destroy the xdg_popup object at this + point. + + + + + + + + Reposition an already-mapped popup. The popup will be placed given the + details in the passed xdg_positioner object, and a + xdg_popup.repositioned followed by xdg_popup.configure and + xdg_surface.configure will be emitted in response. Any parameters set + by the previous positioner will be discarded. + + The passed token will be sent in the corresponding + xdg_popup.repositioned event. The new popup position will not take + effect until the corresponding configure event is acknowledged by the + client. See xdg_popup.repositioned for details. The token itself is + opaque, and has no other special meaning. + + If multiple reposition requests are sent, the compositor may skip all + but the last one. + + If the popup is repositioned in response to a configure event for its + parent, the client should send an xdg_positioner.set_parent_configure + and possibly an xdg_positioner.set_parent_size request to allow the + compositor to properly constrain the popup. + + If the popup is repositioned together with a parent that is being + resized, but not in response to a configure event, the client should + send an xdg_positioner.set_parent_size request. + + + + + + + + The repositioned event is sent as part of a popup configuration + sequence, together with xdg_popup.configure and lastly + xdg_surface.configure to notify the completion of a reposition request. + + The repositioned event is to notify about the completion of a + xdg_popup.reposition request. The token argument is the token passed + in the xdg_popup.reposition request. + + Immediately after this event is emitted, xdg_popup.configure and + xdg_surface.configure will be sent with the updated size and position, + as well as a new configure serial. + + The client should optionally update the content of the popup, but must + acknowledge the new popup configuration for the new position to take + effect. See xdg_surface.ack_configure for details. + + + + + + diff --git a/driver/xinput.c b/driver/xinput.c index f865f429..597ad57e 100644 --- a/driver/xinput.c +++ b/driver/xinput.c @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright © 1991-2022 Jamie Zawinski +/* xscreensaver, Copyright © 1991-2025 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 @@ -269,7 +269,8 @@ xinput_event_to_xlib (int evtype, XIDeviceEvent *in, XEvent *out) static void -print_kbd_event (XKeyEvent *xkey, XComposeStatus *compose, Bool x11_p) +print_kbd_event (XKeyEvent *xkey, XComposeStatus *compose, XIC ic, + Bool x11_p, const char *desc) { if (debug_p) /* Passwords show up in plaintext! */ { @@ -279,6 +280,40 @@ print_kbd_event (XKeyEvent *xkey, XComposeStatus *compose, Bool x11_p) int n = XLookupString (xkey, c, sizeof(c)-1, &keysym, compose); const char *ks = keysym ? XKeysymToString (keysym) : "NULL"; c[n] = 0; + + if (ic && xkey->type == KeyPress) + { + char c2[sizeof(c)]; + Status s = 0; + KeySym keysym2 = 0; + n = Xutf8LookupString (ic, (XKeyPressedEvent *) xkey, + c2, sizeof(c2)-1, &keysym2, &s); + c2[n] = 0; + + switch (s) { + case XLookupChars: /* Set 'c2' to a UTF8 string */ + if (*c2) + strcpy (c, c2); + break; + case XLookupKeySym: /* Set 'keysym2' but not 'c2' */ + if (keysym2) + keysym = keysym2; + break; + case XLookupBoth: /* Set 'keysym2' and 'c2' */ + if (keysym2) + keysym = keysym2; + if (*c2) + strcpy (c, c2); + break; + case XLookupNone: /* No input yet */ + case XBufferOverflow: /* 'c2' was too small */ + break; + default: + abort(); + break; + } + } + if (*c == '\n') strcpy (c, "\\n"); else if (*c == '\r') strcpy (c, "\\r"); else if (*c == '\t') strcpy (c, "\\t"); @@ -295,7 +330,7 @@ print_kbd_event (XKeyEvent *xkey, XComposeStatus *compose, Bool x11_p) if (*mods) mods++; if (!*mods) strcat (mods, "0"); - fprintf (stderr, "%s: %s 0x%02X %s %s \"%s\"\n", blurb(), + fprintf (stderr, "%s: %s 0x%02X %s %s \"%s\"%s\n", blurb(), (x11_p ? (xkey->type == KeyPress ? "X11 KeyPress " @@ -303,7 +338,8 @@ print_kbd_event (XKeyEvent *xkey, XComposeStatus *compose, Bool x11_p) : (xkey->type == KeyPress ? "XI_RawKeyPress " : "XI_RawKeyRelease")), - xkey->keycode, mods, ks, c); + xkey->keycode, mods, ks, c, + (desc ? desc : "")); } else /* Log only that the KeyPress happened. */ { @@ -314,7 +350,7 @@ print_kbd_event (XKeyEvent *xkey, XComposeStatus *compose, Bool x11_p) void -print_xinput_event (Display *dpy, XEvent *xev, const char *desc) +print_xinput_event (Display *dpy, XEvent *xev, XIC ic, const char *desc) { XIRawEvent *re; @@ -323,7 +359,7 @@ print_xinput_event (Display *dpy, XEvent *xev, const char *desc) case KeyRelease: { static XComposeStatus compose = { 0, }; - print_kbd_event (&xev->xkey, &compose, True); + print_kbd_event (&xev->xkey, &compose, ic, True, desc); } break; @@ -368,7 +404,7 @@ print_xinput_event (Display *dpy, XEvent *xev, const char *desc) else { static XComposeStatus compose = { 0, }; - print_kbd_event (&ev2.xkey, &compose, False); + print_kbd_event (&ev2.xkey, &compose, ic, False, desc); } break; } diff --git a/driver/xinput.h b/driver/xinput.h index 75ef2753..92f3a8e6 100644 --- a/driver/xinput.h +++ b/driver/xinput.h @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright © 1991-2022 Jamie Zawinski +/* xscreensaver, Copyright © 1991-2025 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 @@ -14,6 +14,6 @@ extern Bool init_xinput (Display *dpy, int *opcode_ret); extern Bool xinput_event_to_xlib (int evtype, XIDeviceEvent *in, XEvent *out); -extern void print_xinput_event (Display *, XEvent *, const char *desc); +extern void print_xinput_event (Display *, XEvent *, XIC, const char *desc); #endif /* __XSCREENSAVER_XINPUT_H__ */ diff --git a/driver/xscreensaver-command.c b/driver/xscreensaver-command.c index 1e09ba89..93330f11 100644 --- a/driver/xscreensaver-command.c +++ b/driver/xscreensaver-command.c @@ -1,4 +1,4 @@ -/* xscreensaver-command, Copyright © 1991-2023 Jamie Zawinski +/* xscreensaver-command, Copyright © 1991-2025 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 @@ -245,6 +245,16 @@ main (int argc, char **argv) progname, dpyname); } + /* Make sure the X11 socket doesn't get allocated to stderr: >&- 2>&-. */ + { + int fd0 = open ("/dev/null", O_RDWR); + int fd1 = open ("/dev/null", O_RDWR); + int fd2 = open ("/dev/null", O_RDWR); + if (fd0 > 2) close (fd0); + if (fd1 > 2) close (fd1); + if (fd2 > 2) close (fd2); + } + dpy = XOpenDisplay (dpyname); if (!dpy) { @@ -261,15 +271,20 @@ main (int argc, char **argv) exit (i); } +# if 0 if (*cmd == XA_ACTIVATE || *cmd == XA_LOCK || *cmd == XA_SUSPEND || *cmd == XA_NEXT || *cmd == XA_PREV || *cmd == XA_SELECT) /* People never guess that KeyRelease deactivates the screen saver too, so if we're issuing an activation command, wait a second. No need to do this if stdin is not a tty, meaning we're not being run from the command line. + + This isn't necessary as of 6.0, since the daemon ignores all user + activity for ~2 seconds after a ClientMessage activation. */ if (isatty(0)) sleep (1); +# endif i = xscreensaver_command (dpy, *cmd, arg, verbose_p, NULL); if (i < 0) exit (i); @@ -302,6 +317,7 @@ watch (Display *dpy) unsigned long nitems, bytesafter; unsigned char *dataP = 0; + /* XA_SCREENSAVER_STATUS format documented in windows.c. */ if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */ XA_SCREENSAVER_STATUS, @@ -309,41 +325,34 @@ watch (Display *dpy) &type, &format, &nitems, &bytesafter, &dataP) == Success - && type - && dataP) + && type == XA_INTEGER + && nitems >= 3 + && dataP) { - time_t tt; + PROP32 *data = (PROP32 *) dataP; + time_t tt = (time_t) /* 64 bit time_t */ + ((((unsigned long) data[1] & 0xFFFFFFFFL) << 32) | + ((unsigned long) data[2] & 0xFFFFFFFFL)); char *s; Bool changed = False; Bool running = False; - PROP32 *data = (PROP32 *) dataP; - - if (type != XA_INTEGER || nitems < 3) - { - STATUS_LOSE: - if (last) XFree (last); - if (data) XFree (data); - fprintf (stderr, "%s: bad status format on root window\n", - progname); - return -1; - } - - tt = (time_t) data[1]; - if (tt <= (time_t) 666000000L) /* early 1991 */ - goto STATUS_LOSE; + Atom state = data[0]; s = ctime(&tt); if (s[strlen(s)-1] == '\n') s[strlen(s)-1] = 0; - if (!last || data[0] != last[0]) + /* Do not print changes to password dialog presence. */ + if (state == XA_AUTH) state = XA_LOCK; + + if (!last || state != last[0]) { /* State changed. */ - if (data[0] == XA_BLANK) + if (state == XA_BLANK) printf ("BLANK %s\n", s); - else if (data[0] == XA_LOCK) + else if (state == XA_LOCK) printf ("LOCK %s\n", s); - else if (data[0] == 0) + else if (state == 0) printf ("UNBLANK %s\n", s); else goto STATUS_LOSE; @@ -365,9 +374,10 @@ watch (Display *dpy) if (running && changed) { + int off = 3; int i; fprintf (stdout, "RUN"); - for (i = 2; i < nitems; i++) + for (i = off; i < nitems; i++) fprintf (stdout, " %d", (int) data[i]); fprintf (stdout, "\n"); } @@ -379,10 +389,12 @@ watch (Display *dpy) } else { - if (last) XFree (last); - if (dataP) XFree (dataP); + STATUS_LOSE: + fflush (stdout); fprintf (stderr, "%s: no saver status on root window\n", progname); + if (last) XFree (last); + if (dataP) XFree (dataP); return -1; } } diff --git a/driver/xscreensaver-gfx.c b/driver/xscreensaver-gfx.c index 870423c2..b6e6c6ab 100644 --- a/driver/xscreensaver-gfx.c +++ b/driver/xscreensaver-gfx.c @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright © 1991-2022 Jamie Zawinski +/* xscreensaver, Copyright © 1991-2025 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 @@ -141,13 +141,25 @@ saver_ehandler (Display *dpy, XErrorEvent *error) static void connect_to_server (saver_info *si) { + saver_preferences *p = &si->prefs; Widget toplevel_shell; Window daemon_window; XrmOptionDescRec options; - char *p; + char *name; int ac = 1; char *av[] = { "xscreensaver" }; /* For Xt and Xrm purposes */ + /* Make sure the X11 socket doesn't get allocated to stderr: >&- 2>&-. + Parent "xscreensaver" already did this, but let's make sure. */ + { + int fd0 = open ("/dev/null", O_RDWR); + int fd1 = open ("/dev/null", O_RDWR); + int fd2 = open ("/dev/null", O_RDWR); + if (fd0 > 2) close (fd0); + if (fd1 > 2) close (fd1); + if (fd2 > 2) close (fd2); + } + XSetErrorHandler (saver_ehandler); toplevel_shell = XtAppInitialize (&si->app, progclass, &options, 0, @@ -155,7 +167,7 @@ connect_to_server (saver_info *si) si->dpy = XtDisplay (toplevel_shell); si->prefs.db = XtDatabase (si->dpy); - XtGetApplicationNameAndClass (si->dpy, &p, &progclass); + XtGetApplicationNameAndClass (si->dpy, &name, &progclass); db = si->prefs.db; /* resources.c needs this */ @@ -168,15 +180,62 @@ connect_to_server (saver_info *si) daemon_window = find_screensaver_window (si->dpy, 0); if (daemon_window) { + Window root = RootWindow (si->dpy, 0); /* always screen 0 */ XWindowAttributes xgwa; XGetWindowAttributes (si->dpy, daemon_window, &xgwa); XSelectInput (si->dpy, daemon_window, xgwa.your_event_mask | PropertyChangeMask); + + /* For tracking changes to XA_SCREENSAVER_STATUS on the root. */ + XGetWindowAttributes (si->dpy, root, &xgwa); + XSelectInput (si->dpy, root, + xgwa.your_event_mask | PropertyChangeMask); } else { - fprintf (stderr, "%s: xscreensaver does not seem to be running!\n", - blurb()); + if (p->verbose_p || + !(getenv ("WAYLAND_DISPLAY") || getenv ("WAYLAND_SOCKET"))) + fprintf (stderr, "%s: xscreensaver does not seem to be running!\n", + blurb()); + + /* Under normal circumstances, that window should have been created + by the "xscreensaver" process. But if for some reason someone + has run "xscreensaver-gfx" directly (Wayland?) we need this window + to exist for ClientMessages to be receivable. So let's make one. + */ + XClassHint class_hints; + XSetWindowAttributes attrs; + unsigned long attrmask = 0; + pid_t pid = getpid(); + char *id; + const char *version_number = XSCREENSAVER_VERSION; + + class_hints.res_name = (char *) progname; /* not const? */ + class_hints.res_class = "XScreenSaver"; + id = (char *) malloc (20); + sprintf (id, "%lu", (unsigned long) pid); + + attrmask = CWOverrideRedirect | CWEventMask; + attrs.override_redirect = True; + attrs.event_mask = PropertyChangeMask; + + daemon_window = XCreateWindow (si->dpy, RootWindow (si->dpy, 0), + 0, 0, 1, 1, 0, + DefaultDepth (si->dpy, 0), InputOutput, + DefaultVisual (si->dpy, 0), attrmask, &attrs); + XStoreName (si->dpy, daemon_window, "XScreenSaver GFX"); + XSetClassHint (si->dpy, daemon_window, &class_hints); + XChangeProperty (si->dpy, daemon_window, XA_WM_COMMAND, XA_STRING, + 8, PropModeReplace, (unsigned char *) progname, + strlen (progname)); + XChangeProperty (si->dpy, daemon_window, XA_SCREENSAVER_VERSION, XA_STRING, + 8, PropModeReplace, (unsigned char *) version_number, + strlen (version_number)); + XChangeProperty (si->dpy, daemon_window, XA_SCREENSAVER_ID, XA_STRING, + 8, PropModeReplace, (unsigned char *) id, strlen (id)); + free (id); + + si->all_clientmessages_p = True; } } @@ -228,27 +287,69 @@ read_status_prop (saver_info *si) ssi->current_hack = -1; } - XGetWindowProperty (si->dpy, w, XA_SCREENSAVER_STATUS, - 0, 999, False, XA_INTEGER, &type, &format, &nitems, - &bytesafter, &dataP); - if (dataP && type == XA_INTEGER && nitems >= 3) + /* XA_SCREENSAVER_STATUS format documented in windows.c. */ + if (XGetWindowProperty (si->dpy, w, + XA_SCREENSAVER_STATUS, + 0, 999, False, XA_INTEGER, + &type, &format, &nitems, &bytesafter, + &dataP) + == Success + && type == XA_INTEGER + && nitems >= 3 + && dataP) { - for (i = 2; i < nitems; i++) + PROP32 *data = (PROP32 *) dataP; + int off = 3; + for (i = off; i < nitems; i++) { - int j = i - 2; + int j = i - off; if (j < si->nscreens) { saver_screen_info *ssi = &si->screens[j]; - int n = ((PROP32 *) dataP)[i]; - ssi->current_hack = n-1; /* 1-based */ + int n = data[i]; + ssi->current_hack = n-1; /* 1-based */ } } } + if (dataP) XFree (dataP); } +/* When we get a PropertyChange event on XA_SCREENSAVER_STATUS, note + whether the auth dialog is currently posted. + */ +static void +status_prop_changed (saver_info *si) +{ + Window w = RootWindow (si->dpy, 0); /* always screen 0 */ + Atom type; + unsigned char *dataP = 0; + int format; + unsigned long nitems, bytesafter; + Bool oauth_p = si->auth_p; + + if (XGetWindowProperty (si->dpy, w, + XA_SCREENSAVER_STATUS, + 0, 999, False, XA_INTEGER, + &type, &format, &nitems, &bytesafter, + &dataP) + == Success + && type == XA_INTEGER + && nitems >= 3 + && dataP) + { + PROP32 *data = (PROP32 *) dataP; + si->auth_p = (data[0] == XA_AUTH); + if (si->auth_p != oauth_p) /* Faster watchdog while authenticating */ + reset_watchdog_timer (si); + } + else + si->auth_p = False; +} + + /* Processing ClientMessage events. Both xscreensaver and xscreensaver-gfx handle these; some are handled exclusively by one program or another, and a couple (next, prev) are @@ -312,6 +413,14 @@ handle_clientmessage (saver_info *si, XEvent *xev) si->selection_mode = which; goto CYCLE; } + else if (si->all_clientmessages_p) + { + /* The xscreensaver daemon is not running, so return an error response + for unhandled ClientMessages so that xscreensaver-command doesn't + have to time out waiting for a response that will not arrive. */ + clientmessage_response (dpy, xev, False, + "xscreensaver daemon not running"); + } else { /* All other ClientMessages are handled by xscreensaver rather than @@ -356,6 +465,15 @@ main_loop (saver_info *si, Bool init_p) if (p->verbose_p) describe_monitor_layout (si->monitor_layout); + /* The screen blanked (will blank) at xscreensaver-gfx's launch time. + The last user activity time is presumed to be 'timeout' before that. + Unless the command line included --next, --prev, --demo, etc. + */ + si->blank_time = time ((time_t *) 0); + si->activity_time = (si->selection_mode == 0 + ? si->blank_time - p->timeout / 1000 + : si->blank_time); + initialize_screensaver_window (si); init_sigchld (si); read_status_prop (si); @@ -391,6 +509,12 @@ main_loop (saver_info *si, Bool init_p) if (event.x_event.xany.type == ClientMessage) handle_clientmessage (si, &event.x_event); + + else if (event.x_event.xany.type == PropertyNotify && + event.x_event.xproperty.state == PropertyNewValue && + event.x_event.xproperty.atom == XA_SCREENSAVER_STATUS) + status_prop_changed (si); + # ifdef HAVE_RANDR else if (si->using_randr_extension && (event.x_event.type == diff --git a/driver/xscreensaver-systemd.c b/driver/xscreensaver-systemd.c index e58825f1..f9998f7c 100644 --- a/driver/xscreensaver-systemd.c +++ b/driver/xscreensaver-systemd.c @@ -24,7 +24,7 @@ * * - When the system is about to go to sleep (e.g., laptop lid closing) * it locks the screen *before* the system goes to sleep, by running - * "xscreensaver-command -suspend". And then when the system wakes + * "xscreensaver-command --suspend". And then when the system wakes * up again, it runs "xscreensaver-command --deactivate" to force the * unlock dialog to appear immediately. * @@ -186,7 +186,7 @@ * * While playing, it runs "xdg-screensaver reset" every 10 seconds as a * heartbeat. That program is a super-complicated shell script that will - * eventually run "xscreensaver-command -reset". So MPV talks to the + * eventually run "xscreensaver-command --reset". So MPV talks to the * xscreensaver daemon directly rather than going through systemd. * That's fine. * @@ -214,7 +214,7 @@ * 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". + * command to have MPV periodically call "xscreensaver-command --reset". * Good luck with that. Maybe you should just use VLC instead. * * diff --git a/driver/xscreensaver.c b/driver/xscreensaver.c index b5e85a6b..c30d651f 100644 --- a/driver/xscreensaver.c +++ b/driver/xscreensaver.c @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright © 1991-2023 Jamie Zawinski +/* xscreensaver, Copyright © 1991-2025 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 @@ -236,6 +236,11 @@ #include #include +#ifdef HAVE_WAYLAND +# include "wayland-idle.h" +#endif + + #include "xmu.h" #include "blurb.h" #include "atoms.h" @@ -267,6 +272,7 @@ static Bool blanking_disabled_p = False; static unsigned int blank_timeout = 0; static unsigned int lock_timeout = 0; static unsigned int pointer_hysteresis = 0; +static unsigned int cursor_blank_interval = 60 * 30 + 17; /* Subprocesses. */ #define SAVER_GFX_PROGRAM "xscreensaver-gfx" @@ -678,9 +684,9 @@ print_banner(void) if (months > 18) fprintf (stderr, /* Hey jerks, the only time someone will see this particular - message is if they are running xscreensaver with '-log' in - order to send me a bug report, and they had damned well - better try the latest release before they do that -- + message is if they are running xscreensaver with '--log' + to order to send me a bug report, and they had damned + well 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" @@ -853,7 +859,7 @@ read_init_files (Bool both_p) read_p = True; /* Changes to verbose in .xscreenaver after startup are ignored; else - running xscreensaver-settings would turn off cmd line -verbose. */ + running xscreensaver-settings would turn off cmdline --verbose. */ if (!both_p) verbose_p = ov; } } @@ -904,7 +910,7 @@ error_handler (Display *dpy, XErrorEvent *event) "#######################################################################\n" "\n" " If at all possible, please re-run xscreensaver with the command\n" - " line arguments \"-sync -log log.txt\", and reproduce this bug.\n" + " line arguments \"--sync --log log.txt\", and reproduce this bug.\n" " Please include the complete \"log.txt\" file with your bug report.\n" "\n" " https://www.jwz.org/xscreensaver/bugs.html explains how to create\n" @@ -1013,14 +1019,15 @@ ensure_no_screensaver_running (Display *dpy) */ static void store_saver_status (Display *dpy, - Bool blanked_p, Bool locked_p, time_t blank_time) + Bool blanked_p, Bool locked_p, Bool auth_p, + time_t blank_time) { - /* The contents of XA_SCREENSAVER_STATUS has LOCK/BLANK/0 in the first slot, - the time at which that state began in the second slot, and the ordinal of - the running hacks on each screen (1-based) in subsequent slots. Since - we don't know the hacks here (or even how many monitors are attached) we - leave whatever was there before unchanged: it will be updated by - "xscreensaver-gfx". + /* See the different version of this function in windows.c, which explains + the structure of the XA_SCREENSAVER_STATUS property. + + The property has some values updated by this program (state, time) + followed by some updated by "xscreensaver-gfx" (the hack ordinals). + So we read the existing property and only change our parts. XA_SCREENSAVER_STATUS is stored on the (real) root window of screen 0. @@ -1031,8 +1038,6 @@ store_saver_status (Display *dpy, These properties are not used on the windows created by "xscreensaver-gfx" for use by the display hacks. - - See the different version of this function in windows.c. */ Window w = RootWindow (dpy, 0); /* always screen 0 */ Atom type; @@ -1041,7 +1046,12 @@ store_saver_status (Display *dpy, int format; unsigned long nitems, bytesafter; - /* Read the old property, so we can change just parts. */ + /* I hate using XGrabServer, but the calls to read and then alter the + property must be atomic, and there's no other way to do that. */ + XGrabServer (dpy); + XSync (dpy, False); + + /* Read the old property, so we can change just our parts. */ if (XGetWindowProperty (dpy, w, XA_SCREENSAVER_STATUS, 0, 999, False, XA_INTEGER, @@ -1053,31 +1063,44 @@ store_saver_status (Display *dpy, && dataP) status = (PROP32 *) dataP; - if (!status) /* There was no existing property */ + if (!status) /* There was no existing property, or it was bogus. */ { nitems = 3; - status = (PROP32 *) malloc (nitems * sizeof(*status)); + status = (PROP32 *) calloc (nitems, sizeof(*status)); } - status[0] = (PROP32) (locked_p ? XA_LOCK : blanked_p ? XA_BLANK : 0); - status[1] = (PROP32) blank_time; /* Y2038 bug: unsigned 32 bit time_t */ + status[0] = (PROP32) (auth_p ? XA_AUTH : + locked_p ? XA_LOCK : + blanked_p ? XA_BLANK : + 0); + status[1] = (PROP32) (((unsigned long) blank_time) >> 32); + status[2] = (PROP32) (((unsigned long) blank_time) & 0xFFFFFFFFL); + XChangeProperty (dpy, w, XA_SCREENSAVER_STATUS, XA_INTEGER, 32, PropModeReplace, (unsigned char *) status, nitems); + XUngrabServer (dpy); XSync (dpy, False); # if 0 if (debug_p && verbose_p) { int i; - fprintf (stderr, "%s: wrote status property: 0x%lx: %s", blurb(), - (unsigned long) w, - (status[0] == XA_LOCK ? "LOCK" : - status[0] == XA_BLANK ? "BLANK" : - status[0] == 0 ? "0" : "???")); - for (i = 1; i < nitems; i++) - fprintf (stderr, ", %lu", status[i]); + fprintf (stderr, "%s: wrote status property: 0x%lx: ", blurb(), + (unsigned long) w); + for (i = 0; i < nitems; i++) + { + if (i > 0) fprintf (stderr, ", "); + if (i == 0) + fprintf (stderr, "%s", + (status[i] == XA_LOCK ? "LOCK" : + status[i] == XA_BLANK ? "BLANK" : + status[i] == XA_AUTH ? "AUTH" : + status[i] == 0 ? "0" : "???")); + else + fprintf (stderr, "%lu", status[i]); + } fprintf (stderr, "\n"); - if (system ("xprop -root _SCREENSAVER_STATUS") <= 0) + if (system ("xprop -root _SCREENSAVER_STATUS") != 0) fprintf (stderr, "%s: xprop exec failed\n", blurb()); } # endif /* 0 */ @@ -1157,7 +1180,7 @@ create_daemon_window (Display *dpy) XChangeProperty (dpy, daemon_window, XA_SCREENSAVER_ID, XA_STRING, 8, PropModeReplace, (unsigned char *) id, strlen (id)); - store_saver_status (dpy, False, False, now); + store_saver_status (dpy, False, False, False, now); free (id); } @@ -1341,7 +1364,7 @@ grab_keyboard_and_mouse (Screen *screen) - If we don't have a keyboard grab, then we won't be able to read a password to unlock, so the kbd grab is mandatory. (We can't conditionalize this on locked_p, because someone - might run "xscreensaver-command -lock" at any time.) + might run "xscreensaver-command --lock" at any time.) - If we don't have a mouse grab, then we might not see mouse clicks as a signal to unblank -- but we will still see kbd @@ -1407,11 +1430,9 @@ mouse_screen (Display *dpy) static void -maybe_disable_locking (Display *dpy) +maybe_disable_locking (Display *dpy, Bool wayland_p) { const char *why = 0; - Bool wayland_p = (getenv ("WAYLAND_DISPLAY") || - getenv ("WAYLAND_SOCKET")); # ifdef NO_LOCKING why = "locking disabled at compile time"; @@ -1452,25 +1473,6 @@ 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; @@ -1481,6 +1483,28 @@ maybe_disable_locking (Display *dpy) } +static void +query_pointer (Display *dpy, int *root_xP, int *root_yP) +{ + Window root_ret, child_ret; + int win_x, win_y; + unsigned int mask; + XQueryPointer (dpy, DefaultRootWindow (dpy), + &root_ret, &child_ret, root_xP, root_yP, + &win_x, &win_y, &mask); +} + + +#ifdef HAVE_WAYLAND +static void +wayland_activity_cb (void *closure) +{ + Bool *activeP = (Bool *) closure; + *activeP = True; +} +#endif /* HAVE_WAYLAND */ + + static void main_loop (Display *dpy) { @@ -1488,16 +1512,63 @@ main_loop (Display *dpy) time_t now = time ((time_t *) 0); time_t active_at = now; time_t blanked_at = 0; + time_t locked_at = 0; + time_t cursor_blanked_at = 0; time_t ignore_activity_before = now; time_t last_checked_init_file = now; Bool authenticated_p = False; Bool ignore_motion_p = False; + Bool wayland_p = False; enum { UNBLANKED, BLANKED, LOCKED, AUTH } current_state = UNBLANKED; struct { time_t time; int x, y; } last_mouse = { 0, 0, 0 }; - maybe_disable_locking (dpy); +# ifdef HAVE_WAYLAND + Bool wayland_active_p = False; + const char *wayland_err = 0; + wayland_state *wayland = wayland_idle_init (wayland_activity_cb, + &wayland_active_p, + &wayland_err); + if (wayland) + { + wayland_p = True; /* Connected to Wayland */ + } + else if (wayland_err && !strcmp (wayland_err, "connection failed")) + { + if (getenv ("WAYLAND_DISPLAY") || getenv ("WAYLAND_SOCKET")) + { + /* Running under Wayland, but unable to connect. */ + fprintf (stderr, "%s: wayland: %s\n", blurb(), wayland_err); + exit (1); + } + else + { + /* Running under real X11, presumably. */ + if (verbose_p) + fprintf (stderr, "%s: wayland: %s (assuming real X11)\n", + blurb(), wayland_err); + } + } + else if (wayland_err) + { + /* Connected to Wayland, but initialization failed. */ + fprintf (stderr, "%s: wayland: %s\n", blurb(), wayland_err); + exit (1); + } + else + abort(); + +# else /* !HAVE_WAYLAND */ + + if (getenv ("WAYLAND_DISPLAY") || getenv ("WAYLAND_SOCKET")) + { + fprintf (stderr, "%s: not compiled with Wayland support\n", blurb()); + exit (1); + } +# endif /* !HAVE_WAYLAND */ + + maybe_disable_locking (dpy, wayland_p); init_xscreensaver_atoms (dpy); ensure_no_screensaver_running (dpy); @@ -1519,6 +1590,9 @@ main_loop (Display *dpy) blank_cursor = None; /* Cursor of window under mouse (which is blank). */ auth_cursor = XCreateFontCursor (dpy, XC_top_left_arrow); + query_pointer (dpy, &last_mouse.x, &last_mouse.y); + last_mouse.time = now; + if (strchr (version_number, 'a') || strchr (version_number, 'b')) splash_p = True; /* alpha and beta releases */ @@ -1568,26 +1642,55 @@ main_loop (Display *dpy) Atom blank_mode = 0; char blank_mode_arg[20] = { 0 }; - /* Wait until an event comes in, or a timeout. */ - { + /* Wait until an event comes in, or a timeout. + + There may already be X events on the queue that arrived along with an + earlier call to XSync(). If so we need to process those immediately + without waiting for more activity on the socket. + */ + if (! XEventsQueued (dpy, QueuedAlready)) { int xfd = ConnectionNumber (dpy); fd_set in_fds; struct timeval tv; time_t until; + switch (current_state) { case UNBLANKED: until = active_at + blank_timeout; break; case BLANKED: until = blanked_at + lock_timeout; break; default: until = 0; } + if (current_state == BLANKED || current_state == LOCKED) + { + /* On rare occasions the mouse pointer re-appears, even though we + are holding the mouse grabbed with a blank cursor. This should + not be possible, and I don't understand how it happens or under + what conditions. But, let's try blanking it out again by + re-asserting our existing grab with the blank cursor every + few minutes. + */ + time_t blank_cursor_at = cursor_blanked_at + cursor_blank_interval; + if (blank_cursor_at <= now) + { + if (verbose_p > 3) + fprintf (stderr, "%s: re-blanking cursor\n", blurb()); + grab_mouse (mouse_screen (dpy), blank_cursor); + cursor_blanked_at = now; + blank_cursor_at = cursor_blanked_at + cursor_blank_interval; + } + + if (until <= now || blank_cursor_at < until) + until = blank_cursor_at; + } + tv.tv_sec = 0; tv.tv_usec = 0; - if (until >= now) + if (until > now) tv.tv_sec = until - now; if (verbose_p > 3) { - if (!tv.tv_sec) + if (!tv.tv_sec && tv.tv_usec) fprintf (stderr, "%s: block until input\n", blurb()); else { @@ -1595,14 +1698,33 @@ main_loop (Display *dpy) time_t t = now + tv.tv_sec; localtime_r (&t, &tm); fprintf (stderr, - "%s: block for %ld sec until %02d:%02d:%02d\n", - blurb(), tv.tv_sec, tm.tm_hour, tm.tm_min, tm.tm_sec); + "%s: block for %d:%02d:%02d until %02d:%02d:%02d\n", + blurb(), + (int) tv.tv_sec / (60 * 60), + (int) (tv.tv_sec % (60 * 60)) / 60, + (int) tv.tv_sec % 60, + tm.tm_hour, tm.tm_min, tm.tm_sec); } } - FD_ZERO (&in_fds); - FD_SET (xfd, &in_fds); - select (xfd + 1, &in_fds, NULL, NULL, (tv.tv_sec ? &tv : NULL)); + { + int fd = xfd; + FD_ZERO (&in_fds); + FD_SET (xfd, &in_fds); + +# ifdef HAVE_WAYLAND + /* Stop blocking if there is activity on either the X11 socket + or the Wayland socket. */ + if (wayland) + { + int wfd = wayland_idle_get_fd (wayland); + FD_SET (wfd, &in_fds); + fd = MAX (xfd, wfd); + } +# endif /* HAVE_WAYLAND */ + + select (fd + 1, &in_fds, NULL, NULL, (tv.tv_sec ? &tv : NULL)); + } } now = time ((time_t *) 0); @@ -1617,7 +1739,7 @@ main_loop (Display *dpy) to return. Now that we are back on the program stack, handle those signals. */ - /* SIGHUP is the same as "xscreensaver-command -restart". */ + /* SIGHUP is the same as "xscreensaver-command --restart". */ if (sighup_received) { sighup_received = 0; @@ -1657,7 +1779,7 @@ main_loop (Display *dpy) } /* SIGCHLD is fired any time one of our subprocesses dies. - When "xscreensaver-auth" dies, it analyzes its exit code. + When "xscreensaver-auth" dies, we analyze its exit code. */ if (sigchld_received) authenticated_p = handle_sigchld (dpy, current_state != UNBLANKED); @@ -1701,7 +1823,7 @@ main_loop (Display *dpy) msg == XA_NEXT || msg == XA_PREV) { - /* The others are the same as -activate except that they + /* The others are the same as --activate except that they cause some extra args to be added to the xscreensaver-gfx command line. */ @@ -1893,7 +2015,7 @@ main_loop (Display *dpy) if (current_state != AUTH && /* logged by xscreensaver-auth */ (verbose_p > 1 || (verbose_p && now - active_at > 1))) - print_xinput_event (dpy, &xev, ""); + print_xinput_event (dpy, &xev, NULL, ""); active_at = now; continue; break; @@ -1901,7 +2023,7 @@ main_loop (Display *dpy) case ButtonRelease: active_at = now; if (verbose_p) - print_xinput_event (dpy, &xev, ""); + print_xinput_event (dpy, &xev, NULL, ""); continue; break; case MotionNotify: @@ -1909,7 +2031,7 @@ main_loop (Display *dpy) when grabbed, we can just ignore MotionNotify and let the XI_RawMotion clause handle hysteresis. */ if (verbose_p > 1) - print_xinput_event (dpy, &xev, "ignored"); + print_xinput_event (dpy, &xev, NULL, "ignored"); continue; break; default: @@ -1941,7 +2063,7 @@ main_loop (Display *dpy) if (current_state != AUTH && /* logged by xscreensaver-auth */ (verbose_p > 1 || (verbose_p && now - active_at > 1))) - print_xinput_event (dpy, &xev, ""); + print_xinput_event (dpy, &xev, NULL, ""); active_at = now; break; @@ -1964,16 +2086,10 @@ main_loop (Display *dpy) int secs = now - last_mouse.time; if (secs >= 1) { - Window root_ret, child_ret; - int root_x, root_y; - int win_x, win_y; - unsigned int mask; int dist; Bool ignored_p = False; - - XQueryPointer (dpy, DefaultRootWindow (dpy), - &root_ret, &child_ret, &root_x, &root_y, - &win_x, &win_y, &mask); + int root_x = last_mouse.x, root_y = last_mouse.y; + query_pointer (dpy, &root_x, &root_y); dist = MAX (ABS (last_mouse.x - root_x), ABS (last_mouse.y - root_y)); @@ -1989,7 +2105,7 @@ main_loop (Display *dpy) if (verbose_p > 1 || (verbose_p && now - active_at > 5)) - print_xinput_event (dpy, &xev, + print_xinput_event (dpy, &xev, NULL, (ignored_p ? " ignored" : "")); } } @@ -1997,7 +2113,7 @@ main_loop (Display *dpy) default: if (verbose_p) - print_xinput_event (dpy, &xev, ""); + print_xinput_event (dpy, &xev, NULL, ""); break; } @@ -2005,6 +2121,22 @@ main_loop (Display *dpy) } +# ifdef HAVE_WAYLAND + if (wayland) + { + wayland_idle_process_events (wayland); + if (wayland_active_p) + { + active_at = now; + wayland_active_p = False; + if (verbose_p) + fprintf (stderr,"%s: wayland reports user activity\n", + blurb()); + } + } +# endif /* HAVE_WAYLAND */ + + /******************************************************************** Advancing the state machine ********************************************************************/ @@ -2036,8 +2168,10 @@ main_loop (Display *dpy) { current_state = LOCKED; blanked_at = now; + locked_at = now; + cursor_blanked_at = now; authenticated_p = False; - store_saver_status (dpy, True, True, now); + store_saver_status (dpy, True, True, False, locked_at); } else fprintf (stderr, "%s: unable to grab -- locking aborted!\n", @@ -2061,7 +2195,9 @@ main_loop (Display *dpy) { current_state = BLANKED; blanked_at = now; - store_saver_status (dpy, True, False, now); + locked_at = 0; + cursor_blanked_at = now; + store_saver_status (dpy, True, False, False, blanked_at); } else fprintf (stderr, "%s: unable to grab -- blanking aborted!\n", @@ -2118,7 +2254,8 @@ main_loop (Display *dpy) (force_lock_p ? "" : " after timeout")); current_state = LOCKED; authenticated_p = False; - store_saver_status (dpy, True, True, now); + locked_at = now; + store_saver_status (dpy, True, True, False, locked_at); force_lock_p = False; /* Single shot */ } else if (active_at >= now && @@ -2129,7 +2266,7 @@ main_loop (Display *dpy) fprintf (stderr, "%s: unblanking\n", blurb()); current_state = UNBLANKED; ignore_motion_p = False; - store_saver_status (dpy, False, False, now); + store_saver_status (dpy, False, False, False, now); if (saver_gfx_pid) { @@ -2195,6 +2332,8 @@ main_loop (Display *dpy) the auth dialog is raised. We can ignore failures here. */ grab_mouse (mouse_screen (dpy), auth_cursor); + store_saver_status (dpy, True, True, True, locked_at); + av[ac++] = SAVER_AUTH_PROGRAM; if (verbose_p) av[ac++] = "--verbose"; if (verbose_p > 1) av[ac++] = "--verbose"; @@ -2230,6 +2369,7 @@ main_loop (Display *dpy) a different mouse pointer, to hide the pointer again now that the auth dialog is gone. We can ignore failures here. */ grab_mouse (mouse_screen (dpy), blank_cursor); + cursor_blanked_at = now; /* When the unlock dialog is dismissed, ignore any input for a second to give the user time to take their hands off of the @@ -2237,6 +2377,8 @@ main_loop (Display *dpy) immediately. */ ignore_activity_before = now + 1; + store_saver_status (dpy, True, True, False, locked_at); + if (gfx_stopped_p) /* SIGCONT to resume savers */ { if (verbose_p) @@ -2475,7 +2617,7 @@ main (int argc, char **argv) blurb(), dpy_str); } - /* Copy the -dpy arg to $DISPLAY for subprocesses. */ + /* Copy the --display arg to $DISPLAY for subprocesses. */ { char *s = (char *) malloc (strlen(dpy_str) + 20); sprintf (s, "DISPLAY=%s", dpy_str); diff --git a/driver/xscreensaver.h b/driver/xscreensaver.h index 4bd84b47..a3e4926c 100644 --- a/driver/xscreensaver.h +++ b/driver/xscreensaver.h @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright © 1993-2021 Jamie Zawinski +/* xscreensaver, Copyright © 1993-2025 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 @@ -53,6 +53,7 @@ extern void initialize_screensaver_window (saver_info *si); extern void blank_screen (saver_info *si); extern void unblank_screen (saver_info *si); extern void resize_screensaver_window (saver_info *si); +extern void reset_watchdog_timer (saver_info *); extern void get_screen_viewport (saver_screen_info *ssi, int *x_ret, int *y_ret, @@ -96,6 +97,6 @@ extern void screenhack_obituary (saver_screen_info *, extern Visual *get_best_gl_visual (saver_info *si, Screen *screen); extern void maybe_reload_init_file (saver_info *); -void print_available_extensions (saver_info *); +extern void print_available_extensions (saver_info *); #endif /* __XSCREENSAVER_H__ */ diff --git a/driver/xscreensaver.man b/driver/xscreensaver.man index 572c727c..2131a541 100644 --- a/driver/xscreensaver.man +++ b/driver/xscreensaver.man @@ -315,30 +315,42 @@ 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 +X11. After 16+ 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: +If your system is running Wayland, XScreenSaver will malfunction in a +few ways, some small but one quite serious: .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. +\fB1:\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. +.TP 3 +\fB2:\fP It won't work at all under GNOME. + +This is because the GNOME compositor does not support either of the Wayland +protocols that allow us to tell when the user is idle. It works with KDE +and most other compositors, however. +.TP 3 +\fB2:\fP Even a single pixel of mouse motion will cause the screen to +un-blank: there is no pointer hysteresis. +.TP 3 +\fB2:\fP Fading in and out might not work; hacks that grab and manipulate +a desktop screenshot might not work. + +Under Wayland, these features depend on the program +.BR grim (1) +being installed to grab screenshots for us. And \fBgrim\fP doesn't work +under the GNOME or KDE compositors. .RE -In short, for XScreenSaver to work properly, you will need to switch off +For XScreenSaver to be able to lock, 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 diff --git a/driver/xscreensaver.pam.in b/driver/xscreensaver.pam.in index c18fa9f2..00658396 100644 --- a/driver/xscreensaver.pam.in +++ b/driver/xscreensaver.pam.in @@ -1,10 +1,23 @@ #%PAM-1.0 -# Fedora Core 5: -auth include system-auth +# Debian 12: +# auth requisite pam_nologin.so +# auth optional pam_group.so +# @include common-auth +# @include common-account +# @include common-session +# @include common-password + +# Ubuntu 18: +# @include common-auth +# @include common-account -# SuSE 9.0: (along with "configure --with-passwd-helper" and "unix2_chkpwd") -# auth required pam_unix2.so nullok +# AL2023: +# auth substack system-auth +# auth include postlogin + +# Fedora Core 5: +# auth include system-auth # Distant past: # auth required /lib/security/pam_pwdb.so shadow nullok diff --git a/hacks/Makefile.in b/hacks/Makefile.in index 743073ee..38aebd07 100644 --- a/hacks/Makefile.in +++ b/hacks/Makefile.in @@ -314,7 +314,7 @@ install-scripts: $(SCRIPTS) munge-scripts munge-scripts: $(SCRIPTS) @tmp=/tmp/mf.$$$$ ; \ - perl="${PERL}" ; \ + perl="$(PERL)" ; \ rm -f $$tmp ; \ for program in $(SCRIPTS); do \ sed "s@^\(#!\)\(/[^ ]*/perl[^ ]*\)\(.*\)\$$@\1$$perl\3@" \ @@ -458,11 +458,11 @@ check_men: fi validate_xml: - @cd $(srcdir) && ./check-configs.pl --force $(EXES) + @cd $(srcdir) && $(PERL) check-configs.pl --force $(EXES) munge_ad_file: @echo "Updating hack list in XScreenSaver.ad.in..." ; \ - cd $(srcdir) ; ./munge-ad.pl ../driver/XScreenSaver.ad.in + cd $(srcdir) && $(PERL) munge-ad.pl ../driver/XScreenSaver.ad.in distdepend:: check_men validate_xml munge_ad_file diff --git a/hacks/ansi-tty.c b/hacks/ansi-tty.c index a91f364c..5e506ab3 100644 --- a/hacks/ansi-tty.c +++ b/hacks/ansi-tty.c @@ -630,7 +630,7 @@ ansi_tty_print (ansi_tty *tty, unsigned long c) int scrolled_p = False; const char *kind = "?"; - int av[255]; + int av[255] = { UNDEF }; int ac = 0; int i; diff --git a/hacks/asm6502.c b/hacks/asm6502.c index 8876beb2..56855418 100644 --- a/hacks/asm6502.c +++ b/hacks/asm6502.c @@ -15,7 +15,7 @@ /* This is a port of the javascript 6502 assembler, compiler and debugger. The orignal code was copyright 2006 by Stian Soreng - - www.6502asm.com + https://web.archive.org/web/20070516072609/http%3A//www.6502asm.com/ I changed the structure of the assembler in this version. */ diff --git a/hacks/asm6502.h b/hacks/asm6502.h index 79b13a2b..78a43c62 100644 --- a/hacks/asm6502.h +++ b/hacks/asm6502.h @@ -15,7 +15,7 @@ /* This is a port of the javascript 6502 assembler, compiler and debugger. The orignal code was copyright 2006 by Stian Soreng - - www.6502asm.com + https://web.archive.org/web/20070516072609/http%3A//www.6502asm.com/ The stack space is in page $100 to $1ff. The video buffer starts at $200 and is 1024 bytes. Programs get loaded at address diff --git a/hacks/bsod.c b/hacks/bsod.c index 653d6a34..c46cd8d1 100644 --- a/hacks/bsod.c +++ b/hacks/bsod.c @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright © 1998-2024 Jamie Zawinski +/* xscreensaver, Copyright © 1998-2025 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 @@ -2063,7 +2063,7 @@ windows_10 (Display *dpy, Window window) "This place is best shunned and left uninhabited." }; int i, y = 0; - int top, left0, left, right, y1; + int top, left0, left, right, y1 = 0; Bool clownp = !(random() % 10); Bool honorp = !clownp && !(random() % 20); const char * const * lines = (clownp ? lines2 : honorp ? lines3 : lines1); @@ -2134,9 +2134,9 @@ windows_10 (Display *dpy, Window window) } else if (i == 1) { -// y += font->ascent + font->descent; + /* y += font->ascent + font->descent; */ y1 = y; -// y += font->ascent + font->descent; + /* y += font->ascent + font->descent; */ } else if (i == 2) { diff --git a/hacks/config/README b/hacks/config/README index 3e68861c..e1400fc9 100644 --- a/hacks/config/README +++ b/hacks/config/README @@ -4,8 +4,8 @@ a screen saver and locker for the X window system by Jamie Zawinski - version 6.10 - 27-Apr-2025 + version 6.11 + 01-Jul-2025 https://www.jwz.org/xscreensaver/ diff --git a/hacks/config/klondike.xml b/hacks/config/klondike.xml index e74d2219..fd4394d4 100644 --- a/hacks/config/klondike.xml +++ b/hacks/config/klondike.xml @@ -15,8 +15,9 @@ convert="invert"/> + _label="Animation Speed" _low-label="Slow" _high-label="Fast" + low="15" high="200" default="50" + convert="invert"/> diff --git a/hacks/glx/Makefile.in b/hacks/glx/Makefile.in index 8fee19b6..043ff6ec 100644 --- a/hacks/glx/Makefile.in +++ b/hacks/glx/Makefile.in @@ -265,7 +265,7 @@ HACK_OBJS = $(HACK_BIN)/screenhack.o $(HACK_BIN)/xlockmore.o \ $(UTILS_BIN)/usleep.o \ $(UTILS_BIN)/xmu.o \ $(UTILS_BIN)/yarandom.o \ - ${FPS_OBJS} $(JWZGLES_OBJS) $(HACK_GLSL_OBJS) @ANIM_OBJS@ + $(FPS_OBJS) $(JWZGLES_OBJS) $(HACK_GLSL_OBJS) @ANIM_OBJS@ HDRS = atlantis.h bubble3d.h buildlwo.h e_textures.h \ grab-ximage.h tube.h sphere.h boxed.h \ @@ -374,7 +374,7 @@ install-program:: $(EXES) fi ; \ done ; \ \ - exes="${SETCAP_EXES}" ; \ + exes="$(SETCAP_EXES)" ; \ if [ @SETCAP_HACKS@ = yes ]; then \ for program in $$exes; do \ echo $(PROG_SETCAP) $(SETCAP_FLAGS) $$idir/$$program ; \ @@ -410,7 +410,7 @@ install-scripts: $(SCRIPTS) munge-scripts munge-scripts: $(SCRIPTS) @tmp=/tmp/mf.$$$$ ; \ - perl="${PERL}" ; \ + perl="$(PERL)" ; \ rm -f $$tmp ; \ for program in $(SCRIPTS); do \ sed "s@^\(#!\)\(/[^ ]*/perl[^ ]*\)\(.*\)\$$@\1$$perl\3@" \ @@ -565,7 +565,7 @@ check_men: fi validate_xml: - @cd $(HACK_SRC) && ./check-configs.pl --force $(EXES) $(RETIRED_EXES) + @cd $(HACK_SRC) && $(PERL) check-configs.pl --force $(EXES) $(RETIRED_EXES) distdepend:: check_men validate_xml @@ -2098,6 +2098,22 @@ dumpsterfire.o: $(UTILS_SRC)/xft.h dumpsterfire.o: $(UTILS_SRC)/yarandom.h dumpsterfire.o: $(HACK_SRC)/xlockmoreI.h dumpsterfire.o: $(HACK_SRC)/xlockmore.h +dumpster_model.o: ../../config.h +dumpster_model.o: $(HACK_SRC)/fps.h +dumpster_model.o: $(srcdir)/gllist.h +dumpster_model.o: $(HACK_SRC)/recanim.h +dumpster_model.o: $(HACK_SRC)/screenhackI.h +dumpster_model.o: $(UTILS_SRC)/colors.h +dumpster_model.o: $(UTILS_SRC)/erase.h +dumpster_model.o: $(UTILS_SRC)/font-retry.h +dumpster_model.o: $(UTILS_SRC)/grabclient.h +dumpster_model.o: $(UTILS_SRC)/hsv.h +dumpster_model.o: $(UTILS_SRC)/resources.h +dumpster_model.o: $(UTILS_SRC)/usleep.h +dumpster_model.o: $(UTILS_SRC)/visual.h +dumpster_model.o: $(UTILS_SRC)/xft.h +dumpster_model.o: $(UTILS_SRC)/yarandom.h +dumpster_model.o: $(HACK_SRC)/xlockmoreI.h dymaxionmap-coords.o: ../../config.h dymaxionmap-coords.o: $(srcdir)/dymaxionmap-coords.h dymaxionmap.o: ../../config.h diff --git a/hacks/glx/carousel.c b/hacks/glx/carousel.c index a1aa802e..d265e6f8 100644 --- a/hacks/glx/carousel.c +++ b/hacks/glx/carousel.c @@ -296,7 +296,7 @@ image_loaded_cb (const char *filename, XRectangle *geom, if (frame->loading.title && frame->loading.title[0] == '/') { /* strip filename to part after last /. */ char *s = strrchr (frame->loading.title, '/'); - if (s) strcpy (frame->loading.title, s+1); + if (s) memmove (frame->loading.title, s+1, strlen (s)); } if (debug_p) diff --git a/hacks/glx/esper.c b/hacks/glx/esper.c index bd8f5f5b..1302cac4 100644 --- a/hacks/glx/esper.c +++ b/hacks/glx/esper.c @@ -503,7 +503,7 @@ image_loaded_cb (const char *filename, XRectangle *geom, { /* strip filename to part between last "/" and last ".". */ char *s = strrchr (img->title, '/'); - if (s) strcpy (img->title, s+1); + if (s) memmove (img->title, s+1, strlen (s)); s = strrchr (img->title, '.'); if (s) *s = 0; } diff --git a/hacks/glx/glslideshow.c b/hacks/glx/glslideshow.c index 15d2ec35..24930c8f 100644 --- a/hacks/glx/glslideshow.c +++ b/hacks/glx/glslideshow.c @@ -398,7 +398,7 @@ image_loaded_cb (const char *filename, XRectangle *geom, /* strip filename to part between last "/" and end. */ /* xscreensaver-getimage has already stripped off the extension. */ char *s = strrchr (img->title, '/'); - if (s) strcpy (img->title, s+1); + if (s) memmove (img->title, s+1, strlen (s)); } if (verbose_p) diff --git a/hacks/glx/klondike-game.c b/hacks/glx/klondike-game.c index 9f968d56..c5d96b6d 100644 --- a/hacks/glx/klondike-game.c +++ b/hacks/glx/klondike-game.c @@ -18,9 +18,6 @@ #include "gltrackball.h" #include "klondike-game.h" -// random position offset for sloppy mode -#define RANDOM_POSITION_OFFSET (bp->sloppy ? (((float)random()) / ((float)RAND_MAX) - 0.5) * 0.0125 : 0) - // static const char *suits[] = {"Diamonds", "Clubs", "Hearts", "Spades"}; // static const char *short_suits[] = {"D", "C", "H", "S"}; // static const char *ranks[] = {"", "Ace", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Jack", "Queen", "King"}; @@ -44,10 +41,10 @@ void klondike_initialize_deck(klondike_configuration *bp) deck[index].is_face_up = 0; deck[index].x = bp->deck_x; deck[index].y = bp->deck_y; - deck[index].start_x = bp->deck_x; - deck[index].start_y = bp->deck_y; - deck[index].dest_x = bp->deck_x; - deck[index].dest_y = bp->deck_y; + deck[index].start_x = bp->deck_x + RANDOM_POSITION_OFFSET; + deck[index].start_y = bp->deck_y + RANDOM_POSITION_OFFSET; + deck[index].dest_x = bp->deck_x + RANDOM_POSITION_OFFSET; + deck[index].dest_y = bp->deck_y + RANDOM_POSITION_OFFSET; deck[index].start_frame = 0; deck[index].end_frame = 0; deck[index].start_angle = 0.0f; @@ -176,8 +173,8 @@ static void reset_waste(klondike_configuration *bp, game_state_struct *game_stat game_state->deck[i] = game_state->waste[i]; card_struct *animated_card = &game_state->deck[i]; - animated_card->start_frame = bp->tick + (i+5) * bp->animation_ticks / 10; - animated_card->end_frame = bp->tick + (i+5) * bp->animation_ticks / 10 + bp->animation_ticks; + animated_card->start_frame = bp->tick + (i+5) * bp->animation_ticks / 3; + animated_card->end_frame = bp->tick + (i+5) * bp->animation_ticks / 3 + bp->animation_ticks; animated_card->start_x = animated_card->x; animated_card->start_y = animated_card->y; animated_card->dest_x = bp->deck_x + RANDOM_POSITION_OFFSET; diff --git a/hacks/glx/klondike-game.h b/hacks/glx/klondike-game.h index f31dce5d..d33499e2 100644 --- a/hacks/glx/klondike-game.h +++ b/hacks/glx/klondike-game.h @@ -23,6 +23,10 @@ #define MAX_TEXTURE 53 #define BACK_TEXTURE 52 +// random position offset for sloppy mode +#define RANDOM_POSITION_OFFSET (bp->sloppy ? (((float)random()) / ((float)RAND_MAX) - 0.5) * 0.0125 : 0) + + typedef enum { DIAMONDS, CLUBS, HEARTS, SPADES } Suit; typedef enum { NONE=0, ACE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING } Rank; diff --git a/hacks/glx/klondike.c b/hacks/glx/klondike.c index 15677d37..ba4a8f46 100644 --- a/hacks/glx/klondike.c +++ b/hacks/glx/klondike.c @@ -79,7 +79,7 @@ #ifdef USE_GL /* whole file */ #define DEF_CAMERA_SPEED "50" -#define DEF_SPEED "60" +#define DEF_SPEED "50" #define DEF_DRAW_COUNT "3" #define DEF_SLOPPY "True" @@ -180,6 +180,8 @@ reshape_klondike(ModeInfo *mi, int width, int height) } initialize_placeholders(&bps[MI_SCREEN(mi)], width, height); + bps[MI_SCREEN(mi)].redeal = 1; + glClear(GL_COLOR_BUFFER_BIT); } @@ -227,8 +229,8 @@ static void animate_initial_board(klondike_configuration *bp) card->end_frame = card->start_frame + animation_ticks; card->start_x = bp->deck_x; card->start_y = bp->deck_y; - card->dest_x = bp->tableau_placeholders[j].x; - card->dest_y = bp->tableau_placeholders[j].y; + card->dest_x = bp->tableau_placeholders[j].x + RANDOM_POSITION_OFFSET; + card->dest_y = bp->tableau_placeholders[j].y + RANDOM_POSITION_OFFSET; card->angle = 0.0f; card->start_angle = 0.0f; card->is_face_up = i == bp->game_state->tableau_size[j] - 1; @@ -368,6 +370,7 @@ init_klondike(ModeInfo *mi) bp->trackball = gltrackball_init (True); + // initialize the game state bp->game_state = (game_state_struct *)malloc(sizeof(game_state_struct)); bp->tick = 0; @@ -434,12 +437,12 @@ static void animate_board_to_deck(klondike_configuration *bp) for (int j = 0; j < bp->game_state->tableau_size[i]; j++) { card_struct *card = &bp->game_state->tableau[i][j]; - card->start_frame = bp->tick + n * animation_ticks / 12; + card->start_frame = bp->tick + n * animation_ticks / 3; card->end_frame = card->start_frame + animation_ticks; card->start_x = card->x; card->start_y = card->y; - card->dest_x = bp->deck_x; - card->dest_y = bp->deck_y; + card->dest_x = bp->deck_x + RANDOM_POSITION_OFFSET; + card->dest_y = bp->deck_y + RANDOM_POSITION_OFFSET; card->start_angle = card->angle; card->end_angle = 360.0f; card->is_face_up = 0; @@ -452,12 +455,12 @@ static void animate_board_to_deck(klondike_configuration *bp) for (int j = 0; j < bp->game_state->foundation_size[i]; j++) { card_struct *card = &bp->game_state->foundation[i][j]; - card->start_frame = bp->tick + n * animation_ticks / 12; + card->start_frame = bp->tick + n * animation_ticks / 3; card->end_frame = card->start_frame + animation_ticks; card->start_x = card->x; card->start_y = card->y; - card->dest_x = bp->deck_x; - card->dest_y = bp->deck_y; + card->dest_x = bp->deck_x + RANDOM_POSITION_OFFSET; + card->dest_y = bp->deck_y + RANDOM_POSITION_OFFSET; card->start_angle = card->angle; card->end_angle = 360.0f; card->is_face_up = 0; @@ -468,19 +471,19 @@ static void animate_board_to_deck(klondike_configuration *bp) for (int i = 0; i < bp->game_state->waste_size; i++) { card_struct *card = &bp->game_state->waste[i]; - card->start_frame = bp->tick + n * animation_ticks / 12; + card->start_frame = bp->tick + n * animation_ticks / 3; card->end_frame = card->start_frame + animation_ticks; card->start_x = card->x; card->start_y = card->y; - card->dest_x = bp->deck_x; - card->dest_y = bp->deck_y; + card->dest_x = bp->deck_x + RANDOM_POSITION_OFFSET; + card->dest_y = bp->deck_y + RANDOM_POSITION_OFFSET; card->start_angle = card->angle; card->end_angle = 360.0f; card->is_face_up = 0; n++; } - bp->final_animation = bp->tick + n * animation_ticks / 12 + animation_ticks; + bp->final_animation = bp->tick + n * animation_ticks / 3 + animation_ticks; } ENTRYPOINT void @@ -622,8 +625,8 @@ draw_klondike(ModeInfo *mi) 0.1, -0.0, 0, // Look-at point (center) 0, 0, 1); // Up vector - glRotatef (current_device_rotation(), 0, 0, 1); - gltrackball_rotate (bp->trackball); + glRotatef(current_device_rotation(), 0, 0, 1); + gltrackball_rotate(bp->trackball); for (int i = 0; i < animatedCardCount; i++) { @@ -644,7 +647,6 @@ draw_klondike(ModeInfo *mi) { float n = ((float)bp->tick - (float)card->start_frame) / (card->end_frame - card->start_frame); float eased = ease_out_quart(n); - float eased2 = ease_in_out_quart(n); if (card->dest_x != card->start_x) { @@ -668,7 +670,7 @@ draw_klondike(ModeInfo *mi) if (card->end_angle != card->start_angle) { - card->angle = card->start_angle + eased2 * (card->end_angle - card->start_angle); + card->angle = card->start_angle + n * (card->end_angle - card->start_angle); } else { @@ -766,15 +768,6 @@ draw_klondike(ModeInfo *mi) else if (bp->final_animation == 0 && n == NULL) { animate_board_to_deck(bp); - - // Check for win -# if 0 // unused - int won_game = 0; - for (int i = 0; i < 4; i++) - { - won_game &= (bp->game_state->foundation_size[i] == 13); - } -# endif } if (n != NULL) diff --git a/hacks/glx/mapscroller.c b/hacks/glx/mapscroller.c index 7f52b819..d40fa355 100644 --- a/hacks/glx/mapscroller.c +++ b/hacks/glx/mapscroller.c @@ -1375,8 +1375,6 @@ draw_map (ModeInfo *mi) sprintf (buf, "%.3f\xC2\xB0, %.3f\xC2\xB0", bp->pos.lat, bp->pos.lon); # elif 0 /* 37° 46' 15.63" N, 122° 24' 45.70" W */ - /* This screws up the label image. Some kind of bug in texfont.c? - But only on X11, not Cocoa. */ double alat = bp->pos.lat >= 0 ? bp->pos.lat : -bp->pos.lat; double alon = bp->pos.lon >= 0 ? bp->pos.lon : -bp->pos.lon; sprintf (buf, diff --git a/hacks/glx/photopile.c b/hacks/glx/photopile.c index c5d43684..6f530385 100644 --- a/hacks/glx/photopile.c +++ b/hacks/glx/photopile.c @@ -1,5 +1,5 @@ -/* photopile, Copyright (c) 2008-2018 Jens Kilian - * Based on carousel, Copyright (c) 2005-2008 Jamie Zawinskin +/* photopile, Copyright © 2008-2018 Jens Kilian + * Based on carousel, Copyright © 2005-2008 Jamie Zawinski * Loads a sequence of images and shuffles them into a pile. * * Permission to use, copy, modify, distribute, and sell this software and its @@ -292,7 +292,7 @@ image_loaded_cb (const char *filename, XRectangle *geom, { /* strip filename to part after last /. */ char *s = strrchr (frame->title, '/'); - if (s) strcpy (frame->title, s+1); + if (s) memmove (frame->title, s+1, strlen (s)); } if (debug_p) diff --git a/hacks/glx/texfont.c b/hacks/glx/texfont.c index 2a4df299..8a957f19 100644 --- a/hacks/glx/texfont.c +++ b/hacks/glx/texfont.c @@ -1,4 +1,4 @@ -/* texfont, Copyright © 2005-2022 Jamie Zawinski +/* texfont, Copyright © 2005-2025 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 @@ -147,6 +147,8 @@ bitmap_to_texture (const texture_font_data *tfdata, Pixmap p, XImage *image = 0; unsigned char *data = (unsigned char *) calloc (w2 * 2, (h2 + 1)); unsigned char *out = data; + GLint rowpack = 0; + GLint alignment = 0; /* OpenGLES doesn't support GL_INTENSITY, so instead of using a texture with 1 byte per pixel, the intensity value, we have @@ -258,6 +260,12 @@ bitmap_to_texture (const texture_font_data *tfdata, Pixmap p, image = 0; + glGetIntegerv (GL_UNPACK_ROW_LENGTH, &rowpack); + glGetIntegerv (GL_UNPACK_ALIGNMENT, &alignment); + + glPixelStorei (GL_UNPACK_ROW_LENGTH, 0); + glPixelStorei (GL_UNPACK_ALIGNMENT, 1); + { # ifdef GL_INTENSITY GLuint iformat = GL_INTENSITY; @@ -287,6 +295,9 @@ bitmap_to_texture (const texture_font_data *tfdata, Pixmap p, } } + glPixelStorei (GL_UNPACK_ROW_LENGTH, rowpack); + glPixelStorei (GL_UNPACK_ALIGNMENT, alignment); + { char msg[100]; sprintf (msg, "texture font %s (%d x %d)", @@ -377,6 +388,12 @@ load_texture_font (Display *dpy, char *res) !get_boolean_resource (dpy, "texFontOmitDropShadow", "Boolean"); data->mipmap_p = True; + +# if defined(__APPLE__) && !defined(HAVE_COCOA) /* macOS X11 */ + /* Some time before macOS 14.7.3, gluBuild2DMipmaps() started segfaulting. */ + data->mipmap_p = False; +# endif + # ifdef HAVE_JWZGLES /* This would work, but it's wasteful for no benefit. */ /* Wait, is it ever useful? */ diff --git a/hacks/images/klondike/back.png b/hacks/images/klondike/back.png index 59d36b3a..7246d9f0 100644 Binary files a/hacks/images/klondike/back.png and b/hacks/images/klondike/back.png differ diff --git a/hacks/images/klondike/back0.png b/hacks/images/klondike/back0.png index 1dc1e6f6..e8abe54a 100644 Binary files a/hacks/images/klondike/back0.png and b/hacks/images/klondike/back0.png differ diff --git a/hacks/images/m6502/sierpinsky.asm b/hacks/images/m6502/sierpinsky.asm index 6a679062..4ec772d9 100644 --- a/hacks/images/m6502/sierpinsky.asm +++ b/hacks/images/m6502/sierpinsky.asm @@ -1,7 +1,7 @@ ; 6502 assembler Sierpinsky Triangle ver.2 ; by Magnus Wedmark 2007-05-02 ; This program is especially written for -; the 6502asm.com competition and +; the 6502asm dot com competition and ; uses the 32*32 pixel display used in that ; virtual platform. The sierpinsky ; fractal is one of the simplest to diff --git a/hacks/m6502.man b/hacks/m6502.man index 400444e1..67e75ffe 100644 --- a/hacks/m6502.man +++ b/hacks/m6502.man @@ -75,9 +75,6 @@ I'm sure some of the programs contain trademarks. .BR xanalogtv (MANSUFFIX), .BR apple2 (MANSUFFIX) .SH COPYRIGHT -Original Javascript Version by Stian Soreng - www.6502asm.com; 2006. -Ported to XScreenSaver by Jeremy English; 2007. - 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 @@ -86,6 +83,5 @@ 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 -Original Javascript Version by Stian Soreng - www.6502asm.com; 2006. +Original Javascript Version by Stian Soreng; 2006. Ported to XScreenSaver by Jeremy English; 2007. - diff --git a/hacks/webcollage b/hacks/webcollage index 0bfe756e..5b489f43 100755 --- a/hacks/webcollage +++ b/hacks/webcollage @@ -47,7 +47,7 @@ use LWP::UserAgent; my $progname = $0; $progname =~ s@.*/@@g; -my ($version) = ('$Revision: 1.195 $' =~ m/\s(\d[.\d]+)\s/s); +my ($version) = ('$Revision: 1.196 $' =~ m/\s(\d[.\d]+)\s/s); my $copyright = "WebCollage $version, Copyright © 1999-2024" . " Jamie Zawinski \n" . " https://www.jwz.org/webcollage/\n"; @@ -3101,6 +3101,8 @@ my $img_height; my $delay = 2; +my $max_load = 0; + # Like system, but prints status about exit codes, and kills this process # with whatever signal killed the sub-process, if any. # @@ -3165,6 +3167,28 @@ sub pick_root_displayer() { } +# If the load average is higher than the --max-load arg, wait. +sub await_load() { + return unless $max_load; + my $load; + my $count = 0; + do { + my $line = `cat /proc/loadavg 2>&-`; + ($load) = ($line =~ m/^([\d.]+)/s); + return unless defined ($load); + if ($load < $max_load) { + LOG($verbose_warnings, "Load is $load - resuming") if ($count); + return; + } else { + LOG($verbose_warnings, "Load is $load - pausing") + if ($count == 0 || $verbose_load); + sleep (60); + } + $count++; + } while ($load > $max_load); +} + + my $png_to_root_window_cmd = undef; @@ -3321,6 +3345,7 @@ sub x_or_image_output($) { unlink $image_tmp1, $image_tmp2; sleep $delay; + await_load(); } } @@ -3905,6 +3930,8 @@ sub main() { $delay = shift @ARGV; } elsif (m/^--?timeout$/s) { $http_timeout = shift @ARGV; + } elsif (m/^--?max-load$/s) { + $max_load = shift @ARGV; } elsif (m/^--?filter$/s) { $filter_cmd = shift @ARGV; } elsif (m/^--?filter2$/s) { diff --git a/hacks/xscreensaver-getimage.c b/hacks/xscreensaver-getimage.c index b2564b9b..36468879 100644 --- a/hacks/xscreensaver-getimage.c +++ b/hacks/xscreensaver-getimage.c @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright © 2001-2024 Jamie Zawinski +/* xscreensaver, Copyright © 2001-2025 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 @@ -34,19 +34,19 @@ Grabbing screen images works in a few different ways: A: If the hack was invoked by XScreenSaver, then before it blanked the - screen, "xscreensaver-gfx" (or "xscreensaver-settings", in preview - mode) saved a screenshot as a pixmap on a property on the saver - window. This code loads that pixmap, and crops or scales it as + screen, "xscreensaver-gfx" (or "xscreensaver-settings", in preview mode) + saved a screenshot of the whole desktop as a pixmap on a property on the + saver window. This code loads that pixmap, and crops or scales it as needed. This means that the screenshot used will always be what the desktop looked like at the time that the screen blanked, not what it might look like now if the screen happened to be un-blanked. - B: If the pre-saved pixmap isn't there, then we do it the hard way: - un-map our window to expose what is under it; wait an arbitrary - amount of time for all other processes to re-paint their windows; - copy a screen image; put our window back; and then return that image. - This method is slow and unreliable, as there is no way to know how - long we have to wait for the re-paint, and if you don't wait long + B: Under plain-old X11, if the pre-saved pixmap isn't there, then we do it + the hard way: un-map our window to expose what is under it; wait an + arbitrary amount of time for all other processes to re-paint their + windows; copy a screen image; put our window back; and then return that + image. This method is slow and unreliable, as there is no way to know + how long we have to wait for the re-paint, and if you don't wait long enough, you get all black. E.g. on 2022 Raspbian 11.5/Pi4b/LXDE, it takes nearly *five seconds* for the frame buffer to update, which is truly awful. Also, the unmapping and remapping causes ugly flicker, @@ -57,6 +57,17 @@ XQuartz doesn't let you make screenshots by copying the X11 root window, so this instead runs "/usr/sbin/screencapture" to get the Mac desktop image as a file. + + D: On Wayland: We try to use "/usr/bin/grim" to get the desktop image + as a file. That program is not installed by default on all Wayland + systems; and it doesn't work under GNOME or KDE. + + "grim: compositor doesn't support wlr-screencopy-unstable-v1". + + There is no way for XScreenSaver to get a screen image under GNOME + or KDE. They invented their own, incompatible APIs, which always + play a camera noise and flash the screen white, and might also + pop up a confirmation dialog. */ #include "utils.h" @@ -121,16 +132,6 @@ #endif -#ifdef __APPLE__ - /* On macOS under X11, the usual X11 mechanism of getting a screen shot - doesn't work, and we need to use an external program. This is only - used when running under X11 on macOS. If it's a Cocoa build, this - path is not taken, and OSX/grabclient-osx.m is used instead. - */ -# define USE_EXTERNAL_SCREEN_GRABBER -#endif - - const char *progclass = "XScreenSaver"; XrmDatabase db; XtAppContext app; @@ -140,9 +141,30 @@ typedef enum { } grab_type; -#define GETIMAGE_VIDEO_PROGRAM "xscreensaver-getimage-video" -#define GETIMAGE_FILE_PROGRAM "xscreensaver-getimage-file" -#define GETIMAGE_SCREEN_PROGRAM "screencapture" +#if defined(__APPLE__) && !defined(HAVE_COCOA) +# define HAVE_MACOS_X11 +#elif !defined(HAVE_JWXYZ) /* Real X11, possibly Wayland */ +# define HAVE_REAL_X11 +#endif + +#define GETIMAGE_VIDEO_PROGRAM "xscreensaver-getimage-video" +#define GETIMAGE_FILE_PROGRAM "xscreensaver-getimage-file" + +#ifdef HAVE_MACOS_X11 + /* On macOS under X11, the usual X11 mechanism of getting a screen shot + doesn't work, and we need to use an external program. This is only + used when running under X11 on macOS. If it's a Cocoa build, this + path is not taken, and OSX/grabclient-osx.m is used instead. + */ +# define USE_EXTERNAL_SCREEN_GRABBER +# define GETIMAGE_SCREEN_PROGRAM "screencapture" + +#elif defined(HAVE_REAL_X11) + /* Under real X11, we can grab the screen directly. Under XWayland, + we call out to an external program. */ +# define USE_EXTERNAL_SCREEN_GRABBER +# define GETIMAGE_SCREEN_PROGRAM "grim" +#endif static int @@ -246,7 +268,8 @@ xscreensaver_window_p (Display *dpy, Window window) /* Figure out what kind of scaling/positioning we ought to do to display a src-sized image in a dest-sized window/pixmap. Returns the width and height to which the image should be scaled, and the position where - it should be displayed to center it. + it should be displayed to center it. The result may be letterboxed + or pillarboxed to center it, as well as being scaled. */ static void compute_image_scaling (int src_w, int src_h, @@ -1049,7 +1072,8 @@ display_file (Screen *screen, Window window, Drawable drawable, to run. Returned pathname may be relative to 'directory', or absolute. */ static char * -get_filename_1 (Screen *screen, const char *directory, grab_type type, +get_filename_1 (Screen *screen, Window window, + const char *directory, grab_type type, Bool verbose_p) { Display *dpy = DisplayOfScreen (screen); @@ -1086,15 +1110,18 @@ get_filename_1 (Screen *screen, const char *directory, grab_type type, static char rect[100]; if (!tmpdir) tmpdir = "/tmp"; - /* Grab all screens */ - XGetWindowAttributes (dpy, XRootWindowOfScreen (screen), &xgwa); - sprintf (rect, "%d,%d,%d,%d", xgwa.x, xgwa.y, xgwa.width, xgwa.height); + XGetWindowAttributes (dpy, window, &xgwa); + window_root_offset (dpy, window, &xgwa.x, &xgwa.y); outfile = (char *) malloc (strlen(tmpdir) + 100); sprintf (outfile, "%s/xscreensaver.%08x.png", tmpdir, random() % 0xFFFFFFFF); - av[ac++] = GETIMAGE_SCREEN_PROGRAM; +#if defined(HAVE_MACOS_X11) + + sprintf (rect, "%d,%d,%d,%d", xgwa.x, xgwa.y, xgwa.width, xgwa.height); + + av[ac++] = GETIMAGE_SCREEN_PROGRAM; /* "screencapture" */ av[ac++] = "-x"; /* no sound */ av[ac++] = "-C"; /* capture mouse */ av[ac++] = "-R"; /* rect */ @@ -1102,9 +1129,24 @@ get_filename_1 (Screen *screen, const char *directory, grab_type type, av[ac++] = "-t"; /* file type */ av[ac++] = "png"; av[ac++] = outfile; + +# elif defined(HAVE_REAL_X11) + + sprintf (rect, "%d,%d %dx%d", xgwa.x, xgwa.y, xgwa.width, xgwa.height); + + av[ac++] = GETIMAGE_SCREEN_PROGRAM; /* "grim" */ + av[ac++] = "-c"; /* capture mouse */ + av[ac++] = "-g"; /* geometry */ + av[ac++] = rect; + av[ac++] = "-t"; /* file type */ + av[ac++] = "png"; + av[ac++] = outfile; +# else +# error USE_EXTERNAL_SCREEN_GRABBER is wrong +# endif } break; -# endif +# endif /* USE_EXTERNAL_SCREEN_GRABBER */ default: abort(); @@ -1183,7 +1225,9 @@ get_filename_1 (Screen *screen, const char *directory, grab_type type, L = strlen (buf); while (L && buf[L-1] == '\n') buf[--L] = 0; - + + /* In GRAB_DESK mode, outfile is already set. + Otherwise, is must be printed by the subprocess. */ if (! outfile) { if (!*buf) return 0; @@ -1205,9 +1249,11 @@ get_filename_1 (Screen *screen, const char *directory, grab_type type, if (stat (outfile_full, &st)) { - fprintf (stderr, "%s: file does not exist: \"%s\"\n", - blurb(), outfile_full); - free (outfile); + if (verbose_p) + fprintf (stderr, "%s: file does not exist: \"%s\"\n", + blurb(), outfile_full); + if (outfile_full != outfile) + free (outfile); outfile = 0; } @@ -1224,9 +1270,10 @@ get_filename_1 (Screen *screen, const char *directory, grab_type type, /* Returns a pathname to an image file. Free the string when you're done. */ static char * -get_filename (Screen *screen, const char *directory, Bool verbose_p) +get_filename (Screen *screen, Window window, const char *directory, + Bool verbose_p) { - return get_filename_1 (screen, directory, GRAB_FILE, verbose_p); + return get_filename_1 (screen, window, directory, GRAB_FILE, verbose_p); } @@ -1234,31 +1281,32 @@ get_filename (Screen *screen, const char *directory, Bool verbose_p) Delete that file when you are done with it (and free the string.) */ static char * -get_video_filename (Screen *screen, Bool verbose_p) +get_video_filename (Screen *screen, Window window, Bool verbose_p) { - return get_filename_1 (screen, 0, GRAB_VIDEO, verbose_p); + return get_filename_1 (screen, window, 0, GRAB_VIDEO, verbose_p); } -/* Grabs a desktop image to a file, and returns a pathname to that file. +/* Grabs a screenshot to a file, and returns a pathname to that file. + It will be the size of the provided window. Delete that file when you are done with it (and free the string.) */ # ifdef USE_EXTERNAL_SCREEN_GRABBER static char * -get_desktop_filename (Screen *screen, Bool verbose_p) +get_desktop_filename (Screen *screen, Window window, Bool verbose_p) { - return get_filename_1 (screen, 0, GRAB_DESK, verbose_p); + return get_filename_1 (screen, window, 0, GRAB_DESK, verbose_p); } #endif /* USE_EXTERNAL_SCREEN_GRABBER */ /* Grabs a video frame, and renders it on the Drawable. - Returns False if it fails; + Returns False if it fails. */ static Bool display_video (Screen *screen, Window window, Drawable drawable, Bool verbose_p, XRectangle *geom_ret) { - char *filename = get_video_filename (screen, verbose_p); + char *filename = get_video_filename (screen, window, verbose_p); Bool status; if (!filename) @@ -1290,6 +1338,7 @@ display_video (Screen *screen, Window window, Drawable drawable, started with -install, we need to copy the contents of the default colormap into the screensaver's colormap. */ +# ifndef HAVE_MACOS_X11 static void copy_default_colormap_contents (Screen *screen, Colormap to_cmap, @@ -1372,6 +1421,7 @@ copy_default_colormap_contents (Screen *screen, free (new_colors); free (pixels); } +#endif /* !HAVE_MACOS_X11 */ /* Install the colormaps of all visible windows, deepest first. @@ -1428,7 +1478,7 @@ static void raise_window (Display *dpy, Window window, Bool dont_wait, Bool verbose_p) { if (verbose_p) - fprintf(stderr, "%s: raising window 0x%08lX (%s)\n", + fprintf(stderr, "%s: raising window 0x%0lX (%s)\n", blurb(), (unsigned long) window, (dont_wait ? "not waiting" : "waiting")); @@ -1464,22 +1514,23 @@ raise_window (Display *dpy, Window window, Bool dont_wait, Bool verbose_p) /* Returns a pixmap of a screenshot, that is the size of the window and covers that window's extent. - This does it the hard way: by unmapping the window, waiting an arbitrary - amount of time, copying some bits from the root, then re-mapping the - window. + This unmaps the window, waits an arbitrary amount of time, grabs bits, + then re-maps the window. Grabbing bits happens either with XCopyArea + (on real X11) or by running an external program (macOS or Wayland). */ static Pixmap -grab_screen_image_xcopyarea (Screen *screen, Window window, Bool verbose_p) +grab_screen_image (Screen *screen, Window window, Bool cache_p, Bool verbose_p) { Display *dpy = DisplayOfScreen (screen); XWindowAttributes xgwa; Window real_root; Bool root_p; Bool saver_p; + Bool mapped_p; double unmap_time = 0; - Pixmap pixmap = None; - XGCValues gcv; - GC gc; + char *filename = 0; + Pixmap screenshot = None; + XRectangle geom; real_root = XRootWindowOfScreen (screen); /* not vroot */ root_p = (window == real_root); @@ -1487,46 +1538,55 @@ grab_screen_image_xcopyarea (Screen *screen, Window window, Bool verbose_p) XGetWindowAttributes (dpy, window, &xgwa); screen = xgwa.screen; + mapped_p = (xgwa.map_state == IsViewable); + + if (!cache_p) + { + /* This is maybe overloading the meaning of --cache, but for fading + to work, we need to be able to grab a screenshot that includes + the window itself. This only comes into effect when using an + external screen grabber. */ + mapped_p = 0; + unmap_time = 0; + } if (root_p) unmap_time = 0; /* real root window needs no delay */ - else if (saver_p) + else if (!mapped_p) + unmap_time = 0; /* not currently on screen */ + else { unmap_time = get_float_resource (dpy, "grabRootDelay", "Seconds"); if (unmap_time <= 0.00001 || unmap_time > 20) + unmap_time = 0.33; - /* 2022, Raspbian 11.5: after we call XUnmapWindow, it takes nearly - *five seconds* for the framebuffer to update! If we delay for less - than that, the window grabs an image of itself, which usually means - black. One would normally suspect the compositor of being - responsible for these sorts of shenanigans, but this is under - LXDE... - - Oddly, running Debian 11.4 under VirtualBox (meaning slowwwwww) - does not have this problem, and a delay of 0.33 is plenty. - - This problem does not afflict driver/fade.c as it just uses - XCopyArea directly without needing to wait for other processes - to react to the XUnmapWindow and re-paint. - - This nonsense is what led me to write screenshot.c and used a - cached screenshot image instead. - */ + /* 2022, Raspbian 11.5: after we call XUnmapWindow, it takes nearly + *five seconds* for the framebuffer to update! If we delay for less + than that, the window grabs an image of itself, which usually means + black. One would normally suspect the compositor of being + responsible for these sorts of shenanigans, but this is under + LXDE... + + Oddly, running Debian 11.4 under VirtualBox (meaning slowwwwww) + does not have this problem, and a delay of 0.33 is plenty. + + This problem does not afflict driver/fade.c as it just uses + XCopyArea directly without needing to wait for other processes + to react to the XUnmapWindow and re-paint. + + This nonsense is what led me to write screenshot.c and used a + cached screenshot image instead. + */ + if (saver_p && unmap_time < 5.0) unmap_time = 5.0; } - else /* managed window */ - { - unmap_time = get_float_resource (dpy, "grabWindowDelay", "Seconds"); - if (unmap_time <= 0.00001 || unmap_time > 20) - unmap_time = 0.33; - } if (verbose_p) { - fprintf (stderr, "\n%s: window 0x%08lX" - " root: %d saver: %d wait: %.1f\n", + fprintf (stderr, "%s: window 0x%0lX" + " root: %d saver: %d map: %d wait: %.1f\n", blurb(), (unsigned long) window, - root_p, saver_p, unmap_time); + root_p, saver_p, mapped_p, unmap_time); if (xgwa.visual->class != TrueColor) { @@ -1537,211 +1597,164 @@ grab_screen_image_xcopyarea (Screen *screen, Window window, Bool verbose_p) } - if (!root_p && !top_level_window_p (screen, window)) + if (!root_p && + mapped_p && + !top_level_window_p (screen, window)) { if (verbose_p) - fprintf (stderr, "%s: not a top-level window: 0x%08lX: not grabbing\n", + fprintf (stderr, "%s: not a top-level window: 0x%0lX: not grabbing\n", blurb(), (unsigned long) window); return None; } - if (unmap_time > 0) { if (verbose_p) - fprintf (stderr, "%s: unmapping 0x%08lX\n", blurb(), + fprintf (stderr, "%s: unmapping 0x%0lX\n", blurb(), (unsigned long) window); XUnmapWindow (dpy, window); if (xgwa.visual->class != TrueColor) install_screen_colormaps (screen); XSync (dpy, True); + if (verbose_p) + fprintf (stderr, "%s: sleeping %.02f\n", blurb(), unmap_time); /* wait for everyone to swap in and handle exposes */ usleep ((unsigned long) (unmap_time * 1000000)); } - /* Now that the window is off the screen and we have delayed, grab bits. + /* Now that the window is off the screen and we have delayed, grab bits, + then put the window back. */ - pixmap = XCreatePixmap (dpy, window, xgwa.width, xgwa.height, xgwa.depth); - - if (!root_p && xgwa.visual->class != TrueColor) - copy_default_colormap_contents (screen, xgwa.colormap, xgwa.visual, - verbose_p); - - gcv.function = GXcopy; - gcv.subwindow_mode = IncludeInferiors; - gc = XCreateGC (dpy, window, GCFunction | GCSubwindowMode, &gcv); - XCopyArea (dpy, real_root, pixmap, gc, - xgwa.x, xgwa.y, xgwa.width, xgwa.height, 0, 0); - XFreeGC (dpy, gc); - - if (!root_p && xgwa.visual->class != TrueColor) - copy_default_colormap_contents (screen, xgwa.colormap, xgwa.visual, - verbose_p); - - raise_window (dpy, window, saver_p, verbose_p); - - /* Generally it's bad news to call XInstallColormap() explicitly, - but this file does a lot of sleazy stuff already... This is to - make sure that the window's colormap is installed, even in the - case where the window is OverrideRedirect. */ - if (xgwa.colormap && xgwa.visual->class != TrueColor) - XInstallColormap (dpy, xgwa.colormap); +# ifdef USE_EXTERNAL_SCREEN_GRABBER - if (verbose_p) - fprintf (stderr, "%s: grabbed screenshot to %s window\n", blurb(), - (root_p ? "real root" : saver_p ? "saver" : "top-level")); +# if defined(HAVE_REAL_X11) + if (getenv ("WAYLAND_DISPLAY") || getenv ("WAYLAND_SOCKET")) +# endif - return pixmap; -} + { + /* Wayland or macOS: Grab screen via external program. */ + if (verbose_p) + fprintf (stderr, "%s: grabbing via \"%s\"\n", blurb(), + GETIMAGE_SCREEN_PROGRAM); + filename = get_desktop_filename (screen, window, verbose_p); -#ifdef USE_EXTERNAL_SCREEN_GRABBER -static Pixmap -grab_screen_image_external (Screen *screen, Window window, Bool verbose_p) -{ - Display *dpy = DisplayOfScreen (screen); - Window real_root; - Bool root_p; - Bool saver_p; - double unmap_time = 0; - XWindowAttributes xgwa; - char *filename; - Pixmap full, screenshot; - XGCValues gcv; - GC gc; - XRectangle geom; + /* We can raise the window before parsing the file. */ + if (unmap_time > 0) + raise_window (dpy, window, saver_p, verbose_p); + unmap_time = 0; - real_root = XRootWindowOfScreen (screen); /* not vroot */ - root_p = (window == real_root); - saver_p = xscreensaver_window_p (dpy, window); + if (!filename) + { + /* if (verbose_p) */ + fprintf (stderr, "%s: screenshot via \"%s\" failed\n", blurb(), + GETIMAGE_SCREEN_PROGRAM); + return None; + } - /* Using "screencapture" from within xscreensaver-settings doesn't work, - since we don't unmap the xscreensaver-settings window first. */ - /* if (!root_p && !saver_p) return None; */ + /* Read the file into a pixmap the size of the destination drawable. */ + screenshot = XCreatePixmap (dpy, window, xgwa.width, xgwa.height, + xgwa.depth); + if (!screenshot) + { + unlink (filename); + return None; + } - XGetWindowAttributes (dpy, XRootWindowOfScreen (screen), &xgwa); - screen = xgwa.screen; +# if defined(HAVE_GDK_PIXBUF) + if (! read_file_gdk (screen, window, screenshot, filename, + verbose_p, &geom)) + { + unlink (filename); + XFreePixmap (dpy, screenshot); + return None; + } +# elif defined(HAVE_JPEGLIB) + if (! read_file_jpeglib (screen, window, screenshot, filename, + verbose_p, &geom)) + { + unlink (filename); + XFreePixmap (dpy, screenshot); + return None; + } +# else /* !(HAVE_GDK_PIXBUF || HAVE_JPEGLIB) */ + /* shouldn't get here if we have no image-loading methods available. */ + abort(); +# endif /* !(HAVE_GDK_PIXBUF || HAVE_JPEGLIB) */ - if (root_p) - unmap_time = 0; /* real root window needs no delay */ - else - { - unmap_time = get_float_resource (dpy, "grabWindowDelay", "Seconds"); - if (unmap_time <= 0.00001 || unmap_time > 20) + if (unlink (filename)) { - unmap_time = 0.33; - if (saver_p) unmap_time = 5.0; /* WTF */ + char buf[512]; + sprintf (buf, "%s: rm %.100s", blurb(), filename); + perror (buf); } - } + else if (verbose_p) + fprintf (stderr, "%s: rm %s\n", blurb(), filename); - if (unmap_time > 0) - { if (verbose_p) - fprintf (stderr, "%s: unmapping 0x%08lX\n", blurb(), - (unsigned long) window); - XUnmapWindow (dpy, window); - if (xgwa.visual->class != TrueColor) - install_screen_colormaps (screen); - if (verbose_p) - fprintf (stderr, "%s: sleeping %.02f\n", blurb(), unmap_time); - /* wait for everyone to swap in and handle exposes */ - usleep ((unsigned long) (unmap_time * 1000000)); + fprintf (stderr, "%s: screenshot %dx%d+%d+%d via \"%s\"\n", blurb(), + xgwa.width, xgwa.height, xgwa.x, xgwa.y, + GETIMAGE_SCREEN_PROGRAM); } +# endif /* USE_EXTERNAL_SCREEN_GRABBER */ +# ifndef HAVE_MACOS_X11 + else + { + /* Real X11: Grab screen via XCopyArea. */ - /* Now that the window is off the screen and we have delayed, grab bits, - then put the window pack. - */ - filename = get_desktop_filename (screen, verbose_p); - raise_window (dpy, window, saver_p, verbose_p); + XGCValues gcv; + GC gc; - if (!filename) - { if (verbose_p) - fprintf (stderr, "%s: screenshot via \"%s\" failed\n", blurb(), - GETIMAGE_SCREEN_PROGRAM); - return False; - } + fprintf (stderr, "%s: grabbing via XCopyArea\n", blurb()); - /* Read the file into a pixmap the size of the root window. - If there are 2 screens, pixmap will be twice as wide as it needs - to be and the screenshot will be centered on it, per 'geom'. - */ - full = XCreatePixmap (dpy, window, xgwa.width, xgwa.height, xgwa.depth); - if (!full) - { - unlink (filename); - return False; - } + screenshot = XCreatePixmap (dpy, window, xgwa.width, xgwa.height, + xgwa.depth); -# if defined(HAVE_GDK_PIXBUF) - if (! read_file_gdk (screen, window, full, filename, verbose_p, &geom)) - { - unlink (filename); - XFreePixmap (dpy, full); - return False; - } -# elif defined(HAVE_JPEGLIB) - if (! read_file_jpeglib (screen, window, full, filename, verbose_p, &geom)) - { - unlink (filename); - XFreePixmap (dpy, full); - return False; - } -# else /* !(HAVE_GDK_PIXBUF || HAVE_JPEGLIB) */ - /* shouldn't get here if we have no image-loading methods available. */ - abort(); -# endif /* !(HAVE_GDK_PIXBUF || HAVE_JPEGLIB) */ - - if (unlink (filename)) - { - char buf[512]; - sprintf (buf, "%s: rm %.100s", blurb(), filename); - perror (buf); - } - else if (verbose_p) - fprintf (stderr, "%s: rm %s\n", blurb(), filename); + if (!root_p && xgwa.visual->class != TrueColor) + copy_default_colormap_contents (screen, xgwa.colormap, xgwa.visual, + verbose_p); - /* Retrieve the sub-rect of the full screen image that the window covers. */ - XGetWindowAttributes (dpy, window, &xgwa); - if (!root_p && !saver_p) - { - /* Kludge for running inside xscreensaver-settings: show the upper - left of the screen instead, since otherwise we would have captured - the black rectangle that we are about to render into. */ - xgwa.x = xgwa.y = 0; - } - else - { + gcv.function = GXcopy; + gcv.subwindow_mode = IncludeInferiors; + gc = XCreateGC (dpy, window, GCFunction | GCSubwindowMode, &gcv); window_root_offset (dpy, window, &xgwa.x, &xgwa.y); + XCopyArea (dpy, real_root, screenshot, gc, + xgwa.x, xgwa.y, xgwa.width, xgwa.height, 0, 0); + XFreeGC (dpy, gc); + + if (!root_p && xgwa.visual->class != TrueColor) + copy_default_colormap_contents (screen, xgwa.colormap, xgwa.visual, + verbose_p); } +# endif /* !HAVE_MACOS_X11 */ - screenshot = XCreatePixmap (dpy, window, xgwa.width, xgwa.height, - xgwa.depth); - gc = XCreateGC (dpy, screenshot, 0, &gcv); - XCopyArea (dpy, full, screenshot, gc, - geom.x + xgwa.x, geom.y + xgwa.y, - xgwa.width, xgwa.height, 0, 0); - XFreeGC (dpy, gc); - XFreePixmap (dpy, full); + if (unmap_time > 0) + raise_window (dpy, window, saver_p, verbose_p); + + /* Generally it's bad news to call XInstallColormap() explicitly, + but this file does a lot of sleazy stuff already... This is to + make sure that the window's colormap is installed, even in the + case where the window is OverrideRedirect. */ + if (xgwa.colormap && xgwa.visual->class != TrueColor) + XInstallColormap (dpy, xgwa.colormap); if (verbose_p) - fprintf (stderr, "%s: screenshot %dx%d+%d+%d via \"%s\"\n", blurb(), - xgwa.width, xgwa.height, xgwa.x, xgwa.y, - GETIMAGE_SCREEN_PROGRAM); + fprintf (stderr, "%s: grabbed screenshot to %s window\n", blurb(), + (root_p ? "real root" : saver_p ? "saver" : "top-level")); return screenshot; } -#endif /* USE_EXTERNAL_SCREEN_GRABBER */ -/* Grabs a desktop screen shot onto the drawable and possibly the window. +/* Grabs a desktop screen shot that is the size and position of the + Window (the bits under that window), and renders it onto the Drawable. If the window and drawable are not the same size, the image in the - drawable is scaled to fit. - Returns False if it fails. + drawable is scaled. Returns False if it fails. */ static Bool display_desktop (Screen *screen, Window window, Drawable drawable, - Bool verbose_p, XRectangle *geom_ret) + Bool cache_p, Bool verbose_p, XRectangle *geom_ret) { Display *dpy = DisplayOfScreen (screen); Window root; @@ -1754,24 +1767,22 @@ display_desktop (Screen *screen, Window window, Drawable drawable, if (verbose_p) fprintf (stderr, "%s: grabbing desktop image\n", blurb()); - screenshot = screenshot_load (dpy, window, verbose_p); + root = XRootWindowOfScreen (screen); /* not vroot */ -# ifdef USE_EXTERNAL_SCREEN_GRABBER - if (! screenshot) - screenshot = grab_screen_image_external (screen, window, verbose_p); -# endif /* USE_EXTERNAL_SCREEN_GRABBER */ + /* See if we already have a saved screenshot of the whole desktop; + if so, grab a sub-rectangle from that, of *this* window. */ + if (cache_p) + screenshot = screenshot_load (dpy, window, verbose_p); - if (!screenshot && !top_level_window_p (screen, window)) + /* Otherwise, grab a new one. */ + if (! screenshot) { if (verbose_p) - fprintf (stderr, "%s: 0x%x not top-level: can't grab desktop\n", - blurb(), (unsigned int) window); - return False; + fprintf (stderr, "%s: no saved screenshot on 0x%lx\n", blurb(), + window); + screenshot = grab_screen_image (screen, window, cache_p, verbose_p); } - if (! screenshot) - screenshot = grab_screen_image_xcopyarea (screen, window, verbose_p); - if (!screenshot) return False; @@ -1836,7 +1847,7 @@ drawable_miniscule_p (Display *dpy, Drawable drawable) /* Grabs an image (from a file, video, or the desktop) and renders it on - the Drawable. If `file' is specified, always use that file. Otherwise, + the Drawable. If 'file' is specified, always use that file. Otherwise, select randomly, based on the other arguments. */ static void @@ -1844,6 +1855,7 @@ get_image (Screen *screen, Window window, Drawable drawable, Bool verbose_p, Bool desk_p, + Bool cache_p, Bool video_p, Bool image_p, const char *dir, @@ -1899,7 +1911,7 @@ get_image (Screen *screen, if (file) { - desk_p = False; + desk_p = False; video_p = False; image_p = True; } @@ -1922,19 +1934,11 @@ get_image (Screen *screen, image_p = False; } + # ifndef _VROOT_H_ # error Error! This file definitely needs vroot.h! # endif - /* We can grab desktop images (using the normal X11 method) if: - - the window is the real root window; - - the window is a toplevel window. - We cannot grab desktop images that way if: - - the window is a non-top-level window. - - Under X11 on macOS, desktops are just like loaded image files. - Under Cocoa on macOS, this code is not used at all. - */ if (! (desk_p || video_p || image_p)) which = GRAB_BARS; else @@ -1964,7 +1968,7 @@ get_image (Screen *screen, */ if (which == GRAB_FILE && !file) { - file = get_filename (screen, dir, verbose_p); + file = get_filename (screen, window, dir, verbose_p); if (!file) { which = GRAB_BARS; @@ -2000,7 +2004,8 @@ get_image (Screen *screen, break; case GRAB_DESK: - if (! display_desktop (screen, window, drawable, verbose_p, &geom)) + if (! display_desktop (screen, window, drawable, cache_p, + verbose_p, &geom)) goto COLORBARS; file_prop = "desktop"; break; @@ -2069,6 +2074,8 @@ get_image (Screen *screen, } if (absfile) free (absfile); + + XSync (dpy, False); } @@ -2152,6 +2159,7 @@ mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks, " --images / --no-images whether to allow image file loading\n" \ " --video / --no-video whether to allow video grabs\n" \ " --desktop / --no-desktop whether to allow desktop screen grabs\n"\ + " --cache / --no-cache whether to cache the desktop image\n" \ " --directory where to find image files to load\n" \ " --file load this image file\n" \ "\n" \ @@ -2177,6 +2185,7 @@ main (int argc, char **argv) int i; Bool verbose_p, grab_desktop_p, grab_video_p, random_image_p; + Bool cache_desktop_p; char *image_directory; progname = argv[0]; @@ -2231,6 +2240,8 @@ main (int argc, char **argv) grab_video_p = get_boolean_resource(dpy, "grabVideoFrames", "Boolean"); random_image_p = get_boolean_resource(dpy, "chooseRandomImages", "Boolean"); image_directory = get_string_resource (dpy, "imageDirectory", "String"); + cache_desktop_p = True; + if (!image_directory) image_directory = ""; if (!strncmp (image_directory, "~/", 2)) { @@ -2257,14 +2268,22 @@ main (int argc, char **argv) /* Have to re-process these, or else the .xscreensaver file has priority over the command line... */ - if (!strcmp (argv[i], "-v") || !strcmp (argv[i], "-verbose")) + if (!strcmp (argv[i], "-v") || + !strcmp (argv[i], "-vv") || + !strcmp (argv[i], "-vvv") || + !strcmp (argv[i], "-vvvv") || + !strcmp (argv[i], "-vvvvv") || + !strcmp (argv[i], "-vvvvvv") || + !strcmp (argv[i], "-verbose")) verbose_p = True; - else if (!strcmp (argv[i], "-desktop")) grab_desktop_p = True; - else if (!strcmp (argv[i], "-no-desktop")) grab_desktop_p = False; - else if (!strcmp (argv[i], "-video")) grab_video_p = True; - else if (!strcmp (argv[i], "-no-video")) grab_video_p = False; - else if (!strcmp (argv[i], "-images")) random_image_p = True; - else if (!strcmp (argv[i], "-no-images")) random_image_p = False; + else if (!strcmp (argv[i], "-desktop")) grab_desktop_p = True; + else if (!strcmp (argv[i], "-no-desktop")) grab_desktop_p = False; + else if (!strcmp (argv[i], "-cache")) cache_desktop_p = True; + else if (!strcmp (argv[i], "-no-cache")) cache_desktop_p = False; + else if (!strcmp (argv[i], "-video")) grab_video_p = True; + else if (!strcmp (argv[i], "-no-video")) grab_video_p = False; + else if (!strcmp (argv[i], "-images")) random_image_p = True; + else if (!strcmp (argv[i], "-no-images")) random_image_p = False; else if (!strcmp (argv[i], "-file")) file = argv[++i]; else if (!strcmp (argv[i], "-directory") || !strcmp (argv[i], "-dir")) image_directory = argv[++i]; @@ -2352,7 +2371,7 @@ main (int argc, char **argv) if (!drawable) drawable = window; get_image (screen, window, drawable, verbose_p, - grab_desktop_p, grab_video_p, random_image_p, + grab_desktop_p, cache_desktop_p, grab_video_p, random_image_p, image_directory, file); XSync (dpy, False); exit (0); diff --git a/po/POTFILES.in b/po/POTFILES.in index d5a98334..cc017eea 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -1,4 +1,4 @@ -# Auto-generated: Mon Apr 28 12:45:42 PDT 2025 +# Auto-generated: Tue Jul 1 18:29:55 PDT 2025 driver/demo-Gtk-conf.c driver/demo-Gtk.c driver/demo.ui diff --git a/po/be.po b/po/be.po index 4a1ef197..064846e5 100644 --- a/po/be.po +++ b/po/be.po @@ -7088,7 +7088,7 @@ msgid "" "family of 6502 chips were used throughout the 70's and 80's in machines such " "as the Atari 2600, Commodore PET, VIC20 and C64, Apple ][, and the NES. Some " "example programs are included, and it can also read in an assembly file as " -"input. Original JavaScript Version by Stian Soreng: http://www.6502asm.com/. " +"input. Original JavaScript Version by Stian Soreng. " "Ported to XScreenSaver by Jeremy English. Written by Stian Soreng and Jeremy " "English; 2007." msgstr "" diff --git a/po/da.po b/po/da.po index d67f7e66..0c5a8418 100644 --- a/po/da.po +++ b/po/da.po @@ -7934,7 +7934,7 @@ msgid "" "family of 6502 chips were used throughout the 70's and 80's in machines such " "as the Atari 2600, Commodore PET, VIC20 and C64, Apple ][, and the NES. Some " "example programs are included, and it can also read in an assembly file as " -"input. Original JavaScript Version by Stian Soreng: http://www.6502asm.com/. " +"input. Original JavaScript Version by Stian Soreng. " "Ported to XScreenSaver by Jeremy English. Written by Stian Soreng and Jeremy " "English; 2007." msgstr "" diff --git a/po/de.po b/po/de.po index 7390a56d..ee403a0d 100644 --- a/po/de.po +++ b/po/de.po @@ -7854,7 +7854,7 @@ msgid "" "family of 6502 chips were used throughout the 70's and 80's in machines such " "as the Atari 2600, Commodore PET, VIC20 and C64, Apple ][, and the NES. Some " "example programs are included, and it can also read in an assembly file as " -"input. Original JavaScript Version by Stian Soreng: http://www.6502asm.com/. " +"input. Original JavaScript Version by Stian Soreng. " "Ported to XScreenSaver by Jeremy English. Written by Stian Soreng and Jeremy " "English; 2007." msgstr "" diff --git a/po/el.po b/po/el.po index c3effd70..429ed373 100644 --- a/po/el.po +++ b/po/el.po @@ -7382,7 +7382,7 @@ msgid "" "family of 6502 chips were used throughout the 70's and 80's in machines such " "as the Atari 2600, Commodore PET, VIC20 and C64, Apple ][, and the NES. Some " "example programs are included, and it can also read in an assembly file as " -"input. Original JavaScript Version by Stian Soreng: http://www.6502asm.com/. " +"input. Original JavaScript Version by Stian Soreng. " "Ported to XScreenSaver by Jeremy English. Written by Stian Soreng and Jeremy " "English; 2007." msgstr "" diff --git a/po/es.po b/po/es.po index 9788b923..9decfda0 100644 --- a/po/es.po +++ b/po/es.po @@ -7833,7 +7833,7 @@ msgid "" "family of 6502 chips were used throughout the 70's and 80's in machines such " "as the Atari 2600, Commodore PET, VIC20 and C64, Apple ][, and the NES. Some " "example programs are included, and it can also read in an assembly file as " -"input. Original JavaScript Version by Stian Soreng: http://www.6502asm.com/. " +"input. Original JavaScript Version by Stian Soreng. " "Ported to XScreenSaver by Jeremy English. Written by Stian Soreng and Jeremy " "English; 2007." msgstr "" diff --git a/po/et.po b/po/et.po index f520f551..aa701aff 100644 --- a/po/et.po +++ b/po/et.po @@ -7206,7 +7206,7 @@ msgid "" "family of 6502 chips were used throughout the 70's and 80's in machines such " "as the Atari 2600, Commodore PET, VIC20 and C64, Apple ][, and the NES. Some " "example programs are included, and it can also read in an assembly file as " -"input. Original JavaScript Version by Stian Soreng: http://www.6502asm.com/. " +"input. Original JavaScript Version by Stian Soreng. " "Ported to XScreenSaver by Jeremy English. Written by Stian Soreng and Jeremy " "English; 2007." msgstr "" diff --git a/po/fi.po b/po/fi.po index e8294856..568f2f50 100644 --- a/po/fi.po +++ b/po/fi.po @@ -7146,7 +7146,7 @@ msgid "" "family of 6502 chips were used throughout the 70's and 80's in machines such " "as the Atari 2600, Commodore PET, VIC20 and C64, Apple ][, and the NES. Some " "example programs are included, and it can also read in an assembly file as " -"input. Original JavaScript Version by Stian Soreng: http://www.6502asm.com/. " +"input. Original JavaScript Version by Stian Soreng. " "Ported to XScreenSaver by Jeremy English. Written by Stian Soreng and Jeremy " "English; 2007." msgstr "" diff --git a/po/fr.po b/po/fr.po index eccf9012..bcbfe4c1 100644 --- a/po/fr.po +++ b/po/fr.po @@ -7842,7 +7842,7 @@ msgid "" "family of 6502 chips were used throughout the 70's and 80's in machines such " "as the Atari 2600, Commodore PET, VIC20 and C64, Apple ][, and the NES. Some " "example programs are included, and it can also read in an assembly file as " -"input. Original JavaScript Version by Stian Soreng: http://www.6502asm.com/. " +"input. Original JavaScript Version by Stian Soreng. " "Ported to XScreenSaver by Jeremy English. Written by Stian Soreng and Jeremy " "English; 2007." msgstr "" diff --git a/po/hu.po b/po/hu.po index 753e56aa..b6d9bac5 100644 --- a/po/hu.po +++ b/po/hu.po @@ -7904,7 +7904,7 @@ msgid "" "family of 6502 chips were used throughout the 70's and 80's in machines such " "as the Atari 2600, Commodore PET, VIC20 and C64, Apple ][, and the NES. Some " "example programs are included, and it can also read in an assembly file as " -"input. Original JavaScript Version by Stian Soreng: http://www.6502asm.com/. " +"input. Original JavaScript Version by Stian Soreng. " "Ported to XScreenSaver by Jeremy English. Written by Stian Soreng and Jeremy " "English; 2007." msgstr "" diff --git a/po/it.po b/po/it.po index 05e8ea2b..e50bd097 100644 --- a/po/it.po +++ b/po/it.po @@ -7162,7 +7162,7 @@ msgid "" "family of 6502 chips were used throughout the 70's and 80's in machines such " "as the Atari 2600, Commodore PET, VIC20 and C64, Apple ][, and the NES. Some " "example programs are included, and it can also read in an assembly file as " -"input. Original JavaScript Version by Stian Soreng: http://www.6502asm.com/. " +"input. Original JavaScript Version by Stian Soreng. " "Ported to XScreenSaver by Jeremy English. Written by Stian Soreng and Jeremy " "English; 2007." msgstr "" diff --git a/po/ja.po b/po/ja.po index 58391aae..0a76fb03 100644 --- a/po/ja.po +++ b/po/ja.po @@ -7211,7 +7211,7 @@ msgid "" "family of 6502 chips were used throughout the 70's and 80's in machines such " "as the Atari 2600, Commodore PET, VIC20 and C64, Apple ][, and the NES. Some " "example programs are included, and it can also read in an assembly file as " -"input. Original JavaScript Version by Stian Soreng: http://www.6502asm.com/. " +"input. Original JavaScript Version by Stian Soreng. " "Ported to XScreenSaver by Jeremy English. Written by Stian Soreng and Jeremy " "English; 2007." msgstr "" diff --git a/po/ko.po b/po/ko.po index ad80cc7d..7bc27766 100644 --- a/po/ko.po +++ b/po/ko.po @@ -7520,7 +7520,7 @@ msgid "" "family of 6502 chips were used throughout the 70's and 80's in machines such " "as the Atari 2600, Commodore PET, VIC20 and C64, Apple ][, and the NES. Some " "example programs are included, and it can also read in an assembly file as " -"input. Original JavaScript Version by Stian Soreng: http://www.6502asm.com/. " +"input. Original JavaScript Version by Stian Soreng. " "Ported to XScreenSaver by Jeremy English. Written by Stian Soreng and Jeremy " "English; 2007." msgstr "" diff --git a/po/lt.po b/po/lt.po index 6a787643..64cdf9e4 100644 --- a/po/lt.po +++ b/po/lt.po @@ -7212,7 +7212,7 @@ msgid "" "family of 6502 chips were used throughout the 70's and 80's in machines such " "as the Atari 2600, Commodore PET, VIC20 and C64, Apple ][, and the NES. Some " "example programs are included, and it can also read in an assembly file as " -"input. Original JavaScript Version by Stian Soreng: http://www.6502asm.com/. " +"input. Original JavaScript Version by Stian Soreng. " "Ported to XScreenSaver by Jeremy English. Written by Stian Soreng and Jeremy " "English; 2007." msgstr "" diff --git a/po/nb.po b/po/nb.po index 0dce3512..d95f8ac0 100644 --- a/po/nb.po +++ b/po/nb.po @@ -7479,7 +7479,7 @@ msgid "" "family of 6502 chips were used throughout the 70's and 80's in machines such " "as the Atari 2600, Commodore PET, VIC20 and C64, Apple ][, and the NES. Some " "example programs are included, and it can also read in an assembly file as " -"input. Original JavaScript Version by Stian Soreng: http://www.6502asm.com/. " +"input. Original JavaScript Version by Stian Soreng. " "Ported to XScreenSaver by Jeremy English. Written by Stian Soreng and Jeremy " "English; 2007." msgstr "" diff --git a/po/nl.po b/po/nl.po index 80c428b2..2e0624de 100644 --- a/po/nl.po +++ b/po/nl.po @@ -7886,7 +7886,7 @@ msgid "" "family of 6502 chips were used throughout the 70's and 80's in machines such " "as the Atari 2600, Commodore PET, VIC20 and C64, Apple ][, and the NES. Some " "example programs are included, and it can also read in an assembly file as " -"input. Original JavaScript Version by Stian Soreng: http://www.6502asm.com/. " +"input. Original JavaScript Version by Stian Soreng. " "Ported to XScreenSaver by Jeremy English. Written by Stian Soreng and Jeremy " "English; 2007." msgstr "" @@ -7894,7 +7894,7 @@ msgstr "" "gedurende de de jaren 70 en 80 in machines zoals de Atari 2600, Commodore " "PET, VIC20 en C64, Apple ][ en de NES gebruikt. Enige voorbeeldprogramma's " "zijn bijgevoegd en het kan ook een assembly-bestand als invoer lezen. " -"Originele JavaScript versie door Stian Soreng: http://www.6502asm.com/. " +"Originele JavaScript versie door Stian Soreng. " "Overgebracht naar XScreenSaver door Jeremy English. Geschreven door Stian " "Soreng en Jeremy English; 2007." diff --git a/po/pl.po b/po/pl.po index 9156f5ed..560f1dc4 100644 --- a/po/pl.po +++ b/po/pl.po @@ -7605,7 +7605,7 @@ msgid "" "family of 6502 chips were used throughout the 70's and 80's in machines such " "as the Atari 2600, Commodore PET, VIC20 and C64, Apple ][, and the NES. Some " "example programs are included, and it can also read in an assembly file as " -"input. Original JavaScript Version by Stian Soreng: http://www.6502asm.com/. " +"input. Original JavaScript Version by Stian Soreng. " "Ported to XScreenSaver by Jeremy English. Written by Stian Soreng and Jeremy " "English; 2007." msgstr "" diff --git a/po/pt.po b/po/pt.po index 63651dd9..bc8a407b 100644 --- a/po/pt.po +++ b/po/pt.po @@ -7830,7 +7830,7 @@ msgid "" "family of 6502 chips were used throughout the 70's and 80's in machines such " "as the Atari 2600, Commodore PET, VIC20 and C64, Apple ][, and the NES. Some " "example programs are included, and it can also read in an assembly file as " -"input. Original JavaScript Version by Stian Soreng: http://www.6502asm.com/. " +"input. Original JavaScript Version by Stian Soreng. " "Ported to XScreenSaver by Jeremy English. Written by Stian Soreng and Jeremy " "English; 2007." msgstr "" diff --git a/po/pt_BR.po b/po/pt_BR.po index 3978248e..e088ada8 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -7407,7 +7407,7 @@ msgid "" "family of 6502 chips were used throughout the 70's and 80's in machines such " "as the Atari 2600, Commodore PET, VIC20 and C64, Apple ][, and the NES. Some " "example programs are included, and it can also read in an assembly file as " -"input. Original JavaScript Version by Stian Soreng: http://www.6502asm.com/. " +"input. Original JavaScript Version by Stian Soreng. " "Ported to XScreenSaver by Jeremy English. Written by Stian Soreng and Jeremy " "English; 2007." msgstr "" diff --git a/po/ru.po b/po/ru.po index 73a7a310..b0ad3f3c 100644 --- a/po/ru.po +++ b/po/ru.po @@ -7794,7 +7794,7 @@ msgid "" "family of 6502 chips were used throughout the 70's and 80's in machines such " "as the Atari 2600, Commodore PET, VIC20 and C64, Apple ][, and the NES. Some " "example programs are included, and it can also read in an assembly file as " -"input. Original JavaScript Version by Stian Soreng: http://www.6502asm.com/. " +"input. Original JavaScript Version by Stian Soreng. " "Ported to XScreenSaver by Jeremy English. Written by Stian Soreng and Jeremy " "English; 2007." msgstr "" @@ -7803,7 +7803,7 @@ msgstr "" "машинах, таких как Atari 2600, Commodore PET, VIC20 и C64, Apple] [ и NES. " "Включены некоторые примеры программ, об этом также можно прочитать в файле " "сборки в качестве входных данных. Оригинальная версия на JavaScript автора " -"Стиан Сёренг: http://www.6502asm.com/. Портирована на XScreenSaver Джереми " +"Стиан Сёренг. Портирована на XScreenSaver Джереми " "Инглиш. Автор: Стиан Сёренг и Джереми Инглиш; 2007." #: ../hacks/config/mapscroller.xml.h:1 diff --git a/po/sk.po b/po/sk.po index 742b0f3e..d579b4de 100644 --- a/po/sk.po +++ b/po/sk.po @@ -7482,7 +7482,7 @@ msgid "" "family of 6502 chips were used throughout the 70's and 80's in machines such " "as the Atari 2600, Commodore PET, VIC20 and C64, Apple ][, and the NES. Some " "example programs are included, and it can also read in an assembly file as " -"input. Original JavaScript Version by Stian Soreng: http://www.6502asm.com/. " +"input. Original JavaScript Version by Stian Soreng. " "Ported to XScreenSaver by Jeremy English. Written by Stian Soreng and Jeremy " "English; 2007." msgstr "" diff --git a/po/sv.po b/po/sv.po index 9ed0e415..d821ffcd 100644 --- a/po/sv.po +++ b/po/sv.po @@ -7695,7 +7695,7 @@ msgid "" "family of 6502 chips were used throughout the 70's and 80's in machines such " "as the Atari 2600, Commodore PET, VIC20 and C64, Apple ][, and the NES. Some " "example programs are included, and it can also read in an assembly file as " -"input. Original JavaScript Version by Stian Soreng: http://www.6502asm.com/. " +"input. Original JavaScript Version by Stian Soreng. " "Ported to XScreenSaver by Jeremy English. Written by Stian Soreng and Jeremy " "English; 2007." msgstr "" diff --git a/po/tr.po b/po/tr.po index 6df00206..e4bb96e6 100644 --- a/po/tr.po +++ b/po/tr.po @@ -7918,7 +7918,7 @@ msgid "" "family of 6502 chips were used throughout the 70's and 80's in machines such " "as the Atari 2600, Commodore PET, VIC20 and C64, Apple ][, and the NES. Some " "example programs are included, and it can also read in an assembly file as " -"input. Original JavaScript Version by Stian Soreng: http://www.6502asm.com/. " +"input. Original JavaScript Version by Stian Soreng. " "Ported to XScreenSaver by Jeremy English. Written by Stian Soreng and Jeremy " "English; 2007." msgstr "" diff --git a/po/uk.po b/po/uk.po index 7cbeeff5..4346d8e9 100644 --- a/po/uk.po +++ b/po/uk.po @@ -7057,7 +7057,7 @@ msgid "" "family of 6502 chips were used throughout the 70's and 80's in machines such " "as the Atari 2600, Commodore PET, VIC20 and C64, Apple ][, and the NES. Some " "example programs are included, and it can also read in an assembly file as " -"input. Original JavaScript Version by Stian Soreng: http://www.6502asm.com/. " +"input. Original JavaScript Version by Stian Soreng. " "Ported to XScreenSaver by Jeremy English. Written by Stian Soreng and Jeremy " "English; 2007." msgstr "" diff --git a/po/vi.po b/po/vi.po index 9ac8f2d4..2d9d5a8c 100644 --- a/po/vi.po +++ b/po/vi.po @@ -7594,7 +7594,7 @@ msgid "" "family of 6502 chips were used throughout the 70's and 80's in machines such " "as the Atari 2600, Commodore PET, VIC20 and C64, Apple ][, and the NES. Some " "example programs are included, and it can also read in an assembly file as " -"input. Original JavaScript Version by Stian Soreng: http://www.6502asm.com/. " +"input. Original JavaScript Version by Stian Soreng. " "Ported to XScreenSaver by Jeremy English. Written by Stian Soreng and Jeremy " "English; 2007." msgstr "" diff --git a/po/wa.po b/po/wa.po index 2226fa96..afa49fc7 100644 --- a/po/wa.po +++ b/po/wa.po @@ -7382,7 +7382,7 @@ msgid "" "family of 6502 chips were used throughout the 70's and 80's in machines such " "as the Atari 2600, Commodore PET, VIC20 and C64, Apple ][, and the NES. Some " "example programs are included, and it can also read in an assembly file as " -"input. Original JavaScript Version by Stian Soreng: http://www.6502asm.com/. " +"input. Original JavaScript Version by Stian Soreng. " "Ported to XScreenSaver by Jeremy English. Written by Stian Soreng and Jeremy " "English; 2007." msgstr "" diff --git a/po/zh_CN.po b/po/zh_CN.po index edb52ac2..fa5399f0 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -7615,7 +7615,7 @@ msgid "" "family of 6502 chips were used throughout the 70's and 80's in machines such " "as the Atari 2600, Commodore PET, VIC20 and C64, Apple ][, and the NES. Some " "example programs are included, and it can also read in an assembly file as " -"input. Original JavaScript Version by Stian Soreng: http://www.6502asm.com/. " +"input. Original JavaScript Version by Stian Soreng. " "Ported to XScreenSaver by Jeremy English. Written by Stian Soreng and Jeremy " "English; 2007." msgstr "" diff --git a/po/zh_TW.po b/po/zh_TW.po index ffd5c21a..31d0de18 100644 --- a/po/zh_TW.po +++ b/po/zh_TW.po @@ -7529,7 +7529,7 @@ msgid "" "family of 6502 chips were used throughout the 70's and 80's in machines such " "as the Atari 2600, Commodore PET, VIC20 and C64, Apple ][, and the NES. Some " "example programs are included, and it can also read in an assembly file as " -"input. Original JavaScript Version by Stian Soreng: http://www.6502asm.com/. " +"input. Original JavaScript Version by Stian Soreng. " "Ported to XScreenSaver by Jeremy English. Written by Stian Soreng and Jeremy " "English; 2007." msgstr "" diff --git a/utils/erase.c b/utils/erase.c index 7e1cbc9c..67c386ea 100644 --- a/utils/erase.c +++ b/utils/erase.c @@ -548,7 +548,7 @@ random_squares (eraser_state *st) /* I first saw something like this, albeit in reverse, in an early Tetris implementation for the Mac. - -- Torbjörn Andersson + -- Torbjörn Andersson */ static void slide_lines (eraser_state *st) diff --git a/utils/grabclient.c b/utils/grabclient.c index b99c8fd7..561d4ba3 100644 --- a/utils/grabclient.c +++ b/utils/grabclient.c @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright © 1992-2024 Jamie Zawinski +/* xscreensaver, Copyright © 1992-2025 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 @@ -301,22 +301,22 @@ hack_subproc_environment (Display *dpy) /* Store $DISPLAY into the environment, so that the $DISPLAY variable that the spawned processes inherit is what we are actually using. */ + +# if !(defined(__APPLE__) && !defined(HAVE_COCOA)) /* not macOS X11 */ + /* XQuartz's weird $DISPLAY pathnames do not match DisplayString() */ + const char *odpy = DisplayString (dpy); char *ndpy = (char *) malloc(strlen(odpy) + 20); strcpy (ndpy, "DISPLAY="); strcat (ndpy, odpy); - - /* Allegedly, BSD 4.3 didn't have putenv(), but nobody runs such systems - any more, right? It's not Posix, but everyone seems to have it. */ -# ifdef HAVE_PUTENV if (putenv (ndpy)) abort (); -# endif /* HAVE_PUTENV */ /* don't free (ndpy) -- some implementations of putenv (BSD 4.4, glibc 2.0) copy the argument, but some (libc4,5, glibc 2.1.2, MacOS) do not. So we must leak it (and/or the previous setting). Yay. */ +# endif /* not macOS X11 */ } diff --git a/utils/screenshot.c b/utils/screenshot.c index bfdb95c5..85c67194 100644 --- a/utils/screenshot.c +++ b/utils/screenshot.c @@ -1,4 +1,4 @@ -/* xscreensaver-command, Copyright © 2022 Jamie Zawinski +/* xscreensaver-command, Copyright © 2022-2025 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 @@ -16,17 +16,28 @@ # include "config.h" #endif -#include -#include - #include /* for CARD32 */ #include #include +#include +#include +#include + +#ifdef HAVE_SYS_WAIT_H +# include /* for waitpid() and associated macros */ +#endif + #include "screenshot.h" #include "visual.h" #include "../driver/blurb.h" +#if defined(__APPLE__) && !defined(HAVE_COCOA) +# define HAVE_MACOS_X11 +#elif !defined(HAVE_JWXYZ) /* Real X11, possibly Wayland */ +# define HAVE_REAL_X11 +#endif + static Atom XA_SCREENSAVER_SCREENSHOT = 0; static void @@ -77,6 +88,7 @@ ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error) It will be the size and extent of the given window, or the full screen, as requested. Might be None if we failed. + Somewhat duplicated in fade.c:xshm_screenshot_grab(). */ Pixmap screenshot_grab (Display *dpy, Window window, Bool full_screen_p, @@ -89,6 +101,7 @@ screenshot_grab (Display *dpy, Window window, Bool full_screen_p, GC gc; struct { int x, y, x2, y2; } root, win; XErrorHandler old_handler; + Bool external_p = False; XGetWindowAttributes (dpy, window, &win_xgwa); root_window = XRootWindowOfScreen (win_xgwa.screen); @@ -101,12 +114,6 @@ screenshot_grab (Display *dpy, Window window, Bool full_screen_p, return None; } -# ifdef __APPLE__ /* Can't XCopyArea root window under rootless XQuartz. */ - if (verbose_p) - fprintf (stderr, "%s: no screenshot under XQuartz\n", blurb()); - return None; -# endif - root.x = 0; root.y = 0; root.x2 = root_xgwa.width; @@ -133,34 +140,112 @@ screenshot_grab (Display *dpy, Window window, Bool full_screen_p, if (win.x2 > root.x2) win.x2 = root.x2; if (win.y2 > root.y2) win.y2 = root.y2; - gcv.function = GXcopy; - gcv.subwindow_mode = IncludeInferiors; +# ifdef HAVE_MACOS_X11 + external_p = True; +# else /* X11, possibly Wayland */ + external_p = getenv ("WAYLAND_DISPLAY") || getenv ("WAYLAND_SOCKET"); +# endif + pixmap = XCreatePixmap (dpy, root_window, win_xgwa.width, win_xgwa.height, win_xgwa.depth); - gc = XCreateGC (dpy, pixmap, GCFunction | GCSubwindowMode, &gcv); - - /* I do not understand why some systems get a BadMatch on this XCopyArea. - (Sep 2022, Debian 11.5 x86 under VirtualBox.) */ - XSync (dpy, False); - error_handler_hit_p = False; - old_handler = XSetErrorHandler (ignore_all_errors_ehandler); - - XCopyArea (dpy, root_window, pixmap, gc, - win.x, win.y, - win.x2 - win.x, - win.y2 - win.y, - 0, 0); - - XSync (dpy, False); - XSetErrorHandler (old_handler); - if (error_handler_hit_p) + XSync (dpy, False); /* So that the pixmap exists before we exec. */ + + /* Run xscreensaver-getimage to get a screenshot. It will, in turn, + run "screencapture" or "grim" to write the screenshot to a PNG file, + then will load that file onto our Pixmap. + */ + if (external_p) { - XFreePixmap (dpy, pixmap); - pixmap = 0; - } + pid_t forked; + char *av[20]; + int ac = 0; + char buf[1024]; + char rootstr[20], pixstr[20]; + + sprintf (rootstr, "0x%0lx", (unsigned long) window); + sprintf (pixstr, "0x%0lx", (unsigned long) pixmap); + av[ac++] = "xscreensaver-getimage"; + if (verbose_p) + av[ac++] = "--verbose"; + av[ac++] = "--desktop"; + av[ac++] = "--no-images"; + av[ac++] = "--no-video"; + av[ac++] = rootstr; + av[ac++] = pixstr; + av[ac] = 0; - XFreeGC (dpy, gc); + if (verbose_p) + { + int i; + fprintf (stderr, "%s: screenshot: executing:", blurb()); + for (i = 0; i < ac; i++) + fprintf (stderr, " %s", av[i]); + fprintf (stderr, "\n"); + } + + switch ((int) (forked = fork ())) { + case -1: + { + sprintf (buf, "%s: couldn't fork", blurb()); + perror (buf); + return 0; + } + case 0: + { + close (ConnectionNumber (dpy)); /* close display fd */ + execvp (av[0], av); /* shouldn't return. */ + exit (-1); /* exits fork */ + break; + } + default: + { + int wait_status = 0, exit_status = 0; + /* Wait for the child to die. */ + 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 != 0) + { + fprintf (stderr, "%s: screenshot: %s exited with %d\n", + blurb(), av[0], exit_status); + if (pixmap) XFreePixmap (dpy, pixmap); + return None; + } + } + } + } + else + { + /* Grab a screenshot using XCopyArea. */ + + gcv.function = GXcopy; + gcv.subwindow_mode = IncludeInferiors; + gc = XCreateGC (dpy, pixmap, GCFunction | GCSubwindowMode, &gcv); + + /* I do not understand why some systems get a BadMatch on this XCopyArea. + (Sep 2022, Debian 11.5 x86 under VirtualBox.) */ + XSync (dpy, False); + error_handler_hit_p = False; + old_handler = XSetErrorHandler (ignore_all_errors_ehandler); + + XCopyArea (dpy, root_window, pixmap, gc, + win.x, win.y, + win.x2 - win.x, + win.y2 - win.y, + 0, 0); + + XSync (dpy, False); + XSetErrorHandler (old_handler); + if (error_handler_hit_p) + { + XFreePixmap (dpy, pixmap); + pixmap = 0; + } + + XFreeGC (dpy, gc); + } if (verbose_p || !pixmap) fprintf (stderr, "%s: %s screenshot 0x%lx %dx%d" @@ -210,8 +295,9 @@ screenshot_load_prop (Display *dpy, Window window) } -/* Loads the screenshot from screenshot_save() and returns a new pixmap - that is the same size as the window. +/* Loads the screenshot from screenshot_save() and returns a new pixmap that + covers and is the same size as the window. The saved screenshot is assumed + to be the size of the screen. */ Pixmap screenshot_load (Display *dpy, Window window, Bool verbose_p) diff --git a/utils/screenshot.h b/utils/screenshot.h index f7bc8934..962713cf 100644 --- a/utils/screenshot.h +++ b/utils/screenshot.h @@ -23,8 +23,9 @@ extern Pixmap screenshot_grab (Display *, Window, Bool full_screen_p, /* Store the screenshot pixmap on a property on the window. */ extern void screenshot_save (Display *, Window, Pixmap); -/* Loads the screenshot from screenshot_save() and returns a new pixmap - that is the same size as the winow. +/* Loads the screenshot from screenshot_save() and returns a new pixmap that + covers and is the same size as the window. The saved screenshot is assumed + to be the size of the screen. */ extern Pixmap screenshot_load (Display *, Window, Bool verbose_p); diff --git a/utils/version.h b/utils/version.h index 1a14d227..791a40a6 100644 --- a/utils/version.h +++ b/utils/version.h @@ -1,4 +1,4 @@ static const char screensaver_id[] = - "@(#)xscreensaver 6.10 (28-Apr-2025), by Jamie Zawinski (jwz@jwz.org)"; -#define XSCREENSAVER_VERSION "6.10" -#define XSCREENSAVER_RELEASED 1745866800 + "@(#)xscreensaver 6.11 (01-Jul-2025), by Jamie Zawinski (jwz@jwz.org)"; +#define XSCREENSAVER_VERSION "6.11" +#define XSCREENSAVER_RELEASED 1751396400 diff --git a/xscreensaver.spec b/xscreensaver.spec index 57a02e49..3f13e517 100644 --- a/xscreensaver.spec +++ b/xscreensaver.spec @@ -1,11 +1,10 @@ %define name xscreensaver -%define version 6.10 +%define version 6.11 Summary: X screen saver and locker Name: %{name} Version: %{version} -Release: 1 -Epoch: 1 +Release: 0 License: BSD Group: Amusements/Graphics URL: https://www.jwz.org/xscreensaver/ @@ -13,6 +12,12 @@ Source0: https://www.jwz.org/xscreensaver/xscreensaver-%{version}.tar.gz Vendor: Jamie Zawinski Buildroot: %{_tmppath}/%{name}-root +# Red Hat uses an epoch number to make RPM believe that their old RPM with +# number "1:5.45" is newer than your "6.00". The technical term for this +# is "a dick move". If that's happening to you, increment this number: +# +# Epoch: 2 + BuildRequires: perl BuildRequires: pkgconfig BuildRequires: desktop-file-utils @@ -28,7 +33,7 @@ BuildRequires: libXxf86vm-devel BuildRequires: xorg-x11-proto-devel BuildRequires: mesa-libGL-devel BuildRequires: mesa-libGLU-devel -BuildRequires: libgle-devel +#BuildRequires: libgle-devel BuildRequires: pam-devel BuildRequires: systemd-devel BuildRequires: gtk3-devel @@ -38,39 +43,40 @@ BuildRequires: libxml2-devel BuildRequires: gettext-devel BuildRequires: libjpeg-turbo-devel -Requires: SysVinit +#Requires: SysVinit Requires: pam Requires: /etc/pam.d/system-auth -Requires: htmlview -Requires: desktop-backgrounds-basic +#Requires: htmlview +#Requires: desktop-backgrounds-basic Requires: xdg-utils Requires: systemd-libs Provides: xscreensaver -Obsoletes: xscreensaver-base -Obsoletes: xscreensaver-common -Obsoletes: xscreensaver-data -Obsoletes: xscreensaver-data-extra -Obsoletes: xscreensaver-extra -Obsoletes: xscreensaver-extra-base -Obsoletes: xscreensaver-extra-gss -Obsoletes: xscreensaver-extras -Obsoletes: xscreensaver-extras-base -Obsoletes: xscreensaver-extras-gss -Obsoletes: xscreensaver-extrusion -Obsoletes: xscreensaver-gl -Obsoletes: xscreensaver-gl-base -Obsoletes: xscreensaver-gl-extra -Obsoletes: xscreensaver-gl-extra-gss -Obsoletes: xscreensaver-gl-extras -Obsoletes: xscreensaver-gl-extras-gss -Obsoletes: xscreensaver-lang -Obsoletes: xscreensaver-matrix -Obsoletes: xscreensaver-bsod -Obsoletes: xscreensaver-webcollage -Obsoletes: xscreensaver-screensaver-bsod -Obsoletes: xscreensaver-screensaver-webcollage +Obsoletes: xscreensaver-base < %{version} +Obsoletes: xscreensaver-common < %{version} +Obsoletes: xscreensaver-data < %{version} +Obsoletes: xscreensaver-data-extra < %{version} +Obsoletes: xscreensaver-extra < %{version} +Obsoletes: xscreensaver-extra-base < %{version} +Obsoletes: xscreensaver-extra-gss < %{version} +Obsoletes: xscreensaver-extras < %{version} +Obsoletes: xscreensaver-extras-base < %{version} +Obsoletes: xscreensaver-extras-gss < %{version} +Obsoletes: xscreensaver-extrusion < %{version} +Obsoletes: xscreensaver-gl < %{version} +Obsoletes: xscreensaver-gl-base < %{version} +Obsoletes: xscreensaver-gl-extra < %{version} +Obsoletes: xscreensaver-gl-extra-gss < %{version} +Obsoletes: xscreensaver-gl-extras < %{version} +Obsoletes: xscreensaver-gl-extras-gss < %{version} +Obsoletes: xscreensaver-lang < %{version} +Obsoletes: xscreensaver-matrix < %{version} +Obsoletes: xscreensaver-bsod < %{version} +Obsoletes: xscreensaver-webcollage < %{version} +Obsoletes: xscreensaver-screensaver-bsod < %{version} +Obsoletes: xscreensaver-screensaver-webcollage < %{version} + %description A modular screen saver and locker for the X Window System. @@ -79,6 +85,8 @@ More than 260 display modes are included in this package. %prep %setup -q +autoreconf -v -f + if [ -x %{_datadir}/libtool/config.guess ]; then # use system-wide copy cp -p %{_datadir}/libtool/config.{sub,guess} . @@ -124,8 +132,8 @@ sed -e 's@\(.*/app-defaults/\)@%config \1@' \ -e 's@\(.*/pam\.d/\)@%config(missingok) \1@' \ > $dd/all.files -%find_lang %{name} -cat %{name}.lang >> $dd/all.files +#%find_lang %{name} +#cat %{name}.lang >> $dd/all.files chmod -R a+r,u+w,og-w ${RPM_BUILD_ROOT} @@ -136,7 +144,7 @@ rm -rf ${RPM_BUILD_ROOT} %defattr(-,root,root) %changelog -* Mon Nov 16 1998 jwz -- Created. * Mon Jul 31 2023 jwz - Splitting this into multiple packages is a support nightmare, please don't. +* Mon Nov 16 1998 jwz +- Created.