// Copyright (c) Microsoft Corporation.// Licensed under the MIT license.#include "precomp.h"#include "ApiRoutines.h"#include "_stream.h"#include "stream.h"#include "writeData.hpp"#include "_output.h"#include "output.h"#include "dbcs.h"#include "handle.h"#include "misc.h"#include "utf8ToWidecharParser.hpp"#include "../types/inc/convert.hpp"#include "../types/inc/GlyphWidth.hpp"#include "../types/inc/Viewport.hpp"#include "..\interactivity\inc\ServiceLocator.hpp"#pragma hdrstopusing namespace Microsoft::Console::Types;// Used by WriteCharsLegacy.#define IS_GLYPH_CHAR(wch) (((wch) < L' ') || ((wch) == 0x007F))// Routine Description:// - This routine updates the cursor position. Its input is the non-special// cased new location of the cursor. For example, if the cursor were being// moved one space backwards from the left edge of the screen, the X// coordinate would be -1. This routine would set the X coordinate to// the right edge of the screen and decrement the Y coordinate by one.// Arguments:// - screenInfo - reference to screen buffer information structure.// - coordCursor - New location of cursor.// - fKeepCursorVisible - TRUE if changing window origin desirable when hit right edge// Return Value:[[nodiscard]]NTSTATUS AdjustCursorPosition(SCREEN_INFORMATION& screenInfo,_In_ COORD coordCursor,const BOOL fKeepCursorVisible,_Inout_opt_ PSHORT psScrollY){const COORD bufferSize = screenInfo.GetBufferSize().Dimensions();if (coordCursor.X < 0){if (coordCursor.Y > 0){coordCursor.X = (SHORT)(bufferSize.X + coordCursor.X);coordCursor.Y = (SHORT)(coordCursor.Y - 1);}else{coordCursor.X = 0;}}else if (coordCursor.X >= bufferSize.X){// at end of line. if wrap mode, wrap cursor. otherwise leave it where it is.if (screenInfo.OutputMode & ENABLE_WRAP_AT_EOL_OUTPUT){coordCursor.Y += coordCursor.X / bufferSize.X;coordCursor.X = coordCursor.X % bufferSize.X;}else{coordCursor.X = screenInfo.GetTextBuffer().GetCursor().GetPosition().X;}}const auto bufferAttributes = screenInfo.GetAttributes();const auto relativeMargins = screenInfo.GetRelativeScrollMargins();auto viewport = screenInfo.GetViewport();SMALL_RECT srMargins = screenInfo.GetAbsoluteScrollMargins().ToInclusive();const bool fMarginsSet = srMargins.Bottom > srMargins.Top;COORD currentCursor = screenInfo.GetTextBuffer().GetCursor().GetPosition();const int iCurrentCursorY = currentCursor.Y;const bool inVtMode = WI_IsFlagSet(screenInfo.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING);const bool fCursorInMargins = iCurrentCursorY <= srMargins.Bottom && iCurrentCursorY >= srMargins.Top;const bool cursorAboveViewport = coordCursor.Y < 0 && inVtMode;const bool fScrollDown = fMarginsSet && fCursorInMargins && (coordCursor.Y > srMargins.Bottom);bool fScrollUp = fMarginsSet && fCursorInMargins && (coordCursor.Y < srMargins.Top);const bool fScrollUpWithoutMargins = (!fMarginsSet) && cursorAboveViewport;// if we're in VT mode, AND MARGINS AREN'T SET and a Reverse Line Feed took the cursor up past the top of the viewport,// VT style scroll the contents of the screen.// This can happen in applications like `less`, that don't set margins, because they're going to// scroll the entire screen anyways, so no need for them to ever set the margins.if (fScrollUpWithoutMargins){fScrollUp = true;srMargins.Top = 0;srMargins.Bottom = screenInfo.GetViewport().BottomInclusive();}const bool scrollDownAtTop = fScrollDown && relativeMargins.Top() == 0;if (scrollDownAtTop){// We're trying to scroll down, and the top margin is at the top of the viewport.// In this case, we want the lines that are "scrolled off" to appear in// the scrollback instead of being discarded.// To do this, we're going to scroll everything starting at the bottom// margin down, then move the viewport down.const SHORT delta = coordCursor.Y - srMargins.Bottom;SMALL_RECT scrollRect{0};scrollRect.Left = 0;scrollRect.Top = srMargins.Bottom + 1; // One below marginsscrollRect.Bottom = bufferSize.Y - 1; // -1, otherwise this would be an exclusive rect.scrollRect.Right = bufferSize.X - 1; // -1, otherwise this would be an exclusive rect.// This is the Y position we're moving the contents below the bottom margin to.SHORT moveToYPosition = scrollRect.Top + delta;// This is where the viewport will need to be to give the effect of// scrolling the contents in the margins.SHORT newViewTop = viewport.Top() + delta;// This is how many new lines need to be added to the buffer to support this operation.const SHORT newRows = (viewport.BottomExclusive() + delta) - bufferSize.Y;// If we're near the bottom of the buffer, we might need to insert some// new rows at the bottom.// If we do this, then the viewport is now one line higher than it used// to be, so it needs to move down by one less line.for(auto i = 0; i < newRows; i++){screenInfo.GetTextBuffer().IncrementCircularBuffer();moveToYPosition--;newViewTop--;scrollRect.Top--;}const COORD newPostMarginsOrigin = { 0, moveToYPosition };const COORD newViewOrigin = { 0, newViewTop };// Unset the margins to scroll the content below the margins,// then restore them after.screenInfo.SetScrollMargins(Viewport::FromInclusive({0}));try{ScrollRegion(screenInfo, scrollRect, std::nullopt, newPostMarginsOrigin, UNICODE_SPACE, bufferAttributes);}CATCH_LOG();screenInfo.SetScrollMargins(relativeMargins);// Move the viewport downauto hr = screenInfo.SetViewportOrigin(true, newViewOrigin, true);if (FAILED(hr)){return NTSTATUS_FROM_HRESULT(hr);}// If we didn't actually move the viewport, it's because we're at the// bottom of the buffer, and the top lines of the viewport have// changed. Manually invalidate here, to make sure the screen// displays the correct text.if (newViewOrigin == viewport.Origin()){Viewport invalid = Viewport::FromDimensions(viewport.Origin(), {viewport.Width(), delta});screenInfo.GetRenderTarget().TriggerRedraw(invalid);}// reset where our local viewport is, and recalculate the cursor and// margin positions.viewport = screenInfo.GetViewport();if (newRows > 0){currentCursor.Y -= newRows;coordCursor.Y -= newRows;}srMargins = screenInfo.GetAbsoluteScrollMargins().ToInclusive();}// If we did the above scrollDownAtTop case, then we've already scrolled// the margins content, and we can skip this.if (fScrollUp || (fScrollDown && !scrollDownAtTop)){SHORT diff = coordCursor.Y - (fScrollUp ? srMargins.Top : srMargins.Bottom);SMALL_RECT scrollRect = { 0 };scrollRect.Top = srMargins.Top;scrollRect.Bottom = srMargins.Bottom;scrollRect.Left = screenInfo.GetViewport().Left(); // NOTE: Left/Right Scroll margins don't do anything currently.scrollRect.Right = screenInfo.GetViewport().RightInclusive();COORD dest;dest.X = scrollRect.Left;dest.Y = scrollRect.Top - diff;SMALL_RECT clipRect = scrollRect;// Typically ScrollRegion() clips by the scroll margins. However, if// we're scrolling down at the top of the viewport, we'll need to// not clip at the margins, instead move the contents of the margins// up above the viewport. So we'll clear out the current margins, and// set them to the viewport+(#diff rows above the viewport).if (scrollDownAtTop){clipRect.Top -= diff;auto fakeMargins = srMargins;fakeMargins.Top -= diff;auto fakeRelative = viewport.ConvertToOrigin(Viewport::FromInclusive(fakeMargins));screenInfo.SetScrollMargins(fakeRelative);}try{ScrollRegion(screenInfo, scrollRect, clipRect, dest, UNICODE_SPACE, bufferAttributes);}CATCH_LOG();if (scrollDownAtTop){// Undo the fake margins we set abovescreenInfo.SetScrollMargins(relativeMargins);}coordCursor.Y -= diff;}NTSTATUS Status = STATUS_SUCCESS;if (coordCursor.Y >= bufferSize.Y){// At the end of the buffer. Scroll contents of screen buffer so new position is visible.FAIL_FAST_IF(!(coordCursor.Y == bufferSize.Y));if (!StreamScrollRegion(screenInfo)){Status = STATUS_NO_MEMORY;}if (nullptr != psScrollY){*psScrollY += (SHORT)(bufferSize.Y - coordCursor.Y - 1);}coordCursor.Y += (SHORT)(bufferSize.Y - coordCursor.Y - 1);}const bool cursorMovedPastViewport = coordCursor.Y > screenInfo.GetViewport().BottomInclusive();const bool cursorMovedPastVirtualViewport = coordCursor.Y > screenInfo.GetVirtualViewport().BottomInclusive();if (NT_SUCCESS(Status)){// if at right or bottom edge of window, scroll right or down one char.if (cursorMovedPastViewport){COORD WindowOrigin;WindowOrigin.X = 0;WindowOrigin.Y = coordCursor.Y - screenInfo.GetViewport().BottomInclusive();Status = screenInfo.SetViewportOrigin(false, WindowOrigin, true);}}if (NT_SUCCESS(Status)){if (fKeepCursorVisible){screenInfo.MakeCursorVisible(coordCursor);}Status = screenInfo.SetCursorPosition(coordCursor, !!fKeepCursorVisible);// MSFT:19989333 - Only re-initialize the cursor row if the cursor moved// below the terminal section of the buffer (the virtual viewport),// and the visible part of the buffer (the actual viewport).// If this is only cursorMovedPastViewport, and you scroll up, then type// a character, we'll re-initialize the line the cursor is on.// If this is only cursorMovedPastVirtualViewport and you scroll down,// (with terminal scrolling disabled) then all lines newly exposed// will get their attributes constantly cleared out.// Both cursorMovedPastViewport and cursorMovedPastVirtualViewport worksif (inVtMode && cursorMovedPastViewport && cursorMovedPastVirtualViewport){screenInfo.InitializeCursorRowAttributes();}}return Status;}// Routine Description:// - This routine writes a string to the screen, processing any embedded// unicode characters. The string is also copied to the input buffer, if// the output mode is line mode.// Arguments:// - screenInfo - reference to screen buffer information structure.// - pwchBufferBackupLimit - Pointer to beginning of buffer.// - pwchBuffer - Pointer to buffer to copy string to. assumed to be at least as long as pwchRealUnicode.// This pointer is updated to point to the next position in the buffer.// - pwchRealUnicode - Pointer to string to write.// - pcb - On input, number of bytes to write. On output, number of bytes written.// - pcSpaces - On output, the number of spaces consumed by the written characters.// - dwFlags -// WC_DESTRUCTIVE_BACKSPACE backspace overwrites characters.// WC_KEEP_CURSOR_VISIBLE change window origin desirable when hit rt. edge// WC_ECHO if called by Read (echoing characters)// Return Value:// Note:// - This routine does not process tabs and backspace properly. That code will be implemented as part of the line editing services.[[nodiscard]]NTSTATUS WriteCharsLegacy(SCREEN_INFORMATION& screenInfo,_In_range_(<= , pwchBuffer) const wchar_t* const pwchBufferBackupLimit,_In_ const wchar_t* pwchBuffer,_In_reads_bytes_(*pcb) const wchar_t* pwchRealUnicode,_Inout_ size_t* const pcb,_Out_opt_ size_t* const pcSpaces,const SHORT sOriginalXPosition,const DWORD dwFlags,_Inout_opt_ PSHORT const psScrollY){const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();TextBuffer& textBuffer = screenInfo.GetTextBuffer();Cursor& cursor = textBuffer.GetCursor();COORD CursorPosition = cursor.GetPosition();NTSTATUS Status = STATUS_SUCCESS;SHORT XPosition;WCHAR LocalBuffer[LOCAL_BUFFER_SIZE];size_t TempNumSpaces = 0;const bool fUnprocessed = WI_IsFlagClear(screenInfo.OutputMode, ENABLE_PROCESSED_OUTPUT);// Must not adjust cursor here. It has to stay on for many write scenarios. Consumers should call for the// cursor to be turned off if they want that.const TextAttribute Attributes = screenInfo.GetAttributes();const size_t BufferSize = *pcb;*pcb = 0;const wchar_t* lpString = pwchRealUnicode;const COORD coordScreenBufferSize = screenInfo.GetBufferSize().Dimensions();while (*pcb < BufferSize){// correct for delayed EOLif (cursor.IsDelayedEOLWrap()){const COORD coordDelayedAt = cursor.GetDelayedAtPosition();cursor.ResetDelayEOLWrap();// Only act on a delayed EOL if we didn't move the cursor to a different position from where the EOL was marked.if (coordDelayedAt.X == CursorPosition.X && coordDelayedAt.Y == CursorPosition.Y){bool fDoEolWrap = false;if (WI_IsFlagSet(dwFlags, WC_DELAY_EOL_WRAP)){// Correct if it's a printable character and whoever called us still understands/wants delayed EOL wrap.if (*lpString >= UNICODE_SPACE){fDoEolWrap = true;}else if (*lpString == UNICODE_BACKSPACE){// if we have an active wrap and a backspace comes in, process it by moving the cursor// back one cell position unless it's already at the start of a row.*pcb += sizeof(WCHAR);lpString++;pwchRealUnicode++;if (CursorPosition.X != 0){--CursorPosition.X;Status = AdjustCursorPosition(screenInfo, CursorPosition, WI_IsFlagSet(dwFlags, WC_KEEP_CURSOR_VISIBLE), psScrollY);CursorPosition = cursor.GetPosition();}continue;}}else{// Uh oh, we've hit a consumer that doesn't know about delayed end of lines. To rectify this, just quickly jump// forward to the next line as if we had done it earlier, then let everything else play out normally.fDoEolWrap = true;}if (fDoEolWrap){CursorPosition.X = 0;CursorPosition.Y++;Status = AdjustCursorPosition(screenInfo, CursorPosition, WI_IsFlagSet(dwFlags, WC_KEEP_CURSOR_VISIBLE), psScrollY);CursorPosition = cursor.GetPosition();}}}if (screenInfo.InVTMode()){// if we're at the beginning of a row and we get a backspace and told to limit backspacing, skip itif (*lpString == UNICODE_BACKSPACE && CursorPosition.X == 0 && WI_IsFlagSet(dwFlags, WC_LIMIT_BACKSPACE)){*pcb += sizeof(wchar_t);++lpString;++pwchRealUnicode;continue;}}// As an optimization, collect characters in buffer and print out all at once.XPosition = cursor.GetPosition().X;size_t i = 0;wchar_t* LocalBufPtr = LocalBuffer;while (*pcb < BufferSize && i < LOCAL_BUFFER_SIZE && XPosition < coordScreenBufferSize.X){#pragma prefast(suppress:26019, "Buffer is taken in multiples of 2. Validation is ok.")const wchar_t Char = *lpString;const wchar_t RealUnicodeChar = *pwchRealUnicode;if (!IS_GLYPH_CHAR(RealUnicodeChar) || fUnprocessed){if (IsGlyphFullWidth(Char)){if (i < (LOCAL_BUFFER_SIZE - 1) && XPosition < (coordScreenBufferSize.X - 1)){*LocalBufPtr++ = Char;// cursor adjusted by 2 because the char is double widthXPosition += 2;i += 1;pwchBuffer++;}else{goto EndWhile;}}else{*LocalBufPtr = Char;LocalBufPtr++;XPosition++;i++;pwchBuffer++;}}else{FAIL_FAST_IF(!(WI_IsFlagSet(screenInfo.OutputMode, ENABLE_PROCESSED_OUTPUT)));switch (RealUnicodeChar){case UNICODE_BELL:if (dwFlags & WC_ECHO){goto CtrlChar;}else{screenInfo.SendNotifyBeep();}break;case UNICODE_BACKSPACE:// automatically go to EndWhile. this is because// backspace is not destructive, so "aBkSp" prints// a with the cursor on the "a". we could achieve// this behavior staying in this loop and figuring out// the string that needs to be printed, but it would// be expensive and it's the exceptional case.goto EndWhile;break;case UNICODE_TAB:if (screenInfo.InVTMode()){goto EndWhile;}else{const ULONG TabSize = NUMBER_OF_SPACES_IN_TAB(XPosition);XPosition = (SHORT)(XPosition + TabSize);if (XPosition >= coordScreenBufferSize.X || WI_IsFlagSet(dwFlags, WC_NONDESTRUCTIVE_TAB)){goto EndWhile;}for (ULONG j = 0; j < TabSize && i < LOCAL_BUFFER_SIZE; j++, i++){*LocalBufPtr = UNICODE_SPACE;LocalBufPtr++;}}pwchBuffer++;break;case UNICODE_LINEFEED:case UNICODE_CARRIAGERETURN:goto EndWhile;default:// if char is ctrl char, write ^char.if ((dwFlags & WC_ECHO) && (IS_CONTROL_CHAR(RealUnicodeChar))){CtrlChar:if (i < (LOCAL_BUFFER_SIZE - 1)){*LocalBufPtr = (WCHAR)'^';LocalBufPtr++;XPosition++;i++;*LocalBufPtr = (WCHAR)(RealUnicodeChar + (WCHAR)'@');LocalBufPtr++;XPosition++;i++;pwchBuffer++;}else{goto EndWhile;}}else{// As a special favor to incompetent apps that attempt to display control chars,// convert to corresponding OEM Glyph CharsWORD CharType;GetStringTypeW(CT_CTYPE1, &RealUnicodeChar, 1, &CharType);if (CharType == C1_CNTRL){ConvertOutputToUnicode(gci.OutputCP,(LPSTR)&RealUnicodeChar,1,LocalBufPtr,1);}else if (Char == UNICODE_NULL){*LocalBufPtr = UNICODE_SPACE;}else{*LocalBufPtr = Char;}LocalBufPtr++;XPosition++;i++;pwchBuffer++;}}}lpString++;pwchRealUnicode++;*pcb += sizeof(WCHAR);}EndWhile:if (i != 0){CursorPosition = cursor.GetPosition();// Make sure we don't write past the end of the buffer.if (i > (ULONG)coordScreenBufferSize.X - CursorPosition.X){i = (ULONG)coordScreenBufferSize.X - CursorPosition.X;}// line was wrapped if we're writing up to the end of the current rowOutputCellIterator it(std::wstring_view(LocalBuffer, i), Attributes);const auto itEnd = screenInfo.Write(it);// Notify accessibilityscreenInfo.NotifyAccessibilityEventing(CursorPosition.X, CursorPosition.Y,CursorPosition.X + gsl::narrow<SHORT>(i - 1), CursorPosition.Y);// The number of "spaces" or "cells" we have consumed needs to be reported and stored for later// when/if we need to erase the command line.TempNumSpaces += itEnd.GetCellDistance(it);CursorPosition.X = XPosition;// enforce a delayed newline if we're about to pass the end and the WC_DELAY_EOL_WRAP flag is set.if (WI_IsFlagSet(dwFlags, WC_DELAY_EOL_WRAP) && CursorPosition.X >= coordScreenBufferSize.X){// Our cursor position as of this time is going to remain on the last position in this column.CursorPosition.X = coordScreenBufferSize.X - 1;// Update in the structures that we're still pointing to the last character in the rowcursor.SetPosition(CursorPosition);// Record for the delay comparison that we're delaying on the last character in the rowcursor.DelayEOLWrap(CursorPosition);}else{Status = AdjustCursorPosition(screenInfo, CursorPosition, WI_IsFlagSet(dwFlags, WC_KEEP_CURSOR_VISIBLE), psScrollY);}if (*pcb == BufferSize){if (nullptr != pcSpaces){*pcSpaces = TempNumSpaces;}return STATUS_SUCCESS;}continue;}else if (*pcb >= BufferSize){FAIL_FAST_IF(!(WI_IsFlagSet(screenInfo.OutputMode, ENABLE_PROCESSED_OUTPUT)));// this catches the case where the number of backspaces == the number of characters.if (nullptr != pcSpaces){*pcSpaces = TempNumSpaces;}return STATUS_SUCCESS;}FAIL_FAST_IF(!(WI_IsFlagSet(screenInfo.OutputMode, ENABLE_PROCESSED_OUTPUT)));switch (*lpString){case UNICODE_BACKSPACE:{// move cursor backwards one space. overwrite current char with blank.// we get here because we have to backspace from the beginning of the lineTempNumSpaces -= 1;if (pwchBuffer == pwchBufferBackupLimit){CursorPosition.X -= 1;}else{const wchar_t* Tmp;wchar_t* Tmp2 = nullptr;WCHAR LastChar;const size_t bufferSize = pwchBuffer - pwchBufferBackupLimit;std::unique_ptr<wchar_t[]> buffer;try{buffer = std::make_unique<wchar_t[]>(bufferSize);std::fill_n(buffer.get(), bufferSize, UNICODE_NULL);}catch (...){return NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException());}for (i = 0, Tmp2 = buffer.get(), Tmp = pwchBufferBackupLimit;i < bufferSize; i++, Tmp++){// see 18120085, these two need to be seperate if statementsif (*Tmp == UNICODE_BACKSPACE){//it is important we do nothing in the else case for// this one instead of falling through to the below else.if(Tmp2 > buffer.get()){Tmp2--;}}else{FAIL_FAST_IF(!(Tmp2 >= buffer.get()));*Tmp2++ = *Tmp;}}if (Tmp2 == buffer.get()){LastChar = UNICODE_SPACE;}else{#pragma prefast(suppress:26001, "This is fine. Tmp2 has to have advanced or it would equal pBuffer.")LastChar = *(Tmp2 - 1);}if (LastChar == UNICODE_TAB){CursorPosition.X -= (SHORT)(RetrieveNumberOfSpaces(sOriginalXPosition,pwchBufferBackupLimit,(ULONG)(pwchBuffer - pwchBufferBackupLimit - 1)));if (CursorPosition.X < 0){CursorPosition.X = (coordScreenBufferSize.X - 1) / TAB_SIZE;CursorPosition.X *= TAB_SIZE;CursorPosition.X += 1;CursorPosition.Y -= 1;// since you just backspaced yourself back up into the previous row, unset the wrap// flag on the prev row if it was settextBuffer.GetRowByOffset(CursorPosition.Y).GetCharRow().SetWrapForced(false);}}else if (IS_CONTROL_CHAR(LastChar)){CursorPosition.X -= 1;TempNumSpaces -= 1;// overwrite second character of ^x sequence.if (dwFlags & WC_DESTRUCTIVE_BACKSPACE){try{screenInfo.Write(OutputCellIterator(UNICODE_SPACE, Attributes, 1), CursorPosition);Status = STATUS_SUCCESS;}CATCH_LOG();}CursorPosition.X -= 1;}else if (IsGlyphFullWidth(LastChar)){CursorPosition.X -= 1;TempNumSpaces -= 1;Status = AdjustCursorPosition(screenInfo, CursorPosition, dwFlags & WC_KEEP_CURSOR_VISIBLE, psScrollY);if (dwFlags & WC_DESTRUCTIVE_BACKSPACE){try{screenInfo.Write(OutputCellIterator(UNICODE_SPACE, Attributes, 1), CursorPosition);Status = STATUS_SUCCESS;}CATCH_LOG();}CursorPosition.X -= 1;}else{CursorPosition.X--;}}if ((dwFlags & WC_LIMIT_BACKSPACE) && (CursorPosition.X < 0)){CursorPosition.X = 0;OutputDebugStringA(("CONSRV: Ignoring backspace to previous line\n"));}Status = AdjustCursorPosition(screenInfo, CursorPosition, (dwFlags & WC_KEEP_CURSOR_VISIBLE) != 0, psScrollY);if (dwFlags & WC_DESTRUCTIVE_BACKSPACE){try{screenInfo.Write(OutputCellIterator(UNICODE_SPACE, Attributes, 1), cursor.GetPosition());}CATCH_LOG();}if (cursor.GetPosition().X == 0 && (screenInfo.OutputMode & ENABLE_WRAP_AT_EOL_OUTPUT) && pwchBuffer > pwchBufferBackupLimit){if (CheckBisectProcessW(screenInfo,pwchBufferBackupLimit,pwchBuffer + 1 - pwchBufferBackupLimit,coordScreenBufferSize.X - sOriginalXPosition,sOriginalXPosition,dwFlags & WC_ECHO)){CursorPosition.X = coordScreenBufferSize.X - 1;CursorPosition.Y = (SHORT)(cursor.GetPosition().Y - 1);// since you just backspaced yourself back up into the previous row, unset the wrap flag// on the prev row if it was settextBuffer.GetRowByOffset(CursorPosition.Y).GetCharRow().SetWrapForced(false);Status = AdjustCursorPosition(screenInfo, CursorPosition, dwFlags & WC_KEEP_CURSOR_VISIBLE, psScrollY);}}break;}case UNICODE_TAB:{// if VT-style tabs are set, then handle them the VT way, including not inserting spaces.// just move the cursor to the next tab stop.if (screenInfo.InVTMode()){const COORD cCursorOld = cursor.GetPosition();// Get Forward tab handles tabbing past the end of the bufferCursorPosition = screenInfo.GetForwardTab(cCursorOld);}else{const size_t TabSize = NUMBER_OF_SPACES_IN_TAB(cursor.GetPosition().X);CursorPosition.X = (SHORT)(cursor.GetPosition().X + TabSize);// move cursor forward to next tab stop. fill space with blanks.// we get here when the tab extends beyond the right edge of the// window. if the tab goes wraps the line, set the cursor to the first// position in the next line.pwchBuffer++;TempNumSpaces += TabSize;size_t NumChars = 0;if (CursorPosition.X >= coordScreenBufferSize.X){NumChars = gsl::narrow<size_t>(coordScreenBufferSize.X - cursor.GetPosition().X);CursorPosition.X = 0;CursorPosition.Y = cursor.GetPosition().Y + 1;// since you just tabbed yourself past the end of the row, set the wraptextBuffer.GetRowByOffset(cursor.GetPosition().Y).GetCharRow().SetWrapForced(true);}else{NumChars = gsl::narrow<size_t>(CursorPosition.X - cursor.GetPosition().X);CursorPosition.Y = cursor.GetPosition().Y;}if (!WI_IsFlagSet(dwFlags, WC_NONDESTRUCTIVE_TAB)){try{const OutputCellIterator it(UNICODE_SPACE, Attributes, NumChars);const auto done = screenInfo.Write(it, cursor.GetPosition());NumChars = done.GetCellDistance(it);}CATCH_LOG();}}Status = AdjustCursorPosition(screenInfo, CursorPosition, (dwFlags & WC_KEEP_CURSOR_VISIBLE) != 0, psScrollY);break;}case UNICODE_CARRIAGERETURN:{// Carriage return moves the cursor to the beginning of the line.// We don't need to worry about handling cr or lf for// backspace because input is sent to the user on cr or lf.pwchBuffer++;CursorPosition.X = 0;CursorPosition.Y = cursor.GetPosition().Y;Status = AdjustCursorPosition(screenInfo, CursorPosition, (dwFlags & WC_KEEP_CURSOR_VISIBLE) != 0, psScrollY);break;}case UNICODE_LINEFEED:{// move cursor to the next line.pwchBuffer++;if (gci.IsReturnOnNewlineAutomatic()){// Traditionally, we reset the X position to 0 with a newline automatically.// Some things might not want this automatic "ONLCR line discipline" (for example, things that are expecting a *NIX behavior.)// They will turn it off with an output mode flag.CursorPosition.X = 0;}CursorPosition.Y = (SHORT)(cursor.GetPosition().Y + 1);{// since we explicitly just moved down a row, clear the wrap status on the row we just came fromtextBuffer.GetRowByOffset(cursor.GetPosition().Y).GetCharRow().SetWrapForced(false);}Status = AdjustCursorPosition(screenInfo, CursorPosition, (dwFlags & WC_KEEP_CURSOR_VISIBLE) != 0, psScrollY);break;}default:{const wchar_t Char = *lpString;if (Char >= UNICODE_SPACE &&IsGlyphFullWidth(Char) &&XPosition >= (coordScreenBufferSize.X - 1) &&(screenInfo.OutputMode & ENABLE_WRAP_AT_EOL_OUTPUT)){const COORD TargetPoint = cursor.GetPosition();ROW& Row = textBuffer.GetRowByOffset(TargetPoint.Y);CharRow& charRow = Row.GetCharRow();try{// If we're on top of a trailing cell, clear it and the previous cell.if (charRow.DbcsAttrAt(TargetPoint.X).IsTrailing()){// Space to clear for 2 cells.OutputCellIterator it(UNICODE_SPACE, 2);// Back target point up one.auto writeTarget = TargetPoint;writeTarget.X--;// Write 2 clear cells.screenInfo.Write(it, writeTarget);}}catch (...){return NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException());}CursorPosition.X = 0;CursorPosition.Y = (SHORT)(TargetPoint.Y + 1);// since you just moved yourself down onto the next row with 1 character, that sounds like a// forced wrap so set the flagcharRow.SetWrapForced(true);// Additionally, this padding is only called for IsConsoleFullWidth (a.k.a. when a character// is too wide to fit on the current line).charRow.SetDoubleBytePadded(true);Status = AdjustCursorPosition(screenInfo, CursorPosition, dwFlags & WC_KEEP_CURSOR_VISIBLE, psScrollY);continue;}break;}}if (!NT_SUCCESS(Status)){return Status;}*pcb += sizeof(WCHAR);lpString++;pwchRealUnicode++;}if (nullptr != pcSpaces){*pcSpaces = TempNumSpaces;}return STATUS_SUCCESS;}// Routine Description:// - This routine writes a string to the screen, processing any embedded// unicode characters. The string is also copied to the input buffer, if// the output mode is line mode.// Arguments:// - screenInfo - reference to screen buffer information structure.// - pwchBufferBackupLimit - Pointer to beginning of buffer.// - pwchBuffer - Pointer to buffer to copy string to. assumed to be at least as long as pwchRealUnicode.// This pointer is updated to point to the next position in the buffer.// - pwchRealUnicode - Pointer to string to write.// - pcb - On input, number of bytes to write. On output, number of bytes written.// - pcSpaces - On output, the number of spaces consumed by the written characters.// - dwFlags -// WC_DESTRUCTIVE_BACKSPACE backspace overwrites characters.// WC_KEEP_CURSOR_VISIBLE change window origin (viewport) desirable when hit rt. edge// WC_ECHO if called by Read (echoing characters)// Return Value:// Note:// - This routine does not process tabs and backspace properly. That code will be implemented as part of the line editing services.[[nodiscard]]NTSTATUS WriteChars(SCREEN_INFORMATION& screenInfo,_In_range_(<= , pwchBuffer) const wchar_t* const pwchBufferBackupLimit,_In_ const wchar_t* pwchBuffer,_In_reads_bytes_(*pcb) const wchar_t* pwchRealUnicode,_Inout_ size_t* const pcb,_Out_opt_ size_t* const pcSpaces,const SHORT sOriginalXPosition,const DWORD dwFlags,_Inout_opt_ PSHORT const psScrollY){if (!WI_IsFlagSet(screenInfo.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING) ||!WI_IsFlagSet(screenInfo.OutputMode, ENABLE_PROCESSED_OUTPUT)){return WriteCharsLegacy(screenInfo,pwchBufferBackupLimit,pwchBuffer,pwchRealUnicode,pcb,pcSpaces,sOriginalXPosition,dwFlags,psScrollY);}NTSTATUS Status = STATUS_SUCCESS;size_t const BufferSize = *pcb;*pcb = 0;{size_t TempNumSpaces = 0;{if (NT_SUCCESS(Status)){FAIL_FAST_IF(!(WI_IsFlagSet(screenInfo.OutputMode, ENABLE_PROCESSED_OUTPUT)));FAIL_FAST_IF(!(WI_IsFlagSet(screenInfo.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING)));// defined down in the WriteBuffer default case hiding on the other end of the state machine. See outputStream.cpp// This is the only mode used by DoWriteConsole.FAIL_FAST_IF(!(WI_IsFlagSet(dwFlags, WC_LIMIT_BACKSPACE)));StateMachine& machine = screenInfo.GetStateMachine();size_t const cch = BufferSize / sizeof(WCHAR);machine.ProcessString(pwchRealUnicode, cch);*pcb += BufferSize;}}if (nullptr != pcSpaces){*pcSpaces = TempNumSpaces;}}return Status;}// Routine Description:// - Takes the given text and inserts it into the given screen buffer.// Note:// - Console lock must be held when calling this routine// - String has been translated to unicode at this point.// Arguments:// - pwchBuffer - wide character text to be inserted into buffer// - pcbBuffer - byte count of pwchBuffer on the way in, number of bytes consumed on the way out.// - screenInfo - Screen Information class to write the text into at the current cursor position// - ppWaiter - If writing to the console is blocked for whatever reason, this will be filled with a pointer to context// that can be used by the server to resume the call at a later time.// Return Value:// - STATUS_SUCCESS if OK.// - CONSOLE_STATUS_WAIT if we couldn't finish now and need to be called back later (see ppWaiter).// - Or a suitable NTSTATUS format error code for memory/string/math failures.[[nodiscard]]NTSTATUS DoWriteConsole(_In_reads_bytes_(*pcbBuffer) PWCHAR pwchBuffer,_Inout_ size_t* const pcbBuffer,SCREEN_INFORMATION& screenInfo,std::unique_ptr<WriteData>& waiter){const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();if (WI_IsAnyFlagSet(gci.Flags, (CONSOLE_SUSPENDED | CONSOLE_SELECTING | CONSOLE_SCROLLBAR_TRACKING))){try{waiter = std::make_unique<WriteData>(screenInfo,pwchBuffer,*pcbBuffer,gci.OutputCP);}catch (...){return NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException());}return CONSOLE_STATUS_WAIT;}const auto& textBuffer = screenInfo.GetTextBuffer();return WriteChars(screenInfo,pwchBuffer,pwchBuffer,pwchBuffer,pcbBuffer,nullptr,textBuffer.GetCursor().GetPosition().X,WC_LIMIT_BACKSPACE,nullptr);}// Routine Description:// - This method performs the actual work of attempting to write to the console, converting data types as necessary// to adapt from the server types to the legacy internal host types.// - It operates on Unicode data only. It's assumed the text is translated by this point.// Arguments:// - OutContext - the console output object to write the new text into// - pwsTextBuffer - wide character text buffer provided by client application to insert// - cchTextBufferLength - text buffer counted in characters// - pcchTextBufferRead - character count of the number of characters we were able to insert before returning// - ppWaiter - If we are blocked from writing now and need to wait, this is filled with contextual data for the server to restore the call later// Return Value:// - S_OK if successful.// - S_OK if we need to wait (check if ppWaiter is not nullptr).// - Or a suitable HRESULT code for math/string/memory failures.[[nodiscard]]HRESULT WriteConsoleWImplHelper(IConsoleOutputObject& context,const std::wstring_view buffer,size_t& read,std::unique_ptr<WriteData>& waiter) noexcept{try{// Set out variables in case we exit early.read = 0;waiter.reset();// Convert characters to bytes to give to DoWriteConsole.size_t cbTextBufferLength;RETURN_IF_FAILED(SizeTMult(buffer.size(), sizeof(wchar_t), &cbTextBufferLength));NTSTATUS Status = DoWriteConsole(const_cast<wchar_t*>(buffer.data()), &cbTextBufferLength, context, waiter);// Convert back from bytes to characters for the resulting string length written.read = cbTextBufferLength / sizeof(wchar_t);if (Status == CONSOLE_STATUS_WAIT){FAIL_FAST_IF_NULL(waiter.get());Status = STATUS_SUCCESS;}RETURN_NTSTATUS(Status);}CATCH_RETURN();}// Routine Description:// - Writes non-Unicode formatted data into the given console output object.// - This method will convert from the given input into wide characters before chain calling the wide character version of the function.// It uses the current Output Codepage for conversions (set via SetConsoleOutputCP).// - NOTE: This may be blocked for various console states and will return a wait context pointer if necessary.// Arguments:// - context - the console output object to write the new text into// - buffer - char/byte text buffer provided by client application to insert// - read - character count of the number of characters (also bytes because A version) we were able to insert before returning// - waiter - If we are blocked from writing now and need to wait, this is filled with contextual data for the server to restore the call later// Return Value:// - S_OK if successful.// - S_OK if we need to wait (check if ppWaiter is not nullptr).// - Or a suitable HRESULT code for math/string/memory failures.[[nodiscard]]HRESULT ApiRoutines::WriteConsoleAImpl(IConsoleOutputObject& context,const std::string_view buffer,size_t& read,std::unique_ptr<IWaitRoutine>& waiter) noexcept{try{const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();// Ensure output variables are initialized.read = 0;waiter.reset();bool fLeadByteCaptured = false;bool fLeadByteConsumed = false;LockConsole();auto Unlock = wil::scope_exit([&] { UnlockConsole(); });if (buffer.size() == 0){return S_OK;}const auto codepage = gci.OutputCP;// Convert our input parameters to Unicodestd::unique_ptr<wchar_t[]> wideCharBuffer{ nullptr };static Utf8ToWideCharParser parser{ gci.OutputCP };// update current codepage in case it was changed from last time// this was called. We do this outside the UTF-8 check because the parser drops its state// when the codepage changes.parser.SetCodePage(gci.OutputCP);SCREEN_INFORMATION& ScreenInfo = context.GetActiveBuffer();wchar_t* pwchBuffer;size_t cchBuffer;if (codepage == CP_UTF8){wideCharBuffer.release();unsigned int charCount;unsigned int charsConsumed;unsigned int charsGenerated;RETURN_IF_FAILED(SizeTToUInt(buffer.size(), &charCount));RETURN_IF_FAILED(parser.Parse(reinterpret_cast<const byte*>(buffer.data()),charCount,charsConsumed,wideCharBuffer,charsGenerated));pwchBuffer = reinterpret_cast<wchar_t*>(wideCharBuffer.get());cchBuffer = charsGenerated;read = charsConsumed;}else{NTSTATUS Status = STATUS_SUCCESS;PWCHAR TransBuffer;PWCHAR TransBufferOriginalLocation;DWORD Length;ULONG dbcsNumBytes = 0;ULONG BufPtrNumBytes = 0;const char* BufPtr = buffer.data();// (cchTextBufferLength + 2) I think because we might be shoving another unicode char// from ScreenInfo->WriteConsoleDbcsLeadByte in frontTransBuffer = new WCHAR[buffer.size() + 2];RETURN_IF_NULL_ALLOC(TransBuffer);ZeroMemory(TransBuffer, sizeof(WCHAR) * (buffer.size() + 2));TransBufferOriginalLocation = TransBuffer;unsigned int uiTextBufferLength;RETURN_IF_FAILED(SizeTToUInt(buffer.size(), &uiTextBufferLength));if (!ScreenInfo.WriteConsoleDbcsLeadByte[0] || *(PUCHAR)BufPtr < (UCHAR) ' '){dbcsNumBytes = 0;BufPtrNumBytes = uiTextBufferLength;}else if (buffer.size()){// there was a portion of a dbcs character stored from a previous// call so we take the 2nd half from BufPtr[0], put them together// and write the wide char to TransBuffer[0]ScreenInfo.WriteConsoleDbcsLeadByte[1] = *(PCHAR)BufPtr;try{const std::string_view leadByte(reinterpret_cast<const char* const>(ScreenInfo.WriteConsoleDbcsLeadByte),ARRAYSIZE(ScreenInfo.WriteConsoleDbcsLeadByte));const std::wstring converted = ConvertToW(gci.OutputCP, leadByte);FAIL_FAST_IF(converted.size() != 1);dbcsNumBytes = sizeof(wchar_t);TransBuffer[0] = converted.at(0);BufPtr++;}catch (...){Status = STATUS_UNSUCCESSFUL;dbcsNumBytes = 0;}// this looks weird to be always incrementing even if the conversion failed, but this is the// original behavior so it's left unchanged.TransBuffer++;BufPtrNumBytes = uiTextBufferLength - 1;// Note that we used a stored lead byte from a previous call in order to complete this write// Use this to offset the "number of bytes consumed" calculation at the end by -1 to account// for using a byte we had internally, not off the stream.fLeadByteConsumed = true;}else{// nothing in ScreenInfo->WriteConsoleDbcsLeadByte and nothing in BufPtrBufPtrNumBytes = 0;}ScreenInfo.WriteConsoleDbcsLeadByte[0] = 0;// if the last byte in BufPtr is a lead byte for the current code page,// save it for the next time this function is called and we can piece it// back together then__analysis_assume(BufPtrNumBytes <= uiTextBufferLength);if (BufPtrNumBytes && CheckBisectStringA((PCHAR)BufPtr, BufPtrNumBytes, &gci.OutputCPInfo)){ScreenInfo.WriteConsoleDbcsLeadByte[0] = *((PCHAR)BufPtr + BufPtrNumBytes - 1);BufPtrNumBytes--;// Note that we captured a lead byte during this call, but won't actually draw it until later.// Use this to offset the "number of bytes consumed" calculation at the end by +1 to account// for taking a byte off the stream.fLeadByteCaptured = true;}if (BufPtrNumBytes != 0){// convert the remaining bytes in BufPtr to wide charsLength = sizeof(WCHAR) * MultiByteToWideChar(gci.OutputCP,0,(LPCCH)BufPtr,BufPtrNumBytes,TransBuffer,BufPtrNumBytes);if (Length == 0){Status = STATUS_UNSUCCESSFUL;}BufPtrNumBytes = Length;}pwchBuffer = TransBufferOriginalLocation;cchBuffer = (dbcsNumBytes + BufPtrNumBytes) / sizeof(wchar_t);}// Make the W version of the callsize_t cchBufferRead;// Hold the specific version of the waiter locally so we can tinker with it if we must to store additional context.std::unique_ptr<WriteData> writeDataWaiter;HRESULT const hr = WriteConsoleWImplHelper(ScreenInfo, { pwchBuffer, cchBuffer }, cchBufferRead, writeDataWaiter);// If there is no waiter, process the byte count now.if (nullptr == writeDataWaiter.get()){// Calculate how many bytes of the original A buffer were consumed in the W version of the call to satisfy pcchTextBufferRead.// For UTF-8 conversions, we've already returned this information above.if (CP_UTF8 != codepage){size_t cchTextBufferRead = 0;// Start by counting the number of A bytes we used in printing our W string to the screen.try{cchTextBufferRead = GetALengthFromW(codepage, { pwchBuffer, cchBufferRead });}CATCH_LOG();// If we captured a byte off the string this time around up above, it means we didn't feed// it into the WriteConsoleW above, and therefore its consumption isn't accounted for// in the count we just made. Add +1 to compensate.if (fLeadByteCaptured){cchTextBufferRead++;}// If we consumed an internally-stored lead byte this time around up above, it means that we// fed a byte into WriteConsoleW that wasn't a part of this particular call's request.// We need to -1 to compensate and tell the caller the right number of bytes consumed this request.if (fLeadByteConsumed){cchTextBufferRead--;}read = cchTextBufferRead;}}else{// If there is a waiter, then we need to stow some additional information in the wait structure so// we can synthesize the correct byte count later when the wait routine is triggered.if (CP_UTF8 != codepage){// For non-UTF8 codepages, save the lead byte captured/consumed data so we can +1 or -1 the final decoded count// in the WaitData::Notify method later.writeDataWaiter->SetLeadByteAdjustmentStatus(fLeadByteCaptured, fLeadByteConsumed);}else{// For UTF8 codepages, just remember the consumption count from the UTF-8 parser.writeDataWaiter->SetUtf8ConsumedCharacters(read);}}// Free remaining dataif (codepage != CP_UTF8){delete[] pwchBuffer;}// Give back the waiter now that we're done with tinkering with it.waiter.reset(writeDataWaiter.release());return hr;}CATCH_RETURN();}// Routine Description:// - Writes Unicode formatted data into the given console output object.// - NOTE: This may be blocked for various console states and will return a wait context pointer if necessary.// Arguments:// - OutContext - the console output object to write the new text into// - pwsTextBuffer - wide character text buffer provided by client application to insert// - cchTextBufferLength - text buffer counted in characters// - pcchTextBufferRead - character count of the number of characters we were able to insert before returning// - ppWaiter - If we are blocked from writing now and need to wait, this is filled with contextual data for the server to restore the call later// Return Value:// - S_OK if successful.// - S_OK if we need to wait (check if ppWaiter is not nullptr).// - Or a suitable HRESULT code for math/string/memory failures.[[nodiscard]]HRESULT ApiRoutines::WriteConsoleWImpl(IConsoleOutputObject& context,const std::wstring_view buffer,size_t& read,std::unique_ptr<IWaitRoutine>& waiter) noexcept{try{LockConsole();auto Unlock = wil::scope_exit([&] { UnlockConsole(); });std::unique_ptr<WriteData> writeDataWaiter;RETURN_IF_FAILED(WriteConsoleWImplHelper(context.GetActiveBuffer(), buffer, read, writeDataWaiter));// Transfer specific waiter pointer into the generic interface wrapper.waiter.reset(writeDataWaiter.release());return S_OK;}CATCH_RETURN();}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。