Skip to content

Commit 6a10ea5

Browse files
authored
Add support for broadcasting to all panes in a tab (#14393)
Resurrection of #9222. Spec draft in #9365. Consensus from community feedback is that the whole of that spec is _nice to have_, but what REALLY matters is just broadcasting to all the panes in a tab. So, in the interest of best serving our community, I'm pushing this out as the initial implementation, before we figure out the rest of design. Regardless of how we choose to implement the rest of the features detailed in the spec, the UX for this part of the feature remains the same. This PR adds a new action: `toggleBroadcastInput`. Performing this action starts broadcasting to all panes in this tab. Keystrokes in one pane will be sent to all panes in the tab. An icon in the tab is used to indicate when this mode is active. Furthermore, the borders of all panes will be highlighted with `SystemAccentColorDark2`/`SystemAccentColorLight2` (depending on the theme), to indicate they're also active. * [x] Closes #2634. - (we should lick a reserved thread for follow-ups) Co-authored-by: Don-Vito [email protected]
1 parent b4042ea commit 6a10ea5

21 files changed

+416
-13
lines changed

.github/actions/spelling/expect/expect.txt

+1
Original file line numberDiff line numberDiff line change
@@ -2132,6 +2132,7 @@ WDDMCONSOLECONTEXT
21322132
wdm
21332133
webpage
21342134
websites
2135+
websockets
21352136
wekyb
21362137
wex
21372138
wextest

src/cascadia/TerminalApp/App.xaml

+9
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@
174174

175175
<SolidColorBrush x:Key="SettingsUiTabBrush"
176176
Color="#0c0c0c" />
177+
178+
<SolidColorBrush x:Key="BroadcastPaneBorderColor"
179+
Color="{StaticResource SystemAccentColorDark2}" />
177180
</ResourceDictionary>
178181

179182
<ResourceDictionary x:Key="Light">
@@ -190,6 +193,9 @@
190193

191194
<SolidColorBrush x:Key="SettingsUiTabBrush"
192195
Color="#ffffff" />
196+
197+
<SolidColorBrush x:Key="BroadcastPaneBorderColor"
198+
Color="{StaticResource SystemAccentColorLight2}" />
193199
</ResourceDictionary>
194200

195201
<ResourceDictionary x:Key="HighContrast">
@@ -210,6 +216,9 @@
210216

211217
<StaticResource x:Key="SettingsUiTabBrush"
212218
ResourceKey="SystemControlBackgroundBaseLowBrush" />
219+
220+
<SolidColorBrush x:Key="BroadcastPaneBorderColor"
221+
Color="{StaticResource SystemColorHighlightColor}" />
213222
</ResourceDictionary>
214223

215224
</ResourceDictionary.ThemeDictionaries>

src/cascadia/TerminalApp/AppActionHandlers.cpp

+12
Original file line numberDiff line numberDiff line change
@@ -1272,6 +1272,17 @@ namespace winrt::TerminalApp::implementation
12721272
}
12731273
}
12741274

1275+
void TerminalPage::_HandleToggleBroadcastInput(const IInspectable& /*sender*/,
1276+
const ActionEventArgs& args)
1277+
{
1278+
if (const auto activeTab{ _GetFocusedTabImpl() })
1279+
{
1280+
activeTab->ToggleBroadcastInput();
1281+
args.Handled(true);
1282+
}
1283+
// If the focused tab wasn't a TerminalTab, then leave handled=false
1284+
}
1285+
12751286
void TerminalPage::_HandleRestartConnection(const IInspectable& /*sender*/,
12761287
const ActionEventArgs& args)
12771288
{
@@ -1294,4 +1305,5 @@ namespace winrt::TerminalApp::implementation
12941305
}
12951306
args.Handled(true);
12961307
}
1308+
12971309
}

src/cascadia/TerminalApp/CommandPalette.xaml

+7
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,13 @@
205205
Glyph="&#xE72E;"
206206
Visibility="{x:Bind Item.(local:TabPaletteItem.TabStatus).IsReadOnlyActive, Mode=OneWay}" />
207207

208+
<FontIcon x:Name="HeaderBroadcastIcon"
209+
Margin="0,0,8,0"
210+
FontFamily="Segoe MDL2 Assets"
211+
FontSize="12"
212+
Glyph="&#xEC05;"
213+
Visibility="{x:Bind Item.(local:TabPaletteItem.TabStatus).IsInputBroadcastActive, Mode=OneWay}" />
214+
208215
</StackPanel>
209216
</Grid>
210217
</DataTemplate>

src/cascadia/TerminalApp/Pane.cpp

+77-2
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ void Pane::_setupControlEvents()
108108
_controlEvents._WarningBell = _control.WarningBell(winrt::auto_revoke, { this, &Pane::_ControlWarningBellHandler });
109109
_controlEvents._CloseTerminalRequested = _control.CloseTerminalRequested(winrt::auto_revoke, { this, &Pane::_CloseTerminalRequestedHandler });
110110
_controlEvents._RestartTerminalRequested = _control.RestartTerminalRequested(winrt::auto_revoke, { this, &Pane::_RestartTerminalRequestedHandler });
111+
_controlEvents._ReadOnlyChanged = _control.ReadOnlyChanged(winrt::auto_revoke, { this, &Pane::_ControlReadOnlyChangedHandler });
111112
}
112113
void Pane::_removeControlEvents()
113114
{
@@ -1434,8 +1435,9 @@ void Pane::UpdateVisuals()
14341435
{
14351436
_UpdateBorders();
14361437
}
1437-
_borderFirst.BorderBrush(_lastActive ? _themeResources.focusedBorderBrush : _themeResources.unfocusedBorderBrush);
1438-
_borderSecond.BorderBrush(_lastActive ? _themeResources.focusedBorderBrush : _themeResources.unfocusedBorderBrush);
1438+
const auto& brush{ _ComputeBorderColor() };
1439+
_borderFirst.BorderBrush(brush);
1440+
_borderSecond.BorderBrush(brush);
14391441
}
14401442

14411443
// Method Description:
@@ -3177,3 +3179,76 @@ void Pane::CollectTaskbarStates(std::vector<winrt::TerminalApp::TaskbarState>& s
31773179
_secondChild->CollectTaskbarStates(states);
31783180
}
31793181
}
3182+
3183+
void Pane::EnableBroadcast(bool enabled)
3184+
{
3185+
if (_IsLeaf())
3186+
{
3187+
_broadcastEnabled = enabled;
3188+
UpdateVisuals();
3189+
}
3190+
else
3191+
{
3192+
_firstChild->EnableBroadcast(enabled);
3193+
_secondChild->EnableBroadcast(enabled);
3194+
}
3195+
}
3196+
3197+
void Pane::BroadcastKey(const winrt::Microsoft::Terminal::Control::TermControl& sourceControl,
3198+
const WORD vkey,
3199+
const WORD scanCode,
3200+
const winrt::Microsoft::Terminal::Core::ControlKeyStates modifiers,
3201+
const bool keyDown)
3202+
{
3203+
WalkTree([&](const auto& pane) {
3204+
if (pane->_IsLeaf() && pane->_control != sourceControl && !pane->_control.ReadOnly())
3205+
{
3206+
pane->_control.RawWriteKeyEvent(vkey, scanCode, modifiers, keyDown);
3207+
}
3208+
});
3209+
}
3210+
3211+
void Pane::BroadcastChar(const winrt::Microsoft::Terminal::Control::TermControl& sourceControl,
3212+
const wchar_t character,
3213+
const WORD scanCode,
3214+
const winrt::Microsoft::Terminal::Core::ControlKeyStates modifiers)
3215+
{
3216+
WalkTree([&](const auto& pane) {
3217+
if (pane->_IsLeaf() && pane->_control != sourceControl && !pane->_control.ReadOnly())
3218+
{
3219+
pane->_control.RawWriteChar(character, scanCode, modifiers);
3220+
}
3221+
});
3222+
}
3223+
3224+
void Pane::BroadcastString(const winrt::Microsoft::Terminal::Control::TermControl& sourceControl,
3225+
const winrt::hstring& text)
3226+
{
3227+
WalkTree([&](const auto& pane) {
3228+
if (pane->_IsLeaf() && pane->_control != sourceControl && !pane->_control.ReadOnly())
3229+
{
3230+
pane->_control.RawWriteString(text);
3231+
}
3232+
});
3233+
}
3234+
3235+
void Pane::_ControlReadOnlyChangedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
3236+
const winrt::Windows::Foundation::IInspectable& /*e*/)
3237+
{
3238+
UpdateVisuals();
3239+
}
3240+
3241+
winrt::Windows::UI::Xaml::Media::SolidColorBrush Pane::_ComputeBorderColor()
3242+
{
3243+
if (_lastActive)
3244+
{
3245+
return _themeResources.focusedBorderBrush;
3246+
}
3247+
3248+
if (_broadcastEnabled && (_IsLeaf() && !_control.ReadOnly()))
3249+
{
3250+
return _themeResources.broadcastBorderBrush;
3251+
}
3252+
3253+
return _themeResources.unfocusedBorderBrush;
3254+
}

