Skip to content

Windows support — v4's Go architecture makes this feasible #538

Description

@chaoz23

Follows up on #266, which was closed when Finicky was pure Swift and a port wasn't realistic. The v4 rewrite changes that equation.

The pitch

I'd like to contribute Windows support for Finicky. There's nothing like it on Windows — no rule-based browser router, period. Users are stuck manually switching default browsers or clicking through browser picker dialogs.

~87% of the v4 codebase is already cross-platform (Go + Svelte + TypeScript). Only the Objective-C platform layer (~930 lines across 8 files) needs Windows equivalents.

I've done a detailed analysis of every macOS API call in the codebase and mapped each to its Windows equivalent:

Component macOS (current) Windows equivalent Effort
App event loop NSApplication getlantern/systray Go lib Low
URL receiving Apple Events (kAEGetURL) Registry protocol handler + named pipe IPC Medium
System tray NSStatusItem getlantern/systray Low
Default browser get/set NSWorkspace Launch Services Registry UrlAssociations + ms-settings:defaultapps Low
Browser discovery NSWorkspace.URLsForApplicationsToOpenURL Registry StartMenuInternet scan Low
Browser launch exec.Command("open", ...) Direct .exe invocation Low
Config window WKWebView + NSWindow webview/webview Go lib (wraps WebView2) Low
JS↔native bridge WKScriptMessageHandler webview.Bind() Low
Modifier keys NSEvent.modifierFlags GetAsyncKeyState() Low
Battery/power IOPowerSources GetSystemPowerStatus() Low
Running app check NSWorkspace.runningApplications CreateToolhelp32Snapshot Low
Window title Accessibility API GetWindowText() Low
Home/cache dirs NSHomeDirectory() Go stdlib os.UserHomeDir() None
Single instance NSRunningApplication terminate Named mutex (CreateMutex) Low

14 of 18 API mappings are drop-in replacements. Only URL receiving needs architectural work — on macOS the OS pushes URLs to a running app via Apple Events; on Windows the OS launches a new process with the URL as argv[1], so the port needs a named pipe to forward URLs to the running instance. This is a well-established pattern (VS Code, Slack, and Spotify all do it).

Proposed approach

Zero changes to existing macOS code. The approach uses Go build tags:

  • Rename 5 existing files with _darwin.go suffix (adds //go:build darwin constraint)
  • Split 3 files that mix portable logic with cgo calls (e.g., launcher.go — profile parsing is portable, the open command is not)
  • Create 7 new _windows.go files (~620 lines total)
  • Extend browsers.json with Windows exe names and AppData config paths
  • Add 3 Go dependencies: webview/webview, getlantern/systray, golang.org/x/sys/windows

The Svelte UI, TypeScript config engine, Go rule engine, resolver, shorturl expander, config watcher, and JS VM — all compile and run as-is on Windows with zero modifications. The .finicky.js config format stays identical, so users can share configs across platforms.

Files untouched: config/, logger/, resolver/, rules/, shorturl/, version/, assets/, packages/ (the entire Svelte UI).

The one architectural difference

On macOS:

User clicks link → OS routes to running Finicky via Apple Events → HandleURL()

On Windows:

User clicks link → OS launches finicky.exe with URL as argv[1]
  → Check for existing instance via named mutex
    → If exists: send URL over named pipe to running instance, exit
    → If not: become main instance, start pipe server, process URL

What I'm asking

Would you accept a PR that adds Windows support via this approach? Specifically:

  1. Are you open to the build-tag file structure (existing macOS code stays untouched, Windows files live alongside)?
  2. Any concerns about the new dependencies (webview/webview, getlantern/systray, golang.org/x/sys/windows)?
  3. Would you want Windows CI (GitHub Actions) added to the repo, or should that live in a fork?

If you'd prefer this as a community fork rather than upstream, that's completely fine — I'd maintain config format compatibility either way. Just want to check before doing the work.

Happy to share the full file-by-file analysis if helpful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions