6742965bb8
There are some code pages with "unmapped" code points in the C1 range, which results in them being translated into Unicode C1 control codes, even though that is not their intended use. To avoid having these characters triggering unintentional escape sequences, this PR now disables C1 controls by default. Switching to ISO-2022 encoding will re-enable them, though, since that is the most likely scenario in which they would be required. They can also be explicitly enabled, even in UTF-8 mode, with the `DECAC1` escape sequence. What I've done is add a new mode to the `StateMachine` class that controls whether C1 code points are interpreted as control characters or not. When disabled, these code points are simply dropped from the output, similar to the way a `NUL` is interpreted. This isn't exactly the way they were handled in the v1 console (which I think replaces them with the font _notdef_ glyph), but it matches the XTerm behavior, which seems more appropriate considering this is in VT mode. And it's worth noting that Windows Explorer seems to work the same way. As mentioned above, the mode can be enabled by designating the ISO-2022 coding system with a `DOCS` sequence, and it will be disabled again when UTF-8 is designated. You can also enable it explicitly with a `DECAC1` sequence (originally this was actually a DEC printer sequence, but it doesn't seem unreasonable to use it in a terminal). I've also extended the operations that save and restore "cursor state" (e.g. `DECSC` and `DECRC`) to include the state of the C1 parser mode, since it's closely tied to the code page and character sets which are also saved there. Similarly, when a `DECSTR` sequence resets the code page and character sets, I've now made it reset the C1 mode as well. I should note that the new `StateMachine` mode is controlled via a generic `SetParserMode` method (with a matching API in the `ConGetSet` interface) to allow for easier addition of other modes in the future. And I've reimplemented the existing ANSI/VT52 mode in terms of these generic methods instead of it having to have its own separate APIs. ## Validation Steps Performed Some of the unit tests for OSC sequences were using a C1 `0x9C` for the string terminator, which doesn't work by default anymore. Since that's not a good practice anyway, I thought it best to change those to a standard 7-bit terminator. However, in tests that were explicitly validating the C1 controls, I've just enabled the C1 parser mode at the start of the tests in order to get them working again. There were also some ANSI mode adapter tests that had to be updated to account for the fact that it has now been reimplemented in terms of the `SetParserMode` API. I've added a new state machine test to validate the changes in behavior when the C1 parser mode is enabled or disabled. And I've added an adapter test to verify that the `DesignateCodingSystems` and `AcceptC1Controls` methods toggle the C1 parser mode as expected. I've manually verified the test cases in #10069 and #10310 to confirm that they're no longer triggering control sequences by default. Although, as I explained above, the C1 code points are completely dropped from the output rather than displayed as _notdef_ glyphs. I think this is a reasonable compromise though. Closes #10069 Closes #10310
213 lines
8.2 KiB
C++
213 lines
8.2 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
/*
|
|
Module Name:
|
|
- OutputStateMachineEngine.hpp
|
|
|
|
Abstract:
|
|
- This is the implementation of the client VT output state machine engine.
|
|
*/
|
|
#pragma once
|
|
|
|
#include <functional>
|
|
|
|
#include "../adapter/termDispatch.hpp"
|
|
#include "telemetry.hpp"
|
|
#include "IStateMachineEngine.hpp"
|
|
#include "../../inc/ITerminalOutputConnection.hpp"
|
|
|
|
namespace Microsoft::Console::VirtualTerminal
|
|
{
|
|
class OutputStateMachineEngine : public IStateMachineEngine
|
|
{
|
|
public:
|
|
OutputStateMachineEngine(std::unique_ptr<ITermDispatch> pDispatch);
|
|
|
|
bool ActionExecute(const wchar_t wch) override;
|
|
bool ActionExecuteFromEscape(const wchar_t wch) override;
|
|
|
|
bool ActionPrint(const wchar_t wch) override;
|
|
|
|
bool ActionPrintString(const std::wstring_view string) override;
|
|
|
|
bool ActionPassThroughString(const std::wstring_view string) override;
|
|
|
|
bool ActionEscDispatch(const VTID id) override;
|
|
|
|
bool ActionVt52EscDispatch(const VTID id, const VTParameters parameters) override;
|
|
|
|
bool ActionCsiDispatch(const VTID id, const VTParameters parameters) override;
|
|
|
|
StringHandler ActionDcsDispatch(const VTID id, const VTParameters parameters) override;
|
|
|
|
bool ActionClear() noexcept override;
|
|
|
|
bool ActionIgnore() noexcept override;
|
|
|
|
bool ActionOscDispatch(const wchar_t wch,
|
|
const size_t parameter,
|
|
const std::wstring_view string) override;
|
|
|
|
bool ActionSs3Dispatch(const wchar_t wch, const VTParameters parameters) noexcept override;
|
|
|
|
bool ParseControlSequenceAfterSs3() const noexcept override;
|
|
bool FlushAtEndOfString() const noexcept override;
|
|
bool DispatchControlCharsFromEscape() const noexcept override;
|
|
bool DispatchIntermediatesFromEscape() const noexcept override;
|
|
|
|
void SetTerminalConnection(Microsoft::Console::ITerminalOutputConnection* const pTtyConnection,
|
|
std::function<bool()> pfnFlushToTerminal);
|
|
|
|
const ITermDispatch& Dispatch() const noexcept;
|
|
ITermDispatch& Dispatch() noexcept;
|
|
|
|
private:
|
|
std::unique_ptr<ITermDispatch> _dispatch;
|
|
Microsoft::Console::ITerminalOutputConnection* _pTtyConnection;
|
|
std::function<bool()> _pfnFlushToTerminal;
|
|
wchar_t _lastPrintedChar;
|
|
|
|
enum EscActionCodes : uint64_t
|
|
{
|
|
DECSC_CursorSave = VTID("7"),
|
|
DECRC_CursorRestore = VTID("8"),
|
|
DECKPAM_KeypadApplicationMode = VTID("="),
|
|
DECKPNM_KeypadNumericMode = VTID(">"),
|
|
IND_Index = VTID("D"),
|
|
NEL_NextLine = VTID("E"),
|
|
HTS_HorizontalTabSet = VTID("H"),
|
|
RI_ReverseLineFeed = VTID("M"),
|
|
SS2_SingleShift = VTID("N"),
|
|
SS3_SingleShift = VTID("O"),
|
|
DECID_IdentifyDevice = VTID("Z"),
|
|
ST_StringTerminator = VTID("\\"),
|
|
RIS_ResetToInitialState = VTID("c"),
|
|
LS2_LockingShift = VTID("n"),
|
|
LS3_LockingShift = VTID("o"),
|
|
LS1R_LockingShift = VTID("~"),
|
|
LS2R_LockingShift = VTID("}"),
|
|
LS3R_LockingShift = VTID("|"),
|
|
DECAC1_AcceptC1Controls = VTID(" 7"),
|
|
DECDHL_DoubleHeightLineTop = VTID("#3"),
|
|
DECDHL_DoubleHeightLineBottom = VTID("#4"),
|
|
DECSWL_SingleWidthLine = VTID("#5"),
|
|
DECDWL_DoubleWidthLine = VTID("#6"),
|
|
DECALN_ScreenAlignmentPattern = VTID("#8")
|
|
};
|
|
|
|
enum CsiActionCodes : uint64_t
|
|
{
|
|
ICH_InsertCharacter = VTID("@"),
|
|
CUU_CursorUp = VTID("A"),
|
|
CUD_CursorDown = VTID("B"),
|
|
CUF_CursorForward = VTID("C"),
|
|
CUB_CursorBackward = VTID("D"),
|
|
CNL_CursorNextLine = VTID("E"),
|
|
CPL_CursorPrevLine = VTID("F"),
|
|
CHA_CursorHorizontalAbsolute = VTID("G"),
|
|
CUP_CursorPosition = VTID("H"),
|
|
CHT_CursorForwardTab = VTID("I"),
|
|
ED_EraseDisplay = VTID("J"),
|
|
EL_EraseLine = VTID("K"),
|
|
IL_InsertLine = VTID("L"),
|
|
DL_DeleteLine = VTID("M"),
|
|
DCH_DeleteCharacter = VTID("P"),
|
|
SU_ScrollUp = VTID("S"),
|
|
SD_ScrollDown = VTID("T"),
|
|
ECH_EraseCharacters = VTID("X"),
|
|
CBT_CursorBackTab = VTID("Z"),
|
|
HPA_HorizontalPositionAbsolute = VTID("`"),
|
|
HPR_HorizontalPositionRelative = VTID("a"),
|
|
REP_RepeatCharacter = VTID("b"),
|
|
DA_DeviceAttributes = VTID("c"),
|
|
DA2_SecondaryDeviceAttributes = VTID(">c"),
|
|
DA3_TertiaryDeviceAttributes = VTID("=c"),
|
|
VPA_VerticalLinePositionAbsolute = VTID("d"),
|
|
VPR_VerticalPositionRelative = VTID("e"),
|
|
HVP_HorizontalVerticalPosition = VTID("f"),
|
|
TBC_TabClear = VTID("g"),
|
|
DECSET_PrivateModeSet = VTID("?h"),
|
|
DECRST_PrivateModeReset = VTID("?l"),
|
|
SGR_SetGraphicsRendition = VTID("m"),
|
|
DSR_DeviceStatusReport = VTID("n"),
|
|
DECSTBM_SetScrollingRegion = VTID("r"),
|
|
ANSISYSSC_CursorSave = VTID("s"), // NOTE: Overlaps with DECLRMM/DECSLRM. Fix when/if implemented.
|
|
DTTERM_WindowManipulation = VTID("t"), // NOTE: Overlaps with DECSLPP. Fix when/if implemented.
|
|
ANSISYSRC_CursorRestore = VTID("u"),
|
|
DECREQTPARM_RequestTerminalParameters = VTID("x"),
|
|
DECSCUSR_SetCursorStyle = VTID(" q"),
|
|
DECSTR_SoftReset = VTID("!p"),
|
|
XT_PushSgrAlias = VTID("#p"),
|
|
XT_PopSgrAlias = VTID("#q"),
|
|
XT_PushSgr = VTID("#{"),
|
|
XT_PopSgr = VTID("#}"),
|
|
DECSCPP_SetColumnsPerPage = VTID("$|"),
|
|
};
|
|
|
|
enum DcsActionCodes : uint64_t
|
|
{
|
|
DECDLD_DownloadDRCS = VTID("{"),
|
|
DECRQSS_RequestSetting = VTID("$q")
|
|
};
|
|
|
|
enum Vt52ActionCodes : uint64_t
|
|
{
|
|
CursorUp = VTID("A"),
|
|
CursorDown = VTID("B"),
|
|
CursorRight = VTID("C"),
|
|
CursorLeft = VTID("D"),
|
|
EnterGraphicsMode = VTID("F"),
|
|
ExitGraphicsMode = VTID("G"),
|
|
CursorToHome = VTID("H"),
|
|
ReverseLineFeed = VTID("I"),
|
|
EraseToEndOfScreen = VTID("J"),
|
|
EraseToEndOfLine = VTID("K"),
|
|
DirectCursorAddress = VTID("Y"),
|
|
Identify = VTID("Z"),
|
|
EnterAlternateKeypadMode = VTID("="),
|
|
ExitAlternateKeypadMode = VTID(">"),
|
|
ExitVt52Mode = VTID("<")
|
|
};
|
|
|
|
enum OscActionCodes : unsigned int
|
|
{
|
|
SetIconAndWindowTitle = 0,
|
|
SetWindowIcon = 1,
|
|
SetWindowTitle = 2,
|
|
SetWindowProperty = 3, // Not implemented
|
|
SetColor = 4,
|
|
Hyperlink = 8,
|
|
ConEmuAction = 9,
|
|
SetForegroundColor = 10,
|
|
SetBackgroundColor = 11,
|
|
SetCursorColor = 12,
|
|
SetClipboard = 52,
|
|
ResetForegroundColor = 110, // Not implemented
|
|
ResetBackgroundColor = 111, // Not implemented
|
|
ResetCursorColor = 112
|
|
};
|
|
|
|
bool _GetOscTitle(const std::wstring_view string,
|
|
std::wstring& title) const;
|
|
|
|
bool _GetOscSetColorTable(const std::wstring_view string,
|
|
std::vector<size_t>& tableIndexes,
|
|
std::vector<DWORD>& rgbs) const noexcept;
|
|
|
|
bool _GetOscSetColor(const std::wstring_view string,
|
|
std::vector<DWORD>& rgbs) const noexcept;
|
|
|
|
bool _GetOscSetClipboard(const std::wstring_view string,
|
|
std::wstring& content,
|
|
bool& queryClipboard) const noexcept;
|
|
|
|
static constexpr std::wstring_view hyperlinkIDParameter{ L"id=" };
|
|
bool _ParseHyperlink(const std::wstring_view string,
|
|
std::wstring& params,
|
|
std::wstring& uri) const;
|
|
|
|
void _ClearLastChar() noexcept;
|
|
};
|
|
}
|