Skip to content

feat(windows): add Windows cross-platform support#401

Draft
rodrigoluizs wants to merge 21 commits intomainfrom
feat/windows-cross-platform
Draft

feat(windows): add Windows cross-platform support#401
rodrigoluizs wants to merge 21 commits intomainfrom
feat/windows-cross-platform

Conversation

@rodrigoluizs
Copy link
Contributor

Summary

  • Add cross-platform architecture with src/main/platform/ module (types, utils, dynamic loader)
  • Extract macOS paster and permissions into platform/darwin/
  • Implement Windows paster using user32.dll SendInput API via koffi FFI
  • Implement Windows permissions module (no permission gates on Windows)
  • Add finishWithPeriod support to platform PasteOptions
  • Platform-aware titleBarStyle and menu roles for Windows compatibility
  • Resolve whisper binary name on Windows (.exe extension)
  • Add postinstall-whisper.js script to download pre-built whisper.cpp binary for Windows
  • Add persistent whisper-server process on non-macOS to eliminate cold-start latency
  • Use 75% of CPU cores for whisper inference on CPU-only platforms
  • Skip language auto-detection on CPU-only platforms to halve transcription latency
  • Add Windows build job to release CI workflow
  • Rewire all consumers (app, ipc, shortcuts) to use platform modules

Test plan

  • App starts on Windows with no errors
  • Verify whisper transcription works end-to-end on Windows
  • Test paste via SendInput (Ctrl+V simulation) on Windows
  • Verify tray, HUD, and shortcuts work on Windows
  • Confirm macOS functionality is unchanged (regression)
  • Run npm run typecheck, npm run lint, npx vitest run
  • Test Windows release build via CI

@rodrigoluizs rodrigoluizs added feature New feature implementation platform:windows Windows specific labels Mar 14, 2026
@rodrigoluizs rodrigoluizs self-assigned this Mar 14, 2026
@rodrigoluizs rodrigoluizs force-pushed the feat/windows-cross-platform branch from 1c42f7a to 3d86011 Compare March 14, 2026 12:20
@github-actions
Copy link
Contributor

github-actions bot commented Mar 14, 2026

CI Summary

Check Status
Typecheck ✅ Passed
Lint ✅ Passed
Lint CSS ✅ Passed
Design Tokens ✅ Passed
Test ✅ Passed
Build ✅ Passed

Run #962

@github-actions
Copy link
Contributor

github-actions bot commented Mar 14, 2026

MegaLinter analysis: Success

Descriptor Linter Files Fixed Errors Warnings Elapsed time
✅ JSON jsonlint 1 0 0 0.1s
✅ JSON npm-package-json-lint yes no no 1.18s
✅ JSON prettier 1 0 0 0.32s
✅ JSON v8r 1 0 0 7.75s
✅ REPOSITORY checkov yes no no 25.42s
✅ REPOSITORY devskim yes no no 2.3s
✅ REPOSITORY dustilock yes no no 1.62s
✅ REPOSITORY gitleaks yes no no 6.77s
✅ REPOSITORY git_diff yes no no 0.32s
✅ REPOSITORY grype yes no no 45.58s
✅ REPOSITORY kics yes no no 3.52s
✅ REPOSITORY kingfisher yes no no 5.45s
✅ REPOSITORY secretlint yes no no 4.08s
✅ REPOSITORY syft yes no no 5.93s
✅ REPOSITORY trivy yes no no 9.88s
✅ REPOSITORY trivy-sbom yes no no 3.23s
✅ REPOSITORY trufflehog yes no no 4.16s
✅ YAML prettier 1 0 0 0.41s
✅ YAML v8r 1 0 0 3.25s
✅ YAML yamllint 1 0 0 0.51s

See detailed reports in MegaLinter artifacts
Set VALIDATE_ALL_CODEBASE: true in mega-linter.yml to validate all sources, not only the diff

MegaLinter is graciously provided by OX Security
Show us your support by starring ⭐ the repository

- app.ts, ipc.ts, shortcuts/manager.ts now import from platform/
- Removed darwin-only guard from launch-at-login
- Deleted src/main/input/paster.ts (replaced by platform modules)
- Platform loader uses lazy Proxy to avoid test-time require issues
Dynamic template-string require (`./\${os}/paster`) can't be resolved
by Vite/Rollup in bundled builds. Use conditional static string paths
so the bundler includes both platform modules in the bundle.
Replace dynamic require() with static ES imports so Vite/Rollup can
resolve both platform modules at build time. Both modules defer native
library loads to function call time, making it safe to bundle both.
- Add electron-builder win/nsis config to package.json
- Add build-windows job on windows-latest runner
- Generate icon.ico from logo.png via ImageMagick in CI
- Update release job to download and publish both platforms
- Windows code signing via CSC_LINK/CSC_KEY_PASSWORD (optional)
MinGW GCC 15.2 generates AVX code with incorrect stack alignment on
Windows, causing segfaults during whisper inference. The official
MSVC-compiled release binaries handle AVX correctly.

- Add cross-platform postinstall script that downloads pre-built
  whisper.cpp v1.8.3 binary on Windows (macOS still compiles from
  source)
- Update whisper.ts to use whisper-cli.exe on Windows (the v1.8.3
  binary name)
- Add scripts/ to ESLint ignores (CJS postinstall script)

Performance: 7.4s for 11s audio (vs 200s without AVX, vs segfault
with MinGW AVX)
On macOS Metal GPU handles compute so thread count is less critical.
On Windows (CPU-only), whisper defaults to 4 threads which wastes
available cores. Using 75% of available cores nearly halves inference
time on a 16-core system (7.6s -> 4.0s for 11s of audio).
Whisper auto-detection runs the encoder twice (detect + transcribe),
doubling latency on CPU. On macOS Metal GPU this is negligible but on
Windows it adds ~3s to every transcription.

When the user has speech languages configured, use the first one
directly instead of auto-detect. The LLM correction layer handles
any cross-language artifacts.

Before: 13.3s (auto, 4t) → After: ~4s (fixed lang, 12t)
Replace the spawn-per-transcription approach (whisper-cli.exe) with a
persistent whisper-server.exe process on non-macOS platforms. The server
loads the model once at app launch and accepts audio via HTTP POST on
localhost, eliminating the ~500ms model-loading overhead on every
transcription.

Additionally switches from beam-search decoding (--best-of 5 --beam-size
5) to greedy decoding, saving ~600ms per transcription. The LLM
correction layer compensates for any quality difference.

- Add WhisperServer class with lifecycle management and HTTP-based
  readiness polling
- Server auto-restarts when the user changes whisper models
- Falls back to CLI mode if the server fails to start
- macOS keeps the existing CLI approach (Metal GPU makes cold-start
  negligible)

Benchmark (JFK 11s audio, small model, 12 threads):
  Before: ~4,500ms (CLI + beam search)
  After:  ~3,300ms (server + greedy)
…d default

Prefix unused httpRequestHandler variable with underscore to satisfy
ESLint no-unused-vars rule. Update win32 paster test to account for
finishWithPeriod defaulting to true (period kept unless explicitly false).
@rodrigoluizs rodrigoluizs force-pushed the feat/windows-cross-platform branch from a6c6607 to 14ef49d Compare March 14, 2026 18:54
Replace stale input/paster mock with platform module mock so tests
pass on Linux CI where no platform-specific paster is available.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature implementation platform:windows Windows specific

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant