mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-06 03:07:04 +02:00
[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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user