mirror of
https://github.com/microsoft/terminal.git
synced 2026-07-08 17:46:05 +00:00
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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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{};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user