diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 1df5d56266..aeb97f8483 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -42,6 +42,17 @@ namespace winrt::TerminalApp::implementation } return _GetActiveControl(); } + TermControl TerminalPage::_senderOrFocusedElementIfControl(const IInspectable& sender) + { + if (sender) + { + if (auto arg{ sender.try_as() }) + { + return arg; + } + } + return _GetFocusedElementIfControl(); + } winrt::com_ptr TerminalPage::_senderOrFocusedTab(const IInspectable& sender) { if (sender) @@ -134,10 +145,13 @@ namespace winrt::TerminalApp::implementation const ActionEventArgs& args) { const auto& realArgs = args.ActionArgs().try_as(); - if (realArgs) + if (const auto termControl = _GetFocusedElementIfControl()) { - _Scroll(ScrollUp, realArgs.RowsToScroll()); - args.Handled(true); + if (realArgs) + { + _Scroll(ScrollUp, realArgs.RowsToScroll(), termControl); + args.Handled(true); + } } } @@ -145,10 +159,13 @@ namespace winrt::TerminalApp::implementation const ActionEventArgs& args) { const auto& realArgs = args.ActionArgs().try_as(); - if (realArgs) + if (const auto termControl = _GetFocusedElementIfControl()) { - _Scroll(ScrollDown, realArgs.RowsToScroll()); - args.Handled(true); + if (realArgs) + { + _Scroll(ScrollDown, realArgs.RowsToScroll(), termControl); + args.Handled(true); + } } } @@ -174,7 +191,7 @@ namespace winrt::TerminalApp::implementation } } - void TerminalPage::_HandleSendInput(const IInspectable& sender, + void TerminalPage::_HandleSendInput(const IInspectable& /*sender*/, const ActionEventArgs& args) { if (args == nullptr) @@ -183,7 +200,7 @@ namespace winrt::TerminalApp::implementation } else if (const auto& realArgs = args.ActionArgs().try_as()) { - if (const auto termControl{ _senderOrActiveControl(sender) }) + if (const auto termControl = _GetFocusedElementIfControl()) { termControl.SendInput(realArgs.Input()); args.Handled(true); @@ -353,29 +370,41 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_HandleScrollUpPage(const IInspectable& /*sender*/, const ActionEventArgs& args) { - _ScrollPage(ScrollUp); - args.Handled(true); + if (const auto termControl = _GetFocusedElementIfControl()) + { + _ScrollPage(ScrollUp, termControl); + args.Handled(true); + } } void TerminalPage::_HandleScrollDownPage(const IInspectable& /*sender*/, const ActionEventArgs& args) { - _ScrollPage(ScrollDown); - args.Handled(true); + if (const auto termControl = _GetFocusedElementIfControl()) + { + _ScrollPage(ScrollDown, termControl); + args.Handled(true); + } } void TerminalPage::_HandleScrollToTop(const IInspectable& /*sender*/, const ActionEventArgs& args) { - _ScrollToBufferEdge(ScrollUp); - args.Handled(true); + if (const auto termControl = _GetFocusedElementIfControl()) + { + _ScrollToBufferEdge(ScrollUp, termControl); + args.Handled(true); + } } void TerminalPage::_HandleScrollToBottom(const IInspectable& /*sender*/, const ActionEventArgs& args) { - _ScrollToBufferEdge(ScrollDown); - args.Handled(true); + if (const auto termControl = _GetFocusedElementIfControl()) + { + _ScrollToBufferEdge(ScrollDown, termControl); + args.Handled(true); + } } void TerminalPage::_HandleScrollToMark(const IInspectable& /*sender*/, @@ -430,11 +459,11 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_HandleFindMatch(const IInspectable& /*sender*/, const ActionEventArgs& args) { - if (const auto& realArgs = args.ActionArgs().try_as()) + if (const auto termControl = _GetFocusedElementIfControl()) { - if (const auto& control{ _GetActiveControl() }) + if (const auto& realArgs = args.ActionArgs().try_as()) { - control.SearchMatch(realArgs.Direction() == FindMatchDirection::Next); + termControl.SearchMatch(realArgs.Direction() == FindMatchDirection::Next); args.Handled(true); } } @@ -452,8 +481,11 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_HandlePasteText(const IInspectable& /*sender*/, const ActionEventArgs& args) { - _PasteText(); - args.Handled(true); + if (_GetFocusedElementIfControl()) + { + _PasteText(); + args.Handled(true); + } } void TerminalPage::_HandleNewTab(const IInspectable& /*sender*/, @@ -547,10 +579,13 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_HandleCopyText(const IInspectable& /*sender*/, const ActionEventArgs& args) { - if (const auto& realArgs = args.ActionArgs().try_as()) + if (const auto termControl = _GetFocusedElementIfControl()) { - const auto handled = _CopyText(realArgs.DismissSelection(), realArgs.SingleLine(), realArgs.WithControlSequences(), realArgs.CopyFormatting()); - args.Handled(handled); + if (const auto& realArgs = args.ActionArgs().try_as()) + { + const auto handled = termControl.CopySelectionToClipboard(realArgs.DismissSelection(), realArgs.SingleLine(), realArgs.WithControlSequences(), realArgs.CopyFormatting()); + args.Handled(handled); + } } } @@ -1112,7 +1147,7 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_HandleSearchForText(const IInspectable& /*sender*/, const ActionEventArgs& args) { - if (const auto termControl{ _GetActiveControl() }) + if (const auto termControl = _GetFocusedElementIfControl()) { if (termControl.HasSelection()) { @@ -1152,7 +1187,7 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_HandleOpenCWD(const IInspectable& /*sender*/, const ActionEventArgs& args) { - if (const auto& control{ _GetActiveControl() }) + if (const auto control = _GetFocusedElementIfControl()) { control.OpenCWD(); args.Handled(true); @@ -1286,7 +1321,7 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_HandleSelectAll(const IInspectable& sender, const ActionEventArgs& args) { - if (const auto& control{ _senderOrActiveControl(sender) }) + if (const auto control = _senderOrFocusedElementIfControl(sender)) { control.SelectAll(); args.Handled(true); @@ -1308,7 +1343,7 @@ namespace winrt::TerminalApp::implementation auto commandLine = realArgs.Commandline(); if (commandLine.empty()) { - if (const auto termControl{ _GetActiveControl() }) + if (const auto termControl = _GetFocusedElementIfControl()) { if (termControl.HasSelection()) { @@ -1432,7 +1467,7 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_HandleMarkMode(const IInspectable& sender, const ActionEventArgs& args) { - if (const auto& control{ _senderOrActiveControl(sender) }) + if (const auto control = _senderOrFocusedElementIfControl(sender)) { control.ToggleMarkMode(); args.Handled(true); @@ -1442,7 +1477,7 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_HandleToggleBlockSelection(const IInspectable& sender, const ActionEventArgs& args) { - if (const auto& control{ _senderOrActiveControl(sender) }) + if (const auto control = _senderOrFocusedElementIfControl(sender)) { const auto handled = control.ToggleBlockSelection(); args.Handled(handled); @@ -1452,7 +1487,7 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_HandleSwitchSelectionEndpoint(const IInspectable& sender, const ActionEventArgs& args) { - if (const auto& control{ _senderOrActiveControl(sender) }) + if (const auto control = _senderOrFocusedElementIfControl(sender)) { const auto handled = control.SwitchSelectionEndpoint(); args.Handled(handled); @@ -1486,7 +1521,7 @@ namespace winrt::TerminalApp::implementation // then get that here. const bool shouldGetContext = realArgs.UseCommandline() || WI_IsAnyFlagSet(source, SuggestionsSource::CommandHistory | SuggestionsSource::QuickFixes); - if (const auto& control{ _GetActiveControl() }) + if (const auto control = _GetFocusedElementIfControl()) { currentWorkingDirectory = control.CurrentWorkingDirectory(); @@ -1543,7 +1578,7 @@ namespace winrt::TerminalApp::implementation co_await wil::resume_foreground(Dispatcher()); // Open the palette with all these commands in it. - _OpenSuggestions(_GetActiveControl(), + _OpenSuggestions(_GetFocusedElementIfControl(), winrt::single_threaded_vector(std::move(commandsCollection)), SuggestionsMode::Palette, currentCommandline); @@ -1567,7 +1602,7 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_HandleExpandSelectionToWord(const IInspectable& /*sender*/, const ActionEventArgs& args) { - if (const auto& control{ _GetActiveControl() }) + if (const auto control = _GetFocusedElementIfControl()) { const auto handled = control.ExpandSelectionToWord(); args.Handled(handled); @@ -1601,7 +1636,7 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_HandleShowContextMenu(const IInspectable& /*sender*/, const ActionEventArgs& args) { - if (const auto& control{ _GetActiveControl() }) + if (const auto control = _GetFocusedElementIfControl()) { control.ShowContextMenu(); } diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index df172817cf..025f97932c 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -1722,6 +1722,72 @@ namespace winrt::TerminalApp::implementation e.Handled(true); } + void TerminalPage::_PreviewKeyDown(const Windows::Foundation::IInspectable& /*sender*/, const Windows::UI::Xaml::Input::KeyRoutedEventArgs& e) + { + 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(); + + // 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 + // accidental insertion of invalid KeyChords into classes like ActionMap. + if (!vkey && !scanCode) + { + 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; + } + + // 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); + } + bool TerminalPage::OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down) { const auto modifiers = _GetPressedModifierKeys(); @@ -2053,6 +2119,16 @@ namespace winrt::TerminalApp::implementation return nullptr; } + TermControl TerminalPage::_GetFocusedElementIfControl() + { + const auto focusedElement = Windows::UI::Xaml::Input::FocusManager::GetFocusedElement(this->XamlRoot()); + if (const auto termControl = focusedElement.try_as()) + { + return termControl; + } + return nullptr; + } + CommandPalette TerminalPage::LoadCommandPalette() { if (const auto p = CommandPaletteElement()) @@ -2248,16 +2324,16 @@ namespace winrt::TerminalApp::implementation // Arguments: // - scrollDirection: ScrollUp will move the viewport up, ScrollDown will move the viewport down // - rowsToScroll: a number of lines to move the viewport. If not provided we will use a system default. - void TerminalPage::_Scroll(ScrollDirection scrollDirection, const Windows::Foundation::IReference& rowsToScroll) + void TerminalPage::_Scroll(ScrollDirection scrollDirection, const Windows::Foundation::IReference& rowsToScroll, winrt::Microsoft::Terminal::Control::TermControl focusedControl) { - if (const auto terminalTab{ _GetFocusedTabImpl() }) + if (focusedControl) { uint32_t realRowsToScroll; if (rowsToScroll == nullptr) { // The magic value of WHEEL_PAGESCROLL indicates that we need to scroll the entire page realRowsToScroll = _systemRowsToScroll == WHEEL_PAGESCROLL ? - terminalTab->GetActiveTerminalControl().ViewHeight() : + focusedControl.ViewHeight() : _systemRowsToScroll; } else @@ -2266,7 +2342,8 @@ namespace winrt::TerminalApp::implementation realRowsToScroll = rowsToScroll.Value(); } auto scrollDelta = _ComputeScrollDelta(scrollDirection, realRowsToScroll); - terminalTab->Scroll(scrollDelta); + const auto currentOffset = focusedControl.ScrollOffset(); + focusedControl.ScrollViewport(::base::ClampAdd(currentOffset, scrollDelta)); } } @@ -2692,26 +2769,25 @@ namespace winrt::TerminalApp::implementation // down a page. The page length will be dependent on the terminal view height. // Arguments: // - scrollDirection: ScrollUp will move the viewport up, ScrollDown will move the viewport down - void TerminalPage::_ScrollPage(ScrollDirection scrollDirection) + void TerminalPage::_ScrollPage(ScrollDirection scrollDirection, winrt::Microsoft::Terminal::Control::TermControl focusedControl) { // Do nothing if for some reason, there's no terminal tab in focus. We don't want to crash. - if (const auto terminalTab{ _GetFocusedTabImpl() }) + if (focusedControl) { - if (const auto& control{ _GetActiveControl() }) - { - const auto termHeight = control.ViewHeight(); - auto scrollDelta = _ComputeScrollDelta(scrollDirection, termHeight); - terminalTab->Scroll(scrollDelta); - } + const auto termHeight = focusedControl.ViewHeight(); + auto scrollDelta = _ComputeScrollDelta(scrollDirection, termHeight); + const auto currentOffset = focusedControl.ScrollOffset(); + focusedControl.ScrollViewport(::base::ClampAdd(currentOffset, scrollDelta)); } } - void TerminalPage::_ScrollToBufferEdge(ScrollDirection scrollDirection) + void TerminalPage::_ScrollToBufferEdge(ScrollDirection scrollDirection, winrt::Microsoft::Terminal::Control::TermControl focusedControl) { - if (const auto terminalTab{ _GetFocusedTabImpl() }) + if (focusedControl) { auto scrollDelta = _ComputeScrollDelta(scrollDirection, INT_MAX); - terminalTab->Scroll(scrollDelta); + const auto currentOffset = focusedControl.ScrollOffset(); + focusedControl.ScrollViewport(::base::ClampAdd(currentOffset, scrollDelta)); } } diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 0862b2af8d..3594ebff94 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -326,6 +326,7 @@ namespace winrt::TerminalApp::implementation void _AboutButtonOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs); void _KeyDownHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::KeyRoutedEventArgs& e); + void _PreviewKeyDown(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::KeyRoutedEventArgs& 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; @@ -383,6 +384,7 @@ namespace winrt::TerminalApp::implementation } winrt::Microsoft::Terminal::Control::TermControl _GetActiveControl(); + winrt::Microsoft::Terminal::Control::TermControl _GetFocusedElementIfControl(); std::optional _GetFocusedTabIndex() const noexcept; std::optional _GetTabIndex(const TerminalApp::TabBase& tab) const noexcept; TerminalApp::TabBase _GetFocusedTab() const noexcept; @@ -396,7 +398,7 @@ namespace winrt::TerminalApp::implementation winrt::Windows::Foundation::IAsyncOperation _PaneConfirmCloseReadOnly(std::shared_ptr pane); void _AddPreviouslyClosedPaneOrTab(std::vector&& args); - void _Scroll(ScrollDirection scrollDirection, const Windows::Foundation::IReference& rowsToScroll); + void _Scroll(ScrollDirection scrollDirection, const Windows::Foundation::IReference& rowsToScroll, winrt::Microsoft::Terminal::Control::TermControl focusedControl); void _SplitPane(const winrt::com_ptr& tab, const Microsoft::Terminal::Settings::Model::SplitDirection splitType, @@ -405,8 +407,8 @@ namespace winrt::TerminalApp::implementation void _ResizePane(const Microsoft::Terminal::Settings::Model::ResizeDirection& direction); void _ToggleSplitOrientation(); - void _ScrollPage(ScrollDirection scrollDirection); - void _ScrollToBufferEdge(ScrollDirection scrollDirection); + void _ScrollPage(ScrollDirection scrollDirection, winrt::Microsoft::Terminal::Control::TermControl focusedControl); + void _ScrollToBufferEdge(ScrollDirection scrollDirection, winrt::Microsoft::Terminal::Control::TermControl focusedControl); void _SetAcceleratorForMenuItem(Windows::UI::Xaml::Controls::MenuFlyoutItem& menuItem, const winrt::Microsoft::Terminal::Control::KeyChord& keyChord); safe_void_coroutine _PasteFromClipboardHandler(const IInspectable sender, @@ -553,6 +555,7 @@ namespace winrt::TerminalApp::implementation winrt::Windows::UI::Xaml::Controls::MenuFlyout _CreateRunAsAdminFlyout(int profileIndex); winrt::Microsoft::Terminal::Control::TermControl _senderOrActiveControl(const winrt::Windows::Foundation::IInspectable& sender); + winrt::Microsoft::Terminal::Control::TermControl _senderOrFocusedElementIfControl(const winrt::Windows::Foundation::IInspectable& sender); winrt::com_ptr _senderOrFocusedTab(const IInspectable& sender); void _loadQueryExtension(); diff --git a/src/cascadia/TerminalApp/TerminalPage.xaml b/src/cascadia/TerminalApp/TerminalPage.xaml index a2d8ea8568..6e8078be03 100644 --- a/src/cascadia/TerminalApp/TerminalPage.xaml +++ b/src/cascadia/TerminalApp/TerminalPage.xaml @@ -14,7 +14,8 @@ mc:Ignorable="d"> + Background="Transparent" + PreviewKeyDown="_PreviewKeyDown"> @@ -170,7 +171,6 @@ Don't check for key bindings if this is an AltGr key combination. - // - // GH#4999: Only process keybindings on the keydown. If we don't check - // this at all, we'll process the keybinding twice. If we only process - // 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, scanCode, modifiers)) - { - return true; - } - if (_TrySendKeyEvent(vkey, scanCode, modifiers, keyDown)) { return true;