Browse Source

Merge branch 'master' into perf-inpc-before-method

pull/4078/head
Dariusz Komosiński 6 years ago
committed by GitHub
parent
commit
4d2f4b0e26
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      build.sh
  2. 3
      native/Avalonia.Native/inc/avalonia-native.h
  3. 1
      native/Avalonia.Native/src/OSX/AvnString.h
  4. 12
      native/Avalonia.Native/src/OSX/AvnString.mm
  5. 34
      native/Avalonia.Native/src/OSX/clipboard.mm
  6. 6
      src/Android/Avalonia.Android/Platform/ClipboardImpl.cs
  7. 4
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  8. 6
      src/Avalonia.Input/Platform/IClipboard.cs
  9. 15
      src/Avalonia.Native/AvnString.cs
  10. 32
      src/Avalonia.Native/ClipboardImpl.cs
  11. 35
      src/Avalonia.X11/X11Atoms.cs
  12. 135
      src/Avalonia.X11/X11Clipboard.cs
  13. 4
      src/Avalonia.X11/XLib.cs
  14. 76
      src/Windows/Avalonia.Win32/ClipboardImpl.cs
  15. 6
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  16. 6
      src/iOS/Avalonia.iOS/Clipboard.cs
  17. 5
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

2
build.sh

@ -67,6 +67,8 @@ else
fi fi
fi fi
export PATH=$DOTNET_DIRECTORY:$PATH
echo "Microsoft (R) .NET Core SDK version $("$DOTNET_EXE" --version)" echo "Microsoft (R) .NET Core SDK version $("$DOTNET_EXE" --version)"
"$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" -- ${BUILD_ARGUMENTS[@]} "$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" -- ${BUILD_ARGUMENTS[@]}

3
native/Avalonia.Native/inc/avalonia-native.h

@ -387,6 +387,9 @@ AVNCOM(IAvnClipboard, 0f) : IUnknown
virtual HRESULT SetText (char* type, void* utf8Text) = 0; virtual HRESULT SetText (char* type, void* utf8Text) = 0;
virtual HRESULT ObtainFormats(IAvnStringArray**ppv) = 0; virtual HRESULT ObtainFormats(IAvnStringArray**ppv) = 0;
virtual HRESULT GetStrings(char* type, IAvnStringArray**ppv) = 0; virtual HRESULT GetStrings(char* type, IAvnStringArray**ppv) = 0;
virtual HRESULT SetBytes(char* type, void* utf8Text, int len) = 0;
virtual HRESULT GetBytes(char* type, IAvnString**ppv) = 0;
virtual HRESULT Clear() = 0; virtual HRESULT Clear() = 0;
}; };

1
native/Avalonia.Native/src/OSX/AvnString.h

@ -12,4 +12,5 @@
extern IAvnString* CreateAvnString(NSString* string); extern IAvnString* CreateAvnString(NSString* string);
extern IAvnStringArray* CreateAvnStringArray(NSArray<NSString*>* array); extern IAvnStringArray* CreateAvnStringArray(NSArray<NSString*>* array);
extern IAvnStringArray* CreateAvnStringArray(NSString* string); extern IAvnStringArray* CreateAvnStringArray(NSString* string);
extern IAvnString* CreateByteArray(void* data, int len);
#endif /* AvnString_h */ #endif /* AvnString_h */

12
native/Avalonia.Native/src/OSX/AvnString.mm

