// 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(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(), TextAttribute{}, 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(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 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 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) { const auto legacyAttributes = it->TextAttr().GetLegacyAttributes(); // 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(legacyAttributes); } else { retVal.push_back(legacyAttributes | 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(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 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 == TextAttribute{ 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); // If we're scrolling an area that encompasses the full buffer width, // then the filled rows should also have their line rendition reset. if (view.Width() == buffer.Width() && destinationOriginGiven.X == 0) { screenInfo.GetTextBuffer().ResetLineRenditionRange(view.Top(), view.BottomExclusive()); } } } 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(); }); }