Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions doc/cascadia/profiles.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@
"openTabColorPicker",
"openTabRenamer",
"openWindowRenamer",
"openWorkspace",
"paste",
"prevTab",
"quakeMode",
Expand Down Expand Up @@ -479,6 +480,7 @@
"toggleReadOnlyMode",
"toggleShaderEffects",
"toggleSplitOrientation",
"workspaces",
"wt",
"unbound"
],
Expand Down Expand Up @@ -1721,6 +1723,28 @@
}
]
},
"OpenWorkspaceAction": {
"description": "Arguments corresponding to an openWorkspace Action",
"allOf": [
{
"$ref": "#/$defs/ShortcutAction"
},
{
"type": "object",
"properties": {
"action": {
"type": "string",
"const": "openWorkspace"
},
"name": {
"type": "string",
"default": "",
"description": "The name of the workspace to open. If omitted, the workspaces UI will be shown so the user can pick one."
}
}
}
]
},
"FocusPaneAction": {
"description": "Arguments corresponding to a focusPane Action",
"allOf": [
Expand Down Expand Up @@ -2040,6 +2064,11 @@
"unfocusedFrame": {
"description": "The color of the window frame when the window is inactive. This only works on Windows 11",
"$ref": "#/$defs/ThemeColor"
},
"showWorkspacesButton": {
"description": "When set to true, the workspaces button will be shown in the tab row.",
"type": "boolean",
"default": true
}
}
},
Expand Down Expand Up @@ -2208,6 +2237,9 @@
{
"$ref": "#/$defs/RenameWindowAction"
},
{
"$ref": "#/$defs/OpenWorkspaceAction"
},
{
"$ref": "#/$defs/FocusPaneAction"
},
Expand Down
56 changes: 56 additions & 0 deletions src/cascadia/TerminalApp/AppActionHandlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -938,6 +938,30 @@ namespace winrt::TerminalApp::implementation
co_return;
}

// Launch `wt -w <name>` so the monarch can either summon an existing
// window with that name or restore a persisted workspace.
safe_void_coroutine TerminalPage::_OpenWorkspaceWindow(const winrt::hstring name)
{
co_await winrt::resume_background();

const auto exePath{ GetWtExePath() };
// Quote/escape the window name so that ShellExecute -> CommandLineToArgvW
// round-trips it as a single argument even when it contains spaces or
// other characters that need escaping.
const auto cmdline = fmt::format(FMT_COMPILE(L"-w {}"), QuoteAndEscapeCommandlineArg(std::wstring_view{ name }));

SHELLEXECUTEINFOW seInfo{ 0 };
seInfo.cbSize = sizeof(seInfo);
seInfo.fMask = SEE_MASK_NOASYNC;
seInfo.lpVerb = L"open";
seInfo.lpFile = exePath.c_str();
seInfo.lpParameters = cmdline.c_str();
seInfo.nShow = SW_SHOWNORMAL;
LOG_IF_WIN32_BOOL_FALSE(ShellExecuteExW(&seInfo));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are doing this to ourselves? We are literally calling from inside the process, and we are spawning wt TO SPAWN WindowsTerminal to send a commandline back to ourselves. Why.

This is worse than "we serialize the tab to JSON to tell the other part of the app to launch it anew". Why.

Why.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is literally what openWindow already does literally 20 lines above this. I copypasted that code cause it just worked.

We can refactor that one too... but it seemed like the parity between the two implementations was the easiest path.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. Do we want to re-open this PR then? Sorry for the back and forth.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, we're working on the correct solution


co_return;
}

