Skip to content

Commit

Permalink
Add support for the DECREQTPARM report (#7939)
Browse files Browse the repository at this point in the history
This PR adds support for the `DECREQTPARM` (Request Terminal Parameters)
escape sequence, which was originally used on the VT100 terminal to
report the serial communication parameters. Modern terminal emulators
simply hardcode the reported values for backward compatibility.

The `DECREQTPARM` sequence has one parameter, which was originally used
to tell the terminal whether it was permitted to send unsolicited
reports or not. However, since we have no reason to send an unsolicited
report, we don't need to keep track of that state, but the permission
parameter does still determine the value of the first parameter in the
response.

The response parameters are as follows:

| Parameter        | Value  | Meaning                  |
| ---------------- | ------ | ------------------------ |
| response type    | 2 or 3 | unsolicited or solicited |
| parity           | 1      | no parity                |
| data bits        | 1      | 8 bits per character     |
| transmit speed   | 128    | 38400 baud               |
| receive speed    | 128    | 38400 baud               |
| clock multiplier | 1      |                          |
| flags            | 0      |                          |

There is some variation in the baud rate reported by modern terminal
emulators, and 9600 baud seems to be a little more common than 38400
baud, but I thought the higher speed was probably more appropriate,
especially since that's also the value reported by XTerm.

## Validation Steps Performed

I've added a couple of adapter and output engine tests to verify that
the sequence is dispatched correctly, and the expected responses are
generated. I've also manually tested in Vttest and confirmed that we now
pass the `DECREQTPARM` test in the _Test of terminal reports_.

Closes #7852
  • Loading branch information
j4james authored Oct 15, 2020
1 parent 9d911c0 commit 30e363e
Show file tree
Hide file tree
Showing 12 changed files with 125 additions and 0 deletions.
1 change: 1 addition & 0 deletions .github/actions/spell-check/expect/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,7 @@ DECOM
deconstructed
DECPCTERM
DECRC
DECREQTPARM
DECRLM
DECRQM
DECRST
Expand Down
6 changes: 6 additions & 0 deletions src/terminal/adapter/DispatchTypes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,12 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes
SteadyBar = 6
};

enum class ReportingPermission : size_t
{
Unsolicited = 0,
Solicited = 1
};

enum class LineFeedType : unsigned int
{
WithReturn,
Expand Down
1 change: 1 addition & 0 deletions src/terminal/adapter/ITermDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ class Microsoft::Console::VirtualTerminal::ITermDispatch
virtual bool SecondaryDeviceAttributes() = 0; // DA2
virtual bool TertiaryDeviceAttributes() = 0; // DA3
virtual bool Vt52DeviceAttributes() = 0; // VT52 Identify
virtual bool RequestTerminalParameters(const DispatchTypes::ReportingPermission permission) = 0; // DECREQTPARM

virtual bool DesignateCodingSystem(const VTID codingSystem) = 0; // DOCS
virtual bool Designate94Charset(const size_t gsetNumber, const VTID charset) = 0; // SCS
Expand Down
34 changes: 34 additions & 0 deletions src/terminal/adapter/adaptDispatch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,40 @@ bool AdaptDispatch::Vt52DeviceAttributes()
return _WriteResponse(L"\x1b/Z");
}

// Routine Description:
// - DECREQTPARM - This sequence was originally used on the VT100 terminal to
// report the serial communication parameters (baud rate, data bits, parity,
// etc.). On modern terminal emulators, the response is simply hardcoded.
// Arguments:
// - permission - This would originally have determined whether the terminal
// was allowed to send unsolicited reports or not.
// Return Value:
// - True if handled successfully. False otherwise.
bool AdaptDispatch::RequestTerminalParameters(const DispatchTypes::ReportingPermission permission)
{
// We don't care whether unsolicited reports are allowed or not, but the
// requested permission does determine the value of the first response
// parameter. The remaining parameters are just hardcoded to indicate a
// 38400 baud connection, which matches the XTerm response. The full
// parameter sequence is as follows:
// - response type: 2 or 3 (unsolicited or solicited)
// - parity: 1 (no parity)
// - data bits: 1 (8 bits per character)
// - transmit speed: 128 (38400 baud)
// - receive speed: 128 (38400 baud)
// - clock multiplier: 1
// - flags: 0
switch (permission)
{
case DispatchTypes::ReportingPermission::Unsolicited:
return _WriteResponse(L"\x1b[2;1;1;128;128;1;0x");
case DispatchTypes::ReportingPermission::Solicited:
return _WriteResponse(L"\x1b[3;1;1;128;128;1;0x");
default:
return false;
}
}