@ -29,6 +29,13 @@ public:
memcpy((void*)_cstring, (void*)cstring, _length); memcpy((void*)_cstring, (void*)cstring, _length);
} }
AvnStringImpl(void*ptr, int len)
{
_length = len;
_cstring = (const char*)malloc(_length);
memcpy((void*)_cstring, ptr, len);
}
virtual ~AvnStringImpl() virtual ~AvnStringImpl()
{ {
free((void*)_cstring); free((void*)_cstring);
@ -114,3 +121,8 @@ IAvnStringArray* CreateAvnStringArray(NSString* string)
{ {
return new AvnStringArrayImpl(string); return new AvnStringArrayImpl(string);
} }
IAvnString* CreateByteArray(void* data, int len)
{
return new AvnStringImpl(data, len);
}

34
native/Avalonia.Native/src/OSX/clipboard.mm

@ -82,6 +82,40 @@ public:
return S_OK; return S_OK;
} }
virtual HRESULT SetBytes(char* type, void* bytes, int len) override
{
auto typeString = [NSString stringWithUTF8String:(const char*)type];
auto data = [NSData dataWithBytes:bytes length:len];
if(_item == nil)
[_pb setData:data forType:typeString];
else
[_item setData:data forType:typeString];
return S_OK;
}
virtual HRESULT GetBytes(char* type, IAvnString**ppv) override
{
*ppv = nil;
auto typeString = [NSString stringWithUTF8String:(const char*)type];
NSData*data;
@try
{
if(_item)
data = [_item dataForType:typeString];
else
data = [_pb dataForType:typeString];
if(data == nil)
return E_FAIL;
}
@catch(NSException* e)
{
return E_FAIL;
}
*ppv = CreateByteArray((void*)data.bytes, (int)data.length);
return S_OK;
}
virtual HRESULT Clear() override virtual HRESULT Clear() override
{ {

6
src/Android/Avalonia.Android/Platform/ClipboardImpl.cs

@ -43,5 +43,11 @@ namespace Avalonia.Android.Platform
return Task.FromResult<object>(null); return Task.FromResult<object>(null);
} }
public Task SetDataObjectAsync(IDataObject data) => throw new PlatformNotSupportedException();
public Task<string[]> GetFormatsAsync() => throw new PlatformNotSupportedException();
public Task<object> GetDataAsync(string format) => throw new PlatformNotSupportedException();
} }
} }

4
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@ -160,6 +160,10 @@ namespace Avalonia.DesignerSupport.Remote
public Task SetTextAsync(string text) => Task.CompletedTask; public Task SetTextAsync(string text) => Task.CompletedTask;
public Task ClearAsync() => Task.CompletedTask; public Task ClearAsync() => Task.CompletedTask;
public Task SetDataObjectAsync(IDataObject data) => Task.CompletedTask;
public Task<string[]> GetFormatsAsync() => Task.FromResult(new string[0]);
public Task<object> GetDataAsync(string format) => Task.FromResult((object)null);
} }
class CursorFactoryStub : IStandardCursorFactory class CursorFactoryStub : IStandardCursorFactory

6
src/Avalonia.Input/Platform/IClipboard.cs

@ -9,5 +9,11 @@ namespace Avalonia.Input.Platform
Task SetTextAsync(string text); Task SetTextAsync(string text);
Task ClearAsync(); Task ClearAsync();
Task SetDataObjectAsync(IDataObject data);
Task<string[]> GetFormatsAsync();
Task<object> GetDataAsync(string format);
} }
} }

15
src/Avalonia.Native/AvnString.cs

@ -5,6 +5,7 @@ namespace Avalonia.Native.Interop
unsafe partial class IAvnString unsafe partial class IAvnString
{ {
private string _managed; private string _managed;
private byte[] _bytes;
public string String public string String
{ {
@ -22,6 +23,20 @@ namespace Avalonia.Native.Interop
} }
} }
public byte[] Bytes
{
get
{
if (_bytes == null)
{
_bytes = new byte[Length()];
Marshal.Copy(Pointer(), _bytes, 0, _bytes.Length);
}
return _bytes;
}
}
public override string ToString() => String; public override string ToString() => String;
} }

32
src/Avalonia.Native/ClipboardImpl.cs