src/cascadia/TerminalApp/Pane.h

+12
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ struct PaneResources
5555
{
5656
winrt::Windows::UI::Xaml::Media::SolidColorBrush focusedBorderBrush{ nullptr };
5757
winrt::Windows::UI::Xaml::Media::SolidColorBrush unfocusedBorderBrush{ nullptr };
58+
winrt::Windows::UI::Xaml::Media::SolidColorBrush broadcastBorderBrush{ nullptr };
5859
};
5960

6061
class Pane : public std::enable_shared_from_this<Pane>
@@ -142,6 +143,11 @@ class Pane : public std::enable_shared_from_this<Pane>
142143

143144
bool ContainsReadOnly() const;
144145

146+
void EnableBroadcast(bool enabled);
147+
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);
148+
void BroadcastChar(const winrt::Microsoft::Terminal::Control::TermControl& sourceControl, const wchar_t vkey, const WORD scanCode, const winrt::Microsoft::Terminal::Core::ControlKeyStates modifiers);
149+
void BroadcastString(const winrt::Microsoft::Terminal::Control::TermControl& sourceControl, const winrt::hstring& text);
150+
145151
void UpdateResources(const PaneResources& resources);
146152

147153
// Method Description:
@@ -251,6 +257,7 @@ class Pane : public std::enable_shared_from_this<Pane>
251257
winrt::Microsoft::Terminal::Control::TermControl::WarningBell_revoker _WarningBell;
252258
winrt::Microsoft::Terminal::Control::TermControl::CloseTerminalRequested_revoker _CloseTerminalRequested;
253259
winrt::Microsoft::Terminal::Control::TermControl::RestartTerminalRequested_revoker _RestartTerminalRequested;
260+
winrt::Microsoft::Terminal::Control::TermControl::ReadOnlyChanged_revoker _ReadOnlyChanged;
254261
} _controlEvents;
255262
void _setupControlEvents();
256263
void _removeControlEvents();
@@ -263,6 +270,7 @@ class Pane : public std::enable_shared_from_this<Pane>
263270
Borders _borders{ Borders::None };
264271

265272
bool _zoomed{ false };
273+
bool _broadcastEnabled{ false };
266274

267275
winrt::Windows::Media::Playback::MediaPlayer _bellPlayer{ nullptr };
268276
bool _bellPlayerCreated{ false };
@@ -281,6 +289,7 @@ class Pane : public std::enable_shared_from_this<Pane>
281289
void _SetupEntranceAnimation();
282290
void _UpdateBorders();
283291
Borders _GetCommonBorders();
292+
winrt::Windows::UI::Xaml::Media::SolidColorBrush _ComputeBorderColor();
284293

