aboutsummaryrefslogtreecommitdiffstats
path: root/deckit/modules/windows.py
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-11-09 20:41:29 +0100
committerDanilo M. <danix@danix.xyz>2026-11-09 20:41:29 +0100
commit6465ffceeef5c3df404972bf4eaa1b24e451a7c4 (patch)
tree4b1c769fb39809a9b2ca7570190baed3ecc1bde0 /deckit/modules/windows.py
parent853f1ed1928aaeca9ea956c24bee12cd56a7cfdd (diff)
downloaddeck-it-6465ffceeef5c3df404972bf4eaa1b24e451a7c4.tar.gz
deck-it-6465ffceeef5c3df404972bf4eaa1b24e451a7c4.zip
feat: window rows get app icons and a close/move menu
Diffstat (limited to 'deckit/modules/windows.py')
-rw-r--r--deckit/modules/windows.py64
1 files changed, 63 insertions, 1 deletions
diff --git a/deckit/modules/windows.py b/deckit/modules/windows.py
index 8642b1c..69b1210 100644
--- a/deckit/modules/windows.py
+++ b/deckit/modules/windows.py
@@ -8,6 +8,7 @@ from pathlib import Path
from ..module import Module, ModuleError
ADDR = re.compile(r"0x[0-9a-f]{1,16}")
+DESKTOP_BASES = (Path("/usr/share/applications"), Path.home() / ".local/share/applications")
def _run(cmd, timeout=2):
@@ -21,6 +22,51 @@ def _popen(cmd):
subprocess.Popen(cmd, start_new_session=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+def _dispatch(cmd):
+ """Run a hyprctl dispatch and wait: the two-step move depends on the order."""
+ try:
+ subprocess.run(cmd, capture_output=True, timeout=2)
+ except (OSError, subprocess.TimeoutExpired):
+ pass
+
+
+def _desktop_icons(bases=DESKTOP_BASES) -> dict:
+ """Window class -> candidate icon names, from the installed .desktop files."""
+ mapping = {}
+ for base in bases:
+ if not base.is_dir():
+ continue
+ for entry in base.glob("*.desktop"):
+ try:
+ text = entry.read_text(errors="replace")
+ except OSError:
+ continue
+ section = None
+ icon = wmclass = None
+ for line in text.splitlines():
+ line = line.strip()
+ if line.startswith("[") and line.endswith("]"):
+ section = line
+ if section != "[Desktop Entry]":
+ break
+ continue
+ if section != "[Desktop Entry]":
+ continue
+ if line.startswith("Icon="):
+ icon = line.split("=", 1)[1].strip()
+ elif line.startswith("StartupWMClass="):
+ wmclass = line.split("=", 1)[1].strip()
+ if not icon:
+ continue
+ candidates = [icon]
+ if wmclass and wmclass != icon:
+ candidates.append(wmclass)
+ for key in (entry.stem, wmclass):
+ if key:
+ mapping.setdefault(key.lower(), candidates)
+ return mapping
+
+
def hypr_lua(cfg: dict) -> bool:
mode = cfg.get("hyprland", "auto")
if mode == "auto":
@@ -64,6 +110,7 @@ def make(cfg: dict) -> Module:
lua = hypr_lua(cfg)
launch = cfg.get("launch", {})
workspaces = cfg.get("windows", {}).get("workspaces", list(range(1, 11)))
+ icons = _desktop_icons()
def launch_spec(spec):
if isinstance(spec, dict):
@@ -88,6 +135,18 @@ def make(cfg: dict) -> Module:
raise ModuleError("bad window address")
_popen(window_cmd("close", addr, lua))
+ def move(body):
+ addr = str(body.get("address", ""))
+ n = body.get("n")
+ if not ADDR.fullmatch(addr) or not isinstance(n, int) or not 1 <= n <= 99:
+ raise ModuleError("bad window or workspace")
+ if lua:
+ # window.move acts on the focused window, so focus it first.
+ _dispatch(["hyprctl", "eval", f'hl.dispatch(hl.dsp.focus({{window = "address:{addr}"}}))'])
+ _dispatch(["hyprctl", "eval", f'hl.dispatch(hl.dsp.window.move({{workspace = "{n}"}}))'])
+ else:
+ _dispatch(["hyprctl", "dispatch", "movetoworkspacesilent", f"{n},address:{addr}"])
+
def launch_key(body):
key = str(body.get("key", ""))
command, _, _ = launch_spec(launch.get(key))
@@ -97,6 +156,8 @@ def make(cfg: dict) -> Module:
def state():
st = _state(run)
+ for win in st["windows"]:
+ win["icons"] = icons.get(win["class"].lower(), [win["class"].lower()])
known = sorted(set(int(n) for n in workspaces) | set(st["workspaces"]))
launches = []
for key, spec in launch.items():
@@ -105,4 +166,5 @@ def make(cfg: dict) -> Module:
return {**st, "workspaces": known, "launches": launches}
return Module(name="windows", title="Windows", state=state,
- actions={"workspace": workspace, "focus": focus, "close": close, "launch": launch_key})
+ actions={"workspace": workspace, "focus": focus, "close": close,
+ "move": move, "launch": launch_key})