Is a Rust rebuild of the Vocalinux tray dictation app sound on Void Linux Wayland, or should Voculinux stay on Python? Compare capture (PipeWire), whisper.cpp inference, text injection, StatusNotifier tray, and GTK UI parity, plus rebuild cost and xbps packaging effort.
Part of #1
## Question
Is a Rust rebuild of the Vocalinux tray dictation app sound on Void Linux Wayland, or should Voculinux stay on Python? Compare capture (PipeWire), whisper.cpp inference, text injection, StatusNotifier tray, and GTK UI parity, plus rebuild cost and xbps packaging effort.
Question: Is a Rust rebuild of the Vocalinux Python GTK3 tray dictation
app sound on Void Linux Wayland, or stay on Python?
Method: local checkout (src/vocalinux/, v0.18.5) read directly;
external claims traced to primary sources (official docs, protocol
XML, crate docs/source, Void void-packages source). Secondary
write-ups were used only as search leads, never as citations.
Date of local measurement: this checkout, commands quoted in §7.
Verdict
Stay on Python. Do not do a wholesale Rust rebuild now.
A Rust rebuild is technically feasible area by area — PipeWire,
whisper.cpp, tray, even GTK have real Rust bindings — but it is not sound as a rewrite, for three load-bearing reasons:
The hard problems are not language problems. Wayland text
injection and global hotkeys are limited by compositor protocols
and portal consent, identically in Rust and Python (§3). Rust buys
nothing there, and the current app's value is precisely its
multi-backend fallback ladder (IBus, RemoteDesktop portal, wtype,
ydotool/uinput, xdotool/XWayland, clipboard paste).
Rust forces a simultaneous GTK3 → GTK4 migration. The Rust
GTK3 bindings are unmaintained and dropped upstream (§5), so
"port to Rust" secretly means "port language and toolkit and
tray model at once" — three rewrites stacked, each able to break
parity alone.
Rebuild cost dwarfs plausible gain. The app is ~48k LOC of
Python with ~88k LOC of tests (§7). Inference already runs in
native whisper.cpp via pywhispercpp; Python is orchestration
around C/C++ engines, PortAudio, and D-Bus. Rust would mainly
speed up code that is not the bottleneck, while discarding the
test suite that encodes years of compositor/device edge cases.
Sound Rust use, if desired: an incremental native sidecar or
prototype (PipeWire capture + whisper.cpp core behind the existing
Python UI, or a small tray-less dictation daemon) to prove
injection on the actual Void compositors targeted — not a flag-day
rewrite. See §9.
0. What exists today (local, primary)
Fact
Evidence
Python ≥3.11, GTK 3 via PyGObject (distro python3-gi, never pip)
Size: 47,976 LOC Python (84 files), 87,649 LOC tests (155 files)
wc -l on src/vocalinux/**/*.py, tests/test_*.py (§7)
Note the Void-specific wrinkle already present in Python: the
ydotool guidance in text_injector.py:57-67 is systemd-centric,
while Void uses runit — language choice does not fix service
supervision either.
The official pipewire crate (0.10.1 docs) is "Rust bindings to libpipewire, the library for interacting with the pipewire
server", hosted under freedesktop's PipeWire namespace, with MainLoop/Context/Core/Registry and stream APIs.
Source: https://pipewire.pages.freedesktop.org/pipewire-rs/pipewire/
Alternative cpal (RustAudio) on Linux is ALSA-host based; PipeWire
is reached through the ALSA pipewire bridge device, not a native
graph API — fine for a mic, weaker for per-node/monitor capture.
Source: https://docs.rs/cpal/latest/cpal/ and https://github.com/RustAudio/cpal
pw-dump JSON parsing (audio/pipewire.py) and > PortAudio device
negotiation pain documented at length in audio/capture.py
(format/channel aborts, Bluetooth SCO heap corruption comments).
But: the crate is pre-1.0, callback/SPA pod construction is
verbose unsafe-adjacent work, and device-hotplug/format edge cases
would need re-earning the hard way. Net: genuine Rust win,
medium effort, low risk. This is the strongest pro-Rust area.
Silero VAD equivalent: ort (ONNX Runtime Rust bindings) https://docs.rs/ort/latest/ort/ — replaces Python onnxruntime
used in silero_vad.py.
Assessment: same underlying C++ engine as pywhispercpp, so
transcription quality/speed should match; GPU backends (Vulkan etc.)
become Cargo features/CMake flags instead of today's installer
rebuild dance (AGENTS.md, install.sh PYWHISPERCPP_VERSION).
Costs: long native builds, -sys build needs cmake + C++ toolchain
at package-build time (matters for xbps, §8), and re-implementing
the 5.7k-line recognition_manager.py logic (VAD segmentation,
engine dispatch, model download/checksum pinning, keepalive/unload,
diarization via tinydiarize in diarization.py, GPU probing
workarounds for old pywhispercpp versions). Optional engines
(Vosk/Parakeet/faster-whisper/remote) each need their own Rust
story (sherpa-onnx has a C API; Vosk has a Rust binding; none is
free). Net: feasible, no quality gain, high porting effort in the
orchestration layer around the engine.
3. Wayland text injection — Rust: no advantage; the binding constraint
This is the make-or-break area, and it is compositor-bound, not
language-bound. There is still no stable, universal "type this
string into the focused app" Wayland protocol:
input-method-unstable-v2 + virtual-keyboard-unstable-v1
(the wlr-originated pair wtype uses) live outside stable
wayland-protocols and are not implemented by every compositor —
notably not by GNOME/Mutter — which is why the Python app probes
wtype support and falls back (text_injector.py:186-205).
Sources: https://gitlab.freedesktop.org/wayland/wayland-protocols/-/issues/39
(input-method hub issue listing text-input-v3 / input-method /
virtual-keyboard status), https://wayland.app/protocols/virtual-keyboard-unstable-v1
Remaining fallbacks are identical in any language: IBus engine
(needs IBus as the active IM; KDE needs "IBus Wayland" virtual
keyboard selected — text_injector.py:100-106), ydotool/uinput
(needs /dev/uinput access, input group, a daemon — on Void a
runit service, not systemd), clipboard + paste shortcut.
Rust crates exist for the pieces (wayland-client, wayland-protocols crates: https://docs.rs/wayland-protocols/latest/wayland_protocols/ , zbus/ashpd for portals/IBus-over-D-Bus), but every compositor
quirk, probe, and fallback currently encoded in ~6k lines of text_injection/ + focused_window.py would have to be re-derived
and re-tested per compositor (wlroots/Sway, KWin, Mutter at
minimum). Net: parity is achievable in principle, most expensive
to re-prove, zero capability gain. Any Rust plan that starts with
UI or inference and leaves injection for later has the risk order
backwards.
Global hotkeys have the same shape: no general Wayland global-key
grab; today's evdev backend (/dev/input, permissions) maps to
Rust evdev crate (https://docs.rs/evdev/latest/evdev/) one-for-one,
with the same privilege caveats.
Today Python reaches the same protocol through libayatana-
appindicator (tray_indicator.py:37-53, with a GMenuModel path in tray_menu_model.py).
Assessment: ksni is a clean replacement and drops the
Ayatana/AppIndicator typelib fallback chain entirely. Host support
is unchanged: needs a StatusNotifierHost (KDE, Waybar/wlroots
panels, XFCE; GNOME Shell needs the AppIndicator extension) — on a
typical Void Wayland setup (Sway/Waybar, Wayfire, labwc) that is
normally present. Icon-theme/pixmap, menu, and tooltip semantics
need care but are bounded. Net: genuine simplification, low
effort. Second-strongest pro-Rust area.
Rust GTK3 bindings (gtk 0.18, gtk3-rs) state on their docs page:
"This project is UNMAINTAINED. Please take a look at gtk4-rs
instead!" https://docs.rs/gtk/0.18.2/gtk/
gtk-rs officially dropped them: "Support for gtk3-rs crates was
dropped. No more updates will be done on it. If you didn't yet,
please migrate to gtk4-rs." (gtk4-rs 0.8 / gtk-rs-core 0.19
release post, 2024-06-01) https://gtk-rs.org/blog/2024/06/01/new-release.html
GTK 3→4 itself removes/changes APIs the app relies on (migration
guide: https://docs.gtk.org/gtk4/migrating-3to4.html) — e.g. GtkStatusIcon was removed in GTK4, containers/event handling
changed; GtkLayerShell has a separate GTK4 variant (gtk4-layer-shell).
Assessment: there is no "same UI, new language" option. The
settings UI alone is ui/settings_dialog.py at 10,137 lines, plus
tray, overlay, dictation pad, first-run, transcript, logging
dialogs — ~25k lines under ui/ total. Rewriting that in gtk4-rs
means re-laying-out every dialog, re-doing GObject property/signal
wiring from idiomatic Python into Rust's subclassing/builder style,
and losing PyGObject's exact dynamic behaviours the tests pin down.
GTK4 parity is possible (gtk4-rs is actively maintained), but it
is a full UI rewrite, and GTK4-on-Wayland behavioural differences
(popovers, layer-shell overlay, global shortcuts absence) land at
the same time as the language change. Net: feasible only as a
GTK4 migration project; if that migration is ever wanted, doing it
in Python/PyGObject first (GTK4 is available to Python today) is
the lower-risk sequence.
6. Comparison summary
Area
Rust today (primary source)
vs current Python
Effort / risk
PipeWire capture
pipewire crate 0.10, freedesktop-hosted [§1]
Better than pw-record subprocess; device graph native
Inference (estimate, labelled as such): a like-for-like Rust port
is not a transliteration — GObject/GTK, asyncio-free threading,
and subprocess backends all change shape. At typical rewrite rates
for systems/desktop code with hardware-in-the-loop testing, parity
is a multi-person-month project (order 6–12+ months for one
developer to reach tested parity across compositors and audio
devices), during which the Python app either freezes or the port
chases a moving target (v0.18.5 and releasing regularly per docs/RELEASE_PROCESS.md). The 88k-line test suite — which pins
exactly the edge cases a rewrite would rediscover as bugs — does
not transfer; at best its scenarios become a checklist.
Performance motivation is weak on the evidence: hot paths are
already native (whisper.cpp/ggml, ONNX Runtime, PortAudio,
PipeWire). Python overhead is in orchestration/UI, where Rust
would save memory/startup, not transcription latency.
Rust packages use build_style=cargo; the style runs cargo auditable build --release --locked --target $RUST_TARGET
and installs with --offline --locked. Implication: a committed Cargo.lock and vendored/offline-available crates are
mandatory — the build cannot reach crates.io. Void handles this
for existing Rust packages, but every dependency must be
fetchable at template distfiles/vendor time.
Native -sys crates are the friction: whisper-rs-sys (cmake +
C++ + optional Vulkan), pipewire-sys/libspa-sys (bindgen
against Void's libpipewire headers — version skew vs the crate's
expectations is a real failure mode), gtk4-sys (Void gtk4 +
girepository versions), ort (needs ONNX Runtime binary or
source build strategy). Each adds hostmakedepends/ makedepends and cross-build (aarch64/musl — Void supports
glibc and musl) exposure that the current Python package
largely delegates to distro python3-gobject, gtk+3, wheels,
and the existing installer.
Cross/musl note: cargo.sh cross path adds rust-std for the
target; whisper.cpp/Vulkan under musl cross is the least-trodden
part. The Python app already documents Void installs
(DISTRO_COMPATIBILITY.md:196, distro package map §0) — a Rust
xbps template starts from zero, including the runit service
question for any ydotool-style helper and model-download policy
(Void packages cannot download models at build time; runtime
download with checksums, as today, must be preserved).
Acceptance: Void's CONTRIBUTING.md package requirements
(maintainer commitment, build from source, no vendored prebuilt
binaries) apply equally; nothing about Rust exempts the
whisper.cpp/ggml vendoring question — it would need linking
against a system whisper.cpp or a justified bundled build.
Net: xbps can package Rust well (many precedents), but this app's
native stack makes its template a project in itself, and musl +
Vulkan whisper.cpp is the riskiest cell. Python-on-Void already
works today.
9. Recommendation
Stay on Python for the shipping app. Spend effort where
users feel it: compositor injection coverage, Void/runit
ydotool service docs, PipeWire-native capture.
If Rust is strategically wanted, prototype the risk first,
in this order, as throwaway spikes on real Void Wayland
sessions (Sway/wlroots + KDE + GNOME):
a. injection spike (ashpd RemoteDesktop + virtual-keyboard +
IBus) proving Unicode dictation into GTK, Qt, Chromium, and
a terminal;
b. ksni tray + pipewire capture + whisper-rs dictation
loop as a headless daemon driven by the existing Python UI
(D-Bus boundary already exists: dbus_service.py);
c. only then judge a gtk4-rs UI, knowing it is a GTK4 migration
regardless of language.
Revisit a full rewrite only if (a) and (b) succeed and a GTK4
migration is independently decided. At that point, migrate
Python→GTK4 first or accept a long dual-maintenance window.
# Rust rebuild viability — Vocalinux on Void Linux (Wayland)
- Ticket: Gitea "Rust rebuild viability", map "Voculinux port"
- Question: Is a Rust rebuild of the Vocalinux Python GTK3 tray dictation
app sound on Void Linux Wayland, or stay on Python?
- Method: local checkout (`src/vocalinux/`, v0.18.5) read directly;
external claims traced to primary sources (official docs, protocol
XML, crate docs/source, Void `void-packages` source). Secondary
write-ups were used only as search leads, never as citations.
- Date of local measurement: this checkout, commands quoted in §7.
## Verdict
**Stay on Python. Do not do a wholesale Rust rebuild now.**
A Rust rebuild is *technically feasible* area by area — PipeWire,
whisper.cpp, tray, even GTK have real Rust bindings — but it is not
*sound* as a rewrite, for three load-bearing reasons:
1. **The hard problems are not language problems.** Wayland text
injection and global hotkeys are limited by compositor protocols
and portal consent, identically in Rust and Python (§3). Rust buys
nothing there, and the current app's value is precisely its
multi-backend fallback ladder (IBus, RemoteDesktop portal, wtype,
ydotool/uinput, xdotool/XWayland, clipboard paste).
2. **Rust forces a simultaneous GTK3 → GTK4 migration.** The Rust
GTK3 bindings are unmaintained and dropped upstream (§5), so
"port to Rust" secretly means "port language *and* toolkit *and*
tray model at once" — three rewrites stacked, each able to break
parity alone.
3. **Rebuild cost dwarfs plausible gain.** The app is ~48k LOC of
Python with ~88k LOC of tests (§7). Inference already runs in
native whisper.cpp via pywhispercpp; Python is orchestration
around C/C++ engines, PortAudio, and D-Bus. Rust would mainly
speed up code that is not the bottleneck, while discarding the
test suite that encodes years of compositor/device edge cases.
Sound Rust use, if desired: an *incremental* native sidecar or
prototype (PipeWire capture + whisper.cpp core behind the existing
Python UI, or a small tray-less dictation daemon) to prove
injection on the actual Void compositors targeted — not a flag-day
rewrite. See §9.
## 0. What exists today (local, primary)
| Fact | Evidence |
|---|---|
| Python ≥3.11, GTK 3 via PyGObject (distro `python3-gi`, never pip) | `pyproject.toml` (`requires-python`, deps); `AGENTS.md` toolchain section |
| Default engine whisper.cpp via `pywhispercpp>=1.5,<2`; optional OpenAI Whisper, Vosk, faster-whisper, Parakeet (sherpa-onnx), remote API | `pyproject.toml` `[project.optional-dependencies]`; `src/vocalinux/speech_recognition/recognition_manager.py` (5,691 lines) |
| Mic capture via PyAudio/PortAudio; system-audio via PipeWire `pw-record`/`pw-dump` subprocesses | `src/vocalinux/audio/capture.py` (1,207 lines), `src/vocalinux/audio/pipewire.py` (572 lines) |
| Silero VAD via `onnxruntime`, bundled `silero_vad.onnx` | `src/vocalinux/speech_recognition/silero_vad.py`, `data/` |
| Injection backends: IBus engine, XDG RemoteDesktop portal, wtype, ydotool, xdotool, clipboard | `src/vocalinux/text_injection/text_injector.py` (3,489), `ibus_engine.py` (1,882), `remote_desktop_portal.py` (788) |
| Tray via AyatanaAppIndicatorGlib 2.0 → AyatanaAppIndicator3 → AppIndicator3 fallback chain | `src/vocalinux/ui/tray_indicator.py:28-55` (1,983 lines), `main.py:272-288` |
| Hotkeys: pynput (X11), evdev (Wayland) | `src/vocalinux/ui/keyboard_backends/{pynput_backend,evdev_backend}.py` |
| GtkLayerShell overlay, libnotify, logind suspend, single-instance lock, D-Bus service | `ui/dictation_overlay.py`, `ui/notifications.py`, `suspend_handler.py`, `single_instance.py`, `dbus_service.py` |
| Void already a supported install target | `scripts/distro-package-map.yaml:177-183` (`python3-gobject, gtk+3, libappindicator-gtk3, portaudio-devel, xdotool, wtype, ydotool…`), `docs/DISTRO_COMPATIBILITY.md:196`, `install.d/` xbps branches |
| Size: 47,976 LOC Python (84 files), 87,649 LOC tests (155 files) | `wc -l` on `src/vocalinux/**/*.py`, `tests/test_*.py` (§7) |
Note the Void-specific wrinkle already present in Python: the
ydotool guidance in `text_injector.py:57-67` is systemd-centric,
while Void uses runit — language choice does not fix service
supervision either.
## 1. PipeWire capture — Rust: viable, arguably better
- The official `pipewire` crate (0.10.1 docs) is "Rust bindings to
`libpipewire`, the library for interacting with the pipewire
server", hosted under freedesktop's PipeWire namespace, with
`MainLoop`/`Context`/`Core`/`Registry` and stream APIs.
Source: https://pipewire.pages.freedesktop.org/pipewire-rs/pipewire/
- PipeWire's own docs are the reference for the C API those bindings
wrap: https://docs.pipewire.org/
- Alternative `cpal` (RustAudio) on Linux is ALSA-host based; PipeWire
is reached through the ALSA `pipewire` bridge device, not a native
graph API — fine for a mic, weaker for per-node/monitor capture.
Source: https://docs.rs/cpal/latest/cpal/ and
https://github.com/RustAudio/cpal
Assessment: native `pipewire` crate > today's `pw-record` subprocess
+ `pw-dump` JSON parsing (`audio/pipewire.py`) and > PortAudio device
negotiation pain documented at length in `audio/capture.py`
(format/channel aborts, Bluetooth SCO heap corruption comments).
But: the crate is pre-1.0, callback/`SPA` pod construction is
verbose unsafe-adjacent work, and device-hotplug/format edge cases
would need re-earning the hard way. **Net: genuine Rust win,
medium effort, low risk.** This is the strongest pro-Rust area.
## 2. whisper.cpp inference — Rust: viable, parity likely
- `whisper-rs` 0.16.0: "Rust bindings for whisper.cpp", safe wrapper
over `whisper-rs-sys` (bindgen + CMake build of whisper.cpp/ggml);
`WhisperContext::new_with_params` → `create_state` →
`WhisperState::full`.
Sources: https://docs.rs/whisper-rs/latest/whisper_rs/ ,
https://crates.io/crates/whisper-rs ,
repo https://codeberg.org/tazz4843/whisper-rs
- Upstream engine: https://github.com/ggml-org/whisper.cpp
- Silero VAD equivalent: `ort` (ONNX Runtime Rust bindings)
https://docs.rs/ort/latest/ort/ — replaces Python `onnxruntime`
used in `silero_vad.py`.
Assessment: same underlying C++ engine as pywhispercpp, so
transcription quality/speed should match; GPU backends (Vulkan etc.)
become Cargo features/CMake flags instead of today's installer
rebuild dance (`AGENTS.md`, `install.sh` PYWHISPERCPP_VERSION).
Costs: long native builds, `-sys` build needs cmake + C++ toolchain
at package-build time (matters for xbps, §8), and re-implementing
the 5.7k-line `recognition_manager.py` logic (VAD segmentation,
engine dispatch, model download/checksum pinning, keepalive/unload,
diarization via tinydiarize in `diarization.py`, GPU probing
workarounds for old pywhispercpp versions). Optional engines
(Vosk/Parakeet/faster-whisper/remote) each need their own Rust
story (sherpa-onnx has a C API; Vosk has a Rust binding; none is
free). **Net: feasible, no quality gain, high porting effort in the
orchestration layer around the engine.**
## 3. Wayland text injection — Rust: no advantage; the binding constraint
This is the make-or-break area, and it is compositor-bound, not
language-bound. There is still no stable, universal "type this
string into the focused app" Wayland protocol:
- `text-input-unstable-v3` is, by its own XML, *experimental*:
"Warning! The protocol described in this file is experimental and
backward incompatible changes may be made." It serves
compositor↔client text fields via an input method; a dictation
app cannot simply commit strings into arbitrary other clients.
Source (primary XML):
https://gitlab.freedesktop.org/wayland/wayland-protocols/-/raw/main/unstable/text-input/text-input-unstable-v3.xml
- `input-method-unstable-v2` + `virtual-keyboard-unstable-v1`
(the wlr-originated pair wtype uses) live outside stable
wayland-protocols and are not implemented by every compositor —
notably not by GNOME/Mutter — which is why the Python app probes
wtype support and falls back (`text_injector.py:186-205`).
Sources: https://gitlab.freedesktop.org/wayland/wayland-protocols/-/issues/39
(input-method hub issue listing text-input-v3 / input-method /
virtual-keyboard status),
https://wayland.app/protocols/virtual-keyboard-unstable-v1
- The cross-desktop sanctioned path is the XDG **RemoteDesktop
portal**: `NotifyKeyboardKeysym`/`NotifyKeyboardKeycode` on a
consented session ("May only be called if KEYBOARD access was
provided after starting the session"). Keysums, not Unicode
strings — hence today's layout/keysym mapping work.
Source: https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.RemoteDesktop.html
Rust client: `ashpd`
https://docs.rs/ashpd/latest/ashpd/desktop/remote_desktop/index.html
- Remaining fallbacks are identical in any language: IBus engine
(needs IBus as the active IM; KDE needs "IBus Wayland" virtual
keyboard selected — `text_injector.py:100-106`), ydotool/uinput
(needs `/dev/uinput` access, `input` group, a daemon — on Void a
runit service, not systemd), clipboard + paste shortcut.
Rust crates exist for the pieces (`wayland-client`,
`wayland-protocols` crates: https://docs.rs/wayland-protocols/latest/wayland_protocols/ ,
`zbus`/`ashpd` for portals/IBus-over-D-Bus), but every compositor
quirk, probe, and fallback currently encoded in ~6k lines of
`text_injection/` + `focused_window.py` would have to be re-derived
and re-tested per compositor (wlroots/Sway, KWin, Mutter at
minimum). **Net: parity is achievable in principle, most expensive
to re-prove, zero capability gain. Any Rust plan that starts with
UI or inference and leaves injection for later has the risk order
backwards.**
Global hotkeys have the same shape: no general Wayland global-key
grab; today's evdev backend (`/dev/input`, permissions) maps to
Rust `evdev` crate (https://docs.rs/evdev/latest/evdev/) one-for-one,
with the same privilege caveats.
## 4. StatusNotifier tray — Rust: viable, small
- The tray protocol is D-Bus `org.kde.StatusNotifierItem` /
`org.freedesktop.StatusNotifierWatcher` (+ `com.canonical.dbusmenu`);
it was never a GTK API. Spec (draft, freedesktop):
https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/
- Rust: `ksni` 0.3.x — "A Rust implementation of the
KDE/freedesktop StatusNotifierItem specification", pure-Rust on
zbus, implements the `Tray` trait.
Sources: https://docs.rs/ksni/latest/ksni/ ,
https://github.com/iovxw/ksni
- Today Python reaches the same protocol through libayatana-
appindicator (`tray_indicator.py:37-53`, with a GMenuModel path in
`tray_menu_model.py`).
Assessment: `ksni` is a clean replacement and drops the
Ayatana/AppIndicator typelib fallback chain entirely. Host support
is unchanged: needs a StatusNotifierHost (KDE, Waybar/wlroots
panels, XFCE; GNOME Shell needs the AppIndicator extension) — on a
typical Void Wayland setup (Sway/Waybar, Wayfire, labwc) that is
normally present. Icon-theme/pixmap, menu, and tooltip semantics
need care but are bounded. **Net: genuine simplification, low
effort.** Second-strongest pro-Rust area.
## 5. GTK UI parity — Rust: forces GTK4; largest hidden cost
- Rust GTK3 bindings (`gtk` 0.18, gtk3-rs) state on their docs page:
"This project is UNMAINTAINED. Please take a look at gtk4-rs
instead!" https://docs.rs/gtk/0.18.2/gtk/
- gtk-rs officially dropped them: "Support for gtk3-rs crates was
dropped. No more updates will be done on it. If you didn't yet,
please migrate to gtk4-rs." (gtk4-rs 0.8 / gtk-rs-core 0.19
release post, 2024-06-01)
https://gtk-rs.org/blog/2024/06/01/new-release.html
- Supported path is `gtk4-rs`: https://github.com/gtk-rs/gtk4-rs ,
docs https://docs.rs/gtk4/latest/gtk4/ ; idiomatic layer `relm4`
https://github.com/Relm4/Relm4 is optional.
- GTK 3→4 itself removes/changes APIs the app relies on (migration
guide: https://docs.gtk.org/gtk4/migrating-3to4.html) — e.g.
`GtkStatusIcon` was removed in GTK4, containers/event handling
changed; GtkLayerShell has a separate GTK4 variant (gtk4-layer-shell).
Assessment: there is no "same UI, new language" option. The
settings UI alone is `ui/settings_dialog.py` at 10,137 lines, plus
tray, overlay, dictation pad, first-run, transcript, logging
dialogs — ~25k lines under `ui/` total. Rewriting that in gtk4-rs
means re-laying-out every dialog, re-doing GObject property/signal
wiring from idiomatic Python into Rust's subclassing/builder style,
and losing PyGObject's exact dynamic behaviours the tests pin down.
GTK4 parity is *possible* (gtk4-rs is actively maintained), but it
is a full UI rewrite, and GTK4-on-Wayland behavioural differences
(popovers, layer-shell overlay, global shortcuts absence) land at
the same time as the language change. **Net: feasible only as a
GTK4 migration project; if that migration is ever wanted, doing it
in Python/PyGObject first (GTK4 is available to Python today) is
the lower-risk sequence.**
## 6. Comparison summary
| Area | Rust today (primary source) | vs current Python | Effort / risk |
|---|---|---|---|
| PipeWire capture | `pipewire` crate 0.10, freedesktop-hosted [§1] | Better than pw-record subprocess; device graph native | Med / Low |
| whisper.cpp | `whisper-rs` 0.16 over whisper.cpp [§2] | Same engine as pywhispercpp; no quality gain | High (orchestration) / Med |
| VAD (Silero) | `ort` ONNX bindings [§2] | Parity | Low / Low |
| Wayland injection | protocols unstable/compositor-gated; portal keysyms [§3] | **No gain**; fallback ladder must be rebuilt | High / **High** |
| Global hotkey | `evdev` crate = today's evdev backend [§3] | Parity, same perms | Low / Med |
| Tray (SNI) | `ksni` 0.3 on zbus [§4] | Simpler than Ayatana chain | Low / Low |
| UI toolkit | gtk3-rs dead; gtk4-rs maintained [§5] | Forces GTK4 rewrite of ~25k LOC UI | **Very high / High** |
| D-Bus/portals/notify/logind | `zbus`, `ashpd` | Parity with Gio/GLib usage | Med / Med |
| xbps packaging | `build_style=cargo`, `--locked --offline` [§8] | Native -sys deps complicate; Python path already Void-tested | Med–High / Med |
## 7. Rebuild cost (measured, not imagined)
Local measurement on this checkout:
```text
src/vocalinux/**/*.py 47,976 lines, 84 files
tests/test_*.py 87,649 lines, 155 files
recognition_manager.py 5,691 settings_dialog.py 10,137
text_injector.py 3,489 tray_indicator.py 1,983
ibus_engine.py 1,882 capture.py 1,207
```
Inference (estimate, labelled as such): a like-for-like Rust port
is not a transliteration — GObject/GTK, asyncio-free threading,
and subprocess backends all change shape. At typical rewrite rates
for systems/desktop code with hardware-in-the-loop testing, parity
is a multi-person-month project (order 6–12+ months for one
developer to reach *tested* parity across compositors and audio
devices), during which the Python app either freezes or the port
chases a moving target (v0.18.5 and releasing regularly per
`docs/RELEASE_PROCESS.md`). The 88k-line test suite — which pins
exactly the edge cases a rewrite would rediscover as bugs — does
not transfer; at best its *scenarios* become a checklist.
Performance motivation is weak on the evidence: hot paths are
already native (whisper.cpp/ggml, ONNX Runtime, PortAudio,
PipeWire). Python overhead is in orchestration/UI, where Rust
would save memory/startup, not transcription latency.
## 8. xbps packaging (Void)
Primary sources: Void `void-packages`
https://github.com/void-linux/void-packages/blob/master/Manual.md ,
`CONTRIBUTING.md` in the same repo, and the cargo build style
itself:
https://raw.githubusercontent.com/void-linux/void-packages/master/common/build-style/cargo.sh
- Rust packages use `build_style=cargo`; the style runs
`cargo auditable build --release --locked --target $RUST_TARGET`
and installs with `--offline --locked`. Implication: a committed
`Cargo.lock` and **vendored/offline-available crates** are
mandatory — the build cannot reach crates.io. Void handles this
for existing Rust packages, but every dependency must be
fetchable at template `distfiles`/vendor time.
- Native `-sys` crates are the friction: `whisper-rs-sys` (cmake +
C++ + optional Vulkan), `pipewire-sys`/`libspa-sys` (bindgen
against Void's libpipewire headers — version skew vs the crate's
expectations is a real failure mode), `gtk4-sys` (Void gtk4 +
girepository versions), `ort` (needs ONNX Runtime binary or
source build strategy). Each adds `hostmakedepends`/
`makedepends` and cross-build (aarch64/musl — Void supports
glibc **and musl**) exposure that the current Python package
largely delegates to distro `python3-gobject`, `gtk+3`, wheels,
and the existing installer.
- Cross/musl note: `cargo.sh` cross path adds `rust-std` for the
target; whisper.cpp/Vulkan under musl cross is the least-trodden
part. The Python app already documents Void installs
(`DISTRO_COMPATIBILITY.md:196`, distro package map §0) — a Rust
xbps template starts from zero, including the runit service
question for any ydotool-style helper and model-download policy
(Void packages cannot download models at build time; runtime
download with checksums, as today, must be preserved).
- Acceptance: Void's `CONTRIBUTING.md` package requirements
(maintainer commitment, build from source, no vendored prebuilt
binaries) apply equally; nothing about Rust exempts the
whisper.cpp/ggml vendoring question — it would need linking
against a system whisper.cpp or a justified bundled build.
**Net: xbps can package Rust well (many precedents), but this app's
native stack makes its template a project in itself, and musl +
Vulkan whisper.cpp is the riskiest cell. Python-on-Void already
works today.**
## 9. Recommendation
1. **Stay on Python** for the shipping app. Spend effort where
users feel it: compositor injection coverage, Void/runit
ydotool service docs, PipeWire-native capture.
2. If Rust is strategically wanted, **prototype the risk first**,
in this order, as throwaway spikes on real Void Wayland
sessions (Sway/wlroots + KDE + GNOME):
a. injection spike (`ashpd` RemoteDesktop + virtual-keyboard +
IBus) proving Unicode dictation into GTK, Qt, Chromium, and
a terminal;
b. `ksni` tray + `pipewire` capture + `whisper-rs` dictation
loop as a headless daemon driven by the existing Python UI
(D-Bus boundary already exists: `dbus_service.py`);
c. only then judge a gtk4-rs UI, knowing it is a GTK4 migration
regardless of language.
3. Revisit a full rewrite only if (a) and (b) succeed *and* a GTK4
migration is independently decided. At that point, migrate
Python→GTK4 first or accept a long dual-maintenance window.
## Sources (primary)
- Local: `pyproject.toml`, `AGENTS.md`, `src/vocalinux/{audio,speech_recognition,text_injection,ui}/`, `scripts/distro-package-map.yaml`, `docs/DISTRO_COMPATIBILITY.md`
- PipeWire Rust: https://pipewire.pages.freedesktop.org/pipewire-rs/pipewire/ ; https://docs.pipewire.org/
- cpal: https://docs.rs/cpal/latest/cpal/ ; https://github.com/RustAudio/cpal
- whisper-rs: https://docs.rs/whisper-rs/latest/whisper_rs/ ; https://crates.io/crates/whisper-rs ; https://codeberg.org/tazz4843/whisper-rs ; whisper.cpp: https://github.com/ggml-org/whisper.cpp
- ort: https://docs.rs/ort/latest/ort/
- Wayland protocols: https://gitlab.freedesktop.org/wayland/wayland-protocols/-/raw/main/unstable/text-input/text-input-unstable-v3.xml ; https://gitlab.freedesktop.org/wayland/wayland-protocols/-/issues/39 ; https://wayland.app/protocols/virtual-keyboard-unstable-v1 ; https://docs.rs/wayland-protocols/latest/wayland_protocols/
- XDG portal RemoteDesktop: https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.RemoteDesktop.html ; ashpd: https://docs.rs/ashpd/latest/ashpd/
- StatusNotifierItem: https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/ ; ksni: https://docs.rs/ksni/latest/ksni/ , https://github.com/iovxw/ksni
- gtk-rs: https://gtk-rs.org/blog/2024/06/01/new-release.html ; https://docs.rs/gtk/0.18.2/gtk/ ; https://github.com/gtk-rs/gtk4-rs ; GTK migration: https://docs.gtk.org/gtk4/migrating-3to4.html
- evdev crate: https://docs.rs/evdev/latest/evdev/
- Void: https://github.com/void-linux/void-packages/blob/master/Manual.md ; https://raw.githubusercontent.com/void-linux/void-packages/master/common/build-style/cargo.sh ; https://raw.githubusercontent.com/void-linux/void-packages/master/common/environment/build-style/cargo.sh
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.
Part of #1
Question
Is a Rust rebuild of the Vocalinux tray dictation app sound on Void Linux Wayland, or should Voculinux stay on Python? Compare capture (PipeWire), whisper.cpp inference, text injection, StatusNotifier tray, and GTK UI parity, plus rebuild cost and xbps packaging effort.
Rust rebuild viability — Vocalinux on Void Linux (Wayland)
app sound on Void Linux Wayland, or stay on Python?
src/vocalinux/, v0.18.5) read directly;external claims traced to primary sources (official docs, protocol
XML, crate docs/source, Void
void-packagessource). Secondarywrite-ups were used only as search leads, never as citations.
Verdict
Stay on Python. Do not do a wholesale Rust rebuild now.
A Rust rebuild is technically feasible area by area — PipeWire,
whisper.cpp, tray, even GTK have real Rust bindings — but it is not
sound as a rewrite, for three load-bearing reasons:
injection and global hotkeys are limited by compositor protocols
and portal consent, identically in Rust and Python (§3). Rust buys
nothing there, and the current app's value is precisely its
multi-backend fallback ladder (IBus, RemoteDesktop portal, wtype,
ydotool/uinput, xdotool/XWayland, clipboard paste).
GTK3 bindings are unmaintained and dropped upstream (§5), so
"port to Rust" secretly means "port language and toolkit and
tray model at once" — three rewrites stacked, each able to break
parity alone.
Python with ~88k LOC of tests (§7). Inference already runs in
native whisper.cpp via pywhispercpp; Python is orchestration
around C/C++ engines, PortAudio, and D-Bus. Rust would mainly
speed up code that is not the bottleneck, while discarding the
test suite that encodes years of compositor/device edge cases.
Sound Rust use, if desired: an incremental native sidecar or
prototype (PipeWire capture + whisper.cpp core behind the existing
Python UI, or a small tray-less dictation daemon) to prove
injection on the actual Void compositors targeted — not a flag-day
rewrite. See §9.
0. What exists today (local, primary)
python3-gi, never pip)pyproject.toml(requires-python, deps);AGENTS.mdtoolchain sectionpywhispercpp>=1.5,<2; optional OpenAI Whisper, Vosk, faster-whisper, Parakeet (sherpa-onnx), remote APIpyproject.toml[project.optional-dependencies];src/vocalinux/speech_recognition/recognition_manager.py(5,691 lines)pw-record/pw-dumpsubprocessessrc/vocalinux/audio/capture.py(1,207 lines),src/vocalinux/audio/pipewire.py(572 lines)onnxruntime, bundledsilero_vad.onnxsrc/vocalinux/speech_recognition/silero_vad.py,data/src/vocalinux/text_injection/text_injector.py(3,489),ibus_engine.py(1,882),remote_desktop_portal.py(788)src/vocalinux/ui/tray_indicator.py:28-55(1,983 lines),main.py:272-288src/vocalinux/ui/keyboard_backends/{pynput_backend,evdev_backend}.pyui/dictation_overlay.py,ui/notifications.py,suspend_handler.py,single_instance.py,dbus_service.pyscripts/distro-package-map.yaml:177-183(python3-gobject, gtk+3, libappindicator-gtk3, portaudio-devel, xdotool, wtype, ydotool…),docs/DISTRO_COMPATIBILITY.md:196,install.d/xbps brancheswc -lonsrc/vocalinux/**/*.py,tests/test_*.py(§7)Note the Void-specific wrinkle already present in Python: the
ydotool guidance in
text_injector.py:57-67is systemd-centric,while Void uses runit — language choice does not fix service
supervision either.
1. PipeWire capture — Rust: viable, arguably better
pipewirecrate (0.10.1 docs) is "Rust bindings tolibpipewire, the library for interacting with the pipewireserver", hosted under freedesktop's PipeWire namespace, with
MainLoop/Context/Core/Registryand stream APIs.Source: https://pipewire.pages.freedesktop.org/pipewire-rs/pipewire/
wrap: https://docs.pipewire.org/
cpal(RustAudio) on Linux is ALSA-host based; PipeWireis reached through the ALSA
pipewirebridge device, not a nativegraph API — fine for a mic, weaker for per-node/monitor capture.
Source: https://docs.rs/cpal/latest/cpal/ and
https://github.com/RustAudio/cpal
Assessment: native
pipewirecrate > today'spw-recordsubprocesspw-dumpJSON parsing (audio/pipewire.py) and > PortAudio devicenegotiation pain documented at length in
audio/capture.py(format/channel aborts, Bluetooth SCO heap corruption comments).
But: the crate is pre-1.0, callback/
SPApod construction isverbose unsafe-adjacent work, and device-hotplug/format edge cases
would need re-earning the hard way. Net: genuine Rust win,
medium effort, low risk. This is the strongest pro-Rust area.
2. whisper.cpp inference — Rust: viable, parity likely
whisper-rs0.16.0: "Rust bindings for whisper.cpp", safe wrapperover
whisper-rs-sys(bindgen + CMake build of whisper.cpp/ggml);WhisperContext::new_with_params→create_state→WhisperState::full.Sources: https://docs.rs/whisper-rs/latest/whisper_rs/ ,
https://crates.io/crates/whisper-rs ,
repo https://codeberg.org/tazz4843/whisper-rs
ort(ONNX Runtime Rust bindings)https://docs.rs/ort/latest/ort/ — replaces Python
onnxruntimeused in
silero_vad.py.Assessment: same underlying C++ engine as pywhispercpp, so
transcription quality/speed should match; GPU backends (Vulkan etc.)
become Cargo features/CMake flags instead of today's installer
rebuild dance (
AGENTS.md,install.shPYWHISPERCPP_VERSION).Costs: long native builds,
-sysbuild needs cmake + C++ toolchainat package-build time (matters for xbps, §8), and re-implementing
the 5.7k-line
recognition_manager.pylogic (VAD segmentation,engine dispatch, model download/checksum pinning, keepalive/unload,
diarization via tinydiarize in
diarization.py, GPU probingworkarounds for old pywhispercpp versions). Optional engines
(Vosk/Parakeet/faster-whisper/remote) each need their own Rust
story (sherpa-onnx has a C API; Vosk has a Rust binding; none is
free). Net: feasible, no quality gain, high porting effort in the
orchestration layer around the engine.
3. Wayland text injection — Rust: no advantage; the binding constraint
This is the make-or-break area, and it is compositor-bound, not
language-bound. There is still no stable, universal "type this
string into the focused app" Wayland protocol:
text-input-unstable-v3is, by its own XML, experimental:"Warning! The protocol described in this file is experimental and
backward incompatible changes may be made." It serves
compositor↔client text fields via an input method; a dictation
app cannot simply commit strings into arbitrary other clients.
Source (primary XML):
https://gitlab.freedesktop.org/wayland/wayland-protocols/-/raw/main/unstable/text-input/text-input-unstable-v3.xml
input-method-unstable-v2+virtual-keyboard-unstable-v1(the wlr-originated pair wtype uses) live outside stable
wayland-protocols and are not implemented by every compositor —
notably not by GNOME/Mutter — which is why the Python app probes
wtype support and falls back (
text_injector.py:186-205).Sources: https://gitlab.freedesktop.org/wayland/wayland-protocols/-/issues/39
(input-method hub issue listing text-input-v3 / input-method /
virtual-keyboard status),
https://wayland.app/protocols/virtual-keyboard-unstable-v1
portal:
NotifyKeyboardKeysym/NotifyKeyboardKeycodeon aconsented session ("May only be called if KEYBOARD access was
provided after starting the session"). Keysums, not Unicode
strings — hence today's layout/keysym mapping work.
Source: https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.RemoteDesktop.html
Rust client:
ashpdhttps://docs.rs/ashpd/latest/ashpd/desktop/remote_desktop/index.html
(needs IBus as the active IM; KDE needs "IBus Wayland" virtual
keyboard selected —
text_injector.py:100-106), ydotool/uinput(needs
/dev/uinputaccess,inputgroup, a daemon — on Void arunit service, not systemd), clipboard + paste shortcut.
Rust crates exist for the pieces (
wayland-client,wayland-protocolscrates: https://docs.rs/wayland-protocols/latest/wayland_protocols/ ,zbus/ashpdfor portals/IBus-over-D-Bus), but every compositorquirk, probe, and fallback currently encoded in ~6k lines of
text_injection/+focused_window.pywould have to be re-derivedand re-tested per compositor (wlroots/Sway, KWin, Mutter at
minimum). Net: parity is achievable in principle, most expensive
to re-prove, zero capability gain. Any Rust plan that starts with
UI or inference and leaves injection for later has the risk order
backwards.
Global hotkeys have the same shape: no general Wayland global-key
grab; today's evdev backend (
/dev/input, permissions) maps toRust
evdevcrate (https://docs.rs/evdev/latest/evdev/) one-for-one,with the same privilege caveats.
4. StatusNotifier tray — Rust: viable, small
org.kde.StatusNotifierItem/org.freedesktop.StatusNotifierWatcher(+com.canonical.dbusmenu);it was never a GTK API. Spec (draft, freedesktop):
https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/
ksni0.3.x — "A Rust implementation of theKDE/freedesktop StatusNotifierItem specification", pure-Rust on
zbus, implements the
Traytrait.Sources: https://docs.rs/ksni/latest/ksni/ ,
https://github.com/iovxw/ksni
appindicator (
tray_indicator.py:37-53, with a GMenuModel path intray_menu_model.py).Assessment:
ksniis a clean replacement and drops theAyatana/AppIndicator typelib fallback chain entirely. Host support
is unchanged: needs a StatusNotifierHost (KDE, Waybar/wlroots
panels, XFCE; GNOME Shell needs the AppIndicator extension) — on a
typical Void Wayland setup (Sway/Waybar, Wayfire, labwc) that is
normally present. Icon-theme/pixmap, menu, and tooltip semantics
need care but are bounded. Net: genuine simplification, low
effort. Second-strongest pro-Rust area.
5. GTK UI parity — Rust: forces GTK4; largest hidden cost
gtk0.18, gtk3-rs) state on their docs page:"This project is UNMAINTAINED. Please take a look at gtk4-rs
instead!" https://docs.rs/gtk/0.18.2/gtk/
dropped. No more updates will be done on it. If you didn't yet,
please migrate to gtk4-rs." (gtk4-rs 0.8 / gtk-rs-core 0.19
release post, 2024-06-01)
https://gtk-rs.org/blog/2024/06/01/new-release.html
gtk4-rs: https://github.com/gtk-rs/gtk4-rs ,docs https://docs.rs/gtk4/latest/gtk4/ ; idiomatic layer
relm4https://github.com/Relm4/Relm4 is optional.
guide: https://docs.gtk.org/gtk4/migrating-3to4.html) — e.g.
GtkStatusIconwas removed in GTK4, containers/event handlingchanged; GtkLayerShell has a separate GTK4 variant (gtk4-layer-shell).
Assessment: there is no "same UI, new language" option. The
settings UI alone is
ui/settings_dialog.pyat 10,137 lines, plustray, overlay, dictation pad, first-run, transcript, logging
dialogs — ~25k lines under
ui/total. Rewriting that in gtk4-rsmeans re-laying-out every dialog, re-doing GObject property/signal
wiring from idiomatic Python into Rust's subclassing/builder style,
and losing PyGObject's exact dynamic behaviours the tests pin down.
GTK4 parity is possible (gtk4-rs is actively maintained), but it
is a full UI rewrite, and GTK4-on-Wayland behavioural differences
(popovers, layer-shell overlay, global shortcuts absence) land at
the same time as the language change. Net: feasible only as a
GTK4 migration project; if that migration is ever wanted, doing it
in Python/PyGObject first (GTK4 is available to Python today) is
the lower-risk sequence.
6. Comparison summary
pipewirecrate 0.10, freedesktop-hosted [§1]whisper-rs0.16 over whisper.cpp [§2]ortONNX bindings [§2]evdevcrate = today's evdev backend [§3]ksni0.3 on zbus [§4]zbus,ashpdbuild_style=cargo,--locked --offline[§8]7. Rebuild cost (measured, not imagined)
Local measurement on this checkout:
Inference (estimate, labelled as such): a like-for-like Rust port
is not a transliteration — GObject/GTK, asyncio-free threading,
and subprocess backends all change shape. At typical rewrite rates
for systems/desktop code with hardware-in-the-loop testing, parity
is a multi-person-month project (order 6–12+ months for one
developer to reach tested parity across compositors and audio
devices), during which the Python app either freezes or the port
chases a moving target (v0.18.5 and releasing regularly per
docs/RELEASE_PROCESS.md). The 88k-line test suite — which pinsexactly the edge cases a rewrite would rediscover as bugs — does
not transfer; at best its scenarios become a checklist.
Performance motivation is weak on the evidence: hot paths are
already native (whisper.cpp/ggml, ONNX Runtime, PortAudio,
PipeWire). Python overhead is in orchestration/UI, where Rust
would save memory/startup, not transcription latency.
8. xbps packaging (Void)
Primary sources: Void
void-packageshttps://github.com/void-linux/void-packages/blob/master/Manual.md ,
CONTRIBUTING.mdin the same repo, and the cargo build styleitself:
https://raw.githubusercontent.com/void-linux/void-packages/master/common/build-style/cargo.sh
build_style=cargo; the style runscargo auditable build --release --locked --target $RUST_TARGETand installs with
--offline --locked. Implication: a committedCargo.lockand vendored/offline-available crates aremandatory — the build cannot reach crates.io. Void handles this
for existing Rust packages, but every dependency must be
fetchable at template
distfiles/vendor time.-syscrates are the friction:whisper-rs-sys(cmake +C++ + optional Vulkan),
pipewire-sys/libspa-sys(bindgenagainst Void's libpipewire headers — version skew vs the crate's
expectations is a real failure mode),
gtk4-sys(Void gtk4 +girepository versions),
ort(needs ONNX Runtime binary orsource build strategy). Each adds
hostmakedepends/makedependsand cross-build (aarch64/musl — Void supportsglibc and musl) exposure that the current Python package
largely delegates to distro
python3-gobject,gtk+3, wheels,and the existing installer.
cargo.shcross path addsrust-stdfor thetarget; whisper.cpp/Vulkan under musl cross is the least-trodden
part. The Python app already documents Void installs
(
DISTRO_COMPATIBILITY.md:196, distro package map §0) — a Rustxbps template starts from zero, including the runit service
question for any ydotool-style helper and model-download policy
(Void packages cannot download models at build time; runtime
download with checksums, as today, must be preserved).
CONTRIBUTING.mdpackage requirements(maintainer commitment, build from source, no vendored prebuilt
binaries) apply equally; nothing about Rust exempts the
whisper.cpp/ggml vendoring question — it would need linking
against a system whisper.cpp or a justified bundled build.
Net: xbps can package Rust well (many precedents), but this app's
native stack makes its template a project in itself, and musl +
Vulkan whisper.cpp is the riskiest cell. Python-on-Void already
works today.
9. Recommendation
users feel it: compositor injection coverage, Void/runit
ydotool service docs, PipeWire-native capture.
in this order, as throwaway spikes on real Void Wayland
sessions (Sway/wlroots + KDE + GNOME):
a. injection spike (
ashpdRemoteDesktop + virtual-keyboard +IBus) proving Unicode dictation into GTK, Qt, Chromium, and
a terminal;
b.
ksnitray +pipewirecapture +whisper-rsdictationloop as a headless daemon driven by the existing Python UI
(D-Bus boundary already exists:
dbus_service.py);c. only then judge a gtk4-rs UI, knowing it is a GTK4 migration
regardless of language.
migration is independently decided. At that point, migrate
Python→GTK4 first or accept a long dual-maintenance window.
Sources (primary)
pyproject.toml,AGENTS.md,src/vocalinux/{audio,speech_recognition,text_injection,ui}/,scripts/distro-package-map.yaml,docs/DISTRO_COMPATIBILITY.md