@ -82,6 +82,38 @@ namespace Avalonia.Native
using (var strings = _native.GetStrings(NSFilenamesPboardType)) using (var strings = _native.GetStrings(NSFilenamesPboardType))
return strings.ToStringArray(); return strings.ToStringArray();
} }
public unsafe Task SetDataObjectAsync(IDataObject data)
{
_native.Clear();
foreach (var fmt in data.GetDataFormats())
{
var o = data.Get(fmt);
if(o is string s)
using (var b = new Utf8Buffer(s))
_native.SetText(fmt, b.DangerousGetHandle());
else if(o is byte[] bytes)
fixed (byte* pbytes = bytes)
_native.SetBytes(fmt, new IntPtr(pbytes), bytes.Length);
}
return Task.CompletedTask;
}
public Task<string[]> GetFormatsAsync()
{
using (var n = _native.ObtainFormats())
return Task.FromResult(n.ToStringArray());
}
public async Task<object> GetDataAsync(string format)
{
if (format == DataFormats.Text)
return await GetTextAsync();
if (format == DataFormats.FileNames)
return GetFileNames();
using (var n = _native.GetBytes(format))
return n.Bytes;
}
} }
class ClipboardDataObject : IDataObject, IDisposable class ClipboardDataObject : IDataObject, IDisposable

35
src/Avalonia.X11/X11Atoms.cs

@ -22,6 +22,7 @@
// //
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using static Avalonia.X11.XLib; using static Avalonia.X11.XLib;
// ReSharper disable FieldCanBeMadeReadOnly.Global // ReSharper disable FieldCanBeMadeReadOnly.Global
@ -40,8 +41,9 @@ namespace Avalonia.X11
internal class X11Atoms internal class X11Atoms
{ {
private readonly IntPtr _display;
// Our atoms // Our atoms
public readonly IntPtr AnyPropertyType = (IntPtr)0; public readonly IntPtr AnyPropertyType = (IntPtr)0;
public readonly IntPtr XA_PRIMARY = (IntPtr)1; public readonly IntPtr XA_PRIMARY = (IntPtr)1;
public readonly IntPtr XA_SECONDARY = (IntPtr)2; public readonly IntPtr XA_SECONDARY = (IntPtr)2;
@ -187,10 +189,13 @@ namespace Avalonia.X11
public readonly IntPtr ATOM_PAIR; public readonly IntPtr ATOM_PAIR;
public readonly IntPtr MANAGER; public readonly IntPtr MANAGER;
public readonly IntPtr _KDE_NET_WM_BLUR_BEHIND_REGION; public readonly IntPtr _KDE_NET_WM_BLUR_BEHIND_REGION;
public readonly IntPtr INCR;
private readonly Dictionary<string, IntPtr> _namesToAtoms = new Dictionary<string, IntPtr>();
private readonly Dictionary<IntPtr, string> _atomsToNames = new Dictionary<IntPtr, string>();
public X11Atoms(IntPtr display) public X11Atoms(IntPtr display)
{ {
_display = display;
// make sure this array stays in sync with the statements below // make sure this array stays in sync with the statements below
@ -204,7 +209,33 @@ namespace Avalonia.X11
XInternAtoms(display, atomNames, atomNames.Length, true, atoms); XInternAtoms(display, atomNames, atomNames.Length, true, atoms);
for (var c = 0; c < fields.Length; c++) for (var c = 0; c < fields.Length; c++)
{
_namesToAtoms[fields[c].Name] = atoms[c];
_atomsToNames[atoms[c]] = fields[c].Name;
fields[c].SetValue(this, atoms[c]); fields[c].SetValue(this, atoms[c]);
}
}
public IntPtr GetAtom(string name)
{
if (_namesToAtoms.TryGetValue(name, out var rv))
return rv;
var atom = XInternAtom(_display, name, false);
_namesToAtoms[name] = atom;
_atomsToNames[atom] = name;
return atom;
}
public string GetAtomName(IntPtr atom)
{
if (_atomsToNames.TryGetValue(atom, out var rv))
return rv;
var name = XLib.GetAtomName(_display, atom);
if (name == null)
return null;
_atomsToNames[atom] = name;
_namesToAtoms[name] = atom;
return name;
} }
} }
} }

135
src/Avalonia.X11/X11Clipboard.cs

@ -1,8 +1,10 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using static Avalonia.X11.XLib; using static Avalonia.X11.XLib;
namespace Avalonia.X11 namespace Avalonia.X11
@ -10,12 +12,14 @@ namespace Avalonia.X11
class X11Clipboard : IClipboard class X11Clipboard : IClipboard
{ {
private readonly X11Info _x11; private readonly X11Info _x11;
private string _storedString; private IDataObject _storedDataObject;
private IntPtr _handle; private IntPtr _handle;
private TaskCompletionSource<IntPtr[]> _requestedFormatsTcs; private TaskCompletionSource<IntPtr[]> _requestedFormatsTcs;
private TaskCompletionSource<string> _requestedTextTcs; private TaskCompletionSource<object> _requestedDataTcs;
private readonly IntPtr[] _textAtoms; private readonly IntPtr[] _textAtoms;
private readonly IntPtr _avaloniaSaveTargetsAtom; private readonly IntPtr _avaloniaSaveTargetsAtom;
private readonly Dictionary<string, IntPtr> _formatAtoms = new Dictionary<string, IntPtr>();
private readonly Dictionary<IntPtr, string> _atomFormats = new Dictionary<IntPtr, string>();
public X11Clipboard(AvaloniaX11Platform platform) public X11Clipboard(AvaloniaX11Platform platform)
{ {
@ -31,6 +35,11 @@ namespace Avalonia.X11
}.Where(a => a != IntPtr.Zero).ToArray(); }.Where(a => a != IntPtr.Zero).ToArray();
} }
bool IsStringAtom(IntPtr atom)
{
return _textAtoms.Contains(atom);
}
Encoding GetStringEncoding(IntPtr atom) Encoding GetStringEncoding(IntPtr atom)
{ {
return (atom == _x11.Atoms.XA_STRING return (atom == _x11.Atoms.XA_STRING
@ -75,21 +84,31 @@ namespace Avalonia.X11
Encoding textEnc; Encoding textEnc;
if (target == _x11.Atoms.TARGETS) if (target == _x11.Atoms.TARGETS)
{ {
var atoms = _textAtoms; var atoms = new HashSet<IntPtr> { _x11.Atoms.TARGETS, _x11.Atoms.MULTIPLE };
atoms = atoms.Concat(new[] {_x11.Atoms.TARGETS, _x11.Atoms.MULTIPLE}) foreach (var fmt in _storedDataObject.GetDataFormats())
.ToArray(); {
if (fmt == DataFormats.Text)
foreach (var ta in _textAtoms)
atoms.Add(ta);
else
atoms.Add(_x11.Atoms.GetAtom(fmt));
}
XChangeProperty(_x11.Display, window, property, XChangeProperty(_x11.Display, window, property,
_x11.Atoms.XA_ATOM, 32, PropertyMode.Replace, atoms, atoms.Length); _x11.Atoms.XA_ATOM, 32, PropertyMode.Replace, atoms.ToArray(), atoms.Count);
return property; return property;
} }
else if(target == _x11.Atoms.SAVE_TARGETS && _x11.Atoms.SAVE_TARGETS != IntPtr.Zero) else if(target == _x11.Atoms.SAVE_TARGETS && _x11.Atoms.SAVE_TARGETS != IntPtr.Zero)
{ {
return property; return property;
} }
else if ((textEnc = GetStringEncoding(target)) != null) else if ((textEnc = GetStringEncoding(target)) != null
&& _storedDataObject?.Contains(DataFormats.Text) == true)
{ {
var text = _storedDataObject.GetText();
var data = textEnc.GetBytes(_storedString ?? ""); if(text == null)
return IntPtr.Zero;
var data = textEnc.GetBytes(text);
fixed (void* pdata = data) fixed (void* pdata = data)
XChangeProperty(_x11.Display, window, property, target, 8, XChangeProperty(_x11.Display, window, property, target, 8,
PropertyMode.Replace, PropertyMode.Replace,
@ -121,6 +140,23 @@ namespace Avalonia.X11
return property; return property;
} }
else if(_storedDataObject?.Contains(_x11.Atoms.GetAtomName(target)) == true)
{
var objValue = _storedDataObject.Get(_x11.Atoms.GetAtomName(target));
if(!(objValue is byte[] bytes))
{
if (objValue is string s)
bytes = Encoding.UTF8.GetBytes(s);
else
return IntPtr.Zero;
}
XChangeProperty(_x11.Display, window, property, target, 8,
PropertyMode.Replace,
bytes, bytes.Length);
return property;
}
else else
return IntPtr.Zero; return IntPtr.Zero;
} }
@ -131,15 +167,15 @@ namespace Avalonia.X11
if (sel.property == IntPtr.Zero) if (sel.property == IntPtr.Zero)
{ {
_requestedFormatsTcs?.TrySetResult(null); _requestedFormatsTcs?.TrySetResult(null);
_requestedTextTcs?.TrySetResult(null); _requestedDataTcs?.TrySetResult(null);
} }
XGetWindowProperty(_x11.Display, _handle, sel.property, IntPtr.Zero, new IntPtr (0x7fffffff), true, (IntPtr)Atom.AnyPropertyType, XGetWindowProperty(_x11.Display, _handle, sel.property, IntPtr.Zero, new IntPtr (0x7fffffff), true, (IntPtr)Atom.AnyPropertyType,
out var actualAtom, out var actualFormat, out var nitems, out var bytes_after, out var prop); out var actualTypeAtom, out var actualFormat, out var nitems, out var bytes_after, out var prop);
Encoding textEnc = null; Encoding textEnc = null;
if (nitems == IntPtr.Zero) if (nitems == IntPtr.Zero)
{ {
_requestedFormatsTcs?.TrySetResult(null); _requestedFormatsTcs?.TrySetResult(null);
_requestedTextTcs?.TrySetResult(null); _requestedDataTcs?.TrySetResult(null);
} }
else else
{ {
@ -154,10 +190,24 @@ namespace Avalonia.X11
_requestedFormatsTcs?.TrySetResult(formats); _requestedFormatsTcs?.TrySetResult(formats);
} }
} }
else if ((textEnc = GetStringEncoding(sel.property)) != null) else if ((textEnc = GetStringEncoding(actualTypeAtom)) != null)
{ {
var text = textEnc.GetString((byte*)prop.ToPointer(), nitems.ToInt32()); var text = textEnc.GetString((byte*)prop.ToPointer(), nitems.ToInt32());
_requestedTextTcs?.TrySetResult(text); _requestedDataTcs?.TrySetResult(text);
}
else
{
if (actualTypeAtom == _x11.Atoms.INCR)
{
// TODO: Actually implement that monstrosity
_requestedDataTcs.TrySetResult(null);
}
else
{
var data = new byte[(int)nitems * (actualFormat / 8)];
Marshal.Copy(prop, data, 0, data.Length);
_requestedDataTcs?.TrySetResult(data);
}
} }
} }
@ -174,17 +224,19 @@ namespace Avalonia.X11
return _requestedFormatsTcs.Task; return _requestedFormatsTcs.Task;
} }
Task<string> SendTextRequest(IntPtr format) Task<object> SendDataRequest(IntPtr format)
{ {
if (_requestedTextTcs == null || _requestedFormatsTcs.Task.IsCompleted) if (_requestedDataTcs == null || _requestedFormatsTcs.Task.IsCompleted)
_requestedTextTcs = new TaskCompletionSource<string>(); _requestedDataTcs = new TaskCompletionSource<object>();
XConvertSelection(_x11.Display, _x11.Atoms.CLIPBOARD, format, format, _handle, IntPtr.Zero); XConvertSelection(_x11.Display, _x11.Atoms.CLIPBOARD, format, format, _handle, IntPtr.Zero);
return _requestedTextTcs.Task; return _requestedDataTcs.Task;
} }
bool HasOwner => XGetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD) != IntPtr.Zero;
public async Task<string> GetTextAsync() public async Task<string> GetTextAsync()
{ {
if (XGetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD) == IntPtr.Zero) if (!HasOwner)
return null; return null;
var res = await SendFormatRequest(); var res = await SendFormatRequest();
var target = _x11.Atoms.UTF8_STRING; var target = _x11.Atoms.UTF8_STRING;
@ -199,7 +251,7 @@ namespace Avalonia.X11
} }
} }
return await SendTextRequest(target); return (string)await SendDataRequest(target);
} }
void StoreAtomsInClipboardManager(IntPtr[] atoms) void StoreAtomsInClipboardManager(IntPtr[] atoms)
@ -220,15 +272,52 @@ namespace Avalonia.X11
public Task SetTextAsync(string text) public Task SetTextAsync(string text)
{ {
_storedString = text; var data = new DataObject();
data.Set(DataFormats.Text, text);
return SetDataObjectAsync(data);
}
public Task ClearAsync()
{
return SetTextAsync(null);
}
public Task SetDataObjectAsync(IDataObject data)
{
_storedDataObject = data;
XSetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD, _handle, IntPtr.Zero); XSetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD, _handle, IntPtr.Zero);
StoreAtomsInClipboardManager(_textAtoms); StoreAtomsInClipboardManager(_textAtoms);
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task ClearAsync() public async Task<string[]> GetFormatsAsync()
{ {
return SetTextAsync(null); if (!HasOwner)
return null;
var res = await SendFormatRequest();
if (res == null)
return null;
var rv = new List<string>();
if (_textAtoms.Any(res.Contains))
rv.Add(DataFormats.Text);
foreach (var t in res)
rv.Add(_x11.Atoms.GetAtomName(t));
return rv.ToArray();
}
public async Task<object> GetDataAsync(string format)
{
if (!HasOwner)
return null;
if (format == DataFormats.Text)
return await GetTextAsync();
var formatAtom = _x11.Atoms.GetAtom(format);
var res = await SendFormatRequest();
if (!res.Contains(formatAtom))
return null;
return await SendDataRequest(formatAtom);
} }
} }
} }

