Refactor VT parameter handling (#7799)

This PR introduces a pair of classes for managing VT parameters that
automatically handle range checking and default fallback values, so the
individual operations don't have to do that validation themselves. In
addition to simplifying the code, this fixes a few cases where we were
mishandling missing or extraneous parameters, and adds support for
parameter sequences on commands that couldn't previously handle them.
This PR also sets a limit on the number of parameters allowed, to help
thwart DoS memory consumption attacks.

## References

* The new parameter class also introduces the concept of an
  omitted/default parameter which is not necessarily zero, which is a
  prerequisite for addressing issue #4417.

## Detailed Description of the Pull Request / Additional comments

There are two new classes provide by this PR: a `VTParameter` class,
similar in function to a `std::optional<size_t>`, which holds an
individual parameter (which may be an omitted/default value); and a
`VTParameters` class, similar in function to `gsl:span<VTParameter>`,
which holds a sequence of those parameters.

Where `VTParameter` differs from `std::optional` is with the inclusion
of two cast operators. There is a `size_t` cast that interprets omitted
and zero values as 1 (the expected behaviour for most numeric
parameters). And there is a generic cast, for use with the enum
parameter types, which interprets omitted values as 0 (the expected
behaviour for most selective parameters).

The advantage of `VTParameters` class is that it has an `at` method that
can never fail - out of range values simply return the a default
`VTParameter` instance (this is standard behaviour in VT terminals). It
also has a `size` method that will always return a minimum count of 1,
since an empty parameter list is typically the equivalent of a single
"default" parameter, so this guarantees you'll get at least one value
when iterating over the list with `size()`.

For cases where we just need to call the same dispatch method for every
parameter, there is a helper `for_each` method, which repeatedly calls a
given predicate function with each value in the sequence. It also
collates the returned success values to determine the overall result of
the sequence. As with the `size` method, this will always make at least
one call, so it correctly handles empty sequences.

With those two classes in place, we could get rid of all the parameter
validation and default handling code in the `OutputStateMachineEngine`.
We now just use the `VTParameters::at` method to grab a parameter and
typically pass it straight to the appropriate dispatch method, letting
the cast operators automatically handle the assignment of default
values. Occasionally we might need a `value_or` call to specify a
non-standard default value, but those cases are fairly rare.

In some case the `OutputStateMachineEngine` was also checking whether
parameters values were in range, but for the most part this shouldn't
have been necessary, since that is something the dispatch classes would
already have been doing themselves (in the few cases that they weren't,
I've now updated them to do so).

I've also updated the `InputStateMachineEngine` in a similar way to the
`OutputStateMachineEngine`, getting rid of a few of the parameter
extraction methods, and simplifying other parts of the implementation.
It's not as clean a replacement as the output engine, but there are
still benefits in using the new classes.

## Validation Steps Performed

For the most part I haven't had to alter existing tests other than
accounting for changes to the API. There were a couple of tests I needed
to drop because they were checking for failure cases which shouldn't
have been failing (unexpected parameters should never be an error), or
testing output engine validation that is no longer handled at that
level.

I've added a few new tests to cover operations that take sequences of
selective parameters (`ED`, `EL`, `TBC`, `SM`, and `RM`). And I've
extended the cursor movement tests to make sure those operations can
handle extraneous parameters that weren't expected. I've also added a
test to verify that the state machine will correctly ignore parameters
beyond the maximum 32 parameter count limit.

I've also manual confirmed that the various test cases given in issues
#2101 are now working as expected.

Closes #2101
This commit is contained in:
James Holderness 2020-10-15 17:12:52 +01:00 committed by GitHub
parent 6e8388e683
commit 55151a4a04
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 739 additions and 1449 deletions

View file

@ -249,7 +249,7 @@ try
startPos.X = viewport.Left();
nlength = viewport.RightExclusive() - startPos.X;
break;
case DispatchTypes::EraseType::Scrollback:
default:
return false;
}

View file

@ -368,14 +368,14 @@ bool TerminalDispatch::EnableAlternateScroll(const bool enabled) noexcept
return true;
}
bool TerminalDispatch::SetPrivateModes(const gsl::span<const DispatchTypes::PrivateModeParams> params) noexcept
bool TerminalDispatch::SetPrivateMode(const DispatchTypes::PrivateModeParams param) noexcept
{
return _SetResetPrivateModes(params, true);
return _PrivateModeParamsHelper(param, true);
}
bool TerminalDispatch::ResetPrivateModes(const gsl::span<const DispatchTypes::PrivateModeParams> params) noexcept
bool TerminalDispatch::ResetPrivateMode(const DispatchTypes::PrivateModeParams param) noexcept
{
return _SetResetPrivateModes(params, false);
return _PrivateModeParamsHelper(param, false);
}
// Method Description:
@ -399,32 +399,10 @@ bool TerminalDispatch::EndHyperlink() noexcept
return _terminalApi.EndHyperlink();
}
// Routine Description:
// - Generalized handler for the setting/resetting of DECSET/DECRST parameters.
// All params in the rgParams will attempt to be executed, even if one
// fails, to allow us to successfully re/set params that are chained with
// params we don't yet support.
// Arguments:
// - params - array of params to set/reset
// - enable - True for set, false for unset.
// Return Value:
// - True if ALL params were handled successfully. False otherwise.
bool TerminalDispatch::_SetResetPrivateModes(const gsl::span<const DispatchTypes::PrivateModeParams> params, const bool enable) noexcept
{
// because the user might chain together params we don't support with params we DO support, execute all
// params in the sequence, and only return failure if we failed at least one of them
size_t failures = 0;
for (const auto& p : params)
{
failures += _PrivateModeParamsHelper(p, enable) ? 0 : 1; // increment the number of failures if we fail.
}
return failures == 0;
}
// Routine Description:
// - Support routine for routing private mode parameters to be set/reset as flags
// Arguments:
// - params - array of params to set/reset
// - param - mode parameter to set/reset
// - enable - True for set, false for unset.
// Return Value:
// - True if handled successfully. False otherwise.
@ -503,8 +481,7 @@ bool TerminalDispatch::SoftReset() noexcept
// success = _pConApi->SetConsoleOutputCP(_initialCodePage.value()) && success;
// }
const auto opt = DispatchTypes::GraphicsOptions::Off;
success = SetGraphicsRendition({ &opt, 1 }) && success; // Normal rendition.
success = SetGraphicsRendition({}) && success; // Normal rendition.
// // Reset the saved cursor state.
// // Note that XTerm only resets the main buffer state, but that

View file

@ -13,7 +13,7 @@ public:
void Print(const wchar_t wchPrintable) noexcept override;
void PrintString(const std::wstring_view string) noexcept override;
bool SetGraphicsRendition(const gsl::span<const ::Microsoft::Console::VirtualTerminal::DispatchTypes::GraphicsOptions> options) noexcept override;
bool SetGraphicsRendition(const ::Microsoft::Console::VirtualTerminal::VTParameters options) noexcept override;
bool CursorPosition(const size_t line,
const size_t column) noexcept override; // CUP
@ -61,8 +61,8 @@ public:
bool EnableAnyEventMouseMode(const bool enabled) noexcept override; // ?1003
bool EnableAlternateScroll(const bool enabled) noexcept override; // ?1007
bool SetPrivateModes(const gsl::span<const ::Microsoft::Console::VirtualTerminal::DispatchTypes::PrivateModeParams> /*params*/) noexcept override; // DECSET
bool ResetPrivateModes(const gsl::span<const ::Microsoft::Console::VirtualTerminal::DispatchTypes::PrivateModeParams> /*params*/) noexcept override; // DECRST
bool SetPrivateMode(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::PrivateModeParams /*param*/) noexcept override; // DECSET
bool ResetPrivateMode(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::PrivateModeParams /*param*/) noexcept override; // DECRST
bool AddHyperlink(const std::wstring_view uri, const std::wstring_view params) noexcept override;
bool EndHyperlink() noexcept override;
@ -70,10 +70,9 @@ public:
private:
::Microsoft::Terminal::Core::ITerminalApi& _terminalApi;
size_t _SetRgbColorsHelper(const gsl::span<const ::Microsoft::Console::VirtualTerminal::DispatchTypes::GraphicsOptions> options,
size_t _SetRgbColorsHelper(const ::Microsoft::Console::VirtualTerminal::VTParameters options,
TextAttribute& attr,
const bool isForeground) noexcept;
bool _SetResetPrivateModes(const gsl::span<const ::Microsoft::Console::VirtualTerminal::DispatchTypes::PrivateModeParams> params, const bool enable) noexcept;
bool _PrivateModeParamsHelper(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::PrivateModeParams param, const bool enable) noexcept;
};

View file

