Repository: microsoft/terminal (language: cpp)

Issue:
Add support for querying DECAC with DECRQSS
# Description of the new feature/enhancement

This is a follow-up to PR #13058. Ideally we should be able to query the state of the `DECAC` color assignments using a `DECRQSS` query, so if an application changes those values, it can also restore them to what they were initially.

# Proposed technical implementation details (optional)

The problem here is that `DECAC` works differently from most other `DECRQSS` queries. Typically there is only one potential response for a query, but in the case of `DECAC` we need to know whether to return the _Normal Text_ colors, or the _Window Frame_ colors.

This is what the VT525 manual has to say on the subject:
> `DECAC` can be reported using `DECRQSS` and `DECRPSS`. Use **Ps1** in `DECRQSS` to select the item to be reported.

But it's not clear to me what that means. Is the **Ps1** meant to be included as a parameter value following the `DCS` introducer, or within the actual body of the string data? For example, if you're querying the window frame (color item 2), do you use this:

    \eP2$q,|\e\\

or this:

    \eP$q2,|\e\\

Of the modern terminal emulators I've tested, only three support `DECAC` in any capacity, and only two of those respond to a `DECRQSS` query for `DECAC`, but not in the same way. If I was forced to make a choice, I'd likely go with the second option, but I'm hoping we can get confirmation of the correct behavior from someone with a VT525.


Discussion / hints:
/cc @jerch If you get back to doing some testing on your VT525, this is one the escape sequences that I'd love to get more info on. And I'd be happy to put together some test scripts if that would help.
> /cc @jerch If you get back to doing some testing on your VT525, this is one the escape sequences that I'd love to get more info on. And I'd be happy to put together some test scripts if that would help.

Yes, if you have ready-to-go test scripts, that would certainly speed up the process, as I could simply run it and return you the output (without actually going into sequence semantics on my own, which is more time consuming).
Thanks @jerch - that would be much appreciated. I've created a test script here:
https://gist.github.com/j4james/eca9bd29189f2abe851f206f1cbda0cd

When it runs, it'll log the responses to a file name _decac.log_, so it should hopefully be easy to report back. But note that it just appends to the end of the file each time, so you may need to delete it between runs if you rerun the test.

If possible, it would be good if you could assign the default text colors for the terminal to blue on white (more specifically index values 4 on 7) before running the test. You should be able to do this from the Setup menu: select _Color_, then _Assign colors..._, then configure the values for the _Normal text_ option. This is not essential though - I'm just curious to see if that has any effect on the default parameters.
@al20878 I've just remembered this is another test I was hoping to get someone to run on a VT525, so while I'm bombarding you with requests, I'd be grateful if you could add this to the list. The comment above has some notes on how the test should ideally be run (see https://github.com/microsoft/terminal/issues/13091#issuecomment-1126579485).
@j4james Downloaded the script, read the instructions, will try to follow them, and report back the results.  Stay tuned ;-)
So I set the color as 4 on 7 and ran the script.  Screen shot:

![decac](https://user-images.githubusercontent.com/44822487/224578449-8148c553-6ada-494b-a67c-2d4c3516e901.png)

Log:

[decac.log](https://github.com/microsoft/terminal/files/10952271/decac.log)

@al20878 Thank you again. I think this has given me all the info I need.

And I'm grateful for the screenshot, because that's made me realise that our `DECAC` implementation is not quite correct. I had thought the color changes would apply to existing text on the screen, but it only seems to effect new output. Will need to rethink our architecture somewhat.

Fix patch:
diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp
index d43fea1bb83..b3937d9d433 100644
--- a/src/terminal/adapter/adaptDispatch.cpp
+++ b/src/terminal/adapter/adaptDispatch.cpp
@@ -3487,11 +3487,11 @@ ITermDispatch::StringHandler AdaptDispatch::RequestSetting()
     // 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')
+    return [this, parameter = VTInt{}, idBuilder = VTIDBuilder{}](const auto ch) mutable {
+        const auto isFinal = ch >= L'\x40' && ch <= L'\x7e';
+        if (isFinal)
         {
-            const auto id = idBuilder->Finalize(ch);
+            const auto id = idBuilder.Finalize(ch);
             switch (id)
             {
             case VTID("m"):
@@ -3506,6 +3506,9 @@ ITermDispatch::StringHandler AdaptDispatch::RequestSetting()
             case VTID("*x"):
                 _ReportDECSACESetting();
                 break;
+            case VTID(",|"):
+                _ReportDECACSetting(VTParameter{ parameter });
+                break;
             default:
                 _api.ReturnResponse(L"\033P0$r\033\\");
                 break;
@@ -3514,9 +3517,22 @@ ITermDispatch::StringHandler AdaptDispatch::RequestSetting()
         }
         else
         {
-            if (ch >= '\x20' && ch <= '\x2f')
+            // Although we don't yet support any operations with parameter
+            // prefixes, it's important that we still parse the prefix and
+            // include it in the ID. Otherwise we'll mistakenly respond to
+            // prefixed queries that we don't actually recognise.
+            const auto isParameterPrefix = ch >= L'<' && ch <= L'?';
+            const auto isParameter = ch >= L'0' && ch < L'9';
+            const auto isIntermediate = ch >= L'\x20' && ch <= L'\x2f';
+            if (isParameterPrefix || isIntermediate)
+            {
+                idBuilder.AddIntermediate(ch);
+            }
+            else if (isParameter)
             {
-                idBuilder->AddIntermediate(ch);
+                parameter *= 10;
+                parameter += (ch - L'0');
+                parameter = std::min(parameter, MAX_PARAMETER_VALUE);
             }
             return true;
         }
@@ -3656,6 +3672,44 @@ void AdaptDispatch::_ReportDECSACESetting() const
     _api.ReturnResponse({ response.data(), response.size() });
 }
 
+// Method Description:
+// - Reports the DECAC color assignments in response to a DECRQSS query.
+// Arguments:
+// - None
+// Return Value:
+// - 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))
+    {
+    case DispatchTypes::ColorItem::NormalText:
+        fgIndex = _renderSettings.GetColorAliasIndex(ColorAlias::DefaultForeground);
+        bgIndex = _renderSettings.GetColorAliasIndex(ColorAlias::DefaultBackground);
+        break;
+    case DispatchTypes::ColorItem::WindowFrame:
+        fgIndex = _renderSettings.GetColorAliasIndex(ColorAlias::FrameForeground);
+        bgIndex = _renderSettings.GetColorAliasIndex(ColorAlias::FrameBackground);
+        break;
+    default:
+        _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() });
+}
+
 // Routine Description:
 // - DECPS - Plays a sequence of musical notes.
 // Arguments:
diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp
index aab3402416f..b2a0fe1a353 100644
--- a/src/terminal/adapter/adaptDispatch.hpp
+++ b/src/terminal/adapter/adaptDispatch.hpp
@@ -236,6 +236,7 @@ namespace Microsoft::Console::VirtualTerminal
         void _ReportDECSTBMSetting();
         void _ReportDECSCASetting() const;
         void _ReportDECSACESetting() const;
+        void _ReportDECACSetting(const VTInt itemNumber) const;
 
         StringHandler _CreateDrcsPassthroughHandler(const DispatchTypes::DrcsCharsetSize charsetSize);
         StringHandler _CreatePassthroughHandler();
diff --git a/src/terminal/adapter/ut_adapter/adapterTest.cpp b/src/terminal/adapter/ut_adapter/adapterTest.cpp
index 4b49d63a25a..b080dbfc084 100644
--- a/src/terminal/adapter/ut_adapter/adapterTest.cpp
+++ b/src/terminal/adapter/ut_adapter/adapterTest.cpp
@@ -1753,6 +1753,30 @@ class AdapterTest
         requestSetting(L"\"q");
         _testGetSet->ValidateInputEvent(L"\033P1$r1\"q\033\\");
 
+        // Initialize the color alias indices for the DECAC tests below.
+        _testGetSet->PrepData();
+        auto& renderSettings = _testGetSet->_renderer._renderSettings;
+        renderSettings.SetColorAliasIndex(ColorAlias::DefaultForeground, 3);
+        renderSettings.SetColorAliasIndex(ColorAlias::DefaultBackground, 5);
+        renderSettings.SetColorAliasIndex(ColorAlias::FrameForeground, 4);
+        renderSettings.SetColorAliasIndex(ColorAlias::FrameBackground, 6);
+
+        Log::Comment(L"Requesting DECAC colors (default).");
+        requestSetting(L",|");
+        _testGetSet->ValidateInputEvent(L"\033P1$r1;3;5,|\033\\");
+
+        Log::Comment(L"Requesting DECAC colors (normal text).");
+        requestSetting(L"1,|");
+        _testGetSet->ValidateInputEvent(L"\033P1$r1;3;5,|\033\\");
+
+        Log::Comment(L"Requesting DECAC colors (window frame).");
+        requestSetting(L"2,|");
+        _testGetSet->ValidateInputEvent(L"\033P1$r2;4;6,|\033\\");
+
+        Log::Comment(L"Requesting DECAC colors (invalid item).");
+        requestSetting(L"3,|");
+        _testGetSet->ValidateInputEvent(L"\033P0$r\033\\");
+
         Log::Comment(L"Requesting an unsupported setting.");
         _testGetSet->PrepData();
         requestSetting(L"x");


Test patch: