gohook/hook/x11/hook_c.h

1107 lines
33 KiB
C
Raw Normal View History

2024-06-16 04:44:20 +08:00
/* Copyright (C) 2006-2017 Alexander Barker.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#define USE_XKB 0
#define USE_XKBCOMMON 0
#include <inttypes.h>
#include <limits.h>
#ifdef USE_XRECORD_ASYNC
#include <pthread.h>
#endif
#include <stdint.h>
#include <X11/keysym.h>
#include <X11/Xlibint.h>
#include <X11/Xlib.h>
#include <X11/extensions/record.h>
// #ifdef USE_XKB
#include <xcb/xkb.h>
#include <X11/XKBlib.h>
// #endif
#if defined(USE_XINERAMA) && !defined(USE_XRANDR)
#include <X11/extensions/Xinerama.h>
#elif defined(USE_XRANDR)
#include <X11/extensions/Xrandr.h>
#else
// TODO We may need to fallback to the xf86vm extension for things like TwinView.
// #pragma message("*** Warning: Xinerama or XRandR support is required to produce cross-platform mouse coordinates for multi-head configurations!")
// #pragma message("... Assuming single-head display.")
#endif
#include "../iohook.h"
// #include "../logger.h"
#include "input.h"
// Thread and hook handles.
#ifdef USE_XRECORD_ASYNC
static bool running;
static pthread_cond_t hook_xrecord_cond = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t hook_xrecord_mutex = PTHREAD_MUTEX_INITIALIZER;
#endif
typedef struct _hook_info {
struct _data {
Display *display;
XRecordRange *range;
} data;
struct _ctrl {
Display *display;
XRecordContext context;
} ctrl;
struct _input {
#ifdef USE_XKBCOMMON
xcb_connection_t *connection;
struct xkb_context *context;
#endif
uint16_t mask;
struct _mouse {
bool is_dragged;
struct _click {
unsigned short int count;
long int time;
unsigned short int button;
} click;
} mouse;
} input;
} hook_info;
static hook_info *hook;
// For this struct, refer to libxnee, requires Xlibint.h
typedef union {
unsigned char type;
xEvent event;
xResourceReq req;
xGenericReply reply;
xError error;
xConnSetupPrefix setup;
} XRecordDatum;
#if defined(USE_XKBCOMMON)
//struct xkb_keymap *keymap;
//struct xkb_state *state = xkb_state_new(keymap);
static struct xkb_state *state = NULL;
#endif
// Virtual event pointer.
static iohook_event event;
// Event dispatch callback.
static dispatcher_t dispatcher = NULL;
IOHOOK_API void hook_set_dispatch_proc(dispatcher_t dispatch_proc) {
logger(LOG_LEVEL_DEBUG, "%s [%u]: Setting new dispatch callback to %#p.\n",
__FUNCTION__, __LINE__, dispatch_proc);
dispatcher = dispatch_proc;
}
// Send out an event if a dispatcher was set.
static inline void dispatch_event(iohook_event *const event) {
if (dispatcher != NULL) {
logger(LOG_LEVEL_DEBUG, "%s [%u]: Dispatching event type %u.\n",
__FUNCTION__, __LINE__, event->type);
dispatcher(event);
} else {
logger(LOG_LEVEL_WARN, "%s [%u]: No dispatch callback set!\n",
__FUNCTION__, __LINE__);
}
}
// Set the native modifier mask for future events.
static inline void set_modifier_mask(uint16_t mask) {
hook->input.mask |= mask;
}
// Unset the native modifier mask for future events.
static inline void unset_modifier_mask(uint16_t mask) {
hook->input.mask &= ~mask;
}
// Get the current native modifier mask state.
static inline uint16_t get_modifiers() {
return hook->input.mask;
}
// Initialize the modifier lock masks.
static void initialize_locks() {
#ifdef USE_XKBCOMMON
if (xkb_state_led_name_is_active(state, XKB_LED_NAME_CAPS)) {
set_modifier_mask(MASK_CAPS_LOCK);
} else {
unset_modifier_mask(MASK_CAPS_LOCK);
}
if (xkb_state_led_name_is_active(state, XKB_LED_NAME_NUM)) {
set_modifier_mask(MASK_NUM_LOCK);
} else {
unset_modifier_mask(MASK_NUM_LOCK);
}
if (xkb_state_led_name_is_active(state, XKB_LED_NAME_SCROLL)) {
set_modifier_mask(MASK_SCROLL_LOCK);
} else {
unset_modifier_mask(MASK_SCROLL_LOCK);
}
#else
unsigned int led_mask = 0x00;
if (XkbGetIndicatorState(hook->ctrl.display, XkbUseCoreKbd, &led_mask) == Success) {
if (led_mask & 0x01) {
set_modifier_mask(MASK_CAPS_LOCK);
} else {
unset_modifier_mask(MASK_CAPS_LOCK);
}
if (led_mask & 0x02) {
set_modifier_mask(MASK_NUM_LOCK);
} else {
unset_modifier_mask(MASK_NUM_LOCK);
}
if (led_mask & 0x04) {
set_modifier_mask(MASK_SCROLL_LOCK);
} else {
unset_modifier_mask(MASK_SCROLL_LOCK);
}
} else {
logger(LOG_LEVEL_WARN, "%s [%u]: XkbGetIndicatorState failed to get current led mask!\n",
__FUNCTION__, __LINE__);
}
#endif
}
// Initialize the modifier mask to the current modifiers.
static void initialize_modifiers() {
hook->input.mask = 0x0000;
KeyCode keycode;
char keymap[32];
XQueryKeymap(hook->ctrl.display, keymap);
Window unused_win;
int unused_int;
unsigned int mask;
if (XQueryPointer(hook->ctrl.display, DefaultRootWindow(hook->ctrl.display), &unused_win, &unused_win, &unused_int, &unused_int, &unused_int, &unused_int, &mask)) {
if (mask & ShiftMask) {
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Shift_L);
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_SHIFT_L); }
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Shift_R);
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_SHIFT_R); }
}
if (mask & ControlMask) {
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Control_L);
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_CTRL_L); }
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Control_R);
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_CTRL_R); }
}
if (mask & Mod1Mask) {
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Alt_L);
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_ALT_L); }
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Alt_R);
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_ALT_R); }
}
if (mask & Mod4Mask) {
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Super_L);
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_META_L); }
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Super_R);
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_META_R); }
}
if (mask & Button1Mask) { set_modifier_mask(MASK_BUTTON1); }
if (mask & Button2Mask) { set_modifier_mask(MASK_BUTTON2); }
if (mask & Button3Mask) { set_modifier_mask(MASK_BUTTON3); }
if (mask & Button4Mask) { set_modifier_mask(MASK_BUTTON4); }
if (mask & Button5Mask) { set_modifier_mask(MASK_BUTTON5); }
} else {
logger(LOG_LEVEL_WARN, "%s [%u]: XQueryPointer failed to get current modifiers!\n",
__FUNCTION__, __LINE__);
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Shift_L);
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_SHIFT_L); }
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Shift_R);
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_SHIFT_R); }
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Control_L);
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_CTRL_L); }
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Control_R);
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_CTRL_R); }
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Alt_L);
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_ALT_L); }
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Alt_R);
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_ALT_R); }
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Super_L);
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_META_L); }
keycode = XKeysymToKeycode(hook->ctrl.display, XK_Super_R);
if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_META_R); }
}
initialize_locks();
}
void hook_event_proc(XPointer closeure, XRecordInterceptData *recorded_data) {
uint64_t timestamp = (uint64_t) recorded_data->server_time;
if (recorded_data->category == XRecordStartOfData) {
// Populate the hook start event.
event.time = timestamp;
event.reserved = 0x00;
event.type = EVENT_HOOK_ENABLED;
event.mask = 0x00;
// Fire the hook start event.
dispatch_event(&event);
} else if (recorded_data->category == XRecordEndOfData) {
// Populate the hook stop event.
event.time = timestamp;
event.reserved = 0x00;
event.type = EVENT_HOOK_DISABLED;
event.mask = 0x00;
// Fire the hook stop event.
dispatch_event(&event);
} else if (recorded_data->category == XRecordFromServer || recorded_data->category == XRecordFromClient) {
// Get XRecord data.
XRecordDatum *data = (XRecordDatum *) recorded_data->data;
if (data->type == KeyPress) {
// The X11 KeyCode associated with this event.
KeyCode keycode = (KeyCode) data->event.u.u.detail;
KeySym keysym = 0x00;
#if defined(USE_XKBCOMMON)
if (state != NULL) {
keysym = xkb_state_key_get_one_sym(state, keycode);
}
#else
keysym = keycode_to_keysym(keycode, data->event.u.keyButtonPointer.state);
#endif
unsigned short int scancode = keycode_to_scancode(keycode);
// TODO If you have a better suggestion for this ugly, let me know.
if (scancode == VC_SHIFT_L) { set_modifier_mask(MASK_SHIFT_L); }
else if (scancode == VC_SHIFT_R) { set_modifier_mask(MASK_SHIFT_R); }
else if (scancode == VC_CONTROL_L) { set_modifier_mask(MASK_CTRL_L); }
else if (scancode == VC_CONTROL_R) { set_modifier_mask(MASK_CTRL_R); }
else if (scancode == VC_ALT_L) { set_modifier_mask(MASK_ALT_L); }
else if (scancode == VC_ALT_R) { set_modifier_mask(MASK_ALT_R); }
else if (scancode == VC_META_L) { set_modifier_mask(MASK_META_L); }
else if (scancode == VC_META_R) { set_modifier_mask(MASK_META_R); }
xkb_state_update_key(state, keycode, XKB_KEY_DOWN);
initialize_locks();
if ((get_modifiers() & MASK_NUM_LOCK) == 0) {
switch (scancode) {
case VC_KP_SEPARATOR:
case VC_KP_1:
case VC_KP_2:
case VC_KP_3:
case VC_KP_4:
case VC_KP_5:
case VC_KP_6:
case VC_KP_7:
case VC_KP_8:
case VC_KP_0:
case VC_KP_9:
scancode |= 0xEE00;
break;
}
}
// Populate key pressed event.
event.time = timestamp;
event.reserved = 0x00;
event.type = EVENT_KEY_PRESSED;
event.mask = get_modifiers();
event.data.keyboard.keycode = scancode;
event.data.keyboard.rawcode = keysym;
event.data.keyboard.keychar = CHAR_UNDEFINED;
logger(LOG_LEVEL_INFO, "%s [%u]: Key %#X pressed. (%#X)\n",
__FUNCTION__, __LINE__, event.data.keyboard.keycode, event.data.keyboard.rawcode);
// Fire key pressed event.
dispatch_event(&event);
// If the pressed event was not consumed...
if (event.reserved ^ 0x01) {
uint16_t buffer[2];
size_t count = 0;
// Check to make sure the key is printable.
#ifdef USE_XKBCOMMON
if (state != NULL) {
count = keycode_to_unicode(state, keycode, buffer, sizeof(buffer) / sizeof(uint16_t));
}
#else
count = keysym_to_unicode(keysym, buffer, sizeof(buffer) / sizeof(uint16_t));
#endif
unsigned int i;
for (i = 0; i < count; i++) {
// Populate key typed event.
event.time = timestamp;
event.reserved = 0x00;
event.type = EVENT_KEY_TYPED;
event.mask = get_modifiers();
event.data.keyboard.keycode = VC_UNDEFINED;
event.data.keyboard.rawcode = keysym;
event.data.keyboard.keychar = buffer[i];
logger(LOG_LEVEL_INFO, "%s [%u]: Key %#X typed. (%lc)\n",
__FUNCTION__, __LINE__, event.data.keyboard.keycode, (uint16_t) event.data.keyboard.keychar);
// Fire key typed event.
dispatch_event(&event);
}
}
} else if (data->type == KeyRelease) {
// The X11 KeyCode associated with this event.
KeyCode keycode = (KeyCode) data->event.u.u.detail;
KeySym keysym = 0x00;
#ifdef USE_XKBCOMMON
if (state != NULL) {
keysym = xkb_state_key_get_one_sym(state, keycode);
}
#else
keysym = keycode_to_keysym(keycode, data->event.u.keyButtonPointer.state);
#endif
unsigned short int scancode = keycode_to_scancode(keycode);
// TODO If you have a better suggestion for this ugly, let me know.
if (scancode == VC_SHIFT_L) { unset_modifier_mask(MASK_SHIFT_L); }
else if (scancode == VC_SHIFT_R) { unset_modifier_mask(MASK_SHIFT_R); }
else if (scancode == VC_CONTROL_L) { unset_modifier_mask(MASK_CTRL_L); }
else if (scancode == VC_CONTROL_R) { unset_modifier_mask(MASK_CTRL_R); }
else if (scancode == VC_ALT_L) { unset_modifier_mask(MASK_ALT_L); }
else if (scancode == VC_ALT_R) { unset_modifier_mask(MASK_ALT_R); }
else if (scancode == VC_META_L) { unset_modifier_mask(MASK_META_L); }
else if (scancode == VC_META_R) { unset_modifier_mask(MASK_META_R); }
xkb_state_update_key(state, keycode, XKB_KEY_UP);
initialize_locks();
if ((get_modifiers() & MASK_NUM_LOCK) == 0) {
switch (scancode) {
case VC_KP_SEPARATOR:
case VC_KP_1:
case VC_KP_2:
case VC_KP_3:
case VC_KP_4:
case VC_KP_5:
case VC_KP_6:
case VC_KP_7:
case VC_KP_8:
case VC_KP_0:
case VC_KP_9:
scancode |= 0xEE00;
break;
}
}
// Populate key released event.
event.time = timestamp;
event.reserved = 0x00;
event.type = EVENT_KEY_RELEASED;
event.mask = get_modifiers();
event.data.keyboard.keycode = scancode;
event.data.keyboard.rawcode = keysym;
event.data.keyboard.keychar = CHAR_UNDEFINED;
logger(LOG_LEVEL_INFO, "%s [%u]: Key %#X released. (%#X)\n",
__FUNCTION__, __LINE__, event.data.keyboard.keycode, event.data.keyboard.rawcode);
// Fire key released event.
dispatch_event(&event);
} else if (data->type == ButtonPress) {
// X11 handles wheel events as button events.
if (data->event.u.u.detail == WheelUp || data->event.u.u.detail == WheelDown
|| data->event.u.u.detail == WheelLeft || data->event.u.u.detail == WheelRight) {
// Reset the click count and previous button.
hook->input.mouse.click.count = 1;
hook->input.mouse.click.button = MOUSE_NOBUTTON;
/* Scroll wheel release events.
* Scroll type: WHEEL_UNIT_SCROLL
* Scroll amount: 3 unit increments per notch
* Units to scroll: 3 unit increments
* Vertical unit increment: 15 pixels
*/
// Populate mouse wheel event.
event.time = timestamp;
event.reserved = 0x00;
event.type = EVENT_MOUSE_WHEEL;
event.mask = get_modifiers();
event.data.wheel.clicks = hook->input.mouse.click.count;
event.data.wheel.x = data->event.u.keyButtonPointer.rootX;
event.data.wheel.y = data->event.u.keyButtonPointer.rootY;
#if defined(USE_XINERAMA) || defined(USE_XRANDR)
uint8_t count;
screen_data *screens = hook_create_screen_info(&count);
if (count > 1) {
event.data.wheel.x -= screens[0].x;
event.data.wheel.y -= screens[0].y;
}
if (screens != NULL) {
free(screens);
}
#endif
/* X11 does not have an API call for acquiring the mouse scroll type. This
* maybe part of the XInput2 (XI2) extention but I will wont know until it
* is available on my platform. For the time being we will just use the
* unit scroll value.
*/
event.data.wheel.type = WHEEL_UNIT_SCROLL;
/* Some scroll wheel properties are available via the new XInput2 (XI2)
* extension. Unfortunately the extension is not available on my
* development platform at this time. For the time being we will just
* use the Windows default value of 3.
*/
event.data.wheel.amount = 3;
if (data->event.u.u.detail == WheelUp || data->event.u.u.detail == WheelLeft) {
// Wheel Rotated Up and Away.
event.data.wheel.rotation = -1;
} else { // data->event.u.u.detail == WheelDown
// Wheel Rotated Down and Towards.
event.data.wheel.rotation = 1;
}
if (data->event.u.u.detail == WheelUp || data->event.u.u.detail == WheelDown) {
// Wheel Rotated Up or Down.
event.data.wheel.direction = WHEEL_VERTICAL_DIRECTION;
} else { // data->event.u.u.detail == WheelLeft || data->event.u.u.detail == WheelRight
// Wheel Rotated Left or Right.
event.data.wheel.direction = WHEEL_HORIZONTAL_DIRECTION;
}
logger(LOG_LEVEL_INFO, "%s [%u]: Mouse wheel type %u, rotated %i units in the %u direction at %u, %u.\n",
__FUNCTION__, __LINE__, event.data.wheel.type,
event.data.wheel.amount * event.data.wheel.rotation,
event.data.wheel.direction,
event.data.wheel.x, event.data.wheel.y);
// Fire mouse wheel event.
dispatch_event(&event);
} else {
/* This information is all static for X11, its up to the WM to
* decide how to interpret the wheel events.
*/
uint16_t button = MOUSE_NOBUTTON;
switch (data->event.u.u.detail) {
// FIXME This should use a lookup table to handle button remapping.
case Button1:
button = MOUSE_BUTTON1;
set_modifier_mask(MASK_BUTTON1);
break;
case Button2:
button = MOUSE_BUTTON2;
set_modifier_mask(MASK_BUTTON2);
break;
case Button3:
button = MOUSE_BUTTON3;
set_modifier_mask(MASK_BUTTON3);
break;
case XButton1:
button = MOUSE_BUTTON4;
set_modifier_mask(MASK_BUTTON5);
break;
case XButton2:
button = MOUSE_BUTTON5;
set_modifier_mask(MASK_BUTTON5);
break;
default:
// Do not set modifier masks past button MASK_BUTTON5.
break;
}
// Track the number of clicks, the button must match the previous button.
if (button == hook->input.mouse.click.button && (long int) (timestamp - hook->input.mouse.click.time) <= hook_get_multi_click_time()) {
if (hook->input.mouse.click.count < USHRT_MAX) {
hook->input.mouse.click.count++;
} else {
logger(LOG_LEVEL_WARN, "%s [%u]: Click count overflow detected!\n",
__FUNCTION__, __LINE__);
}
} else {
// Reset the click count.
hook->input.mouse.click.count = 1;
// Set the previous button.
hook->input.mouse.click.button = button;
}
// Save this events time to calculate the hook->input.mouse.click.count.
hook->input.mouse.click.time = timestamp;
// Populate mouse pressed event.
event.time = timestamp;
event.reserved = 0x00;
event.type = EVENT_MOUSE_PRESSED;
event.mask = get_modifiers();
event.data.mouse.button = button;
event.data.mouse.clicks = hook->input.mouse.click.count;
event.data.mouse.x = data->event.u.keyButtonPointer.rootX;
event.data.mouse.y = data->event.u.keyButtonPointer.rootY;
#if defined(USE_XINERAMA) || defined(USE_XRANDR)
uint8_t count;
screen_data *screens = hook_create_screen_info(&count);
if (count > 1) {
event.data.mouse.x -= screens[0].x;
event.data.mouse.y -= screens[0].y;
}
if (screens != NULL) {
free(screens);
}
#endif
logger(LOG_LEVEL_INFO, "%s [%u]: Button %u pressed %u time(s). (%u, %u)\n",
__FUNCTION__, __LINE__, event.data.mouse.button, event.data.mouse.clicks,
event.data.mouse.x, event.data.mouse.y);
// Fire mouse pressed event.
dispatch_event(&event);
}
} else if (data->type == ButtonRelease) {
// X11 handles wheel events as button events.
if (data->event.u.u.detail != WheelUp && data->event.u.u.detail != WheelDown) {
/* This information is all static for X11, its up to the WM to
* decide how to interpret the wheel events.
*/
uint16_t button = MOUSE_NOBUTTON;
switch (data->event.u.u.detail) {
// FIXME This should use a lookup table to handle button remapping.
case Button1:
button = MOUSE_BUTTON1;
unset_modifier_mask(MASK_BUTTON1);
break;
case Button2:
button = MOUSE_BUTTON2;
unset_modifier_mask(MASK_BUTTON2);
break;
case Button3:
button = MOUSE_BUTTON3;
unset_modifier_mask(MASK_BUTTON3);
break;
case XButton1:
button = MOUSE_BUTTON4;
unset_modifier_mask(MASK_BUTTON5);
break;
case XButton2:
button = MOUSE_BUTTON5;
unset_modifier_mask(MASK_BUTTON5);
break;
default:
// Do not set modifier masks past button MASK_BUTTON5.
break;
}
// Populate mouse released event.
event.time = timestamp;
event.reserved = 0x00;
event.type = EVENT_MOUSE_RELEASED;
event.mask = get_modifiers();
event.data.mouse.button = button;
event.data.mouse.clicks = hook->input.mouse.click.count;
event.data.mouse.x = data->event.u.keyButtonPointer.rootX;
event.data.mouse.y = data->event.u.keyButtonPointer.rootY;
#if defined(USE_XINERAMA) || defined(USE_XRANDR)
uint8_t count;
screen_data *screens = hook_create_screen_info(&count);
if (count > 1) {
event.data.mouse.x -= screens[0].x;
event.data.mouse.y -= screens[0].y;
}
if (screens != NULL) {
free(screens);
}
#endif
logger(LOG_LEVEL_INFO, "%s [%u]: Button %u released %u time(s). (%u, %u)\n",
__FUNCTION__, __LINE__, event.data.mouse.button,
event.data.mouse.clicks,
event.data.mouse.x, event.data.mouse.y);
// Fire mouse released event.
dispatch_event(&event);
// If the pressed event was not consumed...
if (event.reserved ^ 0x01 && hook->input.mouse.is_dragged != true) {
// Populate mouse clicked event.
event.time = timestamp;
event.reserved = 0x00;
event.type = EVENT_MOUSE_CLICKED;
event.mask = get_modifiers();
event.data.mouse.button = button;
event.data.mouse.clicks = hook->input.mouse.click.count;
event.data.mouse.x = data->event.u.keyButtonPointer.rootX;
event.data.mouse.y = data->event.u.keyButtonPointer.rootY;
#if defined(USE_XINERAMA) || defined(USE_XRANDR)
uint8_t count;
screen_data *screens = hook_create_screen_info(&count);
if (count > 1) {
event.data.mouse.x -= screens[0].x;
event.data.mouse.y -= screens[0].y;
}
if (screens != NULL) {
free(screens);
}
#endif
logger(LOG_LEVEL_INFO, "%s [%u]: Button %u clicked %u time(s). (%u, %u)\n",
__FUNCTION__, __LINE__, event.data.mouse.button,
event.data.mouse.clicks,
event.data.mouse.x, event.data.mouse.y);
// Fire mouse clicked event.
dispatch_event(&event);
}
// Reset the number of clicks.
if (button == hook->input.mouse.click.button && (long int) (event.time - hook->input.mouse.click.time) > hook_get_multi_click_time()) {
// Reset the click count.
hook->input.mouse.click.count = 0;
}
}
} else if (data->type == MotionNotify) {
// Reset the click count.
if (hook->input.mouse.click.count != 0 && (long int) (timestamp - hook->input.mouse.click.time) > hook_get_multi_click_time()) {
hook->input.mouse.click.count = 0;
}
// Populate mouse move event.
event.time = timestamp;
event.reserved = 0x00;
event.mask = get_modifiers();
// Check the upper half of virtual modifiers for non-zero
// values and set the mouse dragged flag.
hook->input.mouse.is_dragged = ((event.mask & 0x1F00) > 0);
if (hook->input.mouse.is_dragged) {
// Create Mouse Dragged event.
event.type = EVENT_MOUSE_DRAGGED;
} else {
// Create a Mouse Moved event.
event.type = EVENT_MOUSE_MOVED;
}
event.data.mouse.button = MOUSE_NOBUTTON;
event.data.mouse.clicks = hook->input.mouse.click.count;
event.data.mouse.x = data->event.u.keyButtonPointer.rootX;
event.data.mouse.y = data->event.u.keyButtonPointer.rootY;
#if defined(USE_XINERAMA) || defined(USE_XRANDR)
uint8_t count;
screen_data *screens = hook_create_screen_info(&count);
if (count > 1) {
event.data.mouse.x -= screens[0].x;
event.data.mouse.y -= screens[0].y;
}
if (screens != NULL) {
free(screens);
}
#endif
logger(LOG_LEVEL_INFO, "%s [%u]: Mouse %s to %i, %i. (%#X)\n",
__FUNCTION__, __LINE__, hook->input.mouse.is_dragged ? "dragged" : "moved",
event.data.mouse.x, event.data.mouse.y, event.mask);
// Fire mouse move event.
dispatch_event(&event);
} else {
// In theory this *should* never execute.
logger(LOG_LEVEL_DEBUG, "%s [%u]: Unhandled X11 event: %#X.\n",
__FUNCTION__, __LINE__, (unsigned int) data->type);
}
} else {
logger(LOG_LEVEL_WARN, "%s [%u]: Unhandled X11 hook category! (%#X)\n",
__FUNCTION__, __LINE__, recorded_data->category);
}
// TODO There is no way to consume the XRecord event.
XRecordFreeData(recorded_data);
}
static inline bool enable_key_repeate() {
// Attempt to setup detectable autorepeat.
// NOTE: is_auto_repeat is NOT stdbool!
Bool is_auto_repeat = False;
#ifdef USE_XKB
// Enable detectable auto-repeat.
XkbSetDetectableAutoRepeat(hook->ctrl.display, True, &is_auto_repeat);
#else
XAutoRepeatOn(hook->ctrl.display);
XKeyboardState kb_state;
XGetKeyboardControl(hook->ctrl.display, &kb_state);
is_auto_repeat = (kb_state.global_auto_repeat == AutoRepeatModeOn);
#endif
return is_auto_repeat;
}
static inline int xrecord_block() {
int status = IOHOOK_FAILURE;
// Save the data display associated with this hook so it is passed to each event.
//XPointer closeure = (XPointer) (ctrl_display);
XPointer closeure = NULL;
#ifdef USE_XRECORD_ASYNC
// Async requires that we loop so that our thread does not return.
if (XRecordEnableContextAsync(hook->data.display, context, hook_event_proc, closeure) != 0) {
// Time in MS to sleep the runloop.
int timesleep = 100;
// Allow the thread loop to block.
pthread_mutex_lock(&hook_xrecord_mutex);
running = true;
do {
// Unlock the mutex from the previous iteration.
pthread_mutex_unlock(&hook_xrecord_mutex);
XRecordProcessReplies(hook->data.display);
// Prevent 100% CPU utilization.
struct timeval tv;
gettimeofday(&tv, NULL);
struct timespec ts;
ts.tv_sec = time(NULL) + timesleep / 1000;
ts.tv_nsec = tv.tv_usec * 1000 + 1000 * 1000 * (timesleep % 1000);
ts.tv_sec += ts.tv_nsec / (1000 * 1000 * 1000);
ts.tv_nsec %= (1000 * 1000 * 1000);
pthread_mutex_lock(&hook_xrecord_mutex);
pthread_cond_timedwait(&hook_xrecord_cond, &hook_xrecord_mutex, &ts);
} while (running);
// Unlock after loop exit.
pthread_mutex_unlock(&hook_xrecord_mutex);
// Set the exit status.
status = NULL;
}
#else
// Sync blocks until XRecordDisableContext() is called.
if (XRecordEnableContext(hook->data.display, hook->ctrl.context, hook_event_proc, closeure) != 0) {
status = IOHOOK_SUCCESS;
}
#endif
else {
logger(LOG_LEVEL_ERROR, "%s [%u]: XRecordEnableContext failure!\n",
__FUNCTION__, __LINE__);
#ifdef USE_XRECORD_ASYNC
// Reset the running state.
pthread_mutex_lock(&hook_xrecord_mutex);
running = false;
pthread_mutex_unlock(&hook_xrecord_mutex);
#endif
// Set the exit status.
status = IOHOOK_ERROR_X_RECORD_ENABLE_CONTEXT;
}
return status;
}
static int xrecord_alloc() {
int status = IOHOOK_FAILURE;
// Make sure the data display is synchronized to prevent late event delivery!
// See Bug 42356 for more information.
// https://bugs.freedesktop.org/show_bug.cgi?id=42356#c4
XSynchronize(hook->data.display, True);
// Setup XRecord range.
XRecordClientSpec clients = XRecordAllClients;
hook->data.range = XRecordAllocRange();
if (hook->data.range != NULL) {
logger(LOG_LEVEL_DEBUG, "%s [%u]: XRecordAllocRange successful.\n",
__FUNCTION__, __LINE__);
hook->data.range->device_events.first = KeyPress;
hook->data.range->device_events.last = MotionNotify;
// Note that the documentation for this function is incorrect,
// hook->data.display should be used!
// See: http://www.x.org/releases/X11R7.6/doc/libXtst/recordlib.txt
hook->ctrl.context = XRecordCreateContext(hook->data.display, XRecordFromServerTime, &clients, 1, &hook->data.range, 1);
if (hook->ctrl.context != 0) {
logger(LOG_LEVEL_DEBUG, "%s [%u]: XRecordCreateContext successful.\n",
__FUNCTION__, __LINE__);
// Block until hook_stop() is called.
status = xrecord_block();
// Free up the context if it was set.
XRecordFreeContext(hook->data.display, hook->ctrl.context);
} else {
logger(LOG_LEVEL_ERROR, "%s [%u]: XRecordCreateContext failure!\n",
__FUNCTION__, __LINE__);
// Set the exit status.
status = IOHOOK_ERROR_X_RECORD_CREATE_CONTEXT;
}
// Free the XRecord range.
XFree(hook->data.range);
} else {
logger(LOG_LEVEL_ERROR, "%s [%u]: XRecordAllocRange failure!\n",
__FUNCTION__, __LINE__);
// Set the exit status.
status = IOHOOK_ERROR_X_RECORD_ALLOC_RANGE;
}
return status;
}
static int xrecord_query() {
int status = IOHOOK_FAILURE;
// Check to make sure XRecord is installed and enabled.
int major, minor;
if (XRecordQueryVersion(hook->ctrl.display, &major, &minor) != 0) {
logger(LOG_LEVEL_INFO, "%s [%u]: XRecord version: %i.%i.\n",
__FUNCTION__, __LINE__, major, minor);
status = xrecord_alloc();
} else {
logger(LOG_LEVEL_ERROR, "%s [%u]: XRecord is not currently available!\n",
__FUNCTION__, __LINE__);
status = IOHOOK_ERROR_X_RECORD_NOT_FOUND;
}
return status;
}
static int xrecord_start() {
int status = IOHOOK_FAILURE;
// Open the control display for XRecord.
hook->ctrl.display = XOpenDisplay(NULL);
// Open a data display for XRecord.
// NOTE This display must be opened on the same thread as XRecord.
hook->data.display = XOpenDisplay(NULL);
if (hook->ctrl.display != NULL && hook->data.display != NULL) {
logger(LOG_LEVEL_DEBUG, "%s [%u]: XOpenDisplay successful.\n",
__FUNCTION__, __LINE__);
bool is_auto_repeat = enable_key_repeate();
if (is_auto_repeat) {
logger(LOG_LEVEL_DEBUG, "%s [%u]: Successfully enabled detectable autorepeat.\n",
__FUNCTION__, __LINE__);
} else {
logger(LOG_LEVEL_WARN, "%s [%u]: Could not enable detectable auto-repeat!\n",
__FUNCTION__, __LINE__);
}
#if defined(USE_XKBCOMMON)
// Open XCB Connection
hook->input.connection = XGetXCBConnection(hook->ctrl.display);
int xcb_status = xcb_connection_has_error(hook->input.connection);
if (xcb_status <= 0) {
// Initialize xkbcommon context.
struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (context != NULL) {
hook->input.context = xkb_context_ref(context);
} else {
logger(LOG_LEVEL_ERROR, "%s [%u]: xkb_context_new failure!\n",
__FUNCTION__, __LINE__);
}
} else {
logger(LOG_LEVEL_ERROR, "%s [%u]: xcb_connect failure! (%d)\n",
__FUNCTION__, __LINE__, xcb_status);
}
#endif
#ifdef USE_XKBCOMMON
state = create_xkb_state(hook->input.context, hook->input.connection);
#endif
// Initialize starting modifiers.
initialize_modifiers();
status = xrecord_query();
#ifdef USE_XKBCOMMON
if (state != NULL) {
destroy_xkb_state(state);
}
if (hook->input.context != NULL) {
xkb_context_unref(hook->input.context);
hook->input.context = NULL;
}
if (hook->input.connection != NULL) {
// xcb_disconnect(hook->input.connection);
hook->input.connection = NULL;
}
#endif
} else {
logger(LOG_LEVEL_ERROR, "%s [%u]: XOpenDisplay failure!\n",
__FUNCTION__, __LINE__);
status = IOHOOK_ERROR_X_OPEN_DISPLAY;
}
// Close down the XRecord data display.
if (hook->data.display != NULL) {
XCloseDisplay(hook->data.display);
hook->data.display = NULL;
}
// Close down the XRecord control display.
if (hook->ctrl.display) {
XCloseDisplay(hook->ctrl.display);
hook->ctrl.display = NULL;
}
return status;
}
IOHOOK_API int hook_run() {
int status = IOHOOK_FAILURE;
// Hook data for future cleanup.
hook = malloc(sizeof(hook_info));
if (hook != NULL) {
hook->input.mask = 0x0000;
hook->input.mouse.is_dragged = false;
hook->input.mouse.click.count = 0;
hook->input.mouse.click.time = 0;
hook->input.mouse.click.button = MOUSE_NOBUTTON;
status = xrecord_start();
// Free data associated with this hook.
free(hook);
hook = NULL;
} else {
logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to allocate memory for hook structure!\n",
__FUNCTION__, __LINE__);
status = IOHOOK_ERROR_OUT_OF_MEMORY;
}
logger(LOG_LEVEL_DEBUG, "%s [%u]: Something, something, something, complete.\n",
__FUNCTION__, __LINE__);
return status;
}
IOHOOK_API int hook_stop() {
int status = IOHOOK_FAILURE;
if (hook != NULL && hook->ctrl.display != NULL && hook->ctrl.context != 0) {
// We need to make sure the context is still valid.
XRecordState *state = malloc(sizeof(XRecordState));
if (state != NULL) {
if (XRecordGetContext(hook->ctrl.display, hook->ctrl.context, &state) != 0) {
// Try to exit the thread naturally.
if (state->enabled && XRecordDisableContext(hook->ctrl.display, hook->ctrl.context) != 0) {
#ifdef USE_XRECORD_ASYNC
pthread_mutex_lock(&hook_xrecord_mutex);
running = false;
pthread_cond_signal(&hook_xrecord_cond);
pthread_mutex_unlock(&hook_xrecord_mutex);
#endif
// See Bug 42356 for more information.
// https://bugs.freedesktop.org/show_bug.cgi?id=42356#c4
//XFlush(hook->ctrl.display);
XSync(hook->ctrl.display, False);
if (hook->ctrl.display) {
XCloseDisplay(hook->ctrl.display);
hook->ctrl.display = NULL;
}
status = IOHOOK_SUCCESS;
}
} else {
logger(LOG_LEVEL_ERROR, "%s [%u]: XRecordGetContext failure!\n",
__FUNCTION__, __LINE__);
status = IOHOOK_ERROR_X_RECORD_GET_CONTEXT;
}
free(state);
} else {
logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to allocate memory for XRecordState!\n",
__FUNCTION__, __LINE__);
status = IOHOOK_ERROR_OUT_OF_MEMORY;
}
return status;
}
logger(LOG_LEVEL_DEBUG, "%s [%u]: Status: %#X.\n",
__FUNCTION__, __LINE__, status);
return status;
}