diff --git a/native/ppt/CMakeLists.txt b/native/ppt/CMakeLists.txt index f2eab44..f054b7f 100644 --- a/native/ppt/CMakeLists.txt +++ b/native/ppt/CMakeLists.txt @@ -2,7 +2,7 @@ set(MODULE_NAME "teaclient_ppt") set(SOURCE_FILES src/KeyboardHook.cpp) if (MSVC) - set(SOURCE_FILES ${SOURCE_FILES} src/Win32KeyboardHook.cpp) + set(SOURCE_FILES ${SOURCE_FILES} src/Win32KeyboardHook.cpp src/Win32KeyboardHookLL.cpp src/Win32KeyboardRawInput.cpp) add_definitions(-DUSING_UV_SHARED) else() add_definitions(-DHAVE_X11) diff --git a/native/ppt/binding.cc b/native/ppt/binding.cc index 1e2ac37..c82b498 100644 --- a/native/ppt/binding.cc +++ b/native/ppt/binding.cc @@ -8,7 +8,12 @@ using namespace std; #include "include/NanException.h" #include "include/NanEventCallback.h" -#include "src/KeyboardHook.h" + +#ifdef WIN32 + #include "src/Win32KeyboardHook.h" +#else + #include "src/KeyboardHook.h" +#endif std::mutex callback_lock; @@ -62,7 +67,7 @@ NAN_METHOD(UnregisterCallback) { } NAN_MODULE_INIT(init) { - hook = make_unique(); + hook = make_unique(); if(!hook->attach()) { NAN_THROW_EXCEPTION(Error, "Failed to attach hook!"); return; diff --git a/native/ppt/src/KeyboardHook.cpp b/native/ppt/src/KeyboardHook.cpp index 738f480..30c4f9d 100644 --- a/native/ppt/src/KeyboardHook.cpp +++ b/native/ppt/src/KeyboardHook.cpp @@ -1,6 +1,26 @@ -#include "KeyboardHook.h" +#include "./KeyboardHook.h" +#include using namespace std; + +KeyboardHook::KeyboardHook(KeyboardHookType type) : type_{type} {}; + +KeyboardHook::~KeyboardHook() { + if(this->_attached) + this->detach(); +} + +bool KeyboardHook::attach() { + assert(!this->_attached); + this->_attached = true; + return true; +} + +void KeyboardHook::detach() { + assert(this->_attached); + this->_attached = false; +} + void KeyboardHook::trigger_key_event(const enum KeyEvent::type& type, const std::string &key) { if(!this->callback_event) return; diff --git a/native/ppt/src/KeyboardHook.h b/native/ppt/src/KeyboardHook.h index e07dd95..4241ea7 100644 --- a/native/ppt/src/KeyboardHook.h +++ b/native/ppt/src/KeyboardHook.h @@ -4,27 +4,35 @@ #include #include -#ifdef HAVE_X11 +//#define HOOK_X11 + +#if defined(HOOK_X11) #include -#elif defined(WIN32) - #include #endif + +enum struct KeyboardHookType { + X11, + + RAW_INPUT, + SYSTEM_HOOK +}; + class KeyboardHook { - #if defined(WIN32) + #ifdef HOOK_WIN32_LL friend LRESULT CALLBACK _keyboard_hook_callback(int, WPARAM, LPARAM); friend LRESULT CALLBACK _mouse_hook_callback(int, WPARAM, LPARAM); #endif public: - struct KeyType { - enum value { - KEY_UNKNOWN, + typedef unsigned int KeyID; - KEY_NORMAL, - KEY_SHIFT, - KEY_ALT, - KEY_WIN, - KEY_CTRL - }; + enum struct KeyType { + KEY_UNKNOWN, + + KEY_NORMAL, + KEY_SHIFT, + KEY_ALT, + KEY_WIN, + KEY_CTRL }; struct KeyEvent { @@ -45,17 +53,23 @@ class KeyboardHook { }; typedef std::function& /* event */)> callback_event_t; - KeyboardHook(); + KeyboardHook(KeyboardHookType); virtual ~KeyboardHook(); - bool attach(); - inline bool attached() { return this->_attached; } - void detach(); + [[nodiscard]] inline KeyboardHookType type() const { return this->type_; } + [[nodiscard]] virtual bool keytype_supported() const = 0; + + [[nodiscard]] virtual bool attach(); + [[nodiscard]] inline bool attached() const { return this->_attached; } + virtual void detach(); void trigger_key_event(const enum KeyEvent::type&, const std::string& /* key */); callback_event_t callback_event; - private: - #ifdef HAVE_X11 + protected: + const KeyboardHookType type_; + +#if 0 + #if defined(HOOK_X11) typedef int KeyID; Display* display = nullptr; Window window_root = 0; @@ -63,20 +77,20 @@ class KeyboardHook { int focus_revert; long end_id = 0; - #elif defined(WIN32) + #elseif defined(HOOK_WIN32_LL) typedef UINT KeyID; HHOOK keyboad_hook_id{nullptr}; - HHOOK mouse_hook_id{nullptr}; - bool keyboard_hook_callback(int, WPARAM, LPARAM); + +#ifdef USE_MOUSE_HOOK + HHOOK mouse_hook_id{nullptr}; bool mouse_hook_callback(int, WPARAM, LPARAM); +#endif #endif +#endif std::map map_key; - std::map map_special; + std::map map_special; bool _attached = false; - bool active = false; - std::thread poll_thread; - void poll_events(); }; \ No newline at end of file diff --git a/native/ppt/src/Win32KeyboardHook.cpp b/native/ppt/src/Win32KeyboardHook.cpp index 3837b4b..8b09ceb 100644 --- a/native/ppt/src/Win32KeyboardHook.cpp +++ b/native/ppt/src/Win32KeyboardHook.cpp @@ -1,321 +1,54 @@ -#include -#include -#include -#include -#include "KeyboardHook.h" +// +// Created by WolverinDEV on 01/05/2020. +// -using namespace std; +#include "./Win32KeyboardHook.h" -typedef KBDLLHOOKSTRUCT KeyboardHookStruct; -typedef MSLLHOOKSTRUCT MouseHookStruct; -thread_local KeyboardHook* thread_hook{nullptr}; +namespace hooks { + std::string key_string_from_vk(DWORD code, bool extended) { + auto scan_code = MapVirtualKey(code, MAPVK_VK_TO_VSC); + if(extended) + scan_code |= KF_EXTENDED; -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; + char key_buffer[255]; + auto length = GetKeyNameTextA(scan_code << 16, key_buffer, 255); + if(length == 0) + return "error"; + else + return std::string{key_buffer, (size_t) length}; } - 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() { - if(this->_attached) - this->detach(); -} - -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; - return true; -} - -void KeyboardHook::detach() { - assert(this->_attached); - this->active = false; - { - //TODO trigger no message! - } - 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) { - 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) { - 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() { - 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; - return; - } - - 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; + std::string key_string_from_sc(USHORT code) { + char key_buffer[255]; + auto length = GetKeyNameTextA(code << 16, key_buffer, 255); + if(length == 0) + return "error"; + else + return std::string{key_buffer, (size_t) length}; } - MSG msg; - while(!GetMessage(&msg, nullptr, 0, 0) && this->active) { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - std::this_thread::sleep_for(std::chrono::seconds{1}); + //https://docs.microsoft.com/en-us/windows/desktop/inputdev/virtual-key-codes + KeyboardHook::KeyType key_type_from_vk(DWORD vk_code) { + using KeyType = KeyboardHook::KeyType; - UnhookWindowsHookEx(this->mouse_hook_id); - UnhookWindowsHookEx(this->keyboad_hook_id); - thread_hook = nullptr; -} - -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); -} - -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; - } -} - -bool KeyboardHook::keyboard_hook_callback(int nCode, WPARAM event, LPARAM ptr_keyboard) { - 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); - if(type != KeyType::KEY_NORMAL) - this->map_special[type] = true; - else - this->map_special[type] = keyboard->vkCode; - - 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)); - } 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) - 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)); - } - - //Consume the event: return 1 - return false; -} - -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; + 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; } - 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; } \ No newline at end of file diff --git a/native/ppt/src/Win32KeyboardHook.h b/native/ppt/src/Win32KeyboardHook.h new file mode 100644 index 0000000..ca50cb2 --- /dev/null +++ b/native/ppt/src/Win32KeyboardHook.h @@ -0,0 +1,68 @@ +#pragma once + +#include "./KeyboardHook.h" +#include +#include + +namespace hooks { + extern KeyboardHook::KeyType key_type_from_vk(DWORD vk_code); + extern std::string key_string_from_vk(DWORD code, bool extended); + extern std::string key_string_from_sc(USHORT code); + + class Win32SystemHook : public KeyboardHook { + public: + Win32SystemHook(); + + bool attach() override; + void detach() override; + + bool keytype_supported() const override { return true; } + private: + static LRESULT CALLBACK _keyboard_hook_callback(int, WPARAM, LPARAM); + static LRESULT CALLBACK _mouse_hook_callback(int, WPARAM, LPARAM); + + HHOOK keyboad_hook_id{nullptr}; + bool keyboard_hook_callback(int, WPARAM, LPARAM); + + HHOOK mouse_hook_id{nullptr}; + bool mouse_hook_callback(int, WPARAM, LPARAM); + + bool active{false}; + std::thread poll_thread; + void poll_events(); + }; + + class Win32RawHook : public KeyboardHook { + public: + Win32RawHook(); + + bool attach() override; + void detach() override; + + bool keytype_supported() const override { return true; } + private: + static LRESULT CALLBACK window_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp); + + std::thread wthread; + void wloop(); + + enum struct WorkerStatus { + STOPPED, + DIED, + + INITIALIZING, + RUNNING + }; + + bool wactive{false}; + WorkerStatus wstatus{WorkerStatus::STOPPED}; + std::mutex wstatus_mutex{}; + std::condition_variable wstatus_changed_cv{}; + std::string worker_died_reason{}; + + void set_wstatus(WorkerStatus); + void handle_raw_input(RAWINPUT&); + + HWND hwnd{0}; + }; +} \ No newline at end of file diff --git a/native/ppt/src/Win32KeyboardHookLL.cpp b/native/ppt/src/Win32KeyboardHookLL.cpp new file mode 100644 index 0000000..6c6eeb4 --- /dev/null +++ b/native/ppt/src/Win32KeyboardHookLL.cpp @@ -0,0 +1,301 @@ +#include +#include +#include +#include +#include "./Win32KeyboardHook.h" + +using namespace std; + +namespace hooks { + void init_global_ed(); + void shutdown_global_ed(); + + typedef KBDLLHOOKSTRUCT KeyboardHookStruct; + thread_local Win32SystemHook* thread_hook{nullptr}; + + Win32SystemHook::Win32SystemHook() : KeyboardHook{KeyboardHookType::SYSTEM_HOOK} {} + + bool Win32SystemHook::attach() { + if(!KeyboardHook::attach()) + return false; + + init_global_ed(); + + this->active = true; + this->poll_thread = std::thread(std::bind(&Win32SystemHook::poll_events, this)); + return true; + } + + void Win32SystemHook::detach() { + this->active = false; + { + //TODO trigger no message! + } + if(this->poll_thread.joinable()) + this->poll_thread.join(); + + /* will flush all events */ + shutdown_global_ed(); + + KeyboardHook::detach(); + } + + + LRESULT Win32SystemHook::_mouse_hook_callback(int nCode, WPARAM event, LPARAM 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); + } + + LRESULT Win32SystemHook::_keyboard_hook_callback(int nCode, WPARAM event, LPARAM 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); + } + + void Win32SystemHook::poll_events() { + thread_hook = this; + + if(!SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS)) + std::cerr << "Failed to set priority class to realtime!" << std::endl; + + #if 0 + std::string msg_box{"Priority Class: "}; + msg_box += std::to_string((int) GetPriorityClass(GetCurrentProcess())); + msg_box += " Priority: "; + msg_box += std::to_string((int) GetThreadPriority(GetCurrentProcess())); + MessageBox(nullptr, msg_box.c_str(), "PPT-Thread", MB_OK); + #endif + #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, &Win32SystemHook::_keyboard_hook_callback, GetModuleHandle(nullptr), 0); + if(!this->keyboad_hook_id) { + cerr << "Failed to register keyboard hook" << endl; + return; + } + +#if 1 + this->mouse_hook_id = SetWindowsHookEx(WH_MOUSE_LL, &Win32SystemHook::_mouse_hook_callback, GetModuleHandle(nullptr), 0); + if(!this->keyboad_hook_id) { + UnhookWindowsHookEx(this->keyboad_hook_id); + cerr << "Failed to register mouse hook" << endl; + return; + } +#else + this->mouse_hook_id = 0; +#endif + + + MSG msg; + while(!GetMessage(&msg, nullptr, 0, 0) && this->active) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + if(this->mouse_hook_id > 0) + UnhookWindowsHookEx(this->mouse_hook_id); + UnhookWindowsHookEx(this->keyboad_hook_id); + thread_hook = nullptr; + } + + inline std::string key_code(KeyboardHookStruct* keyboard) { + return key_string_from_vk(keyboard->vkCode, (keyboard->flags & LLKHF_EXTENDED) > 0); + } + + using KeyType = KeyboardHook::KeyType; + + bool Win32SystemHook::keyboard_hook_callback(int nCode, WPARAM event, LPARAM ptr_keyboard) { + 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_from_vk(keyboard->vkCode); + if(type != KeyType::KEY_NORMAL) + this->map_special[type] = true; + else + this->map_special[type] = keyboard->vkCode; + + if(!typed) + this->trigger_key_event(KeyEvent::PRESS, type == KeyType::KEY_NORMAL ? key_code(keyboard) : key_string_from_vk(this->map_special[KeyType::KEY_NORMAL], false)); + this->trigger_key_event(KeyEvent::TYPE, type == KeyType::KEY_NORMAL ? key_code(keyboard) : key_string_from_vk(this->map_special[KeyType::KEY_NORMAL], false)); + } 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_from_vk(keyboard->vkCode); + if(type != KeyType::KEY_NORMAL) + this->map_special[type] = false; + else if(this->map_special[KeyType::KEY_NORMAL] == keyboard->vkCode) + this->map_special[KeyType::KEY_NORMAL] = 0; + + this->trigger_key_event(KeyEvent::RELEASE, type == KeyType::KEY_NORMAL ? key_code(keyboard) : key_string_from_vk(this->map_special[KeyType::KEY_NORMAL], false)); + } + + //Consume the event: return 1 + return false; + } + + typedef MSLLHOOKSTRUCT MouseLLHookStruct; + + 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() { + /* flush all events */ + 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); + + 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; + } + + bool Win32SystemHook::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 = (MouseLLHookStruct*) 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 = (MouseLLHookStruct*) 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; + } +} \ No newline at end of file diff --git a/native/ppt/src/Win32KeyboardRawInput.cpp b/native/ppt/src/Win32KeyboardRawInput.cpp new file mode 100644 index 0000000..e2fd5c5 --- /dev/null +++ b/native/ppt/src/Win32KeyboardRawInput.cpp @@ -0,0 +1,200 @@ +// +// Created by WolverinDEV on 01/05/2020. +// + +#include "./Win32KeyboardHook.h" +#include +#include + +namespace hooks { + Win32RawHook::Win32RawHook() : KeyboardHook{KeyboardHookType::RAW_INPUT} {} + + bool Win32RawHook::attach() { + if(!KeyboardHook::attach()) + return false; + + this->wactive = true; + this->set_wstatus(WorkerStatus::INITIALIZING); + this->wthread = std::thread(std::bind(&Win32RawHook::wloop, this)); + + std::unique_lock ws_lock{this->wstatus_mutex}; + this->wstatus_changed_cv.wait(ws_lock, [&]{ + return this->wstatus == WorkerStatus::RUNNING || this->wstatus == WorkerStatus::DIED; + }); + + return this->wstatus == WorkerStatus::RUNNING; + } + + void Win32RawHook::detach() { + this->wactive = false; + { + //TODO trigger no message! + } + + if(this->wthread.joinable()) + this->wthread.join(); + this->set_wstatus(WorkerStatus::STOPPED); + + KeyboardHook::detach(); + } + + void Win32RawHook::set_wstatus(WorkerStatus status) { + std::lock_guard ws_lock{this->wstatus_mutex}; + if(this->wstatus == status) return; + this->wstatus = status; + this->wstatus_changed_cv.notify_all(); + } + +#define WORKER_CLASS_NAME ("TeaClient - KeyHook worker") + void Win32RawHook::wloop() { + this->set_wstatus(WorkerStatus::INITIALIZING); + + /* setup */ + { + { + WNDCLASS wc = {0}; + wc.lpfnWndProc = window_proc; + wc.cbWndExtra = sizeof(void*); + wc.hInstance = 0; + wc.lpszClassName = WORKER_CLASS_NAME; + RegisterClass(&wc); + } + + this->hwnd = CreateWindow(WORKER_CLASS_NAME, "TeaClient - KeyHook worker window", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, this); + if(!this->hwnd) { + this->worker_died_reason = "Failed to create window"; + this->set_wstatus(WorkerStatus::DIED); + goto cleanup; + } + + RAWINPUTDEVICE devices[2]; + devices[0].usUsagePage = 0x01; //HID_USAGE_PAGE_GENERIC; + devices[0].usUsage = 0x02; //HID_USAGE_GENERIC_MOUSE; + devices[0].dwFlags = RIDEV_NOLEGACY | RIDEV_INPUTSINK; + devices[0].hwndTarget = hwnd; + + devices[1].usUsagePage = 0x01; //HID_USAGE_PAGE_GENERIC; + devices[1].usUsage = 0x06; //HID_USAGE_GENERIC_KEYBOARD; + devices[1].dwFlags = RIDEV_NOLEGACY | RIDEV_INPUTSINK; + devices[1].hwndTarget = hwnd; + + if(!RegisterRawInputDevices(devices, 2, sizeof *devices)) { + this->worker_died_reason = "failed to register raw input devices"; + this->set_wstatus(WorkerStatus::DIED); + goto cleanup; + } + } + + this->set_wstatus(WorkerStatus::RUNNING); + BOOL ret; + MSG msg; + while (this->wactive) { + ret = GetMessage(&msg, this->hwnd, 0, 0); + if(ret == 0) + break; + if (ret == -1) { + this->worker_died_reason = "GetMessage() threw an error"; + this->set_wstatus(WorkerStatus::DIED); + goto cleanup; + } + else { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + this->set_wstatus(WorkerStatus::STOPPED); + + cleanup: + if(this->hwnd > 0) { + DestroyWindow(this->hwnd); + this->hwnd = 0; + } + } + + LRESULT Win32RawHook::window_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { + auto hook = reinterpret_cast(GetWindowLongPtr(hwnd, GWLP_USERDATA)); + + switch (msg) { + case WM_CREATE: { + CREATESTRUCT *s = reinterpret_cast(lp); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) s->lpCreateParams); + return 0; + } + case WM_CLOSE: + DestroyWindow(hwnd); + PostQuitMessage(0); + return 0; + + case WM_INPUT: { + UINT target_size{0}; + GetRawInputData((HRAWINPUT) lp, RID_INPUT, NULL, &target_size, sizeof(RAWINPUTHEADER)); + if(target_size > sizeof(RAWINPUT)) { + std::cerr << "Failed to retrieve input (Target size is longer than expected)" << std::endl; + return 0; + } + RAWINPUT input{}; + GetRawInputData((HRAWINPUT) lp, RID_INPUT, &input, &target_size, sizeof(RAWINPUTHEADER)); + + hook->handle_raw_input(input); + return 0; + } + default: + return DefWindowProc(hwnd, msg, wp, lp); + } + } + + void Win32RawHook::handle_raw_input(RAWINPUT &input) { + if(input.header.dwType == RIM_TYPEMOUSE) { + auto& data = input.data.mouse; + if(data.ulButtons == 0) return; /* mouse move event */ + +#define BUTTON_EVENT(number, name) \ + case RI_MOUSE_BUTTON_ ##number ##_DOWN: \ + this->trigger_key_event(KeyEvent::PRESS, name); \ + break; \ + case RI_MOUSE_BUTTON_ ##number ##_UP: \ + this->trigger_key_event(KeyEvent::RELEASE, name); \ + break + + switch (data.ulButtons) { + BUTTON_EVENT(1, "MOUSE1"); + BUTTON_EVENT(2, "MOUSE2"); + BUTTON_EVENT(3, "MOUSE3"); + BUTTON_EVENT(4, "MOUSEX1"); + BUTTON_EVENT(5, "MOUSEX2"); + default: + break; + } + } else if(input.header.dwType == RIM_TYPEKEYBOARD) { + auto& data = input.data.keyboard; + + if(data.Message == WM_KEYDOWN || data.Message == WM_SYSKEYDOWN) { + auto& state = this->map_key[data.VKey]; + bool typed = state; + state = true; + + auto type = key_type_from_vk(data.VKey); + if(type != KeyType::KEY_NORMAL) + this->map_special[type] = true; + else + this->map_special[type] = data.VKey; + + if(!typed) + this->trigger_key_event(KeyEvent::PRESS, type == KeyType::KEY_NORMAL ? key_string_from_sc(data.MakeCode) : key_string_from_vk(this->map_special[KeyType::KEY_NORMAL], false)); + this->trigger_key_event(KeyEvent::TYPE, type == KeyType::KEY_NORMAL ? key_string_from_sc(data.MakeCode) : key_string_from_vk(this->map_special[KeyType::KEY_NORMAL], false)); + } else if(data.Message == WM_KEYUP || data.Message == WM_SYSKEYUP) { + auto& state = this->map_key[data.VKey]; + if(!state) return; //Duplicate + state = false; + + auto type = key_type_from_vk(data.VKey); + if(type != KeyType::KEY_NORMAL) + this->map_special[type] = false; + else if(this->map_special[KeyType::KEY_NORMAL] == data.VKey) + this->map_special[KeyType::KEY_NORMAL] = 0; + + this->trigger_key_event(KeyEvent::RELEASE, type == KeyType::KEY_NORMAL ? key_string_from_sc(data.MakeCode) : key_string_from_vk(this->map_special[KeyType::KEY_NORMAL], false)); + } + } + } +} \ No newline at end of file diff --git a/native/ppt/test/HookTest.cpp b/native/ppt/test/HookTest.cpp index 5515046..2c0b309 100644 --- a/native/ppt/test/HookTest.cpp +++ b/native/ppt/test/HookTest.cpp @@ -1,13 +1,83 @@ #include "../src/KeyboardHook.h" +#include "../src/Win32KeyboardHook.h" #include #include +#include using namespace std::chrono; using namespace std; +#if 0 +LRESULT CALLBACK window_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { + // Retrieve "this" pointer + // Guaranteed to be garbage until WM_CREATE finishes, but + // we don't actually use this value until WM_CREATE writes a valid one + //vrpn_DirectXRumblePad *me = reinterpret_cast(GetWindowLongPtr(hwnd, GWLP_USERDATA)); + + switch (msg) { + // Window is being created; store "this" pointer for future retrieval + case WM_CREATE: { + CREATESTRUCT *s = reinterpret_cast(lp); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) s->lpCreateParams); + return 0; + } + // Something (most likely ~vrpn_DirectXRumblePad) wants to close the window + // Go ahead and signal shutdown + case WM_CLOSE: + DestroyWindow(hwnd); + PostQuitMessage(0); + break; + + case WM_INPUT: { + UINT dwSize; + UINT buffer_size = sizeof(RAWINPUT); + GetRawInputData((HRAWINPUT) lp, RID_INPUT, NULL, &dwSize,sizeof(RAWINPUTHEADER)); + if(dwSize > buffer_size) { + std::cerr << "Failed to retreive input" << std::endl; + return 0; + } + RAWINPUT input{}; + GetRawInputData((HRAWINPUT) lp, RID_INPUT, &input, &buffer_size, sizeof(RAWINPUTHEADER)); + + if(input.header.dwType != RIM_TYPEMOUSE) + return 0; + + auto& mouse_data = input.data.mouse; + std::cout << "Input" << std::endl; + std::cout << "Buttons: " << (int) mouse_data.ulButtons << std::endl; + } + + // Everything not explicitly handled goes to DefWindowProc as per usual + default: + return DefWindowProc(hwnd, msg, wp, lp); + } + + return 0; +} +#endif + +std::string GetLastErrorAsString() { + //Get the error message, if any. + DWORD errorMessageID = ::GetLastError(); + if(errorMessageID == 0) + return std::string(); //No error message has been recorded + + LPSTR messageBuffer = nullptr; + size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL); + + std::string message(messageBuffer, size); + + //Free the buffer. + LocalFree(messageBuffer); + + return message; +} + int main() { - KeyboardHook hook; - hook.callback_event = [](const shared_ptr& event) { +#if 1 + KeyboardHook* hook = new hooks::Win32RawHook{}; + hook->callback_event = [](const shared_ptr& event) { if(event->type == KeyboardHook::KeyEvent::PRESS) cout << "press " << event->code.c_str() << ": shift: " << event->key_shift << ", alt: " << event->key_alt << ", ctrl: " << event->key_ctrl << ", win: " << event->key_windows << endl; else if(event->type == KeyboardHook::KeyEvent::TYPE) @@ -16,11 +86,45 @@ int main() { cout << "release " << event->code.c_str() << ": shift: " << event->key_shift << ", alt: " << event->key_alt << ", ctrl: " << event->key_ctrl << ", win: " << event->key_windows << endl; }; - if(!hook.attach()) { + if(!hook->attach()) { cerr << "failed to attach!" << endl; return 0; } +#else +#define CLASS_NAME "TeaClient - Hook" + WNDCLASS wc = {0}; + wc.lpfnWndProc = window_proc; + wc.cbWndExtra = sizeof(void*); + wc.hInstance = 0; + wc.lpszClassName = CLASS_NAME; + RegisterClass(&wc); - this_thread::sleep_for(seconds(100)); + auto hwnd = CreateWindow(CLASS_NAME, "TeaClient - PPT hook", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, 0); + + RAWINPUTDEVICE device; + device.usUsagePage = 0x01; //HID_USAGE_PAGE_GENERIC; + device.usUsage = 0x02; //HID_USAGE_GENERIC_MOUSE; + //device.usUsage = 0x06; //HID_USAGE_GENERIC_KEYBOARD; + device.dwFlags = RIDEV_NOLEGACY | RIDEV_INPUTSINK; + device.hwndTarget = hwnd; + if(!RegisterRawInputDevices(&device, 1, sizeof device)) { + std::cerr << "Invalid: " << GetLastErrorAsString() << std::endl; + } + + BOOL ret; + MSG msg; + while ((ret = GetMessage(&msg, hwnd, 0, 0)) != 0) { + if (ret == -1) { + std::cerr << "GetMessage() threw an error." << std::endl; + return 1; + } + else { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } +#endif + + this_thread::sleep_for(seconds(10)); return 0; } \ No newline at end of file