void TerminalPage::_HandleNewWindow(const IInspectable& /*sender*/,
const ActionEventArgs& actionArgs)
{
Expand Down Expand Up @@ -1058,6 +1082,7 @@ namespace winrt::TerminalApp::implementation
// Fun!
// WindowRenamerTextBox().Focus(FocusState::Programmatic);
_renamerLayoutUpdatedRevoker.revoke();
_renamerLayoutCount = 0;
_renamerLayoutUpdatedRevoker = WindowRenamerTextBox().LayoutUpdated(winrt::auto_revoke, [weakThis = get_weak()](auto&&, auto&&) {
if (auto self{ weakThis.get() })
{
Expand Down Expand Up @@ -1633,4 +1658,35 @@ namespace winrt::TerminalApp::implementation
args.Handled(handled);
}
}

void TerminalPage::_HandleOpenWorkspace(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
// Open (or summon) a named window. We launch a new `wt -w <name>`
// process which the monarch will route to the correct live window or
// restore from a persisted workspace.
if (args)
{
if (const auto& realArgs = args.ActionArgs().try_as<OpenWorkspaceArgs>())
{
const auto name = realArgs.Name();
if (!name.empty())
{
_OpenWorkspaceWindow(name);
}
args.Handled(true);
}
}
}

void TerminalPage::_HandleWorkspaces(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
if (_workspaceFlyout && _workspaceDropdown)
{
_workspaceFlyout.ShowAt(_workspaceDropdown);
}
args.Handled(true);
}

}
4 changes: 4 additions & 0 deletions src/cascadia/TerminalApp/Pane.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ INewContentArgs Pane::GetTerminalArgsForPane(BuildStartupKind kind) const
{
// Leaves are the only things that have controls
assert(_IsLeaf());
if (!_content)
{
return nullptr;
}
return _content.GetNewTerminalArgs(kind);
}

Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/Remoting.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ namespace winrt::TerminalApp::implementation
WINRT_PROPERTY(TerminalApp::CommandlineArgs, Command, nullptr);
WINRT_PROPERTY(winrt::hstring, Content);
WINRT_PROPERTY(Windows::Foundation::IReference<Windows::Foundation::Rect>, InitialBounds);
WINRT_PROPERTY(winrt::Microsoft::Terminal::Settings::Model::WindowLayout, PersistedLayout, nullptr);
};
}

Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/Remoting.idl
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,6 @@ namespace TerminalApp
CommandlineArgs Command { get; };
String Content { get; };
Windows.Foundation.IReference<Windows.Foundation.Rect> InitialBounds { get; };
Microsoft.Terminal.Settings.Model.WindowLayout PersistedLayout;
};
}
39 changes: 39 additions & 0 deletions src/cascadia/TerminalApp/Resources/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,42 @@
<value>unnamed window</value>
<comment>text used to identify when a window hasn't been assigned a name by the user</comment>
</data>
<data name="NameThisWindowMenuItem" xml:space="preserve">
<value>Name this window...</value>
<comment>Menu item text shown when the current window has no name assigned</comment>
Comment thread
zadjii-msft marked this conversation as resolved.
</data>
<data name="RenameThisWindowMenuItem" xml:space="preserve">
<value>Rename this window...</value>
<comment>Menu item text shown when the current window already has a name</comment>
Comment thread
zadjii-msft marked this conversation as resolved.
</data>
<data name="WindowListUnnamedEntry" xml:space="preserve">
<value>#{0} (unnamed)</value>
<comment>{locked="{0}"}{0} is the window ID number. Shown in the workspace flyout for windows that have no name assigned.</comment>
</data>
<data name="DeleteWorkspaceMenuItem" xml:space="preserve">
<value>Delete workspace?</value>
<comment>Menu item text shown in the right-click context menu on a saved workspace</comment>
</data>
<data name="OpenWorkspaceMenuItem" xml:space="preserve">
<value>Open workspace</value>
<comment>Menu item text for opening a saved workspace</comment>
</data>
<data name="ConfirmDeleteWorkspaceTitle" xml:space="preserve">
<value>Delete "{0}"?</value>
<comment>{locked="{0}"}{0} is the workspace name. Shown as the title of a confirmation dialog when the user tries to delete a saved workspace.</comment>
</data>
<data name="ConfirmDeleteWorkspaceBody" xml:space="preserve">
<value>Are you sure you want to delete "{0}"? Sessions associated with this workspace will also be deleted.</value>
<comment>{locked="{0}"}{0} is the workspace name. Body text of the confirmation dialog shown when the user tries to delete a saved workspace.</comment>
</data>
<data name="ConfirmDeleteWorkspaceDelete" xml:space="preserve">
<value>Delete</value>
<comment>Primary button text on the delete workspace confirmation dialog</comment>
</data>
<data name="ConfirmDeleteWorkspaceCancel" xml:space="preserve">
<value>Cancel</value>
<comment>Cancel button text on the delete workspace confirmation dialog</comment>
</data>
<data name="WindowRenamer.Subtitle" xml:space="preserve">
<value>Enter a new name:</value>
</data>
Expand Down Expand Up @@ -801,6 +837,9 @@
<data name="ElevationShield.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>This Terminal window is running as administrator</value>
</data>
<data name="WorkspaceDropdown.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>Workspaces</value>
</data>
<data name="CommandPalette_MatchesAvailable" xml:space="preserve">
<value>Suggestions found: {0}</value>
<comment>{0} will be replaced with a number.</comment>
Expand Down
46 changes: 46 additions & 0 deletions src/cascadia/TerminalApp/TabManagement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,22 @@ namespace winrt::TerminalApp::implementation
_previouslyClosedPanesAndTabs.emplace_back(args);
}

// Method Description:
// - If this window has a name, persist its current workspace layout to
// ApplicationState. Intended to be called from the close-pane / close-tab
// paths while tab/pane content is still alive (before it gets torn down).
void TerminalPage::_SaveWorkspaceIfNeeded()
{
const auto& windowName = _WindowProperties.WindowName();
if (!windowName.empty())
{
if (const auto layout = GetWindowLayout())
{
ApplicationState::SharedInstance().SaveWorkspace(windowName, layout);
}
}
}

