aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--NOTICE7
-rw-r--r--config.toml21
-rw-r--r--deckit/modules/windows.py16
-rw-r--r--static/deck.css14
-rw-r--r--static/logos/firefox.svg1
-rw-r--r--static/logos/folder.svg15
-rw-r--r--static/logos/obsstudio.svg1
-rw-r--r--static/logos/signal.svg1
-rw-r--r--static/logos/terminal.svg16
-rw-r--r--static/modules/windows.js18
-rw-r--r--tests/test_windows.py15
11 files changed, 110 insertions, 15 deletions
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 @@
+<svg fill="#FF7139" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Firefox</title><path d="M20.452 3.445a11.002 11.002 0 00-2.482-1.908C16.944.997 15.098.093 12.477.032c-.734-.017-1.457.03-2.174.144-.72.114-1.398.292-2.118.56-1.017.377-1.996.975-2.574 1.554.583-.349 1.476-.733 2.55-.992a10.083 10.083 0 013.729-.167c2.341.34 4.178 1.381 5.48 2.625a8.066 8.066 0 011.298 1.587c1.468 2.382 1.33 5.376.184 7.142-.85 1.312-2.67 2.544-4.37 2.53-.583-.023-1.438-.152-2.25-.566-2.629-1.343-3.021-4.688-1.118-6.306-.632-.136-1.82.13-2.646 1.363-.742 1.107-.7 2.816-.242 4.028a6.473 6.473 0 01-.59-1.895 7.695 7.695 0 01.416-3.845A8.212 8.212 0 019.45 5.399c.896-1.069 1.908-1.72 2.75-2.005-.54-.471-1.411-.738-2.421-.767C8.31 2.583 6.327 3.061 4.7 4.41a8.148 8.148 0 00-1.976 2.414c-.455.836-.691 1.659-.697 1.678.122-1.445.704-2.994 1.248-4.055-.79.413-1.827 1.668-2.41 3.042C.095 9.37-.2 11.608.14 13.989c.966 5.668 5.9 9.982 11.843 9.982C18.62 23.971 24 18.591 24 11.956a11.93 11.93 0 00-3.548-8.511z"/></svg> \ 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 @@
+<!-- @license lucide-static v0.460.0 - ISC -->
+<svg
+ class="lucide lucide-folder"
+ xmlns="http://www.w3.org/2000/svg"
+ width="24"
+ height="24"
+ viewBox="0 0 24 24"
+ fill="none"
+ stroke="currentColor"
+ stroke-width="1.5"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+>
+ <path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" />
+</svg>
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 @@
+<svg fill="#302E31" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>OBS Studio</title><path d="M12,24C5.383,24,0,18.617,0,12S5.383,0,12,0s12,5.383,12,12S18.617,24,12,24z M12,1.109 C5.995,1.109,1.11,5.995,1.11,12C1.11,18.005,5.995,22.89,12,22.89S22.89,18.005,22.89,12C22.89,5.995,18.005,1.109,12,1.109z M6.182,5.99c0.352-1.698,1.503-3.229,3.05-3.996c-0.269,0.273-0.595,0.483-0.844,0.78c-1.02,1.1-1.48,2.692-1.199,4.156 c0.355,2.235,2.455,4.06,4.732,4.028c1.765,0.079,3.485-0.937,4.348-2.468c1.848,0.063,3.645,1.017,4.7,2.548 c0.54,0.799,0.962,1.736,0.991,2.711c-0.342-1.295-1.202-2.446-2.375-3.095c-1.135-0.639-2.529-0.802-3.772-0.425 c-1.56,0.448-2.849,1.723-3.293,3.293c-0.377,1.25-0.216,2.628,0.377,3.772c-0.825,1.429-2.315,2.449-3.932,2.756 c-1.244,0.261-2.551,0.059-3.709-0.464c1.036,0.302,2.161,0.355,3.191-0.011c1.381-0.457,2.522-1.567,3.024-2.935 c0.556-1.49,0.345-3.261-0.591-4.54c-0.7-1.007-1.803-1.717-3.002-1.969c-0.38-0.068-0.764-0.098-1.148-0.134 c-0.611-1.231-0.834-2.66-0.528-3.996L6.182,5.99z"/></svg> \ 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 @@
+<svg fill="#3B45FD" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Signal</title><path d="M12 0q-.934 0-1.83.139l.17 1.111a11 11 0 0 1 3.32 0l.172-1.111A12 12 0 0 0 12 0M9.152.34A12 12 0 0 0 5.77 1.742l.584.961a10.8 10.8 0 0 1 3.066-1.27zm5.696 0-.268 1.094a10.8 10.8 0 0 1 3.066 1.27l.584-.962A12 12 0 0 0 14.848.34M12 2.25a9.75 9.75 0 0 0-8.539 14.459c.074.134.1.292.064.441l-1.013 4.338 4.338-1.013a.62.62 0 0 1 .441.064A9.7 9.7 0 0 0 12 21.75c5.385 0 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25m-7.092.068a12 12 0 0 0-2.59 2.59l.909.664a11 11 0 0 1 2.345-2.345zm14.184 0-.664.909a11 11 0 0 1 2.345 2.345l.909-.664a12 12 0 0 0-2.59-2.59M1.742 5.77A12 12 0 0 0 .34 9.152l1.094.268a10.8 10.8 0 0 1 1.269-3.066zm20.516 0-.961.584a10.8 10.8 0 0 1 1.27 3.066l1.093-.268a12 12 0 0 0-1.402-3.383M.138 10.168A12 12 0 0 0 0 12q0 .934.139 1.83l1.111-.17A11 11 0 0 1 1.125 12q0-.848.125-1.66zm23.723.002-1.111.17q.125.812.125 1.66c0 .848-.042 1.12-.125 1.66l1.111.172a12.1 12.1 0 0 0 0-3.662M1.434 14.58l-1.094.268a12 12 0 0 0 .96 2.591l-.265 1.14 1.096.255.36-1.539-.188-.365a10.8 10.8 0 0 1-.87-2.35m21.133 0a10.8 10.8 0 0 1-1.27 3.067l.962.584a12 12 0 0 0 1.402-3.383zm-1.793 3.848a11 11 0 0 1-2.345 2.345l.664.909a12 12 0 0 0 2.59-2.59zm-19.959 1.1L.357 21.48a1.8 1.8 0 0 0 2.162 2.161l1.954-.455-.256-1.095-1.953.455a.675.675 0 0 1-.81-.81l.454-1.954zm16.832 1.769a10.8 10.8 0 0 1-3.066 1.27l.268 1.093a12 12 0 0 0 3.382-1.402zm-10.94.213-1.54.36.256 1.095 1.139-.266c.814.415 1.683.74 2.591.961l.268-1.094a10.8 10.8 0 0 1-2.35-.869zm3.634 1.24-.172 1.111a12.1 12.1 0 0 0 3.662 0l-.17-1.111q-.812.125-1.66.125a11 11 0 0 1-1.66-.125"/></svg> \ 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 @@
+<!-- @license lucide-static v0.460.0 - ISC -->
+<svg
+ class="lucide lucide-terminal"
+ xmlns="http://www.w3.org/2000/svg"
+ width="24"
+ height="24"
+ viewBox="0 0 24 24"
+ fill="none"
+ stroke="currentColor"
+ stroke-width="1.5"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+>
+ <polyline points="4 17 10 11 4 5" />
+ <line x1="12" x2="20" y1="19" y2="19" />
+</svg>
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()