aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-11-10 10:03:00 +0100
committerDanilo M. <danix@danix.xyz>2026-11-10 10:03:00 +0100
commit819745a39220b49e848422b4bf0ee71dc317fc4d (patch)
tree17475ac554a44eec1f9adee45ad0be3a437509d3 /tests
parent5ebfb5850713c038e37baddbadde3320aca2c4e4 (diff)
downloaddeck-it-819745a39220b49e848422b4bf0ee71dc317fc4d.tar.gz
deck-it-819745a39220b49e848422b4bf0ee71dc317fc4d.zip
feat: dashboard-only mode and keystone settings integration
The second display is now optional: [display] enabled = false skips the headless output, wayvnc, AVNC and the VNC tunnel, so the tablet runs the dashboard alone. `deck-it env` reports it as EXTEND. Keystone's new deck-it page (quickshell, desktop/modules/deckit) edits the config and the autostart line, which needed: - The user config is merged over the shipped config.toml key by key within each table instead of replacing it whole, so the page writes a sparse file and an unset key falls back to the default. - `deck-it defaults` prints the shipped config as JSON for the page's placeholders, so keystone never copies the defaults. - `deck-it status` prints one JSON line (server, display, waiting, tablet, extend) for the page's status line and restart check. - [launch] is now `apps`, a list of desktop ids run with gtk-launch, with name and icon read from the .desktop file, so the page can pick launchers from the installed apps. The old command tables are gone; the client still sends only an id, which must be listed and installed. A launcher whose icon the theme lacks shows its name. - [wifi] address gives the tablet's address in config. The last address that worked is kept in ~/.config/deck-it/last-wifi: `stop` deletes the runtime serial file, so a restart from the page used to wait forever. - `stop` also ends a login's `wifi --wait`, matched by its own command line only, so repeated restarts do not pile up waiters. Also fixed: `deck-it env` fell through into the usage text, and the usage text printed the license header since the header was added. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'tests')
-rw-r--r--tests/test_config.py10
-rw-r--r--tests/test_launcher.py2
-rw-r--r--tests/test_windows.py34
3 files changed, 36 insertions, 10 deletions
diff --git a/tests/test_config.py b/tests/test_config.py
index 879917c..928a8e8 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -35,11 +35,19 @@ class Config(unittest.TestCase):
def test_rejects_bad_values(self):
bad = [{"display": {"name": "TAB;rm"}},
{"display": {"refresh": 0}},
+ {"display": {"enabled": "no"}},
{"ports": {"command_center": 80}},
- {"ports": {"command_center": 5900, "display": 5900}}]
+ {"ports": {"command_center": 5900, "display": 5900}},
+ {"wifi": {"address": "x; rm -rf ~"}},
+ {"launch": {"apps": ["ok", "../evil"]}}]
for cfg in bad:
self.assertIsNotNone(config.config_error(cfg), cfg)
+ def test_user_file_overrides_key_by_key(self):
+ cfg = config.merge({"display": {"enabled": True, "scale": 1}, "launch": {"apps": ["a", "b"]}},
+ {"display": {"enabled": False}, "launch": {"apps": ["c"]}})
+ self.assertEqual(cfg, {"display": {"enabled": False, "scale": 1}, "launch": {"apps": ["c"]}})
+
def test_user_config_path_honours_xdg(self):
self.assertEqual(config.user_config_path().name, "config.toml")
self.assertEqual(config.user_config_path().parent.name, "deck-it")
diff --git a/tests/test_launcher.py b/tests/test_launcher.py
index 0c16b46..06e5724 100644
--- a/tests/test_launcher.py
+++ b/tests/test_launcher.py
@@ -28,7 +28,7 @@ class Launcher(unittest.TestCase):
def test_env_prints_keys(self):
out = subprocess.run(["bash", str(ROOT / "deck-it"), "env"],
capture_output=True, text=True, cwd=ROOT).stdout
- for key in ("MON=", "PORT=", "VNC_PORT="):
+ for key in ("MON=", "PORT=", "VNC_PORT=", "EXTEND="):
self.assertIn(key, out)
diff --git a/tests/test_windows.py b/tests/test_windows.py
index e52e23d..e632406 100644
--- a/tests/test_windows.py
+++ b/tests/test_windows.py
@@ -31,10 +31,12 @@ class DesktopIcons(unittest.TestCase):
from pathlib import Path
base = Path(tempfile.mkdtemp())
(base / "com.obsproject.Studio.desktop").write_text(
- "[Desktop Entry]\nIcon=com.obsproject.Studio\nStartupWMClass=obs\n")
- mapping = windows._desktop_icons([base])
+ "[Desktop Entry]\nName=OBS Studio\nIcon=com.obsproject.Studio\nStartupWMClass=obs\n"
+ "[Desktop Action x]\nName=Other\n")
+ mapping = windows._desktop_icons(windows._desktop_entries([base]))
self.assertEqual(mapping["com.obsproject.studio"], ["com.obsproject.Studio", "obs"])
self.assertEqual(mapping["obs"], ["com.obsproject.Studio", "obs"])
+ self.assertEqual(windows._desktop_entries([base])["com.obsproject.Studio"]["name"], "OBS Studio")
class Move(unittest.TestCase):
@@ -86,14 +88,30 @@ class Commands(unittest.TestCase):
("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)],
- })
+ entries = {"com.obsproject.Studio": {"name": "OBS Studio", "icon": "obs", "wmclass": None}}
+ with mock.patch.object(windows, "_desktop_entries", lambda: entries):
+ m = windows.make({
+ "windows": {"workspaces": [1, 2, 3, 4]},
+ "launch": {"apps": ["com.obsproject.Studio", "not-installed"]},
+ "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"}])
+ self.assertEqual(st["launches"],
+ [{"key": "com.obsproject.Studio", "icon": "obs", "label": "OBS Studio"}])
+
+ def test_launch_runs_only_listed_installed_apps(self):
+ entries = {"kitty": {"name": "kitty", "icon": "kitty", "wmclass": None},
+ "other": {"name": "Other", "icon": None, "wmclass": None}}
+ calls = []
+ with mock.patch.object(windows, "_desktop_entries", lambda: entries):
+ m = windows.make({"launch": {"apps": ["kitty", "gone"]}, "run": lambda cmd: "[]"})
+ with mock.patch.object(windows, "_popen", calls.append):
+ m.actions["launch"]({"key": "kitty"})
+ for bad in ("other", "gone", "kitty; rm"):
+ with self.assertRaises(windows.ModuleError):
+ m.actions["launch"]({"key": bad})
+ self.assertEqual(calls, [["gtk-launch", "kitty"]])
if __name__ == "__main__":