From 8a3416f457593281b0b6b35ddc2c4b5aecaa020b Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Tue, 17 Mar 2026 16:58:51 +0100 Subject: [PATCH] Xdnd: set cursor --- .../Selections/DragDrop/X11DragSource.cs | 65 +++++++++++++++---- src/Avalonia.X11/X11CursorFactory.cs | 36 ++++++++-- 2 files changed, 80 insertions(+), 21 deletions(-) diff --git a/src/Avalonia.X11/Selections/DragDrop/X11DragSource.cs b/src/Avalonia.X11/Selections/DragDrop/X11DragSource.cs index e6677213a5..5c10d97288 100644 --- a/src/Avalonia.X11/Selections/DragDrop/X11DragSource.cs +++ b/src/Avalonia.X11/Selections/DragDrop/X11DragSource.cs @@ -29,7 +29,7 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra triggerEvent.Pointer.Capture(null); - var cursorFactory = AvaloniaLocator.Current.GetService(); + var cursorFactory = AvaloniaLocator.Current.GetService() as X11CursorFactory; using var handler = new Handler(platform, window.Handle, dataTransfer, allowedEffects, cursorFactory); await handler.Completion; @@ -43,26 +43,28 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra private readonly IntPtr _sourceWindow; private readonly IDataTransfer _dataTransfer; private readonly DragDropEffects _allowedEffects; - private readonly TaskCompletionSource _completionSource = new(); + private readonly X11CursorFactory? _cursorFactory; private readonly DragDropDataProvider _dataProvider; + private readonly TaskCompletionSource _completionSource = new(); private readonly IntPtr[] _formatAtoms; private X11WindowInfo? _originalSourceWindowInfo; private bool _pointerGrabbed; private XdndTargetInfo? _lastTarget; - private DragDropEffects _lastEffects; + private DragDropEffects _currentEffects; public Handler( AvaloniaX11Platform platform, IntPtr sourceWindow, IDataTransfer dataTransfer, DragDropEffects allowedEffects, - ICursorFactory? cursorFactory) + X11CursorFactory? cursorFactory) { _platform = platform; _sourceWindow = sourceWindow; _dataTransfer = dataTransfer; _allowedEffects = allowedEffects; - _lastEffects = allowedEffects; + _cursorFactory = cursorFactory; + _currentEffects = allowedEffects; _dataProvider = new DragDropDataProvider(platform, dataTransfer.ToAsynchronous()); if (!platform.Windows.TryGetValue(sourceWindow, out var sourceWindowInfo)) @@ -86,7 +88,7 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, 0, - platform.Info.DefaultCursor, + GetCursor(allowedEffects), 0); if (grabResult != GrabResult.GrabSuccess) @@ -163,7 +165,7 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra } _lastTarget = target; - _lastEffects = _allowedEffects; + UpdateCurrentEffects(_allowedEffects); if (target is { } newTarget) { @@ -212,7 +214,8 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra var accepted = (message.ptr2 & 1) == 1; var action = accepted ? message.ptr5 : 0; - _lastEffects = XdndActionHelper.ActionToEffects(action, _platform.Info.Atoms); + var effects = XdndActionHelper.ActionToEffects(action, _platform.Info.Atoms); + UpdateCurrentEffects(effects & _allowedEffects); } private void OnXdndFinished(in XClientMessageEvent message) @@ -225,11 +228,12 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra if (lastTarget.Version >= 5) { var accepted = (message.ptr2 & 1) == 1; - var action = accepted ? message.ptr5 : 0; - _lastEffects = XdndActionHelper.ActionToEffects(action, _platform.Info.Atoms); + var action = accepted ? message.ptr3 : 0; + var effects = XdndActionHelper.ActionToEffects(action, _platform.Info.Atoms); + UpdateCurrentEffects(effects & _allowedEffects); } - _completionSource.TrySetResult(_lastEffects); + _completionSource.TrySetResult(_currentEffects); } private XdndTargetInfo? FindXdndTarget(PixelPoint rootPosition) @@ -368,7 +372,7 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra { if (targetWindow.DragDropDevice is not { } dragDropDevice) { - _lastEffects = DragDropEffects.None; + UpdateCurrentEffects(DragDropEffects.None); return; } @@ -385,7 +389,7 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra dragDropDevice.ProcessRawEvent(dragEvent); - _lastEffects = dragEvent.Effects; + UpdateCurrentEffects(dragEvent.Effects & _allowedEffects); } private void UngrabPointer() @@ -396,6 +400,39 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra XFlush(_platform.Display); } + private void UpdateCurrentEffects(DragDropEffects effects) + { + if (_currentEffects == effects) + return; + + _currentEffects = effects; + + if (_pointerGrabbed) + { + XChangeActivePointerGrab( + _platform.Display, + EventMask.ButtonPressMask | EventMask.ButtonReleaseMask | EventMask.PointerMotionMask, + GetCursor(effects), + 0); + } + } + + private IntPtr GetCursor(DragDropEffects effects) + { + if (_cursorFactory is not null) + { + if ((effects & DragDropEffects.Copy) != 0) + return _cursorFactory.GetCursorHandle(StandardCursorType.DragCopy); + if ((effects & DragDropEffects.Move) != 0) + return _cursorFactory.GetCursorHandle(StandardCursorType.DragMove); + if ((effects & DragDropEffects.Link) != 0) + return _cursorFactory.GetCursorHandle(StandardCursorType.DragLink); + return _cursorFactory.DragNoDropCursorHandle; + } + + return _platform.Info.DefaultCursor; + } + public void Dispose() { if (_pointerGrabbed) @@ -409,7 +446,7 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra } _lastTarget = null; - _lastEffects = DragDropEffects.None; + _currentEffects = DragDropEffects.None; if (_dataProvider.GetOwner() == _sourceWindow) _dataProvider.SetOwner(0); diff --git a/src/Avalonia.X11/X11CursorFactory.cs b/src/Avalonia.X11/X11CursorFactory.cs index 1ac4c1fe8d..d68eed8c15 100644 --- a/src/Avalonia.X11/X11CursorFactory.cs +++ b/src/Avalonia.X11/X11CursorFactory.cs @@ -61,16 +61,28 @@ namespace Avalonia.X11 _cursors = new Dictionary(); } + // We don't have a "DragNo" standard cursor type, but Xcursor provides one + public IntPtr DragNoDropCursorHandle + { + get + { + if (field == 0) + field = LoadDragNoDropCursor(); + return field; + } + } + public ICursorImpl GetCursor(StandardCursorType cursorType) { - IntPtr handle; - if (cursorType == StandardCursorType.None) - handle = _nullCursor; - else - handle = GetCursorHandleCached(cursorType); + var handle = GetCursorHandle(cursorType); return new CursorImpl(handle); } + public IntPtr GetCursorHandle(StandardCursorType cursorType) + { + return cursorType == StandardCursorType.None ? _nullCursor : GetCursorHandleCached(cursorType); + } + public unsafe ICursorImpl CreateCursor(Bitmap cursor, PixelPoint hotSpot) { return new XImageCursor(_display, cursor, hotSpot); @@ -84,6 +96,15 @@ namespace Avalonia.X11 return XLib.XCreatePixmapCursor(display, pixmap, pixmap, ref color, ref color, 0, 0); } + private IntPtr LoadDragNoDropCursor() + { + var handle = XLib.XcursorLibraryLoadCursor(_display, "dnd-no-drop"); + + if (handle == 0) + handle = GetCursorHandleCached(StandardCursorType.No); + return handle; + } + private unsafe class XImageCursor : CursorImpl, IPlatformHandle { private readonly IntPtr _display; @@ -130,9 +151,10 @@ namespace Avalonia.X11 { if (!_cursors.TryGetValue(type, out var handle)) { - if(s_libraryCursors.TryGetValue(type, out var cursorName)) + if (s_libraryCursors.TryGetValue(type, out var cursorName)) handle = XLib.XcursorLibraryLoadCursor(_display, cursorName); - else if(s_mapping.TryGetValue(type, out var cursorShape)) + + if (handle == 0 && s_mapping.TryGetValue(type, out var cursorShape)) handle = XLib.XCreateFontCursor(_display, cursorShape); if (handle == IntPtr.Zero)