Refactor VT control sequence identification (#7304)

This PR changes the way VT control sequences are identified and
dispatched, to be more efficient and easier to extend. Instead of
parsing the intermediate characters into a vector, and then having to
identify a sequence using both that vector and the final char, we now
use just a single `uint64_t` value as the identifier.

The way the identifier is constructed is by taking the private parameter
prefix, each of the intermediate characters, and then the final
character, and shifting them into a 64-bit integer one byte at a time,
in reverse order. For example, the `DECTLTC` control has a private
parameter prefix of `?`, one intermediate of `'`, and a final character
of `s`. The ASCII values of those characters are `0x3F`, `0x27`, and
`0x73` respectively, and reversing them gets you 0x73273F, so that would
then be the identifier for the control.

The reason for storing them in reverse order, is because sometimes we
need to look at the first intermediate to determine the operation, and
treat the rest of the sequence as a kind of sub-identifier (the
character set designation sequences are one example of this). When in
reverse order, this can easily be achieved by masking off the low byte
to get the first intermediate, and then shifting the value right by 8
bits to get a new identifier with the rest of the sequence.

With 64 bits we have enough space for a private prefix, six
intermediates, and the final char, which is way more than we should ever
need (the _DEC STD 070_ specification recommends supporting at least
three intermediates, but in practice we're unlikely to see more than
two).

With this new way of identifying controls, it should now be possible for
every action code to be unique (for the most part). So I've also used
this PR to clean up the action codes a bit, splitting the codes for the
escape sequences from the control sequences, and sorting them into
alphabetical order (which also does a reasonable job of clustering
associated controls).

## Validation Steps Performed

I think the existing unit tests should be good enough to confirm that
all sequences are still being dispatched correctly. However, I've also
manually tested a number of sequences to make sure they were still
working as expected, in particular those that used intermediates, since
they were the most affected by the dispatch code refactoring.

Since these changes also affected the input state machine, I've done
some manual testing of the conpty keyboard handling (both with and
without the new Win32 input mode enabled) to make sure the keyboard VT
sequences were processed correctly. I've also manually tested the
various VT mouse modes in Vttest to confirm that they were still working
correctly too.

Closes #7276
This commit is contained in:
James Holderness 2020-08-18 19:57:52 +01:00 committed by GitHub
parent 5d082ffe67
commit 7fcff4d33a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 733 additions and 913 deletions

View file

@ -548,6 +548,7 @@ DECSCUSR
DECSED
DECSEL
DECSET
DECSLPP
DECSLRM
DECSMBV
DECSMKR
@ -2579,6 +2580,7 @@ vstudio
vswhere
vtapp
VTE
VTID
vtio
vtmode
vtpipeterm

View file

@ -3,6 +3,94 @@
#pragma once
namespace Microsoft::Console::VirtualTerminal
{
class VTID
{
public:
template<size_t Length>
constexpr VTID(const char (&s)[Length]) :
_value{ _FromString(s) }
{
}
constexpr VTID(const uint64_t value) :
_value{ value }
{
}
constexpr operator uint64_t() const
{
return _value;
}
constexpr char operator[](const size_t offset) const
{
return SubSequence(offset)._value & 0xFF;
}
constexpr VTID SubSequence(const size_t offset) const
{
return _value >> (CHAR_BIT * offset);
}
private:
template<size_t Length>
static constexpr uint64_t _FromString(const char (&s)[Length])
{
static_assert(Length - 1 <= sizeof(_value));
uint64_t value = 0;
for (auto i = Length - 1; i-- > 0;)
{
value = (value << CHAR_BIT) + gsl::at(s, i);
}
return value;
}
uint64_t _value;
};
class VTIDBuilder
{
public:
void Clear() noexcept
{
_idAccumulator = 0;
_idShift = 0;
}
void AddIntermediate(const wchar_t intermediateChar) noexcept
{
if (_idShift + CHAR_BIT >= sizeof(_idAccumulator) * CHAR_BIT)
{
// If there is not enough space in the accumulator to add
// the intermediate and still have room left for the final,
// then we reset the accumulator to zero. This will result
// in an id with all zero intermediates, which shouldn't
// match anything.
_idAccumulator = 0;
}
else
{
// Otherwise we shift the intermediate so as to add it to the
// accumulator in the next available space, and then increment
// the shift by 8 bits in preparation for the next character.
_idAccumulator += (static_cast<uint64_t>(intermediateChar) << _idShift);
_idShift += CHAR_BIT;
}
}
VTID Finalize(const wchar_t finalChar) noexcept
{
return _idAccumulator + (static_cast<uint64_t>(finalChar) << _idShift);
}
private:
uint64_t _idAccumulator = 0;
size_t _idShift = 0;
};
}
namespace Microsoft::Console::VirtualTerminal::DispatchTypes
{
enum class EraseType : unsigned int
@ -101,16 +189,16 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes
W32IM_Win32InputMode = 9001
};
namespace CharacterSets
enum CharacterSets : uint64_t
{
constexpr auto DecSpecialGraphics = std::make_pair(L'0', L'\0');
constexpr auto ASCII = std::make_pair(L'B', L'\0');
}
DecSpecialGraphics = VTID("0"),
ASCII = VTID("B")
};
enum CodingSystem : wchar_t
enum CodingSystem : uint64_t
{
ISO2022 = L'@',
UTF8 = L'G'
ISO2022 = VTID("@"),
UTF8 = VTID("G")
};
enum TabClearType : unsigned short

View file

@ -98,9 +98,9 @@ public:
virtual bool TertiaryDeviceAttributes() = 0; // DA3
virtual bool Vt52DeviceAttributes() = 0; // VT52 Identify
virtual bool DesignateCodingSystem(const wchar_t codingSystem) = 0; // DOCS
virtual bool Designate94Charset(const size_t gsetNumber, const std::pair<wchar_t, wchar_t> charset) = 0; // SCS
virtual bool Designate96Charset(const size_t gsetNumber, const std::pair<wchar_t, wchar_t> charset) = 0; // SCS
virtual bool DesignateCodingSystem(const VTID codingSystem) = 0; // DOCS
virtual bool Designate94Charset(const size_t gsetNumber, const VTID charset) = 0; // SCS
virtual bool Designate96Charset(const size_t gsetNumber, const VTID charset) = 0; // SCS
virtual bool LockingShift(const size_t gsetNumber) = 0; // LS0, LS1, LS2, LS3
virtual bool LockingShiftRight(const size_t gsetNumber) = 0; // LS1R, LS2R, LS3R
virtual bool SingleShift(const size_t gsetNumber) = 0; // SS2, SS3

