aboutsummaryrefslogtreecommitdiffstats
path: root/desktop
diff options
context:
space:
mode:
Diffstat (limited to 'desktop')
-rw-r--r--desktop/Drawer.qml20
-rw-r--r--desktop/Module.qml7
-rw-r--r--desktop/README.md30
-rw-r--r--desktop/modules/mail/README.md6
-rwxr-xr-xdesktop/modules/mail/waybar-mail.sh2
-rw-r--r--desktop/modules/vm/README.md23
-rw-r--r--desktop/modules/vm/VmPage.qml4
7 files changed, 51 insertions, 41 deletions
diff --git a/desktop/Drawer.qml b/desktop/Drawer.qml
index 368f249..f3b8e6d 100644
--- a/desktop/Drawer.qml
+++ b/desktop/Drawer.qml
@@ -41,7 +41,7 @@ Scope {
}
function show(name) {
- root.page = name ?? "";
+ root.page = root.modules.some(m => m.name === name) ? name : "";
root.open = true;
}
@@ -94,15 +94,6 @@ Scope {
// Keys reach a focused item, never the window: setting
// keyboardFocus above is necessary but not sufficient, and
// Keys.onEscapePressed on a PanelWindow never fires. See AGENTS.md.
- Item {
- anchors.fill: parent
- focus: true
- Keys.onEscapePressed: {
- if (root.page) root.page = "";
- else root.close();
- }
- }
-
Rectangle {
id: panel
anchors { top: parent.top; left: parent.left; bottom: parent.bottom }
@@ -113,6 +104,15 @@ Scope {
border.width: 1
border.color: Qt.alpha(Theme.text, 0.12)
+ // The panel is that focused item. As the ancestor of the page
+ // content, a key pressed in any focused descendant bubbles up
+ // to it.
+ focus: true
+ Keys.onEscapePressed: {
+ if (root.page) root.page = "";
+ else root.close();
+ }
+
// Clicks on the panel must not reach the catcher behind it.
MouseArea { anchors.fill: parent }
diff --git a/desktop/Module.qml b/desktop/Module.qml
index 645c736..eabe434 100644
--- a/desktop/Module.qml
+++ b/desktop/Module.qml
@@ -30,10 +30,9 @@ QtObject {
// Label under the icon. Defaults to the name, capitalised.
property string label: name.charAt(0).toUpperCase() + name.slice(1)
- // Whether this module's background service runs while the drawer is
- // closed. The drawer is closed most of the time, so this is what decides
- // whether the shell is cheap to run all session. It governs the service
- // only: pages are lazily loaded either way.
+ // Declarative metadata: whether this module's service is meant to run
+ // while the drawer is closed. The drawer does not read or enforce it, the
+ // module implements its own lifetime. Pages are lazily loaded either way.
property bool alwaysActive: false
// Rendered inside the tile, below the icon: a short state line. Null for
diff --git a/desktop/README.md b/desktop/README.md
index e443ca0..29223b7 100644
--- a/desktop/README.md
+++ b/desktop/README.md
@@ -43,8 +43,9 @@ drawer needs to show it:
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`: whether the module's background service runs while the
- drawer is closed, covered below.
+- `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
@@ -53,7 +54,8 @@ 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 no service. Sound, mail and vm each add a
+appearance shell. It has no page, and it owns only a `Process` that fires the
+picker on click, no always-active service. Sound, mail and vm each add a
service and a page on top of that same shape.
Module files live under `desktop/modules/<name>/` and reference root types
@@ -63,20 +65,22 @@ directory import does not reach the parent. The moved `TransportButton.qml` and
## alwaysActive
-Sound and mail are `alwaysActive: true`; vm is false. The property governs the
-module's background service, not its page: pages are lazily loaded either way.
+`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 and mail are `alwaysActive: true`; vm
+is 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 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 runs 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.
+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.
-That last line is not the whole story for vm: its lifecycle event stream runs
+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. `alwaysActive` gates the poll, not everything the
-module does.
+while the page is closed. Pages are lazily loaded in every module.
## Geometry
diff --git a/desktop/modules/mail/README.md b/desktop/modules/mail/README.md
index 8ae2bd2..92e4dab 100644
--- a/desktop/modules/mail/README.md
+++ b/desktop/modules/mail/README.md
@@ -244,9 +244,9 @@ is not worth a timer.
## Theme and blur
-`Theme.qml` is the shared one: the palette comes from
-`~/.cache/wal/udt-palette.qml` and is watched. It is a fourth identical copy,
-which is a known loose end, not a palette to grow.
+`Theme.qml` is a symlink to `shared/Theme.qml`: the palette comes from
+`~/.cache/wal/udt-palette.qml` and is watched. Sharing one file is what keeps
+the palette from drifting into per-component copies.
Frosting is Hyprland's, matched on this window's namespace:
diff --git a/desktop/modules/mail/waybar-mail.sh b/desktop/modules/mail/waybar-mail.sh
index 4942f54..207a86e 100755
--- a/desktop/modules/mail/waybar-mail.sh
+++ b/desktop/modules/mail/waybar-mail.sh
@@ -16,7 +16,7 @@
# which is why there is no daemon to supervise and no interval to tune.
#
# The count is the total across every account. A per account breakdown is the
-# drawer's job; see modules/mail/README.md.
+# drawer's job; see desktop/modules/mail/README.md.
set -u
diff --git a/desktop/modules/vm/README.md b/desktop/modules/vm/README.md
index 79c60f5..fbb7bde 100644
--- a/desktop/modules/vm/README.md
+++ b/desktop/modules/vm/README.md
@@ -38,10 +38,9 @@ Write those two paths out in full in the real config: `exec_cmd` runs the
command directly, with no shell to expand `~`.
The drawer opens on the monitor named by the `monitor` property at the top of
-`VmPanel.qml`, defaulting to `DP-3`, and falls back to the first screen when
-that one is not connected. It dims the rest of that monitor, because the
-secondary screen usually has a real window on it, and takes keyboard focus so
-Escape closes it and a delete can be confirmed by typing.
+`Drawer.qml`, defaulting to `DP-1`, and falls back to the first screen when
+that one is not connected. It takes keyboard focus so Escape closes it and a
+delete can be confirmed by typing.
## Where the numbers come from
@@ -73,9 +72,11 @@ for the whole session, panel open or not. Starting a VM from `virsh` or
virt-manager updates the drawer, and opening it shows current state rather
than whatever was true last time.
-Statistics do need sampling, on a 2 second timer, but only while the drawer
-is open: `Virsh.sampling` follows the panel's `open`. A closed panel costs one
-idle process waiting on an event socket.
+Statistics do need sampling, on a 2 second timer, but only while the vm page
+is constructed: `Virsh.sampling` follows the page, set in the page's
+`Component.onCompleted` and cleared in `Component.onDestruction`. The page is
+lazily loaded, so a closed page costs one idle process waiting on an event
+socket.
## Two things that bite
@@ -87,9 +88,11 @@ the key then has nothing to talk to and silently does nothing.
**Key events reach an item, not a window.** Setting `keyboardFocus` on the
layer shell is necessary but not sufficient: `Keys.onEscapePressed` on the
-PanelWindow itself never fires, because no item inside holds focus. A filled
-`Item` with `focus: true` catches it, and takes focus back when the TextInput
-in a delete confirmation gives it up.
+PanelWindow itself never fires, because no item inside holds focus. The panel
+rectangle is focused as the ancestor of the page content, so a key from any
+focused descendant bubbles up to it. The delete confirmation's `TextInput`
+also handles Escape itself, cancelling the pending confirmation rather than
+navigating away.
## Destructive actions
diff --git a/desktop/modules/vm/VmPage.qml b/desktop/modules/vm/VmPage.qml
index 5cb4dc7..9fca5f5 100644
--- a/desktop/modules/vm/VmPage.qml
+++ b/desktop/modules/vm/VmPage.qml
@@ -318,6 +318,10 @@ Column {
focus: needsTyping
Component.onCompleted: if (needsTyping) forceActiveFocus()
+ // Escape cancels the pending confirmation rather than
+ // navigating away from the page; this handler consumes the key.
+ Keys.onEscapePressed: { page.confirming = null; page.typed = ""; }
+
Rectangle {
anchors.fill: parent
anchors.margins: -6