diff --git a/src/windows/dispatch_event.c b/src/windows/dispatch_event.c index b06ebfe4..3b9e88ee 100644 --- a/src/windows/dispatch_event.c +++ b/src/windows/dispatch_event.c @@ -15,3 +15,461 @@ * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ + +#include + +#include "dispatch_event.h" +#include "input_helper.h" +#include "logger.h" + +// Virtual event pointer. +static uiohook_event uio_event; + +// Click count globals. +static unsigned short click_count = 0; +static uint64_t click_time = 0; +static unsigned short int click_button = MOUSE_NOBUTTON; +static POINT last_click; + +// Event dispatch callback. +static dispatcher_t dispatch = NULL; +static void *dispatch_data = NULL; + + +UIOHOOK_API void hook_set_dispatch_proc(dispatcher_t dispatch_proc, void *user_data) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Setting new dispatch callback to %#p.\n", + __FUNCTION__, __LINE__, dispatch_proc); + + dispatch = dispatch_proc; + dispatch_data = user_data; +} + +#ifdef USE_EPOCH_TIME +static uint64_t get_unix_timestamp() { + // Get the local system time in UTC. + GetSystemTimeAsFileTime(&system_time); + + // Convert the local system time to a Unix epoch in MS. + // milliseconds = 100-nanoseconds / 10000 + uint64_t timestamp = (((uint64_t) system_time.dwHighDateTime << 32) | system_time.dwLowDateTime) / 10000; + + // Convert Windows epoch to Unix epoch. (1970 - 1601 in milliseconds) + timestamp -= 11644473600000; + + return timestamp; +} +#endif + +// Send out an event if a dispatcher was set. +static void dispatch_event(uiohook_event *const event) { + if (dispatch != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Dispatching event type %u.\n", + __FUNCTION__, __LINE__, event->type); + + dispatch(event, dispatch_data); + } else { + logger(LOG_LEVEL_WARN, "%s [%u]: No dispatch callback set!\n", + __FUNCTION__, __LINE__); + } +} + +bool dispatch_hook_enable() { + bool consumed = false; + // Initialize native input helper functions. + load_input_helper(); + + // Get the local system time in UNIX epoch form. + #ifdef USE_EPOCH_TIME + uint64_t timestamp = get_unix_timestamp(); + #else + uint64_t timestamp = GetMessageTime(); + #endif + + // Populate the hook start event. + uio_event.time = timestamp; + uio_event.reserved = 0x00; + + uio_event.type = EVENT_HOOK_ENABLED; + uio_event.mask = 0x00; + + // Fire the hook start event. + dispatch_event(&uio_event); + consumed = uio_event.reserved & 0x01; + + return consumed; +} + +bool dispatch_hook_disable() { + bool consumed = false; + // Get the local system time in UNIX epoch form. + #ifdef USE_EPOCH_TIME + uint64_t timestamp = get_unix_timestamp(); + #else + uint64_t timestamp = GetMessageTime(); + #endif + + // Populate the hook stop event. + uio_event.time = timestamp; + uio_event.reserved = 0x00; + + uio_event.type = EVENT_HOOK_DISABLED; + uio_event.mask = 0x00; + + // Fire the hook stop event. + dispatch_event(&uio_event); + consumed = uio_event.reserved & 0x01; + + // Deinitialize native input helper functions. + unload_input_helper(); + + return consumed; +} + +bool dispatch_key_press(KBDLLHOOKSTRUCT *kbhook) { + bool consumed = false; + #ifdef USE_EPOCH_TIME + uint64_t timestamp = get_unix_timestamp(); + #else + uint64_t timestamp = kbhook->time; + #endif + + // Check and setup modifiers. + if (kbhook->vkCode == VK_LSHIFT) { set_modifier_mask(MASK_SHIFT_L); } + else if (kbhook->vkCode == VK_RSHIFT) { set_modifier_mask(MASK_SHIFT_R); } + else if (kbhook->vkCode == VK_LCONTROL) { set_modifier_mask(MASK_CTRL_L); } + else if (kbhook->vkCode == VK_RCONTROL) { set_modifier_mask(MASK_CTRL_R); } + else if (kbhook->vkCode == VK_LMENU) { set_modifier_mask(MASK_ALT_L); } + else if (kbhook->vkCode == VK_RMENU) { set_modifier_mask(MASK_ALT_R); } + else if (kbhook->vkCode == VK_LWIN) { set_modifier_mask(MASK_META_L); } + else if (kbhook->vkCode == VK_RWIN) { set_modifier_mask(MASK_META_R); } + else if (kbhook->vkCode == VK_NUMLOCK) { set_modifier_mask(MASK_NUM_LOCK); } + else if (kbhook->vkCode == VK_CAPITAL) { set_modifier_mask(MASK_CAPS_LOCK); } + else if (kbhook->vkCode == VK_SCROLL) { set_modifier_mask(MASK_SCROLL_LOCK); } + + // Populate key pressed event. + uio_event.time = timestamp; + uio_event.reserved = 0x00; + + uio_event.type = EVENT_KEY_PRESSED; + uio_event.mask = get_modifiers(); + + uio_event.data.keyboard.keycode = keycode_to_scancode(kbhook->vkCode, kbhook->flags); + uio_event.data.keyboard.rawcode = (uint16_t) kbhook->vkCode; + uio_event.data.keyboard.keychar = CHAR_UNDEFINED; + + logger(LOG_LEVEL_DEBUG, "%s [%u]: Key %#X pressed. (%#X)\n", + __FUNCTION__, __LINE__, + uio_event.data.keyboard.keycode, uio_event.data.keyboard.rawcode); + + // Populate key pressed event. + dispatch_event(&uio_event); + consumed = uio_event.reserved & 0x01; + + // If the pressed event was not consumed... + if (!consumed) { + // Buffer for unicode typed chars. No more than 2 needed. + WCHAR buffer[2]; // = { WCH_NONE }; + + // If the pressed event was not consumed and a unicode char exists... + SIZE_T count = keycode_to_unicode(kbhook->vkCode, buffer, sizeof(buffer)); + for (unsigned int i = 0; i < count; i++) { + // Populate key typed event. + uio_event.time = timestamp; + uio_event.reserved = 0x00; + + uio_event.type = EVENT_KEY_TYPED; + uio_event.mask = get_modifiers(); + + uio_event.data.keyboard.keycode = VC_UNDEFINED; + uio_event.data.keyboard.rawcode = (uint16_t) kbhook->vkCode; + uio_event.data.keyboard.keychar = buffer[i]; + + logger(LOG_LEVEL_DEBUG, "%s [%u]: Key %#X typed. (%lc)\n", + __FUNCTION__, __LINE__, + uio_event.data.keyboard.keycode, (wint_t) uio_event.data.keyboard.keychar); + + // Fire key typed event. + dispatch_event(&uio_event); + consumed = uio_event.reserved & 0x01; + } + } + + return consumed; +} + +bool dispatch_key_release(KBDLLHOOKSTRUCT *kbhook) { + bool consumed = false; + #ifdef USE_EPOCH_TIME + uint64_t timestamp = get_unix_timestamp(); + #else + uint64_t timestamp = kbhook->time; + #endif + + // Check and setup modifiers. + if (kbhook->vkCode == VK_LSHIFT) { unset_modifier_mask(MASK_SHIFT_L); } + else if (kbhook->vkCode == VK_RSHIFT) { unset_modifier_mask(MASK_SHIFT_R); } + else if (kbhook->vkCode == VK_LCONTROL) { unset_modifier_mask(MASK_CTRL_L); } + else if (kbhook->vkCode == VK_RCONTROL) { unset_modifier_mask(MASK_CTRL_R); } + else if (kbhook->vkCode == VK_LMENU) { unset_modifier_mask(MASK_ALT_L); } + else if (kbhook->vkCode == VK_RMENU) { unset_modifier_mask(MASK_ALT_R); } + else if (kbhook->vkCode == VK_LWIN) { unset_modifier_mask(MASK_META_L); } + else if (kbhook->vkCode == VK_RWIN) { unset_modifier_mask(MASK_META_R); } + else if (kbhook->vkCode == VK_NUMLOCK) { unset_modifier_mask(MASK_NUM_LOCK); } + else if (kbhook->vkCode == VK_CAPITAL) { unset_modifier_mask(MASK_CAPS_LOCK); } + else if (kbhook->vkCode == VK_SCROLL) { unset_modifier_mask(MASK_SCROLL_LOCK); } + + // Populate key pressed event. + uio_event.time = timestamp; + uio_event.reserved = 0x00; + + uio_event.type = EVENT_KEY_RELEASED; + uio_event.mask = get_modifiers(); + + uio_event.data.keyboard.keycode = keycode_to_scancode(kbhook->vkCode, kbhook->flags); + uio_event.data.keyboard.rawcode = (uint16_t) kbhook->vkCode; + uio_event.data.keyboard.keychar = CHAR_UNDEFINED; + + logger(LOG_LEVEL_DEBUG, "%s [%u]: Key %#X released. (%#X)\n", + __FUNCTION__, __LINE__, + uio_event.data.keyboard.keycode, uio_event.data.keyboard.rawcode); + + // Fire key released event. + dispatch_event(&uio_event); + consumed = uio_event.reserved & 0x01; + + return consumed; +} + +bool dispatch_button_press(MSLLHOOKSTRUCT *mshook, uint16_t button) { + bool consumed = false; + #ifdef USE_EPOCH_TIME + uint64_t timestamp = get_unix_timestamp(); + #else + uint64_t timestamp = mshook->time; + #endif + + // Track the number of clicks, the button must match the previous button. + if (button == click_button && (long int) (timestamp - click_time) <= hook_get_multi_click_time()) { + if (click_count < USHRT_MAX) { + click_count++; + } else { + logger(LOG_LEVEL_WARN, "%s [%u]: Click count overflow detected!\n", + __FUNCTION__, __LINE__); + } + } else { + // Reset the click count. + click_count = 1; + + // Set the previous button. + click_button = button; + } + + // Save this events time to calculate the click_count. + click_time = timestamp; + + // Store the last click point. + last_click.x = mshook->pt.x; + last_click.y = mshook->pt.y; + + // Populate mouse pressed event. + uio_event.time = timestamp; + uio_event.reserved = 0x00; + + uio_event.type = EVENT_MOUSE_PRESSED; + uio_event.mask = get_modifiers(); + + uio_event.data.mouse.button = button; + uio_event.data.mouse.clicks = click_count; + + uio_event.data.mouse.x = (int16_t) mshook->pt.x; + uio_event.data.mouse.y = (int16_t) mshook->pt.y; + + logger(LOG_LEVEL_DEBUG, "%s [%u]: Button %u pressed %u time(s). (%u, %u)\n", + __FUNCTION__, __LINE__, + uio_event.data.mouse.button, uio_event.data.mouse.clicks, + uio_event.data.mouse.x, uio_event.data.mouse.y); + + // Fire mouse pressed event. + dispatch_event(&uio_event); + consumed = uio_event.reserved & 0x01; + + return consumed; +} + +bool dispatch_button_release(MSLLHOOKSTRUCT *mshook, uint16_t button) { + bool consumed = false; + #ifdef USE_EPOCH_TIME + uint64_t timestamp = get_unix_timestamp(); + #else + uint64_t timestamp = mshook->time; + #endif + + // Populate mouse released event. + uio_event.time = timestamp; + uio_event.reserved = 0x00; + + uio_event.type = EVENT_MOUSE_RELEASED; + uio_event.mask = get_modifiers(); + + uio_event.data.mouse.button = button; + uio_event.data.mouse.clicks = click_count; + + uio_event.data.mouse.x = (int16_t) mshook->pt.x; + uio_event.data.mouse.y = (int16_t) mshook->pt.y; + + logger(LOG_LEVEL_DEBUG, "%s [%u]: Button %u released %u time(s). (%u, %u)\n", + __FUNCTION__, __LINE__, + uio_event.data.mouse.button, uio_event.data.mouse.clicks, + uio_event.data.mouse.x, uio_event.data.mouse.y); + + // Fire mouse released event. + dispatch_event(&uio_event); + consumed = uio_event.reserved & 0x01; + + // If the pressed event was not consumed... + if (!consumed && last_click.x == mshook->pt.x && last_click.y == mshook->pt.y) { + // Populate mouse clicked event. + uio_event.time = timestamp; + uio_event.reserved = 0x00; + + uio_event.type = EVENT_MOUSE_CLICKED; + uio_event.mask = get_modifiers(); + + uio_event.data.mouse.button = button; + uio_event.data.mouse.clicks = click_count; + uio_event.data.mouse.x = (int16_t) mshook->pt.x; + uio_event.data.mouse.y = (int16_t) mshook->pt.y; + + logger(LOG_LEVEL_DEBUG, "%s [%u]: Button %u clicked %u time(s). (%u, %u)\n", + __FUNCTION__, __LINE__, + uio_event.data.mouse.button, uio_event.data.mouse.clicks, + uio_event.data.mouse.x, uio_event.data.mouse.y); + + // Fire mouse clicked event. + dispatch_event(&uio_event); + consumed = uio_event.reserved & 0x01; + } + + // Reset the number of clicks. + if (button == click_button && (long int) (timestamp - click_time) > hook_get_multi_click_time()) { + // Reset the click count. + click_count = 0; + } + + return consumed; +} + + +bool dispatch_mouse_move(MSLLHOOKSTRUCT *mshook) { + bool consumed = false; + #ifdef USE_EPOCH_TIME + uint64_t timestamp = get_unix_timestamp(); + #else + uint64_t timestamp = mshook->time; + #endif + + // We received a mouse move event with the mouse actually moving. + // This verifies that the mouse was moved after being depressed. + if (last_click.x != mshook->pt.x || last_click.y != mshook->pt.y) { + // Reset the click count. + if (click_count != 0 && (long) (timestamp - click_time) > hook_get_multi_click_time()) { + click_count = 0; + } + + // Populate mouse move event. + uio_event.time = timestamp; + uio_event.reserved = 0x00; + + uio_event.mask = get_modifiers(); + + // Check the modifier mask range for MASK_BUTTON1 - 5. + bool mouse_dragged = uio_event.mask & (MASK_BUTTON1 | MASK_BUTTON2 | MASK_BUTTON3 | MASK_BUTTON4 | MASK_BUTTON5); + if (mouse_dragged) { + // Create Mouse Dragged event. + uio_event.type = EVENT_MOUSE_DRAGGED; + } else { + // Create a Mouse Moved event. + uio_event.type = EVENT_MOUSE_MOVED; + } + + uio_event.data.mouse.button = MOUSE_NOBUTTON; + uio_event.data.mouse.clicks = click_count; + uio_event.data.mouse.x = (int16_t) mshook->pt.x; + uio_event.data.mouse.y = (int16_t) mshook->pt.y; + + logger(LOG_LEVEL_DEBUG, "%s [%u]: Mouse %s to %u, %u.\n", + __FUNCTION__, __LINE__, + mouse_dragged ? "dragged" : "moved", + uio_event.data.mouse.x, uio_event.data.mouse.y); + + // Fire mouse move event. + dispatch_event(&uio_event); + consumed = uio_event.reserved & 0x01; + } + + return consumed; +} + +bool dispatch_mouse_wheel(MSLLHOOKSTRUCT *mshook, uint8_t direction) { + bool consumed = false; + #ifdef USE_EPOCH_TIME + uint64_t timestamp = get_unix_timestamp(); + #else + uint64_t timestamp = mshook->time; + #endif + + // Track the number of clicks. + // Reset the click count and previous button. + click_count = 1; + click_button = MOUSE_NOBUTTON; + + // Populate mouse wheel event. + uio_event.time = timestamp; + uio_event.reserved = 0x00; + + uio_event.type = EVENT_MOUSE_WHEEL; + uio_event.mask = get_modifiers(); + + uio_event.data.wheel.clicks = click_count; + uio_event.data.wheel.x = (int16_t) mshook->pt.x; + uio_event.data.wheel.y = (int16_t) mshook->pt.y; + + uio_event.data.wheel.rotation = get_scroll_wheel_rotation(mshook->mouseData, direction); + + UINT uiAction = SPI_GETWHEELSCROLLCHARS; + if (direction == WHEEL_VERTICAL_DIRECTION) { + uiAction = SPI_GETWHEELSCROLLLINES; + } + + UINT wheel_amount = 3; + if (SystemParametersInfo(uiAction, 0, &wheel_amount, 0)) { + if (wheel_amount == WHEEL_PAGESCROLL) { + uio_event.data.wheel.type = WHEEL_BLOCK_SCROLL; + uio_event.data.wheel.amount = 1; + } else { + uio_event.data.wheel.type = WHEEL_UNIT_SCROLL; + uio_event.data.wheel.amount = (uint16_t) wheel_amount; + } + + // Set the direction based on what event was received. + uio_event.data.wheel.direction = direction; + + logger(LOG_LEVEL_DEBUG, "%s [%u]: Mouse wheel type %u, rotated %i units in the %u direction at %u, %u.\n", + __FUNCTION__, __LINE__, + uio_event.data.wheel.type, + uio_event.data.wheel.amount * uio_event.data.wheel.rotation, + uio_event.data.wheel.direction, + uio_event.data.wheel.x, uio_event.data.wheel.y); + + // Fire mouse wheel event. + dispatch_event(&uio_event); + consumed = uio_event.reserved & 0x01; + } else { + logger(LOG_LEVEL_WARN, "%s [%u]: SystemParametersInfo() failed, event will be consumed.\n", + __FUNCTION__, __LINE__); + + consumed = true; + } + + return consumed; +} diff --git a/src/windows/dispatch_event.h b/src/windows/dispatch_event.h index b06ebfe4..3ac9edf0 100644 --- a/src/windows/dispatch_event.h +++ b/src/windows/dispatch_event.h @@ -15,3 +15,22 @@ * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ + +#include +#include + +extern bool dispatch_hook_enable(); + +extern bool dispatch_hook_disable(); + +extern bool dispatch_key_press(KBDLLHOOKSTRUCT *kbhook); + +extern bool dispatch_key_release(KBDLLHOOKSTRUCT *kbhook); + +extern bool dispatch_button_press(MSLLHOOKSTRUCT *mshook, uint16_t button); + +extern bool dispatch_button_release(MSLLHOOKSTRUCT *mshook, uint16_t button); + +extern bool dispatch_mouse_move(MSLLHOOKSTRUCT *mshook); + +extern bool dispatch_mouse_wheel(MSLLHOOKSTRUCT *mshook, uint8_t direction); diff --git a/src/windows/input_helper.c b/src/windows/input_helper.c index 4d0afb9a..c2225f84 100644 --- a/src/windows/input_helper.c +++ b/src/windows/input_helper.c @@ -27,6 +27,9 @@ #include "logger.h" #include "input_helper.h" + +static uint16_t modifier_mask; + static const uint16_t keycode_scancode_table[][2] = { /* idx { vk_code, scancode }, */ /* 0 */ { VC_UNDEFINED, 0x0000 }, // 0x00 @@ -255,10 +258,10 @@ static const uint16_t keycode_scancode_table[][2] = { /* 220 */ { VC_BACK_SLASH, VK_RWIN }, // 0xDC VK_OEM_5 Varies by keyboard. For the US standard keyboard, the '\|' key /* 221 */ { VC_CLOSE_BRACKET, VK_APPS }, // 0xDD VK_OEM_6 Varies by keyboard. For the US standard keyboard, the ']}' key /* 222 */ { VC_QUOTE, 0x0000 }, // 0xDE VK_OEM_7 Varies by keyboard. For the US standard keyboard, the 'single-quote/double-quote' key - /* 223 */ { VC_YEN, VK_SLEEP }, // 0xDF VK_OEM_8 Varies by keyboard. + /* 223 */ { VC_UNDEFINED, VK_SLEEP }, // 0xDF VK_OEM_8 Varies by keyboard. /* 224 */ { VC_UNDEFINED, 0x0000 }, // 0xE0 Reserved /* 225 */ { VC_UNDEFINED, 0x0000 }, // 0xE1 OEM specific - /* 226 */ { VC_LESSER_GREATER, VK_OEM_102 }, // 0xE2 VK_OEM_102 Either the angle bracket key or the backslash key on the RT 102-key keyboard + /* 226 */ { VC_UNDEFINED, VK_OEM_102 }, // 0xE2 VK_OEM_102 Either the angle bracket key or the backslash key on the RT 102-key keyboard /* 227 */ { VC_UNDEFINED, 0x0000 }, // 0xE3 OEM specific /* 228 */ { VC_UNDEFINED, 0x00E5 }, // 0xE4 VC_APP_PICTURES OEM specific /* 229 */ { VC_APP_PICTURES, VK_BROWSER_SEARCH }, // 0xE5 VK_PROCESSKEY IME PROCESS key @@ -286,7 +289,7 @@ static const uint16_t keycode_scancode_table[][2] = { /* 251 */ { VC_UNDEFINED, 0x0000 }, // 0xFB VK_ZOOM Zoom key /* 252 */ { VC_UNDEFINED, 0x0000 }, // 0xFC VK_NONAME Reserved /* 253 */ { VC_UNDEFINED, 0x0000 }, // 0xFD - /* 254 */ { VC_CLEAR, 0x0000 }, // 0xFE VK_OEM_CLEAR Clear key + /* 254 */ { VC_KP_CLEAR, 0x0000 }, // 0xFE VK_OEM_CLEAR Clear key /* 255 */ { VC_UNDEFINED, 0x0000 } // 0xFE Unassigned }; @@ -349,6 +352,18 @@ DWORD scancode_to_keycode(unsigned short scancode) { return keycode; } +void set_modifier_mask(uint16_t mask) { + modifier_mask |= mask; +} + +void unset_modifier_mask(uint16_t mask) { + modifier_mask &= ~mask; +} + +uint16_t get_modifiers() { + return modifier_mask; +} + /* Track the amount of vertical and horizontal rotation between "clicks." * This is between mouse wheel delta. */ static int16_t v_rotation, h_rotation; diff --git a/src/windows/input_helper.h b/src/windows/input_helper.h index bfff4c0f..1a4273c8 100644 --- a/src/windows/input_helper.h +++ b/src/windows/input_helper.h @@ -134,6 +134,15 @@ extern unsigned short keycode_to_scancode(DWORD vk_code, DWORD flags); extern DWORD scancode_to_keycode(unsigned short scancode); +/* Set the native modifier mask for future events. */ +extern void set_modifier_mask(uint16_t mask); + +/* Unset the native modifier mask for future events. */ +extern void unset_modifier_mask(uint16_t mask); + +/* Get the current native modifier mask state. */ +extern uint16_t get_modifiers(); + /* Help track how much rotation should be applied to a scroll wheel event. */ extern int16_t get_scroll_wheel_rotation(DWORD data, uint8_t direction); diff --git a/src/windows/input_hook.c b/src/windows/input_hook.c index 2e053427..10e8f5b9 100644 --- a/src/windows/input_hook.c +++ b/src/windows/input_hook.c @@ -20,6 +20,7 @@ #include #include +#include "dispatch_event.h" #include "input_helper.h" #include "logger.h" @@ -39,56 +40,6 @@ static unsigned short int current_modifiers = 0x0000; static FILETIME system_time; #endif -// Click count globals. -static unsigned short click_count = 0; -static uint64_t click_time = 0; -static unsigned short int click_button = MOUSE_NOBUTTON; -static POINT last_click; - -// Static event memory. -static uiohook_event event; - -// Event dispatch callback. -static dispatcher_t dispatch = NULL; -static void *dispatch_data = NULL; - -UIOHOOK_API void hook_set_dispatch_proc(dispatcher_t dispatch_proc, void *user_data) { - logger(LOG_LEVEL_DEBUG, "%s [%u]: Setting new dispatch callback to %#p.\n", - __FUNCTION__, __LINE__, dispatch_proc); - - dispatch = dispatch_proc; - dispatch_data = user_data; -} - -// Send out an event if a dispatcher was set. -static void dispatch_event(uiohook_event *const event) { - if (dispatch != NULL) { - logger(LOG_LEVEL_DEBUG, "%s [%u]: Dispatching event type %u.\n", - __FUNCTION__, __LINE__, event->type); - - dispatch(event, dispatch_data); - } 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(unsigned short int mask) { - current_modifiers |= mask; -} - -// Unset the native modifier mask for future events. -static inline void unset_modifier_mask(unsigned short int mask) { - current_modifiers &= ~mask; -} - -// Get the current native modifier mask state. -static inline unsigned short int get_modifiers() { - return current_modifiers; -} - // Initialize the modifier mask to the current modifiers. static void initialize_modifiers() { current_modifiers = 0x0000; @@ -114,23 +65,6 @@ static void initialize_modifiers() { if (GetKeyState(VK_SCROLL) < 0) { set_modifier_mask(MASK_SCROLL_LOCK); } } - -#ifdef USE_EPOCH_TIME -static inline uint64_t get_unix_timestamp() { - // Get the local system time in UTC. - GetSystemTimeAsFileTime(&system_time); - - // Convert the local system time to a Unix epoch in MS. - // milliseconds = 100-nanoseconds / 10000 - uint64_t timestamp = (((uint64_t) system_time.dwHighDateTime << 32) | system_time.dwLowDateTime) / 10000; - - // Convert Windows epoch to Unix epoch. (1970 - 1601 in milliseconds) - timestamp -= 11644473600000; - - return timestamp; -} -#endif - void unregister_running_hooks() { // Stop the event hook and any timer still running. if (win_event_hhook != NULL) { @@ -150,164 +84,19 @@ void unregister_running_hooks() { } } -void hook_start_proc() { - // Initialize native input helper functions. - load_input_helper(); - - // Get the local system time in UNIX epoch form. - #ifdef USE_EPOCH_TIME - uint64_t timestamp = get_unix_timestamp(); - #else - uint64_t timestamp = GetMessageTime(); - #endif - - // 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); -} - -void hook_stop_proc() { - // Get the local system time in UNIX epoch form. - #ifdef USE_EPOCH_TIME - uint64_t timestamp = get_unix_timestamp(); - #else - uint64_t timestamp = GetMessageTime(); - #endif - - // 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); - - // Deinitialize native input helper functions. - unload_input_helper(); -} - -static void process_key_pressed(KBDLLHOOKSTRUCT *kbhook) { - #ifdef USE_EPOCH_TIME - uint64_t timestamp = get_unix_timestamp(); - #else - uint64_t timestamp = kbhook->time; - #endif - - // Check and setup modifiers. - if (kbhook->vkCode == VK_LSHIFT) { set_modifier_mask(MASK_SHIFT_L); } - else if (kbhook->vkCode == VK_RSHIFT) { set_modifier_mask(MASK_SHIFT_R); } - else if (kbhook->vkCode == VK_LCONTROL) { set_modifier_mask(MASK_CTRL_L); } - else if (kbhook->vkCode == VK_RCONTROL) { set_modifier_mask(MASK_CTRL_R); } - else if (kbhook->vkCode == VK_LMENU) { set_modifier_mask(MASK_ALT_L); } - else if (kbhook->vkCode == VK_RMENU) { set_modifier_mask(MASK_ALT_R); } - else if (kbhook->vkCode == VK_LWIN) { set_modifier_mask(MASK_META_L); } - else if (kbhook->vkCode == VK_RWIN) { set_modifier_mask(MASK_META_R); } - else if (kbhook->vkCode == VK_NUMLOCK) { set_modifier_mask(MASK_NUM_LOCK); } - else if (kbhook->vkCode == VK_CAPITAL) { set_modifier_mask(MASK_CAPS_LOCK); } - else if (kbhook->vkCode == VK_SCROLL) { set_modifier_mask(MASK_SCROLL_LOCK); } - - // Populate key pressed event. - event.time = timestamp; - event.reserved = 0x00; - - event.type = EVENT_KEY_PRESSED; - event.mask = get_modifiers(); - - event.data.keyboard.keycode = keycode_to_scancode(kbhook->vkCode, kbhook->flags); - event.data.keyboard.rawcode = (uint16_t) kbhook->vkCode; - event.data.keyboard.keychar = CHAR_UNDEFINED; - - logger(LOG_LEVEL_DEBUG, "%s [%u]: Key %#X pressed. (%#X)\n", - __FUNCTION__, __LINE__, event.data.keyboard.keycode, event.data.keyboard.rawcode); - - // Populate key pressed event. - dispatch_event(&event); - - // If the pressed event was not consumed... - if (event.reserved ^ 0x01) { - // Buffer for unicode typed chars. No more than 2 needed. - WCHAR buffer[2]; // = { WCH_NONE }; - - // If the pressed event was not consumed and a unicode char exists... - SIZE_T count = keycode_to_unicode(kbhook->vkCode, buffer, sizeof(buffer)); - for (unsigned int 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 = (uint16_t) kbhook->vkCode; - event.data.keyboard.keychar = buffer[i]; - - logger(LOG_LEVEL_DEBUG, "%s [%u]: Key %#X typed. (%lc)\n", - __FUNCTION__, __LINE__, event.data.keyboard.keycode, (wint_t) event.data.keyboard.keychar); - - // Fire key typed event. - dispatch_event(&event); - } - } -} - -static void process_key_released(KBDLLHOOKSTRUCT *kbhook) { - #ifdef USE_EPOCH_TIME - uint64_t timestamp = get_unix_timestamp(); - #else - uint64_t timestamp = kbhook->time; - #endif - - // Check and setup modifiers. - if (kbhook->vkCode == VK_LSHIFT) { unset_modifier_mask(MASK_SHIFT_L); } - else if (kbhook->vkCode == VK_RSHIFT) { unset_modifier_mask(MASK_SHIFT_R); } - else if (kbhook->vkCode == VK_LCONTROL) { unset_modifier_mask(MASK_CTRL_L); } - else if (kbhook->vkCode == VK_RCONTROL) { unset_modifier_mask(MASK_CTRL_R); } - else if (kbhook->vkCode == VK_LMENU) { unset_modifier_mask(MASK_ALT_L); } - else if (kbhook->vkCode == VK_RMENU) { unset_modifier_mask(MASK_ALT_R); } - else if (kbhook->vkCode == VK_LWIN) { unset_modifier_mask(MASK_META_L); } - else if (kbhook->vkCode == VK_RWIN) { unset_modifier_mask(MASK_META_R); } - else if (kbhook->vkCode == VK_NUMLOCK) { unset_modifier_mask(MASK_NUM_LOCK); } - else if (kbhook->vkCode == VK_CAPITAL) { unset_modifier_mask(MASK_CAPS_LOCK); } - else if (kbhook->vkCode == VK_SCROLL) { unset_modifier_mask(MASK_SCROLL_LOCK); } - - // Populate key pressed event. - event.time = timestamp; - event.reserved = 0x00; - - event.type = EVENT_KEY_RELEASED; - event.mask = get_modifiers(); - - event.data.keyboard.keycode = keycode_to_scancode(kbhook->vkCode, kbhook->flags); - event.data.keyboard.rawcode = (uint16_t) kbhook->vkCode; - event.data.keyboard.keychar = CHAR_UNDEFINED; - - logger(LOG_LEVEL_DEBUG, "%s [%u]: Key %#X released. (%#X)\n", - __FUNCTION__, __LINE__, event.data.keyboard.keycode, event.data.keyboard.rawcode); - - // Fire key released event. - dispatch_event(&event); -} - LRESULT CALLBACK keyboard_hook_event_proc(int nCode, WPARAM wParam, LPARAM lParam) { + bool consumed = false; + KBDLLHOOKSTRUCT *kbhook = (KBDLLHOOKSTRUCT *) lParam; switch (wParam) { case WM_KEYDOWN: case WM_SYSKEYDOWN: - process_key_pressed(kbhook); + consumed = dispatch_key_press(kbhook); break; case WM_KEYUP: case WM_SYSKEYUP: - process_key_released(kbhook); + consumed = dispatch_key_release(kbhook); break; default: @@ -318,7 +107,7 @@ LRESULT CALLBACK keyboard_hook_event_proc(int nCode, WPARAM wParam, LPARAM lPara } LRESULT hook_result = -1; - if (nCode < 0 || event.reserved ^ 0x01) { + if (nCode < 0 || !consumed) { hook_result = CallNextHookEx(keyboard_event_hhook, nCode, wParam, lParam); } else { logger(LOG_LEVEL_DEBUG, "%s [%u]: Consuming the current event. (%li)\n", @@ -328,245 +117,34 @@ LRESULT CALLBACK keyboard_hook_event_proc(int nCode, WPARAM wParam, LPARAM lPara return hook_result; } - -static void process_button_pressed(MSLLHOOKSTRUCT *mshook, uint16_t button) { - #ifdef USE_EPOCH_TIME - uint64_t timestamp = get_unix_timestamp(); - #else - uint64_t timestamp = mshook->time; - #endif - - // Track the number of clicks, the button must match the previous button. - if (button == click_button && (long int) (timestamp - click_time) <= hook_get_multi_click_time()) { - if (click_count < USHRT_MAX) { - click_count++; - } else { - logger(LOG_LEVEL_WARN, "%s [%u]: Click count overflow detected!\n", - __FUNCTION__, __LINE__); - } - } else { - // Reset the click count. - click_count = 1; - - // Set the previous button. - click_button = button; - } - - // Save this events time to calculate the click_count. - click_time = timestamp; - - // Store the last click point. - last_click.x = mshook->pt.x; - last_click.y = mshook->pt.y; - - // 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 = click_count; - - event.data.mouse.x = (int16_t) mshook->pt.x; - event.data.mouse.y = (int16_t) mshook->pt.y; - - logger(LOG_LEVEL_DEBUG, "%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); -} - -static void process_button_released(MSLLHOOKSTRUCT *mshook, uint16_t button) { - #ifdef USE_EPOCH_TIME - uint64_t timestamp = get_unix_timestamp(); - #else - uint64_t timestamp = mshook->time; - #endif - - // 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 = click_count; - - event.data.mouse.x = (int16_t) mshook->pt.x; - event.data.mouse.y = (int16_t) mshook->pt.y; - - logger(LOG_LEVEL_DEBUG, "%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 && last_click.x == mshook->pt.x && last_click.y == mshook->pt.y) { - // 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 = click_count; - event.data.mouse.x = (int16_t) mshook->pt.x; - event.data.mouse.y = (int16_t) mshook->pt.y; - - logger(LOG_LEVEL_DEBUG, "%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 == click_button && (long int) (timestamp - click_time) > hook_get_multi_click_time()) { - // Reset the click count. - click_count = 0; - } -} - -static void process_mouse_moved(MSLLHOOKSTRUCT *mshook) { - #ifdef USE_EPOCH_TIME - uint64_t timestamp = get_unix_timestamp(); - #else - uint64_t timestamp = mshook->time; - #endif - - // We received a mouse move event with the mouse actually moving. - // This verifies that the mouse was moved after being depressed. - if (last_click.x != mshook->pt.x || last_click.y != mshook->pt.y) { - // Reset the click count. - if (click_count != 0 && (long) (timestamp - click_time) > hook_get_multi_click_time()) { - click_count = 0; - } - - // Populate mouse move event. - event.time = timestamp; - event.reserved = 0x00; - - event.mask = get_modifiers(); - - // Check the modifier mask range for MASK_BUTTON1 - 5. - bool mouse_dragged = event.mask & (MASK_BUTTON1 | MASK_BUTTON2 | MASK_BUTTON3 | MASK_BUTTON4 | MASK_BUTTON5); - if (mouse_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 = click_count; - event.data.mouse.x = (int16_t) mshook->pt.x; - event.data.mouse.y = (int16_t) mshook->pt.y; - - logger(LOG_LEVEL_DEBUG, "%s [%u]: Mouse %s to %u, %u.\n", - __FUNCTION__, __LINE__, mouse_dragged ? "dragged" : "moved", - event.data.mouse.x, event.data.mouse.y); - - // Fire mouse move event. - dispatch_event(&event); - } -} - -static void process_mouse_wheel(MSLLHOOKSTRUCT *mshook, uint8_t direction) { - #ifdef USE_EPOCH_TIME - uint64_t timestamp = get_unix_timestamp(); - #else - uint64_t timestamp = mshook->time; - #endif - - // Track the number of clicks. - // Reset the click count and previous button. - click_count = 1; - click_button = MOUSE_NOBUTTON; - - // Populate mouse wheel event. - event.time = timestamp; - event.reserved = 0x00; - - event.type = EVENT_MOUSE_WHEEL; - event.mask = get_modifiers(); - - event.data.wheel.clicks = click_count; - event.data.wheel.x = (int16_t) mshook->pt.x; - event.data.wheel.y = (int16_t) mshook->pt.y; - - event.data.wheel.rotation = get_scroll_wheel_rotation(mshook->mouseData, direction); - - UINT uiAction = SPI_GETWHEELSCROLLCHARS; - if (direction == WHEEL_VERTICAL_DIRECTION) { - uiAction = SPI_GETWHEELSCROLLLINES; - } - - UINT wheel_amount = 3; - if (SystemParametersInfo(uiAction, 0, &wheel_amount, 0)) { - if (wheel_amount == WHEEL_PAGESCROLL) { - event.data.wheel.type = WHEEL_BLOCK_SCROLL; - event.data.wheel.amount = 1; - } else { - event.data.wheel.type = WHEEL_UNIT_SCROLL; - event.data.wheel.amount = (uint16_t) wheel_amount; - } - - // Set the direction based on what event was received. - event.data.wheel.direction = direction; - - logger(LOG_LEVEL_DEBUG, "%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 { - event.reserved = 0x01; - logger(LOG_LEVEL_WARN, "%s [%u]: SystemParametersInfo() failed, event will be ignored.\n", - __FUNCTION__, __LINE__); - } -} - LRESULT CALLBACK mouse_hook_event_proc(int nCode, WPARAM wParam, LPARAM lParam) { + bool consumed = false; + MSLLHOOKSTRUCT *mshook = (MSLLHOOKSTRUCT *) lParam; switch (wParam) { case WM_LBUTTONDOWN: set_modifier_mask(MASK_BUTTON1); - process_button_pressed(mshook, MOUSE_BUTTON1); + consumed = dispatch_button_press(mshook, MOUSE_BUTTON1); break; case WM_RBUTTONDOWN: set_modifier_mask(MASK_BUTTON2); - process_button_pressed(mshook, MOUSE_BUTTON2); + consumed = dispatch_button_press(mshook, MOUSE_BUTTON2); break; case WM_MBUTTONDOWN: set_modifier_mask(MASK_BUTTON3); - process_button_pressed(mshook, MOUSE_BUTTON3); + consumed = dispatch_button_press(mshook, MOUSE_BUTTON3); break; case WM_XBUTTONDOWN: case WM_NCXBUTTONDOWN: if (HIWORD(mshook->mouseData) == XBUTTON1) { set_modifier_mask(MASK_BUTTON4); - process_button_pressed(mshook, MOUSE_BUTTON4); + consumed = dispatch_button_press(mshook, MOUSE_BUTTON4); } else if (HIWORD(mshook->mouseData) == XBUTTON2) { set_modifier_mask(MASK_BUTTON5); - process_button_pressed(mshook, MOUSE_BUTTON5); + consumed = dispatch_button_press(mshook, MOUSE_BUTTON5); } else { // Extra mouse buttons. uint16_t button = HIWORD(mshook->mouseData); @@ -578,34 +156,34 @@ LRESULT CALLBACK mouse_hook_event_proc(int nCode, WPARAM wParam, LPARAM lParam) set_modifier_mask(MOUSE_BUTTON5); } - process_button_pressed(mshook, button); + consumed = dispatch_button_press(mshook, button); } break; case WM_LBUTTONUP: unset_modifier_mask(MASK_BUTTON1); - process_button_released(mshook, MOUSE_BUTTON1); + consumed = dispatch_button_release(mshook, MOUSE_BUTTON1); break; case WM_RBUTTONUP: unset_modifier_mask(MASK_BUTTON2); - process_button_released(mshook, MOUSE_BUTTON2); + consumed = dispatch_button_release(mshook, MOUSE_BUTTON2); break; case WM_MBUTTONUP: unset_modifier_mask(MASK_BUTTON3); - process_button_released(mshook, MOUSE_BUTTON3); + consumed = dispatch_button_release(mshook, MOUSE_BUTTON3); break; case WM_XBUTTONUP: case WM_NCXBUTTONUP: if (HIWORD(mshook->mouseData) == XBUTTON1) { unset_modifier_mask(MASK_BUTTON4); - process_button_released(mshook, MOUSE_BUTTON4); + consumed = dispatch_button_release(mshook, MOUSE_BUTTON4); } else if (HIWORD(mshook->mouseData) == XBUTTON2) { unset_modifier_mask(MASK_BUTTON5); - process_button_released(mshook, MOUSE_BUTTON5); + consumed = dispatch_button_release(mshook, MOUSE_BUTTON5); } else { // Extra mouse buttons. uint16_t button = HIWORD(mshook->mouseData); @@ -617,16 +195,16 @@ LRESULT CALLBACK mouse_hook_event_proc(int nCode, WPARAM wParam, LPARAM lParam) unset_modifier_mask(MOUSE_BUTTON5); } - process_button_released(mshook, MOUSE_BUTTON5); + consumed = dispatch_button_release(mshook, MOUSE_BUTTON5); } break; case WM_MOUSEMOVE: - process_mouse_moved(mshook); + consumed = dispatch_mouse_move(mshook); break; case WM_MOUSEWHEEL: - process_mouse_wheel(mshook, WHEEL_VERTICAL_DIRECTION); + consumed = dispatch_mouse_wheel(mshook, WHEEL_VERTICAL_DIRECTION); break; /* For horizontal scroll wheel support. @@ -634,7 +212,7 @@ LRESULT CALLBACK mouse_hook_event_proc(int nCode, WPARAM wParam, LPARAM lParam) * case 0x020E: */ case WM_MOUSEHWHEEL: - process_mouse_wheel(mshook, WHEEL_HORIZONTAL_DIRECTION); + consumed = dispatch_mouse_wheel(mshook, WHEEL_HORIZONTAL_DIRECTION); break; default: @@ -645,7 +223,7 @@ LRESULT CALLBACK mouse_hook_event_proc(int nCode, WPARAM wParam, LPARAM lParam) } LRESULT hook_result = -1; - if (nCode < 0 || event.reserved ^ 0x01) { + if (nCode < 0 || !consumed) { hook_result = CallNextHookEx(mouse_event_hhook, nCode, wParam, lParam); } else { logger(LOG_LEVEL_DEBUG, "%s [%u]: Consuming the current event. (%li)\n", @@ -655,7 +233,6 @@ LRESULT CALLBACK mouse_hook_event_proc(int nCode, WPARAM wParam, LPARAM lParam) return hook_result; } - // Callback function that handles events. void CALLBACK win_hook_event_proc(HWINEVENTHOOK hook, DWORD event, HWND hWnd, LONG idObject, LONG idChild, DWORD dwEventThread, DWORD dwmsEventTime) { switch (event) { @@ -750,7 +327,7 @@ UIOHOOK_API int hook_run() { // Windows does not have a hook start event or callback so we need to // manually fake it. - hook_start_proc(); + dispatch_hook_enable(); // Block until the thread receives an WM_QUIT request. MSG message; @@ -771,7 +348,7 @@ UIOHOOK_API int hook_run() { // We must explicitly call the cleanup handler because Windows does not // provide a thread cleanup method like POSIX pthread_cleanup_push/pop. - hook_stop_proc(); + dispatch_hook_disable(); return status; }