diff options
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/superpowers/specs/2026-10-07-island-core-design.md | 213 |
1 files changed, 213 insertions, 0 deletions
diff --git a/docs/superpowers/specs/2026-10-07-island-core-design.md b/docs/superpowers/specs/2026-10-07-island-core-design.md new file mode 100644 index 0000000..79828ce --- /dev/null +++ b/docs/superpowers/specs/2026-10-07-island-core-design.md @@ -0,0 +1,213 @@ +# Island core design + +A top-centre island on DP-1, hidden behind a thin sliver, with two pills that +slide out of it: a clock pill and a status pill. It replaces waybar, both +bars, which is removed when this ships. + +This is sub-project 1 of a roadmap that moves the desktop toward the +integrated model of [Modesty](https://github.com/sandeepbist/Modesty). Modesty +is GPLv3 and this repo is GPLv2-only, so its ideas and layouts are borrowed +and none of its code. Later sub-projects, each with its own spec: preferences +store and small control panel (2), calendar with full CRUD (3), performance +group from conky (4), expose on Alt+Tab (5), drawer as the large control +center (6). + +## Where it lives + +The island is part of the `desktop/` process, next to the drawer. The repo is +moving to a hybrid model: `desktop/` hosts the parts that need to talk to each +other (island, control panel, drawer), while window-switcher, keybinds, +notifications and assistant stay separate shells. `desktop/shell.qml` +instantiates the island; it gets the drawer's IPC and keepalive window for +free. + + desktop/island/ + Island.qml the window, the sliver, reveal and hide + Capsule.qml one animated pill: rest content or an open panel + ClockPill.qml date, time, VM dots, the flick + StatusPill.qml volume icon, tray, layout flag + SoundPanel.qml the volume panel opened from the status pill + Privacy.qml singleton: what is capturing right now + Keyboard.qml singleton: current layout, switching + assets/ flag-it.svg, burger.svg (copied from waybar-theme-udt) + shared/Workspaces.qml + the workspace icon map, moved out of + window-switcher/Windows.qml and linked from both + +`Workspaces.qml` is a symlinked shared singleton like `Theme.qml`, for the +same reason: a singleton outside a config directory needs a `qmldir`, a +symlink does not. Remember hot reload does not follow symlinks (AGENTS.md): +after editing it, force a reload of both components. + +## Theming + +Colours come only from `Theme`, which follows the unified-desktop-theme +palette live. Icons are freedesktop names resolved with +`Quickshell.iconPath(name, true)` through the active icon theme, the theme the +appearance Icons tab sets, so switching icon theme changes the island like it +changed waybar. Nerd Font glyphs, in `Theme.iconFamily`, only where no named +icon exists. The island defines no colour and no icon theme of its own. + +## The window + +One `PanelWindow` on DP-1, layer Overlay, namespace `quickshell-island`, +`ExclusionMode.Ignore`: it floats over windows and reserves no space, so +there is nothing to special-case for fullscreen. It is anchored top, full +width, and tall enough for the largest panel. **Its size never changes.** +Resizing a layer surface while animating causes configure churn; instead the +pills animate inside the fixed surface and the input `mask` follows them. + +The mask is the union of the sliver's hover strip and the live rectangle of +each visible capsule, bound to their animated geometry, so the clickable area +tracks the pills frame by frame and everything else on the surface is +click-through. + +Keyboard focus is `OnDemand` while a panel is open, `None` otherwise. Escape +is handled on a focused item inside the panel, not on the window (AGENTS.md: +key events reach a focused item). + +A blur rule for namespace `quickshell-island` goes in +`~/.config/hypr/sections/decorations.lua`; without it the island renders flat +translucent and still works. + +## States + +**Hidden.** Only the sliver shows: a short, thin bar at top centre in +`Theme.accent`. While anything captures, the sliver takes the capture colours +instead, split into equal segments, one per active kind: + +| Kind | Colour | Source | +| --- | --- | --- | +| mic | `Theme.green` | a running PipeWire `Audio/Source` node with a `Stream/Input/Audio` client | +| camera | `Theme.yellow` | a process holding `/dev/video*` open | +| location | `Theme.blue` | geoclue running | +| screen | `Theme.red` | a PipeWire node whose `media.name` matches `xdph-streaming` or `gsr-default` | + +The detection rules are those of `waybar-theme-udt/bin/privacy_dots.sh`. Mic +and screen use `Quickshell.Services.Pipewire` instead of `pw-dump`; camera and +location have no binding and stay as a short poll. + +**Revealed.** The pointer enters the sliver's strip. The two pills slide down +and split apart from it in one continuous move: they start overlapping at the +sliver, travel down and outward, and settle side by side with a gap, clock on +the left, status on the right. The motion is one animation driven by a single +`reveal` amount from 0 to 1, so an interrupted reveal reverses smoothly from +wherever it is. + +**Hide.** 400ms after the pointer leaves both pills, unless a panel is open. +Re-entering cancels it. + +**Panel open.** A pill morphs into a larger capsule holding its panel, and +the other pill slides aside. Closed by an outside click (`HyprlandFocusGrab` +`onCleared`) or Escape. The island hides after the panel closes if the pointer +is not over it. + +**Flick.** On a workspace change or an app opening, and not on a plain focus +change, the clock pill shows `<desktop icon> <app icon> <app name>` for 1.5s +and then returns to its rest content. If the island is hidden, the clock pill +alone drops out of the sliver for the flick and retracts afterwards. A new +event during a flick restarts its timer. Events in the first 1.5s after +startup are ignored, because Hyprland reports the initial state as events. + +- Desktop icon: the workspace icon from `Workspaces.qml`, the same eight icons + waybar draws and window-switcher's cards use. A named workspace, or one past + eight, falls back to its number. +- App icon and name: the window focused on that workspace after the switch, + or the window that just opened. An empty workspace shows the desktop icon + and number only. App icon by `appId` through `Quickshell.iconPath`. +- Sources: `Hyprland.rawEvent`, `workspacev2` and `openwindow`. + +## Clock pill + +Rest: `Wed 07/10 15:07`, waybar's two formats (`%a %d/%m`, `%H:%M`) from +`SystemClock`. When at least one VM is running, one dot per VM follows the +time, coloured by state as waybar's `vms_dots.sh` did: + +| State | Colour | +| --- | --- | +| running | `Theme.green` | +| paused, idle | `Theme.yellow` | +| pmsuspended | `Theme.accent` | +| saved (managed save) | `Theme.blue` | +| crashed | `Theme.red` | + +Shut-off VMs get no dot, and with no VM running there are no dots at all. +States come from `desktop/modules/vm/Virsh.qml`, whose lifecycle event stream +already runs whether the drawer is open or not. + +Click: nothing in this sub-project. The calendar panel is sub-project 3. + +## Status pill + +Rest: volume icon, tray, layout flag, left to right. + +**Volume icon.** Shows the default sink's state: `audio-volume-muted`, `-low`, +`-medium` or `-high` by thresholds of 33 and 66. Click opens the sound panel. +No scroll, middle-click or right-click: the icon shows state and is the way +into the sound panel, and from there to the drawer. The volume OSD is +unchanged and separate. + +**Sound panel.** Output slider and mute, input slider and mute, and an arrow +that opens the drawer's sound page (`drawer.show("sound")`) and closes the +panel. Values come from `desktop/modules/sound/Service.qml`, which already +binds the default sink and source, tracks them with `PwObjectTracker`, and +handles PipeWire's pre-`ready` values. No device pickers and no per-app +volume here; those are on the drawer page. + +**Tray.** `SystemTray` items with their own icons. Left click activates, right +click opens the item's menu (`QsMenuAnchor`), as a normal desktop tray. No +panel. Removing waybar would otherwise leave the session with no +StatusNotifier host. + +**Layout flag.** The flag for the current layout: `flag-it.svg` for Italian, +`burger.svg` for English, copied into `assets/`. Scroll switches layout with +`hyprctl switchxkblayout <device> next|prev`; click does nothing. The current +layout comes from Hyprland's `activelayout` event rather than a 2s poll. The +keyboard is the one Hyprland marks `main`, not a hardcoded device name, which +`wb-lang` used. + +## Removing waybar + +Done when the island is verified, in this order: + +1. Remove the waybar line from `~/.config/hypr/sections/autostart.lua`. It is + the live config, outside this repo: confirm with the user before editing. +2. Drop `killall -SIGUSR2 waybar` from `appearance/Icons.qml` and from + `unified-desktop-theme/install.sh`. The icon switch still reruns + `udt-accent`; the waybar `install.sh` call goes too. +3. Delete `desktop/modules/mail/waybar-mail.sh`. The drawer now carries the + only unread count, kept live by its own watcher (commit d0ebda2). +4. The drawer uses `ExclusionMode.Normal` to sit below waybar's exclusive + zone. With no waybar the zone is gone: check where the drawer now sits and + adjust. +5. AGENTS.md: rewrite "What this is" for the hybrid model, add the island to + the component list, add any new gotchas found while building it. Mail + README: drop the waybar wiring section. + +Waybar-theme-udt itself is left alone; whether to archive it is the user's +call. + +## Verification + +Agent side, following AGENTS.md: `qmllint` on new files, then the user +reloads (`./reload.sh desktop`) and the agent reads the log for errors and +checks `pgrep -cx qs` did not grow. + +User side, visual, on DP-1: + +- sliver visible; pointer on it reveals both pills with the split motion; + leaving hides after the delay; brushing past does not flicker +- switch workspace with the island hidden: clock pill drops out with desktop + icon, app icon, app name, then retracts +- open an app: same flick +- `pw-record /dev/null` running: sliver turns green; stops, back to accent +- start a VM: dot appears, green; pause it: yellow +- volume icon matches mute and level; click opens the sound panel; sliders + and mutes work; arrow opens the drawer sound page; Escape and outside click + close the panel +- tray icons present, menus open +- scroll on the flag switches layout and the flag follows; switching with the + keybind also updates the flag +- palette change (`udt-accent <wallpaper>`) recolours the island live; icon + theme switch changes its icons +- waybar not running, nothing missing from the list above |
