Browse Source

Added XI2 support for mouse events (smooth scrolling!)

pull/2011/head
Nikita Tsukanov 7 years ago
parent
commit
24a0592890
  1. 2
      src/Avalonia.X11/X11Clipboard.cs
  2. 26
      src/Avalonia.X11/X11Info.cs
  3. 9
      src/Avalonia.X11/X11Platform.cs
  4. 32
      src/Avalonia.X11/X11PlatformThreading.cs
  5. 3
      src/Avalonia.X11/X11Screens.cs
  6. 27
      src/Avalonia.X11/X11Structs.cs
  7. 25
      src/Avalonia.X11/X11Window.cs
  8. 269
      src/Avalonia.X11/XI2Manager.cs
  9. 283
      src/Avalonia.X11/XIStructs.cs
  10. 70
      src/Avalonia.X11/XLib.cs

2
src/Avalonia.X11/X11Clipboard.cs

@ -20,9 +20,9 @@ namespace Avalonia.X11
public X11Clipboard(AvaloniaX11Platform platform)
{
_x11 = platform.Info;
_handle = CreateEventWindow(platform, OnEvent);
_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,

26
src/Avalonia.X11/X11Info.cs

@ -24,6 +24,11 @@ namespace Avalonia.X11
public Version RandrVersion { get; }
public int XInputOpcode { get; }
public int XInputEventBase { get; }
public int XInputErrorBase { get; }
public Version XInputVersion { get; }
public IntPtr LastActivityTimestamp { get; set; }
@ -55,7 +60,26 @@ namespace Avalonia.X11
{
//Ignore, randr is not supported
}
try
{
if (XQueryExtension(display, "XInputExtension",
out var xiopcode, out var xievent, out var xierror))
{
int major = 2, minor = 2;
if (XIQueryVersion(display, ref major, ref minor) == Status.Success)
{
XInputVersion = new Version(major, minor);
XInputOpcode = xiopcode;
XInputEventBase = xievent;
XInputErrorBase = xierror;
}
}
}
catch
{
//Ignore, XI is not supported
}
}
}
}

9
src/Avalonia.X11/X11Platform.cs

@ -18,6 +18,7 @@ namespace Avalonia.X11
public KeyboardDevice KeyboardDevice => _keyboardDevice.Value;
public MouseDevice MouseDevice => _mouseDevice.Value;
public Dictionary<IntPtr, Action<XEvent>> Windows = new Dictionary<IntPtr, Action<XEvent>>();
public XI2Manager XI2;
public X11Info Info { get; private set; }
public void Initialize()
{
@ -31,7 +32,7 @@ namespace Avalonia.X11
AvaloniaLocator.CurrentMutable.BindToSelf(this)
.Bind<IWindowingPlatform>().ToConstant(this)
.Bind<IPlatformThreadingInterface>().ToConstant(new X11PlatformThreading(Display, Windows))
.Bind<IPlatformThreadingInterface>().ToConstant(new X11PlatformThreading(this))
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(InputModifiers.Control))
@ -42,6 +43,12 @@ namespace Avalonia.X11
.Bind<ISystemDialogImpl>().ToConstant(new SystemDialogsStub())
.Bind<IPlatformIconLoader>().ToConstant(new IconLoaderStub());
X11Screens.Init(this);
if (Info.XInputVersion != null)
{
var xi2 = new XI2Manager();
if (xi2.Init(this))
XI2 = xi2;
}
EglGlPlatformFeature.TryInitialize();
}

32
src/Avalonia.X11/X11PlatformThreading.cs

