Commit Graph

25 Commits (main)

Author SHA1 Message Date
Amir Alexander Abdelbaki dc0436136b fix(astal-menu): fix layout-tab sync race across workspace switches
_sync_layout_controls() fires 3 concurrent hyprctl calls, and refresh()
can trigger it twice per open (once from the clients query, once from
the activeworkspace one) — each rebuilding a fresh set of layout
widgets. Two bugs followed:

1. The 3 callbacks each toggled the shared self._syncing flag
   independently (True at start, False at end). Whichever resolved
   first dropped the guard while the others — notably the tab-
   selection one, which programmatically flips the visible stack
   child — were still in flight. That let _on_tab_switch/_on_opt_change
   misfire as if the user had clicked, re-applying stale layout data
   via layouts.set() to whatever workspace was active by the time the
   callback landed.

2. A second _sync_layout_controls() call (from the redundant rebuild)
   could land mid-flight of the first, so a stale callback from sync
   #1 could decrement sync #2's guard or write sync #1's data into
   sync #2's freshly built widgets.

Fixed with a generation token: any sync superseded by a newer one is
invalidated outright, and self._syncing only lifts once the *current*
generation's 3 calls have all resolved. This is what "query the
current workspace's layout when opened, so it doesn't break switching
workspaces" needed — refresh() already re-queried on open, the gap was
in trusting stale in-flight results.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TLz6HWsXCwzQ97LrLt2em6
2026-07-09 16:48:42 +02:00
Amir Alexander Abdelbaki 84420733bb adjustments for astal+ewwbar 2026-07-08 21:56:51 +02:00
Amir Alexander Abdelbaki ed44b84afa feat(hyprlua/columns): center-focused toggle, mirroring scrolling's fit method
Adds a "center-focused" switch to the Columns layout's menu tab: when a column
overflows and scrolls, it can now center the focused window in the column
instead of only nudging the pan the minimum amount to keep it in view. Off by
default so existing behavior is unchanged unless explicitly enabled.

