niri and Dank integration APIs #5

Closed
opened 2026-11-05 15:23:06 +00:00 by xavierk · 1 comment
Owner

Part of #1

Question

What do niri and Dank Material Shell expose for global keybinds that spawn commands, StatusNotifier tray hosting, and bar widgets, and which of those should Voculinux target without forking either project?

Part of #1 ## Question What do niri and Dank Material Shell expose for global keybinds that spawn commands, StatusNotifier tray hosting, and bar widgets, and which of those should Voculinux target without forking either project?
xavierk added the wayfinder:research label 2026-11-05 15:23:06 +00:00
Author
Owner

niri and Dank Material Shell integration APIs — research findings

Ticket: "niri and Dank integration APIs" (map: Voculinux port)
Question: What do niri and Dank Material Shell (DMS) expose for spawn
keybinds, StatusNotifier tray hosting, and bar widgets, and which should
Voculinux target without forking?
Researched 2026-11-05 against primary sources (official docs, upstream
repos, this repo's source). No repo files were edited.

Naming note: upstream DMS has rebranded. danklinux.com now redirects to
duxlabs.dev, the shell is "Dux Modular Shell (dms)" 2.x, and the dms
CLI name is retained [S8][S9]. "DMS" below means that codebase.

Verdict

Target neither project's internals and fork neither. Target three
already-standard seams, in this order:

  1. Activation: niri's own keybind config spawning the existing
    Vocalinux CLI/D-Bus triggers
    (vocalinux --toggle/--start/--stop).
    This is exactly how DMS itself is driven on niri
    (spawn "dms" "ipc" "call" ...) [S10]. Ship a documented KDL snippet;
    add no compositor code.
  2. Tray: the freedesktop StatusNotifier standard Vocalinux already
    speaks
    via Ayatana AppIndicator. niri hosts nothing; DMS (via
    Quickshell) is one conforming host among several [S8][S12]. Nothing
    DMS-specific is needed, subject to one watcher-name check below.
  3. Bar widget: optional, later, as a separate DMS QML plugin — not in
    core, and only after Vocalinux's D-Bus interface gains a state signal
    (today it is write-only: Toggle/Start/Stop methods, no signals or
    properties). A widget built before that is a blind button duplicating
    the tray icon.

Push-to-hold dictation and text injection stay on the existing
compositor-agnostic paths (evdev, wtype/ydotool). niri's native
press/release binds would eventually allow compositor-driven
push-to-talk, but upstream marks them "Since: next release" [S2] and DMS
likewise gates them behind "Next release (niri main)" vs 26.04 [S10] —
do not depend on them yet.

1. Spawn keybinds

niri (primary)

  • Binds are user config, KDL: binds { Mod+T { spawn "alacritty"; } }.
    spawn takes an argv vector (no shell); spawn-sh (since 25.08) runs
    one string through sh; spawn-at-startup is the autostart form [S1].
  • Binds repeat by default; repeat=false and cooldown-ms= rate-limit
    them. Spawn binds accept allow-when-locked=true. Bound keys are
    consumed by the compositor and not passed to applications by default
    [S1].
  • Press and release binds exist in the wiki but are labelled
    "Since: next release", explicitly motivated by push-to-talk, with a
    guarantee that a fired press action always gets its release action
    [S1][S2]. The implementing PR describes the same push-to-talk use
    case [S3]. Until a numbered release ships them, stable niri can only
    express toggle activation, not hold.
  • Every bindable action is also invocable at runtime:
    niri msg action spawn -- ..., and the IPC socket at $NIRI_SOCKET
    accepts one JSON request per line, with a stated backwards-compat
    policy for JSON output (fields/variants not renamed or removed;
    handle unknown additions) [S4]. Vocalinux does not need this — the
    compositor spawns us, not vice versa — except that
    focused_window.py already shells out to
    niri msg -j focused-window [L3].

Consequence for Voculinux: the integration is a five-line user snippet,
e.g. Mod+R { spawn "vocalinux" "--toggle"; }, because the receiving
end already exists: --toggle/--start/--stop forward to
com.vocalinux.Vocalinux Toggle/Start/Stop on the session bus and
exit [L5][L6]. DMS's niri integration uses the same shape plus
include optional=true "dms/binds.kdl" fragment files [S10] — a pattern
Voculinux can copy for an optional include file instead of asking users
to hand-edit binds {}.

DMS

DMS exposes no global-keybind registry of its own. Its "Keybinds & IPC"
surface is dms ipc call <target> <function> [args] — a CLI meant to be
spawned by the compositor's binds [S9][S10]. There is no DMS API for a
third-party app to register a hotkey; the compositor owns keys on this
stack. Targeting DMS for activation would add a middleman that itself
just shells out.

Current Vocalinux assumption (conflict to document)

Hold-to-talk today reads raw evdev: EvdevKeyboardBackend opens
/dev/input/event*, optionally EVIOCGRABs each keyboard and
re-emits unconsumed events through a /dev/uinput clone [L1]. This is
compositor-agnostic and works under niri, but requires input-group
(keylogger-grade) access [L2]. If a user also binds the same key in
niri, both fire: evdev sees the device event before the compositor, and
niri's "bound keys are not passed to apps" [S1] does not stop the evdev
reader. The niri snippet docs must say: compositor bind or in-app
shortcut for a given key, never both.

2. StatusNotifier tray hosting

  • niri: none. niri is a compositor only — no bar, no tray, no
    StatusNotifierWatcher. Users bring a shell/bar; that is the entire
    reason DMS/Waybar pair with it [S10][S11].
  • DMS: yes. DMS lists "System Tray" as a general feature; its
    architecture is a Go backend (dms) spawning a Quickshell (QML) UI,
    both integrating with D-Bus independently [S8]. Quickshell implements
    the StatusNotifier host side (watcher + item proxies + DBusMenu) per
    the freedesktop StatusNotifierItem spec [S12][S13].
  • Vocalinux: already a client. tray_indicator.py loads
    AyatanaAppIndicatorGlib → AyatanaAppIndicator3 → AppIndicator3
    and registers an APPLICATION_STATUS indicator, which is a
    StatusNotifierItem on the session bus [L4]. Any conforming watcher —
    DMS, Waybar's tray module, Plasma — hosts it. No DMS-specific tray
    work exists to do.

One unverified risk: Vocalinux probes for a watcher by looking for the
exact bus name org.kde.StatusNotifierWatcher
(tray_indicator.py:702, main.py:341) and pops a "tray icon may not
appear" dialog if absent [L4]. If Quickshell's watcher owns only
org.freedesktop.StatusNotifierWatcher, Vocalinux would false-positive
that dialog on DMS despite a working tray. Verify on a real DMS box
(busctl --user list | grep StatusNotifierWatcher) before changing
anything; if confirmed, accept both names. This was not testable from
docs alone and is marked unconfirmed.

3. Bar widgets

  • niri: no widget host. Its IPC/event-stream is compositor state
    (windows, workspaces, outputs) "useful for implementing various bars
    and indicators" [S4] — data for bar authors, not an extension API
    in niri. There is nothing Voculinux-shaped to plug into.
  • DMS: a real plugin system. Plugins are QML directories discovered
    from $CONFIGPATH/dms/plugins/, each with a plugin.json manifest
    (id, type: widget/daemon/launcher/desktop/dash/dashCard/
    composite, capabilities e.g. ["dankbar-widget"], component,
    requires_dms) [S7]. A bar widget wraps PluginComponent and
    provides horizontalBarPill / verticalBarPill components plus
    optional popoutContent; clicks can run external commands via
    Quickshell Process / Quickshell.execDetached [S7]. So a DMS pill
    that shells out to vocalinux --toggle is a small, supported,
    no-fork extension — distributed through DMS's plugin registry [S7].
  • Blocker is on our side, not theirs. A useful pill must show
    idle/listening/processing state. com.vocalinux.Vocalinux exposes
    only the three methods above — the introspection XML has no signals
    and no properties [L6]. A plugin today could only fire-and-forget.
    Sequencing: (1) add a state signal/property to dbus_service.py
    (compositor-agnostic; also benefits Waybar custom/ modules and
    scripts), (2) then optionally ship the QML plugin as a separate
    artifact. Do not invert the order.
  • Churn risk. DMS just rebranded, moved docs domains, and publishes
    versioned docs (1.6 / 2.0 / 2.1 / 2.2) with config paths shifting
    from ~/.config/DankMaterialShell/plugins/ to
    $CONFIGPATH/dms/plugins/ [S7][S8]. Pin requires_dms, keep the
    plugin thin (icon + click + D-Bus state), and keep it out of the
    core package so DMS API drift cannot break the app.

Local assumptions audit (text_injection, keyboard_backends)

  • text_injector.py already lists "niri" in
    _IBUS_UNBRIDGED_COMPOSITORS (line 310): IBus commit_text is
    treated as unbridged on niri unless the ibus-wayland
    input-method-v2 bridge process is running, falling back to the
    wtype probe → ydotool (uinput) path [L7]. Backend pinning already
    allows wtype/ydotool explicitly [L7]. No niri change needed.
  • focused_window.py already identifies the focused window on niri
    via niri msg -j focused-window (line 441), beside Hyprland/Sway
    equivalents [L3]. Terminal detection for Ctrl+Shift+V paste works
    by the same path.
  • remote_desktop_portal.py uses
    org.freedesktop.portal.RemoteDesktop (NotifyKeyboardKeysym)
    [L8]. niri ships niri-portals.conf preferring gnome;gtk
    backends [S5]. Whether xdg-desktop-portal-gnome's RemoteDesktop
    works against niri (vs Mutter) is unproven from primary docs —
    treat portal as unsupported on niri until tested on hardware;
    the code already probes and degrades [L8]. Do not pin portal in
    niri docs.
  • keyboard_backends/: pynput is X11-only in practice; evdev is
    the Wayland path and is compositor-blind (kernel devices) [L1][L2].
    Default gesture is hold Right Alt, push-to-talk
    (base.py:95,103) [L9] — the one mode stable-niri binds cannot yet
    replace (see §1). XDG GlobalShortcuts portal is not used anywhere
    in the codebase and has no niri-side implementation documented;
    not a viable target today.

Recommended sequence

  1. Docs + config only: niri page/snippet (spawn "vocalinux" "--toggle", repeat=false, optional include file), with the
    evdev-double-trigger warning and "hold mode stays in-app" note.
  2. Hardware verification on niri + DMS: tray icon/menu render,
    watcher bus name (§2), wtype vs ydotool selection, toggle-bind
    latency, portal probe result. Fix the watcher-name check only if
    step 2 proves it wrong.
  3. Add D-Bus state signal to dbus_service.py (agnostic; unblocks
    Waybar modules too).
  4. Optional DMS QML plugin (separate repo/artifact, requires_dms
    pinned).
  5. When a numbered niri release ships press/release binds [S2],
    document compositor push-to-talk:
    press { spawn "vocalinux" "--start"; } /
    release { spawn "vocalinux" "--stop"; } — and re-evaluate whether
    evdev remains the default on niri. Until then, do not document it
    as supported.

Sources

Primary (upstream):

Secondary (context only):

Local (this repo, read-only inspection):

  • [L1] src/vocalinux/ui/keyboard_backends/evdev_backend.py:2-19
    (evdev / EVIOCGRAB / uinput clone design).
  • [L2] src/vocalinux/ui/keyboard_shortcuts.py:110-125
    (Wayland = python-evdev + input group guidance).
  • [L3] src/vocalinux/text_injection/focused_window.py:435-443
    (niri msg -j focused-window).
  • [L4] src/vocalinux/ui/tray_indicator.py:22-53,589-617,702
    (Ayatana AppIndicator chain; org.kde.StatusNotifierWatcher
    probe); src/vocalinux/main.py:341.
  • [L5] src/vocalinux/main.py:102-117,138
    (--toggle/--start/--stop trigger flags).
  • [L6] src/vocalinux/dbus_service.py:24-38,175-198
    (com.vocalinux.Vocalinux Toggle/Start/Stop; methods only, no
    signals/properties — verified by grep, no matches).
  • [L7] src/vocalinux/text_injection/text_injector.py:298-318, 395-455 (niri in _IBUS_UNBRIDGED_COMPOSITORS; ibus-wayland
    bridge exception; selectable backends).
  • [L8] src/vocalinux/text_injection/remote_desktop_portal.py:2-6, 67-71,235-259 (RemoteDesktop portal client + probe).
  • [L9] src/vocalinux/ui/keyboard_backends/base.py:93-103
    (default hold-Right-Alt, push_to_talk).
# niri and Dank Material Shell integration APIs — research findings Ticket: "niri and Dank integration APIs" (map: Voculinux port) Question: What do niri and Dank Material Shell (DMS) expose for spawn keybinds, StatusNotifier tray hosting, and bar widgets, and which should Voculinux target without forking? Researched 2026-11-05 against primary sources (official docs, upstream repos, this repo's source). No repo files were edited. Naming note: upstream DMS has rebranded. `danklinux.com` now redirects to `duxlabs.dev`, the shell is "Dux Modular Shell (dms)" 2.x, and the `dms` CLI name is retained [S8][S9]. "DMS" below means that codebase. ## Verdict Target neither project's internals and fork neither. Target three already-standard seams, in this order: 1. **Activation: niri's own keybind config spawning the existing Vocalinux CLI/D-Bus triggers** (`vocalinux --toggle/--start/--stop`). This is exactly how DMS itself is driven on niri (`spawn "dms" "ipc" "call" ...`) [S10]. Ship a documented KDL snippet; add no compositor code. 2. **Tray: the freedesktop StatusNotifier standard Vocalinux already speaks** via Ayatana AppIndicator. niri hosts nothing; DMS (via Quickshell) is one conforming host among several [S8][S12]. Nothing DMS-specific is needed, subject to one watcher-name check below. 3. **Bar widget: optional, later, as a separate DMS QML plugin** — not in core, and only after Vocalinux's D-Bus interface gains a state signal (today it is write-only: Toggle/Start/Stop methods, no signals or properties). A widget built before that is a blind button duplicating the tray icon. Push-to-hold dictation and text injection stay on the existing compositor-agnostic paths (evdev, wtype/ydotool). niri's native press/release binds would eventually allow compositor-driven push-to-talk, but upstream marks them "Since: next release" [S2] and DMS likewise gates them behind "Next release (niri main)" vs 26.04 [S10] — do not depend on them yet. ## 1. Spawn keybinds ### niri (primary) - Binds are user config, KDL: `binds { Mod+T { spawn "alacritty"; } }`. `spawn` takes an argv vector (no shell); `spawn-sh` (since 25.08) runs one string through `sh`; `spawn-at-startup` is the autostart form [S1]. - Binds repeat by default; `repeat=false` and `cooldown-ms=` rate-limit them. Spawn binds accept `allow-when-locked=true`. Bound keys are consumed by the compositor and not passed to applications by default [S1]. - Press **and release** binds exist in the wiki but are labelled "Since: next release", explicitly motivated by push-to-talk, with a guarantee that a fired press action always gets its release action [S1][S2]. The implementing PR describes the same push-to-talk use case [S3]. Until a numbered release ships them, stable niri can only express *toggle* activation, not hold. - Every bindable action is also invocable at runtime: `niri msg action spawn -- ...`, and the IPC socket at `$NIRI_SOCKET` accepts one JSON request per line, with a stated backwards-compat policy for JSON output (fields/variants not renamed or removed; handle unknown additions) [S4]. Vocalinux does not need this — the compositor spawns *us*, not vice versa — except that `focused_window.py` already shells out to `niri msg -j focused-window` [L3]. Consequence for Voculinux: the integration is a five-line user snippet, e.g. `Mod+R { spawn "vocalinux" "--toggle"; }`, because the receiving end already exists: `--toggle/--start/--stop` forward to `com.vocalinux.Vocalinux` `Toggle/Start/Stop` on the session bus and exit [L5][L6]. DMS's niri integration uses the same shape plus `include optional=true "dms/binds.kdl"` fragment files [S10] — a pattern Voculinux can copy for an optional include file instead of asking users to hand-edit `binds {}`. ### DMS DMS exposes no global-keybind registry of its own. Its "Keybinds & IPC" surface is `dms ipc call <target> <function> [args]` — a CLI meant to be spawned *by* the compositor's binds [S9][S10]. There is no DMS API for a third-party app to register a hotkey; the compositor owns keys on this stack. Targeting DMS for activation would add a middleman that itself just shells out. ### Current Vocalinux assumption (conflict to document) Hold-to-talk today reads raw evdev: `EvdevKeyboardBackend` opens `/dev/input/event*`, optionally `EVIOCGRAB`s each keyboard and re-emits unconsumed events through a `/dev/uinput` clone [L1]. This is compositor-agnostic and works under niri, but requires `input`-group (keylogger-grade) access [L2]. If a user *also* binds the same key in niri, both fire: evdev sees the device event before the compositor, and niri's "bound keys are not passed to apps" [S1] does not stop the evdev reader. The niri snippet docs must say: compositor bind **or** in-app shortcut for a given key, never both. ## 2. StatusNotifier tray hosting - **niri: none.** niri is a compositor only — no bar, no tray, no StatusNotifierWatcher. Users bring a shell/bar; that is the entire reason DMS/Waybar pair with it [S10][S11]. - **DMS: yes.** DMS lists "System Tray" as a general feature; its architecture is a Go backend (`dms`) spawning a Quickshell (QML) UI, both integrating with D-Bus independently [S8]. Quickshell implements the StatusNotifier host side (watcher + item proxies + DBusMenu) per the freedesktop StatusNotifierItem spec [S12][S13]. - **Vocalinux: already a client.** `tray_indicator.py` loads `AyatanaAppIndicatorGlib` → `AyatanaAppIndicator3` → `AppIndicator3` and registers an `APPLICATION_STATUS` indicator, which is a StatusNotifierItem on the session bus [L4]. Any conforming watcher — DMS, Waybar's tray module, Plasma — hosts it. No DMS-specific tray work exists to do. One unverified risk: Vocalinux probes for a watcher by looking for the exact bus name `org.kde.StatusNotifierWatcher` (`tray_indicator.py:702`, `main.py:341`) and pops a "tray icon may not appear" dialog if absent [L4]. If Quickshell's watcher owns only `org.freedesktop.StatusNotifierWatcher`, Vocalinux would false-positive that dialog on DMS despite a working tray. Verify on a real DMS box (`busctl --user list | grep StatusNotifierWatcher`) before changing anything; if confirmed, accept both names. This was not testable from docs alone and is marked unconfirmed. ## 3. Bar widgets - **niri: no widget host.** Its IPC/event-stream is compositor state (windows, workspaces, outputs) "useful for implementing various bars and indicators" [S4] — data *for* bar authors, not an extension API *in* niri. There is nothing Voculinux-shaped to plug into. - **DMS: a real plugin system.** Plugins are QML directories discovered from `$CONFIGPATH/dms/plugins/`, each with a `plugin.json` manifest (`id`, `type`: widget/daemon/launcher/desktop/dash/dashCard/ composite, `capabilities` e.g. `["dankbar-widget"]`, `component`, `requires_dms`) [S7]. A bar widget wraps `PluginComponent` and provides `horizontalBarPill` / `verticalBarPill` components plus optional `popoutContent`; clicks can run external commands via Quickshell `Process` / `Quickshell.execDetached` [S7]. So a DMS pill that shells out to `vocalinux --toggle` is a small, supported, no-fork extension — distributed through DMS's plugin registry [S7]. - **Blocker is on our side, not theirs.** A useful pill must show idle/listening/processing state. `com.vocalinux.Vocalinux` exposes only the three methods above — the introspection XML has no signals and no properties [L6]. A plugin today could only fire-and-forget. Sequencing: (1) add a state signal/property to `dbus_service.py` (compositor-agnostic; also benefits Waybar `custom/` modules and scripts), (2) then optionally ship the QML plugin as a separate artifact. Do not invert the order. - **Churn risk.** DMS just rebranded, moved docs domains, and publishes versioned docs (1.6 / 2.0 / 2.1 / 2.2) with config paths shifting from `~/.config/DankMaterialShell/plugins/` to `$CONFIGPATH/dms/plugins/` [S7][S8]. Pin `requires_dms`, keep the plugin thin (icon + click + D-Bus state), and keep it out of the core package so DMS API drift cannot break the app. ## Local assumptions audit (text_injection, keyboard_backends) - `text_injector.py` already lists `"niri"` in `_IBUS_UNBRIDGED_COMPOSITORS` (line 310): IBus `commit_text` is treated as unbridged on niri unless the `ibus-wayland` input-method-v2 bridge process is running, falling back to the wtype probe → ydotool (uinput) path [L7]. Backend pinning already allows `wtype`/`ydotool` explicitly [L7]. No niri change needed. - `focused_window.py` already identifies the focused window on niri via `niri msg -j focused-window` (line 441), beside Hyprland/Sway equivalents [L3]. Terminal detection for Ctrl+Shift+V paste works by the same path. - `remote_desktop_portal.py` uses `org.freedesktop.portal.RemoteDesktop` (`NotifyKeyboardKeysym`) [L8]. niri ships `niri-portals.conf` preferring `gnome;gtk` backends [S5]. Whether xdg-desktop-portal-gnome's RemoteDesktop works against niri (vs Mutter) is unproven from primary docs — treat `portal` as unsupported on niri until tested on hardware; the code already probes and degrades [L8]. Do not pin `portal` in niri docs. - `keyboard_backends/`: `pynput` is X11-only in practice; `evdev` is the Wayland path and is compositor-blind (kernel devices) [L1][L2]. Default gesture is hold Right Alt, push-to-talk (`base.py:95,103`) [L9] — the one mode stable-niri binds cannot yet replace (see §1). XDG GlobalShortcuts portal is not used anywhere in the codebase and has no niri-side implementation documented; not a viable target today. ## Recommended sequence 1. Docs + config only: niri page/snippet (`spawn "vocalinux" "--toggle"`, `repeat=false`, optional include file), with the evdev-double-trigger warning and "hold mode stays in-app" note. 2. Hardware verification on niri + DMS: tray icon/menu render, watcher bus name (§2), wtype vs ydotool selection, toggle-bind latency, portal probe result. Fix the watcher-name check only if step 2 proves it wrong. 3. Add D-Bus state signal to `dbus_service.py` (agnostic; unblocks Waybar modules too). 4. Optional DMS QML plugin (separate repo/artifact, `requires_dms` pinned). 5. When a numbered niri release ships press/release binds [S2], document compositor push-to-talk: `press { spawn "vocalinux" "--start"; }` / `release { spawn "vocalinux" "--stop"; }` — and re-evaluate whether evdev remains the default on niri. Until then, do not document it as supported. ## Sources Primary (upstream): - [S1] niri wiki, Configuration: Key Bindings — binds{}, spawn, spawn-sh, repeat/cooldown, allow-when-locked, press/release, key consumption. https://raw.githubusercontent.com/wiki/niri-wm/niri/Configuration:-Key-Bindings.md - [S2] Same page, "Press and Release Bindings — Since: next release" (push-to-talk guarantee). https://github.com/niri-wm/niri/wiki/Configuration:-Key-Bindings - [S3] niri PR #2456, "Implement release keybinds and modifier-only binds" (push-to-talk motivation). https://github.com/niri-wm/niri/pull/2456 - [S4] niri wiki, IPC — `$NIRI_SOCKET`, JSON requests, event-stream, JSON backwards-compatibility policy. https://raw.githubusercontent.com/wiki/niri-wm/niri/IPC.md - [S5] niri `resources/niri-portals.conf` (`default=gnome;gtk;`). https://raw.githubusercontent.com/niri-wm/niri/main/resources/niri-portals.conf - [S7] DMS plugin system README — discovery path, plugin.json, PluginComponent pills/popouts, Process/execDetached. https://raw.githubusercontent.com/AvengeMedia/DankMaterialShell/refs/heads/master/quickshell/PLUGINS/README.md - [S8] Dux/DMS Overview & Architecture — "System Tray" feature, Quickshell+Go client-server architecture. https://duxlabs.dev/docs/dms/overview - [S9] Dux/DMS Keybinds & IPC — `dms ipc call <target> <function>`. https://duxlabs.dev/docs/dms/keybinds-ipc - [S10] Dux/DMS Compositor Setup — niri includes (`dms/binds.kdl`, version-gated `binds-next.kdl`: 26.04 vs next-release), `spawn-at-startup "dms" "run"`, `spawn "dms" "ipc" "call" ...` bind examples. https://duxlabs.dev/docs/dms/compositors - [S12] Quickshell system-tray implementation notes (StatusNotifierItem host, DBusMenu). https://deepwiki.com/quickshell-mirror/quickshell/7.3-system-tray-(status-notifier) - [S13] freedesktop StatusNotifierItem specification. https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/StatusNotifierItem/ Secondary (context only): - [S11] "bars, notification daemons, and app launchers are not included. Niri is strictly the compositor." https://itsfoss.com/niri-window-manager/ Local (this repo, read-only inspection): - [L1] `src/vocalinux/ui/keyboard_backends/evdev_backend.py:2-19` (evdev / EVIOCGRAB / uinput clone design). - [L2] `src/vocalinux/ui/keyboard_shortcuts.py:110-125` (Wayland = python-evdev + `input` group guidance). - [L3] `src/vocalinux/text_injection/focused_window.py:435-443` (`niri msg -j focused-window`). - [L4] `src/vocalinux/ui/tray_indicator.py:22-53,589-617,702` (Ayatana AppIndicator chain; `org.kde.StatusNotifierWatcher` probe); `src/vocalinux/main.py:341`. - [L5] `src/vocalinux/main.py:102-117,138` (`--toggle/--start/--stop` trigger flags). - [L6] `src/vocalinux/dbus_service.py:24-38,175-198` (`com.vocalinux.Vocalinux` Toggle/Start/Stop; methods only, no signals/properties — verified by grep, no matches). - [L7] `src/vocalinux/text_injection/text_injector.py:298-318, 395-455` (niri in `_IBUS_UNBRIDGED_COMPOSITORS`; `ibus-wayland` bridge exception; selectable backends). - [L8] `src/vocalinux/text_injection/remote_desktop_portal.py:2-6, 67-71,235-259` (RemoteDesktop portal client + probe). - [L9] `src/vocalinux/ui/keyboard_backends/base.py:93-103` (default hold-Right-Alt, push_to_talk).
Sign in to join this conversation.