A niri user gets first-class activation and a working tray with no compositor-specific code. Documentation gains a niri integration page with a ready-to-use keybind snippet (ideally an optional include file) that spawns the renamed toggle triggers, marked non-repeating, with a plain warning that a key is either a compositor bind or an in-app shortcut, never both, and a note that hold-to-talk stays in the app until a numbered niri release ships press/release binds. The same ticket settles the tray watcher question by observation on a real Dank Material Shell session: if the shell's watcher owns only the freedesktop watcher name, the tray-presence probe is widened to accept both watcher names so no false warning appears; if the current probe is already correct, that evidence is recorded and no code changes.
Acceptance criteria
Following only the new documentation, a fresh niri configuration toggles dictation from the documented bind, once per press.
The docs carry the compositor-bind-XOR-in-app-shortcut warning and the hold-to-talk status note.
Recorded evidence on this ticket names the StatusNotifier watcher bus name(s) observed under Dank Material Shell.
If the probe needed widening, the tray appears under Dank Material Shell with no false missing-tray warning, and behaviour tests cover both watcher names; if it did not, no probe code changed.
No niri- or shell-internal APIs are used anywhere; hold-to-talk behaviour is unchanged.
## Parent
[Spec: Voculinux — Void Linux (glibc) Wayland/niri port of Vocalinux](https://git.bongbetic.com/xavierk/voculinux/issues/16)
## What to build
A niri user gets first-class activation and a working tray with no compositor-specific code. Documentation gains a niri integration page with a ready-to-use keybind snippet (ideally an optional include file) that spawns the renamed toggle triggers, marked non-repeating, with a plain warning that a key is either a compositor bind or an in-app shortcut, never both, and a note that hold-to-talk stays in the app until a numbered niri release ships press/release binds. The same ticket settles the tray watcher question by observation on a real Dank Material Shell session: if the shell's watcher owns only the freedesktop watcher name, the tray-presence probe is widened to accept both watcher names so no false warning appears; if the current probe is already correct, that evidence is recorded and no code changes.
## Acceptance criteria
- [ ] Following only the new documentation, a fresh niri configuration toggles dictation from the documented bind, once per press.
- [ ] The docs carry the compositor-bind-XOR-in-app-shortcut warning and the hold-to-talk status note.
- [ ] Recorded evidence on this ticket names the StatusNotifier watcher bus name(s) observed under Dank Material Shell.
- [ ] If the probe needed widening, the tray appears under Dank Material Shell with no false missing-tray warning, and behaviour tests cover both watcher names; if it did not, no probe code changed.
- [ ] No niri- or shell-internal APIs are used anywhere; hold-to-talk behaviour is unchanged.
## Blocked by
- [Rename user-visible identity to voculinux with one-time legacy import](https://git.bongbetic.com/xavierk/voculinux/issues/18)
#22 evidence: niri and Dank Material Shell integration (2026-11-06)
StatusNotifier watcher under Dank Material Shell (DMS)
busctl --user list: org.kde.StatusNotifierWatcher is owned by PID 2478 (qs, DMS), with org.kde.StatusNotifierHost-2478-* registered. org.freedesktop.StatusNotifierWatcher is not owned; only org.x.StatusNotifierWatcher is activatable.
busctl --user get-property org.kde.StatusNotifierWatcher /StatusNotifierWatcher org.kde.StatusNotifierWatcher IsStatusNotifierHostRegistered returns b true.
With voculinux running, RegisteredStatusNotifierItems returns as 1 ":<bus>/org/ayatana/NotificationItem/voculinux", so the app's item is registered with DMS's watcher.
Installed probe, run with /usr/bin/python3 and no code change: TrayIndicator._check_status_notifier_watcher() from /usr/lib/python3.14/site-packages/vocalinux/ui/tray_indicator.py returns True.
App logs contain no "tray icon may not appear" or missing-watcher line (0 matches across three instances).
Verdict: the current probe already accepts the name DMS owns, so no probe code changes (the spec's "already correct" branch). Seeing the icon in the DMS bar is NEEDS-HUMAN.
Keybind example (docs-style text)
Ready-made file: resources/niri/voculinux.kdl. Copy it to ~/.config/niri/voculinux.kdl and add include "~/.config/niri/voculinux.kdl" near the top of config.kdl. Prerequisite: Settings, Shortcuts, External Activation on.
Warning, as the spec requires: a key is either a compositor bind or an in-app Voculinux shortcut, never both. A key claimed on both sides fires twice. Hold-to-talk stays in the app: a compositor bind sends only the press, so push-to-talk keeps working only through the built-in shortcut until a numbered niri release ships press/release binds.
Verification
niri validate -c, all rc=0:
(a) the bind block alone;
(b) a scratch copy of the user's config with the include line appended, under HOME=<scratch> so the ~ expands there;
(c) resources/niri/voculinux.kdl standalone.
Conflict found:Mod+Shift+D is already bound on this machine in ~/.config/niri/dms/binds.kdl:131 (Move Column Right). niri validate accepts the duplicate, so the shipped example silently competes with DMS. Finding for the docs: choose a free default key, or add a conflict-check line.
Spawn path: /usr/sbin/voculinux --toggle with no running instance exits 1 with the expected ServiceUnknown D-Bus error. niri msg action spawn -- /usr/sbin/voculinux --toggle is accepted (rc 0). No instance was started.
Hold-to-talk status note and compositor-bind/in-app warning: present in docs/NIRI.md (checked by reading).
Live "one press = one toggle" from a fresh niri config: NEEDS-HUMAN (see the #32 checklist).
No niri- or DMS-internal API is used; the example relies only on spawn.
## #22 evidence: niri and Dank Material Shell integration (2026-11-06)
### StatusNotifier watcher under Dank Material Shell (DMS)
- `busctl --user list`: `org.kde.StatusNotifierWatcher` is owned by PID 2478 (`qs`, DMS), with `org.kde.StatusNotifierHost-2478-*` registered. `org.freedesktop.StatusNotifierWatcher` is **not** owned; only `org.x.StatusNotifierWatcher` is activatable.
- `busctl --user get-property org.kde.StatusNotifierWatcher /StatusNotifierWatcher org.kde.StatusNotifierWatcher IsStatusNotifierHostRegistered` returns `b true`.
- With voculinux running, `RegisteredStatusNotifierItems` returns `as 1 ":<bus>/org/ayatana/NotificationItem/voculinux"`, so the app's item is registered with DMS's watcher.
- Installed probe, run with `/usr/bin/python3` and no code change: `TrayIndicator._check_status_notifier_watcher()` from `/usr/lib/python3.14/site-packages/vocalinux/ui/tray_indicator.py` returns `True`.
- App logs contain no "tray icon may not appear" or missing-watcher line (0 matches across three instances).
- Verdict: the current probe already accepts the name DMS owns, so **no probe code changes** (the spec's "already correct" branch). Seeing the icon in the DMS bar is NEEDS-HUMAN.
### Keybind example (docs-style text)
Ready-made file: `resources/niri/voculinux.kdl`. Copy it to `~/.config/niri/voculinux.kdl` and add `include "~/.config/niri/voculinux.kdl"` near the top of `config.kdl`. Prerequisite: Settings, Shortcuts, External Activation on.
```kdl
binds {
Mod+Shift+D repeat=false hotkey-overlay-title="Toggle Voculinux dictation" {
spawn "voculinux" "--toggle";
}
}
```
Warning, as the spec requires: a key is either a compositor bind or an in-app Voculinux shortcut, never both. A key claimed on both sides fires twice. Hold-to-talk stays in the app: a compositor bind sends only the press, so push-to-talk keeps working only through the built-in shortcut until a numbered niri release ships press/release binds.
### Verification
- `niri validate -c`, all rc=0:
- (a) the bind block alone;
- (b) a scratch copy of the user's config with the include line appended, under `HOME=<scratch>` so the `~` expands there;
- (c) `resources/niri/voculinux.kdl` standalone.
- **Conflict found:** `Mod+Shift+D` is already bound on this machine in `~/.config/niri/dms/binds.kdl:131` (`Move Column Right`). `niri validate` accepts the duplicate, so the shipped example silently competes with DMS. Finding for the docs: choose a free default key, or add a conflict-check line.
- Spawn path: `/usr/sbin/voculinux --toggle` with no running instance exits 1 with the expected `ServiceUnknown` D-Bus error. `niri msg action spawn -- /usr/sbin/voculinux --toggle` is accepted (rc 0). No instance was started.
- Hold-to-talk status note and compositor-bind/in-app warning: present in `docs/NIRI.md` (checked by reading).
- Live "one press = one toggle" from a fresh niri config: NEEDS-HUMAN (see the #32 checklist).
- No niri- or DMS-internal API is used; the example relies only on `spawn`.
Human session (2026-11-07) on the installed build from main 0c48db5. Tray icon visible in the Dank Material Shell bar, registered with org.kde.StatusNotifierWatcher (no probe change needed). The niri keybind include works live; the user rebound the toggle to Mod+Alt+V, which is free in niri and DMS, and niri validate passes. The example key conflict was fixed in #45. Full results are on #32.
Human session (2026-11-07) on the installed build from main 0c48db5. Tray icon visible in the Dank Material Shell bar, registered with org.kde.StatusNotifierWatcher (no probe change needed). The niri keybind include works live; the user rebound the toggle to Mod+Alt+V, which is free in niri and DMS, and niri validate passes. The example key conflict was fixed in #45. Full results are on #32.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Parent
Spec: Voculinux — Void Linux (glibc) Wayland/niri port of Vocalinux
What to build
A niri user gets first-class activation and a working tray with no compositor-specific code. Documentation gains a niri integration page with a ready-to-use keybind snippet (ideally an optional include file) that spawns the renamed toggle triggers, marked non-repeating, with a plain warning that a key is either a compositor bind or an in-app shortcut, never both, and a note that hold-to-talk stays in the app until a numbered niri release ships press/release binds. The same ticket settles the tray watcher question by observation on a real Dank Material Shell session: if the shell's watcher owns only the freedesktop watcher name, the tray-presence probe is widened to accept both watcher names so no false warning appears; if the current probe is already correct, that evidence is recorded and no code changes.
Acceptance criteria
Blocked by
#22 evidence: niri and Dank Material Shell integration (2026-11-06)
StatusNotifier watcher under Dank Material Shell (DMS)
busctl --user list:org.kde.StatusNotifierWatcheris owned by PID 2478 (qs, DMS), withorg.kde.StatusNotifierHost-2478-*registered.org.freedesktop.StatusNotifierWatcheris not owned; onlyorg.x.StatusNotifierWatcheris activatable.busctl --user get-property org.kde.StatusNotifierWatcher /StatusNotifierWatcher org.kde.StatusNotifierWatcher IsStatusNotifierHostRegisteredreturnsb true.RegisteredStatusNotifierItemsreturnsas 1 ":<bus>/org/ayatana/NotificationItem/voculinux", so the app's item is registered with DMS's watcher./usr/bin/python3and no code change:TrayIndicator._check_status_notifier_watcher()from/usr/lib/python3.14/site-packages/vocalinux/ui/tray_indicator.pyreturnsTrue.Keybind example (docs-style text)
Ready-made file:
resources/niri/voculinux.kdl. Copy it to~/.config/niri/voculinux.kdland addinclude "~/.config/niri/voculinux.kdl"near the top ofconfig.kdl. Prerequisite: Settings, Shortcuts, External Activation on.Warning, as the spec requires: a key is either a compositor bind or an in-app Voculinux shortcut, never both. A key claimed on both sides fires twice. Hold-to-talk stays in the app: a compositor bind sends only the press, so push-to-talk keeps working only through the built-in shortcut until a numbered niri release ships press/release binds.
Verification
niri validate -c, all rc=0:HOME=<scratch>so the~expands there;resources/niri/voculinux.kdlstandalone.Mod+Shift+Dis already bound on this machine in~/.config/niri/dms/binds.kdl:131(Move Column Right).niri validateaccepts the duplicate, so the shipped example silently competes with DMS. Finding for the docs: choose a free default key, or add a conflict-check line./usr/sbin/voculinux --togglewith no running instance exits 1 with the expectedServiceUnknownD-Bus error.niri msg action spawn -- /usr/sbin/voculinux --toggleis accepted (rc 0). No instance was started.docs/NIRI.md(checked by reading).spawn.Human session (2026-11-07) on the installed build from main
0c48db5. Tray icon visible in the Dank Material Shell bar, registered with org.kde.StatusNotifierWatcher (no probe change needed). The niri keybind include works live; the user rebound the toggle to Mod+Alt+V, which is free in niri and DMS, and niri validate passes. The example key conflict was fixed in #45. Full results are on #32.