4
src/Avalonia.X11/XLib.cs

@ -236,6 +236,10 @@ namespace Avalonia.X11
public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type,
int format, PropertyMode mode, ref IntPtr value, int nelements); int format, PropertyMode mode, ref IntPtr value, int nelements);
[DllImport(libX11)]
public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type,
int format, PropertyMode mode, byte[] data, int nelements);
[DllImport(libX11)] [DllImport(libX11)]
public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type,
int format, PropertyMode mode, uint[] data, int nelements); int format, PropertyMode mode, uint[] data, int nelements);

76
src/Windows/Avalonia.Win32/ClipboardImpl.cs

@ -1,25 +1,30 @@
using System; using System;
using System.Linq;
using System.Reactive.Disposables;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.Threading;
using Avalonia.Win32.Interop; using Avalonia.Win32.Interop;
namespace Avalonia.Win32 namespace Avalonia.Win32
{ {
internal class ClipboardImpl : IClipboard internal class ClipboardImpl : IClipboard
{ {
private async Task OpenClipboard() private async Task<IDisposable> OpenClipboard()
{ {
while (!UnmanagedMethods.OpenClipboard(IntPtr.Zero)) while (!UnmanagedMethods.OpenClipboard(IntPtr.Zero))
{ {
await Task.Delay(100); await Task.Delay(100);
} }
return Disposable.Create(() => UnmanagedMethods.CloseClipboard());
} }
public async Task<string> GetTextAsync() public async Task<string> GetTextAsync()
{ {
await OpenClipboard(); using(await OpenClipboard())
try
{ {
IntPtr hText = UnmanagedMethods.GetClipboardData(UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT); IntPtr hText = UnmanagedMethods.GetClipboardData(UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT);
if (hText == IntPtr.Zero) if (hText == IntPtr.Zero)
@ -37,10 +42,6 @@ namespace Avalonia.Win32
UnmanagedMethods.GlobalUnlock(hText); UnmanagedMethods.GlobalUnlock(hText);
return rv; return rv;
} }
finally
{
UnmanagedMethods.CloseClipboard();
}
} }
public async Task SetTextAsync(string text) public async Task SetTextAsync(string text)
@ -50,31 +51,66 @@ namespace Avalonia.Win32
throw new ArgumentNullException(nameof(text)); throw new ArgumentNullException(nameof(text));
} }
await OpenClipboard(); using(await OpenClipboard())
UnmanagedMethods.EmptyClipboard();
try
{ {
UnmanagedMethods.EmptyClipboard();
var hGlobal = Marshal.StringToHGlobalUni(text); var hGlobal = Marshal.StringToHGlobalUni(text);
UnmanagedMethods.SetClipboardData(UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT, hGlobal); UnmanagedMethods.SetClipboardData(UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT, hGlobal);
} }
finally
{
UnmanagedMethods.CloseClipboard();
}
} }
public async Task ClearAsync() public async Task ClearAsync()
{ {
await OpenClipboard(); using(await OpenClipboard())
try
{ {
UnmanagedMethods.EmptyClipboard(); UnmanagedMethods.EmptyClipboard();
} }
finally }
public async Task SetDataObjectAsync(IDataObject data)
{
Dispatcher.UIThread.VerifyAccess();
var wrapper = new DataObject(data);
while (true)
{ {
UnmanagedMethods.CloseClipboard(); if (UnmanagedMethods.OleSetClipboard(wrapper) == 0)
break;
await Task.Delay(100);
}
}
public async Task<string[]> GetFormatsAsync()
{
Dispatcher.UIThread.VerifyAccess();
while (true)
{
if (UnmanagedMethods.OleGetClipboard(out var dataObject) == 0)
{
var wrapper = new OleDataObject(dataObject);
var formats = wrapper.GetDataFormats().ToArray();
Marshal.ReleaseComObject(dataObject);
return formats;
}
await Task.Delay(100);
}
}
public async Task<object> GetDataAsync(string format)
{
Dispatcher.UIThread.VerifyAccess();
while (true)
{
if (UnmanagedMethods.OleGetClipboard(out var dataObject) == 0)
{
var wrapper = new OleDataObject(dataObject);
var rv = wrapper.Get(format);
Marshal.ReleaseComObject(dataObject);
return rv;
}
await Task.Delay(100);
} }
} }
} }

