From 6b323fc06ec255153e86eafa4dffa67ad8c21f8c Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Mon, 9 Nov 2026 20:31:56 +0100 Subject: feat: Windows tab shows all workspaces and icon-only launchers --- NOTICE | 7 ++++--- config.toml | 21 +++++++++++++++------ deckit/modules/windows.py | 16 ++++++++++++++-- static/deck.css | 14 +++++++++++++- static/logos/firefox.svg | 1 + static/logos/folder.svg | 15 +++++++++++++++ static/logos/obsstudio.svg | 1 + static/logos/signal.svg | 1 + static/logos/terminal.svg | 16 ++++++++++++++++ static/modules/windows.js | 18 +++++++++++++++--- tests/test_windows.py | 15 +++++++++++++++ 11 files changed, 110 insertions(+), 15 deletions(-) create mode 100644 static/logos/firefox.svg create mode 100644 static/logos/folder.svg create mode 100644 static/logos/obsstudio.svg create mode 100644 static/logos/signal.svg create mode 100644 static/logos/terminal.svg diff --git a/NOTICE b/NOTICE index 25324c1..7aa17d1 100644 --- a/NOTICE +++ b/NOTICE @@ -11,6 +11,7 @@ Gridstack.js (https://gridstackjs.com) is vendored under `static/vendor/gridstack/`, MIT License. Copyright (c) 2019-2025 Alain Dumesny and contributors. -The Claude and OpenCode brand marks under `static/logos/` are from Simple Icons -(https://simpleicons.org), released under CC0 1.0. The marks themselves are -trademarks of their respective owners. +The brand marks under `static/logos/` (Claude, OpenCode, OBS Studio, Firefox, +Signal) are from Simple Icons (https://simpleicons.org), released under CC0 +1.0. The `terminal` and `folder` icons are from Lucide (https://lucide.dev), +ISC. The marks themselves are trademarks of their respective owners. diff --git a/config.toml b/config.toml index 517fbe6..9a50512 100644 --- a/config.toml +++ b/config.toml @@ -7,8 +7,9 @@ hyprland = "auto" [display] # Resolution of the virtual monitor. "auto" reads it from the tablet over adb. resolution = "auto" -# Any Hyprland monitor position: "auto-right", "auto-left", or "2646x0". -position = "auto-right" +# "auto-below" centers the tablet under the first monitor; any Hyprland +# position ("auto-right", "auto-left", "2646x0") also works. +position = "auto-below" scale = 1 refresh = 60 # Hyprland output name for the virtual monitor. @@ -55,8 +56,16 @@ url = "http://127.0.0.1:8420" # ssh host alias of the build VM; the module runs `sbb-remote jobs` there. host = "buildsystem" +[windows] +# Workspaces shown on the Windows tab, including empty ones. +workspaces = [1, 2, 3, 4, 5, 6, 7, 8] + [launch] -# Buttons on the Windows panel. Each value runs as a declared command: a string -# with sh -c, or a list run directly. The client sends the key, never a command. -terminal = "kitty" -files = "xdg-open ~" +# Buttons on the Windows tab. `icon` is an SVG name from static/logos; with one, +# the button is icon-only. `command` is a string (sh -c) or a list (run direct). +# The client sends the key, never a command. +terminal = { icon = "terminal", command = "kitty" } +files = { icon = "folder", command = "xdg-open ~" } +obs = { icon = "obsstudio", command = "obs" } +firefox = { icon = "firefox", command = "firefox" } +signal = { icon = "signal", command = "signal-desktop" } diff --git a/deckit/modules/windows.py b/deckit/modules/windows.py index d02fc0c..8642b1c 100644 --- a/deckit/modules/windows.py +++ b/deckit/modules/windows.py @@ -63,6 +63,12 @@ def make(cfg: dict) -> Module: run = cfg.get("run", _run) lua = hypr_lua(cfg) launch = cfg.get("launch", {}) + workspaces = cfg.get("windows", {}).get("workspaces", list(range(1, 11))) + + def launch_spec(spec): + if isinstance(spec, dict): + return spec.get("command"), spec.get("icon"), spec.get("label") + return spec, None, None def workspace(body): n = body.get("n") @@ -84,13 +90,19 @@ def make(cfg: dict) -> Module: def launch_key(body): key = str(body.get("key", "")) - command = launch.get(key) + command, _, _ = launch_spec(launch.get(key)) if command is None: raise ModuleError("unknown launch key") _popen(["sh", "-c", command] if isinstance(command, str) else command) def state(): - return {**_state(run), "launches": list(launch)} + st = _state(run) + known = sorted(set(int(n) for n in workspaces) | set(st["workspaces"])) + launches = [] + for key, spec in launch.items(): + _, icon, label = launch_spec(spec) + launches.append({"key": key, "icon": icon, "label": label or key}) + return {**st, "workspaces": known, "launches": launches} return Module(name="windows", title="Windows", state=state, actions={"workspace": workspace, "focus": focus, "close": close, "launch": launch_key}) diff --git a/static/deck.css b/static/deck.css index 6f3fa97..1a09141 100644 --- a/static/deck.css +++ b/static/deck.css @@ -249,7 +249,19 @@ select { cursor: pointer; } .wtile.on { background: var(--udt-accent); color: var(--udt-crust); border-color: var(--udt-accent); } -.wtile.launch { font-size: 13px; font-weight: 500; min-width: 0; } +.wtile.launch { min-width: 64px; min-height: 64px; padding: 0 12px; } +.laticon { + display: block; + width: 30px; + height: 30px; + background: var(--udt-text); + -webkit-mask-repeat: no-repeat; + -webkit-mask-position: center; + -webkit-mask-size: contain; + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; +} .windows .winlist { display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px; } .wrow { display: flex; diff --git a/static/logos/firefox.svg b/static/logos/firefox.svg new file mode 100644 index 0000000..b32c6a4 --- /dev/null +++ b/static/logos/firefox.svg @@ -0,0 +1 @@ +Firefox \ No newline at end of file diff --git a/static/logos/folder.svg b/static/logos/folder.svg new file mode 100644 index 0000000..e45b3ce --- /dev/null +++ b/static/logos/folder.svg @@ -0,0 +1,15 @@ + + + + diff --git a/static/logos/obsstudio.svg b/static/logos/obsstudio.svg new file mode 100644 index 0000000..090e856 --- /dev/null +++ b/static/logos/obsstudio.svg @@ -0,0 +1 @@ +OBS Studio \ No newline at end of file diff --git a/static/logos/signal.svg b/static/logos/signal.svg new file mode 100644 index 0000000..81bc449 --- /dev/null +++ b/static/logos/signal.svg @@ -0,0 +1 @@ +Signal \ No newline at end of file diff --git a/static/logos/terminal.svg b/static/logos/terminal.svg new file mode 100644 index 0000000..f91c114 --- /dev/null +++ b/static/logos/terminal.svg @@ -0,0 +1,16 @@ + + + + + diff --git a/static/modules/windows.js b/static/modules/windows.js index 7d2d8c8..018cc6a 100644 --- a/static/modules/windows.js +++ b/static/modules/windows.js @@ -61,9 +61,21 @@ export default { const launches = el.querySelector(".launches"); launches.textContent = ""; - for (const key of st.launches ?? []) { - const b = tile(key, () => post("launch", { key })); - b.classList.add("launch"); + for (const item of st.launches ?? []) { + const b = document.createElement("button"); + b.className = "wtile launch"; + b.setAttribute("aria-label", item.label || item.key); + b.title = item.label || item.key; + if (item.icon) { + const icon = document.createElement("span"); + icon.className = "laticon"; + icon.style.webkitMaskImage = `url(/logos/${item.icon}.svg)`; + icon.style.maskImage = `url(/logos/${item.icon}.svg)`; + b.append(icon); + } else { + b.textContent = item.label || item.key; + } + b.onclick = () => post("launch", { key: item.key }); launches.append(b); } }, diff --git a/tests/test_windows.py b/tests/test_windows.py index 6aa7925..ca9be71 100644 --- a/tests/test_windows.py +++ b/tests/test_windows.py @@ -28,6 +28,21 @@ class Commands(unittest.TestCase): st = windows._state(lambda cmd: responses[tuple(cmd)]) self.assertEqual(st["workspaces"], [1, 2, 5]) + def test_state_includes_empty_workspaces_and_launch_icons(self): + responses = { + ("hyprctl", "clients", "-j"): "[]", + ("hyprctl", "activewindow", "-j"): "{}", + ("hyprctl", "workspaces", "-j"): '[{"id": 3}]', + } + m = windows.make({ + "windows": {"workspaces": [1, 2, 3, 4]}, + "launch": {"obs": {"icon": "obsstudio", "command": "obs"}}, + "run": lambda cmd: responses[tuple(cmd)], + }) + st = m.state() + self.assertEqual(st["workspaces"], [1, 2, 3, 4]) + self.assertEqual(st["launches"], [{"key": "obs", "icon": "obsstudio", "label": "obs"}]) + if __name__ == "__main__": unittest.main() -- cgit v1.2.3