[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
This commit is contained in:
Andrey Nekrasov 2023-11-23 11:46:07 +01:00 committed by GitHub
parent 2543dee1f1
commit f742d3c1c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 698 additions and 173 deletions

View File

@ -287,6 +287,16 @@
<value>Key</value>
<comment>Key on a keyboard</comment>
</data>
<data name="Mapping_Type_DropDown_Text" xml:space="preserve">
<value>Text</value>
</data>
<data name="Mapping_Type_DropDown_Shortcut" xml:space="preserve">
<value>Shortcut</value>
</data>
<data name="Mapping_Type_DropDown_Key" xml:space="preserve">
<value>Key</value>
<comment>Key on a keyboard</comment>
</data>
<data name="Add_Key_Remap_Button" xml:space="preserve">
<value>Add key remapping</value>
<comment>Key on a keyboard</comment>

View File

@ -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; });

View File

@ -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)));

View File

@ -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;

View File

@ -66,7 +66,7 @@ void KeyDropDownControl::SetDefaultProperties(bool isShortcut, bool renderDisabl
}
dropDown.as<ComboBox>().MaxDropDownHeight(EditorConstants::TableDropDownHeight);
// Initialise layout attribute
previousLayout = GetKeyboardLayout(0);
dropDown.as<ComboBox>().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<ComboBox>());
dropDown.as<ComboBox>().Loaded([&](winrt::Windows::Foundation::IInspectable const& sender, auto args) {
Media::VisualTreeHelper::GetChild(dropDown.as<ComboBox>(), 0).as<Grid>().Children().Append(warningTip);
auto combo = dropDown.as<ComboBox>();
auto child0 = Media::VisualTreeHelper::GetChild(combo, 0);
if (!child0)
return;
auto grid = child0.as<Grid>();
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<Flyout>().FlyoutPresenterStyle(style);
dropDown.as<ComboBox>().ContextFlyout().SetAttachedFlyout((FrameworkElement)dropDown.as<ComboBox>(), warningFlyout.as<Flyout>());
#endif
// To set the accessible name of the combo-box (by default index 1)
SetAccessibleNameForComboBox(dropDown.as<ComboBox>(), 1);
}
@ -141,7 +154,7 @@ void KeyDropDownControl::SetSelectionHandler(StackPanel& table, StackPanel row,
ComboBox currentDropDown = sender.as<ComboBox>();
int selectedKeyCode = GetSelectedValue(currentDropDown);
// Validate current remap selection
ShortcutErrorType errorType = BufferValidationHelpers::ValidateAndUpdateKeyBufferElement(rowIndex, colIndex, selectedKeyCode, singleKeyRemapBuffer);
@ -228,7 +241,7 @@ std::pair<ShortcutErrorType, int> 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<int32_t> 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<DWORD> shortcutKeyCodes = shortcut.GetKeyCodes();

View File

@ -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);
}

View File

