Browse Source

Xdnd: share common DataReader/ReadSession with clipboard

pull/20926/head
Julien Lebosquain 7 months ago
parent
commit
bec4bec6e7
No known key found for this signature in database GPG Key ID: 1833CAD10ACC46FD
  1. 78
      src/Avalonia.X11/Clipboard/ClipboardDataReader.cs
  2. 15
      src/Avalonia.X11/DragDrop/DragDropDataTransferItem.cs
  3. 39
      src/Avalonia.X11/Selections/Clipboard/ClipboardDataReader.cs
  4. 12
      src/Avalonia.X11/Selections/Clipboard/ClipboardDataTransfer.cs
  5. 9
      src/Avalonia.X11/Selections/Clipboard/ClipboardDataTransferItem.cs
  6. 2
      src/Avalonia.X11/Selections/Clipboard/ClipboardReadSessionFactory.cs
  7. 5
      src/Avalonia.X11/Selections/Clipboard/EventStreamWindow.cs
  8. 50
      src/Avalonia.X11/Selections/Clipboard/X11Clipboard.cs
  9. 4
      src/Avalonia.X11/Selections/DataFormatHelper.cs
  10. 49
      src/Avalonia.X11/Selections/DragDrop/DragDropDataReader.cs
  11. 22
      src/Avalonia.X11/Selections/DragDrop/DragDropDataTransfer.cs
  12. 31
      src/Avalonia.X11/Selections/DragDrop/DragDropDataTransferItem.cs
  13. 2
      src/Avalonia.X11/Selections/DragDrop/IXdndWindow.cs
  14. 10
      src/Avalonia.X11/Selections/DragDrop/SynchronousXEventWaiter.cs
  15. 32
      src/Avalonia.X11/Selections/DragDrop/XdndHandler.cs
  16. 4
      src/Avalonia.X11/Selections/IXEventWaiter.cs
  17. 108
      src/Avalonia.X11/Selections/SelectionDataReader.cs
  18. 10
      src/Avalonia.X11/Selections/SelectionReadSession.cs
  19. 4
      src/Avalonia.X11/Selections/UriListHelper.cs
  20. 4
      src/Avalonia.X11/X11Platform.cs
  21. 16
      src/Avalonia.X11/X11Window.cs

78
src/Avalonia.X11/Clipboard/ClipboardDataReader.cs

@ -1,78 +0,0 @@
using System;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Input;
using Avalonia.Media.Imaging;
using static Avalonia.X11.XLib;
namespace Avalonia.X11.Clipboard;
/// <summary>
/// An object used to read values, converted to the correct format, from the X11 clipboard.
/// </summary>
internal sealed class ClipboardDataReader(
X11Info x11,
AvaloniaX11Platform platform,
IntPtr[] textFormatAtoms,
IntPtr owner,
DataFormat[] dataFormats)
: IDisposable
{
private IntPtr _owner = owner;
private bool IsOwnerStillValid()
=> _owner != IntPtr.Zero && XGetSelectionOwner(x11.Display, x11.Atoms.CLIPBOARD) == _owner;
public async Task<object?> TryGetAsync(DataFormat format)
{
if (!IsOwnerStillValid())
return null;
var formatAtom = ClipboardDataFormatHelper.ToAtom(format, textFormatAtoms, x11.Atoms, dataFormats);
if (formatAtom == IntPtr.Zero)
return null;
using var session = ClipboardReadSessionFactory.CreateSession(platform);
var result = await session.SendDataRequest(formatAtom, 0).ConfigureAwait(false);
return ConvertDataResult(result, format, formatAtom);
}
private object? ConvertDataResult(SelectionReadSession.GetDataResult? result, DataFormat format, IntPtr formatAtom)
{
if (result is null)
return null;
if (DataFormat.Text.Equals(format))
{
return ClipboardDataFormatHelper.TryGetStringEncoding(result.TypeAtom, x11.Atoms) is { } textEncoding ?
textEncoding.GetString(result.AsBytes()) :
null;
}
if (DataFormat.Bitmap.Equals(format))
{
using var data = result.AsStream();
return new Bitmap(data);
}
if (DataFormat.File.Equals(format))
{
// text/uri-list might not be supported
return formatAtom != IntPtr.Zero && result.TypeAtom == formatAtom ?
ClipboardUriListHelper.Utf8BytesToFileUriList(result.AsBytes()) :
null;
}
if (format is DataFormat<string>)
return Encoding.UTF8.GetString(result.AsBytes());
if (format is DataFormat<byte[]>)
return result.AsBytes();
return null;
}
public void Dispose()
=> _owner = IntPtr.Zero;
}

