Compare commits

...

54 commits

Author SHA1 Message Date
Mike Griese b2463a131f fix the tests too 2020-03-05 12:03:20 -06:00
Mike Griese 5ef1488316 Clean this up for review 2020-03-05 10:07:33 -06:00
Mike Griese 01755f77af add notes on how method.10 works 2020-03-05 09:35:07 -06:00
Mike Griese 162d3fea67 I can't believe I pushed out an internal selfhost with an off-by-one 2020-03-05 08:43:11 -06:00
Mike Griese f2d1d95055 Add a --resizeQuirk flag to conpty
This will enable the quirky resize behavior for applications that are prepared
  for it. The "quirky resize" is "don't `InvalidateAll` when the terminal
  resizes". This is added as a quirk as to not regress other terminal
  applications that aren't prepared for this behavior (gnome-terminal, conhost
  in particular). For those kinds of terminals, when the buffer is resized, it's
  just going to lose lines. That's what currently happens for them.

  When the quirk is enabled, conpty won't repaint the entire buffer. This gets
  around the "duplicated lines" issue that requesting multiple resizes in a row
  can cause. However, for these terminals that are unprepared, the conpty cursor
  might end up in the wrong position after a quirky resize.

  The case in point is maximizing the terminal. For maximizing (height->50) from
  a buffer that's 30 lines tall, with the cursor on y=30, this is what happens:
  * With the quirk disabled, conpty reprints the entire buffer. This is 60 lines
    that get printed. This ends up blowing away about 20 lines of scrollback
    history, as the terminal app would have tried to keep the text pinned to the
    bottom of the window. The term. app moved the viewport up 20 lines, and then
    the 50 lines of conpty output (30 lines of text, and 20 blank lines at the
    bottom) overwrote the lines from the scrollback. This is bad, but not
    immediately obvious, and is **what currently happens**.
  * With the quirk enabled, conpty doesn't emit any lines, but the actual
    content of the window is still only in the top 30 lines. However, the
    terminal app has still moved 20 lines down from the scrollback back into the
    viewport. So the terminal's cursor is at y=50 now, but conpty's is at 30.
    This means that the terminal and conpty are out of sync, and there's not a
    good way of re-syncing these. It's very possible (trivial in `powershell`)
    that the new output will jump up to y=30 override the existing output in the
    terminal buffer.

  the Windows Terminal is already prepared for this quirky behavior, so it
  doesn't keep the output at the bottom of the window. It shifts it's viewport
  down to match what conpty things the buffer looks like.

  What happens when we have passthroguh mode and WT is like "I would like quirky
  resize"? I guess things will just work fine, cause there won't be a buffer
  behind the passthrough app that the terminal cares about. Sure, in the
  passthrough case the Terminal could _not_ quirky resize, but the quirky resize
  won't be wrong.
2020-03-04 17:23:19 -06:00
Mike Griese 60229a2cdc This works _so close_ to well. The terminal and the conpty sometimes
miscommunicate about the cursor position in this case. But I'm going to try
dustin's "add a flag" to make only the WT opt into this for now.
2020-03-04 16:29:54 -06:00
Mike Griese 646a63b52e These are some changes from #4354. Maybe I wasn't that far off here?
Mike what are you doing

  These changes make conpty reprint less when the buffer resizes. This works
  largely quite well tbh. The biggest problem is that the input line seems to
  move, and that's no good. Presumably that's because the cursor position moved
  in conpty, but we didn't update the terminal appropriately. That can be fixed.

  This is also a horrible idea and I'm sure will come back to bite me, but hey
  lets try it.

  But seriously, this works really well for fast resizing (I mean, not including
  the cursor position thing). Didn't check maximize/restore yet but I bet that's
  _just wrong_ right now.
2020-03-04 12:44:17 -06:00
Mike Griese 1470abe1fe Switch back to conpty
We still get lots of duplicated lines in the "resize twice before the terminal
  catches the whole frame" case, which is a bummer.

  This also revealed that the maximize/restore scenario really doesn't work all
  that well. I think the "shift down cause one line de-wrapped" doesn't work
  well for when many lines are de-wrapping.

  I'm going to revert this commit and try fixing the maximize/restore
2020-03-04 12:17:23 -06:00
Mike Griese f1ff8345c6 method.7 & fix resizing down - method.8
B-A-B-Y this works for both horizontal and vertical resizes. It's
    really a mix of m.7 and m.2. Basically took that m.2 code for
    figuring out if the text was on the last line of the viewport and put
    it in here too.

    Lets cross our fingers that it works for a real conpty as well.
2020-03-04 11:19:53 -06:00
Mike Griese 4849dab5dc classic me wrote the comment then didn't save. This belongs with the previous commit. 2020-03-04 10:58:03 -06:00
Mike Griese 58aa64da55 screenInfo + de-wrap last scrollback line adjustment - method.7
This one seems a tad bit hacky, but works alright enough.

    This is basically the screeninfo method with a small change. After
    calculating where the viewport should be, check the scrollback line that was
    immediately before the viewport. If it previously wrapped, that means the
    old top line of the viewport had wrapped from a scrollback line. If that
    scrollback line doesn't wrap anymore, we took the content from the top line
    of the viewport and moved it into that scrollback line. Conpty however
    couldn't do that, so it's going to reprint that top line, even if it's now
    been successfully dewrapped into the scrollback. To avoid losing output,
    we'll shift our new viewport location down one row, so that the new output
    from conpty doesn't blow away the previously scrolled back line.

    I have yet to test this with vertical resizing, but I want to checkpoint
    this as-is
2020-03-04 10:57:14 -06:00
Mike Griese 658295eab0 Fix the screenInfo method
I don't know what crack I was smoking before, this code didn't even sorta
    resemble the screenInfo code. Re-written like this, it handles the
    scrollback lines wrapping and unwrapping just fine - viewport is in the
    right spot.

    Unfortunately this suffers from the same problem as 5. As the lines in the
    original viewport get unwrapped, they come back into view, but they'll be
    gone from conpty. So when conpty re-prints, we'll blow them away. Maybe we
    could combo this with 4?