// Method Description:
// - Removes the tab (both TerminalControl and XAML) after prompting for approval
// Arguments:
Expand Down Expand Up @@ -450,6 +466,14 @@ namespace winrt::TerminalApp::implementation
auto actions = t->BuildStartupActions(BuildStartupKind::None);
_AddPreviouslyClosedPaneOrTab(std::move(actions));

// If this is the last tab in a named window, persist the workspace
// layout now while tab content is still alive. After tab.Close()
// the pane content will be torn down by the time _RemoveTab runs.
if (_tabs.Size() == 1)
{
_SaveWorkspaceIfNeeded();
}

tab.Close();
}

Expand All @@ -471,6 +495,13 @@ namespace winrt::TerminalApp::implementation

const auto focusedTabIndex{ _GetFocusedTabIndex() };

// NOTE: Workspace persistence for named windows used to live here,
// but by the time _RemoveTab runs the pane content may already be
// torn down (e.g. from the close-pane path). Instead, workspace
// saves are handled earlier:
// - Close-pane (last pane): in _HandleClosePaneRequested
// - Close-tab: in _HandleCloseTabRequested

// Removing the tab from the collection should destroy its control and disconnect its connection,
// but it doesn't always do so. The UI tree may still be holding the control and preventing its destruction.
tab.Shutdown();
Expand Down Expand Up @@ -798,6 +829,21 @@ namespace winrt::TerminalApp::implementation
}
_AddPreviouslyClosedPaneOrTab(std::move(state.args));

// If this is the last pane on the last tab of a named window, persist
// the workspace layout now while the pane content is still alive.
// We can't wait until _RemoveTab, because pane->Close() below will
// destroy the content before _RemoveTab is reached.
if (_tabs.Size() == 1)
{
if (const auto activeTab{ _GetFocusedTabImpl() })
{
if (activeTab->GetLeafPaneCount() == 1)
{
_SaveWorkspaceIfNeeded();
}
}
}

// If specified, detach before closing to directly update the pane structure
pane->Close();
}
Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalApp/TabRowControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ namespace winrt::TerminalApp::implementation

til::property_changed_event PropertyChanged;
WINRT_OBSERVABLE_PROPERTY(bool, ShowElevationShield, PropertyChanged.raise, false);
WINRT_OBSERVABLE_PROPERTY(bool, ShowWorkspacesButton, PropertyChanged.raise, true);
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, WorkspaceName, PropertyChanged.raise, L"");
};
}

Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalApp/TabRowControl.idl
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ namespace TerminalApp
TabRowControl();
Microsoft.UI.Xaml.Controls.TabView TabView { get; };
Boolean ShowElevationShield;
Boolean ShowWorkspacesButton;
String WorkspaceName;
}
}
48 changes: 40 additions & 8 deletions src/cascadia/TerminalApp/TabRowControl.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:TerminalApp"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mtu="using:Microsoft.Terminal.UI"
xmlns:mux="using:Microsoft.UI.Xaml.Controls"
Background="{ThemeResource TabViewBackground}"
mc:Ignorable="d">
Expand Down Expand Up @@ -35,14 +36,45 @@
TabWidthMode="Equal">

<mux:TabView.TabStripHeader>
<!-- EA18 is the "Shield" glyph -->
<FontIcon x:Uid="ElevationShield"
Margin="9,4,0,4"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="16"
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
Glyph="&#xEA18;"
Visibility="{x:Bind ShowElevationShield, Mode=OneWay}" />
<StackPanel Orientation="Horizontal">
<!-- EA18 is the "Shield" glyph -->
<FontIcon x:Uid="ElevationShield"
Margin="9,4,0,4"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="16"
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
Glyph="&#xEA18;"
Visibility="{x:Bind ShowElevationShield, Mode=OneWay}" />

<!-- Workspace/windows button -->
<Button x:Name="WorkspaceDropdown"
Comment thread
zadjii-msft marked this conversation as resolved.
x:Uid="WorkspaceDropdown"
Margin="4,0,0,4"
Padding="8,0,0,0"
VerticalAlignment="Stretch"
Comment thread
zadjii-msft marked this conversation as resolved.
Background="Transparent"
BorderThickness="0"
Visibility="{x:Bind ShowWorkspacesButton, Mode=OneWay}">
<Button.Content>
<StackPanel Orientation="Horizontal"
Spacing="8">
<!-- EE40 is the "TaskViewSettings" glyph -->
<FontIcon FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="12"
Glyph="&#xEE40;" />
<TextBlock x:Name="WorkspaceNameText"
Padding="0,0,8,0"
VerticalAlignment="Center"
FontSize="12"
Text="{x:Bind WorkspaceName, Mode=OneWay}"
Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(WorkspaceName), Mode=OneWay}" />
</StackPanel>
</Button.Content>
<Button.Flyout>
<MenuFlyout x:Name="WorkspaceFlyout" />
</Button.Flyout>
</Button>
</StackPanel>
</mux:TabView.TabStripHeader>

<mux:TabView.TabStripFooter>
Expand Down
Loading
Loading