From f742d3c1c320526acf5cbd6307f162148af6a1ad Mon Sep 17 00:00:00 2001 From: Andrey Nekrasov Date: Thu, 23 Nov 2023 11:46:07 +0100 Subject: [PATCH] [KBM]Allow remapping keys and shortcuts to arbitrary unicode sequences (#29399) * [KBM] Allow remapping keys and shortcuts to arbitrary unicode sequences * f: spelling * f: tests * f: split shortcut configuration * f: address ui layout comments * [BugReport]Don't report personal info * f: fix crash in KBME * f: add missed type button * f: fix shortcut line UI elements alignment * f: align elements size * f: add warning about non-mapped keys --- .../KeyboardManagerEditor/Resources.resx | 10 + .../BufferValidationHelpers.cpp | 2 +- .../EditKeyboardWindow.cpp | 7 + .../EditorConstants.h | 2 +- .../KeyDropDownControl.cpp | 27 ++- .../KeyboardManagerEditorStrings.h | 17 +- .../LoadingAndSavingRemappingHelper.cpp | 98 ++++++---- .../LoadingAndSavingRemappingHelper.h | 4 +- .../ShortcutControl.cpp | 111 +++++++++-- .../ShortcutControl.h | 5 +- .../SingleKeyRemapControl.cpp | 95 +++++++-- .../SingleKeyRemapControl.h | 4 +- .../UIHelpers.cpp | 32 ++- .../KeyboardManagerEditorLibrary/UIHelpers.h | 5 + .../KeyboardManagerEditorLibrary/trace.cpp | 5 +- .../KeyboardManagerEditorLibrary/trace.h | 4 +- .../KeyboardEventHandlers.cpp | 141 ++++++++++++-- .../KeyboardEventHandlers.h | 3 + .../KeyboardManager.cpp | 12 +- .../KeyboardManagerEngineLibrary/State.cpp | 12 ++ .../KeyboardManagerEngineLibrary/State.h | 3 + .../common/KeyboardManagerConstants.h | 14 +- .../common/MappingConfiguration.cpp | 183 ++++++++++++++---- .../common/MappingConfiguration.h | 26 ++- .../keyboardmanager/common/RemapShortcut.h | 4 +- src/modules/keyboardmanager/common/Shortcut.h | 4 +- .../AppSpecificKeysDataModel.cs | 7 +- .../KeyboardManagerProfile.cs | 9 + .../Settings.UI.Library/KeysDataModel.cs | 5 +- .../ViewModels/DashboardViewModel.cs | 7 + .../ViewModels/KeyboardManagerViewModel.cs | 9 +- tools/BugReportTool/BugReportTool/Main.cpp | 4 +- 32 files changed, 698 insertions(+), 173 deletions(-) diff --git a/src/modules/keyboardmanager/KeyboardManagerEditor/Resources.resx b/src/modules/keyboardmanager/KeyboardManagerEditor/Resources.resx index 1239cc7786..2ddc68f7a6 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditor/Resources.resx +++ b/src/modules/keyboardmanager/KeyboardManagerEditor/Resources.resx @@ -287,6 +287,16 @@ Key Key on a keyboard + + Text + + + Shortcut + + + Key + Key on a keyboard + Add key remapping Key on a keyboard diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/BufferValidationHelpers.cpp b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/BufferValidationHelpers.cpp index 79ffcf0152..228e680f26 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/BufferValidationHelpers.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/BufferValidationHelpers.cpp @@ -220,7 +220,7 @@ namespace BufferValidationHelpers // 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 == ShortcutErrorType::NoError) { - KeyShortcutUnion tempShortcut; + KeyShortcutTextUnion tempShortcut; if (isHybridControl && KeyDropDownControl::GetNumberOfSelectedKeys(selectedCodes) == 1) { tempShortcut = (DWORD)*std::find_if(selectedCodes.begin(), selectedCodes.end(), [](int32_t a) { return a != -1 && a != 0; }); diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditKeyboardWindow.cpp b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditKeyboardWindow.cpp index 24126abebd..c3ec42a7ee 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditKeyboardWindow.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditKeyboardWindow.cpp @@ -285,14 +285,21 @@ inline void CreateEditKeyboardWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMan // Load existing remaps into UI SingleKeyRemapTable singleKeyRemapCopy = mappingConfiguration.singleKeyReMap; + SingleKeyToTextRemapTable singleKeyToTextRemapCopy = mappingConfiguration.singleKeyToTextReMap; LoadingAndSavingRemappingHelper::PreProcessRemapTable(singleKeyRemapCopy); + LoadingAndSavingRemappingHelper::PreProcessRemapTable(singleKeyToTextRemapCopy); for (const auto& it : singleKeyRemapCopy) { SingleKeyRemapControl::AddNewControlKeyRemapRow(keyRemapTable, keyboardRemapControlObjects, it.first, it.second); } + for (const auto& it : singleKeyToTextRemapCopy) + { + SingleKeyRemapControl::AddNewControlKeyRemapRow(keyRemapTable, keyboardRemapControlObjects, it.first, it.second); + } + // Main Header Apply button Button applyButton; applyButton.Content(winrt::box_value(GET_RESOURCE_STRING(IDS_OK_BUTTON))); diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditorConstants.h b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditorConstants.h index 9943c146f4..cd38ad8d83 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditorConstants.h +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditorConstants.h @@ -35,7 +35,7 @@ namespace EditorConstants inline const long ShortcutTableRemoveColIndex = 4; inline const long ShortcutArrowColumnWidth = 90; inline const DWORD64 ShortcutTableDropDownWidth = 160; - inline const DWORD64 ShortcutTableDropDownSpacing = 10; + inline const long ShortcutTableDropDownSpacing = 10; inline const long ShortcutOriginColumnWidth = 3 * ShortcutTableDropDownWidth + 3 * ShortcutTableDropDownSpacing; inline const long ShortcutTargetColumnWidth = 3 * ShortcutTableDropDownWidth + 3 * ShortcutTableDropDownSpacing + 15; diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyDropDownControl.cpp b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyDropDownControl.cpp index a3ff546ac8..39a7bb2aa3 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyDropDownControl.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyDropDownControl.cpp @@ -66,7 +66,7 @@ void KeyDropDownControl::SetDefaultProperties(bool isShortcut, bool renderDisabl } dropDown.as().MaxDropDownHeight(EditorConstants::TableDropDownHeight); - + // Initialise layout attribute previousLayout = GetKeyboardLayout(0); dropDown.as().SelectedValuePath(L"DataContext"); @@ -83,7 +83,20 @@ void KeyDropDownControl::SetDefaultProperties(bool isShortcut, bool renderDisabl // Attach the tip to the drop down warningTip.Target(dropDown.as()); dropDown.as().Loaded([&](winrt::Windows::Foundation::IInspectable const& sender, auto args) { - Media::VisualTreeHelper::GetChild(dropDown.as(), 0).as().Children().Append(warningTip); + auto combo = dropDown.as(); + auto child0 = Media::VisualTreeHelper::GetChild(combo, 0); + if (!child0) + return; + + auto grid = child0.as(); + if (!grid) + return; + + auto& gridChildren = grid.Children(); + if (!gridChildren) + return; + + gridChildren.Append(warningTip); }); // Tip properties @@ -102,7 +115,7 @@ void KeyDropDownControl::SetDefaultProperties(bool isShortcut, bool renderDisabl warningFlyout.as().FlyoutPresenterStyle(style); dropDown.as().ContextFlyout().SetAttachedFlyout((FrameworkElement)dropDown.as(), warningFlyout.as()); #endif - + // To set the accessible name of the combo-box (by default index 1) SetAccessibleNameForComboBox(dropDown.as(), 1); } @@ -141,7 +154,7 @@ void KeyDropDownControl::SetSelectionHandler(StackPanel& table, StackPanel row, ComboBox currentDropDown = sender.as(); int selectedKeyCode = GetSelectedValue(currentDropDown); - + // Validate current remap selection ShortcutErrorType errorType = BufferValidationHelpers::ValidateAndUpdateKeyBufferElement(rowIndex, colIndex, selectedKeyCode, singleKeyRemapBuffer); @@ -228,7 +241,7 @@ std::pair KeyDropDownControl::ValidateShortcutSelection( } parent.Children().RemoveAt(dropDownIndex); - + // delete drop down control object from the vector so that it can be destructed keyDropDownControlObjects.erase(keyDropDownControlObjects.begin() + dropDownIndex); } @@ -368,7 +381,7 @@ void KeyDropDownControl::ValidateShortcutFromDropDownList(StackPanel table, Stac { // Check for errors only if the current selection is a valid shortcut std::vector selectedKeyCodes = GetSelectedCodesFromStackPanel(parent); - KeyShortcutUnion currentShortcut; + KeyShortcutTextUnion currentShortcut; if (GetNumberOfSelectedKeys(selectedKeyCodes) == 1 && isHybridControl) { currentShortcut = (DWORD)selectedKeyCodes[0]; @@ -415,7 +428,7 @@ void KeyDropDownControl::AddShortcutToControl(Shortcut shortcut, StackPanel tabl { // Delete the existing drop down menus parent.Children().Clear(); - + // Remove references to the old drop down objects to destroy them keyDropDownControlObjects.clear(); std::vector shortcutKeyCodes = shortcut.GetKeyCodes(); diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerEditorStrings.h b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerEditorStrings.h index 3538b32e30..f41002b4be 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerEditorStrings.h +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerEditorStrings.h @@ -11,7 +11,22 @@ namespace KeyboardManagerEditorStrings { return GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_ALLAPPS); } - + + inline std::wstring MappingTypeText() + { + return GET_RESOURCE_STRING(IDS_MAPPING_TYPE_DROPDOWN_TEXT); + } + + inline std::wstring MappingTypeShortcut() + { + return GET_RESOURCE_STRING(IDS_MAPPING_TYPE_DROPDOWN_SHORTCUT); + } + + inline std::wstring MappingTypeKey() + { + return GET_RESOURCE_STRING(IDS_MAPPING_TYPE_DROPDOWN_KEY); + } + // Function to return the error message winrt::hstring GetErrorMessage(ShortcutErrorType errorType); } diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/LoadingAndSavingRemappingHelper.cpp b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/LoadingAndSavingRemappingHelper.cpp index c817d328d6..d1777e574b 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/LoadingAndSavingRemappingHelper.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/LoadingAndSavingRemappingHelper.cpp @@ -17,20 +17,20 @@ namespace LoadingAndSavingRemappingHelper ShortcutErrorType CheckIfRemappingsAreValid(const RemapBuffer& remappings) { ShortcutErrorType isSuccess = ShortcutErrorType::NoError; - std::map> ogKeys; + std::map> ogKeys; for (int i = 0; i < remappings.size(); i++) { - KeyShortcutUnion ogKey = remappings[i].first[0]; - KeyShortcutUnion newKey = remappings[i].first[1]; + KeyShortcutTextUnion ogKey = remappings[i].first[0]; + KeyShortcutTextUnion newKey = remappings[i].first[1]; std::wstring appName = remappings[i].second; - bool ogKeyValidity = (ogKey.index() == 0 && std::get(ogKey) != NULL) || (ogKey.index() == 1 && EditorHelpers::IsValidShortcut(std::get(ogKey))); - bool newKeyValidity = (newKey.index() == 0 && std::get(newKey) != NULL) || (newKey.index() == 1 && EditorHelpers::IsValidShortcut(std::get(newKey))); + const bool ogKeyValidity = (ogKey.index() == 0 && std::get(ogKey) != NULL) || (ogKey.index() == 1 && EditorHelpers::IsValidShortcut(std::get(ogKey))); + const bool newKeyValidity = (newKey.index() == 0 && std::get(newKey) != NULL) || (newKey.index() == 1 && EditorHelpers::IsValidShortcut(std::get(newKey))) || (newKey.index() == 2 && !std::get(newKey).empty()); // Add new set for a new target app name if (ogKeys.find(appName) == ogKeys.end()) { - ogKeys[appName] = std::set(); + ogKeys[appName] = std::set(); } if (ogKeyValidity && newKeyValidity && ogKeys[appName].find(ogKey) == ogKeys[appName].end()) @@ -59,9 +59,12 @@ namespace LoadingAndSavingRemappingHelper for (int i = 0; i < remappings.size(); i++) { DWORD ogKey = std::get(remappings[i].first[0]); - KeyShortcutUnion newKey = remappings[i].first[1]; + KeyShortcutTextUnion newKey = remappings[i].first[1]; - if (ogKey != NULL && ((newKey.index() == 0 && std::get(newKey) != 0) || (newKey.index() == 1 && EditorHelpers::IsValidShortcut(std::get(newKey))))) + const bool hasValidKeyRemapping = newKey.index() == 0 && std::get(newKey) != 0; + const bool hasValidShortcutRemapping = newKey.index() == 1 && EditorHelpers::IsValidShortcut(std::get(newKey)); + const bool hasValidTextRemapping = newKey.index() == 2 && !std::get(newKey).empty(); + if (ogKey != NULL && (hasValidKeyRemapping || hasValidShortcutRemapping || hasValidTextRemapping)) { ogKeys.insert(ogKey); @@ -116,53 +119,64 @@ namespace LoadingAndSavingRemappingHelper { // Clear existing Key Remaps mappingConfiguration.ClearSingleKeyRemaps(); + mappingConfiguration.ClearSingleKeyToTextRemaps(); DWORD successfulKeyToKeyRemapCount = 0; DWORD successfulKeyToShortcutRemapCount = 0; + DWORD successfulKeyToTextRemapCount = 0; for (int i = 0; i < remappings.size(); i++) { - DWORD originalKey = std::get(remappings[i].first[0]); - KeyShortcutUnion newKey = remappings[i].first[1]; + const DWORD originalKey = std::get(remappings[i].first[0]); + KeyShortcutTextUnion newKey = remappings[i].first[1]; - if (originalKey != NULL && !(newKey.index() == 0 && std::get(newKey) == NULL) && !(newKey.index() == 1 && !EditorHelpers::IsValidShortcut(std::get(newKey)))) + if (originalKey != NULL && !(newKey.index() == 0 && std::get(newKey) == NULL) && !(newKey.index() == 1 && !EditorHelpers::IsValidShortcut(std::get(newKey))) && !(newKey.index() == 2 && std::get(newKey).empty())) { // 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) + std::vector originalKeysWithModifiers; + if (originalKey == VK_CONTROL) { - case VK_CONTROL: - res1 = mappingConfiguration.AddSingleKeyRemap(VK_LCONTROL, newKey); - res2 = mappingConfiguration.AddSingleKeyRemap(VK_RCONTROL, newKey); - result = res1 && res2; - break; - case VK_MENU: - res1 = mappingConfiguration.AddSingleKeyRemap(VK_LMENU, newKey); - res2 = mappingConfiguration.AddSingleKeyRemap(VK_RMENU, newKey); - result = res1 && res2; - break; - case VK_SHIFT: - res1 = mappingConfiguration.AddSingleKeyRemap(VK_LSHIFT, newKey); - res2 = mappingConfiguration.AddSingleKeyRemap(VK_RSHIFT, newKey); - result = res1 && res2; - break; - case CommonSharedConstants::VK_WIN_BOTH: - res1 = mappingConfiguration.AddSingleKeyRemap(VK_LWIN, newKey); - res2 = mappingConfiguration.AddSingleKeyRemap(VK_RWIN, newKey); - result = res1 && res2; - break; - default: - result = mappingConfiguration.AddSingleKeyRemap(originalKey, newKey); + originalKeysWithModifiers.push_back(VK_LCONTROL); + originalKeysWithModifiers.push_back(VK_RCONTROL); + } + else if (originalKey == VK_MENU) + { + originalKeysWithModifiers.push_back(VK_LMENU); + originalKeysWithModifiers.push_back(VK_RMENU); + } + else if (originalKey == VK_SHIFT) + { + originalKeysWithModifiers.push_back(VK_LSHIFT); + originalKeysWithModifiers.push_back(VK_RSHIFT); + } + else if (originalKey == CommonSharedConstants::VK_WIN_BOTH) + { + originalKeysWithModifiers.push_back(VK_LWIN); + originalKeysWithModifiers.push_back(VK_RWIN); + } + else + { + originalKeysWithModifiers.push_back(originalKey); + } + + for (const DWORD key : originalKeysWithModifiers) + { + const bool mappedToText = newKey.index() == 2; + result = mappedToText ? mappingConfiguration.AddSingleKeyToTextRemap(key, std::get(newKey)) : mappingConfiguration.AddSingleKeyRemap(key, newKey) && result; } if (result) { if (newKey.index() == 0) { - successfulKeyToKeyRemapCount += 1; + ++successfulKeyToKeyRemapCount; } - else + else if (newKey.index() == 1) { - successfulKeyToShortcutRemapCount += 1; + ++successfulKeyToShortcutRemapCount; + } + else if (newKey.index() == 2) + { + ++successfulKeyToTextRemapCount; } } } @@ -171,7 +185,7 @@ namespace LoadingAndSavingRemappingHelper // If telemetry is to be logged, log the key remap counts if (isTelemetryRequired) { - Trace::KeyRemapCount(successfulKeyToKeyRemapCount, successfulKeyToShortcutRemapCount); + Trace::KeyRemapCount(successfulKeyToKeyRemapCount, successfulKeyToShortcutRemapCount, successfulKeyToTextRemapCount); } } @@ -185,14 +199,14 @@ namespace LoadingAndSavingRemappingHelper 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(remappings[i].first[0]); - KeyShortcutUnion newShortcut = remappings[i].first[1]; + KeyShortcutTextUnion newShortcut = remappings[i].first[1]; - if (EditorHelpers::IsValidShortcut(originalShortcut) && ((newShortcut.index() == 0 && std::get(newShortcut) != NULL) || (newShortcut.index() == 1 && EditorHelpers::IsValidShortcut(std::get(newShortcut))))) + if (EditorHelpers::IsValidShortcut(originalShortcut) && ((newShortcut.index() == 0 && std::get(newShortcut) != NULL) || (newShortcut.index() == 1 && EditorHelpers::IsValidShortcut(std::get(newShortcut))) || (newShortcut.index() == 2 && !std::get(newShortcut).empty()))) { if (remappings[i].second == L"") { diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/LoadingAndSavingRemappingHelper.h b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/LoadingAndSavingRemappingHelper.h index db7b47b744..7197959024 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/LoadingAndSavingRemappingHelper.h +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/LoadingAndSavingRemappingHelper.h @@ -15,10 +15,10 @@ namespace LoadingAndSavingRemappingHelper std::vector 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& table, DWORD leftKey, DWORD rightKey, DWORD combinedKey); + void CombineRemappings(std::unordered_map& 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& table); + void PreProcessRemapTable(std::unordered_map& table); // Function to apply the single key remappings from the buffer to the KeyboardManagerState variable void ApplySingleKeyRemappings(MappingConfiguration& mappingConfiguration, const RemapBuffer& remappings, bool isTelemetryRequired); diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/ShortcutControl.cpp b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/ShortcutControl.cpp index b73ddef074..8de7350979 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/ShortcutControl.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/ShortcutControl.cpp @@ -21,7 +21,7 @@ ShortcutControl::ShortcutControl(StackPanel table, StackPanel row, const int col shortcutDropDownVariableSizedWrapGrid = VariableSizedWrapGrid(); typeShortcut = Button(); shortcutControlLayout = StackPanel(); - bool isHybridControl = colIndex == 1 ? true : false; + const bool isHybridControl = colIndex == 1; // TODO: Check if there is a VariableSizedWrapGrid equivalent. // shortcutDropDownVariableSizedWrapGrid.as().Spacing(EditorConstants::ShortcutTableDropDownSpacing); @@ -41,7 +41,13 @@ ShortcutControl::ShortcutControl(StackPanel table, StackPanel row, const int col shortcutControlLayout.as().Spacing(EditorConstants::ShortcutTableDropDownSpacing); - shortcutControlLayout.as().Children().Append(typeShortcut.as