2020-03-04 09:17:03 -06:00
Mike Griese 1448c52228 don't calulate the last scrollback row, instead try the last viewport row
This works quite well for the decreasing width. As lines get wrapped,
    we'll move down with them appropriately. Where it falls apart is in
    the unwrapping of lines - we'll basically keep the bottom in the same
    place, and when conpty reprints, we'll duplicate the lines. This is
    because the top line that conpty actually still knows about will get
    moved up in our buffer (from previous lines de-wrapping).  We need to
    instead keep the top in the same place in that case, so that all the
    lines conpty knows about stay on the screen.

    This should be paired with
      short scrollbackLines = ::base::saturated_cast<short>(_mutableViewport.BottomInclusive());
    and
      *lastScrollbackRow = gsl::narrow_cast<short>(newCursor.GetPosition().Y - 1);
    TextBuffer::Reflow
2020-03-04 08:59:40 -06:00
Mike Griese bb3d9102e7 Adjust the "let the textbuffer figure out the scrollback" algo
This works very well for decreasing width up until the point where
    the lines _in_ the viewport start wrapping. In that case, the
    viewport really _should_ be moving down as well, but this method
    can't really tell that.
2020-03-04 08:33:39 -06:00
Mike Griese 7a177100e1 Use a dummy connection to test reflowing 2020-03-04 08:33:05 -06:00
Mike Griese 25c593f2ea Revert "Maybe try to be smarter about how we mark lines as wrapped"
This reverts commit e7c55117a7.
2020-03-04 08:10:22 -06:00
Mike Griese e7c55117a7 Maybe try to be smarter about how we mark lines as wrapped
But conpty is giving us lines that are wrapped when they really aren't
2020-03-04 08:10:05 -06:00
Mike Griese e6c2746900 Try having the textbuffer figure out for us where the scrollback ended? 2020-03-03 15:54:10 -06:00
Mike Griese 8ca2a4abf8 Try a bunch of different methods of placing the viewport after a resize
None of these seem particularily right though
2020-03-03 15:53:42 -06:00
Mike Griese cc7b2d34e1 okay I'll actually build the SA locally 2020-03-02 15:05:17 -06:00
Mike Griese 2ef2d88d0a add safemath for carlos 2020-03-02 11:13:25 -06:00
Mike Griese 0f283b4723 Merge remote-tracking branch 'origin/master' into dev/migrie/f/reflow-buffer-on-resize 2020-03-02 10:00:17 -06:00
Mike Griese 0c91a9b4d4 @ our static analysis build: you're wrong here, but fine 2020-02-28 12:15:32 -06:00
Mike Griese 1d87f66792 Merge branch 'master' into dev/migrie/f/reflow-buffer-on-resize
# Conflicts:
#	src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp
#	src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp
#	src/renderer/base/renderer.cpp
#	src/renderer/vt/paint.cpp
2020-02-28 09:32:19 -06:00
Mike Griese e65371393d Merge remote-tracking branch 'origin/master' into dev/migrie/f/reflow-buffer-on-resize
# Conflicts:
#	src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp
2020-02-11 10:48:13 -08:00
Mike Griese de9911df02 Merge remote-tracking branch 'origin/master' into dev/migrie/f/reflow-buffer-on-resize 2020-02-07 10:53:22 -06:00
Mike Griese 2e815c8954 fix tests 2020-01-31 17:05:10 -06:00
Mike Griese 097b62c896 Merge remote-tracking branch 'origin/master' into dev/migrie/f/reflow-buffer-on-resize 2020-01-31 16:48:37 -06:00
Mike Griese edea9a335c Cleanup for review - this is a _great_ fix for #3490 as well as #1465 2020-01-30 15:27:34 -06:00
Mike Griese 95807154b8 wait why does this work so well 2020-01-30 14:51:58 -06:00
Mike Griese 74a528357c ResizeWithReflow the Terminal buffer 2020-01-30 14:28:27 -06:00
Mike Griese 1788cb1e45 Merge branch 'dev/migrie/b/1245-I-actually-did-it-this-time' into dev/migrie/f/reflow-buffer-on-resize
# Conflicts:
#	src/renderer/vt/XtermEngine.cpp
#	src/renderer/vt/vtrenderer.hpp
2020-01-30 14:28:03 -06:00
Mike Griese 5a72af9822 Merge branch 'dev/migrie/f/conpty-wrapping-003' into dev/migrie/f/reflow-buffer-on-resize 2020-01-30 12:35:00 -06:00
Mike Griese 96642deb39 remove other dead code for PR 2020-01-30 11:10:45 -06:00
Mike Griese b3de042c46 remove some old TODO comments 2020-01-30 10:31:07 -06:00
Mike Griese e0d251c349 Merge remote-tracking branch 'origin/master' into dev/migrie/b/1245-I-actually-did-it-this-time 2020-01-29 16:07:20 -06:00
Mike Griese 40b4966782 Revert "Try adding a test, but I can't get the test to repro the original behavior quite right"
This reverts commit 0a98cceddb.
2020-01-29 16:06:34 -06:00
Mike Griese 0a98cceddb Try adding a test, but I can't get the test to repro the original behavior quite right 2020-01-29 16:06:25 -06:00
Mike Griese 9b6554b10f Add some comments for PR 2020-01-29 16:05:53 -06:00
Mike Griese 7fd5d515b2 This actually fixes this bug - different terminals EOL wrap differently, esp conhost v wt v gnome-terminal. Remove ambiguity - just hardcore move the cursor in this scenario. 2020-01-29 12:52:19 -06:00
Mike Griese 86623f57d5 Add PaintCursor tracing 2020-01-29 11:38:07 -06:00
Mike Griese 0755fd73e1 This is polished for PR, ready to go in after #4382 2020-01-28 15:12:42 -06:00
Mike Griese c040a82e62 I've found a bug with the line wrapping, going to go update the PaintBufferLine interface 2020-01-28 12:34:55 -06:00
Mike Griese e000388baf add a roundtrip test 2020-01-28 11:17:56 -06:00
Mike Griese bfde821b2f A simple test for wrapped lines 2020-01-28 10:42:34 -06:00
Mike Griese 4a7f2e4f7b Merge branch 'dev/migrie/b/just-conpty-test-fixes' into dev/migrie/f/conpty-wrapping-003 2020-01-28 09:35:18 -06:00
Mike Griese ce3138c685 Let's pull all the test fixes into their own file 2020-01-28 09:24:04 -06:00
Mike Griese edeb346325 I think this is all I need to support wrapped lines in the Terminal
(cherry picked from commit c21f74029d)
2020-01-28 08:36:08 -06:00
Mike Griese 7f341a25a5 Merge branch 'master' into dev/migrie/f/conpty-wrapping-003
# Conflicts:
#	src/buffer/out/textBuffer.cpp
#	src/buffer/out/textBuffer.hpp
#	src/host/screenInfo.cpp
2020-01-27 16:46:29 -06:00
Mike Griese 416be4656f This is some cleanup, almost ready for PR but I need to write tests which are blocked on #4213 2020-01-14 16:46:33 -06:00
Mike Griese aae6ce60a4 This works, fixing the ECH at end of wrapped line bug
The trick was that when a line that was exactly filled was followed by an
  empty line, we'd ECH when the cursor is virtually on the last char of the
  wrapped line. That was wrong. For a scenario like:

  |ABCDEF|
  |      |

  We'd incorrectly render that as ["ABCDEF", "^[[K", "\r\n"]. That ECH in the
  middle there would erase the 'F', because the cursor was virtually still on
  the 'F' (because it had deferred wrapping to the next char).

  So, when we're about to ECH following a wrapped line, reset the _wrappedRow
  first, to make sure we correctly \r\n first.
2020-01-14 16:07:22 -06:00
Mike Griese 1a2654d291 Try to wrap the line properly with conpty
This confusingly doesn't always work
2020-01-13 17:07:43 -06:00
Mike Griese 9aec69467c add a doc comment because I'm not a barbarian 2020-01-13 11:34:50 -06:00
Mike Griese b5c8c854cc let's first move reflowing to the text buffer 2020-01-13 11:23:42 -06:00
20 changed files with 500 additions and 60 deletions

View file

@ -1900,9 +1900,18 @@ std::string TextBuffer::GenRTF(const TextAndColor& rows, const int fontHeightPoi
// Arguments:
// - oldBuffer - the text buffer to copy the contents FROM
// - newBuffer - the text buffer to copy the contents TO
// - lastCharacterViewport - Optional. If the caller knows that the last
// nonspace character is in a particular Viewport, the caller can provide this
// parameter as an optimization, as opposed to searching the entire buffer.
// - oldViewportTop - Optional. The caller can provide a row in this parameter
// and we'll calculate that row's position in the new buffer. The row's new
// value is placed back into this pointer.
// Return Value:
// - S_OK if we successfully copied the contents to the new buffer, otherwise an appropriate HRESULT.
HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer, TextBuffer& newBuffer)
HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
TextBuffer& newBuffer,
const std::optional<Viewport> lastCharacterViewport,
short* const oldViewportTop) noexcept
{
Cursor& oldCursor = oldBuffer.GetCursor();
Cursor& newCursor = newBuffer.GetCursor();
@ -1914,18 +1923,32 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer, TextBuffer& newBuffer)
// place the new cursor back on the equivalent character in
// the new buffer.
const COORD cOldCursorPos = oldCursor.GetPosition();
const COORD cOldLastChar = oldBuffer.GetLastNonSpaceCharacter();
const COORD cOldLastChar = lastCharacterViewport.has_value() ?
oldBuffer.GetLastNonSpaceCharacter(lastCharacterViewport.value()) :
oldBuffer.GetLastNonSpaceCharacter();
short const cOldRowsTotal = cOldLastChar.Y + 1;
short const cOldColsTotal = oldBuffer.GetSize().Width();
const short cOldRowsTotal = cOldLastChar.Y + 1;
const short cOldColsTotal = oldBuffer.GetSize().Width();
COORD cNewCursorPos = { 0 };
bool fFoundCursorPos = false;
bool foundOldRow = false;
HRESULT hr = S_OK;
// Loop through all the rows of the old buffer and reprint them into the new buffer
for (short iOldRow = 0; iOldRow < cOldRowsTotal; iOldRow++)
{
// If we found the old row that the caller was interested in, set the
// out value of that parameter to the cursor's current Y position (the
// new location of that row in the buffer).
if (oldViewportTop && !foundOldRow)
{
if (iOldRow >= *oldViewportTop)
{
*oldViewportTop = newCursor.GetPosition().Y;
foundOldRow = true;
}
}
// Fetch the row and its "right" which is the last printable character.
const ROW& row = oldBuffer.GetRowByOffset(iOldRow);
const CharRow& charRow = row.GetCharRow();

View file

@ -162,7 +162,10 @@ public:
const std::wstring_view fontFaceName,
const COLORREF backgroundColor);
static HRESULT Reflow(TextBuffer& oldBuffer, TextBuffer& newBuffer);
static HRESULT Reflow(TextBuffer& oldBuffer,
TextBuffer& newBuffer,
const std::optional<Microsoft::Console::Types::Viewport> lastCharacterViewport = std::nullopt,
short* const lastScrollbackRow = nullptr) noexcept;
private:
std::deque<ROW> _storage;

View file

@ -510,7 +510,7 @@ namespace winrt::TerminalApp::implementation
// Create a connection based on the values in our settings object.
const auto connection = _CreateConnectionFromSettings(profileGuid, settings);
// const auto connection = TerminalConnection::EchoConnection();
TermControl term{ settings, connection };
// Add the new tab to the list of our tabs.

View file

@ -201,7 +201,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
try
{
const COORD dimensions{ gsl::narrow_cast<SHORT>(_initialCols), gsl::narrow_cast<SHORT>(_initialRows) };
THROW_IF_FAILED(_CreatePseudoConsoleAndPipes(dimensions, 0, &_inPipe, &_outPipe, &_hPC));
THROW_IF_FAILED(_CreatePseudoConsoleAndPipes(dimensions, PSEUDOCONSOLE_RESIZE_QUIRK, &_inPipe, &_outPipe, &_hPC));
THROW_IF_FAILED(_LaunchAttachedClient());
_startTime = std::chrono::high_resolution_clock::now();

View file

@ -26,7 +26,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
std::wstringstream prettyPrint;
for (const auto& wch : data)
{
if (wch < 0x20)
if (wch < 0x20 && (wch != L'\r' && wch != L'\n'))
{
prettyPrint << L"^" << gsl::narrow_cast<wchar_t>(wch + 0x40);
}

View file

@ -637,6 +637,17 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_connection.Start();
_initializedTerminal = true;
_InitializedHandlers(*this, nullptr);
// {
// for (int i = 0; i < 40; i++)
// {
// // _connection.WriteInput(L"01234567890123456789012345678901234567890123456789 ");
// _connection.WriteInput(L"0123456789 123456789 ");
// _connection.WriteInput({ std::wstring(80 - (i * 2), static_cast<wchar_t>(L'@' + i)) });
// _connection.WriteInput(L"\r\n");
// }
// }
return true;
}

View file

@ -178,19 +178,89 @@ void Terminal::UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSetting
const short newBufferHeight = viewportSize.Y + _scrollbackLines;
COORD bufferSize{ viewportSize.X, newBufferHeight };
RETURN_IF_FAILED(_buffer->ResizeTraditional(bufferSize));
auto proposedTop = oldTop;
// Save cursor's relative height versus the viewport
const short sCursorHeightInViewportBefore = _buffer->GetCursor().GetPosition().Y - _mutableViewport.Top();
// This will be used to determine where the viewport should be in the new buffer.
short oldViewportTop = _mutableViewport.Top();
// First allocate a new text buffer to take the place of the current one.
std::unique_ptr<TextBuffer> newTextBuffer;
try
{
newTextBuffer = std::make_unique<TextBuffer>(bufferSize,
_buffer->GetCurrentAttributes(),
0, // temporarily set size to 0 so it won't render.
_buffer->GetRenderTarget());
}
CATCH_RETURN();
RETURN_IF_FAILED(TextBuffer::Reflow(*_buffer.get(),
*newTextBuffer.get(),
_mutableViewport,
&oldViewportTop));
// Conpty resizes a little oddly - if the height decreased, and there were
// blank lines at the bottom, those lines will get trimmed. If there's not
// blank lines, then the top will get "shifted down", moving the top line
// into scrollback. See GH#3490 for more details.
//
// If the final position in the buffer is on the bottom row of the new
// viewport, then we're going to need to move the top down. Otherwise, move
// the bottom up.
//
// There are also important things to consider with line wrapping.
// * If a line in scrollback wrapped that didn't previously, we'll need to
// make sure to have the new viewport down another line. This will cause
// our top to move down.
// * If a line _in the viewport_ wrapped that didn't previously, then the
// conpty buffer will also have that wrapped line, and will move the
// cursor & text down a line in response. This causes our bottom to move
// down.
//
// We're going to use a combo of both these things to calculate where the
// new viewport should be. To keep in sync with conpty, we'll need to make
// sure that any lines that entered the scrollback _stay in scrollback_. We
// do that by taking the max of
// * Where the old top line in the viewport exists in the new buffer (as
// calculated by TextBuffer::Reflow)
// * Where the bottom of the text in the new buffer is (and using that to
// calculate another proposed top location).
const COORD newCursorPos = newTextBuffer->GetCursor().GetPosition();
#pragma warning(push)
#pragma warning(disable : 26496) // cpp core checks wants this const, but it's assigned immediately below...
COORD newLastChar = newCursorPos;
try
{
newLastChar = newTextBuffer->GetLastNonSpaceCharacter(_mutableViewport);
}
CATCH_LOG();
#pragma warning(pop)
const auto maxRow = std::max(newLastChar.Y, newCursorPos.Y);
const short proposedTopFromLastLine = ::base::saturated_cast<short>(maxRow - viewportSize.Y + 1);
const short proposedTopFromScrollback = oldViewportTop;
short proposedTop = std::max(proposedTopFromLastLine,
proposedTopFromScrollback);
const auto newView = Viewport::FromDimensions({ 0, proposedTop }, viewportSize);
const auto proposedBottom = newView.BottomExclusive();
// If the new bottom would be below the bottom of the buffer, then slide the
// top up so that we'll still fit within the buffer.
if (proposedBottom > bufferSize.Y)
{
proposedTop -= (proposedBottom - bufferSize.Y);
proposedTop = ::base::saturated_cast<short>(proposedTop - (proposedBottom - bufferSize.Y));
}
_mutableViewport = Viewport::FromDimensions({ 0, proposedTop }, viewportSize);
_buffer.swap(newTextBuffer);
_scrollOffset = 0;
_NotifyScrollEvent();

View file

@ -115,6 +115,9 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
auto pfn = std::bind(&ConptyRoundtripTests::_writeCallback, this, std::placeholders::_1, std::placeholders::_2);
_pVtRenderEngine->SetTestCallback(pfn);
// Enable the resize quirk, as the Terminal is going to be reacting as if it's enabled.
_pVtRenderEngine->SetResizeQuirk(true);
// Configure the OutputStateMachine's _pfnFlushToTerminal
// Use OutputStateMachineEngine::SetTerminalConnection
g.pRender->AddRenderEngine(_pVtRenderEngine.get());
@ -162,6 +165,8 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
TEST_METHOD(MoveCursorAtEOL);
TEST_METHOD(TestResizeHeight);
private:
bool _writeCallback(const char* const pch, size_t const cch);
void _flushFirstFrame();
@ -550,6 +555,7 @@ void ConptyRoundtripTests::TestExactWrappingWithSpaces()
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& hostSm = si.GetStateMachine();
auto& hostTb = si.GetTextBuffer();
auto& termTb = *term->_buffer;
const auto initialTermView = term->GetViewport();
@ -620,6 +626,7 @@ void ConptyRoundtripTests::MoveCursorAtEOL()
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& hostSm = si.GetStateMachine();
auto& hostTb = si.GetTextBuffer();
auto& termTb = *term->_buffer;
_flushFirstFrame();
@ -673,6 +680,230 @@ void ConptyRoundtripTests::MoveCursorAtEOL()
verifyData1(termTb);
}
void ConptyRoundtripTests::TestResizeHeight()
{
// This test class is _60_ tests to ensure that resizing the terminal works
// with conpty correctly. There's a lot of min/maxing in expressions here,
// to account for the sheer number of cases here, and that we have to handle
// both resizing larger and smaller all in one test.
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
TEST_METHOD_PROPERTY(L"Data:dx", L"{-1, 0, 1}")
TEST_METHOD_PROPERTY(L"Data:dy", L"{-10, -1, 0, 1, 10}")
TEST_METHOD_PROPERTY(L"Data:printedRows", L"{1, 10, 50, 200}")
END_TEST_METHOD_PROPERTIES()
int dx, dy;
int printedRows;
VERIFY_SUCCEEDED(TestData::TryGetValue(L"dx", dx), L"change in width of buffer");
VERIFY_SUCCEEDED(TestData::TryGetValue(L"dy", dy), L"change in height of buffer");
VERIFY_SUCCEEDED(TestData::TryGetValue(L"printedRows", printedRows), L"Number of rows of text to print");
_checkConptyOutput = false;
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& hostSm = si.GetStateMachine();
auto* hostTb = &si.GetTextBuffer();
auto* termTb = term->_buffer.get();
const auto initialHostView = si.GetViewport();
const auto initialTermView = term->GetViewport();
const auto initialTerminalBufferHeight = term->GetTextBuffer().GetSize().Height();
VERIFY_ARE_EQUAL(0, initialHostView.Top());
VERIFY_ARE_EQUAL(TerminalViewHeight, initialHostView.BottomExclusive());
VERIFY_ARE_EQUAL(0, initialTermView.Top());
VERIFY_ARE_EQUAL(TerminalViewHeight, initialTermView.BottomExclusive());
Log::Comment(NoThrowString().Format(
L"Print %d lines of output, which will scroll the viewport", printedRows));
for (auto i = 0; i < printedRows; i++)
{
// This looks insane, but this expression is carefully crafted to give
// us only printable characters, starting with `!` (0n33).
// Similar statements are used elsewhere throughout this test.
auto wstr = std::wstring(1, static_cast<wchar_t>((i) % 93) + 33);
hostSm.ProcessString(wstr);
hostSm.ProcessString(L"\r\n");
}
// Conpty doesn't have a scrollback, it's view's origin is always 0,0
const auto secondHostView = si.GetViewport();
VERIFY_ARE_EQUAL(0, secondHostView.Top());
VERIFY_ARE_EQUAL(TerminalViewHeight, secondHostView.BottomExclusive());
VERIFY_SUCCEEDED(renderer.PaintFrame());
const auto secondTermView = term->GetViewport();
// If we've printed more lines than the height of the buffer, then we're
// expecting the viewport to have moved down. Otherwise, the terminal's
// viewport will stay at 0,0.
const auto expectedTerminalViewBottom = std::max(std::min(::base::saturated_cast<short>(printedRows + 1),
term->GetBufferHeight()),
term->GetViewport().Height());
VERIFY_ARE_EQUAL(expectedTerminalViewBottom, secondTermView.BottomExclusive());
VERIFY_ARE_EQUAL(expectedTerminalViewBottom - initialTermView.Height(), secondTermView.Top());
auto verifyTermData = [&expectedTerminalViewBottom, &printedRows, this, &initialTerminalBufferHeight](TextBuffer& termTb, const int resizeDy = 0) {
// Some number of lines of text were lost from the scrollback. The
// number of lines lost will be determined by whichever of the initial
// or current buffer is smaller.
const auto numLostRows = std::max(0,
printedRows - std::min(term->GetTextBuffer().GetSize().Height(), initialTerminalBufferHeight) + 1);
const auto rowsWithText = std::min(::base::saturated_cast<short>(printedRows),
expectedTerminalViewBottom) -
1 + std::min(resizeDy, 0);
for (short row = 0; row < rowsWithText; row++)
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
auto iter = termTb.GetCellDataAt({ 0, row });
const wchar_t expectedChar = static_cast<wchar_t>((row + numLostRows) % 93) + 33;
auto expectedString = std::wstring(1, expectedChar);
if (iter->Chars() != expectedString)
{
Log::Comment(NoThrowString().Format(L"row [%d] was mismatched", row));
}
VERIFY_ARE_EQUAL(expectedString, (iter++)->Chars());
VERIFY_ARE_EQUAL(L" ", (iter)->Chars());
}
};
auto verifyHostData = [&si, &initialHostView, &printedRows](TextBuffer& hostTb, const int resizeDy = 0) {
const auto hostView = si.GetViewport();
// In the host, there are two regions we're interested in:
// 1. the first section of the buffer with the output in it. Before
// we're resized, this will be filled with one character on each row.
// 2. The second area below the first that's empty (filled with spaces).
// Initially, this is only one row.
// After we resize, different things will happen.
// * If we decrease the height of the buffer, the characters in the
// buffer will all move _up_ the same number of rows. We'll want to
// only check the first initialView+dy rows for characters.
// * If we increase the height, rows will be added at the bottom. We'll
// want to check the initial viewport height for the original
// characters, but then we'll want to look for more blank rows at the
// bottom. The characters in the initial viewport won't have moved.
const short originalViewHeight = ::base::saturated_cast<short>(resizeDy < 0 ?
initialHostView.Height() + resizeDy :
initialHostView.Height());
const auto rowsWithText = std::min(originalViewHeight - 1, printedRows);
const bool scrolled = printedRows > initialHostView.Height();
// The last row of the viewport should be empty
// The second last row will have '0'+50
// The third last row will have '0'+49
// ...
// The <height> last row will have '0'+(50-height+1)
const auto firstChar = static_cast<wchar_t>(scrolled ?
(printedRows - originalViewHeight + 1) :
0);
short row = 0;
// Don't include the last row of the viewport in this check, since it'll
// be blank. We'll check it in the below loop.
for (; row < rowsWithText; row++)
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
auto iter = hostTb.GetCellDataAt({ 0, row });
const auto expectedChar = static_cast<wchar_t>(((firstChar + row) % 93) + 33);
auto expectedString = std::wstring(1, static_cast<wchar_t>(expectedChar));
if (iter->Chars() != expectedString)
{
Log::Comment(NoThrowString().Format(L"row [%d] was mismatched", row));
}
VERIFY_ARE_EQUAL(expectedString, (iter++)->Chars(), NoThrowString().Format(L"%s", expectedString.data()));
VERIFY_ARE_EQUAL(L" ", (iter)->Chars());
}
// Check that the remaining rows in the viewport are empty.
for (; row < hostView.Height(); row++)
{
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
auto iter = hostTb.GetCellDataAt({ 0, row });
VERIFY_ARE_EQUAL(L" ", (iter)->Chars());
}
};
verifyHostData(*hostTb);
verifyTermData(*termTb);
const COORD newViewportSize{
::base::saturated_cast<short>(TerminalViewWidth + dx),
::base::saturated_cast<short>(TerminalViewHeight + dy)
};
Log::Comment(NoThrowString().Format(L"Resize the Terminal and conpty here"));
auto resizeResult = term->UserResize(newViewportSize);
VERIFY_SUCCEEDED(resizeResult);
_resizeConpty(newViewportSize.X, newViewportSize.Y);
// After we resize, make sure to get the new textBuffers
hostTb = &si.GetTextBuffer();
termTb = term->_buffer.get();
// Conpty's doesn't have a scrollback, it's view's origin is always 0,0
const auto thirdHostView = si.GetViewport();
VERIFY_ARE_EQUAL(0, thirdHostView.Top());
VERIFY_ARE_EQUAL(newViewportSize.Y, thirdHostView.BottomExclusive());
// The Terminal should be stuck to the top of the viewport, unless dy<0,
// rows=50. In that set of cases, we _didn't_ pin the top of the Terminal to
// the old top, we actually shifted it down (because the output was at the
// bottom of the window, not empty lines).
const auto thirdTermView = term->GetViewport();
if (dy < 0 && (printedRows > initialTermView.Height() && printedRows < initialTerminalBufferHeight))
{
VERIFY_ARE_EQUAL(secondTermView.Top() - dy, thirdTermView.Top());
VERIFY_ARE_EQUAL(expectedTerminalViewBottom, thirdTermView.BottomExclusive());
}
else
{
VERIFY_ARE_EQUAL(secondTermView.Top(), thirdTermView.Top());
VERIFY_ARE_EQUAL(expectedTerminalViewBottom + dy, thirdTermView.BottomExclusive());
}
verifyHostData(*hostTb, dy);
// Note that at this point, nothing should have changed with the Terminal.
verifyTermData(*termTb, dy);
Log::Comment(NoThrowString().Format(L"Paint a frame to update the Terminal"));
VERIFY_SUCCEEDED(renderer.PaintFrame());
// Conpty's doesn't have a scrollback, it's view's origin is always 0,0
const auto fourthHostView = si.GetViewport();
VERIFY_ARE_EQUAL(0, fourthHostView.Top());
VERIFY_ARE_EQUAL(newViewportSize.Y, fourthHostView.BottomExclusive());
// The Terminal should be stuck to the top of the viewport, unless dy<0,
// rows=50. In that set of cases, we _didn't_ pin the top of the Terminal to
// the old top, we actually shifted it down (because the output was at the
// bottom of the window, not empty lines).
const auto fourthTermView = term->GetViewport();
if (dy < 0 && (printedRows > initialTermView.Height() && printedRows < initialTerminalBufferHeight))
{
VERIFY_ARE_EQUAL(secondTermView.Top() - dy, thirdTermView.Top());
VERIFY_ARE_EQUAL(expectedTerminalViewBottom, thirdTermView.BottomExclusive());
}
else
{
VERIFY_ARE_EQUAL(secondTermView.Top(), thirdTermView.Top());
VERIFY_ARE_EQUAL(expectedTerminalViewBottom + dy, thirdTermView.BottomExclusive());
}
verifyHostData(*hostTb, dy);
verifyTermData(*termTb, dy);
}
void ConptyRoundtripTests::PassthroughClearScrollback()
{
Log::Comment(NoThrowString().Format(
@ -684,6 +915,7 @@ void ConptyRoundtripTests::PassthroughClearScrollback()
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& hostSm = si.GetStateMachine();
auto& termTb = *term->_buffer;
_flushFirstFrame();

View file

@ -18,6 +18,7 @@ const std::wstring_view ConsoleArguments::FILEPATH_LEADER_PREFIX = L"\\??\\";
const std::wstring_view ConsoleArguments::WIDTH_ARG = L"--width";
const std::wstring_view ConsoleArguments::HEIGHT_ARG = L"--height";
const std::wstring_view ConsoleArguments::INHERIT_CURSOR_ARG = L"--inheritcursor";
const std::wstring_view ConsoleArguments::RESIZE_QUIRK = L"--resizeQuirk";
const std::wstring_view ConsoleArguments::FEATURE_ARG = L"--feature";
const std::wstring_view ConsoleArguments::FEATURE_PTY_ARG = L"pty";
@ -479,6 +480,12 @@ void ConsoleArguments::s_ConsumeArg(_Inout_ std::vector<std::wstring>& args, _In
s_ConsumeArg(args, i);
hr = S_OK;
}
else if (arg == RESIZE_QUIRK)
{
_resizeQuirk = true;
s_ConsumeArg(args, i);
hr = S_OK;
}
else if (arg == CLIENT_COMMANDLINE_ARG)
{
// Everything after this is the explicit commandline
@ -611,6 +618,10 @@ bool ConsoleArguments::GetInheritCursor() const
{
return _inheritCursor;
}
bool ConsoleArguments::IsResizeQuirkEnabled() const
{
return _resizeQuirk;
}
// Method Description:
// - Tell us to use a different size than the one parsed as the size of the

View file

@ -50,6 +50,7 @@ public:
short GetWidth() const;
short GetHeight() const;
bool GetInheritCursor() const;
bool IsResizeQuirkEnabled() const;
void SetExpectedSize(COORD dimensions) noexcept;
@ -68,6 +69,7 @@ public:
static const std::wstring_view WIDTH_ARG;
static const std::wstring_view HEIGHT_ARG;
static const std::wstring_view INHERIT_CURSOR_ARG;
static const std::wstring_view RESIZE_QUIRK;
static const std::wstring_view FEATURE_ARG;
static const std::wstring_view FEATURE_PTY_ARG;
@ -100,6 +102,7 @@ private:
_serverHandle(serverHandle),
_signalHandle(signalHandle),
_inheritCursor(inheritCursor),
_resizeQuirk(false),
_receivedEarlySizeChange{ false },
_originalWidth{ -1 },
_originalHeight{ -1 }
@ -127,6 +130,7 @@ private:
DWORD _serverHandle;
DWORD _signalHandle;
bool _inheritCursor;
bool _resizeQuirk{ false };
bool _receivedEarlySizeChange;
short _originalWidth;

View file

@ -73,6 +73,7 @@ VtIo::VtIo() :
[[nodiscard]] HRESULT VtIo::Initialize(const ConsoleArguments* const pArgs)
{
_lookingForCursorPosition = pArgs->GetInheritCursor();
_resizeQuirk = pArgs->IsResizeQuirkEnabled();
// If we were already given VT handles, set up the VT IO engine to use those.
if (pArgs->InConptyMode())
@ -192,6 +193,7 @@ VtIo::VtIo() :
if (_pVtRenderEngine)
{
_pVtRenderEngine->SetTerminalOwner(this);
_pVtRenderEngine->SetResizeQuirk(_resizeQuirk);
}
}
}
@ -446,3 +448,19 @@ void VtIo::EnableConptyModeForTests()
_objectsCreated = true;
}
#endif
// Method Description:
// - Returns true if the Resize Quirk is enabled. This changes the behavior of
// conpty to _not_ InvalidateAll the entire viewport on a resize operation.
// This is used by the Windows Terminal, because it is prepared to be
// connected to a conpty, and handles it's own buffer specifically for a
// conpty scenario.
// - See also: GH#3490, #4354, #4741
// Arguments:
// - <none>
// Return Value:
// - true iff we were started with the `--resizeQuirk` flag enabled.
bool VtIo::IsResizeQuirkEnabled() const
{
return _resizeQuirk;
}

View file

@ -43,6 +43,8 @@ namespace Microsoft::Console::VirtualTerminal
void EnableConptyModeForTests();
#endif
bool IsResizeQuirkEnabled() const;
private:
// After CreateIoHandlers is called, these will be invalid.
wil::unique_hfile _hInput;
@ -57,6 +59,8 @@ namespace Microsoft::Console::VirtualTerminal
bool _lookingForCursorPosition;
std::mutex _shutdownLock;
bool _resizeQuirk{ false };
std::unique_ptr<Microsoft::Console::Render::VtEngine> _pVtRenderEngine;
std::unique_ptr<Microsoft::Console::VtInputThread> _pVtInputThread;
std::unique_ptr<Microsoft::Console::PtySignalInputThread> _pPtySignalInputThread;

View file

@ -776,7 +776,15 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
{
// TODO: MSFT: 9574827 - shouldn't we be looking at or at least logging the failure codes here? (Or making them non-void?)
context.PostUpdateWindowSize();
WriteToScreen(context, context.GetViewport());
// Use WriteToScreen to invalidate the viewport with the renderer.
// GH#3490 - If we're in conpty mode, don't invalidate the entire
// viewport. In conpty mode, the VtEngine will later decide what
// part of the buffer actually needs to be re-sent to the terminal.
if (!(g.getConsoleInformation().IsInVtIoMode() && g.getConsoleInformation().GetVtIo()->IsResizeQuirkEnabled()))
{
WriteToScreen(context, context.GetViewport());
}
}
return S_OK;
}

View file

@ -1428,6 +1428,9 @@ bool SCREEN_INFORMATION::IsMaximizedY() const
_textBuffer.swap(newTextBuffer);
}
// const auto cursorPos = _textBuffer->GetCursor().GetPosition();
// GetRenderTarget().TriggerRedrawCursor(&cursorPos);
return NTSTATUS_FROM_HRESULT(hr);
}
@ -2176,8 +2179,13 @@ void SCREEN_INFORMATION::SetDefaultAttributes(const TextAttribute& attributes,
commandLine.UpdatePopups(attributes, popupAttributes, oldPrimaryAttributes, oldPopupAttributes);
}
// force repaint of entire viewport
GetRenderTarget().TriggerRedrawAll();
// Force repaint of entire viewport, unless we're in conpty mode. In that
// case, we don't really need to force a redraw of the entire screen just
// because the text attributes changed.
if (!(gci.IsInVtIoMode()))
{
GetRenderTarget().TriggerRedrawAll();
}
gci.ConsoleIme.RefreshAreaAttributes();

View file

@ -15,6 +15,8 @@
extern "C" {
#endif
#define PSEUDOCONSOLE_RESIZE_QUIRK (2u)
HRESULT WINAPI ConptyCreatePseudoConsole(COORD size, HANDLE hInput, HANDLE hOutput, DWORD dwFlags, HPCON* phPC);
HRESULT WINAPI ConptyResizePseudoConsole(HPCON hPC, COORD size);

View file

@ -278,38 +278,50 @@ VtEngine::VtEngine(_In_ wil::unique_hfile pipe,
// lead to the first _actual_ resize being suppressed.
_suppressResizeRepaint = false;
if (SUCCEEDED(hr))
if (_resizeQuirk)
{
// Viewport is smaller now - just update it all.
if (oldView.Height() > newView.Height() || oldView.Width() > newView.Width())
// GH#3490 - When the viewport width changed, don't do anything extra here.
// If the buffer had areas that were invalid due to the resize, then the
// buffer will have triggered it's own invalidations for what it knows is
// invalid. Previously, we'd invalidate everything if the width changed,
// because we couldn't be sure if lines were reflowed.
}
else
{
if (SUCCEEDED(hr))
{
hr = InvalidateAll();
}
else
{
// At least one of the directions grew.
// First try and add everything to the right of the old viewport,
// then everything below where the old viewport ended.
if (oldView.Width() < newView.Width())
// Viewport is smaller now - just update it all.
if (oldView.Height() > newView.Height() || oldView.Width() > newView.Width())
{
short left = oldView.RightExclusive();
short top = 0;
short right = newView.RightInclusive();
short bottom = oldView.BottomInclusive();
Viewport rightOfOldViewport = Viewport::FromInclusive({ left, top, right, bottom });
hr = _InvalidCombine(rightOfOldViewport);
hr = InvalidateAll();
}
if (SUCCEEDED(hr) && oldView.Height() < newView.Height())
else
{
short left = 0;
short top = oldView.BottomExclusive();
short right = newView.RightInclusive();
short bottom = newView.BottomInclusive();
Viewport belowOldViewport = Viewport::FromInclusive({ left, top, right, bottom });
hr = _InvalidCombine(belowOldViewport);
// At least one of the directions grew.
// First try and add everything to the right of the old viewport,
// then everything below where the old viewport ended.
if (oldView.Width() < newView.Width())
{
short left = oldView.RightExclusive();
short top = 0;
short right = newView.RightInclusive();
short bottom = oldView.BottomInclusive();
Viewport rightOfOldViewport = Viewport::FromInclusive({ left, top, right, bottom });
hr = _InvalidCombine(rightOfOldViewport);
}
if (SUCCEEDED(hr) && oldView.Height() < newView.Height())
{
short left = 0;
short top = oldView.BottomExclusive();
short right = newView.RightInclusive();
short bottom = newView.BottomInclusive();
Viewport belowOldViewport = Viewport::FromInclusive({ left, top, right, bottom });
hr = _InvalidCombine(belowOldViewport);
}
}
}
}
_resized = true;
return hr;
}
@ -456,3 +468,19 @@ void VtEngine::EndResizeRequest()
{
_inResizeRequest = false;
}
// Method Description:
// - Configure the renderer for the resize quirk. This changes the behavior of
// conpty to _not_ InvalidateAll the entire viewport on a resize operation.
// This is used by the Windows Terminal, because it is prepared to be
// connected to a conpty, and handles it's own buffer specifically for a
// conpty scenario.
// - See also: GH#3490, #4354, #4741
// Arguments:
// - <none>
// Return Value:
// - true iff we were started with the `--resizeQuirk` flag enabled.
void VtEngine::SetResizeQuirk(const bool resizeQuirk)
{
_resizeQuirk = resizeQuirk;
}

View file

@ -228,17 +228,20 @@ void RenderTracing::TraceLastText(const COORD lastTextPos) const
void RenderTracing::TraceMoveCursor(const COORD lastTextPos, const COORD cursor) const
{
#ifndef UNIT_TESTING
const auto lastTextStr = _CoordToString(lastTextPos);
const auto lastText = lastTextStr.c_str();
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
const auto lastTextStr = _CoordToString(lastTextPos);
const auto lastText = lastTextStr.c_str();
const auto cursorStr = _CoordToString(cursor);
const auto cursorPos = cursorStr.c_str();
const auto cursorStr = _CoordToString(cursor);
const auto cursorPos = cursorStr.c_str();
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
"VtEngine_TraceMoveCursor",
TraceLoggingString(lastText),
TraceLoggingString(cursorPos),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
"VtEngine_TraceMoveCursor",
TraceLoggingString(lastText),
TraceLoggingString(cursorPos),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
}
#else
UNREFERENCED_PARAMETER(lastTextPos);
UNREFERENCED_PARAMETER(cursor);
@ -248,11 +251,14 @@ void RenderTracing::TraceMoveCursor(const COORD lastTextPos, const COORD cursor)
void RenderTracing::TraceWrapped() const
{
#ifndef UNIT_TESTING
const auto* const msg = "Wrapped instead of \\r\\n";
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
"VtEngine_TraceWrapped",
TraceLoggingString(msg),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
const auto* const msg = "Wrapped instead of \\r\\n";
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
"VtEngine_TraceWrapped",
TraceLoggingString(msg),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
}
#else
#endif UNIT_TESTING
}
@ -260,12 +266,15 @@ void RenderTracing::TraceWrapped() const
void RenderTracing::TracePaintCursor(const COORD coordCursor) const
{
#ifndef UNIT_TESTING
const auto cursorPosString = _CoordToString(coordCursor);
const auto cursorPos = cursorPosString.c_str();
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
"VtEngine_TracePaintCursor",
TraceLoggingString(cursorPos),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
const auto cursorPosString = _CoordToString(coordCursor);
const auto cursorPos = cursorPosString.c_str();
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
"VtEngine_TracePaintCursor",
TraceLoggingString(cursorPos),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
}
#else
UNREFERENCED_PARAMETER(coordCursor);
#endif UNIT_TESTING

