Repository navigation
Windows 11 support - #7
Draft
itsdevcoffee wants to merge 45 commits into
Draft
itsdevcoffee wants to merge 45 commits into
itsdevcoffee wants to merge 45 commits into
Conversation
Replace std::os::unix::net with interprocess local sockets: the same socket file on Unix, a per-user named pipe on Windows. is_daemon_running is now a ping with a 2s timeout instead of checking for the socket file, and the desktop app's duplicate protocol/client is replaced by the core one. Co-Authored-By: Claude <[email protected]>
Unix keeps kill(pid, 0) and SIGUSR1. Windows checks liveness with OpenProcess/GetExitCodeProcess and sends stop requests as a stop-<pid> file that a watcher thread in the target process turns into STOP_RECORDING. Co-Authored-By: Claude <[email protected]>
Co-Authored-By: Claude <[email protected]>
Co-Authored-By: Claude <[email protected]>
Co-Authored-By: Claude <[email protected]>
tauri-build needs an .ico to build on Windows. The new windows-latest job runs check, unit tests, a release build, and the transcription smoke test (Git Bash). The smoke script now writes the model path as a TOML literal string so Windows backslashes aren't treated as escapes. Co-Authored-By: Claude <[email protected]>
- daemon logs: tail/follow implemented in Rust instead of spawning tail - doctor: PipeWire check only on Linux; suggest the real default model - notify-send and the Waybar refresh_command default are Linux-only Co-Authored-By: Claude <[email protected]>
…p app - Find the CLI next to the app, in ~/.local/bin, or on PATH (no `which`) - Detect the running daemon's executable from its PID file via sysinfo (no `ps aux`), and do it before shutdown during restart; it previously ran after the daemon had exited, so detection could never succeed - Spawn console programs with CREATE_NO_WINDOW on Windows - GPU detection via nvidia-smi on Windows too; ~ expansion via dirs Co-Authored-By: Claude <[email protected]>
sed -i writes its temp file in the cwd (D: on the runner), which can't be renamed onto the config on C:. Co-Authored-By: Claude <[email protected]>
New [hotkey] toggle setting (default Ctrl+Alt+Space on Windows, unset elsewhere). The daemon registers it with global-hotkey on a thread running a Win32 message loop; each press runs `mojovoice start` hidden, reusing the CLI toggle (start/stop recording, type the transcription). On other platforms a configured hotkey only logs a pointer to desktop keybindings. Co-Authored-By: Claude <[email protected]>
save_config overwrote config.toml with only the app's sections, which would drop [hotkey] (and any section added by newer CLI versions). Co-Authored-By: Claude <[email protected]>
GNU sed treats backslashes in the replacement as escapes (\U, \r, ...), which mangled C:\Users\... in the config. Co-Authored-By: Claude <[email protected]>
Co-Authored-By: Claude <[email protected]>
Co-Authored-By: Claude <[email protected]>
tauri.windows.conf.json limits Windows bundles to NSIS and bundles the CLI as a sidecar next to the app, where find_mojovoice_binary looks first. The CI job uploads the installer and a CLI zip as artifacts for manual testing. Co-Authored-By: Claude <[email protected]>
Co-Authored-By: Claude <[email protected]>
Co-Authored-By: Claude <[email protected]>
ipc.ts detected Tauri by checking for window.__TAURI__, which Tauri v2 only defines when app.withGlobalTauri is enabled (it is false). The desktop app on every platform therefore served the browser-mode mock data: fake models, 'mock path' settings, and no-op model switching, deleting and downloading. Use isTauri() from @tauri-apps/api/core instead. Co-Authored-By: Claude <[email protected]>
The app's own partial copy of the config struct required fields the CLI gives defaults for, so older config.toml files failed to load (Settings stuck on loading), and saving dropped output.display_server. Reuse mojovoice::config::Config instead. switch_model now stores models_dir/<name> rather than the canonicalized path, which has a \\?\ prefix on Windows. Co-Authored-By: Claude <[email protected]>
Built against mock data, several views broke on real responses:
- Models: storage bar threw on undefined models_size_gb (blank panel);
sizes/download/active state read fields the registry doesn't send (now
joined with list_downloaded_models); delete sent { modelName } instead of
{ filename }, so it always failed. Reloads after download/delete.
- System status: read snake_case fields the backend sends in camelCase.
- Status bar: read modelId/deviceName (config keys are snake_case) and never
highlighted the active model.
- Settings: model switch split paths on '/' only, failing on Windows.
- Downloads that fail before the first progress event now show an error
instead of staying stuck on 'downloading'.
Mocks now use the same shapes as the backend.
Co-Authored-By: Claude <[email protected]>
Panels collapsed by animating max-height to a fixed cap (Models 800px, System status/Advanced 400px, history 2000px), so the 22-model list was cut off. Use a grid-template-rows 0fr/1fr collapse instead (animates, no cap). Advanced settings: native folder picker (Browse) for model_path and clips_path. Navigating to Models/History from settings closes the drawer. Co-Authored-By: Claude <[email protected]>
- Switching models restarts the daemon (10-15s+), but the pickers showed no progress and the backend gave up after 5s. One shared switchModel in the store: pickers show the pending model with a spinner, are disabled until done (no stacked restarts), and report success/failure. - The daemon start/restart wait is now up to 180s, polls off the async runtime, and fails fast with a pointer to the logs if the daemon exits. - First run: with no models installed the UI showed the configured (not downloaded) model as selected. Show 'No model installed' with a button to the Models page; the status bar names the installed active model. - Model pickers use the directory name as their value instead of the path. Co-Authored-By: Claude <[email protected]>
# Conflicts: # ui/src/components/SettingsPanel.tsx # ui/src/components/ui/StatusBar.tsx
There was no way to tell whether MojoVoice was recording or transcribing. The daemon now tracks its activity (daemon::indicator) around the recording and transcription requests it handles, and on Windows shows a tray icon on the same message-loop thread as the global hotkey: grey when ready, red while recording, amber while transcribing, with a tooltip naming the hotkey and a 'Quit MojoVoice daemon' menu item. Hotkey registration failures no longer prevent the tray from appearing. Co-Authored-By: Claude <[email protected]>
Co-Authored-By: Claude <[email protected]>
Co-Authored-By: Claude <[email protected]>
Co-Authored-By: Claude <[email protected]>
Candle links CUDA at load time (cudarc dynamic-linking), so a CUDA build of mojovoice.exe can't even start without the CUDA 12 runtime. The installer now bundles both mojovoice.exe (CPU) and mojovoice-cuda.exe (compute capability 8.0+), built on windows-2022 since CUDA 12 rejects newer Visual Studio. The desktop app probes for the DLLs the CUDA build imports (nvcuda, cuBLAS, cuBLASLt, cuRAND) and starts the CUDA daemon when they load, otherwise the CPU one. Releases also publish mojovoice-windows-x64-cuda.zip. Co-Authored-By: Claude <[email protected]>
Candle only accelerates on NVIDIA (CUDA) and Apple (Metal), so AMD and Intel GPUs fell back to the CPU. Add a whisper.cpp engine (whisper-rs) behind the whisper-cpp feature; whisper-vulkan adds its Vulkan backend, which runs on any GPU driver with Vulkan. The model format picks the engine (transcribe:: load_engine): GGML (model.bin) -> whisper.cpp, safetensors/GGUF -> Candle. Engines report their device for status, and whisper.cpp logs go to tracing. On an RTX 3090 Ti via Vulkan, GGML large-v3-turbo: post-warmup RTF 0.026 and WER 0.173 (Candle+CUDA: 0.037 / 0.172). Co-Authored-By: Claude <[email protected]>
…ile list Adds whisper.cpp's GGML models (large-v3-turbo full/Q8/Q5, distil-large-v3.5, medium.en, small.en, base.en) as a single model.bin each. The desktop app now takes download files from ModelInfo::files() instead of its own per-format logic, recognizes GGML downloads, and picks the bundled CLI build: CUDA (NVIDIA + CUDA 12 runtime), else Vulkan (vulkan-1.dll), else CPU. Co-Authored-By: Claude <[email protected]>
Installer sidecars: mojovoice.exe (Candle + whisper.cpp CPU), mojovoice-vulkan.exe (whisper.cpp on Vulkan) and mojovoice-cuda.exe (Candle CUDA + whisper.cpp on Vulkan). Installs the Vulkan SDK for glslc/headers. UI labels GGML models as whisper.cpp / GPU via Vulkan and suggests them for AMD/Intel GPUs on first run. Co-Authored-By: Claude <[email protected]>
whisper.cpp's nested vulkan-shaders-gen MSBuild project exceeded the 260-character path limit under target-vulkan/ (FileTracker FTK1011). Co-Authored-By: Claude <[email protected]>
whisper.cpp takes the first GPU it finds; on a machine with integrated graphics plus a discrete card (Intel UHD 770 + AMD RX 6600) that's the integrated one, which made transcription near CPU speed. Enumerate ggml devices and pass the first discrete GPU (else the first integrated one) as gpu_device. Status now names the GPU, e.g. 'whisper.cpp (Vulkan: AMD Radeon RX 6600)'. Co-Authored-By: Claude <[email protected]>
Co-Authored-By: Claude <[email protected]>
Windows 11 throttles background processes without a window. The daemon is exactly that, which can slow transcription severalfold; on Wario a test daemon ran whisper.cpp ~9x slower than the same build on a comparable Linux CPU. Co-Authored-By: Claude <[email protected]>
- Flash attention by default when running on a GPU: on an RTX 3090 Ti via Vulkan, benchmark total 8.02s -> 5.74s and short clips RTF 0.144 -> 0.090, identical WER. MOJOVOICE_WHISPER_FLASH_ATTN overrides it. - MOJOVOICE_WHISPER_AUDIO_CTX=auto trims the encoder window to the clip length; off by default since it doubled WER (0.17 -> 0.34). - Windows builds set GGML_NATIVE=OFF with AVX/AVX2/FMA/F16C/BMI2: native detection baked in the CI runner's AVX-512, which desktop CPUs like the i7-12700K don't have. Co-Authored-By: Claude <[email protected]>
After stop, recording continues briefly so words spoken while pressing the key aren't cut off. 1s added a full second to every dictation; 400ms covers the same case (configurable: audio.trailing_buffer_ms). Cancelled recordings are discarded, so they now skip it entirely. Also fixes a Windows thread leak: setup_signal_handler, called for every daemon recording, started a new never-ending stop-file watcher each time. Co-Authored-By: Claude <[email protected]>
Test Mic recorded for a fixed 5s with a CANCEL button beside it and no stop
button, so 'stop' clicks landed on Cancel; the 5s timer then sent
stop_recording for an already-cancelled recording ('Daemon error: Not
recording'). Now: click to start, click again to stop & transcribe (auto-stop
at 30s), Cancel discards and clears the pending auto-stop. Shows recording
time and how long transcription took.
Co-Authored-By: Claude <[email protected]>
The CUDA toolkit install and build add ~10-15 minutes per CI run. PR test builds now bundle only the CPU and Vulkan CLIs (tauri.windows-test.conf.json overrides externalBin); release builds still include CUDA. Co-Authored-By: Claude <[email protected]>
- Settings > RECORDING > hotkey: shows the current combination; Change captures the next key combination (KeyboardEvent.code names, which is the format global-hotkey parses), requires a modifier unless it's F1-F24/Pause etc., saves [hotkey] toggle and restarts a running daemon to apply it. - The daemon logs every hotkey press/release and the 'mojovoice start' it launches (and its exit status on failure). Wario's log showed no trace of Ctrl+Alt+Space presses at all, so this shows whether presses arrive. Co-Authored-By: Claude <[email protected]>
…hift+1 - Status overlay: a small always-on-top pill near the bottom of the screen (layered, click-through, never activated, DPI-aware; drawn with GDI on the daemon's UI thread). Shows 'Recording' (with the stop hint), 'Transcribing', then the transcribed text for 1.6s, 'No speech detected', or the actual error for 5s. [overlay] enabled, on by default. - Push-to-talk ([hotkey] mode = push_to_talk, off by default): press starts recording, release transcribes and types; taps under 300ms are cancelled. The release waits for the press's CLI run so the stop can't overtake it. - Default hotkey is now Alt+Shift+1: Ctrl+Alt+Space never reached the daemon on a test machine (likely taken by another app's keyboard hook). - The daemon records how each dictation ended (indicator::finish). Co-Authored-By: Claude <[email protected]>
Both restart a running daemon to apply, sharing one save-and-restart helper with the hotkey picker. Co-Authored-By: Claude <[email protected]>
…for it 'ready for transcription' is logged after the model loads, but the daemon only accepts connections once the tray/overlay are up (~0.5s later now). On Windows a named pipe refuses connections until then, so the smoke test's request raced it and failed. Log 'accepting connections' right before the accept loop and wait for that instead. Co-Authored-By: Claude <[email protected]>
Windows sends the hidden overlay a WM_PAINT before any status text is set, and DrawTextW was called with an empty Vec's dangling pointer (length 0), which user32 still reads: access violation (0xC0000005 / 0xC000041D) right after startup. Skip drawing when there's no text. Found by reproducing on Wario via the Application event log; verified the fixed build survives startup and painting. Co-Authored-By: Claude <[email protected]>
DaemonServer::new ignored its model_path argument and always loaded the config's model, while logging the override. Co-Authored-By: Claude <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Work in progress: Windows 11 port. Plan and decisions:
docs/research/2026-10-03-windows-port-scoping.md.Done so far
interprocess): Unix socket unchanged, per-user named pipe on Windows; desktop app now uses the core clienttoggle.rs): SIGUSR1 on Unix, stop file on Windowsicon.icofor Tauri Windows buildswindows-latestjob running check, unit tests, release build, and the end-to-end transcription smoke testVerified
x86_64-pc-windows-gnu; clippy clean (--all-targets -D warnings) on Linux and Windows targetskill -9Remaining (P1–P3 in the scoping doc): shell-outs to Linux tools, desktop sidecar/console windows, CUDA on Windows, global hotkey in the daemon, NSIS release job.
🤖 Generated with Claude Code