diff --git a/PKGBUILD b/PKGBUILD index e0fd459..717210f 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -9,7 +9,9 @@ pkgdesc='Honor org.freedesktop.ScreenSaver inhibit requests by disabling xautolo arch=('any') url="https://git.shouldnt.work/fuxino/${pkgname}" license=('GPL-3.0-or-later') -depends=('python' +depends=('libx11' + 'libxss' + 'python' 'python-gobject' 'xautolock') makedepends=('git') diff --git a/xautolock-inhibit b/xautolock-inhibit index 594603e..ec4f58d 100755 --- a/xautolock-inhibit +++ b/xautolock-inhibit @@ -1,7 +1,11 @@ #!/usr/bin/env python3 -"""Own org.freedesktop.ScreenSaver and turn inhibit requests into xautolock -disable/-enable.""" +"""Own org.freedesktop.ScreenSaver and turn inhibit requests into xautolock -disable/-enable. + +While an inhibit is active, X screen blanking is also paused (like mpv does). +""" import argparse +import ctypes import itertools import signal import subprocess @@ -62,9 +66,49 @@ def xautolock(arg): log(f"xautolock {arg} failed: {e}") +class XBlanking: + """Pause X screen blanking with XScreenSaverSuspend (MIT-SCREEN-SAVER extension). + + Unlike `xset s off -dpms` this changes no global setting: the X server ends the + pause by itself when this client releases it or its connection closes (exit, crash). + """ + + def __init__(self): + self.dpy = None + try: + self.x11 = ctypes.CDLL("libX11.so.6") + self.xss = ctypes.CDLL("libXss.so.1") + except OSError as e: + log(f"Cannot load X11 libraries ({e}); X blanking will not be paused") + return + self.x11.XOpenDisplay.argtypes = [ctypes.c_char_p] + self.x11.XOpenDisplay.restype = ctypes.c_void_p + self.x11.XFlush.argtypes = [ctypes.c_void_p] + self.xss.XScreenSaverQueryExtension.argtypes = [ + ctypes.c_void_p, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int)] + self.xss.XScreenSaverSuspend.argtypes = [ctypes.c_void_p, ctypes.c_int] + + dpy = self.x11.XOpenDisplay(None) + if not dpy: + log("Cannot open the X display; X blanking will not be paused") + return + event_base, error_base = ctypes.c_int(), ctypes.c_int() + if not self.xss.XScreenSaverQueryExtension(dpy, ctypes.byref(event_base), ctypes.byref(error_base)): + log("X server lacks the MIT-SCREEN-SAVER extension; X blanking will not be paused") + return + self.dpy = dpy + + def suspend(self, on): + if self.dpy: + self.xss.XScreenSaverSuspend(self.dpy, int(on)) + self.x11.XFlush(self.dpy) + log(f"X blanking {'paused' if on else 'resumed'}") + + class Inhibitor: - def __init__(self, ignored_reasons, release_delay): + def __init__(self, ignored_reasons, release_delay, blanking): self.ignored_reasons = ignored_reasons + self.blanking = blanking self.release_delay = release_delay self.cookies = {} # cookie -> (sender, application_name, reason, counts) self.next_cookie = itertools.count(1) @@ -85,8 +129,7 @@ class Inhibitor: GLib.source_remove(self.release_timer) self.release_timer = 0 if active and not self.disabled: - xautolock("-disable") - self.disabled = True + self.block() elif not active and self.disabled and not self.release_timer: # Browsers send quick Inhibit/UnInhibit bursts when a video starts. self.release_timer = GLib.timeout_add_seconds(self.release_delay, self.on_release_timeout) @@ -94,17 +137,25 @@ class Inhibitor: def on_release_timeout(self): self.release_timer = 0 if not self.active_count() and self.disabled: - xautolock("-enable") - self.disabled = False + self.unblock() return GLib.SOURCE_REMOVE + def block(self): + xautolock("-disable") + self.blanking.suspend(True) + self.disabled = True + + def unblock(self): + xautolock("-enable") + self.blanking.suspend(False) + self.disabled = False + def shutdown(self): if self.release_timer: GLib.source_remove(self.release_timer) self.release_timer = 0 if self.disabled: - xautolock("-enable") - self.disabled = False + self.unblock() # --- D-Bus ------------------------------------------------------------ @@ -205,7 +256,7 @@ def main(): ignored = set() else: ignored = set(args.ignore_reason or ["Playing audio"]) - inhibitor = Inhibitor(ignored, max(args.release_delay, 0)) + inhibitor = Inhibitor(ignored, max(args.release_delay, 0), XBlanking()) loop = GLib.MainLoop() exit_code = 0 acquired = False