Move ConPTY handoff logic into WindowEmperor (#19088)

This changes the ConPTY handoff COM server from `REGCLS_SINGLEUSE`
to `REGCLS_MULTIPLEUSE`. The former causes a race condition, because
handoff runs concurrently with the creation of WinUI windows.
This can then result in the a window getting the wrong handoff.

It then moves the "root" of ConPTY handoff from `TerminalPage`
(WindowEmperor -> AppHost -> TerminalWindow -> TerminalPage)
into `WindowEmperor` (WindowEmperor).

Closes #19049

## Validation Steps Performed
* Launching cmd from the Start Menu shows a "Command Prompt" tab 
* Win+R -> `cmd` creates windows in the foreground 
* Win+R -> `cmd /c start /max cmd` creates a fullscreen tab 
  * This even works for multiple windows, unlike with Canary 
* Win+R -> `cmd /c start /min cmd` does not work 
  * It also doesn't work in Canary, so it's not a bug in this PR 
This commit is contained in:
Leonard Hecker 2025-07-01 21:00:00 +02:00 committed by GitHub
parent fc0a06c3b6
commit a25d968fe0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 166 additions and 293 deletions

View File

@ -756,7 +756,7 @@ namespace winrt::TerminalApp::implementation
if (!actions.empty()) if (!actions.empty())
{ {
actionArgs.Handled(true); actionArgs.Handled(true);
ProcessStartupActions(std::move(actions), false); ProcessStartupActions(std::move(actions));
} }
} }
} }

View File

@ -961,18 +961,6 @@ std::vector<ActionAndArgs>& AppCommandlineArgs::GetStartupActions()
return _startupActions; return _startupActions;
} }
// Method Description:
// - Returns whether we should start listening for inbound PTY connections
// coming from the operating system default application feature.
// Arguments:
// - <none>
// Return Value:
// - True if the listener should be started. False otherwise.
bool AppCommandlineArgs::IsHandoffListener() const noexcept
{
return _isHandoffListener;
}
// Method Description: // Method Description:
// - Get the string of text that should be displayed to the user on exit. This // - Get the string of text that should be displayed to the user on exit. This
// is usually helpful for cases where the user entered some sort of invalid // is usually helpful for cases where the user entered some sort of invalid
@ -1015,34 +1003,28 @@ bool AppCommandlineArgs::ShouldExitEarly() const noexcept
// - <none> // - <none>
void AppCommandlineArgs::ValidateStartupCommands() void AppCommandlineArgs::ValidateStartupCommands()
{ {
// Only check over the actions list for the potential to add a new-tab // If we only have a single x-save command, then set our target to the
// command if we are not starting for the purposes of receiving an inbound // current terminal window. This will prevent us from spawning a new
// handoff connection from the operating system. // window just to save the commandline.
if (!_isHandoffListener) if (_startupActions.size() == 1 &&
_startupActions.front().Action() == ShortcutAction::SaveSnippet &&
_windowTarget.empty())
{ {
// If we only have a single x-save command, then set our target to the _windowTarget = "0";
// current terminal window. This will prevent us from spawning a new }
// window just to save the commandline. // If we parsed no commands, or the first command we've parsed is not a new
if (_startupActions.size() == 1 && // tab action, prepend a new-tab command to the front of the list.
_startupActions.front().Action() == ShortcutAction::SaveSnippet && // (also, we don't need to do this if the only action is a x-save)
_windowTarget.empty()) else if (_startupActions.empty() ||
{ (_startupActions.front().Action() != ShortcutAction::NewTab &&
_windowTarget = "0"; _startupActions.front().Action() != ShortcutAction::SaveSnippet))
} {
// If we parsed no commands, or the first command we've parsed is not a new // Build the NewTab action from the values we've parsed on the commandline.
// tab action, prepend a new-tab command to the front of the list. NewTerminalArgs newTerminalArgs{};
// (also, we don't need to do this if the only action is a x-save) NewTabArgs args{ newTerminalArgs };
else if (_startupActions.empty() || ActionAndArgs newTabAction{ ShortcutAction::NewTab, args };
(_startupActions.front().Action() != ShortcutAction::NewTab && // push the arg onto the front
_startupActions.front().Action() != ShortcutAction::SaveSnippet)) _startupActions.insert(_startupActions.begin(), 1, newTabAction);
{
// Build the NewTab action from the values we've parsed on the commandline.
NewTerminalArgs newTerminalArgs{};
NewTabArgs args{ newTerminalArgs };
ActionAndArgs newTabAction{ ShortcutAction::NewTab, args };
// push the arg onto the front
_startupActions.insert(_startupActions.begin(), 1, newTabAction);
}
} }
} }
std::optional<uint32_t> AppCommandlineArgs::GetPersistedLayoutIdx() const noexcept std::optional<uint32_t> AppCommandlineArgs::GetPersistedLayoutIdx() const noexcept
@ -1082,13 +1064,9 @@ std::optional<til::size> AppCommandlineArgs::GetSize() const noexcept
// - 0 if the commandline was successfully parsed // - 0 if the commandline was successfully parsed
int AppCommandlineArgs::ParseArgs(winrt::array_view<const winrt::hstring> args) int AppCommandlineArgs::ParseArgs(winrt::array_view<const winrt::hstring> args)
{ {
for (const auto& arg : args) if (args.size() == 2 && args[1] == L"-Embedding")
{ {
if (arg == L"-Embedding") return 0;
{
_isHandoffListener = true;
return 0;
}
} }
auto commands = ::TerminalApp::AppCommandlineArgs::BuildCommands(args); auto commands = ::TerminalApp::AppCommandlineArgs::BuildCommands(args);
@ -1195,7 +1173,6 @@ void AppCommandlineArgs::FullResetState()
_startupActions.clear(); _startupActions.clear();
_exitMessage = ""; _exitMessage = "";
_shouldExitEarly = false; _shouldExitEarly = false;
_isHandoffListener = false;
_windowTarget = {}; _windowTarget = {};
} }

View File

@ -35,7 +35,6 @@ public:
void ValidateStartupCommands(); void ValidateStartupCommands();
std::vector<winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs>& GetStartupActions(); std::vector<winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs>& GetStartupActions();
bool IsHandoffListener() const noexcept;
const std::string& GetExitMessage() const noexcept; const std::string& GetExitMessage() const noexcept;
bool ShouldExitEarly() const noexcept; bool ShouldExitEarly() const noexcept;
@ -132,7 +131,6 @@ private:
std::optional<winrt::Microsoft::Terminal::Settings::Model::LaunchMode> _launchMode{ std::nullopt }; std::optional<winrt::Microsoft::Terminal::Settings::Model::LaunchMode> _launchMode{ std::nullopt };
std::optional<winrt::Microsoft::Terminal::Settings::Model::LaunchPosition> _position{ std::nullopt }; std::optional<winrt::Microsoft::Terminal::Settings::Model::LaunchPosition> _position{ std::nullopt };
std::optional<til::size> _size{ std::nullopt }; std::optional<til::size> _size{ std::nullopt };
bool _isHandoffListener{ false };
std::vector<winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs> _startupActions; std::vector<winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs> _startupActions;
std::string _exitMessage; std::string _exitMessage;
bool _shouldExitEarly{ false }; bool _shouldExitEarly{ false };

View File

@ -15,19 +15,6 @@ using namespace winrt::Windows::Foundation;
namespace winrt::TerminalApp::implementation namespace winrt::TerminalApp::implementation
{ {
CommandlineArgs::CommandlineArgs(winrt::array_view<const winrt::hstring> args, winrt::hstring currentDirectory, uint32_t showWindowCommand, winrt::hstring envString) :
_args{ args.begin(), args.end() },
CurrentDirectory{ std::move(currentDirectory) },
ShowWindowCommand{ showWindowCommand },
CurrentEnvironment{ std::move(envString) }
{
_parseResult = _parsed.ParseArgs(_args);
if (_parseResult == 0)
{
_parsed.ValidateStartupCommands();
}
}
::TerminalApp::AppCommandlineArgs& CommandlineArgs::ParsedArgs() noexcept ::TerminalApp::AppCommandlineArgs& CommandlineArgs::ParsedArgs() noexcept
{ {
return _parsed; return _parsed;
@ -56,6 +43,11 @@ namespace winrt::TerminalApp::implementation
void CommandlineArgs::Commandline(const winrt::array_view<const winrt::hstring>& value) void CommandlineArgs::Commandline(const winrt::array_view<const winrt::hstring>& value)
{ {
_args = { value.begin(), value.end() }; _args = { value.begin(), value.end() };
_parseResult = _parsed.ParseArgs(_args);
if (_parseResult == 0)
{
_parsed.ValidateStartupCommands();
}
} }
winrt::com_array<winrt::hstring> CommandlineArgs::Commandline() winrt::com_array<winrt::hstring> CommandlineArgs::Commandline()

View File

@ -13,21 +13,17 @@ namespace winrt::TerminalApp::implementation
{ {
struct CommandlineArgs : public CommandlineArgsT<CommandlineArgs> struct CommandlineArgs : public CommandlineArgsT<CommandlineArgs>
{ {
CommandlineArgs() = default;
CommandlineArgs(winrt::array_view<const winrt::hstring> args, winrt::hstring currentDirectory, uint32_t showWindowCommand, winrt::hstring envString);
::TerminalApp::AppCommandlineArgs& ParsedArgs() noexcept; ::TerminalApp::AppCommandlineArgs& ParsedArgs() noexcept;
winrt::com_array<winrt::hstring>& CommandlineRef() noexcept; winrt::com_array<winrt::hstring>& CommandlineRef() noexcept;
// These bits are exposed via WinRT: // These bits are exposed via WinRT:
public:
int32_t ExitCode() const noexcept; int32_t ExitCode() const noexcept;
winrt::hstring ExitMessage() const; winrt::hstring ExitMessage() const;
winrt::hstring TargetWindow() const; winrt::hstring TargetWindow() const;
til::property<winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection> Connection;
void Commandline(const winrt::array_view<const winrt::hstring>& value); void Commandline(const winrt::array_view<const winrt::hstring>& value);
winrt::com_array<winrt::hstring> Commandline(); winrt::com_array<winrt::hstring> Commandline();
til::property<winrt::hstring> CurrentDirectory; til::property<winrt::hstring> CurrentDirectory;
til::property<winrt::hstring> CurrentEnvironment; til::property<winrt::hstring> CurrentEnvironment;
til::property<uint32_t> ShowWindowCommand{ static_cast<uint32_t>(SW_NORMAL) }; // SW_NORMAL is 1, 0 is SW_HIDE til::property<uint32_t> ShowWindowCommand{ static_cast<uint32_t>(SW_NORMAL) }; // SW_NORMAL is 1, 0 is SW_HIDE
@ -86,11 +82,9 @@ namespace winrt::TerminalApp::implementation
WINRT_PROPERTY(uint64_t, Id); WINRT_PROPERTY(uint64_t, Id);
WINRT_PROPERTY(winrt::hstring, WindowName); WINRT_PROPERTY(winrt::hstring, WindowName);
WINRT_PROPERTY(TerminalApp::CommandlineArgs, Command); WINRT_PROPERTY(TerminalApp::CommandlineArgs, Command, nullptr);
WINRT_PROPERTY(winrt::hstring, Content); WINRT_PROPERTY(winrt::hstring, Content);
WINRT_PROPERTY(Windows::Foundation::IReference<Windows::Foundation::Rect>, InitialBounds); WINRT_PROPERTY(Windows::Foundation::IReference<Windows::Foundation::Rect>, InitialBounds);
private:
}; };
} }

View File

@ -6,16 +6,16 @@ namespace TerminalApp
runtimeclass CommandlineArgs runtimeclass CommandlineArgs
{ {
CommandlineArgs(); CommandlineArgs();
CommandlineArgs(String[] args, String cwd, UInt32 showWindowCommand, String env);
Int32 ExitCode { get; }; Int32 ExitCode { get; };
String ExitMessage { get; }; String ExitMessage { get; };
String TargetWindow { get; }; String TargetWindow { get; };
Microsoft.Terminal.TerminalConnection.ITerminalConnection Connection;
String[] Commandline; String[] Commandline;
String CurrentDirectory { get; }; String CurrentDirectory;
UInt32 ShowWindowCommand { get; }; UInt32 ShowWindowCommand;
String CurrentEnvironment { get; }; String CurrentEnvironment;
}; };
enum MonitorBehavior enum MonitorBehavior

View File

@ -7,7 +7,6 @@
#include <LibraryResources.h> #include <LibraryResources.h>
#include <TerminalCore/ControlKeyStates.hpp> #include <TerminalCore/ControlKeyStates.hpp>
#include <til/latch.h>
#include <Utils.h> #include <Utils.h>
#include "../../types/inc/utils.hpp" #include "../../types/inc/utils.hpp"
@ -293,12 +292,11 @@ namespace winrt::TerminalApp::implementation
// - true if we're not elevated but all relevant pane-spawning actions are elevated // - true if we're not elevated but all relevant pane-spawning actions are elevated
bool TerminalPage::ShouldImmediatelyHandoffToElevated(const CascadiaSettings& settings) const bool TerminalPage::ShouldImmediatelyHandoffToElevated(const CascadiaSettings& settings) const
{ {
// GH#12267: Don't forget about defterm handoff here. If we're being if (_startupActions.empty() || _startupConnection || IsRunningElevated())
// created for embedding, then _yea_, we don't need to handoff to an
// elevated window.
if (_startupActions.empty() || IsRunningElevated() || _shouldStartInboundListener)
{ {
// there aren't startup actions, or we're elevated. In that case, go for it. // No point in handing off if we got no startup actions, or we're already elevated.
// Also, we shouldn't need to elevate handoff ConPTY connections.
assert(!_startupConnection);
return false; return false;
} }
@ -488,46 +486,16 @@ namespace winrt::TerminalApp::implementation
{ {
_startupState = StartupState::InStartup; _startupState = StartupState::InStartup;
ProcessStartupActions(std::move(_startupActions), true); if (_startupConnection)
// If we were told that the COM server needs to be started to listen for incoming
// default application connections, start it now.
// This MUST be done after we've registered the event listener for the new connections
// or the COM server might start receiving requests on another thread and dispatch
// them to nowhere.
_StartInboundListener();
}
}
// Routine Description:
// - Will start the listener for inbound console handoffs if we have already determined
// that we should do so.
// NOTE: Must be after TerminalPage::_OnNewConnection has been connected up.
// Arguments:
// - <unused> - Looks at _shouldStartInboundListener
// Return Value:
// - <none> - May fail fast if setup fails as that would leave us in a weird state.
void TerminalPage::_StartInboundListener()
{
if (_shouldStartInboundListener)
{
_shouldStartInboundListener = false;
// Hook up inbound connection event handler
_newConnectionRevoker = ConptyConnection::NewConnection(winrt::auto_revoke, { this, &TerminalPage::_OnNewConnection });
try
{ {
winrt::Microsoft::Terminal::TerminalConnection::ConptyConnection::StartInboundListener(); CreateTabFromConnection(std::move(_startupConnection));
} }
// If we failed to start the listener, it will throw. else if (!_startupActions.empty())
// We don't want to fail fast here because if a peasant has some trouble with
// starting the listener, we don't want it to crash and take all its tabs down
// with it.
catch (...)
{ {
LOG_CAUGHT_EXCEPTION(); ProcessStartupActions(std::move(_startupActions));
} }
_CompleteInitialization();
} }
} }
@ -545,7 +513,7 @@ namespace winrt::TerminalApp::implementation
// nt -d .` from inside another directory to work as expected. // nt -d .` from inside another directory to work as expected.
// Return Value: // Return Value:
// - <none> // - <none>
safe_void_coroutine TerminalPage::ProcessStartupActions(std::vector<ActionAndArgs> actions, const bool initial, const winrt::hstring cwd, const winrt::hstring env) safe_void_coroutine TerminalPage::ProcessStartupActions(std::vector<ActionAndArgs> actions, const winrt::hstring cwd, const winrt::hstring env)
{ {
const auto strong = get_strong(); const auto strong = get_strong();
@ -597,11 +565,29 @@ namespace winrt::TerminalApp::implementation
content.Focus(FocusState::Programmatic); content.Focus(FocusState::Programmatic);
} }
} }
}
if (initial) void TerminalPage::CreateTabFromConnection(ITerminalConnection connection)
{
NewTerminalArgs newTerminalArgs;
if (const auto conpty = connection.try_as<ConptyConnection>())
{ {
_CompleteInitialization(); newTerminalArgs.Commandline(conpty.Commandline());
newTerminalArgs.TabTitle(conpty.StartingTitle());
} }
// GH #12370: We absolutely cannot allow a defterm connection to
// auto-elevate. Defterm doesn't work for elevated scenarios in the
// first place. If we try accepting the connection, the spawning an
// elevated version of the Terminal with that profile... that's a
// recipe for disaster. We won't ever open up a tab in this window.
newTerminalArgs.Elevate(false);
const auto newPane = _MakePane(newTerminalArgs, nullptr, std::move(connection));
newPane->WalkTree([](const auto& pane) {
pane->FinalizeConfigurationGivenDefault();
});
_CreateNewTabFromPane(newPane);
} }
// Method Description: // Method Description:
@ -629,7 +615,7 @@ namespace winrt::TerminalApp::implementation
// GH#12267: Make sure that we don't instantly close ourselves when // GH#12267: Make sure that we don't instantly close ourselves when
// we're readying to accept a defterm connection. In that case, we don't // we're readying to accept a defterm connection. In that case, we don't
// have a tab yet, but will once we're initialized. // have a tab yet, but will once we're initialized.
if (_tabs.Size() == 0 && !_shouldStartInboundListener && !_isEmbeddingInboundListener) if (_tabs.Size() == 0)
{ {
CloseWindowRequested.raise(*this, nullptr); CloseWindowRequested.raise(*this, nullptr);
co_return; co_return;
@ -3653,25 +3639,9 @@ namespace winrt::TerminalApp::implementation
_startupActions = std::move(actions); _startupActions = std::move(actions);
} }
// Routine Description: void TerminalPage::SetStartupConnection(ITerminalConnection connection)
// - Notifies this Terminal Page that it should start the incoming connection
// listener for command-line tools attempting to join this Terminal
// through the default application channel.
// Arguments:
// - isEmbedding - True if COM started us to be a server. False if we're doing it of our own accord.
// Return Value:
// - <none>
void TerminalPage::SetInboundListener(bool isEmbedding)
{ {
_shouldStartInboundListener = true; _startupConnection = std::move(connection);
_isEmbeddingInboundListener = isEmbedding;
// If the page has already passed the NotInitialized state,
// then it is ready-enough for us to just start this immediately.
if (_startupState != StartupState::NotInitialized)
{
_StartInboundListener();
}
} }
winrt::TerminalApp::IDialogPresenter TerminalPage::DialogPresenter() const winrt::TerminalApp::IDialogPresenter TerminalPage::DialogPresenter() const
@ -4073,68 +4043,6 @@ namespace winrt::TerminalApp::implementation
ChangeMaximizeRequested.raise(*this, nullptr); ChangeMaximizeRequested.raise(*this, nullptr);
} }
HRESULT TerminalPage::_OnNewConnection(const ConptyConnection& connection)
{
_newConnectionRevoker.revoke();
// We need to be on the UI thread in order for _OpenNewTab to run successfully.
// HasThreadAccess will return true if we're currently on a UI thread and false otherwise.
// When we're on a COM thread, we'll need to dispatch the calls to the UI thread
// and wait on it hence the locking mechanism.
if (!Dispatcher().HasThreadAccess())
{
til::latch latch{ 1 };
auto finalVal = S_OK;
Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [&]() {
finalVal = _OnNewConnection(connection);
latch.count_down();
});
latch.wait();
return finalVal;
}
try
{
NewTerminalArgs newTerminalArgs;
newTerminalArgs.Commandline(connection.Commandline());
newTerminalArgs.TabTitle(connection.StartingTitle());
// GH #12370: We absolutely cannot allow a defterm connection to
// auto-elevate. Defterm doesn't work for elevated scenarios in the
// first place. If we try accepting the connection, the spawning an
// elevated version of the Terminal with that profile... that's a
// recipe for disaster. We won't ever open up a tab in this window.
newTerminalArgs.Elevate(false);
const auto newPane = _MakePane(newTerminalArgs, nullptr, connection);
newPane->WalkTree([](const auto& pane) {
pane->FinalizeConfigurationGivenDefault();
});
_CreateNewTabFromPane(newPane);
// Request a summon of this window to the foreground
SummonWindowRequested.raise(*this, nullptr);
// TEMPORARY SOLUTION
// If the connection has requested for the window to be maximized,
// manually maximize it here. Ideally, we should be _initializing_
// the session maximized, instead of manually maximizing it after initialization.
// However, because of the current way our defterm handoff works,
// we are unable to get the connection info before the terminal session
// has already started.
// Make sure that there were no other tabs already existing (in
// the case that we are in glomming mode), because we don't want
// to be maximizing other existing sessions that did not ask for it.
if (_tabs.Size() == 1 && connection.ShowWindow() == SW_SHOWMAXIMIZED)
{
RequestSetMaximized(true);
}
return S_OK;
}
CATCH_RETURN()
}
TerminalApp::IPaneContent TerminalPage::_makeSettingsContent() TerminalApp::IPaneContent TerminalPage::_makeSettingsContent()
{ {
if (auto app{ winrt::Windows::UI::Xaml::Application::Current().try_as<winrt::TerminalApp::App>() }) if (auto app{ winrt::Windows::UI::Xaml::Application::Current().try_as<winrt::TerminalApp::App>() })

View File

@ -129,8 +129,8 @@ namespace winrt::TerminalApp::implementation
void RequestSetMaximized(bool newMaximized); void RequestSetMaximized(bool newMaximized);
void SetStartupActions(std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs> actions); void SetStartupActions(std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs> actions);
void SetStartupConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection connection);
void SetInboundListener(bool isEmbedding);
static std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs> ConvertExecuteCommandlineToActions(const Microsoft::Terminal::Settings::Model::ExecuteCommandlineArgs& args); static std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs> ConvertExecuteCommandlineToActions(const Microsoft::Terminal::Settings::Model::ExecuteCommandlineArgs& args);
winrt::TerminalApp::IDialogPresenter DialogPresenter() const; winrt::TerminalApp::IDialogPresenter DialogPresenter() const;
@ -147,9 +147,9 @@ namespace winrt::TerminalApp::implementation
void ShowTerminalWorkingDirectory(); void ShowTerminalWorkingDirectory();
safe_void_coroutine ProcessStartupActions(std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs> actions, safe_void_coroutine ProcessStartupActions(std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs> actions,
const bool initial,
const winrt::hstring cwd = winrt::hstring{}, const winrt::hstring cwd = winrt::hstring{},
const winrt::hstring env = winrt::hstring{}); const winrt::hstring env = winrt::hstring{});
void CreateTabFromConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection connection);
TerminalApp::WindowProperties WindowProperties() const noexcept { return _WindowProperties; }; TerminalApp::WindowProperties WindowProperties() const noexcept { return _WindowProperties; };
@ -257,8 +257,7 @@ namespace winrt::TerminalApp::implementation
StartupState _startupState{ StartupState::NotInitialized }; StartupState _startupState{ StartupState::NotInitialized };
std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs> _startupActions; std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs> _startupActions;
bool _shouldStartInboundListener{ false }; winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _startupConnection{ nullptr };
bool _isEmbeddingInboundListener{ false };
std::shared_ptr<Toast> _windowIdToast{ nullptr }; std::shared_ptr<Toast> _windowIdToast{ nullptr };
std::shared_ptr<Toast> _actionSavedToast{ nullptr }; std::shared_ptr<Toast> _actionSavedToast{ nullptr };
@ -282,8 +281,6 @@ namespace winrt::TerminalApp::implementation
winrt::Windows::Foundation::Point dragOffset{ 0, 0 }; winrt::Windows::Foundation::Point dragOffset{ 0, 0 };
} _stashed; } _stashed;
winrt::Microsoft::Terminal::TerminalConnection::ConptyConnection::NewConnection_revoker _newConnectionRevoker;
safe_void_coroutine _NewTerminalByDrop(const Windows::Foundation::IInspectable&, winrt::Windows::UI::Xaml::DragEventArgs e); safe_void_coroutine _NewTerminalByDrop(const Windows::Foundation::IInspectable&, winrt::Windows::UI::Xaml::DragEventArgs e);
__declspec(noinline) CommandPalette _loadCommandPaletteSlowPath(); __declspec(noinline) CommandPalette _loadCommandPaletteSlowPath();
@ -469,8 +466,6 @@ namespace winrt::TerminalApp::implementation
void _SetNewTabButtonColor(const Windows::UI::Color& color, const Windows::UI::Color& accentColor); void _SetNewTabButtonColor(const Windows::UI::Color& color, const Windows::UI::Color& accentColor);
void _ClearNewTabButtonColor(); void _ClearNewTabButtonColor();
void _StartInboundListener();
safe_void_coroutine _CompleteInitialization(); safe_void_coroutine _CompleteInitialization();
void _FocusActiveControl(IInspectable sender, IInspectable eventArgs); void _FocusActiveControl(IInspectable sender, IInspectable eventArgs);

View File

@ -152,38 +152,23 @@ namespace winrt::TerminalApp::implementation
// instead. // instead.
// * if we have commandline arguments, Pass commandline args into the // * if we have commandline arguments, Pass commandline args into the
// TerminalPage. // TerminalPage.
if (!_initialContentArgs.empty()) if (_startupConnection)
{ {
_root->SetStartupActions(_initialContentArgs); _root->SetStartupConnection(std::move(_startupConnection));
} }
else else if (!_initialContentArgs.empty())
{
_root->SetStartupActions(std::move(_initialContentArgs));
}
else if (const auto& layout = LoadPersistedLayout())
{ {
// layout will only ever be non-null if there were >0 tabs persisted in // layout will only ever be non-null if there were >0 tabs persisted in
// .TabLayout(). We can re-evaluate that as a part of TODO: GH#12633 // .TabLayout(). We can re-evaluate that as a part of TODO: GH#12633
if (const auto& layout = LoadPersistedLayout()) _root->SetStartupActions(wil::to_vector(layout.TabLayout()));
{
std::vector<Settings::Model::ActionAndArgs> actions;
for (const auto& a : layout.TabLayout())
{
actions.emplace_back(a);
}
_root->SetStartupActions(actions);
}
else if (_appArgs)
{
_root->SetStartupActions(_appArgs->ParsedArgs().GetStartupActions());
}
} }
else if (_appArgs)
// Check if we were started as a COM server for inbound connections of console sessions
// coming out of the operating system default application feature. If so,
// tell TerminalPage to start the listener as we have to make sure it has the chance
// to register a handler to hear about the requests first and is all ready to receive
// them before the COM server registers itself. Otherwise, the request might come
// in and be routed to an event with no handlers or a non-ready Page.
if (_appArgs && _appArgs->ParsedArgs().IsHandoffListener())
{ {
_root->SetInboundListener(true); _root->SetStartupActions(_appArgs->ParsedArgs().GetStartupActions());
} }
return _root->Initialize(hwnd); return _root->Initialize(hwnd);
@ -1054,6 +1039,7 @@ namespace winrt::TerminalApp::implementation
int32_t TerminalWindow::SetStartupCommandline(TerminalApp::CommandlineArgs args) int32_t TerminalWindow::SetStartupCommandline(TerminalApp::CommandlineArgs args)
{ {
_appArgs = winrt::get_self<CommandlineArgs>(args); _appArgs = winrt::get_self<CommandlineArgs>(args);
_startupConnection = args.Connection();
auto& parsedArgs = _appArgs->ParsedArgs(); auto& parsedArgs = _appArgs->ParsedArgs();
_WindowProperties->SetInitialCwd(_appArgs->CurrentDirectory()); _WindowProperties->SetInitialCwd(_appArgs->CurrentDirectory());
@ -1114,13 +1100,16 @@ namespace winrt::TerminalApp::implementation
auto& parsedArgs = _appArgs->ParsedArgs(); auto& parsedArgs = _appArgs->ParsedArgs();
auto& actions = parsedArgs.GetStartupActions(); auto& actions = parsedArgs.GetStartupActions();
_root->ProcessStartupActions(actions, false, _appArgs->CurrentDirectory(), _appArgs->CurrentEnvironment()); if (auto conn = args.Connection())
if (parsedArgs.IsHandoffListener())
{ {
_root->SetInboundListener(true); _root->CreateTabFromConnection(std::move(conn));
}
else if (!actions.empty())
{
_root->ProcessStartupActions(actions, _appArgs->CurrentDirectory(), _appArgs->CurrentEnvironment());
} }
} }
// Return the result of parsing with commandline, though it may or may not be used. // Return the result of parsing with commandline, though it may or may not be used.
return _appArgs->ExitCode(); return _appArgs->ExitCode();
} }

View File

@ -169,6 +169,7 @@ namespace winrt::TerminalApp::implementation
winrt::com_ptr<TerminalPage> _root{ nullptr }; winrt::com_ptr<TerminalPage> _root{ nullptr };
wil::com_ptr<CommandlineArgs> _appArgs{ nullptr }; wil::com_ptr<CommandlineArgs> _appArgs{ nullptr };
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _startupConnection{ nullptr };
bool _hasCommandLineArguments{ false }; bool _hasCommandLineArguments{ false };
bool _gotSettingsStartupActions{ false }; bool _gotSettingsStartupActions{ false };
std::vector<winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs> _settingsStartupArgs{}; std::vector<winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs> _settingsStartupArgs{};

View File

@ -39,7 +39,7 @@ try
ComPtr<IUnknown> unk; ComPtr<IUnknown> unk;
RETURN_IF_FAILED(classFactory.As(&unk)); RETURN_IF_FAILED(classFactory.As(&unk));
RETURN_IF_FAILED(CoRegisterClassObject(__uuidof(CTerminalHandoff), unk.Get(), CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE, &g_cTerminalHandoffRegistration)); RETURN_IF_FAILED(CoRegisterClassObject(__uuidof(CTerminalHandoff), unk.Get(), CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &g_cTerminalHandoffRegistration));
return S_OK; return S_OK;
} }
@ -83,41 +83,19 @@ HRESULT CTerminalHandoff::s_StopListening()
// from the registered handler event function. // from the registered handler event function.
HRESULT CTerminalHandoff::EstablishPtyHandoff(HANDLE* in, HANDLE* out, HANDLE signal, HANDLE reference, HANDLE server, HANDLE client, const TERMINAL_STARTUP_INFO* startupInfo) HRESULT CTerminalHandoff::EstablishPtyHandoff(HANDLE* in, HANDLE* out, HANDLE signal, HANDLE reference, HANDLE server, HANDLE client, const TERMINAL_STARTUP_INFO* startupInfo)
{ {
try // Report an error if no one registered a handoff function before calling this.
{ RETURN_HR_IF_NULL(E_NOT_VALID_STATE, _pfnHandoff);
// Because we are REGCLS_SINGLEUSE... we need to `CoRevokeClassObject` after we handle this ONE call.
// COM does not automatically clean that up for us. We must do it.
LOG_IF_FAILED(s_StopListening());
// Report an error if no one registered a handoff function before calling this. // Call registered handler from when we started listening.
THROW_HR_IF_NULL(E_NOT_VALID_STATE, _pfnHandoff); RETURN_IF_FAILED(_pfnHandoff(in, out, signal, reference, server, client, startupInfo));
// Call registered handler from when we started listening.
THROW_IF_FAILED(_pfnHandoff(in, out, signal, reference, server, client, startupInfo));
#pragma warning(suppress : 26477) #pragma warning(suppress : 26477)
TraceLoggingWrite( TraceLoggingWrite(
g_hTerminalConnectionProvider, g_hTerminalConnectionProvider,
"ReceiveTerminalHandoff_Success", "ReceiveTerminalHandoff_Success",
TraceLoggingDescription("successfully received a terminal handoff"), TraceLoggingDescription("successfully received a terminal handoff"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
return S_OK; return S_OK;
}
catch (...)
{
const auto hr = wil::ResultFromCaughtException();
#pragma warning(suppress : 26477)
TraceLoggingWrite(
g_hTerminalConnectionProvider,
"ReceiveTerminalHandoff_Failed",
TraceLoggingDescription("failed while receiving a terminal handoff"),
TraceLoggingHResult(hr),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
return hr;
}
} }

View File

@ -77,6 +77,11 @@ AppHost::AppHost(WindowEmperor* manager, const winrt::TerminalApp::AppLogic& log
_windowCallbacks.ShouldExitFullscreen = _window->ShouldExitFullscreen({ &_windowLogic, &winrt::TerminalApp::TerminalWindow::RequestExitFullscreen }); _windowCallbacks.ShouldExitFullscreen = _window->ShouldExitFullscreen({ &_windowLogic, &winrt::TerminalApp::TerminalWindow::RequestExitFullscreen });
_window->MakeWindow(); _window->MakeWindow();
// Does window creation mean the window was activated (WM_ACTIVATE)? No.
// But it simplifies `WindowEmperor::_mostRecentWindow()`, because now the creation of a
// new window marks it as the most recent one immediately, even before it becomes active.
QueryPerformanceCounter(&_lastActivatedTime);
} }
bool AppHost::OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down) bool AppHost::OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down)

View File

@ -1242,9 +1242,12 @@ void IslandWindow::_SetIsFullscreen(const bool fullscreenEnabled)
// - <none> // - <none>
void IslandWindow::SummonWindow(winrt::TerminalApp::SummonWindowBehavior args) void IslandWindow::SummonWindow(winrt::TerminalApp::SummonWindowBehavior args)
{ {
auto actualDropdownDuration = args.DropdownDuration(); const auto toggleVisibility = args ? args.ToggleVisibility() : false;
const auto toMonitor = args ? args.ToMonitor() : winrt::TerminalApp::MonitorBehavior::InPlace;
auto dropdownDuration = args ? args.DropdownDuration() : 0;
// If the user requested an animation, let's check if animations are enabled in the OS. // If the user requested an animation, let's check if animations are enabled in the OS.
if (actualDropdownDuration > 0) if (dropdownDuration > 0)
{ {
auto animationsEnabled = TRUE; auto animationsEnabled = TRUE;
SystemParametersInfoW(SPI_GETCLIENTAREAANIMATION, 0, &animationsEnabled, 0); SystemParametersInfoW(SPI_GETCLIENTAREAANIMATION, 0, &animationsEnabled, 0);
@ -1258,7 +1261,7 @@ void IslandWindow::SummonWindow(winrt::TerminalApp::SummonWindowBehavior args)
// _globalActivateWindow/_globalDismissWindow might do if they think // _globalActivateWindow/_globalDismissWindow might do if they think
// there should be an animation (like making the window appear with // there should be an animation (like making the window appear with
// SetWindowPlacement rather than ShowWindow) // SetWindowPlacement rather than ShowWindow)
actualDropdownDuration = 0; dropdownDuration = 0;
} }
} }
@ -1269,33 +1272,33 @@ void IslandWindow::SummonWindow(winrt::TerminalApp::SummonWindowBehavior args)
// - activate the window // - activate the window
// - else // - else
// - dismiss the window // - dismiss the window
if (args.ToggleVisibility() && GetForegroundWindow() == _window.get()) if (toggleVisibility && GetForegroundWindow() == _window.get())
{ {
auto handled = false; auto handled = false;
// They want to toggle the window when it is the FG window, and we are // They want to toggle the window when it is the FG window, and we are
// the FG window. However, if we're on a different monitor than the // the FG window. However, if we're on a different monitor than the
// mouse, then we should move to that monitor instead of dismissing. // mouse, then we should move to that monitor instead of dismissing.
if (args.ToMonitor() == winrt::TerminalApp::MonitorBehavior::ToMouse) if (toMonitor == winrt::TerminalApp::MonitorBehavior::ToMouse)
{ {
const til::rect cursorMonitorRect{ _getMonitorForCursor().rcMonitor }; const til::rect cursorMonitorRect{ _getMonitorForCursor().rcMonitor };
const til::rect currentMonitorRect{ _getMonitorForWindow(GetHandle()).rcMonitor }; const til::rect currentMonitorRect{ _getMonitorForWindow(GetHandle()).rcMonitor };
if (cursorMonitorRect != currentMonitorRect) if (cursorMonitorRect != currentMonitorRect)
{ {
// We're not on the same monitor as the mouse. Go to that monitor. // We're not on the same monitor as the mouse. Go to that monitor.
_globalActivateWindow(actualDropdownDuration, args.ToMonitor()); _globalActivateWindow(dropdownDuration, toMonitor);
handled = true; handled = true;
} }
} }
if (!handled) if (!handled)
{ {
_globalDismissWindow(actualDropdownDuration); _globalDismissWindow(dropdownDuration);
} }
} }
else else
{ {
_globalActivateWindow(actualDropdownDuration, args.ToMonitor()); _globalActivateWindow(dropdownDuration, toMonitor);
} }
} }

View File

@ -345,20 +345,31 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow)
for (const auto layout : layouts) for (const auto layout : layouts)
{ {
hstring args[] = { L"wt", L"-w", L"new", L"-s", winrt::to_hstring(startIdx) }; hstring args[] = { L"wt", L"-w", L"new", L"-s", winrt::to_hstring(startIdx) };
_dispatchCommandline({ args, cwd, showCmd, env }); _dispatchCommandlineCommon(args, cwd, env, showCmd);
startIdx += 1; startIdx += 1;
} }
} }
// Create another window if needed: There aren't any yet, or we got an explicit command line.
const auto args = commandlineToArgArray(GetCommandLineW()); const auto args = commandlineToArgArray(GetCommandLineW());
if (_windows.empty() || args.size() != 1)
{
_dispatchCommandline({ args, cwd, showCmd, env });
}
// If we created no windows, e.g. because the args are "/?" we can just exit now. if (args.size() == 2 && args[1] == L"-Embedding")
_postQuitMessageIfNeeded(); {
// We were launched for ConPTY handoff. We have no windows and also don't want to exit.
//
// TODO: Here we could start a timer and exit after, say, 5 seconds
// if no windows are created. But that's a minor concern.
}
else
{
// Create another window if needed: There aren't any yet, OR we got an explicit command line.
if (_windows.empty() || args.size() != 1)
{
_dispatchCommandlineCommon(args, cwd, env, showCmd);
}
// If we created no windows, e.g. because the args are "/?" we can just exit now.
_postQuitMessageIfNeeded();
}
} }
// ALWAYS change the _real_ CWD of the Terminal to system32, // ALWAYS change the _real_ CWD of the Terminal to system32,
@ -386,6 +397,19 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow)
} }
} }
{
TerminalConnection::ConptyConnection::NewConnection([this](TerminalConnection::ConptyConnection conn) {
TerminalApp::CommandlineArgs args;
args.ShowWindowCommand(conn.ShowWindow());
args.Connection(std::move(conn));
_dispatchCommandline(std::move(args));
_summonWindow(SummonWindowSelectionArgs{
.SummonBehavior = nullptr,
});
});
TerminalConnection::ConptyConnection::StartInboundListener();
}
// Main message loop. It pumps all windows. // Main message loop. It pumps all windows.
bool loggedInteraction = false; bool loggedInteraction = false;
MSG msg{}; MSG msg{};
@ -588,6 +612,16 @@ void WindowEmperor::_dispatchCommandline(winrt::TerminalApp::CommandlineArgs arg
} }
} }
void WindowEmperor::_dispatchCommandlineCommon(winrt::array_view<const winrt::hstring> args, std::wstring_view currentDirectory, std::wstring_view envString, uint32_t showWindowCommand)
{
winrt::TerminalApp::CommandlineArgs c;
c.Commandline(args);
c.CurrentDirectory(currentDirectory);
c.CurrentEnvironment(envString);
c.ShowWindowCommand(showWindowCommand);
_dispatchCommandline(std::move(c));
}
// This is an implementation-detail of _dispatchCommandline(). // This is an implementation-detail of _dispatchCommandline().
safe_void_coroutine WindowEmperor::_dispatchCommandlineCurrentDesktop(winrt::TerminalApp::CommandlineArgs args) safe_void_coroutine WindowEmperor::_dispatchCommandlineCurrentDesktop(winrt::TerminalApp::CommandlineArgs args)
{ {
@ -896,10 +930,8 @@ LRESULT WindowEmperor::_messageHandler(HWND window, UINT const message, WPARAM c
{ {
const auto handoff = deserializeHandoffPayload(static_cast<const uint8_t*>(cds->lpData), static_cast<const uint8_t*>(cds->lpData) + cds->cbData); const auto handoff = deserializeHandoffPayload(static_cast<const uint8_t*>(cds->lpData), static_cast<const uint8_t*>(cds->lpData) + cds->cbData);
const winrt::hstring args{ handoff.args }; const winrt::hstring args{ handoff.args };
const winrt::hstring env{ handoff.env };
const winrt::hstring cwd{ handoff.cwd };
const auto argv = commandlineToArgArray(args.c_str()); const auto argv = commandlineToArgArray(args.c_str());
_dispatchCommandline({ argv, cwd, gsl::narrow_cast<uint32_t>(handoff.show), env }); _dispatchCommandlineCommon(argv, handoff.cwd, handoff.env, handoff.show);
} }
return 0; return 0;
case WM_HOTKEY: case WM_HOTKEY:
@ -1198,7 +1230,7 @@ void WindowEmperor::_hotkeyPressed(const long hotkeyIndex)
const wil::unique_environstrings_ptr envMem{ GetEnvironmentStringsW() }; const wil::unique_environstrings_ptr envMem{ GetEnvironmentStringsW() };
const auto env = stringFromDoubleNullTerminated(envMem.get()); const auto env = stringFromDoubleNullTerminated(envMem.get());
const auto cwd = wil::GetCurrentDirectoryW<std::wstring>(); const auto cwd = wil::GetCurrentDirectoryW<std::wstring>();
_dispatchCommandline({ argv, cwd, SW_SHOWDEFAULT, std::move(env) }); _dispatchCommandlineCommon(argv, cwd, env, SW_SHOWDEFAULT);
} }
void WindowEmperor::_registerHotKey(const int index, const winrt::Microsoft::Terminal::Control::KeyChord& hotkey) noexcept void WindowEmperor::_registerHotKey(const int index, const winrt::Microsoft::Terminal::Control::KeyChord& hotkey) noexcept

View File

@ -52,10 +52,10 @@ private:
void _summonAllWindows() const; void _summonAllWindows() const;
void _dispatchSpecialKey(const MSG& msg) const; void _dispatchSpecialKey(const MSG& msg) const;
void _dispatchCommandline(winrt::TerminalApp::CommandlineArgs args); void _dispatchCommandline(winrt::TerminalApp::CommandlineArgs args);
void _dispatchCommandlineCommon(winrt::array_view<const winrt::hstring> args, std::wstring_view currentDirectory, std::wstring_view envString, uint32_t showWindowCommand);
safe_void_coroutine _dispatchCommandlineCurrentDesktop(winrt::TerminalApp::CommandlineArgs args); safe_void_coroutine _dispatchCommandlineCurrentDesktop(winrt::TerminalApp::CommandlineArgs args);
LRESULT _messageHandler(HWND window, UINT message, WPARAM wParam, LPARAM lParam) noexcept; LRESULT _messageHandler(HWND window, UINT message, WPARAM wParam, LPARAM lParam) noexcept;
void _createMessageWindow(const wchar_t* className); void _createMessageWindow(const wchar_t* className);
bool _shouldSkipClosingWindows() const;
void _postQuitMessageIfNeeded() const; void _postQuitMessageIfNeeded() const;
safe_void_coroutine _showMessageBox(winrt::hstring message, bool error); safe_void_coroutine _showMessageBox(winrt::hstring message, bool error);
void _notificationAreaMenuRequested(WPARAM wParam); void _notificationAreaMenuRequested(WPARAM wParam);

View File

@ -67,8 +67,9 @@ Abstract:
#include <winrt/Windows.UI.Composition.h> #include <winrt/Windows.UI.Composition.h>
#include <winrt/TerminalApp.h> #include <winrt/TerminalApp.h>
#include <winrt/Microsoft.Terminal.Settings.Model.h>
#include <winrt/Microsoft.Terminal.Control.h> #include <winrt/Microsoft.Terminal.Control.h>
#include <winrt/Microsoft.Terminal.Settings.Model.h>
#include <winrt/Microsoft.Terminal.TerminalConnection.h>
#include <winrt/Microsoft.Terminal.UI.h> #include <winrt/Microsoft.Terminal.UI.h>
#include <wil/cppwinrt.h> #include <wil/cppwinrt.h>