Skip to content

feat: Linux support + configurable push-to-talk key - #5

Open
Xokuss0 wants to merge 2 commits into
Blueturboguy07:mainfrom
Xokuss0:main
Open

Xokuss0 wants to merge 2 commits into
Blueturboguy07:mainfrom
Xokuss0:main

Conversation

@Xokuss0

@Xokuss0 Xokuss0 commented Aug 2, 2026

Copy link
Copy Markdown

Summary

Adds full Linux platform support to WhimprFlow, plus a configurable push-to-talk key setting.

Linux Support

  • New module: src-tauri/src/linux.rs — full dictation pipeline:
    • Global keyboard listener via rdev (X11 + Wayland) for push-to-talk
    • Clipboard paste via ydotool (Wayland) / xdotool (X11 fallback)
    • Audio capture → Whisper ASR → cleanup LLM → paste
    • XDG-compliant data paths (~/.local/share/WhimprFlow/)
    • Frontmost-app detection via xdotool
  • Wired up: hotkey.rs, lib.rs, paste.rs, appctx.rs, local_llm.rs all updated with Linux cfg branches
  • Dependencies: rdev for global keyboard, shell-out to ydotool/xdotool for paste (no C library deps)
  • Fixed whisper-rs from v0.12 → v0.16 for working Linux build

Configurable Push-to-Talk Key

  • New push_to_talk_key field in Settings (default: ControlRight)
  • Linux keyboard listener reads the configured key at startup
  • UI key picker in Settings pane with 21 options (Ctrl/Alt/Shift/Super/CapsLock/F1-F12)

Tested

  • cargo check + cargo build pass cleanly on Arch Linux (Wayland)
  • Whisper ASR model loads and transcribes
  • Keyboard listener starts and reports configured key
  • ydotoold paste verified working

Xokuss0 added 2 commits August 2, 2026 17:00
- Create linux.rs platform module (rdev hotkey + ydotool/xdotool paste + full pipeline)
- Wire up hotkey.rs to export from linux module on Linux
- Update paste.rs for Linux (arboard clipboard + ydotool Ctrl+V)
- Update appctx.rs for Linux frontmost-app detection via xdotool
- Update local_llm.rs with XDG-compliant paths on Linux
- Update lib.rs with Linux permission stubs and xdg-open
- Add rdev dependency for global keyboard listening
- Update tauri.conf.json for Linux bundle targets
- Fix whisper-rs to v0.16 for working Linux build
- Add push_to_talk_key field to whimpr_core::Settings (default ControlRight)
- Linux: use configured key from settings in rdev keyboard listener
- Parse key string to rdev::Key with Alt/AltGr/Shift/Ctrl/Meta/CapsLock/F1-F12
- Add key picker UI in Settings pane with 21 key options
- Human-readable key labels in startup log messages

@AnttonioAzevedo AnttonioAzevedo left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Linux support looks solid overall, but flagging two real functional bugs (PTT race condition orphaning the mic capture handle, and a silent fallback in parse_ptt_key that contradicts its own doc comment) plus two duplication/architecture cleanups (reusing foreground_app() instead of re-implementing it, and migrating onto the shared whimpr_core::StateMachine instead of hand-rolled recording state — which would also incidentally fix the race).

Comment thread src-tauri/src/linux.rs
let _ = now_ms();
eprintln!("[whimpr:linux] PTT DOWN — starting capture");
emit_bar("recording");
std::thread::spawn(|| match whimpr_audio::start(|_: &[f32]| {}) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Race condition: PTT-down opens the mic on a spawned thread and only stores the CaptureHandle into CAPTURE after whimpr_audio::start() succeeds. If PTT-up fires before that thread finishes, on_ptt_up()'s take() returns None (nothing to stop), and the spawned thread then writes Some(handle) into CAPTURE afterward — orphaned and never stopped. The mic stays open indefinitely, and the next PTT cycle's take() grabs this stale handle instead of the new one.

This happens on a normal quick tap of the PTT key, not just under artificial load — worth fixing before merge (e.g. by registering the handle synchronously before spawning, or by having PTT-up cancel a still-in-flight open).

Comment thread src-tauri/src/linux.rs
"F10" => Some(F10),
"F11" => Some(F11),
"F12" => Some(F12),
_ => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parse_ptt_key's doc comment says it "Returns None for unrecognized keys," but the fallback arm here returns Some(ControlRight) instead — silently substituting a different key. This makes the "invalid push-to-talk key" error branch in spawn_keyboard_listener (lines ~369-372) dead code.

If settings.json ever gets a bad/corrupted push_to_talk_key value, the listener will silently bind to Right Ctrl instead of surfacing an error, and the user has no way to discover their configured key was ignored. Please make the fallback actually return None per the doc comment.

Comment thread src-tauri/src/linux.rs

// ── The push-to-talk pipeline ───────────────────────────────────────────────────

fn on_ptt_down() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Linux platform hand-rolls its own recording state (a bare AtomicBool plus a raw OnceLock<Mutex<Option<CaptureHandle>>>) instead of reusing whimpr_core::StateMachine, which the macOS implementation (src-tauri/src/hotkey.rs) already uses to manage the same recording/transcribing/done/idle lifecycle.

This ad hoc state is the direct cause of the orphaned-capture race flagged above — the shared StateMachine was presumably built to avoid exactly this class of bug. Any lifecycle fix made to StateMachine won't benefit Linux, and bugs specific to this duplicate implementation have to be found and fixed twice. Recommend migrating Linux onto StateMachine rather than maintaining a parallel state model.

Comment thread src-tauri/src/appctx.rs
#[cfg(target_os = "linux")]
pub fn frontmost_bundle_id() -> Option<String> {
// Try xdotool first (most reliable on X11)
if let Ok(out) = std::process::Command::new("xdotool")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

frontmost_bundle_id() re-implements the same xdotool getactivewindow / getwindowclassname logic that already exists as foreground_app() in src-tauri/src/linux.rs (~lines 99-111), instead of calling that helper.

Two independent copies of the xdotool-invocation logic now exist — a future fix to one (e.g. the xprop fallback linux.rs already has) won't be applied to the other, so frontmost-app detection will silently diverge between call sites. Worth extracting/reusing the existing helper instead of duplicating it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants