2253 lines
89 KiB
C++
2253 lines
89 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "precomp.h"
|
|
|
|
#include "getset.h"
|
|
|
|
#include "_output.h"
|
|
#include "_stream.h"
|
|
#include "output.h"
|
|
#include "dbcs.h"
|
|
#include "handle.h"
|
|
#include "misc.h"
|
|
#include "cmdline.h"
|
|
|
|
#include "../types/inc/convert.hpp"
|
|
#include "../types/inc/viewport.hpp"
|
|
|
|
#include "ApiRoutines.h"
|
|
|
|
#include "../interactivity/inc/ServiceLocator.hpp"
|
|
|
|
#pragma hdrstop
|
|
|
|
// The following mask is used to test for valid text attributes.
|
|
#define VALID_TEXT_ATTRIBUTES (FG_ATTRS | BG_ATTRS | META_ATTRS)
|
|
|
|
#define INPUT_MODES (ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT | ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT | ENABLE_VIRTUAL_TERMINAL_INPUT)
|
|
#define OUTPUT_MODES (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN | ENABLE_LVB_GRID_WORLDWIDE)
|
|
#define PRIVATE_MODES (ENABLE_INSERT_MODE | ENABLE_QUICK_EDIT_MODE | ENABLE_AUTO_POSITION | ENABLE_EXTENDED_FLAGS)
|
|
|
|
using namespace Microsoft::Console::Types;
|
|
using namespace Microsoft::Console::Interactivity;
|
|
|
|
// Routine Description:
|
|
// - Retrieves the console input mode (settings that apply when manipulating the input buffer)
|
|
// Arguments:
|
|
// - context - The input buffer concerned
|
|
// - mode - Receives the mode flags set
|
|
void ApiRoutines::GetConsoleInputModeImpl(InputBuffer& context, ULONG& mode) noexcept
|
|
{
|
|
try
|
|
{
|
|
Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleMode);
|
|
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
mode = context.InputMode;
|
|
|
|
if (WI_IsFlagSet(gci.Flags, CONSOLE_USE_PRIVATE_FLAGS))
|
|
{
|
|
WI_SetFlag(mode, ENABLE_EXTENDED_FLAGS);
|
|
WI_SetFlagIf(mode, ENABLE_INSERT_MODE, gci.GetInsertMode());
|
|
WI_SetFlagIf(mode, ENABLE_QUICK_EDIT_MODE, WI_IsFlagSet(gci.Flags, CONSOLE_QUICK_EDIT_MODE));
|
|
WI_SetFlagIf(mode, ENABLE_AUTO_POSITION, WI_IsFlagSet(gci.Flags, CONSOLE_AUTO_POSITION));
|
|
}
|
|
}
|
|
CATCH_LOG();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Retrieves the console output mode (settings that apply when manipulating the output buffer)
|
|
// Arguments:
|
|
// - context - The output buffer concerned
|
|
// - mode - Receives the mode flags set
|
|
void ApiRoutines::GetConsoleOutputModeImpl(SCREEN_INFORMATION& context, ULONG& mode) noexcept
|
|
{
|
|
try
|
|
{
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
mode = context.GetActiveBuffer().OutputMode;
|
|
}
|
|
CATCH_LOG();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Retrieves the number of console event items in the input queue right now
|
|
// Arguments:
|
|
// - context - The input buffer concerned
|
|
// - event - The count of events in the queue
|
|
// Return Value:
|
|
// - S_OK or math failure.
|
|
[[nodiscard]] HRESULT ApiRoutines::GetNumberOfConsoleInputEventsImpl(const InputBuffer& context, ULONG& events) noexcept
|
|
{
|
|
try
|
|
{
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
const auto readyEventCount = context.GetNumberOfReadyEvents();
|
|
RETURN_IF_FAILED(SizeTToULong(readyEventCount, &events));
|
|
|
|
return S_OK;
|
|
}
|
|
CATCH_RETURN();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Retrieves metadata associated with the output buffer (size, default colors, etc.)
|
|
// Arguments:
|
|
// - context - The output buffer concerned
|
|
// - data - Receives structure filled with metadata about the output buffer
|
|
void ApiRoutines::GetConsoleScreenBufferInfoExImpl(const SCREEN_INFORMATION& context,
|
|
CONSOLE_SCREEN_BUFFER_INFOEX& data) noexcept
|
|
{
|
|
try
|
|
{
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
data.bFullscreenSupported = FALSE; // traditional full screen with the driver support is no longer supported.
|
|
// see MSFT: 19918103
|
|
// Make sure to use the active buffer here. There are clients that will
|
|
// use WINDOW_SIZE_EVENTs as a signal to then query the console
|
|
// with GetConsoleScreenBufferInfoEx to get the actual viewport
|
|
// size.
|
|
// If they're in the alt buffer, then when they query in that way, the
|
|
// value they'll get is the main buffer's size, which isn't updated
|
|
// until we switch back to it.
|
|
context.GetActiveBuffer().GetScreenBufferInformation(&data.dwSize,
|
|
&data.dwCursorPosition,
|
|
&data.srWindow,
|
|
&data.wAttributes,
|
|
&data.dwMaximumWindowSize,
|
|
&data.wPopupAttributes,
|
|
data.ColorTable);
|
|
|
|
// Callers of this function expect to receive an exclusive rect, not an
|
|
// inclusive one. The driver will mangle this value for us
|
|
// - For GetConsoleScreenBufferInfoEx, it will re-decrement these values
|
|
// to return an inclusive rect.
|
|
// - For GetConsoleScreenBufferInfo, it will leave these values
|
|
// untouched, returning an exclusive rect.
|
|
data.srWindow.Right += 1;
|
|
data.srWindow.Bottom += 1;
|
|
}
|
|
CATCH_LOG();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Retrieves information about the console cursor's display state
|
|
// Arguments:
|
|
// - context - The output buffer concerned
|
|
// - size - The size as a percentage of the total possible height (0-100 for percentages).
|
|
// - isVisible - Whether the cursor is displayed or hidden
|
|
void ApiRoutines::GetConsoleCursorInfoImpl(const SCREEN_INFORMATION& context,
|
|
ULONG& size,
|
|
bool& isVisible) noexcept
|
|
{
|
|
try
|
|
{
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
size = context.GetActiveBuffer().GetTextBuffer().GetCursor().GetSize();
|
|
isVisible = context.GetTextBuffer().GetCursor().IsVisible();
|
|
}
|
|
CATCH_LOG();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Retrieves information about the selected area in the console
|
|
// Arguments:
|
|
// - consoleSelectionInfo - contains flags, anchors, and area to describe selection area
|
|
void ApiRoutines::GetConsoleSelectionInfoImpl(CONSOLE_SELECTION_INFO& consoleSelectionInfo) noexcept
|
|
{
|
|
try
|
|
{
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
const auto& selection = Selection::Instance();
|
|
if (selection.IsInSelectingState())
|
|
{
|
|
consoleSelectionInfo.dwFlags = selection.GetPublicSelectionFlags();
|
|
|
|
WI_SetFlag(consoleSelectionInfo.dwFlags, CONSOLE_SELECTION_IN_PROGRESS);
|
|
|
|
consoleSelectionInfo.dwSelectionAnchor = selection.GetSelectionAnchor();
|
|
consoleSelectionInfo.srSelection = selection.GetSelectionRectangle();
|
|
}
|
|
else
|
|
{
|
|
ZeroMemory(&consoleSelectionInfo, sizeof(consoleSelectionInfo));
|
|
}
|
|
}
|
|
CATCH_LOG();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Retrieves the number of buttons on the mouse as reported by the system
|
|
// Arguments:
|
|
// - buttons - Count of buttons
|
|
void ApiRoutines::GetNumberOfConsoleMouseButtonsImpl(ULONG& buttons) noexcept
|
|
{
|
|
try
|
|
{
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
buttons = ServiceLocator::LocateSystemConfigurationProvider()->GetNumberOfMouseButtons();
|
|
}
|
|
CATCH_LOG();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Retrieves information about a known font based on index
|
|
// Arguments:
|
|
// - context - The output buffer concerned
|
|
// - index - We only accept 0 now as we don't keep a list of fonts in memory.
|
|
// - size - The X by Y pixel size of the font
|
|
// Return Value:
|
|
// - S_OK, E_INVALIDARG or code from thrown exception
|
|
[[nodiscard]] HRESULT ApiRoutines::GetConsoleFontSizeImpl(const SCREEN_INFORMATION& context,
|
|
const DWORD index,
|
|
COORD& size) noexcept
|
|
{
|
|
try
|
|
{
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
if (index == 0)
|
|
{
|
|
// As of the November 2015 renderer system, we only have a single font at index 0.
|
|
size = context.GetActiveBuffer().GetCurrentFont().GetUnscaledSize();
|
|
return S_OK;
|
|
}
|
|
else
|
|
{
|
|
// Invalid font is 0,0 with STATUS_INVALID_PARAMETER
|
|
size = { 0 };
|
|
return E_INVALIDARG;
|
|
}
|
|
}
|
|
CATCH_RETURN();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Retrieves information about the console cursor's display state
|
|
// Arguments:
|
|
// - context - The output buffer concerned
|
|
// - isForMaximumWindowSize - Returns the maximum number of characters in the largest window size if true. Otherwise, it's the size of the font.
|
|
// - consoleFontInfoEx - structure containing font information like size, family, weight, etc.
|
|
// Return Value:
|
|
// - S_OK, string copy failure code or code from thrown exception
|
|
[[nodiscard]] HRESULT ApiRoutines::GetCurrentConsoleFontExImpl(const SCREEN_INFORMATION& context,
|
|
const bool isForMaximumWindowSize,
|
|
CONSOLE_FONT_INFOEX& consoleFontInfoEx) noexcept
|
|
{
|
|
try
|
|
{
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
const SCREEN_INFORMATION& activeScreenInfo = context.GetActiveBuffer();
|
|
|
|
COORD WindowSize;
|
|
if (isForMaximumWindowSize)
|
|
{
|
|
WindowSize = activeScreenInfo.GetMaxWindowSizeInCharacters();
|
|
}
|
|
else
|
|
{
|
|
WindowSize = activeScreenInfo.GetCurrentFont().GetUnscaledSize();
|
|
}
|
|
consoleFontInfoEx.dwFontSize = WindowSize;
|
|
|
|
consoleFontInfoEx.nFont = 0;
|
|
|
|
const FontInfo& fontInfo = activeScreenInfo.GetCurrentFont();
|
|
consoleFontInfoEx.FontFamily = fontInfo.GetFamily();
|
|
consoleFontInfoEx.FontWeight = fontInfo.GetWeight();
|
|
|
|
RETURN_IF_FAILED(fontInfo.FillLegacyNameBuffer(gsl::make_span(consoleFontInfoEx.FaceName)));
|
|
|
|
return S_OK;
|
|
}
|
|
CATCH_RETURN();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Sets the current font to be used for drawing
|
|
// Arguments:
|
|
// - context - The output buffer concerned
|
|
// - isForMaximumWindowSize - Obsolete.
|
|
// - consoleFontInfoEx - structure containing font information like size, family, weight, etc.
|
|
// Return Value:
|
|
// - S_OK, string copy failure code or code from thrown exception
|
|
[[nodiscard]] HRESULT ApiRoutines::SetCurrentConsoleFontExImpl(IConsoleOutputObject& context,
|
|
const bool /*isForMaximumWindowSize*/,
|
|
const CONSOLE_FONT_INFOEX& consoleFontInfoEx) noexcept
|
|
{
|
|
try
|
|
{
|
|
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
SCREEN_INFORMATION& activeScreenInfo = context.GetActiveBuffer();
|
|
|
|
WCHAR FaceName[ARRAYSIZE(consoleFontInfoEx.FaceName)];
|
|
RETURN_IF_FAILED(StringCchCopyW(FaceName, ARRAYSIZE(FaceName), consoleFontInfoEx.FaceName));
|
|
|
|
FontInfo fi(FaceName,
|
|
gsl::narrow_cast<unsigned char>(consoleFontInfoEx.FontFamily),
|
|
consoleFontInfoEx.FontWeight,
|
|
consoleFontInfoEx.dwFontSize,
|
|
gci.OutputCP);
|
|
|
|
// TODO: MSFT: 9574827 - should this have a failure case?
|
|
activeScreenInfo.UpdateFont(&fi);
|
|
|
|
return S_OK;
|
|
}
|
|
CATCH_RETURN();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Sets the input mode for the console
|
|
// Arguments:
|
|
// - context - The input buffer concerned
|
|
// - mode - flags that change behavior of the buffer
|
|
// Return Value:
|
|
// - S_OK, E_INVALIDARG, or failure code from thrown exception
|
|
[[nodiscard]] HRESULT ApiRoutines::SetConsoleInputModeImpl(InputBuffer& context, const ULONG mode) noexcept
|
|
{
|
|
try
|
|
{
|
|
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
if (WI_IsAnyFlagSet(mode, PRIVATE_MODES))
|
|
{
|
|
WI_SetFlag(gci.Flags, CONSOLE_USE_PRIVATE_FLAGS);
|
|
|
|
WI_UpdateFlag(gci.Flags, CONSOLE_QUICK_EDIT_MODE, WI_IsFlagSet(mode, ENABLE_QUICK_EDIT_MODE));
|
|
WI_UpdateFlag(gci.Flags, CONSOLE_AUTO_POSITION, WI_IsFlagSet(mode, ENABLE_AUTO_POSITION));
|
|
|
|
const bool PreviousInsertMode = gci.GetInsertMode();
|
|
gci.SetInsertMode(WI_IsFlagSet(mode, ENABLE_INSERT_MODE));
|
|
if (gci.GetInsertMode() != PreviousInsertMode)
|
|
{
|
|
gci.GetActiveOutputBuffer().SetCursorDBMode(false);
|
|
if (gci.HasPendingCookedRead())
|
|
{
|
|
gci.CookedReadData().SetInsertMode(gci.GetInsertMode());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WI_ClearFlag(gci.Flags, CONSOLE_USE_PRIVATE_FLAGS);
|
|
}
|
|
|
|
context.InputMode = mode;
|
|
WI_ClearAllFlags(context.InputMode, PRIVATE_MODES);
|
|
|
|
// NOTE: For compatibility reasons, we need to set the modes and then return the error codes, not the other way around
|
|
// as might be expected.
|
|
// This is a bug from a long time ago and some applications depend on this functionality to operate properly.
|
|
// ---
|
|
// A prime example of this is that PSReadline module in Powershell will set the invalid mode 0x1e4
|
|
// which includes 0x4 for ECHO_INPUT but turns off 0x2 for LINE_INPUT. This is invalid, but PSReadline
|
|
// relies on it to properly receive the ^C printout and make a new line when the user presses Ctrl+C.
|
|
{
|
|
// Flags we don't understand are invalid.
|
|
RETURN_HR_IF(E_INVALIDARG, WI_IsAnyFlagSet(mode, ~(INPUT_MODES | PRIVATE_MODES)));
|
|
|
|
// ECHO on with LINE off is invalid.
|
|
RETURN_HR_IF(E_INVALIDARG, WI_IsFlagSet(mode, ENABLE_ECHO_INPUT) && WI_IsFlagClear(mode, ENABLE_LINE_INPUT));
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
CATCH_RETURN();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Sets the output mode for the console
|
|
// Arguments:
|
|
// - context - The output buffer concerned
|
|
// - mode - flags that change behavior of the buffer
|
|
// Return Value:
|
|
// - S_OK, E_INVALIDARG, or failure code from thrown exception
|
|
[[nodiscard]] HRESULT ApiRoutines::SetConsoleOutputModeImpl(SCREEN_INFORMATION& context, const ULONG mode) noexcept
|
|
{
|
|
try
|
|
{
|
|
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
// Flags we don't understand are invalid.
|
|
RETURN_HR_IF(E_INVALIDARG, WI_IsAnyFlagSet(mode, ~OUTPUT_MODES));
|
|
|
|
SCREEN_INFORMATION& screenInfo = context.GetActiveBuffer();
|
|
const DWORD dwOldMode = screenInfo.OutputMode;
|
|
const DWORD dwNewMode = mode;
|
|
|
|
screenInfo.OutputMode = dwNewMode;
|
|
|
|
// if we're moving from VT on->off
|
|
if (WI_IsFlagClear(dwNewMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING) &&
|
|
WI_IsFlagSet(dwOldMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING))
|
|
{
|
|
// jiggle the handle
|
|
screenInfo.GetStateMachine().ResetState();
|
|
}
|
|
|
|
gci.SetVirtTermLevel(WI_IsFlagSet(dwNewMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING) ? 1 : 0);
|
|
gci.SetAutomaticReturnOnNewline(WI_IsFlagSet(screenInfo.OutputMode, DISABLE_NEWLINE_AUTO_RETURN) ? false : true);
|
|
gci.SetGridRenderingAllowedWorldwide(WI_IsFlagSet(screenInfo.OutputMode, ENABLE_LVB_GRID_WORLDWIDE));
|
|
|
|
// if we changed rendering modes then redraw the output buffer,
|
|
// but only do this if we're not in conpty mode.
|
|
if (!gci.IsInVtIoMode() &&
|
|
(WI_IsFlagSet(dwNewMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING) != WI_IsFlagSet(dwOldMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING) ||
|
|
WI_IsFlagSet(dwNewMode, ENABLE_LVB_GRID_WORLDWIDE) != WI_IsFlagSet(dwOldMode, ENABLE_LVB_GRID_WORLDWIDE)))
|
|
{
|
|
auto* pRender = ServiceLocator::LocateGlobals().pRender;
|
|
if (pRender)
|
|
{
|
|
pRender->TriggerRedrawAll();
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
CATCH_RETURN();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Sets the given output buffer as the active one
|
|
// Arguments:
|
|
// - context - The output buffer concerned
|
|
void ApiRoutines::SetConsoleActiveScreenBufferImpl(SCREEN_INFORMATION& newContext) noexcept
|
|
{
|
|
try
|
|
{
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
SetActiveScreenBuffer(newContext.GetActiveBuffer());
|
|
}
|
|
CATCH_LOG();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Clears all items out of the input buffer queue
|
|
// Arguments:
|
|
// - context - The input buffer concerned
|
|
void ApiRoutines::FlushConsoleInputBuffer(InputBuffer& context) noexcept
|
|
{
|
|
try
|
|
{
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
context.Flush();
|
|
}
|
|
CATCH_LOG();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Gets the largest possible window size in characters.
|
|
// Arguments:
|
|
// - context - The output buffer concerned
|
|
// - size - receives the size in character count (rows/columns)
|
|
void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& context,
|
|
COORD& size) noexcept
|
|
{
|
|
try
|
|
{
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
const SCREEN_INFORMATION& screenInfo = context.GetActiveBuffer();
|
|
|
|
size = screenInfo.GetLargestWindowSizeInCharacters();
|
|
}
|
|
CATCH_LOG();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Sets the size of the output buffer (screen buffer) in rows/columns
|
|
// Arguments:
|
|
// - context - The output buffer concerned
|
|
// - size - size in character rows and columns
|
|
// Return Value:
|
|
// - S_OK, E_INVALIDARG, or failure code from thrown exception
|
|
[[nodiscard]] HRESULT ApiRoutines::SetConsoleScreenBufferSizeImpl(SCREEN_INFORMATION& context,
|
|
const COORD size) noexcept
|
|
{
|
|
try
|
|
{
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
SCREEN_INFORMATION& screenInfo = context.GetActiveBuffer();
|
|
|
|
// microsoft/terminal#3907 - We shouldn't resize the buffer to be
|
|
// smaller than the viewport. This was previously erroneously checked
|
|
// when the host was not in conpty mode.
|
|
RETURN_HR_IF(E_INVALIDARG, (size.X < screenInfo.GetViewport().Width() || size.Y < screenInfo.GetViewport().Height()));
|
|
|
|
// see MSFT:17415266
|
|
// We only really care about the minimum window size if we have a head.
|
|
if (!ServiceLocator::LocateGlobals().IsHeadless())
|
|
{
|
|
COORD const coordMin = screenInfo.GetMinWindowSizeInCharacters();
|
|
// Make sure requested screen buffer size isn't smaller than the window.
|
|
RETURN_HR_IF(E_INVALIDARG, (size.Y < coordMin.Y || size.X < coordMin.X));
|
|
}
|
|
|
|
// Ensure the requested size isn't larger than we can handle in our data type.
|
|
RETURN_HR_IF(E_INVALIDARG, (size.X == SHORT_MAX || size.Y == SHORT_MAX));
|
|
|
|
// Only do the resize if we're actually changing one of the dimensions
|
|
COORD const coordScreenBufferSize = screenInfo.GetBufferSize().Dimensions();
|
|
if (size.X != coordScreenBufferSize.X || size.Y != coordScreenBufferSize.Y)
|
|
{
|
|
RETURN_IF_NTSTATUS_FAILED(screenInfo.ResizeScreenBuffer(size, TRUE));
|
|
}
|
|
|
|
// Make sure the viewport doesn't now overflow the buffer dimensions.
|
|
auto overflow = screenInfo.GetViewport().EndExclusive() - screenInfo.GetBufferSize().Dimensions();
|
|
if (overflow.X > 0 || overflow.Y > 0)
|
|
{
|
|
overflow = { std::max<SHORT>(overflow.X, 0), std::max<SHORT>(overflow.Y, 0) };
|
|
RETURN_IF_NTSTATUS_FAILED(screenInfo.SetViewportOrigin(false, -overflow, false));
|
|
}
|
|
|
|
// And also that the cursor position is clamped within the buffer boundaries.
|
|
auto& cursor = screenInfo.GetTextBuffer().GetCursor();
|
|
auto clampedCursorPosition = cursor.GetPosition();
|
|
screenInfo.GetBufferSize().Clamp(clampedCursorPosition);
|
|
if (clampedCursorPosition != cursor.GetPosition())
|
|
{
|
|
cursor.SetPosition(clampedCursorPosition);
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
CATCH_RETURN();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Sets metadata information on the output buffer
|
|
// Arguments:
|
|
// - context - The output buffer concerned
|
|
// - data - metadata information structure like buffer size, viewport size, colors, and more.
|
|
// Return Value:
|
|
// - S_OK, E_INVALIDARG, or failure code from thrown exception
|
|
[[nodiscard]] HRESULT ApiRoutines::SetConsoleScreenBufferInfoExImpl(SCREEN_INFORMATION& context,
|
|
const CONSOLE_SCREEN_BUFFER_INFOEX& data) noexcept
|
|
{
|
|
try
|
|
{
|
|
// clang-format off
|
|
RETURN_HR_IF(E_INVALIDARG, (data.dwSize.X == 0 ||
|
|
data.dwSize.Y == 0 ||
|
|
data.dwSize.X == SHRT_MAX ||
|
|
data.dwSize.Y == SHRT_MAX));
|
|
// clang-format on
|
|
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
Globals& g = ServiceLocator::LocateGlobals();
|
|
CONSOLE_INFORMATION& gci = g.getConsoleInformation();
|
|
|
|
const COORD coordScreenBufferSize = context.GetBufferSize().Dimensions();
|
|
const COORD requestedBufferSize = data.dwSize;
|
|
if (requestedBufferSize.X != coordScreenBufferSize.X ||
|
|
requestedBufferSize.Y != coordScreenBufferSize.Y)
|
|
{
|
|
CommandLine& commandLine = CommandLine::Instance();
|
|
|
|
commandLine.Hide(FALSE);
|
|
|
|
LOG_IF_FAILED(context.ResizeScreenBuffer(data.dwSize, TRUE));
|
|
|
|
commandLine.Show();
|
|
}
|
|
const COORD newBufferSize = context.GetBufferSize().Dimensions();
|
|
|
|
for (size_t i = 0; i < std::size(data.ColorTable); i++)
|
|
{
|
|
gci.SetColorTableEntry(i, data.ColorTable[i]);
|
|
}
|
|
|
|
context.SetDefaultAttributes(TextAttribute{ data.wAttributes }, TextAttribute{ data.wPopupAttributes });
|
|
|
|
const Viewport requestedViewport = Viewport::FromExclusive(data.srWindow);
|
|
|
|
COORD NewSize = requestedViewport.Dimensions();
|
|
// If we have a window, clamp the requested viewport to the max window size
|
|
if (!ServiceLocator::LocateGlobals().IsHeadless())
|
|
{
|
|
NewSize.X = std::min(NewSize.X, data.dwMaximumWindowSize.X);
|
|
NewSize.Y = std::min(NewSize.Y, data.dwMaximumWindowSize.Y);
|
|
}
|
|
|
|
// If wrap text is on, then the window width must be the same size as the buffer width
|
|
if (gci.GetWrapText())
|
|
{
|
|
NewSize.X = newBufferSize.X;
|
|
}
|
|
|
|
if (NewSize.X != context.GetViewport().Width() ||
|
|
NewSize.Y != context.GetViewport().Height())
|
|
{
|
|
// GH#1856 - make sure to hide the commandline _before_ we execute
|
|
// the resize, and the re-display it after the resize. If we leave
|
|
// it displayed, we'll crash during the resize when we try to figure
|
|
// out if the bounds of the old commandline fit within the new
|
|
// window (it might not).
|
|
CommandLine& commandLine = CommandLine::Instance();
|
|
commandLine.Hide(FALSE);
|
|
context.SetViewportSize(&NewSize);
|
|
commandLine.Show();
|
|
|
|
IConsoleWindow* const pWindow = ServiceLocator::LocateConsoleWindow();
|
|
if (pWindow != nullptr)
|
|
{
|
|
pWindow->UpdateWindowSize(NewSize);
|
|
}
|
|
}
|
|
|
|
// Despite the fact that this API takes in a srWindow for the viewport, it traditionally actually doesn't set
|
|
// anything using that member - for moving the viewport, you need SetConsoleWindowInfo
|
|
// (see https://msdn.microsoft.com/en-us/library/windows/desktop/ms686125(v=vs.85).aspx and DoSrvSetConsoleWindowInfo)
|
|
// Note that it also doesn't set cursor position.
|
|
|
|
// However, we do need to make sure the viewport doesn't now overflow the buffer dimensions.
|
|
auto overflow = context.GetViewport().EndExclusive() - context.GetBufferSize().Dimensions();
|
|
if (overflow.X > 0 || overflow.Y > 0)
|
|
{
|
|
overflow = { std::max<SHORT>(overflow.X, 0), std::max<SHORT>(overflow.Y, 0) };
|
|
RETURN_IF_NTSTATUS_FAILED(context.SetViewportOrigin(false, -overflow, false));
|
|
}
|
|
|
|
// And also that the cursor position is clamped within the buffer boundaries.
|
|
auto& cursor = context.GetTextBuffer().GetCursor();
|
|
auto clampedCursorPosition = cursor.GetPosition();
|
|
context.GetBufferSize().Clamp(clampedCursorPosition);
|
|
if (clampedCursorPosition != cursor.GetPosition())
|
|
{
|
|
cursor.SetPosition(clampedCursorPosition);
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
CATCH_RETURN();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Sets the cursor position in the given output buffer
|
|
// Arguments:
|
|
// - context - The output buffer concerned
|
|
// - position - The X/Y (row/column) position in the buffer to place the cursor
|
|
// Return Value:
|
|
// - S_OK, E_INVALIDARG, or failure code from thrown exception
|
|
[[nodiscard]] HRESULT ApiRoutines::SetConsoleCursorPositionImpl(SCREEN_INFORMATION& context,
|
|
const COORD position) noexcept
|
|
{
|
|
try
|
|
{
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
|
|
auto& buffer = context.GetActiveBuffer();
|
|
|
|
const COORD coordScreenBufferSize = buffer.GetBufferSize().Dimensions();
|
|
// clang-format off
|
|
RETURN_HR_IF(E_INVALIDARG, (position.X >= coordScreenBufferSize.X ||
|
|
position.Y >= coordScreenBufferSize.Y ||
|
|
position.X < 0 ||
|
|
position.Y < 0));
|
|
// clang-format on
|
|
|
|
// MSFT: 15813316 - Try to use this SetCursorPosition call to inherit the cursor position.
|
|
RETURN_IF_FAILED(gci.GetVtIo()->SetCursorPosition(position));
|
|
|
|
RETURN_IF_NTSTATUS_FAILED(buffer.SetCursorPosition(position, true));
|
|
|
|
LOG_IF_FAILED(ConsoleImeResizeCompStrView());
|
|
|
|
// Attempt to "snap" the viewport to the cursor position. If the cursor
|
|
// is not in the current viewport, we'll try and move the viewport so
|
|
// that the cursor is visible.
|
|
// microsoft/terminal#1222 - Use the "virtual" viewport here, so that
|
|
// when the console is in terminal-scrolling mode, the viewport snaps
|
|
// back to the virtual viewport's location.
|
|
const SMALL_RECT currentViewport = gci.IsTerminalScrolling() ?
|
|
buffer.GetVirtualViewport().ToInclusive() :
|
|
buffer.GetViewport().ToInclusive();
|
|
COORD delta{ 0 };
|
|
{
|
|
if (currentViewport.Left > position.X)
|
|
{
|
|
delta.X = position.X - currentViewport.Left;
|
|
}
|
|
else if (currentViewport.Right < position.X)
|
|
{
|
|
delta.X = position.X - currentViewport.Right;
|
|
}
|
|
|
|
if (currentViewport.Top > position.Y)
|
|
{
|
|
delta.Y = position.Y - currentViewport.Top;
|
|
}
|
|
else if (currentViewport.Bottom < position.Y)
|
|
{
|
|
delta.Y = position.Y - currentViewport.Bottom;
|
|
}
|
|
}
|
|
|
|
COORD newWindowOrigin{ 0 };
|
|
newWindowOrigin.X = currentViewport.Left + delta.X;
|
|
newWindowOrigin.Y = currentViewport.Top + delta.Y;
|
|
// SetViewportOrigin will worry about clamping these values to the
|
|
// buffer for us.
|
|
RETURN_IF_NTSTATUS_FAILED(buffer.SetViewportOrigin(true, newWindowOrigin, true));
|
|
|
|
return S_OK;
|
|
}
|
|
CATCH_RETURN();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Sets metadata on the cursor
|
|
// Arguments:
|
|
// - context - The output buffer concerned
|
|
// - size - Height percentage of the displayed cursor (when visible)
|
|
// - isVisible - Whether or not the cursor should be displayed
|
|
// Return Value:
|
|
// - S_OK, E_INVALIDARG, or failure code from thrown exception
|
|
[[nodiscard]] HRESULT ApiRoutines::SetConsoleCursorInfoImpl(SCREEN_INFORMATION& context,
|
|
const ULONG size,
|
|
const bool isVisible) noexcept
|
|
{
|
|
try
|
|
{
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
// If more than 100% or less than 0% cursor height, reject it.
|
|
RETURN_HR_IF(E_INVALIDARG, (size > 100 || size == 0));
|
|
|
|
context.SetCursorInformation(size, isVisible);
|
|
|
|
return S_OK;
|
|
}
|
|
CATCH_RETURN();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Sets the viewport/window information for displaying a portion of the output buffer visually
|
|
// Arguments:
|
|
// - context - The output buffer concerned
|
|
// - isAbsolute - Coordinates are based on the entire screen buffer (origin 0,0) if true.
|
|
// - If false, coordinates are a delta from the existing viewport position
|
|
// - windowRect - Updated viewport rectangle information
|
|
// Return Value:
|
|
// - S_OK, E_INVALIDARG, or failure code from thrown exception
|
|
[[nodiscard]] HRESULT ApiRoutines::SetConsoleWindowInfoImpl(SCREEN_INFORMATION& context,
|
|
const bool isAbsolute,
|
|
const SMALL_RECT& windowRect) noexcept
|
|
{
|
|
try
|
|
{
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
Globals& g = ServiceLocator::LocateGlobals();
|
|
SMALL_RECT Window = windowRect;
|
|
|
|
if (!isAbsolute)
|
|
{
|
|
SMALL_RECT currentViewport = context.GetViewport().ToInclusive();
|
|
Window.Left += currentViewport.Left;
|
|
Window.Right += currentViewport.Right;
|
|
Window.Top += currentViewport.Top;
|
|
Window.Bottom += currentViewport.Bottom;
|
|
}
|
|
|
|
RETURN_HR_IF(E_INVALIDARG, (Window.Right < Window.Left || Window.Bottom < Window.Top));
|
|
|
|
COORD NewWindowSize;
|
|
NewWindowSize.X = (SHORT)(CalcWindowSizeX(Window));
|
|
NewWindowSize.Y = (SHORT)(CalcWindowSizeY(Window));
|
|
|
|
// see MSFT:17415266
|
|
// If we have a actual head, we care about the maximum size the window can be.
|
|
// if we're headless, not so much. However, GetMaxWindowSizeInCharacters
|
|
// will only return the buffer size, so we can't use that to clip the arg here.
|
|
// So only clip the requested size if we're not headless
|
|
if (g.getConsoleInformation().IsInVtIoMode())
|
|
{
|
|
// SetViewportRect doesn't cause the buffer to resize. Manually resize the buffer.
|
|
RETURN_IF_NTSTATUS_FAILED(context.ResizeScreenBuffer(Viewport::FromInclusive(Window).Dimensions(), false));
|
|
}
|
|
if (!g.IsHeadless())
|
|
{
|
|
COORD const coordMax = context.GetMaxWindowSizeInCharacters();
|
|
RETURN_HR_IF(E_INVALIDARG, (NewWindowSize.X > coordMax.X || NewWindowSize.Y > coordMax.Y));
|
|
}
|
|
|
|
// Even if it's the same size, we need to post an update in case the scroll bars need to go away.
|
|
context.SetViewport(Viewport::FromInclusive(Window), true);
|
|
if (context.IsActiveScreenBuffer())
|
|
{
|
|
// TODO: MSFT: 9574827 - shouldn't we be looking at or at least logging the failure codes here? (Or making them non-void?)
|
|
context.PostUpdateWindowSize();
|
|
|
|
// Use WriteToScreen to invalidate the viewport with the renderer.
|
|
// GH#3490 - If we're in conpty mode, don't invalidate the entire
|
|
// viewport. In conpty mode, the VtEngine will later decide what
|
|
// part of the buffer actually needs to be re-sent to the terminal.
|
|
if (!(g.getConsoleInformation().IsInVtIoMode() && g.getConsoleInformation().GetVtIo()->IsResizeQuirkEnabled()))
|
|
{
|
|
WriteToScreen(context, context.GetViewport());
|
|
}
|
|
}
|
|
return S_OK;
|
|
}
|
|
CATCH_RETURN();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Moves a portion of text from one part of the output buffer to another
|
|
// Arguments:
|
|
// - context - The output buffer concerned
|
|
// - source - The rectangular region to copy from
|
|
// - target - The top left corner of the destination to paste the copy (source)
|
|
// - clip - The rectangle inside which all operations should be bounded (or no bounds if not given)
|
|
// - fillCharacter - Fills in the region left behind when the source is "lifted" out of its original location. The symbol to display.
|
|
// - fillAttribute - Fills in the region left behind when the source is "lifted" out of its original location. The color to use.
|
|
// Return Value:
|
|
// - S_OK, E_INVALIDARG, or failure code from thrown exception
|
|
[[nodiscard]] HRESULT ApiRoutines::ScrollConsoleScreenBufferAImpl(SCREEN_INFORMATION& context,
|
|
const SMALL_RECT& source,
|
|
const COORD target,
|
|
std::optional<SMALL_RECT> clip,
|
|
const char fillCharacter,
|
|
const WORD fillAttribute) noexcept
|
|
{
|
|
try
|
|
{
|
|
wchar_t const unicodeFillCharacter = CharToWchar(&fillCharacter, 1);
|
|
|
|
return ScrollConsoleScreenBufferWImpl(context, source, target, clip, unicodeFillCharacter, fillAttribute);
|
|
}
|
|
CATCH_RETURN();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Moves a portion of text from one part of the output buffer to another
|
|
// Arguments:
|
|
// - context - The output buffer concerned
|
|
// - source - The rectangular region to copy from
|
|
// - target - The top left corner of the destination to paste the copy (source)
|
|
// - clip - The rectangle inside which all operations should be bounded (or no bounds if not given)
|
|
// - fillCharacter - Fills in the region left behind when the source is "lifted" out of its original location. The symbol to display.
|
|
// - fillAttribute - Fills in the region left behind when the source is "lifted" out of its original location. The color to use.
|
|
// - enableCmdShim - true iff the client process that's calling this
|
|
// method is "cmd.exe". Used to enable certain compatibility shims for
|
|
// conpty mode. See GH#3126.
|
|
// Return Value:
|
|
// - S_OK, E_INVALIDARG, or failure code from thrown exception
|
|
[[nodiscard]] HRESULT ApiRoutines::ScrollConsoleScreenBufferWImpl(SCREEN_INFORMATION& context,
|
|
const SMALL_RECT& source,
|
|
const COORD target,
|
|
std::optional<SMALL_RECT> clip,
|
|
const wchar_t fillCharacter,
|
|
const WORD fillAttribute,
|
|
const bool enableCmdShim) noexcept
|
|
{
|
|
try
|
|
{
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
auto& buffer = context.GetActiveBuffer();
|
|
|
|
TextAttribute useThisAttr(fillAttribute);
|
|
ScrollRegion(buffer, source, clip, target, fillCharacter, useThisAttr);
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
// GH#3126 - This is a shim for cmd's `cls` function. In the
|
|
// legacy console, `cls` is supposed to clear the entire buffer. In
|
|
// conpty however, there's no difference between the viewport and the
|
|
// entirety of the buffer. We're going to see if this API call exactly
|
|
// matched the way we expect cmd to call it. If it does, then
|
|
// let's manually emit a ^[[3J to the connected terminal, so that their
|
|
// entire buffer will be cleared as well.
|
|
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
if (enableCmdShim && gci.IsInVtIoMode())
|
|
{
|
|
const auto currentBufferDimensions = buffer.GetBufferSize().Dimensions();
|
|
const bool sourceIsWholeBuffer = (source.Top == 0) &&
|
|
(source.Left == 0) &&
|
|
(source.Right == currentBufferDimensions.X) &&
|
|
(source.Bottom == currentBufferDimensions.Y);
|
|
const bool targetIsNegativeBufferHeight = (target.X == 0) &&
|
|
(target.Y == -currentBufferDimensions.Y);
|
|
const bool noClipProvided = clip == std::nullopt;
|
|
const bool fillIsBlank = (fillCharacter == UNICODE_SPACE) &&
|
|
(fillAttribute == buffer.GetAttributes().GetLegacyAttributes());
|
|
|
|
if (sourceIsWholeBuffer && targetIsNegativeBufferHeight && noClipProvided && fillIsBlank)
|
|
{
|
|
hr = gci.GetVtIo()->ManuallyClearScrollback();
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
CATCH_RETURN();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Adjusts the default color used for future text written to this output buffer
|
|
// Arguments:
|
|
// - context - The output buffer concerned
|
|
// - attribute - Color information
|
|
// Return Value:
|
|
// - S_OK, E_INVALIDARG, or failure code from thrown exception
|
|
[[nodiscard]] HRESULT ApiRoutines::SetConsoleTextAttributeImpl(SCREEN_INFORMATION& context,
|
|
const WORD attribute) noexcept
|
|
{
|
|
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
try
|
|
{
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
RETURN_HR_IF(E_INVALIDARG, WI_IsAnyFlagSet(attribute, ~VALID_TEXT_ATTRIBUTES));
|
|
|
|
const TextAttribute attr{ attribute };
|
|
context.SetAttributes(attr);
|
|
|
|
gci.ConsoleIme.RefreshAreaAttributes();
|
|
|
|
return S_OK;
|
|
}
|
|
CATCH_RETURN();
|
|
}
|
|
|
|
[[nodiscard]] HRESULT DoSrvSetConsoleOutputCodePage(const unsigned int codepage)
|
|
{
|
|
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
|
|
// Return if it's not known as a valid codepage ID.
|
|
RETURN_HR_IF(E_INVALIDARG, !(IsValidCodePage(codepage)));
|
|
|
|
// Do nothing if no change.
|
|
if (gci.OutputCP != codepage)
|
|
{
|
|
// Set new code page
|
|
gci.OutputCP = codepage;
|
|
|
|
SetConsoleCPInfo(TRUE);
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Sets the codepage used for translating text when calling A versions of functions affecting the output buffer.
|
|
// Arguments:
|
|
// - codepage - The codepage
|
|
// Return Value:
|
|
// - S_OK, E_INVALIDARG, or failure code from thrown exception
|
|
[[nodiscard]] HRESULT ApiRoutines::SetConsoleOutputCodePageImpl(const ULONG codepage) noexcept
|
|
{
|
|
try
|
|
{
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
return DoSrvSetConsoleOutputCodePage(codepage);
|
|
}
|
|
CATCH_RETURN();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Sets the codepage used for translating text when calling A versions of functions affecting the input buffer.
|
|
// Arguments:
|
|
// - codepage - The codepage
|
|
// Return Value:
|
|
// - S_OK, E_INVALIDARG, or failure code from thrown exception
|
|
[[nodiscard]] HRESULT ApiRoutines::SetConsoleInputCodePageImpl(const ULONG codepage) noexcept
|
|
{
|
|
try
|
|
{
|
|
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
// Return if it's not known as a valid codepage ID.
|
|
RETURN_HR_IF(E_INVALIDARG, !(IsValidCodePage(codepage)));
|
|
|
|
// Do nothing if no change.
|
|
if (gci.CP != codepage)
|
|
{
|
|
// Set new code page
|
|
gci.CP = codepage;
|
|
|
|
SetConsoleCPInfo(FALSE);
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
CATCH_RETURN();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Gets the codepage used for translating text when calling A versions of functions affecting the input buffer.
|
|
// Arguments:
|
|
// - codepage - The codepage
|
|
void ApiRoutines::GetConsoleInputCodePageImpl(ULONG& codepage) noexcept
|
|
{
|
|
try
|
|
{
|
|
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
codepage = gci.CP;
|
|
}
|
|
CATCH_LOG();
|
|
}
|
|
|
|
void DoSrvGetConsoleOutputCodePage(unsigned int& codepage)
|
|
{
|
|
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
codepage = gci.OutputCP;
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Gets the codepage used for translating text when calling A versions of functions affecting the output buffer.
|
|
// Arguments:
|
|
// - codepage - The codepage
|
|
void ApiRoutines::GetConsoleOutputCodePageImpl(ULONG& codepage) noexcept
|
|
{
|
|
try
|
|
{
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
unsigned int cp;
|
|
DoSrvGetConsoleOutputCodePage(cp);
|
|
codepage = cp;
|
|
}
|
|
CATCH_LOG();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Gets the window handle ID for the console
|
|
// Arguments:
|
|
// - hwnd - The window handle ID
|
|
void ApiRoutines::GetConsoleWindowImpl(HWND& hwnd) noexcept
|
|
{
|
|
try
|
|
{
|
|
// Set return to null before we do anything in case of failures/errors.
|
|
hwnd = nullptr;
|
|
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
const IConsoleWindow* pWindow = ServiceLocator::LocateConsoleWindow();
|
|
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
if (pWindow != nullptr)
|
|
{
|
|
hwnd = pWindow->GetWindowHandle();
|
|
}
|
|
else
|
|
{
|
|
// Some applications will fail silently if this API returns 0 (cygwin)
|
|
// If we're in pty mode, we need to return a fake window handle that
|
|
// doesn't actually do anything, but is a unique HWND to this
|
|
// console, so that they know that this console is in fact a real
|
|
// console window.
|
|
if (gci.IsInVtIoMode())
|
|
{
|
|
hwnd = ServiceLocator::LocatePseudoWindow();
|
|
}
|
|
}
|
|
}
|
|
CATCH_LOG();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Gets metadata about the storage of command history for cooked read modes
|
|
// Arguments:
|
|
// - consoleHistoryInformation - metadata pertaining to the number of history buffers and their size and modes.
|
|
void ApiRoutines::GetConsoleHistoryInfoImpl(CONSOLE_HISTORY_INFO& consoleHistoryInfo) noexcept
|
|
{
|
|
try
|
|
{
|
|
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
consoleHistoryInfo.HistoryBufferSize = gci.GetHistoryBufferSize();
|
|
consoleHistoryInfo.NumberOfHistoryBuffers = gci.GetNumberOfHistoryBuffers();
|
|
WI_SetFlagIf(consoleHistoryInfo.dwFlags, HISTORY_NO_DUP_FLAG, WI_IsFlagSet(gci.Flags, CONSOLE_HISTORY_NODUP));
|
|
}
|
|
CATCH_LOG();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Sets metadata about the storage of command history for cooked read modes
|
|
// Arguments:
|
|
// - consoleHistoryInformation - metadata pertaining to the number of history buffers and their size and modes.
|
|
// Return Value:
|
|
// - S_OK, E_INVALIDARG, or failure code from thrown exception
|
|
HRESULT ApiRoutines::SetConsoleHistoryInfoImpl(const CONSOLE_HISTORY_INFO& consoleHistoryInfo) noexcept
|
|
{
|
|
try
|
|
{
|
|
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
RETURN_HR_IF(E_INVALIDARG, consoleHistoryInfo.HistoryBufferSize > SHORT_MAX);
|
|
RETURN_HR_IF(E_INVALIDARG, consoleHistoryInfo.NumberOfHistoryBuffers > SHORT_MAX);
|
|
RETURN_HR_IF(E_INVALIDARG, WI_IsAnyFlagSet(consoleHistoryInfo.dwFlags, ~CHI_VALID_FLAGS));
|
|
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
CommandHistory::s_ResizeAll(consoleHistoryInfo.HistoryBufferSize);
|
|
gci.SetNumberOfHistoryBuffers(consoleHistoryInfo.NumberOfHistoryBuffers);
|
|
|
|
WI_UpdateFlag(gci.Flags, CONSOLE_HISTORY_NODUP, WI_IsFlagSet(consoleHistoryInfo.dwFlags, HISTORY_NO_DUP_FLAG));
|
|
|
|
return S_OK;
|
|
}
|
|
CATCH_RETURN();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Gets whether or not the console is full screen
|
|
// Arguments:
|
|
// - flags - Field contains full screen flag or doesn't.
|
|
// NOTE: This was in private.c, but turns out to be a public API: http://msdn.microsoft.com/en-us/library/windows/desktop/ms683164(v=vs.85).aspx
|
|
void ApiRoutines::GetConsoleDisplayModeImpl(ULONG& flags) noexcept
|
|
{
|
|
try
|
|
{
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
// Initialize flags portion of structure
|
|
flags = 0;
|
|
|
|
IConsoleWindow* const pWindow = ServiceLocator::LocateConsoleWindow();
|
|
if (pWindow != nullptr && pWindow->IsInFullscreen())
|
|
{
|
|
WI_SetFlag(flags, CONSOLE_FULLSCREEN_MODE);
|
|
}
|
|
}
|
|
CATCH_LOG();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - This routine sets the console display mode for an output buffer.
|
|
// - This API is only supported on x86 machines.
|
|
// Parameters:
|
|
// - context - Supplies a console output handle.
|
|
// - flags - Specifies the display mode. Options are:
|
|
// CONSOLE_FULLSCREEN_MODE - data is displayed fullscreen
|
|
// CONSOLE_WINDOWED_MODE - data is displayed in a window
|
|
// - newSize - On output, contains the new dimensions of the screen buffer. The dimensions are in rows and columns for textmode screen buffers.
|
|
// Return value:
|
|
// - TRUE - The operation was successful.
|
|
// - FALSE/nullptr - The operation failed. Extended error status is available using GetLastError.
|
|
// NOTE:
|
|
// - This was in private.c, but turns out to be a public API:
|
|
// - See: http://msdn.microsoft.com/en-us/library/windows/desktop/ms686028(v=vs.85).aspx
|
|
[[nodiscard]] HRESULT ApiRoutines::SetConsoleDisplayModeImpl(SCREEN_INFORMATION& context,
|
|
const ULONG flags,
|
|
COORD& newSize) noexcept
|
|
{
|
|
try
|
|
{
|
|
// SetIsFullscreen() below ultimately calls SetwindowLong, which ultimately calls SendMessage(). If we retain
|
|
// the console lock, we'll deadlock since ConsoleWindowProc takes the lock before processing messages. Instead,
|
|
// we'll release early.
|
|
LockConsole();
|
|
{
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
SCREEN_INFORMATION& screenInfo = context.GetActiveBuffer();
|
|
|
|
newSize = screenInfo.GetBufferSize().Dimensions();
|
|
RETURN_HR_IF(E_INVALIDARG, !(screenInfo.IsActiveScreenBuffer()));
|
|
}
|
|
|
|
IConsoleWindow* const pWindow = ServiceLocator::LocateConsoleWindow();
|
|
if (WI_IsFlagSet(flags, CONSOLE_FULLSCREEN_MODE))
|
|
{
|
|
if (pWindow != nullptr)
|
|
{
|
|
pWindow->SetIsFullscreen(true);
|
|
}
|
|
}
|
|
else if (WI_IsFlagSet(flags, CONSOLE_WINDOWED_MODE))
|
|
{
|
|
if (pWindow != nullptr)
|
|
{
|
|
pWindow->SetIsFullscreen(false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RETURN_HR(E_INVALIDARG);
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
CATCH_RETURN();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - A private API call for changing the cursor keys input mode between normal and application mode.
|
|
// The cursor keys are the arrows, plus Home and End.
|
|
// Parameters:
|
|
// - fApplicationMode - set to true to enable Application Mode Input, false for Numeric Mode Input.
|
|
// Return value:
|
|
// - True if handled successfully. False otherwise.
|
|
[[nodiscard]] NTSTATUS DoSrvPrivateSetCursorKeysMode(_In_ bool fApplicationMode)
|
|
{
|
|
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
if (gci.pInputBuffer == nullptr)
|
|
{
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
gci.pInputBuffer->GetTerminalInput().ChangeCursorKeysMode(fApplicationMode);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// Routine Description:
|
|
// - A private API call for changing the keypad input mode between numeric and application mode.
|
|
// This controls what the keys on the numpad translate to.
|
|
// Parameters:
|
|
// - fApplicationMode - set to true to enable Application Mode Input, false for Numeric Mode Input.
|
|
// Return value:
|
|
// - True if handled successfully. False otherwise.
|
|
[[nodiscard]] NTSTATUS DoSrvPrivateSetKeypadMode(_In_ bool fApplicationMode)
|
|
{
|
|
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
if (gci.pInputBuffer == nullptr)
|
|
{
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
gci.pInputBuffer->GetTerminalInput().ChangeKeypadMode(fApplicationMode);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// Function Description:
|
|
// - A private API call which enables/disables sending full input records
|
|
// encoded as a string of characters to the client application.
|
|
// Parameters:
|
|
// - win32InputMode - set to true to enable win32-input-mode, false to disable.
|
|
// Return value:
|
|
// - <none>
|
|
void DoSrvPrivateEnableWin32InputMode(const bool win32InputMode)
|
|
{
|
|
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
gci.pInputBuffer->GetTerminalInput().ChangeWin32InputMode(win32InputMode);
|
|
}
|
|
|
|
// Routine Description:
|
|
// - A private API call for changing the screen mode between normal and reverse.
|
|
// When in reverse screen mode, the background and foreground colors are switched.
|
|
// Parameters:
|
|
// - reverseMode - set to true to enable reverse screen mode, false for normal mode.
|
|
// Return value:
|
|
// - STATUS_SUCCESS if handled successfully. Otherwise, an appropriate error code.
|
|
[[nodiscard]] NTSTATUS DoSrvPrivateSetScreenMode(const bool reverseMode)
|
|
{
|
|
try
|
|
{
|
|
Globals& g = ServiceLocator::LocateGlobals();
|
|
CONSOLE_INFORMATION& gci = g.getConsoleInformation();
|
|
|
|
gci.SetScreenReversed(reverseMode);
|
|
|
|
if (g.pRender)
|
|
{
|
|
g.pRender->TriggerRedrawAll();
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
catch (...)
|
|
{
|
|
return NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException());
|
|
}
|
|
}
|
|
|
|
// Routine Description:
|
|
// - A private API call for setting the ENABLE_WRAP_AT_EOL_OUTPUT mode.
|
|
// This controls whether the cursor moves to the beginning of the next row
|
|
// when it reaches the end of the current row.
|
|
// Parameters:
|
|
// - wrapAtEOL - set to true to wrap, false to overwrite the last character.
|
|
// Return value:
|
|
// - STATUS_SUCCESS if handled successfully.
|
|
[[nodiscard]] NTSTATUS DoSrvPrivateSetAutoWrapMode(const bool wrapAtEOL)
|
|
{
|
|
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
auto& outputMode = gci.GetActiveOutputBuffer().GetActiveBuffer().OutputMode;
|
|
WI_UpdateFlag(outputMode, ENABLE_WRAP_AT_EOL_OUTPUT, wrapAtEOL);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// Routine Description:
|
|
// - A private API call for making the cursor visible or not. Does not modify
|
|
// blinking state.
|
|
// Parameters:
|
|
// - show - set to true to make the cursor visible, false to hide.
|
|
// Return value:
|
|
// - <none>
|
|
void DoSrvPrivateShowCursor(SCREEN_INFORMATION& screenInfo, const bool show) noexcept
|
|
{
|
|
screenInfo.GetActiveBuffer().GetTextBuffer().GetCursor().SetIsVisible(show);
|
|
}
|
|
|
|
// Routine Description:
|
|
// - A private API call for enabling or disabling the cursor blinking.
|
|
// Parameters:
|
|
// - fEnable - set to true to enable blinking, false to disable
|
|
// Return value:
|
|
// - True if handled successfully. False otherwise.
|
|
void DoSrvPrivateAllowCursorBlinking(SCREEN_INFORMATION& screenInfo, const bool fEnable)
|
|
{
|
|
screenInfo.GetActiveBuffer().GetTextBuffer().GetCursor().SetBlinkingAllowed(fEnable);
|
|
|
|
// GH#2642 - From what we've gathered from other terminals, when blinking is
|
|
// disabled, the cursor should remain On always, and have the visibility
|
|
// controlled by the IsVisible property. So when you do a printf "\e[?12l"
|
|
// to disable blinking, the cursor stays stuck On. At this point, only the
|
|
// cursor visibility property controls whether the user can see it or not.
|
|
// (Yes, the cursor can be On and NOT Visible)
|
|
screenInfo.GetActiveBuffer().GetTextBuffer().GetCursor().SetIsOn(true);
|
|
}
|
|
|
|
// Routine Description:
|
|
// - A private API call for setting the top and bottom scrolling margins for
|
|
// the current page. This creates a subsection of the screen that scrolls
|
|
// when input reaches the end of the region, leaving the rest of the screen
|
|
// untouched.
|
|
// Currently only accessible through the use of ANSI sequence DECSTBM
|
|
// Parameters:
|
|
// - scrollMargins - A rect who's Top and Bottom members will be used to set
|
|
// the new values of the top and bottom margins. If (0,0), then the margins
|
|
// will be disabled. NOTE: This is a rect in the case that we'll need the
|
|
// left and right margins in the future.
|
|
// Return value:
|
|
// - True if handled successfully. False otherwise.
|
|
[[nodiscard]] NTSTATUS DoSrvPrivateSetScrollingRegion(SCREEN_INFORMATION& screenInfo, const SMALL_RECT& scrollMargins)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
if (scrollMargins.Top > scrollMargins.Bottom)
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
}
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
SMALL_RECT srScrollMargins = screenInfo.GetRelativeScrollMargins().ToInclusive();
|
|
srScrollMargins.Top = scrollMargins.Top;
|
|
srScrollMargins.Bottom = scrollMargins.Bottom;
|
|
screenInfo.GetActiveBuffer().SetScrollMargins(Viewport::FromInclusive(srScrollMargins));
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
// Routine Description:
|
|
// - A private API call for performing a line feed, possibly preceded by carriage return.
|
|
// Moves the cursor down one line, and possibly also to the leftmost column.
|
|
// Parameters:
|
|
// - screenInfo - A pointer to the screen buffer that should perform the line feed.
|
|
// - withReturn - Set to true if a carriage return should be performed as well.
|
|
// Return value:
|
|
// - STATUS_SUCCESS if handled successfully. Otherwise, an appropriate status code indicating the error.
|
|
[[nodiscard]] NTSTATUS DoSrvPrivateLineFeed(SCREEN_INFORMATION& screenInfo, const bool withReturn)
|
|
{
|
|
auto& textBuffer = screenInfo.GetTextBuffer();
|
|
auto cursorPosition = textBuffer.GetCursor().GetPosition();
|
|
|
|
// We turn the cursor on before an operation that might scroll the viewport, otherwise
|
|
// that can result in an old copy of the cursor being left behind on the screen.
|
|
textBuffer.GetCursor().SetIsOn(true);
|
|
|
|
// Since we are explicitly moving down a row, clear the wrap status on the row we're leaving
|
|
textBuffer.GetRowByOffset(cursorPosition.Y).GetCharRow().SetWrapForced(false);
|
|
|
|
cursorPosition.Y += 1;
|
|
if (withReturn)
|
|
{
|
|
cursorPosition.X = 0;
|
|
}
|
|
|
|
return AdjustCursorPosition(screenInfo, cursorPosition, FALSE, nullptr);
|
|
}
|
|
|
|
// Routine Description:
|
|
// - A private API call for performing a "Reverse line feed", essentially, the opposite of '\n'.
|
|
// Moves the cursor up one line, and tries to keep its position in the line
|
|
// Parameters:
|
|
// - screenInfo - a pointer to the screen buffer that should perform the reverse line feed
|
|
// Return value:
|
|
// - True if handled successfully. False otherwise.
|
|
[[nodiscard]] NTSTATUS DoSrvPrivateReverseLineFeed(SCREEN_INFORMATION& screenInfo)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
const SMALL_RECT viewport = screenInfo.GetActiveBuffer().GetViewport().ToInclusive();
|
|
const COORD oldCursorPosition = screenInfo.GetTextBuffer().GetCursor().GetPosition();
|
|
const COORD newCursorPosition = { oldCursorPosition.X, oldCursorPosition.Y - 1 };
|
|
|
|
// If the cursor is at the top of the viewport, we don't want to shift the viewport up.
|
|
// We want it to stay exactly where it is.
|
|
// In that case, shift the buffer contents down, to emulate inserting a line
|
|
// at the top of the buffer.
|
|
if (oldCursorPosition.Y > viewport.Top)
|
|
{
|
|
// Cursor is below the top line of the viewport
|
|
Status = AdjustCursorPosition(screenInfo, newCursorPosition, TRUE, nullptr);
|
|
}
|
|
else
|
|
{
|
|
// If we don't have margins, or the cursor is within the boundaries of the margins
|
|
// It's important to check if the cursor is in the margins,
|
|
// If it's not, but the margins are set, then we don't want to scroll anything
|
|
if (screenInfo.IsCursorInMargins(oldCursorPosition))
|
|
{
|
|
// Cursor is at the top of the viewport
|
|
// Rectangle to cut out of the existing buffer. This is inclusive.
|
|
// It will be clipped to the buffer boundaries so SHORT_MAX gives us the full buffer width.
|
|
SMALL_RECT srScroll;
|
|
srScroll.Left = 0;
|
|
srScroll.Right = SHORT_MAX;
|
|
srScroll.Top = viewport.Top;
|
|
srScroll.Bottom = viewport.Bottom;
|
|
// Clip to the DECSTBM margin boundary
|
|
if (screenInfo.AreMarginsSet())
|
|
{
|
|
srScroll.Bottom = screenInfo.GetAbsoluteScrollMargins().BottomInclusive();
|
|
}
|
|
// Paste coordinate for cut text above
|
|
COORD coordDestination;
|
|
coordDestination.X = 0;
|
|
coordDestination.Y = viewport.Top + 1;
|
|
|
|
// Note the revealed lines are filled with the standard erase attributes.
|
|
Status = NTSTATUS_FROM_HRESULT(DoSrvPrivateScrollRegion(screenInfo,
|
|
srScroll,
|
|
srScroll,
|
|
coordDestination,
|
|
true));
|
|
}
|
|
}
|
|
return Status;
|
|
}
|
|
|
|
// Routine Description:
|
|
// - A private API call for swapping to the alternate screen 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.
|
|
// Parameters:
|
|
// - screenInfo - a reference to the screen buffer that should use an alternate buffer
|
|
// Return value:
|
|
// - True if handled successfully. False otherwise.
|
|
[[nodiscard]] NTSTATUS DoSrvPrivateUseAlternateScreenBuffer(SCREEN_INFORMATION& screenInfo)
|
|
{
|
|
return screenInfo.GetActiveBuffer().UseAlternateScreenBuffer();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - A private API call for swapping to the main screen buffer. From the
|
|
// alternate buffer, returns to the main screen buffer. From the main
|
|
// screen buffer, does nothing. The alternate is discarded.
|
|
// Parameters:
|
|
// - screenInfo - a reference to the screen buffer that should use the main buffer
|
|
// Return value:
|
|
// - True if handled successfully. False otherwise.
|
|
void DoSrvPrivateUseMainScreenBuffer(SCREEN_INFORMATION& screenInfo)
|
|
{
|
|
screenInfo.GetActiveBuffer().UseMainScreenBuffer();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - A private API call for enabling VT200 style mouse mode.
|
|
// Parameters:
|
|
// - fEnable - true to enable default tracking mode, false to disable mouse mode.
|
|
// Return value:
|
|
// - None
|
|
void DoSrvPrivateEnableVT200MouseMode(const bool fEnable)
|
|
{
|
|
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
gci.GetActiveInputBuffer()->GetTerminalInput().EnableDefaultTracking(fEnable);
|
|
}
|
|
|
|
// Routine Description:
|
|
// - A private API call for enabling utf8 style mouse mode.
|
|
// Parameters:
|
|
// - fEnable - true to enable, false to disable.
|
|
// Return value:
|
|
// - None
|
|
void DoSrvPrivateEnableUTF8ExtendedMouseMode(const bool fEnable)
|
|
{
|
|
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
gci.GetActiveInputBuffer()->GetTerminalInput().SetUtf8ExtendedMode(fEnable);
|
|
}
|
|
|
|
// Routine Description:
|
|
// - A private API call for enabling SGR style mouse mode.
|
|
// Parameters:
|
|
// - fEnable - true to enable, false to disable.
|
|
// Return value:
|
|
// - None
|
|
void DoSrvPrivateEnableSGRExtendedMouseMode(const bool fEnable)
|
|
{
|
|
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
gci.GetActiveInputBuffer()->GetTerminalInput().SetSGRExtendedMode(fEnable);
|
|
}
|
|
|
|
// Routine Description:
|
|
// - A private API call for enabling button-event mouse mode.
|
|
// Parameters:
|
|
// - fEnable - true to enable button-event mode, false to disable mouse mode.
|
|
// Return value:
|
|
// - None
|
|
void DoSrvPrivateEnableButtonEventMouseMode(const bool fEnable)
|
|
{
|
|
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
gci.GetActiveInputBuffer()->GetTerminalInput().EnableButtonEventTracking(fEnable);
|
|
}
|
|
|
|
// Routine Description:
|
|
// - A private API call for enabling any-event mouse mode.
|
|
// Parameters:
|
|
// - fEnable - true to enable any-event mode, false to disable mouse mode.
|
|
// Return value:
|
|
// - None
|
|
void DoSrvPrivateEnableAnyEventMouseMode(const bool fEnable)
|
|
{
|
|
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
gci.GetActiveInputBuffer()->GetTerminalInput().EnableAnyEventTracking(fEnable);
|
|
}
|
|
|
|
// Routine Description:
|
|
// - A private API call for enabling alternate scroll mode
|
|
// Parameters:
|
|
// - fEnable - true to enable alternate scroll mode, false to disable.
|
|
// Return value:
|
|
// None
|
|
void DoSrvPrivateEnableAlternateScroll(const bool fEnable)
|
|
{
|
|
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
gci.GetActiveInputBuffer()->GetTerminalInput().EnableAlternateScroll(fEnable);
|
|
}
|
|
|
|
// Routine Description:
|
|
// - A private API call for performing a VT-style erase all operation on the buffer.
|
|
// See SCREEN_INFORMATION::VtEraseAll's description for details.
|
|
// Parameters:
|
|
// The ScreenBuffer to perform the erase on.
|
|
// Return value:
|
|
// - S_OK if we succeeded, otherwise the HRESULT of the failure.
|
|
[[nodiscard]] HRESULT DoSrvPrivateEraseAll(SCREEN_INFORMATION& screenInfo)
|
|
{
|
|
return screenInfo.GetActiveBuffer().VtEraseAll();
|
|
}
|
|
|
|
void DoSrvSetCursorStyle(SCREEN_INFORMATION& screenInfo,
|
|
const CursorType cursorType)
|
|
{
|
|
screenInfo.GetActiveBuffer().GetTextBuffer().GetCursor().SetType(cursorType);
|
|
}
|
|
|
|
void DoSrvSetCursorColor(SCREEN_INFORMATION& screenInfo,
|
|
const COLORREF cursorColor)
|
|
{
|
|
screenInfo.GetActiveBuffer().GetTextBuffer().GetCursor().SetColor(cursorColor);
|
|
}
|
|
|
|
void DoSrvAddHyperlink(SCREEN_INFORMATION& screenInfo,
|
|
const std::wstring_view uri,
|
|
const std::wstring_view params)
|
|
{
|
|
auto attr = screenInfo.GetAttributes();
|
|
const auto id = screenInfo.GetTextBuffer().GetHyperlinkId(uri, params);
|
|
attr.SetHyperlinkId(id);
|
|
screenInfo.GetTextBuffer().SetCurrentAttributes(attr);
|
|
screenInfo.GetTextBuffer().AddHyperlinkToMap(uri, id);
|
|
}
|
|
|
|
void DoSrvEndHyperlink(SCREEN_INFORMATION& screenInfo)
|
|
{
|
|
auto attr = screenInfo.GetAttributes();
|
|
attr.SetHyperlinkId(0);
|
|
screenInfo.GetTextBuffer().SetCurrentAttributes(attr);
|
|
}
|
|
|
|
// Routine Description:
|
|
// - A private API call for forcing the renderer to repaint the screen. If the
|
|
// input screen buffer is not the active one, then just do nothing. We only
|
|
// want to redraw the screen buffer that requested the repaint, and
|
|
// switching screen buffers will already force a repaint.
|
|
// Parameters:
|
|
// The ScreenBuffer to perform the repaint for.
|
|
// Return value:
|
|
// - None
|
|
void DoSrvPrivateRefreshWindow(_In_ const SCREEN_INFORMATION& screenInfo)
|
|
{
|
|
Globals& g = ServiceLocator::LocateGlobals();
|
|
if (&screenInfo == &g.getConsoleInformation().GetActiveOutputBuffer().GetActiveBuffer())
|
|
{
|
|
g.pRender->TriggerRedrawAll();
|
|
}
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Gets title information from the console. It can be truncated if the buffer is too small.
|
|
// Arguments:
|
|
// - title - If given, this buffer is filled with the title information requested.
|
|
// - Use nullopt to request buffer size required.
|
|
// - written - The number of characters filled in the title buffer.
|
|
// - needed - The number of characters we would need to completely write out the title.
|
|
// - isOriginal - If true, gets the title when we booted up. If false, gets whatever it is set to right now.
|
|
// Return Value:
|
|
// - S_OK, E_INVALIDARG, or failure code from thrown exception
|
|
[[nodiscard]] HRESULT GetConsoleTitleWImplHelper(std::optional<gsl::span<wchar_t>> title,
|
|
size_t& written,
|
|
size_t& needed,
|
|
const bool isOriginal) noexcept
|
|
{
|
|
try
|
|
{
|
|
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
// Ensure output variables are initialized.
|
|
written = 0;
|
|
needed = 0;
|
|
|
|
if (title.has_value() && title->size() > 0)
|
|
{
|
|
til::at(*title, 0) = ANSI_NULL;
|
|
}
|
|
|
|
// Get the appropriate title and length depending on the mode.
|
|
const wchar_t* pwszTitle;
|
|
size_t cchTitleLength;
|
|
|
|
if (isOriginal)
|
|
{
|
|
pwszTitle = gci.GetOriginalTitle().c_str();
|
|
cchTitleLength = gci.GetOriginalTitle().length();
|
|
}
|
|
else
|
|
{
|
|
pwszTitle = gci.GetTitle().c_str();
|
|
cchTitleLength = gci.GetTitle().length();
|
|
}
|
|
|
|
// Always report how much space we would need.
|
|
needed = cchTitleLength;
|
|
|
|
// If we have a pointer to receive the data, then copy it out.
|
|
if (title.has_value())
|
|
{
|
|
HRESULT const hr = StringCchCopyNW(title->data(), title->size(), pwszTitle, cchTitleLength);
|
|
|
|
// Insufficient buffer is allowed. If we return a partial string, that's still OK by historical/compat standards.
|
|
// Just say how much we managed to return.
|
|
if (SUCCEEDED(hr) || STRSAFE_E_INSUFFICIENT_BUFFER == hr)
|
|
{
|
|
written = std::min(title->size(), cchTitleLength);
|
|
}
|
|
}
|
|
return S_OK;
|
|
}
|
|
CATCH_RETURN();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Gets title information from the console. It can be truncated if the buffer is too small.
|
|
// Arguments:
|
|
// - title - If given, this buffer is filled with the title information requested.
|
|
// - Use nullopt to request buffer size required.
|
|
// - written - The number of characters filled in the title buffer.
|
|
// - needed - The number of characters we would need to completely write out the title.
|
|
// - isOriginal - If true, gets the title when we booted up. If false, gets whatever it is set to right now.
|
|
// Return Value:
|
|
// - S_OK, E_INVALIDARG, or failure code from thrown exception
|
|
|
|
[[nodiscard]] HRESULT GetConsoleTitleAImplHelper(gsl::span<char> title,
|
|
size_t& written,
|
|
size_t& needed,
|
|
const bool isOriginal) noexcept
|
|
{
|
|
try
|
|
{
|
|
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
// Ensure output variables are initialized.
|
|
written = 0;
|
|
needed = 0;
|
|
|
|
if (title.size() > 0)
|
|
{
|
|
til::at(title, 0) = ANSI_NULL;
|
|
}
|
|
|
|
// Figure out how big our temporary Unicode buffer must be to get the title.
|
|
size_t unicodeNeeded;
|
|
size_t unicodeWritten;
|
|
RETURN_IF_FAILED(GetConsoleTitleWImplHelper(std::nullopt, unicodeWritten, unicodeNeeded, isOriginal));
|
|
|
|
// If there's nothing to get, then simply return.
|
|
RETURN_HR_IF(S_OK, 0 == unicodeNeeded);
|
|
|
|
// Allocate a unicode buffer of the right size.
|
|
size_t const unicodeSize = unicodeNeeded + 1; // add one for null terminator space
|
|
std::unique_ptr<wchar_t[]> unicodeBuffer = std::make_unique<wchar_t[]>(unicodeSize);
|
|
RETURN_IF_NULL_ALLOC(unicodeBuffer);
|
|
|
|
const gsl::span<wchar_t> unicodeSpan(unicodeBuffer.get(), unicodeSize);
|
|
|
|
// Retrieve the title in Unicode.
|
|
RETURN_IF_FAILED(GetConsoleTitleWImplHelper(unicodeSpan, unicodeWritten, unicodeNeeded, isOriginal));
|
|
|
|
// Convert result to A
|
|
const auto converted = ConvertToA(gci.CP, { unicodeBuffer.get(), unicodeWritten });
|
|
|
|
// The legacy A behavior is a bit strange. If the buffer given doesn't have enough space to hold
|
|
// the string without null termination (e.g. the title is 9 long, 10 with null. The buffer given isn't >= 9).
|
|
// then do not copy anything back and do not report how much space we need.
|
|
if (title.size() >= converted.size())
|
|
{
|
|
// Say how many characters of buffer we would need to hold the entire result.
|
|
needed = converted.size();
|
|
|
|
// Copy safely to output buffer
|
|
HRESULT const hr = StringCchCopyNA(title.data(), title.size(), converted.data(), converted.size());
|
|
|
|
// Insufficient buffer is allowed. If we return a partial string, that's still OK by historical/compat standards.
|
|
// Just say how much we managed to return.
|
|
if (SUCCEEDED(hr) || STRSAFE_E_INSUFFICIENT_BUFFER == hr)
|
|
{
|
|
// And return the size copied (either the size of the buffer or the null terminated length of the string we filled it with.)
|
|
written = std::min(title.size(), converted.size() + 1);
|
|
|
|
// Another compatibility fix... If we had exactly the number of bytes needed for an unterminated string,
|
|
// then replace the terminator left behind by StringCchCopyNA with the final character of the title string.
|
|
if (title.size() == converted.size())
|
|
{
|
|
title.back() = converted.back();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If we didn't copy anything back and there is space, null terminate the given buffer and return.
|
|
if (title.size() > 0)
|
|
{
|
|
til::at(title, 0) = ANSI_NULL;
|
|
written = 1;
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
CATCH_RETURN();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Gets title information from the console. It can be truncated if the buffer is too small.
|
|
// Arguments:
|
|
// - title - If given, this buffer is filled with the title information requested.
|
|
// - Use nullopt to request buffer size required.
|
|
// - written - The number of characters filled in the title buffer.
|
|
// - needed - The number of characters we would need to completely write out the title.
|
|
// Return Value:
|
|
// - S_OK, E_INVALIDARG, or failure code from thrown exception
|
|
[[nodiscard]] HRESULT ApiRoutines::GetConsoleTitleAImpl(gsl::span<char> title,
|
|
size_t& written,
|
|
size_t& needed) noexcept
|
|
{
|
|
try
|
|
{
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
return GetConsoleTitleAImplHelper(title, written, needed, false);
|
|
}
|
|
CATCH_RETURN();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Gets title information from the console. It can be truncated if the buffer is too small.
|
|
// Arguments:
|
|
// - title - If given, this buffer is filled with the title information requested.
|
|
// - Use nullopt to request buffer size required.
|
|
// - written - The number of characters filled in the title buffer.
|
|
// - needed - The number of characters we would need to completely write out the title.
|
|
// Return Value:
|
|
// - S_OK, E_INVALIDARG, or failure code from thrown exception
|
|
[[nodiscard]] HRESULT ApiRoutines::GetConsoleTitleWImpl(gsl::span<wchar_t> title,
|
|
size_t& written,
|
|
size_t& needed) noexcept
|
|
{
|
|
try
|
|
{
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
return GetConsoleTitleWImplHelper(title, written, needed, false);
|
|
}
|
|
CATCH_RETURN();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Gets title information from the console. It can be truncated if the buffer is too small.
|
|
// Arguments:
|
|
// - title - If given, this buffer is filled with the title information requested.
|
|
// - Use nullopt to request buffer size required.
|
|
// - written - The number of characters filled in the title buffer.
|
|
// - needed - The number of characters we would need to completely write out the title.
|
|
// Return Value:
|
|
// - S_OK, E_INVALIDARG, or failure code from thrown exception
|
|
[[nodiscard]] HRESULT ApiRoutines::GetConsoleOriginalTitleAImpl(gsl::span<char> title,
|
|
size_t& written,
|
|
size_t& needed) noexcept
|
|
{
|
|
try
|
|
{
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
return GetConsoleTitleAImplHelper(title, written, needed, true);
|
|
}
|
|
CATCH_RETURN();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Gets title information from the console. It can be truncated if the buffer is too small.
|
|
// Arguments:
|
|
// - title - If given, this buffer is filled with the title information requested.
|
|
// - Use nullopt to request buffer size required.
|
|
// - written - The number of characters filled in the title buffer.
|
|
// - needed - The number of characters we would need to completely write out the title.
|
|
// Return Value:
|
|
// - S_OK, E_INVALIDARG, or failure code from thrown exception
|
|
[[nodiscard]] HRESULT ApiRoutines::GetConsoleOriginalTitleWImpl(gsl::span<wchar_t> title,
|
|
size_t& written,
|
|
size_t& needed) noexcept
|
|
{
|
|
try
|
|
{
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
return GetConsoleTitleWImplHelper(title, written, needed, true);
|
|
}
|
|
CATCH_RETURN();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Sets title information from the console.
|
|
// Arguments:
|
|
// - title - The new title to store and display on the console window.
|
|
// Return Value:
|
|
// - S_OK, E_INVALIDARG, or failure code from thrown exception
|
|
[[nodiscard]] HRESULT ApiRoutines::SetConsoleTitleAImpl(const std::string_view title) noexcept
|
|
{
|
|
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
|
|
try
|
|
{
|
|
const auto titleW = ConvertToW(gci.CP, title);
|
|
|
|
return SetConsoleTitleWImpl(titleW);
|
|
}
|
|
CATCH_RETURN();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Sets title information from the console.
|
|
// Arguments:
|
|
// - title - The new title to store and display on the console window.
|
|
// Return Value:
|
|
// - S_OK, E_INVALIDARG, or failure code from thrown exception
|
|
[[nodiscard]] HRESULT ApiRoutines::SetConsoleTitleWImpl(const std::wstring_view title) noexcept
|
|
{
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
return DoSrvSetConsoleTitleW(title);
|
|
}
|
|
|
|
[[nodiscard]] HRESULT DoSrvSetConsoleTitleW(const std::wstring_view title) noexcept
|
|
{
|
|
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
|
|
// Sanitize the input if we're in pty mode. No control chars - this string
|
|
// will get emitted back to the TTY in a VT sequence, and we don't want
|
|
// to embed control characters in that string.
|
|
if (gci.IsInVtIoMode())
|
|
{
|
|
std::wstring sanitized;
|
|
sanitized.reserve(title.size());
|
|
for (size_t i = 0; i < title.size(); i++)
|
|
{
|
|
if (title.at(i) >= UNICODE_SPACE)
|
|
{
|
|
sanitized.push_back(title.at(i));
|
|
}
|
|
}
|
|
|
|
gci.SetTitle({ sanitized });
|
|
}
|
|
else
|
|
{
|
|
// SetTitle will trigger the renderer to update the titlebar for us.
|
|
gci.SetTitle(title);
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
// Routine Description:
|
|
// - A private API call for forcing the VT Renderer to NOT paint the next resize
|
|
// event. This is used by InteractDispatch, to prevent resizes from echoing
|
|
// between terminal and host.
|
|
// Parameters:
|
|
// <none>
|
|
// Return value:
|
|
// - STATUS_SUCCESS if we succeeded, otherwise the NTSTATUS version of the failure.
|
|
[[nodiscard]] NTSTATUS DoSrvPrivateSuppressResizeRepaint()
|
|
{
|
|
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
FAIL_FAST_IF(!(gci.IsInVtIoMode()));
|
|
return NTSTATUS_FROM_HRESULT(gci.GetVtIo()->SuppressResizeRepaint());
|
|
}
|
|
|
|
// Routine Description:
|
|
// - An API call for checking if the console host is acting as a pty.
|
|
// Parameters:
|
|
// - isPty: receives the bool indicating whether or not we're in pty mode.
|
|
// Return value:
|
|
// <none>
|
|
void DoSrvIsConsolePty(bool& isPty)
|
|
{
|
|
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
isPty = gci.IsInVtIoMode();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - internal logic for adding or removing lines in the active screen buffer
|
|
// this also moves the cursor to the left margin, which is expected behavior for IL and DL
|
|
// Parameters:
|
|
// - count - the number of lines to modify
|
|
// - insert - true if inserting lines, false if deleting lines
|
|
void DoSrvPrivateModifyLinesImpl(const size_t count, const bool insert)
|
|
{
|
|
auto& screenInfo = ServiceLocator::LocateGlobals().getConsoleInformation().GetActiveOutputBuffer().GetActiveBuffer();
|
|
auto& textBuffer = screenInfo.GetTextBuffer();
|
|
const auto cursorPosition = textBuffer.GetCursor().GetPosition();
|
|
if (screenInfo.IsCursorInMargins(cursorPosition))
|
|
{
|
|
// Rectangle to cut out of the existing buffer. This is inclusive.
|
|
// It will be clipped to the buffer boundaries so SHORT_MAX gives us the full buffer width.
|
|
SMALL_RECT srScroll;
|
|
srScroll.Left = 0;
|
|
srScroll.Right = SHORT_MAX;
|
|
srScroll.Top = cursorPosition.Y;
|
|
srScroll.Bottom = screenInfo.GetViewport().BottomInclusive();
|
|
// Clip to the DECSTBM margin boundary
|
|
if (screenInfo.AreMarginsSet())
|
|
{
|
|
srScroll.Bottom = screenInfo.GetAbsoluteScrollMargins().BottomInclusive();
|
|
}
|
|
// Paste coordinate for cut text above
|
|
COORD coordDestination;
|
|
coordDestination.X = 0;
|
|
if (insert)
|
|
{
|
|
coordDestination.Y = (cursorPosition.Y) + gsl::narrow<short>(count);
|
|
}
|
|
else
|
|
{
|
|
coordDestination.Y = (cursorPosition.Y) - gsl::narrow<short>(count);
|
|
}
|
|
|
|
// Note the revealed lines are filled with the standard erase attributes.
|
|
LOG_IF_FAILED(DoSrvPrivateScrollRegion(screenInfo,
|
|
srScroll,
|
|
srScroll,
|
|
coordDestination,
|
|
true));
|
|
|
|
// The IL and DL controls are also expected to move the cursor to the left margin.
|
|
// For now this is just column 0, since we don't yet support DECSLRM.
|
|
LOG_IF_NTSTATUS_FAILED(screenInfo.SetCursorPosition({ 0, cursorPosition.Y }, false));
|
|
}
|
|
}
|
|
|
|
// Routine Description:
|
|
// - a private API call for deleting lines in the active screen buffer.
|
|
// Parameters:
|
|
// - count - the number of lines to delete
|
|
void DoSrvPrivateDeleteLines(const size_t count)
|
|
{
|
|
DoSrvPrivateModifyLinesImpl(count, false);
|
|
}
|
|
|
|
// Routine Description:
|
|
// - a private API call for inserting lines in the active screen buffer.
|
|
// Parameters:
|
|
// - count - the number of lines to insert
|
|
void DoSrvPrivateInsertLines(const size_t count)
|
|
{
|
|
DoSrvPrivateModifyLinesImpl(count, true);
|
|
}
|
|
|
|
// Method Description:
|
|
// - Snaps the screen buffer's viewport to the "virtual bottom", the last place
|
|
//the viewport was before the user scrolled it (with the mouse or scrollbar)
|
|
// Arguments:
|
|
// - screenInfo: the buffer to move the viewport for.
|
|
// Return Value:
|
|
// - <none>
|
|
void DoSrvPrivateMoveToBottom(SCREEN_INFORMATION& screenInfo)
|
|
{
|
|
screenInfo.GetActiveBuffer().MoveToBottom();
|
|
}
|
|
|
|
// Method Description:
|
|
// - Retrieve the color table value at the specified index.
|
|
// Arguments:
|
|
// - index: the index in the table to retrieve.
|
|
// - value: receives the RGB value for the color at that index in the table.
|
|
// Return Value:
|
|
// - E_INVALIDARG if index is >= 256, else S_OK
|
|
[[nodiscard]] HRESULT DoSrvPrivateGetColorTableEntry(const size_t index, COLORREF& value) noexcept
|
|
{
|
|
RETURN_HR_IF(E_INVALIDARG, index >= 256);
|
|
try
|
|
{
|
|
Globals& g = ServiceLocator::LocateGlobals();
|
|
CONSOLE_INFORMATION& gci = g.getConsoleInformation();
|
|
|
|
value = gci.GetColorTableEntry(::Xterm256ToWindowsIndex(index));
|
|
|
|
return S_OK;
|
|
}
|
|
CATCH_RETURN();
|
|
}
|
|
|
|
// Method Description:
|
|
// - Sets the color table value in index to the color specified in value.
|
|
// Can be used to set the 256-color table as well as the 16-color table.
|
|
// Arguments:
|
|
// - index: the index in the table to change.
|
|
// - value: the new RGB value to use for that index in the color table.
|
|
// Return Value:
|
|
// - E_INVALIDARG if index is >= 256, else S_OK
|
|
// Notes:
|
|
// Does not take a buffer parameter. The color table for a console and for
|
|
// terminals as well is global, not per-screen-buffer.
|
|
[[nodiscard]] HRESULT DoSrvPrivateSetColorTableEntry(const size_t index, const COLORREF value) noexcept
|
|
{
|
|
RETURN_HR_IF(E_INVALIDARG, index >= 256);
|
|
try
|
|
{
|
|
Globals& g = ServiceLocator::LocateGlobals();
|
|
CONSOLE_INFORMATION& gci = g.getConsoleInformation();
|
|
|
|
gci.SetColorTableEntry(::Xterm256ToWindowsIndex(index), value);
|
|
|
|
// Update the screen colors if we're not a pty
|
|
// No need to force a redraw in pty mode.
|
|
if (g.pRender && !gci.IsInVtIoMode())
|
|
{
|
|
g.pRender->TriggerRedrawAll();
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
CATCH_RETURN();
|
|
}
|
|
|
|
// Method Description:
|
|
// - Sets the default foreground color to the color specified in value.
|
|
// Arguments:
|
|
// - value: the new RGB value to use, as a COLORREF, format 0x00BBGGRR.
|
|
// Return Value:
|
|
// - S_OK
|
|
[[nodiscard]] HRESULT DoSrvPrivateSetDefaultForegroundColor(const COLORREF value) noexcept
|
|
{
|
|
try
|
|
{
|
|
Globals& g = ServiceLocator::LocateGlobals();
|
|
CONSOLE_INFORMATION& gci = g.getConsoleInformation();
|
|
|
|
gci.SetDefaultForegroundColor(value);
|
|
|
|
// Update the screen colors if we're not a pty
|
|
// No need to force a redraw in pty mode.
|
|
if (g.pRender && !gci.IsInVtIoMode())
|
|
{
|
|
g.pRender->TriggerRedrawAll();
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
CATCH_RETURN();
|
|
}
|
|
|
|
// Method Description:
|
|
// - Sets the default background color to the color specified in value.
|
|
// Arguments:
|
|
// - value: the new RGB value to use, as a COLORREF, format 0x00BBGGRR.
|
|
// Return Value:
|
|
// - S_OK
|
|
[[nodiscard]] HRESULT DoSrvPrivateSetDefaultBackgroundColor(const COLORREF value) noexcept
|
|
{
|
|
try
|
|
{
|
|
Globals& g = ServiceLocator::LocateGlobals();
|
|
CONSOLE_INFORMATION& gci = g.getConsoleInformation();
|
|
|
|
gci.SetDefaultBackgroundColor(value);
|
|
|
|
// Update the screen colors if we're not a pty
|
|
// No need to force a redraw in pty mode.
|
|
if (g.pRender && !gci.IsInVtIoMode())
|
|
{
|
|
g.pRender->TriggerRedrawAll();
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
CATCH_RETURN();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - A private API call for filling a region of the screen buffer.
|
|
// Arguments:
|
|
// - screenInfo - Reference to screen buffer info.
|
|
// - startPosition - The position to begin filling at.
|
|
// - fillLength - The number of characters to fill.
|
|
// - fillChar - Character to fill the target region with.
|
|
// - standardFillAttrs - If true, fill with the standard erase attributes.
|
|
// If false, fill with the default attributes.
|
|
// Return value:
|
|
// - S_OK or failure code from thrown exception
|
|
[[nodiscard]] HRESULT DoSrvPrivateFillRegion(SCREEN_INFORMATION& screenInfo,
|
|
const COORD startPosition,
|
|
const size_t fillLength,
|
|
const wchar_t fillChar,
|
|
const bool standardFillAttrs) noexcept
|
|
{
|
|
try
|
|
{
|
|
if (fillLength == 0)
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
// For most VT erasing operations, the standard requires that the
|
|
// erased area be filled with the current background color, but with
|
|
// no additional meta attributes set. For all other cases, we just
|
|
// fill with the default attributes.
|
|
auto fillAttrs = TextAttribute{};
|
|
if (standardFillAttrs)
|
|
{
|
|
fillAttrs = screenInfo.GetAttributes();
|
|
fillAttrs.SetStandardErase();
|
|
}
|
|
|
|
const auto fillData = OutputCellIterator{ fillChar, fillAttrs, fillLength };
|
|
screenInfo.Write(fillData, startPosition, false);
|
|
|
|
// Notify accessibility
|
|
auto endPosition = startPosition;
|
|
const auto bufferSize = screenInfo.GetBufferSize();
|
|
bufferSize.MoveInBounds(fillLength - 1, endPosition);
|
|
screenInfo.NotifyAccessibilityEventing(startPosition.X, startPosition.Y, endPosition.X, endPosition.Y);
|
|
return S_OK;
|
|
}
|
|
CATCH_RETURN();
|
|
}
|
|
|
|
// Routine Description:
|
|
// - A private API call for moving a block of data in the screen buffer,
|
|
// optionally limiting the effects of the move to a clipping rectangle.
|
|
// Arguments:
|
|
// - screenInfo - Reference to screen buffer info.
|
|
// - scrollRect - Region to copy/move (source and size).
|
|
// - clipRect - Optional clip region to contain buffer change effects.
|
|
// - destinationOrigin - Upper left corner of target region.
|
|
// - standardFillAttrs - If true, fill with the standard erase attributes.
|
|
// If false, fill with the default attributes.
|
|
// Return value:
|
|
// - S_OK or failure code from thrown exception
|
|
[[nodiscard]] HRESULT DoSrvPrivateScrollRegion(SCREEN_INFORMATION& screenInfo,
|
|
const SMALL_RECT scrollRect,
|
|
const std::optional<SMALL_RECT> clipRect,
|
|
const COORD destinationOrigin,
|
|
const bool standardFillAttrs) noexcept
|
|
{
|
|
try
|
|
{
|
|
LockConsole();
|
|
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
|
|
|
// For most VT scrolling operations, the standard requires that the
|
|
// erased area be filled with the current background color, but with
|
|
// no additional meta attributes set. For all other cases, we just
|
|
// fill with the default attributes.
|
|
auto fillAttrs = TextAttribute{};
|
|
if (standardFillAttrs)
|
|
{
|
|
fillAttrs = screenInfo.GetAttributes();
|
|
fillAttrs.SetStandardErase();
|
|
}
|
|
|
|
ScrollRegion(screenInfo,
|
|
scrollRect,
|
|
clipRect,
|
|
destinationOrigin,
|
|
UNICODE_SPACE,
|
|
fillAttrs);
|
|
return S_OK;
|
|
}
|
|
CATCH_RETURN();
|
|
}
|