/ src / modules / keyboardmanager / KeyboardManagerEditor / KeyboardManagerEditor.cpp
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  }