diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-07 18:33:24 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-07 18:33:24 +0200 |
| commit | 897ce3ba988d4606e4b456a3a40cb852d1963fb0 (patch) | |
| tree | 7d21e87805facb8b844d83c55bc92126a6f60e3f /docs/superpowers/specs/2026-10-07-island-core-design.md | |
| parent | d0ebda2fb2576544fecb99c5dd1375231ff6514b (diff) | |
| download | quickshell-897ce3ba988d4606e4b456a3a40cb852d1963fb0.tar.gz quickshell-897ce3ba988d4606e4b456a3a40cb852d1963fb0.zip | |
docs(spec): island core design
Sub-project 1 of the move toward a Modesty-style integrated desktop: a
top-centre island on DP-1 hidden behind a sliver, a clock pill and a status
pill that slide out of it, a sound panel, and the removal of waybar.
Records the decisions taken while brainstorming: the island floats over
windows with no exclusive zone, privacy capture recolours the sliver rather
than revealing the pills, the clock pill flicks the desktop icon, app icon and
app name on workspace change and app open but not on focus change, the volume
icon has no gestures beyond opening its panel, and the flag images move into
this repo. Modesty is GPLv3, so only its ideas are used.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'docs/superpowers/specs/2026-10-07-island-core-design.md')
| -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 |
