1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
|
# Notification settings page
A settings window page for the notification daemon (`notifyd`, separate repo)
and the balloon shell (`notifications/`). Today nothing is configurable: the
daemon hardcodes its timeouts and caps, the balloon shell hardcodes DP-1
bottom-right, and DND always passes critical only.
Snooze is out of scope: the status tile already has a snooze switch with a
remembered minutes field.
## One file, three readers
`~/.config/notifyd/config.json`. A missing file, a missing key, or a value of
the wrong type reads as the default below, so the defaults reproduce today's
behaviour exactly.
```json
{
"timeouts": { "low": 10, "normal": 10, "critical": 0 },
"liveCap": 20,
"historyCap": 20,
"balloons": { "screen": "DP-1", "corner": "bottom-right" },
"dndPassCritical": true,
"muted": []
}
```
- `timeouts` are seconds, used only when the client sends `expire_timeout -1`.
`0` means never expires. A client's explicit timeout still wins, as the
freedesktop spec requires.
- `liveCap`, `historyCap`: 1..100. Out of range clamps.
- `balloons.screen`: a monitor name. An unknown name falls back to the first
screen, the existing pattern.
- `balloons.corner`: `top-left`, `top-right`, `bottom-left`, `bottom-right`.
- `dndPassCritical`: under DND, whether critical balloons still show. Low and
normal are always withheld under DND.
- `muted`: app names, matched exactly against the queue entry's `app`. A muted
app gets no balloon; it still appears in the drawer's reserved space and in
history (suppression lives in the balloon shell, see `notifications/README.md`).
Readers:
| reader | keys | picks up a change |
| --- | --- | --- |
| notifyd | `timeouts`, `liveCap`, `historyCap` | `notifyctl reload` |
| balloon shell | `balloons`, `dndPassCritical`, `muted` | watched `FileView` |
| settings page | all | watched `FileView` |
The daemon ignores keys it does not use, and the shells do too, so either side
can grow keys alone. A write preserves unknown keys (same rule as `Prefs.qml`).
## notifyd changes
- `internal/notify/config.go`: `Config` struct, `DefaultConfig()`,
`LoadConfig(path) Config`. Malformed JSON logs and returns the defaults: a
hand typo must never stop the daemon. Path is
`$XDG_CONFIG_HOME/notifyd/config.json`, falling back to `~/.config`.
- `DefaultTimeoutMS` takes the config's timeouts instead of constants.
- `Store` reads `liveCap`/`historyCap` from fields set at construction and by
`SetCaps`. A lowered cap trims at the next add, not at reload: a reload must
not silently drop live notifications.
- `control.Reload()` D-Bus method re-reads the file and applies it under the
store's lock; `notifyctl reload` drives it. A missing daemon makes
`notifyctl` exit non-zero, which the page reports.
- `config_test.go`: defaults on missing file, defaults on malformed JSON,
partial file merges over defaults, cap clamping.
## Quickshell changes
- `shared/Notify.qml` gains `config`, parsed from a watched `FileView` with the
defaults applied, so both shells see the same resolved values.
- `notifications/Balloons.qml`: screen and anchors from
`Notify.config.balloons`; the column grows away from the anchored edge
(top corners stack downward). The balloon visibility check adds the mute
list and `dndPassCritical`.
- `desktop/modules/notifications/`: a `Module` in group "Desktop", settings
only, with a `SaveBar` (draft, save, discard), like HyprIdle. Rows:
- Timeouts: a seconds field and a "never" switch per urgency.
- Caps: two number fields.
- Placement: screen chooser from `Quickshell.screens`, a four-corner picker.
- Suppression: "Critical passes DND" switch; muted apps as removable chips,
added from the app names in the current queue and history, or typed.
- Save writes the file through a `Process` running `cat > <file>` (the
`notes` pattern: the exit code is the outcome) after `mkdir -p`, then runs
`notifyctl reload`. If reload fails the page says the file was saved but the
daemon was not reached.
- Search keywords cover timeout, history, mute, dnd, corner, monitor.
Editing `shared/Notify.qml` needs a forced reload of both shells (hot reload
does not follow the symlink), and adding the module directory needs
`desktop/shell.qml` touched or a restart.
## Docs
- notifyd `README.md`: the config file and `notifyctl reload`.
- `notifications/README.md`: placement, mute and DND now come from the file.
- `AGENTS.md`: the settings list in "What this is" gains notifications.
## Verification
- `go test ./...` in notifyd.
- `notify-send` with a 3s low timeout configured: balloon leaves at 3s.
- Muted app: no balloon, present in drawer list.
- Corner and screen changes: by eye, user's screen.
|