/ src / modules / keyboardmanager / KeyboardManagerEngineTest / AppSpecificShortcutRemappingTests.cpp
AppSpecificShortcutRemappingTests.cpp
  1  #include "pch.h"
  2  
  3  // Suppressing 26466 - Don't use static_cast downcasts - in CppUnitTest.h
  4  #pragma warning(push)
  5  #pragma warning(disable : 26466)
  6  #include "CppUnitTest.h"
  7  #pragma warning(pop)
  8  
  9  #include "MockedInput.h"
 10  #include <keyboardmanager/KeyboardManagerEngineLibrary/State.h>
 11  #include <keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.h>
 12  #include "TestHelpers.h"
 13  #include <common/interop/shared_constants.h>
 14  
 15  using namespace Microsoft::VisualStudio::CppUnitTestFramework;
 16  
 17  namespace RemappingLogicTests
 18  {
 19      TEST_CLASS (AppSpecificShortcutRemappingTests)
 20  
 21      {
 22      private:
 23          KeyboardManagerInput::MockedInput mockedInputHandler;
 24          State testState;
 25          std::wstring testApp1 = L"testprocess1.exe";
 26          std::wstring testApp2 = L"testprocess2.exe";
 27  
 28      public:
 29          TEST_METHOD_INITIALIZE(InitializeTestEnv)
 30          {
 31              // Reset test environment
 32              TestHelpers::ResetTestEnv(mockedInputHandler, testState);
 33  
 34              // Set HandleOSLevelShortcutRemapEvent as the hook procedure
 35              std::function<intptr_t(LowlevelKeyboardEvent*)> currentHookProc = std::bind(&KeyboardEventHandlers::HandleAppSpecificShortcutRemapEvent, std::ref(mockedInputHandler), std::placeholders::_1, std::ref(testState));
 36              mockedInputHandler.SetHookProc(currentHookProc);
 37          }
 38  
 39          // Test if the app specific remap takes place when the target app is in foreground
 40          TEST_METHOD (AppSpecificShortcut_ShouldGetRemapped_WhenAppIsInForeground)
 41          {
 42              // Remap Ctrl+A to Alt+V
 43              Shortcut src;
 44              src.SetKey(VK_CONTROL);
 45              src.SetKey(0x41);
 46              Shortcut dest;
 47              dest.SetKey(VK_MENU);
 48              dest.SetKey(0x56);
 49              testState.AddAppSpecificShortcut(testApp1, src, dest);
 50  
 51              // Set the testApp as the foreground process
 52              mockedInputHandler.SetForegroundProcess(testApp1);
 53  
 54              std::vector<INPUT> inputs{
 55                  { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL } },
 56                  { .type = INPUT_KEYBOARD, .ki = { .wVk = 'A' } }
 57              };
 58  
 59              // Send Ctrl+A keydown
 60              mockedInputHandler.SendVirtualInput(inputs);
 61  
 62              // Ctrl and A key states should be unchanged, Alt and V key states should be true
 63              Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_CONTROL), false);
 64              Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), false);
 65              Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_MENU), true);
 66              Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x56), true);
 67          }
 68  
 69          // Test if the app specific remap takes place when the target app is not in foreground
 70          TEST_METHOD (AppSpecificShortcut_ShouldNotGetRemapped_WhenAppIsNotInForeground)
 71          {
 72              // Remap Ctrl+A to Alt+V
 73              Shortcut src;
 74              src.SetKey(VK_CONTROL);
 75              src.SetKey(0x41);
 76              Shortcut dest;
 77              dest.SetKey(VK_MENU);
 78              dest.SetKey(0x56);
 79              testState.AddAppSpecificShortcut(testApp1, src, dest);
 80  
 81              // Set the testApp as the foreground process
 82              mockedInputHandler.SetForegroundProcess(testApp2);
 83  
 84              std::vector<INPUT> inputs{
 85                  { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL } },
 86                  { .type = INPUT_KEYBOARD, .ki = { .wVk = 'A' } }
 87              };
 88  
 89              // Send Ctrl+A keydown
 90              mockedInputHandler.SendVirtualInput(inputs);
 91  
 92              // Ctrl and A key states should be true, Alt and V key states should be false
 93              Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_CONTROL), true);
 94              Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), true);
 95              Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_MENU), false);
 96              Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x56), false);
 97          }
 98  
 99          // Test if the keyboard manager state's activated app is correctly set after an app specific remap takes place
