Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correct fill attributes when scrolling and erasing #3100

Merged
18 commits merged into from
Dec 10, 2019
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
4c09e68
Add private scroll region API with support for standard erase attribu…
j4james Sep 30, 2019
093b94f
Update scrolling operations to use the new API that sets the fill att…
j4james Oct 1, 2019
b41d2ff
Update existing screen buffer tests to check that the scrolling opera…
j4james Oct 1, 2019
6f2ef8f
Update the line feed handling to set the fill attributes correctly fo…
j4james Oct 2, 2019
6aa3e97
Update and add screen buffer tests to check that line feeds initializ…
j4james Oct 2, 2019
32321a9
Add private fill region API with support for standard erase attributes.
j4james Oct 2, 2019
ba1e16f
Update the erase operations to use the new API that sets the fill att…
j4james Oct 2, 2019
a83fd64
Update the VtEraseAll implementation to use the correct fill attribut…
j4james Oct 2, 2019
480a924
Update the EraseScrollback implementation to use the new APIs that se…
j4james Oct 5, 2019
d85c7e2
Replace the adapter tests with more appropriate screen buffer tests f…
j4james Oct 5, 2019
ff8a329
Remove the unneeded FillConsoleOutputCharacterW, FillConsoleOutputAtt…
j4james Oct 6, 2019
655184c
Removed the unneeded hacks for handling legacy default colors in the …
j4james Oct 6, 2019
11b6644
Only clear the meta attributes while incrementing the circular buffer…
j4james Oct 8, 2019
e5c273d
Initialize the alt buffer with the standard erase attributes.
j4james Oct 10, 2019
d03f24a
Generate an accessibility update event in the DoSrvPrivateFillRegion …
j4james Oct 16, 2019
3099d1a
Make sure we're resetting both the extended and meta attributes for s…
j4james Nov 25, 2019
786cc36
Add a SetStandardErase method to the TextAttribute class to simplify …
j4james Dec 10, 2019
171a4f9
Update the comments to document the new argument in the IncrementCirc…
j4james Dec 10, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/buffer/out/textBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -542,14 +542,22 @@ bool TextBuffer::NewlineCursor()
// - <none>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arguments comment not updated.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I totally overlooked that. Added in commit 171a4f9

//Return Value:
// - true if we successfully incremented the buffer.
bool TextBuffer::IncrementCircularBuffer()
bool TextBuffer::IncrementCircularBuffer(const bool inVtMode)
{
// FirstRow is at any given point in time the array index in the circular buffer that corresponds
// to the logical position 0 in the window (cursor coordinates and all other coordinates).
_renderTarget.TriggerCircling();

// First, clean out the old "first row" as it will become the "last row" of the buffer after the circle is performed.
const bool fSuccess = _storage.at(_firstRow).Reset(_currentAttributes);
auto fillAttributes = _currentAttributes;
if (inVtMode)
{
// The VT standard requires that the new row is initialized with
// the current background color, but with no meta attributes set.
fillAttributes.SetExtendedAttributes(ExtendedAttributes::Normal);
fillAttributes.SetMetaAttributes(0);
}
const bool fSuccess = _storage.at(_firstRow).Reset(fillAttributes);
if (fSuccess)
{
// Now proceed to increment.
Expand Down
2 changes: 1 addition & 1 deletion src/buffer/out/textBuffer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ class TextBuffer final
bool NewlineCursor();

// Scroll needs access to this to quickly rotate around the buffer.
bool IncrementCircularBuffer();
bool IncrementCircularBuffer(const bool inVtMode = false);

COORD GetLastNonSpaceCharacter() const;
COORD GetLastNonSpaceCharacter(const Microsoft::Console::Types::Viewport viewport) const;
Expand Down
18 changes: 0 additions & 18 deletions src/host/_output.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -219,24 +219,6 @@ void WriteToScreen(SCREEN_INFORMATION& screenInfo, const Viewport& region)
try
{
TextAttribute useThisAttr(attribute);

// Here we're being a little clever -
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved
// Because RGB color can't roundtrip the API, certain VT sequences will forget the RGB color
// because their first call to GetScreenBufferInfo returned a legacy attr.
// If they're calling this with the default attrs, they likely wanted to use the RGB default attrs.
// This could create a scenario where someone emitted RGB with VT,
// THEN used the API to FillConsoleOutput with the default attrs, and DIDN'T want the RGB color
// they had set.
if (screenBuffer.InVTMode())
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto bufferLegacy = gci.GenerateLegacyAttributes(screenBuffer.GetAttributes());
if (bufferLegacy == attribute)
{
useThisAttr = TextAttribute(screenBuffer.GetAttributes());
}
}

const OutputCellIterator it(useThisAttr, lengthToWrite);
const auto done = screenBuffer.Write(it, startingCoordinate);

Expand Down
10 changes: 7 additions & 3 deletions src/host/_stream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,11 @@ using Microsoft::Console::VirtualTerminal::StateMachine;
}
}

