Compare commits
54 commits
main
...
dev/migrie
Author | SHA1 | Date | |
---|---|---|---|
b2463a131f | |||
5ef1488316 | |||
01755f77af | |||
162d3fea67 | |||
f2d1d95055 | |||
60229a2cdc | |||
646a63b52e | |||
1470abe1fe | |||
f1ff8345c6 | |||
4849dab5dc | |||
58aa64da55 | |||
658295eab0 | |||
1448c52228 | |||
bb3d9102e7 | |||
7a177100e1 | |||
25c593f2ea | |||
e7c55117a7 | |||
e6c2746900 | |||
8ca2a4abf8 | |||
cc7b2d34e1 | |||
2ef2d88d0a | |||
0f283b4723 | |||
0c91a9b4d4 | |||
1d87f66792 | |||
e65371393d | |||
de9911df02 | |||
2e815c8954 | |||
097b62c896 | |||
edea9a335c | |||
95807154b8 | |||
74a528357c | |||
1788cb1e45 | |||
5a72af9822 | |||
96642deb39 | |||
b3de042c46 | |||
e0d251c349 | |||
40b4966782 | |||
0a98cceddb | |||
9b6554b10f | |||
7fd5d515b2 | |||
86623f57d5 | |||
0755fd73e1 | |||
c040a82e62 | |||
e000388baf | |||
bfde821b2f | |||
4a7f2e4f7b | |||
ce3138c685 | |||
edeb346325 | |||
7f341a25a5 | |||
416be4656f | |||
aae6ce60a4 | |||
1a2654d291 | |||
9aec69467c | |||
b5c8c854cc |
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue