From fd9d6dafd12348e7ecd0aab216dfeda1fe13a088 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Mon, 9 Nov 2026 19:43:48 +0100 Subject: feat: paint the virtual monitor with the first monitor's swaybg wallpaper --- deck-it | 1 + deckit/display.py | 55 ++++++++++++++++++++++++++++++++++++++++++++------- tests/test_display.py | 11 +++++++++++ 3 files changed, 60 insertions(+), 7 deletions(-) diff --git a/deck-it b/deck-it index 1463b5e..56927e5 100755 --- a/deck-it +++ b/deck-it @@ -25,6 +25,7 @@ teardown() { pkill -f "^bash $SELF watch" 2>/dev/null pkill -x wayvnc pkill -f '^python3 -m deckit' 2>/dev/null + pkill -f "^[^ ]*swaybg -o $MON( |$)" 2>/dev/null hyprctl output remove "$MON" >/dev/null 2>&1 local s for s in $(adb devices | awk 'NR > 1 && $2 == "device" {print $1}'); do diff --git a/deckit/display.py b/deckit/display.py index f4d85f4..50fa3f6 100644 --- a/deckit/display.py +++ b/deckit/display.py @@ -1,8 +1,10 @@ """The virtual monitor half: a headless Hyprland output sized to the tablet.""" import json +import os import re import shutil +import signal import subprocess import sys import time @@ -35,8 +37,35 @@ def hypr_lua(cfg: dict) -> bool: return mode == "lua" -def setup_display(cfg: dict, run_cmd=None) -> None: +def _swaybg_for(output: str, proc: Path = Path("/proc")): + """(pid, image, mode) of the swaybg process painting `output`, from /proc.""" + for cmdline in proc.glob("[0-9]*/cmdline"): + try: + argv = cmdline.read_bytes().decode(errors="replace").split("\0") + except OSError: + continue + if not argv or not argv[0].endswith("swaybg"): + continue + out = image = None + mode = "fill" + for i, arg in enumerate(argv): + if i + 1 >= len(argv): + break + if arg == "-o": + out = argv[i + 1] + elif arg == "-i": + image = argv[i + 1] + elif arg == "-m": + mode = argv[i + 1] + if out == output and image: + return int(cmdline.parent.name), image, mode + return None + + +def setup_display(cfg: dict, run_cmd=None, popen=None) -> None: run_cmd = run_cmd or run + popen = popen or (lambda cmd: subprocess.Popen( + cmd, start_new_session=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)) d = cfg.get("display", {}) mon = d.get("name", "TAB") monitors = json.loads(run_cmd(["hyprctl", "monitors", "-j"]) or "[]") @@ -51,11 +80,23 @@ def setup_display(cfg: dict, run_cmd=None) -> None: f'hl.monitor({{output="{mon}", mode="{mode}", position="{pos}", scale={scale}}})']) else: run_cmd(["hyprctl", "keyword", "monitor", f"{mon},{mode},{pos},{scale}"]) - tool = shutil.which("awww") or shutil.which("swww") - if d.get("wallpaper", True) and tool: + if d.get("wallpaper", True): others = [m["name"] for m in monitors if m["name"] != mon] - for line in run_cmd([tool, "query"]).splitlines(): - if others and others[0] in line and "image: " in line: - run_cmd([tool, "img", "-o", mon, line.split("image: ", 1)[1]], timeout=10) - break + tool = shutil.which("awww") or shutil.which("swww") + if tool: + for line in run_cmd([tool, "query"]).splitlines(): + if others and others[0] in line and "image: " in line: + run_cmd([tool, "img", "-o", mon, line.split("image: ", 1)[1]], timeout=10) + break + elif shutil.which("swaybg"): + # swaybg runs one process per output; copy the first monitor's image. + source = next((found for name in others if (found := _swaybg_for(name))), None) + if source: + existing = _swaybg_for(mon) + if existing: + try: + os.kill(existing[0], signal.SIGTERM) + except OSError: + pass + popen(["swaybg", "-o", mon, "-i", source[1], "-m", source[2]]) print(f"{mon}: {mode} at {pos}") diff --git a/tests/test_display.py b/tests/test_display.py index b5d5b11..b69759a 100644 --- a/tests/test_display.py +++ b/tests/test_display.py @@ -1,4 +1,5 @@ import sys +import tempfile from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) @@ -21,5 +22,15 @@ class Resolution(unittest.TestCase): "2560x1600") +class Swaybg(unittest.TestCase): + def test_finds_the_image_for_an_output(self): + root = Path(tempfile.mkdtemp()) + d = root / "123" + d.mkdir() + (d / "cmdline").write_bytes(b"/usr/bin/swaybg\0-o\0DP-1\0-i\0/a.png\0-m\0fill\0") + self.assertEqual(display._swaybg_for("DP-1", root), (123, "/a.png", "fill")) + self.assertIsNone(display._swaybg_for("TAB", root)) + + if __name__ == "__main__": unittest.main() -- cgit v1.2.3