-
Notifications
You must be signed in to change notification settings - Fork 6.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Settings/Run] LowLevel Keyboard hooking for Hotkeys (#3825)
* [Launcher/Settings] Low Level Keyboard Hooks * [Run] LowLevel Keyboard Hook for Hotkeys * Prevent shortcuts from auto repeating when keeping the keys pressed down
- Loading branch information
Showing
28 changed files
with
584 additions
and
546 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
#include "pch.h" | ||
#include "HotkeyManager.h" | ||
|
||
using namespace interop; | ||
|
||
HotkeyManager::HotkeyManager() | ||
{ | ||
keyboardEventCallback = gcnew KeyboardEventCallback(this, &HotkeyManager::KeyboardEventProc); | ||
isActiveCallback = gcnew IsActiveCallback(this, &HotkeyManager::IsActiveProc); | ||
filterKeyboardCallback = gcnew FilterKeyboardEvent(this, &HotkeyManager::FilterKeyboardProc); | ||
|
||
keyboardHook = gcnew KeyboardHook( | ||
keyboardEventCallback, | ||
isActiveCallback, | ||
filterKeyboardCallback | ||
); | ||
hotkeys = gcnew Dictionary<HOTKEY_HANDLE, HotkeyCallback ^>(); | ||
pressedKeys = gcnew Hotkey(); | ||
keyboardHook->Start(); | ||
} | ||
|
||
HotkeyManager::~HotkeyManager() | ||
{ | ||
delete keyboardHook; | ||
} | ||
|
||
// When all Shortcut keys are pressed, fire the HotkeyCallback event. | ||
void HotkeyManager::KeyboardEventProc(KeyboardEvent^ ev) | ||
{ | ||
auto pressedKeysHandle = GetHotkeyHandle(pressedKeys); | ||
if (hotkeys->ContainsKey(pressedKeysHandle)) | ||
{ | ||
hotkeys[pressedKeysHandle]->Invoke(); | ||
} | ||
} | ||
|
||
// Hotkeys are intended to be global, therefore they are always active no matter the | ||
// context in which the keypress occurs. | ||
bool HotkeyManager::IsActiveProc() | ||
{ | ||
return true; | ||
} | ||
|
||
// KeyboardEvent callback is only fired for relevant key events. | ||
bool HotkeyManager::FilterKeyboardProc(KeyboardEvent^ ev) | ||
{ | ||
auto oldHandle = GetHotkeyHandle(pressedKeys); | ||
|
||
// Updating the pressed keys here so we know if the keypress event | ||
// should be propagated or not. | ||
UpdatePressedKeys(ev); | ||
|
||
auto pressedKeysHandle = GetHotkeyHandle(pressedKeys); | ||
|
||
// Check if the hotkey matches the pressed keys, and check if the pressed keys aren't duplicate | ||
// (there shouldn't be auto repeating hotkeys) | ||
if (hotkeys->ContainsKey(pressedKeysHandle) && oldHandle != pressedKeysHandle) | ||
{ | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
// NOTE: Replaces old hotkey if one already present. | ||
HOTKEY_HANDLE HotkeyManager::RegisterHotkey(Hotkey ^ hotkey, HotkeyCallback ^ callback) | ||
{ | ||
auto handle = GetHotkeyHandle(hotkey); | ||
hotkeys[handle] = callback; | ||
return handle; | ||
} | ||
|
||
void HotkeyManager::UnregisterHotkey(HOTKEY_HANDLE handle) | ||
{ | ||
hotkeys->Remove(handle); | ||
} | ||
|
||
HOTKEY_HANDLE HotkeyManager::GetHotkeyHandle(Hotkey ^ hotkey) | ||
{ | ||
HOTKEY_HANDLE handle = hotkey->Key; | ||
handle |= hotkey->Win << 8; | ||
handle |= hotkey->Ctrl << 9; | ||
handle |= hotkey->Shift << 10; | ||
handle |= hotkey->Alt << 11; | ||
return handle; | ||
} | ||
|
||
void HotkeyManager::UpdatePressedKey(DWORD code, bool replaceWith, unsigned char replaceWithKey) | ||
{ | ||
switch (code) | ||
{ | ||
case VK_LWIN: | ||
case VK_RWIN: | ||
pressedKeys->Win = replaceWith; | ||
break; | ||
case VK_CONTROL: | ||
case VK_LCONTROL: | ||
case VK_RCONTROL: | ||
pressedKeys->Ctrl = replaceWith; | ||
break; | ||
case VK_SHIFT: | ||
case VK_LSHIFT: | ||
case VK_RSHIFT: | ||
pressedKeys->Shift = replaceWith; | ||
break; | ||
case VK_MENU: | ||
case VK_LMENU: | ||
case VK_RMENU: | ||
pressedKeys->Alt = replaceWith; | ||
break; | ||
default: | ||
pressedKeys->Key = replaceWithKey; | ||
break; | ||
} | ||
} | ||
|
||
void HotkeyManager::UpdatePressedKeys(KeyboardEvent ^ ev) | ||
{ | ||
switch (ev->message) | ||
{ | ||
case WM_KEYDOWN: | ||
case WM_SYSKEYDOWN: | ||
{ | ||
UpdatePressedKey(ev->key, true, ev->key); | ||
} | ||
break; | ||
case WM_KEYUP: | ||
case WM_SYSKEYUP: | ||
{ | ||
UpdatePressedKey(ev->key, false, 0); | ||
} | ||
break; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
#pragma once | ||
#include <Windows.h> | ||
#include "KeyboardHook.h" | ||
|
||
namespace interop | ||
{ | ||
public | ||
ref struct Hotkey | ||
{ | ||
bool Win; | ||
bool Ctrl; | ||
bool Shift; | ||
bool Alt; | ||
unsigned char Key; | ||
|
||
Hotkey() | ||
{ | ||
Win = false; | ||
Ctrl = false; | ||
Shift = false; | ||
Alt = false; | ||
Key = 0; | ||
} | ||
}; | ||
|
||
public | ||
delegate void HotkeyCallback(); | ||
|
||
typedef unsigned short HOTKEY_HANDLE; | ||
|
||
public | ||
ref class HotkeyManager | ||
{ | ||
public: | ||
HotkeyManager(); | ||
~HotkeyManager(); | ||
|
||
HOTKEY_HANDLE RegisterHotkey(Hotkey ^ hotkey, HotkeyCallback ^ callback); | ||
void UnregisterHotkey(HOTKEY_HANDLE handle); | ||
|
||
private: | ||
KeyboardHook ^ keyboardHook; | ||
Dictionary<HOTKEY_HANDLE, HotkeyCallback ^> ^ hotkeys; | ||
Hotkey ^ pressedKeys; | ||
KeyboardEventCallback ^ keyboardEventCallback; | ||
IsActiveCallback ^ isActiveCallback; | ||
FilterKeyboardEvent ^ filterKeyboardCallback; | ||
|
||
|
||
void KeyboardEventProc(KeyboardEvent ^ ev); | ||
bool IsActiveProc(); | ||
bool FilterKeyboardProc(KeyboardEvent ^ ev); | ||
HOTKEY_HANDLE GetHotkeyHandle(Hotkey ^ hotkey); | ||
void UpdatePressedKeys(KeyboardEvent ^ ev); | ||
void UpdatePressedKey(DWORD code, bool replaceWith, unsigned char replaceWithKey); | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
#include "pch.h" | ||
#include "KeyboardHook.h" | ||
#include <exception> | ||
#include <msclr\marshal.h> | ||
#include <msclr\marshal_cppstd.h> | ||
|
||
using namespace interop; | ||
using namespace System::Runtime::InteropServices; | ||
using namespace System; | ||
using namespace System::Diagnostics; | ||
|
||
KeyboardHook::KeyboardHook( | ||
KeyboardEventCallback ^ keyboardEventCallback, | ||
IsActiveCallback ^ isActiveCallback, | ||
FilterKeyboardEvent ^ filterKeyboardEvent) | ||
{ | ||
kbEventDispatch = gcnew Thread(gcnew ThreadStart(this, &KeyboardHook::DispatchProc)); | ||
queue = gcnew Queue<KeyboardEvent ^>(); | ||
this->keyboardEventCallback = keyboardEventCallback; | ||
this->isActiveCallback = isActiveCallback; | ||
this->filterKeyboardEvent = filterKeyboardEvent; | ||
} | ||
|
||
KeyboardHook::~KeyboardHook() | ||
{ | ||
quit = true; | ||
kbEventDispatch->Join(); | ||
|
||
// Unregister low level hook procedure | ||
UnhookWindowsHookEx(hookHandle); | ||
} | ||
|
||
void KeyboardHook::DispatchProc() | ||
{ | ||
Monitor::Enter(queue); | ||
quit = false; | ||
while (!quit) | ||
{ | ||
if (queue->Count == 0) | ||
{ | ||
Monitor::Wait(queue); | ||
continue; | ||
} | ||
auto nextEv = queue->Dequeue(); | ||
|
||
// Release lock while callback is being invoked | ||
Monitor::Exit(queue); | ||
|
||
keyboardEventCallback->Invoke(nextEv); | ||
|
||
// Re-aquire lock | ||
Monitor::Enter(queue); | ||
} | ||
|
||
Monitor::Exit(queue); | ||
} | ||
|
||
void KeyboardHook::Start() | ||
{ | ||
hookProc = gcnew HookProcDelegate(this, &KeyboardHook::HookProc); | ||
Process ^ curProcess = Process::GetCurrentProcess(); | ||
ProcessModule ^ curModule = curProcess->MainModule; | ||
// register low level hook procedure | ||
hookHandle = SetWindowsHookEx( | ||
WH_KEYBOARD_LL, | ||
(HOOKPROC)(void*)Marshal::GetFunctionPointerForDelegate(hookProc), | ||
0, | ||
0); | ||
if (hookHandle == nullptr) | ||
{ | ||
throw std::exception("SetWindowsHookEx failed."); | ||
} | ||
|
||
kbEventDispatch->Start(); | ||
} | ||
|
||
LRESULT CALLBACK KeyboardHook::HookProc(int nCode, WPARAM wParam, LPARAM lParam) | ||
{ | ||
if (nCode == HC_ACTION && isActiveCallback->Invoke()) | ||
{ | ||
KeyboardEvent ^ ev = gcnew KeyboardEvent(); | ||
ev->message = wParam; | ||
ev->key = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam)->vkCode; | ||
if (filterKeyboardEvent != nullptr && !filterKeyboardEvent->Invoke(ev)) | ||
{ | ||
return CallNextHookEx(hookHandle, nCode, wParam, lParam); | ||
} | ||
|
||
Monitor::Enter(queue); | ||
queue->Enqueue(ev); | ||
Monitor::Pulse(queue); | ||
Monitor::Exit(queue); | ||
return 1; | ||
} | ||
return CallNextHookEx(hookHandle, nCode, wParam, lParam); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
#pragma once | ||
|
||
using namespace System::Threading; | ||
using namespace System::Collections::Generic; | ||
|
||
namespace interop | ||
{ | ||
public | ||
ref struct KeyboardEvent | ||
{ | ||
WPARAM message; | ||
int key; | ||
}; | ||
|
||
public | ||
delegate void KeyboardEventCallback(KeyboardEvent ^ ev); | ||
public | ||
delegate bool IsActiveCallback(); | ||
public | ||
delegate bool FilterKeyboardEvent(KeyboardEvent ^ ev); | ||
|
||
public | ||
ref class KeyboardHook | ||
{ | ||
public: | ||
KeyboardHook( | ||
KeyboardEventCallback ^ keyboardEventCallback, | ||
IsActiveCallback ^ isActiveCallback, | ||
FilterKeyboardEvent ^ filterKeyboardEvent); | ||
~KeyboardHook(); | ||
|
||
void Start(); | ||
|
||
private: | ||
delegate LRESULT HookProcDelegate(int nCode, WPARAM wParam, LPARAM lParam); | ||
Thread ^ kbEventDispatch; | ||
Queue<KeyboardEvent ^> ^ queue; | ||
KeyboardEventCallback ^ keyboardEventCallback; | ||
IsActiveCallback ^ isActiveCallback; | ||
FilterKeyboardEvent ^ filterKeyboardEvent; | ||
bool quit; | ||
HHOOK hookHandle; | ||
HookProcDelegate ^ hookProc; | ||
|
||
void DispatchProc(); | ||
LRESULT CALLBACK HookProc(int nCode, WPARAM wParam, LPARAM lParam); | ||
}; | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.