Skip to content

Fix pointer capture cancellation on Win32 - #22054

Merged
MrJul merged 2 commits into
mainfrom
fixes/win32-pen-pointer-capture-lost
Aug 25, 2026
Merged

MrJul merged 2 commits into
mainfrom
fixes/win32-pen-pointer-capture-lost

Conversation

@kekekeks

@kekekeks kekekeks commented Aug 24, 2026 •

Copy link
Copy Markdown
Member

This PR has separate connected changes:

  1. hardened Pointer class:
    a) Dispose now properly releases any dangling captures and prevents pointer from being captured again
    b) Gesture recognizer captures are now correctly released, platforms are required to call CaptureLost(source) instead of Capture(null) which doesn't affect recognizer capture.

E2E-ish test coverage will arrive with headless pen/touch PR.

  1. fix for cancelled pen (e. g. Ctrl+Alt+Del, Win+L) interaction was reported as PointerUp because POINTER_FLAG_CANCELED was invalid due to what I assume to be a typo.

WM_POINTERCAPTURECHANGED and previously mishandled POINTER_FLAG_CANCELED now map to CancelCapture.

  1. PenDevice handles CancelCapture just as cancelled capture, pointer is not dropped until actual WM_POINTERLEAVE/LeaveWindow.

Non-win32 backends are not yet updated, the only observable change is that any (invalid) leftover capture is cleared when pointer is disposed in LeaveWindow.

followup plan:

  1. test coverage of x-plat code as via pen/touch headless PR
  2. update platform backends

A cancelled pen or mouse interaction was reported as an ordinary leave,
and nothing released the capture that came with it, so the captured
element never saw PointerCaptureLost.

Win32 constants: POINTER_FLAG_CONFIDENCE and POINTER_FLAG_CANCELED were
declared an order of magnitude too small - the SDK defines them as
0x4000 and 0x8000 - so the cancellation check in GetEventType could
never match. Ctrl+Alt+Del or Win+L mid stroke therefore arrived as a
normal button up, and the app committed an interaction the user had
aborted.

Win32 handling: WM_POINTERCAPTURECHANGED and POINTER_FLAG_CANCELED now
map to CancelCapture for non touch pointers, matching what the legacy
WM_CAPTURECHANGED path has always done.

PenDevice: handle CancelCapture by releasing the capture while keeping
the pointer alive, since the pen is still in range and a leave arrives
separately.

Pointer hardening: CaptureLost ends every capture the pointer holds, on
the element and on a gesture recognizer, and the devices release through
it instead of repeating the steps. Capturing to a recognizer clears
Captured, so the null check in the platform path used to skip the whole
release while a scroll, pull or swipe gesture owned the pointer, and a
touch up never cleared the recognizer at all. Dispose releases as well
rather than leaving that to every caller, a disposed pointer ignores
further capture calls and asserts when a capturer is passed, and devices
drop a pointer from their map before disposing it.

Co-Authored-By: Claude Opus 5 (1M context) 
@MrJul MrJul added bug backport-candidate-12.1.x Consider this PR for backporting to 12.1 branch os-windows labels Aug 24, 2026
@avaloniaui-bot

Copy link
Copy Markdown

You can test this PR using the following package version. 12.2.999-cibuild0068693-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@kekekeks
kekekeks marked this pull request as ready for review August 24, 2026 17:21
@kekekeks
kekekeks requested a lite review from Copilot August 24, 2026 17:21

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 hardens pointer capture lifecycle handling and fixes Win32 pen/touch cancellation/capture-change mapping so that cancellations end capture correctly (instead of being misreported as pointer-up/leave).

Changes:

  • Add a centralized Pointer.CaptureLost(source) path and make Pointer.Dispose() clear dangling captures (including gesture-recognizer capture).
  • Update Win32 message handling to map WM_POINTERCAPTURECHANGED and POINTER_FLAG_CANCELED to CancelCapture/TouchCancel as appropriate.
  • Update mouse/pen/touch devices and unit test helpers to use CaptureLost(...) rather than manually clearing multiple pointer fields.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/Avalonia.UnitTests/TouchTestHelper.cs Switch test helper cancellation to the new CaptureLost(...) API.