@ -17,20 +17,20 @@ namespace LoadingAndSavingRemappingHelper
ShortcutErrorType CheckIfRemappingsAreValid(const RemapBuffer& remappings)
{
ShortcutErrorType isSuccess = ShortcutErrorType::NoError;
std::map<std::wstring, std::set<KeyShortcutUnion>> ogKeys;
std::map<std::wstring, std::set<KeyShortcutTextUnion>> 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<DWORD>(ogKey) != NULL) || (ogKey.index() == 1 && EditorHelpers::IsValidShortcut(std::get<Shortcut>(ogKey)));
bool newKeyValidity = (newKey.index() == 0 && std::get<DWORD>(newKey) != NULL) || (newKey.index() == 1 && EditorHelpers::IsValidShortcut(std::get<Shortcut>(newKey)));
const bool ogKeyValidity = (ogKey.index() == 0 && std::get<DWORD>(ogKey) != NULL) || (ogKey.index() == 1 && EditorHelpers::IsValidShortcut(std::get<Shortcut>(ogKey)));
const bool newKeyValidity = (newKey.index() == 0 && std::get<DWORD>(newKey) != NULL) || (newKey.index() == 1 && EditorHelpers::IsValidShortcut(std::get<Shortcut>(newKey))) || (newKey.index() == 2 && !std::get<std::wstring>(newKey).empty());
// Add new set for a new target app name
if (ogKeys.find(appName) == ogKeys.end())
{
ogKeys[appName] = std::set<KeyShortcutUnion>();
ogKeys[appName] = std::set<KeyShortcutTextUnion>();
}
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<DWORD>(remappings[i].first[0]);
KeyShortcutUnion newKey = remappings[i].first[1];
KeyShortcutTextUnion newKey = remappings[i].first[1];
if (ogKey != NULL && ((newKey.index() == 0 && std::get<DWORD>(newKey) != 0) || (newKey.index() == 1 && EditorHelpers::IsValidShortcut(std::get<Shortcut>(newKey)))))
const bool hasValidKeyRemapping = newKey.index() == 0 && std::get<DWORD>(newKey) != 0;
const bool hasValidShortcutRemapping = newKey.index() == 1 && EditorHelpers::IsValidShortcut(std::get<Shortcut>(newKey));
const bool hasValidTextRemapping = newKey.index() == 2 && !std::get<std::wstring>(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<DWORD>(remappings[i].first[0]);
KeyShortcutUnion newKey = remappings[i].first[1];
const DWORD originalKey = std::get<DWORD>(remappings[i].first[0]);
KeyShortcutTextUnion newKey = remappings[i].first[1];
if (originalKey != NULL && !(newKey.index() == 0 && std::get<DWORD>(newKey) == NULL) && !(newKey.index() == 1 && !EditorHelpers::IsValidShortcut(std::get<Shortcut>(newKey))))
if (originalKey != NULL && !(newKey.index() == 0 && std::get<DWORD>(newKey) == NULL) && !(newKey.index() == 1 && !EditorHelpers::IsValidShortcut(std::get<Shortcut>(newKey))) && !(newKey.index() == 2 && std::get<std::wstring>(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<DWORD> 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<std::wstring>(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<Shortcut>(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<DWORD>(newShortcut) != NULL) || (newShortcut.index() == 1 && EditorHelpers::IsValidShortcut(std::get<Shortcut>(newShortcut)))))
if (EditorHelpers::IsValidShortcut(originalShortcut) && ((newShortcut.index() == 0 && std::get<DWORD>(newShortcut) != NULL) || (newShortcut.index() == 1 && EditorHelpers::IsValidShortcut(std::get<Shortcut>(newShortcut))) || (newShortcut.index() == 2 && !std::get<std::wstring>(newShortcut).empty())))
{
if (remappings[i].second == L"")
{

View File

@ -15,10 +15,10 @@ namespace LoadingAndSavingRemappingHelper
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, KeyShortcutUnion>& table, DWORD leftKey, DWORD rightKey, DWORD combinedKey);
void CombineRemappings(std::unordered_map<DWORD, KeyShortcutTextUnion>& 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, KeyShortcutUnion>& table);
void PreProcessRemapTable(std::unordered_map<DWORD, KeyShortcutTextUnion>& table);
// Function to apply the single key remappings from the buffer to the KeyboardManagerState variable
void ApplySingleKeyRemappings(MappingConfiguration& mappingConfiguration, const RemapBuffer& remappings, bool isTelemetryRequired);

View File

@ -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<VariableSizedWrapGrid>().Spacing(EditorConstants::ShortcutTableDropDownSpacing);
@ -41,7 +41,13 @@ ShortcutControl::ShortcutControl(StackPanel table, StackPanel row, const int col
shortcutControlLayout.as<StackPanel>().Spacing(EditorConstants::ShortcutTableDropDownSpacing);
shortcutControlLayout.as<StackPanel>().Children().Append(typeShortcut.as<Button>());
keyComboAndSelectStackPanel = StackPanel();
keyComboAndSelectStackPanel.as<StackPanel>().Orientation(Windows::UI::Xaml::Controls::Orientation::Horizontal);
keyComboAndSelectStackPanel.as<StackPanel>().Spacing(EditorConstants::ShortcutTableDropDownSpacing);
keyComboAndSelectStackPanel.as<StackPanel>().Children().Append(typeShortcut.as<Button>());
shortcutControlLayout.as<StackPanel>().Children().InsertAt(0, keyComboAndSelectStackPanel.as<StackPanel>());
shortcutControlLayout.as<StackPanel>().Children().Append(shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>());
KeyDropDownControl::AddDropDown(table, row, shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>(), colIndex, shortcutRemapBuffer, keyDropDownControlObjects, targetApp, isHybridControl, false);
try
@ -63,7 +69,7 @@ void ShortcutControl::SetAccessibleNameForTextBox(TextBox targetAppTextBox, int
{
targetAppTextBoxAccessibleName += GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_ALLAPPS);
}
targetAppTextBox.SetValue(Automation::AutomationProperties::NameProperty(), box_value(targetAppTextBoxAccessibleName));
}
@ -77,7 +83,7 @@ void ShortcutControl::UpdateAccessibleNames(StackPanel sourceColumn, StackPanel
}
// Function to add a new row to the shortcut table. If the originalKeys and newKeys args are provided, then the displayed shortcuts are set to those values.
void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector<std::vector<std::unique_ptr<ShortcutControl>>>& keyboardRemapControlObjects, const Shortcut& originalKeys, const KeyShortcutUnion& newKeys, const std::wstring& targetAppName)
void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector<std::vector<std::unique_ptr<ShortcutControl>>>& keyboardRemapControlObjects, const Shortcut& originalKeys, const KeyShortcutTextUnion& newKeys, const std::wstring& targetAppName)
{
// Textbox for target application
TextBox targetAppTextBox;
@ -115,6 +121,60 @@ void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector<s
// ShortcutControl for the new shortcut
auto target = keyboardRemapControlObjects.back()[1]->GetShortcutControl();
target.Width(EditorConstants::ShortcutTargetColumnWidth);
auto typeCombo = ComboBox();
typeCombo.Width(EditorConstants::RemapTableDropDownWidth);
typeCombo.Items().Append(winrt::box_value(KeyboardManagerEditorStrings::MappingTypeShortcut()));
typeCombo.Items().Append(winrt::box_value(KeyboardManagerEditorStrings::MappingTypeText()));
auto controlStackPanel = keyboardRemapControlObjects.back()[1]->shortcutControlLayout.as<StackPanel>();
auto firstLineStackPanel = keyboardRemapControlObjects.back()[1]->keyComboAndSelectStackPanel.as<StackPanel>();
firstLineStackPanel.Children().InsertAt(0, typeCombo);
auto textInput = TextBox();
auto textInputMargin = Windows::UI::Xaml::Thickness();
textInputMargin.Top = -EditorConstants::ShortcutTableDropDownSpacing;
textInputMargin.Bottom = EditorConstants::ShortcutTableDropDownSpacing; // compensate for a collapsed UIElement
textInput.Margin(textInputMargin);
textInput.AcceptsReturn(false);
textInput.Visibility(Visibility::Collapsed);
textInput.Width(EditorConstants::TableDropDownHeight);
controlStackPanel.Children().Append(textInput);
textInput.HorizontalAlignment(HorizontalAlignment::Left);
textInput.TextChanged([parent, row](winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Controls::TextChangedEventArgs const& e) mutable {
auto textbox = sender.as<TextBox>();
auto text = textbox.Text();
uint32_t rowIndex = -1;
if (!parent.Children().IndexOf(row, rowIndex))
{
return;
}
shortcutRemapBuffer[rowIndex].first[1] = text.c_str();
});
auto grid = keyboardRemapControlObjects.back()[1]->shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>();
auto gridMargin = Windows::UI::Xaml::Thickness();
gridMargin.Bottom = -EditorConstants::ShortcutTableDropDownSpacing; // compensate for a collapsed textInput
grid.Margin(gridMargin);
auto button = keyboardRemapControlObjects.back()[1]->typeShortcut.as<Button>();
typeCombo.SelectionChanged([typeCombo, grid, button, textInput](winrt::Windows::Foundation::IInspectable const&, SelectionChangedEventArgs const&) {
const bool textSelected = typeCombo.SelectedIndex() == 1;
const auto shortcutInputVisibility = textSelected ? Visibility::Collapsed : Visibility::Visible;
grid.Visibility(shortcutInputVisibility);
button.Visibility(shortcutInputVisibility);
const auto textInputVisibility = textSelected ? Visibility::Visible : Visibility::Collapsed;
textInput.Visibility(textInputVisibility);
});
const bool textSelected = newKeys.index() == 2;
typeCombo.SelectedIndex(textSelected);
row.Children().Append(target);
targetAppTextBox.Width(EditorConstants::ShortcutTableDropDownWidth);
@ -129,7 +189,7 @@ void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector<s
});
// LostFocus handler will be called whenever text is updated by a user and then they click something else or tab to another control. Does not get called if Text is updated while the TextBox isn't in focus (i.e. from code)
targetAppTextBox.LostFocus([&keyboardRemapControlObjects, parent, row, targetAppTextBox](auto const& sender, auto const& e) {
targetAppTextBox.LostFocus([&keyboardRemapControlObjects, parent, row, targetAppTextBox, typeCombo, textInput](auto const& sender, auto const& e) {
// Get index of targetAppTextBox button
uint32_t rowIndex;
if (!parent.Children().IndexOf(row, rowIndex))
@ -151,19 +211,27 @@ void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector<s
std::get<Shortcut>(shortcutRemapBuffer[rowIndex].first[0]).SetKeyCodes(KeyDropDownControl::GetSelectedCodesFromStackPanel(keyboardRemapControlObjects[rowIndex][0]->shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>()));
// second column is a hybrid column
std::vector<int32_t> selectedKeyCodes = KeyDropDownControl::GetSelectedCodesFromStackPanel(keyboardRemapControlObjects[rowIndex][1]->shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>());
// If exactly one key is selected consider it to be a key remap
if (selectedKeyCodes.size() == 1)
const bool textSelected = typeCombo.SelectedIndex() == 1;
if (textSelected)
{
shortcutRemapBuffer[rowIndex].first[1] = (DWORD)selectedKeyCodes[0];
shortcutRemapBuffer[rowIndex].first[1] = textInput.Text().c_str();
}
else
{
Shortcut tempShortcut;
tempShortcut.SetKeyCodes(selectedKeyCodes);
// Assign instead of setting the value in the buffer since the previous value may not be a Shortcut
shortcutRemapBuffer[rowIndex].first[1] = tempShortcut;
std::vector<int32_t> selectedKeyCodes = KeyDropDownControl::GetSelectedCodesFromStackPanel(keyboardRemapControlObjects[rowIndex][1]->shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>());
// If exactly one key is selected consider it to be a key remap
if (selectedKeyCodes.size() == 1)
{
shortcutRemapBuffer[rowIndex].first[1] = (DWORD)selectedKeyCodes[0];
}
else
{
Shortcut tempShortcut;
tempShortcut.SetKeyCodes(selectedKeyCodes);
// Assign instead of setting the value in the buffer since the previous value may not be a Shortcut
shortcutRemapBuffer[rowIndex].first[1] = tempShortcut;
}
}
std::wstring newText = targetAppTextBox.Text().c_str();
std::wstring lowercaseDefAppName = KeyboardManagerEditorStrings::DefaultAppName();
@ -263,10 +331,19 @@ void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector<s
{
keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1]->keyDropDownControlObjects[0]->SetSelectedValue(std::to_wstring(std::get<DWORD>(newKeys)));
}
else
else if (newKeys.index() == 1)
{
KeyDropDownControl::AddShortcutToControl(std::get<Shortcut>(newKeys), parent, keyboardRemapControlObjects.back()[1]->shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>(), *keyboardManagerState, 1, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1]->keyDropDownControlObjects, shortcutRemapBuffer, row, targetAppTextBox, true, false);
}
else if (newKeys.index() == 2)
{
shortcutRemapBuffer.back().first[1] = std::get<std::wstring>(newKeys);
const auto& remapControl = keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1];
const auto& controlChildren = remapControl->GetShortcutControl().Children();
const auto& topLineChildren = controlChildren.GetAt(0).as<StackPanel>();
topLineChildren.Children().GetAt(0).as<ComboBox>().SelectedIndex(1);
controlChildren.GetAt(2).as<TextBox>().Text(std::get<std::wstring>(newKeys));
}
}
else
{
@ -291,8 +368,8 @@ void ShortcutControl::CreateDetectShortcutWindow(winrt::Windows::Foundation::IIn
detectShortcutBox.XamlRoot(xamlRoot);
detectShortcutBox.Title(box_value(GET_RESOURCE_STRING(IDS_TYPESHORTCUT_TITLE)));
// Get the linked stack panel for the "Type shortcut" button that was clicked
VariableSizedWrapGrid linkedShortcutVariableSizedWrapGrid = UIHelpers::GetSiblingElement(sender).as<VariableSizedWrapGrid>();
// Get the parent linked stack panel for the "Type shortcut" button that was clicked
VariableSizedWrapGrid linkedShortcutVariableSizedWrapGrid = UIHelpers::GetSiblingElement(sender.as<FrameworkElement>().Parent()).as<VariableSizedWrapGrid>();
auto unregisterKeys = [&keyboardManagerState]() {
keyboardManagerState.ClearRegisteredKeyDelays();

View File

@ -31,6 +31,9 @@ private:
// StackPanel to parent the above controls
winrt::Windows::Foundation::IInspectable shortcutControlLayout;
// StackPanel to parent the first line of "To" Column
winrt::Windows::Foundation::IInspectable keyComboAndSelectStackPanel;
// Function to set the accessible name of the target app text box
static void SetAccessibleNameForTextBox(TextBox targetAppTextBox, int rowIndex);
@ -54,7 +57,7 @@ public:
ShortcutControl(StackPanel table, StackPanel row, const int colIndex, TextBox targetApp);
// Function to add a new row to the shortcut table. If the originalKeys and newKeys args are provided, then the displayed shortcuts are set to those values.
static void AddNewShortcutControlRow(StackPanel& parent, std::vector<std::vector<std::unique_ptr<ShortcutControl>>>& keyboardRemapControlObjects, const Shortcut& originalKeys = Shortcut(), const KeyShortcutUnion& newKeys = Shortcut(), const std::wstring& targetAppName = L"");
static void AddNewShortcutControlRow(StackPanel& parent, std::vector<std::vector<std::unique_ptr<ShortcutControl>>>& keyboardRemapControlObjects, const Shortcut& originalKeys = Shortcut(), const KeyShortcutTextUnion& newKeys = Shortcut(), const std::wstring& targetAppName = L"");
// Function to return the stack panel element of the ShortcutControl. This is the externally visible UI element which can be used to add it to other layouts
StackPanel GetShortcutControl();

View File

@ -2,6 +2,7 @@
#include "SingleKeyRemapControl.h"
#include "KeyboardManagerState.h"
#include "KeyboardManagerEditorStrings.h"
#include "ShortcutControl.h"
#include "UIHelpers.h"
#include "EditorHelpers.h"
@ -20,25 +21,80 @@ SingleKeyRemapControl::SingleKeyRemapControl(StackPanel table, StackPanel row, c
typeKey.as<Button>().Content(winrt::box_value(GET_RESOURCE_STRING(IDS_TYPE_BUTTON)));
singleKeyRemapControlLayout = StackPanel();
singleKeyRemapControlLayout.as<StackPanel>().Spacing(10);
singleKeyRemapControlLayout.as<StackPanel>().Children().Append(typeKey.as<Button>());
singleKeyRemapControlLayout.as<StackPanel>().Spacing(EditorConstants::ShortcutTableDropDownSpacing);
// Key column
// Key column (From key)
if (colIndex == 0)
{
singleKeyRemapControlLayout.as<StackPanel>().Children().Append(typeKey.as<Button>());
keyDropDownControlObjects.emplace_back(std::make_unique<KeyDropDownControl>(false));
singleKeyRemapControlLayout.as<StackPanel>().Children().Append(keyDropDownControlObjects[0]->GetComboBox());
// Set selection handler for the drop down
keyDropDownControlObjects[0]->SetSelectionHandler(table, row, colIndex, singleKeyRemapBuffer);
}
// Hybrid column
// Hybrid column (To Key/Shortcut/Text)
else
{
StackPanel keyComboAndSelectStackPanel;
keyComboAndSelectStackPanel.Orientation(Windows::UI::Xaml::Controls::Orientation::Horizontal);
keyComboAndSelectStackPanel.Spacing(EditorConstants::ShortcutTableDropDownSpacing);
hybridDropDownVariableSizedWrapGrid = VariableSizedWrapGrid();
hybridDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>().Orientation(Windows::UI::Xaml::Controls::Orientation::Horizontal);
KeyDropDownControl::AddDropDown(table, row, hybridDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>(), colIndex, singleKeyRemapBuffer, keyDropDownControlObjects, nullptr, true, true);
singleKeyRemapControlLayout.as<StackPanel>().Children().Append(hybridDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>());
auto grid = hybridDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>();
grid.Orientation(Windows::UI::Xaml::Controls::Orientation::Horizontal);
auto gridMargin = Windows::UI::Xaml::Thickness();
gridMargin.Bottom = -EditorConstants::ShortcutTableDropDownSpacing; // compensate for a collapsed textInput
grid.Margin(gridMargin);
KeyDropDownControl::AddDropDown(table, row, grid, colIndex, singleKeyRemapBuffer, keyDropDownControlObjects, nullptr, true, true);
singleKeyRemapControlLayout.as<StackPanel>().Children().Append(grid);
auto textInput = TextBox();
auto textBoxMargin = Windows::UI::Xaml::Thickness();
textBoxMargin.Top = -EditorConstants::ShortcutTableDropDownSpacing; // compensate for a collapsed grid
textBoxMargin.Bottom = EditorConstants::ShortcutTableDropDownSpacing;
textInput.Margin(textBoxMargin);
textInput.AcceptsReturn(false);
textInput.Visibility(Visibility::Collapsed);
textInput.Width(EditorConstants::TableDropDownHeight);
singleKeyRemapControlLayout.as<StackPanel>().Children().Append(textInput);
textInput.HorizontalAlignment(HorizontalAlignment::Left);
textInput.TextChanged([this, row, table](winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Controls::TextChangedEventArgs const& e) mutable {
auto textbox = sender.as<TextBox>();
auto text = textbox.Text();
uint32_t rowIndex = -1;
if (!table.Children().IndexOf(row, rowIndex))
{
return;
}
singleKeyRemapBuffer[rowIndex].first[1] = text.c_str();
});
auto typeCombo = ComboBox();
typeCombo.Width(EditorConstants::RemapTableDropDownWidth);
typeCombo.Items().Append(winrt::box_value(KeyboardManagerEditorStrings::MappingTypeKey()));
typeCombo.Items().Append(winrt::box_value(KeyboardManagerEditorStrings::MappingTypeText()));
keyComboAndSelectStackPanel.Children().Append(typeCombo);
keyComboAndSelectStackPanel.Children().Append(typeKey.as<Button>());
singleKeyRemapControlLayout.as<StackPanel>().Children().InsertAt(0, keyComboAndSelectStackPanel);
typeCombo.SelectedIndex(0);
typeCombo.SelectionChanged([this, typeCombo, grid, textInput](winrt::Windows::Foundation::IInspectable const&, SelectionChangedEventArgs const&) {
const bool textSelected = typeCombo.SelectedIndex() == 1;
const auto keyInputVisibility = textSelected ? Visibility::Collapsed : Visibility::Visible;
grid.Visibility(keyInputVisibility);
typeKey.as<Button>().Visibility(keyInputVisibility);
const auto textInputVisibility = textSelected ? Visibility::Visible : Visibility::Collapsed;
textInput.Visibility(textInputVisibility);
});
}
typeKey.as<Button>().Click([&, table, colIndex, row](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) {
@ -73,8 +129,15 @@ void SingleKeyRemapControl::UpdateAccessibleNames(StackPanel sourceColumn, Stack
deleteButton.SetValue(Automation::AutomationProperties::NameProperty(), box_value(GET_RESOURCE_STRING(IDS_AUTOMATIONPROPERTIES_ROW) + std::to_wstring(rowIndex) + L", " + GET_RESOURCE_STRING(IDS_DELETE_REMAPPING_BUTTON)));
}
void SingleKeyRemapControl::TextToMapChangedHandler(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Controls::TextChangedEventArgs const& e) // TODO: remove
{
auto textbox = sender.as<TextBox>();
auto text = textbox.Text();
(void)text;
}
// Function to add a new row to the remap keys table. If the originalKey and newKey args are provided, then the displayed remap keys are set to those values.
void SingleKeyRemapControl::AddNewControlKeyRemapRow(StackPanel& parent, std::vector<std::vector<std::unique_ptr<SingleKeyRemapControl>>>& keyboardRemapControlObjects, const DWORD originalKey, const KeyShortcutUnion newKey)
void SingleKeyRemapControl::AddNewControlKeyRemapRow(StackPanel& parent, std::vector<std::vector<std::unique_ptr<SingleKeyRemapControl>>>& keyboardRemapControlObjects, const DWORD originalKey, const KeyShortcutTextUnion newKey)
{
// Create new SingleKeyRemapControl objects dynamically so that we does not get destructed
std::vector<std::unique_ptr<SingleKeyRemapControl>> newrow;
@ -112,7 +175,7 @@ void SingleKeyRemapControl::AddNewControlKeyRemapRow(StackPanel& parent, std::ve
row.Children().Append(targetElement);
// 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 && !EditorHelpers::IsValidShortcut(std::get<Shortcut>(newKey))))
if (originalKey != NULL && !(newKey.index() == 0 && std::get<DWORD>(newKey) == NULL) && !(newKey.index() == 1 && !EditorHelpers::IsValidShortcut(std::get<Shortcut>(newKey))) && !(newKey.index() == 2 && std::get<std::wstring>(newKey).empty()))
{
singleKeyRemapBuffer.push_back(std::make_pair<RemapBufferItem, std::wstring>(RemapBufferItem{ originalKey, newKey }, L""));
keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][0]->keyDropDownControlObjects[0]->SetSelectedValue(std::to_wstring(originalKey));
@ -120,10 +183,20 @@ void SingleKeyRemapControl::AddNewControlKeyRemapRow(StackPanel& parent, std::ve
{
keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1]->keyDropDownControlObjects[0]->SetSelectedValue(std::to_wstring(std::get<DWORD>(newKey)));
}
else
else if (newKey.index() == 1)
{
KeyDropDownControl::AddShortcutToControl(std::get<Shortcut>(newKey), parent, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1]->hybridDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>(), *keyboardManagerState, 1, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1]->keyDropDownControlObjects, singleKeyRemapBuffer, row, nullptr, true, true);
}
else if (newKey.index() == 2)
{
auto& singleKeyRemapControl = keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1];
const auto& firstLineStackPanel = singleKeyRemapControl->singleKeyRemapControlLayout.as<StackPanel>().Children().GetAt(0).as<StackPanel>();
firstLineStackPanel.Children().GetAt(0).as<ComboBox>().SelectedIndex(1);
singleKeyRemapControl->singleKeyRemapControlLayout.as<StackPanel>().Children().GetAt(2).as<TextBox>().Text(std::get<std::wstring>(newKey));
}
}
else
{
@ -178,7 +251,7 @@ void SingleKeyRemapControl::AddNewControlKeyRemapRow(StackPanel& parent, std::ve
{
}
singleKeyRemapBuffer.erase(singleKeyRemapBuffer.begin() + rowIndex);
// delete the SingleKeyRemapControl objects so that they get destructed
keyboardRemapControlObjects.erase(keyboardRemapControlObjects.begin() + rowIndex);
});

View File

@ -35,6 +35,8 @@ private:
// Function to set the accessible names for all the controls in a row
static void UpdateAccessibleNames(StackPanel sourceColumn, StackPanel mappedToColumn, Button deleteButton, int rowIndex);
void TextToMapChangedHandler(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Controls::TextChangedEventArgs const& e);
public:
// Vector to store dynamically allocated KeyDropDownControl objects to avoid early destruction
std::vector<std::unique_ptr<KeyDropDownControl>> keyDropDownControlObjects;
@ -52,7 +54,7 @@ public:
SingleKeyRemapControl(StackPanel table, StackPanel row, const int colIndex);
// Function to add a new row to the remap keys table. If the originalKey and newKey args are provided, then the displayed remap keys are set to those values.
static void AddNewControlKeyRemapRow(StackPanel& parent, std::vector<std::vector<std::unique_ptr<SingleKeyRemapControl>>>& keyboardRemapControlObjects, const DWORD originalKey = 0, const KeyShortcutUnion newKey = (DWORD)0);
static void AddNewControlKeyRemapRow(StackPanel& parent, std::vector<std::vector<std::unique_ptr<SingleKeyRemapControl>>>& keyboardRemapControlObjects, const DWORD originalKey = 0, const KeyShortcutTextUnion newKey = (DWORD)0);
// Function to return the stack panel element of the SingleKeyRemapControl. This is the externally visible UI element which can be used to add it to other layouts
winrt::Windows::UI::Xaml::Controls::StackPanel getSingleKeyRemapControl();

View File

@ -9,13 +9,19 @@ namespace UIHelpers
void SetFocusOnTypeButtonInLastRow(StackPanel& parent, long colCount)
{
// First element in the last row (StackPanel)
StackPanel firstElementInLastRow = parent.Children().GetAt(parent.Children().Size() - 1).as<StackPanel>().Children().GetAt(0).as<StackPanel>();
auto lastHotKeyLine = parent.Children().GetAt(parent.Children().Size() - 1).as<StackPanel>();
// Type button is the first child in the StackPanel
Button firstTypeButtonInLastRow = firstElementInLastRow.Children().GetAt(0).as<Button>();
// Get "To" Column
auto toColumn = lastHotKeyLine.Children().GetAt(2).as<StackPanel>();
// Get first line in "To" Column
auto firstLineIntoColumn = toColumn.Children().GetAt(0).as<StackPanel>();
// Get Type Button from the first line
Button typeButton = firstLineIntoColumn.Children().GetAt(1).as<Button>();
// Set programmatic focus on the button
firstTypeButtonInLastRow.Focus(FocusState::Programmatic);
typeButton.Focus(FocusState::Programmatic);
}
RECT GetForegroundWindowDesktopRect()
@ -68,4 +74,22 @@ namespace UIHelpers
return boxList;
}
#ifndef NDEBUG
std::vector<std::wstring> GetChildrenNames(StackPanel& s)
{
std::vector<std::wstring> result;
for (auto child : s.Children())
{
std::wstring nameAndClass =
child.as<IFrameworkElement>().Name().c_str();
nameAndClass += L" ";
nameAndClass += winrt::get_class_name(child.try_as<winrt::Windows::Foundation::IInspectable>()).c_str();
result.push_back(nameAndClass);
}
return result;
}
#endif
}

View File

@ -29,4 +29,9 @@ namespace UIHelpers
// Function to return the list of key name in the order for the drop down based on the key codes
winrt::Windows::Foundation::Collections::IVector<winrt::Windows::Foundation::IInspectable> ToBoxValue(const std::vector<std::pair<DWORD, std::wstring>>& list);
#ifndef NDEBUG
// Useful For debugging issues
std::vector<std::wstring> GetChildrenNames(StackPanel& s);
#endif
}

View File

@ -19,7 +19,7 @@ void Trace::UnregisterProvider() noexcept
}
// Log number of key remaps when the user uses Edit Keyboard and saves settings
void Trace::KeyRemapCount(const DWORD keyToKeyCount, const DWORD keyToShortcutCount) noexcept
void Trace::KeyRemapCount(const DWORD keyToKeyCount, const DWORD keyToShortcutCount, const DWORD keyToTextCount) noexcept
{
TraceLoggingWrite(
g_hProvider,
@ -28,7 +28,8 @@ void Trace::KeyRemapCount(const DWORD keyToKeyCount, const DWORD keyToShortcutCo
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingValue(keyToKeyCount + keyToShortcutCount, "KeyRemapCount"),
TraceLoggingValue(keyToKeyCount, "KeyToKeyRemapCount"),
TraceLoggingValue(keyToShortcutCount, "KeyToShortcutRemapCount"));
TraceLoggingValue(keyToShortcutCount, "KeyToShortcutRemapCount"),
TraceLoggingValue(keyToTextCount, "KeyToTextRemapCount"));
}
// Log number of os level shortcut remaps when the user uses Edit Shortcuts and saves settings

View File

@ -7,14 +7,14 @@ public:
static void UnregisterProvider() noexcept;
// Log number of key remaps when the user uses Edit Keyboard and saves settings
static void KeyRemapCount(const DWORD keyToKeyCount, const DWORD keyToShortcutCount) noexcept;
static void KeyRemapCount(const DWORD keyToKeyCount, const DWORD keyToShortcutCount, const DWORD keyToTextCount) noexcept;
// Log number of os level shortcut remaps when the user uses Edit Shortcuts and saves settings
static void OSLevelShortcutRemapCount(const DWORD shortcutToShortcutCount, const DWORD shortcutToKeyCount) noexcept;
// Log number of app specific shortcut remaps when the user uses Edit Shortcuts and saves settings
static void AppSpecificShortcutRemapCount(const DWORD shortcutToShortcutCount, const DWORD shortcutToKeyCount) noexcept;
// Log if an error occurs in KBM
static void Error(const DWORD errorCode, std::wstring errorMessage, std::wstring methodName) noexcept;
};