View file

@ -106,6 +106,8 @@ namespace Microsoft::Console::Render
void BeginResizeRequest();
void EndResizeRequest();
void SetResizeQuirk(const bool resizeQuirk);
protected:
wil::unique_hfile _hFile;
std::string _buffer;
@ -149,6 +151,8 @@ namespace Microsoft::Console::Render
bool _delayedEolWrap{ false };
bool _resizeQuirk{ false };
[[nodiscard]] HRESULT _Write(std::string_view const str) noexcept;
[[nodiscard]] HRESULT _WriteFormattedString(const std::string* const pFormat, ...) noexcept;
[[nodiscard]] HRESULT _Flush() noexcept;

View file

@ -83,15 +83,17 @@ HRESULT _CreatePseudoConsole(const HANDLE hToken,
RETURN_IF_WIN32_BOOL_FALSE(SetHandleInformation(signalPipeConhostSide.get(), HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT));
// GH4061: Ensure that the path to executable in the format is escaped so C:\Program.exe cannot collide with C:\Program Files
const wchar_t* pwszFormat = L"\"%s\" --headless %s--width %hu --height %hu --signal 0x%x --server 0x%x";
const wchar_t* pwszFormat = L"\"%s\" --headless %s%s --width %hu --height %hu --signal 0x%x --server 0x%x";
// This is plenty of space to hold the formatted string
wchar_t cmd[MAX_PATH]{};
const BOOL bInheritCursor = (dwFlags & PSEUDOCONSOLE_INHERIT_CURSOR) == PSEUDOCONSOLE_INHERIT_CURSOR;
const BOOL bResizeQuirk = (dwFlags & PSEUDOCONSOLE_RESIZE_QUIRK) == PSEUDOCONSOLE_RESIZE_QUIRK;
swprintf_s(cmd,
MAX_PATH,
pwszFormat,
_ConsoleHostPath(),
bInheritCursor ? L"--inheritcursor " : L"",
bResizeQuirk ? L"--resizeQuirk" : L"",
size.X,
size.Y,
signalPipeConhostSide.get(),

View file

@ -19,6 +19,9 @@ typedef struct _PseudoConsole
// the signal pipe.
#define PTY_SIGNAL_RESIZE_WINDOW (8u)
//
#define PSEUDOCONSOLE_RESIZE_QUIRK (2u)
// Implementations of the various PseudoConsole functions.
HRESULT _CreatePseudoConsole(const HANDLE hToken,
const COORD size,