diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 81e2bc517..bec406169 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -3,38 +3,38 @@ #include "pch.h" #include "TerminalPage.h" -#include "Utils.h" -#include "../../types/inc/utils.hpp" +#include "TerminalPage.g.cpp" +#include "RenameWindowRequestedArgs.g.cpp" #include + +#include #include +#include +#include -#include "TerminalPage.g.cpp" -#include - -#include "TabRowControl.h" +#include "../../types/inc/utils.hpp" #include "ColorHelper.h" #include "DebugTapConnection.h" #include "SettingsTab.h" -#include "RenameWindowRequestedArgs.g.cpp" -#include "../inc/WindowingBehavior.h" - -#include +#include "TabRowControl.h" using namespace winrt; -using namespace winrt::Windows::Foundation::Collections; -using namespace winrt::Windows::UI::Xaml; -using namespace winrt::Windows::UI::Xaml::Controls; -using namespace winrt::Windows::UI::Core; -using namespace winrt::Windows::System; -using namespace winrt::Windows::ApplicationModel::DataTransfer; -using namespace winrt::Windows::UI::Text; -using namespace winrt::Microsoft::Terminal; using namespace winrt::Microsoft::Terminal::Control; -using namespace winrt::Microsoft::Terminal::TerminalConnection; using namespace winrt::Microsoft::Terminal::Settings::Model; +using namespace winrt::Microsoft::Terminal::TerminalConnection; +using namespace winrt::Microsoft::Terminal; +using namespace winrt::Windows::ApplicationModel::DataTransfer; +using namespace winrt::Windows::Foundation::Collections; +using namespace winrt::Windows::System; +using namespace winrt::Windows::System; +using namespace winrt::Windows::UI::Core; +using namespace winrt::Windows::UI::Text; +using namespace winrt::Windows::UI::Xaml::Controls; +using namespace winrt::Windows::UI::Xaml; using namespace ::TerminalApp; using namespace ::Microsoft::Console; +using namespace ::Microsoft::Terminal::Core; using namespace std::chrono_literals; #define HOOKUP_ACTION(action) _actionDispatch->action({ this, &TerminalPage::_Handle##action }); @@ -1038,29 +1038,153 @@ namespace winrt::TerminalApp::implementation } // Method Description: - // Called when the users pressed keyBindings while CommandPalette is open. + // - Called when the users pressed keyBindings while CommandPalette is open. + // - This method is effectively an extract of TermControl::_KeyHandler and TermControl::_TryHandleKeyBinding. // Arguments: // - e: the KeyRoutedEventArgs containing info about the keystroke. // Return Value: // - void TerminalPage::_KeyDownHandler(Windows::Foundation::IInspectable const& /*sender*/, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e) { - const auto key = e.OriginalKey(); - const auto scanCode = e.KeyStatus().ScanCode; - const auto coreWindow = CoreWindow::GetForCurrentThread(); - const auto ctrlDown = WI_IsFlagSet(coreWindow.GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down); - const auto altDown = WI_IsFlagSet(coreWindow.GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down); - const auto shiftDown = WI_IsFlagSet(coreWindow.GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down); + const auto keyStatus = e.KeyStatus(); + const auto vkey = gsl::narrow_cast(e.OriginalKey()); + const auto scanCode = gsl::narrow_cast(keyStatus.ScanCode); + const auto modifiers = _GetPressedModifierKeys(); - winrt::Microsoft::Terminal::Control::KeyChord kc{ ctrlDown, altDown, shiftDown, false, static_cast(key), static_cast(scanCode) }; - if (const auto cmd{ _settings.ActionMap().GetActionByKeyChord(kc) }) + // GH#11076: + // For some weird reason we sometimes receive a WM_KEYDOWN + // message without vkey or scanCode if a user drags a tab. + // The KeyChord constructor has a debug assertion ensuring that all KeyChord + // either have a valid vkey/scanCode. This is important, because this prevents + // accidential insertion of invalid KeyChords into classes like ActionMap. + if (!vkey && !scanCode) { - if (CommandPalette().Visibility() == Visibility::Visible && cmd.ActionAndArgs().Action() != ShortcutAction::ToggleCommandPalette) + return; + } + + // Alt-Numpad# input will send us a character once the user releases + // Alt, so we should be ignoring the individual keydowns. The character + // will be sent through the TSFInputControl. See GH#1401 for more + // details + if (modifiers.IsAltPressed() && (vkey >= VK_NUMPAD0 && vkey <= VK_NUMPAD9)) + { + return; + } + + // 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()) + { + return; + } + + const auto actionMap = _settings.ActionMap(); + if (!actionMap) + { + return; + } + + const auto cmd = actionMap.GetActionByKeyChord({ + modifiers.IsCtrlPressed(), + modifiers.IsAltPressed(), + modifiers.IsShiftPressed(), + modifiers.IsWinPressed(), + vkey, + scanCode, + }); + if (!cmd) + { + return; + } + + if (!_actionDispatch->DoAction(cmd.ActionAndArgs())) + { + return; + } + + if (const auto p = CommandPalette(); p.Visibility() == Visibility::Visible && cmd.ActionAndArgs().Action() != ShortcutAction::ToggleCommandPalette) + { + p.Visibility(Visibility::Collapsed); + } + + // 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); + e.Handled(true); + } + + // Method Description: + // - Get the modifier keys that are currently pressed. This can be used to + // find out which modifiers (ctrl, alt, shift) are pressed in events that + // don't necessarily include that state. + // - This is a copy of TermControl::_GetPressedModifierKeys. + // Return Value: + // - The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states. + ControlKeyStates TerminalPage::_GetPressedModifierKeys() noexcept + { + const CoreWindow window = CoreWindow::GetForCurrentThread(); + // DONT USE + // != CoreVirtualKeyStates::None + // OR + // == CoreVirtualKeyStates::Down + // Sometimes with the key down, the state is Down | Locked. + // Sometimes with the key up, the state is Locked. + // IsFlagSet(Down) is the only correct solution. + + struct KeyModifier + { + VirtualKey vkey; + ControlKeyStates flags; + }; + + constexpr std::array modifiers{ { + { VirtualKey::RightMenu, ControlKeyStates::RightAltPressed }, + { VirtualKey::LeftMenu, ControlKeyStates::LeftAltPressed }, + { VirtualKey::RightControl, ControlKeyStates::RightCtrlPressed }, + { VirtualKey::LeftControl, ControlKeyStates::LeftCtrlPressed }, + { VirtualKey::Shift, ControlKeyStates::ShiftPressed }, + { VirtualKey::RightWindows, ControlKeyStates::RightWinPressed }, + { VirtualKey::LeftWindows, ControlKeyStates::LeftWinPressed }, + } }; + + ControlKeyStates flags; + + for (const auto& mod : modifiers) + { + const auto state = window.GetKeyState(mod.vkey); + const auto isDown = WI_IsFlagSet(state, CoreVirtualKeyStates::Down); + + if (isDown) { - CommandPalette().Visibility(Visibility::Collapsed); + flags |= mod.flags; } - _actionDispatch->DoAction(cmd.ActionAndArgs()); - e.Handled(true); + } + + return flags; + } + + // Method Description: + // - Discards currently pressed dead keys. + // - This is a copy of TermControl::_ClearKeyboardState. + // Arguments: + // - vkey: The vkey of the key pressed. + // - scanCode: The scan code of the key pressed. + void TerminalPage::_ClearKeyboardState(const WORD vkey, const WORD scanCode) noexcept + { + std::array 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 buffer; + while (ToUnicodeEx(vkey, scanCode, keyState.data(), buffer.data(), gsl::narrow_cast(buffer.size()), 0b1, nullptr) < 0) + { } } diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index cf9477d2e..f08eb775d 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -14,12 +14,17 @@ static constexpr uint32_t DefaultRowsToScroll{ 3 }; static constexpr std::wstring_view TabletInputServiceKey{ L"TabletInputService" }; -// fwdecl unittest classes + namespace TerminalAppLocalTests { class TabTests; class SettingsTests; -}; +} + +namespace Microsoft::Terminal::Core +{ + class ControlKeyStates; +} namespace winrt::TerminalApp::implementation { @@ -225,6 +230,8 @@ namespace winrt::TerminalApp::implementation void _ThirdPartyNoticesOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs); void _KeyDownHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e); + static ::Microsoft::Terminal::Core::ControlKeyStates _GetPressedModifierKeys() noexcept; + static void _ClearKeyboardState(const WORD vkey, const WORD scanCode) noexcept; void _HookupKeyBindings(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap) noexcept; void _RegisterActionCallbacks(); diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 25fd6d2d4..d78d88ab5 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -773,12 +773,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation _HidePointerCursorHandlers(*this, nullptr); const auto ch = e.Character(); - const auto scanCode = gsl::narrow_cast(e.KeyStatus().ScanCode); + const auto keyStatus = e.KeyStatus(); + const auto scanCode = gsl::narrow_cast(keyStatus.ScanCode); auto modifiers = _GetPressedModifierKeys(); - if (e.KeyStatus().IsExtendedKey) + + if (keyStatus.IsExtendedKey) { modifiers |= ControlKeyStates::EnhancedKey; } + const bool handled = _core.SendCharEvent(ch, scanCode, modifiers); e.Handled(handled); } @@ -873,6 +876,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation const auto scanCode = gsl::narrow_cast(keyStatus.ScanCode); auto modifiers = _GetPressedModifierKeys(); + if (keyStatus.IsExtendedKey) + { + modifiers |= ControlKeyStates::EnhancedKey; + } + // GH#11076: // For some weird reason we sometimes receive a WM_KEYDOWN // message without vkey or scanCode if a user drags a tab. @@ -906,11 +914,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation return; } - if (keyStatus.IsExtendedKey) - { - modifiers |= ControlKeyStates::EnhancedKey; - } - // Alt-Numpad# input will send us a character once the user releases // Alt, so we should be ignoring the individual keydowns. The character // will be sent through the TSFInputControl. See GH#1401 for more @@ -988,7 +991,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // 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 + void TermControl::_ClearKeyboardState(const WORD vkey, const WORD scanCode) noexcept { std::array keyState; if (!GetKeyboardState(keyState.data())) @@ -2060,7 +2063,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // don't necessarily include that state. // Return Value: // - The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states. - ControlKeyStates TermControl::_GetPressedModifierKeys() const + ControlKeyStates TermControl::_GetPressedModifierKeys() noexcept { const CoreWindow window = CoreWindow::GetForCurrentThread(); // DONT USE diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index cde51ec12..441e947b4 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -245,9 +245,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation void _UpdateAutoScroll(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e); void _KeyHandler(Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e, const bool keyDown); - ::Microsoft::Terminal::Core::ControlKeyStates _GetPressedModifierKeys() const; + static ::Microsoft::Terminal::Core::ControlKeyStates _GetPressedModifierKeys() noexcept; bool _TryHandleKeyBinding(const WORD vkey, const WORD scanCode, ::Microsoft::Terminal::Core::ControlKeyStates modifiers) const; - void _ClearKeyboardState(const WORD vkey, const WORD scanCode) const noexcept; + static void _ClearKeyboardState(const WORD vkey, const WORD scanCode) noexcept; bool _TrySendKeyEvent(const WORD vkey, const WORD scanCode, ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const bool keyDown); const til::point _toTerminalOrigin(winrt::Windows::Foundation::Point cursorPosition);