# desktop One drawer, left of DP-1, hosting the things a desktop lets you adjust. Reached over IPC (`ipc call drawer toggle`), from a Hyprland bind. The same process also hosts the island, see `island/README.md`. The drawer opens as a grid of tiles, and clicking a tile either opens that module's page or fires an action. A module's back arrow, and Escape while a page is showing, return to the grid; Escape on the grid, or a click outside, closes the drawer. It takes keyboard focus while it is open, so those keys reach it. ## Running it qs -p ./desktop qs -p ./desktop ipc call drawer toggle # the grid qs -p ./desktop ipc call drawer open mail # straight to a page `open` always takes a page name, because quickshell 0.3.1 IPC requires every declared argument to be present and a defaulted parameter registers as a type the IPC layer rejects. The page-less grid entry is therefore the zero-argument `toggle`. `ipc call drawer close` closes it. ## The modules modules/sound/ output and input volume, the OSD, the player modules/network/ wired and wifi, radio, scan, join, forget modules/bluetooth/ adapter, scan, pair, connect, forget, trust modules/kdeconnect/ devices, battery, pairing, ping, clipboard, share, mount modules/mail/ unread per account, threads, the watcher dot modules/vm/ libvirt state, live stats, snapshots modules/appearance/ a tile that opens the separate appearance shell modules/ai/ local AI engines, start/stop for the apps using them Sound, mail, vm, network, bluetooth, kdeconnect and ai each carry their own README. Appearance is a tile onto the separate appearance shell, whose README covers the picker it opens. ## Writing a module A module is a `Module` (from `Module.qml` in this directory) declaring what the drawer needs to show it: - `name`: identifies the module, both for grid ordering and for IPC, where it is the page name `ipc call drawer open ` takes. - `icon` and `label`: the glyph and the text on the tile. - `tileContent`: a `Component` for the short state line under the label, or null for a tile that says nothing beyond its name. - `page`: a `Component` for the full-height page behind the tile, or null. - `activate()`: what a tile with no page does when clicked. - `alwaysActive`: declares whether the module's service is meant to run while the drawer is closed. The drawer does not read it, the module implements its own lifetime. Covered below. A module provides a tile, a page, both, or neither. The drawer does not care which: it reads those properties and does the obvious thing. A page is wrapped in `Page.qml` for the header and back arrow, and a tile with no page is fire-and-forget, so its click calls `activate()` and closes the drawer. `modules/appearance/AppearanceModule.qml` is the smallest complete example: a name, an icon, a label, and an `activate()` that launches the separate appearance shell. It has no page, and it owns only a `Process` that fires the picker on click, no always-active service. Sound, mail, vm, network, bluetooth and kdeconnect each add a service and a page on top of that same shape. Module files live under `desktop/modules//` and reference root types (`Module`, `Page`, `Button`, `Theme`), so each needs `import "../.."`. A plain directory import does not reach the parent. The moved `TransportButton.qml` and `Stat.qml` gained that import for the same reason. ## alwaysActive `alwaysActive` is declarative metadata: it documents whether a module's service is meant to run while the drawer is closed. The drawer does not read or enforce it. Each module implements its own service lifetime, and the property records that choice rather than driving it. Sound, mail, network and bluetooth are `alwaysActive: true`; vm and kdeconnect are false. Sound's OSD has to answer a volume keypress with no drawer open, which is the whole point of it, so its PipeWire service is instantiated eagerly and runs for the session. Mail's unread count outlives the drawer and its config watcher makes a newly added account appear without a restart, so its service is instantiated eagerly too. The vm module's 2s stats poll exists only to paint a page nobody is looking at, so it starts and stops with the page. vm is not only its poll, however: its lifecycle event stream runs unconditionally, which is what keeps the VM list and the tile's dots current while the page is closed. Pages are lazily loaded in every module. ## Geometry The panel is 600px wide and full height, on `DP-1`. Conky holds the right side of the screen, so the drawer belongs on the left. It falls back to the first screen when `DP-1` is not connected, so the drawer is never invisible. The window uses `ExclusionMode.Normal`, not `Ignore`. Waybar used to claim an exclusive zone at the top of the screen, and respecting it put the drawer below the bar without this code knowing the bar's height. Waybar is gone, so the drawer now starts at the top edge of `DP-1`; `Normal` stays so a bar added later is respected. The island is an overlay with no exclusive zone, so it floats over the drawer's top edge. The whole surface is an overlay so a click outside closes it. The top of the panel is an empty reserved area for the notification engine. It is an empty `Item` claiming the space, not a placeholder graphic, so the grid below it sits where it will sit once notifications arrive. The bottom is a fixed, never-scrolled `Flow` grid: three columns at 180px minimum, wrapping and adding rows up to a 3x3 ceiling for the modules that exist, in the order Sound, Network, Bluetooth, KDE Connect, Mail, Appearance, Machines. ## Hyprland Nothing here is in the repo's control, and the drawer works without it. Frosting is the compositor's, matched on the namespace this window sets. The drawer sets `quickshell-desktop`, so it needs: hl.layer_rule({ name = "blur-desktop", match = { namespace = "^(quickshell-desktop)$" }, blur = true, xray = false, ignore_alpha = 0.1, }) The sound module's OSD owns its own window and keeps its old `quickshell-volume-osd` namespace, so the existing `blur-volume-osd` rule still matches and stays. Without these the surfaces still render, just flat translucent. `SUPER+v` reaches the vm page with `ipc call drawer open vm`, and the drawer toggles with `ipc call drawer toggle`. The island's sound panel opens the drawer's sound page through the same `show` the IPC uses. The drawer is the only place network and Bluetooth are managed. Waybar's own indicators for both went with waybar. The drawer is the only KDE Connect surface, so the `kdeconnect-indicator` autostart line was removed in the same change. The `kdeconnectd` daemon is a separate system autostart and stays; the phone-to-PC clipboard is the daemon's, not the indicator's. ## Theme `Theme.qml` is a symlink to `shared/Theme.qml`, as in every component here.