From bde2c99b956c5a7e0834fb488debaee3ed4ad338 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Ku=C4=8Dera?= <10546952+miloush@users.noreply.github.com> Date: Tue, 4 Nov 2025 14:59:18 +0000 Subject: [PATCH] Primary mouse device (#19898) (#19990) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Primary mouse device * Addressing feedback --------- Co-authored-by: Jan Kučera Co-authored-by: Julien Lebosquain --- src/Avalonia.Base/Input/MouseDevice.cs | 15 +++++++++ src/Avalonia.Native/TopLevelImpl.cs | 4 +-- src/Avalonia.X11/X11Window.cs | 3 +- .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 31 ++++++++++++------- src/Windows/Avalonia.Win32/WindowImpl.cs | 6 ++-- 5 files changed, 41 insertions(+), 18 deletions(-) diff --git a/src/Avalonia.Base/Input/MouseDevice.cs b/src/Avalonia.Base/Input/MouseDevice.cs index 49945f1e8a..5a0cdad755 100644 --- a/src/Avalonia.Base/Input/MouseDevice.cs +++ b/src/Avalonia.Base/Input/MouseDevice.cs @@ -18,6 +18,9 @@ namespace Avalonia.Input [PrivateApi] public class MouseDevice : IMouseDevice, IDisposable { + private static MouseDevice? _primary; + internal static MouseDevice Primary => _primary ??= new MouseDevice(); + private int _clickCount; private Rect _lastClickRect; private ulong _lastClickTime; @@ -31,6 +34,16 @@ namespace Avalonia.Input _pointer = pointer ?? new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true); } + internal static TMouseDevice GetOrCreatePrimary() where TMouseDevice : MouseDevice, new() + { + if (_primary is TMouseDevice device) + return device; + + device = new TMouseDevice(); + _primary = device; + return device; + } + public void ProcessRawEvent(RawInputEventArgs e) { if (!e.Handled && e is RawPointerEventArgs margs) @@ -300,6 +313,8 @@ namespace Avalonia.Input public void Dispose() { + System.Diagnostics.Debug.Assert(this != _primary, "Disposing primary mouse device."); + _disposed = true; _pointer?.Dispose(); } diff --git a/src/Avalonia.Native/TopLevelImpl.cs b/src/Avalonia.Native/TopLevelImpl.cs index 52ad0bc30f..05cbb15765 100644 --- a/src/Avalonia.Native/TopLevelImpl.cs +++ b/src/Avalonia.Native/TopLevelImpl.cs @@ -89,7 +89,7 @@ internal class TopLevelImpl : ITopLevelImpl, IFramebufferPlatformSurface Factory = factory; _keyboard = AvaloniaLocator.Current.GetService(); - _mouse = new MouseDevice(); + _mouse = Avalonia.Input.MouseDevice.Primary; _pen = new PenDevice(); _cursorFactory = AvaloniaLocator.Current.GetService(); } @@ -387,8 +387,6 @@ internal class TopLevelImpl : ITopLevelImpl, IFramebufferPlatformSurface _nativeControlHost?.Dispose(); _nativeControlHost = null; - - _mouse?.Dispose(); } protected virtual bool ChromeHitTest(RawPointerEventArgs e) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index a7f52d8adc..802f617f89 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -96,7 +96,7 @@ namespace Avalonia.X11 _popup = popupParent != null; _overrideRedirect = _popup || overrideRedirect; _x11 = platform.Info; - _mouse = new MouseDevice(); + _mouse = Avalonia.Input.MouseDevice.Primary; _touch = new TouchDevice(); _keyboard = platform.KeyboardDevice; @@ -1077,7 +1077,6 @@ namespace Avalonia.X11 _platform.XI2?.OnWindowDestroyed(_handle); var handle = _handle; _handle = IntPtr.Zero; - _mouse.Dispose(); _touch.Dispose(); if (!fromDestroyNotification) XDestroyWindow(_x11.Display, handle); diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 0f79a26ece..662f07623e 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -114,9 +114,9 @@ namespace Avalonia.Win32 //Window doesn't exist anymore _hwnd = IntPtr.Zero; //Remove root reference to this class, so unmanaged delegate can be collected - s_instances.Remove(this); + lock (s_instances) + s_instances.Remove(this); - _mouseDevice.Dispose(); _touchDevice.Dispose(); //Free other resources Dispose(); @@ -280,14 +280,6 @@ namespace Avalonia.Win32 DipFromLParam(lParam), GetMouseModifiers(wParam)); break; } - // Mouse capture is lost - case WindowsMessage.WM_CANCELMODE: - if (!IsMouseInPointerEnabled) - { - _mouseDevice.Capture(null); - } - - break; case WindowsMessage.WM_MOUSEMOVE: { @@ -394,13 +386,14 @@ namespace Avalonia.Win32 break; } + // covers WM_CANCELMODE which sends WM_CAPTURECHANGED in DefWindowProc case WindowsMessage.WM_CAPTURECHANGED: { if (IsMouseInPointerEnabled) { break; } - if (_hwnd != lParam) + if (!IsOurWindow(lParam)) { _trackingMouse = false; e = new RawPointerEventArgs( @@ -907,6 +900,22 @@ namespace Avalonia.Win32 return DefWindowProc(hWnd, msg, wParam, lParam); } + private bool IsOurWindow(IntPtr hwnd) + { + if (hwnd == IntPtr.Zero) + return false; + + if (hwnd == _hwnd) + return true; + + lock (s_instances) + for (int i = 0; i < s_instances.Count; i++) + if (s_instances[i]._hwnd == hwnd) + return true; + + return false; + } + private void OnShowHideMessage(bool shown) { _shown = shown; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index ac230744bc..9cc07b8075 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -119,7 +119,7 @@ namespace Avalonia.Win32 public WindowImpl() { _touchDevice = new TouchDevice(); - _mouseDevice = new WindowsMouseDevice(); + _mouseDevice = Avalonia.Input.MouseDevice.GetOrCreatePrimary(); _penDevice = new PenDevice(); #if USE_MANAGED_DRAG @@ -177,7 +177,9 @@ namespace Avalonia.Win32 _nativeControlHost = new Win32NativeControlHost(this, !UseRedirectionBitmap); _defaultTransparencyLevel = UseRedirectionBitmap ? WindowTransparencyLevel.None : WindowTransparencyLevel.Transparent; _transparencyLevel = _defaultTransparencyLevel; - s_instances.Add(this); + + lock (s_instances) + s_instances.Add(this); } internal IInputRoot Owner