@ -11,6 +11,7 @@ namespace Avalonia.X11
{
unsafe class X11PlatformThreading : IPlatformThreadingInterface
{
private readonly AvaloniaX11Platform _platform;
private readonly IntPtr _display;
private readonly Dictionary<IntPtr, Action<XEvent>> _eventHandlers;
private Thread _mainThread;
@ -103,12 +104,13 @@ namespace Avalonia.X11
List<X11Timer> _timers = new List<X11Timer>();
public X11PlatformThreading(IntPtr display, Dictionary<IntPtr, Action<XEvent>> eventHandlers)
public X11PlatformThreading(AvaloniaX11Platform platform)
{
_display = display;
_eventHandlers = eventHandlers;
_platform = platform;
_display = platform.Display;
_eventHandlers = platform.Windows;
_mainThread = Thread.CurrentThread;
var fd = XLib.XConnectionNumber(display);
var fd = XLib.XConnectionNumber(_display);
var ev = new epoll_event()
{
events = EPOLLIN,
@ -214,9 +216,27 @@ namespace Avalonia.X11
if (cancellationToken.IsCancellationRequested)
return;
XNextEvent(_display, out var xev);
if (xev.type == XEventName.GenericEvent)
XGetEventData(_display, &xev.GenericEventCookie);
pending--;
if (_eventHandlers.TryGetValue(xev.AnyEvent.window, out var handler))
handler(xev);
try
{
if (xev.type == XEventName.GenericEvent)
{
if (_platform.XI2 != null && _platform.Info.XInputOpcode ==
xev.GenericEventCookie.extension)
{
_platform.XI2.OnEvent((XIEvent*)xev.GenericEventCookie.data);
}
}
else if (_eventHandlers.TryGetValue(xev.AnyEvent.window, out var handler))
handler(xev);
}
finally
{
if (xev.type == XEventName.GenericEvent && xev.GenericEventCookie.data != null)
XFreeEventData(_display, &xev.GenericEventCookie);
}
}
}
Dispatcher.UIThread.RunJobs();

3
src/Avalonia.X11/X11Screens.cs

@ -67,8 +67,7 @@ namespace Avalonia.X11
{
_settings = settings;
_x11 = platform.Info;
_window = XCreateSimpleWindow(_x11.Display, _x11.DefaultRootWindow, 0, 0, 1, 1, 0, IntPtr.Zero, IntPtr.Zero);
platform.Windows[_window] = OnEvent;
_window = CreateEventWindow(platform, OnEvent);
XRRSelectInput(_x11.Display, _window, RandrEventMask.RRScreenChangeNotify);
}

27
src/Avalonia.X11/X11Structs.cs

@ -133,7 +133,7 @@ namespace Avalonia.X11 {
internal NotifyDetail detail;
internal bool same_screen;
internal bool focus;
internal int state;
internal XModifierMask state;
}
[StructLayout(LayoutKind.Sequential)]
@ -520,7 +520,27 @@ namespace Avalonia.X11 {
internal IntPtr pad23;
}
[StructLayout(LayoutKind.Explicit)]
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct XGenericEventCookie
{
internal int type; /* of event. Always GenericEvent */
internal IntPtr serial; /* # of last request processed */
internal bool send_event; /* true if from SendEvent request */
internal IntPtr display; /* Display the event was read from */
internal int extension; /* major opcode of extension that caused the event */
internal int evtype; /* actual event type. */
internal uint cookie;
internal void* data;
public T GetEvent<T>() where T : unmanaged
{
if (data == null)
throw new InvalidOperationException();
return *(T*)data;
}
}
[StructLayout(LayoutKind.Explicit)]
internal struct XEvent {
[ FieldOffset(0) ] internal XEventName type;
[ FieldOffset(0) ] internal XAnyEvent AnyEvent;
@ -554,6 +574,7 @@ namespace Avalonia.X11 {
[ FieldOffset(0) ] internal XMappingEvent MappingEvent;
[ FieldOffset(0) ] internal XErrorEvent ErrorEvent;
[ FieldOffset(0) ] internal XKeymapEvent KeymapEvent;
[ FieldOffset(0) ] internal XGenericEventCookie GenericEventCookie;
//[MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst=24)]
//[ FieldOffset(0) ] internal int[] pad;
@ -738,7 +759,7 @@ namespace Avalonia.X11 {
ColormapNotify = 32,
ClientMessage = 33,
MappingNotify = 34,
GenericEvent = 35,
LASTEvent
}

25
src/Avalonia.X11/X11Window.cs

@ -17,7 +17,7 @@ using static Avalonia.X11.XLib;
// ReSharper disable StringLiteralTypo
namespace Avalonia.X11
{
unsafe class X11Window : IWindowImpl, IPopupImpl
unsafe class X11Window : IWindowImpl, IPopupImpl, IXI2Client
{
private readonly AvaloniaX11Platform _platform;
private readonly bool _popup;
@ -81,11 +81,13 @@ namespace Avalonia.X11
Handle = new PlatformHandle(_handle, "XID");
ClientSize = new Size(400, 400);
platform.Windows[_handle] = OnEvent;
XSelectInput(_x11.Display, _handle,
new IntPtr(0xffffff
^ (int)XEventMask.SubstructureRedirectMask
^ (int)XEventMask.ResizeRedirectMask
^ (int)XEventMask.PointerMotionHintMask));
XEventMask ignoredMask = XEventMask.SubstructureRedirectMask
| XEventMask.ResizeRedirectMask
| XEventMask.PointerMotionHintMask;
if (platform.XI2 != null)
ignoredMask |= platform.XI2.AddWindow(_handle, this);
var mask = new IntPtr(0xffffff ^ (int)ignoredMask);
XSelectInput(_x11.Display, _handle, mask);
var protocols = new[]
{
_x11.Atoms.WM_DELETE_WINDOW
@ -229,6 +231,8 @@ namespace Avalonia.X11
Deactivated?.Invoke();
else if (ev.type == XEventName.MotionNotify)
MouseEvent(RawMouseEventType.Move, ref ev, ev.MotionEvent.state);
else if (ev.type == XEventName.LeaveNotify)
MouseEvent(RawMouseEventType.LeaveWindow, ref ev, ev.CrossingEvent.state);
else if (ev.type == XEventName.PropertyNotify)
{
OnPropertyChange(ev.PropertyEvent.atom, ev.PropertyEvent.state == 0);
@ -431,6 +435,12 @@ namespace Avalonia.X11
void ScheduleInput(RawInputEventArgs args, ref XEvent xev)
{
_x11.LastActivityTimestamp = xev.ButtonEvent.time;
ScheduleInput(args);
}
public void ScheduleInput(RawInputEventArgs args)
{
_lastEvent = new InputEventContainer() {Event = args};
_inputQueue.Enqueue(_lastEvent);
if (_inputQueue.Count == 1)
@ -479,6 +489,8 @@ namespace Avalonia.X11
});
}
public IInputRoot InputRoot => _inputRoot;
public void SetInputRoot(IInputRoot inputRoot)
{
_inputRoot = inputRoot;
@ -506,6 +518,7 @@ namespace Avalonia.X11
{
XDestroyWindow(_x11.Display, _handle);
_platform.Windows.Remove(_handle);
_platform.XI2?.OnWindowDestroyed(_handle);
_handle = IntPtr.Zero;
Closed?.Invoke();
}

269
src/Avalonia.X11/XI2Manager.cs

@ -0,0 +1,269 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Avalonia.Input;
using Avalonia.Input.Raw;
using static Avalonia.X11.XLib;
namespace Avalonia.X11
{
unsafe class XI2Manager
{
private X11Info _x11;
private Dictionary<IntPtr, IXI2Client> _clients = new Dictionary<IntPtr, IXI2Client>();
class DeviceInfo
{
public int Id { get; }
public XIValuatorClassInfo[] Valuators { get; private set; }
public XIScrollClassInfo[] Scrollers { get; private set; }
public DeviceInfo(XIDeviceInfo info)
{
Id = info.Deviceid;
Update(info.Classes, info.NumClasses);
}
public virtual void Update(XIAnyClassInfo** classes, int num)
{
var valuators = new List<XIValuatorClassInfo>();
var scrollers = new List<XIScrollClassInfo>();
for (var c = 0; c < num; c++)
{
if (classes[c]->Type == XiDeviceClass.XIValuatorClass)
valuators.Add(*((XIValuatorClassInfo**)classes)[c]);
if (classes[c]->Type == XiDeviceClass.XIScrollClass)
scrollers.Add(*((XIScrollClassInfo**)classes)[c]);
}
Valuators = valuators.ToArray();
Scrollers = scrollers.ToArray();
}
public void UpdateValuators(Dictionary<int, double> valuators)
{
foreach (var v in valuators)
{
if (Valuators.Length > v.Key)
Valuators[v.Key].Value = v.Value;
}
}
}
class PointerDeviceInfo : DeviceInfo
{
public PointerDeviceInfo(XIDeviceInfo info) : base(info)
{
}
public bool HasScroll(ParsedDeviceEvent ev)
{
foreach (var val in ev.Valuators)
if (Scrollers.Any(s => s.Number == val.Key))
return true;
return false;
}
public bool HasMotion(ParsedDeviceEvent ev)
{
foreach (var val in ev.Valuators)
if (Scrollers.All(s => s.Number != val.Key))
return true;
return false;
}
}
private PointerDeviceInfo _pointerDevice;
private AvaloniaX11Platform _platform;
public bool Init(AvaloniaX11Platform platform)
{
_platform = platform;
_x11 = platform.Info;
var devices =(XIDeviceInfo*) XIQueryDevice(_x11.Display,
(int)XiPredefinedDeviceId.XIAllMasterDevices, out int num);
for (var c = 0; c < num; c++)
{
if (devices[c].Use == XiDeviceType.XIMasterPointer)
{
_pointerDevice = new PointerDeviceInfo(devices[c]);
break;
}
}
if(_pointerDevice == null)
return false;
/*
int mask = 0;
XISetMask(ref mask, XiEventType.XI_DeviceChanged);
var emask = new XIEventMask
{
Mask = &mask,
Deviceid = _pointerDevice.Id,
MaskLen = XiEventMaskLen
};
if (XISelectEvents(_x11.Display, _x11.RootWindow, &emask, 1) != Status.Success)
return false;
return true;
*/
return XiSelectEvents(_x11.Display, _x11.RootWindow, new Dictionary<int, List<XiEventType>>
{
[_pointerDevice.Id] = new List<XiEventType>
{
XiEventType.XI_DeviceChanged
}
}) == Status.Success;
}
public XEventMask AddWindow(IntPtr xid, IXI2Client window)
{
_clients[xid] = window;
XiSelectEvents(_x11.Display, xid, new Dictionary<int, List<XiEventType>>
{
[_pointerDevice.Id] = new List<XiEventType>()
{
XiEventType.XI_Motion,
XiEventType.XI_ButtonPress,
XiEventType.XI_ButtonRelease,
}
});
// We are taking over mouse input handling from here
return XEventMask.PointerMotionMask
| XEventMask.ButtonMotionMask
| XEventMask.Button1MotionMask
| XEventMask.Button2MotionMask
| XEventMask.Button3MotionMask
| XEventMask.Button4MotionMask
| XEventMask.Button5MotionMask
| XEventMask.ButtonPressMask
| XEventMask.ButtonReleaseMask;
}
public void OnWindowDestroyed(IntPtr xid) => _clients.Remove(xid);
public void OnEvent(XIEvent* xev)
{
if (xev->evtype == XiEventType.XI_DeviceChanged)
{
var changed = (XIDeviceChangedEvent*)xev;
_pointerDevice.Update(changed->Classes, changed->NumClasses);
}
//TODO: this should only be used for non-touch devices
if (xev->evtype >= XiEventType.XI_ButtonPress && xev->evtype <= XiEventType.XI_Motion)
{
var dev = (XIDeviceEvent*)xev;
if (_clients.TryGetValue(dev->EventWindow, out var client))
OnDeviceEvent(client, new ParsedDeviceEvent(dev));
}
}
void OnDeviceEvent(IXI2Client client, ParsedDeviceEvent ev)
{
if (ev.Type == XiEventType.XI_Motion)
{
Vector scrollDelta = default;
foreach (var v in ev.Valuators)
{
foreach (var scroller in _pointerDevice.Scrollers)
{
if (scroller.Number == v.Key)
{
var old = _pointerDevice.Valuators[scroller.Number].Value;
// Value was zero after reset, ignore the event and use it as a reference next time
if (old == 0)
continue;
var diff = (old - v.Value) / scroller.Increment;
if (scroller.ScrollType == XiScrollType.Horizontal)
scrollDelta = scrollDelta.WithX(scrollDelta.X + diff);
else
scrollDelta = scrollDelta.WithY(scrollDelta.Y + diff);
}
}
}
if (scrollDelta != default)
client.ScheduleInput(new RawMouseWheelEventArgs(_platform.MouseDevice, ev.Timestamp,
client.InputRoot, ev.Position, scrollDelta, ev.Modifiers));
if (_pointerDevice.HasMotion(ev))
client.ScheduleInput(new RawMouseEventArgs(_platform.MouseDevice, ev.Timestamp, client.InputRoot,
RawMouseEventType.Move, ev.Position, ev.Modifiers));
}
if (ev.Type == XiEventType.XI_ButtonPress || ev.Type == XiEventType.XI_ButtonRelease)
{
var down = ev.Type == XiEventType.XI_ButtonPress;
var type =
ev.Button == 1 ? (down ? RawMouseEventType.LeftButtonDown : RawMouseEventType.LeftButtonUp)
: ev.Button == 2 ? (down ? RawMouseEventType.MiddleButtonDown : RawMouseEventType.MiddleButtonUp)
: ev.Button == 3 ? (down ? RawMouseEventType.RightButtonDown : RawMouseEventType.RightButtonUp)
: (RawMouseEventType?)null;
if (type.HasValue)
client.ScheduleInput(new RawMouseEventArgs(_platform.MouseDevice, ev.Timestamp, client.InputRoot,
type.Value, ev.Position, ev.Modifiers));
}
_pointerDevice.UpdateValuators(ev.Valuators);
}
}
unsafe class ParsedDeviceEvent
{
public XiEventType Type { get; }
public InputModifiers Modifiers { get; }
public ulong Timestamp { get; }
public Point Position { get; }
public int Button { get; set; }
public Dictionary<int, double> Valuators { get; }
public ParsedDeviceEvent(XIDeviceEvent* ev)
{
Type = ev->evtype;
Timestamp = (ulong)ev->time.ToInt64();
var state = (XModifierMask)ev->mods.Effective;
if (state.HasFlag(XModifierMask.ShiftMask))
Modifiers |= InputModifiers.Shift;
if (state.HasFlag(XModifierMask.ControlMask))
Modifiers |= InputModifiers.Control;
if (state.HasFlag(XModifierMask.Mod1Mask))
Modifiers |= InputModifiers.Alt;
if (state.HasFlag(XModifierMask.Mod4Mask))
Modifiers |= InputModifiers.Windows;
if (ev->buttons.MaskLen > 0)
{
var buttons = ev->buttons.Mask;
if (XIMaskIsSet(buttons, 1))
Modifiers |= InputModifiers.LeftMouseButton;
if (XIMaskIsSet(buttons, 2))
Modifiers |= InputModifiers.MiddleMouseButton;
if (XIMaskIsSet(buttons, 3))
Modifiers |= InputModifiers.RightMouseButton;
}
Valuators = new Dictionary<int, double>();
Position = new Point(ev->event_x, ev->event_y);
var values = ev->valuators.Values;
for (var c = 0; c < ev->valuators.MaskLen * 8; c++)
if (XIMaskIsSet(ev->valuators.Mask, c))
Valuators[c] = *values++;
if (Type == XiEventType.XI_ButtonPress || Type == XiEventType.XI_ButtonRelease)
Button = ev->detail;
}
}
interface IXI2Client
{
IInputRoot InputRoot { get; }
void ScheduleInput(RawInputEventArgs args);
}
}

283
src/Avalonia.X11/XIStructs.cs

@ -0,0 +1,283 @@
using System;
using System.Runtime.InteropServices;
using Bool = System.Boolean;
using Atom = System.IntPtr;
// ReSharper disable IdentifierTypo
// ReSharper disable FieldCanBeMadeReadOnly.Global
// ReSharper disable MemberCanBePrivate.Global
#pragma warning disable 649
namespace Avalonia.X11
{
[StructLayout(LayoutKind.Sequential)]
struct XIAddMasterInfo
{
public int Type;
public IntPtr Name;
public Bool SendCore;
public Bool Enable;
}
[StructLayout(LayoutKind.Sequential)]
struct XIRemoveMasterInfo
{
public int Type;
public int Deviceid;
public int ReturnMode; /* AttachToMaster, Floating */
public int ReturnPointer;
public int ReturnKeyboard;
};
[StructLayout(LayoutKind.Sequential)]
struct XIAttachSlaveInfo
{
public int Type;
public int Deviceid;
public int NewMaster;
};
[StructLayout(LayoutKind.Sequential)]
struct XIDetachSlaveInfo
{
public int Type;
public int Deviceid;
};
[StructLayout(LayoutKind.Explicit)]
struct XIAnyHierarchyChangeInfo
{
[FieldOffset(0)]
public int type; /* must be first element */
[FieldOffset(4)]
public XIAddMasterInfo add;
[FieldOffset(4)]
public XIRemoveMasterInfo remove;
[FieldOffset(4)]
public XIAttachSlaveInfo attach;
[FieldOffset(4)]
public XIDetachSlaveInfo detach;
};
[StructLayout(LayoutKind.Sequential)]
struct XIModifierState
{
public int Base;
public int Latched;
public int Locked;
public int Effective;
};
[StructLayout(LayoutKind.Sequential)]
unsafe struct XIButtonState
{
public int MaskLen;
public byte* Mask;
};
[StructLayout(LayoutKind.Sequential)]
unsafe struct XIValuatorState
{
public int MaskLen;
public byte* Mask;
public double* Values;
};
[StructLayout(LayoutKind.Sequential)]
unsafe struct XIEventMask
{
public int Deviceid;
public int MaskLen;
public int* Mask;
};
[StructLayout(LayoutKind.Sequential)]
struct XIAnyClassInfo
{
public XiDeviceClass Type;
public int Sourceid;
};
[StructLayout(LayoutKind.Sequential)]
unsafe struct XIButtonClassInfo
{
public int Type;
public int Sourceid;
public int NumButtons;
public IntPtr* Labels;
public XIButtonState State;
};
[StructLayout(LayoutKind.Sequential)]
unsafe struct XIKeyClassInfo
{
public int Type;
public int Sourceid;
public int NumKeycodes;
public int* Keycodes;
};
[StructLayout(LayoutKind.Sequential)]
struct XIValuatorClassInfo
{
public int Type;
public int Sourceid;
public int Number;
public IntPtr Label;
public double Min;
public double Max;
public double Value;
public int Resolution;
public int Mode;
};
/* new in XI 2.1 */
[StructLayout(LayoutKind.Sequential)]
struct XIScrollClassInfo
{
public int Type;
public int Sourceid;
public int Number;
public XiScrollType ScrollType;
public double Increment;
public int Flags;
};
enum XiScrollType
{
Vertical = 1,
Horizontal = 2
}
[StructLayout(LayoutKind.Sequential)]
struct XITouchClassInfo
{
public int Type;
public int Sourceid;
public int Mode;
public int NumTouches;
};
[StructLayout(LayoutKind.Sequential)]
unsafe struct XIDeviceInfo
{
public int Deviceid;
public IntPtr Name;
public XiDeviceType Use;
public int Attachment;
public Bool Enabled;
public int NumClasses;
public XIAnyClassInfo** Classes;
}
enum XiDeviceType
{
XIMasterPointer = 1,
XIMasterKeyboard = 2,
XISlavePointer = 3,
XISlaveKeyboard = 4,
XIFloatingSlave = 5
}
enum XiPredefinedDeviceId : int
{
XIAllDevices = 0,
XIAllMasterDevices = 1
}
enum XiDeviceClass
{
XIKeyClass = 0,
XIButtonClass = 1,
XIValuatorClass = 2,
XIScrollClass = 3,
XITouchClass = 8,
}
[StructLayout(LayoutKind.Sequential)]
unsafe struct XIDeviceChangedEvent
{
public int Type; /* GenericEvent */
public ulong Serial; /* # of last request processed by server */
public Bool SendEvent; /* true if this came from a SendEvent request */
public IntPtr Display; /* Display the event was read from */
public int Extension; /* XI extension offset */
public int Evtype; /* XI_DeviceChanged */
public IntPtr Time;
public int Deviceid; /* id of the device that changed */
public int Sourceid; /* Source for the new classes. */
public int Reason; /* Reason for the change */
public int NumClasses;
public XIAnyClassInfo** Classes; /* same as in XIDeviceInfo */
}
[StructLayout(LayoutKind.Sequential)]
struct XIDeviceEvent
{
public XEventName type; /* GenericEvent */
public ulong serial; /* # of last request processed by server */
public Bool send_event; /* true if this came from a SendEvent request */
public IntPtr display; /* Display the event was read from */
public int extension; /* XI extension offset */
public XiEventType evtype;
public IntPtr time;
public int deviceid;
public int sourceid;
public int detail;
public IntPtr RootWindow;
public IntPtr EventWindow;
public IntPtr ChildWindow;
public double root_x;
public double root_y;
public double event_x;
public double event_y;
public int flags;
public XIButtonState buttons;
public XIValuatorState valuators;
public XIModifierState mods;
public XIModifierState group;
}
[StructLayout(LayoutKind.Sequential)]
unsafe struct XIEvent
{
public int type; /* GenericEvent */
public ulong serial; /* # of last request processed by server */
public Bool send_event; /* true if this came from a SendEvent request */
public IntPtr display; /* Display the event was read from */
public int extension; /* XI extension offset */
public XiEventType evtype;
public IntPtr time;
}
enum XiEventType
{
XI_DeviceChanged = 1,
XI_KeyPress = 2,
XI_KeyRelease = 3,
XI_ButtonPress = 4,
XI_ButtonRelease = 5,
XI_Motion = 6,
XI_Enter = 7,
XI_Leave = 8,
XI_FocusIn = 9,
XI_FocusOut = 10,
XI_HierarchyChanged = 11,
XI_PropertyEvent = 12,
XI_RawKeyPress = 13,
XI_RawKeyRelease = 14,
XI_RawButtonPress = 15,
XI_RawButtonRelease = 16,
XI_RawMotion = 17,
XI_TouchBegin = 18 /* XI 2.2 */,
XI_TouchUpdate = 19,
XI_TouchEnd = 20,
XI_TouchOwnership = 21,
XI_RawTouchBegin = 22,
XI_RawTouchUpdate = 23,
XI_RawTouchEnd = 24,
XI_BarrierHit = 25 /* XI 2.3 */,
XI_BarrierLeave = 26,
XI_LASTEVENT = XI_BarrierLeave,
}
}

70
src/Avalonia.X11/XLib.cs

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
@ -16,6 +17,8 @@ namespace Avalonia.X11
{
const string libX11 = "libX11.so.6";
const string libX11Randr = "libXrandr.so.2";
const string libX11Ext = "libXext.so.6";
const string libXInput = "libXi.so.6";
[DllImport(libX11)]
public static extern IntPtr XOpenDisplay(IntPtr display);
@ -451,6 +454,16 @@ namespace Avalonia.X11
[DllImport (libX11)]
public static extern void XDestroyIC (IntPtr xic);
[DllImport(libX11)]
public static extern bool XQueryExtension(IntPtr display, [MarshalAs(UnmanagedType.LPStr)] string name,
out int majorOpcode, out int firstEvent, out int firstError);
[DllImport(libX11)]
public static extern bool XGetEventData(IntPtr display, void* cookie);
[DllImport(libX11)]
public static extern void XFreeEventData(IntPtr display, void* cookie);
[DllImport(libX11Randr)]
public static extern int XRRQueryExtension (IntPtr dpy,
@ -467,7 +480,57 @@ namespace Avalonia.X11
XRRGetMonitors(IntPtr dpy, IntPtr window, bool get_active, out int nmonitors);
[DllImport(libX11Randr)]
public static extern void XRRSelectInput(IntPtr dpy, IntPtr window, RandrEventMask mask);
[DllImport(libXInput)]
public static extern Status XIQueryVersion(IntPtr dpy, ref int major, ref int minor);
[DllImport(libXInput)]
public static extern IntPtr XIQueryDevice(IntPtr dpy, int deviceid, out int ndevices_return);
[DllImport(libXInput)]
public static extern void XIFreeDeviceInfo(XIDeviceInfo* info);
public static void XISetMask(ref int mask, XiEventType ev)
{
mask |= (1 << (int)ev);
}
public static int XiEventMaskLen { get; } = 4;
public static bool XIMaskIsSet(void* ptr, int shift) =>
(((byte*)(ptr))[(shift) >> 3] & (1 << (shift & 7))) != 0;
[DllImport(libXInput)]
public static extern Status XISelectEvents(
IntPtr dpy,
IntPtr win,
XIEventMask* masks,
int num_masks
);
public static Status XiSelectEvents(IntPtr display, IntPtr window, Dictionary<int, List<XiEventType>> devices)
{
var masks = stackalloc int[devices.Count];
var emasks = stackalloc XIEventMask[devices.Count];
int c = 0;
foreach (var d in devices)
{
foreach (var ev in d.Value)
XISetMask(ref masks[c], ev);
emasks[c] = new XIEventMask
{
Mask = &masks[c],
Deviceid = d.Key,
MaskLen = XiEventMaskLen
};
c++;
}
return XISelectEvents(display, window, emasks, devices.Count);
}
public struct XGeometry
{
public IntPtr root;
@ -541,6 +604,13 @@ namespace Avalonia.X11
}
}
public static IntPtr CreateEventWindow(AvaloniaX11Platform plat, Action<XEvent> handler)
{
var win = XCreateSimpleWindow(plat.Display, plat.Info.DefaultRootWindow,
0, 0, 1, 1, 0, IntPtr.Zero, IntPtr.Zero);
plat.Windows[win] = handler;
return win;
}
}

Loading…
Cancel
Save