Add basic support for the DECRQSS settings query (#11152)
This PR adds support for the `DECRQSS` (Request Selection or Setting) escape sequence, which is a standard VT query for reporting the state of various control functions. This initial implementation only supports queries for the `DECSTBM` margins, and the `SGR` graphic rendition attributes. This can be useful for certain forms of capability detection (#1040). As one example in particular, it can serve as an alternative to the `COLORTERM` environment variable for detecting truecolor support (#11057). Of the settings that can be queried by `DECRQSS`, the only other one that we could be supporting at the moment is `DECSCUSR` (Cursor Style). However, that would require passing the query through to the conpty client, which is a lot more complicated, so I thought it best to leave for a future PR. For now this gets the basic framework in place, so we are at least responding to queries, and even just supporting the `SGR` attributes query is useful in itself. Validation ---------- I've added a unit test verifying the reports for the `DECSTBM` and `SGR` settings with a range of different parameters. I've also tested the `DECSTBM` and `SGR` reports manually in _Vttest_, under menu 11.2.5.3.6 (Status-String Reports).
This commit is contained in:
parent
13e9546bab
commit
6140fd9ab8
|
@ -536,6 +536,7 @@ DECRC
|
|||
DECREQTPARM
|
||||
DECRLM
|
||||
DECRQM
|
||||
DECRQSS
|
||||
DECRST
|
||||
DECSASD
|
||||
DECSC
|
||||
|
|
|
@ -141,6 +141,8 @@ public:
|
|||
const DispatchTypes::DrcsFontUsage fontUsage,
|
||||
const VTParameter cellHeight,
|
||||
const DispatchTypes::DrcsCharsetSize charsetSize) = 0; // DECDLD
|
||||
|
||||
virtual StringHandler RequestSetting() = 0; // DECRQSS
|
||||
};
|
||||
inline Microsoft::Console::VirtualTerminal::ITermDispatch::~ITermDispatch() {}
|
||||
#pragma warning(pop)
|
||||
|
|
|
@ -2527,6 +2527,152 @@ ITermDispatch::StringHandler AdaptDispatch::DownloadDRCS(const size_t fontNumber
|
|||
};
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - DECRQSS - Requests the state of a VT setting. The value being queried is
|
||||
// identified by the intermediate and final characters of its control
|
||||
// sequence, which are passed to the string handler.
|
||||
// Arguments:
|
||||
// - None
|
||||
// Return Value:
|
||||
// - a function to receive the VTID of the setting being queried
|
||||
ITermDispatch::StringHandler AdaptDispatch::RequestSetting()
|
||||
{
|
||||
// We use a VTIDBuilder to parse the characters in the control string into
|
||||
// an ID which represents the setting being queried. If the given ID isn't
|
||||
// supported, we respond with an error sequence: DCS 0 $ r ST. Note that
|
||||
// this is the opposite of what is documented in most DEC manuals, which
|
||||
// say that 0 is for a valid response, and 1 is for an error. The correct
|
||||
// interpretation is documented in the DEC STD 070 reference.
|
||||
const auto idBuilder = std::make_shared<VTIDBuilder>();
|
||||
return [=](const auto ch) {
|
||||
if (ch >= '\x40' && ch <= '\x7e')
|
||||
{
|
||||
const auto id = idBuilder->Finalize(ch);
|
||||
switch (id)
|
||||
{
|
||||
case VTID('m'):
|
||||
_ReportSGRSetting();
|
||||
break;
|
||||
case VTID('r'):
|
||||
_ReportDECSTBMSetting();
|
||||
break;
|
||||
default:
|
||||
_WriteResponse(L"\033P0$r\033\\");
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ch >= '\x20' && ch <= '\x2f')
|
||||
{
|
||||
idBuilder->AddIntermediate(ch);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Reports the current SGR attributes in response to a DECRQSS query.
|
||||
// Arguments:
|
||||
// - None
|
||||
// Return Value:
|
||||
// - None
|
||||
void AdaptDispatch::_ReportSGRSetting() const
|
||||
{
|
||||
// A valid response always starts with DCS 1 $ r.
|
||||
// Then the '0' parameter is to reset the SGR attributes to the defaults.
|
||||
std::wstring response = L"\033P1$r0";
|
||||
|
||||
TextAttribute attr;
|
||||
if (_pConApi->PrivateGetTextAttributes(attr))
|
||||
{
|
||||
// For each boolean attribute that is set, we add the appropriate
|
||||
// parameter value to the response string.
|
||||
const auto addAttribute = [&](const auto parameter, const auto enabled) {
|
||||
if (enabled)
|
||||
{
|
||||
response += parameter;
|
||||
}
|
||||
};
|
||||
addAttribute(L";1", attr.IsBold());
|
||||
addAttribute(L";2", attr.IsFaint());
|
||||
addAttribute(L";3", attr.IsItalic());
|
||||
addAttribute(L";4", attr.IsUnderlined());
|
||||
addAttribute(L";5", attr.IsBlinking());
|
||||
addAttribute(L";7", attr.IsReverseVideo());
|
||||
addAttribute(L";8", attr.IsInvisible());
|
||||
addAttribute(L";9", attr.IsCrossedOut());
|
||||
addAttribute(L";21", attr.IsDoublyUnderlined());
|
||||
addAttribute(L";53", attr.IsOverlined());
|
||||
|
||||
// We also need to add the appropriate color encoding parameters for
|
||||
// both the foreground and background colors.
|
||||
const auto addColor = [&](const auto base, const auto color) {
|
||||
const auto iterator = std::back_insert_iterator(response);
|
||||
if (color.IsIndex16())
|
||||
{
|
||||
const auto index = XtermToWindowsIndex(color.GetIndex());
|
||||
const auto colorParameter = base + (index >= 8 ? 60 : 0) + (index % 8);
|
||||
fmt::format_to(iterator, FMT_STRING(L";{}"), colorParameter);
|
||||
}
|
||||
else if (color.IsIndex256())
|
||||
{
|
||||
const auto index = Xterm256ToWindowsIndex(color.GetIndex());
|
||||
fmt::format_to(iterator, FMT_STRING(L";{};5;{}"), base + 8, index);
|
||||
}
|
||||
else if (color.IsRgb())
|
||||
{
|
||||
const auto r = GetRValue(color.GetRGB());
|
||||
const auto g = GetGValue(color.GetRGB());
|
||||
const auto b = GetBValue(color.GetRGB());
|
||||
fmt::format_to(iterator, FMT_STRING(L";{};2;{};{};{}"), base + 8, r, g, b);
|
||||
}
|
||||
};
|
||||
addColor(30, attr.GetForeground());
|
||||
addColor(40, attr.GetBackground());
|
||||
}
|
||||
|
||||
// The 'm' indicates this is an SGR response, and ST ends the sequence.
|
||||
response += L"m\033\\";
|
||||
_WriteResponse(response);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Reports the DECSTBM margin range in response to a DECRQSS query.
|
||||
// Arguments:
|
||||
// - None
|
||||
// Return Value:
|
||||
// - None
|
||||
void AdaptDispatch::_ReportDECSTBMSetting() const
|
||||
{
|
||||
// A valid response always starts with DCS 1 $ r.
|
||||
std::wstring response = L"\033P1$r";
|
||||
|
||||
CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 };
|
||||
csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
|
||||
if (_pConApi->GetConsoleScreenBufferInfoEx(csbiex))
|
||||
{
|
||||
auto marginTop = _scrollMargins.Top + 1;
|
||||
auto marginBottom = _scrollMargins.Bottom + 1;
|
||||
// If the margin top is greater than or equal to the bottom, then the
|
||||
// margins aren't actually set, so we need to return the full height
|
||||
// of the window for the margin range.
|
||||
if (marginTop >= marginBottom)
|
||||
{
|
||||
marginTop = 1;
|
||||
marginBottom = csbiex.srWindow.Bottom - csbiex.srWindow.Top;
|
||||
}
|
||||
const auto iterator = std::back_insert_iterator(response);
|
||||
fmt::format_to(iterator, FMT_STRING(L"{};{}"), marginTop, marginBottom);
|
||||
}
|
||||
|
||||
// The 'r' indicates this is an DECSTBM response, and ST ends the sequence.
|
||||
response += L"r\033\\";
|
||||
_WriteResponse(response);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Determines whether we should pass any sequence that manipulates
|
||||
// TerminalInput's input generator through the PTY. It encapsulates
|
||||
|
|
|
@ -140,6 +140,8 @@ namespace Microsoft::Console::VirtualTerminal
|
|||
const VTParameter cellHeight,
|
||||
const DispatchTypes::DrcsCharsetSize charsetSize) override; // DECDLD
|
||||
|
||||
StringHandler RequestSetting() override; // DECRQSS
|
||||
|
||||
private:
|
||||
enum class ScrollDirection
|
||||
{
|
||||
|
@ -189,6 +191,9 @@ namespace Microsoft::Console::VirtualTerminal
|
|||
void _ResetTabStops() noexcept;
|
||||
void _InitTabStopsForWidth(const size_t width);
|
||||
|
||||
void _ReportSGRSetting() const;
|
||||
void _ReportDECSTBMSetting() const;
|
||||
|
||||
bool _ShouldPassThroughInputModeChange() const;
|
||||
|
||||
std::vector<bool> _tabStopColumns;
|
||||
|
|
|
@ -132,4 +132,6 @@ public:
|
|||
const DispatchTypes::DrcsFontUsage /*fontUsage*/,
|
||||
const VTParameter /*cellHeight*/,
|
||||
const DispatchTypes::DrcsCharsetSize /*charsetSize*/) noexcept override { return nullptr; }
|
||||
|
||||
StringHandler RequestSetting() noexcept override { return nullptr; }; // DECRQSS
|
||||
};
|
||||
|
|
|
@ -1990,6 +1990,109 @@ public:
|
|||
VERIFY_IS_FALSE(_pDispatch.get()->RequestTerminalParameters(DispatchTypes::ReportingPermission::Unsolicited));
|
||||
}
|
||||
|
||||
TEST_METHOD(RequestSettingsTests)
|
||||
{
|
||||
const auto requestSetting = [=](const std::wstring_view settingId = {}) {
|
||||
const auto stringHandler = _pDispatch.get()->RequestSetting();
|
||||
for (auto ch : settingId)
|
||||
{
|
||||
stringHandler(ch);
|
||||
}
|
||||
stringHandler(L'\033'); // String terminator
|
||||
};
|
||||
|
||||
Log::Comment(L"Requesting DECSTBM margins (5 to 10).");
|
||||
_testGetSet->PrepData();
|
||||
_pDispatch.get()->SetTopBottomScrollingMargins(5, 10);
|
||||
requestSetting(L"r");
|
||||
_testGetSet->ValidateInputEvent(L"\033P1$r5;10r\033\\");
|
||||
|
||||
Log::Comment(L"Requesting DECSTBM margins (full screen).");
|
||||
_testGetSet->PrepData();
|
||||
// Set screen height to 25 - this will be the expected margin range.
|
||||
_testGetSet->_viewport.Bottom = _testGetSet->_viewport.Top + 25;
|
||||
_pDispatch.get()->SetTopBottomScrollingMargins(0, 0);
|
||||
requestSetting(L"r");
|
||||
_testGetSet->ValidateInputEvent(L"\033P1$r1;25r\033\\");
|
||||
|
||||
Log::Comment(L"Requesting SGR attributes (default).");
|
||||
_testGetSet->PrepData();
|
||||
_testGetSet->_attribute = {};
|
||||
requestSetting(L"m");
|
||||
_testGetSet->ValidateInputEvent(L"\033P1$r0m\033\\");
|
||||
|
||||
Log::Comment(L"Requesting SGR attributes (bold, underlined, reversed).");
|
||||
_testGetSet->PrepData();
|
||||
_testGetSet->_attribute = {};
|
||||
_testGetSet->_attribute.SetBold(true);
|
||||
_testGetSet->_attribute.SetUnderlined(true);
|
||||
_testGetSet->_attribute.SetReverseVideo(true);
|
||||
requestSetting(L"m");
|
||||
_testGetSet->ValidateInputEvent(L"\033P1$r0;1;4;7m\033\\");
|
||||
|
||||
Log::Comment(L"Requesting SGR attributes (faint, blinking, invisible).");
|
||||
_testGetSet->PrepData();
|
||||
_testGetSet->_attribute = {};
|
||||
_testGetSet->_attribute.SetFaint(true);
|
||||
_testGetSet->_attribute.SetBlinking(true);
|
||||
_testGetSet->_attribute.SetInvisible(true);
|
||||
requestSetting(L"m");
|
||||
_testGetSet->ValidateInputEvent(L"\033P1$r0;2;5;8m\033\\");
|
||||
|
||||
Log::Comment(L"Requesting SGR attributes (italic, crossed-out).");
|
||||
_testGetSet->PrepData();
|
||||
_testGetSet->_attribute = {};
|
||||
_testGetSet->_attribute.SetItalic(true);
|
||||
_testGetSet->_attribute.SetCrossedOut(true);
|
||||
requestSetting(L"m");
|
||||
_testGetSet->ValidateInputEvent(L"\033P1$r0;3;9m\033\\");
|
||||
|
||||
Log::Comment(L"Requesting SGR attributes (doubly underlined, overlined).");
|
||||
_testGetSet->PrepData();
|
||||
_testGetSet->_attribute = {};
|
||||
_testGetSet->_attribute.SetDoublyUnderlined(true);
|
||||
_testGetSet->_attribute.SetOverlined(true);
|
||||
requestSetting(L"m");
|
||||
_testGetSet->ValidateInputEvent(L"\033P1$r0;21;53m\033\\");
|
||||
|
||||
Log::Comment(L"Requesting SGR attributes (standard colors).");
|
||||
_testGetSet->PrepData();
|
||||
_testGetSet->_attribute = {};
|
||||
_testGetSet->_attribute.SetIndexedForeground((BYTE)::XtermToWindowsIndex(3));
|
||||
_testGetSet->_attribute.SetIndexedBackground((BYTE)::XtermToWindowsIndex(6));
|
||||
requestSetting(L"m");
|
||||
_testGetSet->ValidateInputEvent(L"\033P1$r0;33;46m\033\\");
|
||||
|
||||
Log::Comment(L"Requesting SGR attributes (AIX colors).");
|
||||
_testGetSet->PrepData();
|
||||
_testGetSet->_attribute = {};
|
||||
_testGetSet->_attribute.SetIndexedForeground((BYTE)::XtermToWindowsIndex(14));
|
||||
_testGetSet->_attribute.SetIndexedBackground((BYTE)::XtermToWindowsIndex(11));
|
||||
requestSetting(L"m");
|
||||
_testGetSet->ValidateInputEvent(L"\033P1$r0;96;103m\033\\");
|
||||
|
||||
Log::Comment(L"Requesting SGR attributes (ITU indexed colors).");
|
||||
_testGetSet->PrepData();
|
||||
_testGetSet->_attribute = {};
|
||||
_testGetSet->_attribute.SetIndexedForeground256(123);
|
||||
_testGetSet->_attribute.SetIndexedBackground256(45);
|
||||
requestSetting(L"m");
|
||||
_testGetSet->ValidateInputEvent(L"\033P1$r0;38;5;123;48;5;45m\033\\");
|
||||
|
||||
Log::Comment(L"Requesting SGR attributes (ITU RGB colors).");
|
||||
_testGetSet->PrepData();
|
||||
_testGetSet->_attribute = {};
|
||||
_testGetSet->_attribute.SetForeground(RGB(12, 34, 56));
|
||||
_testGetSet->_attribute.SetBackground(RGB(65, 43, 21));
|
||||
requestSetting(L"m");
|
||||
_testGetSet->ValidateInputEvent(L"\033P1$r0;38;2;12;34;56;48;2;65;43;21m\033\\");
|
||||
|
||||
Log::Comment(L"Requesting an unsupported setting.");
|
||||
_testGetSet->PrepData();
|
||||
requestSetting(L"x");
|
||||
_testGetSet->ValidateInputEvent(L"\033P0$r\033\\");
|
||||
}
|
||||
|
||||
TEST_METHOD(CursorKeysModeTest)
|
||||
{
|
||||
Log::Comment(L"Starting test...");
|
||||
|
|
|
@ -661,6 +661,9 @@ IStateMachineEngine::StringHandler OutputStateMachineEngine::ActionDcsDispatch(c
|
|||
parameters.at(6),
|
||||
parameters.at(7));
|
||||
break;
|
||||
case DcsActionCodes::DECRQSS_RequestSetting:
|
||||
handler = _dispatch->RequestSetting();
|
||||
break;
|
||||
default:
|
||||
handler = nullptr;
|
||||
break;
|
||||
|
|
|
@ -147,6 +147,7 @@ namespace Microsoft::Console::VirtualTerminal
|
|||
enum DcsActionCodes : uint64_t
|
||||
{
|
||||
DECDLD_DownloadDRCS = VTID("{"),
|
||||
DECRQSS_RequestSetting = VTID("$q")
|
||||
};
|
||||
|
||||
enum Vt52ActionCodes : uint64_t
|
||||
|
|
Loading…
Reference in New Issue