Convert the Open New Window handler to an event (#20311)

From ye old days where we were a multi-process app, we would
`shellexecute` to create a new Terminal process when we wanted to open a
new window.

Now that we're all one process (#18215), this dance doesn't make sense
anymore.

So: let's just add a bunch of plumbing to take the IContentArgs up to
the emperor and have it hand them back to a new window.

(this will be used to resurrect #20162)
This commit is contained in:
Mike Griese
2026-06-11 16:26:34 -05:00
committed by GitHub
parent 60a58c93d7
commit 2c9d15200e
11 changed files with 59 additions and 61 deletions

View File

@@ -872,70 +872,27 @@ namespace winrt::TerminalApp::implementation
}
}
// Function Description:
// - Helper to launch a new WT instance. It can either launch the instance
// elevated or unelevated.
// - To launch elevated, it will as the shell to elevate the process for us.
// This might cause a UAC prompt. The elevation is performed on a
// background thread, as to not block the UI thread.
// Arguments:
// - newTerminalArgs: A NewTerminalArgs describing the terminal instance
// that should be spawned. The Profile should be filled in with the GUID
// of the profile we want to launch.
// Return Value:
// - <none>
// Important: Don't take the param by reference, since we'll be doing work
// on another thread.
safe_void_coroutine TerminalPage::_OpenNewWindow(const INewContentArgs newContentArgs)
// Ask the WindowEmperor (in-process) to create a brand-new window whose
// first tab is described by `contentArgs`. This will bubble up to AppHost,
// who will call WindowEmperor::CreateNewWindow.
void TerminalPage::_OpenNewWindow(const INewContentArgs& contentArgs)
{
auto terminalArgs{ newContentArgs.try_as<NewTerminalArgs>() };
// Do nothing for non-terminal panes.
//
// Theoretically, we could define a `IHasCommandline` interface, and
// stick `ToCommandline` on that interface, for any kind of pane that
// wants to be convertable to a wt commandline.
//
// Another idea we're thinking about is just `wt do {literal json for an
// action}`, which might be less leaky
if (terminalArgs == nullptr)
if (!contentArgs)
{
co_return;
return;
}
// ShellExecuteExW may block, so do it on a background thread.
//
// NOTE: All remaining code of this function doesn't touch `this`, so we don't need weak/strong_ref.
// NOTE NOTE: Don't touch `this` when you make changes here.
co_await winrt::resume_background();
ActionAndArgs newTabAction{};
newTabAction.Action(ShortcutAction::NewTab);
newTabAction.Args(NewTabArgs{ contentArgs });
// This will get us the correct exe for dev/preview/release. If you
// don't stick this in a local, it'll get mangled by ShellExecute. I
// have no idea why.
const auto exePath{ GetWtExePath() };
auto actions = winrt::single_threaded_vector<ActionAndArgs>({ std::move(newTabAction) });
// Build the commandline to pass to wt for this set of NewTerminalArgs
// `-w -1` will ensure a new window is created.
const auto commandline = terminalArgs.ToCommandline();
const auto cmdline = fmt::format(FMT_COMPILE(L"-w -1 new-tab {}"), commandline);
// Build the args to ShellExecuteEx. We need to use ShellExecuteEx so we
// can pass the SEE_MASK_NOASYNC flag. That flag allows us to safely
// call this on the background thread, and have ShellExecute _not_ call
// back to us on the main thread. Without this, if you close the
// Terminal quickly after the UAC prompt, the elevated WT will never
// actually spawn.
SHELLEXECUTEINFOW seInfo{ 0 };
seInfo.cbSize = sizeof(seInfo);
seInfo.fMask = SEE_MASK_NOASYNC;
// `open` will just run the executable normally.
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));
co_return;
// It's fine to pass `0` as the window ID, since this event path will
// always land in CreateNewWindow, which will just ignore it.
winrt::TerminalApp::WindowRequestedArgs request{ 0, winrt::TerminalApp::CommandlineArgs{} };
request.StartupActions(std::move(actions));
RequestNewWindow.raise(*this, request);
}
void TerminalPage::_HandleNewWindow(const IInspectable& /*sender*/,
@@ -959,13 +916,13 @@ namespace winrt::TerminalApp::implementation
newContentArgs = NewTerminalArgs{};
}
if (const auto& terminalArgs{ newContentArgs.try_as<NewTerminalArgs>() })
// Manually fill in the evaluated profile
if (const auto terminalArgs{ newContentArgs.try_as<NewTerminalArgs>() })
{
const auto profile{ _settings.GetProfileForArgs(terminalArgs) };
terminalArgs.Profile(::Microsoft::Console::Utils::GuidToString(profile.Guid()));
}
// Manually fill in the evaluated profile.
_OpenNewWindow(newContentArgs);
actionArgs.Handled(true);
}