15
src/Avalonia.X11/DragDrop/DragDropDataTransferItem.cs

@ -1,15 +0,0 @@
using Avalonia.Input;
using Avalonia.Input.Platform;
namespace Avalonia.X11.DragDrop;
internal sealed class DragDropDataTransferItem(DataFormat[] formats) : PlatformDataTransferItem
{
protected override DataFormat[] ProvideFormats()
=> formats;
protected override object? TryGetRawCore(DataFormat format)
{
throw new System.NotImplementedException();
}
}

39
src/Avalonia.X11/Selections/Clipboard/ClipboardDataReader.cs

@ -0,0 +1,39 @@
using System;
using System.Threading.Tasks;
using Avalonia.Input;
using static Avalonia.X11.XLib;
namespace Avalonia.X11.Selections.Clipboard;
/// <summary>
/// An object used to read values, converted to the correct format, from the X11 clipboard.
/// </summary>
internal sealed class ClipboardDataReader(
AvaloniaX11Platform platform,
IntPtr[] textFormatAtoms,
DataFormat[] dataFormats,
IntPtr owner)
: SelectionDataReader<IAsyncDataTransferItem>(platform.Info.Atoms, textFormatAtoms, dataFormats)
{
private IntPtr _owner = owner;
private bool IsOwnerStillValid()
=> _owner != IntPtr.Zero && XGetSelectionOwner(platform.Display, platform.Info.Atoms.CLIPBOARD) == _owner;
public override Task<object?> TryGetAsync(DataFormat format)
{
if (!IsOwnerStillValid())
return Task.FromResult<object?>(null);
return base.TryGetAsync(format);
}
protected override IAsyncDataTransferItem CreateSingleItem(DataFormat[] nonFileFormats)
=> new ClipboardDataTransferItem(this, nonFileFormats);
protected override SelectionReadSession CreateReadSession()
=> ClipboardReadSessionFactory.CreateSession(platform);
public override void Dispose()
=> _owner = IntPtr.Zero;
}

12
src/Avalonia.X11/Clipboard/ClipboardDataTransfer.cs → src/Avalonia.X11/Selections/Clipboard/ClipboardDataTransfer.cs

@ -1,7 +1,7 @@
using Avalonia.Input;
using Avalonia.Input.Platform;
namespace Avalonia.X11.Clipboard;
namespace Avalonia.X11.Selections.Clipboard;
/// <summary>
/// Implementation of <see cref="IAsyncDataTransfer"/> for the X11 clipboard.
@ -19,16 +19,12 @@ internal sealed class ClipboardDataTransfer(
IAsyncDataTransferItem[] items)
: PlatformAsyncDataTransfer
{
private readonly ClipboardDataReader _reader = reader;
private readonly DataFormat[] _formats = formats;
private readonly IAsyncDataTransferItem[] _items = items;
protected override DataFormat[] ProvideFormats()
=> _formats;
=> formats;
protected override IAsyncDataTransferItem[] ProvideItems()
=> _items;
=> items;
public override void Dispose()
=> _reader.Dispose();
=> reader.Dispose();
}

9
src/Avalonia.X11/Clipboard/ClipboardDataTransferItem.cs → src/Avalonia.X11/Selections/Clipboard/ClipboardDataTransferItem.cs

@ -2,7 +2,7 @@ using System.Threading.Tasks;
using Avalonia.Input;
using Avalonia.Input.Platform;
namespace Avalonia.X11.Clipboard;
namespace Avalonia.X11.Selections.Clipboard;
/// <summary>
/// Implementation of <see cref="IAsyncDataTransferItem"/> for the X11 clipboard.
@ -12,12 +12,9 @@ namespace Avalonia.X11.Clipboard;
internal sealed class ClipboardDataTransferItem(ClipboardDataReader reader, DataFormat[] formats)
: PlatformAsyncDataTransferItem
{
private readonly ClipboardDataReader _reader = reader;
private readonly DataFormat[] _formats = formats;
protected override DataFormat[] ProvideFormats()
=> _formats;
=> formats;
protected override Task<object?> TryGetRawCoreAsync(DataFormat format)
=> _reader.TryGetAsync(format);
=> reader.TryGetAsync(format);
}

