Skip to content

Windows: restore main window after Pick location (fixes #3)#4

Merged
Quadstronaut merged 3 commits into
masterfrom
fix/pick-location-restore-window
Apr 26, 2026
Merged

Windows: restore main window after Pick location (fixes #3)#4
Quadstronaut merged 3 commits into
masterfrom
fix/pick-location-restore-window

Conversation

@Quadstronaut
Copy link
Copy Markdown
Owner

Summary

  • Fixes Windows: Pick location loses main window — coordinates captured but app appears to vanish #3 — clicking Pick… captured coordinates but left the main window hidden, so the app appeared to vanish.
  • BeginPick minimizes the owner so the target is visible; Window_StateChanged turns that into Hide(). The pick callbacks only restored WindowState, which does not undo Hide() in WPF.
  • Adds Show() to OnLocationPicked and OnPickCancelled, matching the existing RestoreFromTray pattern.

Test plan

  • dotnet build windows/QuadClicker.csproj -c Release — clean
  • Manual: select Fixed XY, click Pick…, click target → main window returns with X/Y populated
  • Manual: select Fixed XY, click Pick…, press ESC → main window returns unchanged
  • Manual: minimize-to-tray (close button / minimize) still hides as before

🤖 Generated with Claude Code

Quadstronaut and others added 2 commits April 25, 2026 23:49
BeginPick minimizes the owner so the user can pick coordinates beneath
the app. Window_StateChanged turns that minimize into Hide(), which
leaves the window with Visibility.Hidden. The pick callbacks only set
WindowState = Normal, which doesn't unhide the window — Activate() on a
hidden window is a no-op too. Result: coordinates were captured but the
app appeared to vanish.

Fix: call Show() in OnLocationPicked and OnPickCancelled, matching what
RestoreFromTray already does.

Fixes #3

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The pick overlay was showing but clicks were never captured. Symptom:
overlay's "Click to select location | ESC to cancel" hint stays on
screen, X/Y boxes are never populated, and the previous OnLocationPicked
restore code never runs (which is why the earlier Show() fix alone did
not help — the callback was never reaching it).

Root cause (likely): SetHook passed
GetModuleHandle(MainModule.ModuleName) — i.e. GetModuleHandle("QuadClicker.exe").
On .NET 10 apphost / single-file deployments the loaded EXE module can
register under a name that does not match Process.MainModule.ModuleName,
so the lookup silently returns 0. SetWindowsHookEx then refuses to
install with hMod=0, returns IntPtr.Zero, and the hook never fires.

Fix: pass NULL for hMod (the Win32-documented form: Windows substitutes
the EXE's HMODULE). Required widening NativeMethods.GetModuleHandle to
accept string?.

Also adds file-based diagnostics at %APPDATA%\QuadClicker\picker.log
covering the BeginPick → ShowOverlay → SetHook → HookCallback → dispatch
chain, plus a visible red error in the overlay when SetHook fails — so
if the picker still misbehaves we have evidence instead of guessing.

Refs #3

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Quadstronaut
Copy link
Copy Markdown
Owner Author

Pushed c10a9e8 — diagnosed wrong root cause initially.

What the previous fix actually addressed: restoring the main window after OnLocationPicked runs. That code path was never being entered because the click was never captured in the first place — the overlay just sat there.

Real root cause (likely): LocationPicker.SetHook passed GetModuleHandle(Process.MainModule.ModuleName) for hMod. On .NET 10 apphost / single-file deployments the loaded EXE module can register under a name that doesn't match MainModule.ModuleName, so that lookup silently returns 0. SetWindowsHookEx then returns IntPtr.Zero and the hook is never installed. The overlay shows, the user clicks, nothing happens.

Fix: pass GetModuleHandle(null) — Win32-documented form that always returns the EXE's HMODULE.

Also added file-based diagnostics at %APPDATA%\QuadClicker\picker.log covering each stage of the pick (BeginPick → ShowOverlay → SetHook result + GetLastError → HookCallback fired → dispatcher action ran). Plus the overlay now turns its hint label red and points at the log file if SetHook returns 0, so future regressions are debuggable on a user's machine without attaching a debugger.

Test plan extension:

  • If picker still doesn't capture clicks, attach %APPDATA%\QuadClicker\picker.log

…TANCE (#3)

Diagnostic log captured the actual root cause:

  EntryPointNotFoundException: Unable to find an entry point named
  'GetModuleHandle' in DLL 'kernel32.dll'.

kernel32.dll only exports GetModuleHandleA / GetModuleHandleW. The
[DllImport(CharSet=Auto)] convention auto-appended the W suffix, but
the [LibraryImport] source generator does not — even with
StringMarshalling.Utf16 you must set EntryPoint explicitly. Calling
NativeMethods.GetModuleHandle(...) therefore threw at runtime, SetHook
blew up before installing the WH_MOUSE_LL hook (after the overlay had
already been shown), and clicks went nowhere.

Fix: drop the GetModuleHandle P/Invoke entirely and use
Marshal.GetHINSTANCE(typeof(LocationPicker).Module) for hMod. WH_MOUSE_LL
hooks run in the calling thread regardless of hMod, so any valid module
handle works — and this avoids the marshaler edge case entirely.

The diagnostics added in c10a9e8 stay; they are what surfaced this
within one user-test cycle.

Refs #3

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Quadstronaut
Copy link
Copy Markdown
Owner Author

Pushed ae79f48. The diagnostic log from the previous build pointed at the actual root cause within one repro:

[2026-04-26 00:10:09.687] BeginPick: exception EntryPointNotFoundException:
Unable to find an entry point named 'GetModuleHandle' in DLL 'kernel32.dll'.

kernel32 only exports GetModuleHandleA / GetModuleHandleW. The classic [DllImport(CharSet=Auto)] style auto-appended W; the newer [LibraryImport] source generator does not — even with StringMarshalling.Utf16 you must set EntryPoint explicitly. So every call to NativeMethods.GetModuleHandle was throwing. The overlay was already shown by the time SetHook blew up, which is why the picker just sat there with no obvious failure.

Fix: drop the GetModuleHandle P/Invoke and use Marshal.GetHINSTANCE(typeof(LocationPicker).Module) for hMod. WH_MOUSE_LL hooks run in the calling thread regardless of hMod, so any valid module handle works.

Diagnostics stay — they paid for themselves immediately and will catch the next regression in this area without another guess-and-build cycle.

@Quadstronaut Quadstronaut merged commit 3aba242 into master Apr 26, 2026
3 checks passed
@Quadstronaut Quadstronaut deleted the fix/pick-location-restore-window branch April 26, 2026 07:36
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.

Windows: Pick location loses main window — coordinates captured but app appears to vanish

1 participant