100          TEST_METHOD (AppSpecificShortcut_ShouldSetCorrectActivatedApp_WhenRemapOccurs)
101          {
102              // Remap Ctrl+A to Alt+V
103              Shortcut src;
104              src.SetKey(VK_CONTROL);
105              src.SetKey(0x41);
106              Shortcut dest;
107              dest.SetKey(VK_MENU);
108              dest.SetKey(0x56);
109              testState.AddAppSpecificShortcut(testApp1, src, dest);
110  
111              // Set the testApp as the foreground process
112              mockedInputHandler.SetForegroundProcess(testApp1);
113  
114              std::vector<INPUT> inputs1{
115                  { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL } },
116                  { .type = INPUT_KEYBOARD, .ki = { .wVk = 'A' } }
117              };
118  
119              // Send Ctrl+A keydown
120              mockedInputHandler.SendVirtualInput(inputs1);
121  
122              // Activated app should be testApp1
123              Assert::AreEqual(testApp1, testState.GetActivatedApp());
124  
125              std::vector<INPUT> inputs2{
126                  { .type = INPUT_KEYBOARD, .ki = { .wVk = 'A', .dwFlags = KEYEVENTF_KEYUP } },
127                  { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL, .dwFlags = KEYEVENTF_KEYUP } }
128              };
129  
130              // Release A then Ctrl
131              mockedInputHandler.SendVirtualInput(inputs2);
132  
133              // Activated app should be empty string
134              Assert::AreEqual(std::wstring(KeyboardManagerConstants::NoActivatedApp), testState.GetActivatedApp());
135          }
136          // Test if the key states get cleared if foreground app changes after app-specific shortcut is invoked and then released
137          TEST_METHOD (AppSpecificShortcut_ShouldClearKeyStates_WhenForegroundAppChangesAfterShortcutIsPressedOnRelease)
138          {
139              // Remap Ctrl+A to Alt+Tab
140              Shortcut src;
141              src.SetKey(VK_CONTROL);
142              src.SetKey(0x41);
143              Shortcut dest;
144              dest.SetKey(VK_MENU);
145              dest.SetKey(VK_TAB);
146              testState.AddAppSpecificShortcut(testApp1, src, dest);
147  
148              // Set the testApp as the foreground process
149              mockedInputHandler.SetForegroundProcess(testApp1);
150  
151              std::vector<INPUT> inputs1{
152                  { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL } },
153                  { .type = INPUT_KEYBOARD, .ki = { .wVk = 'A' } }
154              };
155  
156              // Send Ctrl+A keydown
157              mockedInputHandler.SendVirtualInput(inputs1);
158  
159              // Set the testApp as the foreground process
160              mockedInputHandler.SetForegroundProcess(testApp2);
161  
162              std::vector<INPUT> inputs2{
163                  { .type = INPUT_KEYBOARD, .ki = { .wVk = 'A', .dwFlags = KEYEVENTF_KEYUP } },
164                  { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL, .dwFlags = KEYEVENTF_KEYUP } }
165              };
166  
167              // Release A then Ctrl
168              mockedInputHandler.SendVirtualInput(inputs2);
169  
170              // Ctrl, A, Alt and Tab should all be false
171              Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_CONTROL), false);
172              Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), false);
173              Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_MENU), false);
174              Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_TAB), false);
175          }
176  
177          // Test if the app specific shortcut to key remap takes place when the target app is in foreground
178          TEST_METHOD (AppSpecificShortcutToSingleKey_ShouldGetRemapped_WhenAppIsInForeground)
179          {
180              // Remap Ctrl+A to V
181              Shortcut src;
182              src.SetKey(VK_CONTROL);
183              src.SetKey(0x41);
184              testState.AddAppSpecificShortcut(testApp1, src, (DWORD)0x56);
185  
186              // Set the testApp as the foreground process
187              mockedInputHandler.SetForegroundProcess(testApp1);
188  
189              std::vector<INPUT> inputs{
190                  { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL } },
191                  { .type = INPUT_KEYBOARD, .ki = { .wVk = 'A' } }
192              };
193  
194              // Send Ctrl+A keydown
195              mockedInputHandler.SendVirtualInput(inputs);
196  
197              // Ctrl and A key states should be unchanged, V key states should be true
198              Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_CONTROL), false);
199              Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), false);
200              Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x56), true);
201          }
202  
203          // Test if the app specific shortcut to key remap takes place when the target app is not in foreground
204          TEST_METHOD (AppSpecificShortcutToSingleKey_ShouldNotGetRemapped_WhenAppIsNotInForeground)
205          {
206              // Remap Ctrl+A to V
207              Shortcut src;
208              src.SetKey(VK_CONTROL);
209              src.SetKey(0x41);
210              testState.AddAppSpecificShortcut(testApp1, src, (DWORD)0x56);
211  
212              // Set the testApp as the foreground process
213              mockedInputHandler.SetForegroundProcess(testApp2);
214  
215              std::vector<INPUT> inputs{
216                  { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL } },
217                  { .type = INPUT_KEYBOARD, .ki = { .wVk = 'A' } }
218              };
219  
220              // Send Ctrl+A keydown
221              mockedInputHandler.SendVirtualInput(inputs);
222  
223              // Ctrl and A key states should be true, V key state should be false
224              Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_CONTROL), true);
225              Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), true);
226              Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x56), false);
227          }
228  
229          // Test if the keyboard manager state's activated app is correctly set after an app specific shortcut to key remap takes place
230          TEST_METHOD (AppSpecificShortcutToSingleKey_ShouldSetCorrectActivatedApp_WhenRemapOccurs)
231          {
232              // Remap Ctrl+A to V
233              Shortcut src;
234              src.SetKey(VK_CONTROL);
235              src.SetKey(0x41);
236              testState.AddAppSpecificShortcut(testApp1, src, (DWORD)0x56);
237  
238              // Set the testApp as the foreground process
239              mockedInputHandler.SetForegroundProcess(testApp1);
240  
241              std::vector<INPUT> inputs1{
242                  { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL } },
243                  { .type = INPUT_KEYBOARD, .ki = { .wVk = 'A' } }
244              };
245  
246              // Send Ctrl+A keydown
247              mockedInputHandler.SendVirtualInput(inputs1);
248  
249              // Activated app should be testApp1
250              Assert::AreEqual(testApp1, testState.GetActivatedApp());
251  
252              std::vector<INPUT> inputs2{
253                  { .type = INPUT_KEYBOARD, .ki = { .wVk = 'A', .dwFlags = KEYEVENTF_KEYUP } },
254                  { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL, .dwFlags = KEYEVENTF_KEYUP } }
255              };
256  
257              // Release A then Ctrl
258              mockedInputHandler.SendVirtualInput(inputs2);
259  
260              // Activated app should be empty string
261              Assert::AreEqual(std::wstring(KeyboardManagerConstants::NoActivatedApp), testState.GetActivatedApp());
262          }
263          // Test if the key states get cleared if foreground app changes after app-specific shortcut to key shortcut is invoked and then released
264          TEST_METHOD (AppSpecificShortcutToSingleKey_ShouldClearKeyStates_WhenForegroundAppChangesAfterShortcutIsPressedOnRelease)
265          {
266              // Remap Ctrl+A to V
267              Shortcut src;
268              src.SetKey(VK_CONTROL);
269              src.SetKey(0x41);
270              testState.AddAppSpecificShortcut(testApp1, src, (DWORD)0x56);
271  
272              // Set the testApp as the foreground process
273              mockedInputHandler.SetForegroundProcess(testApp1);
274  
275              std::vector<INPUT> inputs1{
276                  { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL } },
277                  { .type = INPUT_KEYBOARD, .ki = { .wVk = 'A' } }
278              };
279  
280              // Send Ctrl+A keydown
281              mockedInputHandler.SendVirtualInput(inputs1);
282  
283              // Set the testApp as the foreground process
284              mockedInputHandler.SetForegroundProcess(testApp2);
285  
286              std::vector<INPUT> inputs2{
287                  { .type = INPUT_KEYBOARD, .ki = { .wVk = 'A', .dwFlags = KEYEVENTF_KEYUP } },
288                  { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL, .dwFlags = KEYEVENTF_KEYUP } }
289              };
290  
291              // Release A then Ctrl
292              mockedInputHandler.SendVirtualInput(inputs2);
293  
294              // Ctrl, A, V should all be false
295              Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_CONTROL), false);
296              Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), false);
297              Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x56), false);
298          }
299  
300          // Disable app specific shortcut
301          TEST_METHOD (AppSpecificShortcutToDisable_ShouldDisable_WhenAppIsOnForeground)
302          {
303              Shortcut src;
304              src.SetKey(VK_CONTROL);
305              WORD actionKey = 0x41;
306              src.SetKey(actionKey);
307              WORD disableKey = CommonSharedConstants::VK_DISABLED;
308              testState.AddAppSpecificShortcut(testApp1, src, disableKey);
309  
310              // Set the testApp as the foreground process
311              mockedInputHandler.SetForegroundProcess(testApp1);
312  
313              std::vector<INPUT> inputs{
314                  { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL } },
315                  { .type = INPUT_KEYBOARD, .ki = { .wVk = actionKey } }
316              };
317  
318              // Send Ctrl+A keydown
319              mockedInputHandler.SendVirtualInput(inputs);
320  
321              // Check if Ctrl+A is released and disable key was not send
322              Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_CONTROL), false);
323              Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(actionKey), false);
324          }
325      };
326  }