Skip to content

Commit

Permalink
Add support for querying the DECSCUSR setting (#17659)
Browse files Browse the repository at this point in the history
This PR adds support for querying the cursor style - technically the
state of the `DECSCUSR` setting - using a `DECRQSS` escape sequence.

## References and Relevant Issues

The initial `DECRQSS` support was added in PR #11152, but it wasn't
practical to report the cursor style until conpty passthrough was added
in PR #17510.

## Detailed Description of the Pull Request / Additional comments

If the user has chosen a cursor style that isn't one of the shapes
supported by the `DECSCUSR` control, we report those as 0 (i.e. the
default style). That way, if an application later tries to restore the
cursor using the returned value, it should still be reset to its
original state.

I also took the opportunity in this PR to do some refactoring of the
other `DECRQSS` reports, since several of them were using unnecessary
appending that could be simplified to a single `fmt::format` call, or
even just static strings in some cases.

## Validation Steps Performed

I've checked the reports are working as expected in Vttest, and also
added some unit tests.

## PR Checklist
- [x] Tests added/passed
  • Loading branch information
j4james authored Aug 5, 2024
1 parent 8149bd0 commit 5174c96
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 56 deletions.
111 changes: 55 additions & 56 deletions src/terminal/adapter/adaptDispatch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4259,6 +4259,9 @@ ITermDispatch::StringHandler AdaptDispatch::RequestSetting()
case VTID("s"):
_ReportDECSLRMSetting();
break;
case VTID(" q"):
_ReportDECSCUSRSetting();
break;
case VTID("\"q"):
_ReportDECSCASetting();
break;
Expand Down Expand Up @@ -4376,20 +4379,12 @@ void AdaptDispatch::_ReportSGRSetting() const
// - None
void AdaptDispatch::_ReportDECSTBMSetting()
{
using namespace std::string_view_literals;

// A valid response always starts with DCS 1 $ r.
fmt::basic_memory_buffer<wchar_t, 64> response;
response.append(L"\033P1$r"sv);

const auto page = _pages.ActivePage();
const auto [marginTop, marginBottom] = _GetVerticalMargins(page, false);
// A valid response always starts with DCS 1 $ r, the 'r' indicates this
// is a DECSTBM response, and ST ends the sequence.
// VT origin is at 1,1 so we need to add 1 to these margins.
fmt::format_to(std::back_inserter(response), FMT_COMPILE(L"{};{}"), marginTop + 1, marginBottom + 1);

// The 'r' indicates this is an DECSTBM response, and ST ends the sequence.
response.append(L"r\033\\"sv);
_api.ReturnResponse({ response.data(), response.size() });
_api.ReturnResponse(fmt::format(FMT_COMPILE(L"\033P1$r{};{}r\033\\"), marginTop + 1, marginBottom + 1));
}

// Method Description:
Expand All @@ -4400,20 +4395,46 @@ void AdaptDispatch::_ReportDECSTBMSetting()
// - None
void AdaptDispatch::_ReportDECSLRMSetting()
{
using namespace std::string_view_literals;

// A valid response always starts with DCS 1 $ r.
fmt::basic_memory_buffer<wchar_t, 64> response;
response.append(L"\033P1$r"sv);

const auto pageWidth = _pages.ActivePage().Width();
const auto [marginLeft, marginRight] = _GetHorizontalMargins(pageWidth);
// A valid response always starts with DCS 1 $ r, the 's' indicates this
// is a DECSLRM response, and ST ends the sequence.
// VT origin is at 1,1 so we need to add 1 to these margins.
fmt::format_to(std::back_inserter(response), FMT_COMPILE(L"{};{}"), marginLeft + 1, marginRight + 1);
_api.ReturnResponse(fmt::format(FMT_COMPILE(L"\033P1$r{};{}s\033\\"), marginLeft + 1, marginRight + 1));
}

// The 's' indicates this is an DECSLRM response, and ST ends the sequence.
response.append(L"s\033\\"sv);
_api.ReturnResponse({ response.data(), response.size() });
// Method Description:
// - Reports the DECSCUSR cursor style in response to a DECRQSS query.
// Arguments:
// - None
// Return Value:
// - None
void AdaptDispatch::_ReportDECSCUSRSetting() const
{
const auto& cursor = _pages.ActivePage().Cursor();
const auto blinking = cursor.IsBlinkingAllowed();
// A valid response always starts with DCS 1 $ r. This is followed by a
// number from 1 to 6 representing the cursor style. The ' q' indicates
// this is a DECSCUSR response, and ST ends the sequence.
switch (cursor.GetType())
{
case CursorType::FullBox:
_api.ReturnResponse(blinking ? L"\033P1$r1 q\033\\" : L"\033P1$r2 q\033\\");
break;
case CursorType::Underscore:
_api.ReturnResponse(blinking ? L"\033P1$r3 q\033\\" : L"\033P1$r4 q\033\\");
break;
case CursorType::VerticalBar:
_api.ReturnResponse(blinking ? L"\033P1$r5 q\033\\" : L"\033P1$r6 q\033\\");
break;
default:
// If we have a non-standard style, this is likely because it's the
// user's chosen default style, so we report a default value of 0.
// That way, if an application later tries to restore the cursor with
// the returned value, it should be reset to its original state.
_api.ReturnResponse(L"\033P1$r0 q\033\\");
break;
}
}

// Method Description:
Expand All @@ -4424,18 +4445,11 @@ void AdaptDispatch::_ReportDECSLRMSetting()
// - None
void AdaptDispatch::_ReportDECSCASetting() const
{
using namespace std::string_view_literals;

// A valid response always starts with DCS 1 $ r.
fmt::basic_memory_buffer<wchar_t, 64> response;
response.append(L"\033P1$r"sv);

const auto& attr = _pages.ActivePage().Attributes();
response.append(attr.IsProtected() ? L"1"sv : L"0"sv);

// The '"q' indicates this is an DECSCA response, and ST ends the sequence.
response.append(L"\"q\033\\"sv);
_api.ReturnResponse({ response.data(), response.size() });
const auto isProtected = _pages.ActivePage().Attributes().IsProtected();
// A valid response always starts with DCS 1 $ r. This is followed by '1' if
// the protected attribute is set, or '0' if not. The '"q' indicates this is
// a DECSCA response, and ST ends the sequence.
_api.ReturnResponse(isProtected ? L"\033P1$r1\"q\033\\" : L"\033P1$r0\"q\033\\");
}

// Method Description:
Expand All @@ -4446,17 +4460,11 @@ void AdaptDispatch::_ReportDECSCASetting() const
// - None
void AdaptDispatch::_ReportDECSACESetting() const
{
using namespace std::string_view_literals;

// A valid response always starts with DCS 1 $ r.
fmt::basic_memory_buffer<wchar_t, 64> response;
response.append(L"\033P1$r"sv);

response.append(_modes.test(Mode::RectangularChangeExtent) ? L"2"sv : L"1"sv);

// The '*x' indicates this is an DECSACE response, and ST ends the sequence.
response.append(L"*x\033\\"sv);
_api.ReturnResponse({ response.data(), response.size() });
const auto rectangularExtent = _modes.test(Mode::RectangularChangeExtent);
// A valid response always starts with DCS 1 $ r. This is followed by '2' if
// the extent is rectangular, or '1' if it's a stream. The '*x' indicates
// this is a DECSACE response, and ST ends the sequence.
_api.ReturnResponse(rectangularExtent ? L"\033P1$r2*x\033\\" : L"\033P1$r1*x\033\\");
}

// Method Description:
Expand All @@ -4467,8 +4475,6 @@ void AdaptDispatch::_ReportDECSACESetting() const
// - None
void AdaptDispatch::_ReportDECACSetting(const VTInt itemNumber) const
{
using namespace std::string_view_literals;

size_t fgIndex = 0;
size_t bgIndex = 0;
switch (static_cast<DispatchTypes::ColorItem>(itemNumber))
Expand All @@ -4485,16 +4491,9 @@ void AdaptDispatch::_ReportDECACSetting(const VTInt itemNumber) const
_api.ReturnResponse(L"\033P0$r\033\\");
return;
}

// A valid response always starts with DCS 1 $ r.
fmt::basic_memory_buffer<wchar_t, 64> response;
response.append(L"\033P1$r"sv);

fmt::format_to(std::back_inserter(response), FMT_COMPILE(L"{};{};{}"), itemNumber, fgIndex, bgIndex);

// The ',|' indicates this is a DECAC response, and ST ends the sequence.
response.append(L",|\033\\"sv);
_api.ReturnResponse({ response.data(), response.size() });
// A valid response always starts with DCS 1 $ r, the ',|' indicates this
// is a DECAC response, and ST ends the sequence.
_api.ReturnResponse(fmt::format(FMT_COMPILE(L"\033P1$r{};{};{},|\033\\"), itemNumber, fgIndex, bgIndex));
}