View File

@ -7,13 +7,21 @@
#include <keyboardmanager/common/Helpers.h>
#include <keyboardmanager/KeyboardManagerEngineLibrary/trace.h>
namespace
{
bool GeneratedByKBM(const LowlevelKeyboardEvent* data)
{
return data->lParam->dwExtraInfo & CommonSharedConstants::KEYBOARDMANAGER_INJECTED_FLAG;
}
}
namespace KeyboardEventHandlers
{
// Function to a handle a single key remap
intptr_t HandleSingleKeyRemapEvent(KeyboardManagerInput::InputInterface& ii, LowlevelKeyboardEvent* data, State& state) noexcept
{
// Check if the key event was generated by KeyboardManager to avoid remapping events generated by us.
if (!(data->lParam->dwExtraInfo & CommonSharedConstants::KEYBOARDMANAGER_INJECTED_FLAG))
if (!GeneratedByKBM(data))
{
const auto remapping = state.GetSingleKeyRemap(data->lParam->vkCode);
if (remapping)
@ -21,7 +29,7 @@ namespace KeyboardEventHandlers
auto it = remapping.value();
// Check if the remap is to a key or a shortcut
bool remapToKey = (it->second.index() == 0);
const bool remapToKey = it->second.index() == 0;
// If mapped to VK_DISABLED then the key is disabled
if (remapToKey)
@ -191,7 +199,9 @@ namespace KeyboardEventHandlers
}
// Check if the remap is to a key or a shortcut
bool remapToShortcut = (it->second.targetShortcut.index() == 1);
const bool remapToKey = it->second.targetShortcut.index() == 0;
const bool remapToShortcut = it->second.targetShortcut.index() == 1;
const bool remapToText = it->second.targetShortcut.index() == 2;
const size_t src_size = it->first.Size();
const size_t dest_size = remapToShortcut ? std::get<Shortcut>(it->second.targetShortcut).Size() : 1;
@ -202,13 +212,13 @@ 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 || std::get<DWORD>(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED))
if (!it->first.IsKeyboardStateClearExceptShortcut(ii) && (remapToShortcut || (remapToKey && std::get<DWORD>(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED)))
{
continue;
}
size_t key_count;
LPINPUT keyEventList;
size_t key_count = 0;
LPINPUT keyEventList = nullptr;
// Remember which win key was pressed initially
if (ii.GetVirtualKeyState(VK_RWIN))
@ -265,7 +275,7 @@ namespace KeyboardEventHandlers
}
}
}
else
else if (remapToKey)
{
// Dummy key, key up for all the original shortcut modifier keys and key down for remapped key
key_count = KeyboardManagerConstants::DUMMY_KEY_EVENT_SIZE + (src_size - 1) + dest_size;
@ -300,6 +310,33 @@ namespace KeyboardEventHandlers
ResetIfModifierKeyForLowerLevelKeyHandlers(ii, static_cast<WORD>(Helpers::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut))), data->lParam->vkCode);
}
}
// Remapped to text
else
{
key_count = KeyboardManagerConstants::DUMMY_KEY_EVENT_SIZE + src_size;
auto remapping = std::get<std::wstring>(it->second.targetShortcut);
const UINT inputEventCount = static_cast<UINT>(remapping.length() * 2);
key_count += inputEventCount;
keyEventList = new INPUT[key_count]{};
int i = 0;
Helpers::SetDummyKeyEvent(keyEventList, i, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
// Release original shortcut state (release in reverse order of shortcut to be accurate)
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, i, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
for (size_t idx = 0; idx < inputEventCount; ++idx)
{
auto& input = keyEventList[idx + i];
input.type = INPUT_KEYBOARD;
const bool upEvent = idx & 0x1;
input.ki.dwFlags = KEYEVENTF_UNICODE | (upEvent ? KEYEVENTF_KEYUP : 0);
input.ki.dwExtraInfo = KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG;
input.ki.wScan = remapping[idx >> 1];
}
}
it->second.isShortcutInvoked = true;
// If app specific shortcut is invoked, store the target application
@ -332,8 +369,8 @@ namespace KeyboardEventHandlers
if ((it->first.CheckWinKey(data->lParam->vkCode) || it->first.CheckCtrlKey(data->lParam->vkCode) || it->first.CheckAltKey(data->lParam->vkCode) || it->first.CheckShiftKey(data->lParam->vkCode)) && (data->wParam == WM_KEYUP || data->wParam == WM_SYSKEYUP))
{
// Release new shortcut, and set original shortcut keys except the one released
size_t key_count;
LPINPUT keyEventList;
size_t key_count = 0;
LPINPUT keyEventList = nullptr;
if (remapToShortcut)
{
// if the released key is present in both shortcuts' modifiers (i.e part of the common modifiers)
@ -373,7 +410,7 @@ namespace KeyboardEventHandlers
// Send a dummy key event to prevent modifier press+release from being triggered. Example: Win+Ctrl+A->Ctrl+V, press Win+Ctrl+A and release A then Ctrl, since Win will be pressed here we need to send a dummy event after it
Helpers::SetDummyKeyEvent(keyEventList, i, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
}
else
else if (remapToKey)
{
// 1 for releasing new key and original shortcut modifiers except the one released and dummy key
key_count = dest_size + src_size - 2 + KeyboardManagerConstants::DUMMY_KEY_EVENT_SIZE;
@ -432,13 +469,13 @@ namespace KeyboardEventHandlers
}
// The system will see the modifiers of the new shortcut as being held down because of the shortcut remap
if (!remapToShortcut || std::get<Shortcut>(it->second.targetShortcut).CheckModifiersKeyboardState(ii))
if (!remapToShortcut || (remapToShortcut && std::get<Shortcut>(it->second.targetShortcut).CheckModifiersKeyboardState(ii)))
{
// 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<DWORD>(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED)
if (remapToKey && std::get<DWORD>(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED)
{
// Since the original shortcut's action key is pressed, set it to true
it->second.isOriginalActionKeyPressed = true;
@ -446,15 +483,34 @@ namespace KeyboardEventHandlers
}
size_t key_count = 1;
LPINPUT keyEventList = new INPUT[key_count]{};
LPINPUT keyEventList = nullptr;
if (remapToShortcut)
{
keyEventList = new INPUT[key_count]{};
Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD, static_cast<WORD>(std::get<Shortcut>(it->second.targetShortcut).GetActionKey()), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
}
else
else if (remapToKey)
{
keyEventList = new INPUT[key_count]{};
Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD, static_cast<WORD>(Helpers::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut))), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
}
else if (remapToText)
{
auto remapping = std::get<std::wstring>(it->second.targetShortcut);
const UINT inputEventCount = static_cast<UINT>(remapping.length() * 2);
key_count = inputEventCount;
keyEventList = new INPUT[key_count]{};
for (size_t idx = 0; idx < inputEventCount; ++idx)
{
auto& input = keyEventList[idx];
input.type = INPUT_KEYBOARD;
const bool upEvent = idx & 0x1;
input.ki.dwFlags = KEYEVENTF_UNICODE | (upEvent ? KEYEVENTF_KEYUP : 0);
input.ki.dwExtraInfo = KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG;
input.ki.wScan = remapping[idx >> 1];
}
}
UINT res = ii.SendVirtualInput(static_cast<UINT>(key_count), keyEventList, sizeof(INPUT));
delete[] keyEventList;
@ -462,10 +518,10 @@ namespace KeyboardEventHandlers
}
// Case 3: If the action key is released from the original shortcut, keep modifiers of the new shortcut until some other key event which doesn't apply to the original shortcut
if (data->lParam->vkCode == it->first.GetActionKey() && (data->wParam == WM_KEYUP || data->wParam == WM_SYSKEYUP))
if (!remapToText && data->lParam->vkCode == it->first.GetActionKey() && (data->wParam == WM_KEYUP || data->wParam == WM_SYSKEYUP))
{
size_t key_count = 1;
LPINPUT keyEventList;
LPINPUT keyEventList = nullptr;
if (remapToShortcut)
{
keyEventList = new INPUT[key_count]{};
@ -683,16 +739,22 @@ namespace KeyboardEventHandlers
// For remap to key, if the original action key is not currently pressed, we should revert the keyboard state to the physical keys. If it is pressed we should not suppress the event so that shortcut to key remaps can be pressed with other keys. Example use-case: Alt+D->Win, allows Alt+D+A to perform Win+A
// Modifier state reset might be required for this key depending on the target key - ex: Ctrl+A -> Caps, Shift is pressed. System should not see Shift and Caps pressed together
ResetIfModifierKeyForLowerLevelKeyHandlers(ii, data->lParam->vkCode, Helpers::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut)));
auto maybeTargetKey = std::get_if<DWORD>(&it->second.targetShortcut);
if (maybeTargetKey)
{
ResetIfModifierKeyForLowerLevelKeyHandlers(ii, data->lParam->vkCode, Helpers::FilterArtificialKeys(*maybeTargetKey));
}
// If the shortcut is remapped to Disable then we have to revert the keyboard state to the physical keys
bool isRemapToDisable = (std::get<DWORD>(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED);
bool isRemapToDisable = maybeTargetKey && (*maybeTargetKey == CommonSharedConstants::VK_DISABLED);
bool isOriginalActionKeyPressed = false;
if (!isRemapToDisable)
if (maybeTargetKey && !isRemapToDisable)
{
// If the remap target key is currently pressed, then we do not have to revert the keyboard state to the physical keys
if (ii.GetVirtualKeyState((Helpers::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut)))))
if (ii.GetVirtualKeyState((Helpers::FilterArtificialKeys(*maybeTargetKey))))
{
isOriginalActionKeyPressed = true;
}
@ -850,4 +912,43 @@ namespace KeyboardEventHandlers
}
}
}
// Function to generate a unicode string in response to a single keypress
intptr_t HandleSingleKeyToTextRemapEvent(KeyboardManagerInput::InputInterface& ii, LowlevelKeyboardEvent* data, State& state)
{
if (GeneratedByKBM(data))
{
return 0;
}
// Only send the text on keydown event
if (data->wParam != WM_KEYDOWN)
{
return 0;
}
const auto remapping = state.GetSingleKeyToTextRemapEvent(data->lParam->vkCode);
if (!remapping)
{
return 0;
}
const size_t keyCount = remapping->length();
const UINT eventCount = static_cast<UINT>(keyCount * 2);
auto keyEventList = std::make_unique<INPUT[]>(keyCount * 2);
for (size_t i = 0; i < eventCount; ++i)
{
auto& input = keyEventList[i];
input.type = INPUT_KEYBOARD;
const bool upEvent = i & 0x1;
input.ki.dwFlags = KEYEVENTF_UNICODE | (upEvent ? KEYEVENTF_KEYUP : 0);
input.ki.dwExtraInfo = KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG;
input.ki.wScan = (*remapping)[i >> 1];
}
UINT res = ii.SendVirtualInput(eventCount, keyEventList.get(), sizeof(INPUT));
return 1;
}
}