6
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -1169,6 +1169,12 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")] [DllImport("user32.dll")]
public static extern IntPtr SetClipboardData(ClipboardFormat uFormat, IntPtr hMem); public static extern IntPtr SetClipboardData(ClipboardFormat uFormat, IntPtr hMem);
[DllImport("ole32.dll", PreserveSig = false)]
public static extern int OleGetClipboard(out IOleDataObject dataObject);
[DllImport("ole32.dll", PreserveSig = true)]
public static extern int OleSetClipboard(IOleDataObject dataObject);
[DllImport("kernel32.dll", ExactSpelling = true)] [DllImport("kernel32.dll", ExactSpelling = true)]
public static extern IntPtr GlobalLock(IntPtr handle); public static extern IntPtr GlobalLock(IntPtr handle);

6
src/iOS/Avalonia.iOS/Clipboard.cs

@ -22,5 +22,11 @@ namespace Avalonia.iOS
UIPasteboard.General.String = ""; UIPasteboard.General.String = "";
return Task.FromResult(0); return Task.FromResult(0);
} }
public Task SetDataObjectAsync(IDataObject data) => throw new PlatformNotSupportedException();
public Task<string[]> GetFormatsAsync() => throw new PlatformNotSupportedException();
public Task<object> GetDataAsync(string format) => throw new PlatformNotSupportedException();
} }
} }

5
tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

@ -654,6 +654,11 @@ namespace Avalonia.Controls.UnitTests
public Task SetTextAsync(string text) => Task.CompletedTask; public Task SetTextAsync(string text) => Task.CompletedTask;
public Task ClearAsync() => Task.CompletedTask; public Task ClearAsync() => Task.CompletedTask;
public Task SetDataObjectAsync(IDataObject data) => Task.CompletedTask;
public Task<string[]> GetFormatsAsync() => Task.FromResult(Array.Empty<string>());
public Task<object> GetDataAsync(string format) => Task.FromResult((object)null);
} }
} }
} }

Loading…
Cancel
Save