Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix macOS clipboard formats mapping #13197

Merged
merged 7 commits into from
Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions native/Avalonia.Native/src/OSX/clipboard.mm
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,22 @@ virtual HRESULT GetText (char* type, IAvnString**ppv) override
}
}

virtual HRESULT SetStrings(char* type, IAvnStringArray*ppv) override
{
START_COM_CALL;

@autoreleasepool
{
NSArray<NSString*>* data = GetNSArrayOfStringsAndRelease(ppv);
NSString* typeString = [NSString stringWithUTF8String:(const char*)type];
if(_item == nil)
[_pb setPropertyList: data forType: typeString];
else
[_item setPropertyList: data forType:typeString];
return S_OK;
}
}

virtual HRESULT GetStrings(char* type, IAvnStringArray**ppv) override
{
START_COM_CALL;
Expand Down
6 changes: 5 additions & 1 deletion src/Avalonia.Base/Input/DataFormats.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.ComponentModel;
using Avalonia.Metadata;

namespace Avalonia.Input
{
Expand All @@ -18,7 +19,10 @@ public static class DataFormats
/// <summary>
/// Dataformat for one or more filenames
/// </summary>
[Obsolete("Use DataFormats.Files, this format is supported only on desktop platforms."), EditorBrowsable(EditorBrowsableState.Never)]
/// <remarks>
/// This data format is supported only on desktop platforms.
/// </remarks>
[Unstable("Use DataFormats.Files, this format is supported only on desktop platforms. And it will be removed in 12.0"), EditorBrowsable(EditorBrowsableState.Never)]
jmacato marked this conversation as resolved.
Show resolved Hide resolved
public static readonly string FileNames = nameof(FileNames);
}
}
7 changes: 3 additions & 4 deletions src/Avalonia.Native/AvaloniaNativeDragSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,9 @@ public Task<DragDropEffects> DoDragDrop(PointerEventArgs triggerEvent, IDataObje
using (var clipboard = new ClipboardImpl(clipboardImpl))
using (var cb = new DndCallback(tcs))
{
if (data.Contains(DataFormats.Text))
// API is synchronous, so it's OK
clipboard.SetTextAsync(data.GetText()).Wait();

// Native API is synchronous, so it's OK. For now.
clipboard.SetDataObjectAsync(data).GetAwaiter().GetResult();

view.BeginDraggingSession((AvnDragDropEffects)allowedEffects,
triggerEvent.GetPosition(tl).ToAvnPoint(), clipboardImpl, cb,
GCHandle.ToIntPtr(GCHandle.Alloc(data)));
Expand Down
92 changes: 62 additions & 30 deletions src/Avalonia.Native/ClipboardImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Threading.Tasks;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Logging;
using Avalonia.Native.Interop;
using Avalonia.Platform.Storage;
using Avalonia.Platform.Storage.FileIO;
Expand All @@ -13,6 +14,7 @@ namespace Avalonia.Native
class ClipboardImpl : IClipboard, IDisposable
{
private IAvnClipboard _native;
// TODO hide native types behind IAvnClipboard abstraction, so managed side won't depend on macOS.
private const string NSPasteboardTypeString = "public.utf8-plain-text";
private const string NSFilenamesPboardType = "NSFilenamesPboardType";

Expand Down Expand Up @@ -46,7 +48,7 @@ public unsafe Task SetTextAsync(string text)

public IEnumerable<string> GetFormats()
{
var rv = new List<string>();
var rv = new HashSet<string>();
using (var formats = _native.ObtainFormats())
{
var cnt = formats.Count;
Expand All @@ -58,11 +60,11 @@ public IEnumerable<string> GetFormats()
rv.Add(DataFormats.Text);
if (fmt.String == NSFilenamesPboardType)
{
#pragma warning disable CS0618 // Type or member is obsolete
rv.Add(DataFormats.FileNames);
#pragma warning restore CS0618 // Type or member is obsolete
rv.Add(DataFormats.Files);
rv.Add(DataFormats.FileNames);
rv.Add(DataFormats.Files);
}
else
rv.Add(fmt.String);
}
}
}
Expand Down Expand Up @@ -91,32 +93,71 @@ public IEnumerable<IStorageItem> GetFiles()
public unsafe Task SetDataObjectAsync(IDataObject data)
{
_native.Clear();
foreach (var fmt in data.GetDataFormats())

// If there is multiple values with the same "to" format, prefer these that were not mapped.
var formats = data.GetDataFormats().Select(f =>
{
string from, to;
bool mapped;
if (f == DataFormats.Text)
(from, to, mapped) = (f, NSPasteboardTypeString, true);
else if (f == DataFormats.Files || f == DataFormats.FileNames)
(from, to, mapped) = (f, NSFilenamesPboardType, true);
else (from, to, mapped) = (f, f, false);
return (from, to, mapped);
})
.GroupBy(p => p.to)
.Select(g => g.OrderBy(f => f.mapped).First());


foreach (var (fromFormat, toFormat, _) in formats)
{
var o = data.Get(fmt);
if(o is string s)
_native.SetText(fmt, s);
else if(o is byte[] bytes)
fixed (byte* pbytes = bytes)
_native.SetBytes(fmt, pbytes, bytes.Length);
var o = data.Get(fromFormat);
switch (o)
{
case string s:
_native.SetText(toFormat, s);
break;
case IEnumerable<IStorageItem> storageItems:
using (var strings = new AvnStringArray(storageItems
.Select(s => s.TryGetLocalPath())
.Where(p => p is not null)))
{
_native.SetStrings(toFormat, strings);
}
break;
case IEnumerable<string> managedStrings:
using (var strings = new AvnStringArray(managedStrings))
{
_native.SetStrings(toFormat, strings);
}
break;
case byte[] bytes:
{
fixed (byte* pbytes = bytes)
_native.SetBytes(toFormat, pbytes, bytes.Length);
break;
}
default:
Logger.TryGet(LogEventLevel.Warning, LogArea.macOSPlatform)?.Log(this,
"Unsupported IDataObject value type: {0}", o?.GetType().FullName ?? "(null)");
break;
}
}
return Task.CompletedTask;
}

public Task<string[]> GetFormatsAsync()
{
using (var n = _native.ObtainFormats())
return Task.FromResult(n.ToStringArray());
return Task.FromResult(GetFormats().ToArray());
}

public async Task<object> GetDataAsync(string format)
{
if (format == DataFormats.Text)
if (format == DataFormats.Text || format == NSPasteboardTypeString)
return await GetTextAsync();
#pragma warning disable CS0618 // Type or member is obsolete
if (format == DataFormats.FileNames)
if (format == DataFormats.FileNames || format == NSFilenamesPboardType)
return GetFileNames();
#pragma warning restore CS0618 // Type or member is obsolete
if (format == DataFormats.Files)
return GetFiles();
using (var n = _native.GetBytes(format))
Expand Down Expand Up @@ -146,17 +187,8 @@ public void Dispose()

public bool Contains(string dataFormat) => Formats.Contains(dataFormat);

public object Get(string dataFormat)
{
if (dataFormat == DataFormats.Text)
return _clipboard.GetTextAsync().Result;
if (dataFormat == DataFormats.Files)
return _clipboard.GetFiles();
#pragma warning disable CS0618
if (dataFormat == DataFormats.FileNames)
#pragma warning restore CS0618
return _clipboard.GetFileNames();
return null;
}
public object Get(string dataFormat) => _clipboard.GetDataAsync(dataFormat).GetAwaiter().GetResult();

public Task SetFromDataObjectAsync(IDataObject dataObject) => _clipboard.SetDataObjectAsync(dataObject);
}
}
1 change: 1 addition & 0 deletions src/Avalonia.Native/avn.idl
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,7 @@ interface IAvnClipboard : IUnknown
HRESULT SetText(char* type, char* utf8Text);
HRESULT ObtainFormats(IAvnStringArray**ppv);
HRESULT GetStrings(char* type, IAvnStringArray**ppv);
HRESULT SetStrings(char* type, IAvnStringArray*ppv);
HRESULT SetBytes(char* type, void* utf8Text, int len);
HRESULT GetBytes(char* type, IAvnString**ppv);

Expand Down
Loading