├── .gitignore ├── version.h ├── blurb.h ├── swayidle-inhibit ├── Makefile ├── idlehack.service ├── LICENSE.txt ├── blurb.c ├── README ├── yarandom.h ├── yarandom.c ├── queue.h └── idlehack.c /.gitignore: -------------------------------------------------------------------------------- 1 | idlehack 2 | *.o 3 | -------------------------------------------------------------------------------- /version.h: -------------------------------------------------------------------------------- 1 | static const char screensaver_id[] = 2 | "@(#)xscreensaver 6.02 (11-Oct-2021), by Jamie Zawinski (jwz@jwz.org)"; 3 | #define XSCREENSAVER_VERSION "6.02" 4 | #define XSCREENSAVER_RELEASED 1633978800 5 | -------------------------------------------------------------------------------- /blurb.h: -------------------------------------------------------------------------------- 1 | /* progname plus timestamp */ 2 | 3 | #ifndef __BLURB_H__ 4 | #define __BLURB_H__ 5 | 6 | extern const char *progname; 7 | extern int verbose_p; 8 | extern const char *blurb (void); 9 | 10 | #endif /* __BLURB_H__ */ 11 | 12 | -------------------------------------------------------------------------------- /swayidle-inhibit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Swayidle respects systemd-inhibit messages, so use that to send the signal 4 | 5 | # block system idle for a minute 6 | systemd-inhibit --what=idle --who=swayidle-inhibit --why=commanded --mode=block sleep 60 & 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS:=$(shell pkg-config --cflags dbus-1) -DHAVE_LIBSYSTEMD -DHAVE_UNISTD_H -DGETTIMEOFDAY_TWO_ARGS -O3 2 | LDFLAGS:=$(shell pkg-config --libs dbus-1) $(shell pkg-config --libs libsystemd) 3 | idlehack: idlehack.o blurb.o yarandom.o 4 | cc -o $@ $? $(LDFLAGS) 5 | 6 | -------------------------------------------------------------------------------- /idlehack.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Idle Hack. It will run /bin/swayidle-inhibit when firefox requests idle inhibit 3 | After=syslog.target 4 | 5 | [Service] 6 | Type=simple 7 | ExecStart=/usr/libexec/idlehack 8 | Restart=on-failure 9 | RestartSec=10 10 | KillMode=process 11 | 12 | [Install] 13 | WantedBy=default.target 14 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* xscreensaver-systemd, Copyright (c) 2019-2021 2 | * Martin Lucina and Jamie Zawinski 3 | * Modified by Sean Estabrooks 4 | * 5 | * ISC License 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software 8 | * for any purpose with or without fee is hereby granted, provided 9 | * that the above copyright notice and this permission notice appear 10 | * in all copies. 11 | * 12 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 13 | * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 14 | * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 15 | * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR 16 | * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 17 | * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, 18 | * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 19 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 20 | */ 21 | -------------------------------------------------------------------------------- /blurb.c: -------------------------------------------------------------------------------- 1 | /* xscreensaver, Copyright © 1991-2021 Jamie Zawinski 2 | * 3 | * Permission to use, copy, modify, distribute, and sell this software and its 4 | * documentation for any purpose is hereby granted without fee, provided that 5 | * the above copyright notice appear in all copies and that both that 6 | * copyright notice and this permission notice appear in supporting 7 | * documentation. No representations are made about the suitability of this 8 | * software for any purpose. It is provided "as is" without express or 9 | * implied warranty. 10 | */ 11 | 12 | #ifdef HAVE_CONFIG_H 13 | # include "config.h" 14 | #endif 15 | 16 | #include "blurb.h" 17 | 18 | #include 19 | #include 20 | 21 | const char *progname = ""; 22 | int verbose_p = 1; 23 | 24 | const char * 25 | blurb (void) 26 | { 27 | static char buf[255] = { 0 }; 28 | struct tm tm; 29 | time_t now; 30 | int i; 31 | 32 | now = time ((time_t *) 0); 33 | localtime_r (&now, &tm); 34 | i = strlen (progname); 35 | if (i > 40) i = 40; 36 | memcpy (buf, progname, i); 37 | buf[i++] = ':'; 38 | buf[i++] = ' '; 39 | buf[i++] = '0' + (tm.tm_hour >= 10 ? tm.tm_hour/10 : 0); 40 | buf[i++] = '0' + (tm.tm_hour % 10); 41 | buf[i++] = ':'; 42 | buf[i++] = '0' + (tm.tm_min >= 10 ? tm.tm_min/10 : 0); 43 | buf[i++] = '0' + (tm.tm_min % 10); 44 | buf[i++] = ':'; 45 | buf[i++] = '0' + (tm.tm_sec >= 10 ? tm.tm_sec/10 : 0); 46 | buf[i++] = '0' + (tm.tm_sec % 10); 47 | buf[i] = 0; 48 | return buf; 49 | } 50 | 51 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Idlehack for Sway 2 | ----------------- 3 | 4 | Listen for Firefox/Chromium dbus messages that request screensaver inhibit, 5 | typically because the user is watching video. Sway doesn't currently listen 6 | for such messages, so here we create a daemon that listens for these messages 7 | and then invokes "/bin/swayidle-inhibit" which is responsible for temporarily 8 | disabling the screen blanking. 9 | 10 | The XScreenSaver code which handled this was scavenged off of the net and 11 | hacked, most of the credit should go to them. 12 | 13 | Idlehack needs to be running in the background in order to create the dbus 14 | listener before Firefox is launched. Firefox only checks for the Dbus 15 | end-points when first starting. If it sees the dbus listener, it will 16 | send the inhibit messages every time video is playing. 17 | 18 | Idlehack will call /bin/swayidle-inhibit everytime such a message is 19 | received from Firefox. 20 | 21 | You need a "/bin/swayidle-inhibit" that will do whatever is necessary 22 | to inhibit the screensaver on your system for say 60 seconds. Idlehack 23 | will keep calling this command while the video is playing. It's done 24 | this way so that your screensaver isn't permanently inhibited if Firefox 25 | crashes while holding the lock. 26 | 27 | 28 | For a Systemd based system 29 | -------------------------- 30 | 31 | Compile "idlehack" and move the executable to "/usr/libexec/" 32 | 33 | Copy the "idlehack.service" file to "/etc/systemd/user/" 34 | 35 | Copy "swayidle-inhibit" to "/bin/" 36 | 37 | Run: 38 | # systemctl --user enable idlehack 39 | # systemctl --user start idlehack 40 | 41 | Then restart Firefox 42 | 43 | 44 | You can do "systemctl --user status idlehack" after starting a video to see that 45 | the inhibit has been called. 46 | -------------------------------------------------------------------------------- /yarandom.h: -------------------------------------------------------------------------------- 1 | /* xscreensaver, Copyright (c) 1997, 1998, 2003 by Jamie Zawinski 2 | * 3 | * Permission to use, copy, modify, distribute, and sell this software and its 4 | * documentation for any purpose is hereby granted without fee, provided that 5 | * the above copyright notice appear in all copies and that both that 6 | * copyright notice and this permission notice appear in supporting 7 | * documentation. No representations are made about the suitability of this 8 | * software for any purpose. It is provided "as is" without express or 9 | * implied warranty. 10 | */ 11 | 12 | #ifndef __YARANDOM_H__ 13 | #define __YARANDOM_H__ 14 | 15 | #ifdef HAVE_CONFIG_H 16 | # include "config.h" 17 | #endif 18 | 19 | #ifdef HAVE_INTTYPES_H 20 | # include 21 | #endif 22 | 23 | #undef random 24 | #undef rand 25 | #undef drand48 26 | #undef srandom 27 | #undef srand 28 | #undef frand 29 | #undef sranddev 30 | #undef srandomdev 31 | #undef arc4random 32 | #undef arc4random_addrandom 33 | #undef arc4random_buf 34 | #undef arc4random_stir 35 | #undef arc4random_uniform 36 | #undef erand48 37 | #undef jrand48 38 | #undef lcong48 39 | #undef lrand48 40 | #undef mrand48 41 | #undef nrand48 42 | #undef seed48 43 | #undef srand48 44 | #undef rand_r 45 | #undef RAND_MAX 46 | 47 | #ifdef VMS 48 | # include "vms-gtod.h" 49 | #endif 50 | 51 | extern unsigned int ya_random (void); 52 | extern void ya_rand_init (unsigned int); 53 | 54 | #define random() ya_random() 55 | #define RAND_MAX 0xFFFFFFFF 56 | 57 | /*#define srandom(i) ya_rand_init(0)*/ 58 | 59 | /* Define these away to keep people from using the wrong APIs in xscreensaver. 60 | */ 61 | #define rand __ERROR_use_random_not_rand_in_xscreensaver__ 62 | #define drand48 __ERROR_use_frand_not_drand48_in_xscreensaver__ 63 | #define srandom __ERROR_do_not_call_srandom_in_xscreensaver__ 64 | #define srand __ERROR_do_not_call_srand_in_xscreensaver__ 65 | #define sranddev __ERROR_do_not_call_sranddev_in_xscreensaver__ 66 | #define ya_rand_init __ERROR_do_not_call_ya_rand_init_in_xscreensaver__ 67 | #define srandomdev __ERROR_do_not_call_srandomdev_in_xscreensaver__ 68 | #define arc4random __ERROR_do_not_call_arc4random_in_xscreensaver__ 69 | #define arc4random_addrandom __ERROR_do_not_call_arc4random_in_xscreensaver__ 70 | #define arc4random_buf __ERROR_do_not_call_arc4random_in_xscreensaver__ 71 | #define arc4random_stir __ERROR_do_not_call_arc4random_in_xscreensaver__ 72 | #define arc4random_uniform __ERROR_do_not_call_arc4random_in_xscreensaver__ 73 | #define erand48 __ERROR_do_not_call_erand48_in_xscreensaver__ 74 | #define jrand48 __ERROR_do_not_call_jrand48_in_xscreensaver__ 75 | #define lcong48 __ERROR_do_not_call_lcong48_in_xscreensaver__ 76 | #define lrand48 __ERROR_do_not_call_lrand48_in_xscreensaver__ 77 | #define mrand48 __ERROR_do_not_call_mrand48_in_xscreensaver__ 78 | #define nrand48 __ERROR_do_not_call_nrand48_in_xscreensaver__ 79 | #define seed48 __ERROR_do_not_call_seed48_in_xscreensaver__ 80 | #define srand48 __ERROR_do_not_call_srand48_in_xscreensaver__ 81 | #define rand_r __ERROR_do_not_call_rand_r_in_xscreensaver__ 82 | 83 | 84 | #if defined (__GNUC__) && (__GNUC__ >= 2) 85 | /* Implement frand using GCC's statement-expression extension. */ 86 | 87 | # define frand(f) \ 88 | __extension__ \ 89 | ({ double tmp = ((((double) random()) * ((double) (f))) / \ 90 | ((double) ((unsigned int)~0))); \ 91 | tmp < 0 ? (-tmp) : tmp; }) 92 | 93 | #else /* not GCC2 - implement frand using a global variable.*/ 94 | 95 | static double _frand_tmp_; 96 | # define frand(f) \ 97 | (_frand_tmp_ = ((((double) random()) * ((double) (f))) / \ 98 | ((double) ((unsigned int)~0))), \ 99 | _frand_tmp_ < 0 ? (-_frand_tmp_) : _frand_tmp_) 100 | 101 | #endif /* not GCC2 */ 102 | 103 | /* Compatibility with the xlockmore RNG API 104 | (note that the xlockmore hacks never expect negative numbers.) 105 | */ 106 | #define LRAND() ((long) (random() & 0x7fffffff)) 107 | 108 | /* The first NRAND(n) is much faster, when uint64_t is available to allow it. 109 | * Especially on ARM and other processors without built-in division. 110 | * 111 | * n must be greater than zero. 112 | * 113 | * The division by RAND_MAX+1 should optimize down to a bit shift. 114 | * 115 | * Although the result here is never negative, this needs to return signed 116 | * integers: A binary operator in C requires operands have the same type, and 117 | * if one side is signed and the other is unsigned, but they're both the same 118 | * size, then the signed operand is cast to unsigned, and the result is 119 | * similarly unsigned. This can potentially cause problems when idioms like 120 | * "NRAND(n) * RANDSIGN()" (where RANDSIGN() returns either -1 or 1) is used 121 | * to get random numbers from the range (-n,n). 122 | */ 123 | #ifdef HAVE_INTTYPES_H 124 | # define NRAND(n) ((int32_t) ((uint64_t) random() * (uint32_t) (n) / \ 125 | ((uint64_t) RAND_MAX + 1))) 126 | #else 127 | # define NRAND(n) ((int) (LRAND() % (n))) 128 | #endif 129 | 130 | #define MAXRAND (2147483648.0) /* unsigned 1<<31 as a float */ 131 | #define SRAND(n) /* already seeded by screenhack.c */ 132 | 133 | #endif /* __YARANDOM_H__ */ 134 | -------------------------------------------------------------------------------- /yarandom.c: -------------------------------------------------------------------------------- 1 | /* yarandom.c -- Yet Another Random Number Generator. 2 | * Copyright (c) 1997-2014 by Jamie Zawinski 3 | * 4 | * Permission to use, copy, modify, distribute, and sell this software and its 5 | * documentation for any purpose is hereby granted without fee, provided that 6 | * the above copyright notice appear in all copies and that both that 7 | * copyright notice and this permission notice appear in supporting 8 | * documentation. No representations are made about the suitability of this 9 | * software for any purpose. It is provided "as is" without express or 10 | * implied warranty. 11 | */ 12 | 13 | /* The unportable mess that is rand(), random(), drand48() and friends led me 14 | to ask Phil Karlton what the Right Thing to Do was. 15 | He responded with this. It is non-cryptographically secure, reasonably 16 | random (more so than anything that is in any C library), and very fast. 17 | 18 | I don't understand how it works at all, but he says "look at Knuth, 19 | Vol. 2 (original edition), page 26, Algorithm A. In this case n=55, 20 | k=24 and m=2^32." 21 | 22 | So there you have it. 23 | 24 | --------------------------- 25 | Note: xlockmore 4.03a10 uses this very simple RNG: 26 | 27 | if ((seed = seed % 44488 * 48271 - seed / 44488 * 3399) < 0) 28 | seed += 2147483647; 29 | return seed-1; 30 | 31 | of which it says 32 | 33 | ``Dr. Park's algorithm published in the Oct. '88 ACM "Random Number 34 | Generators: Good Ones Are Hard To Find" His version available at 35 | ftp://cs.wm.edu/pub/rngs.tar Present form by many authors.'' 36 | 37 | Karlton says: ``the usual problem with that kind of RNG turns out to 38 | be unexepected short cycles for some word lengths.'' 39 | 40 | Karlton's RNG is faster, since it does three adds and two stores, while the 41 | xlockmore RNG does two multiplies, two divides, three adds, and one store. 42 | 43 | Compiler optimizations make a big difference here: 44 | gcc -O: difference is 1.2x. 45 | gcc -O2: difference is 1.4x. 46 | gcc -O3: difference is 1.5x. 47 | SGI cc -O: difference is 2.4x. 48 | SGI cc -O2: difference is 2.4x. 49 | SGI cc -O3: difference is 5.1x. 50 | Irix 6.2; Indy r5k; SGI cc version 6; gcc version 2.7.2.1. 51 | */ 52 | 53 | 54 | #ifdef HAVE_CONFIG_H 55 | # include "config.h" 56 | #endif 57 | 58 | #ifdef HAVE_UNISTD_H 59 | # include /* for getpid() */ 60 | #endif 61 | #include /* for gettimeofday() */ 62 | 63 | #include "yarandom.h" 64 | # undef ya_rand_init 65 | 66 | 67 | /* The following 'random' numbers are taken from CRC, 18th Edition, page 622. 68 | Each array element was taken from the corresponding line in the table, 69 | except that a[0] was from line 100. 8s and 9s in the table were simply 70 | skipped. The high order digit was taken mod 4. 71 | */ 72 | #define VectorSize 55 73 | static unsigned int a[VectorSize] = { 74 | 035340171546, 010401501101, 022364657325, 024130436022, 002167303062, /* 5 */ 75 | 037570375137, 037210607110, 016272055420, 023011770546, 017143426366, /* 10 */ 76 | 014753657433, 021657231332, 023553406142, 004236526362, 010365611275, /* 14 */ 77 | 007117336710, 011051276551, 002362132524, 001011540233, 012162531646, /* 20 */ 78 | 007056762337, 006631245521, 014164542224, 032633236305, 023342700176, /* 25 */ 79 | 002433062234, 015257225043, 026762051606, 000742573230, 005366042132, /* 30 */ 80 | 012126416411, 000520471171, 000725646277, 020116577576, 025765742604, /* 35 */ 81 | 007633473735, 015674255275, 017555634041, 006503154145, 021576344247, /* 40 */ 82 | 014577627653, 002707523333, 034146376720, 030060227734, 013765414060, /* 45 */ 83 | 036072251540, 007255221037, 024364674123, 006200353166, 010126373326, /* 50 */ 84 | 015664104320, 016401041535, 016215305520, 033115351014, 017411670323 /* 55 */ 85 | }; 86 | 87 | static int i1, i2; 88 | 89 | unsigned int 90 | ya_random (void) 91 | { 92 | register int ret = a[i1] + a[i2]; 93 | a[i1] = ret; 94 | if (++i1 >= VectorSize) i1 = 0; 95 | if (++i2 >= VectorSize) i2 = 0; 96 | return ret; 97 | } 98 | 99 | void 100 | ya_rand_init(unsigned int seed) 101 | { 102 | int i; 103 | if (seed == 0) 104 | { 105 | struct timeval tp; 106 | #ifdef GETTIMEOFDAY_TWO_ARGS 107 | struct timezone tzp; 108 | gettimeofday(&tp, &tzp); 109 | #else 110 | gettimeofday(&tp); 111 | #endif 112 | /* Since the multiplications will have a larger effect on the 113 | upper bits than the lower bits, after every addition in the 114 | seed, perform a bitwise rotate by an odd number, resulting 115 | in a better distribution of randomness throughout the bits. 116 | -- Brian Carlson, 2010. 117 | */ 118 | #define ROT(X,N) (((X)<<(N)) | ((X)>>((sizeof(unsigned int)*8)-(N)))) 119 | seed = (999U * (unsigned int) tp.tv_sec); 120 | seed = ROT (seed, 11); 121 | seed += (1001 * (unsigned int) tp.tv_usec); 122 | seed = ROT (seed, 7); 123 | seed += (1003 * (unsigned int) getpid()); 124 | seed = ROT (seed, 13); 125 | } 126 | 127 | a[0] += seed; 128 | for (i = 1; i < VectorSize; i++) 129 | { 130 | seed = seed*999; 131 | seed = ROT (seed, 9); 132 | seed += a[i-1]*1001; 133 | seed = ROT (seed, 15); 134 | a[i] += seed; 135 | } 136 | 137 | i1 = a[0] % VectorSize; 138 | i2 = (i1 + 24) % VectorSize; 139 | } 140 | -------------------------------------------------------------------------------- /queue.h: -------------------------------------------------------------------------------- 1 | /* $NetBSD: queue.h,v 1.68 2014/11/19 08:10:01 uebayasi Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 1991, 1993 5 | * The Regents of the University of California. All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 3. Neither the name of the University nor the names of its contributors 16 | * may be used to endorse or promote products derived from this software 17 | * without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 | * SUCH DAMAGE. 30 | * 31 | * @(#)queue.h 8.5 (Berkeley) 8/20/94 32 | */ 33 | 34 | #ifndef _QUEUE_H_ 35 | #define _QUEUE_H_ 36 | 37 | /* 38 | * This file defines five types of data structures: singly-linked lists, 39 | * lists, simple queues, tail queues, and circular queues. 40 | * 41 | * A singly-linked list is headed by a single forward pointer. The 42 | * elements are singly linked for minimum space and pointer manipulation 43 | * overhead at the expense of O(n) removal for arbitrary elements. New 44 | * elements can be added to the list after an existing element or at the 45 | * head of the list. Elements being removed from the head of the list 46 | * should use the explicit macro for this purpose for optimum 47 | * efficiency. A singly-linked list may only be traversed in the forward 48 | * direction. Singly-linked lists are ideal for applications with large 49 | * datasets and few or no removals or for implementing a LIFO queue. 50 | * 51 | * A list is headed by a single forward pointer (or an array of forward 52 | * pointers for a hash table header). The elements are doubly linked 53 | * so that an arbitrary element can be removed without a need to 54 | * traverse the list. New elements can be added to the list before 55 | * or after an existing element or at the head of the list. A list 56 | * may only be traversed in the forward direction. 57 | * 58 | * A simple queue is headed by a pair of pointers, one the head of the 59 | * list and the other to the tail of the list. The elements are singly 60 | * linked to save space, so elements can only be removed from the 61 | * head of the list. New elements can be added to the list after 62 | * an existing element, at the head of the list, or at the end of the 63 | * list. A simple queue may only be traversed in the forward direction. 64 | * 65 | * A tail queue is headed by a pair of pointers, one to the head of the 66 | * list and the other to the tail of the list. The elements are doubly 67 | * linked so that an arbitrary element can be removed without a need to 68 | * traverse the list. New elements can be added to the list before or 69 | * after an existing element, at the head of the list, or at the end of 70 | * the list. A tail queue may be traversed in either direction. 71 | * 72 | * A circle queue is headed by a pair of pointers, one to the head of the 73 | * list and the other to the tail of the list. The elements are doubly 74 | * linked so that an arbitrary element can be removed without a need to 75 | * traverse the list. New elements can be added to the list before or after 76 | * an existing element, at the head of the list, or at the end of the list. 77 | * A circle queue may be traversed in either direction, but has a more 78 | * complex end of list detection. 79 | * 80 | * For details on the use of these macros, see the queue(3) manual page. 81 | */ 82 | 83 | /* 84 | * Singly-linked List definitions. 85 | */ 86 | #define SLIST_HEAD(name, type) \ 87 | struct name { \ 88 | struct type *slh_first; /* first element */ \ 89 | } 90 | 91 | #define SLIST_HEAD_INITIALIZER(head) \ 92 | { NULL } 93 | 94 | #define SLIST_ENTRY(type) \ 95 | struct { \ 96 | struct type *sle_next; /* next element */ \ 97 | } 98 | 99 | /* 100 | * Singly-linked List access methods. 101 | */ 102 | #define SLIST_FIRST(head) ((head)->slh_first) 103 | #define SLIST_END(head) NULL 104 | #define SLIST_EMPTY(head) ((head)->slh_first == NULL) 105 | #define SLIST_NEXT(elm, field) ((elm)->field.sle_next) 106 | 107 | #define SLIST_FOREACH(var, head, field) \ 108 | for((var) = (head)->slh_first; \ 109 | (var) != SLIST_END(head); \ 110 | (var) = (var)->field.sle_next) 111 | 112 | #define SLIST_FOREACH_SAFE(var, head, field, tvar) \ 113 | for ((var) = SLIST_FIRST((head)); \ 114 | (var) != SLIST_END(head) && \ 115 | ((tvar) = SLIST_NEXT((var), field), 1); \ 116 | (var) = (tvar)) 117 | 118 | /* 119 | * Singly-linked List functions. 120 | */ 121 | #define SLIST_INIT(head) do { \ 122 | (head)->slh_first = SLIST_END(head); \ 123 | } while (/*CONSTCOND*/0) 124 | 125 | #define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ 126 | (elm)->field.sle_next = (slistelm)->field.sle_next; \ 127 | (slistelm)->field.sle_next = (elm); \ 128 | } while (/*CONSTCOND*/0) 129 | 130 | #define SLIST_INSERT_HEAD(head, elm, field) do { \ 131 | (elm)->field.sle_next = (head)->slh_first; \ 132 | (head)->slh_first = (elm); \ 133 | } while (/*CONSTCOND*/0) 134 | 135 | #define SLIST_REMOVE_AFTER(slistelm, field) do { \ 136 | (slistelm)->field.sle_next = \ 137 | SLIST_NEXT(SLIST_NEXT((slistelm), field), field); \ 138 | } while (/*CONSTCOND*/0) 139 | 140 | #define SLIST_REMOVE_HEAD(head, field) do { \ 141 | (head)->slh_first = (head)->slh_first->field.sle_next; \ 142 | } while (/*CONSTCOND*/0) 143 | 144 | #define SLIST_REMOVE(head, elm, type, field) do { \ 145 | if ((head)->slh_first == (elm)) { \ 146 | SLIST_REMOVE_HEAD((head), field); \ 147 | } \ 148 | else { \ 149 | struct type *curelm = (head)->slh_first; \ 150 | while(curelm->field.sle_next != (elm)) \ 151 | curelm = curelm->field.sle_next; \ 152 | curelm->field.sle_next = \ 153 | curelm->field.sle_next->field.sle_next; \ 154 | } \ 155 | } while (/*CONSTCOND*/0) 156 | 157 | 158 | /* 159 | * List definitions. 160 | */ 161 | #define LIST_HEAD(name, type) \ 162 | struct name { \ 163 | struct type *lh_first; /* first element */ \ 164 | } 165 | 166 | #define LIST_HEAD_INITIALIZER(head) \ 167 | { NULL } 168 | 169 | #define LIST_ENTRY(type) \ 170 | struct { \ 171 | struct type *le_next; /* next element */ \ 172 | struct type **le_prev; /* address of previous next element */ \ 173 | } 174 | 175 | /* 176 | * List access methods. 177 | */ 178 | #define LIST_FIRST(head) ((head)->lh_first) 179 | #define LIST_END(head) NULL 180 | #define LIST_EMPTY(head) ((head)->lh_first == LIST_END(head)) 181 | #define LIST_NEXT(elm, field) ((elm)->field.le_next) 182 | 183 | #define LIST_FOREACH(var, head, field) \ 184 | for ((var) = ((head)->lh_first); \ 185 | (var) != LIST_END(head); \ 186 | (var) = ((var)->field.le_next)) 187 | 188 | #define LIST_FOREACH_SAFE(var, head, field, tvar) \ 189 | for ((var) = LIST_FIRST((head)); \ 190 | (var) != LIST_END(head) && \ 191 | ((tvar) = LIST_NEXT((var), field), 1); \ 192 | (var) = (tvar)) 193 | 194 | #define LIST_MOVE(head1, head2) do { \ 195 | LIST_INIT((head2)); \ 196 | if (!LIST_EMPTY((head1))) { \ 197 | (head2)->lh_first = (head1)->lh_first; \ 198 | LIST_INIT((head1)); \ 199 | } \ 200 | } while (/*CONSTCOND*/0) 201 | 202 | /* 203 | * List functions. 204 | */ 205 | #if defined(QUEUEDEBUG) 206 | #define QUEUEDEBUG_LIST_INSERT_HEAD(head, elm, field) \ 207 | if ((head)->lh_first && \ 208 | (head)->lh_first->field.le_prev != &(head)->lh_first) \ 209 | QUEUEDEBUG_ABORT("LIST_INSERT_HEAD %p %s:%d", (head), \ 210 | __FILE__, __LINE__); 211 | #define QUEUEDEBUG_LIST_OP(elm, field) \ 212 | if ((elm)->field.le_next && \ 213 | (elm)->field.le_next->field.le_prev != \ 214 | &(elm)->field.le_next) \ 215 | QUEUEDEBUG_ABORT("LIST_* forw %p %s:%d", (elm), \ 216 | __FILE__, __LINE__); \ 217 | if (*(elm)->field.le_prev != (elm)) \ 218 | QUEUEDEBUG_ABORT("LIST_* back %p %s:%d", (elm), \ 219 | __FILE__, __LINE__); 220 | #define QUEUEDEBUG_LIST_POSTREMOVE(elm, field) \ 221 | (elm)->field.le_next = (void *)1L; \ 222 | (elm)->field.le_prev = (void *)1L; 223 | #else 224 | #define QUEUEDEBUG_LIST_INSERT_HEAD(head, elm, field) 225 | #define QUEUEDEBUG_LIST_OP(elm, field) 226 | #define QUEUEDEBUG_LIST_POSTREMOVE(elm, field) 227 | #endif 228 | 229 | #define LIST_INIT(head) do { \ 230 | (head)->lh_first = LIST_END(head); \ 231 | } while (/*CONSTCOND*/0) 232 | 233 | #define LIST_INSERT_AFTER(listelm, elm, field) do { \ 234 | QUEUEDEBUG_LIST_OP((listelm), field) \ 235 | if (((elm)->field.le_next = (listelm)->field.le_next) != \ 236 | LIST_END(head)) \ 237 | (listelm)->field.le_next->field.le_prev = \ 238 | &(elm)->field.le_next; \ 239 | (listelm)->field.le_next = (elm); \ 240 | (elm)->field.le_prev = &(listelm)->field.le_next; \ 241 | } while (/*CONSTCOND*/0) 242 | 243 | #define LIST_INSERT_BEFORE(listelm, elm, field) do { \ 244 | QUEUEDEBUG_LIST_OP((listelm), field) \ 245 | (elm)->field.le_prev = (listelm)->field.le_prev; \ 246 | (elm)->field.le_next = (listelm); \ 247 | *(listelm)->field.le_prev = (elm); \ 248 | (listelm)->field.le_prev = &(elm)->field.le_next; \ 249 | } while (/*CONSTCOND*/0) 250 | 251 | #define LIST_INSERT_HEAD(head, elm, field) do { \ 252 | QUEUEDEBUG_LIST_INSERT_HEAD((head), (elm), field) \ 253 | if (((elm)->field.le_next = (head)->lh_first) != LIST_END(head))\ 254 | (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ 255 | (head)->lh_first = (elm); \ 256 | (elm)->field.le_prev = &(head)->lh_first; \ 257 | } while (/*CONSTCOND*/0) 258 | 259 | #define LIST_REMOVE(elm, field) do { \ 260 | QUEUEDEBUG_LIST_OP((elm), field) \ 261 | if ((elm)->field.le_next != NULL) \ 262 | (elm)->field.le_next->field.le_prev = \ 263 | (elm)->field.le_prev; \ 264 | *(elm)->field.le_prev = (elm)->field.le_next; \ 265 | QUEUEDEBUG_LIST_POSTREMOVE((elm), field) \ 266 | } while (/*CONSTCOND*/0) 267 | 268 | #define LIST_REPLACE(elm, elm2, field) do { \ 269 | if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \ 270 | (elm2)->field.le_next->field.le_prev = \ 271 | &(elm2)->field.le_next; \ 272 | (elm2)->field.le_prev = (elm)->field.le_prev; \ 273 | *(elm2)->field.le_prev = (elm2); \ 274 | QUEUEDEBUG_LIST_POSTREMOVE((elm), field) \ 275 | } while (/*CONSTCOND*/0) 276 | 277 | /* 278 | * Simple queue definitions. 279 | */ 280 | #define SIMPLEQ_HEAD(name, type) \ 281 | struct name { \ 282 | struct type *sqh_first; /* first element */ \ 283 | struct type **sqh_last; /* addr of last next element */ \ 284 | } 285 | 286 | #define SIMPLEQ_HEAD_INITIALIZER(head) \ 287 | { NULL, &(head).sqh_first } 288 | 289 | #define SIMPLEQ_ENTRY(type) \ 290 | struct { \ 291 | struct type *sqe_next; /* next element */ \ 292 | } 293 | 294 | /* 295 | * Simple queue access methods. 296 | */ 297 | #define SIMPLEQ_FIRST(head) ((head)->sqh_first) 298 | #define SIMPLEQ_END(head) NULL 299 | #define SIMPLEQ_EMPTY(head) ((head)->sqh_first == SIMPLEQ_END(head)) 300 | #define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) 301 | 302 | #define SIMPLEQ_FOREACH(var, head, field) \ 303 | for ((var) = ((head)->sqh_first); \ 304 | (var) != SIMPLEQ_END(head); \ 305 | (var) = ((var)->field.sqe_next)) 306 | 307 | #define SIMPLEQ_FOREACH_SAFE(var, head, field, next) \ 308 | for ((var) = ((head)->sqh_first); \ 309 | (var) != SIMPLEQ_END(head) && \ 310 | ((next = ((var)->field.sqe_next)), 1); \ 311 | (var) = (next)) 312 | 313 | /* 314 | * Simple queue functions. 315 | */ 316 | #define SIMPLEQ_INIT(head) do { \ 317 | (head)->sqh_first = NULL; \ 318 | (head)->sqh_last = &(head)->sqh_first; \ 319 | } while (/*CONSTCOND*/0) 320 | 321 | #define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \ 322 | if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ 323 | (head)->sqh_last = &(elm)->field.sqe_next; \ 324 | (head)->sqh_first = (elm); \ 325 | } while (/*CONSTCOND*/0) 326 | 327 | #define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \ 328 | (elm)->field.sqe_next = NULL; \ 329 | *(head)->sqh_last = (elm); \ 330 | (head)->sqh_last = &(elm)->field.sqe_next; \ 331 | } while (/*CONSTCOND*/0) 332 | 333 | #define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ 334 | if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\ 335 | (head)->sqh_last = &(elm)->field.sqe_next; \ 336 | (listelm)->field.sqe_next = (elm); \ 337 | } while (/*CONSTCOND*/0) 338 | 339 | #define SIMPLEQ_REMOVE_HEAD(head, field) do { \ 340 | if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \ 341 | (head)->sqh_last = &(head)->sqh_first; \ 342 | } while (/*CONSTCOND*/0) 343 | 344 | #define SIMPLEQ_REMOVE_AFTER(head, elm, field) do { \ 345 | if (((elm)->field.sqe_next = (elm)->field.sqe_next->field.sqe_next) \ 346 | == NULL) \ 347 | (head)->sqh_last = &(elm)->field.sqe_next; \ 348 | } while (/*CONSTCOND*/0) 349 | 350 | #define SIMPLEQ_REMOVE(head, elm, type, field) do { \ 351 | if ((head)->sqh_first == (elm)) { \ 352 | SIMPLEQ_REMOVE_HEAD((head), field); \ 353 | } else { \ 354 | struct type *curelm = (head)->sqh_first; \ 355 | while (curelm->field.sqe_next != (elm)) \ 356 | curelm = curelm->field.sqe_next; \ 357 | if ((curelm->field.sqe_next = \ 358 | curelm->field.sqe_next->field.sqe_next) == NULL) \ 359 | (head)->sqh_last = &(curelm)->field.sqe_next; \ 360 | } \ 361 | } while (/*CONSTCOND*/0) 362 | 363 | #define SIMPLEQ_CONCAT(head1, head2) do { \ 364 | if (!SIMPLEQ_EMPTY((head2))) { \ 365 | *(head1)->sqh_last = (head2)->sqh_first; \ 366 | (head1)->sqh_last = (head2)->sqh_last; \ 367 | SIMPLEQ_INIT((head2)); \ 368 | } \ 369 | } while (/*CONSTCOND*/0) 370 | 371 | #define SIMPLEQ_LAST(head, type, field) \ 372 | (SIMPLEQ_EMPTY((head)) ? \ 373 | NULL : \ 374 | ((struct type *)(void *) \ 375 | ((char *)((head)->sqh_last) - offsetof(struct type, field)))) 376 | 377 | /* 378 | * Tail queue definitions. 379 | */ 380 | #define _TAILQ_HEAD(name, type, qual) \ 381 | struct name { \ 382 | qual type *tqh_first; /* first element */ \ 383 | qual type *qual *tqh_last; /* addr of last next element */ \ 384 | } 385 | #define TAILQ_HEAD(name, type) _TAILQ_HEAD(name, struct type,) 386 | 387 | #define TAILQ_HEAD_INITIALIZER(head) \ 388 | { TAILQ_END(head), &(head).tqh_first } 389 | 390 | #define _TAILQ_ENTRY(type, qual) \ 391 | struct { \ 392 | qual type *tqe_next; /* next element */ \ 393 | qual type *qual *tqe_prev; /* address of previous next element */\ 394 | } 395 | #define TAILQ_ENTRY(type) _TAILQ_ENTRY(struct type,) 396 | 397 | /* 398 | * Tail queue access methods. 399 | */ 400 | #define TAILQ_FIRST(head) ((head)->tqh_first) 401 | #define TAILQ_END(head) (NULL) 402 | #define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) 403 | #define TAILQ_LAST(head, headname) \ 404 | (*(((struct headname *)((head)->tqh_last))->tqh_last)) 405 | #define TAILQ_PREV(elm, headname, field) \ 406 | (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) 407 | #define TAILQ_EMPTY(head) (TAILQ_FIRST(head) == TAILQ_END(head)) 408 | 409 | 410 | #define TAILQ_FOREACH(var, head, field) \ 411 | for ((var) = ((head)->tqh_first); \ 412 | (var) != TAILQ_END(head); \ 413 | (var) = ((var)->field.tqe_next)) 414 | 415 | #define TAILQ_FOREACH_SAFE(var, head, field, next) \ 416 | for ((var) = ((head)->tqh_first); \ 417 | (var) != TAILQ_END(head) && \ 418 | ((next) = TAILQ_NEXT(var, field), 1); (var) = (next)) 419 | 420 | #define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ 421 | for ((var) = (*(((struct headname *)((head)->tqh_last))->tqh_last));\ 422 | (var) != TAILQ_END(head); \ 423 | (var) = (*(((struct headname *)((var)->field.tqe_prev))->tqh_last))) 424 | 425 | #define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, prev) \ 426 | for ((var) = TAILQ_LAST((head), headname); \ 427 | (var) != TAILQ_END(head) && \ 428 | ((prev) = TAILQ_PREV((var), headname, field), 1); (var) = (prev)) 429 | 430 | /* 431 | * Tail queue functions. 432 | */ 433 | #if defined(QUEUEDEBUG) 434 | #define QUEUEDEBUG_TAILQ_INSERT_HEAD(head, elm, field) \ 435 | if ((head)->tqh_first && \ 436 | (head)->tqh_first->field.tqe_prev != &(head)->tqh_first) \ 437 | QUEUEDEBUG_ABORT("TAILQ_INSERT_HEAD %p %s:%d", (head), \ 438 | __FILE__, __LINE__); 439 | #define QUEUEDEBUG_TAILQ_INSERT_TAIL(head, elm, field) \ 440 | if (*(head)->tqh_last != NULL) \ 441 | QUEUEDEBUG_ABORT("TAILQ_INSERT_TAIL %p %s:%d", (head), \ 442 | __FILE__, __LINE__); 443 | #define QUEUEDEBUG_TAILQ_OP(elm, field) \ 444 | if ((elm)->field.tqe_next && \ 445 | (elm)->field.tqe_next->field.tqe_prev != \ 446 | &(elm)->field.tqe_next) \ 447 | QUEUEDEBUG_ABORT("TAILQ_* forw %p %s:%d", (elm), \ 448 | __FILE__, __LINE__); \ 449 | if (*(elm)->field.tqe_prev != (elm)) \ 450 | QUEUEDEBUG_ABORT("TAILQ_* back %p %s:%d", (elm), \ 451 | __FILE__, __LINE__); 452 | #define QUEUEDEBUG_TAILQ_PREREMOVE(head, elm, field) \ 453 | if ((elm)->field.tqe_next == NULL && \ 454 | (head)->tqh_last != &(elm)->field.tqe_next) \ 455 | QUEUEDEBUG_ABORT("TAILQ_PREREMOVE head %p elm %p %s:%d",\ 456 | (head), (elm), __FILE__, __LINE__); 457 | #define QUEUEDEBUG_TAILQ_POSTREMOVE(elm, field) \ 458 | (elm)->field.tqe_next = (void *)1L; \ 459 | (elm)->field.tqe_prev = (void *)1L; 460 | #else 461 | #define QUEUEDEBUG_TAILQ_INSERT_HEAD(head, elm, field) 462 | #define QUEUEDEBUG_TAILQ_INSERT_TAIL(head, elm, field) 463 | #define QUEUEDEBUG_TAILQ_OP(elm, field) 464 | #define QUEUEDEBUG_TAILQ_PREREMOVE(head, elm, field) 465 | #define QUEUEDEBUG_TAILQ_POSTREMOVE(elm, field) 466 | #endif 467 | 468 | #define TAILQ_INIT(head) do { \ 469 | (head)->tqh_first = TAILQ_END(head); \ 470 | (head)->tqh_last = &(head)->tqh_first; \ 471 | } while (/*CONSTCOND*/0) 472 | 473 | #define TAILQ_INSERT_HEAD(head, elm, field) do { \ 474 | QUEUEDEBUG_TAILQ_INSERT_HEAD((head), (elm), field) \ 475 | if (((elm)->field.tqe_next = (head)->tqh_first) != TAILQ_END(head))\ 476 | (head)->tqh_first->field.tqe_prev = \ 477 | &(elm)->field.tqe_next; \ 478 | else \ 479 | (head)->tqh_last = &(elm)->field.tqe_next; \ 480 | (head)->tqh_first = (elm); \ 481 | (elm)->field.tqe_prev = &(head)->tqh_first; \ 482 | } while (/*CONSTCOND*/0) 483 | 484 | #define TAILQ_INSERT_TAIL(head, elm, field) do { \ 485 | QUEUEDEBUG_TAILQ_INSERT_TAIL((head), (elm), field) \ 486 | (elm)->field.tqe_next = TAILQ_END(head); \ 487 | (elm)->field.tqe_prev = (head)->tqh_last; \ 488 | *(head)->tqh_last = (elm); \ 489 | (head)->tqh_last = &(elm)->field.tqe_next; \ 490 | } while (/*CONSTCOND*/0) 491 | 492 | #define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ 493 | QUEUEDEBUG_TAILQ_OP((listelm), field) \ 494 | if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != \ 495 | TAILQ_END(head)) \ 496 | (elm)->field.tqe_next->field.tqe_prev = \ 497 | &(elm)->field.tqe_next; \ 498 | else \ 499 | (head)->tqh_last = &(elm)->field.tqe_next; \ 500 | (listelm)->field.tqe_next = (elm); \ 501 | (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ 502 | } while (/*CONSTCOND*/0) 503 | 504 | #define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ 505 | QUEUEDEBUG_TAILQ_OP((listelm), field) \ 506 | (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ 507 | (elm)->field.tqe_next = (listelm); \ 508 | *(listelm)->field.tqe_prev = (elm); \ 509 | (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ 510 | } while (/*CONSTCOND*/0) 511 | 512 | #define TAILQ_REMOVE(head, elm, field) do { \ 513 | QUEUEDEBUG_TAILQ_PREREMOVE((head), (elm), field) \ 514 | QUEUEDEBUG_TAILQ_OP((elm), field) \ 515 | if (((elm)->field.tqe_next) != TAILQ_END(head)) \ 516 | (elm)->field.tqe_next->field.tqe_prev = \ 517 | (elm)->field.tqe_prev; \ 518 | else \ 519 | (head)->tqh_last = (elm)->field.tqe_prev; \ 520 | *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ 521 | QUEUEDEBUG_TAILQ_POSTREMOVE((elm), field); \ 522 | } while (/*CONSTCOND*/0) 523 | 524 | #define TAILQ_REPLACE(head, elm, elm2, field) do { \ 525 | if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != \ 526 | TAILQ_END(head)) \ 527 | (elm2)->field.tqe_next->field.tqe_prev = \ 528 | &(elm2)->field.tqe_next; \ 529 | else \ 530 | (head)->tqh_last = &(elm2)->field.tqe_next; \ 531 | (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \ 532 | *(elm2)->field.tqe_prev = (elm2); \ 533 | QUEUEDEBUG_TAILQ_POSTREMOVE((elm), field); \ 534 | } while (/*CONSTCOND*/0) 535 | 536 | #define TAILQ_CONCAT(head1, head2, field) do { \ 537 | if (!TAILQ_EMPTY(head2)) { \ 538 | *(head1)->tqh_last = (head2)->tqh_first; \ 539 | (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ 540 | (head1)->tqh_last = (head2)->tqh_last; \ 541 | TAILQ_INIT((head2)); \ 542 | } \ 543 | } while (/*CONSTCOND*/0) 544 | 545 | /* 546 | * Singly-linked Tail queue declarations. 547 | */ 548 | #define STAILQ_HEAD(name, type) \ 549 | struct name { \ 550 | struct type *stqh_first; /* first element */ \ 551 | struct type **stqh_last; /* addr of last next element */ \ 552 | } 553 | 554 | #define STAILQ_HEAD_INITIALIZER(head) \ 555 | { NULL, &(head).stqh_first } 556 | 557 | #define STAILQ_ENTRY(type) \ 558 | struct { \ 559 | struct type *stqe_next; /* next element */ \ 560 | } 561 | 562 | /* 563 | * Singly-linked Tail queue access methods. 564 | */ 565 | #define STAILQ_FIRST(head) ((head)->stqh_first) 566 | #define STAILQ_END(head) NULL 567 | #define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next) 568 | #define STAILQ_EMPTY(head) (STAILQ_FIRST(head) == STAILQ_END(head)) 569 | 570 | /* 571 | * Singly-linked Tail queue functions. 572 | */ 573 | #define STAILQ_INIT(head) do { \ 574 | (head)->stqh_first = NULL; \ 575 | (head)->stqh_last = &(head)->stqh_first; \ 576 | } while (/*CONSTCOND*/0) 577 | 578 | #define STAILQ_INSERT_HEAD(head, elm, field) do { \ 579 | if (((elm)->field.stqe_next = (head)->stqh_first) == NULL) \ 580 | (head)->stqh_last = &(elm)->field.stqe_next; \ 581 | (head)->stqh_first = (elm); \ 582 | } while (/*CONSTCOND*/0) 583 | 584 | #define STAILQ_INSERT_TAIL(head, elm, field) do { \ 585 | (elm)->field.stqe_next = NULL; \ 586 | *(head)->stqh_last = (elm); \ 587 | (head)->stqh_last = &(elm)->field.stqe_next; \ 588 | } while (/*CONSTCOND*/0) 589 | 590 | #define STAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ 591 | if (((elm)->field.stqe_next = (listelm)->field.stqe_next) == NULL)\ 592 | (head)->stqh_last = &(elm)->field.stqe_next; \ 593 | (listelm)->field.stqe_next = (elm); \ 594 | } while (/*CONSTCOND*/0) 595 | 596 | #define STAILQ_REMOVE_HEAD(head, field) do { \ 597 | if (((head)->stqh_first = (head)->stqh_first->field.stqe_next) == NULL) \ 598 | (head)->stqh_last = &(head)->stqh_first; \ 599 | } while (/*CONSTCOND*/0) 600 | 601 | #define STAILQ_REMOVE(head, elm, type, field) do { \ 602 | if ((head)->stqh_first == (elm)) { \ 603 | STAILQ_REMOVE_HEAD((head), field); \ 604 | } else { \ 605 | struct type *curelm = (head)->stqh_first; \ 606 | while (curelm->field.stqe_next != (elm)) \ 607 | curelm = curelm->field.stqe_next; \ 608 | if ((curelm->field.stqe_next = \ 609 | curelm->field.stqe_next->field.stqe_next) == NULL) \ 610 | (head)->stqh_last = &(curelm)->field.stqe_next; \ 611 | } \ 612 | } while (/*CONSTCOND*/0) 613 | 614 | #define STAILQ_FOREACH(var, head, field) \ 615 | for ((var) = ((head)->stqh_first); \ 616 | (var); \ 617 | (var) = ((var)->field.stqe_next)) 618 | 619 | #define STAILQ_FOREACH_SAFE(var, head, field, tvar) \ 620 | for ((var) = STAILQ_FIRST((head)); \ 621 | (var) && ((tvar) = STAILQ_NEXT((var), field), 1); \ 622 | (var) = (tvar)) 623 | 624 | #define STAILQ_CONCAT(head1, head2) do { \ 625 | if (!STAILQ_EMPTY((head2))) { \ 626 | *(head1)->stqh_last = (head2)->stqh_first; \ 627 | (head1)->stqh_last = (head2)->stqh_last; \ 628 | STAILQ_INIT((head2)); \ 629 | } \ 630 | } while (/*CONSTCOND*/0) 631 | 632 | #define STAILQ_LAST(head, type, field) \ 633 | (STAILQ_EMPTY((head)) ? \ 634 | NULL : \ 635 | ((struct type *)(void *) \ 636 | ((char *)((head)->stqh_last) - offsetof(struct type, field)))) 637 | 638 | #endif /* !_QUEUE_H_ */ 639 | -------------------------------------------------------------------------------- /idlehack.c: -------------------------------------------------------------------------------- 1 | /* IdleHack 2 | * - Hacked to inhibit swayidle 3 | */ 4 | 5 | /* xscreensaver-systemd, Copyright (c) 2019-2021 6 | * Martin Lucina and Jamie Zawinski 7 | * 8 | * ISC License 9 | * 10 | * Permission to use, copy, modify, and/or distribute this software 11 | * for any purpose with or without fee is hereby granted, provided 12 | * that the above copyright notice and this permission notice appear 13 | * in all copies. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 16 | * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 17 | * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 18 | * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR 19 | * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 20 | * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, 21 | * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 22 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 23 | * 24 | * 25 | * This utility provides systemd integration for XScreenSaver. 26 | * 27 | * - When another process on the system makes asks for the screen saver 28 | * to be inhibited (e.g. because a video is playing) this program 29 | * periodically runs "swayidle-inhibit -deactivate" to keep the 30 | * display un-blanked. It does this until the other program asks for 31 | * it to stop. 32 | * 33 | * For this to work at all, you must prevent Gnome and KDE from usurping 34 | * the "org.freedesktop.ScreenSaver" messages, or else this program can't 35 | * receive them. The "xscreensaver" man page contains the (complicated) 36 | * installation instructions. 37 | * 38 | * Background: 39 | * 40 | * For decades, the traditional way for a video player to temporarily 41 | * inhibit the screen saver was to have a heartbeat command that ran 42 | * "xscreensaver-command -deactivate" once a minute while the video was 43 | * playing, and ceased when the video was paused or stopped. The reason 44 | * to do it as a heartbeat rather than a toggle is so that the player 45 | * fails SAFE -- if the player exits abnormally, the heart stops beating, 46 | * and screen saving and locking resumes. 47 | * 48 | * These days, the popular apps do this by using systemd. The design of 49 | * the systemd method easily and trivially allows an app to inhibit the 50 | * screen saver, crash, and then never un-inhibit it, so now your screen 51 | * will never blank again. 52 | * 53 | * Furthermore, since the systemd method uses cookies to ensure that only 54 | * the app that sent "inhibit" can send the matching "uninhibit", simply 55 | * re-launching the crashed video player does not fix the problem. 56 | * 57 | * "Did IQs just drop sharply while I was away?" -- Ellen Ripley 58 | * 59 | * We can sometimes detect that the inhibiting app has exited abnormally 60 | * by using "tracking peers" but I'm not sure how reliable that is. 61 | * 62 | * Furthermore, we can't listen for these "inhibit blanking" requests 63 | * if some other program is already listening for them -- which Gnome and 64 | * KDE do by default, even if their screen savers are otherwise disabled. 65 | * That makes it far more complicated for the user to install XScreenSaver 66 | * in such a way that "xscreensaver-systemd" can even launch at all. 67 | * 68 | * To recap: because the existing video players decided to delete the 69 | * single line of code that they already had -- the heartbeat call to 70 | * "xscreensaver-command -deactivate" -- we had to respond by adding a 71 | * THOUSAND LINES of complicated code that talks to a server that may 72 | * not be running, and that may not allow us to connect, and that may 73 | * not work properly anyway, and that makes installation hellaciously 74 | * difficult and confusing for the end user. 75 | * 76 | * This is what is laughingly referred to as "progress". 77 | * 78 | * So here's what we're dealing with now, with the various apps that 79 | * you might use to play video on Linux at the end of 2020: 80 | * 81 | * 82 | ***************************************************************************** 83 | * 84 | * Firefox (version 78.5) 85 | * 86 | * When playing media, Firefox will send "inhibit" to one of these 87 | * targets: "org.freedesktop.ScreenSaver" or "org.gnome.SessionManager". 88 | * 89 | * However, Firefox decides which, if any, of those to use at launch time, 90 | * and does not revisit that decision. So if xscreensaver-systemd has not 91 | * been launched before Firefox, it won't work. Fortunately, in most use 92 | * cases, xscreensaver will have been launched earlier in the startup 93 | * sequence than the web browser. 94 | * 95 | * If you close the tab or exit while playing, Firefox sends "uninhibit". 96 | * 97 | * Critical Firefox Bug: 98 | * 99 | * If Firefox crashes or is killed while playing, it never sends 100 | * "uninhibit", leaving the screen saver permanently inhibited. Once 101 | * that happens, the only way to un-fuck things is to kill and restart 102 | * the "xscreensaver-systemd" program. 103 | * 104 | * Annoying Firefox Bug: 105 | * 106 | * Firefox sends an "inhibit" message when it is merely playing audio. 107 | * That's horrible. Playing audio should prevent your machine from going 108 | * to sleep, but it should NOT prevent your screen from blanking or 109 | * locking. 110 | * 111 | * However at least it sends it with the reason "audio-playing" instead 112 | * of "video-playing", meaning we can (and do) special-case Firefox and 113 | * ignore that one. 114 | * 115 | * 116 | ***************************************************************************** 117 | * 118 | * Chrome (version 87) 119 | * 120 | * Sends "inhibit" to "org.freedesktop.ScreenSaver" (though it uses a 121 | * a different object path than Firefox does). Unlike Firefox, Chrome 122 | * does not send an "inhibit" message when only audio is playing. 123 | * 124 | * Critical Chrome Bug: 125 | * 126 | * If Chrome crashes or is killed while playing, it never sends 127 | * "uninhibit", leaving the screen saver permanently inhibited. 128 | * 129 | * 130 | ***************************************************************************** 131 | * 132 | * Chromium (version 78, Raspbian 10.4) 133 | * 134 | * Does not use "org.freedesktop.ScreenSaver" or "xdg-screensaver". 135 | * It appears to make no attempt to inhibit the screen saver while 136 | * video is playing. 137 | * 138 | * 139 | ***************************************************************************** 140 | * 141 | * Chromium (version 84.0.4147.141, Raspbian 10.6) 142 | * 143 | * Sends "inhibit" to "org.freedesktop.ScreenSaver" (though it uses a 144 | * a different object path than Firefox does). Unlike Firefox, Chrome 145 | * does not send an "inhibit" message when only audio is playing. 146 | * 147 | * If you close the tab or exit while playing, Chromium sends "uninhibit". 148 | * 149 | * Critical Chromium Bug: 150 | * 151 | * If Chromium crashes or is killed while playing, it never sends 152 | * "uninhibit", leaving the screen saver permanently inhibited. 153 | * 154 | * Annoying Chromium Bug: 155 | * 156 | * Like Firefox, Chromium sends an "inhibit" message when it is merely 157 | * playing audio. Unlike Firefox, it sends exactly the same "reason" 158 | * string as it does when playing video, so we can't tell them apart. 159 | * 160 | * 161 | ***************************************************************************** 162 | * 163 | * MPV (version 0.29.1) 164 | * 165 | * While playing, it runs "xdg-screensaver reset" every 10 seconds as a 166 | * heartbeat. That program is a super-complicated shell script that will 167 | * eventually run "xscreensaver-command -reset". So MPV talks to the 168 | * xscreensaver daemon directly rather than going through systemd. 169 | * That's fine. 170 | * 171 | * On Debian 10.4 and 10.6, MPV does not have a dependency on the 172 | * "xdg-utils" package, so "xdg-screensaver" might not be installed. 173 | * Oddly, Chromium *does* have a dependency on "xdg-utils", even though 174 | * Chromium doesn't run "xdg-screensaver". 175 | * 176 | * The source code suggests that MPlayer and MPV call XResetScreenSaver() 177 | * as well, but only affects the X11 server's built-in screen saver, not 178 | * a userspace screen locker like xscreensaver. 179 | * 180 | * They also call XScreenSaverSuspend() which is part of the MIT 181 | * SCREEN-SAVER server extension. XScreenSaver does make use of that 182 | * extension because it is worse than useless. See the commentary at 183 | * the top of xscreensaver.c for details. 184 | * 185 | * Annoying MPV Bug: 186 | * 187 | * Like Firefox and Chromium, MPV inhibits screen blanking when only 188 | * audio is playing. 189 | * 190 | * 191 | ***************************************************************************** 192 | * 193 | * MPlayer (version mplayer-gui 2:1.3.0) 194 | * 195 | * I can't get this thing to play video at all. It only plays the audio 196 | * of MP4 files, so I can't guess what it might or might not do with video. 197 | * It appears to make no attempt to inhibit the screen saver. 198 | * 199 | * 200 | ***************************************************************************** 201 | * 202 | * VLC (version 3.0.11-0+deb10u1+rpt3) 203 | * 204 | * VLC sends "inhibit" to "org.freedesktop.ScreenSaver" when playing 205 | * video. It does not send "inhibit" when playing audio only, and it 206 | * sends "uninhibit" under all the right circumstances. 207 | * 208 | * NOTE: that's what I saw when I tested it on Raspbian 10.6. However, 209 | * the version that came with Raspbian 10.4 -- which also called itself 210 | * "VLC 3.0.11" -- did not send "uninhibit" when using the window 211 | * manager's "close" button! Or when killed with "kill". 212 | * 213 | * NOTE ALSO: The VLC source code suggests that under some circumstances 214 | * it might be talking to these instead: "org.freedesktop.ScreenSaver", 215 | * "org.freedesktop.PowerManagement.Inhibit", "org.mate.SessionManager", 216 | * and/or "org.gnome.SessionManager". It also contains code to run 217 | * "xdg-screensaver reset" as a heartbeat. I can't tell how it decides 218 | * which system to use. I have never seen it run "xdg-screensaver". 219 | * 220 | * 221 | ***************************************************************************** 222 | * 223 | * Zoom 224 | * 225 | * I'm told that the proprietary Zoom executable for Linux sends "inhibit" 226 | * to "org.freedesktop.ScreenSaver", but I don't have any further details. 227 | * 228 | * 229 | ***************************************************************************** 230 | * 231 | * Steam: 232 | * 233 | * Inhibits as "My SDL application" (ooh, "Baby's First Hello World", 234 | * nice! You get a gold star sticker) and then 30 seconds later, 235 | * uninhibits and immediately re-inhibits, forever. This works, but 236 | * is dumb. 237 | * 238 | ***************************************************************************** 239 | * 240 | * 241 | * TO DO: 242 | * 243 | * - What precisely does the standalone Zoom executable do on Linux? 244 | * There doesn't seem to be a Raspbian build, so I can't test it. 245 | * 246 | * - xscreensaver_method_uninhibit() does not actually send a reply, are 247 | * we doing the right thing when registering it? 248 | * 249 | * - Currently this code is only listening to "org.freedesktop.ScreenSaver". 250 | * Perhaps it should listen to "org.mate.SessionManager" and 251 | * "org.gnome.SessionManager"? Where are those documented? 252 | * 253 | * - Do we need to call sd_bus_release_name() explicitly on exit? 254 | * 255 | * - Run under valgrind to check for any memory leaks. 256 | * 257 | * - Apparently the two different desktops have managed to come up with 258 | * *three* different ways for dbus clients to ask the question, "is the 259 | * screen currently blanked?" We should probably also respond to these: 260 | * 261 | * qdbus org.freedesktop.ScreenSaver /ScreenSaver org.freedesktop.ScreenSaver.GetActive 262 | * qdbus org.kde.screensaver /ScreenSaver org.freedesktop.ScreenSaver.GetActive 263 | * qdbus org.gnome.ScreenSaver /ScreenSaver org.gnome.ScreenSaver.GetActive 264 | * 265 | * 266 | * 267 | * TESTING: 268 | * 269 | * To call the D-BUS methods manually, you can use "busctl": 270 | * 271 | * busctl --user call org.freedesktop.ScreenSaver \ 272 | * /ScreenSaver org.freedesktop.ScreenSaver \ 273 | * Inhibit ss test-application test-reason 274 | * 275 | * This will hand out a cookie, which you can pass back to UnInhibit: 276 | * 277 | * u 1792821391 278 | * 279 | * busctl --user call org.freedesktop.ScreenSaver \ 280 | * /ScreenSaver org.freedesktop.ScreenSaver \ 281 | * UnInhibit u 1792821391 282 | * 283 | * https://github.com/mato/xscreensaver-systemd 284 | */ 285 | 286 | #ifdef HAVE_CONFIG_H 287 | # include "config.h" 288 | #endif 289 | 290 | #define _GNU_SOURCE 291 | #include 292 | #include 293 | #include 294 | #include 295 | #include 296 | #include 297 | #include 298 | #include 299 | #include 300 | #include 301 | #include 302 | #include 303 | #include 304 | 305 | #if defined (HAVE_LIBSYSTEMD) 306 | # include 307 | #elif defined (HAVE_LIBELOGIND) 308 | # include 309 | #else 310 | # error Neither HAVE_LIBSYSTEMD nor HAVE_LIBELOGIND is defined. 311 | #endif 312 | 313 | #include "version.h" 314 | #include "blurb.h" 315 | #include "yarandom.h" 316 | #include "queue.h" 317 | 318 | static char *screensaver_version; 319 | 320 | #define DBUS_CLIENT_NAME "org.jwz.XScreenSaver" 321 | #define DBUS_SD_SERVICE_NAME "org.freedesktop.login1" 322 | #define DBUS_SD_OBJECT_PATH "/org/freedesktop/login1" 323 | #define DBUS_SD_INTERFACE "org.freedesktop.login1.Manager" 324 | #define DBUS_SD_METHOD "Inhibit" 325 | #define DBUS_SD_METHOD_ARGS "ssss" 326 | #define DBUS_SD_METHOD_WHAT "sleep" 327 | #define DBUS_SD_METHOD_WHO "idlehack" 328 | #define DBUS_SD_METHOD_WHY "org.freedesktop.ScreenSaver -> systemd" 329 | #define DBUS_SD_METHOD_MODE "delay" 330 | 331 | #define DBUS_SD_MATCH "type='signal'," \ 332 | "interface='" DBUS_SD_INTERFACE "'," \ 333 | "member='PrepareForSleep'" 334 | 335 | #define DBUS_FDO_NAME "org.freedesktop.ScreenSaver" 336 | #define DBUS_FDO_OBJECT_PATH "/ScreenSaver" /* Firefox */ 337 | #define DBUS_FDO_OBJECT_PATH_2 "/org/freedesktop/ScreenSaver" /* Chrome */ 338 | #define DBUS_FDO_INTERFACE "org.freedesktop.ScreenSaver" 339 | 340 | #define HEARTBEAT_INTERVAL 50 /* seconds */ 341 | 342 | #undef countof 343 | #define countof(x) (sizeof((x))/sizeof((*x))) 344 | 345 | 346 | struct handler_ctx { 347 | sd_bus *system_bus; 348 | sd_bus_message *lock_message; 349 | int lock_fd; 350 | int is_inhibited; 351 | sd_bus_track *track; 352 | }; 353 | 354 | static struct handler_ctx global_ctx = { NULL, NULL, -1, 0, NULL }; 355 | 356 | SLIST_HEAD(inhibit_head, inhibit_entry) inhibit_head = 357 | SLIST_HEAD_INITIALIZER(inhibit_head); 358 | 359 | struct inhibit_entry { 360 | uint32_t cookie; 361 | time_t start_time; 362 | char *appname; 363 | char *peer; 364 | SLIST_ENTRY(inhibit_entry) entries; 365 | }; 366 | 367 | 368 | static void 369 | xscreensaver_command (const char *cmd) 370 | { 371 | char buf[1024]; 372 | int rc; 373 | sprintf (buf, "swayidle-inhibit %.100s -%.100s", 374 | (verbose_p ? "-verbose" : "-quiet"), 375 | cmd); 376 | if (verbose_p) 377 | fprintf (stderr, "%s: exec: %s\n", blurb(), buf); 378 | rc = system (buf); 379 | if (rc == -1) 380 | fprintf (stderr, "%s: exec failed: %s\n", blurb(), buf); 381 | else if (WEXITSTATUS(rc) != 0) 382 | fprintf (stderr, "%s: exec: \"%s\" exited with status %d\n", 383 | blurb(), buf, WEXITSTATUS(rc)); 384 | } 385 | 386 | 387 | static int 388 | xscreensaver_register_sleep_lock (struct handler_ctx *ctx) 389 | { 390 | return 0; // SBE - no need to monitor shutdown or sleep 391 | sd_bus_error error = SD_BUS_ERROR_NULL; 392 | sd_bus_message *reply = NULL; 393 | int fd = -1; 394 | int rc = sd_bus_call_method (ctx->system_bus, 395 | DBUS_SD_SERVICE_NAME, DBUS_SD_OBJECT_PATH, 396 | DBUS_SD_INTERFACE, DBUS_SD_METHOD, 397 | &error, &reply, 398 | DBUS_SD_METHOD_ARGS, 399 | DBUS_SD_METHOD_WHAT, DBUS_SD_METHOD_WHO, 400 | DBUS_SD_METHOD_WHY, DBUS_SD_METHOD_MODE); 401 | if (rc < 0) { 402 | fprintf (stderr, "%s: inhibit sleep failed: %s\n", 403 | blurb(), error.message); 404 | goto DONE; 405 | } 406 | 407 | /* Save the lock fd and explicitly take a ref to the lock message. */ 408 | rc = sd_bus_message_read (reply, "h", &fd); 409 | if (rc < 0 || fd < 0) { 410 | fprintf (stderr, "%s: inhibit sleep failed: no lock fd: %s\n", 411 | blurb(), strerror(-rc)); 412 | goto DONE; 413 | } 414 | sd_bus_message_ref(reply); 415 | ctx->lock_message = reply; 416 | ctx->lock_fd = fd; 417 | 418 | DONE: 419 | sd_bus_error_free (&error); 420 | 421 | return rc; 422 | } 423 | 424 | 425 | /* Called when DBUS_SD_INTERFACE sends a "PrepareForSleep" signal. 426 | The event is sent twice: before sleep, and after. 427 | */ 428 | static int 429 | xscreensaver_systemd_handler (sd_bus_message *m, void *arg, 430 | sd_bus_error *ret_error) 431 | { 432 | struct handler_ctx *ctx = arg; 433 | int before_sleep; 434 | int rc; 435 | 436 | rc = sd_bus_message_read (m, "b", &before_sleep); 437 | if (rc < 0) { 438 | fprintf (stderr, "%s: message read failed: %s\n", 439 | blurb(), strerror(-rc)); 440 | return 1; /* >= 0 means success */ 441 | } 442 | 443 | /* Use the scheme described at 444 | https://www.freedesktop.org/wiki/Software/systemd/inhibit/ 445 | under "Taking Delay Locks". 446 | */ 447 | 448 | if (before_sleep) { 449 | /* Tell xscreensaver that we are suspending, and to lock if desired. */ 450 | xscreensaver_command ("suspend"); 451 | 452 | if (ctx->lock_message) { 453 | /* Release the lock, meaning we are done and it's ok to sleep now. 454 | Don't rely on unref'ing the message to close the fd, do that 455 | explicitly here. 456 | */ 457 | close(ctx->lock_fd); 458 | sd_bus_message_unref (ctx->lock_message); 459 | ctx->lock_message = NULL; 460 | ctx->lock_fd = -1; 461 | } else { 462 | fprintf (stderr, "%s: no context lock\n", blurb()); 463 | } 464 | } else { 465 | /* Tell xscreensaver to present the unlock dialog right now. */ 466 | xscreensaver_command ("deactivate"); 467 | 468 | /* We woke from sleep, so we need to re-register for the next sleep. */ 469 | rc = xscreensaver_register_sleep_lock (ctx); 470 | if (rc < 0) 471 | fprintf (stderr, "%s: could not re-register sleep lock\n", blurb()); 472 | } 473 | 474 | return 1; /* >= 0 means success */ 475 | } 476 | 477 | 478 | /* Called from the vtable when another process sends a request to systemd 479 | to inhibit the screen saver. We return to them a cookie which they must 480 | present with their "uninhibit" request. 481 | */ 482 | static int 483 | xscreensaver_method_inhibit (sd_bus_message *m, void *arg, 484 | sd_bus_error *ret_error) 485 | { 486 | struct handler_ctx *ctx = arg; 487 | const char *application_name = 0, *inhibit_reason = 0; 488 | struct inhibit_entry *entry = 0; 489 | const char *s; 490 | const char *sender; 491 | 492 | fprintf(stderr, "INHIBIT!!\n"); 493 | int rc = sd_bus_message_read(m, "ss", &application_name, &inhibit_reason); 494 | if (rc < 0) { 495 | fprintf (stderr, "%s: failed to parse method call: %s\n", 496 | blurb(), strerror(-rc)); 497 | return rc; 498 | } 499 | 500 | if (!application_name || !*application_name) { 501 | fprintf (stderr, "%s: no app name in method call\n", blurb()); 502 | return -1; 503 | } 504 | 505 | if (!inhibit_reason || !*inhibit_reason) { 506 | fprintf (stderr, "%s: no reason in method call from \"%s\"\n", 507 | blurb(), application_name); 508 | return -1; 509 | } 510 | 511 | sender = sd_bus_message_get_sender (m); 512 | 513 | /* Omit directory (Chrome does this shit) */ 514 | s = strrchr (application_name, '/'); 515 | if (s && s[1]) application_name = s+1; 516 | 517 | if (strcasestr (inhibit_reason, "audio") && 518 | !strcasestr (inhibit_reason, "video")) { 519 | /* Firefox 78 sends an inhibit when playing audio only, with reason 520 | "audio-playing". This is horrible. Ignore it. (But perhaps it 521 | would be better to accept it, issue them a cookie, and then just 522 | ignore that entry?) */ 523 | if (verbose_p) 524 | fprintf (stderr, "%s: inhibited by \"%s\" (%s) with \"%s\", ignored\n", 525 | blurb(), application_name, sender, inhibit_reason); 526 | return -1; 527 | } 528 | 529 | /* Tell the global tracker object to monitor when this peer exits. */ 530 | rc = sd_bus_track_add_name(ctx->track, sender); 531 | if (rc < 0) { 532 | fprintf (stderr, "%s: failed to track peer \"%s\": %s\n", 533 | blurb(), sender, strerror(-rc)); 534 | sender = NULL; 535 | } 536 | 537 | entry = malloc(sizeof (struct inhibit_entry)); 538 | entry->cookie = ya_random(); 539 | entry->appname = strdup(application_name); 540 | entry->peer = sender ? strdup(sender) : NULL; 541 | entry->start_time = time ((time_t *)0); 542 | SLIST_INSERT_HEAD(&inhibit_head, entry, entries); 543 | ctx->is_inhibited++; 544 | if (verbose_p) 545 | fprintf (stderr, "%s: inhibited by \"%s\" (%s) with \"%s\"" 546 | " -> cookie %08X\n", 547 | blurb(), application_name, sender, inhibit_reason, entry->cookie); 548 | 549 | return sd_bus_reply_method_return (m, "u", entry->cookie); 550 | } 551 | 552 | 553 | /* Called from the vtable when another process sends a request to systemd 554 | to uninhibit the screen saver. The cookie must match an earlier "inhibit" 555 | request. 556 | */ 557 | static int 558 | xscreensaver_method_uninhibit (sd_bus_message *m, void *arg, 559 | sd_bus_error *ret_error) 560 | { 561 | struct handler_ctx *ctx = arg; 562 | uint32_t cookie; 563 | struct inhibit_entry *entry; 564 | int found = 0; 565 | const char *sender; 566 | 567 | fprintf(stderr, "UNINHIBIT!!\n"); 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 | 697 | /* 'user_bus' is where we receive messages from other programs sending 698 | inhibit/uninhibit to org.freedesktop.ScreenSaver, etc. 699 | */ 700 | 701 | rc = sd_bus_open_user (&user_bus); 702 | if (rc < 0) { 703 | fprintf (stderr, "%s: user bus connection failed: %s\n", 704 | blurb(), strerror(-rc)); 705 | goto FAIL; 706 | } 707 | 708 | /* Create a single tracking object so that we can later ask it, 709 | "is the peer with this name still around?" This is how we tell 710 | that Firefox has exited without calling 'uninhibit'. 711 | */ 712 | rc = sd_bus_track_new (user_bus, 713 | &global_ctx.track, 714 | NULL, 715 | NULL); 716 | if (rc < 0) { 717 | fprintf (stderr, "%s: cannot create user bus tracker: %s\n", 718 | blurb(), strerror(-rc)); 719 | goto FAIL; 720 | } 721 | 722 | rc = sd_bus_add_object_vtable (user_bus, 723 | NULL, 724 | DBUS_FDO_OBJECT_PATH, 725 | DBUS_FDO_INTERFACE, 726 | xscreensaver_dbus_vtable, 727 | &global_ctx); 728 | if (rc < 0) { 729 | fprintf (stderr, "%s: vtable registration failed: %s\n", 730 | blurb(), strerror(-rc)); 731 | goto FAIL; 732 | } 733 | 734 | rc = sd_bus_add_object_vtable (user_bus, 735 | NULL, 736 | DBUS_FDO_OBJECT_PATH_2, 737 | DBUS_FDO_INTERFACE, 738 | xscreensaver_dbus_vtable, 739 | &global_ctx); 740 | if (rc < 0) { 741 | fprintf (stderr, "%s: vtable registration failed: %s\n", 742 | blurb(), strerror(-rc)); 743 | goto FAIL; 744 | } 745 | 746 | { 747 | const char * const names[] = { DBUS_FDO_NAME, DBUS_CLIENT_NAME }; 748 | int i = 0; 749 | for (i = 0; i < countof(names); i++) { 750 | rc = sd_bus_request_name (user_bus, names[i], 0); 751 | if (rc < 0) { 752 | pid_t pid = get_bus_name_pid (user_bus, names[i]); 753 | if (pid != -1) { 754 | char *pname = process_name (pid); 755 | if (pname) { 756 | fprintf (stderr, 757 | "%s: connection failed: \"%s\" in use by pid %lu (%s)\n", 758 | blurb(), names[i], (unsigned long) pid, pname); 759 | free (pname); 760 | } else { 761 | fprintf (stderr, 762 | "%s: connection failed: \"%s\" in use by pid %lu\n", 763 | blurb(), names[i], (unsigned long) pid); 764 | } 765 | } else if (-rc == EEXIST || -rc == EALREADY) { 766 | fprintf (stderr, "%s: connection failed: \"%s\" already in use\n", 767 | blurb(), names[i]); 768 | } else { 769 | fprintf (stderr, "%s: connection failed for \"%s\": %s\n", 770 | blurb(), names[i], strerror(-rc)); 771 | } 772 | goto FAIL; 773 | } 774 | } 775 | } 776 | 777 | /* 'system_bus' is where we hold a lock on org.freedesktop.login1, meaning 778 | that the system will send us a PrepareForSleep message when the system is 779 | about to suspend. 780 | */ 781 | 782 | rc = sd_bus_open_system (&system_bus); 783 | if (rc < 0) { 784 | fprintf (stderr, "%s: system bus connection failed: %s\n", 785 | blurb(), strerror(-rc)); 786 | goto FAIL; 787 | } 788 | 789 | /* Obtain a lock fd from the "Inhibit" method, so that we can delay 790 | sleep when a "PrepareForSleep" signal is posted. */ 791 | 792 | ctx->system_bus = system_bus; 793 | rc = xscreensaver_register_sleep_lock (ctx); 794 | if (rc < 0) 795 | goto FAIL; 796 | 797 | /* This is basically an event mask, saying that we are interested in 798 | "PrepareForSleep", and to run our callback when that signal is thrown. 799 | */ 800 | rc = sd_bus_add_match (system_bus, NULL, DBUS_SD_MATCH, 801 | xscreensaver_systemd_handler, 802 | &global_ctx); 803 | if (rc < 0) { 804 | fprintf (stderr, "%s: add match failed: %s\n", blurb(), strerror(-rc)); 805 | goto FAIL; 806 | } 807 | 808 | if (verbose_p) 809 | fprintf (stderr, "%s: connected\n", blurb()); 810 | 811 | 812 | /* Run an event loop forever, and wait for our callback to run. 813 | */ 814 | while (1) { 815 | struct pollfd fds[2]; 816 | uint64_t poll_timeout_msec, system_timeout_usec, user_timeout_usec; 817 | struct inhibit_entry *entry; 818 | 819 | /* We MUST call sd_bus_process() on each bus at least once before calling 820 | sd_bus_get_events(), so just always start the event loop by processing 821 | all outstanding requests on both busses. */ 822 | do { 823 | rc = sd_bus_process (system_bus, NULL); 824 | if (rc < 0) { 825 | fprintf (stderr, "%s: failed to process system bus: %s\n", 826 | blurb(), strerror(-rc)); 827 | goto FAIL; 828 | } 829 | } while (rc > 0); 830 | 831 | do { 832 | rc = sd_bus_process (user_bus, NULL); 833 | if (rc < 0) { 834 | fprintf (stderr, "%s: failed to process user bus: %s\n", 835 | blurb(), strerror(-rc)); 836 | goto FAIL; 837 | } 838 | } while (rc > 0); 839 | 840 | /* Prune any entries whose original sender has gone away: this happens 841 | if a program inhibits, then exits without having called uninhibit. 842 | That would have left us inhibited forever, even if the inhibiting 843 | program was re-launched, since the new instance won't have the 844 | same cookie. */ 845 | SLIST_FOREACH (entry, &inhibit_head, entries) { 846 | if (entry->peer && 847 | !sd_bus_track_count_name (ctx->track, entry->peer)) { 848 | if (verbose_p) 849 | fprintf (stderr, 850 | "%s: peer %s for inhibiting app \"%s\" has died:" 851 | " uninhibiting %08X\n", 852 | blurb(), 853 | entry->peer, 854 | entry->appname, 855 | entry->cookie); 856 | SLIST_REMOVE (&inhibit_head, entry, inhibit_entry, entries); 857 | if (entry->appname) free (entry->appname); 858 | free(entry->peer); 859 | free (entry); 860 | ctx->is_inhibited--; 861 | if (ctx->is_inhibited < 0) 862 | ctx->is_inhibited = 0; 863 | } 864 | } 865 | 866 | /* If we are inhibited and HEARTBEAT_INTERVAL has passed, de-activate the 867 | screensaver. */ 868 | if (ctx->is_inhibited) { 869 | time_t now = time ((time_t *) 0); 870 | if (now - last_deactivate_time >= HEARTBEAT_INTERVAL) { 871 | if (verbose_p) { 872 | SLIST_FOREACH (entry, &inhibit_head, entries) { 873 | char ct[100]; 874 | ctime_r (&entry->start_time, ct); 875 | fprintf (stderr, "%s: inhibited by \"%s\" since %s", 876 | blurb(), entry->appname, ct); 877 | } 878 | } 879 | xscreensaver_command ("deactivate"); 880 | last_deactivate_time = now; 881 | } 882 | } 883 | 884 | /* The remainder of the code that follows is concerned solely with 885 | determining how long we should wait until the next iteration of the 886 | event loop. 887 | */ 888 | rc = sd_bus_get_fd (system_bus); 889 | if (rc < 0) { 890 | fprintf (stderr, "%s: sd_bus_get_fd failed for system bus: %s\n", 891 | blurb(), strerror(-rc)); 892 | goto FAIL; 893 | } 894 | fds[0].fd = rc; 895 | rc = sd_bus_get_events (system_bus); 896 | if (rc < 0) { 897 | fprintf (stderr, "%s: sd_bus_get_events failed for system bus: %s\n", 898 | blurb(), strerror(-rc)); 899 | goto FAIL; 900 | } 901 | fds[0].events = rc; 902 | fds[0].revents = 0; 903 | 904 | rc = sd_bus_get_fd (user_bus); 905 | if (rc < 0) { 906 | fprintf (stderr, "%s: sd_bus_get_fd failed for user bus: %s\n", 907 | blurb(), strerror(-rc)); 908 | goto FAIL; 909 | } 910 | fds[1].fd = rc; 911 | rc = sd_bus_get_events (user_bus); 912 | if (rc < 0) { 913 | fprintf (stderr, "%s: sd_bus_get_events failed for user bus: %s\n", 914 | blurb(), strerror(-rc)); 915 | goto FAIL; 916 | } 917 | fds[1].events = rc; 918 | fds[1].revents = 0; 919 | 920 | rc = sd_bus_get_timeout (system_bus, &system_timeout_usec); 921 | if (rc < 0) { 922 | fprintf (stderr, "%s: sd_bus_get_timeout failed for system bus: %s\n", 923 | blurb(), strerror(-rc)); 924 | goto FAIL; 925 | } 926 | sd_bus_get_timeout (user_bus, &user_timeout_usec); 927 | if (rc < 0) { 928 | fprintf (stderr, "%s: sd_bus_get_timeout failed for user bus: %s\n", 929 | blurb(), strerror(-rc)); 930 | goto FAIL; 931 | } 932 | 933 | /* Pick the smaller of the two bus timeouts and convert from microseconds 934 | to milliseconds expected by poll(). */ 935 | poll_timeout_msec = ((system_timeout_usec < user_timeout_usec 936 | ? system_timeout_usec : user_timeout_usec) 937 | / 1000); 938 | 939 | /* If we have been inhibited, we want to wake up at least once every N 940 | seconds to de-activate the screensaver. 941 | */ 942 | if (ctx->is_inhibited && 943 | poll_timeout_msec > HEARTBEAT_INTERVAL * 1000) 944 | poll_timeout_msec = HEARTBEAT_INTERVAL * 1000; 945 | 946 | if (poll_timeout_msec < 1000) 947 | poll_timeout_msec = 1000; 948 | 949 | rc = poll (fds, 2, poll_timeout_msec); 950 | if (rc < 0) { 951 | fprintf (stderr, "%s: poll failed: %s\n", blurb(), strerror(errno)); 952 | goto FAIL; 953 | } 954 | 955 | } /* Event loop end */ 956 | 957 | FAIL: 958 | 959 | if (system_bus) 960 | sd_bus_flush_close_unref (system_bus); 961 | 962 | if (ctx->track) 963 | sd_bus_track_unref (ctx->track); 964 | 965 | if (user_bus) 966 | sd_bus_flush_close_unref (user_bus); 967 | 968 | sd_bus_error_free (&error); 969 | 970 | return EXIT_FAILURE; 971 | } 972 | 973 | 974 | static char *usage = "\n\ 975 | usage: %s [-verbose]\n\ 976 | \n\ 977 | This program is launched by the xscreensaver daemon to monitor DBus.\n\ 978 | It invokes 'swayidle-inhibit' to tell the xscreensaver daemon to lock\n\ 979 | the screen before the system suspends, e.g., when a laptop's lid is closed.\n\ 980 | \n\ 981 | It also responds to certain messages sent by media players allowing them to\n\ 982 | request that the screen not be blanked during playback.\n\ 983 | \n\ 984 | From XScreenSaver %s, (c) 1991-%s Jamie Zawinski .\n"; 985 | 986 | 987 | #define USAGE() do { \ 988 | fprintf (stderr, usage, progname, screensaver_version, year); exit (1); \ 989 | } while(0) 990 | 991 | 992 | int 993 | main (int argc, char **argv) 994 | { 995 | int i; 996 | char *version = strdup (screensaver_id + 17); 997 | char *year = strchr (version, '-'); 998 | char *s = strchr (version, ' '); 999 | *s = 0; 1000 | year = strchr (year+1, '-') + 1; 1001 | s = strchr (year, ')'); 1002 | *s = 0; 1003 | 1004 | screensaver_version = version; 1005 | 1006 | progname = argv[0]; 1007 | s = strrchr (progname, '/'); 1008 | if (s) progname = s+1; 1009 | 1010 | for (i = 1; i < argc; i++) 1011 | { 1012 | const char *s = argv [i]; 1013 | int L; 1014 | if (s[0] == '-' && s[1] == '-') s++; 1015 | L = strlen (s); 1016 | if (L < 2) USAGE (); 1017 | else if (!strncmp (s, "-verbose", L)) verbose_p = 1; 1018 | else if (!strncmp (s, "-quiet", L)) verbose_p = 0; 1019 | else USAGE (); 1020 | } 1021 | 1022 | # undef ya_rand_init 1023 | ya_rand_init (0); 1024 | 1025 | exit (xscreensaver_systemd_loop()); 1026 | } 1027 | 1028 | --------------------------------------------------------------------------------