[KBM] Refactor KBM's thread safety logic to avoid mutex re-entrancy bugs and improve performance (#6803)

* Unlock mutex before ResetModifierForLowerLevelKeyHandlers method to avoid crash if two instances of KBM are running

* Added alias for Shortcut DWORD variant to clean up code

* Removed mutex usage in single key remap method and added GetSingleKeyRemap

* Added more alias

* Moved to boolean disable remapping

* Added missing ! in condition

* Remove lock statement from bad auto-merge
This commit is contained in:
Arjun Balgovind
2020-10-08 11:28:24 -07:00
committed by GitHub
parent 9928579364
commit e1d22c74b0
18 changed files with 190 additions and 109 deletions

View File

@@ -8,7 +8,7 @@
// Constructor
KeyboardManagerState::KeyboardManagerState() :
uiState(KeyboardManagerUIState::Deactivated), currentUIWindow(nullptr), currentShortcutUI1(nullptr), currentShortcutUI2(nullptr), currentSingleKeyUI(nullptr), detectedRemapKey(NULL)
uiState(KeyboardManagerUIState::Deactivated), currentUIWindow(nullptr), currentShortcutUI1(nullptr), currentShortcutUI2(nullptr), currentSingleKeyUI(nullptr), detectedRemapKey(NULL), remappingsEnabled(true)
{
configFile_mutex = CreateMutex(
NULL, // default security descriptor
@@ -104,7 +104,6 @@ void KeyboardManagerState::ResetUIState()
// Function to clear the OS Level shortcut remapping table
void KeyboardManagerState::ClearOSLevelShortcuts()
{
std::lock_guard<std::mutex> lock(osLevelShortcutReMap_mutex);
osLevelShortcutReMap.clear();
osLevelShortcutReMapSortedKeys.clear();
}
@@ -112,23 +111,19 @@ void KeyboardManagerState::ClearOSLevelShortcuts()
// Function to clear the Keys remapping table.
void KeyboardManagerState::ClearSingleKeyRemaps()
{
std::lock_guard<std::mutex> lock(singleKeyReMap_mutex);
singleKeyReMap.clear();
}
// Function to clear the App specific shortcut remapping table
void KeyboardManagerState::ClearAppSpecificShortcuts()
{
std::lock_guard<std::mutex> lock(appSpecificShortcutReMap_mutex);
appSpecificShortcutReMap.clear();
appSpecificShortcutReMapSortedKeys.clear();
}
// Function to add a new OS level shortcut remapping
bool KeyboardManagerState::AddOSLevelShortcut(const Shortcut& originalSC, const std::variant<DWORD, Shortcut>& newSC)
bool KeyboardManagerState::AddOSLevelShortcut(const Shortcut& originalSC, const KeyShortcutUnion& newSC)
{
std::lock_guard<std::mutex> lock(osLevelShortcutReMap_mutex);
// Check if the shortcut is already remapped
auto it = osLevelShortcutReMap.find(originalSC);
if (it != osLevelShortcutReMap.end())
@@ -144,10 +139,8 @@ bool KeyboardManagerState::AddOSLevelShortcut(const Shortcut& originalSC, const
}
// Function to add a new single key to key/shortcut remapping
bool KeyboardManagerState::AddSingleKeyRemap(const DWORD& originalKey, const std::variant<DWORD, Shortcut>& newRemapKey)
bool KeyboardManagerState::AddSingleKeyRemap(const DWORD& originalKey, const KeyShortcutUnion& newRemapKey)
{
std::lock_guard<std::mutex> lock(singleKeyReMap_mutex);
// Check if the key is already remapped
auto it = singleKeyReMap.find(originalKey);
if (it != singleKeyReMap.end())
@@ -160,10 +153,8 @@ bool KeyboardManagerState::AddSingleKeyRemap(const DWORD& originalKey, const std
}
// Function to add a new App specific shortcut remapping
bool KeyboardManagerState::AddAppSpecificShortcut(const std::wstring& app, const Shortcut& originalSC, const std::variant<DWORD, Shortcut>& newSC)
bool KeyboardManagerState::AddAppSpecificShortcut(const std::wstring& app, const Shortcut& originalSC, const KeyShortcutUnion& newSC)
{
std::lock_guard<std::mutex> lock(appSpecificShortcutReMap_mutex);
// Convert app name to lower case
std::wstring process_name;
process_name.resize(app.length());
@@ -191,6 +182,54 @@ bool KeyboardManagerState::AddAppSpecificShortcut(const std::wstring& app, const
return true;
}
// 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> KeyboardManagerState::GetSingleKeyRemap(const DWORD& originalKey)
{
auto it = singleKeyReMap.find(originalKey);
if (it != singleKeyReMap.end())
{
return it;
}
return std::nullopt;
}
bool KeyboardManagerState::CheckShortcutRemapInvoked(const std::optional<std::wstring>& appName)
{
// Assumes appName exists in the app-specific remap table
ShortcutRemapTable& currentRemapTable = appName ? appSpecificShortcutReMap[*appName] : osLevelShortcutReMap;
for (auto& it : currentRemapTable)
{
if (it.second.isShortcutInvoked)
{
return true;
}
}
return false;
}
std::vector<Shortcut>& KeyboardManagerState::GetSortedShortcutRemapVector(const std::optional<std::wstring>& appName)
{
// Assumes appName exists in the app-specific remap table
return appName ? appSpecificShortcutReMapSortedKeys[*appName] : osLevelShortcutReMapSortedKeys;
}
// Function to get the source and target of a shortcut remap given the source shortcut. Returns nullopt if it isn't remapped
ShortcutRemapTable& KeyboardManagerState::GetShortcutRemapTable(const std::optional<std::wstring>& appName)
{
if (appName)
{
auto itTable = appSpecificShortcutReMap.find(*appName);
if (itTable != appSpecificShortcutReMap.end())
{
return itTable->second;
}
}
return osLevelShortcutReMap;
}
// Function to set the textblock of the detect shortcut UI so that it can be accessed by the hook
void KeyboardManagerState::ConfigureDetectShortcutUI(const StackPanel& textBlock1, const StackPanel& textBlock2)
{
@@ -468,7 +507,6 @@ bool KeyboardManagerState::SaveConfigToFile()
json::JsonArray inProcessRemapKeysArray;
json::JsonArray appSpecificRemapShortcutsArray;
json::JsonArray globalRemapShortcutsArray;
std::unique_lock<std::mutex> lockSingleKeyReMap(singleKeyReMap_mutex);
for (const auto& it : singleKeyReMap)
{
json::JsonObject keys;
@@ -488,9 +526,7 @@ bool KeyboardManagerState::SaveConfigToFile()
inProcessRemapKeysArray.Append(keys);
}
lockSingleKeyReMap.unlock();
std::unique_lock<std::mutex> lockOsLevelShortcutReMap(osLevelShortcutReMap_mutex);
for (const auto& it : osLevelShortcutReMap)
{
json::JsonObject keys;
@@ -510,9 +546,7 @@ bool KeyboardManagerState::SaveConfigToFile()
globalRemapShortcutsArray.Append(keys);
}
lockOsLevelShortcutReMap.unlock();
std::unique_lock<std::mutex> lockAppSpecificShortcutReMap(appSpecificShortcutReMap_mutex);
for (const auto& itApp : appSpecificShortcutReMap)
{
// Iterate over apps
@@ -538,7 +572,6 @@ bool KeyboardManagerState::SaveConfigToFile()
appSpecificRemapShortcutsArray.Append(keys);
}
}
lockAppSpecificShortcutReMap.unlock();
remapShortcuts.SetNamedValue(KeyboardManagerConstants::GlobalRemapShortcutsSettingName, globalRemapShortcutsArray);
remapShortcuts.SetNamedValue(KeyboardManagerConstants::AppSpecificRemapShortcutsSettingName, appSpecificRemapShortcutsArray);
@@ -596,3 +629,20 @@ std::wstring KeyboardManagerState::GetActivatedApp()
{
return activatedAppSpecificShortcutTarget;
}
bool KeyboardManagerState::AreRemappingsEnabled()
{
return remappingsEnabled;
}
void KeyboardManagerState::RemappingsDisabledWrapper(std::function<void()> method)
{
// Disable keyboard remappings
remappingsEnabled = false;
// Run the method which requires the remappings to be disabled
method();
// Re-enable the keyboard remappings
remappingsEnabled = true;
}

View File

@@ -20,6 +20,10 @@ namespace winrt::Windows::UI::Xaml::Controls
struct StackPanel;
}
using SingleKeyRemapTable = std::unordered_map<DWORD, KeyShortcutUnion>;
using ShortcutRemapTable = std::map<Shortcut, RemapShortcut>;
using AppSpecificShortcutRemapTable = std::map<std::wstring, ShortcutRemapTable>;
// Enum type to store different states of the UI
enum class KeyboardManagerUIState
{
@@ -84,6 +88,9 @@ private:
// Stores the activated target application in app-specfic shortcut
std::wstring activatedAppSpecificShortcutTarget;
// Thread safe boolean value to check if remappings are currently enabled. This is used to disable remappings while the remap tables are being updated by the UI thread
std::atomic_bool remappingsEnabled;
// Display a key by appending a border Control as a child of the panel.
void AddKeyToLayout(const winrt::Windows::UI::Xaml::Controls::StackPanel& panel, const winrt::hstring& key);
@@ -91,22 +98,21 @@ public:
// 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, std::variant<DWORD, Shortcut>> singleKeyReMap;
std::mutex singleKeyReMap_mutex;
std::unordered_map<DWORD, KeyShortcutUnion> singleKeyReMap;
/* This feature has not been enabled (code from proof of concept stage)
*
// Stores keys which need to be changed from toggle behavior to modifier behavior. Eg. Caps Lock
std::unordered_map<DWORD, bool> singleKeyToggleToMod;
std::mutex singleKeyToggleToMod_mutex;
*/
// Stores the os level shortcut remappings
std::map<Shortcut, RemapShortcut> osLevelShortcutReMap;
ShortcutRemapTable osLevelShortcutReMap;
std::vector<Shortcut> osLevelShortcutReMapSortedKeys;
std::mutex osLevelShortcutReMap_mutex;
// Stores the app-specific shortcut remappings. Maps application name to the shortcut map
std::map<std::wstring, std::map<Shortcut, RemapShortcut>> appSpecificShortcutReMap;
AppSpecificShortcutRemapTable appSpecificShortcutReMap;
std::map<std::wstring, std::vector<Shortcut>> appSpecificShortcutReMapSortedKeys;
std::mutex appSpecificShortcutReMap_mutex;
// Stores the keyboard layout
LayoutMap keyboardMap;
@@ -139,13 +145,23 @@ public:
void ClearAppSpecificShortcuts();
// Function to add a new single key to key remapping
bool AddSingleKeyRemap(const DWORD& originalKey, const std::variant<DWORD, Shortcut>& newRemapKey);
bool AddSingleKeyRemap(const DWORD& originalKey, const KeyShortcutUnion& newRemapKey);
// Function to add a new OS level shortcut remapping
bool AddOSLevelShortcut(const Shortcut& originalSC, const std::variant<DWORD, Shortcut>& newSC);
bool AddOSLevelShortcut(const Shortcut& originalSC, const KeyShortcutUnion& newSC);
// Function to add a new App specific level shortcut remapping
bool AddAppSpecificShortcut(const std::wstring& app, const Shortcut& originalSC, const std::variant<DWORD, Shortcut>& newSC);
bool AddAppSpecificShortcut(const std::wstring& app, const Shortcut& originalSC, const KeyShortcutUnion& newSC);
// 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);
bool CheckShortcutRemapInvoked(const std::optional<std::wstring>& appName);
std::vector<Shortcut>& GetSortedShortcutRemapVector(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
ShortcutRemapTable& GetShortcutRemapTable(const std::optional<std::wstring>& appName);
// Function to set the textblock of the detect shortcut UI so that it can be accessed by the hook
void ConfigureDetectShortcutUI(const winrt::Windows::UI::Xaml::Controls::StackPanel& textBlock1, const winrt::Windows::UI::Xaml::Controls::StackPanel& textBlock2);
@@ -214,4 +230,8 @@ public:
// Gets the activated target application in app-specfic shortcut
std::wstring GetActivatedApp();
bool AreRemappingsEnabled();
void RemappingsDisabledWrapper(std::function<void()> method);
};

View File

@@ -6,11 +6,11 @@
class RemapShortcut
{
public:
std::variant<DWORD, Shortcut> targetShortcut;
KeyShortcutUnion targetShortcut;
bool isShortcutInvoked;
ModifierKey winKeyInvoked;
RemapShortcut(const std::variant<DWORD, Shortcut>& sc) :
RemapShortcut(const KeyShortcutUnion& sc) :
targetShortcut(sc), isShortcutInvoked(false), winKeyInvoked(ModifierKey::Disabled)
{
}

View File

@@ -171,6 +171,7 @@ public:
KeyboardManagerHelper::ErrorType IsShortcutIllegal() const;
};
using RemapBufferItem = std::vector<std::variant<DWORD, Shortcut>>;
using KeyShortcutUnion = std::variant<DWORD, Shortcut>;
using RemapBufferItem = std::vector<KeyShortcutUnion>;
using RemapBufferRow = std::pair<RemapBufferItem, std::wstring>;
using RemapBuffer = std::vector<RemapBufferRow>;

View File

@@ -16,11 +16,11 @@ namespace KeyboardEventHandlers
// Check if the key event was generated by KeyboardManager to avoid remapping events generated by us.
if (!(data->lParam->dwExtraInfo & CommonSharedConstants::KEYBOARDMANAGER_INJECTED_FLAG))
{
// The mutex should be unlocked before SendInput is called to avoid re-entry into the same mutex. More details can be found at https://github.com/microsoft/PowerToys/pull/1789#issuecomment-607555837
std::unique_lock<std::mutex> lock(keyboardManagerState.singleKeyReMap_mutex);
auto it = keyboardManagerState.singleKeyReMap.find(data->lParam->vkCode);
if (it != keyboardManagerState.singleKeyReMap.end())
auto& remapping = keyboardManagerState.GetSingleKeyRemap(data->lParam->vkCode);
if (remapping)
{
auto it = remapping.value();
// Check if the remap is to a key or a shortcut
bool remapToKey = (it->second.index() == 0);
@@ -95,7 +95,6 @@ namespace KeyboardEventHandlers
}
}
lock.unlock();
UINT res = ii.SendVirtualInput(key_count, keyEventList, sizeof(INPUT));
delete[] keyEventList;
@@ -126,6 +125,8 @@ namespace KeyboardEventHandlers
return 0;
}
/* This feature has not been enabled (code from proof of concept stage)
*
// Function to a change a key's behavior from toggle to modifier
__declspec(dllexport) intptr_t HandleSingleKeyToggleToModEvent(InputInterface& ii, LowlevelKeyboardEvent* data, KeyboardManagerState& keyboardManagerState) noexcept
{
@@ -174,26 +175,19 @@ namespace KeyboardEventHandlers
return 0;
}
*/
// Function to a handle a shortcut remap
__declspec(dllexport) intptr_t HandleShortcutRemapEvent(InputInterface& ii, LowlevelKeyboardEvent* data, std::map<Shortcut, RemapShortcut>& reMap, std::vector<Shortcut>& sortedReMapKeys, std::mutex& map_mutex, KeyboardManagerState& keyboardManagerState, const std::wstring& activatedApp) noexcept
__declspec(dllexport) intptr_t HandleShortcutRemapEvent(InputInterface& ii, LowlevelKeyboardEvent* data, KeyboardManagerState& keyboardManagerState, const std::optional<std::wstring>& activatedApp) noexcept
{
// The mutex should be unlocked before SendInput is called to avoid re-entry into the same mutex. More details can be found at https://github.com/microsoft/PowerToys/pull/1789#issuecomment-607555837
std::unique_lock<std::mutex> lock(map_mutex);
// Check if any shortcut is currently in the invoked state
bool isShortcutInvoked = false;
for (auto& it : reMap)
{
if (it.second.isShortcutInvoked)
{
isShortcutInvoked = true;
break;
}
}
bool isShortcutInvoked = keyboardManagerState.CheckShortcutRemapInvoked(activatedApp);
// Get shortcut table for given activatedApp
ShortcutRemapTable& reMap = keyboardManagerState.GetShortcutRemapTable(activatedApp);
// Iterate through the shortcut remaps and apply whichever has been pressed
for (auto& itShortcut : sortedReMapKeys)
for (auto& itShortcut : keyboardManagerState.GetSortedShortcutRemapVector(activatedApp))
{
auto& it = reMap.find(itShortcut);
@@ -317,16 +311,16 @@ namespace KeyboardEventHandlers
it->second.isShortcutInvoked = true;
// If app specific shortcut is invoked, store the target application
if (activatedApp != KeyboardManagerConstants::NoActivatedApp)
if (activatedApp)
{
keyboardManagerState.SetActivatedApp(activatedApp);
keyboardManagerState.SetActivatedApp(*activatedApp);
}
lock.unlock();
UINT res = ii.SendVirtualInput((UINT)key_count, keyEventList, sizeof(INPUT));
delete[] keyEventList;
// Log telemetry event when shortcut remap is invoked
Trace::ShortcutRemapInvoked(remapToShortcut, activatedApp != KeyboardManagerConstants::NoActivatedApp);
Trace::ShortcutRemapInvoked(remapToShortcut, activatedApp.has_value());
return 1;
}
@@ -415,11 +409,10 @@ namespace KeyboardEventHandlers
it->second.isShortcutInvoked = false;
it->second.winKeyInvoked = ModifierKey::Disabled;
// If app specific shortcut has finished invoking, reset the target application
if (activatedApp != KeyboardManagerConstants::NoActivatedApp)
if (activatedApp)
{
keyboardManagerState.SetActivatedApp(KeyboardManagerConstants::NoActivatedApp);
}
lock.unlock();
// key count can be 0 if both shortcuts have same modifiers and the action key is not held down. delete will throw an error if keyEventList is empty
if (key_count > 0)
@@ -455,7 +448,6 @@ namespace KeyboardEventHandlers
}
it->second.isShortcutInvoked = true;
lock.unlock();
UINT res = ii.SendVirtualInput((UINT)key_count, keyEventList, sizeof(INPUT));
delete[] keyEventList;
return 1;
@@ -507,13 +499,12 @@ namespace KeyboardEventHandlers
it->second.isShortcutInvoked = false;
it->second.winKeyInvoked = ModifierKey::Disabled;
// If app specific shortcut has finished invoking, reset the target application
if (activatedApp != KeyboardManagerConstants::NoActivatedApp)
if (activatedApp)
{
keyboardManagerState.SetActivatedApp(KeyboardManagerConstants::NoActivatedApp);
}
}
lock.unlock();
UINT res = ii.SendVirtualInput((UINT)key_count, keyEventList, sizeof(INPUT));
delete[] keyEventList;
return 1;
@@ -639,11 +630,11 @@ namespace KeyboardEventHandlers
it->second.isShortcutInvoked = false;
it->second.winKeyInvoked = ModifierKey::Disabled;
// If app specific shortcut has finished invoking, reset the target application
if (activatedApp != KeyboardManagerConstants::NoActivatedApp)
if (activatedApp)
{
keyboardManagerState.SetActivatedApp(KeyboardManagerConstants::NoActivatedApp);
}
lock.unlock();
UINT res = ii.SendVirtualInput((UINT)key_count, keyEventList, sizeof(INPUT));
delete[] keyEventList;
return 1;
@@ -680,7 +671,7 @@ namespace KeyboardEventHandlers
{
keyboardManagerState.SetActivatedApp(KeyboardManagerConstants::NoActivatedApp);
}
lock.unlock();
UINT res = ii.SendVirtualInput((UINT)key_count, keyEventList, sizeof(INPUT));
delete[] keyEventList;
return 1;
@@ -701,7 +692,7 @@ namespace KeyboardEventHandlers
// Check if the key event was generated by KeyboardManager to avoid remapping events generated by us.
if (data->lParam->dwExtraInfo != KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG)
{
bool result = HandleShortcutRemapEvent(ii, data, keyboardManagerState.osLevelShortcutReMap, keyboardManagerState.osLevelShortcutReMapSortedKeys, keyboardManagerState.osLevelShortcutReMap_mutex, keyboardManagerState);
bool result = HandleShortcutRemapEvent(ii, data, keyboardManagerState);
return result;
}
@@ -731,10 +722,9 @@ namespace KeyboardEventHandlers
// Convert process name to lower case
std::transform(process_name.begin(), process_name.end(), process_name.begin(), towlower);
std::unique_lock<std::mutex> lock(keyboardManagerState.appSpecificShortcutReMap_mutex);
std::wstring query_string;
std::map<std::wstring, std::map<Shortcut, RemapShortcut>>::iterator it;
AppSpecificShortcutRemapTable::iterator it;
// Check if an app-specific shortcut is already activated
if (keyboardManagerState.GetActivatedApp() == KeyboardManagerConstants::NoActivatedApp)
{
@@ -758,8 +748,7 @@ namespace KeyboardEventHandlers
if (it != keyboardManagerState.appSpecificShortcutReMap.end())
{
lock.unlock();
bool result = HandleShortcutRemapEvent(ii, data, keyboardManagerState.appSpecificShortcutReMap[query_string], keyboardManagerState.appSpecificShortcutReMapSortedKeys[query_string], keyboardManagerState.appSpecificShortcutReMap_mutex, keyboardManagerState, query_string);
bool result = HandleShortcutRemapEvent(ii, data, keyboardManagerState, query_string);
return result;
}
}

View File

@@ -15,11 +15,14 @@ namespace KeyboardEventHandlers
// Function to a handle a single key remap
__declspec(dllexport) intptr_t HandleSingleKeyRemapEvent(InputInterface& ii, LowlevelKeyboardEvent* data, KeyboardManagerState& keyboardManagerState) noexcept;
/* This feature has not been enabled (code from proof of concept stage)
*
// Function to a change a key's behavior from toggle to modifier
__declspec(dllexport) intptr_t HandleSingleKeyToggleToModEvent(InputInterface& ii, LowlevelKeyboardEvent* data, KeyboardManagerState& keyboardManagerState) noexcept;
*/
// Function to a handle a shortcut remap
__declspec(dllexport) intptr_t HandleShortcutRemapEvent(InputInterface& ii, LowlevelKeyboardEvent* data, std::map<Shortcut, RemapShortcut>& reMap, std::vector<Shortcut>& sortedReMapKeys, std::mutex& map_mutex, KeyboardManagerState& keyboardManagerState, const std::wstring& activatedApp = KeyboardManagerConstants::NoActivatedApp) noexcept;
__declspec(dllexport) intptr_t HandleShortcutRemapEvent(InputInterface& ii, LowlevelKeyboardEvent* data, KeyboardManagerState& keyboardManagerState, const std::optional<std::wstring>& activatedApp = std::nullopt) noexcept;
// Function to a handle an os-level shortcut remap
__declspec(dllexport) intptr_t HandleOSLevelShortcutRemapEvent(InputInterface& ii, LowlevelKeyboardEvent* data, KeyboardManagerState& keyboardManagerState) noexcept;

View File

@@ -384,6 +384,12 @@ public:
// Function called by the hook procedure to handle the events. This is the starting point function for remapping
intptr_t HandleKeyboardHookEvent(LowlevelKeyboardEvent* data) noexcept
{
// If remappings are disabled (due to the remap tables getting updated) skip the rest of the hook
if (!keyboardManagerState.AreRemappingsEnabled())
{
return 0;
}
// If key has suppress flag, then suppress it
if (data->lParam->dwExtraInfo == KeyboardManagerConstants::KEYBOARDMANAGER_SUPPRESS_FLAG)
{
@@ -432,8 +438,11 @@ public:
return 0;
}
/* This feature has not been enabled (code from proof of concept stage)
*
//// Remap a key to behave like a modifier instead of a toggle
//intptr_t SingleKeyToggleToModResult = KeyboardEventHandlers::HandleSingleKeyToggleToModEvent(inputHandler, data, keyboardManagerState);
*/
// Handle an app-specific shortcut remapping
intptr_t AppSpecificShortcutRemapResult = KeyboardEventHandlers::HandleAppSpecificShortcutRemapEvent(inputHandler, data, keyboardManagerState);

View File

@@ -298,7 +298,7 @@ namespace RemappingUITests
// Test if the PreProcessRemapTable method combines all the modifier pairs when the left and right modifiers are remapped to the same target
TEST_METHOD (PreProcessRemapTable_ShouldCombineAllPairs_OnPassingLeftAndRightModifiersRemappedToTheSameTarget)
{
std::unordered_map<DWORD, std::variant<DWORD, Shortcut>> remapTable;
SingleKeyRemapTable remapTable;
// Remap LCtrl and RCtrl to A, LAlt and RAlt to B, LShift and RShift to C, LWin and RWin to D
remapTable[VK_LCONTROL] = 0x41;
@@ -314,7 +314,7 @@ namespace RemappingUITests
LoadingAndSavingRemappingHelper::PreProcessRemapTable(remapTable);
// Expected Ctrl remapped to A, Alt to B, Shift to C, Win to D
std::unordered_map<DWORD, std::variant<DWORD, Shortcut>> expectedTable;
SingleKeyRemapTable expectedTable;
expectedTable[VK_CONTROL] = 0x41;
expectedTable[VK_MENU] = 0x42;
expectedTable[VK_SHIFT] = 0x43;
@@ -327,7 +327,7 @@ namespace RemappingUITests
// Test if the PreProcessRemapTable method does not combines any of the modifier pairs when the left and right modifiers are remapped to different targets
TEST_METHOD (PreProcessRemapTable_ShouldNotCombineAnyPairs_OnPassingLeftAndRightModifiersRemappedToTheDifferentTargets)
{
std::unordered_map<DWORD, std::variant<DWORD, Shortcut>> remapTable;
SingleKeyRemapTable remapTable;
// Remap left modifiers to A and right modifiers to B
remapTable[VK_LCONTROL] = 0x41;
@@ -343,7 +343,7 @@ namespace RemappingUITests
LoadingAndSavingRemappingHelper::PreProcessRemapTable(remapTable);
// Expected unchanged table
std::unordered_map<DWORD, std::variant<DWORD, Shortcut>> expectedTable;
SingleKeyRemapTable expectedTable;
expectedTable[VK_LCONTROL] = 0x41;
expectedTable[VK_RCONTROL] = 0x42;
expectedTable[VK_LMENU] = 0x41;
@@ -394,7 +394,7 @@ namespace RemappingUITests
LoadingAndSavingRemappingHelper::ApplySingleKeyRemappings(testState, remapBuffer, false);
// Expected A remapped to B, B remapped to Ctrl+V
std::unordered_map<DWORD, std::variant<DWORD, Shortcut>> expectedTable;
SingleKeyRemapTable expectedTable;
expectedTable[0x41] = 0x42;
expectedTable[0x42] = s1;
@@ -418,7 +418,7 @@ namespace RemappingUITests
LoadingAndSavingRemappingHelper::ApplySingleKeyRemappings(testState, remapBuffer, false);
// Expected LCtrl/RCtrl remapped to A, LAlt/RAlt to B, LShift/RShift to C, LWin/RWin to D
std::unordered_map<DWORD, std::variant<DWORD, Shortcut>> expectedTable;
SingleKeyRemapTable expectedTable;
expectedTable[VK_LCONTROL] = 0x41;
expectedTable[VK_RCONTROL] = 0x41;
expectedTable[VK_LMENU] = 0x42;
@@ -503,12 +503,12 @@ namespace RemappingUITests
LoadingAndSavingRemappingHelper::ApplyShortcutRemappings(testState, remapBuffer, false);
// Ctrl+A->Ctrl+B and Ctrl+C->Alt+V
std::map<Shortcut, RemapShortcut> expectedOSLevelTable;
ShortcutRemapTable expectedOSLevelTable;
expectedOSLevelTable[src1] = RemapShortcut(dest1);
expectedOSLevelTable[src2] = RemapShortcut(dest2);
// Ctrl+F->Alt+V and Ctrl+G->Ctrl+B for testApp1
std::map<std::wstring, std::map<Shortcut, RemapShortcut>> expectedAppSpecificLevelTable;
AppSpecificShortcutRemapTable expectedAppSpecificLevelTable;
expectedAppSpecificLevelTable[testApp1][src3] = RemapShortcut(dest2);
expectedAppSpecificLevelTable[testApp1][src4] = RemapShortcut(dest1);

View File

@@ -204,7 +204,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 == KeyboardManagerHelper::ErrorType::NoError)
{
std::variant<DWORD, Shortcut> tempShortcut;
KeyShortcutUnion tempShortcut;
if (isHybridControl && selectedKeyCodes.size() == 1)
{
tempShortcut = selectedKeyCodes[0];

View File

@@ -252,9 +252,8 @@ void createEditKeyboardWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMan
keyboardManagerState.SetUIState(KeyboardManagerUIState::EditKeyboardWindowActivated, _hWndEditKeyboardWindow);
// Load existing remaps into UI
std::unique_lock<std::mutex> lock(keyboardManagerState.singleKeyReMap_mutex);
std::unordered_map<DWORD, std::variant<DWORD, Shortcut>> singleKeyRemapCopy = keyboardManagerState.singleKeyReMap;
lock.unlock();
SingleKeyRemapTable singleKeyRemapCopy = keyboardManagerState.singleKeyReMap;
LoadingAndSavingRemappingHelper::PreProcessRemapTable(singleKeyRemapCopy);
for (const auto& it : singleKeyRemapCopy)
@@ -272,9 +271,13 @@ void createEditKeyboardWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMan
header.SetLeftOf(applyButton, cancelButton);
auto ApplyRemappings = [&keyboardManagerState, _hWndEditKeyboardWindow]() {
LoadingAndSavingRemappingHelper::ApplySingleKeyRemappings(keyboardManagerState, SingleKeyRemapControl::singleKeyRemapBuffer, true);
// Save the updated shortcuts remaps to file.
bool saveResult = keyboardManagerState.SaveConfigToFile();
// Disable the remappings while the remapping table is updated
keyboardManagerState.RemappingsDisabledWrapper(
[&keyboardManagerState]() {
LoadingAndSavingRemappingHelper::ApplySingleKeyRemappings(keyboardManagerState, SingleKeyRemapControl::singleKeyRemapBuffer, true);
// Save the updated shortcuts remaps to file.
bool saveResult = keyboardManagerState.SaveConfigToFile();
});
PostMessage(_hWndEditKeyboardWindow, WM_CLOSE, 0, 0);
};

View File

@@ -226,17 +226,20 @@ void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMa
keyboardManagerState.SetUIState(KeyboardManagerUIState::EditShortcutsWindowActivated, _hWndEditShortcutsWindow);
// Load existing os level shortcuts into UI
std::unique_lock<std::mutex> lockOSLevel(keyboardManagerState.osLevelShortcutReMap_mutex);
for (const auto& it : keyboardManagerState.osLevelShortcutReMap)
// Create copy of the remaps to avoid concurrent access
ShortcutRemapTable osLevelShortcutReMapCopy = keyboardManagerState.osLevelShortcutReMap;
for (const auto& it : osLevelShortcutReMapCopy)
{
ShortcutControl::AddNewShortcutControlRow(shortcutTable, keyboardRemapControlObjects, it.first, it.second.targetShortcut);
}
lockOSLevel.unlock();
// Load existing app-specific shortcuts into UI
std::unique_lock<std::mutex> lockAppSpecific(keyboardManagerState.appSpecificShortcutReMap_mutex);
// Create copy of the remaps to avoid concurrent access
AppSpecificShortcutRemapTable appSpecificShortcutReMapCopy = keyboardManagerState.appSpecificShortcutReMap;
// Iterate through all the apps
for (const auto& itApp : keyboardManagerState.appSpecificShortcutReMap)
for (const auto& itApp : appSpecificShortcutReMapCopy)
{
// Iterate through shortcuts for each app
for (const auto& itShortcut : itApp.second)
@@ -244,7 +247,6 @@ void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMa
ShortcutControl::AddNewShortcutControlRow(shortcutTable, keyboardRemapControlObjects, itShortcut.first, itShortcut.second.targetShortcut, itApp.first);
}
}
lockAppSpecific.unlock();
// Apply button
Button applyButton;
@@ -256,9 +258,13 @@ void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMa
header.SetLeftOf(applyButton, cancelButton);
auto ApplyRemappings = [&keyboardManagerState, _hWndEditShortcutsWindow]() {
LoadingAndSavingRemappingHelper::ApplyShortcutRemappings(keyboardManagerState, ShortcutControl::shortcutRemapBuffer, true);
// Save the updated key remaps to file.
bool saveResult = keyboardManagerState.SaveConfigToFile();
// Disable the remappings while the remapping table is updated
keyboardManagerState.RemappingsDisabledWrapper(
[&keyboardManagerState]() {
LoadingAndSavingRemappingHelper::ApplyShortcutRemappings(keyboardManagerState, ShortcutControl::shortcutRemapBuffer, true);
// Save the updated key remaps to file.
bool saveResult = keyboardManagerState.SaveConfigToFile();
});
PostMessage(_hWndEditShortcutsWindow, WM_CLOSE, 0, 0);
};

View File

@@ -341,7 +341,7 @@ void KeyDropDownControl::ValidateShortcutFromDropDownList(Grid table, StackPanel
{
// Check for errors only if the current selection is a valid shortcut
std::vector<DWORD> selectedKeyCodes = KeyboardManagerHelper::GetKeyCodesFromSelectedIndices(keyDropDownControlObjects[i]->GetSelectedIndicesFromStackPanel(parent), GetKeyCodeList(true, colIndex == 1));
std::variant<DWORD, Shortcut> currentShortcut;
KeyShortcutUnion currentShortcut;
if (selectedKeyCodes.size() == 1 && isHybridControl)
{
currentShortcut = selectedKeyCodes[0];

View File

@@ -11,11 +11,11 @@ namespace LoadingAndSavingRemappingHelper
KeyboardManagerHelper::ErrorType CheckIfRemappingsAreValid(const RemapBuffer& remappings)
{
KeyboardManagerHelper::ErrorType isSuccess = KeyboardManagerHelper::ErrorType::NoError;
std::map<std::wstring, std::set<std::variant<DWORD, Shortcut>>> ogKeys;
std::map<std::wstring, std::set<KeyShortcutUnion>> ogKeys;
for (int i = 0; i < remappings.size(); i++)
{
std::variant<DWORD, Shortcut> ogKey = remappings[i].first[0];
std::variant<DWORD, Shortcut> newKey = remappings[i].first[1];
KeyShortcutUnion ogKey = remappings[i].first[0];
KeyShortcutUnion newKey = remappings[i].first[1];
std::wstring appName = remappings[i].second;
bool ogKeyValidity = (ogKey.index() == 0 && std::get<DWORD>(ogKey) != NULL) || (ogKey.index() == 1 && std::get<Shortcut>(ogKey).IsValidShortcut());
@@ -24,7 +24,7 @@ namespace LoadingAndSavingRemappingHelper
// Add new set for a new target app name
if (ogKeys.find(appName) == ogKeys.end())
{
ogKeys[appName] = std::set<std::variant<DWORD, Shortcut>>();
ogKeys[appName] = std::set<KeyShortcutUnion>();
}
if (ogKeyValidity && newKeyValidity && ogKeys[appName].find(ogKey) == ogKeys[appName].end())
@@ -52,7 +52,7 @@ namespace LoadingAndSavingRemappingHelper
for (int i = 0; i < remappings.size(); i++)
{
DWORD ogKey = std::get<DWORD>(remappings[i].first[0]);
std::variant<DWORD, Shortcut> newKey = remappings[i].first[1];
KeyShortcutUnion newKey = remappings[i].first[1];
if (ogKey != NULL && ((newKey.index() == 0 && std::get<DWORD>(newKey) != 0) || (newKey.index() == 1 && std::get<Shortcut>(newKey).IsValidShortcut())))
{
@@ -75,7 +75,7 @@ namespace LoadingAndSavingRemappingHelper
}
// Function to combine remappings if the L and R version of the modifier is mapped to the same key
void CombineRemappings(std::unordered_map<DWORD, std::variant<DWORD, Shortcut>>& table, DWORD leftKey, DWORD rightKey, DWORD combinedKey)
void CombineRemappings(SingleKeyRemapTable& table, DWORD leftKey, DWORD rightKey, DWORD combinedKey)
{
if (table.find(leftKey) != table.end() && table.find(rightKey) != table.end())
{
@@ -90,7 +90,7 @@ namespace LoadingAndSavingRemappingHelper
}
// Function to pre process the remap table before loading it into the UI
void PreProcessRemapTable(std::unordered_map<DWORD, std::variant<DWORD, Shortcut>>& table)
void PreProcessRemapTable(SingleKeyRemapTable& table)
{
// Pre process the table to combine L and R versions of Ctrl/Alt/Shift/Win that are mapped to the same key
CombineRemappings(table, VK_LCONTROL, VK_RCONTROL, VK_CONTROL);
@@ -109,7 +109,7 @@ namespace LoadingAndSavingRemappingHelper
for (int i = 0; i < remappings.size(); i++)
{
DWORD originalKey = std::get<DWORD>(remappings[i].first[0]);
std::variant<DWORD, Shortcut> newKey = remappings[i].first[1];
KeyShortcutUnion newKey = remappings[i].first[1];
if (originalKey != NULL && !(newKey.index() == 0 && std::get<DWORD>(newKey) == NULL) && !(newKey.index() == 1 && !std::get<Shortcut>(newKey).IsValidShortcut()))
{
@@ -177,7 +177,7 @@ namespace LoadingAndSavingRemappingHelper
for (int i = 0; i < remappings.size(); i++)
{
Shortcut originalShortcut = std::get<Shortcut>(remappings[i].first[0]);
std::variant<DWORD, Shortcut> newShortcut = remappings[i].first[1];
KeyShortcutUnion newShortcut = remappings[i].first[1];
if (originalShortcut.IsValidShortcut() && ((newShortcut.index() == 0 && std::get<DWORD>(newShortcut) != NULL) || (newShortcut.index() == 1 && std::get<Shortcut>(newShortcut).IsValidShortcut())))
{

View File

@@ -14,10 +14,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, std::variant<DWORD, Shortcut>>& table, DWORD leftKey, DWORD rightKey, DWORD combinedKey);
void CombineRemappings(std::unordered_map<DWORD, KeyShortcutUnion>& table, DWORD leftKey, DWORD rightKey, DWORD combinedKey);
// Function to pre process the remap table before loading it into the UI
void PreProcessRemapTable(std::unordered_map<DWORD, std::variant<DWORD, Shortcut>>& table);
void PreProcessRemapTable(std::unordered_map<DWORD, KeyShortcutUnion>& table);
// Function to apply the single key remappings from the buffer to the KeyboardManagerState variable
void ApplySingleKeyRemappings(KeyboardManagerState& keyboardManagerState, const RemapBuffer& remappings, bool isTelemetryRequired);

View File

@@ -65,7 +65,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(Grid& parent, std::vector<std::vector<std::unique_ptr<ShortcutControl>>>& keyboardRemapControlObjects, const Shortcut& originalKeys, const std::variant<DWORD, Shortcut>& newKeys, const std::wstring& targetAppName)
void ShortcutControl::AddNewShortcutControlRow(Grid& parent, std::vector<std::vector<std::unique_ptr<ShortcutControl>>>& keyboardRemapControlObjects, const Shortcut& originalKeys, const KeyShortcutUnion& newKeys, const std::wstring& targetAppName)
{
// Textbox for target application
TextBox targetAppTextBox;

View File

@@ -48,7 +48,7 @@ public:
ShortcutControl(Grid table, 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(Grid& parent, std::vector<std::vector<std::unique_ptr<ShortcutControl>>>& keyboardRemapControlObjects, const Shortcut& originalKeys = Shortcut(), const std::variant<DWORD, Shortcut>& newKeys = Shortcut(), const std::wstring& targetAppName = L"");
static void AddNewShortcutControlRow(Grid& parent, std::vector<std::vector<std::unique_ptr<ShortcutControl>>>& keyboardRemapControlObjects, const Shortcut& originalKeys = Shortcut(), const KeyShortcutUnion& 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

@@ -72,7 +72,7 @@ void SingleKeyRemapControl::UpdateAccessibleNames(StackPanel sourceColumn, Stack
}
// 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(Grid& parent, std::vector<std::vector<std::unique_ptr<SingleKeyRemapControl>>>& keyboardRemapControlObjects, const DWORD originalKey, const std::variant<DWORD, Shortcut> newKey)
void SingleKeyRemapControl::AddNewControlKeyRemapRow(Grid& parent, std::vector<std::vector<std::unique_ptr<SingleKeyRemapControl>>>& keyboardRemapControlObjects, const DWORD originalKey, const KeyShortcutUnion newKey)
{
// Create new SingleKeyRemapControl objects dynamically so that we does not get destructed
std::vector<std::unique_ptr<SingleKeyRemapControl>> newrow;

View File

@@ -43,7 +43,7 @@ public:
SingleKeyRemapControl(Grid table, 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(winrt::Windows::UI::Xaml::Controls::Grid& parent, std::vector<std::vector<std::unique_ptr<SingleKeyRemapControl>>>& keyboardRemapControlObjects, const DWORD originalKey = NULL, const std::variant<DWORD, Shortcut> newKey = NULL);
static void AddNewControlKeyRemapRow(winrt::Windows::UI::Xaml::Controls::Grid& parent, std::vector<std::vector<std::unique_ptr<SingleKeyRemapControl>>>& keyboardRemapControlObjects, const DWORD originalKey = NULL, const KeyShortcutUnion newKey = NULL);
// 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();