Skip to content

Commit

Permalink
Keyboard disable (#6874)
Browse files Browse the repository at this point in the history
Added disable key/shortcut functionality
  • Loading branch information
mykhailopylyp committed Oct 2, 2020
1 parent b2e72e1 commit 3f25d7c
Show file tree
Hide file tree
Showing 15 changed files with 298 additions and 34 deletions.
1 change: 1 addition & 0 deletions src/common/keyboard_layout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ void LayoutMap::LayoutMapImpl::UpdateLayout()
keyboardLayoutMap[VK_NONCONVERT] = L"IME Non-Convert";
keyboardLayoutMap[VK_ACCEPT] = L"IME Kana";
keyboardLayoutMap[VK_MODECHANGE] = L"IME Mode Change";
keyboardLayoutMap[CommonSharedConstants::VK_DISABLED] = L"Disable";
}

// Function to return the list of key codes in the order for the drop down. It creates it if it doesn't exist
Expand Down
5 changes: 4 additions & 1 deletion src/common/shared_constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@ namespace CommonSharedConstants

// Path to the event used by PowerLauncher
const wchar_t POWER_LAUNCHER_SHARED_EVENT[] = L"Local\\PowerToysRunInvokeEvent-30f26ad7-d36d-4c0e-ab02-68bb5ff3c4ab";
}

// Max DWORD for key code to disable keys.
const DWORD VK_DISABLED = 0x100;
}
2 changes: 2 additions & 0 deletions src/modules/keyboardmanager/common/Helpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ namespace KeyboardManagerHelper
return GET_RESOURCE_STRING(IDS_ERRORMESSAGE_SHORTCUTMAXONEACTIONKEY).c_str();
case ErrorType::ShortcutMaxShortcutSizeOneActionKey:
return GET_RESOURCE_STRING(IDS_ERRORMESSAGE_MAXSHORTCUTSIZE).c_str();
case ErrorType::ShortcutDisableAsActionKey:
return GET_RESOURCE_STRING(IDS_ERRORMESSAGE_DISABLEASACTIONKEY).c_str();
default:
return GET_RESOURCE_STRING(IDS_ERRORMESSAGE_DEFAULT).c_str();
}
Expand Down
3 changes: 2 additions & 1 deletion src/modules/keyboardmanager/common/Helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ namespace KeyboardManagerHelper
ShortcutAtleast2Keys,
ShortcutOneActionKey,
ShortcutNotMoreThanOneActionKey,
ShortcutMaxShortcutSizeOneActionKey
ShortcutMaxShortcutSizeOneActionKey,
ShortcutDisableAsActionKey
};

// Enum type to store possible decision for input in the low level hook
Expand Down
93 changes: 84 additions & 9 deletions src/modules/keyboardmanager/dll/KeyboardEventHandlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ namespace KeyboardEventHandlers
// Check if the remap is to a key or a shortcut
bool remapToKey = (it->second.index() == 0);

// If mapped to 0x0 then the key is disabled
// If mapped to VK_DISABLED then the key is disabled
if (remapToKey)
{
if (std::get<DWORD>(it->second) == 0x0)
if (std::get<DWORD>(it->second) == CommonSharedConstants::VK_DISABLED)
{
return 1;
}
Expand Down Expand Up @@ -214,7 +214,7 @@ 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)
if (!it->first.IsKeyboardStateClearExceptShortcut(ii) && (remapToShortcut || std::get<DWORD>(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED))
{
continue;
}
Expand Down Expand Up @@ -284,6 +284,12 @@ namespace KeyboardEventHandlers
{
// Dummy key, key up for all the original shortcut modifier keys and key down for remapped key
key_count = 1 + (src_size - 1) + dest_size;
// Do not send Disable key
if (std::get<DWORD>(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED)
{
key_count--;
}

keyEventList = new INPUT[key_count]();
memset(keyEventList, 0, sizeof(keyEventList));

Expand All @@ -296,8 +302,11 @@ namespace KeyboardEventHandlers
KeyboardManagerHelper::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, i, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);

// Set target key down state
KeyboardManagerHelper::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, (WORD)KeyboardManagerHelper::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut)), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
i++;
if (std::get<DWORD>(it->second.targetShortcut) != CommonSharedConstants::VK_DISABLED)
{
KeyboardManagerHelper::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, (WORD)KeyboardManagerHelper::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut)), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
i++;
}

