diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index 4369f9e07d3..78658e70c65 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -1377,8 +1377,7 @@ void AdaptDispatch::RequestChecksumRectangularArea(const VTInt id, const VTInt p } } } - const auto response = wil::str_printf(L"\033P%d!~%04X\033\\", id, checksum); - _api.ReturnResponse(response); + _ReturnDcsResponse(wil::str_printf(L"%d!~%04X", id, checksum)); } // Routine Description: @@ -1484,7 +1483,7 @@ void AdaptDispatch::DeviceAttributes() // 32 = Text macros // 42 = ISO Latin-2 character set - _api.ReturnResponse(L"\x1b[?61;4;6;7;14;21;22;23;24;28;32;42c"); + _ReturnCsiResponse(L"?61;4;6;7;14;21;22;23;24;28;32;42c"); } // Routine Description: @@ -1496,7 +1495,7 @@ void AdaptDispatch::DeviceAttributes() // - void AdaptDispatch::SecondaryDeviceAttributes() { - _api.ReturnResponse(L"\x1b[>0;10;1c"); + _ReturnCsiResponse(L">0;10;1c"); } // Routine Description: @@ -1506,7 +1505,7 @@ void AdaptDispatch::SecondaryDeviceAttributes() // - void AdaptDispatch::TertiaryDeviceAttributes() { - _api.ReturnResponse(L"\x1bP!|00000000\x1b\\"); + _ReturnDcsResponse(L"!|00000000"); } // Routine Description: @@ -1544,10 +1543,10 @@ void AdaptDispatch::RequestTerminalParameters(const DispatchTypes::ReportingPerm switch (permission) { case DispatchTypes::ReportingPermission::Unsolicited: - _api.ReturnResponse(L"\x1b[2;1;1;128;128;1;0x"); + _ReturnCsiResponse(L"2;1;1;128;128;1;0x"); break; case DispatchTypes::ReportingPermission::Solicited: - _api.ReturnResponse(L"\x1b[3;1;1;128;128;1;0x"); + _ReturnCsiResponse(L"3;1;1;128;128;1;0x"); break; default: break; @@ -1562,7 +1561,7 @@ void AdaptDispatch::RequestTerminalParameters(const DispatchTypes::ReportingPerm // - void AdaptDispatch::_DeviceStatusReport(const wchar_t* parameters) const { - _api.ReturnResponse(fmt::format(FMT_COMPILE(L"\033[{}n"), parameters)); + _ReturnCsiResponse(fmt::format(FMT_COMPILE(L"{}n"), parameters)); } // Routine Description: @@ -1598,14 +1597,12 @@ void AdaptDispatch::_CursorPositionReport(const bool extendedReport) { // An extended report also includes the page number. const auto pageNumber = page.Number(); - const auto response = wil::str_printf(L"\x1b[?%d;%d;%dR", cursorPosition.y, cursorPosition.x, pageNumber); - _api.ReturnResponse(response); + _ReturnCsiResponse(wil::str_printf(L"?%d;%d;%dR", cursorPosition.y, cursorPosition.x, pageNumber)); } else { // The standard report only returns the cursor position. - const auto response = wil::str_printf(L"\x1b[%d;%dR", cursorPosition.y, cursorPosition.x); - _api.ReturnResponse(response); + _ReturnCsiResponse(wil::str_printf(L"%d;%dR", cursorPosition.y, cursorPosition.x)); } } @@ -1619,8 +1616,7 @@ void AdaptDispatch::_MacroSpaceReport() const { const auto spaceInBytes = _macroBuffer ? _macroBuffer->GetSpaceAvailable() : MacroBuffer::MAX_SPACE; // The available space is measured in blocks of 16 bytes, so we need to divide by 16. - const auto response = wil::str_printf(L"\x1b[%zu*{", spaceInBytes / 16); - _api.ReturnResponse(response); + _ReturnCsiResponse(wil::str_printf(L"%zu*{", spaceInBytes / 16)); } // Routine Description: @@ -1633,8 +1629,7 @@ void AdaptDispatch::_MacroChecksumReport(const VTParameter id) const { const auto requestId = id.value_or(0); const auto checksum = _macroBuffer ? _macroBuffer->CalculateChecksum() : 0; - const auto response = wil::str_printf(L"\033P%d!~%04X\033\\", requestId, checksum); - _api.ReturnResponse(response); + _ReturnDcsResponse(wil::str_printf(L"%d!~%04X", requestId, checksum)); } // Routine Description: @@ -1732,7 +1727,7 @@ void AdaptDispatch::RequestDisplayedExtent() const auto height = page.Viewport().height(); const auto left = page.XPanOffset() + 1; const auto top = page.YPanOffset() + 1; - _api.ReturnResponse(fmt::format(FMT_COMPILE(L"\033[{};{};{};{};{}\"w"), height, width, left, top, page.Number())); + _ReturnCsiResponse(fmt::format(FMT_COMPILE(L"{};{};{};{};{}\"w"), height, width, left, top, page.Number())); } // Routine Description: @@ -2068,7 +2063,7 @@ void AdaptDispatch::RequestMode(const DispatchTypes::ModeParams param) prefix = L"?"; } - _api.ReturnResponse(fmt::format(FMT_COMPILE(L"\x1b[{}{};{}$y"), prefix, mode, state)); + _ReturnCsiResponse(fmt::format(FMT_COMPILE(L"{}{};{}$y"), prefix, mode, state)); } // - DECKPAM, DECKPNM - Sets the keypad input mode to either Application mode or Numeric mode (true, false respectively) @@ -3288,7 +3283,7 @@ void AdaptDispatch::RequestColorTableEntry(const size_t tableIndex) { const til::color c{ color }; // Scale values up to match xterm's 16-bit color report format. - _api.ReturnResponse(fmt::format(FMT_COMPILE(L"\033]4;{};rgb:{:04x}/{:04x}/{:04x}\033\\"), tableIndex, c.r * 0x0101, c.g * 0x0101, c.b * 0x0101)); + _ReturnOscResponse(fmt::format(FMT_COMPILE(L"4;{};rgb:{:04x}/{:04x}/{:04x}"), tableIndex, c.r * 0x0101, c.g * 0x0101, c.b * 0x0101)); } } @@ -3333,7 +3328,7 @@ void AdaptDispatch::RequestXtermColorResource(const size_t resource) { const til::color c{ color }; // Scale values up to match xterm's 16-bit color report format. - _api.ReturnResponse(fmt::format(FMT_COMPILE(L"\033]{};rgb:{:04x}/{:04x}/{:04x}\033\\"), resource, c.r * 0x0101, c.g * 0x0101, c.b * 0x0101)); + _ReturnOscResponse(fmt::format(FMT_COMPILE(L"{};rgb:{:04x}/{:04x}/{:04x}"), resource, c.r * 0x0101, c.g * 0x0101, c.b * 0x0101)); } } } @@ -3389,7 +3384,7 @@ void AdaptDispatch::WindowManipulation(const DispatchTypes::WindowManipulationTy const auto reportSize = [&](const auto size) { const auto reportType = function - 10; - _api.ReturnResponse(fmt::format(FMT_COMPILE(L"\033[{};{};{}t"), reportType, size.height, size.width)); + _ReturnCsiResponse(fmt::format(FMT_COMPILE(L"{};{};{}t"), reportType, size.height, size.width)); }; switch (function) @@ -3856,7 +3851,7 @@ void AdaptDispatch::RequestUserPreferenceCharset() { const auto size = _termOutput.GetUserPreferenceCharsetSize(); const auto id = _termOutput.GetUserPreferenceCharsetId(); - _api.ReturnResponse(fmt::format(FMT_COMPILE(L"\033P{}!u{}\033\\"), (size == 96 ? 1 : 0), id)); + _ReturnDcsResponse(fmt::format(FMT_COMPILE(L"{}!u{}"), (size == 96 ? 1 : 0), id)); } // Method Description: @@ -3988,9 +3983,9 @@ void AdaptDispatch::_ReportColorTable(const DispatchTypes::ColorModel colorModel { using namespace std::string_view_literals; - // A valid response always starts with DCS 2 $ s. + // A valid response always starts with 2 $ s. fmt::basic_memory_buffer response; - response.append(L"\033P2$s"sv); + response.append(L"2$s"sv); const auto modelNumber = static_cast(colorModel); for (size_t colorNumber = 0; colorNumber < TextColor::TABLE_SIZE; colorNumber++) @@ -4015,9 +4010,7 @@ void AdaptDispatch::_ReportColorTable(const DispatchTypes::ColorModel colorModel } } - // An ST ends the sequence. - response.append(L"\033\\"sv); - _api.ReturnResponse({ response.data(), response.size() }); + _ReturnDcsResponse({ response.data(), response.size() }); } // Method Description: @@ -4120,7 +4113,7 @@ ITermDispatch::StringHandler AdaptDispatch::RequestSetting() _ReportDECACSetting(VTParameter{ parameter }); break; default: - _api.ReturnResponse(L"\033P0$r\033\\"); + _ReturnDcsResponse(L"0$r"); break; } return false; @@ -4159,10 +4152,10 @@ void AdaptDispatch::_ReportSGRSetting() const { using namespace std::string_view_literals; - // A valid response always starts with DCS 1 $ r. + // A valid response always starts with 1 $ r. // Then the '0' parameter is to reset the SGR attributes to the defaults. fmt::basic_memory_buffer response; - response.append(L"\033P1$r0"sv); + response.append(L"1$r0"sv); const auto& attr = _pages.ActivePage().Attributes(); const auto ulStyle = attr.GetUnderlineStyle(); @@ -4214,9 +4207,9 @@ void AdaptDispatch::_ReportSGRSetting() const addColor(40, attr.GetBackground()); addColor(50, attr.GetUnderlineColor()); - // The 'm' indicates this is an SGR response, and ST ends the sequence. - response.append(L"m\033\\"sv); - _api.ReturnResponse({ response.data(), response.size() }); + // The 'm' indicates this is an SGR response. + response.append(L"m"sv); + _ReturnDcsResponse({ response.data(), response.size() }); } // Method Description: @@ -4229,10 +4222,9 @@ void AdaptDispatch::_ReportDECSTBMSetting() { 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. + // A valid response always starts with 1 $ r and the final 'r' indicates this is a DECSTBM response. // VT origin is at 1,1 so we need to add 1 to these margins. - _api.ReturnResponse(fmt::format(FMT_COMPILE(L"\033P1$r{};{}r\033\\"), marginTop + 1, marginBottom + 1)); + _ReturnDcsResponse(fmt::format(FMT_COMPILE(L"1$r{};{}r"), marginTop + 1, marginBottom + 1)); } // Method Description: @@ -4245,10 +4237,9 @@ void AdaptDispatch::_ReportDECSLRMSetting() { 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. + // A valid response always starts with 1 $ r and the 's' indicates this is a DECSLRM response. // VT origin is at 1,1 so we need to add 1 to these margins. - _api.ReturnResponse(fmt::format(FMT_COMPILE(L"\033P1$r{};{}s\033\\"), marginLeft + 1, marginRight + 1)); + _ReturnDcsResponse(fmt::format(FMT_COMPILE(L"1$r{};{}s"), marginLeft + 1, marginRight + 1)); } // Method Description: @@ -4261,26 +4252,26 @@ 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 + // A valid response always starts with 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. + // this is a DECSCUSR response. switch (cursor.GetType()) { case CursorType::FullBox: - _api.ReturnResponse(blinking ? L"\033P1$r1 q\033\\" : L"\033P1$r2 q\033\\"); + _ReturnDcsResponse(blinking ? L"1$r1 q" : L"1$r2 q"); break; case CursorType::Underscore: - _api.ReturnResponse(blinking ? L"\033P1$r3 q\033\\" : L"\033P1$r4 q\033\\"); + _ReturnDcsResponse(blinking ? L"1$r3 q" : L"1$r4 q"); break; case CursorType::VerticalBar: - _api.ReturnResponse(blinking ? L"\033P1$r5 q\033\\" : L"\033P1$r6 q\033\\"); + _ReturnDcsResponse(blinking ? L"1$r5 q" : L"1$r6 q"); 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\\"); + _ReturnDcsResponse(L"1$r0 q"); break; } } @@ -4294,10 +4285,10 @@ void AdaptDispatch::_ReportDECSCUSRSetting() const void AdaptDispatch::_ReportDECSCASetting() const { 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\\"); + // A valid response always starts with 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. + _ReturnDcsResponse(isProtected ? L"1$r1\"q" : L"1$r0\"q"); } // Method Description: @@ -4309,10 +4300,10 @@ void AdaptDispatch::_ReportDECSCASetting() const void AdaptDispatch::_ReportDECSACESetting() const { const auto rectangularExtent = _modes.test(Mode::RectangularChangeExtent); - // A valid response always starts with DCS 1 $ r. This is followed by '2' if + // A valid response always starts with 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\\"); + // this is a DECSACE response. + _ReturnDcsResponse(rectangularExtent ? L"1$r2*x" : L"1$r1*x"); } // Method Description: @@ -4336,12 +4327,11 @@ void AdaptDispatch::_ReportDECACSetting(const VTInt itemNumber) const bgIndex = _renderSettings.GetColorAliasIndex(ColorAlias::FrameBackground); break; default: - _api.ReturnResponse(L"\033P0$r\033\\"); + _ReturnDcsResponse(L"0$r"); return; } - // 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)); + // A valid response always starts with 1 $ r and the ',|' indicates this is a DECAC response. + _ReturnDcsResponse(fmt::format(FMT_COMPILE(L"1$r{};{};{},|"), itemNumber, fgIndex, bgIndex)); } // Routine Description: @@ -4454,9 +4444,9 @@ void AdaptDispatch::_ReportCursorInformation() const auto charset2 = _termOutput.GetCharsetId(2); const auto charset3 = _termOutput.GetCharsetId(3); - // A valid response always starts with DCS 1 $ u and ends with ST. + // A valid response always starts with 1 $ u. const auto response = fmt::format( - FMT_COMPILE(L"\033P1$u{};{};{};{};{};{};{};{};{};{}{}{}{}\033\\"), + FMT_COMPILE(L"1$u{};{};{};{};{};{};{};{};{};{}{}{}{}"), cursorPosition.y, cursorPosition.x, page.Number(), @@ -4470,7 +4460,7 @@ void AdaptDispatch::_ReportCursorInformation() charset1, charset2, charset3); - _api.ReturnResponse({ response.data(), response.size() }); + _ReturnDcsResponse({ response.data(), response.size() }); } // Method Description: @@ -4635,9 +4625,9 @@ void AdaptDispatch::_ReportTabStops() using namespace std::string_view_literals; - // A valid response always starts with DCS 2 $ u. + // A valid response always starts with 2 $ u. fmt::basic_memory_buffer response; - response.append(L"\033P2$u"sv); + response.append(L"2$u"sv); auto need_separator = false; for (auto column = 0; column < width; column++) @@ -4650,9 +4640,7 @@ void AdaptDispatch::_ReportTabStops() } } - // An ST ends the sequence. - response.append(L"\033\\"sv); - _api.ReturnResponse({ response.data(), response.size() }); + _ReturnDcsResponse({ response.data(), response.size() }); } // Method Description: @@ -4697,6 +4685,26 @@ ITermDispatch::StringHandler AdaptDispatch::_RestoreTabStops() }; } +void AdaptDispatch::_ReturnCsiResponse(const std::wstring_view response) const +{ + const auto csi = _terminalInput.GetInputMode(TerminalInput::Mode::SendC1) ? L"\x9B" : L"\x1B["; + _api.ReturnResponse(fmt::format(FMT_COMPILE(L"{}{}"), csi, response)); +} + +void AdaptDispatch::_ReturnDcsResponse(const std::wstring_view response) const +{ + const auto dcs = _terminalInput.GetInputMode(TerminalInput::Mode::SendC1) ? L"\x90" : L"\x1BP"; + const auto st = _terminalInput.GetInputMode(TerminalInput::Mode::SendC1) ? L"\x9C" : L"\x1B\\"; + _api.ReturnResponse(fmt::format(FMT_COMPILE(L"{}{}{}"), dcs, response, st)); +} + +void AdaptDispatch::_ReturnOscResponse(const std::wstring_view response) const +{ + const auto osc = _terminalInput.GetInputMode(TerminalInput::Mode::SendC1) ? L"\x9D" : L"\x1B]"; + const auto st = _terminalInput.GetInputMode(TerminalInput::Mode::SendC1) ? L"\x9C" : L"\x1B\\"; + _api.ReturnResponse(fmt::format(FMT_COMPILE(L"{}{}{}"), osc, response, st)); +} + // 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 65c02c70d94..969a49d9f2d 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -290,6 +290,10 @@ namespace Microsoft::Console::VirtualTerminal void _ReportTabStops(); StringHandler _RestoreTabStops(); + void _ReturnCsiResponse(const std::wstring_view response) const; + void _ReturnDcsResponse(const std::wstring_view response) const; + void _ReturnOscResponse(const std::wstring_view response) const; + std::vector _tabStopColumns; bool _initDefaultTabStops = true; diff --git a/src/terminal/input/terminalInput.cpp b/src/terminal/input/terminalInput.cpp index 7c09ef46155..ac0795bdc41 100644 --- a/src/terminal/input/terminalInput.cpp +++ b/src/terminal/input/terminalInput.cpp @@ -53,7 +53,7 @@ void TerminalInput::SetInputMode(const Mode mode, const bool enabled) noexcept // If we've changed one of the modes that alter the VT input sequences, // we'll need to regenerate our keyboard map. - static constexpr auto keyMapModes = til::enumset{ Mode::LineFeed, Mode::Ansi, Mode::Keypad, Mode::CursorKey, Mode::BackarrowKey }; + static constexpr auto keyMapModes = til::enumset{ Mode::LineFeed, Mode::Ansi, Mode::Keypad, Mode::CursorKey, Mode::BackarrowKey, Mode::SendC1 }; if (keyMapModes.test(mode)) { _initKeyboardMap(); @@ -353,6 +353,19 @@ try _keyMap.clear(); + // The CSI and SS3 introducers are C1 control codes, which can either be + // sent as a single codepoint, or as a two character escape sequence. + if (_inputMode.test(Mode::SendC1)) + { + _csi = L"\x9B"; + _ss3 = L"\x8F"; + } + else + { + _csi = L"\x1B["; + _ss3 = L"\x1BO"; + } + // PAUSE doesn't have a VT mapping, but traditionally we've mapped it to ^Z, // regardless of modifiers. defineKeyWithUnusedModifiers(VK_PAUSE, L"\x1A"s); diff --git a/src/terminal/input/terminalInput.hpp b/src/terminal/input/terminalInput.hpp index 56ffb41362c..40488269569 100644 --- a/src/terminal/input/terminalInput.hpp +++ b/src/terminal/input/terminalInput.hpp @@ -81,10 +81,8 @@ namespace Microsoft::Console::VirtualTerminal til::enumset _inputMode{ Mode::Ansi, Mode::AutoRepeat, Mode::AlternateScroll }; bool _forceDisableWin32InputMode{ false }; - // In the future, if we add support for "8-bit" input mode, these prefixes - // will sometimes be replaced with equivalent C1 control characters. - static constexpr auto _csi = L"\x1B["; - static constexpr auto _ss3 = L"\x1BO"; + const wchar_t* _csi = L"\x1B["; + const wchar_t* _ss3 = L"\x1BO"; void _initKeyboardMap() noexcept; DWORD _trackControlKeyState(const KEY_EVENT_RECORD& key); @@ -109,9 +107,9 @@ namespace Microsoft::Console::VirtualTerminal #pragma endregion #pragma region MouseInput - [[nodiscard]] static OutputType _GenerateDefaultSequence(til::point position, unsigned int button, bool isHover, short modifierKeyState, short delta); - [[nodiscard]] static OutputType _GenerateUtf8Sequence(til::point position, unsigned int button, bool isHover, short modifierKeyState, short delta); - [[nodiscard]] static OutputType _GenerateSGRSequence(til::point position, unsigned int button, bool isDown, bool isHover, short modifierKeyState, short delta); + [[nodiscard]] OutputType _GenerateDefaultSequence(til::point position, unsigned int button, bool isHover, short modifierKeyState, short delta); + [[nodiscard]] OutputType _GenerateUtf8Sequence(til::point position, unsigned int button, bool isHover, short modifierKeyState, short delta); + [[nodiscard]] OutputType _GenerateSGRSequence(til::point position, unsigned int button, bool isDown, bool isHover, short modifierKeyState, short delta); [[nodiscard]] OutputType _makeAlternateScrollOutput(short delta) const; diff --git a/src/terminal/parser/stateMachine.cpp b/src/terminal/parser/stateMachine.cpp index 65e7460e9b8..12954512bdb 100644 --- a/src/terminal/parser/stateMachine.cpp +++ b/src/terminal/parser/stateMachine.cpp @@ -25,6 +25,9 @@ StateMachine::StateMachine(std::unique_ptr engine, const bo _oscString{}, _cachedSequence{ std::nullopt } { + // The state machine must always accept C1 controls for the input engine, + // otherwise it won't work when the ConPTY terminal has S8C1T enabled. + _parserMode.set(Mode::AcceptC1, _isEngineForInput); _ActionClear(); }