Browse Source

Xdnd: handle drop properly

pull/20926/head
Julien Lebosquain 7 months ago
parent
commit
5bf439383c
No known key found for this signature in database GPG Key ID: 1833CAD10ACC46FD
  1. 48
      src/Avalonia.X11/Selections/Clipboard/X11ClipboardImpl.cs
  2. 13
      src/Avalonia.X11/Selections/DataFormatHelper.cs
  3. 14
      src/Avalonia.X11/Selections/DragDrop/DragDropDataProvider.cs
  4. 156
      src/Avalonia.X11/Selections/DragDrop/X11DragSource.cs
  5. 8
      src/Avalonia.X11/Selections/DragDrop/X11DropTarget.cs
  6. 82
      src/Avalonia.X11/Selections/SelectionDataProvider.cs
  7. 8
      src/Avalonia.X11/X11Window.cs

48
src/Avalonia.X11/Selections/Clipboard/X11ClipboardImpl.cs

@ -7,35 +7,41 @@ using static Avalonia.X11.XLib;
namespace Avalonia.X11.Selections.Clipboard;
internal sealed class X11ClipboardImpl(AvaloniaX11Platform platform)
: SelectionDataProvider(platform, platform.Info.Atoms.CLIPBOARD), IOwnedClipboardImpl
internal sealed class X11ClipboardImpl : SelectionDataProvider, IOwnedClipboardImpl
{
private TaskCompletionSource? _storeAtomTcs;
private IntPtr _handle;
protected override void OnEvent(ref XEvent ev)
public X11ClipboardImpl(AvaloniaX11Platform platform)
: base(platform, platform.Info.Atoms.CLIPBOARD)
{
if (ev.type == XEventName.SelectionClear)
_handle = CreateEventWindow(platform, OnEvent);
}
private void OnEvent(ref XEvent evt)
{
if (evt.type == XEventName.SelectionClear)
{
// We night have already regained the clipboard ownership by the time a SelectionClear message arrives.
if (GetOwner() != Window)
// We might have already regained the clipboard ownership by the time a SelectionClear message arrives.
if (GetOwner() != _handle)
DataTransfer = null;
_storeAtomTcs?.TrySetResult();
}
else if (ev.type == XEventName.SelectionNotify)
else if (evt.type == XEventName.SelectionNotify)
{
var atoms = Platform.Info.Atoms;
if (ev.SelectionEvent.selection == atoms.CLIPBOARD_MANAGER &&
ev.SelectionEvent.target == atoms.SAVE_TARGETS)
if (evt.SelectionEvent.selection == atoms.CLIPBOARD_MANAGER &&
evt.SelectionEvent.target == atoms.SAVE_TARGETS)
{
_storeAtomTcs?.TrySetResult();
}
}
else
base.OnEvent(ref ev);
else if (evt.type == XEventName.SelectionRequest)
OnSelectionRequest(in evt.SelectionRequestEvent);
}
private SelectionReadSession OpenReadSession()
@ -67,11 +73,11 @@ internal sealed class X11ClipboardImpl(AvaloniaX11Platform platform)
var atomValues = ConvertDataTransfer(dataTransfer);
XChangeProperty(Platform.Display, Window, atoms.AVALONIA_SAVE_TARGETS_PROPERTY_ATOM, atoms.ATOM, 32,
XChangeProperty(Platform.Display, _handle, atoms.AVALONIA_SAVE_TARGETS_PROPERTY_ATOM, atoms.ATOM, 32,
PropertyMode.Replace, atomValues, atomValues.Length);
XConvertSelection(Platform.Display, atoms.CLIPBOARD_MANAGER, atoms.SAVE_TARGETS,
atoms.AVALONIA_SAVE_TARGETS_PROPERTY_ATOM, Window, 0);
atoms.AVALONIA_SAVE_TARGETS_PROPERTY_ATOM, _handle, 0);
await _storeAtomTcs.Task;
}
@ -90,7 +96,7 @@ internal sealed class X11ClipboardImpl(AvaloniaX11Platform platform)
if (owner == 0)
return null;
if (owner == Window && DataTransfer is { } storedDataTransfer)
if (owner == _handle && DataTransfer is { } storedDataTransfer)
return storedDataTransfer;
// Get the formats while we're in an async method, since IAsyncDataTransfer.GetFormats() is synchronous.
@ -115,10 +121,20 @@ internal sealed class X11ClipboardImpl(AvaloniaX11Platform platform)
public Task SetDataAsync(IAsyncDataTransfer dataTransfer)
{
DataTransfer = dataTransfer;
SetOwner(Window);
SetOwner(_handle);
return StoreAtomsInClipboardManager(dataTransfer);
}
public Task<bool> IsCurrentOwnerAsync()
=> Task.FromResult(GetOwner() == Window);
=> Task.FromResult(GetOwner() == _handle);
public override void Dispose()
{
if (_handle == 0)
return;
Platform.Windows.Remove(_handle);
XDestroyWindow(Platform.Display, _handle);
_handle = 0;
}
}

