Files
PowerToys/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardManager.cpp
Andrey Nekrasov 2a544583c0 [KBM] Do not register low level hook if there're no remappings (#29708)
* [KBM] Do not register low level hook if there're no remappings

* f: typo

* f: address review comment
2024-01-03 16:49:29 +01:00

223 lines
7.3 KiB
C++

#include "pch.h"
#include "KeyboardManager.h"
#include <interface/powertoy_module_interface.h>
#include <common/SettingsAPI/settings_objects.h>
#include <common/interop/shared_constants.h>
#include <common/debug_control.h>
#include <common/utils/winapi_error.h>
#include <common/logger/logger_settings.h>
#include <keyboardmanager/common/Shortcut.h>
#include <keyboardmanager/common/RemapShortcut.h>
#include <keyboardmanager/common/KeyboardManagerConstants.h>
#include <keyboardmanager/common/Helpers.h>
#include <keyboardmanager/common/KeyboardEventHandlers.h>
#include <ctime>
#include "KeyboardEventHandlers.h"
#include "trace.h"
HHOOK KeyboardManager::hookHandleCopy;
HHOOK KeyboardManager::hookHandle;
KeyboardManager* KeyboardManager::keyboardManagerObjectPtr;
namespace
{
DWORD mainThreadId = {};
}
KeyboardManager::KeyboardManager()
{
mainThreadId = GetCurrentThreadId();
// Load the initial settings.
LoadSettings();
// Set the static pointer to the newest object of the class
keyboardManagerObjectPtr = this;
std::filesystem::path modulePath(PTSettingsHelper::get_module_save_folder_location(moduleName));
auto changeSettingsCallback = [this](DWORD err) {
Logger::trace(L"{} event was signaled", KeyboardManagerConstants::SettingsEventName);
if (err != ERROR_SUCCESS)
{
Logger::error(L"Failed to watch settings changes. {}", get_last_error_or_default(err));
}
loadingSettings = true;
bool loadedSuccessfully = false;
try
{
LoadSettings();
loadedSuccessfully = true;
}
catch (...)
{
Logger::error("Failed to load settings");
}
loadingSettings = false;
if (!loadedSuccessfully)
return;
const bool newHasRemappings = HasRegisteredRemappingsUnchecked();
// We didn't have any bindings before and we have now
if (newHasRemappings && !hookHandle)
PostThreadMessageW(mainThreadId, StartHookMessageID, 0, 0);
// All bindings were removed
if (!newHasRemappings && hookHandle)
StopLowlevelKeyboardHook();
};
editorIsRunningEvent = CreateEvent(nullptr, true, false, KeyboardManagerConstants::EditorWindowEventName.c_str());
settingsEventWaiter = EventWaiter(KeyboardManagerConstants::SettingsEventName, changeSettingsCallback);
}
void KeyboardManager::LoadSettings()
{
bool loadedSuccessful = state.LoadSettings();
if (!loadedSuccessful)
{
std::this_thread::sleep_for(std::chrono::milliseconds(500));
// retry once
state.LoadSettings();
}
}
LRESULT CALLBACK KeyboardManager::HookProc(int nCode, const WPARAM wParam, const LPARAM lParam)
{
LowlevelKeyboardEvent event{};
if (nCode == HC_ACTION)
{
event.lParam = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam);
event.wParam = wParam;
event.lParam->vkCode = Helpers::EncodeKeyNumpadOrigin(event.lParam->vkCode, event.lParam->flags & LLKHF_EXTENDED);
if (keyboardManagerObjectPtr->HandleKeyboardHookEvent(&event) == 1)
{
// Reset Num Lock whenever a NumLock key down event is suppressed since Num Lock key state change occurs before it is intercepted by low level hooks
if (event.lParam->vkCode == VK_NUMLOCK && (event.wParam == WM_KEYDOWN || event.wParam == WM_SYSKEYDOWN) && event.lParam->dwExtraInfo != KeyboardManagerConstants::KEYBOARDMANAGER_SUPPRESS_FLAG)
{
KeyboardEventHandlers::SetNumLockToPreviousState(keyboardManagerObjectPtr->inputHandler);
}
return 1;
}
}
return CallNextHookEx(hookHandleCopy, nCode, wParam, lParam);
}
void KeyboardManager::StartLowlevelKeyboardHook()
{
#if defined(DISABLE_LOWLEVEL_HOOKS_WHEN_DEBUGGED)
if (IsDebuggerPresent())
{
return;
}
#endif
if (!hookHandle)
{
hookHandle = SetWindowsHookEx(WH_KEYBOARD_LL, HookProc, GetModuleHandle(NULL), NULL);
hookHandleCopy = hookHandle;
if (!hookHandle)
{
DWORD errorCode = GetLastError();
show_last_error_message(L"SetWindowsHookEx", errorCode, L"PowerToys - Keyboard Manager");
auto errorMessage = get_last_error_message(errorCode);
Trace::Error(errorCode, errorMessage.has_value() ? errorMessage.value() : L"", L"StartLowlevelKeyboardHook::SetWindowsHookEx");
}
}
}
void KeyboardManager::StopLowlevelKeyboardHook()
{
if (hookHandle)
{
UnhookWindowsHookEx(hookHandle);
hookHandle = nullptr;
}
}
bool KeyboardManager::HasRegisteredRemappings() const
{
constexpr int MaxAttempts = 5;
if (loadingSettings)
{
for (int currentAttempt = 0; currentAttempt < MaxAttempts; ++currentAttempt)
{
std::this_thread::sleep_for(std::chrono::milliseconds(500));
if (!loadingSettings)
break;
}
}
// Assume that we have registered remappings to be on the safe side if we couldn't check
if (loadingSettings)
return true;
return HasRegisteredRemappingsUnchecked();
}
bool KeyboardManager::HasRegisteredRemappingsUnchecked() const
{
return !(state.appSpecificShortcutReMap.empty() && state.appSpecificShortcutReMapSortedKeys.empty() && state.osLevelShortcutReMap.empty() && state.osLevelShortcutReMapSortedKeys.empty() && state.singleKeyReMap.empty() && state.singleKeyToTextReMap.empty());
}
intptr_t KeyboardManager::HandleKeyboardHookEvent(LowlevelKeyboardEvent* data) noexcept
{
if (loadingSettings)
{
return 0;
}
// Suspend remapping if remap key/shortcut window is opened
if (editorIsRunningEvent != nullptr && WaitForSingleObject(editorIsRunningEvent, 0) == WAIT_OBJECT_0)
{
return 0;
}
// If key has suppress flag, then suppress it
if (data->lParam->dwExtraInfo == KeyboardManagerConstants::KEYBOARDMANAGER_SUPPRESS_FLAG)
{
return 1;
}
// Remap a key
intptr_t SingleKeyRemapResult = KeyboardEventHandlers::HandleSingleKeyRemapEvent(inputHandler, data, state);
// Single key remaps have priority. If a key is remapped, only the remapped version should be visible to the shortcuts and hence the event should be suppressed here.
if (SingleKeyRemapResult == 1)
{
return 1;
}
/* This feature has not been enabled (code from proof of concept stage)
// Remap a key to behave like a modifier instead of a toggle
intptr_t SingleKeyToggleToModResult = KeyboardEventHandlers::HandleSingleKeyToggleToModEvent(inputHandler, data, keyboardManagerState);
*/
// Handle an app-specific shortcut remapping
intptr_t AppSpecificShortcutRemapResult = KeyboardEventHandlers::HandleAppSpecificShortcutRemapEvent(inputHandler, data, state);
// If an app-specific shortcut is remapped then the os-level shortcut remapping should be suppressed.
if (AppSpecificShortcutRemapResult == 1)
{
return 1;
}
intptr_t SingleKeyToTextRemapResult = KeyboardEventHandlers::HandleSingleKeyToTextRemapEvent(inputHandler, data, state);
if (SingleKeyToTextRemapResult == 1)
{
return 1;
}
// Handle an os-level shortcut remapping
return KeyboardEventHandlers::HandleOSLevelShortcutRemapEvent(inputHandler, data, state);
}