diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index e98de6b9957..bb9d4df8a6d 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -3742,7 +3742,13 @@ namespace winrt::TerminalApp::implementation // for nulls if (const auto& connection{ _duplicateConnectionForRestart(paneContent) }) { - paneContent.GetTermControl().Connection(connection); + // Reset the terminal's VT state before attaching the new connection. + // The previous client may have left dirty modes (e.g., bracketed + // paste, mouse tracking, alternate buffer, kitty keyboard) that + // would corrupt input/output for the new shell process. + const auto& termControl = paneContent.GetTermControl(); + termControl.ResetConnection(); + termControl.Connection(connection); connection.Start(); } } diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 86747d0f8ff..41ed85ad84e 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -355,6 +355,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation } } + void ControlCore::ResetConnection() + { + const auto lock = _terminal->LockForWriting(); + _terminal->ResetConnection(); + } + bool ControlCore::Initialize(const float actualWidth, const float actualHeight, const float compositionScale) diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 690f6fb465b..99b4f801b62 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -257,6 +257,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation TerminalConnection::ITerminalConnection Connection(); void Connection(const TerminalConnection::ITerminalConnection& connection); + void ResetConnection(); void AnchorContextMenu(til::point viewportRelativeCharacterPosition); diff --git a/src/cascadia/TerminalControl/ControlCore.idl b/src/cascadia/TerminalControl/ControlCore.idl index c09fdf2d1aa..40fc49e9309 100644 --- a/src/cascadia/TerminalControl/ControlCore.idl +++ b/src/cascadia/TerminalControl/ControlCore.idl @@ -98,6 +98,7 @@ namespace Microsoft.Terminal.Control void ApplyAppearance(Boolean focused); Microsoft.Terminal.TerminalConnection.ITerminalConnection Connection; + void ResetConnection(); IControlSettings Settings { get; }; IControlAppearance FocusedAppearance { get; }; diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 990a3d84543..7f059af2246 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -532,6 +532,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation _core.Connection(newConnection); } + void TermControl::ResetConnection() + { + _core.ResetConnection(); + } + void TermControl::_throttledUpdateScrollbar(const ScrollBarUpdate& update) { if (!_initializedTerminal) diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 3db3cec6730..a79d604f5c4 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -192,6 +192,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation TerminalConnection::ITerminalConnection Connection(); void Connection(const TerminalConnection::ITerminalConnection& connection); + void ResetConnection(); Control::CursorDisplayState CursorVisibility() const noexcept; void CursorVisibility(Control::CursorDisplayState cursorVisibility); diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl index fb994786321..2a00c21def4 100644 --- a/src/cascadia/TerminalControl/TermControl.idl +++ b/src/cascadia/TerminalControl/TermControl.idl @@ -53,6 +53,7 @@ namespace Microsoft.Terminal.Control void SetOverrideColorScheme(Microsoft.Terminal.Core.ICoreScheme scheme); Microsoft.Terminal.TerminalConnection.ITerminalConnection Connection; + void ResetConnection(); UInt64 ContentId{ get; }; diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index e43180e9aaf..5163485bd4f 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -55,6 +55,18 @@ void Terminal::Create(til::size viewportSize, til::CoordType scrollbackLines, Re _stateMachine = std::make_unique(std::move(engine)); } +// Method Description: +// - Resets all VT state to defaults without clearing the buffer content. +// Called when a connection is restarted so that any dirty modes left +// behind by a crashed application don't affect the new connection. +void Terminal::ResetConnection() +{ + _assertLocked(); + _stateMachine->ResetState(); + auto& engine = reinterpret_cast(_stateMachine->Engine()); + engine.Dispatch().HardResetWithoutBufferClear(); +} + // Method Description: // - Initializes the Terminal from the given set of settings. // Arguments: diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index e69b42e373b..35072fb21ec 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -85,6 +85,7 @@ class Microsoft::Terminal::Core::Terminal final : void Create(til::size viewportSize, til::CoordType scrollbackLines, Microsoft::Console::Render::Renderer& renderer); + void ResetConnection(); void CreateFromSettings(winrt::Microsoft::Terminal::Core::ICoreSettings settings, Microsoft::Console::Render::Renderer& renderer); diff --git a/src/terminal/adapter/ITermDispatch.hpp b/src/terminal/adapter/ITermDispatch.hpp index 233fe51ecd2..cdac061b58f 100644 --- a/src/terminal/adapter/ITermDispatch.hpp +++ b/src/terminal/adapter/ITermDispatch.hpp @@ -140,6 +140,7 @@ class Microsoft::Console::VirtualTerminal::ITermDispatch virtual void SoftReset() = 0; // DECSTR virtual void HardReset() = 0; // RIS + virtual void HardResetWithoutBufferClear() = 0; virtual void ScreenAlignmentPattern() = 0; // DECALN virtual void SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle) = 0; // DECSCUSR diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index d49bbfad8fa..6733c393a8d 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -2994,6 +2994,31 @@ void AdaptDispatch::SoftReset() //Arguments: // void AdaptDispatch::HardReset() +{ + _hardResetCore(true); +} + +// Routine Description: +// - Performs a hard reset of all VT state without clearing the buffer content +// or scrollback. This is used when a connection is restarted to clean up +// any dirty state left behind by a crashed application (e.g., bracketed +// paste, mouse tracking, alternate buffer, kitty keyboard, etc.). +// Arguments: +// - None +void AdaptDispatch::HardResetWithoutBufferClear() +{ + _hardResetCore(false); +} + +// Routine Description: +// - Shared implementation for HardReset and HardResetWithoutBufferClear. +// Resets all VT state (parser, input modes, display modes, character sets, +// color table, tab stops, macros, etc.) to initial defaults. When +// clearBuffers is true, the screen and scrollback are also erased (full +// RIS behavior). When false, buffer content is preserved. +// Arguments: +// - clearBuffers: if true, erase the screen and scrollback +void AdaptDispatch::_hardResetCore(const bool clearBuffers) { // If in the alt buffer, switch back to main before doing anything else. if (_usingAltBuffer) @@ -3020,9 +3045,12 @@ void AdaptDispatch::HardReset() // to ensure that it clears with the default background color. SoftReset(); - // Clears the screen - Needs to be done in two operations. - EraseInDisplay(DispatchTypes::EraseType::All); - EraseInDisplay(DispatchTypes::EraseType::Scrollback); + if (clearBuffers) + { + // Clears the screen - Needs to be done in two operations. + EraseInDisplay(DispatchTypes::EraseType::All); + EraseInDisplay(DispatchTypes::EraseType::Scrollback); + } // Set the color table and render modes back to their initial startup values. _renderSettings.RestoreDefaultSettings(); @@ -3033,8 +3061,14 @@ void AdaptDispatch::HardReset() _renderer->SynchronizedOutputChanged(); } - // Cursor to 1,1 - the Soft Reset guarantees this is absolute - CursorPosition(1, 1); + if (clearBuffers) + { + // Cursor to 1,1 - the Soft Reset guarantees this is absolute. + // Only done when clearing buffers, because when preserving content + // the cursor should stay where the previous shell left it so the + // new shell prompt appears in the right place. + CursorPosition(1, 1); + } // We only reset the system line feed mode if the input mode is set. If it // isn't set, that either means they're both reset, and there's nothing for diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp index 74a6be1b57a..9ed4c3eb29d 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -129,6 +129,7 @@ namespace Microsoft::Console::VirtualTerminal void AnnounceCodeStructure(const VTInt ansiLevel) override; // ACS void SoftReset() override; // DECSTR void HardReset() override; // RIS + void HardResetWithoutBufferClear() override; void ScreenAlignmentPattern() override; // DECALN void SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle) override; // DECSCUSR @@ -195,6 +196,7 @@ namespace Microsoft::Console::VirtualTerminal void SetOptionalFeatures(const til::enumset features) noexcept override; private: + void _hardResetCore(const bool clearBuffers); enum class Mode { InsertReplace, diff --git a/src/terminal/adapter/termDispatch.hpp b/src/terminal/adapter/termDispatch.hpp index 5400339355d..f1a190e7f6b 100644 --- a/src/terminal/adapter/termDispatch.hpp +++ b/src/terminal/adapter/termDispatch.hpp @@ -127,6 +127,7 @@ class Microsoft::Console::VirtualTerminal::TermDispatch : public Microsoft::Cons void SoftReset() override {} // DECSTR void HardReset() override {} // RIS + void HardResetWithoutBufferClear() override {} void ScreenAlignmentPattern() override {} // DECALN void SetCursorStyle(const DispatchTypes::CursorStyle /*cursorStyle*/) override {} // DECSCUSR