From 24a0592890fdec3689f7d7beaa2569fe9301bb33 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 13 Jan 2019 21:51:43 +0300 Subject: [PATCH] Added XI2 support for mouse events (smooth scrolling!) --- src/Avalonia.X11/X11Clipboard.cs | 2 +- src/Avalonia.X11/X11Info.cs | 26 ++- src/Avalonia.X11/X11Platform.cs | 9 +- src/Avalonia.X11/X11PlatformThreading.cs | 32 ++- src/Avalonia.X11/X11Screens.cs | 3 +- src/Avalonia.X11/X11Structs.cs | 27 ++- src/Avalonia.X11/X11Window.cs | 25 +- src/Avalonia.X11/XI2Manager.cs | 269 +++++++++++++++++++++ src/Avalonia.X11/XIStructs.cs | 283 +++++++++++++++++++++++ src/Avalonia.X11/XLib.cs | 70 ++++++ 10 files changed, 726 insertions(+), 20 deletions(-) create mode 100644 src/Avalonia.X11/XI2Manager.cs create mode 100644 src/Avalonia.X11/XIStructs.cs diff --git a/src/Avalonia.X11/X11Clipboard.cs b/src/Avalonia.X11/X11Clipboard.cs index 74d26d1d08..55eb4b563e 100644 --- a/src/Avalonia.X11/X11Clipboard.cs +++ b/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, diff --git a/src/Avalonia.X11/X11Info.cs b/src/Avalonia.X11/X11Info.cs index e102283f87..53da9e0cf3 100644 --- a/src/Avalonia.X11/X11Info.cs +++ b/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 + } } } } diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index d315ba5ffb..79aef75e4c 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -18,6 +18,7 @@ namespace Avalonia.X11 public KeyboardDevice KeyboardDevice => _keyboardDevice.Value; public MouseDevice MouseDevice => _mouseDevice.Value; public Dictionary> Windows = new Dictionary>(); + public XI2Manager XI2; public X11Info Info { get; private set; } public void Initialize() { @@ -31,7 +32,7 @@ namespace Avalonia.X11 AvaloniaLocator.CurrentMutable.BindToSelf(this) .Bind().ToConstant(this) - .Bind().ToConstant(new X11PlatformThreading(Display, Windows)) + .Bind().ToConstant(new X11PlatformThreading(this)) .Bind().ToConstant(new DefaultRenderTimer(60)) .Bind().ToConstant(new RenderLoop()) .Bind().ToConstant(new PlatformHotkeyConfiguration(InputModifiers.Control)) @@ -42,6 +43,12 @@ namespace Avalonia.X11 .Bind().ToConstant(new SystemDialogsStub()) .Bind().ToConstant(new IconLoaderStub()); X11Screens.Init(this); + if (Info.XInputVersion != null) + { + var xi2 = new XI2Manager(); + if (xi2.Init(this)) + XI2 = xi2; + } EglGlPlatformFeature.TryInitialize(); } diff --git a/src/Avalonia.X11/X11PlatformThreading.cs b/src/Avalonia.X11/X11PlatformThreading.cs index 298b4da19c..5736500262 100644 --- a/src/Avalonia.X11/X11PlatformThreading.cs +++ b/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> _eventHandlers; private Thread _mainThread; @@ -103,12 +104,13 @@ namespace Avalonia.X11 List _timers = new List(); - public X11PlatformThreading(IntPtr display, Dictionary> 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(); diff --git a/src/Avalonia.X11/X11Screens.cs b/src/Avalonia.X11/X11Screens.cs index 4a9cbe98a4..46ad4d7320 100644 --- a/src/Avalonia.X11/X11Screens.cs +++ b/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); } diff --git a/src/Avalonia.X11/X11Structs.cs b/src/Avalonia.X11/X11Structs.cs index fc1bb01b6e..0ae5c16aef 100644 --- a/src/Avalonia.X11/X11Structs.cs +++ b/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() 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 } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 3212123238..4441006fda 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/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(); } diff --git a/src/Avalonia.X11/XI2Manager.cs b/src/Avalonia.X11/XI2Manager.cs new file mode 100644 index 0000000000..ee73ccc907 --- /dev/null +++ b/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 _clients = new Dictionary(); + 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(); + var scrollers = new List(); + 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 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> + { + [_pointerDevice.Id] = new List + { + XiEventType.XI_DeviceChanged + } + }) == Status.Success; + } + + public XEventMask AddWindow(IntPtr xid, IXI2Client window) + { + _clients[xid] = window; + + XiSelectEvents(_x11.Display, xid, new Dictionary> + { + [_pointerDevice.Id] = new List() + { + 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 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(); + 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); + } +} diff --git a/src/Avalonia.X11/XIStructs.cs b/src/Avalonia.X11/XIStructs.cs new file mode 100644 index 0000000000..772bd1989c --- /dev/null +++ b/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, + } + +} diff --git a/src/Avalonia.X11/XLib.cs b/src/Avalonia.X11/XLib.cs index 1688d69f4f..a6c2c6ac1c 100644 --- a/src/Avalonia.X11/XLib.cs +++ b/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> 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 handler) + { + var win = XCreateSimpleWindow(plat.Display, plat.Info.DefaultRootWindow, + 0, 0, 1, 1, 0, IntPtr.Zero, IntPtr.Zero); + plat.Windows[win] = handler; + return win; + } }