13
src/Avalonia.X11/Selections/DataFormatHelper.cs

@ -132,6 +132,19 @@ internal static class DataFormatHelper
return [atoms.GetAtom(systemName)];
}
public static IntPtr[] ToAtoms(IReadOnlyList<DataFormat> formats, X11Atoms atoms)
{
var atomValues = new List<IntPtr>(formats.Count);
foreach (var format in formats)
{
foreach (var atom in ToAtoms(format, atoms))
atomValues.Add(atom);
}
return atomValues.ToArray();
}
private static IntPtr GetPreferredStringFormatAtom(IntPtr[] textFormatAtoms, X11Atoms atoms)
{
ReadOnlySpan<IntPtr> preferredFormats = [atoms.UTF16_STRING, atoms.UTF8_STRING, atoms.STRING];

14
src/Avalonia.X11/Selections/DragDrop/DragDropDataProvider.cs

@ -1,3 +1,4 @@
using System;
using Avalonia.Input;
namespace Avalonia.X11.Selections.DragDrop;
@ -10,6 +11,15 @@ internal sealed class DragDropDataProvider : SelectionDataProvider
DataTransfer = dataTransfer;
}
public void SetAsOwner()
=> SetOwner(Window);
public new IntPtr GetOwner()
=> base.GetOwner();
public new void SetOwner(IntPtr window)
=> base.SetOwner(window);
public override void Dispose()
{
DataTransfer?.Dispose();
DataTransfer = null;
}
}

156
src/Avalonia.X11/Selections/DragDrop/X11DragSource.cs

