Improved mouse hook performance (not slowing down the system anymore)
This commit is contained in:
		
							parent
							
								
									383c9fbda9
								
							
						
					
					
						commit
						b92f584247
					
				| @ -2,7 +2,7 @@ set(MODULE_NAME "teaclient_ppt") | |||||||
| 
 | 
 | ||||||
| set(SOURCE_FILES src/KeyboardHook.cpp) | set(SOURCE_FILES src/KeyboardHook.cpp) | ||||||
| if (MSVC) | 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) | 	add_definitions(-DUSING_UV_SHARED) | ||||||
| else() | else() | ||||||
| 	add_definitions(-DHAVE_X11) | 	add_definitions(-DHAVE_X11) | ||||||
|  | |||||||
| @ -8,7 +8,12 @@ using namespace std; | |||||||
| 
 | 
 | ||||||
| #include "include/NanException.h" | #include "include/NanException.h" | ||||||
| #include "include/NanEventCallback.h" | #include "include/NanEventCallback.h" | ||||||
|  | 
 | ||||||
|  | #ifdef WIN32 | ||||||
|  | 	#include "src/Win32KeyboardHook.h" | ||||||
|  | #else | ||||||
| 	#include "src/KeyboardHook.h" | 	#include "src/KeyboardHook.h" | ||||||
|  | #endif | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| std::mutex callback_lock; | std::mutex callback_lock; | ||||||
| @ -62,7 +67,7 @@ NAN_METHOD(UnregisterCallback) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| NAN_MODULE_INIT(init) { | NAN_MODULE_INIT(init) { | ||||||
| 	hook = make_unique<KeyboardHook>(); | 	hook = make_unique<hooks::Win32RawHook>(); | ||||||
| 	if(!hook->attach()) { | 	if(!hook->attach()) { | ||||||
| 		NAN_THROW_EXCEPTION(Error, "Failed to attach hook!"); | 		NAN_THROW_EXCEPTION(Error, "Failed to attach hook!"); | ||||||
| 		return; | 		return; | ||||||
|  | |||||||
| @ -1,6 +1,26 @@ | |||||||
| #include "KeyboardHook.h" | #include "./KeyboardHook.h" | ||||||
|  | #include <cassert> | ||||||
| 
 | 
 | ||||||
| using namespace std; | 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) { | void KeyboardHook::trigger_key_event(const enum KeyEvent::type& type, const std::string &key) { | ||||||
| 	if(!this->callback_event) return; | 	if(!this->callback_event) return; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -4,19 +4,28 @@ | |||||||
| #include <thread> | #include <thread> | ||||||
| #include <map> | #include <map> | ||||||
| 
 | 
 | ||||||
| #ifdef HAVE_X11 | //#define HOOK_X11
 | ||||||
|  | 
 | ||||||
|  | #if defined(HOOK_X11) | ||||||
| 	#include <X11/Xlib.h> | 	#include <X11/Xlib.h> | ||||||
| #elif defined(WIN32) |  | ||||||
| 	#include <Windows.h> |  | ||||||
| #endif | #endif | ||||||
|  | 
 | ||||||
|  | enum struct KeyboardHookType { | ||||||
|  | 	X11, | ||||||
|  | 
 | ||||||
|  | 	RAW_INPUT, | ||||||
|  | 	SYSTEM_HOOK | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| class KeyboardHook { | class KeyboardHook { | ||||||
| 	#if defined(WIN32) | 	#ifdef HOOK_WIN32_LL | ||||||
| 		friend LRESULT CALLBACK _keyboard_hook_callback(int, WPARAM, LPARAM); | 		friend LRESULT CALLBACK _keyboard_hook_callback(int, WPARAM, LPARAM); | ||||||
|         friend LRESULT CALLBACK _mouse_hook_callback(int, WPARAM, LPARAM); |         friend LRESULT CALLBACK _mouse_hook_callback(int, WPARAM, LPARAM); | ||||||
| 	#endif | 	#endif | ||||||
| 	public: | 	public: | ||||||
| 		struct KeyType { | 		typedef unsigned int KeyID; | ||||||
| 			enum value { | 
 | ||||||
|  | 		enum struct KeyType { | ||||||
| 			KEY_UNKNOWN, | 			KEY_UNKNOWN, | ||||||
| 
 | 
 | ||||||
| 			KEY_NORMAL, | 			KEY_NORMAL, | ||||||
| @ -25,7 +34,6 @@ class KeyboardHook { | |||||||
| 			KEY_WIN, | 			KEY_WIN, | ||||||
| 			KEY_CTRL | 			KEY_CTRL | ||||||
| 		}; | 		}; | ||||||
| 		}; |  | ||||||
| 
 | 
 | ||||||
| 		struct KeyEvent { | 		struct KeyEvent { | ||||||
| 			enum type { | 			enum type { | ||||||
| @ -45,17 +53,23 @@ class KeyboardHook { | |||||||
| 		}; | 		}; | ||||||
| 		typedef std::function<void(const std::shared_ptr<KeyEvent>& /* event */)> callback_event_t; | 		typedef std::function<void(const std::shared_ptr<KeyEvent>& /* event */)> callback_event_t; | ||||||
| 
 | 
 | ||||||
| 		KeyboardHook(); | 		KeyboardHook(KeyboardHookType); | ||||||
| 		virtual ~KeyboardHook(); | 		virtual ~KeyboardHook(); | ||||||
| 
 | 
 | ||||||
| 		bool attach(); | 		[[nodiscard]] inline KeyboardHookType type() const { return this->type_; } | ||||||
| 		inline bool attached() { return this->_attached; } | 		[[nodiscard]] virtual bool keytype_supported() const = 0; | ||||||
| 		void detach(); | 
 | ||||||
|  | 		[[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 */); | 		void trigger_key_event(const enum KeyEvent::type&, const std::string& /* key */); | ||||||
| 		callback_event_t callback_event; | 		callback_event_t callback_event; | ||||||
| 	private: | 	protected: | ||||||
| 	#ifdef HAVE_X11 | 		const KeyboardHookType type_; | ||||||
|  | 
 | ||||||
|  | #if 0 | ||||||
|  | 	#if defined(HOOK_X11) | ||||||
| 		typedef int KeyID; | 		typedef int KeyID; | ||||||
| 		Display* display = nullptr; | 		Display* display = nullptr; | ||||||
| 		Window window_root = 0; | 		Window window_root = 0; | ||||||
| @ -63,20 +77,20 @@ class KeyboardHook { | |||||||
| 		int focus_revert; | 		int focus_revert; | ||||||
| 
 | 
 | ||||||
| 		long end_id = 0; | 		long end_id = 0; | ||||||
| 	#elif defined(WIN32) |     #elseif defined(HOOK_WIN32_LL) | ||||||
| 		typedef UINT KeyID; | 		typedef UINT KeyID; | ||||||
| 		HHOOK keyboad_hook_id{nullptr}; | 		HHOOK keyboad_hook_id{nullptr}; | ||||||
|         HHOOK mouse_hook_id{nullptr}; |  | ||||||
| 
 |  | ||||||
|         bool keyboard_hook_callback(int, WPARAM, LPARAM); |         bool keyboard_hook_callback(int, WPARAM, LPARAM); | ||||||
|  | 
 | ||||||
|  | #ifdef USE_MOUSE_HOOK | ||||||
|  |         HHOOK mouse_hook_id{nullptr}; | ||||||
|         bool mouse_hook_callback(int, WPARAM, LPARAM); |         bool mouse_hook_callback(int, WPARAM, LPARAM); | ||||||
| #endif | #endif | ||||||
|  |     #endif | ||||||
|  | #endif | ||||||
| 
 | 
 | ||||||
| 		std::map<KeyID, bool> map_key; | 		std::map<KeyID, bool> map_key; | ||||||
| 		std::map<KeyID, KeyID> map_special; | 		std::map<KeyType, KeyID> map_special; | ||||||
| 
 | 
 | ||||||
| 		bool _attached = false; | 		bool _attached = false; | ||||||
| 		bool active = false; |  | ||||||
| 		std::thread poll_thread; |  | ||||||
| 		void poll_events(); |  | ||||||
| }; | }; | ||||||
| @ -1,193 +1,11 @@ | |||||||
| #include <iostream> | //
 | ||||||
| #include <cassert> | // Created by WolverinDEV on 01/05/2020.
 | ||||||
| #include <string> | //
 | ||||||
| #include <mutex> |  | ||||||
| #include "KeyboardHook.h" |  | ||||||
| 
 | 
 | ||||||
| using namespace std; | #include "./Win32KeyboardHook.h" | ||||||
| 
 | 
 | ||||||
| typedef KBDLLHOOKSTRUCT KeyboardHookStruct; | namespace hooks { | ||||||
| typedef MSLLHOOKSTRUCT MouseHookStruct; |     std::string key_string_from_vk(DWORD code, bool extended) { | ||||||
| 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() { |  | ||||||
| 	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; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| 	MSG msg; |  | ||||||
| 	while(!GetMessage(&msg, nullptr, 0, 0) && this->active) { |  | ||||||
| 		TranslateMessage(&msg); |  | ||||||
| 		DispatchMessage(&msg); |  | ||||||
| 	} |  | ||||||
|     std::this_thread::sleep_for(std::chrono::seconds{1}); |  | ||||||
| 
 |  | ||||||
| 	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); |         auto scan_code = MapVirtualKey(code, MAPVK_VK_TO_VSC); | ||||||
|         if(extended) |         if(extended) | ||||||
|             scan_code |= KF_EXTENDED; |             scan_code |= KF_EXTENDED; | ||||||
| @ -197,16 +15,22 @@ inline std::string key_code(DWORD code, bool extended = false) { | |||||||
|         if(length == 0) |         if(length == 0) | ||||||
|             return "error"; |             return "error"; | ||||||
|         else |         else | ||||||
| 		return string(key_buffer, length); |             return std::string{key_buffer, (size_t) length}; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| inline std::string key_code(KeyboardHookStruct* keyboard) { |     std::string key_string_from_sc(USHORT code) { | ||||||
| 	return key_code(keyboard->vkCode, (keyboard->flags & LLKHF_EXTENDED) > 0); |         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}; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| using KeyType = KeyboardHook::KeyType; |  | ||||||
|     //https://docs.microsoft.com/en-us/windows/desktop/inputdev/virtual-key-codes
 |     //https://docs.microsoft.com/en-us/windows/desktop/inputdev/virtual-key-codes
 | ||||||
| inline KeyType::value key_type(DWORD vk_code) { |     KeyboardHook::KeyType key_type_from_vk(DWORD vk_code) { | ||||||
|  |         using KeyType = KeyboardHook::KeyType; | ||||||
|  | 
 | ||||||
|         switch(vk_code) { |         switch(vk_code) { | ||||||
|             case VK_CONTROL: |             case VK_CONTROL: | ||||||
|             case VK_LCONTROL: |             case VK_LCONTROL: | ||||||
| @ -227,95 +51,4 @@ inline KeyType::value key_type(DWORD vk_code) { | |||||||
|                 return KeyType::KEY_NORMAL; |                 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; |  | ||||||
|         } |  | ||||||
|         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; |  | ||||||
| } | } | ||||||
							
								
								
									
										68
									
								
								native/ppt/src/Win32KeyboardHook.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								native/ppt/src/Win32KeyboardHook.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,68 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "./KeyboardHook.h" | ||||||
|  | #include <condition_variable> | ||||||
|  | #include <Windows.h> | ||||||
|  | 
 | ||||||
|  | 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}; | ||||||
|  |     }; | ||||||
|  | } | ||||||
							
								
								
									
										301
									
								
								native/ppt/src/Win32KeyboardHookLL.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										301
									
								
								native/ppt/src/Win32KeyboardHookLL.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,301 @@ | |||||||
|  | #include <iostream> | ||||||
|  | #include <cassert> | ||||||
|  | #include <string> | ||||||
|  | #include <mutex> | ||||||
|  | #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; | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										200
									
								
								native/ppt/src/Win32KeyboardRawInput.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								native/ppt/src/Win32KeyboardRawInput.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,200 @@ | |||||||
|  | //
 | ||||||
|  | // Created by WolverinDEV on 01/05/2020.
 | ||||||
|  | //
 | ||||||
|  | 
 | ||||||
|  | #include "./Win32KeyboardHook.h" | ||||||
|  | #include <iostream> | ||||||
|  | #include <WinUser.h> | ||||||
|  | 
 | ||||||
|  | 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<Win32RawHook *>(GetWindowLongPtr(hwnd, GWLP_USERDATA)); | ||||||
|  | 
 | ||||||
|  |         switch (msg) { | ||||||
|  |             case WM_CREATE: { | ||||||
|  |                 CREATESTRUCT *s = reinterpret_cast<CREATESTRUCT *>(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)); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -1,13 +1,83 @@ | |||||||
| #include "../src/KeyboardHook.h" | #include "../src/KeyboardHook.h" | ||||||
|  | #include "../src/Win32KeyboardHook.h" | ||||||
| #include <iostream> | #include <iostream> | ||||||
| #include <thread> | #include <thread> | ||||||
|  | #include <Windows.h> | ||||||
| 
 | 
 | ||||||
| using namespace std::chrono; | using namespace std::chrono; | ||||||
| using namespace std; | 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<vrpn_DirectXRumblePad *>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
 | ||||||
|  | 
 | ||||||
|  | 	switch (msg) { | ||||||
|  | 		// Window is being created; store "this" pointer for future retrieval
 | ||||||
|  | 		case WM_CREATE: { | ||||||
|  | 			CREATESTRUCT *s = reinterpret_cast<CREATESTRUCT *>(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() { | int main() { | ||||||
| 	KeyboardHook hook; | #if 1 | ||||||
| 	hook.callback_event = [](const shared_ptr<KeyboardHook::KeyEvent>& event) { | 	KeyboardHook* hook = new hooks::Win32RawHook{}; | ||||||
|  | 	hook->callback_event = [](const shared_ptr<KeyboardHook::KeyEvent>& event) { | ||||||
| 		if(event->type == KeyboardHook::KeyEvent::PRESS) | 		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; | 			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) | 		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; | 			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; | 		cerr << "failed to attach!" << endl; | ||||||
| 		return 0; | 		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; | 	return 0; | ||||||
| } | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user