Skip to content

Dedicated mouse hook #20436

Draft
Boumtchack wants to merge 7 commits into
nvaccess:masterfrom
France-Travail:fix/dpiIssue
Draft

Dedicated mouse hook #20436
Boumtchack wants to merge 7 commits into
nvaccess:masterfrom
France-Travail:fix/dpiIssue

Conversation

@Boumtchack

Copy link
Copy Markdown
Contributor

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:

  • Documentation:
    • Change log entry
    • User Documentation
    • Developer / Technical Documentation
    • Context sensitive help for GUI changes
  • Testing:
    • Unit tests
    • System (end to end) tests
    • Manual testing
  • UX of all users considered:
    • Speech
    • Braille
    • Low Vision
    • Different web browsers
    • Localization in other languages / culture than English
  • API is compatible with existing add-ons.
  • Security precautions taken.

@Boumtchack Boumtchack changed the title first draft Dedicated mouse hook Jul 1, 2026
@Boumtchack Boumtchack marked this pull request as ready for review July 1, 2026 14:55
@Boumtchack Boumtchack requested a review from a team as a code owner July 1, 2026 14:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 Magnifier start/stop flow and added a hook-driven _onMouseMove update path.
  • Added unit tests covering hook lifecycle wiring and _onMouseMove behavior.

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.

Comment thread source/_magnifier/utils/mouseHook.py
Comment thread source/_magnifier/utils/mouseHook.py Outdated
Comment thread source/_magnifier/magnifier.py
@Boumtchack

Boumtchack commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

_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.

@SaschaCowley SaschaCowley added the conceptApproved Similar 'triaged' for issues, PR accepted in theory, implementation needs review. label Jul 7, 2026
@seanbudd

seanbudd commented Jul 9, 2026

Copy link
Copy Markdown
Member

can this one re-target to beta please

@seanbudd seanbudd left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Boumtchack looks good to me, just 2 minor things

thread, e.g. via wx.CallAfter.
"""

def __init__(self, onMouseMove):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please move this function out and add type hints

@seanbudd seanbudd marked this pull request as draft July 9, 2026 00:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

conceptApproved Similar 'triaged' for issues, PR accepted in theory, implementation needs review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Higher DPI effects magnifier performance

4 participants