Improved mouse hook performance (not slowing down the system anymore)

This commit is contained in:
WolverinDEV 2020-05-01 17:37:18 +02:00
parent 383c9fbda9
commit b92f584247
9 changed files with 789 additions and 344 deletions

View File

@ -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)

View File

@ -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<KeyboardHook>();
hook = make_unique<hooks::Win32RawHook>();
if(!hook->attach()) {
NAN_THROW_EXCEPTION(Error, "Failed to attach hook!");
return;

View File

@ -1,6 +1,26 @@
#include "KeyboardHook.h"
#include "./KeyboardHook.h"
#include <cassert>
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;

View File

@ -4,27 +4,35 @@
#include <thread>
#include <map>
#ifdef HAVE_X11
//#define HOOK_X11
#if defined(HOOK_X11)
#include <X11/Xlib.h>
#elif defined(WIN32)
#include <Windows.h>
#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<void(const std::shared_ptr<KeyEvent>& /* 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<KeyID, bool> map_key;
std::map<KeyID, KeyID> map_special;
std::map<KeyType, KeyID> map_special;
bool _attached = false;
bool active = false;
std::thread poll_thread;
void poll_events();
};

View File

@ -1,321 +1,54 @@
#include <iostream>
#include <cassert>
#include <string>
#include <mutex>
#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;
}

View 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};
};
}

View 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;
}
}

View 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));
}
}
}
}

View File

@ -1,13 +1,83 @@
#include "../src/KeyboardHook.h"
#include "../src/Win32KeyboardHook.h"
#include <iostream>
#include <thread>
#include <Windows.h>
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<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() {
KeyboardHook hook;
hook.callback_event = [](const shared_ptr<KeyboardHook::KeyEvent>& event) {
#if 1
KeyboardHook* hook = new hooks::Win32RawHook{};
hook->callback_event = [](const shared_ptr<KeyboardHook::KeyEvent>& 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;
}