doc/devdocs/modules/keyboardmanager/keyboardeventhandlers.md
This file contains documentation for all the methods involved in key/shortcut remapping.
This method is used for handling the key to key and key to shortcut remapping logic. The general logic is as follows:
dwExtraInfo field contains the KEYBOARDMANAGER_INJECTED_FLAG bit set. This bit is used to indicate that the key event was generated by KBM using SendInput. This ensures that we don't read events generated by the key or shortcut remap methods.VK_WIN (which is a keycode added by us), so that it is translated to VK_LWIN instead.KEYBOARDMANAGER_SINGLEKEY_FLAG on the dwExtraInfo field.This method is used for handling the shortcut to shortcut and shortcut to key remapping logic. The general logic is as follows:
activatedApp argument (i.e. if it is empty, we get the global shortcut remap table and otherwise we get the corresponding app-specific shortcut remap table).isShortcutInvoked flag to true, and set the KeyboardManagerState.activatedApp if it is an app-specific shortcut remap.isShortcutInvoked is true scenario (i.e. the initial remap keydown section is done) there are several cases depending on the key pressed or released:
isOriginalActionKeyPressed flag which we keep track of whenever the action key is pressed or released for remap to Disable), then we set the original shortcut's action key, followed by the current key press. If it is not remapped to disable and the target key is pressed, then we don't suppress the event as we allow shortcut to key remappings to be pressed along with other keys.KEYBOARDMANAGER_SHORTCUT_FLAG on the dwExtraInfo field, except the usage of the current key press in Case 5, for which we don't send any extra info so that it is considered as a normal key event which may in turn invoke some other remap.Note: Shortcuts are considered valid if they have modifiers and an action key. The reason why we haven't supported key combinations of just modifiers (which is requested in this issue) (like remapping <kbd>Ctrl+Alt</kbd>) is because this would require more cases and handling as these remappings have to take place only on press and release and if there is no key pressed in between similar to what Start Menu does. The remapping would have to be invoked only for this specific sequence <kbd>Ctrl</kbd> key down, <kbd>Alt</kbd> key down, <kbd>Alt</kbd> key up, <kbd>Ctrl</kbd> key up (ordering between Ctrl and Alt can be swapped). If any other key is pressed in between it shouldn't be invoked, and since this logic requires tracking exact states instead of using GetAsyncKeyStates, this could cause false positives if a user is not running as admin.
This method is used for handling global shortcut to shortcut and shortcut to key remaps. The general logic is as follows:
dwExtraInfo field is set to KEYBOARDMANAGER_SHORTCUT_FLAG. This indicates that the key event was generated by the KBM shortcut remap method using SendInput. This ensures that we don't read events generated by the shortcut remap method, but we still read events which are generated by the key remap method.HandleShortcutRemapEvent without the activatedApp argument so that global shortcut remapping takes place if it applies for the current key event.This method is used for handling app-specific shortcut to shortcut and shortcut to key remaps. The general logic is as follows:
dwExtraInfo field is set to KEYBOARDMANAGER_SHORTCUT_FLAG. This indicates that the key event was generated by the KBM shortcut remap method using SendInput. This ensures that we don't read events generated by the shortcut remap method, but we still read events which are generated by the key remap method.GetCurrentApplication which uses GetForegroundWindow to get the window handle and get_process_path from the common lib. This approach can fail for UWP apps in full screen, so for that scenario we use the GetGUIThreadInfo approach to find the correct window handle, and hence the correct process name. This method is described in more detailKeyboardManagerState.GetActivatedApp we check if an app-specific shortcut is currently invoked. If so, we consider this application to be the activated app. This is required because some shortcut remaps could cause the current app to lose focus and hence until the shortcut is completely released we should allow that remap to continue; otherwise, the user could end up in a state where some keys do not get released. For example: remap <kbd>Ctrl+A</kbd> to <kbd>Alt+Tab</kbd> for Edge, when a user presses <kbd>Ctrl+A</kbd> the window loses focus as <kbd>Alt+Tab</kbd> gets executed.HandleShortcutRemapEvent with the activatedApp argument so that app-specific shortcut remapping takes place if it applies for the current key event.This method was added to support a feature for converting the behavior of a key from behaving like a toggle (like Caps Lock/Num Lock) to a modifier (like Ctrl), such that when you hold Caps Lock it would behave as if Caps Lock was active, and when it was not pressed Caps Lock would be off. For Caps Lock this would be similar to behaving like Shift, but for Num Lock there is no existing key which can substitute for this. This was added while testing out remapping for the KBM PoC, but wasn't added as a feature since it wasn't a priority.
In order to test the remapping logic, a mocked keyboard input handler had to be created because otherwise the tests would process and send actual key events. For this the InputInterface was made, and in production code the methods are implemented using SendInput and GetAsyncKeyState. In addition to this, GetCurrentApplication had to be mocked so that app-specific remapping can be tested.
The MockedInput class uses a 256 size bool vector to store the key state for each key code. Identifying the foreground process is mocked by simply setting and getting a string value for the name of the current process.
To mock the SendInput method, the steps for processing the input are as follows. This implementation is based on public documentation for SendInput and the behavior of key messages and keyboard hooks:
INPUT vector argument.WM_SYSKEYUP if Alt is held down; otherwise, it is WM_KEYUP.WM_SYSKEYDOWN if either Alt is held down or if it is F10; otherwise, it is WM_KEYDOWN.MockedInput handler can be used to test for the number of times a key event is received by the system with a particular condition using sendVirtualInputCallCondition.MockedInput handler such that it behaves like a low level hook would behave with actual keyboard input. If the method returns 1, then the keyboard state is not updated, and if it returns 0 the corresponding key event is used to update the key state. This works in the recursive way as well similar to low level hooks, as SendVirtualInput can be called from within the hook, thus simulating identical behavior to calling SendInput in a low level hook (as soon as SendInput is called, the low level hook is called for the new input event, and only after those are processed it returns back to the current event, check this blog for more details).Using the MockedInput handler, all the expected (and known) key scenarios that can occur for while pressing a remapped key or remapped shortcut are tested. The foreground app behavior which is specific to app-specific shortcuts is tested in AppSpecificShortcutRemappingTests.cpp.