From 239d0ae2f00ab58e88a0b31c5407a483c582ab24 Mon Sep 17 00:00:00 2001 From: Sunderland93 Date: Sat, 20 Jul 2024 19:42:39 +0400 Subject: [PATCH] Integrate with systemd, add custom idle-inhibitor, fix configs --- debian/control | 2 +- etc/skel/.config/nwg-bar/bar.json | 4 +- etc/skel/.config/qtile/autostart.sh | 22 +++-- etc/skel/.config/swayidle/config | 2 +- usr/bin/wayland-idle-inhibitor.py | 81 +++++++++++++++++++ .../user/qtile-session-shutdown.target | 10 +++ usr/lib/systemd/user/qtile-session.target | 6 ++ .../systemd/user/qtile-xdg-autostart.target | 10 +++ usr/lib/systemd/user/swayidle-restart.path | 10 +++ usr/lib/systemd/user/swayidle-restart.service | 6 ++ usr/lib/systemd/user/swayidle.service | 12 +++ usr/share/qtile/scripts/idle-inhibit | 19 ++--- usr/share/qtile/scripts/qtile-exit.sh | 18 +++++ usr/share/qtile/scripts/wait-sni-ready | 61 ++++++++++++++ 14 files changed, 239 insertions(+), 24 deletions(-) create mode 100755 usr/bin/wayland-idle-inhibitor.py create mode 100644 usr/lib/systemd/user/qtile-session-shutdown.target create mode 100644 usr/lib/systemd/user/qtile-session.target create mode 100644 usr/lib/systemd/user/qtile-xdg-autostart.target create mode 100644 usr/lib/systemd/user/swayidle-restart.path create mode 100644 usr/lib/systemd/user/swayidle-restart.service create mode 100644 usr/lib/systemd/user/swayidle.service create mode 100755 usr/share/qtile/scripts/qtile-exit.sh create mode 100755 usr/share/qtile/scripts/wait-sni-ready diff --git a/debian/control b/debian/control index f7b0eb3..52f2ccb 100644 --- a/debian/control +++ b/debian/control @@ -9,6 +9,6 @@ Rules-Requires-Root: no Package: tileos-settings-qtile Architecture: all -Depends: ${shlibs:Depends}, ${misc:Depends} +Depends: ${shlibs:Depends}, ${misc:Depends}, python3-tenacity, python3-dbus-next Conflicts: tileos-settings-river, tileos-settings-sway Description: default settings for Qtile on TileOS diff --git a/etc/skel/.config/nwg-bar/bar.json b/etc/skel/.config/nwg-bar/bar.json index 66427ce..65b751c 100644 --- a/etc/skel/.config/nwg-bar/bar.json +++ b/etc/skel/.config/nwg-bar/bar.json @@ -1,12 +1,12 @@ [ { "label": "Lock", - "exec": "~/.config/swaylock/lock.sh", + "exec": "bash -c ~/.config/swaylock/lock.sh", "icon": "system-lock-screen" }, { "label": "Logout", - "exec": "qtile cmd-obj -o cmd -f shutdown", + "exec": "bash -c /usr/share/qtile/scripts/qtile-exit.sh", "icon": "system-log-out" }, { diff --git a/etc/skel/.config/qtile/autostart.sh b/etc/skel/.config/qtile/autostart.sh index c310532..3349cea 100755 --- a/etc/skel/.config/qtile/autostart.sh +++ b/etc/skel/.config/qtile/autostart.sh @@ -1,13 +1,27 @@ -#!/usr/bin/env bash +#!/bin/bash export XDG_CURRENT_DESKTOP=wlroots export XDG_SESSION_DESKTOP="${XDG_SESSION_DESKTOP:-wlroots}" export XDG_SESSION_TYPE=wayland VARIABLES="DESKTOP_SESSION XDG_CURRENT_DESKTOP XDG_SESSION_DESKTOP XDG_SESSION_TYPE" VARIABLES="${VARIABLES} DISPLAY WAYLAND_DISPLAY" +SESSION_TARGET="qtile-session.target" +SESSION_XDG_AUTOSTART_TARGET="qtile-xdg-autostart.target" +SNI_CHECK="/usr/share/qtile/scripts/wait-sni-ready" +# shellcheck disable=SC2086 dbus-update-activation-environment --systemd ${VARIABLES:- --all} & +# reset failed state of all user units +systemctl --user reset-failed + +# shellcheck disable=SC2086 +systemctl --user import-environment $VARIABLES +systemctl --user start "$SESSION_TARGET" + +# Wait for StatusNotifierWatcher is available and start XDG Autostart target +"$SNI_CHECK" && systemctl --user start "$SESSION_XDG_AUTOSTART_TARGET" + # PolicyKit Agent /usr/bin/mate-polkit @@ -24,8 +38,4 @@ pcmanfm-qt -d & # Clipboard daemon pkill wl-paste -wl-paste --watch cliphist store & - -# Power daemon -pkill poweralertd -poweralertd & +wl-paste --watch cliphist store & \ No newline at end of file diff --git a/etc/skel/.config/swayidle/config b/etc/skel/.config/swayidle/config index 36c92e1..7a061c6 100644 --- a/etc/skel/.config/swayidle/config +++ b/etc/skel/.config/swayidle/config @@ -1,6 +1,6 @@ timeout 240 'light -G > /tmp/brightness && light -S 10' resume 'light -S $([ -f /tmp/brightness ] && cat /tmp/brightness || echo 100%)' timeout 300 ~/.config/swaylock/lock.sh -timeout 600 'swaymsg "output * power off"' resume 'swaymsg "output * power on"' +timeout 600 'wlopm --off \*' resume 'wlopm --on \*' before-sleep 'playerctl pause' before-sleep ~/.config/swaylock/lock.sh lock ~/.config/swaylock/lock.sh diff --git a/usr/bin/wayland-idle-inhibitor.py b/usr/bin/wayland-idle-inhibitor.py new file mode 100755 index 0000000..d44dc02 --- /dev/null +++ b/usr/bin/wayland-idle-inhibitor.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 + +import sys +from dataclasses import dataclass +from signal import SIGINT, SIGTERM, signal +from threading import Event + +from pywayland.client.display import Display +from pywayland.protocol.idle_inhibit_unstable_v1.zwp_idle_inhibit_manager_v1 import ( + ZwpIdleInhibitManagerV1, +) +from pywayland.protocol.wayland.wl_compositor import WlCompositor +from pywayland.protocol.wayland.wl_registry import WlRegistryProxy +from pywayland.protocol.wayland.wl_surface import WlSurface + + +@dataclass +class GlobalRegistry: + surface: WlSurface | None = None + inhibit_manager: ZwpIdleInhibitManagerV1 | None = None + + +def handle_registry_global( + wl_registry: WlRegistryProxy, id_num: int, iface_name: str, version: int +) -> None: + global_registry: GlobalRegistry = wl_registry.user_data or GlobalRegistry() + + if iface_name == "wl_compositor": + compositor = wl_registry.bind(id_num, WlCompositor, version) + global_registry.surface = compositor.create_surface() # type: ignore + elif iface_name == "zwp_idle_inhibit_manager_v1": + global_registry.inhibit_manager = wl_registry.bind( + id_num, ZwpIdleInhibitManagerV1, version + ) + + +def main() -> None: + done = Event() + signal(SIGINT, lambda _, __: done.set()) + signal(SIGTERM, lambda _, __: done.set()) + + global_registry = GlobalRegistry() + + display = Display() + display.connect() + + registry = display.get_registry() # type: ignore + registry.user_data = global_registry + registry.dispatcher["global"] = handle_registry_global + + def shutdown() -> None: + display.dispatch() + display.roundtrip() + display.disconnect() + + display.dispatch() + display.roundtrip() + + if global_registry.surface is None or global_registry.inhibit_manager is None: + print("Wayland seems not to support idle_inhibit_unstable_v1 protocol.") + shutdown() + sys.exit(1) + + inhibitor = global_registry.inhibit_manager.create_inhibitor( # type: ignore + global_registry.surface + ) + + display.dispatch() + display.roundtrip() + + print("Inhibiting idle...") + done.wait() + print("Shutting down...") + + inhibitor.destroy() + + shutdown() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/usr/lib/systemd/user/qtile-session-shutdown.target b/usr/lib/systemd/user/qtile-session-shutdown.target new file mode 100644 index 0000000..06a7c30 --- /dev/null +++ b/usr/lib/systemd/user/qtile-session-shutdown.target @@ -0,0 +1,10 @@ +[Unit] +Description=Shutdown running Qtile session +DefaultDependencies=no +StopWhenUnneeded=true + +Conflicts=graphical-session.target graphical-session-pre.target +After=graphical-session.target graphical-session-pre.target + +Conflicts=qtile-session.target +After=qtile-session.target diff --git a/usr/lib/systemd/user/qtile-session.target b/usr/lib/systemd/user/qtile-session.target new file mode 100644 index 0000000..c9cb237 --- /dev/null +++ b/usr/lib/systemd/user/qtile-session.target @@ -0,0 +1,6 @@ +[Unit] +Description=Qtile session +Documentation=man:systemd.special(7) +BindsTo=graphical-session.target +Wants=graphical-session-pre.target +After=graphical-session-pre.target diff --git a/usr/lib/systemd/user/qtile-xdg-autostart.target b/usr/lib/systemd/user/qtile-xdg-autostart.target new file mode 100644 index 0000000..3cd4b7d --- /dev/null +++ b/usr/lib/systemd/user/qtile-xdg-autostart.target @@ -0,0 +1,10 @@ +# Systemd provides xdg-desktop-autostart.target as a way to process XDG autostart +# desktop files. The target sets RefuseManualStart though, and thus cannot be +# used from scripts. +# +[Unit] +Description=XDG autostart for Qtile session +Documentation=man:systemd.special(7) man:systemd-xdg-autostart-generator(8) +BindsTo=xdg-desktop-autostart.target +PartOf=qtile-session.target +After=qtile-session.target diff --git a/usr/lib/systemd/user/swayidle-restart.path b/usr/lib/systemd/user/swayidle-restart.path new file mode 100644 index 0000000..4807f36 --- /dev/null +++ b/usr/lib/systemd/user/swayidle-restart.path @@ -0,0 +1,10 @@ +[Unit] +Description=Automatically restart the swayidle when its configuration changes +PartOf=swayidle.service +After=swayidle.service + +[Path] +PathChanged=%h/.config/swayidle/config + +[Install] +WantedBy=qtile-session.target diff --git a/usr/lib/systemd/user/swayidle-restart.service b/usr/lib/systemd/user/swayidle-restart.service new file mode 100644 index 0000000..839bb44 --- /dev/null +++ b/usr/lib/systemd/user/swayidle-restart.service @@ -0,0 +1,6 @@ +[Service] +ExecStart=systemctl --user restart swayidle.service +Type=oneshot + +[Install] +WantedBy=graphical-session.target diff --git a/usr/lib/systemd/user/swayidle.service b/usr/lib/systemd/user/swayidle.service new file mode 100644 index 0000000..2fe3dca --- /dev/null +++ b/usr/lib/systemd/user/swayidle.service @@ -0,0 +1,12 @@ +[Unit] +Description=Idle manager for Wayland +Documentation=man:swayidle(1) +PartOf=graphical-session.target +ConditionPathExists=/usr/bin/swayidle + +[Service] +Type=simple +ExecStart=/usr/bin/swayidle -w + +[Install] +WantedBy=qtile-session.target diff --git a/usr/share/qtile/scripts/idle-inhibit b/usr/share/qtile/scripts/idle-inhibit index 2da3e7b..99b4e5f 100755 --- a/usr/share/qtile/scripts/idle-inhibit +++ b/usr/share/qtile/scripts/idle-inhibit @@ -1,22 +1,13 @@ #!/bin/bash -# Qtile swayidle toggle +# Qtile idle-inhibitor toggle function toggle { - if pgrep "swayidle" > /dev/null + if pgrep -f "wayland-idle-inhibitor" > /dev/null then - pkill swayidle + pkill -f wayland-idle-inhibitor notify-send -r 5556 -u normal " Screensaver Disabled" else - cd /tmp - grim screen.png - # Delete existing image - rm screen-out.png - #Adds a blur and vignette - ffmpeg -i screen.png -vf "gblur=sigma=10, vignette=PI/5" -c:a copy screen-out.png - swayidle \ - timeout 5 'qtile cmd-obj -o core -f hide_cursor' resume 'qtile cmd-obj -o core -f unhide_cursor' \ - timeout 300 'swaylock -f -i screen-out.png' \ - timeout 600 'wlopm --off \*' resume 'wlopm --on \*' & + /usr/bin/wayland-idle-inhibitor.py & notify-send -r 5556 -u normal " Screensaver Enabled" fi } @@ -26,7 +17,7 @@ case $1 in toggle ;; *) - if pgrep "swayidle" > /dev/null + if pgrep -f "wayland-idle-inhibitor" > /dev/null then icon="" else diff --git a/usr/share/qtile/scripts/qtile-exit.sh b/usr/share/qtile/scripts/qtile-exit.sh new file mode 100755 index 0000000..725c56f --- /dev/null +++ b/usr/share/qtile/scripts/qtile-exit.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +VARIABLES="DESKTOP_SESSION XDG_CURRENT_DESKTOP XDG_SESSION_DESKTOP XDG_SESSION_TYPE" +VARIABLES="${VARIABLES} DISPLAY WAYLAND_DISPLAY" +SESSION_SHUTDOWN_TARGET="qtile-session-shutdown.target" + +session_cleanup () { + # stop the session target and unset the variables + systemctl --user start --job-mode=replace-irreversibly "$SESSION_SHUTDOWN_TARGET" + if [ -n "$VARIABLES" ]; then + # shellcheck disable=SC2086 + systemctl --user unset-environment $VARIABLES + fi +} + +session_cleanup +qtile cmd-obj -o cmd -f shutdown + diff --git a/usr/share/qtile/scripts/wait-sni-ready b/usr/share/qtile/scripts/wait-sni-ready new file mode 100755 index 0000000..68f7be6 --- /dev/null +++ b/usr/share/qtile/scripts/wait-sni-ready @@ -0,0 +1,61 @@ +#!/usr/bin/python3 +""" +A simple script for waiting until an org.kde.StatusNotifierItem host implementation +is available and ready. + +Dependencies: dbus-next, tenacity +""" +import asyncio +import logging +import os + +from dbus_next.aio import MessageBus +from tenacity import retry, stop_after_delay, wait_fixed + +LOG = logging.getLogger("wait-sni-host") +TIMEOUT = int(os.environ.get("SNI_WAIT_TIMEOUT", default=25)) + + +@retry(reraise=True, stop=stop_after_delay(TIMEOUT), wait=wait_fixed(0.5)) +async def get_service(bus, name, object_path, interface_name): + """Wait until the service appears on the bus""" + introspection = await bus.introspect(name, object_path) + proxy = bus.get_proxy_object(name, object_path, introspection) + return proxy.get_interface(interface_name) + + +async def wait_sni_host(bus: MessageBus): + """Wait until a StatusNotifierWatcher service is available and has a + StatusNotifierHost instance""" + future = asyncio.get_event_loop().create_future() + + async def on_host_registered(): + value = await sni_watcher.get_is_status_notifier_host_registered() + LOG.debug("StatusNotifierHostRegistered: %s", value) + if value: + future.set_result(value) + + sni_watcher = await get_service(bus, "org.kde.StatusNotifierWatcher", + "/StatusNotifierWatcher", + "org.kde.StatusNotifierWatcher") + sni_watcher.on_status_notifier_host_registered(on_host_registered) + # fetch initial value + await on_host_registered() + return await asyncio.wait_for(future, timeout=TIMEOUT) + + +async def main(): + """asyncio entrypoint""" + bus = await MessageBus().connect() + try: + await wait_sni_host(bus) + LOG.info("Successfully waited for org.kde.StatusNotifierHost") + # pylint: disable=broad-except + except Exception as err: + LOG.error("Failed to wait for org.kde.StatusNotifierHost: %s", + str(err)) + + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO) + asyncio.run(main())