terminal/src/host/screenInfo.cpp
James Holderness b604117421
Standardize the color table order (#11602)
## Summary of the Pull Request

In the original implementation, we used two different orderings for the color tables. The WT color table used ANSI order, while the conhost color table used a Windows-specific order. This PR standardizes on the ANSI color order everywhere, so the usage of indexed colors is consistent across both parts of the code base, which will hopefully allow more of the code to be shared one day.

## References

This is another small step towards de-duplicating `AdaptDispatch` and `TerminalDispatch` for issue #3849, and is essentially a followup to the SGR dispatch refactoring in PR #6728.

## PR Checklist
* [x] Closes #11461
* [x] CLA signed.
* [x] Tests added/passed
* [ ] Documentation updated.
* [ ] Schema updated.
* [x] I've discussed this with core contributors already. Issue number where discussion took place: #11461

## Detailed Description of the Pull Request / Additional comments

Conhost still needs to deal with legacy attributes using Windows color order, so those values now need to be transposed to ANSI colors order when creating a `TextAttribute` object. This is done with a simple mapping table, which also handles the translation of the default color entries, so it's actually slightly faster than the original code.

And when converting `TextAttribute` values back to legacy console attributes, we were already using a mapping table to handle the narrowing of 256-color values down to 16 colors, so we just needed to adjust that table to account for the translation from ANSI to Windows, and then could make use of the same table for both 256-color and 16-color values.

There are also a few places in conhost that read from or write to the color tables, and those now need to transpose the index values. I've addressed this by creating separate `SetLegacyColorTableEntry` and `GetLegacyColorTableEntry` methods in the `Settings` class which take care of the mapping, so it's now clearer in which cases the code is dealing with legacy values, and which are ANSI values.

These methods are used in the `SetConsoleScreenBufferInfoEx` and `GetConsoleScreenBufferInfoEx` APIs, as well as a few place where color preferences are handled (the registry, shortcut links, and the properties dialog), none of which are particularly sensitive to performance. However, we also use the legacy table when looking up the default colors for rendering (which happens a lot), so I've refactored that code so the default color calculations now only occur once per frame.

The plus side of all of this is that the VT code doesn't need to do the index translation anymore, so we can finally get rid of all the calls to `XTermToWindowsIndex`, and we no longer need a separate color table initialization method for conhost, so I was able to merge a number of color initialization methods into one. We also no longer need to translate from legacy values to ANSI when generating VT sequences for conpty.

The one exception to that is the 16-color VT renderer, which uses the `TextColor::GetLegacyIndex` method to approximate 16-color equivalents for RGB and 256-color values. Since that method returns a legacy index, it still needs to be translated to ANSI before it can be used in a VT sequence. But this should be no worse than it was before.

One more special case is conhost's secret _Color Selection_ feature. That uses `Ctrl`+Number and `Alt`+Number key sequences to highlight parts of the buffer, and the mapping from number to color is based on the Windows color order. So that mapping now needs to be transposed, but that's also not performance sensitive.

The only thing that I haven't bothered to update is the trace logging code in the `Telemetry` class, which logs the first 16 entries in the color table. Those entries are now going to be in a different order, but I didn't think that would be of great concern to anyone.

## Validation Steps Performed

A lot of unit tests needed to be updated to use ANSI color constants when setting indexed colors, where before they might have been expecting values in Windows order. But this replaced a wild mix of different constants, sometimes having to use bit shifting, as well as values mapped with `XTermToWindowsIndex`, so I think the tests are a whole lot clearer now. Only a few cases have been left with literal numbers where that seemed more appropriate.

In addition to getting the unit tests working, I've also manually tested the behaviour of all the console APIs which I thought could be affected by these changes, and confirmed that they produced the same results in the new code as they did in the original implementation.

This includes:
- `WriteConsoleOutput`
- `ReadConsoleOutput`
- `SetConsoleTextAttribute` with `WriteConsoleOutputCharacter`
- `FillConsoleOutputAttribute` and `FillConsoleOutputCharacter` 
- `ScrollConsoleScreenBuffer`
- `GetConsoleScreenBufferInfo`
- `GetConsoleScreenBufferInfoEx`
- `SetConsoleScreenBufferInfoEx`

I've also manually tested changing colors via the console properties menu, the registry, and shortcut links, including setting default colors and popup colors. And I've tested that the "Quirks Mode" is still working as expected in PowerShell.

In terms of performance, I wrote a little test app that filled a 80x9999 buffer with random color combinations using `WriteConsoleOutput`, which I figured was likely to be the most performance sensitive call, and I think it now actually performs slightly better than the original implementation.

I've also tested similar code - just filling the visible window - with SGR VT sequences of various types, and the performance seems about the same as it was before.
2021-11-04 22:13:22 +00:00

2797 lines
106 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "screenInfo.hpp"
#include "dbcs.h"
#include "output.h"
#include "_output.h"
#include "misc.h"
#include "handle.h"
#include "../buffer/out/CharRow.hpp"
#include <cmath>
#include "../interactivity/inc/ServiceLocator.hpp"
#include "../types/inc/Viewport.hpp"
#include "../types/inc/GlyphWidth.hpp"
#include "../terminal/parser/OutputStateMachineEngine.hpp"
#include "../types/inc/convert.hpp"
#pragma hdrstop
using namespace Microsoft::Console;
using namespace Microsoft::Console::Types;
using namespace Microsoft::Console::Render;
using namespace Microsoft::Console::Interactivity;
using namespace Microsoft::Console::VirtualTerminal;
#pragma region Construct_Destruct
SCREEN_INFORMATION::SCREEN_INFORMATION(
_In_ IWindowMetrics* pMetrics,
_In_ IAccessibilityNotifier* pNotifier,
const TextAttribute popupAttributes,
const FontInfo fontInfo) :
OutputMode{ ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT },
ResizingWindow{ 0 },
WheelDelta{ 0 },
HWheelDelta{ 0 },
_textBuffer{ nullptr },
Next{ nullptr },
WriteConsoleDbcsLeadByte{ 0, 0 },
FillOutDbcsLeadChar{ 0 },
ConvScreenInfo{ nullptr },
ScrollScale{ 1ul },
_pConsoleWindowMetrics{ pMetrics },
_pAccessibilityNotifier{ pNotifier },
_stateMachine{ nullptr },
_scrollMargins{ Viewport::FromCoord({ 0 }) },
_viewport(Viewport::Empty()),
_psiAlternateBuffer{ nullptr },
_psiMainBuffer{ nullptr },
_rcAltSavedClientNew{ 0 },
_rcAltSavedClientOld{ 0 },
_fAltWindowChanged{ false },
_PopupAttributes{ popupAttributes },
_virtualBottom{ 0 },
_renderTarget{ *this },
_currentFont{ fontInfo },
_desiredFont{ fontInfo },
_ignoreLegacyEquivalentVTAttributes{ false }
{
// Check if VT mode is enabled. Note that this can be true w/o calling
// SetConsoleMode, if VirtualTerminalLevel is set to !=0 in the registry.
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
if (gci.GetVirtTermLevel() != 0)
{
OutputMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
}
}
// Routine Description:
// - This routine frees the memory associated with a screen buffer.
// Arguments:
// - ScreenInfo - screen buffer data to free.
// Return Value:
// Note:
// - console handle table lock must be held when calling this routine
SCREEN_INFORMATION::~SCREEN_INFORMATION()
{
_FreeOutputStateMachine();
}
// Routine Description:
// - This routine allocates and initializes the data associated with a screen buffer.
// Arguments:
// - ScreenInformation - the new screen buffer.
// - coordWindowSize - the initial size of screen buffer's window (in rows/columns)
// - nFont - the initial font to generate text with.
// - dwScreenBufferSize - the initial size of the screen buffer (in rows/columns).
// Return Value:
[[nodiscard]] NTSTATUS SCREEN_INFORMATION::CreateInstance(_In_ COORD coordWindowSize,
const FontInfo fontInfo,
_In_ COORD coordScreenBufferSize,
const TextAttribute defaultAttributes,
const TextAttribute popupAttributes,
const UINT uiCursorSize,
_Outptr_ SCREEN_INFORMATION** const ppScreen)
{
*ppScreen = nullptr;
try
{
IWindowMetrics* pMetrics = ServiceLocator::LocateWindowMetrics();
THROW_HR_IF_NULL(E_FAIL, pMetrics);
IAccessibilityNotifier* pNotifier = ServiceLocator::LocateAccessibilityNotifier();
// It is possible for pNotifier to be null and that's OK.
// For instance, the PTY doesn't need to send events. Just pass it along
// and be sure that `SCREEN_INFORMATION` bypasses all event work if it's not there.
SCREEN_INFORMATION* const pScreen = new SCREEN_INFORMATION(pMetrics, pNotifier, popupAttributes, fontInfo);
// Set up viewport
pScreen->_viewport = Viewport::FromDimensions({ 0, 0 },
pScreen->_IsInPtyMode() ? coordScreenBufferSize : coordWindowSize);
pScreen->UpdateBottom();
// Set up text buffer
pScreen->_textBuffer = std::make_unique<TextBuffer>(coordScreenBufferSize,
defaultAttributes,
uiCursorSize,
pScreen->_renderTarget);
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
pScreen->_textBuffer->GetCursor().SetColor(gci.GetCursorColor());
pScreen->_textBuffer->GetCursor().SetType(gci.GetCursorType());
const NTSTATUS status = pScreen->_InitializeOutputStateMachine();
if (NT_SUCCESS(status))
{
*ppScreen = pScreen;
}
LOG_IF_NTSTATUS_FAILED(status);
return status;
}
catch (...)
{
return NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException());
}
}
Viewport SCREEN_INFORMATION::GetBufferSize() const
{
return _textBuffer->GetSize();
}
// Method Description:
// - Returns the "terminal" dimensions of this buffer. If we're in Terminal
// Scrolling mode, this will return our Y dimension as only extending up to
// the _virtualBottom. The height of the returned viewport would then be
// (number of lines in scrollback) + (number of lines in viewport).
// If we're not in terminal scrolling mode, this will return our normal buffer
// size.
// Arguments:
// - <none>
// Return Value:
// - a viewport whose height is the height of the "terminal" portion of the
// buffer in terminal scrolling mode, and is the height of the full buffer
// in normal scrolling mode.
Viewport SCREEN_INFORMATION::GetTerminalBufferSize() const
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
Viewport v = _textBuffer->GetSize();
if (gci.IsTerminalScrolling() && v.Height() > _virtualBottom)
{
v = Viewport::FromDimensions({ 0, 0 }, v.Width(), _virtualBottom + 1);
}
return v;
}
const StateMachine& SCREEN_INFORMATION::GetStateMachine() const
{
return *_stateMachine;
}
StateMachine& SCREEN_INFORMATION::GetStateMachine()
{
return *_stateMachine;
}
// Routine Description:
// - This routine inserts the screen buffer pointer into the console's list of screen buffers.
// Arguments:
// - ScreenInfo - Pointer to screen information structure.
// Return Value:
// Note:
// - The console lock must be held when calling this routine.
void SCREEN_INFORMATION::s_InsertScreenBuffer(_In_ SCREEN_INFORMATION* const pScreenInfo)
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
FAIL_FAST_IF(!(gci.IsConsoleLocked()));
pScreenInfo->Next = gci.ScreenBuffers;
gci.ScreenBuffers = pScreenInfo;
}
// Routine Description:
// - This routine removes the screen buffer pointer from the console's list of screen buffers.
// Arguments:
// - ScreenInfo - Pointer to screen information structure.
// Return Value:
// Note:
// - The console lock must be held when calling this routine.
void SCREEN_INFORMATION::s_RemoveScreenBuffer(_In_ SCREEN_INFORMATION* const pScreenInfo)
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
if (pScreenInfo == gci.ScreenBuffers)
{
gci.ScreenBuffers = pScreenInfo->Next;
}
else
{
auto* Cur = gci.ScreenBuffers;
auto* Prev = Cur;
while (Cur != nullptr)
{
if (pScreenInfo == Cur)
{
break;
}
Prev = Cur;
Cur = Cur->Next;
}
FAIL_FAST_IF_NULL(Cur);
Prev->Next = Cur->Next;
}
if (pScreenInfo == gci.pCurrentScreenBuffer &&
gci.ScreenBuffers != gci.pCurrentScreenBuffer)
{
if (gci.ScreenBuffers != nullptr)
{
SetActiveScreenBuffer(*gci.ScreenBuffers);
}
else
{
gci.pCurrentScreenBuffer = nullptr;
}
}
delete pScreenInfo;
}
#pragma endregion
#pragma region Output State Machine
[[nodiscard]] NTSTATUS SCREEN_INFORMATION::_InitializeOutputStateMachine()
{
try
{
auto getset = std::make_unique<ConhostInternalGetSet>(*this);
auto defaults = std::make_unique<WriteBuffer>(*this);
auto adapter = std::make_unique<AdaptDispatch>(std::move(getset), std::move(defaults));
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(adapter));
// Note that at this point in the setup, we haven't determined if we're
// in VtIo mode or not yet. We'll set the OutputStateMachine's
// TerminalConnection later, in VtIo::StartIfNeeded
_stateMachine = std::make_shared<StateMachine>(std::move(engine));
}
catch (...)
{
// if any part of initialization failed, free the allocated ones.
_FreeOutputStateMachine();
return NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException());
}
return STATUS_SUCCESS;
}
// If we're an alternate buffer, we want to give the GetSet back to our main
void SCREEN_INFORMATION::_FreeOutputStateMachine()
{
if (_psiMainBuffer == nullptr) // If this is a main buffer
{
if (_psiAlternateBuffer != nullptr)
{
s_RemoveScreenBuffer(_psiAlternateBuffer);
}
_stateMachine.reset();
}
}
#pragma endregion
#pragma region IIoProvider
// Method Description:
// - Return the active screen buffer of the console.
// Arguments:
// - <none>
// Return Value:
// - the active screen buffer of the console.
SCREEN_INFORMATION& SCREEN_INFORMATION::GetActiveOutputBuffer()
{
return GetActiveBuffer();
}
const SCREEN_INFORMATION& SCREEN_INFORMATION::GetActiveOutputBuffer() const
{
return GetActiveBuffer();
}
// Method Description:
// - Return the active input buffer of the console.
// Arguments:
// - <none>
// Return Value:
// - the active input buffer of the console.
InputBuffer* const SCREEN_INFORMATION::GetActiveInputBuffer() const
{
return ServiceLocator::LocateGlobals().getConsoleInformation().GetActiveInputBuffer();
}
#pragma endregion
#pragma region Get Data
bool SCREEN_INFORMATION::IsActiveScreenBuffer() const
{
// the following macro returns TRUE if the given screen buffer is the active screen buffer.
//#define ACTIVE_SCREEN_BUFFER(SCREEN_INFO) (gci.CurrentScreenBuffer == SCREEN_INFO)
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
return (gci.pCurrentScreenBuffer == this);
}
// Routine Description:
// - This routine returns data about the screen buffer.
// Arguments:
// - Size - Pointer to location in which to store screen buffer size.
// - CursorPosition - Pointer to location in which to store the cursor position.
// - ScrollPosition - Pointer to location in which to store the scroll position.
// - Attributes - Pointer to location in which to store the default attributes.
// - CurrentWindowSize - Pointer to location in which to store current window size.
// - MaximumWindowSize - Pointer to location in which to store maximum window size.
// Return Value:
// - None
void SCREEN_INFORMATION::GetScreenBufferInformation(_Out_ PCOORD pcoordSize,
_Out_ PCOORD pcoordCursorPosition,
_Out_ PSMALL_RECT psrWindow,
_Out_ PWORD pwAttributes,
_Out_ PCOORD pcoordMaximumWindowSize,
_Out_ PWORD pwPopupAttributes,
_Out_writes_(COLOR_TABLE_SIZE) LPCOLORREF lpColorTable) const
{
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
*pcoordSize = GetBufferSize().Dimensions();
*pcoordCursorPosition = _textBuffer->GetCursor().GetPosition();
*psrWindow = _viewport.ToInclusive();
*pwAttributes = GetAttributes().GetLegacyAttributes();
*pwPopupAttributes = _PopupAttributes.GetLegacyAttributes();
// the copy length must be constant for now to keep OACR happy with buffer overruns.
for (size_t i = 0; i < COLOR_TABLE_SIZE; i++)
{
lpColorTable[i] = gci.GetLegacyColorTableEntry(i);
}
*pcoordMaximumWindowSize = GetMaxWindowSizeInCharacters();
}
// Routine Description:
// - Gets the smallest possible client area in characters. Takes the window client area and divides by the active font dimensions.
// Arguments:
// - coordFontSize - The font size to use for calculation if a screen buffer is not yet attached.
// Return Value:
// - COORD containing the width and height representing the minimum character grid that can be rendered in the window.
COORD SCREEN_INFORMATION::GetMinWindowSizeInCharacters(const COORD coordFontSize /*= { 1, 1 }*/) const
{
FAIL_FAST_IF(coordFontSize.X == 0);
FAIL_FAST_IF(coordFontSize.Y == 0);
// prepare rectangle
RECT const rcWindowInPixels = _pConsoleWindowMetrics->GetMinClientRectInPixels();
// assign the pixel widths and heights to the final output
COORD coordClientAreaSize;
coordClientAreaSize.X = (SHORT)RECT_WIDTH(&rcWindowInPixels);
coordClientAreaSize.Y = (SHORT)RECT_HEIGHT(&rcWindowInPixels);
// now retrieve the font size and divide the pixel counts into character counts
COORD coordFont = coordFontSize; // by default, use the size we were given
// If text info has been set up, instead retrieve its font size
if (_textBuffer != nullptr)
{
coordFont = GetScreenFontSize();
}
FAIL_FAST_IF(coordFont.X == 0);
FAIL_FAST_IF(coordFont.Y == 0);
coordClientAreaSize.X /= coordFont.X;
coordClientAreaSize.Y /= coordFont.Y;
return coordClientAreaSize;
}
// Routine Description:
// - Gets the maximum client area in characters that would fit on the current monitor or given the current buffer size.
// Takes the monitor work area and divides by the active font dimensions then limits by buffer size.
// Arguments:
// - coordFontSize - The font size to use for calculation if a screen buffer is not yet attached.
// Return Value:
// - COORD containing the width and height representing the largest character
// grid that can be rendered on the current monitor and/or from the current buffer size.
COORD SCREEN_INFORMATION::GetMaxWindowSizeInCharacters(const COORD coordFontSize /*= { 1, 1 }*/) const
{
FAIL_FAST_IF(coordFontSize.X == 0);
FAIL_FAST_IF(coordFontSize.Y == 0);
const COORD coordScreenBufferSize = GetBufferSize().Dimensions();
COORD coordClientAreaSize = coordScreenBufferSize;
// Important re: headless consoles on onecore (for telnetd, etc.)
// GetConsoleScreenBufferInfoEx hits this to get the max size of the display.
// Because we're headless, we don't really care about the max size of the display.
// In that case, we'll just return the buffer size as the "max" window size.
if (!ServiceLocator::LocateGlobals().IsHeadless())
{
const COORD coordWindowRestrictedSize = GetLargestWindowSizeInCharacters(coordFontSize);
// If the buffer is smaller than what the max window would allow, then the max client area can only be as big as the
// buffer we have.
coordClientAreaSize.X = std::min(coordScreenBufferSize.X, coordWindowRestrictedSize.X);
coordClientAreaSize.Y = std::min(coordScreenBufferSize.Y, coordWindowRestrictedSize.Y);
}
return coordClientAreaSize;
}
// Routine Description:
// - Gets the largest possible client area in characters if the window were stretched as large as it could go.
// - Takes the window client area and divides by the active font dimensions.
// Arguments:
// - coordFontSize - The font size to use for calculation if a screen buffer is not yet attached.
// Return Value:
// - COORD containing the width and height representing the largest character
// grid that can be rendered on the current monitor with the maximum size window.
COORD SCREEN_INFORMATION::GetLargestWindowSizeInCharacters(const COORD coordFontSize /*= { 1, 1 }*/) const
{
FAIL_FAST_IF(coordFontSize.X == 0);
FAIL_FAST_IF(coordFontSize.Y == 0);
RECT const rcClientInPixels = _pConsoleWindowMetrics->GetMaxClientRectInPixels();
// first assign the pixel widths and heights to the final output
COORD coordClientAreaSize;
coordClientAreaSize.X = (SHORT)RECT_WIDTH(&rcClientInPixels);
coordClientAreaSize.Y = (SHORT)RECT_HEIGHT(&rcClientInPixels);
// now retrieve the font size and divide the pixel counts into character counts
COORD coordFont = coordFontSize; // by default, use the size we were given
// If renderer has been set up, instead retrieve its font size
if (ServiceLocator::LocateGlobals().pRender != nullptr)
{
coordFont = GetScreenFontSize();
}
FAIL_FAST_IF(coordFont.X == 0);
FAIL_FAST_IF(coordFont.Y == 0);
coordClientAreaSize.X /= coordFont.X;
coordClientAreaSize.Y /= coordFont.Y;
return coordClientAreaSize;
}
COORD SCREEN_INFORMATION::GetScrollBarSizesInCharacters() const
{
COORD coordFont = GetScreenFontSize();
SHORT vScrollSize = ServiceLocator::LocateGlobals().sVerticalScrollSize;
SHORT hScrollSize = ServiceLocator::LocateGlobals().sHorizontalScrollSize;
COORD coordBarSizes;
coordBarSizes.X = (vScrollSize / coordFont.X) + ((vScrollSize % coordFont.X) != 0 ? 1 : 0);
coordBarSizes.Y = (hScrollSize / coordFont.Y) + ((hScrollSize % coordFont.Y) != 0 ? 1 : 0);
return coordBarSizes;
}
void SCREEN_INFORMATION::GetRequiredConsoleSizeInPixels(_Out_ PSIZE const pRequiredSize) const
{
COORD const coordFontSize = GetCurrentFont().GetSize();
// TODO: Assert valid size boundaries
pRequiredSize->cx = GetViewport().Width() * coordFontSize.X;
pRequiredSize->cy = GetViewport().Height() * coordFontSize.Y;
}
COORD SCREEN_INFORMATION::GetScreenFontSize() const
{
// If we have no renderer, then we don't really need any sort of pixel math. so the "font size" for the scale factor
// (which is used almost everywhere around the code as * and / calls) should just be 1,1 so those operations will do
// effectively nothing.
COORD coordRet = { 1, 1 };
if (ServiceLocator::LocateGlobals().pRender != nullptr)
{
coordRet = GetCurrentFont().GetSize();
}
// For sanity's sake, make sure not to leak 0 out as a possible value. These values are used in division operations.
coordRet.X = std::max(coordRet.X, 1i16);
coordRet.Y = std::max(coordRet.Y, 1i16);
return coordRet;
}
#pragma endregion
#pragma region Set Data
void SCREEN_INFORMATION::RefreshFontWithRenderer()
{
if (IsActiveScreenBuffer())
{
// Hand the handle to our internal structure to the font change trigger in case it updates it based on what's appropriate.
if (ServiceLocator::LocateGlobals().pRender != nullptr)
{
ServiceLocator::LocateGlobals().pRender->TriggerFontChange(ServiceLocator::LocateGlobals().dpi,
GetDesiredFont(),
GetCurrentFont());
NotifyGlyphWidthFontChanged();
}
}
}
void SCREEN_INFORMATION::UpdateFont(const FontInfo* const pfiNewFont)
{
FontInfoDesired fiDesiredFont(*pfiNewFont);
GetDesiredFont() = fiDesiredFont;
RefreshFontWithRenderer();
// If we're the active screen buffer...
if (IsActiveScreenBuffer())
{
// If there is a window attached, let it know that it should try to update so the rows/columns are now accounting for the new font.
IConsoleWindow* const pWindow = ServiceLocator::LocateConsoleWindow();
if (nullptr != pWindow)
{
COORD coordViewport = GetViewport().Dimensions();
pWindow->UpdateWindowSize(coordViewport);
}
}
// If we're an alt buffer, also update our main buffer.
if (_psiMainBuffer)
{
_psiMainBuffer->UpdateFont(pfiNewFont);
}
}
// Routine Description:
// - Informs clients whether we have accessibility eventing so they can
// save themselves the work of performing math or lookups before calling
// `NotifyAccessibilityEventing`.
// Arguments:
// - <none>
// Return Value:
// - True if we have an accessibility listener. False otherwise.
bool SCREEN_INFORMATION::HasAccessibilityEventing() const noexcept
{
return _pAccessibilityNotifier;
}
// NOTE: This method was historically used to notify accessibility apps AND
// to aggregate drawing metadata to determine whether or not to use PolyTextOut.
// After the Nov 2015 graphics refactor, the metadata drawing flag calculation is no longer necessary.
// This now only notifies accessibility apps of a change.
void SCREEN_INFORMATION::NotifyAccessibilityEventing(const short sStartX,
const short sStartY,
const short sEndX,
const short sEndY)
{
if (!_pAccessibilityNotifier)
{
return;
}
// Fire off a winevent to let accessibility apps know what changed.
if (IsActiveScreenBuffer())
{
const COORD coordScreenBufferSize = GetBufferSize().Dimensions();
FAIL_FAST_IF(!(sEndX < coordScreenBufferSize.X));
if (sStartX == sEndX && sStartY == sEndY)
{
try
{
const auto cellData = GetCellDataAt({ sStartX, sStartY });
const LONG charAndAttr = MAKELONG(Utf16ToUcs2(cellData->Chars()),
cellData->TextAttr().GetLegacyAttributes());
_pAccessibilityNotifier->NotifyConsoleUpdateSimpleEvent(MAKELONG(sStartX, sStartY),
charAndAttr);
}
catch (...)
{
LOG_HR(wil::ResultFromCaughtException());
return;
}
}
else
{
_pAccessibilityNotifier->NotifyConsoleUpdateRegionEvent(MAKELONG(sStartX, sStartY),
MAKELONG(sEndX, sEndY));
}
IConsoleWindow* pConsoleWindow = ServiceLocator::LocateConsoleWindow();
if (pConsoleWindow)
{
LOG_IF_FAILED(pConsoleWindow->SignalUia(UIA_Text_TextChangedEventId));
// TODO MSFT 7960168 do we really need this event to not signal?
//pConsoleWindow->SignalUia(UIA_LayoutInvalidatedEventId);
}
}
}
#pragma endregion
#pragma region UI_Refresh
VOID SCREEN_INFORMATION::UpdateScrollBars()
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
if (!IsActiveScreenBuffer())
{
return;
}
if (gci.Flags & CONSOLE_UPDATING_SCROLL_BARS)
{
return;
}
gci.Flags |= CONSOLE_UPDATING_SCROLL_BARS;
if (ServiceLocator::LocateConsoleWindow() != nullptr)
{
ServiceLocator::LocateConsoleWindow()->PostUpdateScrollBars();
}
}
VOID SCREEN_INFORMATION::InternalUpdateScrollBars()
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
IConsoleWindow* const pWindow = ServiceLocator::LocateConsoleWindow();
WI_ClearFlag(gci.Flags, CONSOLE_UPDATING_SCROLL_BARS);
if (!IsActiveScreenBuffer())
{
return;
}
ResizingWindow++;
if (pWindow != nullptr)
{
const auto buffer = GetBufferSize();
// If this is the main buffer, make sure we enable both of the scroll bars.
// The alt buffer likely disabled the scroll bars, this is the only
// way to re-enable it.
if (!_IsAltBuffer())
{
pWindow->EnableBothScrollBars();
}
pWindow->UpdateScrollBar(true,
_IsAltBuffer() || gci.IsTerminalScrolling(),
_viewport.Height(),
gci.IsTerminalScrolling() ? _virtualBottom : buffer.BottomInclusive(),
_viewport.Top());
pWindow->UpdateScrollBar(false,
_IsAltBuffer(),
_viewport.Width(),
buffer.RightInclusive(),
_viewport.Left());
}
// Fire off an event to let accessibility apps know the layout has changed.
if (_pAccessibilityNotifier)
{
_pAccessibilityNotifier->NotifyConsoleLayoutEvent();
}
ResizingWindow--;
}
// Routine Description:
// - Modifies the size of the current viewport to match the width/height of the request given.
// - This will act like a resize operation from the bottom right corner of the window.
// Arguments:
// - pcoordSize - Requested viewport width/heights in characters
// Return Value:
// - <none>
void SCREEN_INFORMATION::SetViewportSize(const COORD* const pcoordSize)
{
// If this is the alt buffer or a VT I/O buffer:
// first resize ourselves to match the new viewport
// then also make sure that the main buffer gets the same call
// (if necessary)
if (_IsInPtyMode())
{
LOG_IF_FAILED(ResizeScreenBuffer(*pcoordSize, TRUE));
if (_psiMainBuffer)
{
const auto bufferSize = GetBufferSize().Dimensions();
_psiMainBuffer->SetViewportSize(&bufferSize);
}
}
_InternalSetViewportSize(pcoordSize, false, false);
}
// Method Description:
// - Update the origin of the buffer's viewport. You can either move the
// viewport with a delta relative to its current location, or set its
// absolute origin. Either way leaves the dimensions of the viewport
// unchanged. Also potentially updates our "virtual bottom", the last real
// location of the viewport in the buffer.
// Also notifies the window implementation to update its scrollbars.
// Arguments:
// - fAbsolute: If true, coordWindowOrigin is the absolute location of the origin of the new viewport.
// If false, coordWindowOrigin is a delta to move the viewport relative to its current position.
// - coordWindowOrigin: Either the new absolute position of the origin of the
// viewport, or a delta to add to the current viewport location.
// - updateBottom: If true, update our virtual bottom position. This should be
// false if we're moving the viewport in response to the users scrolling up
// and down in the buffer, but API calls should set this to true.
// Return Value:
// - STATUS_INVALID_PARAMETER if the new viewport would be outside the buffer,
// else STATUS_SUCCESS
[[nodiscard]] NTSTATUS SCREEN_INFORMATION::SetViewportOrigin(const bool fAbsolute,
const COORD coordWindowOrigin,
const bool updateBottom)
{
// calculate window size
COORD WindowSize = _viewport.Dimensions();
SMALL_RECT NewWindow;
// if relative coordinates, figure out absolute coords.
if (!fAbsolute)
{
if (coordWindowOrigin.X == 0 && coordWindowOrigin.Y == 0)
{
return STATUS_SUCCESS;
}
NewWindow.Left = _viewport.Left() + coordWindowOrigin.X;
NewWindow.Top = _viewport.Top() + coordWindowOrigin.Y;
}
else
{
if (coordWindowOrigin == _viewport.Origin())
{
return STATUS_SUCCESS;
}
NewWindow.Left = coordWindowOrigin.X;
NewWindow.Top = coordWindowOrigin.Y;
}
NewWindow.Right = (SHORT)(NewWindow.Left + WindowSize.X - 1);
NewWindow.Bottom = (SHORT)(NewWindow.Top + WindowSize.Y - 1);
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
// If we're in terminal scrolling mode, and we're trying to set the viewport
// below the logical viewport, without updating our virtual bottom
// (the logical viewport's position), dont.
// Instead move us to the bottom of the logical viewport.
if (gci.IsTerminalScrolling() && !updateBottom && NewWindow.Bottom > _virtualBottom)
{
const short delta = _virtualBottom - NewWindow.Bottom;
NewWindow.Top += delta;
NewWindow.Bottom += delta;
}
// see if new window origin would extend window beyond extent of screen buffer
const COORD coordScreenBufferSize = GetBufferSize().Dimensions();
if (NewWindow.Left < 0 ||
NewWindow.Top < 0 ||
NewWindow.Right < 0 ||
NewWindow.Bottom < 0 ||
NewWindow.Right >= coordScreenBufferSize.X ||
NewWindow.Bottom >= coordScreenBufferSize.Y)
{
return STATUS_INVALID_PARAMETER;
}
if (IsActiveScreenBuffer() && ServiceLocator::LocateConsoleWindow() != nullptr)
{
// Tell the window that it needs to set itself to the new origin if we're the active buffer.
ServiceLocator::LocateConsoleWindow()->ChangeViewport(NewWindow);
}
else
{
// Otherwise, just store the new position and go on.
_viewport = Viewport::FromInclusive(NewWindow);
Tracing::s_TraceWindowViewport(_viewport);
}
// Update our internal virtual bottom tracker if requested. This helps keep
// the viewport's logical position consistent from the perspective of a
// VT client application, even if the user scrolls the viewport with the mouse.
if (updateBottom)
{
UpdateBottom();
}
return STATUS_SUCCESS;
}
bool SCREEN_INFORMATION::SendNotifyBeep() const
{
if (IsActiveScreenBuffer())
{
if (ServiceLocator::LocateConsoleWindow() != nullptr)
{
return ServiceLocator::LocateConsoleWindow()->SendNotifyBeep();
}
}
return false;
}
bool SCREEN_INFORMATION::PostUpdateWindowSize() const
{
if (IsActiveScreenBuffer())
{
if (ServiceLocator::LocateConsoleWindow() != nullptr)
{
return ServiceLocator::LocateConsoleWindow()->PostUpdateWindowSize();
}
}
return false;
}
// Routine Description:
// - Modifies the screen buffer and viewport dimensions when the available client area rendering space changes.
// Arguments:
// - prcClientNew - Client rectangle in pixels after this update
// - prcClientOld - Client rectangle in pixels before this update
// Return Value:
// - <none>
void SCREEN_INFORMATION::ProcessResizeWindow(const RECT* const prcClientNew,
const RECT* const prcClientOld)
{
if (_IsAltBuffer())
{
// Stash away the size of the window, we'll need to do this to the main when we pop back
// We set this on the main, so that main->alt(resize)->alt keeps the resize
_psiMainBuffer->_fAltWindowChanged = true;
_psiMainBuffer->_rcAltSavedClientNew = *prcClientNew;
_psiMainBuffer->_rcAltSavedClientOld = *prcClientOld;
}
// 1.a In some modes, the screen buffer size needs to change on window size,
// so do that first.
// _AdjustScreenBuffer might hide the commandline. If it does so, it'll
// return S_OK instead of S_FALSE. In that case, we'll need to re-show
// the commandline ourselves once the viewport size is updated.
// (See 1.b below)
const HRESULT adjustBufferSizeResult = _AdjustScreenBuffer(prcClientNew);
LOG_IF_FAILED(adjustBufferSizeResult);
// 2. Now calculate how large the new viewport should be
COORD coordViewportSize;
_CalculateViewportSize(prcClientNew, &coordViewportSize);
// 3. And adjust the existing viewport to match the same dimensions.
// The old/new comparison is to figure out which side the window was resized from.
_AdjustViewportSize(prcClientNew, prcClientOld, &coordViewportSize);
// 1.b If we did actually change the buffer size, then we need to show the
// commandline again. We hid it during _AdjustScreenBuffer, but we
// couldn't turn it back on until the Viewport was updated to the new
// size. See MSFT:19976291
if (SUCCEEDED(adjustBufferSizeResult) && adjustBufferSizeResult != S_FALSE)
{
CommandLine& commandLine = CommandLine::Instance();
commandLine.Show();
}
// 4. Finally, update the scroll bars.
UpdateScrollBars();
FAIL_FAST_IF(!(_viewport.Top() >= 0));
// TODO MSFT: 17663344 - Audit call sites for this precondition. Extremely tiny offscreen windows.
//FAIL_FAST_IF(!(_viewport.IsValid()));
}
#pragma endregion
#pragma region Support_Calculation
// Routine Description:
// - This helper converts client pixel areas into the number of characters that could fit into the client window.
// - It requires the buffer size to figure out whether it needs to reserve space for the scroll bars (or not).
// Arguments:
// - prcClientNew - Client region of window in pixels
// - coordBufferOld - Size of backing buffer in characters
// - pcoordClientNewCharacters - The maximum number of characters X by Y that can be displayed in the window with the given backing buffer.
// Return Value:
// - S_OK if math was successful. Check with SUCCEEDED/FAILED macro.
[[nodiscard]] HRESULT SCREEN_INFORMATION::_AdjustScreenBufferHelper(const RECT* const prcClientNew,
const COORD coordBufferOld,
_Out_ COORD* const pcoordClientNewCharacters)
{
// Get the font size ready.
COORD const coordFontSize = GetScreenFontSize();
// We cannot operate if the font size is 0. This shouldn't happen, but stop early if it does.
RETURN_HR_IF(E_NOT_VALID_STATE, 0 == coordFontSize.X || 0 == coordFontSize.Y);
// Find out how much client space we have to work with in the new area.
SIZE sizeClientNewPixels = { 0 };
sizeClientNewPixels.cx = RECT_WIDTH(prcClientNew);
sizeClientNewPixels.cy = RECT_HEIGHT(prcClientNew);
// Subtract out scroll bar space if scroll bars will be necessary.
bool fIsHorizontalVisible = false;
bool fIsVerticalVisible = false;
s_CalculateScrollbarVisibility(prcClientNew, &coordBufferOld, &coordFontSize, &fIsHorizontalVisible, &fIsVerticalVisible);
if (fIsHorizontalVisible)
{
sizeClientNewPixels.cy -= ServiceLocator::LocateGlobals().sHorizontalScrollSize;
}
if (fIsVerticalVisible)
{
sizeClientNewPixels.cx -= ServiceLocator::LocateGlobals().sVerticalScrollSize;
}
// Now with the scroll bars removed, calculate how many characters could fit into the new window area.
pcoordClientNewCharacters->X = (SHORT)(sizeClientNewPixels.cx / coordFontSize.X);
pcoordClientNewCharacters->Y = (SHORT)(sizeClientNewPixels.cy / coordFontSize.Y);
// If the new client is too tiny, our viewport will be 1x1.
pcoordClientNewCharacters->X = std::max(pcoordClientNewCharacters->X, 1i16);
pcoordClientNewCharacters->Y = std::max(pcoordClientNewCharacters->Y, 1i16);
return S_OK;
}
// Routine Description:
// - Modifies the size of the backing text buffer when the window changes to support "intuitive" resizing modes by grabbing the window edges.
// - This function will compensate for scroll bars.
// - Buffer size changes will happen internally to this function.
// Arguments:
// - prcClientNew - Client rectangle in pixels after this update
// Return Value:
// - appropriate HRESULT
[[nodiscard]] HRESULT SCREEN_INFORMATION::_AdjustScreenBuffer(const RECT* const prcClientNew)
{
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
// Prepare the buffer sizes.
// We need the main's size here to maintain the right scrollbar visibility.
COORD const coordBufferSizeOld = _IsAltBuffer() ? _psiMainBuffer->GetBufferSize().Dimensions() : GetBufferSize().Dimensions();
COORD coordBufferSizeNew = coordBufferSizeOld;
// First figure out how many characters we could fit into the new window given the old buffer size
COORD coordClientNewCharacters;
RETURN_IF_FAILED(_AdjustScreenBufferHelper(prcClientNew, coordBufferSizeOld, &coordClientNewCharacters));
// If we're in wrap text mode, then we want to be fixed to the window size. So use the character calculation we just got
// to fix the buffer and window width together.
if (gci.GetWrapText())
{
coordBufferSizeNew.X = coordClientNewCharacters.X;
}
// Reanalyze scroll bars in case we fixed the edge together for word wrap.
// Use the new buffer client size.
RETURN_IF_FAILED(_AdjustScreenBufferHelper(prcClientNew, coordBufferSizeNew, &coordClientNewCharacters));
// Now reanalyze the buffer size and grow if we can fit more characters into the window no matter the console mode.
if (_IsInPtyMode())
{
// The alt buffer always wants to be exactly the size of the screen, never more or less.
// This prevents scrollbars when you increase the alt buffer size, then decrease it.
// Can't have a buffer dimension of 0 - that'll cause divide by zeros in the future.
coordBufferSizeNew.X = std::max(coordClientNewCharacters.X, 1i16);
coordBufferSizeNew.Y = std::max(coordClientNewCharacters.Y, 1i16);
}
else
{
if (coordClientNewCharacters.X > coordBufferSizeNew.X)
{
coordBufferSizeNew.X = coordClientNewCharacters.X;
}
if (coordClientNewCharacters.Y > coordBufferSizeNew.Y)
{
coordBufferSizeNew.Y = coordClientNewCharacters.Y;
}
}
HRESULT hr = S_FALSE;
// Only attempt to modify the buffer if something changed. Expensive operation.
if (coordBufferSizeOld.X != coordBufferSizeNew.X ||
coordBufferSizeOld.Y != coordBufferSizeNew.Y)
{
CommandLine& commandLine = CommandLine::Instance();
// TODO: Deleting and redrawing the command line during resizing can cause flickering. See: http://osgvsowi/658439
// 1. Delete input string if necessary (see menu.c)
commandLine.Hide(FALSE);
_textBuffer->GetCursor().SetIsVisible(false);
// 2. Call the resize screen buffer method (expensive) to redimension the backing buffer (and reflow)
LOG_IF_FAILED(ResizeScreenBuffer(coordBufferSizeNew, FALSE));
// MSFT:19976291 Don't re-show the commandline here. We need to wait for
// the viewport to also get resized before we can re-show the commandline.
// ProcessResizeWindow will call commandline.Show() for us.
_textBuffer->GetCursor().SetIsVisible(true);
// Return S_OK, to indicate we succeeded and actually did something.
hr = S_OK;
}
return hr;
}
// Routine Description:
// - Calculates what width/height the viewport must have to consume all the available space in the given client area.
// - This compensates for scroll bars and will leave space in the client area for the bars if necessary.
// Arguments:
// - prcClientArea - The client rectangle in pixels of available rendering space.
// - pcoordSize - Filled with the width/height to which the viewport should be set.
// Return Value:
// - <none>
void SCREEN_INFORMATION::_CalculateViewportSize(const RECT* const prcClientArea, _Out_ COORD* const pcoordSize)
{
COORD const coordBufferSize = GetBufferSize().Dimensions();
COORD const coordFontSize = GetScreenFontSize();
SIZE sizeClientPixels = { 0 };
sizeClientPixels.cx = RECT_WIDTH(prcClientArea);
sizeClientPixels.cy = RECT_HEIGHT(prcClientArea);
bool fIsHorizontalVisible;
bool fIsVerticalVisible;
s_CalculateScrollbarVisibility(prcClientArea,
&coordBufferSize,
&coordFontSize,
&fIsHorizontalVisible,
&fIsVerticalVisible);
if (fIsHorizontalVisible)
{
sizeClientPixels.cy -= ServiceLocator::LocateGlobals().sHorizontalScrollSize;
}
if (fIsVerticalVisible)
{
sizeClientPixels.cx -= ServiceLocator::LocateGlobals().sVerticalScrollSize;
}
pcoordSize->X = (SHORT)(sizeClientPixels.cx / coordFontSize.X);
pcoordSize->Y = (SHORT)(sizeClientPixels.cy / coordFontSize.Y);
}
// Routine Description:
// - Modifies the size of the current viewport to match the width/height of the request given.
// - Must specify which corner to adjust from. Default to false/false to resize from the bottom right corner.
// Arguments:
// - pcoordSize - Requested viewport width/heights in characters
// - fResizeFromTop - If false, will trim/add to bottom of viewport first. If true, will trim/add to top.
// - fResizeFromBottom - If false, will trim/add to top of viewport first. If true, will trim/add to left.
// Return Value:
// - <none>
void SCREEN_INFORMATION::_InternalSetViewportSize(const COORD* const pcoordSize,
const bool fResizeFromTop,
const bool fResizeFromLeft)
{
const short DeltaX = pcoordSize->X - _viewport.Width();
const short DeltaY = pcoordSize->Y - _viewport.Height();
const COORD coordScreenBufferSize = GetBufferSize().Dimensions();
// do adjustments on a copy that's easily manipulated.
SMALL_RECT srNewViewport = _viewport.ToInclusive();
// Now we need to determine what our new Window size should
// be. Note that Window here refers to the character/row window.
if (fResizeFromLeft)
{
// we're being horizontally sized from the left border
const SHORT sLeftProposed = (srNewViewport.Left - DeltaX);
if (sLeftProposed >= 0)
{
// there's enough room in the backlog to just expand left
srNewViewport.Left -= DeltaX;
}
else
{
// if we're resizing horizontally, we want to show as much
// content above as we can, but we can't show more
// than the left of the window
srNewViewport.Left = 0;
srNewViewport.Right += (SHORT)abs(sLeftProposed);
}
}
else
{
// we're being horizontally sized from the right border
const SHORT sRightProposed = (srNewViewport.Right + DeltaX);
if (sRightProposed <= (coordScreenBufferSize.X - 1))
{
srNewViewport.Right += DeltaX;
}
else
{
srNewViewport.Right = (coordScreenBufferSize.X - 1);
srNewViewport.Left -= (sRightProposed - (coordScreenBufferSize.X - 1));
}
}
if (fResizeFromTop)
{
const SHORT sTopProposed = (srNewViewport.Top - DeltaY);
// we're being vertically sized from the top border
if (sTopProposed >= 0)
{
// Special case: Only modify the top position if we're not
// on the 0th row of the buffer.
// If we're on the 0th row, people expect it to stay stuck
// to the top of the window, not to start collapsing down
// and hiding the top rows.
if (srNewViewport.Top > 0)
{
// there's enough room in the backlog to just expand the top
srNewViewport.Top -= DeltaY;
}
else
{
// If we didn't adjust the top, we need to trim off
// the number of rows from the bottom instead.
// NOTE: It's += because DeltaY will be negative
// already for this circumstance.
FAIL_FAST_IF(!(DeltaY <= 0));
srNewViewport.Bottom += DeltaY;
}
}
else
{
// if we're resizing vertically, we want to show as much
// content above as we can, but we can't show more
// than the top of the window
srNewViewport.Top = 0;
srNewViewport.Bottom += (SHORT)abs(sTopProposed);
}
}
else
{
// we're being vertically sized from the bottom border
const SHORT sBottomProposed = (srNewViewport.Bottom + DeltaY);
if (sBottomProposed <= (coordScreenBufferSize.Y - 1))
{
// If the new bottom is supposed to be before the final line of the buffer
// Check to ensure that we don't hide the prompt by collapsing the window.
// The final valid end position will be the coordinates of
// the last character displayed (including any characters
// in the input line)
COORD coordValidEnd;
Selection::Instance().GetValidAreaBoundaries(nullptr, &coordValidEnd);
// If the bottom of the window when adjusted would be
// above the final line of valid text...
if (srNewViewport.Bottom + DeltaY < coordValidEnd.Y)
{
// Adjust the top of the window instead of the bottom
// (so the lines slide upward)
srNewViewport.Top -= DeltaY;
// If we happened to move the top of the window past
// the 0th row (first row in the buffer)
if (srNewViewport.Top < 0)
{
// Find the amount we went past 0, correct the top
// of the window back to 0, and instead adjust the
// bottom even though it will cause us to lose the
// prompt line.
const short cRemainder = 0 - srNewViewport.Top;
srNewViewport.Top += cRemainder;
FAIL_FAST_IF(!(srNewViewport.Top == 0));
srNewViewport.Bottom += cRemainder;
}
}
else
{
srNewViewport.Bottom += DeltaY;
}
}
else
{
srNewViewport.Bottom = (coordScreenBufferSize.Y - 1);
srNewViewport.Top -= (sBottomProposed - (coordScreenBufferSize.Y - 1));
}
}
// Ensure the viewport is valid.
// We can't have a negative left or top.
if (srNewViewport.Left < 0)
{
srNewViewport.Right -= srNewViewport.Left;
srNewViewport.Left = 0;
}
if (srNewViewport.Top < 0)
{
srNewViewport.Bottom -= srNewViewport.Top;
srNewViewport.Top = 0;
}
// Bottom and right cannot pass the final characters in the array.
const SHORT offRightDelta = srNewViewport.Right - (coordScreenBufferSize.X - 1);
if (offRightDelta > 0) // the viewport was off the right of the buffer...
{
// ...so slide both left/right back into the buffer. This will prevent us
// from having a negative width later.
srNewViewport.Right -= offRightDelta;
srNewViewport.Left = std::max<SHORT>(0, srNewViewport.Left - offRightDelta);
}
srNewViewport.Bottom = std::min(srNewViewport.Bottom, gsl::narrow<SHORT>(coordScreenBufferSize.Y - 1));
// See MSFT:19917443
// If we're in terminal scrolling mode, and we've changed the height of the
// viewport, the new viewport's bottom to the _virtualBottom.
// GH#1206 - Only do this if the viewport is _growing_ in height. This can
// cause unexpected behavior if we try to anchor the _virtualBottom to a
// position that will be greater than the height of the buffer.
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto newViewport = Viewport::FromInclusive(srNewViewport);
if (gci.IsTerminalScrolling() && newViewport.Height() >= _viewport.Height())
{
const short newTop = static_cast<short>(std::max(0, _virtualBottom - (newViewport.Height() - 1)));
newViewport = Viewport::FromDimensions(COORD({ newViewport.Left(), newTop }), newViewport.Dimensions());
}
_viewport = newViewport;
UpdateBottom();
Tracing::s_TraceWindowViewport(_viewport);
// In Conpty mode, call TriggerScroll here without params. By not providing
// params, the renderer will make sure to update the VtEngine with the
// updated viewport size. If we don't do this, the engine can get into a
// torn state on this frame.
//
// Without this statement, the engine won't be told about the new view size
// till the start of the next frame. If any other text gets output before
// that frame starts, there's a very real chance that it'll cause errors as
// the engine tries to invalidate those regions.
if (gci.IsInVtIoMode() && ServiceLocator::LocateGlobals().pRender)
{
ServiceLocator::LocateGlobals().pRender->TriggerScroll();
}
}
// Routine Description:
// - Modifies the size of the current viewport to match the width/height of the request given.
// - Uses the old and new client areas to determine which side the window was resized from.
// Arguments:
// - prcClientNew - Client rectangle in pixels after this update
// - prcClientOld - Client rectangle in pixels before this update
// - pcoordSize - Requested viewport width/heights in characters
// Return Value:
// - <none>
void SCREEN_INFORMATION::_AdjustViewportSize(const RECT* const prcClientNew,
const RECT* const prcClientOld,
const COORD* const pcoordSize)
{
// If the left is the only one that changed (and not the right
// also), then adjust from the left. Otherwise if the right
// changes or both changed, bias toward leaving the top-left
// corner in place and resize from the bottom right.
// --
// Resizing from the bottom right is more expected by
// users. Normally only one dimension (or one corner) will change
// at a time if the user is moving it. However, if the window is
// being dragged and forced to resize at a monitor boundary, all 4
// will change. In this case especially, users expect the top left
// to stay in place and the bottom right to adapt.
bool const fResizeFromLeft = prcClientNew->left != prcClientOld->left &&
prcClientNew->right == prcClientOld->right;
bool const fResizeFromTop = prcClientNew->top != prcClientOld->top &&
prcClientNew->bottom == prcClientOld->bottom;
const Viewport oldViewport = Viewport(_viewport);
_InternalSetViewportSize(pcoordSize, fResizeFromTop, fResizeFromLeft);
// MSFT 13194969, related to 12092729.
// If we're in virtual terminal mode, and the viewport dimensions change,
// send a WindowBufferSizeEvent. If the client wants VT mode, then they
// probably want the viewport resizes, not just the screen buffer
// resizes. This does change the behavior of the API for v2 callers,
// but only callers who've requested VT mode. In 12092729, we enabled
// sending notifications from window resizes in cases where the buffer
// didn't resize, so this applies the same expansion to resizes using
// the window, not the API.
if (IsInVirtualTerminalInputMode())
{
if ((_viewport.Width() != oldViewport.Width()) ||
(_viewport.Height() != oldViewport.Height()))
{
ScreenBufferSizeChange(GetBufferSize().Dimensions());
}
}
}
// Routine Description:
// - From a window client area in pixels, a buffer size, and the font size, this will determine
// whether scroll bars will need to be shown (and consume a portion of the client area) for the
// given buffer to be rendered.
// Arguments:
// - prcClientArea - Client area in pixels of the available space for rendering
// - pcoordBufferSize - Buffer size in characters
// - pcoordFontSize - Font size in pixels per character
// - pfIsHorizontalVisible - Indicates whether the horizontal scroll
// bar (consuming vertical space) will need to be visible
// - pfIsVerticalVisible - Indicates whether the vertical scroll bar
// (consuming horizontal space) will need to be visible
// Return Value:
// - <none>
void SCREEN_INFORMATION::s_CalculateScrollbarVisibility(const RECT* const prcClientArea,
const COORD* const pcoordBufferSize,
const COORD* const pcoordFontSize,
_Out_ bool* const pfIsHorizontalVisible,
_Out_ bool* const pfIsVerticalVisible)
{
// Start with bars not visible as the initial state of the client area doesn't account for scroll bars.
*pfIsHorizontalVisible = false;
*pfIsVerticalVisible = false;
// Set up the client area in pixels
SIZE sizeClientPixels = { 0 };
sizeClientPixels.cx = RECT_WIDTH(prcClientArea);
sizeClientPixels.cy = RECT_HEIGHT(prcClientArea);
// Set up the buffer area in pixels by multiplying the size by the font size scale factor
SIZE sizeBufferPixels = { 0 };
sizeBufferPixels.cx = pcoordBufferSize->X * pcoordFontSize->X;
sizeBufferPixels.cy = pcoordBufferSize->Y * pcoordFontSize->Y;
// Now figure out whether we need one or both scroll bars.
// Showing a scroll bar in one direction may necessitate showing
// the scroll bar in the other (as it will consume client area
// space).
if (sizeBufferPixels.cx > sizeClientPixels.cx)
{
*pfIsHorizontalVisible = true;
// If we have a horizontal bar, remove it from available
// vertical space and check that remaining client area is
// enough.
sizeClientPixels.cy -= ServiceLocator::LocateGlobals().sHorizontalScrollSize;
if (sizeBufferPixels.cy > sizeClientPixels.cy)
{
*pfIsVerticalVisible = true;
}
}
else if (sizeBufferPixels.cy > sizeClientPixels.cy)
{
*pfIsVerticalVisible = true;
// If we have a vertical bar, remove it from available
// horizontal space and check that remaining client area is
// enough.
sizeClientPixels.cx -= ServiceLocator::LocateGlobals().sVerticalScrollSize;
if (sizeBufferPixels.cx > sizeClientPixels.cx)
{
*pfIsHorizontalVisible = true;
}
}
}
bool SCREEN_INFORMATION::IsMaximizedBoth() const
{
return IsMaximizedX() && IsMaximizedY();
}
bool SCREEN_INFORMATION::IsMaximizedX() const
{
// If the viewport is displaying the entire size of the allocated buffer, it's maximized.
return _viewport.Left() == 0 && (_viewport.Width() == GetBufferSize().Width());
}
bool SCREEN_INFORMATION::IsMaximizedY() const
{
// If the viewport is displaying the entire size of the allocated buffer, it's maximized.
return _viewport.Top() == 0 && (_viewport.Height() == GetBufferSize().Height());
}
#pragma endregion
// Routine Description:
// - This is a screen resize algorithm which will reflow the ends of lines based on the
// line wrap state used for clipboard line-based copy.
// Arguments:
// - <in> Coordinates of the new screen size
// Return Value:
// - Success if successful. Invalid parameter if screen buffer size is unexpected. No memory if allocation failed.
[[nodiscard]] NTSTATUS SCREEN_INFORMATION::ResizeWithReflow(const COORD coordNewScreenSize)
{
if ((USHORT)coordNewScreenSize.X >= SHORT_MAX || (USHORT)coordNewScreenSize.Y >= SHORT_MAX)
{
RIPMSG2(RIP_WARNING, "Invalid screen buffer size (0x%x, 0x%x)", coordNewScreenSize.X, coordNewScreenSize.Y);
return STATUS_INVALID_PARAMETER;
}
// First allocate a new text buffer to take the place of the current one.
std::unique_ptr<TextBuffer> newTextBuffer;
// GH#3848 - Stash away the current attributes the old text buffer is using.
// We'll initialize the new buffer with the default attributes, but after
// the resize, we'll want to make sure that the new buffer's current
// attributes (the ones used for printing new text) match the old buffer's.
const auto oldPrimaryAttributes = _textBuffer->GetCurrentAttributes();
try
{
newTextBuffer = std::make_unique<TextBuffer>(coordNewScreenSize,
TextAttribute{},
0,
_renderTarget); // temporarily set size to 0 so it won't render.
}
catch (...)
{
return NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException());
}
// Save cursor's relative height versus the viewport
SHORT const sCursorHeightInViewportBefore = _textBuffer->GetCursor().GetPosition().Y - _viewport.Top();
// skip any drawing updates that might occur until we swap _textBuffer with the new buffer or we exit early.
newTextBuffer->GetCursor().StartDeferDrawing();
_textBuffer->GetCursor().StartDeferDrawing();
// we're capturing _textBuffer by reference here because when we exit, we want to EndDefer on the current active buffer.
auto endDefer = wil::scope_exit([&]() noexcept { _textBuffer->GetCursor().EndDeferDrawing(); });
HRESULT hr = TextBuffer::Reflow(*_textBuffer.get(), *newTextBuffer.get(), std::nullopt, std::nullopt);
if (SUCCEEDED(hr))
{
Cursor& newCursor = newTextBuffer->GetCursor();
// Adjust the viewport so the cursor doesn't wildly fly off up or down.
SHORT const sCursorHeightInViewportAfter = newCursor.GetPosition().Y - _viewport.Top();
COORD coordCursorHeightDiff = { 0 };
coordCursorHeightDiff.Y = sCursorHeightInViewportAfter - sCursorHeightInViewportBefore;
LOG_IF_FAILED(SetViewportOrigin(false, coordCursorHeightDiff, true));
_textBuffer->SetCurrentAttributes(oldPrimaryAttributes);
_textBuffer.swap(newTextBuffer);
}
return NTSTATUS_FROM_HRESULT(hr);
}
//
// Routine Description:
// - This is the legacy screen resize with minimal changes
// Arguments:
// - coordNewScreenSize - new size of screen.
// Return Value:
// - Success if successful. Invalid parameter if screen buffer size is unexpected. No memory if allocation failed.
[[nodiscard]] NTSTATUS SCREEN_INFORMATION::ResizeTraditional(const COORD coordNewScreenSize)
{
return NTSTATUS_FROM_HRESULT(_textBuffer->ResizeTraditional(coordNewScreenSize));
}
//
// Routine Description:
// - This routine resizes the screen buffer.
// Arguments:
// - NewScreenSize - new size of screen in characters
// - DoScrollBarUpdate - indicates whether to update scroll bars at the end
// Return Value:
// - Success if successful. Invalid parameter if screen buffer size is unexpected. No memory if allocation failed.
[[nodiscard]] NTSTATUS SCREEN_INFORMATION::ResizeScreenBuffer(const COORD coordNewScreenSize,
const bool fDoScrollBarUpdate)
{
// If the size hasn't actually changed, do nothing.
if (coordNewScreenSize == GetBufferSize().Dimensions())
{
return STATUS_SUCCESS;
}
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
NTSTATUS status = STATUS_SUCCESS;
// If we're in conpty mode, suppress any immediate painting we might do
// during the resize.
if (gci.IsInVtIoMode())
{
gci.GetVtIo()->BeginResize();
}
auto endResize = wil::scope_exit([&] {
if (gci.IsInVtIoMode())
{
gci.GetVtIo()->EndResize();
}
});
// cancel any active selection before resizing or it will not necessarily line up with the new buffer positions
Selection::Instance().ClearSelection();
// cancel any popups before resizing or they will not necessarily line up with new buffer positions
CommandLine::Instance().EndAllPopups();
const bool fWrapText = gci.GetWrapText();
if (fWrapText)
{
status = ResizeWithReflow(coordNewScreenSize);
}
else
{
status = NTSTATUS_FROM_HRESULT(ResizeTraditional(coordNewScreenSize));
}
if (NT_SUCCESS(status))
{
if (HasAccessibilityEventing())
{
NotifyAccessibilityEventing(0, 0, (SHORT)(coordNewScreenSize.X - 1), (SHORT)(coordNewScreenSize.Y - 1));
}
if ((!ConvScreenInfo))
{
if (FAILED(ConsoleImeResizeCompStrScreenBuffer(coordNewScreenSize)))
{
// If something went wrong, just bail out.
return STATUS_INVALID_HANDLE;
}
}
// Fire off an event to let accessibility apps know the layout has changed.
if (_pAccessibilityNotifier && IsActiveScreenBuffer())
{
_pAccessibilityNotifier->NotifyConsoleLayoutEvent();
}
if (fDoScrollBarUpdate)
{
UpdateScrollBars();
}
ScreenBufferSizeChange(coordNewScreenSize);
}
return status;
}
// Routine Description:
// - Given a rectangle containing screen buffer coordinates (character-level positioning, not pixel)
// This method will trim the rectangle to ensure it is within the buffer.
// For example, if the rectangle given has a right position of 85, but the current screen buffer
// is only reaching from 0-79, then the right position will be set to 79.
// Arguments:
// - psrRect - Pointer to rectangle holding data to be trimmed
// Return Value:
// - <none>
void SCREEN_INFORMATION::ClipToScreenBuffer(_Inout_ SMALL_RECT* const psrClip) const
{
const auto bufferSize = GetBufferSize();
psrClip->Left = std::max(psrClip->Left, bufferSize.Left());
psrClip->Top = std::max(psrClip->Top, bufferSize.Top());
psrClip->Right = std::min(psrClip->Right, bufferSize.RightInclusive());
psrClip->Bottom = std::min(psrClip->Bottom, bufferSize.BottomInclusive());
}
void SCREEN_INFORMATION::MakeCurrentCursorVisible()
{
MakeCursorVisible(_textBuffer->GetCursor().GetPosition());
}
// Routine Description:
// - This routine sets the cursor size and visibility both in the data
// structures and on the screen. Also updates the cursor information of
// this buffer's main buffer, if this buffer is an alt buffer.
// Arguments:
// - Size - cursor size
// - Visible - cursor visibility
// Return Value:
// - None
void SCREEN_INFORMATION::SetCursorInformation(const ULONG Size,
const bool Visible) noexcept
{
Cursor& cursor = _textBuffer->GetCursor();
const auto originalSize = cursor.GetSize();
cursor.SetSize(Size);
cursor.SetIsVisible(Visible);
// If we are just trying to change the visibility, we don't want to reset
// the cursor type. We only need to force it to the Legacy style if the
// size is actually being changed.
if (Size != originalSize)
{
cursor.SetType(CursorType::Legacy);
}
// If we're an alt buffer, also update our main buffer.
// Users of the API expect both to be set - this can't be set by VT
if (_psiMainBuffer)
{
_psiMainBuffer->SetCursorInformation(Size, Visible);
}
}
// Routine Description:
// - This routine sets the cursor color. Also updates the cursor information of
// this buffer's main buffer, if this buffer is an alt buffer.
// Arguments:
// - Color - The new color to set the cursor to
// - setMain - If true, propagate change to main buffer as well.
// Return Value:
// - None
void SCREEN_INFORMATION::SetCursorColor(const unsigned int Color, const bool setMain) noexcept
{
Cursor& cursor = _textBuffer->GetCursor();
cursor.SetColor(Color);
// If we're an alt buffer, DON'T propagate this setting up to the main buffer.
// We don't want to pollute that buffer with this state,
// UNLESS we're getting called from the propsheet, then we DO want to update this.
if (_psiMainBuffer && setMain)
{
_psiMainBuffer->SetCursorColor(Color);
}
}
// Routine Description:
// - This routine sets the cursor shape both in the data
// structures and on the screen. Also updates the cursor information of
// this buffer's main buffer, if this buffer is an alt buffer.
// Arguments:
// - Type - The new shape to set the cursor to
// - setMain - If true, propagate change to main buffer as well.
// Return Value:
// - None
void SCREEN_INFORMATION::SetCursorType(const CursorType Type, const bool setMain) noexcept
{
Cursor& cursor = _textBuffer->GetCursor();
cursor.SetType(Type);
// If we're an alt buffer, DON'T propagate this setting up to the main buffer.
// We don't want to pollute that buffer with this state,
// UNLESS we're getting called from the propsheet, then we DO want to update this.
if (_psiMainBuffer && setMain)
{
_psiMainBuffer->SetCursorType(Type);
}
}
// Routine Description:
// - This routine sets a flag saying whether the cursor should be displayed
// with its default size or it should be modified to indicate the
// insert/overtype mode has changed.
// Arguments:
// - ScreenInfo - pointer to screen info structure.
// - DoubleCursor - should we indicated non-normal mode
// Return Value:
// - None
void SCREEN_INFORMATION::SetCursorDBMode(const bool DoubleCursor)
{
Cursor& cursor = _textBuffer->GetCursor();
if ((cursor.IsDouble() != DoubleCursor))
{
cursor.SetIsDouble(DoubleCursor);
}
// If we're an alt buffer, also update our main buffer.
if (_psiMainBuffer)
{
_psiMainBuffer->SetCursorDBMode(DoubleCursor);
}
}
// Routine Description:
// - This routine sets the cursor position in the data structures and on the screen.
// Arguments:
// - ScreenInfo - pointer to screen info structure.
// - Position - new position of cursor
// - TurnOn - true if cursor should be left on, false if should be left off
// Return Value:
// - Status
[[nodiscard]] NTSTATUS SCREEN_INFORMATION::SetCursorPosition(const COORD Position, const bool TurnOn)
{
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
Cursor& cursor = _textBuffer->GetCursor();
//
// Ensure that the cursor position is within the constraints of the screen
// buffer.
//
const COORD coordScreenBufferSize = GetBufferSize().Dimensions();
if (Position.X >= coordScreenBufferSize.X || Position.Y >= coordScreenBufferSize.Y || Position.X < 0 || Position.Y < 0)
{
return STATUS_INVALID_PARAMETER;
}
// In GH#5291, we experimented with manually breaking the line on all cursor
// movements here. As we print lines into the buffer, we mark lines as
// wrapped when we print the last cell of the row, not the first cell of the
// subsequent row (the row the first line wrapped onto).
//
// Logically, we thought that manually breaking lines when we move the
// cursor was a good idea. We however, did not have the time to fully
// validate that this was the correct answer, and a simpler solution for the
// bug on hand was found. Furthermore, we thought it would be a more
// comprehensive solution to only mark lines as wrapped when we print the
// first cell of the second row, which would require some WriteCharsLegacy
// work.
cursor.SetPosition(Position);
// If the cursor has moved below the virtual bottom, the bottom should be updated.
if (Position.Y > _virtualBottom)
{
_virtualBottom = Position.Y;
}
// if we have the focus, adjust the cursor state
if (gci.Flags & CONSOLE_HAS_FOCUS)
{
if (TurnOn)
{
cursor.SetDelay(false);
cursor.SetIsOn(true);
}
else
{
cursor.SetDelay(true);
}
cursor.SetHasMoved(true);
}
return STATUS_SUCCESS;
}
void SCREEN_INFORMATION::MakeCursorVisible(const COORD CursorPosition, const bool updateBottom)
{
COORD WindowOrigin;
if (CursorPosition.X > _viewport.RightInclusive())
{
WindowOrigin.X = CursorPosition.X - _viewport.RightInclusive();
}
else if (CursorPosition.X < _viewport.Left())
{
WindowOrigin.X = CursorPosition.X - _viewport.Left();
}
else
{
WindowOrigin.X = 0;
}
if (CursorPosition.Y > _viewport.BottomInclusive())
{
WindowOrigin.Y = CursorPosition.Y - _viewport.BottomInclusive();
}
else if (CursorPosition.Y < _viewport.Top())
{
WindowOrigin.Y = CursorPosition.Y - _viewport.Top();
}
else
{
WindowOrigin.Y = 0;
}
if (WindowOrigin.X != 0 || WindowOrigin.Y != 0)
{
LOG_IF_FAILED(SetViewportOrigin(false, WindowOrigin, updateBottom));
}
}
// Method Description:
// - Sets the scroll margins for this buffer.
// Arguments:
// - margins: The new values of the scroll margins, *relative to the viewport*
void SCREEN_INFORMATION::SetScrollMargins(const Viewport margins)
{
_scrollMargins = margins;
}
// Method Description:
// - Returns the scrolling margins boundaries for this screen buffer, relative
// to the origin of the text buffer. Most callers will want the absolute
// positions of the margins, though they are set and stored relative to
// origin of the viewport.
// Arguments:
// - <none>
Viewport SCREEN_INFORMATION::GetAbsoluteScrollMargins() const
{
return _viewport.ConvertFromOrigin(_scrollMargins);
}
// Method Description:
// - Returns the scrolling margins boundaries for this screen buffer, relative
// to the current viewport.
// Arguments:
// - <none>
Viewport SCREEN_INFORMATION::GetRelativeScrollMargins() const
{
return _scrollMargins;
}
// Routine Description:
// - Retrieves the active buffer of this buffer. If this buffer has an
// alternate buffer, this is the alternate buffer. Otherwise, it is this buffer.
// Parameters:
// - None
// Return value:
// - a reference to this buffer's active buffer.
SCREEN_INFORMATION& SCREEN_INFORMATION::GetActiveBuffer()
{
return const_cast<SCREEN_INFORMATION&>(static_cast<const SCREEN_INFORMATION* const>(this)->GetActiveBuffer());
}
const SCREEN_INFORMATION& SCREEN_INFORMATION::GetActiveBuffer() const
{
if (_psiAlternateBuffer != nullptr)
{
return *_psiAlternateBuffer;
}
return *this;
}
// Routine Description:
// - Retrieves the main buffer of this buffer. If this buffer has an
// alternate buffer, this is the main buffer. Otherwise, it is this buffer's main buffer.
// The main buffer is not necessarily the active buffer.
// Parameters:
// - None
// Return value:
// - a reference to this buffer's main buffer.
SCREEN_INFORMATION& SCREEN_INFORMATION::GetMainBuffer()
{
return const_cast<SCREEN_INFORMATION&>(static_cast<const SCREEN_INFORMATION* const>(this)->GetMainBuffer());
}
const SCREEN_INFORMATION& SCREEN_INFORMATION::GetMainBuffer() const
{
if (_psiMainBuffer != nullptr)
{
return *_psiMainBuffer;
}
return *this;
}
// Routine Description:
// - Instantiates a new buffer to be used as an alternate buffer. This buffer
// does not have a driver handle associated with it and shares a state
// machine with the main buffer it belongs to.
// TODO: MSFT:19817348 Don't create alt screenbuffer's via an out SCREEN_INFORMATION**
// Parameters:
// - ppsiNewScreenBuffer - a pointer to receive the newly created buffer.
// Return value:
// - STATUS_SUCCESS if handled successfully. Otherwise, an appropriate status code indicating the error.
[[nodiscard]] NTSTATUS SCREEN_INFORMATION::_CreateAltBuffer(_Out_ SCREEN_INFORMATION** const ppsiNewScreenBuffer)
{
// Create new screen buffer.
COORD WindowSize = _viewport.Dimensions();
const FontInfo& existingFont = GetCurrentFont();
// The buffer needs to be initialized with the standard erase attributes,
// i.e. the current background color, but with no meta attributes set.
auto initAttributes = GetAttributes();
initAttributes.SetStandardErase();
NTSTATUS Status = SCREEN_INFORMATION::CreateInstance(WindowSize,
existingFont,
WindowSize,
initAttributes,
GetPopupAttributes(),
Cursor::CURSOR_SMALL_SIZE,
ppsiNewScreenBuffer);
if (NT_SUCCESS(Status))
{
// Update the alt buffer's cursor style, visibility, and position to match our own.
auto& myCursor = GetTextBuffer().GetCursor();
auto* const createdBuffer = *ppsiNewScreenBuffer;
auto& altCursor = createdBuffer->GetTextBuffer().GetCursor();
altCursor.SetStyle(myCursor.GetSize(), myCursor.GetColor(), myCursor.GetType());
altCursor.SetIsVisible(myCursor.IsVisible());
altCursor.SetBlinkingAllowed(myCursor.IsBlinkingAllowed());
// The new position should match the viewport-relative position of the main buffer.
auto altCursorPos = myCursor.GetPosition();
altCursorPos.Y -= GetVirtualViewport().Top();
altCursor.SetPosition(altCursorPos);
s_InsertScreenBuffer(createdBuffer);
// delete the alt buffer's state machine. We don't want it.
createdBuffer->_FreeOutputStateMachine(); // this has to be done before we give it a main buffer
// we'll attach the GetSet, etc once we successfully make this buffer the active buffer.
// Set up the new buffers references to our current state machine, dispatcher, getset, etc.
createdBuffer->_stateMachine = _stateMachine;
}
return Status;
}
// Routine Description:
// - Creates an "alternate" screen buffer for this buffer. In virtual terminals, there exists both a "main"
// screen buffer and an alternate. ASBSET creates a new alternate, and switches to it. If there is an already
// existing alternate, it is discarded. This allows applications to retain one HANDLE, and switch which buffer it points to seamlessly.
// Parameters:
// - None
// Return value:
// - STATUS_SUCCESS if handled successfully. Otherwise, an appropriate status code indicating the error.
[[nodiscard]] NTSTATUS SCREEN_INFORMATION::UseAlternateScreenBuffer()
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
SCREEN_INFORMATION& siMain = GetMainBuffer();
// If we're in an alt that resized, resize the main before making the new alt
if (siMain._fAltWindowChanged)
{
siMain.ProcessResizeWindow(&(siMain._rcAltSavedClientNew), &(siMain._rcAltSavedClientOld));
siMain._fAltWindowChanged = false;
}
SCREEN_INFORMATION* psiNewAltBuffer;
NTSTATUS Status = _CreateAltBuffer(&psiNewAltBuffer);
if (NT_SUCCESS(Status))
{
// if this is already an alternate buffer, we want to make the new
// buffer the alt on our main buffer, not on ourself, because there
// can only ever be one main and one alternate.
SCREEN_INFORMATION* const psiOldAltBuffer = siMain._psiAlternateBuffer;
psiNewAltBuffer->_psiMainBuffer = &siMain;
siMain._psiAlternateBuffer = psiNewAltBuffer;
if (psiOldAltBuffer != nullptr)
{
s_RemoveScreenBuffer(psiOldAltBuffer); // this will also delete the old alt buffer
}
::SetActiveScreenBuffer(*psiNewAltBuffer);
// Kind of a hack until we have proper signal channels: If the client app wants window size events, send one for
// the new alt buffer's size (this is so WSL can update the TTY size when the MainSB.viewportWidth <
// MainSB.bufferWidth (which can happen with wrap text disabled))
ScreenBufferSizeChange(psiNewAltBuffer->GetBufferSize().Dimensions());
// Tell the VT MouseInput handler that we're in the Alt buffer now
gci.GetActiveInputBuffer()->GetTerminalInput().UseAlternateScreenBuffer();
}
return Status;
}
// Routine Description:
// - Restores the active buffer to be this buffer's main buffer. If this is the main buffer, then nothing happens.
// Parameters:
// - None
// Return value:
// - STATUS_SUCCESS if handled successfully. Otherwise, an appropriate status code indicating the error.
void SCREEN_INFORMATION::UseMainScreenBuffer()
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
SCREEN_INFORMATION* psiMain = _psiMainBuffer;
if (psiMain != nullptr)
{
if (psiMain->_fAltWindowChanged)
{
psiMain->ProcessResizeWindow(&(psiMain->_rcAltSavedClientNew), &(psiMain->_rcAltSavedClientOld));
psiMain->_fAltWindowChanged = false;
}
::SetActiveScreenBuffer(*psiMain);
psiMain->UpdateScrollBars(); // The alt had disabled scrollbars, re-enable them
// send a _coordScreenBufferSizeChangeEvent for the new Sb viewport
ScreenBufferSizeChange(psiMain->GetBufferSize().Dimensions());
SCREEN_INFORMATION* psiAlt = psiMain->_psiAlternateBuffer;
psiMain->_psiAlternateBuffer = nullptr;
// Copy the alt buffer's cursor style and visibility back to the main buffer.
const auto& altCursor = psiAlt->GetTextBuffer().GetCursor();
auto& mainCursor = psiMain->GetTextBuffer().GetCursor();
mainCursor.SetStyle(altCursor.GetSize(), altCursor.GetColor(), altCursor.GetType());
mainCursor.SetIsVisible(altCursor.IsVisible());
mainCursor.SetBlinkingAllowed(altCursor.IsBlinkingAllowed());
s_RemoveScreenBuffer(psiAlt); // this will also delete the alt buffer
// deleting the alt buffer will give the GetSet back to its main
// Tell the VT MouseInput handler that we're in the main buffer now
gci.GetActiveInputBuffer()->GetTerminalInput().UseMainScreenBuffer();
}
}
// Routine Description:
// - Helper indicating if the buffer has a main buffer, meaning that this is an alternate buffer.
// Parameters:
// - None
// Return value:
// - true iff this buffer has a main buffer.
bool SCREEN_INFORMATION::_IsAltBuffer() const
{
return _psiMainBuffer != nullptr;
}
// Routine Description:
// - Helper indicating if the buffer is acting as a pty - with the screenbuffer
// clamped to the viewport size. This can be the case either when we're in
// VT I/O mode, or when this buffer is an alt buffer.
// Parameters:
// - None
// Return value:
// - true iff this buffer has a main buffer.
bool SCREEN_INFORMATION::_IsInPtyMode() const
{
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
return _IsAltBuffer() || gci.IsInVtIoMode();
}
// Routine Description:
// - returns true if this buffer is in Virtual Terminal Output mode.
// Parameters:
// - None
// Return Value:
// - true iff this buffer is in Virtual Terminal Output mode.
bool SCREEN_INFORMATION::_IsInVTMode() const
{
return WI_IsFlagSet(OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING);
}
// Routine Description:
// - Returns the value of the attributes
// Parameters:
// <none>
// Return value:
// - This screen buffer's attributes
TextAttribute SCREEN_INFORMATION::GetAttributes() const
{
return _textBuffer->GetCurrentAttributes();
}
// Routine Description:
// - Returns the value of the popup attributes
// Parameters:
// <none>
// Return value:
// - This screen buffer's popup attributes
TextAttribute SCREEN_INFORMATION::GetPopupAttributes() const
{
return _PopupAttributes;
}
// Routine Description:
// - Sets the value of the attributes on this screen buffer. Also propagates
// the change down to the fill of the text buffer attached to this screen buffer.
// Parameters:
// - attributes - The new value of the attributes to use.
// Return value:
// <none>
void SCREEN_INFORMATION::SetAttributes(const TextAttribute& attributes)
{
if (_ignoreLegacyEquivalentVTAttributes)
{
// See the comment on StripErroneousVT16VersionsOfLegacyDefaults for more info.
_textBuffer->SetCurrentAttributes(TextAttribute::StripErroneousVT16VersionsOfLegacyDefaults(attributes));
return;
}
_textBuffer->SetCurrentAttributes(attributes);
// If we're an alt buffer, DON'T propagate this setting up to the main buffer.
// We don't want to pollute that buffer with this state.
}
// Method Description:
// - Sets the value of the popup attributes on this screen buffer.
// Parameters:
// - popupAttributes - The new value of the popup attributes to use.
// Return value:
// <none>
void SCREEN_INFORMATION::SetPopupAttributes(const TextAttribute& popupAttributes)
{
_PopupAttributes = popupAttributes;
// If we're an alt buffer, DON'T propagate this setting up to the main buffer.
// We don't want to pollute that buffer with this state.
}
// Method Description:
// - Sets the value of the attributes on this screen buffer. Also propagates
// the change down to the fill of the attached text buffer.
// - Additionally updates any popups to match the new color scheme.
// - Also updates the defaults of the main buffer. This method is called by the
// propsheet menu when you set the colors via the propsheet. In that
// workflow, we want the main buffer's colors changed as well as our own.
// Parameters:
// - attributes - The new value of the attributes to use.
// - popupAttributes - The new value of the popup attributes to use.
// Return value:
// <none>
// Notes:
// This code is merged from the old global function SetScreenColors
void SCREEN_INFORMATION::SetDefaultAttributes(const TextAttribute& attributes,
const TextAttribute& popupAttributes)
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const TextAttribute oldPrimaryAttributes = GetAttributes();
const TextAttribute oldPopupAttributes = GetPopupAttributes();
// Quick return if we don't need to do anything.
if (oldPrimaryAttributes == attributes && oldPopupAttributes == popupAttributes)
{
return;
}
SetAttributes(attributes);
SetPopupAttributes(popupAttributes);
// 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();
// If we're an alt buffer, also update our main buffer.
if (_psiMainBuffer)
{
_psiMainBuffer->SetDefaultAttributes(attributes, popupAttributes);
}
}
// Method Description:
// - Returns an inclusive rectangle that describes the bounds of the buffer viewport.
// Arguments:
// - <none>
// Return Value:
// - the viewport bounds as an inclusive rect.
const Viewport& SCREEN_INFORMATION::GetViewport() const noexcept
{
return _viewport;
}
// Routine Description:
// - This routine updates the size of the rectangle representing the viewport into the text buffer.
// - It is specified in character count within the buffer.
// - It will be corrected to not exceed the limits of the current screen buffer dimensions.
// Arguments:
// newViewport: The new viewport to use. If it's out of bounds in the negative
// direction it will be shifted to positive coordinates. If it's bigger
// that the screen buffer, it will be clamped to the size of the buffer.
// updateBottom: if true, update our virtual bottom. This should be false when
// called from UX interactions, such as scrolling with the mouse wheel,
// and true when called from API endpoints, such as SetConsoleWindowInfo
// Return Value:
// - None
void SCREEN_INFORMATION::SetViewport(const Viewport& newViewport,
const bool updateBottom)
{
// make sure there's something to do
if (newViewport == _viewport)
{
return;
}
// do adjustments on a copy that's easily manipulated.
SMALL_RECT srCorrected = newViewport.ToExclusive();
if (srCorrected.Left < 0)
{
srCorrected.Right -= srCorrected.Left;
srCorrected.Left = 0;
}
if (srCorrected.Top < 0)
{
srCorrected.Bottom -= srCorrected.Top;
srCorrected.Top = 0;
}
const COORD coordScreenBufferSize = GetBufferSize().Dimensions();
if (srCorrected.Right > coordScreenBufferSize.X)
{
srCorrected.Right = coordScreenBufferSize.X;
}
if (srCorrected.Bottom > coordScreenBufferSize.Y)
{
srCorrected.Bottom = coordScreenBufferSize.Y;
}
_viewport = Viewport::FromExclusive(srCorrected);
if (updateBottom)
{
UpdateBottom();
}
Tracing::s_TraceWindowViewport(_viewport);
}
// Method Description:
// - Performs a VT Erase All operation. In most terminals, this is done by
// moving the viewport into the scrollback, clearing out the current screen.
// For them, there can never be any characters beneath the viewport, as the
// viewport is always at the bottom. So, we can accomplish the same behavior
// by using the LastNonspaceCharacter as the "bottom", and placing the new
// viewport underneath that character.
// Parameters:
// <none>
// Return value:
// - S_OK if we succeeded, or another status if there was a failure.
[[nodiscard]] HRESULT SCREEN_INFORMATION::VtEraseAll()
{
const COORD coordLastChar = _textBuffer->GetLastNonSpaceCharacter();
short sNewTop = coordLastChar.Y + 1;
const Viewport oldViewport = _viewport;
// Stash away the current position of the cursor within the viewport.
// We'll need to restore the cursor to that same relative position, after
// we move the viewport.
const COORD oldCursorPos = _textBuffer->GetCursor().GetPosition();
COORD relativeCursor = oldCursorPos;
oldViewport.ConvertToOrigin(&relativeCursor);
short delta = (sNewTop + _viewport.Height()) - (GetBufferSize().Height());
for (auto i = 0; i < delta; i++)
{
_textBuffer->IncrementCircularBuffer();
sNewTop--;
}
const COORD coordNewOrigin = { 0, sNewTop };
RETURN_IF_FAILED(SetViewportOrigin(true, coordNewOrigin, true));
// Restore the relative cursor position
_viewport.ConvertFromOrigin(&relativeCursor);
RETURN_IF_FAILED(SetCursorPosition(relativeCursor, false));
// Update all the rows in the current viewport with the standard erase attributes,
// i.e. the current background color, but with no meta attributes set.
auto fillAttributes = GetAttributes();
fillAttributes.SetStandardErase();
auto fillPosition = COORD{ 0, _viewport.Top() };
auto fillLength = gsl::narrow_cast<size_t>(_viewport.Height() * GetBufferSize().Width());
auto fillData = OutputCellIterator{ fillAttributes, fillLength };
Write(fillData, fillPosition, false);
// Also reset the line rendition for the erased rows.
_textBuffer->ResetLineRenditionRange(_viewport.Top(), _viewport.BottomExclusive());
return S_OK;
}
// Method Description:
// - Clear the entire contents of the viewport, except for the cursor's row,
// which is moved to the top line of the viewport.
// - This is used exclusively by ConPTY to support GH#1193, GH#1882. This allows
// a terminal to clear the contents of the ConPTY buffer, which is important
// if the user would like to be able to clear the terminal-side buffer.
// Arguments:
// - <none>
// Return Value:
// - S_OK
[[nodiscard]] HRESULT SCREEN_INFORMATION::ClearBuffer()
{
const COORD oldCursorPos = _textBuffer->GetCursor().GetPosition();
short sNewTop = oldCursorPos.Y;
const Viewport oldViewport = _viewport;
short delta = (sNewTop + _viewport.Height()) - (GetBufferSize().Height());
for (auto i = 0; i < delta; i++)
{
_textBuffer->IncrementCircularBuffer();
sNewTop--;
}
const COORD coordNewOrigin = { 0, sNewTop };
RETURN_IF_FAILED(SetViewportOrigin(true, coordNewOrigin, true));
// Place the cursor at the same x coord, on the row that's now the top
RETURN_IF_FAILED(SetCursorPosition(COORD{ oldCursorPos.X, sNewTop }, false));
// Update all the rows in the current viewport with the standard erase attributes,
// i.e. the current background color, but with no meta attributes set.
auto fillAttributes = GetAttributes();
fillAttributes.SetStandardErase();
// +1 on the y coord because we don't want to clear the attributes of the
// cursor row, the one we saved.
auto fillPosition = COORD{ 0, _viewport.Top() + 1 };
auto fillLength = gsl::narrow_cast<size_t>(_viewport.Height() * GetBufferSize().Width());
auto fillData = OutputCellIterator{ fillAttributes, fillLength };
Write(fillData, fillPosition, false);
_textBuffer->GetRenderTarget().TriggerRedrawAll();
// Also reset the line rendition for the erased rows.
_textBuffer->ResetLineRenditionRange(_viewport.Top(), _viewport.BottomExclusive());
return S_OK;
}
// Method Description:
// - Sets up the Output state machine to be in pty mode. Sequences it doesn't
// understand will be written to the pTtyConnection passed in here.
// Arguments:
// - pTtyConnection: This is a TerminalOutputConnection that we can write the
// sequence we didn't understand to.
// Return Value:
// - <none>
void SCREEN_INFORMATION::SetTerminalConnection(_In_ ITerminalOutputConnection* const pTtyConnection)
{
OutputStateMachineEngine& engine = reinterpret_cast<OutputStateMachineEngine&>(_stateMachine->Engine());
if (pTtyConnection)
{
engine.SetTerminalConnection(pTtyConnection,
std::bind(&StateMachine::FlushToTerminal, _stateMachine.get()));
}
else
{
engine.SetTerminalConnection(nullptr,
nullptr);
}
}
// Routine Description:
// - This routine copies a rectangular region from the screen buffer. no clipping is done.
// Arguments:
// - viewport - rectangle in source buffer to copy
// Return Value:
// - output cell rectangle copy of screen buffer data
// Note:
// - will throw exception on error.
OutputCellRect SCREEN_INFORMATION::ReadRect(const Viewport viewport) const
{
// If the viewport given doesn't fit inside this screen, it's not a valid argument.
THROW_HR_IF(E_INVALIDARG, !GetBufferSize().IsInBounds(viewport));
OutputCellRect result(viewport.Height(), viewport.Width());
const OutputCell paddingCell{ std::wstring_view{ &UNICODE_SPACE, 1 }, {}, GetAttributes() };
for (size_t rowIndex = 0; rowIndex < gsl::narrow<size_t>(viewport.Height()); ++rowIndex)
{
COORD location = viewport.Origin();
location.Y += (SHORT)rowIndex;
auto data = GetCellLineDataAt(location);
const auto span = result.GetRow(rowIndex);
auto it = span.begin();
// Copy row data while there still is data and we haven't run out of rect to store it into.
while (data && it < span.end())
{
*it++ = *data++;
}
// Pad out any remaining space.
while (it < span.end())
{
*it++ = paddingCell;
}
// if we're clipping a dbcs char then don't include it, add a space instead
if (span.begin()->DbcsAttr().IsTrailing())
{
*span.begin() = paddingCell;
}
if (span.rbegin()->DbcsAttr().IsLeading())
{
*span.rbegin() = paddingCell;
}
}
return result;
}
// Routine Description:
// - Writes cells to the output buffer at the cursor position.
// Arguments:
// - it - Iterator representing output cell data to write.
// Return Value:
// - the iterator at its final position
// Note:
// - will throw exception on error.
OutputCellIterator SCREEN_INFORMATION::Write(const OutputCellIterator it)
{
return _textBuffer->Write(it);
}
// Routine Description:
// - Writes cells to the output buffer.
// Arguments:
// - it - Iterator representing output cell data to write.
// - target - The position to start writing at
// - wrap - change the wrap flag if we hit the end of the row while writing and there's still more data
// Return Value:
// - the iterator at its final position
// Note:
// - will throw exception on error.
OutputCellIterator SCREEN_INFORMATION::Write(const OutputCellIterator it,
const COORD target,
const std::optional<bool> wrap)
{
// NOTE: if wrap = true/false, we want to set the line's wrap to true/false (respectively) if we reach the end of the line
return _textBuffer->Write(it, target, wrap);
}
// Routine Description:
// - This routine writes a rectangular region into the screen buffer.
// Arguments:
// - it - Iterator to the data to insert
// - viewport - rectangular region for insertion
// Return Value:
// - the iterator at its final position
// Note:
// - will throw exception on error.
OutputCellIterator SCREEN_INFORMATION::WriteRect(const OutputCellIterator it,
const Viewport viewport)
{
THROW_HR_IF(E_INVALIDARG, viewport.Height() <= 0);
THROW_HR_IF(E_INVALIDARG, viewport.Width() <= 0);
OutputCellIterator iter = it;
for (auto i = viewport.Top(); i < viewport.BottomExclusive(); i++)
{
iter = _textBuffer->WriteLine(iter, { viewport.Left(), i }, false, viewport.RightInclusive());
}
return iter;
}
// Routine Description:
// - This routine writes a rectangular region into the screen buffer.
// Arguments:
// - data - rectangular data in memory buffer
// - location - origin point (top left corner) of where to write rectangular data
// Return Value:
// - <none>
// Note:
// - will throw exception on error.
void SCREEN_INFORMATION::WriteRect(const OutputCellRect& data,
const COORD location)
{
for (size_t i = 0; i < data.Height(); i++)
{
const auto iter = data.GetRowIter(i);
COORD point;
point.X = location.X;
point.Y = location.Y + static_cast<short>(i);
_textBuffer->WriteLine(iter, point);
}
}
// Routine Description:
// - Clears out the entire text buffer with the default character and
// the current default attribute applied to this screen.
void SCREEN_INFORMATION::ClearTextData()
{
// Clear the text buffer.
_textBuffer->Reset();
}
// Routine Description:
// - finds the boundaries of the word at the given position on the screen
// Arguments:
// - position - location on the screen to get the word boundary for
// Return Value:
// - word boundary positions
std::pair<COORD, COORD> SCREEN_INFORMATION::GetWordBoundary(const COORD position) const
{
// The position argument is in screen coordinates, but we need the
// equivalent buffer position, taking line rendition into account.
COORD clampedPosition = _textBuffer->ScreenToBufferPosition(position);
GetBufferSize().Clamp(clampedPosition);
COORD start{ clampedPosition };
COORD end{ clampedPosition };
// find the start of the word
auto startIt = GetTextLineDataAt(clampedPosition);
while (startIt)
{
--startIt;
if (!startIt || IsWordDelim(*startIt))
{
break;
}
--start.X;
}
// find the end of the word
auto endIt = GetTextLineDataAt(clampedPosition);
while (endIt)
{
if (IsWordDelim(*endIt))
{
break;
}
++endIt;
++end.X;
}
// trim leading zeros if we need to
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
if (gci.GetTrimLeadingZeros())
{
// Trim the leading zeros: 000fe12 -> fe12, except 0x and 0n.
// Useful for debugging
// Get iterator from the start of the selection
auto trimIt = GetTextLineDataAt(start);
// Advance to the second character to check if it's an x or n.
trimIt++;
// Only process if it's a single character. If it's a complicated run, then it's not an x or n.
if (trimIt->size() == 1)
{
// Get the single character
const auto wch = trimIt->front();
// If the string is long enough to have stuff after the 0x/0n and it doesn't have one...
if (end.X > start.X + 2 &&
wch != L'x' &&
wch != L'X' &&
wch != L'n')
{
trimIt--; // Back up to the first character again
// Now loop through and advance the selection forward each time
// we find a single character '0' to Trim off the leading zeroes.
while (trimIt->size() == 1 &&
trimIt->front() == L'0' &&
start.X < end.X - 1)
{
start.X++;
trimIt++;
}
}
}
}
// The calculated range is in buffer coordinates, but the caller is
// expecting screen offsets, so we have to convert these back again.
start = _textBuffer->BufferToScreenPosition(start);
end = _textBuffer->BufferToScreenPosition(end);
return { start, end };
}
TextBuffer& SCREEN_INFORMATION::GetTextBuffer() noexcept
{
return *_textBuffer;
}
const TextBuffer& SCREEN_INFORMATION::GetTextBuffer() const noexcept
{
return *_textBuffer;
}
TextBufferTextIterator SCREEN_INFORMATION::GetTextDataAt(const COORD at) const
{
return _textBuffer->GetTextDataAt(at);
}
TextBufferCellIterator SCREEN_INFORMATION::GetCellDataAt(const COORD at) const
{
return _textBuffer->GetCellDataAt(at);
}
TextBufferTextIterator SCREEN_INFORMATION::GetTextLineDataAt(const COORD at) const
{
return _textBuffer->GetTextLineDataAt(at);
}
TextBufferCellIterator SCREEN_INFORMATION::GetCellLineDataAt(const COORD at) const
{
return _textBuffer->GetCellLineDataAt(at);
}
TextBufferTextIterator SCREEN_INFORMATION::GetTextDataAt(const COORD at, const Viewport limit) const
{
return _textBuffer->GetTextDataAt(at, limit);
}
TextBufferCellIterator SCREEN_INFORMATION::GetCellDataAt(const COORD at, const Viewport limit) const
{
return _textBuffer->GetCellDataAt(at, limit);
}
// Method Description:
// - Updates our internal "virtual bottom" tracker with wherever the viewport
// currently is.
// - <none>
// Return Value:
// - <none>
void SCREEN_INFORMATION::UpdateBottom()
{
_virtualBottom = _viewport.BottomInclusive();
}
// Method Description:
// - Initialize the row with the cursor on it to the standard erase attributes.
// This is executed when we move the cursor below the current viewport in
// VT mode. When that happens in a real terminal, the line is brand new,
// so it gets initialized for the first time with the current attributes.
// Our rows are usually pre-initialized, so re-initialize it here to
// emulate that behavior.
// See MSFT:17415310.
// Arguments:
// - <none>
// Return Value:
// - <none>
void SCREEN_INFORMATION::InitializeCursorRowAttributes()
{
if (_textBuffer)
{
const auto& cursor = _textBuffer->GetCursor();
ROW& row = _textBuffer->GetRowByOffset(cursor.GetPosition().Y);
// The VT standard requires that the new row is initialized with
// the current background color, but with no meta attributes set.
auto fillAttributes = GetAttributes();
fillAttributes.SetStandardErase();
row.GetAttrRow().SetAttrToEnd(0, fillAttributes);
// The row should also be single width to start with.
row.SetLineRendition(LineRendition::SingleWidth);
}
}
// Method Description:
// - Moves the viewport to where we last believed the "virtual bottom" was. This
// emulates linux terminal behavior, where there's no buffer, only a
// viewport. This is called by WriteChars, on output from an application in
// VT mode, before the output is processed by the state machine.
// This ensures that if a user scrolls around in the buffer, and a client
// application uses VT to control the cursor/buffer, those commands are
// still processed relative to the coordinates before the user scrolled the buffer.
// Arguments:
// - <none>
// Return Value:
// - <none>
void SCREEN_INFORMATION::MoveToBottom()
{
const auto virtualView = GetVirtualViewport();
LOG_IF_NTSTATUS_FAILED(SetViewportOrigin(true, virtualView.Origin(), true));
}
// Method Description:
// - Returns the "virtual" Viewport - the viewport with its bottom at
// `_virtualBottom`. For VT operations, this is essentially the mutable
// section of the buffer.
// Arguments:
// - <none>
// Return Value:
// - the virtual terminal viewport
Viewport SCREEN_INFORMATION::GetVirtualViewport() const noexcept
{
const short newTop = _virtualBottom - _viewport.Height() + 1;
return Viewport::FromDimensions({ _viewport.Left(), newTop }, _viewport.Dimensions());
}
// Method Description:
// - Returns true if the character at the cursor's current position is wide.
// See IsGlyphFullWidth
// Arguments:
// - <none>
// Return Value:
// - true if the character at the cursor's current position is wide
bool SCREEN_INFORMATION::CursorIsDoubleWidth() const
{
const auto& buffer = GetTextBuffer();
const auto position = buffer.GetCursor().GetPosition();
TextBufferTextIterator it(TextBufferCellIterator(buffer, position));
return IsGlyphFullWidth(*it);
}
// Method Description:
// - Retrieves this buffer's current render target.
// Arguments:
// - <none>
// Return Value:
// - This buffer's current render target.
IRenderTarget& SCREEN_INFORMATION::GetRenderTarget() noexcept
{
return _renderTarget;
}
// Method Description:
// - Gets the current font of the screen buffer.
// Arguments:
// - <none>
// Return Value:
// - A FontInfo describing our current font.
FontInfo& SCREEN_INFORMATION::GetCurrentFont() noexcept
{
return _currentFont;
}
// Method Description:
// See the non-const version of this function.
const FontInfo& SCREEN_INFORMATION::GetCurrentFont() const noexcept
{
return _currentFont;
}
// Method Description:
// - Gets the desired font of the screen buffer. If we try loading this font and
// have to fallback to another, then GetCurrentFont()!=GetDesiredFont().
// We store this separately, so that if we need to reload the font, we can
// try again with our prefered font info (in the desired font info) instead
// of re-using the looked up value from before.
// Arguments:
// - <none>
// Return Value:
// - A FontInfo describing our desired font.
FontInfoDesired& SCREEN_INFORMATION::GetDesiredFont() noexcept
{
return _desiredFont;
}
// Method Description:
// See the non-const version of this function.
const FontInfoDesired& SCREEN_INFORMATION::GetDesiredFont() const noexcept
{
return _desiredFont;
}
// Method Description:
// - Returns true iff the scroll margins have been set.
// Arguments:
// - <none>
// Return Value:
// - true iff the scroll margins have been set.
bool SCREEN_INFORMATION::AreMarginsSet() const noexcept
{
return _scrollMargins.BottomInclusive() > _scrollMargins.Top();
}
// Routine Description:
// - Determines whether a cursor position is within the vertical bounds of the
// scroll margins, or the margins aren't set.
// Parameters:
// - cursorPosition - The cursor position to test
// Return value:
// - true iff the position is in bounds.
bool SCREEN_INFORMATION::IsCursorInMargins(const COORD cursorPosition) const noexcept
{
// If the margins aren't set, then any position is considered in bounds.
if (!AreMarginsSet())
{
return true;
}
const auto margins = GetAbsoluteScrollMargins().ToInclusive();
return cursorPosition.Y <= margins.Bottom && cursorPosition.Y >= margins.Top;
}
// Routine Description:
// - Engages the legacy VT handling quirk; see TextAttribute::StripErroneousVT16VersionsOfLegacyDefaults
void SCREEN_INFORMATION::SetIgnoreLegacyEquivalentVTAttributes() noexcept
{
_ignoreLegacyEquivalentVTAttributes = true;
}
// Routine Description:
// - Disengages the legacy VT handling quirk; see TextAttribute::StripErroneousVT16VersionsOfLegacyDefaults
void SCREEN_INFORMATION::ResetIgnoreLegacyEquivalentVTAttributes() noexcept
{
_ignoreLegacyEquivalentVTAttributes = false;
}