Add support for DA2 and DA3 device attributes reports (#6850)
This PR adds support for the `DA2` (Secondary Device Attributes) and `DA3` (Tertiary Device Attributes) escape sequences, which are standard VT queries reporting basic information about the terminal. The _Secondary Device Attributes_ response is made up of a number of parameters: 1. An identification code, for which I've used 0 to indicate that we have the capabilities of a VT100 (using code 0 for this is an XTerm convention, since technically DA2 would not have been supported by a VT100). 2. A firmware revision level, which some terminal emulators use to report their actual version number, but I thought it best we just hardcode a value of 10 (the DEC convention for 1.0). 3. Additional hardware options, which tend to be device specific, but I've followed the convention of the later DEC terminals using 1 to indicate the presence of a PC keyboard. The _Tertiary Device Attributes_ response was originally used to provide a unique terminal identification code, and which some terminal emulators use as a way to identify themselves. However, I think that's information we'd probably prefer not to reveal, so I've followed the more common practice of returning all zeros for the ID. In terms of implementation, the only complication was the need to add an additional code path in the `OutputStateMachine` to handle the `>` and `=` intermediates (technically private parameter prefixes) that these sequences require. I've done this as a single method - rather than one for each prefix - since I think that makes the code easier to follow. VALIDATION ---------- I've added output engine tests to make sure the sequences are dispatched correctly, and adapter tests to confirm that they are returning the responses we expect. I've also manually confirmed that they pass the _Test of terminal reports_ in Vttest. Closes #5836
This commit is contained in:
parent
3388a486dc
commit
53b224b1c6
|
@ -94,6 +94,8 @@ public:
|
|||
|
||||
virtual bool DeviceStatusReport(const DispatchTypes::AnsiStatusType statusType) = 0; // DSR, DSR-OS, DSR-CPR
|
||||
virtual bool DeviceAttributes() = 0; // DA1
|
||||
virtual bool SecondaryDeviceAttributes() = 0; // DA2
|
||||
virtual bool TertiaryDeviceAttributes() = 0; // DA3
|
||||
virtual bool Vt52DeviceAttributes() = 0; // VT52 Identify
|
||||
|
||||
virtual bool DesignateCodingSystem(const wchar_t codingSystem) = 0; // DOCS
|
||||
|
|
|
@ -730,6 +730,32 @@ bool AdaptDispatch::DeviceAttributes()
|
|||
return _WriteResponse(L"\x1b[?1;0c");
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - DA2 - Reports the terminal type, firmware version, and hardware options.
|
||||
// For now we're following the XTerm practice of using 0 to represent a VT100
|
||||
// terminal, the version is hardcoded as 10 (1.0), and the hardware option
|
||||
// is set to 1 (indicating a PC Keyboard).
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - True if handled successfully. False otherwise.
|
||||
bool AdaptDispatch::SecondaryDeviceAttributes()
|
||||
{
|
||||
return _WriteResponse(L"\x1b[>0;10;1c");
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - DA3 - Reports the terminal unit identification code. Terminal emulators
|
||||
// typically return a hardcoded value, the most common being all zeros.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - True if handled successfully. False otherwise.
|
||||
bool AdaptDispatch::TertiaryDeviceAttributes()
|
||||
{
|
||||
return _WriteResponse(L"\x1bP!|00000000\x1b\\");
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - VT52 Identify - Reports the identity of the terminal in VT52 emulation mode.
|
||||
// An actual VT52 terminal would typically identify itself with ESC / K.
|
||||
|
|
|
@ -58,6 +58,8 @@ namespace Microsoft::Console::VirtualTerminal
|
|||
bool SetGraphicsRendition(const std::basic_string_view<DispatchTypes::GraphicsOptions> options) override; // SGR
|
||||
bool DeviceStatusReport(const DispatchTypes::AnsiStatusType statusType) override; // DSR, DSR-OS, DSR-CPR
|
||||
bool DeviceAttributes() override; // DA1
|
||||
bool SecondaryDeviceAttributes() override; // DA2
|
||||
bool TertiaryDeviceAttributes() override; // DA3
|
||||
bool Vt52DeviceAttributes() override; // VT52 Identify
|
||||
bool ScrollUp(const size_t distance) override; // SU
|
||||
bool ScrollDown(const size_t distance) override; // SD
|
||||
|
|
|
@ -88,6 +88,8 @@ public:
|
|||
|
||||
bool DeviceStatusReport(const DispatchTypes::AnsiStatusType /*statusType*/) noexcept override { return false; } // DSR, DSR-OS, DSR-CPR
|
||||
bool DeviceAttributes() noexcept override { return false; } // DA1
|
||||
bool SecondaryDeviceAttributes() noexcept override { return false; } // DA2
|
||||
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
|
||||
|
|
|
@ -1688,6 +1688,42 @@ public:
|
|||
VERIFY_IS_FALSE(_pDispatch.get()->DeviceAttributes());
|
||||
}
|
||||
|
||||
TEST_METHOD(SecondaryDeviceAttributesTests)
|
||||
{
|
||||
Log::Comment(L"Starting test...");
|
||||
|
||||
Log::Comment(L"Test 1: Verify normal response.");
|
||||
_testGetSet->PrepData();
|
||||
VERIFY_IS_TRUE(_pDispatch.get()->SecondaryDeviceAttributes());
|
||||
|
||||
PCWSTR pwszExpectedResponse = L"\x1b[>0;10;1c";
|
||||
_testGetSet->ValidateInputEvent(pwszExpectedResponse);
|
||||
|
||||
Log::Comment(L"Test 2: Verify failure when WriteConsoleInput doesn't work.");
|
||||
_testGetSet->PrepData();
|
||||
_testGetSet->_privatePrependConsoleInputResult = FALSE;
|
||||
|
||||
VERIFY_IS_FALSE(_pDispatch.get()->SecondaryDeviceAttributes());
|
||||
}
|
||||
|
||||
TEST_METHOD(TertiaryDeviceAttributesTests)
|
||||
{
|
||||
Log::Comment(L"Starting test...");
|
||||
|
||||
Log::Comment(L"Test 1: Verify normal response.");
|
||||
_testGetSet->PrepData();
|
||||
VERIFY_IS_TRUE(_pDispatch.get()->TertiaryDeviceAttributes());
|
||||
|
||||
PCWSTR pwszExpectedResponse = L"\x1bP!|00000000\x1b\\";
|
||||
_testGetSet->ValidateInputEvent(pwszExpectedResponse);
|
||||
|
||||
Log::Comment(L"Test 2: Verify failure when WriteConsoleInput doesn't work.");
|
||||
_testGetSet->PrepData();
|
||||
_testGetSet->_privatePrependConsoleInputResult = FALSE;
|
||||
|
||||
VERIFY_IS_FALSE(_pDispatch.get()->TertiaryDeviceAttributes());
|
||||
}
|
||||
|
||||
TEST_METHOD(CursorKeysModeTest)
|
||||
{
|
||||
Log::Comment(L"Starting test...");
|
||||
|
|
|
@ -701,6 +701,10 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const wchar_t wch,
|
|||
case L'?':
|
||||
success = _IntermediateQuestionMarkDispatch(wch, parameters);
|
||||
break;
|
||||
case L'>':
|
||||
case L'=':
|
||||
success = _IntermediateGreaterThanOrEqualDispatch(wch, value, parameters);
|
||||
break;
|
||||
case L'!':
|
||||
success = _IntermediateExclamationDispatch(wch);
|
||||
break;
|
||||
|
@ -773,6 +777,43 @@ bool OutputStateMachineEngine::_IntermediateQuestionMarkDispatch(const wchar_t w
|
|||
return success;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Handles actions that have postfix params on an intermediate '>' or '='.
|
||||
// Arguments:
|
||||
// - wch - Character to dispatch.
|
||||
// - intermediate - The intermediate character.
|
||||
// - parameters - Set of numeric parameters collected while parsing the sequence.
|
||||
// Return Value:
|
||||
// - True if handled successfully. False otherwise.
|
||||
bool OutputStateMachineEngine::_IntermediateGreaterThanOrEqualDispatch(const wchar_t wch,
|
||||
const wchar_t intermediate,
|
||||
const std::basic_string_view<size_t> parameters)
|
||||
{
|
||||
bool success = false;
|
||||
|
||||
switch (wch)
|
||||
{
|
||||
case VTActionCodes::DA_DeviceAttributes:
|
||||
if (_VerifyDeviceAttributesParams(parameters))
|
||||
{
|
||||
switch (intermediate)
|
||||
{
|
||||
case L'>':
|
||||
success = _dispatch->SecondaryDeviceAttributes();
|
||||
TermTelemetry::Instance().Log(TermTelemetry::Codes::DA2);
|
||||
break;
|
||||
case L'=':
|
||||
success = _dispatch->TertiaryDeviceAttributes();
|
||||
TermTelemetry::Instance().Log(TermTelemetry::Codes::DA3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Handles actions that have an intermediate '!', such as DECSTR
|
||||
// Arguments:
|
||||
|
|
|
@ -77,6 +77,9 @@ namespace Microsoft::Console::VirtualTerminal
|
|||
const std::basic_string_view<wchar_t> intermediates);
|
||||
bool _IntermediateQuestionMarkDispatch(const wchar_t wchAction,
|
||||
const std::basic_string_view<size_t> parameters);
|
||||
bool _IntermediateGreaterThanOrEqualDispatch(const wchar_t wch,
|
||||
const wchar_t intermediate,
|
||||
const std::basic_string_view<size_t> parameters);
|
||||
bool _IntermediateExclamationDispatch(const wchar_t wch);
|
||||
bool _IntermediateSpaceDispatch(const wchar_t wchAction,
|
||||
const std::basic_string_view<size_t> parameters);
|
||||
|
|
|
@ -225,6 +225,8 @@ void TermTelemetry::WriteFinalTraceLog() const
|
|||
TraceLoggingUInt32(_uiTimesUsed[DECKPNM], "DECKPNM"),
|
||||
TraceLoggingUInt32(_uiTimesUsed[DSR], "DSR"),
|
||||
TraceLoggingUInt32(_uiTimesUsed[DA], "DA"),
|
||||
TraceLoggingUInt32(_uiTimesUsed[DA2], "DA2"),
|
||||
TraceLoggingUInt32(_uiTimesUsed[DA3], "DA3"),
|
||||
TraceLoggingUInt32(_uiTimesUsed[VPA], "VPA"),
|
||||
TraceLoggingUInt32(_uiTimesUsed[HPR], "HPR"),
|
||||
TraceLoggingUInt32(_uiTimesUsed[VPR], "VPR"),
|
||||
|
|
|
@ -52,6 +52,8 @@ namespace Microsoft::Console::VirtualTerminal
|
|||
DECKPNM,
|
||||
DSR,
|
||||
DA,
|
||||
DA2,
|
||||
DA3,
|
||||
VPA,
|
||||
HPR,
|
||||
VPR,
|
||||
|
|
|
@ -594,6 +594,8 @@ public:
|
|||
_statusReportType{ (DispatchTypes::AnsiStatusType)-1 },
|
||||
_deviceStatusReport{ false },
|
||||
_deviceAttributes{ false },
|
||||
_secondaryDeviceAttributes{ false },
|
||||
_tertiaryDeviceAttributes{ false },
|
||||
_vt52DeviceAttributes{ false },
|
||||
_isAltBuffer{ false },
|
||||
_cursorKeysMode{ false },
|
||||
|
@ -770,6 +772,20 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
bool SecondaryDeviceAttributes() noexcept override
|
||||
{
|
||||
_secondaryDeviceAttributes = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TertiaryDeviceAttributes() noexcept override
|
||||
{
|
||||
_tertiaryDeviceAttributes = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Vt52DeviceAttributes() noexcept override
|
||||
{
|
||||
_vt52DeviceAttributes = true;
|
||||
|
@ -976,6 +992,8 @@ public:
|
|||
DispatchTypes::AnsiStatusType _statusReportType;
|
||||
bool _deviceStatusReport;
|
||||
bool _deviceAttributes;
|
||||
bool _secondaryDeviceAttributes;
|
||||
bool _tertiaryDeviceAttributes;
|
||||
bool _vt52DeviceAttributes;
|
||||
bool _isAltBuffer;
|
||||
bool _cursorKeysMode;
|
||||
|
@ -1804,6 +1822,86 @@ class StateMachineExternalTest final
|
|||
pDispatch->ClearState();
|
||||
}
|
||||
|
||||
TEST_METHOD(TestSecondaryDeviceAttributes)
|
||||
{
|
||||
auto dispatch = std::make_unique<StatefulDispatch>();
|
||||
auto pDispatch = dispatch.get();
|
||||
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
||||
StateMachine mach(std::move(engine));
|
||||
|
||||
Log::Comment(L"Test 1: Check default case, no params.");
|
||||
mach.ProcessCharacter(AsciiChars::ESC);
|
||||
mach.ProcessCharacter(L'[');
|
||||
mach.ProcessCharacter(L'>');
|
||||
mach.ProcessCharacter(L'c');
|
||||
|
||||
VERIFY_IS_TRUE(pDispatch->_secondaryDeviceAttributes);
|
||||
|
||||
pDispatch->ClearState();
|
||||
|
||||
Log::Comment(L"Test 2: Check default case, 0 param.");
|
||||
mach.ProcessCharacter(AsciiChars::ESC);
|
||||
mach.ProcessCharacter(L'[');
|
||||
mach.ProcessCharacter(L'>');
|
||||
mach.ProcessCharacter(L'0');
|
||||
mach.ProcessCharacter(L'c');
|
||||
|
||||
VERIFY_IS_TRUE(pDispatch->_secondaryDeviceAttributes);
|
||||
|
||||
pDispatch->ClearState();
|
||||
|
||||
Log::Comment(L"Test 3: Check fail case, 1 (or any other) param.");
|
||||
mach.ProcessCharacter(AsciiChars::ESC);
|
||||
mach.ProcessCharacter(L'[');
|
||||
mach.ProcessCharacter(L'>');
|
||||
mach.ProcessCharacter(L'1');
|
||||
mach.ProcessCharacter(L'c');
|
||||
|
||||
VERIFY_IS_FALSE(pDispatch->_secondaryDeviceAttributes);
|
||||
|
||||
pDispatch->ClearState();
|
||||
}
|
||||
|
||||
TEST_METHOD(TestTertiaryDeviceAttributes)
|
||||
{
|
||||
auto dispatch = std::make_unique<StatefulDispatch>();
|
||||
auto pDispatch = dispatch.get();
|
||||
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
||||
StateMachine mach(std::move(engine));
|
||||
|
||||
Log::Comment(L"Test 1: Check default case, no params.");
|
||||
mach.ProcessCharacter(AsciiChars::ESC);
|
||||
mach.ProcessCharacter(L'[');
|
||||
mach.ProcessCharacter(L'=');
|
||||
mach.ProcessCharacter(L'c');
|
||||
|
||||
VERIFY_IS_TRUE(pDispatch->_tertiaryDeviceAttributes);
|
||||
|
||||
pDispatch->ClearState();
|
||||
|
||||
Log::Comment(L"Test 2: Check default case, 0 param.");
|
||||
mach.ProcessCharacter(AsciiChars::ESC);
|
||||
mach.ProcessCharacter(L'[');
|
||||
mach.ProcessCharacter(L'=');
|
||||
mach.ProcessCharacter(L'0');
|
||||
mach.ProcessCharacter(L'c');
|
||||
|
||||
VERIFY_IS_TRUE(pDispatch->_tertiaryDeviceAttributes);
|
||||
|
||||
pDispatch->ClearState();
|
||||
|
||||
Log::Comment(L"Test 3: Check fail case, 1 (or any other) param.");
|
||||
mach.ProcessCharacter(AsciiChars::ESC);
|
||||
mach.ProcessCharacter(L'[');
|
||||
mach.ProcessCharacter(L'=');
|
||||
mach.ProcessCharacter(L'1');
|
||||
mach.ProcessCharacter(L'c');
|
||||
|
||||
VERIFY_IS_FALSE(pDispatch->_tertiaryDeviceAttributes);
|
||||
|
||||
pDispatch->ClearState();
|
||||
}
|
||||
|
||||
TEST_METHOD(TestStrings)
|
||||
{
|
||||
auto dispatch = std::make_unique<StatefulDispatch>();
|
||||
|
|
Loading…
Reference in a new issue