mirror of
https://github.com/robotn/gohook.git
synced 2024-11-24 05:13:17 +08:00
740 lines
22 KiB
C
740 lines
22 KiB
C
/* 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
|
|
|
|
#include <inttypes.h>
|
|
#include <windows.h>
|
|
|
|
#include "../iohook.h"
|
|
#include "input.h"
|
|
// #include "logger.h"
|
|
|
|
// Thread and hook handles.
|
|
static DWORD hook_thread_id = 0;
|
|
static HHOOK keyboard_event_hhook = NULL, mouse_event_hhook = NULL;
|
|
static HWINEVENTHOOK win_event_hhook = NULL;
|
|
|
|
// The handle to the DLL module pulled in DllMain on DLL_PROCESS_ATTACH.
|
|
extern HINSTANCE hInst;
|
|
|
|
// Modifiers for tracking key masks.
|
|
static unsigned short int current_modifiers = 0x0000;
|
|
|
|
// Click count globals.
|
|
static unsigned short click_count = 0;
|
|
static DWORD click_time = 0;
|
|
static unsigned short int click_button = MOUSE_NOBUTTON;
|
|
static POINT last_click;
|
|
|
|
// Static event memory.
|
|
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(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;
|
|
|
|
// NOTE We are checking the high order bit, so it will be < 0 for a singed short.
|
|
if (GetKeyState(VK_LSHIFT) < 0) { set_modifier_mask(MASK_SHIFT_L); }
|
|
if (GetKeyState(VK_RSHIFT) < 0) { set_modifier_mask(MASK_SHIFT_R); }
|
|
if (GetKeyState(VK_LCONTROL) < 0) { set_modifier_mask(MASK_CTRL_L); }
|
|
if (GetKeyState(VK_RCONTROL) < 0) { set_modifier_mask(MASK_CTRL_R); }
|
|
if (GetKeyState(VK_LMENU) < 0) { set_modifier_mask(MASK_ALT_L); }
|
|
if (GetKeyState(VK_RMENU) < 0) { set_modifier_mask(MASK_ALT_R); }
|
|
if (GetKeyState(VK_LWIN) < 0) { set_modifier_mask(MASK_META_L); }
|
|
if (GetKeyState(VK_RWIN) < 0) { set_modifier_mask(MASK_META_R); }
|
|
|
|
if (GetKeyState(VK_LBUTTON) < 0) { set_modifier_mask(MASK_BUTTON1); }
|
|
if (GetKeyState(VK_RBUTTON) < 0) { set_modifier_mask(MASK_BUTTON2); }
|
|
if (GetKeyState(VK_MBUTTON) < 0) { set_modifier_mask(MASK_BUTTON3); }
|
|
if (GetKeyState(VK_XBUTTON1) < 0) { set_modifier_mask(MASK_BUTTON4); }
|
|
if (GetKeyState(VK_XBUTTON2) < 0) { set_modifier_mask(MASK_BUTTON5); }
|
|
|
|
if (GetKeyState(VK_NUMLOCK) < 0) { set_modifier_mask(MASK_NUM_LOCK); }
|
|
if (GetKeyState(VK_CAPITAL) < 0) { set_modifier_mask(MASK_CAPS_LOCK); }
|
|
if (GetKeyState(VK_SCROLL) < 0) { set_modifier_mask(MASK_SCROLL_LOCK); }
|
|
}
|
|
|
|
|
|
/* Retrieves the mouse wheel scroll type. This function cannot be included as
|
|
* part of the input.h due to platform specific calling restrictions.
|
|
*/
|
|
static unsigned short int get_scroll_wheel_type() {
|
|
unsigned short int value;
|
|
UINT wheel_type;
|
|
|
|
SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &wheel_type, 0);
|
|
if (wheel_type == WHEEL_PAGESCROLL) {
|
|
value = WHEEL_BLOCK_SCROLL;
|
|
} else {
|
|
value = WHEEL_UNIT_SCROLL;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
/* Retrieves the mouse wheel scroll amount. This function cannot be included as
|
|
* part of the input.h due to platform specific calling restrictions.
|
|
*/
|
|
static unsigned short int get_scroll_wheel_amount() {
|
|
unsigned short int value;
|
|
UINT wheel_amount;
|
|
|
|
SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &wheel_amount, 0);
|
|
if (wheel_amount == WHEEL_PAGESCROLL) {
|
|
value = 1;
|
|
} else {
|
|
value = (unsigned short int) wheel_amount;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
void unregister_running_hooks() {
|
|
// Stop the event hook and any timer still running.
|
|
if (win_event_hhook != NULL) {
|
|
UnhookWinEvent(win_event_hhook);
|
|
win_event_hhook = NULL;
|
|
}
|
|
|
|
// Destroy the native hooks.
|
|
if (keyboard_event_hhook != NULL) {
|
|
UnhookWindowsHookEx(keyboard_event_hhook);
|
|
keyboard_event_hhook = NULL;
|
|
}
|
|
|
|
if (mouse_event_hhook != NULL) {
|
|
UnhookWindowsHookEx(mouse_event_hhook);
|
|
mouse_event_hhook = NULL;
|
|
}
|
|
}
|
|
|
|
void hook_start_proc() {
|
|
// Get the local system time in UNIX epoch form.
|
|
uint64_t timestamp = GetMessageTime();
|
|
|
|
// 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.
|
|
uint64_t timestamp = GetMessageTime();
|
|
|
|
// 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);
|
|
}
|
|
|
|
static void process_key_pressed(KBDLLHOOKSTRUCT *kbhook) {
|
|
// 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 = kbhook->time;
|
|
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 = kbhook->vkCode;
|
|
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);
|
|
|
|
// 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));
|
|
unsigned int i;
|
|
for (i = 0; i < count; i++) {
|
|
// Populate key typed event.
|
|
event.time = kbhook->time;
|
|
event.reserved = 0x00;
|
|
|
|
event.type = EVENT_KEY_TYPED;
|
|
event.mask = get_modifiers();
|
|
|
|
event.data.keyboard.keycode = VC_UNDEFINED;
|
|
event.data.keyboard.rawcode = kbhook->vkCode;
|
|
event.data.keyboard.keychar = buffer[i];
|
|
|
|
logger(LOG_LEVEL_INFO, "%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) {
|
|
// 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 = kbhook->time;
|
|
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 = kbhook->vkCode;
|
|
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);
|
|
}
|
|
|
|
LRESULT CALLBACK keyboard_hook_event_proc(int nCode, WPARAM wParam, LPARAM lParam) {
|
|
KBDLLHOOKSTRUCT *kbhook = (KBDLLHOOKSTRUCT *) lParam;
|
|
switch (wParam) {
|
|
case WM_KEYDOWN:
|
|
case WM_SYSKEYDOWN:
|
|
process_key_pressed(kbhook);
|
|
break;
|
|
|
|
case WM_KEYUP:
|
|
case WM_SYSKEYUP:
|
|
process_key_released(kbhook);
|
|
break;
|
|
|
|
default:
|
|
// In theory this *should* never execute.
|
|
logger(LOG_LEVEL_DEBUG, "%s [%u]: Unhandled Windows keyboard event: %#X.\n",
|
|
__FUNCTION__, __LINE__, (unsigned int) wParam);
|
|
break;
|
|
}
|
|
|
|
LRESULT hook_result = -1;
|
|
if (nCode < 0 || event.reserved ^ 0x01) {
|
|
hook_result = CallNextHookEx(keyboard_event_hhook, nCode, wParam, lParam);
|
|
} else {
|
|
logger(LOG_LEVEL_DEBUG, "%s [%u]: Consuming the current event. (%li)\n",
|
|
__FUNCTION__, __LINE__, (long) hook_result);
|
|
}
|
|
|
|
return hook_result;
|
|
}
|
|
|
|
|
|
static void process_button_pressed(MSLLHOOKSTRUCT *mshook, uint16_t button) {
|
|
// uint64_t timestamp = GetMessageTime();
|
|
uint64_t timestamp = mshook->time;
|
|
|
|
// 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 = mshook->pt.x;
|
|
event.data.mouse.y = mshook->pt.y;
|
|
|
|
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);
|
|
}
|
|
|
|
static void process_button_released(MSLLHOOKSTRUCT *mshook, uint16_t button) {
|
|
// Populate mouse released event.
|
|
event.time = mshook->time;
|
|
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 = mshook->pt.x;
|
|
event.data.mouse.y = mshook->pt.y;
|
|
|
|
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 && last_click.x == mshook->pt.x && last_click.y == mshook->pt.y) {
|
|
// Populate mouse clicked event.
|
|
event.time = mshook->time;
|
|
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 = mshook->pt.x;
|
|
event.data.mouse.y = mshook->pt.y;
|
|
|
|
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 == click_button && (long int) (event.time - click_time) > hook_get_multi_click_time()) {
|
|
// Reset the click count.
|
|
click_count = 0;
|
|
}
|
|
}
|
|
|
|
static void process_mouse_moved(MSLLHOOKSTRUCT *mshook) {
|
|
uint64_t timestamp = mshook->time;
|
|
|
|
// 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 = mshook->pt.x;
|
|
event.data.mouse.y = mshook->pt.y;
|
|
|
|
logger(LOG_LEVEL_INFO, "%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) {
|
|
// 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 = mshook->time;
|
|
event.reserved = 0x00;
|
|
|
|
event.type = EVENT_MOUSE_WHEEL;
|
|
event.mask = get_modifiers();
|
|
|
|
event.data.wheel.clicks = click_count;
|
|
event.data.wheel.x = mshook->pt.x;
|
|
event.data.wheel.y = mshook->pt.y;
|
|
|
|
event.data.wheel.type = get_scroll_wheel_type();
|
|
event.data.wheel.amount = get_scroll_wheel_amount();
|
|
|
|
/* Delta HIWORD(mshook->mouseData)
|
|
* A positive value indicates that the wheel was rotated
|
|
* forward, away from the user; a negative value indicates that
|
|
* the wheel was rotated backward, toward the user. One wheel
|
|
* click is defined as WHEEL_DELTA, which is 120. */
|
|
event.data.wheel.rotation = ((int16_t) HIWORD(mshook->mouseData) / WHEEL_DELTA) * -1;
|
|
|
|
// Set the direction based on what event was received.
|
|
event.data.wheel.direction = 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);
|
|
}
|
|
|
|
LRESULT CALLBACK mouse_hook_event_proc(int nCode, WPARAM wParam, LPARAM lParam) {
|
|
MSLLHOOKSTRUCT *mshook = (MSLLHOOKSTRUCT *) lParam;
|
|
switch (wParam) {
|
|
case WM_LBUTTONDOWN:
|
|
set_modifier_mask(MASK_BUTTON1);
|
|
process_button_pressed(mshook, MOUSE_BUTTON1);
|
|
break;
|
|
|
|
case WM_RBUTTONDOWN:
|
|
set_modifier_mask(MASK_BUTTON2);
|
|
process_button_pressed(mshook, MOUSE_BUTTON2);
|
|
break;
|
|
|
|
case WM_MBUTTONDOWN:
|
|
set_modifier_mask(MASK_BUTTON3);
|
|
process_button_pressed(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);
|
|
} else if (HIWORD(mshook->mouseData) == XBUTTON2) {
|
|
set_modifier_mask(MASK_BUTTON5);
|
|
process_button_pressed(mshook, MOUSE_BUTTON5);
|
|
} else {
|
|
// Extra mouse buttons.
|
|
uint16_t button = HIWORD(mshook->mouseData);
|
|
|
|
// Add support for mouse 4 & 5.
|
|
if (button == 4) {
|
|
set_modifier_mask(MOUSE_BUTTON4);
|
|
} else if (button == 5) {
|
|
set_modifier_mask(MOUSE_BUTTON5);
|
|
}
|
|
|
|
process_button_pressed(mshook, button);
|
|
}
|
|
break;
|
|
|
|
|
|
case WM_LBUTTONUP:
|
|
unset_modifier_mask(MASK_BUTTON1);
|
|
process_button_released(mshook, MOUSE_BUTTON1);
|
|
break;
|
|
|
|
case WM_RBUTTONUP:
|
|
unset_modifier_mask(MASK_BUTTON2);
|
|
process_button_released(mshook, MOUSE_BUTTON2);
|
|
break;
|
|
|
|
case WM_MBUTTONUP:
|
|
unset_modifier_mask(MASK_BUTTON3);
|
|
process_button_released(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);
|
|
} else if (HIWORD(mshook->mouseData) == XBUTTON2) {
|
|
unset_modifier_mask(MASK_BUTTON5);
|
|
process_button_released(mshook, MOUSE_BUTTON5);
|
|
} else {
|
|
// Extra mouse buttons.
|
|
uint16_t button = HIWORD(mshook->mouseData);
|
|
|
|
// Add support for mouse 4 & 5.
|
|
if (button == 4) {
|
|
unset_modifier_mask(MOUSE_BUTTON4);
|
|
} else if (button == 5) {
|
|
unset_modifier_mask(MOUSE_BUTTON5);
|
|
}
|
|
|
|
process_button_released(mshook, MOUSE_BUTTON5);
|
|
}
|
|
break;
|
|
|
|
case WM_MOUSEMOVE:
|
|
process_mouse_moved(mshook);
|
|
break;
|
|
|
|
case WM_MOUSEWHEEL:
|
|
process_mouse_wheel(mshook, WHEEL_VERTICAL_DIRECTION);
|
|
break;
|
|
|
|
/* For horizontal scroll wheel support.
|
|
* NOTE Windows >= Vista
|
|
* case 0x020E:
|
|
*/
|
|
case WM_MOUSEHWHEEL:
|
|
process_mouse_wheel(mshook, WHEEL_HORIZONTAL_DIRECTION);
|
|
break;
|
|
|
|
default:
|
|
// In theory this *should* never execute.
|
|
logger(LOG_LEVEL_DEBUG, "%s [%u]: Unhandled Windows mouse event: %#X.\n",
|
|
__FUNCTION__, __LINE__, (unsigned int) wParam);
|
|
break;
|
|
}
|
|
|
|
LRESULT hook_result = -1;
|
|
if (nCode < 0 || event.reserved ^ 0x01) {
|
|
hook_result = CallNextHookEx(mouse_event_hhook, nCode, wParam, lParam);
|
|
} else {
|
|
logger(LOG_LEVEL_DEBUG, "%s [%u]: Consuming the current event. (%li)\n",
|
|
__FUNCTION__, __LINE__, (long) hook_result);
|
|
}
|
|
|
|
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) {
|
|
case EVENT_OBJECT_NAMECHANGE:
|
|
logger(LOG_LEVEL_INFO, "%s [%u]: Restarting Windows input hook on window event: %#X.\n",
|
|
__FUNCTION__, __LINE__, event);
|
|
|
|
// Remove any keyboard or mouse hooks that are still running.
|
|
if (keyboard_event_hhook != NULL) {
|
|
UnhookWindowsHookEx(keyboard_event_hhook);
|
|
}
|
|
|
|
if (mouse_event_hhook != NULL) {
|
|
UnhookWindowsHookEx(mouse_event_hhook);
|
|
}
|
|
|
|
// Restart the event hooks.
|
|
keyboard_event_hhook = SetWindowsHookEx(WH_KEYBOARD_LL, keyboard_hook_event_proc, hInst, 0);
|
|
mouse_event_hhook = SetWindowsHookEx(WH_MOUSE_LL, mouse_hook_event_proc, hInst, 0);
|
|
|
|
// Re-initialize modifier masks.
|
|
initialize_modifiers();
|
|
|
|
// FIXME We should compare the modifier mask before and after the restart
|
|
// to determine if we should synthesize missing events.
|
|
|
|
// Check for event hook error.
|
|
if (keyboard_event_hhook == NULL || mouse_event_hhook == NULL) {
|
|
logger(LOG_LEVEL_ERROR, "%s [%u]: SetWindowsHookEx() failed! (%#lX)\n",
|
|
__FUNCTION__, __LINE__, (unsigned long) GetLastError());
|
|
}
|
|
break;
|
|
|
|
default:
|
|
logger(LOG_LEVEL_INFO, "%s [%u]: Unhandled Windows window event: %#X.\n",
|
|
__FUNCTION__, __LINE__, event);
|
|
}
|
|
}
|
|
|
|
|
|
IOHOOK_API int hook_run() {
|
|
int status = IOHOOK_FAILURE;
|
|
|
|
// Set the thread id we want to signal later.
|
|
hook_thread_id = GetCurrentThreadId();
|
|
|
|
// Spot check the hInst incase the library was statically linked and DllMain
|
|
// did not receive a pointer on load.
|
|
if (hInst == NULL) {
|
|
logger(LOG_LEVEL_INFO, "%s [%u]: hInst was not set by DllMain().\n",
|
|
__FUNCTION__, __LINE__);
|
|
|
|
hInst = GetModuleHandle(NULL);
|
|
if (hInst != NULL) {
|
|
// Initialize native input helper functions.
|
|
load_input_helper();
|
|
} else {
|
|
logger(LOG_LEVEL_ERROR, "%s [%u]: Could not determine hInst for SetWindowsHookEx()! (%#lX)\n",
|
|
__FUNCTION__, __LINE__, (unsigned long) GetLastError());
|
|
|
|
status = IOHOOK_ERROR_GET_MODULE_HANDLE;
|
|
}
|
|
}
|
|
|
|
// Create the native hooks.
|
|
keyboard_event_hhook = SetWindowsHookEx(WH_KEYBOARD_LL, keyboard_hook_event_proc, hInst, 0);
|
|
mouse_event_hhook = SetWindowsHookEx(WH_MOUSE_LL, mouse_hook_event_proc, hInst, 0);
|
|
|
|
// Create a window event hook to listen for capture change.
|
|
win_event_hhook = SetWinEventHook(
|
|
EVENT_OBJECT_NAMECHANGE, EVENT_OBJECT_NAMECHANGE,
|
|
NULL,
|
|
win_hook_event_proc,
|
|
0, 0,
|
|
WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);
|
|
|
|
// If we did not encounter a problem, start processing events.
|
|
if (keyboard_event_hhook != NULL && mouse_event_hhook != NULL) {
|
|
if (win_event_hhook == NULL) {
|
|
logger(LOG_LEVEL_WARN, "%s [%u]: SetWinEventHook() failed!\n",
|
|
__FUNCTION__, __LINE__);
|
|
}
|
|
|
|
logger(LOG_LEVEL_DEBUG, "%s [%u]: SetWindowsHookEx() successful.\n",
|
|
__FUNCTION__, __LINE__);
|
|
|
|
// Check and setup modifiers.
|
|
initialize_modifiers();
|
|
|
|
// Set the exit status.
|
|
status = IOHOOK_SUCCESS;
|
|
|
|
// Windows does not have a hook start event or callback so we need to
|
|
// manually fake it.
|
|
hook_start_proc();
|
|
|
|
// Block until the thread receives an WM_QUIT request.
|
|
MSG message;
|
|
while (GetMessage(&message, (HWND) NULL, 0, 0) > 0) {
|
|
TranslateMessage(&message);
|
|
DispatchMessage(&message);
|
|
}
|
|
} else {
|
|
logger(LOG_LEVEL_ERROR, "%s [%u]: SetWindowsHookEx() failed! (%#lX)\n",
|
|
__FUNCTION__, __LINE__, (unsigned long) GetLastError());
|
|
|
|
status = IOHOOK_ERROR_SET_WINDOWS_HOOK_EX;
|
|
}
|
|
|
|
|
|
// Unregister any hooks that may still be installed.
|
|
unregister_running_hooks();
|
|
|
|
// 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();
|
|
|
|
return status;
|
|
}
|
|
|
|
IOHOOK_API int hook_stop() {
|
|
int status = IOHOOK_FAILURE;
|
|
|
|
// Try to exit the thread naturally.
|
|
if (PostThreadMessage(hook_thread_id, WM_QUIT, (WPARAM) NULL, (LPARAM) NULL)) {
|
|
status = IOHOOK_SUCCESS;
|
|
}
|
|
|
|
logger(LOG_LEVEL_DEBUG, "%s [%u]: Status: %#X.\n",
|
|
__FUNCTION__, __LINE__, status);
|
|
|
|
return status;
|
|
}
|