@ -41,13 +41,13 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
{
private readonly AvaloniaX11Platform _platform;
private readonly IntPtr _sourceWindow;
private readonly IAsyncDataTransfer _dataTransfer;
private readonly DragDropEffects _allowedEffects;
private readonly TaskCompletionSource<DragDropEffects> _completionSource = new();
private readonly DragDropDataProvider _dataProvider;
private readonly IntPtr[] _formatAtoms;
private X11EventDispatcher.EventHandler? _originalEventHandler;
private bool _pointerGrabbed;
private XdndTargetInfo? _lastTarget;
private DragDropDataProvider? _dataProvider;
private IntPtr _lastStatusAction;
public Handler(
@ -59,11 +59,12 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
{
_platform = platform;
_sourceWindow = sourceWindow;
_dataTransfer = dataTransfer;
_allowedEffects = allowedEffects;
_dataProvider = new DragDropDataProvider(platform, dataTransfer);
if (!platform.Windows.TryGetValue(sourceWindow, out _originalEventHandler))
{
_formatAtoms = [];
_completionSource.TrySetResult(DragDropEffects.None);
return;
}
@ -91,6 +92,7 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
{
Console.WriteLine($"Could not grab: {grabResult}");
_formatAtoms = [];
_completionSource.TrySetResult(DragDropEffects.None);
return;
}
@ -99,69 +101,35 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
_pointerGrabbed = true;
_platform.Windows[_sourceWindow] = OnEvent;
}
public Task<DragDropEffects> Completion
=> _completionSource.Task;
private IntPtr[] DataFormatAtoms
{
get
{
return field ??= CalcDataFormatAtoms();
IntPtr[] CalcDataFormatAtoms()
{
var atomValues = new List<IntPtr>(_dataTransfer.Formats.Count);
foreach (var format in _dataTransfer.Formats)
{
foreach (var atom in DataFormatHelper.ToAtoms(format, _platform.Info.Atoms))
atomValues.Add(atom);
}
var atoms = _platform.Info.Atoms;
_formatAtoms = DataFormatHelper.ToAtoms(dataTransfer.Formats, atoms);
return atomValues.ToArray();
}
}
XChangeProperty(
_platform.Display,
_sourceWindow,
atoms.XdndTypeList,
atoms.ATOM,
32,
PropertyMode.Replace,
_formatAtoms,
_formatAtoms.Length);
}
private DragDropDataProvider DataProvider
{
get
{
return _dataProvider ??= CreateDataProvider();
DragDropDataProvider CreateDataProvider()
{
var dataProvider = new DragDropDataProvider(_platform, _dataTransfer);
var formats = DataFormatAtoms;
XChangeProperty(
_platform.Display,
dataProvider.Window,
_platform.Info.Atoms.XdndTypeList,
_platform.Info.Atoms.ATOM,
32,
PropertyMode.Replace,
formats,
formats.Length);
return dataProvider;
}
}
}
public Task<DragDropEffects> Completion
=> _completionSource.Task;
private void OnEvent(ref XEvent xev)
private void OnEvent(ref XEvent evt)
{
if (xev.type == XEventName.MotionNotify && _pointerGrabbed)
OnMotionNotify(in xev.MotionEvent);
if (evt.type == XEventName.MotionNotify && _pointerGrabbed)
OnMotionNotify(in evt.MotionEvent);
else if (xev.type == XEventName.ButtonRelease && _pointerGrabbed)
OnButtonRelease(in xev.ButtonEvent);
else if (evt.type == XEventName.ButtonRelease && _pointerGrabbed)
OnButtonRelease(in evt.ButtonEvent);
else if (xev.type == XEventName.ClientMessage)
else if (evt.type == XEventName.ClientMessage)
{
ref var message = ref xev.ClientMessageEvent;
ref var message = ref evt.ClientMessageEvent;
var atoms = _platform.Info.Atoms;
if (message.message_type == atoms.XdndStatus)
@ -169,11 +137,14 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
else if (message.message_type == atoms.XdndFinished)
OnXdndFinished(in message);
else
_originalEventHandler?.Invoke(ref xev);
_originalEventHandler?.Invoke(ref evt);
}
else if (evt.type == XEventName.SelectionRequest)
_dataProvider.OnSelectionRequest(in evt.SelectionRequestEvent);
else
_originalEventHandler?.Invoke(ref xev);
_originalEventHandler?.Invoke(ref evt);
}
private void OnMotionNotify(in XMotionEvent motion)
@ -204,19 +175,18 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
{
UngrabPointer();
if (_lastTarget is { } lastTarget)
SendXdndDrop(lastTarget, button.time);
if (_lastTarget is not { } lastTarget)
return;
_dataProvider.SetOwner(_sourceWindow);
SendXdndDrop(lastTarget, button.time);
}
private void OnXdndStatus(in XClientMessageEvent message)
{
Console.WriteLine("Received XdndStatus");
if (_lastTarget is null)
return;
var targetWindow = message.ptr1;
if (targetWindow == 0 || targetWindow != _dataProvider?.Window)
if (_lastTarget is not { } lastTarget || message.ptr1 != lastTarget.TargetWindow)
return;
var accepted = (message.ptr2 & 1) == 1;
@ -229,11 +199,7 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
{
Console.WriteLine("Received XdndFinished");
if (_lastTarget is not { } lastTarget)
return;
var targetWindow = message.ptr1;
if (targetWindow == 0 || targetWindow != _dataProvider?.Window)
if (_lastTarget is not { } lastTarget || message.ptr1 != lastTarget.TargetWindow)
return;
_lastTarget = null;
@ -314,17 +280,15 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
Console.WriteLine("Sending XdndEnter");
var version = Math.Min(target.Version, XdndVersion);
var formats = DataFormatAtoms;
var hasMoreFormats = formats.Length > 3;
var hasMoreFormats = _formatAtoms.Length > 3;
SendXdndMessage(
_platform.Info.Atoms.XdndEnter,
target,
DataProvider.Window,
target.MessageWindow,
(version << 24) | (hasMoreFormats ? 1 : 0),
formats.Length >= 1 ? formats[0] : 0,
formats.Length >= 2 ? formats[1] : 0,
formats.Length >= 3 ? formats[2] : 0);
_formatAtoms.Length >= 1 ? _formatAtoms[0] : 0,
_formatAtoms.Length >= 2 ? _formatAtoms[1] : 0,
_formatAtoms.Length >= 3 ? _formatAtoms[2] : 0);
}
private void SendXdndPosition(XdndTargetInfo target, int x, int y, IntPtr timestamp, IntPtr action)
@ -333,8 +297,7 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
SendXdndMessage(
_platform.Info.Atoms.XdndPosition,
target,
DataProvider.Window,
target.MessageWindow,
0,
(x << 16) | y,
timestamp,
@ -345,34 +308,19 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
{
Console.WriteLine("Sending XdndLeave");
SendXdndMessage(
_platform.Info.Atoms.XdndLeave,
target,
DataProvider.Window,
0,
0,
0,
0);
SendXdndMessage(_platform.Info.Atoms.XdndLeave, target.MessageWindow, 0, 0, 0, 0);
}
private void SendXdndDrop(XdndTargetInfo target, IntPtr timestamp)
{
Console.WriteLine("Sending XdndDrop");
SendXdndMessage(
_platform.Info.Atoms.XdndDrop,
target,
DataProvider.Window,
0,
timestamp,
0,
0);
SendXdndMessage(_platform.Info.Atoms.XdndDrop, target.MessageWindow, 0, timestamp, 0, 0);
}
private void SendXdndMessage(
IntPtr messageType,
XdndTargetInfo target,
IntPtr sourceWindow,
IntPtr messageWindow,
IntPtr ptr2,
IntPtr ptr3,
IntPtr ptr4,
@ -384,10 +332,10 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
{
type = XEventName.ClientMessage,
display = _platform.Display,
window = target.MessageWindow,
window = messageWindow,
message_type = messageType,
format = 32,
ptr1 = sourceWindow,
ptr1 = _sourceWindow,
ptr2 = ptr2,
ptr3 = ptr3,
ptr4 = ptr4,
@ -395,7 +343,7 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
}
};
XSendEvent(_platform.Display, target.MessageWindow, false, (IntPtr)EventMask.NoEventMask, ref evt);
XSendEvent(_platform.Display, messageWindow, false, (IntPtr)EventMask.NoEventMask, ref evt);
XFlush(_platform.Display);
}
@ -421,8 +369,10 @@ internal sealed class X11DragSource(AvaloniaX11Platform platform) : IPlatformDra
_lastTarget = null;
_lastStatusAction = 0;
_dataProvider?.Dispose();
_dataProvider = null;
if (_dataProvider.GetOwner() == _sourceWindow)
_dataProvider.SetOwner(0);
_dataProvider.Dispose();
}
private readonly record struct XdndTargetInfo(

8
src/Avalonia.X11/Selections/DragDrop/X11DropTarget.cs

@ -31,7 +31,7 @@ internal sealed class X11DropTarget
XChangeProperty(_display, _window.Handle, _atoms.XdndAware, _atoms.ATOM, 32, PropertyMode.Replace, ref version, 1);
}
public void HandleXdndEnter(in XClientMessageEvent message)
public void OnXdndEnter(in XClientMessageEvent message)
{
if (_window.InputRoot is not { } inputRoot)
return;
@ -84,7 +84,7 @@ internal sealed class X11DropTarget
inputRoot);
}
public void HandleXdndPosition(in XClientMessageEvent message)
public void OnXdndPosition(in XClientMessageEvent message)
{
if (_currentDrag is null)
return;
@ -136,7 +136,7 @@ internal sealed class X11DropTarget
XFlush(_display);
}
public void HandleXdndLeave(in XClientMessageEvent message)
public void OnXdndLeave(in XClientMessageEvent message)
{
if (_currentDrag is null)
return;
@ -159,7 +159,7 @@ internal sealed class X11DropTarget
DisposeCurrentDrag();
}
public void HandleXdndDrop(in XClientMessageEvent message)
public void OnXdndDrop(in XClientMessageEvent message)
{
if (_currentDrag is null)
return;

82
src/Avalonia.X11/Selections/SelectionDataProvider.cs

@ -1,6 +1,5 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
@ -16,64 +15,55 @@ namespace Avalonia.X11.Selections;
/// </summary>
internal abstract class SelectionDataProvider : IDisposable
{
private readonly IntPtr _selection;
private readonly int _maximumPropertySize;
public AvaloniaX11Platform Platform { get; }
public IntPtr Selection { get; }
public IntPtr Window { get; private set; }
protected AvaloniaX11Platform Platform { get; }
public IAsyncDataTransfer? DataTransfer { get; protected set; }
protected SelectionDataProvider(AvaloniaX11Platform platform, IntPtr selection)
{
Platform = platform;
Selection = selection;
_selection = selection;
var maxRequestSize = XExtendedMaxRequestSize(Platform.Display);
if (maxRequestSize == 0)
maxRequestSize = XMaxRequestSize(Platform.Display);
_maximumPropertySize = (int)Math.Min(0x100000, maxRequestSize - 0x100);
Window = CreateEventWindow(platform, OnEvent);
}
protected IntPtr GetOwner()
=> XGetSelectionOwner(Platform.Display, Selection);
=> XGetSelectionOwner(Platform.Display, _selection);
protected void SetOwner(IntPtr owner)
=> XSetSelectionOwner(Platform.Display, Selection, owner, 0);
=> XSetSelectionOwner(Platform.Display, _selection, owner, 0);
protected virtual void OnEvent(ref XEvent ev)
public void OnSelectionRequest(in XSelectionRequestEvent request)
{
if (ev.type == XEventName.SelectionRequest)
var response = new XEvent
{
var sel = ev.SelectionRequestEvent;
var resp = new XEvent
{
SelectionEvent =
{
type = XEventName.SelectionNotify,
send_event = 1,
display = Platform.Display,
selection = sel.selection,
target = sel.target,
requestor = sel.requestor,
time = sel.time,
property = 0
}
};
if (sel.selection == Selection)
SelectionEvent =
{
resp.SelectionEvent.property = WriteTargetToProperty(sel.target, sel.requestor, sel.property);
type = XEventName.SelectionNotify,
send_event = 1,
display = Platform.Display,
selection = request.selection,
target = request.target,
requestor = request.requestor,
time = request.time,
property = 0
}
};
XSendEvent(Platform.Display, sel.requestor, false, new IntPtr((int)EventMask.NoEventMask), ref resp);
if (request.selection == _selection)
{
response.SelectionEvent.property = WriteTargetToProperty(request.target, request.requestor, request.property);
}
XSendEvent(Platform.Display, request.requestor, false, new IntPtr((int)EventMask.NoEventMask), ref response);
IntPtr WriteTargetToProperty(IntPtr target, IntPtr window, IntPtr property)
{
var atoms = Platform.Info.Atoms;
@ -247,32 +237,10 @@ internal abstract class SelectionDataProvider : IDisposable
protected IntPtr[] ConvertDataTransfer(IAsyncDataTransfer? dataTransfer)
{
var atoms = Platform.Info.Atoms;
var atomValues = new List<IntPtr> { atoms.TARGETS, atoms.MULTIPLE };
if (dataTransfer is not null)
{
foreach (var format in dataTransfer.Formats)
{
foreach (var atom in DataFormatHelper.ToAtoms(format, atoms))
atomValues.Add(atom);
}
}
return atomValues.ToArray();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
var formatAtoms = DataFormatHelper.ToAtoms(dataTransfer?.Formats ?? [], atoms);
return [atoms.TARGETS, atoms.MULTIPLE, ..formatAtoms];
}
protected virtual void Dispose(bool disposing)
{
if (Window == 0)
return;
XDestroyWindow(Platform.Display, Window);
Window = 0;
}
public abstract void Dispose();
}

8
src/Avalonia.X11/X11Window.cs

@ -700,13 +700,13 @@ namespace Avalonia.X11
}
}
else if (messageType == _x11.Atoms.XdndEnter)
_dropTarget?.HandleXdndEnter(ev.ClientMessageEvent);
_dropTarget?.OnXdndEnter(ev.ClientMessageEvent);
else if (messageType == _x11.Atoms.XdndPosition)
_dropTarget?.HandleXdndPosition(ev.ClientMessageEvent);
_dropTarget?.OnXdndPosition(ev.ClientMessageEvent);
else if (messageType == _x11.Atoms.XdndLeave)
_dropTarget?.HandleXdndLeave(ev.ClientMessageEvent);
_dropTarget?.OnXdndLeave(ev.ClientMessageEvent);
else if (messageType == _x11.Atoms.XdndDrop)
_dropTarget?.HandleXdndDrop(ev.ClientMessageEvent);
_dropTarget?.OnXdndDrop(ev.ClientMessageEvent);
}
else if (ev.type == XEventName.KeyPress || ev.type == XEventName.KeyRelease)
{

Loading…
Cancel
Save