// Routine Description:
// - DSR-OS - Reports the operating status back to the input channel
// Arguments:
Expand Down
1 change: 1 addition & 0 deletions src/terminal/adapter/adaptDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ namespace Microsoft::Console::VirtualTerminal
bool SecondaryDeviceAttributes() override; // DA2
bool TertiaryDeviceAttributes() override; // DA3
bool Vt52DeviceAttributes() override; // VT52 Identify
bool RequestTerminalParameters(const DispatchTypes::ReportingPermission permission) override; // DECREQTPARM
bool ScrollUp(const size_t distance) override; // SU
bool ScrollDown(const size_t distance) override; // SD
bool InsertLine(const size_t distance) override; // IL
Expand Down
1 change: 1 addition & 0 deletions src/terminal/adapter/termDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ class Microsoft::Console::VirtualTerminal::TermDispatch : public Microsoft::Cons
bool SecondaryDeviceAttributes() noexcept override { return false; } // DA2
bool TertiaryDeviceAttributes() noexcept override { return false; } // DA3
bool Vt52DeviceAttributes() noexcept override { return false; } // VT52 Identify
bool RequestTerminalParameters(const DispatchTypes::ReportingPermission /*permission*/) noexcept override { return false; } // DECREQTPARM

bool DesignateCodingSystem(const VTID /*codingSystem*/) noexcept override { return false; } // DOCS
bool Designate94Charset(const size_t /*gsetNumber*/, const VTID /*charset*/) noexcept override { return false; } // SCS
Expand Down
24 changes: 24 additions & 0 deletions src/terminal/adapter/ut_adapter/adapterTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1823,6 +1823,30 @@ class AdapterTest
VERIFY_IS_FALSE(_pDispatch.get()->TertiaryDeviceAttributes());
}

TEST_METHOD(RequestTerminalParametersTests)
{
Log::Comment(L"Starting test...");

Log::Comment(L"Test 1: Verify response for unsolicited permission.");
_testGetSet->PrepData();
VERIFY_IS_TRUE(_pDispatch.get()->RequestTerminalParameters(DispatchTypes::ReportingPermission::Unsolicited));
_testGetSet->ValidateInputEvent(L"\x1b[2;1;1;128;128;1;0x");

Log::Comment(L"Test 2: Verify response for solicited permission.");
_testGetSet->PrepData();
VERIFY_IS_TRUE(_pDispatch.get()->RequestTerminalParameters(DispatchTypes::ReportingPermission::Solicited));
_testGetSet->ValidateInputEvent(L"\x1b[3;1;1;128;128;1;0x");

Log::Comment(L"Test 3: Verify failure with invalid parameter.");
_testGetSet->PrepData();
VERIFY_IS_FALSE(_pDispatch.get()->RequestTerminalParameters((DispatchTypes::ReportingPermission)2));

Log::Comment(L"Test 4: Verify failure when WriteConsoleInput doesn't work.");
_testGetSet->PrepData();
_testGetSet->_privateWriteConsoleInputWResult = FALSE;
VERIFY_IS_FALSE(_pDispatch.get()->RequestTerminalParameters(DispatchTypes::ReportingPermission::Unsolicited));
}