View File

@@ -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(Windows::Foundation::Collections::IVector<winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs>, StartupActions, nullptr);
};
}

View File

@@ -51,5 +51,6 @@ namespace TerminalApp
CommandlineArgs Command { get; };
String Content { get; };
Windows.Foundation.IReference<Windows.Foundation.Rect> InitialBounds { get; };
Windows.Foundation.Collections.IVector<Microsoft.Terminal.Settings.Model.ActionAndArgs> StartupActions;
};
}

View File

@@ -215,6 +215,7 @@ namespace winrt::TerminalApp::implementation
til::typed_event<Windows::Foundation::IInspectable, winrt::TerminalApp::RequestReceiveContentArgs> RequestReceiveContent;
til::typed_event<IInspectable, winrt::TerminalApp::LaunchPositionRequest> RequestLaunchPosition;
til::typed_event<IInspectable, winrt::TerminalApp::WindowRequestedArgs> RequestNewWindow;
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, TitlebarBrush, PropertyChanged.raise, nullptr);
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, FrameBrush, PropertyChanged.raise, nullptr);
@@ -334,7 +335,7 @@ namespace winrt::TerminalApp::implementation
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _duplicateConnectionForRestart(const TerminalApp::TerminalPaneContent& paneContent);
void _restartPaneConnection(const TerminalApp::TerminalPaneContent&, const winrt::Windows::Foundation::IInspectable&);
safe_void_coroutine _OpenNewWindow(const Microsoft::Terminal::Settings::Model::INewContentArgs newContentArgs);
void _OpenNewWindow(const Microsoft::Terminal::Settings::Model::INewContentArgs& contentArgs);
void _OpenNewTerminalViaDropdown(const Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs);

View File

@@ -103,5 +103,7 @@ namespace TerminalApp
event Windows.Foundation.TypedEventHandler<Object, RequestReceiveContentArgs> RequestReceiveContent;
event Windows.Foundation.TypedEventHandler<Object, LaunchPositionRequest> RequestLaunchPosition;
event Windows.Foundation.TypedEventHandler<Object, WindowRequestedArgs> RequestNewWindow;
}
}

View File

@@ -1103,6 +1103,15 @@ namespace winrt::TerminalApp::implementation
_initialContentArgs = wil::to_vector(args);
}
// Method Description:
// - Provide a pre-built list of startup actions for this window. Used by
// the in-proc OpenNewWindow event (see TerminalPage::_OpenNewWindow ->
// TerminalWindow -> AppHost -> WindowEmperor::OpenNewWindow)
void TerminalWindow::SetStartupActions(const IVector<ActionAndArgs>& actions)
{
_initialContentArgs = wil::to_vector(actions);
}
// Method Description:
// - Parse the provided commandline arguments into actions, and try to
// perform them immediately.

View File

@@ -79,6 +79,7 @@ namespace winrt::TerminalApp::implementation
int32_t SetStartupCommandline(TerminalApp::CommandlineArgs args);
void SetStartupContent(const winrt::hstring& content, const Windows::Foundation::IReference<Windows::Foundation::Rect>& contentBounds);
void SetStartupActions(const Windows::Foundation::Collections::IVector<winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs>& actions);
int32_t ExecuteCommandline(TerminalApp::CommandlineArgs args);
void SetSettingsStartupArgs(const std::vector<winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs>& actions);
@@ -231,6 +232,7 @@ namespace winrt::TerminalApp::implementation
FORWARDED_TYPED_EVENT(RequestReceiveContent, Windows::Foundation::IInspectable, winrt::TerminalApp::RequestReceiveContentArgs, _root, RequestReceiveContent);
FORWARDED_TYPED_EVENT(RequestLaunchPosition, Windows::Foundation::IInspectable, winrt::TerminalApp::LaunchPositionRequest, _root, RequestLaunchPosition);
FORWARDED_TYPED_EVENT(RequestNewWindow, Windows::Foundation::IInspectable, winrt::TerminalApp::WindowRequestedArgs, _root, RequestNewWindow);
#ifdef UNIT_TESTING
friend class TerminalAppLocalTests::CommandlineTest;

View File

@@ -56,6 +56,7 @@ namespace TerminalApp
Int32 SetStartupCommandline(CommandlineArgs args);
void SetStartupContent(String json, Windows.Foundation.IReference<Windows.Foundation.Rect> bounds);
void SetStartupActions(Windows.Foundation.Collections.IVector<Microsoft.Terminal.Settings.Model.ActionAndArgs> actions);
Int32 ExecuteCommandline(CommandlineArgs args);
Boolean ShouldImmediatelyHandoffToElevated();
@@ -140,6 +141,7 @@ namespace TerminalApp
event Windows.Foundation.TypedEventHandler<Object, RequestMoveContentArgs> RequestMoveContent;
event Windows.Foundation.TypedEventHandler<Object, RequestReceiveContentArgs> RequestReceiveContent;
event Windows.Foundation.TypedEventHandler<Object, LaunchPositionRequest> RequestLaunchPosition;
event Windows.Foundation.TypedEventHandler<Object, WindowRequestedArgs> RequestNewWindow;
void AttachContent(String content, UInt32 tabIndex);
void SendContentToOther(RequestReceiveContentArgs args);

View File

@@ -137,6 +137,11 @@ void AppHost::_HandleCommandlineArgs(const winrt::TerminalApp::WindowRequestedAr
_windowLogic.SetStartupContent(content, windowArgs.InitialBounds());
_launchShowWindowCommand = SW_NORMAL;
}
else if (const auto actions = windowArgs.StartupActions(); actions && actions.Size() > 0)
{
_windowLogic.SetStartupActions(actions);
_launchShowWindowCommand = SW_NORMAL;
}
else
{
const auto args = windowArgs.Command();
@@ -271,6 +276,7 @@ void AppHost::Initialize()
_revokers.ShowWindowChanged = _windowLogic.ShowWindowChanged(winrt::auto_revoke, { this, &AppHost::_ShowWindowChanged });
_revokers.RequestMoveContent = _windowLogic.RequestMoveContent(winrt::auto_revoke, { this, &AppHost::_handleMoveContent });
_revokers.RequestReceiveContent = _windowLogic.RequestReceiveContent(winrt::auto_revoke, { this, &AppHost::_handleReceiveContent });
_revokers.RequestNewWindow = _windowLogic.RequestNewWindow(winrt::auto_revoke, { this, &AppHost::_HandleNewWindowRequested });
// BODGY
// On certain builds of Windows, when Terminal is set as the default
@@ -410,6 +416,18 @@ void AppHost::_HandleRequestLaunchPosition(const winrt::Windows::Foundation::IIn
args.Position(_GetWindowLaunchPosition());
}
// In-process replacement for the old `ShellExecute("wt -w -1 new-tab ...")`
// dance. The page hands us a pre-built WindowRequestedArgs (with its
// StartupActions already populated); we just forward it to the WindowEmperor.
void AppHost::_HandleNewWindowRequested(const winrt::Windows::Foundation::IInspectable&,
const winrt::TerminalApp::WindowRequestedArgs& args)
{
if (_windowManager && args)
{
_windowManager->CreateNewWindow(args);
}
}
LaunchPosition AppHost::_GetWindowLaunchPosition()
{
LaunchPosition pos{};

View File

@@ -97,6 +97,9 @@ private:
void _OpenSystemMenu(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& args);
void _HandleNewWindowRequested(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::TerminalApp::WindowRequestedArgs& args);
void _SystemMenuChangeRequested(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::TerminalApp::SystemMenuChangeArgs& args);
@@ -161,6 +164,7 @@ private:
winrt::TerminalApp::TerminalWindow::RequestMoveContent_revoker RequestMoveContent;
winrt::TerminalApp::TerminalWindow::RequestReceiveContent_revoker RequestReceiveContent;
winrt::TerminalApp::TerminalWindow::RequestLaunchPosition_revoker RequestLaunchPosition;
winrt::TerminalApp::TerminalWindow::RequestNewWindow_revoker RequestNewWindow;
winrt::TerminalApp::TerminalWindow::PropertyChanged_revoker PropertyChanged;
winrt::TerminalApp::TerminalWindow::SettingsChanged_revoker SettingsChanged;
winrt::TerminalApp::TerminalWindow::WindowSizeChanged_revoker WindowSizeChanged;

View File

@@ -33,6 +33,7 @@ public:
HWND GetMainWindow() const noexcept;
AppHost* GetWindowById(uint64_t id) const noexcept;
AppHost* GetWindowByName(std::wstring_view name) const noexcept;
// CreateNewWindow is used for creating a new window from existing Content
void CreateNewWindow(winrt::TerminalApp::WindowRequestedArgs args);
void HandleCommandlineArgs(int nCmdShow);
void FocusTabInAnyWindow(const winrt::TerminalApp::Tab& tab) const;