Compare commits

...

8 Commits

Author SHA1 Message Date
Mike Griese
f448beaa73 Add an "Tab Overview" pane to the Terminal
This is a new action we're adding to allow the user to quickly view all
of their open tabs at a glance.

When the user performs the `toggleOverview` action, we'll create a tab
overview control, which handles all the animations. We'll set that
OverviewPane as the content of the TerminalPage, and then we'll use
XAML translations and transforms to make all the other TermControls
children of that overviewpane.

This lets us render a bunch of live previews of the tabs all at once!
neat!

full disclosure: lots of this XAML animation code inside of OverviewPane
was written by AI. Sorry, but animations are kinda just clanker work,
and I'd never get it to look like that myself.

The Overview pane will update it's layout to try and maximize how big
the previews are. It'll try to lay out the previews so that we fit as
many as we can, up to 4 in a row, but also it'll try to intelligently
lay them out in a 2x2 grid if there's only 4 (for example).

Closes: #15385
2026-05-20 16:17:11 -05:00
Dustin L. Howett
8fe6c21ef8 Keep the font size delta across settings reloads (#20230)
This is a trivial fix for an issue we get a somewhat outsized number of
complaints about.

When the user has adjusted the font size in any direction, we'll just
keep track of the magnitude and apply it every time the settings change.

Yes, this means that if you zoom once and then change your real font
size it's going to zoom even more.

That probably doesn't matter.

Refs #11522
2026-05-15 13:56:48 -05:00
aarushi singh
abeac1b135 Use PlaySoundW for profile bell sounds (#20031)
We believed that this would fix an issue on Windows 10, where the volume
mixer would forget Windows Terminal after every relaunch. It turns out
that it does not.

Still, this code is much more concise and doesn't require yet another
WinRT object. Story of our lives.

Refs #17733
2026-05-15 20:51:01 +02:00
aarushi singh
12e3455bb2 Make DECSTR cursor restore behave like xterm in alt screen buffer (#20032)
There is some disagreement among terminal emulators as to whether DECSTR
(and therefore, soft reset) restores the cursor in the active buffer or
in all buffers.

We had previously chosen to restore the cursor in all buffers.

xterm restores the cursor only in the active buffer.

Closes #19918
2026-05-14 17:04:58 +00:00
Lucas Trzesniewski
fb71a0462e Add safeUriSchemes setting (#20207)
This adds a `safeUriSchemes` global setting which lets you define
hyperlink URI schemes which the user considers safe. No confirmation
dialog will be shown when trying to open hyperlinks which use these
schemes.

- This solves the root issue, but doesn't introduce any UI or
  documentation changes. I wanted to validate the approach and
  implementation with you first.
- I closely followed the code handling the `disabledProfileSources`
  setting, which is of the same type.
- This feature does not change the behavior of `http`, `https` and
  `file` schemes.

Validation

I ran the dev terminal, and tested the behavior by clicking on `vscode`
hyperlinks generated by ripgrep with various `safeUriSchemes` settings:

- Setting not defined - asks for confirmation
- `["vscode"]` - does not ask for confirmation
- `["foo", "vscode"]` - does not ask for confirmation
- `["foo"]` - asks for confirmation
- `null` - asks for confirmation
- `[]` - asks for confirmation
- `[""]` - asks for confirmation
- `[{"foo": "bar"}]` - fails to deserialize (as expected)

A few uinit tests failed, but they seem unrelated to these changes:
- `KeyBindingTests` in `UnitTests_SettingsModel`, probably because I use
  an AZERTY keyboard.
- A few `Conhost` tests, but I didn't touch this part

Refs #20065
Closes #20191
2026-05-12 21:32:56 +00:00
Dustin L. Howett
c829d4ca54 sixel: prevent allocating an absurd amount of memory or writing OOB (#20213)
This commit implements two fixes for the integer overflow/out-of-bounds
write reported in #20149.

First, it catches any exception generated in sixel char processing
(which will prevent `out_of_memory` or `bad_alloc` from being ignored
sight-unseen, and will prevent the consumption of further DCS content).

Second, it prevents us from allocating memory for an image which will
never be displayed (because it exceeds the height of the display.)

This supersedes prior work in #20153 for the same issues.

Closes #20149
Closes #20153

Co-authored-by: James Holderness <j4_james@hotmail.com>
2026-05-12 18:15:19 +02:00
Dustin L. Howett
b991eb048e Only set startingTitle once, clear up title fallback handling (#20214)
This commit ensures that we only set the starting title once when we
open a new terminal pane.

It also consolidates all title selection into Terminal::GetConsoleTitle,
which is now used in the TitleChanged event.

TitleChanged no longer stores a separate copy of the starting title if
an application attempts to _clear_ the title; that seems like a poorer
implementation of what we already had.

This supersedes work in #20204.

Closes #19340
Closes #20204

Co-authored-by: imsh <im.shaedar@gmail.com>
2026-05-12 00:08:57 +00:00
Dustin L. Howett
3e3b3ad883 Reject DTTERM Window Manipulation resizes with the current size (#20183)
This may help #20182 and #20112
Closes #19310
2026-05-11 17:42:18 -05:00
34 changed files with 1742 additions and 78 deletions

View File

@@ -1075,6 +1075,7 @@ NOCONTEXTHELP
NOCOPYBITS
nodiscard
NODUP
NODEFAULT
noexcepts
NOFONT
NOHIDDENTEXT
@@ -1561,6 +1562,7 @@ SMARTQUOTE
SMTO
snapcx
snapcy
SND
snk
SOLIDBOX
Solutiondir

View File

@@ -475,6 +475,7 @@
"toggleBlockSelection",
"toggleFocusMode",
"toggleFullscreen",
"toggleOverview",
"togglePaneZoom",
"toggleReadOnlyMode",
"toggleShaderEffects",
@@ -2472,6 +2473,13 @@
},
"type": "array"
},
"safeUriSchemes": {
"description": "Specifies a list of URI schemes that are considered safe. No confirmation will be required to open URIs with these schemes.",
"items": {
"type": "string"
},
"type": "array"
},
"rendering.graphicsAPI": {
"description": "Direct3D 11 provides a more performant and feature-rich experience, whereas Direct2D is more stable. The default option \"Automatic\" will pick the API that best fits your graphics hardware. If you experience significant issues, consider using Direct2D.",
"type": "string",

View File

@@ -604,6 +604,13 @@ namespace winrt::TerminalApp::implementation
args.Handled(true);
}
void TerminalPage::_HandleToggleOverview(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
ToggleOverview();
args.Handled(true);
}
void TerminalPage::_HandleSetFocusMode(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,119 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include <limits>
#include <optional>
#include "OverviewPane.g.h"
namespace winrt::TerminalApp::implementation
{
struct OverviewPane : OverviewPaneT<OverviewPane>
{
OverviewPane();
~OverviewPane();
void UpdateTabContent(Windows::Foundation::Collections::IVector<TerminalApp::Tab> tabs, int32_t focusedIndex);
void ClearTabContent();
int32_t SelectedIndex() const;
void SelectedIndex(int32_t value);
bool UseMica() const;
void UseMica(bool value);
hstring ControlName() const;
// Events
til::typed_event<Windows::Foundation::IInspectable, Windows::Foundation::IReference<int32_t>> TabSelected;
til::typed_event<> Dismissed;
private:
friend struct OverviewPaneT<OverviewPane>; // for Xaml to bind events
void _OnKeyDown(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::KeyRoutedEventArgs& e);
void _OnPreviewKeyDown(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::KeyRoutedEventArgs& e);
void _OnItemClicked(int32_t index);
void _UpdateSelection();
void _PlayEnterAnimation();
void _PlayExitAnimation(std::function<void()> onComplete = nullptr);
void _StartEnterZoomAnimation();
std::optional<std::tuple<double, double, double>> _GetZoomParamsForCell(int32_t index);
Windows::UI::Xaml::FrameworkElement _BuildPreviewCell(const TerminalApp::Tab& tab, int32_t index, int32_t totalCount, double previewWidth, double previewHeight, double referenceWidth, double referenceHeight);
void _DetachContent(const Windows::UI::Xaml::FrameworkElement& content);
struct AdaptiveLayout
{
double cellWidth{ 0.0 };
double cellHeight{ 0.0 };
double previewWidth{ 0.0 };
double previewHeight{ 0.0 };
int32_t columnCount{ 1 };
};
AdaptiveLayout _ComputeAdaptiveLayout(int32_t tabCount, double viewportWidth, double viewportHeight, double aspect) const;
void _ApplyAdaptiveLayout();
static void _AddDoubleAnimation(
const Windows::UI::Xaml::Media::Animation::Storyboard& storyboard,
const Windows::UI::Xaml::Media::CompositeTransform& target,
const hstring& property,
double from,
double to,
const Windows::UI::Xaml::Duration& duration,
const Windows::UI::Xaml::Media::Animation::EasingFunctionBase& easing);
int32_t _selectedIndex{ 0 };
int32_t _columnCount{ 1 }; // updated at UpdateTabContent time to match the adaptive layout
bool _pendingEnterAnimation{ false };
bool _pendingAdaptiveLayout{ false };
// Homogeneous-aspect assumption: every cell uses the active tab's
// content aspect ratio. Per-tab aspect (e.g. one tab with a Settings
// page that's narrow + tall, another with a TermControl that's wide)
// would require a different layout pass — cells could no longer
// share a single ItemWidth/ItemHeight on the WrapGrid.
double _referenceAspect{ 16.0 / 10.0 };
double _lastAppliedViewportWidth{ 0.0 };
double _lastAppliedViewportHeight{ 0.0 };
bool _useMica{ false };
void _UpdateBackgroundForMica();
winrt::event_token _exitAnimationToken{};
Windows::UI::Xaml::Media::Animation::Storyboard _exitContentStoryboard{ nullptr };
// Subscriptions on our own owned children. Stored so the destructor
// can explicitly revoke them before ClearTabContent runs — matches
// the token-revoke pattern used in TerminalPage for overview events.
winrt::event_token _layoutUpdatedToken{};
winrt::event_token _sizeChangedToken{};
// Per-cell state for the adaptive layout pass. INVARIANT: there is
// exactly one ReparentedEntry per cell in PreviewGrid().Items(), in
// cell order. When the source tab had no live content,
// `content` / `previewCanvas` are null and `layoutWidth` /
// `layoutHeight` are zero — the cell still gets a previewBorder
// + titleBlock sized by _ApplyAdaptiveLayout, but the
// ScaleTransform / reparent path is skipped. _ApplyAdaptiveLayout
// and ClearTabContent both null-check before touching content.
struct ReparentedEntry
{
Windows::UI::Xaml::FrameworkElement content{ nullptr };
Windows::UI::Xaml::Controls::Panel originalParent{ nullptr };
double originalWidth{ std::numeric_limits<double>::quiet_NaN() };
double originalHeight{ std::numeric_limits<double>::quiet_NaN() };
Windows::UI::Xaml::Media::Transform originalRenderTransform{ nullptr };
Windows::Foundation::Point originalRenderTransformOrigin{ 0.0f, 0.0f };
// Per-cell handles for adaptive resizing after the first layout pass.
Windows::UI::Xaml::Controls::Border previewBorder{ nullptr };
Windows::UI::Xaml::Controls::TextBlock titleBlock{ nullptr };
Windows::UI::Xaml::Controls::Canvas previewCanvas{ nullptr };
double layoutWidth{ 0.0 };
double layoutHeight{ 0.0 };
};
std::vector<ReparentedEntry> _reparentedContent;
};
}
namespace winrt::TerminalApp::factory_implementation
{
BASIC_FACTORY(OverviewPane);
}

View File

@@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import "Tab.idl";
namespace TerminalApp
{
[default_interface] runtimeclass OverviewPane : Windows.UI.Xaml.Controls.UserControl
{
OverviewPane();
void UpdateTabContent(Windows.Foundation.Collections.IVector<Tab> tabs, Int32 focusedIndex);
void ClearTabContent();
Int32 SelectedIndex;
Boolean UseMica;
String ControlName { get; };
event Windows.Foundation.TypedEventHandler<Object, Windows.Foundation.IReference<Int32> > TabSelected;
event Windows.Foundation.TypedEventHandler<Object, Object> Dismissed;
}
}

View File

@@ -0,0 +1,90 @@
<!--
Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
the MIT License. See LICENSE in the project root for license information.
-->
<UserControl x:Class="TerminalApp.OverviewPane"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:TerminalApp"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
AllowFocusOnInteraction="True"
AutomationProperties.Name="{x:Bind ControlName, Mode=OneWay}"
IsTabStop="True"
KeyDown="_OnKeyDown"
PreviewKeyDown="_OnPreviewKeyDown"
mc:Ignorable="d">
<UserControl.Resources>
<ResourceDictionary>
<Storyboard x:Key="BackgroundFadeIn">
<DoubleAnimation Storyboard.TargetName="BackgroundOverlay"
Storyboard.TargetProperty="Opacity"
From="0.0"
To="1.0"
Duration="0:0:0.2">
<DoubleAnimation.EasingFunction>
<QuadraticEase EasingMode="EaseOut" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
<Storyboard x:Key="BackgroundFadeOut">
<DoubleAnimation Storyboard.TargetName="BackgroundOverlay"
Storyboard.TargetProperty="Opacity"
From="1.0"
To="0.0"
Duration="0:0:0.15">
<DoubleAnimation.EasingFunction>
<QuadraticEase EasingMode="EaseIn" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</ResourceDictionary>
</UserControl.Resources>
<Grid x:Name="RootGrid"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<!-- Semi-transparent dark overlay -->
<Border x:Name="BackgroundOverlay"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="{ThemeResource SmokeFillColorDefaultBrush}" />
<!-- Zoom-animated wrapper around the scrollable content -->
<Grid x:Name="ContentWrapper"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
RenderTransformOrigin="0.5,0.5">
<Grid.RenderTransform>
<CompositeTransform x:Name="ContentTransform" />
</Grid.RenderTransform>
<ScrollViewer x:Name="PreviewScrollViewer"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<!-- WrapGrid-style layout using ItemsControl with WrapGrid panel -->
<ItemsControl x:Name="PreviewGrid"
Margin="12"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<!-- ItemWidth, ItemHeight, and MaximumRowsOrColumns are owned by -->
<!-- OverviewPane.cpp (_ComputeAdaptiveLayout / _ApplyAdaptiveLayout) — -->
<!-- they are reassigned on every UpdateTabContent and on every resize. -->
<WrapGrid HorizontalChildrenAlignment="Center"
Orientation="Horizontal"
VerticalChildrenAlignment="Center" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
</Grid>
</Grid>
</UserControl>

View File

@@ -606,6 +606,10 @@
<data name="CommandPaletteControlName" xml:space="preserve">
<value>Command palette</value>
</data>
<data name="OverviewPaneControlName" xml:space="preserve">
<value>Tab Overview</value>
<comment>The name announced by assistive technologies (e.g. Narrator) when the tab overview pane receives focus.</comment>
</data>
<data name="TabSwitcherControlName" xml:space="preserve">
<value>Tab switcher</value>
</data>

View File

@@ -1065,6 +1065,7 @@ namespace winrt::TerminalApp::implementation
void TerminalPage::_UpdatedSelectedTab(const winrt::TerminalApp::Tab& tab)
{
OutputDebugStringW(fmt::format(FMT_COMPILE(L"[OV] _UpdatedSelectedTab enter, tab={}\n"), static_cast<bool>(tab)).c_str());
// Unfocus all the tabs.
for (const auto& tab : _tabs)
{
@@ -1074,7 +1075,12 @@ namespace winrt::TerminalApp::implementation
try
{
_tabContent.Children().Clear();
auto content = tab ? tab.Content() : nullptr;
const bool hasContent = static_cast<bool>(content);
const bool hasParent = hasContent && static_cast<bool>(WUX::Media::VisualTreeHelper::GetParent(content));
OutputDebugStringW(fmt::format(FMT_COMPILE(L"[OV] _UpdatedSelectedTab: about to Append, hasContent={} hasParent={}\n"), hasContent, hasParent).c_str());
_tabContent.Children().Append(tab.Content());
OutputDebugStringW(L"[OV] _UpdatedSelectedTab: Append succeeded\n");
// GH#7409: If the tab switcher is open, then we _don't_ want to
// automatically focus the new tab here. The tab switcher wants
@@ -1089,6 +1095,7 @@ namespace winrt::TerminalApp::implementation
const auto p = CommandPaletteElement();
if (!p || p.Visibility() != Visibility::Visible)
{
OutputDebugStringW(L"[OV] _UpdatedSelectedTab: calling tab.Focus(Programmatic)\n");
tab.Focus(FocusState::Programmatic);
_UpdateMRUTab(tab);
_updateAllTabCloseButtons();
@@ -1109,8 +1116,10 @@ namespace winrt::TerminalApp::implementation
}
_adjustProcessPriorityThrottled->Run();
OutputDebugStringW(L"[OV] _UpdatedSelectedTab: try-block finished cleanly\n");
}
CATCH_LOG();
OutputDebugStringW(L"[OV] _UpdatedSelectedTab: exiting\n");
}
void TerminalPage::_UpdateBackground(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile)
@@ -1129,10 +1138,19 @@ namespace winrt::TerminalApp::implementation
// - eventArgs: the event's constituent arguments
void TerminalPage::_OnTabSelectionChanged(const IInspectable& sender, const WUX::Controls::SelectionChangedEventArgs& /*eventArgs*/)
{
OutputDebugStringW(fmt::format(FMT_COMPILE(L"[OV] _OnTabSelectionChanged: _rearranging={} _removing={} _isInOverviewMode={}\n"), _rearranging, _removing, _isInOverviewMode).c_str());
if (!_rearranging && !_removing)
{
// If the user clicked a tab in the tab row while the overview is
// open, the overview is still holding that tab's Content reparented
// into one of its preview cells. Tear down the overview visuals
// first so _UpdatedSelectedTab can mount the content into the
// active content area without hitting the XAML single-parent rule.
_DismissOverviewVisuals();
auto tabView = sender.as<MUX::Controls::TabView>();
auto selectedIndex = tabView.SelectedIndex();
OutputDebugStringW(fmt::format(FMT_COMPILE(L"[OV] _OnTabSelectionChanged: tabView.SelectedIndex={}\n"), selectedIndex).c_str());
if (selectedIndex >= 0 && selectedIndex < gsl::narrow_cast<int32_t>(_tabs.Size()))
{
const auto tab{ _tabs.GetAt(selectedIndex) };

View File

@@ -71,6 +71,9 @@
<Page Include="CommandPalette.xaml">
<SubType>Designer</SubType>
</Page>
<Page Include="OverviewPane.xaml">
<SubType>Designer</SubType>
</Page>
<Page Include="SuggestionsControl.xaml">
<SubType>Designer</SubType>
</Page>
@@ -132,6 +135,9 @@
<ClInclude Include="CommandPalette.h">
<DependentUpon>CommandPalette.xaml</DependentUpon>
</ClInclude>
<ClInclude Include="OverviewPane.h">
<DependentUpon>OverviewPane.xaml</DependentUpon>
</ClInclude>
<ClInclude Include="FilteredCommand.h" />
<ClInclude Include="Pane.h" />
<ClInclude Include="../fzf/fzf.h" />
@@ -243,6 +249,9 @@
<ClCompile Include="CommandPalette.cpp">
<DependentUpon>CommandPalette.xaml</DependentUpon>
</ClCompile>
<ClCompile Include="OverviewPane.cpp">
<DependentUpon>OverviewPane.xaml</DependentUpon>
</ClCompile>
<ClCompile Include="FilteredCommand.cpp" />
<ClCompile Include="Pane.cpp" />
<ClCompile Include="Pane.LayoutSizeNode.cpp" />
@@ -354,6 +363,10 @@
<DependentUpon>CommandPalette.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
<Midl Include="OverviewPane.idl">
<DependentUpon>OverviewPane.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
<Midl Include="SuggestionsControl.idl">
<DependentUpon>SuggestionsControl.xaml</DependentUpon>
<SubType>Code</SubType>

View File

@@ -1790,6 +1790,7 @@ namespace winrt::TerminalApp::implementation
const auto vkey = gsl::narrow_cast<WORD>(e.OriginalKey());
const auto scanCode = gsl::narrow_cast<WORD>(keyStatus.ScanCode);
const auto modifiers = _GetPressedModifierKeys();
OutputDebugStringW(fmt::format(FMT_COMPILE(L"[OV] TP::_KeyDownHandler vkey={} handled={} overviewMode={}\n"), static_cast<uint32_t>(vkey), e.Handled(), _isInOverviewMode).c_str());
// GH#11076:
// For some weird reason we sometimes receive a WM_KEYDOWN
@@ -1835,8 +1836,22 @@ namespace winrt::TerminalApp::implementation
});
if (!cmd)
{
OutputDebugStringW(L"[OV] TP::_KeyDownHandler: no cmd bound, returning\n");
return;
}
OutputDebugStringW(fmt::format(FMT_COMPILE(L"[OV] TP::_KeyDownHandler: cmd found, action={}\n"), static_cast<uint32_t>(cmd.ActionAndArgs().Action())).c_str());
// If the overview pane is open, most actions mutate tab state whose
// Content() is currently reparented under the overview. Tear down the
// overview's visuals first so the content is back under its original
// parent before the action runs. ToggleOverview is the exception — it
// handles its own exit.
if (_isInOverviewMode &&
cmd.ActionAndArgs().Action() != ShortcutAction::ToggleOverview)
{
OutputDebugStringW(L"[OV] TP::_KeyDownHandler: dismissing overview before action dispatch\n");
_DismissOverviewVisuals();
}
if (!_actionDispatch->DoAction(cmd.ActionAndArgs()))
{
@@ -3307,13 +3322,15 @@ namespace winrt::TerminalApp::implementation
return true;
}
bool TerminalPage::_IsUriConsideredSomewhatSafe(const winrt::Windows::Foundation::Uri& parsedUri)
bool TerminalPage::_IsUriConsideredSomewhatSafe(const winrt::Windows::Foundation::Uri& parsedUri) const
{
if (parsedUri.SchemeName() == L"http" || parsedUri.SchemeName() == L"https")
const auto& schemeName = parsedUri.SchemeName();
if (schemeName == L"http" || schemeName == L"https")
{
return true;
}
if (parsedUri.SchemeName() == L"file")
if (schemeName == L"file")
{
static const auto pathext{ wil::TryGetEnvironmentVariableW<std::wstring>(L"PATHEXT") };
const auto filename = parsedUri.Path();
@@ -3327,6 +3344,16 @@ namespace winrt::TerminalApp::implementation
return true;
}
if (const auto& safeSchemes = _settings.GlobalSettings().SafeUriSchemes())
{
for (const auto& scheme : safeSchemes)
{
if (til::equals_insensitive_ascii(schemeName, scheme))
{
return true;
}
}
}
return false;
}
@@ -4257,6 +4284,128 @@ namespace winrt::TerminalApp::implementation
AlwaysOnTopChanged.raise(*this, nullptr);
}
void TerminalPage::ToggleOverview()
{
if (_isInOverviewMode)
{
_ExitOverview(std::nullopt);
}
else
{
_EnterOverview();
}
}
void TerminalPage::_EnterOverview()
{
_isInOverviewMode = true;
// Use FindName to lazily load the OverviewPane (same pattern as CommandPalette)
auto overview = FindName(L"OverviewPaneElement").try_as<OverviewPane>();
if (!overview)
{
return;
}
const auto focusedIdx = _GetFocusedTabIndex();
const auto idx = focusedIdx.has_value() ? static_cast<int32_t>(focusedIdx.value()) : 0;
// Wire up events to handle tab selection and dismissal
_overviewTabSelectedToken = overview.TabSelected([weakThis = get_weak()](auto&&, const auto& args) {
if (auto self = weakThis.get())
{
if (args)
{
self->_ExitOverview(static_cast<uint32_t>(args.Value()));
}
}
});
_overviewDismissedToken = overview.Dismissed([weakThis = get_weak()](auto&&, auto&&) {
if (auto self = weakThis.get())
{
self->_ExitOverview(std::nullopt);
}
});
// _tabs is already IObservableVector<Tab>, which inherits from IVector<Tab>
const auto theme = _settings.GlobalSettings().CurrentTheme();
const auto windowTheme = theme ? theme.Window() : nullptr;
overview.UseMica(windowTheme ? windowTheme.UseMica() : false);
overview.UpdateTabContent(_tabs, idx);
overview.Visibility(WUX::Visibility::Visible);
overview.Focus(WUX::FocusState::Programmatic);
}
void TerminalPage::_ExitOverview(const std::optional<uint32_t>& selectedIndex)
{
OutputDebugStringW(fmt::format(FMT_COMPILE(L"[OV] TP::_ExitOverview enter, selectedIndex.has_value={} value={}\n"), selectedIndex.has_value(), selectedIndex.value_or(0xFFFFFFFFu)).c_str());
auto overview = FindName(L"OverviewPaneElement").try_as<OverviewPane>();
// Determine which tab to switch to. Prefer the explicitly passed index
// (from TabSelected event), but fall back to the overview pane's
// current selection — this covers ToggleOverview and Dismiss paths so
// the user always lands on whichever tab they navigated to.
std::optional<uint32_t> tabToSelect = selectedIndex;
if (!tabToSelect.has_value() && overview)
{
const auto overviewIdx = overview.SelectedIndex();
if (overviewIdx >= 0 && overviewIdx < static_cast<int32_t>(_tabs.Size()))
{
tabToSelect = static_cast<uint32_t>(overviewIdx);
}
}
OutputDebugStringW(fmt::format(FMT_COMPILE(L"[OV] TP::_ExitOverview before _DismissOverviewVisuals, tabToSelect.has_value={} value={}\n"), tabToSelect.has_value(), tabToSelect.value_or(0xFFFFFFFFu)).c_str());
_DismissOverviewVisuals();
if (tabToSelect.has_value())
{
OutputDebugStringW(fmt::format(FMT_COMPILE(L"[OV] TP::_ExitOverview calling _SelectTab({})\n"), tabToSelect.value()).c_str());
_SelectTab(tabToSelect.value());
}
OutputDebugStringW(L"[OV] TP::_ExitOverview calling _UpdatedSelectedTab(_GetFocusedTab())\n");
_UpdatedSelectedTab(_GetFocusedTab());
OutputDebugStringW(L"[OV] TP::_ExitOverview returning\n");
}
// Method Description:
// - Tears down the overview's reparented content and hides the overlay,
// without changing tab selection. Safe to call when not in overview mode.
// - Used by both _ExitOverview (which then selects a tab) and by
// _OnTabSelectionChanged (where the TabView has already updated the
// selection and we just need to release the reparented content before
// _UpdatedSelectedTab tries to mount it back into the content area).
void TerminalPage::_DismissOverviewVisuals()
{
OutputDebugStringW(fmt::format(FMT_COMPILE(L"[OV] _DismissOverviewVisuals enter, _isInOverviewMode={}\n"), _isInOverviewMode).c_str());
if (!_isInOverviewMode)
{
return;
}
if (auto overview = FindName(L"OverviewPaneElement").try_as<OverviewPane>())
{
// Revoke event handlers to avoid stale callbacks
overview.TabSelected(_overviewTabSelectedToken);
overview.Dismissed(_overviewDismissedToken);
_overviewTabSelectedToken = {};
_overviewDismissedToken = {};
OutputDebugStringW(L"[OV] _DismissOverviewVisuals calling overview.ClearTabContent()\n");
overview.ClearTabContent();
overview.Visibility(WUX::Visibility::Collapsed);
}
_isInOverviewMode = false;
OutputDebugStringW(L"[OV] _DismissOverviewVisuals exit\n");
}
bool TerminalPage::OverviewMode() const
{
return _isInOverviewMode;
}
// Method Description:
// - Sets the tab split button color when a new tab color is selected
// Arguments:

View File

@@ -138,9 +138,11 @@ namespace winrt::TerminalApp::implementation
void ToggleFocusMode();
void ToggleFullscreen();
void ToggleAlwaysOnTop();
void ToggleOverview();
bool FocusMode() const;
bool Fullscreen() const;
bool AlwaysOnTop() const;
bool OverviewMode() const;
bool ShowTabsFullscreen() const;
void SetShowTabsFullscreen(bool newShowTabsFullscreen);
void SetFullscreen(bool);
@@ -250,6 +252,9 @@ namespace winrt::TerminalApp::implementation
TerminalApp::Tab _settingsTab{ nullptr };
bool _isInFocusMode{ false };
bool _isInOverviewMode{ false };
winrt::event_token _overviewTabSelectedToken{};
winrt::event_token _overviewDismissedToken{};
bool _isFullscreen{ false };
bool _isMaximized{ false };
bool _isAlwaysOnTop{ false };
@@ -375,6 +380,9 @@ namespace winrt::TerminalApp::implementation
void _SelectNextTab(const bool bMoveRight, const Windows::Foundation::IReference<Microsoft::Terminal::Settings::Model::TabSwitcherMode>& customTabSwitcherMode);
bool _SelectTab(uint32_t tabIndex);
void _EnterOverview();
void _ExitOverview(const std::optional<uint32_t>& selectedIndex);
void _DismissOverviewVisuals();
bool _MoveFocus(const Microsoft::Terminal::Settings::Model::FocusDirection& direction);
bool _SwapPane(const Microsoft::Terminal::Settings::Model::FocusDirection& direction);
bool _MovePane(const Microsoft::Terminal::Settings::Model::MovePaneArgs args);
@@ -438,7 +446,7 @@ namespace winrt::TerminalApp::implementation
safe_void_coroutine _OpenHyperlinkHandler(const IInspectable sender, const Microsoft::Terminal::Control::OpenHyperlinkEventArgs eventArgs);
static bool _IsUriSupported(const winrt::Windows::Foundation::Uri& parsedUri);
static bool _IsUriConsideredSomewhatSafe(const winrt::Windows::Foundation::Uri& parsedUri);
bool _IsUriConsideredSomewhatSafe(const winrt::Windows::Foundation::Uri& parsedUri) const;
void _ShowCouldNotOpenDialog(winrt::hstring reason, winrt::hstring uri);
bool _CopyText(bool dismissSelection, bool singleLine, bool withControlSequences, Microsoft::Terminal::Control::CopyFormat formats);

View File

@@ -61,6 +61,7 @@ namespace TerminalApp
Boolean FocusMode { get; };
Boolean Fullscreen { get; };
Boolean AlwaysOnTop { get; };
Boolean OverviewMode { get; };
WindowProperties WindowProperties { get; };
void IdentifyWindow();

View File

@@ -155,6 +155,12 @@
</TextBlock>
</ContentDialog>
<local:OverviewPane x:Name="OverviewPaneElement"
Grid.Row="2"
x:Load="False"
PreviewKeyDown="_KeyDownHandler"
Visibility="Collapsed" />
<local:CommandPalette x:Name="CommandPaletteElement"
Grid.Row="2"
VerticalAlignment="Stretch"

View File

@@ -71,18 +71,6 @@ namespace winrt::TerminalApp::implementation
_removeControlEvents();
_control.Close();
// Clear out our media player callbacks, and stop any playing media. This
// will prevent the callback from being triggered after we've closed, and
// also make sure that our sound stops when we're closed.
if (_bellPlayer)
{
_bellPlayer.Pause();
_bellPlayer.Source(nullptr);
_bellPlayer.Close();
_bellPlayer = nullptr;
_bellPlayerCreated = false;
}
}
winrt::hstring TerminalPaneContent::Icon() const
@@ -275,14 +263,15 @@ namespace winrt::TerminalApp::implementation
auto sounds{ _profile.BellSound() };
if (sounds && sounds.Size() > 0)
{
winrt::hstring soundPath{ sounds.GetAt(rand() % sounds.Size()).Resolved() };
winrt::Windows::Foundation::Uri uri{ soundPath };
_playBellSound(uri);
// Sound paths are resolved and validated by CascadiaSettings
// before we reach this point.
auto soundPath{ sounds.GetAt(rand() % sounds.Size()).Resolved() };
PlaySoundW(soundPath.c_str(), nullptr, SND_FILENAME | SND_ASYNC | SND_SENTRY | SND_NODEFAULT);
}
else
{
const auto soundAlias = reinterpret_cast<LPCTSTR>(SND_ALIAS_SYSTEMHAND);
PlaySound(soundAlias, NULL, SND_ALIAS_ID | SND_ASYNC | SND_SENTRY);
const auto soundAlias = reinterpret_cast<LPCWSTR>(SND_ALIAS_SYSTEMHAND);
PlaySoundW(soundAlias, nullptr, SND_ALIAS_ID | SND_ASYNC | SND_SENTRY);
}
}
@@ -300,33 +289,6 @@ namespace winrt::TerminalApp::implementation
}
}
safe_void_coroutine TerminalPaneContent::_playBellSound(winrt::Windows::Foundation::Uri uri)
{
auto weakThis{ get_weak() };
co_await wil::resume_foreground(_control.Dispatcher());
if (auto pane{ weakThis.get() })
{
if (!_bellPlayerCreated)
{
// The MediaPlayer might not exist on Windows N SKU.
try
{
_bellPlayerCreated = true;
_bellPlayer = winrt::Windows::Media::Playback::MediaPlayer();
// GH#12258: The media keys (like play/pause) should have no effect on our bell sound.
_bellPlayer.CommandManager().IsEnabled(false);
}
CATCH_LOG();
}
if (_bellPlayer)
{
const auto source{ winrt::Windows::Media::Core::MediaSource::CreateFromUri(uri) };
const auto item{ winrt::Windows::Media::Playback::MediaPlaybackItem(source) };
_bellPlayer.Source(item);
_bellPlayer.Play();
}
}
}
void TerminalPaneContent::_closeTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
const winrt::Windows::Foundation::IInspectable& /*args*/)
{

View File

@@ -76,9 +76,6 @@ namespace winrt::TerminalApp::implementation
std::shared_ptr<TerminalSettingsCache> _cache{};
bool _isDefTermSession{ false };
winrt::Windows::Media::Playback::MediaPlayer _bellPlayer{ nullptr };
bool _bellPlayerCreated{ false };
struct ControlEventTokens
{
winrt::Microsoft::Terminal::Control::TermControl::ConnectionStateChanged_revoker _ConnectionStateChanged;
@@ -96,8 +93,6 @@ namespace winrt::TerminalApp::implementation
void _setupControlEvents();
void _removeControlEvents();
safe_void_coroutine _playBellSound(winrt::Windows::Foundation::Uri uri);
safe_void_coroutine _controlConnectionStateChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& /*args*/);
void _controlWarningBellHandler(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& e);

View File

@@ -925,7 +925,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Manually turn off acrylic if they turn off transparency.
_runtimeUseAcrylic = _settings.Opacity() < 1.0 && _settings.UseAcrylic();
const auto sizeChanged = _setFontSizeUnderLock(_settings.FontSize());
const auto sizeChanged = _setFontSizeUnderLock(_settings.FontSize() + _accumulatedFontSizeDelta);
// Update the terminal core with its new Core settings
_terminal->UpdateSettings(_settings);
@@ -1163,11 +1163,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// - none
void ControlCore::ResetFontSize()
{
const auto lock = _terminal->LockForWriting();
if (_setFontSizeUnderLock(_settings.FontSize()))
if (std::exchange(_accumulatedFontSizeDelta, 0.f) != 0.f)
{
_refreshSizeUnderLock();
// No point in doing this if there was no delta.
AdjustFontSize(0);
}
}
@@ -1177,9 +1176,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// - fontSizeDelta: The amount to increase or decrease the font size by.
void ControlCore::AdjustFontSize(float fontSizeDelta)
{
_accumulatedFontSizeDelta += fontSizeDelta;
const auto lock = _terminal->LockForWriting();
if (_setFontSizeUnderLock(_desiredFont.GetFontSize() + fontSizeDelta))
if (_setFontSizeUnderLock(_settings.FontSize() + _accumulatedFontSizeDelta))
{
_refreshSizeUnderLock();
}

View File

@@ -391,6 +391,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bool _colorGlyphs = true;
CSSLengthPercentage _cellWidth;
CSSLengthPercentage _cellHeight;
float _accumulatedFontSizeDelta = 0.f; // Preserved across reloads to prevent user zoom from being overwritten.
// Rendering stuff.
winrt::handle _lastSwapChainHandle{ nullptr };

View File

@@ -2906,6 +2906,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
else
{
// Yes, this is reachable: when a pane is split, the new TermControl
// is added to the XAML tree before its Loaded event fires, so
// _initializedTerminal is still false when layout queries MinimumSize().
// Return a small fallback — the real size will be used once initialized.
return { 10, 10 };
}
}

View File

@@ -98,7 +98,6 @@ void Terminal::UpdateSettings(ICoreSettings settings)
_answerbackMessage = settings.AnswerbackMessage();
_wordDelimiters = settings.WordDelimiters();
_suppressApplicationTitle = settings.SuppressApplicationTitle();
_startingTitle = settings.StartingTitle();
_trimBlockSelection = settings.TrimBlockSelection();
_autoMarkPrompts = settings.AutoMarkPrompts();
_rainbowSuggestions = settings.RainbowSuggestions();
@@ -124,6 +123,11 @@ void Terminal::UpdateSettings(ICoreSettings settings)
// Save the changes made above and in UpdateAppearance as the new default render settings.
GetRenderSettings().SaveDefaultSettings();
if (!_startingTitle)
{
_startingTitle = settings.StartingTitle();
}
if (!_startingTabColor && settings.StartingTabColor())
{
_startingTabColor = settings.StartingTabColor().Value();

View File

@@ -349,7 +349,7 @@ private:
::Microsoft::Console::VirtualTerminal::TerminalInput _terminalInput;
std::optional<std::wstring> _title;
std::wstring _startingTitle;
std::optional<std::wstring> _startingTitle;
std::optional<til::color> _startingTabColor;
std::vector<til::point_span> _searchHighlights;

View File

@@ -91,8 +91,12 @@ void Terminal::SetWindowTitle(const std::wstring_view title)
_assertLocked();
if (!_suppressApplicationTitle)
{
_title.emplace(title.empty() ? _startingTitle : title);
_pfnTitleChanged(_title.value());
_title.reset();
if (!title.empty())
{
_title.emplace(title);
}
_pfnTitleChanged(GetConsoleTitle());
}
}
@@ -112,6 +116,13 @@ bool Terminal::ResizeWindow(const til::CoordType width, const til::CoordType hei
return false;
}
const auto currentDimensions = _GetMutableViewport().Dimensions();
if (width == currentDimensions.width && height == currentDimensions.height)
{
return false;
}
if (_pfnWindowSizeChanged)
{
_pfnWindowSizeChanged(width, height);

View File

@@ -184,11 +184,18 @@ void Terminal::SelectNewRegion(const til::point coordStart, const til::point coo
std::wstring_view Terminal::GetConsoleTitle() const noexcept
{
_assertLocked();
if (_title.has_value())
if (_title)
{
return *_title;
}
return _startingTitle;
if (_startingTitle)
{
return *_startingTitle;
}
return {};
}
// Method Description:

View File

@@ -57,6 +57,7 @@ static constexpr std::string_view SuggestionsKey{ "showSuggestions" };
static constexpr std::string_view ToggleFocusModeKey{ "toggleFocusMode" };
static constexpr std::string_view SetFocusModeKey{ "setFocusMode" };
static constexpr std::string_view ToggleFullscreenKey{ "toggleFullscreen" };
static constexpr std::string_view ToggleOverviewKey{ "toggleOverview" };
static constexpr std::string_view SetFullScreenKey{ "setFullScreen" };
static constexpr std::string_view SetMaximizedKey{ "setMaximized" };
static constexpr std::string_view TogglePaneZoomKey{ "togglePaneZoom" };

View File

@@ -59,6 +59,7 @@
ON_ALL_ACTIONS(ToggleShaderEffects) \
ON_ALL_ACTIONS(ToggleFocusMode) \
ON_ALL_ACTIONS(ToggleFullscreen) \
ON_ALL_ACTIONS(ToggleOverview) \
ON_ALL_ACTIONS(ToggleAlwaysOnTop) \
ON_ALL_ACTIONS(OpenSettings) \
ON_ALL_ACTIONS(SetFocusMode) \

View File

@@ -102,6 +102,14 @@ winrt::com_ptr<GlobalAppSettings> GlobalAppSettings::Copy() const
globals->_DisabledProfileSources->Append(src);
}
}
if (_SafeUriSchemes)
{
globals->_SafeUriSchemes = winrt::single_threaded_vector<hstring>();
for (const auto& src : *_SafeUriSchemes)
{
globals->_SafeUriSchemes->Append(src);
}
}
for (const auto& parent : _parents)
{

View File

@@ -114,6 +114,7 @@ namespace Microsoft.Terminal.Settings.Model
INHERITABLE_SETTING(Boolean, EnableUnfocusedAcrylic);
INHERITABLE_SETTING(Boolean, AllowHeadless);
INHERITABLE_SETTING(String, SearchWebDefaultQueryUrl);
INHERITABLE_SETTING(IVector<String>, SafeUriSchemes);
Windows.Foundation.Collections.IMapView<String, ColorScheme> ColorSchemes();
void AddColorScheme(ColorScheme scheme);

View File

@@ -63,6 +63,7 @@ Author(s):
X(bool, MinimizeToNotificationArea, "minimizeToNotificationArea", false) \
X(bool, AlwaysShowNotificationIcon, "alwaysShowNotificationIcon", false) \
X(winrt::Windows::Foundation::Collections::IVector<winrt::hstring>, DisabledProfileSources, "disabledProfileSources", nullptr) \
X(winrt::Windows::Foundation::Collections::IVector<winrt::hstring>, SafeUriSchemes, "safeUriSchemes", nullptr) \
X(bool, ShowAdminShield, "showAdminShield", true) \
X(bool, TrimPaste, "trimPaste", true) \
X(bool, EnableColorSelection, "experimental.enableColorSelection", false) \

View File

@@ -466,6 +466,9 @@
<value>Toggle focus mode</value>
<comment>"Focus mode" is a mode with minimal UI elements, for a distraction-free experience</comment>
</data>
<data name="ToggleOverviewCommandKey" xml:space="preserve">
<value>Toggle overview mode</value>
</data>
<data name="EnableFocusModeCommandKey" xml:space="preserve">
<value>Enable focus mode</value>
</data>

View File

@@ -517,6 +517,7 @@
{ "command": "closeWindow", "id": "Terminal.CloseWindow" },
{ "command": "toggleFullscreen", "id": "Terminal.ToggleFullscreen" },
{ "command": "toggleFocusMode", "id": "Terminal.ToggleFocusMode" },
{ "command": "toggleOverview", "id": "Terminal.ToggleOverview" },
{ "command": "toggleAlwaysOnTop", "id": "Terminal.ToggleAlwaysOnTop" },
{ "command": "openNewTabDropdown", "id": "Terminal.OpenNewTabDropdown" },
{ "command": { "action": "openSettings", "target": "settingsUI" }, "id": "Terminal.OpenSettingsUI" },

View File

@@ -467,6 +467,7 @@ namespace SettingsModelUnitTests
"$schema" : "https://aka.ms/terminal-profiles-schema",
"defaultProfile": "{61c54bbd-1111-5271-96e7-009a87ff44bf}",
"disabledProfileSources": [ "Windows.Terminal.Wsl" ],
"safeUriSchemes": [ "vscode" ],
"newTabMenu":
[
{

View File

@@ -123,6 +123,7 @@ class ScreenBufferTests
TEST_METHOD(VtResizePreservingAttributes);
TEST_METHOD(VtSoftResetCursorPosition);
TEST_METHOD(VtSoftResetAltBufferCursorState);
TEST_METHOD(VtScrollMarginsNewlineColor);
@@ -1510,6 +1511,30 @@ void ScreenBufferTests::VtSoftResetCursorPosition()
VERIFY_ARE_EQUAL(til::point(1, 1), cursor.GetPosition());
}
void ScreenBufferTests::VtSoftResetAltBufferCursorState()
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
gci.LockConsole(); // Lock must be taken to manipulate buffer.
auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); });
auto& si = gci.GetActiveOutputBuffer();
auto& stateMachine = si.GetStateMachine();
Log::Comment(L"Move cursor on the main buffer.");
stateMachine.ProcessString(L"\x1b[4;7H");
VERIFY_ARE_EQUAL(til::point(6, 3), si.GetTextBuffer().GetCursor().GetPosition());
Log::Comment(L"Enter alt buffer, soft reset, and return to main buffer.");
stateMachine.ProcessString(L"\x1b[?1049h");
VERIFY_IS_TRUE(gci.GetActiveOutputBuffer()._IsAltBuffer());
stateMachine.ProcessString(L"\x1b[!p");
stateMachine.ProcessString(L"\x1b[?1049l");
VERIFY_IS_FALSE(gci.GetActiveOutputBuffer()._IsAltBuffer());
Log::Comment(L"Returning from alt buffer should restore the main cursor position.");
VERIFY_ARE_EQUAL(til::point(6, 3), gci.GetActiveOutputBuffer().GetTextBuffer().GetCursor().GetPosition());
}
void ScreenBufferTests::VtScrollMarginsNewlineColor()
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();

View File

@@ -90,7 +90,15 @@ std::function<bool(wchar_t)> SixelParser::DefineImage(const VTInt macroParameter
_state = States::Normal;
_parameters.clear();
return [&](const auto ch) {
_parseCommandChar(ch);
try
{
_parseCommandChar(ch);
}
catch (...)
{
// Ignore all further content.
return false;
}
return true;
};
}
@@ -234,10 +242,18 @@ void SixelParser::_executeNextLine()
_executeCarriageReturn();
_imageLineCount++;
_maybeFlushImageBuffer();
_imageCursor.y += _sixelHeight;
_availablePixelHeight -= _sixelHeight;
_resizeImageBuffer(_sixelHeight);
_fillImageBackgroundWhenScrolled();
// If we don't have any available pixel height, that means the image has
// extended beyond the bottom of the display and we haven't triggered a
// a scroll (because sixel display mode is enabled). In this state, there
// is no point in extending the image any further, because the additional
// content will never be seen, so we'll just be wasting memory.
if (_availablePixelHeight > 0)
{
_imageCursor.y += _sixelHeight;
_availablePixelHeight -= _sixelHeight;
_resizeImageBuffer(_sixelHeight);
_fillImageBackgroundWhenScrolled();
}
}
void SixelParser::_executeMoveToHome()

View File

@@ -3002,17 +3002,15 @@ void AdaptDispatch::SoftReset()
SetGraphicsRendition({}); // Normal rendition.
SetCharacterProtectionAttribute({}); // Default (unprotected)
// Reset the saved cursor state.
// Note that XTerm only resets the main buffer state, but that
// seems likely to be a bug. Most other terminals reset both.
_savedCursorState.at(0) = {}; // Main buffer
_savedCursorState.at(1) = {}; // Alt buffer
// Reset only the active saved cursor state.
// This matches xterm behavior when DECSTR is processed while using
// the alternate screen buffer (GH#19918).
_savedCursorState.at(_usingAltBuffer ? 1 : 0) = {};
// The TerminalOutput state in these buffers must be reset to
// The TerminalOutput state in this buffer must be reset to
// the same state as the _termOutput instance, which is not
// necessarily equivalent to a full reset.
_savedCursorState.at(0).TermOutput = _termOutput;
_savedCursorState.at(1).TermOutput = _termOutput;
_savedCursorState.at(_usingAltBuffer ? 1 : 0).TermOutput = _termOutput;
// Soft reset the Sixel parser if in use.
if (_sixelParser)