diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index 57e2dc4196f..be3c28f7bab 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -107,6 +107,7 @@ void Pane::_setupControlEvents() _controlEvents._ConnectionStateChanged = _control.ConnectionStateChanged(winrt::auto_revoke, { this, &Pane::_ControlConnectionStateChangedHandler }); _controlEvents._WarningBell = _control.WarningBell(winrt::auto_revoke, { this, &Pane::_ControlWarningBellHandler }); _controlEvents._CloseTerminalRequested = _control.CloseTerminalRequested(winrt::auto_revoke, { this, &Pane::_CloseTerminalRequestedHandler }); + _controlEvents._RestartTerminalRequested = _control.RestartTerminalRequested(winrt::auto_revoke, { this, &Pane::_RestartTerminalRequestedHandler }); } void Pane::_removeControlEvents() { @@ -1103,6 +1104,16 @@ void Pane::_CloseTerminalRequestedHandler(const winrt::Windows::Foundation::IIns Close(); } +void Pane::_RestartTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/, + const winrt::Windows::Foundation::IInspectable& /*args*/) +{ + if (!_IsLeaf()) + { + return; + } + _RestartTerminalRequestedHandlers(shared_from_this()); +} + winrt::fire_and_forget Pane::_playBellSound(winrt::Windows::Foundation::Uri uri) { auto weakThis{ weak_from_this() }; diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h index 001f8d0a16c..6fce797d77c 100644 --- a/src/cascadia/TerminalApp/Pane.h +++ b/src/cascadia/TerminalApp/Pane.h @@ -212,6 +212,7 @@ class Pane : public std::enable_shared_from_this WINRT_CALLBACK(LostFocus, winrt::delegate>); WINRT_CALLBACK(PaneRaiseBell, winrt::Windows::Foundation::EventHandler); WINRT_CALLBACK(Detached, winrt::delegate>); + WINRT_CALLBACK(RestartTerminalRequested, winrt::delegate>); private: struct PanePoint; @@ -249,6 +250,7 @@ class Pane : public std::enable_shared_from_this winrt::Microsoft::Terminal::Control::TermControl::ConnectionStateChanged_revoker _ConnectionStateChanged; winrt::Microsoft::Terminal::Control::TermControl::WarningBell_revoker _WarningBell; winrt::Microsoft::Terminal::Control::TermControl::CloseTerminalRequested_revoker _CloseTerminalRequested; + winrt::Microsoft::Terminal::Control::TermControl::RestartTerminalRequested_revoker _RestartTerminalRequested; } _controlEvents; void _setupControlEvents(); void _removeControlEvents(); @@ -306,6 +308,7 @@ class Pane : public std::enable_shared_from_this void _ControlLostFocusHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::RoutedEventArgs& e); void _CloseTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& /*args*/); + void _RestartTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& /*args*/); std::pair _CalcChildrenSizes(const float fullSize) const; SnapChildrenSizeResult _CalcSnappedChildrenSizes(const bool widthOrHeight, const float fullSize) const; diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index ebd0a180a4b..c4561e9364c 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -1165,7 +1165,8 @@ namespace winrt::TerminalApp::implementation // Return value: // - the desired connection TerminalConnection::ITerminalConnection TerminalPage::_CreateConnectionFromSettings(Profile profile, - TerminalSettings settings) + TerminalSettings settings, + const bool inheritCursor) { TerminalConnection::ITerminalConnection connection{ nullptr }; @@ -1248,6 +1249,11 @@ namespace winrt::TerminalApp::implementation valueSet.Insert(L"reloadEnvironmentVariables", Windows::Foundation::PropertyValue::CreateBoolean(_settings.GlobalSettings().ReloadEnvironmentVariables())); + if (inheritCursor) + { + valueSet.Insert(L"inheritCursor", Windows::Foundation::PropertyValue::CreateBoolean(true)); + } + conhostConn.Initialize(valueSet); sessionGuid = conhostConn.Guid(); @@ -1267,6 +1273,44 @@ namespace winrt::TerminalApp::implementation return connection; } + TerminalConnection::ITerminalConnection TerminalPage::_duplicateConnectionForRestart(std::shared_ptr pane) + { + const auto& control{ pane->GetTerminalControl() }; + if (control == nullptr) + { + return nullptr; + } + const auto& connection = control.Connection(); + auto profile{ pane->GetProfile() }; + + TerminalSettingsCreateResult controlSettings{ nullptr }; + + if (profile) + { + // TODO GH#5047 If we cache the NewTerminalArgs, we no longer need to do this. + profile = GetClosestProfileForDuplicationOfProfile(profile); + controlSettings = TerminalSettings::CreateWithProfile(_settings, profile, *_bindings); + + // Replace the Starting directory with the CWD, if given + const auto workingDirectory = control.WorkingDirectory(); + const auto validWorkingDirectory = !workingDirectory.empty(); + if (validWorkingDirectory) + { + controlSettings.DefaultSettings().StartingDirectory(workingDirectory); + } + + // To facilitate restarting defterm connections: grab the original + // commandline out of the connection and shove that back into the + // settings. + if (const auto& conpty{ connection.try_as() }) + { + controlSettings.DefaultSettings().Commandline(conpty.Commandline()); + } + } + + return _CreateConnectionFromSettings(profile, controlSettings.DefaultSettings(), true); + } + // Method Description: // - Called when the settings button is clicked. Launches a background // thread to open the settings file in the default JSON editor. @@ -2893,7 +2937,7 @@ namespace winrt::TerminalApp::implementation return nullptr; } - auto connection = existingConnection ? existingConnection : _CreateConnectionFromSettings(profile, controlSettings.DefaultSettings()); + auto connection = existingConnection ? existingConnection : _CreateConnectionFromSettings(profile, controlSettings.DefaultSettings(), false); if (existingConnection) { connection.Resize(controlSettings.DefaultSettings().InitialRows(), controlSettings.DefaultSettings().InitialCols()); @@ -2935,6 +2979,15 @@ namespace winrt::TerminalApp::implementation original->SetActive(); } + resultPane->RestartTerminalRequested([this](const auto& pane) { + auto connection = _duplicateConnectionForRestart(pane); + if (connection) + { + pane->GetTerminalControl().Connection(connection); + connection.Start(); + } + }); + return resultPane; } diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 1d8e616538d..da694f50ed5 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -293,7 +293,8 @@ namespace winrt::TerminalApp::implementation void _OpenNewTabDropdown(); HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr); void _CreateNewTabFromPane(std::shared_ptr pane, uint32_t insertPosition = -1); - winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _CreateConnectionFromSettings(Microsoft::Terminal::Settings::Model::Profile profile, Microsoft::Terminal::Settings::Model::TerminalSettings settings); + winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _CreateConnectionFromSettings(Microsoft::Terminal::Settings::Model::Profile profile, Microsoft::Terminal::Settings::Model::TerminalSettings settings, const bool inheritCursor); + winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _duplicateConnectionForRestart(std::shared_ptr pane); winrt::fire_and_forget _OpenNewWindow(const Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs); diff --git a/src/cascadia/TerminalConnection/ConnectionStateHolder.h b/src/cascadia/TerminalConnection/ConnectionStateHolder.h index f037b6e9c9c..947e16788f3 100644 --- a/src/cascadia/TerminalConnection/ConnectionStateHolder.h +++ b/src/cascadia/TerminalConnection/ConnectionStateHolder.h @@ -86,14 +86,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation return _isStateOneOf(ConnectionState::Connected); } - void _resetConnectionState() - { - { - std::lock_guard stateLock{ _stateMutex }; - _connectionState = ConnectionState::NotConnected; - } - } - private: std::atomic _connectionState{ ConnectionState::NotConnected }; mutable std::mutex _stateMutex; diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index 9e59098985d..7bb1b399c7f 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -282,6 +282,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation { _passthroughMode = winrt::unbox_value_or(settings.TryLookup(L"passthroughMode").try_as(), _passthroughMode); } + _inheritCursor = winrt::unbox_value_or(settings.TryLookup(L"inheritCursor").try_as(), _inheritCursor); _reloadEnvironmentVariables = winrt::unbox_value_or(settings.TryLookup(L"reloadEnvironmentVariables").try_as(), _reloadEnvironmentVariables); _profileGuid = winrt::unbox_value_or(settings.TryLookup(L"profileGuid").try_as(), _profileGuid); @@ -316,13 +317,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation void ConptyConnection::Start() try { - bool usingExistingBuffer = false; - if (_isStateAtOrBeyond(ConnectionState::Closed)) - { - _resetConnectionState(); - usingExistingBuffer = true; - } - _transitionToState(ConnectionState::Connecting); const til::size dimensions{ gsl::narrow(_cols), gsl::narrow(_rows) }; @@ -338,7 +332,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation // PseudoConsole sends a clear screen VT code which our renderer // interprets into making all the previous lines be outside the // current viewport. - if (usingExistingBuffer) + if (_inheritCursor) { flags |= PSEUDOCONSOLE_INHERIT_CURSOR; } diff --git a/src/cascadia/TerminalConnection/ConptyConnection.h b/src/cascadia/TerminalConnection/ConptyConnection.h index 8ac589d6462..747434f6dba 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.h +++ b/src/cascadia/TerminalConnection/ConptyConnection.h @@ -90,6 +90,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation std::wstring _u16Str{}; std::array _buffer{}; bool _passthroughMode{}; + bool _inheritCursor{ false }; bool _reloadEnvironmentVariables{}; guid _profileGuid{}; diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index b2777ed580e..9d27d916263 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -78,7 +78,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation ControlCore::ControlCore(Control::IControlSettings settings, Control::IControlAppearance unfocusedAppearance, TerminalConnection::ITerminalConnection connection) : - _connection{ connection }, _desiredFont{ DEFAULT_FONT_FACE, 0, DEFAULT_FONT_WEIGHT, DEFAULT_FONT_SIZE, CP_UTF8 }, _actualFont{ DEFAULT_FONT_FACE, 0, DEFAULT_FONT_WEIGHT, { 0, DEFAULT_FONT_SIZE }, CP_UTF8, false } { @@ -86,13 +85,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation _terminal = std::make_shared<::Microsoft::Terminal::Core::Terminal>(); - // Subscribe to the connection's disconnected event and call our connection closed handlers. - _connectionStateChangedRevoker = _connection.StateChanged(winrt::auto_revoke, [this](auto&& /*s*/, auto&& /*v*/) { - _ConnectionStateChangedHandlers(*this, nullptr); - }); - - // This event is explicitly revoked in the destructor: does not need weak_ref - _connectionOutputEventToken = _connection.TerminalOutput({ this, &ControlCore::_connectionOutputHandler }); + Connection(connection); _terminal->SetWriteInputCallback([this](std::wstring_view wstr) { _sendInputToConnection(wstr); @@ -256,6 +249,62 @@ namespace winrt::Microsoft::Terminal::Control::implementation _AttachedHandlers(*this, nullptr); } + TerminalConnection::ITerminalConnection ControlCore::Connection() + { + return _connection; + } + + // Method Description: + // - Setup our event handlers for this connection. If we've currently got a + // connection, then this'll revoke the existing connection's handlers. + // - This will not call Start on the incoming connection. The caller should do that. + // - If the caller doesn't want the old connection to be closed, then they + // should grab a reference to it before calling this (so that it doesn't + // destruct, and close) during this call. + void ControlCore::Connection(const TerminalConnection::ITerminalConnection& newConnection) + { + auto oldState = ConnectionState(); // rely on ControlCore's automatic null handling + // revoke ALL old handlers immediately + + _connectionOutputEventRevoker.revoke(); + _connectionStateChangedRevoker.revoke(); + + _connection = newConnection; + if (_connection) + { + // Subscribe to the connection's disconnected event and call our connection closed handlers. + _connectionStateChangedRevoker = newConnection.StateChanged(winrt::auto_revoke, [this](auto&& /*s*/, auto&& /*v*/) { + _ConnectionStateChangedHandlers(*this, nullptr); + }); + + // Get our current size in rows/cols, and hook them up to + // this connection too. + { + const auto vp = _terminal->GetViewport(); + const auto width = vp.Width(); + const auto height = vp.Height(); + + newConnection.Resize(height, width); + } + // Window owner too. + if (auto conpty{ newConnection.try_as() }) + { + conpty.ReparentWindow(_owningHwnd); + } + + // This event is explicitly revoked in the destructor: does not need weak_ref + _connectionOutputEventRevoker = _connection.TerminalOutput(winrt::auto_revoke, { this, &ControlCore::_connectionOutputHandler }); + } + + // Fire off a connection state changed notification, to let our hosting + // app know that we're in a different state now. + if (oldState != ConnectionState()) + { // rely on the null handling again + // send the notification + _ConnectionStateChangedHandlers(*this, nullptr); + } + } + bool ControlCore::Initialize(const float actualWidth, const float actualHeight, const float compositionScale) @@ -427,8 +476,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation if (ch == Enter) { - _connection.Close(); - _connection.Start(); + // Ask the hosting application to give us a new connection. + _RestartTerminalRequestedHandlers(*this, nullptr); return true; } } @@ -1541,7 +1590,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation _midiAudio.BeginSkip(); // Stop accepting new output and state changes before we disconnect everything. - _connection.TerminalOutput(_connectionOutputEventToken); + _connectionOutputEventRevoker.revoke(); _connectionStateChangedRevoker.revoke(); _connection.Close(); } diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index ec333d04d06..a493f07e090 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -224,6 +224,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation uint64_t OwningHwnd(); void OwningHwnd(uint64_t owner); + TerminalConnection::ITerminalConnection Connection(); + void Connection(const TerminalConnection::ITerminalConnection& connection); + void AnchorContextMenu(til::point viewportRelativeCharacterPosition); bool ShouldShowSelectCommand(); @@ -257,6 +260,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation TYPED_EVENT(UpdateSelectionMarkers, IInspectable, Control::UpdateSelectionMarkersEventArgs); TYPED_EVENT(OpenHyperlink, IInspectable, Control::OpenHyperlinkEventArgs); TYPED_EVENT(CloseTerminalRequested, IInspectable, IInspectable); + TYPED_EVENT(RestartTerminalRequested, IInspectable, IInspectable); TYPED_EVENT(Attached, IInspectable, IInspectable); // clang-format on @@ -266,7 +270,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation bool _closing{ false }; TerminalConnection::ITerminalConnection _connection{ nullptr }; - event_token _connectionOutputEventToken; + TerminalConnection::ITerminalConnection::TerminalOutput_revoker _connectionOutputEventRevoker; TerminalConnection::ITerminalConnection::StateChanged_revoker _connectionStateChangedRevoker; winrt::com_ptr _settings{ nullptr }; diff --git a/src/cascadia/TerminalControl/ControlCore.idl b/src/cascadia/TerminalControl/ControlCore.idl index 10ab1b11d3e..3b6fe071600 100644 --- a/src/cascadia/TerminalControl/ControlCore.idl +++ b/src/cascadia/TerminalControl/ControlCore.idl @@ -71,6 +71,8 @@ namespace Microsoft.Terminal.Control void UpdateSettings(IControlSettings settings, IControlAppearance appearance); void ApplyAppearance(Boolean focused); + Microsoft.Terminal.TerminalConnection.ITerminalConnection Connection; + IControlSettings Settings { get; }; IControlAppearance FocusedAppearance { get; }; IControlAppearance UnfocusedAppearance { get; }; @@ -170,6 +172,7 @@ namespace Microsoft.Terminal.Control event Windows.Foundation.TypedEventHandler UpdateSelectionMarkers; event Windows.Foundation.TypedEventHandler OpenHyperlink; event Windows.Foundation.TypedEventHandler CloseTerminalRequested; + event Windows.Foundation.TypedEventHandler RestartTerminalRequested; event Windows.Foundation.TypedEventHandler Attached; }; diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 128d016a785..2c615be22f1 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -104,6 +104,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation _revokers.ConnectionStateChanged = _core.ConnectionStateChanged(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleConnectionStateChanged }); _revokers.ShowWindowChanged = _core.ShowWindowChanged(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleShowWindowChanged }); _revokers.CloseTerminalRequested = _core.CloseTerminalRequested(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleCloseTerminalRequested }); + _revokers.RestartTerminalRequested = _core.RestartTerminalRequested(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleRestartTerminalRequested }); _revokers.PasteFromClipboard = _interactivity.PasteFromClipboard(winrt::auto_revoke, { get_weak(), &TermControl::_bubblePasteFromClipboard }); @@ -262,6 +263,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation return _interactivity.Id(); } + TerminalConnection::ITerminalConnection TermControl::Connection() + { + return _core.Connection(); + } + void TermControl::Connection(const TerminalConnection::ITerminalConnection& newConnection) + { + _core.Connection(newConnection); + } + void TermControl::_throttledUpdateScrollbar(const ScrollBarUpdate& update) { // Assumptions: diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index abebe6c94e1..40586d24c43 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -145,6 +145,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation void Detach(); + TerminalConnection::ITerminalConnection Connection(); + void Connection(const TerminalConnection::ITerminalConnection& connection); + WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); // -------------------------------- WinRT Events --------------------------------- // clang-format off @@ -160,6 +163,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation BUBBLED_FORWARDED_TYPED_EVENT(ConnectionStateChanged, IInspectable, IInspectable); BUBBLED_FORWARDED_TYPED_EVENT(ShowWindowChanged, IInspectable, Control::ShowWindowArgs); BUBBLED_FORWARDED_TYPED_EVENT(CloseTerminalRequested, IInspectable, IInspectable); + BUBBLED_FORWARDED_TYPED_EVENT(RestartTerminalRequested, IInspectable, IInspectable); BUBBLED_FORWARDED_TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs); @@ -371,6 +375,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation Control::ControlCore::ConnectionStateChanged_revoker ConnectionStateChanged; Control::ControlCore::ShowWindowChanged_revoker ShowWindowChanged; Control::ControlCore::CloseTerminalRequested_revoker CloseTerminalRequested; + Control::ControlCore::RestartTerminalRequested_revoker RestartTerminalRequested; // These are set up in _InitializeTerminal Control::ControlCore::RendererWarning_revoker RendererWarning; Control::ControlCore::SwapChainChanged_revoker SwapChainChanged; diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl index b622db628e9..5896d8d6aee 100644 --- a/src/cascadia/TerminalControl/TermControl.idl +++ b/src/cascadia/TerminalControl/TermControl.idl @@ -34,6 +34,8 @@ namespace Microsoft.Terminal.Control void UpdateControlSettings(IControlSettings settings); void UpdateControlSettings(IControlSettings settings, IControlAppearance unfocusedAppearance); + Microsoft.Terminal.TerminalConnection.ITerminalConnection Connection; + UInt64 ContentId{ get; }; Microsoft.Terminal.Control.IControlSettings Settings { get; }; @@ -63,6 +65,7 @@ namespace Microsoft.Terminal.Control event Windows.Foundation.TypedEventHandler ShowWindowChanged; event Windows.Foundation.TypedEventHandler CloseTerminalRequested; + event Windows.Foundation.TypedEventHandler RestartTerminalRequested; Boolean CopySelectionToClipboard(Boolean singleLine, Windows.Foundation.IReference formats); void PasteTextFromClipboard();