Browse Source

[X11] Implemented clipboard support

pull/2011/head
Nikita Tsukanov 8 years ago
parent
commit
a29d74fb98
  1. 21
      src/Avalonia.X11/Stubs.cs
  2. 5
      src/Avalonia.X11/X11Atoms.cs
  3. 235
      src/Avalonia.X11/X11Clipboard.cs
  4. 2
      src/Avalonia.X11/X11Platform.cs
  5. 13
      src/Avalonia.X11/XLib.cs

21
src/Avalonia.X11/Stubs.cs

@ -9,27 +9,6 @@ using Avalonia.Platform;
namespace Avalonia.X11
{
class ClipboardStub : IClipboard
{
private string _text;
public Task<string> GetTextAsync()
{
return Task.FromResult(_text);
}
public Task SetTextAsync(string text)
{
_text = text;
return Task.CompletedTask;
}
public Task ClearAsync()
{
_text = null;
return Task.CompletedTask;
}
}
class PlatformSettingsStub : IPlatformSettings
{
public Size DoubleClickSize { get; } = new Size(2, 2);

5
src/Avalonia.X11/X11Atoms.cs

@ -172,11 +172,16 @@ namespace Avalonia.X11 {
public readonly IntPtr _NET_WM_WINDOW_TYPE_DIALOG;
public readonly IntPtr _NET_WM_WINDOW_TYPE_NORMAL;
public readonly IntPtr CLIPBOARD;
public readonly IntPtr CLIPBOARD_MANAGER;
public readonly IntPtr SAVE_TARGETS;
public readonly IntPtr MULTIPLE;
public readonly IntPtr PRIMARY;
public readonly IntPtr OEMTEXT;
public readonly IntPtr UNICODETEXT;
public readonly IntPtr TARGETS;
public readonly IntPtr UTF8_STRING;
public readonly IntPtr UTF16_STRING;
public readonly IntPtr ATOM_PAIR;
public X11Atoms (IntPtr display) {

235
src/Avalonia.X11/X11Clipboard.cs

@ -0,0 +1,235 @@
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Input.Platform;
using static Avalonia.X11.XLib;
namespace Avalonia.X11
{
class X11Clipboard : IClipboard
{
private readonly X11Info _x11;
private string _storedString;
private IntPtr _handle;
private TaskCompletionSource<IntPtr[]> _requestedFormatsTcs;
private TaskCompletionSource<string> _requestedTextTcs;
private readonly IntPtr[] _textAtoms;
private readonly IntPtr _avaloniaSaveTargetsAtom;
public X11Clipboard(AvaloniaX11Platform platform)
{
_x11 = platform.Info;
_handle = XCreateSimpleWindow(_x11.Display, _x11.DefaultRootWindow, 0, 0, 1, 1, 0, IntPtr.Zero, IntPtr.Zero);
_avaloniaSaveTargetsAtom = XInternAtom(_x11.Display, "AVALONIA_SAVE_TARGETS_PROPERTY_ATOM", false);
platform.Windows[_handle] = OnEvent;
_textAtoms = new[]
{
_x11.Atoms.XA_STRING,
_x11.Atoms.OEMTEXT,
_x11.Atoms.UTF8_STRING,
_x11.Atoms.UTF16_STRING
}.Where(a => a != IntPtr.Zero).ToArray();
}
Encoding GetStringEncoding(IntPtr atom)
{
return (atom == _x11.Atoms.XA_STRING
|| atom == _x11.Atoms.OEMTEXT)
? Encoding.ASCII
: atom == _x11.Atoms.UTF8_STRING
? Encoding.UTF8
: atom == _x11.Atoms.UTF16_STRING
? Encoding.Unicode
: null;
}
private unsafe void OnEvent(XEvent ev)
{
if (ev.type == XEventName.SelectionRequest)
{
var sel = ev.SelectionRequestEvent;
var resp = new XEvent
{
SelectionEvent =
{
type = XEventName.SelectionNotify,
send_event = true,
display = _x11.Display,
selection = sel.selection,
target = sel.target,
requestor = sel.requestor,
time = sel.time,
property = IntPtr.Zero
}
};
if (sel.selection == _x11.Atoms.CLIPBOARD)
{
resp.SelectionEvent.property = WriteTargetToProperty(sel.target, sel.requestor, sel.property);
}
XSendEvent(_x11.Display, sel.requestor, false, new IntPtr((int)EventMask.NoEventMask), ref resp);
}
IntPtr WriteTargetToProperty(IntPtr target, IntPtr window, IntPtr property)
{
Encoding textEnc;
if (target == _x11.Atoms.TARGETS)
{
var atoms = _textAtoms;
atoms = atoms.Concat(new[] {_x11.Atoms.TARGETS, _x11.Atoms.MULTIPLE})
.ToArray();
XChangeProperty(_x11.Display, window, property,
target, 32, PropertyMode.Replace, atoms, atoms.Length);
return property;
}
else if(target == _x11.Atoms.SAVE_TARGETS && _x11.Atoms.SAVE_TARGETS != IntPtr.Zero)
{
return property;
}
else if ((textEnc = GetStringEncoding(target)) != null)
{
var data = textEnc.GetBytes(_storedString ?? "");
fixed (void* pdata = data)
XChangeProperty(_x11.Display, window, property, target, 8,
PropertyMode.Replace,
pdata, data.Length);
return property;
}
else if (target == _x11.Atoms.MULTIPLE && _x11.Atoms.MULTIPLE != IntPtr.Zero)
{
XGetWindowProperty(_x11.Display, window, property, IntPtr.Zero, new IntPtr(0x7fffffff), false,
_x11.Atoms.ATOM_PAIR, out _, out var actualFormat, out var nitems, out _, out var prop);
if (nitems == IntPtr.Zero)
return IntPtr.Zero;
if (actualFormat == 32)
{
var data = (IntPtr*)prop.ToPointer();
for (var c = 0; c < nitems.ToInt32(); c += 2)
{
var subTarget = data[c];
var subProp = data[c + 1];
var converted = WriteTargetToProperty(subTarget, window, subProp);
data[c + 1] = converted;
}
XChangeProperty(_x11.Display, window, property, _x11.Atoms.ATOM_PAIR, 32, PropertyMode.Replace,
prop.ToPointer(), nitems.ToInt32());
}
XFree(prop);
return property;
}
else
return IntPtr.Zero;
}
if (ev.type == XEventName.SelectionNotify && ev.SelectionEvent.selection == _x11.Atoms.CLIPBOARD)
{
var sel = ev.SelectionEvent;
if (sel.property == IntPtr.Zero)
{
_requestedFormatsTcs?.TrySetResult(null);
_requestedTextTcs?.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);
Encoding textEnc = null;
if (nitems == IntPtr.Zero)
{
_requestedFormatsTcs?.TrySetResult(null);
_requestedTextTcs?.TrySetResult(null);
}
else
{
if (sel.property == _x11.Atoms.TARGETS)
{
if (actualFormat != 32)
_requestedFormatsTcs?.TrySetResult(null);
else
{
var formats = new IntPtr[nitems.ToInt32()];
Marshal.Copy(prop, formats, 0, formats.Length);
_requestedFormatsTcs?.TrySetResult(formats);
}
}
else if ((textEnc = GetStringEncoding(sel.property)) != null)
{
var text = textEnc.GetString((byte*)prop.ToPointer(), nitems.ToInt32());
_requestedTextTcs?.TrySetResult(text);
}
}
XFree(prop);
}
}
Task<IntPtr[]> SendFormatRequest()
{
if (_requestedFormatsTcs == null || _requestedFormatsTcs.Task.IsCompleted)
_requestedFormatsTcs = new TaskCompletionSource<IntPtr[]>();
XConvertSelection(_x11.Display, _x11.Atoms.CLIPBOARD, _x11.Atoms.TARGETS, _x11.Atoms.TARGETS, _handle,
IntPtr.Zero);
return _requestedFormatsTcs.Task;
}
Task<string> SendTextRequest(IntPtr format)
{
if (_requestedTextTcs == null || _requestedFormatsTcs.Task.IsCompleted)
_requestedTextTcs = new TaskCompletionSource<string>();
XConvertSelection(_x11.Display, _x11.Atoms.CLIPBOARD, format, format, _handle, IntPtr.Zero);
return _requestedTextTcs.Task;
}
public async Task<string> GetTextAsync()
{
if (XGetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD) == IntPtr.Zero)
return null;
var res = await SendFormatRequest();
var target = _x11.Atoms.UTF8_STRING;
if (res != null)
{
var preferredFormats = new[] {_x11.Atoms.UTF16_STRING, _x11.Atoms.UTF8_STRING, _x11.Atoms.XA_STRING};
foreach (var pf in preferredFormats)
if (res.Contains(pf))
{
target = pf;
break;
}
}
return await SendTextRequest(target);
}
void StoreAtomsInClipboardManager(IntPtr[] atoms)
{
if (_x11.Atoms.CLIPBOARD_MANAGER != IntPtr.Zero && _x11.Atoms.SAVE_TARGETS != IntPtr.Zero)
{
var clipboardManager = XGetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD_MANAGER);
if (clipboardManager != IntPtr.Zero)
{
XChangeProperty(_x11.Display, _handle, _avaloniaSaveTargetsAtom, _x11.Atoms.XA_ATOM, 32,
PropertyMode.Replace,
atoms, atoms.Length);
XConvertSelection(_x11.Display, _x11.Atoms.CLIPBOARD_MANAGER, _x11.Atoms.SAVE_TARGETS,
_avaloniaSaveTargetsAtom, _handle, IntPtr.Zero);
}
}
}
public Task SetTextAsync(string text)
{
_storedString = text;
XSetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD, _handle, IntPtr.Zero);
StoreAtomsInClipboardManager(_textAtoms);
return Task.CompletedTask;
}
public Task ClearAsync()
{
return SetTextAsync(null);
}
}
}

2
src/Avalonia.X11/X11Platform.cs

@ -37,7 +37,7 @@ namespace Avalonia.X11
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(InputModifiers.Control))
.Bind<IKeyboardDevice>().ToFunc(() => KeyboardDevice)
.Bind<IStandardCursorFactory>().ToConstant(new X11CursorFactory(Display))
.Bind<IClipboard>().ToSingleton<ClipboardStub>()
.Bind<IClipboard>().ToConstant(new X11Clipboard(this))
.Bind<IPlatformSettings>().ToConstant(new PlatformSettingsStub())
.Bind<ISystemDialogImpl>().ToConstant(new SystemDialogsStub())
.Bind<IPlatformIconLoader>().ToConstant(new IconLoaderStub());

13
src/Avalonia.X11/XLib.cs

@ -117,6 +117,19 @@ namespace Avalonia.X11
[DllImport(libX11)]
public static extern int XInternAtoms(IntPtr display, string[] atom_names, int atom_count, bool only_if_exists,
IntPtr[] atoms);
[DllImport(libX11)]
public static extern IntPtr XGetAtomName(IntPtr display, IntPtr atom);
public static string GetAtomName(IntPtr display, IntPtr atom)
{
var ptr = XGetAtomName(display, atom);
if (ptr == IntPtr.Zero)
return null;
var s = Marshal.PtrToStringAnsi(ptr);
XFree(ptr);
return s;
}
[DllImport(libX11)]
public static extern int XSetWMProtocols(IntPtr display, IntPtr window, IntPtr[] protocols, int count);

Loading…
Cancel
Save