/ src / common / interop / KeyboardHook.cpp
KeyboardHook.cpp
 1  #include "pch.h"
 2  #include "KeyboardHook.h"
 3  #include "KeyboardHook.g.cpp"
 4  #include <common/debug_control.h>
 5  #include <common/utils/winapi_error.h>
 6  
 7  namespace winrt::PowerToys::Interop::implementation
 8  {
 9      std::mutex KeyboardHook::instancesMutex;
10      std::unordered_set<KeyboardHook*> KeyboardHook::instances;
11  
12      KeyboardHook::KeyboardHook(winrt::PowerToys::Interop::KeyboardEventCallback const& keyboardEventCallback, winrt::PowerToys::Interop::IsActiveCallback const& isActiveCallback, winrt::PowerToys::Interop::FilterKeyboardEvent const& filterKeyboardEvent)
13      {
14          this->keyboardEventCallback = keyboardEventCallback;
15          this->isActiveCallback = isActiveCallback;
16          this->filterKeyboardEvent = filterKeyboardEvent;
17      }
18  
19      void KeyboardHook::Close()
20      {
21          std::unique_lock lock { instancesMutex };
22          auto iter = instances.find(this);
23          if (iter != instances.end())
24          {
25              instances.erase(iter);
26          }
27          if (instances.size() < 1 && hookHandle != nullptr)
28          {
29              if (UnhookWindowsHookEx(hookHandle))
30              {
31                  hookHandle = nullptr;
32              }
33          }
34      }
35  
36  
37      void KeyboardHook::Start()
38      {
39  #if defined(DISABLE_LOWLEVEL_HOOKS_WHEN_DEBUGGED)
40          const bool hookDisabled = IsDebuggerPresent();
41  #else
42          const bool hookDisabled = false;
43  #endif
44          if (!hookDisabled)
45          {
46              std::unique_lock lock { instancesMutex };
47              assert(instances.find(this) == instances.end());
48              // register low level hook procedure
49              instances.insert(this);
50              if (hookHandle == nullptr)
51              {
52                  hookHandle = SetWindowsHookEx(
53                      WH_KEYBOARD_LL,
54                      HookProc,
55                      0,
56                      0);
57                  if (hookHandle == nullptr)
58                  {
59                      DWORD errorCode = GetLastError();
60                      show_last_error_message(L"SetWindowsHookEx", errorCode, L"PowerToys - Interop");
61                  }
62              }
63          }
64      }
65      LRESULT KeyboardHook::HookProc(int nCode, WPARAM wParam, LPARAM lParam)
66      {
67          if (nCode == HC_ACTION)
68          {
69              std::vector<KeyboardHook*> instances_copy;
70              {
71                  /* Use a copy of instances, to iterate through the copy without needing to maintain the lock */
72                  std::unique_lock lock{ instancesMutex };
73                  instances_copy.reserve(instances.size());
74                  std::copy(instances.begin(), instances.end(), std::back_inserter(instances_copy));
75              }
76  
77              for (auto const& s_instance : instances_copy)
78              {
79                  if (s_instance->isActiveCallback())
80                  {
81                      KeyboardEvent ev;
82                      ev.message = wParam;
83                      ev.key = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam)->vkCode;
84                      ev.dwExtraInfo = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam)->dwExtraInfo;
85  
86                      // Ignore the keyboard hook if the FilterKeyboardEvent returns false.
87                      if ((s_instance->filterKeyboardEvent != nullptr && !s_instance->filterKeyboardEvent(ev)))
88                      {
89                          continue;
90                      }
91  
92                      s_instance->keyboardEventCallback(ev);
93                      return 1;
94                  }
95              }
96          }
97          return CallNextHookEx(NULL, nCode, wParam, lParam);
98      }
99  }