/* xscreensaver-systemd, Copyright (c) 2019-2021 * Martin Lucina and Jamie Zawinski * * ISC License * * Permission to use, copy, modify, and/or distribute this software * for any purpose with or without fee is hereby granted, provided * that the above copyright notice and this permission notice appear * in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * * This utility provides systemd integration for XScreenSaver. * It does two things: * * - When the system is about to go to sleep (e.g., laptop lid closing) * it locks the screen *before* the system goes to sleep, by running * "xscreensaver-command -suspend". And then when the system wakes * up again, it runs "xscreensaver-command -deactivate" to force the * unlock dialog to appear immediately. * * - When another process on the system makes asks for the screen saver * to be inhibited (e.g. because a video is playing) this program * periodically runs "xscreensaver-command -deactivate" to keep the * display un-blanked. It does this until the other program asks for * it to stop. * * For this to work at all, you must prevent Gnome and KDE from usurping * the "org.freedesktop.ScreenSaver" messages, or else this program can't * receive them. The "xscreensaver" man page contains the (complicated) * installation instructions. * * Background: * * For decades, the traditional way for a video player to temporarily * inhibit the screen saver was to have a heartbeat command that ran * "xscreensaver-command -deactivate" once a minute while the video was * playing, and ceased when the video was paused or stopped. The reason * to do it as a heartbeat rather than a toggle is so that the player * fails SAFE -- if the player exits abnormally, the heart stops beating, * and screen saving and locking resumes. * * These days, the popular apps do this by using systemd. The design of * the systemd method easily and trivially allows an app to inhibit the * screen saver, crash, and then never un-inhibit it, so now your screen * will never blank again. * * Furthermore, since the systemd method uses cookies to ensure that only * the app that sent "inhibit" can send the matching "uninhibit", simply * re-launching the crashed video player does not fix the problem. * * "Did IQs just drop sharply while I was away?" -- Ellen Ripley * * We can sometimes detect that the inhibiting app has exited abnormally * by using "tracking peers" but I'm not sure how reliable that is. * * Furthermore, we can't listen for these "inhibit blanking" requests * if some other program is already listening for them -- which Gnome and * KDE do by default, even if their screen savers are otherwise disabled. * That makes it far more complicated for the user to install XScreenSaver * in such a way that "xscreensaver-systemd" can even launch at all. * * To recap: because the existing video players decided to delete the * single line of code that they already had -- the heartbeat call to * "xscreensaver-command -deactivate" -- we had to respond by adding a * THOUSAND LINES of complicated code that talks to a server that may * not be running, and that may not allow us to connect, and that may * not work properly anyway, and that makes installation hellaciously * difficult and confusing for the end user. * * This is what is laughingly referred to as "progress". * * So here's what we're dealing with now, with the various apps that * you might use to play video on Linux at the end of 2020: * * ***************************************************************************** * * Firefox (version 78.5) * * When playing media, Firefox will send "inhibit" to one of these * targets: "org.freedesktop.ScreenSaver" or "org.gnome.SessionManager". * * However, Firefox decides which, if any, of those to use at launch time, * and does not revisit that decision. So if xscreensaver-systemd has not * been launched before Firefox, it won't work. Fortunately, in most use * cases, xscreensaver will have been launched earlier in the startup * sequence than the web browser. * * If you close the tab or exit while playing, Firefox sends "uninhibit". * * Critical Firefox Bug: * * If Firefox crashes or is killed while playing, it never sends * "uninhibit", leaving the screen saver permanently inhibited. Once * that happens, the only way to un-fuck things is to kill and restart * the "xscreensaver-systemd" program. * * Annoying Firefox Bug: * * Firefox sends an "inhibit" message when it is merely playing audio. * That's horrible. Playing audio should prevent your machine from going * to sleep, but it should NOT prevent your screen from blanking or * locking. * * However at least it sends it with the reason "audio-playing" instead * of "video-playing", meaning we can (and do) special-case Firefox and * ignore that one. * * ***************************************************************************** * * Chrome (version 87) * * Sends "inhibit" to "org.freedesktop.ScreenSaver" (though it uses a * a different object path than Firefox does). Unlike Firefox, Chrome * does not send an "inhibit" message when only audio is playing. * * Critical Chrome Bug: * * If Chrome crashes or is killed while playing, it never sends * "uninhibit", leaving the screen saver permanently inhibited. * * ***************************************************************************** * * Chromium (version 78, Raspbian 10.4) * * Does not use "org.freedesktop.ScreenSaver" or "xdg-screensaver". * It appears to make no attempt to inhibit the screen saver while * video is playing. * * ***************************************************************************** * * Chromium (version 84.0.4147.141, Raspbian 10.6) * * Sends "inhibit" to "org.freedesktop.ScreenSaver" (though it uses a * a different object path than Firefox does). Unlike Firefox, Chrome * does not send an "inhibit" message when only audio is playing. * * If you close the tab or exit while playing, Chromium sends "uninhibit". * * Critical Chromium Bug: * * If Chromium crashes or is killed while playing, it never sends * "uninhibit", leaving the screen saver permanently inhibited. * * Annoying Chromium Bug: * * Like Firefox, Chromium sends an "inhibit" message when it is merely * playing audio. Unlike Firefox, it sends exactly the same "reason" * string as it does when playing video, so we can't tell them apart. * * ***************************************************************************** * * MPV (version 0.29.1) * * While playing, it runs "xdg-screensaver reset" every 10 seconds as a * heartbeat. That program is a super-complicated shell script that will * eventually run "xscreensaver-command -reset". So MPV talks to the * xscreensaver daemon directly rather than going through systemd. * That's fine. * * On Debian 10.4 and 10.6, MPV does not have a dependency on the * "xdg-utils" package, so "xdg-screensaver" might not be installed. * Oddly, Chromium *does* have a dependency on "xdg-utils", even though * Chromium doesn't run "xdg-screensaver". * * The source code suggests that MPlayer and MPV call XResetScreenSaver() * as well, but only affects the X11 server's built-in screen saver, not * a userspace screen locker like xscreensaver. * * They also call XScreenSaverSuspend() which is part of the MIT * SCREEN-SAVER server extension. XScreenSaver does make use of that * extension because it is worse than useless. See the commentary at * the top of xscreensaver.c for details. * * Annoying MPV Bug: * * Like Firefox and Chromium, MPV inhibits screen blanking when only * audio is playing. * * ***************************************************************************** * * MPlayer (version mplayer-gui 2:1.3.0) * * I can't get this thing to play video at all. It only plays the audio * of MP4 files, so I can't guess what it might or might not do with video. * It appears to make no attempt to inhibit the screen saver. * * ***************************************************************************** * * VLC (version 3.0.11-0+deb10u1+rpt3) * * VLC sends "inhibit" to "org.freedesktop.ScreenSaver" when playing * video. It does not send "inhibit" when playing audio only, and it * sends "uninhibit" under all the right circumstances. * * NOTE: that's what I saw when I tested it on Raspbian 10.6. However, * the version that came with Raspbian 10.4 -- which also called itself * "VLC 3.0.11" -- did not send "uninhibit" when using the window * manager's "close" button! Or when killed with "kill". * * NOTE ALSO: The VLC source code suggests that under some circumstances * it might be talking to these instead: "org.freedesktop.ScreenSaver", * "org.freedesktop.PowerManagement.Inhibit", "org.mate.SessionManager", * and/or "org.gnome.SessionManager". It also contains code to run * "xdg-screensaver reset" as a heartbeat. I can't tell how it decides * which system to use. I have never seen it run "xdg-screensaver". * * ***************************************************************************** * * Zoom * * I'm told that the proprietary Zoom executable for Linux sends "inhibit" * to "org.freedesktop.ScreenSaver", but I don't have any further details. * ***************************************************************************** * * TO DO: * * - What precisely does the standalone Zoom executable do on Linux? * There doesn't seem to be a Raspbian build, so I can't test it. * * - xscreensaver_method_uninhibit() does not actually send a reply, are * we doing the right thing when registering it? * * - Currently this code is only listening to "org.freedesktop.ScreenSaver". * Perhaps it should listen to "org.mate.SessionManager" and * "org.gnome.SessionManager"? Where are those documented? * * - Do we need to call sd_bus_release_name() explicitly on exit? * * - Run under valgrind to check for any memory leaks. * * - Apparently the two different desktops have managed to come up with * *three* different ways for dbus clients to ask the question, "is the * screen currently blanked?" We should probably also respond to these: * * qdbus org.freedesktop.ScreenSaver /ScreenSaver org.freedesktop.ScreenSaver.GetActive * qdbus org.kde.screensaver /ScreenSaver org.freedesktop.ScreenSaver.GetActive * qdbus org.gnome.ScreenSaver /ScreenSaver org.gnome.ScreenSaver.GetActive * * * * TESTING: * * To call the D-BUS methods manually, you can use "busctl": * * busctl --user call org.freedesktop.ScreenSaver \ * /ScreenSaver org.freedesktop.ScreenSaver \ * Inhibit ss test-application test-reason * * This will hand out a cookie, which you can pass back to UnInhibit: * * u 1792821391 * * busctl --user call org.freedesktop.ScreenSaver \ * /ScreenSaver org.freedesktop.ScreenSaver \ * UnInhibit u 1792821391 * * https://github.com/mato/xscreensaver-systemd */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "version.h" #include "blurb.h" #include "yarandom.h" #include "queue.h" static char *screensaver_version; #define DBUS_CLIENT_NAME "org.jwz.XScreenSaver" #define DBUS_SD_SERVICE_NAME "org.freedesktop.login1" #define DBUS_SD_OBJECT_PATH "/org/freedesktop/login1" #define DBUS_SD_INTERFACE "org.freedesktop.login1.Manager" #define DBUS_SD_METHOD "Inhibit" #define DBUS_SD_METHOD_ARGS "ssss" #define DBUS_SD_METHOD_WHAT "sleep" #define DBUS_SD_METHOD_WHO "xscreensaver" #define DBUS_SD_METHOD_WHY "lock screen on suspend" #define DBUS_SD_METHOD_MODE "delay" #define DBUS_SD_MATCH "type='signal'," \ "interface='" DBUS_SD_INTERFACE "'," \ "member='PrepareForSleep'" #define DBUS_FDO_NAME "org.freedesktop.ScreenSaver" #define DBUS_FDO_OBJECT_PATH "/ScreenSaver" /* Firefox */ #define DBUS_FDO_OBJECT_PATH_2 "/org/freedesktop/ScreenSaver" /* Chrome */ #define DBUS_FDO_INTERFACE "org.freedesktop.ScreenSaver" #define HEARTBEAT_INTERVAL 50 /* seconds */ #undef countof #define countof(x) (sizeof((x))/sizeof((*x))) struct handler_ctx { sd_bus *system_bus; sd_bus_message *lock_message; int lock_fd; int is_inhibited; sd_bus_track *track; }; static struct handler_ctx global_ctx = { NULL, NULL, -1, 0, NULL }; SLIST_HEAD(inhibit_head, inhibit_entry) inhibit_head = SLIST_HEAD_INITIALIZER(inhibit_head); struct inhibit_entry { uint32_t cookie; time_t start_time; char *appname; char *peer; SLIST_ENTRY(inhibit_entry) entries; }; static void xscreensaver_command (const char *cmd) { char buf[1024]; int rc; sprintf (buf, "xscreensaver-command %.100s -%.100s", (verbose_p ? "-verbose" : "-quiet"), cmd); if (verbose_p) fprintf (stderr, "%s: exec: %s\n", blurb(), buf); rc = system (buf); if (rc == -1) fprintf (stderr, "%s: exec failed: %s\n", blurb(), buf); else if (WEXITSTATUS(rc) != 0) fprintf (stderr, "%s: exec: \"%s\" exited with status %d\n", blurb(), buf, WEXITSTATUS(rc)); } static int xscreensaver_register_sleep_lock (struct handler_ctx *ctx) { sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus_message *reply = NULL; int fd = -1; int rc = sd_bus_call_method (ctx->system_bus, DBUS_SD_SERVICE_NAME, DBUS_SD_OBJECT_PATH, DBUS_SD_INTERFACE, DBUS_SD_METHOD, &error, &reply, DBUS_SD_METHOD_ARGS, DBUS_SD_METHOD_WHAT, DBUS_SD_METHOD_WHO, DBUS_SD_METHOD_WHY, DBUS_SD_METHOD_MODE); if (rc < 0) { fprintf (stderr, "%s: inhibit sleep failed: %s\n", blurb(), error.message); goto DONE; } /* Save the lock fd and explicitly take a ref to the lock message. */ rc = sd_bus_message_read (reply, "h", &fd); if (rc < 0 || fd < 0) { fprintf (stderr, "%s: inhibit sleep failed: no lock fd: %s\n", blurb(), strerror(-rc)); goto DONE; } sd_bus_message_ref(reply); ctx->lock_message = reply; ctx->lock_fd = fd; DONE: sd_bus_error_free (&error); return rc; } /* Called when DBUS_SD_INTERFACE sends a "PrepareForSleep" signal. The event is sent twice: before sleep, and after. */ static int xscreensaver_systemd_handler (sd_bus_message *m, void *arg, sd_bus_error *ret_error) { struct handler_ctx *ctx = arg; int before_sleep; int rc; rc = sd_bus_message_read (m, "b", &before_sleep); if (rc < 0) { fprintf (stderr, "%s: message read failed: %s\n", blurb(), strerror(-rc)); return 1; /* >= 0 means success */ } /* Use the scheme described at https://www.freedesktop.org/wiki/Software/systemd/inhibit/ under "Taking Delay Locks". */ if (before_sleep) { /* Tell xscreensaver that we are suspending, and to lock if desired. */ xscreensaver_command ("suspend"); if (ctx->lock_message) { /* Release the lock, meaning we are done and it's ok to sleep now. Don't rely on unref'ing the message to close the fd, do that explicitly here. */ close(ctx->lock_fd); sd_bus_message_unref (ctx->lock_message); ctx->lock_message = NULL; ctx->lock_fd = -1; } else { fprintf (stderr, "%s: no context lock\n", blurb()); } } else { /* Tell xscreensaver to present the unlock dialog right now. */ xscreensaver_command ("deactivate"); /* We woke from sleep, so we need to re-register for the next sleep. */ rc = xscreensaver_register_sleep_lock (ctx); if (rc < 0) fprintf (stderr, "%s: could not re-register sleep lock\n", blurb()); } return 1; /* >= 0 means success */ } /* Called from the vtable when another process sends a request to systemd to inhibit the screen saver. We return to them a cookie which they must present with their "uninhibit" request. */ static int xscreensaver_method_inhibit (sd_bus_message *m, void *arg, sd_bus_error *ret_error) { struct handler_ctx *ctx = arg; const char *application_name = 0, *inhibit_reason = 0; struct inhibit_entry *entry = 0; const char *s; const char *sender; int rc = sd_bus_message_read(m, "ss", &application_name, &inhibit_reason); if (rc < 0) { fprintf (stderr, "%s: failed to parse method call: %s\n", blurb(), strerror(-rc)); return rc; } if (!application_name || !*application_name) { fprintf (stderr, "%s: no app name in method call\n", blurb()); return -1; } if (!inhibit_reason || !*inhibit_reason) { fprintf (stderr, "%s: no reason in method call from \"%s\"\n", blurb(), application_name); return -1; } sender = sd_bus_message_get_sender (m); /* Omit directory (Chrome does this shit) */ s = strrchr (application_name, '/'); if (s && s[1]) application_name = s+1; if (strcasestr (inhibit_reason, "audio") && !strcasestr (inhibit_reason, "video")) { /* Firefox 78 sends an inhibit when playing audio only, with reason "audio-playing". This is horrible. Ignore it. (But perhaps it would be better to accept it, issue them a cookie, and then just ignore that entry?) */ if (verbose_p) fprintf (stderr, "%s: inhibited by \"%s\" (%s) with \"%s\", ignored\n", blurb(), application_name, sender, inhibit_reason); return -1; } /* Tell the global tracker object to monitor when this peer exits. */ rc = sd_bus_track_add_name(ctx->track, sender); if (rc < 0) { fprintf (stderr, "%s: failed to track peer \"%s\": %s\n", blurb(), sender, strerror(-rc)); sender = NULL; } entry = malloc(sizeof (struct inhibit_entry)); entry->cookie = ya_random(); entry->appname = strdup(application_name); entry->peer = sender ? strdup(sender) : NULL; entry->start_time = time ((time_t *)0); SLIST_INSERT_HEAD(&inhibit_head, entry, entries); ctx->is_inhibited++; if (verbose_p) fprintf (stderr, "%s: inhibited by \"%s\" (%s) with \"%s\"" " -> cookie %08X\n", blurb(), application_name, sender, inhibit_reason, entry->cookie); return sd_bus_reply_method_return (m, "u", entry->cookie); } /* Called from the vtable when another process sends a request to systemd to uninhibit the screen saver. The cookie must match an earlier "inhibit" request. */ static int xscreensaver_method_uninhibit (sd_bus_message *m, void *arg, sd_bus_error *ret_error) { struct handler_ctx *ctx = arg; uint32_t cookie; struct inhibit_entry *entry; int found = 0; const char *sender; int rc = sd_bus_message_read (m, "u", &cookie); if (rc < 0) { fprintf (stderr, "%s: failed to parse method call: %s\n", blurb(), strerror(-rc)); return rc; } sender = sd_bus_message_get_sender (m); SLIST_FOREACH(entry, &inhibit_head, entries) { if (entry->cookie == cookie) { if (verbose_p) fprintf (stderr, "%s: uninhibited by \"%s\" (%s) with cookie %08X\n", blurb(), entry->appname, sender, cookie); SLIST_REMOVE (&inhibit_head, entry, inhibit_entry, entries); if (entry->appname) free (entry->appname); if (entry->peer) { rc = sd_bus_track_remove_name(ctx->track, entry->peer); if (rc < 0) { fprintf (stderr, "%s: failed to stop tracking peer \"%s\": %s\n", blurb(), entry->peer, strerror(-rc)); } free(entry->peer); } free(entry); ctx->is_inhibited--; if (ctx->is_inhibited < 0) ctx->is_inhibited = 0; found = 1; break; } } if (! found) fprintf (stderr, "%s: uninhibit: no match for cookie %08X\n", blurb(), cookie); return sd_bus_reply_method_return (m, ""); } /* * This vtable defines the service interface we implement. */ static const sd_bus_vtable xscreensaver_dbus_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_METHOD("Inhibit", "ss", "u", xscreensaver_method_inhibit, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("UnInhibit", "u", "", xscreensaver_method_uninhibit, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END }; /* The only reason this program connects to X at all is so that it dies right away when the X server shuts down. Otherwise the process might linger, still connected to systemd but unable to connect to xscreensaver. */ static Display * open_dpy (void) { Display *d; const char *s = getenv("DISPLAY"); if (!s || !*s) { fprintf (stderr, "%s: $DISPLAY unset\n", progname); exit (1); } d = XOpenDisplay (s); if (!d) { fprintf (stderr, "%s: can't open display %s\n", progname, s); exit (1); } return d; } static pid_t get_bus_name_pid (sd_bus *bus, const char *name) { int rc; sd_bus_creds *creds; pid_t pid; rc = sd_bus_get_name_creds (bus, name, SD_BUS_CREDS_PID, &creds); if (rc == 0) { rc = sd_bus_creds_get_pid (creds, &pid); sd_bus_creds_unref (creds); if (rc == 0) return pid; } return -1; } /* This only works on Linux, but it's useful for the error message. */ static char * process_name (pid_t pid) { char fn[100], buf[100], *s; FILE *fd = 0; if (pid <= 0) goto FAIL; /* "comm" truncates at 16 characters. "cmdline" has nulls between args. */ sprintf (fn, "/proc/%lu/cmdline", (unsigned long) pid); fd = fopen (fn, "r"); if (!fd) goto FAIL; if (!fgets (buf, sizeof(buf)-1, fd)) goto FAIL; if (fclose (fd) != 0) goto FAIL; s = strchr (buf, '\n'); if (s) *s = 0; return strdup (buf); FAIL: if (fd) fclose (fd); return 0; } static int xscreensaver_systemd_loop (void) { sd_bus *system_bus = NULL, *user_bus = NULL; struct handler_ctx *ctx = &global_ctx; sd_bus_error error = SD_BUS_ERROR_NULL; int rc; time_t last_deactivate_time = 0; Display *dpy = open_dpy(); /* 'user_bus' is where we receive messages from other programs sending inhibit/uninhibit to org.freedesktop.ScreenSaver, etc. */ rc = sd_bus_open_user (&user_bus); if (rc < 0) { fprintf (stderr, "%s: user bus connection failed: %s\n", blurb(), strerror(-rc)); goto FAIL; } /* Create a single tracking object so that we can later ask it, "is the peer with this name still around?" This is how we tell that Firefox has exited without calling 'uninhibit'. */ rc = sd_bus_track_new (user_bus, &global_ctx.track, NULL, NULL); if (rc < 0) { fprintf (stderr, "%s: cannot create user bus tracker: %s\n", blurb(), strerror(-rc)); goto FAIL; } rc = sd_bus_add_object_vtable (user_bus, NULL, DBUS_FDO_OBJECT_PATH, DBUS_FDO_INTERFACE, xscreensaver_dbus_vtable, &global_ctx); if (rc < 0) { fprintf (stderr, "%s: vtable registration failed: %s\n", blurb(), strerror(-rc)); goto FAIL; } rc = sd_bus_add_object_vtable (user_bus, NULL, DBUS_FDO_OBJECT_PATH_2, DBUS_FDO_INTERFACE, xscreensaver_dbus_vtable, &global_ctx); if (rc < 0) { fprintf (stderr, "%s: vtable registration failed: %s\n", blurb(), strerror(-rc)); goto FAIL; } { const char * const names[] = { DBUS_FDO_NAME, DBUS_CLIENT_NAME }; int i = 0; for (i = 0; i < countof(names); i++) { rc = sd_bus_request_name (user_bus, names[i], 0); if (rc < 0) { pid_t pid = get_bus_name_pid (user_bus, names[i]); if (pid != -1) { char *pname = process_name (pid); if (pname) { fprintf (stderr, "%s: connection failed: \"%s\" in use by pid %lu (%s)\n", blurb(), names[i], (unsigned long) pid, pname); free (pname); } else { fprintf (stderr, "%s: connection failed: \"%s\" in use by pid %lu\n", blurb(), names[i], (unsigned long) pid); } } else if (-rc == EEXIST || -rc == EALREADY) { fprintf (stderr, "%s: connection failed: \"%s\" already in use\n", blurb(), names[i]); } else { fprintf (stderr, "%s: connection failed for \"%s\": %s\n", blurb(), names[i], strerror(-rc)); } goto FAIL; } } } /* 'system_bus' is where we hold a lock on org.freedesktop.login1, meaning that the system will send us a PrepareForSleep message when the system is about to suspend. */ rc = sd_bus_open_system (&system_bus); if (rc < 0) { fprintf (stderr, "%s: system bus connection failed: %s\n", blurb(), strerror(-rc)); goto FAIL; } /* Obtain a lock fd from the "Inhibit" method, so that we can delay sleep when a "PrepareForSleep" signal is posted. */ ctx->system_bus = system_bus; rc = xscreensaver_register_sleep_lock (ctx); if (rc < 0) goto FAIL; /* This is basically an event mask, saying that we are interested in "PrepareForSleep", and to run our callback when that signal is thrown. */ rc = sd_bus_add_match (system_bus, NULL, DBUS_SD_MATCH, xscreensaver_systemd_handler, &global_ctx); if (rc < 0) { fprintf (stderr, "%s: add match failed: %s\n", blurb(), strerror(-rc)); goto FAIL; } if (verbose_p) fprintf (stderr, "%s: connected\n", blurb()); /* Run an event loop forever, and wait for our callback to run. */ while (1) { struct pollfd fds[3]; uint64_t poll_timeout, system_timeout, user_timeout; struct inhibit_entry *entry; /* We MUST call sd_bus_process() on each bus at least once before calling sd_bus_get_events(), so just always start the event loop by processing all outstanding requests on both busses. */ do { rc = sd_bus_process (system_bus, NULL); if (rc < 0) { fprintf (stderr, "%s: failed to process system bus: %s\n", blurb(), strerror(-rc)); goto FAIL; } } while (rc > 0); do { rc = sd_bus_process (user_bus, NULL); if (rc < 0) { fprintf (stderr, "%s: failed to process user bus: %s\n", blurb(), strerror(-rc)); goto FAIL; } } while (rc > 0); fds[0].fd = sd_bus_get_fd (system_bus); fds[0].events = sd_bus_get_events (system_bus); fds[0].revents = 0; fds[1].fd = sd_bus_get_fd (user_bus); fds[1].events = sd_bus_get_events (user_bus); fds[1].revents = 0; fds[2].fd = XConnectionNumber (dpy); fds[2].events = POLLIN; fds[2].revents = 0; sd_bus_get_timeout (system_bus, &system_timeout); sd_bus_get_timeout (user_bus, &user_timeout); if (system_timeout == 0 && user_timeout == 0) poll_timeout = 0; else if (system_timeout == UINT64_MAX && user_timeout == UINT64_MAX) poll_timeout = -1; else { poll_timeout = (system_timeout < user_timeout ? system_timeout : user_timeout); poll_timeout /= 1000000; } /* Prune any entries whose original sender has gone away: this happens if a program inhibits, then exits without having called uninhibit. That would have left us inhibited forever, even if the inhibiting program was re-launched, since the new instance won't have the same cookie. */ SLIST_FOREACH (entry, &inhibit_head, entries) { if (entry->peer && !sd_bus_track_count_name (ctx->track, entry->peer)) { if (verbose_p) fprintf (stderr, "%s: peer %s for inhibiting app \"%s\" has died:" " uninhibiting %08X\n", blurb(), entry->peer, entry->appname, entry->cookie); SLIST_REMOVE (&inhibit_head, entry, inhibit_entry, entries); if (entry->appname) free (entry->appname); free(entry->peer); free (entry); ctx->is_inhibited--; if (ctx->is_inhibited < 0) ctx->is_inhibited = 0; } } /* We want to wake up at least once every N seconds to de-activate the screensaver if we have been inhibited. */ if (poll_timeout > HEARTBEAT_INTERVAL * 1000) poll_timeout = HEARTBEAT_INTERVAL * 1000; rc = poll (fds, 3, poll_timeout); if (rc < 0) { fprintf (stderr, "%s: poll failed: %s\n", blurb(), strerror(-rc)); exit (EXIT_FAILURE); } if (fds[2].revents & (POLLERR | POLLHUP | POLLNVAL)) { fprintf (stderr, "%s: X connection closed\n", blurb()); goto FAIL; } if (ctx->is_inhibited) { time_t now = time ((time_t *) 0); if (now - last_deactivate_time >= HEARTBEAT_INTERVAL) { if (verbose_p) { SLIST_FOREACH (entry, &inhibit_head, entries) { char ct[100]; ctime_r (&entry->start_time, ct); fprintf (stderr, "%s: inhibited by \"%s\" since %s", blurb(), entry->appname, ct); } } xscreensaver_command ("deactivate"); last_deactivate_time = now; } } } FAIL: XCloseDisplay(dpy); if (system_bus) sd_bus_flush_close_unref (system_bus); if (ctx->track) sd_bus_track_unref (ctx->track); if (user_bus) sd_bus_flush_close_unref (user_bus); sd_bus_error_free (&error); return EXIT_FAILURE; } static char *usage = "\n\ usage: %s [-verbose]\n\ \n\ This program is launched by the xscreensaver daemon to monitor DBus.\n\ It invokes 'xscreensaver-command' to tell the xscreensaver daemon to lock\n\ the screen before the system suspends, e.g., when a laptop's lid is closed.\n\ \n\ It also responds to certain messages sent by media players allowing them to\n\ request that the screen not be blanked during playback.\n\ \n\ From XScreenSaver %s, (c) 1991-%s Jamie Zawinski .\n"; #define USAGE() do { \ fprintf (stderr, usage, progname, screensaver_version, year); exit (1); \ } while(0) int main (int argc, char **argv) { int i; char *version = strdup (screensaver_id + 17); char *year = strchr (version, '-'); char *s = strchr (version, ' '); *s = 0; year = strchr (year+1, '-') + 1; s = strchr (year, ')'); *s = 0; screensaver_version = version; progname = argv[0]; s = strrchr (progname, '/'); if (s) progname = s+1; for (i = 1; i < argc; i++) { const char *s = argv [i]; int L; if (s[0] == '-' && s[1] == '-') s++; L = strlen (s); if (L < 2) USAGE (); else if (!strncmp (s, "-verbose", L)) verbose_p = 1; else if (!strncmp (s, "-quiet", L)) verbose_p = 0; else USAGE (); } # undef ya_rand_init ya_rand_init (0); exit (xscreensaver_systemd_loop()); }