@ -41,43 +41,39 @@ const BYTE BRIGHT_WHITE = BRIGHT_ATTR | RED_ATTR | GREEN_ATTR | BLUE_ATTR;
// - isForeground - Whether or not the parsed color is for the foreground.
// Return Value:
// - The number of options consumed, not including the initial 38/48.
size_t TerminalDispatch::_SetRgbColorsHelper(const gsl::span<const DispatchTypes::GraphicsOptions> options,
size_t TerminalDispatch::_SetRgbColorsHelper(const VTParameters options,
TextAttribute& attr,
const bool isForeground) noexcept
{
size_t optionsConsumed = 0;
if (options.size() >= 1)
size_t optionsConsumed = 1;
const DispatchTypes::GraphicsOptions typeOpt = options.at(0);
if (typeOpt == DispatchTypes::GraphicsOptions::RGBColorOrFaint)
{
optionsConsumed = 1;
const auto typeOpt = til::at(options, 0);
if (typeOpt == DispatchTypes::GraphicsOptions::RGBColorOrFaint && options.size() >= 4)
optionsConsumed = 4;
const size_t red = options.at(1).value_or(0);
const size_t green = options.at(2).value_or(0);
const size_t blue = options.at(3).value_or(0);
// ensure that each value fits in a byte
if (red <= 255 && green <= 255 && blue <= 255)
{
optionsConsumed = 4;
const size_t red = til::at(options, 1);
const size_t green = til::at(options, 2);
const size_t blue = til::at(options, 3);
// ensure that each value fits in a byte
if (red <= 255 && green <= 255 && blue <= 255)
{
const COLORREF rgbColor = RGB(red, green, blue);
attr.SetColor(rgbColor, isForeground);
}
const COLORREF rgbColor = RGB(red, green, blue);
attr.SetColor(rgbColor, isForeground);
}
else if (typeOpt == DispatchTypes::GraphicsOptions::BlinkOrXterm256Index && options.size() >= 2)
}
else if (typeOpt == DispatchTypes::GraphicsOptions::BlinkOrXterm256Index)
{
optionsConsumed = 2;
const size_t tableIndex = options.at(1).value_or(0);
if (tableIndex <= 255)
{
optionsConsumed = 2;
const size_t tableIndex = til::at(options, 1);
if (tableIndex <= 255)
const auto adjustedIndex = gsl::narrow_cast<BYTE>(tableIndex);
if (isForeground)
{
const auto adjustedIndex = gsl::narrow_cast<BYTE>(tableIndex);
if (isForeground)
{
attr.SetIndexedForeground256(adjustedIndex);
}
else
{
attr.SetIndexedBackground256(adjustedIndex);
}
attr.SetIndexedForeground256(adjustedIndex);
}
else
{
attr.SetIndexedBackground256(adjustedIndex);
}
}
}
@ -94,14 +90,14 @@ size_t TerminalDispatch::_SetRgbColorsHelper(const gsl::span<const DispatchTypes
// one at a time by setting or removing flags in the font style properties.
// Return Value:
// - True if handled successfully. False otherwise.
bool TerminalDispatch::SetGraphicsRendition(const gsl::span<const DispatchTypes::GraphicsOptions> options) noexcept
bool TerminalDispatch::SetGraphicsRendition(const VTParameters options) noexcept
{
TextAttribute attr = _terminalApi.GetTextAttributes();
// Run through the graphics options and apply them
for (size_t i = 0; i < options.size(); i++)
{
const auto opt = til::at(options, i);
const GraphicsOptions opt = options.at(i);
switch (opt)
{
case Off:

View file

@ -3818,7 +3818,7 @@ void ScreenBufferTests::EraseTests()
END_TEST_METHOD_PROPERTIES()
DispatchTypes::EraseType eraseType;
VERIFY_SUCCEEDED(TestData::TryGetValue(L"eraseType", (int&)eraseType));
VERIFY_SUCCEEDED(TestData::TryGetValue(L"eraseType", (size_t&)eraseType));
bool eraseScreen;
VERIFY_SUCCEEDED(TestData::TryGetValue(L"eraseScreen", eraseScreen));

View file

@ -89,11 +89,110 @@ namespace Microsoft::Console::VirtualTerminal
uint64_t _idAccumulator = 0;
size_t _idShift = 0;
};
class VTParameter
{
public:
constexpr VTParameter() noexcept :
_value{ -1 }
{
}
constexpr VTParameter(const size_t rhs) noexcept :
_value{ gsl::narrow_cast<decltype(_value)>(rhs) }
{
}
constexpr bool has_value() const noexcept
{
// A negative value indicates that the parameter was omitted.
return _value >= 0;
}
constexpr size_t value() const noexcept
{
return _value;
}
constexpr size_t value_or(size_t defaultValue) const noexcept
{
return has_value() ? _value : defaultValue;
}
template<typename T, std::enable_if_t<sizeof(T) == sizeof(size_t), int> = 0>
constexpr operator T() const noexcept
{
// For most selective parameters, omitted values will default to 0.
return static_cast<T>(value_or(0));
}
constexpr operator size_t() const noexcept
{
// For numeric parameters, both 0 and omitted values will default to 1.
return has_value() && _value != 0 ? _value : 1;
}
private:
std::make_signed<size_t>::type _value;
};
class VTParameters
{
public:
constexpr VTParameters() noexcept
{
}
constexpr VTParameters(const VTParameter* ptr, const size_t count) noexcept :
_values{ ptr, count }
{
}
constexpr VTParameter at(const size_t index) const noexcept
{
// If the index is out of range, we return a parameter with no value.
return index < _values.size() ? _values[index] : VTParameter{};
}
constexpr bool empty() const noexcept
{
return _values.empty();
}
constexpr size_t size() const noexcept
{
// We always return a size of at least 1, since an empty parameter
// list is the equivalent of a single "default" parameter.
return std::max<size_t>(_values.size(), 1);
}
VTParameters subspan(const size_t offset) const noexcept
{
const auto subValues = _values.subspan(offset);
return { subValues.data(), subValues.size() };
}
template<typename T>
bool for_each(const T&& predicate) const
{
// We always return at least 1 value here, since an empty parameter
// list is the equivalent of a single "default" parameter.
auto success = predicate(at(0));
for (auto i = 1u; i < _values.size(); i++)
{
success = predicate(_values[i]) && success;
}
return success;
}
private:
gsl::span<const VTParameter> _values;
};
}
namespace Microsoft::Console::VirtualTerminal::DispatchTypes
{
enum class EraseType : unsigned int
enum class EraseType : size_t
{
ToEnd = 0,
FromBeginning = 1,
@ -101,7 +200,7 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes
Scrollback = 3
};
enum GraphicsOptions : unsigned int
enum GraphicsOptions : size_t
{
Off = 0,
BoldBright = 1,
@ -163,13 +262,13 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes
BrightBackgroundWhite = 107,
};
enum class AnsiStatusType : unsigned int
enum class AnsiStatusType : size_t
{
OS_OperatingStatus = 5,
CPR_CursorPositionReport = 6,
};
enum PrivateModeParams : unsigned short
enum PrivateModeParams : size_t
{
DECCKM_CursorKeysMode = 1,
DECANM_AnsiMode = 2,
@ -202,20 +301,20 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes
UTF8 = VTID("G")
};
enum TabClearType : unsigned short
enum TabClearType : size_t
{
ClearCurrentColumn = 0,
ClearAllColumns = 3
};
enum WindowManipulationType : unsigned int
enum WindowManipulationType : size_t
{
Invalid = 0,
RefreshWindow = 7,
ResizeWindowInCharacters = 8,
};
enum class CursorStyle : unsigned int
enum class CursorStyle : size_t
{
UserDefault = 0, // Implemented as "restore cursor to user default".
BlinkingBlock = 1,

View file

@ -35,7 +35,8 @@ namespace Microsoft::Console::VirtualTerminal
virtual bool WriteString(const std::wstring_view string) = 0;
virtual bool WindowManipulation(const DispatchTypes::WindowManipulationType function,
const gsl::span<const size_t> parameters) = 0;
const VTParameter parameter1,
const VTParameter parameter2) = 0;
virtual bool MoveCursor(const size_t row,
const size_t col) = 0;

View file

@ -70,7 +70,7 @@ public:
virtual bool HorizontalTabSet() = 0; // HTS
virtual bool ForwardTab(const size_t numTabs) = 0; // CHT, HT
virtual bool BackwardsTab(const size_t numTabs) = 0; // CBT
virtual bool TabClear(const size_t clearType) = 0; // TBC
virtual bool TabClear(const DispatchTypes::TabClearType clearType) = 0; // TBC
virtual bool EnableDECCOLMSupport(const bool enabled) = 0; // ?40
virtual bool EnableVT200MouseMode(const bool enabled) = 0; // ?1000
virtual bool EnableUTF8ExtendedMouseMode(const bool enabled) = 0; // ?1005
@ -86,11 +86,11 @@ public:
virtual bool EraseInLine(const DispatchTypes::EraseType eraseType) = 0; // EL
virtual bool EraseCharacters(const size_t numChars) = 0; // ECH
virtual bool SetGraphicsRendition(const gsl::span<const DispatchTypes::GraphicsOptions> options) = 0; // SGR
virtual bool SetGraphicsRendition(const VTParameters options) = 0; // SGR
virtual bool SetPrivateModes(const gsl::span<const DispatchTypes::PrivateModeParams> params) = 0; // DECSET
virtual bool SetPrivateMode(const DispatchTypes::PrivateModeParams param) = 0; // DECSET
virtual bool ResetPrivateModes(const gsl::span<const DispatchTypes::PrivateModeParams> params) = 0; // DECRST
virtual bool ResetPrivateMode(const DispatchTypes::PrivateModeParams param) = 0; // DECRST
virtual bool DeviceStatusReport(const DispatchTypes::AnsiStatusType statusType) = 0; // DSR, DSR-OS, DSR-CPR
virtual bool DeviceAttributes() = 0; // DA1
@ -116,7 +116,8 @@ public:
// DTTERM_WindowManipulation
virtual bool WindowManipulation(const DispatchTypes::WindowManipulationType function,
const gsl::span<const size_t> parameters) = 0;
const VTParameter parameter1,
const VTParameter parameter2) = 0;
virtual bool AddHyperlink(const std::wstring_view uri, const std::wstring_view params) = 0;
virtual bool EndHyperlink() = 0;

View file

@ -92,11 +92,13 @@ bool InteractDispatch::WriteString(const std::wstring_view string)
// codes that are supported in one direction but not the other.
//Arguments:
// - function - An identifier of the WindowManipulation function to perform
// - parameters - Additional parameters to pass to the function
// - parameter1 - The first optional parameter for the function
// - parameter2 - The second optional parameter for the function
// Return value:
// True if handled successfully. False otherwise.
bool InteractDispatch::WindowManipulation(const DispatchTypes::WindowManipulationType function,
const gsl::span<const size_t> parameters)
const VTParameter parameter1,
const VTParameter parameter2)
{
bool success = false;
// Other Window Manipulation functions:
@ -105,21 +107,15 @@ bool InteractDispatch::WindowManipulation(const DispatchTypes::WindowManipulatio
switch (function)
{
case DispatchTypes::WindowManipulationType::RefreshWindow:
if (parameters.empty())
{
success = DispatchCommon::s_RefreshWindow(*_pConApi);
}
success = DispatchCommon::s_RefreshWindow(*_pConApi);
break;
case DispatchTypes::WindowManipulationType::ResizeWindowInCharacters:
// TODO:GH#1765 We should introduce a better `ResizeConpty` function to
// the ConGetSet interface, that specifically handles a conpty resize.
if (parameters.size() == 2)
success = DispatchCommon::s_ResizeWindow(*_pConApi, parameter2.value_or(0), parameter1.value_or(0));
if (success)
{
success = DispatchCommon::s_ResizeWindow(*_pConApi, til::at(parameters, 1), til::at(parameters, 0));
if (success)
{
DispatchCommon::s_SuppressResizeRepaint(*_pConApi);
}
DispatchCommon::s_SuppressResizeRepaint(*_pConApi);
}
break;
default:

View file

@ -29,7 +29,8 @@ namespace Microsoft::Console::VirtualTerminal
bool WriteCtrlKey(const KeyEvent& event) override;
bool WriteString(const std::wstring_view string) override;
bool WindowManipulation(const DispatchTypes::WindowManipulationType function,
const gsl::span<const size_t> parameters) override; // DTTERM_WindowManipulation
const VTParameter parameter1,
const VTParameter parameter2) override; // DTTERM_WindowManipulation
bool MoveCursor(const size_t row, const size_t col) override;
bool IsVtInputEnabled() const override;

View file

@ -584,6 +584,8 @@ bool AdaptDispatch::EraseCharacters(const size_t numChars)
// - True if handled successfully. False otherwise.
bool AdaptDispatch::EraseInDisplay(const DispatchTypes::EraseType eraseType)
{
RETURN_BOOL_IF_FALSE(eraseType <= DispatchTypes::EraseType::Scrollback);
// First things first. If this is a "Scrollback" clear, then just do that.
// Scrollback clears erase everything in the "scrollback" of a *nix terminal
// Everything that's scrolled off the screen so far.
@ -681,6 +683,8 @@ bool AdaptDispatch::EraseInDisplay(const DispatchTypes::EraseType eraseType)
// - True if handled successfully. False otherwise.
bool AdaptDispatch::EraseInLine(const DispatchTypes::EraseType eraseType)
{
RETURN_BOOL_IF_FALSE(eraseType <= DispatchTypes::EraseType::All);
CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 };
csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
bool success = _pConApi->GetConsoleScreenBufferInfoEx(csbiex);
@ -1002,7 +1006,7 @@ bool AdaptDispatch::_DoDECCOLMHelper(const size_t columns)
// Routine Description:
// - Support routine for routing private mode parameters to be set/reset as flags
// Arguments:
// - params - array of params to set/reset
// - param - mode parameter to set/reset
// - enable - True for set, false for unset.
// Return Value:
// - True if handled successfully. False otherwise.
@ -1072,48 +1076,26 @@ bool AdaptDispatch::_PrivateModeParamsHelper(const DispatchTypes::PrivateModePar
return success;
}
// Routine Description:
// - Generalized handler for the setting/resetting of DECSET/DECRST parameters.
// All params in the rgParams will attempt to be executed, even if one
// fails, to allow us to successfully re/set params that are chained with
// params we don't yet support.
// Arguments:
// - params - array of params to set/reset
// - enable - True for set, false for unset.
// Return Value:
// - True if ALL params were handled successfully. False otherwise.
bool AdaptDispatch::_SetResetPrivateModes(const gsl::span<const DispatchTypes::PrivateModeParams> params, const bool enable)
{
// because the user might chain together params we don't support with params we DO support, execute all
// params in the sequence, and only return failure if we failed at least one of them
size_t failures = 0;
for (const auto& p : params)
{
failures += _PrivateModeParamsHelper(p, enable) ? 0 : 1; // increment the number of failures if we fail.
}
return failures == 0;
}
// Routine Description:
// - DECSET - Enables the given DEC private mode params.
// Arguments:
// - params - array of params to set
// - param - mode parameter to set
// Return Value:
// - True if handled successfully. False otherwise.
bool AdaptDispatch::SetPrivateModes(const gsl::span<const DispatchTypes::PrivateModeParams> params)
bool AdaptDispatch::SetPrivateMode(const DispatchTypes::PrivateModeParams param)
{
return _SetResetPrivateModes(params, true);
return _PrivateModeParamsHelper(param, true);
}
// Routine Description:
// - DECRST - Disables the given DEC private mode params.
// Arguments:
// - params - array of params to reset
// - param - mode parameter to reset
// Return Value:
// - True if handled successfully. False otherwise.
bool AdaptDispatch::ResetPrivateModes(const gsl::span<const DispatchTypes::PrivateModeParams> params)
bool AdaptDispatch::ResetPrivateMode(const DispatchTypes::PrivateModeParams param)
{
return _SetResetPrivateModes(params, false);
return _PrivateModeParamsHelper(param, false);
}
// - DECKPAM, DECKPNM - Sets the keypad input mode to either Application mode or Numeric mode (true, false respectively)
@ -1571,7 +1553,7 @@ bool AdaptDispatch::BackwardsTab(const size_t numTabs)
// - clearType - Whether to clear the current column, or all columns, defined in DispatchTypes::TabClearType
// Return value:
// True if handled successfully. False otherwise.
bool AdaptDispatch::TabClear(const size_t clearType)
bool AdaptDispatch::TabClear(const DispatchTypes::TabClearType clearType)
{
bool success = false;
switch (clearType)
@ -1821,8 +1803,7 @@ bool AdaptDispatch::SoftReset()
success = _pConApi->SetConsoleOutputCP(_initialCodePage.value()) && success;
}
const auto opt = DispatchTypes::GraphicsOptions::Off;
success = SetGraphicsRendition({ &opt, 1 }) && success; // Normal rendition.
success = SetGraphicsRendition({}) && success; // Normal rendition.
// Reset the saved cursor state.
// Note that XTerm only resets the main buffer state, but that
@ -2321,11 +2302,13 @@ bool Microsoft::Console::VirtualTerminal::AdaptDispatch::SetDefaultBackground(co
// codes that are supported in one direction but not the other.
//Arguments:
// - function - An identifier of the WindowManipulation function to perform
// - parameters - Additional parameters to pass to the function
// - parameter1 - The first optional parameter for the function
// - parameter2 - The second optional parameter for the function
// Return value:
// True if handled successfully. False otherwise.
bool AdaptDispatch::WindowManipulation(const DispatchTypes::WindowManipulationType function,
const gsl::span<const size_t> parameters)
const VTParameter parameter1,
const VTParameter parameter2)
{
bool success = false;
// Other Window Manipulation functions:
@ -2334,16 +2317,10 @@ bool AdaptDispatch::WindowManipulation(const DispatchTypes::WindowManipulationTy
switch (function)
{
case DispatchTypes::WindowManipulationType::RefreshWindow:
if (parameters.empty())
{
success = DispatchCommon::s_RefreshWindow(*_pConApi);
}
success = DispatchCommon::s_RefreshWindow(*_pConApi);
break;
case DispatchTypes::WindowManipulationType::ResizeWindowInCharacters:
if (parameters.size() == 2)
{
success = DispatchCommon::s_ResizeWindow(*_pConApi, til::at(parameters, 1), til::at(parameters, 0));
}
success = DispatchCommon::s_ResizeWindow(*_pConApi, parameter2.value_or(0), parameter1.value_or(0));
break;
default:
success = false;

View file

@ -55,7 +55,7 @@ namespace Microsoft::Console::VirtualTerminal
bool EraseCharacters(const size_t numChars) override; // ECH
bool InsertCharacter(const size_t count) override; // ICH
bool DeleteCharacter(const size_t count) override; // DCH
bool SetGraphicsRendition(const gsl::span<const DispatchTypes::GraphicsOptions> options) override; // SGR
bool SetGraphicsRendition(const VTParameters options) override; // SGR
bool DeviceStatusReport(const DispatchTypes::AnsiStatusType statusType) override; // DSR, DSR-OS, DSR-CPR
bool DeviceAttributes() override; // DA1
bool SecondaryDeviceAttributes() override; // DA2
@ -66,8 +66,8 @@ namespace Microsoft::Console::VirtualTerminal
bool InsertLine(const size_t distance) override; // IL
bool DeleteLine(const size_t distance) override; // DL
bool SetColumns(const size_t columns) override; // DECCOLM
bool SetPrivateModes(const gsl::span<const DispatchTypes::PrivateModeParams> params) override; // DECSET
bool ResetPrivateModes(const gsl::span<const DispatchTypes::PrivateModeParams> params) override; // DECRST
bool SetPrivateMode(const DispatchTypes::PrivateModeParams param) override; // DECSET
bool ResetPrivateMode(const DispatchTypes::PrivateModeParams param) override; // DECRST
bool SetCursorKeysMode(const bool applicationMode) override; // DECCKM
bool SetKeypadMode(const bool applicationMode) override; // DECKPAM, DECKPNM
bool EnableWin32InputMode(const bool win32InputMode) override; // win32-input-mode
@ -88,7 +88,7 @@ namespace Microsoft::Console::VirtualTerminal
bool HorizontalTabSet() override; // HTS
bool ForwardTab(const size_t numTabs) override; // CHT, HT
bool BackwardsTab(const size_t numTabs) override; // CBT
bool TabClear(const size_t clearType) override; // TBC
bool TabClear(const DispatchTypes::TabClearType clearType) override; // TBC
bool DesignateCodingSystem(const VTID codingSystem) override; // DOCS
bool Designate94Charset(const size_t gsetNumber, const VTID charset) override; // SCS
bool Designate96Charset(const size_t gsetNumber, const VTID charset) override; // SCS
@ -116,7 +116,8 @@ namespace Microsoft::Console::VirtualTerminal
bool SetDefaultBackground(const DWORD color) override; // OSCDefaultBackground
bool WindowManipulation(const DispatchTypes::WindowManipulationType function,
const gsl::span<const size_t> parameters) override; // DTTERM_WindowManipulation
const VTParameter parameter1,
const VTParameter parameter2) override; // DTTERM_WindowManipulation
bool AddHyperlink(const std::wstring_view uri, const std::wstring_view params) override;
bool EndHyperlink() override;
@ -162,7 +163,6 @@ namespace Microsoft::Console::VirtualTerminal
bool _CursorPositionReport() const;
bool _WriteResponse(const std::wstring_view reply) const;
bool _SetResetPrivateModes(const gsl::span<const DispatchTypes::PrivateModeParams> params, const bool enable);
bool _PrivateModeParamsHelper(const DispatchTypes::PrivateModeParams param, const bool enable);
bool _DoDECCOLMHelper(const size_t columns);
@ -195,7 +195,7 @@ namespace Microsoft::Console::VirtualTerminal
bool _isDECCOLMAllowed;
size_t _SetRgbColorsHelper(const gsl::span<const DispatchTypes::GraphicsOptions> options,
size_t _SetRgbColorsHelper(const VTParameters options,
TextAttribute& attr,
const bool isForeground) noexcept;
};

View file

@ -47,43 +47,39 @@ const BYTE BRIGHT_WHITE = BRIGHT_ATTR | RED_ATTR | GREEN_ATTR | BLUE_ATTR;
// - isForeground - Whether or not the parsed color is for the foreground.
// Return Value:
// - The number of options consumed, not including the initial 38/48.
size_t AdaptDispatch::_SetRgbColorsHelper(const gsl::span<const DispatchTypes::GraphicsOptions> options,
size_t AdaptDispatch::_SetRgbColorsHelper(const VTParameters options,
TextAttribute& attr,
const bool isForeground) noexcept
{
size_t optionsConsumed = 0;
if (options.size() >= 1)
size_t optionsConsumed = 1;
const DispatchTypes::GraphicsOptions typeOpt = options.at(0);
if (typeOpt == DispatchTypes::GraphicsOptions::RGBColorOrFaint)
{
optionsConsumed = 1;
const auto typeOpt = til::at(options, 0);
if (typeOpt == DispatchTypes::GraphicsOptions::RGBColorOrFaint && options.size() >= 4)
optionsConsumed = 4;
const size_t red = options.at(1).value_or(0);
const size_t green = options.at(2).value_or(0);
const size_t blue = options.at(3).value_or(0);
// ensure that each value fits in a byte
if (red <= 255 && green <= 255 && blue <= 255)
{
optionsConsumed = 4;
const size_t red = til::at(options, 1);
const size_t green = til::at(options, 2);
const size_t blue = til::at(options, 3);
// ensure that each value fits in a byte
if (red <= 255 && green <= 255 && blue <= 255)
{
const COLORREF rgbColor = RGB(red, green, blue);
attr.SetColor(rgbColor, isForeground);
}
const COLORREF rgbColor = RGB(red, green, blue);
attr.SetColor(rgbColor, isForeground);
}
else if (typeOpt == DispatchTypes::GraphicsOptions::BlinkOrXterm256Index && options.size() >= 2)
}
else if (typeOpt == DispatchTypes::GraphicsOptions::BlinkOrXterm256Index)
{
optionsConsumed = 2;
const size_t tableIndex = options.at(1).value_or(0);
if (tableIndex <= 255)
{
optionsConsumed = 2;
const size_t tableIndex = til::at(options, 1);
if (tableIndex <= 255)
const auto adjustedIndex = gsl::narrow_cast<BYTE>(::Xterm256ToWindowsIndex(tableIndex));
if (isForeground)
{
const auto adjustedIndex = gsl::narrow_cast<BYTE>(::Xterm256ToWindowsIndex(tableIndex));
if (isForeground)
{
attr.SetIndexedForeground256(adjustedIndex);
}
else
{
attr.SetIndexedBackground256(adjustedIndex);
}
attr.SetIndexedForeground256(adjustedIndex);
}
else
{
attr.SetIndexedBackground256(adjustedIndex);
}
}
}
@ -100,7 +96,7 @@ size_t AdaptDispatch::_SetRgbColorsHelper(const gsl::span<const DispatchTypes::G
// one at a time by setting or removing flags in the font style properties.
// Return Value:
// - True if handled successfully. False otherwise.
bool AdaptDispatch::SetGraphicsRendition(const gsl::span<const DispatchTypes::GraphicsOptions> options)
bool AdaptDispatch::SetGraphicsRendition(const VTParameters options)
{
TextAttribute attr;
bool success = _pConApi->PrivateGetTextAttributes(attr);
@ -110,7 +106,7 @@ bool AdaptDispatch::SetGraphicsRendition(const gsl::span<const DispatchTypes::Gr
// Run through the graphics options and apply them
for (size_t i = 0; i < options.size(); i++)
{
const auto opt = til::at(options, i);
const GraphicsOptions opt = options.at(i);
switch (opt)
{
case Off:

View file

@ -64,7 +64,7 @@ public:
bool HorizontalTabSet() noexcept override { return false; } // HTS
bool ForwardTab(const size_t /*numTabs*/) noexcept override { return false; } // CHT, HT
bool BackwardsTab(const size_t /*numTabs*/) noexcept override { return false; } // CBT
bool TabClear(const size_t /*clearType*/) noexcept override { return false; } // TBC
bool TabClear(const DispatchTypes::TabClearType /*clearType*/) noexcept override { return false; } // TBC
bool EnableDECCOLMSupport(const bool /*enabled*/) noexcept override { return false; } // ?40
bool EnableVT200MouseMode(const bool /*enabled*/) noexcept override { return false; } // ?1000
bool EnableUTF8ExtendedMouseMode(const bool /*enabled*/) noexcept override { return false; } // ?1005
@ -80,11 +80,11 @@ public:
bool EraseInLine(const DispatchTypes::EraseType /* eraseType*/) noexcept override { return false; } // EL
bool EraseCharacters(const size_t /*numChars*/) noexcept override { return false; } // ECH
bool SetGraphicsRendition(const gsl::span<const DispatchTypes::GraphicsOptions> /*options*/) noexcept override { return false; } // SGR
bool SetGraphicsRendition(const VTParameters /*options*/) noexcept override { return false; } // SGR
bool SetPrivateModes(const gsl::span<const DispatchTypes::PrivateModeParams> /*params*/) noexcept override { return false; } // DECSET
bool SetPrivateMode(const DispatchTypes::PrivateModeParams /*param*/) noexcept override { return false; } // DECSET
bool ResetPrivateModes(const gsl::span<const DispatchTypes::PrivateModeParams> /*params*/) noexcept override { return false; } // DECRST
bool ResetPrivateMode(const DispatchTypes::PrivateModeParams /*param*/) noexcept override { return false; } // DECRST
bool DeviceStatusReport(const DispatchTypes::AnsiStatusType /*statusType*/) noexcept override { return false; } // DSR, DSR-OS, DSR-CPR
bool DeviceAttributes() noexcept override { return false; } // DA1
@ -110,7 +110,8 @@ public:
// DTTERM_WindowManipulation
bool WindowManipulation(const DispatchTypes::WindowManipulationType /*function*/,
const gsl::span<const size_t> /*params*/) noexcept override { return false; }
const VTParameter /*parameter1*/,
const VTParameter /*parameter2*/) noexcept override { return false; }
bool AddHyperlink(const std::wstring_view /*uri*/, const std::wstring_view /*params*/) noexcept override { return false; }
bool EndHyperlink() noexcept override { return false; }

View file

@ -1255,7 +1255,7 @@ public:
_testGetSet->PrepData();
DispatchTypes::GraphicsOptions rgOptions[16];
VTParameter rgOptions[16];
size_t cOptions = 0;
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
@ -1292,7 +1292,7 @@ public:
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"uiGraphicsOptions", uiGraphicsOption));
graphicsOption = (DispatchTypes::GraphicsOptions)uiGraphicsOption;
DispatchTypes::GraphicsOptions rgOptions[16];
VTParameter rgOptions[16];
size_t cOptions = 1;
rgOptions[0] = graphicsOption;
@ -1605,7 +1605,7 @@ public:
_testGetSet->PrepData(); // default color from here is gray on black, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED
DispatchTypes::GraphicsOptions rgOptions[16];
VTParameter rgOptions[16];
size_t cOptions = 1;
Log::Comment(L"Test 1: Basic brightness test");
@ -2094,7 +2094,7 @@ public:
_testGetSet->PrepData(); // default color from here is gray on black, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED
DispatchTypes::GraphicsOptions rgOptions[16];
VTParameter rgOptions[16];
size_t cOptions = 3;
_testGetSet->_privateGetColorTableEntryResult = true;
@ -2139,6 +2139,53 @@ public:
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
}
TEST_METHOD(XtermExtendedColorDefaultParameterTest)
{
Log::Comment(L"Starting test...");
_testGetSet->PrepData(); // default color from here is gray on black, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED
VTParameter rgOptions[16];
_testGetSet->_privateGetColorTableEntryResult = true;
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
Log::Comment(L"Test 1: Change Indexed Foreground with missing index parameter");
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundExtended;
rgOptions[1] = DispatchTypes::GraphicsOptions::BlinkOrXterm256Index;
_testGetSet->_expectedAttribute.SetIndexedForeground256(0);
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, 2 }));
Log::Comment(L"Test 2: Change Indexed Background with default index parameter");
rgOptions[0] = DispatchTypes::GraphicsOptions::BackgroundExtended;
rgOptions[1] = DispatchTypes::GraphicsOptions::BlinkOrXterm256Index;
rgOptions[2] = {};
_testGetSet->_expectedAttribute.SetIndexedBackground256(0);
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, 3 }));
Log::Comment(L"Test 3: Change RGB Foreground with all RGB parameters missing");
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundExtended;
rgOptions[1] = DispatchTypes::GraphicsOptions::RGBColorOrFaint;
_testGetSet->_expectedAttribute.SetForeground(RGB(0, 0, 0));
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, 2 }));
Log::Comment(L"Test 4: Change RGB Background with some missing RGB parameters");
rgOptions[0] = DispatchTypes::GraphicsOptions::BackgroundExtended;
rgOptions[1] = DispatchTypes::GraphicsOptions::RGBColorOrFaint;
rgOptions[2] = 123;
_testGetSet->_expectedAttribute.SetBackground(RGB(123, 0, 0));
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, 3 }));
Log::Comment(L"Test 5: Change RGB Foreground with some default RGB parameters");
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundExtended;
rgOptions[1] = DispatchTypes::GraphicsOptions::RGBColorOrFaint;
rgOptions[2] = {};
rgOptions[3] = {};
rgOptions[4] = 123;
_testGetSet->_expectedAttribute.SetForeground(RGB(0, 0, 123));
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, 5 }));
}
TEST_METHOD(SetColorTableValue)
{
_testGetSet->PrepData();

View file

@ -34,8 +34,8 @@ namespace Microsoft::Console::VirtualTerminal
virtual bool ActionPassThroughString(const std::wstring_view string) = 0;
virtual bool ActionEscDispatch(const VTID id) = 0;
virtual bool ActionVt52EscDispatch(const VTID id, const gsl::span<const size_t> parameters) = 0;
virtual bool ActionCsiDispatch(const VTID id, const gsl::span<const size_t> parameters) = 0;
virtual bool ActionVt52EscDispatch(const VTID id, const VTParameters parameters) = 0;
virtual bool ActionCsiDispatch(const VTID id, const VTParameters parameters) = 0;
virtual bool ActionClear() = 0;
@ -45,8 +45,7 @@ namespace Microsoft::Console::VirtualTerminal
const size_t parameter,
const std::wstring_view string) = 0;
virtual bool ActionSs3Dispatch(const wchar_t wch,
const gsl::span<const size_t> parameters) = 0;
virtual bool ActionSs3Dispatch(const wchar_t wch, const VTParameters parameters) = 0;
virtual bool ParseControlSequenceAfterSs3() const = 0;
virtual bool FlushAtEndOfString() const = 0;

