Repository navigation
Windows: native quick view, sharp tray icon, start at login - #150
Merged
prakersh merged 3 commits intoOct 17, 2026
Merged
Conversation
The Windows quick view was Microsoft Edge in --app mode: a normal titled window with min/max/close buttons, a taskbar button, a fixed 360x680 size (the page's resize reports only reached the macOS WKWebView), no dismiss on focus loss, and a cold browser start on every click. - Host /menubar in WebView2 inside a borderless tool window, the Windows counterpart of the macOS popover: no title bar, taskbar or Alt-Tab entry, rounded corners on Windows 11, kept warm and soft-refreshed on open, sized to its content, closed by a second click, a click elsewhere, Esc or the page's close button. Links open in the default browser. The page and host share the macOS channel names (onwatchResize, onwatchAction) through chrome.webview. Pure Go (jchv/go-webview2), so CGO_ENABLED=0 builds keep working; the Edge --app window remains the fallback without the runtime. - Render the tray badge at the exact small-icon size for the display scale. fyne.io/systray loads icons with LR_DEFAULTSIZE (the 32 px large icon), which the shell shrank into the tray slot - the dotted, blurry badge. The badge is now an anti-aliased rounded square with Go Bold digits that grow with the icon instead of a 7 px pixel font. - Make the companion per-monitor DPI aware, so the menu and quick view are crisp at 125% and above, and let the tray menu follow the dark theme. - Mark provider rows in the tray menu with a status dot (Windows, Linux). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`onwatch service install` now works on Windows. It registers a per-user
Task Scheduler task (no administrator rights) that starts at logon, runs on
battery and has no run-time limit. Task Scheduler cannot restart a process
that dies after launch, so the task runs `onwatch service run`, a small
supervisor that keeps the daemon running and restarts it after a crash with
a 10s-5m backoff. A clean exit ends supervision, matching launchd's
KeepAlive {SuccessfulExit: false}:
- `onwatch stop` terminates with exit code 0 on Windows and ends the task,
so it stays stopped until the next login.
- `onwatch update` and dashboard updates restart through the task, so the
new binary stays supervised.
- The task runs under a headless conhost, so nothing flashes at logon.
- A Task Scheduler start has no SESSIONNAME; fall back to the process's
session ID so the tray companion still starts.
- Wait for a stopped instance to exit before starting the new one: on
Windows the port stays bound until then and the new daemon failed to
start.
- install.ps1 offers auto-start, and re-registers an existing task after an
upgrade.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
prakersh
approved these changes
Oct 17, 2026
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.
Summary
On Windows the tray companion looked broken compared to macOS:
--appwindow: title bar with min/max/close, taskbar button, fixed 360x680 with a large empty area, no dismiss on focus loss, and a cold browser start on every click.onwatch service installwas macOS-only).This PR fixes all four, in three commits.
1. Native quick view and sharp tray icon (
feat(menubar))Root causes
window.webkit.messageHandlers.onwatchResize, which only exists in the macOS WKWebView, so the Edge window never resized. Chromium--appwindows always get a standard frame.fyne.io/systrayloads icons withLoadImage(..., 0, 0, LR_DEFAULTSIZE), which picks the 32 px large-icon size. The shell then shrank it into the 16-24 px tray slot, so the per-DPI sizes in our.icowere never used. The 7 px pixel font stayed 7 px from 16 to 31 px.Changes
quickview_webview2_windows.go: hosts/menubarin WebView2 inside a borderlessWS_EX_TOOLWINDOWpopup, the Windows counterpart ofpopover_darwin.m. It has rounded corners on Windows 11 and is kept warm with a soft refresh on open, like macOS. It sizes itself to the page'sonwatchResize, clamped withClampPopoverHeightand scaled per monitor DPI. It closes on a second click, a click outside, Esc, or the page's close button, and links open in the default browser. It is pure Go (jchv/go-webview2), soCGO_ENABLED=0release builds are unchanged. The Edge--appwindow remains the fallback when the WebView2 runtime is missing orONWATCH_QUICKVIEW_BROWSERis set.menubar.html: a smallnativeHandler(name)helper resolves either the WKWebView handler or the WebView2chrome.webviewchannel. Messages carry their channel name, andpopover_bridge.goroutes them (platform-neutral, unit tested).golang.org/x/image, pinned to v0.44.0 because later versions need Go 1.26). On Windows it is rendered at exactlySM_CXSMICONfor the system DPI and pushed withShell_NotifyIconW, while systray keeps the multi-size.icoas its fallback.2. Start at login and restart after a crash on Windows (
feat(service))onwatch service install|uninstall|statusnow works on Windows. It registers a per-user Task Scheduler task (no admin rights) with a logon trigger, battery restrictions off, and no 72 h run-time limit.onwatch service run: a small supervisor (~13 MB) that keeps the daemon running and restarts it 10 s after a crash, backing off to 5 min. A clean exit (code 0) ends supervision, matching launchd'sKeepAlive {SuccessfulExit: false}:terminateProcesson Windows now exits with code 0, andonwatch stopalso ends the task, so a stop stays stopped until the next login.onwatch updateand dashboard updates restart through the task (/End+/Run), reusing the existing launchd branches, so the new binary stays supervised.conhost.exe --headless, so no window flashes at logon. The supervisor exits with its console host, soschtasks /Endreally ends supervision.windowsSessionAvailablerelied onSESSIONNAME, which Task Scheduler does not set, so no tray appeared. It now falls back to the process session ID.stopPreviousInstanceslept a fixed 500 ms after terminating, but on Windows the port stays bound until the process is gone, so a manual takeover could fail with "Only one usage of each socket address". It now waits for the process to exit.install.ps1offers auto-start, and re-registers an existing task after an upgrade.3. Docs
docs/TRAY_LINUX_WINDOWS.mdanddocs/WINDOWS_SETUP.md(new Start at Login section).Testing
schtaskscallsterminateProcessexiting with code 0go test -cover ./...and-tags menubarformenubar,service,cmd/onwatchandwebon Windows 11: everything passes except two failures that also fail onmainon Windows:TestResolveExeKeepsStableSymlink(symlinks need a privilege) and theTestDaemonChildRun_DebugModeAllProviders*TempDir cleanup (a test-spawned Antigravity CLI keeps a file open).-racecould not run locally (no C toolchain); CI covers it.CGO_ENABLED=0with-tags menubarfor windows/amd64, windows/arm64, linux/amd64 and linux/arm64, plus untagged builds.go vetfor darwin untagged (the darwin tray needs cgo and a Mac).schtasks /Run), kill the daemon (restarted after 10 s),onwatch stop(stays stopped),/End+/Run(exactly one daemon after takeover), uninstall (daemon keeps running). No visible windows at any point.flake.nixvendorHashwas updated by computing the NAR hash ofgo mod vendoroutput locally. The same script reproduces the current hash onmain; CI'snixjob confirms it.Notes for review
go-webview2'sedgepackage callsruntime.LockOSThread()andCoInitializeExininit(). Since the Windows release is one binary built with-tags menubar, the daemon's main goroutine is pinned to the main thread too. That's harmless (nothing else uses COM), but worth knowing.fyne.io/systrayregistered: its hiddenSystrayClasswindow,uID 100. Both are fixed in the pinned v1.12.0. If a future version changes them, the call fails quietly and the systray icon stays.SetPreferredAppMode/FlushMenuThemesexports (ordinals 135/136), guarded to Windows 10 1903+, as Chromium and Windows Terminal do.MenuItem.SetIconover dbusmenu and are untested on a real Linux desktop. macOS only gains no-op hooks.🤖 Generated with Claude Code