Refactored Keyboard Manager UI code and added unit tests for Keyboard Manager UI (#5718)

* Added tests for loading and saving remappings in the UI

* Added tests for ApplyRemappings methods

* Moved single key remap validation logic to separate method so that it can be tested

* Added tests for single key remap validation in UI

* Refactored shortcut validation code to be testable

* Added some shortcut validation tests

* Refactored code to be cleaner

* Added tests for action key and modifier key selection and formatted file

* Added tests for selecting None

* Added tests for selecting Null

* Added tests for WinL error

* Added CtrlAltDel tests

* Added tests for MapToSameKey

* Added tests for mapping repeated shortcut

* Fixed const correctness

* Clean up type names with type alias

* Clean up ValidateAndUpdateKeyBufferElement tests and tweak ValidateShortcutBufferElement signature

* Fixed bug when None selected

* Refactored one test as per test case framework

* Cleaned up more tests

* Cleaned up buffer validation tests

* Added tests for KBM Common Helpers and Shortcut
This commit is contained in:
Arjun Balgovind
2020-08-13 16:32:15 -07:00
committed by GitHub
parent 0b5749d491
commit 9e8b0d2807
30 changed files with 3077 additions and 602 deletions

View File

@@ -3,6 +3,7 @@
#include <sstream> #include <sstream>
#include "../common/shared_constants.h" #include "../common/shared_constants.h"
#include <shlwapi.h> #include <shlwapi.h>
#include "../common/keyboard_layout.h"
using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation;
@@ -325,4 +326,59 @@ namespace KeyboardManagerHelper
return first.Size() > second.Size(); return first.Size() > second.Size();
}); });
} }
// Function to check if a modifier has been repeated in the previous drop downs
bool CheckRepeatedModifier(std::vector<DWORD>& currentKeys, int selectedKeyIndex, const std::vector<DWORD>& keyCodeList)
{
// check if modifier has already been added before in a previous drop down
int currentDropDownIndex = -1;
// Find the key index of the current drop down selection so that we skip that index while searching for repeated modifiers
for (int i = 0; i < currentKeys.size(); i++)
{
if (currentKeys[i] == keyCodeList[selectedKeyIndex])
{
currentDropDownIndex = i;
break;
}
}
bool matchPreviousModifier = false;
for (int i = 0; i < currentKeys.size(); i++)
{
// Skip the current drop down
if (i != currentDropDownIndex)
{
// If the key type for the newly added key matches any of the existing keys in the shortcut
if (KeyboardManagerHelper::GetKeyType(keyCodeList[selectedKeyIndex]) == KeyboardManagerHelper::GetKeyType(currentKeys[i]))
{
matchPreviousModifier = true;
break;
}
}
}
return matchPreviousModifier;
}
// Function to get the selected key codes from the list of selected indices
std::vector<DWORD> GetKeyCodesFromSelectedIndices(const std::vector<int32_t>& selectedIndices, const std::vector<DWORD>& keyCodeList)
{
std::vector<DWORD> keys;
for (int i = 0; i < selectedIndices.size(); i++)
{
int selectedKeyIndex = selectedIndices[i];
if (selectedKeyIndex != -1 && keyCodeList.size() > selectedKeyIndex)
{
// If None is not the selected key
if (keyCodeList[selectedKeyIndex] != 0)
{
keys.push_back(keyCodeList[selectedKeyIndex]);
}
}
}
return keys;
}
} }

View File

@@ -15,6 +15,8 @@ namespace winrt
} }
} }
class LayoutMap;
namespace KeyboardManagerHelper namespace KeyboardManagerHelper
{ {
// Type to distinguish between keys // Type to distinguish between keys
@@ -99,4 +101,10 @@ namespace KeyboardManagerHelper
// Function to sort a vector of shortcuts based on it's size // Function to sort a vector of shortcuts based on it's size
void SortShortcutVectorBasedOnSize(std::vector<Shortcut>& shortcutVector); void SortShortcutVectorBasedOnSize(std::vector<Shortcut>& shortcutVector);
// Function to check if a modifier has been repeated in the previous drop downs
bool CheckRepeatedModifier(std::vector<DWORD>& currentKeys, int selectedKeyIndex, const std::vector<DWORD>& keyCodeList);
// Function to get the selected key codes from the list of selected indices
std::vector<DWORD> GetKeyCodesFromSelectedIndices(const std::vector<int32_t>& selectedIndices, const std::vector<DWORD>& keyCodeList);
} }

View File

@@ -19,4 +19,9 @@ public:
targetShortcut(Shortcut()), isShortcutInvoked(false), winKeyInvoked(ModifierKey::Disabled) targetShortcut(Shortcut()), isShortcutInvoked(false), winKeyInvoked(ModifierKey::Disabled)
{ {
} }
inline bool operator==(const RemapShortcut& sc) const
{
return targetShortcut == sc.targetShortcut && isShortcutInvoked == sc.isShortcutInvoked && winKeyInvoked == sc.winKeyInvoked;
}
}; };

View File