285294
bool _Resize(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction);
286295

@@ -307,6 +316,9 @@ class Pane : public std::enable_shared_from_this<Pane>
307316
const winrt::Windows::UI::Xaml::RoutedEventArgs& e);
308317
void _ControlLostFocusHandler(const winrt::Windows::Foundation::IInspectable& sender,
309318
const winrt::Windows::UI::Xaml::RoutedEventArgs& e);
319+
320+
void _ControlReadOnlyChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& e);
321+
310322
void _CloseTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& /*args*/);
311323
void _RestartTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& /*args*/);
312324

src/cascadia/TerminalApp/TabHeaderControl.xaml

+6
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@
4343
FontSize="12"
4444
Glyph="&#xE72E;"
4545
Visibility="{x:Bind TabStatus.IsReadOnlyActive, Mode=OneWay}" />
46+
<FontIcon x:Name="HeaderBroadcastIcon"
47+
Margin="0,0,8,0"
48+
FontFamily="{ThemeResource SymbolThemeFontFamily}"
49+
FontSize="12"
50+
Glyph="&#xEC05;"
51+
Visibility="{x:Bind TabStatus.IsInputBroadcastActive, Mode=OneWay}" />
4652
<TextBlock x:Name="HeaderTextBlock"
4753
Text="{x:Bind Title, Mode=OneWay}"
4854
Visibility="Visible" />

src/cascadia/TerminalApp/TerminalPage.cpp

+33
Original file line numberDiff line numberDiff line change
@@ -2787,6 +2787,24 @@ namespace winrt::TerminalApp::implementation
27872787
// - Paste text from the Windows Clipboard to the focused terminal
27882788
void TerminalPage::_PasteText()
27892789
{
2790+
// First, check if we're in broadcast input mode. If so, let's tell all
2791+
// the controls to paste.
2792+
if (const auto& tab{ _GetFocusedTabImpl() })
2793+
{
2794+
if (tab->TabStatus().IsInputBroadcastActive())
2795+
{
2796+
tab->GetRootPane()->WalkTree([](auto&& pane) {
2797+
if (auto control = pane->GetTerminalControl())
2798+
{
2799+
control.PasteTextFromClipboard();
2800+
}
2801+
});
2802+
return;
2803+
}
2804+
}
2805+
2806+
// The focused tab wasn't in broadcast mode. No matter. Just ask the
2807+
// current one to paste.
27902808
if (const auto& control{ _GetActiveControl() })
27912809
{
27922810
control.PasteTextFromClipboard();
@@ -4570,6 +4588,21 @@ namespace winrt::TerminalApp::implementation
45704588
// will eat focus.
45714589
_paneResources.unfocusedBorderBrush = SolidColorBrush{ Colors::Black() };
45724590
}
4591+
4592+
const auto broadcastColorKey = winrt::box_value(L"BroadcastPaneBorderColor");
4593+
if (res.HasKey(broadcastColorKey))
4594+
{
4595+
// MAKE SURE TO USE ThemeLookup
4596+
auto obj = ThemeLookup(res, requestedTheme, broadcastColorKey);
4597+
_paneResources.broadcastBorderBrush = obj.try_as<winrt::Windows::UI::Xaml::Media::SolidColorBrush>();
4598+
}
4599+
else
4600+
{
4601+
// DON'T use Transparent here - if it's "Transparent", then it won't
4602+
// be able to hittest for clicks, and then clicking on the border
4603+
// will eat focus.
4604+
_paneResources.broadcastBorderBrush = SolidColorBrush{ Colors::Black() };
4605+
}
45734606
}
45744607

45754608
void TerminalPage::WindowActivated(const bool activated)

0 commit comments

Comments
 (0)