# niri and Dank Material Shell Dictation on the [niri](https://github.com/YaLTeR/niri) Wayland compositor, including sessions running Dank Material Shell (DMS). There is no compositor-specific code: activation reuses the standard external triggers over D-Bus, and the tray is the standard StatusNotifier item. ## Prerequisites 1. Install Voculinux first: [INSTALL.md](INSTALL.md). 2. Open **Settings → Shortcuts** and turn on **External Activation (Desktop Shortcut)**. The built-in key listener stops, and a running instance answers these triggers on the session bus (`com.voculinux.Voculinux`): ```bash voculinux --toggle # start if idle, stop if active voculinux --start # start voice typing voculinux --stop # stop voice typing ``` ## Keybind: toggle once per press Either paste the bind below into the `binds {}` section of `~/.config/niri/config.kdl`, or copy the ready-made include file and reference it (easier to keep across config edits): ```bash mkdir -p ~/.config/niri cp resources/niri/voculinux.kdl ~/.config/niri/voculinux.kdl ``` then near the top of `~/.config/niri/config.kdl`: ```kdl include "~/.config/niri/voculinux.kdl" ``` The shipped bind: ```kdl binds { Mod+Shift+M repeat=false hotkey-overlay-title="Toggle Voculinux dictation" { spawn "voculinux" "--toggle"; } } ``` `repeat=false` is what makes it toggle once per press: without it, holding the key repeats the spawn and toggles dictation back off before you let go. `Mod+Shift+M` was free in niri's stock binds and in Dank Material Shell's binds when this was written. To use another key, check it first (below). The bind's title shows in niri's hotkey overlay (`Mod+Shift+Slash`). The include file's commented `--start` / `--stop` variants use `Mod+Shift+S` and `Mod+Shift+X`, which were free in the same way. A key is either a compositor bind or an in-app Voculinux shortcut — never both. With External Activation on, do not keep the same key configured under **Settings → Shortcuts**; with it off, remove the niri bind. A key claimed on both sides fires twice: the compositor toggles over D-Bus while the internal listener fires on its own. ## Check a key before you bind it `niri validate` rejects a key bound twice inside one `binds {}` block, but not a key bound in two blocks: a bind in your include file and one in `dms/binds.kdl` both pass validation with no warning. So validation cannot tell you whether a key is free. Check with grep, against your own config and niri's stock binds. Run this before you copy the include file, or your own toggle bind will match: ```bash KEY_RE='(Mod|Super)\+Shift\+M([[:space:]{]|$)' # the key you want to check grep -rniE --include='*.kdl' "$KEY_RE" ~/.config/niri/ strings "$(command -v niri)" | grep -iE "^[[:space:]]*(//[[:space:]]*)?$KEY_RE" ``` Matching ignores case, because niri also accepts `mod+shift+m`. `strings` comes from binutils. The first command searches your config. `~/.config/niri/` holds `config.kdl` and the DMS-managed `dms/binds.kdl`, which `config.kdl` includes. `--include='*.kdl'` skips the `binds.kdl.pre-*` backups. If you include files from elsewhere, add their paths. The second command searches the stock defaults embedded in the niri binary. Commented lines match too, so a hit can be a bind you have commented out. No output from either command means the key is free. For a hit, remove or move the other bind, because niri does not warn about a key bound in two places. `Mod` and `Super` are both checked because niri reads `Mod` as Super on a TTY. ## First dictation There is no permission prompt on niri. The RemoteDesktop portal is never used there, because no portal backend implements it, so your first dictation types straight into the focused window through `wtype` with nothing to approve or deny. Do not pin `portal` as a niri recommendation. IBus is excluded on niri as well. The GNOME and KDE first-run behaviour is described in [USER_GUIDE.md](USER_GUIDE.md#first-dictation-on-niri-gnome-and-kde). ## Hold-to-talk stays in the app A compositor bind only sends the key press; it never sees the release, so it can only toggle dictation. Push-to-talk (hold Right Alt, release to stop) keeps working only through the built-in Voculinux shortcut. This pack ships plain press binds only, so if you dictate with hold-to-talk, leave the in-app shortcut in place for it and use the niri bind for toggle. ## Flatpak `voculinux` is not on the host PATH from a sandbox, so bind the host D-Bus call instead (same toggle, no extra sandbox spawned per press). The include file carries a commented-out `gdbus` variant; background and the exact command live under [Running without device access](../packaging/flatpak/README.md#running-without-device-access). ## Tray under Dank Material Shell Not yet observed on hardware: the StatusNotifier watcher bus name (or names) DMS owns will be recorded with ticket #32, the hardware acceptance run. The tray probe (`_check_status_notifier_watcher` in `src/vocalinux/ui/tray_indicator.py`) currently accepts only `org.kde.StatusNotifierWatcher`, and no probe code changed in this ticket. The observation to record on #32, with a DMS session running: ```bash busctl --user list | grep -i -E 'watcher|statusnotifier' ``` If DMS owns only `org.freedesktop.StatusNotifierWatcher`, the probe is widened to accept both watcher names so no false missing-tray warning appears, with behaviour tests covering both names. If `org.kde.StatusNotifierWatcher` is present, the current probe stands and the recorded output closes the question.