tests/Avalonia.UnitTests/MouseTestHelper.cs Switch test helper capture release to CaptureLost(...).
src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs Map Win32 pointer capture-change/canceled-flag to cancel-capture semantics.
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs Correct PointerFlags bit values for confidence/canceled.
src/Avalonia.Base/Input/TouchDevice.cs Use CaptureLost(...)/Dispose() for touch end/cancel cleanup.
src/Avalonia.Base/Input/Pointer.cs Add disposed-guarding and the unified CaptureLost(...) capture teardown path.
src/Avalonia.Base/Input/PenDevice.cs Handle CancelCapture without dropping the pointer until leave, and use CaptureLost(...) on pen up.
src/Avalonia.Base/Input/MouseDevice.cs Use CaptureLost(...) on mouse up and honor cancel-capture events.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/Avalonia.Base/Input/Pointer.cs
Comment thread tests/Avalonia.UnitTests/TouchTestHelper.cs
Comment thread tests/Avalonia.UnitTests/MouseTestHelper.cs
@avaloniaui-bot

Copy link
Copy Markdown

You can test this PR using the following package version. 12.2.999-cibuild0068721-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@MrJul MrJul self-assigned this Aug 25, 2026
MrJul
MrJul previously approved these changes Aug 25, 2026
@kekekeks
kekekeks force-pushed the fixes/win32-pen-pointer-capture-lost branch from f9d049c to a4cc301 Compare August 25, 2026 13:39
The helper captures through Pointer.Capture(target), which is an
explicit capture, and used to release with Capture(null) - explicit as
well. Releasing as implicit changed the source that capture changing
handlers observe.

Co-Authored-By: Claude Opus 5 (1M context) 
@kekekeks
kekekeks force-pushed the fixes/win32-pen-pointer-capture-lost branch from a4cc301 to a162279 Compare August 25, 2026 13:47
@MrJul
MrJul enabled auto-merge August 25, 2026 13:50
@MrJul
MrJul added this pull request to the merge queue Aug 25, 2026
@avaloniaui-bot

Copy link
Copy Markdown

You can test this PR using the following package version. 12.2.999-cibuild0068792-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

Merged via the queue into main with commit a873896 Aug 25, 2026
9 checks passed
@MrJul
MrJul deleted the fixes/win32-pen-pointer-capture-lost branch August 25, 2026 15:23
MrJul pushed a commit to MrJul/Avalonia that referenced this pull request Sep 2, 2026
* Fix pointer capture cancellation on Win32

A cancelled pen or mouse interaction was reported as an ordinary leave,
and nothing released the capture that came with it, so the captured
element never saw PointerCaptureLost.

Win32 constants: POINTER_FLAG_CONFIDENCE and POINTER_FLAG_CANCELED were
declared an order of magnitude too small - the SDK defines them as
0x4000 and 0x8000 - so the cancellation check in GetEventType could
never match. Ctrl+Alt+Del or Win+L mid stroke therefore arrived as a
normal button up, and the app committed an interaction the user had
aborted.

Win32 handling: WM_POINTERCAPTURECHANGED and POINTER_FLAG_CANCELED now
map to CancelCapture for non touch pointers, matching what the legacy
WM_CAPTURECHANGED path has always done.

PenDevice: handle CancelCapture by releasing the capture while keeping
the pointer alive, since the pen is still in range and a leave arrives
separately.

Pointer hardening: CaptureLost ends every capture the pointer holds, on
the element and on a gesture recognizer, and the devices release through
it instead of repeating the steps. Capturing to a recognizer clears
Captured, so the null check in the platform path used to skip the whole
release while a scroll, pull or swipe gesture owned the pointer, and a
touch up never cleared the recognizer at all. Dispose releases as well
rather than leaving that to every caller, a disposed pointer ignores
further capture calls and asserts when a capturer is passed, and devices
drop a pointer from their map before disposing it.

Co-Authored-By: Claude Opus 5 (1M context) 

* Keep the explicit capture source in MouseTestHelper

The helper captures through Pointer.Capture(target), which is an
explicit capture, and used to release with Capture(null) - explicit as
well. Releasing as implicit changed the source that capture changing
handlers observe.

Co-Authored-By: Claude Opus 5 (1M context) 

---------

Co-authored-by: Claude Opus 5 (1M context) 
@MrJul MrJul added backported-12.1.x and removed backport-candidate-12.1.x Consider this PR for backporting to 12.1 branch labels Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants