terminal/src/host/screenInfo.cpp

2773 lines
105 KiB
C++
Raw Normal View History

// 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 },
Reintroduce a color compatibility hack, but only for PowerShells (#6810) There is going to be a very long tail of applications that will explicitly request VT SGR 40/37 when what they really want is to SetConsoleTextAttribute() with a black background/white foreground. Instead of making those applications look bad (and therefore making us look bad, because we're releasing this as an update to something that "looks good" already), we're introducing this compatibility quirk. Before the color reckoning in #6698 + #6506, *every* color was subject to being spontaneously and erroneously turned into the default color. Now, only the 16-color palette value that matches the active console background/foreground color will be destroyed, and only when received from specific applications. Removal will be tracked by #6807. Michael and I discussed what layer this quirk really belonged in. I originally believed it would be sufficient to detect a background color that matched the legacy default background, but @j4james provided an example of where that wouldn't work out (powershell setting the foreground color to white/gray). In addition, it was too heavyhanded: it re-broke black backgrounds for every application. Michael thought that it should live in the server, as a small VT parser that righted the wrongs coming directly out of the application. On further investigation, however, I realized that we'd need to push more information up into the server (so that it could make the decision about which VT was wrong and which was right) than should be strictly necessary. The host knows which colors are right and wrong, and it gets final say in what ends up in the buffer. Because of that, I chose to push the quirk state down through WriteConsole to DoWriteConsole and toggle state on the SCREEN_INFORMATION that indicates whether the colors coming out of the application are to be distrusted. This quirk _only applies to pwsh.exe and powershell.exe._ NOTE: This doesn't work for PowerShell the .NET Global tool, because it is run as an assembly through dotnet.exe. I have no opinion on how to fix this, or whether it is worth fixing. VALIDATION ---------- I configured my terminals to have an incredibly garish color scheme to show exactly what's going to happen as a result of this. The _default terminal background_ is purple or red, and the foreground green. I've printed out a heap of test colors to see how black interacts with them. Pull request #6810 contains the images generated from this test. The only color lines that change are the ones where black as a background or white as a foreground is selected out of the 16-color palette explicitly. Reverse video still works fine (because black is in the foreground!), and it's even possible to represent "black on default" and reverse it into "default on black", despite the black in question having been `40`. Fixes #6767.
2020-07-11 00:25:39 +02:00
_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().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();
Improve conpty rendering of default colors in legacy apps (#6698) Essentially what this does is map the default legacy foreground and background attributes (typically white on black) to the `IsDefault` color type in the `TextColor` class. As a result, we can now initialize the buffer for "legacy" shells (like PowerShell and cmd.exe) with default colors, instead of white on black. This fixes the startup rendering in conpty clients, which expect an initial default background color. It also makes these colors update appropriately when the default palette values change. One complication in getting this to work, is that the console permits users to change which color indices are designated as defaults, so we can't assume they'll always be white on black. This means that the legacy-to-`TextAttribute` conversion will need access to those default values. Unfortunately the defaults are stored in the conhost `Settings` class (the `_wFillAttribute` field), which isn't easily accessible to all the code that needs to construct a `TextAttribute` from a legacy value. The `OutputCellIterator` is particularly problematic, because some iterator types need to generate a new `TextAttribute` on every iteration. So after trying a couple of different approaches, I decided that the least worst option would be to add a pair of static properties for the legacy defaults in the `TextAttribute` class itself, then refresh those values from the `Settings` class whenever the defaults changed (this only happens on startup, or when the conhost _Properties_ dialog is edited). And once the `TextAttribute` class had access to those defaults, it was fairly easy to adapt the constructor to handle the conversion of default values to the `IsDefault` color type. I could also then simplify the `TextAttribute::GetLegacyAttributes` method which does the reverse mapping, and which previously required the default values to be passed in as a parameter VALIDATION I had to make one small change to the `TestRoundtripExhaustive` unit test which assumed that all legacy attributes would convert to legacy color types, which is no longer the case, but otherwise all the existing tests passed as is. I added a new unit test verifying that the default legacy attributes correctly mapped to default color types, and the default color types were mapped back to the correct legacy attributes. I've manually confirmed that this fixed the issue raised in #5952, namely that the conhost screen is cleared with the correct default colors, and also that it is correctly refreshed when changing the palette from the properties dialog. And I've combined this PR with #6506, and confirmed that the PowerShell and the cmd shell renderings in Windows Terminal are at least improved, if not always perfect. This is a prerequisite for PR #6506 Closes #5952
2020-07-01 20:08:30 +02:00
*pwAttributes = GetAttributes().GetLegacyAttributes();
*pwPopupAttributes = _PopupAttributes.GetLegacyAttributes();
// the copy length must be constant for now to keep OACR happy with buffer overruns.
Fix SGR indexed colors to distinguish Indexed256 color (and more) (#5834) This PR introduces a new `ColorType` to allow us to distinguish between `SGR` indexed colors from the 16 color table, the lower half of which can be brightened, and the ISO/ITU indexed colors from the 256 color table, which have a fixed brightness. Retaining the distinction between these two types will enable us to forward the correct `SGR` sequences to conpty when addressing issue #2661. The other benefit of retaining the color index (which we didn't previously do for ISO/ITU colors) is that it ensures that the colors are updated correctly when the color scheme is changed. ## References * This is another step towards fixing the conpty narrowing bugs in issue #2661. * This is technically a fix for issue #5384, but that won't be apparent until #2661 is complete. ## PR Checklist * [x] Closes #1223 * [x] CLA signed. * [x] Tests added/passed * [ ] Requires documentation to be updated * [x] I've discussed this with core contributors already. ## Detailed Description of the Pull Request / Additional comments The first part of this PR was the introduction of a new `ColorType` in the `TextColor` class. Instead of just the one `IsIndex` type, there is now an `IsIndex16` and an `IsIndex256`. `IsIndex16` covers the eight original ANSI colors set with `SGR 3x` and `SGR 4x`, as well as the brighter aixterm variants set with `SGR 9x` and `SGR 10x`. `IsIndex256` covers the 256 ISO/ITU indexed colors set with `SGR 38;5` and `SGR 48;5`. There are two reasons for this distinction. The first is that the ANSI colors have the potential to be brightened by the `SGR 1` bold attribute, while the ISO/ITO color do not. The second reason is that when forwarding an attributes through conpty, we want to try and preserve the original SGR sequence that generated each color (to the extent that that is possible). By having the two separate types, we can map the `IsIndex16` colors back to ANSI/aixterm values, and `IsIndex256` to the ISO/ITU sequences. In addition to the VT colors, we also have to deal with the legacy colors set by the Windows console APIs, but we don't really need a separate type for those. It seemed most appropriate to me to store them as `IsIndex256` colors, since it doesn't make sense to have them brightened by the `SGR 1` attribute (which is what would happen if they were stored as `IsIndex16`). If a console app wanted a bright color it would have selected one, so we shouldn't be messing with that choice. The second part of the PR was the unification of the two color tables. Originally we had a 16 color table for the legacy colors, and a separate table for the 256 ISO/ITU colors. These have now been merged into one, so color table lookups no longer need to decide which of the two tables they should be referencing. I've also updated all the methods that took a color table as a parameter to use a `basic_string_view` instead of separate pointer and length variables, which I think makes them a lot easier and safer to work with. With this new architecture in place, I could now update the `AdaptDispatch` SGR implementation to store the ISO/ITU indexed colors as `IsIndex256` values, where before they were mapped to RGB values (which prevented them reflecting any color scheme changes). I could also update the `TerminalDispatch` implementation to differentiate between the two index types, so that the `SGR 1` brightening would only be applied to the ANSI colors. I've also done a bit of code refactoring to try and minimise any direct access to the color tables, getting rid of a lot of places that were copying tables with `memmove` operations. I'm hoping this will make it easier for us to update the code in the future if we want to reorder the table entries (which is likely a requirement for unifying the `AdaptDispatch` and `TerminalDispatch` implementations). ## Validation Steps Performed For testing, I've just updated the existing unit tests to account for the API changes. The `TextColorTests` required an extra parameter specifying the index type when setting an index. And the `AdapterTest` and `ScreenBufferTests` required the use of the new `SetIndexedXXX` methods in order to be explicit about the index type, instead of relying on the `TextAttribute` constructor and the old `SetForeground` and `SetBackground` methods which didn't have a way to differentiate index types. I've manually tested the various console APIs (`SetConsoleTextAttribute`, `ReadConsoleOutputAttribute`, and `ReadConsoleOutput`), to make sure they are still setting and reading the attributes as well as they used to. And I've tested the `SetConsoleScreenBufferInfoEx` and `GetConsoleScreenBufferInfoEx` APIs to make sure they can read and write the color table correctly. I've also tested the color table in the properties dialog, made sure it was saved and restored from the registry correctly, and similarly saved and restored from a shortcut link. Note that there are still a bunch of issues with the color table APIs, but no new problems have been introduced by the changes in this PR, as far as I could tell. I've also done a bunch of manual tests of `OSC 4` to make sure it's updating all the colors correctly (at least in conhost), and confirmed that the test case in issue #1223 now works as expected.
2020-05-28 00:34:45 +02:00
for (size_t i = 0; i < COLOR_TABLE_SIZE; i++)
{
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 23:13:22 +01:00
lpColorTable[i] = gci.GetLegacyColorTableEntry(i);
Fix SGR indexed colors to distinguish Indexed256 color (and more) (#5834) This PR introduces a new `ColorType` to allow us to distinguish between `SGR` indexed colors from the 16 color table, the lower half of which can be brightened, and the ISO/ITU indexed colors from the 256 color table, which have a fixed brightness. Retaining the distinction between these two types will enable us to forward the correct `SGR` sequences to conpty when addressing issue #2661. The other benefit of retaining the color index (which we didn't previously do for ISO/ITU colors) is that it ensures that the colors are updated correctly when the color scheme is changed. ## References * This is another step towards fixing the conpty narrowing bugs in issue #2661. * This is technically a fix for issue #5384, but that won't be apparent until #2661 is complete. ## PR Checklist * [x] Closes #1223 * [x] CLA signed. * [x] Tests added/passed * [ ] Requires documentation to be updated * [x] I've discussed this with core contributors already. ## Detailed Description of the Pull Request / Additional comments The first part of this PR was the introduction of a new `ColorType` in the `TextColor` class. Instead of just the one `IsIndex` type, there is now an `IsIndex16` and an `IsIndex256`. `IsIndex16` covers the eight original ANSI colors set with `SGR 3x` and `SGR 4x`, as well as the brighter aixterm variants set with `SGR 9x` and `SGR 10x`. `IsIndex256` covers the 256 ISO/ITU indexed colors set with `SGR 38;5` and `SGR 48;5`. There are two reasons for this distinction. The first is that the ANSI colors have the potential to be brightened by the `SGR 1` bold attribute, while the ISO/ITO color do not. The second reason is that when forwarding an attributes through conpty, we want to try and preserve the original SGR sequence that generated each color (to the extent that that is possible). By having the two separate types, we can map the `IsIndex16` colors back to ANSI/aixterm values, and `IsIndex256` to the ISO/ITU sequences. In addition to the VT colors, we also have to deal with the legacy colors set by the Windows console APIs, but we don't really need a separate type for those. It seemed most appropriate to me to store them as `IsIndex256` colors, since it doesn't make sense to have them brightened by the `SGR 1` attribute (which is what would happen if they were stored as `IsIndex16`). If a console app wanted a bright color it would have selected one, so we shouldn't be messing with that choice. The second part of the PR was the unification of the two color tables. Originally we had a 16 color table for the legacy colors, and a separate table for the 256 ISO/ITU colors. These have now been merged into one, so color table lookups no longer need to decide which of the two tables they should be referencing. I've also updated all the methods that took a color table as a parameter to use a `basic_string_view` instead of separate pointer and length variables, which I think makes them a lot easier and safer to work with. With this new architecture in place, I could now update the `AdaptDispatch` SGR implementation to store the ISO/ITU indexed colors as `IsIndex256` values, where before they were mapped to RGB values (which prevented them reflecting any color scheme changes). I could also update the `TerminalDispatch` implementation to differentiate between the two index types, so that the `SGR 1` brightening would only be applied to the ANSI colors. I've also done a bit of code refactoring to try and minimise any direct access to the color tables, getting rid of a lot of places that were copying tables with `memmove` operations. I'm hoping this will make it easier for us to update the code in the future if we want to reorder the table entries (which is likely a requirement for unifying the `AdaptDispatch` and `TerminalDispatch` implementations). ## Validation Steps Performed For testing, I've just updated the existing unit tests to account for the API changes. The `TextColorTests` required an extra parameter specifying the index type when setting an index. And the `AdapterTest` and `ScreenBufferTests` required the use of the new `SetIndexedXXX` methods in order to be explicit about the index type, instead of relying on the `TextAttribute` constructor and the old `SetForeground` and `SetBackground` methods which didn't have a way to differentiate index types. I've manually tested the various console APIs (`SetConsoleTextAttribute`, `ReadConsoleOutputAttribute`, and `ReadConsoleOutput`), to make sure they are still setting and reading the attributes as well as they used to. And I've tested the `SetConsoleScreenBufferInfoEx` and `GetConsoleScreenBufferInfoEx` APIs to make sure they can read and write the color table correctly. I've also tested the color table in the properties dialog, made sure it was saved and restored from the registry correctly, and similarly saved and restored from a shortcut link. Note that there are still a bunch of issues with the color table APIs, but no new problems have been introduced by the changes in this PR, as far as I could tell. I've also done a bunch of manual tests of `OSC 4` to make sure it's updating all the colors correctly (at least in conhost), and confirmed that the test case in issue #1223 now works as expected.
2020-05-28 00:34:45 +02:00
}
*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());
Merged PR 3215853: Fix spacing/layout for block characters and many retroactively-recategorized emoji (and more!) This encompasses a handful of problems with column counting. The Terminal project didn't set a fallback column counter. Oops. I've fixed this to use the `DxEngine` as the fallback. The `DxEngine` didn't implement its fallback method. Oops. I've fixed this to use the `CustomTextLayout` to figure out the advances based on the same font and fallback pattern as the real final layout, just without "rounding" it into cells yet. - `CustomTextLayout` has been updated to move the advance-correction into a separate phase from glyph shaping. Previously, we corrected the advances to nice round cell counts during shaping, which is fine for drawing, but hard for column count analysis. - Now that there are separate phases, an `Analyze` method was added to the `CustomTextLayout` which just performs the text analysis steps and the glyph shaping, but no advance correction to column boundaries nor actual drawing. I've taken the caching code that I was working on to improve chafa, and I've brought it into this. Now that we're doing a lot of fallback and heavy lifting in terms of analysis via the layout, we should cache the results until the font changes. I've adjusted how column counting is done overall. It's always been in these phases: 1. We used a quick-lookup of ranges of characters we knew to rapidly decide `Narrow`, `Wide` or `Invalid` (a.k.a. "I dunno") 2. If it was `Invalid`, we consulted a table based off of the Unicode standard that has either `Narrow`, `Wide`, or `Ambiguous` as a result. 3. If it's still `Ambiguous`, we consult a render engine fallback (usually GDI or now DX) to see how many columns it would take. 4. If we still don't know, then it's `Wide` to be safe. - I've added an additional flow here. The quick-lookup can now return `Ambiguous` off the bat for some glyph characters in the x2000-x3000 range that used to just be simple shapes but have been retroactively recategorized as emoji and are frequently now using full width color glyphs. - This new state causes the lookup to go immediately to the render engine if it is available instead of consulting the Unicode standard table first because the half/fullwidth table doesn't appear to have been updated for this nuance to reclass these characters as ambiguous, but we'd like to keep that table as a "generated from the spec" sort of table and keep our exceptions in the "quick lookup" function. I have confirmed the following things "just work" now: - The windows logo flag from the demo. (⚫⚪💖✅🌌😊) - The dotted chart on the side of crossterm demo (•) - The powerline characters that make arrows with the Consolas patched font (██) - An accented é - The warning and checkmark symbols appearing same size as the X. (✔⚠🔥) Related work items: #21167256, #21237515, #21243859, #21274645, #21296827
2019-05-02 01:13:53 +02:00
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()),
Improve conpty rendering of default colors in legacy apps (#6698) Essentially what this does is map the default legacy foreground and background attributes (typically white on black) to the `IsDefault` color type in the `TextColor` class. As a result, we can now initialize the buffer for "legacy" shells (like PowerShell and cmd.exe) with default colors, instead of white on black. This fixes the startup rendering in conpty clients, which expect an initial default background color. It also makes these colors update appropriately when the default palette values change. One complication in getting this to work, is that the console permits users to change which color indices are designated as defaults, so we can't assume they'll always be white on black. This means that the legacy-to-`TextAttribute` conversion will need access to those default values. Unfortunately the defaults are stored in the conhost `Settings` class (the `_wFillAttribute` field), which isn't easily accessible to all the code that needs to construct a `TextAttribute` from a legacy value. The `OutputCellIterator` is particularly problematic, because some iterator types need to generate a new `TextAttribute` on every iteration. So after trying a couple of different approaches, I decided that the least worst option would be to add a pair of static properties for the legacy defaults in the `TextAttribute` class itself, then refresh those values from the `Settings` class whenever the defaults changed (this only happens on startup, or when the conhost _Properties_ dialog is edited). And once the `TextAttribute` class had access to those defaults, it was fairly easy to adapt the constructor to handle the conversion of default values to the `IsDefault` color type. I could also then simplify the `TextAttribute::GetLegacyAttributes` method which does the reverse mapping, and which previously required the default values to be passed in as a parameter VALIDATION I had to make one small change to the `TestRoundtripExhaustive` unit test which assumed that all legacy attributes would convert to legacy color types, which is no longer the case, but otherwise all the existing tests passed as is. I added a new unit test verifying that the default legacy attributes correctly mapped to default color types, and the default color types were mapped back to the correct legacy attributes. I've manually confirmed that this fixed the issue raised in #5952, namely that the conhost screen is cleared with the correct default colors, and also that it is correctly refreshed when changing the palette from the properties dialog. And I've combined this PR with #6506, and confirmed that the PowerShell and the cmd shell renderings in Windows Terminal are at least improved, if not always perfect. This is a prerequisite for PR #6506 Closes #5952
2020-07-01 20:08:30 +02:00
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
2019-05-21 08:15:44 +02:00
// 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.
2019-05-21 08:15:44 +02:00
// 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.
2019-05-21 08:15:44 +02:00
// 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);
Adjusts High DPI scaling to enable differential rendering (#5345) ## Summary of the Pull Request - Adjusts scaling practices in `DxEngine` (and related scaling practices in `TerminalControl`) for pixel-perfect row baselines and spacing at High DPI such that differential row-by-row rendering can be applied at High DPI. ## References - #5185 ## PR Checklist * [x] Closes #5320, closes #3515, closes #1064 * [x] I work here. * [x] Manually tested. * [x] No doc. * [x] Am core contributor. Also discussed with some of them already via Teams. ## Detailed Description of the Pull Request / Additional comments **WAS:** - We were using implicit DPI scaling on the `ID2D1RenderTarget` and running all of our processing in DIPs (Device-Independent Pixels). That's all well and good for getting things bootstrapped quickly, but it leaves the actual scaling of the draw commands up to the discretion of the rendering target. - When we don't get to explicitly choose exactly how many pixels tall/wide and our X/Y placement perfectly, the nature of floating point multiplication and division required to do the presentation can cause us to drift off slightly out of our control depending on what the final display resolution actually is. - Differential drawing cannot work unless we can know the exact integer pixels that need to be copied/moved/preserved/replaced between frames to give to the `IDXGISwapChain1::Present1` method. If things spill into fractional pixels or the sizes of rows/columns vary as they are rounded up and down implicitly, then we cannot do the differential rendering. **NOW:** - When deciding on a font, the `DxEngine` will take the scale factor into account and adjust the proposed height of the requested font. Then the remainder of the existing code that adjusts the baseline and integer-ifies each character cell will run naturally from there. That code already works correctly to align the height at normal DPI and scale out the font heights and advances to take an exact integer of pixels. - `TermControl` has to use the scale now, in some places, and stop scaling in other places. This has to do with how the target's nature used to be implicit and is now explicit. For instance, determining where the cursor click hits must be scaled now. And determining the pixel size of the display canvas must no longer be scaled. - `DxEngine` will no longer attempt to scale the invalid regions per my attempts in #5185 because the cell size is scaled. So it should work the same as at 96 DPI. - The block is removed from the `DxEngine` that was causing a full invalidate on every frame at High DPI. - A TODO was removed from `TermControl` that was invalidating everything when the DPI changed because the underlying renderer will already do that. ## Validation Steps Performed * [x] Check at 150% DPI. Print text, scroll text down and up, do selection. * [x] Check at 100% DPI. Print text, scroll text down and up, do selection. * [x] Span two different DPI monitors and drag between them. * [x] Giant pile of tests in https://github.com/microsoft/terminal/pull/5345#issuecomment-614127648 Co-authored-by: Dustin Howett <duhowett@microsoft.com> Co-authored-by: Mike Griese <migrie@microsoft.com>
2020-04-22 23:59:51 +02:00
// 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();
Show a double width cursor for double width characters (#5319) # Summary of the Pull Request This PR will allow the cursor to be double width when on top of a double width character. This required changing `IsCursorDoubleWidth` to check whether the glyph the cursor's on top of is double width. This code is exactly the same as the original PR that addressed this issue in #2932. That one got reverted at some point due to the crashes related to it, but due to a combination of Terminal having come further since that PR and other changes to address use-after-frees, some of the crashes may/may not be relevant now. The ones that seemed to be relevant/repro-able, I attempt to address in this PR. The `IsCursorDoubleWidth` check would fail during the `TextBuffer::Reflow` call inside of `Terminal::UserResize` occasionally, particularly when `newCursor.EndDeferDrawing()` is called. This is because when we tell the newCursor to `EndDefer`, the renderer will attempt to redraw the cursor. As part of this redraw, it'll ask if `IsCursorDoubleWidth`, and if the renderer managed to ask this before `UserResize` swapped out the old buffer with the new one from `Reflow`, the renderer will be asking the old buffer if its out-of-bounds cursor is double width. This was pretty easily repro'd using `cmatrix -u0` and resizing the window like a madman. As a solution, I've moved the Start/End DeferDrawing calls out of `Reflow` and into `UserResize`. This way, I can "clamp" the portion of the code where the newBuffer is getting created and reflowed and swapped into the Terminal buffer, and only allow the renderer to draw once the swap is done. This also means that ConHost's `ResizeWithReflow` needed to change slightly. In addition, I've added a WriteLock to `SetCursorOn`. It was mentioned as a fix for a crash in #2965 (although I can't repro), and I also figured it would be good to try to emulate where ConHost locks with regards to Cursor operations, and this seemed to be one that we were missing. ## PR Checklist * [x] Closes #2713 * [x] CLA signed * [x] Tests added/passed ## Validation Steps Performed Manual validation that the cursor is indeed chonky, added a test case to check that we are correctly saying that the cursor is double width (not too sure if I put it in the right place). Also open to other test case ideas and thoughts on what else I should be careful for since I am quite nervous about what other crashes might occur.
2020-04-15 21:23:06 +02:00
// 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();
Prevent the cursor type being reset when changing the visibility (#5251) A side effect of the `SetConsoleCursorInfo` API is that it resets the cursor type to _Legacy_. This makes it impossible to change the cursor visibility via the console APIs without also resetting the user's preferred cursor type. This PR attempts to fix that limitation, by only resetting the cursor type if the size has also been changed. ## PR Checklist * [x] Closes #4124 * [x] CLA signed * [ ] Tests added/passed * [ ] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments I suspect the reason for the original behaviour was because the `SetConsoleCursorInfo` API sets both the visibility and the size, and if you're setting the size, it's assumed you'd want the _Legacy_ cursor type, because that's the only style for which the size is applicable. So my solution was to only reset the cursor type if the requested cursor size was actually different from the current size. That should be reasonably backwards compatible with most size-changing code, but also allow for changing the visibility without resetting the cursor type. ## Validation Steps Performed I've tested the example code from issue #4124, and confirmed that it now works correctly without resetting the cursor type. I've also tested the console's _mark mode_, which temporarily changes the cursor size while selecting. I've confirmed that the size still changes, and that the original cursor type is restored afterwards.
2020-04-07 01:00:40 +02:00
const auto originalSize = cursor.GetSize();
cursor.SetSize(Size);
cursor.SetIsVisible(Visible);
Prevent the cursor type being reset when changing the visibility (#5251) A side effect of the `SetConsoleCursorInfo` API is that it resets the cursor type to _Legacy_. This makes it impossible to change the cursor visibility via the console APIs without also resetting the user's preferred cursor type. This PR attempts to fix that limitation, by only resetting the cursor type if the size has also been changed. ## PR Checklist * [x] Closes #4124 * [x] CLA signed * [ ] Tests added/passed * [ ] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments I suspect the reason for the original behaviour was because the `SetConsoleCursorInfo` API sets both the visibility and the size, and if you're setting the size, it's assumed you'd want the _Legacy_ cursor type, because that's the only style for which the size is applicable. So my solution was to only reset the cursor type if the requested cursor size was actually different from the current size. That should be reasonably backwards compatible with most size-changing code, but also allow for changing the visibility without resetting the cursor type. ## Validation Steps Performed I've tested the example code from issue #4124, and confirmed that it now works correctly without resetting the cursor type. I've also tested the console's _mark mode_, which temporarily changes the cursor size while selecting. I've confirmed that the size still changes, and that the original cursor type is restored afterwards.
2020-04-07 01:00:40 +02:00
// 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 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
2019-05-21 08:15:44 +02:00
// 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;
}
Emit lines wrapped due to spaces at the end correctly (#5294) ## Summary of the Pull Request When WSL vim prints the initial empty buffer (the one that's just a bunch of '\~'s), it prints this by doing the following: * Print '\~' followed by enough spaces to clear the line * Use CUP (`^[[H`) to move the cursor to the start of the next line * repeat until the buffer is full When we'd get the line of "\~ "... in conhost, we'd mark that line as wrapped. Logically, it doesn't really make any sense that when we follow that up by moving the cursor, the line is wrapped. However, this is just how conhost is right now. This wasn't ever a problem in just conhost before, because we really didn't care if lines in the alt buffer were "wrapped" or not. Plus, when vim would get resized, it would just reprint it's own buffer anyways. Nor was this a problem in conpty before this year (2020). We've only just recently added logic to conpty to try and preserve wrapped lines. Initially, I tried fixing this by breaking the line manually when the cursor was moved. This seemed to work great, except for the win32 vim.exe. Vim.exe doesn't emit a newline or a CUP to get to the next line. It just _goes for it_ and keeps printing. So there's _no way_ for us to know the line broke, because they're essentially just printing one long line, assuming we'll automatically move the cursor. So instead, I'm making sure to emit the proper number of spaces at the end of a line when the line is wrapped. We won't do any funny business in that scenario and try to optimize for them, we'll _just print the spaces_. ## References * #5181 - This change regressed this * #4415 - Actually implemented wrapped lines in conpty ## PR Checklist * [x] Closes #5291 * [x] I work here * [x] Tests added/passed * [n/a] Requires documentation to be updated ## Validation Steps Performed * Wrote a unittest first and foremost * Checked vtpipeterm to make sure vim still works * checked Terminal to make sure vim still works
2020-04-15 17:52:11 +02:00
// 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();
Correct fill attributes when scrolling and erasing (#3100) ## Summary of the Pull Request Operations that erase areas of the screen are typically meant to do so using the current color attributes, but with the rendition attributes reset (what we refer to as meta attributes). This also includes scroll operations that have to clear the area of the screen that has scrolled into view. The only exception is the _Erase Scrollback_ operation, which needs to reset the buffer with the default attributes. This PR updates all of these cases to apply the correct attributes when scrolling and erasing. ## PR Checklist * [x] Closes #2553 * [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA * [x] Tests added/passed * [ ] Requires documentation to be updated * [ ] I've not really discussed this with core contributors. I'm ready to accept this work might be rejected in favor of a different grand plan. ## Detailed Description of the Pull Request / Additional comments My initial plan was to use a special case legacy attribute value to indicate the "standard erase attribute" which could safely be passed through the legacy APIs. But this wouldn't cover the cases that required default attributes to be used. And then with the changes in PR #2668 and #2987, it became clear that our requirements could be better achieved with a couple of new private APIs that wouldn't have to depend on legacy attribute hacks at all. To that end, I've added the `PrivateFillRegion` and `PrivateScrollRegion` APIs to the `ConGetSet` interface. These are just thin wrappers around the existing `SCREEN_INFORMATION::Write` method and the `ScrollRegion` function respectively, but with a simple boolean parameter to choose between filling with default attributes or the standard erase attributes (i.e the current colors but with meta attributes reset). With those new APIs in place, I could then update most scroll operations to use `PrivateScrollRegion`, and most erase operations to use `PrivateFillRegion`. The functions affected by scrolling included: * `DoSrvPrivateReverseLineFeed` (the RI command) * `DoSrvPrivateModifyLinesImpl` (the IL and DL commands) * `AdaptDispatch::_InsertDeleteHelper` (the ICH and DCH commands) * `AdaptDispatch::_ScrollMovement` (the SU and SD commands) The functions affected by erasing included: * `AdaptDispatch::_EraseSingleLineHelper` (the EL command, and most ED variants) * `AdaptDispatch::EraseCharacters` (the ECH command) While updating these erase methods, I noticed that both of them also required boundary fixes similar to those in PR #2505 (i.e. the horizontal extent of the erase operation should apply to the full width of the buffer, and not just the current viewport width), so I've addressed that at the same time. In addition to the changes above, there were also a few special cases, the first being the line feed handling, which required updating in a number of places to use the correct erase attributes: * `SCREEN_INFORMATION::InitializeCursorRowAttributes` - this is used to initialise the rows that pan into view when the viewport is moved down the buffer. * `TextBuffer::IncrementCircularBuffer` - this occurs when we scroll passed the very end of the buffer, and a recycled row now needs to be reinitialised. * `AdjustCursorPosition` - when within margin boundaries, this relies on a couple of direct calls to `ScrollRegion` which needed to be passed the correct fill attributes. The second special case was the full screen erase sequence (`ESC 2 J`), which is handled separately from the other ED sequences. This required updating the `SCREEN_INFORMATION::VtEraseAll` method to use the standard erase attributes, and also required changes to the horizontal extent of the filled area, since it should have been clearing the full buffer width (the same issue as the other erase operations mentioned above). Finally, there was the `AdaptDispatch::_EraseScrollback` method, which uses both scroll and fill operations, which could now be handled by the new `PrivateScrollRegion` and `PrivateFillRegion` APIs. But in this case we needed to fill with the default attributes rather than the standard erase attributes. And again this implementation needed some changes to make sure the full width of the active area was retained after the erase, similar to the horizontal boundary issues with the other erase operations. Once all these changes were made, there were a few areas of the code that could then be simplified quite a bit. The `FillConsoleOutputCharacterW`, `FillConsoleOutputAttribute`, and `ScrollConsoleScreenBufferW` were no longer needed in the `ConGetSet` interface, so all of that code could now be removed. The `_EraseSingleLineDistanceHelper` and `_EraseAreaHelper` methods in the `AdaptDispatch` class were also no longer required and could be removed. Then there were the hacks to handle legacy default colors in the `FillConsoleOutputAttributeImpl` and `ScrollConsoleScreenBufferWImpl` implementations. Since those hacks were only needed for VT operations, and the VT code no longer calls those methods, there was no longer a need to retain that behaviour (in fact there are probably some edge cases where that behaviour might have been considered a bug when reached via the public console APIs). ## Validation Steps Performed For most of the scrolling operations there were already existing tests in place, and those could easily be extended to check that the meta attributes were correctly reset when filling the revealed lines of the scrolling region. In the screen buffer tests, I made updates of that sort to the `ScrollOperations` method (handling SU, SD, IL, DL, and RI), the `InsertChars` and `DeleteChars` methods (ICH and DCH), and the `VtNewlinePastViewport` method (LF). I also added a new `VtNewlinePastEndOfBuffer` test to check the case where the line feed causes the viewport to pan past the end of the buffer. The erase operations, however, were being covered by adapter tests, and those aren't really suited for this kind of functionality (the same sort of issue came up in PR #2505). As a result I've had to reimplement those tests as screen buffer tests. Most of the erase operations are covered by the `EraseTests` method, except the for the scrollback erase which has a dedicated `EraseScrollbackTests` method. I've also had to replace the `HardReset` adapter test, but that was already mostly covered by the `HardResetBuffer` screen buffer test, which I've now extended slightly (it could do with some more checks, but I think that can wait for a future PR when we're fixing other RIS issues).
2019-12-11 00:14:40 +01:00
// 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,
Correct fill attributes when scrolling and erasing (#3100) ## Summary of the Pull Request Operations that erase areas of the screen are typically meant to do so using the current color attributes, but with the rendition attributes reset (what we refer to as meta attributes). This also includes scroll operations that have to clear the area of the screen that has scrolled into view. The only exception is the _Erase Scrollback_ operation, which needs to reset the buffer with the default attributes. This PR updates all of these cases to apply the correct attributes when scrolling and erasing. ## PR Checklist * [x] Closes #2553 * [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA * [x] Tests added/passed * [ ] Requires documentation to be updated * [ ] I've not really discussed this with core contributors. I'm ready to accept this work might be rejected in favor of a different grand plan. ## Detailed Description of the Pull Request / Additional comments My initial plan was to use a special case legacy attribute value to indicate the "standard erase attribute" which could safely be passed through the legacy APIs. But this wouldn't cover the cases that required default attributes to be used. And then with the changes in PR #2668 and #2987, it became clear that our requirements could be better achieved with a couple of new private APIs that wouldn't have to depend on legacy attribute hacks at all. To that end, I've added the `PrivateFillRegion` and `PrivateScrollRegion` APIs to the `ConGetSet` interface. These are just thin wrappers around the existing `SCREEN_INFORMATION::Write` method and the `ScrollRegion` function respectively, but with a simple boolean parameter to choose between filling with default attributes or the standard erase attributes (i.e the current colors but with meta attributes reset). With those new APIs in place, I could then update most scroll operations to use `PrivateScrollRegion`, and most erase operations to use `PrivateFillRegion`. The functions affected by scrolling included: * `DoSrvPrivateReverseLineFeed` (the RI command) * `DoSrvPrivateModifyLinesImpl` (the IL and DL commands) * `AdaptDispatch::_InsertDeleteHelper` (the ICH and DCH commands) * `AdaptDispatch::_ScrollMovement` (the SU and SD commands) The functions affected by erasing included: * `AdaptDispatch::_EraseSingleLineHelper` (the EL command, and most ED variants) * `AdaptDispatch::EraseCharacters` (the ECH command) While updating these erase methods, I noticed that both of them also required boundary fixes similar to those in PR #2505 (i.e. the horizontal extent of the erase operation should apply to the full width of the buffer, and not just the current viewport width), so I've addressed that at the same time. In addition to the changes above, there were also a few special cases, the first being the line feed handling, which required updating in a number of places to use the correct erase attributes: * `SCREEN_INFORMATION::InitializeCursorRowAttributes` - this is used to initialise the rows that pan into view when the viewport is moved down the buffer. * `TextBuffer::IncrementCircularBuffer` - this occurs when we scroll passed the very end of the buffer, and a recycled row now needs to be reinitialised. * `AdjustCursorPosition` - when within margin boundaries, this relies on a couple of direct calls to `ScrollRegion` which needed to be passed the correct fill attributes. The second special case was the full screen erase sequence (`ESC 2 J`), which is handled separately from the other ED sequences. This required updating the `SCREEN_INFORMATION::VtEraseAll` method to use the standard erase attributes, and also required changes to the horizontal extent of the filled area, since it should have been clearing the full buffer width (the same issue as the other erase operations mentioned above). Finally, there was the `AdaptDispatch::_EraseScrollback` method, which uses both scroll and fill operations, which could now be handled by the new `PrivateScrollRegion` and `PrivateFillRegion` APIs. But in this case we needed to fill with the default attributes rather than the standard erase attributes. And again this implementation needed some changes to make sure the full width of the active area was retained after the erase, similar to the horizontal boundary issues with the other erase operations. Once all these changes were made, there were a few areas of the code that could then be simplified quite a bit. The `FillConsoleOutputCharacterW`, `FillConsoleOutputAttribute`, and `ScrollConsoleScreenBufferW` were no longer needed in the `ConGetSet` interface, so all of that code could now be removed. The `_EraseSingleLineDistanceHelper` and `_EraseAreaHelper` methods in the `AdaptDispatch` class were also no longer required and could be removed. Then there were the hacks to handle legacy default colors in the `FillConsoleOutputAttributeImpl` and `ScrollConsoleScreenBufferWImpl` implementations. Since those hacks were only needed for VT operations, and the VT code no longer calls those methods, there was no longer a need to retain that behaviour (in fact there are probably some edge cases where that behaviour might have been considered a bug when reached via the public console APIs). ## Validation Steps Performed For most of the scrolling operations there were already existing tests in place, and those could easily be extended to check that the meta attributes were correctly reset when filling the revealed lines of the scrolling region. In the screen buffer tests, I made updates of that sort to the `ScrollOperations` method (handling SU, SD, IL, DL, and RI), the `InsertChars` and `DeleteChars` methods (ICH and DCH), and the `VtNewlinePastViewport` method (LF). I also added a new `VtNewlinePastEndOfBuffer` test to check the case where the line feed causes the viewport to pan past the end of the buffer. The erase operations, however, were being covered by adapter tests, and those aren't really suited for this kind of functionality (the same sort of issue came up in PR #2505). As a result I've had to reimplement those tests as screen buffer tests. Most of the erase operations are covered by the `EraseTests` method, except the for the scrollback erase which has a dedicated `EraseScrollbackTests` method. I've also had to replace the `HardReset` adapter test, but that was already mostly covered by the `HardResetBuffer` screen buffer test, which I've now extended slightly (it could do with some more checks, but I think that can wait for a future PR when we're fixing other RIS issues).
2019-12-11 00:14:40 +01:00
initAttributes,
Improve the legacy color conversions (#6358) This PR provides a faster algorithm for converting 8-bit and 24-bit colors into the 4-bit legacy values that are required by the Win32 console APIs. It also fixes areas of the code that were incorrectly using a simple 16-color conversion that didn't handle 8-bit and 24-bit values. The faster conversion algorithm should be an improvement for issues #783 and #3950. One of the main points of this PR was to fix the `ReadConsoleOutputAttribute` API, which was using a simplified legacy color conversion (the original `TextAttribute:GetLegacyAttributes` method), which could only handle values from the 16-color table. RGB values, and colors from the 256-color table, would be mapped to completely nonsensical values. This API has now been updated to use the more correct `Settings::GenerateLegacyAttributes` method. But there were also a couple of other places in the code that were using `GetLegacyAttributes` when they really had no reason to be working with legacy attributes at all. This could result in colors being downgraded to 4-bit values (often badly, as explained above), when the code was already perfectly capable of displaying the full 24-bits. This included the fill colors in the IME composer (in `ConsoleImeInfo`), and the construction of the highlighting colors in the color search/selection handler (`Selection::_HandleColorSelection`). I also got rid of some legacy attribute code in the `Popup` class, which was originally intended to update colors below the popup when the settings changed, but actually caused more problems than it solved. The other major goal of this PR was to improve the performance of the `GenerateLegacyAttributes` method, since the existing implementation could be quite slow when dealing with RGB values. The simple cases are handled much the same as they were before. For an `IsDefault` color, we get the default index from the `Settings::_wFillAttribute` field. For an `IsIndex16` color, the index can just be returned as is. For an `IsRgb` color, the RGB components are compressed down to 8 bits (3 red, 3 green, 2 blue), simply by dropping the least significant bits. This 8-bit value is then used to lookup a representative 16-color value from a hard-coded table. An `IsIndex256` color is also converted with a lookup table, just using the existing 8-bit index. The RGB mapping table was calculated by taking each compressed 8-bit color, and picking a entry from the _Campbell_ palette that best approximated that color. This was done by looking at a range of 24-bit colors that mapped to the 8-bit value, finding the best _Campbell_ match for each of them (using a [CIEDE2000] color difference calculation), and then the most common match became the index that the 8-bit value would map to. The 256-color table was just a simpler version of this process. For each entry in the table, we take the default RGB palette value, and find it's closest match in the _Campbell_ palette. Because these tables are hard-coded, the results won't adjust to changes in the palette. However, they should still produce reasonable results for palettes that follow the standard ANSI color range. And since they're only a very loose approximation of the colors anyway, the exact value really isn't that important. That said, I have tried to make sure that if you take an RGB value for a particular index in a reasonable color scheme, then the legacy color mapped from that value should ideally match the same index. This will never be possible for all color schemes, but I have tweaked a few of the table entries to improve the results for some of the common schemes. One other point worth making regarding the hard-coded tables: even if we wanted to take the active palette into account, that wouldn't actually be possible over a conpty connection, because we can't easily know what color scheme the client application is using. At least this way the results in conhost are guaranteed to be the same as in the Windows Terminal. [CIEDE2000]: https://en.wikipedia.org/wiki/Color_difference#CIEDE2000 ## Validation Steps Performed This code still passes the `TextAttributeTests` that check the basic `GetLegacyAttribute` behaviour and verify the all legacy attributes roundtrip correctly. However, some of the values in the `RgbColorTests` had to be updated, since we're now intentionally returning different values as a result of the changes to the RGB conversion algorithm. I haven't added additional unit tests, but I have done a lot of manual testing to see how well the new algorithm works with a range of colors and a variety of different color schemes. It's not perfect in every situation, but I think it works well enough for the purpose it serves. I've also confirmed that the issues reported in #5940 and #6247 are now fixed by these changes. Closes #5940 Closes #6247
2020-06-08 21:05:06 +02:00
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();
Consolidate the color palette APIs (#11784) This PR merges the default colors and cursor color into the main color table, enabling us to simplify the `ConGetSet` and `ITerminalApi` interfaces, with just two methods required for getting and setting any form of color palette entry. The is a follow-up to the color table standardization in #11602, and a another small step towards de-duplicating `AdaptDispatch` and `TerminalDispatch` for issue #3849. It should also make it easier to support color queries (#3718) and a configurable bold color (#5682) in the future. On the conhost side, default colors could originally be either indexed positions in the 16-color table, or separate standalone RGB values. With the new system, the default colors will always be in the color table, so we just need to track their index positions. To make this work, those positions need to be calculated at startup based on the loaded registry/shortcut settings, and updated when settings are changed (this is handled in `CalculateDefaultColorIndices`). But the plus side is that it's now much easier to lookup the default color values for rendering. For now the default colors in Windows Terminal use hardcoded positions, because it doesn't need indexed default colors like conhost. But in the future I'd like to extend the index handling to both terminals, so we can eventually support the VT525 indexed color operations. As for the cursor color, that was previously stored in the `Cursor` class, which meant that it needed to be copied around in various places where cursors were being instantiated. Now that it's managed separately in the color table, a lot of that code is no longer required. ## Validation Some of the unit test initialization code needed to be updated to setup the color table and default index values as required for the new system. There were also some adjustments needed to account for API changes, in particular for methods that now take index values for the default colors in place of COLORREFs. But for the most part, the essential behavior of the tests remains unchanged. I've also run a variety of manual tests looking at the legacy console APIs as well as the various VT color sequences, and checking that everything works as expected when color schemes are changed, both in Windows Terminal and conhost, and in the latter case with both indexed colors and RGB values. Closes #11768
2021-11-23 19:28:55 +01:00
altCursor.SetStyle(myCursor.GetSize(), 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();
Consolidate the color palette APIs (#11784) This PR merges the default colors and cursor color into the main color table, enabling us to simplify the `ConGetSet` and `ITerminalApi` interfaces, with just two methods required for getting and setting any form of color palette entry. The is a follow-up to the color table standardization in #11602, and a another small step towards de-duplicating `AdaptDispatch` and `TerminalDispatch` for issue #3849. It should also make it easier to support color queries (#3718) and a configurable bold color (#5682) in the future. On the conhost side, default colors could originally be either indexed positions in the 16-color table, or separate standalone RGB values. With the new system, the default colors will always be in the color table, so we just need to track their index positions. To make this work, those positions need to be calculated at startup based on the loaded registry/shortcut settings, and updated when settings are changed (this is handled in `CalculateDefaultColorIndices`). But the plus side is that it's now much easier to lookup the default color values for rendering. For now the default colors in Windows Terminal use hardcoded positions, because it doesn't need indexed default colors like conhost. But in the future I'd like to extend the index handling to both terminals, so we can eventually support the VT525 indexed color operations. As for the cursor color, that was previously stored in the `Cursor` class, which meant that it needed to be copied around in various places where cursors were being instantiated. Now that it's managed separately in the color table, a lot of that code is no longer required. ## Validation Some of the unit test initialization code needed to be updated to setup the color table and default index values as required for the new system. There were also some adjustments needed to account for API changes, in particular for methods that now take index values for the default colors in place of COLORREFs. But for the most part, the essential behavior of the tests remains unchanged. I've also run a variety of manual tests looking at the legacy console APIs as well as the various VT color sequences, and checking that everything works as expected when color schemes are changed, both in Windows Terminal and conhost, and in the latter case with both indexed colors and RGB values. Closes #11768
2021-11-23 19:28:55 +01:00
mainCursor.SetStyle(altCursor.GetSize(), 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();
}
Remove unneeded VT-specific control character handling (#4289) ## Summary of the Pull Request This PR removes all of the VT-specific functionality from the `WriteCharsLegacy` function that dealt with control characters, since those controls are now handled in the state machine when in VT mode. It also removes most of the control character handling from the `Terminal::_WriteBuffer` method for the same reason. ## References This is a followup to PR #4171 ## PR Checklist * [x] Closes #3971 * [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA * [ ] Tests added/passed * [ ] Requires documentation to be updated * [x] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: https://github.com/microsoft/terminal/issues/780#issuecomment-570287435 ## Detailed Description of the Pull Request / Additional comments There are four changes to the `WriteCharsLegacy` implementation: 1. The `TAB` character had special case handling in VT mode which is now no longer required. This fixes a bug in the Python REPL editor (when run from a cmd shell in Windows Terminal), which would prevent you tabbing past the end of the line. It also fixes #3971. 2. Following on from point 1, the `WC_NONDESTRUCTIVE_TAB` flag could also now be removed. It only ever applied in VT mode, in which case the `TAB` character isn't handled in `WriteCharsLegacy`, so there isn't a need for a non-destructive version. 3. There used to be special case handling for a `BS` character at the beginning of the line when in VT mode, and that is also no longer required. This fixes an edge-case bug which would prevent a glyph being output for code point 8 when `ENABLE_PROCESSED_OUTPUT` was disabled. 4. There was quite a lot of special case handling for control characters in the "end-of-line wrap" implementation, which is no longer required. This fixes a bug which would prevent "low ASCII" characters from wrapping when output at the end of a line. Then in the `Terminal::_WriteBuffer` implementation, I've simply removed all control character handling, except for `LF`. The Terminal is always in VT mode, so the control characters are always handled by the state machine. The exception for the `LF` character is simply because it doesn't have a proper implementation yet, so it still passes the character through to `_WriteBuffer`. That will get cleaned up eventually, but I thought that could wait for a later PR. Finally, with the removal of the VT mode handling in `WriteCharsLegacy`, there was no longer a need for the `SCREEN_INFORMATION::InVTMode` method to be publicly accessible. That has now been made private. ## Validation Steps Performed I've only tested manually, making sure the conhost and Windows Terminal still basically work, and confirming that the above-mentioned bugs are fixed by these changes.
2020-01-29 20:18:46 +01:00
// 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
Improve the legacy color conversions (#6358) This PR provides a faster algorithm for converting 8-bit and 24-bit colors into the 4-bit legacy values that are required by the Win32 console APIs. It also fixes areas of the code that were incorrectly using a simple 16-color conversion that didn't handle 8-bit and 24-bit values. The faster conversion algorithm should be an improvement for issues #783 and #3950. One of the main points of this PR was to fix the `ReadConsoleOutputAttribute` API, which was using a simplified legacy color conversion (the original `TextAttribute:GetLegacyAttributes` method), which could only handle values from the 16-color table. RGB values, and colors from the 256-color table, would be mapped to completely nonsensical values. This API has now been updated to use the more correct `Settings::GenerateLegacyAttributes` method. But there were also a couple of other places in the code that were using `GetLegacyAttributes` when they really had no reason to be working with legacy attributes at all. This could result in colors being downgraded to 4-bit values (often badly, as explained above), when the code was already perfectly capable of displaying the full 24-bits. This included the fill colors in the IME composer (in `ConsoleImeInfo`), and the construction of the highlighting colors in the color search/selection handler (`Selection::_HandleColorSelection`). I also got rid of some legacy attribute code in the `Popup` class, which was originally intended to update colors below the popup when the settings changed, but actually caused more problems than it solved. The other major goal of this PR was to improve the performance of the `GenerateLegacyAttributes` method, since the existing implementation could be quite slow when dealing with RGB values. The simple cases are handled much the same as they were before. For an `IsDefault` color, we get the default index from the `Settings::_wFillAttribute` field. For an `IsIndex16` color, the index can just be returned as is. For an `IsRgb` color, the RGB components are compressed down to 8 bits (3 red, 3 green, 2 blue), simply by dropping the least significant bits. This 8-bit value is then used to lookup a representative 16-color value from a hard-coded table. An `IsIndex256` color is also converted with a lookup table, just using the existing 8-bit index. The RGB mapping table was calculated by taking each compressed 8-bit color, and picking a entry from the _Campbell_ palette that best approximated that color. This was done by looking at a range of 24-bit colors that mapped to the 8-bit value, finding the best _Campbell_ match for each of them (using a [CIEDE2000] color difference calculation), and then the most common match became the index that the 8-bit value would map to. The 256-color table was just a simpler version of this process. For each entry in the table, we take the default RGB palette value, and find it's closest match in the _Campbell_ palette. Because these tables are hard-coded, the results won't adjust to changes in the palette. However, they should still produce reasonable results for palettes that follow the standard ANSI color range. And since they're only a very loose approximation of the colors anyway, the exact value really isn't that important. That said, I have tried to make sure that if you take an RGB value for a particular index in a reasonable color scheme, then the legacy color mapped from that value should ideally match the same index. This will never be possible for all color schemes, but I have tweaked a few of the table entries to improve the results for some of the common schemes. One other point worth making regarding the hard-coded tables: even if we wanted to take the active palette into account, that wouldn't actually be possible over a conpty connection, because we can't easily know what color scheme the client application is using. At least this way the results in conhost are guaranteed to be the same as in the Windows Terminal. [CIEDE2000]: https://en.wikipedia.org/wiki/Color_difference#CIEDE2000 ## Validation Steps Performed This code still passes the `TextAttributeTests` that check the basic `GetLegacyAttribute` behaviour and verify the all legacy attributes roundtrip correctly. However, some of the values in the `RgbColorTests` had to be updated, since we're now intentionally returning different values as a result of the changes to the RGB conversion algorithm. I haven't added additional unit tests, but I have done a lot of manual testing to see how well the new algorithm works with a range of colors and a variety of different color schemes. It's not perfect in every situation, but I think it works well enough for the purpose it serves. I've also confirmed that the issues reported in #5940 and #6247 are now fixed by these changes. Closes #5940 Closes #6247
2020-06-08 21:05:06 +02:00
TextAttribute SCREEN_INFORMATION::GetPopupAttributes() const
{
Improve the legacy color conversions (#6358) This PR provides a faster algorithm for converting 8-bit and 24-bit colors into the 4-bit legacy values that are required by the Win32 console APIs. It also fixes areas of the code that were incorrectly using a simple 16-color conversion that didn't handle 8-bit and 24-bit values. The faster conversion algorithm should be an improvement for issues #783 and #3950. One of the main points of this PR was to fix the `ReadConsoleOutputAttribute` API, which was using a simplified legacy color conversion (the original `TextAttribute:GetLegacyAttributes` method), which could only handle values from the 16-color table. RGB values, and colors from the 256-color table, would be mapped to completely nonsensical values. This API has now been updated to use the more correct `Settings::GenerateLegacyAttributes` method. But there were also a couple of other places in the code that were using `GetLegacyAttributes` when they really had no reason to be working with legacy attributes at all. This could result in colors being downgraded to 4-bit values (often badly, as explained above), when the code was already perfectly capable of displaying the full 24-bits. This included the fill colors in the IME composer (in `ConsoleImeInfo`), and the construction of the highlighting colors in the color search/selection handler (`Selection::_HandleColorSelection`). I also got rid of some legacy attribute code in the `Popup` class, which was originally intended to update colors below the popup when the settings changed, but actually caused more problems than it solved. The other major goal of this PR was to improve the performance of the `GenerateLegacyAttributes` method, since the existing implementation could be quite slow when dealing with RGB values. The simple cases are handled much the same as they were before. For an `IsDefault` color, we get the default index from the `Settings::_wFillAttribute` field. For an `IsIndex16` color, the index can just be returned as is. For an `IsRgb` color, the RGB components are compressed down to 8 bits (3 red, 3 green, 2 blue), simply by dropping the least significant bits. This 8-bit value is then used to lookup a representative 16-color value from a hard-coded table. An `IsIndex256` color is also converted with a lookup table, just using the existing 8-bit index. The RGB mapping table was calculated by taking each compressed 8-bit color, and picking a entry from the _Campbell_ palette that best approximated that color. This was done by looking at a range of 24-bit colors that mapped to the 8-bit value, finding the best _Campbell_ match for each of them (using a [CIEDE2000] color difference calculation), and then the most common match became the index that the 8-bit value would map to. The 256-color table was just a simpler version of this process. For each entry in the table, we take the default RGB palette value, and find it's closest match in the _Campbell_ palette. Because these tables are hard-coded, the results won't adjust to changes in the palette. However, they should still produce reasonable results for palettes that follow the standard ANSI color range. And since they're only a very loose approximation of the colors anyway, the exact value really isn't that important. That said, I have tried to make sure that if you take an RGB value for a particular index in a reasonable color scheme, then the legacy color mapped from that value should ideally match the same index. This will never be possible for all color schemes, but I have tweaked a few of the table entries to improve the results for some of the common schemes. One other point worth making regarding the hard-coded tables: even if we wanted to take the active palette into account, that wouldn't actually be possible over a conpty connection, because we can't easily know what color scheme the client application is using. At least this way the results in conhost are guaranteed to be the same as in the Windows Terminal. [CIEDE2000]: https://en.wikipedia.org/wiki/Color_difference#CIEDE2000 ## Validation Steps Performed This code still passes the `TextAttributeTests` that check the basic `GetLegacyAttribute` behaviour and verify the all legacy attributes roundtrip correctly. However, some of the values in the `RgbColorTests` had to be updated, since we're now intentionally returning different values as a result of the changes to the RGB conversion algorithm. I haven't added additional unit tests, but I have done a lot of manual testing to see how well the new algorithm works with a range of colors and a variety of different color schemes. It's not perfect in every situation, but I think it works well enough for the purpose it serves. I've also confirmed that the issues reported in #5940 and #6247 are now fixed by these changes. Closes #5940 Closes #6247
2020-06-08 21:05:06 +02:00
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)
{
Reintroduce a color compatibility hack, but only for PowerShells (#6810) There is going to be a very long tail of applications that will explicitly request VT SGR 40/37 when what they really want is to SetConsoleTextAttribute() with a black background/white foreground. Instead of making those applications look bad (and therefore making us look bad, because we're releasing this as an update to something that "looks good" already), we're introducing this compatibility quirk. Before the color reckoning in #6698 + #6506, *every* color was subject to being spontaneously and erroneously turned into the default color. Now, only the 16-color palette value that matches the active console background/foreground color will be destroyed, and only when received from specific applications. Removal will be tracked by #6807. Michael and I discussed what layer this quirk really belonged in. I originally believed it would be sufficient to detect a background color that matched the legacy default background, but @j4james provided an example of where that wouldn't work out (powershell setting the foreground color to white/gray). In addition, it was too heavyhanded: it re-broke black backgrounds for every application. Michael thought that it should live in the server, as a small VT parser that righted the wrongs coming directly out of the application. On further investigation, however, I realized that we'd need to push more information up into the server (so that it could make the decision about which VT was wrong and which was right) than should be strictly necessary. The host knows which colors are right and wrong, and it gets final say in what ends up in the buffer. Because of that, I chose to push the quirk state down through WriteConsole to DoWriteConsole and toggle state on the SCREEN_INFORMATION that indicates whether the colors coming out of the application are to be distrusted. This quirk _only applies to pwsh.exe and powershell.exe._ NOTE: This doesn't work for PowerShell the .NET Global tool, because it is run as an assembly through dotnet.exe. I have no opinion on how to fix this, or whether it is worth fixing. VALIDATION ---------- I configured my terminals to have an incredibly garish color scheme to show exactly what's going to happen as a result of this. The _default terminal background_ is purple or red, and the foreground green. I've printed out a heap of test colors to see how black interacts with them. Pull request #6810 contains the images generated from this test. The only color lines that change are the ones where black as a background or white as a foreground is selected out of the 16-color palette explicitly. Reverse video still works fine (because black is in the foreground!), and it's even possible to represent "black on default" and reverse it into "default on black", despite the black in question having been `40`. Fixes #6767.
2020-07-11 00:25:39 +02:00
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();
Improve the legacy color conversions (#6358) This PR provides a faster algorithm for converting 8-bit and 24-bit colors into the 4-bit legacy values that are required by the Win32 console APIs. It also fixes areas of the code that were incorrectly using a simple 16-color conversion that didn't handle 8-bit and 24-bit values. The faster conversion algorithm should be an improvement for issues #783 and #3950. One of the main points of this PR was to fix the `ReadConsoleOutputAttribute` API, which was using a simplified legacy color conversion (the original `TextAttribute:GetLegacyAttributes` method), which could only handle values from the 16-color table. RGB values, and colors from the 256-color table, would be mapped to completely nonsensical values. This API has now been updated to use the more correct `Settings::GenerateLegacyAttributes` method. But there were also a couple of other places in the code that were using `GetLegacyAttributes` when they really had no reason to be working with legacy attributes at all. This could result in colors being downgraded to 4-bit values (often badly, as explained above), when the code was already perfectly capable of displaying the full 24-bits. This included the fill colors in the IME composer (in `ConsoleImeInfo`), and the construction of the highlighting colors in the color search/selection handler (`Selection::_HandleColorSelection`). I also got rid of some legacy attribute code in the `Popup` class, which was originally intended to update colors below the popup when the settings changed, but actually caused more problems than it solved. The other major goal of this PR was to improve the performance of the `GenerateLegacyAttributes` method, since the existing implementation could be quite slow when dealing with RGB values. The simple cases are handled much the same as they were before. For an `IsDefault` color, we get the default index from the `Settings::_wFillAttribute` field. For an `IsIndex16` color, the index can just be returned as is. For an `IsRgb` color, the RGB components are compressed down to 8 bits (3 red, 3 green, 2 blue), simply by dropping the least significant bits. This 8-bit value is then used to lookup a representative 16-color value from a hard-coded table. An `IsIndex256` color is also converted with a lookup table, just using the existing 8-bit index. The RGB mapping table was calculated by taking each compressed 8-bit color, and picking a entry from the _Campbell_ palette that best approximated that color. This was done by looking at a range of 24-bit colors that mapped to the 8-bit value, finding the best _Campbell_ match for each of them (using a [CIEDE2000] color difference calculation), and then the most common match became the index that the 8-bit value would map to. The 256-color table was just a simpler version of this process. For each entry in the table, we take the default RGB palette value, and find it's closest match in the _Campbell_ palette. Because these tables are hard-coded, the results won't adjust to changes in the palette. However, they should still produce reasonable results for palettes that follow the standard ANSI color range. And since they're only a very loose approximation of the colors anyway, the exact value really isn't that important. That said, I have tried to make sure that if you take an RGB value for a particular index in a reasonable color scheme, then the legacy color mapped from that value should ideally match the same index. This will never be possible for all color schemes, but I have tweaked a few of the table entries to improve the results for some of the common schemes. One other point worth making regarding the hard-coded tables: even if we wanted to take the active palette into account, that wouldn't actually be possible over a conpty connection, because we can't easily know what color scheme the client application is using. At least this way the results in conhost are guaranteed to be the same as in the Windows Terminal. [CIEDE2000]: https://en.wikipedia.org/wiki/Color_difference#CIEDE2000 ## Validation Steps Performed This code still passes the `TextAttributeTests` that check the basic `GetLegacyAttribute` behaviour and verify the all legacy attributes roundtrip correctly. However, some of the values in the `RgbColorTests` had to be updated, since we're now intentionally returning different values as a result of the changes to the RGB conversion algorithm. I haven't added additional unit tests, but I have done a lot of manual testing to see how well the new algorithm works with a range of colors and a variety of different color schemes. It's not perfect in every situation, but I think it works well enough for the purpose it serves. I've also confirmed that the issues reported in #5940 and #6247 are now fixed by these changes. Closes #5940 Closes #6247
2020-06-08 21:05:06 +02:00
const TextAttribute oldPopupAttributes = GetPopupAttributes();
// Quick return if we don't need to do anything.
if (oldPrimaryAttributes == attributes && oldPopupAttributes == popupAttributes)
{
return;
}
SetAttributes(attributes);
SetPopupAttributes(popupAttributes);
Add support for "reflow"ing the Terminal buffer (#4741) This PR adds support for "Resize with Reflow" to the Terminal. In conhost, `ResizeWithReflow` is the function that's responsible for reflowing wrapped lines of text as the buffer gets resized. Now that #4415 has merged, we can also implement this in the Terminal. Now, when the Terminal is resized, it will reflow the lines of it's buffer in the same way that conhost does. This means, the terminal will no longer chop off the ends of lines as the buffer is too small to represent them. As a happy side effect of this PR, it also fixed #3490. This was a bug that plagued me during the investigation into this functionality. The original #3490 PR, #4354, tried to fix this bug with some heavy conpty changes. Turns out, that only made things worse, and far more complicated. When I really got to thinking about it, I realized "conhost can handle this right, why can't the Terminal?". Turns out, by adding resize with reflow, I was also able to fix this at the same time. Conhost does a little bit of math after reflowing to attempt to keep the viewport in the same relative place after a reflow. By re-using that logic in the Terminal, I was able to fix #3490. I also included that big ole test from #3490, because everyone likes adding 60 test cases in a PR. ## References * #4200 - this scenario * #405/#4415 - conpty emits wrapped lines, which was needed for this PR * #4403 - delayed EOL wrapping via conpty, which was also needed for this * #4354 - we don't speak of this PR anymore ## PR Checklist * [x] Closes #1465 * [x] Closes #3490 * [x] Closes #4771 * [x] Tests added/passed ## EDIT: Changes to this PR on 5 March 2020 I learned more since my original version of this PR. I wrote that in January, and despite my notes that say it was totally working, it _really_ wasn't. Part of the hard problem, as mentioned in #3490, is that the Terminal might request a resize to (W, H-1), and while conpty is preparing that frame, or before the terminal has received that frame, the Terminal resizes to (W, H-2). Now, there aren't enough lines in the terminal buffer to catch all the lines that conpty is about to emit. When that happens, lines get duplicated in the buffer. From a UX perspective, this certainly looks a lot worse than a couple lost lines. It looks like utter chaos. So I've introduced a new mode to conpty to try and counteract this behavior. This behavior I'm calling "quirky resize". The **TL;DR** of quirky resize mode is that conpty won't emit the entire buffer on a resize, and will trust that the terminal is prepared to reflow it's buffer on it's own. This will enable the quirky resize behavior for applications that are prepared for it. The "quirky resize" is "don't `InvalidateAll` when the terminal resizes". This is added as a quirk as to not regress other terminal applications that aren't prepared for this behavior (gnome-terminal, conhost in particular). For those kinds of terminals, when the buffer is resized, it's just going to lose lines. That's what currently happens for them. When the quirk is enabled, conpty won't repaint the entire buffer. This gets around the "duplicated lines" issue that requesting multiple resizes in a row can cause. However, for these terminals that are unprepared, the conpty cursor might end up in the wrong position after a quirky resize. The case in point is maximizing the terminal. For maximizing (height->50) from a buffer that's 30 lines tall, with the cursor on y=30, this is what happens: * With the quirk disabled, conpty reprints the entire buffer. This is 60 lines that get printed. This ends up blowing away about 20 lines of scrollback history, as the terminal app would have tried to keep the text pinned to the bottom of the window. The term. app moved the viewport up 20 lines, and then the 50 lines of conpty output (30 lines of text, and 20 blank lines at the bottom) overwrote the lines from the scrollback. This is bad, but not immediately obvious, and is **what currently happens**. * With the quirk enabled, conpty doesn't emit any lines, but the actual content of the window is still only in the top 30 lines. However, the terminal app has still moved 20 lines down from the scrollback back into the viewport. So the terminal's cursor is at y=50 now, but conpty's is at 30. This means that the terminal and conpty are out of sync, and there's not a good way of re-syncing these. It's very possible (trivial in `powershell`) that the new output will jump up to y=30 override the existing output in the terminal buffer. The Windows Terminal is already prepared for this quirky behavior, so it doesn't keep the output at the bottom of the window. It shifts it's viewport down to match what conpty things the buffer looks like. What happens when we have passthrough mode and WT is like "I would like quirky resize"? I guess things will just work fine, cause there won't be a buffer behind the passthrough app that the terminal cares about. Sure, in the passthrough case the Terminal could _not_ quirky resize, but the quirky resize won't be wrong.
2020-03-13 01:43:37 +01:00
// 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.
Fix out-of-bounds exceptions in Set...{Buffer,Screen}Size (#8309) This fixes a number of exceptions that can cause conhost to crash when the buffer is resized in such a way that the viewport or cursor position end up out of bounds. Technically this is a fix for issue #256, although that has been closed as "needs-repro". The main fix was to add checks in the `SetConsoleScreenBufferSizeImpl` and `SetConsoleScreenBufferInfoExImpl` methods, to make sure the viewport doesn't extend past the bottom or right of the buffer after a resize. If it has overflowed, we move the viewport back up or left until it's back within the buffer boundaries. We also check if the cursor position has ended up out of bounds, and if so, clamp it back inside the buffer. The `SCREEN_INFORMATION::SetViewport` was also a source of viewport overflow problems, because it was mistakenly using inclusive coordinates in its range checks, which resulted in them being off by one. That has now been corrected to use exclusive coordinates. Finally, the `IsCursorDoubleWidth` method was incorrectly marked as `noexcept`, which was preventing its exceptions from being caught. Ideally it shouldn't be throwing exceptions at all any more, but I've removed the `noexcept` specifier, so if it does throw an exception, it'll at least have more chance of recovering without a crash. ## Validation Steps Performed I put together a few test cases (based on the reports in issues #276 and #1976) which consistently caused conhost to crash, or to generate an exception visible in the debug output. With this PR applied, those test cases are no longer crashing or triggering exceptions. Closes #1976
2020-11-17 22:17:51 +01:00
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();
Fix out-of-bounds exceptions in Set...{Buffer,Screen}Size (#8309) This fixes a number of exceptions that can cause conhost to crash when the buffer is resized in such a way that the viewport or cursor position end up out of bounds. Technically this is a fix for issue #256, although that has been closed as "needs-repro". The main fix was to add checks in the `SetConsoleScreenBufferSizeImpl` and `SetConsoleScreenBufferInfoExImpl` methods, to make sure the viewport doesn't extend past the bottom or right of the buffer after a resize. If it has overflowed, we move the viewport back up or left until it's back within the buffer boundaries. We also check if the cursor position has ended up out of bounds, and if so, clamp it back inside the buffer. The `SCREEN_INFORMATION::SetViewport` was also a source of viewport overflow problems, because it was mistakenly using inclusive coordinates in its range checks, which resulted in them being off by one. That has now been corrected to use exclusive coordinates. Finally, the `IsCursorDoubleWidth` method was incorrectly marked as `noexcept`, which was preventing its exceptions from being caught. Ideally it shouldn't be throwing exceptions at all any more, but I've removed the `noexcept` specifier, so if it does throw an exception, it'll at least have more chance of recovering without a crash. ## Validation Steps Performed I put together a few test cases (based on the reports in issues #276 and #1976) which consistently caused conhost to crash, or to generate an exception visible in the debug output. With this PR applied, those test cases are no longer crashing or triggering exceptions. Closes #1976
2020-11-17 22:17:51 +01:00
if (srCorrected.Right > coordScreenBufferSize.X)
{
srCorrected.Right = coordScreenBufferSize.X;
}
Fix out-of-bounds exceptions in Set...{Buffer,Screen}Size (#8309) This fixes a number of exceptions that can cause conhost to crash when the buffer is resized in such a way that the viewport or cursor position end up out of bounds. Technically this is a fix for issue #256, although that has been closed as "needs-repro". The main fix was to add checks in the `SetConsoleScreenBufferSizeImpl` and `SetConsoleScreenBufferInfoExImpl` methods, to make sure the viewport doesn't extend past the bottom or right of the buffer after a resize. If it has overflowed, we move the viewport back up or left until it's back within the buffer boundaries. We also check if the cursor position has ended up out of bounds, and if so, clamp it back inside the buffer. The `SCREEN_INFORMATION::SetViewport` was also a source of viewport overflow problems, because it was mistakenly using inclusive coordinates in its range checks, which resulted in them being off by one. That has now been corrected to use exclusive coordinates. Finally, the `IsCursorDoubleWidth` method was incorrectly marked as `noexcept`, which was preventing its exceptions from being caught. Ideally it shouldn't be throwing exceptions at all any more, but I've removed the `noexcept` specifier, so if it does throw an exception, it'll at least have more chance of recovering without a crash. ## Validation Steps Performed I put together a few test cases (based on the reports in issues #276 and #1976) which consistently caused conhost to crash, or to generate an exception visible in the debug output. With this PR applied, those test cases are no longer crashing or triggering exceptions. Closes #1976
2020-11-17 22:17:51 +01:00
if (srCorrected.Bottom > coordScreenBufferSize.Y)
{
srCorrected.Bottom = coordScreenBufferSize.Y;
}
Fix out-of-bounds exceptions in Set...{Buffer,Screen}Size (#8309) This fixes a number of exceptions that can cause conhost to crash when the buffer is resized in such a way that the viewport or cursor position end up out of bounds. Technically this is a fix for issue #256, although that has been closed as "needs-repro". The main fix was to add checks in the `SetConsoleScreenBufferSizeImpl` and `SetConsoleScreenBufferInfoExImpl` methods, to make sure the viewport doesn't extend past the bottom or right of the buffer after a resize. If it has overflowed, we move the viewport back up or left until it's back within the buffer boundaries. We also check if the cursor position has ended up out of bounds, and if so, clamp it back inside the buffer. The `SCREEN_INFORMATION::SetViewport` was also a source of viewport overflow problems, because it was mistakenly using inclusive coordinates in its range checks, which resulted in them being off by one. That has now been corrected to use exclusive coordinates. Finally, the `IsCursorDoubleWidth` method was incorrectly marked as `noexcept`, which was preventing its exceptions from being caught. Ideally it shouldn't be throwing exceptions at all any more, but I've removed the `noexcept` specifier, so if it does throw an exception, it'll at least have more chance of recovering without a crash. ## Validation Steps Performed I put together a few test cases (based on the reports in issues #276 and #1976) which consistently caused conhost to crash, or to generate an exception visible in the debug output. With this PR applied, those test cases are no longer crashing or triggering exceptions. Closes #1976
2020-11-17 22:17:51 +01:00
_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));
Correct fill attributes when scrolling and erasing (#3100) ## Summary of the Pull Request Operations that erase areas of the screen are typically meant to do so using the current color attributes, but with the rendition attributes reset (what we refer to as meta attributes). This also includes scroll operations that have to clear the area of the screen that has scrolled into view. The only exception is the _Erase Scrollback_ operation, which needs to reset the buffer with the default attributes. This PR updates all of these cases to apply the correct attributes when scrolling and erasing. ## PR Checklist * [x] Closes #2553 * [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA * [x] Tests added/passed * [ ] Requires documentation to be updated * [ ] I've not really discussed this with core contributors. I'm ready to accept this work might be rejected in favor of a different grand plan. ## Detailed Description of the Pull Request / Additional comments My initial plan was to use a special case legacy attribute value to indicate the "standard erase attribute" which could safely be passed through the legacy APIs. But this wouldn't cover the cases that required default attributes to be used. And then with the changes in PR #2668 and #2987, it became clear that our requirements could be better achieved with a couple of new private APIs that wouldn't have to depend on legacy attribute hacks at all. To that end, I've added the `PrivateFillRegion` and `PrivateScrollRegion` APIs to the `ConGetSet` interface. These are just thin wrappers around the existing `SCREEN_INFORMATION::Write` method and the `ScrollRegion` function respectively, but with a simple boolean parameter to choose between filling with default attributes or the standard erase attributes (i.e the current colors but with meta attributes reset). With those new APIs in place, I could then update most scroll operations to use `PrivateScrollRegion`, and most erase operations to use `PrivateFillRegion`. The functions affected by scrolling included: * `DoSrvPrivateReverseLineFeed` (the RI command) * `DoSrvPrivateModifyLinesImpl` (the IL and DL commands) * `AdaptDispatch::_InsertDeleteHelper` (the ICH and DCH commands) * `AdaptDispatch::_ScrollMovement` (the SU and SD commands) The functions affected by erasing included: * `AdaptDispatch::_EraseSingleLineHelper` (the EL command, and most ED variants) * `AdaptDispatch::EraseCharacters` (the ECH command) While updating these erase methods, I noticed that both of them also required boundary fixes similar to those in PR #2505 (i.e. the horizontal extent of the erase operation should apply to the full width of the buffer, and not just the current viewport width), so I've addressed that at the same time. In addition to the changes above, there were also a few special cases, the first being the line feed handling, which required updating in a number of places to use the correct erase attributes: * `SCREEN_INFORMATION::InitializeCursorRowAttributes` - this is used to initialise the rows that pan into view when the viewport is moved down the buffer. * `TextBuffer::IncrementCircularBuffer` - this occurs when we scroll passed the very end of the buffer, and a recycled row now needs to be reinitialised. * `AdjustCursorPosition` - when within margin boundaries, this relies on a couple of direct calls to `ScrollRegion` which needed to be passed the correct fill attributes. The second special case was the full screen erase sequence (`ESC 2 J`), which is handled separately from the other ED sequences. This required updating the `SCREEN_INFORMATION::VtEraseAll` method to use the standard erase attributes, and also required changes to the horizontal extent of the filled area, since it should have been clearing the full buffer width (the same issue as the other erase operations mentioned above). Finally, there was the `AdaptDispatch::_EraseScrollback` method, which uses both scroll and fill operations, which could now be handled by the new `PrivateScrollRegion` and `PrivateFillRegion` APIs. But in this case we needed to fill with the default attributes rather than the standard erase attributes. And again this implementation needed some changes to make sure the full width of the active area was retained after the erase, similar to the horizontal boundary issues with the other erase operations. Once all these changes were made, there were a few areas of the code that could then be simplified quite a bit. The `FillConsoleOutputCharacterW`, `FillConsoleOutputAttribute`, and `ScrollConsoleScreenBufferW` were no longer needed in the `ConGetSet` interface, so all of that code could now be removed. The `_EraseSingleLineDistanceHelper` and `_EraseAreaHelper` methods in the `AdaptDispatch` class were also no longer required and could be removed. Then there were the hacks to handle legacy default colors in the `FillConsoleOutputAttributeImpl` and `ScrollConsoleScreenBufferWImpl` implementations. Since those hacks were only needed for VT operations, and the VT code no longer calls those methods, there was no longer a need to retain that behaviour (in fact there are probably some edge cases where that behaviour might have been considered a bug when reached via the public console APIs). ## Validation Steps Performed For most of the scrolling operations there were already existing tests in place, and those could easily be extended to check that the meta attributes were correctly reset when filling the revealed lines of the scrolling region. In the screen buffer tests, I made updates of that sort to the `ScrollOperations` method (handling SU, SD, IL, DL, and RI), the `InsertChars` and `DeleteChars` methods (ICH and DCH), and the `VtNewlinePastViewport` method (LF). I also added a new `VtNewlinePastEndOfBuffer` test to check the case where the line feed causes the viewport to pan past the end of the buffer. The erase operations, however, were being covered by adapter tests, and those aren't really suited for this kind of functionality (the same sort of issue came up in PR #2505). As a result I've had to reimplement those tests as screen buffer tests. Most of the erase operations are covered by the `EraseTests` method, except the for the scrollback erase which has a dedicated `EraseScrollbackTests` method. I've also had to replace the `HardReset` adapter test, but that was already mostly covered by the `HardResetBuffer` screen buffer test, which I've now extended slightly (it could do with some more checks, but I think that can wait for a future PR when we're fixing other RIS issues).
2019-12-11 00:14:40 +01:00
// 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);
Add support for double-width/double-height lines in conhost (#8664) This PR adds support for the VT line rendition attributes, which allow for double-width and double-height line renditions. These renditions are enabled with the `DECDWL` (double-width line) and `DECDHL` (double-height line) escape sequences. Both reset to the default rendition with the `DECSWL` (single-width line) escape sequence. For now this functionality is only supported by the GDI renderer in conhost. There are a lot of changes, so this is just a general overview of the main areas affected. Previously it was safe to assume that the screen had a fixed width, at least for a given point in time. But now we need to deal with the possibility of different lines have different widths, so all the functions that are constrained by the right border (text wrapping, cursor movement operations, and sequences like `EL` and `ICH`) now need to lookup the width of the active line in order to behave correctly. Similarly it used to be safe to assume that buffer and screen coordinates were the same thing, but that is no longer true. Lots of places now need to translate back and forth between coordinate systems dependent on the line rendition. This includes clipboard handling, the conhost color selection and search, accessibility location tracking and screen reading, IME editor positioning, "snapping" the viewport, and of course all the rendering calculations. For the rendering itself, I've had to introduce a new `PrepareLineTransform` method that the render engines can use to setup the necessary transform matrix for a given line rendition. This is also now used to handle the horizontal viewport offset, since that could no longer be achieved just by changing the target coordinates (on a double width line, the viewport offset may be halfway through a character). I've also had to change the renderer's existing `InvalidateCursor` method to take a `SMALL_RECT` rather than a `COORD`, to allow for the cursor being a variable width. Technically this was already a problem, because the cursor could occupy two screen cells when over a double-width character, but now it can be anything between one and four screen cells (e.g. a double-width character on the double-width line). In terms of architectural changes, there is now a new `lineRendition` field in the `ROW` class that keeps track of the line rendition for each row, and several new methods in the `ROW` and `TextBuffer` classes for manipulating that state. This includes a few helper methods for handling the various issues discussed above, e.g. position clamping and translating between coordinate systems. ## Validation Steps Performed I've manually confirmed all the double-width and double-height tests in _Vttest_ are now working as expected, and the _VT100 Torture Test_ now renders correctly (at least the line rendition aspects). I've also got my own test scripts that check many of the line rendition boundary cases and have confirmed that those are now passing. I've manually tested as many areas of the conhost UI that I could think of, that might be affected by line rendition, including things like searching, selection, copying, and color highlighting. For accessibility, I've confirmed that the _Magnifier_ and _Narrator_ correctly handle double-width lines. And I've also tested the Japanese IME, which while not perfect, is at least useable. Closes #7865
2021-02-18 06:44:50 +01:00
// 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
{
Add support for double-width/double-height lines in conhost (#8664) This PR adds support for the VT line rendition attributes, which allow for double-width and double-height line renditions. These renditions are enabled with the `DECDWL` (double-width line) and `DECDHL` (double-height line) escape sequences. Both reset to the default rendition with the `DECSWL` (single-width line) escape sequence. For now this functionality is only supported by the GDI renderer in conhost. There are a lot of changes, so this is just a general overview of the main areas affected. Previously it was safe to assume that the screen had a fixed width, at least for a given point in time. But now we need to deal with the possibility of different lines have different widths, so all the functions that are constrained by the right border (text wrapping, cursor movement operations, and sequences like `EL` and `ICH`) now need to lookup the width of the active line in order to behave correctly. Similarly it used to be safe to assume that buffer and screen coordinates were the same thing, but that is no longer true. Lots of places now need to translate back and forth between coordinate systems dependent on the line rendition. This includes clipboard handling, the conhost color selection and search, accessibility location tracking and screen reading, IME editor positioning, "snapping" the viewport, and of course all the rendering calculations. For the rendering itself, I've had to introduce a new `PrepareLineTransform` method that the render engines can use to setup the necessary transform matrix for a given line rendition. This is also now used to handle the horizontal viewport offset, since that could no longer be achieved just by changing the target coordinates (on a double width line, the viewport offset may be halfway through a character). I've also had to change the renderer's existing `InvalidateCursor` method to take a `SMALL_RECT` rather than a `COORD`, to allow for the cursor being a variable width. Technically this was already a problem, because the cursor could occupy two screen cells when over a double-width character, but now it can be anything between one and four screen cells (e.g. a double-width character on the double-width line). In terms of architectural changes, there is now a new `lineRendition` field in the `ROW` class that keeps track of the line rendition for each row, and several new methods in the `ROW` and `TextBuffer` classes for manipulating that state. This includes a few helper methods for handling the various issues discussed above, e.g. position clamping and translating between coordinate systems. ## Validation Steps Performed I've manually confirmed all the double-width and double-height tests in _Vttest_ are now working as expected, and the _VT100 Torture Test_ now renders correctly (at least the line rendition aspects). I've also got my own test scripts that check many of the line rendition boundary cases and have confirmed that those are now passing. I've manually tested as many areas of the conhost UI that I could think of, that might be affected by line rendition, including things like searching, selection, copying, and color highlighting. For accessibility, I've confirmed that the _Magnifier_ and _Narrator_ correctly handle double-width lines. And I've also tested the Japanese IME, which while not perfect, is at least useable. Closes #7865
2021-02-18 06:44:50 +01:00
// 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++;
}
}
}
}
Add support for double-width/double-height lines in conhost (#8664) This PR adds support for the VT line rendition attributes, which allow for double-width and double-height line renditions. These renditions are enabled with the `DECDWL` (double-width line) and `DECDHL` (double-height line) escape sequences. Both reset to the default rendition with the `DECSWL` (single-width line) escape sequence. For now this functionality is only supported by the GDI renderer in conhost. There are a lot of changes, so this is just a general overview of the main areas affected. Previously it was safe to assume that the screen had a fixed width, at least for a given point in time. But now we need to deal with the possibility of different lines have different widths, so all the functions that are constrained by the right border (text wrapping, cursor movement operations, and sequences like `EL` and `ICH`) now need to lookup the width of the active line in order to behave correctly. Similarly it used to be safe to assume that buffer and screen coordinates were the same thing, but that is no longer true. Lots of places now need to translate back and forth between coordinate systems dependent on the line rendition. This includes clipboard handling, the conhost color selection and search, accessibility location tracking and screen reading, IME editor positioning, "snapping" the viewport, and of course all the rendering calculations. For the rendering itself, I've had to introduce a new `PrepareLineTransform` method that the render engines can use to setup the necessary transform matrix for a given line rendition. This is also now used to handle the horizontal viewport offset, since that could no longer be achieved just by changing the target coordinates (on a double width line, the viewport offset may be halfway through a character). I've also had to change the renderer's existing `InvalidateCursor` method to take a `SMALL_RECT` rather than a `COORD`, to allow for the cursor being a variable width. Technically this was already a problem, because the cursor could occupy two screen cells when over a double-width character, but now it can be anything between one and four screen cells (e.g. a double-width character on the double-width line). In terms of architectural changes, there is now a new `lineRendition` field in the `ROW` class that keeps track of the line rendition for each row, and several new methods in the `ROW` and `TextBuffer` classes for manipulating that state. This includes a few helper methods for handling the various issues discussed above, e.g. position clamping and translating between coordinate systems. ## Validation Steps Performed I've manually confirmed all the double-width and double-height tests in _Vttest_ are now working as expected, and the _VT100 Torture Test_ now renders correctly (at least the line rendition aspects). I've also got my own test scripts that check many of the line rendition boundary cases and have confirmed that those are now passing. I've manually tested as many areas of the conhost UI that I could think of, that might be affected by line rendition, including things like searching, selection, copying, and color highlighting. For accessibility, I've confirmed that the _Magnifier_ and _Narrator_ correctly handle double-width lines. And I've also tested the Japanese IME, which while not perfect, is at least useable. Closes #7865
2021-02-18 06:44:50 +01:00
// 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:
Correct fill attributes when scrolling and erasing (#3100) ## Summary of the Pull Request Operations that erase areas of the screen are typically meant to do so using the current color attributes, but with the rendition attributes reset (what we refer to as meta attributes). This also includes scroll operations that have to clear the area of the screen that has scrolled into view. The only exception is the _Erase Scrollback_ operation, which needs to reset the buffer with the default attributes. This PR updates all of these cases to apply the correct attributes when scrolling and erasing. ## PR Checklist * [x] Closes #2553 * [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA * [x] Tests added/passed * [ ] Requires documentation to be updated * [ ] I've not really discussed this with core contributors. I'm ready to accept this work might be rejected in favor of a different grand plan. ## Detailed Description of the Pull Request / Additional comments My initial plan was to use a special case legacy attribute value to indicate the "standard erase attribute" which could safely be passed through the legacy APIs. But this wouldn't cover the cases that required default attributes to be used. And then with the changes in PR #2668 and #2987, it became clear that our requirements could be better achieved with a couple of new private APIs that wouldn't have to depend on legacy attribute hacks at all. To that end, I've added the `PrivateFillRegion` and `PrivateScrollRegion` APIs to the `ConGetSet` interface. These are just thin wrappers around the existing `SCREEN_INFORMATION::Write` method and the `ScrollRegion` function respectively, but with a simple boolean parameter to choose between filling with default attributes or the standard erase attributes (i.e the current colors but with meta attributes reset). With those new APIs in place, I could then update most scroll operations to use `PrivateScrollRegion`, and most erase operations to use `PrivateFillRegion`. The functions affected by scrolling included: * `DoSrvPrivateReverseLineFeed` (the RI command) * `DoSrvPrivateModifyLinesImpl` (the IL and DL commands) * `AdaptDispatch::_InsertDeleteHelper` (the ICH and DCH commands) * `AdaptDispatch::_ScrollMovement` (the SU and SD commands) The functions affected by erasing included: * `AdaptDispatch::_EraseSingleLineHelper` (the EL command, and most ED variants) * `AdaptDispatch::EraseCharacters` (the ECH command) While updating these erase methods, I noticed that both of them also required boundary fixes similar to those in PR #2505 (i.e. the horizontal extent of the erase operation should apply to the full width of the buffer, and not just the current viewport width), so I've addressed that at the same time. In addition to the changes above, there were also a few special cases, the first being the line feed handling, which required updating in a number of places to use the correct erase attributes: * `SCREEN_INFORMATION::InitializeCursorRowAttributes` - this is used to initialise the rows that pan into view when the viewport is moved down the buffer. * `TextBuffer::IncrementCircularBuffer` - this occurs when we scroll passed the very end of the buffer, and a recycled row now needs to be reinitialised. * `AdjustCursorPosition` - when within margin boundaries, this relies on a couple of direct calls to `ScrollRegion` which needed to be passed the correct fill attributes. The second special case was the full screen erase sequence (`ESC 2 J`), which is handled separately from the other ED sequences. This required updating the `SCREEN_INFORMATION::VtEraseAll` method to use the standard erase attributes, and also required changes to the horizontal extent of the filled area, since it should have been clearing the full buffer width (the same issue as the other erase operations mentioned above). Finally, there was the `AdaptDispatch::_EraseScrollback` method, which uses both scroll and fill operations, which could now be handled by the new `PrivateScrollRegion` and `PrivateFillRegion` APIs. But in this case we needed to fill with the default attributes rather than the standard erase attributes. And again this implementation needed some changes to make sure the full width of the active area was retained after the erase, similar to the horizontal boundary issues with the other erase operations. Once all these changes were made, there were a few areas of the code that could then be simplified quite a bit. The `FillConsoleOutputCharacterW`, `FillConsoleOutputAttribute`, and `ScrollConsoleScreenBufferW` were no longer needed in the `ConGetSet` interface, so all of that code could now be removed. The `_EraseSingleLineDistanceHelper` and `_EraseAreaHelper` methods in the `AdaptDispatch` class were also no longer required and could be removed. Then there were the hacks to handle legacy default colors in the `FillConsoleOutputAttributeImpl` and `ScrollConsoleScreenBufferWImpl` implementations. Since those hacks were only needed for VT operations, and the VT code no longer calls those methods, there was no longer a need to retain that behaviour (in fact there are probably some edge cases where that behaviour might have been considered a bug when reached via the public console APIs). ## Validation Steps Performed For most of the scrolling operations there were already existing tests in place, and those could easily be extended to check that the meta attributes were correctly reset when filling the revealed lines of the scrolling region. In the screen buffer tests, I made updates of that sort to the `ScrollOperations` method (handling SU, SD, IL, DL, and RI), the `InsertChars` and `DeleteChars` methods (ICH and DCH), and the `VtNewlinePastViewport` method (LF). I also added a new `VtNewlinePastEndOfBuffer` test to check the case where the line feed causes the viewport to pan past the end of the buffer. The erase operations, however, were being covered by adapter tests, and those aren't really suited for this kind of functionality (the same sort of issue came up in PR #2505). As a result I've had to reimplement those tests as screen buffer tests. Most of the erase operations are covered by the `EraseTests` method, except the for the scrollback erase which has a dedicated `EraseScrollbackTests` method. I've also had to replace the `HardReset` adapter test, but that was already mostly covered by the `HardResetBuffer` screen buffer test, which I've now extended slightly (it could do with some more checks, but I think that can wait for a future PR when we're fixing other RIS issues).
2019-12-11 00:14:40 +01:00
// - 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);
Correct fill attributes when scrolling and erasing (#3100) ## Summary of the Pull Request Operations that erase areas of the screen are typically meant to do so using the current color attributes, but with the rendition attributes reset (what we refer to as meta attributes). This also includes scroll operations that have to clear the area of the screen that has scrolled into view. The only exception is the _Erase Scrollback_ operation, which needs to reset the buffer with the default attributes. This PR updates all of these cases to apply the correct attributes when scrolling and erasing. ## PR Checklist * [x] Closes #2553 * [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA * [x] Tests added/passed * [ ] Requires documentation to be updated * [ ] I've not really discussed this with core contributors. I'm ready to accept this work might be rejected in favor of a different grand plan. ## Detailed Description of the Pull Request / Additional comments My initial plan was to use a special case legacy attribute value to indicate the "standard erase attribute" which could safely be passed through the legacy APIs. But this wouldn't cover the cases that required default attributes to be used. And then with the changes in PR #2668 and #2987, it became clear that our requirements could be better achieved with a couple of new private APIs that wouldn't have to depend on legacy attribute hacks at all. To that end, I've added the `PrivateFillRegion` and `PrivateScrollRegion` APIs to the `ConGetSet` interface. These are just thin wrappers around the existing `SCREEN_INFORMATION::Write` method and the `ScrollRegion` function respectively, but with a simple boolean parameter to choose between filling with default attributes or the standard erase attributes (i.e the current colors but with meta attributes reset). With those new APIs in place, I could then update most scroll operations to use `PrivateScrollRegion`, and most erase operations to use `PrivateFillRegion`. The functions affected by scrolling included: * `DoSrvPrivateReverseLineFeed` (the RI command) * `DoSrvPrivateModifyLinesImpl` (the IL and DL commands) * `AdaptDispatch::_InsertDeleteHelper` (the ICH and DCH commands) * `AdaptDispatch::_ScrollMovement` (the SU and SD commands) The functions affected by erasing included: * `AdaptDispatch::_EraseSingleLineHelper` (the EL command, and most ED variants) * `AdaptDispatch::EraseCharacters` (the ECH command) While updating these erase methods, I noticed that both of them also required boundary fixes similar to those in PR #2505 (i.e. the horizontal extent of the erase operation should apply to the full width of the buffer, and not just the current viewport width), so I've addressed that at the same time. In addition to the changes above, there were also a few special cases, the first being the line feed handling, which required updating in a number of places to use the correct erase attributes: * `SCREEN_INFORMATION::InitializeCursorRowAttributes` - this is used to initialise the rows that pan into view when the viewport is moved down the buffer. * `TextBuffer::IncrementCircularBuffer` - this occurs when we scroll passed the very end of the buffer, and a recycled row now needs to be reinitialised. * `AdjustCursorPosition` - when within margin boundaries, this relies on a couple of direct calls to `ScrollRegion` which needed to be passed the correct fill attributes. The second special case was the full screen erase sequence (`ESC 2 J`), which is handled separately from the other ED sequences. This required updating the `SCREEN_INFORMATION::VtEraseAll` method to use the standard erase attributes, and also required changes to the horizontal extent of the filled area, since it should have been clearing the full buffer width (the same issue as the other erase operations mentioned above). Finally, there was the `AdaptDispatch::_EraseScrollback` method, which uses both scroll and fill operations, which could now be handled by the new `PrivateScrollRegion` and `PrivateFillRegion` APIs. But in this case we needed to fill with the default attributes rather than the standard erase attributes. And again this implementation needed some changes to make sure the full width of the active area was retained after the erase, similar to the horizontal boundary issues with the other erase operations. Once all these changes were made, there were a few areas of the code that could then be simplified quite a bit. The `FillConsoleOutputCharacterW`, `FillConsoleOutputAttribute`, and `ScrollConsoleScreenBufferW` were no longer needed in the `ConGetSet` interface, so all of that code could now be removed. The `_EraseSingleLineDistanceHelper` and `_EraseAreaHelper` methods in the `AdaptDispatch` class were also no longer required and could be removed. Then there were the hacks to handle legacy default colors in the `FillConsoleOutputAttributeImpl` and `ScrollConsoleScreenBufferWImpl` implementations. Since those hacks were only needed for VT operations, and the VT code no longer calls those methods, there was no longer a need to retain that behaviour (in fact there are probably some edge cases where that behaviour might have been considered a bug when reached via the public console APIs). ## Validation Steps Performed For most of the scrolling operations there were already existing tests in place, and those could easily be extended to check that the meta attributes were correctly reset when filling the revealed lines of the scrolling region. In the screen buffer tests, I made updates of that sort to the `ScrollOperations` method (handling SU, SD, IL, DL, and RI), the `InsertChars` and `DeleteChars` methods (ICH and DCH), and the `VtNewlinePastViewport` method (LF). I also added a new `VtNewlinePastEndOfBuffer` test to check the case where the line feed causes the viewport to pan past the end of the buffer. The erase operations, however, were being covered by adapter tests, and those aren't really suited for this kind of functionality (the same sort of issue came up in PR #2505). As a result I've had to reimplement those tests as screen buffer tests. Most of the erase operations are covered by the `EraseTests` method, except the for the scrollback erase which has a dedicated `EraseScrollbackTests` method. I've also had to replace the `HardReset` adapter test, but that was already mostly covered by the `HardResetBuffer` screen buffer test, which I've now extended slightly (it could do with some more checks, but I think that can wait for a future PR when we're fixing other RIS issues).
2019-12-11 00:14:40 +01:00
// 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);
Add support for double-width/double-height lines in conhost (#8664) This PR adds support for the VT line rendition attributes, which allow for double-width and double-height line renditions. These renditions are enabled with the `DECDWL` (double-width line) and `DECDHL` (double-height line) escape sequences. Both reset to the default rendition with the `DECSWL` (single-width line) escape sequence. For now this functionality is only supported by the GDI renderer in conhost. There are a lot of changes, so this is just a general overview of the main areas affected. Previously it was safe to assume that the screen had a fixed width, at least for a given point in time. But now we need to deal with the possibility of different lines have different widths, so all the functions that are constrained by the right border (text wrapping, cursor movement operations, and sequences like `EL` and `ICH`) now need to lookup the width of the active line in order to behave correctly. Similarly it used to be safe to assume that buffer and screen coordinates were the same thing, but that is no longer true. Lots of places now need to translate back and forth between coordinate systems dependent on the line rendition. This includes clipboard handling, the conhost color selection and search, accessibility location tracking and screen reading, IME editor positioning, "snapping" the viewport, and of course all the rendering calculations. For the rendering itself, I've had to introduce a new `PrepareLineTransform` method that the render engines can use to setup the necessary transform matrix for a given line rendition. This is also now used to handle the horizontal viewport offset, since that could no longer be achieved just by changing the target coordinates (on a double width line, the viewport offset may be halfway through a character). I've also had to change the renderer's existing `InvalidateCursor` method to take a `SMALL_RECT` rather than a `COORD`, to allow for the cursor being a variable width. Technically this was already a problem, because the cursor could occupy two screen cells when over a double-width character, but now it can be anything between one and four screen cells (e.g. a double-width character on the double-width line). In terms of architectural changes, there is now a new `lineRendition` field in the `ROW` class that keeps track of the line rendition for each row, and several new methods in the `ROW` and `TextBuffer` classes for manipulating that state. This includes a few helper methods for handling the various issues discussed above, e.g. position clamping and translating between coordinate systems. ## Validation Steps Performed I've manually confirmed all the double-width and double-height tests in _Vttest_ are now working as expected, and the _VT100 Torture Test_ now renders correctly (at least the line rendition aspects). I've also got my own test scripts that check many of the line rendition boundary cases and have confirmed that those are now passing. I've manually tested as many areas of the conhost UI that I could think of, that might be affected by line rendition, including things like searching, selection, copying, and color highlighting. For accessibility, I've confirmed that the _Magnifier_ and _Narrator_ correctly handle double-width lines. And I've also tested the Japanese IME, which while not perfect, is at least useable. Closes #7865
2021-02-18 06:44:50 +01:00
// 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:
2019-05-21 08:15:44 +02:00
// - 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;
Retain horizontal viewport offset when moving to bottom (#8434) When the viewport is moved to the "virtual bottom" of the buffer (via the `MoveToBottom` method), it is important that the horizontal viewport offset be left as it is, otherwise that can result in some undesirable side effects. Since the VT coordinate system is relative to the top of the viewport, many VT operations call the `MoveToBottom` method to make sure the viewport is correctly positioned before proceeding. There is no need for the horizontal position to be adjusted, though, since the X coordinates are not constrained by the viewport, but are instead relative to the underlying buffer. Setting the viewport X coordinate to 0 in `MoveToBottom` (as we were previously doing) could result in the cursor being pushed off screen. And if the operation itself was moving the cursor, that would then trigger another viewport move to bring the cursor back into view. These conflicting movements meant the viewport was always forced as far left as possible, and could also result in cursor "droppings" as the cursor lost track of where it had been. I've now fixed this by updating the `GetVirtualViewport` method to match the horizontal offset of the active viewport, instead of having the X coordinate hardcoded to 0. ## Validation Steps Performed I've manually confirmed that this fixes the cursor "droppings" test case reported in issue #8213. I've also added a screen buffer test that makes sure the `MoveToBottom` method is working as expected, and not changing the horizontal viewport offset when it moves down. Closes #8213
2020-11-30 20:58:03 +01:00
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;
}
Reintroduce a color compatibility hack, but only for PowerShells (#6810) There is going to be a very long tail of applications that will explicitly request VT SGR 40/37 when what they really want is to SetConsoleTextAttribute() with a black background/white foreground. Instead of making those applications look bad (and therefore making us look bad, because we're releasing this as an update to something that "looks good" already), we're introducing this compatibility quirk. Before the color reckoning in #6698 + #6506, *every* color was subject to being spontaneously and erroneously turned into the default color. Now, only the 16-color palette value that matches the active console background/foreground color will be destroyed, and only when received from specific applications. Removal will be tracked by #6807. Michael and I discussed what layer this quirk really belonged in. I originally believed it would be sufficient to detect a background color that matched the legacy default background, but @j4james provided an example of where that wouldn't work out (powershell setting the foreground color to white/gray). In addition, it was too heavyhanded: it re-broke black backgrounds for every application. Michael thought that it should live in the server, as a small VT parser that righted the wrongs coming directly out of the application. On further investigation, however, I realized that we'd need to push more information up into the server (so that it could make the decision about which VT was wrong and which was right) than should be strictly necessary. The host knows which colors are right and wrong, and it gets final say in what ends up in the buffer. Because of that, I chose to push the quirk state down through WriteConsole to DoWriteConsole and toggle state on the SCREEN_INFORMATION that indicates whether the colors coming out of the application are to be distrusted. This quirk _only applies to pwsh.exe and powershell.exe._ NOTE: This doesn't work for PowerShell the .NET Global tool, because it is run as an assembly through dotnet.exe. I have no opinion on how to fix this, or whether it is worth fixing. VALIDATION ---------- I configured my terminals to have an incredibly garish color scheme to show exactly what's going to happen as a result of this. The _default terminal background_ is purple or red, and the foreground green. I've printed out a heap of test colors to see how black interacts with them. Pull request #6810 contains the images generated from this test. The only color lines that change are the ones where black as a background or white as a foreground is selected out of the 16-color palette explicitly. Reverse video still works fine (because black is in the foreground!), and it's even possible to represent "black on default" and reverse it into "default on black", despite the black in question having been `40`. Fixes #6767.
2020-07-11 00:25:39 +02:00
// 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;
}