@@ -17,6 +17,12 @@ Shortcut::Shortcut(const std::wstring& shortcutVK) :
} }
} }
// Constructor to initialize shortcut from a list of keys
Shortcut::Shortcut(const std::vector<DWORD>& keys)
{
SetKeyCodes(keys);
}
// Function to return the number of keys in the shortcut // Function to return the number of keys in the shortcut
int Shortcut::Size() const int Shortcut::Size() const
{ {

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include "ModifierKey.h" #include "ModifierKey.h"
#include <variant>
class InputInterface; class InputInterface;
class LayoutMap; class LayoutMap;
namespace KeyboardManagerHelper namespace KeyboardManagerHelper
@@ -26,6 +27,9 @@ public:
// Constructor to initialize Shortcut from it's virtual key code string representation. // Constructor to initialize Shortcut from it's virtual key code string representation.
Shortcut(const std::wstring& shortcutVK); Shortcut(const std::wstring& shortcutVK);
// Constructor to initialize shortcut from a list of keys
Shortcut(const std::vector<DWORD>& keys);
// == operator // == operator
inline bool operator==(const Shortcut& sc) const inline bool operator==(const Shortcut& sc) const
{ {
@@ -166,3 +170,7 @@ public:
// Function to check if the shortcut is illegal (i.e. Win+L or Ctrl+Alt+Del) // Function to check if the shortcut is illegal (i.e. Win+L or Ctrl+Alt+Del)
KeyboardManagerHelper::ErrorType IsShortcutIllegal() const; KeyboardManagerHelper::ErrorType IsShortcutIllegal() const;
}; };
using RemapBufferItem = std::vector<std::variant<DWORD, Shortcut>>;
using RemapBufferRow = std::pair<RemapBufferItem, std::wstring>;
using RemapBuffer = std::vector<RemapBufferRow>;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,173 @@
#include "pch.h"
#include "CppUnitTest.h"
#include <keyboardmanager/common/Helpers.h>
#include "TestHelpers.h"
#include <common/keyboard_layout.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace KeyboardManagerCommonTests
{
// Tests for methods in the KeyboardManagerHelper namespace
TEST_CLASS (KeyboardManagerHelperTests)
{
public:
TEST_METHOD_INITIALIZE(InitializeTestEnv)
{
}
// Test if the DoKeysOverlap method returns SameKeyPreviouslyMapped on passing the same key for both arguments
TEST_METHOD (DoKeysOverlap_ShouldReturnSameKeyPreviouslyMapped_OnPassingSameKeyForBothArguments)
{
// Arrange
DWORD key1 = 0x41;
DWORD key2 = key1;
// Act
auto result = KeyboardManagerHelper::DoKeysOverlap(key1, key2);
// Assert
Assert::IsTrue(result == KeyboardManagerHelper::ErrorType::SameKeyPreviouslyMapped);
}
// Test if the DoKeysOverlap method returns ConflictingModifierKey on passing left modifier and common modifier
TEST_METHOD (DoKeysOverlap_ShouldReturnConflictingModifierKey_OnPassingLeftModifierAndCommonModifierOfSameType)
{
// Arrange
DWORD key1 = VK_LCONTROL;
DWORD key2 = VK_CONTROL;
// Act
auto result = KeyboardManagerHelper::DoKeysOverlap(key1, key2);
// Assert
Assert::IsTrue(result == KeyboardManagerHelper::ErrorType::ConflictingModifierKey);
}
// Test if the DoKeysOverlap method returns ConflictingModifierKey on passing right modifier and common modifier
TEST_METHOD (DoKeysOverlap_ShouldReturnConflictingModifierKey_OnPassingRightModifierAndCommonModifierOfSameType)
{
// Arrange
DWORD key1 = VK_RCONTROL;
DWORD key2 = VK_CONTROL;
// Act
auto result = KeyboardManagerHelper::DoKeysOverlap(key1, key2);
// Assert
Assert::IsTrue(result == KeyboardManagerHelper::ErrorType::ConflictingModifierKey);
}
// Test if the DoKeysOverlap method returns NoError on passing left modifier and right modifier
TEST_METHOD (DoKeysOverlap_ShouldReturnNoError_OnPassingLeftModifierAndRightModifierOfSameType)
{
// Arrange
DWORD key1 = VK_LCONTROL;
DWORD key2 = VK_RCONTROL;
// Act
auto result = KeyboardManagerHelper::DoKeysOverlap(key1, key2);
// Assert
Assert::IsTrue(result == KeyboardManagerHelper::ErrorType::NoError);
}
// Test if the DoKeysOverlap method returns NoError on passing keys of different types
TEST_METHOD (DoKeysOverlap_ShouldReturnNoError_OnPassingKeysOfDifferentTypes)
{
// Arrange
DWORD key1 = VK_CONTROL;
DWORD key2 = VK_SHIFT;
// Act
auto result = KeyboardManagerHelper::DoKeysOverlap(key1, key2);
// Assert
Assert::IsTrue(result == KeyboardManagerHelper::ErrorType::NoError);
}
// Test if the DoKeysOverlap method returns NoError on passing different action keys
TEST_METHOD (DoKeysOverlap_ShouldReturnNoError_OnPassingDifferentActionKeys)
{
// Arrange
DWORD key1 = 0x41;
DWORD key2 = 0x42;
// Act
auto result = KeyboardManagerHelper::DoKeysOverlap(key1, key2);
// Assert
Assert::IsTrue(result == KeyboardManagerHelper::ErrorType::NoError);
}
// Test if the CheckRepeatedModifier method returns true on passing vector with same modifier repeated
TEST_METHOD (CheckRepeatedModifier_ShouldReturnTrue_OnPassingSameModifierRepeated)
{
// Arrange
std::vector<DWORD> keyList = LayoutMap().GetKeyCodeList(true);
std::vector<DWORD> keys = { VK_CONTROL, VK_CONTROL, 0x41 };
// Act
bool result = KeyboardManagerHelper::CheckRepeatedModifier(keys, TestHelpers::GetDropDownIndexFromDropDownList(VK_CONTROL, keyList), keyList);
// Assert
Assert::IsTrue(result);
}
// Test if the CheckRepeatedModifier method returns true on passing vector with conflicting modifier repeated
TEST_METHOD (CheckRepeatedModifier_ShouldReturnTrue_OnPassingConflictingModifierRepeated)
{
// Arrange
std::vector<DWORD> keyList = LayoutMap().GetKeyCodeList(true);
std::vector<DWORD> keys = { VK_CONTROL, VK_LCONTROL, 0x41 };
// Act
bool result = KeyboardManagerHelper::CheckRepeatedModifier(keys, TestHelpers::GetDropDownIndexFromDropDownList(VK_LCONTROL, keyList), keyList);
// Assert
Assert::IsTrue(result);
}
// Test if the CheckRepeatedModifier method returns false on passing vector with different modifiers
TEST_METHOD (CheckRepeatedModifier_ShouldReturnFalse_OnPassingDifferentModifiers)
{
// Arrange
std::vector<DWORD> keyList = LayoutMap().GetKeyCodeList(true);
std::vector<DWORD> keys = { VK_CONTROL, VK_SHIFT, 0x41 };
// Act
bool result = KeyboardManagerHelper::CheckRepeatedModifier(keys, TestHelpers::GetDropDownIndexFromDropDownList(VK_SHIFT, keyList), keyList);
// Assert
Assert::IsFalse(result);
}
// Test if the GetKeyCodesFromSelectedIndices method ignores -1 and 0 from argument in return value
TEST_METHOD (GetKeyCodesFromSelectedIndices_ShouldIgnoreMinus1And0_OnPassingArgumentWithMinus1Or0)
{
// Arrange
std::vector<DWORD> keyList = LayoutMap().GetKeyCodeList(true);
std::vector<int32_t> indices = { -1, 0, -1, 0 };
// Act
auto result = KeyboardManagerHelper::GetKeyCodesFromSelectedIndices(indices, keyList);
// Assert
Assert::IsTrue(result.empty());
}
// Test if the GetKeyCodesFromSelectedIndices method returns vector with key codes from vector with indices as per key code list
TEST_METHOD (GetKeyCodesFromSelectedIndices_ShouldReturnVectorWithKeyCodes_OnPassingVectorWithIndicesAsPerKeyCodeList)
{
// Arrange
std::vector<DWORD> keyList = LayoutMap().GetKeyCodeList(true);
std::vector<int32_t> indices = { TestHelpers::GetDropDownIndexFromDropDownList(0x41, keyList), TestHelpers::GetDropDownIndexFromDropDownList(0x42, keyList) };
// Act
auto result = KeyboardManagerHelper::GetKeyCodesFromSelectedIndices(indices, keyList);
// Assert
Assert::IsTrue(result == std::vector<DWORD>{ 0x41, 0x42 });
}
};
}

View File

@@ -107,6 +107,8 @@
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="AppSpecificShortcutRemappingTests.cpp" /> <ClCompile Include="AppSpecificShortcutRemappingTests.cpp" />
<ClCompile Include="BufferValidationTests.cpp" />
<ClCompile Include="LoadingAndSavingRemappingTests.cpp" />
<ClCompile Include="MockedInputSanityTests.cpp" /> <ClCompile Include="MockedInputSanityTests.cpp" />
<ClCompile Include="SetKeyEventTests.cpp" /> <ClCompile Include="SetKeyEventTests.cpp" />
<ClCompile Include="OSLevelShortcutRemappingTests.cpp" /> <ClCompile Include="OSLevelShortcutRemappingTests.cpp" />
@@ -114,7 +116,9 @@
<ClCompile Include="pch.cpp"> <ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(CIBuild)'!='true'">Create</PrecompiledHeader> <PrecompiledHeader Condition="'$(CIBuild)'!='true'">Create</PrecompiledHeader>
</ClCompile> </ClCompile>
<ClCompile Include="ShortcutTests.cpp" />
<ClCompile Include="SingleKeyRemappingTests.cpp" /> <ClCompile Include="SingleKeyRemappingTests.cpp" />
<ClCompile Include="KeyboardManagerHelperTests.cpp" />
<ClCompile Include="TestHelpers.cpp" /> <ClCompile Include="TestHelpers.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -39,6 +39,18 @@
<ClCompile Include="AppSpecificShortcutRemappingTests.cpp"> <ClCompile Include="AppSpecificShortcutRemappingTests.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="LoadingAndSavingRemappingTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="BufferValidationTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="KeyboardManagerHelperTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ShortcutTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="pch.h"> <ClInclude Include="pch.h">

View File

@@ -0,0 +1,521 @@
#include "pch.h"
#include "CppUnitTest.h"
#include <keyboardmanager/common/KeyboardManagerState.h>
#include <keyboardmanager/ui/LoadingAndSavingRemappingHelper.h>
#include "TestHelpers.h"
#include "../common/shared_constants.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace RemappingUITests
{
// Tests for methods in the LoadingAndSavingRemappingHelper namespace
TEST_CLASS (LoadingAndSavingRemappingTests)
{
std::wstring testApp1 = L"testtrocess1.exe";
std::wstring testApp2 = L"testprocess2.exe";
public:
TEST_METHOD_INITIALIZE(InitializeTestEnv)
{
}
// Test if the CheckIfRemappingsAreValid method is successful when no remaps are passed
TEST_METHOD (CheckIfRemappingsAreValid_ShouldReturnNoError_OnPassingNoRemaps)
{
RemapBuffer remapBuffer;
// Assert that remapping set is valid
bool isSuccess = (LoadingAndSavingRemappingHelper::CheckIfRemappingsAreValid(remapBuffer) == KeyboardManagerHelper::ErrorType::NoError);
Assert::AreEqual(true, isSuccess);
}
// Test if the CheckIfRemappingsAreValid method is successful when valid key to key remaps are passed
TEST_METHOD (CheckIfRemappingsAreValid_ShouldReturnNoError_OnPassingValidKeyToKeyRemaps)
{
RemapBuffer remapBuffer;
// Remap A to B and B to C
remapBuffer.push_back(std::make_pair(RemapBufferItem({ 0x41, 0x42 }), std::wstring()));
remapBuffer.push_back(std::make_pair(RemapBufferItem({ 0x42, 0x43 }), std::wstring()));
// Assert that remapping set is valid
bool isSuccess = (LoadingAndSavingRemappingHelper::CheckIfRemappingsAreValid(remapBuffer) == KeyboardManagerHelper::ErrorType::NoError);
Assert::AreEqual(true, isSuccess);
}
// Test if the CheckIfRemappingsAreValid method is successful when valid key to shortcut remaps are passed
TEST_METHOD (CheckIfRemappingsAreValid_ShouldReturnNoError_OnPassingValidKeyToShortcutRemaps)
{
RemapBuffer remapBuffer;
// Remap A to Ctrl+V and B to Alt+Tab
Shortcut s1;
s1.SetKey(VK_CONTROL);
s1.SetKey(0x56);
Shortcut s2;
s2.SetKey(VK_MENU);
s2.SetKey(VK_TAB);
remapBuffer.push_back(std::make_pair(RemapBufferItem({ 0x41, s1 }), std::wstring()));
remapBuffer.push_back(std::make_pair(RemapBufferItem({ 0x42, s2 }), std::wstring()));
// Assert that remapping set is valid
bool isSuccess = (LoadingAndSavingRemappingHelper::CheckIfRemappingsAreValid(remapBuffer) == KeyboardManagerHelper::ErrorType::NoError);
Assert::AreEqual(true, isSuccess);
}
// Test if the CheckIfRemappingsAreValid method is successful when valid shortcut to key remaps are passed
TEST_METHOD (CheckIfRemappingsAreValid_ShouldReturnNoError_OnPassingValidShortcutToKeyRemaps)
{
RemapBuffer remapBuffer;
// Remap Ctrl+V to A and Alt+Tab to B
Shortcut s1;
s1.SetKey(VK_CONTROL);
s1.SetKey(0x56);
Shortcut s2;
s2.SetKey(VK_MENU);
s2.SetKey(VK_TAB);
remapBuffer.push_back(std::make_pair(RemapBufferItem({ s1, 0x41 }), std::wstring()));
remapBuffer.push_back(std::make_pair(RemapBufferItem({ s2, 0x42 }), std::wstring()));
// Assert that remapping set is valid
bool isSuccess = (LoadingAndSavingRemappingHelper::CheckIfRemappingsAreValid(remapBuffer) == KeyboardManagerHelper::ErrorType::NoError);
Assert::AreEqual(true, isSuccess);
}
// Test if the CheckIfRemappingsAreValid method is successful when valid shortcut to shortcut remaps are passed
TEST_METHOD (CheckIfRemappingsAreValid_ShouldReturnNoError_OnPassingValidShortcutToShortcutRemaps)
{
RemapBuffer remapBuffer;
// Remap Ctrl+V to Ctrl+D and Alt+Tab to Win+A
Shortcut src1;
src1.SetKey(VK_CONTROL);
src1.SetKey(0x56);
Shortcut dest1;
dest1.SetKey(VK_CONTROL);
dest1.SetKey(0x44);
Shortcut src2;
src2.SetKey(VK_MENU);
src2.SetKey(VK_TAB);
Shortcut dest2;
dest2.SetKey(CommonSharedConstants::VK_WIN_BOTH);
dest2.SetKey(0x41);
remapBuffer.push_back(std::make_pair(RemapBufferItem({ src1, dest1 }), std::wstring()));
remapBuffer.push_back(std::make_pair(RemapBufferItem({ src2, dest2 }), std::wstring()));
// Assert that remapping set is valid
bool isSuccess = (LoadingAndSavingRemappingHelper::CheckIfRemappingsAreValid(remapBuffer) == KeyboardManagerHelper::ErrorType::NoError);
Assert::AreEqual(true, isSuccess);
}
// Test if the CheckIfRemappingsAreValid method is successful when valid remaps are passed
TEST_METHOD (CheckIfRemappingsAreValid_ShouldReturnNoError_OnPassingValidRemapsOfAllTypes)
{
RemapBuffer remapBuffer;
// Remap Ctrl+V to Ctrl+D, Alt+Tab to A, A to B and B to Win+A
Shortcut src1;
src1.SetKey(VK_CONTROL);
src1.SetKey(0x56);
Shortcut dest1;
dest1.SetKey(VK_CONTROL);
dest1.SetKey(0x44);
Shortcut src2;
src2.SetKey(VK_MENU);
src2.SetKey(VK_TAB);
Shortcut dest2;
dest2.SetKey(CommonSharedConstants::VK_WIN_BOTH);
dest2.SetKey(0x41);
remapBuffer.push_back(std::make_pair(RemapBufferItem({ src1, dest1 }), std::wstring()));
remapBuffer.push_back(std::make_pair(RemapBufferItem({ src2, 0x41 }), std::wstring()));
remapBuffer.push_back(std::make_pair(RemapBufferItem({ 0x41, 0x42 }), std::wstring()));
remapBuffer.push_back(std::make_pair(RemapBufferItem({ 0x42, dest2 }), std::wstring()));
// Assert that remapping set is valid
bool isSuccess = (LoadingAndSavingRemappingHelper::CheckIfRemappingsAreValid(remapBuffer) == KeyboardManagerHelper::ErrorType::NoError);
Assert::AreEqual(true, isSuccess);
}
// Test if the CheckIfRemappingsAreValid method is unsuccessful when remaps with null keys are passed
TEST_METHOD (CheckIfRemappingsAreValid_ShouldReturnRemapUnsuccessful_OnPassingRemapsWithNullKeys)
{
RemapBuffer remapBuffer;
// Remap A to NULL
remapBuffer.push_back(std::make_pair(RemapBufferItem({ 0x41, NULL }), std::wstring()));
// Assert that remapping set is invalid
bool isSuccess = (LoadingAndSavingRemappingHelper::CheckIfRemappingsAreValid(remapBuffer) == KeyboardManagerHelper::ErrorType::RemapUnsuccessful);
Assert::AreEqual(true, isSuccess);
}
// Test if the CheckIfRemappingsAreValid method is unsuccessful when remaps with invalid shortcuts are passed
TEST_METHOD (CheckIfRemappingsAreValid_ShouldReturnRemapUnsuccessful_OnPassingRemapsWithInvalidShortcut)
{
RemapBuffer remapBuffer;
// Remap A to incomplete shortcut (Ctrl)
Shortcut src1;
src1.SetKey(VK_CONTROL);
remapBuffer.push_back(std::make_pair(RemapBufferItem({ 0x41, src1 }), std::wstring()));
// Assert that remapping set is invalid
bool isSuccess = (LoadingAndSavingRemappingHelper::CheckIfRemappingsAreValid(remapBuffer) == KeyboardManagerHelper::ErrorType::RemapUnsuccessful);
Assert::AreEqual(true, isSuccess);
}
// Test if the CheckIfRemappingsAreValid method is unsuccessful when remaps with the same key remapped twice are passed
TEST_METHOD (CheckIfRemappingsAreValid_ShouldReturnRemapUnsuccessful_OnPassingRemapsWithSameKeyRemappedTwice)
{
RemapBuffer remapBuffer;
// Remap A to B and A to Ctrl+C
Shortcut src1;
src1.SetKey(VK_CONTROL);
src1.SetKey(0x43);
remapBuffer.push_back(std::make_pair(RemapBufferItem({ 0x41, 0x42 }), std::wstring()));
remapBuffer.push_back(std::make_pair(RemapBufferItem({ 0x41, src1 }), std::wstring()));
// Assert that remapping set is invalid
bool isSuccess = (LoadingAndSavingRemappingHelper::CheckIfRemappingsAreValid(remapBuffer) == KeyboardManagerHelper::ErrorType::RemapUnsuccessful);
Assert::AreEqual(true, isSuccess);
}
// Test if the CheckIfRemappingsAreValid method is unsuccessful when remaps with the same shortcut remapped twice are passed
TEST_METHOD (CheckIfRemappingsAreValid_ShouldReturnRemapUnsuccessful_OnPassingRemapsWithSameShortcutRemappedTwice)
{
RemapBuffer remapBuffer;
// Remap Ctrl+A to B and Ctrl+A to Ctrl+V
Shortcut src1;
src1.SetKey(VK_CONTROL);
src1.SetKey(0x41);
Shortcut dest1;
dest1.SetKey(VK_CONTROL);
dest1.SetKey(0x56);
remapBuffer.push_back(std::make_pair(RemapBufferItem({ src1, 0x42 }), std::wstring()));
remapBuffer.push_back(std::make_pair(RemapBufferItem({ src1, dest1 }), std::wstring()));
// Assert that remapping set is invalid
bool isSuccess = (LoadingAndSavingRemappingHelper::CheckIfRemappingsAreValid(remapBuffer) == KeyboardManagerHelper::ErrorType::RemapUnsuccessful);
Assert::AreEqual(true, isSuccess);
}
// Test if the CheckIfRemappingsAreValid method is unsuccessful when app specific remaps with the same shortcut remapped twice for the same target app are passed
TEST_METHOD (CheckIfRemappingsAreValid_ShouldReturnRemapUnsuccessful_OnPassingAppSpecificRemapsWithSameShortcutRemappedTwiceForTheSameTargetApp)
{
RemapBuffer remapBuffer;
// Remap Ctrl+A to B and Ctrl+A to Ctrl+V for testApp1
Shortcut src1;
src1.SetKey(VK_CONTROL);
src1.SetKey(0x41);
Shortcut dest1;
dest1.SetKey(VK_CONTROL);
dest1.SetKey(0x56);
remapBuffer.push_back(std::make_pair(RemapBufferItem({ src1, 0x42 }), testApp1));
remapBuffer.push_back(std::make_pair(RemapBufferItem({ src1, dest1 }), testApp1));
// Assert that remapping set is invalid
bool isSuccess = (LoadingAndSavingRemappingHelper::CheckIfRemappingsAreValid(remapBuffer) == KeyboardManagerHelper::ErrorType::RemapUnsuccessful);
Assert::AreEqual(true, isSuccess);
}
// Test if the CheckIfRemappingsAreValid method is successful when app specific remaps with the same shortcut remapped twice for different target apps are passed
TEST_METHOD (CheckIfRemappingsAreValid_ShouldReturnNoError_OnPassingAppSpecificRemapsWithSameShortcutRemappedTwiceForDifferentTargetApps)
{
RemapBuffer remapBuffer;
// Remap Ctrl+A to B for testApp1 and Ctrl+A to Ctrl+V for testApp2
Shortcut src1;
src1.SetKey(VK_CONTROL);
src1.SetKey(0x41);
Shortcut dest1;
dest1.SetKey(VK_CONTROL);
dest1.SetKey(0x56);
remapBuffer.push_back(std::make_pair(RemapBufferItem({ src1, 0x42 }), testApp1));
remapBuffer.push_back(std::make_pair(RemapBufferItem({ src1, dest1 }), testApp2));
// Assert that remapping set is valid
bool isSuccess = (LoadingAndSavingRemappingHelper::CheckIfRemappingsAreValid(remapBuffer) == KeyboardManagerHelper::ErrorType::NoError);
Assert::AreEqual(true, isSuccess);
}
// Test if the GetOrphanedKeys method return an empty vector on passing no remaps
TEST_METHOD (GetOrphanedKeys_ShouldReturnEmptyVector_OnPassingNoRemaps)
{
RemapBuffer remapBuffer;
// Assert that there are no orphaned keys
Assert::AreEqual(true, LoadingAndSavingRemappingHelper::GetOrphanedKeys(remapBuffer).empty());
}
// Test if the GetOrphanedKeys method return one orphaned on passing one key remap
TEST_METHOD (GetOrphanedKeys_ShouldReturnOneOrphanedKey_OnPassingOneKeyRemap)
{
RemapBuffer remapBuffer;
// Remap A to B
remapBuffer.push_back(std::make_pair(RemapBufferItem({ 0x41, 0x42 }), std::wstring()));
// Assert that only A is orphaned
Assert::AreEqual((size_t)1, LoadingAndSavingRemappingHelper::GetOrphanedKeys(remapBuffer).size());
Assert::AreEqual((DWORD)0x41, LoadingAndSavingRemappingHelper::GetOrphanedKeys(remapBuffer)[0]);
}
// Test if the GetOrphanedKeys method return an empty vector on passing swapped key remaps
TEST_METHOD (GetOrphanedKeys_ShouldReturnEmptyVector_OnPassingSwappedKeyRemap)
{
RemapBuffer remapBuffer;
// Remap A to B and B to A
remapBuffer.push_back(std::make_pair(RemapBufferItem({ 0x41, 0x42 }), std::wstring()));
remapBuffer.push_back(std::make_pair(RemapBufferItem({ 0x42, 0x41 }), std::wstring()));
// Assert that there are no orphaned keys
Assert::AreEqual(true, LoadingAndSavingRemappingHelper::GetOrphanedKeys(remapBuffer).empty());
}
// Test if the GetOrphanedKeys method return one orphaned on passing two key remaps where one key is mapped to a remapped key
TEST_METHOD (GetOrphanedKeys_ShouldReturnOneOrphanedKey_OnPassingTwoKeyRemapsWhereOneKeyIsMappedToARemappedKey)
{
RemapBuffer remapBuffer;
// Remap A to Ctrl+B and C to A
Shortcut dest1;
dest1.SetKey(VK_CONTROL);
dest1.SetKey(0x42);
remapBuffer.push_back(std::make_pair(RemapBufferItem({ 0x41, dest1 }), std::wstring()));
remapBuffer.push_back(std::make_pair(RemapBufferItem({ 0x43, 0x41 }), std::wstring()));
// Assert that only C is orphaned
Assert::AreEqual((size_t)1, LoadingAndSavingRemappingHelper::GetOrphanedKeys(remapBuffer).size());
Assert::AreEqual((DWORD)0x43, LoadingAndSavingRemappingHelper::GetOrphanedKeys(remapBuffer)[0]);
}
// Test if the PreProcessRemapTable method combines all the modifier pairs when the left and right modifiers are remapped to the same target
TEST_METHOD (PreProcessRemapTable_ShouldCombineAllPairs_OnPassingLeftAndRightModifiersRemappedToTheSameTarget)
{
std::unordered_map<DWORD, std::variant<DWORD, Shortcut>> remapTable;
// Remap LCtrl and RCtrl to A, LAlt and RAlt to B, LShift and RShift to C, LWin and RWin to D
remapTable[VK_LCONTROL] = 0x41;
remapTable[VK_RCONTROL] = 0x41;
remapTable[VK_LMENU] = 0x42;
remapTable[VK_RMENU] = 0x42;
remapTable[VK_LSHIFT] = 0x43;
remapTable[VK_RSHIFT] = 0x43;
remapTable[VK_LWIN] = 0x44;
remapTable[VK_RWIN] = 0x44;
// Pre process table
LoadingAndSavingRemappingHelper::PreProcessRemapTable(remapTable);
// Expected Ctrl remapped to A, Alt to B, Shift to C, Win to D
std::unordered_map<DWORD, std::variant<DWORD, Shortcut>> expectedTable;
expectedTable[VK_CONTROL] = 0x41;
expectedTable[VK_MENU] = 0x42;
expectedTable[VK_SHIFT] = 0x43;
expectedTable[CommonSharedConstants::VK_WIN_BOTH] = 0x44;
bool areTablesEqual = (expectedTable == remapTable);
Assert::AreEqual(true, areTablesEqual);
}
// Test if the PreProcessRemapTable method does not combines any of the modifier pairs when the left and right modifiers are remapped to different targets
TEST_METHOD (PreProcessRemapTable_ShouldNotCombineAnyPairs_OnPassingLeftAndRightModifiersRemappedToTheDifferentTargets)
{
std::unordered_map<DWORD, std::variant<DWORD, Shortcut>> remapTable;
// Remap left modifiers to A and right modifiers to B
remapTable[VK_LCONTROL] = 0x41;
remapTable[VK_RCONTROL] = 0x42;
remapTable[VK_LMENU] = 0x41;
remapTable[VK_RMENU] = 0x42;
remapTable[VK_LSHIFT] = 0x41;
remapTable[VK_RSHIFT] = 0x42;
remapTable[VK_LWIN] = 0x41;
remapTable[VK_RWIN] = 0x42;
// Pre process table
LoadingAndSavingRemappingHelper::PreProcessRemapTable(remapTable);
// Expected unchanged table
std::unordered_map<DWORD, std::variant<DWORD, Shortcut>> expectedTable;
expectedTable[VK_LCONTROL] = 0x41;
expectedTable[VK_RCONTROL] = 0x42;
expectedTable[VK_LMENU] = 0x41;
expectedTable[VK_RMENU] = 0x42;
expectedTable[VK_LSHIFT] = 0x41;
expectedTable[VK_RSHIFT] = 0x42;
expectedTable[VK_LWIN] = 0x41;
expectedTable[VK_RWIN] = 0x42;
bool areTablesEqual = (expectedTable == remapTable);
Assert::AreEqual(true, areTablesEqual);
}
// Test if the ApplySingleKeyRemappings method resets the keyboard manager state's single key remappings on passing an empty buffer
TEST_METHOD (ApplySingleKeyRemappings_ShouldResetSingleKeyRemappings_OnPassingEmptyBuffer)
{
KeyboardManagerState testState;
RemapBuffer remapBuffer;
// Remap A to B
testState.AddSingleKeyRemap(0x41, 0x42);
// Apply the single key remaps from the buffer to the keyboard manager state variable
LoadingAndSavingRemappingHelper::ApplySingleKeyRemappings(testState, remapBuffer, false);
// Assert that single key remapping in the kbm state variable is empty
Assert::AreEqual((size_t)0, testState.singleKeyReMap.size());
}
// Test if the ApplySingleKeyRemappings method copies only the valid remappings to the keyboard manager state variable when some of the remappings are invalid
TEST_METHOD (ApplySingleKeyRemappings_ShouldCopyOnlyValidRemappings_OnPassingBufferWithSomeInvalidRemappings)
{
KeyboardManagerState testState;
RemapBuffer remapBuffer;
// Add A->B, B->Ctrl+V, C to incomplete shortcut and D to incomplete key remappings to the buffer
Shortcut s1;
s1.SetKey(VK_CONTROL);
s1.SetKey(0x56);
Shortcut s2;
s2.SetKey(VK_LMENU);
remapBuffer.push_back(std::make_pair(RemapBufferItem({ 0x41, 0x42 }), std::wstring()));
remapBuffer.push_back(std::make_pair(RemapBufferItem({ 0x42, s1 }), std::wstring()));
remapBuffer.push_back(std::make_pair(RemapBufferItem({ 0x43, NULL }), std::wstring()));
remapBuffer.push_back(std::make_pair(RemapBufferItem({ 0x44, s2 }), std::wstring()));
// Apply the single key remaps from the buffer to the keyboard manager state variable
LoadingAndSavingRemappingHelper::ApplySingleKeyRemappings(testState, remapBuffer, false);
// Expected A remapped to B, B remapped to Ctrl+V
std::unordered_map<DWORD, std::variant<DWORD, Shortcut>> expectedTable;
expectedTable[0x41] = 0x42;
expectedTable[0x42] = s1;
bool areTablesEqual = (expectedTable == testState.singleKeyReMap);
Assert::AreEqual(true, areTablesEqual);
}
// Test if the ApplySingleKeyRemappings method splits common modifiers to their left and right version when copying to the keyboard manager state variable if remappings from common modifiers are passed
TEST_METHOD (ApplySingleKeyRemappings_ShouldSplitRemappingsFromCommonModifiers_OnPassingBufferWithSomeMappingsFromCommonModifiers)
{
KeyboardManagerState testState;
RemapBuffer remapBuffer;
// Add Ctrl->A, Alt->B, Shift->C and Win->D remappings to the buffer
remapBuffer.push_back(std::make_pair(RemapBufferItem({ VK_CONTROL, 0x41 }), std::wstring()));
remapBuffer.push_back(std::make_pair(RemapBufferItem({ VK_MENU, 0x42 }), std::wstring()));
remapBuffer.push_back(std::make_pair(RemapBufferItem({ VK_SHIFT, 0x43 }), std::wstring()));
remapBuffer.push_back(std::make_pair(RemapBufferItem({ CommonSharedConstants::VK_WIN_BOTH, 0x44 }), std::wstring()));
// Apply the single key remaps from the buffer to the keyboard manager state variable
LoadingAndSavingRemappingHelper::ApplySingleKeyRemappings(testState, remapBuffer, false);
// Expected LCtrl/RCtrl remapped to A, LAlt/RAlt to B, LShift/RShift to C, LWin/RWin to D
std::unordered_map<DWORD, std::variant<DWORD, Shortcut>> expectedTable;
expectedTable[VK_LCONTROL] = 0x41;
expectedTable[VK_RCONTROL] = 0x41;
expectedTable[VK_LMENU] = 0x42;
expectedTable[VK_RMENU] = 0x42;
expectedTable[VK_LSHIFT] = 0x43;
expectedTable[VK_RSHIFT] = 0x43;
expectedTable[VK_LWIN] = 0x44;
expectedTable[VK_RWIN] = 0x44;
bool areTablesEqual = (expectedTable == testState.singleKeyReMap);
Assert::AreEqual(true, areTablesEqual);
}
// Test if the ApplyShortcutRemappings method resets the keyboard manager state's os level and app specific shortcut remappings on passing an empty buffer
TEST_METHOD (ApplyShortcutRemappings_ShouldResetShortcutRemappings_OnPassingEmptyBuffer)
{
KeyboardManagerState testState;
RemapBuffer remapBuffer;
// Remap Ctrl+A to Ctrl+B for all apps and Ctrl+C to Alt+V for testApp1
Shortcut src1;
src1.SetKey(VK_CONTROL);
src1.SetKey(0x41);
Shortcut dest1;
dest1.SetKey(VK_CONTROL);
dest1.SetKey(0x42);
Shortcut src2;
src2.SetKey(VK_CONTROL);
src2.SetKey(0x43);
Shortcut dest2;
dest2.SetKey(VK_MENU);
dest2.SetKey(0x56);
testState.AddOSLevelShortcut(src1, dest1);
testState.AddAppSpecificShortcut(testApp1, src1, dest1);
// Apply the shortcut remaps from the buffer to the keyboard manager state variable
LoadingAndSavingRemappingHelper::ApplyShortcutRemappings(testState, remapBuffer, false);
// Assert that shortcut remappings in the kbm state variable is empty
Assert::AreEqual((size_t)0, testState.osLevelShortcutReMap.size());
Assert::AreEqual((size_t)0, testState.appSpecificShortcutReMap.size());
}
// Test if the ApplyShortcutRemappings method copies only the valid remappings to the keyboard manager state variable when some of the remappings are invalid
TEST_METHOD (ApplyShortcutRemappings_ShouldCopyOnlyValidRemappings_OnPassingBufferWithSomeInvalidRemappings)
{
KeyboardManagerState testState;
RemapBuffer remapBuffer;
// Add Ctrl+A->Ctrl+B, Ctrl+C->Alt+V, Ctrl+F->incomplete shortcut and Ctrl+G->incomplete key os level remappings to buffer
// Add Ctrl+F->Alt+V, Ctrl+G->Ctrl+B, Ctrl+A->incomplete shortcut and Ctrl+C->incomplete key app specific remappings to buffer
Shortcut src1;
src1.SetKey(VK_CONTROL);
src1.SetKey(0x41);
Shortcut dest1;
dest1.SetKey(VK_CONTROL);
dest1.SetKey(0x42);
Shortcut src2;
src2.SetKey(VK_CONTROL);
src2.SetKey(0x43);
Shortcut dest2;
dest2.SetKey(VK_MENU);
dest2.SetKey(0x56);
Shortcut src3;
src3.SetKey(VK_CONTROL);
src3.SetKey(0x46);
Shortcut src4;
src4.SetKey(VK_CONTROL);
src4.SetKey(0x47);
Shortcut dest4;
dest4.SetKey(VK_CONTROL);
remapBuffer.push_back(std::make_pair(RemapBufferItem({ src1, dest1 }), std::wstring()));
remapBuffer.push_back(std::make_pair(RemapBufferItem({ src2, dest2 }), std::wstring()));
remapBuffer.push_back(std::make_pair(RemapBufferItem({ src3, NULL }), std::wstring()));
remapBuffer.push_back(std::make_pair(RemapBufferItem({ src4, dest4 }), std::wstring()));
remapBuffer.push_back(std::make_pair(RemapBufferItem({ src3, dest2 }), testApp1));
remapBuffer.push_back(std::make_pair(RemapBufferItem({ src4, dest1 }), testApp1));
remapBuffer.push_back(std::make_pair(RemapBufferItem({ src1, NULL }), testApp1));
remapBuffer.push_back(std::make_pair(RemapBufferItem({ src2, dest4 }), testApp1));
// Apply the shortcut remaps from the buffer to the keyboard manager state variable
LoadingAndSavingRemappingHelper::ApplyShortcutRemappings(testState, remapBuffer, false);
// Ctrl+A->Ctrl+B and Ctrl+C->Alt+V
std::map<Shortcut, RemapShortcut> expectedOSLevelTable;
expectedOSLevelTable[src1] = RemapShortcut(dest1);
expectedOSLevelTable[src2] = RemapShortcut(dest2);
// Ctrl+F->Alt+V and Ctrl+G->Ctrl+B for testApp1
std::map<std::wstring, std::map<Shortcut, RemapShortcut>> expectedAppSpecificLevelTable;
expectedAppSpecificLevelTable[testApp1][src3] = RemapShortcut(dest2);
expectedAppSpecificLevelTable[testApp1][src4] = RemapShortcut(dest1);
bool areOSLevelTablesEqual = (expectedOSLevelTable == testState.osLevelShortcutReMap);
bool areAppSpecificTablesEqual = (expectedAppSpecificLevelTable == testState.appSpecificShortcutReMap);
Assert::AreEqual(true, areOSLevelTablesEqual);
Assert::AreEqual(true, areAppSpecificTablesEqual);
}
};
}

View File

@@ -0,0 +1,176 @@
#include "pch.h"
#include "CppUnitTest.h"
#include <keyboardmanager/common/Shortcut.h>
#include <keyboardmanager/common/Helpers.h>
#include "TestHelpers.h"
#include "../common/keyboard_layout.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace KeyboardManagerCommonTests
{
// Tests for methods in the Shortcut class
TEST_CLASS (KeyboardManagerHelperTests)
{
public:
TEST_METHOD_INITIALIZE(InitializeTestEnv)
{
}
// Test if the IsValidShortcut method returns false on passing shortcut with null action key
TEST_METHOD (IsValidShortcut_ShouldReturnFalse_OnPassingShortcutWithNullActionKey)
{
// Arrange
Shortcut s;
s.SetKey(NULL);
// Act
bool result = s.IsValidShortcut();
// Assert
Assert::IsFalse(result);
}
// Test if the IsValidShortcut method returns false on passing shortcut with only action key
TEST_METHOD (IsValidShortcut_ShouldReturnFalse_OnPassingShortcutWithOnlyActionKey)
{
// Arrange
Shortcut s;
s.SetKey(0x41);
// Act
bool result = s.IsValidShortcut();
// Assert
Assert::IsFalse(result);
}
// Test if the IsValidShortcut method returns false on passing shortcut with only modifier keys
TEST_METHOD (IsValidShortcut_ShouldReturnFalse_OnPassingShortcutWithOnlyModifierKeys)
{
// Arrange
Shortcut s;
s.SetKey(VK_CONTROL);
s.SetKey(VK_SHIFT);
// Act
bool result = s.IsValidShortcut();
// Assert
Assert::IsFalse(result);
}
// Test if the IsValidShortcut method returns true on passing shortcut with modifier and action key
TEST_METHOD (IsValidShortcut_ShouldReturnFalse_OnPassingShortcutWithModifierAndActionKey)
{
// Arrange
Shortcut s;
s.SetKey(VK_CONTROL);
s.SetKey(0x41);
// Act
bool result = s.IsValidShortcut();
// Assert
Assert::IsTrue(result);
}
// Test if the DoKeysOverlap method returns NoError on passing invalid shortcut for one of the arguments
TEST_METHOD (DoKeysOverlap_ShouldReturnNoError_OnPassingInvalidShortcutForOneOfTheArguments)
{
// Arrange
Shortcut s1(std::vector<DWORD>{ NULL });
Shortcut s2(std::vector<DWORD>{ VK_CONTROL, 0x41 });
// Act
auto result = Shortcut::DoKeysOverlap(s1, s2);
// Assert
Assert::IsTrue(result == KeyboardManagerHelper::ErrorType::NoError);
}
// Test if the DoKeysOverlap method returns SameShortcutPreviouslyMapped on passing same shortcut for both arguments
TEST_METHOD (DoKeysOverlap_ShouldReturnSameShortcutPreviouslyMapped_OnPassingSameShortcutForBothArguments)
{
// Arrange
Shortcut s1(std::vector<DWORD>{ VK_CONTROL, 0x41 });
Shortcut s2 = s1;
// Act
auto result = Shortcut::DoKeysOverlap(s1, s2);
// Assert
Assert::IsTrue(result == KeyboardManagerHelper::ErrorType::SameShortcutPreviouslyMapped);
}
// Test if the DoKeysOverlap method returns NoError on passing shortcuts with different action keys
TEST_METHOD (DoKeysOverlap_ShouldReturnNoError_OnPassingShortcutsWithDifferentActionKeys)
{
// Arrange
Shortcut s1(std::vector<DWORD>{ VK_CONTROL, 0x42 });
Shortcut s2(std::vector<DWORD>{ VK_CONTROL, 0x41 });
// Act
auto result = Shortcut::DoKeysOverlap(s1, s2);
// Assert
Assert::IsTrue(result == KeyboardManagerHelper::ErrorType::NoError);
}
// Test if the DoKeysOverlap method returns NoError on passing shortcuts with different modifiers
TEST_METHOD (DoKeysOverlap_ShouldReturnNoError_OnPassingShortcutsWithDifferentModifiers)
{
// Arrange
Shortcut s1(std::vector<DWORD>{ VK_CONTROL, 0x42 });
Shortcut s2(std::vector<DWORD>{ VK_SHIFT, 0x42 });
// Act
auto result = Shortcut::DoKeysOverlap(s1, s2);
// Assert
Assert::IsTrue(result == KeyboardManagerHelper::ErrorType::NoError);
}
// Test if the DoKeysOverlap method returns ConflictingModifierShortcut on passing shortcuts with left modifier and common modifier
TEST_METHOD (DoKeysOverlap_ShouldReturnConflictingModifierShortcut_OnPassingShortcutsWithLeftModifierAndCommonModifierOfSameType)
{
// Arrange
Shortcut s1(std::vector<DWORD>{ VK_LCONTROL, 0x42 });
Shortcut s2(std::vector<DWORD>{ VK_CONTROL, 0x42 });
// Act
auto result = Shortcut::DoKeysOverlap(s1, s2);
// Assert
Assert::IsTrue(result == KeyboardManagerHelper::ErrorType::ConflictingModifierShortcut);
}
// Test if the DoKeysOverlap method returns ConflictingModifierShortcut on passing shortcuts with right modifier and common modifier
TEST_METHOD (DoKeysOverlap_ShouldReturnConflictingModifierShortcut_OnPassingShortcutsWithRightModifierAndCommonModifierOfSameType)
{
// Arrange
Shortcut s1(std::vector<DWORD>{ VK_RCONTROL, 0x42 });
Shortcut s2(std::vector<DWORD>{ VK_CONTROL, 0x42 });
// Act
auto result = Shortcut::DoKeysOverlap(s1, s2);
// Assert
Assert::IsTrue(result == KeyboardManagerHelper::ErrorType::ConflictingModifierShortcut);
}
// Test if the DoKeysOverlap method returns ConflictingModifierShortcut on passing shortcuts with left modifier and right modifier
TEST_METHOD (DoKeysOverlap_ShouldReturnConflictingModifierShortcut_OnPassingShortcutsWithLeftModifierAndRightModifierOfSameType)
{
// Arrange
Shortcut s1(std::vector<DWORD>{ VK_LCONTROL, 0x42 });
Shortcut s2(std::vector<DWORD>{ VK_RCONTROL, 0x42 });
// Act
auto result = Shortcut::DoKeysOverlap(s1, s2);
// Assert
Assert::IsTrue(result == KeyboardManagerHelper::ErrorType::NoError);
}
};
}

View File

@@ -22,4 +22,10 @@ namespace TestHelpers
state.SetActivatedApp(maxLengthString); state.SetActivatedApp(maxLengthString);
state.SetActivatedApp(KeyboardManagerConstants::NoActivatedApp); state.SetActivatedApp(KeyboardManagerConstants::NoActivatedApp);
} }
// Function to return the index of the given key code from the drop down key list
int GetDropDownIndexFromDropDownList(DWORD key, const std::vector<DWORD>& keyList)
{
return (int)std::distance(keyList.begin(), std::find(keyList.begin(), keyList.end(), key));
}
} }

