10 changed files with 726 additions and 20 deletions
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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, |
||||
|
} |
||||
|
|
||||
|
} |
||||
Loading…
Reference in new issue