// Modifier state reset might be required for this key depending on the shortcut's action and target modifier - ex: Win+Caps -> Ctrl
if (it->first.GetCtrlKey() == NULL && it->first.GetAltKey() == NULL && it->first.GetShiftKey() == NULL)
Expand Down Expand Up @@ -382,13 +391,22 @@ namespace KeyboardEventHandlers
{
// 1 for releasing new key and original shortcut modifiers except the one released
key_count = dest_size + src_size - 2;
// Do not send Disable key up
if (std::get<DWORD>(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED)
{
key_count--;
}

keyEventList = new INPUT[key_count]();
memset(keyEventList, 0, sizeof(keyEventList));

// Release new key state
int i = 0;
KeyboardManagerHelper::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, (WORD)KeyboardManagerHelper::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut)), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
i++;
if (std::get<DWORD>(it->second.targetShortcut) != CommonSharedConstants::VK_DISABLED)
{
KeyboardManagerHelper::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, (WORD)KeyboardManagerHelper::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut)), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
i++;
}

// Set original shortcut key down state except the action key and the released modifier since the original action key may or may not be held down. If it is held down it will generate it's own key message
KeyboardManagerHelper::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, i, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, Shortcut(), data->lParam->vkCode);
Expand Down Expand Up @@ -418,6 +436,12 @@ namespace KeyboardEventHandlers
// 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)
{
return 1;
}

size_t key_count = 1;
LPINPUT keyEventList = new INPUT[key_count]();
memset(keyEventList, 0, sizeof(keyEventList));
Expand Down Expand Up @@ -455,13 +479,23 @@ namespace KeyboardEventHandlers
{
// 1 for releasing new key and original shortcut modifiers, and dummy key
key_count = dest_size + src_size;
// Do not send Disable key
if (std::get<DWORD>(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED)
{
key_count--;
}

keyEventList = new INPUT[key_count]();
memset(keyEventList, 0, sizeof(keyEventList));

// Release new key state
int i = 0;
KeyboardManagerHelper::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, (WORD)KeyboardManagerHelper::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut)), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
i++;
// Do not send Disable key
if (std::get<DWORD>(it->second.targetShortcut) != CommonSharedConstants::VK_DISABLED)
{
KeyboardManagerHelper::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, (WORD)KeyboardManagerHelper::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut)), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
i++;
}

// Set original shortcut key down state except the action key and the released modifier
KeyboardManagerHelper::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, i, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
Expand Down Expand Up @@ -499,6 +533,10 @@ namespace KeyboardEventHandlers
return 1;
}
}
else if (std::get<DWORD>(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED)
{
return 1;
}
}

// Case 5: If any key apart from the action key or a modifier key in the original shortcut is pressed then revert the keyboard state to just the original modifiers being held down along with the current key press
Expand Down Expand Up @@ -598,6 +636,43 @@ namespace KeyboardEventHandlers
i++;
}

it->second.isShortcutInvoked = false;
it->second.winKeyInvoked = ModifierKey::Disabled;
// If app specific shortcut has finished invoking, reset the target application
if (activatedApp != KeyboardManagerConstants::NoActivatedApp)
{
keyboardManagerState.SetActivatedApp(KeyboardManagerConstants::NoActivatedApp);
}
lock.unlock();
UINT res = ii.SendVirtualInput((UINT)key_count, keyEventList, sizeof(INPUT));
delete[] keyEventList;
return 1;
}
// All modifier keys and action key will be pressed down because if they are not pressed that means that handler has already been invoked on key release
else if (std::get<DWORD>(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED)
{
// Key down for original shortcut modifiers and action key, dummy key, and current key press
size_t key_count = src_size + 1 + 1;

LPINPUT keyEventList = new INPUT[key_count]();
memset(keyEventList, 0, sizeof(keyEventList));

// Set old shortcut key down state
int i = 0;
KeyboardManagerHelper::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, i, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);

// Set old action key
KeyboardManagerHelper::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, (WORD)it->first.GetActionKey(), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
i++;

// Send current key pressed without shortcut flag so that it can be reprocessed in case the physical keys pressed are a different remapped shortcut
KeyboardManagerHelper::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, (WORD)data->lParam->vkCode, 0, 0);
i++;

