Skip to content

Dedicated mouse hook #20436

Merged
seanbudd merged 8 commits into
nvaccess:betafrom
France-Travail:fix/dpiIssue
Jul 16, 2026
Merged

Dedicated mouse hook #20436
seanbudd merged 8 commits into
nvaccess:betafrom
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

Comment thread source/_magnifier/utils/mouseHook.py Outdated
Comment thread source/_magnifier/utils/mouseHook.py Outdated
@seanbudd
seanbudd marked this pull request as draft July 9, 2026 00:21
@Boumtchack
Boumtchack marked this pull request as ready for review July 15, 2026 09:36
@Boumtchack
Boumtchack changed the base branch from master to beta July 15, 2026 09:43
@hwf1324

hwf1324 commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Screen content changes are now very smooth when the mouse moves, but the mouse pointer still exhibits ghosting (especially in applications like Firefox). There is still a gap compared to Windows Magnifier. However, I noticed that Windows Magnifier seems to consume GPU resources while running; perhaps it utilizes GPU-based optimizations?

By the way, why don't we return directly within the mouse hook function? We could move the data calculations into an asynchronous call as well.

@seanbudd seanbudd added this to the 2026.2 milestone Jul 16, 2026
@seanbudd
seanbudd merged commit 3ae1c24 into nvaccess:beta Jul 16, 2026
40 of 45 checks passed
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

5 participants