aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-11-09 19:43:48 +0100
committerDanilo M. <danix@danix.xyz>2026-11-09 19:43:48 +0100
commitfd9d6dafd12348e7ecd0aab216dfeda1fe13a088 (patch)
tree737877d6057d7ed08e85ee0ca64846afb728f9e5
parent05b01ce3ec09302fe1a47ff71540c91a92d1cab6 (diff)
downloaddeck-it-fd9d6dafd12348e7ecd0aab216dfeda1fe13a088.tar.gz
deck-it-fd9d6dafd12348e7ecd0aab216dfeda1fe13a088.zip
feat: paint the virtual monitor with the first monitor's swaybg wallpaper
-rwxr-xr-xdeck-it1
-rw-r--r--deckit/display.py55
-rw-r--r--tests/test_display.py11
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()