@mukea/uiohook-napi 1.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +77 -0
- package/binding.gyp +85 -0
- package/dist/index.d.ts +194 -0
- package/dist/index.js +206 -0
- package/dist/index.js.map +1 -0
- package/dist/prebuild-test-noop.d.ts +0 -0
- package/dist/prebuild-test-noop.js +3 -0
- package/dist/prebuild-test-noop.js.map +1 -0
- package/libuiohook/include/uiohook.h +457 -0
- package/libuiohook/src/darwin/input_helper.c +535 -0
- package/libuiohook/src/darwin/input_helper.h +203 -0
- package/libuiohook/src/darwin/input_hook.c +1436 -0
- package/libuiohook/src/darwin/post_event.c +303 -0
- package/libuiohook/src/darwin/system_properties.c +479 -0
- package/libuiohook/src/logger.c +40 -0
- package/libuiohook/src/logger.h +32 -0
- package/libuiohook/src/windows/input_helper.c +913 -0
- package/libuiohook/src/windows/input_helper.h +146 -0
- package/libuiohook/src/windows/input_hook.c +722 -0
- package/libuiohook/src/windows/post_event.c +248 -0
- package/libuiohook/src/windows/system_properties.c +231 -0
- package/libuiohook/src/x11/input_helper.c +1846 -0
- package/libuiohook/src/x11/input_helper.h +108 -0
- package/libuiohook/src/x11/input_hook.c +1116 -0
- package/libuiohook/src/x11/post_event.c +427 -0
- package/libuiohook/src/x11/system_properties.c +494 -0
- package/package.json +60 -0
- package/prebuilds/darwin/darwin-arm64/@mukea+uiohook-napi.node +0 -0
- package/prebuilds/darwin/darwin-x64/@mukea+uiohook-napi.node +0 -0
- package/prebuilds/linux/linux-arm64/@mukea+uiohook-napi.node +0 -0
- package/prebuilds/linux/linux-x64/@mukea+uiohook-napi.node +0 -0
- package/prebuilds/windows/win32-x64/@mukea+uiohook-napi.node +0 -0
- package/src/lib/addon.c +337 -0
- package/src/lib/napi_helpers.c +51 -0
- package/src/lib/napi_helpers.h +53 -0
- package/src/lib/uiohook_worker.c +200 -0
- package/src/lib/uiohook_worker.h +12 -0
|
@@ -0,0 +1,1116 @@
|
|
|
1
|
+
/* libUIOHook: Cross-platform keyboard and mouse hooking from userland.
|
|
2
|
+
* Copyright (C) 2006-2023 Alexander Barker. All Rights Reserved.
|
|
3
|
+
* https://github.com/kwhat/libuiohook/
|
|
4
|
+
*
|
|
5
|
+
* libUIOHook is free software: you can redistribute it and/or modify
|
|
6
|
+
* it under the terms of the GNU Lesser General Public License as published
|
|
7
|
+
* by the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
* (at your option) any later version.
|
|
9
|
+
*
|
|
10
|
+
* libUIOHook is distributed in the hope that it will be useful,
|
|
11
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
* GNU General Public License for more details.
|
|
14
|
+
*
|
|
15
|
+
* You should have received a copy of the GNU Lesser General Public License
|
|
16
|
+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
#include <inttypes.h>
|
|
20
|
+
#include <limits.h>
|
|
21
|
+
|
|
22
|
+
#ifdef USE_XRECORD_ASYNC
|
|
23
|
+
#include <pthread.h>
|
|
24
|
+
#endif
|
|
25
|
+
|
|
26
|
+
#include <stdint.h>
|
|
27
|
+
#include <uiohook.h>
|
|
28
|
+
|
|
29
|
+
#ifdef USE_XKB_COMMON
|
|
30
|
+
#include <xcb/xkb.h>
|
|
31
|
+
#else
|
|
32
|
+
#include <X11/XKBlib.h>
|
|
33
|
+
#endif
|
|
34
|
+
|
|
35
|
+
#include <X11/keysym.h>
|
|
36
|
+
#include <X11/Xlibint.h>
|
|
37
|
+
#include <X11/Xlib.h>
|
|
38
|
+
#include <X11/extensions/record.h>
|
|
39
|
+
|
|
40
|
+
#if defined(USE_XINERAMA) && !defined(USE_XRANDR)
|
|
41
|
+
#include <X11/extensions/Xinerama.h>
|
|
42
|
+
#elif defined(USE_XRANDR)
|
|
43
|
+
#include <X11/extensions/Xrandr.h>
|
|
44
|
+
#else
|
|
45
|
+
// TODO We may need to fallback to the xf86vm extension for things like TwinView.
|
|
46
|
+
#pragma message("*** Warning: Xinerama or XRandR support is required to produce cross-platform mouse coordinates for multi-head configurations!")
|
|
47
|
+
#pragma message("... Assuming single-head display.")
|
|
48
|
+
#endif
|
|
49
|
+
|
|
50
|
+
#include "logger.h"
|
|
51
|
+
#include "input_helper.h"
|
|
52
|
+
|
|
53
|
+
// Thread and hook handles.
|
|
54
|
+
#ifdef USE_XRECORD_ASYNC
|
|
55
|
+
static bool running;
|
|
56
|
+
|
|
57
|
+
static pthread_cond_t hook_xrecord_cond = PTHREAD_COND_INITIALIZER;
|
|
58
|
+
static pthread_mutex_t hook_xrecord_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
59
|
+
#endif
|
|
60
|
+
|
|
61
|
+
typedef struct _hook_info {
|
|
62
|
+
struct _data {
|
|
63
|
+
Display *display;
|
|
64
|
+
XRecordRange *range;
|
|
65
|
+
} data;
|
|
66
|
+
struct _ctrl {
|
|
67
|
+
Display *display;
|
|
68
|
+
XRecordContext context;
|
|
69
|
+
} ctrl;
|
|
70
|
+
struct _input {
|
|
71
|
+
#ifdef USE_XKB_COMMON
|
|
72
|
+
xcb_connection_t *connection;
|
|
73
|
+
struct xkb_context *context;
|
|
74
|
+
#endif
|
|
75
|
+
uint16_t mask;
|
|
76
|
+
struct _mouse {
|
|
77
|
+
bool is_dragged;
|
|
78
|
+
struct _click {
|
|
79
|
+
unsigned short int count;
|
|
80
|
+
long int time;
|
|
81
|
+
unsigned short int button;
|
|
82
|
+
} click;
|
|
83
|
+
} mouse;
|
|
84
|
+
} input;
|
|
85
|
+
} hook_info;
|
|
86
|
+
static hook_info *hook;
|
|
87
|
+
|
|
88
|
+
// For this struct, refer to libxnee, requires Xlibint.h
|
|
89
|
+
typedef union {
|
|
90
|
+
unsigned char type;
|
|
91
|
+
xEvent event;
|
|
92
|
+
xResourceReq req;
|
|
93
|
+
xGenericReply reply;
|
|
94
|
+
xError error;
|
|
95
|
+
xConnSetupPrefix setup;
|
|
96
|
+
} XRecordDatum;
|
|
97
|
+
|
|
98
|
+
#if defined(USE_XKB_COMMON)
|
|
99
|
+
static struct xkb_state *state = NULL;
|
|
100
|
+
#endif
|
|
101
|
+
|
|
102
|
+
// Virtual event pointer.
|
|
103
|
+
static uiohook_event event;
|
|
104
|
+
|
|
105
|
+
// Event dispatch callback.
|
|
106
|
+
static dispatcher_t dispatcher = NULL;
|
|
107
|
+
|
|
108
|
+
UIOHOOK_API void hook_set_dispatch_proc(dispatcher_t dispatch_proc) {
|
|
109
|
+
logger(LOG_LEVEL_DEBUG, "%s [%u]: Setting new dispatch callback to %#p.\n",
|
|
110
|
+
__FUNCTION__, __LINE__, dispatch_proc);
|
|
111
|
+
|
|
112
|
+
dispatcher = dispatch_proc;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Send out an event if a dispatcher was set.
|
|
116
|
+
static inline void dispatch_event(uiohook_event *const event) {
|
|
117
|
+
if (dispatcher != NULL) {
|
|
118
|
+
logger(LOG_LEVEL_DEBUG, "%s [%u]: Dispatching event type %u.\n",
|
|
119
|
+
__FUNCTION__, __LINE__, event->type);
|
|
120
|
+
|
|
121
|
+
dispatcher(event);
|
|
122
|
+
} else {
|
|
123
|
+
logger(LOG_LEVEL_WARN, "%s [%u]: No dispatch callback set!\n",
|
|
124
|
+
__FUNCTION__, __LINE__);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Set the native modifier mask for future events.
|
|
129
|
+
static inline void set_modifier_mask(uint16_t mask) {
|
|
130
|
+
hook->input.mask |= mask;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Unset the native modifier mask for future events.
|
|
134
|
+
static inline void unset_modifier_mask(uint16_t mask) {
|
|
135
|
+
hook->input.mask &= ~mask;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Get the current native modifier mask state.
|
|
139
|
+
static inline uint16_t get_modifiers() {
|
|
140
|
+
return hook->input.mask;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Initialize the modifier lock masks.
|
|
144
|
+
static void initialize_locks() {
|
|
145
|
+
#ifdef USE_XKB_COMMON
|
|
146
|
+
if (xkb_state_led_name_is_active(state, XKB_LED_NAME_CAPS)) {
|
|
147
|
+
set_modifier_mask(MASK_CAPS_LOCK);
|
|
148
|
+
} else {
|
|
149
|
+
unset_modifier_mask(MASK_CAPS_LOCK);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (xkb_state_led_name_is_active(state, XKB_LED_NAME_NUM)) {
|
|
153
|
+
set_modifier_mask(MASK_NUM_LOCK);
|
|
154
|
+
} else {
|
|
155
|
+
unset_modifier_mask(MASK_NUM_LOCK);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (xkb_state_led_name_is_active(state, XKB_LED_NAME_SCROLL)) {
|
|
159
|
+
set_modifier_mask(MASK_SCROLL_LOCK);
|
|
160
|
+
} else {
|
|
161
|
+
unset_modifier_mask(MASK_SCROLL_LOCK);
|
|
162
|
+
}
|
|
163
|
+
#else
|
|
164
|
+
unsigned int led_mask = 0x00;
|
|
165
|
+
if (XkbGetIndicatorState(hook->ctrl.display, XkbUseCoreKbd, &led_mask) == Success) {
|
|
166
|
+
if (led_mask & 0x01) {
|
|
167
|
+
set_modifier_mask(MASK_CAPS_LOCK);
|
|
168
|
+
} else {
|
|
169
|
+
unset_modifier_mask(MASK_CAPS_LOCK);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (led_mask & 0x02) {
|
|
173
|
+
set_modifier_mask(MASK_NUM_LOCK);
|
|
174
|
+
} else {
|
|
175
|
+
unset_modifier_mask(MASK_NUM_LOCK);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (led_mask & 0x04) {
|
|
179
|
+
set_modifier_mask(MASK_SCROLL_LOCK);
|
|
180
|
+
} else {
|
|
181
|
+
unset_modifier_mask(MASK_SCROLL_LOCK);
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
logger(LOG_LEVEL_WARN, "%s [%u]: XkbGetIndicatorState failed to get current led mask!\n",
|
|
185
|
+
__FUNCTION__, __LINE__);
|
|
186
|
+
}
|
|
187
|
+
#endif
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Initialize the modifier mask to the current modifiers.
|
|
191
|
+
static void initialize_modifiers() {
|
|
192
|
+
hook->input.mask = 0x0000;
|
|
193
|
+
|
|
194
|
+
KeyCode keycode;
|
|
195
|
+
char keymap[32];
|
|
196
|
+
XQueryKeymap(hook->ctrl.display, keymap);
|
|
197
|
+
|
|
198
|
+
Window unused_win;
|
|
199
|
+
int unused_int;
|
|
200
|
+
unsigned int mask;
|
|
201
|
+
if (XQueryPointer(hook->ctrl.display, DefaultRootWindow(hook->ctrl.display), &unused_win, &unused_win, &unused_int, &unused_int, &unused_int, &unused_int, &mask)) {
|
|
202
|
+
if (mask & ShiftMask) {
|
|
203
|
+
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Shift_L);
|
|
204
|
+
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_SHIFT_L); }
|
|
205
|
+
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Shift_R);
|
|
206
|
+
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_SHIFT_R); }
|
|
207
|
+
}
|
|
208
|
+
if (mask & ControlMask) {
|
|
209
|
+
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Control_L);
|
|
210
|
+
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_CTRL_L); }
|
|
211
|
+
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Control_R);
|
|
212
|
+
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_CTRL_R); }
|
|
213
|
+
}
|
|
214
|
+
if (mask & Mod1Mask) {
|
|
215
|
+
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Alt_L);
|
|
216
|
+
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_ALT_L); }
|
|
217
|
+
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Alt_R);
|
|
218
|
+
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_ALT_R); }
|
|
219
|
+
}
|
|
220
|
+
if (mask & Mod4Mask) {
|
|
221
|
+
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Super_L);
|
|
222
|
+
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_META_L); }
|
|
223
|
+
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Super_R);
|
|
224
|
+
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_META_R); }
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (mask & Button1Mask) { set_modifier_mask(MASK_BUTTON1); }
|
|
228
|
+
if (mask & Button2Mask) { set_modifier_mask(MASK_BUTTON2); }
|
|
229
|
+
if (mask & Button3Mask) { set_modifier_mask(MASK_BUTTON3); }
|
|
230
|
+
if (mask & Button4Mask) { set_modifier_mask(MASK_BUTTON4); }
|
|
231
|
+
if (mask & Button5Mask) { set_modifier_mask(MASK_BUTTON5); }
|
|
232
|
+
} else {
|
|
233
|
+
logger(LOG_LEVEL_WARN, "%s [%u]: XQueryPointer failed to get current modifiers!\n",
|
|
234
|
+
__FUNCTION__, __LINE__);
|
|
235
|
+
|
|
236
|
+
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Shift_L);
|
|
237
|
+
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_SHIFT_L); }
|
|
238
|
+
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Shift_R);
|
|
239
|
+
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_SHIFT_R); }
|
|
240
|
+
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Control_L);
|
|
241
|
+
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_CTRL_L); }
|
|
242
|
+
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Control_R);
|
|
243
|
+
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_CTRL_R); }
|
|
244
|
+
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Alt_L);
|
|
245
|
+
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_ALT_L); }
|
|
246
|
+
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Alt_R);
|
|
247
|
+
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_ALT_R); }
|
|
248
|
+
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Super_L);
|
|
249
|
+
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_META_L); }
|
|
250
|
+
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Super_R);
|
|
251
|
+
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_META_R); }
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
initialize_locks();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
void hook_event_proc(XPointer closeure, XRecordInterceptData *recorded_data) {
|
|
258
|
+
uint64_t timestamp = (uint64_t) recorded_data->server_time;
|
|
259
|
+
|
|
260
|
+
if (recorded_data->category == XRecordStartOfData) {
|
|
261
|
+
// Initialize native input helper functions.
|
|
262
|
+
load_input_helper();
|
|
263
|
+
|
|
264
|
+
// Populate the hook start event.
|
|
265
|
+
event.time = timestamp;
|
|
266
|
+
event.reserved = 0x00;
|
|
267
|
+
|
|
268
|
+
event.type = EVENT_HOOK_ENABLED;
|
|
269
|
+
event.mask = 0x00;
|
|
270
|
+
|
|
271
|
+
// Fire the hook start event.
|
|
272
|
+
dispatch_event(&event);
|
|
273
|
+
} else if (recorded_data->category == XRecordEndOfData) {
|
|
274
|
+
// Populate the hook stop event.
|
|
275
|
+
event.time = timestamp;
|
|
276
|
+
event.reserved = 0x00;
|
|
277
|
+
|
|
278
|
+
event.type = EVENT_HOOK_DISABLED;
|
|
279
|
+
event.mask = 0x00;
|
|
280
|
+
|
|
281
|
+
// Fire the hook stop event.
|
|
282
|
+
dispatch_event(&event);
|
|
283
|
+
|
|
284
|
+
// Deinitialize native input helper functions.
|
|
285
|
+
unload_input_helper();
|
|
286
|
+
} else if (recorded_data->category == XRecordFromServer || recorded_data->category == XRecordFromClient) {
|
|
287
|
+
// Get XRecord data.
|
|
288
|
+
XRecordDatum *data = (XRecordDatum *) recorded_data->data;
|
|
289
|
+
|
|
290
|
+
if (data->type == KeyPress) {
|
|
291
|
+
// The X11 KeyCode associated with this event.
|
|
292
|
+
KeyCode keycode = (KeyCode) data->event.u.u.detail;
|
|
293
|
+
KeySym keysym = 0x00;
|
|
294
|
+
#if defined(USE_XKB_COMMON)
|
|
295
|
+
if (state != NULL) {
|
|
296
|
+
keysym = xkb_state_key_get_one_sym(state, keycode);
|
|
297
|
+
}
|
|
298
|
+
#else
|
|
299
|
+
keysym = keycode_to_keysym(keycode, data->event.u.keyButtonPointer.state);
|
|
300
|
+
#endif
|
|
301
|
+
|
|
302
|
+
// Check to make sure the key is printable.
|
|
303
|
+
uint16_t buffer[2];
|
|
304
|
+
size_t count = 0;
|
|
305
|
+
#ifdef USE_XKB_COMMON
|
|
306
|
+
if (state != NULL) {
|
|
307
|
+
count = keycode_to_unicode(state, keycode, buffer, sizeof(buffer) / sizeof(uint16_t));
|
|
308
|
+
}
|
|
309
|
+
#else
|
|
310
|
+
count = keysym_to_unicode(keysym, buffer, sizeof(buffer) / sizeof(uint16_t));
|
|
311
|
+
#endif
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
unsigned short int scancode = keycode_to_scancode(keycode);
|
|
315
|
+
|
|
316
|
+
// TODO If you have a better suggestion for this ugly, let me know.
|
|
317
|
+
if (scancode == VC_SHIFT_L) { set_modifier_mask(MASK_SHIFT_L); }
|
|
318
|
+
else if (scancode == VC_SHIFT_R) { set_modifier_mask(MASK_SHIFT_R); }
|
|
319
|
+
else if (scancode == VC_CONTROL_L) { set_modifier_mask(MASK_CTRL_L); }
|
|
320
|
+
else if (scancode == VC_CONTROL_R) { set_modifier_mask(MASK_CTRL_R); }
|
|
321
|
+
else if (scancode == VC_ALT_L) { set_modifier_mask(MASK_ALT_L); }
|
|
322
|
+
else if (scancode == VC_ALT_R) { set_modifier_mask(MASK_ALT_R); }
|
|
323
|
+
else if (scancode == VC_META_L) { set_modifier_mask(MASK_META_L); }
|
|
324
|
+
else if (scancode == VC_META_R) { set_modifier_mask(MASK_META_R); }
|
|
325
|
+
#ifdef USE_XKB_COMMON
|
|
326
|
+
xkb_state_update_key(state, keycode, XKB_KEY_DOWN);
|
|
327
|
+
#endif
|
|
328
|
+
initialize_locks();
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
if ((get_modifiers() & MASK_NUM_LOCK) == 0) {
|
|
332
|
+
switch (scancode) {
|
|
333
|
+
case VC_KP_SEPARATOR:
|
|
334
|
+
case VC_KP_1:
|
|
335
|
+
case VC_KP_2:
|
|
336
|
+
case VC_KP_3:
|
|
337
|
+
case VC_KP_4:
|
|
338
|
+
case VC_KP_5:
|
|
339
|
+
case VC_KP_6:
|
|
340
|
+
case VC_KP_7:
|
|
341
|
+
case VC_KP_8:
|
|
342
|
+
case VC_KP_0:
|
|
343
|
+
case VC_KP_9:
|
|
344
|
+
scancode |= 0xEE00;
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Populate key pressed event.
|
|
350
|
+
event.time = timestamp;
|
|
351
|
+
event.reserved = 0x00;
|
|
352
|
+
|
|
353
|
+
event.type = EVENT_KEY_PRESSED;
|
|
354
|
+
event.mask = get_modifiers();
|
|
355
|
+
|
|
356
|
+
event.data.keyboard.keycode = scancode;
|
|
357
|
+
event.data.keyboard.rawcode = keysym;
|
|
358
|
+
event.data.keyboard.keychar = CHAR_UNDEFINED;
|
|
359
|
+
|
|
360
|
+
logger(LOG_LEVEL_DEBUG, "%s [%u]: Key %#X pressed. (%#X)\n",
|
|
361
|
+
__FUNCTION__, __LINE__, event.data.keyboard.keycode, event.data.keyboard.rawcode);
|
|
362
|
+
|
|
363
|
+
// Fire key pressed event.
|
|
364
|
+
dispatch_event(&event);
|
|
365
|
+
|
|
366
|
+
// If the pressed event was not consumed...
|
|
367
|
+
if (event.reserved ^ 0x01) {
|
|
368
|
+
for (unsigned int i = 0; i < count; i++) {
|
|
369
|
+
// Populate key typed event.
|
|
370
|
+
event.time = timestamp;
|
|
371
|
+
event.reserved = 0x00;
|
|
372
|
+
|
|
373
|
+
event.type = EVENT_KEY_TYPED;
|
|
374
|
+
event.mask = get_modifiers();
|
|
375
|
+
|
|
376
|
+
event.data.keyboard.keycode = VC_UNDEFINED;
|
|
377
|
+
event.data.keyboard.rawcode = keysym;
|
|
378
|
+
event.data.keyboard.keychar = buffer[i];
|
|
379
|
+
|
|
380
|
+
logger(LOG_LEVEL_DEBUG, "%s [%u]: Key %#X typed. (%lc)\n",
|
|
381
|
+
__FUNCTION__, __LINE__, event.data.keyboard.keycode, (uint16_t) event.data.keyboard.keychar);
|
|
382
|
+
|
|
383
|
+
// Fire key typed event.
|
|
384
|
+
dispatch_event(&event);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
} else if (data->type == KeyRelease) {
|
|
388
|
+
// The X11 KeyCode associated with this event.
|
|
389
|
+
KeyCode keycode = (KeyCode) data->event.u.u.detail;
|
|
390
|
+
KeySym keysym = 0x00;
|
|
391
|
+
#ifdef USE_XKB_COMMON
|
|
392
|
+
if (state != NULL) {
|
|
393
|
+
keysym = xkb_state_key_get_one_sym(state, keycode);
|
|
394
|
+
}
|
|
395
|
+
#else
|
|
396
|
+
keysym = keycode_to_keysym(keycode, data->event.u.keyButtonPointer.state);
|
|
397
|
+
#endif
|
|
398
|
+
|
|
399
|
+
// Check to make sure the key is printable.
|
|
400
|
+
uint16_t buffer[2];
|
|
401
|
+
#ifdef USE_XKB_COMMON
|
|
402
|
+
if (state != NULL) {
|
|
403
|
+
keycode_to_unicode(state, keycode, buffer, sizeof(buffer) / sizeof(uint16_t));
|
|
404
|
+
}
|
|
405
|
+
#else
|
|
406
|
+
keysym_to_unicode(keysym, buffer, sizeof(buffer) / sizeof(uint16_t));
|
|
407
|
+
#endif
|
|
408
|
+
|
|
409
|
+
unsigned short int scancode = keycode_to_scancode(keycode);
|
|
410
|
+
|
|
411
|
+
// TODO If you have a better suggestion for this ugly, let me know.
|
|
412
|
+
if (scancode == VC_SHIFT_L) { unset_modifier_mask(MASK_SHIFT_L); }
|
|
413
|
+
else if (scancode == VC_SHIFT_R) { unset_modifier_mask(MASK_SHIFT_R); }
|
|
414
|
+
else if (scancode == VC_CONTROL_L) { unset_modifier_mask(MASK_CTRL_L); }
|
|
415
|
+
else if (scancode == VC_CONTROL_R) { unset_modifier_mask(MASK_CTRL_R); }
|
|
416
|
+
else if (scancode == VC_ALT_L) { unset_modifier_mask(MASK_ALT_L); }
|
|
417
|
+
else if (scancode == VC_ALT_R) { unset_modifier_mask(MASK_ALT_R); }
|
|
418
|
+
else if (scancode == VC_META_L) { unset_modifier_mask(MASK_META_L); }
|
|
419
|
+
else if (scancode == VC_META_R) { unset_modifier_mask(MASK_META_R); }
|
|
420
|
+
#ifdef USE_XKB_COMMON
|
|
421
|
+
xkb_state_update_key(state, keycode, XKB_KEY_UP);
|
|
422
|
+
#endif
|
|
423
|
+
initialize_locks();
|
|
424
|
+
|
|
425
|
+
if ((get_modifiers() & MASK_NUM_LOCK) == 0) {
|
|
426
|
+
switch (scancode) {
|
|
427
|
+
case VC_KP_SEPARATOR:
|
|
428
|
+
case VC_KP_1:
|
|
429
|
+
case VC_KP_2:
|
|
430
|
+
case VC_KP_3:
|
|
431
|
+
case VC_KP_4:
|
|
432
|
+
case VC_KP_5:
|
|
433
|
+
case VC_KP_6:
|
|
434
|
+
case VC_KP_7:
|
|
435
|
+
case VC_KP_8:
|
|
436
|
+
case VC_KP_0:
|
|
437
|
+
case VC_KP_9:
|
|
438
|
+
scancode |= 0xEE00;
|
|
439
|
+
break;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Populate key released event.
|
|
444
|
+
event.time = timestamp;
|
|
445
|
+
event.reserved = 0x00;
|
|
446
|
+
|
|
447
|
+
event.type = EVENT_KEY_RELEASED;
|
|
448
|
+
event.mask = get_modifiers();
|
|
449
|
+
|
|
450
|
+
event.data.keyboard.keycode = scancode;
|
|
451
|
+
event.data.keyboard.rawcode = keysym;
|
|
452
|
+
event.data.keyboard.keychar = CHAR_UNDEFINED;
|
|
453
|
+
|
|
454
|
+
logger(LOG_LEVEL_DEBUG, "%s [%u]: Key %#X released. (%#X)\n",
|
|
455
|
+
__FUNCTION__, __LINE__, event.data.keyboard.keycode, event.data.keyboard.rawcode);
|
|
456
|
+
|
|
457
|
+
// Fire key released event.
|
|
458
|
+
dispatch_event(&event);
|
|
459
|
+
} else if (data->type == ButtonPress) {
|
|
460
|
+
unsigned int map_button = button_map_lookup(data->event.u.u.detail);
|
|
461
|
+
|
|
462
|
+
// X11 handles wheel events as button events.
|
|
463
|
+
if (map_button == WheelUp || map_button == WheelDown
|
|
464
|
+
|| map_button == WheelLeft || map_button == WheelRight) {
|
|
465
|
+
|
|
466
|
+
// Reset the click count and previous button.
|
|
467
|
+
hook->input.mouse.click.count = 1;
|
|
468
|
+
hook->input.mouse.click.button = MOUSE_NOBUTTON;
|
|
469
|
+
|
|
470
|
+
/* Scroll wheel release events.
|
|
471
|
+
* Scroll type: WHEEL_UNIT_SCROLL
|
|
472
|
+
* Scroll amount: 3 unit increments per notch
|
|
473
|
+
* Units to scroll: 3 unit increments
|
|
474
|
+
* Vertical unit increment: 15 pixels
|
|
475
|
+
*/
|
|
476
|
+
|
|
477
|
+
// Populate mouse wheel event.
|
|
478
|
+
event.time = timestamp;
|
|
479
|
+
event.reserved = 0x00;
|
|
480
|
+
|
|
481
|
+
event.type = EVENT_MOUSE_WHEEL;
|
|
482
|
+
event.mask = get_modifiers();
|
|
483
|
+
|
|
484
|
+
event.data.wheel.clicks = hook->input.mouse.click.count;
|
|
485
|
+
event.data.wheel.x = data->event.u.keyButtonPointer.rootX;
|
|
486
|
+
event.data.wheel.y = data->event.u.keyButtonPointer.rootY;
|
|
487
|
+
|
|
488
|
+
#if defined(USE_XINERAMA) || defined(USE_XRANDR)
|
|
489
|
+
uint8_t count;
|
|
490
|
+
screen_data *screens = hook_create_screen_info(&count);
|
|
491
|
+
if (count > 1) {
|
|
492
|
+
event.data.wheel.x -= screens[0].x;
|
|
493
|
+
event.data.wheel.y -= screens[0].y;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (screens != NULL) {
|
|
497
|
+
free(screens);
|
|
498
|
+
}
|
|
499
|
+
#endif
|
|
500
|
+
|
|
501
|
+
/* X11 does not have an API call for acquiring the mouse scroll type. This
|
|
502
|
+
* maybe part of the XInput2 (XI2) extention but I will wont know until it
|
|
503
|
+
* is available on my platform. For the time being we will just use the
|
|
504
|
+
* unit scroll value.
|
|
505
|
+
*/
|
|
506
|
+
event.data.wheel.type = WHEEL_UNIT_SCROLL;
|
|
507
|
+
|
|
508
|
+
/* Some scroll wheel properties are available via the new XInput2 (XI2)
|
|
509
|
+
* extension. Unfortunately the extension is not available on my
|
|
510
|
+
* development platform at this time. For the time being we will just
|
|
511
|
+
* use the Windows default value of 3.
|
|
512
|
+
*/
|
|
513
|
+
event.data.wheel.amount = 3;
|
|
514
|
+
|
|
515
|
+
if (data->event.u.u.detail == WheelUp || data->event.u.u.detail == WheelLeft) {
|
|
516
|
+
// Wheel Rotated Up and Away.
|
|
517
|
+
event.data.wheel.rotation = -1;
|
|
518
|
+
} else { // data->event.u.u.detail == WheelDown
|
|
519
|
+
// Wheel Rotated Down and Towards.
|
|
520
|
+
event.data.wheel.rotation = 1;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (data->event.u.u.detail == WheelUp || data->event.u.u.detail == WheelDown) {
|
|
524
|
+
// Wheel Rotated Up or Down.
|
|
525
|
+
event.data.wheel.direction = WHEEL_VERTICAL_DIRECTION;
|
|
526
|
+
} else { // data->event.u.u.detail == WheelLeft || data->event.u.u.detail == WheelRight
|
|
527
|
+
// Wheel Rotated Left or Right.
|
|
528
|
+
event.data.wheel.direction = WHEEL_HORIZONTAL_DIRECTION;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
logger(LOG_LEVEL_DEBUG, "%s [%u]: Mouse wheel type %u, rotated %i units in the %u direction at %u, %u.\n",
|
|
532
|
+
__FUNCTION__, __LINE__, event.data.wheel.type,
|
|
533
|
+
event.data.wheel.amount * event.data.wheel.rotation,
|
|
534
|
+
event.data.wheel.direction,
|
|
535
|
+
event.data.wheel.x, event.data.wheel.y);
|
|
536
|
+
|
|
537
|
+
// Fire mouse wheel event.
|
|
538
|
+
dispatch_event(&event);
|
|
539
|
+
} else {
|
|
540
|
+
/* This information is all static for X11, its up to the WM to
|
|
541
|
+
* decide how to interpret the wheel events.
|
|
542
|
+
*/
|
|
543
|
+
uint16_t button = MOUSE_NOBUTTON;
|
|
544
|
+
switch (map_button) {
|
|
545
|
+
case Button1:
|
|
546
|
+
button = MOUSE_BUTTON1;
|
|
547
|
+
set_modifier_mask(MASK_BUTTON1);
|
|
548
|
+
break;
|
|
549
|
+
|
|
550
|
+
case Button2:
|
|
551
|
+
button = MOUSE_BUTTON2;
|
|
552
|
+
set_modifier_mask(MASK_BUTTON2);
|
|
553
|
+
break;
|
|
554
|
+
|
|
555
|
+
case Button3:
|
|
556
|
+
button = MOUSE_BUTTON3;
|
|
557
|
+
set_modifier_mask(MASK_BUTTON3);
|
|
558
|
+
break;
|
|
559
|
+
|
|
560
|
+
case XButton1:
|
|
561
|
+
button = MOUSE_BUTTON4;
|
|
562
|
+
set_modifier_mask(MASK_BUTTON5);
|
|
563
|
+
break;
|
|
564
|
+
|
|
565
|
+
case XButton2:
|
|
566
|
+
button = MOUSE_BUTTON5;
|
|
567
|
+
set_modifier_mask(MASK_BUTTON5);
|
|
568
|
+
break;
|
|
569
|
+
|
|
570
|
+
default:
|
|
571
|
+
// Do not set modifier masks past button MASK_BUTTON5.
|
|
572
|
+
break;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
// Track the number of clicks, the button must match the previous button.
|
|
577
|
+
if (button == hook->input.mouse.click.button && (long int) (timestamp - hook->input.mouse.click.time) <= hook_get_multi_click_time()) {
|
|
578
|
+
if (hook->input.mouse.click.count < USHRT_MAX) {
|
|
579
|
+
hook->input.mouse.click.count++;
|
|
580
|
+
} else {
|
|
581
|
+
logger(LOG_LEVEL_WARN, "%s [%u]: Click count overflow detected!\n",
|
|
582
|
+
__FUNCTION__, __LINE__);
|
|
583
|
+
}
|
|
584
|
+
} else {
|
|
585
|
+
// Reset the click count.
|
|
586
|
+
hook->input.mouse.click.count = 1;
|
|
587
|
+
|
|
588
|
+
// Set the previous button.
|
|
589
|
+
hook->input.mouse.click.button = button;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Save this events time to calculate the hook->input.mouse.click.count.
|
|
593
|
+
hook->input.mouse.click.time = timestamp;
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
// Populate mouse pressed event.
|
|
597
|
+
event.time = timestamp;
|
|
598
|
+
event.reserved = 0x00;
|
|
599
|
+
|
|
600
|
+
event.type = EVENT_MOUSE_PRESSED;
|
|
601
|
+
event.mask = get_modifiers();
|
|
602
|
+
|
|
603
|
+
event.data.mouse.button = button;
|
|
604
|
+
event.data.mouse.clicks = hook->input.mouse.click.count;
|
|
605
|
+
event.data.mouse.x = data->event.u.keyButtonPointer.rootX;
|
|
606
|
+
event.data.mouse.y = data->event.u.keyButtonPointer.rootY;
|
|
607
|
+
|
|
608
|
+
#if defined(USE_XINERAMA) || defined(USE_XRANDR)
|
|
609
|
+
uint8_t count;
|
|
610
|
+
screen_data *screens = hook_create_screen_info(&count);
|
|
611
|
+
if (count > 1) {
|
|
612
|
+
event.data.mouse.x -= screens[0].x;
|
|
613
|
+
event.data.mouse.y -= screens[0].y;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
if (screens != NULL) {
|
|
617
|
+
free(screens);
|
|
618
|
+
}
|
|
619
|
+
#endif
|
|
620
|
+
|
|
621
|
+
logger(LOG_LEVEL_DEBUG, "%s [%u]: Button %u pressed %u time(s). (%u, %u)\n",
|
|
622
|
+
__FUNCTION__, __LINE__, event.data.mouse.button, event.data.mouse.clicks,
|
|
623
|
+
event.data.mouse.x, event.data.mouse.y);
|
|
624
|
+
|
|
625
|
+
// Fire mouse pressed event.
|
|
626
|
+
dispatch_event(&event);
|
|
627
|
+
}
|
|
628
|
+
} else if (data->type == ButtonRelease) {
|
|
629
|
+
unsigned int map_button = button_map_lookup(data->event.u.u.detail);
|
|
630
|
+
|
|
631
|
+
// X11 handles wheel events as button events.
|
|
632
|
+
if (map_button != WheelUp && map_button != WheelDown
|
|
633
|
+
&& map_button != WheelLeft && map_button != WheelRight) {
|
|
634
|
+
|
|
635
|
+
/* This information is all static for X11, its up to the WM to
|
|
636
|
+
* decide how to interpret the wheel events.
|
|
637
|
+
*/
|
|
638
|
+
uint16_t button = MOUSE_NOBUTTON;
|
|
639
|
+
switch (map_button) {
|
|
640
|
+
// FIXME This should use a lookup table to handle button remapping.
|
|
641
|
+
case Button1:
|
|
642
|
+
button = MOUSE_BUTTON1;
|
|
643
|
+
unset_modifier_mask(MASK_BUTTON1);
|
|
644
|
+
break;
|
|
645
|
+
|
|
646
|
+
case Button2:
|
|
647
|
+
button = MOUSE_BUTTON2;
|
|
648
|
+
unset_modifier_mask(MASK_BUTTON2);
|
|
649
|
+
break;
|
|
650
|
+
|
|
651
|
+
case Button3:
|
|
652
|
+
button = MOUSE_BUTTON3;
|
|
653
|
+
unset_modifier_mask(MASK_BUTTON3);
|
|
654
|
+
break;
|
|
655
|
+
|
|
656
|
+
case XButton1:
|
|
657
|
+
button = MOUSE_BUTTON4;
|
|
658
|
+
unset_modifier_mask(MASK_BUTTON5);
|
|
659
|
+
break;
|
|
660
|
+
|
|
661
|
+
case XButton2:
|
|
662
|
+
button = MOUSE_BUTTON5;
|
|
663
|
+
unset_modifier_mask(MASK_BUTTON5);
|
|
664
|
+
break;
|
|
665
|
+
|
|
666
|
+
default:
|
|
667
|
+
// Do not set modifier masks past button MASK_BUTTON5.
|
|
668
|
+
break;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// Populate mouse released event.
|
|
672
|
+
event.time = timestamp;
|
|
673
|
+
event.reserved = 0x00;
|
|
674
|
+
|
|
675
|
+
event.type = EVENT_MOUSE_RELEASED;
|
|
676
|
+
event.mask = get_modifiers();
|
|
677
|
+
|
|
678
|
+
event.data.mouse.button = button;
|
|
679
|
+
event.data.mouse.clicks = hook->input.mouse.click.count;
|
|
680
|
+
event.data.mouse.x = data->event.u.keyButtonPointer.rootX;
|
|
681
|
+
event.data.mouse.y = data->event.u.keyButtonPointer.rootY;
|
|
682
|
+
|
|
683
|
+
#if defined(USE_XINERAMA) || defined(USE_XRANDR)
|
|
684
|
+
uint8_t count;
|
|
685
|
+
screen_data *screens = hook_create_screen_info(&count);
|
|
686
|
+
if (count > 1) {
|
|
687
|
+
event.data.mouse.x -= screens[0].x;
|
|
688
|
+
event.data.mouse.y -= screens[0].y;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
if (screens != NULL) {
|
|
692
|
+
free(screens);
|
|
693
|
+
}
|
|
694
|
+
#endif
|
|
695
|
+
|
|
696
|
+
logger(LOG_LEVEL_DEBUG, "%s [%u]: Button %u released %u time(s). (%u, %u)\n",
|
|
697
|
+
__FUNCTION__, __LINE__, event.data.mouse.button,
|
|
698
|
+
event.data.mouse.clicks,
|
|
699
|
+
event.data.mouse.x, event.data.mouse.y);
|
|
700
|
+
|
|
701
|
+
// Fire mouse released event.
|
|
702
|
+
dispatch_event(&event);
|
|
703
|
+
|
|
704
|
+
// If the pressed event was not consumed...
|
|
705
|
+
if (event.reserved ^ 0x01 && hook->input.mouse.is_dragged != true) {
|
|
706
|
+
// Populate mouse clicked event.
|
|
707
|
+
event.time = timestamp;
|
|
708
|
+
event.reserved = 0x00;
|
|
709
|
+
|
|
710
|
+
event.type = EVENT_MOUSE_CLICKED;
|
|
711
|
+
event.mask = get_modifiers();
|
|
712
|
+
|
|
713
|
+
event.data.mouse.button = button;
|
|
714
|
+
event.data.mouse.clicks = hook->input.mouse.click.count;
|
|
715
|
+
event.data.mouse.x = data->event.u.keyButtonPointer.rootX;
|
|
716
|
+
event.data.mouse.y = data->event.u.keyButtonPointer.rootY;
|
|
717
|
+
|
|
718
|
+
#if defined(USE_XINERAMA) || defined(USE_XRANDR)
|
|
719
|
+
uint8_t count;
|
|
720
|
+
screen_data *screens = hook_create_screen_info(&count);
|
|
721
|
+
if (count > 1) {
|
|
722
|
+
event.data.mouse.x -= screens[0].x;
|
|
723
|
+
event.data.mouse.y -= screens[0].y;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
if (screens != NULL) {
|
|
727
|
+
free(screens);
|
|
728
|
+
}
|
|
729
|
+
#endif
|
|
730
|
+
|
|
731
|
+
logger(LOG_LEVEL_DEBUG, "%s [%u]: Button %u clicked %u time(s). (%u, %u)\n",
|
|
732
|
+
__FUNCTION__, __LINE__, event.data.mouse.button,
|
|
733
|
+
event.data.mouse.clicks,
|
|
734
|
+
event.data.mouse.x, event.data.mouse.y);
|
|
735
|
+
|
|
736
|
+
// Fire mouse clicked event.
|
|
737
|
+
dispatch_event(&event);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// Reset the number of clicks.
|
|
741
|
+
if (button == hook->input.mouse.click.button && (long int) (event.time - hook->input.mouse.click.time) > hook_get_multi_click_time()) {
|
|
742
|
+
// Reset the click count.
|
|
743
|
+
hook->input.mouse.click.count = 0;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
} else if (data->type == MotionNotify) {
|
|
747
|
+
// Reset the click count.
|
|
748
|
+
if (hook->input.mouse.click.count != 0 && (long int) (timestamp - hook->input.mouse.click.time) > hook_get_multi_click_time()) {
|
|
749
|
+
hook->input.mouse.click.count = 0;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// Populate mouse move event.
|
|
753
|
+
event.time = timestamp;
|
|
754
|
+
event.reserved = 0x00;
|
|
755
|
+
|
|
756
|
+
event.mask = get_modifiers();
|
|
757
|
+
|
|
758
|
+
// Check the upper half of virtual modifiers for non-zero values and set the mouse
|
|
759
|
+
// dragged flag. The last 3 bits are reserved for lock masks.
|
|
760
|
+
hook->input.mouse.is_dragged = ((event.mask & 0x1F00) > 0);
|
|
761
|
+
if (hook->input.mouse.is_dragged) {
|
|
762
|
+
// Create Mouse Dragged event.
|
|
763
|
+
event.type = EVENT_MOUSE_DRAGGED;
|
|
764
|
+
} else {
|
|
765
|
+
// Create a Mouse Moved event.
|
|
766
|
+
event.type = EVENT_MOUSE_MOVED;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
event.data.mouse.button = MOUSE_NOBUTTON;
|
|
770
|
+
event.data.mouse.clicks = hook->input.mouse.click.count;
|
|
771
|
+
event.data.mouse.x = data->event.u.keyButtonPointer.rootX;
|
|
772
|
+
event.data.mouse.y = data->event.u.keyButtonPointer.rootY;
|
|
773
|
+
|
|
774
|
+
#if defined(USE_XINERAMA) || defined(USE_XRANDR)
|
|
775
|
+
uint8_t count;
|
|
776
|
+
screen_data *screens = hook_create_screen_info(&count);
|
|
777
|
+
if (count > 1) {
|
|
778
|
+
event.data.mouse.x -= screens[0].x;
|
|
779
|
+
event.data.mouse.y -= screens[0].y;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
if (screens != NULL) {
|
|
783
|
+
free(screens);
|
|
784
|
+
}
|
|
785
|
+
#endif
|
|
786
|
+
|
|
787
|
+
logger(LOG_LEVEL_DEBUG, "%s [%u]: Mouse %s to %i, %i. (%#X)\n",
|
|
788
|
+
__FUNCTION__, __LINE__, hook->input.mouse.is_dragged ? "dragged" : "moved",
|
|
789
|
+
event.data.mouse.x, event.data.mouse.y, event.mask);
|
|
790
|
+
|
|
791
|
+
// Fire mouse move event.
|
|
792
|
+
dispatch_event(&event);
|
|
793
|
+
} else {
|
|
794
|
+
// In theory this *should* never execute.
|
|
795
|
+
logger(LOG_LEVEL_DEBUG, "%s [%u]: Unhandled X11 event: %#X.\n",
|
|
796
|
+
__FUNCTION__, __LINE__, (unsigned int) data->type);
|
|
797
|
+
}
|
|
798
|
+
} else {
|
|
799
|
+
logger(LOG_LEVEL_WARN, "%s [%u]: Unhandled X11 hook category! (%#X)\n",
|
|
800
|
+
__FUNCTION__, __LINE__, recorded_data->category);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// TODO There is no way to consume the XRecord event.
|
|
804
|
+
|
|
805
|
+
XRecordFreeData(recorded_data);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
static inline bool enable_key_repeate() {
|
|
810
|
+
// Attempt to setup detectable autorepeat.
|
|
811
|
+
// NOTE: is_auto_repeat is NOT stdbool!
|
|
812
|
+
Bool is_auto_repeat = False;
|
|
813
|
+
|
|
814
|
+
// Enable detectable auto-repeat.
|
|
815
|
+
XkbSetDetectableAutoRepeat(hook->ctrl.display, True, &is_auto_repeat);
|
|
816
|
+
|
|
817
|
+
return is_auto_repeat;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
|
|
821
|
+
static inline int xrecord_block() {
|
|
822
|
+
int status = UIOHOOK_FAILURE;
|
|
823
|
+
|
|
824
|
+
// Save the data display associated with this hook so it is passed to each event.
|
|
825
|
+
//XPointer closeure = (XPointer) (ctrl_display);
|
|
826
|
+
XPointer closeure = NULL;
|
|
827
|
+
|
|
828
|
+
#ifdef USE_XRECORD_ASYNC
|
|
829
|
+
// Async requires that we loop so that our thread does not return.
|
|
830
|
+
if (XRecordEnableContextAsync(hook->data.display, context, hook_event_proc, closeure) != 0) {
|
|
831
|
+
// Time in MS to sleep the runloop.
|
|
832
|
+
int timesleep = 100;
|
|
833
|
+
|
|
834
|
+
// Allow the thread loop to block.
|
|
835
|
+
pthread_mutex_lock(&hook_xrecord_mutex);
|
|
836
|
+
running = true;
|
|
837
|
+
|
|
838
|
+
do {
|
|
839
|
+
// Unlock the mutex from the previous iteration.
|
|
840
|
+
pthread_mutex_unlock(&hook_xrecord_mutex);
|
|
841
|
+
|
|
842
|
+
XRecordProcessReplies(hook->data.display);
|
|
843
|
+
|
|
844
|
+
// Prevent 100% CPU utilization.
|
|
845
|
+
struct timeval tv;
|
|
846
|
+
gettimeofday(&tv, NULL);
|
|
847
|
+
|
|
848
|
+
struct timespec ts;
|
|
849
|
+
ts.tv_sec = time(NULL) + timesleep / 1000;
|
|
850
|
+
ts.tv_nsec = tv.tv_usec * 1000 + 1000 * 1000 * (timesleep % 1000);
|
|
851
|
+
ts.tv_sec += ts.tv_nsec / (1000 * 1000 * 1000);
|
|
852
|
+
ts.tv_nsec %= (1000 * 1000 * 1000);
|
|
853
|
+
|
|
854
|
+
pthread_mutex_lock(&hook_xrecord_mutex);
|
|
855
|
+
pthread_cond_timedwait(&hook_xrecord_cond, &hook_xrecord_mutex, &ts);
|
|
856
|
+
} while (running);
|
|
857
|
+
|
|
858
|
+
// Unlock after loop exit.
|
|
859
|
+
pthread_mutex_unlock(&hook_xrecord_mutex);
|
|
860
|
+
|
|
861
|
+
// Set the exit status.
|
|
862
|
+
status = NULL;
|
|
863
|
+
}
|
|
864
|
+
#else
|
|
865
|
+
// Sync blocks until XRecordDisableContext() is called.
|
|
866
|
+
if (XRecordEnableContext(hook->data.display, hook->ctrl.context, hook_event_proc, closeure) != 0) {
|
|
867
|
+
status = UIOHOOK_SUCCESS;
|
|
868
|
+
}
|
|
869
|
+
#endif
|
|
870
|
+
else {
|
|
871
|
+
logger(LOG_LEVEL_ERROR, "%s [%u]: XRecordEnableContext failure!\n",
|
|
872
|
+
__FUNCTION__, __LINE__);
|
|
873
|
+
|
|
874
|
+
#ifdef USE_XRECORD_ASYNC
|
|
875
|
+
// Reset the running state.
|
|
876
|
+
pthread_mutex_lock(&hook_xrecord_mutex);
|
|
877
|
+
running = false;
|
|
878
|
+
pthread_mutex_unlock(&hook_xrecord_mutex);
|
|
879
|
+
#endif
|
|
880
|
+
|
|
881
|
+
// Set the exit status.
|
|
882
|
+
status = UIOHOOK_ERROR_X_RECORD_ENABLE_CONTEXT;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
return status;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
static int xrecord_alloc() {
|
|
889
|
+
int status = UIOHOOK_FAILURE;
|
|
890
|
+
|
|
891
|
+
// Make sure the data display is synchronized to prevent late event delivery!
|
|
892
|
+
// See Bug 42356 for more information.
|
|
893
|
+
// https://bugs.freedesktop.org/show_bug.cgi?id=42356#c4
|
|
894
|
+
XSynchronize(hook->data.display, True);
|
|
895
|
+
|
|
896
|
+
// Setup XRecord range.
|
|
897
|
+
XRecordClientSpec clients = XRecordAllClients;
|
|
898
|
+
|
|
899
|
+
hook->data.range = XRecordAllocRange();
|
|
900
|
+
if (hook->data.range != NULL) {
|
|
901
|
+
logger(LOG_LEVEL_DEBUG, "%s [%u]: XRecordAllocRange successful.\n",
|
|
902
|
+
__FUNCTION__, __LINE__);
|
|
903
|
+
|
|
904
|
+
hook->data.range->device_events.first = KeyPress;
|
|
905
|
+
hook->data.range->device_events.last = MotionNotify;
|
|
906
|
+
|
|
907
|
+
// Note that the documentation for this function is incorrect,
|
|
908
|
+
// hook->data.display should be used!
|
|
909
|
+
// See: http://www.x.org/releases/X11R7.6/doc/libXtst/recordlib.txt
|
|
910
|
+
hook->ctrl.context = XRecordCreateContext(hook->data.display, XRecordFromServerTime, &clients, 1, &hook->data.range, 1);
|
|
911
|
+
if (hook->ctrl.context != 0) {
|
|
912
|
+
logger(LOG_LEVEL_DEBUG, "%s [%u]: XRecordCreateContext successful.\n",
|
|
913
|
+
__FUNCTION__, __LINE__);
|
|
914
|
+
|
|
915
|
+
// Block until hook_stop() is called.
|
|
916
|
+
status = xrecord_block();
|
|
917
|
+
|
|
918
|
+
// Free up the context if it was set.
|
|
919
|
+
XRecordFreeContext(hook->data.display, hook->ctrl.context);
|
|
920
|
+
} else {
|
|
921
|
+
logger(LOG_LEVEL_ERROR, "%s [%u]: XRecordCreateContext failure!\n",
|
|
922
|
+
__FUNCTION__, __LINE__);
|
|
923
|
+
|
|
924
|
+
// Set the exit status.
|
|
925
|
+
status = UIOHOOK_ERROR_X_RECORD_CREATE_CONTEXT;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// Free the XRecord range.
|
|
929
|
+
XFree(hook->data.range);
|
|
930
|
+
} else {
|
|
931
|
+
logger(LOG_LEVEL_ERROR, "%s [%u]: XRecordAllocRange failure!\n",
|
|
932
|
+
__FUNCTION__, __LINE__);
|
|
933
|
+
|
|
934
|
+
// Set the exit status.
|
|
935
|
+
status = UIOHOOK_ERROR_X_RECORD_ALLOC_RANGE;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
return status;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
static int xrecord_query() {
|
|
942
|
+
int status = UIOHOOK_FAILURE;
|
|
943
|
+
|
|
944
|
+
// Check to make sure XRecord is installed and enabled.
|
|
945
|
+
int major, minor;
|
|
946
|
+
if (XRecordQueryVersion(hook->ctrl.display, &major, &minor) != 0) {
|
|
947
|
+
logger(LOG_LEVEL_DEBUG, "%s [%u]: XRecord version: %i.%i.\n",
|
|
948
|
+
__FUNCTION__, __LINE__, major, minor);
|
|
949
|
+
|
|
950
|
+
status = xrecord_alloc();
|
|
951
|
+
} else {
|
|
952
|
+
logger(LOG_LEVEL_ERROR, "%s [%u]: XRecord is not currently available!\n",
|
|
953
|
+
__FUNCTION__, __LINE__);
|
|
954
|
+
|
|
955
|
+
status = UIOHOOK_ERROR_X_RECORD_NOT_FOUND;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
return status;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
static int xrecord_start() {
|
|
962
|
+
int status = UIOHOOK_FAILURE;
|
|
963
|
+
|
|
964
|
+
// Open the control display for XRecord.
|
|
965
|
+
hook->ctrl.display = XOpenDisplay(NULL);
|
|
966
|
+
|
|
967
|
+
// Open a data display for XRecord.
|
|
968
|
+
// NOTE This display must be opened on the same thread as XRecord.
|
|
969
|
+
hook->data.display = XOpenDisplay(NULL);
|
|
970
|
+
if (hook->ctrl.display != NULL && hook->data.display != NULL) {
|
|
971
|
+
logger(LOG_LEVEL_DEBUG, "%s [%u]: XOpenDisplay successful.\n",
|
|
972
|
+
__FUNCTION__, __LINE__);
|
|
973
|
+
|
|
974
|
+
bool is_auto_repeat = enable_key_repeate();
|
|
975
|
+
if (is_auto_repeat) {
|
|
976
|
+
logger(LOG_LEVEL_DEBUG, "%s [%u]: Successfully enabled detectable auto-repeat.\n",
|
|
977
|
+
__FUNCTION__, __LINE__);
|
|
978
|
+
} else {
|
|
979
|
+
logger(LOG_LEVEL_WARN, "%s [%u]: Could not enable detectable auto-repeat!\n",
|
|
980
|
+
__FUNCTION__, __LINE__);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
#if defined(USE_XKB_COMMON)
|
|
984
|
+
// Open XCB Connection
|
|
985
|
+
hook->input.connection = XGetXCBConnection(hook->ctrl.display);
|
|
986
|
+
int xcb_status = xcb_connection_has_error(hook->input.connection);
|
|
987
|
+
if (xcb_status <= 0) {
|
|
988
|
+
// Initialize xkbcommon context.
|
|
989
|
+
struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
|
990
|
+
|
|
991
|
+
if (context != NULL) {
|
|
992
|
+
hook->input.context = xkb_context_ref(context);
|
|
993
|
+
} else {
|
|
994
|
+
logger(LOG_LEVEL_ERROR, "%s [%u]: xkb_context_new failure!\n",
|
|
995
|
+
__FUNCTION__, __LINE__);
|
|
996
|
+
}
|
|
997
|
+
} else {
|
|
998
|
+
logger(LOG_LEVEL_ERROR, "%s [%u]: xcb_connect failure! (%d)\n",
|
|
999
|
+
__FUNCTION__, __LINE__, xcb_status);
|
|
1000
|
+
}
|
|
1001
|
+
#endif
|
|
1002
|
+
|
|
1003
|
+
#ifdef USE_XKB_COMMON
|
|
1004
|
+
state = create_xkb_state(hook->input.context, hook->input.connection);
|
|
1005
|
+
#endif
|
|
1006
|
+
|
|
1007
|
+
// Initialize starting modifiers.
|
|
1008
|
+
initialize_modifiers();
|
|
1009
|
+
|
|
1010
|
+
status = xrecord_query();
|
|
1011
|
+
|
|
1012
|
+
#ifdef USE_XKB_COMMON
|
|
1013
|
+
if (state != NULL) {
|
|
1014
|
+
destroy_xkb_state(state);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
if (hook->input.context != NULL) {
|
|
1018
|
+
xkb_context_unref(hook->input.context);
|
|
1019
|
+
hook->input.context = NULL;
|
|
1020
|
+
}
|
|
1021
|
+
#endif
|
|
1022
|
+
} else {
|
|
1023
|
+
logger(LOG_LEVEL_ERROR, "%s [%u]: XOpenDisplay failure!\n",
|
|
1024
|
+
__FUNCTION__, __LINE__);
|
|
1025
|
+
|
|
1026
|
+
status = UIOHOOK_ERROR_X_OPEN_DISPLAY;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// Close down the XRecord data display.
|
|
1030
|
+
if (hook->data.display != NULL) {
|
|
1031
|
+
XCloseDisplay(hook->data.display);
|
|
1032
|
+
hook->data.display = NULL;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// Close down the XRecord control display.
|
|
1036
|
+
if (hook->ctrl.display) {
|
|
1037
|
+
XCloseDisplay(hook->ctrl.display);
|
|
1038
|
+
hook->ctrl.display = NULL;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
return status;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
UIOHOOK_API int hook_run() {
|
|
1045
|
+
// Hook data for future cleanup.
|
|
1046
|
+
hook = malloc(sizeof(hook_info));
|
|
1047
|
+
if (hook == NULL) {
|
|
1048
|
+
logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to allocate memory for hook structure!\n",
|
|
1049
|
+
__FUNCTION__, __LINE__);
|
|
1050
|
+
|
|
1051
|
+
return UIOHOOK_ERROR_OUT_OF_MEMORY;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
hook->input.mask = 0x0000;
|
|
1055
|
+
hook->input.mouse.is_dragged = false;
|
|
1056
|
+
hook->input.mouse.click.count = 0;
|
|
1057
|
+
hook->input.mouse.click.time = 0;
|
|
1058
|
+
hook->input.mouse.click.button = MOUSE_NOBUTTON;
|
|
1059
|
+
|
|
1060
|
+
int status = xrecord_start();
|
|
1061
|
+
|
|
1062
|
+
// Free data associated with this hook.
|
|
1063
|
+
free(hook);
|
|
1064
|
+
hook = NULL;
|
|
1065
|
+
|
|
1066
|
+
logger(LOG_LEVEL_DEBUG, "%s [%u]: Something, something, something, complete.\n",
|
|
1067
|
+
__FUNCTION__, __LINE__);
|
|
1068
|
+
|
|
1069
|
+
return status;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
UIOHOOK_API int hook_stop() {
|
|
1073
|
+
int status = UIOHOOK_FAILURE;
|
|
1074
|
+
|
|
1075
|
+
if (hook != NULL && hook->ctrl.display != NULL && hook->ctrl.context != 0) {
|
|
1076
|
+
// We need to make sure the context is still valid.
|
|
1077
|
+
XRecordState *state = malloc(sizeof(XRecordState));
|
|
1078
|
+
if (state != NULL) {
|
|
1079
|
+
if (XRecordGetContext(hook->ctrl.display, hook->ctrl.context, &state) != 0) {
|
|
1080
|
+
// Try to exit the thread naturally.
|
|
1081
|
+
if (state->enabled && XRecordDisableContext(hook->ctrl.display, hook->ctrl.context) != 0) {
|
|
1082
|
+
#ifdef USE_XRECORD_ASYNC
|
|
1083
|
+
pthread_mutex_lock(&hook_xrecord_mutex);
|
|
1084
|
+
running = false;
|
|
1085
|
+
pthread_cond_signal(&hook_xrecord_cond);
|
|
1086
|
+
pthread_mutex_unlock(&hook_xrecord_mutex);
|
|
1087
|
+
#endif
|
|
1088
|
+
|
|
1089
|
+
// See Bug 42356 for more information.
|
|
1090
|
+
// https://bugs.freedesktop.org/show_bug.cgi?id=42356#c4
|
|
1091
|
+
//XFlush(hook->ctrl.display);
|
|
1092
|
+
XSync(hook->ctrl.display, False);
|
|
1093
|
+
|
|
1094
|
+
status = UIOHOOK_SUCCESS;
|
|
1095
|
+
}
|
|
1096
|
+
} else {
|
|
1097
|
+
logger(LOG_LEVEL_ERROR, "%s [%u]: XRecordGetContext failure!\n",
|
|
1098
|
+
__FUNCTION__, __LINE__);
|
|
1099
|
+
|
|
1100
|
+
status = UIOHOOK_ERROR_X_RECORD_GET_CONTEXT;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
free(state);
|
|
1104
|
+
} else {
|
|
1105
|
+
logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to allocate memory for XRecordState!\n",
|
|
1106
|
+
__FUNCTION__, __LINE__);
|
|
1107
|
+
|
|
1108
|
+
status = UIOHOOK_ERROR_OUT_OF_MEMORY;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
logger(LOG_LEVEL_DEBUG, "%s [%u]: Status: %#X.\n",
|
|
1113
|
+
__FUNCTION__, __LINE__, status);
|
|
1114
|
+
|
|
1115
|
+
return status;
|
|
1116
|
+
}
|