Added unique lock mutexes to KeyboardManagerState (dev/keyboardManager) (#1789)

* Added unique lock mutexes for thread safety

* Fixed a bug in detect key logic

* Added early unlock statements to fix issue with shortcut guide

* Added comments for unlocks before SendInput and changed some unique_locks to lock_guards
This commit is contained in:
Arjun Balgovind 2020-04-03 10:57:46 -07:00 committed by Udit Singh
parent 467cf919be
commit ac26818005
6 changed files with 134 additions and 25 deletions

View File

@ -3,15 +3,17 @@
// Constructor
KeyboardManagerState::KeyboardManagerState() :
uiState(KeyboardManagerUIState::Deactivated), currentUIWindow(nullptr), currentShortcutTextBlock(nullptr), currentSingleKeyRemapTextBlock(nullptr)
uiState(KeyboardManagerUIState::Deactivated), currentUIWindow(nullptr), currentShortcutTextBlock(nullptr), currentSingleKeyRemapTextBlock(nullptr), detectedRemapKey(NULL)
{
}
// Function to check the if the UI state matches the argument state. For states with activated windows it also checks if the window is in focus.
bool KeyboardManagerState::CheckUIState(KeyboardManagerUIState state)
{
std::lock_guard<std::mutex> lock(uiState_mutex);
if (uiState == state)
{
std::unique_lock<std::mutex> lock(currentUIWindow_mutex);
if (uiState == KeyboardManagerUIState::Deactivated)
{
return true;
@ -30,73 +32,114 @@ bool KeyboardManagerState::CheckUIState(KeyboardManagerUIState state)
// Function to set the window handle of the current UI window that is activated
void KeyboardManagerState::SetCurrentUIWindow(HWND windowHandle)
{
std::lock_guard<std::mutex> lock(currentUIWindow_mutex);
currentUIWindow = windowHandle;
}
// Function to set the UI state. When a window is activated, the handle to the window can be passed in the windowHandle argument.
void KeyboardManagerState::SetUIState(KeyboardManagerUIState state, HWND windowHandle)
{
std::lock_guard<std::mutex> lock(uiState_mutex);
uiState = state;
currentUIWindow = windowHandle;
SetCurrentUIWindow(windowHandle);
}
// Function to reset the UI state members
void KeyboardManagerState::ResetUIState()
{
SetUIState(KeyboardManagerUIState::Deactivated);
currentShortcutTextBlock = nullptr;
detectedShortcut.clear();
// Reset all the single key remap stored variables.
// Reset the shortcut UI stored variables
std::unique_lock<std::mutex> currentShortcutTextBlock_lock(currentShortcutTextBlock_mutex);
currentShortcutTextBlock = nullptr;
currentShortcutTextBlock_lock.unlock();
std::unique_lock<std::mutex> detectedShortcut_lock(detectedShortcut_mutex);
detectedShortcut.clear();
detectedShortcut_lock.unlock();
// Reset all the single key remap UI stored variables.
std::unique_lock<std::mutex> currentSingleKeyRemapTextBlock_lock(currentSingleKeyRemapTextBlock_mutex);
currentSingleKeyRemapTextBlock = nullptr;
currentSingleKeyRemapTextBlock_lock.unlock();
std::unique_lock<std::mutex> detectedRemapKey_lock(detectedRemapKey_mutex);
detectedRemapKey = NULL;
detectedRemapKey_lock.unlock();
}
// Function to clear the OS Level shortcut remapping table
void KeyboardManagerState::ClearOSLevelShortcuts()
{
std::lock_guard<std::mutex> lock(osLevelShortcutReMap_mutex);
osLevelShortcutReMap.clear();
}
// Function to clear the Keys remapping table.
void KeyboardManagerState::ClearSingleKeyRemaps()
{
std::lock_guard<std::mutex> lock(singleKeyReMap_mutex);
singleKeyReMap.clear();
}
// Function to add a new OS level shortcut remapping
void KeyboardManagerState::AddOSLevelShortcut(const std::vector<DWORD>& originalSC, const std::vector<WORD>& newSC)
bool KeyboardManagerState::AddOSLevelShortcut(const std::vector<DWORD>& originalSC, const std::vector<WORD>& 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())
{
return false;
}
osLevelShortcutReMap[originalSC] = std::make_pair(newSC, false);
return true;
}
// Function to add a new OS level shortcut remapping
void KeyboardManagerState::AddSingleKeyRemap(const DWORD& originalKey, const WORD& newRemapKey)
bool KeyboardManagerState::AddSingleKeyRemap(const DWORD& originalKey, const WORD& 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())
{
return false;
}
singleKeyReMap[originalKey] = newRemapKey;
return true;
}
// Function to set the textblock of the detect shortcut UI so that it can be accessed by the hook
void KeyboardManagerState::ConfigureDetectShortcutUI(const TextBlock& textBlock)
{
std::lock_guard<std::mutex> lock(currentShortcutTextBlock_mutex);
currentShortcutTextBlock = textBlock;
}
// Function to set the textblock of the detect remap key UI so that it can be accessed by the hook
void KeyboardManagerState::ConfigureDetectSingleKeyRemapUI(const TextBlock& textBlock)
{
std::lock_guard<std::mutex> lock(currentSingleKeyRemapTextBlock_mutex);
currentSingleKeyRemapTextBlock = textBlock;
}
// Function to update the detect shortcut UI based on the entered keys
void KeyboardManagerState::UpdateDetectShortcutUI()
{
std::lock_guard<std::mutex> currentShortcutTextBlock_lock(currentShortcutTextBlock_mutex);
if (currentShortcutTextBlock == nullptr)
{
return;
}
std::unique_lock<std::mutex> detectedShortcut_lock(detectedShortcut_mutex);
hstring shortcutString = convertVectorToHstring<DWORD>(detectedShortcut);
detectedShortcut_lock.unlock();
// Since this function is invoked from the back-end thread, in order to update the UI the dispatcher must be used.
currentShortcutTextBlock.Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, [=]() {
@ -107,12 +150,15 @@ void KeyboardManagerState::UpdateDetectShortcutUI()
// Function to update the detect remap key UI based on the entered key.
void KeyboardManagerState::UpdateDetectSingleKeyRemapUI()
{
std::lock_guard<std::mutex> currentSingleKeyRemapTextBlock_lock(currentSingleKeyRemapTextBlock_mutex);
if (currentSingleKeyRemapTextBlock == nullptr)
{
return;
}
std::unique_lock<std::mutex> detectedRemapKey_lock(detectedRemapKey_mutex);
hstring remapKeyString = winrt::to_hstring((unsigned int)detectedRemapKey);
detectedRemapKey_lock.unlock();
// Since this function is invoked from the back-end thread, in order to update the UI the dispatcher must be used.
currentSingleKeyRemapTextBlock.Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, [=]() {
@ -123,16 +169,22 @@ void KeyboardManagerState::UpdateDetectSingleKeyRemapUI()
// Function to return the currently detected shortcut which is displayed on the UI
std::vector<DWORD> KeyboardManagerState::GetDetectedShortcut()
{
std::unique_lock<std::mutex> lock(currentShortcutTextBlock_mutex);
hstring detectedShortcutString = currentShortcutTextBlock.Text();
lock.unlock();
std::wstring detectedShortcutWstring = detectedShortcutString.c_str();
std::vector<std::wstring> detectedShortcutVector = splitwstring(detectedShortcutWstring, L' ');
return convertWStringVectorToIntegerVector<DWORD>(detectedShortcutVector);
}
// Function to return the currently detected remap key which is displayed on the UI
DWORD KeyboardManagerState::GetDetectedSingleRemapKey()
DWORD KeyboardManagerState::GetDetectedSingleRemapKey()
{
std::unique_lock<std::mutex> lock(currentSingleKeyRemapTextBlock_mutex);
hstring remapKeyString = currentSingleKeyRemapTextBlock.Text();
lock.unlock();
std::wstring remapKeyWString = remapKeyString.c_str();
DWORD remapKey = NULL;
if (!remapKeyString.empty())
@ -149,8 +201,16 @@ bool KeyboardManagerState::DetectSingleRemapKeyUIBackend(LowlevelKeyboardEvent*
// Check if the detect key UI window has been activated
if (CheckUIState(KeyboardManagerUIState::DetectSingleKeyRemapWindowActivated))
{
detectedRemapKey = data->lParam->vkCode;
UpdateDetectSingleKeyRemapUI();
// detect the key if it is pressed down
if (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN)
{
std::unique_lock<std::mutex> detectedRemapKey_lock(detectedRemapKey_mutex);
detectedRemapKey = data->lParam->vkCode;
detectedRemapKey_lock.unlock();
UpdateDetectSingleKeyRemapUI();
}
// Suppress the keyboard event
return true;
}
@ -167,9 +227,12 @@ bool KeyboardManagerState::DetectShortcutUIBackend(LowlevelKeyboardEvent* data)
// Add the key if it is pressed down
if (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN)
{
std::unique_lock<std::mutex> lock(detectedShortcut_mutex);
if (std::find(detectedShortcut.begin(), detectedShortcut.end(), data->lParam->vkCode) == detectedShortcut.end())
{
detectedShortcut.push_back(data->lParam->vkCode);
lock.unlock();
// Update the UI. This function is called here because it should store the set of keys pressed till the last key which was pressed down.
UpdateDetectShortcutUI();
}
@ -177,6 +240,7 @@ bool KeyboardManagerState::DetectShortcutUIBackend(LowlevelKeyboardEvent* data)
// Remove the key if it has been released
else if (data->wParam == WM_KEYUP || data->wParam == WM_SYSKEYUP)
{
std::lock_guard<std::mutex> lock(detectedShortcut_mutex);
detectedShortcut.erase(std::remove(detectedShortcut.begin(), detectedShortcut.end(), data->lParam->vkCode), detectedShortcut.end());
}
@ -185,9 +249,13 @@ bool KeyboardManagerState::DetectShortcutUIBackend(LowlevelKeyboardEvent* data)
}
// If the detect shortcut UI window is not activated, then clear the shortcut buffer if it isn't empty
else if (!detectedShortcut.empty())
else
{
detectedShortcut.clear();
std::lock_guard<std::mutex> lock(detectedShortcut_mutex);
if (!detectedShortcut.empty())
{
detectedShortcut.clear();
}
}
return false;

View File

@ -1,6 +1,7 @@
#pragma once
#include "Helpers.h"
#include <interface/lowlevel_keyboard_event_data.h>
#include <mutex>
#include <winrt/Windows.UI.Xaml.Controls.h>
using namespace winrt::Windows::UI::Xaml::Controls;
@ -21,35 +22,46 @@ class KeyboardManagerState
private:
// State variable used to store which UI window is currently active that requires interaction with the hook
KeyboardManagerUIState uiState;
std::mutex uiState_mutex;
// Window handle for the current UI window which is active. Should be set to nullptr if UI is deactivated
HWND currentUIWindow;
std::mutex currentUIWindow_mutex;
// Vector to store the shortcut detected in the detect shortcut UI window. This is used in both the backend and the UI.
std::vector<DWORD> detectedShortcut;
std::mutex detectedShortcut_mutex;
// Store detected remap key in the remap UI window. This is used in both the backend and the UI.
DWORD detectedRemapKey;
std::mutex detectedRemapKey_mutex;
// Stores the UI element which is to be updated based on the remap key entered.
TextBlock currentSingleKeyRemapTextBlock;
std::mutex currentSingleKeyRemapTextBlock_mutex;
// Stores the UI element which is to be updated based on the shortcut entered
TextBlock currentShortcutTextBlock;
std::mutex currentShortcutTextBlock_mutex;
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 initalised 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, WORD> singleKeyReMap;
std::mutex singleKeyReMap_mutex;
// Stores keys which need to be changed from toggle behaviour to modifier behaviour. Eg. Caps Lock
std::unordered_map<DWORD, bool> singleKeyToggleToMod;
std::mutex singleKeyToggleToMod_mutex;
// Stores the os level shortcut remappings
std::map<std::vector<DWORD>, std::pair<std::vector<WORD>, bool>> osLevelShortcutReMap;
std::mutex osLevelShortcutReMap_mutex;
// Stores the app-specific shortcut remappings. Maps application name to the shortcut map
std::map<std::wstring, std::map<std::vector<DWORD>, std::pair<std::vector<WORD>, bool>>> appSpecificShortcutReMap;
std::mutex appSpecificShortcutReMap_mutex;
// Constructor
KeyboardManagerState();
@ -73,10 +85,10 @@ public:
void ClearSingleKeyRemaps();
// Function to add a new single key remapping
void AddSingleKeyRemap(const DWORD& originalKey, const WORD& newRemapKey);
bool AddSingleKeyRemap(const DWORD& originalKey, const WORD& newRemapKey);
// Function to add a new OS level shortcut remapping
void AddOSLevelShortcut(const std::vector<DWORD>& originalSC, const std::vector<WORD>& newSC);
bool AddOSLevelShortcut(const std::vector<DWORD>& originalSC, const std::vector<WORD>& newSC);
// Function to set the textblock of the detect shortcut UI so that it can be accessed by the hook
void ConfigureDetectShortcutUI(const TextBlock& textBlock);

View File

@ -313,6 +313,8 @@ public:
// Check if the key event was generated by KeyboardManager to avoid remapping events generated by us.
if (!(data->lParam->dwExtraInfo & 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())
{
@ -334,6 +336,7 @@ public:
keyEventList[0].ki.dwFlags = KEYEVENTF_KEYUP;
}
lock.unlock();
UINT res = SendInput(key_count, keyEventList, sizeof(INPUT));
delete[] keyEventList;
return 1;
@ -349,6 +352,8 @@ public:
// Check if the key event was generated by KeyboardManager to avoid remapping events generated by us.
if (!(data->lParam->dwExtraInfo & 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.singleKeyToggleToMod_mutex);
auto it = keyboardManagerState.singleKeyToggleToMod.find(data->lParam->vkCode);
if (it != keyboardManagerState.singleKeyToggleToMod.end())
{
@ -361,6 +366,7 @@ public:
}
else
{
lock.unlock();
return 1;
}
}
@ -376,14 +382,18 @@ public:
keyEventList[1].ki.dwFlags = KEYEVENTF_KEYUP;
keyEventList[1].ki.dwExtraInfo = KEYBOARDMANAGER_SINGLEKEY_FLAG;
lock.unlock();
UINT res = SendInput(key_count, keyEventList, sizeof(INPUT));
delete[] keyEventList;
// Reset the long press flag when the key has been lifted.
if (data->wParam == WM_KEYUP || data->wParam == WM_SYSKEYUP)
{
lock.lock();
keyboardManagerState.singleKeyToggleToMod[data->lParam->vkCode] = false;
lock.unlock();
}
return 1;
}
}
@ -495,8 +505,10 @@ public:
}
// Function to a handle a shortcut remap
intptr_t HandleShortcutRemapEvent(LowlevelKeyboardEvent* data, std::map<std::vector<DWORD>, std::pair<std::vector<WORD>, bool>>& reMap) noexcept
intptr_t HandleShortcutRemapEvent(LowlevelKeyboardEvent* data, std::map<std::vector<DWORD>, std::pair<std::vector<WORD>, bool>>& reMap, std::mutex& map_mutex) 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);
for (auto& it : reMap)
{
const size_t src_size = it.first.size();
@ -588,6 +600,7 @@ public:
}
it.second.second = true;
lock.unlock();
UINT res = SendInput((UINT)key_count, keyEventList, sizeof(INPUT));
delete[] keyEventList;
return 1;
@ -657,8 +670,8 @@ public:
}
it.second.second = false;
lock.unlock();
UINT res = SendInput((UINT)key_count, keyEventList, sizeof(INPUT));
delete[] keyEventList;
return 1;
}
@ -678,6 +691,7 @@ public:
keyEventList[0].ki.dwExtraInfo = KEYBOARDMANAGER_SHORTCUT_FLAG;
it.second.second = true;
lock.unlock();
UINT res = SendInput((UINT)key_count, keyEventList, sizeof(INPUT));
delete[] keyEventList;
return 1;
@ -758,6 +772,7 @@ public:
}
it.second.second = false;
lock.unlock();
UINT res = SendInput((UINT)key_count, keyEventList, sizeof(INPUT));
delete[] keyEventList;
return 1;
@ -775,7 +790,8 @@ public:
// Check if the key event was generated by KeyboardManager to avoid remapping events generated by us.
if (data->lParam->dwExtraInfo != KEYBOARDMANAGER_SHORTCUT_FLAG)
{
return HandleShortcutRemapEvent(data, keyboardManagerState.osLevelShortcutReMap);
bool result = HandleShortcutRemapEvent(data, keyboardManagerState.osLevelShortcutReMap, keyboardManagerState.osLevelShortcutReMap_mutex);
return result;
}
return 0;
@ -838,11 +854,13 @@ public:
{
return 0;
}
std::unique_lock<std::mutex> lock(keyboardManagerState.appSpecificShortcutReMap_mutex);
auto it = keyboardManagerState.appSpecificShortcutReMap.find(process_name);
if (it != keyboardManagerState.appSpecificShortcutReMap.end())
{
return HandleShortcutRemapEvent(data, keyboardManagerState.appSpecificShortcutReMap[process_name]);
lock.unlock();
bool result = HandleShortcutRemapEvent(data, keyboardManagerState.appSpecificShortcutReMap[process_name], keyboardManagerState.appSpecificShortcutReMap_mutex);
return result;
}
}

View File

@ -150,7 +150,12 @@ void createEditKeyboardWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMan
{
DWORD originalKey = std::stoi(originalKeyString.c_str());
DWORD newKey = std::stoi(newKeyString.c_str());
keyboardManagerState.AddSingleKeyRemap(originalKey, newKey);
bool result = keyboardManagerState.AddSingleKeyRemap(originalKey, newKey);
if (!result)
{
isSuccess = false;
}
}
else
{
@ -181,10 +186,12 @@ void createEditKeyboardWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMan
SingleKeyRemapControl::keyboardManagerState = &keyboardManagerState;
// Load existing remaps into UI
std::unique_lock<std::mutex> lock(keyboardManagerState.singleKeyReMap_mutex);
for (const auto& it : keyboardManagerState.singleKeyReMap)
{
SingleKeyRemapControl::AddNewControlKeyRemapRow(keyRemapTable, it.first, it.second);
}
lock.unlock();
// Add remap key button
Windows::UI::Xaml::Controls::Button addRemapKey;

View File

@ -109,7 +109,7 @@ void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMa
originalShortcutHeader.Margin({ 0, 0, 0, 10 });
tableHeaderRow.Children().Append(originalShortcutHeader);
// Second header textblock in the header row of the shortcut table
// Second header textblock in the header row of the shortcut table
TextBlock newShortcutHeader;
newShortcutHeader.Text(winrt::to_hstring("New Shortcut:"));
newShortcutHeader.FontWeight(Text::FontWeights::Bold());
@ -143,7 +143,11 @@ void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMa
// Shortcut should consist of atleast two keys
if (originalKeys.size() > 1 && newKeys.size() > 1)
{
keyboardManagerState.AddOSLevelShortcut(originalKeys, newKeys);
bool result = keyboardManagerState.AddOSLevelShortcut(originalKeys, newKeys);
if (!result)
{
isSuccess = false;
}
}
else
{
@ -179,10 +183,12 @@ void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMa
ShortcutControl::keyboardManagerState = &keyboardManagerState;
// Load existing shortcuts into UI
for (const auto& it: keyboardManagerState.osLevelShortcutReMap)
std::unique_lock<std::mutex> lock(keyboardManagerState.osLevelShortcutReMap_mutex);
for (const auto& it : keyboardManagerState.osLevelShortcutReMap)
{
ShortcutControl::AddNewShortcutControlRow(shortcutTable, it.first, it.second.first);
}
lock.unlock();
// Add shortcut button
Windows::UI::Xaml::Controls::Button addShortcut;

View File

@ -93,10 +93,8 @@ void createMainWindow(HINSTANCE hInstance, KeyboardManagerState& keyboardManager
Windows::UI::Xaml::Controls::Button bt;
bt.Content(winrt::box_value(winrt::to_hstring("Edit Keyboard")));
bt.Click([&](IInspectable const& sender, RoutedEventArgs const&) {
//keyboardManagerState.SetUIState(KeyboardManagerUIState::DetectKeyWindowActivated);
std::thread th(createEditKeyboardWindow, _hInstance, std::ref(keyboardManagerState));
th.join();
//keyboardManagerState.ResetUIState();
});
Windows::UI::Xaml::Controls::Button bt2;