terminal/src/terminal/parser/telemetry.cpp

290 lines
14 KiB
C++
Raw Normal View History

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include <precomp.h>
#include "telemetry.hpp"
#pragma warning(push)
#pragma warning(disable : 26494) // _Tlgdata uninitialized from TraceLoggingWrite
#pragma warning(disable : 26477) // Use nullptr instead of NULL or 0 from TraceLoggingWrite
#pragma warning(disable : 26485) // _Tlgdata, no array to pointer decay from TraceLoggingWrite
#pragma warning(disable : 26446) // Prefer gsl::at over unchecked subscript from TraceLoggingLevel
#pragma warning(disable : 26482) // Only index to arrays with constant expressions from TraceLoggingLevel
TRACELOGGING_DEFINE_PROVIDER(g_hConsoleVirtTermParserEventTraceProvider,
"Microsoft.Windows.Console.VirtualTerminal.Parser",
// {c9ba2a84-d3ca-5e19-2bd6-776a0910cb9d}
(0xc9ba2a84, 0xd3ca, 0x5e19, 0x2b, 0xd6, 0x77, 0x6a, 0x09, 0x10, 0xcb, 0x9d));
using namespace Microsoft::Console::VirtualTerminal;
#pragma warning(push)
// Disable 4351 so we can initialize the arrays to 0 without a warning.
#pragma warning(disable : 4351)
TermTelemetry::TermTelemetry() noexcept :
_uiTimesUsedCurrent(0),
_uiTimesFailedCurrent(0),
_uiTimesFailedOutsideRangeCurrent(0),
_uiTimesUsed(),
_uiTimesFailed(),
_uiTimesFailedOutsideRange(0),
_activityId(),
_fShouldWriteFinalLog(false)
{
TraceLoggingRegister(g_hConsoleVirtTermParserEventTraceProvider);
// Create a random activityId just in case it doesn't get set later in SetActivityId().
EventActivityIdControl(EVENT_ACTIVITY_CTRL_CREATE_ID, &_activityId);
}
#pragma warning(pop)
TermTelemetry::~TermTelemetry()
{
try
{
WriteFinalTraceLog();
TraceLoggingUnregister(g_hConsoleVirtTermParserEventTraceProvider);
}
CATCH_LOG()
}
// Routine Description:
// - Logs the usage of a particular VT100 code.
//
// Arguments:
// - code - VT100 code.
// Return Value:
// - <none>
void TermTelemetry::Log(const Codes code) noexcept
{
// Initially we wanted to pass over a string (ex. "CUU") and use a dictionary data type to hold the counts.
// However we would have to search through the dictionary every time we called this method, so we decided
// to use an array which has very quick access times.
// The downside is we have to create an enum type, and then convert them to strings when we finally
// send out the telemetry, but the upside is we should have very good performance.
_uiTimesUsed[code]++;
_uiTimesUsedCurrent++;
}
// Routine Description:
// - Logs a particular VT100 escape code failed or was unsupported.
//
// Arguments:
// - code - VT100 code.
// Return Value:
// - <none>
void TermTelemetry::LogFailed(const wchar_t wch) noexcept
{
if (wch > CHAR_MAX)
{
_uiTimesFailedOutsideRange++;
_uiTimesFailedOutsideRangeCurrent++;
}
else
{
// Even though we pass over a wide character, we only care about the ASCII single byte character.
_uiTimesFailed[wch]++;
_uiTimesFailedCurrent++;
}
}
// Routine Description:
// - Gets and resets the total count of codes used.
//
// Arguments:
// - <none>
// Return Value:
// - total number.
unsigned int TermTelemetry::GetAndResetTimesUsedCurrent() noexcept
{
const auto temp = _uiTimesUsedCurrent;
_uiTimesUsedCurrent = 0;
return temp;
}
// Routine Description:
// - Gets and resets the total count of codes failed.
//
// Arguments:
// - <none>
// Return Value:
// - total number.
unsigned int TermTelemetry::GetAndResetTimesFailedCurrent() noexcept
{
const auto temp = _uiTimesFailedCurrent;
_uiTimesFailedCurrent = 0;
return temp;
}
// Routine Description:
// - Gets and resets the total count of codes failed outside the valid range.
//
// Arguments:
// - <none>
// Return Value:
// - total number.
unsigned int TermTelemetry::GetAndResetTimesFailedOutsideRangeCurrent() noexcept
{
const auto temp = _uiTimesFailedOutsideRangeCurrent;
_uiTimesFailedOutsideRangeCurrent = 0;
return temp;
}
// Routine Description:
// - Lets us know whether we should write the final log. Typically set true when the console has been
// interacted with, to help reduce the amount of telemetry we're sending.
//
// Arguments:
// - writeLog - true if we should write the log.
// Return Value:
// - <none>
void TermTelemetry::SetShouldWriteFinalLog(const bool writeLog) noexcept
{
_fShouldWriteFinalLog = writeLog;
}
// Routine Description:
// - Sets the activity Id, so we can match our events with other providers (such as Microsoft.Windows.Console.Host).
//
// Arguments:
// - activityId - Pointer to Guid to set our activity Id to.
// Return Value:
// - <none>
void TermTelemetry::SetActivityId(const GUID* activityId) noexcept
{
_activityId = *activityId;
}
// Routine Description:
// - Writes the final log of all the telemetry collected. The primary reason to send back a final log instead
// of individual events is to reduce the amount of telemetry being sent and potentially overloading our servers.
//
// Arguments:
// - code - VT100 code.
// Return Value:
// - <none>
void TermTelemetry::WriteFinalTraceLog() const
{
if (_fShouldWriteFinalLog)
{
// Determine if we've logged any VT100 sequences at all.
bool fLoggedSequence = (_uiTimesFailedOutsideRange > 0);
if (!fLoggedSequence)
{
for (int n = 0; n < ARRAYSIZE(_uiTimesUsed); n++)
{
if (_uiTimesUsed[n] > 0)
{
fLoggedSequence = true;
break;
}
}
}
if (!fLoggedSequence)
{
for (int n = 0; n < ARRAYSIZE(_uiTimesFailed); n++)
{
if (_uiTimesFailed[n] > 0)
{
fLoggedSequence = true;
break;
}
}
}
// Only send telemetry if we've logged some VT100 sequences. This should help reduce the amount of unnecessary
// telemetry being sent.
if (fLoggedSequence)
{
// I could use the TraceLoggingUIntArray, but then we would have to know the order of the enums on the backend.
// So just log each enum count separately with its string representation which makes it more human readable.
// Set the related activity to NULL since we aren't using it.
TraceLoggingWriteActivity(g_hConsoleVirtTermParserEventTraceProvider,
"ControlCodesUsed",
&_activityId,
NULL,
TraceLoggingUInt32(_uiTimesUsed[CUU], "CUU"),
TraceLoggingUInt32(_uiTimesUsed[CUD], "CUD"),
TraceLoggingUInt32(_uiTimesUsed[CUF], "CUF"),
TraceLoggingUInt32(_uiTimesUsed[CUB], "CUB"),
TraceLoggingUInt32(_uiTimesUsed[CNL], "CNL"),
TraceLoggingUInt32(_uiTimesUsed[CPL], "CPL"),
TraceLoggingUInt32(_uiTimesUsed[CHA], "CHA"),
TraceLoggingUInt32(_uiTimesUsed[CUP], "CUP"),
TraceLoggingUInt32(_uiTimesUsed[ED], "ED"),
TraceLoggingUInt32(_uiTimesUsed[EL], "EL"),
TraceLoggingUInt32(_uiTimesUsed[SGR], "SGR"),
TraceLoggingUInt32(_uiTimesUsed[DECSC], "DECSC"),
TraceLoggingUInt32(_uiTimesUsed[DECRC], "DECRC"),
TraceLoggingUInt32(_uiTimesUsed[DECSET], "DECSET"),
TraceLoggingUInt32(_uiTimesUsed[DECRST], "DECRST"),
TraceLoggingUInt32(_uiTimesUsed[DECKPAM], "DECKPAM"),
TraceLoggingUInt32(_uiTimesUsed[DECKPNM], "DECKPNM"),
TraceLoggingUInt32(_uiTimesUsed[DSR], "DSR"),
TraceLoggingUInt32(_uiTimesUsed[DA], "DA"),
Add support for DA2 and DA3 device attributes reports (#6850) This PR adds support for the `DA2` (Secondary Device Attributes) and `DA3` (Tertiary Device Attributes) escape sequences, which are standard VT queries reporting basic information about the terminal. The _Secondary Device Attributes_ response is made up of a number of parameters: 1. An identification code, for which I've used 0 to indicate that we have the capabilities of a VT100 (using code 0 for this is an XTerm convention, since technically DA2 would not have been supported by a VT100). 2. A firmware revision level, which some terminal emulators use to report their actual version number, but I thought it best we just hardcode a value of 10 (the DEC convention for 1.0). 3. Additional hardware options, which tend to be device specific, but I've followed the convention of the later DEC terminals using 1 to indicate the presence of a PC keyboard. The _Tertiary Device Attributes_ response was originally used to provide a unique terminal identification code, and which some terminal emulators use as a way to identify themselves. However, I think that's information we'd probably prefer not to reveal, so I've followed the more common practice of returning all zeros for the ID. In terms of implementation, the only complication was the need to add an additional code path in the `OutputStateMachine` to handle the `>` and `=` intermediates (technically private parameter prefixes) that these sequences require. I've done this as a single method - rather than one for each prefix - since I think that makes the code easier to follow. VALIDATION ---------- I've added output engine tests to make sure the sequences are dispatched correctly, and adapter tests to confirm that they are returning the responses we expect. I've also manually confirmed that they pass the _Test of terminal reports_ in Vttest. Closes #5836
2020-07-11 00:27:47 +02:00
TraceLoggingUInt32(_uiTimesUsed[DA2], "DA2"),
TraceLoggingUInt32(_uiTimesUsed[DA3], "DA3"),
Add support for the DECREQTPARM report (#7939) This PR adds support for the `DECREQTPARM` (Request Terminal Parameters) escape sequence, which was originally used on the VT100 terminal to report the serial communication parameters. Modern terminal emulators simply hardcode the reported values for backward compatibility. The `DECREQTPARM` sequence has one parameter, which was originally used to tell the terminal whether it was permitted to send unsolicited reports or not. However, since we have no reason to send an unsolicited report, we don't need to keep track of that state, but the permission parameter does still determine the value of the first parameter in the response. The response parameters are as follows: | Parameter | Value | Meaning | | ---------------- | ------ | ------------------------ | | response type | 2 or 3 | unsolicited or solicited | | parity | 1 | no parity | | data bits | 1 | 8 bits per character | | transmit speed | 128 | 38400 baud | | receive speed | 128 | 38400 baud | | clock multiplier | 1 | | | flags | 0 | | There is some variation in the baud rate reported by modern terminal emulators, and 9600 baud seems to be a little more common than 38400 baud, but I thought the higher speed was probably more appropriate, especially since that's also the value reported by XTerm. ## Validation Steps Performed I've added a couple of adapter and output engine tests to verify that the sequence is dispatched correctly, and the expected responses are generated. I've also manually tested in Vttest and confirmed that we now pass the `DECREQTPARM` test in the _Test of terminal reports_. Closes #7852
2020-10-16 00:50:02 +02:00
TraceLoggingUInt32(_uiTimesUsed[DECREQTPARM], "DECREQTPARM"),
TraceLoggingUInt32(_uiTimesUsed[VPA], "VPA"),
Add support for the HPR and VPR escape sequences (#4297) ## Summary of the Pull Request This PR adds support for the `HPR` and `VPR` escape sequences from the VT510 terminal. `HPR` moves the cursor position forward by a given number of columns, and `VPR` moves the cursor position downward by a given number of rows. They're similar in function to the `CUF` and `CUD` escape sequences, except that they're not constrained by the scrolling margins. ## References #3628 provided the new `_CursorMovePosition` method that made these operations possible ## PR Checklist * [x] Closes #3428 * [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA * [x] Tests added/passed * [ ] Requires documentation to be updated * [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx ## Detailed Description of the Pull Request / Additional comments Most of the implementation is in the new `_CursorMovePosition` method that was created in PR #3628, so all we're really doing here is hooking up the escape sequences to call that method with the appropriate parameters. ## Validation Steps Performed I've extended the existing state machine tests for CSI cursor movement to confirm that the `HPR` and `VPR` sequences are dispatched correctly, and also added screen buffer tests to make sure the movement is clamped by the screen boundaries and not the scrolling margins (we don't yet support horizontal margins, but the test is at least in place for when we do eventually add that support). I've also checked the `HPR` and `VPR` tests in Vttest (under _Test non-VT100 / ISO-6429 cursor-movement_) and confirmed that they are now working as expected.
2020-01-21 23:39:15 +01:00
TraceLoggingUInt32(_uiTimesUsed[HPR], "HPR"),
TraceLoggingUInt32(_uiTimesUsed[VPR], "VPR"),
TraceLoggingUInt32(_uiTimesUsed[ICH], "ICH"),
TraceLoggingUInt32(_uiTimesUsed[DCH], "DCH"),
TraceLoggingUInt32(_uiTimesUsed[IL], "IL"),
TraceLoggingUInt32(_uiTimesUsed[DL], "DL"),
TraceLoggingUInt32(_uiTimesUsed[SU], "SU"),
TraceLoggingUInt32(_uiTimesUsed[SD], "SD"),
TraceLoggingUInt32(_uiTimesUsed[ANSISYSSC], "ANSISYSSC"),
TraceLoggingUInt32(_uiTimesUsed[ANSISYSRC], "ANSISYSRC"),
TraceLoggingUInt32(_uiTimesUsed[DECSTBM], "DECSTBM"),
Add support for all the line feed control sequences (#3271) ## Summary of the Pull Request This adds support for the `FF` (form feed) and `VT` (vertical tab) [control characters](https://vt100.net/docs/vt510-rm/chapter4.html#T4-1), as well as the [`NEL` (Next Line)](https://vt100.net/docs/vt510-rm/NEL.html) and [`IND` (Index)](https://vt100.net/docs/vt510-rm/IND.html) escape sequences. ## References #976 discusses the conflict between VT100 Index sequence and the VT52 cursor back sequence. ## PR Checklist * [x] Closes #3189 * [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA * [x] Tests added/passed * [ ] Requires documentation to be updated * [x] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #3189 ## Detailed Description of the Pull Request / Additional comments I've added a `LineFeed` method to the `ITermDispatch` interface, with an enum parameter specifying the required line feed type (i.e. with carriage return, without carriage return, or dependent on the [`LNM` mode](https://vt100.net/docs/vt510-rm/LNM.html)). The output state machine can then call that method to handle the various line feed control characters (parsed in the `ActionExecute` method), as well the `NEL` and `IND` escape sequences (parsed in the `ActionEscDispatch` method). The `AdaptDispatch` implementation of `LineFeed` then forwards the call to a new `PrivateLineFeed` method in the `ConGetSet` interface, which simply takes a bool parameter specifying whether a carriage return is required or not. In the case of mode-dependent line feeds, the `AdaptDispatch` implementation determines whether the return is necessary or not, based on the existing _AutoReturnOnNewLine_ setting (which I'm obtaining via another new `PrivateGetLineFeedMode` method). Ultimately we'll want to support changing the mode via the [`LNM` escape sequence](https://vt100.net/docs/vt510-rm/LNM.html), but there's no urgent need for that now. And using the existing _AutoReturnOnNewLine_ setting as a substitute for the mode gives us backwards compatible behaviour, since that will be true for the Windows shells (which expect a linefeed to also generate a carriage return), and false in a WSL bash shell (which won't want the carriage return by default). As for the actual `PrivateLineFeed` implementation, that is just a simplified version of how the line feed would previously have been executed in the `WriteCharsLegacy` function. This includes setting the cursor to "On" (with `Cursor::SetIsOn`), potentially clearing the wrap property of the line being left (with `CharRow::SetWrapForced` false), and then setting the new position using `AdjustCursorPosition` with the _fKeepCursorVisible_ parameter set to false. I'm unsure whether the `SetIsOn` call is really necessary, and I think the way the forced wrap is handled needs a rethink in general, but for now this should at least be compatible with the existing behaviour. Finally, in order to make this all work in the _Windows Terminal_ app, I also had to add a basic implementation of the `ITermDispatch::LineFeed` method in the `TerminalDispatch` class. There is currently no need to support mode-specific line feeds here, so this simply forwards a `\n` or `\r\n` to the `Execute` method, which is ultimately handled by the `Terminal::_WriteBuffer` implementation. ## Validation Steps Performed I've added output engine tests which confirm that the various control characters and escape sequences trigger the dispatch method correctly. Then I've added adapter tests which confirm the various dispatch options trigger the `PrivateLineFeed` API correctly. And finally I added some screen buffer tests that check the actual results of the `NEL` and `IND` sequences, which covers both forms of the `PrivateLineFeed` API (i.e. with and without a carriage return). I've also run the _Test of cursor movements_ in the [Vttest](https://invisible-island.net/vttest/) utility, and confirmed that screens 1, 2, and 5 are now working correctly. The first two depend on `NEL` and `IND` being supported, and screen 5 requires the `VT` control character.
2020-01-15 14:41:55 +01:00
TraceLoggingUInt32(_uiTimesUsed[NEL], "NEL"),
TraceLoggingUInt32(_uiTimesUsed[IND], "IND"),
TraceLoggingUInt32(_uiTimesUsed[RI], "RI"),
TraceLoggingUInt32(_uiTimesUsed[OSCWT], "OscWindowTitle"),
TraceLoggingUInt32(_uiTimesUsed[HTS], "HTS"),
TraceLoggingUInt32(_uiTimesUsed[CHT], "CHT"),
TraceLoggingUInt32(_uiTimesUsed[CBT], "CBT"),
TraceLoggingUInt32(_uiTimesUsed[TBC], "TBC"),
TraceLoggingUInt32(_uiTimesUsed[ECH], "ECH"),
TraceLoggingUInt32(_uiTimesUsed[DesignateG0], "DesignateG0"),
TraceLoggingUInt32(_uiTimesUsed[DesignateG1], "DesignateG1"),
TraceLoggingUInt32(_uiTimesUsed[DesignateG2], "DesignateG2"),
TraceLoggingUInt32(_uiTimesUsed[DesignateG3], "DesignateG3"),
Improve support for VT character sets (#4496) This PR improves our VT character set support, enabling the [`SCS`] escape sequences to designate into all four G-sets with both 94- and 96-character sets, and supports invoking those G-sets into both the GL and GR areas of the code table, with [locking shifts] and [single shifts]. It also adds [`DOCS`] sequences to switch between UTF-8 and the ISO-2022 coding system (which is what the VT character sets require), and adds support for a lot more characters sets, up to around the level of a VT510. [`SCS`]: https://vt100.net/docs/vt510-rm/SCS.html [locking shifts]: https://vt100.net/docs/vt510-rm/LS.html [single shifts]: https://vt100.net/docs/vt510-rm/SS.html [`DOCS`]: https://en.wikipedia.org/wiki/ISO/IEC_2022#Interaction_with_other_coding_systems ## Detailed Description of the Pull Request / Additional comments To make it easier for us to declare a bunch of character sets, I've made a little `constexpr` class that can build up a mapping table from a base character set (ASCII or Latin1), along with a collection of mappings for the characters the deviate from the base set. Many of the character sets are simple variations of ASCII, so they're easy to define this way. This class then casts directly to a `wstring_view` which is how the translation tables are represented in most of the code. We have an array of four of these tables representing the four G-sets, two instances for the active left and right tables, and one instance for the single shift table. Initially we had just one `DesignateCharset` method, which could select the active character set. We now have two designate methods (for 94- and 96- character sets), and each takes a G-set number specifying the target of the designation, and a pair of characters identifying the character set that will be designated (at the higher VT levels, character sets are often identified by more than one character). There are then two new `LockingShift` methods to invoke these G-sets into either the GL or GR area of the code table, and a `SingleShift` method which invokes a G-set temporarily (for just the next character that is output). I should mention here that I had to make some changes to the state machine to make these single shift sequences work. The problem is that the input state machine treats `SS3` as the start of a control sequence, while the output state machine needs it to be dispatched immediately (it's literally the _Single Shift 3_ escape sequence). To make that work, I've added a `ParseControlSequenceAfterSs3` callback in the `IStateMachineEngine` interface to decide which behavior is appropriate. When it comes to mapping a character, it's simply an array reference into the appropriate `wstring_view` table. If the single shift table is set, that takes preference. Otherwise the GL table is used for characters in the range 0x20 to 0x7F, and the GR table for characters 0xA0 to 0xFF (technically some character sets will only map up to 0x7E and 0xFE, but that's easily controlled by the length of the `wstring_view`). The `DEL` character is a bit of a special case. By default it's meant to be ignored like the `NUL` character (it's essentially a time-fill character). However, it's possible that it could be remapped to a printable character in a 96-character set, so we need to check for that after the translation. This is handled in the `AdaptDispatch::Print` method, so it doesn't interfere with the primary `PrintString` code path. The biggest problem with this whole process, though, is that the GR mappings only really make sense if you have access to the raw output, but by the time the output gets to us, it would already have been translated to Unicode by the active code page. And in the case of UTF-8, the characters we eventually receive may originally have been composed from two or more code points. The way I've dealt with this was to disable the GR translations by default, and then added support for a pair of ISO-2022 `DOCS` sequences, which can switch the code page between UTF-8 and ISO-8859-1. When the code page is ISO-8859-1, we're essentially receiving the raw output bytes, so it's safe to enable the GR translations. This is not strictly correct ISO-2022 behavior, and there are edge cases where it's not going to work, but it's the best solution I could come up with. ## Validation Steps Performed As a result of the `SS3` changes in the state machine engine, I've had to move the existing `SS3` tests from the `OutputEngineTest` to the `InputEngineTest`, otherwise they would now fail (technically they should never have been output tests). I've added no additional unit tests, but I have done a lot of manual testing, and made sure we passed all the character set tests in Vttest (at least for the character sets we currently support). Note that this required a slightly hacked version of the app, since by default it doesn't expose a lot of the test to low-level terminals, and we currently identify as a VT100. Closes #3377 Closes #3487
2020-06-04 21:40:15 +02:00
TraceLoggingUInt32(_uiTimesUsed[LS2], "LS2"),
TraceLoggingUInt32(_uiTimesUsed[LS3], "LS3"),
TraceLoggingUInt32(_uiTimesUsed[LS1R], "LS1R"),
TraceLoggingUInt32(_uiTimesUsed[LS2R], "LS2R"),
TraceLoggingUInt32(_uiTimesUsed[LS3R], "LS3R"),
TraceLoggingUInt32(_uiTimesUsed[SS2], "SS2"),
TraceLoggingUInt32(_uiTimesUsed[SS3], "SS3"),
TraceLoggingUInt32(_uiTimesUsed[DOCS], "DOCS"),
TraceLoggingUInt32(_uiTimesUsed[HVP], "HVP"),
TraceLoggingUInt32(_uiTimesUsed[DECSTR], "DECSTR"),
TraceLoggingUInt32(_uiTimesUsed[RIS], "RIS"),
TraceLoggingUInt32(_uiTimesUsed[DECSCUSR], "DECSCUSR"),
TraceLoggingUInt32(_uiTimesUsed[DTTERM_WM], "DTTERM_WM"),
TraceLoggingUInt32(_uiTimesUsed[OSCCT], "OscColorTable"),
TraceLoggingUInt32(_uiTimesUsed[OSCSCC], "OscSetCursorColor"),
TraceLoggingUInt32(_uiTimesUsed[OSCRCC], "OscResetCursorColor"),
TraceLoggingUInt32(_uiTimesUsed[OSCFG], "OscForegroundColor"),
TraceLoggingUInt32(_uiTimesUsed[OSCBG], "OscBackgroundColor"),
TraceLoggingUInt32(_uiTimesUsed[OSCSCB], "OscSetClipboard"),
TraceLoggingUInt32(_uiTimesUsed[REP], "REP"),
Disable the acceptance of C1 control codes by default (#11690) 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
2021-11-18 00:40:31 +01:00
TraceLoggingUInt32(_uiTimesUsed[DECAC1], "DECAC1"),
Add support for double-width/double-height lines in conhost (#8664) This PR adds support for the VT line rendition attributes, which allow for double-width and double-height line renditions. These renditions are enabled with the `DECDWL` (double-width line) and `DECDHL` (double-height line) escape sequences. Both reset to the default rendition with the `DECSWL` (single-width line) escape sequence. For now this functionality is only supported by the GDI renderer in conhost. There are a lot of changes, so this is just a general overview of the main areas affected. Previously it was safe to assume that the screen had a fixed width, at least for a given point in time. But now we need to deal with the possibility of different lines have different widths, so all the functions that are constrained by the right border (text wrapping, cursor movement operations, and sequences like `EL` and `ICH`) now need to lookup the width of the active line in order to behave correctly. Similarly it used to be safe to assume that buffer and screen coordinates were the same thing, but that is no longer true. Lots of places now need to translate back and forth between coordinate systems dependent on the line rendition. This includes clipboard handling, the conhost color selection and search, accessibility location tracking and screen reading, IME editor positioning, "snapping" the viewport, and of course all the rendering calculations. For the rendering itself, I've had to introduce a new `PrepareLineTransform` method that the render engines can use to setup the necessary transform matrix for a given line rendition. This is also now used to handle the horizontal viewport offset, since that could no longer be achieved just by changing the target coordinates (on a double width line, the viewport offset may be halfway through a character). I've also had to change the renderer's existing `InvalidateCursor` method to take a `SMALL_RECT` rather than a `COORD`, to allow for the cursor being a variable width. Technically this was already a problem, because the cursor could occupy two screen cells when over a double-width character, but now it can be anything between one and four screen cells (e.g. a double-width character on the double-width line). In terms of architectural changes, there is now a new `lineRendition` field in the `ROW` class that keeps track of the line rendition for each row, and several new methods in the `ROW` and `TextBuffer` classes for manipulating that state. This includes a few helper methods for handling the various issues discussed above, e.g. position clamping and translating between coordinate systems. ## Validation Steps Performed I've manually confirmed all the double-width and double-height tests in _Vttest_ are now working as expected, and the _VT100 Torture Test_ now renders correctly (at least the line rendition aspects). I've also got my own test scripts that check many of the line rendition boundary cases and have confirmed that those are now passing. I've manually tested as many areas of the conhost UI that I could think of, that might be affected by line rendition, including things like searching, selection, copying, and color highlighting. For accessibility, I've confirmed that the _Magnifier_ and _Narrator_ correctly handle double-width lines. And I've also tested the Japanese IME, which while not perfect, is at least useable. Closes #7865
2021-02-18 06:44:50 +01:00
TraceLoggingUInt32(_uiTimesUsed[DECSWL], "DECSWL"),
TraceLoggingUInt32(_uiTimesUsed[DECDWL], "DECDWL"),
TraceLoggingUInt32(_uiTimesUsed[DECDHL], "DECDHL"),
Add support for the DECALN escape sequence (#3968) ## Summary of the Pull Request This adds support for the [`DECALN`](https://vt100.net/docs/vt510-rm/DECALN.html) escape sequence, which produces a kind of test pattern, originally used on VT terminals to adjust the screen alignment. It's needed to pass several of the tests in the [Vttest](https://invisible-island.net/vttest/) suite. ## PR Checklist * [x] Closes #3671 * [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA * [x] Tests added/passed * [ ] Requires documentation to be updated * [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx ## Detailed Description of the Pull Request / Additional comments To start with, the `ActionEscDispatch` method in the `OutputStateMachineEngine` needed to be extended to check for a new intermediate type (`#`). Then when that intermediate is followed by an `8`, it dispatches to a new `ScreenAlignmentPattern` method in the `ITermDispatch` interface. The implementation of the `ScreenAlignmentPattern` itself is fairly simple. It uses the recently added `PrivateFillRegion` API to fill the screen with the character `E` using default attributes. Then in addition to that, a bunch of VT properties are reset: * The meta/extended attributes are reset (although the active colors must be left unchanged). * The origin mode is set to absolute positioning. * The scrolling margins are cleared. * The cursor position is moved to home. ## Validation Steps Performed I've added a screen buffer test that makes sure the `DECALN` sequence fills the screen with the correct character and attributes, and that the above mentioned properties are all updated appropriately. I've also tested in Vttest, and confirmed that the first two pages of the _Test of cursor movements_ are now showing the frame of E's that are expected there.
2019-12-17 19:11:52 +01:00
TraceLoggingUInt32(_uiTimesUsed[DECALN], "DECALN"),
TraceLoggingUInt32(_uiTimesUsed[XTPUSHSGR], "XTPUSHSGR"),
TraceLoggingUInt32(_uiTimesUsed[XTPOPSGR], "XTPOPSGR"),
TraceLoggingUInt32Array(_uiTimesFailed, ARRAYSIZE(_uiTimesFailed), "Failed"),
TraceLoggingUInt32(_uiTimesFailedOutsideRange, "FailedOutsideRange"));
}
}
}
#pragma warning(pop)