# 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 ` ` 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 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 and which broke when the same keyboard moved from its 2.4GHz dongle to USB. The main keyboard is resolved again on each switch and on `activelayout`, never cached, because plugging the other connection changes which device is main. ## 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 `) recolours the island live; icon theme switch changes its icons - waybar not running, nothing missing from the list above