aboutsummaryrefslogtreecommitdiffstats
path: root/desktop
diff options
context:
space:
mode:
Diffstat (limited to 'desktop')
-rw-r--r--desktop/README.md45
-rw-r--r--desktop/island/README.md93
-rw-r--r--desktop/modules/mail/README.md67
-rw-r--r--desktop/modules/status/README.md23
4 files changed, 148 insertions, 80 deletions
diff --git a/desktop/README.md b/desktop/README.md
index f51feb4..5536d7d 100644
--- a/desktop/README.md
+++ b/desktop/README.md
@@ -1,7 +1,8 @@
# desktop
One drawer, left of DP-1, hosting the things a desktop lets you adjust.
-Reached from a launcher at the left end of waybar.
+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
@@ -89,15 +90,16 @@ while the page is closed. Pages are lazily loaded in every module.
## Geometry
-The panel is 600px wide and full height, on `DP-1`. Waybar runs there and the
-launcher sits at its left end, while conky holds the right side of the screen,
-so the drawer belongs on the left. It falls back to the first screen when
+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 claims an
-exclusive zone at the top of the screen, so respecting it puts the drawer below
-the bar without this code knowing the bar's height, and the bar stays visible
-and clickable while the drawer is open. The whole surface is an overlay so a
+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
@@ -107,12 +109,9 @@ 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 and waybar
+## Hyprland
-Nothing here is in the repo's control, and the drawer works without it. Two
-pieces of outside configuration belong to it.
-
-Frosting is the compositor's, matched on the namespace this window sets. The
+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({
@@ -125,23 +124,15 @@ drawer sets `quickshell-desktop`, so it needs:
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 any of these the surfaces still render, just flat
+matches and stays. Without these the surfaces still render, just flat
translucent.
-The waybar launcher is a static `custom` module with no `exec`, so it cannot
-show whether the drawer is open. Its `on-click` runs the zero-argument toggle:
-
- "on-click": "qs -p ~/Programming/GIT/quickshell/desktop ipc call drawer toggle"
-
-The mail module's waybar count keeps its own `custom` module, whose click
-deep-links to the mail page with `ipc call drawer open mail`. `SUPER+v` reaches
-the vm page the same way, with `ipc call drawer open vm`. The full list of
-edits to the live Hyprland and waybar configuration is Task 10 of the plan in
-`docs/superpowers/plans/2026-09-14-desktop-shell.md`.
+`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. The drawer
-replaces waybar's own network and Bluetooth indicators; removing them is a
-live-configuration change outside this repo.
+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
diff --git a/desktop/island/README.md b/desktop/island/README.md
new file mode 100644
index 0000000..2b2c9fe
--- /dev/null
+++ b/desktop/island/README.md
@@ -0,0 +1,93 @@
+# island
+
+A thin neutral bar at the top centre of `DP-1`. Point at it and two pills
+slide out of it: a clock pill on the left and a status pill on the right. It
+replaced waybar, both bars. It is not a separate shell: `desktop/shell.qml`
+instantiates it next to the drawer, so it starts with `qs -p ./desktop` and
+shares the drawer's IPC and keepalive window.
+
+## Running it
+
+ qs -p ./desktop
+
+The pure logic (volume icon thresholds, VM dot colours, flick text) lives in
+`Logic.js` and has one check, which needs only node:
+
+ node desktop/island/test_logic.js
+
+## States
+
+**Hidden.** Only the sliver shows, in `Theme.overlay`. While anything is
+capturing it takes the capture colours instead, split into equal segments, one
+per active kind: mic green, camera yellow, location blue, screen red.
+
+**Revealed.** The pointer enters the sliver and the pills slide down and apart
+in one move, driven by a single `reveal` amount, so an interrupted reveal turns
+around from wherever it is. They hide 400ms after the pointer leaves both,
+unless a panel is open.
+
+**Panel open.** Clicking the volume icon morphs the status pill into the sound
+panel (output and input sliders with mute, and an arrow to the drawer's sound
+page). An outside click or Escape closes 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.
+If the island is hidden, the clock pill alone drops out for the flick and
+retracts. Events in the first 1.5s after startup are ignored, because Hyprland
+reports its initial state as events. The desktop icons come from
+`shared/Workspaces.qml`, the same ones the window switcher uses.
+
+## Clock pill
+
+`Wed 07/10 15:07`, then one dot per VM that is running, paused, suspended,
+saved or crashed (a plainly shut-off VM gets none). States come from the
+drawer's `Virsh` service, whose lifecycle event stream runs whether the drawer
+is open or not.
+
+## Status pill
+
+Left to right: volume icon, tray, layout flag.
+
+- **Volume icon** shows the default sink (muted, low, medium, high). Click
+ opens the sound panel.
+- **Tray** shows `SystemTray` items. Left click activates, right click opens
+ the item's menu. The island is the session's StatusNotifier host. A tray
+ menu is a separate popup, so the island holds itself shown after `display()`
+ until the pointer returns.
+- **Layout flag** shows the current keyboard layout, driven by Hyprland's
+ `activelayout` event. Scroll switches layout with
+ `hyprctl switchxkblayout current next|prev`.
+
+## Privacy
+
+`privacy.sh` prints `mic cam loc scr`, each 0 or 1, and `Privacy.qml` polls it
+every 2s. It polls because Quickshell's `PwNode` exposes no running state,
+which is what tells a capturing source from an idle one, so mic and screen go
+through `pw-dump`, and camera and location through `fuser` and `pgrep`. A
+failed run prints nothing and the last reading is kept, since no dump is a
+broken run and not "nothing is capturing".
+
+## 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, so switching
+icon theme in the appearance panel changes the island. 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 idle sliver is never the accent,
+because udt can snap the accent to the same colour as a capture kind.
+
+## Blur
+
+The window sets namespace `quickshell-island`. For frosting, add to
+`~/.config/hypr/sections/decorations.lua`:
+
+ hl.layer_rule({
+ name = "blur-island",
+ match = { namespace = "^(quickshell-island)$" },
+ blur = true,
+ xray = false,
+ ignore_alpha = 0.1,
+ })
+
+Without it the island renders flat translucent.
diff --git a/desktop/modules/mail/README.md b/desktop/modules/mail/README.md
index ced87f3..b6a3159 100644
--- a/desktop/modules/mail/README.md
+++ b/desktop/modules/mail/README.md
@@ -7,8 +7,8 @@
> `blur-mail` namespace in the Theme section below is historical, since this
> page now rides the drawer's `quickshell-desktop` surface.
-Unread mail across every account, as one waybar number and a drawer behind it.
-Clicking the icon opens the drawer; Escape or a click outside closes it.
+Unread mail across every account, as a tile and a page in the drawer.
+Opening the page shows the accounts; Escape or a click outside closes it.
┌──────────────────────────────────────────────────┐
│ Mail 33 unread │
@@ -33,12 +33,12 @@ Clicking the icon opens the drawer; Escape or a click outside closes it.
qs -p ./desktop
It is started from `autostart.lua` and reached over IPC, so the shell has to be
-running for the waybar click to do anything:
+running for a deep link to do anything:
qs -p ./desktop ipc call drawer open mail
-Write that path out in full in the real config. Waybar's `exec` and `on-click`
-have no shell to expand `~`, and neither do Hyprland's Lua strings.
+Write that path out in full in the real config. Hyprland's Lua strings have no
+shell to expand `~`.
## The accounts are not listed here
@@ -77,7 +77,7 @@ account with `path:"<maildir>/**"`, and copying that query would be wrong for
this panel. notmuch deduplicates by message id, so a message that arrived at
two of the configured addresses is one message with two file paths: a glob
counts it under both accounts, and the rows then sum to more than the total the
-waybar icon shows. Measured here, five accounts summed to 102 against a global
+header shows. Measured here, five accounts summed to 102 against a global
total of 101. The tag is a property of the message, so it is singular and the
rows always sum to the header. The cost is that such a message appears under
only the account the `post-new` hook attributed it to, which is the right trade
@@ -104,35 +104,18 @@ An unknown count shows a dash, never a zero, for the same reason the VM panel
refuses to substitute libvirt's host-side figures: a number that is actually a
failure reads as a fact.
-## The waybar module
+## The count
-A `custom` module in **continuous mode**: the script never exits, prints one
-JSON line per database commit, and waybar redraws on each line. There is no
-`interval`, and no daemon to supervise, because waybar owns the process.
+The drawer's `Accounts.qml` watches the xapian directory itself, so the count
+drops the moment mail is read in qtmaildir and rises the moment mbsync commits.
+The count lives only in the drawer: the old bar module is gone with waybar.
- "custom/mail": {
- "format": "<span font='18px'>󰇮</span> {}",
- "return-type": "json",
- "exec": "~/Programming/GIT/quickshell/desktop/modules/mail/waybar-mail.sh",
- "on-click": "qs -p ~/Programming/GIT/quickshell/desktop ipc call drawer open mail",
- "on-click-right": "~/bin/mailsync.sh",
- "tooltip": false
- }
-
-The count drops the moment mail is read in qtmaildir and rises the moment
-mbsync commits, because both write the database and the watcher sees either.
-
-Two details in that loop are load-bearing. The watch is on the **xapian
-directory**, not on a file inside it: a commit replaces files, and a watch held
-on a filename dies with it. And a short sleep coalesces the several writes of
-one commit, without which the module redraws three or four times per sync with
+Two details in the watch are load-bearing. It is on the **xapian directory**,
+not on a file inside it: a commit replaces files, and a watch held on a
+filename dies with it. And a short debounce coalesces the several writes of one
+commit, without which the page redraws three or four times per sync with
intermediate counts.
-The old setup this replaces ran three copies of a python script polling the
-Gmail API with stored credentials, covered three of the five accounts, and
-opened Thunderbird. Everything it fetched over the network was already in the
-local index.
-
## Notifications on arrival
`mail-notify.sh` sends one notification per account when mail lands, with the
@@ -143,10 +126,9 @@ Started from `autostart.lua`, it runs for the whole session.
/home/you/Programming/GIT/quickshell/desktop/modules/mail/mail-notify.sh
-It watches the same xapian directory the waybar module does, for the same
-reason and with the same 0.3s debounce. It is a **separate process rather
-than part of `waybar-mail.sh`**, which already has that edge: waybar owns
-that script's process, so a bar restart would stop notifications with nothing
+It watches the same xapian directory `Accounts.qml` does, for the same reason
+and with the same 0.3s debounce. It is a **separate process rather than part of
+the shell**, so a shell restart does not stop notifications with nothing
reporting it.
**A commit is not the same as new mail.** Reading a message in qtmaildir drops
@@ -171,7 +153,7 @@ from the old one means nothing.
revision and notifying nothing. Without that floor, `lastmod:0..` matches every
unread message ever: 101 of them here, which is a wall of popups at every
login. The same applies to a restart, so mail that arrived while it was down is
-never notified. The waybar count is still right and the drawer still shows it,
+never notified. The drawer count is still right and still shows it,
so nothing is lost but a stale popup.
The notification is sent with dunstify. The icon is passed as an **absolute
@@ -197,10 +179,12 @@ anchored to its top right, which is the idiom the vm and appearance panels
both use and what gives click-outside-to-close and a focusable item for Escape.
It differs from those two in one property: `exclusionMode` is `Normal`, not
-`Ignore`. Waybar claims an exclusive zone at the top of this screen, so
-respecting it places the overlay below the bar with no height constant here to
-drift. Measured: waybar at `y=-540 h=42`, this overlay at `y=-498 h=1038`,
-starting exactly where the bar ends. The backdrop therefore never dims waybar.
+`Ignore`. Waybar claimed an exclusive zone at the top of this screen, so
+respecting it placed the overlay below the bar with no height constant here to
+drift. Measured then: waybar at `y=-540 h=42`, this overlay at `y=-498 h=1038`,
+starting exactly where the bar ended. Waybar is gone, so the overlay now starts
+at the top edge of the screen; `Normal` stays, so a bar added later is still
+respected.
**Key events reach a focused item, not a window.** Setting
`WlrLayershell.keyboardFocus` is necessary but not sufficient, and
@@ -219,8 +203,7 @@ possibly running client.
"Open qtmaildir" launches the client. There is no "Sync now" button: the
watcher triggers a sync the moment mail lands, the cron tick is the backstop,
-and `on-click-right` on the waybar module runs `~/bin/mailsync.sh` for a manual
-pull.
+and `~/bin/mailsync.sh` is a manual pull.
## The watcher status dot
diff --git a/desktop/modules/status/README.md b/desktop/modules/status/README.md
index e435d94..b25505f 100644
--- a/desktop/modules/status/README.md
+++ b/desktop/modules/status/README.md
@@ -28,7 +28,7 @@ mechanism, which is why it keeps working while quickshell is down.
statusctl <mode> get prints 0 or 1
statusctl <mode> set 0|1
statusctl <mode> toggle
- statusctl <mode> watch waybar JSON on every change
+ statusctl <mode> watch JSON on every change (no consumer now)
The repo copy is the source; `~/bin/statusctl` is a symlink to it. `watch` watches
the directory rather than the file, because an atomic write replaces the file
@@ -70,8 +70,8 @@ The traffic runs both ways, and only one way writes.
$XDG_RUNTIME_DIR/breaktimer.remain seconds left in the phase
exposed as `Status.btState`, `btPhase` and `btRemain`, with `btRunning` and
-`btPaused` derived from the first. `waybar-breaktimer.sh` reads the same three
-files and the two consumers do not know about each other.
+`btPaused` derived from the first. The old bar script read the same three
+files; the two consumers never knew about each other.
**Writing: never.** The daemon owns those files and rewrites state and phase on
every transition, so a second writer would race its loop. Every control calls a
@@ -83,7 +83,7 @@ end the daemon write `stopped` there: `stop_daemon`, and the `cleanup` trap on
`TERM`. A daemon lost to `KILL` leaves a stale `running` and the drawer shows a
frozen countdown, a visible wrong answer the Start button resolves, which is
cheaper than a liveness probe on every repaint. QML cannot send a signal, so
-the `kill -0` check the waybar module uses is not available here anyway.
+the `kill -0` check the old bar module used is not available here anyway.
**The countdown counts in five second steps**, because that is the daemon's
tick and the shell does not interpolate between its writes. A local one second
@@ -123,16 +123,17 @@ runs, since `status.gaming` holds it on until the game exits.
`status.gaming` is never set by `statusctl`; the detector owns it. The effects
are unchanged, because they key off the combined `presentation` value.
-## Waybar
+## The watch (no consumer now)
-`custom/presentation` reads `statusctl presentation watch`. It replaces
-waybar's built-in `idle_inhibitor`, which cannot be kept alongside it: that
-module owns its own inhibitor object, so both would have to be released
-before the screen could lock.
+`statusctl presentation watch` fed waybar's `custom/presentation` module, which
+replaced waybar's built-in `idle_inhibitor`. Waybar is gone, so the watch has
+no consumer; the command stays. Presentation is toggled from the drawer's
+status page, and later from the control panel. The island shows no presentation
+state at rest.
The watch emits three classes, not two. `status.gaming` turns the mode on
-without touching the manual half, so a game would otherwise leave the widget
-reading `deactivated` while the effects were asserted. `gaming` is its own
+without touching the manual half, so a bar widget would otherwise read
+`deactivated` while the effects were asserted. `gaming` is its own
class with its own icon, and `toggle` is refused while it holds: the detector
reasserts the file within its poll, so the write would not stick. `set` is
still allowed, since the manual half is worth setting for when the game exits.