Files
PowerToys/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardManager.cpp
Andrey Nekrasov f742d3c1c3 [KBM]Allow remapping keys and shortcuts to arbitrary unicode sequences (#29399)
* [KBM] Allow remapping keys and shortcuts to arbitrary unicode sequences

* f: spelling

* f: tests

* f: split shortcut configuration

* f: address ui layout comments

* [BugReport]Don't report personal info

* f: fix crash in KBME

* f: add missed type button

* f: fix shortcut line UI elements alignment

* f: align elements size

* f: add warning about non-mapped keys
2023-11-23 10:46:07 +00:00

176 lines
5.8 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;
KeyboardManager::KeyboardManager()
{
// 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;
try
{
LoadSettings();
}
catch (...)
{
Logger::error("Failed to load settings");
}
loadingSettings = false;
};
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;
}
}
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);
}