mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-10 00:48:23 -06:00
Add support for the S8C1T/S7C1T escape sequences (#17945)
This PR adds support for the `S8C1T` and `S7C1T` commands, which enable an application to choose whether the terminal should use C1 controls when sending key sequences and query responses. This also updates the `DOCS` command to set both the input and output code pages. So when switched to ISO2022 mode, the C1 controls will be transmitted as 8-bit, which is what legacy systems would be expecting. ## Detailed Description of the Pull Request / Additional comments While adding the input code page support, I also reworked the way we handle the code page reset in `RIS`. In the original implementation we saved the active code page when the `DOCS` sequence was first used, and that would become the default value for a reset. With this PR I'm now saving the code pages whenever `SetConsoleCP` or `SetConsoleOutputCP` is called, so those APIs now control what the default values will be. This feels more consistent than the previous approach. And this is how WSL sets its initial code page to UTF-8. ## Validation Steps Performed I've added a couple of unit tests that check one of each applicable C1 control in the key sequences and query reports. I also built myself a code page aware telnet client so I could log into WSL in 8-bit mode, and confirmed that the C1 transmissions are working as expected in vttest. Closes #17931 Tests added/passed
This commit is contained in:
parent
b715008de3
commit
aa256ad5c9
@ -139,8 +139,10 @@ public:
|
||||
void SetWindowTitle(const std::wstring_view title) override;
|
||||
CursorType GetUserDefaultCursorStyle() const noexcept override;
|
||||
bool ResizeWindow(const til::CoordType width, const til::CoordType height) override;
|
||||
void SetConsoleOutputCP(const unsigned int codepage) noexcept override;
|
||||
unsigned int GetConsoleOutputCP() const noexcept override;
|
||||
void SetCodePage(const unsigned int codepage) noexcept override;
|
||||
void ResetCodePage() noexcept override;
|
||||
unsigned int GetOutputCodePage() const noexcept override;
|
||||
unsigned int GetInputCodePage() const noexcept override;
|
||||
void CopyToClipboard(wil::zwstring_view content) override;
|
||||
void SetTaskbarProgress(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::TaskbarState state, const size_t progress) override;
|
||||
void SetWorkingDirectory(std::wstring_view uri) override;
|
||||
|
||||
@ -116,14 +116,25 @@ bool Terminal::ResizeWindow(const til::CoordType width, const til::CoordType hei
|
||||
return false;
|
||||
}
|
||||
|
||||
void Terminal::SetConsoleOutputCP(const unsigned int /*codepage*/) noexcept
|
||||
void Terminal::SetCodePage(const unsigned int /*codepage*/) noexcept
|
||||
{
|
||||
// TODO: This will be needed to support 8-bit charsets and DOCS sequences.
|
||||
// Code pages are dealt with in ConHost, so this isn't needed.
|
||||
}
|
||||
|
||||
unsigned int Terminal::GetConsoleOutputCP() const noexcept
|
||||
void Terminal::ResetCodePage() noexcept
|
||||
{
|
||||
// TODO: See SetConsoleOutputCP above.
|
||||
// There is nothing to reset, since the code page never changes.
|
||||
}
|
||||
|
||||
unsigned int Terminal::GetOutputCodePage() const noexcept
|
||||
{
|
||||
// See above. The code page is always UTF-8.
|
||||
return CP_UTF8;
|
||||
}
|
||||
|
||||
unsigned int Terminal::GetInputCodePage() const noexcept
|
||||
{
|
||||
// See above. The code page is always UTF-8.
|
||||
return CP_UTF8;
|
||||
}
|
||||
|
||||
|
||||
@ -1140,7 +1140,6 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
|
||||
{
|
||||
// Set new code page
|
||||
gci.OutputCP = codepage;
|
||||
|
||||
SetConsoleCPInfo(TRUE);
|
||||
}
|
||||
|
||||
@ -1157,13 +1156,36 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
|
||||
{
|
||||
try
|
||||
{
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
LockConsole();
|
||||
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
||||
return DoSrvSetConsoleOutputCodePage(codepage);
|
||||
RETURN_IF_FAILED(DoSrvSetConsoleOutputCodePage(codepage));
|
||||
// Setting the code page via the API also updates the default value.
|
||||
// This is how the initial code page is set to UTF-8 in a WSL shell.
|
||||
gci.DefaultOutputCP = codepage;
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT DoSrvSetConsoleInputCodePage(const unsigned int codepage)
|
||||
{
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
|
||||
// Return if it's not known as a valid codepage ID.
|
||||
RETURN_HR_IF(E_INVALIDARG, !(IsValidCodePage(codepage)));
|
||||
|
||||
// Do nothing if no change.
|
||||
if (gci.CP != codepage)
|
||||
{
|
||||
// Set new code page
|
||||
gci.CP = codepage;
|
||||
SetConsoleCPInfo(FALSE);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Sets the codepage used for translating text when calling A versions of functions affecting the input buffer.
|
||||
// Arguments:
|
||||
@ -1177,19 +1199,10 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
LockConsole();
|
||||
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
||||
|
||||
// Return if it's not known as a valid codepage ID.
|
||||
RETURN_HR_IF(E_INVALIDARG, !(IsValidCodePage(codepage)));
|
||||
|
||||
// Do nothing if no change.
|
||||
if (gci.CP != codepage)
|
||||
{
|
||||
// Set new code page
|
||||
gci.CP = codepage;
|
||||
|
||||
SetConsoleCPInfo(FALSE);
|
||||
}
|
||||
|
||||
RETURN_IF_FAILED(DoSrvSetConsoleInputCodePage(codepage));
|
||||
// Setting the code page via the API also updates the default value.
|
||||
// This is how the initial code page is set to UTF-8 in a WSL shell.
|
||||
gci.DefaultCP = codepage;
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
@ -19,3 +19,4 @@ Revision History:
|
||||
class SCREEN_INFORMATION;
|
||||
|
||||
[[nodiscard]] HRESULT DoSrvSetConsoleOutputCodePage(const unsigned int codepage);
|
||||
[[nodiscard]] HRESULT DoSrvSetConsoleInputCodePage(const unsigned int codepage);
|
||||
|
||||
@ -35,6 +35,8 @@ using namespace Microsoft::Console::Interactivity;
|
||||
// codepage by console.cpl or shell32. The default codepage is OEMCP.
|
||||
gci.CP = gci.GetCodePage();
|
||||
gci.OutputCP = gci.GetCodePage();
|
||||
gci.DefaultCP = gci.GetCodePage();
|
||||
gci.DefaultOutputCP = gci.GetCodePage();
|
||||
|
||||
gci.Flags |= CONSOLE_USE_PRIVATE_FLAGS;
|
||||
|
||||
|
||||
@ -221,27 +221,52 @@ void ConhostInternalGetSet::ShowWindow(bool showOrHide)
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Connects the SetConsoleOutputCP API call directly into our Driver Message servicing call inside Conhost.exe
|
||||
// - Set the code page used for translating text when calling A versions of I/O functions.
|
||||
// Arguments:
|
||||
// - codepage - the new output codepage of the console.
|
||||
// - codepage - the new code page of the console.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void ConhostInternalGetSet::SetConsoleOutputCP(const unsigned int codepage)
|
||||
void ConhostInternalGetSet::SetCodePage(const unsigned int codepage)
|
||||
{
|
||||
THROW_IF_FAILED(DoSrvSetConsoleOutputCodePage(codepage));
|
||||
LOG_IF_FAILED(DoSrvSetConsoleOutputCodePage(codepage));
|
||||
LOG_IF_FAILED(DoSrvSetConsoleInputCodePage(codepage));
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Gets the codepage used for translating text when calling A versions of functions affecting the output buffer.
|
||||
// - Reset the code pages to their default values.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - the outputCP of the console.
|
||||
unsigned int ConhostInternalGetSet::GetConsoleOutputCP() const
|
||||
// - <none>
|
||||
void ConhostInternalGetSet::ResetCodePage()
|
||||
{
|
||||
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
LOG_IF_FAILED(DoSrvSetConsoleOutputCodePage(gci.DefaultOutputCP));
|
||||
LOG_IF_FAILED(DoSrvSetConsoleInputCodePage(gci.DefaultCP));
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Gets the code page used for translating text when calling A versions of output functions.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - the output code page of the console.
|
||||
unsigned int ConhostInternalGetSet::GetOutputCodePage() const
|
||||
{
|
||||
return ServiceLocator::LocateGlobals().getConsoleInformation().OutputCP;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Gets the code page used for translating text when calling A versions of input functions.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - the input code page of the console.
|
||||
unsigned int ConhostInternalGetSet::GetInputCodePage() const
|
||||
{
|
||||
return ServiceLocator::LocateGlobals().getConsoleInformation().CP;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Copies the given content to the clipboard.
|
||||
// Arguments:
|
||||
|
||||
@ -53,8 +53,10 @@ public:
|
||||
|
||||
bool ResizeWindow(const til::CoordType width, const til::CoordType height) override;
|
||||
|
||||
void SetConsoleOutputCP(const unsigned int codepage) override;
|
||||
unsigned int GetConsoleOutputCP() const override;
|
||||
void SetCodePage(const unsigned int codepage) override;
|
||||
void ResetCodePage() override;
|
||||
unsigned int GetOutputCodePage() const override;
|
||||
unsigned int GetInputCodePage() const override;
|
||||
|
||||
void CopyToClipboard(const wil::zwstring_view content) override;
|
||||
void SetTaskbarProgress(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::TaskbarState state, const size_t progress) override;
|
||||
|
||||
@ -90,6 +90,9 @@ public:
|
||||
// the following fields are used for ansi-unicode translation
|
||||
UINT CP = 0;
|
||||
UINT OutputCP = 0;
|
||||
// the VT RIS sequence uses these default values to reset the code pages
|
||||
UINT DefaultCP = 0;
|
||||
UINT DefaultOutputCP = 0;
|
||||
|
||||
ULONG CtrlFlags = 0; // indicates outstanding ctrl requests
|
||||
ULONG LimitingProcessId = 0;
|
||||
|
||||
@ -122,6 +122,7 @@ public:
|
||||
virtual void LockingShiftRight(const VTInt gsetNumber) = 0; // LS1R, LS2R, LS3R
|
||||
virtual void SingleShift(const VTInt gsetNumber) = 0; // SS2, SS3
|
||||
virtual void AcceptC1Controls(const bool enabled) = 0; // DECAC1
|
||||
virtual void SendC1Controls(const bool enabled) = 0; // S8C1T, S7C1T
|
||||
virtual void AnnounceCodeStructure(const VTInt ansiLevel) = 0; // ACS
|
||||
|
||||
virtual void SoftReset() = 0; // DECSTR
|
||||
|
||||
@ -72,8 +72,10 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
|
||||
virtual void ShowWindow(bool showOrHide) = 0;
|
||||
|
||||
virtual void SetConsoleOutputCP(const unsigned int codepage) = 0;
|
||||
virtual unsigned int GetConsoleOutputCP() const = 0;
|
||||
virtual void SetCodePage(const unsigned int codepage) = 0;
|
||||
virtual void ResetCodePage() = 0;
|
||||
virtual unsigned int GetOutputCodePage() const = 0;
|
||||
virtual unsigned int GetInputCodePage() const = 0;
|
||||
|
||||
virtual void CopyToClipboard(const wil::zwstring_view content) = 0;
|
||||
virtual void SetTaskbarProgress(const DispatchTypes::TaskbarState state, const size_t progress) = 0;
|
||||
|
||||
@ -62,7 +62,7 @@ void InteractDispatch::WriteString(const std::wstring_view string)
|
||||
{
|
||||
if (!string.empty())
|
||||
{
|
||||
const auto codepage = _api.GetConsoleOutputCP();
|
||||
const auto codepage = _api.GetOutputCodePage();
|
||||
InputEventQueue keyEvents;
|
||||
|
||||
for (const auto& wch : string)
|
||||
|
||||
@ -1243,7 +1243,7 @@ void AdaptDispatch::FillRectangularArea(const VTParameter ch, const VTInt top, c
|
||||
const auto charValue = ch.value_or(0) == 0 ? 32 : ch.value();
|
||||
const auto glChar = (charValue >= 32 && charValue <= 126);
|
||||
const auto grChar = (charValue >= 160 && charValue <= 255);
|
||||
const auto unicodeChar = (charValue >= 256 && charValue <= 65535 && _api.GetConsoleOutputCP() == CP_UTF8);
|
||||
const auto unicodeChar = (charValue >= 256 && charValue <= 65535 && _api.GetOutputCodePage() == CP_UTF8);
|
||||
if (glChar || grChar || unicodeChar)
|
||||
{
|
||||
const auto fillChar = _termOutput.TranslateKey(gsl::narrow_cast<wchar_t>(charValue));
|
||||
@ -1377,8 +1377,7 @@ void AdaptDispatch::RequestChecksumRectangularArea(const VTInt id, const VTInt p
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto response = wil::str_printf<std::wstring>(L"\033P%d!~%04X\033\\", id, checksum);
|
||||
_api.ReturnResponse(response);
|
||||
_ReturnDcsResponse(wil::str_printf<std::wstring>(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()
|
||||
// - <none>
|
||||
void AdaptDispatch::SecondaryDeviceAttributes()
|
||||
{
|
||||
_api.ReturnResponse(L"\x1b[>0;10;1c");
|
||||
_ReturnCsiResponse(L">0;10;1c");
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@ -1506,7 +1505,7 @@ void AdaptDispatch::SecondaryDeviceAttributes()
|
||||
// - <none>
|
||||
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
|
||||
// - <none>
|
||||
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<std::wstring>(L"\x1b[?%d;%d;%dR", cursorPosition.y, cursorPosition.x, pageNumber);
|
||||
_api.ReturnResponse(response);
|
||||
_ReturnCsiResponse(wil::str_printf<std::wstring>(L"?%d;%d;%dR", cursorPosition.y, cursorPosition.x, pageNumber));
|
||||
}
|
||||
else
|
||||
{
|
||||
// The standard report only returns the cursor position.
|
||||
const auto response = wil::str_printf<std::wstring>(L"\x1b[%d;%dR", cursorPosition.y, cursorPosition.x);
|
||||
_api.ReturnResponse(response);
|
||||
_ReturnCsiResponse(wil::str_printf<std::wstring>(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<std::wstring>(L"\x1b[%zu*{", spaceInBytes / 16);
|
||||
_api.ReturnResponse(response);
|
||||
_ReturnCsiResponse(wil::str_printf<std::wstring>(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<std::wstring>(L"\033P%d!~%04X\033\\", requestId, checksum);
|
||||
_api.ReturnResponse(response);
|
||||
_ReturnDcsResponse(wil::str_printf<std::wstring>(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)
|
||||
@ -2785,22 +2780,15 @@ void AdaptDispatch::_InitTabStopsForWidth(const VTInt width)
|
||||
// - codingSystem - The coding system that will be selected.
|
||||
void AdaptDispatch::DesignateCodingSystem(const VTID codingSystem)
|
||||
{
|
||||
// If we haven't previously saved the initial code page, do so now.
|
||||
// This will be used to restore the code page in response to a reset.
|
||||
if (!_initialCodePage.has_value())
|
||||
{
|
||||
_initialCodePage = _api.GetConsoleOutputCP();
|
||||
}
|
||||
|
||||
switch (codingSystem)
|
||||
{
|
||||
case DispatchTypes::CodingSystem::ISO2022:
|
||||
_api.SetConsoleOutputCP(28591);
|
||||
_api.SetCodePage(28591);
|
||||
AcceptC1Controls(true);
|
||||
_termOutput.EnableGrTranslation(true);
|
||||
break;
|
||||
case DispatchTypes::CodingSystem::UTF8:
|
||||
_api.SetConsoleOutputCP(CP_UTF8);
|
||||
_api.SetCodePage(CP_UTF8);
|
||||
AcceptC1Controls(false);
|
||||
_termOutput.EnableGrTranslation(false);
|
||||
break;
|
||||
@ -2871,6 +2859,24 @@ void AdaptDispatch::AcceptC1Controls(const bool enabled)
|
||||
_api.GetStateMachine().SetParserMode(StateMachine::Mode::AcceptC1, enabled);
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
// S8C1T/S7C1T - Enable or disable the sending of C1 controls in key sequences
|
||||
// and query responses. When this is enabled, C1 controls are sent as a single
|
||||
// codepoint. When disabled, they're sent as a two character escape sequence.
|
||||
//Arguments:
|
||||
// - enabled - true to send C1 controls, false to send escape sequences.
|
||||
void AdaptDispatch::SendC1Controls(const bool enabled)
|
||||
{
|
||||
// If this is an attempt to enable C1 controls, the input code page must be
|
||||
// one of the DOCS choices (UTF-8 or ISO-8859-1), otherwise there's a risk
|
||||
// that those controls won't have a valid encoding.
|
||||
const auto codepage = _api.GetInputCodePage();
|
||||
if (enabled == false || codepage == CP_UTF8 || codepage == 28591)
|
||||
{
|
||||
_terminalInput.SetInputMode(TerminalInput::Mode::SendC1, enabled);
|
||||
}
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
// ACS - Announces the ANSI conformance level for subsequent data exchange.
|
||||
// This requires certain character sets to be mapped into the terminal's
|
||||
@ -3003,13 +3009,11 @@ void AdaptDispatch::HardReset()
|
||||
|
||||
// Completely reset the TerminalOutput state.
|
||||
_termOutput = {};
|
||||
if (_initialCodePage.has_value())
|
||||
{
|
||||
// Restore initial code page if previously changed by a DOCS sequence.
|
||||
_api.SetConsoleOutputCP(_initialCodePage.value());
|
||||
}
|
||||
// Disable parsing of C1 control codes.
|
||||
// Reset the code page to the default value.
|
||||
_api.ResetCodePage();
|
||||
// Disable parsing and sending of C1 control codes.
|
||||
AcceptC1Controls(false);
|
||||
SendC1Controls(false);
|
||||
|
||||
// Sets the SGR state to normal - this must be done before EraseInDisplay
|
||||
// to ensure that it clears with the default background color.
|
||||
@ -3276,7 +3280,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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -3321,7 +3325,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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3377,7 +3381,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)
|
||||
@ -3844,7 +3848,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:
|
||||
@ -3976,9 +3980,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<wchar_t, TextColor::TABLE_SIZE * 18> response;
|
||||
response.append(L"\033P2$s"sv);
|
||||
response.append(L"2$s"sv);
|
||||
|
||||
const auto modelNumber = static_cast<int>(colorModel);
|
||||
for (size_t colorNumber = 0; colorNumber < TextColor::TABLE_SIZE; colorNumber++)
|
||||
@ -4003,9 +4007,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:
|
||||
@ -4108,7 +4110,7 @@ ITermDispatch::StringHandler AdaptDispatch::RequestSetting()
|
||||
_ReportDECACSetting(VTParameter{ parameter });
|
||||
break;
|
||||
default:
|
||||
_api.ReturnResponse(L"\033P0$r\033\\");
|
||||
_ReturnDcsResponse(L"0$r");
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
@ -4147,10 +4149,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<wchar_t, 64> response;
|
||||
response.append(L"\033P1$r0"sv);
|
||||
response.append(L"1$r0"sv);
|
||||
|
||||
const auto& attr = _pages.ActivePage().Attributes();
|
||||
const auto ulStyle = attr.GetUnderlineStyle();
|
||||
@ -4202,9 +4204,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:
|
||||
@ -4217,10 +4219,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:
|
||||
@ -4233,10 +4234,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:
|
||||
@ -4249,26 +4249,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;
|
||||
}
|
||||
}
|
||||
@ -4282,10 +4282,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:
|
||||
@ -4297,10 +4297,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:
|
||||
@ -4324,12 +4324,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:
|
||||
@ -4442,9 +4441,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(),
|
||||
@ -4458,7 +4457,7 @@ void AdaptDispatch::_ReportCursorInformation()
|
||||
charset1,
|
||||
charset2,
|
||||
charset3);
|
||||
_api.ReturnResponse({ response.data(), response.size() });
|
||||
_ReturnDcsResponse({ response.data(), response.size() });
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@ -4623,9 +4622,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<wchar_t, 64> response;
|
||||
response.append(L"\033P2$u"sv);
|
||||
response.append(L"2$u"sv);
|
||||
|
||||
auto need_separator = false;
|
||||
for (auto column = 0; column < width; column++)
|
||||
@ -4638,9 +4637,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:
|
||||
@ -4685,6 +4682,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:
|
||||
|
||||
@ -121,6 +121,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
void LockingShiftRight(const VTInt gsetNumber) override; // LS1R, LS2R, LS3R
|
||||
void SingleShift(const VTInt gsetNumber) noexcept override; // SS2, SS3
|
||||
void AcceptC1Controls(const bool enabled) override; // DECAC1
|
||||
void SendC1Controls(const bool enabled) override; // S8C1T, S7C1T
|
||||
void AnnounceCodeStructure(const VTInt ansiLevel) override; // ACS
|
||||
void SoftReset() override; // DECSTR
|
||||
void HardReset() override; // RIS
|
||||
@ -289,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<uint8_t> _tabStopColumns;
|
||||
bool _initDefaultTabStops = true;
|
||||
|
||||
@ -302,7 +307,6 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
std::shared_ptr<SixelParser> _sixelParser;
|
||||
std::unique_ptr<FontBuffer> _fontBuffer;
|
||||
std::shared_ptr<MacroBuffer> _macroBuffer;
|
||||
std::optional<unsigned int> _initialCodePage;
|
||||
|
||||
// We have two instances of the saved cursor state, because we need
|
||||
// one for the main buffer (at index 0), and another for the alt buffer
|
||||
|
||||
@ -115,6 +115,7 @@ public:
|
||||
void LockingShiftRight(const VTInt /*gsetNumber*/) override {} // LS1R, LS2R, LS3R
|
||||
void SingleShift(const VTInt /*gsetNumber*/) override {} // SS2, SS3
|
||||
void AcceptC1Controls(const bool /*enabled*/) override {} // DECAC1
|
||||
void SendC1Controls(const bool /*enabled*/) override {} // S8C1T, S7C1T
|
||||
void AnnounceCodeStructure(const VTInt /*ansiLevel*/) override {} // ACS
|
||||
|
||||
void SoftReset() override {} // DECSTR
|
||||
|
||||
@ -158,17 +158,28 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
void SetConsoleOutputCP(const unsigned int codepage) override
|
||||
void SetCodePage(const unsigned int codepage) override
|
||||
{
|
||||
Log::Comment(L"SetConsoleOutputCP MOCK called...");
|
||||
THROW_HR_IF(E_FAIL, !_setConsoleOutputCPResult);
|
||||
VERIFY_ARE_EQUAL(_expectedOutputCP, codepage);
|
||||
Log::Comment(L"SetCodePage MOCK called...");
|
||||
THROW_HR_IF(E_FAIL, !_setCodePageResult);
|
||||
VERIFY_ARE_EQUAL(_expectedCodePage, codepage);
|
||||
}
|
||||
|
||||
unsigned int GetConsoleOutputCP() const override
|
||||
void ResetCodePage() override
|
||||
{
|
||||
Log::Comment(L"GetConsoleOutputCP MOCK called...");
|
||||
return _expectedOutputCP;
|
||||
Log::Comment(L"ResetCodePage MOCK called...");
|
||||
}
|
||||
|
||||
unsigned int GetOutputCodePage() const override
|
||||
{
|
||||
Log::Comment(L"GetOutputCodePage MOCK called...");
|
||||
return _expectedCodePage;
|
||||
}
|
||||
|
||||
unsigned int GetInputCodePage() const override
|
||||
{
|
||||
Log::Comment(L"GetInputCodePage MOCK called...");
|
||||
return _expectedCodePage;
|
||||
}
|
||||
|
||||
void CopyToClipboard(const wil::zwstring_view /*content*/)
|
||||
@ -367,7 +378,7 @@ public:
|
||||
til::point _expectedCursorPos;
|
||||
|
||||
TextAttribute _expectedAttribute = {};
|
||||
unsigned int _expectedOutputCP = 0;
|
||||
unsigned int _expectedCodePage = 0;
|
||||
bool _isPty = false;
|
||||
|
||||
bool _returnResponseResult = false;
|
||||
@ -376,8 +387,7 @@ public:
|
||||
|
||||
bool _setWindowTitleResult = false;
|
||||
std::wstring_view _expectedWindowTitle{};
|
||||
bool _setConsoleOutputCPResult = false;
|
||||
bool _getConsoleOutputCPResult = false;
|
||||
bool _setCodePageResult = false;
|
||||
bool _expectedShowWindow = false;
|
||||
|
||||
std::wstring _expectedMenuJson{};
|
||||
@ -3529,15 +3539,15 @@ public:
|
||||
|
||||
Log::Comment(L"3. Designate ISO-2022 coding system");
|
||||
// Code page should be set to ISO-8859-1 and C1 parsing enabled
|
||||
_testGetSet->_setConsoleOutputCPResult = true;
|
||||
_testGetSet->_expectedOutputCP = 28591;
|
||||
_testGetSet->_setCodePageResult = true;
|
||||
_testGetSet->_expectedCodePage = 28591;
|
||||
_pDispatch->DesignateCodingSystem(DispatchTypes::CodingSystem::ISO2022);
|
||||
VERIFY_IS_TRUE(_stateMachine->GetParserMode(StateMachine::Mode::AcceptC1));
|
||||
|
||||
Log::Comment(L"4. Designate UTF-8 coding system");
|
||||
// Code page should be set to UTF-8 and C1 parsing disabled
|
||||
_testGetSet->_setConsoleOutputCPResult = true;
|
||||
_testGetSet->_expectedOutputCP = CP_UTF8;
|
||||
_testGetSet->_setCodePageResult = true;
|
||||
_testGetSet->_expectedCodePage = CP_UTF8;
|
||||
_pDispatch->DesignateCodingSystem(DispatchTypes::CodingSystem::UTF8);
|
||||
VERIFY_IS_FALSE(_stateMachine->GetParserMode(StateMachine::Mode::AcceptC1));
|
||||
}
|
||||
@ -3962,6 +3972,39 @@ public:
|
||||
_pDispatch->PagePositionAbsolute(1);
|
||||
}
|
||||
|
||||
TEST_METHOD(SendC1ControlTest)
|
||||
{
|
||||
const auto S7C1T = L"\033 F";
|
||||
const auto S8C1T = L"\033 G";
|
||||
|
||||
_testGetSet->PrepData();
|
||||
_testGetSet->_expectedCodePage = CP_UTF8;
|
||||
|
||||
Log::Comment(L"Generating reports with C1 control sequences");
|
||||
_stateMachine->ProcessString(S8C1T);
|
||||
|
||||
_pDispatch->SecondaryDeviceAttributes();
|
||||
_testGetSet->ValidateInputEvent(L"\x9b>0;10;1c");
|
||||
|
||||
_pDispatch->TertiaryDeviceAttributes();
|
||||
_testGetSet->ValidateInputEvent(L"\x90!|00000000\x9c");
|
||||
|
||||
_pDispatch->RequestColorTableEntry(0);
|
||||
_testGetSet->ValidateInputEvent(L"\x9d\x34;0;rgb:0c0c/0c0c/0c0c\x9c");
|
||||
|
||||
Log::Comment(L"Generating reports with 7-bit escape sequence");
|
||||
_stateMachine->ProcessString(S7C1T);
|
||||
|
||||
_pDispatch->SecondaryDeviceAttributes();
|
||||
_testGetSet->ValidateInputEvent(L"\x1b[>0;10;1c");
|
||||
|
||||
_pDispatch->TertiaryDeviceAttributes();
|
||||
_testGetSet->ValidateInputEvent(L"\x1bP!|00000000\x1b\\");
|
||||
|
||||
_pDispatch->RequestColorTableEntry(0);
|
||||
_testGetSet->ValidateInputEvent(L"\x1b]4;0;rgb:0c0c/0c0c/0c0c\x1b\\");
|
||||
}
|
||||
|
||||
private:
|
||||
TerminalInput _terminalInput;
|
||||
std::unique_ptr<TestGetSet> _testGetSet;
|
||||
|
||||
@ -36,6 +36,7 @@ public:
|
||||
TEST_METHOD(CtrlNumTest);
|
||||
TEST_METHOD(BackarrowKeyModeTest);
|
||||
TEST_METHOD(AutoRepeatModeTest);
|
||||
TEST_METHOD(SendC1ControlTest);
|
||||
|
||||
wchar_t GetModifierChar(const bool fShift, const bool fAlt, const bool fCtrl)
|
||||
{
|
||||
@ -790,3 +791,20 @@ void InputTest::AutoRepeatModeTest()
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"A"), input.HandleKey(down));
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput({}), input.HandleKey(up));
|
||||
}
|
||||
|
||||
void InputTest::SendC1ControlTest()
|
||||
{
|
||||
TerminalInput input;
|
||||
|
||||
Log::Comment(L"Sending keys with C1 control sequences");
|
||||
input.SetInputMode(TerminalInput::Mode::SendC1, true);
|
||||
|
||||
TestKey(TerminalInput::MakeOutput(L"\x9bH"), input, 0, VK_HOME);
|
||||
TestKey(TerminalInput::MakeOutput(L"\x8fP"), input, 0, VK_F1);
|
||||
|
||||
Log::Comment(L"Sending keys with 7-bit escape sequence");
|
||||
input.SetInputMode(TerminalInput::Mode::SendC1, false);
|
||||
|
||||
TestKey(TerminalInput::MakeOutput(L"\x1b[H"), input, 0, VK_HOME);
|
||||
TestKey(TerminalInput::MakeOutput(L"\x1bOP"), input, 0, VK_F1);
|
||||
}
|
||||
|
||||
@ -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>{ Mode::LineFeed, Mode::Ansi, Mode::Keypad, Mode::CursorKey, Mode::BackarrowKey };
|
||||
static constexpr auto keyMapModes = til::enumset<Mode>{ 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);
|
||||
|
||||
@ -33,6 +33,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
CursorKey,
|
||||
BackarrowKey,
|
||||
Win32,
|
||||
SendC1,
|
||||
|
||||
Utf8MouseEncoding,
|
||||
SgrMouseEncoding,
|
||||
@ -80,10 +81,8 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
til::enumset<Mode> _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);
|
||||
@ -108,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;
|
||||
|
||||
|
||||
@ -256,6 +256,12 @@ bool OutputStateMachineEngine::ActionEscDispatch(const VTID id)
|
||||
case EscActionCodes::DECAC1_AcceptC1Controls:
|
||||
_dispatch->AcceptC1Controls(true);
|
||||
break;
|
||||
case EscActionCodes::S7C1T_Send7bitC1Controls:
|
||||
_dispatch->SendC1Controls(false);
|
||||
break;
|
||||
case EscActionCodes::S8C1T_Send8bitC1Controls:
|
||||
_dispatch->SendC1Controls(true);
|
||||
break;
|
||||
case EscActionCodes::ACS_AnsiLevel1:
|
||||
_dispatch->AnnounceCodeStructure(1);
|
||||
break;
|
||||
|
||||
@ -77,6 +77,8 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
LS2R_LockingShift = VTID("}"),
|
||||
LS3R_LockingShift = VTID("|"),
|
||||
DECAC1_AcceptC1Controls = VTID(" 7"),
|
||||
S7C1T_Send7bitC1Controls = VTID(" F"),
|
||||
S8C1T_Send8bitC1Controls = VTID(" G"),
|
||||
ACS_AnsiLevel1 = VTID(" L"),
|
||||
ACS_AnsiLevel2 = VTID(" M"),
|
||||
ACS_AnsiLevel3 = VTID(" N"),
|
||||
|
||||
@ -25,6 +25,9 @@ StateMachine::StateMachine(std::unique_ptr<IStateMachineEngine> 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();
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user