516 lines
21 KiB
C++
516 lines
21 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "precomp.h"
|
|
|
|
#include "_output.h"
|
|
#include "output.h"
|
|
#include "handle.h"
|
|
|
|
#include "getset.h"
|
|
#include "misc.h"
|
|
|
|
#include "../interactivity/inc/ServiceLocator.hpp"
|
|
#include "../types/inc/Viewport.hpp"
|
|
#include "../types/inc/convert.hpp"
|
|
|
|
#pragma hdrstop
|
|
|
|
using namespace Microsoft::Console::Types;
|
|
using namespace Microsoft::Console::Interactivity;
|
|
|
|
// This routine figures out what parameters to pass to CreateScreenBuffer based on the data from STARTUPINFO and the
|
|
// registry defaults, and then calls CreateScreenBuffer.
|
|
[[nodiscard]] NTSTATUS DoCreateScreenBuffer()
|
|
{
|
|
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
|
|
FontInfo fiFont(gci.GetFaceName(),
|
|
gsl::narrow_cast<unsigned char>(gci.GetFontFamily()),
|
|
gci.GetFontWeight(),
|
|
gci.GetFontSize(),
|
|
gci.GetCodePage());
|
|
|
|
// For East Asian version, we want to get the code page from the registry or shell32, so we can specify console
|
|
// codepage by console.cpl or shell32. The default codepage is OEMCP.
|
|
gci.CP = gci.GetCodePage();
|
|
gci.OutputCP = gci.GetCodePage();
|
|
|
|
gci.Flags |= CONSOLE_USE_PRIVATE_FLAGS;
|
|
|
|
NTSTATUS Status = SCREEN_INFORMATION::CreateInstance(gci.GetWindowSize(),
|
|
fiFont,
|
|
gci.GetScreenBufferSize(),
|
|
gci.GetDefaultAttributes(),
|
|
TextAttribute{ gci.GetPopupFillAttribute() },
|
|
gci.GetCursorSize(),
|
|
&gci.ScreenBuffers);
|
|
|
|
// TODO: MSFT 9355013: This needs to be resolved. We increment it once with no handle to ensure it's never cleaned up
|
|
// and one always exists for the renderer (and potentially other functions.)
|
|
// It's currently a load-bearing piece of code. http://osgvsowi/9355013
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
gci.ScreenBuffers[0].IncrementOriginalScreenBuffer();
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
// Routine Description:
|
|
// - This routine copies a rectangular region from the screen buffer to the screen buffer.
|
|
// Arguments:
|
|
// - screenInfo - reference to screen info
|
|
// - source - rectangle in source buffer to copy
|
|
// - targetOrigin - upper left coordinates of new location rectangle
|
|
static void _CopyRectangle(SCREEN_INFORMATION& screenInfo,
|
|
const Viewport& source,
|
|
const COORD targetOrigin)
|
|
{
|
|
const auto sourceOrigin = source.Origin();
|
|
|
|
// 0. If the source and the target are the same... we have nothing to do. Leave.
|
|
if (sourceOrigin == targetOrigin)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// 1. If we're copying entire rows of the buffer and moving them directly up or down,
|
|
// then we can send a rotate command to the underlying buffer to just adjust the
|
|
// row locations instead of copying or moving anything.
|
|
{
|
|
const auto bufferSize = screenInfo.GetBufferSize().Dimensions();
|
|
const auto sourceFullRows = source.Width() == bufferSize.X;
|
|
const auto verticalCopyOnly = source.Left() == 0 && targetOrigin.X == 0;
|
|
if (sourceFullRows && verticalCopyOnly)
|
|
{
|
|
const auto delta = targetOrigin.Y - source.Top();
|
|
|
|
screenInfo.GetTextBuffer().ScrollRows(source.Top(), source.Height(), gsl::narrow<SHORT>(delta));
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 2. We can move any other scenario in-place without copying. We just have to carefully
|
|
// choose which direction we walk through filling up the target so it doesn't accidentally
|
|
// erase the source material before it can be copied/moved to the new location.
|
|
{
|
|
const auto target = Viewport::FromDimensions(targetOrigin, source.Dimensions());
|
|
const auto walkDirection = Viewport::DetermineWalkDirection(source, target);
|
|
|
|
auto sourcePos = source.GetWalkOrigin(walkDirection);
|
|
auto targetPos = target.GetWalkOrigin(walkDirection);
|
|
|
|
do
|
|
{
|
|
const auto data = OutputCell(*screenInfo.GetCellDataAt(sourcePos));
|
|
screenInfo.Write(OutputCellIterator({ &data, 1 }), targetPos);
|
|
|
|
source.WalkInBounds(sourcePos, walkDirection);
|
|
} while (target.WalkInBounds(targetPos, walkDirection));
|
|
}
|
|
}
|
|
|
|
// Routine Description:
|
|
// - This routine reads a sequence of attributes from the screen buffer.
|
|
// Arguments:
|
|
// - screenInfo - reference to screen buffer information.
|
|
// - coordRead - Screen buffer coordinate to begin reading from.
|
|
// - amountToRead - the number of elements to read
|
|
// Return Value:
|
|
// - vector of attribute data
|
|
std::vector<WORD> ReadOutputAttributes(const SCREEN_INFORMATION& screenInfo,
|
|
const COORD coordRead,
|
|
const size_t amountToRead)
|
|
{
|
|
// Short circuit. If nothing to read, leave early.
|
|
if (amountToRead == 0)
|
|
{
|
|
return {};
|
|
}
|
|
|
|
// Short circuit, if reading out of bounds, leave early.
|
|
if (!screenInfo.GetBufferSize().IsInBounds(coordRead))
|
|
{
|
|
return {};
|
|
}
|
|
|
|
// Get iterator to the position we should start reading at.
|
|
auto it = screenInfo.GetCellDataAt(coordRead);
|
|
// Count up the number of cells we've attempted to read.
|
|
ULONG amountRead = 0;
|
|
// Prepare the return value string.
|
|
std::vector<WORD> retVal;
|
|
// Reserve the number of cells. If we have >U+FFFF, it will auto-grow later and that's OK.
|
|
retVal.reserve(amountToRead);
|
|
|
|
// While we haven't read enough cells yet and the iterator is still valid (hasn't reached end of buffer)
|
|
while (amountRead < amountToRead && it)
|
|
{
|
|
// If the first thing we read is trailing, pad with a space.
|
|
// OR If the last thing we read is leading, pad with a space.
|
|
if ((amountRead == 0 && it->DbcsAttr().IsTrailing()) ||
|
|
(amountRead == (amountToRead - 1) && it->DbcsAttr().IsLeading()))
|
|
{
|
|
retVal.push_back(it->TextAttr().GetLegacyAttributes());
|
|
}
|
|
else
|
|
{
|
|
retVal.push_back(it->TextAttr().GetLegacyAttributes() | it->DbcsAttr().GeneratePublicApiAttributeFormat());
|
|
}
|
|
|
|
amountRead++;
|
|
it++;
|
|
}
|
|
|
|
return retVal;
|
|
}
|
|
|
|
// Routine Description:
|
|
// - This routine reads a sequence of unicode characters from the screen buffer
|
|
// Arguments:
|
|
// - screenInfo - reference to screen buffer information.
|
|
// - coordRead - Screen buffer coordinate to begin reading from.
|
|
// - amountToRead - the number of elements to read
|
|
// Return Value:
|
|
// - wstring
|
|
std::wstring ReadOutputStringW(const SCREEN_INFORMATION& screenInfo,
|
|
const COORD coordRead,
|
|
const size_t amountToRead)
|
|
{
|
|
// Short circuit. If nothing to read, leave early.
|
|
if (amountToRead == 0)
|
|
{
|
|
return {};
|
|
}
|
|
|
|
// Short circuit, if reading out of bounds, leave early.
|
|
if (!screenInfo.GetBufferSize().IsInBounds(coordRead))
|
|
{
|
|
return {};
|
|
}
|
|
|
|
// Get iterator to the position we should start reading at.
|
|
auto it = screenInfo.GetCellDataAt(coordRead);
|
|
|
|
// Count up the number of cells we've attempted to read.
|
|
ULONG amountRead = 0;
|
|
|
|
// Prepare the return value string.
|
|
std::wstring retVal;
|
|
retVal.reserve(amountToRead); // Reserve the number of cells. If we have >U+FFFF, it will auto-grow later and that's OK.
|
|
|
|
// While we haven't read enough cells yet and the iterator is still valid (hasn't reached end of buffer)
|
|
while (amountRead < amountToRead && it)
|
|
{
|
|
// If the first thing we read is trailing, pad with a space.
|
|
// OR If the last thing we read is leading, pad with a space.
|
|
if ((amountRead == 0 && it->DbcsAttr().IsTrailing()) ||
|
|
(amountRead == (amountToRead - 1) && it->DbcsAttr().IsLeading()))
|
|
{
|
|
retVal += UNICODE_SPACE;
|
|
}
|
|
else
|
|
{
|
|
// Otherwise, add anything that isn't a trailing cell. (Trailings are duplicate copies of the leading.)
|
|
if (!it->DbcsAttr().IsTrailing())
|
|
{
|
|
retVal += it->Chars();
|
|
}
|
|
}
|
|
|
|
amountRead++;
|
|
it++;
|
|
}
|
|
|
|
return retVal;
|
|
}
|
|
|
|
// Routine Description:
|
|
// - This routine reads a sequence of ascii characters from the screen buffer
|
|
// Arguments:
|
|
// - screenInfo - reference to screen buffer information.
|
|
// - coordRead - Screen buffer coordinate to begin reading from.
|
|
// - amountToRead - the number of elements to read
|
|
// Return Value:
|
|
// - string of char data
|
|
std::string ReadOutputStringA(const SCREEN_INFORMATION& screenInfo,
|
|
const COORD coordRead,
|
|
const size_t amountToRead)
|
|
{
|
|
const auto wstr = ReadOutputStringW(screenInfo, coordRead, amountToRead);
|
|
|
|
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
return ConvertToA(gci.OutputCP, wstr);
|
|
}
|
|
|
|
void ScreenBufferSizeChange(const COORD coordNewSize)
|
|
{
|
|
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
|
|
try
|
|
{
|
|
gci.pInputBuffer->Write(std::make_unique<WindowBufferSizeEvent>(coordNewSize));
|
|
}
|
|
catch (...)
|
|
{
|
|
LOG_HR(wil::ResultFromCaughtException());
|
|
}
|
|
}
|
|
|
|
// Routine Description:
|
|
// - This is simply a notifier method to let accessibility and renderers know that a region of the buffer
|
|
// has been copied/moved to another location in a block fashion.
|
|
// Arguments:
|
|
// - screenInfo - The relevant screen buffer where data was moved
|
|
// - source - The viewport describing the region where data was copied from
|
|
// - fill - The viewport describing the area that was filled in with the fill character (uncovered area)
|
|
// - target - The viewport describing the region where data was copied to
|
|
static void _ScrollScreen(SCREEN_INFORMATION& screenInfo, const Viewport& source, const Viewport& fill, const Viewport& target)
|
|
{
|
|
if (screenInfo.IsActiveScreenBuffer())
|
|
{
|
|
IAccessibilityNotifier* pNotifier = ServiceLocator::LocateAccessibilityNotifier();
|
|
if (pNotifier != nullptr)
|
|
{
|
|
pNotifier->NotifyConsoleUpdateScrollEvent(target.Origin().X - source.Left(), target.Origin().Y - source.RightInclusive());
|
|
}
|
|
}
|
|
|
|
// Get the render target and send it commands.
|
|
// It will figure out whether or not we're active and where the messages need to go.
|
|
auto& render = screenInfo.GetRenderTarget();
|
|
// Redraw anything in the target area
|
|
render.TriggerRedraw(target);
|
|
// Also redraw anything that was filled.
|
|
render.TriggerRedraw(fill);
|
|
}
|
|
|
|
// Routine Description:
|
|
// - This routine is a special-purpose scroll for use by AdjustCursorPosition.
|
|
// Arguments:
|
|
// - screenInfo - reference to screen buffer info.
|
|
// Return Value:
|
|
// - true if we succeeded in scrolling the buffer, otherwise false (if we're out of memory)
|
|
bool StreamScrollRegion(SCREEN_INFORMATION& screenInfo)
|
|
{
|
|
// Rotate the circular buffer around and wipe out the previous final line.
|
|
const bool inVtMode = WI_IsFlagSet(screenInfo.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING);
|
|
bool fSuccess = screenInfo.GetTextBuffer().IncrementCircularBuffer(inVtMode);
|
|
if (fSuccess)
|
|
{
|
|
// Trigger a graphical update if we're active.
|
|
if (screenInfo.IsActiveScreenBuffer())
|
|
{
|
|
COORD coordDelta = { 0 };
|
|
coordDelta.Y = -1;
|
|
|
|
IAccessibilityNotifier* pNotifier = ServiceLocator::LocateAccessibilityNotifier();
|
|
if (pNotifier)
|
|
{
|
|
// Notify accessibility that a scroll has occurred.
|
|
pNotifier->NotifyConsoleUpdateScrollEvent(coordDelta.X, coordDelta.Y);
|
|
}
|
|
|
|
if (ServiceLocator::LocateGlobals().pRender != nullptr)
|
|
{
|
|
ServiceLocator::LocateGlobals().pRender->TriggerScroll(&coordDelta);
|
|
}
|
|
}
|
|
}
|
|
return fSuccess;
|
|
}
|
|
|
|
// Routine Description:
|
|
// - This routine copies ScrollRectangle to DestinationOrigin then fills in ScrollRectangle with Fill.
|
|
// - The scroll region is copied to a third buffer, the scroll region is filled, then the original contents of the scroll region are copied to the destination.
|
|
// Arguments:
|
|
// - screenInfo - reference to screen buffer info.
|
|
// - scrollRectGiven - Region to copy/move (source and size)
|
|
// - clipRectGiven - Optional clip region to contain buffer change effects
|
|
// - destinationOriginGiven - Upper left corner of target region.
|
|
// - fillCharGiven - Character to fill source region with.
|
|
// - fillAttrsGiven - Attribute to fill source region with.
|
|
// NOTE: Throws exceptions
|
|
void ScrollRegion(SCREEN_INFORMATION& screenInfo,
|
|
const SMALL_RECT scrollRectGiven,
|
|
const std::optional<SMALL_RECT> clipRectGiven,
|
|
const COORD destinationOriginGiven,
|
|
const wchar_t fillCharGiven,
|
|
const TextAttribute fillAttrsGiven)
|
|
{
|
|
// ------ 1. PREP SOURCE ------
|
|
// Set up the source viewport.
|
|
auto source = Viewport::FromInclusive(scrollRectGiven);
|
|
const auto originalSourceOrigin = source.Origin();
|
|
|
|
// Alright, let's make sure that our source fits inside the buffer.
|
|
const auto buffer = screenInfo.GetBufferSize();
|
|
source = Viewport::Intersect(source, buffer);
|
|
|
|
// If the source is no longer valid, then there's nowhere we can copy from
|
|
// and also nowhere we can fill. We're done. Return early.
|
|
if (!source.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// ------ 2. PREP CLIP ------
|
|
// Now figure out our clipping area. If we have clipping specified, it will limit
|
|
// the area that can be affected (targeted or filling) throughout this operation.
|
|
// If there was no clip rect, we'll clip to the entire buffer size.
|
|
auto clip = Viewport::FromInclusive(clipRectGiven.value_or(buffer.ToInclusive()));
|
|
|
|
// OK, make sure that the clip rectangle also fits inside the buffer
|
|
clip = Viewport::Intersect(buffer, clip);
|
|
|
|
// ------ 3. PREP FILL ------
|
|
// Then think about fill. We will fill in any area of the source that we copied from
|
|
// with the fill character as long as it falls inside the clip region (the area
|
|
// that is allowed to be affected).
|
|
auto fill = Viewport::Intersect(clip, source);
|
|
|
|
// If fill is no longer valid, then there is no area that we're allowed to write to
|
|
// within the buffer. So we can just exit early.
|
|
if (!fill.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Determine the cell we will use to fill in any revealed/uncovered space.
|
|
// We generally use exactly what was given to us.
|
|
OutputCellIterator fillData(fillCharGiven, fillAttrsGiven);
|
|
|
|
// However, if the character is null and we were given a null attribute (represented as legacy 0),
|
|
// then we'll just fill with spaces and whatever the buffer's default colors are.
|
|
if (fillCharGiven == UNICODE_NULL && fillAttrsGiven.IsLegacy() && fillAttrsGiven.GetLegacyAttributes() == 0)
|
|
{
|
|
fillData = OutputCellIterator(UNICODE_SPACE, screenInfo.GetAttributes());
|
|
}
|
|
|
|
// ------ 4. PREP TARGET ------
|
|
// Now it's time to think about the target. We're only given the origin of the target
|
|
// because it is assumed that it will have the same relative dimensions as the original source.
|
|
auto targetOrigin = destinationOriginGiven;
|
|
|
|
// However, if we got to this point, we may have clipped the source because some part of it
|
|
// fell outside of the buffer.
|
|
// Apply any delta between the original source rectangle's origin and its current position to
|
|
// the target origin.
|
|
{
|
|
auto currentSourceOrigin = source.Origin();
|
|
targetOrigin.X += currentSourceOrigin.X - originalSourceOrigin.X;
|
|
targetOrigin.Y += currentSourceOrigin.Y - originalSourceOrigin.Y;
|
|
}
|
|
|
|
// And now the target viewport is the same size as the source viewport but at the different position.
|
|
auto target = Viewport::FromDimensions(targetOrigin, source.Dimensions());
|
|
|
|
// However, this might mean that the target is falling outside of the region we're allowed to edit
|
|
// (the clip area). So we need to reduce the target to only inside the clip.
|
|
// But backup the original target origin first, because we need to know how it has changed.
|
|
const auto originalTargetOrigin = target.Origin();
|
|
target = Viewport::Intersect(clip, target);
|
|
|
|
// OK, if the target became smaller than before, we need to also adjust the source accordingly
|
|
// so we don't waste time loading up/copying things that have no place to go within the target.
|
|
{
|
|
const auto currentTargetOrigin = target.Origin();
|
|
auto sourceOrigin = source.Origin();
|
|
sourceOrigin.X += currentTargetOrigin.X - originalTargetOrigin.X;
|
|
sourceOrigin.Y += currentTargetOrigin.Y - originalTargetOrigin.Y;
|
|
|
|
source = Viewport::FromDimensions(sourceOrigin, target.Dimensions());
|
|
}
|
|
|
|
// ------ 5. COPY ------
|
|
// If the target region is valid, let's do this.
|
|
if (target.IsValid())
|
|
{
|
|
// Perform the copy from the source to the target.
|
|
_CopyRectangle(screenInfo, source, target.Origin());
|
|
|
|
// Notify the renderer and accessibility as to what moved and where.
|
|
_ScrollScreen(screenInfo, source, fill, target);
|
|
}
|
|
|
|
// ------ 6. FILL ------
|
|
// Now fill in anything that wasn't already touched by the copy above.
|
|
// Fill as a single viewport represents the entire region we were allowed to
|
|
// write into. But since we already copied, filling the whole thing might
|
|
// overwrite what we just placed at the target.
|
|
// So use the special subtraction function to get the viewports that fall
|
|
// within the fill area but outside of the target area.
|
|
const auto remaining = Viewport::Subtract(fill, target);
|
|
|
|
// Apply the fill data to each of the viewports we're given here.
|
|
for (size_t i = 0; i < remaining.size(); i++)
|
|
{
|
|
const auto& view = remaining.at(i);
|
|
screenInfo.WriteRect(fillData, view);
|
|
}
|
|
}
|
|
|
|
void SetActiveScreenBuffer(SCREEN_INFORMATION& screenInfo)
|
|
{
|
|
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
gci.pCurrentScreenBuffer = &screenInfo;
|
|
|
|
// initialize cursor GH#4102 - Typically, the cursor is set to on by the
|
|
// cursor blinker. Unfortunately, in conpty mode, there is no cursor
|
|
// blinker. So, in conpty mode, we need to leave the cursor on always. The
|
|
// cursor can still be set to hidden, and whether the cursor should be
|
|
// blinking will still be passed through to the terminal, but internally,
|
|
// the cursor should always be on.
|
|
//
|
|
// In particular, some applications make use of a calling
|
|
// `SetConsoleScreenBuffer` and `SetCursorPosition` without printing any
|
|
// text in between these calls. If we initialize the cursor to Off in conpty
|
|
// mode, then the cursor will remain off until they print text. This can
|
|
// lead to alignment problems in the terminal, because we won't move the
|
|
// terminal's cursor in this _exact_ scenario.
|
|
screenInfo.GetTextBuffer().GetCursor().SetIsOn(gci.IsInVtIoMode());
|
|
|
|
// set font
|
|
screenInfo.RefreshFontWithRenderer();
|
|
|
|
// Empty input buffer.
|
|
gci.pInputBuffer->FlushAllButKeys();
|
|
|
|
// Set window size.
|
|
screenInfo.PostUpdateWindowSize();
|
|
|
|
gci.ConsoleIme.RefreshAreaAttributes();
|
|
|
|
// Write data to screen.
|
|
WriteToScreen(screenInfo, screenInfo.GetViewport());
|
|
}
|
|
|
|
// TODO: MSFT 9450717 This should join the ProcessList class when CtrlEvents become moved into the server. https://osgvsowi/9450717
|
|
void CloseConsoleProcessState()
|
|
{
|
|
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
// If there are no connected processes, sending control events is pointless as there's no one do send them to. In
|
|
// this case we'll just exit conhost.
|
|
|
|
// N.B. We can get into this state when a process has a reference to the console but hasn't connected. For example,
|
|
// when it's created suspended and never resumed.
|
|
if (gci.ProcessHandleList.IsEmpty())
|
|
{
|
|
ServiceLocator::RundownAndExit(STATUS_SUCCESS);
|
|
}
|
|
|
|
HandleCtrlEvent(CTRL_CLOSE_EVENT);
|
|
|
|
// Jiggle the handle: (see MSFT:19419231)
|
|
// When we call this function, we'll only actually close the console once
|
|
// we're totally unlocked. If our caller has the console locked, great,
|
|
// we'll dispatch the ctrl event once they unlock. However, if they're
|
|
// not running under lock (eg PtySignalInputThread::_GetData), then the
|
|
// ctrl event will never actually get dispatched.
|
|
// So, lock and unlock here, to make sure the ctrl event gets handled.
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
}
|