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