A one-shot setup for getting Handy — the local voice-input app — working properly on Fedora 43 with GNOME on Wayland, with state-aware GNOME notifications and a reliable global hotkey.
Handy is a great tool, but the Linux + Wayland path has a few sharp edges that aren't obvious until you hit them. This repo packages the fixes.
- A reliable global hotkey.
Ctrl+Space(configurable) toggles transcription from anywhere, routed through a GNOME custom shortcut — not Handy's built-in hotkey, which is unreliable on Wayland. - State-aware GNOME notifications. You see "🎙 Recording…" when it starts and "Stopped" when it ends, with proper mic icons.
- Debounced toggles. GNOME occasionally double-fires a shortcut; the wrapper swallows anything within 1 second of the last press.
- A working
ydotoolduser service. Handy needsydotoolfor synthetic keystroke injection on Wayland; this sets it up correctly as a user-session service.
Wayland intentionally prevents apps from registering global hotkeys or injecting keystrokes into other apps — these were X11 footguns that let malware spy on keyboards. That security model breaks any voice-input tool that wants to (a) listen for a hotkey anywhere and (b) type the transcribed text into the focused app.
The workarounds form a three-layer stack:
| Layer | Problem | Solution |
|---|---|---|
| Global hotkey | Apps can't register global keys under Wayland | Use a GNOME custom shortcut → compositor delivers the key to our script |
| Keystroke injection | Apps can't synthesize input into other apps | ydotoold writes to /dev/uinput (kernel-level, bypasses Wayland) |
| State feedback | Handy has no status CLI or system tray that works under GNOME | Tail Handy's log to derive state; notify-send for visible feedback |
Each piece solves a distinct constraint. Removing any one breaks the flow.
- Fedora 43 (should work on any distro with GNOME on Wayland, but only tested on Fedora)
- GNOME desktop on Wayland session
- Handy installed — grab the
.rpmfrom their releases ydotool—sudo dnf install ydotool- Your user must be in the
inputgroup (the installer checks this and offers to fix it) libnotify(providesnotify-send) — usually already installed on GNOME
git clone https://github.com/<you>/handy-fedora-wayland.git
cd handy-fedora-wayland
./install.shThe installer is idempotent — safe to re-run. It will:
- Verify required commands are on
PATH. - Check that your user is in the
inputgroup (and offer to add you if not; you'll need to log out/in once). - Install
handy-toggle.shto~/.local/bin/. - Install and enable the
ydotooldsystemd user service. - Register
Ctrl+Spaceas a GNOME custom keybinding that invokes the toggle script.
To pick a different key, set HANDY_KEYBINDING before running — using GNOME's <Modifier>key syntax:
HANDY_KEYBINDING='<Super>v' ./install.shHandy itself manages its own autostart via its settings panel ("Launch on startup"); this repo doesn't touch that.
./uninstall.shRemoves the toggle script, the systemd user unit, and the GNOME keybinding. Leaves Handy and ydotool packages alone.
Upstream Handy transcribes after you release the hotkey — a long dictation means waiting for the whole clip to process. PR #1173 adds VAD-chunked live streaming (text pastes as you talk), but it's stalled behind an upstream feature freeze as of April 2026.
If you want streaming today, build from the fork at DylPorter/Handy, which has PR #1173 rebased onto latest main:
# Extra build dep on Fedora (beyond the usual webkit2gtk/gtk3/etc.)
sudo dnf install -y gtk-layer-shell-devel
# Clone, build, install
git clone https://github.com/DylPorter/Handy.git ~/Handy-streaming
cd ~/Handy-streaming
git checkout streaming-rebase
npm install --ignore-scripts
# The Tauri config calls `bun`; swap to npm if you don't have bun installed:
sed -i 's|bun run |npm run |g' src-tauri/tauri.conf.json
# Generate TypeScript bindings (runs the debug binary briefly so tauri-specta
# can write src/bindings.ts, then exits)
(cd src-tauri && cargo build && timeout 5 ./target/debug/handy || true)
# Build the RPM
npm run tauri build
# Install
sudo dnf install -y src-tauri/target/release/bundle/rpm/Handy-*.x86_64.rpmThen restart Handy and go to Settings → Advanced → Transcription Mode. Stream (VAD-chunked, live paste) is the most useful setting for long dictations. Realtime uses fixed-duration chunks (faster first-chunk latency, more boundary errors). Standard keeps the original behavior.
Accuracy caveat: Whisper is fundamentally non-streaming, so chunked modes lose a little cross-sentence context. VAD-chunking at natural pauses minimizes this. If you can tolerate the tradeoff, the UX win on long recordings is significant.
Invoked by GNOME on hotkey press. Reads the last 200 lines of Handy's log (~/.local/share/com.pais.handy/logs/handy.log), greps for the most recent Recording started / Microphone stream stopped line, fires the appropriate notification, then runs handy --toggle-transcription.
Debounce uses a lockfile in /tmp with nanosecond timestamps. The default 1-second window can be overridden with HANDY_TOGGLE_DEBOUNCE_MS.
All output is appended to /tmp/handy-toggle.log for debugging.
A minimal user unit that runs /usr/bin/ydotoold as your user. Because your user is in the input group, the daemon can open /dev/uinput without root. The daemon creates a socket at /tmp/.ydotool_socket that the ydotool client — and Handy, internally — connect to for keystroke injection.
Fedora's ydotool RPM also ships a system service (ydotool.service), but a user service gives cleaner session lifecycle: the daemon dies when you log out, no PID files to clean up, and it runs under your own session so any environment tweaks are local to you.
The installer appends a new entry to org.gnome.settings-daemon.plugins.media-keys.custom-keybindings, finding the next free customN slot. On uninstall, it reverses the append without disturbing your other custom shortcuts.
Hotkey does nothing.
systemctl --user status ydotoold— is the service running?tail /tmp/handy-toggle.log— is the script being called at all? If not, the GNOME keybinding isn't registered (checkgnome-control-center→ Keyboard → View and Customize Shortcuts → Custom Shortcuts).
Notifications appear but no text is typed.
- Your user isn't in the
inputgroup, or you haven't re-logged-in since adding yourself. Rungroupsto verify. - Try
ydotool type "hello"in a terminal to isolate — if that fails,ydotooldisn't working.
ydotoold.service stuck in activating (auto-restart) with error: Another ydotoold is running with the same socket.
- An orphan
ydotooldfrom a previous session is holding the socket. The installer now cleans this up automatically on re-run, but you can also fix it manually:pkill -u "$USER" -x ydotoold && systemctl --user restart ydotoold.
"Recording…" notification appears but transcription doesn't start.
- Check
~/.local/share/com.pais.handy/logs/handy.logfor errors. - Make sure Handy is running:
pgrep -a handy.
State-detection misbehaves (shows wrong state).
- Handy's log format may have changed in a newer release. The wrapper greps for
Recording startedandMicrophone stream stopped— adjust these patterns inhandy-toggle.shif upstream renamed the log lines.
This repo is MIT-licensed and does not include any of Handy's source code. Handy itself is a separate project by CJ Pais, also MIT-licensed, available at github.com/cjpais/Handy. All credit for the voice-input tool goes to the upstream author — this repo is only Linux packaging glue.