pax_global_header00006660000000000000000000000064122357523620014521gustar00rootroot0000000000000052 comment=94aec045ffd63f37efcf5443e9dafccb13da6f99 xss-lock-0.3.0/000077500000000000000000000000001223575236200132645ustar00rootroot00000000000000xss-lock-0.3.0/.gitignore000066400000000000000000000000061223575236200152500ustar00rootroot00000000000000build xss-lock-0.3.0/CMakeLists.txt000066400000000000000000000003301223575236200160200ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.8) project(xss-lock C) set(PROJECT_VERSION 0.3.0) install(FILES NEWS DESTINATION share/doc/${PROJECT_NAME}) add_subdirectory(src) add_subdirectory(doc) add_subdirectory(completion) xss-lock-0.3.0/LICENSE000066400000000000000000000020461223575236200142730ustar00rootroot00000000000000Copyright (c) 2013 Raymond Wagenmaker 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 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. xss-lock-0.3.0/NEWS000066400000000000000000000031601223575236200137630ustar00rootroot000000000000000.3.0 ----- * New option --tranfer-sleep-lock gives lockers control over the inhibit delay. Two bash scripts are included to use this with existing lockers. (Bumps required glib version from 2.30 to 2.32.) * Don't kill locker on forced ScreenSaverOff event. This resulted in killing the locker upon returning to the VT running X after switching to another VT. Since there is no reliable way to detect VT-switching, this is necessary to keep things secure without enabling DontVTSwitch in xorg.conf. Now the only difference between `xset s reset` and sending SIGHUP to xss-lock (while the locker is active) is that the former resets the idle hint and (with DPMS) turns the screen back on. Killing the locker process directly is now the only way to deactivate the locker programmatically. Perhaps the next release will include something like `xss-lock-command` to provide more fine-grained control over a running xss-lock instance. * Type 'm' for tmpfiles.d introduced in systemd-208 is more suitable for modifying sysfs brightness file permissions. * Add zsh and bash completion. * Update man page. 0.2.2 ----- * Updated dim-screen script (fading, configuration at the top). 0.2.1 ----- * xdg-screensaver patch + man page note. * New options: --quiet and --verbose. 0.2.0 ----- * Set idle hint on logind session. * Fix bugs related to notifier and (--ignore-)sleep. 0.1.1 ----- * Warning message for additional X errors that pop up in the event loop. * Allow building with glib>=2.30 (exit status of notifier/locker is not checked for <2.34). * Typo fixes and other small improvements to documentation. xss-lock-0.3.0/completion/000077500000000000000000000000001223575236200154355ustar00rootroot00000000000000xss-lock-0.3.0/completion/CMakeLists.txt000066400000000000000000000002751223575236200202010ustar00rootroot00000000000000install(FILES xss-lock.zsh DESTINATION share/zsh/site-functions RENAME _xss-lock) install(FILES xss-lock.bash DESTINATION share/bash-completion/completions RENAME xss-lock) xss-lock-0.3.0/completion/xss-lock.bash000066400000000000000000000013321223575236200200360ustar00rootroot00000000000000_xss-lock() { local cur prev words cword _init_completion || return local i notifier=@(-n|--notifier) for (( i=1; i <= COMP_CWORD; i++ )); do if [[ ${COMP_WORDS[i]} != -* ]]; then _command_offset $i return elif [[ ${COMP_WORDS[i]} == $notifier ]]; then (( i++ )) fi done if [[ $prev == $notifier ]]; then COMPREPLY=( $(compgen -c -- $cur) ) fi if [[ $cur == -* ]]; then COMPREPLY=( $(compgen -W '-n --notifier -l --transfer-sleep-lock \ --ignore-sleep -q --quiet -v --verbose \ --version -h --help' -- $cur) ) fi } complete -F _xss-lock xss-lock xss-lock-0.3.0/completion/xss-lock.zsh000066400000000000000000000016371223575236200177350ustar00rootroot00000000000000#compdef xss-lock function _xss-lock_arguments { _arguments -S -s : $@ \ '(-n --notifier)'{-n,--notifier=}'[set notification command]: : _command_names -e' \ '(-l --transfer-sleep-lock)'{-l,--transfer-sleep-lock}'[pass sleep delay lock file descriptor to locker]' \ '--ignore-sleep[do not lock on suspend/hibernate]' \ '(-q --quiet -v --verbose)'{-q,--quiet}'[output only fatal errors]' \ '(-q --quiet -v --verbose)'{-v,--verbose}'[output more messages]' \ '--version[print version number and exit]' \ '(-h --help)'{-h,--help}'[print usage info and exit]' } function _arguments_after_locker { if (( complete_locker_args )); then _normal else _xss-lock_arguments fi } integer complete_locker_args=$(( words[(i)--] < CURRENT )) _xss-lock_arguments \ '(-):lock command: _command_names -e' \ '*::arguments: _arguments_after_locker' xss-lock-0.3.0/doc/000077500000000000000000000000001223575236200140315ustar00rootroot00000000000000xss-lock-0.3.0/doc/CMakeLists.txt000066400000000000000000000014611223575236200165730ustar00rootroot00000000000000configure_file(xss-lock.1.rst.in xss-lock.1.rst) find_program(rst2man NAMES rst2man rst2man.py rst2man2 rst2man2.py) if(rst2man) set(man_input "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.1.rst") set(man_output "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.1") add_custom_command(OUTPUT ${man_output} COMMAND ${rst2man} ${man_input} ${man_output} DEPENDS ${man_input}) add_custom_target(man ALL DEPENDS ${man_output}) install(FILES ${man_output} DESTINATION share/man/man1) else() message(WARNING "rst2man (docutils) not found, disabling man page generation") endif() install(FILES dim-screen.sh xdg-screensaver.patch transfer-sleep-lock-i3lock.sh transfer-sleep-lock-generic-delay.sh DESTINATION share/doc/${PROJECT_NAME}) xss-lock-0.3.0/doc/dim-screen.sh000066400000000000000000000033721223575236200164200ustar00rootroot00000000000000#!/bin/bash # Example notifier script -- lowers screen brightness, then waits to be killed # and restores previous brightness on exit. ## CONFIGURATION ############################################################## # Brightness will be lowered to this value. min_brightness=0 # If your video driver works with xbacklight, set -time and -steps for fading # to $min_brightness here. Setting steps to 1 disables fading. fade_time=200 fade_steps=20 # If you have a driver without RandR backlight property (e.g. radeon), set this # to use the sysfs interface and create a .conf file in /etc/tmpfiles.d/ # containing the following line to make the sysfs file writable for group # "users": # # m /sys/class/backlight/acpi_video0/brightness 0664 root users - - # #sysfs_path=/sys/class/backlight/acpi_video0/brightness # Time to sleep (in seconds) between increments when using sysfs. If unset or # empty, fading is disabled. fade_step_time=0.05 ############################################################################### get_brightness() { if [[ -z $sysfs_path ]]; then xbacklight -get else cat $sysfs_path fi } set_brightness() { if [[ -z $sysfs_path ]]; then xbacklight -steps 1 -set $1 else echo $1 > $sysfs_path fi } fade_brightness() { if [[ -z $sysfs_path ]]; then xbacklight -time $fade_time -steps $fade_steps -set $1 elif [[ -z $fade_step_time ]]; then set_brightness $1 else local level for level in $(eval echo {$(get_brightness)..$1}); do set_brightness $level sleep $fade_step_time done fi } trap 'exit 0' TERM INT trap "set_brightness $(get_brightness); kill %%" EXIT fade_brightness $min_brightness sleep 2147483647 & wait xss-lock-0.3.0/doc/transfer-sleep-lock-generic-delay.sh000066400000000000000000000021411223575236200227510ustar00rootroot00000000000000#!/bin/bash # Example locker script -- demonstrates how to use the --transfer-sleep-lock # option with a fixed delay to give simple lockers a little bit of time to lock # the screen before the system goes the sleep. ## CONFIGURATION ############################################################## # Command to start the locker (should not fork) locker="xlock +resetsaver" # Delay in seconds. Note that by default systemd-logind allows a maximum sleep # delay of 5 seconds. sleep_delay=1 # Run before starting the locker pre_lock() { #mpc pause return } # Run after the locker exits post_lock() { return } ############################################################################### pre_lock # kill locker if we get killed trap 'kill %%' TERM INT if [[ -e /dev/fd/${XSS_SLEEP_LOCK_FD:--1} ]]; then # lock fd is open, make sure the locker does not inherit a copy $locker {XSS_SLEEP_LOCK_FD}<&- & sleep $sleep_delay # now close our fd (only remaining copy) to indicate we're ready to sleep exec {XSS_SLEEP_LOCK_FD}<&- else $locker & fi wait # for locker to exit post_lock xss-lock-0.3.0/doc/transfer-sleep-lock-i3lock.sh000066400000000000000000000024041223575236200214270ustar00rootroot00000000000000#!/bin/bash # Example locker script -- demonstrates how to use the --transfer-sleep-lock # option with i3lock's forking mode to delay sleep until the screen is locked. ## CONFIGURATION ############################################################## # Options to pass to i3lock i3lock_options="-d" # Run before starting the locker pre_lock() { #mpc pause return } # Run after the locker exits post_lock() { return } ############################################################################### pre_lock # We set a trap to kill the locker if we get killed, then start the locker and # wait for it to exit. The waiting is not that straightforward when the locker # forks, so we use this polling only if we have a sleep lock to deal with. if [[ -e /dev/fd/${XSS_SLEEP_LOCK_FD:--1} ]]; then kill_i3lock() { pkill -xu $EUID "$@" i3lock } trap kill_i3lock TERM INT # we have to make sure the locker does not inherit a copy of the lock fd i3lock $i3lock_options {XSS_SLEEP_LOCK_FD}<&- # now close our fd (only remaining copy) to indicate we're ready to sleep exec {XSS_SLEEP_LOCK_FD}<&- while kill_i3lock -0; do sleep 0.5 done else trap 'kill %%' TERM INT i3lock -n $i3lock_options & wait fi post_lock xss-lock-0.3.0/doc/xdg-screensaver.patch000066400000000000000000000010771223575236200201570ustar00rootroot00000000000000diff -u old/xdg-screensaver new/xdg-screensaver --- old/xdg-screensaver 2013-05-21 15:08:51.152812849 +0200 +++ new/xdg-screensaver 2013-05-21 15:13:16.843096616 +0200 @@ -825,13 +825,13 @@ { case "$1" in suspend) - xset s off > /dev/null + screensaver_suspend_loop eval killall -HUP xss-lock '||' xset s reset > /dev/null 2>&1 result=$? ;; resume) - xset s default > /dev/null - result=$? + # Automatic resume when $screensaver_file disappears + result=0 ;; activate) xss-lock-0.3.0/doc/xss-lock.1.rst.in000066400000000000000000000117251223575236200171000ustar00rootroot00000000000000======== xss-lock ======== ------------------------------------- use external locker as X screen saver ------------------------------------- :Author: Raymond Wagenmaker :Date: November 2013 :Manual section: 1 Synopsis ======== | xss-lock [-n *notify_cmd*] [--ignore-sleep] [-l] [-v|-q] [--] *locker* [*arg*] ... | xss-lock --help|--version Description =========== **xss-lock** hooks up your favorite locker to the MIT screen saver extension for X and also to systemd's login manager. The locker is executed in response to events from these two sources: - X signals when screen saver activation is forced or after a period of user inactivity (as set with ``xset s TIMEOUT``). In the latter case, the notifier command, if specified, is executed first. - The login manager can also request that the session be locked; as a result of ``loginctl lock-sessions``, for example. Additionally, **xss-lock** uses the inhibition logic to lock the screen before the system goes to sleep. **xss-lock** waits for the locker to exit -- or kills it when screen saver deactivation or session unlocking is forced -- so the command should not fork. Also, **xss-lock** manages the idle hint on the login session. The idle state of the session is directly linked to user activity as reported by X (except when the notifier runs before locking the screen). When all sessions are idle, the login manager can take action (such as suspending the system) after a preconfigured delay. Options ======= -n cmd, --notifier=cmd Run *cmd* when the screen saver activates because of user inactivity. Shell-style quoting is supported. The notifier is killed when X signals user activity or when the locker is started. The locker is started after the first screen saver cycle, as set with ``xset s TIMEOUT CYCLE``. This can be used to run a countdown or (on laptops) dim the screen before locking. For an example, see the script *@CMAKE_INSTALL_PREFIX@/share/doc/xss-lock/dim-screen.sh*. -l, --tranfer-sleep-lock Allow the locker process to inherit the file descriptor that represents the delay lock obtained from the login manager. The corresponding index will be made available in the environment variable **$XSS_SLEEP_LOCK_FD**; this will only be set if the reason for locking is that the system is preparing to go to sleep. The locker should close this file descriptor to indicate it is ready. Example scripts that wrap existing lockers are available as *@CMAKE_INSTALL_PREFIX@/share/doc/xss-lock/transfer-sleep-lock-\*.sh*. --ignore-sleep Do not lock on suspend/hibernate. -q, --quiet Output only fatal errors. -v, --verbose Output more messages. -h, --help Print help message and exit. --version Print version number and exit. Signals ======= SIGHUP Upon receiving this signal, **xss-lock** resets the screen saver, but only if the screen is not currently locked (unlike ``xset s reset``). This can be used in MPlayer's configuration as a workaround for MPlayer's failure to restart the screen saver timer when playback is paused:: heartbeat-cmd="killall -HUP xss-lock" stop-xscreensaver=false .. note:: This is ineffective with mplayer2 (and mpv), because its heart keeps beating while playback is paused. SIGINT/SIGTERM Upon receiving this signal, **xss-lock** exits after killing any running notifier or locker. Notes ===== - Some applications rely on the **xdg-screensaver** script from xdg-utils, which uses ``xset s off`` and ``xset s default`` to suspend and resume the screen saver, respectively. The latter resets the timeout and cycle to the server defaults (``xset s on`` uses a hardcoded default instead), so this only works if you are happy with (or can control) the server settings. To fix the resume action in this script (or a copy in *~/bin* preceding the original in **$PATH**), either replace ``on`` by your preferred timeout and cycle, or avoid hardcoded time values by patching the script to run a suspend loop as it does for other screen savers, using *@CMAKE_INSTALL_PREFIX@/share/doc/xss-lock/xdg-screensaver.patch*. Examples ======== - Run **xlock** after ten minutes of inactivity:: xset 600 xss-lock xlock +resetsaver Without ``+resetsaver``, **xlock** forces a screen saver reset during startup, thereby telling **xss-lock** to immediately kill **xlock** again. - Dim the screen after three minutes of inactivity, lock the screen two minutes later using **i3lock**:: xset 180 120 xss-lock -n dim-screen.sh -- i3lock -n .. note:: A script is provided to use **i3lock**'s forking mode with the ``--tranfer-sleep-lock`` option (see above). See also ======== **xset**\(1), **systemd-logind.service**\(8) xss-lock-0.3.0/src/000077500000000000000000000000001223575236200140535ustar00rootroot00000000000000xss-lock-0.3.0/src/CMakeLists.txt000066400000000000000000000012531223575236200166140ustar00rootroot00000000000000include(FindPkgConfig) pkg_check_modules(GLIB2 REQUIRED glib-2.0>=2.32 gio-unix-2.0) pkg_check_modules(XCB REQUIRED xcb xcb-event xcb-screensaver) include_directories(${GLIB2_INCLUDE_DIRS} ${XCB_INCLUDE_DIRS}) link_directories(${GLIB2_LIBRARY_DIRS} ${XCB_LIBRARY_DIRS}) if(XCB_xcb_VERSION VERSION_LESS 1.8) set(XCB_POLL_FOR_QUEUED_EVENT 0) else() set(XCB_POLL_FOR_QUEUED_EVENT 1) endif() configure_file(config.h.in config.h) include_directories(${CMAKE_CURRENT_BINARY_DIR}) add_executable(xss-lock xss-lock.c xcb_utils.c xcb_utils.h config.h ) target_link_libraries(xss-lock ${GLIB2_LIBRARIES} ${XCB_LIBRARIES}) install(TARGETS xss-lock DESTINATION bin) xss-lock-0.3.0/src/config.h.in000066400000000000000000000001601223575236200160730ustar00rootroot00000000000000#define APP_NAME "@PROJECT_NAME@" #define VERSION "@PROJECT_VERSION@" #cmakedefine01 XCB_POLL_FOR_QUEUED_EVENT xss-lock-0.3.0/src/xcb_utils.c000066400000000000000000000101121223575236200162060ustar00rootroot00000000000000/* Copyright (c) 2013 Raymond Wagenmaker * * See LICENSE for the MIT license. */ #include "xcb_utils.h" #include // TODO: // - handle xcb_connection_has_error? typedef struct XcbEventSource { GSource source; xcb_connection_t *connection; #if !GLIB_CHECK_VERSION(2, 36, 0) GPollFD poll; #endif GQueue *queue; } XcbEventSource; static void xcb_enqueue_events(XcbEventSource *xcb_event_source, xcb_generic_event_t *(*poll)(xcb_connection_t *)); static gboolean xcb_event_prepare(GSource *source, gint *timeout); static gboolean xcb_event_check(GSource *source); static gboolean xcb_event_dispatch(GSource *source, GSourceFunc callback, gpointer user_data); static void xcb_event_finalize(GSource *source); static GSourceFuncs xcb_event_funcs = { xcb_event_prepare, xcb_event_check, xcb_event_dispatch, xcb_event_finalize }; GQuark xcb_error_quark(void) { return g_quark_from_static_string("xcb-error-quark"); } xcb_screen_t * xcb_get_screen(xcb_connection_t *connection, int screen_number) { const xcb_setup_t *setup = xcb_get_setup(connection); xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup); g_return_val_if_fail(screen_number < xcb_setup_roots_length(setup), NULL); while (screen_number--) xcb_screen_next(&iter); return iter.data; } static void xcb_enqueue_events(XcbEventSource *xcb_event_source, xcb_generic_event_t *(*poll)(xcb_connection_t *)) { xcb_generic_event_t *event; while (event = (*poll)(xcb_event_source->connection)) g_queue_push_tail(xcb_event_source->queue, event); } static gboolean xcb_event_prepare(GSource *source, gint *timeout) { XcbEventSource *xcb_event_source = (XcbEventSource *)source; xcb_flush(xcb_event_source->connection); #if XCB_POLL_FOR_QUEUED_EVENT xcb_enqueue_events(xcb_event_source, xcb_poll_for_queued_event); #endif if (g_queue_is_empty(xcb_event_source->queue)) { *timeout = -1; return FALSE; } else { *timeout = 0; return TRUE; } } static gboolean xcb_event_check(GSource *source) { XcbEventSource *xcb_event_source = (XcbEventSource *)source; xcb_enqueue_events(xcb_event_source, xcb_poll_for_event); return !g_queue_is_empty(xcb_event_source->queue); } static gboolean xcb_event_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) { XcbEventSource *xcb_event_source = (XcbEventSource *)source; XcbEventFunc xcb_event_callback = (XcbEventFunc)callback; xcb_generic_event_t *event; gboolean again = TRUE; if (!callback) { g_warning("XcbEvent source dispatched without a callback"); return FALSE; } while (again && (event = g_queue_pop_head(xcb_event_source->queue))) { again = xcb_event_callback(xcb_event_source->connection, event, user_data); free(event); } return again; } static void xcb_event_finalize(GSource *source) { XcbEventSource *xcb_event_source = (XcbEventSource *)source; g_queue_free_full(xcb_event_source->queue, free); } GSource * xcb_event_source_new(xcb_connection_t *connection) { GSource *source = g_source_new(&xcb_event_funcs, sizeof(XcbEventSource)); XcbEventSource *xcb_event_source = (XcbEventSource *)source; gint xcb_fd = xcb_get_file_descriptor(connection); GIOCondition fd_event_mask = G_IO_IN | G_IO_HUP | G_IO_ERR; xcb_event_source->connection = connection; xcb_event_source->queue = g_queue_new(); #if GLIB_CHECK_VERSION(2, 36, 0) g_source_add_unix_fd(source, xcb_fd, fd_event_mask); #else xcb_event_source->poll.fd = xcb_fd; xcb_event_source->poll.events = fd_event_mask; g_source_add_poll(source, &xcb_event_source->poll); #endif return source; } guint xcb_event_add(xcb_connection_t *connection, XcbEventFunc function, gpointer data) { guint id; GSource *source; g_return_val_if_fail(function != NULL, 0); source = xcb_event_source_new(connection); g_source_set_callback(source, (GSourceFunc)function, data, NULL); id = g_source_attach(source, NULL); g_source_unref(source); return id; } xss-lock-0.3.0/src/xcb_utils.h000066400000000000000000000022761223575236200162270ustar00rootroot00000000000000/* Copyright (c) 2013 Raymond Wagenmaker * * See LICENSE for the MIT license. */ #ifndef XCB_UTILS_H #define XCB_UTILS_H #include #include G_BEGIN_DECLS #define XCB_ERROR xcb_error_quark() #define XCB_SCREENSAVER_PROPERTY_NAME "_MIT_SCREEN_SAVER_ID" #define xcb_screensaver_notify_event_t xcb_screensaver_notify_event_t_fixed typedef struct xcb_screensaver_notify_event_t_fixed { uint8_t response_type; /**< */ uint8_t state; /**< */ uint16_t sequence; /**< */ xcb_timestamp_t time; /**< */ xcb_window_t root; /**< */ xcb_window_t window; /**< */ uint8_t kind; /**< */ uint8_t forced; /**< */ uint8_t pad1[14]; /**< */ } xcb_screensaver_notify_event_t_fixed; GQuark xcb_error_quark(void) G_GNUC_CONST; xcb_screen_t *xcb_get_screen(xcb_connection_t *connection, int screen_number); typedef gboolean (*XcbEventFunc)(xcb_connection_t *connection, xcb_generic_event_t *event, gpointer user_data); GSource *xcb_event_source_new(xcb_connection_t *connection); guint xcb_event_add(xcb_connection_t *connection, XcbEventFunc function, gpointer data); G_END_DECLS #endif /* XCB_UTILS_H */ xss-lock-0.3.0/src/xss-lock.c000066400000000000000000000456671223575236200160040ustar00rootroot00000000000000/* Copyright (c) 2013 Raymond Wagenmaker * * See LICENSE for the MIT license. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "xcb_utils.h" #define LOGIND_SERVICE "org.freedesktop.login1" #define LOGIND_PATH "/org/freedesktop/login1" #define LOGIND_MANAGER_INTERFACE "org.freedesktop.login1.Manager" #define LOGIND_SESSION_INTERFACE "org.freedesktop.login1.Session" typedef struct Child { gchar *name; gchar **cmd; GPid pid; gboolean transfer_sleep_lock_fd; struct Child *kill_first; } Child; static gboolean register_screensaver(xcb_connection_t *connection, xcb_screen_t *screen, xcb_atom_t *atom, GError **error); static void unregister_screensaver(xcb_connection_t *connection, xcb_screen_t *screen, xcb_atom_t atom); static gboolean screensaver_event_cb(xcb_connection_t *connection, xcb_generic_event_t *event, const int *xcb_screensaver_notify); static void keep_sleep_lock_fd_open(gpointer user_data); static void start_child(Child *child); static void kill_child(Child *child); static void child_watch_cb(GPid pid, gint status, Child *child); static void logind_manager_proxy_new_cb(GObject *source_object, GAsyncResult *res, gpointer user_data); static void logind_manager_take_sleep_delay_lock(void); static void logind_manager_call_inhibit_cb(GObject *source_object, GAsyncResult *res, gpointer user_data); static void logind_manager_on_signal_prepare_for_sleep(GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, gpointer user_data); static void logind_manager_call_get_session_cb(GObject *source_object, GAsyncResult *res, gpointer user_data); static void logind_session_proxy_new_cb(GObject *source_object, GAsyncResult *res, gpointer user_data); static void logind_session_on_signal_lock(GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, gpointer user_data); static void logind_session_set_idle_hint(gboolean idle); static gboolean parse_options(int argc, char *argv[], GError **error); static gboolean parse_notifier_cmd(const gchar *option_name, const gchar *value, gpointer data, GError **error); static gboolean reset_screensaver(xcb_connection_t *connection); static gboolean exit_service(GMainLoop *loop); static void log_handler(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data); static Child notifier = {"notifier", NULL, 0, FALSE, NULL}; static Child locker = {"locker", NULL, 0, FALSE, ¬ifier}; static gboolean opt_quiet = FALSE; static gboolean opt_verbose = FALSE; static gboolean opt_ignore_sleep = FALSE; static gboolean opt_print_version = FALSE; static GOptionEntry opt_entries[] = { {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &locker.cmd, NULL, "LOCK_CMD [ARG...]"}, {"notifier", 'n', G_OPTION_FLAG_FILENAME, G_OPTION_ARG_CALLBACK, parse_notifier_cmd, "Send notification using CMD", "CMD"}, {"transfer-sleep-lock", 'l', 0, G_OPTION_ARG_NONE, &locker.transfer_sleep_lock_fd, "Pass sleep delay lock file descriptor to locker", NULL}, {"ignore-sleep", 0, 0, G_OPTION_ARG_NONE, &opt_ignore_sleep, "Do not lock on suspend/hibernate", NULL}, {"quiet", 'q', 0, G_OPTION_ARG_NONE, &opt_quiet, "Output only fatal errors", NULL}, {"verbose", 'v', 0, G_OPTION_ARG_NONE, &opt_verbose, "Output more messages", NULL}, {"version", 0, 0, G_OPTION_ARG_NONE, &opt_print_version, "Print version number and exit", NULL}, {NULL} }; static GDBusProxy *logind_manager = NULL; static GDBusProxy *logind_session = NULL; static gint sleep_lock_fd = -1; static gboolean preparing_for_sleep = FALSE; static gboolean register_screensaver(xcb_connection_t *connection, xcb_screen_t *screen, xcb_atom_t *atom, GError **error) { uint32_t xid; const xcb_query_extension_reply_t *extension_reply; xcb_screensaver_query_version_cookie_t version_cookie; xcb_screensaver_query_version_reply_t *version_reply = NULL; xcb_intern_atom_cookie_t atom_cookie; xcb_intern_atom_reply_t *atom_reply = NULL; xcb_void_cookie_t set_attributes_cookie; xcb_generic_error_t *xcb_error = NULL; xcb_prefetch_extension_data(connection, &xcb_screensaver_id); xid = xcb_generate_id(connection); xcb_create_pixmap(connection, screen->root_depth, xid, screen->root, 1, 1); atom_cookie = xcb_intern_atom(connection, FALSE, strlen(XCB_SCREENSAVER_PROPERTY_NAME), XCB_SCREENSAVER_PROPERTY_NAME); extension_reply = xcb_get_extension_data(connection, &xcb_screensaver_id); if (!extension_reply || !extension_reply->present) { g_set_error(error, XCB_ERROR, 0, "Screensaver extension unavailable"); goto out; } version_cookie = xcb_screensaver_query_version(connection, 1, 0); set_attributes_cookie = xcb_screensaver_set_attributes_checked(connection, screen->root, -1, -1, 1, 1, 0, XCB_COPY_FROM_PARENT, XCB_COPY_FROM_PARENT, XCB_COPY_FROM_PARENT, 0, NULL); xcb_screensaver_select_input(connection, screen->root, XCB_SCREENSAVER_EVENT_NOTIFY_MASK | XCB_SCREENSAVER_EVENT_CYCLE_MASK); version_reply = xcb_screensaver_query_version_reply(connection, version_cookie, &xcb_error); if (xcb_error = xcb_request_check(connection, set_attributes_cookie)) { g_set_error(error, XCB_ERROR, 0, "Failed to set screensaver attributes; " "is another one running?"); goto out; } atom_reply = xcb_intern_atom_reply(connection, atom_cookie, &xcb_error); *atom = atom_reply->atom; xcb_change_property(connection, XCB_PROP_MODE_REPLACE, screen->root, *atom, XCB_ATOM_PIXMAP, 32, 1, &xid); xcb_event_add(connection, (XcbEventFunc)screensaver_event_cb, (void *)&extension_reply->first_event); out: if (version_reply) free(version_reply); if (atom_reply) free(atom_reply); if (xcb_error) { free(xcb_error); return FALSE; } return TRUE; } static void unregister_screensaver(xcb_connection_t *connection, xcb_screen_t *screen, xcb_atom_t atom) { xcb_screensaver_unset_attributes(connection, screen->root); xcb_delete_property(connection, screen->root, atom); } static gboolean screensaver_event_cb(xcb_connection_t *connection, xcb_generic_event_t *event, const int *const xcb_screensaver_notify) { const uint8_t type = XCB_EVENT_RESPONSE_TYPE(event); if (type == 0) { xcb_generic_error_t *error = (xcb_generic_error_t *)event; g_warning("X error: %s", xcb_event_get_error_label(error->error_code)); } else if (type == *xcb_screensaver_notify) { xcb_screensaver_notify_event_t *xss_event = (xcb_screensaver_notify_event_t *)event; switch (xss_event->state) { case XCB_SCREENSAVER_STATE_ON: if (xss_event->kind == XCB_SCREENSAVER_KIND_INTERNAL) /* According to the original protocol, this forces the external * saver (i.e., me) to be started after deactivating the * internal saver, which may be started if the server is * grabbed when the saver activates, but Xorg does not seem to * work that way; I'm leaving this in anyway. */ xcb_force_screen_saver(connection, XCB_SCREEN_SAVER_ACTIVE); else if (!notifier.cmd || xss_event->forced) { start_child(&locker); logind_session_set_idle_hint(TRUE); } else if (!locker.pid) start_child(¬ifier); else logind_session_set_idle_hint(TRUE); break; case XCB_SCREENSAVER_STATE_OFF: kill_child(¬ifier); logind_session_set_idle_hint(FALSE); break; case XCB_SCREENSAVER_STATE_CYCLE: if (!locker.pid) { logind_session_set_idle_hint(TRUE); start_child(&locker); } break; } } return TRUE; } static void keep_sleep_lock_fd_open(gpointer user_data) { fcntl(sleep_lock_fd, F_SETFD, ~FD_CLOEXEC & fcntl(sleep_lock_fd, F_GETFD)); } static void start_child(Child *child) { GSpawnFlags flags = G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD; GSpawnChildSetupFunc setup = NULL; gchar **env = NULL; GError *error = NULL; if (child->pid) return; if (child->kill_first) kill_child(child->kill_first); if (preparing_for_sleep && child->transfer_sleep_lock_fd) { gchar *fd = g_strdup_printf("%d", sleep_lock_fd); env = g_environ_setenv(g_get_environ(), "XSS_SLEEP_LOCK_FD", fd, TRUE); g_free(fd); flags |= G_SPAWN_LEAVE_DESCRIPTORS_OPEN; setup = keep_sleep_lock_fd_open; } if (!g_spawn_async(NULL, child->cmd, env, flags, setup, NULL, &child->pid, &error)) { g_warning("Error spawning %s: %s", child->name, error->message); g_error_free(error); goto out; } g_child_watch_add(child->pid, (GChildWatchFunc)child_watch_cb, child); out: g_strfreev(env); } static void kill_child(Child *child) { if (child->pid && kill(child->pid, SIGTERM)) g_warning("Error sending SIGTERM to %s: %s", child->name, g_strerror(errno)); } static void child_watch_cb(GPid pid, gint status, Child *child) { #if GLIB_CHECK_VERSION(2, 34, 0) GError *error = NULL; if (!g_spawn_check_exit_status(status, &error)) { g_message("%s exited abnormally: %s", child->name, error->message); g_error_free(error); } #endif child->pid = 0; g_spawn_close_pid(pid); } static void logind_manager_proxy_new_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) { GError *error = NULL; logind_manager = g_dbus_proxy_new_for_bus_finish(res, &error); if (!logind_manager) { g_warning("Error connecting to systemd login manager: %s", error->message); g_error_free(error); return; } if (!opt_ignore_sleep) { g_signal_connect(logind_manager, "g-signal", G_CALLBACK(logind_manager_on_signal_prepare_for_sleep), NULL); logind_manager_take_sleep_delay_lock(); } g_dbus_proxy_call(logind_manager, "GetSessionByPID", g_variant_new("(u)", getpid()), G_DBUS_CALL_FLAGS_NONE, -1, NULL, logind_manager_call_get_session_cb, NULL); } static void logind_manager_take_sleep_delay_lock(void) { if (sleep_lock_fd >= 0) return; g_dbus_proxy_call_with_unix_fd_list(logind_manager, "Inhibit", g_variant_new("(ssss)", "sleep", APP_NAME, "Lock screen first", "delay"), G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, logind_manager_call_inhibit_cb, NULL); } static void logind_manager_call_inhibit_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) { GVariant *result = NULL; GError *error = NULL; GUnixFDList *fd_list; gint32 fd_index = 0; result = g_dbus_proxy_call_with_unix_fd_list_finish(logind_manager, &fd_list, res, &error); if (!result) { g_warning("Error taking sleep inhibitor lock: %s", error->message); g_error_free(error); return; } g_variant_get(result, "(h)", &fd_index); sleep_lock_fd = g_unix_fd_list_get(fd_list, fd_index, &error); if (sleep_lock_fd == -1) { g_warning("Error getting file descriptor for sleep inhibitor lock: %s", error->message); g_error_free(error); } g_variant_unref(result); g_object_unref(fd_list); } static void logind_manager_on_signal_prepare_for_sleep(GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, gpointer user_data) { gboolean active; if (g_strcmp0(signal_name, "PrepareForSleep")) return; g_variant_get(parameters, "(b)", &active); if (active) { preparing_for_sleep = TRUE; start_child(&locker); if (sleep_lock_fd >= 0) { close(sleep_lock_fd); sleep_lock_fd = -1; } preparing_for_sleep = FALSE; } else logind_manager_take_sleep_delay_lock(); } static void logind_manager_call_get_session_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) { GVariant *result; GError *error = NULL; gchar *session_object_path = NULL; result = g_dbus_proxy_call_finish(logind_manager, res, &error); if (!result) { g_warning("Error getting current session: %s", error->message); g_error_free(error); return; } g_variant_get(result, "(o)", &session_object_path); g_dbus_proxy_new_for_bus(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, NULL, LOGIND_SERVICE, session_object_path, LOGIND_SESSION_INTERFACE, NULL, logind_session_proxy_new_cb, NULL); g_variant_unref(result); g_free(session_object_path); } static void logind_session_proxy_new_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) { GError *error = NULL; logind_session = g_dbus_proxy_new_for_bus_finish(res, &error); if (!logind_session) { g_warning("Error connecting to session: %s", error->message); g_error_free(error); return; } g_signal_connect(logind_session, "g-signal", G_CALLBACK(logind_session_on_signal_lock), NULL); } static void logind_session_on_signal_lock(GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, gpointer user_data) { if (!g_strcmp0(signal_name, "Lock")) start_child(&locker); else if (!g_strcmp0(signal_name, "Unlock")) kill_child(&locker); } static void logind_session_set_idle_hint(gboolean idle) { if (logind_session) g_dbus_proxy_call(logind_session, "SetIdleHint", g_variant_new("(b)", idle), G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); } static gboolean parse_options(int argc, char *argv[], GError **error) { GOptionContext *opt_context; gboolean success; opt_context = g_option_context_new("- use external locker as X screen saver"); g_option_context_add_main_entries(opt_context, opt_entries, NULL); success = g_option_context_parse(opt_context, &argc, &argv, error); g_option_context_free(opt_context); if (success && !locker.cmd) { g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "No %s specified", locker.name); success = FALSE; } return success; } static gboolean parse_notifier_cmd(const gchar *option_name, const gchar *value, gpointer data, GError **error) { GError *parse_error = NULL; if (!g_shell_parse_argv(value, NULL, ¬ifier.cmd, &parse_error)) { g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "Error parsing argument for %s: %s", option_name, parse_error->message); g_error_free(parse_error); return FALSE; } return TRUE; } static gboolean reset_screensaver(xcb_connection_t *connection) { if (!locker.pid) xcb_force_screen_saver(connection, XCB_SCREEN_SAVER_RESET); return TRUE; } static gboolean exit_service(GMainLoop *loop) { kill_child(¬ifier); kill_child(&locker); g_main_loop_quit(loop); return TRUE; } static void log_handler(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) { if (opt_verbose || log_level & G_LOG_FLAG_FATAL || !(opt_quiet || log_level & G_LOG_LEVEL_MESSAGE)) g_log_default_handler(log_domain, log_level, message, user_data); } int main(int argc, char *argv[]) { GMainLoop *loop; GError *error = NULL; xcb_connection_t *connection = NULL; int default_screen_number; xcb_screen_t *default_screen; xcb_atom_t atom; setlocale(LC_ALL, ""); if (!parse_options(argc, argv, &error) || opt_print_version) { if (opt_print_version) { g_print(VERSION "\n"); g_clear_error(&error); } goto init_error; } g_log_set_default_handler(log_handler, NULL); connection = xcb_connect(NULL, &default_screen_number); if (xcb_connection_has_error(connection)) { g_set_error(&error, XCB_ERROR, 0, "Connecting to X server failed"); goto init_error; } #if !GLIB_CHECK_VERSION(2, 36, 0) g_type_init(); #endif g_dbus_proxy_new_for_bus(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, NULL, LOGIND_SERVICE, LOGIND_PATH, LOGIND_MANAGER_INTERFACE, NULL, logind_manager_proxy_new_cb, NULL); loop = g_main_loop_new(NULL, FALSE); g_unix_signal_add(SIGTERM, (GSourceFunc)exit_service, loop); g_unix_signal_add(SIGINT, (GSourceFunc)exit_service, loop); g_unix_signal_add(SIGHUP, (GSourceFunc)reset_screensaver, connection); default_screen = xcb_get_screen(connection, default_screen_number); if (!register_screensaver(connection, default_screen, &atom, &error)) goto init_error; g_main_loop_run(loop); unregister_screensaver(connection, default_screen, atom); g_main_loop_unref(loop); if (sleep_lock_fd >= 0) close(sleep_lock_fd); if (logind_manager) g_object_unref(logind_manager); if (logind_session) g_object_unref(logind_session); init_error: g_strfreev(notifier.cmd); g_strfreev(locker.cmd); if (connection) xcb_disconnect(connection); if (error) { g_printerr("%s\n", error->message); if (error->domain == G_OPTION_ERROR) g_printerr("Use --help or -h to see usage information.\n"); g_error_free(error); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); }