// Routine Description:
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 @@ -276,6 +276,7 @@ namespace Microsoft::Console::VirtualTerminal
void _ReportSGRSetting() const;
void _ReportDECSTBMSetting();
void _ReportDECSLRMSetting();
void _ReportDECSCUSRSetting() const;
void _ReportDECSCASetting() const;
void _ReportDECSACESetting() const;
void _ReportDECACSetting(const VTInt itemNumber) const;
Expand Down
49 changes: 49 additions & 0 deletions src/terminal/adapter/ut_adapter/adapterTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1942,6 +1942,55 @@ class AdapterTest
requestSetting(L"m");
_testGetSet->ValidateInputEvent(L"\033P1$r0;38:2::12:34:56;48:2::65:43:21;58:2::128:222:45m\033\\");

Log::Comment(L"Requesting DECSCUSR style (blinking block).");
_testGetSet->PrepData();
_testGetSet->_textBuffer->GetCursor().SetBlinkingAllowed(true);
_testGetSet->_textBuffer->GetCursor().SetType(CursorType::FullBox);
requestSetting(L" q");
_testGetSet->ValidateInputEvent(L"\033P1$r1 q\033\\");

Log::Comment(L"Requesting DECSCUSR style (steady block).");
_testGetSet->PrepData();
_testGetSet->_textBuffer->GetCursor().SetBlinkingAllowed(false);
_testGetSet->_textBuffer->GetCursor().SetType(CursorType::FullBox);
requestSetting(L" q");
_testGetSet->ValidateInputEvent(L"\033P1$r2 q\033\\");