TEST_METHOD(CursorKeysModeTest)
{
Log::Comment(L"Starting test...");
Expand Down
4 changes: 4 additions & 0 deletions src/terminal/parser/OutputStateMachineEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,10 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParamete
success = parameters.at(0).value_or(0) == 0 && _dispatch->TertiaryDeviceAttributes();
TermTelemetry::Instance().Log(TermTelemetry::Codes::DA3);
break;
case CsiActionCodes::DECREQTPARM_RequestTerminalParameters:
success = _dispatch->RequestTerminalParameters(parameters.at(0));
TermTelemetry::Instance().Log(TermTelemetry::Codes::DECREQTPARM);
break;
case CsiActionCodes::SU_ScrollUp:
success = _dispatch->ScrollUp(parameters.at(0));
TermTelemetry::Instance().Log(TermTelemetry::Codes::SU);
Expand Down
1 change: 1 addition & 0 deletions src/terminal/parser/OutputStateMachineEngine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ namespace Microsoft::Console::VirtualTerminal
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"),
DECREQTPARM_RequestTerminalParameters = VTID("x"),
DECSCUSR_SetCursorStyle = VTID(" q"),
DECSTR_SoftReset = VTID("!p"),
DECSCPP_SetColumnsPerPage = VTID("$|")
Expand Down
1 change: 1 addition & 0 deletions src/terminal/parser/telemetry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ void TermTelemetry::WriteFinalTraceLog() const
TraceLoggingUInt32(_uiTimesUsed[DA], "DA"),
TraceLoggingUInt32(_uiTimesUsed[DA2], "DA2"),
TraceLoggingUInt32(_uiTimesUsed[DA3], "DA3"),
TraceLoggingUInt32(_uiTimesUsed[DECREQTPARM], "DECREQTPARM"),
TraceLoggingUInt32(_uiTimesUsed[VPA], "VPA"),
TraceLoggingUInt32(_uiTimesUsed[HPR], "HPR"),
TraceLoggingUInt32(_uiTimesUsed[VPR], "VPR"),
Expand Down
1 change: 1 addition & 0 deletions src/terminal/parser/telemetry.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ namespace Microsoft::Console::VirtualTerminal
DA,
DA2,
DA3,
DECREQTPARM,
VPA,
HPR,
VPR,
Expand Down
50 changes: 50 additions & 0 deletions src/terminal/parser/ut_parser/OutputEngineTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1026,6 +1026,8 @@ class StatefulDispatch final : public TermDispatch
_secondaryDeviceAttributes{ false },
_tertiaryDeviceAttributes{ false },
_vt52DeviceAttributes{ false },
_requestTerminalParameters{ false },
_reportingPermission{ (DispatchTypes::ReportingPermission)-1 },
_isAltBuffer{ false },
_cursorKeysMode{ false },
_cursorBlinking{ true },
Expand Down Expand Up @@ -1237,6 +1239,14 @@ class StatefulDispatch final : public TermDispatch
return true;
}

bool RequestTerminalParameters(const DispatchTypes::ReportingPermission permission) noexcept override
{
_requestTerminalParameters = true;
_reportingPermission = permission;

return true;
}

bool _PrivateModeParamsHelper(_In_ DispatchTypes::PrivateModeParams const param, const bool fEnable)
{
bool fSuccess = false;
Expand Down Expand Up @@ -1476,6 +1486,8 @@ class StatefulDispatch final : public TermDispatch
bool _secondaryDeviceAttributes;
bool _tertiaryDeviceAttributes;
bool _vt52DeviceAttributes;
bool _requestTerminalParameters;
DispatchTypes::ReportingPermission _reportingPermission;
bool _isAltBuffer;
bool _cursorKeysMode;
bool _cursorBlinking;
Expand Down Expand Up @@ -2434,6 +2446,44 @@ class StateMachineExternalTest final
pDispatch->ClearState();
}

TEST_METHOD(TestRequestTerminalParameters)
{
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'x');

VERIFY_IS_TRUE(pDispatch->_requestTerminalParameters);
VERIFY_ARE_EQUAL(DispatchTypes::ReportingPermission::Unsolicited, pDispatch->_reportingPermission);

pDispatch->ClearState();

Log::Comment(L"Test 2: Check unsolicited permission, 0 param.");
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L'0');
mach.ProcessCharacter(L'x');

VERIFY_IS_TRUE(pDispatch->_requestTerminalParameters);
VERIFY_ARE_EQUAL(DispatchTypes::ReportingPermission::Unsolicited, pDispatch->_reportingPermission);

Log::Comment(L"Test 3: Check solicited permission, 1 param.");
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L'1');
mach.ProcessCharacter(L'x');

VERIFY_IS_TRUE(pDispatch->_requestTerminalParameters);
VERIFY_ARE_EQUAL(DispatchTypes::ReportingPermission::Solicited, pDispatch->_reportingPermission);

pDispatch->ClearState();
}

TEST_METHOD(TestStrings)
{
auto dispatch = std::make_unique<StatefulDispatch>();
Expand Down

0 comments on commit 30e363e

Please sign in to comment.