-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathinput.cpp
More file actions
376 lines (333 loc) · 12.3 KB
/
Copy pathinput.cpp
File metadata and controls
376 lines (333 loc) · 12.3 KB
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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
#include "input.hpp"
#include <FL/Fl_Window.H>
#include <FL/x.H>
#include <assert.h>
#include <iostream>
#ifdef _WIN32
// clang-format off
#include <windows.h>
#include <hidusage.h>
// clang-format on
#else
#include <X11/Xlib.h>
#include <X11/extensions/XInput2.h>
#endif
#ifdef _WIN32
class RawMouseManager::Platform {
private:
friend class RawMouseManager;
HWND window;
public:
Platform(Fl_Window* window):
window(fl_xid(window)) {}
};
RawMouseManager::RawMouseManager(Fl_Window* window):
platform(std::make_unique<Platform>(window)),
window(window) {
RAWINPUTDEVICE device;
device.usUsagePage = HID_USAGE_PAGE_GENERIC;
device.usUsage = HID_USAGE_GENERIC_MOUSE;
device.dwFlags = RIDEV_INPUTSINK;
device.hwndTarget = platform->window;
// Not inside assert(): NDEBUG would drop the registration itself, and raw
// mouse input would silently stop working in exactly the builds we ship
if (!RegisterRawInputDevices(&device, 1, sizeof device)) {
std::cerr << "Error: Failed to register raw mouse input" << std::endl;
}
}
RawMouseManager::~RawMouseManager() {
unlock_mouse();
RAWINPUTDEVICE device;
device.usUsagePage = HID_USAGE_PAGE_GENERIC;
device.usUsage = HID_USAGE_GENERIC_MOUSE;
device.dwFlags = RIDEV_REMOVE;
device.hwndTarget = nullptr;
if (!RegisterRawInputDevices(&device, 1, sizeof device)) {
std::cerr << "Error: Failed to unregister raw mouse input" << std::endl;
}
}
void RawMouseManager::lock_mouse() {
window->cursor(FL_CURSOR_NONE);
RECT rect;
GetClientRect(platform->window, &rect);
POINT ul = {rect.left, rect.top};
POINT lr = {rect.right, rect.bottom};
ClientToScreen(platform->window, &ul);
ClientToScreen(platform->window, &lr);
RECT screen_rect = {ul.x + 1, ul.y + 1, lr.x - 2, lr.y - 2};
ClipCursor(&screen_rect);
mouse_locked = true;
}
void RawMouseManager::unlock_mouse() {
window->cursor(FL_CURSOR_DEFAULT);
ClipCursor(nullptr);
mouse_locked = false;
}
std::optional<RawMouseEvent> RawMouseManager::parse_event(void* event) {
if (mouse_locked) {
auto message = (MSG*) event;
if (message->message == WM_INPUT) {
UINT size;
if (GetRawInputData((HRAWINPUT) message->lParam, RID_INPUT, nullptr, &size, sizeof(RAWINPUTHEADER)) == 0) {
auto data = new BYTE[size];
if (GetRawInputData((HRAWINPUT) message->lParam, RID_INPUT, data, &size, sizeof(RAWINPUTHEADER)) == size) {
RAWINPUT* raw_event = (RAWINPUT*) data;
if (raw_event->header.dwType == RIM_TYPEMOUSE) {
RawMouseEvent ret;
RAWMOUSE raw_mouse_event = raw_event->data.mouse;
ret.x = raw_mouse_event.lLastX;
ret.y = raw_mouse_event.lLastY;
RECT rect;
GetClientRect(platform->window, &rect);
POINT center = {
rect.right / 2,
rect.bottom / 2,
};
ClientToScreen(platform->window, ¢er);
SetCursorPos(center.x, center.y);
return ret;
}
}
delete[] data;
}
}
}
return std::nullopt;
}
class KeyboardGrabManager::Platform {
private:
friend class KeyboardGrabManager;
static HWND window;
static HHOOK hook;
static bool keyboard_grabbed;
static void post_key(WORD vkCode, UINT type, DWORD flags) {
UINT scancode = MapVirtualKey(vkCode, MAPVK_VK_TO_VSC);
LPARAM lParam = 1 | (scancode << 16); // Repeat count = 1
if (flags & LLKHF_EXTENDED) lParam |= (1U << 24);
// Bit 29 is the context code, which is how a window is told Alt was held.
// FLTK reads it directly to set FL_ALT, so a synthesised WM_SYSKEYDOWN
// without it arrives claiming the Alt combination was not one
if (flags & LLKHF_ALTDOWN) lParam |= (1U << 29);
if (type == WM_KEYUP || type == WM_SYSKEYUP) {
lParam |= (1U << 31); // Transition (up)
lParam |= (1U << 30); // Previous state
}
PostMessage(window, type, vkCode, lParam);
}
// The hook is installed globally, so being grabbed is not enough to justify
// swallowing a key: the pointer can sit over our window while another
// application is focused, and eating the shell's shortcuts desktop-wide would
// be far worse now that more than four keys are taken. GA_ROOT so that a
// focused child window still counts as ours
static bool foreground_is_ours() {
HWND foreground = GetForegroundWindow();
return foreground && window && GetAncestor(foreground, GA_ROOT) == GetAncestor(window, GA_ROOT);
}
static LRESULT CALLBACK hook_cb(int nCode, WPARAM wParam, LPARAM lParam) {
if (keyboard_grabbed &&
nCode == HC_ACTION &&
(wParam == WM_KEYDOWN ||
wParam == WM_SYSKEYDOWN ||
wParam == WM_KEYUP ||
wParam == WM_SYSKEYUP) &&
foreground_is_ours()) {
auto event_info = (KBDLLHOOKSTRUCT*) lParam;
switch (event_info->vkCode) {
case VK_LWIN:
case VK_RWIN:
case VK_TAB:
case VK_SNAPSHOT:
post_key(event_info->vkCode, wParam, event_info->flags);
return 1;
case VK_MENU:
case VK_LMENU:
case VK_RMENU:
// Alt itself is left on the ordinary path, which already delivers
// it and is known to work. Swallowing it here would gain nothing
// and would risk the window never learning Alt is down
break;
default:
// Windows classifies a key as a system key exactly when Alt is
// held (and for F10), which is precisely where window manager
// bindings live: GlazeWM's Alt+1..9, Alt+Enter, Alt+arrows.
// Taking the whole class beats enumerating combinations we would
// otherwise keep discovering one complaint at a time
if (wParam == WM_SYSKEYDOWN || wParam == WM_SYSKEYUP) {
post_key(event_info->vkCode, wParam, event_info->flags);
return 1;
}
break;
}
}
return CallNextHookEx(hook, nCode, wParam, lParam);
}
public:
Platform(Fl_Window* window) {
assert(this->window == nullptr);
this->window = fl_xid(window);
assert(hook == nullptr);
hook = SetWindowsHookEx(WH_KEYBOARD_LL, hook_cb, GetModuleHandle(NULL), 0);
}
Platform(const Platform&) = delete;
Platform(Platform&&) = delete;
Platform& operator=(const Platform&) = delete;
Platform& operator=(Platform&&) = delete;
~Platform() {
if (hook) {
UnhookWindowsHookEx(hook);
hook = nullptr;
}
if (window) window = nullptr;
}
};
HWND KeyboardGrabManager::Platform::window = nullptr;
HHOOK KeyboardGrabManager::Platform::hook = nullptr;
bool KeyboardGrabManager::Platform::keyboard_grabbed = false;
KeyboardGrabManager::KeyboardGrabManager(Fl_Window* window):
platform(std::make_unique<Platform>(window)) {}
KeyboardGrabManager::~KeyboardGrabManager() {
ungrab_keyboard();
}
void KeyboardGrabManager::grab_keyboard() {
keyboard_grabbed = platform->keyboard_grabbed = true;
}
void KeyboardGrabManager::ungrab_keyboard() {
keyboard_grabbed = platform->keyboard_grabbed = false;
}
#else
class RawMouseManager::Platform {
private:
friend class RawMouseManager;
Window window;
Display* display;
int xi_opcode = -1; // Never matches a real event, so a failed query just disables raw input
public:
Platform(Fl_Window* window, Display* display = fl_x11_display()):
window(fl_xid(window)),
display(display) {
int event;
int error;
// Not inside assert(): NDEBUG would drop the query and leave xi_opcode unset
if (XQueryExtension(display, "XInputExtension", &xi_opcode, &event, &error) != True) {
std::cerr << "Error: XInput extension is unavailable, raw mouse input is disabled" << std::endl;
}
}
};
RawMouseManager::RawMouseManager(Fl_Window* window):
platform(std::make_unique<Platform>(window)),
window(window) {
XIEventMask masks[1];
unsigned char mask[(XI_LASTEVENT + 7) / 8] = {0};
masks[0].deviceid = XIAllMasterDevices;
masks[0].mask_len = sizeof mask;
masks[0].mask = mask;
XISetMask(mask, XI_RawMotion);
XISelectEvents(platform->display, DefaultRootWindow(platform->display), masks, 1);
XFlush(platform->display);
}
RawMouseManager::~RawMouseManager() {
unlock_mouse();
XIEventMask masks[1];
unsigned char mask[] = {0};
masks[0].deviceid = XIAllMasterDevices;
masks[0].mask_len = sizeof mask;
masks[0].mask = mask;
XISelectEvents(platform->display, DefaultRootWindow(platform->display), masks, 1);
XFlush(platform->display);
}
void RawMouseManager::lock_mouse() {
window->cursor(FL_CURSOR_NONE);
if (!mouse_locked) {
XGrabPointer(platform->display,
platform->window,
False,
ButtonPressMask |
ButtonReleaseMask |
EnterWindowMask |
LeaveWindowMask |
PointerMotionMask |
PointerMotionHintMask |
Button1MotionMask |
Button2MotionMask |
Button3MotionMask |
Button4MotionMask |
Button5MotionMask |
ButtonMotionMask |
KeymapStateMask,
GrabModeAsync,
GrabModeAsync,
platform->window,
None,
CurrentTime);
mouse_locked = true;
}
}
void RawMouseManager::unlock_mouse() {
window->cursor(FL_CURSOR_DEFAULT);
if (mouse_locked) {
XUngrabPointer(platform->display, CurrentTime);
mouse_locked = false;
}
}
std::optional<RawMouseEvent> RawMouseManager::parse_event(void* event) {
if (mouse_locked) {
auto x11_event = (XEvent*) event;
if (XGenericEventCookie* cookie = &x11_event->xcookie;
cookie->type == GenericEvent &&
cookie->extension == platform->xi_opcode &&
XGetEventData(platform->display, cookie)) {
if (cookie->evtype == XI_RawMotion) {
RawMouseEvent ret;
XIRawEvent* raw_event = (XIRawEvent*) cookie->data;
double* value = raw_event->raw_values;
for (int i = 0; i < raw_event->valuators.mask_len * 8; ++i) {
if (XIMaskIsSet(raw_event->valuators.mask, i)) {
if (i == 0) {
ret.x = *value;
} else if (i == 1) {
ret.y = *value;
} else {
break;
}
++value;
}
}
XFreeEventData(platform->display, cookie);
return ret;
}
XFreeEventData(platform->display, cookie);
}
}
return std::nullopt;
}
class KeyboardGrabManager::Platform {
private:
friend class KeyboardGrabManager;
Window window;
Display* display;
public:
Platform(Fl_Window* window, Display* display = fl_x11_display()):
window(fl_xid(window)),
display(display) {}
};
KeyboardGrabManager::KeyboardGrabManager(Fl_Window* window):
platform(std::make_unique<Platform>(window)) {}
KeyboardGrabManager::~KeyboardGrabManager() {
ungrab_keyboard();
}
void KeyboardGrabManager::grab_keyboard() {
if (!keyboard_grabbed) {
if (int result = XGrabKeyboard(platform->display, platform->window, False, GrabModeAsync, GrabModeAsync, CurrentTime); result != GrabSuccess) {
std::cerr << "Error: Failed to grab keyboard: " << result << std::endl;
} else {
keyboard_grabbed = true;
}
}
}
void KeyboardGrabManager::ungrab_keyboard() {
if (keyboard_grabbed) {
XUngrabKeyboard(platform->display, CurrentTime);
keyboard_grabbed = false;
}
}
#endif