Repository navigation
Description
wispr-flow --doctor output
doctor
Wispr Flow Diagnostics
==========================
Session: wayland Desktop: ubuntu:GNOME Arch: x86_64
Helper: /tmp/.mount_XXXX/usr/lib/wispr-flow/resources/Release/wispr-flow-linux-helper
Display / Session
[PASS] Display server: Wayland (WAYLAND_DISPLAY=wayland-0)
Mode: Electron Ozone auto-detect (default)
Desktop: ubuntu:GNOME [GNOME (Mutter)]
Text Injection (uinput)
[PASS] /dev/uinput: writable (keystroke injection available)
[WARN] input group: current user is NOT a member
Harmless if /dev/uinput is already writable above.
Otherwise: sudo usermod -aG input "$USER" (then re-login)
Push-to-Talk (input monitor)
[PASS] /dev/input: 16/16 event device(s) readable (push-to-talk available)
Clipboard
[PASS] Clipboard: wl-copy and wl-paste present (Wayland)
Accessibility (AT-SPI)
[PASS] AT-SPI: accessibility enabled (toolkit-accessibility=true)
GNOME Window Bridge
[PASS] GNOME extension: active ([email protected])
Helper & Runtime
[PASS] Helper binary: launches OK (wispr-flow-linux-helper 0.1.2)
[PASS] Electron runtime: v42.3.0 (/tmp/.mount_XXXX/usr/lib/wispr-flow/wispr-flow)
chrome-sandbox: AppImage runs with --no-sandbox (setuid not required)
[WARN] Desktop entry: not found (expected for AppImage installs)
[PASS] Disk space: 162459MB free on the config partition
[PASS] SingletonLock: held by running process (PID 264811)
[PASS] Recent crashes: none in launcher.log tail
All checks passed.
Distribution
Ubuntu 24.04.5 LTS
Desktop environment
GNOME Shell 46.0 (Mutter)
Session type
Wayland
Install format
AppImage: wispr-flow-1.6.897-1.0.3-x86_64.AppImage (helper 0.1.2; v1.0.4 pins the same helper in helper-version.txt)
What happened
After a dictation is pasted, the keyboard sometimes becomes unusable. Every key acts as Ctrl+key, and Alt+Tab opens GNOME's Ctrl+Alt+Tab "switch system controls" panel. When Alt is the stuck key, Esc cycles windows (Alt+Esc). Pressing the key again doesn't clear it, and later pastes don't either.
EVIOCGKEY on the Wispr Flow Linux Helper input device shows the modifier (for example KEY_LEFTCTRL) still held. Writing a release (KEY_LEFTCTRL 0 + SYN_REPORT) to that event node, or restarting the helper, fixes it.
Trigger: a modifier is physically held at the moment the helper injects the paste. For example, after the default Ctrl+Win push-to-talk you release Win first and are still holding Ctrl when the text lands.
Steps to reproduce
- Hold Ctrl+Win and dictate a sentence.
- Release Win, but keep holding Ctrl until the text is pasted.
- Release Ctrl and type.
Typing now produces Ctrl shortcuts, and Ctrl is still down on the helper's virtual keyboard.
Headless and deterministic, with no real keyboard involved: create a uinput keyboard, EVIOCGRAB it so the compositor ignores it, and hold KEY_LEFTALT on it. Then send the helper a PasteText request. The helper's virtual keyboard emits:
KEY_LEFTCTRL 1, KEY_V 1, KEY_V 0, KEY_LEFTCTRL 0, KEY_LEFTALT 1
Afterwards, EVIOCGKEY on it reports KEY_LEFTALT held.
Expected vs actual
Expected: after a paste, the helper's virtual keyboard holds no keys.
Actual: any modifier held on any keyboard at paste time stays pressed on the virtual keyboard until the helper exits.
Logs / errors
The helper's stderr shows nothing. The trace above was read from the helper's event device.
Anything else
Root cause: UInput::chord() in src/backend/uinput.rs (helper main @ fa93fcf, v0.1.2):
let held = held_modifiers(); // EVIOCGKEY on *every* /dev/input/event*
for &m in &held { let _ = self.key(m, false); } // "release first"
// ... Ctrl down, V down, V up, Ctrl up ...
for &m in held.iter().rev() { let _ = self.key(m, true); } // "restore"This mirrors the Windows helper's GetKeyState dance, but on Linux key state is per device:
- Release step: the key-ups go to the helper's own uinput device, where those keys aren't down. The kernel drops them, so they never release the physical key. They don't appear in the trace above.
- Restore step: the key-downs are new presses on the uinput device. Nothing ever releases them, because the user's later key-up arrives on the physical device. libinput and Mutter then keep the modifier down for the whole seat.
- Self-perpetuating:
held_modifiers()also scans the helper's own device. Every later paste sees the stuck key there and presses it again.
This should affect any compositor that uses libinput. The X11 backend does a plain chord and is not affected. Its TODO to add the same dance via XQueryKeymap would be worth dropping.
Proposed fix: remove the dance from the uinput backend. The release half can't work from a separate device, and the restore half is what causes the bug.
--- a/src/backend/uinput.rs
+++ b/src/backend/uinput.rs
@@ pub fn chord(&mut self, key: u16, mods: &[u16]) -> Result<()> {
- let held = held_modifiers();
- for &m in &held {
- let _ = self.key(m, false);
- }
for &m in mods {
self.key(m, true)?;
}
self.key(key, true)?;
self.key(key, false)?;
for &m in mods.iter().rev() {
self.key(m, false)?;
}
- // Restore physically-held modifiers (reverse order). Best-effort.
- for &m in held.iter().rev() {
- let _ = self.key(m, true);
- }
Ok(())
}held_modifiers() and keymap::EVDEV_MODIFIERS are then unused and can be removed to keep clippy clean.
With a modifier held, the paste becomes for example Ctrl+Shift+V. That already happens today, because the release step never worked. If you want to neutralise held modifiers properly, wait briefly before injecting (bounded, say up to 300–500 ms) until no physical modifier is down. That avoids touching another device's state.
Verified on the shipped 0.1.2 binary, with a local change equivalent to the diff (held_modifiers() forced to return nothing):
- The headless repro now emits only
Ctrl↓ V↓ V↑ Ctrl↑and leaves nothing held. - A live dictation with Ctrl held through the paste no longer sticks.
CheckStaleKeysanswers are unchanged.
Activity
switch87 commentedon Oct 4, 2026
Confirming this on Gentoo (amd64), KDE Plasma 6.7.5, Wayland — Wispr Flow 1.6.957, wrapper 1.0.4, helper 0.1.2 built from source. Same symptom: Ctrl stays logically held after a paste if it is still down at injection time.
Our Gentoo package (
app-accessibility/wispr-flow-bin) carries the equivalent of wispr-flow-linux/helper#14 (dropping the held-modifier release/restore inUInput::chord()) as a distro patch; with it the stuck modifier no longer occurs. +1 for merging #14 so the patch can be dropped.hassanaziz0012 commentedon Oct 6, 2026
Same issue happens to me (Gnome, Arch Linux).
switch87 commentedon Oct 13, 2026
@hassanaziz0012 the fix is in wispr-flow-linux/helper#14 (it drops the release/restore of held modifiers in
UInput::chord()). Until it's merged, building the helper with that patch applied removes the stuck modifier.aaddrick commentedon Oct 27, 2026
Thanks for the precise root cause and the headless repro; the code matched what you describe. The fix is wispr-flow-linux/helper#14, now merged, and it reaches the packages once it is in a helper release and the pin here moves. Until then,
pkill -f wispr-flow-linux-helperclears a stuck key without logging out, since the app restarts the helper.I'm working through these with Claude Duet, which is driving this on my behalf.
Generated by Claude Code
Bump the helper to v0.1.3
Bump the helper to v0.1.3
Merge pull request wispr-flow-linux#14 from khalil-omer/fix/chord-str…