TeaSpeak-Client/native/ppt/src/Win32KeyboardHook.cpp

321 lines
9.6 KiB
C++
Raw Normal View History

2019-10-26 01:51:40 +02:00
#include <iostream>
#include <cassert>
2019-10-29 18:11:35 +01:00
#include <string>
2020-04-25 12:48:44 +02:00
#include <mutex>
2019-10-26 01:51:40 +02:00
#include "KeyboardHook.h"
using namespace std;
typedef KBDLLHOOKSTRUCT KeyboardHookStruct;
2019-10-29 18:11:35 +01:00
typedef MSLLHOOKSTRUCT MouseHookStruct;
2020-04-25 12:48:44 +02:00
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;
2019-10-26 01:51:40 +02:00
KeyboardHook::~KeyboardHook() {
if(this->_attached)
this->detach();
}
bool KeyboardHook::attach() {
assert(!this->_attached);
this->active = true;
2020-04-25 12:48:44 +02:00
init_global_ed();
2019-10-26 01:51:40 +02:00
this->poll_thread = std::thread(std::bind(&KeyboardHook::poll_events, this));
this->_attached = true;
return true;
}
void KeyboardHook::detach() {
assert(this->_attached);
this->active = false;
{
//TODO trigger no message!
}
if(this->poll_thread.joinable())
this->poll_thread.join();
2020-04-25 12:48:44 +02:00
/* 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();
2019-10-26 01:51:40 +02:00
this->_attached = false;
}
2019-10-29 18:11:35 +01:00
LRESULT _keyboard_hook_callback(int nCode, WPARAM event, LPARAM ptr_keyboard) {
2020-04-25 12:48:44 +02:00
assert(thread_hook);
auto consume = thread_hook->keyboard_hook_callback(nCode, event, ptr_keyboard);
2019-10-29 18:11:35 +01:00
if(consume)
return 1;
return CallNextHookEx(nullptr, nCode, event, ptr_keyboard);
}
LRESULT _mouse_hook_callback(int nCode, WPARAM event, LPARAM ptr_keyboard) {
2020-04-25 12:48:44 +02:00
assert(thread_hook);
auto consume = thread_hook->mouse_hook_callback(nCode, event, ptr_keyboard);
auto end = std::chrono::high_resolution_clock::now();
2019-10-29 18:11:35 +01:00
if(consume)
return 1;
return CallNextHookEx(nullptr, nCode, event, ptr_keyboard);
}
2019-10-26 01:51:40 +02:00
void KeyboardHook::poll_events() {
2020-04-25 12:48:44 +02:00
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
2019-10-29 18:11:35 +01:00
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;
2019-10-26 01:51:40 +02:00
return;
}
2019-10-29 18:11:35 +01:00
this->mouse_hook_id = SetWindowsHookEx(WH_MOUSE_LL, _mouse_hook_callback, GetModuleHandle(nullptr), 0);
if(!this->keyboad_hook_id) {
UnhookWindowsHookEx(this->keyboad_hook_id);
cerr << "Failed to register mouse hook" << endl;
return;
}
2019-10-26 01:51:40 +02:00
MSG msg;
while(!GetMessage(&msg, nullptr, 0, 0) && this->active) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
2020-04-25 12:48:44 +02:00
std::this_thread::sleep_for(std::chrono::seconds{1});
2019-10-26 01:51:40 +02:00
2020-04-25 12:48:44 +02:00
UnhookWindowsHookEx(this->mouse_hook_id);
UnhookWindowsHookEx(this->keyboad_hook_id);
thread_hook = nullptr;
2019-10-26 01:51:40 +02:00
}
inline std::string key_code(DWORD code, bool extended = false) {
auto scan_code = MapVirtualKey(code, MAPVK_VK_TO_VSC);
if(extended)
scan_code |= KF_EXTENDED;
char key_buffer[255];
auto length = GetKeyNameTextA(scan_code << 16, key_buffer, 255);
if(length == 0)
return "error";
else
return string(key_buffer, length);
}
inline std::string key_code(KeyboardHookStruct* keyboard) {
return key_code(keyboard->vkCode, (keyboard->flags & LLKHF_EXTENDED) > 0);
2019-10-29 18:11:35 +01:00
}
2019-10-26 01:51:40 +02:00
using KeyType = KeyboardHook::KeyType;
//https://docs.microsoft.com/en-us/windows/desktop/inputdev/virtual-key-codes
inline KeyType::value key_type(DWORD vk_code) {
switch(vk_code) {
case VK_CONTROL:
case VK_LCONTROL:
case VK_RCONTROL:
return KeyType::KEY_CTRL;
case VK_MENU:
case VK_RMENU:
case VK_LMENU:
return KeyType::KEY_ALT;
case VK_SHIFT:
case VK_RSHIFT:
case VK_LSHIFT:
return KeyType::KEY_SHIFT;
case VK_LWIN:
case VK_RWIN:
return KeyType::KEY_WIN;
default:
return KeyType::KEY_NORMAL;
}
}
2019-10-29 18:11:35 +01:00
bool KeyboardHook::keyboard_hook_callback(int nCode, WPARAM event, LPARAM ptr_keyboard) {
2019-10-26 01:51:40 +02:00
auto keyboard = (KeyboardHookStruct*) ptr_keyboard;
if(event == WM_KEYDOWN || event == WM_SYSKEYDOWN) {
auto& state = this->map_key[keyboard->vkCode];
bool typed = state;
state = true;
auto type = key_type(keyboard->vkCode);
2019-10-29 18:11:35 +01:00
if(type != KeyType::KEY_NORMAL)
2019-10-26 01:51:40 +02:00
this->map_special[type] = true;
else
this->map_special[type] = keyboard->vkCode;
2019-10-29 18:11:35 +01:00
if(!typed)
this->trigger_key_event(KeyEvent::PRESS, type == KeyType::KEY_NORMAL ? key_code(keyboard) : key_code(this->map_special[KeyType::KEY_NORMAL], false));
this->trigger_key_event(KeyEvent::TYPE, type == KeyType::KEY_NORMAL ? key_code(keyboard) : key_code(this->map_special[KeyType::KEY_NORMAL], false));
2019-10-26 01:51:40 +02:00
} else if(event == WM_KEYUP || event == WM_SYSKEYUP) {
auto& state = this->map_key[keyboard->vkCode];
if(!state) return false; //Duplicate
state = false;
auto type = key_type(keyboard->vkCode);
if(type != KeyType::KEY_NORMAL)
this->map_special[type] = false;
else if(this->map_special[KeyType::KEY_NORMAL] == keyboard->vkCode)
2019-10-29 18:11:35 +01:00
this->map_special[KeyType::KEY_NORMAL] = 0;
this->trigger_key_event(KeyEvent::RELEASE, type == KeyType::KEY_NORMAL ? key_code(keyboard) : key_code(this->map_special[KeyType::KEY_NORMAL], false));
2019-10-26 01:51:40 +02:00
}
//Consume the event: return 1
return false;
2019-10-29 18:11:35 +01:00
}
2020-04-25 12:48:44 +02:00
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;
auto x_index = GET_XBUTTON_WPARAM(mouse->mouseData);
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;
2019-10-29 18:11:35 +01:00
}
2020-04-25 12:48:44 +02:00
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);
2019-10-29 18:11:35 +01:00
return false;
2019-10-26 01:51:40 +02:00
}