// Send dummy key
KeyboardManagerHelper::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, (WORD)KeyboardManagerConstants::DUMMY_KEY, KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
i++;

it->second.isShortcutInvoked = false;
it->second.winKeyInvoked = ModifierKey::Disabled;
// If app specific shortcut has finished invoking, reset the target application
Expand Down
3 changes: 3 additions & 0 deletions src/modules/keyboardmanager/dll/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -279,4 +279,7 @@
<data name="AutomationProperties_Row" xml:space="preserve">
<value>Row </value>
</data>
<data name="ERRORMESSAGE_DISABLEASACTIONKEY" xml:space="preserve">
<value>Disable can not be an action or a modifier key</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
#include <keyboardmanager/common/KeyboardManagerState.h>
#include <keyboardmanager/dll/KeyboardEventHandlers.h>
#include "TestHelpers.h"
#include <common\shared_constants.h>

using namespace Microsoft::VisualStudio::CppUnitTestFramework;

namespace RemappingLogicTests
{
TEST_CLASS (AppSpecificShortcutRemappingTests)

{
private:
MockedInput mockedInputHandler;
Expand Down Expand Up @@ -312,5 +314,33 @@ namespace RemappingLogicTests
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), false);
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x56), false);
}

// Disable app specific shortcut
TEST_METHOD (AppSpecificShortcutToDisable_ShouldDisable_WhenAppIsOnForeground)
{
Shortcut src;
src.SetKey(VK_CONTROL);
WORD actionKey = 0x41;
src.SetKey(actionKey);
WORD disableKey = CommonSharedConstants::VK_DISABLED;
testState.AddAppSpecificShortcut(testApp1, src, disableKey);

// Set the testApp as the foreground process
mockedInputHandler.SetForegroundProcess(testApp1);

const int nInputs = 2;
INPUT input[nInputs] = {};
input[0].type = INPUT_KEYBOARD;
input[0].ki.wVk = VK_CONTROL;
input[1].type = INPUT_KEYBOARD;
input[1].ki.wVk = actionKey;

// Send Ctrl+A keydown
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));

// Check if Ctrl+A is released and disable key was not send
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_CONTROL), false);
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(actionKey), false);
}
};
}
21 changes: 21 additions & 0 deletions src/modules/keyboardmanager/test/BufferValidationTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1438,5 +1438,26 @@ namespace RemappingUITests
Assert::AreEqual(true, result.first == KeyboardManagerHelper::ErrorType::NoError);
});
}

// Return error on Disable as second modifier key or action key
TEST_METHOD (ValidateShortcutBufferElement_ShouldReturnDisableAsActionKeyError_OnSettingSecondDropdownAsDisable)
{
std::vector<DWORD> keyList = keyboardLayout.GetKeyCodeList(true);
keyList.insert(keyList.begin(), CommonSharedConstants::VK_DISABLED);
// Arrange
RemapBuffer remapBuffer;
remapBuffer.push_back(std::make_pair(RemapBufferItem{ std::vector<DWORD>{ VK_SHIFT, CommonSharedConstants::VK_DISABLED }, Shortcut() }, testApp1));
std::vector<int32_t> selectedIndices = {
GetDropDownIndexFromDropDownList(VK_SHIFT, keyList),
GetDropDownIndexFromDropDownList(CommonSharedConstants::VK_DISABLED, keyList)
};

// Act
std::pair<KeyboardManagerHelper::ErrorType, BufferValidationHelpers::DropDownAction> result = BufferValidationHelpers::ValidateShortcutBufferElement(0, 1, 1, selectedIndices, testApp1, true, keyList, remapBuffer, true);

// Assert
Assert::AreEqual(true, result.first == KeyboardManagerHelper::ErrorType::ShortcutDisableAsActionKey);
Assert::AreEqual(true, result.second == BufferValidationHelpers::DropDownAction::NoAction);
}
};
}

0 comments on commit 3f25d7c

Please sign in to comment.