The astal-menu switch widget already existed generically (built for scrolling's
native focus_fit_method) but was hard-wired to that one Hyprland option; it's
now layout-aware and reads/writes columns.lua's own persisted state instead.
2026-07-08 13:35:47 +02:00
Amir Alexander Abdelbaki 4c747221f2 style(astal-menu): unfullscreen glyphs, animated takeover, CyberQueer TUIs
- Expand/collapse controls now use consistent fullscreen ()/unfullscreen ()
  glyphs: the quad "Back" and the taskbar-panel "Back" became  buttons (matching
  the appdrawer's - toggle).
- The taskbar panel takeover now plays the same bouncy quad-pop scale as the quads
  when it expands, so every expansion in the menu is animated.
- monitor-manager (curses) and timer-pick (bash TUI) now use the CyberQueer accent
  palette (red #E40046 + violet #5018dd, true-colour with ANSI fallback) instead of
  cyan/green, matching the rest of the config.
- Added a .stepper-value style for the Columns count stepper.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SUN7gg6GGfnToghMijLm5Y
2026-07-07 13:43:11 +02:00
Amir Alexander Abdelbaki de666acac7 feat(astal-menu): Columns count stepper + expanded taskbar is one module
- Columns layout publishes per-workspace column/row counts to columns-state.json
  and handles a direct "cols +1/-1/N" message. The menu's Columns page now shows a
  "Columns  (−)[N](+)" stepper that reads the live count and drives it.
- Expanding the taskbar now fully collapses the strip (header included) via its
  revealer, so the panel that covers the quads reads as a single module instead of a
  thin strip plus a detached card below it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SUN7gg6GGfnToghMijLm5Y
2026-07-07 13:31:45 +02:00
Amir Alexander Abdelbaki 6631405570 feat(hyprlua): taskbar quad-takeover, columns layout, screen-absolute nav
astal-menu: the taskbar ⤢ toggle now collapses the compact strip and mounts
its workspace/window panel as a takeover overlay covering the 2x2 quads (with
a Back button), instead of a popover/inline reveal. QuadGrid gains a generic
takeover overlay; the menu window coordinates show/hide and resets on close.

hypr: new "Columns" layout (scrolling backend presented as independent columns)
with a menu dropdown to pick orientation — "Left / Right" or "Up / Down" — via a
new dir_labels manifest field. The layout menu now tracks direction dropdowns and
fit switches per-layout, so a second directional layout no longer clobbers the
first.

binds/scripts: focus & window-move keys (hjkl + arrows) route through a new
hypr-nav helper so they stay screen-absolute in the scrolling/columns layouts
(the layout otherwise resolves a perpendicular direction onto "scroll the tape",
rotating the keys with scrolling:direction). Adds explicit cross-monitor binds:
Super+,/. focus prev/next monitor, Super+Shift+,/. move the window there.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SUN7gg6GGfnToghMijLm5Y
2026-07-07 09:28:41 +02:00
Amir Alexander Abdelbaki 4196b84ad3 feat(astal-menu): tabbed workspace-layout selector
Replace the layout dropdown with a tab bar (one tab per layout — Dwindle · Master ·
Monocle · Scrolling, from the manifest) over a Gtk.Stack of per-layout options that
switches with the tab. Selecting a tab applies that layout to the currently focused
workspace; the scrolling page carries its options — a "direction" dropdown and a
"center-focused" switch (focus_fit_method). The active tab reflects the workspace's
real (per-ws) layout via the layouts state file.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XUWCXM4KhjRkwheaA3X7bP
2026-07-06 11:59:47 +02:00
Amir Alexander Abdelbaki fa097da436 feat(hyprlua): per-file layout registry (default scrolling↓); menu layout+window controls
- hypr/layouts/: drop-in layout registry. init.lua auto-discovers layouts/<name>.lua
  (each self-describing) and exposes the global `layouts` table: set_default (picked
  in hyprland.lua — now scrolling, direction down), set(ws,name,dir) for live
  per-workspace switching (verified it re-tiles the active ws), set_fit(0/1). Writes
  a manifest + per-ws state for the menu. Ships scrolling/dwindle/master/monocle.
- Scrolling default: column_width 0.5 (active column < viewport so prev/next stay
  clickable), focus_fit_method 1 (centered follow), fullscreen_on_one_column.
- astal-menu taskbar pop-open: workspace layout selector + direction + a "Centered"
  toggle (focus_fit_method), and a per-window list — [icon+title → jump][⇤ here →
  pull to current workspace], keeping jump-to-window intact.
- eww bar menu buttons open the menu explicitly from the top (menu-toggle.sh toggle top).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XUWCXM4KhjRkwheaA3X7bP
2026-07-06 11:52:46 +02:00
Amir Alexander Abdelbaki 4fe6181a93 made the hw-stats bar taller, so the close button doesn't touch any borders 2026-07-06 10:12:13 +02:00
Amir Alexander Abdelbaki 57dede403e feat(astal-menu): stats line, side-slide flag, drawer pop, tighter buttons
- Add a minimalist system-stats line at the top of the panel: CPU / MEM / GPU / disk
  utilisation + network down/up rate, read straight from /proc and /sys on a 2s timer
  (no subprocess). ui/statsbar.py.
- Opening side is selectable: `main.py --side top|bottom|left|right` (also via
  `menu-toggle.sh <verb> <side>` or just `menu-toggle.sh <side>`). The panel anchors
  flush to that edge, and the compositor's layer slide brings it in from there.
  Unified all layer anchoring into MenuWindow._apply_anchor.
- App drawer now does a bouncy pop-expand (CSS scale, play-once guard like the quads).
- Tighten the action-pill buttons vertically (padding 6->1px, min-height 32->22) so
  they stop wasting vertical space.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XUWCXM4KhjRkwheaA3X7bP
2026-07-06 09:26:37 +02:00
Amir Alexander Abdelbaki e63c99f299 feat(astal-menu): pannable map with night/satellite tiles
The Location map was a fixed static image with no way to move the viewport, on the
light OSM tiles. Now:
- Drag to pan (re-renders at the new centre; the pin stays anchored to the real
  location and clamps to the edge when panned away), scroll to zoom, double-click to
  recentre. Panning/zooming coalesce into ~11 renders/sec.
- Tiles default to the dark CartoDB "night" map (fits the theme); a new "Satellite
  view" feature toggle switches to Esri World Imagery. staticmap.py gained a style
  arg + a separate marker coordinate, and caches tiles per style.

Verified: dark + satellite render, drag re-centres the view (pin goes off-centre),
and the coordinate round-trip is exact.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XUWCXM4KhjRkwheaA3X7bP
2026-07-06 08:59:05 +02:00
Amir Alexander Abdelbaki abea1bec43 feat(astal-menu): Bluetooth connect status + Forget button
- Connect flow now shows real status: a spinner + Cancel while connecting, and a red
  "failed" tag + Retry if the attempt stops without linking (via notify::connecting
  going false) or a 25s timeout backstop fires. Previously Connect gave no feedback
  and could look stuck forever.
- Add a Forget button on paired devices (adapter.remove_device) to unpair them.
- Track per-device pending/failed state so the row reflects connecting/failed/
  connected/paired accurately.

Verified the paired row renders Forget + Connect and the connected row renders
Forget + Disconnect.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XUWCXM4KhjRkwheaA3X7bP
2026-07-06 08:43:13 +02:00
Amir Alexander Abdelbaki e79d427c2a fix(astal-menu): refresh Bluetooth list on device connection changes
The device rows already render a "Disconnect" button when a device is connected,
but the list only rebuilt on adapter-level notify::devices / notify::is-powered —
not on per-device state changes. So a device connected (via the menu or elsewhere)
while the menu was open kept its "Connect" button and offered no way to disconnect.
Hook each device's notify::connected/connecting/paired (once each) to refresh the
list, so a connected device flips to "Disconnect".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XUWCXM4KhjRkwheaA3X7bP
2026-07-06 00:17:35 +02:00
Amir Alexander Abdelbaki 221d0dde55 fix(astal-menu): keep toggle switches pill-shaped (valign center)
Switches sat with the default FILL vertical alignment, so in taller rows (e.g. the
Bluetooth Power switch in the expanded card's header) they stretched to the row
height and rendered as a squared/vertical blob. Give every Gtk.Switch
valign=CENTER so it keeps its fixed pill height regardless of the row.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XUWCXM4KhjRkwheaA3X7bP
2026-07-06 00:12:16 +02:00
Amir Alexander Abdelbaki 0e8cdd5335 fix(astal-menu): play the quad-expand pop once, not on a loop
The .quad-pop CSS animation stayed applied for the whole time a quad was expanded,
so every re-layout of the expanded card (module content streaming in, the compact
grid updating beneath the overlay) restarted the transform animation — making the
pop repeat continuously. Remove the class ~340ms after expanding (just past the
300ms animation) so it plays exactly once and no later re-layout can retrigger it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XUWCXM4KhjRkwheaA3X7bP
2026-07-06 00:06:18 +02:00
Amir Alexander Abdelbaki 6a5a5bce97 feat(astal-menu): bouncy slide-in on open, springy pop on quad expand
- Main open: enable Hyprland's `layers` animation with the bouncy myBezier curve and
  a slide style, so the layer-shell control centre slides in from the top edge with a
  little overshoot instead of just appearing. (Also gives notifications/bar the same
  springy slide, which suits the DE.) A per-namespace layerrule was tried first but
  this hyprlua build doesn't honour its animation timing.
- Quad expand: a CSS @keyframes scale-bounce (scale 0.82 -> 1.05 -> 1.0) added to the
  expanded card on expand and cleared on collapse so it replays every time. Verified
  GTK4 CSS transforms/keyframes render on this build, and that the card scales during
  the animation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XUWCXM4KhjRkwheaA3X7bP
2026-07-06 00:00:14 +02:00
Amir Alexander Abdelbaki 3aafd2cc17 fix(astal-menu): working favourites, 3-day weather, deploy-safe settings
Favourites:
- Pin/unpin from the drawer never worked: the right-click / long-press gestures
  were added to a Gtk.Button in the default (bubble) phase, where the button's own
  primary-click gesture swallowed them. Move them to the CAPTURE phase and claim the
  sequence (shared add_pin_gestures helper) so they fire reliably and don't also
  launch the app.
- Show a ★ on pinned tiles and re-render tiles when favourites change, so the state
  is visible and updates live.
- Verified end-to-end (right-click pins, right-click again unpins).

Weather: the expanded quad showed current conditions only. weather.sh used
`${2:-0}`, which rewrote the expanded view's intentionally-empty opts to "0"
(current only); use `${2-0}` so the empty value reaches wttr.in as its default
3-day forecast. Verified the expanded view now renders all three days.

Settings: move settings.json from CONFIG_DIR to XDG_STATE_HOME. The config-updater
`rm -rf`s ~/.config/astal-menu on every deploy, which was wiping pinned favourites
and quad toggles; the state dir is untouched by config sync. Migrates the old file.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XUWCXM4KhjRkwheaA3X7bP
2026-07-05 23:35:23 +02:00
Amir Alexander Abdelbaki 16f75edb13 style(astal-menu): fixed-shape switches, inset close button, shorter panel, uniform expand icons
- Switches: give them a fixed pill with a bordered round knob (the reset theme
  left them shapeless).
- Close (x) button: inset with a margin so it no longer overlaps the drawn border.
- Height: the panel overflowed the monitor. Trim the map compact height, quad
  min-height, card padding, grid/panel spacing, top margin, and drawer strip so it
  fits (≈1600 -> ≈1310 logical on a 1440 display).
- App drawer expand button: use the same nf-fa-expand/compress glyphs as the module
  expand buttons instead of chevrons, so all expand controls look uniform.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XUWCXM4KhjRkwheaA3X7bP
2026-07-05 20:48:52 +02:00
Amir Alexander Abdelbaki d468d66e5f feat(astal-menu): NetworkManager control panel; single-ring settings/close buttons
Network expanded view is rewritten into four tabs over a new nmcli backend
(backend/nm.py, JSON in/out):
- Adapters: each device foldout with editable IPv4 and IPv6 — Automatic/DHCP
  toggle, address, a linked subnet-mask <-> CIDR pair (edit either, the other
  follows), gateway; applied via `nmcli connection modify/up`. Manual fields dim
  when DHCP is on.
- Routes: the kernel routing table (read-only; editing kernel routes needs root).
- VLAN: pick a parent device + ID to create/bring up a VLAN, list + activate/delete.
- DNS: effective servers per device + a per-connection override (servers +
  ignore-automatic).
Verified reads and the write path end-to-end (VLAN add/delete via nmcli; each tab
rendered in a harness).

Also: flatten the settings-cog MenuButton's inner `button` node so it shows one
ring instead of two, and drop the border on the close (x) button.

Weather keeps its wttr.in 3-day forecast (unchanged, per request).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XUWCXM4KhjRkwheaA3X7bP
2026-07-05 20:32:39 +02:00
Amir Alexander Abdelbaki 4e5ee4f9ba fix(astal-menu): single module border; unblock rfkill so Bluetooth powers on
- Borders: with the app stylesheet now above the theme, the CSS `.quad-card`
  border rendered a second ring concentric with the Cairo bordered() ring. Drop the
  CSS border/background/radius from the card boxes so only the Cairo card shows.
- Bluetooth: the power/scan/connect controls were correctly wired to AstalBluetooth,
  but the adapter was rfkill soft-blocked, making set_powered a silent no-op. The
  power toggle now runs `rfkill unblock bluetooth` before powering on.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XUWCXM4KhjRkwheaA3X7bP
2026-07-05 20:11:36 +02:00
Amir Alexander Abdelbaki 503b7f3336 fix(astal-menu): float modules on transparent gaps (beat theme's global bg)
The panel showed a solid #1a1a1a slab between modules. Root cause: the CyberQueer
GTK theme is installed as ~/.config/gtk-4.0/gtk.css (a symlink), which GTK4 loads
at PRIORITY_USER (800) — above our PRIORITY_APPLICATION (600) — and its reset rule
`* { background-color: #1a1a1a }` painted every node, including the structural
containers between modules. Our transparency overrides silently lost the cascade.

Fixes:
- theme.py: load the app stylesheets at USER+1 so they sit just above the
  user-level theme symlink and actually win.
- style.css: blank the structural container nodes (window/.panel/overlay/revealer/
  grid/scrolledwindow/viewport/drawingarea) so the desktop shows between modules.
- window.py/quadgrid.py: draw each module's card background with the Cairo
  bordered(fill_bg=True) so cards stay opaque (incl. rounded corners) while the
  gaps go transparent — this GTK build doesn't paint CSS backgrounds on containers,
  hence the Cairo fill.

Popovers/buttons keep their backgrounds (the theme's rule still covers those nodes;
only structural containers are overridden). Verified visually end-to-end.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XUWCXM4KhjRkwheaA3X7bP
2026-07-05 19:51:54 +02:00
Amir Alexander Abdelbaki 76194980cb fix(astal-menu): focus windows via hyprlua dispatcher, not native focuswindow
The Open-windows taskbar dispatched `hyprctl dispatch focuswindow address:…`,
but this is a hyprlua setup where `hyprctl dispatch` evaluates its argument as
Lua — so the native dispatcher syntax raised a Lua parse error and nothing
happened. Switch to `hl.dsp.focus({ window = "address:…" })`, the hyprlua focus
dispatcher, which focuses the window and switches to its workspace. Verified it
moves across workspaces end-to-end.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XUWCXM4KhjRkwheaA3X7bP
2026-07-05 19:22:37 +02:00
Amir Alexander Abdelbaki b9e80fbfbe fix(astal-menu): stop LD_PRELOAD leaking into launched apps (Firefox crash)
astal-menu-start.sh LD_PRELOADs libgtk4-layer-shell so it loads before
libwayland-client. That preload is only needed at the daemon's own exec — the
library is resident afterwards and the variable is never re-read — but every app
started via AstalApps.launch() inherited it, and Firefox aborts at startup with
libgtk4-layer-shell preloaded (gdk_display_manager_get() before gtk_init()).

main.py now drops LD_PRELOAD from its environment right after startup, so
launched GUI apps (and backend subprocesses) run with a clean env. Verified the
layer-shell library stays mapped and the menu surface still maps afterwards.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XUWCXM4KhjRkwheaA3X7bP
2026-07-05 19:14:16 +02:00
Amir Alexander Abdelbaki 277f04c65d fix(astal-menu): live-apply widget settings, float modules, IP-locate via traceroute
Widget settings popover toggles now take effect immediately. QuadCard only
rebuilt a card when its Enabled state flipped, so per-module feature toggles
(Wi-Fi, discovery, routes, CLI art, …) were persisted but never applied. The
card now rebuilds whenever any of its own feature values change, and the
incomplete partial per-module refresh callbacks (which also leaked a dead
subscription per rebuild) are dropped in favour of that single path.

Make the backmost surface transparent so each module floats on its own drawn
border/background: force `window`/`.background`/`.panel` transparent, since a
plain `.menu-window` rule did not override the GTK theme's solid window node.

Locate via IP now works and uses traceroute: geolocate.py traceroutes to
1.1.1.1, takes the first globally-routable hop (the ISP egress) and resolves it
through a public geolocation API, falling back to self-IP when traceroute is
missing or finds no public hop. The ip_locate toggle gates the lookup entirely
(placeholder when off). Adds `traceroute` to the hyprlua package list.

Autostart: `sleep 1 && hyprctl reload` after all spawns so layer-shell clients
settle on the final config.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XUWCXM4KhjRkwheaA3X7bP
2026-07-05 17:02:40 +02:00
Amir Alexander Abdelbaki 3d75182b54 feat(hyprlua): add astal-menu popup, replacing nwg-dock + nwg-drawer
A touch-friendly GTK4 popup control centre (Python + PyGObject, wlr-layer-shell)
launched from the EWW bar or Super+D. Replaces nwg-dock and nwg-drawer.

Layout: a floating, drawn-bordered panel (anchored top-centre so it never blocks
the rest of the screen) with a 2x2 quad grid over a full-width app drawer, plus a
top taskbar. Any quad expands over the others; the drawer expands to the bottom.

Modules:
- Location  — static OSM map (backend/staticmap.py) on IP geolocation
              (libshumate won't paint tiles in this env; shumate-demo is blank too)
- Weather   — wttr.in ANSI art rendered via an SGR parser into a TextView
- Bluetooth — AstalBluetooth: discovery/connect/disconnect + local history
- Network   — AstalNetwork wifi + nmcli/ip/ss for IP, DHCP/manual, routes, ports,
              public IP; per-feature toggles
- Taskbar   — open windows from hyprctl, grouped by app with pop-out per instance
- Favorites — pinned apps (right-click/long-press to pin) atop the app drawer

Notes:
- Frontend is Python/GTK4, not Lua: lgi/Astal-Lua are GTK3-only.
- Module borders are drawn with a Cairo overlay (lib/border.py); this GTK build's
  renderer skips CSS border/background on plain container widgets.
- Consumes Astal GObject libs (io/apps/network/bluetooth) via introspection.

Wiring: autostart + Super+D/Super+Shift+A binds, EWW launcher button in all three
bar variants, apply-theme.sh (_colors.css), installer packages, config-updater.
drawer.sh moved to scripts/deprecated/.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 00:11:39 +02:00