terminal/src/terminal/parser/OutputStateMachineEngine.cpp
Michael Niksa 322989d017 Apply audit mode to TerminalInput/Adapter/Parser libraries (#4005)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request
- Enables auditing of Virtual Terminal libraries (input, adapter, parser)

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [x] Rolls audit out to more things
* [x] I work here
* [x] Tests should still pass
* [x] Am core contributor
* [x] Closes #3957

<!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
This is turning on the auditing of these projects (as enabled by the heavier lifting in the other refactor) and then cleaning up the remaining warnings.

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
- [x] Built it
- [x] Ran the tests
2020-01-03 14:25:21 +00:00

1695 lines
60 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "stateMachine.hpp"
#include "OutputStateMachineEngine.hpp"
#include "ascii.hpp"
using namespace Microsoft::Console;
using namespace Microsoft::Console::VirtualTerminal;
// takes ownership of pDispatch
OutputStateMachineEngine::OutputStateMachineEngine(std::unique_ptr<ITermDispatch> pDispatch) :
_dispatch(std::move(pDispatch)),
_pfnFlushToTerminal(nullptr),
_pTtyConnection(nullptr),
_lastPrintedChar(AsciiChars::NUL)
{
THROW_IF_NULL_ALLOC(_dispatch.get());
}
const ITermDispatch& OutputStateMachineEngine::Dispatch() const noexcept
{
return *_dispatch;
}
ITermDispatch& OutputStateMachineEngine::Dispatch() noexcept
{
return *_dispatch;
}
// Routine Description:
// - Triggers the Execute action to indicate that the listener should
// immediately respond to a C0 control character.
// Arguments:
// - wch - Character to dispatch.
// Return Value:
// - true iff we successfully dispatched the sequence.
bool OutputStateMachineEngine::ActionExecute(const wchar_t wch)
{
// microsoft/terminal#1825 - VT applications expect to be able to write NUL
// and have _nothing_ happen. Filter the NULs here, so they don't fill the
// buffer with empty spaces.
if (wch == AsciiChars::NUL)
{
return true;
}
_dispatch->Execute(wch);
_ClearLastChar();
if (wch == AsciiChars::BEL)
{
// microsoft/terminal#2952
// If we're attached to a terminal, let's also pass the BEL through.
if (_pfnFlushToTerminal != nullptr)
{
_pfnFlushToTerminal();
}
}
return true;
}
// Routine Description:
// - Triggers the Execute action to indicate that the listener should
// immediately respond to a C0 control character.
// This is called from the Escape state in the state machine, indicating the
// immediately previous character was an 0x1b. The output state machine
// does not treat this any differently than a normal ActionExecute.
// Arguments:
// - wch - Character to dispatch.
// Return Value:
// - true iff we successfully dispatched the sequence.
bool OutputStateMachineEngine::ActionExecuteFromEscape(const wchar_t wch)
{
return ActionExecute(wch);
}
// Routine Description:
// - Triggers the Print action to indicate that the listener should render the
// character given.
// Arguments:
// - wch - Character to dispatch.
// Return Value:
// - true iff we successfully dispatched the sequence.
bool OutputStateMachineEngine::ActionPrint(const wchar_t wch)
{
// Stash the last character of the string, if it's a graphical character
if (wch >= AsciiChars::SPC)
{
_lastPrintedChar = wch;
}
_dispatch->Print(wch); // call print
return true;
}
// Routine Description:
// - Triggers the Print action to indicate that the listener should render the
// string of characters given.
// Arguments:
// - string - string to dispatch.
// Return Value:
// - true iff we successfully dispatched the sequence.
bool OutputStateMachineEngine::ActionPrintString(const std::wstring_view string)
{
if (string.empty())
{
return true;
}
// Stash the last character of the string, if it's a graphical character
const wchar_t wch = string.back();
if (wch >= AsciiChars::SPC)
{
_lastPrintedChar = wch;
}
_dispatch->PrintString(string); // call print
return true;
}
// Routine Description:
// This is called when we have determined that we don't understand a particular
// sequence, or the adapter has determined that the string is intended for
// the actual terminal (when we're acting as a pty).
// - Pass the string through to the target terminal application. If we're a pty,
// then we'll have a TerminalConnection that we'll write the string to.
// Otherwise, we're the terminal device, and we'll eat the string (because
// we don't know what to do with it)
// Arguments:
// - string - string to dispatch.
// Return Value:
// - true iff we successfully dispatched the sequence.
bool OutputStateMachineEngine::ActionPassThroughString(const std::wstring_view string)
{
bool success = true;
if (_pTtyConnection != nullptr)
{
const auto hr = _pTtyConnection->WriteTerminalW(string);
LOG_IF_FAILED(hr);
success = SUCCEEDED(hr);
}
// If there's not a TTY connection, our previous behavior was to eat the string.
return success;
}
// Routine Description:
// - Triggers the EscDispatch action to indicate that the listener should handle
// a simple escape sequence. These sequences traditionally start with ESC
// and a simple letter. No complicated parameters.
// Arguments:
// - wch - Character to dispatch.
// - intermediates - Intermediate characters in the sequence
// Return Value:
// - true iff we successfully dispatched the sequence.
bool OutputStateMachineEngine::ActionEscDispatch(const wchar_t wch,
const std::basic_string_view<wchar_t> intermediates)
{
bool success = false;
// no intermediates.
if (intermediates.empty())
{
switch (wch)
{
case VTActionCodes::DECSC_CursorSave:
success = _dispatch->CursorSaveState();
TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSC);
break;
case VTActionCodes::DECRC_CursorRestore:
success = _dispatch->CursorRestoreState();
TermTelemetry::Instance().Log(TermTelemetry::Codes::DECRC);
break;
case VTActionCodes::DECKPAM_KeypadApplicationMode:
success = _dispatch->SetKeypadMode(true);
TermTelemetry::Instance().Log(TermTelemetry::Codes::DECKPAM);
break;
case VTActionCodes::DECKPNM_KeypadNumericMode:
success = _dispatch->SetKeypadMode(false);
TermTelemetry::Instance().Log(TermTelemetry::Codes::DECKPNM);
break;
case VTActionCodes::RI_ReverseLineFeed:
success = _dispatch->ReverseLineFeed();
TermTelemetry::Instance().Log(TermTelemetry::Codes::RI);
break;
case VTActionCodes::HTS_HorizontalTabSet:
success = _dispatch->HorizontalTabSet();
TermTelemetry::Instance().Log(TermTelemetry::Codes::HTS);
break;
case VTActionCodes::RIS_ResetToInitialState:
success = _dispatch->HardReset();
TermTelemetry::Instance().Log(TermTelemetry::Codes::RIS);
break;
default:
// If no functions to call, overall dispatch was a failure.
success = false;
break;
}
}
else if (intermediates.size() == 1)
{
const auto value = til::at(intermediates, 0);
DesignateCharsetTypes designateType = DefaultDesignateCharsetType;
success = _GetDesignateType(value, designateType);
if (success)
{
switch (designateType)
{
case DesignateCharsetTypes::G0:
success = _dispatch->DesignateCharset(wch);
TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG0);
break;
case DesignateCharsetTypes::G1:
success = false;
TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG1);
break;
case DesignateCharsetTypes::G2:
success = false;
TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG2);
break;
case DesignateCharsetTypes::G3:
success = false;
TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG3);
break;
default:
// If no functions to call, overall dispatch was a failure.
success = false;
break;
}
}
else if (value == L'#')
{
switch (wch)
{
case VTActionCodes::DECALN_ScreenAlignmentPattern:
success = _dispatch->ScreenAlignmentPattern();
TermTelemetry::Instance().Log(TermTelemetry::Codes::DECALN);
break;
default:
// If no functions to call, overall dispatch was a failure.
success = false;
break;
}
}
}
_ClearLastChar();
return success;
}
// Routine Description:
// - Triggers the CsiDispatch action to indicate that the listener should handle
// a control sequence. These sequences perform various API-type commands
// that can include many parameters.
// Arguments:
// - wch - Character to dispatch.
// - intermediates - Intermediate characters in the sequence
// - parameters - set of numeric parameters collected while pasring the sequence.
// Return Value:
// - true iff we successfully dispatched the sequence.
bool OutputStateMachineEngine::ActionCsiDispatch(const wchar_t wch,
const std::basic_string_view<wchar_t> intermediates,
std::basic_string_view<size_t> parameters)
{
bool success = false;
size_t distance = 0;
size_t line = 0;
size_t column = 0;
size_t topMargin = 0;
size_t bottomMargin = 0;
size_t numTabs = 0;
size_t clearType = 0;
unsigned int function = 0;
DispatchTypes::EraseType eraseType = DispatchTypes::EraseType::ToEnd;
std::vector<DispatchTypes::GraphicsOptions> graphicsOptions;
DispatchTypes::AnsiStatusType deviceStatusType = static_cast<DispatchTypes::AnsiStatusType>(0); // there is no default status type.
size_t repeatCount = 0;
// This is all the args after the first arg, and the count of args not including the first one.
const auto remainingParams = parameters.size() > 1 ? parameters.substr(1) : std::basic_string_view<size_t>{};
if (intermediates.empty())
{
// fill params
switch (wch)
{
case VTActionCodes::CUU_CursorUp:
case VTActionCodes::CUD_CursorDown:
case VTActionCodes::CUF_CursorForward:
case VTActionCodes::CUB_CursorBackward:
case VTActionCodes::CNL_CursorNextLine:
case VTActionCodes::CPL_CursorPrevLine:
case VTActionCodes::CHA_CursorHorizontalAbsolute:
case VTActionCodes::HPA_HorizontalPositionAbsolute:
case VTActionCodes::VPA_VerticalLinePositionAbsolute:
case VTActionCodes::ICH_InsertCharacter:
case VTActionCodes::DCH_DeleteCharacter:
case VTActionCodes::ECH_EraseCharacters:
success = _GetCursorDistance(parameters, distance);
break;
case VTActionCodes::HVP_HorizontalVerticalPosition:
case VTActionCodes::CUP_CursorPosition:
success = _GetXYPosition(parameters, line, column);
break;
case VTActionCodes::DECSTBM_SetScrollingRegion:
success = _GetTopBottomMargins(parameters, topMargin, bottomMargin);
break;
case VTActionCodes::ED_EraseDisplay:
case VTActionCodes::EL_EraseLine:
success = _GetEraseOperation(parameters, eraseType);
break;
case VTActionCodes::SGR_SetGraphicsRendition:
success = _GetGraphicsOptions(parameters, graphicsOptions);
break;
case VTActionCodes::DSR_DeviceStatusReport:
success = _GetDeviceStatusOperation(parameters, deviceStatusType);
break;
case VTActionCodes::DA_DeviceAttributes:
success = _VerifyDeviceAttributesParams(parameters);
break;
case VTActionCodes::SU_ScrollUp:
case VTActionCodes::SD_ScrollDown:
success = _GetScrollDistance(parameters, distance);
break;
case VTActionCodes::ANSISYSSC_CursorSave:
case VTActionCodes::ANSISYSRC_CursorRestore:
success = _VerifyHasNoParameters(parameters);
break;
case VTActionCodes::IL_InsertLine:
case VTActionCodes::DL_DeleteLine:
success = _GetScrollDistance(parameters, distance);
break;
case VTActionCodes::CHT_CursorForwardTab:
case VTActionCodes::CBT_CursorBackTab:
success = _GetTabDistance(parameters, numTabs);
break;
case VTActionCodes::TBC_TabClear:
success = _GetTabClearType(parameters, clearType);
break;
case VTActionCodes::DTTERM_WindowManipulation:
success = _GetWindowManipulationType(parameters, function);
break;
case VTActionCodes::REP_RepeatCharacter:
success = _GetRepeatCount(parameters, repeatCount);
break;
default:
// If no params to fill, param filling was successful.
success = true;
break;
}
// if param filling successful, try to dispatch
if (success)
{
switch (wch)
{
case VTActionCodes::CUU_CursorUp:
success = _dispatch->CursorUp(distance);
TermTelemetry::Instance().Log(TermTelemetry::Codes::CUU);
break;
case VTActionCodes::CUD_CursorDown:
success = _dispatch->CursorDown(distance);
TermTelemetry::Instance().Log(TermTelemetry::Codes::CUD);
break;
case VTActionCodes::CUF_CursorForward:
success = _dispatch->CursorForward(distance);
TermTelemetry::Instance().Log(TermTelemetry::Codes::CUF);
break;
case VTActionCodes::CUB_CursorBackward:
success = _dispatch->CursorBackward(distance);
TermTelemetry::Instance().Log(TermTelemetry::Codes::CUB);
break;
case VTActionCodes::CNL_CursorNextLine:
success = _dispatch->CursorNextLine(distance);
TermTelemetry::Instance().Log(TermTelemetry::Codes::CNL);
break;
case VTActionCodes::CPL_CursorPrevLine:
success = _dispatch->CursorPrevLine(distance);
TermTelemetry::Instance().Log(TermTelemetry::Codes::CPL);
break;
case VTActionCodes::CHA_CursorHorizontalAbsolute:
case VTActionCodes::HPA_HorizontalPositionAbsolute:
success = _dispatch->CursorHorizontalPositionAbsolute(distance);
TermTelemetry::Instance().Log(TermTelemetry::Codes::CHA);
break;
case VTActionCodes::VPA_VerticalLinePositionAbsolute:
success = _dispatch->VerticalLinePositionAbsolute(distance);
TermTelemetry::Instance().Log(TermTelemetry::Codes::VPA);
break;
case VTActionCodes::CUP_CursorPosition:
case VTActionCodes::HVP_HorizontalVerticalPosition:
success = _dispatch->CursorPosition(line, column);
TermTelemetry::Instance().Log(TermTelemetry::Codes::CUP);
break;
case VTActionCodes::DECSTBM_SetScrollingRegion:
success = _dispatch->SetTopBottomScrollingMargins(topMargin, bottomMargin);
TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSTBM);
break;
case VTActionCodes::ICH_InsertCharacter:
success = _dispatch->InsertCharacter(distance);
TermTelemetry::Instance().Log(TermTelemetry::Codes::ICH);
break;
case VTActionCodes::DCH_DeleteCharacter:
success = _dispatch->DeleteCharacter(distance);
TermTelemetry::Instance().Log(TermTelemetry::Codes::DCH);
break;
case VTActionCodes::ED_EraseDisplay:
success = _dispatch->EraseInDisplay(eraseType);
TermTelemetry::Instance().Log(TermTelemetry::Codes::ED);
break;
case VTActionCodes::EL_EraseLine:
success = _dispatch->EraseInLine(eraseType);
TermTelemetry::Instance().Log(TermTelemetry::Codes::EL);
break;
case VTActionCodes::SGR_SetGraphicsRendition:
success = _dispatch->SetGraphicsRendition({ graphicsOptions.data(), graphicsOptions.size() });
TermTelemetry::Instance().Log(TermTelemetry::Codes::SGR);
break;
case VTActionCodes::DSR_DeviceStatusReport:
success = _dispatch->DeviceStatusReport(deviceStatusType);
TermTelemetry::Instance().Log(TermTelemetry::Codes::DSR);
break;
case VTActionCodes::DA_DeviceAttributes:
success = _dispatch->DeviceAttributes();
TermTelemetry::Instance().Log(TermTelemetry::Codes::DA);
break;
case VTActionCodes::SU_ScrollUp:
success = _dispatch->ScrollUp(distance);
TermTelemetry::Instance().Log(TermTelemetry::Codes::SU);
break;
case VTActionCodes::SD_ScrollDown:
success = _dispatch->ScrollDown(distance);
TermTelemetry::Instance().Log(TermTelemetry::Codes::SD);
break;
case VTActionCodes::ANSISYSSC_CursorSave:
success = _dispatch->CursorSaveState();
TermTelemetry::Instance().Log(TermTelemetry::Codes::ANSISYSSC);
break;
case VTActionCodes::ANSISYSRC_CursorRestore:
success = _dispatch->CursorRestoreState();
TermTelemetry::Instance().Log(TermTelemetry::Codes::ANSISYSRC);
break;
case VTActionCodes::IL_InsertLine:
success = _dispatch->InsertLine(distance);
TermTelemetry::Instance().Log(TermTelemetry::Codes::IL);
break;
case VTActionCodes::DL_DeleteLine:
success = _dispatch->DeleteLine(distance);
TermTelemetry::Instance().Log(TermTelemetry::Codes::DL);
break;
case VTActionCodes::CHT_CursorForwardTab:
success = _dispatch->ForwardTab(numTabs);
TermTelemetry::Instance().Log(TermTelemetry::Codes::CHT);
break;
case VTActionCodes::CBT_CursorBackTab:
success = _dispatch->BackwardsTab(numTabs);
TermTelemetry::Instance().Log(TermTelemetry::Codes::CBT);
break;
case VTActionCodes::TBC_TabClear:
success = _dispatch->TabClear(clearType);
TermTelemetry::Instance().Log(TermTelemetry::Codes::TBC);
break;
case VTActionCodes::ECH_EraseCharacters:
success = _dispatch->EraseCharacters(distance);
TermTelemetry::Instance().Log(TermTelemetry::Codes::ECH);
break;
case VTActionCodes::DTTERM_WindowManipulation:
success = _dispatch->WindowManipulation(static_cast<DispatchTypes::WindowManipulationType>(function),
remainingParams);
TermTelemetry::Instance().Log(TermTelemetry::Codes::DTTERM_WM);
break;
case VTActionCodes::REP_RepeatCharacter:
// Handled w/o the dispatch. This function is unique in that way
// If this were in the ITerminalDispatch, then each
// implementation would effectively be the same, calling only
// functions that are already part of the interface.
// Print the last graphical character a number of times.
if (_lastPrintedChar != AsciiChars::NUL)
{
std::wstring wstr(repeatCount, _lastPrintedChar);
_dispatch->PrintString(wstr);
}
success = true;
TermTelemetry::Instance().Log(TermTelemetry::Codes::REP);
break;
default:
// If no functions to call, overall dispatch was a failure.
success = false;
break;
}
}
}
else if (intermediates.size() == 1)
{
const auto value = til::at(intermediates, 0);
switch (value)
{
case L'?':
success = _IntermediateQuestionMarkDispatch(wch, parameters);
break;
case L'!':
success = _IntermediateExclamationDispatch(wch);
break;
case L' ':
success = _IntermediateSpaceDispatch(wch, parameters);
break;
default:
// If no functions to call, overall dispatch was a failure.
success = false;
break;
}
}
// If we were unable to process the string, and there's a TTY attached to us,
// trigger the state machine to flush the string to the terminal.
if (_pfnFlushToTerminal != nullptr && !success)
{
success = _pfnFlushToTerminal();
}
_ClearLastChar();
return success;
}
// Routine Description:
// - Handles actions that have postfix params on an intermediate '?', such as DECTCEM, DECCOLM, ATT610
// Arguments:
// - wch - Character to dispatch.
// - parameters - set of numeric parameters collected while parsing the sequence.
// Return Value:
// - True if handled successfully. False otherwise.
bool OutputStateMachineEngine::_IntermediateQuestionMarkDispatch(const wchar_t wchAction,
const std::basic_string_view<size_t> parameters)
{
bool success = false;
std::vector<DispatchTypes::PrivateModeParams> privateModeParams;
// Ensure that there was the right number of params
switch (wchAction)
{
case VTActionCodes::DECSET_PrivateModeSet:
case VTActionCodes::DECRST_PrivateModeReset:
success = _GetPrivateModeParams(parameters, privateModeParams);
break;
default:
// If no params to fill, param filling was successful.
success = true;
break;
}
if (success)
{
switch (wchAction)
{
case VTActionCodes::DECSET_PrivateModeSet:
success = _dispatch->SetPrivateModes({ privateModeParams.data(), privateModeParams.size() });
//TODO: MSFT:6367459 Add specific logging for each of the DECSET/DECRST codes
TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSET);
break;
case VTActionCodes::DECRST_PrivateModeReset:
success = _dispatch->ResetPrivateModes({ privateModeParams.data(), privateModeParams.size() });
TermTelemetry::Instance().Log(TermTelemetry::Codes::DECRST);
break;
default:
// If no functions to call, overall dispatch was a failure.
success = false;
break;
}
}
return success;
}
// Routine Description:
// - Handles actions that have an intermediate '!', such as DECSTR
// Arguments:
// - wch - Character to dispatch.
// Return Value:
// - True if handled successfully. False otherwise.
bool OutputStateMachineEngine::_IntermediateExclamationDispatch(const wchar_t wchAction)
{
bool success = false;
switch (wchAction)
{
case VTActionCodes::DECSTR_SoftReset:
success = _dispatch->SoftReset();
TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSTR);
break;
default:
// If no functions to call, overall dispatch was a failure.
success = false;
break;
}
return success;
}
// Routine Description:
// - Handles actions that have an intermediate ' ' (0x20), such as DECSCUSR
// Arguments:
// - wch - Character to dispatch.
// - parameters - set of numeric parameters collected while parsing the sequence.
// Return Value:
// - True if handled successfully. False otherwise.
bool OutputStateMachineEngine::_IntermediateSpaceDispatch(const wchar_t wchAction,
const std::basic_string_view<size_t> parameters)
{
bool success = false;
DispatchTypes::CursorStyle cursorStyle = DefaultCursorStyle;
// Parse params
switch (wchAction)
{
case VTActionCodes::DECSCUSR_SetCursorStyle:
success = _GetCursorStyle(parameters, cursorStyle);
break;
default:
// If no functions to call, overall dispatch was a failure.
success = false;
break;
}
// if param filling successful, try to dispatch
if (success)
{
switch (wchAction)
{
case VTActionCodes::DECSCUSR_SetCursorStyle:
success = _dispatch->SetCursorStyle(cursorStyle);
TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSCUSR);
break;
default:
// If no functions to call, overall dispatch was a failure.
success = false;
break;
}
}
return success;
}
// Routine Description:
// - Triggers the Clear action to indicate that the state machine should erase
// all internal state.
// Arguments:
// - <none>
// Return Value:
// - <none>
bool OutputStateMachineEngine::ActionClear() noexcept
{
// do nothing.
return true;
}
// Routine Description:
// - Triggers the Ignore action to indicate that the state machine should eat
// this character and say nothing.
// Arguments:
// - <none>
// Return Value:
// - <none>
bool OutputStateMachineEngine::ActionIgnore() noexcept
{
// do nothing.
return true;
}
// Routine Description:
// - Triggers the OscDispatch action to indicate that the listener should handle a control sequence.
// These sequences perform various API-type commands that can include many parameters.
// Arguments:
// - wch - Character to dispatch. This will be a BEL or ST char.
// - parameter - identifier of the OSC action to perform
// - string - OSC string we've collected. NOT null terminated.
// Return Value:
// - true if we handled the dsipatch.
bool OutputStateMachineEngine::ActionOscDispatch(const wchar_t /*wch*/,
const size_t parameter,
const std::wstring_view string)
{
bool success = false;
std::wstring title;
size_t tableIndex = 0;
DWORD color = 0;
switch (parameter)
{
case OscActionCodes::SetIconAndWindowTitle:
case OscActionCodes::SetWindowIcon:
case OscActionCodes::SetWindowTitle:
success = _GetOscTitle(string, title);
break;
case OscActionCodes::SetColor:
success = _GetOscSetColorTable(string, tableIndex, color);
break;
case OscActionCodes::SetForegroundColor:
case OscActionCodes::SetBackgroundColor:
case OscActionCodes::SetCursorColor:
success = _GetOscSetColor(string, color);
break;
case OscActionCodes::ResetCursorColor:
// the console uses 0xffffffff as an "invalid color" value
color = 0xffffffff;
success = true;
break;
default:
// If no functions to call, overall dispatch was a failure.
success = false;
break;
}
if (success)
{
switch (parameter)
{
case OscActionCodes::SetIconAndWindowTitle:
case OscActionCodes::SetWindowIcon:
case OscActionCodes::SetWindowTitle:
success = _dispatch->SetWindowTitle(title);
TermTelemetry::Instance().Log(TermTelemetry::Codes::OSCWT);
break;
case OscActionCodes::SetColor:
success = _dispatch->SetColorTableEntry(tableIndex, color);
TermTelemetry::Instance().Log(TermTelemetry::Codes::OSCCT);
break;
case OscActionCodes::SetForegroundColor:
success = _dispatch->SetDefaultForeground(color);
TermTelemetry::Instance().Log(TermTelemetry::Codes::OSCFG);
break;
case OscActionCodes::SetBackgroundColor:
success = _dispatch->SetDefaultBackground(color);
TermTelemetry::Instance().Log(TermTelemetry::Codes::OSCBG);
break;
case OscActionCodes::SetCursorColor:
success = _dispatch->SetCursorColor(color);
TermTelemetry::Instance().Log(TermTelemetry::Codes::OSCSCC);
break;
case OscActionCodes::ResetCursorColor:
success = _dispatch->SetCursorColor(color);
TermTelemetry::Instance().Log(TermTelemetry::Codes::OSCRCC);
break;
default:
// If no functions to call, overall dispatch was a failure.
success = false;
break;
}
}
// If we were unable to process the string, and there's a TTY attached to us,
// trigger the state machine to flush the string to the terminal.
if (_pfnFlushToTerminal != nullptr && !success)
{
success = _pfnFlushToTerminal();
}
_ClearLastChar();
return success;
}
// Routine Description:
// - Triggers the Ss3Dispatch action to indicate that the listener should handle
// a control sequence. These sequences perform various API-type commands
// that can include many parameters.
// Arguments:
// - wch - Character to dispatch.
// - parameters - set of numeric parameters collected while parsing the sequence.
// Return Value:
// - true iff we successfully dispatched the sequence.
bool OutputStateMachineEngine::ActionSs3Dispatch(const wchar_t /*wch*/,
const std::basic_string_view<size_t> /*parameters*/) noexcept
{
// The output engine doesn't handle any SS3 sequences.
_ClearLastChar();
return false;
}
// Routine Description:
// - Retrieves the listed graphics options to be applied in order to the "font style" of the next characters inserted into the buffer.
// Arguments:
// - parameters - The parameters to parse
// - options - Space that will be filled with valid options from the GraphicsOptions enum
// Return Value:
// - True if we successfully retrieved an array of valid graphics options from the parameters we've stored. False otherwise.
bool OutputStateMachineEngine::_GetGraphicsOptions(const std::basic_string_view<size_t> parameters,
std::vector<DispatchTypes::GraphicsOptions>& options) const
{
bool success = false;
if (parameters.empty())
{
options.push_back(DefaultGraphicsOption);
success = true;
}
else
{
for (const auto& p : parameters)
{
options.push_back((DispatchTypes::GraphicsOptions)p);
}
success = true;
}
// If we were unable to process the string, and there's a TTY attached to us,
// trigger the state machine to flush the string to the terminal.
if (_pfnFlushToTerminal != nullptr && !success)
{
success = _pfnFlushToTerminal();
}
return success;
}
// Routine Description:
// - Retrieves the erase type parameter for an upcoming operation.
// Arguments:
// - parameters - The parameters to parse
// - eraseType - Receives the erase type parameter
// Return Value:
// - True if we successfully pulled an erase type from the parameters we've stored. False otherwise.
bool OutputStateMachineEngine::_GetEraseOperation(const std::basic_string_view<size_t> parameters,
DispatchTypes::EraseType& eraseType) const noexcept
{
bool success = false; // If we have too many parameters or don't know what to do with the given value, return false.
eraseType = DefaultEraseType; // if we fail, just put the default type in.
if (parameters.empty())
{
// Empty parameter sequences should use the default
eraseType = DefaultEraseType;
success = true;
}
else if (parameters.size() == 1)
{
// If there's one parameter, attempt to match it to the values we accept.
const auto param = static_cast<DispatchTypes::EraseType>(til::at(parameters, 0));
switch (param)
{
case DispatchTypes::EraseType::ToEnd:
case DispatchTypes::EraseType::FromBeginning:
case DispatchTypes::EraseType::All:
case DispatchTypes::EraseType::Scrollback:
eraseType = param;
success = true;
break;
}
}
return success;
}
// Routine Description:
// - Retrieves a distance for a cursor operation from the parameter pool stored during Param actions.
// Arguments:
// - parameters - The parameters to parse
// - distance - Receives the distance
// Return Value:
// - True if we successfully pulled the cursor distance from the parameters we've stored. False otherwise.
bool OutputStateMachineEngine::_GetCursorDistance(const std::basic_string_view<size_t> parameters,
size_t& distance) const noexcept
{
bool success = false;
distance = DefaultCursorDistance;
if (parameters.empty())
{
// Empty parameter sequences should use the default
success = true;
}
else if (parameters.size() == 1)
{
// If there's one parameter, use it.
distance = til::at(parameters, 0);
success = true;
}
// Distances of 0 should be changed to 1.
if (distance == 0)
{
distance = DefaultCursorDistance;
}
return success;
}
// Routine Description:
// - Retrieves a distance for a scroll operation from the parameter pool stored during Param actions.
// Arguments:
// - parameters - The parameters to parse
// - distance - Receives the distance
// Return Value:
// - True if we successfully pulled the scroll distance from the parameters we've stored. False otherwise.
bool OutputStateMachineEngine::_GetScrollDistance(const std::basic_string_view<size_t> parameters,
size_t& distance) const noexcept
{
bool success = false;
distance = DefaultScrollDistance;
if (parameters.empty())
{
// Empty parameter sequences should use the default
success = true;
}
else if (parameters.size() == 1)
{
// If there's one parameter, use it.
distance = til::at(parameters, 0);
success = true;
}
// Distances of 0 should be changed to 1.
if (distance == 0)
{
distance = DefaultScrollDistance;
}
return success;
}
// Routine Description:
// - Retrieves a width for the console window from the parameter pool stored during Param actions.
// Arguments:
// - parameters - The parameters to parse
// - consoleWidth - Receives the width
// Return Value:
// - True if we successfully pulled the width from the parameters we've stored. False otherwise.
bool OutputStateMachineEngine::_GetConsoleWidth(const std::basic_string_view<size_t> parameters,
size_t& consoleWidth) const noexcept
{
bool success = false;
consoleWidth = DefaultConsoleWidth;
if (parameters.empty())
{
// Empty parameter sequences should use the default
success = true;
}
else if (parameters.size() == 1)
{
// If there's one parameter, use it.
consoleWidth = til::at(parameters, 0);
success = true;
}
// Distances of 0 should be changed to 80.
if (consoleWidth == 0)
{
consoleWidth = DefaultConsoleWidth;
}
return success;
}
// Routine Description:
// - Retrieves an X/Y coordinate pair for a cursor operation from the parameter pool stored during Param actions.
// Arguments:
// - parameters - The parameters to parse
// - line - Receives the Y/Line/Row position
// - column - Receives the X/Column position
// Return Value:
// - True if we successfully pulled the cursor coordinates from the parameters we've stored. False otherwise.
bool OutputStateMachineEngine::_GetXYPosition(const std::basic_string_view<size_t> parameters,
size_t& line,
size_t& column) const noexcept
{
bool success = false;
line = DefaultLine;
column = DefaultColumn;
if (parameters.empty())
{
// Empty parameter sequences should use the default
success = true;
}
else if (parameters.size() == 1)
{
// If there's only one param, leave the default for the column, and retrieve the specified row.
line = til::at(parameters, 0);
success = true;
}
else if (parameters.size() == 2)
{
// If there are exactly two parameters, use them.
line = til::at(parameters, 0);
column = til::at(parameters, 1);
success = true;
}
// Distances of 0 should be changed to 1.
if (line == 0)
{
line = DefaultLine;
}
if (column == 0)
{
column = DefaultColumn;
}
return success;
}
// Routine Description:
// - Retrieves a top and bottom pair for setting the margins from the parameter pool stored during Param actions
// Arguments:
// - parameters - The parameters to parse
// - topMargin - Receives the top margin
// - bottomMargin - Receives the bottom margin
// Return Value:
// - True if we successfully pulled the margin settings from the parameters we've stored. False otherwise.
bool OutputStateMachineEngine::_GetTopBottomMargins(const std::basic_string_view<size_t> parameters,
size_t& topMargin,
size_t& bottomMargin) const noexcept
{
// Notes: (input -> state machine out)
// having only a top param is legal ([3;r -> 3,0)
// having only a bottom param is legal ([;3r -> 0,3)
// having neither uses the defaults ([;r [r -> 0,0)
// an illegal combo (eg, 3;2r) is ignored
bool success = false;
topMargin = DefaultTopMargin;
bottomMargin = DefaultBottomMargin;
if (parameters.empty())
{
// Empty parameter sequences should use the default
success = true;
}
else if (parameters.size() == 1)
{
topMargin = til::at(parameters, 0);
success = true;
}
else if (parameters.size() == 2)
{
// If there are exactly two parameters, use them.
topMargin = til::at(parameters, 0);
bottomMargin = til::at(parameters, 1);
success = true;
}
if (bottomMargin > 0 && bottomMargin < topMargin)
{
success = false;
}
return success;
}
// Routine Description:
// - Retrieves the status type parameter for an upcoming device query operation
// Arguments:
// - parameters - The parameters to parse
// - statusType - Receives the Status Type parameter
// Return Value:
// - True if we successfully found a device operation in the parameters stored. False otherwise.
bool OutputStateMachineEngine::_GetDeviceStatusOperation(const std::basic_string_view<size_t> parameters,
DispatchTypes::AnsiStatusType& statusType) const noexcept
{
bool success = false;
statusType = static_cast<DispatchTypes::AnsiStatusType>(0);
if (parameters.size() == 1)
{
// If there's one parameter, attempt to match it to the values we accept.
const auto param = til::at(parameters, 0);
switch (param)
{
// This looks kinda silly, but I want the parser to reject (success = false) any status types we haven't put here.
case (unsigned short)DispatchTypes::AnsiStatusType::CPR_CursorPositionReport:
statusType = DispatchTypes::AnsiStatusType::CPR_CursorPositionReport;
success = true;
break;
}
}
return success;
}
// Routine Description:
// - Retrieves the listed private mode params be set/reset by DECSET/DECRST
// Arguments:
// - parameters - The parameters to parse
// - privateModes - Space that will be filled with valid params from the PrivateModeParams enum
// Return Value:
// - True if we successfully retrieved an array of private mode params from the parameters we've stored. False otherwise.
bool OutputStateMachineEngine::_GetPrivateModeParams(const std::basic_string_view<size_t> parameters,
std::vector<DispatchTypes::PrivateModeParams>& privateModes) const
{
bool success = false;
// Can't just set nothing at all
if (parameters.size() > 0)
{
for (const auto& p : parameters)
{
privateModes.push_back((DispatchTypes::PrivateModeParams)p);
}
success = true;
}
return success;
}
// - Verifies that no parameters were parsed for the current CSI sequence
// Arguments:
// - parameters - The parameters to parse
// Return Value:
// - True if there were no parameters. False otherwise.
bool OutputStateMachineEngine::_VerifyHasNoParameters(const std::basic_string_view<size_t> parameters) const noexcept
{
return parameters.empty();
}
// Routine Description:
// - Validates that we received the correct parameter sequence for the Device Attributes command.
// - For DA, we should have received either NO parameters or just one 0 parameter. Anything else is not acceptable.
// Arguments:
// - parameters - The parameters to parse
// Return Value:
// - True if the DA params were valid. False otherwise.
bool OutputStateMachineEngine::_VerifyDeviceAttributesParams(const std::basic_string_view<size_t> parameters) const noexcept
{
bool success = false;
if (parameters.empty())
{
success = true;
}
else if (parameters.size() == 1)
{
if (til::at(parameters, 0) == 0)
{
success = true;
}
}
return success;
}
// Routine Description:
// - Null terminates, then returns, the string that we've collected as part of the OSC string.
// Arguments:
// - string - Osc String input
// - title - Where to place the Osc String to use as a title.
// Return Value:
// - True if there was a title to output. (a title with length=0 is still valid)
bool OutputStateMachineEngine::_GetOscTitle(const std::wstring_view string,
std::wstring& title) const
{
title = string;
return !string.empty();
}
// Routine Description:
// - Retrieves a distance for a tab operation from the parameter pool stored during Param actions.
// Arguments:
// - parameters - The parameters to parse
// - distance - Receives the distance
// Return Value:
// - True if we successfully pulled the tab distance from the parameters we've stored. False otherwise.
bool OutputStateMachineEngine::_GetTabDistance(const std::basic_string_view<size_t> parameters,
size_t& distance) const noexcept
{
bool success = false;
distance = DefaultTabDistance;
if (parameters.empty())
{
// Empty parameter sequences should use the default
success = true;
}
else if (parameters.size() == 1)
{
// If there's one parameter, use it.
distance = til::at(parameters, 0);
success = true;
}
// Distances of 0 should be changed to 1.
if (distance == 0)
{
distance = DefaultTabDistance;
}
return success;
}
// Routine Description:
// - Retrieves the type of tab clearing operation from the parameter pool stored during Param actions.
// Arguments:
// - parameters - The parameters to parse
// - clearType - Receives the clear type
// Return Value:
// - True if we successfully pulled the tab clear type from the parameters we've stored. False otherwise.
bool OutputStateMachineEngine::_GetTabClearType(const std::basic_string_view<size_t> parameters,
size_t& clearType) const noexcept
{
bool success = false;
clearType = DefaultTabClearType;
if (parameters.empty())
{
// Empty parameter sequences should use the default
success = true;
}
else if (parameters.size() == 1)
{
// If there's one parameter, use it.
clearType = til::at(parameters, 0);
success = true;
}
return success;
}
// Routine Description:
// - Retrieves a designate charset type from the intermediate we've stored. False otherwise.
// Arguments:
// - intermediate - Intermediate character in the sequence
// - designateType - Receives the designate type.
// Return Value:
// - True if we successfully pulled the designate type from the intermediate we've stored. False otherwise.
bool OutputStateMachineEngine::_GetDesignateType(const wchar_t intermediate,
DesignateCharsetTypes& designateType) const noexcept
{
bool success = false;
designateType = DefaultDesignateCharsetType;
switch (intermediate)
{
case '(':
designateType = DesignateCharsetTypes::G0;
success = true;
break;
case ')':
case '-':
designateType = DesignateCharsetTypes::G1;
success = true;
break;
case '*':
case '.':
designateType = DesignateCharsetTypes::G2;
success = true;
break;
case '+':
case '/':
designateType = DesignateCharsetTypes::G3;
success = true;
break;
}
return success;
}
// Routine Description:
// - Returns true if the engine should dispatch on the last charater of a string
// always, even if the sequence hasn't normally dispatched.
// If this is false, the engine will persist its state across calls to
// ProcessString, and dispatch only at the end of the sequence.
// Return Value:
// - True iff we should manually dispatch on the last character of a string.
bool OutputStateMachineEngine::FlushAtEndOfString() const noexcept
{
return false;
}
// Routine Description:
// - Returns true if the engine should dispatch control characters in the Escape
// state. Typically, control characters are immediately executed in the
// Escape state without returning to ground. If this returns true, the
// state machine will instead call ActionExecuteFromEscape and then enter
// the Ground state when a control character is encountered in the escape
// state.
// Return Value:
// - True iff we should return to the Ground state when the state machine
// encounters a Control (C0) character in the Escape state.
bool OutputStateMachineEngine::DispatchControlCharsFromEscape() const noexcept
{
return false;
}
// Routine Description:
// - Returns false if the engine wants to be able to collect intermediate
// characters in the Escape state. We do want to buffer characters as
// intermediates. We need them for things like Designate G0 Character Set
// Return Value:
// - True iff we should dispatch in the Escape state when we encounter a
// Intermediate character.
bool OutputStateMachineEngine::DispatchIntermediatesFromEscape() const noexcept
{
return false;
}
// Routine Description:
// - Converts a hex character to its equivalent integer value.
// Arguments:
// - wch - Character to convert.
// - value - recieves the int value of the char
// Return Value:
// - true iff the character is a hex character.
bool OutputStateMachineEngine::s_HexToUint(const wchar_t wch,
unsigned int& value) noexcept
{
value = 0;
bool success = false;
if (wch >= L'0' && wch <= L'9')
{
value = wch - L'0';
success = true;
}
else if (wch >= L'A' && wch <= L'F')
{
value = (wch - L'A') + 10;
success = true;
}
else if (wch >= L'a' && wch <= L'f')
{
value = (wch - L'a') + 10;
success = true;
}
return success;
}
// Routine Description:
// - Determines if a character is a valid number character, 0-9.
// Arguments:
// - wch - Character to check.
// Return Value:
// - True if it is. False if it isn't.
static constexpr bool _isNumber(const wchar_t wch) noexcept
{
return wch >= L'0' && wch <= L'9'; // 0x30 - 0x39
}
// Routine Description:
// - Determines if a character is a valid hex character, 0-9a-fA-F.
// Arguments:
// - wch - Character to check.
// Return Value:
// - True if it is. False if it isn't.
static constexpr bool _isHexNumber(const wchar_t wch) noexcept
{
return (wch >= L'0' && wch <= L'9') || // 0x30 - 0x39
(wch >= L'A' && wch <= L'F') ||
(wch >= L'a' && wch <= L'f');
}
// Routine Description:
// - Given a color spec string, attempts to parse the color that's encoded.
// The only supported spec currently is the following:
// spec: a color in the following format:
// "rgb:<red>/<green>/<blue>"
// where <color> is one or two hex digits, upper or lower case.
// Arguments:
// - string - The string containing the color spec string to parse.
// - rgb - recieves the color that we parsed
// Return Value:
// - True if a color was successfully parsed
bool OutputStateMachineEngine::s_ParseColorSpec(const std::wstring_view string,
DWORD& rgb) noexcept
{
bool foundRGB = false;
bool foundValidColorSpec = false;
std::array<unsigned int, 3> colorValues = { 0 };
bool success = false;
// We can have anywhere between [11,15] characters
// 9 "rgb:h/h/h"
// 12 "rgb:hh/hh/hh"
// Any fewer cannot be valid, and any more will be too many.
// Return early in this case.
// We'll still have to bounds check when parsing the hh/hh/hh values
if (string.size() < 9 || string.size() > 12)
{
return false;
}
// Now we look for "rgb:"
// Other colorspaces are theoretically possible, but we don't support them.
auto curr = string.cbegin();
if ((*curr++ == L'r') &&
(*curr++ == L'g') &&
(*curr++ == L'b') &&
(*curr++ == L':'))
{
foundRGB = true;
}
if (foundRGB)
{
// Colorspecs are up to hh/hh/hh, for 1-2 h's
for (size_t component = 0; component < 3; component++)
{
bool foundColor = false;
auto& value = colorValues.at(component);
for (size_t i = 0; i < 3; i++)
{
const wchar_t wch = *curr++;
if (_isHexNumber(wch))
{
value *= 16;
unsigned int intVal = 0;
if (s_HexToUint(wch, intVal))
{
value += intVal;
}
else
{
// Encountered something weird oh no
foundColor = false;
break;
}
// If we're on the blue component, we're not going to see a /.
// Break out once we hit the end.
if (component == 2 && curr >= string.cend())
{
foundValidColorSpec = true;
break;
}
}
else if (wch == L'/')
{
// Break this component, and start the next one.
foundColor = true;
break;
}
else
{
// Encountered something weird oh no
foundColor = false;
break;
}
}
if (!foundColor || curr >= string.cend())
{
// Indicates there was a some error parsing color
// or we're at the end of the string.
break;
}
}
}
// Only if we find a valid colorspec can we pass it out successfully.
if (foundValidColorSpec)
{
DWORD color = RGB(LOBYTE(colorValues.at(0)),
LOBYTE(colorValues.at(1)),
LOBYTE(colorValues.at(2)));
rgb = color;
success = true;
}
return success;
}
// Routine Description:
// - OSC 4 ; c ; spec ST
// c: the index of the ansi color table
// spec: a color in the following format:
// "rgb:<red>/<green>/<blue>"
// where <color> is two hex digits
// Arguments:
// - string - the Osc String to parse
// - tableIndex - recieves the table index
// - rgb - recieves the color that we parsed in the format: 0x00BBGGRR
// Return Value:
// - True if a table index and color was parsed successfully. False otherwise.
bool OutputStateMachineEngine::_GetOscSetColorTable(const std::wstring_view string,
size_t& tableIndex,
DWORD& rgb) const noexcept
{
tableIndex = 0;
rgb = 0;
size_t _TableIndex = 0;
bool foundTableIndex = false;
bool success = false;
// We can have anywhere between [11,16] characters
// 11 "#;rgb:h/h/h"
// 16 "###;rgb:hh/hh/hh"
// Any fewer cannot be valid, and any more will be too many.
// Return early in this case.
// We'll still have to bounds check when parsing the hh/hh/hh values
if (string.size() < 11 || string.size() > 16)
{
return false;
}
// First try to get the table index, a number between [0,256]
size_t current = 0;
for (size_t i = 0; i < 4; i++)
{
const wchar_t wch = string.at(current);
if (_isNumber(wch))
{
_TableIndex *= 10;
_TableIndex += wch - L'0';
++current;
}
else if (wch == L';' && i > 0)
{
// We need to explicitly pass in a number, we can't default to 0 if
// there's no param
++current;
foundTableIndex = true;
break;
}
else
{
// Found an unexpected character, fail.
break;
}
}
// Now we look for "rgb:"
// Other colorspaces are theoretically possible, but we don't support them.
if (foundTableIndex)
{
DWORD color = 0;
success = s_ParseColorSpec(string.substr(current), color);
if (success)
{
tableIndex = _TableIndex;
rgb = color;
}
}
return success;
}
// Routine Description:
// - OSC 10, 11, 12 ; spec ST
// spec: a color in the following format:
// "rgb:<red>/<green>/<blue>"
// where <color> is two hex digits
// Arguments:
// - string - the Osc String to parse
// - rgb - recieves the color that we parsed in the format: 0x00BBGGRR
// Return Value:
// - True if a table index and color was parsed successfully. False otherwise.
bool OutputStateMachineEngine::_GetOscSetColor(const std::wstring_view string,
DWORD& rgb) const noexcept
{
rgb = 0;
bool success = false;
DWORD color = 0;
success = s_ParseColorSpec(string, color);
if (success)
{
rgb = color;
}
return success;
}
// Method Description:
// - Retrieves the type of window manipulation operation from the parameter pool
// stored during Param actions.
// This is kept seperate from the input version, as there may be
// codes that are supported in one direction but not the other.
// Arguments:
// - parameters - The parameters to parse
// - function - Receives the function type
// Return Value:
// - True iff we successfully pulled the function type from the parameters
bool OutputStateMachineEngine::_GetWindowManipulationType(const std::basic_string_view<size_t> parameters,
unsigned int& function) const noexcept
{
bool success = false;
function = DefaultWindowManipulationType;
if (parameters.size() > 0)
{
switch (til::at(parameters, 0))
{
case DispatchTypes::WindowManipulationType::RefreshWindow:
function = DispatchTypes::WindowManipulationType::RefreshWindow;
success = true;
break;
case DispatchTypes::WindowManipulationType::ResizeWindowInCharacters:
function = DispatchTypes::WindowManipulationType::ResizeWindowInCharacters;
success = true;
break;
default:
success = false;
break;
}
}
return success;
}
// Routine Description:
// - Retrieves the cursor style from the parameter list
// Arguments:
// - parameters - The parameters to parse
// - cursorStyle - Receives the cursorStyle
// Return Value:
// - True if we successfully pulled the cursor style from the parameters we've stored. False otherwise.
bool OutputStateMachineEngine::_GetCursorStyle(const std::basic_string_view<size_t> parameters,
DispatchTypes::CursorStyle& cursorStyle) const noexcept
{
bool success = false;
cursorStyle = DefaultCursorStyle;
if (parameters.empty())
{
// Empty parameter sequences should use the default
success = true;
}
else if (parameters.size() == 1)
{
// If there's one parameter, use it.
cursorStyle = (DispatchTypes::CursorStyle)til::at(parameters, 0);
success = true;
}
return success;
}
// Method Description:
// - Sets us up to have another terminal acting as the tty instead of conhost.
// We'll set a couple members, and if they aren't null, when we get a
// sequence we don't understand, we'll pass it along to the terminal
// instead of eating it ourselves.
// Arguments:
// - pTtyConnection: This is a TerminalOutputConnection that we can write the
// sequence we didn't understand to.
// - pfnFlushToTerminal: This is a callback to the underlying state machine to
// trigger it to call ActionPassThroughString with whatever sequence it's
// currently processing.
// Return Value:
// - <none>
void OutputStateMachineEngine::SetTerminalConnection(ITerminalOutputConnection* const pTtyConnection,
std::function<bool()> pfnFlushToTerminal)
{
this->_pTtyConnection = pTtyConnection;
this->_pfnFlushToTerminal = pfnFlushToTerminal;
}
// Routine Description:
// - Retrieves a number of times to repeat the last graphical character
// Arguments:
// - parameters - The parameters to parse
// - repeatCount - Receives the repeat count
// Return Value:
// - True if we successfully pulled the repeat count from the parameters.
// False otherwise.
bool OutputStateMachineEngine::_GetRepeatCount(std::basic_string_view<size_t> parameters,
size_t& repeatCount) const noexcept
{
bool success = false;
repeatCount = DefaultRepeatCount;
if (parameters.empty())
{
// Empty parameter sequences should use the default
success = true;
}
else if (parameters.size() == 1)
{
// If there's one parameter, use it.
repeatCount = til::at(parameters, 0);
success = true;
}
// Distances of 0 should be changed to 1.
if (repeatCount == 0)
{
repeatCount = DefaultRepeatCount;
}
return success;
}
// Method Description:
// - Clears our last stored character. The last stored character is the last
// graphical character we printed, which is reset if any other action is
// dispatched.
// Arguments:
// - <none>
// Return Value:
// - <none>
void OutputStateMachineEngine::_ClearLastChar() noexcept
{
_lastPrintedChar = AsciiChars::NUL;
}