/
githubmirror
/
PowerToys
Обзор
Документация
Войти
/
githubmirror
/
PowerToys
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardManager.cpp
238 строк
8 KB
Kai Tao
Cmdpal extension: Powertoys extension for cmdpal (#44006)
23 дек 2025, 16:07
Не верифицирован
23 дек 2025, 16:07
d87dde1
Код
Авторство
О чём код?
#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/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.start(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(); } try { // Send telemetry about configured key/shortcut to key/shortcut mappings, OS an app specific level. Trace::SendKeyAndShortcutRemapLoadedConfiguration(state); } catch (...) { try { Logger::error("Failed to send telemetry for the configured remappings."); // Try not to crash the app sending telemetry. Everything inside a try. Trace::ErrorSendingKeyAndShortcutRemapLoadedConfiguration(); } catch (...) { } } } 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); }