# 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 > ` (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.