From 52124c7835eeaf16b60544c42aa640d238ef2a9c Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Fri, 13 Mar 2020 16:19:31 -0500 Subject: [PATCH] Implement Hard Reset for Terminal ## Summary of the Pull Request This _actually_ implements `\033c` ([RIS](https://vt100.net/docs/vt220-rm/chapter4.html)) for the Windows Terminal. I thought I had done this in #4433, but that PR actually only passthrough'd `\x1b[3J`. I didn't realize at the time that #2715 was mostly about hard reset, not erase scrollback. Not only should conpty pass through RIS, but the Terminal should also be prepared to actually handle that sequence. So this PR adds that support as well. ## References * #4433: original PR I thought fixed this. ## PR Checklist * [x] Closes #2715 for real this time * [x] I work here * [x] Tests added/passed * [n/a] Requires documentation to be updated ## Validation Steps Performed Actually tested `printf \033c` in the Terminal this time --- .../TerminalCore/TerminalDispatch.cpp | 97 +++++++++++++++++++ .../TerminalCore/TerminalDispatch.hpp | 3 + .../ConptyRoundtripTests.cpp | 72 +++++++++++++- src/terminal/adapter/adaptDispatch.cpp | 11 ++- .../parser/OutputStateMachineEngine.cpp | 7 ++ 5 files changed, 186 insertions(+), 4 deletions(-) diff --git a/src/cascadia/TerminalCore/TerminalDispatch.cpp b/src/cascadia/TerminalCore/TerminalDispatch.cpp index f5d9a02559a..9e41146d5ed 100644 --- a/src/cascadia/TerminalCore/TerminalDispatch.cpp +++ b/src/cascadia/TerminalCore/TerminalDispatch.cpp @@ -401,3 +401,100 @@ bool TerminalDispatch::_PrivateModeParamsHelper(const DispatchTypes::PrivateMode } return success; } + +bool TerminalDispatch::SoftReset() noexcept +{ + // TODO:GH#1883 much of this method is not yet implemented in the Terminal, + // because the Terminal _doesn't need to_ yet. The terminal is only ever + // connected to conpty, so it doesn't implement most of these things that + // Hard/Soft Reset would reset. As those things ar implemented, they should + // also get cleared here. + // + // This code is left here (from its original form in conhost) as a reminder + // of what needs to be done. + + bool success = CursorVisibility(true); // Cursor enabled. + // if (success) + // { + // success = SetOriginMode(false); // Absolute cursor addressing. + // } + // if (success) + // { + // success = SetAutoWrapMode(true); // Wrap at end of line. + // } + if (success) + { + success = SetCursorKeysMode(false); // Normal characters. + } + if (success) + { + success = SetKeypadMode(false); // Numeric characters. + } + // if (success) + // { + // // Top margin = 1; bottom margin = page length. + // success = _DoSetTopBottomScrollingMargins(0, 0); + // } + // if (success) + // { + // success = DesignateCharset(DispatchTypes::VTCharacterSets::USASCII); // Default Charset + // } + if (success) + { + const auto opt = DispatchTypes::GraphicsOptions::Off; + success = SetGraphicsRendition({ &opt, 1 }); // Normal rendition. + } + // if (success) + // { + // // Reset the saved cursor state. + // // Note that XTerm only resets the main buffer state, but that + // // seems likely to be a bug. Most other terminals reset both. + // _savedCursorState.at(0) = {}; // Main buffer + // _savedCursorState.at(1) = {}; // Alt buffer + // } + + return success; +} + +bool TerminalDispatch::HardReset() noexcept +{ + // TODO:GH#1883 much of this method is not yet implemented in the Terminal, + // because the Terminal _doesn't need to_ yet. The terminal is only ever + // connected to conpty, so it doesn't implement most of these things that + // Hard/Soft Reset would reset. As those things ar implemented, they should + // also get cleared here. + // + // This code is left here (from its original form in conhost) as a reminder + // of what needs to be done. + + // Sets the SGR state to normal - this must be done before EraseInDisplay + // to ensure that it clears with the default background color. + bool success = SoftReset(); + + // Clears the screen - Needs to be done in two operations. + if (success) + { + success = EraseInDisplay(DispatchTypes::EraseType::All); + } + if (success) + { + success = EraseInDisplay(DispatchTypes::EraseType::Scrollback); + } + + // // Set the DECSCNM screen mode back to normal. + // if (success) + // { + // success = SetScreenMode(false); + // } + + // Cursor to 1,1 - the Soft Reset guarantees this is absolute + if (success) + { + success = CursorPosition(1, 1); + } + + // // delete all current tab stops and reapply + // _pConApi->PrivateSetDefaultTabStops(); + + return success; +} diff --git a/src/cascadia/TerminalCore/TerminalDispatch.hpp b/src/cascadia/TerminalCore/TerminalDispatch.hpp index 5321f67dbfa..e3be6ecbe92 100644 --- a/src/cascadia/TerminalCore/TerminalDispatch.hpp +++ b/src/cascadia/TerminalCore/TerminalDispatch.hpp @@ -44,6 +44,9 @@ class TerminalDispatch : public Microsoft::Console::VirtualTerminal::TermDispatc bool SetCursorKeysMode(const bool applicationMode) noexcept override; // DECCKM bool SetKeypadMode(const bool applicationMode) noexcept override; // DECKPAM, DECKPNM + bool SoftReset() noexcept override; // DECSTR + bool HardReset() noexcept override; // RIS + bool EnableVT200MouseMode(const bool enabled) noexcept override; // ?1000 bool EnableUTF8ExtendedMouseMode(const bool enabled) noexcept override; // ?1005 bool EnableSGRExtendedMouseMode(const bool enabled) noexcept override; // ?1006 diff --git a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp index 762f1ff7f9a..39abbb76fa1 100644 --- a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp @@ -158,6 +158,8 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final TEST_METHOD(PassthroughClearScrollback); + TEST_METHOD(PassthroughHardReset); + TEST_METHOD(PassthroughCursorShapeImmediately); TEST_METHOD(TestWrappingALongString); @@ -926,7 +928,7 @@ void ConptyRoundtripTests::PassthroughCursorShapeImmediately() void ConptyRoundtripTests::PassthroughClearScrollback() { Log::Comment(NoThrowString().Format( - L"Write more lines of outout. We should use \r\n to move the cursor")); + L"Write more lines of output than there are lines in the viewport. Clear the scrollback with ^[[3J")); VERIFY_IS_NOT_NULL(_pVtRenderEngine.get()); auto& g = ServiceLocator::LocateGlobals(); @@ -985,7 +987,7 @@ void ConptyRoundtripTests::PassthroughClearScrollback() const auto termSecondView = term->GetViewport(); VERIFY_ARE_EQUAL(0, termSecondView.Top()); - // Verify the top of the Terminal veiwoprt contains the contents of the old viewport + // Verify the top of the Terminal viewport contains the contents of the old viewport for (short y = 0; y < termSecondView.BottomInclusive(); y++) { TestUtils::VerifyExpectedString(termTb, L"X ", { 0, y }); @@ -997,3 +999,69 @@ void ConptyRoundtripTests::PassthroughClearScrollback() TestUtils::VerifyExpectedString(termTb, std::wstring(TerminalViewWidth, L' '), { 0, y }); } } + +void ConptyRoundtripTests::PassthroughHardReset() +{ + // This test is highly similar to PassthroughClearScrollback. + Log::Comment(NoThrowString().Format( + L"Write more lines of output than there are lines in the viewport. Clear everything with ^[c")); + VERIFY_IS_NOT_NULL(_pVtRenderEngine.get()); + + auto& g = ServiceLocator::LocateGlobals(); + auto& renderer = *g.pRender; + auto& gci = g.getConsoleInformation(); + auto& si = gci.GetActiveOutputBuffer(); + auto& hostSm = si.GetStateMachine(); + + auto& termTb = *term->_buffer; + + _flushFirstFrame(); + + _logConpty = true; + + const auto hostView = si.GetViewport(); + const auto end = 2 * hostView.Height(); + for (auto i = 0; i < end; i++) + { + Log::Comment(NoThrowString().Format(L"Writing line %d/%d", i, end)); + expectedOutput.push_back("X"); + if (i < hostView.BottomInclusive()) + { + expectedOutput.push_back("\r\n"); + } + else + { + // After we hit the bottom of the viewport, the newlines come in + // seperated for whatever reason. + expectedOutput.push_back("\r"); + expectedOutput.push_back("\n"); + expectedOutput.push_back(""); + } + + hostSm.ProcessString(L"X\n"); + + VERIFY_SUCCEEDED(renderer.PaintFrame()); + } + + VERIFY_SUCCEEDED(renderer.PaintFrame()); + + // Verify that we've printed height*2 lines of X's to the Terminal + const auto termFirstView = term->GetViewport(); + for (short y = 0; y < 2 * termFirstView.Height(); y++) + { + TestUtils::VerifyExpectedString(termTb, L"X ", { 0, y }); + } + + // Write a Hard Reset VT sequence to the host, it should come through to the Terminal + expectedOutput.push_back("\033c"); + hostSm.ProcessString(L"\033c"); + + const auto termSecondView = term->GetViewport(); + VERIFY_ARE_EQUAL(0, termSecondView.Top()); + + // Verify everything has been cleared out + for (short y = 0; y < termFirstView.BottomInclusive(); y++) + { + TestUtils::VerifyExpectedString(termTb, std::wstring(TerminalViewWidth, L' '), { 0, y }); + } +} diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index 35eb0b64eac..236d10283e7 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -1461,6 +1461,9 @@ bool AdaptDispatch::DesignateCharset(const wchar_t wchCharset) noexcept // True if handled successfully. False otherwise. bool AdaptDispatch::SoftReset() { + bool isPty = false; + _pConApi->IsConsolePty(isPty); + bool success = CursorVisibility(true); // Cursor enabled. if (success) { @@ -1474,11 +1477,15 @@ bool AdaptDispatch::SoftReset() { success = SetCursorKeysMode(false); // Normal characters. } - if (success) + // SetCursorKeysMode will return false if we're in conpty mode, as to + // trigger a passthrough. If that's the case, just power through here. + if (success || isPty) { success = SetKeypadMode(false); // Numeric characters. } - if (success) + // SetKeypadMode will return false if we're in conpty mode, as to trigger a + // passthrough. If that's the case, just power through here. + if (success || isPty) { // Top margin = 1; bottom margin = page length. success = _DoSetTopBottomScrollingMargins(0, 0); diff --git a/src/terminal/parser/OutputStateMachineEngine.cpp b/src/terminal/parser/OutputStateMachineEngine.cpp index be2763a2708..156488f01fa 100644 --- a/src/terminal/parser/OutputStateMachineEngine.cpp +++ b/src/terminal/parser/OutputStateMachineEngine.cpp @@ -274,6 +274,13 @@ bool OutputStateMachineEngine::ActionEscDispatch(const wchar_t wch, } } + // If we were unable to process the string, and there's a TTY attached to us, + // trigger the state machine to flush the string to the terminal. + if (_pfnFlushToTerminal != nullptr && !success) + { + success = _pfnFlushToTerminal(); + } + _ClearLastChar(); return success;