|
|
|
@ -1,15 +1,103 @@
|
|
|
|
|
#include <iostream>
|
|
|
|
|
#include <cassert>
|
|
|
|
|
#include <string>
|
|
|
|
|
#include <mutex>
|
|
|
|
|
#include "KeyboardHook.h"
|
|
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
|
|
|
|
|
typedef KBDLLHOOKSTRUCT KeyboardHookStruct;
|
|
|
|
|
typedef MSLLHOOKSTRUCT MouseHookStruct;
|
|
|
|
|
std::map<thread::id, KeyboardHook*> hook_handles;
|
|
|
|
|
thread_local KeyboardHook* thread_hook{nullptr};
|
|
|
|
|
|
|
|
|
|
struct MouseButtonEventEntry {
|
|
|
|
|
MouseButtonEventEntry* next;
|
|
|
|
|
KeyboardHook* hook;
|
|
|
|
|
|
|
|
|
|
enum KeyboardHook::KeyEvent::type type;
|
|
|
|
|
std::string key;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
inline MouseButtonEventEntry* allocate_mb_event() {
|
|
|
|
|
return new MouseButtonEventEntry{};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void delete_mb_event(MouseButtonEventEntry* event) {
|
|
|
|
|
delete event;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct MouseButtonEventDispatcher {
|
|
|
|
|
bool active{true};
|
|
|
|
|
|
|
|
|
|
std::thread dispatcher{};
|
|
|
|
|
|
|
|
|
|
CRITICAL_SECTION mutex;
|
|
|
|
|
CONDITION_VARIABLE cv_flushed;
|
|
|
|
|
CONDITION_VARIABLE cv_work;
|
|
|
|
|
|
|
|
|
|
MouseButtonEventEntry* event_head{nullptr};
|
|
|
|
|
MouseButtonEventEntry** event_tail{&event_head};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MouseButtonEventDispatcher* global_event_dispatcher{};
|
|
|
|
|
size_t global_ed_ref_count{0};
|
|
|
|
|
|
|
|
|
|
void init_global_ed() {
|
|
|
|
|
if(global_event_dispatcher) {
|
|
|
|
|
global_ed_ref_count++;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
global_event_dispatcher = new MouseButtonEventDispatcher{};
|
|
|
|
|
InitializeCriticalSection(&global_event_dispatcher->mutex);
|
|
|
|
|
InitializeConditionVariable(&global_event_dispatcher->cv_flushed);
|
|
|
|
|
InitializeConditionVariable(&global_event_dispatcher->cv_work);
|
|
|
|
|
|
|
|
|
|
global_event_dispatcher->dispatcher = std::thread([]{
|
|
|
|
|
auto ed = global_event_dispatcher;
|
|
|
|
|
|
|
|
|
|
while(ed->active) {
|
|
|
|
|
MouseButtonEventEntry* entry{nullptr};
|
|
|
|
|
{
|
|
|
|
|
EnterCriticalSection(&ed->mutex);
|
|
|
|
|
while(!global_event_dispatcher->event_head && ed->active) {
|
|
|
|
|
WakeAllConditionVariable(&ed->cv_flushed);
|
|
|
|
|
SleepConditionVariableCS(&ed->cv_work, &ed->mutex, INFINITE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
entry = global_event_dispatcher->event_head;
|
|
|
|
|
global_event_dispatcher->event_head = nullptr;
|
|
|
|
|
global_event_dispatcher->event_tail = &global_event_dispatcher->event_head;
|
|
|
|
|
LeaveCriticalSection(&ed->mutex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while(entry) {
|
|
|
|
|
entry->hook->trigger_key_event(entry->type, std::string{entry->key});
|
|
|
|
|
|
|
|
|
|
auto next = entry->next;
|
|
|
|
|
delete_mb_event(entry);
|
|
|
|
|
entry = next;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void shutdown_global_ed() {
|
|
|
|
|
if(--global_ed_ref_count > 0) return;
|
|
|
|
|
|
|
|
|
|
auto ed = std::exchange(global_event_dispatcher, nullptr);
|
|
|
|
|
ed->active = false;
|
|
|
|
|
WakeAllConditionVariable(&ed->cv_work);
|
|
|
|
|
|
|
|
|
|
if(ed->dispatcher.joinable())
|
|
|
|
|
ed->dispatcher.join();
|
|
|
|
|
|
|
|
|
|
DeleteCriticalSection(&ed->mutex);
|
|
|
|
|
delete ed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
KeyboardHook::KeyboardHook() = default;
|
|
|
|
|
|
|
|
|
|
KeyboardHook::KeyboardHook() {}
|
|
|
|
|
KeyboardHook::~KeyboardHook() {
|
|
|
|
|
if(this->_attached)
|
|
|
|
|
this->detach();
|
|
|
|
@ -19,6 +107,7 @@ bool KeyboardHook::attach() {
|
|
|
|
|
assert(!this->_attached);
|
|
|
|
|
this->active = true;
|
|
|
|
|
|
|
|
|
|
init_global_ed();
|
|
|
|
|
this->poll_thread = std::thread(std::bind(&KeyboardHook::poll_events, this));
|
|
|
|
|
|
|
|
|
|
this->_attached = true;
|
|
|
|
@ -34,29 +123,45 @@ void KeyboardHook::detach() {
|
|
|
|
|
if(this->poll_thread.joinable())
|
|
|
|
|
this->poll_thread.join();
|
|
|
|
|
|
|
|
|
|
/* all events flushed */
|
|
|
|
|
EnterCriticalSection(&global_event_dispatcher->mutex);
|
|
|
|
|
WakeAllConditionVariable(&global_event_dispatcher->cv_work);
|
|
|
|
|
SleepConditionVariableCS(&global_event_dispatcher->cv_flushed, &global_event_dispatcher->mutex, INFINITE);
|
|
|
|
|
LeaveCriticalSection(&global_event_dispatcher->mutex);
|
|
|
|
|
shutdown_global_ed();
|
|
|
|
|
|
|
|
|
|
this->_attached = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LRESULT _keyboard_hook_callback(int nCode, WPARAM event, LPARAM ptr_keyboard) {
|
|
|
|
|
auto handle = hook_handles[this_thread::get_id()];
|
|
|
|
|
assert(handle);
|
|
|
|
|
auto consume = handle->keyboard_hook_callback(nCode, event, ptr_keyboard);
|
|
|
|
|
assert(thread_hook);
|
|
|
|
|
auto consume = thread_hook->keyboard_hook_callback(nCode, event, ptr_keyboard);
|
|
|
|
|
if(consume)
|
|
|
|
|
return 1;
|
|
|
|
|
return CallNextHookEx(nullptr, nCode, event, ptr_keyboard);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LRESULT _mouse_hook_callback(int nCode, WPARAM event, LPARAM ptr_keyboard) {
|
|
|
|
|
auto handle = hook_handles[this_thread::get_id()];
|
|
|
|
|
assert(handle);
|
|
|
|
|
auto consume = handle->mouse_hook_callback(nCode, event, ptr_keyboard);
|
|
|
|
|
assert(thread_hook);
|
|
|
|
|
auto consume = thread_hook->mouse_hook_callback(nCode, event, ptr_keyboard);
|
|
|
|
|
auto end = std::chrono::high_resolution_clock::now();
|
|
|
|
|
if(consume)
|
|
|
|
|
return 1;
|
|
|
|
|
return CallNextHookEx(nullptr, nCode, event, ptr_keyboard);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void KeyboardHook::poll_events() {
|
|
|
|
|
hook_handles[this_thread::get_id()] = this;
|
|
|
|
|
thread_hook = this;
|
|
|
|
|
|
|
|
|
|
if(!SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS))
|
|
|
|
|
std::cerr << "Failed to set priority class to realtime!" << std::endl;
|
|
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
|
int cur_priority = GetThreadPriority(GetCurrentThread());
|
|
|
|
|
DWORD cur_priority_class = GetPriorityClass(GetCurrentProcess());
|
|
|
|
|
std::cout << "P: " << cur_priority << " C: " << cur_priority_class << std::endl;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
this->keyboad_hook_id = SetWindowsHookEx(WH_KEYBOARD_LL, _keyboard_hook_callback, GetModuleHandle(nullptr), 0);
|
|
|
|
|
if(!this->keyboad_hook_id) {
|
|
|
|
|
cerr << "Failed to register keyboard hook" << endl;
|
|
|
|
@ -75,9 +180,11 @@ void KeyboardHook::poll_events() {
|
|
|
|
|
TranslateMessage(&msg);
|
|
|
|
|
DispatchMessage(&msg);
|
|
|
|
|
}
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::seconds{1});
|
|
|
|
|
|
|
|
|
|
UnhookWindowsHookEx(this->mouse_hook_id);
|
|
|
|
|
UnhookWindowsHookEx(this->keyboad_hook_id);
|
|
|
|
|
hook_handles[this_thread::get_id()] = nullptr;
|
|
|
|
|
thread_hook = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline std::string key_code(DWORD code, bool extended = false) {
|
|
|
|
@ -156,22 +263,59 @@ bool KeyboardHook::keyboard_hook_callback(int nCode, WPARAM event, LPARAM ptr_ke
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool KeyboardHook::mouse_hook_callback(int nCode, WPARAM event, LPARAM ptr_mouse) {
|
|
|
|
|
MouseButtonEventEntry* mb_event;
|
|
|
|
|
switch (event) {
|
|
|
|
|
case WM_LBUTTONDOWN:
|
|
|
|
|
mb_event = allocate_mb_event();
|
|
|
|
|
mb_event->type = KeyEvent::PRESS;
|
|
|
|
|
mb_event->key = "MOUSE1";
|
|
|
|
|
break;
|
|
|
|
|
case WM_LBUTTONUP:
|
|
|
|
|
mb_event = allocate_mb_event();
|
|
|
|
|
mb_event->type = KeyEvent::RELEASE;
|
|
|
|
|
mb_event->key = "MOUSE1";
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case WM_RBUTTONDOWN:
|
|
|
|
|
mb_event = allocate_mb_event();
|
|
|
|
|
mb_event->type = KeyEvent::PRESS;
|
|
|
|
|
mb_event->key = "MOUSE3";
|
|
|
|
|
break;
|
|
|
|
|
case WM_RBUTTONUP:
|
|
|
|
|
mb_event = allocate_mb_event();
|
|
|
|
|
mb_event->type = KeyEvent::RELEASE;
|
|
|
|
|
mb_event->key = "MOUSE3";
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case WM_XBUTTONDOWN: {
|
|
|
|
|
auto mouse = (MouseHookStruct*) ptr_mouse;
|
|
|
|
|
if(event == WM_RBUTTONDOWN)
|
|
|
|
|
this->trigger_key_event(KeyEvent::PRESS, "MOUSE1");
|
|
|
|
|
else if(event == WM_RBUTTONUP)
|
|
|
|
|
this->trigger_key_event(KeyEvent::RELEASE, "MOUSE1");
|
|
|
|
|
if(event == WM_LBUTTONDOWN)
|
|
|
|
|
this->trigger_key_event(KeyEvent::PRESS, "MOUSE2");
|
|
|
|
|
else if(event == WM_LBUTTONUP)
|
|
|
|
|
this->trigger_key_event(KeyEvent::RELEASE, "MOUSE2");
|
|
|
|
|
if(event == WM_MBUTTONDOWN)
|
|
|
|
|
this->trigger_key_event(KeyEvent::PRESS, "MOUSE3");
|
|
|
|
|
else if(event == WM_MBUTTONUP)
|
|
|
|
|
this->trigger_key_event(KeyEvent::RELEASE, "MOUSE3");
|
|
|
|
|
else if(event == WM_XBUTTONDOWN || event == WM_XBUTTONUP) {
|
|
|
|
|
auto x_index = GET_XBUTTON_WPARAM(mouse->mouseData);
|
|
|
|
|
this->trigger_key_event(event == WM_XBUTTONDOWN ? KeyEvent::PRESS : KeyEvent::RELEASE, "MOUSEX" + std::to_string(x_index));
|
|
|
|
|
|
|
|
|
|
mb_event = allocate_mb_event();
|
|
|
|
|
mb_event->type = KeyEvent::PRESS;
|
|
|
|
|
mb_event->key = "MOUSEX" + std::to_string(x_index);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case WM_XBUTTONUP: {
|
|
|
|
|
auto mouse = (MouseHookStruct*) ptr_mouse;
|
|
|
|
|
auto x_index = GET_XBUTTON_WPARAM(mouse->mouseData);
|
|
|
|
|
|
|
|
|
|
mb_event = allocate_mb_event();
|
|
|
|
|
mb_event->type = KeyEvent::RELEASE;
|
|
|
|
|
mb_event->key = "MOUSEX" + std::to_string(x_index);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mb_event->next = nullptr;
|
|
|
|
|
mb_event->hook = thread_hook;
|
|
|
|
|
|
|
|
|
|
EnterCriticalSection(&global_event_dispatcher->mutex);
|
|
|
|
|
*global_event_dispatcher->event_tail = mb_event;
|
|
|
|
|
global_event_dispatcher->event_tail = &mb_event->next;
|
|
|
|
|
WakeAllConditionVariable(&global_event_dispatcher->cv_work);
|
|
|
|
|
LeaveCriticalSection(&global_event_dispatcher->mutex);
|
|
|
|
|
return false;
|
|
|
|
|
}
|