diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index 1589f708d55..f93559cf27c 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -446,6 +446,16 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation } } + void ConptyConnection::ClearBuffer() + { + // If we haven't started connecting at all, it's still fair to update + // the initial rows and columns before we set things up. + if (_isConnected()) + { + THROW_IF_FAILED(ConptyClearPseudoConsole(_hPC.get())); + } + } + void ConptyConnection::Close() noexcept try { diff --git a/src/cascadia/TerminalConnection/ConptyConnection.h b/src/cascadia/TerminalConnection/ConptyConnection.h index 8f82a617be3..9a2fc3a6e0f 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.h +++ b/src/cascadia/TerminalConnection/ConptyConnection.h @@ -35,6 +35,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation void WriteInput(hstring const& data); void Resize(uint32_t rows, uint32_t columns); void Close() noexcept; + void ClearBuffer(); winrt::guid Guid() const noexcept; diff --git a/src/cascadia/TerminalConnection/ConptyConnection.idl b/src/cascadia/TerminalConnection/ConptyConnection.idl index a1cfa979084..2e6cce5c9aa 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.idl +++ b/src/cascadia/TerminalConnection/ConptyConnection.idl @@ -9,6 +9,7 @@ namespace Microsoft.Terminal.TerminalConnection { ConptyConnection(); Guid Guid { get; }; + void ClearBuffer(); static event NewConnectionHandler NewConnection; static void StartInboundListener(); diff --git a/src/host/PtySignalInputThread.cpp b/src/host/PtySignalInputThread.cpp index df42f45f7a2..cbd59579757 100644 --- a/src/host/PtySignalInputThread.cpp +++ b/src/host/PtySignalInputThread.cpp @@ -82,6 +82,21 @@ void PtySignalInputThread::ConnectConsole() noexcept { switch (signalId) { + case PtySignal::ClearBuffer: + { + LockConsole(); + auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); + + // If the client app hasn't yet connected, stash the new size in the launchArgs. + // We'll later use the value in launchArgs to set up the console buffer + // We must be under lock here to ensure that someone else doesn't come in + // and set with `ConnectConsole` while we're looking and modifying this. + if (_consoleConnected) + { + _DoClearBuffer(); + } + break; + } case PtySignal::ResizeWindow: { ResizeWindowData resizeMsg = { 0 }; @@ -128,6 +143,11 @@ void PtySignalInputThread::_DoResizeWindow(const ResizeWindowData& data) } } +void PtySignalInputThread::_DoClearBuffer() +{ + DispatchCommon::s_EraseInDisplay(*_pConApi, DispatchTypes::EraseType::All); +} + // Method Description: // - Retrieves bytes from the file stream and exits or throws errors should the pipe state // be compromised. diff --git a/src/host/PtySignalInputThread.hpp b/src/host/PtySignalInputThread.hpp index d54cf683345..e6c4d8bb4b9 100644 --- a/src/host/PtySignalInputThread.hpp +++ b/src/host/PtySignalInputThread.hpp @@ -35,6 +35,7 @@ namespace Microsoft::Console private: enum class PtySignal : unsigned short { + ClearBuffer = 2, ResizeWindow = 8 }; @@ -47,6 +48,7 @@ namespace Microsoft::Console [[nodiscard]] HRESULT _InputThread(); bool _GetData(_Out_writes_bytes_(cbBuffer) void* const pBuffer, const DWORD cbBuffer); void _DoResizeWindow(const ResizeWindowData& data); + void _DoClearBuffer(); void _Shutdown(); wil::unique_hfile _hFile; diff --git a/src/inc/conpty.h b/src/inc/conpty.h index ff980d7c278..6f51ba94246 100644 --- a/src/inc/conpty.h +++ b/src/inc/conpty.h @@ -7,6 +7,7 @@ #include #pragma once +const unsigned int PTY_SIGNAL_CLEAR_WINDOW = 2u; const unsigned int PTY_SIGNAL_RESIZE_WINDOW = 8u; HRESULT CreateConPty(const std::wstring& cmdline, // _In_ diff --git a/src/terminal/adapter/DispatchCommon.cpp b/src/terminal/adapter/DispatchCommon.cpp index 1449aa8e289..7012bb0003d 100644 --- a/src/terminal/adapter/DispatchCommon.cpp +++ b/src/terminal/adapter/DispatchCommon.cpp @@ -91,3 +91,113 @@ bool DispatchCommon::s_SuppressResizeRepaint(ConGetSet& conApi) { return conApi.PrivateSuppressResizeRepaint(); } + +bool DispatchCommon::s_EraseInDisplay(ConGetSet& conApi, const DispatchTypes::EraseType eraseType) +{ + RETURN_BOOL_IF_FALSE(eraseType <= DispatchTypes::EraseType::Scrollback); + + // First things first. If this is a "Scrollback" clear, then just do that. + // Scrollback clears erase everything in the "scrollback" of a *nix terminal + // Everything that's scrolled off the screen so far. + // Or if it's an Erase All, then we also need to handle that specially + // by moving the current contents of the viewport into the scrollback. + if (eraseType == DispatchTypes::EraseType::Scrollback) + { + const bool eraseScrollbackResult = _EraseScrollback(); + // GH#2715 - If this succeeded, but we're in a conpty, return `false` to + // make the state machine propagate this ED sequence to the connected + // terminal application. While we're in conpty mode, we don't really + // have a scrollback, but the attached terminal might. + const bool isPty = _pConApi->IsConsolePty(); + return eraseScrollbackResult && (!isPty); + } + else if (eraseType == DispatchTypes::EraseType::All) + { + // GH#5683 - If this succeeded, but we're in a conpty, return `false` to + // make the state machine propagate this ED sequence to the connected + // terminal application. While we're in conpty mode, when the client + // requests a Erase All operation, we need to manually tell the + // connected terminal to do the same thing, so that the terminal will + // move it's own buffer contents into the scrollback. + const bool eraseAllResult = _EraseAll(); + const bool isPty = _pConApi->IsConsolePty(); + return eraseAllResult && (!isPty); + } + + CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 }; + csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX); + // Make sure to reset the viewport (with MoveToBottom )to where it was + // before the user scrolled the console output + bool success = (_pConApi->MoveToBottom() && _pConApi->GetConsoleScreenBufferInfoEx(csbiex)); + + if (success) + { + // When erasing the display, every line that is erased in full should be + // reset to single width. When erasing to the end, this could include + // the current line, if the cursor is in the first column. When erasing + // from the beginning, though, the current line would never be included, + // because the cursor could never be in the rightmost column (assuming + // the line is double width). + if (eraseType == DispatchTypes::EraseType::FromBeginning) + { + const auto endRow = csbiex.dwCursorPosition.Y; + _pConApi->PrivateResetLineRenditionRange(csbiex.srWindow.Top, endRow); + } + if (eraseType == DispatchTypes::EraseType::ToEnd) + { + const auto startRow = csbiex.dwCursorPosition.Y + (csbiex.dwCursorPosition.X > 0 ? 1 : 0); + _pConApi->PrivateResetLineRenditionRange(startRow, csbiex.srWindow.Bottom); + } + + // What we need to erase is grouped into 3 types: + // 1. Lines before cursor + // 2. Cursor Line + // 3. Lines after cursor + // We erase one or more of these based on the erase type: + // A. FromBeginning - Erase 1 and Some of 2. + // B. ToEnd - Erase some of 2 and 3. + // C. All - Erase 1, 2, and 3. + + // 1. Lines before cursor line + if (eraseType == DispatchTypes::EraseType::FromBeginning) + { + // For beginning and all, erase all complete lines before (above vertically) from the cursor position. + for (SHORT startLine = csbiex.srWindow.Top; startLine < csbiex.dwCursorPosition.Y; startLine++) + { + success = _EraseSingleLineHelper(csbiex, DispatchTypes::EraseType::All, startLine); + + if (!success) + { + break; + } + } + } + + if (success) + { + // 2. Cursor Line + success = _EraseSingleLineHelper(csbiex, eraseType, csbiex.dwCursorPosition.Y); + } + + if (success) + { + // 3. Lines after cursor line + if (eraseType == DispatchTypes::EraseType::ToEnd) + { + // For beginning and all, erase all complete lines after (below vertically) the cursor position. + // Remember that the viewport bottom value is 1 beyond the viewable area of the viewport. + for (SHORT startLine = csbiex.dwCursorPosition.Y + 1; startLine < csbiex.srWindow.Bottom; startLine++) + { + success = _EraseSingleLineHelper(csbiex, DispatchTypes::EraseType::All, startLine); + + if (!success) + { + break; + } + } + } + } + } + + return success; +} diff --git a/src/terminal/adapter/DispatchCommon.hpp b/src/terminal/adapter/DispatchCommon.hpp index f7af1e28d03..d6724071360 100644 --- a/src/terminal/adapter/DispatchCommon.hpp +++ b/src/terminal/adapter/DispatchCommon.hpp @@ -29,5 +29,6 @@ namespace Microsoft::Console::VirtualTerminal static bool s_RefreshWindow(ConGetSet& conApi); static bool s_SuppressResizeRepaint(ConGetSet& conApi); + static bool s_EraseInDisplay(ConGetSet& conApi, const DispatchTypes::EraseType eraseType); }; } diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index 4bd30cbc847..971b52c36be 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -583,112 +583,7 @@ bool AdaptDispatch::EraseCharacters(const size_t numChars) // - True if handled successfully. False otherwise. bool AdaptDispatch::EraseInDisplay(const DispatchTypes::EraseType eraseType) { - RETURN_BOOL_IF_FALSE(eraseType <= DispatchTypes::EraseType::Scrollback); - - // First things first. If this is a "Scrollback" clear, then just do that. - // Scrollback clears erase everything in the "scrollback" of a *nix terminal - // Everything that's scrolled off the screen so far. - // Or if it's an Erase All, then we also need to handle that specially - // by moving the current contents of the viewport into the scrollback. - if (eraseType == DispatchTypes::EraseType::Scrollback) - { - const bool eraseScrollbackResult = _EraseScrollback(); - // GH#2715 - If this succeeded, but we're in a conpty, return `false` to - // make the state machine propagate this ED sequence to the connected - // terminal application. While we're in conpty mode, we don't really - // have a scrollback, but the attached terminal might. - const bool isPty = _pConApi->IsConsolePty(); - return eraseScrollbackResult && (!isPty); - } - else if (eraseType == DispatchTypes::EraseType::All) - { - // GH#5683 - If this succeeded, but we're in a conpty, return `false` to - // make the state machine propagate this ED sequence to the connected - // terminal application. While we're in conpty mode, when the client - // requests a Erase All operation, we need to manually tell the - // connected terminal to do the same thing, so that the terminal will - // move it's own buffer contents into the scrollback. - const bool eraseAllResult = _EraseAll(); - const bool isPty = _pConApi->IsConsolePty(); - return eraseAllResult && (!isPty); - } - - CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 }; - csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX); - // Make sure to reset the viewport (with MoveToBottom )to where it was - // before the user scrolled the console output - bool success = (_pConApi->MoveToBottom() && _pConApi->GetConsoleScreenBufferInfoEx(csbiex)); - - if (success) - { - // When erasing the display, every line that is erased in full should be - // reset to single width. When erasing to the end, this could include - // the current line, if the cursor is in the first column. When erasing - // from the beginning, though, the current line would never be included, - // because the cursor could never be in the rightmost column (assuming - // the line is double width). - if (eraseType == DispatchTypes::EraseType::FromBeginning) - { - const auto endRow = csbiex.dwCursorPosition.Y; - _pConApi->PrivateResetLineRenditionRange(csbiex.srWindow.Top, endRow); - } - if (eraseType == DispatchTypes::EraseType::ToEnd) - { - const auto startRow = csbiex.dwCursorPosition.Y + (csbiex.dwCursorPosition.X > 0 ? 1 : 0); - _pConApi->PrivateResetLineRenditionRange(startRow, csbiex.srWindow.Bottom); - } - - // What we need to erase is grouped into 3 types: - // 1. Lines before cursor - // 2. Cursor Line - // 3. Lines after cursor - // We erase one or more of these based on the erase type: - // A. FromBeginning - Erase 1 and Some of 2. - // B. ToEnd - Erase some of 2 and 3. - // C. All - Erase 1, 2, and 3. - - // 1. Lines before cursor line - if (eraseType == DispatchTypes::EraseType::FromBeginning) - { - // For beginning and all, erase all complete lines before (above vertically) from the cursor position. - for (SHORT startLine = csbiex.srWindow.Top; startLine < csbiex.dwCursorPosition.Y; startLine++) - { - success = _EraseSingleLineHelper(csbiex, DispatchTypes::EraseType::All, startLine); - - if (!success) - { - break; - } - } - } - - if (success) - { - // 2. Cursor Line - success = _EraseSingleLineHelper(csbiex, eraseType, csbiex.dwCursorPosition.Y); - } - - if (success) - { - // 3. Lines after cursor line - if (eraseType == DispatchTypes::EraseType::ToEnd) - { - // For beginning and all, erase all complete lines after (below vertically) the cursor position. - // Remember that the viewport bottom value is 1 beyond the viewable area of the viewport. - for (SHORT startLine = csbiex.dwCursorPosition.Y + 1; startLine < csbiex.srWindow.Bottom; startLine++) - { - success = _EraseSingleLineHelper(csbiex, DispatchTypes::EraseType::All, startLine); - - if (!success) - { - break; - } - } - } - } - } - - return success; + return DispatchCommon::s_EraseInDisplay(*_pConApi, eraseType); } // Routine Description: diff --git a/src/winconpty/dll/winconpty.def b/src/winconpty/dll/winconpty.def index 1ea7e916ae7..da8a5653533 100644 --- a/src/winconpty/dll/winconpty.def +++ b/src/winconpty/dll/winconpty.def @@ -2,3 +2,4 @@ EXPORTS CreatePseudoConsole = ConptyCreatePseudoConsole ResizePseudoConsole = ConptyResizePseudoConsole ClosePseudoConsole = ConptyClosePseudoConsole + ClearPseudoConsole = ConptyClearPseudoConsole diff --git a/src/winconpty/winconpty.cpp b/src/winconpty/winconpty.cpp index 5ef63295517..9667e39418c 100644 --- a/src/winconpty/winconpty.cpp +++ b/src/winconpty/winconpty.cpp @@ -231,6 +231,27 @@ HRESULT _ResizePseudoConsole(_In_ const PseudoConsole* const pPty, _In_ const CO return fSuccess ? S_OK : HRESULT_FROM_WIN32(GetLastError()); } +// Function Description: +// - Clears the conpty +// Arguments: +// - hSignal: A signal pipe as returned by CreateConPty. +// Return Value: +// - S_OK if the call succeeded, else an appropriate HRESULT for failing to +// write the clear message to the pty. +HRESULT _ClearPseudoConsole(_In_ const PseudoConsole* const pPty) +{ + if (pPty == nullptr) + { + return E_INVALIDARG; + } + + unsigned short signalPacket[1]; + signalPacket[0] = PTY_SIGNAL_CLEAR_WINDOW; + + const BOOL fSuccess = WriteFile(pPty->hSignal, signalPacket, sizeof(signalPacket), nullptr, nullptr); + return fSuccess ? S_OK : HRESULT_FROM_WIN32(GetLastError()); +} + // Function Description: // - This closes each of the members of a PseudoConsole. It does not free the // data associated with the PseudoConsole. This is helpful for testing, @@ -385,6 +406,19 @@ extern "C" HRESULT WINAPI ConptyResizePseudoConsole(_In_ HPCON hPC, _In_ COORD s return hr; } +// Function Description: +// TODO! +extern "C" HRESULT WINAPI ConptyClearPseudoConsole(_In_ HPCON hPC) +{ + const PseudoConsole* const pPty = (PseudoConsole*)hPC; + HRESULT hr = pPty == nullptr ? E_INVALIDARG : S_OK; + if (SUCCEEDED(hr)) + { + hr = _ClearPseudoConsole(pPty, size); + } + return hr; +} + // Function Description: // Closes the conpty and all associated state. // Client applications attached to the conpty will also behave as though the diff --git a/src/winconpty/winconpty.h b/src/winconpty/winconpty.h index 0916bcd6330..f2a7ff429ee 100644 --- a/src/winconpty/winconpty.h +++ b/src/winconpty/winconpty.h @@ -17,6 +17,7 @@ typedef struct _PseudoConsole // Signals // These are not defined publicly, but are used for controlling the conpty via // the signal pipe. +#define PTY_SIGNAL_CLEAR_WINDOW (2u) #define PTY_SIGNAL_RESIZE_WINDOW (8u) // CreatePseudoConsole Flags @@ -34,6 +35,7 @@ HRESULT _CreatePseudoConsole(const HANDLE hToken, _Inout_ PseudoConsole* pPty); HRESULT _ResizePseudoConsole(_In_ const PseudoConsole* const pPty, _In_ const COORD size); +HRESULT _ClearPseudoConsole(_In_ const PseudoConsole* const pPty); void _ClosePseudoConsoleMembers(_In_ PseudoConsole* pPty); VOID _ClosePseudoConsole(_In_ PseudoConsole* pPty);