2
src/Avalonia.X11/Clipboard/ClipboardReadSessionFactory.cs → src/Avalonia.X11/Selections/Clipboard/ClipboardReadSessionFactory.cs

@ -1,7 +1,7 @@
using System;
using static Avalonia.X11.XLib;
namespace Avalonia.X11.Clipboard;
namespace Avalonia.X11.Selections.Clipboard;
internal static class ClipboardReadSessionFactory
{

5
src/Avalonia.X11/Clipboard/EventStreamWindow.cs → src/Avalonia.X11/Selections/Clipboard/EventStreamWindow.cs

@ -4,11 +4,10 @@ using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Threading;
using Avalonia.X11.DragDrop;
namespace Avalonia.X11.Clipboard;
namespace Avalonia.X11.Selections.Clipboard;
internal sealed class EventStreamWindow : IEventWaiter
internal sealed class EventStreamWindow : IXEventWaiter
{
private readonly AvaloniaX11Platform _platform;
private IntPtr _handle;

50
src/Avalonia.X11/Clipboard/X11Clipboard.cs → src/Avalonia.X11/Selections/Clipboard/X11Clipboard.cs

@ -11,7 +11,7 @@ using Avalonia.Logging;
using Avalonia.Platform.Storage;
using static Avalonia.X11.XLib;
namespace Avalonia.X11.Clipboard
namespace Avalonia.X11.Selections.Clipboard
{
internal sealed class X11ClipboardImpl : IOwnedClipboardImpl
{
@ -106,7 +106,7 @@ namespace Avalonia.X11.Clipboard
{
return property;
}
else if (ClipboardDataFormatHelper.ToDataFormat(target, _x11.Atoms) is { } dataFormat)
else if (DataFormatHelper.ToDataFormat(target, _x11.Atoms) is { } dataFormat)
{
if (_storedDataTransfer is null)
return IntPtr.Zero;
@ -161,7 +161,7 @@ namespace Avalonia.X11.Clipboard
{
var text = dataTransfer.TryGetValueAsync(DataFormat.Text).GetAwaiter().GetResult();
return ClipboardDataFormatHelper.TryGetStringEncoding(targetFormatAtom, _x11.Atoms) is { } encoding ?
return DataFormatHelper.TryGetStringEncoding(targetFormatAtom, _x11.Atoms) is { } encoding ?
encoding.GetBytes(text ?? string.Empty) :
null;
}
@ -182,7 +182,7 @@ namespace Avalonia.X11.Clipboard
if (dataTransfer.TryGetValuesAsync(DataFormat.File).GetAwaiter().GetResult() is not { } files)
return null;
return ClipboardUriListHelper.FileUriListToUtf8Bytes(files);
return UriListHelper.FileUriListToUtf8Bytes(files);
}
if (format is DataFormat<string> stringFormat)
@ -253,7 +253,7 @@ namespace Avalonia.X11.Clipboard
{
foreach (var format in dataTransfer.Formats)
{
foreach (var atom in ClipboardDataFormatHelper.ToAtoms(format, _textAtoms, _x11.Atoms))
foreach (var atom in DataFormatHelper.ToAtoms(format, _textAtoms, _x11.Atoms))
atoms.Add(atom);
}
}
@ -317,8 +317,8 @@ namespace Avalonia.X11.Clipboard
return null;
// Get the items while we're in an async method. This does not get values, except for DataFormat.File.
var reader = new ClipboardDataReader(_x11, _platform, textFormatAtoms, owner, dataFormats);
var items = await CreateItemsAsync(reader, dataFormats);
var reader = new ClipboardDataReader(_platform, textFormatAtoms, dataFormats, owner);
var items = await reader.CreateItemsAsync();
return new ClipboardDataTransfer(reader, dataFormats, items);
}
@ -327,41 +327,7 @@ namespace Avalonia.X11.Clipboard
using var session = OpenReadSession();
var formatAtoms = await session.SendFormatRequest(0) ?? [];
return ClipboardDataFormatHelper.ToDataFormats(formatAtoms, _x11.Atoms);
}
private static async Task<IAsyncDataTransferItem[]> CreateItemsAsync(ClipboardDataReader reader, DataFormat[] formats)
{
List<DataFormat>? nonFileFormats = null;
var items = new List<IAsyncDataTransferItem>();
var hasFiles = false;
foreach (var format in formats)
{
if (DataFormat.File.Equals(format))
{
if (hasFiles)
continue;
// We're reading the filenames ahead of time to generate the appropriate items.
// This is async, so it should be fine.
if (await reader.TryGetAsync(format) is IEnumerable<IStorageItem> storageItems)
{
hasFiles = true;
foreach (var storageItem in storageItems)
items.Add(PlatformDataTransferItem.Create(DataFormat.File, storageItem));
}
}
else
(nonFileFormats ??= new()).Add(format);
}
// Single item containing all formats except for DataFormat.File.
if (nonFileFormats is not null)
items.Add(new ClipboardDataTransferItem(reader, formats));
return items.ToArray();
return DataFormatHelper.ToDataFormats(formatAtoms, _x11.Atoms);
}
public Task SetDataAsync(IAsyncDataTransfer dataTransfer)

4
src/Avalonia.X11/Clipboard/ClipboardDataFormatHelper.cs → src/Avalonia.X11/Selections/DataFormatHelper.cs

@ -3,9 +3,9 @@ using System.Collections.Generic;
using System.Text;
using Avalonia.Input;
namespace Avalonia.X11.Clipboard;
namespace Avalonia.X11.Selections;
internal static class ClipboardDataFormatHelper
internal static class DataFormatHelper
{
private const string MimeTypeTextUriList = "text/uri-list";
private const string AppPrefix = "application/avn-fmt.";

49
src/Avalonia.X11/Selections/DragDrop/DragDropDataReader.cs

@ -0,0 +1,49 @@
using System;
using System.Diagnostics;
using Avalonia.Input;
using Avalonia.Input.Platform;
namespace Avalonia.X11.Selections.DragDrop;
/// <summary>
/// An object used to read values, converted to the correct format, from a Xdnd selection.
/// </summary>
internal sealed class DragDropDataReader(
X11Atoms atoms,
IntPtr[] textFormatAtoms,
DataFormat[] dataFormats,
IntPtr display,
IntPtr targetWindow)
: SelectionDataReader<PlatformDataTransferItem>(atoms, textFormatAtoms, dataFormats)
{
protected override SelectionReadSession CreateReadSession()
{
var eventWaiter = new SynchronousXEventWaiter(display);
return new SelectionReadSession(display, targetWindow, Atoms.XdndSelection, eventWaiter, Atoms);
}
protected override PlatformDataTransferItem CreateSingleItem(DataFormat[] nonFileFormats)
=> new DragDropDataTransferItem(this, nonFileFormats);
public PlatformDataTransferItem[] CreateItems()
{
// Note: this doesn't cause any deadlock, CreateItemsAsync() will always complete synchronously
// thanks to the SynchronousXEventWaiter used in the SelectionReadSession.
var task = CreateItemsAsync();
Debug.Assert(task.IsCompleted);
return task.GetAwaiter().GetResult();
}
public object? TryGet(DataFormat format)
{
// Note: this doesn't cause any deadlock, TryGetAsync() will always complete synchronously
// thanks to the SynchronousXEventWaiter used in the SelectionReadSession.
var task = TryGetAsync(format);
Debug.Assert(task.IsCompleted);
return task.GetAwaiter().GetResult();
}
public override void Dispose()
{
}
}

22
src/Avalonia.X11/DragDrop/DragDropDataTransfer.cs → src/Avalonia.X11/Selections/DragDrop/DragDropDataTransfer.cs

@ -1,21 +1,21 @@
using System;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.X11.Clipboard;
using static Avalonia.X11.XLib;
namespace Avalonia.X11.DragDrop;
namespace Avalonia.X11.Selections.DragDrop;
/// <summary>
/// Implementation of <see cref="IDataTransfer"/> for data being dragged into an Avalonia window via Xdnd.
/// </summary>
internal sealed class DragDropDataTransfer(
DragDropDataReader reader,
X11Atoms atoms,
DataFormat[] dataFormats,
IntPtr display,
IntPtr sourceWindow,
IntPtr targetWindow,
IInputRoot inputRoot,
DataFormat[] dataFormats,
X11Atoms atoms)
IInputRoot inputRoot)
: PlatformDataTransfer
{
public IntPtr SourceWindow { get; } = sourceWindow;
@ -30,18 +30,12 @@ internal sealed class DragDropDataTransfer(
=> dataFormats;
protected override PlatformDataTransferItem[] ProvideItems()
{
return [];
}
private SelectionReadSession CreateSession()
{
var eventWaiter = new SynchronousEventWaiter(display);
return new SelectionReadSession(display, targetWindow, atoms.XdndSelection, eventWaiter, atoms);
}
=> reader.CreateItems();
public override void Dispose()
{
reader.Dispose();
var evt = new XEvent
{
ClientMessageEvent = new XClientMessageEvent

31
src/Avalonia.X11/Selections/DragDrop/DragDropDataTransferItem.cs

@ -0,0 +1,31 @@
using System.Collections.Generic;
using Avalonia.Input;
using Avalonia.Input.Platform;
namespace Avalonia.X11.Selections.DragDrop;
/// <summary>
/// Implementation of <see cref="IAsyncDataTransferItem"/> for Xdnd.
/// </summary>
/// <param name="reader">The object used to read values.</param>
/// <param name="formats">The formats.</param>
internal sealed class DragDropDataTransferItem(DragDropDataReader reader, DataFormat[] formats)
: PlatformDataTransferItem
{
private Dictionary<DataFormat, object?>? _cachedValues;
protected override DataFormat[] ProvideFormats()
=> formats;
protected override object? TryGetRawCore(DataFormat format)
{
if (_cachedValues is null || !_cachedValues.TryGetValue(format, out var value))
{
value = reader.TryGet(format);
_cachedValues ??= [];
_cachedValues[format] = value;
}
return value;
}
}

2
src/Avalonia.X11/DragDrop/IXdndWindow.cs → src/Avalonia.X11/Selections/DragDrop/IXdndWindow.cs

@ -1,7 +1,7 @@
using System;
using Avalonia.Input;
namespace Avalonia.X11.DragDrop;
namespace Avalonia.X11.Selections.DragDrop;
internal interface IXdndWindow
{

10
src/Avalonia.X11/DragDrop/SynchronousEventWaiter.cs → src/Avalonia.X11/Selections/DragDrop/SynchronousXEventWaiter.cs

@ -5,9 +5,13 @@ using System.Runtime.InteropServices;
using System.Threading.Tasks;
using static Avalonia.X11.XLib;
namespace Avalonia.X11.DragDrop;
namespace Avalonia.X11.Selections.DragDrop;
internal sealed partial class SynchronousEventWaiter(IntPtr display) : IEventWaiter
/// <summary>
/// An implementation of <see cref="IXEventWaiter"/> that waits for an event synchronously by using its own event loop.
/// Unprocessed events are put back onto the main queue after the wait.
/// </summary>
internal sealed partial class SynchronousXEventWaiter(IntPtr display) : IXEventWaiter
{
private const short POLLIN = 0x0001;
@ -100,7 +104,7 @@ internal sealed partial class SynchronousEventWaiter(IntPtr display) : IEventWai
}
}
Task<XEvent?> IEventWaiter.WaitForEventAsync(Func<XEvent, bool> predicate, TimeSpan timeout)
Task<XEvent?> IXEventWaiter.WaitForEventAsync(Func<XEvent, bool> predicate, TimeSpan timeout)
=> Task.FromResult(WaitForEvent(predicate, timeout));
void IDisposable.Dispose()

32
src/Avalonia.X11/DragDrop/XdndHandler.cs → src/Avalonia.X11/Selections/DragDrop/XdndHandler.cs

@ -3,10 +3,9 @@ using System.Collections.Generic;
using System.Linq;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.X11.Clipboard;
using static Avalonia.X11.XLib;
namespace Avalonia.X11.DragDrop;
namespace Avalonia.X11.Selections.DragDrop;
/// <summary>
/// Manages XDND (drag and drop) for a given X11 window.
@ -53,16 +52,13 @@ internal sealed class XdndHandler
var formats = new HashSet<IntPtr>();
if (hasExtraFormats)
if (hasExtraFormats &&
XGetWindowPropertyAsIntPtrArray(_display, sourceWindow, _atoms.XdndTypeList, _atoms.ATOM) is { } formatList)
{
if (XGetWindowPropertyAsIntPtrArray(_display, sourceWindow, _atoms.XdndTypeList, _atoms.ATOM)
is { } formatList)
foreach (var format in formatList)
{
foreach (var format in formatList)
{
if (format != 0)
formats.Add(format);
}
if (format != 0)
formats.Add(format);
}
}
else
@ -75,10 +71,18 @@ internal sealed class XdndHandler
formats.Add(message.ptr5);
}
var (dataFormats, _) = ClipboardDataFormatHelper.ToDataFormats(formats.ToArray(), _atoms);
var drag = new DragDropDataTransfer(_display, sourceWindow, _window.Handle, inputRoot, dataFormats, _atoms);
var (dataFormats, textFormats) = DataFormatHelper.ToDataFormats(formats.ToArray(), _atoms);
_currentDrag = drag;
var reader = new DragDropDataReader(_atoms, textFormats, dataFormats, _display, _window.Handle);
_currentDrag = new DragDropDataTransfer(
reader,
_atoms,
dataFormats,
_display,
sourceWindow,
_window.Handle,
inputRoot);
}
public void HandleXdndPosition(in XClientMessageEvent message)
@ -131,8 +135,6 @@ internal sealed class XdndHandler
XSendEvent(_display, sourceWindow, false, (IntPtr)EventMask.NoEventMask, ref evt);
XFlush(_display);
_currentDrag.ReadText();
}
public void HandleXdndLeave(in XClientMessageEvent message)

4
src/Avalonia.X11/DragDrop/IEventWaiter.cs → src/Avalonia.X11/Selections/IXEventWaiter.cs

@ -1,9 +1,9 @@
using System;
using System.Threading.Tasks;
namespace Avalonia.X11.DragDrop;
namespace Avalonia.X11.Selections;
internal interface IEventWaiter : IDisposable
internal interface IXEventWaiter : IDisposable
{
Task<XEvent?> WaitForEventAsync(Func<XEvent, bool> predicate, TimeSpan timeout);
}

108
src/Avalonia.X11/Selections/SelectionDataReader.cs

@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Media.Imaging;
using Avalonia.Platform.Storage;
namespace Avalonia.X11.Selections;
/// <summary>
/// An object used to read values, converted to the correct format, from an X11 selection (clipboard/drag-and-drop).
/// </summary>
internal abstract class SelectionDataReader<TItem>(
X11Atoms atoms,
IntPtr[] textFormatAtoms,
DataFormat[] dataFormats)
: IDisposable
where TItem : class
{
protected X11Atoms Atoms { get; } = atoms;
public async Task<TItem[]> CreateItemsAsync()
{
List<DataFormat>? nonFileFormats = null;
var items = new List<TItem>();
var hasFiles = false;
foreach (var format in dataFormats)
{
if (DataFormat.File.Equals(format))
{
if (hasFiles)
continue;
if (await TryGetAsync(format) is IEnumerable<IStorageItem> storageItems)
{
hasFiles = true;
foreach (var storageItem in storageItems)
items.Add((TItem)(object)PlatformDataTransferItem.Create(DataFormat.File, storageItem));
}
}
else
(nonFileFormats ??= []).Add(format);
}
// Single item containing all formats except for DataFormat.File.
if (nonFileFormats is not null)
items.Add(CreateSingleItem(nonFileFormats.ToArray()));
return items.ToArray();
}
public virtual async Task<object?> TryGetAsync(DataFormat format)
{
var formatAtom = DataFormatHelper.ToAtom(format, textFormatAtoms, Atoms, dataFormats);
if (formatAtom == IntPtr.Zero)
return null;
using var session = CreateReadSession();
var result = await session.SendDataRequest(formatAtom, 0).ConfigureAwait(false);
return ConvertDataResult(result, format, formatAtom);
}
protected abstract TItem CreateSingleItem(DataFormat[] nonFileFormats);
protected abstract SelectionReadSession CreateReadSession();
private object? ConvertDataResult(SelectionReadSession.GetDataResult? result, DataFormat format, IntPtr formatAtom)
{
if (result is null)
return null;
if (DataFormat.Text.Equals(format))
{
return DataFormatHelper.TryGetStringEncoding(result.TypeAtom, Atoms) is { } textEncoding ?
textEncoding.GetString(result.AsBytes()) :
null;
}
if (DataFormat.Bitmap.Equals(format))
{
using var data = result.AsStream();
return new Bitmap(data);
}
if (DataFormat.File.Equals(format))
{
// text/uri-list might not be supported
return formatAtom != IntPtr.Zero && result.TypeAtom == formatAtom ?
UriListHelper.Utf8BytesToFileUriList(result.AsBytes()) :
null;
}
if (format is DataFormat<string>)
return Encoding.UTF8.GetString(result.AsBytes());
if (format is DataFormat<byte[]>)
return result.AsBytes();
return null;
}
public abstract void Dispose();
}

10
src/Avalonia.X11/Clipboard/SelectionReadSession.cs → src/Avalonia.X11/Selections/SelectionReadSession.cs

@ -3,16 +3,18 @@ using System.Buffers;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Avalonia.X11.DragDrop;
using static Avalonia.X11.XLib;
namespace Avalonia.X11.Clipboard;
namespace Avalonia.X11.Selections;
/// <summary>
/// A session used to read a X11 selection (Clipboard/Drag-and-Drop) from a given window.
/// </summary>
internal sealed class SelectionReadSession(
IntPtr display,
IntPtr window,
IntPtr selection,
IEventWaiter eventWaiter,
IXEventWaiter eventWaiter,
X11Atoms atoms)
: IDisposable
{
@ -52,7 +54,7 @@ internal sealed class SelectionReadSession(
{
XGetWindowProperty(display, window, property, IntPtr.Zero, new IntPtr (0x7fffffff), true,
(IntPtr)Atom.AnyPropertyType,
out var actualTypeAtom, out var actualFormat, out var nitems, out var bytes_after, out var prop);
out var actualTypeAtom, out var actualFormat, out var nitems, out _, out var prop);
return new (prop, actualTypeAtom, actualFormat, nitems);
}

4
src/Avalonia.X11/Clipboard/ClipboardUriListHelper.cs → src/Avalonia.X11/Selections/UriListHelper.cs

@ -5,9 +5,9 @@ using System.Text;
using Avalonia.Platform.Storage;
using Avalonia.Platform.Storage.FileIO;
namespace Avalonia.X11.Clipboard;
namespace Avalonia.X11.Selections;
internal static class ClipboardUriListHelper
internal static class UriListHelper
{
private static readonly Encoding s_utf8NoBomEncoding = new UTF8Encoding(false);

4
src/Avalonia.X11/X11Platform.cs

@ -17,11 +17,11 @@ using Avalonia.Rendering.Composition;
using Avalonia.Threading;
using Avalonia.Vulkan;
using Avalonia.X11;
using Avalonia.X11.Clipboard;
using Avalonia.X11.Dispatching;
using Avalonia.X11.Glx;
using Avalonia.X11.Vulkan;
using Avalonia.X11.Screens;
using Avalonia.X11.Selections.Clipboard;
using Avalonia.X11.Vulkan;
using static Avalonia.X11.XLib;
namespace Avalonia.X11

16
src/Avalonia.X11/X11Window.cs

@ -2,32 +2,30 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Avalonia.Reactive;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Dialogs;
using Avalonia.FreeDesktop;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Avalonia.OpenGL;
using Avalonia.OpenGL.Egl;
using Avalonia.Platform;
using Avalonia.Platform.Surfaces;
using Avalonia.Platform.Storage;
using Avalonia.Platform.Storage.FileIO;
using Avalonia.Platform.Surfaces;
using Avalonia.Rendering.Composition;
using Avalonia.Threading;
using Avalonia.X11.Glx;
using Avalonia.X11.NativeDialogs;
using Avalonia.X11.Selections.DragDrop;
using static Avalonia.X11.XLib;
using Avalonia.Input.Platform;
using System.Runtime.InteropServices;
using Avalonia.Dialogs;
using Avalonia.Platform.Storage.FileIO;
using Avalonia.X11.DragDrop;
// ReSharper disable IdentifierTypo
// ReSharper disable StringLiteralTypo

Loading…
Cancel
Save