View File

@ -27,6 +27,9 @@ namespace KeyboardEventHandlers
// Function to a handle an app-specific shortcut remap
intptr_t HandleAppSpecificShortcutRemapEvent(KeyboardManagerInput::InputInterface& ii, LowlevelKeyboardEvent* data, State& state) noexcept;
// Function to generate a unicode string in response to a single keypress
intptr_t HandleSingleKeyToTextRemapEvent(KeyboardManagerInput::InputInterface& ii, LowlevelKeyboardEvent* data, State& state);
// Function to ensure Ctrl/Shift/Alt modifier key state is not detected as pressed down by applications which detect keys at a lower level than hooks when it is remapped for scenarios where its required
void ResetIfModifierKeyForLowerLevelKeyHandlers(KeyboardManagerInput::InputInterface& ii, DWORD key, DWORD target);
};

View File

@ -1,4 +1,4 @@
#include "pch.h"
#include "pch.h"
#include "KeyboardManager.h"
#include <interface/powertoy_module_interface.h>
#include <common/SettingsAPI/settings_objects.h>
@ -68,12 +68,13 @@ void KeyboardManager::LoadSettings()
LRESULT CALLBACK KeyboardManager::HookProc(int nCode, const WPARAM wParam, const LPARAM lParam)
{
LowlevelKeyboardEvent event;
LowlevelKeyboardEvent event{};
if (nCode == HC_ACTION)
{
event.lParam = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam);
event.wParam = wParam;
event.lParam->vkCode = Helpers::EncodeKeyNumpadOrigin(event.lParam->vkCode, event.lParam->flags & LLKHF_EXTENDED);
if (keyboardManagerObjectPtr->HandleKeyboardHookEvent(&event) == 1)
{
// Reset Num Lock whenever a NumLock key down event is suppressed since Num Lock key state change occurs before it is intercepted by low level hooks
@ -162,6 +163,13 @@ intptr_t KeyboardManager::HandleKeyboardHookEvent(LowlevelKeyboardEvent* data) n
return 1;
}
intptr_t SingleKeyToTextRemapResult = KeyboardEventHandlers::HandleSingleKeyToTextRemapEvent(inputHandler, data, state);
if (SingleKeyToTextRemapResult == 1)
{
return 1;
}
// Handle an os-level shortcut remapping
return KeyboardEventHandlers::HandleOSLevelShortcutRemapEvent(inputHandler, data, state);
}

View File

@ -14,6 +14,18 @@ std::optional<SingleKeyRemapTable::iterator> State::GetSingleKeyRemap(const DWOR
return std::nullopt;
}
std::optional<std::wstring> State::GetSingleKeyToTextRemapEvent(const DWORD originalKey) const
{
if (auto it = singleKeyToTextReMap.find(originalKey); it != end(singleKeyToTextReMap))
{
return std::get<std::wstring>(it->second);
}
else
{
return std::nullopt;
}
}
bool State::CheckShortcutRemapInvoked(const std::optional<std::wstring>& appName)
{
// Assumes appName exists in the app-specific remap table

View File

@ -11,6 +11,9 @@ public:
// Function to get the iterator of a single key remap given the source key. Returns nullopt if it isn't remapped
std::optional<SingleKeyRemapTable::iterator> GetSingleKeyRemap(const DWORD& originalKey);
// Function to get a unicode string remap given the source key. Returns nullopt if it isn't remapped
std::optional<std::wstring> GetSingleKeyToTextRemapEvent(const DWORD originalKey) const;
bool CheckShortcutRemapInvoked(const std::optional<std::wstring>& appName);
// Function to get the source and target of a shortcut remap given the source shortcut. Returns nullopt if it isn't remapped

View File

@ -3,9 +3,9 @@
namespace KeyboardManagerConstants
{
// Event name for signaling settings changes
inline const std::wstring SettingsEventName = L"PowerToys_KeyboardManager_Event_Settings";
inline const std::wstring SettingsEventName = L"PowerToys_KeyboardManager_Event_Settings";
inline const std::wstring EditorWindowEventName = L"PowerToys_KeyboardManager_Event_EditorWindow";
inline const std::wstring EditorWindowEventName = L"PowerToys_KeyboardManager_Event_EditorWindow";
// Name of the powertoy module.
inline const std::wstring ModuleName = L"Keyboard Manager";
@ -16,12 +16,18 @@ namespace KeyboardManagerConstants
// Name of the property use to store single keyremaps.
inline const std::wstring RemapKeysSettingName = L"remapKeys";
// Name of the property use to store single to text keyremaps.
inline const std::wstring RemapKeysToTextSettingName = L"remapKeysToText";
// Name of the property use to store single keyremaps array in case of in process approach.
inline const std::wstring InProcessRemapKeysSettingName = L"inProcess";
// Name of the property use to store shortcut remaps.
inline const std::wstring RemapShortcutsSettingName = L"remapShortcuts";
// Name of the property use to store shortcut to text remaps.
inline const std::wstring RemapShortcutsToTextSettingName = L"remapShortcutsToText";
// Name of the property use to store global shortcut remaps array.
inline const std::wstring GlobalRemapShortcutsSettingName = L"global";
@ -34,6 +40,9 @@ namespace KeyboardManagerConstants
// Name of the property use to store new remap keys.
inline const std::wstring NewRemapKeysSettingName = L"newRemapKeys";
// Name of the property use to store new remapped string.
inline const std::wstring NewTextSettingName = L"unicodeText";
// Name of the property use to store the target application.
inline const std::wstring TargetAppSettingName = L"targetApp";
@ -44,7 +53,6 @@ namespace KeyboardManagerConstants
inline const int MinimumEditKeyboardWindowWidth = 200;
inline const int MinimumEditKeyboardWindowHeight = 200;
// Flags used for distinguishing key events sent by Keyboard Manager
inline const ULONG_PTR KEYBOARDMANAGER_SINGLEKEY_FLAG = 0x11; // Single key remaps
inline const ULONG_PTR KEYBOARDMANAGER_SHORTCUT_FLAG = 0x101; // Shortcut remaps

View File

@ -1,4 +1,4 @@
#include "pch.h"
#include "pch.h"
#include "MappingConfiguration.h"
#include <common/SettingsAPI/settings_objects.h>
@ -17,13 +17,18 @@ void MappingConfiguration::ClearOSLevelShortcuts()
osLevelShortcutReMapSortedKeys.clear();
}
// Function to clear the Keys remapping table.
void MappingConfiguration::ClearSingleKeyRemaps()
{
singleKeyReMap.clear();
}
// Function to clear the Keys remapping table.
void MappingConfiguration::ClearSingleKeyToTextRemaps()
{
singleKeyToTextReMap.clear();
}
// Function to clear the App specific shortcut remapping table
void MappingConfiguration::ClearAppSpecificShortcuts()
{
@ -32,7 +37,7 @@ void MappingConfiguration::ClearAppSpecificShortcuts()
}
// Function to add a new OS level shortcut remapping
bool MappingConfiguration::AddOSLevelShortcut(const Shortcut& originalSC, const KeyShortcutUnion& newSC)
bool MappingConfiguration::AddOSLevelShortcut(const Shortcut& originalSC, const KeyShortcutTextUnion& newSC)
{
// Check if the shortcut is already remapped
auto it = osLevelShortcutReMap.find(originalSC);
@ -49,7 +54,7 @@ bool MappingConfiguration::AddOSLevelShortcut(const Shortcut& originalSC, const
}
// Function to add a new single key to key/shortcut remapping
bool MappingConfiguration::AddSingleKeyRemap(const DWORD& originalKey, const KeyShortcutUnion& newRemapKey)
bool MappingConfiguration::AddSingleKeyRemap(const DWORD& originalKey, const KeyShortcutTextUnion& newRemapKey)
{
// Check if the key is already remapped
auto it = singleKeyReMap.find(originalKey);
@ -62,8 +67,21 @@ bool MappingConfiguration::AddSingleKeyRemap(const DWORD& originalKey, const Key
return true;
}
bool MappingConfiguration::AddSingleKeyToTextRemap(const DWORD originalKey, const std::wstring& text)
{
if (auto it = singleKeyToTextReMap.find(originalKey); it != end(singleKeyToTextReMap))
{
return false;
}
else
{
singleKeyToTextReMap[originalKey] = text;
return true;
}
}
// Function to add a new App specific shortcut remapping
bool MappingConfiguration::AddAppSpecificShortcut(const std::wstring& app, const Shortcut& originalSC, const KeyShortcutUnion& newSC)
bool MappingConfiguration::AddAppSpecificShortcut(const std::wstring& app, const Shortcut& originalSC, const KeyShortcutTextUnion& newSC)
{
// Convert app name to lower case
std::wstring process_name;
@ -92,7 +110,6 @@ bool MappingConfiguration::AddAppSpecificShortcut(const std::wstring& app, const
return true;
}
bool MappingConfiguration::LoadSingleKeyRemaps(const json::JsonObject& jsonData)
{
bool result = true;
@ -141,6 +158,45 @@ bool MappingConfiguration::LoadSingleKeyRemaps(const json::JsonObject& jsonData)
return result;
}
bool MappingConfiguration::LoadSingleKeyToTextRemaps(const json::JsonObject& jsonData)
{
bool result = true;
try
{
auto remapKeysData = jsonData.GetNamedObject(KeyboardManagerConstants::RemapKeysToTextSettingName);
ClearSingleKeyToTextRemaps();
if (!remapKeysData)
{
return result;
}
auto inProcessRemapKeys = remapKeysData.GetNamedArray(KeyboardManagerConstants::InProcessRemapKeysSettingName);
for (const auto& it : inProcessRemapKeys)
{
try
{
auto originalKey = it.GetObjectW().GetNamedString(KeyboardManagerConstants::OriginalKeysSettingName);
auto newText = it.GetObjectW().GetNamedString(KeyboardManagerConstants::NewTextSettingName);
AddSingleKeyToTextRemap(std::stoul(originalKey.c_str()), newText.c_str());
}
catch (...)
{
Logger::error(L"Improper Key Data JSON. Try the next remap.");
result = false;
}
}
}
catch (...)
{
Logger::error(L"Improper JSON format for single key to text remaps. Skip to next remap type");
result = false;
}
return result;
}
bool MappingConfiguration::LoadAppSpecificShortcutRemaps(const json::JsonObject& remapShortcutsData)
{
bool result = true;
@ -153,19 +209,27 @@ bool MappingConfiguration::LoadAppSpecificShortcutRemaps(const json::JsonObject&
try
{
auto originalKeys = it.GetObjectW().GetNamedString(KeyboardManagerConstants::OriginalKeysSettingName);
auto newRemapKeys = it.GetObjectW().GetNamedString(KeyboardManagerConstants::NewRemapKeysSettingName);
auto newRemapKeys = it.GetObjectW().GetNamedString(KeyboardManagerConstants::NewRemapKeysSettingName, {});
auto newRemapText = it.GetObjectW().GetNamedString(KeyboardManagerConstants::NewTextSettingName, {});
auto targetApp = it.GetObjectW().GetNamedString(KeyboardManagerConstants::TargetAppSettingName);
// If remapped to a shortcut
if (std::wstring(newRemapKeys).find(L";") != std::string::npos)
if (!newRemapKeys.empty())
{
AddAppSpecificShortcut(targetApp.c_str(), Shortcut(originalKeys.c_str()), Shortcut(newRemapKeys.c_str()));
}
// If remapped to a shortcut
if (std::wstring(newRemapKeys).find(L";") != std::string::npos)
{
AddAppSpecificShortcut(targetApp.c_str(), Shortcut(originalKeys.c_str()), Shortcut(newRemapKeys.c_str()));
}
// If remapped to a key
// If remapped to a key
else
{
AddAppSpecificShortcut(targetApp.c_str(), Shortcut(originalKeys.c_str()), std::stoul(newRemapKeys.c_str()));
}
}
else
{
AddAppSpecificShortcut(targetApp.c_str(), Shortcut(originalKeys.c_str()), std::stoul(newRemapKeys.c_str()));
AddAppSpecificShortcut(targetApp.c_str(), Shortcut(originalKeys.c_str()), newRemapText.c_str());
}
}
catch (...)
@ -184,15 +248,13 @@ bool MappingConfiguration::LoadAppSpecificShortcutRemaps(const json::JsonObject&
return result;
}
bool MappingConfiguration::LoadShortcutRemaps(const json::JsonObject& jsonData)
bool MappingConfiguration::LoadShortcutRemaps(const json::JsonObject& jsonData, const std::wstring& objectName)
{
bool result = true;
try
{
auto remapShortcutsData = jsonData.GetNamedObject(KeyboardManagerConstants::RemapShortcutsSettingName);
ClearOSLevelShortcuts();
ClearAppSpecificShortcuts();
auto remapShortcutsData = jsonData.GetNamedObject(objectName);
if (remapShortcutsData)
{
// Load os level shortcut remaps
@ -204,18 +266,25 @@ bool MappingConfiguration::LoadShortcutRemaps(const json::JsonObject& jsonData)
try
{
auto originalKeys = it.GetObjectW().GetNamedString(KeyboardManagerConstants::OriginalKeysSettingName);
auto newRemapKeys = it.GetObjectW().GetNamedString(KeyboardManagerConstants::NewRemapKeysSettingName);
auto newRemapKeys = it.GetObjectW().GetNamedString(KeyboardManagerConstants::NewRemapKeysSettingName, {});
auto newRemapText = it.GetObjectW().GetNamedString(KeyboardManagerConstants::NewTextSettingName, {});
// If remapped to a shortcut
if (std::wstring(newRemapKeys).find(L";") != std::string::npos)
if (!newRemapKeys.empty())
{
AddOSLevelShortcut(Shortcut(originalKeys.c_str()), Shortcut(newRemapKeys.c_str()));
// If remapped to a shortcut
if (std::wstring(newRemapKeys).find(L";") != std::string::npos)
{
AddOSLevelShortcut(Shortcut(originalKeys.c_str()), Shortcut(newRemapKeys.c_str()));
}
// If remapped to a key
else
{
AddOSLevelShortcut(Shortcut(originalKeys.c_str()), std::stoul(newRemapKeys.c_str()));
}
}
// If remapped to a key
else
{
AddOSLevelShortcut(Shortcut(originalKeys.c_str()), std::stoul(newRemapKeys.c_str()));
AddOSLevelShortcut(Shortcut(originalKeys.c_str()), newRemapText.c_str());
}
}
catch (...)
@ -244,10 +313,6 @@ bool MappingConfiguration::LoadShortcutRemaps(const json::JsonObject& jsonData)
return result;
}
MappingConfiguration::MappingConfiguration()
{
}
bool MappingConfiguration::LoadSettings()
{
Logger::trace(L"SettingsHelper::LoadSettings()");
@ -271,7 +336,11 @@ bool MappingConfiguration::LoadSettings()
}
bool result = LoadSingleKeyRemaps(*configFile);
result = result && LoadShortcutRemaps(*configFile);
ClearOSLevelShortcuts();
ClearAppSpecificShortcuts();
result = LoadShortcutRemaps(*configFile, KeyboardManagerConstants::RemapShortcutsSettingName) && result;
result = LoadShortcutRemaps(*configFile, KeyboardManagerConstants::RemapShortcutsToTextSettingName) && result;
result = LoadSingleKeyToTextRemaps(*configFile) && result;
return result;
}
@ -289,10 +358,20 @@ bool MappingConfiguration::SaveSettingsToFile()
bool result = true;
json::JsonObject configJson;
json::JsonObject remapShortcuts;
json::JsonObject remapShortcutsToText;
json::JsonObject remapKeys;
json::JsonObject remapKeysToText;
json::JsonArray inProcessRemapKeysArray;
json::JsonArray inProcessRemapKeysToTextArray;
json::JsonArray appSpecificRemapShortcutsArray;
json::JsonArray appSpecificRemapShortcutsToTextArray;
json::JsonArray globalRemapShortcutsArray;
json::JsonArray globalRemapShortcutsToTextArray;
for (const auto& it : singleKeyReMap)
{
json::JsonObject keys;
@ -313,24 +392,43 @@ bool MappingConfiguration::SaveSettingsToFile()
inProcessRemapKeysArray.Append(keys);
}
for (const auto& [code, text] : singleKeyToTextReMap)
{
json::JsonObject keys;
keys.SetNamedValue(KeyboardManagerConstants::OriginalKeysSettingName, json::value(winrt::to_hstring(static_cast<unsigned int>(code))));
keys.SetNamedValue(KeyboardManagerConstants::NewTextSettingName, json::value(std::get<std::wstring>(text)));
inProcessRemapKeysToTextArray.Append(keys);
}
for (const auto& it : osLevelShortcutReMap)
{
json::JsonObject keys;
keys.SetNamedValue(KeyboardManagerConstants::OriginalKeysSettingName, json::value(it.first.ToHstringVK()));
bool remapToText = false;
// For shortcut to key remapping
if (it.second.targetShortcut.index() == 0)
{
keys.SetNamedValue(KeyboardManagerConstants::NewRemapKeysSettingName, json::value(winrt::to_hstring((unsigned int)std::get<DWORD>(it.second.targetShortcut))));
}
// For shortcut to shortcut remapping
else
else if (it.second.targetShortcut.index() == 1)
{
keys.SetNamedValue(KeyboardManagerConstants::NewRemapKeysSettingName, json::value(std::get<Shortcut>(it.second.targetShortcut).ToHstringVK()));
}
// For shortcut to text remapping
else if (it.second.targetShortcut.index() == 2)
{
remapToText = true;
keys.SetNamedValue(KeyboardManagerConstants::NewTextSettingName, json::value(std::get<std::wstring>(it.second.targetShortcut)));
}
globalRemapShortcutsArray.Append(keys);
if (!remapToText)
globalRemapShortcutsArray.Append(keys);
else
globalRemapShortcutsToTextArray.Append(keys);
}
for (const auto& itApp : appSpecificShortcutReMap)
@ -341,6 +439,8 @@ bool MappingConfiguration::SaveSettingsToFile()
json::JsonObject keys;
keys.SetNamedValue(KeyboardManagerConstants::OriginalKeysSettingName, json::value(itKeys.first.ToHstringVK()));
bool remapToText = false;
// For shortcut to key remapping
if (itKeys.second.targetShortcut.index() == 0)
{
@ -348,22 +448,37 @@ bool MappingConfiguration::SaveSettingsToFile()
}
// For shortcut to shortcut remapping
else
else if (itKeys.second.targetShortcut.index() == 1)
{
keys.SetNamedValue(KeyboardManagerConstants::NewRemapKeysSettingName, json::value(std::get<Shortcut>(itKeys.second.targetShortcut).ToHstringVK()));
}
else if (itKeys.second.targetShortcut.index() == 2)
{
keys.SetNamedValue(KeyboardManagerConstants::NewTextSettingName, json::value(std::get<std::wstring>(itKeys.second.targetShortcut)));
remapToText = true;
}
keys.SetNamedValue(KeyboardManagerConstants::TargetAppSettingName, json::value(itApp.first));
appSpecificRemapShortcutsArray.Append(keys);
if (!remapToText)
appSpecificRemapShortcutsArray.Append(keys);
else
appSpecificRemapShortcutsToTextArray.Append(keys);
}
}
remapShortcuts.SetNamedValue(KeyboardManagerConstants::GlobalRemapShortcutsSettingName, globalRemapShortcutsArray);
remapShortcuts.SetNamedValue(KeyboardManagerConstants::AppSpecificRemapShortcutsSettingName, appSpecificRemapShortcutsArray);
remapShortcutsToText.SetNamedValue(KeyboardManagerConstants::GlobalRemapShortcutsSettingName, globalRemapShortcutsToTextArray);
remapShortcutsToText.SetNamedValue(KeyboardManagerConstants::AppSpecificRemapShortcutsSettingName, appSpecificRemapShortcutsToTextArray);
remapKeys.SetNamedValue(KeyboardManagerConstants::InProcessRemapKeysSettingName, inProcessRemapKeysArray);
remapKeysToText.SetNamedValue(KeyboardManagerConstants::InProcessRemapKeysSettingName, inProcessRemapKeysToTextArray);
configJson.SetNamedValue(KeyboardManagerConstants::RemapKeysSettingName, remapKeys);
configJson.SetNamedValue(KeyboardManagerConstants::RemapKeysToTextSettingName, remapKeysToText);
configJson.SetNamedValue(KeyboardManagerConstants::RemapShortcutsSettingName, remapShortcuts);
configJson.SetNamedValue(KeyboardManagerConstants::RemapShortcutsToTextSettingName, remapShortcutsToText);
try
{

View File

@ -6,15 +6,14 @@
#include <keyboardmanager/common/Shortcut.h>
#include <keyboardmanager/common/RemapShortcut.h>
using SingleKeyRemapTable = std::unordered_map<DWORD, KeyShortcutUnion>;
using SingleKeyRemapTable = std::unordered_map<DWORD, KeyShortcutTextUnion>;
using SingleKeyToTextRemapTable = SingleKeyRemapTable;
using ShortcutRemapTable = std::map<Shortcut, RemapShortcut>;
using AppSpecificShortcutRemapTable = std::map<std::wstring, ShortcutRemapTable>;
class MappingConfiguration
{
public:
MappingConfiguration();
~MappingConfiguration() = default;
// Load the configuration.
@ -29,22 +28,31 @@ public:
// Function to clear the Keys remapping table
void ClearSingleKeyRemaps();
// Function to clear the Keys to text remapping table
void ClearSingleKeyToTextRemaps();
// Function to clear the App specific shortcut remapping table
void ClearAppSpecificShortcuts();
// Function to add a new single key to key remapping
bool AddSingleKeyRemap(const DWORD& originalKey, const KeyShortcutUnion& newRemapKey);
bool AddSingleKeyRemap(const DWORD& originalKey, const KeyShortcutTextUnion& newRemapKey);
// Function to add a new single key to unicode string remapping
bool AddSingleKeyToTextRemap(const DWORD originalKey, const std::wstring& text);
// Function to add a new OS level shortcut remapping
bool AddOSLevelShortcut(const Shortcut& originalSC, const KeyShortcutUnion& newSC);
bool AddOSLevelShortcut(const Shortcut& originalSC, const KeyShortcutTextUnion& newSC);
// Function to add a new App specific level shortcut remapping
bool AddAppSpecificShortcut(const std::wstring& app, const Shortcut& originalSC, const KeyShortcutUnion& newSC);
bool AddAppSpecificShortcut(const std::wstring& app, const Shortcut& originalSC, const KeyShortcutTextUnion& newSC);
// The map members and their mutexes are left as public since the maps are used extensively in dllmain.cpp.
// Maps which store the remappings for each of the features. The bool fields should be initialized to false. They are used to check the current state of the shortcut (i.e is that particular shortcut currently pressed down or not).
// Stores single key remappings
std::unordered_map<DWORD, KeyShortcutUnion> singleKeyReMap;
SingleKeyRemapTable singleKeyReMap;
// Stores single key to text remappings
SingleKeyToTextRemapTable singleKeyToTextReMap;
// Stores the os level shortcut remappings
ShortcutRemapTable osLevelShortcutReMap;
@ -57,9 +65,9 @@ public:
// Stores the current configuration name.
std::wstring currentConfig = KeyboardManagerConstants::DefaultConfiguration;
private:
bool LoadSingleKeyRemaps(const json::JsonObject& jsonData);
bool LoadShortcutRemaps(const json::JsonObject& jsonData);
bool LoadSingleKeyToTextRemaps(const json::JsonObject& jsonData);
bool LoadShortcutRemaps(const json::JsonObject& jsonData, const std::wstring& objectName);
bool LoadAppSpecificShortcutRemaps(const json::JsonObject& remapShortcutsData);
};

View File

@ -6,13 +6,13 @@
class RemapShortcut
{
public:
KeyShortcutUnion targetShortcut;
KeyShortcutTextUnion targetShortcut;
bool isShortcutInvoked;
ModifierKey winKeyInvoked;
// This bool value is only required for remapping shortcuts to Disable
bool isOriginalActionKeyPressed;
RemapShortcut(const KeyShortcutUnion& sc) :
RemapShortcut(const KeyShortcutTextUnion& sc) :
targetShortcut(sc), isShortcutInvoked(false), winKeyInvoked(ModifierKey::Disabled), isOriginalActionKeyPressed(false)
{
}

View File

@ -108,7 +108,7 @@ public:
int GetCommonModifiersCount(const Shortcut& input) const;
};
using KeyShortcutUnion = std::variant<DWORD, Shortcut>;
using RemapBufferItem = std::vector<KeyShortcutUnion>;
using KeyShortcutTextUnion = std::variant<DWORD, Shortcut, std::wstring>;
using RemapBufferItem = std::vector<KeyShortcutTextUnion>;
using RemapBufferRow = std::pair<RemapBufferItem, std::wstring>;
using RemapBuffer = std::vector<RemapBufferRow>;

View File

@ -28,9 +28,10 @@ namespace Microsoft.PowerToys.Settings.UI.Library
ArgumentNullException.ThrowIfNull(arg);
// Using Ordinal comparison for internal text
return OriginalKeys.Equals(arg.OriginalKeys, StringComparison.Ordinal) &&
NewRemapKeys.Equals(arg.NewRemapKeys, StringComparison.Ordinal) &&
TargetApp.Equals(arg.TargetApp, StringComparison.Ordinal);
return string.Equals(OriginalKeys, arg.OriginalKeys, StringComparison.Ordinal) &&
string.Equals(NewRemapKeys, arg.NewRemapKeys, StringComparison.Ordinal) &&
string.Equals(NewRemapString, arg.NewRemapString, StringComparison.Ordinal) &&
string.Equals(TargetApp, arg.TargetApp, StringComparison.Ordinal);
}
}
}

View File

@ -13,13 +13,22 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("remapKeys")]
public RemapKeysDataModel RemapKeys { get; set; }
[JsonPropertyName("remapKeysToText")]
public RemapKeysDataModel RemapKeysToText { get; set; }
[JsonPropertyName("remapShortcuts")]
public ShortcutsKeyDataModel RemapShortcuts { get; set; }
[JsonPropertyName("remapShortcutsToText")]
public ShortcutsKeyDataModel RemapShortcutsToText { get; set; }
public KeyboardManagerProfile()
{
RemapKeys = new RemapKeysDataModel();
RemapKeysToText = new RemapKeysDataModel();
RemapShortcuts = new ShortcutsKeyDataModel();
RemapShortcutsToText = new ShortcutsKeyDataModel();
}
public string ToJsonString()

View File

@ -18,6 +18,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("newRemapKeys")]
public string NewRemapKeys { get; set; }
[JsonPropertyName("unicodeText")]
public string NewRemapString { get; set; }
private static List<string> MapKeys(string stringOfKeys)
{
return stringOfKeys
@ -34,7 +37,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public List<string> GetMappedNewRemapKeys()
{
return MapKeys(NewRemapKeys);
return string.IsNullOrEmpty(NewRemapString) ? MapKeys(NewRemapKeys) : new List<string> { NewRemapString };
}
public string ToJsonString()

View File

@ -306,6 +306,13 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
KeyboardManagerProfile kbmProfile = GetKBMProfile();
_kbmItem = new DashboardModuleKBMItem() { RemapKeys = kbmProfile?.RemapKeys.InProcessRemapKeys, RemapShortcuts = KeyboardManagerViewModel.CombineShortcutLists(kbmProfile?.RemapShortcuts.GlobalRemapShortcuts, kbmProfile?.RemapShortcuts.AppSpecificRemapShortcuts) };
_kbmItem.RemapKeys = _kbmItem.RemapKeys.Concat(kbmProfile?.RemapKeysToText.InProcessRemapKeys).ToList();
var shortcutsToTextRemappings = KeyboardManagerViewModel.CombineShortcutLists(kbmProfile?.RemapShortcutsToText.GlobalRemapShortcuts, kbmProfile?.RemapShortcutsToText.AppSpecificRemapShortcuts);
_kbmItem.RemapShortcuts = _kbmItem.RemapShortcuts.Concat(shortcutsToTextRemappings).ToList();
var list = new List<DashboardModuleItem>
{
_kbmItem,

View File

@ -158,7 +158,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
if (_profile != null)
{
return _profile.RemapKeys.InProcessRemapKeys;
return _profile.RemapKeys.InProcessRemapKeys.Concat(_profile.RemapKeysToText.InProcessRemapKeys).ToList();
}
else
{
@ -190,11 +190,11 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
else if (appSpecificShortcutList == null)
{
return globalShortcutList.ConvertAll(x => new AppSpecificKeysDataModel { OriginalKeys = x.OriginalKeys, NewRemapKeys = x.NewRemapKeys, TargetApp = allAppsDescription }).ToList();
return globalShortcutList.ConvertAll(x => new AppSpecificKeysDataModel { OriginalKeys = x.OriginalKeys, NewRemapKeys = x.NewRemapKeys, NewRemapString = x.NewRemapString, TargetApp = allAppsDescription }).ToList();
}
else
{
return globalShortcutList.ConvertAll(x => new AppSpecificKeysDataModel { OriginalKeys = x.OriginalKeys, NewRemapKeys = x.NewRemapKeys, TargetApp = allAppsDescription }).Concat(appSpecificShortcutList).ToList();
return globalShortcutList.ConvertAll(x => new AppSpecificKeysDataModel { OriginalKeys = x.OriginalKeys, NewRemapKeys = x.NewRemapKeys, NewRemapString = x.NewRemapString, TargetApp = allAppsDescription }).Concat(appSpecificShortcutList).ToList();
}
}
@ -204,7 +204,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
if (_profile != null)
{
return CombineShortcutLists(_profile.RemapShortcuts.GlobalRemapShortcuts, _profile.RemapShortcuts.AppSpecificRemapShortcuts);
return CombineShortcutLists(_profile.RemapShortcuts.GlobalRemapShortcuts, _profile.RemapShortcuts.AppSpecificRemapShortcuts).Concat(CombineShortcutLists(_profile.RemapShortcutsToText.GlobalRemapShortcuts, _profile.RemapShortcutsToText.AppSpecificRemapShortcuts)).ToList();
}
else
{
@ -333,6 +333,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
if (readSuccessfully)
{
FilterRemapKeysList(_profile?.RemapKeys?.InProcessRemapKeys);
FilterRemapKeysList(_profile?.RemapKeysToText?.InProcessRemapKeys);
}
else
{

View File

@ -27,7 +27,9 @@ using namespace winrt::Windows::Data::Json;
map<wstring, vector<wstring>> escapeInfo = {
{ L"FancyZones\\app-zone-history.json", { L"app-zone-history/app-path" } },
{ L"FancyZones\\settings.json", { L"properties/fancyzones_excluded_apps" } }
{ L"FancyZones\\settings.json", { L"properties/fancyzones_excluded_apps" } },
{ L"MouseWithoutBorders\\settings.json", { L"properties/SecurityKey" } }, // avoid leaking connection key
{ L"Keyboard Manager\\default.json", { L"remapKeysToText", L"remapShortcutsToText" } }, // avoid leaking personal information from text mappings
};
vector<wstring> filesToDelete = {