Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic support for the DECRQSS settings query #11152

Merged
5 commits merged into from Sep 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/actions/spelling/expect/expect.txt
Expand Up @@ -536,6 +536,7 @@ DECRC
DECREQTPARM
DECRLM
DECRQM
DECRQSS
DECRST
DECSASD
DECSC
Expand Down
2 changes: 2 additions & 0 deletions src/terminal/adapter/ITermDispatch.hpp
Expand Up @@ -141,6 +141,8 @@ class Microsoft::Console::VirtualTerminal::ITermDispatch
const DispatchTypes::DrcsFontUsage fontUsage,
const VTParameter cellHeight,
const DispatchTypes::DrcsCharsetSize charsetSize) = 0; // DECDLD

virtual StringHandler RequestSetting() = 0; // DECRQSS
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/cc @oising we were chatting about how to indicate what "sub-things" we support for things that have multiple .. well, sub-things.

};
inline Microsoft::Console::VirtualTerminal::ITermDispatch::~ITermDispatch() {}
#pragma warning(pop)
146 changes: 146 additions & 0 deletions src/terminal/adapter/adaptDispatch.cpp
Expand Up @@ -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'):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why'd we use the raw constants here instead of the predefined ones like SGR_SetGraphicsRendition? Is it just because they live in an annoying-to-get-to place?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I would have liked to use them. But maybe we can move them somewhere more accessible at some point in the future.

_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());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this conversion backwards? Won't GetIndex() return the windows index, which we'll want to use as a VT index?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semantically yes, but in practice it's a bidirectional operation (if that's the right term). It's just swapping bits 0 and 2. I suppose we could create a couple of aliases for these functions to makes things more readable, but I really would like to just get rid of them one day.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well I'll be danged, that does happen to work the other way, doesn't it

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
Expand Down
5 changes: 5 additions & 0 deletions src/terminal/adapter/adaptDispatch.hpp
Expand Up @@ -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
{
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/terminal/adapter/termDispatch.hpp
Expand Up @@ -132,4 +132,6 @@ class Microsoft::Console::VirtualTerminal::TermDispatch : public Microsoft::Cons
const DispatchTypes::DrcsFontUsage /*fontUsage*/,
const VTParameter /*cellHeight*/,
const DispatchTypes::DrcsCharsetSize /*charsetSize*/) noexcept override { return nullptr; }

StringHandler RequestSetting() noexcept override { return nullptr; }; // DECRQSS
};
103 changes: 103 additions & 0 deletions src/terminal/adapter/ut_adapter/adapterTest.cpp
Expand Up @@ -1990,6 +1990,109 @@ class AdapterTest
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...");
Expand Down
3 changes: 3 additions & 0 deletions src/terminal/parser/OutputStateMachineEngine.cpp
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/terminal/parser/OutputStateMachineEngine.hpp
Expand Up @@ -147,6 +147,7 @@ namespace Microsoft::Console::VirtualTerminal
enum DcsActionCodes : uint64_t
{
DECDLD_DownloadDRCS = VTID("{"),
DECRQSS_RequestSetting = VTID("$q")
};

enum Vt52ActionCodes : uint64_t
Expand Down