Added basic Pen support for X11 (#19910)
* Added surface dispose to DrawingSurfaceDemoBase * Added pen support for x11 * Fixed intermediate points defaulting properties * Detect the current device being an eraser * Formatting fixes * Removed spaces --------- Co-authored-by: flabbet <flabbet@fedora> Co-authored-by: Nikita Tsukanov <keks9n@gmail.com>
This commit is contained in:
committed by
GitHub
parent
7a340a6579
commit
79be86d923
@@ -44,6 +44,7 @@ namespace Avalonia.X11
|
||||
private bool _triggeredExpose;
|
||||
private IInputRoot? _inputRoot;
|
||||
private readonly MouseDevice _mouse;
|
||||
private readonly PenDevice _pen;
|
||||
private readonly TouchDevice _touch;
|
||||
private readonly IKeyboardDevice _keyboard;
|
||||
private readonly ITopLevelNativeMenuExporter? _nativeMenuExporter;
|
||||
@@ -97,6 +98,7 @@ namespace Avalonia.X11
|
||||
_overrideRedirect = _popup || overrideRedirect;
|
||||
_x11 = platform.Info;
|
||||
_mouse = Avalonia.Input.MouseDevice.Primary;
|
||||
_pen = new PenDevice();
|
||||
_touch = new TouchDevice();
|
||||
_keyboard = platform.KeyboardDevice;
|
||||
|
||||
@@ -1072,6 +1074,7 @@ namespace Avalonia.X11
|
||||
_platform.XI2?.OnWindowDestroyed(_handle);
|
||||
var handle = _handle;
|
||||
_handle = IntPtr.Zero;
|
||||
_pen.Dispose();
|
||||
_touch.Dispose();
|
||||
if (!fromDestroyNotification)
|
||||
XDestroyWindow(_x11.Display, handle);
|
||||
@@ -1238,6 +1241,7 @@ namespace Avalonia.X11
|
||||
}
|
||||
|
||||
public IMouseDevice MouseDevice => _mouse;
|
||||
public IPenDevice PenDevice => _pen;
|
||||
public TouchDevice TouchDevice => _touch;
|
||||
|
||||
public IPopupImpl? CreatePopup()
|
||||
|
||||
+122
-22
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Input.Raw;
|
||||
using static Avalonia.X11.XLib;
|
||||
@@ -16,7 +17,6 @@ namespace Avalonia.X11
|
||||
XiEventType.XI_ButtonRelease,
|
||||
XiEventType.XI_Leave,
|
||||
XiEventType.XI_Enter,
|
||||
|
||||
};
|
||||
|
||||
private static readonly XiEventType[] MultiTouchEventTypes = new XiEventType[]
|
||||
@@ -35,13 +35,14 @@ namespace Avalonia.X11
|
||||
public int Id { get; }
|
||||
public XIValuatorClassInfo[] Valuators { get; private set; } = [];
|
||||
public XIScrollClassInfo[] Scrollers { get; private set; } = [];
|
||||
|
||||
public DeviceInfo(XIDeviceInfo info)
|
||||
{
|
||||
Id = info.Deviceid;
|
||||
UpdateCore(info.Classes, info.NumClasses);
|
||||
}
|
||||
|
||||
public virtual void Update(XIAnyClassInfo** classes, int num)
|
||||
public virtual void Update(XIAnyClassInfo** classes, int num, int? slaveId)
|
||||
{
|
||||
UpdateCore(classes, num);
|
||||
}
|
||||
@@ -74,6 +75,9 @@ namespace Avalonia.X11
|
||||
|
||||
private class PointerDeviceInfo : DeviceInfo
|
||||
{
|
||||
private string? _currentSlaveName = null;
|
||||
private bool _currentSlaveIsEraser = false;
|
||||
|
||||
public PointerDeviceInfo(XIDeviceInfo info, X11Info x11Info) : base(info)
|
||||
{
|
||||
_x11 = x11Info;
|
||||
@@ -91,6 +95,9 @@ namespace Avalonia.X11
|
||||
var touchMinorAtom = XInternAtom(_x11.Display, "Abs MT Touch Minor", false);
|
||||
|
||||
var pressureAtom = XInternAtom(_x11.Display, "Abs MT Pressure", false);
|
||||
var pressureAtomPen = XInternAtom(_x11.Display, "Abs Pressure", false);
|
||||
var absTiltXAtom = XInternAtom(_x11.Display, "Abs Tilt X", false);
|
||||
var absTiltYAtom = XInternAtom(_x11.Display, "Abs Tilt Y", false);
|
||||
|
||||
PressureXIValuatorClassInfo = null;
|
||||
TouchMajorXIValuatorClassInfo = null;
|
||||
@@ -98,7 +105,8 @@ namespace Avalonia.X11
|
||||
|
||||
foreach (var xiValuatorClassInfo in Valuators)
|
||||
{
|
||||
if (xiValuatorClassInfo.Label == pressureAtom)
|
||||
if (xiValuatorClassInfo.Label == pressureAtom ||
|
||||
xiValuatorClassInfo.Label == pressureAtomPen)
|
||||
{
|
||||
PressureXIValuatorClassInfo = xiValuatorClassInfo;
|
||||
}
|
||||
@@ -110,15 +118,52 @@ namespace Avalonia.X11
|
||||
{
|
||||
TouchMinorXIValuatorClassInfo = xiValuatorClassInfo;
|
||||
}
|
||||
else if (xiValuatorClassInfo.Label == absTiltXAtom)
|
||||
{
|
||||
TiltXXIValuatorClassInfo = xiValuatorClassInfo;
|
||||
}
|
||||
else if (xiValuatorClassInfo.Label == absTiltYAtom)
|
||||
{
|
||||
TiltYXIValuatorClassInfo = xiValuatorClassInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(XIAnyClassInfo** classes, int num)
|
||||
public override void Update(XIAnyClassInfo** classes, int num, int? slaveId)
|
||||
{
|
||||
base.Update(classes, num);
|
||||
base.Update(classes, num, slaveId);
|
||||
|
||||
if (slaveId != null)
|
||||
{
|
||||
_currentSlaveName = null;
|
||||
_currentSlaveIsEraser = false;
|
||||
var devices = (XIDeviceInfo*)XIQueryDevice(_x11.Display,
|
||||
(int)XiPredefinedDeviceId.XIAllDevices, out int deviceNum);
|
||||
|
||||
for (var c = 0; c < deviceNum; c++)
|
||||
{
|
||||
if (devices[c].Deviceid == slaveId)
|
||||
{
|
||||
_currentSlaveName = Marshal.PtrToStringAnsi(devices[c].Name);
|
||||
_currentSlaveIsEraser =
|
||||
_currentSlaveName?.IndexOf("eraser", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
XIFreeDeviceInfo(devices);
|
||||
}
|
||||
|
||||
UpdateKnownValuator();
|
||||
}
|
||||
|
||||
public bool HasPressureValuator()
|
||||
{
|
||||
return PressureXIValuatorClassInfo is not null;
|
||||
}
|
||||
|
||||
public bool IsEraser => _currentSlaveIsEraser;
|
||||
public string? Name => _currentSlaveName;
|
||||
|
||||
public bool HasScroll(ParsedDeviceEvent ev)
|
||||
{
|
||||
foreach (var val in ev.Valuators)
|
||||
@@ -140,6 +185,8 @@ namespace Avalonia.X11
|
||||
public XIValuatorClassInfo? PressureXIValuatorClassInfo { get; private set; }
|
||||
public XIValuatorClassInfo? TouchMajorXIValuatorClassInfo { get; private set; }
|
||||
public XIValuatorClassInfo? TouchMinorXIValuatorClassInfo { get; private set; }
|
||||
public XIValuatorClassInfo? TiltXXIValuatorClassInfo { get; private set; }
|
||||
public XIValuatorClassInfo? TiltYXIValuatorClassInfo { get; private set; }
|
||||
}
|
||||
|
||||
private readonly PointerDeviceInfo _pointerDevice;
|
||||
@@ -225,10 +272,9 @@ namespace Avalonia.X11
|
||||
if (xev->evtype == XiEventType.XI_DeviceChanged)
|
||||
{
|
||||
var changed = (XIDeviceChangedEvent*)xev;
|
||||
_pointerDevice.Update(changed->Classes, changed->NumClasses);
|
||||
_pointerDevice.Update(changed->Classes, changed->NumClasses, changed->Reason == XiDeviceChangeReason.XISlaveSwitch ? changed->Sourceid : null);
|
||||
}
|
||||
|
||||
|
||||
if ((xev->evtype >= XiEventType.XI_ButtonPress && xev->evtype <= XiEventType.XI_Motion)
|
||||
|| (xev->evtype >= XiEventType.XI_TouchBegin && xev->evtype <= XiEventType.XI_TouchEnd))
|
||||
{
|
||||
@@ -280,17 +326,15 @@ namespace Avalonia.X11
|
||||
RawPointerEventType.TouchUpdate :
|
||||
RawPointerEventType.TouchEnd);
|
||||
|
||||
var rawPointerPoint = new RawPointerPoint()
|
||||
{
|
||||
Position = ev.Position
|
||||
};
|
||||
var rawPointerPoint = new RawPointerPoint() { Position = ev.Position };
|
||||
|
||||
if (_pointerDevice.PressureXIValuatorClassInfo is { } valuatorClassInfo)
|
||||
{
|
||||
if (ev.Valuators.TryGetValue(valuatorClassInfo.Number, out var pressureValue))
|
||||
{
|
||||
// In our API we use range from 0.0 to 1.0.
|
||||
var pressure = (pressureValue - valuatorClassInfo.Min) / (valuatorClassInfo.Max - valuatorClassInfo.Min);
|
||||
var pressure = (pressureValue - valuatorClassInfo.Min) /
|
||||
(valuatorClassInfo.Max - valuatorClassInfo.Min);
|
||||
rawPointerPoint.Pressure = (float)pressure;
|
||||
}
|
||||
}
|
||||
@@ -310,7 +354,8 @@ namespace Avalonia.X11
|
||||
|
||||
// As https://www.kernel.org/doc/html/latest/input/multi-touch-protocol.html says, using `screenBounds.Width` is not accurate enough.
|
||||
touchMajor = (touchMajorValue - touchMajorXIValuatorClassInfo.Min) /
|
||||
(touchMajorXIValuatorClassInfo.Max - touchMajorXIValuatorClassInfo.Min) * screenBounds.Width;
|
||||
(touchMajorXIValuatorClassInfo.Max - touchMajorXIValuatorClassInfo.Min) *
|
||||
screenBounds.Width;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,7 +366,8 @@ namespace Avalonia.X11
|
||||
if (ev.Valuators.TryGetValue(touchMinorXIValuatorClassInfo.Number, out var touchMinorValue))
|
||||
{
|
||||
touchMinor = (touchMinorValue - touchMinorXIValuatorClassInfo.Min) /
|
||||
(touchMinorXIValuatorClassInfo.Max - touchMinorXIValuatorClassInfo.Min) * screenBounds.Height;
|
||||
(touchMinorXIValuatorClassInfo.Max - touchMinorXIValuatorClassInfo.Min) *
|
||||
screenBounds.Height;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,9 +398,16 @@ namespace Avalonia.X11
|
||||
if (!client.IsEnabled || (_multitouch && ev.Emulated))
|
||||
return;
|
||||
|
||||
var eventModifiers = ev.Modifiers;
|
||||
if (_pointerDevice.IsEraser)
|
||||
eventModifiers |= RawInputModifiers.PenEraser;
|
||||
|
||||
if (ev.Type == XiEventType.XI_Motion)
|
||||
{
|
||||
Vector scrollDelta = default;
|
||||
var rawPointerPoint = new RawPointerPoint() { Position = ev.Position };
|
||||
IInputDevice device = _pointerDevice.HasPressureValuator() ? client.PenDevice : client.MouseDevice;
|
||||
|
||||
foreach (var v in ev.Valuators)
|
||||
{
|
||||
foreach (var scroller in _pointerDevice.Scrollers)
|
||||
@@ -374,15 +427,15 @@ namespace Avalonia.X11
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SetPenSpecificValues(v, ref rawPointerPoint);
|
||||
}
|
||||
|
||||
if (scrollDelta != default)
|
||||
client.ScheduleXI2Input(new RawMouseWheelEventArgs(client.MouseDevice, ev.Timestamp,
|
||||
client.InputRoot, ev.Position, scrollDelta, ev.Modifiers));
|
||||
client.ScheduleXI2Input(new RawMouseWheelEventArgs(device, ev.Timestamp,
|
||||
client.InputRoot, ev.Position, scrollDelta, eventModifiers));
|
||||
if (_pointerDevice.HasMotion(ev))
|
||||
client.ScheduleXI2Input(new RawPointerEventArgs(client.MouseDevice, ev.Timestamp, client.InputRoot,
|
||||
RawPointerEventType.Move, ev.Position, ev.Modifiers));
|
||||
client.ScheduleXI2Input(new RawPointerEventArgs(device, ev.Timestamp, client.InputRoot,
|
||||
RawPointerEventType.Move, rawPointerPoint, eventModifiers));
|
||||
}
|
||||
|
||||
if (ev.Type == XiEventType.XI_ButtonPress && ev.Button >= 4 && ev.Button <= 7 && !ev.Emulated)
|
||||
@@ -398,7 +451,7 @@ namespace Avalonia.X11
|
||||
|
||||
if (scrollDelta.HasValue)
|
||||
client.ScheduleXI2Input(new RawMouseWheelEventArgs(client.MouseDevice, ev.Timestamp,
|
||||
client.InputRoot, ev.Position, scrollDelta.Value, ev.Modifiers));
|
||||
client.InputRoot, ev.Position, scrollDelta.Value, eventModifiers));
|
||||
}
|
||||
|
||||
if (ev.Type == XiEventType.XI_ButtonPress || ev.Type == XiEventType.XI_ButtonRelease)
|
||||
@@ -413,13 +466,57 @@ namespace Avalonia.X11
|
||||
9 => down ? RawPointerEventType.XButton2Down : RawPointerEventType.XButton2Up,
|
||||
_ => (RawPointerEventType?)null
|
||||
};
|
||||
|
||||
if (type.HasValue)
|
||||
client.ScheduleXI2Input(new RawPointerEventArgs(client.MouseDevice, ev.Timestamp, client.InputRoot,
|
||||
type.Value, ev.Position, ev.Modifiers));
|
||||
{
|
||||
IInputDevice device = _pointerDevice.HasPressureValuator() ? client.PenDevice : client.MouseDevice;
|
||||
var pointerPoint = new RawPointerPoint() { Position = ev.Position };
|
||||
|
||||
SetPenSpecificValues(ev, ref pointerPoint);
|
||||
|
||||
client.ScheduleXI2Input(new RawPointerEventArgs(device, ev.Timestamp, client.InputRoot,
|
||||
type.Value, pointerPoint, eventModifiers));
|
||||
}
|
||||
}
|
||||
|
||||
_pointerDevice.UpdateValuators(ev.Valuators);
|
||||
}
|
||||
|
||||
private void SetPenSpecificValues(ParsedDeviceEvent ev, ref RawPointerPoint pointerPoint)
|
||||
{
|
||||
foreach (var evValuator in ev.Valuators)
|
||||
{
|
||||
SetPenSpecificValues(evValuator, ref pointerPoint);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetPenSpecificValues(KeyValuePair<int, double> item, ref RawPointerPoint rawPointerPoint)
|
||||
{
|
||||
if (_pointerDevice.PressureXIValuatorClassInfo is { } valuatorClassInfo)
|
||||
{
|
||||
if (item.Key == valuatorClassInfo.Number)
|
||||
{
|
||||
var pressure = (item.Value - valuatorClassInfo.Min) /
|
||||
(valuatorClassInfo.Max - valuatorClassInfo.Min);
|
||||
rawPointerPoint.Pressure = (float)pressure;
|
||||
}
|
||||
}
|
||||
|
||||
if (_pointerDevice.TiltXXIValuatorClassInfo is { } tiltXValuatorClassInfo)
|
||||
{
|
||||
if (item.Key == tiltXValuatorClassInfo.Number)
|
||||
{
|
||||
rawPointerPoint.XTilt = (float)item.Value;
|
||||
}
|
||||
}
|
||||
|
||||
if (_pointerDevice.TiltYXIValuatorClassInfo is { } tiltYValuatorClassInfo)
|
||||
{
|
||||
if (item.Key == tiltYValuatorClassInfo.Number)
|
||||
{
|
||||
rawPointerPoint.YTilt = (float)item.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal unsafe class ParsedDeviceEvent
|
||||
@@ -453,6 +550,7 @@ namespace Avalonia.X11
|
||||
rv |= RawInputModifiers.XButton2MouseButton;
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
@@ -487,6 +585,7 @@ namespace Avalonia.X11
|
||||
Emulated = ev->flags.HasAllFlags(XiDeviceEventFlags.XIPointerEmulated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal interface IXI2Client
|
||||
{
|
||||
@@ -494,6 +593,7 @@ namespace Avalonia.X11
|
||||
IInputRoot InputRoot { get; }
|
||||
void ScheduleXI2Input(RawInputEventArgs args);
|
||||
IMouseDevice MouseDevice { get; }
|
||||
IPenDevice PenDevice { get; }
|
||||
TouchDevice TouchDevice { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ namespace Avalonia.X11
|
||||
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 XiDeviceChangeReason Reason; /* Reason for the change */
|
||||
public int NumClasses;
|
||||
public XIAnyClassInfo** Classes; /* same as in XIDeviceInfo */
|
||||
}
|
||||
@@ -272,6 +272,12 @@ namespace Avalonia.X11
|
||||
XIPointerEmulated = (1 << 16)
|
||||
}
|
||||
|
||||
internal enum XiDeviceChangeReason : int
|
||||
{
|
||||
XISlaveSwitch = 1,
|
||||
XIDeviceChange = 2
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal unsafe struct XIEvent
|
||||
{
|
||||
|
||||
@@ -180,7 +180,7 @@ internal class RawEventGrouper : IDisposable
|
||||
{
|
||||
|
||||
last.IntermediatePoints ??= new Lazy<IReadOnlyList<RawPointerPoint>?>(s_getPooledListDelegate);
|
||||
((PooledList<RawPointerPoint>)last.IntermediatePoints.Value!).Add(new RawPointerPoint { Position = last.Position });
|
||||
((PooledList<RawPointerPoint>)last.IntermediatePoints.Value!).Add(new RawPointerPoint { Position = last.Position, Pressure = last.Point.Pressure, ContactRect = last.Point.ContactRect, Twist = last.Point.Twist, XTilt = last.Point.XTilt, YTilt = last.Point.YTilt });
|
||||
last.Position = current.Position;
|
||||
last.Timestamp = current.Timestamp;
|
||||
last.InputModifiers = current.InputModifiers;
|
||||
|
||||
Reference in New Issue
Block a user