Partially fix mapping of virtual keys to characters (#2836)
This commit is contained in:
parent
847d6b56ad
commit
33361698f7
|
@ -638,41 +638,29 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
|||
}
|
||||
|
||||
const auto modifiers = _GetPressedModifierKeys();
|
||||
|
||||
// AltGr key combinations don't always contain any meaningful,
|
||||
// pretranslated unicode character during WM_KEYDOWN.
|
||||
// E.g. on a German keyboard AltGr+Q should result in a "@" character,
|
||||
// but actually results in "Q" with Alt and Ctrl modifier states.
|
||||
// By returning false though, we can abort handling this WM_KEYDOWN
|
||||
// event and let the WM_CHAR handler kick in, which will be
|
||||
// provided with an appropriate unicode character.
|
||||
//
|
||||
// GH#2235: Make sure to handle AltGr before trying keybindings,
|
||||
// so Ctrl+Alt keybindings won't eat an AltGr keypress.
|
||||
if (modifiers.IsAltGrPressed())
|
||||
{
|
||||
_HandleVoidKeyEvent();
|
||||
e.Handled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto vkey = static_cast<WORD>(e.OriginalKey());
|
||||
const auto scanCode = e.KeyStatus().ScanCode;
|
||||
bool handled = false;
|
||||
|
||||
auto bindings = _settings.KeyBindings();
|
||||
if (bindings)
|
||||
// GH#2235: Terminal::Settings hasn't been modified to differentiate between AltGr and Ctrl+Alt yet.
|
||||
// -> Don't check for key bindings if this is an AltGr key combination.
|
||||
if (!modifiers.IsAltGrPressed())
|
||||
{
|
||||
handled = bindings.TryKeyChord({
|
||||
modifiers.IsCtrlPressed(),
|
||||
modifiers.IsAltPressed(),
|
||||
modifiers.IsShiftPressed(),
|
||||
vkey,
|
||||
});
|
||||
auto bindings = _settings.KeyBindings();
|
||||
if (bindings)
|
||||
{
|
||||
handled = bindings.TryKeyChord({
|
||||
modifiers.IsCtrlPressed(),
|
||||
modifiers.IsAltPressed(),
|
||||
modifiers.IsShiftPressed(),
|
||||
vkey,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!handled)
|
||||
{
|
||||
handled = _TrySendKeyEvent(vkey, modifiers);
|
||||
handled = _TrySendKeyEvent(vkey, scanCode, modifiers);
|
||||
}
|
||||
|
||||
// Manually prevent keyboard navigation with tab. We want to send tab to
|
||||
|
@ -686,17 +674,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
|||
e.Handled(handled);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Some key events cannot be handled (e.g. AltGr combinations) and are
|
||||
// delegated to the character handler. Just like with _TrySendKeyEvent(),
|
||||
// the character handler counts on us though to:
|
||||
// - Clears the current selection.
|
||||
// - Makes the cursor briefly visible during typing.
|
||||
void TermControl::_HandleVoidKeyEvent()
|
||||
{
|
||||
_TrySendKeyEvent(0, {});
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Send this particular key event to the terminal.
|
||||
// See Terminal::SendKeyEvent for more information.
|
||||
|
@ -705,14 +682,14 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
|||
// Arguments:
|
||||
// - vkey: The vkey of the key pressed.
|
||||
// - states: The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states.
|
||||
bool TermControl::_TrySendKeyEvent(WORD vkey, const ControlKeyStates modifiers)
|
||||
bool TermControl::_TrySendKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates modifiers)
|
||||
{
|
||||
_terminal->ClearSelection();
|
||||
|
||||
// If the terminal translated the key, mark the event as handled.
|
||||
// This will prevent the system from trying to get the character out
|
||||
// of it and sending us a CharacterRecieved event.
|
||||
const auto handled = vkey ? _terminal->SendKeyEvent(vkey, modifiers) : true;
|
||||
const auto handled = vkey ? _terminal->SendKeyEvent(vkey, scanCode, modifiers) : true;
|
||||
|
||||
if (_cursorTimer.has_value())
|
||||
{
|
||||
|
|
|
@ -186,8 +186,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
|||
static Windows::UI::Xaml::Thickness _ParseThicknessFromPadding(const hstring padding);
|
||||
|
||||
::Microsoft::Terminal::Core::ControlKeyStates _GetPressedModifierKeys() const;
|
||||
void _HandleVoidKeyEvent();
|
||||
bool _TrySendKeyEvent(WORD vkey, ::Microsoft::Terminal::Core::ControlKeyStates modifiers);
|
||||
bool _TrySendKeyEvent(const WORD vkey, const WORD scanCode, ::Microsoft::Terminal::Core::ControlKeyStates modifiers);
|
||||
|
||||
const COORD _GetTerminalPosition(winrt::Windows::Foundation::Point cursorPosition);
|
||||
const unsigned int _NumberOfClicks(winrt::Windows::Foundation::Point clickPos, Timestamp clickTime);
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace Microsoft::Terminal::Core
|
|||
public:
|
||||
virtual ~ITerminalInput() {}
|
||||
|
||||
virtual bool SendKeyEvent(const WORD vkey, const ControlKeyStates states) = 0;
|
||||
virtual bool SendKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states) = 0;
|
||||
|
||||
// void SendMouseEvent(uint row, uint col, KeyModifiers modifiers);
|
||||
[[nodiscard]] virtual HRESULT UserResize(const COORD size) noexcept = 0;
|
||||
|
|
|
@ -205,7 +205,7 @@ void Terminal::Write(std::wstring_view stringView)
|
|||
// Return Value:
|
||||
// - true if we translated the key event, and it should not be processed any further.
|
||||
// - false if we did not translate the key, and it should be processed into a character.
|
||||
bool Terminal::SendKeyEvent(const WORD vkey, const ControlKeyStates states)
|
||||
bool Terminal::SendKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states)
|
||||
{
|
||||
if (_snapOnInput && _scrollOffset != 0)
|
||||
{
|
||||
|
@ -222,14 +222,7 @@ bool Terminal::SendKeyEvent(const WORD vkey, const ControlKeyStates states)
|
|||
wchar_t ch = UNICODE_NULL;
|
||||
if (states.IsAltPressed() && vkey != VK_SPACE)
|
||||
{
|
||||
ch = static_cast<wchar_t>(LOWORD(MapVirtualKey(vkey, MAPVK_VK_TO_CHAR)));
|
||||
// MapVirtualKey will give us the capitalized version of the char.
|
||||
// However, if shift isn't pressed, we want to send the lowercase version.
|
||||
// (See GH#637)
|
||||
if (!states.IsShiftPressed())
|
||||
{
|
||||
ch = towlower(ch);
|
||||
}
|
||||
ch = _CharacterFromKeyEvent(vkey, scanCode, states);
|
||||
}
|
||||
|
||||
if (states.IsCtrlPressed())
|
||||
|
@ -260,12 +253,54 @@ bool Terminal::SendKeyEvent(const WORD vkey, const ControlKeyStates states)
|
|||
|
||||
const bool manuallyHandled = ch != UNICODE_NULL;
|
||||
|
||||
KeyEvent keyEv{ true, 0, vkey, 0, ch, states.Value() };
|
||||
KeyEvent keyEv{ true, 0, vkey, scanCode, ch, states.Value() };
|
||||
const bool translated = _terminalInput->HandleKey(&keyEv);
|
||||
|
||||
return translated && manuallyHandled;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns the keyboard's scan code for the given virtual key code.
|
||||
// Arguments:
|
||||
// - vkey: The virtual key code.
|
||||
// Return Value:
|
||||
// - The keyboard's scan code.
|
||||
WORD Terminal::_ScanCodeFromVirtualKey(const WORD vkey) noexcept
|
||||
{
|
||||
return LOWORD(MapVirtualKeyW(vkey, MAPVK_VK_TO_VSC));
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Translates the specified virtual key code and keyboard state to the corresponding character.
|
||||
// Arguments:
|
||||
// - vkey: The virtual key code that initiated this keyboard event.
|
||||
// - scanCode: The scan code that initiated this keyboard event.
|
||||
// - states: The current keyboard state.
|
||||
// Return Value:
|
||||
// - The character that would result from this virtual key code and keyboard state.
|
||||
wchar_t Terminal::_CharacterFromKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states) noexcept
|
||||
{
|
||||
const auto sc = scanCode != 0 ? scanCode : _ScanCodeFromVirtualKey(vkey);
|
||||
|
||||
// We might want to use GetKeyboardState() instead of building our own keyState.
|
||||
// The question is whether that's necessary though. For now it seems to work fine as it is.
|
||||
BYTE keyState[256] = {};
|
||||
keyState[VK_SHIFT] = states.IsShiftPressed() ? 0x80 : 0;
|
||||
keyState[VK_CONTROL] = states.IsCtrlPressed() ? 0x80 : 0;
|
||||
keyState[VK_MENU] = states.IsAltPressed() ? 0x80 : 0;
|
||||
|
||||
// Technically ToUnicodeEx() can produce arbitrarily long sequences of diacritics etc.
|
||||
// Since we only handle the case of a single UTF-16 code point, we can set the buffer size to 2 though.
|
||||
constexpr size_t bufferSize = 2;
|
||||
wchar_t buffer[bufferSize];
|
||||
|
||||
// wFlags: If bit 2 is set, keyboard state is not changed (Windows 10, version 1607 and newer)
|
||||
const auto result = ToUnicodeEx(vkey, sc, keyState, buffer, bufferSize, 0b100, nullptr);
|
||||
|
||||
// TODO:GH#2853 We're only handling single UTF-16 code points right now, since that's the only thing KeyEvent supports.
|
||||
return result == 1 || result == -1 ? buffer[0] : 0;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Aquire a read lock on the terminal.
|
||||
// Return Value:
|
||||
|
|
|
@ -83,7 +83,7 @@ public:
|
|||
|
||||
#pragma region ITerminalInput
|
||||
// These methods are defined in Terminal.cpp
|
||||
bool SendKeyEvent(const WORD vkey, const Microsoft::Terminal::Core::ControlKeyStates states) override;
|
||||
bool SendKeyEvent(const WORD vkey, const WORD scanCode, const Microsoft::Terminal::Core::ControlKeyStates states) override;
|
||||
[[nodiscard]] HRESULT UserResize(const COORD viewportSize) noexcept override;
|
||||
void UserScrollViewport(const int viewTop) override;
|
||||
int GetScrollOffset() override;
|
||||
|
@ -208,6 +208,9 @@ private:
|
|||
// underneath them, while others would prefer to anchor it in place.
|
||||
// Either way, we sohould make this behavior controlled by a setting.
|
||||
|
||||
static WORD _ScanCodeFromVirtualKey(const WORD vkey) noexcept;
|
||||
static wchar_t _CharacterFromKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states) noexcept;
|
||||
|
||||
int _ViewStartIndex() const noexcept;
|
||||
int _VisibleStartIndex() const noexcept;
|
||||
|
||||
|
|
|
@ -48,12 +48,12 @@ namespace TerminalCoreUnitTests
|
|||
// Verify that Alt+a generates a lowercase a on the input
|
||||
expectedinput = L"\x1b"
|
||||
"a";
|
||||
VERIFY_IS_TRUE(term.SendKeyEvent(L'A', ControlKeyStates::LeftAltPressed));
|
||||
VERIFY_IS_TRUE(term.SendKeyEvent(L'A', 0, ControlKeyStates::LeftAltPressed));
|
||||
|
||||
// Verify that Alt+shift+a generates a uppercase a on the input
|
||||
expectedinput = L"\x1b"
|
||||
"A";
|
||||
VERIFY_IS_TRUE(term.SendKeyEvent(L'A', ControlKeyStates::LeftAltPressed | ControlKeyStates::ShiftPressed));
|
||||
VERIFY_IS_TRUE(term.SendKeyEvent(L'A', 0, ControlKeyStates::LeftAltPressed | ControlKeyStates::ShiftPressed));
|
||||
}
|
||||
|
||||
void InputTest::AltSpace()
|
||||
|
@ -61,6 +61,6 @@ namespace TerminalCoreUnitTests
|
|||
// Make sure we don't handle Alt+Space. The system will use this to
|
||||
// bring up the system menu for restore, min/maximimize, size, move,
|
||||
// close
|
||||
VERIFY_IS_FALSE(term.SendKeyEvent(L' ', ControlKeyStates::LeftAltPressed));
|
||||
VERIFY_IS_FALSE(term.SendKeyEvent(L' ', 0, ControlKeyStates::LeftAltPressed));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue