forked from unoplatform/uno
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathX11PointerInputSource.cs
205 lines (169 loc) · 7.76 KB
/
X11PointerInputSource.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
using System;
using System.Runtime.CompilerServices;
using Windows.Devices.Input;
using Windows.Foundation;
using Windows.UI.Core;
using Windows.UI.Input;
using Uno.Foundation.Logging;
using Uno.UI.Hosting;
namespace Uno.WinUI.Runtime.Skia.X11;
internal partial class X11PointerInputSource : IUnoCorePointerInputSource
{
#pragma warning disable CS0067 // Some event are not raised on X11 ... yet!
public event TypedEventHandler<object, PointerEventArgs>? PointerCaptureLost;
public event TypedEventHandler<object, PointerEventArgs>? PointerEntered;
public event TypedEventHandler<object, PointerEventArgs>? PointerExited;
public event TypedEventHandler<object, PointerEventArgs>? PointerMoved;
public event TypedEventHandler<object, PointerEventArgs>? PointerPressed;
public event TypedEventHandler<object, PointerEventArgs>? PointerReleased;
public event TypedEventHandler<object, PointerEventArgs>? PointerWheelChanged;
// X11 doesn't have the concept of a canceled Pointer
public event TypedEventHandler<object, PointerEventArgs>? PointerCancelled; // Uno Only
#pragma warning restore CS0067
private readonly X11XamlRootHost _host;
private CoreCursor _pointerCursor;
public X11PointerInputSource(IXamlRootHost host)
{
if (host is not X11XamlRootHost)
{
throw new ArgumentException($"{nameof(host)} must be an X11 host instance");
}
_host = (X11XamlRootHost)host;
_host.SetPointerSource(this);
// Set this on startup in case a different global default was set beforehand
PointerCursor = new(CoreCursorType.Arrow, 0);
_pointerCursor = PointerCursor; // initialization is not needed, we're just keeping the compiler happy
}
[NotImplemented] public bool HasCapture => false;
public CoreCursor PointerCursor
{
get => _pointerCursor;
set
{
_pointerCursor = value;
// These will have the look of the DE cursor themes if they exist (instead of the ugly x11 defaults)
// using the XCURSOR extension. https://wiki.archlinux.org/title/Cursor_themes
var shape = value.Type switch
{
CoreCursorType.Arrow => CursorFontShape.XC_arrow,
CoreCursorType.Cross => CursorFontShape.XC_crosshair,
CoreCursorType.Hand => CursorFontShape.XC_hand2, // subjective: XC_hand2 looks better than XC_hand1, XFCE renders both the same way
CoreCursorType.Help => CursorFontShape.XC_question_arrow,
CoreCursorType.IBeam => CursorFontShape.XC_xterm,
CoreCursorType.SizeAll => CursorFontShape.XC_fleur,
CoreCursorType.SizeNortheastSouthwest => CursorFontShape.XC_sizing, // this is the wrong direction, but it's all we have
CoreCursorType.SizeNorthSouth => CursorFontShape.XC_sb_v_double_arrow, // this works better with XFCE than XC_double_arrow
CoreCursorType.SizeNorthwestSoutheast => CursorFontShape.XC_sizing,
CoreCursorType.SizeWestEast => CursorFontShape.XC_sb_h_double_arrow,
CoreCursorType.UniversalNo => CursorFontShape.XC_circle, // maybe XC_pirate or XC_X_cursor? XFCE renders XC_circle as a red stop sign
CoreCursorType.UpArrow => CursorFontShape.XC_sb_up_arrow,
CoreCursorType.Wait => CursorFontShape.XC_watch, // this works better with XFCE than XC_exchange
CoreCursorType.Person => CursorFontShape.XC_gumby, // sidenote: turns out this gumby is an ancient tv character
CoreCursorType.Pin => CursorFontShape.XC_dot, // eh, not really
_ => CursorFontShape.XC_arrow // including CoreCursorType.Custom
};
using var lockDiposable = X11Helper.XLock(_host.TopX11Window.Display);
var cursor = XLib.XCreateFontCursor(_host.TopX11Window.Display, shape);
_ = XLib.XDefineCursor(_host.TopX11Window.Display, _host.TopX11Window.Window, cursor);
_ = XLib.XFreeCursor(_host.TopX11Window.Display, cursor);
}
}
public Point PointerPosition => _mousePosition;
public void SetPointerCapture(PointerIdentifier pointer)
{
LogNotSupported();
// XGrabPointer will globally lock pointer actions to the window, preventing any interaction elsewhere.
// AFAICT, pointers are captured as long as a button is held.
// var mask =
// EventMask.ButtonPressMask |
// EventMask.ButtonReleaseMask |
// EventMask.PointerMotionMask |
// EventMask.PointerMotionHintMask |
// EventMask.Button1MotionMask |
// EventMask.Button2MotionMask |
// EventMask.Button3MotionMask |
// EventMask.Button4MotionMask |
// EventMask.Button5MotionMask |
// EventMask.ButtonMotionMask;
// XLib.XGrabPointer(_host.Display, _host.Window, false, mask,
// GrabMode.GrabModeSync, GrabMode.GrabModeSync, /* None */ IntPtr.Zero, /* None */ IntPtr.Zero, /* CurrentTime */ IntPtr.Zero);
}
public void ReleasePointerCapture(PointerIdentifier pointer)
{
LogNotSupported();
// XLib.XUngrabPointer(_host.Display, /* CurrentTime */ IntPtr.Zero);
}
public void ReleasePointerCapture() => LogNotSupported();
public void SetPointerCapture() => LogNotSupported();
private void RaisePointerMoved(PointerEventArgs args)
=> PointerMoved?.Invoke(this, args);
private void RaisePointerPressed(PointerEventArgs args)
=> PointerPressed?.Invoke(this, args);
private void RaisePointerReleased(PointerEventArgs args)
=> PointerReleased?.Invoke(this, args);
private void RaisePointerWheelChanged(PointerEventArgs args)
=> PointerWheelChanged?.Invoke(this, args);
private void RaisePointerExited(PointerEventArgs args)
=> PointerExited?.Invoke(this, args);
private void RaisePointerEntered(PointerEventArgs args)
=> PointerEntered?.Invoke(this, args);
private void LogNotSupported([CallerMemberName] string member = "")
{
if (this.Log().IsEnabled(LogLevel.Debug))
{
this.Log().Debug($"{member} not supported on Skia for X11.");
}
}
public void ProcessLeaveEvent(XCrossingEvent ev)
{
_mousePosition = new Point(ev.x, ev.y);
var point = CreatePointFromCurrentState(ev.time);
var modifiers = X11XamlRootHost.XModifierMaskToVirtualKeyModifiers(ev.state);
var args = new PointerEventArgs(point, modifiers);
CreatePointFromCurrentState(ev.time);
X11XamlRootHost.QueueAction(_host, () => RaisePointerExited(args));
}
public void ProcessEnterEvent(XCrossingEvent ev)
{
_mousePosition = new Point(ev.x, ev.y);
var args = CreatePointerEventArgsFromCurrentState(ev.time, ev.state);
X11XamlRootHost.QueueAction(_host, () => RaisePointerEntered(args));
}
private PointerEventArgs CreatePointerEventArgsFromCurrentState(IntPtr time, XModifierMask state)
{
var point = CreatePointFromCurrentState(time);
var modifiers = X11XamlRootHost.XModifierMaskToVirtualKeyModifiers(state);
return new PointerEventArgs(point, modifiers);
}
/// <summary>
/// Create a new PointerPoint from the current state of the PointerInputSource
/// </summary>
private PointerPoint CreatePointFromCurrentState(IntPtr time)
{
var properties = new PointerPointProperties
{
// TODO: fill this comprehensively like GTK's AsPointerArgs
IsLeftButtonPressed = (_pressedButtons & (1 << LEFT)) != 0,
IsMiddleButtonPressed = (_pressedButtons & (1 << MIDDLE)) != 0,
IsRightButtonPressed = (_pressedButtons & (1 << RIGHT)) != 0
};
var scale = ((IXamlRootHost)_host).RootElement?.XamlRoot is { } root
? root.RasterizationScale
: 1;
// Time is given in milliseconds since system boot
// This matches the format of WinUI. See also: https://github.com/unoplatform/uno/issues/14535
var point = new PointerPoint(
frameId: (uint)time, // UNO TODO: How should set the frame, timestamp may overflow.
timestamp: (ulong)(time * TimeSpan.TicksPerMillisecond),
PointerDevice.For(PointerDeviceType.Mouse),
0, // TODO: XInput
new Point(_mousePosition.X / scale, _mousePosition.Y / scale),
new Point(_mousePosition.X / scale, _mousePosition.Y / scale),
// TODO: is isInContact correct?
(_pressedButtons & 0b1111) != 0,
properties.SetUpdateKindFromPrevious(_previousPointerPointProperties)
);
_previousPointerPointProperties = properties;
return point;
}
}