diff --git a/src/cascadia/TerminalApp/App.xaml b/src/cascadia/TerminalApp/App.xaml index a12620b8db8..b6a46ce3a97 100644 --- a/src/cascadia/TerminalApp/App.xaml +++ b/src/cascadia/TerminalApp/App.xaml @@ -42,11 +42,15 @@ the MIT License. See LICENSE in the project root for license information. --> + + + + diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index c7b75fc605e..8d074778154 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -167,6 +167,17 @@ namespace winrt::TerminalApp::implementation args.Handled(true); } + void TerminalPage::_HandleToggleInputBroadcast(const IInspectable& /*sender*/, + const ActionEventArgs& args) + { + if (const auto activeTab{ _GetFocusedTabImpl() }) + { + activeTab->ToggleInputBroadcast(); + } + + args.Handled(true); + } + void TerminalPage::_HandleScrollUpPage(const IInspectable& /*sender*/, const ActionEventArgs& args) { diff --git a/src/cascadia/TerminalApp/CommandPalette.xaml b/src/cascadia/TerminalApp/CommandPalette.xaml index 56bac30e6be..a54e554547e 100644 --- a/src/cascadia/TerminalApp/CommandPalette.xaml +++ b/src/cascadia/TerminalApp/CommandPalette.xaml @@ -246,6 +246,13 @@ the MIT License. See LICENSE in the project root for license information. --> FontSize="12" Margin="0,0,8,0"/> + + diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index f7d681564a5..9340c8c3e0b 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -33,6 +33,8 @@ static const Duration AnimationDuration = DurationHelper::FromTimeSpan(winrt::Wi winrt::Windows::UI::Xaml::Media::SolidColorBrush Pane::s_focusedBorderBrush = { nullptr }; winrt::Windows::UI::Xaml::Media::SolidColorBrush Pane::s_unfocusedBorderBrush = { nullptr }; +winrt::Windows::UI::Xaml::Media::SolidColorBrush Pane::s_readOnlyBorderBrush = { nullptr }; +winrt::Windows::UI::Xaml::Media::SolidColorBrush Pane::s_broadcastBorderBrush = { nullptr }; Pane::Pane(const GUID& profile, const TermControl& control, const bool lastFocused) : _control{ control }, @@ -44,11 +46,12 @@ Pane::Pane(const GUID& profile, const TermControl& control, const bool lastFocus _connectionStateChangedToken = _control.ConnectionStateChanged({ this, &Pane::_ControlConnectionStateChangedHandler }); _warningBellToken = _control.WarningBell({ this, &Pane::_ControlWarningBellHandler }); + _readOnlyChangedToken = _control.ReadOnlyChanged({ this, &Pane::_ControlReadOnlyChangedHandler }); // On the first Pane's creation, lookup resources we'll use to theme the // Pane, including the brushed to use for the focused/unfocused border // color. - if (s_focusedBorderBrush == nullptr || s_unfocusedBorderBrush == nullptr) + if (s_focusedBorderBrush == nullptr || s_unfocusedBorderBrush == nullptr || s_readOnlyBorderBrush == nullptr || s_broadcastBorderBrush == nullptr) { _SetupResources(); } @@ -586,7 +589,7 @@ bool Pane::_HasFocusedChild() const noexcept // - void Pane::UpdateVisuals() { - _border.BorderBrush(_lastActive ? s_focusedBorderBrush : s_unfocusedBorderBrush); + _border.BorderBrush(_ComputeBorderColor()); } // Method Description: @@ -698,6 +701,7 @@ void Pane::_CloseChild(const bool closeFirst) // Add our new event handler before revoking the old one. _connectionStateChangedToken = _control.ConnectionStateChanged({ this, &Pane::_ControlConnectionStateChangedHandler }); _warningBellToken = _control.WarningBell({ this, &Pane::_ControlWarningBellHandler }); + _readOnlyChangedToken = _control.ReadOnlyChanged({ this, &Pane::_ControlReadOnlyChangedHandler }); // Revoke the old event handlers. Remove both the handlers for the panes // themselves closing, and remove their handlers for their controls @@ -709,6 +713,8 @@ void Pane::_CloseChild(const bool closeFirst) remainingChild->_control.ConnectionStateChanged(remainingChild->_connectionStateChangedToken); closedChild->_control.WarningBell(closedChild->_warningBellToken); remainingChild->_control.WarningBell(remainingChild->_warningBellToken); + closedChild->_control.ReadOnlyChanged(closedChild->_readOnlyChangedToken); + remainingChild->_control.ReadOnlyChanged(remainingChild->_readOnlyChangedToken); // If either of our children was focused, we want to take that focus from // them. @@ -1440,6 +1446,8 @@ std::pair, std::shared_ptr> Pane::_Split(SplitState _connectionStateChangedToken.value = 0; _control.WarningBell(_warningBellToken); _warningBellToken.value = 0; + _control.ReadOnlyChanged(_readOnlyChangedToken); + _readOnlyChangedToken.value = 0; // Remove our old GotFocus handler from the control. We don't what the // control telling us that it's now focused, we want it telling its new @@ -2022,6 +2030,34 @@ void Pane::_SetupResources() // will eat focus. s_unfocusedBorderBrush = SolidColorBrush{ Colors::Black() }; } + + const auto readOnlyColorKey = winrt::box_value(L"ReadOnlyPaneBorderColor"); + if (res.HasKey(readOnlyColorKey)) + { + winrt::Windows::Foundation::IInspectable obj = res.Lookup(readOnlyColorKey); + s_readOnlyBorderBrush = obj.try_as(); + } + else + { + // DON'T use Transparent here - if it's "Transparent", then it won't + // be able to hittest for clicks, and then clicking on the border + // will eat focus. + s_readOnlyBorderBrush = SolidColorBrush{ Colors::Black() }; + } + + const auto broadcastColorKey = winrt::box_value(L"BroadcastPaneBorderColor"); + if (res.HasKey(broadcastColorKey)) + { + winrt::Windows::Foundation::IInspectable obj = res.Lookup(broadcastColorKey); + s_broadcastBorderBrush = obj.try_as(); + } + else + { + // DON'T use Transparent here - if it's "Transparent", then it won't + // be able to hittest for clicks, and then clicking on the border + // will eat focus. + s_broadcastBorderBrush = SolidColorBrush{ Colors::Black() }; + } } int Pane::GetLeafPaneCount() const noexcept @@ -2101,6 +2137,78 @@ bool Pane::ContainsReadOnly() const return _IsLeaf() ? _control.ReadOnly() : (_firstChild->ContainsReadOnly() || _secondChild->ContainsReadOnly()); } +void Pane::EnableBroadcast(bool enabled) +{ + if (_IsLeaf()) + { + _broadcastEnabled = enabled; + UpdateVisuals(); + } + else + { + _firstChild->EnableBroadcast(enabled); + _secondChild->EnableBroadcast(enabled); + } +} + + +void Pane::BroadcastKey(const winrt::Microsoft::Terminal::TerminalControl::TermControl& sourceControl, const WORD vkey, const WORD scanCode, const DWORD modifiers, const bool keyDown) +{ + if (_IsLeaf()) + { + if (_control != sourceControl && !_control.ReadOnly()) + { + _control.TrySendKeyEvent(vkey, scanCode, modifiers, keyDown); + } + } + else + { + _firstChild->BroadcastKey(sourceControl, vkey, scanCode, modifiers, keyDown); + _secondChild->BroadcastKey(sourceControl, vkey, scanCode, modifiers, keyDown); + } +} + +void Pane::BroadcastChar(const winrt::Microsoft::Terminal::TerminalControl::TermControl& sourceControl, const wchar_t character, const WORD scanCode, const DWORD modifiers) +{ + if (_IsLeaf()) + { + if (_control != sourceControl && !_control.ReadOnly()) + { + _control.TrySendChar(character, scanCode, modifiers); + } + } + else + { + _firstChild->BroadcastChar(sourceControl, character, scanCode, modifiers); + _secondChild->BroadcastChar(sourceControl, character, scanCode, modifiers); + } +} + +void Pane::_ControlReadOnlyChangedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/, const winrt::Windows::Foundation::IInspectable& /*e*/) +{ + UpdateVisuals(); +} + +winrt::Windows::UI::Xaml::Media::SolidColorBrush Pane::_ComputeBorderColor() +{ + if (_lastActive) + { + return s_focusedBorderBrush; + } + + if (_control && _control.ReadOnly()) + { + return s_readOnlyBorderBrush; + } + + if (_broadcastEnabled) + { + return s_broadcastBorderBrush; + } + + return s_unfocusedBorderBrush; +} + DEFINE_EVENT(Pane, GotFocus, _GotFocusHandlers, winrt::delegate>); DEFINE_EVENT(Pane, LostFocus, _LostFocusHandlers, winrt::delegate>); DEFINE_EVENT(Pane, PaneRaiseBell, _PaneRaiseBellHandlers, winrt::Windows::Foundation::EventHandler); diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h index 0aa41958e1f..758412cd47c 100644 --- a/src/cascadia/TerminalApp/Pane.h +++ b/src/cascadia/TerminalApp/Pane.h @@ -83,6 +83,10 @@ class Pane : public std::enable_shared_from_this bool ContainsReadOnly() const; + void EnableBroadcast(bool enabled); + void BroadcastKey(const winrt::Microsoft::Terminal::TerminalControl::TermControl& sourceControl, const WORD vkey, const WORD scanCode, const DWORD modifiers, const bool keyDown); + void BroadcastChar(const winrt::Microsoft::Terminal::TerminalControl::TermControl& sourceControl, const wchar_t vkey, const WORD scanCode, const DWORD modifiers); + WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler); DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate>); DECLARE_EVENT(LostFocus, _LostFocusHandlers, winrt::delegate>); @@ -98,6 +102,8 @@ class Pane : public std::enable_shared_from_this winrt::Microsoft::Terminal::TerminalControl::TermControl _control{ nullptr }; static winrt::Windows::UI::Xaml::Media::SolidColorBrush s_focusedBorderBrush; static winrt::Windows::UI::Xaml::Media::SolidColorBrush s_unfocusedBorderBrush; + static winrt::Windows::UI::Xaml::Media::SolidColorBrush s_readOnlyBorderBrush; + static winrt::Windows::UI::Xaml::Media::SolidColorBrush s_broadcastBorderBrush; std::shared_ptr _firstChild{ nullptr }; std::shared_ptr _secondChild{ nullptr }; @@ -112,6 +118,7 @@ class Pane : public std::enable_shared_from_this winrt::event_token _firstClosedToken{ 0 }; winrt::event_token _secondClosedToken{ 0 }; winrt::event_token _warningBellToken{ 0 }; + winrt::event_token _readOnlyChangedToken{ 0 }; winrt::Windows::UI::Xaml::UIElement::GotFocus_revoker _gotFocusRevoker; winrt::Windows::UI::Xaml::UIElement::LostFocus_revoker _lostFocusRevoker; @@ -123,6 +130,7 @@ class Pane : public std::enable_shared_from_this std::atomic _isClosing{ false }; bool _zoomed{ false }; + bool _broadcastEnabled{ false }; bool _IsLeaf() const noexcept; bool _HasFocusedChild() const noexcept; @@ -137,6 +145,7 @@ class Pane : public std::enable_shared_from_this void _ApplySplitDefinitions(); void _SetupEntranceAnimation(); void _UpdateBorders(); + winrt::Windows::UI::Xaml::Media::SolidColorBrush _ComputeBorderColor(); bool _Resize(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction); bool _NavigateFocus(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction); @@ -153,6 +162,8 @@ class Pane : public std::enable_shared_from_this void _ControlLostFocusHandler(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e); + void _ControlReadOnlyChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& e); + std::pair _CalcChildrenSizes(const float fullSize) const; SnapChildrenSizeResult _CalcSnappedChildrenSizes(const bool widthOrHeight, const float fullSize) const; SnapSizeResult _CalcSnappedDimension(const bool widthOrHeight, const float dimension) const; diff --git a/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp b/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp index 90eede9d057..3a09c719cbe 100644 --- a/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp +++ b/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp @@ -265,6 +265,11 @@ namespace winrt::TerminalApp::implementation _NewWindowHandlers(*this, eventArgs); break; } + case ShortcutAction::ToggleInputBroadcast: + { + _ToggleInputBroadcastHandlers(*this, eventArgs); + break; + } default: return false; } diff --git a/src/cascadia/TerminalApp/ShortcutActionDispatch.h b/src/cascadia/TerminalApp/ShortcutActionDispatch.h index bdcf59e7a17..aba0d110770 100644 --- a/src/cascadia/TerminalApp/ShortcutActionDispatch.h +++ b/src/cascadia/TerminalApp/ShortcutActionDispatch.h @@ -67,6 +67,7 @@ namespace winrt::TerminalApp::implementation TYPED_EVENT(FindMatch, TerminalApp::ShortcutActionDispatch, Microsoft::Terminal::Settings::Model::ActionEventArgs); TYPED_EVENT(TogglePaneReadOnly, TerminalApp::ShortcutActionDispatch, Microsoft::Terminal::Settings::Model::ActionEventArgs); TYPED_EVENT(NewWindow, TerminalApp::ShortcutActionDispatch, Microsoft::Terminal::Settings::Model::ActionEventArgs); + TYPED_EVENT(ToggleInputBroadcast, TerminalApp::ShortcutActionDispatch, Microsoft::Terminal::Settings::Model::ActionEventArgs); // clang-format on private: diff --git a/src/cascadia/TerminalApp/ShortcutActionDispatch.idl b/src/cascadia/TerminalApp/ShortcutActionDispatch.idl index 4a8b366b945..c628e7b171e 100644 --- a/src/cascadia/TerminalApp/ShortcutActionDispatch.idl +++ b/src/cascadia/TerminalApp/ShortcutActionDispatch.idl @@ -53,5 +53,6 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler FindMatch; event Windows.Foundation.TypedEventHandler TogglePaneReadOnly; event Windows.Foundation.TypedEventHandler NewWindow; + event Windows.Foundation.TypedEventHandler ToggleInputBroadcast; } } diff --git a/src/cascadia/TerminalApp/TabHeaderControl.xaml b/src/cascadia/TerminalApp/TabHeaderControl.xaml index 8b9741b07ab..ddf9f02aa7c 100644 --- a/src/cascadia/TerminalApp/TabHeaderControl.xaml +++ b/src/cascadia/TerminalApp/TabHeaderControl.xaml @@ -61,6 +61,12 @@ the MIT License. See LICENSE in the project root for license information. --> Glyph="" FontSize="12" Margin="0,0,8,0"/> + diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 42c771a229b..4b527933aea 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -1158,6 +1158,7 @@ namespace winrt::TerminalApp::implementation _actionDispatch->FindMatch({ this, &TerminalPage::_HandleFindMatch }); _actionDispatch->TogglePaneReadOnly({ this, &TerminalPage::_HandleTogglePaneReadOnly }); _actionDispatch->NewWindow({ this, &TerminalPage::_HandleNewWindow }); + _actionDispatch->ToggleInputBroadcast({ this, &TerminalPage::_HandleToggleInputBroadcast }); } // Method Description: diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index d26e025516b..add18077780 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -320,6 +320,7 @@ namespace winrt::TerminalApp::implementation void _HandleFindMatch(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args); void _HandleTogglePaneReadOnly(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args); void _HandleNewWindow(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args); + void _HandleToggleInputBroadcast(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args); // Make sure to hook new actions up in _RegisterActionCallbacks! #pragma endregion diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp index 115833893dd..642823e82eb 100644 --- a/src/cascadia/TerminalApp/TerminalTab.cpp +++ b/src/cascadia/TerminalApp/TerminalTab.cpp @@ -413,7 +413,6 @@ namespace winrt::TerminalApp::implementation ++_nextPaneId; } _activePane = first; - _AttachEventHandlersToControl(control); // Add a event handlers to the new panes' GotFocus event. When the pane // gains focus, we'll mark it as the new active pane. @@ -629,6 +628,32 @@ namespace winrt::TerminalApp::implementation } } }); + + control.KeySent([weakThis](auto&& sender, auto&& e) { + if (const auto tab{ weakThis.get() }) + { + if (tab->_tabStatus.IsInputBroadcastActive()) + { + if (const auto termControl{ sender.try_as() }) + { + tab->_rootPane->BroadcastKey(termControl, e.VKey(), e.ScanCode(), e.Modifiers(), e.KeyDown()); + } + } + } + }); + + control.CharSent([weakThis](auto&& sender, auto&& e) { + if (const auto tab{ weakThis.get() }) + { + if (tab->_tabStatus.IsInputBroadcastActive()) + { + if (const auto termControl{ sender.try_as() }) + { + tab->_rootPane->BroadcastChar(termControl, e.Character(), e.ScanCode(), e.Modifiers()); + } + } + } + }); } // Method Description: @@ -1156,6 +1181,14 @@ namespace winrt::TerminalApp::implementation } } + // Method Description: + // - Toggle read-only mode on the active pane + void TerminalTab::ToggleInputBroadcast() + { + _tabStatus.IsInputBroadcastActive(!_tabStatus.IsInputBroadcastActive()); + _rootPane->EnableBroadcast(_tabStatus.IsInputBroadcastActive()); + } + // Method Description: // - Calculates if the tab is read-only. // The tab is considered read-only if one of the panes is read-only. diff --git a/src/cascadia/TerminalApp/TerminalTab.h b/src/cascadia/TerminalApp/TerminalTab.h index f8fb560ee60..5e9f65d96ec 100644 --- a/src/cascadia/TerminalApp/TerminalTab.h +++ b/src/cascadia/TerminalApp/TerminalTab.h @@ -78,6 +78,8 @@ namespace winrt::TerminalApp::implementation int GetLeafPaneCount() const noexcept; void TogglePaneReadOnly(); + void ToggleInputBroadcast(); + std::shared_ptr GetActivePane() const; winrt::TerminalApp::TerminalTabStatus TabStatus() diff --git a/src/cascadia/TerminalApp/TerminalTabStatus.h b/src/cascadia/TerminalApp/TerminalTabStatus.h index 64b75cda5d5..0c5dd96e053 100644 --- a/src/cascadia/TerminalApp/TerminalTabStatus.h +++ b/src/cascadia/TerminalApp/TerminalTabStatus.h @@ -18,6 +18,7 @@ namespace winrt::TerminalApp::implementation OBSERVABLE_GETSET_PROPERTY(bool, IsProgressRingIndeterminate, _PropertyChangedHandlers); OBSERVABLE_GETSET_PROPERTY(bool, BellIndicator, _PropertyChangedHandlers); OBSERVABLE_GETSET_PROPERTY(bool, IsReadOnlyActive, _PropertyChangedHandlers); + OBSERVABLE_GETSET_PROPERTY(bool, IsInputBroadcastActive, _PropertyChangedHandlers); OBSERVABLE_GETSET_PROPERTY(uint32_t, ProgressValue, _PropertyChangedHandlers); }; } diff --git a/src/cascadia/TerminalApp/TerminalTabStatus.idl b/src/cascadia/TerminalApp/TerminalTabStatus.idl index 11d659f745f..57f4e05fa1a 100644 --- a/src/cascadia/TerminalApp/TerminalTabStatus.idl +++ b/src/cascadia/TerminalApp/TerminalTabStatus.idl @@ -13,5 +13,6 @@ namespace TerminalApp Boolean BellIndicator { get; set; }; UInt32 ProgressValue { get; set; }; Boolean IsReadOnlyActive { get; set; }; + Boolean IsInputBroadcastActive { get; set; }; } } diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 041f9393802..26c24852ed5 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -909,10 +909,21 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation { modifiers |= ControlKeyStates::EnhancedKey; } - const bool handled = _terminal->SendCharEvent(ch, scanCode, modifiers); + + const bool handled = TrySendChar(ch, scanCode, modifiers.Value()); + auto charSentArgs = winrt::make(ch, scanCode, modifiers.Value()); + _charSentHandlers(*this, charSentArgs); e.Handled(handled); } + // Method Description: + // - Sends character to terminal + bool TermControl::TrySendChar(const wchar_t character, const WORD scanCode, const DWORD modifiers) + { + ControlKeyStates keyStates{ modifiers }; + return _terminal->SendCharEvent(character, scanCode, keyStates); + } + // Method Description: // - Manually handles key events for certain keys that can't be passed to us // normally. Namely, the keys we're concerned with are F7 down and Alt up. @@ -1111,6 +1122,25 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation } } + // Method Description: + // - Invokes TrySendKeyEvent and triggers KeySent event + // Arguments: + // - vkey: The vkey of the key pressed. + // - scanCode: The scan code of the key pressed. + // - keyStates: 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, + const WORD scanCode, + const ControlKeyStates keyStates, + const bool keyDown) + { + const auto modifiers = keyStates.Value(); + const auto result = TrySendKeyEvent(vkey, scanCode, modifiers, keyDown); + auto keySentArgs = winrt::make(vkey, scanCode, modifiers, keyDown); + _keySentHandlers(*this, std::move(keySentArgs)); + return result; + } + // Method Description: // - Send this particular key event to the terminal. // See Terminal::SendKeyEvent for more information. @@ -1121,10 +1151,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // - 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, - const WORD scanCode, - const ControlKeyStates modifiers, - const bool keyDown) + bool TermControl::TrySendKeyEvent(const WORD vkey, + const WORD scanCode, + const DWORD modifiers, + const bool keyDown) { // When there is a selection active, escape should clear it and NOT flow through // to the terminal. With any other keypress, it should clear the selection AND @@ -1169,7 +1199,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // 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 CharacterReceived event. - const auto handled = vkey ? _terminal->SendKeyEvent(vkey, scanCode, modifiers, keyDown) : true; + ControlKeyStates keyStates{ modifiers }; + const auto handled = vkey ? _terminal->SendKeyEvent(vkey, scanCode, keyStates, keyDown) : true; if (_cursorTimer.has_value()) { @@ -3383,5 +3414,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TermControl, OpenHyperlink, _openHyperlinkHandlers, TerminalControl::TermControl, TerminalControl::OpenHyperlinkEventArgs); DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TermControl, SetTaskbarProgress, _setTaskbarProgressHandlers, TerminalControl::TermControl, IInspectable); DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TermControl, RaiseNotice, _raiseNoticeHandlers, TerminalControl::TermControl, TerminalControl::NoticeEventArgs); + DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TermControl, KeySent, _keySentHandlers, TerminalControl::TermControl, TerminalControl::KeySentEventArgs); + DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TermControl, CharSent, _charSentHandlers, TerminalControl::TermControl, TerminalControl::CharSentEventArgs); // clang-format on } diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 4a85142002b..ecb0f06109e 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -8,6 +8,8 @@ #include "PasteFromClipboardEventArgs.g.h" #include "OpenHyperlinkEventArgs.g.h" #include "NoticeEventArgs.g.h" +#include "KeySentEventArgs.g.h" +#include "CharSentEventArgs.g.h" #include #include "../../renderer/base/Renderer.hpp" #include "../../renderer/dx/DxRenderer.hpp" @@ -98,6 +100,47 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation const hstring _message; }; + struct KeySentEventArgs : + public KeySentEventArgsT + { + public: + KeySentEventArgs(const WORD vkey, const WORD scanCode, const DWORD modifiers, const bool keyDown) : + _vkey(vkey), + _scanCode(scanCode), + _modifiers(modifiers), + _keyDown(keyDown) {} + + WORD VKey() { return _vkey; }; + WORD ScanCode() { return _scanCode; }; + DWORD Modifiers() { return _modifiers; }; + bool KeyDown() { return _keyDown; }; + + private: + const WORD _vkey; + const WORD _scanCode; + const DWORD _modifiers; + const bool _keyDown; + }; + + struct CharSentEventArgs : + public CharSentEventArgsT + { + public: + CharSentEventArgs(const wchar_t character, const WORD scanCode, const DWORD modifiers) : + _character(character), + _scanCode(scanCode), + _modifiers(modifiers) {} + + wchar_t Character() { return _character; }; + WORD ScanCode() { return _scanCode; }; + DWORD Modifiers() { return _modifiers; }; + + private: + const wchar_t _character; + const WORD _scanCode; + const DWORD _modifiers; + }; + struct TermControl : TermControlT { TermControl(IControlSettings settings, TerminalConnection::ITerminalConnection connection); @@ -169,6 +212,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation bool ReadOnly() const noexcept; void ToggleReadOnly(); + bool TrySendKeyEvent(const WORD vkey, const WORD scanCode, const DWORD modifiers, const bool keyDown); + bool TrySendChar(const wchar_t character, const WORD scanCode, const DWORD modifiers); + // clang-format off // -------------------------------- WinRT Events --------------------------------- DECLARE_EVENT(TitleChanged, _titleChangedHandlers, TerminalControl::TitleChangedEventArgs); @@ -180,6 +226,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(OpenHyperlink, _openHyperlinkHandlers, TerminalControl::TermControl, TerminalControl::OpenHyperlinkEventArgs); DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(SetTaskbarProgress, _setTaskbarProgressHandlers, TerminalControl::TermControl, IInspectable); DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(RaiseNotice, _raiseNoticeHandlers, TerminalControl::TermControl, TerminalControl::NoticeEventArgs); + DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(KeySent, _keySentHandlers, TerminalControl::TermControl, TerminalControl::KeySentEventArgs); + DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(CharSent, _charSentHandlers, TerminalControl::TermControl, TerminalControl::CharSentEventArgs); TYPED_EVENT(WarningBell, IInspectable, IInspectable); TYPED_EVENT(ConnectionStateChanged, TerminalControl::TermControl, IInspectable); diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl index 4470c0555d6..e6d31266486 100644 --- a/src/cascadia/TerminalControl/TermControl.idl +++ b/src/cascadia/TerminalControl/TermControl.idl @@ -59,6 +59,21 @@ namespace Microsoft.Terminal.TerminalControl String Message { get; }; } + runtimeclass KeySentEventArgs + { + UInt16 VKey { get; }; + UInt16 ScanCode { get; }; + UInt32 Modifiers { get; }; + Boolean KeyDown { get; }; + } + + runtimeclass CharSentEventArgs + { + Char Character { get; }; + UInt16 ScanCode { get; }; + UInt32 Modifiers { get; }; + } + [default_interface] runtimeclass TermControl : Windows.UI.Xaml.Controls.UserControl, IDirectKeyListener, IMouseWheelListener { TermControl(Microsoft.Terminal.TerminalControl.IControlSettings settings, Microsoft.Terminal.TerminalConnection.ITerminalConnection connection); @@ -79,6 +94,8 @@ namespace Microsoft.Terminal.TerminalControl event Windows.Foundation.TypedEventHandler WarningBell; event Windows.Foundation.TypedEventHandler HidePointerCursor; event Windows.Foundation.TypedEventHandler RestorePointerCursor; + event Windows.Foundation.TypedEventHandler KeySent; + event Windows.Foundation.TypedEventHandler CharSent; event Windows.Foundation.TypedEventHandler Initialized; // This is an event handler forwarder for the underlying connection. @@ -109,6 +126,8 @@ namespace Microsoft.Terminal.TerminalControl void ToggleShaderEffects(); void SendInput(String input); + Boolean TrySendKeyEvent(UInt16 vkey, UInt16 scanCode, UInt32 modifiers, Boolean keyDown); + Boolean TrySendChar(Char character, UInt16 scanCode, UInt32 modifiers); void TaskbarProgressChanged(); UInt64 TaskbarState { get; }; diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp index 7069d448b42..813c496eb76 100644 --- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp @@ -53,6 +53,7 @@ static constexpr std::string_view BreakIntoDebuggerKey{ "breakIntoDebugger" }; static constexpr std::string_view FindMatchKey{ "findMatch" }; static constexpr std::string_view TogglePaneReadOnlyKey{ "toggleReadOnlyMode" }; static constexpr std::string_view NewWindowKey{ "newWindow" }; +static constexpr std::string_view ToggleInputBroadcastKey{ "toggleInputBroadcast" }; static constexpr std::string_view ActionKey{ "action" }; @@ -119,6 +120,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { FindMatchKey, ShortcutAction::FindMatch }, { TogglePaneReadOnlyKey, ShortcutAction::TogglePaneReadOnly }, { NewWindowKey, ShortcutAction::NewWindow }, + { ToggleInputBroadcastKey, ShortcutAction::ToggleInputBroadcast }, }; using ParseResult = std::tuple>; @@ -322,6 +324,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { ShortcutAction::FindMatch, L"" }, // Intentionally omitted, must be generated by GenerateName { ShortcutAction::TogglePaneReadOnly, RS_(L"TogglePaneReadOnlyCommandKey") }, { ShortcutAction::NewWindow, RS_(L"NewWindowCommandKey") }, + { ShortcutAction::ToggleInputBroadcast, RS_(L"ToggleInputBroadcastCommandKey") }, }; }(); diff --git a/src/cascadia/TerminalSettingsModel/KeyMapping.idl b/src/cascadia/TerminalSettingsModel/KeyMapping.idl index a51a601afee..414fee1a78e 100644 --- a/src/cascadia/TerminalSettingsModel/KeyMapping.idl +++ b/src/cascadia/TerminalSettingsModel/KeyMapping.idl @@ -54,7 +54,8 @@ namespace Microsoft.Terminal.Settings.Model BreakIntoDebugger, TogglePaneReadOnly, FindMatch, - NewWindow + NewWindow, + ToggleInputBroadcast }; [default_interface] runtimeclass ActionAndArgs { diff --git a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw index f8a588553f5..14dc4353c3d 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw @@ -366,6 +366,9 @@ Toggle pane read-only mode + + Toggle input broadcast + Toggle terminal visual effects diff --git a/src/cascadia/TerminalSettingsModel/defaults.json b/src/cascadia/TerminalSettingsModel/defaults.json index 3bc56c560be..a58141ba410 100644 --- a/src/cascadia/TerminalSettingsModel/defaults.json +++ b/src/cascadia/TerminalSettingsModel/defaults.json @@ -342,6 +342,7 @@ { "command": { "action": "moveFocus", "direction": "previous" }, "keys": "ctrl+alt+left" }, { "command": "togglePaneZoom" }, { "command": "toggleReadOnlyMode" }, + { "command": "toggleInputBroadcast" }, // Clipboard Integration { "command": { "action": "copy", "singleLine": false }, "keys": "ctrl+shift+c" },