Dedicated mouse hook #20436
Conversation
There was a problem hiding this comment.
Pull request overview
This PR addresses NVDA Magnifier mouse-follow lag at high DPI scaling by adding a dedicated low-level mouse hook thread that calls the magnifier update path directly on WM_MOUSEMOVE, aiming to bypass wx main-thread timer delays.
Changes:
- Added
MagnifierMouseHook(WH_MOUSE_LL) running in a dedicated daemon thread with a message pump. - Wired the hook lifecycle into the base
Magnifierstart/stop flow and added a hook-driven_onMouseMoveupdate path. - Added unit tests covering hook lifecycle wiring and
_onMouseMovebehavior.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| tests/unit/test_magnifier/test_mouseHook.py | Adds unit tests for hook lifecycle and hook-callback update behavior. |
| source/_magnifier/utils/mouseHook.py | Implements a dedicated WH_MOUSE_LL hook thread and WM_MOUSEMOVE callback forwarding. |
| source/_magnifier/magnifier.py | Starts/stops the hook with magnifier lifecycle and adds _onMouseMove update method. |
|
_onMouseMove was calling _doUpdate() (and so MagSetFullscreenTransform) synchronously from inside the WH_MOUSE_LL hook callback in mouseHook.py. Since that hook is global, a slow callback there could delay delivery of mouse messages system-wide. This is a precaution will investigating #20072. Change: _onMouseMove now only stores the coordinates and schedules _applyPendingMousePosition via wx.CallAfter. The actual API call now runs on the main thread, outside the hook. |
|
can this one re-target to beta please |
seanbudd
left a comment
There was a problem hiding this comment.
Thanks @Boumtchack looks good to me, just 2 minor things
| thread, e.g. via wx.CallAfter. | ||
| """ | ||
|
|
||
| def __init__(self, onMouseMove): |
There was a problem hiding this comment.
add a type hint for this paramater please
| # Ensure the thread message queue exists so PostThreadMessage(WM_QUIT) succeeds. | ||
| user32.PeekMessage(byref(windowsMessage), None, 0, 0, PM_NOREMOVE) | ||
|
|
||
| def _onRawMouseEvent(code, eventType, mouseDataPointer): |
There was a problem hiding this comment.
please move this function out and add type hints
Link to issue number:
partially closes #19987
Summary of the issue:
Fixes the lag observed when following the mouse with NVDA's magnifier at high Windows display scale factors (e.g. 225 %).
Description of user facing changes:
When following the mouse with NVDA's magnifier at high Windows display scale factors (e.g. 225 %), the magnified view should now follows the cursor smoothly. Previously, the view was noticeably delayed compared to the actual cursor position at such scale factors.
Description of developer facing changes:
source/_magnifier/utils/mouseHook.py installs a WH_MOUSE_LL Windows hook in a dedicated thread with its own message pump. The base Magnifier class manages its lifecycle _startMagnifier /_stopMagnifier and calls _onMouseMove(x, y) on every mouse move event, which updates MagSetFullscreenTransform directly without going through the wx main thread.
The existing wx.Timer loop is unchanged and continues to handle all other tracking modes (keyboard focus, review cursor, navigator object) as well as error recovery and manual panning.
Description of development approach:
At high DPI, Windows increases the rendering workload of the wx main thread significantly. Since the magnifier's mouse tracking relied on a wx.Timer firing every 12 ms, delayed timer callbacks caused visible stutter even though the mouse moved smoothly.
The fix bypasses the wx thread entirely for mouse tracking by using a WH_MOUSE_LL hook in a dedicated daemon thread. This hook fires immediately on every WM_MOUSEMOVE event regardless of main-thread load. MagSetFullscreenTransform is thread-safe, so calling it from the hook thread is safe. A second WH_MOUSE_LL hook in the same process is valid and does not interfere with NVDA's existing hook in winInputHook.py. The hook is installed and uninstalled within the same thread, as required by Windows.
Testing strategy:
unit
Known issues with pull request:
Code Review Checklist: