[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

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