terminal/src/terminal/parser/InputStateMachineEngine.hpp
James Holderness 7fcff4d33a
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
2020-08-18 18:57:52 +00:00

226 lines
7.9 KiB
C++

/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- InputStateMachineEngine.hpp
Abstract:
- This is the implementation of the client VT input state machine engine.
This generates InputEvents from a stream of VT sequences emitted by a
client "terminal" application.
Author(s):
- Mike Griese (migrie) 18 Aug 2017
--*/
#pragma once
#include "telemetry.hpp"
#include "IStateMachineEngine.hpp"
#include <functional>
#include "../../types/inc/IInputEvent.hpp"
#include "../adapter/IInteractDispatch.hpp"
namespace Microsoft::Console::VirtualTerminal
{
// The values used by VkKeyScan to encode modifiers in the high order byte
const short KEYSCAN_SHIFT = 1;
const short KEYSCAN_CTRL = 2;
const short KEYSCAN_ALT = 4;
// The values with which VT encodes modifier values.
const short VT_SHIFT = 1;
const short VT_ALT = 2;
const short VT_CTRL = 4;
// The assumed values for SGR Mouse Scroll Wheel deltas
constexpr DWORD SCROLL_DELTA_BACKWARD = 0xFF800000;
constexpr DWORD SCROLL_DELTA_FORWARD = 0x00800000;
const size_t WRAPPED_SEQUENCE_MAX_LENGTH = 8;
// For reference, the equivalent INPUT_RECORD values are:
// RIGHT_ALT_PRESSED 0x0001
// LEFT_ALT_PRESSED 0x0002
// RIGHT_CTRL_PRESSED 0x0004
// LEFT_CTRL_PRESSED 0x0008
// SHIFT_PRESSED 0x0010
// NUMLOCK_ON 0x0020
// SCROLLLOCK_ON 0x0040
// CAPSLOCK_ON 0x0080
// ENHANCED_KEY 0x0100
enum CsiActionCodes : uint64_t
{
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
{
Left = 0,
Middle = 1,
Right = 2,
Released = 3,
ScrollForward = 4,
ScrollBack = 5,
};
constexpr unsigned short CsiMouseModifierCode_Drag = 32;
enum CsiMouseModifierCodes : unsigned short
{
Shift = 4,
Meta = 8,
Ctrl = 16,
Drag = 32,
};
// Sequences ending in '~' use these numbers as identifiers.
enum class GenericKeyIdentifiers : unsigned short
{
GenericHome = 1,
Insert = 2,
Delete = 3,
GenericEnd = 4,
Prior = 5, //PgUp
Next = 6, //PgDn
F5 = 15,
F6 = 17,
F7 = 18,
F8 = 19,
F9 = 20,
F10 = 21,
F11 = 23,
F12 = 24,
};
enum class Ss3ActionCodes : wchar_t
{
ArrowUp = L'A',
ArrowDown = L'B',
ArrowRight = L'C',
ArrowLeft = L'D',
Home = L'H',
End = L'F',
SS3_F1 = L'P',
SS3_F2 = L'Q',
SS3_F3 = L'R',
SS3_F4 = L'S',
};
class InputStateMachineEngine : public IStateMachineEngine
{
public:
InputStateMachineEngine(std::unique_ptr<IInteractDispatch> pDispatch);
InputStateMachineEngine(std::unique_ptr<IInteractDispatch> pDispatch,
const bool lookingForDSR);
bool ActionExecute(const wchar_t wch) override;
bool ActionExecuteFromEscape(const wchar_t wch) override;
bool ActionPrint(const wchar_t wch) override;
bool ActionPrintString(const std::wstring_view string) override;
bool ActionPassThroughString(const std::wstring_view string) override;
bool ActionEscDispatch(const VTID id) override;
bool ActionVt52EscDispatch(const VTID id, const gsl::span<const size_t> parameters) noexcept override;
bool ActionCsiDispatch(const VTID id, const gsl::span<const size_t> parameters) override;
bool ActionClear() noexcept override;
bool ActionIgnore() noexcept override;
bool ActionOscDispatch(const wchar_t wch,
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 ParseControlSequenceAfterSs3() const noexcept override;
bool FlushAtEndOfString() const noexcept override;
bool DispatchControlCharsFromEscape() const noexcept override;
bool DispatchIntermediatesFromEscape() const noexcept override;
void SetFlushToInputQueueCallback(std::function<bool()> pfnFlushToInputQueue);
private:
const std::unique_ptr<IInteractDispatch> _pDispatch;
std::function<bool()> _pfnFlushToInputQueue;
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;
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,
DWORD& buttonState,
DWORD& eventFlags) noexcept;
bool _GetGenericVkey(const gsl::span<const size_t> parameters,
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);
bool _WriteSingleKey(const wchar_t wch, const short vkey, const DWORD modifierState);
bool _WriteMouseEvent(const size_t column, const size_t line, const DWORD buttonState, const DWORD controlKeyState, const DWORD eventFlags);
void _GenerateWrappedSequence(const wchar_t wch,
const short vkey,
const DWORD modifierState,
std::vector<INPUT_RECORD>& input);
void _GetSingleKeypress(const wchar_t wch,
const short vkey,
const DWORD modifierState,
std::vector<INPUT_RECORD>& input);
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;
bool _DoControlCharacter(const wchar_t wch, const bool writeAlt);
#ifdef UNIT_TESTING
friend class InputEngineTest;
#endif
};
}