diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 0238b4cd43e..722bf19093b 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -2132,6 +2132,7 @@ WDDMCONSOLECONTEXT wdm webpage websites +websockets wekyb wex wextest diff --git a/src/cascadia/TerminalApp/App.xaml b/src/cascadia/TerminalApp/App.xaml index 59613fa02b7..1cda23ddd7c 100644 --- a/src/cascadia/TerminalApp/App.xaml +++ b/src/cascadia/TerminalApp/App.xaml @@ -174,6 +174,9 @@ + + @@ -190,6 +193,9 @@ + + @@ -210,6 +216,9 @@ + + diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 74ae4cdbb6a..3b33f8b5897 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -1272,6 +1272,17 @@ namespace winrt::TerminalApp::implementation } } + void TerminalPage::_HandleToggleBroadcastInput(const IInspectable& /*sender*/, + const ActionEventArgs& args) + { + if (const auto activeTab{ _GetFocusedTabImpl() }) + { + activeTab->ToggleBroadcastInput(); + args.Handled(true); + } + // If the focused tab wasn't a TerminalTab, then leave handled=false + } + void TerminalPage::_HandleRestartConnection(const IInspectable& /*sender*/, const ActionEventArgs& args) { @@ -1294,4 +1305,5 @@ namespace winrt::TerminalApp::implementation } args.Handled(true); } + } diff --git a/src/cascadia/TerminalApp/CommandPalette.xaml b/src/cascadia/TerminalApp/CommandPalette.xaml index 7122a750197..c8100e3568c 100644 --- a/src/cascadia/TerminalApp/CommandPalette.xaml +++ b/src/cascadia/TerminalApp/CommandPalette.xaml @@ -205,6 +205,13 @@ Glyph="" Visibility="{x:Bind Item.(local:TabPaletteItem.TabStatus).IsReadOnlyActive, Mode=OneWay}" /> + + diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index 7b85a5f5a62..c70b3fa27da 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -108,6 +108,7 @@ void Pane::_setupControlEvents() _controlEvents._WarningBell = _control.WarningBell(winrt::auto_revoke, { this, &Pane::_ControlWarningBellHandler }); _controlEvents._CloseTerminalRequested = _control.CloseTerminalRequested(winrt::auto_revoke, { this, &Pane::_CloseTerminalRequestedHandler }); _controlEvents._RestartTerminalRequested = _control.RestartTerminalRequested(winrt::auto_revoke, { this, &Pane::_RestartTerminalRequestedHandler }); + _controlEvents._ReadOnlyChanged = _control.ReadOnlyChanged(winrt::auto_revoke, { this, &Pane::_ControlReadOnlyChangedHandler }); } void Pane::_removeControlEvents() { @@ -1434,8 +1435,9 @@ void Pane::UpdateVisuals() { _UpdateBorders(); } - _borderFirst.BorderBrush(_lastActive ? _themeResources.focusedBorderBrush : _themeResources.unfocusedBorderBrush); - _borderSecond.BorderBrush(_lastActive ? _themeResources.focusedBorderBrush : _themeResources.unfocusedBorderBrush); + const auto& brush{ _ComputeBorderColor() }; + _borderFirst.BorderBrush(brush); + _borderSecond.BorderBrush(brush); } // Method Description: @@ -3177,3 +3179,76 @@ void Pane::CollectTaskbarStates(std::vector& s _secondChild->CollectTaskbarStates(states); } } + +void Pane::EnableBroadcast(bool enabled) +{ + if (_IsLeaf()) + { + _broadcastEnabled = enabled; + UpdateVisuals(); + } + else + { + _firstChild->EnableBroadcast(enabled); + _secondChild->EnableBroadcast(enabled); + } +} + +void Pane::BroadcastKey(const winrt::Microsoft::Terminal::Control::TermControl& sourceControl, + const WORD vkey, + const WORD scanCode, + const winrt::Microsoft::Terminal::Core::ControlKeyStates modifiers, + const bool keyDown) +{ + WalkTree([&](const auto& pane) { + if (pane->_IsLeaf() && pane->_control != sourceControl && !pane->_control.ReadOnly()) + { + pane->_control.RawWriteKeyEvent(vkey, scanCode, modifiers, keyDown); + } + }); +} + +void Pane::BroadcastChar(const winrt::Microsoft::Terminal::Control::TermControl& sourceControl, + const wchar_t character, + const WORD scanCode, + const winrt::Microsoft::Terminal::Core::ControlKeyStates modifiers) +{ + WalkTree([&](const auto& pane) { + if (pane->_IsLeaf() && pane->_control != sourceControl && !pane->_control.ReadOnly()) + { + pane->_control.RawWriteChar(character, scanCode, modifiers); + } + }); +} + +void Pane::BroadcastString(const winrt::Microsoft::Terminal::Control::TermControl& sourceControl, + const winrt::hstring& text) +{ + WalkTree([&](const auto& pane) { + if (pane->_IsLeaf() && pane->_control != sourceControl && !pane->_control.ReadOnly()) + { + pane->_control.RawWriteString(text); + } + }); +} + +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 _themeResources.focusedBorderBrush; + } + + if (_broadcastEnabled && (_IsLeaf() && !_control.ReadOnly())) + { + return _themeResources.broadcastBorderBrush; + } + + return _themeResources.unfocusedBorderBrush; +} diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h index bc3e41a4a23..a6137534dac 100644 --- a/src/cascadia/TerminalApp/Pane.h +++ b/src/cascadia/TerminalApp/Pane.h @@ -55,6 +55,7 @@ struct PaneResources { winrt::Windows::UI::Xaml::Media::SolidColorBrush focusedBorderBrush{ nullptr }; winrt::Windows::UI::Xaml::Media::SolidColorBrush unfocusedBorderBrush{ nullptr }; + winrt::Windows::UI::Xaml::Media::SolidColorBrush broadcastBorderBrush{ nullptr }; }; class Pane : public std::enable_shared_from_this @@ -142,6 +143,11 @@ class Pane : public std::enable_shared_from_this bool ContainsReadOnly() const; + void EnableBroadcast(bool enabled); + void BroadcastKey(const winrt::Microsoft::Terminal::Control::TermControl& sourceControl, const WORD vkey, const WORD scanCode, const winrt::Microsoft::Terminal::Core::ControlKeyStates modifiers, const bool keyDown); + void BroadcastChar(const winrt::Microsoft::Terminal::Control::TermControl& sourceControl, const wchar_t vkey, const WORD scanCode, const winrt::Microsoft::Terminal::Core::ControlKeyStates modifiers); + void BroadcastString(const winrt::Microsoft::Terminal::Control::TermControl& sourceControl, const winrt::hstring& text); + void UpdateResources(const PaneResources& resources); // Method Description: @@ -251,6 +257,7 @@ class Pane : public std::enable_shared_from_this winrt::Microsoft::Terminal::Control::TermControl::WarningBell_revoker _WarningBell; winrt::Microsoft::Terminal::Control::TermControl::CloseTerminalRequested_revoker _CloseTerminalRequested; winrt::Microsoft::Terminal::Control::TermControl::RestartTerminalRequested_revoker _RestartTerminalRequested; + winrt::Microsoft::Terminal::Control::TermControl::ReadOnlyChanged_revoker _ReadOnlyChanged; } _controlEvents; void _setupControlEvents(); void _removeControlEvents(); @@ -263,6 +270,7 @@ class Pane : public std::enable_shared_from_this Borders _borders{ Borders::None }; bool _zoomed{ false }; + bool _broadcastEnabled{ false }; winrt::Windows::Media::Playback::MediaPlayer _bellPlayer{ nullptr }; bool _bellPlayerCreated{ false }; @@ -281,6 +289,7 @@ class Pane : public std::enable_shared_from_this void _SetupEntranceAnimation(); void _UpdateBorders(); Borders _GetCommonBorders(); + winrt::Windows::UI::Xaml::Media::SolidColorBrush _ComputeBorderColor(); bool _Resize(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction); @@ -307,6 +316,9 @@ class Pane : public std::enable_shared_from_this const winrt::Windows::UI::Xaml::RoutedEventArgs& e); void _ControlLostFocusHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::RoutedEventArgs& e); + + void _ControlReadOnlyChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& e); + void _CloseTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& /*args*/); void _RestartTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& /*args*/); diff --git a/src/cascadia/TerminalApp/TabHeaderControl.xaml b/src/cascadia/TerminalApp/TabHeaderControl.xaml index 80889eabb8c..5fde709ecbf 100644 --- a/src/cascadia/TerminalApp/TabHeaderControl.xaml +++ b/src/cascadia/TerminalApp/TabHeaderControl.xaml @@ -43,6 +43,12 @@ FontSize="12" Glyph="" Visibility="{x:Bind TabStatus.IsReadOnlyActive, Mode=OneWay}" /> + diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index c0b3f0d5943..b38a0d82daa 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -2787,6 +2787,24 @@ namespace winrt::TerminalApp::implementation // - Paste text from the Windows Clipboard to the focused terminal void TerminalPage::_PasteText() { + // First, check if we're in broadcast input mode. If so, let's tell all + // the controls to paste. + if (const auto& tab{ _GetFocusedTabImpl() }) + { + if (tab->TabStatus().IsInputBroadcastActive()) + { + tab->GetRootPane()->WalkTree([](auto&& pane) { + if (auto control = pane->GetTerminalControl()) + { + control.PasteTextFromClipboard(); + } + }); + return; + } + } + + // The focused tab wasn't in broadcast mode. No matter. Just ask the + // current one to paste. if (const auto& control{ _GetActiveControl() }) { control.PasteTextFromClipboard(); @@ -4570,6 +4588,21 @@ namespace winrt::TerminalApp::implementation // will eat focus. _paneResources.unfocusedBorderBrush = SolidColorBrush{ Colors::Black() }; } + + const auto broadcastColorKey = winrt::box_value(L"BroadcastPaneBorderColor"); + if (res.HasKey(broadcastColorKey)) + { + // MAKE SURE TO USE ThemeLookup + auto obj = ThemeLookup(res, requestedTheme, broadcastColorKey); + _paneResources.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. + _paneResources.broadcastBorderBrush = SolidColorBrush{ Colors::Black() }; + } } void TerminalPage::WindowActivated(const bool activated) diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp index fe8bee8f81c..ad90c6b1d7b 100644 --- a/src/cascadia/TerminalApp/TerminalTab.cpp +++ b/src/cascadia/TerminalApp/TerminalTab.cpp @@ -512,6 +512,8 @@ namespace winrt::TerminalApp::implementation } return false; }); + pane->EnableBroadcast(_tabStatus.IsInputBroadcastActive()); + // Make sure to take the ID before calling Split() - Split() will clear out the active pane's ID const auto activePaneId = _activePane->Id(); // Depending on which direction will be split, the new pane can be @@ -620,7 +622,7 @@ namespace winrt::TerminalApp::implementation _nextPaneId++; } }); - + pane->EnableBroadcast(_tabStatus.IsInputBroadcastActive()); // pass the old id to the new child const auto previousId = _activePane->Id(); @@ -890,6 +892,10 @@ namespace winrt::TerminalApp::implementation control.ReadOnlyChanged(events.readOnlyToken); control.FocusFollowMouseRequested(events.focusToken); + events.KeySent.revoke(); + events.CharSent.revoke(); + events.StringSent.revoke(); + _controlEvents.erase(paneId); } } @@ -964,7 +970,12 @@ namespace winrt::TerminalApp::implementation } }); - _controlEvents[paneId] = events; + if (_tabStatus.IsInputBroadcastActive()) + { + _addBroadcastHandlers(control, events); + } + + _controlEvents[paneId] = std::move(events); } // Method Description: @@ -1763,4 +1774,88 @@ namespace winrt::TerminalApp::implementation return Title(); } + + // Method Description: + // - Toggle broadcasting input to all the panes in this tab. + void TerminalTab::ToggleBroadcastInput() + { + const bool newIsBroadcasting = !_tabStatus.IsInputBroadcastActive(); + _tabStatus.IsInputBroadcastActive(newIsBroadcasting); + _rootPane->EnableBroadcast(newIsBroadcasting); + + auto weakThis{ get_weak() }; + + // When we change the state of broadcasting, add or remove event + // handlers appropriately, so that controls won't be propagating events + // needlessly if no one is listening. + + _rootPane->WalkTree([&](const auto& p) { + const auto paneId = p->Id(); + if (!paneId.has_value()) + { + return; + } + if (const auto& control{ p->GetTerminalControl() }) + { + auto it = _controlEvents.find(*paneId); + if (it != _controlEvents.end()) + { + auto& events = it->second; + + // Always clear out old ones, just in case. + events.KeySent.revoke(); + events.CharSent.revoke(); + events.StringSent.revoke(); + + if (newIsBroadcasting) + { + _addBroadcastHandlers(control, events); + } + } + } + }); + } + + void TerminalTab::_addBroadcastHandlers(const TermControl& termControl, ControlEventTokens& events) + { + auto weakThis{ get_weak() }; + // ADD EVENT HANDLERS HERE + events.KeySent = termControl.KeySent(winrt::auto_revoke, [weakThis](auto&& sender, auto&& e) { + if (const auto tab{ weakThis.get() }) + { + if (tab->_tabStatus.IsInputBroadcastActive()) + { + tab->_rootPane->BroadcastKey(sender.try_as(), + e.VKey(), + e.ScanCode(), + e.Modifiers(), + e.KeyDown()); + } + } + }); + + events.CharSent = termControl.CharSent(winrt::auto_revoke, [weakThis](auto&& sender, auto&& e) { + if (const auto tab{ weakThis.get() }) + { + if (tab->_tabStatus.IsInputBroadcastActive()) + { + tab->_rootPane->BroadcastChar(sender.try_as(), + e.Character(), + e.ScanCode(), + e.Modifiers()); + } + } + }); + + events.StringSent = termControl.StringSent(winrt::auto_revoke, [weakThis](auto&& sender, auto&& e) { + if (const auto tab{ weakThis.get() }) + { + if (tab->_tabStatus.IsInputBroadcastActive()) + { + tab->_rootPane->BroadcastString(sender.try_as(), + e.Text()); + } + } + }); + } } diff --git a/src/cascadia/TerminalApp/TerminalTab.h b/src/cascadia/TerminalApp/TerminalTab.h index e2a225e2c63..d7c1cede718 100644 --- a/src/cascadia/TerminalApp/TerminalTab.h +++ b/src/cascadia/TerminalApp/TerminalTab.h @@ -85,6 +85,7 @@ namespace winrt::TerminalApp::implementation void TogglePaneReadOnly(); void SetPaneReadOnly(const bool readOnlyState); + void ToggleBroadcastInput(); std::shared_ptr GetActivePane() const; winrt::TerminalApp::TaskbarState GetCombinedTaskbarState() const; @@ -133,6 +134,10 @@ namespace winrt::TerminalApp::implementation winrt::event_token taskbarToken; winrt::event_token readOnlyToken; winrt::event_token focusToken; + + winrt::Microsoft::Terminal::Control::TermControl::KeySent_revoker KeySent; + winrt::Microsoft::Terminal::Control::TermControl::CharSent_revoker CharSent; + winrt::Microsoft::Terminal::Control::TermControl::StringSent_revoker StringSent; }; std::unordered_map _controlEvents; @@ -179,6 +184,8 @@ namespace winrt::TerminalApp::implementation virtual winrt::Windows::UI::Xaml::Media::Brush _BackgroundBrush() override; + void _addBroadcastHandlers(const winrt::Microsoft::Terminal::Control::TermControl& control, ControlEventTokens& events); + friend class ::TerminalAppLocalTests::TabTests; }; } diff --git a/src/cascadia/TerminalApp/TerminalTabStatus.h b/src/cascadia/TerminalApp/TerminalTabStatus.h index d25341829ef..95080935a38 100644 --- a/src/cascadia/TerminalApp/TerminalTabStatus.h +++ b/src/cascadia/TerminalApp/TerminalTabStatus.h @@ -18,6 +18,7 @@ namespace winrt::TerminalApp::implementation WINRT_OBSERVABLE_PROPERTY(bool, BellIndicator, _PropertyChangedHandlers); WINRT_OBSERVABLE_PROPERTY(bool, IsReadOnlyActive, _PropertyChangedHandlers); WINRT_OBSERVABLE_PROPERTY(uint32_t, ProgressValue, _PropertyChangedHandlers); + WINRT_OBSERVABLE_PROPERTY(bool, IsInputBroadcastActive, _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/EventArgs.cpp b/src/cascadia/TerminalControl/EventArgs.cpp index c4089ebd76c..12739e38867 100644 --- a/src/cascadia/TerminalControl/EventArgs.cpp +++ b/src/cascadia/TerminalControl/EventArgs.cpp @@ -15,3 +15,6 @@ #include "FoundResultsArgs.g.cpp" #include "ShowWindowArgs.g.cpp" #include "UpdateSelectionMarkersEventArgs.g.cpp" +#include "KeySentEventArgs.g.cpp" +#include "CharSentEventArgs.g.cpp" +#include "StringSentEventArgs.g.cpp" diff --git a/src/cascadia/TerminalControl/EventArgs.h b/src/cascadia/TerminalControl/EventArgs.h index e1586e600a3..d3588eea672 100644 --- a/src/cascadia/TerminalControl/EventArgs.h +++ b/src/cascadia/TerminalControl/EventArgs.h @@ -15,6 +15,9 @@ #include "FoundResultsArgs.g.h" #include "ShowWindowArgs.g.h" #include "UpdateSelectionMarkersEventArgs.g.h" +#include "KeySentEventArgs.g.h" +#include "CharSentEventArgs.g.h" +#include "StringSentEventArgs.g.h" namespace winrt::Microsoft::Terminal::Control::implementation { @@ -179,6 +182,43 @@ namespace winrt::Microsoft::Terminal::Control::implementation WINRT_PROPERTY(bool, ClearMarkers, false); }; + + struct KeySentEventArgs : public KeySentEventArgsT + { + public: + KeySentEventArgs(const WORD vkey, const WORD scanCode, const winrt::Microsoft::Terminal::Core::ControlKeyStates modifiers, const bool keyDown) : + _VKey(vkey), + _ScanCode(scanCode), + _Modifiers(modifiers), + _KeyDown(keyDown) {} + + WINRT_PROPERTY(WORD, VKey); + WINRT_PROPERTY(WORD, ScanCode); + WINRT_PROPERTY(winrt::Microsoft::Terminal::Core::ControlKeyStates, Modifiers); + WINRT_PROPERTY(bool, KeyDown, false); + }; + + struct CharSentEventArgs : public CharSentEventArgsT + { + public: + CharSentEventArgs(const wchar_t character, const WORD scanCode, const winrt::Microsoft::Terminal::Core::ControlKeyStates modifiers) : + _Character(character), + _ScanCode(scanCode), + _Modifiers(modifiers) {} + + WINRT_PROPERTY(wchar_t, Character); + WINRT_PROPERTY(WORD, ScanCode); + WINRT_PROPERTY(winrt::Microsoft::Terminal::Core::ControlKeyStates, Modifiers); + }; + + struct StringSentEventArgs : public StringSentEventArgsT + { + public: + StringSentEventArgs(const winrt::hstring& text) : + _Text(text) {} + + WINRT_PROPERTY(winrt::hstring, Text); + }; } namespace winrt::Microsoft::Terminal::Control::factory_implementation diff --git a/src/cascadia/TerminalControl/EventArgs.idl b/src/cascadia/TerminalControl/EventArgs.idl index 7dbe9d47e47..1b07517b32d 100644 --- a/src/cascadia/TerminalControl/EventArgs.idl +++ b/src/cascadia/TerminalControl/EventArgs.idl @@ -89,4 +89,24 @@ namespace Microsoft.Terminal.Control { Boolean ClearMarkers { get; }; } + + runtimeclass KeySentEventArgs + { + UInt16 VKey { get; }; + UInt16 ScanCode { get; }; + Microsoft.Terminal.Core.ControlKeyStates Modifiers { get; }; + Boolean KeyDown { get; }; + } + + runtimeclass CharSentEventArgs + { + Char Character { get; }; + UInt16 ScanCode { get; }; + Microsoft.Terminal.Core.ControlKeyStates Modifiers { get; }; + } + + runtimeclass StringSentEventArgs + { + String Text { get; }; + } } diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 257c7df9251..aefe057a635 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -524,7 +524,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - void TermControl::SendInput(const winrt::hstring& wstr) { - _core.SendInput(wstr); + // only broadcast if there's an actual listener. Saves the overhead of some object creation. + if (_StringSentHandlers) + { + _StringSentHandlers(*this, winrt::make(wstr)); + } + + RawWriteString(wstr); } void TermControl::ClearBuffer(Control::ClearBufferType clearType) { @@ -1083,10 +1089,31 @@ namespace winrt::Microsoft::Terminal::Control::implementation modifiers |= ControlKeyStates::EnhancedKey; } - const auto handled = _core.SendCharEvent(ch, scanCode, modifiers); + // Broadcast the character to all listeners + // only broadcast if there's an actual listener. Saves the overhead of some object creation. + if (_CharSentHandlers) + { + auto charSentArgs = winrt::make(ch, scanCode, modifiers); + _CharSentHandlers(*this, charSentArgs); + } + + const auto handled = RawWriteChar(ch, scanCode, modifiers); + e.Handled(handled); } + bool TermControl::RawWriteChar(const wchar_t character, + const WORD scanCode, + const winrt::Microsoft::Terminal::Core::ControlKeyStates modifiers) + { + return _core.SendCharEvent(character, scanCode, modifiers); + } + + void TermControl::RawWriteString(const winrt::hstring& text) + { + _core.SendInput(text); + } + // 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. @@ -1333,6 +1360,22 @@ namespace winrt::Microsoft::Terminal::Control::implementation const WORD scanCode, const ControlKeyStates modifiers, const bool keyDown) + { + // Broadcast the key to all listeners + // only broadcast if there's an actual listener. Saves the overhead of some object creation. + if (_KeySentHandlers) + { + auto keySentArgs = winrt::make(vkey, scanCode, modifiers, keyDown); + _KeySentHandlers(*this, keySentArgs); + } + + return RawWriteKeyEvent(vkey, scanCode, modifiers, keyDown); + } + + bool TermControl::RawWriteKeyEvent(const WORD vkey, + const WORD scanCode, + const winrt::Microsoft::Terminal::Core::ControlKeyStates modifiers, + const bool keyDown) { const auto window = CoreWindow::GetForCurrentThread(); @@ -2587,7 +2630,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation return; } - _core.SendInput(text); + // SendInput will take care of broadcasting for us. + SendInput(text); } // Method Description: @@ -2666,7 +2710,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation try { auto link{ co_await e.DataView().GetApplicationLinkAsync() }; - _core.PasteText(link.AbsoluteUri()); + _pasteTextWithBroadcast(link.AbsoluteUri()); } CATCH_LOG(); } @@ -2675,7 +2719,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation try { auto link{ co_await e.DataView().GetWebLinkAsync() }; - _core.PasteText(link.AbsoluteUri()); + _pasteTextWithBroadcast(link.AbsoluteUri()); } CATCH_LOG(); } @@ -2684,7 +2728,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation try { auto text{ co_await e.DataView().GetTextAsync() }; - _core.PasteText(text); + _pasteTextWithBroadcast(text); } CATCH_LOG(); } @@ -2812,11 +2856,27 @@ namespace winrt::Microsoft::Terminal::Control::implementation allPathsString += fullPath; } - _core.PasteText(winrt::hstring{ allPathsString }); + _pasteTextWithBroadcast(winrt::hstring{ allPathsString }); } } } + // Method Description: + // - Paste this text, and raise a StringSent, to potentially broadcast this + // text to other controls in the app. For certain interactions, like + // drag/dropping a file, we want to act like we "pasted" the text (even if + // the text didn't come from the clipboard). This lets those interactions + // broadcast as well. + void TermControl::_pasteTextWithBroadcast(const winrt::hstring& text) + { + // only broadcast if there's an actual listener. Saves the overhead of some object creation. + if (_StringSentHandlers) + { + _StringSentHandlers(*this, winrt::make(text)); + } + _core.PasteText(text); + } + // Method Description: // - Handle the DragOver event. We'll signal that the drag operation we // support is the "copy" operation, and we'll also customize the diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 827c0d29a4e..01f86a409aa 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -146,6 +146,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation void AdjustOpacity(const double opacity, const bool relative); + bool RawWriteKeyEvent(const WORD vkey, const WORD scanCode, const winrt::Microsoft::Terminal::Core::ControlKeyStates modifiers, const bool keyDown); + bool RawWriteChar(const wchar_t character, const WORD scanCode, const winrt::Microsoft::Terminal::Core::ControlKeyStates modifiers); + void RawWriteString(const winrt::hstring& text); + void ShowContextMenu(); void Detach(); @@ -180,7 +184,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation TYPED_EVENT(FocusFollowMouseRequested, IInspectable, IInspectable); TYPED_EVENT(Initialized, Control::TermControl, Windows::UI::Xaml::RoutedEventArgs); TYPED_EVENT(WarningBell, IInspectable, IInspectable); - + TYPED_EVENT(KeySent, IInspectable, Control::KeySentEventArgs); + TYPED_EVENT(CharSent, IInspectable, Control::CharSentEventArgs); + TYPED_EVENT(StringSent, IInspectable, Control::StringSentEventArgs); // clang-format on WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, BackgroundBrush, _PropertyChangedHandlers, nullptr); @@ -355,6 +361,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation til::point _toPosInDips(const Core::Point terminalCellPos); void _throttledUpdateScrollbar(const ScrollBarUpdate& update); + void _pasteTextWithBroadcast(const winrt::hstring& text); + void _contextMenuHandler(IInspectable sender, Control::ContextMenuRequestedEventArgs args); void _showContextMenuAt(const til::point& controlRelativePos); diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl index b52cfea794b..ad881f233f9 100644 --- a/src/cascadia/TerminalControl/TermControl.idl +++ b/src/cascadia/TerminalControl/TermControl.idl @@ -53,6 +53,9 @@ namespace Microsoft.Terminal.Control event Windows.Foundation.TypedEventHandler TabColorChanged; event Windows.Foundation.TypedEventHandler ReadOnlyChanged; event Windows.Foundation.TypedEventHandler FocusFollowMouseRequested; + event Windows.Foundation.TypedEventHandler KeySent; + event Windows.Foundation.TypedEventHandler CharSent; + event Windows.Foundation.TypedEventHandler StringSent; Microsoft.UI.Xaml.Controls.CommandBarFlyout ContextMenu { get; }; Microsoft.UI.Xaml.Controls.CommandBarFlyout SelectionContextMenu { get; }; @@ -94,6 +97,9 @@ namespace Microsoft.Terminal.Control void ToggleShaderEffects(); void SendInput(String input); + Boolean RawWriteKeyEvent(UInt16 vkey, UInt16 scanCode, Microsoft.Terminal.Core.ControlKeyStates modifiers, Boolean keyDown); + Boolean RawWriteChar(Char character, UInt16 scanCode, Microsoft.Terminal.Core.ControlKeyStates modifiers); + void RawWriteString(String text); void BellLightOn(); diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp index 75ba4487b0c..757b440c82c 100644 --- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp @@ -93,6 +93,7 @@ static constexpr std::string_view ColorSelectionKey{ "experimental.colorSelectio static constexpr std::string_view ShowContextMenuKey{ "showContextMenu" }; static constexpr std::string_view ExpandSelectionToWordKey{ "expandSelectionToWord" }; static constexpr std::string_view RestartConnectionKey{ "restartConnection" }; +static constexpr std::string_view ToggleBroadcastInputKey{ "toggleBroadcastInput" }; static constexpr std::string_view ActionKey{ "action" }; @@ -424,6 +425,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { ShortcutAction::ShowContextMenu, RS_(L"ShowContextMenuCommandKey") }, { ShortcutAction::ExpandSelectionToWord, RS_(L"ExpandSelectionToWordCommandKey") }, { ShortcutAction::RestartConnection, RS_(L"RestartConnectionKey") }, + { ShortcutAction::ToggleBroadcastInput, RS_(L"ToggleBroadcastInputCommandKey") }, }; }(); diff --git a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h index 127b0bf2df9..571c20632a4 100644 --- a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h +++ b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h @@ -106,7 +106,8 @@ ON_ALL_ACTIONS(ShowContextMenu) \ ON_ALL_ACTIONS(ExpandSelectionToWord) \ ON_ALL_ACTIONS(CloseOtherPanes) \ - ON_ALL_ACTIONS(RestartConnection) + ON_ALL_ACTIONS(RestartConnection) \ + ON_ALL_ACTIONS(ToggleBroadcastInput) #define ALL_SHORTCUT_ACTIONS_WITH_ARGS \ ON_ALL_ACTIONS_WITH_ARGS(AdjustFontSize) \ diff --git a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw index 414783c6557..1cd8b5d51f5 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw @@ -684,6 +684,10 @@ Close all other panes + + Toggle broadcast input to all panes + When enabled, input will go to all panes in this tab simultaneously + Restart connection