Files
PowerToys/src/modules/keyboardmanager/KeyboardManagerEngineTest/SingleKeyRemappingTests.cpp

333 lines
14 KiB
C++
Raw Normal View History

#include "pch.h"
// Suppressing 26466 - Don't use static_cast downcasts - in CppUnitTest.h
#pragma warning(push)
#pragma warning(disable : 26466)
#include "CppUnitTest.h"
#pragma warning(pop)
#include "MockedInput.h"
#include <keyboardmanager/KeyboardManagerEngineLibrary/State.h>
[KBM] Migrate Engine and Editor into separate processes (#10774) * Move KBM engine into separate process (#10672) * [KBM] Migrate KBM UI out of the runner (#10709) * Clean up keyboard hook handles (#10817) * [C++ common] Unhandled exception handler (#10821) * [KBM] Use icon in the KeyboardManagerEditor (#10845) * [KBM] Move resources from the Common project to the Editor. (#10844) * KBM Editor tests (#10858) * Rename engine executable (#10868) * clean up (#10870) * [KBM] Changed Editor and libraries output folders (#10871) * [KBM] New logs structure (#10872) * Add unhandled exception handling to the editor (#10874) * [KBM] Trace for edit keyboard window * Logging for XamlBridge message loop * [KBM] Added Editor and Engine to the installer (#10876) * Fix spelling * Interprocess communication logs, remove unnecessary windows message logs * [KBM] Separated telemetry for the engine and editor. (#10889) * [KBM] Editor test project (#10891) * Versions for the engine and the editor (#10897) * Add the editor's and the engine's executables to signing process (#10900) * [KBM editor] Run only one instance, exit when parent process exits (#10890) * [KBM] Force kill editor process to avoid XAML crash (#10907) * [KBM] Force kill editor process to avoid XAML crash * Fix event releasing Co-authored-by: mykhailopylyp <17161067+mykhailopylyp@users.noreply.github.com> * Make the editor dpi aware (#10908) * [KBM] KeyboardManagerCommon refactoring (#10909) * Do not start the process if it is already started (#10910) * logs * Update src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditKeyboardWindow.cpp * Update src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditKeyboardWindow.cpp * [KBM] Rename InitUnhandledExceptionHandler to make it explicit that is for x64 only. We will fix it properly when adding support for ARM64 and add a header with the proper conditional building. * [KBM] rename file/class/variables using camel case * [KBM] Rename "event_locker" -> "EventLocker" * [KBM] rename process_waiter Add a TODO comment * [KBM] rename methods Add TODO comment * [KBM] use uppercase for function names * [KBM] use uppercase for methos, lowercase for properties * [KBM] rename method, make methods private, formatting * [KBM] rename private variables * [KBM] use uppercase for function names * [KBM] Added support to run the editor stand-alone when built in debug mode * Update src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.cpp * Check success of event creation, comment (#10947) * [KBM] code formatting (#10951) * [KBM] code formatting * Update src/modules/keyboardmanager/KeyboardManagerEditorLibrary/BufferValidationHelpers.cpp * [KBM] tracing * [KBM] Remappings not showing fix. (#10954) * removed mutex * retry loop for reading * retry on reading config once * log error Co-authored-by: Enrico Giordani <enricogior@users.noreply.github.com> Co-authored-by: Enrico Giordani <enricogior@users.noreply.github.com> Co-authored-by: Seraphima Zykova <zykovas91@gmail.com> Co-authored-by: Enrico Giordani <enricogior@users.noreply.github.com> Co-authored-by: Enrico Giordani <enrico.giordani@gmail.com>
2021-04-26 22:01:38 +03:00
#include <keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.h>
#include "TestHelpers.h"
#include <common/interop/shared_constants.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace RemappingLogicTests
{
// Tests for single key remapping logic
TEST_CLASS (SingleKeyRemappingTests)
{
private:
[KBM] Migrate Engine and Editor into separate processes (#10774) * Move KBM engine into separate process (#10672) * [KBM] Migrate KBM UI out of the runner (#10709) * Clean up keyboard hook handles (#10817) * [C++ common] Unhandled exception handler (#10821) * [KBM] Use icon in the KeyboardManagerEditor (#10845) * [KBM] Move resources from the Common project to the Editor. (#10844) * KBM Editor tests (#10858) * Rename engine executable (#10868) * clean up (#10870) * [KBM] Changed Editor and libraries output folders (#10871) * [KBM] New logs structure (#10872) * Add unhandled exception handling to the editor (#10874) * [KBM] Trace for edit keyboard window * Logging for XamlBridge message loop * [KBM] Added Editor and Engine to the installer (#10876) * Fix spelling * Interprocess communication logs, remove unnecessary windows message logs * [KBM] Separated telemetry for the engine and editor. (#10889) * [KBM] Editor test project (#10891) * Versions for the engine and the editor (#10897) * Add the editor's and the engine's executables to signing process (#10900) * [KBM editor] Run only one instance, exit when parent process exits (#10890) * [KBM] Force kill editor process to avoid XAML crash (#10907) * [KBM] Force kill editor process to avoid XAML crash * Fix event releasing Co-authored-by: mykhailopylyp <17161067+mykhailopylyp@users.noreply.github.com> * Make the editor dpi aware (#10908) * [KBM] KeyboardManagerCommon refactoring (#10909) * Do not start the process if it is already started (#10910) * logs * Update src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditKeyboardWindow.cpp * Update src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditKeyboardWindow.cpp * [KBM] Rename InitUnhandledExceptionHandler to make it explicit that is for x64 only. We will fix it properly when adding support for ARM64 and add a header with the proper conditional building. * [KBM] rename file/class/variables using camel case * [KBM] Rename "event_locker" -> "EventLocker" * [KBM] rename process_waiter Add a TODO comment * [KBM] rename methods Add TODO comment * [KBM] use uppercase for function names * [KBM] use uppercase for methos, lowercase for properties * [KBM] rename method, make methods private, formatting * [KBM] rename private variables * [KBM] use uppercase for function names * [KBM] Added support to run the editor stand-alone when built in debug mode * Update src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.cpp * Check success of event creation, comment (#10947) * [KBM] code formatting (#10951) * [KBM] code formatting * Update src/modules/keyboardmanager/KeyboardManagerEditorLibrary/BufferValidationHelpers.cpp * [KBM] tracing * [KBM] Remappings not showing fix. (#10954) * removed mutex * retry loop for reading * retry on reading config once * log error Co-authored-by: Enrico Giordani <enricogior@users.noreply.github.com> Co-authored-by: Enrico Giordani <enricogior@users.noreply.github.com> Co-authored-by: Seraphima Zykova <zykovas91@gmail.com> Co-authored-by: Enrico Giordani <enricogior@users.noreply.github.com> Co-authored-by: Enrico Giordani <enrico.giordani@gmail.com>
2021-04-26 22:01:38 +03:00
KeyboardManagerInput::MockedInput mockedInputHandler;
State testState;
public:
TEST_METHOD_INITIALIZE(InitializeTestEnv)
{
// Reset test environment
TestHelpers::ResetTestEnv(mockedInputHandler, testState);
// Set HandleSingleKeyRemapEvent as the hook procedure
std::function<intptr_t(LowlevelKeyboardEvent*)> currentHookProc = std::bind(&KeyboardEventHandlers::HandleSingleKeyRemapEvent, std::ref(mockedInputHandler), std::placeholders::_1, std::ref(testState));
mockedInputHandler.SetHookProc(currentHookProc);
}
// Test if correct keyboard states are set for a single key remap
TEST_METHOD (RemappedKey_ShouldSetTargetKeyState_OnKeyEvent)
{
// Remap A to B
2021-06-01 19:15:16 +03:00
testState.AddSingleKeyRemap(0x41, (DWORD)0x42);
std::vector<INPUT> inputs1{
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A' } },
};
// Send A keydown
mockedInputHandler.SendVirtualInput(inputs1);
// A key state should be unchanged, and B key state should be true
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), false);
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x42), true);
std::vector<INPUT> inputs2{
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A', .dwFlags = KEYEVENTF_KEYUP } },
};
// Send A keyup
mockedInputHandler.SendVirtualInput(inputs2);
// A key state should be unchanged, and B key state should be false
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), false);
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x42), false);
}
// Test if key is suppressed if a key is disabled by single key remap
TEST_METHOD (RemappedKeyDisabled_ShouldNotChangeKeyState_OnKeyEvent)
{
// Remap A to VK_DISABLE (disabled)
testState.AddSingleKeyRemap(0x41, CommonSharedConstants::VK_DISABLED);
std::vector<INPUT> inputs1{
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A' } },
};
// Send A keydown
mockedInputHandler.SendVirtualInput(inputs1);
// A key state should be unchanged
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), false);
std::vector<INPUT> inputs2{
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A', .dwFlags = KEYEVENTF_KEYUP } },
};
// Send A keyup
mockedInputHandler.SendVirtualInput(inputs2);
// A key state should be unchanged
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), false);
}
// Test if correct keyboard states are set for a remap to Win (Both) key
TEST_METHOD (RemappedKeyToWinBoth_ShouldSetWinLeftKeyState_OnKeyEvent)
{
// Remap A to Common Win key
testState.AddSingleKeyRemap(0x41, CommonSharedConstants::VK_WIN_BOTH);
std::vector<INPUT> inputs1{
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A' } },
};
// Send A keydown
mockedInputHandler.SendVirtualInput(inputs1);
// A key state should be unchanged, and common Win key state should be true
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), false);
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_LWIN), true);
std::vector<INPUT> inputs2{
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A', .dwFlags = KEYEVENTF_KEYUP } },
};
// Send A keyup
mockedInputHandler.SendVirtualInput(inputs2);
// A key state should be unchanged, and common Win key state should be false
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), false);
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_LWIN), false);
}
// Test if SendVirtualInput is sent exactly once with the suppress flag when Caps Lock is remapped to Ctrl
TEST_METHOD (HandleSingleKeyRemapEvent_ShouldSendVirtualInputWithSuppressFlagExactlyOnce_WhenCapsLockIsMappedToCtrlAltShift)
{
// Set sendvirtualinput call count condition to return true if the key event was sent with the suppress flag
mockedInputHandler.SetSendVirtualInputTestHandler([](LowlevelKeyboardEvent* data) {
if (data->lParam->dwExtraInfo == KeyboardManagerConstants::KEYBOARDMANAGER_SUPPRESS_FLAG)
return true;
else
return false;
});
// Remap Caps Lock to Ctrl
2021-06-01 19:15:16 +03:00
testState.AddSingleKeyRemap(VK_CAPITAL, (DWORD)VK_CONTROL);
std::vector<INPUT> inputs{
{ .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CAPITAL } },
};
// Send Caps Lock keydown
mockedInputHandler.SendVirtualInput(inputs);
// SendVirtualInput should be called exactly once with the above condition
Assert::AreEqual(1, mockedInputHandler.GetSendVirtualInputCallCount());
}
// Test if SendVirtualInput is sent exactly once with the suppress flag when Ctrl is remapped to Caps Lock
TEST_METHOD (HandleSingleKeyRemapEvent_ShouldSendVirtualInputWithSuppressFlagExactlyOnce_WhenCtrlAltShiftIsMappedToCapsLock)
{
// Set sendvirtualinput call count condition to return true if the key event was sent with the suppress flag
mockedInputHandler.SetSendVirtualInputTestHandler([](LowlevelKeyboardEvent* data) {
if (data->lParam->dwExtraInfo == KeyboardManagerConstants::KEYBOARDMANAGER_SUPPRESS_FLAG)
return true;
else
return false;
});
// Remap Ctrl to Caps Lock
2021-06-01 19:15:16 +03:00
testState.AddSingleKeyRemap(VK_CONTROL, (DWORD)VK_CAPITAL);
std::vector<INPUT> inputs{
{ .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL } },
};
// Send Ctrl keydown
mockedInputHandler.SendVirtualInput(inputs);
// SendVirtualInput should be called exactly once with the above condition
Assert::AreEqual(1, mockedInputHandler.GetSendVirtualInputCallCount());
}
// Test if SendVirtualInput is sent exactly twice with the suppress flag when Caps Lock is remapped to shortcut with Ctrl and Shift
TEST_METHOD (HandleSingleKeyRemapEvent_ShouldSendVirtualInputWithSuppressFlagExactlyTwice_WhenCapsLockIsMappedToShortcutWithCtrlAltShift)
{
// Set sendvirtualinput call count condition to return true if the key event was sent with the suppress flag
mockedInputHandler.SetSendVirtualInputTestHandler([](LowlevelKeyboardEvent* data) {
if (data->lParam->dwExtraInfo == KeyboardManagerConstants::KEYBOARDMANAGER_SUPPRESS_FLAG)
return true;
else
return false;
});
// Remap Caps Lock to Ctrl+Shift+V
Shortcut dest;
dest.SetKey(VK_CONTROL);
dest.SetKey(VK_SHIFT);
dest.SetKey(0x56);
testState.AddSingleKeyRemap(VK_CAPITAL, dest);
std::vector<INPUT> inputs{
{ .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CAPITAL } },
};
// Send Caps Lock keydown
mockedInputHandler.SendVirtualInput(inputs);
// SendVirtualInput should be called exactly twice with the above condition
Assert::AreEqual(2, mockedInputHandler.GetSendVirtualInputCallCount());
}
// Test if SendVirtualInput is sent exactly once with the suppress flag when Ctrl is remapped to a shortcut with Caps Lock
TEST_METHOD (HandleSingleKeyRemapEvent_ShouldSendVirtualInputWithSuppressFlagExactlyOnce_WhenCtrlAltShiftIsMappedToShortcutWithCapsLock)
{
// Set sendvirtualinput call count condition to return true if the key event was sent with the suppress flag
mockedInputHandler.SetSendVirtualInputTestHandler([](LowlevelKeyboardEvent* data) {
if (data->lParam->dwExtraInfo == KeyboardManagerConstants::KEYBOARDMANAGER_SUPPRESS_FLAG)
return true;
else
return false;
});
// Remap Ctrl to Ctrl+Caps Lock
Shortcut dest;
dest.SetKey(VK_CONTROL);
dest.SetKey(VK_CAPITAL);
testState.AddSingleKeyRemap(VK_CONTROL, dest);
std::vector<INPUT> inputs{
{ .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL } },
};
// Send Ctrl keydown
mockedInputHandler.SendVirtualInput(inputs);
// SendVirtualInput should be called exactly once with the above condition
Assert::AreEqual(1, mockedInputHandler.GetSendVirtualInputCallCount());
}
// Test if correct keyboard states are set for a single key to two key shortcut remap
TEST_METHOD (RemappedKeyToTwoKeyShortcut_ShouldSetTargetKeyState_OnKeyEvent)
{
// Remap A to Ctrl+V
Shortcut dest;
dest.SetKey(VK_CONTROL);
dest.SetKey(0x56);
testState.AddSingleKeyRemap(0x41, dest);
std::vector<INPUT> inputs1{
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A' } },
};
// Send A keydown
mockedInputHandler.SendVirtualInput(inputs1);
// A key state should be unchanged, and Ctrl, V key state should be true
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), false);
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_CONTROL), true);
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x56), true);
std::vector<INPUT> inputs2{
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A', .dwFlags = KEYEVENTF_KEYUP } },
};
// Send A keyup
mockedInputHandler.SendVirtualInput(inputs2);
// A key state should be unchanged, and Ctrl, V key state should be false
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), false);
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_CONTROL), false);
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x56), false);
}
// Test if correct keyboard states are set for a single key to three key shortcut remap
TEST_METHOD (RemappedKeyToThreeKeyShortcut_ShouldSetTargetKeyState_OnKeyEvent)
{
// Remap A to Ctrl+Shift+V
Shortcut dest;
dest.SetKey(VK_CONTROL);
dest.SetKey(VK_SHIFT);
dest.SetKey(0x56);
testState.AddSingleKeyRemap(0x41, dest);
std::vector<INPUT> inputs1{
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A' } },
};
// Send A keydown
mockedInputHandler.SendVirtualInput(inputs1);
// A key state should be unchanged, and Ctrl, Shift, V key state should be true
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), false);
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_CONTROL), true);
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_SHIFT), true);
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x56), true);
std::vector<INPUT> inputs2{
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A', .dwFlags = KEYEVENTF_KEYUP } },
};
// Send A keyup
mockedInputHandler.SendVirtualInput(inputs2);
// A key state should be unchanged, and Ctrl, Shift, V key state should be false
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), false);
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_CONTROL), false);
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_SHIFT), false);
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x56), false);
}
// Test if correct keyboard states are set for a remap from a single key to a shortcut containing the source key
TEST_METHOD (RemappedKeyToShortcutContainingSourceKey_ShouldSetTargetKeyState_OnKeyEvent)
{
// Remap LCtrl to LCtrl+V
Shortcut dest;
dest.SetKey(VK_LCONTROL);
dest.SetKey(0x56);
testState.AddSingleKeyRemap(VK_LCONTROL, dest);
std::vector<INPUT> inputs1{
{ .type = INPUT_KEYBOARD, .ki = { .wVk = VK_LCONTROL } },
};
// Send LCtrl keydown
mockedInputHandler.SendVirtualInput(inputs1);
// LCtrl, V key state should be true
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_LCONTROL), true);
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x56), true);
std::vector<INPUT> inputs2{
{ .type = INPUT_KEYBOARD, .ki = { .wVk = VK_LCONTROL, .dwFlags = KEYEVENTF_KEYUP } },
};
// Send LCtrl keyup
mockedInputHandler.SendVirtualInput(inputs2);
// LCtrl, V key state should be false
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_LCONTROL), false);
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x56), false);
}
};
}