View file

@ -1670,7 +1670,7 @@ void AdaptDispatch::_InitTabStopsForWidth(const size_t width)
// - codingSystem - The coding system that will be selected.
// Return value:
// True if handled successfully. False otherwise.
bool AdaptDispatch::DesignateCodingSystem(const wchar_t codingSystem)
bool AdaptDispatch::DesignateCodingSystem(const VTID codingSystem)
{
// If we haven't previously saved the initial code page, do so now.
// This will be used to restore the code page in response to a reset.
@ -1712,10 +1712,10 @@ bool AdaptDispatch::DesignateCodingSystem(const wchar_t codingSystem)
// If the specified charset is unsupported, we do nothing (remain on the current one)
//Arguments:
// - gsetNumber - The G-set into which the charset will be selected.
// - charset - The characters indicating the charset that will be used.
// - charset - The identifier indicating the charset that will be used.
// Return value:
// True if handled successfully. False otherwise.
bool AdaptDispatch::Designate94Charset(const size_t gsetNumber, const std::pair<wchar_t, wchar_t> charset)
bool AdaptDispatch::Designate94Charset(const size_t gsetNumber, const VTID charset)
{
return _termOutput.Designate94Charset(gsetNumber, charset);
}
@ -1727,10 +1727,10 @@ bool AdaptDispatch::Designate94Charset(const size_t gsetNumber, const std::pair<
// If the specified charset is unsupported, we do nothing (remain on the current one)
//Arguments:
// - gsetNumber - The G-set into which the charset will be selected.
// - charset - The characters indicating the charset that will be used.
// - charset - The identifier indicating the charset that will be used.
// Return value:
// True if handled successfully. False otherwise.
bool AdaptDispatch::Designate96Charset(const size_t gsetNumber, const std::pair<wchar_t, wchar_t> charset)
bool AdaptDispatch::Designate96Charset(const size_t gsetNumber, const VTID charset)
{
return _termOutput.Designate96Charset(gsetNumber, charset);
}

View file

@ -89,9 +89,9 @@ namespace Microsoft::Console::VirtualTerminal
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 DesignateCodingSystem(const wchar_t codingSystem) override; // DOCS
bool Designate94Charset(const size_t gsetNumber, const std::pair<wchar_t, wchar_t> charset) override; // SCS
bool Designate96Charset(const size_t gsetNumber, const std::pair<wchar_t, wchar_t> charset) override; // SCS
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
bool LockingShift(const size_t gsetNumber) override; // LS0, LS1, LS2, LS3
bool LockingShiftRight(const size_t gsetNumber) override; // LS1R, LS2R, LS3R
bool SingleShift(const size_t gsetNumber) override; // SS2, SS3

View file

@ -92,9 +92,9 @@ public:
bool TertiaryDeviceAttributes() noexcept override { return false; } // DA3
bool Vt52DeviceAttributes() noexcept override { return false; } // VT52 Identify
bool DesignateCodingSystem(const wchar_t /*codingSystem*/) noexcept override { return false; } // DOCS
bool Designate94Charset(const size_t /*gsetNumber*/, const std::pair<wchar_t, wchar_t> /*charset*/) noexcept override { return false; } // SCS
bool Designate96Charset(const size_t /*gsetNumber*/, const std::pair<wchar_t, wchar_t> /*charset*/) noexcept override { return false; } // SCS
bool DesignateCodingSystem(const VTID /*codingSystem*/) noexcept override { return false; } // DOCS
bool Designate94Charset(const size_t /*gsetNumber*/, const VTID /*charset*/) noexcept override { return false; } // SCS
bool Designate96Charset(const size_t /*gsetNumber*/, const VTID /*charset*/) noexcept override { return false; } // SCS
bool LockingShift(const size_t /*gsetNumber*/) noexcept override { return false; } // LS0, LS1, LS2, LS3
bool LockingShiftRight(const size_t /*gsetNumber*/) noexcept override { return false; } // LS1R, LS2R, LS3R
bool SingleShift(const size_t /*gsetNumber*/) noexcept override { return false; } // SS2, SS3

View file

@ -17,107 +17,89 @@ TerminalOutput::TerminalOutput() noexcept
_gsetTranslationTables.at(3) = Latin1;
}
bool TerminalOutput::Designate94Charset(size_t gsetNumber, const std::pair<wchar_t, wchar_t> charset)
bool TerminalOutput::Designate94Charset(size_t gsetNumber, const VTID charset)
{
switch (charset.first)
switch (charset)
{
case L'B': // US ASCII
case L'1': // Alternate Character ROM
case VTID("B"): // US ASCII
case VTID("1"): // Alternate Character ROM
return _SetTranslationTable(gsetNumber, Ascii);
case L'0': // DEC Special Graphics
case L'2': // Alternate Character ROM Special Graphics
case VTID("0"): // DEC Special Graphics
case VTID("2"): // Alternate Character ROM Special Graphics
return _SetTranslationTable(gsetNumber, DecSpecialGraphics);
case L'<': // DEC Supplemental
case VTID("<"): // DEC Supplemental
return _SetTranslationTable(gsetNumber, DecSupplemental);
case L'A': // British NRCS
case VTID("A"): // British NRCS
return _SetTranslationTable(gsetNumber, BritishNrcs);
case L'4': // Dutch NRCS
case VTID("4"): // Dutch NRCS
return _SetTranslationTable(gsetNumber, DutchNrcs);
case L'5': // Finnish NRCS
case L'C': // (fallback)
case VTID("5"): // Finnish NRCS
case VTID("C"): // (fallback)
return _SetTranslationTable(gsetNumber, FinnishNrcs);
case L'R': // French NRCS
case VTID("R"): // French NRCS
return _SetTranslationTable(gsetNumber, FrenchNrcs);
case L'f': // French NRCS (ISO update)
case VTID("f"): // French NRCS (ISO update)
return _SetTranslationTable(gsetNumber, FrenchNrcsIso);
case L'9': // French Canadian NRCS
case L'Q': // (fallback)
case VTID("9"): // French Canadian NRCS
case VTID("Q"): // (fallback)
return _SetTranslationTable(gsetNumber, FrenchCanadianNrcs);
case L'K': // German NRCS
case VTID("K"): // German NRCS
return _SetTranslationTable(gsetNumber, GermanNrcs);
case L'Y': // Italian NRCS
case VTID("Y"): // Italian NRCS
return _SetTranslationTable(gsetNumber, ItalianNrcs);
case L'6': // Norwegian/Danish NRCS
case L'E': // (fallback)
case VTID("6"): // Norwegian/Danish NRCS
case VTID("E"): // (fallback)
return _SetTranslationTable(gsetNumber, NorwegianDanishNrcs);
case L'`': // Norwegian/Danish NRCS (ISO standard)
case VTID("`"): // Norwegian/Danish NRCS (ISO standard)
return _SetTranslationTable(gsetNumber, NorwegianDanishNrcsIso);
case L'Z': // Spanish NRCS
case VTID("Z"): // Spanish NRCS
return _SetTranslationTable(gsetNumber, SpanishNrcs);
case L'7': // Swedish NRCS
case L'H': // (fallback)
case VTID("7"): // Swedish NRCS
case VTID("H"): // (fallback)
return _SetTranslationTable(gsetNumber, SwedishNrcs);
case L'=': // Swiss NRCS
case VTID("="): // Swiss NRCS
return _SetTranslationTable(gsetNumber, SwissNrcs);
case L'&':
switch (charset.second)
{
case L'4': // DEC Cyrillic
return _SetTranslationTable(gsetNumber, DecCyrillic);
case L'5': // Russian NRCS
return _SetTranslationTable(gsetNumber, RussianNrcs);
default:
return false;
}
case L'"':
switch (charset.second)
{
case L'?': // DEC Greek
return _SetTranslationTable(gsetNumber, DecGreek);
case L'>': // Greek NRCS
return _SetTranslationTable(gsetNumber, GreekNrcs);
case L'4': // DEC Hebrew
return _SetTranslationTable(gsetNumber, DecHebrew);
default:
return false;
}
case L'%':
switch (charset.second)
{
case L'=': // Hebrew NRCS
return _SetTranslationTable(gsetNumber, HebrewNrcs);
case L'0': // DEC Turkish
return _SetTranslationTable(gsetNumber, DecTurkish);
case L'2': // Turkish NRCS
return _SetTranslationTable(gsetNumber, TurkishNrcs);
case L'5': // DEC Supplemental
return _SetTranslationTable(gsetNumber, DecSupplemental);
case L'6': // Portuguese NRCS
return _SetTranslationTable(gsetNumber, PortugueseNrcs);
default:
return false;
}
case VTID("&4"): // DEC Cyrillic
return _SetTranslationTable(gsetNumber, DecCyrillic);
case VTID("&5"): // Russian NRCS
return _SetTranslationTable(gsetNumber, RussianNrcs);
case VTID("\"?"): // DEC Greek
return _SetTranslationTable(gsetNumber, DecGreek);
case VTID("\">"): // Greek NRCS
return _SetTranslationTable(gsetNumber, GreekNrcs);
case VTID("\"4"): // DEC Hebrew
return _SetTranslationTable(gsetNumber, DecHebrew);
case VTID("%="): // Hebrew NRCS
return _SetTranslationTable(gsetNumber, HebrewNrcs);
case VTID("%0"): // DEC Turkish
return _SetTranslationTable(gsetNumber, DecTurkish);
case VTID("%2"): // Turkish NRCS
return _SetTranslationTable(gsetNumber, TurkishNrcs);
case VTID("%5"): // DEC Supplemental
return _SetTranslationTable(gsetNumber, DecSupplemental);
case VTID("%6"): // Portuguese NRCS
return _SetTranslationTable(gsetNumber, PortugueseNrcs);
default:
return false;
}
}
bool TerminalOutput::Designate96Charset(size_t gsetNumber, const std::pair<wchar_t, wchar_t> charset)
bool TerminalOutput::Designate96Charset(size_t gsetNumber, const VTID charset)
{
switch (charset.first)
switch (charset)
{
case L'A': // ISO Latin-1 Supplemental
case L'<': // (UPSS when assigned to Latin-1)
case VTID("A"): // ISO Latin-1 Supplemental
case VTID("<"): // (UPSS when assigned to Latin-1)
return _SetTranslationTable(gsetNumber, Latin1);
case L'B': // ISO Latin-2 Supplemental
case VTID("B"): // ISO Latin-2 Supplemental
return _SetTranslationTable(gsetNumber, Latin2);
case L'L': // ISO Latin-Cyrillic Supplemental
case VTID("L"): // ISO Latin-Cyrillic Supplemental
return _SetTranslationTable(gsetNumber, LatinCyrillic);
case L'F': // ISO Latin-Greek Supplemental
case VTID("F"): // ISO Latin-Greek Supplemental
return _SetTranslationTable(gsetNumber, LatinGreek);
case L'H': // ISO Latin-Hebrew Supplemental
case VTID("H"): // ISO Latin-Hebrew Supplemental
return _SetTranslationTable(gsetNumber, LatinHebrew);
case L'M': // ISO Latin-5 Supplemental
case VTID("M"): // ISO Latin-5 Supplemental
return _SetTranslationTable(gsetNumber, Latin5);
default:
return false;

View file

@ -26,8 +26,8 @@ namespace Microsoft::Console::VirtualTerminal
TerminalOutput() noexcept;
wchar_t TranslateKey(const wchar_t wch) const noexcept;
bool Designate94Charset(const size_t gsetNumber, const std::pair<wchar_t, wchar_t> charset);
bool Designate96Charset(const size_t gsetNumber, const std::pair<wchar_t, wchar_t> charset);
bool Designate94Charset(const size_t gsetNumber, const VTID charset);
bool Designate96Charset(const size_t gsetNumber, const VTID charset);
bool LockingShift(const size_t gsetNumber);
bool LockingShiftRight(const size_t gsetNumber);
bool SingleShift(const size_t gsetNumber);

View file

@ -12,6 +12,9 @@ Abstract:
the existing VT parsing.
*/
#pragma once
#include "../adapter/DispatchTypes.hpp"
namespace Microsoft::Console::VirtualTerminal
{
class IStateMachineEngine
@ -30,14 +33,9 @@ namespace Microsoft::Console::VirtualTerminal
virtual bool ActionPassThroughString(const std::wstring_view string) = 0;
virtual bool ActionEscDispatch(const wchar_t wch,
const gsl::span<const wchar_t> intermediates) = 0;
virtual bool ActionVt52EscDispatch(const wchar_t wch,
const gsl::span<const wchar_t> intermediates,
const gsl::span<const size_t> parameters) = 0;
virtual bool ActionCsiDispatch(const wchar_t wch,
const gsl::span<const wchar_t> intermediates,
const gsl::span<const size_t> parameters) = 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 ActionClear() = 0;

View file

@ -34,9 +34,9 @@ static constexpr std::array<CsiToVkey, 10> s_csiMap = {
CsiToVkey{ CsiActionCodes::CSI_F4, VK_F4 }
};
static bool operator==(const CsiToVkey& pair, const CsiActionCodes code) noexcept
static bool operator==(const CsiToVkey& pair, const VTID id) noexcept
{
return pair.action == code;
return pair.action == id;
}
struct GenericToVkey
@ -298,12 +298,10 @@ bool InputStateMachineEngine::ActionPassThroughString(const std::wstring_view st
// a simple escape sequence. These sequences traditionally start with ESC
// and a simple letter. No complicated parameters.
// Arguments:
// - wch - Character to dispatch.
// - intermediates - Intermediate characters in the sequence
// - id - Identifier of the escape sequence to dispatch.
// Return Value:
// - true iff we successfully dispatched the sequence.
bool InputStateMachineEngine::ActionEscDispatch(const wchar_t wch,
const gsl::span<const wchar_t> /*intermediates*/)
bool InputStateMachineEngine::ActionEscDispatch(const VTID id)
{
if (_pDispatch->IsVtInputEnabled() && _pfnFlushToInputQueue)
{
@ -312,6 +310,9 @@ bool InputStateMachineEngine::ActionEscDispatch(const wchar_t wch,
bool success = false;
// There are no intermediates, so the id is effectively the final char.
const wchar_t wch = gsl::narrow_cast<wchar_t>(id);
// 0x7f is DEL, which we treat effectively the same as a ctrl character.
if (wch == 0x7f)
{
@ -339,14 +340,11 @@ bool InputStateMachineEngine::ActionEscDispatch(const wchar_t wch,
// a VT52 escape sequence. These sequences start with ESC and a single letter,
// sometimes followed by parameters.
// Arguments:
// - wch - Character to dispatch.
// - intermediates - Intermediate characters in the sequence.
// - id - Identifier of the VT52 sequence to dispatch.
// - parameters - Set of parameters collected while parsing the sequence.
// Return Value:
// - true iff we successfully dispatched the sequence.
bool InputStateMachineEngine::ActionVt52EscDispatch(const wchar_t /*wch*/,
const gsl::span<const wchar_t> /*intermediates*/,
const gsl::span<const size_t> /*parameters*/) noexcept
bool InputStateMachineEngine::ActionVt52EscDispatch(const VTID /*id*/, const gsl::span<const size_t> /*parameters*/) noexcept
{
// VT52 escape sequences are not used in the input state machine.
return false;
@ -357,17 +355,12 @@ bool InputStateMachineEngine::ActionVt52EscDispatch(const wchar_t /*wch*/,
// a control sequence. These sequences perform various API-type commands
// that can include many parameters.
// Arguments:
// - wch - Character to dispatch.
// - intermediates - Intermediate characters in the sequence
// - id - Identifier of the control sequence to dispatch.
// - parameters - set of numeric parameters collected while parsing the sequence.
// Return Value:
// - true iff we successfully dispatched the sequence.
bool InputStateMachineEngine::ActionCsiDispatch(const wchar_t wch,
const gsl::span<const wchar_t> intermediates,
const gsl::span<const size_t> parameters)
bool InputStateMachineEngine::ActionCsiDispatch(const VTID id, const gsl::span<const size_t> parameters)
{
const auto actionCode = static_cast<CsiActionCodes>(wch);
// 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
// client. It's impossibly unlikely that the client actually wanted
@ -376,7 +369,7 @@ bool InputStateMachineEngine::ActionCsiDispatch(const wchar_t wch,
// client reads it.
if (_pDispatch->IsVtInputEnabled() &&
_pfnFlushToInputQueue &&
actionCode != CsiActionCodes::Win32KeyboardInput)
id != CsiActionCodes::Win32KeyboardInput)
{
return _pfnFlushToInputQueue();
}
@ -392,32 +385,22 @@ bool InputStateMachineEngine::ActionCsiDispatch(const wchar_t wch,
const auto remainingArgs = parameters.size() > 1 ? parameters.subspan(1) : gsl::span<const size_t>{};
bool success = false;
// Handle intermediate characters, if any
if (!intermediates.empty())
switch (id)
{
switch (static_cast<CsiIntermediateCodes>(til::at(intermediates, 0)))
{
case CsiIntermediateCodes::MOUSE_SGR:
{
DWORD buttonState = 0;
DWORD eventFlags = 0;
modifierState = _GetSGRMouseModifierState(parameters);
success = _GetSGRXYPosition(parameters, row, col);
case CsiActionCodes::MouseDown:
case CsiActionCodes::MouseUp:
{
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(wch, parameters, buttonState, eventFlags) && success;
success = success && _WriteMouseEvent(col, row, buttonState, modifierState, eventFlags);
break;
}
default:
success = false;
break;
}
// 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;
}
switch (actionCode)
{
case CsiActionCodes::Generic:
modifierState = _GetGenericKeysModifierState(parameters);
success = _GetGenericVkey(parameters, vkey);
@ -443,8 +426,8 @@ bool InputStateMachineEngine::ActionCsiDispatch(const wchar_t wch,
case CsiActionCodes::CSI_F1:
case CsiActionCodes::CSI_F2:
case CsiActionCodes::CSI_F4:
success = _GetCursorKeysVkey(wch, vkey);
modifierState = _GetCursorKeysModifierState(parameters, static_cast<CsiActionCodes>(wch));
success = _GetCursorKeysVkey(id, vkey);
modifierState = _GetCursorKeysModifierState(parameters, id);
break;
case CsiActionCodes::CursorBackTab:
modifierState = SHIFT_PRESSED;
@ -464,7 +447,7 @@ bool InputStateMachineEngine::ActionCsiDispatch(const wchar_t wch,
if (success)
{
switch (static_cast<CsiActionCodes>(wch))
switch (id)
{
// case CsiActionCodes::DSR_DeviceStatusReportResponse:
case CsiActionCodes::CSI_F3:
@ -799,10 +782,10 @@ bool InputStateMachineEngine::_WriteMouseEvent(const size_t column, const size_t
// sequence. This is for Arrow keys, Home, End, etc.
// Arguments:
// - parameters - the set of parameters to get the modifier state from.
// - actionCode - the actionCode for the sequence we're operating on.
// - 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 CsiActionCodes actionCode) noexcept
DWORD InputStateMachineEngine::_GetCursorKeysModifierState(const gsl::span<const size_t> parameters, const VTID id) noexcept
{
DWORD modifiers = 0;
if (_IsModified(parameters.size()) && parameters.size() >= 2)
@ -816,7 +799,7 @@ DWORD InputStateMachineEngine::_GetCursorKeysModifierState(const gsl::span<const
// of the keypad; and the divide (/) and ENTER keys in the keypad.
// This snippet detects the direction keys + HOME + END
// actionCode should be one of the above, so just make sure it's not a CSI_F# code
if (actionCode < CsiActionCodes::CSI_F1 || actionCode > CsiActionCodes::CSI_F4)
if (id < CsiActionCodes::CSI_F1 || id > CsiActionCodes::CSI_F4)
{
WI_SetFlag(modifiers, ENHANCED_KEY);
}
@ -917,13 +900,13 @@ DWORD InputStateMachineEngine::_GetModifier(const size_t modifierParam) noexcept
// - Here, we refer to and maintain the global state of our mouse.
// - Mouse wheel events are added at the end to keep them out of the global state
// Arguments:
// - wch: the wchar_t representing whether the button was pressed or released
// - 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)
// - 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 wchar_t wch,
bool InputStateMachineEngine::_UpdateSGRMouseButtonState(const VTID id,
const gsl::span<const size_t> parameters,
DWORD& buttonState,
DWORD& eventFlags) noexcept
@ -987,7 +970,7 @@ bool InputStateMachineEngine::_UpdateSGRMouseButtonState(const wchar_t wch,
// Step 2: Decide whether to set or clear that button's bit
// NOTE: WI_SetFlag/WI_ClearFlag can't be used here because buttonFlag would have to be a compile-time constant
switch (static_cast<CsiActionCodes>(wch))
switch (id)
{
case CsiActionCodes::MouseDown:
// set flag
@ -1049,15 +1032,15 @@ bool InputStateMachineEngine::_GetGenericVkey(const gsl::span<const size_t> para
// Method Description:
// - Gets the Vkey from the CSI codes table associated with a particular character.
// Arguments:
// - wch: the wchar_t to get the mapped vkey of.
// - id: the sequence identifier to get the mapped vkey of.
// - vkey: Receives the vkey
// Return Value:
// true iff we found the key
bool InputStateMachineEngine::_GetCursorKeysVkey(const wchar_t wch, short& vkey) const
bool InputStateMachineEngine::_GetCursorKeysVkey(const VTID id, short& vkey) const
{
vkey = 0;
const auto mapping = std::find(s_csiMap.cbegin(), s_csiMap.cend(), (CsiActionCodes)wch);
const auto mapping = std::find(s_csiMap.cbegin(), s_csiMap.cend(), id);
if (mapping != s_csiMap.end())
{
vkey = mapping->vkey;

View file

@ -50,30 +50,25 @@ namespace Microsoft::Console::VirtualTerminal
// CAPSLOCK_ON 0x0080
// ENHANCED_KEY 0x0100
enum CsiIntermediateCodes : wchar_t
enum CsiActionCodes : uint64_t
{
MOUSE_SGR = L'<',
};
enum class CsiActionCodes : wchar_t
{
ArrowUp = L'A',
ArrowDown = L'B',
ArrowRight = L'C',
ArrowLeft = L'D',
Home = L'H',
End = L'F',
MouseDown = L'M',
MouseUp = L'm',
Generic = L'~', // Used for a whole bunch of possible keys
CSI_F1 = L'P',
CSI_F2 = L'Q',
CSI_F3 = L'R', // Both F3 and DSR are on R.
// DSR_DeviceStatusReportResponse = L'R',
CSI_F4 = L'S',
DTTERM_WindowManipulation = L't',
CursorBackTab = L'Z',
Win32KeyboardInput = L'_'
ArrowUp = VTID("A"),
ArrowDown = VTID("B"),
ArrowRight = VTID("C"),
ArrowLeft = VTID("D"),
Home = VTID("H"),
End = VTID("F"),
MouseDown = VTID("<M"),
MouseUp = VTID("<m"),
Generic = VTID("~"), // Used for a whole bunch of possible keys
CSI_F1 = VTID("P"),
CSI_F2 = VTID("Q"),
CSI_F3 = VTID("R"), // Both F3 and DSR are on R.
// DSR_DeviceStatusReportResponse = VTID("R"),
CSI_F4 = VTID("S"),
DTTERM_WindowManipulation = VTID("t"),
CursorBackTab = VTID("Z"),
Win32KeyboardInput = VTID("_")
};
enum CsiMouseButtonCodes : unsigned short
@ -145,16 +140,11 @@ namespace Microsoft::Console::VirtualTerminal
bool ActionPassThroughString(const std::wstring_view string) override;
bool ActionEscDispatch(const wchar_t wch,
const gsl::span<const wchar_t> intermediates) override;
bool ActionEscDispatch(const VTID id) override;
bool ActionVt52EscDispatch(const wchar_t wch,
const gsl::span<const wchar_t> intermediates,
const gsl::span<const size_t> parameters) noexcept override;
bool ActionVt52EscDispatch(const VTID id, const gsl::span<const size_t> parameters) noexcept override;
bool ActionCsiDispatch(const wchar_t wch,
const gsl::span<const wchar_t> intermediates,
const gsl::span<const size_t> parameters) override;
bool ActionCsiDispatch(const VTID id, const gsl::span<const size_t> parameters) override;
bool ActionClear() noexcept override;
@ -180,7 +170,7 @@ namespace Microsoft::Console::VirtualTerminal
bool _lookingForDSR;
DWORD _mouseButtonState = 0;
DWORD _GetCursorKeysModifierState(const gsl::span<const size_t> parameters, const CsiActionCodes actionCode) noexcept;
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;
bool _GenerateKeyFromChar(const wchar_t wch, short& vkey, DWORD& modifierState) noexcept;
@ -188,13 +178,13 @@ namespace Microsoft::Console::VirtualTerminal
bool _IsModified(const size_t paramCount) noexcept;
DWORD _GetModifier(const size_t parameter) noexcept;
bool _UpdateSGRMouseButtonState(const wchar_t wch,
bool _UpdateSGRMouseButtonState(const VTID id,
const gsl::span<const size_t> parameters,
DWORD& buttonState,
DWORD& eventFlags) noexcept;
bool _GetGenericVkey(const gsl::span<const size_t> parameters,
short& vkey) const;
bool _GetCursorKeysVkey(const wchar_t wch, short& vkey) const;
bool _GetCursorKeysVkey(const VTID id, short& vkey) const;
bool _GetSs3KeysVkey(const wchar_t wch, short& vkey) const;
bool _WriteSingleKey(const short vkey, const DWORD modifierState);

File diff suppressed because it is too large Load diff

View file

@ -33,16 +33,11 @@ namespace Microsoft::Console::VirtualTerminal
bool ActionPassThroughString(const std::wstring_view string) override;
bool ActionEscDispatch(const wchar_t wch,
const gsl::span<const wchar_t> intermediates) override;
bool ActionEscDispatch(const VTID id) override;
bool ActionVt52EscDispatch(const wchar_t wch,
const gsl::span<const wchar_t> intermediates,
const gsl::span<const size_t> parameters) override;
bool ActionVt52EscDispatch(const VTID id, const gsl::span<const size_t> parameters) override;
bool ActionCsiDispatch(const wchar_t wch,
const gsl::span<const wchar_t> intermediates,
const gsl::span<const size_t> parameters) override;
bool ActionCsiDispatch(const VTID id, const gsl::span<const size_t> parameters) override;
bool ActionClear() noexcept override;
@ -73,94 +68,89 @@ namespace Microsoft::Console::VirtualTerminal
wchar_t _lastPrintedChar;
std::vector<DispatchTypes::GraphicsOptions> _graphicsOptions;
bool _IntermediateScsDispatch(const wchar_t wch,
const gsl::span<const wchar_t> intermediates);
bool _IntermediateQuestionMarkDispatch(const wchar_t wchAction,
const gsl::span<const size_t> parameters);
bool _IntermediateGreaterThanOrEqualDispatch(const wchar_t wch,
const wchar_t intermediate,
const gsl::span<const size_t> parameters);
bool _IntermediateExclamationDispatch(const wchar_t wch);
bool _IntermediateSpaceDispatch(const wchar_t wchAction,
const gsl::span<const size_t> parameters);
enum VTActionCodes : wchar_t
enum EscActionCodes : uint64_t
{
CUU_CursorUp = L'A',
CUD_CursorDown = L'B',
CUF_CursorForward = L'C',
CUB_CursorBackward = L'D',
CNL_CursorNextLine = L'E',
CPL_CursorPrevLine = L'F',
CHA_CursorHorizontalAbsolute = L'G',
CUP_CursorPosition = L'H',
ED_EraseDisplay = L'J',
EL_EraseLine = L'K',
SU_ScrollUp = L'S',
SD_ScrollDown = L'T',
ICH_InsertCharacter = L'@',
DCH_DeleteCharacter = L'P',
SGR_SetGraphicsRendition = L'm',
DECSC_CursorSave = L'7',
DECRC_CursorRestore = L'8',
DECSET_PrivateModeSet = L'h',
DECRST_PrivateModeReset = L'l',
ANSISYSSC_CursorSave = L's', // NOTE: Overlaps with DECLRMM/DECSLRM. Fix when/if implemented.
ANSISYSRC_CursorRestore = L'u', // NOTE: Overlaps with DECSMBV. Fix when/if implemented.
DECKPAM_KeypadApplicationMode = L'=',
DECKPNM_KeypadNumericMode = L'>',
DSR_DeviceStatusReport = L'n',
DA_DeviceAttributes = L'c',
DECSCPP_SetColumnsPerPage = L'|',
IL_InsertLine = L'L',
DL_DeleteLine = L'M', // Yes, this is the same as RI, however, RI is not preceded by a CSI, and DL is.
HPA_HorizontalPositionAbsolute = L'`',
VPA_VerticalLinePositionAbsolute = L'd',
HPR_HorizontalPositionRelative = L'a',
VPR_VerticalPositionRelative = L'e',
DECSTBM_SetScrollingRegion = L'r',
NEL_NextLine = L'E', // Not a CSI, so doesn't overlap with CNL
IND_Index = L'D', // Not a CSI, so doesn't overlap with CUB
RI_ReverseLineFeed = L'M',
HTS_HorizontalTabSet = L'H', // Not a CSI, so doesn't overlap with CUP
CHT_CursorForwardTab = L'I',
CBT_CursorBackTab = L'Z',
TBC_TabClear = L'g',
ECH_EraseCharacters = L'X',
HVP_HorizontalVerticalPosition = L'f',
DECSTR_SoftReset = L'p',
RIS_ResetToInitialState = L'c', // DA is prefaced by CSI, RIS by ESC
// 'q' is overloaded - no postfix is DECLL, ' ' postfix is DECSCUSR, and '"' is DECSCA
DECSCUSR_SetCursorStyle = L'q', // I believe we'll only ever implement DECSCUSR
DTTERM_WindowManipulation = L't',
REP_RepeatCharacter = L'b',
SS2_SingleShift = L'N',
SS3_SingleShift = L'O',
LS2_LockingShift = L'n',
LS3_LockingShift = L'o',
LS1R_LockingShift = L'~',
LS2R_LockingShift = L'}',
LS3R_LockingShift = L'|',
DECALN_ScreenAlignmentPattern = L'8'
DECSC_CursorSave = VTID("7"),
DECRC_CursorRestore = VTID("8"),
DECKPAM_KeypadApplicationMode = VTID("="),
DECKPNM_KeypadNumericMode = VTID(">"),
IND_Index = VTID("D"),
NEL_NextLine = VTID("E"),
HTS_HorizontalTabSet = VTID("H"),
RI_ReverseLineFeed = VTID("M"),
SS2_SingleShift = VTID("N"),
SS3_SingleShift = VTID("O"),
ST_StringTerminator = VTID("\\"),
RIS_ResetToInitialState = VTID("c"),
LS2_LockingShift = VTID("n"),
LS3_LockingShift = VTID("o"),
LS1R_LockingShift = VTID("~"),
LS2R_LockingShift = VTID("}"),
LS3R_LockingShift = VTID("|"),
DECALN_ScreenAlignmentPattern = VTID("#8")
};
enum Vt52ActionCodes : wchar_t
enum CsiActionCodes : uint64_t
{
CursorUp = L'A',
CursorDown = L'B',
CursorRight = L'C',
CursorLeft = L'D',
EnterGraphicsMode = L'F',
ExitGraphicsMode = L'G',
CursorToHome = L'H',
ReverseLineFeed = L'I',
EraseToEndOfScreen = L'J',
EraseToEndOfLine = L'K',
DirectCursorAddress = L'Y',
Identify = L'Z',
EnterAlternateKeypadMode = L'=',
ExitAlternateKeypadMode = L'>',
ExitVt52Mode = L'<'
ICH_InsertCharacter = VTID("@"),
CUU_CursorUp = VTID("A"),
CUD_CursorDown = VTID("B"),
CUF_CursorForward = VTID("C"),
CUB_CursorBackward = VTID("D"),
CNL_CursorNextLine = VTID("E"),
CPL_CursorPrevLine = VTID("F"),
CHA_CursorHorizontalAbsolute = VTID("G"),
CUP_CursorPosition = VTID("H"),
CHT_CursorForwardTab = VTID("I"),
ED_EraseDisplay = VTID("J"),
EL_EraseLine = VTID("K"),
IL_InsertLine = VTID("L"),
DL_DeleteLine = VTID("M"),
DCH_DeleteCharacter = VTID("P"),
SU_ScrollUp = VTID("S"),
SD_ScrollDown = VTID("T"),
ECH_EraseCharacters = VTID("X"),
CBT_CursorBackTab = VTID("Z"),
HPA_HorizontalPositionAbsolute = VTID("`"),
HPR_HorizontalPositionRelative = VTID("a"),
REP_RepeatCharacter = VTID("b"),
DA_DeviceAttributes = VTID("c"),
DA2_SecondaryDeviceAttributes = VTID(">c"),
DA3_TertiaryDeviceAttributes = VTID("=c"),
VPA_VerticalLinePositionAbsolute = VTID("d"),
VPR_VerticalPositionRelative = VTID("e"),
HVP_HorizontalVerticalPosition = VTID("f"),
TBC_TabClear = VTID("g"),
DECSET_PrivateModeSet = VTID("?h"),
DECRST_PrivateModeReset = VTID("?l"),
SGR_SetGraphicsRendition = VTID("m"),
DSR_DeviceStatusReport = VTID("n"),
DECSTBM_SetScrollingRegion = VTID("r"),
ANSISYSSC_CursorSave = VTID("s"), // NOTE: Overlaps with DECLRMM/DECSLRM. Fix when/if implemented.
DTTERM_WindowManipulation = VTID("t"), // NOTE: Overlaps with DECSLPP. Fix when/if implemented.
ANSISYSRC_CursorRestore = VTID("u"),
DECSCUSR_SetCursorStyle = VTID(" q"),
DECSTR_SoftReset = VTID("!p"),
DECSCPP_SetColumnsPerPage = VTID("$|")
};
enum Vt52ActionCodes : uint64_t
{
CursorUp = VTID("A"),
CursorDown = VTID("B"),
CursorRight = VTID("C"),
CursorLeft = VTID("D"),
EnterGraphicsMode = VTID("F"),
ExitGraphicsMode = VTID("G"),
CursorToHome = VTID("H"),
ReverseLineFeed = VTID("I"),
EraseToEndOfScreen = VTID("J"),
EraseToEndOfLine = VTID("K"),
DirectCursorAddress = VTID("Y"),
Identify = VTID("Z"),
EnterAlternateKeypadMode = VTID("="),
ExitAlternateKeypadMode = VTID(">"),
ExitVt52Mode = VTID("<")
};
enum OscActionCodes : unsigned int

View file

@ -15,7 +15,6 @@ StateMachine::StateMachine(std::unique_ptr<IStateMachineEngine> engine) :
_state(VTStates::Ground),
_trace(Microsoft::Console::VirtualTerminal::ParserTracing()),
_isInAnsiMode(true),
_intermediates{},
_parameters{},
_oscString{},
_cachedSequence{ std::nullopt },
@ -422,7 +421,7 @@ void StateMachine::_ActionEscDispatch(const wchar_t wch)
{
_trace.TraceOnAction(L"EscDispatch");
const bool success = _engine->ActionEscDispatch(wch, { _intermediates.data(), _intermediates.size() });
const bool success = _engine->ActionEscDispatch(_identifier.Finalize(wch));
// Trace the result.
_trace.DispatchSequenceTrace(success);
@ -445,8 +444,7 @@ void StateMachine::_ActionVt52EscDispatch(const wchar_t wch)
{
_trace.TraceOnAction(L"Vt52EscDispatch");
const bool success = _engine->ActionVt52EscDispatch(wch,
{ _intermediates.data(), _intermediates.size() },
const bool success = _engine->ActionVt52EscDispatch(_identifier.Finalize(wch),
{ _parameters.data(), _parameters.size() });
// Trace the result.
@ -470,8 +468,7 @@ void StateMachine::_ActionCsiDispatch(const wchar_t wch)
{
_trace.TraceOnAction(L"CsiDispatch");
const bool success = _engine->ActionCsiDispatch(wch,
{ _intermediates.data(), _intermediates.size() },
const bool success = _engine->ActionCsiDispatch(_identifier.Finalize(wch),
{ _parameters.data(), _parameters.size() });
// Trace the result.
@ -490,12 +487,12 @@ void StateMachine::_ActionCsiDispatch(const wchar_t wch)
// - wch - Character to dispatch.
// Return Value:
// - <none>
void StateMachine::_ActionCollect(const wchar_t wch)
void StateMachine::_ActionCollect(const wchar_t wch) noexcept
{
_trace.TraceOnAction(L"Collect");
// store collect data
_intermediates.push_back(wch);
_identifier.AddIntermediate(wch);
}
// Routine Description:
@ -541,7 +538,7 @@ void StateMachine::_ActionClear()
_trace.TraceOnAction(L"Clear");
// clear all internal stored state.
_intermediates.clear();
_identifier.Clear();
_parameters.clear();

View file

@ -55,7 +55,7 @@ namespace Microsoft::Console::VirtualTerminal
void _ActionPrint(const wchar_t wch);
void _ActionEscDispatch(const wchar_t wch);
void _ActionVt52EscDispatch(const wchar_t wch);
void _ActionCollect(const wchar_t wch);
void _ActionCollect(const wchar_t wch) noexcept;
void _ActionParam(const wchar_t wch);
void _ActionCsiDispatch(const wchar_t wch);
void _ActionOscParam(const wchar_t wch) noexcept;
@ -142,7 +142,7 @@ namespace Microsoft::Console::VirtualTerminal
std::wstring_view _run;
std::vector<wchar_t> _intermediates;
VTIDBuilder _identifier;
std::vector<size_t> _parameters;
std::wstring _oscString;

View file

@ -222,7 +222,7 @@ class Microsoft::Console::VirtualTerminal::InputEngineTest
std::wstring GenerateSgrMouseSequence(const CsiMouseButtonCodes button,
const unsigned short modifiers,
const COORD position,
const CsiActionCodes direction);
const VTID direction);
// SGR_PARAMS serves as test input
// - the state of the buttons (constructed via InputStateMachineEngine::CsiActionMouseCodes)
@ -1089,7 +1089,7 @@ void InputEngineTest::AltBackspaceEnterTest()
std::wstring InputEngineTest::GenerateSgrMouseSequence(const CsiMouseButtonCodes button,
const unsigned short modifiers,
const COORD position,
const CsiActionCodes direction)
const VTID direction)
{
// we first need to convert "button" and "modifiers" into an 8 bit sequence
unsigned int actionCode = 0;
@ -1102,7 +1102,11 @@ std::wstring InputEngineTest::GenerateSgrMouseSequence(const CsiMouseButtonCodes
// modifiers represents the middle 4 bits
actionCode |= modifiers;
return wil::str_printf_failfast<std::wstring>(L"\x1b[<%d;%d;%d%c", static_cast<int>(actionCode), position.X, position.Y, direction);
// mouse sequence identifiers consist of a private parameter prefix and a final character
const wchar_t prefixChar = direction[0];
const wchar_t finalChar = direction[1];
return wil::str_printf_failfast<std::wstring>(L"\x1b[%c%d;%d;%d%c", prefixChar, static_cast<int>(actionCode), position.X, position.Y, finalChar);
}
void InputEngineTest::VerifySGRMouseData(const std::vector<std::tuple<SGR_PARAMS, MOUSE_EVENT_PARAMS>> testData)

View file

@ -50,12 +50,9 @@ public:
return true;
};
bool ActionEscDispatch(const wchar_t /* wch */,
const gsl::span<const wchar_t> /* intermediates */) override { return true; };
bool ActionEscDispatch(const VTID /* id */) override { return true; };
bool ActionVt52EscDispatch(const wchar_t /*wch*/,
const gsl::span<const wchar_t> /*intermediates*/,
const gsl::span<const size_t> /*parameters*/) override { return true; };
bool ActionVt52EscDispatch(const VTID /*id*/, const gsl::span<const size_t> /*parameters*/) override { return true; };
bool ActionClear() override { return true; };
@ -82,9 +79,7 @@ public:
bool DispatchIntermediatesFromEscape() const override { return false; };
// ActionCsiDispatch is the only method that's actually implemented.
bool ActionCsiDispatch(const wchar_t /*wch*/,
const gsl::span<const wchar_t> /*intermediates*/,
const gsl::span<const size_t> parameters) override
bool ActionCsiDispatch(const VTID /*id*/, const gsl::span<const size_t> parameters) override
{
// If flush to terminal is registered for a test, then use it.
if (pfnFlushToTerminal)