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
export PATH=$DOTNET_DIRECTORY:$PATH
echo "Microsoft (R) .NET Core SDK version $("$DOTNET_EXE" --version)"
"$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 ObtainFormats(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;
};

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

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

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

@ -29,6 +29,13 @@ public:
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()
{
free((void*)_cstring);
@ -114,3 +121,8 @@ IAvnStringArray* CreateAvnStringArray(NSString* 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;
}
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
{

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

@ -43,5 +43,11 @@ namespace Avalonia.Android.Platform
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 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

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

@ -9,5 +9,11 @@ namespace Avalonia.Input.Platform
Task SetTextAsync(string text);
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
{
private string _managed;
private byte[] _bytes;
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;
}

32
src/Avalonia.Native/ClipboardImpl.cs

@ -82,6 +82,38 @@ namespace Avalonia.Native
using (var strings = _native.GetStrings(NSFilenamesPboardType))
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

35
src/Avalonia.X11/X11Atoms.cs

@ -22,6 +22,7 @@
//
using System;
using System.Collections.Generic;
using System.Linq;
using static Avalonia.X11.XLib;
// ReSharper disable FieldCanBeMadeReadOnly.Global
@ -40,8 +41,9 @@ namespace Avalonia.X11
internal class X11Atoms
{
private readonly IntPtr _display;
// Our atoms
// Our atoms
public readonly IntPtr AnyPropertyType = (IntPtr)0;
public readonly IntPtr XA_PRIMARY = (IntPtr)1;
public readonly IntPtr XA_SECONDARY = (IntPtr)2;
@ -187,10 +189,13 @@ namespace Avalonia.X11
public readonly IntPtr ATOM_PAIR;
public readonly IntPtr MANAGER;
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)
{
_display = display;
// 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);
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]);
}
}
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.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Input;
using Avalonia.Input.Platform;
using static Avalonia.X11.XLib;
namespace Avalonia.X11
@ -10,12 +12,14 @@ namespace Avalonia.X11
class X11Clipboard : IClipboard
{
private readonly X11Info _x11;
private string _storedString;
private IDataObject _storedDataObject;
private IntPtr _handle;
private TaskCompletionSource<IntPtr[]> _requestedFormatsTcs;
private TaskCompletionSource<string> _requestedTextTcs;
private TaskCompletionSource<object> _requestedDataTcs;
private readonly IntPtr[] _textAtoms;
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)
{
@ -31,6 +35,11 @@ namespace Avalonia.X11
}.Where(a => a != IntPtr.Zero).ToArray();
}
bool IsStringAtom(IntPtr atom)
{
return _textAtoms.Contains(atom);
}
Encoding GetStringEncoding(IntPtr atom)
{
return (atom == _x11.Atoms.XA_STRING
@ -75,21 +84,31 @@ namespace Avalonia.X11
Encoding textEnc;
if (target == _x11.Atoms.TARGETS)
{
var atoms = _textAtoms;
atoms = atoms.Concat(new[] {_x11.Atoms.TARGETS, _x11.Atoms.MULTIPLE})
.ToArray();
var atoms = new HashSet<IntPtr> { _x11.Atoms.TARGETS, _x11.Atoms.MULTIPLE };
foreach (var fmt in _storedDataObject.GetDataFormats())
{
if (fmt == DataFormats.Text)
foreach (var ta in _textAtoms)
atoms.Add(ta);
else
atoms.Add(_x11.Atoms.GetAtom(fmt));
}
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;
}
else if(target == _x11.Atoms.SAVE_TARGETS && _x11.Atoms.SAVE_TARGETS != IntPtr.Zero)
{
return property;
}
else if ((textEnc = GetStringEncoding(target)) != null)
else if ((textEnc = GetStringEncoding(target)) != null
&& _storedDataObject?.Contains(DataFormats.Text) == true)
{
var data = textEnc.GetBytes(_storedString ?? "");
var text = _storedDataObject.GetText();
if(text == null)
return IntPtr.Zero;
var data = textEnc.GetBytes(text);
fixed (void* pdata = data)
XChangeProperty(_x11.Display, window, property, target, 8,
PropertyMode.Replace,
@ -121,6 +140,23 @@ namespace Avalonia.X11
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
return IntPtr.Zero;
}
@ -131,15 +167,15 @@ namespace Avalonia.X11
if (sel.property == IntPtr.Zero)
{
_requestedFormatsTcs?.TrySetResult(null);
_requestedTextTcs?.TrySetResult(null);
_requestedDataTcs?.TrySetResult(null);
}
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;
if (nitems == IntPtr.Zero)
{
_requestedFormatsTcs?.TrySetResult(null);
_requestedTextTcs?.TrySetResult(null);
_requestedDataTcs?.TrySetResult(null);
}
else
{
@ -154,10 +190,24 @@ namespace Avalonia.X11
_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());
_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;
}
Task<string> SendTextRequest(IntPtr format)
Task<object> SendDataRequest(IntPtr format)
{
if (_requestedTextTcs == null || _requestedFormatsTcs.Task.IsCompleted)
_requestedTextTcs = new TaskCompletionSource<string>();
if (_requestedDataTcs == null || _requestedFormatsTcs.Task.IsCompleted)
_requestedDataTcs = new TaskCompletionSource<object>();
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()
{
if (XGetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD) == IntPtr.Zero)
if (!HasOwner)
return null;
var res = await SendFormatRequest();
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)
@ -220,15 +272,52 @@ namespace Avalonia.X11
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);
StoreAtomsInClipboardManager(_textAtoms);
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,
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)]
public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type,
int format, PropertyMode mode, uint[] data, int nelements);

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

@ -1,25 +1,30 @@
using System;
using System.Linq;
using System.Reactive.Disposables;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Threading;
using Avalonia.Win32.Interop;
namespace Avalonia.Win32
{
internal class ClipboardImpl : IClipboard
{
private async Task OpenClipboard()
private async Task<IDisposable> OpenClipboard()
{
while (!UnmanagedMethods.OpenClipboard(IntPtr.Zero))
{
await Task.Delay(100);
}
return Disposable.Create(() => UnmanagedMethods.CloseClipboard());
}
public async Task<string> GetTextAsync()
{
await OpenClipboard();
try
using(await OpenClipboard())
{
IntPtr hText = UnmanagedMethods.GetClipboardData(UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT);
if (hText == IntPtr.Zero)
@ -37,10 +42,6 @@ namespace Avalonia.Win32
UnmanagedMethods.GlobalUnlock(hText);
return rv;
}
finally
{
UnmanagedMethods.CloseClipboard();
}
}
public async Task SetTextAsync(string text)
@ -50,31 +51,66 @@ namespace Avalonia.Win32
throw new ArgumentNullException(nameof(text));
}
await OpenClipboard();
UnmanagedMethods.EmptyClipboard();
try
using(await OpenClipboard())
{
UnmanagedMethods.EmptyClipboard();
var hGlobal = Marshal.StringToHGlobalUni(text);
UnmanagedMethods.SetClipboardData(UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT, hGlobal);
}
finally
{
UnmanagedMethods.CloseClipboard();
}
}
public async Task ClearAsync()
{
await OpenClipboard();
try
using(await OpenClipboard())
{
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")]
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)]
public static extern IntPtr GlobalLock(IntPtr handle);

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

@ -22,5 +22,11 @@ namespace Avalonia.iOS
UIPasteboard.General.String = "";
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 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