diff options
| author | Danilo M. <danix@danix.xyz> | 2026-11-10 10:03:00 +0100 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-11-10 10:03:00 +0100 |
| commit | 819745a39220b49e848422b4bf0ee71dc317fc4d (patch) | |
| tree | 17475ac554a44eec1f9adee45ad0be3a437509d3 /deckit/config.py | |
| parent | 5ebfb5850713c038e37baddbadde3320aca2c4e4 (diff) | |
| download | deck-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 'deckit/config.py')
| -rw-r--r-- | deckit/config.py | 32 |
1 files changed, 29 insertions, 3 deletions
diff --git a/deckit/config.py b/deckit/config.py index aac5ad9..e20bc00 100644 --- a/deckit/config.py +++ b/deckit/config.py @@ -24,6 +24,8 @@ from pathlib import Path ROOT = Path(__file__).resolve().parent.parent MONITOR_NAME = re.compile(r"[A-Za-z0-9_-]{1,32}") +WIFI_ADDR = re.compile(r"[A-Za-z0-9.-]{1,253}:[0-9]{1,5}") +DESKTOP_ID = re.compile(r"[A-Za-z0-9._-]{1,255}") def user_config_path() -> Path: @@ -34,6 +36,8 @@ def config_error(cfg: dict) -> str | None: d, ports = cfg.get("display", {}), cfg.get("ports", {}) if not MONITOR_NAME.fullmatch(str(d.get("name", "TAB"))): return "display.name must be letters, digits, - or _" + if not isinstance(d.get("enabled", True), bool): + return "display.enabled must be true or false" refresh = d.get("refresh", 60) if not isinstance(refresh, int) or not 1 <= refresh <= 240: return "display.refresh must be a number from 1 to 240" @@ -44,13 +48,35 @@ def config_error(cfg: dict) -> str | None: return f"ports.{key} must be a number from 1024 to 65535" if ports.get("command_center", 8765) == ports.get("display", 5900): return "ports.command_center and ports.display must differ" + addr = cfg.get("wifi", {}).get("address", "") + if addr and not (isinstance(addr, str) and WIFI_ADDR.fullmatch(addr)): + return "wifi.address must be host:port, like 192.0.2.10:37099" + apps = cfg.get("launch", {}).get("apps", []) + if not isinstance(apps, list) or not all(isinstance(a, str) and DESKTOP_ID.fullmatch(a) for a in apps): + return "launch.apps must be a list of desktop ids, like \"kitty\"" return None +def merge(base: dict, over: dict) -> dict: + """The user's file over the defaults, key by key within each table.""" + out = dict(base) + for key, value in over.items(): + both = isinstance(value, dict) and isinstance(base.get(key), dict) + out[key] = {**base[key], **value} if both else value + return out + + +def defaults() -> dict: + with open(ROOT / "config.toml", "rb") as f: + return tomllib.load(f) + + def load_config() -> dict: - path = user_config_path() if user_config_path().is_file() else ROOT / "config.toml" - with open(path, "rb") as f: - cfg = tomllib.load(f) + cfg, path = defaults(), ROOT / "config.toml" + if user_config_path().is_file(): + path = user_config_path() + with open(path, "rb") as f: + cfg = merge(cfg, tomllib.load(f)) if err := config_error(cfg): sys.exit(f"{path}: {err}") return cfg |
