diff --git a/src/Avalonia.X11/Selections/Clipboard/X11ClipboardImpl.cs b/src/Avalonia.X11/Selections/Clipboard/X11ClipboardImpl.cs index a322b1c3a4..a2503ef893 100644 --- a/src/Avalonia.X11/Selections/Clipboard/X11ClipboardImpl.cs +++ b/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 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; + } } diff --git a/src/Avalonia.X11/Selections/DataFormatHelper.cs b/src/Avalonia.X11/Selections/DataFormatHelper.cs index ff44fc9953..aac23afb80 100644 --- a/src/Avalonia.X11/Selections/DataFormatHelper.cs +++ b/src/Avalonia.X11/Selections/DataFormatHelper.cs @@ -132,6 +132,19 @@ internal static class DataFormatHelper return [atoms.GetAtom(systemName)]; } + public static IntPtr[] ToAtoms(IReadOnlyList formats, X11Atoms atoms) + { + var atomValues = new List(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 preferredFormats = [atoms.UTF16_STRING, atoms.UTF8_STRING, atoms.STRING]; diff --git a/src/Avalonia.X11/Selections/DragDrop/DragDropDataProvider.cs b/src/Avalonia.X11/Selections/DragDrop/DragDropDataProvider.cs index 7c4a822b95..d01cf8a6bc 100644 --- a/src/Avalonia.X11/Selections/DragDrop/DragDropDataProvider.cs +++ b/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; + } } diff --git a/src/Avalonia.X11/Selections/DragDrop/X11DragSource.cs b/src/Avalonia.X11/Selections/DragDrop/X11DragSource.cs index 28fbf80f34..a9c01d5e68 100644 --- a/src/Avalonia.X11/Selections/DragDrop/X11DragSource.cs +++ b/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 _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 Completion - => _completionSource.Task; - private IntPtr[] DataFormatAtoms - { - get - { - return field ??= CalcDataFormatAtoms(); - - IntPtr[] CalcDataFormatAtoms() - { - var atomValues = new List(_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 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( diff --git a/src/Avalonia.X11/Selections/DragDrop/X11DropTarget.cs b/src/Avalonia.X11/Selections/DragDrop/X11DropTarget.cs index 8d8670caa7..f4d1578777 100644 --- a/src/Avalonia.X11/Selections/DragDrop/X11DropTarget.cs +++ b/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; diff --git a/src/Avalonia.X11/Selections/SelectionDataProvider.cs b/src/Avalonia.X11/Selections/SelectionDataProvider.cs index e9c947f0e1..85547a7c89 100644 --- a/src/Avalonia.X11/Selections/SelectionDataProvider.cs +++ b/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; /// 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 { 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(); } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index d64113a37b..1ce6a6cfc5 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/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) {