const auto bufferAttributes = screenInfo.GetAttributes();
// The VT standard requires the lines revealed when scrolling are filled
// with the current background color, but with no meta attributes set.
auto fillAttributes = screenInfo.GetAttributes();
fillAttributes.SetExtendedAttributes(ExtendedAttributes::Normal);
fillAttributes.SetMetaAttributes(0);
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved

const auto relativeMargins = screenInfo.GetRelativeScrollMargins();
auto viewport = screenInfo.GetViewport();
Expand Down Expand Up @@ -142,7 +146,7 @@ using Microsoft::Console::VirtualTerminal::StateMachine;

try
{
ScrollRegion(screenInfo, scrollRect, std::nullopt, newPostMarginsOrigin, UNICODE_SPACE, bufferAttributes);
ScrollRegion(screenInfo, scrollRect, std::nullopt, newPostMarginsOrigin, UNICODE_SPACE, fillAttributes);
}
CATCH_LOG();

Expand Down Expand Up @@ -191,7 +195,7 @@ using Microsoft::Console::VirtualTerminal::StateMachine;

try
{
ScrollRegion(screenInfo, scrollRect, scrollRect, dest, UNICODE_SPACE, bufferAttributes);
ScrollRegion(screenInfo, scrollRect, scrollRect, dest, UNICODE_SPACE, fillAttributes);
}
CATCH_LOG();

Expand Down
174 changes: 110 additions & 64 deletions src/host/getset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -836,32 +836,6 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
auto& buffer = context.GetActiveBuffer();

TextAttribute useThisAttr(fillAttribute);

// Here we're being a little clever - similar to FillConsoleOutputAttributeImpl
// Because RGB/default color can't roundtrip the API, certain VT
// sequences will forget the RGB color because their first call to
// GetScreenBufferInfo returned a legacy attr.
// If they're calling this with the legacy attrs version of our current
// attributes, they likely wanted to use the full version of
// our current attributes, whether that be RGB or _default_ colored.
// This could create a scenario where someone emitted RGB with VT,
// THEN used the API to ScrollConsoleOutput with the legacy attrs,
// and DIDN'T want the RGB color. As in FillConsoleOutputAttribute,
// this scenario is highly unlikely, and we can reasonably do this
// on their behalf.
// see MSFT:19853701

if (buffer.InVTMode())
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto currentAttributes = buffer.GetAttributes();
const auto bufferLegacy = gci.GenerateLegacyAttributes(currentAttributes);
if (bufferLegacy == fillAttribute)
{
useThisAttr = currentAttributes;
}
}

ScrollRegion(buffer, source, clip, target, fillCharacter, useThisAttr);

return S_OK;
Expand Down Expand Up @@ -1422,25 +1396,12 @@ void DoSrvPrivateAllowCursorBlinking(SCREEN_INFORMATION& screenInfo, const bool
coordDestination.X = 0;
coordDestination.Y = viewport.Top + 1;

// Here we previously called to ScrollConsoleScreenBufferWImpl to
// perform the scrolling operation. However, that function only
// accepts a WORD for the fill attributes. That means we'd lose
// 256/RGB fidelity for fill attributes. So instead, we'll just call
// ScrollRegion ourselves, with the same params that
// ScrollConsoleScreenBufferWImpl would have.
// See microsoft/terminal#832, #2702 for more context.
try
{
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
ScrollRegion(screenInfo,
srScroll,
srScroll,
coordDestination,
UNICODE_SPACE,
screenInfo.GetAttributes());
}
CATCH_LOG();
// Note the revealed lines are filled with the standard erase attributes.
Status = NTSTATUS_FROM_HRESULT(DoSrvPrivateScrollRegion(screenInfo,
srScroll,
srScroll,
coordDestination,
true));
}
}
return Status;
Expand Down Expand Up @@ -2149,25 +2110,12 @@ void DoSrvPrivateModifyLinesImpl(const unsigned int count, const bool insert)
coordDestination.Y = (cursorPosition.Y) - gsl::narrow<short>(count);
}

// Here we previously called to ScrollConsoleScreenBufferWImpl to
// perform the scrolling operation. However, that function only accepts
// a WORD for the fill attributes. That means we'd lose 256/RGB fidelity
// for fill attributes. So instead, we'll just call ScrollRegion
// ourselves, with the same params that ScrollConsoleScreenBufferWImpl
// would have.
// See microsoft/terminal#832 for more context.
try
{
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
ScrollRegion(screenInfo,
srScroll,
srScroll,
coordDestination,
UNICODE_SPACE,
screenInfo.GetAttributes());
}
CATCH_LOG();
// Note the revealed lines are filled with the standard erase attributes.
LOG_IF_FAILED(DoSrvPrivateScrollRegion(screenInfo,
srScroll,
srScroll,
coordDestination,
true));

// The IL and DL controls are also expected to move the cursor to the left margin.
// For now this is just column 0, since we don't yet support DECSLRM.
Expand Down Expand Up @@ -2291,3 +2239,101 @@ void DoSrvPrivateMoveToBottom(SCREEN_INFORMATION& screenInfo)
}
CATCH_RETURN();
}

// Routine Description:
// - A private API call for filling a region of the screen buffer.
// Arguments:
// - screenInfo - Reference to screen buffer info.
// - startPosition - The position to begin filling at.
// - fillLength - The number of characters to fill.
// - fillChar - Character to fill the target region with.
// - standardFillAttrs - If true, fill with the standard erase attributes.
// If false, fill with the default attributes.
// Return value:
// - S_OK or failure code from thrown exception
[[nodiscard]] HRESULT DoSrvPrivateFillRegion(SCREEN_INFORMATION& screenInfo,
const COORD startPosition,
const size_t fillLength,
const wchar_t fillChar,
const bool standardFillAttrs) noexcept
{
try
{
if (fillLength == 0)
{
return S_OK;
}

LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });

// For most VT erasing operations, the standard requires that the
// erased area be filled with the current background color, but with
// no additional meta attributes set. For all other cases, we just
// fill with the default attributes.
auto fillAttrs = TextAttribute{};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: TextAttribute fillAttrs{};?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ARGH i apparently wrote this comment eighteen months ago and never published it
literally don't care now
😄

if (standardFillAttrs)
{
fillAttrs = screenInfo.GetAttributes();
fillAttrs.SetExtendedAttributes(ExtendedAttributes::Normal);
fillAttrs.SetMetaAttributes(0);
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved
}

const auto fillData = OutputCellIterator{ fillChar, fillAttrs, fillLength };
screenInfo.Write(fillData, startPosition, false);

// Notify accessibility
auto endPosition = startPosition;
const auto bufferSize = screenInfo.GetBufferSize();
bufferSize.MoveInBounds(fillLength - 1, endPosition);
screenInfo.NotifyAccessibilityEventing(startPosition.X, startPosition.Y, endPosition.X, endPosition.Y);
return S_OK;
}
CATCH_RETURN();
}

// Routine Description:
// - A private API call for moving a block of data in the screen buffer,
// optionally limiting the effects of the move to a clipping rectangle.
// Arguments:
// - screenInfo - Reference to screen buffer info.
// - scrollRect - Region to copy/move (source and size).
// - clipRect - Optional clip region to contain buffer change effects.
// - destinationOrigin - Upper left corner of target region.
// - standardFillAttrs - If true, fill with the standard erase attributes.
// If false, fill with the default attributes.
// Return value:
// - S_OK or failure code from thrown exception
[[nodiscard]] HRESULT DoSrvPrivateScrollRegion(SCREEN_INFORMATION& screenInfo,
const SMALL_RECT scrollRect,
const std::optional<SMALL_RECT> clipRect,
const COORD destinationOrigin,
const bool standardFillAttrs) noexcept
{
try
{
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });

// For most VT scrolling operations, the standard requires that the
// erased area be filled with the current background color, but with
// no additional meta attributes set. For all other cases, we just
// fill with the default attributes.
auto fillAttrs = TextAttribute{};
if (standardFillAttrs)
{
fillAttrs = screenInfo.GetAttributes();
fillAttrs.SetExtendedAttributes(ExtendedAttributes::Normal);
fillAttrs.SetMetaAttributes(0);
}

ScrollRegion(screenInfo,
scrollRect,
clipRect,
destinationOrigin,
UNICODE_SPACE,
fillAttrs);
return S_OK;
}
CATCH_RETURN();
}
12 changes: 12 additions & 0 deletions src/host/getset.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,15 @@ void DoSrvPrivateMoveToBottom(SCREEN_INFORMATION& screenInfo);
[[nodiscard]] HRESULT DoSrvPrivateSetDefaultForegroundColor(const COLORREF value) noexcept;

[[nodiscard]] HRESULT DoSrvPrivateSetDefaultBackgroundColor(const COLORREF value) noexcept;

[[nodiscard]] HRESULT DoSrvPrivateFillRegion(SCREEN_INFORMATION& screenInfo,
const COORD startPosition,
const size_t fillLength,
const wchar_t fillChar,
const bool standardFillAttrs) noexcept;

[[nodiscard]] HRESULT DoSrvPrivateScrollRegion(SCREEN_INFORMATION& screenInfo,
const SMALL_RECT scrollRect,
const std::optional<SMALL_RECT> clipRect,
const COORD destinationOrigin,
const bool standardFillAttrs) noexcept;
3 changes: 2 additions & 1 deletion src/host/output.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,8 @@ static void _ScrollScreen(SCREEN_INFORMATION& screenInfo, const Viewport& source
bool StreamScrollRegion(SCREEN_INFORMATION& screenInfo)
{
// Rotate the circular buffer around and wipe out the previous final line.
bool fSuccess = screenInfo.GetTextBuffer().IncrementCircularBuffer();
const bool inVtMode = WI_IsFlagSet(screenInfo.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING);
bool fSuccess = screenInfo.GetTextBuffer().IncrementCircularBuffer(inVtMode);
if (fSuccess)
{
// Trigger a graphical update if we're active.
Expand Down
Loading