From 3f25d7ccc8625b57b7e752670ac47a1bd2cfc95b Mon Sep 17 00:00:00 2001 From: Mykhailo Pylyp Date: Fri, 2 Oct 2020 15:36:36 +0300 Subject: [PATCH] Keyboard disable (#6874) Added disable key/shortcut functionality --- src/common/keyboard_layout.cpp | 1 + src/common/shared_constants.h | 5 +- .../keyboardmanager/common/Helpers.cpp | 2 + src/modules/keyboardmanager/common/Helpers.h | 3 +- .../dll/KeyboardEventHandlers.cpp | 93 +++++++++++++++++-- .../keyboardmanager/dll/Resources.resx | 3 + .../AppSpecificShortcutRemappingTests.cpp | 30 ++++++ .../test/BufferValidationTests.cpp | 21 +++++ .../test/OSLevelShortcutRemappingTests.cpp | 83 +++++++++++++++++ .../test/SingleKeyRemappingTests.cpp | 4 +- .../ui/BufferValidationHelpers.cpp | 13 ++- .../keyboardmanager/ui/KeyDropDownControl.cpp | 48 +++++++--- .../keyboardmanager/ui/KeyDropDownControl.h | 16 +++- .../keyboardmanager/ui/ShortcutControl.cpp | 7 +- .../ui/SingleKeyRemapControl.cpp | 3 +- 15 files changed, 298 insertions(+), 34 deletions(-) diff --git a/src/common/keyboard_layout.cpp b/src/common/keyboard_layout.cpp index 16b681078d..2084aba8e1 100644 --- a/src/common/keyboard_layout.cpp +++ b/src/common/keyboard_layout.cpp @@ -215,6 +215,7 @@ void LayoutMap::LayoutMapImpl::UpdateLayout() keyboardLayoutMap[VK_NONCONVERT] = L"IME Non-Convert"; keyboardLayoutMap[VK_ACCEPT] = L"IME Kana"; keyboardLayoutMap[VK_MODECHANGE] = L"IME Mode Change"; + keyboardLayoutMap[CommonSharedConstants::VK_DISABLED] = L"Disable"; } // Function to return the list of key codes in the order for the drop down. It creates it if it doesn't exist diff --git a/src/common/shared_constants.h b/src/common/shared_constants.h index 69f5499454..196901394c 100644 --- a/src/common/shared_constants.h +++ b/src/common/shared_constants.h @@ -11,4 +11,7 @@ namespace CommonSharedConstants // Path to the event used by PowerLauncher const wchar_t POWER_LAUNCHER_SHARED_EVENT[] = L"Local\\PowerToysRunInvokeEvent-30f26ad7-d36d-4c0e-ab02-68bb5ff3c4ab"; -} + + // Max DWORD for key code to disable keys. + const DWORD VK_DISABLED = 0x100; +} \ No newline at end of file diff --git a/src/modules/keyboardmanager/common/Helpers.cpp b/src/modules/keyboardmanager/common/Helpers.cpp index 40f03db1c0..0087a54a9b 100644 --- a/src/modules/keyboardmanager/common/Helpers.cpp +++ b/src/modules/keyboardmanager/common/Helpers.cpp @@ -173,6 +173,8 @@ namespace KeyboardManagerHelper return GET_RESOURCE_STRING(IDS_ERRORMESSAGE_SHORTCUTMAXONEACTIONKEY).c_str(); case ErrorType::ShortcutMaxShortcutSizeOneActionKey: return GET_RESOURCE_STRING(IDS_ERRORMESSAGE_MAXSHORTCUTSIZE).c_str(); + case ErrorType::ShortcutDisableAsActionKey: + return GET_RESOURCE_STRING(IDS_ERRORMESSAGE_DISABLEASACTIONKEY).c_str(); default: return GET_RESOURCE_STRING(IDS_ERRORMESSAGE_DEFAULT).c_str(); } diff --git a/src/modules/keyboardmanager/common/Helpers.h b/src/modules/keyboardmanager/common/Helpers.h index a81c218eb8..e2183fd168 100644 --- a/src/modules/keyboardmanager/common/Helpers.h +++ b/src/modules/keyboardmanager/common/Helpers.h @@ -48,7 +48,8 @@ namespace KeyboardManagerHelper ShortcutAtleast2Keys, ShortcutOneActionKey, ShortcutNotMoreThanOneActionKey, - ShortcutMaxShortcutSizeOneActionKey + ShortcutMaxShortcutSizeOneActionKey, + ShortcutDisableAsActionKey }; // Enum type to store possible decision for input in the low level hook diff --git a/src/modules/keyboardmanager/dll/KeyboardEventHandlers.cpp b/src/modules/keyboardmanager/dll/KeyboardEventHandlers.cpp index d8b985b2b3..e09dc133a7 100644 --- a/src/modules/keyboardmanager/dll/KeyboardEventHandlers.cpp +++ b/src/modules/keyboardmanager/dll/KeyboardEventHandlers.cpp @@ -24,10 +24,10 @@ namespace KeyboardEventHandlers // Check if the remap is to a key or a shortcut bool remapToKey = (it->second.index() == 0); - // If mapped to 0x0 then the key is disabled + // If mapped to VK_DISABLED then the key is disabled if (remapToKey) { - if (std::get(it->second) == 0x0) + if (std::get(it->second) == CommonSharedConstants::VK_DISABLED) { return 1; } @@ -214,7 +214,7 @@ namespace KeyboardEventHandlers if (data->lParam->vkCode == it->first.GetActionKey() && (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN)) { // Check if any other keys have been pressed apart from the shortcut. If true, then check for the next shortcut. This is to be done only for shortcut to shortcut remaps - if (!it->first.IsKeyboardStateClearExceptShortcut(ii) && remapToShortcut) + if (!it->first.IsKeyboardStateClearExceptShortcut(ii) && (remapToShortcut || std::get(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED)) { continue; } @@ -284,6 +284,12 @@ namespace KeyboardEventHandlers { // Dummy key, key up for all the original shortcut modifier keys and key down for remapped key key_count = 1 + (src_size - 1) + dest_size; + // Do not send Disable key + if (std::get(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED) + { + key_count--; + } + keyEventList = new INPUT[key_count](); memset(keyEventList, 0, sizeof(keyEventList)); @@ -296,8 +302,11 @@ namespace KeyboardEventHandlers KeyboardManagerHelper::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, i, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); // Set target key down state - KeyboardManagerHelper::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, (WORD)KeyboardManagerHelper::FilterArtificialKeys(std::get(it->second.targetShortcut)), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); - i++; + if (std::get(it->second.targetShortcut) != CommonSharedConstants::VK_DISABLED) + { + KeyboardManagerHelper::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, (WORD)KeyboardManagerHelper::FilterArtificialKeys(std::get(it->second.targetShortcut)), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); + i++; + } // Modifier state reset might be required for this key depending on the shortcut's action and target modifier - ex: Win+Caps -> Ctrl if (it->first.GetCtrlKey() == NULL && it->first.GetAltKey() == NULL && it->first.GetShiftKey() == NULL) @@ -382,13 +391,22 @@ namespace KeyboardEventHandlers { // 1 for releasing new key and original shortcut modifiers except the one released key_count = dest_size + src_size - 2; + // Do not send Disable key up + if (std::get(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED) + { + key_count--; + } + keyEventList = new INPUT[key_count](); memset(keyEventList, 0, sizeof(keyEventList)); // Release new key state int i = 0; - KeyboardManagerHelper::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, (WORD)KeyboardManagerHelper::FilterArtificialKeys(std::get(it->second.targetShortcut)), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); - i++; + if (std::get(it->second.targetShortcut) != CommonSharedConstants::VK_DISABLED) + { + KeyboardManagerHelper::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, (WORD)KeyboardManagerHelper::FilterArtificialKeys(std::get(it->second.targetShortcut)), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); + i++; + } // Set original shortcut key down state except the action key and the released modifier since the original action key may or may not be held down. If it is held down it will generate it's own key message KeyboardManagerHelper::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, i, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, Shortcut(), data->lParam->vkCode); @@ -418,6 +436,12 @@ namespace KeyboardEventHandlers // Case 2: If the original shortcut is still held down the keyboard will get a key down message of the action key in the original shortcut and the new shortcut's modifiers will be held down (keys held down send repeated keydown messages) if (data->lParam->vkCode == it->first.GetActionKey() && (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN)) { + // In case of mapping to disable do not send anything + if (!remapToShortcut && std::get(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED) + { + return 1; + } + size_t key_count = 1; LPINPUT keyEventList = new INPUT[key_count](); memset(keyEventList, 0, sizeof(keyEventList)); @@ -455,13 +479,23 @@ namespace KeyboardEventHandlers { // 1 for releasing new key and original shortcut modifiers, and dummy key key_count = dest_size + src_size; + // Do not send Disable key + if (std::get(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED) + { + key_count--; + } + keyEventList = new INPUT[key_count](); memset(keyEventList, 0, sizeof(keyEventList)); // Release new key state int i = 0; - KeyboardManagerHelper::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, (WORD)KeyboardManagerHelper::FilterArtificialKeys(std::get(it->second.targetShortcut)), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); - i++; + // Do not send Disable key + if (std::get(it->second.targetShortcut) != CommonSharedConstants::VK_DISABLED) + { + KeyboardManagerHelper::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, (WORD)KeyboardManagerHelper::FilterArtificialKeys(std::get(it->second.targetShortcut)), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); + i++; + } // Set original shortcut key down state except the action key and the released modifier KeyboardManagerHelper::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, i, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); @@ -499,6 +533,10 @@ namespace KeyboardEventHandlers return 1; } } + else if (std::get(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED) + { + return 1; + } } // Case 5: If any key apart from the action key or a modifier key in the original shortcut is pressed then revert the keyboard state to just the original modifiers being held down along with the current key press @@ -598,6 +636,43 @@ namespace KeyboardEventHandlers i++; } + it->second.isShortcutInvoked = false; + it->second.winKeyInvoked = ModifierKey::Disabled; + // If app specific shortcut has finished invoking, reset the target application + if (activatedApp != KeyboardManagerConstants::NoActivatedApp) + { + keyboardManagerState.SetActivatedApp(KeyboardManagerConstants::NoActivatedApp); + } + lock.unlock(); + UINT res = ii.SendVirtualInput((UINT)key_count, keyEventList, sizeof(INPUT)); + delete[] keyEventList; + return 1; + } + // All modifier keys and action key will be pressed down because if they are not pressed that means that handler has already been invoked on key release + else if (std::get(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED) + { + // Key down for original shortcut modifiers and action key, dummy key, and current key press + size_t key_count = src_size + 1 + 1; + + LPINPUT keyEventList = new INPUT[key_count](); + memset(keyEventList, 0, sizeof(keyEventList)); + + // Set old shortcut key down state + int i = 0; + KeyboardManagerHelper::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, i, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); + + // Set old action key + KeyboardManagerHelper::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, (WORD)it->first.GetActionKey(), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); + i++; + + // Send current key pressed without shortcut flag so that it can be reprocessed in case the physical keys pressed are a different remapped shortcut + KeyboardManagerHelper::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, (WORD)data->lParam->vkCode, 0, 0); + i++; + + // Send dummy key + KeyboardManagerHelper::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, (WORD)KeyboardManagerConstants::DUMMY_KEY, KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); + i++; + it->second.isShortcutInvoked = false; it->second.winKeyInvoked = ModifierKey::Disabled; // If app specific shortcut has finished invoking, reset the target application diff --git a/src/modules/keyboardmanager/dll/Resources.resx b/src/modules/keyboardmanager/dll/Resources.resx index 2113b8b34e..a226598fa0 100644 --- a/src/modules/keyboardmanager/dll/Resources.resx +++ b/src/modules/keyboardmanager/dll/Resources.resx @@ -279,4 +279,7 @@ Row + + Disable can not be an action or a modifier key + \ No newline at end of file diff --git a/src/modules/keyboardmanager/test/AppSpecificShortcutRemappingTests.cpp b/src/modules/keyboardmanager/test/AppSpecificShortcutRemappingTests.cpp index 28228b8cf5..ee71909d63 100644 --- a/src/modules/keyboardmanager/test/AppSpecificShortcutRemappingTests.cpp +++ b/src/modules/keyboardmanager/test/AppSpecificShortcutRemappingTests.cpp @@ -4,12 +4,14 @@ #include #include #include "TestHelpers.h" +#include using namespace Microsoft::VisualStudio::CppUnitTestFramework; namespace RemappingLogicTests { TEST_CLASS (AppSpecificShortcutRemappingTests) + { private: MockedInput mockedInputHandler; @@ -312,5 +314,33 @@ namespace RemappingLogicTests Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), false); Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x56), false); } + + // Disable app specific shortcut + TEST_METHOD (AppSpecificShortcutToDisable_ShouldDisable_WhenAppIsOnForeground) + { + Shortcut src; + src.SetKey(VK_CONTROL); + WORD actionKey = 0x41; + src.SetKey(actionKey); + WORD disableKey = CommonSharedConstants::VK_DISABLED; + testState.AddAppSpecificShortcut(testApp1, src, disableKey); + + // Set the testApp as the foreground process + mockedInputHandler.SetForegroundProcess(testApp1); + + const int nInputs = 2; + INPUT input[nInputs] = {}; + input[0].type = INPUT_KEYBOARD; + input[0].ki.wVk = VK_CONTROL; + input[1].type = INPUT_KEYBOARD; + input[1].ki.wVk = actionKey; + + // Send Ctrl+A keydown + mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT)); + + // Check if Ctrl+A is released and disable key was not send + Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_CONTROL), false); + Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(actionKey), false); + } }; } diff --git a/src/modules/keyboardmanager/test/BufferValidationTests.cpp b/src/modules/keyboardmanager/test/BufferValidationTests.cpp index 6a8529bae1..161cf8f8bb 100644 --- a/src/modules/keyboardmanager/test/BufferValidationTests.cpp +++ b/src/modules/keyboardmanager/test/BufferValidationTests.cpp @@ -1438,5 +1438,26 @@ namespace RemappingUITests Assert::AreEqual(true, result.first == KeyboardManagerHelper::ErrorType::NoError); }); } + + // Return error on Disable as second modifier key or action key + TEST_METHOD (ValidateShortcutBufferElement_ShouldReturnDisableAsActionKeyError_OnSettingSecondDropdownAsDisable) + { + std::vector keyList = keyboardLayout.GetKeyCodeList(true); + keyList.insert(keyList.begin(), CommonSharedConstants::VK_DISABLED); + // Arrange + RemapBuffer remapBuffer; + remapBuffer.push_back(std::make_pair(RemapBufferItem{ std::vector{ VK_SHIFT, CommonSharedConstants::VK_DISABLED }, Shortcut() }, testApp1)); + std::vector selectedIndices = { + GetDropDownIndexFromDropDownList(VK_SHIFT, keyList), + GetDropDownIndexFromDropDownList(CommonSharedConstants::VK_DISABLED, keyList) + }; + + // Act + std::pair result = BufferValidationHelpers::ValidateShortcutBufferElement(0, 1, 1, selectedIndices, testApp1, true, keyList, remapBuffer, true); + + // Assert + Assert::AreEqual(true, result.first == KeyboardManagerHelper::ErrorType::ShortcutDisableAsActionKey); + Assert::AreEqual(true, result.second == BufferValidationHelpers::DropDownAction::NoAction); + } }; } diff --git a/src/modules/keyboardmanager/test/OSLevelShortcutRemappingTests.cpp b/src/modules/keyboardmanager/test/OSLevelShortcutRemappingTests.cpp index 140137b1f5..fa98385762 100644 --- a/src/modules/keyboardmanager/test/OSLevelShortcutRemappingTests.cpp +++ b/src/modules/keyboardmanager/test/OSLevelShortcutRemappingTests.cpp @@ -2004,5 +2004,88 @@ namespace RemappingLogicTests // Shortcut invoked state should be true Assert::AreEqual(true, testState.osLevelShortcutReMap[src].isShortcutInvoked); } + + TEST_METHOD (ShortcutDisable_ShouldDisableShortcut_OnExactMatch) + { + Shortcut src; + src.SetKey(VK_CONTROL); + WORD actionKey = 0x41; + src.SetKey(actionKey); + WORD disableKey = CommonSharedConstants::VK_DISABLED; + + testState.AddOSLevelShortcut(src, disableKey); + + const int nInputs = 2; + INPUT input[nInputs] = {}; + input[0].type = INPUT_KEYBOARD; + input[0].ki.wVk = VK_CONTROL; + input[1].type = INPUT_KEYBOARD; + input[1].ki.wVk = actionKey; + + // send Ctrl+A + mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT)); + + // Check that Ctrl+A was released and Disable key was not sent + Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_CONTROL), false); + Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(actionKey), false); + } + + TEST_METHOD (ShortcutDisable_ShouldNotDisableShortcut_OnSubsetMatch) + { + Shortcut src; + src.SetKey(VK_CONTROL); + WORD actionKey = 0x41; + src.SetKey(actionKey); + WORD disableKey = CommonSharedConstants::VK_DISABLED; + + testState.AddOSLevelShortcut(src, disableKey); + + const int nInputs = 3; + INPUT input[nInputs] = {}; + input[0].type = INPUT_KEYBOARD; + input[0].ki.wVk = VK_CONTROL; + input[1].type = INPUT_KEYBOARD; + input[1].ki.wVk = VK_SHIFT; + input[2].type = INPUT_KEYBOARD; + input[2].ki.wVk = actionKey; + + // send Ctrl+Shift+A + mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT)); + + // Check that Ctrl+A was not released and Disable key was not sent + Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_CONTROL), true); + Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(actionKey), true); + } + + TEST_METHOD (ShortcutDisable_ShouldNotDisableShortcutSuperset_AfterShorcutWasDisabled) + { + Shortcut src; + src.SetKey(VK_CONTROL); + WORD actionKey = 0x41; + src.SetKey(actionKey); + WORD disableKey = CommonSharedConstants::VK_DISABLED; + + testState.AddOSLevelShortcut(src, disableKey); + + const int nInputs = 2; + INPUT input[nInputs] = {}; + input[0].type = INPUT_KEYBOARD; + input[0].ki.wVk = VK_CONTROL; + input[1].type = INPUT_KEYBOARD; + input[1].ki.wVk = actionKey; + + // send Ctrl+A + mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT)); + + input[0].type = INPUT_KEYBOARD; + input[0].ki.wVk = 0x42; + // send B + mockedInputHandler.SendVirtualInput(1, input, sizeof(INPUT)); + + // Check that Ctrl+A+B was pressed + Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_CONTROL), true); + Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(actionKey), true); + Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x42), true); + } }; } diff --git a/src/modules/keyboardmanager/test/SingleKeyRemappingTests.cpp b/src/modules/keyboardmanager/test/SingleKeyRemappingTests.cpp index 700266d2d2..1c713afeb8 100644 --- a/src/modules/keyboardmanager/test/SingleKeyRemappingTests.cpp +++ b/src/modules/keyboardmanager/test/SingleKeyRemappingTests.cpp @@ -58,8 +58,8 @@ namespace RemappingLogicTests // Test if key is suppressed if a key is disabled by single key remap TEST_METHOD (RemappedKeyDisabled_ShouldNotChangeKeyState_OnKeyEvent) { - // Remap A to 0x0 (disabled) - testState.AddSingleKeyRemap(0x41, 0x0); + // Remap A to VK_DISABLE (disabled) + testState.AddSingleKeyRemap(0x41, CommonSharedConstants::VK_DISABLED); const int nInputs = 1; INPUT input[nInputs] = {}; diff --git a/src/modules/keyboardmanager/ui/BufferValidationHelpers.cpp b/src/modules/keyboardmanager/ui/BufferValidationHelpers.cpp index 1a8ff75ffe..a8ead4fc9f 100644 --- a/src/modules/keyboardmanager/ui/BufferValidationHelpers.cpp +++ b/src/modules/keyboardmanager/ui/BufferValidationHelpers.cpp @@ -1,6 +1,7 @@ #include "pch.h" #include "BufferValidationHelpers.h" #include +#include namespace BufferValidationHelpers { @@ -121,9 +122,14 @@ namespace BufferValidationHelpers errorType = KeyboardManagerHelper::ErrorType::ShortcutOneActionKey; } } + // Disable can not be selected if one modifier key has already been selected + else if (keyCodeList[selectedKeyIndex] == CommonSharedConstants::VK_DISABLED && dropDownIndex) + { + errorType = KeyboardManagerHelper::ErrorType::ShortcutDisableAsActionKey; + } // If none of the above, then the action key will be set } - // If it is the not the last drop down + // If it is not the last drop down else { if (KeyboardManagerHelper::IsModifierKey(keyCodeList[selectedKeyIndex])) @@ -158,6 +164,11 @@ namespace BufferValidationHelpers errorType = KeyboardManagerHelper::ErrorType::ShortcutAtleast2Keys; } } + // Allow selection of VK_DISABLE only in first dropdown + else if (keyCodeList[selectedKeyIndex] == CommonSharedConstants::VK_DISABLED && dropDownIndex) + { + errorType = KeyboardManagerHelper::ErrorType::ShortcutDisableAsActionKey; + } // 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) { diff --git a/src/modules/keyboardmanager/ui/KeyDropDownControl.cpp b/src/modules/keyboardmanager/ui/KeyDropDownControl.cpp index 6b42ae929a..ac4add0252 100644 --- a/src/modules/keyboardmanager/ui/KeyDropDownControl.cpp +++ b/src/modules/keyboardmanager/ui/KeyDropDownControl.cpp @@ -3,12 +3,38 @@ #include "keyboardmanager/common/Helpers.h" #include #include "BufferValidationHelpers.h" +#include +#include // Initialized to null KeyboardManagerState* KeyDropDownControl::keyboardManagerState = nullptr; +// Get keys code list depending if Disable is in dropdown +std::vector KeyDropDownControl::GetKeyCodeList(bool isShortcut, bool renderDisable) +{ + auto list = keyboardManagerState->keyboardMap.GetKeyCodeList(isShortcut); + if (renderDisable) + { + list.insert(list.begin(), CommonSharedConstants::VK_DISABLED); + } + + return list; +} + +// Get keys name list depending if Disable is in dropdown +std::vector KeyDropDownControl::GetKeyNameList(bool isShortcut, bool renderDisable) +{ + auto list = keyboardManagerState->keyboardMap.GetKeyNameList(isShortcut); + if (renderDisable) + { + list.insert(list.begin(), keyboardManagerState->keyboardMap.GetKeyName(CommonSharedConstants::VK_DISABLED)); + } + + return list; +} + // Function to set properties apart from the SelectionChanged event handler -void KeyDropDownControl::SetDefaultProperties(bool isShortcut) +void KeyDropDownControl::SetDefaultProperties(bool isShortcut, bool renderDisable) { dropDown = ComboBox(); warningFlyout = Flyout(); @@ -25,12 +51,12 @@ void KeyDropDownControl::SetDefaultProperties(bool isShortcut) dropDown.as().MaxDropDownHeight(KeyboardManagerConstants::TableDropDownHeight); // Initialise layout attribute previousLayout = GetKeyboardLayout(0); - keyCodeList = keyboardManagerState->keyboardMap.GetKeyCodeList(isShortcut); - dropDown.as().ItemsSource(KeyboardManagerHelper::ToBoxValue(keyboardManagerState->keyboardMap.GetKeyNameList(isShortcut))); + keyCodeList = GetKeyCodeList(isShortcut, renderDisable); + dropDown.as().ItemsSource(KeyboardManagerHelper::ToBoxValue(GetKeyNameList(isShortcut, renderDisable))); // drop down open handler - to reload the items with the latest layout dropDown.as().DropDownOpened([&, isShortcut](winrt::Windows::Foundation::IInspectable const& sender, auto args) { ComboBox currentDropDown = sender.as(); - CheckAndUpdateKeyboardLayout(currentDropDown, isShortcut); + CheckAndUpdateKeyboardLayout(currentDropDown, isShortcut, renderDisable); }); // Attach flyout to the drop down @@ -48,7 +74,7 @@ void KeyDropDownControl::SetAccessibleNameForComboBox(ComboBox dropDown, int ind } // Function to check if the layout has changed and accordingly update the drop down list -void KeyDropDownControl::CheckAndUpdateKeyboardLayout(ComboBox currentDropDown, bool isShortcut) +void KeyDropDownControl::CheckAndUpdateKeyboardLayout(ComboBox currentDropDown, bool isShortcut, bool renderDisable) { // Get keyboard layout for current thread HKL layout = GetKeyboardLayout(0); @@ -56,8 +82,8 @@ void KeyDropDownControl::CheckAndUpdateKeyboardLayout(ComboBox currentDropDown, // Check if the layout has changed if (previousLayout != layout) { - keyCodeList = keyboardManagerState->keyboardMap.GetKeyCodeList(isShortcut); - currentDropDown.ItemsSource(KeyboardManagerHelper::ToBoxValue(keyboardManagerState->keyboardMap.GetKeyNameList(isShortcut))); + keyCodeList = GetKeyCodeList(isShortcut, renderDisable); + currentDropDown.ItemsSource(KeyboardManagerHelper::ToBoxValue(GetKeyNameList(isShortcut, renderDisable))); previousLayout = layout; } } @@ -203,7 +229,7 @@ void KeyDropDownControl::SetSelectionHandler(Grid& table, StackPanel shortcutCon } // 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 = KeyboardManagerHelper::GetKeyCodesFromSelectedIndices(GetSelectedIndicesFromStackPanel(parent), KeyDropDownControl::keyboardManagerState->keyboardMap.GetKeyCodeList(true)); + std::vector selectedKeyCodes = KeyboardManagerHelper::GetKeyCodesFromSelectedIndices(GetSelectedIndicesFromStackPanel(parent), GetKeyCodeList(true, colIndex == 1)); if (!isHybridControl) { std::get(shortcutRemapBuffer[validationResult.second].first[colIndex]).SetKeyCodes(selectedKeyCodes); @@ -283,7 +309,7 @@ ComboBox KeyDropDownControl::GetComboBox() // Function to add a drop down to the shortcut stack panel void KeyDropDownControl::AddDropDown(Grid table, StackPanel shortcutControl, StackPanel parent, const int colIndex, RemapBuffer& shortcutRemapBuffer, std::vector>& keyDropDownControlObjects, TextBox targetApp, bool isHybridControl, bool isSingleKeyWindow, bool ignoreWarning) { - keyDropDownControlObjects.push_back(std::move(std::unique_ptr(new KeyDropDownControl(true, ignoreWarning)))); + keyDropDownControlObjects.push_back(std::move(std::unique_ptr(new KeyDropDownControl(true, ignoreWarning, colIndex == 1)))); parent.Children().Append(keyDropDownControlObjects[keyDropDownControlObjects.size() - 1]->GetComboBox()); keyDropDownControlObjects[keyDropDownControlObjects.size() - 1]->SetSelectionHandler(table, shortcutControl, parent, colIndex, shortcutRemapBuffer, keyDropDownControlObjects, targetApp, isHybridControl, isSingleKeyWindow); parent.UpdateLayout(); @@ -314,7 +340,7 @@ void KeyDropDownControl::ValidateShortcutFromDropDownList(Grid table, StackPanel for (int i = 0; i < keyDropDownControlObjects.size(); i++) { // Check for errors only if the current selection is a valid shortcut - std::vector selectedKeyCodes = KeyboardManagerHelper::GetKeyCodesFromSelectedIndices(keyDropDownControlObjects[i]->GetSelectedIndicesFromStackPanel(parent), KeyDropDownControl::keyboardManagerState->keyboardMap.GetKeyCodeList(true)); + std::vector selectedKeyCodes = KeyboardManagerHelper::GetKeyCodesFromSelectedIndices(keyDropDownControlObjects[i]->GetSelectedIndicesFromStackPanel(parent), GetKeyCodeList(true, colIndex == 1)); std::variant currentShortcut; if (selectedKeyCodes.size() == 1 && isHybridControl) { @@ -352,7 +378,7 @@ void KeyDropDownControl::AddShortcutToControl(Shortcut shortcut, Grid table, Sta keyDropDownControlObjects.clear(); std::vector shortcutKeyCodes = shortcut.GetKeyCodes(); - std::vector keyCodeList = keyboardManagerState.keyboardMap.GetKeyCodeList(true); + std::vector keyCodeList = GetKeyCodeList(true, colIndex == 1); if (shortcutKeyCodes.size() != 0) { bool ignoreWarning = false; diff --git a/src/modules/keyboardmanager/ui/KeyDropDownControl.h b/src/modules/keyboardmanager/ui/KeyDropDownControl.h index f50d37cfb4..c7a9b24fba 100644 --- a/src/modules/keyboardmanager/ui/KeyDropDownControl.h +++ b/src/modules/keyboardmanager/ui/KeyDropDownControl.h @@ -1,5 +1,6 @@ #pragma once #include +#include class KeyboardManagerState; namespace winrt::Windows @@ -42,23 +43,22 @@ private: bool ignoreKeyToShortcutWarning; // Function to set properties apart from the SelectionChanged event handler - void SetDefaultProperties(bool isShortcut); + void SetDefaultProperties(bool isShortcut, bool renderDisable); // Function to check if the layout has changed and accordingly update the drop down list - void CheckAndUpdateKeyboardLayout(ComboBox currentDropDown, bool isShortcut); + void CheckAndUpdateKeyboardLayout(ComboBox currentDropDown, bool isShortcut, bool renderDisable); // Function to set accessible name for combobox static void SetAccessibleNameForComboBox(ComboBox dropDown, int index); - public: // Pointer to the keyboard manager state static KeyboardManagerState* keyboardManagerState; // Constructor - the last default parameter should be passed as false only if it originates from Type shortcut or when an old shortcut is reloaded - KeyDropDownControl(bool isShortcut, bool fromAddShortcutToControl = false) : + KeyDropDownControl(bool isShortcut, bool fromAddShortcutToControl = false, bool renderDisable = false) : ignoreKeyToShortcutWarning(fromAddShortcutToControl) { - SetDefaultProperties(isShortcut); + SetDefaultProperties(isShortcut, renderDisable); } // 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 @@ -90,4 +90,10 @@ public: // 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>& keyDropDownControlObjects, RemapBuffer& remapBuffer, StackPanel controlLayout, TextBox targetApp, bool isHybridControl, bool isSingleKeyWindow); + + // Get keys code list depending if Disable is in dropdown + static std::vector GetKeyCodeList(bool isShortcut, bool renderDisable); + + // Get keys name list depending if Disable is in dropdown + static std::vector GetKeyNameList(bool isShortcut, bool renderDisable); }; diff --git a/src/modules/keyboardmanager/ui/ShortcutControl.cpp b/src/modules/keyboardmanager/ui/ShortcutControl.cpp index c1f1da5fab..bae7e88174 100644 --- a/src/modules/keyboardmanager/ui/ShortcutControl.cpp +++ b/src/modules/keyboardmanager/ui/ShortcutControl.cpp @@ -5,6 +5,7 @@ #include "keyboardmanager/common/Helpers.h" #include "common/common.h" #include "keyboardmanager/dll/Generated Files/resource.h" +#include extern "C" IMAGE_DOS_HEADER __ImageBase; //Both static members are initialized to null @@ -132,9 +133,9 @@ void ShortcutControl::AddNewShortcutControlRow(Grid& parent, std::vectorgetShortcutControl(), keyboardRemapControlObjects[rowIndex][1]->shortcutDropDownStackPanel.as(), 1, ShortcutControl::shortcutRemapBuffer, keyboardRemapControlObjects[rowIndex][1]->keyDropDownControlObjects, targetAppTextBox, true, false); // Reset the buffer based on the selected drop down items - std::get(shortcutRemapBuffer[rowIndex].first[0]).SetKeyCodes(KeyboardManagerHelper::GetKeyCodesFromSelectedIndices(KeyDropDownControl::GetSelectedIndicesFromStackPanel(keyboardRemapControlObjects[rowIndex][0]->shortcutDropDownStackPanel.as()), KeyDropDownControl::keyboardManagerState->keyboardMap.GetKeyCodeList(true))); + std::get(shortcutRemapBuffer[rowIndex].first[0]).SetKeyCodes(KeyboardManagerHelper::GetKeyCodesFromSelectedIndices(KeyDropDownControl::GetSelectedIndicesFromStackPanel(keyboardRemapControlObjects[rowIndex][0]->shortcutDropDownStackPanel.as()), KeyDropDownControl::GetKeyCodeList(true, false))); // second column is a hybrid column - std::vector selectedKeyCodes = KeyboardManagerHelper::GetKeyCodesFromSelectedIndices(KeyDropDownControl::GetSelectedIndicesFromStackPanel(keyboardRemapControlObjects[rowIndex][1]->shortcutDropDownStackPanel.as()), KeyDropDownControl::keyboardManagerState->keyboardMap.GetKeyCodeList(true)); + std::vector selectedKeyCodes = KeyboardManagerHelper::GetKeyCodesFromSelectedIndices(KeyDropDownControl::GetSelectedIndicesFromStackPanel(keyboardRemapControlObjects[rowIndex][1]->shortcutDropDownStackPanel.as()), KeyDropDownControl::GetKeyCodeList(true, true)); // If exactly one key is selected consider it to be a key remap if (selectedKeyCodes.size() == 1) @@ -243,7 +244,7 @@ void ShortcutControl::AddNewShortcutControlRow(Grid& parent, std::vector shortcutListKeyCodes = keyboardManagerState->keyboardMap.GetKeyCodeList(true); + std::vector shortcutListKeyCodes = KeyDropDownControl::GetKeyCodeList(true, true); auto it = std::find(shortcutListKeyCodes.begin(), shortcutListKeyCodes.end(), std::get(newKeys)); if (it != shortcutListKeyCodes.end()) { diff --git a/src/modules/keyboardmanager/ui/SingleKeyRemapControl.cpp b/src/modules/keyboardmanager/ui/SingleKeyRemapControl.cpp index a9545bbf1e..57f06a8786 100644 --- a/src/modules/keyboardmanager/ui/SingleKeyRemapControl.cpp +++ b/src/modules/keyboardmanager/ui/SingleKeyRemapControl.cpp @@ -6,6 +6,7 @@ #include "ShortcutControl.h" #include "common/common.h" #include "keyboardmanager/dll/Generated Files/resource.h" +#include extern "C" IMAGE_DOS_HEADER __ImageBase; //Both static members are initialized to null @@ -106,7 +107,7 @@ void SingleKeyRemapControl::AddNewControlKeyRemapRow(Grid& parent, std::vector(RemapBufferItem{ originalKey, newKey }, L"")); std::vector keyCodes = keyboardManagerState->keyboardMap.GetKeyCodeList(); - std::vector shortcutListKeyCodes = keyboardManagerState->keyboardMap.GetKeyCodeList(true); + std::vector shortcutListKeyCodes = KeyDropDownControl::GetKeyCodeList(true, true); auto it = std::find(keyCodes.begin(), keyCodes.end(), originalKey); if (it != keyCodes.end()) {