Dotfiles/desktopenvs/hyprdrive/astro-menu
Amir Alexander Abdelbaki 68b3f2bdd0 fix(astro-menu): stop the taskbar poll resetting the window list's scroll
The workspace/window panel couldn't be scrolled: it snapped back to the top
about once a second. Nothing to do with the hologram overlay (a DrawingArea
with can_target=False that only ever queue_draw()s itself) — it was the 1 Hz
refresh that start_polling() runs while the menu is open. Both of its
callbacks called _rebuild_panel(), which did _clear(self._panel) and rebuilt
every widget, including a fresh Gtk.ScrolledWindow for the list. Emptying
scrolled content collapses the scroll adjustment's upper bound, so its value
is clamped back to 0 and the viewport jumps to the top.

The panel scaffolding (header, layout stack + switcher, list scroller) is now
built once in _build_panel_scaffold() and refreshed in place by _update_panel:

  * The layout tabs are the same for every workspace — only the selection is
    per-workspace, and _sync_layout_controls() already refreshes that — so
    they no longer get torn down either.
  * _update_window_list() compares the sorted window addresses with the last
    set. Unchanged: row contents (title, ws N, icon) are updated in place, so
    a retitled window can't move the viewport. Changed: only the rows are
    rebuilt, and both scrollers' offsets are saved and restored on idle, once
    the new rows are allocated and the adjustment's upper is no longer stale.
  * _rebuild_strip() gets the same signature guard, so the poll stops
    resetting the compact strip's horizontal scroll and no longer tears down a
    grouped app's instance popover while it's open. It keys on addresses only:
    strip titles are tooltip/popover-only and refresh on the next real change.

Applied identically to hyprlua and hyprdrive.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 23:15:34 +02:00
..
backend feat(hyprdrive): materialize animations, dmenu mode, ship-systems astro rework 2026-07-18 17:17:48 +02:00
lib fix(astro-menu): clip card bloom so corners stop poking out of the rounded border 2026-07-22 23:13:29 +02:00
modules feat(hyprdrive): materialize animations, dmenu mode, ship-systems astro rework 2026-07-18 17:17:48 +02:00
services feat(hyprdrive): add hyprdrive DE with full Cosmonaut Shell suite 2026-07-17 15:28:07 +02:00
style feat(hyprdrive): materialize animations, dmenu mode, ship-systems astro rework 2026-07-18 17:17:48 +02:00
ui fix(astro-menu): stop the taskbar poll resetting the window list's scroll 2026-08-05 23:15:34 +02:00
.gitignore feat(hyprdrive): add hyprdrive DE with full Cosmonaut Shell suite 2026-07-17 15:28:07 +02:00
README.md feat(hyprdrive): add hyprdrive DE with full Cosmonaut Shell suite 2026-07-17 15:28:07 +02:00
appservices.py feat(hyprdrive): add hyprdrive DE with full Cosmonaut Shell suite 2026-07-17 15:28:07 +02:00
config.py feat(hyprdrive): configurable/live-polled workspace layout picker in astro-menu 2026-07-24 15:07:02 +02:00
main.py feat(hyprdrive): add hyprdrive DE with full Cosmonaut Shell suite 2026-07-17 15:28:07 +02:00
module_base.py feat(hyprdrive): add hyprdrive DE with full Cosmonaut Shell suite 2026-07-17 15:28:07 +02:00
paths.py feat(hyprdrive): add hyprdrive DE with full Cosmonaut Shell suite 2026-07-17 15:28:07 +02:00
registry.py feat(hyprdrive): materialize animations, dmenu mode, ship-systems astro rework 2026-07-18 17:17:48 +02:00
settings.py feat(hyprdrive): add hyprdrive DE with full Cosmonaut Shell suite 2026-07-17 15:28:07 +02:00
theme.py feat(hyprdrive): add hyprdrive DE with full Cosmonaut Shell suite 2026-07-17 15:28:07 +02:00
window.py feat(hyprdrive): configurable/live-polled workspace layout picker in astro-menu 2026-07-24 15:07:02 +02:00

README.md

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/<name>.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