View file

@ -344,7 +344,7 @@ bool InputStateMachineEngine::ActionEscDispatch(const VTID id)
// - parameters - Set of parameters collected while parsing the sequence.
// Return Value:
// - true iff we successfully dispatched the sequence.
bool InputStateMachineEngine::ActionVt52EscDispatch(const VTID /*id*/, const gsl::span<const size_t> /*parameters*/) noexcept
bool InputStateMachineEngine::ActionVt52EscDispatch(const VTID /*id*/, const VTParameters /*parameters*/) noexcept
{
// VT52 escape sequences are not used in the input state machine.
return false;
@ -359,7 +359,7 @@ bool InputStateMachineEngine::ActionVt52EscDispatch(const VTID /*id*/, const gsl
// - parameters - set of numeric parameters collected while parsing the sequence.
// Return Value:
// - true iff we successfully dispatched the sequence.
bool InputStateMachineEngine::ActionCsiDispatch(const VTID id, const gsl::span<const size_t> parameters)
bool InputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParameters parameters)
{
// GH#4999 - If the client was in VT input mode, but we received a
// win32-input-mode sequence, then _don't_ passthrough the sequence to the
@ -376,13 +376,6 @@ bool InputStateMachineEngine::ActionCsiDispatch(const VTID id, const gsl::span<c
DWORD modifierState = 0;
short vkey = 0;
unsigned int function = 0;
size_t col = 0;
size_t row = 0;
KeyEvent key;
// This is all the args after the first arg, and the count of args not including the first one.
const auto remainingArgs = parameters.size() > 1 ? parameters.subspan(1) : gsl::span<const size_t>{};
bool success = false;
switch (id)
@ -392,19 +385,12 @@ bool InputStateMachineEngine::ActionCsiDispatch(const VTID id, const gsl::span<c
{
DWORD buttonState = 0;
DWORD eventFlags = 0;
modifierState = _GetSGRMouseModifierState(parameters);
success = _GetSGRXYPosition(parameters, row, col);
// we need _UpdateSGRMouseButtonState() on the left side here because we _always_ should be updating our state
// even if we failed to parse a portion of this sequence.
success = _UpdateSGRMouseButtonState(id, parameters, buttonState, eventFlags) && success;
success = success && _WriteMouseEvent(col, row, buttonState, modifierState, eventFlags);
return success;
}
case CsiActionCodes::Generic:
modifierState = _GetGenericKeysModifierState(parameters);
success = _GetGenericVkey(parameters, vkey);
const size_t firstParameter = parameters.at(0).value_or(0);
modifierState = _GetSGRMouseModifierState(firstParameter);
success = _UpdateSGRMouseButtonState(id, firstParameter, buttonState, eventFlags);
success = success && _WriteMouseEvent(parameters.at(1), parameters.at(2), buttonState, modifierState, eventFlags);
break;
}
// case CsiActionCodes::DSR_DeviceStatusReportResponse:
case CsiActionCodes::CSI_F3:
// The F3 case is special - it shares a code with the DeviceStatusResponse.
@ -412,8 +398,10 @@ bool InputStateMachineEngine::ActionCsiDispatch(const VTID id, const gsl::span<c
// Else, fall though to the _GetCursorKeysModifierState handler.
if (_lookingForDSR)
{
success = true;
success = _GetXYPosition(parameters, row, col);
success = _pDispatch->MoveCursor(parameters.at(0), parameters.at(1));
// Right now we're only looking for on initial cursor
// position response. After that, only look for F3.
_lookingForDSR = false;
break;
}
[[fallthrough]];
@ -428,72 +416,33 @@ bool InputStateMachineEngine::ActionCsiDispatch(const VTID id, const gsl::span<c
case CsiActionCodes::CSI_F4:
success = _GetCursorKeysVkey(id, vkey);
modifierState = _GetCursorKeysModifierState(parameters, id);
success = success && _WriteSingleKey(vkey, modifierState);
break;
case CsiActionCodes::Generic:
success = _GetGenericVkey(parameters.at(0), vkey);
modifierState = _GetGenericKeysModifierState(parameters);
success = success && _WriteSingleKey(vkey, modifierState);
break;
case CsiActionCodes::CursorBackTab:
modifierState = SHIFT_PRESSED;
vkey = VK_TAB;
success = true;
success = _WriteSingleKey(VK_TAB, SHIFT_PRESSED);
break;
case CsiActionCodes::DTTERM_WindowManipulation:
success = _GetWindowManipulationType(parameters, function);
success = _pDispatch->WindowManipulation(parameters.at(0), parameters.at(1), parameters.at(2));
break;
case CsiActionCodes::Win32KeyboardInput:
success = _GenerateWin32Key(parameters, key);
{
// Use WriteCtrlKey here, even for keys that _aren't_ control keys,
// because that will take extra steps to make sure things like
// Ctrl+C, Ctrl+Break are handled correctly.
const auto key = _GenerateWin32Key(parameters);
success = _pDispatch->WriteCtrlKey(key);
break;
}
default:
success = false;
break;
}
if (success)
{
switch (id)
{
// case CsiActionCodes::DSR_DeviceStatusReportResponse:
case CsiActionCodes::CSI_F3:
// The F3 case is special - it shares a code with the DeviceStatusResponse.
// If we're looking for that response, then do that, and break out.
// Else, fall though to the _GetCursorKeysModifierState handler.
if (_lookingForDSR)
{
success = _pDispatch->MoveCursor(row, col);
// Right now we're only looking for on initial cursor
// position response. After that, only look for F3.
_lookingForDSR = false;
break;
}
[[fallthrough]];
case CsiActionCodes::Generic:
case CsiActionCodes::ArrowUp:
case CsiActionCodes::ArrowDown:
case CsiActionCodes::ArrowRight:
case CsiActionCodes::ArrowLeft:
case CsiActionCodes::Home:
case CsiActionCodes::End:
case CsiActionCodes::CSI_F1:
case CsiActionCodes::CSI_F2:
case CsiActionCodes::CSI_F4:
case CsiActionCodes::CursorBackTab:
success = _WriteSingleKey(vkey, modifierState);
break;
case CsiActionCodes::DTTERM_WindowManipulation:
success = _pDispatch->WindowManipulation(static_cast<DispatchTypes::WindowManipulationType>(function),
remainingArgs);
break;
case CsiActionCodes::Win32KeyboardInput:
{
// Use WriteCtrlKey here, even for keys that _aren't_ control keys,
// because that will take extra steps to make sure things like
// Ctrl+C, Ctrl+Break are handled correctly.
success = _pDispatch->WriteCtrlKey(key);
break;
}
default:
success = false;
break;
}
}
return success;
}
@ -506,8 +455,7 @@ bool InputStateMachineEngine::ActionCsiDispatch(const VTID id, const gsl::span<c
// - parameters - set of numeric parameters collected while parsing the sequence.
// Return Value:
// - true iff we successfully dispatched the sequence.
bool InputStateMachineEngine::ActionSs3Dispatch(const wchar_t wch,
const gsl::span<const size_t> /*parameters*/)
bool InputStateMachineEngine::ActionSs3Dispatch(const wchar_t wch, const VTParameters /*parameters*/)
{
if (_pDispatch->IsVtInputEnabled() && _pfnFlushToInputQueue)
{
@ -785,13 +733,9 @@ bool InputStateMachineEngine::_WriteMouseEvent(const size_t column, const size_t
// - id - the identifier for the sequence we're operating on.
// Return Value:
// - the INPUT_RECORD compatible modifier state.
DWORD InputStateMachineEngine::_GetCursorKeysModifierState(const gsl::span<const size_t> parameters, const VTID id) noexcept
DWORD InputStateMachineEngine::_GetCursorKeysModifierState(const VTParameters parameters, const VTID id) noexcept
{
DWORD modifiers = 0;
if (_IsModified(parameters.size()) && parameters.size() >= 2)
{
modifiers = _GetModifier(til::at(parameters, 1));
}
DWORD modifiers = _GetModifier(parameters.at(1));
// Enhanced Keys (from https://docs.microsoft.com/en-us/windows/console/key-event-record-str):
// Enhanced keys for the IBM 101- and 102-key keyboards are the INS, DEL,
@ -814,20 +758,16 @@ DWORD InputStateMachineEngine::_GetCursorKeysModifierState(const gsl::span<const
// - parameters - the set of parameters to get the modifier state from.
// Return Value:
// - the INPUT_RECORD compatible modifier state.
DWORD InputStateMachineEngine::_GetGenericKeysModifierState(const gsl::span<const size_t> parameters) noexcept
DWORD InputStateMachineEngine::_GetGenericKeysModifierState(const VTParameters parameters) noexcept
{
DWORD modifiers = 0;
if (_IsModified(parameters.size()) && parameters.size() >= 2)
{
modifiers = _GetModifier(til::at(parameters, 1));
}
DWORD modifiers = _GetModifier(parameters.at(1));
// Enhanced Keys (from https://docs.microsoft.com/en-us/windows/console/key-event-record-str):
// Enhanced keys for the IBM 101- and 102-key keyboards are the INS, DEL,
// HOME, END, PAGE UP, PAGE DOWN, and direction keys in the clusters to the left
// of the keypad; and the divide (/) and ENTER keys in the keypad.
// This snippet detects the non-direction keys
const auto identifier = static_cast<GenericKeyIdentifiers>(til::at(parameters, 0));
const GenericKeyIdentifiers identifier = parameters.at(0);
if (identifier <= GenericKeyIdentifiers::Next)
{
modifiers = WI_SetFlag(modifiers, ENHANCED_KEY);
@ -837,46 +777,28 @@ DWORD InputStateMachineEngine::_GetGenericKeysModifierState(const gsl::span<cons
}
// Method Description:
// - Retrieves the modifier state from a set of parameters for an SGR
// - Retrieves the modifier state from the first parameter of an SGR
// Mouse Sequence - one who's sequence is terminated with an 'M' or 'm'.
// Arguments:
// - parameters - the set of parameters to get the modifier state from.
// - modifierParam - the first parameter to get the modifier state from.
// Return Value:
// - the INPUT_RECORD compatible modifier state.
DWORD InputStateMachineEngine::_GetSGRMouseModifierState(const gsl::span<const size_t> parameters) noexcept
DWORD InputStateMachineEngine::_GetSGRMouseModifierState(const size_t modifierParam) noexcept
{
DWORD modifiers = 0;
if (parameters.size() == 3)
{
// The first parameter of mouse events is encoded as the following two bytes:
// BBDM'MMBB
// Where each of the bits mean the following
// BB__'__BB - which button was pressed/released
// MMM - Control, Alt, Shift state (respectively)
// D - flag signifying a drag event
// This retrieves the modifier state from bits [5..3] ('M' above)
const auto modifierParam = til::at(parameters, 0);
WI_SetFlagIf(modifiers, SHIFT_PRESSED, WI_IsFlagSet(modifierParam, CsiMouseModifierCodes::Shift));
WI_SetFlagIf(modifiers, LEFT_ALT_PRESSED, WI_IsFlagSet(modifierParam, CsiMouseModifierCodes::Meta));
WI_SetFlagIf(modifiers, LEFT_CTRL_PRESSED, WI_IsFlagSet(modifierParam, CsiMouseModifierCodes::Ctrl));
}
// The first parameter of mouse events is encoded as the following two bytes:
// BBDM'MMBB
// Where each of the bits mean the following
// BB__'__BB - which button was pressed/released
// MMM - Control, Alt, Shift state (respectively)
// D - flag signifying a drag event
// This retrieves the modifier state from bits [5..3] ('M' above)
WI_SetFlagIf(modifiers, SHIFT_PRESSED, WI_IsFlagSet(modifierParam, CsiMouseModifierCodes::Shift));
WI_SetFlagIf(modifiers, LEFT_ALT_PRESSED, WI_IsFlagSet(modifierParam, CsiMouseModifierCodes::Meta));
WI_SetFlagIf(modifiers, LEFT_CTRL_PRESSED, WI_IsFlagSet(modifierParam, CsiMouseModifierCodes::Ctrl));
return modifiers;
}
// Method Description:
// - Determines if a set of parameters indicates a modified keypress
// Arguments:
// - paramCount - the number of parameters we've collected in this sequence
// Return Value:
// - true iff the sequence is a modified sequence.
bool InputStateMachineEngine::_IsModified(const size_t paramCount) noexcept
{
// modified input either looks like
// \x1b[1;mA or \x1b[17;m~
// Both have two parameters
return paramCount == 2;
}
// Method Description:
// - Converts a VT encoded modifier param into a INPUT_RECORD compatible one.
// Arguments:
@ -901,21 +823,16 @@ DWORD InputStateMachineEngine::_GetModifier(const size_t modifierParam) noexcept
// - Mouse wheel events are added at the end to keep them out of the global state
// Arguments:
// - id: the sequence identifier representing whether the button was pressed or released
// - parameters: the wchar_t to get the mapped vkey of. Represents the direction of the button (down vs up)
// - sgrEncoding: the first parameter, encoding the button and drag state
// - buttonState: Receives the button state for the record
// - eventFlags: Receives the special mouse events for the record
// Return Value:
// true iff we were able to synthesize buttonState
bool InputStateMachineEngine::_UpdateSGRMouseButtonState(const VTID id,
const gsl::span<const size_t> parameters,
const size_t sgrEncoding,
DWORD& buttonState,
DWORD& eventFlags) noexcept
{
if (parameters.empty())
{
return false;
}
// Starting with the state from the last mouse event we received
buttonState = _mouseButtonState;
eventFlags = 0;
@ -926,7 +843,6 @@ bool InputStateMachineEngine::_UpdateSGRMouseButtonState(const VTID id,
// BB__'__BB - which button was pressed/released
// MMM - Control, Alt, Shift state (respectively)
// D - flag signifying a drag event
const auto sgrEncoding = til::at(parameters, 0);
// This retrieves the 2 MSBs and concatenates them to the 2 LSBs to create BBBB in binary
// This represents which button had a change in state
@ -1002,22 +918,15 @@ bool InputStateMachineEngine::_UpdateSGRMouseButtonState(const VTID id,
// Method Description:
// - Gets the Vkey form the generic keys table associated with a particular
// identifier code. The identifier code will be the first param in rgusParams.
// identifier code.
// Arguments:
// - parameters: an array of shorts where the first is the identifier of the key
// we're looking for.
// - identifier: the identifier of the key we're looking for.
// - vkey: Receives the vkey
// Return Value:
// true iff we found the key
bool InputStateMachineEngine::_GetGenericVkey(const gsl::span<const size_t> parameters, short& vkey) const
bool InputStateMachineEngine::_GetGenericVkey(const GenericKeyIdentifiers identifier, short& vkey) const
{
vkey = 0;
if (parameters.empty())
{
return false;
}
const auto identifier = (GenericKeyIdentifiers)til::at(parameters, 0);
const auto mapping = std::find(s_genericMap.cbegin(), s_genericMap.cend(), identifier);
if (mapping != s_genericMap.end())
@ -1211,104 +1120,13 @@ bool InputStateMachineEngine::_GetWindowManipulationType(const gsl::span<const s
return success;
}
// Routine Description:
// - Retrieves an X/Y coordinate pair for a cursor operation from the parameter pool stored during Param actions.
// Arguments:
// - parameters - set of numeric parameters collected while parsing the sequence.
// - 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 InputStateMachineEngine::_GetXYPosition(const gsl::span<const size_t> parameters,
size_t& line,
size_t& column) const noexcept
{
line = DefaultLine;
column = DefaultColumn;
if (parameters.empty())
{
// Empty parameter sequences should use the default
}
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);
}
else if (parameters.size() == 2)
{
// If there are exactly two parameters, use them.
line = til::at(parameters, 0);
column = til::at(parameters, 1);
}
else
{
return false;
}
// Distances of 0 should be changed to 1.
if (line == 0)
{
line = DefaultLine;
}
if (column == 0)
{
column = DefaultColumn;
}
return true;
}
// Routine Description:
// - Retrieves an X/Y coordinate pair for an SGR Mouse sequence from the parameter pool stored during Param actions.
// Arguments:
// - parameters - set of numeric parameters collected while parsing the sequence.
// - 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 InputStateMachineEngine::_GetSGRXYPosition(const gsl::span<const size_t> parameters,
size_t& line,
size_t& column) const noexcept
{
line = DefaultLine;
column = DefaultColumn;
// SGR Mouse sequences have exactly 3 parameters
if (parameters.size() == 3)
{
column = til::at(parameters, 1);
line = til::at(parameters, 2);
}
else
{
return false;
}
// Distances of 0 should be changed to 1.
if (line == 0)
{
line = DefaultLine;
}
if (column == 0)
{
column = DefaultColumn;
}
return true;
}
// Method Description:
// - Attempt to parse our parameters into a win32-input-mode serialized KeyEvent.
// Arguments:
// - parameters: the list of numbers to parse into values for the KeyEvent.
// - key: receives the values of the deserialized KeyEvent.
// Return Value:
// - true if we successfully parsed the key event.
bool InputStateMachineEngine::_GenerateWin32Key(const gsl::span<const size_t> parameters,
KeyEvent& key)
// - The deserialized KeyEvent.
KeyEvent InputStateMachineEngine::_GenerateWin32Key(const VTParameters parameters)
{
// Sequences are formatted as follows:
//
@ -1322,36 +1140,12 @@ bool InputStateMachineEngine::_GenerateWin32Key(const gsl::span<const size_t> pa
// Cs: the value of dwControlKeyState - any number. If omitted, defaults to '0'.
// Rc: the value of wRepeatCount - any number. If omitted, defaults to '1'.
if (parameters.size() > 6)
{
return false;
}
key = KeyEvent();
key.SetRepeatCount(1);
switch (parameters.size())
{
case 6:
key.SetRepeatCount(::base::saturated_cast<WORD>(til::at(parameters, 5)));
[[fallthrough]];
case 5:
key.SetActiveModifierKeys(::base::saturated_cast<DWORD>(til::at(parameters, 4)));
[[fallthrough]];
case 4:
key.SetKeyDown(static_cast<bool>(til::at(parameters, 3)));
[[fallthrough]];
case 3:
key.SetCharData(static_cast<wchar_t>(til::at(parameters, 2)));
[[fallthrough]];
case 2:
key.SetVirtualScanCode(::base::saturated_cast<WORD>(til::at(parameters, 1)));
[[fallthrough]];
case 1:
key.SetVirtualKeyCode(::base::saturated_cast<WORD>(til::at(parameters, 0)));
break;
default:
break;
}
return true;
auto key = KeyEvent();
key.SetVirtualKeyCode(::base::saturated_cast<WORD>(parameters.at(0).value_or(0)));
key.SetVirtualScanCode(::base::saturated_cast<WORD>(parameters.at(1).value_or(0)));
key.SetCharData(::base::saturated_cast<wchar_t>(parameters.at(2).value_or(0)));
key.SetKeyDown(parameters.at(3).value_or(0));
key.SetActiveModifierKeys(::base::saturated_cast<DWORD>(parameters.at(4).value_or(0)));
key.SetRepeatCount(::base::saturated_cast<WORD>(parameters.at(5).value_or(1)));
return key;
}

View file

@ -92,7 +92,7 @@ namespace Microsoft::Console::VirtualTerminal
};
// Sequences ending in '~' use these numbers as identifiers.
enum class GenericKeyIdentifiers : unsigned short
enum class GenericKeyIdentifiers : size_t
{
GenericHome = 1,
Insert = 2,
@ -142,9 +142,9 @@ namespace Microsoft::Console::VirtualTerminal
bool ActionEscDispatch(const VTID id) override;
bool ActionVt52EscDispatch(const VTID id, const gsl::span<const size_t> parameters) noexcept override;
bool ActionVt52EscDispatch(const VTID id, const VTParameters parameters) noexcept override;
bool ActionCsiDispatch(const VTID id, const gsl::span<const size_t> parameters) override;
bool ActionCsiDispatch(const VTID id, const VTParameters parameters) override;
bool ActionClear() noexcept override;
@ -154,8 +154,7 @@ namespace Microsoft::Console::VirtualTerminal
const size_t parameter,
const std::wstring_view string) noexcept override;
bool ActionSs3Dispatch(const wchar_t wch,
const gsl::span<const size_t> parameters) override;
bool ActionSs3Dispatch(const wchar_t wch, const VTParameters parameters) override;
bool ParseControlSequenceAfterSs3() const noexcept override;
bool FlushAtEndOfString() const noexcept override;
@ -170,20 +169,18 @@ namespace Microsoft::Console::VirtualTerminal
bool _lookingForDSR;
DWORD _mouseButtonState = 0;
DWORD _GetCursorKeysModifierState(const gsl::span<const size_t> parameters, const VTID id) noexcept;
DWORD _GetGenericKeysModifierState(const gsl::span<const size_t> parameters) noexcept;
DWORD _GetSGRMouseModifierState(const gsl::span<const size_t> parameters) noexcept;
DWORD _GetCursorKeysModifierState(const VTParameters parameters, const VTID id) noexcept;
DWORD _GetGenericKeysModifierState(const VTParameters parameters) noexcept;
DWORD _GetSGRMouseModifierState(const size_t modifierParam) noexcept;
bool _GenerateKeyFromChar(const wchar_t wch, short& vkey, DWORD& modifierState) noexcept;
bool _IsModified(const size_t paramCount) noexcept;
DWORD _GetModifier(const size_t parameter) noexcept;
bool _UpdateSGRMouseButtonState(const VTID id,
const gsl::span<const size_t> parameters,
const size_t sgrEncoding,
DWORD& buttonState,
DWORD& eventFlags) noexcept;
bool _GetGenericVkey(const gsl::span<const size_t> parameters,
short& vkey) const;
bool _GetGenericVkey(const GenericKeyIdentifiers identifier, short& vkey) const;
bool _GetCursorKeysVkey(const VTID id, short& vkey) const;
bool _GetSs3KeysVkey(const wchar_t wch, short& vkey) const;
@ -205,16 +202,7 @@ namespace Microsoft::Console::VirtualTerminal
bool _GetWindowManipulationType(const gsl::span<const size_t> parameters,
unsigned int& function) const noexcept;
bool _GenerateWin32Key(const gsl::span<const size_t> parameters, KeyEvent& key);
static constexpr size_t DefaultLine = 1;
static constexpr size_t DefaultColumn = 1;
bool _GetXYPosition(const gsl::span<const size_t> parameters,
size_t& line,
size_t& column) const noexcept;
bool _GetSGRXYPosition(const gsl::span<const size_t> parameters,
size_t& line,
size_t& column) const noexcept;
KeyEvent _GenerateWin32Key(const VTParameters parameters);
bool _DoControlCharacter(const wchar_t wch, const bool writeAlt);

File diff suppressed because it is too large Load diff

View file

@ -35,9 +35,9 @@ namespace Microsoft::Console::VirtualTerminal
bool ActionEscDispatch(const VTID id) override;
bool ActionVt52EscDispatch(const VTID id, const gsl::span<const size_t> parameters) override;
bool ActionVt52EscDispatch(const VTID id, const VTParameters parameters) override;
bool ActionCsiDispatch(const VTID id, const gsl::span<const size_t> parameters) override;
bool ActionCsiDispatch(const VTID id, const VTParameters parameters) override;
bool ActionClear() noexcept override;
@ -47,8 +47,7 @@ namespace Microsoft::Console::VirtualTerminal
const size_t parameter,
const std::wstring_view string) override;
bool ActionSs3Dispatch(const wchar_t wch,
const gsl::span<const size_t> parameters) noexcept override;
bool ActionSs3Dispatch(const wchar_t wch, const VTParameters parameters) noexcept override;
bool ParseControlSequenceAfterSs3() const noexcept override;
bool FlushAtEndOfString() const noexcept override;
@ -66,7 +65,6 @@ namespace Microsoft::Console::VirtualTerminal
Microsoft::Console::ITerminalOutputConnection* _pTtyConnection;
std::function<bool()> _pfnFlushToTerminal;
wchar_t _lastPrintedChar;
std::vector<DispatchTypes::GraphicsOptions> _graphicsOptions;
enum EscActionCodes : uint64_t
{
@ -170,63 +168,9 @@ namespace Microsoft::Console::VirtualTerminal
ResetCursorColor = 112
};
static constexpr DispatchTypes::GraphicsOptions DefaultGraphicsOption = DispatchTypes::GraphicsOptions::Off;
bool _GetGraphicsOptions(const gsl::span<const size_t> parameters,
std::vector<DispatchTypes::GraphicsOptions>& options) const;
static constexpr DispatchTypes::EraseType DefaultEraseType = DispatchTypes::EraseType::ToEnd;
bool _GetEraseOperation(const gsl::span<const size_t> parameters,
DispatchTypes::EraseType& eraseType) const noexcept;
static constexpr size_t DefaultCursorDistance = 1;
bool _GetCursorDistance(const gsl::span<const size_t> parameters,
size_t& distance) const noexcept;
static constexpr size_t DefaultScrollDistance = 1;
bool _GetScrollDistance(const gsl::span<const size_t> parameters,
size_t& distance) const noexcept;
static constexpr size_t DefaultConsoleWidth = 80;
bool _GetConsoleWidth(const gsl::span<const size_t> parameters,
size_t& consoleWidth) const noexcept;
static constexpr size_t DefaultLine = 1;
static constexpr size_t DefaultColumn = 1;
bool _GetXYPosition(const gsl::span<const size_t> parameters,
size_t& line,
size_t& column) const noexcept;
bool _GetDeviceStatusOperation(const gsl::span<const size_t> parameters,
DispatchTypes::AnsiStatusType& statusType) const noexcept;
bool _VerifyHasNoParameters(const gsl::span<const size_t> parameters) const noexcept;
bool _VerifyDeviceAttributesParams(const gsl::span<const size_t> parameters) const noexcept;
bool _GetPrivateModeParams(const gsl::span<const size_t> parameters,
std::vector<DispatchTypes::PrivateModeParams>& privateModes) const;
static constexpr size_t DefaultTopMargin = 0;
static constexpr size_t DefaultBottomMargin = 0;
bool _GetTopBottomMargins(const gsl::span<const size_t> parameters,
size_t& topMargin,
size_t& bottomMargin) const noexcept;
bool _GetOscTitle(const std::wstring_view string,
std::wstring& title) const;
static constexpr size_t DefaultTabDistance = 1;
bool _GetTabDistance(const gsl::span<const size_t> parameters,
size_t& distance) const noexcept;
static constexpr size_t DefaultTabClearType = 0;
bool _GetTabClearType(const gsl::span<const size_t> parameters,
size_t& clearType) const noexcept;
static constexpr DispatchTypes::WindowManipulationType DefaultWindowManipulationType = DispatchTypes::WindowManipulationType::Invalid;
bool _GetWindowManipulationType(const gsl::span<const size_t> parameters,
unsigned int& function) const noexcept;
bool _GetOscSetColorTable(const std::wstring_view string,
std::vector<size_t>& tableIndexes,
std::vector<DWORD>& rgbs) const noexcept;
@ -234,14 +178,6 @@ namespace Microsoft::Console::VirtualTerminal
bool _GetOscSetColor(const std::wstring_view string,
std::vector<DWORD>& rgbs) const noexcept;
static constexpr DispatchTypes::CursorStyle DefaultCursorStyle = DispatchTypes::CursorStyle::UserDefault;
bool _GetCursorStyle(const gsl::span<const size_t> parameters,
DispatchTypes::CursorStyle& cursorStyle) const noexcept;
static constexpr size_t DefaultRepeatCount = 1;
bool _GetRepeatCount(const gsl::span<const size_t> parameters,
size_t& repeatCount) const noexcept;
bool _GetOscSetClipboard(const std::wstring_view string,
std::wstring& content,
bool& queryClipboard) const noexcept;

View file

@ -16,6 +16,7 @@ StateMachine::StateMachine(std::unique_ptr<IStateMachineEngine> engine) :
_trace(Microsoft::Console::VirtualTerminal::ParserTracing()),
_isInAnsiMode(true),
_parameters{},
_parameterLimitReached(false),
_oscString{},
_cachedSequence{ std::nullopt },
_processingIndividually(false)
@ -507,24 +508,41 @@ void StateMachine::_ActionParam(const wchar_t wch)
{
_trace.TraceOnAction(L"Param");
// If we have no parameters and we're about to add one, get the 0 value ready here.
if (_parameters.empty())
// Once we've reached the parameter limit, additional parameters are ignored.
if (!_parameterLimitReached)
{
_parameters.push_back(0);
}
// If we have no parameters and we're about to add one, get the next value ready here.
if (_parameters.empty())
{
_parameters.push_back({});
}
// On a delimiter, increase the number of params we've seen.
// "Empty" params should still count as a param -
// eg "\x1b[0;;m" should be three "0" params
if (wch == L';')
{
// Move to next param.
_parameters.push_back(0);
}
else
{
// Accumulate the character given into the last (current) parameter
_AccumulateTo(wch, _parameters.back());
// On a delimiter, increase the number of params we've seen.
// "Empty" params should still count as a param -
// eg "\x1b[0;;m" should be three params
if (_isParameterDelimiter(wch))
{
// If we receive a delimiter after we've already accumulated the
// maximum allowed parameters, then we need to set a flag to
// indicate that further parameter characters should be ignored.
if (_parameters.size() >= MAX_PARAMETER_COUNT)
{
_parameterLimitReached = true;
}
else
{
// Otherwise move to next param.
_parameters.push_back({});
}
}
else
{
// Accumulate the character given into the last (current) parameter.
// If the value hasn't been initialized yet, it'll start as 0.
auto currentParameter = _parameters.back().value_or(0);
_AccumulateTo(wch, currentParameter);
_parameters.back() = currentParameter;
}
}
}
@ -542,6 +560,7 @@ void StateMachine::_ActionClear()
_identifier.Clear();
_parameters.clear();
_parameterLimitReached = false;
_oscString.clear();
_oscParameter = 0;

View file

@ -27,6 +27,11 @@ namespace Microsoft::Console::VirtualTerminal
// but for now 32767 is the safest limit for our existing code base.
constexpr size_t MAX_PARAMETER_VALUE = 32767;
// The DEC STD 070 reference requires that a minimum of 16 parameter values
// are supported, but most modern terminal emulators will allow around twice
// that number.
constexpr size_t MAX_PARAMETER_COUNT = 32;
class StateMachine final
{
#ifdef UNIT_TESTING
@ -148,7 +153,8 @@ namespace Microsoft::Console::VirtualTerminal
std::wstring_view _run;
VTIDBuilder _identifier;
std::vector<size_t> _parameters;
std::vector<VTParameter> _parameters;
bool _parameterLimitReached;
std::wstring _oscString;
size_t _oscParameter;

View file

@ -73,8 +73,7 @@ public:
_expectSendCtrlC{ false },
_expectCursorPosition{ false },
_expectedCursor{ -1, -1 },
_expectedWindowManipulation{ DispatchTypes::WindowManipulationType::Invalid },
_expectedCParams{ 0 }
_expectedWindowManipulation{ DispatchTypes::WindowManipulationType::Invalid }
{
std::fill_n(_expectedParams, ARRAYSIZE(_expectedParams), gsl::narrow<short>(0));
}
@ -207,7 +206,6 @@ public:
COORD _expectedCursor;
DispatchTypes::WindowManipulationType _expectedWindowManipulation;
unsigned short _expectedParams[16];
size_t _expectedCParams;
};
class Microsoft::Console::VirtualTerminal::InputEngineTest
@ -327,7 +325,8 @@ public:
virtual bool WriteCtrlKey(const KeyEvent& event) override;
virtual bool WindowManipulation(const DispatchTypes::WindowManipulationType function,
const gsl::span<const size_t> parameters) override; // DTTERM_WindowManipulation
const VTParameter parameter1,
const VTParameter parameter2) override; // DTTERM_WindowManipulation
virtual bool WriteString(const std::wstring_view string) override;
virtual bool MoveCursor(const size_t row,
@ -362,16 +361,13 @@ bool TestInteractDispatch::WriteCtrlKey(const KeyEvent& event)
}
bool TestInteractDispatch::WindowManipulation(const DispatchTypes::WindowManipulationType function,
const gsl::span<const size_t> parameters)
const VTParameter parameter1,
const VTParameter parameter2)
{
VERIFY_ARE_EQUAL(true, _testState->_expectedToCallWindowManipulation);
VERIFY_ARE_EQUAL(_testState->_expectedWindowManipulation, function);
for (size_t i = 0; i < parameters.size(); i++)
{
unsigned short actual;
VERIFY_SUCCEEDED(SizeTToUShort(til::at(parameters, i), &actual));
VERIFY_ARE_EQUAL(_testState->_expectedParams[i], actual);
}
VERIFY_ARE_EQUAL(_testState->_expectedParams[0], parameter1.value_or(0));
VERIFY_ARE_EQUAL(_testState->_expectedParams[1], parameter2.value_or(0));
return true;
}
@ -634,8 +630,6 @@ void InputEngineTest::WindowManipulationTest()
L"Only the valid ones should call the "
L"TestInteractDispatch::WindowManipulation callback."));
bool fValidType = false;
const unsigned short param1 = 123;
const unsigned short param2 = 456;
const wchar_t* const wszParam1 = L"123";
@ -643,11 +637,6 @@ void InputEngineTest::WindowManipulationTest()
for (unsigned int i = 0; i < static_cast<unsigned int>(BYTE_MAX); i++)
{
if (i == DispatchTypes::WindowManipulationType::ResizeWindowInCharacters)
{
fValidType = true;
}
std::wstringstream seqBuilder;
seqBuilder << L"\x1b[" << i;
@ -659,24 +648,18 @@ void InputEngineTest::WindowManipulationTest()
seqBuilder << L";" << wszParam1 << L";" << wszParam2;
testState._expectedToCallWindowManipulation = true;
testState._expectedCParams = 2;
testState._expectedParams[0] = param1;
testState._expectedParams[1] = param2;
testState._expectedWindowManipulation = static_cast<DispatchTypes::WindowManipulationType>(i);
}
else if (i == DispatchTypes::WindowManipulationType::RefreshWindow)
{
// refresh window doesn't expect any params.
testState._expectedToCallWindowManipulation = true;
testState._expectedCParams = 0;
testState._expectedWindowManipulation = static_cast<DispatchTypes::WindowManipulationType>(i);
}
else
{
testState._expectedToCallWindowManipulation = false;
testState._expectedCParams = 0;
testState._expectedWindowManipulation = DispatchTypes::WindowManipulationType::Invalid;
// other operations don't expect any params.
testState._expectedToCallWindowManipulation = true;
testState._expectedParams[0] = 0;
testState._expectedParams[1] = 0;
testState._expectedWindowManipulation = static_cast<DispatchTypes::WindowManipulationType>(i);
}
seqBuilder << L"t";
std::wstring seq = seqBuilder.str();
@ -1422,9 +1405,8 @@ void InputEngineTest::TestWin32InputParsing()
auto engine = std::make_unique<InputStateMachineEngine>(std::move(dispatch));
{
KeyEvent key{};
std::vector<size_t> params{ 1 };
VERIFY_IS_TRUE(engine->_GenerateWin32Key({ params.data(), params.size() }, key));
std::vector<VTParameter> params{ 1 };
KeyEvent key = engine->_GenerateWin32Key({ params.data(), params.size() });
VERIFY_ARE_EQUAL(1, key.GetVirtualKeyCode());
VERIFY_ARE_EQUAL(0, key.GetVirtualScanCode());
VERIFY_ARE_EQUAL(L'\0', key.GetCharData());
@ -1433,9 +1415,8 @@ void InputEngineTest::TestWin32InputParsing()
VERIFY_ARE_EQUAL(1, key.GetRepeatCount());
}
{
KeyEvent key{};
std::vector<size_t> params{ 1, 2 };
VERIFY_IS_TRUE(engine->_GenerateWin32Key({ params.data(), params.size() }, key));
std::vector<VTParameter> params{ 1, 2 };
KeyEvent key = engine->_GenerateWin32Key({ params.data(), params.size() });
VERIFY_ARE_EQUAL(1, key.GetVirtualKeyCode());
VERIFY_ARE_EQUAL(2, key.GetVirtualScanCode());
VERIFY_ARE_EQUAL(L'\0', key.GetCharData());
@ -1444,9 +1425,8 @@ void InputEngineTest::TestWin32InputParsing()
VERIFY_ARE_EQUAL(1, key.GetRepeatCount());
}
{
KeyEvent key{};
std::vector<size_t> params{ 1, 2, 3 };
VERIFY_IS_TRUE(engine->_GenerateWin32Key({ params.data(), params.size() }, key));
std::vector<VTParameter> params{ 1, 2, 3 };
KeyEvent key = engine->_GenerateWin32Key({ params.data(), params.size() });
VERIFY_ARE_EQUAL(1, key.GetVirtualKeyCode());
VERIFY_ARE_EQUAL(2, key.GetVirtualScanCode());
VERIFY_ARE_EQUAL(L'\x03', key.GetCharData());
@ -1455,9 +1435,8 @@ void InputEngineTest::TestWin32InputParsing()
VERIFY_ARE_EQUAL(1, key.GetRepeatCount());
}
{
KeyEvent key{};
std::vector<size_t> params{ 1, 2, 3, 4 };
VERIFY_IS_TRUE(engine->_GenerateWin32Key({ params.data(), params.size() }, key));
std::vector<VTParameter> params{ 1, 2, 3, 4 };
KeyEvent key = engine->_GenerateWin32Key({ params.data(), params.size() });
VERIFY_ARE_EQUAL(1, key.GetVirtualKeyCode());
VERIFY_ARE_EQUAL(2, key.GetVirtualScanCode());
VERIFY_ARE_EQUAL(L'\x03', key.GetCharData());
@ -1466,9 +1445,8 @@ void InputEngineTest::TestWin32InputParsing()
VERIFY_ARE_EQUAL(1, key.GetRepeatCount());
}
{
KeyEvent key{};
std::vector<size_t> params{ 1, 2, 3, 1 };
VERIFY_IS_TRUE(engine->_GenerateWin32Key({ params.data(), params.size() }, key));
std::vector<VTParameter> params{ 1, 2, 3, 1 };
KeyEvent key = engine->_GenerateWin32Key({ params.data(), params.size() });
VERIFY_ARE_EQUAL(1, key.GetVirtualKeyCode());
VERIFY_ARE_EQUAL(2, key.GetVirtualScanCode());
VERIFY_ARE_EQUAL(L'\x03', key.GetCharData());
@ -1477,9 +1455,8 @@ void InputEngineTest::TestWin32InputParsing()
VERIFY_ARE_EQUAL(1, key.GetRepeatCount());
}
{
KeyEvent key{};
std::vector<size_t> params{ 1, 2, 3, 4, 5 };
VERIFY_IS_TRUE(engine->_GenerateWin32Key({ params.data(), params.size() }, key));
std::vector<VTParameter> params{ 1, 2, 3, 4, 5 };
KeyEvent key = engine->_GenerateWin32Key({ params.data(), params.size() });
VERIFY_ARE_EQUAL(1, key.GetVirtualKeyCode());
VERIFY_ARE_EQUAL(2, key.GetVirtualScanCode());
VERIFY_ARE_EQUAL(L'\x03', key.GetCharData());
@ -1488,9 +1465,8 @@ void InputEngineTest::TestWin32InputParsing()
VERIFY_ARE_EQUAL(1, key.GetRepeatCount());
}
{
KeyEvent key{};
std::vector<size_t> params{ 1, 2, 3, 4, 5, 6 };
VERIFY_IS_TRUE(engine->_GenerateWin32Key({ params.data(), params.size() }, key));
std::vector<VTParameter> params{ 1, 2, 3, 4, 5, 6 };
KeyEvent key = engine->_GenerateWin32Key({ params.data(), params.size() });
VERIFY_ARE_EQUAL(1, key.GetVirtualKeyCode());
VERIFY_ARE_EQUAL(2, key.GetVirtualScanCode());
VERIFY_ARE_EQUAL(L'\x03', key.GetCharData());
@ -1498,11 +1474,6 @@ void InputEngineTest::TestWin32InputParsing()
VERIFY_ARE_EQUAL(0x5u, key.GetActiveModifierKeys());
VERIFY_ARE_EQUAL(6, key.GetRepeatCount());
}
{
KeyEvent key{};
std::vector<size_t> params{ 1, 2, 3, 4, 5, 6, 7 };
VERIFY_IS_FALSE(engine->_GenerateWin32Key({ params.data(), params.size() }, key));
}
}
void InputEngineTest::TestWin32InputOptionals()
@ -1532,8 +1503,7 @@ void InputEngineTest::TestWin32InputOptionals()
auto engine = std::make_unique<InputStateMachineEngine>(std::move(dispatch));
{
KeyEvent key{};
std::vector<size_t> params{
std::vector<VTParameter> params{
::base::saturated_cast<size_t>(provideVirtualKeyCode ? 1 : 0),
::base::saturated_cast<size_t>(provideVirtualScanCode ? 2 : 0),
::base::saturated_cast<size_t>(provideCharData ? 3 : 0),
@ -1542,7 +1512,7 @@ void InputEngineTest::TestWin32InputOptionals()
::base::saturated_cast<size_t>(provideRepeatCount ? 6 : 0)
};
VERIFY_IS_TRUE(engine->_GenerateWin32Key({ params.data(), static_cast<size_t>(numParams) }, key));
KeyEvent key = engine->_GenerateWin32Key({ params.data(), static_cast<size_t>(numParams) });
VERIFY_ARE_EQUAL((provideVirtualKeyCode && numParams > 0) ? 1 : 0,
key.GetVirtualKeyCode());
VERIFY_ARE_EQUAL((provideVirtualScanCode && numParams > 1) ? 2 : 0,

View file

@ -337,12 +337,46 @@ class Microsoft::Console::VirtualTerminal::OutputEngineTest final
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
VERIFY_ARE_EQUAL(mach._parameters.size(), 4u);
VERIFY_ARE_EQUAL(mach._parameters.at(0), 0u);
VERIFY_IS_FALSE(mach._parameters.at(0).has_value());
VERIFY_ARE_EQUAL(mach._parameters.at(1), 324u);
VERIFY_ARE_EQUAL(mach._parameters.at(2), 0u);
VERIFY_IS_FALSE(mach._parameters.at(2).has_value());
VERIFY_ARE_EQUAL(mach._parameters.at(3), 8u);
}
TEST_METHOD(TestCsiMaxParamCount)
{
auto dispatch = std::make_unique<DummyDispatch>();
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
StateMachine mach(std::move(engine));
Log::Comment(L"Output a sequence with 100 parameters");
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'[');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiEntry);
for (size_t i = 0; i < 100; i++)
{
if (i > 0)
{
mach.ProcessCharacter(L';');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
}
mach.ProcessCharacter(L'0' + i % 10);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
}
mach.ProcessCharacter(L'J');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
Log::Comment(L"Only MAX_PARAMETER_COUNT (32) parameters should be stored");
VERIFY_ARE_EQUAL(mach._parameters.size(), MAX_PARAMETER_COUNT);
for (size_t i = 0; i < MAX_PARAMETER_COUNT; i++)
{
VERIFY_IS_TRUE(mach._parameters.at(i).has_value());
VERIFY_ARE_EQUAL(mach._parameters.at(i).value(), i % 10);
}
}
TEST_METHOD(TestLeadingZeroCsiParam)
{
auto dispatch = std::make_unique<DummyDispatch>();
@ -748,9 +782,9 @@ class Microsoft::Console::VirtualTerminal::OutputEngineTest final
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsParam);
VERIFY_ARE_EQUAL(mach._parameters.size(), 4u);
VERIFY_ARE_EQUAL(mach._parameters.at(0), 0u);
VERIFY_IS_FALSE(mach._parameters.at(0).has_value());
VERIFY_ARE_EQUAL(mach._parameters.at(1), 324u);
VERIFY_ARE_EQUAL(mach._parameters.at(2), 0u);
VERIFY_IS_FALSE(mach._parameters.at(2).has_value());
VERIFY_ARE_EQUAL(mach._parameters.at(3), 8u);
mach.ProcessCharacter(AsciiChars::ESC);
@ -984,6 +1018,7 @@ public:
_insertCharacter{ false },
_deleteCharacter{ false },
_eraseType{ (DispatchTypes::EraseType)-1 },
_eraseTypes{},
_setGraphics{ false },
_statusReportType{ (DispatchTypes::AnsiStatusType)-1 },
_deviceStatusReport{ false },
@ -1005,6 +1040,8 @@ public:
_reverseLineFeed{ false },
_forwardTab{ false },
_numTabs{ 0 },
_tabClear{ false },
_tabClearTypes{},
_isDECCOLMAllowed{ false },
_windowWidth{ 80 },
_win32InputMode{ false },
@ -1119,6 +1156,7 @@ public:
{
_eraseDisplay = true;
_eraseType = eraseType;
_eraseTypes.push_back(eraseType);
return true;
}
@ -1126,6 +1164,7 @@ public:
{
_eraseLine = true;
_eraseType = eraseType;
_eraseTypes.push_back(eraseType);
return true;
}
@ -1149,10 +1188,14 @@ public:
return true;
}
bool SetGraphicsRendition(const gsl::span<const DispatchTypes::GraphicsOptions> options) noexcept override
bool SetGraphicsRendition(const VTParameters options) noexcept override
try
{
_options.assign(options.begin(), options.end());
_options.clear();
for (size_t i = 0; i < options.size(); i++)
{
_options.push_back(options.at(i));
}
_setGraphics = true;
return true;
}
@ -1242,25 +1285,14 @@ public:
return fSuccess;
}
bool _SetResetPrivateModesHelper(const gsl::span<const DispatchTypes::PrivateModeParams> params,
const bool enable)
bool SetPrivateMode(const DispatchTypes::PrivateModeParams param) noexcept override
{
size_t cFailures = 0;
for (const auto& p : params)
{
cFailures += _PrivateModeParamsHelper(p, enable) ? 0 : 1; // increment the number of failures if we fail.
}
return cFailures == 0;
return _PrivateModeParamsHelper(param, true);
}
bool SetPrivateModes(const gsl::span<const DispatchTypes::PrivateModeParams> params) noexcept override
bool ResetPrivateMode(const DispatchTypes::PrivateModeParams param) noexcept override
{
return _SetResetPrivateModesHelper(params, true);
}
bool ResetPrivateModes(const gsl::span<const DispatchTypes::PrivateModeParams> params) noexcept override
{
return _SetResetPrivateModesHelper(params, false);
return _PrivateModeParamsHelper(param, false);
}
bool SetColumns(_In_ size_t const uiColumns) noexcept override
@ -1343,6 +1375,13 @@ public:
return true;
}
bool TabClear(const DispatchTypes::TabClearType clearType) noexcept override
{
_tabClear = true;
_tabClearTypes.push_back(clearType);
return true;
}
bool EnableDECCOLMSupport(const bool fEnabled) noexcept override
{
_isDECCOLMAllowed = fEnabled;
@ -1429,6 +1468,7 @@ public:
bool _insertCharacter;
bool _deleteCharacter;
DispatchTypes::EraseType _eraseType;
std::vector<DispatchTypes::EraseType> _eraseTypes;
bool _setGraphics;
DispatchTypes::AnsiStatusType _statusReportType;
bool _deviceStatusReport;
@ -1450,6 +1490,8 @@ public:
bool _reverseLineFeed;
bool _forwardTab;
size_t _numTabs;
bool _tabClear;
std::vector<DispatchTypes::TabClearType> _tabClearTypes;
bool _isDECCOLMAllowed;
size_t _windowWidth;
bool _win32InputMode;
@ -1517,6 +1559,7 @@ class StateMachineExternalTest final
void TestCsiCursorMovement(wchar_t const wchCommand,
size_t const uiDistance,
const bool fUseDistance,
const bool fAddExtraParam,
const bool* const pfFlag,
StateMachine& mach,
StatefulDispatch& dispatch)
@ -1527,6 +1570,13 @@ class StateMachineExternalTest final
if (fUseDistance)
{
InsertNumberToMachine(&mach, uiDistance);
// Extraneous parameters should be ignored.
if (fAddExtraParam)
{
mach.ProcessCharacter(L';');
mach.ProcessCharacter(L'9');
}
}
mach.ProcessCharacter(wchCommand);
@ -1547,41 +1597,44 @@ class StateMachineExternalTest final
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:uiDistance", PARAM_VALUES)
TEST_METHOD_PROPERTY(L"Data:fExtraParam", L"{false,true}")
END_TEST_METHOD_PROPERTIES()
size_t uiDistance;
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"uiDistance", uiDistance));
bool fExtra;
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"fExtraParam", fExtra));
auto dispatch = std::make_unique<StatefulDispatch>();
auto pDispatch = dispatch.get();
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
StateMachine mach(std::move(engine));
TestCsiCursorMovement(L'A', uiDistance, true, &pDispatch->_cursorUp, mach, *pDispatch);
TestCsiCursorMovement(L'A', uiDistance, true, fExtra, &pDispatch->_cursorUp, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'B', uiDistance, true, &pDispatch->_cursorDown, mach, *pDispatch);
TestCsiCursorMovement(L'B', uiDistance, true, fExtra, &pDispatch->_cursorDown, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'C', uiDistance, true, &pDispatch->_cursorForward, mach, *pDispatch);
TestCsiCursorMovement(L'C', uiDistance, true, fExtra, &pDispatch->_cursorForward, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'D', uiDistance, true, &pDispatch->_cursorBackward, mach, *pDispatch);
TestCsiCursorMovement(L'D', uiDistance, true, fExtra, &pDispatch->_cursorBackward, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'E', uiDistance, true, &pDispatch->_cursorNextLine, mach, *pDispatch);
TestCsiCursorMovement(L'E', uiDistance, true, fExtra, &pDispatch->_cursorNextLine, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'F', uiDistance, true, &pDispatch->_cursorPreviousLine, mach, *pDispatch);
TestCsiCursorMovement(L'F', uiDistance, true, fExtra, &pDispatch->_cursorPreviousLine, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'G', uiDistance, true, &pDispatch->_cursorHorizontalPositionAbsolute, mach, *pDispatch);
TestCsiCursorMovement(L'G', uiDistance, true, fExtra, &pDispatch->_cursorHorizontalPositionAbsolute, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'`', uiDistance, true, &pDispatch->_cursorHorizontalPositionAbsolute, mach, *pDispatch);
TestCsiCursorMovement(L'`', uiDistance, true, fExtra, &pDispatch->_cursorHorizontalPositionAbsolute, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'd', uiDistance, true, &pDispatch->_verticalLinePositionAbsolute, mach, *pDispatch);
TestCsiCursorMovement(L'd', uiDistance, true, fExtra, &pDispatch->_verticalLinePositionAbsolute, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'a', uiDistance, true, &pDispatch->_horizontalPositionRelative, mach, *pDispatch);
TestCsiCursorMovement(L'a', uiDistance, true, fExtra, &pDispatch->_horizontalPositionRelative, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'e', uiDistance, true, &pDispatch->_verticalPositionRelative, mach, *pDispatch);
TestCsiCursorMovement(L'e', uiDistance, true, fExtra, &pDispatch->_verticalPositionRelative, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'@', uiDistance, true, &pDispatch->_insertCharacter, mach, *pDispatch);
TestCsiCursorMovement(L'@', uiDistance, true, fExtra, &pDispatch->_insertCharacter, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'P', uiDistance, true, &pDispatch->_deleteCharacter, mach, *pDispatch);
TestCsiCursorMovement(L'P', uiDistance, true, fExtra, &pDispatch->_deleteCharacter, mach, *pDispatch);
}
TEST_METHOD(TestCsiCursorMovementWithoutValues)
@ -1592,31 +1645,31 @@ class StateMachineExternalTest final
StateMachine mach(std::move(engine));
size_t uiDistance = 9999; // this value should be ignored with the false below.
TestCsiCursorMovement(L'A', uiDistance, false, &pDispatch->_cursorUp, mach, *pDispatch);
TestCsiCursorMovement(L'A', uiDistance, false, false, &pDispatch->_cursorUp, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'B', uiDistance, false, &pDispatch->_cursorDown, mach, *pDispatch);
TestCsiCursorMovement(L'B', uiDistance, false, false, &pDispatch->_cursorDown, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'C', uiDistance, false, &pDispatch->_cursorForward, mach, *pDispatch);
TestCsiCursorMovement(L'C', uiDistance, false, false, &pDispatch->_cursorForward, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'D', uiDistance, false, &pDispatch->_cursorBackward, mach, *pDispatch);
TestCsiCursorMovement(L'D', uiDistance, false, false, &pDispatch->_cursorBackward, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'E', uiDistance, false, &pDispatch->_cursorNextLine, mach, *pDispatch);
TestCsiCursorMovement(L'E', uiDistance, false, false, &pDispatch->_cursorNextLine, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'F', uiDistance, false, &pDispatch->_cursorPreviousLine, mach, *pDispatch);
TestCsiCursorMovement(L'F', uiDistance, false, false, &pDispatch->_cursorPreviousLine, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'G', uiDistance, false, &pDispatch->_cursorHorizontalPositionAbsolute, mach, *pDispatch);
TestCsiCursorMovement(L'G', uiDistance, false, false, &pDispatch->_cursorHorizontalPositionAbsolute, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'`', uiDistance, false, &pDispatch->_cursorHorizontalPositionAbsolute, mach, *pDispatch);
TestCsiCursorMovement(L'`', uiDistance, false, false, &pDispatch->_cursorHorizontalPositionAbsolute, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'd', uiDistance, false, &pDispatch->_verticalLinePositionAbsolute, mach, *pDispatch);
TestCsiCursorMovement(L'd', uiDistance, false, false, &pDispatch->_verticalLinePositionAbsolute, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'a', uiDistance, false, &pDispatch->_horizontalPositionRelative, mach, *pDispatch);
TestCsiCursorMovement(L'a', uiDistance, false, false, &pDispatch->_horizontalPositionRelative, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'e', uiDistance, false, &pDispatch->_verticalPositionRelative, mach, *pDispatch);
TestCsiCursorMovement(L'e', uiDistance, false, false, &pDispatch->_verticalPositionRelative, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'@', uiDistance, false, &pDispatch->_insertCharacter, mach, *pDispatch);
TestCsiCursorMovement(L'@', uiDistance, false, false, &pDispatch->_insertCharacter, mach, *pDispatch);
pDispatch->ClearState();
TestCsiCursorMovement(L'P', uiDistance, false, &pDispatch->_deleteCharacter, mach, *pDispatch);
TestCsiCursorMovement(L'P', uiDistance, false, false, &pDispatch->_deleteCharacter, mach, *pDispatch);
}
TEST_METHOD(TestCsiCursorPosition)
@ -1928,6 +1981,31 @@ class StateMachineExternalTest final
pDispatch->ClearState();
}
TEST_METHOD(TestMultipleModes)
{
auto dispatch = std::make_unique<StatefulDispatch>();
auto pDispatch = dispatch.get();
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
StateMachine mach(std::move(engine));
mach.ProcessString(L"\x1b[?5;1;6h");
VERIFY_IS_TRUE(pDispatch->_isScreenModeReversed);
VERIFY_IS_TRUE(pDispatch->_cursorKeysMode);
VERIFY_IS_TRUE(pDispatch->_isOriginModeRelative);
pDispatch->ClearState();
pDispatch->_isScreenModeReversed = true;
pDispatch->_cursorKeysMode = true;
pDispatch->_isOriginModeRelative = true;
mach.ProcessString(L"\x1b[?5;1;6l");
VERIFY_IS_FALSE(pDispatch->_isScreenModeReversed);
VERIFY_IS_FALSE(pDispatch->_cursorKeysMode);
VERIFY_IS_FALSE(pDispatch->_isOriginModeRelative);
pDispatch->ClearState();
}
TEST_METHOD(TestErase)
{
BEGIN_TEST_METHOD_PROPERTIES()
@ -1996,6 +2074,28 @@ class StateMachineExternalTest final
VERIFY_ARE_EQUAL(expectedDispatchTypes, pDispatch->_eraseType);
}
TEST_METHOD(TestMultipleErase)
{
auto dispatch = std::make_unique<StatefulDispatch>();
auto pDispatch = dispatch.get();
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
StateMachine mach(std::move(engine));
mach.ProcessString(L"\x1b[3;2J");
auto expectedEraseTypes = std::vector{ DispatchTypes::EraseType::Scrollback, DispatchTypes::EraseType::All };
VERIFY_IS_TRUE(pDispatch->_eraseDisplay);
VERIFY_ARE_EQUAL(expectedEraseTypes, pDispatch->_eraseTypes);
pDispatch->ClearState();
mach.ProcessString(L"\x1b[0;1K");
expectedEraseTypes = std::vector{ DispatchTypes::EraseType::ToEnd, DispatchTypes::EraseType::FromBeginning };
VERIFY_IS_TRUE(pDispatch->_eraseLine);
VERIFY_ARE_EQUAL(expectedEraseTypes, pDispatch->_eraseTypes);
pDispatch->ClearState();
}
void VerifyDispatchTypes(const gsl::span<const DispatchTypes::GraphicsOptions> expectedOptions,
const StatefulDispatch& dispatch)
{
@ -2194,16 +2294,7 @@ class StateMachineExternalTest final
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
StateMachine mach(std::move(engine));
Log::Comment(L"Test 1: Check empty case. Should fail.");
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L'n');
VERIFY_IS_FALSE(pDispatch->_deviceStatusReport);
pDispatch->ClearState();
Log::Comment(L"Test 2: Check OS (operating status) case 5. Should succeed.");
Log::Comment(L"Test 1: Check OS (operating status) case 5. Should succeed.");
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L'5');
@ -2214,7 +2305,7 @@ class StateMachineExternalTest final
pDispatch->ClearState();
Log::Comment(L"Test 3: Check CPR (cursor position report) case 6. Should succeed.");
Log::Comment(L"Test 2: Check CPR (cursor position report) case 6. Should succeed.");
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L'6');
@ -2224,16 +2315,6 @@ class StateMachineExternalTest final
VERIFY_ARE_EQUAL(DispatchTypes::AnsiStatusType::CPR_CursorPositionReport, pDispatch->_statusReportType);
pDispatch->ClearState();
Log::Comment(L"Test 4: Check unimplemented case 1. Should fail.");
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L'1');
mach.ProcessCharacter(L'n');
VERIFY_IS_FALSE(pDispatch->_deviceStatusReport);
pDispatch->ClearState();
}
TEST_METHOD(TestDeviceAttributes)
@ -2551,6 +2632,35 @@ class StateMachineExternalTest final
pDispatch->ClearState();
}
TEST_METHOD(TestTabClear)
{
auto dispatch = std::make_unique<StatefulDispatch>();
auto pDispatch = dispatch.get();
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
StateMachine mach(std::move(engine));
mach.ProcessString(L"\x1b[g");
auto expectedClearTypes = std::vector{ DispatchTypes::TabClearType::ClearCurrentColumn };
VERIFY_IS_TRUE(pDispatch->_tabClear);
VERIFY_ARE_EQUAL(expectedClearTypes, pDispatch->_tabClearTypes);
pDispatch->ClearState();
mach.ProcessString(L"\x1b[3g");
expectedClearTypes = std::vector{ DispatchTypes::TabClearType::ClearAllColumns };
VERIFY_IS_TRUE(pDispatch->_tabClear);
VERIFY_ARE_EQUAL(expectedClearTypes, pDispatch->_tabClearTypes);
pDispatch->ClearState();
mach.ProcessString(L"\x1b[0;3g");
expectedClearTypes = std::vector{ DispatchTypes::TabClearType::ClearCurrentColumn, DispatchTypes::TabClearType::ClearAllColumns };
VERIFY_IS_TRUE(pDispatch->_tabClear);
VERIFY_ARE_EQUAL(expectedClearTypes, pDispatch->_tabClearTypes);
pDispatch->ClearState();
}
TEST_METHOD(TestVt52Sequences)
{
auto dispatch = std::make_unique<StatefulDispatch>();

View file

@ -32,7 +32,7 @@ public:
{
printed.clear();
passedThrough.clear();
csiParams.reset();
csiParams.clear();
}
bool ActionExecute(const wchar_t /* wch */) override { return true; };
@ -52,7 +52,7 @@ public:
bool ActionEscDispatch(const VTID /* id */) override { return true; };
bool ActionVt52EscDispatch(const VTID /*id*/, const gsl::span<const size_t> /*parameters*/) override { return true; };
bool ActionVt52EscDispatch(const VTID /*id*/, const VTParameters /*parameters*/) override { return true; };
bool ActionClear() override { return true; };
@ -70,8 +70,7 @@ public:
return true;
};
bool ActionSs3Dispatch(const wchar_t /* wch */,
const gsl::span<const size_t> /* parameters */) override { return true; };
bool ActionSs3Dispatch(const wchar_t /* wch */, const VTParameters /* parameters */) override { return true; };
bool ParseControlSequenceAfterSs3() const override { return false; }
bool FlushAtEndOfString() const override { return false; };
@ -79,7 +78,7 @@ public:
bool DispatchIntermediatesFromEscape() const override { return false; };
// ActionCsiDispatch is the only method that's actually implemented.
bool ActionCsiDispatch(const VTID /*id*/, const gsl::span<const size_t> parameters) override
bool ActionCsiDispatch(const VTID /*id*/, const VTParameters parameters) override
{
// If flush to terminal is registered for a test, then use it.
if (pfnFlushToTerminal)
@ -89,13 +88,16 @@ public:
}
else
{
csiParams.emplace(parameters.begin(), parameters.end());
for (size_t i = 0; i < parameters.size(); i++)
{
csiParams.push_back(parameters.at(i).value_or(0));
}
return true;
}
}
// This will only be populated if ActionCsiDispatch is called.
std::optional<std::vector<size_t>> csiParams;
std::vector<size_t> csiParams;
// Flush function for pass-through test.
std::function<bool()> pfnFlushToTerminal;