diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 7e0edef7bd..a8360d92de 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -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: - // - - // 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() }; - - // 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({ 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() }) + // Manually fill in the evaluated profile + if (const auto terminalArgs{ newContentArgs.try_as() }) { 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); } diff --git a/src/cascadia/TerminalApp/Remoting.h b/src/cascadia/TerminalApp/Remoting.h index 9d4a48df76..4634989f27 100644 --- a/src/cascadia/TerminalApp/Remoting.h +++ b/src/cascadia/TerminalApp/Remoting.h @@ -85,6 +85,7 @@ namespace winrt::TerminalApp::implementation WINRT_PROPERTY(TerminalApp::CommandlineArgs, Command, nullptr); WINRT_PROPERTY(winrt::hstring, Content); WINRT_PROPERTY(Windows::Foundation::IReference, InitialBounds); + WINRT_PROPERTY(Windows::Foundation::Collections::IVector, StartupActions, nullptr); }; } diff --git a/src/cascadia/TerminalApp/Remoting.idl b/src/cascadia/TerminalApp/Remoting.idl index 07fde64e50..937e0a45d5 100644 --- a/src/cascadia/TerminalApp/Remoting.idl +++ b/src/cascadia/TerminalApp/Remoting.idl @@ -51,5 +51,6 @@ namespace TerminalApp CommandlineArgs Command { get; }; String Content { get; }; Windows.Foundation.IReference InitialBounds { get; }; + Windows.Foundation.Collections.IVector StartupActions; }; } diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 72f13bf43d..74aade5f19 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -215,6 +215,7 @@ namespace winrt::TerminalApp::implementation til::typed_event RequestReceiveContent; til::typed_event RequestLaunchPosition; + til::typed_event 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); diff --git a/src/cascadia/TerminalApp/TerminalPage.idl b/src/cascadia/TerminalApp/TerminalPage.idl index ce15059864..27be726db0 100644 --- a/src/cascadia/TerminalApp/TerminalPage.idl +++ b/src/cascadia/TerminalApp/TerminalPage.idl @@ -103,5 +103,7 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler RequestReceiveContent; event Windows.Foundation.TypedEventHandler RequestLaunchPosition; + + event Windows.Foundation.TypedEventHandler RequestNewWindow; } } diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp index 67110a1667..d69b5487ef 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.cpp +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -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& actions) + { + _initialContentArgs = wil::to_vector(actions); + } + // Method Description: // - Parse the provided commandline arguments into actions, and try to // perform them immediately. diff --git a/src/cascadia/TerminalApp/TerminalWindow.h b/src/cascadia/TerminalApp/TerminalWindow.h index 08ddeabafc..306f109c13 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.h +++ b/src/cascadia/TerminalApp/TerminalWindow.h @@ -79,6 +79,7 @@ namespace winrt::TerminalApp::implementation int32_t SetStartupCommandline(TerminalApp::CommandlineArgs args); void SetStartupContent(const winrt::hstring& content, const Windows::Foundation::IReference& contentBounds); + void SetStartupActions(const Windows::Foundation::Collections::IVector& actions); int32_t ExecuteCommandline(TerminalApp::CommandlineArgs args); void SetSettingsStartupArgs(const std::vector& 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; diff --git a/src/cascadia/TerminalApp/TerminalWindow.idl b/src/cascadia/TerminalApp/TerminalWindow.idl index c12f73e137..26ed1bc7c8 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.idl +++ b/src/cascadia/TerminalApp/TerminalWindow.idl @@ -56,6 +56,7 @@ namespace TerminalApp Int32 SetStartupCommandline(CommandlineArgs args); void SetStartupContent(String json, Windows.Foundation.IReference bounds); + void SetStartupActions(Windows.Foundation.Collections.IVector actions); Int32 ExecuteCommandline(CommandlineArgs args); Boolean ShouldImmediatelyHandoffToElevated(); @@ -140,6 +141,7 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler RequestMoveContent; event Windows.Foundation.TypedEventHandler RequestReceiveContent; event Windows.Foundation.TypedEventHandler RequestLaunchPosition; + event Windows.Foundation.TypedEventHandler RequestNewWindow; void AttachContent(String content, UInt32 tabIndex); void SendContentToOther(RequestReceiveContentArgs args); diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 0fa2f61ae4..1b59ba3571 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -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{}; diff --git a/src/cascadia/WindowsTerminal/AppHost.h b/src/cascadia/WindowsTerminal/AppHost.h index ffc16d916b..d75b2379ed 100644 --- a/src/cascadia/WindowsTerminal/AppHost.h +++ b/src/cascadia/WindowsTerminal/AppHost.h @@ -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; diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.h b/src/cascadia/WindowsTerminal/WindowEmperor.h index 80d87023d7..5eb93d6401 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.h +++ b/src/cascadia/WindowsTerminal/WindowEmperor.h @@ -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;