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