win32: convert WM_MOUSEWHEEL position to client coordinates - #4
Open
nanasess wants to merge 1 commit into
Open
Conversation
Unlike WM_MOUSEMOVE and the button messages, WM_MOUSEWHEEL carries the pointer position in screen coordinates. The 0x020A handler in surfaceDispatch reused the WM_MOUSEMOVE conversion verbatim and stored those screen coordinates into surface.cursor_pos as if they were surface coordinates. When mouse reporting is active (a fullscreen TUI such as Claude Code), Surface.scrollCallback reports wheel events at rt_surface.getCursorPos(), so the bogus position reaches posOutOfViewport() in input/mouse_encode.zig. Whenever the window sits far enough from the screen origin that the screen coordinates exceed the surface size (for example a 1054px wide client area with the window at x=1299), every wheel event is classified as outside the viewport and silently dropped, so the application never receives a scroll report at all. This also explains the odd repro: moving the window towards the top-left of the screen makes the coordinates happen to land inside the viewport again. Scrollback scrolling is unaffected because it calls scrollViewport() without using the position. Convert with ScreenToClient() before storing into cursor_pos. The WM_MOUSEWHEEL that the scrollbar child window forwards to its parent via SendMessageW is fixed by the same change, since forwarding preserves the original screen coordinates. Verified on Windows with `zig build -Doptimize=ReleaseFast -Dtarget=x86_64-windows-gnu`: with the window positioned on the right half of the screen, a Claude Code session can be scrolled with the mouse wheel again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 tasks
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.
Problem
With mouse reporting active (any fullscreen TUI — reproduced with Claude Code), the mouse wheel does nothing once the window sits far enough right/down on the screen. Moving the window towards the top-left corner makes it work again.
Scrollback scrolling (mouse reporting off) is unaffected.
Cause
Unlike
WM_MOUSEMOVEand the button messages,WM_MOUSEWHEELcarries the pointer position in screen coordinates. The0x020Aarm ofsurfaceDispatch(src/apprt/win32/App.zig) reuses theWM_MOUSEMOVEconversion verbatim and stores those screen coordinates intosurface.cursor_posas if they were surface coordinates.From there:
Surface.getCursorPos()(src/apprt/win32/Surface.zig) returnsself.cursor_posunchanged.Surface.scrollCallback()(src/Surface.zig) reports wheel events atrt_surface.getCursorPos()whenisMouseReporting().mouse_encode.encode()(src/input/mouse_encode.zig) returns without writing anything whenposOutOfViewport(event.pos, opts.size)holds and the mode is not motion-tracking.posOutOfViewportispos.x > size.screen.width or pos.y > size.screen.height, so as soon as the screen coordinates exceed the client area, every wheel event is classified as outside the viewport and silently dropped — the application never receives a scroll report at all.Concretely: a 1054px-wide client area with the window at x=1299 reproduces it every time. This also explains the odd repro condition — moving the window towards the screen origin makes the coordinates happen to land inside the viewport again.
Scrollback scrolling is unaffected because it goes through
scrollViewport(), which does not use the position.Secondarily, every wheel event leaves
cursor_posholding a bogus value until the nextWM_MOUSEMOVEarrives.Fix
Convert with
ScreenToClient()before storing intocursor_pos. ThePOINT+@as(i16, @truncate(...))shape matches the existingClientToScreenusage in the same file.The
WM_MOUSEWHEELthat the scrollbar child window forwards to its parent viaSendMessageW(src/apprt/win32/Surface.zig) is fixed by the same change, since forwarding preserves the original screen coordinates.Testing
zig fmt --check src\apprt\win32\App.zigpasses.zig build -Doptimize=ReleaseFast -Dtarget=x86_64-windows-gnu(zig 0.16.0) builds cleanly.Upstream status
This code comes from mattn/ghostty's
win32-apprtbranch and is still present there in the same form.ghostty-org/ghosttymain has nosrc/apprt/win32/yet, so upstream Ghostty is unaffected.AI disclosure
Per AI_POLICY.md: Claude Code assisted the investigation, and the code comment and this description were drafted with its help and edited by me. I diagnosed the behaviour, verified the fix on real hardware, and I understand and stand behind the change.
🤖 Generated with Claude Code