KeyboardManagerEditor.cpp
1 // KeyboardManagerEditor.cpp : Defines the entry point for the application. 2 // 3 4 #include "pch.h" 5 #include "KeyboardManagerEditor.h" 6 7 #include <common/utils/winapi_error.h> 8 #include <common/utils/logger_helper.h> 9 #include <common/utils/ProcessWaiter.h> 10 #include <common/utils/UnhandledExceptionHandler.h> 11 #include <common/utils/gpo.h> 12 13 #include <trace.h> 14 15 #include <keyboardmanager/common/KeyboardEventHandlers.h> 16 17 #include <EditKeyboardWindow.h> 18 #include <EditShortcutsWindow.h> 19 #include <KeyboardManagerState.h> 20 21 std::unique_ptr<KeyboardManagerEditor> editor = nullptr; 22 const std::wstring instanceMutexName = L"Local\\PowerToys_KBMEditor_InstanceMutex"; 23 24 int APIENTRY wWinMain(_In_ HINSTANCE hInstance, 25 _In_opt_ HINSTANCE /*hPrevInstance*/, 26 _In_ LPWSTR /*lpCmdLine*/, 27 _In_ int /*nCmdShow*/) 28 { 29 LoggerHelpers::init_logger(KeyboardManagerConstants::ModuleName, L"Editor", LogSettings::keyboardManagerLoggerName); 30 31 if (powertoys_gpo::getConfiguredKeyboardManagerEnabledValue() == powertoys_gpo::gpo_rule_configured_disabled) 32 { 33 Logger::warn(L"Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator."); 34 return 0; 35 } 36 37 InitUnhandledExceptionHandler(); 38 Trace::RegisterProvider(); 39 40 auto mutex = CreateMutex(nullptr, true, instanceMutexName.c_str()); 41 if (mutex == nullptr) 42 { 43 Logger::error(L"Failed to create mutex. {}", get_last_error_or_default(GetLastError())); 44 } 45 else 46 { 47 Logger::trace(L"Created/Opened {} mutex", instanceMutexName); 48 } 49 50 if (GetLastError() == ERROR_ALREADY_EXISTS) 51 { 52 Logger::info(L"KBM editor instance is already running"); 53 return 0; 54 } 55 56 int numArgs; 57 LPWSTR* cmdArgs = CommandLineToArgvW(GetCommandLineW(), &numArgs); 58 59 KeyboardManagerEditorType type = KeyboardManagerEditorType::KeyEditor; 60 if (!IsDebuggerPresent()) 61 { 62 if (cmdArgs == nullptr) 63 { 64 Logger::error(L"Keyboard Manager Editor cannot start as a standalone application"); 65 return -1; 66 } 67 68 if (numArgs < 2) 69 { 70 Logger::error(L"Invalid arguments on Keyboard Manager Editor start"); 71 return -1; 72 } 73 } 74 75 if (numArgs > 1) 76 { 77 type = static_cast<KeyboardManagerEditorType>(_wtoi(cmdArgs[1])); 78 } 79 80 std::wstring keysForShortcutToEdit = L""; 81 std::wstring action = L""; 82 83 // do some parsing of the cmdline arg to see if we need to behave different 84 // like, single edit mode, or "delete" mode. 85 // These extra args are from "OpenEditor" in the KeyboardManagerViewModel 86 if (numArgs >= 3) 87 { 88 if (numArgs >= 4) 89 { 90 keysForShortcutToEdit = std::wstring(cmdArgs[3]); 91 } 92 93 if (numArgs >= 5) 94 { 95 action = std::wstring(cmdArgs[4]); 96 } 97 98 99 std::wstring pid = std::wstring(cmdArgs[2]); 100 Logger::trace(L"Editor started from the settings with pid {}", pid); 101 if (!pid.empty()) 102 { 103 auto mainThreadId = GetCurrentThreadId(); 104 ProcessWaiter::OnProcessTerminate(pid, [mainThreadId](int err) { 105 if (err != ERROR_SUCCESS) 106 { 107 Logger::error(L"Failed to wait for parent process exit. {}", get_last_error_or_default(err)); 108 } 109 110 Logger::trace(L"Parent process exited. Exiting KeyboardManager editor"); 111 PostThreadMessage(mainThreadId, WM_QUIT, 0, 0); 112 }); 113 } 114 } 115 116 editor = std::make_unique<KeyboardManagerEditor>(hInstance); 117 if (!editor->StartLowLevelKeyboardHook()) 118 { 119 DWORD errorCode = GetLastError(); 120 show_last_error_message(L"SetWindowsHookEx", errorCode, L"PowerToys - Keyboard Manager Editor"); 121 auto errorMessage = get_last_error_message(errorCode); 122 Logger::error(L"Unable to start keyboard hook: {}", errorMessage.has_value() ? errorMessage.value() : L""); 123 Trace::Error(errorCode, errorMessage.has_value() ? errorMessage.value() : L"", L"start_lowlevel_keyboard_hook.SetWindowsHookEx"); 124 125 return -1; 126 } 127 128 editor->OpenEditorWindow(type, keysForShortcutToEdit, action); 129 130 editor = nullptr; 131 132 Trace::UnregisterProvider(); 133 return 0; 134 } 135 136 KeyboardManagerEditor::KeyboardManagerEditor(HINSTANCE hInst) : 137 hInstance(hInst) 138 { 139 bool loadedSuccessful = mappingConfiguration.LoadSettings(); 140 if (!loadedSuccessful) 141 { 142 std::this_thread::sleep_for(std::chrono::milliseconds(500)); 143 144 // retry once 145 mappingConfiguration.LoadSettings(); 146 } 147 148 StartLowLevelKeyboardHook(); 149 } 150 151 KeyboardManagerEditor::~KeyboardManagerEditor() 152 { 153 UnhookWindowsHookEx(hook); 154 } 155 156 bool KeyboardManagerEditor::StartLowLevelKeyboardHook() 157 { 158 #if defined(DISABLE_LOWLEVEL_HOOKS_WHEN_DEBUGGED) 159 if (IsDebuggerPresent()) 160 { 161 return true; 162 } 163 #endif 164 165 hook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyHookProc, GetModuleHandle(NULL), NULL); 166 return (hook != nullptr); 167 } 168 169 void KeyboardManagerEditor::OpenEditorWindow(KeyboardManagerEditorType type, std::wstring keysForShortcutToEdit, std::wstring action) 170 { 171 switch (type) 172 { 173 case KeyboardManagerEditorType::KeyEditor: 174 CreateEditKeyboardWindow(hInstance, keyboardManagerState, mappingConfiguration); 175 break; 176 case KeyboardManagerEditorType::ShortcutEditor: 177 CreateEditShortcutsWindow(hInstance, keyboardManagerState, mappingConfiguration, keysForShortcutToEdit, action); 178 } 179 } 180 181 intptr_t KeyboardManagerEditor::HandleKeyboardHookEvent(LowlevelKeyboardEvent* data) noexcept 182 { 183 // If the Detect Key Window is currently activated, then suppress the keyboard event 184 Helpers::KeyboardHookDecision singleKeyRemapUIDetected = keyboardManagerState.DetectSingleRemapKeyUIBackend(data); 185 if (singleKeyRemapUIDetected == Helpers::KeyboardHookDecision::Suppress) 186 { 187 return 1; 188 } 189 else if (singleKeyRemapUIDetected == Helpers::KeyboardHookDecision::SkipHook) 190 { 191 return 0; 192 } 193 194 // If the Detect Shortcut Window from Remap Keys is currently activated, then suppress the keyboard event 195 Helpers::KeyboardHookDecision remapKeyShortcutUIDetected = keyboardManagerState.DetectShortcutUIBackend(data, true); 196 if (remapKeyShortcutUIDetected == Helpers::KeyboardHookDecision::Suppress) 197 { 198 return 1; 199 } 200 else if (remapKeyShortcutUIDetected == Helpers::KeyboardHookDecision::SkipHook) 201 { 202 return 0; 203 } 204 205 // If the Detect Shortcut Window is currently activated, then suppress the keyboard event 206 Helpers::KeyboardHookDecision shortcutUIDetected = keyboardManagerState.DetectShortcutUIBackend(data, false); 207 if (shortcutUIDetected == Helpers::KeyboardHookDecision::Suppress) 208 { 209 return 1; 210 } 211 else if (shortcutUIDetected == Helpers::KeyboardHookDecision::SkipHook) 212 { 213 return 0; 214 } 215 216 return 0; 217 } 218 219 // Hook procedure definition 220 LRESULT KeyboardManagerEditor::KeyHookProc(int nCode, WPARAM wParam, LPARAM lParam) 221 { 222 LowlevelKeyboardEvent event; 223 if (nCode == HC_ACTION) 224 { 225 event.lParam = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam); 226 event.wParam = wParam; 227 event.lParam->vkCode = Helpers::EncodeKeyNumpadOrigin(event.lParam->vkCode, event.lParam->flags & LLKHF_EXTENDED); 228 229 if (editor->HandleKeyboardHookEvent(&event) == 1) 230 { 231 // 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 232 if (event.lParam->vkCode == VK_NUMLOCK && (event.wParam == WM_KEYDOWN || event.wParam == WM_SYSKEYDOWN) && event.lParam->dwExtraInfo != KeyboardManagerConstants::KEYBOARDMANAGER_SUPPRESS_FLAG) 233 { 234 KeyboardEventHandlers::SetNumLockToPreviousState(editor->GetInputHandler()); 235 } 236 return 1; 237 } 238 } 239 240 return CallNextHookEx(hook, nCode, wParam, lParam); 241 }