diff options
| author | Danilo M. <danix@danix.xyz> | 2026-11-09 18:11:02 +0100 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-11-09 18:11:02 +0100 |
| commit | 0b303ba68c19e38a4487de59df0fdc93c6dd48cf (patch) | |
| tree | a052ffeeb1eed0780d6f90c9a2539ae930344444 | |
| parent | 4b98da80bdfd5016ad9f5858310d4b8bacafe7bb (diff) | |
| download | deck-it-0b303ba68c19e38a4487de59df0fdc93c6dd48cf.tar.gz deck-it-0b303ba68c19e38a4487de59df0fdc93c6dd48cf.zip | |
feat: scaffold and config
| -rw-r--r-- | AGENTS.md | 14 | ||||
| -rw-r--r-- | LICENSE.tabdeck | 21 | ||||
| -rw-r--r-- | NOTICE | 8 | ||||
| -rw-r--r-- | README.md | 26 | ||||
| -rw-r--r-- | config.toml | 53 | ||||
| -rw-r--r-- | deckit/__init__.py | 1 | ||||
| -rw-r--r-- | deckit/config.py | 40 | ||||
| -rw-r--r-- | tests/test_config.py | 33 |
8 files changed, 196 insertions, 0 deletions
diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..ab4df97 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,14 @@ +# AGENTS.md + +Guidance for agents working in this repository. + +Read the design (`docs/superpowers/specs/2026-11-09-deck-it-design.md`) and the +implementation plan (`docs/superpowers/plans/2026-11-09-deck-it.md`) first. The +plan is the authority for interfaces and test values. + +- Python 3.11+, standard library only except `websocket-client` for OBS. +- Run the suite with `python3 -m unittest discover -s tests`. +- Each module is `deckit/modules/<name>.py` exporting `make(cfg) -> Module`; the + registry discovers files lazily, so a new module is a new file. +- The server listens only on a Unix socket; `adb reverse` exposes it. +- UDT's palette is read, never restated. diff --git a/LICENSE.tabdeck b/LICENSE.tabdeck new file mode 100644 index 0000000..b43e763 --- /dev/null +++ b/LICENSE.tabdeck @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Akhilesh Tiwari + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. @@ -0,0 +1,8 @@ +# NOTICE + +deck-it derives from tabdeck (https://github.com/silky-x0/tabdeck), which is +distributed under the MIT License. Its notice is reproduced in +`LICENSE.tabdeck` and must be retained in copies of the derived work. + +The command-center page pattern, the adb-reverse tunnel, the virtual-monitor +setup and the stdlib HTTP server are adapted from tabdeck. diff --git a/README.md b/README.md new file mode 100644 index 0000000..19705e0 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# deck-it + +A tablet dashboard, stream-deck, and second monitor for a Slackware-current +Hyprland desktop, themed by +[unified-desktop-theme](https://git.danix.xyz/unified-desktop-theme/). + +An Android tablet shows an always-on control surface served over `adb reverse`, +plus a virtual Hyprland monitor streamed with wayvnc and shown in AVNC. The +surface is a module system: system stats, music, windows, OBS, LLM usage and +build status plug in independently. + +Status: in development. The design is in +`docs/superpowers/specs/`, the plan in `docs/superpowers/plans/`. + +## License + +Not decided yet. Portions derived from tabdeck are MIT; see `NOTICE` and +`LICENSE.tabdeck`. + +## Development Approach + +This project is developed using AI-assisted tools. Code is generated with the help of AI based on human-provided specifications, design decisions, and iterative feedback. + +All contributions are reviewed, tested, and curated by the maintainer before being included in the codebase. AI is used as a productivity and exploration tool, while human oversight remains central to all decisions. + +The goal is to combine the flexibility of AI-assisted development with standard open-source practices such as transparency, review, and accountability. diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..9800ff0 --- /dev/null +++ b/config.toml @@ -0,0 +1,53 @@ +# deck-it configuration. +# Copy to ~/.config/deck-it/config.toml to customize; that file replaces this one entirely. + +# "auto" picks "lua" when ~/.config/hypr/hyprland.lua exists, otherwise "classic". +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" +scale = 1 +refresh = 60 +# Hyprland output name for the virtual monitor. +name = "TAB" + +[ports] +# Ports on the tablet's localhost, tunneled with adb reverse. +command_center = 8765 +display = 5900 + +[hardware] +# Each one is "auto" or a fixed value, like net = "wlan0", battery = "BAT1", +# cpu_sensor = "coretemp", gpu = "nvidia" | "amd" | "none". +net = "auto" +battery = "auto" +cpu_sensor = "auto" +gpu = "auto" + +[modules] +# A module is disabled by setting it false. A new module file under +# deckit/modules/ is picked up automatically; add a flag here only to turn it off. +stats = true +music = true +windows = true +obs = true +llm = true +sbbuilder = true +assistant = false + +[obs] +host = "127.0.0.1" +port = 4455 +# obs-websocket password (Tools > obs-websocket Settings). Empty if auth is off. +password = "" + +[llm] +# llm-cost-dashboard's backend. +url = "http://127.0.0.1:8420" + +[sbbuilder] +# ssh host alias of the build VM; the module runs `sbb-remote jobs` there. +host = "buildsystem" diff --git a/deckit/__init__.py b/deckit/__init__.py new file mode 100644 index 0000000..f5a692a --- /dev/null +++ b/deckit/__init__.py @@ -0,0 +1 @@ +"""deck-it: a tablet dashboard, stream-deck and second monitor.""" diff --git a/deckit/config.py b/deckit/config.py new file mode 100644 index 0000000..151207b --- /dev/null +++ b/deckit/config.py @@ -0,0 +1,40 @@ +"""Configuration loading and validation for deck-it.""" + +import os +import re +import sys +import tomllib +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +MONITOR_NAME = re.compile(r"[A-Za-z0-9_-]{1,32}") + + +def user_config_path() -> Path: + return Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config")) / "deck-it" / "config.toml" + + +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 _" + 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" + for key in ("command_center", "display"): + if key in ports: + v = ports[key] + if not isinstance(v, int) or not 1024 <= v <= 65535: + 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" + return None + + +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) + if err := config_error(cfg): + sys.exit(f"{path}: {err}") + return cfg diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000..ff56325 --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,33 @@ +# tests/test_config.py +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(ROOT)) + +import tomllib +import unittest + +import deckit.config as config + + +class Config(unittest.TestCase): + def test_default_config_is_valid(self): + with open(ROOT / "config.toml", "rb") as f: + self.assertIsNone(config.config_error(tomllib.load(f))) + + def test_rejects_bad_values(self): + bad = [{"display": {"name": "TAB;rm"}}, + {"display": {"refresh": 0}}, + {"ports": {"command_center": 80}}, + {"ports": {"command_center": 5900, "display": 5900}}] + for cfg in bad: + self.assertIsNotNone(config.config_error(cfg), cfg) + + 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") + + +if __name__ == "__main__": + unittest.main() |
