|
|
|
@ -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); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|