Add support for more DSR queries. (#16525)

## Summary of the Pull Request

This PR adds support for more Device Status Report (`DSR`) queries,
specifically:

* Printer Status (`DSR ?15`)
* User Defined Keys (`DSR ?25`)
* Keyboard Status (`DSR ?26`)
* Locator Status (`DSR ?55`)
* Locator Identity (`DSR ?56`)
* Data Integrity (`DSR ?75`)
* Multiple Session Status (`DSR ?85`)

## Detailed Description of the Pull Request / Additional comments

For most of these, we just need to return a `DSR` sequence indicating
that the functionality isn't supported.

* `DSR ?13` indicates that a printer isn't connected.
* `DSR ?23` indicates the UDK extension isn't supported.
* `DSR ?53` indicates that a locator device isn't connected
* `DSR ?57;0` indicates the locator type is unknown or not connected.
* `DSR ?83` indicates that multiple sessions aren't supported.

For the keyboard, we report `DSR ?27;0;0;5`, indicating a PC keyboard
(the `5` parameter), a "ready" status (the second `0` parameter), and an
unknown language (the first `0` parameter). In the long term, there may
be some value in identifying the actual keyboard language, but for now
this should be good enough.

The data integrity report was originally used to detect communication
errors between the terminal and host, but that's not really applicable
for modern terminals, so we always just report `DSR ?70`, indicating
that there are no errors.

## Validation Steps Performed

I've added some more adapter tests and output engine tests covering the
new reports.

## PR Checklist
- [x] Closes #16518
- [x] Tests added/passed
This commit is contained in:
James Holderness 2024-01-24 12:01:46 +00:00 committed by GitHub
parent da182e6c59
commit 6c192d15be
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 227 additions and 34 deletions

View File

@ -500,11 +500,18 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes
enum class StatusType : VTInt
{
OS_OperatingStatus = ANSIStandardStatus(5),
CPR_CursorPositionReport = ANSIStandardStatus(6),
ExCPR_ExtendedCursorPositionReport = DECPrivateStatus(6),
MSR_MacroSpaceReport = DECPrivateStatus(62),
MEM_MemoryChecksum = DECPrivateStatus(63),
OperatingStatus = ANSIStandardStatus(5),
CursorPositionReport = ANSIStandardStatus(6),
ExtendedCursorPositionReport = DECPrivateStatus(6),
PrinterStatus = DECPrivateStatus(15),
UserDefinedKeys = DECPrivateStatus(25),
KeyboardStatus = DECPrivateStatus(26),
LocatorStatus = DECPrivateStatus(55),
LocatorIdentity = DECPrivateStatus(56),
MacroSpaceReport = DECPrivateStatus(62),
MemoryChecksum = DECPrivateStatus(63),
DataIntegrity = DECPrivateStatus(75),
MultipleSessionStatus = DECPrivateStatus(85),
};
using ANSIStandardMode = FlaggedEnumValue<0x00000000>;

View File

@ -1473,23 +1473,53 @@ bool AdaptDispatch::SetLineRendition(const LineRendition rendition)
// - True if handled successfully. False otherwise.
bool AdaptDispatch::DeviceStatusReport(const DispatchTypes::StatusType statusType, const VTParameter id)
{
constexpr auto GoodCondition = L"0";
constexpr auto PrinterNotConnected = L"?13";
constexpr auto UserDefinedKeysNotSupported = L"?23";
constexpr auto UnknownPcKeyboard = L"?27;0;0;5";
constexpr auto LocatorNotConnected = L"?53";
constexpr auto UnknownLocatorDevice = L"?57;0";
constexpr auto TerminalReady = L"?70";
constexpr auto MultipleSessionsNotSupported = L"?83";
switch (statusType)
{
case DispatchTypes::StatusType::OS_OperatingStatus:
_OperatingStatus();
case DispatchTypes::StatusType::OperatingStatus:
_DeviceStatusReport(GoodCondition);
return true;
case DispatchTypes::StatusType::CPR_CursorPositionReport:
case DispatchTypes::StatusType::CursorPositionReport:
_CursorPositionReport(false);
return true;
case DispatchTypes::StatusType::ExCPR_ExtendedCursorPositionReport:
case DispatchTypes::StatusType::ExtendedCursorPositionReport:
_CursorPositionReport(true);
return true;
case DispatchTypes::StatusType::MSR_MacroSpaceReport:
case DispatchTypes::StatusType::PrinterStatus:
_DeviceStatusReport(PrinterNotConnected);
return true;
case DispatchTypes::StatusType::UserDefinedKeys:
_DeviceStatusReport(UserDefinedKeysNotSupported);
return true;
case DispatchTypes::StatusType::KeyboardStatus:
_DeviceStatusReport(UnknownPcKeyboard);
return true;
case DispatchTypes::StatusType::LocatorStatus:
_DeviceStatusReport(LocatorNotConnected);
return true;
case DispatchTypes::StatusType::LocatorIdentity:
_DeviceStatusReport(UnknownLocatorDevice);
return true;
case DispatchTypes::StatusType::MacroSpaceReport:
_MacroSpaceReport();
return true;
case DispatchTypes::StatusType::MEM_MemoryChecksum:
case DispatchTypes::StatusType::MemoryChecksum:
_MacroChecksumReport(id);
return true;
case DispatchTypes::StatusType::DataIntegrity:
_DeviceStatusReport(TerminalReady);
return true;
case DispatchTypes::StatusType::MultipleSessionStatus:
_DeviceStatusReport(MultipleSessionsNotSupported);
return true;
default:
return false;
}
@ -1609,15 +1639,14 @@ bool AdaptDispatch::RequestTerminalParameters(const DispatchTypes::ReportingPerm
}
// Routine Description:
// - DSR-OS - Reports the operating status back to the input channel
// - DSR - Transmits a device status report with a given parameter string.
// Arguments:
// - <none>
// - parameters - One or more parameter values representing the status
// Return Value:
// - <none>
void AdaptDispatch::_OperatingStatus() const
void AdaptDispatch::_DeviceStatusReport(const wchar_t* parameters) const
{
// We always report a good operating condition.
_api.ReturnResponse(L"\x1b[0n");
_api.ReturnResponse(fmt::format(FMT_COMPILE(L"\033[{}n"), parameters));
}
// Routine Description:

View File

@ -239,7 +239,7 @@ namespace Microsoft::Console::VirtualTerminal
void _DoLineFeed(TextBuffer& textBuffer, const bool withReturn, const bool wrapForced);
void _OperatingStatus() const;
void _DeviceStatusReport(const wchar_t* parameters) const;
void _CursorPositionReport(const bool extendedReport);
void _MacroSpaceReport() const;
void _MacroChecksumReport(const VTParameter id) const;

View File

@ -1508,7 +1508,7 @@ public:
Log::Comment(L"Test 1: Verify good operating condition.");
_testGetSet->PrepData();
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::OS_OperatingStatus, {}));
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::OperatingStatus, {}));
_testGetSet->ValidateInputEvent(L"\x1b[0n");
}
@ -1531,7 +1531,7 @@ public:
coordCursorExpected.x++;
coordCursorExpected.y++;
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::CPR_CursorPositionReport, {}));
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::CursorPositionReport, {}));
wchar_t pwszBuffer[50];
@ -1555,7 +1555,7 @@ public:
// Then note that VT is 1,1 based for the top left, so add 1. (The rest of the console uses 0,0 for array index bases.)
coordCursorExpectedFirst += til::point{ 1, 1 };
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::CPR_CursorPositionReport, {}));
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::CursorPositionReport, {}));
auto cursorPos = _testGetSet->_textBuffer->GetCursor().GetPosition();
cursorPos.x++;
@ -1565,7 +1565,7 @@ public:
auto coordCursorExpectedSecond{ coordCursorExpectedFirst };
coordCursorExpectedSecond += til::point{ 1, 1 };
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::CPR_CursorPositionReport, {}));
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::CursorPositionReport, {}));
wchar_t pwszBuffer[50];
@ -1594,7 +1594,7 @@ public:
// Until we support paging (GH#13892) the reported page number should always be 1.
const auto pageExpected = 1;
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::ExCPR_ExtendedCursorPositionReport, {}));
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::ExtendedCursorPositionReport, {}));
wchar_t pwszBuffer[50];
swprintf_s(pwszBuffer, ARRAYSIZE(pwszBuffer), L"\x1b[?%d;%d;%dR", coordCursorExpected.y, coordCursorExpected.x, pageExpected);
@ -1610,7 +1610,7 @@ public:
Log::Comment(L"Test 1: Verify maximum space available");
_testGetSet->PrepData();
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::MSR_MacroSpaceReport, {}));
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::MacroSpaceReport, {}));
wchar_t pwszBuffer[50];
swprintf_s(pwszBuffer, ARRAYSIZE(pwszBuffer), L"\x1b[%zu*{", availableSpace);
@ -1623,7 +1623,7 @@ public:
_stateMachine->ProcessString(L"\033P2;0;0!z12345678\033\\");
_stateMachine->ProcessString(L"\033P3;0;0!z12345678\033\\");
_stateMachine->ProcessString(L"\033P4;0;0!z12345678\033\\");
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::MSR_MacroSpaceReport, {}));
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::MacroSpaceReport, {}));
swprintf_s(pwszBuffer, ARRAYSIZE(pwszBuffer), L"\x1b[%zu*{", availableSpace - 2);
_testGetSet->ValidateInputEvent(pwszBuffer);
@ -1631,7 +1631,7 @@ public:
Log::Comment(L"Test 3: Verify space reset");
_testGetSet->PrepData();
VERIFY_IS_TRUE(_pDispatch->HardReset());
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::MSR_MacroSpaceReport, {}));
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::MacroSpaceReport, {}));
swprintf_s(pwszBuffer, ARRAYSIZE(pwszBuffer), L"\x1b[%zu*{", availableSpace);
_testGetSet->ValidateInputEvent(pwszBuffer);
@ -1643,7 +1643,7 @@ public:
Log::Comment(L"Test 1: Verify initial checksum is 0");
_testGetSet->PrepData();
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::MEM_MemoryChecksum, 12));
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::MemoryChecksum, 12));
_testGetSet->ValidateInputEvent(L"\033P12!~0000\033\\");
@ -1652,7 +1652,7 @@ public:
// Define a couple of text macros
_stateMachine->ProcessString(L"\033P1;0;0!zABCD\033\\");
_stateMachine->ProcessString(L"\033P2;0;0!zabcd\033\\");
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::MEM_MemoryChecksum, 34));
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::MemoryChecksum, 34));
// Checksum is a 16-bit negated sum of the macro buffer characters.
const auto checksum = gsl::narrow_cast<uint16_t>(-('A' + 'B' + 'C' + 'D' + 'a' + 'b' + 'c' + 'd'));
@ -1663,11 +1663,51 @@ public:
Log::Comment(L"Test 3: Verify checksum resets to 0");
_testGetSet->PrepData();
VERIFY_IS_TRUE(_pDispatch->HardReset());
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::MEM_MemoryChecksum, 56));
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::MemoryChecksum, 56));
_testGetSet->ValidateInputEvent(L"\033P56!~0000\033\\");
}
TEST_METHOD(DeviceStatus_PrivateStatusTests)
{
Log::Comment(L"Starting test...");
Log::Comment(L"Test 1: Verify printer is not connected.");
_testGetSet->PrepData();
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::PrinterStatus, {}));
_testGetSet->ValidateInputEvent(L"\x1b[?13n");
Log::Comment(L"Test 2: Verify UDKs are not supported.");
_testGetSet->PrepData();
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::UserDefinedKeys, {}));
_testGetSet->ValidateInputEvent(L"\x1b[?23n");
Log::Comment(L"Test 3: Verify PC keyboard with unknown dialect.");
_testGetSet->PrepData();
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::KeyboardStatus, {}));
_testGetSet->ValidateInputEvent(L"\x1b[?27;0;0;5n");
Log::Comment(L"Test 4: Verify locator is not connected.");
_testGetSet->PrepData();
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::LocatorStatus, {}));
_testGetSet->ValidateInputEvent(L"\x1b[?53n");
Log::Comment(L"Test 5: Verify locator type is unknown.");
_testGetSet->PrepData();
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::LocatorIdentity, {}));
_testGetSet->ValidateInputEvent(L"\x1b[?57;0n");
Log::Comment(L"Test 6: Verify terminal is ready.");
_testGetSet->PrepData();
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::DataIntegrity, {}));
_testGetSet->ValidateInputEvent(L"\x1b[?70n");
Log::Comment(L"Test 7: Verify multiple sessions are not supported.");
_testGetSet->PrepData();
VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::MultipleSessionStatus, {}));
_testGetSet->ValidateInputEvent(L"\x1b[?83n");
}
TEST_METHOD(DeviceAttributesTests)
{
Log::Comment(L"Starting test...");

View File

@ -2159,29 +2159,29 @@ class StateMachineExternalTest final
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
StateMachine mach(std::move(engine));
Log::Comment(L"Test 1: Check OS (operating status) case 5. Should succeed.");
Log::Comment(L"Test 1: Check operating status (case 5). Should succeed.");
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L'5');
mach.ProcessCharacter(L'n');
VERIFY_IS_TRUE(pDispatch->_deviceStatusReport);
VERIFY_ARE_EQUAL(DispatchTypes::StatusType::OS_OperatingStatus, pDispatch->_statusReportType);
VERIFY_ARE_EQUAL(DispatchTypes::StatusType::OperatingStatus, pDispatch->_statusReportType);
pDispatch->ClearState();
Log::Comment(L"Test 2: Check CPR (cursor position report) case 6. Should succeed.");
Log::Comment(L"Test 2: Check cursor position report (case 6). Should succeed.");
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L'6');
mach.ProcessCharacter(L'n');
VERIFY_IS_TRUE(pDispatch->_deviceStatusReport);
VERIFY_ARE_EQUAL(DispatchTypes::StatusType::CPR_CursorPositionReport, pDispatch->_statusReportType);
VERIFY_ARE_EQUAL(DispatchTypes::StatusType::CursorPositionReport, pDispatch->_statusReportType);
pDispatch->ClearState();
Log::Comment(L"Test 3: Check DECXCPR (extended cursor position report) case ?6. Should succeed.");
Log::Comment(L"Test 3: Check extended cursor position report (case ?6). Should succeed.");
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L'?');
@ -2189,7 +2189,124 @@ class StateMachineExternalTest final
mach.ProcessCharacter(L'n');
VERIFY_IS_TRUE(pDispatch->_deviceStatusReport);
VERIFY_ARE_EQUAL(DispatchTypes::StatusType::ExCPR_ExtendedCursorPositionReport, pDispatch->_statusReportType);
VERIFY_ARE_EQUAL(DispatchTypes::StatusType::ExtendedCursorPositionReport, pDispatch->_statusReportType);
pDispatch->ClearState();
Log::Comment(L"Test 4: Check printer status (case ?15). Should succeed.");
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L'?');
mach.ProcessCharacter(L'1');
mach.ProcessCharacter(L'5');
mach.ProcessCharacter(L'n');
VERIFY_IS_TRUE(pDispatch->_deviceStatusReport);
VERIFY_ARE_EQUAL(DispatchTypes::StatusType::PrinterStatus, pDispatch->_statusReportType);
pDispatch->ClearState();
Log::Comment(L"Test 5: Check user-defined keys (case ?25). Should succeed.");
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L'?');
mach.ProcessCharacter(L'2');
mach.ProcessCharacter(L'5');
mach.ProcessCharacter(L'n');
VERIFY_IS_TRUE(pDispatch->_deviceStatusReport);
VERIFY_ARE_EQUAL(DispatchTypes::StatusType::UserDefinedKeys, pDispatch->_statusReportType);
pDispatch->ClearState();
Log::Comment(L"Test 6: Check keyboard status / dialect (case ?26). Should succeed.");
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L'?');
mach.ProcessCharacter(L'2');
mach.ProcessCharacter(L'6');
mach.ProcessCharacter(L'n');
VERIFY_IS_TRUE(pDispatch->_deviceStatusReport);
VERIFY_ARE_EQUAL(DispatchTypes::StatusType::KeyboardStatus, pDispatch->_statusReportType);
pDispatch->ClearState();
Log::Comment(L"Test 7: Check locator status (case ?55). Should succeed.");
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L'?');
mach.ProcessCharacter(L'5');
mach.ProcessCharacter(L'5');
mach.ProcessCharacter(L'n');
VERIFY_IS_TRUE(pDispatch->_deviceStatusReport);
VERIFY_ARE_EQUAL(DispatchTypes::StatusType::LocatorStatus, pDispatch->_statusReportType);
pDispatch->ClearState();
Log::Comment(L"Test 8: Check locator identity (case ?56). Should succeed.");
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L'?');
mach.ProcessCharacter(L'5');
mach.ProcessCharacter(L'6');
mach.ProcessCharacter(L'n');
VERIFY_IS_TRUE(pDispatch->_deviceStatusReport);
VERIFY_ARE_EQUAL(DispatchTypes::StatusType::LocatorIdentity, pDispatch->_statusReportType);
pDispatch->ClearState();
Log::Comment(L"Test 9: Check macro space report (case ?62). Should succeed.");
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L'?');
mach.ProcessCharacter(L'6');
mach.ProcessCharacter(L'2');
mach.ProcessCharacter(L'n');
VERIFY_IS_TRUE(pDispatch->_deviceStatusReport);
VERIFY_ARE_EQUAL(DispatchTypes::StatusType::MacroSpaceReport, pDispatch->_statusReportType);
pDispatch->ClearState();
Log::Comment(L"Test 10: Check memory checksum (case ?63). Should succeed.");
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L'?');
mach.ProcessCharacter(L'6');
mach.ProcessCharacter(L'3');
mach.ProcessCharacter(L'n');
VERIFY_IS_TRUE(pDispatch->_deviceStatusReport);
VERIFY_ARE_EQUAL(DispatchTypes::StatusType::MemoryChecksum, pDispatch->_statusReportType);
pDispatch->ClearState();
Log::Comment(L"Test 11: Check data integrity report (case ?75). Should succeed.");
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L'?');
mach.ProcessCharacter(L'7');
mach.ProcessCharacter(L'5');
mach.ProcessCharacter(L'n');
VERIFY_IS_TRUE(pDispatch->_deviceStatusReport);
VERIFY_ARE_EQUAL(DispatchTypes::StatusType::DataIntegrity, pDispatch->_statusReportType);
pDispatch->ClearState();
Log::Comment(L"Test 12: Check multiple session status (case ?85). Should succeed.");
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L'?');
mach.ProcessCharacter(L'8');
mach.ProcessCharacter(L'5');
mach.ProcessCharacter(L'n');
VERIFY_IS_TRUE(pDispatch->_deviceStatusReport);
VERIFY_ARE_EQUAL(DispatchTypes::StatusType::MultipleSessionStatus, pDispatch->_statusReportType);
pDispatch->ClearState();
}