View File

@@ -6,4 +6,7 @@ namespace TestHelpers
{ {
// Function to reset the environment variables for tests // Function to reset the environment variables for tests
void ResetTestEnv(MockedInput& input, KeyboardManagerState& state); void ResetTestEnv(MockedInput& input, KeyboardManagerState& state);
// Function to return the index of the given key code from the drop down key list
int GetDropDownIndexFromDropDownList(DWORD key, const std::vector<DWORD>& keyList);
} }

View File

@@ -6,3 +6,4 @@
#include <shlwapi.h> #include <shlwapi.h>
#include <stdexcept> #include <stdexcept>
#include <unordered_set> #include <unordered_set>
#include "winrt/Windows.Foundation.h"

View File

@@ -0,0 +1,294 @@
#include "pch.h"
#include "BufferValidationHelpers.h"
#include <keyboardmanager/common/KeyboardManagerConstants.h>
namespace BufferValidationHelpers
{
// Function to validate and update an element of the key remap buffer when the selection has changed
KeyboardManagerHelper::ErrorType ValidateAndUpdateKeyBufferElement(int rowIndex, int colIndex, int selectedKeyIndex, const std::vector<DWORD>& keyCodeList, RemapBuffer& remapBuffer)
{
KeyboardManagerHelper::ErrorType errorType = KeyboardManagerHelper::ErrorType::NoError;
// Check if the element was not found or the index exceeds the known keys
if (selectedKeyIndex != -1 && keyCodeList.size() > selectedKeyIndex)
{
// Check if the value being set is the same as the other column
if (remapBuffer[rowIndex].first[std::abs(int(colIndex) - 1)].index() == 0)
{
if (std::get<DWORD>(remapBuffer[rowIndex].first[std::abs(int(colIndex) - 1)]) == keyCodeList[selectedKeyIndex])
{
errorType = KeyboardManagerHelper::ErrorType::MapToSameKey;
}
}
// If one column is shortcut and other is key no warning required
if (errorType == KeyboardManagerHelper::ErrorType::NoError && colIndex == 0)
{
// Check if the key is already remapped to something else
for (int i = 0; i < remapBuffer.size(); i++)
{
if (i != rowIndex)
{
if (remapBuffer[i].first[colIndex].index() == 0)
{
KeyboardManagerHelper::ErrorType result = KeyboardManagerHelper::DoKeysOverlap(std::get<DWORD>(remapBuffer[i].first[colIndex]), keyCodeList[selectedKeyIndex]);
if (result != KeyboardManagerHelper::ErrorType::NoError)
{
errorType = result;
break;
}
}
// If one column is shortcut and other is key no warning required
}
}
}
// If there is no error, set the buffer
if (errorType == KeyboardManagerHelper::ErrorType::NoError)
{
remapBuffer[rowIndex].first[colIndex] = keyCodeList[selectedKeyIndex];
}
else
{
remapBuffer[rowIndex].first[colIndex] = NULL;
}
}
else
{
// Reset to null if the key is not found
remapBuffer[rowIndex].first[colIndex] = NULL;
}
return errorType;
}
// Function to validate an element of the shortcut remap buffer when the selection has changed
std::pair<KeyboardManagerHelper::ErrorType, DropDownAction> ValidateShortcutBufferElement(int rowIndex, int colIndex, uint32_t dropDownIndex, const std::vector<int32_t>& selectedIndices, std::wstring appName, bool isHybridControl, const std::vector<DWORD>& keyCodeList, const RemapBuffer& remapBuffer, bool dropDownFound)
{
BufferValidationHelpers::DropDownAction dropDownAction = BufferValidationHelpers::DropDownAction::NoAction;
KeyboardManagerHelper::ErrorType errorType = KeyboardManagerHelper::ErrorType::NoError;
size_t dropDownCount = selectedIndices.size();
std::vector<DWORD> selectedKeyCodes = KeyboardManagerHelper::GetKeyCodesFromSelectedIndices(selectedIndices, keyCodeList);
int selectedKeyIndex = dropDownFound ? selectedIndices[dropDownIndex] : -1;
if (selectedKeyIndex != -1 && keyCodeList.size() > selectedKeyIndex && dropDownFound)
{
// If only 1 drop down and action key is chosen: Warn that a modifier must be chosen (if the drop down is not for a hybrid scenario)
if (dropDownCount == 1 && !KeyboardManagerHelper::IsModifierKey(keyCodeList[selectedKeyIndex]) && !isHybridControl)
{
// warn and reset the drop down
errorType = KeyboardManagerHelper::ErrorType::ShortcutStartWithModifier;
}
// If it is the last drop down
else if (dropDownIndex == dropDownCount - 1)
{
// If last drop down and a modifier is selected: add a new drop down (max drop down count should be enforced)
if (KeyboardManagerHelper::IsModifierKey(keyCodeList[selectedKeyIndex]) && dropDownCount < KeyboardManagerConstants::MaxShortcutSize)
{
// If it matched any of the previous modifiers then reset that drop down
if (KeyboardManagerHelper::CheckRepeatedModifier(selectedKeyCodes, selectedKeyIndex, keyCodeList))
{
// warn and reset the drop down
errorType = KeyboardManagerHelper::ErrorType::ShortcutCannotHaveRepeatedModifier;
}
// If not, add a new drop down
else
{
dropDownAction = BufferValidationHelpers::DropDownAction::AddDropDown;
}
}
// If last drop down and a modifier is selected but there are already max drop downs: warn the user
else if (KeyboardManagerHelper::IsModifierKey(keyCodeList[selectedKeyIndex]) && dropDownCount >= KeyboardManagerConstants::MaxShortcutSize)
{
// warn and reset the drop down
errorType = KeyboardManagerHelper::ErrorType::ShortcutMaxShortcutSizeOneActionKey;
}
// If None is selected but it's the last index: warn
else if (keyCodeList[selectedKeyIndex] == 0)
{
// If it is a hybrid control and there are 2 drop downs then deletion is allowed
if (isHybridControl && dropDownCount == KeyboardManagerConstants::MinShortcutSize)
{
// set delete drop down flag
dropDownAction = BufferValidationHelpers::DropDownAction::DeleteDropDown;
// do not delete the drop down now since there may be some other error which would cause the drop down to be invalid after removal
}
else
{
// warn and reset the drop down
errorType = KeyboardManagerHelper::ErrorType::ShortcutOneActionKey;
}
}
// If none of the above, then the action key will be set
}
// If it is the not the last drop down
else
{
if (KeyboardManagerHelper::IsModifierKey(keyCodeList[selectedKeyIndex]))
{
// If it matched any of the previous modifiers then reset that drop down
if (KeyboardManagerHelper::CheckRepeatedModifier(selectedKeyCodes, selectedKeyIndex, keyCodeList))
{
// warn and reset the drop down
errorType = KeyboardManagerHelper::ErrorType::ShortcutCannotHaveRepeatedModifier;
}
// If not, the modifier key will be set
}
// If None is selected and there are more than 2 drop downs
else if (keyCodeList[selectedKeyIndex] == 0 && dropDownCount > KeyboardManagerConstants::MinShortcutSize)
{
// set delete drop down flag
dropDownAction = BufferValidationHelpers::DropDownAction::DeleteDropDown;
// do not delete the drop down now since there may be some other error which would cause the drop down to be invalid after removal
}
else if (keyCodeList[selectedKeyIndex] == 0 && dropDownCount <= KeyboardManagerConstants::MinShortcutSize)
{
// If it is a hybrid control and there are 2 drop downs then deletion is allowed
if (isHybridControl && dropDownCount == KeyboardManagerConstants::MinShortcutSize)
{
// set delete drop down flag
dropDownAction = BufferValidationHelpers::DropDownAction::DeleteDropDown;
// do not delete the drop down now since there may be some other error which would cause the drop down to be invalid after removal
}
else
{
// warn and reset the drop down
errorType = KeyboardManagerHelper::ErrorType::ShortcutAtleast2Keys;
}
}
// If the user tries to set an action key check if all drop down menus after this are empty if it is not the first key. If it is a hybrid control, this can be done even on the first key
else if (dropDownIndex != 0 || isHybridControl)
{
bool isClear = true;
for (int i = dropDownIndex + 1; i < (int)dropDownCount; i++)
{
if (selectedIndices[i] != -1)
{
isClear = false;
break;
}
}
if (isClear)
{
dropDownAction = BufferValidationHelpers::DropDownAction::ClearUnusedDropDowns;
}
else
{
// warn and reset the drop down
errorType = KeyboardManagerHelper::ErrorType::ShortcutNotMoreThanOneActionKey;
}
}
// If there an action key is chosen on the first drop down and there are more than one drop down menus
else
{
// warn and reset the drop down
errorType = KeyboardManagerHelper::ErrorType::ShortcutStartWithModifier;
}
}
}
// After validating the shortcut, now for errors like remap to same shortcut, remap shortcut more than once, Win L and Ctrl Alt Del
if (errorType == KeyboardManagerHelper::ErrorType::NoError)
{
std::variant<DWORD, Shortcut> tempShortcut;
if (isHybridControl && selectedKeyCodes.size() == 1)
{
tempShortcut = selectedKeyCodes[0];
}
else
{
tempShortcut = Shortcut();
std::get<Shortcut>(tempShortcut).SetKeyCodes(selectedKeyCodes);
}
// Convert app name to lower case
std::transform(appName.begin(), appName.end(), appName.begin(), towlower);
std::wstring lowercaseDefAppName = KeyboardManagerConstants::DefaultAppName;
std::transform(lowercaseDefAppName.begin(), lowercaseDefAppName.end(), lowercaseDefAppName.begin(), towlower);
if (appName == lowercaseDefAppName)
{
appName = L"";
}
// Check if the value being set is the same as the other column - index of other column does not have to be checked since only one column is hybrid
if (tempShortcut.index() == 1)
{
// If shortcut to shortcut
if (remapBuffer[rowIndex].first[std::abs(int(colIndex) - 1)].index() == 1)
{
if (std::get<Shortcut>(remapBuffer[rowIndex].first[std::abs(int(colIndex) - 1)]) == std::get<Shortcut>(tempShortcut) && std::get<Shortcut>(remapBuffer[rowIndex].first[std::abs(int(colIndex) - 1)]).IsValidShortcut() && std::get<Shortcut>(tempShortcut).IsValidShortcut())
{
errorType = KeyboardManagerHelper::ErrorType::MapToSameShortcut;
}
}
// If one column is shortcut and other is key no warning required
}
else
{
// If key to key
if (remapBuffer[rowIndex].first[std::abs(int(colIndex) - 1)].index() == 0)
{
if (std::get<DWORD>(remapBuffer[rowIndex].first[std::abs(int(colIndex) - 1)]) == std::get<DWORD>(tempShortcut) && std::get<DWORD>(remapBuffer[rowIndex].first[std::abs(int(colIndex) - 1)]) != NULL && std::get<DWORD>(tempShortcut) != NULL)
{
errorType = KeyboardManagerHelper::ErrorType::MapToSameKey;
}
}
// If one column is shortcut and other is key no warning required
}
if (errorType == KeyboardManagerHelper::ErrorType::NoError && colIndex == 0)
{
// Check if the key is already remapped to something else for the same target app
for (int i = 0; i < remapBuffer.size(); i++)
{
std::wstring currAppName = remapBuffer[i].second;
std::transform(currAppName.begin(), currAppName.end(), currAppName.begin(), towlower);
if (i != rowIndex && currAppName == appName)
{
KeyboardManagerHelper::ErrorType result = KeyboardManagerHelper::ErrorType::NoError;
if (!isHybridControl)
{
result = Shortcut::DoKeysOverlap(std::get<Shortcut>(remapBuffer[i].first[colIndex]), std::get<Shortcut>(tempShortcut));
}
else
{
if (tempShortcut.index() == 0 && remapBuffer[i].first[colIndex].index() == 0)
{
if (std::get<DWORD>(tempShortcut) != NULL && std::get<DWORD>(remapBuffer[i].first[colIndex]) != NULL)
{
result = KeyboardManagerHelper::DoKeysOverlap(std::get<DWORD>(remapBuffer[i].first[colIndex]), std::get<DWORD>(tempShortcut));
}
}
else if (tempShortcut.index() == 1 && remapBuffer[i].first[colIndex].index() == 1)
{
if (std::get<Shortcut>(tempShortcut).IsValidShortcut() && std::get<Shortcut>(remapBuffer[i].first[colIndex]).IsValidShortcut())
{
result = Shortcut::DoKeysOverlap(std::get<Shortcut>(remapBuffer[i].first[colIndex]), std::get<Shortcut>(tempShortcut));
}
}
// Other scenarios not possible since key to shortcut is with key to key, and shortcut to key is with shortcut to shortcut
}
if (result != KeyboardManagerHelper::ErrorType::NoError)
{
errorType = result;
break;
}
}
}
}
if (errorType == KeyboardManagerHelper::ErrorType::NoError && tempShortcut.index() == 1)
{
errorType = std::get<Shortcut>(tempShortcut).IsShortcutIllegal();
}
}
return std::make_pair(errorType, dropDownAction);
}
}

