diff --git a/src/Avalonia.X11/Selections/DragDrop/X11DragSource.cs b/src/Avalonia.X11/Selections/DragDrop/X11DragSource.cs index 247fa50154..e6677213a5 100644 --- a/src/Avalonia.X11/Selections/DragDrop/X11DragSource.cs +++ b/src/Avalonia.X11/Selections/DragDrop/X11DragSource.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Input.Platform; +using Avalonia.Input.Raw; using Avalonia.Platform; using Avalonia.Threading; using static Avalonia.X11.Selections.DragDrop.XdndConstants; @@ -30,7 +31,7 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra var cursorFactory = AvaloniaLocator.Current.GetService(); - using var handler = new Handler(platform, window.Handle, dataTransfer.ToAsynchronous(), allowedEffects, cursorFactory); + using var handler = new Handler(platform, window.Handle, dataTransfer, allowedEffects, cursorFactory); await handler.Completion; return DragDropEffects.None; @@ -40,6 +41,7 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra { private readonly AvaloniaX11Platform _platform; private readonly IntPtr _sourceWindow; + private readonly IDataTransfer _dataTransfer; private readonly DragDropEffects _allowedEffects; private readonly TaskCompletionSource _completionSource = new(); private readonly DragDropDataProvider _dataProvider; @@ -47,19 +49,21 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra private X11WindowInfo? _originalSourceWindowInfo; private bool _pointerGrabbed; private XdndTargetInfo? _lastTarget; - private IntPtr _lastStatusAction; + private DragDropEffects _lastEffects; public Handler( AvaloniaX11Platform platform, IntPtr sourceWindow, - IAsyncDataTransfer dataTransfer, + IDataTransfer dataTransfer, DragDropEffects allowedEffects, ICursorFactory? cursorFactory) { _platform = platform; _sourceWindow = sourceWindow; + _dataTransfer = dataTransfer; _allowedEffects = allowedEffects; - _dataProvider = new DragDropDataProvider(platform, dataTransfer); + _lastEffects = allowedEffects; + _dataProvider = new DragDropDataProvider(platform, dataTransfer.ToAsynchronous()); if (!platform.Windows.TryGetValue(sourceWindow, out var sourceWindowInfo)) { @@ -145,23 +149,40 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra private void OnMotionNotify(in XMotionEvent motion) { - var target = FindXdndTarget(motion.x_root, motion.y_root); + var rootPosition = new PixelPoint(motion.x_root, motion.y_root); + var target = FindXdndTarget(rootPosition); if (_lastTarget != target) { if (_lastTarget is { } lastTarget) - SendXdndLeave(lastTarget); + { + if (lastTarget.InProcessWindow is not null) + ProcessRawDragEvent(lastTarget.InProcessWindow, RawDragEventType.DragLeave, rootPosition); + else + SendXdndLeave(lastTarget); + } _lastTarget = target; + _lastEffects = _allowedEffects; if (target is { } newTarget) - SendXdndEnter(newTarget); + { + if (newTarget.InProcessWindow is not null) + ProcessRawDragEvent(newTarget.InProcessWindow, RawDragEventType.DragEnter, rootPosition); + else + SendXdndEnter(newTarget); + } } if (target is { } currentTarget) { - var action = XdndActionHelper.EffectsToAction(_allowedEffects, _platform.Info.Atoms); - SendXdndPosition(currentTarget, motion.x_root, motion.y_root, motion.time, action); + if (currentTarget.InProcessWindow is not null) + ProcessRawDragEvent(currentTarget.InProcessWindow, RawDragEventType.DragOver, rootPosition); + else + { + var action = XdndActionHelper.EffectsToAction(_allowedEffects, _platform.Info.Atoms); + SendXdndPosition(currentTarget, rootPosition, motion.time, action); + } } } @@ -172,8 +193,16 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra if (_lastTarget is not { } lastTarget) return; - _dataProvider.SetOwner(_sourceWindow); - SendXdndDrop(lastTarget, button.time); + if (lastTarget.InProcessWindow is not null) + { + var rootPosition = new PixelPoint(button.x_root, button.y_root); + ProcessRawDragEvent(lastTarget.InProcessWindow, RawDragEventType.Drop, rootPosition); + } + else + { + _dataProvider.SetOwner(_sourceWindow); + SendXdndDrop(lastTarget, button.time); + } } private void OnXdndStatus(in XClientMessageEvent message) @@ -182,7 +211,8 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra return; var accepted = (message.ptr2 & 1) == 1; - _lastStatusAction = accepted ? message.ptr5 : 0; + var action = accepted ? message.ptr5 : 0; + _lastEffects = XdndActionHelper.ActionToEffects(action, _platform.Info.Atoms); } private void OnXdndFinished(in XClientMessageEvent message) @@ -192,20 +222,17 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra _lastTarget = null; - IntPtr action; if (lastTarget.Version >= 5) { var accepted = (message.ptr2 & 1) == 1; - action = accepted ? message.ptr3 : 0; + var action = accepted ? message.ptr5 : 0; + _lastEffects = XdndActionHelper.ActionToEffects(action, _platform.Info.Atoms); } - else - action = _lastStatusAction; - var effects = XdndActionHelper.ActionToEffects(action, _platform.Info.Atoms); - _completionSource.TrySetResult(effects); + _completionSource.TrySetResult(_lastEffects); } - private XdndTargetInfo? FindXdndTarget(int x, int y) + private XdndTargetInfo? FindXdndTarget(PixelPoint rootPosition) { var display = _platform.Display; var rootWindow = _platform.Info.RootWindow; @@ -216,8 +243,11 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra if (TryGetXdndTargetInfo(currentWindow) is { } info) return info; - if (!XTranslateCoordinates(display, rootWindow, currentWindow, x, y, out _, out _, out var childWindow)) + if (!XTranslateCoordinates(display, rootWindow, currentWindow, rootPosition.X, rootPosition.Y, + out _, out _, out var childWindow)) + { return null; + } currentWindow = childWindow; } @@ -227,11 +257,18 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra private XdndTargetInfo? TryGetXdndTargetInfo(IntPtr window) { + // Special case our own windows: we don't need to go through X for them. + if (_platform.Windows.TryGetValue(window, out var windowInfo) + && windowInfo.Window is { } inProcessWindow) + { + return new XdndTargetInfo(XdndVersion, window, window, inProcessWindow); + } + var proxyWindow = GetXdndProxyWindow(window); var messageWindow = proxyWindow != 0 ? proxyWindow : window; var version = GetXdndVersion(messageWindow); - return version != 0 ? new XdndTargetInfo(version, window, messageWindow) : null; + return version != 0 ? new XdndTargetInfo(version, window, messageWindow, null) : null; } private IntPtr GetXdndProxyWindow(IntPtr window) @@ -277,13 +314,13 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra _formatAtoms.Length >= 3 ? _formatAtoms[2] : 0); } - private void SendXdndPosition(XdndTargetInfo target, int x, int y, IntPtr timestamp, IntPtr action) + private void SendXdndPosition(XdndTargetInfo target, PixelPoint rootPosition, IntPtr timestamp, IntPtr action) { SendXdndMessage( _platform.Info.Atoms.XdndPosition, target.MessageWindow, 0, - (x << 16) | y, + (rootPosition.X << 16) | rootPosition.Y, timestamp, action); } @@ -327,6 +364,30 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra XFlush(_platform.Display); } + private void ProcessRawDragEvent(X11Window targetWindow, RawDragEventType eventType, PixelPoint rootPosition) + { + if (targetWindow.DragDropDevice is not { } dragDropDevice) + { + _lastEffects = DragDropEffects.None; + return; + } + + var localPosition = targetWindow.PointToClient(rootPosition); + + var dragEvent = new RawDragEvent( + dragDropDevice, + eventType, + targetWindow.InputRoot, + localPosition, + _dataTransfer, + _allowedEffects, + RawInputModifiers.None); + + dragDropDevice.ProcessRawEvent(dragEvent); + + _lastEffects = dragEvent.Effects; + } + private void UngrabPointer() { _pointerGrabbed = false; @@ -348,7 +409,7 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra } _lastTarget = null; - _lastStatusAction = 0; + _lastEffects = DragDropEffects.None; if (_dataProvider.GetOwner() == _sourceWindow) _dataProvider.SetOwner(0); @@ -359,6 +420,7 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra private readonly record struct XdndTargetInfo( byte Version, IntPtr TargetWindow, - IntPtr MessageWindow); + IntPtr MessageWindow, + X11Window? InProcessWindow); } } diff --git a/src/Avalonia.X11/Selections/DragDrop/X11DropTarget.cs b/src/Avalonia.X11/Selections/DragDrop/X11DropTarget.cs index f4d1578777..65b2be5c72 100644 --- a/src/Avalonia.X11/Selections/DragDrop/X11DropTarget.cs +++ b/src/Avalonia.X11/Selections/DragDrop/X11DropTarget.cs @@ -19,6 +19,7 @@ internal sealed class X11DropTarget private readonly IntPtr _display; private readonly X11Atoms _atoms; private DragDropDataTransfer? _currentDrag; + private DragDropEffects _resultEffects; public X11DropTarget(IDragDropDevice dragDropDevice, IXdndWindow window, IntPtr _display, X11Atoms atoms) { @@ -102,7 +103,7 @@ internal sealed class X11DropTarget _currentDrag.LastPosition = position; _currentDrag.LastTimestamp = message.ptr4; - var dragEnter = new RawDragEvent( + var dragEvent = new RawDragEvent( _dragDropDevice, eventType, _currentDrag.InputRoot, @@ -111,9 +112,10 @@ internal sealed class X11DropTarget requestedEffects, RawInputModifiers.None); - _dragDropDevice.ProcessRawEvent(dragEnter); + _dragDropDevice.ProcessRawEvent(dragEvent); - var resultAction = XdndActionHelper.EffectsToAction(dragEnter.Effects, _atoms); + _resultEffects = dragEvent.Effects; + var resultAction = XdndActionHelper.EffectsToAction(_resultEffects, _atoms); var evt = new XEvent { @@ -145,6 +147,8 @@ internal sealed class X11DropTarget if (sourceWindow != _currentDrag.SourceWindow) return; + _resultEffects = DragDropEffects.None; + var dragLeave = new RawDragEvent( _dragDropDevice, RawDragEventType.DragLeave, @@ -174,7 +178,7 @@ internal sealed class X11DropTarget _currentDrag.InputRoot, _currentDrag.LastPosition ?? default, _currentDrag, - DragDropEffects.None, + _resultEffects, RawInputModifiers.None); _dragDropDevice.ProcessRawEvent(drop); diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 92ed2840e3..61a7b14de9 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -280,7 +280,10 @@ namespace Avalonia.X11 }); if (AvaloniaLocator.Current.GetService() is { } dragDropDevice) + { + DragDropDevice = dragDropDevice; _dropTarget = new X11DropTarget(dragDropDevice, this, _x11.Display, _x11.Atoms); + } platform.X11Screens.Changed += OnScreensChanged; } @@ -523,6 +526,8 @@ namespace Avalonia.X11 public Compositor Compositor => _platform.Compositor; + public IDragDropDevice? DragDropDevice { get; } + private PlatformAllowedWindowActions GetAllowedActions(IntPtr[]? netSupported) { if (netSupported == null) @@ -545,7 +550,7 @@ namespace Avalonia.X11 private void OnNetSupportedChanged() => AllowedWindowActionsChanged?.Invoke(AllowedWindowActions); - + private void OnEvent(ref XEvent ev) { if (_inputRoot is null) @@ -1174,7 +1179,7 @@ namespace Avalonia.X11 _platform.X11Screens.Changed -= OnScreensChanged; _platform.Globals.NetSupportedChanged -= OnNetSupportedChanged; - + if (_useRenderWindow && _renderHandle != IntPtr.Zero) { _renderHandle = IntPtr.Zero;