Files
mesh-catalog/modules/dunst/README.md
T
mesh-tools 652b00b25b
mesh/merge-gate checking again: the gate refused `unknown` because the controller judged with a node-engine validator pinned before mesh-host #117; mesh-co…
mesh/repo-check checking again: the gate refused `unknown` because the controller judged with a node-engine validator pinned before mesh-host #117; mesh-co…
mesh/delivery-group group feat/owned-paths checking: 0 of 2 member(s) ready
mesh/delivery superseded: a newer head of the same pull request
Say on every directory how it treats files it does not know, and check it (issue 1024, ADR 0422)
Every directory and archive resource carries `unknown`: move-out for a home folder whose program writes nothing there (the node-engine, delivered in mesh-host #117, moves the rest to ~/.local/state/mesh-moved/<module>/ with a list), module for ~/.ssh and ~/.claude, program elsewhere; `knows` keeps what a program or the operator relies on (powerlevel10k's compiled files, i3's scripts and the asus/g14 drop-ins, dunst's slack rule, betterbird's records, node-env's forge credential, issue 1033). Two shared folders are no longer declared. Each module's README says it. checks/owned holds every manifest to it; checks/sshdir stays while main's merge check runs it (issue 1035). Tests that pin a manifest follow the new key only. Reviewed in three rounds.
2026-10-17 16:23:03 +00:00

127 lines
8.4 KiB
Markdown

# dunst
The notifier as a module (novox/hq ADR 0208, research 026/05).
- Installs `dunst`, and `libnotify` for `notify-send`, the client every program and these tools use.
The context menu its `dunstrc` calls is `rofi`, which the `rofi` module installs: two modules on one
machine may not declare the same package, so a machine dunst serves also has `rofi` assigned.
- Claims the mesh's `node-notifier` seat and serves its verbs `send` and `history`.
- Owns `~/.config/dunst/dunstrc` and the directory `~/.config/dunst/dunstrc.d/`. It holds what this
module declares and `50-slack.conf` (named in `knows`); any other file is moved out to
`~/.local/state/mesh-moved/dunst/` with a list, never deleted (ADR 0422). Another module's rule is that
module's own declared file in the directory (ADR 0208 §4). dunst reads the directory after
`dunstrc`, so a drop-in outranks it.
- **Starts nothing.** The package registers dunst with D-Bus, which starts it on the first
notification, inside the account's service manager. There is no autostart line and no
session-start contribution. The package's own `dunst.service` is declared without a state, in the
account's manager (`scope: user`, ADR 0177), only so the node-engine can reload it: when the mesh
rewrites `dunstrc`, a running dunst is told to read it again (`reload-on`, the unit's
`ExecReload=dunstctl reload`). A dunst that is not running is left alone and reads the file when the bus
starts it (ADR 0117).
- **Requires no display of its own.** dunst speaks both X11 and Wayland and picks the one the session
has, so it serves an X session and a later sway one alike.
## Answers from a notification
`send` with `actions` runs `notify-send --wait` with one `--action` per answer, answers the id at once and
waits in this bundle for the operator's choice. A tap (left click) on the notification chooses: with
one action it runs it, with several it opens rofi's menu of them, and without any it closes the
notification (dunst's `do_action, close_current`; a notification with no action but one link opens the
link). An Escape in the menu leaves the notification shown. The middle click does the same, and
**Super+N** (`dunstctl context`, this module's line in i3's configuration) opens the menu of every
notification on screen from the keyboard. The token `default` is refused, because dunst runs an action of
that name at a tap without offering the others. The token chosen is said as this module's event
`action-chosen`; it becomes the node-notifier seat's event once seats publish events of their own
(novox/hq to-be 46 phase 3). Nothing is said for a notification closed, expired or replaced without an answer, and a wait ends after seven
days. A notification that offers answers stays until dismissed (`expire_ms` 0). A restart of this bundle
ends the waits it held; it says `started` {machine} once it serves, and the operator channel shows the
open answers there again (novox/hq ADR 0258). `notify-send` prints the id before it waits, also into a
pipe (checked on the laptop on 2026-10-08: the id at once, and "Wait timeout expired" when it expired).
## What is shown once, and whether it would be seen
- **A secret notification** (`send` with `secret`: a link's code) is sent transient and in the category
`mesh.secret`, whatever category was named. The dunstrc's rules keep it, and every transient one, out of
dunst's history (`history_ignore`), and `history` leaves out any that got there anyway, counting them as
`withheld`: the history is served to every caller of the mesh's verbs, agents among them.
- **Whether notifications are paused** is said as this module's event `paused` {paused, machine}: at start,
on every change, and every two minutes, read from dunst itself so a pause set by a key counts too. The desk
shows a link's code only on a machine it heard is not paused lately.
## Tools
Every tool goes over the account's session bus. None needs the screen, and each answers clearly when
the account is not logged in.
| tool | does |
|---|---|
| `node-notifier.send` | a notification: title, body, urgency, sender, icon, how long, and the answers it offers (`actions`: token and label); answers its id, and says the chosen answer later as the event `action-chosen` {id, token, machine} |
| `node-notifier.history` | what was shown, newest first, with how long ago |
| `dunst_pause` / `dunst_resume` | do not disturb: notifications are held back, not lost |
| `dunst_close_all` | clear the screen; the history keeps them |
| `dunst_rules` | the rules the running notifier holds, and the files they come from |
| `dunst_count` | shown, waiting, in history, and whether paused |
| `dunst_reload` | read the configuration again now, keeping what is shown; answers the files read and the font, font size and width they set, with the file each came from (dunst does not say its settings over the bus, so these are read from the same files) |
## Settings
Two preferences have defaults in the definition (novox/hq ADR 0262). The mesh's layer, then a machine's,
overrides them, and the file is written with the value, never with a variable dunst reads.
| setting | default | what it is |
|---|---|---|
| `font-size` | 10 | the points of `font = Inter <size>`; 10 reads well on a screen of about 100 DPI, which is the workstations |
| `width` | 250 | the notification's width in pixels; a larger font needs a wider notification |
A machine with a denser screen sets both for itself, and the next push writes them:
mesh-controller.settings {module: "dunst", node: "<machine>", values: '{"font-size": 16, "width": 400}'}
`mesh-controller.settings {module: "dunst", node: "<machine>"}` shows each value and where it came from:
the default, the mesh, or the machine. When the push rewrites `dunstrc`, the node-engine reloads the
running dunst, so the next notification is drawn at the new size, without a login. A reload keeps the
notifications on screen and in the history; a restart would drop them, which is why this is a reload.
## What it chose, and what it improves
The workstations' files differed: one had the notifications bottom-right, 15 % transparent and with
rounded corners; the other top-right, opaque and square. This module takes the second, because the
rest of the desktop is square and opaque, and the top-right corner sits under the bar that shows the
count. The file keeps only the settings that differ from dunst's defaults.
- **The context menu works.** It called `/usr/bin/dmenu`, installed on neither machine, and then
`dmenu`, the node-launcher's command in `~/.local/bin`, which dunst did not find on the laptop
(2026-10-10): it runs in the account's service manager, whose `PATH` does not hold that directory. It now calls `rofi -dmenu`, a
system package the `rofi` module installs.
- **A tap chooses an answer.** The answers were on the middle click only, which the laptop's and the
workstation's touchpads cannot give (novox/hq issue 360).
- **The face is the interface one,** Inter (research 026/04), instead of a monospace Nerd font.
- `icon_path`, which named two directories of an icon theme that is not installed, is gone. The icon
theme is looked up recursively.
## What it keeps
- `~/.config/dunst/dunstrc.d/50-slack.conf`, the Slack rule, named in `knows`: kept until its owner exists.
It becomes the Slack module's own drop-in when there is one. Any other file in the folder is moved out.
## Migration (ADR 0182)
- The first push keeps the found `dunstrc` once, then writes the module's.
- **The desktop runs two notification daemons** because its session began before the session bus
fix (research 026/01). That ends at the next login, and nothing here starts a second one.
`dunst_count` after logging in again shows the one daemon's counts.
## Blockers
- `node-notifier` is ADR 0208's seat. Until the controller knows it, `mctl` reads the claim as
unknown.
- `dunst_rules` and the counts ask the running notifier. When none runs, the bus starts one, which
needs a session to draw on.
## Files it does not know
Each directory or archive this module declares says how it treats a file it did not write (ADR 0422, ADR 0416).
- `~/.config/dunst`: holds only what this module declares; anything else is moved out by the node-engine to `~/.local/state/mesh-moved/dunst/` with a list, never deleted (ADR 0422 §4).
- `~/.config/dunst/dunstrc.d`: holds only what this module declares and `50-slack.conf` (the Slack rule, kept until its owner exists); anything else is moved out by the node-engine to `~/.local/state/mesh-moved/dunst/` with a list, never deleted (ADR 0422 §4).