View File

@@ -0,0 +1,22 @@
#pragma once
#include "keyboardmanager/common/Helpers.h"
#include <variant>
#include <vector>
#include "keyboardmanager/common/Shortcut.h"
namespace BufferValidationHelpers
{
enum class DropDownAction
{
NoAction,
AddDropDown,
DeleteDropDown,
ClearUnusedDropDowns
};
// Function to validate and update an element of the key remap buffer when the selection has changed
KeyboardManagerHelper::ErrorType ValidateAndUpdateKeyBufferElement(int rowIndex, int colIndex, int selectedKeyIndex, const std::vector<DWORD>& keyCodeList, RemapBuffer& remapBuffer);
// Function to validate an element of the shortcut remap buffer when the selection has changed
std::pair<KeyboardManagerHelper::ErrorType, DropDownAction> ValidateShortcutBufferElement(int rowIndex, int colIndex, uint32_t dropDownIndex, const std::vector<int32_t>& selectedIndices, std::wstring appName, bool isHybridControl, const std::vector<DWORD>& keyCodeList, const RemapBuffer& remapBuffer, bool dropDownFound);
}

View File

@@ -1,44 +1,8 @@
#include "pch.h" #include "pch.h"
#include "Dialog.h" #include "Dialog.h"
#include <set>
using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation;
KeyboardManagerHelper::ErrorType Dialog::CheckIfRemappingsAreValid(const std::vector<std::pair<std::vector<std::variant<DWORD, Shortcut>>, std::wstring>>& remappings)
{
KeyboardManagerHelper::ErrorType isSuccess = KeyboardManagerHelper::ErrorType::NoError;
std::map<std::wstring, std::set<std::variant<DWORD, Shortcut>>> ogKeys;
for (int i = 0; i < remappings.size(); i++)
{
std::variant<DWORD, Shortcut> ogKey = remappings[i].first[0];
std::variant<DWORD, Shortcut> newKey = remappings[i].first[1];
std::wstring appName = remappings[i].second;
bool ogKeyValidity = (ogKey.index() == 0 && std::get<DWORD>(ogKey) != NULL) || (ogKey.index() == 1 && std::get<Shortcut>(ogKey).IsValidShortcut());
bool newKeyValidity = (newKey.index() == 0 && std::get<DWORD>(newKey) != NULL) || (newKey.index() == 1 && std::get<Shortcut>(newKey).IsValidShortcut());
// Add new set for a new target app name
if (ogKeys.find(appName) == ogKeys.end())
{
ogKeys[appName] = std::set<std::variant<DWORD, Shortcut>>();
}
if (ogKeyValidity && newKeyValidity && ogKeys[appName].find(ogKey) == ogKeys[appName].end())
{
ogKeys[appName].insert(ogKey);
}
else if (ogKeyValidity && newKeyValidity && ogKeys[appName].find(ogKey) != ogKeys[appName].end())
{
isSuccess = KeyboardManagerHelper::ErrorType::RemapUnsuccessful;
}
else
{
isSuccess = KeyboardManagerHelper::ErrorType::RemapUnsuccessful;
}
}
return isSuccess;
}
IAsyncOperation<bool> Dialog::PartialRemappingConfirmationDialog(XamlRoot root, std::wstring dialogTitle) IAsyncOperation<bool> Dialog::PartialRemappingConfirmationDialog(XamlRoot root, std::wstring dialogTitle)
{ {
ContentDialog confirmationDialog; ContentDialog confirmationDialog;

View File

@@ -1,7 +1,4 @@
#pragma once #pragma once
#include <vector>
#include <keyboardmanager/common/Helpers.h>
#include <variant>
namespace winrt::Windows::UI::Xaml namespace winrt::Windows::UI::Xaml
{ {
@@ -18,7 +15,5 @@ namespace winrt::Windows::UI::Xaml
namespace Dialog namespace Dialog
{ {
KeyboardManagerHelper::ErrorType CheckIfRemappingsAreValid(const std::vector<std::pair<std::vector<std::variant<DWORD, Shortcut>>, std::wstring>>& remappings);
winrt::Windows::Foundation::IAsyncOperation<bool> PartialRemappingConfirmationDialog(winrt::Windows::UI::Xaml::XamlRoot root, std::wstring dialogTitle); winrt::Windows::Foundation::IAsyncOperation<bool> PartialRemappingConfirmationDialog(winrt::Windows::UI::Xaml::XamlRoot root, std::wstring dialogTitle);
}; };

View File

@@ -13,6 +13,7 @@
#include <keyboardmanager/dll/resource.h> #include <keyboardmanager/dll/resource.h>
#include "../common/shared_constants.h" #include "../common/shared_constants.h"
#include "keyboardmanager/common/KeyboardManagerState.h" #include "keyboardmanager/common/KeyboardManagerState.h"
#include "LoadingAndSavingRemappingHelper.h"
using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation;
@@ -28,36 +29,6 @@ std::mutex editKeyboardWindowMutex;
// Stores a pointer to the Xaml Bridge object so that it can be accessed from the window procedure // Stores a pointer to the Xaml Bridge object so that it can be accessed from the window procedure
static XamlBridge* xamlBridgePtr = nullptr; static XamlBridge* xamlBridgePtr = nullptr;
static std::vector<DWORD> GetOrphanedKeys()
{
std::set<DWORD> ogKeys;
std::set<DWORD> newKeys;
for (int i = 0; i < SingleKeyRemapControl::singleKeyRemapBuffer.size(); i++)
{
DWORD ogKey = std::get<DWORD>(SingleKeyRemapControl::singleKeyRemapBuffer[i].first[0]);
std::variant<DWORD, Shortcut> newKey = SingleKeyRemapControl::singleKeyRemapBuffer[i].first[1];
if (ogKey != NULL && ((newKey.index() == 0 && std::get<DWORD>(newKey) != 0) || (newKey.index() == 1 && std::get<Shortcut>(newKey).IsValidShortcut())))
{
ogKeys.insert(ogKey);
// newKey should be added only if the target is a key
if (SingleKeyRemapControl::singleKeyRemapBuffer[i].first[1].index() == 0)
{
newKeys.insert(std::get<DWORD>(newKey));
}
}
}
for (auto& k : newKeys)
{
ogKeys.erase(k);
}
return std::vector(ogKeys.begin(), ogKeys.end());
}
static IAsyncOperation<bool> OrphanKeysConfirmationDialog( static IAsyncOperation<bool> OrphanKeysConfirmationDialog(
KeyboardManagerState& state, KeyboardManagerState& state,
const std::vector<DWORD>& keys, const std::vector<DWORD>& keys,
@@ -92,7 +63,7 @@ static IAsyncOperation<bool> OrphanKeysConfirmationDialog(
static IAsyncAction OnClickAccept(KeyboardManagerState& keyboardManagerState, XamlRoot root, std::function<void()> ApplyRemappings) static IAsyncAction OnClickAccept(KeyboardManagerState& keyboardManagerState, XamlRoot root, std::function<void()> ApplyRemappings)
{ {
KeyboardManagerHelper::ErrorType isSuccess = Dialog::CheckIfRemappingsAreValid(SingleKeyRemapControl::singleKeyRemapBuffer); KeyboardManagerHelper::ErrorType isSuccess = LoadingAndSavingRemappingHelper::CheckIfRemappingsAreValid(SingleKeyRemapControl::singleKeyRemapBuffer);
if (isSuccess != KeyboardManagerHelper::ErrorType::NoError) if (isSuccess != KeyboardManagerHelper::ErrorType::NoError)
{ {
@@ -104,7 +75,7 @@ static IAsyncAction OnClickAccept(KeyboardManagerState& keyboardManagerState, Xa
// Check for orphaned keys // Check for orphaned keys
// Draw content Dialog // Draw content Dialog
std::vector<DWORD> orphanedKeys = GetOrphanedKeys(); std::vector<DWORD> orphanedKeys = LoadingAndSavingRemappingHelper::GetOrphanedKeys(SingleKeyRemapControl::singleKeyRemapBuffer);
if (orphanedKeys.size() > 0) if (orphanedKeys.size() > 0)
{ {
if (!co_await OrphanKeysConfirmationDialog(keyboardManagerState, orphanedKeys, root)) if (!co_await OrphanKeysConfirmationDialog(keyboardManagerState, orphanedKeys, root))
@@ -115,31 +86,6 @@ static IAsyncAction OnClickAccept(KeyboardManagerState& keyboardManagerState, Xa
ApplyRemappings(); ApplyRemappings();
} }
// Function to combine remappings if the L and R version of the modifier is mapped to the same key
void CombineRemappings(std::unordered_map<DWORD, std::variant<DWORD, Shortcut>>& table, DWORD leftKey, DWORD rightKey, DWORD combinedKey)
{
if (table.find(leftKey) != table.end() && table.find(rightKey) != table.end())
{
// If they are mapped to the same key, delete those entries and set the common version
if (table[leftKey] == table[rightKey])
{
table[combinedKey] = table[leftKey];
table.erase(leftKey);
table.erase(rightKey);
}
}
}
// Function to pre process the remap table before loading it into the UI
void PreProcessRemapTable(std::unordered_map<DWORD, std::variant<DWORD, Shortcut>>& table)
{
// Pre process the table to combine L and R versions of Ctrl/Alt/Shift/Win that are mapped to the same key
CombineRemappings(table, VK_LCONTROL, VK_RCONTROL, VK_CONTROL);
CombineRemappings(table, VK_LMENU, VK_RMENU, VK_MENU);
CombineRemappings(table, VK_LSHIFT, VK_RSHIFT, VK_SHIFT);
CombineRemappings(table, VK_LWIN, VK_RWIN, CommonSharedConstants::VK_WIN_BOTH);
}
// Function to create the Edit Keyboard Window // Function to create the Edit Keyboard Window
void createEditKeyboardWindow(HINSTANCE hInst, KeyboardManagerState& keyboardManagerState) void createEditKeyboardWindow(HINSTANCE hInst, KeyboardManagerState& keyboardManagerState)
{ {
@@ -306,7 +252,7 @@ void createEditKeyboardWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMan
std::unique_lock<std::mutex> lock(keyboardManagerState.singleKeyReMap_mutex); std::unique_lock<std::mutex> lock(keyboardManagerState.singleKeyReMap_mutex);
std::unordered_map<DWORD, std::variant<DWORD, Shortcut>> singleKeyRemapCopy = keyboardManagerState.singleKeyReMap; std::unordered_map<DWORD, std::variant<DWORD, Shortcut>> singleKeyRemapCopy = keyboardManagerState.singleKeyReMap;
lock.unlock(); lock.unlock();
PreProcessRemapTable(singleKeyRemapCopy); LoadingAndSavingRemappingHelper::PreProcessRemapTable(singleKeyRemapCopy);
for (const auto& it : singleKeyRemapCopy) for (const auto& it : singleKeyRemapCopy)
{ {
@@ -323,78 +269,9 @@ void createEditKeyboardWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMan
header.SetLeftOf(applyButton, cancelButton); header.SetLeftOf(applyButton, cancelButton);
auto ApplyRemappings = [&keyboardManagerState, _hWndEditKeyboardWindow]() { auto ApplyRemappings = [&keyboardManagerState, _hWndEditKeyboardWindow]() {
KeyboardManagerHelper::ErrorType isSuccess = KeyboardManagerHelper::ErrorType::NoError; LoadingAndSavingRemappingHelper::ApplySingleKeyRemappings(keyboardManagerState, SingleKeyRemapControl::singleKeyRemapBuffer, true);
// Clear existing Key Remaps
keyboardManagerState.ClearSingleKeyRemaps();
DWORD successfulKeyToKeyRemapCount = 0;
DWORD successfulKeyToShortcutRemapCount = 0;
for (int i = 0; i < SingleKeyRemapControl::singleKeyRemapBuffer.size(); i++)
{
DWORD originalKey = std::get<DWORD>(SingleKeyRemapControl::singleKeyRemapBuffer[i].first[0]);
std::variant<DWORD, Shortcut> newKey = SingleKeyRemapControl::singleKeyRemapBuffer[i].first[1];
if (originalKey != NULL && !(newKey.index() == 0 && std::get<DWORD>(newKey) == NULL) && !(newKey.index() == 1 && !std::get<Shortcut>(newKey).IsValidShortcut()))
{
// If Ctrl/Alt/Shift are added, add their L and R versions instead to the same key
bool result = false;
bool res1, res2;
switch (originalKey)
{
case VK_CONTROL:
res1 = keyboardManagerState.AddSingleKeyRemap(VK_LCONTROL, newKey);
res2 = keyboardManagerState.AddSingleKeyRemap(VK_RCONTROL, newKey);
result = res1 && res2;
break;
case VK_MENU:
res1 = keyboardManagerState.AddSingleKeyRemap(VK_LMENU, newKey);
res2 = keyboardManagerState.AddSingleKeyRemap(VK_RMENU, newKey);
result = res1 && res2;
break;
case VK_SHIFT:
res1 = keyboardManagerState.AddSingleKeyRemap(VK_LSHIFT, newKey);
res2 = keyboardManagerState.AddSingleKeyRemap(VK_RSHIFT, newKey);
result = res1 && res2;
break;
case CommonSharedConstants::VK_WIN_BOTH:
res1 = keyboardManagerState.AddSingleKeyRemap(VK_LWIN, newKey);
res2 = keyboardManagerState.AddSingleKeyRemap(VK_RWIN, newKey);
result = res1 && res2;
break;
default:
result = keyboardManagerState.AddSingleKeyRemap(originalKey, newKey);
}
if (!result)
{
isSuccess = KeyboardManagerHelper::ErrorType::RemapUnsuccessful;
// Tooltip is already shown for this row
}
else
{
if (newKey.index() == 0)
{
successfulKeyToKeyRemapCount += 1;
}
else
{
successfulKeyToShortcutRemapCount += 1;
}
}
}
else
{
isSuccess = KeyboardManagerHelper::ErrorType::RemapUnsuccessful;
}
}
Trace::KeyRemapCount(successfulKeyToKeyRemapCount, successfulKeyToShortcutRemapCount);
// Save the updated shortcuts remaps to file. // Save the updated shortcuts remaps to file.
bool saveResult = keyboardManagerState.SaveConfigToFile(); bool saveResult = keyboardManagerState.SaveConfigToFile();
if (!saveResult)
{
isSuccess = KeyboardManagerHelper::ErrorType::SaveFailed;
}
PostMessage(_hWndEditKeyboardWindow, WM_CLOSE, 0, 0); PostMessage(_hWndEditKeyboardWindow, WM_CLOSE, 0, 0);
}; };

View File

@@ -11,6 +11,7 @@
#include "Dialog.h" #include "Dialog.h"
#include <keyboardmanager/dll/resource.h> #include <keyboardmanager/dll/resource.h>
#include <keyboardmanager/common/KeyboardManagerState.h> #include <keyboardmanager/common/KeyboardManagerState.h>
#include "LoadingAndSavingRemappingHelper.h"
using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation;
@@ -31,7 +32,7 @@ static IAsyncAction OnClickAccept(
XamlRoot root, XamlRoot root,
std::function<void()> ApplyRemappings) std::function<void()> ApplyRemappings)
{ {
KeyboardManagerHelper::ErrorType isSuccess = Dialog::CheckIfRemappingsAreValid(ShortcutControl::shortcutRemapBuffer); KeyboardManagerHelper::ErrorType isSuccess = LoadingAndSavingRemappingHelper::CheckIfRemappingsAreValid(ShortcutControl::shortcutRemapBuffer);
if (isSuccess != KeyboardManagerHelper::ErrorType::NoError) if (isSuccess != KeyboardManagerHelper::ErrorType::NoError)
{ {
@@ -252,78 +253,9 @@ void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMa
header.SetLeftOf(applyButton, cancelButton); header.SetLeftOf(applyButton, cancelButton);
auto ApplyRemappings = [&keyboardManagerState, _hWndEditShortcutsWindow]() { auto ApplyRemappings = [&keyboardManagerState, _hWndEditShortcutsWindow]() {
KeyboardManagerHelper::ErrorType isSuccess = KeyboardManagerHelper::ErrorType::NoError; LoadingAndSavingRemappingHelper::ApplyShortcutRemappings(keyboardManagerState, ShortcutControl::shortcutRemapBuffer, true);
// Clear existing shortcuts
keyboardManagerState.ClearOSLevelShortcuts();
keyboardManagerState.ClearAppSpecificShortcuts();
DWORD successfulOSLevelShortcutToShortcutRemapCount = 0;
DWORD successfulOSLevelShortcutToKeyRemapCount = 0;
DWORD successfulAppSpecificShortcutToShortcutRemapCount = 0;
DWORD successfulAppSpecificShortcutToKeyRemapCount = 0;
// Save the shortcuts that are valid and report if any of them were invalid
for (int i = 0; i < ShortcutControl::shortcutRemapBuffer.size(); i++)
{
Shortcut originalShortcut = std::get<Shortcut>(ShortcutControl::shortcutRemapBuffer[i].first[0]);
std::variant<DWORD, Shortcut> newShortcut = ShortcutControl::shortcutRemapBuffer[i].first[1];
if (originalShortcut.IsValidShortcut() && ((newShortcut.index() == 0 && std::get<DWORD>(newShortcut) != NULL) || (newShortcut.index() == 1 && std::get<Shortcut>(newShortcut).IsValidShortcut())))
{
if (ShortcutControl::shortcutRemapBuffer[i].second == L"")
{
bool result = keyboardManagerState.AddOSLevelShortcut(originalShortcut, newShortcut);
if (!result)
{
isSuccess = KeyboardManagerHelper::ErrorType::RemapUnsuccessful;
}
else
{
if (newShortcut.index() == 0)
{
successfulOSLevelShortcutToKeyRemapCount += 1;
}
else
{
successfulOSLevelShortcutToShortcutRemapCount += 1;
}
}
}
else
{
bool result = keyboardManagerState.AddAppSpecificShortcut(ShortcutControl::shortcutRemapBuffer[i].second, originalShortcut, newShortcut);
if (!result)
{
isSuccess = KeyboardManagerHelper::ErrorType::RemapUnsuccessful;
}
else
{
if (newShortcut.index() == 0)
{
successfulAppSpecificShortcutToKeyRemapCount += 1;
}
else
{
successfulAppSpecificShortcutToShortcutRemapCount += 1;
}
}
}
}
else
{
isSuccess = KeyboardManagerHelper::ErrorType::RemapUnsuccessful;
}
}
// Telemetry events
Trace::OSLevelShortcutRemapCount(successfulOSLevelShortcutToShortcutRemapCount, successfulOSLevelShortcutToKeyRemapCount);
Trace::AppSpecificShortcutRemapCount(successfulAppSpecificShortcutToShortcutRemapCount, successfulAppSpecificShortcutToKeyRemapCount);
// Save the updated key remaps to file. // Save the updated key remaps to file.
bool saveResult = keyboardManagerState.SaveConfigToFile(); bool saveResult = keyboardManagerState.SaveConfigToFile();
if (!saveResult)
{
isSuccess = KeyboardManagerHelper::ErrorType::SaveFailed;
}
PostMessage(_hWndEditShortcutsWindow, WM_CLOSE, 0, 0); PostMessage(_hWndEditShortcutsWindow, WM_CLOSE, 0, 0);
}; };

View File

@@ -2,6 +2,7 @@
#include "KeyDropDownControl.h" #include "KeyDropDownControl.h"
#include "keyboardmanager/common/Helpers.h" #include "keyboardmanager/common/Helpers.h"
#include <keyboardmanager/common/KeyboardManagerState.h> #include <keyboardmanager/common/KeyboardManagerState.h>
#include "BufferValidationHelpers.h"
// Initialized to null // Initialized to null
KeyboardManagerState* KeyDropDownControl::keyboardManagerState = nullptr; KeyboardManagerState* KeyDropDownControl::keyboardManagerState = nullptr;
@@ -53,7 +54,7 @@ void KeyDropDownControl::CheckAndUpdateKeyboardLayout(ComboBox currentDropDown,
} }
// Function to set selection handler for single key remap drop down. Needs to be called after the constructor since the singleKeyControl StackPanel is null if called in the constructor // Function to set selection handler for single key remap drop down. Needs to be called after the constructor since the singleKeyControl StackPanel is null if called in the constructor
void KeyDropDownControl::SetSelectionHandler(Grid& table, StackPanel singleKeyControl, int colIndex, std::vector<std::pair<std::vector<std::variant<DWORD, Shortcut>>, std::wstring>>& singleKeyRemapBuffer) void KeyDropDownControl::SetSelectionHandler(Grid& table, StackPanel singleKeyControl, int colIndex, RemapBuffer& singleKeyRemapBuffer)
{ {
// drop down selection handler // drop down selection handler
auto onSelectionChange = [&, table, singleKeyControl, colIndex](winrt::Windows::Foundation::IInspectable const& sender) { auto onSelectionChange = [&, table, singleKeyControl, colIndex](winrt::Windows::Foundation::IInspectable const& sender) {
@@ -64,62 +65,12 @@ void KeyDropDownControl::SetSelectionHandler(Grid& table, StackPanel singleKeyCo
bool indexFound = table.Children().IndexOf(singleKeyControl, controlIndex); bool indexFound = table.Children().IndexOf(singleKeyControl, controlIndex);
if (indexFound) if (indexFound)
{ {
KeyboardManagerHelper::ErrorType errorType = KeyboardManagerHelper::ErrorType::NoError;
int rowIndex = (controlIndex - KeyboardManagerConstants::RemapTableHeaderCount) / KeyboardManagerConstants::RemapTableColCount; int rowIndex = (controlIndex - KeyboardManagerConstants::RemapTableHeaderCount) / KeyboardManagerConstants::RemapTableColCount;
// Check if the element was not found or the index exceeds the known keys
if (selectedKeyIndex != -1 && keyCodeList.size() > selectedKeyIndex)
{
// Check if the value being set is the same as the other column
if (singleKeyRemapBuffer[rowIndex].first[std::abs(int(colIndex) - 1)].index() == 0)
{
if (std::get<DWORD>(singleKeyRemapBuffer[rowIndex].first[std::abs(int(colIndex) - 1)]) == keyCodeList[selectedKeyIndex])
{
errorType = KeyboardManagerHelper::ErrorType::MapToSameKey;
}
}
// If one column is shortcut and other is key no warning required // Validate current remap selection
KeyboardManagerHelper::ErrorType errorType = BufferValidationHelpers::ValidateAndUpdateKeyBufferElement(rowIndex, colIndex, selectedKeyIndex, keyCodeList, singleKeyRemapBuffer);
if (errorType == KeyboardManagerHelper::ErrorType::NoError && colIndex == 0)
{
// Check if the key is already remapped to something else
for (int i = 0; i < singleKeyRemapBuffer.size(); i++)
{
if (i != rowIndex)
{
if (singleKeyRemapBuffer[i].first[colIndex].index() == 0)
{
KeyboardManagerHelper::ErrorType result = KeyboardManagerHelper::DoKeysOverlap(std::get<DWORD>(singleKeyRemapBuffer[i].first[colIndex]), keyCodeList[selectedKeyIndex]);
if (result != KeyboardManagerHelper::ErrorType::NoError)
{
errorType = result;
break;
}
}
else
{
// check key to shortcut error
}
}
}
}
// If there is no error, set the buffer
if (errorType == KeyboardManagerHelper::ErrorType::NoError)
{
singleKeyRemapBuffer[rowIndex].first[colIndex] = keyCodeList[selectedKeyIndex];
}
else
{
singleKeyRemapBuffer[rowIndex].first[colIndex] = NULL;
}
}
else
{
// Reset to null if the key is not found
singleKeyRemapBuffer[rowIndex].first[colIndex] = NULL;
}
// If there is an error set the warning flyout
if (errorType != KeyboardManagerHelper::ErrorType::NoError) if (errorType != KeyboardManagerHelper::ErrorType::NoError)
{ {
SetDropDownError(currentDropDown, KeyboardManagerHelper::GetErrorMessage(errorType)); SetDropDownError(currentDropDown, KeyboardManagerHelper::GetErrorMessage(errorType));
@@ -142,7 +93,7 @@ void KeyDropDownControl::SetSelectionHandler(Grid& table, StackPanel singleKeyCo
}); });
} }
std::pair<KeyboardManagerHelper::ErrorType, int> KeyDropDownControl::ValidateShortcutSelection(Grid table, StackPanel shortcutControl, StackPanel parent, int colIndex, std::vector<std::pair<std::vector<std::variant<DWORD, Shortcut>>, std::wstring>>& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, TextBox targetApp, bool isHybridControl, bool isSingleKeyWindow) std::pair<KeyboardManagerHelper::ErrorType, int> KeyDropDownControl::ValidateShortcutSelection(Grid table, StackPanel shortcutControl, StackPanel parent, int colIndex, RemapBuffer& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, TextBox targetApp, bool isHybridControl, bool isSingleKeyWindow)
{ {
ComboBox currentDropDown = dropDown.as<ComboBox>(); ComboBox currentDropDown = dropDown.as<ComboBox>();
int selectedKeyIndex = currentDropDown.SelectedIndex(); int selectedKeyIndex = currentDropDown.SelectedIndex();
@@ -151,9 +102,8 @@ std::pair<KeyboardManagerHelper::ErrorType, int> KeyDropDownControl::ValidateSho
// Get row index of the single key control // Get row index of the single key control
uint32_t controlIndex; uint32_t controlIndex;
bool controlIindexFound = table.Children().IndexOf(shortcutControl, controlIndex); bool controlIindexFound = table.Children().IndexOf(shortcutControl, controlIndex);
KeyboardManagerHelper::ErrorType errorType = KeyboardManagerHelper::ErrorType::NoError;
bool IsDeleteDropDownRequired = false;
int rowIndex = -1; int rowIndex = -1;
std::pair<KeyboardManagerHelper::ErrorType, BufferValidationHelpers::DropDownAction> validationResult = std::make_pair(KeyboardManagerHelper::ErrorType::NoError, BufferValidationHelpers::DropDownAction::NoAction);
if (controlIindexFound) if (controlIindexFound)
{ {
@@ -165,242 +115,42 @@ std::pair<KeyboardManagerHelper::ErrorType, int> KeyDropDownControl::ValidateSho
{ {
rowIndex = (controlIndex - KeyboardManagerConstants::ShortcutTableHeaderCount) / KeyboardManagerConstants::ShortcutTableColCount; rowIndex = (controlIndex - KeyboardManagerConstants::ShortcutTableHeaderCount) / KeyboardManagerConstants::ShortcutTableColCount;
} }
if (selectedKeyIndex != -1 && keyCodeList.size() > selectedKeyIndex && dropDownFound)
{
// If only 1 drop down and action key is chosen: Warn that a modifier must be chosen (if the drop down is not for a hybrid scenario)
if (parent.Children().Size() == 1 && !KeyboardManagerHelper::IsModifierKey(keyCodeList[selectedKeyIndex]) && !isHybridControl)
{
// warn and reset the drop down
errorType = KeyboardManagerHelper::ErrorType::ShortcutStartWithModifier;
}
// If it is the last drop down
else if (dropDownIndex == parent.Children().Size() - 1)
{
// If last drop down and a modifier is selected: add a new drop down (max drop down count should be enforced)
if (KeyboardManagerHelper::IsModifierKey(keyCodeList[selectedKeyIndex]) && parent.Children().Size() < KeyboardManagerConstants::MaxShortcutSize)
{
// If it matched any of the previous modifiers then reset that drop down
if (CheckRepeatedModifier(parent, selectedKeyIndex, keyCodeList))
{
// warn and reset the drop down
errorType = KeyboardManagerHelper::ErrorType::ShortcutCannotHaveRepeatedModifier;
}
// If not, add a new drop down
else
{
AddDropDown(table, shortcutControl, parent, colIndex, shortcutRemapBuffer, keyDropDownControlObjects, targetApp, isHybridControl, isSingleKeyWindow);
}
}
// If last drop down and a modifier is selected but there are already max drop downs: warn the user
else if (KeyboardManagerHelper::IsModifierKey(keyCodeList[selectedKeyIndex]) && parent.Children().Size() >= KeyboardManagerConstants::MaxShortcutSize)
{
// warn and reset the drop down
errorType = KeyboardManagerHelper::ErrorType::ShortcutMaxShortcutSizeOneActionKey;
}
// If None is selected but it's the last index: warn
else if (keyCodeList[selectedKeyIndex] == 0)
{
// If it is a hybrid control and there are 2 drop downs then deletion is allowed
if (isHybridControl && parent.Children().Size() == KeyboardManagerConstants::MinShortcutSize)
{
// set delete drop down flag
IsDeleteDropDownRequired = true;
// do not delete the drop down now since there may be some other error which would cause the drop down to be invalid after removal
}
else
{
// warn and reset the drop down
errorType = KeyboardManagerHelper::ErrorType::ShortcutOneActionKey;
}
}
// If none of the above, then the action key will be set
}
// If it is the not the last drop down
else
{
if (KeyboardManagerHelper::IsModifierKey(keyCodeList[selectedKeyIndex]))
{
// If it matched any of the previous modifiers then reset that drop down
if (CheckRepeatedModifier(parent, selectedKeyIndex, keyCodeList))
{
// warn and reset the drop down
errorType = KeyboardManagerHelper::ErrorType::ShortcutCannotHaveRepeatedModifier;
}
// If not, the modifier key will be set
}
// If None is selected and there are more than 2 drop downs
else if (keyCodeList[selectedKeyIndex] == 0 && parent.Children().Size() > KeyboardManagerConstants::MinShortcutSize)
{
// set delete drop down flag
IsDeleteDropDownRequired = true;
// do not delete the drop down now since there may be some other error which would cause the drop down to be invalid after removal
}
else if (keyCodeList[selectedKeyIndex] == 0 && parent.Children().Size() <= KeyboardManagerConstants::MinShortcutSize)
{
// If it is a hybrid control and there are 2 drop downs then deletion is allowed
if (isHybridControl && parent.Children().Size() == KeyboardManagerConstants::MinShortcutSize)
{
// set delete drop down flag
IsDeleteDropDownRequired = true;
// do not delete the drop down now since there may be some other error which would cause the drop down to be invalid after removal
}
else
{
// warn and reset the drop down
errorType = KeyboardManagerHelper::ErrorType::ShortcutAtleast2Keys;
}
}
// If the user tries to set an action key check if all drop down menus after this are empty if it is not the first key. If it is a hybrid control, this can be done even on the first key
else if (dropDownIndex != 0 || isHybridControl)
{
bool isClear = true;
for (int i = dropDownIndex + 1; i < (int)parent.Children().Size(); i++)
{
ComboBox ItDropDown = parent.Children().GetAt(i).as<ComboBox>();
if (ItDropDown.SelectedIndex() != -1)
{
isClear = false;
break;
}
}
if (isClear) std::vector<int32_t> selectedIndices = GetSelectedIndicesFromStackPanel(parent);
{
// remove all the drop down std::wstring appName;
int elementsToBeRemoved = parent.Children().Size() - dropDownIndex - 1; if (targetApp != nullptr)
for (int i = 0; i < elementsToBeRemoved; i++) {
{ appName = targetApp.Text().c_str();
parent.Children().RemoveAtEnd();
keyDropDownControlObjects.erase(keyDropDownControlObjects.end() - 1);
}
parent.UpdateLayout();
}
else
{
// warn and reset the drop down
errorType = KeyboardManagerHelper::ErrorType::ShortcutNotMoreThanOneActionKey;
}
}
// If there an action key is chosen on the first drop down and there are more than one drop down menus
else
{
// warn and reset the drop down
errorType = KeyboardManagerHelper::ErrorType::ShortcutStartWithModifier;
}
}
} }
// After validating the shortcut, now for errors like remap to same shortcut, remap shortcut more than once, Win L and Ctrl Alt Del // Validate shortcut element
if (errorType == KeyboardManagerHelper::ErrorType::NoError) validationResult = BufferValidationHelpers::ValidateShortcutBufferElement(rowIndex, colIndex, dropDownIndex, selectedIndices, appName, isHybridControl, keyCodeList, shortcutRemapBuffer, dropDownFound);
// Add or clear unused drop downs
if (validationResult.second == BufferValidationHelpers::DropDownAction::AddDropDown)
{ {
std::variant<DWORD, Shortcut> tempShortcut; AddDropDown(table, shortcutControl, parent, colIndex, shortcutRemapBuffer, keyDropDownControlObjects, targetApp, isHybridControl, isSingleKeyWindow);
std::vector<DWORD> selectedKeyCodes = GetKeysFromStackPanel(parent); }
if (isHybridControl && selectedKeyCodes.size() == 1) else if (validationResult.second == BufferValidationHelpers::DropDownAction::ClearUnusedDropDowns)
{
// remove all the drop downs after the current index
int elementsToBeRemoved = parent.Children().Size() - dropDownIndex - 1;
for (int i = 0; i < elementsToBeRemoved; i++)
{ {
tempShortcut = selectedKeyCodes[0]; parent.Children().RemoveAtEnd();
} keyDropDownControlObjects.erase(keyDropDownControlObjects.end() - 1);
else
{
tempShortcut = Shortcut();
std::get<Shortcut>(tempShortcut).SetKeyCodes(GetKeysFromStackPanel(parent));
}
std::wstring appName;
if (targetApp != nullptr)
{
appName = targetApp.Text().c_str();
}
// Convert app name to lower case
std::transform(appName.begin(), appName.end(), appName.begin(), towlower);
std::wstring lowercaseDefAppName = KeyboardManagerConstants::DefaultAppName;
std::transform(lowercaseDefAppName.begin(), lowercaseDefAppName.end(), lowercaseDefAppName.begin(), towlower);
if (appName == lowercaseDefAppName)
{
appName = L"";
}
// Check if the value being set is the same as the other column - index of other column does not have to be checked since only one column is hybrid
if (tempShortcut.index() == 1)
{
// If shortcut to shortcut
if (shortcutRemapBuffer[rowIndex].first[std::abs(int(colIndex) - 1)].index() == 1)
{
if (std::get<Shortcut>(shortcutRemapBuffer[rowIndex].first[std::abs(int(colIndex) - 1)]) == std::get<Shortcut>(tempShortcut) && std::get<Shortcut>(shortcutRemapBuffer[rowIndex].first[std::abs(int(colIndex) - 1)]).IsValidShortcut() && std::get<Shortcut>(tempShortcut).IsValidShortcut())
{
errorType = KeyboardManagerHelper::ErrorType::MapToSameShortcut;
}
}
// If one column is shortcut and other is key no warning required
}
else
{
// If key to key
if (shortcutRemapBuffer[rowIndex].first[std::abs(int(colIndex) - 1)].index() == 0)
{
if (std::get<DWORD>(shortcutRemapBuffer[rowIndex].first[std::abs(int(colIndex) - 1)]) == std::get<DWORD>(tempShortcut) && std::get<DWORD>(shortcutRemapBuffer[rowIndex].first[std::abs(int(colIndex) - 1)]) != NULL && std::get<DWORD>(tempShortcut) != NULL)
{
errorType = KeyboardManagerHelper::ErrorType::MapToSameKey;
}
}
// If one column is shortcut and other is key no warning required
}
if (errorType == KeyboardManagerHelper::ErrorType::NoError && colIndex == 0)
{
// Check if the key is already remapped to something else for the same target app
for (int i = 0; i < shortcutRemapBuffer.size(); i++)
{
std::wstring currAppName = shortcutRemapBuffer[i].second;
std::transform(currAppName.begin(), currAppName.end(), currAppName.begin(), towlower);
if (i != rowIndex && currAppName == appName)
{
KeyboardManagerHelper::ErrorType result = KeyboardManagerHelper::ErrorType::NoError;
if (!isHybridControl)
{
result = Shortcut::DoKeysOverlap(std::get<Shortcut>(shortcutRemapBuffer[i].first[colIndex]), std::get<Shortcut>(tempShortcut));
}
else
{
if (tempShortcut.index() == 0 && shortcutRemapBuffer[i].first[colIndex].index() == 0)
{
if (std::get<DWORD>(tempShortcut) != NULL && std::get<DWORD>(shortcutRemapBuffer[i].first[colIndex]) != NULL)
{
result = KeyboardManagerHelper::DoKeysOverlap(std::get<DWORD>(shortcutRemapBuffer[i].first[colIndex]), std::get<DWORD>(tempShortcut));
}
}
else if (tempShortcut.index() == 1 && shortcutRemapBuffer[i].first[colIndex].index() == 1)
{
if (std::get<Shortcut>(tempShortcut).IsValidShortcut() && std::get<Shortcut>(shortcutRemapBuffer[i].first[colIndex]).IsValidShortcut())
{
result = Shortcut::DoKeysOverlap(std::get<Shortcut>(shortcutRemapBuffer[i].first[colIndex]), std::get<Shortcut>(tempShortcut));
}
}
// Other scenarios not possible since key to shortcut is with key to key, and shortcut to key is with shortcut to shortcut
}
if (result != KeyboardManagerHelper::ErrorType::NoError)
{
errorType = result;
break;
}
}
}
}
if (errorType == KeyboardManagerHelper::ErrorType::NoError && tempShortcut.index() == 1)
{
errorType = std::get<Shortcut>(tempShortcut).IsShortcutIllegal();
} }
parent.UpdateLayout();
} }
if (errorType != KeyboardManagerHelper::ErrorType::NoError) if (validationResult.first != KeyboardManagerHelper::ErrorType::NoError)
{ {
SetDropDownError(currentDropDown, KeyboardManagerHelper::GetErrorMessage(errorType)); SetDropDownError(currentDropDown, KeyboardManagerHelper::GetErrorMessage(validationResult.first));
} }
// Handle None case if there are no other errors // Handle None case if there are no other errors
else if (IsDeleteDropDownRequired) else if (validationResult.second == BufferValidationHelpers::DropDownAction::DeleteDropDown)
{ {
parent.Children().RemoveAt(dropDownIndex); parent.Children().RemoveAt(dropDownIndex);
// delete drop down control object from the vector so that it can be destructed // delete drop down control object from the vector so that it can be destructed
@@ -409,11 +159,11 @@ std::pair<KeyboardManagerHelper::ErrorType, int> KeyDropDownControl::ValidateSho
} }
} }
return std::make_pair(errorType, rowIndex); return std::make_pair(validationResult.first, rowIndex);
} }
// Function to set selection handler for shortcut drop down. Needs to be called after the constructor since the shortcutControl StackPanel is null if called in the constructor // Function to set selection handler for shortcut drop down. Needs to be called after the constructor since the shortcutControl StackPanel is null if called in the constructor
void KeyDropDownControl::SetSelectionHandler(Grid& table, StackPanel shortcutControl, StackPanel parent, int colIndex, std::vector<std::pair<std::vector<std::variant<DWORD, Shortcut>>, std::wstring>>& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, TextBox& targetApp, bool isHybridControl, bool isSingleKeyWindow) void KeyDropDownControl::SetSelectionHandler(Grid& table, StackPanel shortcutControl, StackPanel parent, int colIndex, RemapBuffer& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, TextBox& targetApp, bool isHybridControl, bool isSingleKeyWindow)
{ {
auto onSelectionChange = [&, table, shortcutControl, colIndex, parent, targetApp, isHybridControl, isSingleKeyWindow](winrt::Windows::Foundation::IInspectable const& sender) { auto onSelectionChange = [&, table, shortcutControl, colIndex, parent, targetApp, isHybridControl, isSingleKeyWindow](winrt::Windows::Foundation::IInspectable const& sender) {
std::pair<KeyboardManagerHelper::ErrorType, int> validationResult = ValidateShortcutSelection(table, shortcutControl, parent, colIndex, shortcutRemapBuffer, keyDropDownControlObjects, targetApp, isHybridControl, isSingleKeyWindow); std::pair<KeyboardManagerHelper::ErrorType, int> validationResult = ValidateShortcutSelection(table, shortcutControl, parent, colIndex, shortcutRemapBuffer, keyDropDownControlObjects, targetApp, isHybridControl, isSingleKeyWindow);
@@ -428,8 +178,8 @@ void KeyDropDownControl::SetSelectionHandler(Grid& table, StackPanel shortcutCon
ValidateShortcutFromDropDownList(table, shortcutControl, parent, colIndex, shortcutRemapBuffer, keyDropDownControlObjects, targetApp, isHybridControl, isSingleKeyWindow); ValidateShortcutFromDropDownList(table, shortcutControl, parent, colIndex, shortcutRemapBuffer, keyDropDownControlObjects, targetApp, isHybridControl, isSingleKeyWindow);
} }
// Reset the buffer based on the new selected drop down items // Reset the buffer based on the new selected drop down items. Use static key code list since the KeyDropDownControl object might be deleted
std::vector selectedKeyCodes = GetKeysFromStackPanel(parent); std::vector selectedKeyCodes = KeyboardManagerHelper::GetKeyCodesFromSelectedIndices(GetSelectedIndicesFromStackPanel(parent), KeyDropDownControl::keyboardManagerState->keyboardMap.GetKeyCodeList(true));
if (!isHybridControl) if (!isHybridControl)
{ {
std::get<Shortcut>(shortcutRemapBuffer[validationResult.second].first[colIndex]).SetKeyCodes(selectedKeyCodes); std::get<Shortcut>(shortcutRemapBuffer[validationResult.second].first[colIndex]).SetKeyCodes(selectedKeyCodes);
@@ -507,7 +257,7 @@ ComboBox KeyDropDownControl::GetComboBox()
} }
// Function to add a drop down to the shortcut stack panel // Function to add a drop down to the shortcut stack panel
void KeyDropDownControl::AddDropDown(Grid table, StackPanel shortcutControl, StackPanel parent, const int colIndex, std::vector<std::pair<std::vector<std::variant<DWORD, Shortcut>>, std::wstring>>& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, TextBox targetApp, bool isHybridControl, bool isSingleKeyWindow) void KeyDropDownControl::AddDropDown(Grid table, StackPanel shortcutControl, StackPanel parent, const int colIndex, RemapBuffer& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, TextBox targetApp, bool isHybridControl, bool isSingleKeyWindow)
{ {
keyDropDownControlObjects.push_back(std::move(std::unique_ptr<KeyDropDownControl>(new KeyDropDownControl(true)))); keyDropDownControlObjects.push_back(std::move(std::unique_ptr<KeyDropDownControl>(new KeyDropDownControl(true))));
parent.Children().Append(keyDropDownControlObjects[keyDropDownControlObjects.size() - 1]->GetComboBox()); parent.Children().Append(keyDropDownControlObjects[keyDropDownControlObjects.size() - 1]->GetComboBox());
@@ -516,70 +266,28 @@ void KeyDropDownControl::AddDropDown(Grid table, StackPanel shortcutControl, Sta
} }
// Function to get the list of key codes from the shortcut combo box stack panel // Function to get the list of key codes from the shortcut combo box stack panel
std::vector<DWORD> KeyDropDownControl::GetKeysFromStackPanel(StackPanel parent) std::vector<int32_t> KeyDropDownControl::GetSelectedIndicesFromStackPanel(StackPanel parent)
{ {
std::vector<DWORD> keys; std::vector<int32_t> selectedIndices;
std::vector<DWORD> keyCodeList = keyboardManagerState->keyboardMap.GetKeyCodeList(true);
// Get selected indices for each drop down
for (int i = 0; i < (int)parent.Children().Size(); i++) for (int i = 0; i < (int)parent.Children().Size(); i++)
{ {
ComboBox currentDropDown = parent.Children().GetAt(i).as<ComboBox>(); ComboBox ItDropDown = parent.Children().GetAt(i).as<ComboBox>();
int selectedKeyIndex = currentDropDown.SelectedIndex(); selectedIndices.push_back(ItDropDown.SelectedIndex());
if (selectedKeyIndex != -1 && keyCodeList.size() > selectedKeyIndex)
{
// If None is not the selected key
if (keyCodeList[selectedKeyIndex] != 0)
{
keys.push_back(keyCodeList[selectedKeyIndex]);
}
}
} }
return keys; return selectedIndices;
}
// Function to check if a modifier has been repeated in the previous drop downs
bool KeyDropDownControl::CheckRepeatedModifier(StackPanel parent, int selectedKeyIndex, const std::vector<DWORD>& keyCodeList)
{
// check if modifier has already been added before in a previous drop down
std::vector<DWORD> currentKeys = GetKeysFromStackPanel(parent);
int currentDropDownIndex = -1;
// Find the key index of the current drop down selection so that we skip that index while searching for repeated modifiers
for (int i = 0; i < currentKeys.size(); i++)
{
if (currentKeys[i] == keyCodeList[selectedKeyIndex])
{
currentDropDownIndex = i;
break;
}
}
bool matchPreviousModifier = false;
for (int i = 0; i < currentKeys.size(); i++)
{
// Skip the current drop down
if (i != currentDropDownIndex)
{
// If the key type for the newly added key matches any of the existing keys in the shortcut
if (KeyboardManagerHelper::GetKeyType(keyCodeList[selectedKeyIndex]) == KeyboardManagerHelper::GetKeyType(currentKeys[i]))
{
matchPreviousModifier = true;
break;
}
}
}
return matchPreviousModifier;
} }
// Function for validating the selection of shortcuts for all the associated drop downs // Function for validating the selection of shortcuts for all the associated drop downs
void KeyDropDownControl::ValidateShortcutFromDropDownList(Grid table, StackPanel shortcutControl, StackPanel parent, int colIndex, std::vector<std::pair<std::vector<std::variant<DWORD, Shortcut>>, std::wstring>>& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, TextBox targetApp, bool isHybridControl, bool isSingleKeyWindow) void KeyDropDownControl::ValidateShortcutFromDropDownList(Grid table, StackPanel shortcutControl, StackPanel parent, int colIndex, RemapBuffer& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, TextBox targetApp, bool isHybridControl, bool isSingleKeyWindow)
{ {
// Iterate over all drop downs from left to right in that row/col and validate if there is an error in any of the drop downs. After this the state should be error-free (if it is a valid shortcut) // Iterate over all drop downs from left to right in that row/col and validate if there is an error in any of the drop downs. After this the state should be error-free (if it is a valid shortcut)
for (int i = 0; i < keyDropDownControlObjects.size(); i++) for (int i = 0; i < keyDropDownControlObjects.size(); i++)
{ {
// Check for errors only if the current selection is a valid shortcut // Check for errors only if the current selection is a valid shortcut
std::vector<DWORD> selectedKeyCodes = keyDropDownControlObjects[i]->GetKeysFromStackPanel(parent); std::vector<DWORD> selectedKeyCodes = KeyboardManagerHelper::GetKeyCodesFromSelectedIndices(keyDropDownControlObjects[i]->GetSelectedIndicesFromStackPanel(parent), KeyDropDownControl::keyboardManagerState->keyboardMap.GetKeyCodeList(true));
std::variant<DWORD, Shortcut> currentShortcut; std::variant<DWORD, Shortcut> currentShortcut;
if (selectedKeyCodes.size() == 1 && isHybridControl) if (selectedKeyCodes.size() == 1 && isHybridControl)
{ {
@@ -609,7 +317,7 @@ void KeyDropDownControl::SetDropDownError(ComboBox currentDropDown, hstring mess
} }
// Function to add a shortcut to the UI control as combo boxes // Function to add a shortcut to the UI control as combo boxes
void KeyDropDownControl::AddShortcutToControl(Shortcut shortcut, Grid table, StackPanel parent, KeyboardManagerState& keyboardManagerState, const int colIndex, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, std::vector<std::pair<std::vector<std::variant<DWORD, Shortcut>>, std::wstring>>& remapBuffer, StackPanel controlLayout, TextBox targetApp, bool isHybridControl, bool isSingleKeyWindow) void KeyDropDownControl::AddShortcutToControl(Shortcut shortcut, Grid table, StackPanel parent, KeyboardManagerState& keyboardManagerState, const int colIndex, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, RemapBuffer& remapBuffer, StackPanel controlLayout, TextBox targetApp, bool isHybridControl, bool isSingleKeyWindow)
{ {
// Delete the existing drop down menus // Delete the existing drop down menus
parent.Children().Clear(); parent.Children().Clear();

View File

@@ -1,6 +1,5 @@
#pragma once #pragma once
#include <keyboardmanager/common/Shortcut.h> #include <keyboardmanager/common/Shortcut.h>
#include <variant>
class KeyboardManagerState; class KeyboardManagerState;
namespace winrt::Windows namespace winrt::Windows
@@ -57,13 +56,13 @@ public:
} }
// Function to set selection handler for single key remap drop down. Needs to be called after the constructor since the singleKeyControl StackPanel is null if called in the constructor // Function to set selection handler for single key remap drop down. Needs to be called after the constructor since the singleKeyControl StackPanel is null if called in the constructor
void SetSelectionHandler(winrt::Windows::UI::Xaml::Controls::Grid& table, winrt::Windows::UI::Xaml::Controls::StackPanel singleKeyControl, int colIndex, std::vector<std::pair<std::vector<std::variant<DWORD, Shortcut>>, std::wstring>>& singleKeyRemapBuffer); void SetSelectionHandler(winrt::Windows::UI::Xaml::Controls::Grid& table, winrt::Windows::UI::Xaml::Controls::StackPanel singleKeyControl, int colIndex, RemapBuffer& singleKeyRemapBuffer);
// Function for validating the selection of shortcuts for the drop down // Function for validating the selection of shortcuts for the drop down
std::pair<KeyboardManagerHelper::ErrorType, int> ValidateShortcutSelection(winrt::Windows::UI::Xaml::Controls::Grid table, winrt::Windows::UI::Xaml::Controls::StackPanel shortcutControl, winrt::Windows::UI::Xaml::Controls::StackPanel parent, int colIndex, std::vector<std::pair<std::vector<std::variant<DWORD, Shortcut>>, std::wstring>>& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, winrt::Windows::UI::Xaml::Controls::TextBox targetApp, bool isHybridControl, bool isSingleKeyWindow); std::pair<KeyboardManagerHelper::ErrorType, int> ValidateShortcutSelection(winrt::Windows::UI::Xaml::Controls::Grid table, winrt::Windows::UI::Xaml::Controls::StackPanel shortcutControl, winrt::Windows::UI::Xaml::Controls::StackPanel parent, int colIndex, RemapBuffer& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, winrt::Windows::UI::Xaml::Controls::TextBox targetApp, bool isHybridControl, bool isSingleKeyWindow);
// Function to set selection handler for shortcut drop down. Needs to be called after the constructor since the shortcutControl StackPanel is null if called in the constructor // Function to set selection handler for shortcut drop down. Needs to be called after the constructor since the shortcutControl StackPanel is null if called in the constructor
void SetSelectionHandler(winrt::Windows::UI::Xaml::Controls::Grid& table, winrt::Windows::UI::Xaml::Controls::StackPanel shortcutControl, winrt::Windows::UI::Xaml::Controls::StackPanel parent, int colIndex, std::vector<std::pair<std::vector<std::variant<DWORD, Shortcut>>, std::wstring>>& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, winrt::Windows::UI::Xaml::Controls::TextBox& targetApp, bool isHybridControl, bool isSingleKeyWindow); void SetSelectionHandler(winrt::Windows::UI::Xaml::Controls::Grid& table, winrt::Windows::UI::Xaml::Controls::StackPanel shortcutControl, winrt::Windows::UI::Xaml::Controls::StackPanel parent, int colIndex, RemapBuffer& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, winrt::Windows::UI::Xaml::Controls::TextBox& targetApp, bool isHybridControl, bool isSingleKeyWindow);
// Function to set the selected index of the drop down // Function to set the selected index of the drop down
void SetSelectedIndex(int32_t index); void SetSelectedIndex(int32_t index);
@@ -72,20 +71,17 @@ public:
ComboBox GetComboBox(); ComboBox GetComboBox();
// Function to add a drop down to the shortcut stack panel // Function to add a drop down to the shortcut stack panel
static void AddDropDown(winrt::Windows::UI::Xaml::Controls::Grid table, winrt::Windows::UI::Xaml::Controls::StackPanel shortcutControl, winrt::Windows::UI::Xaml::Controls::StackPanel parent, const int colIndex, std::vector<std::pair<std::vector<std::variant<DWORD, Shortcut>>, std::wstring>>& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, winrt::Windows::UI::Xaml::Controls::TextBox targetApp, bool isHybridControl, bool isSingleKeyWindow); static void AddDropDown(winrt::Windows::UI::Xaml::Controls::Grid table, winrt::Windows::UI::Xaml::Controls::StackPanel shortcutControl, winrt::Windows::UI::Xaml::Controls::StackPanel parent, const int colIndex, RemapBuffer& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, winrt::Windows::UI::Xaml::Controls::TextBox targetApp, bool isHybridControl, bool isSingleKeyWindow);
// Function to get the list of key codes from the shortcut combo box stack panel // Function to get the list of key codes from the shortcut combo box stack panel
static std::vector<DWORD> GetKeysFromStackPanel(StackPanel parent); static std::vector<int32_t> GetSelectedIndicesFromStackPanel(StackPanel parent);
// Function to check if a modifier has been repeated in the previous drop downs
static bool CheckRepeatedModifier(winrt::Windows::UI::Xaml::Controls::StackPanel parent, int selectedKeyIndex, const std::vector<DWORD>& keyCodeList);
// Function for validating the selection of shortcuts for all the associated drop downs // Function for validating the selection of shortcuts for all the associated drop downs
static void ValidateShortcutFromDropDownList(Grid table, StackPanel shortcutControl, StackPanel parent, int colIndex, std::vector<std::pair<std::vector<std::variant<DWORD, Shortcut>>, std::wstring>>& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, TextBox targetApp, bool isHybridControl, bool isSingleKeyWindow); static void ValidateShortcutFromDropDownList(Grid table, StackPanel shortcutControl, StackPanel parent, int colIndex, RemapBuffer& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, TextBox targetApp, bool isHybridControl, bool isSingleKeyWindow);
// Function to set the warning message // Function to set the warning message
void SetDropDownError(winrt::Windows::UI::Xaml::Controls::ComboBox currentDropDown, winrt::hstring message); void SetDropDownError(winrt::Windows::UI::Xaml::Controls::ComboBox currentDropDown, winrt::hstring message);
// Function to add a shortcut to the UI control as combo boxes // Function to add a shortcut to the UI control as combo boxes
static void AddShortcutToControl(Shortcut shortcut, Grid table, StackPanel parent, KeyboardManagerState& keyboardManagerState, const int colIndex, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, std::vector<std::pair<std::vector<std::variant<DWORD, Shortcut>>, std::wstring>>& remapBuffer, StackPanel controlLayout, TextBox targetApp, bool isHybridControl, bool isSingleKeyWindow); static void AddShortcutToControl(Shortcut shortcut, Grid table, StackPanel parent, KeyboardManagerState& keyboardManagerState, const int colIndex, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, RemapBuffer& remapBuffer, StackPanel controlLayout, TextBox targetApp, bool isHybridControl, bool isSingleKeyWindow);
}; };

View File

@@ -112,10 +112,12 @@
</ClCompile> </ClCompile>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="BufferValidationHelpers.cpp" />
<ClCompile Include="Dialog.cpp" /> <ClCompile Include="Dialog.cpp" />
<ClCompile Include="EditKeyboardWindow.cpp" /> <ClCompile Include="EditKeyboardWindow.cpp" />
<ClCompile Include="EditShortcutsWindow.cpp" /> <ClCompile Include="EditShortcutsWindow.cpp" />
<ClCompile Include="KeyDropDownControl.cpp" /> <ClCompile Include="KeyDropDownControl.cpp" />
<ClCompile Include="LoadingAndSavingRemappingHelper.cpp" />
<ClCompile Include="pch.cpp"> <ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(CIBuild)'!='true'">Create</PrecompiledHeader> <PrecompiledHeader Condition="'$(CIBuild)'!='true'">Create</PrecompiledHeader>
</ClCompile> </ClCompile>
@@ -125,9 +127,11 @@
<ClCompile Include="XamlBridge.cpp" /> <ClCompile Include="XamlBridge.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="BufferValidationHelpers.h" />
<ClInclude Include="Dialog.h" /> <ClInclude Include="Dialog.h" />
<ClInclude Include="EditKeyboardWindow.h" /> <ClInclude Include="EditKeyboardWindow.h" />
<ClInclude Include="EditShortcutsWindow.h" /> <ClInclude Include="EditShortcutsWindow.h" />
<ClInclude Include="LoadingAndSavingRemappingHelper.h" />
<ClInclude Include="Styles.h" /> <ClInclude Include="Styles.h" />
<ClInclude Include="KeyDropDownControl.h" /> <ClInclude Include="KeyDropDownControl.h" />
<ClInclude Include="pch.h" /> <ClInclude Include="pch.h" />

View File

@@ -28,6 +28,12 @@
<ClCompile Include="XamlBridge.cpp"> <ClCompile Include="XamlBridge.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="LoadingAndSavingRemappingHelper.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="BufferValidationHelpers.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="Dialog.h"> <ClInclude Include="Dialog.h">
@@ -57,6 +63,12 @@
<ClInclude Include="pch.h"> <ClInclude Include="pch.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="LoadingAndSavingRemappingHelper.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="BufferValidationHelpers.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Filter Include="Source Files"> <Filter Include="Source Files">

View File

@@ -0,0 +1,224 @@
#include "pch.h"
#include "LoadingAndSavingRemappingHelper.h"
#include <set>
#include "../common/shared_constants.h"
#include <keyboardmanager/common/KeyboardManagerState.h>
#include <keyboardmanager/common/trace.h>
namespace LoadingAndSavingRemappingHelper
{
// Function to check if the set of remappings in the buffer are valid
KeyboardManagerHelper::ErrorType CheckIfRemappingsAreValid(const RemapBuffer& remappings)
{
KeyboardManagerHelper::ErrorType isSuccess = KeyboardManagerHelper::ErrorType::NoError;
std::map<std::wstring, std::set<std::variant<DWORD, Shortcut>>> ogKeys;
for (int i = 0; i < remappings.size(); i++)
{
std::variant<DWORD, Shortcut> ogKey = remappings[i].first[0];
std::variant<DWORD, Shortcut> newKey = remappings[i].first[1];
std::wstring appName = remappings[i].second;
bool ogKeyValidity = (ogKey.index() == 0 && std::get<DWORD>(ogKey) != NULL) || (ogKey.index() == 1 && std::get<Shortcut>(ogKey).IsValidShortcut());
bool newKeyValidity = (newKey.index() == 0 && std::get<DWORD>(newKey) != NULL) || (newKey.index() == 1 && std::get<Shortcut>(newKey).IsValidShortcut());
// Add new set for a new target app name
if (ogKeys.find(appName) == ogKeys.end())
{
ogKeys[appName] = std::set<std::variant<DWORD, Shortcut>>();
}
if (ogKeyValidity && newKeyValidity && ogKeys[appName].find(ogKey) == ogKeys[appName].end())
{
ogKeys[appName].insert(ogKey);
}
else if (ogKeyValidity && newKeyValidity && ogKeys[appName].find(ogKey) != ogKeys[appName].end())
{
isSuccess = KeyboardManagerHelper::ErrorType::RemapUnsuccessful;
}
else
{
isSuccess = KeyboardManagerHelper::ErrorType::RemapUnsuccessful;
}
}
return isSuccess;
}
// Function to return the set of keys that have been orphaned from the remap buffer
std::vector<DWORD> GetOrphanedKeys(const RemapBuffer& remappings)
{
std::set<DWORD> ogKeys;
std::set<DWORD> newKeys;
for (int i = 0; i < remappings.size(); i++)
{
DWORD ogKey = std::get<DWORD>(remappings[i].first[0]);
std::variant<DWORD, Shortcut> newKey = remappings[i].first[1];
if (ogKey != NULL && ((newKey.index() == 0 && std::get<DWORD>(newKey) != 0) || (newKey.index() == 1 && std::get<Shortcut>(newKey).IsValidShortcut())))
{
ogKeys.insert(ogKey);
// newKey should be added only if the target is a key
if (remappings[i].first[1].index() == 0)
{
newKeys.insert(std::get<DWORD>(newKey));
}
}
}
for (auto& k : newKeys)
{
ogKeys.erase(k);
}
return std::vector(ogKeys.begin(), ogKeys.end());
}
// Function to combine remappings if the L and R version of the modifier is mapped to the same key
void CombineRemappings(std::unordered_map<DWORD, std::variant<DWORD, Shortcut>>& table, DWORD leftKey, DWORD rightKey, DWORD combinedKey)
{
if (table.find(leftKey) != table.end() && table.find(rightKey) != table.end())
{
// If they are mapped to the same key, delete those entries and set the common version
if (table[leftKey] == table[rightKey])
{
table[combinedKey] = table[leftKey];
table.erase(leftKey);
table.erase(rightKey);
}
}
}
// Function to pre process the remap table before loading it into the UI
void PreProcessRemapTable(std::unordered_map<DWORD, std::variant<DWORD, Shortcut>>& table)
{
// Pre process the table to combine L and R versions of Ctrl/Alt/Shift/Win that are mapped to the same key
CombineRemappings(table, VK_LCONTROL, VK_RCONTROL, VK_CONTROL);
CombineRemappings(table, VK_LMENU, VK_RMENU, VK_MENU);
CombineRemappings(table, VK_LSHIFT, VK_RSHIFT, VK_SHIFT);
CombineRemappings(table, VK_LWIN, VK_RWIN, CommonSharedConstants::VK_WIN_BOTH);
}
// Function to apply the single key remappings from the buffer to the KeyboardManagerState variable
void ApplySingleKeyRemappings(KeyboardManagerState& keyboardManagerState, const RemapBuffer& remappings, bool isTelemetryRequired)
{
// Clear existing Key Remaps
keyboardManagerState.ClearSingleKeyRemaps();
DWORD successfulKeyToKeyRemapCount = 0;
DWORD successfulKeyToShortcutRemapCount = 0;
for (int i = 0; i < remappings.size(); i++)
{
DWORD originalKey = std::get<DWORD>(remappings[i].first[0]);
std::variant<DWORD, Shortcut> newKey = remappings[i].first[1];
if (originalKey != NULL && !(newKey.index() == 0 && std::get<DWORD>(newKey) == NULL) && !(newKey.index() == 1 && !std::get<Shortcut>(newKey).IsValidShortcut()))
{
// If Ctrl/Alt/Shift are added, add their L and R versions instead to the same key
bool result = false;
bool res1, res2;
switch (originalKey)
{
case VK_CONTROL:
res1 = keyboardManagerState.AddSingleKeyRemap(VK_LCONTROL, newKey);
res2 = keyboardManagerState.AddSingleKeyRemap(VK_RCONTROL, newKey);
result = res1 && res2;
break;
case VK_MENU:
res1 = keyboardManagerState.AddSingleKeyRemap(VK_LMENU, newKey);
res2 = keyboardManagerState.AddSingleKeyRemap(VK_RMENU, newKey);
result = res1 && res2;
break;
case VK_SHIFT:
res1 = keyboardManagerState.AddSingleKeyRemap(VK_LSHIFT, newKey);
res2 = keyboardManagerState.AddSingleKeyRemap(VK_RSHIFT, newKey);
result = res1 && res2;
break;
case CommonSharedConstants::VK_WIN_BOTH:
res1 = keyboardManagerState.AddSingleKeyRemap(VK_LWIN, newKey);
res2 = keyboardManagerState.AddSingleKeyRemap(VK_RWIN, newKey);
result = res1 && res2;
break;
default:
result = keyboardManagerState.AddSingleKeyRemap(originalKey, newKey);
}
if (result)
{
if (newKey.index() == 0)
{
successfulKeyToKeyRemapCount += 1;
}
else
{
successfulKeyToShortcutRemapCount += 1;
}
}
}
}
// If telemetry is to be logged, log the key remap counts
if (isTelemetryRequired)
{
Trace::KeyRemapCount(successfulKeyToKeyRemapCount, successfulKeyToShortcutRemapCount);
}
}
// Function to apply the shortcut remappings from the buffer to the KeyboardManagerState variable
void ApplyShortcutRemappings(KeyboardManagerState& keyboardManagerState, const RemapBuffer& remappings, bool isTelemetryRequired)
{
// Clear existing shortcuts
keyboardManagerState.ClearOSLevelShortcuts();
keyboardManagerState.ClearAppSpecificShortcuts();
DWORD successfulOSLevelShortcutToShortcutRemapCount = 0;
DWORD successfulOSLevelShortcutToKeyRemapCount = 0;
DWORD successfulAppSpecificShortcutToShortcutRemapCount = 0;
DWORD successfulAppSpecificShortcutToKeyRemapCount = 0;
// Save the shortcuts that are valid and report if any of them were invalid
for (int i = 0; i < remappings.size(); i++)
{
Shortcut originalShortcut = std::get<Shortcut>(remappings[i].first[0]);
std::variant<DWORD, Shortcut> newShortcut = remappings[i].first[1];
if (originalShortcut.IsValidShortcut() && ((newShortcut.index() == 0 && std::get<DWORD>(newShortcut) != NULL) || (newShortcut.index() == 1 && std::get<Shortcut>(newShortcut).IsValidShortcut())))
{
if (remappings[i].second == L"")
{
bool result = keyboardManagerState.AddOSLevelShortcut(originalShortcut, newShortcut);
if (result)
{
if (newShortcut.index() == 0)
{
successfulOSLevelShortcutToKeyRemapCount += 1;
}
else
{
successfulOSLevelShortcutToShortcutRemapCount += 1;
}
}
}
else
{
bool result = keyboardManagerState.AddAppSpecificShortcut(remappings[i].second, originalShortcut, newShortcut);
if (result)
{
if (newShortcut.index() == 0)
{
successfulAppSpecificShortcutToKeyRemapCount += 1;
}
else
{
successfulAppSpecificShortcutToShortcutRemapCount += 1;
}
}
}
}
}
// If telemetry is to be logged, log the shortcut remap counts
if (isTelemetryRequired)
{
Trace::OSLevelShortcutRemapCount(successfulOSLevelShortcutToShortcutRemapCount, successfulOSLevelShortcutToKeyRemapCount);
Trace::AppSpecificShortcutRemapCount(successfulAppSpecificShortcutToShortcutRemapCount, successfulAppSpecificShortcutToKeyRemapCount);
}
}
}

View File

@@ -0,0 +1,27 @@
#pragma once
#include <vector>
#include <keyboardmanager/common/Helpers.h>
#include <variant>
class KeyboardManagerState;
namespace LoadingAndSavingRemappingHelper
{
// Function to check if the set of remappings in the buffer are valid
KeyboardManagerHelper::ErrorType CheckIfRemappingsAreValid(const RemapBuffer& remappings);
// Function to return the set of keys that have been orphaned from the remap buffer
std::vector<DWORD> GetOrphanedKeys(const RemapBuffer& remappings);
// Function to combine remappings if the L and R version of the modifier is mapped to the same key
void CombineRemappings(std::unordered_map<DWORD, std::variant<DWORD, Shortcut>>& table, DWORD leftKey, DWORD rightKey, DWORD combinedKey);
// Function to pre process the remap table before loading it into the UI
void PreProcessRemapTable(std::unordered_map<DWORD, std::variant<DWORD, Shortcut>>& table);
// Function to apply the single key remappings from the buffer to the KeyboardManagerState variable
void ApplySingleKeyRemappings(KeyboardManagerState& keyboardManagerState, const RemapBuffer& remappings, bool isTelemetryRequired);
// Function to apply the shortcut remappings from the buffer to the KeyboardManagerState variable
void ApplyShortcutRemappings(KeyboardManagerState& keyboardManagerState, const RemapBuffer& remappings, bool isTelemetryRequired);
}

View File

@@ -8,7 +8,7 @@
HWND ShortcutControl::EditShortcutsWindowHandle = nullptr; HWND ShortcutControl::EditShortcutsWindowHandle = nullptr;
KeyboardManagerState* ShortcutControl::keyboardManagerState = nullptr; KeyboardManagerState* ShortcutControl::keyboardManagerState = nullptr;
// Initialized as new vector // Initialized as new vector
std::vector<std::pair<std::vector<std::variant<DWORD, Shortcut>>, std::wstring>> ShortcutControl::shortcutRemapBuffer; RemapBuffer ShortcutControl::shortcutRemapBuffer;
ShortcutControl::ShortcutControl(Grid table, const int colIndex, TextBox targetApp) ShortcutControl::ShortcutControl(Grid table, const int colIndex, TextBox targetApp)
{ {
@@ -93,9 +93,9 @@ void ShortcutControl::AddNewShortcutControlRow(Grid& parent, std::vector<std::ve
KeyDropDownControl::ValidateShortcutFromDropDownList(parent, keyboardRemapControlObjects[rowIndex][1]->getShortcutControl(), keyboardRemapControlObjects[rowIndex][1]->shortcutDropDownStackPanel.as<StackPanel>(), 1, ShortcutControl::shortcutRemapBuffer, keyboardRemapControlObjects[rowIndex][1]->keyDropDownControlObjects, targetAppTextBox, true, false); KeyDropDownControl::ValidateShortcutFromDropDownList(parent, keyboardRemapControlObjects[rowIndex][1]->getShortcutControl(), keyboardRemapControlObjects[rowIndex][1]->shortcutDropDownStackPanel.as<StackPanel>(), 1, ShortcutControl::shortcutRemapBuffer, keyboardRemapControlObjects[rowIndex][1]->keyDropDownControlObjects, targetAppTextBox, true, false);
// Reset the buffer based on the selected drop down items // Reset the buffer based on the selected drop down items
std::get<Shortcut>(shortcutRemapBuffer[rowIndex].first[0]).SetKeyCodes(KeyDropDownControl::GetKeysFromStackPanel(keyboardRemapControlObjects[rowIndex][0]->shortcutDropDownStackPanel.as<StackPanel>())); std::get<Shortcut>(shortcutRemapBuffer[rowIndex].first[0]).SetKeyCodes(KeyboardManagerHelper::GetKeyCodesFromSelectedIndices(KeyDropDownControl::GetSelectedIndicesFromStackPanel(keyboardRemapControlObjects[rowIndex][0]->shortcutDropDownStackPanel.as<StackPanel>()), KeyDropDownControl::keyboardManagerState->keyboardMap.GetKeyCodeList(true)));
// second column is a hybrid column // second column is a hybrid column
std::vector<DWORD> selectedKeyCodes = KeyDropDownControl::GetKeysFromStackPanel(keyboardRemapControlObjects[rowIndex][1]->shortcutDropDownStackPanel.as<StackPanel>()); std::vector<DWORD> selectedKeyCodes = KeyboardManagerHelper::GetKeyCodesFromSelectedIndices(KeyDropDownControl::GetSelectedIndicesFromStackPanel(keyboardRemapControlObjects[rowIndex][1]->shortcutDropDownStackPanel.as<StackPanel>()), KeyDropDownControl::keyboardManagerState->keyboardMap.GetKeyCodeList(true));
// If exactly one key is selected consider it to be a key remap // If exactly one key is selected consider it to be a key remap
if (selectedKeyCodes.size() == 1) if (selectedKeyCodes.size() == 1)
@@ -172,7 +172,7 @@ void ShortcutControl::AddNewShortcutControlRow(Grid& parent, std::vector<std::ve
if (originalKeys.IsValidShortcut() && !(newKeys.index() == 0 && std::get<DWORD>(newKeys) == NULL) && !(newKeys.index() == 1 && !std::get<Shortcut>(newKeys).IsValidShortcut())) if (originalKeys.IsValidShortcut() && !(newKeys.index() == 0 && std::get<DWORD>(newKeys) == NULL) && !(newKeys.index() == 1 && !std::get<Shortcut>(newKeys).IsValidShortcut()))
{ {
// change to load app name // change to load app name
shortcutRemapBuffer.push_back(std::make_pair<std::vector<std::variant<DWORD, Shortcut>>, std::wstring>(std::vector<std::variant<DWORD, Shortcut>>{ Shortcut(), Shortcut() }, std::wstring(targetAppName))); shortcutRemapBuffer.push_back(std::make_pair<RemapBufferItem, std::wstring>(RemapBufferItem{ Shortcut(), Shortcut() }, std::wstring(targetAppName)));
KeyDropDownControl::AddShortcutToControl(originalKeys, parent, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][0]->shortcutDropDownStackPanel.as<StackPanel>(), *keyboardManagerState, 0, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][0]->keyDropDownControlObjects, shortcutRemapBuffer, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][0]->shortcutControlLayout.as<StackPanel>(), targetAppTextBox, false, false); KeyDropDownControl::AddShortcutToControl(originalKeys, parent, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][0]->shortcutDropDownStackPanel.as<StackPanel>(), *keyboardManagerState, 0, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][0]->keyDropDownControlObjects, shortcutRemapBuffer, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][0]->shortcutControlLayout.as<StackPanel>(), targetAppTextBox, false, false);
if (newKeys.index() == 0) if (newKeys.index() == 0)
@@ -192,7 +192,7 @@ void ShortcutControl::AddNewShortcutControlRow(Grid& parent, std::vector<std::ve
else else
{ {
// Initialize both shortcuts as empty shortcuts // Initialize both shortcuts as empty shortcuts
shortcutRemapBuffer.push_back(std::make_pair<std::vector<std::variant<DWORD, Shortcut>>, std::wstring>(std::vector<std::variant<DWORD, Shortcut>>{ Shortcut(), Shortcut() }, std::wstring(targetAppName))); shortcutRemapBuffer.push_back(std::make_pair<RemapBufferItem, std::wstring>(RemapBufferItem{ Shortcut(), Shortcut() }, std::wstring(targetAppName)));
} }
} }
@@ -203,7 +203,7 @@ StackPanel ShortcutControl::getShortcutControl()
} }
// Function to create the detect shortcut UI window // Function to create the detect shortcut UI window
void ShortcutControl::createDetectShortcutWindow(winrt::Windows::Foundation::IInspectable const& sender, XamlRoot xamlRoot, KeyboardManagerState& keyboardManagerState, const int colIndex, Grid table, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, StackPanel controlLayout, TextBox targetApp, bool isHybridControl, bool isSingleKeyWindow, HWND parentWindow, std::vector<std::pair<std::vector<std::variant<DWORD, Shortcut>>, std::wstring>>& remapBuffer) void ShortcutControl::createDetectShortcutWindow(winrt::Windows::Foundation::IInspectable const& sender, XamlRoot xamlRoot, KeyboardManagerState& keyboardManagerState, const int colIndex, Grid table, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, StackPanel controlLayout, TextBox targetApp, bool isHybridControl, bool isSingleKeyWindow, HWND parentWindow, RemapBuffer& remapBuffer)
{ {
// ContentDialog for detecting shortcuts. This is the parent UI element. // ContentDialog for detecting shortcuts. This is the parent UI element.
ContentDialog detectShortcutBox; ContentDialog detectShortcutBox;

View File

@@ -33,7 +33,7 @@ public:
// Pointer to the keyboard manager state // Pointer to the keyboard manager state
static KeyboardManagerState* keyboardManagerState; static KeyboardManagerState* keyboardManagerState;
// Stores the current list of remappings // Stores the current list of remappings
static std::vector<std::pair<std::vector<std::variant<DWORD, Shortcut>>, std::wstring>> shortcutRemapBuffer; static RemapBuffer shortcutRemapBuffer;
// Vector to store dynamically allocated KeyDropDownControl objects to avoid early destruction // Vector to store dynamically allocated KeyDropDownControl objects to avoid early destruction
std::vector<std::unique_ptr<KeyDropDownControl>> keyDropDownControlObjects; std::vector<std::unique_ptr<KeyDropDownControl>> keyDropDownControlObjects;
@@ -47,5 +47,5 @@ public:
StackPanel getShortcutControl(); StackPanel getShortcutControl();
// Function to create the detect shortcut UI window // Function to create the detect shortcut UI window
static void createDetectShortcutWindow(winrt::Windows::Foundation::IInspectable const& sender, XamlRoot xamlRoot, KeyboardManagerState& keyboardManagerState, const int colIndex, Grid table, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, StackPanel controlLayout, TextBox targetApp, bool isHybridControl, bool isSingleKeyWindow, HWND parentWindow, std::vector<std::pair<std::vector<std::variant<DWORD, Shortcut>>, std::wstring>>& remapBuffer); static void createDetectShortcutWindow(winrt::Windows::Foundation::IInspectable const& sender, XamlRoot xamlRoot, KeyboardManagerState& keyboardManagerState, const int colIndex, Grid table, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, StackPanel controlLayout, TextBox targetApp, bool isHybridControl, bool isSingleKeyWindow, HWND parentWindow, RemapBuffer& remapBuffer);
}; };

View File

@@ -9,7 +9,7 @@
HWND SingleKeyRemapControl::EditKeyboardWindowHandle = nullptr; HWND SingleKeyRemapControl::EditKeyboardWindowHandle = nullptr;
KeyboardManagerState* SingleKeyRemapControl::keyboardManagerState = nullptr; KeyboardManagerState* SingleKeyRemapControl::keyboardManagerState = nullptr;
// Initialized as new vector // Initialized as new vector
std::vector<std::pair<std::vector<std::variant<DWORD, Shortcut>>, std::wstring>> SingleKeyRemapControl::singleKeyRemapBuffer; RemapBuffer SingleKeyRemapControl::singleKeyRemapBuffer;
SingleKeyRemapControl::SingleKeyRemapControl(Grid table, const int colIndex) SingleKeyRemapControl::SingleKeyRemapControl(Grid table, const int colIndex)
{ {
@@ -93,7 +93,7 @@ void SingleKeyRemapControl::AddNewControlKeyRemapRow(Grid& parent, std::vector<s
// Set the key text if the two keys are not null (i.e. default args) // Set the key text if the two keys are not null (i.e. default args)
if (originalKey != NULL && !(newKey.index() == 0 && std::get<DWORD>(newKey) == NULL) && !(newKey.index() == 1 && !std::get<Shortcut>(newKey).IsValidShortcut())) if (originalKey != NULL && !(newKey.index() == 0 && std::get<DWORD>(newKey) == NULL) && !(newKey.index() == 1 && !std::get<Shortcut>(newKey).IsValidShortcut()))
{ {
singleKeyRemapBuffer.push_back(std::make_pair<std::vector<std::variant<DWORD, Shortcut>>, std::wstring>(std::vector<std::variant<DWORD, Shortcut>>{ originalKey, newKey }, L"")); singleKeyRemapBuffer.push_back(std::make_pair<RemapBufferItem, std::wstring>(RemapBufferItem{ originalKey, newKey }, L""));
std::vector<DWORD> keyCodes = keyboardManagerState->keyboardMap.GetKeyCodeList(); std::vector<DWORD> keyCodes = keyboardManagerState->keyboardMap.GetKeyCodeList();
std::vector<DWORD> shortcutListKeyCodes = keyboardManagerState->keyboardMap.GetKeyCodeList(true); std::vector<DWORD> shortcutListKeyCodes = keyboardManagerState->keyboardMap.GetKeyCodeList(true);
auto it = std::find(keyCodes.begin(), keyCodes.end(), originalKey); auto it = std::find(keyCodes.begin(), keyCodes.end(), originalKey);
@@ -117,7 +117,7 @@ void SingleKeyRemapControl::AddNewControlKeyRemapRow(Grid& parent, std::vector<s
else else
{ {
// Initialize both keys to NULL // Initialize both keys to NULL
singleKeyRemapBuffer.push_back(std::make_pair<std::vector<std::variant<DWORD, Shortcut>>, std::wstring>(std::vector<std::variant<DWORD, Shortcut>>{ NULL, NULL }, L"")); singleKeyRemapBuffer.push_back(std::make_pair<RemapBufferItem, std::wstring>(RemapBufferItem{ NULL, NULL }, L""));
} }
// Delete row button // Delete row button

View File

@@ -1,7 +1,6 @@
#pragma once #pragma once
#include "KeyDropDownControl.h" #include "KeyDropDownControl.h"
#include <keyboardmanager/common/Shortcut.h> #include <keyboardmanager/common/Shortcut.h>
#include <variant>
class KeyboardManagerState; class KeyboardManagerState;
namespace winrt::Windows::UI::Xaml namespace winrt::Windows::UI::Xaml
@@ -34,7 +33,7 @@ public:
// Pointer to the keyboard manager state // Pointer to the keyboard manager state
static KeyboardManagerState* keyboardManagerState; static KeyboardManagerState* keyboardManagerState;
// Stores the current list of remappings // Stores the current list of remappings
static std::vector<std::pair<std::vector<std::variant<DWORD, Shortcut>>, std::wstring>> singleKeyRemapBuffer; static RemapBuffer singleKeyRemapBuffer;
// constructor // constructor
SingleKeyRemapControl(Grid table, const int colIndex); SingleKeyRemapControl(Grid table, const int colIndex);