Skip to content

Commit

Permalink
Fix #5784: Key bindings won't consume dead keys (#7686)
Browse files Browse the repository at this point in the history
Let's assume the user has bound the dead key ^ to a sendInput command
that sends "b".  If the user presses the two keys ^a it'll produce "bâ",
despite us marking the key event as handled.  We can use `ToUnicodeEx`
to clear such dead keys from the keyboard state and should make use of
that for keybindings.  Unfortunately `SetKeyboardState` cannot be used
for this purpose as it doesn't clear the dead key state.

Validation
* Enabled a German keyboard layout
* Added the following two keybindings:
  { "command": { "action": "sendInput", "input": "x" }, "keys": "q" },
  { "command": { "action": "sendInput", "input": "b" }, "keys": "^" }
* Pressed the following keys → ensured that the given text is printed:
  * q → x
  * ´ → nothing
  * a → á
  * ^ → b
  * a → a (previously this would print: â)
  * ´ → nothing
  * ^ → b
  * a → a (unfortunately we cannot specifically clear only ^)

Closes #5784
  • Loading branch information
lhecker authored Oct 19, 2020
1 parent 1ad7a25 commit 4099aac
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 4 deletions.
2 changes: 2 additions & 0 deletions .github/actions/spell-check/dictionary/names.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ mbadolato
Mehrain
mgravell
michaelniksa
michkap
migrie
mikegr
mikemaccana
miloush
miniksa
niksa
oising
Expand Down
40 changes: 37 additions & 3 deletions src/cascadia/TerminalControl/TermControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -895,7 +895,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// keybindings on the keyUp, then we'll still send the keydown to the
// connected terminal application, and something like ctrl+shift+T will
// emit a ^T to the pipe.
if (!modifiers.IsAltGrPressed() && keyDown && _TryHandleKeyBinding(vkey, modifiers))
if (!modifiers.IsAltGrPressed() && keyDown && _TryHandleKeyBinding(vkey, scanCode, modifiers))
{
e.Handled(true);
return;
Expand All @@ -917,21 +917,54 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// - Attempt to handle this key combination as a key binding
// Arguments:
// - vkey: The vkey of the key pressed.
// - scanCode: The scan code of the key pressed.
// - modifiers: The ControlKeyStates representing the modifier key states.
bool TermControl::_TryHandleKeyBinding(const WORD vkey, ::Microsoft::Terminal::Core::ControlKeyStates modifiers) const
bool TermControl::_TryHandleKeyBinding(const WORD vkey, const WORD scanCode, ::Microsoft::Terminal::Core::ControlKeyStates modifiers) const
{
auto bindings = _settings.KeyBindings();
if (!bindings)
{
return false;
}

return bindings.TryKeyChord({
auto success = bindings.TryKeyChord({
modifiers.IsCtrlPressed(),
modifiers.IsAltPressed(),
modifiers.IsShiftPressed(),
vkey,
});
if (!success)
{
return false;
}

// Let's assume the user has bound the dead key "^" to a sendInput command that sends "b".
// If the user presses the two keys "^a" it'll produce "bâ", despite us marking the key event as handled.
// The following is used to manually "consume" such dead keys and clear them from the keyboard state.
_ClearKeyboardState(vkey, scanCode);
return true;
}

// Method Description:
// - Discards currently pressed dead keys.
// Arguments:
// - vkey: The vkey of the key pressed.
// - scanCode: The scan code of the key pressed.
void TermControl::_ClearKeyboardState(const WORD vkey, const WORD scanCode) const noexcept
{
std::array<BYTE, 256> keyState;
if (!GetKeyboardState(keyState.data()))
{
return;
}

// As described in "Sometimes you *want* to interfere with the keyboard's state buffer":
// http://archives.miloush.net/michkap/archive/2006/09/10/748775.html
// > "The key here is to keep trying to pass stuff to ToUnicode until -1 is not returned."
std::array<wchar_t, 16> buffer;
while (ToUnicodeEx(vkey, scanCode, keyState.data(), buffer.data(), gsl::narrow_cast<int>(buffer.size()), 0b1, nullptr) < 0)
{
}
}

// Method Description:
Expand All @@ -941,6 +974,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// - Makes the cursor briefly visible during typing.
// Arguments:
// - vkey: The vkey of the key pressed.
// - scanCode: The scan code of the key pressed.
// - states: The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states.
// - keyDown: If true, the key was pressed, otherwise the key was released.
bool TermControl::_TrySendKeyEvent(const WORD vkey,
Expand Down
3 changes: 2 additions & 1 deletion src/cascadia/TerminalControl/TermControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation

void _KeyHandler(Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e, const bool keyDown);
::Microsoft::Terminal::Core::ControlKeyStates _GetPressedModifierKeys() const;
bool _TryHandleKeyBinding(const WORD vkey, ::Microsoft::Terminal::Core::ControlKeyStates modifiers) const;
bool _TryHandleKeyBinding(const WORD vkey, const WORD scanCode, ::Microsoft::Terminal::Core::ControlKeyStates modifiers) const;
void _ClearKeyboardState(const WORD vkey, const WORD scanCode) const noexcept;
bool _TrySendKeyEvent(const WORD vkey, const WORD scanCode, ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const bool keyDown);
bool _TrySendMouseEvent(Windows::UI::Input::PointerPoint const& point);
bool _CanSendVTMouseInput();
Expand Down

0 comments on commit 4099aac

Please sign in to comment.