Log::Comment(L"Requesting DECSCUSR style (blinking underline).");
_testGetSet->PrepData();
_testGetSet->_textBuffer->GetCursor().SetBlinkingAllowed(true);
_testGetSet->_textBuffer->GetCursor().SetType(CursorType::Underscore);
requestSetting(L" q");
_testGetSet->ValidateInputEvent(L"\033P1$r3 q\033\\");

Log::Comment(L"Requesting DECSCUSR style (steady underline).");
_testGetSet->PrepData();
_testGetSet->_textBuffer->GetCursor().SetBlinkingAllowed(false);
_testGetSet->_textBuffer->GetCursor().SetType(CursorType::Underscore);
requestSetting(L" q");
_testGetSet->ValidateInputEvent(L"\033P1$r4 q\033\\");

Log::Comment(L"Requesting DECSCUSR style (blinking bar).");
_testGetSet->PrepData();
_testGetSet->_textBuffer->GetCursor().SetBlinkingAllowed(true);
_testGetSet->_textBuffer->GetCursor().SetType(CursorType::VerticalBar);
requestSetting(L" q");
_testGetSet->ValidateInputEvent(L"\033P1$r5 q\033\\");

Log::Comment(L"Requesting DECSCUSR style (steady bar).");
_testGetSet->PrepData();
_testGetSet->_textBuffer->GetCursor().SetBlinkingAllowed(false);
_testGetSet->_textBuffer->GetCursor().SetType(CursorType::VerticalBar);
requestSetting(L" q");
_testGetSet->ValidateInputEvent(L"\033P1$r6 q\033\\");

Log::Comment(L"Requesting DECSCUSR style (non-standard).");
_testGetSet->PrepData();
_testGetSet->_textBuffer->GetCursor().SetBlinkingAllowed(true);
_testGetSet->_textBuffer->GetCursor().SetType(CursorType::Legacy);
requestSetting(L" q");
_testGetSet->ValidateInputEvent(L"\033P1$r0 q\033\\");

Log::Comment(L"Requesting DECSCA attributes (unprotected).");
_testGetSet->PrepData();
attribute = {};
Expand Down

0 comments on commit 5174c96

Please sign in to comment.