"Fossies" - the Fresh Open Source Software Archive

Member "xscreensaver-6.01/driver/xscreensaver-systemd.c" (4 Jun 2021, 34997 Bytes) of package /linux/misc/xscreensaver-6.01.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "xscreensaver-systemd.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 6.00_vs_6.01.

    1 /* xscreensaver-systemd, Copyright (c) 2019-2021
    2  * Martin Lucina <martin@lucina.net> and Jamie Zawinski <jwz@jwz.org>
    3  *
    4  * ISC License
    5  *
    6  * Permission to use, copy, modify, and/or distribute this software
    7  * for any purpose with or without fee is hereby granted, provided
    8  * that the above copyright notice and this permission notice appear
    9  * in all copies.
   10  *
   11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
   12  * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
   13  * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
   14  * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
   15  * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
   16  * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
   17  * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
   18  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
   19  *
   20  *
   21  * This utility provides systemd integration for XScreenSaver.
   22  * It does two things:
   23  *
   24  *   - When the system is about to go to sleep (e.g., laptop lid closing)
   25  *     it locks the screen *before* the system goes to sleep, by running
   26  *     "xscreensaver-command -suspend".  And then when the system wakes
   27  *     up again, it runs "xscreensaver-command -deactivate" to force the
   28  *     unlock dialog to appear immediately.
   29  *
   30  *   - When another process on the system makes asks for the screen saver
   31  *     to be inhibited (e.g. because a video is playing) this program
   32  *     periodically runs "xscreensaver-command -deactivate" to keep the
   33  *     display un-blanked.  It does this until the other program asks for
   34  *     it to stop.
   35  *
   36  * For this to work at all, you must prevent Gnome and KDE from usurping
   37  * the "org.freedesktop.ScreenSaver" messages, or else this program can't
   38  * receive them.  The "xscreensaver" man page contains the (complicated)
   39  * installation instructions.
   40  *
   41  * Background:
   42  *
   43  *     For decades, the traditional way for a video player to temporarily
   44  *     inhibit the screen saver was to have a heartbeat command that ran
   45  *     "xscreensaver-command -deactivate" once a minute while the video was
   46  *     playing, and ceased when the video was paused or stopped.  The reason
   47  *     to do it as a heartbeat rather than a toggle is so that the player
   48  *     fails SAFE -- if the player exits abnormally, the heart stops beating,
   49  *     and screen saving and locking resumes.
   50  *
   51  *     These days, the popular apps do this by using systemd.  The design of
   52  *     the systemd method easily and trivially allows an app to inhibit the
   53  *     screen saver, crash, and then never un-inhibit it, so now your screen
   54  *     will never blank again.
   55  *
   56  *     Furthermore, since the systemd method uses cookies to ensure that only
   57  *     the app that sent "inhibit" can send the matching "uninhibit", simply
   58  *     re-launching the crashed video player does not fix the problem.
   59  *
   60  *         "Did IQs just drop sharply while I was away?" -- Ellen Ripley
   61  *
   62  *     We can sometimes detect that the inhibiting app has exited abnormally
   63  *     by using "tracking peers" but I'm not sure how reliable that is.
   64  *
   65  *     Furthermore, we can't listen for these "inhibit blanking" requests
   66  *     if some other program is already listening for them -- which Gnome and
   67  *     KDE do by default, even if their screen savers are otherwise disabled.
   68  *     That makes it far more complicated for the user to install XScreenSaver
   69  *     in such a way that "xscreensaver-systemd" can even launch at all.
   70  *
   71  *     To recap: because the existing video players decided to delete the
   72  *     single line of code that they already had -- the heartbeat call to
   73  *     "xscreensaver-command -deactivate" -- we had to respond by adding a
   74  *     THOUSAND LINES of complicated code that talks to a server that may
   75  *     not be running, and that may not allow us to connect, and that may
   76  *     not work properly anyway, and that makes installation hellaciously
   77  *     difficult and confusing for the end user.
   78  *
   79  *     This is what is laughingly referred to as "progress".
   80  *
   81  *     So here's what we're dealing with now, with the various apps that
   82  *     you might use to play video on Linux at the end of 2020:
   83  *
   84  *
   85  *****************************************************************************
   86  *
   87  * Firefox (version 78.5)
   88  *
   89  *     When playing media, Firefox will send "inhibit" to one of these
   90  *     targets: "org.freedesktop.ScreenSaver" or "org.gnome.SessionManager".
   91  *
   92  *     However, Firefox decides which, if any, of those to use at launch time,
   93  *     and does not revisit that decision.  So if xscreensaver-systemd has not
   94  *     been launched before Firefox, it won't work.  Fortunately, in most use
   95  *     cases, xscreensaver will have been launched earlier in the startup
   96  *     sequence than the web browser.
   97  *
   98  *     If you close the tab or exit while playing, Firefox sends "uninhibit".
   99  *
  100  * Critical Firefox Bug:
  101  *
  102  *     If Firefox crashes or is killed while playing, it never sends
  103  *     "uninhibit", leaving the screen saver permanently inhibited.  Once
  104  *     that happens, the only way to un-fuck things is to kill and restart
  105  *     the "xscreensaver-systemd" program.
  106  *
  107  * Annoying Firefox Bug:
  108  *
  109  *     Firefox sends an "inhibit" message when it is merely playing audio.
  110  *     That's horrible.  Playing audio should prevent your machine from going
  111  *     to sleep, but it should NOT prevent your screen from blanking or
  112  *     locking.
  113  *
  114  *     However at least it sends it with the reason "audio-playing" instead
  115  *     of "video-playing", meaning we can (and do) special-case Firefox and
  116  *     ignore that one.
  117  *
  118  *
  119  *****************************************************************************
  120  *
  121  * Chrome (version 87)
  122  *
  123  *     Sends "inhibit" to "org.freedesktop.ScreenSaver" (though it uses a
  124  *     a different object path than Firefox does).  Unlike Firefox, Chrome
  125  *     does not send an "inhibit" message when only audio is playing.
  126  *
  127  * Critical Chrome Bug:
  128  *
  129  *     If Chrome crashes or is killed while playing, it never sends
  130  *     "uninhibit", leaving the screen saver permanently inhibited.
  131  *
  132  *
  133  *****************************************************************************
  134  *
  135  * Chromium (version 78, Raspbian 10.4)
  136  *
  137  *     Does not use "org.freedesktop.ScreenSaver" or "xdg-screensaver".
  138  *     It appears to make no attempt to inhibit the screen saver while
  139  *     video is playing.
  140  *
  141  *
  142  *****************************************************************************
  143  *
  144  * Chromium (version 84.0.4147.141, Raspbian 10.6)
  145  *
  146  *     Sends "inhibit" to "org.freedesktop.ScreenSaver" (though it uses a
  147  *     a different object path than Firefox does).  Unlike Firefox, Chrome
  148  *     does not send an "inhibit" message when only audio is playing.
  149  *
  150  *     If you close the tab or exit while playing, Chromium sends "uninhibit".
  151  *
  152  * Critical Chromium Bug:
  153  *
  154  *     If Chromium crashes or is killed while playing, it never sends
  155  *     "uninhibit", leaving the screen saver permanently inhibited.
  156  *
  157  * Annoying Chromium Bug:
  158  *
  159  *     Like Firefox, Chromium sends an "inhibit" message when it is merely
  160  *     playing audio.  Unlike Firefox, it sends exactly the same "reason"
  161  *     string as it does when playing video, so we can't tell them apart.
  162  *
  163  *
  164  *****************************************************************************
  165  *
  166  * MPV (version 0.29.1)
  167  *
  168  *     While playing, it runs "xdg-screensaver reset" every 10 seconds as a
  169  *     heartbeat.  That program is a super-complicated shell script that will
  170  *     eventually run "xscreensaver-command -reset".  So MPV talks to the
  171  *     xscreensaver daemon directly rather than going through systemd.
  172  *     That's fine.
  173  *
  174  *     On Debian 10.4 and 10.6, MPV does not have a dependency on the
  175  *     "xdg-utils" package, so "xdg-screensaver" might not be installed.
  176  *     Oddly, Chromium *does* have a dependency on "xdg-utils", even though
  177  *     Chromium doesn't run "xdg-screensaver".
  178  *
  179  *     The source code suggests that MPlayer and MPV call XResetScreenSaver()
  180  *     as well, but only affects the X11 server's built-in screen saver, not
  181  *     a userspace screen locker like xscreensaver.
  182  *
  183  *     They also call XScreenSaverSuspend() which is part of the MIT
  184  *     SCREEN-SAVER server extension.  XScreenSaver does make use of that
  185  *     extension because it is worse than useless.  See the commentary at
  186  *     the top of xscreensaver.c for details.
  187  *
  188  * Annoying MPV Bug:
  189  *
  190  *     Like Firefox and Chromium, MPV inhibits screen blanking when only
  191  *     audio is playing.
  192  *
  193  *
  194  *****************************************************************************
  195  *
  196  * MPlayer (version mplayer-gui 2:1.3.0)
  197  *
  198  *     I can't get this thing to play video at all.  It only plays the audio
  199  *     of MP4 files, so I can't guess what it might or might not do with video.
  200  *     It appears to make no attempt to inhibit the screen saver.
  201  *
  202  *
  203  *****************************************************************************
  204  *
  205  * VLC (version 3.0.11-0+deb10u1+rpt3)
  206  *
  207  *     VLC sends "inhibit" to "org.freedesktop.ScreenSaver" when playing
  208  *     video.  It does not send "inhibit" when playing audio only, and it
  209  *     sends "uninhibit" under all the right circumstances.
  210  *
  211  *     NOTE: that's what I saw when I tested it on Raspbian 10.6. However,
  212  *     the version that came with Raspbian 10.4 -- which also called itself
  213  *     "VLC 3.0.11" -- did not send "uninhibit" when using the window
  214  *     manager's "close" button!  Or when killed with "kill".
  215  *
  216  *     NOTE ALSO: The VLC source code suggests that under some circumstances
  217  *     it might be talking to these instead: "org.freedesktop.ScreenSaver",
  218  *     "org.freedesktop.PowerManagement.Inhibit", "org.mate.SessionManager",
  219  *     and/or "org.gnome.SessionManager".  It also contains code to run
  220  *     "xdg-screensaver reset" as a heartbeat.  I can't tell how it decides
  221  *     which system to use.  I have never seen it run "xdg-screensaver".
  222  *
  223  *
  224  *****************************************************************************
  225  *
  226  * Zoom
  227  *
  228  *    I'm told that the proprietary Zoom executable for Linux sends "inhibit"
  229  *    to "org.freedesktop.ScreenSaver", but I don't have any further details.
  230  *
  231  *
  232  *****************************************************************************
  233  *
  234  * Steam:
  235  *
  236  *     Inhibits as "My SDL application" (ooh, "Baby's First Hello World",
  237  *     nice!  You get a gold star sticker) and then 30 seconds later,
  238  *     uninhibits and immediately re-inhibits, forever.  This works, but
  239  *     is dumb.
  240  *
  241  *****************************************************************************
  242  *
  243  *
  244  * TO DO:
  245  *
  246  *   - What precisely does the standalone Zoom executable do on Linux?
  247  *     There doesn't seem to be a Raspbian build, so I can't test it.
  248  *
  249  *   - xscreensaver_method_uninhibit() does not actually send a reply, are
  250  *     we doing the right thing when registering it?
  251  *
  252  *   - Currently this code is only listening to "org.freedesktop.ScreenSaver".
  253  *     Perhaps it should listen to "org.mate.SessionManager" and
  254  *     "org.gnome.SessionManager"?  Where are those documented?
  255  *
  256  *   - Do we need to call sd_bus_release_name() explicitly on exit?
  257  *
  258  *   - Run under valgrind to check for any memory leaks.
  259  *
  260  *   - Apparently the two different desktops have managed to come up with
  261  *     *three* different ways for dbus clients to ask the question, "is the
  262  *     screen currently blanked?"  We should probably also respond to these:
  263  *
  264  *     qdbus org.freedesktop.ScreenSaver /ScreenSaver org.freedesktop.ScreenSaver.GetActive
  265  *     qdbus org.kde.screensaver         /ScreenSaver org.freedesktop.ScreenSaver.GetActive
  266  *     qdbus org.gnome.ScreenSaver       /ScreenSaver org.gnome.ScreenSaver.GetActive
  267  *
  268  *
  269  *
  270  * TESTING:
  271  *
  272  *   To call the D-BUS methods manually, you can use "busctl":
  273  *
  274  *   busctl --user call org.freedesktop.ScreenSaver \
  275  *     /ScreenSaver org.freedesktop.ScreenSaver \
  276  *     Inhibit ss test-application test-reason
  277  *
  278  *   This will hand out a cookie, which you can pass back to UnInhibit:
  279  *
  280  *   u 1792821391
  281  *
  282  *   busctl --user call org.freedesktop.ScreenSaver \
  283  *     /ScreenSaver org.freedesktop.ScreenSaver \
  284  *     UnInhibit u 1792821391
  285  *
  286  * https://github.com/mato/xscreensaver-systemd
  287  */
  288 
  289 #ifdef HAVE_CONFIG_H
  290 # include "config.h"
  291 #endif
  292 
  293 #define _GNU_SOURCE
  294 #include <stdio.h>
  295 #include <poll.h>
  296 #include <errno.h>
  297 #include <stdint.h>
  298 #include <stdlib.h>
  299 #include <string.h>
  300 #include <sys/wait.h>
  301 #include <unistd.h>
  302 #include <time.h>
  303 #include <sys/time.h>
  304 #include <sys/types.h>
  305 #include <signal.h>
  306 #include <X11/Xlib.h>
  307 
  308 #if defined (HAVE_LIBSYSTEMD)
  309 # include <systemd/sd-bus.h>
  310 #elif defined (HAVE_LIBELOGIND)
  311 # include <elogind/sd-bus.h>
  312 #else
  313 # error Neither HAVE_LIBSYSTEMD nor HAVE_LIBELOGIND is defined.
  314 #endif
  315 
  316 #include "version.h"
  317 #include "blurb.h"
  318 #include "yarandom.h"
  319 #include "queue.h"
  320 
  321 static char *screensaver_version;
  322 
  323 #define DBUS_CLIENT_NAME     "org.jwz.XScreenSaver"
  324 #define DBUS_SD_SERVICE_NAME "org.freedesktop.login1"
  325 #define DBUS_SD_OBJECT_PATH  "/org/freedesktop/login1"
  326 #define DBUS_SD_INTERFACE    "org.freedesktop.login1.Manager"
  327 #define DBUS_SD_METHOD       "Inhibit"
  328 #define DBUS_SD_METHOD_ARGS  "ssss"
  329 #define DBUS_SD_METHOD_WHAT  "sleep"
  330 #define DBUS_SD_METHOD_WHO   "xscreensaver"
  331 #define DBUS_SD_METHOD_WHY   "lock screen on suspend"
  332 #define DBUS_SD_METHOD_MODE  "delay"
  333 
  334 #define DBUS_SD_MATCH "type='signal'," \
  335                       "interface='" DBUS_SD_INTERFACE "'," \
  336                       "member='PrepareForSleep'"
  337 
  338 #define DBUS_FDO_NAME          "org.freedesktop.ScreenSaver"
  339 #define DBUS_FDO_OBJECT_PATH   "/ScreenSaver"           /* Firefox */
  340 #define DBUS_FDO_OBJECT_PATH_2 "/org/freedesktop/ScreenSaver"   /* Chrome  */
  341 #define DBUS_FDO_INTERFACE     "org.freedesktop.ScreenSaver"
  342 
  343 #define HEARTBEAT_INTERVAL 50  /* seconds */
  344 
  345 #undef countof
  346 #define countof(x) (sizeof((x))/sizeof((*x)))
  347 
  348 
  349 struct handler_ctx {
  350   sd_bus *system_bus;
  351   sd_bus_message *lock_message;
  352   int lock_fd;
  353   int is_inhibited;
  354   sd_bus_track *track;
  355 };
  356 
  357 static struct handler_ctx global_ctx = { NULL, NULL, -1, 0, NULL };
  358 
  359 SLIST_HEAD(inhibit_head, inhibit_entry) inhibit_head =
  360   SLIST_HEAD_INITIALIZER(inhibit_head);
  361 
  362 struct inhibit_entry {
  363   uint32_t cookie;
  364   time_t start_time;
  365   char *appname;
  366   char *peer;
  367   SLIST_ENTRY(inhibit_entry) entries;
  368 };
  369 
  370 
  371 static void
  372 xscreensaver_command (const char *cmd)
  373 {
  374   char buf[1024];
  375   int rc;
  376   sprintf (buf, "xscreensaver-command %.100s -%.100s",
  377            (verbose_p ? "-verbose" : "-quiet"),
  378            cmd);
  379   if (verbose_p)
  380     fprintf (stderr, "%s: exec: %s\n", blurb(), buf);
  381   rc = system (buf);
  382   if (rc == -1)
  383     fprintf (stderr, "%s: exec failed: %s\n", blurb(), buf);
  384   else if (WEXITSTATUS(rc) != 0)
  385     fprintf (stderr, "%s: exec: \"%s\" exited with status %d\n", 
  386              blurb(), buf, WEXITSTATUS(rc));
  387 }
  388 
  389 
  390 static int
  391 xscreensaver_register_sleep_lock (struct handler_ctx *ctx)
  392 {
  393   sd_bus_error error = SD_BUS_ERROR_NULL;
  394   sd_bus_message *reply = NULL;
  395   int fd = -1;
  396   int rc = sd_bus_call_method (ctx->system_bus,
  397                                DBUS_SD_SERVICE_NAME, DBUS_SD_OBJECT_PATH,
  398                                DBUS_SD_INTERFACE, DBUS_SD_METHOD,
  399                                &error, &reply,
  400                                DBUS_SD_METHOD_ARGS,
  401                                DBUS_SD_METHOD_WHAT, DBUS_SD_METHOD_WHO,
  402                                DBUS_SD_METHOD_WHY, DBUS_SD_METHOD_MODE);
  403   if (rc < 0) {
  404     fprintf (stderr, "%s: inhibit sleep failed: %s\n", 
  405              blurb(), error.message);
  406     goto DONE;
  407   }
  408 
  409   /* Save the lock fd and explicitly take a ref to the lock message. */
  410   rc = sd_bus_message_read (reply, "h", &fd);
  411   if (rc < 0 || fd < 0) {
  412     fprintf (stderr, "%s: inhibit sleep failed: no lock fd: %s\n",
  413              blurb(), strerror(-rc));
  414     goto DONE;
  415   }
  416   sd_bus_message_ref(reply);
  417   ctx->lock_message = reply;
  418   ctx->lock_fd = fd;
  419 
  420  DONE:
  421   sd_bus_error_free (&error);
  422 
  423   return rc;
  424 }
  425 
  426 
  427 /* Called when DBUS_SD_INTERFACE sends a "PrepareForSleep" signal.
  428    The event is sent twice: before sleep, and after.
  429  */
  430 static int
  431 xscreensaver_systemd_handler (sd_bus_message *m, void *arg,
  432                               sd_bus_error *ret_error)
  433 {
  434   struct handler_ctx *ctx = arg;
  435   int before_sleep;
  436   int rc;
  437 
  438   rc = sd_bus_message_read (m, "b", &before_sleep);
  439   if (rc < 0) {
  440     fprintf (stderr, "%s: message read failed: %s\n",
  441              blurb(), strerror(-rc));
  442     return 1;  /* >= 0 means success */
  443   }
  444 
  445   /* Use the scheme described at
  446      https://www.freedesktop.org/wiki/Software/systemd/inhibit/
  447      under "Taking Delay Locks".
  448    */
  449 
  450   if (before_sleep) {
  451     /* Tell xscreensaver that we are suspending, and to lock if desired. */
  452     xscreensaver_command ("suspend");
  453 
  454     if (ctx->lock_message) {
  455       /* Release the lock, meaning we are done and it's ok to sleep now.
  456          Don't rely on unref'ing the message to close the fd, do that
  457          explicitly here.
  458       */
  459       close(ctx->lock_fd);
  460       sd_bus_message_unref (ctx->lock_message);
  461       ctx->lock_message = NULL;
  462       ctx->lock_fd = -1;
  463     } else {
  464       fprintf (stderr, "%s: no context lock\n", blurb());
  465     }
  466   } else {
  467     /* Tell xscreensaver to present the unlock dialog right now. */
  468     xscreensaver_command ("deactivate");
  469 
  470     /* We woke from sleep, so we need to re-register for the next sleep. */
  471     rc = xscreensaver_register_sleep_lock (ctx);
  472     if (rc < 0)
  473       fprintf (stderr, "%s: could not re-register sleep lock\n", blurb());
  474   }
  475 
  476   return 1;  /* >= 0 means success */
  477 }
  478 
  479 
  480 /* Called from the vtable when another process sends a request to systemd
  481    to inhibit the screen saver.  We return to them a cookie which they must
  482    present with their "uninhibit" request.
  483  */
  484 static int
  485 xscreensaver_method_inhibit (sd_bus_message *m, void *arg,
  486                              sd_bus_error *ret_error)
  487 {
  488   struct handler_ctx *ctx = arg;
  489   const char *application_name = 0, *inhibit_reason = 0;
  490   struct inhibit_entry *entry = 0;
  491   const char *s;
  492   const char *sender;
  493 
  494   int rc = sd_bus_message_read(m, "ss", &application_name, &inhibit_reason);
  495   if (rc < 0) {
  496     fprintf (stderr, "%s: failed to parse method call: %s\n",
  497              blurb(), strerror(-rc));
  498     return rc;
  499   }
  500 
  501   if (!application_name || !*application_name) {
  502     fprintf (stderr, "%s: no app name in method call\n", blurb());
  503     return -1;
  504   }
  505 
  506   if (!inhibit_reason || !*inhibit_reason) {
  507     fprintf (stderr, "%s: no reason in method call from \"%s\"\n",
  508              blurb(), application_name);
  509     return -1;
  510   }
  511 
  512   sender = sd_bus_message_get_sender (m);
  513 
  514   /* Omit directory (Chrome does this shit) */
  515   s = strrchr (application_name, '/');
  516   if (s && s[1]) application_name = s+1;
  517 
  518   if (strcasestr (inhibit_reason, "audio") &&
  519       !strcasestr (inhibit_reason, "video")) {
  520     /* Firefox 78 sends an inhibit when playing audio only, with reason
  521        "audio-playing".  This is horrible.  Ignore it.  (But perhaps it
  522        would be better to accept it, issue them a cookie, and then just
  523        ignore that entry?) */
  524     if (verbose_p)
  525       fprintf (stderr, "%s: inhibited by \"%s\" (%s) with \"%s\", ignored\n",
  526                blurb(), application_name, sender, inhibit_reason);
  527     return -1;
  528   }
  529   
  530   /* Tell the global tracker object to monitor when this peer exits. */
  531   rc = sd_bus_track_add_name(ctx->track, sender);
  532   if (rc < 0) {
  533     fprintf (stderr, "%s: failed to track peer \"%s\": %s\n",
  534              blurb(), sender, strerror(-rc));
  535     sender = NULL;
  536   }
  537 
  538   entry = malloc(sizeof (struct inhibit_entry));
  539   entry->cookie = ya_random();
  540   entry->appname = strdup(application_name);
  541   entry->peer = sender ? strdup(sender) : NULL;
  542   entry->start_time = time ((time_t *)0);
  543   SLIST_INSERT_HEAD(&inhibit_head, entry, entries);
  544   ctx->is_inhibited++;
  545   if (verbose_p)
  546     fprintf (stderr, "%s: inhibited by \"%s\" (%s) with \"%s\""
  547              " -> cookie %08X\n",
  548              blurb(), application_name, sender, inhibit_reason, entry->cookie);
  549 
  550   return sd_bus_reply_method_return (m, "u", entry->cookie);
  551 }
  552 
  553 
  554 /* Called from the vtable when another process sends a request to systemd
  555    to uninhibit the screen saver.  The cookie must match an earlier "inhibit"
  556    request.
  557  */
  558 static int
  559 xscreensaver_method_uninhibit (sd_bus_message *m, void *arg,
  560                                sd_bus_error *ret_error)
  561 {
  562   struct handler_ctx *ctx = arg;
  563   uint32_t cookie;
  564   struct inhibit_entry *entry;
  565   int found = 0;
  566   const char *sender;
  567 
  568   int rc = sd_bus_message_read (m, "u", &cookie);
  569   if (rc < 0) {
  570     fprintf (stderr, "%s: failed to parse method call: %s\n",
  571              blurb(), strerror(-rc));
  572     return rc;
  573   }
  574 
  575   sender = sd_bus_message_get_sender (m);
  576 
  577   SLIST_FOREACH(entry, &inhibit_head, entries) {
  578     if (entry->cookie == cookie) {
  579       if (verbose_p)
  580         fprintf (stderr, "%s: uninhibited by \"%s\" (%s) with cookie %08X\n",
  581                  blurb(), entry->appname, sender, cookie);
  582       SLIST_REMOVE (&inhibit_head, entry, inhibit_entry, entries);
  583       if (entry->appname) free (entry->appname);
  584       if (entry->peer) {
  585         rc = sd_bus_track_remove_name(ctx->track, entry->peer);
  586         if (rc < 0) {
  587           fprintf (stderr, "%s: failed to stop tracking peer \"%s\": %s\n",
  588                    blurb(), entry->peer, strerror(-rc));
  589         }
  590         free(entry->peer);
  591       }
  592       free(entry);
  593       ctx->is_inhibited--;
  594       if (ctx->is_inhibited < 0)
  595         ctx->is_inhibited = 0;
  596       found = 1;
  597       break;
  598     }
  599   }
  600 
  601   if (! found)
  602     fprintf (stderr, "%s: uninhibit: no match for cookie %08X\n",
  603              blurb(), cookie);
  604 
  605   return sd_bus_reply_method_return (m, "");
  606 }
  607 
  608 /*
  609  * This vtable defines the service interface we implement.
  610  */
  611 static const sd_bus_vtable
  612 xscreensaver_dbus_vtable[] = {
  613     SD_BUS_VTABLE_START(0),
  614     SD_BUS_METHOD("Inhibit", "ss", "u", xscreensaver_method_inhibit,
  615                   SD_BUS_VTABLE_UNPRIVILEGED),
  616     SD_BUS_METHOD("UnInhibit", "u", "", xscreensaver_method_uninhibit,
  617                   SD_BUS_VTABLE_UNPRIVILEGED),
  618     SD_BUS_VTABLE_END
  619 };
  620 
  621 
  622 /* The only reason this program connects to X at all is so that it dies
  623    right away when the X server shuts down.  Otherwise the process might
  624    linger, still connected to systemd but unable to connect to xscreensaver.
  625  */
  626 static Display *
  627 open_dpy (void)
  628 {
  629   Display *d;
  630   const char *s = getenv("DISPLAY");
  631   if (!s || !*s) {
  632     fprintf (stderr, "%s: $DISPLAY unset\n", progname);
  633     exit (1);
  634   }
  635 
  636   d = XOpenDisplay (s);
  637   if (!d) {
  638     fprintf (stderr, "%s: can't open display %s\n", progname, s);
  639     exit (1);
  640   }
  641 
  642   return d;
  643 }
  644 
  645 
  646 static pid_t
  647 get_bus_name_pid (sd_bus *bus, const char *name)
  648 {
  649   int rc;
  650   sd_bus_creds *creds;
  651   pid_t pid;
  652 
  653   rc = sd_bus_get_name_creds (bus, name, SD_BUS_CREDS_PID, &creds);
  654   if (rc == 0) {
  655     rc = sd_bus_creds_get_pid (creds, &pid);
  656     sd_bus_creds_unref (creds);
  657     if (rc == 0)
  658       return pid;
  659   }
  660 
  661   return -1;
  662 }
  663 
  664 
  665 /* This only works on Linux, but it's useful for the error message.
  666  */
  667 static char *
  668 process_name (pid_t pid)
  669 {
  670   char fn[100], buf[100], *s;
  671   FILE *fd = 0;
  672   if (pid <= 0) goto FAIL;
  673   /* "comm" truncates at 16 characters. "cmdline" has nulls between args. */
  674   sprintf (fn, "/proc/%lu/cmdline", (unsigned long) pid);
  675   fd = fopen (fn, "r");
  676   if (!fd) goto FAIL;
  677   if (!fgets (buf, sizeof(buf)-1, fd)) goto FAIL;
  678   if (fclose (fd) != 0) goto FAIL;
  679   s = strchr (buf, '\n');
  680   if (s) *s = 0;
  681   return strdup (buf);
  682  FAIL:
  683   if (fd) fclose (fd);
  684   return 0;
  685 }
  686 
  687 
  688 static int
  689 xscreensaver_systemd_loop (void)
  690 {
  691   sd_bus *system_bus = NULL, *user_bus = NULL;
  692   struct handler_ctx *ctx = &global_ctx;
  693   sd_bus_error error = SD_BUS_ERROR_NULL;
  694   int rc;
  695   time_t last_deactivate_time = 0;
  696   Display *dpy = open_dpy();
  697 
  698   /* 'user_bus' is where we receive messages from other programs sending
  699      inhibit/uninhibit to org.freedesktop.ScreenSaver, etc.
  700    */
  701 
  702   rc = sd_bus_open_user (&user_bus);
  703   if (rc < 0) {
  704     fprintf (stderr, "%s: user bus connection failed: %s\n",
  705              blurb(), strerror(-rc));
  706     goto FAIL;
  707   }
  708 
  709   /* Create a single tracking object so that we can later ask it,
  710      "is the peer with this name still around?"  This is how we tell
  711      that Firefox has exited without calling 'uninhibit'.
  712    */
  713   rc = sd_bus_track_new (user_bus,
  714                          &global_ctx.track,
  715                          NULL,
  716                          NULL);
  717   if (rc < 0) {
  718     fprintf (stderr, "%s: cannot create user bus tracker: %s\n",
  719              blurb(), strerror(-rc));
  720     goto FAIL;
  721   }
  722 
  723   rc = sd_bus_add_object_vtable (user_bus,
  724                                  NULL,
  725                                  DBUS_FDO_OBJECT_PATH,
  726                                  DBUS_FDO_INTERFACE,
  727                                  xscreensaver_dbus_vtable,
  728                                  &global_ctx);
  729   if (rc < 0) {
  730     fprintf (stderr, "%s: vtable registration failed: %s\n",
  731              blurb(), strerror(-rc));
  732     goto FAIL;
  733   }
  734 
  735   rc = sd_bus_add_object_vtable (user_bus,
  736                                  NULL,
  737                                  DBUS_FDO_OBJECT_PATH_2,
  738                                  DBUS_FDO_INTERFACE,
  739                                  xscreensaver_dbus_vtable,
  740                                  &global_ctx);
  741   if (rc < 0) {
  742     fprintf (stderr, "%s: vtable registration failed: %s\n",
  743              blurb(), strerror(-rc));
  744     goto FAIL;
  745   }
  746 
  747   {
  748     const char * const names[] = { DBUS_FDO_NAME, DBUS_CLIENT_NAME };
  749     int i = 0;
  750     for (i = 0; i < countof(names); i++) {
  751       rc = sd_bus_request_name (user_bus, names[i], 0);
  752       if (rc < 0) {
  753         pid_t pid = get_bus_name_pid (user_bus, names[i]);
  754         if (pid != -1) {
  755           char *pname = process_name (pid);
  756           if (pname) {
  757             fprintf (stderr,
  758                      "%s: connection failed: \"%s\" in use by pid %lu (%s)\n",
  759                      blurb(), names[i], (unsigned long) pid, pname);
  760             free (pname);
  761           } else {
  762             fprintf (stderr,
  763                      "%s: connection failed: \"%s\" in use by pid %lu\n",
  764                      blurb(), names[i], (unsigned long) pid);
  765           }
  766         } else if (-rc == EEXIST || -rc == EALREADY) {
  767           fprintf (stderr, "%s: connection failed: \"%s\" already in use\n",
  768                    blurb(), names[i]);
  769         } else {
  770           fprintf (stderr, "%s: connection failed for \"%s\": %s\n",
  771                    blurb(), names[i], strerror(-rc));
  772         }
  773         goto FAIL;
  774       }
  775     }
  776   }
  777 
  778   /* 'system_bus' is where we hold a lock on org.freedesktop.login1, meaning
  779      that the system will send us a PrepareForSleep message when the system is
  780      about to suspend.
  781   */
  782 
  783   rc = sd_bus_open_system (&system_bus);
  784   if (rc < 0) {
  785     fprintf (stderr, "%s: system bus connection failed: %s\n",
  786              blurb(), strerror(-rc));
  787     goto FAIL;
  788   }
  789 
  790   /* Obtain a lock fd from the "Inhibit" method, so that we can delay
  791      sleep when a "PrepareForSleep" signal is posted. */
  792 
  793   ctx->system_bus = system_bus;
  794   rc = xscreensaver_register_sleep_lock (ctx);
  795   if (rc < 0)
  796     goto FAIL;
  797 
  798   /* This is basically an event mask, saying that we are interested in
  799      "PrepareForSleep", and to run our callback when that signal is thrown.
  800   */
  801   rc = sd_bus_add_match (system_bus, NULL, DBUS_SD_MATCH,
  802                          xscreensaver_systemd_handler,
  803                          &global_ctx);
  804   if (rc < 0) {
  805     fprintf (stderr, "%s: add match failed: %s\n", blurb(), strerror(-rc));
  806     goto FAIL;
  807   }
  808 
  809   if (verbose_p)
  810     fprintf (stderr, "%s: connected\n", blurb());
  811 
  812 
  813   /* Run an event loop forever, and wait for our callback to run.
  814    */
  815   while (1) {
  816     struct pollfd fds[3];
  817     uint64_t poll_timeout_msec, system_timeout_usec, user_timeout_usec;
  818     struct inhibit_entry *entry;
  819 
  820     /* We MUST call sd_bus_process() on each bus at least once before calling
  821        sd_bus_get_events(), so just always start the event loop by processing
  822        all outstanding requests on both busses. */
  823     do {
  824       rc = sd_bus_process (system_bus, NULL);
  825       if (rc < 0) {
  826         fprintf (stderr, "%s: failed to process system bus: %s\n",
  827                  blurb(), strerror(-rc));
  828         goto FAIL;
  829       }
  830     } while (rc > 0);
  831 
  832     do {
  833       rc = sd_bus_process (user_bus, NULL);
  834       if (rc < 0) {
  835         fprintf (stderr, "%s: failed to process user bus: %s\n",
  836                  blurb(), strerror(-rc));
  837         goto FAIL;
  838       }
  839     } while (rc > 0);
  840 
  841     /* Prune any entries whose original sender has gone away: this happens
  842        if a program inhibits, then exits without having called uninhibit.
  843        That would have left us inhibited forever, even if the inhibiting
  844        program was re-launched, since the new instance won't have the
  845        same cookie. */
  846     SLIST_FOREACH (entry, &inhibit_head, entries) {
  847       if (entry->peer &&
  848           !sd_bus_track_count_name (ctx->track, entry->peer)) {
  849         if (verbose_p)
  850           fprintf (stderr,
  851                    "%s: peer %s for inhibiting app \"%s\" has died:"
  852                    " uninhibiting %08X\n",
  853                    blurb(),
  854                    entry->peer,
  855                    entry->appname,
  856                    entry->cookie);
  857         SLIST_REMOVE (&inhibit_head, entry, inhibit_entry, entries);
  858         if (entry->appname) free (entry->appname);
  859         free(entry->peer);
  860         free (entry);
  861         ctx->is_inhibited--;
  862         if (ctx->is_inhibited < 0)
  863           ctx->is_inhibited = 0;
  864       }
  865     }
  866 
  867     /* If we are inhibited and HEARTBEAT_INTERVAL has passed, de-activate the
  868        screensaver. */
  869     if (ctx->is_inhibited) {
  870       time_t now = time ((time_t *) 0);
  871       if (now - last_deactivate_time >= HEARTBEAT_INTERVAL) {
  872         if (verbose_p) {
  873           SLIST_FOREACH (entry, &inhibit_head, entries) {
  874             char ct[100];
  875             ctime_r (&entry->start_time, ct);
  876             fprintf (stderr, "%s: inhibited by \"%s\" since %s",
  877                      blurb(), entry->appname, ct);
  878           }
  879         }
  880         xscreensaver_command ("deactivate");
  881         last_deactivate_time = now;
  882       }
  883     }
  884 
  885     /* The remainder of the code that follows is concerned solely with
  886        determining how long we should wait until the next iteration of the
  887        event loop.
  888     */
  889     rc = sd_bus_get_fd (system_bus);
  890     if (rc < 0) {
  891       fprintf (stderr, "%s: sd_bus_get_fd failed for system bus: %s\n",
  892                blurb(), strerror(-rc));
  893       goto FAIL;
  894     }
  895     fds[0].fd = rc;
  896     rc = sd_bus_get_events (system_bus);
  897     if (rc < 0) {
  898       fprintf (stderr, "%s: sd_bus_get_events failed for system bus: %s\n",
  899                blurb(), strerror(-rc));
  900       goto FAIL;
  901     }
  902     fds[0].events = rc;
  903     fds[0].revents = 0;
  904 
  905     rc = sd_bus_get_fd (user_bus);
  906     if (rc < 0) {
  907       fprintf (stderr, "%s: sd_bus_get_fd failed for user bus: %s\n",
  908                blurb(), strerror(-rc));
  909       goto FAIL;
  910     }
  911     fds[1].fd = rc;
  912     rc = sd_bus_get_events (user_bus);
  913     if (rc < 0) {
  914       fprintf (stderr, "%s: sd_bus_get_events failed for user bus: %s\n",
  915                blurb(), strerror(-rc));
  916       goto FAIL;
  917     }
  918     fds[1].events = rc;
  919     fds[1].revents = 0;
  920 
  921     /* Activity on the X server connection will wake us from the poll(). */
  922     fds[2].fd = XConnectionNumber (dpy);
  923     fds[2].events = POLLIN;
  924     fds[2].revents = 0;
  925 
  926     rc = sd_bus_get_timeout (system_bus, &system_timeout_usec);
  927     if (rc < 0) {
  928       fprintf (stderr, "%s: sd_bus_get_timeout failed for system bus: %s\n",
  929                blurb(), strerror(-rc));
  930       goto FAIL;
  931     }
  932     sd_bus_get_timeout (user_bus, &user_timeout_usec);
  933     if (rc < 0) {
  934       fprintf (stderr, "%s: sd_bus_get_timeout failed for user bus: %s\n",
  935                blurb(), strerror(-rc));
  936       goto FAIL;
  937     }
  938 
  939     /* Pick the smaller of the two bus timeouts and convert from microseconds
  940        to milliseconds expected by poll(). */
  941     poll_timeout_msec = ((system_timeout_usec < user_timeout_usec
  942                           ? system_timeout_usec : user_timeout_usec)
  943                          / 1000);
  944 
  945     /* If we have been inhibited, we want to wake up at least once every N
  946        seconds to de-activate the screensaver.
  947      */
  948     if (ctx->is_inhibited &&
  949         poll_timeout_msec > HEARTBEAT_INTERVAL * 1000)
  950       poll_timeout_msec = HEARTBEAT_INTERVAL * 1000;
  951 
  952     if (poll_timeout_msec < 1000)
  953       poll_timeout_msec = 1000;
  954 
  955     rc = poll (fds, 3, poll_timeout_msec);
  956     if (rc < 0) {
  957       fprintf (stderr, "%s: poll failed: %s\n", blurb(), strerror(errno));
  958       goto FAIL;
  959     }
  960 
  961     if (fds[2].revents & (POLLERR | POLLHUP | POLLNVAL)) {
  962       fprintf (stderr, "%s: X connection closed\n", blurb());
  963       dpy = 0;
  964       goto FAIL;
  965     } else if (fds[2].revents & POLLIN) {
  966       /* Even though we have requested no events, there are some events that
  967          the X server sends anyway, e.g. MappingNotify, and if we don't flush
  968          the fd, it will constantly be ready for reading, and we busy-loop. */
  969       char buf[1024];
  970       while (read (fds[2].fd, buf, sizeof(buf)) > 0)
  971         ;
  972     }
  973 
  974   } /* Event loop end */
  975 
  976  FAIL:
  977 
  978   if (system_bus)
  979     sd_bus_flush_close_unref (system_bus);
  980 
  981   if (ctx->track)
  982     sd_bus_track_unref (ctx->track);
  983 
  984   if (user_bus)
  985     sd_bus_flush_close_unref (user_bus);
  986 
  987   sd_bus_error_free (&error);
  988 
  989   if (dpy)
  990     XCloseDisplay (dpy);
  991 
  992   return EXIT_FAILURE;
  993 }
  994 
  995 
  996 static char *usage = "\n\
  997 usage: %s [-verbose]\n\
  998 \n\
  999 This program is launched by the xscreensaver daemon to monitor DBus.\n\
 1000 It invokes 'xscreensaver-command' to tell the xscreensaver daemon to lock\n\
 1001 the screen before the system suspends, e.g., when a laptop's lid is closed.\n\
 1002 \n\
 1003 It also responds to certain messages sent by media players allowing them to\n\
 1004 request that the screen not be blanked during playback.\n\
 1005 \n\
 1006 From XScreenSaver %s, (c) 1991-%s Jamie Zawinski <jwz@jwz.org>.\n";
 1007 
 1008 
 1009 #define USAGE() do { \
 1010  fprintf (stderr, usage, progname, screensaver_version, year); exit (1); \
 1011  } while(0)
 1012 
 1013 
 1014 int
 1015 main (int argc, char **argv)
 1016 {
 1017   int i;
 1018   char *version = strdup (screensaver_id + 17);
 1019   char *year = strchr (version, '-');
 1020   char *s = strchr (version, ' ');
 1021   *s = 0;
 1022   year = strchr (year+1, '-') + 1;
 1023   s = strchr (year, ')');
 1024   *s = 0;
 1025 
 1026   screensaver_version = version;
 1027 
 1028   progname = argv[0];
 1029   s = strrchr (progname, '/');
 1030   if (s) progname = s+1;
 1031 
 1032   for (i = 1; i < argc; i++)
 1033     {
 1034       const char *s = argv [i];
 1035       int L;
 1036       if (s[0] == '-' && s[1] == '-') s++;
 1037       L = strlen (s);
 1038       if (L < 2) USAGE ();
 1039       else if (!strncmp (s, "-verbose", L)) verbose_p = 1;
 1040       else if (!strncmp (s, "-quiet",   L)) verbose_p = 0;
 1041       else USAGE ();
 1042     }
 1043 
 1044 # undef ya_rand_init
 1045   ya_rand_init (0);
 1046 
 1047   exit (xscreensaver_systemd_loop());
 1048 }