/* 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 #endif #define USE_XKB 0 #define USE_XKBCOMMON 0 #include #include #ifdef USE_XRECORD_ASYNC #include #endif #include #include #include #include #include // #ifdef USE_XKB #include #include // #endif #if defined(USE_XINERAMA) && !defined(USE_XRANDR) #include #elif defined(USE_XRANDR) #include #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 >> 8 > 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; }