# astro-menu A touch-friendly GTK4 popup control centre for the hyprlua desktop, replacing `nwg-dock` and `nwg-drawer`. Triggered from the EWW top bar (or `Super+D`). Layout: a **2×2 quad grid** of feature modules over a full-width **application drawer**. Any quad can expand to overlay the other three; the drawer can expand to the bottom of the screen. A single margined root box letterboxes the menu identically in every state. ## Stack - **Frontend:** Python + PyGObject + **GTK4**, as a `wlr-layer-shell` surface (`gtk4-layer-shell`). Chosen over Lua because lgi/Astal-Lua only support GTK3. - **Location map:** a static OSM image stitched from tiles (`backend/staticmap.py`, Pillow). libshumate does not paint tiles in this environment (the official `shumate-demo` shows the same blank map), though tile downloads work — hence the static fallback. - **Services:** Astal GObject libraries via introspection — `AstalNetwork`, `AstalBluetooth`, `AstalApps` — plus our own IP-geolocation singleton. - **Backends:** Python/Bash scripts in `backend/` (JSON on stdout), run async via `Gio.Subprocess` so nothing blocks the UI. - **Theme:** `style/_colors.css` (`@define-color`, generated from `~/Dotfiles/colors.conf` by `apply-theme.sh`) + `style/style.css`. ## Running `main.py` is single-instance. The autostart launches a hidden resident daemon; verbs are forwarded over D-Bus: scripts/astro-menu-start.sh # resident daemon (hidden); sets LD_PRELOAD scripts/menu-toggle.sh # --toggle scripts/menu-toggle.sh appdrawer # open with the app drawer expanded `astro-menu-start.sh` must `LD_PRELOAD` libgtk4-layer-shell (it loads after libwayland under PyGObject otherwise). ## Adding a module 1. Create `modules/.py` exposing a top-level `SPEC = ModuleSpec(...)` whose `build(ctx)` returns a `ModuleInstance(compact=…, expanded=…, …)`. - `compact` shows in the 2×2 cell; a distinct `expanded` widget enables the expand button (they must be separate instances — GTK widgets have one parent). - Declare per-feature toggles via `features=[Feature("id", "Label", default)]`; read them with `ctx.feature("id")`. A disabled quad never calls `build()`. - Shared state (network, bluetooth, location) is on `ctx.services`. 2. Append its `SPEC` to `ALL_SPECS` in `registry.py`. Nothing else changes. ## Files main.py app + single-instance IPC window.py layer-shell + letterbox registry.py module list (extension seam) settings.py JSON toggles/order module_base.py ModuleSpec / ModuleInstance / ModuleContext appservices.py shared Astal services + location ui/ quadcard, quadgrid, appdrawer modules/ location, weather, bluetooth, network services/ location (geolocation singleton) lib/ ansi (SGR→TextTag), proc (async subprocess) backend/ geolocate.py, weather.sh, network.sh style/ _colors.css, style.css