From b6feabe9cc9e1de97489d39724e96d3ae1066571 Mon Sep 17 00:00:00 2001 From: Kieren Wou Date: Tue, 29 Nov 2022 19:34:29 +0000 Subject: [PATCH 001/167] Merged PR 8188601: [Git2Git] !8082133: LKG14 Build Fix - ARM64EC does not support AVX Arm64EC does not support AVX and the usage of it in EC compilation is now an error with the LKG14 compiler update. The fix is to conditionalize using AVX for non-EC compilation. Related work items: MSFT-42045281 Retrieved from https://microsoft.visualstudio.com os.2020 OS official/rs_we_adept_e4d2 31ca1e08e001988b95ff29a5e098441cae0363bd --- oss/libpopcnt/libpopcnt.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oss/libpopcnt/libpopcnt.h b/oss/libpopcnt/libpopcnt.h index ffcd976b032..de24253d9a6 100644 --- a/oss/libpopcnt/libpopcnt.h +++ b/oss/libpopcnt/libpopcnt.h @@ -95,7 +95,7 @@ #define HAVE_AVX512 #endif -#if defined(X86_OR_X64) +#if defined(X86_OR_X64) && !defined(_M_ARM64EC) /* MSVC compatible compilers (Windows) */ #if defined(_MSC_VER) /* clang-cl (LLVM 10 from 2020) requires /arch:AVX2 or From 3c104440a897e551b867225d9fb8cfe4ff158dd8 Mon Sep 17 00:00:00 2001 From: Dustin Howett Date: Tue, 29 Nov 2022 22:07:37 +0000 Subject: [PATCH 002/167] Merged PR 8189936: [Git2Git] Lift an optional check out of RefreshRowIDs loop to fix a crash In the most recent compiler ingestion into Windows ("LKG14"), we found that this particular construction--checking an optional for a value during this range-for loop--resulted in bad code generation. When optimized, it generates code that looks effectively like this: ```c++ if (!newRowWidth.has_value()) { while (true) { // do the row stuff... ++it; } } ``` The loop never exits, and `_RefreshRowIDs` walks off the end of the buffer. Whoops. This commit fixes that issue by tricking the optimizer to go another way. Leonard tells me it's harmless to call `Resize` a bunch of times, even if it's a no-op, so I trust that this change results in the right outcome with none of the crashing. Fixes MSFT-41456525 Retrieved from https://microsoft.visualstudio.com os.2020 OS official/rs_we_adept_e4d2 c2b3697c867bddf5660da8b222e99ff4bfd1ea5b --- src/buffer/out/textBuffer.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 21f153d1794..ce7a8ed980d 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -1032,6 +1032,7 @@ void TextBuffer::_RefreshRowIDs(std::optional newRowWidth) { std::unordered_map rowMap; til::CoordType i = 0; + til::CoordType newWidth = newRowWidth.value_or(_storage.at(0).size()); for (auto& it : _storage) { // Build a map so we can update Unicode Storage @@ -1043,12 +1044,13 @@ void TextBuffer::_RefreshRowIDs(std::optional newRowWidth) // Also update the char row parent pointers as they can get shuffled up in the rotates. it.GetCharRow().UpdateParent(&it); - // Resize the rows in the X dimension if we have a new width - if (newRowWidth.has_value()) - { - // Realloc in the X direction - THROW_IF_FAILED(it.Resize(newRowWidth.value())); - } + // Realloc in the X direction + // BODGY: We unpack the optional early and resize here unconditionally + // due to a codegen issue in LKG14. We used to check the optional in + // every iteration of the loop, but that resulted in the optimizer + // emitting a copy of the loop, used when the optional was empty, that + // never exited. Oops. + THROW_IF_FAILED(it.Resize(newWidth)); } // Give the new mapping to Unicode Storage From 154ac2b9160177c079f1a93436a0b6240761c5a2 Mon Sep 17 00:00:00 2001 From: Dustin Howett Date: Wed, 15 Feb 2023 19:15:28 +0000 Subject: [PATCH 003/167] Merged PR 8428085: [Git2Git] Fix a GDI font object leak in FontInfo "Leak in font object 1952 times in last 2k GDI objects created, that lead console to run out of GDI objects." Fixes MSFT-42906562 Retrieved from https://microsoft.visualstudio.com os.2020 OS official/rs_we_adept_e4d2 44f47bf7dbe4bff1986ba5fd8940b56f854c58b7 --- src/propsheet/misc.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/propsheet/misc.cpp b/src/propsheet/misc.cpp index 1dd7ff4b71c..17d089e3798 100644 --- a/src/propsheet/misc.cpp +++ b/src/propsheet/misc.cpp @@ -398,6 +398,10 @@ int AddFont( /* * Store the font info */ + if (FontInfo[nFont].hFont != nullptr) + { + DeleteObject(FontInfo[nFont].hFont); + } FontInfo[nFont].hFont = hFont; FontInfo[nFont].Family = tmFamily; FontInfo[nFont].Size = SizeActual; From 553ad121afabe7f73f1e8039c7b902f88977620a Mon Sep 17 00:00:00 2001 From: Dustin Howett Date: Tue, 7 Mar 2023 17:54:31 +0000 Subject: [PATCH 004/167] Merged PR 8603508: [Git2Git] Merged PR 8535345: More scrubbing of ONECORE_PRIV_SDK_INC_PATH I have been working feverishly to remove the use of undefined macros used in sources\dirs files (such as this one ONECORE_PRIV_SDK_INC_PATH) that our telemetry constantly catches and reports as an issue. Retrieved from https://microsoft.visualstudio.com os.2020 OS official/rs_we_adept_e4d2 bb08e422cfb4dc2f8b99a2c34bac67a61654a572 Related work items: MSFT-40126326 --- src/host/ft_integrity/sources | 1 - src/tsf/sources | 1 - 2 files changed, 2 deletions(-) diff --git a/src/host/ft_integrity/sources b/src/host/ft_integrity/sources index a7eddd8bef0..0dcd996d5c3 100644 --- a/src/host/ft_integrity/sources +++ b/src/host/ft_integrity/sources @@ -33,7 +33,6 @@ INCLUDES=\ $(COM_INC_PATH); \ $(ONECOREBASE_INTERNAL_INC_PATH_L)\appmodel\test\common; \ $(ONECOREREDIST_INTERNAL_INC_PATH_L)\TAEF; \ - $(ONECORE_PRIV_SDK_INC_PATH); \ $(MINCORE_INTERNAL_PRIV_SDK_INC_PATH_L); \ TARGETLIBS=\ diff --git a/src/tsf/sources b/src/tsf/sources index d18e015d2c9..54c79162097 100644 --- a/src/tsf/sources +++ b/src/tsf/sources @@ -53,7 +53,6 @@ SOURCES = \ INCLUDES = \ $(INCLUDES); \ ..\inc; \ - $(ONECORE_PRIV_SDK_INC_PATH); \ $(MINWIN_INTERNAL_PRIV_SDK_INC_PATH_L); \ $(SDK_INC_PATH)\atl30; \ $(ONECORE_EXTERNAL_SDK_INC_PATH)\atl30; \ From 7aada2fd1e432fdc72383da411fec5d78c7c072d Mon Sep 17 00:00:00 2001 From: Dustin Howett Date: Tue, 7 Mar 2023 19:00:36 +0000 Subject: [PATCH 005/167] Merged PR 8612870: [Git2Git] Update WIL's get_token_info output type A customer reports that `wil::get_token_information` and its use of `wistd::unique_ptr` is hitting a code analysis error in that dynamically-sized token information blocks are allocated with `operator new` but deleted with `delete T*`. This is a "mismatched allocator" error and should be removed. ## What changed? The output type of token information changed from `wistd::unique_ptr` to `wil::unique_tokeninfo_ptr` which has a custom deleter that uses `operator delete(p)` to match the allocator. As the new type is incompatible with the old type, all call sites for `wil::GetTokenInformation` were updated to use the new type. ## How was the change tested? 1. Ran the WIL unit tests 2. Prime build of impacted directories Related: https://github.com/microsoft/wil/pull/306 Related: https://github.com/microsoft/wil/issues/276 Retrieved from https://microsoft.visualstudio.com os.2020 OS official/rs_we_adept_e4d2 d86d562b7559c2ca8de036085de6e52e80da8c93 --- src/host/ft_integrity/IntegrityTest.cpp | 2 +- src/tools/integrity/lib/util.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/host/ft_integrity/IntegrityTest.cpp b/src/host/ft_integrity/IntegrityTest.cpp index be3db377fe1..0ca45b0b5d1 100644 --- a/src/host/ft_integrity/IntegrityTest.cpp +++ b/src/host/ft_integrity/IntegrityTest.cpp @@ -312,7 +312,7 @@ PCWSTR IntegrityTest::s_GetMyIntegrityLevel() DWORD dwIntegrityLevel = 0; // Get the Integrity level. - wistd::unique_ptr tokenLabel; + wil::unique_tokeninfo_ptr tokenLabel; THROW_IF_FAILED(wil::GetTokenInformationNoThrow(tokenLabel, GetCurrentProcessToken())); dwIntegrityLevel = *GetSidSubAuthority(tokenLabel->Label.Sid, diff --git a/src/tools/integrity/lib/util.cpp b/src/tools/integrity/lib/util.cpp index 3ea8e4c6e9f..557ce8061b0 100644 --- a/src/tools/integrity/lib/util.cpp +++ b/src/tools/integrity/lib/util.cpp @@ -14,7 +14,7 @@ PCWSTR GetIntegrityLevel() DWORD dwIntegrityLevel = 0; // Get the Integrity level. - wistd::unique_ptr tokenLabel; + wil::unique_tokeninfo_ptr tokenLabel; THROW_IF_FAILED(wil::GetTokenInformationNoThrow(tokenLabel, GetCurrentProcessToken())); dwIntegrityLevel = *GetSidSubAuthority(tokenLabel->Label.Sid, From a132ba82e7b80df154d514fc78996a093c4cf4e3 Mon Sep 17 00:00:00 2001 From: Dustin Howett Date: Wed, 15 Mar 2023 17:06:21 +0000 Subject: [PATCH 006/167] Merged PR 8628818: [Git2Git] Merged PR 8561941: Scrubs SDKTOOLS_INC_PATH from onecore, onecoreuap I have been working feverishly to remove the use of undefined macros used in sources\dirs files (such as this one SDKTOOLS_INC_PATH) that our telemetry constantly catches and reports as an issue. Related work items: MSFT-40126326 Retrieved from https://microsoft.visualstudio.com os.2020 OS official/rs_we_adept_e4d2 8589e01e23c4ec64ad270dbf0c1beb8a78b5a833 --- src/terminal/adapter/ut_adapter/sources | 1 - 1 file changed, 1 deletion(-) diff --git a/src/terminal/adapter/ut_adapter/sources b/src/terminal/adapter/ut_adapter/sources index 2f3850c2d63..a0618ffe43b 100644 --- a/src/terminal/adapter/ut_adapter/sources +++ b/src/terminal/adapter/ut_adapter/sources @@ -144,7 +144,6 @@ DLOAD_ERROR_HANDLER = kernelbase #INCLUDES = $(INCLUDES); \ # ..\..\..\inc; \ -# $(SDKTOOLS_INC_PATH)\WexTest\Cue; \ # #SOURCES = $(SOURCES) \ # From 2ecfac80e7c23ef3592ae6711e0505448a691989 Mon Sep 17 00:00:00 2001 From: Dustin Howett Date: Mon, 25 Sep 2023 13:42:35 -0500 Subject: [PATCH 007/167] pgo: PGO specifically for 1.19 branch --- build/pgo/Terminal.PGO.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/pgo/Terminal.PGO.props b/build/pgo/Terminal.PGO.props index 109bdd7ffa0..b3735920c27 100644 --- a/build/pgo/Terminal.PGO.props +++ b/build/pgo/Terminal.PGO.props @@ -9,7 +9,7 @@ - main + release-1.19 Microsoft.Internal.Windows.Terminal.PGODatabase From b772b2da572bcaba04acdd90fdb9044db9643ee9 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Mon, 25 Sep 2023 19:24:16 -0500 Subject: [PATCH 008/167] About: check PackageManager for updates in addition to Store (#16012) With us adding a .appinstaller distribution of Canary, the Store services update checker has beome insufficient to determine whether there are package updates. App Installer supports us checking for updates by using PackageManager and the Package interfaces. We'll use those instead of the Store services interface, and bail out early if the App Installer gives us an answer. (cherry picked from commit e0fc3bcd0a68e8be0c599b388818ca20ebe1cddb) Service-Card-Id: 90644882 Service-Version: 1.19 --- src/cascadia/TerminalApp/AboutDialog.cpp | 52 ++++++++++++++++++++---- src/cascadia/TerminalApp/pch.h | 1 + 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/src/cascadia/TerminalApp/AboutDialog.cpp b/src/cascadia/TerminalApp/AboutDialog.cpp index 764f7b6dcaf..c443b73e09f 100644 --- a/src/cascadia/TerminalApp/AboutDialog.cpp +++ b/src/cascadia/TerminalApp/AboutDialog.cpp @@ -86,16 +86,54 @@ namespace winrt::TerminalApp::implementation co_await wil::resume_foreground(strongThis->Dispatcher()); UpdatesAvailable(true); #else // release build, likely has a store context - if (auto storeContext{ winrt::Windows::Services::Store::StoreContext::GetDefault() }) + bool packageManagerAnswered{ false }; + + try + { + if (auto currentPackage{ winrt::Windows::ApplicationModel::Package::Current() }) + { + // We need to look up our package in the Package Manager; we cannot use Current + winrt::Windows::Management::Deployment::PackageManager pm; + if (auto lookedUpPackage{ pm.FindPackageForUser(winrt::hstring{}, currentPackage.Id().FullName()) }) + { + using winrt::Windows::ApplicationModel::PackageUpdateAvailability; + auto availabilityResult = co_await lookedUpPackage.CheckUpdateAvailabilityAsync(); + co_await wil::resume_foreground(strongThis->Dispatcher()); + auto availability = availabilityResult.Availability(); + switch (availability) + { + case PackageUpdateAvailability::Available: + case PackageUpdateAvailability::Required: + case PackageUpdateAvailability::NoUpdates: + UpdatesAvailable(availability != PackageUpdateAvailability::NoUpdates); + packageManagerAnswered = true; + break; + case PackageUpdateAvailability::Error: + case PackageUpdateAvailability::Unknown: + default: + // Do not set packageManagerAnswered, which will trigger the store check. + break; + } + } + } + } + catch (...) + { + } // Do nothing on failure + + if (!packageManagerAnswered) { - const auto updates = co_await storeContext.GetAppAndOptionalStorePackageUpdatesAsync(); - co_await wil::resume_foreground(strongThis->Dispatcher()); - if (updates) + if (auto storeContext{ winrt::Windows::Services::Store::StoreContext::GetDefault() }) { - const auto numUpdates = updates.Size(); - if (numUpdates > 0) + const auto updates = co_await storeContext.GetAppAndOptionalStorePackageUpdatesAsync(); + co_await wil::resume_foreground(strongThis->Dispatcher()); + if (updates) { - UpdatesAvailable(true); + const auto numUpdates = updates.Size(); + if (numUpdates > 0) + { + UpdatesAvailable(true); + } } } } diff --git a/src/cascadia/TerminalApp/pch.h b/src/cascadia/TerminalApp/pch.h index 0065e5278fc..01811ad62f6 100644 --- a/src/cascadia/TerminalApp/pch.h +++ b/src/cascadia/TerminalApp/pch.h @@ -49,6 +49,7 @@ #include #include #include +#include #include #include From 6183b27547fba6a219d185c82cdeb93b1dbb2714 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 26 Sep 2023 02:24:29 +0200 Subject: [PATCH 009/167] Fix the prompt sometimes not being erased properly (#15880) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A carriage return (enter key) will increase the _distanceEnd by up to viewport-width many columns, since it increases the Y distance between the start and end by 1 (it's a newline after all). This will make _flushBuffer() think that the new _buffer is way longer than the old one and so _erase() ends up not erasing the tail end of the prompt, even if the new prompt is actually shorter. This commit fixes the issue by separating the newline printing out from the regular text printing loops. ## Validation Steps Performed * Run cmd.exe * Write "echo hello" and press Enter * Write "foobar foo bar" (don't press Enter) * Press F7, select "echo hello" and press Enter * Previous prompt says "echo hello" ✅ (cherry picked from commit c7f30a86d7d48585bdd72493283fa10c22c54664) Service-Card-Id: 90642765 Service-Version: 1.19 --- src/host/readDataCooked.cpp | 149 +++++++++++++++++++++--------------- src/host/readDataCooked.hpp | 22 ++++-- 2 files changed, 103 insertions(+), 68 deletions(-) diff --git a/src/host/readDataCooked.cpp b/src/host/readDataCooked.cpp index a5859d6766f..d05502dc0d0 100644 --- a/src/host/readDataCooked.cpp +++ b/src/host/readDataCooked.cpp @@ -204,18 +204,19 @@ bool COOKED_READ_DATA::Read(const bool isUnicode, size_t& numBytes, ULONG& contr { controlKeyState = 0; - const auto done = _readCharInputLoop(); + _readCharInputLoop(); // NOTE: Don't call _flushBuffer in a wil::scope_exit/defer. // It may throw and throwing during an ongoing exception is a bad idea. _flushBuffer(); - if (done) + if (_state == State::Accumulating) { - _handlePostCharInputLoop(isUnicode, numBytes, controlKeyState); + return false; } - return done; + _handlePostCharInputLoop(isUnicode, numBytes, controlKeyState); + return true; } // Printing wide glyphs at the end of a row results in a forced line wrap and a padding whitespace to be inserted. @@ -308,17 +309,10 @@ size_t COOKED_READ_DATA::_wordNext(const std::wstring_view& chars, size_t positi return position; } -const std::wstring_view& COOKED_READ_DATA::_newlineSuffix() const noexcept -{ - static constexpr std::wstring_view cr{ L"\r" }; - static constexpr std::wstring_view crlf{ L"\r\n" }; - return WI_IsFlagSet(_pInputBuffer->InputMode, ENABLE_PROCESSED_INPUT) ? crlf : cr; -} - // Reads text off of the InputBuffer and dispatches it to the current popup or otherwise into the _buffer contents. -bool COOKED_READ_DATA::_readCharInputLoop() +void COOKED_READ_DATA::_readCharInputLoop() { - for (;;) + while (_state == State::Accumulating) { const auto hasPopup = !_popups.empty(); auto charOrVkey = UNICODE_NULL; @@ -331,7 +325,7 @@ bool COOKED_READ_DATA::_readCharInputLoop() const auto status = GetChar(_pInputBuffer, &charOrVkey, true, pCommandLineEditingKeys, pPopupKeys, &modifiers); if (status == CONSOLE_STATUS_WAIT) { - return false; + break; } THROW_IF_NTSTATUS_FAILED(status); @@ -339,10 +333,7 @@ bool COOKED_READ_DATA::_readCharInputLoop() { const auto wch = static_cast(popupKeys ? 0 : charOrVkey); const auto vkey = static_cast(popupKeys ? charOrVkey : 0); - if (_popupHandleInput(wch, vkey, modifiers)) - { - return true; - } + _popupHandleInput(wch, vkey, modifiers); } else { @@ -350,16 +341,16 @@ bool COOKED_READ_DATA::_readCharInputLoop() { _handleVkey(charOrVkey, modifiers); } - else if (_handleChar(charOrVkey, modifiers)) + else { - return true; + _handleChar(charOrVkey, modifiers); } } } } // Handles character input for _readCharInputLoop() when no popups exist. -bool COOKED_READ_DATA::_handleChar(wchar_t wch, const DWORD modifiers) +void COOKED_READ_DATA::_handleChar(wchar_t wch, const DWORD modifiers) { // All paths in this function modify the buffer. @@ -376,17 +367,19 @@ bool COOKED_READ_DATA::_handleChar(wchar_t wch, const DWORD modifiers) _bufferCursor++; _controlKeyState = modifiers; - return true; + _transitionState(State::DoneWithWakeupMask); + return; } switch (wch) { case UNICODE_CARRIAGERETURN: { - _buffer.append(_newlineSuffix()); + // NOTE: Don't append newlines to the buffer just yet! See _handlePostCharInputLoop for more information. _bufferCursor = _buffer.size(); _markAsDirty(); - return true; + _transitionState(State::DoneWithCarriageReturn); + return; } case EXTKEY_ERASE_PREV_WORD: // Ctrl+Backspace case UNICODE_BACKSPACE: @@ -415,7 +408,7 @@ bool COOKED_READ_DATA::_handleChar(wchar_t wch, const DWORD modifiers) LOG_IF_FAILED(pConsoleWindow->SignalUia(UIA_Text_TextChangedEventId)); } } - return false; + return; } // If processed mode is disabled, control characters like backspace are treated like any other character. break; @@ -437,7 +430,6 @@ bool COOKED_READ_DATA::_handleChar(wchar_t wch, const DWORD modifiers) _bufferCursor++; _markAsDirty(); - return false; } // Handles non-character input for _readCharInputLoop() when no popups exist. @@ -656,15 +648,34 @@ void COOKED_READ_DATA::_handlePostCharInputLoop(const bool isUnicode, size_t& nu std::wstring_view input{ _buffer }; size_t lineCount = 1; - if (WI_IsFlagSet(_pInputBuffer->InputMode, ENABLE_ECHO_INPUT)) + if (_state == State::DoneWithCarriageReturn) { - // The last characters in line-read are a \r or \r\n unless _ctrlWakeupMask was used. - // Neither History nor s_MatchAndCopyAlias want to know about them. - const auto& suffix = _newlineSuffix(); - if (input.ends_with(suffix)) - { - input.remove_suffix(suffix.size()); + static constexpr std::wstring_view cr{ L"\r" }; + static constexpr std::wstring_view crlf{ L"\r\n" }; + const auto newlineSuffix = WI_IsFlagSet(_pInputBuffer->InputMode, ENABLE_PROCESSED_INPUT) ? crlf : cr; + std::wstring alias; + // Here's why we can't easily use _flushBuffer() to handle newlines: + // + // A carriage return (enter key) will increase the _distanceEnd by up to viewport-width many columns, + // since it increases the Y distance between the start and end by 1 (it's a newline after all). + // This will make _flushBuffer() think that the new _buffer is way longer than the old one and so + // _erase() ends up not erasing the tail end of the prompt, even if the new prompt is actually shorter. + // + // If you were to break this (remove this code and then append \r\n in _handleChar()) + // you can reproduce the issue easily if you do this: + // * Run cmd.exe + // * Write "echo hello" and press Enter + // * Write "foobar foo bar" (don't press Enter) + // * Press F7, select "echo hello" and press Enter + // + // It'll print "hello" but the previous prompt will say "echo hello bar" because the _distanceEnd + // ended up being well over 14 leading it to believe that "bar" got overwritten during WriteCharsLegacy(). + + WriteCharsLegacy(_screenInfo, newlineSuffix, true, nullptr); + + if (WI_IsFlagSet(_pInputBuffer->InputMode, ENABLE_ECHO_INPUT)) + { if (_history) { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); @@ -672,24 +683,27 @@ void COOKED_READ_DATA::_handlePostCharInputLoop(const bool isUnicode, size_t& nu } Tracing::s_TraceCookedRead(_processHandle, input); + alias = Alias::s_MatchAndCopyAlias(input, _exeName, lineCount); + } - const auto alias = Alias::s_MatchAndCopyAlias(input, _exeName, lineCount); - if (!alias.empty()) - { - _buffer = alias; - } + if (!alias.empty()) + { + _buffer = std::move(alias); + } + else + { + _buffer.append(newlineSuffix); + } - // NOTE: Even if there's no alias we should restore the trailing \r\n that we removed above. - input = std::wstring_view{ _buffer }; + input = std::wstring_view{ _buffer }; - // doskey aliases may result in multiple lines of output (for instance `doskey test=echo foo$Techo bar$Techo baz`). - // We need to emit them as multiple cooked reads as well, so that each read completes at a \r\n. - if (lineCount > 1) - { - // ProcessAliases() is supposed to end each line with \r\n. If it doesn't we might as well fail-fast. - const auto firstLineEnd = input.find(UNICODE_LINEFEED) + 1; - input = input.substr(0, std::min(input.size(), firstLineEnd)); - } + // doskey aliases may result in multiple lines of output (for instance `doskey test=echo foo$Techo bar$Techo baz`). + // We need to emit them as multiple cooked reads as well, so that each read completes at a \r\n. + if (lineCount > 1) + { + // ProcessAliases() is supposed to end each line with \r\n. If it doesn't we might as well fail-fast. + const auto firstLineEnd = input.find(UNICODE_LINEFEED) + 1; + input = input.substr(0, std::min(input.size(), firstLineEnd)); } } @@ -722,6 +736,12 @@ void COOKED_READ_DATA::_handlePostCharInputLoop(const bool isUnicode, size_t& nu controlKeyState = _controlKeyState; } +void COOKED_READ_DATA::_transitionState(State state) noexcept +{ + assert(_state == State::Accumulating); + _state = state; +} + // Signals to _flushBuffer() that the contents of _buffer are stale and need to be redrawn. // ALL _buffer and _bufferCursor changes must be flagged with _markAsDirty(). // @@ -733,6 +753,7 @@ void COOKED_READ_DATA::_markAsDirty() } // Draws the contents of _buffer onto the screen. +// NOTE: Don't call _flushBuffer() after appending newlines to the buffer! See _handlePostCharInputLoop for more information. void COOKED_READ_DATA::_flushBuffer() { // _flushBuffer() is called often and is a good place to assert() that our _bufferCursor is still in bounds. @@ -1043,12 +1064,12 @@ void COOKED_READ_DATA::_popupsDone() _screenInfo.GetTextBuffer().GetCursor().SetIsPopupShown(false); } -bool COOKED_READ_DATA::_popupHandleInput(wchar_t wch, uint16_t vkey, DWORD modifiers) +void COOKED_READ_DATA::_popupHandleInput(wchar_t wch, uint16_t vkey, DWORD modifiers) { if (_popups.empty()) { assert(false); // Don't call this function. - return false; + return; } auto& popup = _popups.back(); @@ -1056,17 +1077,18 @@ bool COOKED_READ_DATA::_popupHandleInput(wchar_t wch, uint16_t vkey, DWORD modif { case PopupKind::CopyToChar: _popupHandleCopyToCharInput(popup, wch, vkey, modifiers); - return false; + break; case PopupKind::CopyFromChar: _popupHandleCopyFromCharInput(popup, wch, vkey, modifiers); - return false; + break; case PopupKind::CommandNumber: _popupHandleCommandNumberInput(popup, wch, vkey, modifiers); - return false; + break; case PopupKind::CommandList: - return _popupHandleCommandListInput(popup, wch, vkey, modifiers); + _popupHandleCommandListInput(popup, wch, vkey, modifiers); + break; default: - return false; + break; } } @@ -1166,7 +1188,7 @@ void COOKED_READ_DATA::_popupHandleCommandNumberInput(Popup& popup, const wchar_ } } -bool COOKED_READ_DATA::_popupHandleCommandListInput(Popup& popup, const wchar_t wch, const uint16_t vkey, const DWORD modifiers) +void COOKED_READ_DATA::_popupHandleCommandListInput(Popup& popup, const wchar_t wch, const uint16_t vkey, const DWORD modifiers) { auto& cl = popup.commandList; @@ -1174,30 +1196,31 @@ bool COOKED_READ_DATA::_popupHandleCommandListInput(Popup& popup, const wchar_t { _buffer.assign(_history->RetrieveNth(cl.selected)); _popupsDone(); - return _handleChar(UNICODE_CARRIAGERETURN, modifiers); + _handleChar(UNICODE_CARRIAGERETURN, modifiers); + return; } switch (vkey) { case VK_ESCAPE: _popupsDone(); - return false; + return; case VK_F9: _popupPush(PopupKind::CommandNumber); - return false; + return; case VK_DELETE: _history->Remove(cl.selected); if (_history->GetNumberOfCommands() <= 0) { _popupsDone(); - return false; + return; } break; case VK_LEFT: case VK_RIGHT: _replaceBuffer(_history->RetrieveNth(cl.selected)); _popupsDone(); - return false; + return; case VK_UP: if (WI_IsFlagSet(modifiers, SHIFT_PRESSED)) { @@ -1230,11 +1253,11 @@ bool COOKED_READ_DATA::_popupHandleCommandListInput(Popup& popup, const wchar_t cl.selected += popup.contentRect.height(); break; default: - return false; + return; } _popupDrawCommandList(popup); - return false; + return; } void COOKED_READ_DATA::_popupDrawPrompt(const Popup& popup, const UINT id) const diff --git a/src/host/readDataCooked.hpp b/src/host/readDataCooked.hpp index 8f1e5849e81..803724b6f52 100644 --- a/src/host/readDataCooked.hpp +++ b/src/host/readDataCooked.hpp @@ -41,6 +41,13 @@ class COOKED_READ_DATA final : public ReadData private: static constexpr uint8_t CommandNumberMaxInputLength = 5; + enum class State : uint8_t + { + Accumulating = 0, + DoneWithWakeupMask, + DoneWithCarriageReturn, + }; + enum class PopupKind { // Copies text from the previous command between the current cursor position and the first instance @@ -106,11 +113,11 @@ class COOKED_READ_DATA final : public ReadData static size_t _wordPrev(const std::wstring_view& chars, size_t position); static size_t _wordNext(const std::wstring_view& chars, size_t position); - const std::wstring_view& _newlineSuffix() const noexcept; - bool _readCharInputLoop(); - bool _handleChar(wchar_t wch, DWORD modifiers); + void _readCharInputLoop(); + void _handleChar(wchar_t wch, DWORD modifiers); void _handleVkey(uint16_t vkey, DWORD modifiers); void _handlePostCharInputLoop(bool isUnicode, size_t& numBytes, ULONG& controlKeyState); + void _transitionState(State state) noexcept; void _markAsDirty(); void _flushBuffer(); void _erase(ptrdiff_t distance) const; @@ -124,8 +131,8 @@ class COOKED_READ_DATA final : public ReadData void _popupHandleCopyToCharInput(Popup& popup, wchar_t wch, uint16_t vkey, DWORD modifiers); void _popupHandleCopyFromCharInput(Popup& popup, wchar_t wch, uint16_t vkey, DWORD modifiers); void _popupHandleCommandNumberInput(Popup& popup, wchar_t wch, uint16_t vkey, DWORD modifiers); - bool _popupHandleCommandListInput(Popup& popup, wchar_t wch, uint16_t vkey, DWORD modifiers); - bool _popupHandleInput(wchar_t wch, uint16_t vkey, DWORD keyState); + void _popupHandleCommandListInput(Popup& popup, wchar_t wch, uint16_t vkey, DWORD modifiers); + void _popupHandleInput(wchar_t wch, uint16_t vkey, DWORD keyState); void _popupDrawPrompt(const Popup& popup, UINT id) const; void _popupDrawCommandList(Popup& popup) const; @@ -140,10 +147,15 @@ class COOKED_READ_DATA final : public ReadData std::wstring _buffer; size_t _bufferCursor = 0; + // _distanceCursor is the distance between the start of the prompt and the + // current cursor location in columns (including wide glyph padding columns). ptrdiff_t _distanceCursor = 0; + // _distanceEnd is the distance between the start of the prompt and its last + // glyph at the end in columns (including wide glyph padding columns). ptrdiff_t _distanceEnd = 0; bool _bufferDirty = false; bool _insertMode = false; + State _state = State::Accumulating; std::vector _popups; }; From aef2dbd07627aa7bf9d45d2ca6c3f46b4bf0e5ec Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 26 Sep 2023 02:28:51 +0200 Subject: [PATCH 010/167] Reimplement TextBuffer::Reflow (#15701) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Subjectively speaking, this commit makes 3 improvements: * Most importantly, it now would work with arbitrary Unicode text. (No more `IsGlyphFullWidth` or DBCS handling during reflow.) * Due to the simpler implementation it hopefully makes review of future changes and maintenance simpler. (~3x less LOC.) * It improves perf. by 1-2 orders of magnitude. (At 120x9001 with a full buffer I get 60ms -> 2ms.) Unfortunately, I'm not confident that the new code replicates the old code exactly, because I failed to understand it. During development I simply tried to match its behavior with what I think reflow should do. Closes #797 Closes #3088 Closes #4968 Closes #6546 Closes #6901 Closes #15964 Closes MSFT:19446208 Related to #5800 and #8000 ## Validation Steps Performed * Unit tests ✅ * Feature tests ✅ * Reflow with a scrollback ✅ * Reflowing the cursor cell causes a forced line-wrap ✅ (Even at the end of the buffer. ✅) * `color 8f` and reflowing retains the background color ✅ * Enter alt buffer, Resize window, Exit alt buffer ✅ (cherry picked from commit 74748394c17c168843b511dd837268445e5dfd6c) Service-Card-Id: 90642727 Service-Version: 1.19 --- .github/actions/spelling/expect/expect.txt | 8 +- src/buffer/out/Row.cpp | 42 +- src/buffer/out/Row.hpp | 1 + src/buffer/out/textBuffer.cpp | 470 ++++++++---------- src/buffer/out/textBuffer.hpp | 9 +- src/buffer/out/ut_textbuffer/ReflowTests.cpp | 223 ++++----- src/cascadia/TerminalCore/Terminal.cpp | 148 ++---- src/cascadia/TerminalCore/TerminalApi.cpp | 50 +- src/host/VtIo.cpp | 34 -- src/host/VtIo.hpp | 3 - src/host/screenInfo.cpp | 94 ++-- src/host/ut_host/ApiRoutinesTests.cpp | 2 +- src/host/ut_host/ScreenBufferTests.cpp | 4 +- src/host/ut_host/TextBufferTests.cpp | 8 +- src/inc/til/rle.h | 12 +- src/renderer/vt/invalidate.cpp | 16 +- src/renderer/vt/state.cpp | 29 -- src/renderer/vt/vtrenderer.hpp | 1 - .../adapter/ut_adapter/adapterTest.cpp | 10 +- src/types/UiaTextRangeBase.cpp | 2 +- 20 files changed, 464 insertions(+), 702 deletions(-) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index af587b42b67..7505061fab2 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -749,7 +749,6 @@ gfx GGI GHIJK GHIJKL -GHIJKLM gitfilters gitmodules gle @@ -1000,7 +999,6 @@ listptrsize lld llx LMENU -LMNOP lnk lnkd lnkfile @@ -1223,7 +1221,6 @@ nonspace NOOWNERZORDER Nop NOPAINT -NOPQRST noprofile NOREDRAW NOREMOVE @@ -1498,7 +1495,6 @@ pwsz pythonw Qaabbcc qos -QRSTU QUERYOPEN QUESTIONMARK quickedit @@ -1861,7 +1857,6 @@ testname TESTNULL testpass testpasses -testtimeout TEXCOORD texel TExpected @@ -2019,7 +2014,6 @@ utext UText UTEXT utr -UVWX UVWXY UVWXYZ uwa @@ -2078,7 +2072,6 @@ VTRGBTo vtseq vtterm vttest -VWX waitable WANSUNG WANTARROWS @@ -2274,6 +2267,7 @@ YCast YCENTER YCount YDPI +YLimit YOffset YSubstantial YVIRTUALSCREEN diff --git a/src/buffer/out/Row.cpp b/src/buffer/out/Row.cpp index 6c1c136e947..4036b991d81 100644 --- a/src/buffer/out/Row.cpp +++ b/src/buffer/out/Row.cpp @@ -152,11 +152,15 @@ til::CoordType CharToColumnMapper::GetTrailingColumnAt(ptrdiff_t offset) noexcep return col; } +// If given a pointer inside the ROW's text buffer, this function will return the corresponding column. +// This function in particular returns the glyph's first column. til::CoordType CharToColumnMapper::GetLeadingColumnAt(const wchar_t* str) noexcept { return GetLeadingColumnAt(str - _chars); } +// If given a pointer inside the ROW's text buffer, this function will return the corresponding column. +// This function in particular returns the glyph's last column (this matters for wide glyphs). til::CoordType CharToColumnMapper::GetTrailingColumnAt(const wchar_t* str) noexcept { return GetTrailingColumnAt(str - _chars); @@ -364,11 +368,16 @@ void ROW::TransferAttributes(const til::small_rle& a void ROW::CopyFrom(const ROW& source) { - RowCopyTextFromState state{ .source = source }; - CopyTextFrom(state); - TransferAttributes(source.Attributes(), _columnCount); _lineRendition = source._lineRendition; _wrapForced = source._wrapForced; + + RowCopyTextFromState state{ + .source = source, + .sourceColumnLimit = source.GetReadableColumnCount(), + }; + CopyTextFrom(state); + + TransferAttributes(source.Attributes(), _columnCount); } // Returns the previous possible cursor position, preceding the given column. @@ -382,7 +391,17 @@ til::CoordType ROW::NavigateToPrevious(til::CoordType column) const noexcept // Returns the row width if column is beyond the width of the row. til::CoordType ROW::NavigateToNext(til::CoordType column) const noexcept { - return _adjustForward(_clampedColumn(column + 1)); + return _adjustForward(_clampedColumnInclusive(column + 1)); +} + +// Returns the starting column of the glyph at the given column. +// In other words, if you have 3 wide glyphs +// AA BB CC +// 01 23 45 <-- column +// then `AdjustToGlyphStart(3)` returns 2. +til::CoordType ROW::AdjustToGlyphStart(til::CoordType column) const noexcept +{ + return _adjustBackward(_clampedColumn(column)); } // Routine Description: @@ -719,11 +738,12 @@ try if (sourceColBeg < sourceColLimit) { charOffsets = source._charOffsets.subspan(sourceColBeg, static_cast(sourceColLimit) - sourceColBeg + 1); - const auto charsOffset = charOffsets.front() & CharOffsetsMask; + const auto beg = size_t{ charOffsets.front() } & CharOffsetsMask; + const auto end = size_t{ charOffsets.back() } & CharOffsetsMask; // We _are_ using span. But C++ decided that string_view and span aren't convertible. // _chars is a std::span for performance and because it refers to raw, shared memory. #pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1). - chars = { source._chars.data() + charsOffset, source._chars.size() - charsOffset }; + chars = { source._chars.data() + beg, end - beg }; } WriteHelper h{ *this, state.columnBegin, state.columnLimit, chars }; @@ -939,6 +959,16 @@ til::CoordType ROW::MeasureLeft() const noexcept til::CoordType ROW::MeasureRight() const noexcept { + if (_wrapForced) + { + auto width = _columnCount; + if (_doubleBytePadded) + { + width--; + } + return width; + } + const auto text = GetText(); const auto beg = text.begin(); const auto end = text.end(); diff --git a/src/buffer/out/Row.hpp b/src/buffer/out/Row.hpp index 586aa12b95b..29f7fe18b8c 100644 --- a/src/buffer/out/Row.hpp +++ b/src/buffer/out/Row.hpp @@ -136,6 +136,7 @@ class ROW final til::CoordType NavigateToPrevious(til::CoordType column) const noexcept; til::CoordType NavigateToNext(til::CoordType column) const noexcept; + til::CoordType AdjustToGlyphStart(til::CoordType column) const noexcept; void ClearCell(til::CoordType column); OutputCellIterator WriteCells(OutputCellIterator it, til::CoordType columnBegin, std::optional wrap = std::nullopt, std::optional limitRight = std::nullopt); diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 84336eaf03e..26c8152ea8e 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -806,9 +806,9 @@ void TextBuffer::IncrementCircularBuffer(const TextAttribute& fillAttributes) // - The viewport //Return value: // - Coordinate position (relative to the text buffer) -til::point TextBuffer::GetLastNonSpaceCharacter(std::optional viewOptional) const +til::point TextBuffer::GetLastNonSpaceCharacter(const Viewport* viewOptional) const { - const auto viewport = viewOptional.has_value() ? viewOptional.value() : GetSize(); + const auto viewport = viewOptional ? *viewOptional : GetSize(); til::point coordEndOfText; // Search the given viewport by starting at the bottom. @@ -1070,46 +1070,40 @@ void TextBuffer::Reset() noexcept // - newSize - new size of screen. // Return Value: // - Success if successful. Invalid parameter if screen buffer size is unexpected. No memory if allocation failed. -[[nodiscard]] NTSTATUS TextBuffer::ResizeTraditional(til::size newSize) noexcept +void TextBuffer::ResizeTraditional(til::size newSize) { // Guard against resizing the text buffer to 0 columns/rows, which would break being able to insert text. newSize.width = std::max(newSize.width, 1); newSize.height = std::max(newSize.height, 1); - try - { - TextBuffer newBuffer{ newSize, _currentAttributes, 0, false, _renderer }; - const auto cursorRow = GetCursor().GetPosition().y; - const auto copyableRows = std::min(_height, newSize.height); - til::CoordType srcRow = 0; - til::CoordType dstRow = 0; - - if (cursorRow >= newSize.height) - { - srcRow = cursorRow - newSize.height + 1; - } + TextBuffer newBuffer{ newSize, _currentAttributes, 0, false, _renderer }; + const auto cursorRow = GetCursor().GetPosition().y; + const auto copyableRows = std::min(_height, newSize.height); + til::CoordType srcRow = 0; + til::CoordType dstRow = 0; - for (; dstRow < copyableRows; ++dstRow, ++srcRow) - { - newBuffer.GetMutableRowByOffset(dstRow).CopyFrom(GetRowByOffset(srcRow)); - } - - // NOTE: Keep this in sync with _reserve(). - _buffer = std::move(newBuffer._buffer); - _bufferEnd = newBuffer._bufferEnd; - _commitWatermark = newBuffer._commitWatermark; - _initialAttributes = newBuffer._initialAttributes; - _bufferRowStride = newBuffer._bufferRowStride; - _bufferOffsetChars = newBuffer._bufferOffsetChars; - _bufferOffsetCharOffsets = newBuffer._bufferOffsetCharOffsets; - _width = newBuffer._width; - _height = newBuffer._height; + if (cursorRow >= newSize.height) + { + srcRow = cursorRow - newSize.height + 1; + } - _SetFirstRowIndex(0); + for (; dstRow < copyableRows; ++dstRow, ++srcRow) + { + newBuffer.GetMutableRowByOffset(dstRow).CopyFrom(GetRowByOffset(srcRow)); } - CATCH_RETURN(); - return S_OK; + // NOTE: Keep this in sync with _reserve(). + _buffer = std::move(newBuffer._buffer); + _bufferEnd = newBuffer._bufferEnd; + _commitWatermark = newBuffer._commitWatermark; + _initialAttributes = newBuffer._initialAttributes; + _bufferRowStride = newBuffer._bufferRowStride; + _bufferOffsetChars = newBuffer._bufferOffsetChars; + _bufferOffsetCharOffsets = newBuffer._bufferOffsetCharOffsets; + _width = newBuffer._width; + _height = newBuffer._height; + + _SetFirstRowIndex(0); } void TextBuffer::SetAsActiveBuffer(const bool isActiveBuffer) noexcept @@ -2412,204 +2406,181 @@ void TextBuffer::_AppendRTFText(std::ostringstream& contentBuilder, const std::w // the new buffer. The rows's new value is placed back into this parameter. // Return Value: // - S_OK if we successfully copied the contents to the new buffer, otherwise an appropriate HRESULT. -HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer, - TextBuffer& newBuffer, - const std::optional lastCharacterViewport, - std::optional> positionInfo) -try +void TextBuffer::Reflow(TextBuffer& oldBuffer, TextBuffer& newBuffer, const Viewport* lastCharacterViewport, PositionInformation* positionInfo) { const auto& oldCursor = oldBuffer.GetCursor(); auto& newCursor = newBuffer.GetCursor(); - // We need to save the old cursor position so that we can - // place the new cursor back on the equivalent character in - // the new buffer. - const auto cOldCursorPos = oldCursor.GetPosition(); - const auto cOldLastChar = oldBuffer.GetLastNonSpaceCharacter(lastCharacterViewport); - - const auto cOldRowsTotal = cOldLastChar.y + 1; - - til::point cNewCursorPos; - auto fFoundCursorPos = false; - auto foundOldMutable = false; - auto foundOldVisible = false; - // Loop through all the rows of the old buffer and reprint them into the new buffer - til::CoordType iOldRow = 0; - for (; iOldRow < cOldRowsTotal; iOldRow++) - { - // Fetch the row and its "right" which is the last printable character. - const auto& row = oldBuffer.GetRowByOffset(iOldRow); - const auto cOldColsTotal = oldBuffer.GetLineWidth(iOldRow); - auto iRight = row.MeasureRight(); - - // If we're starting a new row, try and preserve the line rendition - // from the row in the original buffer. - const auto newBufferPos = newCursor.GetPosition(); - if (newBufferPos.x == 0) - { - auto& newRow = newBuffer.GetMutableRowByOffset(newBufferPos.y); - newRow.SetLineRendition(row.GetLineRendition()); - } + til::point oldCursorPos = oldCursor.GetPosition(); + til::point newCursorPos; + + // BODGY: We use oldCursorPos in two critical places below: + // * To compute an oldHeight that includes at a minimum the cursor row + // * For REFLOW_JANK_CURSOR_WRAP (see comment below) + // Both of these would break the reflow algorithm, but the latter of the two in particular + // would cause the main copy loop below to deadlock. In other words, these two lines + // protect this function against yet-unknown bugs in other parts of the code base. + oldCursorPos.x = std::clamp(oldCursorPos.x, 0, oldBuffer._width - 1); + oldCursorPos.y = std::clamp(oldCursorPos.y, 0, oldBuffer._height - 1); + + const auto lastRowWithText = oldBuffer.GetLastNonSpaceCharacter(lastCharacterViewport).y; + + auto mutableViewportTop = positionInfo ? positionInfo->mutableViewportTop : til::CoordTypeMax; + auto visibleViewportTop = positionInfo ? positionInfo->visibleViewportTop : til::CoordTypeMax; + + til::CoordType oldY = 0; + til::CoordType newY = 0; + til::CoordType newX = 0; + til::CoordType newWidth = newBuffer.GetSize().Width(); + til::CoordType newYLimit = til::CoordTypeMax; + + const auto oldHeight = std::max(lastRowWithText, oldCursorPos.y) + 1; + const auto newHeight = newBuffer.GetSize().Height(); + const auto newWidthU16 = gsl::narrow_cast(newWidth); - // There is a special case here. If the row has a "wrap" - // flag on it, but the right isn't equal to the width (one - // index past the final valid index in the row) then there - // were a bunch trailing of spaces in the row. - // (But the measuring functions for each row Left/Right do - // not count spaces as "displayable" so they're not - // included.) - // As such, adjust the "right" to be the width of the row - // to capture all these spaces - if (row.WasWrapForced()) + // Copy oldBuffer into newBuffer until oldBuffer has been fully consumed. + for (; oldY < oldHeight && newY < newYLimit; ++oldY) + { + const auto& oldRow = oldBuffer.GetRowByOffset(oldY); + + // A pair of double height rows should optimally wrap as a union (i.e. after wrapping there should be 4 lines). + // But for this initial implementation I chose the alternative approach: Just truncate them. + if (oldRow.GetLineRendition() != LineRendition::SingleWidth) { - iRight = cOldColsTotal; - - // And a combined special case. - // If we wrapped off the end of the row by adding a - // piece of padding because of a double byte LEADING - // character, then remove one from the "right" to - // leave this padding out of the copy process. - if (row.WasDoubleBytePadded()) + // Since rows with a non-standard line rendition should be truncated it's important + // that we pretend as if the previous row ended in a newline, even if it didn't. + // This is what this if does: It newlines. + if (newX) { - iRight--; + newX = 0; + newY++; } - } - // Loop through every character in the current row (up to - // the "right" boundary, which is one past the final valid - // character) - til::CoordType iOldCol = 0; - const auto copyRight = iRight; - for (; iOldCol < copyRight; iOldCol++) - { - if (iOldCol == cOldCursorPos.x && iOldRow == cOldCursorPos.y) + auto& newRow = newBuffer.GetMutableRowByOffset(newY); + + // See the comment marked with "REFLOW_RESET". + if (newY >= newHeight) { - cNewCursorPos = newCursor.GetPosition(); - fFoundCursorPos = true; + newRow.Reset(newBuffer._initialAttributes); } - // TODO: MSFT: 19446208 - this should just use an iterator and the inserter... - const auto glyph = row.GlyphAt(iOldCol); - const auto dbcsAttr = row.DbcsAttrAt(iOldCol); - const auto textAttr = row.GetAttrByColumn(iOldCol); + newRow.CopyFrom(oldRow); + newRow.SetWrapForced(false); - newBuffer.InsertCharacter(glyph, dbcsAttr, textAttr); + if (oldY == oldCursorPos.y) + { + newCursorPos = { newRow.AdjustToGlyphStart(oldCursorPos.x), newY }; + } + if (oldY >= mutableViewportTop) + { + positionInfo->mutableViewportTop = newY; + mutableViewportTop = til::CoordTypeMax; + } + if (oldY >= visibleViewportTop) + { + positionInfo->visibleViewportTop = newY; + visibleViewportTop = til::CoordTypeMax; + } + + newY++; + continue; } - // GH#32: Copy the attributes from the rest of the row into this new buffer. - // From where we are in the old buffer, to the end of the row, copy the - // remaining attributes. - // - if the old buffer is smaller than the new buffer, then just copy - // what we have, as it was. We already copied all _text_ with colors, - // but it's possible for someone to just put some color into the - // buffer to the right of that without any text (as just spaces). The - // buffer looks weird to the user when we resize and it starts losing - // those colors, so we need to copy them over too... as long as there - // is space. The last attr in the row will be extended to the end of - // the row in the new buffer. - // - if the old buffer is WIDER, than we might have wrapped onto a new - // line. Use the cursor's position's Y so that we know where the new - // row is, and start writing at the cursor position. Again, the attr - // in the last column of the old row will be extended to the end of the - // row that the text was flowed onto. - // - if the text in the old buffer didn't actually fill the whole - // line in the new buffer, then we didn't wrap. That's fine. just - // copy attributes from the old row till the end of the new row, and - // move on. - const auto newRowY = newCursor.GetPosition().y; - auto& newRow = newBuffer.GetMutableRowByOffset(newRowY); - auto newAttrColumn = newCursor.GetPosition().x; - const auto newWidth = newBuffer.GetLineWidth(newRowY); - // Stop when we get to the end of the buffer width, or the new position - // for inserting an attr would be past the right of the new buffer. - for (auto copyAttrCol = iOldCol; - copyAttrCol < cOldColsTotal && newAttrColumn < newWidth; - copyAttrCol++, newAttrColumn++) + // Rows don't store any information for what column the last written character is in. + // We simply truncate all trailing whitespace in this implementation. + auto oldRowLimit = oldRow.MeasureRight(); + if (oldY == oldCursorPos.y) { - // TODO: MSFT: 19446208 - this should just use an iterator and the inserter... - const auto textAttr = row.GetAttrByColumn(copyAttrCol); - newRow.SetAttrToEnd(newAttrColumn, textAttr); + // REFLOW_JANK_CURSOR_WRAP: + // Pretending as if there's always at least whitespace in front of the cursor has the benefit that + // * the cursor retains its distance from any preceding text. + // * when a client application starts writing on this new, empty line, + // enlarging the buffer unwraps the text onto the preceding line. + oldRowLimit = std::max(oldRowLimit, oldCursorPos.x + 1); } - // If we found the old row that the caller was interested in, set the - // out value of that parameter to the cursor's current Y position (the - // new location of the _end_ of that row in the buffer). - if (positionInfo.has_value()) + til::CoordType oldX = 0; + + // Copy oldRow into newBuffer until oldRow has been fully consumed. + // We use a do-while loop to ensure that line wrapping occurs and + // that attributes are copied over even for seemingly empty rows. + do { - if (!foundOldMutable) + // This if condition handles line wrapping. + // Only if we write past the last column we should wrap and as such this if + // condition is in front of the text insertion code instead of behind it. + // A SetWrapForced of false implies an explicit newline, which is the default. + if (newX >= newWidth) { - if (iOldRow >= positionInfo.value().get().mutableViewportTop) - { - positionInfo.value().get().mutableViewportTop = newCursor.GetPosition().y; - foundOldMutable = true; - } + newBuffer.GetMutableRowByOffset(newY).SetWrapForced(true); + newX = 0; + newY++; } - if (!foundOldVisible) + // REFLOW_RESET: + // If we shrink the buffer vertically, for instance from 100 rows to 90 rows, we will write 10 rows in the + // new buffer twice. We need to reset them before copying text, or otherwise we'll see the previous contents. + // We don't need to be smart about this. Reset() is fast and shrinking doesn't occur often. + if (newY >= newHeight && newX == 0) { - if (iOldRow >= positionInfo.value().get().visibleViewportTop) + // We need to ensure not to overwrite the row the cursor is on. + if (newY >= newYLimit) { - positionInfo.value().get().visibleViewportTop = newCursor.GetPosition().y; - foundOldVisible = true; + break; } + newBuffer.GetMutableRowByOffset(newY).Reset(newBuffer._initialAttributes); } - } - // If we didn't have a full row to copy, insert a new - // line into the new buffer. - // Only do so if we were not forced to wrap. If we did - // force a word wrap, then the existing line break was - // only because we ran out of space. - if (iRight < cOldColsTotal && !row.WasWrapForced()) - { - if (!fFoundCursorPos && (iRight == cOldCursorPos.x && iOldRow == cOldCursorPos.y)) + auto& newRow = newBuffer.GetMutableRowByOffset(newY); + + RowCopyTextFromState state{ + .source = oldRow, + .columnBegin = newX, + .columnLimit = til::CoordTypeMax, + .sourceColumnBegin = oldX, + .sourceColumnLimit = oldRowLimit, + }; + newRow.CopyTextFrom(state); + + const auto& oldAttr = oldRow.Attributes(); + auto& newAttr = newRow.Attributes(); + const auto attributes = oldAttr.slice(gsl::narrow_cast(oldX), oldAttr.size()); + newAttr.replace(gsl::narrow_cast(newX), newAttr.size(), attributes); + newAttr.resize_trailing_extent(newWidthU16); + + if (oldY == oldCursorPos.y && oldCursorPos.x >= oldX) { - cNewCursorPos = newCursor.GetPosition(); - fFoundCursorPos = true; + // In theory AdjustToGlyphStart ensures we don't put the cursor on a trailing wide glyph. + // In practice I don't think that this can possibly happen. Better safe than sorry. + newCursorPos = { newRow.AdjustToGlyphStart(oldCursorPos.x - oldX + newX), newY }; + // If there's so much text past the old cursor position that it doesn't fit into new buffer, + // then the new cursor position will be "lost", because it's overwritten by unrelated text. + // We have two choices how can handle this: + // * If the new cursor is at an y < 0, just put the cursor at (0,0) + // * Stop writing into the new buffer before we overwrite the new cursor position + // This implements the second option. There's no fundamental reason why this is better. + newYLimit = newY + newHeight; } - // Only do this if it's not the final line in the buffer. - // On the final line, we want the cursor to sit - // where it is done printing for the cursor - // adjustment to follow. - if (iOldRow < cOldRowsTotal - 1) + if (oldY >= mutableViewportTop) { - newBuffer.NewlineCursor(); + positionInfo->mutableViewportTop = newY; + mutableViewportTop = til::CoordTypeMax; } - else + if (oldY >= visibleViewportTop) { - // If we are on the final line of the buffer, we have one more check. - // We got into this code path because we are at the right most column of a row in the old buffer - // that had a hard return (no wrap was forced). - // However, as we're inserting, the old row might have just barely fit into the new buffer and - // caused a new soft return (wrap was forced) putting the cursor at x=0 on the line just below. - // We need to preserve the memory of the hard return at this point by inserting one additional - // hard newline, otherwise we've lost that information. - // We only do this when the cursor has just barely poured over onto the next line so the hard return - // isn't covered by the soft one. - // e.g. - // The old line was: - // |aaaaaaaaaaaaaaaaaaa | with no wrap which means there was a newline after that final a. - // The cursor was here ^ - // And the new line will be: - // |aaaaaaaaaaaaaaaaaaa| and show a wrap at the end - // | | - // ^ and the cursor is now there. - // If we leave it like this, we've lost the newline information. - // So we insert one more newline so a continued reflow of this buffer by resizing larger will - // continue to look as the original output intended with the newline data. - // After this fix, it looks like this: - // |aaaaaaaaaaaaaaaaaaa| no wrap at the end (preserved hard newline) - // | | - // ^ and the cursor is now here. - const auto coordNewCursor = newCursor.GetPosition(); - if (coordNewCursor.x == 0 && coordNewCursor.y > 0) - { - if (newBuffer.GetRowByOffset(coordNewCursor.y - 1).WasWrapForced()) - { - newBuffer.NewlineCursor(); - } - } + positionInfo->visibleViewportTop = newY; + visibleViewportTop = til::CoordTypeMax; } + + oldX = state.sourceColumnEnd; + newX = state.columnEnd; + } while (oldX < oldRowLimit); + + // If the row had an explicit newline we also need to newline. :) + if (!oldRow.WasWrapForced()) + { + newX = 0; + newY++; } } @@ -2617,85 +2588,40 @@ try // printable character. This is to fix the `color 2f` scenario, where you // change the buffer colors then resize and everything below the last // printable char gets reset. See GH #12567 - auto newRowY = newCursor.GetPosition().y + 1; - const auto newHeight = newBuffer.GetSize().Height(); - const auto oldHeight = oldBuffer._estimateOffsetOfLastCommittedRow() + 1; - for (; - iOldRow < oldHeight && newRowY < newHeight; - iOldRow++) + const auto initializedRowsEnd = oldBuffer._estimateOffsetOfLastCommittedRow() + 1; + for (; oldY < initializedRowsEnd && newY < newHeight; oldY++, newY++) { - const auto& row = oldBuffer.GetRowByOffset(iOldRow); - - // Optimization: Since all these rows are below the last printable char, - // we can reasonably assume that they are filled with just spaces. - // That's convenient, we can just copy the attr row from the old buffer - // into the new one, and resize the row to match. We'll rely on the - // behavior of ATTR_ROW::Resize to trim down when narrower, or extend - // the last attr when wider. - auto& newRow = newBuffer.GetMutableRowByOffset(newRowY); - const auto newWidth = newBuffer.GetLineWidth(newRowY); - newRow.TransferAttributes(row.Attributes(), newWidth); - - newRowY++; + auto& oldRow = oldBuffer.GetRowByOffset(oldY); + auto& newRow = newBuffer.GetMutableRowByOffset(newY); + auto& newAttr = newRow.Attributes(); + newAttr = oldRow.Attributes(); + newAttr.resize_trailing_extent(newWidthU16); } - // Finish copying remaining parameters from the old text buffer to the new one - newBuffer.CopyProperties(oldBuffer); - newBuffer.CopyHyperlinkMaps(oldBuffer); - - // If we found where to put the cursor while placing characters into the buffer, - // just put the cursor there. Otherwise we have to advance manually. - if (fFoundCursorPos) + // Since we didn't use IncrementCircularBuffer() we need to compute the proper + // _firstRow offset now, in a way that replicates IncrementCircularBuffer(). + // We need to do the same for newCursorPos.y for basically the same reason. + if (newY > newHeight) { - newCursor.SetPosition(cNewCursorPos); + newBuffer._firstRow = newY % newHeight; + // _firstRow maps from API coordinates that always start at 0,0 in the top left corner of the + // terminal's scrollback, to the underlying buffer Y coordinate via `(y + _firstRow) % height`. + // Here, we need to un-map the `newCursorPos.y` from the underlying Y coordinate to the API coordinate + // and so we do `(y - _firstRow) % height`, but we add `+ newHeight` to avoid getting negative results. + newCursorPos.y = (newCursorPos.y - newBuffer._firstRow + newHeight) % newHeight; } - else - { - // Advance the cursor to the same offset as before - // get the number of newlines and spaces between the old end of text and the old cursor, - // then advance that many newlines and chars - auto iNewlines = cOldCursorPos.y - cOldLastChar.y; - const auto iIncrements = cOldCursorPos.x - cOldLastChar.x; - const auto cNewLastChar = newBuffer.GetLastNonSpaceCharacter(); - // If the last row of the new buffer wrapped, there's going to be one less newline needed, - // because the cursor is already on the next line - if (newBuffer.GetRowByOffset(cNewLastChar.y).WasWrapForced()) - { - iNewlines = std::max(iNewlines - 1, 0); - } - else - { - // if this buffer didn't wrap, but the old one DID, then the d(columns) of the - // old buffer will be one more than in this buffer, so new need one LESS. - if (oldBuffer.GetRowByOffset(cOldLastChar.y).WasWrapForced()) - { - iNewlines = std::max(iNewlines - 1, 0); - } - } - - for (auto r = 0; r < iNewlines; r++) - { - newBuffer.NewlineCursor(); - } - for (auto c = 0; c < iIncrements - 1; c++) - { - newBuffer.IncrementCursor(); - } - } - - // Save old cursor size before we delete it - const auto ulSize = oldCursor.GetSize(); + newBuffer.CopyProperties(oldBuffer); + newBuffer.CopyHyperlinkMaps(oldBuffer); - // Set size back to real size as it will be taking over the rendering duties. - newCursor.SetSize(ulSize); + assert(newCursorPos.x >= 0 && newCursorPos.x < newWidth); + assert(newCursorPos.y >= 0 && newCursorPos.y < newHeight); + newCursor.SetSize(oldCursor.GetSize()); + newCursor.SetPosition(newCursorPos); newBuffer._marks = oldBuffer._marks; newBuffer._trimMarksOutsideBuffer(); - - return S_OK; } -CATCH_RETURN() // Method Description: // - Adds or updates a hyperlink in our hyperlink table @@ -2916,14 +2842,10 @@ void TextBuffer::AddMark(const ScrollMark& m) void TextBuffer::_trimMarksOutsideBuffer() { - const auto height = GetSize().Height(); - _marks.erase(std::remove_if(_marks.begin(), - _marks.end(), - [height](const auto& m) { - return (m.start.y < 0) || - (m.start.y >= height); - }), - _marks.end()); + const til::CoordType height = _height; + std::erase_if(_marks, [height](const auto& m) { + return (m.start.y < 0) || (m.start.y >= height); + }); } std::wstring_view TextBuffer::CurrentCommand() const diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp index 360c74aab8c..a9d2e1714b6 100644 --- a/src/buffer/out/textBuffer.hpp +++ b/src/buffer/out/textBuffer.hpp @@ -163,7 +163,7 @@ class TextBuffer final // Scroll needs access to this to quickly rotate around the buffer. void IncrementCircularBuffer(const TextAttribute& fillAttributes = {}); - til::point GetLastNonSpaceCharacter(std::optional viewOptional = std::nullopt) const; + til::point GetLastNonSpaceCharacter(const Microsoft::Console::Types::Viewport* viewOptional = nullptr) const; Cursor& GetCursor() noexcept; const Cursor& GetCursor() const noexcept; @@ -194,7 +194,7 @@ class TextBuffer final void Reset() noexcept; - [[nodiscard]] HRESULT ResizeTraditional(const til::size newSize) noexcept; + void ResizeTraditional(const til::size newSize); void SetAsActiveBuffer(const bool isActiveBuffer) noexcept; bool IsActiveBuffer() const noexcept; @@ -262,10 +262,7 @@ class TextBuffer final til::CoordType visibleViewportTop{ 0 }; }; - static HRESULT Reflow(TextBuffer& oldBuffer, - TextBuffer& newBuffer, - const std::optional lastCharacterViewport, - std::optional> positionInfo); + static void Reflow(TextBuffer& oldBuffer, TextBuffer& newBuffer, const Microsoft::Console::Types::Viewport* lastCharacterViewport = nullptr, PositionInformation* positionInfo = nullptr); std::vector SearchText(const std::wstring_view& needle, bool caseInsensitive) const; std::vector SearchText(const std::wstring_view& needle, bool caseInsensitive, til::CoordType rowBeg, til::CoordType rowEnd) const; diff --git a/src/buffer/out/ut_textbuffer/ReflowTests.cpp b/src/buffer/out/ut_textbuffer/ReflowTests.cpp index 3c99a9fb583..d1fad4c407f 100644 --- a/src/buffer/out/ut_textbuffer/ReflowTests.cpp +++ b/src/buffer/out/ut_textbuffer/ReflowTests.cpp @@ -46,8 +46,6 @@ namespace std::vector buffers; }; - static constexpr auto true_due_to_exact_wrap_bug{ true }; - static const TestCase testCases[] = { TestCase{ L"No reflow required", @@ -61,7 +59,7 @@ namespace { L"EFG ", false }, { L" ", false }, }, - { 0, 1 } // cursor on $ + { 0, 1 }, // cursor on $ }, TestBuffer{ { 5, 5 }, // reduce width by 1 @@ -72,7 +70,7 @@ namespace { L"EFG ", false }, { L" ", false }, }, - { 0, 1 } // cursor on $ + { 0, 1 }, // cursor on $ }, TestBuffer{ { 4, 5 }, @@ -83,7 +81,7 @@ namespace { L"EFG ", false }, { L" ", false }, }, - { 0, 1 } // cursor on $ + { 0, 1 }, // cursor on $ }, }, }, @@ -99,40 +97,40 @@ namespace { L" ", false }, { L" ", false }, }, - { 0, 1 } // cursor on $ + { 0, 1 }, // cursor on $ }, TestBuffer{ { 5, 5 }, // reduce width by 1 { { L"ABCDE", true }, - { L"F$ ", false }, // [BUG] EXACT WRAP. $ should be alone on next line. - { L" ", false }, + { L"F ", false }, + { L"$ ", false }, { L" ", false }, { L" ", false }, }, - { 1, 1 } // cursor on $ + { 0, 2 }, // cursor on $ }, TestBuffer{ { 6, 5 }, // grow width back to original { - { L"ABCDEF", true_due_to_exact_wrap_bug }, + { L"ABCDEF", false }, { L"$ ", false }, { L" ", false }, { L" ", false }, { L" ", false }, }, - { 0, 1 } // cursor on $ + { 0, 1 }, // cursor on $ }, TestBuffer{ { 7, 5 }, // grow width wider than original { - { L"ABCDEF$", true_due_to_exact_wrap_bug }, // EXACT WRAP BUG: $ should be alone on next line - { L" ", false }, + { L"ABCDEF ", false }, + { L"$ ", false }, { L" ", false }, { L" ", false }, { L" ", false }, }, - { 6, 0 } // cursor on $ + { 0, 1 }, // cursor on $ }, }, }, @@ -148,7 +146,7 @@ namespace { L" ", false }, { L" ", false }, }, - { 1, 1 } // cursor on $ + { 1, 1 }, // cursor on $ }, TestBuffer{ { 5, 5 }, // reduce width by 1 @@ -159,7 +157,7 @@ namespace { L" ", false }, { L" ", false }, }, - { 2, 1 } // cursor on $ + { 2, 1 }, // cursor on $ }, TestBuffer{ { 6, 5 }, // grow width back to original @@ -170,7 +168,7 @@ namespace { L" ", false }, { L" ", false }, }, - { 1, 1 } // cursor on $ + { 1, 1 }, // cursor on $ }, TestBuffer{ { 7, 5 }, // grow width wider than original @@ -181,7 +179,7 @@ namespace { L" ", false }, { L" ", false }, }, - { 0, 1 } // cursor on $ + { 0, 1 }, // cursor on $ }, }, }, @@ -197,29 +195,29 @@ namespace { L"EFG ", false }, { L" ", false }, }, - { 0, 1 } // cursor on $ + { 0, 1 }, // cursor on $ }, TestBuffer{ { 7, 5 }, // reduce width by 1 { { L"AB $", true }, - { L" CD", true_due_to_exact_wrap_bug }, - { L" ", false }, + { L" CD", false }, // CD ends with a newline -> .wrap = false { L"EFG ", false }, { L" ", false }, + { L" ", false }, }, - { 6, 0 } // cursor on $ + { 6, 0 }, // cursor on $ }, TestBuffer{ { 8, 5 }, { { L"AB $ ", true }, - { L" CD ", false }, // Goes to false because we hit the end of ..CD - { L"EFG ", false }, // [BUG] EFG moves up due to exact wrap bug above + { L" CD ", false }, + { L"EFG ", false }, { L" ", false }, { L" ", false }, }, - { 6, 0 } // cursor on $ + { 6, 0 }, // cursor on $ }, }, }, @@ -236,19 +234,19 @@ namespace { L" ", false }, { L" ", false }, }, - { 2, 1 } // cursor on $ + { 2, 1 }, // cursor on $ }, TestBuffer{ { 5, 5 }, // reduce width by 1 { //--012345-- { L"カタ ", true }, // KA TA [FORCED SPACER] - { L"カナ$", true_due_to_exact_wrap_bug }, // KA NA + { L"カナ$", false }, // KA NA { L" ", false }, { L" ", false }, { L" ", false }, }, - { 4, 1 } // cursor on $ + { 4, 1 }, // cursor on $ }, TestBuffer{ { 6, 5 }, // grow width back to original @@ -260,7 +258,7 @@ namespace { L" ", false }, { L" ", false }, }, - { 2, 1 } // cursor on $ + { 2, 1 }, // cursor on $ }, TestBuffer{ { 7, 5 }, // grow width wider than original (by one; no visible change!) @@ -272,7 +270,7 @@ namespace { L" ", false }, { L" ", false }, }, - { 2, 1 } // cursor on $ + { 2, 1 }, // cursor on $ }, TestBuffer{ { 8, 5 }, // grow width enough to fit second DBCS @@ -284,7 +282,7 @@ namespace { L" ", false }, { L" ", false }, }, - { 0, 1 } // cursor on $ + { 0, 1 }, // cursor on $ }, }, }, @@ -300,41 +298,29 @@ namespace { L"MNOPQR", false }, { L"STUVWX", false }, }, - { 0, 1 } // cursor on $ + { 0, 1 }, // cursor on $ }, TestBuffer{ { 5, 5 }, // reduce width by 1 { - { L"F$ ", false }, - { L"GHIJK", true }, // [BUG] We should see GHIJK\n L\n MNOPQ\n R\n - { L"LMNOP", true }, // The wrapping here is irregular - { L"QRSTU", true }, - { L"VWX ", false }, + { L"$ ", false }, + { L"GHIJK", true }, + { L"L ", false }, + { L"MNOPQ", true }, + { L"R ", false }, }, - { 1, 1 } // [BUG] cursor moves to 1,1 instead of sticking with the $ + { 0, 0 }, }, TestBuffer{ { 6, 5 }, // going back to 6,5, the data lost has been destroyed { - //{ L"F$ ", false }, // [BUG] this line is erroneously destroyed too! - { L"GHIJKL", true }, - { L"MNOPQR", true }, - { L"STUVWX", true }, + { L"$ ", false }, + { L"GHIJKL", false }, + { L"MNOPQR", false }, + { L" ", false }, { L" ", false }, - { L" ", false }, // [BUG] this line is added - }, - { 1, 1 }, // [BUG] cursor does not follow [H], it sticks at 1,1 - }, - TestBuffer{ - { 7, 5 }, // a number of errors are carried forward from the previous buffer - { - { L"GHIJKLM", true }, - { L"NOPQRST", true }, - { L"UVWX ", false }, // [BUG] This line loses wrap for some reason - { L" ", false }, - { L" ", false }, }, - { 0, 1 }, // [BUG] The cursor moves to 0, 1 now, sticking with the [N] from before + { 0, 0 }, }, }, }, @@ -353,18 +339,18 @@ namespace { L" ", false }, { L" ", false }, }, - { 1, 1 } // cursor *after* $ + { 1, 1 }, // cursor *after* $ }, TestBuffer{ { 5, 5 }, // reduce width by 1 { { L"ABCDE", true }, - { L"F$ ", false }, // [BUG] EXACT WRAP. $ should be alone on next line. - { L" ", false }, + { L"F ", false }, + { L"$ ", false }, { L" ", false }, { L" ", false }, }, - { 2, 1 } // cursor follows space after $ to next line + { 1, 2 }, // cursor follows space after $ to next line }, }, }, @@ -380,7 +366,7 @@ namespace { L"STUVWX", true }, { L"YZ0 $ ", false }, }, - { 5, 4 } // cursor *after* $ + { 5, 4 }, // cursor *after* $ }, TestBuffer{ { 5, 5 }, // reduce width by 1 @@ -391,7 +377,7 @@ namespace { L"UVWXY", true }, { L"Z0 $ ", false }, }, - { 4, 4 } // cursor follows space after $ to newly introduced bottom line + { 4, 4 }, // cursor follows space after $ to newly introduced bottom line }, }, }, @@ -402,40 +388,36 @@ namespace { 6, 5 }, { { L"ABCDEF", false }, - // v cursor { L"$ ", false }, - // ^ cursor { L" ", false }, { L" ", false }, { L" ", false }, }, - { 5, 1 } // cursor in space far after $ + { 5, 1 }, // The cursor is 5 columns to the right of the $ (last column). }, TestBuffer{ { 5, 5 }, // reduce width by 1 { { L"ABCDE", true }, - { L"F$ ", true }, // [BUG] This line is marked wrapped, and I do not know why - // v cursor - { L" ", false }, - // ^ cursor + { L"F ", false }, + // The reflow implementation marks a wrapped cursor as a forced row-wrap (= the row is padded with whitespace), so that when + // the buffer is enlarged again, we restore the original cursor position correctly. That's why it says .cursor={5,1} below. + { L"$ ", true }, { L" ", false }, { L" ", false }, }, - { 1, 2 } // cursor stays same linear distance from $ + { 0, 3 }, // $ is now at 0,2 and the cursor used to be 5 columns to the right. -> 0,3 }, TestBuffer{ { 6, 5 }, // grow back to original size { - { L"ABCDEF", true_due_to_exact_wrap_bug }, - // v cursor [BUG] cursor does not retain linear distance from $ + { L"ABCDEF", false }, { L"$ ", false }, - // ^ cursor { L" ", false }, { L" ", false }, { L" ", false }, }, - { 4, 1 } // cursor stays same linear distance from $ + { 5, 1 }, }, }, }, @@ -446,39 +428,37 @@ namespace { 6, 5 }, { { L"ABCDEF", false }, - // v cursor { L"$ ", false }, - // ^ cursor { L"BLAH ", false }, { L"BLAH ", false }, { L" ", false }, }, - { 5, 1 } // cursor in space far after $ + { 5, 1 }, // The cursor is 5 columns to the right of the $ (last column). }, TestBuffer{ { 5, 5 }, // reduce width by 1 { - { L"ABCDE", true }, - { L"F$ ", false }, - { L"BLAH ", false }, - { L"BLAH ", true }, // [BUG] this line wraps, no idea why - // v cursor [BUG] cursor erroneously moved to end of all content + { L"F ", false }, + // The reflow implementation pads the row with the cursor with whitespace. + // Search for "REFLOW_JANK_CURSOR_WRAP" to find the corresponding code. + { L"$ ", true }, { L" ", false }, - // ^ cursor + { L"BLAH ", false }, + { L"BLAH ", false }, }, - { 0, 4 } }, + { 0, 2 }, + }, TestBuffer{ { 6, 5 }, // grow back to original size { - { L"ABCDEF", true }, + { L"F ", false }, { L"$ ", false }, { L"BLAH ", false }, - // v cursor [BUG] cursor is pulled up to previous line because it was marked wrapped { L"BLAH ", false }, - // ^ cursor { L" ", false }, }, - { 5, 3 } }, + { 5, 1 }, + }, }, }, TestCase{ @@ -489,27 +469,24 @@ namespace { 6, 5 }, { { L"ABCDEF", false }, - // v cursor { L"$ ", false }, - // ^ cursor { L" ", false }, { L" ", false }, { L" ", false }, }, - { 5, 1 } // cursor in space far after $ + { 5, 1 }, // The cursor is 5 columns to the right of the $ (last column). }, TestBuffer{ { 2, 5 }, // reduce width aggressively { { L"CD", true }, - { L"EF", true }, + { L"EF", false }, { L"$ ", true }, { L" ", true }, - // v cursor { L" ", false }, - // ^ cursor }, - { 1, 4 } }, + { 1, 4 }, + }, }, }, TestCase{ @@ -519,27 +496,24 @@ namespace { 6, 5 }, { { L"ABCDEF", true }, - // v cursor { L"$ ", true }, - // ^ cursor { L" ", true }, { L" ", true }, { L" ", true }, }, - { 5, 1 } // cursor in space far after $ + { 5, 1 }, // cursor in space far after $ }, TestBuffer{ { 2, 5 }, // reduce width aggressively { - { L"EF", true }, - { L"$ ", true }, { L" ", true }, { L" ", true }, - // v cursor [BUG] cursor does not maintain linear distance from $ - { L" ", false }, - // ^ cursor + { L" ", true }, + { L" ", true }, + { L" ", true }, }, - { 1, 4 } }, + { 1, 0 }, + }, }, }, TestCase{ @@ -549,14 +523,12 @@ namespace { 6, 5 }, { { L"ABCDEF", true }, - // v cursor { L"$ ", true }, - // ^ cursor { L" ", true }, { L" ", true }, { L" Q", true }, }, - { 5, 1 } // cursor in space far after $ + { 5, 1 }, // cursor in space far after $ }, TestBuffer{ { 2, 5 }, // reduce width aggressively @@ -564,12 +536,11 @@ namespace { L" ", true }, { L" ", true }, { L" ", true }, - { L" Q", true }, - // v cursor [BUG] cursor jumps to end of world - { L" ", false }, // POTENTIAL [BUG] a whole new blank line is added for the cursor - // ^ cursor + { L" ", true }, + { L" ", true }, }, - { 1, 4 } }, + { 1, 0 }, + }, }, }, TestCase{ @@ -579,27 +550,24 @@ namespace { 6, 5 }, { { L"ABCDEF", false }, - // v cursor { L"$ ", false }, - // ^ cursor { L" ", false }, { L" ", true }, { L" Q", true }, }, - { 5, 1 } // cursor in space far after $ + { 5, 1 }, // cursor in space far after $ }, TestBuffer{ { 2, 5 }, // reduce width aggressively { + { L" ", false }, + { L" ", false }, { L" ", true }, { L" ", true }, { L" ", true }, - { L" Q", true }, - // v cursor [BUG] cursor jumps to different place than fully wrapped case - { L" ", false }, - // ^ cursor }, - { 0, 4 } }, + { 1, 0 }, + }, }, }, TestCase{ @@ -614,24 +582,21 @@ namespace { L"$ ", false }, { L" Q", true }, { L" ", true }, - // v cursor { L" ", true }, - // ^ cursor }, - { 5, 4 } // cursor at end of buffer + { 5, 4 }, // cursor at end of buffer }, TestBuffer{ { 2, 5 }, // reduce width aggressively { + { L" ", false }, + { L" ", true }, + { L" ", true }, { L" ", true }, { L" ", true }, - { L" Q", true }, - { L" ", false }, - // v cursor [BUG] cursor loses linear distance from Q; is this important? - { L" ", false }, - // ^ cursor }, - { 0, 4 } }, + { 1, 0 }, + }, }, }, }; @@ -761,7 +726,7 @@ class ReflowTests static std::unique_ptr _textBufferByReflowingTextBuffer(TextBuffer& originalBuffer, const til::size newSize) { auto buffer = std::make_unique(newSize, TextAttribute{ 0x7 }, 0, false, renderer); - TextBuffer::Reflow(originalBuffer, *buffer, std::nullopt, std::nullopt); + TextBuffer::Reflow(originalBuffer, *buffer); return buffer; } diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index b2a99833587..fe2417b1f97 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -229,6 +229,7 @@ std::wstring_view Terminal::GetWorkingDirectory() noexcept // nothing to do (the viewportSize is the same as our current size), or an // appropriate HRESULT for failing to resize. [[nodiscard]] HRESULT Terminal::UserResize(const til::size viewportSize) noexcept +try { const auto oldDimensions = _GetMutableViewport().Dimensions(); if (viewportSize == oldDimensions) @@ -236,97 +237,59 @@ std::wstring_view Terminal::GetWorkingDirectory() noexcept return S_FALSE; } - // Shortcut: if we're in the alt buffer, just resize the - // alt buffer and put off resizing the main buffer till we switch back. Fortunately, this is easy. We don't need to - // worry about the viewport and scrollback at all! The alt buffer never has - // any scrollback, so we just need to resize it and presto, we're done. if (_inAltBuffer()) { - // stash this resize for the future. + // _deferredResize will indicate to UseMainScreenBuffer() that it needs to reflow the main buffer. + // Deferring the reflow of the main buffer has the benefit that it avoids destroying the state + // of the text buffer any more than necessary. For ConPTY in particular a reflow is destructive, + // because it "forgets" text that wraps beyond the top of its viewport when shrinking it. _deferredResize = viewportSize; - _altBuffer->GetCursor().StartDeferDrawing(); - // we're capturing `this` here because when we exit, we want to EndDefer on the (newly created) active buffer. - auto endDefer = wil::scope_exit([this]() noexcept { _altBuffer->GetCursor().EndDeferDrawing(); }); - - // GH#3494: We don't need to reflow the alt buffer. Apps that use the - // alt buffer will redraw themselves. This prevents graphical artifacts. - // - // This is consistent with VTE - RETURN_IF_FAILED(_altBuffer->ResizeTraditional(viewportSize)); + // GH#3494: We don't need to reflow the alt buffer. Apps that use the alt buffer will + // redraw themselves. This prevents graphical artifacts and is consistent with VTE. + _altBuffer->ResizeTraditional(viewportSize); - // Since the _mutableViewport is no longer the size of the actual - // viewport, then update our _altBufferSize tracker we're using to help - // us out here. _altBufferSize = viewportSize; + _altBuffer->TriggerRedrawAll(); return S_OK; } - const auto dx = viewportSize.width - oldDimensions.width; - const auto newBufferHeight = std::clamp(viewportSize.height + _scrollbackLines, 0, SHRT_MAX); - - til::size bufferSize{ viewportSize.width, newBufferHeight }; - - // This will be used to determine where the viewport should be in the new buffer. - const auto oldViewportTop = _mutableViewport.Top(); - auto newViewportTop = oldViewportTop; - auto newVisibleTop = _VisibleStartIndex(); + const auto newBufferHeight = std::clamp(viewportSize.height + _scrollbackLines, 1, SHRT_MAX); + const til::size bufferSize{ viewportSize.width, newBufferHeight }; // If the original buffer had _no_ scroll offset, then we should be at the // bottom in the new buffer as well. Track that case now. const auto originalOffsetWasZero = _scrollOffset == 0; - // skip any drawing updates that might occur until we swap _buffer with the new buffer or if we exit early. - _mainBuffer->GetCursor().StartDeferDrawing(); - // we're capturing `this` here because when we exit, we want to EndDefer on the (newly created) active buffer. - auto endDefer = wil::scope_exit([this]() noexcept { _mainBuffer->GetCursor().EndDeferDrawing(); }); + // GH#3848 - We'll initialize the new buffer with the default attributes, + // but after the resize, we'll want to make sure that the new buffer's + // current attributes (the ones used for printing new text) match the + // old buffer's. + auto newTextBuffer = std::make_unique(bufferSize, + TextAttribute{}, + 0, + _mainBuffer->IsActiveBuffer(), + _mainBuffer->GetRenderer()); + + // Build a PositionInformation to track the position of both the top of + // the mutable viewport and the top of the visible viewport in the new + // buffer. + // * the new value of mutableViewportTop will be used to figure out + // where we should place the mutable viewport in the new buffer. This + // requires a bit of trickiness to remain consistent with conpty's + // buffer (as seen below). + // * the new value of visibleViewportTop will be used to calculate the + // new scrollOffset in the new buffer, so that the visible lines on + // the screen remain roughly the same. + TextBuffer::PositionInformation positionInfo{ + .mutableViewportTop = _mutableViewport.Top(), + .visibleViewportTop = _VisibleStartIndex(), + }; - // First allocate a new text buffer to take the place of the current one. - std::unique_ptr newTextBuffer; - try - { - // GH#3848 - Stash away the current attributes the old text buffer is - // using. We'll initialize the new buffer with the default attributes, - // but after the resize, we'll want to make sure that the new buffer's - // current attributes (the ones used for printing new text) match the - // old buffer's. - const auto& oldBufferAttributes = _mainBuffer->GetCurrentAttributes(); - newTextBuffer = std::make_unique(bufferSize, - TextAttribute{}, - 0, // temporarily set size to 0 so it won't render. - _mainBuffer->IsActiveBuffer(), - _mainBuffer->GetRenderer()); - - // start defer drawing on the new buffer - newTextBuffer->GetCursor().StartDeferDrawing(); - - // Build a PositionInformation to track the position of both the top of - // the mutable viewport and the top of the visible viewport in the new - // buffer. - // * the new value of mutableViewportTop will be used to figure out - // where we should place the mutable viewport in the new buffer. This - // requires a bit of trickiness to remain consistent with conpty's - // buffer (as seen below). - // * the new value of visibleViewportTop will be used to calculate the - // new scrollOffset in the new buffer, so that the visible lines on - // the screen remain roughly the same. - TextBuffer::PositionInformation oldRows{ 0 }; - oldRows.mutableViewportTop = oldViewportTop; - oldRows.visibleViewportTop = newVisibleTop; - - const std::optional oldViewStart{ oldViewportTop }; - RETURN_IF_FAILED(TextBuffer::Reflow(*_mainBuffer.get(), - *newTextBuffer.get(), - _mutableViewport, - { oldRows })); - - newViewportTop = oldRows.mutableViewportTop; - newVisibleTop = oldRows.visibleViewportTop; - - // Restore the active text attributes - newTextBuffer->SetCurrentAttributes(oldBufferAttributes); - } - CATCH_RETURN(); + TextBuffer::Reflow(*_mainBuffer.get(), *newTextBuffer.get(), &_mutableViewport, &positionInfo); + + // Restore the active text attributes + newTextBuffer->SetCurrentAttributes(_mainBuffer->GetCurrentAttributes()); // Conpty resizes a little oddly - if the height decreased, and there were // blank lines at the bottom, those lines will get trimmed. If there's not @@ -369,7 +332,7 @@ std::wstring_view Terminal::GetWorkingDirectory() noexcept const auto maxRow = std::max(newLastChar.y, newCursorPos.y); const auto proposedTopFromLastLine = maxRow - viewportSize.height + 1; - const auto proposedTopFromScrollback = newViewportTop; + const auto proposedTopFromScrollback = positionInfo.mutableViewportTop; auto proposedTop = std::max(proposedTopFromLastLine, proposedTopFromScrollback); @@ -392,17 +355,13 @@ std::wstring_view Terminal::GetWorkingDirectory() noexcept const auto proposedViewFromTop = Viewport::FromDimensions({ 0, proposedTopFromScrollback }, viewportSize); if (maxRow < proposedViewFromTop.BottomInclusive()) { - if (dx < 0 && proposedTop > 0) + if (viewportSize.width < oldDimensions.width && proposedTop > 0) { - try + const auto& row = newTextBuffer->GetRowByOffset(proposedTop - 1); + if (row.WasWrapForced()) { - const auto& row = newTextBuffer->GetRowByOffset(::base::ClampSub(proposedTop, 1)); - if (row.WasWrapForced()) - { - proposedTop--; - } + proposedTop--; } - CATCH_LOG(); } } } @@ -435,7 +394,7 @@ std::wstring_view Terminal::GetWorkingDirectory() noexcept // GH#3494: Maintain scrollbar position during resize // Make sure that we don't scroll past the mutableViewport at the bottom of the buffer - newVisibleTop = std::min(newVisibleTop, _mutableViewport.Top()); + auto newVisibleTop = std::min(positionInfo.visibleViewportTop, _mutableViewport.Top()); // Make sure we don't scroll past the top of the scrollback newVisibleTop = std::max(newVisibleTop, 0); @@ -443,16 +402,11 @@ std::wstring_view Terminal::GetWorkingDirectory() noexcept // before, and shouldn't be now either. _scrollOffset = originalOffsetWasZero ? 0 : static_cast(::base::ClampSub(_mutableViewport.Top(), newVisibleTop)); - // GH#5029 - make sure to InvalidateAll here, so that we'll paint the entire visible viewport. - try - { - _activeBuffer().TriggerRedrawAll(); - _NotifyScrollEvent(); - } - CATCH_LOG(); - + _mainBuffer->TriggerRedrawAll(); + _NotifyScrollEvent(); return S_OK; } +CATCH_RETURN() void Terminal::Write(std::wstring_view stringView) { @@ -1073,14 +1027,12 @@ const TerminalInput& Terminal::_getTerminalInput() const noexcept // _VisibleStartIndex is the first visible line of the buffer int Terminal::_VisibleStartIndex() const noexcept { - return _inAltBuffer() ? ViewStartIndex() : - std::max(0, ViewStartIndex() - _scrollOffset); + return _inAltBuffer() ? 0 : std::max(0, _mutableViewport.Top() - _scrollOffset); } int Terminal::_VisibleEndIndex() const noexcept { - return _inAltBuffer() ? ViewEndIndex() : - std::max(0, ViewEndIndex() - _scrollOffset); + return _inAltBuffer() ? _altBufferSize.height - 1 : std::max(0, _mutableViewport.BottomInclusive() - _scrollOffset); } Viewport Terminal::_GetVisibleViewport() const noexcept diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index 0f4b1407031..efdb9f8ee77 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -248,33 +248,19 @@ void Terminal::UseAlternateScreenBuffer(const TextAttribute& attrs) } void Terminal::UseMainScreenBuffer() { - // Short-circuit: do nothing. - if (!_inAltBuffer()) + // To make UserResize() work as if we're back in the main buffer, we first need to unset + // _altBuffer, which is used throughout this class as an indicator via _inAltBuffer(). + // + // We delay destroying the alt buffer instance to get a valid altBuffer->GetCursor() reference below. + const auto altBuffer = std::exchange(_altBuffer, nullptr); + if (!altBuffer) { return; } ClearSelection(); - // Copy our cursor state back to the main buffer's cursor - { - // Update the alt buffer's cursor style, visibility, and position to match our own. - const auto& myCursor = _altBuffer->GetCursor(); - auto& tgtCursor = _mainBuffer->GetCursor(); - tgtCursor.SetStyle(myCursor.GetSize(), myCursor.GetType()); - tgtCursor.SetIsVisible(myCursor.IsVisible()); - tgtCursor.SetBlinkingAllowed(myCursor.IsBlinkingAllowed()); - - // The new position should match the viewport-relative position of the main buffer. - // This is the equal and opposite effect of what we did in UseAlternateScreenBuffer - auto tgtCursorPos = myCursor.GetPosition(); - tgtCursorPos.y += _mutableViewport.Top(); - tgtCursor.SetPosition(tgtCursorPos); - } - _mainBuffer->SetAsActiveBuffer(true); - // destroy the alt buffer - _altBuffer = nullptr; if (_deferredResize.has_value()) { @@ -282,6 +268,24 @@ void Terminal::UseMainScreenBuffer() _deferredResize = std::nullopt; } + // After exiting the alt buffer, the main buffer should adopt the current cursor position and style. + // This is the equal and opposite effect of what we did in UseAlternateScreenBuffer and matches xterm. + // + // We have to do this after the call to UserResize() to ensure that the TextBuffer sizes match up. + // Otherwise the cursor position may be temporarily out of bounds and some code may choke on that. + { + const auto& altCursor = altBuffer->GetCursor(); + auto& mainCursor = _mainBuffer->GetCursor(); + + mainCursor.SetStyle(altCursor.GetSize(), altCursor.GetType()); + mainCursor.SetIsVisible(altCursor.IsVisible()); + mainCursor.SetBlinkingAllowed(altCursor.IsBlinkingAllowed()); + + auto tgtCursorPos = altCursor.GetPosition(); + tgtCursorPos.y += _mutableViewport.Top(); + mainCursor.SetPosition(tgtCursorPos); + } + // update all the hyperlinks on the screen _updateUrlDetection(); @@ -293,11 +297,7 @@ void Terminal::UseMainScreenBuffer() _NotifyScrollEvent(); // redraw the screen - try - { - _activeBuffer().TriggerRedrawAll(); - } - CATCH_LOG(); + _activeBuffer().TriggerRedrawAll(); } // NOTE: This is the version of AddMark that comes from VT diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index cb31fa407e8..dd8b635efd9 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -462,40 +462,6 @@ void VtIo::SendCloseEvent() } } -// Method Description: -// - Tell the vt renderer to begin a resize operation. During a resize -// operation, the vt renderer should _not_ request to be repainted during a -// text buffer circling event. Any callers of this method should make sure to -// call EndResize to make sure the renderer returns to normal behavior. -// See GH#1795 for context on this method. -// Arguments: -// - -// Return Value: -// - -void VtIo::BeginResize() -{ - if (_pVtRenderEngine) - { - _pVtRenderEngine->BeginResizeRequest(); - } -} - -// Method Description: -// - Tell the vt renderer to end a resize operation. -// See BeginResize for more details. -// See GH#1795 for context on this method. -// Arguments: -// - -// Return Value: -// - -void VtIo::EndResize() -{ - if (_pVtRenderEngine) - { - _pVtRenderEngine->EndResizeRequest(); - } -} - // The name of this method is an analogy to TCP_CORK. It instructs // the VT renderer to stop flushing its buffer to the output pipe. // Don't forget to uncork it! diff --git a/src/host/VtIo.hpp b/src/host/VtIo.hpp index 3e23350d153..2ecc0d51754 100644 --- a/src/host/VtIo.hpp +++ b/src/host/VtIo.hpp @@ -40,9 +40,6 @@ namespace Microsoft::Console::VirtualTerminal void CloseInput(); void CloseOutput(); - void BeginResize(); - void EndResize(); - void CorkRenderer(bool corked) const noexcept; #ifdef UNIT_TESTING diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp index 58ac0812d52..d02bbaf3d67 100644 --- a/src/host/screenInfo.cpp +++ b/src/host/screenInfo.cpp @@ -1387,6 +1387,7 @@ bool SCREEN_INFORMATION::IsMaximizedY() const // Return Value: // - Success if successful. Invalid parameter if screen buffer size is unexpected. No memory if allocation failed. [[nodiscard]] NTSTATUS SCREEN_INFORMATION::ResizeWithReflow(const til::size coordNewScreenSize) +try { if ((USHORT)coordNewScreenSize.width >= SHORT_MAX || (USHORT)coordNewScreenSize.height >= SHORT_MAX) { @@ -1394,26 +1395,14 @@ bool SCREEN_INFORMATION::IsMaximizedY() const return STATUS_INVALID_PARAMETER; } - // First allocate a new text buffer to take the place of the current one. - std::unique_ptr newTextBuffer; - - // GH#3848 - Stash away the current attributes the old text buffer is using. - // We'll initialize the new buffer with the default attributes, but after - // the resize, we'll want to make sure that the new buffer's current + // GH#3848 - We'll initialize the new buffer with the default attributes, + // but after the resize, we'll want to make sure that the new buffer's current // attributes (the ones used for printing new text) match the old buffer's. - const auto oldPrimaryAttributes = _textBuffer->GetCurrentAttributes(); - try - { - newTextBuffer = std::make_unique(coordNewScreenSize, - TextAttribute{}, - 0, // temporarily set size to 0 so it won't render. - _textBuffer->IsActiveBuffer(), - _textBuffer->GetRenderer()); - } - catch (...) - { - return NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException()); - } + auto newTextBuffer = std::make_unique(coordNewScreenSize, + TextAttribute{}, + 0, // temporarily set size to 0 so it won't render. + _textBuffer->IsActiveBuffer(), + _textBuffer->GetRenderer()); // Save cursor's relative height versus the viewport const auto sCursorHeightInViewportBefore = _textBuffer->GetCursor().GetPosition().y - _viewport.Top(); @@ -1426,38 +1415,35 @@ bool SCREEN_INFORMATION::IsMaximizedY() const // we're capturing _textBuffer by reference here because when we exit, we want to EndDefer on the current active buffer. auto endDefer = wil::scope_exit([&]() noexcept { _textBuffer->GetCursor().EndDeferDrawing(); }); - auto hr = TextBuffer::Reflow(*_textBuffer.get(), *newTextBuffer.get(), std::nullopt, std::nullopt); + TextBuffer::Reflow(*_textBuffer.get(), *newTextBuffer.get()); - if (SUCCEEDED(hr)) - { - // Since the reflow doesn't preserve the virtual bottom, we try and - // estimate where it ought to be by making it the same distance from - // the cursor row as it was before the resize. However, we also need - // to make sure it is far enough down to include the last non-space - // row, and it shouldn't be less than the height of the viewport, - // otherwise the top of the virtual viewport would end up negative. - const auto cursorRow = newTextBuffer->GetCursor().GetPosition().y; - const auto lastNonSpaceRow = newTextBuffer->GetLastNonSpaceCharacter().y; - const auto estimatedBottom = cursorRow + cursorDistanceFromBottom; - const auto viewportBottom = _viewport.Height() - 1; - _virtualBottom = std::max({ lastNonSpaceRow, estimatedBottom, viewportBottom }); + // Since the reflow doesn't preserve the virtual bottom, we try and + // estimate where it ought to be by making it the same distance from + // the cursor row as it was before the resize. However, we also need + // to make sure it is far enough down to include the last non-space + // row, and it shouldn't be less than the height of the viewport, + // otherwise the top of the virtual viewport would end up negative. + const auto cursorRow = newTextBuffer->GetCursor().GetPosition().y; + const auto lastNonSpaceRow = newTextBuffer->GetLastNonSpaceCharacter().y; + const auto estimatedBottom = cursorRow + cursorDistanceFromBottom; + const auto viewportBottom = _viewport.Height() - 1; + _virtualBottom = std::max({ lastNonSpaceRow, estimatedBottom, viewportBottom }); - // We can't let it extend past the bottom of the buffer either. - _virtualBottom = std::min(_virtualBottom, newTextBuffer->GetSize().BottomInclusive()); + // We can't let it extend past the bottom of the buffer either. + _virtualBottom = std::min(_virtualBottom, newTextBuffer->GetSize().BottomInclusive()); - // Adjust the viewport so the cursor doesn't wildly fly off up or down. - const auto sCursorHeightInViewportAfter = cursorRow - _viewport.Top(); - til::point coordCursorHeightDiff; - coordCursorHeightDiff.y = sCursorHeightInViewportAfter - sCursorHeightInViewportBefore; - LOG_IF_FAILED(SetViewportOrigin(false, coordCursorHeightDiff, false)); + // Adjust the viewport so the cursor doesn't wildly fly off up or down. + const auto sCursorHeightInViewportAfter = cursorRow - _viewport.Top(); + til::point coordCursorHeightDiff; + coordCursorHeightDiff.y = sCursorHeightInViewportAfter - sCursorHeightInViewportBefore; + LOG_IF_FAILED(SetViewportOrigin(false, coordCursorHeightDiff, false)); - newTextBuffer->SetCurrentAttributes(oldPrimaryAttributes); + newTextBuffer->SetCurrentAttributes(_textBuffer->GetCurrentAttributes()); - _textBuffer.swap(newTextBuffer); - } - - return NTSTATUS_FROM_HRESULT(hr); + _textBuffer = std::move(newTextBuffer); + return STATUS_SUCCESS; } +NT_CATCH_RETURN() // // Routine Description: @@ -1467,11 +1453,14 @@ bool SCREEN_INFORMATION::IsMaximizedY() const // Return Value: // - Success if successful. Invalid parameter if screen buffer size is unexpected. No memory if allocation failed. [[nodiscard]] NTSTATUS SCREEN_INFORMATION::ResizeTraditional(const til::size coordNewScreenSize) +try { _textBuffer->GetCursor().StartDeferDrawing(); auto endDefer = wil::scope_exit([&]() noexcept { _textBuffer->GetCursor().EndDeferDrawing(); }); - return NTSTATUS_FROM_HRESULT(_textBuffer->ResizeTraditional(coordNewScreenSize)); + _textBuffer->ResizeTraditional(coordNewScreenSize); + return STATUS_SUCCESS; } +NT_CATCH_RETURN() // // Routine Description: @@ -1493,19 +1482,6 @@ bool SCREEN_INFORMATION::IsMaximizedY() const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); auto status = STATUS_SUCCESS; - // If we're in conpty mode, suppress any immediate painting we might do - // during the resize. - if (gci.IsInVtIoMode()) - { - gci.GetVtIo()->BeginResize(); - } - auto endResize = wil::scope_exit([&] { - if (gci.IsInVtIoMode()) - { - gci.GetVtIo()->EndResize(); - } - }); - // cancel any active selection before resizing or it will not necessarily line up with the new buffer positions Selection::Instance().ClearSelection(); diff --git a/src/host/ut_host/ApiRoutinesTests.cpp b/src/host/ut_host/ApiRoutinesTests.cpp index 90d3fd1044b..611eb55f14c 100644 --- a/src/host/ut_host/ApiRoutinesTests.cpp +++ b/src/host/ut_host/ApiRoutinesTests.cpp @@ -607,7 +607,7 @@ class ApiRoutinesTests auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); auto& si = gci.GetActiveOutputBuffer(); - VERIFY_SUCCEEDED(si.GetTextBuffer().ResizeTraditional({ 5, 5 }), L"Make the buffer small so this doesn't take forever."); + si.GetTextBuffer().ResizeTraditional({ 5, 5 }); // Tests are run both with and without the DECSTBM margins set. This should not alter // the results, since ScrollConsoleScreenBuffer should not be affected by VT margins. diff --git a/src/host/ut_host/ScreenBufferTests.cpp b/src/host/ut_host/ScreenBufferTests.cpp index 334d770d0b8..b7ea13e6dc6 100644 --- a/src/host/ut_host/ScreenBufferTests.cpp +++ b/src/host/ut_host/ScreenBufferTests.cpp @@ -2307,7 +2307,7 @@ void ScreenBufferTests::GetWordBoundary() // Make the buffer as big as our test text. const til::size newBufferSize = { gsl::narrow(length), 10 }; - VERIFY_SUCCEEDED(si.GetTextBuffer().ResizeTraditional(newBufferSize)); + si.GetTextBuffer().ResizeTraditional(newBufferSize); const OutputCellIterator it(text, si.GetAttributes()); si.Write(it, { 0, 0 }); @@ -2383,7 +2383,7 @@ void ScreenBufferTests::GetWordBoundaryTrimZeros(const bool on) // Make the buffer as big as our test text. const til::size newBufferSize = { gsl::narrow(length), 10 }; - VERIFY_SUCCEEDED(si.GetTextBuffer().ResizeTraditional(newBufferSize)); + si.GetTextBuffer().ResizeTraditional(newBufferSize); const OutputCellIterator it(text, si.GetAttributes()); si.Write(it, { 0, 0 }); diff --git a/src/host/ut_host/TextBufferTests.cpp b/src/host/ut_host/TextBufferTests.cpp index d52dfc5ae69..d1c7395635f 100644 --- a/src/host/ut_host/TextBufferTests.cpp +++ b/src/host/ut_host/TextBufferTests.cpp @@ -1755,7 +1755,7 @@ void TextBufferTests::ResizeTraditional() auto expectedSpace = UNICODE_SPACE; std::wstring_view expectedSpaceView(&expectedSpace, 1); - VERIFY_SUCCEEDED(buffer.ResizeTraditional(newSize)); + buffer.ResizeTraditional(newSize); Log::Comment(L"Verify every cell in the X dimension is still the same as when filled and the new Y row is just empty default cells."); { @@ -1821,7 +1821,7 @@ void TextBufferTests::ResizeTraditionalRotationPreservesHighUnicode() _buffer->_SetFirstRowIndex(pos.y); // Perform resize to rotate the rows around - VERIFY_NT_SUCCESS(_buffer->ResizeTraditional(bufferSize)); + _buffer->ResizeTraditional(bufferSize); // Retrieve the text at the old and new positions. const auto shouldBeEmptyText = *_buffer->GetTextDataAt(pos); @@ -1893,7 +1893,7 @@ void TextBufferTests::ResizeTraditionalHighUnicodeRowRemoval() // Perform resize to trim off the row of the buffer that included the emoji til::size trimmedBufferSize{ bufferSize.width, bufferSize.height - 1 }; - VERIFY_NT_SUCCESS(_buffer->ResizeTraditional(trimmedBufferSize)); + _buffer->ResizeTraditional(trimmedBufferSize); } // This tests that columns removed from the buffer while resizing traditionally will also drop the high unicode @@ -1923,7 +1923,7 @@ void TextBufferTests::ResizeTraditionalHighUnicodeColumnRemoval() // Perform resize to trim off the column of the buffer that included the emoji til::size trimmedBufferSize{ bufferSize.width - 1, bufferSize.height }; - VERIFY_NT_SUCCESS(_buffer->ResizeTraditional(trimmedBufferSize)); + _buffer->ResizeTraditional(trimmedBufferSize); } void TextBufferTests::TestBurrito() diff --git a/src/inc/til/rle.h b/src/inc/til/rle.h index d94c3a744e6..34b6c929a0b 100644 --- a/src/inc/til/rle.h +++ b/src/inc/til/rle.h @@ -426,7 +426,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" // Returns the range [start_index, end_index) as a new vector. // It works just like std::string::substr(), but with absolute indices. - [[nodiscard]] basic_rle slice(size_type start_index, size_type end_index) const noexcept + [[nodiscard]] basic_rle slice(size_type start_index, size_type end_index) const { if (end_index > _total_length) { @@ -446,14 +446,14 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" // --> It's safe to subtract 1 from end_index rle_scanner scanner(_runs.begin(), _runs.end()); - auto [begin_run, start_run_pos] = scanner.scan(start_index); - auto [end_run, end_run_pos] = scanner.scan(end_index - 1); + const auto [begin_run, start_run_pos] = scanner.scan(start_index); + const auto [end_run, end_run_pos] = scanner.scan(end_index - 1); container slice{ begin_run, end_run + 1 }; slice.back().length = end_run_pos + 1; slice.front().length -= start_run_pos; - return { std::move(slice), static_cast(end_index - start_index) }; + return { std::move(slice), gsl::narrow_cast(end_index - start_index) }; } // Replace the range [start_index, end_index) with the given value. @@ -463,7 +463,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" { _check_indices(start_index, end_index); - const rle_type replacement{ value, static_cast(end_index - start_index) }; + const rle_type replacement{ value, gsl::narrow_cast(end_index - start_index) }; _replace_unchecked(start_index, end_index, { &replacement, 1 }); } @@ -651,7 +651,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" size_type total = 0; }; - basic_rle(container&& runs, size_type size) : + basic_rle(container&& runs, size_type size) noexcept : _runs(std::forward(runs)), _total_length(size) { diff --git a/src/renderer/vt/invalidate.cpp b/src/renderer/vt/invalidate.cpp index 8969b04a39d..7ef59bc03b0 100644 --- a/src/renderer/vt/invalidate.cpp +++ b/src/renderer/vt/invalidate.cpp @@ -106,19 +106,11 @@ CATCH_RETURN(); // - S_OK [[nodiscard]] HRESULT VtEngine::InvalidateFlush(_In_ const bool circled, _Out_ bool* const pForcePaint) noexcept { - // If we're in the middle of a resize request, don't try to immediately start a frame. - if (_inResizeRequest) - { - *pForcePaint = false; - } - else - { - *pForcePaint = true; + *pForcePaint = true; - // Keep track of the fact that we circled, we'll need to do some work on - // end paint to specifically handle this. - _circled = circled; - } + // Keep track of the fact that we circled, we'll need to do some work on + // end paint to specifically handle this. + _circled = circled; _trace.TraceTriggerCircling(*pForcePaint); return S_OK; diff --git a/src/renderer/vt/state.cpp b/src/renderer/vt/state.cpp index b360b7b93a6..8408ee3aeb6 100644 --- a/src/renderer/vt/state.cpp +++ b/src/renderer/vt/state.cpp @@ -49,7 +49,6 @@ VtEngine::VtEngine(_In_ wil::unique_hfile pipe, _terminalOwner{ nullptr }, _newBottomLine{ false }, _deferredCursorPos{ INVALID_COORDS }, - _inResizeRequest{ false }, _trace{}, _bufferLine{}, _buffer{}, @@ -459,34 +458,6 @@ HRESULT VtEngine::RequestCursor() noexcept return S_OK; } -// Method Description: -// - Tell the vt renderer to begin a resize operation. During a resize -// operation, the vt renderer should _not_ request to be repainted during a -// text buffer circling event. Any callers of this method should make sure to -// call EndResize to make sure the renderer returns to normal behavior. -// See GH#1795 for context on this method. -// Arguments: -// - -// Return Value: -// - -void VtEngine::BeginResizeRequest() -{ - _inResizeRequest = true; -} - -// Method Description: -// - Tell the vt renderer to end a resize operation. -// See BeginResize for more details. -// See GH#1795 for context on this method. -// Arguments: -// - -// Return Value: -// - -void VtEngine::EndResizeRequest() -{ - _inResizeRequest = false; -} - // Method Description: // - Configure the renderer for the resize quirk. This changes the behavior of // conpty to _not_ InvalidateAll the entire viewport on a resize operation. diff --git a/src/renderer/vt/vtrenderer.hpp b/src/renderer/vt/vtrenderer.hpp index 208918407da..15de6e0e760 100644 --- a/src/renderer/vt/vtrenderer.hpp +++ b/src/renderer/vt/vtrenderer.hpp @@ -132,7 +132,6 @@ namespace Microsoft::Console::Render Microsoft::Console::VirtualTerminal::VtIo* _terminalOwner; Microsoft::Console::VirtualTerminal::RenderTracing _trace; - bool _inResizeRequest{ false }; std::optional _wrappedRow{ std::nullopt }; diff --git a/src/terminal/adapter/ut_adapter/adapterTest.cpp b/src/terminal/adapter/ut_adapter/adapterTest.cpp index 3c2e1ba4d76..c3192462052 100644 --- a/src/terminal/adapter/ut_adapter/adapterTest.cpp +++ b/src/terminal/adapter/ut_adapter/adapterTest.cpp @@ -2106,26 +2106,26 @@ class AdapterTest auto& stateMachine = *_testGetSet->_stateMachine; Log::Comment(L"Default tabs stops in 80-column mode"); - VERIFY_SUCCEEDED(textBuffer.ResizeTraditional({ 80, 600 })); + textBuffer.ResizeTraditional({ 80, 600 }); _pDispatch->RequestPresentationStateReport(DispatchTypes::PresentationReportFormat::TabulationStopReport); _testGetSet->ValidateInputEvent(L"\033P2$u9/17/25/33/41/49/57/65/73\033\\"); Log::Comment(L"Default tabs stops in 132-column mode"); - VERIFY_SUCCEEDED(textBuffer.ResizeTraditional({ 132, 600 })); + textBuffer.ResizeTraditional({ 132, 600 }); _pDispatch->RequestPresentationStateReport(DispatchTypes::PresentationReportFormat::TabulationStopReport); _testGetSet->ValidateInputEvent(L"\033P2$u9/17/25/33/41/49/57/65/73/81/89/97/105/113/121/129\033\\"); Log::Comment(L"Custom tab stops in 80 columns"); - VERIFY_SUCCEEDED(textBuffer.ResizeTraditional({ 80, 600 })); + textBuffer.ResizeTraditional({ 80, 600 }); _testGetSet->_stateMachine->ProcessString(L"\033P2$t30/60/120/240\033\\"); _pDispatch->RequestPresentationStateReport(DispatchTypes::PresentationReportFormat::TabulationStopReport); _testGetSet->ValidateInputEvent(L"\033P2$u30/60\033\\"); Log::Comment(L"After expanding width to 132 columns"); - VERIFY_SUCCEEDED(textBuffer.ResizeTraditional({ 132, 600 })); + textBuffer.ResizeTraditional({ 132, 600 }); _pDispatch->RequestPresentationStateReport(DispatchTypes::PresentationReportFormat::TabulationStopReport); _testGetSet->ValidateInputEvent(L"\033P2$u30/60/120\033\\"); - VERIFY_SUCCEEDED(textBuffer.ResizeTraditional({ 80, 600 })); + textBuffer.ResizeTraditional({ 80, 600 }); Log::Comment(L"Out of order tab stops"); stateMachine.ProcessString(L"\033P2$t44/22/66\033\\"); diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index 1f56c04b535..af5ebb431ea 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -1335,7 +1335,7 @@ til::point UiaTextRangeBase::_getDocumentEnd() const { const auto optimizedBufferSize{ _getOptimizedBufferSize() }; const auto& buffer{ _pData->GetTextBuffer() }; - const auto lastCharPos{ buffer.GetLastNonSpaceCharacter(optimizedBufferSize) }; + const auto lastCharPos{ buffer.GetLastNonSpaceCharacter(&optimizedBufferSize) }; const auto cursorPos{ buffer.GetCursor().GetPosition() }; return { optimizedBufferSize.Left(), std::max(lastCharPos.y, cursorPos.y) + 1 }; } From f1a868517cb603accf430c0cdf8cab96747ffddd Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Thu, 28 Sep 2023 09:34:03 -0500 Subject: [PATCH 011/167] Fix a crash for users without a `tab` theme (#16046) One day into 1.19, and there's a LOT of hits here (**76.25%** of our ~300 crashes). A crash if the Theme doesn't have a `tab` member. Regressed in #15948 Closes MSFT:46714723 (cherry picked from commit cf193858f6e12da49d98eba541c69a636041f9f3) Service-Card-Id: 90670731 Service-Version: 1.19 --- src/cascadia/TerminalApp/TabManagement.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index d727989162d..bb77019aad5 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -176,7 +176,9 @@ namespace winrt::TerminalApp::implementation { if (!profile.Icon().empty()) { - newTabImpl->UpdateIcon(profile.Icon(), _settings.GlobalSettings().CurrentTheme().Tab().IconStyle()); + const auto theme = _settings.GlobalSettings().CurrentTheme(); + const auto iconStyle = (theme && theme.Tab()) ? theme.Tab().IconStyle() : IconStyle::Default; + newTabImpl->UpdateIcon(profile.Icon(), iconStyle); } } @@ -241,7 +243,9 @@ namespace winrt::TerminalApp::implementation { if (const auto profile = tab.GetFocusedProfile()) { - tab.UpdateIcon(profile.Icon(), _settings.GlobalSettings().CurrentTheme().Tab().IconStyle()); + const auto theme = _settings.GlobalSettings().CurrentTheme(); + const auto iconStyle = (theme && theme.Tab()) ? theme.Tab().IconStyle() : IconStyle::Default; + tab.UpdateIcon(profile.Icon(), iconStyle); } } From 057d1334dbc410434252c45fa1058dcdc4a8bb79 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 28 Sep 2023 08:46:26 -0700 Subject: [PATCH 012/167] Fix URL sanitizer for long URLs (#16026) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit f1aa699 was fundamentally incorrect as it used `IdnToAscii` and `IdnToUnicode` on the entire URL, even though these functions only work on domain names. This commit fixes the issue by using the WinRT `Url` class and its `AbsoluteUri` and `AbsoluteCanonicalUri` getters. The algorithm still works the same way though. Closes #16017 ## Validation Steps Performed * ``"`e]8;;https://www.xn--fcbook-3nf5b.com/`e\test`e]8;;`e\"`` still shows as two URLs in the popup ✅ * Shows the given URI if it's canonical and not an IDN ✅ * Works with >100 char long file:// URIs ✅ (cherry picked from commit 198c11f36d54eb199ced8a2c55bff3e8495a78fe) Service-Card-Id: 90642844 Service-Version: 1.19 --- .github/actions/spelling/allow/apis.txt | 2 + src/cascadia/TerminalControl/TermControl.cpp | 85 +++++++++----------- src/cascadia/inc/cppwinrt_utils.h | 9 +++ 3 files changed, 48 insertions(+), 48 deletions(-) diff --git a/.github/actions/spelling/allow/apis.txt b/.github/actions/spelling/allow/apis.txt index b25346b9ce1..4ce5fe88d6d 100644 --- a/.github/actions/spelling/allow/apis.txt +++ b/.github/actions/spelling/allow/apis.txt @@ -220,6 +220,7 @@ UFIELD ULARGE UOI UPDATEINIFILE +urlmon userenv USEROBJECTFLAGS Vcpp @@ -231,6 +232,7 @@ wcsstr wcstoui WDJ winhttp +wininet winmain winsta winstamin diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 4c12c056930..8a1c9018809 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -4,11 +4,9 @@ #include "pch.h" #include "TermControl.h" -#include #include #include "TermControlAutomationPeer.h" -#include "../../types/inc/GlyphWidth.hpp" #include "../../renderer/atlas/AtlasEngine.h" #include "TermControl.g.cpp" @@ -3208,51 +3206,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation _core.ClearHoveredCell(); } - // Attackers abuse Unicode characters that happen to look similar to ASCII characters. Cyrillic for instance has - // its own glyphs for а, с, е, о, р, х, and у that look practically identical to their ASCII counterparts. - // This is called an "IDN homoglyph attack". - // - // But outright showing Punycode URIs only is similarly flawed as they can end up looking similar to valid ASCII URIs. - // xn--cnn.com for instance looks confusingly similar to cnn.com, but actually represents U+407E. - // - // An optimal solution would detect any URI that contains homoglyphs and show them in their Punycode form. - // Such a detector however is not quite trivial and requires constant maintenance, which this project's - // maintainers aren't currently well equipped to handle. As such we do the next best thing and show the - // Punycode encoding side-by-side with the Unicode string for any IDN. - static winrt::hstring sanitizeURI(winrt::hstring uri) - { - if (uri.empty()) - { - return uri; - } - - wchar_t punycodeBuffer[256]; - wchar_t unicodeBuffer[256]; - - // These functions return int, but are documented to only return positive numbers. - // Better make sure though. It allows us to pass punycodeLength right into IdnToUnicode. - const auto punycodeLength = std::max(0, IdnToAscii(0, uri.data(), gsl::narrow(uri.size()), &punycodeBuffer[0], 256)); - const auto unicodeLength = std::max(0, IdnToUnicode(0, &punycodeBuffer[0], punycodeLength, &unicodeBuffer[0], 256)); - - if (punycodeLength <= 0 || unicodeLength <= 0) - { - return RS_(L"InvalidUri"); - } - - const std::wstring_view punycode{ &punycodeBuffer[0], gsl::narrow_cast(punycodeLength) }; - const std::wstring_view unicode{ &unicodeBuffer[0], gsl::narrow_cast(unicodeLength) }; - - // IdnToAscii/IdnToUnicode return the input string as is if it's all - // plain ASCII. But we don't know if the input URI is Punycode or not. - // --> It's non-Punycode and ASCII if it round-trips. - if (uri == punycode && uri == unicode) - { - return uri; - } - - return winrt::hstring{ fmt::format(FMT_COMPILE(L"{}\n({})"), punycode, unicode) }; - } - void TermControl::_hoveredHyperlinkChanged(const IInspectable& /*sender*/, const IInspectable& /*args*/) { const auto lastHoveredCell = _core.HoveredCell(); @@ -3261,12 +3214,48 @@ namespace winrt::Microsoft::Terminal::Control::implementation return; } - const auto uriText = sanitizeURI(_core.HoveredUriText()); + auto uriText = _core.HoveredUriText(); if (uriText.empty()) { return; } + // Attackers abuse Unicode characters that happen to look similar to ASCII characters. Cyrillic for instance has + // its own glyphs for а, с, е, о, р, х, and у that look practically identical to their ASCII counterparts. + // This is called an "IDN homoglyph attack". + // + // But outright showing Punycode URIs only is similarly flawed as they can end up looking similar to valid ASCII URIs. + // xn--cnn.com for instance looks confusingly similar to cnn.com, but actually represents U+407E. + // + // An optimal solution would detect any URI that contains homoglyphs and show them in their Punycode form. + // Such a detector however is not quite trivial and requires constant maintenance, which this project's + // maintainers aren't currently well equipped to handle. As such we do the next best thing and show the + // Punycode encoding side-by-side with the Unicode string for any IDN. + try + { + // DisplayUri/Iri drop authentication credentials, which is probably great, but AbsoluteCanonicalUri() + // is the only getter that returns a punycode encoding of the URL. AbsoluteUri() is the only possible + // counterpart, but as the name indicates, we'll end up hitting the != below for any non-canonical URL. + // + // This issue can be fixed by using the IUrl API from urlmon.h directly, which the WinRT API simply wraps. + // IUrl is a very complex system with a ton of useful functionality, but we don't rely on it (neither WinRT), + // so we could alternatively use its underlying API in wininet.h (InternetCrackUrlW, etc.). + // That API however is rather difficult to use for such seldom executed code. + const Windows::Foundation::Uri uri{ uriText }; + const auto unicode = uri.AbsoluteUri(); + const auto punycode = uri.AbsoluteCanonicalUri(); + + if (punycode != unicode) + { + const auto text = fmt::format(FMT_COMPILE(L"{}\n({})"), punycode, unicode); + uriText = winrt::hstring{ text }; + } + } + catch (...) + { + uriText = RS_(L"InvalidUri"); + } + const auto panel = SwapChainPanel(); const auto scale = panel.CompositionScaleX(); const auto offset = panel.ActualOffset(); diff --git a/src/cascadia/inc/cppwinrt_utils.h b/src/cascadia/inc/cppwinrt_utils.h index 7ca1943d4b4..17c720614b1 100644 --- a/src/cascadia/inc/cppwinrt_utils.h +++ b/src/cascadia/inc/cppwinrt_utils.h @@ -17,6 +17,15 @@ Revision History: #pragma once +template<> +struct fmt::formatter : fmt::formatter +{ + auto format(const winrt::hstring& str, auto& ctx) + { + return fmt::formatter::format({ str.data(), str.size() }, ctx); + } +}; + // This is a helper macro for both declaring the signature of an event, and // defining the body. Winrt events need a method for adding a callback to the // event and removing the callback. This macro will both declare the method From 3e0b3e39258f685d7f92efd10eddba4aeaaa7960 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Thu, 28 Sep 2023 13:21:13 -0500 Subject: [PATCH 013/167] Theoretical fix for some crashes (#16047) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found this while looking through dumps for failure `f544cf8e-1879-c59b-3f0b-1a364b92b974`. That's MSFT:45210947. (1% of our 1.19 crashes) From the dump I looked at, Looks like, * we're on Windows 10 * We're refrigerating a window * We are pumping the remaining XAML messages as we refrigerate (`_pumpRemainingXamlMessages`) * In there, we're finally getting the `TerminalPage::_CompleteInitialization` * that calls up to the `_root->Initialized` lambda set up in `TerminalWindow::Initialize` * There it tries to get the launch mode from the settings, and explodes. Presumably _settings is null, but can't see in this dump. so the window is closing before it's initialized. When we `_warmWindow = std::move(_host->Refrigerate())`, we call `AppHost::Refrigerate`, which will null out the TerminalWindow. So when we're getting to `TerminalWindow::Initialize`, we're calling that on a nullptr. That's the trick. We need to revoke the internal Initialized callback. Which makes sense. It's a lambda that binds _this_ 🤦 --- After more looking, it really doesn't _seem_ like the stacks that are tracked in `f544cf8e-1879-c59b-3f0b-1a364b92b974` look like the same stack that I was debugging, but this _is_ a realy issue regardless. (cherry picked from commit 7073ec01bf78e5cfebab903e6de165c535841470) Service-Card-Id: 90672654 Service-Version: 1.19 --- src/cascadia/TerminalApp/TerminalWindow.cpp | 55 +++++++++++---------- src/cascadia/TerminalApp/TerminalWindow.h | 1 + 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp index 3aa5e0bbaec..e6414406aaa 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.cpp +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -211,32 +211,7 @@ namespace winrt::TerminalApp::implementation _root->SetSettings(_settings, false); // We're on our UI thread right now, so this is safe _root->Loaded({ get_weak(), &TerminalWindow::_OnLoaded }); - - _root->Initialized([this](auto&&, auto&&) { - // GH#288 - When we finish initialization, if the user wanted us - // launched _fullscreen_, toggle fullscreen mode. This will make sure - // that the window size is _first_ set up as something sensible, so - // leaving fullscreen returns to a reasonable size. - const auto launchMode = this->GetLaunchMode(); - if (_WindowProperties->IsQuakeWindow() || WI_IsFlagSet(launchMode, LaunchMode::FocusMode)) - { - _root->SetFocusMode(true); - } - - // The IslandWindow handles (creating) the maximized state - // we just want to record it here on the page as well. - if (WI_IsFlagSet(launchMode, LaunchMode::MaximizedMode)) - { - _root->Maximized(true); - } - - if (WI_IsFlagSet(launchMode, LaunchMode::FullscreenMode) && !_WindowProperties->IsQuakeWindow()) - { - _root->SetFullscreen(true); - } - - AppLogic::Current()->NotifyRootInitialized(); - }); + _root->Initialized({ get_weak(), &TerminalWindow::_pageInitialized }); _root->Create(); AppLogic::Current()->SettingsChanged({ get_weak(), &TerminalWindow::UpdateSettingsHandler }); @@ -255,6 +230,34 @@ namespace winrt::TerminalApp::implementation TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); } + + void TerminalWindow::_pageInitialized(const IInspectable&, const IInspectable&) + { + // GH#288 - When we finish initialization, if the user wanted us + // launched _fullscreen_, toggle fullscreen mode. This will make sure + // that the window size is _first_ set up as something sensible, so + // leaving fullscreen returns to a reasonable size. + const auto launchMode = this->GetLaunchMode(); + if (_WindowProperties->IsQuakeWindow() || WI_IsFlagSet(launchMode, LaunchMode::FocusMode)) + { + _root->SetFocusMode(true); + } + + // The IslandWindow handles (creating) the maximized state + // we just want to record it here on the page as well. + if (WI_IsFlagSet(launchMode, LaunchMode::MaximizedMode)) + { + _root->Maximized(true); + } + + if (WI_IsFlagSet(launchMode, LaunchMode::FullscreenMode) && !_WindowProperties->IsQuakeWindow()) + { + _root->SetFullscreen(true); + } + + AppLogic::Current()->NotifyRootInitialized(); + } + void TerminalWindow::Quit() { if (_root) diff --git a/src/cascadia/TerminalApp/TerminalWindow.h b/src/cascadia/TerminalApp/TerminalWindow.h index f005ad0b0ba..8eff22ef455 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.h +++ b/src/cascadia/TerminalApp/TerminalWindow.h @@ -202,6 +202,7 @@ namespace winrt::TerminalApp::implementation void _RefreshThemeRoutine(); void _OnLoaded(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs); + void _pageInitialized(const IInspectable& sender, const IInspectable& eventArgs); void _OpenSettingsUI(); winrt::Windows::Foundation::Collections::IVector _contentStringToActions(const winrt::hstring& content, From 3eae898ddc4ad9270387974a2af0e7f44d73a7ed Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Fri, 29 Sep 2023 03:49:34 -0700 Subject: [PATCH 014/167] [Schema] Fix incorrect default value for 'allowEmpty' (#16040) (cherry picked from commit 4382a17352869bb1f33e020ac281d04aa15a8e8f) Service-Card-Id: 90686477 Service-Version: 1.19 --- doc/cascadia/profiles.schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 6f3d62fa297..9dc3a8afd58 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -673,7 +673,7 @@ }, "allowEmpty": { "description": "Whether to render a folder without entries, or to hide it", - "default": "false", + "default": false, "type": "boolean" } } From 828c1ebf661eaf152d9a745b1a845ff86868b639 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Fri, 29 Sep 2023 13:25:01 -0500 Subject: [PATCH 015/167] Fix CFG on our static-lib-only DLL projects (#16056) Control Flow Guard requires both linker and compiler flags. It turns out that the MSVC build rules determine whether to _link_ with CFG based on whether it compiled anything with CFG. It also turns out that when you don't compile anything (such as in our DLL projects that only consume a static library!), the build rules can't guess whether to link with CFG. Whoops. We need to force it. (cherry picked from commit 1b143e34a81ad55a74033961ea16bfab542f032f) Service-Card-Id: 90688105 Service-Version: 1.19 --- src/common.build.pre.props | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/common.build.pre.props b/src/common.build.pre.props index 1cf31b37e44..9404c24c4bd 100644 --- a/src/common.build.pre.props +++ b/src/common.build.pre.props @@ -101,6 +101,13 @@ x64 true false + + true From 4c795b5bcd66b3004d840b6d9ca73c67933ce57a Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Fri, 29 Sep 2023 13:25:13 -0500 Subject: [PATCH 016/167] build: switch the EsrpCodeSigning task to version 3 (#16057) The version we were using requires .NET 2.1 (wow) which is way out of support. Task version 3 supports much newer versions. (cherry picked from commit ac2b0e744c7438010b7487f57064a179bc20b329) Service-Card-Id: 90688108 Service-Version: 1.19 --- build/pipelines/templates-v2/job-build-package-wpf.yml | 2 +- build/pipelines/templates-v2/job-build-project.yml | 2 +- build/pipelines/templates-v2/job-merge-msix-into-bundle.yml | 2 +- build/pipelines/templates-v2/job-package-conpty.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pipelines/templates-v2/job-build-package-wpf.yml b/build/pipelines/templates-v2/job-build-package-wpf.yml index c9329eab519..9d64e1c7cff 100644 --- a/build/pipelines/templates-v2/job-build-package-wpf.yml +++ b/build/pipelines/templates-v2/job-build-package-wpf.yml @@ -97,7 +97,7 @@ jobs: flattenFolders: true - ${{ if eq(parameters.codeSign, true) }}: - - task: EsrpCodeSigning@1 + - task: EsrpCodeSigning@3 displayName: Submit *.nupkg to ESRP for code signing inputs: ConnectedServiceName: 9d6d2960-0793-4d59-943e-78dcb434840a diff --git a/build/pipelines/templates-v2/job-build-project.yml b/build/pipelines/templates-v2/job-build-project.yml index 39d5020c5c3..90acb44cbd1 100644 --- a/build/pipelines/templates-v2/job-build-project.yml +++ b/build/pipelines/templates-v2/job-build-project.yml @@ -224,7 +224,7 @@ jobs: # Code-sign everything we just put together. # We run the signing in Terminal.BinDir, because all of the signing batches are relative to the final architecture/configuration output folder. - - task: EsrpCodeSigning@1 + - task: EsrpCodeSigning@3 displayName: Submit Signing Request inputs: ConnectedServiceName: 9d6d2960-0793-4d59-943e-78dcb434840a diff --git a/build/pipelines/templates-v2/job-merge-msix-into-bundle.yml b/build/pipelines/templates-v2/job-merge-msix-into-bundle.yml index 1457178f973..ffca7e450e3 100644 --- a/build/pipelines/templates-v2/job-merge-msix-into-bundle.yml +++ b/build/pipelines/templates-v2/job-merge-msix-into-bundle.yml @@ -89,7 +89,7 @@ jobs: displayName: Create msixbundle - ${{ if eq(parameters.codeSign, true) }}: - - task: EsrpCodeSigning@1 + - task: EsrpCodeSigning@3 displayName: Submit *.msixbundle to ESRP for code signing inputs: ConnectedServiceName: 9d6d2960-0793-4d59-943e-78dcb434840a diff --git a/build/pipelines/templates-v2/job-package-conpty.yml b/build/pipelines/templates-v2/job-package-conpty.yml index 52f3a5c15d9..2f777cdf4f5 100644 --- a/build/pipelines/templates-v2/job-package-conpty.yml +++ b/build/pipelines/templates-v2/job-package-conpty.yml @@ -82,7 +82,7 @@ jobs: versionEnvVar: XES_PACKAGEVERSIONNUMBER - ${{ if eq(parameters.codeSign, true) }}: - - task: EsrpCodeSigning@1 + - task: EsrpCodeSigning@3 displayName: Submit *.nupkg to ESRP for code signing inputs: ConnectedServiceName: 9d6d2960-0793-4d59-943e-78dcb434840a From b9cbf0f24c57e229bc7bde8a594e90ea57c7433b Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Mon, 2 Oct 2023 14:52:54 -0500 Subject: [PATCH 017/167] build: add a OneBranch Official release pipeline (#16081) This pipeline does everything the existing release pipeline does, except it does it using the OneBranch official templates. Most of our existing build infrastructure has been reused, with the following changes: - We are no longer using `job-submit-windows-vpack`, as OneBranch does this for us. - `job-merge-msix-into-bundle` now supports afterBuildSteps, which we use to stage the msixbundle into the right place for the vpack - `job-build-project` supports deleting all non-signed files (which the OneBranch post-build validation requires) - `job-build-project` now deletes `console.dll`, which is unused in any of our builds, because XFGCheck blows up on it for some reason on x86 - `job-publish-symbols` now supports two different types of PAT ingestion - I have pulled out the NuGet filename variables into a shared variables template I have also introduced a TSA config (which files bugs on us for binary analysis failures as well as using the word 'sucks' and stuff.) I have also baselined a number of control flow guard/binary analysis failures. (cherry picked from commit 6489f6b39daaef634dd0e56e50afa8866f0103c7) Service-Card-Id: 90706777 Service-Version: 1.19 --- build/config/release.gdnbaselines | 1127 +++++++++++++++++ build/config/tsa.json | 6 + build/pipelines/ob-nightly.yml | 47 + build/pipelines/ob-release.yml | 81 ++ .../templates-v2/job-build-project.yml | 14 + .../job-merge-msix-into-bundle.yml | 9 +- .../templates-v2/job-publish-symbols.yml | 13 +- .../pipeline-full-release-build.yml | 23 +- .../pipeline-onebranch-full-release-build.yml | 256 ++++ .../variables-nuget-package-version.yml | 23 + .../variables-onebranch-config.yml | 2 + 11 files changed, 1577 insertions(+), 24 deletions(-) create mode 100644 build/config/release.gdnbaselines create mode 100644 build/config/tsa.json create mode 100644 build/pipelines/ob-nightly.yml create mode 100644 build/pipelines/ob-release.yml create mode 100644 build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml create mode 100644 build/pipelines/templates-v2/variables-nuget-package-version.yml create mode 100644 build/pipelines/templates-v2/variables-onebranch-config.yml diff --git a/build/config/release.gdnbaselines b/build/config/release.gdnbaselines new file mode 100644 index 00000000000..b8f686e3a94 --- /dev/null +++ b/build/config/release.gdnbaselines @@ -0,0 +1,1127 @@ +{ + "hydrated": false, + "properties": { + "helpUri": "https://eng.ms/docs/microsoft-security/security/azure-security/cloudai-security-fundamentals-engineering/security-integration/guardian-wiki/microsoft-guardian/general/baselines", + "hydrationStatus": "This file does not contain identifying data. It is safe to check into your repo. To hydrate this file with identifying data, run `guardian hydrate --help` and follow the guidance." + }, + "version": "1.0.0", + "baselines": { + "default": { + "name": "default", + "createdDate": "2023-09-29 19:39:12Z", + "lastUpdatedDate": "2023-09-29 19:39:12Z" + } + }, + "results": { + "b1e592dc96e9286d806682680cffcb44b7ba2f7ea883b022371037689fe2bb5b": { + "signature": "b1e592dc96e9286d806682680cffcb44b7ba2f7ea883b022371037689fe2bb5b", + "alternativeSignatures": [ + "25d5f2d86325f3e6666cf720c3b0827c7b5773c42f3770a6e83ab8bc0f716686" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "fd608e2146a69491516290ae3b65d4d094e5f9ba53c6624ff457c3b21f67e433": { + "signature": "fd608e2146a69491516290ae3b65d4d094e5f9ba53c6624ff457c3b21f67e433", + "alternativeSignatures": [ + "528526d5f81c7387e9eca88c1dc1e7886bb3189d9c53f7678c63a44a7e78d2b8" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "104eb68a26c9a422f9973b85603046c42bb6894f398e1ebd454d829a386c47cd": { + "signature": "104eb68a26c9a422f9973b85603046c42bb6894f398e1ebd454d829a386c47cd", + "alternativeSignatures": [ + "33b75434f899dc72a71c55e6667420b5f3406641eae1a784bad8632fe80eccc6" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "fa8ab295467d81fd138402f12899da84cdf312e2e84712a539e08361b6900651": { + "signature": "fa8ab295467d81fd138402f12899da84cdf312e2e84712a539e08361b6900651", + "alternativeSignatures": [ + "b0ef4eae3f158488f99f7705cc93d037d55e8bd39b173c40e25581a35c6889d2" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "2d5cf239e62a256384275134ea1e1180b927f4cd53e51193751b0b5671045be8": { + "signature": "2d5cf239e62a256384275134ea1e1180b927f4cd53e51193751b0b5671045be8", + "alternativeSignatures": [ + "bd73f2f051d6525c01db61a4c4a6c3b9a80083da83ed602eae2a70e17286a7ed" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "e88b3902e68d0315f7bef3a24860a7d3d9abd2d960fde06a0c650c6bb569df74": { + "signature": "e88b3902e68d0315f7bef3a24860a7d3d9abd2d960fde06a0c650c6bb569df74", + "alternativeSignatures": [ + "143ade735d3d6d19f999555f2ce706f647a64688054b5f19052f99992ab325fd" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "01306239b92e4508b0ab60e025cadc4c974c33c29f1eb28d2b3fdda8d4019e09": { + "signature": "01306239b92e4508b0ab60e025cadc4c974c33c29f1eb28d2b3fdda8d4019e09", + "alternativeSignatures": [ + "dca24210a3064ca7069a737f9a88f4a25b1cc1cd4c9e2a095156297246611de5" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "675ea9bd7809b2f210105e9703dffe8e48ee134ab21677e7cc4556bf22782e8d": { + "signature": "675ea9bd7809b2f210105e9703dffe8e48ee134ab21677e7cc4556bf22782e8d", + "alternativeSignatures": [ + "7d5287a33645767969627a20e3a65fb2f1bb2db0e79a689b5a804a98f65d01fa" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "6e040bc57aa08165f8af6007e3794149d3a2c0325021e746e9705ada7c05b30c": { + "signature": "6e040bc57aa08165f8af6007e3794149d3a2c0325021e746e9705ada7c05b30c", + "alternativeSignatures": [ + "218c0d7d5332113e33632d06eb0f209bac711ac515bd76a0fcfda2a0cbed885d" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "73e85b3af96a27c797fbe9f36f00feccd4118204615b9477da67e5868dd5dfb9": { + "signature": "73e85b3af96a27c797fbe9f36f00feccd4118204615b9477da67e5868dd5dfb9", + "alternativeSignatures": [ + "49c44c3dc27233eba172214cb302de4b5c9b2a845e8595e7930fad74805183ac" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "15bd7ecdb19cdd263867db23c12a98029cede5476c89e870b811fce45abb8db4": { + "signature": "15bd7ecdb19cdd263867db23c12a98029cede5476c89e870b811fce45abb8db4", + "alternativeSignatures": [ + "2705f7f6c9e83d2a812e264f39878e0e0173a1a5efea16e7206c4d02dc419af8" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "3f7d3360f0b185361b1e67b94c22b2a319b7c2d5aa2c8fd7fc3e4626394cba54": { + "signature": "3f7d3360f0b185361b1e67b94c22b2a319b7c2d5aa2c8fd7fc3e4626394cba54", + "alternativeSignatures": [ + "71f05133f86c279daee4f1b85f997955e36857bcc5945dbae71f8bbb91b5df6d" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "3bcb157fd01d81513a15da41b4fc8b2820247331689fef5d0026578d95b85716": { + "signature": "3bcb157fd01d81513a15da41b4fc8b2820247331689fef5d0026578d95b85716", + "alternativeSignatures": [ + "67e145d1f3bf6eb205e385884ca6356decffc3cac3e15339cc7ca43441cff99b" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "27ae6a9a7047c5ca98d1dc3a9b0885733875387d475f1426afc4d031b357fc1b": { + "signature": "27ae6a9a7047c5ca98d1dc3a9b0885733875387d475f1426afc4d031b357fc1b", + "alternativeSignatures": [ + "b87d72abe37f6144614469221a0ce60e389792843f602805afe149b3ad1dd098" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "bd2aea1d24e36c1dcc128eb87a2c27292ed251d95aedf5f11204647bc68719f8": { + "signature": "bd2aea1d24e36c1dcc128eb87a2c27292ed251d95aedf5f11204647bc68719f8", + "alternativeSignatures": [ + "ca29329aae819543d357e86acf0ccce914dbc3a15e0f91016091c3079638ad34" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "b836a6e0d58634003e054c88f3ed615d8e92182ebc78e9dd2168d8126c33e694": { + "signature": "b836a6e0d58634003e054c88f3ed615d8e92182ebc78e9dd2168d8126c33e694", + "alternativeSignatures": [ + "850aa5d2e92dac06fc119ed297e84dfde9fdb3f9b116bdb2c83e8d66ea442865" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "e54972bb37d35b3b9c7c764cdf5351f0254ae2b8526465fe03c9ba8a04b06651": { + "signature": "e54972bb37d35b3b9c7c764cdf5351f0254ae2b8526465fe03c9ba8a04b06651", + "alternativeSignatures": [ + "9e8723695743edaddfc5b188ec6bde240ddde68516f5c2342bd1c4d2a8c68430" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "3786d03543e25eec1f68beb21a3884953460239c269ba4071baf9fd4f564e763": { + "signature": "3786d03543e25eec1f68beb21a3884953460239c269ba4071baf9fd4f564e763", + "alternativeSignatures": [ + "05e0c9bc16b8ade4d40d4e4371864da4ee7d9c8d68b19c3319543dbebea97a39" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "5f7c1c0c1f9290d3afad4bb3a17d5e5d33f2176e18b8ec406df71e8245ec0b7f": { + "signature": "5f7c1c0c1f9290d3afad4bb3a17d5e5d33f2176e18b8ec406df71e8245ec0b7f", + "alternativeSignatures": [ + "952d91003467bdf2dbf5e72f56b9c69de52c4abf44cc82c0833ba8d89d9befac" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "605037e76782777be2f16c12c023247671ebad0afea868ed0c4bf939c9ff443a": { + "signature": "605037e76782777be2f16c12c023247671ebad0afea868ed0c4bf939c9ff443a", + "alternativeSignatures": [ + "4f77f69716e098fec8214948428cfe9d2c2324d0719250b25e5c084a21d05e69" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "4a173deb8aeab5f4f2463774f117b909dee0168c99bc54b29b1141fca64598bd": { + "signature": "4a173deb8aeab5f4f2463774f117b909dee0168c99bc54b29b1141fca64598bd", + "alternativeSignatures": [ + "66526149b57bbdaa8d4e3437a0099a4e3b7818c911b2110fa40d7d0264ce99f6" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "e557e9c4d1c36f69fd4a2310d22bbeadc1dff3dd2404449b8424662bb1c6adc7": { + "signature": "e557e9c4d1c36f69fd4a2310d22bbeadc1dff3dd2404449b8424662bb1c6adc7", + "alternativeSignatures": [ + "6b93c58cd83ba89289c12b408c2988dfd54d7936c0920981cf690725155caf7a" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "70eb589101b9b57d2e47f6879348a8b93cd0c2f223e2c20d2289c5e1e8be0b9d": { + "signature": "70eb589101b9b57d2e47f6879348a8b93cd0c2f223e2c20d2289c5e1e8be0b9d", + "alternativeSignatures": [ + "e9fe7fc2a31de263521f635058984dd81b4bab9cba174949fc5582fbeef6f693" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "3e5c0b1cad192bf5fa00d6b0f46f4cfaa7251ebaea30177d2d6891890f0dafab": { + "signature": "3e5c0b1cad192bf5fa00d6b0f46f4cfaa7251ebaea30177d2d6891890f0dafab", + "alternativeSignatures": [ + "9a1dd0e934ddd3a33f3cf37372052731f5c157c13cac05ef0539815f845db9bf" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "dd3b2ddb278bb70559efcfc10e19c2f3a3bfe805cbbecd6a16dd141433f0b433": { + "signature": "dd3b2ddb278bb70559efcfc10e19c2f3a3bfe805cbbecd6a16dd141433f0b433", + "alternativeSignatures": [ + "3fcb3a01715c6d244d26ef49fa4c06f8a92616a9c6465c26c4398c554f7fd67f" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "a635cb613925ab6c9f6b133890169bdf77acee212b6ac8f83cca7253faafaec7": { + "signature": "a635cb613925ab6c9f6b133890169bdf77acee212b6ac8f83cca7253faafaec7", + "alternativeSignatures": [ + "2ff5cc7a3bf6cd63862cffc5088690fc824eb1d2f9e42c732fcd1611e9e25bfc" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "4ecdfc33e6267bfeadb1507d5304a9737f3ba0ee3132e6d7bc6f3fd68a47824e": { + "signature": "4ecdfc33e6267bfeadb1507d5304a9737f3ba0ee3132e6d7bc6f3fd68a47824e", + "alternativeSignatures": [ + "23dc3273d54a3db12ad729569696b875523201dc13d4cbba9e3642dde17492bb" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "a9b9f171556057eaf197eb19780abfac5df82154f6c06c07240ce73970127df7": { + "signature": "a9b9f171556057eaf197eb19780abfac5df82154f6c06c07240ce73970127df7", + "alternativeSignatures": [ + "0a9f4d3a4aa85e918744a5af36851791eb4b2f44fc132560cc56cf5661ba28fb" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "ad9da2b392f1622c6cff4a2b63eadf0da2ff31811dbbf8c9f2b2c4e8d9fefa1f": { + "signature": "ad9da2b392f1622c6cff4a2b63eadf0da2ff31811dbbf8c9f2b2c4e8d9fefa1f", + "alternativeSignatures": [ + "b1fafebeb213b04346d56182ce395ccb9a0ad866dc28bda801c222499e550396" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "b8f591f33a84c38402239941484ead425c3e37bfcfd486b9be3a15d2213d9fa3": { + "signature": "b8f591f33a84c38402239941484ead425c3e37bfcfd486b9be3a15d2213d9fa3", + "alternativeSignatures": [ + "de56fbe02702da318ea6a113e592b2efee3f942e04e31c5e9300cf6798abea0c" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "37adbb986601cf75f2d1eaebbe6d430a70a0886e60685a880e7d1f2c48f9b125": { + "signature": "37adbb986601cf75f2d1eaebbe6d430a70a0886e60685a880e7d1f2c48f9b125", + "alternativeSignatures": [ + "7ebe5c4d5a144a3f356114bf5e062295ee90d5cdc7ca615dc729b7dcba8bed33" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "e0de291c50fb0cae3bdaf75237ac8e3f34acc6a8de18ba589dde4e76352ab849": { + "signature": "e0de291c50fb0cae3bdaf75237ac8e3f34acc6a8de18ba589dde4e76352ab849", + "alternativeSignatures": [ + "52c0187472041933b5d850e47cf28ac6a7ef5ba0a6a85bca65af3b3531ddb06a" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "6b163fce31228367e0323768ab0dcc1224f8416cc856ddc03ccc2b8a2cf9a08e": { + "signature": "6b163fce31228367e0323768ab0dcc1224f8416cc856ddc03ccc2b8a2cf9a08e", + "alternativeSignatures": [ + "0e6f9f2e1008091bcfbf520d7407c3690564ea5118580fd8cfca965946f53a78" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "25012b8cab7c6dddc276e2c0d7888554e8542dd686891914d2c2e82dfd3f697a": { + "signature": "25012b8cab7c6dddc276e2c0d7888554e8542dd686891914d2c2e82dfd3f697a", + "alternativeSignatures": [ + "ea05d52f5ee2b483e83c14216a3e56bc8a4474396b71cbb426ced61242d81919" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "bfc7ac3b82698c138a970968cce2c849ed0e45bb8930d42215f6704659f63762": { + "signature": "bfc7ac3b82698c138a970968cce2c849ed0e45bb8930d42215f6704659f63762", + "alternativeSignatures": [ + "c59c7529464c717d1909058d1f4fbc5109881effafab3c67bbbf02ccdffddd4e" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "25582fa1ab3be3a375a0d053304e65b50ebfa590f81a5d957759304ba29e3053": { + "signature": "25582fa1ab3be3a375a0d053304e65b50ebfa590f81a5d957759304ba29e3053", + "alternativeSignatures": [ + "e2628baf1e09340a2063cd39c238e541014a65f4433018cc36cd25c5c8c3043d" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "38b73c3711c0c9cd958195f318fd2e6ab1f10cfa94abdfb7505d784ba022cfae": { + "signature": "38b73c3711c0c9cd958195f318fd2e6ab1f10cfa94abdfb7505d784ba022cfae", + "alternativeSignatures": [ + "6688a9492755e33e541513bf297d0b4f459f70a43486fd8411749a2caf5cb91c" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "250a0184e9feee525e9a54930f5714965bd2bc29d0fe91c502c560443fa0e10a": { + "signature": "250a0184e9feee525e9a54930f5714965bd2bc29d0fe91c502c560443fa0e10a", + "alternativeSignatures": [ + "d7b1083c67959b8f095acd532a10244a0a5afcbc93eefc66be20c19841b2588e" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "2c64c980db98b04a7daeedf4c5d358b8ecaecca81a8e0f70911fd211e06c8e20": { + "signature": "2c64c980db98b04a7daeedf4c5d358b8ecaecca81a8e0f70911fd211e06c8e20", + "alternativeSignatures": [ + "7778c4704d515b92d4dc77a9b612c151e6bb9e7cd88e100c7d0419fb150f34e6" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "3bb5f422737103df8f018ec0789d2b7793ce5b1641927d5646e963126e73e1b5": { + "signature": "3bb5f422737103df8f018ec0789d2b7793ce5b1641927d5646e963126e73e1b5", + "alternativeSignatures": [ + "225e02e47ea3f999bc43279246ee51b4287e4e2b4b7dac899da91c365d8acd0e" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "6e39bdcfe28e2cb6868a5dcbe12fa712d2a60762bb57fd82734089e19ca85b52": { + "signature": "6e39bdcfe28e2cb6868a5dcbe12fa712d2a60762bb57fd82734089e19ca85b52", + "alternativeSignatures": [ + "f6ebab2bea88d78f69c8ae78f4ec641edd0f0796b9ca193260c44cee9d543a18" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "e9a5cff46470c62be3e1db1e43785b71daa88e466c802d4393bf9248a9e6fb56": { + "signature": "e9a5cff46470c62be3e1db1e43785b71daa88e466c802d4393bf9248a9e6fb56", + "alternativeSignatures": [ + "3bdabc64cff83a1322f107a97ce6eab6e8cb41b8492643e9a0df3561d0169b95" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "9f19392621589641c13fc5762e36429cc4d38fc04f98297006be89e5ca3e5631": { + "signature": "9f19392621589641c13fc5762e36429cc4d38fc04f98297006be89e5ca3e5631", + "alternativeSignatures": [ + "f448a0535740dcb9bddebe7765ea55c819472efdb9cd63761dc156b8af11a260" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "60b306912ae3399a167514dabf495a7f57e0ff17ec9a9491e66cf39c1ecbf60b": { + "signature": "60b306912ae3399a167514dabf495a7f57e0ff17ec9a9491e66cf39c1ecbf60b", + "alternativeSignatures": [ + "fbd4d1fe2e8e21ed5fe000fc592ef9fa98ff52f529b849c9ceca12c85c3cf4f3" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "2498be9bbe27f393fad6af89f23cf228fe5c263e38fa3d20d3652b68c356243b": { + "signature": "2498be9bbe27f393fad6af89f23cf228fe5c263e38fa3d20d3652b68c356243b", + "alternativeSignatures": [ + "750b9e1ff2f1b443502036d226678bcdda2f22a3879e36b61e3bcec24c0eec3e" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "147fa7e9b0187ab163dfe5049876c838b2b599a8ac2c243226ab4fed8d9e3513": { + "signature": "147fa7e9b0187ab163dfe5049876c838b2b599a8ac2c243226ab4fed8d9e3513", + "alternativeSignatures": [ + "5fe9a0ee3ada52e4dbee1aa6f45683a82d14ed812d9d7c1634b786f28fc2ca51" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "a2fe2c57ef4efc03e72dfd1fa653fc9d731b69bda91711f8ab8d46edc3b12667": { + "signature": "a2fe2c57ef4efc03e72dfd1fa653fc9d731b69bda91711f8ab8d46edc3b12667", + "alternativeSignatures": [ + "42ab6b70626c246ddea96b060cb7a79248576d19f003c4f4eedf171f4f5992f4" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "e7973a3b90d74d4ff6ee544dee45bb35ac66214a3e92eadecbf8fafccca9abb1": { + "signature": "e7973a3b90d74d4ff6ee544dee45bb35ac66214a3e92eadecbf8fafccca9abb1", + "alternativeSignatures": [ + "3359d44d5bbf533487fc65e4a98220af62eef0cc6b883bc35b8286ee1a31ca4f" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "190440674b3fec2fcc77b341a684a9e9100c8dec0911a8c83f19f6ccedcb2b78": { + "signature": "190440674b3fec2fcc77b341a684a9e9100c8dec0911a8c83f19f6ccedcb2b78", + "alternativeSignatures": [ + "c7aab0deb9b97032353766e961f4b23bc694e85b8b06709beaddfa2e5743e742" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "4d258afa7d015f4fc96c8866e2e43cb00290526d56394eec3848ed650cec840d": { + "signature": "4d258afa7d015f4fc96c8866e2e43cb00290526d56394eec3848ed650cec840d", + "alternativeSignatures": [ + "e23b41b521b849057b8c5ba0594af11fc5ceec59714f3d0ebbeda708511d5803" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "530d02de7ec8a3c9f575cf3770e34665a58b127e01555000e611ee4c5e37e24d": { + "signature": "530d02de7ec8a3c9f575cf3770e34665a58b127e01555000e611ee4c5e37e24d", + "alternativeSignatures": [ + "23f217577bcf9d3a6f87c0ac259cf891a80e78f2b77dd58e631816f3183101bd" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "69a35d8bd5d4766e5dc981526229ccc0d27f238b06357bdd8e0469cd35b47d08": { + "signature": "69a35d8bd5d4766e5dc981526229ccc0d27f238b06357bdd8e0469cd35b47d08", + "alternativeSignatures": [ + "cab1cab825e5846acc097221236feaf3a06895e648c2ef16a4c285616b714987" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "c78a17b7f8d46b9ddedd34240d76adf92e4f1bf27a978c022e4599fe6a7b150d": { + "signature": "c78a17b7f8d46b9ddedd34240d76adf92e4f1bf27a978c022e4599fe6a7b150d", + "alternativeSignatures": [ + "fa75ccd56697c3073a6ef17e2d959ee24b6afc11eabc4bd5dbea1d74ecdb81cb" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "7bd1757994e7f1909ffd25e56393f991d436232150cff2555a97e9b08b347b99": { + "signature": "7bd1757994e7f1909ffd25e56393f991d436232150cff2555a97e9b08b347b99", + "alternativeSignatures": [ + "dbdb5c5ebe211b21d9b25e346dfbc58e6b32500258a1efed248e4630216ce57e" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "cef01bb2a477968c8cbc997179d04b90c36febd9cfd21ca973d4a22b3efc4caa": { + "signature": "cef01bb2a477968c8cbc997179d04b90c36febd9cfd21ca973d4a22b3efc4caa", + "alternativeSignatures": [ + "9ec52d86604abde8a13124534ec587d3a1686630ea7bb2f736a66963349abdbd" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "11026da7398e29b59d98ffee5ed5317c243407638a77d7a09637ebe2d8bb09f3": { + "signature": "11026da7398e29b59d98ffee5ed5317c243407638a77d7a09637ebe2d8bb09f3", + "alternativeSignatures": [ + "a642f1f37cf06c75c2082d720e317481bb7e73249dcda33dba0b5cdd6933926f" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "8f2550b4f39a798d217679c04dd7cb21b72ef63e6f6d63b06aadb1c40d8c2546": { + "signature": "8f2550b4f39a798d217679c04dd7cb21b72ef63e6f6d63b06aadb1c40d8c2546", + "alternativeSignatures": [ + "10c95aac8513a0d6cc2c0ef08b51f7f4ac3be4b23ddd90d68d06fc536421acc3" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "022eb0065260832f06f12313f8bf99765488c1db9ff9ef8ecf8a0cbf393e878d": { + "signature": "022eb0065260832f06f12313f8bf99765488c1db9ff9ef8ecf8a0cbf393e878d", + "alternativeSignatures": [ + "4801e53b7e46778aa21a8c9a0ec2725e73746fb648e76c9359f6db0f32ac2963" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "36f66ad8b00e978f29a7b1c09c7942247e3b068d131431c3b321190aa7cb2978": { + "signature": "36f66ad8b00e978f29a7b1c09c7942247e3b068d131431c3b321190aa7cb2978", + "alternativeSignatures": [ + "7d3ee020a3a1601c49be0c5d687f46858f679ecf6d4150b97ba9ae5abfa742e4" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "c50b78c8370aa9d767f783bddf8c57b2f1dbd749073ccde60b3b210b116b6951": { + "signature": "c50b78c8370aa9d767f783bddf8c57b2f1dbd749073ccde60b3b210b116b6951", + "alternativeSignatures": [ + "87568cc368760fe9f38d9fb95e93297d8bb5a83587d20d071f97713443df2e04" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "8e2be08973d6028cf58e152fba8dcd5616b7938591d2292eaf3bb5f80bdcf004": { + "signature": "8e2be08973d6028cf58e152fba8dcd5616b7938591d2292eaf3bb5f80bdcf004", + "alternativeSignatures": [ + "d9df5f887062a990099a2cb70e46778aad25304d9758db556040beb4b23a8776" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "be36363b685b28e3ac4457b7a415120cab5a01c14d5c9a19192ef5ebd71c176a": { + "signature": "be36363b685b28e3ac4457b7a415120cab5a01c14d5c9a19192ef5ebd71c176a", + "alternativeSignatures": [ + "13779d67c6e5f5eba285b83093a2c9eec2d79b3b36ee86400873191310088255" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "a6e48721d67f94b2876a97fe4b385ee37adc1b345825f751e87d6bd05095bfb9": { + "signature": "a6e48721d67f94b2876a97fe4b385ee37adc1b345825f751e87d6bd05095bfb9", + "alternativeSignatures": [ + "f6a5898d9652b23b269283d4bc1a8e95865ee9f7125479ab78087e3dd3b3b12f" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "b1b1aba985591c904b0faf09b54ddc4763890d25e5dcfa2d3ca758165a616970": { + "signature": "b1b1aba985591c904b0faf09b54ddc4763890d25e5dcfa2d3ca758165a616970", + "alternativeSignatures": [ + "b7ad084f971c1fae38509e87d50fd4984185ea6915d501d6cc0d1eac8bd93abf" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "078e8e5c43a51876fb09d7fab78a07fe896bbbb74c9507f3b6313ed3fe7586d4": { + "signature": "078e8e5c43a51876fb09d7fab78a07fe896bbbb74c9507f3b6313ed3fe7586d4", + "alternativeSignatures": [ + "7046bab04be2bfce75f0a34461871d3dccddab009c2a223a78c08696610c7a55" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "9443465290e747bbefb4435296af78a82a948222097df6559db0d0488f7df81a": { + "signature": "9443465290e747bbefb4435296af78a82a948222097df6559db0d0488f7df81a", + "alternativeSignatures": [ + "47a1795431bb9f78383c54fd33cf3427a774ae9802aa994cbd688f81286904bd" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "01a56e39bd6a3652d95b21035efc6d165eb16451f9558d79ff13d297cd345cd9": { + "signature": "01a56e39bd6a3652d95b21035efc6d165eb16451f9558d79ff13d297cd345cd9", + "alternativeSignatures": [ + "2169e62964a8fa3bb4ccde41e33232efa8b7ddfb6d5a45688c28318c96ccbe52" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "a9c67896453ec9e7fb742e05ec0a47f13c5c6b7bd3886497afe3c3bb860ef006": { + "signature": "a9c67896453ec9e7fb742e05ec0a47f13c5c6b7bd3886497afe3c3bb860ef006", + "alternativeSignatures": [ + "5973211461980400fe2aeea7dafe6079e6f771d799c347a0bcb7c631dc379d5e" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "2689cc88d4721d762e3783920aa7150cd00e4b5f7f15bdd81476dbb56876aa08": { + "signature": "2689cc88d4721d762e3783920aa7150cd00e4b5f7f15bdd81476dbb56876aa08", + "alternativeSignatures": [ + "ba41ff939940ab49d1a4b39dfb5fac6108111616c0d7fb87e6f64f1b82cbfbd2" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "64d08c256034d4907fa6655e4914850c945bf1f6db4e14f52a8032eefe8e1245": { + "signature": "64d08c256034d4907fa6655e4914850c945bf1f6db4e14f52a8032eefe8e1245", + "alternativeSignatures": [ + "639e5b7c91137d49bd041d7f241bd2403067f445de5058048c4c6c926af226bd" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "689ed96056f498ca67a5a056efa247ee9aea196362e693a0b196e6e9eb2cba0f": { + "signature": "689ed96056f498ca67a5a056efa247ee9aea196362e693a0b196e6e9eb2cba0f", + "alternativeSignatures": [ + "9473554d81655d5cf548e8033fc5f34cd42cec8697306b75fc2ec31e8c19b29e" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "f497cbbab92fbbd65c27b122c7ceaf0d181859365ea121a885d32898dec963c6": { + "signature": "f497cbbab92fbbd65c27b122c7ceaf0d181859365ea121a885d32898dec963c6", + "alternativeSignatures": [ + "2fe25bb29ed8a90e5dba2e8e899f42713b61d178fd18231d560d4873656fe8a4" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "669b421bcaea46b4f3a2b63097a357920df16b02075d1b86a476f9ee6fc4314a": { + "signature": "669b421bcaea46b4f3a2b63097a357920df16b02075d1b86a476f9ee6fc4314a", + "alternativeSignatures": [ + "5647958533f690de004b8f82064ed0fc779fe1206b64a1813f90988481dfb72e" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "8bb84450c75dcbee365ee2b2eb2ff83ef91925afb50d9c96aec845729f3a9be1": { + "signature": "8bb84450c75dcbee365ee2b2eb2ff83ef91925afb50d9c96aec845729f3a9be1", + "alternativeSignatures": [ + "85d7ccfcf73dbbc98b78cbd81581f3e81e9bc9a632a589fd79abb6f9cdd3236d" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "9beca9c8ed341ed7dbc682811ca43ec7174d945df99613c85e76e45ad55ac7f3": { + "signature": "9beca9c8ed341ed7dbc682811ca43ec7174d945df99613c85e76e45ad55ac7f3", + "alternativeSignatures": [ + "147836cc11afe71d92bdf75ef6d5b1b97ae5f5709d07a9d8c58ef8bce0631a86" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "a0f61b31ca15ee653301086835bfb62889b4f4b537fd703ccfc21df9aa280a59": { + "signature": "a0f61b31ca15ee653301086835bfb62889b4f4b537fd703ccfc21df9aa280a59", + "alternativeSignatures": [ + "62828b4278eba310bc7982e5afdac6b0a865bb647f8d3cb078a6391e1a48054f" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "235159b32d9022a9e8bf8e37fd5489bb11ca6f1bf56e8ee8737e63358c3610e5": { + "signature": "235159b32d9022a9e8bf8e37fd5489bb11ca6f1bf56e8ee8737e63358c3610e5", + "alternativeSignatures": [ + "ddb5cc4ca95b12e6fcccfd2c5089c798307d3bb664d91e9a87f6efbd1ea78a19" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "48738e93956293a663a2d1f6933bd6856afc4699c22d45da51ca02ffe4781230": { + "signature": "48738e93956293a663a2d1f6933bd6856afc4699c22d45da51ca02ffe4781230", + "alternativeSignatures": [ + "e83017371567ceabc524ef3e26a5520be1aac07e75b1f02a173f2eb847fbc3e5" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "6d0c84954b066bde9f165d3e7471f621d824bf0f9d920125f2188196c3e7be99": { + "signature": "6d0c84954b066bde9f165d3e7471f621d824bf0f9d920125f2188196c3e7be99", + "alternativeSignatures": [ + "07e93499ac1047ac94bc10474ba57d397bf395f28f8d6e0dd08eeab03f17a539" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "b033555adb13ce0c0ec9e65689e4be5f9856f49f9e201f3dbbb24d430d76ec15": { + "signature": "b033555adb13ce0c0ec9e65689e4be5f9856f49f9e201f3dbbb24d430d76ec15", + "alternativeSignatures": [ + "0a59d205aee1fdc5cb027a46c424e4a2da06c5650aadbfdfec77d744278d6b7b" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "c522ca7bfcad2bc36cfb9567a5bbe36395bb3a5969ce411832197ff9ccfd4b32": { + "signature": "c522ca7bfcad2bc36cfb9567a5bbe36395bb3a5969ce411832197ff9ccfd4b32", + "alternativeSignatures": [ + "32eb8b00cbe99b945a02337c349a0fb97e1763fa9782469531a6859714a7160f" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "1c58db43c898e119381ecd4a9bb9701f2bbbffd3d0889ca734a905c151b168db": { + "signature": "1c58db43c898e119381ecd4a9bb9701f2bbbffd3d0889ca734a905c151b168db", + "alternativeSignatures": [ + "a87bb1c17c23667355c9b8e17f84f37671b65040a3aa7870a7d169165583211e" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "ef7ba70ebe8984da430b1787f439496e14810afd9848a851a3d4cacc37ae7eac": { + "signature": "ef7ba70ebe8984da430b1787f439496e14810afd9848a851a3d4cacc37ae7eac", + "alternativeSignatures": [ + "bbff3ea19b6dd5baa7d56928526a38313685c1d81a30c7c2e7c31d7105d8287f" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "5bb44f02281666fb7ef18753b4ed0e08923d741efe0361ab1336c18dd0086111": { + "signature": "5bb44f02281666fb7ef18753b4ed0e08923d741efe0361ab1336c18dd0086111", + "alternativeSignatures": [ + "a4f1b29f64aa99073ad3678c41b29ab15bd3af2cbc87ae6b05e6568265734146" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "1621b0375d4c98eb6822a7ae37feccda594bbd0137cba47b721f9f9288929947": { + "signature": "1621b0375d4c98eb6822a7ae37feccda594bbd0137cba47b721f9f9288929947", + "alternativeSignatures": [ + "05396ecc761c29e0e0b0a1508c1ed091b213ea18917b9f0ba118c1e7032c51df" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "c566d2437202984d3a9d2682ca810d7c52eb78c7b6236c7b9c82db6f7ba63ac8": { + "signature": "c566d2437202984d3a9d2682ca810d7c52eb78c7b6236c7b9c82db6f7ba63ac8", + "alternativeSignatures": [ + "2bcbfb431661bacfa69c2c43c8f09f47444b22353d9e3f7db28f98c7b8126bd6" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "8f8856d0c4f85bb601ea84e7b9da0d5d636573c772951c4e85bf4cf61fef72bc": { + "signature": "8f8856d0c4f85bb601ea84e7b9da0d5d636573c772951c4e85bf4cf61fef72bc", + "alternativeSignatures": [ + "769ffcc5ae3360b1017647384c184dc56574e0d9ddd00f4083e1c986e9f616b2" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "b4c31c908facbb22d23b15c3280359184d736901c154d516be16495dbb35ccb0": { + "signature": "b4c31c908facbb22d23b15c3280359184d736901c154d516be16495dbb35ccb0", + "alternativeSignatures": [ + "d4603cccc6c303b5f16f1120b15dc74e8bdbef5877922fdcf57f3b29423c027b" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "7e3401dab114451f8ccc26a6ee0ac27639a41cef983b5c7ca7b22f8e03748b53": { + "signature": "7e3401dab114451f8ccc26a6ee0ac27639a41cef983b5c7ca7b22f8e03748b53", + "alternativeSignatures": [ + "32964f7bed67a8e77a61cdc1cdedfb55c8800e04f16e9673dbc1c9dda0d2fcf0" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "1599eec39bd8bfe41754c1b87f6d4cf2517107bf55a31fd6f06e57853f0edcb3": { + "signature": "1599eec39bd8bfe41754c1b87f6d4cf2517107bf55a31fd6f06e57853f0edcb3", + "alternativeSignatures": [ + "1d0195a64263b4be03267b1ce650ac0ad6b3589f32639ecd3792df0a39134d2a" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "c7e28dfe20e2c60575af07324e9960283b6ab567f4d0877763109330077b535d": { + "signature": "c7e28dfe20e2c60575af07324e9960283b6ab567f4d0877763109330077b535d", + "alternativeSignatures": [ + "22da5e4da2ab3befa6aea6c7b562de7057d367a0bf4f05a5791a02c6d7de5886" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "da7db308719d405aa91b08fcda683bc73d9fb73906ad14857b07f1487e2538fb": { + "signature": "da7db308719d405aa91b08fcda683bc73d9fb73906ad14857b07f1487e2538fb", + "alternativeSignatures": [ + "e8a05e57577cfff0f0614b0e2ac77ef86185b636ac9a85322decda6d93bb1025" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "406449365cf5b8a747cda6bcb9f698ab04e5f23c76ea26b15ae5f6dd199e200a": { + "signature": "406449365cf5b8a747cda6bcb9f698ab04e5f23c76ea26b15ae5f6dd199e200a", + "alternativeSignatures": [ + "ab85af1c9f00c18837eddc1a658df6f16759b57337f0c576979a7ead9ca5aea5" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "2d3985d721950fdc54d6dd67f6da4e2f26da1de752237d6eff2bee79e76e74cf": { + "signature": "2d3985d721950fdc54d6dd67f6da4e2f26da1de752237d6eff2bee79e76e74cf", + "alternativeSignatures": [ + "eb8fe28224b13b24a5fd5dab39c73356ab0e958242fefd3468a632b6f1fc8468" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "73a9b54e10ff814734690c8e99319851d3caffb8d7ee02237ccef08057623c57": { + "signature": "73a9b54e10ff814734690c8e99319851d3caffb8d7ee02237ccef08057623c57", + "alternativeSignatures": [ + "a8395f556a6edc7f633b06820ab22d4ae275f1e2f0fb9f0389520cba4a3fa038" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "f5e7a576035cfef04eaefb5c3d64e7575c80aea1449c6528d16bd18f19601938": { + "signature": "f5e7a576035cfef04eaefb5c3d64e7575c80aea1449c6528d16bd18f19601938", + "alternativeSignatures": [ + "e3bf1d972a8173cd7c9e853facdd28b184614cffc18ce2a47e00114894a9a0f3" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "85150297f02b3d19a5a032b1ec6b083b4bc2eec2643f60cceac18fd07551134e": { + "signature": "85150297f02b3d19a5a032b1ec6b083b4bc2eec2643f60cceac18fd07551134e", + "alternativeSignatures": [ + "56a1cb19171420c70451d831140f44f8beace9ee605870a54998170e9b9eea44" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "db5e6cce78b5039be2e56eb828caea7fbc1b2c0ddf2204f3c95e90805f480025": { + "signature": "db5e6cce78b5039be2e56eb828caea7fbc1b2c0ddf2204f3c95e90805f480025", + "alternativeSignatures": [ + "38588e5b5cbf9208115ae67488467545f67a83e5ea18d08f65e76318ba2809c1" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "87cbf986f8699bddcd94445159a5772945dd63bb4f55587afaecb5be2846e3cb": { + "signature": "87cbf986f8699bddcd94445159a5772945dd63bb4f55587afaecb5be2846e3cb", + "alternativeSignatures": [ + "229e52e7992660959c3cc4581b73623f6807f87a266ecced4af4343f1f8a2c06" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "c3cc891778043c08b6177ace3e90a4af4be52f7936d9df603efcfa77f11df9cf": { + "signature": "c3cc891778043c08b6177ace3e90a4af4be52f7936d9df603efcfa77f11df9cf", + "alternativeSignatures": [ + "4d98cab41917051c77b027daaa0ebaad30ae99b738bb3721331ddacb3d13e990" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "dbd00c32ac388db25b7b4095d242411fb192ecc6d471c80d72f6c83bc5790f5d": { + "signature": "dbd00c32ac388db25b7b4095d242411fb192ecc6d471c80d72f6c83bc5790f5d", + "alternativeSignatures": [ + "5d3a76ad12be60e1fc117dae08b4e56f70e0e905000e6bac82b7c0d7d47efa12" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "5b68b91f6e33cf24cb0a6b106bad2cccc5f7191510437ce703a42f1fefbb1d13": { + "signature": "5b68b91f6e33cf24cb0a6b106bad2cccc5f7191510437ce703a42f1fefbb1d13", + "alternativeSignatures": [ + "cbd85190a9dc566b475d9d0da29ef2890469d545129695dccd33e12b042c7f87" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "48a3d50b09e89367e161fa2e843b605dbf840ac3264623bad804634b9e17f0b1": { + "signature": "48a3d50b09e89367e161fa2e843b605dbf840ac3264623bad804634b9e17f0b1", + "alternativeSignatures": [ + "ecf7b4e72800b2f2881b924e7c602984c0101738bfe7d901ef9bf68d54f22acc" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "5b641532bc10265a307b0984e2c1cf7478d345aa73e0b9f52f57de6b6139db0d": { + "signature": "5b641532bc10265a307b0984e2c1cf7478d345aa73e0b9f52f57de6b6139db0d", + "alternativeSignatures": [ + "93f72434bf401ab42b1478aae49174f0f231385f2d190646de90b2f321bf0405" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "f5bbdce1f432ce0cc0f8eab2a548e86389c11972a0fd807d5b0218929dc0667f": { + "signature": "f5bbdce1f432ce0cc0f8eab2a548e86389c11972a0fd807d5b0218929dc0667f", + "alternativeSignatures": [ + "c0dd620ed7794312e1f535291893133d2983ef6af266bf1261e08f5a40030184" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "b48abf4f4fc439b180232125d632b47c3ca44cde8f518e08d38905578255a76e": { + "signature": "b48abf4f4fc439b180232125d632b47c3ca44cde8f518e08d38905578255a76e", + "alternativeSignatures": [ + "8b1d9c30d610788b227bcf7b1a02f715887adb26d9d476705a864bfbd3eec42d" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "5b91412070b80c595c93a131c6423a78aca0eb174e67b49a72f3a28b2f2e04fc": { + "signature": "5b91412070b80c595c93a131c6423a78aca0eb174e67b49a72f3a28b2f2e04fc", + "alternativeSignatures": [ + "3b52062806f35937f2372d4497d5679d95e9c28d9cef710ac9f0551b3bf6fa69" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "4e6e328cd795ad8ffa3d409d0e0313ae91f87124a058e5dfe64dfbc93e236f78": { + "signature": "4e6e328cd795ad8ffa3d409d0e0313ae91f87124a058e5dfe64dfbc93e236f78", + "alternativeSignatures": [ + "656279220733cb5f45e3ef13a50ae9174c9763f7c24ff163245f6bea398d0794" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "4dd68b37dee025fa0a77ac00e6e85ce7e1ad8add70a2226e10a6423534359a9b": { + "signature": "4dd68b37dee025fa0a77ac00e6e85ce7e1ad8add70a2226e10a6423534359a9b", + "alternativeSignatures": [ + "c63dd7cfcff47e1a2f686035319925e8b2d3b0f1ac8db86b39d4b1821ae4797d" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "62a125c376534b19e9083efbad081f32bc294602b7f19a5210ad940e1c450a5f": { + "signature": "62a125c376534b19e9083efbad081f32bc294602b7f19a5210ad940e1c450a5f", + "alternativeSignatures": [ + "2b2732249979c5e87cc79cbdec660e2b1c8eba1f93a362e5595ec03cdf1e4951" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + }, + "f918c167a1a70deba61361fd3138b76fba86387a1f41237efeda8f01d464457e": { + "signature": "f918c167a1a70deba61361fd3138b76fba86387a1f41237efeda8f01d464457e", + "alternativeSignatures": [ + "959e93d957e0e5ec521d2f3b9dae02db123812fb8d523d8175329b08694d65d9" + ], + "memberOf": [ + "default" + ], + "createdDate": "2023-09-29 19:39:12Z" + } + } +} \ No newline at end of file diff --git a/build/config/tsa.json b/build/config/tsa.json new file mode 100644 index 00000000000..89874d37fc0 --- /dev/null +++ b/build/config/tsa.json @@ -0,0 +1,6 @@ +{ + "instanceUrl": "https://microsoft.visualstudio.com", + "projectName": "OS", + "areaPath": "OS\\Windows Client and Services\\ADEPT\\E4D-Engineered for Developers\\SHINE\\Terminal", + "notificationAliases": ["condev@microsoft.com", "duhowett@microsoft.com"] +} diff --git a/build/pipelines/ob-nightly.yml b/build/pipelines/ob-nightly.yml new file mode 100644 index 00000000000..c8c62e278f0 --- /dev/null +++ b/build/pipelines/ob-nightly.yml @@ -0,0 +1,47 @@ +trigger: none +pr: none +schedules: + - cron: "30 3 * * 2-6" # Run at 03:30 UTC Tuesday through Saturday (After the work day in Pacific, Mon-Fri) + displayName: "Nightly Terminal Build" + branches: + include: + - main + always: false # only run if there's code changes! + +parameters: + - name: publishToAzure + displayName: "Deploy to **PUBLIC** Azure Storage" + type: boolean + default: true + +name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr) + +variables: + - template: templates-v2/variables-nuget-package-version.yml + - template: templates-v2/variables-onebranch-config.yml + +extends: + template: templates-v2/pipeline-onebranch-full-release-build.yml + parameters: + official: true + branding: Canary + buildTerminal: true + pgoBuildMode: Optimize + codeSign: true + publishSymbolsToPublic: true + publishVpackToWindows: false + symbolExpiryTime: 15 + ${{ if eq(true, parameters.publishToAzure) }}: + extraPublishJobs: + - template: job-deploy-to-azure-storage.yml + parameters: + pool: { type: windows } + dependsOn: [PublishSymbols] + storagePublicRootURL: $(AppInstallerRootURL) + subscription: $(AzureSubscriptionName) + storageAccount: $(AzureStorageAccount) + storageContainer: $(AzureStorageContainer) + buildConfiguration: Release + buildPlatforms: [x64, x86, arm64] + environment: production-canary + diff --git a/build/pipelines/ob-release.yml b/build/pipelines/ob-release.yml new file mode 100644 index 00000000000..9c81f570666 --- /dev/null +++ b/build/pipelines/ob-release.yml @@ -0,0 +1,81 @@ +trigger: none +pr: none + +# Expose all of these parameters for user configuration. +parameters: + - name: branding + displayName: "Branding (Build Type)" + type: string + default: Release + values: + - Release + - Preview + - Canary + - Dev + - name: buildTerminal + displayName: "Build Windows Terminal MSIX" + type: boolean + default: true + - name: buildConPTY + displayName: "Build ConPTY NuGet" + type: boolean + default: false + - name: buildWPF + displayName: "Build Terminal WPF Control" + type: boolean + default: false + - name: pgoBuildMode + displayName: "PGO Build Mode" + type: string + default: Optimize + values: + - Optimize + - Instrument + - None + - name: buildConfigurations + displayName: "Build Configurations" + type: object + default: + - Release + - name: buildPlatforms + displayName: "Build Platforms" + type: object + default: + - x64 + - x86 + - arm64 + - name: terminalInternalPackageVersion + displayName: "Terminal Internal Package Version" + type: string + default: '0.0.8' + + - name: publishSymbolsToPublic + displayName: "Publish Symbols to MSDL" + type: boolean + default: true + - name: publishVpackToWindows + displayName: "Publish VPack to Windows" + type: boolean + default: false + +name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr) + +variables: + - template: templates-v2/variables-nuget-package-version.yml + - template: templates-v2/variables-onebranch-config.yml + +extends: + template: templates-v2/pipeline-onebranch-full-release-build.yml + parameters: + official: true + branding: ${{ parameters.branding }} + buildTerminal: ${{ parameters.buildTerminal }} + buildConPTY: ${{ parameters.buildConPTY }} + buildWPF: ${{ parameters.buildWPF }} + pgoBuildMode: ${{ parameters.pgoBuildMode }} + buildConfigurations: ${{ parameters.buildConfigurations }} + buildPlatforms: ${{ parameters.buildPlatforms }} + codeSign: true + terminalInternalPackageVersion: ${{ parameters.terminalInternalPackageVersion }} + publishSymbolsToPublic: ${{ parameters.publishSymbolsToPublic }} + publishVpackToWindows: ${{ parameters.publishVpackToWindows }} diff --git a/build/pipelines/templates-v2/job-build-project.yml b/build/pipelines/templates-v2/job-build-project.yml index 90acb44cbd1..d04561230f4 100644 --- a/build/pipelines/templates-v2/job-build-project.yml +++ b/build/pipelines/templates-v2/job-build-project.yml @@ -62,6 +62,9 @@ parameters: - name: publishArtifacts type: boolean default: true + - name: removeAllNonSignedFiles + type: boolean + default: false jobs: - job: ${{ parameters.jobName }} @@ -168,6 +171,7 @@ jobs: # - Directories ending in Lib (static lib projects that we fully linked into DLLs which may also contain unnecessary resources) # - All LocalTests_ project outputs, as they were subsumed into TestHostApp # - All PDB files inside the WindowsTerminal/ output, which do not belong there. + # - console.dll, which apparently breaks XFGCheck? lol. - pwsh: |- $binDir = '$(Terminal.BinDir)' $ImportLibs = Get-ChildItem $binDir -Recurse -File -Filter '*.exp' | ForEach-Object { $_.FullName -Replace "exp$","lib" } @@ -184,6 +188,8 @@ jobs: $Items += Get-ChildItem '$(Terminal.BinDir)' -Filter '*.pdb' -Recurse } + $Items += Get-ChildItem $binDir -Filter 'console.dll' + $Items | Remove-Item -Recurse -Force -Verbose -ErrorAction:Ignore displayName: Clean up static libs and extra symbols errorActionPreference: silentlyContinue # It's OK if this silently fails @@ -241,6 +247,14 @@ jobs: Write-Host "##vso[task.setvariable variable=WindowsTerminalPackagePath]${PackageFilename}" displayName: Re-pack the new Terminal package after signing + # Some of our governed pipelines explicitly fail builds that have *any* non-codesigned filed (!) + - ${{ if eq(parameters.removeAllNonSignedFiles, true) }}: + - pwsh: |- + Get-ChildItem "$(Terminal.BinDir)" -Recurse -Include "*.dll","*.exe" | + Where-Object { (Get-AuthenticodeSignature $_).Status -Ne "Valid" } | + Remove-Item -Verbose -Force + displayName: Remove all non-signed output files + - ${{ else }}: # No Signing - ${{ if or(parameters.buildTerminal, parameters.buildEverything) }}: - pwsh: |- diff --git a/build/pipelines/templates-v2/job-merge-msix-into-bundle.yml b/build/pipelines/templates-v2/job-merge-msix-into-bundle.yml index ffca7e450e3..6d6ad09cdad 100644 --- a/build/pipelines/templates-v2/job-merge-msix-into-bundle.yml +++ b/build/pipelines/templates-v2/job-merge-msix-into-bundle.yml @@ -29,6 +29,9 @@ parameters: - name: publishArtifacts type: boolean default: true + - name: afterBuildSteps + type: stepList + default: [] jobs: - job: ${{ parameters.jobName }} @@ -85,7 +88,9 @@ jobs: $Components[0] = ([int]$Components[0] + $VersionEpoch) $BundleVersion = $Components -Join "." New-Item -Type Directory "$(System.ArtifactsDirectory)/bundle" - .\build\scripts\Create-AppxBundle.ps1 -InputPath 'bin/' -ProjectName CascadiaPackage -BundleVersion $BundleVersion -OutputPath "$(System.ArtifactsDirectory)\bundle\$(BundleStemName)_$(XES_APPXMANIFESTVERSION)_8wekyb3d8bbwe.msixbundle" + $BundlePath = "$(System.ArtifactsDirectory)\bundle\$(BundleStemName)_$(XES_APPXMANIFESTVERSION)_8wekyb3d8bbwe.msixbundle" + .\build\scripts\Create-AppxBundle.ps1 -InputPath 'bin/' -ProjectName CascadiaPackage -BundleVersion $BundleVersion -OutputPath $BundlePath + Write-Host "##vso[task.setvariable variable=MsixBundlePath]${BundlePath}" displayName: Create msixbundle - ${{ if eq(parameters.codeSign, true) }}: @@ -139,6 +144,8 @@ jobs: ValidateSignature: true Verbosity: 'Verbose' + - ${{ parameters.afterBuildSteps }} + - ${{ if eq(parameters.publishArtifacts, true) }}: - publish: $(JobOutputDirectory) artifact: $(JobOutputArtifactName) diff --git a/build/pipelines/templates-v2/job-publish-symbols.yml b/build/pipelines/templates-v2/job-publish-symbols.yml index 65663885602..83c472872b8 100644 --- a/build/pipelines/templates-v2/job-publish-symbols.yml +++ b/build/pipelines/templates-v2/job-publish-symbols.yml @@ -17,6 +17,12 @@ parameters: - name: symbolExpiryTime type: string default: 36530 # This is the default from PublishSymbols@2 + - name: variables + type: object + default: {} + - name: symbolPatGoesInTaskInputs + type: boolean + default: false jobs: - job: ${{ parameters.jobName }} @@ -27,6 +33,8 @@ jobs: ${{ else }}: displayName: Publish Symbols Internally dependsOn: ${{ parameters.dependsOn }} + variables: + ${{ insert }}: ${{ parameters.variables }} steps: - checkout: self clean: true @@ -76,6 +84,8 @@ jobs: SymbolsProduct: 'Windows Terminal Converged Symbols' SymbolsVersion: '$(XES_APPXMANIFESTVERSION)' SymbolExpirationInDays: ${{ parameters.symbolExpiryTime }} + ${{ if eq(parameters.symbolPatGoesInTaskInputs, true) }}: + Pat: $(ADO_microsoftpublicsymbols_PAT) # The ADO task does not support indexing of GitHub sources. # There is a bug which causes this task to fail if LIB includes an inaccessible path (even though it does not depend on it). # To work around this issue, we just force LIB to be any dir that we know exists. @@ -83,4 +93,5 @@ jobs: env: LIB: $(Build.SourcesDirectory) ArtifactServices_Symbol_AccountName: microsoftpublicsymbols - ArtifactServices_Symbol_PAT: $(ADO_microsoftpublicsymbols_PAT) + ${{ if ne(parameters.symbolPatGoesInTaskInputs, true) }}: + ArtifactServices_Symbol_PAT: $(ADO_microsoftpublicsymbols_PAT) diff --git a/build/pipelines/templates-v2/pipeline-full-release-build.yml b/build/pipelines/templates-v2/pipeline-full-release-build.yml index b785deef64e..03826cac85d 100644 --- a/build/pipelines/templates-v2/pipeline-full-release-build.yml +++ b/build/pipelines/templates-v2/pipeline-full-release-build.yml @@ -61,28 +61,7 @@ parameters: demands: ImageOverride -equals SHINE-VS17-Latest variables: - # If we are building a branch called "release-*", change the NuGet suffix - # to "preview". If we don't do that, XES will set the suffix to "release1" - # because it truncates the value after the first period. - # We also want to disable the suffix entirely if we're Release branded while - # on a release branch. - # main is special, however. XES ignores main. Since we never produce actual - # shipping builds from main, we want to force it to have a beta label as - # well. - # - # In effect: - # BRANCH / BRANDING | Release | Preview - # ------------------|----------------------------|----------------------------- - # release-* | 1.12.20220427 | 1.13.20220427-preview - # main | 1.14.20220427-experimental | 1.14.20220427-experimental - # all others | 1.14.20220427-mybranch | 1.14.20220427-mybranch - ${{ if startsWith(variables['Build.SourceBranchName'], 'release-') }}: - ${{ if eq(parameters.branding, 'Release') }}: - NoNuGetPackBetaVersion: true - ${{ else }}: - NuGetPackBetaVersion: preview - ${{ elseif eq(variables['Build.SourceBranchName'], 'main') }}: - NuGetPackBetaVersion: experimental + - template: variables-nuget-package-version.yml resources: repositories: diff --git a/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml b/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml new file mode 100644 index 00000000000..a466e987f08 --- /dev/null +++ b/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml @@ -0,0 +1,256 @@ +parameters: + - name: official + type: boolean + default: false + - name: branding + type: string + default: Release + values: + - Release + - Preview + - Canary + - Dev + - name: buildTerminal + type: boolean + default: true + - name: buildConPTY + type: boolean + default: false + - name: buildWPF + type: boolean + default: false + - name: pgoBuildMode + type: string + default: Optimize + values: + - Optimize + - Instrument + - None + - name: buildConfigurations + type: object + default: + - Release + - name: buildPlatforms + type: object + default: + - x64 + - x86 + - arm64 + - name: codeSign + type: boolean + default: true + - name: terminalInternalPackageVersion + type: string + default: '0.0.8' + + - name: publishSymbolsToPublic + type: boolean + default: true + - name: publishVpackToWindows + type: boolean + default: false + + - name: extraPublishJobs + type: object + default: [] + +resources: + repositories: + - repository: templates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + +extends: + ${{ if eq(parameters.official, true) }}: + template: v2/Microsoft.Official.yml@templates # https://aka.ms/obpipelines/templates + ${{ else }}: + template: v2/Microsoft.NonOfficial.yml@templates + parameters: + featureFlags: + WindowsHostVersion: 1ESWindows2022 + platform: + name: 'windows_undocked' + product: 'Windows Terminal' + cloudvault: # https://aka.ms/obpipelines/cloudvault + enabled: false + globalSdl: # https://aka.ms/obpipelines/sdl + tsa: + enabled: true + configFile: '$(Build.SourcesDirectory)\build\config\tsa.json' + binskim: + break: false + scanOutputDirectoryOnly: true + policheck: + break: false + severity: Note + baseline: + baselineFile: '$(Build.SourcesDirectory)\build\config\release.gdnbaselines' + suppressionSet: default + + stages: + - stage: Build + displayName: Build + dependsOn: [] + jobs: + - template: ./build/pipelines/templates-v2/job-build-project.yml@self + parameters: + pool: { type: windows } + variables: + ob_git_checkout: false # This job checks itself out + ob_git_skip_checkout_none: true + ob_outputDirectory: $(JobOutputDirectory) + ob_artifactBaseName: $(JobOutputArtifactName) + publishArtifacts: false # Handled by OneBranch + branding: ${{ parameters.branding }} + buildTerminal: ${{ parameters.buildTerminal }} + buildConPTY: ${{ parameters.buildConPTY }} + buildWPF: ${{ parameters.buildWPF }} + pgoBuildMode: ${{ parameters.pgoBuildMode }} + buildConfigurations: ${{ parameters.buildConfigurations }} + buildPlatforms: ${{ parameters.buildPlatforms }} + generateSbom: false # this is handled by onebranch + removeAllNonSignedFiles: true # appease the overlords + codeSign: ${{ parameters.codeSign }} + beforeBuildSteps: # Right before we build, lay down the universal package and localizations + - task: PkgESSetupBuild@12 + displayName: Package ES - Setup Build + inputs: + disableOutputRedirect: true + + - task: UniversalPackages@0 + displayName: Download terminal-internal Universal Package + inputs: + feedListDownload: 2b3f8893-a6e8-411f-b197-a9e05576da48 + packageListDownload: e82d490c-af86-4733-9dc4-07b772033204 + versionListDownload: ${{ parameters.terminalInternalPackageVersion }} + + - template: ./build/pipelines/templates-v2/steps-fetch-and-prepare-localizations.yml@self + parameters: + includePseudoLoc: true + + - ${{ if eq(parameters.buildWPF, true) }}: + # Add an Any CPU build flavor for the WPF control bits + - template: ./build/pipelines/templates-v2/job-build-project.yml@self + parameters: + pool: { type: windows } + variables: + ob_git_checkout: false # This job checks itself out + ob_git_skip_checkout_none: true + ob_outputDirectory: $(JobOutputDirectory) + ob_artifactBaseName: $(JobOutputArtifactName) + publishArtifacts: false # Handled by OneBranch + jobName: BuildWPF + branding: ${{ parameters.branding }} + buildTerminal: false + buildWPFDotNetComponents: true + buildConfigurations: ${{ parameters.buildConfigurations }} + buildPlatforms: + - Any CPU + generateSbom: false # this is handled by onebranch + removeAllNonSignedFiles: true # appease the overlords + codeSign: ${{ parameters.codeSign }} + beforeBuildSteps: + - task: PkgESSetupBuild@12 + displayName: Package ES - Setup Build + inputs: + disableOutputRedirect: true + # WPF doesn't need the localizations or the universal package, but if it does... put them here. + + - stage: Package + displayName: Package + dependsOn: [Build] + jobs: + - ${{ if eq(parameters.buildTerminal, true) }}: + - template: ./build/pipelines/templates-v2/job-merge-msix-into-bundle.yml@self + parameters: + pool: { type: windows } + variables: + ob_git_checkout: false # This job checks itself out + ob_git_skip_checkout_none: true + ob_outputDirectory: $(JobOutputDirectory) + ob_artifactBaseName: $(JobOutputArtifactName) + ### This job is also in charge of submitting the vpack to Windows if it's enabled + ob_createvpack_enabled: ${{ and(parameters.buildTerminal, parameters.publishVpackToWindows) }} + ob_updateOSManifest_enabled: ${{ and(parameters.buildTerminal, parameters.publishVpackToWindows) }} + ### If enabled above, these options are in play. + ob_createvpack_packagename: 'WindowsTerminal.app' + ob_createvpack_owneralias: 'conhost@microsoft.com' + ob_createvpack_description: 'VPack for the Windows Terminal Application' + ob_createvpack_targetDestinationDirectory: '$(Destination)' + ob_createvpack_propsFile: false + ob_createvpack_provData: true + ob_createvpack_metadata: '$(Build.SourceVersion)' + ob_createvpack_topLevelRetries: 0 + ob_createvpack_failOnStdErr: true + ob_createvpack_taskLogVerbosity: Detailed + ob_createvpack_verbose: true + ob_createvpack_vpackdirectory: '$(JobOutputDirectory)\vpack' + ob_updateOSManifest_gitcheckinConfigPath: '$(Build.SourcesDirectory)\build\config\GitCheckin.json' + # We're skipping the 'fetch' part of the OneBranch rules, but that doesn't mean + # that it doesn't expect to have downloaded a manifest directly to some 'destination' + # folder that it can then update and upload. + # Effectively: it says "destination" but it means "source" + # DH: Don't ask why. + ob_updateOSManifest_destination: $(XES_VPACKMANIFESTDIRECTORY) + ob_updateOSManifest_skipFetch: true + publishArtifacts: false # Handled by OneBranch + jobName: Bundle + branding: ${{ parameters.branding }} + buildConfigurations: ${{ parameters.buildConfigurations }} + buildPlatforms: ${{ parameters.buildPlatforms }} + generateSbom: false # Handled by onebranch + codeSign: ${{ parameters.codeSign }} + afterBuildSteps: + - pwsh: |- + $d = New-Item "$(JobOutputDirectory)/vpack" -Type Directory + Copy-Item -Verbose -Path "$(MsixBundlePath)" -Destination (Join-Path $d 'Microsoft.WindowsTerminal_8wekyb3d8bbwe.msixbundle') + displayName: Stage msixbundle for vpack + + - ${{ if eq(parameters.buildConPTY, true) }}: + - template: ./build/pipelines/templates-v2/job-package-conpty.yml@self + parameters: + pool: { type: windows } + variables: + ob_git_checkout: false # This job checks itself out + ob_git_skip_checkout_none: true + ob_outputDirectory: $(JobOutputDirectory) + ob_artifactBaseName: $(JobOutputArtifactName) + publishArtifacts: false # Handled by OneBranch + buildConfigurations: ${{ parameters.buildConfigurations }} + buildPlatforms: ${{ parameters.buildPlatforms }} + generateSbom: false # this is handled by onebranch + codeSign: ${{ parameters.codeSign }} + + - ${{ if eq(parameters.buildWPF, true) }}: + - template: ./build/pipelines/templates-v2/job-build-package-wpf.yml@self + parameters: + pool: { type: windows } + variables: + ob_git_checkout: false # This job checks itself out + ob_git_skip_checkout_none: true + ob_outputDirectory: $(JobOutputDirectory) + ob_artifactBaseName: $(JobOutputArtifactName) + publishArtifacts: false # Handled by OneBranch + buildConfigurations: ${{ parameters.buildConfigurations }} + buildPlatforms: ${{ parameters.buildPlatforms }} + generateSbom: false # this is handled by onebranch + codeSign: ${{ parameters.codeSign }} + + - stage: Publish + displayName: Publish + dependsOn: [Build, Package] + jobs: + - template: ./build/pipelines/templates-v2/job-publish-symbols.yml@self + parameters: + pool: { type: windows } + includePublicSymbolServer: ${{ parameters.publishSymbolsToPublic }} + symbolPatGoesInTaskInputs: true # onebranch tries to muck with the PAT variable, so we need to change how it get the PAT + variables: + ob_git_checkout: false # This job checks itself out + ob_git_skip_checkout_none: true + ob_outputDirectory: $(Build.ArtifactStagingDirectory) + # Without this, OneBranch will nerf our symbol tasks + ob_symbolsPublishing_enabled: true + + - ${{ parameters.extraPublishJobs }} diff --git a/build/pipelines/templates-v2/variables-nuget-package-version.yml b/build/pipelines/templates-v2/variables-nuget-package-version.yml new file mode 100644 index 00000000000..af195c043f6 --- /dev/null +++ b/build/pipelines/templates-v2/variables-nuget-package-version.yml @@ -0,0 +1,23 @@ +variables: + # If we are building a branch called "release-*", change the NuGet suffix + # to "preview". If we don't do that, XES will set the suffix to "release1" + # because it truncates the value after the first period. + # We also want to disable the suffix entirely if we're Release branded while + # on a release branch. + # main is special, however. XES ignores main. Since we never produce actual + # shipping builds from main, we want to force it to have a beta label as + # well. + # + # In effect: + # BRANCH / BRANDING | Release | Preview + # ------------------|----------------------------|----------------------------- + # release-* | 1.12.20220427 | 1.13.20220427-preview + # main | 1.14.20220427-experimental | 1.14.20220427-experimental + # all others | 1.14.20220427-mybranch | 1.14.20220427-mybranch + ${{ if startsWith(variables['Build.SourceBranchName'], 'release-') }}: + ${{ if eq(parameters.branding, 'Release') }}: + NoNuGetPackBetaVersion: true + ${{ else }}: + NuGetPackBetaVersion: preview + ${{ elseif eq(variables['Build.SourceBranchName'], 'main') }}: + NuGetPackBetaVersion: experimental diff --git a/build/pipelines/templates-v2/variables-onebranch-config.yml b/build/pipelines/templates-v2/variables-onebranch-config.yml new file mode 100644 index 00000000000..d7180e3e090 --- /dev/null +++ b/build/pipelines/templates-v2/variables-onebranch-config.yml @@ -0,0 +1,2 @@ +variables: + WindowsContainerImage: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' From 47728dc38bd6d2285ae4077321f81c999febed50 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 3 Oct 2023 15:29:38 -0500 Subject: [PATCH 018/167] Fix tearout with `startupActions` set. (#16089) Wow our preview population must just not use `startupActions`. This obviously never worked in 1.18 Preview. Closes #16050 (cherry picked from commit f6425dbd594b18f2f25ed968663f8c94f3e94aa1) Service-Card-Id: 90715243 Service-Version: 1.19 --- src/cascadia/TerminalApp/TerminalWindow.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp index e6414406aaa..8be6872e98b 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.cpp +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -204,7 +204,12 @@ namespace winrt::TerminalApp::implementation // Pay attention, that even if some command line arguments were parsed (like launch mode), // we will not use the startup actions from settings. // While this simplifies the logic, we might want to reconsider this behavior in the future. - if (!_hasCommandLineArguments && _gotSettingsStartupActions) + // + // Obviously, don't use the `startupActions` from the settings in the + // case of a tear-out / reattach. GH#16050 + if (!_hasCommandLineArguments && + _initialContentArgs.empty() && + _gotSettingsStartupActions) { _root->SetStartupActions(_settingsStartupArgs); } From 70ad67053df2645fd6df0333b765ab660bd61f5a Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 3 Oct 2023 15:31:01 -0500 Subject: [PATCH 019/167] Use `weak_ptr`s for `AppHost` for coroutines (#16065) See MSFT:46763065. Looks like we're in the middle of being `Refrigerate`d, we're pumping messages, and as we pump messages, we get to a `co_await` in `AppHost::_WindowInitializedHandler`. When we resume, we just try to use `this` like everything's fine but OH NO, IT'S NOT. To fix this, I'm * Adding `enable_shared_from_this` to `AppHost` * Holding the `AppHost` in a shared_ptr in WindowThread - though, this is a singular owning `shared_ptr`. This is probably ripe for other footguns, but there's little we can do about this. * whenever we `co_await` in `AppHost`, make sure we grab a weak ref first, and check it on the other side. This is another "squint and yep that's a bug" fix, that I haven't been able to verify locally. This is [allegedly](https://media.tenor.com/VQi3bktwLdIAAAAC/allegedly-supposedly.gif) about 10% of our 1.19 crashes after 3 days. Closes #16061 (cherry picked from commit 8521aae889b08cc3619624ceb212d810bb5cbaa7) Service-Card-Id: 90731962 Service-Version: 1.19 --- src/cascadia/WindowsTerminal/AppHost.cpp | 43 +++++++++++++++++++ src/cascadia/WindowsTerminal/AppHost.h | 2 +- src/cascadia/WindowsTerminal/WindowThread.cpp | 4 +- src/cascadia/WindowsTerminal/WindowThread.h | 6 ++- 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 81218dbf4c9..36d47211b79 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -931,8 +931,17 @@ winrt::fire_and_forget AppHost::_peasantNotifyActivateWindow() const auto peasant = _peasant; const auto hwnd = _window->GetHandle(); + auto weakThis{ weak_from_this() }; + co_await winrt::resume_background(); + // If we're gone on the other side of this co_await, well, that's fine. Just bail. + const auto strongThis = weakThis.lock(); + if (!strongThis) + { + co_return; + } + GUID currentDesktopGuid{}; if (FAILED_LOG(desktopManager->GetWindowDesktopId(hwnd, ¤tDesktopGuid))) { @@ -963,8 +972,18 @@ winrt::fire_and_forget AppHost::_peasantNotifyActivateWindow() winrt::Windows::Foundation::IAsyncOperation AppHost::_GetWindowLayoutAsync() { winrt::hstring layoutJson = L""; + + auto weakThis{ weak_from_this() }; + // Use the main thread since we are accessing controls. co_await wil::resume_foreground(_windowLogic.GetRoot().Dispatcher()); + + const auto strongThis = weakThis.lock(); + if (!strongThis) + { + co_return layoutJson; + } + try { const auto pos = _GetWindowLaunchPosition(); @@ -1021,10 +1040,19 @@ void AppHost::_HandleSummon(const winrt::Windows::Foundation::IInspectable& /*se winrt::fire_and_forget AppHost::_IdentifyWindowsRequested(const winrt::Windows::Foundation::IInspectable /*sender*/, const winrt::Windows::Foundation::IInspectable /*args*/) { + auto weakThis{ weak_from_this() }; + // We'll be raising an event that may result in a RPC call to the monarch - // make sure we're on the background thread, or this will silently fail co_await winrt::resume_background(); + // If we're gone on the other side of this co_await, well, that's fine. Just bail. + const auto strongThis = weakThis.lock(); + if (!strongThis) + { + co_return; + } + if (_peasant) { _peasant.RequestIdentifyWindows(); @@ -1234,9 +1262,16 @@ void AppHost::_IsQuakeWindowChanged(const winrt::Windows::Foundation::IInspectab winrt::fire_and_forget AppHost::_QuitRequested(const winrt::Windows::Foundation::IInspectable&, const winrt::Windows::Foundation::IInspectable&) { + auto weakThis{ weak_from_this() }; // Need to be on the main thread to close out all of the tabs. co_await wil::resume_foreground(_windowLogic.GetRoot().Dispatcher()); + const auto strongThis = weakThis.lock(); + if (!strongThis) + { + co_return; + } + _windowLogic.Quit(); } @@ -1387,12 +1422,20 @@ winrt::fire_and_forget AppHost::_WindowInitializedHandler(const winrt::Windows:: nCmdShow = SW_MAXIMIZE; } + auto weakThis{ weak_from_this() }; // For inexplicable reasons, again, hop to the BG thread, then back to the // UI thread. This is shockingly load bearing - without this, then // sometimes, we'll _still_ show the HWND before the XAML island actually // paints. co_await wil::resume_foreground(_windowLogic.GetRoot().Dispatcher(), winrt::Windows::UI::Core::CoreDispatcherPriority::Low); + // If we're gone on the other side of this co_await, well, that's fine. Just bail. + const auto strongThis = weakThis.lock(); + if (!strongThis) + { + co_return; + } + ShowWindow(_window->GetHandle(), nCmdShow); // If we didn't start the window hidden (in one way or another), then try to diff --git a/src/cascadia/WindowsTerminal/AppHost.h b/src/cascadia/WindowsTerminal/AppHost.h index dabe517394b..fd912aee67d 100644 --- a/src/cascadia/WindowsTerminal/AppHost.h +++ b/src/cascadia/WindowsTerminal/AppHost.h @@ -6,7 +6,7 @@ #include "NotificationIcon.h" #include -class AppHost +class AppHost : public std::enable_shared_from_this { public: AppHost(const winrt::TerminalApp::AppLogic& logic, diff --git a/src/cascadia/WindowsTerminal/WindowThread.cpp b/src/cascadia/WindowsTerminal/WindowThread.cpp index 25fb94d5b07..2c70741ad1c 100644 --- a/src/cascadia/WindowsTerminal/WindowThread.cpp +++ b/src/cascadia/WindowsTerminal/WindowThread.cpp @@ -24,7 +24,7 @@ void WindowThread::CreateHost() assert(_warmWindow == nullptr); // Start the AppHost HERE, on the actual thread we want XAML to run on - _host = std::make_unique<::AppHost>(_appLogic, + _host = std::make_shared<::AppHost>(_appLogic, _args, _manager, _peasant); @@ -164,7 +164,7 @@ void WindowThread::Microwave( _peasant = std::move(peasant); _args = std::move(args); - _host = std::make_unique<::AppHost>(_appLogic, + _host = std::make_shared<::AppHost>(_appLogic, _args, _manager, _peasant, diff --git a/src/cascadia/WindowsTerminal/WindowThread.h b/src/cascadia/WindowsTerminal/WindowThread.h index 0e8bc5429d1..a1af2db6500 100644 --- a/src/cascadia/WindowsTerminal/WindowThread.h +++ b/src/cascadia/WindowsTerminal/WindowThread.h @@ -35,7 +35,11 @@ class WindowThread : public std::enable_shared_from_this winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs _args{ nullptr }; winrt::Microsoft::Terminal::Remoting::WindowManager _manager{ nullptr }; - std::unique_ptr<::AppHost> _host{ nullptr }; + // This is a "shared_ptr", but it should be treated as a unique, owning ptr. + // It's shared, because there are edge cases in refrigeration where internal + // co_awaits inside AppHost might resume after we've dtor'd it, and there's + // no other way for us to let the AppHost know it has passed on. + std::shared_ptr<::AppHost> _host{ nullptr }; winrt::event_token _UpdateSettingsRequestedToken; std::unique_ptr<::IslandWindow> _warmWindow{ nullptr }; From 4ee21b38b5c1cc50979e4fcc11f97d35df9c95fb Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 3 Oct 2023 15:31:43 -0500 Subject: [PATCH 020/167] Fix a crash in the GenerateName for `SearchForTextArgs` (#16054) Fixes MSFT:46725264 don't explode trying to parse a URL, if the string wasn't one. (cherry picked from commit 59aaba7c5be7cd6df0385b39835ad1d0c270ed8c) Service-Card-Id: 90687770 Service-Version: 1.19 --- .../TerminalSettingsModel/ActionArgs.cpp | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionArgs.cpp index b2247321d5f..a276ebeff2b 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.cpp @@ -814,10 +814,25 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation winrt::hstring SearchForTextArgs::GenerateName() const { - return winrt::hstring{ - fmt::format(std::wstring_view(RS_(L"SearchForTextCommandKey")), - Windows::Foundation::Uri(QueryUrl()).Domain().c_str()) - }; + if (QueryUrl().empty()) + { + // Return the default command name, because we'll just use the + // default search engine for this. + return RS_(L"SearchWebCommandKey"); + } + + try + { + return winrt::hstring{ + fmt::format(std::wstring_view(RS_(L"SearchForTextCommandKey")), + Windows::Foundation::Uri(QueryUrl()).Domain().c_str()) + }; + } + CATCH_LOG(); + + // We couldn't parse a URL out of this. Return no string at all, so that + // we don't even put this into the command palette. + return L""; } winrt::hstring GlobalSummonArgs::GenerateName() const From f6e1126b7726c6cb7da1fbfad5935c8771dcdfa7 Mon Sep 17 00:00:00 2001 From: inisarg Date: Wed, 4 Oct 2023 02:05:34 +0530 Subject: [PATCH 021/167] Dismiss flyouts before opening warning dialog when exiting app (#16075) Updated the function `TerminalPage::CloseWindow` to include logic for closing context and flyout menus so that they are dismissed before the warning is displayed. Closes #16039 (cherry picked from commit aafb91745e6600d482b37644884a1a7461f8cc9d) Service-Card-Id: 90731989 Service-Version: 1.19 --- src/cascadia/TerminalApp/TerminalPage.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 64dd32c41f4..6a4715e3ca7 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -1983,6 +1983,11 @@ namespace winrt::TerminalApp::implementation _settings.GlobalSettings().ConfirmCloseAllTabs() && !_displayingCloseDialog) { + if (_newTabButton && _newTabButton.Flyout()) + { + _newTabButton.Flyout().Hide(); + } + _DismissTabContextMenus(); _displayingCloseDialog = true; auto warningResult = co_await _ShowCloseWarningDialog(); _displayingCloseDialog = false; From 6ecdae796f5e8a9bd08d56e9c317fe245ca51f23 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 3 Oct 2023 15:41:18 -0500 Subject: [PATCH 022/167] Fix `closeOnExit: always` (#16090) Well, Pane doesn't _only_ care if the connection isn't entering a terminal state. It does need to update its own state first. Regressed in #15335 Closes #16068 (cherry picked from commit 4145f18768772270da03f9ea56b5ec6f7157375c) Service-Card-Id: 90731934 Service-Version: 1.19 --- src/cascadia/TerminalApp/Pane.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index 0d0fe1a71b1..fccd8b8c5d1 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -1040,6 +1040,7 @@ winrt::fire_and_forget Pane::_ControlConnectionStateChangedHandler(const winrt:: newConnectionState = coreState.ConnectionState(); } + const auto previousConnectionState = std::exchange(_connectionState, newConnectionState); if (newConnectionState < ConnectionState::Closed) { // Pane doesn't care if the connection isn't entering a terminal state. @@ -1066,7 +1067,6 @@ winrt::fire_and_forget Pane::_ControlConnectionStateChangedHandler(const winrt:: co_return; } - const auto previousConnectionState = std::exchange(_connectionState, newConnectionState); if (previousConnectionState < ConnectionState::Connected && newConnectionState >= ConnectionState::Failed) { // A failure to complete the connection (before it has _connected_) is not covered by "closeOnExit". From 145c4d36412ba07bbe1b7d8b49c031c337df28f3 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Thu, 5 Oct 2023 09:31:20 -0500 Subject: [PATCH 023/167] Bounds check some tab GetAt()s (#16016) `GetAt` can throw if the index is out of range. We don't check that in some places. This fixes some of those. I don't think this will take care of #15689, but it might help? (cherry picked from commit 5aadddaea9274ea14daaa58b8c65d45d3a785a8f) Service-Card-Id: 90731981 Service-Version: 1.19 --- .../Resources/en-US/Resources.resw | 6 ++++- src/cascadia/TerminalApp/TabManagement.cpp | 24 ++++++++++++------- src/cascadia/TerminalApp/TerminalPage.cpp | 7 ++---- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw index 7cebf63ea09..3ad4bdb73b8 100644 --- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw @@ -882,7 +882,11 @@ Active pane moved to "{0}" tab in "{1}" window - {Locked="{0}"}{Locked="{1}"}This text is read out by screen readers upon a successful pane movement. {0} is the name of the tab the pane was moved to. {1} is the name of the window the pane was moved to. + {Locked="{0}"}{Locked="{1}"}This text is read out by screen readers upon a successful pane movement. {0} is the name of the tab the pane was moved to. {1} is the name of the window the pane was moved to. Replaced in 1.19 by TerminalPage_PaneMovedAnnouncement_ExistingWindow2 + + + Active pane moved to "{0}" window + {Locked="{0}"}This text is read out by screen readers upon a successful pane movement. {0} is the name of the window the pane was moved to. Active pane moved to new window diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index bb77019aad5..1717c05b8b7 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -515,7 +515,7 @@ namespace winrt::TerminalApp::implementation // * B (tabIndex=1): We'll want to focus tab A (now in index 0) // * C (tabIndex=2): We'll want to focus tab B (now in index 1) // * D (tabIndex=3): We'll want to focus tab C (now in index 2) - const auto newSelectedIndex = std::clamp(tabIndex - 1, 0, _tabs.Size()); + const auto newSelectedIndex = std::clamp(tabIndex - 1, 0, _tabs.Size() - 1); // _UpdatedSelectedTab will do the work of setting up the new tab as // the focused one, and unfocusing all the others. auto newSelectedTab{ _tabs.GetAt(newSelectedIndex) }; @@ -682,7 +682,7 @@ namespace winrt::TerminalApp::implementation { uint32_t tabIndexFromControl{}; const auto items{ _tabView.TabItems() }; - if (items.IndexOf(tabViewItem, tabIndexFromControl)) + if (items.IndexOf(tabViewItem, tabIndexFromControl) && tabIndexFromControl < _tabs.Size()) { // If IndexOf returns true, we've actually got an index return _tabs.GetAt(tabIndexFromControl); @@ -1039,7 +1039,8 @@ namespace winrt::TerminalApp::implementation // - suggestedNewTabIndex: the new index of the tab, might get clamped to fit int the tabs row boundaries // Return Value: // - - void TerminalPage::_TryMoveTab(const uint32_t currentTabIndex, const int32_t suggestedNewTabIndex) + void TerminalPage::_TryMoveTab(const uint32_t currentTabIndex, + const int32_t suggestedNewTabIndex) { auto newTabIndex = gsl::narrow_cast(std::clamp(suggestedNewTabIndex, 0, _tabs.Size() - 1)); if (currentTabIndex != newTabIndex) @@ -1081,16 +1082,21 @@ namespace winrt::TerminalApp::implementation if (from.has_value() && to.has_value() && to != from) { - auto& tabs{ _tabs }; - auto tab = tabs.GetAt(from.value()); - tabs.RemoveAt(from.value()); - tabs.InsertAt(to.value(), tab); - _UpdateTabIndices(); + try + { + auto& tabs{ _tabs }; + auto tab = tabs.GetAt(from.value()); + tabs.RemoveAt(from.value()); + tabs.InsertAt(to.value(), tab); + _UpdateTabIndices(); + } + CATCH_LOG(); } _rearranging = false; - if (to.has_value()) + if (to.has_value() && + *to < gsl::narrow_cast(TabRow().TabView().TabItems().Size())) { // Selecting the dropped tab TabRow().TabView().SelectedIndex(to.value()); diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 6a4715e3ca7..d8673377ccf 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -2068,9 +2068,6 @@ namespace winrt::TerminalApp::implementation { if (const auto pane{ terminalTab->GetActivePane() }) { - // Get the tab title _before_ moving things around in case the tabIdx doesn't point to the right one after the move - const auto tabTitle = _tabs.GetAt(tabIdx).Title(); - auto startupActions = pane->BuildStartupActions(0, 1, true, true); _DetachPaneFromWindow(pane); _MoveContent(std::move(startupActions.args), windowId, tabIdx); @@ -2089,7 +2086,7 @@ namespace winrt::TerminalApp::implementation { autoPeer.RaiseNotificationEvent(Automation::Peers::AutomationNotificationKind::ActionCompleted, Automation::Peers::AutomationNotificationProcessing::ImportantMostRecent, - fmt::format(std::wstring_view{ RS_(L"TerminalPage_PaneMovedAnnouncement_ExistingWindow") }, tabTitle, windowId), + fmt::format(std::wstring_view{ RS_(L"TerminalPage_PaneMovedAnnouncement_ExistingWindow2") }, windowId), L"TerminalPageMovePaneToExistingWindow" /* unique name for this notification category */); } } @@ -2106,7 +2103,7 @@ namespace winrt::TerminalApp::implementation // Moving the pane from the current tab might close it, so get the next // tab before its index changes. - if (_tabs.Size() > tabIdx) + if (tabIdx < _tabs.Size()) { auto targetTab = _GetTerminalTabImpl(_tabs.GetAt(tabIdx)); // if the selected tab is not a host of terminals (e.g. settings) From 4b58fc6c8ef82088bb1f4968bc6991e675cc45c0 Mon Sep 17 00:00:00 2001 From: Dustin Howett Date: Wed, 25 Oct 2023 13:31:53 -0500 Subject: [PATCH 024/167] Migrate sources.dep changes from OS --- src/host/ft_host/sources.dep | 3 --- src/host/ft_integrity/sources.dep | 6 ------ src/tools/vtapp/sources.dep | 3 --- 3 files changed, 12 deletions(-) delete mode 100644 src/host/ft_integrity/sources.dep delete mode 100644 src/tools/vtapp/sources.dep diff --git a/src/host/ft_host/sources.dep b/src/host/ft_host/sources.dep index 11b0599b179..bc61c95c6b0 100644 --- a/src/host/ft_host/sources.dep +++ b/src/host/ft_host/sources.dep @@ -1,6 +1,3 @@ BUILD_PASS1_CONSUMES= \ onecore\windows\vcpkg|PASS1 \ -BUILD_PASS2_CONSUMES= \ - onecore\windows\core\console\open\src\tools\nihilist|PASS2 \ - diff --git a/src/host/ft_integrity/sources.dep b/src/host/ft_integrity/sources.dep deleted file mode 100644 index 83c5a0a70ef..00000000000 --- a/src/host/ft_integrity/sources.dep +++ /dev/null @@ -1,6 +0,0 @@ -PUBLIC_PASS1_CONSUMES= \ - onecore\base\appmodel\test\common\testhelper\winrt\private|PASS1 \ - -BUILD_PASS2_CONSUMES= \ - onecore\base\appmodel\test\common\testhelper\samples\nativecxapp\appx|PASS2 \ - diff --git a/src/tools/vtapp/sources.dep b/src/tools/vtapp/sources.dep deleted file mode 100644 index 5aef2e2c5c6..00000000000 --- a/src/tools/vtapp/sources.dep +++ /dev/null @@ -1,3 +0,0 @@ -PUBLIC_PASS0_CONSUMES= \ - onecore\redist\mspartners\netfx45\core\binary_release|PASS0 \ - From b6593e713e82816d22206f69237027c9639fb556 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Wed, 11 Oct 2023 12:58:55 -0500 Subject: [PATCH 025/167] build: pass branding into the nuget variable template (#16122) This fixes a cosmetic issue with the version number in the unpackaged builds and NuGet packages. They were showing up as `-preview`, even when they were stable, because the variable template didn't know about the branding. (cherry picked from commit 544cdd78afe17323f879ef3f6662eef87e6c7611) Service-Card-Id: 90786432 Service-Version: 1.19 --- build/pipelines/ob-nightly.yml | 2 ++ build/pipelines/ob-release.yml | 2 ++ build/pipelines/templates-v2/pipeline-full-release-build.yml | 2 ++ .../templates-v2/variables-nuget-package-version.yml | 4 ++++ 4 files changed, 10 insertions(+) diff --git a/build/pipelines/ob-nightly.yml b/build/pipelines/ob-nightly.yml index c8c62e278f0..b38010b084c 100644 --- a/build/pipelines/ob-nightly.yml +++ b/build/pipelines/ob-nightly.yml @@ -18,6 +18,8 @@ name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr) variables: - template: templates-v2/variables-nuget-package-version.yml + parameters: + branding: Canary - template: templates-v2/variables-onebranch-config.yml extends: diff --git a/build/pipelines/ob-release.yml b/build/pipelines/ob-release.yml index 9c81f570666..ca168e6d337 100644 --- a/build/pipelines/ob-release.yml +++ b/build/pipelines/ob-release.yml @@ -62,6 +62,8 @@ name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr) variables: - template: templates-v2/variables-nuget-package-version.yml + parameters: + branding: ${{ parameters.branding }} - template: templates-v2/variables-onebranch-config.yml extends: diff --git a/build/pipelines/templates-v2/pipeline-full-release-build.yml b/build/pipelines/templates-v2/pipeline-full-release-build.yml index 03826cac85d..06358de5c5b 100644 --- a/build/pipelines/templates-v2/pipeline-full-release-build.yml +++ b/build/pipelines/templates-v2/pipeline-full-release-build.yml @@ -62,6 +62,8 @@ parameters: variables: - template: variables-nuget-package-version.yml + parameters: + branding: ${{ parameters.branding }} resources: repositories: diff --git a/build/pipelines/templates-v2/variables-nuget-package-version.yml b/build/pipelines/templates-v2/variables-nuget-package-version.yml index af195c043f6..d3a137ba0e0 100644 --- a/build/pipelines/templates-v2/variables-nuget-package-version.yml +++ b/build/pipelines/templates-v2/variables-nuget-package-version.yml @@ -1,3 +1,7 @@ +parameters: + - name: branding + type: string + variables: # If we are building a branch called "release-*", change the NuGet suffix # to "preview". If we don't do that, XES will set the suffix to "release1" From 229ba89c83426274191d8e3dea2fa5c68b3e7a3a Mon Sep 17 00:00:00 2001 From: Jaswir Date: Fri, 13 Oct 2023 22:43:38 +0200 Subject: [PATCH 026/167] Allow Opacity to be set differently in both focused and unfocused terminals (#15974) ## Summary of the Pull Request Closes #11092 Allowing `opacity `to be set differently in both focused and unfocused terminals ## References and Relevant Issues #11092 , references: #7158 ## Detailed Description of the Pull Request / Additional comments ### Allowing Opacity to be set differently in both focused and unfocused terminals: ![unfocused_opacity](https://github.com/microsoft/terminal/assets/15957528/1c38e40b-4678-43ec-b328-ad79d222579f) ![image](https://github.com/microsoft/terminal/assets/15957528/3e3342a8-7908-41db-9c37-26c89f7f2456) ![jolsen](https://github.com/microsoft/terminal/assets/15957528/68553507-d29e-4513-89ce-b1cd305d28b7) ![image](https://github.com/microsoft/terminal/assets/15957528/18864f60-91d0-4159-87da-2b2ee1637a4c) ## `_runtimeFocusedOpacity` Mike also had to say something about this: https://github.com/microsoft/terminal/issues/2531#issuecomment-1668442774 Initially I had something like ` _setOpacity(newAppearance->Opacity());` But with the introduction of unfocused opacity we encounter new challenges: When Adjusting the Opacity with **CTRL+SHIFT+Mouse Scroll Wheel** or **Set background opacity** in command pallette, the Runtime opacity changes, but when we go to unfocused and back to focused the opacity changes back to focused opacity in Settings. Also when adjusting opacity through the command palette the window becomes unfocused and then focused again after setting background opacity hence the ` _setOpacity(newAppearance->Opacity());` would override the changes made through command palette ![runtimeFocusedOpacity](https://github.com/microsoft/terminal/assets/15957528/4de63057-d658-4b5e-99ad-7db050834ade) ![command_pallette_focusswitches](https://github.com/microsoft/terminal/assets/15957528/372526eb-cf0c-40f8-a4e5-a0739f1f0e05) With the introduction of unfocused opacity we encounter new challenges. The runtime opacity stores both the unfocused opacity and focused opacity from settings at different moments. This all works well until we combine this with Adjusting the Opacity with **CTRL+SHIFT+Mouse Scroll Wheel** or **Set background opacity** in command pallette. This brings the need for a separate Focused Opacity. When we change the runtime opacity with scroll wheel or through command pallette this value needs to be stored separately from the one in settings. So we can change back to it when going to unfocused mode and back to focused instead of the focused opacity defined in settings. ## `skipUnfocusedOpacity` solves Opacity going from solid to unfocused to focused bug: ![skipUnfocusedOpacity_bug](https://github.com/microsoft/terminal/assets/15957528/ecc06dcf-fbef-4fef-a40f-68278fdbfb12) ## Validation Steps Performed - Checked if unfocused Opacity works well when adjusting opacity through Mouse Scroll Wheel or Command Palette and in combination with Acrylic as mentioned in "Detailed Description of the Pull Request / Additional comments" ## PR Checklist - [x] Closes #11092 - [ ] Tests added/passed - [x] Documentation updated - If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here:(https://github.com/MicrosoftDocs/terminal/pull/714) - [ ] Schema updated (if necessary) (cherry picked from commit 27e1081c8cc417fade9e053b7b6738a6c382f7b9) Service-Card-Id: 90949918 Service-Version: 1.19 --- doc/cascadia/profiles.schema.json | 7 ++++ src/cascadia/TerminalControl/ControlCore.cpp | 38 +++++++++++++++----- src/cascadia/TerminalControl/ControlCore.h | 3 +- 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 9dc3a8afd58..f4b0d5e37c8 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -273,6 +273,13 @@ "useAcrylic":{ "description": "When set to true, the window will have an acrylic material background when unfocused. When set to false, the window will have a plain, untextured background when unfocused.", "type": "boolean" + }, + "opacity": { + "default": 100, + "description": "Sets the opacity of the window for the profile when unfocused. Accepts values from 0-100.", + "maximum": 100, + "minimum": 0, + "type": "number" } }, "type": "object" diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index a7609d29747..7925fc16eb8 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -680,7 +680,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation _setOpacity(Opacity() + adjustment); } - void ControlCore::_setOpacity(const double opacity) + // Method Description: + // - Updates the opacity of the terminal + // Arguments: + // - opacity: The new opacity to set. + // - focused (default == true): Whether the window is focused or unfocused. + // Return Value: + // - + void ControlCore::_setOpacity(const double opacity, bool focused) { const auto newOpacity = std::clamp(opacity, 0.0, @@ -694,6 +701,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation // Update our runtime opacity value _runtimeOpacity = newOpacity; + //Stores the focused runtime opacity separately from unfocused opacity + //to transition smoothly between the two. + _runtimeFocusedOpacity = focused ? newOpacity : _runtimeFocusedOpacity; + // Manually turn off acrylic if they turn off transparency. _runtimeUseAcrylic = newOpacity < 1.0 && _settings->UseAcrylic(); @@ -824,6 +835,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation _cellWidth = CSSLengthPercentage::FromString(_settings->CellWidth().c_str()); _cellHeight = CSSLengthPercentage::FromString(_settings->CellHeight().c_str()); _runtimeOpacity = std::nullopt; + _runtimeFocusedOpacity = std::nullopt; // Manually turn off acrylic if they turn off transparency. _runtimeUseAcrylic = _settings->Opacity() < 1.0 && _settings->UseAcrylic(); @@ -874,21 +886,29 @@ namespace winrt::Microsoft::Terminal::Control::implementation _renderEngine->SetRetroTerminalEffect(newAppearance->RetroTerminalEffect()); _renderEngine->SetPixelShaderPath(newAppearance->PixelShaderPath()); + // Incase EnableUnfocusedAcrylic is disabled and Focused Acrylic is set to true, + // the terminal should ignore the unfocused opacity from settings. + // The Focused Opacity from settings should be ignored if overridden at runtime. + bool useFocusedRuntimeOpacity = focused || (!_settings->EnableUnfocusedAcrylic() && UseAcrylic()); + double newOpacity = useFocusedRuntimeOpacity ? + FocusedOpacity() : + newAppearance->Opacity(); + _setOpacity(newOpacity, focused); + // No need to update Acrylic if UnfocusedAcrylic is disabled if (_settings->EnableUnfocusedAcrylic()) { // Manually turn off acrylic if they turn off transparency. _runtimeUseAcrylic = Opacity() < 1.0 && newAppearance->UseAcrylic(); + } - // Update the renderer as well. It might need to fall back from - // cleartype -> grayscale if the BG is transparent / acrylic. - _renderEngine->EnableTransparentBackground(_isBackgroundTransparent()); - _renderer->NotifyPaintFrame(); - - auto eventArgs = winrt::make_self(Opacity()); + // Update the renderer as well. It might need to fall back from + // cleartype -> grayscale if the BG is transparent / acrylic. + _renderEngine->EnableTransparentBackground(_isBackgroundTransparent()); + _renderer->NotifyPaintFrame(); - _TransparencyChangedHandlers(*this, *eventArgs); - } + auto eventArgs = winrt::make_self(Opacity()); + _TransparencyChangedHandlers(*this, *eventArgs); _renderer->TriggerRedrawAll(true, true); } diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index c21730f4789..90cc5fca4d4 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -246,6 +246,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation bool ShouldShowSelectOutput(); RUNTIME_SETTING(double, Opacity, _settings->Opacity()); + RUNTIME_SETTING(double, FocusedOpacity, FocusedAppearance().Opacity()); RUNTIME_SETTING(bool, UseAcrylic, _settings->UseAcrylic()); // -------------------------------- WinRT Events --------------------------------- @@ -386,7 +387,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation void _updateAntiAliasingMode(); void _connectionOutputHandler(const hstring& hstr); void _updateHoveredCell(const std::optional terminalPosition); - void _setOpacity(const double opacity); + void _setOpacity(const double opacity, const bool focused = true); bool _isBackgroundTransparent(); void _focusChanged(bool focused); From c0275018ea3cf7f51c72e884f2dd4d242210e37b Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 17 Oct 2023 14:11:54 -0500 Subject: [PATCH 027/167] Fix the color of marks (#16106) Guess what _doesn't_ have the same layout as a bitmap? A `til::color`. Noticed in 1.19. Regressed in #16006 (cherry picked from commit 174585740759f3e473c5a4efe3207a20d2c78511) Service-Card-Id: 90758500 Service-Version: 1.19 --- src/cascadia/TerminalControl/TermControl.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 8a1c9018809..4406d364c92 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -359,9 +359,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation const auto end = beg + pipHeight * stride; for (; beg < end; beg += stride) { - // Coincidentally a til::color has the same RGBA format as the bitmap. + // a til::color does NOT have the same RGBA format as the bitmap. #pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1). - std::fill_n(reinterpret_cast(beg), pipWidth, color); + const DWORD c = 0xff << 24 | color.r << 16 | color.g << 8 | color.b; + std::fill_n(reinterpret_cast(beg), pipWidth, c); } }; From 7fd476d9ed632b1d7325936ba46a17c0e81bd6cf Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 18 Oct 2023 17:46:16 -0700 Subject: [PATCH 028/167] env: properly handle nulls in REG_SZ strings (#16190) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit eb871bf fails to properly handle REG_SZ strings, which are documented as being null-terminated _and_ length restricted. `wcsnlen` is the perfect fit for handling this situation as it returns the position of the first \0, or the given length parameter. As a drive by improvement, this also drops some redundant code: * `to_environment_strings_w` which is the same as `to_string` * Retrieving `USERNAME`/`USERDOMAIN` via `LookupAccountSidW` and `COMPUTERNAME` via `GetComputerNameW` is not necessary as the variables are "volatile" and I believe there's generally no expectation that they change unless you log in again. Closes #16051 ## Validation Steps Performed * Run this in PowerShell to insert a env value with \0: ```pwsh $hklm = [Microsoft.Win32.RegistryKey]::OpenBaseKey( [Microsoft.Win32.RegistryHive]::LocalMachine, 0 ) $key = $hklm.OpenSubKey( 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', $true ) $key.SetValue('test', "foo`0bar") ``` * All `EnvTests` still pass ✅ * (Don't forget to remove the above value again!) (cherry picked from commit 64b5b2884a3ef4dbed5026c73079f4ffcdad6de5) Service-Card-Id: 90879164 Service-Version: 1.19 --- .../TerminalConnection/ConptyConnection.cpp | 20 +- src/inc/til/env.h | 229 ++---------------- src/til/ut_til/EnvTests.cpp | 33 ++- 3 files changed, 40 insertions(+), 242 deletions(-) diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index e086c9b2cfc..5b20f0d6ef1 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -82,14 +82,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation nullptr)); auto cmdline{ wil::ExpandEnvironmentStringsW(_commandline.c_str()) }; // mutable copy -- required for CreateProcessW - - til::env environment; - auto zeroEnvMap = wil::scope_exit([&]() noexcept { - environment.clear(); - }); - - // Populate the environment map with the current environment. - environment = _initialEnv; + auto environment = _initialEnv; { // Convert connection Guid to string and ignore the enclosing '{}'. @@ -140,15 +133,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation environment.as_map().insert_or_assign(L"WSLENV", wslEnv); } - std::vector newEnvVars; - auto zeroNewEnv = wil::scope_exit([&]() noexcept { - ::SecureZeroMemory(newEnvVars.data(), - newEnvVars.size() * sizeof(decltype(newEnvVars.begin())::value_type)); - }); - - RETURN_IF_FAILED(environment.to_environment_strings_w(newEnvVars)); - - auto lpEnvironment = newEnvVars.empty() ? nullptr : newEnvVars.data(); + auto newEnvVars = environment.to_string(); + const auto lpEnvironment = newEnvVars.empty() ? nullptr : newEnvVars.data(); // If we have a startingTitle, create a mutable character buffer to add // it to the STARTUPINFO. diff --git a/src/inc/til/env.h b/src/inc/til/env.h index 4d95f9f2f98..1546a83afb6 100644 --- a/src/inc/til/env.h +++ b/src/inc/til/env.h @@ -76,65 +76,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" namespace wil_env { - /** Looks up the computer name and fails if it is not found. */ - template - HRESULT GetComputerNameW(string_type& result) WI_NOEXCEPT - { - return wil::AdaptFixedSizeToAllocatedResult(result, [&](_Out_writes_(valueLength) PWSTR value, size_t valueLength, _Out_ size_t* valueLengthNeededWithNul) -> HRESULT { - // If the function succeeds, the return value is the number of characters stored in the buffer - // pointed to by lpBuffer, not including the terminating null character. - // - // If lpBuffer is not large enough to hold the data, the return value is the buffer size, in - // characters, required to hold the string and its terminating null character and the contents of - // lpBuffer are undefined. - // - // If the function fails, the return value is zero. If the specified environment variable was not - // found in the environment block, GetLastError returns ERROR_ENVVAR_NOT_FOUND. - - ::SetLastError(ERROR_SUCCESS); - - DWORD length = static_cast(valueLength); - - const auto result = ::GetComputerNameW(value, &length); - *valueLengthNeededWithNul = length; - RETURN_IF_WIN32_BOOL_FALSE_EXPECTED(result); - if (*valueLengthNeededWithNul < valueLength) - { - (*valueLengthNeededWithNul)++; // It fit, account for the null. - } - return S_OK; - }); - } - - /** Looks up the computer name and returns null if it is not found. */ - template - HRESULT TryGetComputerNameW(string_type& result) WI_NOEXCEPT - { - const auto hr = wil_env::GetComputerNameW(result); - RETURN_HR_IF(hr, FAILED(hr) && (hr != HRESULT_FROM_WIN32(ERROR_ENVVAR_NOT_FOUND))); - return S_OK; - } - -#ifdef WIL_ENABLE_EXCEPTIONS - /** Looks up the computer name and fails if it is not found. */ - template - string_type GetComputerNameW() - { - string_type result; - THROW_IF_FAILED((wil_env::GetComputerNameW(result))); - return result; - } - - /** Looks up the computer name and returns null if it is not found. */ - template - string_type TryGetComputerNameW() - { - string_type result; - THROW_IF_FAILED((wil_env::TryGetComputerNameW(result))); - return result; - } -#endif - /** Looks up a registry value from 'key' and fails if it is not found. */ template HRESULT RegQueryValueExW(HKEY key, PCWSTR valueName, string_type& result) WI_NOEXCEPT @@ -231,41 +172,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" } } - void get_computer_name() - { - if (auto value = til::details::wil_env::TryGetComputerNameW(); !value.empty()) - { - save_to_map(std::wstring{ til::details::vars::computer_name }, std::move(value)); - } - } - - void get_user_name_and_domain() - try - { - const auto token = wil::open_current_access_token(); - const auto user = wil::get_token_information(token.get()); - - DWORD accountNameSize = 0, userDomainSize = 0; - SID_NAME_USE sidNameUse; - SetLastError(ERROR_SUCCESS); - if (LookupAccountSidW(nullptr, user.get()->User.Sid, nullptr, &accountNameSize, nullptr, &userDomainSize, &sidNameUse) || GetLastError() == ERROR_INSUFFICIENT_BUFFER) - { - std::wstring accountName, userDomain; - accountName.resize(accountNameSize); - userDomain.resize(userDomainSize); - - SetLastError(ERROR_SUCCESS); - if (LookupAccountSidW(nullptr, user.get()->User.Sid, accountName.data(), &accountNameSize, userDomain.data(), &userDomainSize, &sidNameUse)) - { - strip_trailing_null(accountName); - strip_trailing_null(userDomain); - save_to_map(std::wstring{ til::details::vars::user_name }, std::move(accountName)); - save_to_map(std::wstring{ til::details::vars::user_domain }, std::move(userDomain)); - } - } - } - CATCH_LOG() - void get_program_files() { wil::unique_hkey key; @@ -325,23 +231,18 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" std::wstring data; if (pass == 0 && (type == REG_SZ) && valueDataSize >= sizeof(wchar_t)) { - data = { - reinterpret_cast(valueData.data()), valueData.size() / sizeof(wchar_t) - }; + const auto p = reinterpret_cast(valueData.data()); + const auto l = wcsnlen(p, valueData.size() / sizeof(wchar_t)); + data.assign(p, l); } else if (pass == 1 && (type == REG_EXPAND_SZ) && valueDataSize >= sizeof(wchar_t)) { - data = { - reinterpret_cast(valueData.data()), valueData.size() / sizeof(wchar_t) - }; + const auto p = reinterpret_cast(valueData.data()); + const auto l = wcsnlen(p, valueData.size() / sizeof(wchar_t)); + data.assign(p, l); data = expand_environment_strings(data.data()); } - // Because Registry data may or may not be null terminated... check if we've managed - // to store an extra null in the wstring by telling it to create itself from pointer and size. - // If we did, pull it off. - strip_trailing_null(data); - if (!data.empty()) { if (isPathVar) @@ -468,14 +369,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" til::compare_string_ordinal(input, os2LibPath) == CSTR_EQUAL; } - static void strip_trailing_null(std::wstring& str) noexcept - { - if (!str.empty() && str.back() == L'\0') - { - str.pop_back(); - } - } - void parse(const wchar_t* lastCh) { for (; *lastCh != '\0'; ++lastCh) @@ -537,13 +430,20 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" void regenerate() { // Generally replicates the behavior of shell32!RegenerateUserEnvironment + // The difference is that we don't + // * handle autoexec.bat (intentionally unsupported). + // * call LookupAccountSidW to get the USERNAME/USERDOMAIN variables. + // Windows sets these as values of the "Volatile Environment" key at each login. + // We don't expect our process to impersonate another user so we can get them from the PEB. + // * call GetComputerNameW to get the COMPUTERNAME variable, for the same reason. get(til::details::vars::system_root); get(til::details::vars::system_drive); get(til::details::vars::all_users_profile); get(til::details::vars::public_var); get(til::details::vars::program_data); - get_computer_name(); - get_user_name_and_domain(); + get(til::details::vars::computer_name); + get(til::details::vars::user_name); + get(til::details::vars::user_domain); get(til::details::vars::user_dns_domain); get(til::details::vars::home_drive); get(til::details::vars::home_share); @@ -562,104 +462,17 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" std::wstring to_string() const { std::wstring result; - for (const auto& [name, value] : _envMap) + for (const auto& [k, v] : _envMap) { - result += name; - result += L"="; - result += value; - result.append(L"\0", 1); // Override string's natural propensity to stop at \0 + result.append(k); + result.push_back(L'='); + result.append(v); + result.push_back(L'\0'); } - result.append(L"\0", 1); + result.push_back(L'\0'); return result; } - void clear() noexcept - { - // Can't zero the keys, but at least we can zero the values. - for (auto& [name, value] : _envMap) - { - ::SecureZeroMemory(value.data(), value.size() * sizeof(decltype(value.begin())::value_type)); - } - - _envMap.clear(); - } - - // Function Description: - // - Creates a new environment block using the provided vector as appropriate - // (resizing if needed) based on the current environment variable map - // matching the format of GetEnvironmentStringsW. - // Arguments: - // - newEnvVars: The vector that will be used to create the new environment block. - // Return Value: - // - S_OK if we succeeded, or an appropriate HRESULT for failing - HRESULT to_environment_strings_w(std::vector& newEnvVars) - try - { - // Clear environment block before use. - constexpr auto cbChar{ sizeof(decltype(newEnvVars.begin())::value_type) }; - - if (!newEnvVars.empty()) - { - ::SecureZeroMemory(newEnvVars.data(), newEnvVars.size() * cbChar); - } - - // Resize environment block to fit map. - size_t cchEnv{ 2 }; // For the block's double NULL-terminator. - for (const auto& [name, value] : _envMap) - { - // Final form of "name=value\0". - cchEnv += name.size() + 1 + value.size() + 1; - } - newEnvVars.resize(cchEnv); - - // Ensure new block is wiped if we exit due to failure. - auto zeroNewEnv = wil::scope_exit([&]() noexcept { - ::SecureZeroMemory(newEnvVars.data(), newEnvVars.size() * cbChar); - }); - - // Transform each map entry and copy it into the new environment block. - auto pEnvVars{ newEnvVars.data() }; - auto cbRemaining{ cchEnv * cbChar }; - for (const auto& [name, value] : _envMap) - { - // Final form of "name=value\0" for every entry. - { - const auto cchSrc{ name.size() }; - const auto cbSrc{ cchSrc * cbChar }; - RETURN_HR_IF(E_OUTOFMEMORY, memcpy_s(pEnvVars, cbRemaining, name.c_str(), cbSrc) != 0); - pEnvVars += cchSrc; - cbRemaining -= cbSrc; - } - - RETURN_HR_IF(E_OUTOFMEMORY, memcpy_s(pEnvVars, cbRemaining, L"=", cbChar) != 0); - ++pEnvVars; - cbRemaining -= cbChar; - - { - const auto cchSrc{ value.size() }; - const auto cbSrc{ cchSrc * cbChar }; - RETURN_HR_IF(E_OUTOFMEMORY, memcpy_s(pEnvVars, cbRemaining, value.c_str(), cbSrc) != 0); - pEnvVars += cchSrc; - cbRemaining -= cbSrc; - } - - RETURN_HR_IF(E_OUTOFMEMORY, memcpy_s(pEnvVars, cbRemaining, L"\0", cbChar) != 0); - ++pEnvVars; - cbRemaining -= cbChar; - } - - // Environment block only has to be NULL-terminated, but double NULL-terminate anyway. - RETURN_HR_IF(E_OUTOFMEMORY, memcpy_s(pEnvVars, cbRemaining, L"\0\0", cbChar * 2) != 0); - cbRemaining -= cbChar * 2; - - RETURN_HR_IF(E_UNEXPECTED, cbRemaining != 0); - - zeroNewEnv.release(); // success; don't wipe new environment block on exit - - return S_OK; - } - CATCH_RETURN(); - auto& as_map() noexcept { return _envMap; diff --git a/src/til/ut_til/EnvTests.cpp b/src/til/ut_til/EnvTests.cpp index 1a9fd53c603..f0d2e97cd95 100644 --- a/src/til/ut_til/EnvTests.cpp +++ b/src/til/ut_til/EnvTests.cpp @@ -9,7 +9,7 @@ using namespace WEX::Common; using namespace WEX::Logging; using namespace WEX::TestExecution; -BOOL APIENTRY RegenerateUserEnvironment(void** pNewEnv, BOOL bSetCurrentEnv); +BOOL APIENTRY RegenerateUserEnvironment(wchar_t** pNewEnv, BOOL bSetCurrentEnv); class EnvTests { @@ -26,9 +26,11 @@ class EnvTests wil::unique_hmodule shell32{ LoadLibraryW(L"shell32.dll") }; auto func = GetProcAddressByFunctionDeclaration(shell32.get(), RegenerateUserEnvironment); + wchar_t* newEnvPtr = nullptr; + VERIFY_WIN32_BOOL_SUCCEEDED(func(&newEnvPtr, FALSE)); + // Use a WIL pointer to free it on scope exit. - wil::unique_environstrings_ptr newEnv; - VERIFY_WIN32_BOOL_SUCCEEDED(func((void**)&newEnv, FALSE)); + const wil::unique_environstrings_ptr newEnv{ newEnvPtr }; // Parse the string into our environment table. til::env expected{ newEnv.get() }; @@ -38,12 +40,15 @@ class EnvTests actual.regenerate(); VERIFY_ARE_EQUAL(expected.as_map().size(), actual.as_map().size()); - for (const auto& [expectedKey, expectedValue] : expected.as_map()) + + const auto expectedEnd = expected.as_map().end(); + auto expectedIt = expected.as_map().begin(); + auto actualIt = actual.as_map().begin(); + + for (; expectedIt != expectedEnd; ++expectedIt, ++actualIt) { - Log::Comment(String().Format(L"Environment Variable: '%s'", expectedKey.data())); - const auto actualValue = actual.as_map()[expectedKey]; - VERIFY_IS_TRUE(actual.as_map().find(expectedKey) != actual.as_map().end()); - VERIFY_ARE_EQUAL(expectedValue, actual.as_map()[expectedKey]); + VERIFY_ARE_EQUAL(expectedIt->first, actualIt->first); + VERIFY_ARE_EQUAL(expectedIt->second, actualIt->second); } } @@ -54,16 +59,10 @@ class EnvTests environment.as_map().insert_or_assign(L"B", L"Banana"); environment.as_map().insert_or_assign(L"C", L"Cassowary"); - wchar_t expectedArray[] = L"A=Apple\0B=Banana\0C=Cassowary\0"; - - const std::wstring expected{ expectedArray, ARRAYSIZE(expectedArray) }; + static constexpr wchar_t expectedArray[] = L"A=Apple\0B=Banana\0C=Cassowary\0"; + static constexpr std::wstring_view expected{ expectedArray, std::size(expectedArray) }; const auto& actual = environment.to_string(); - - VERIFY_ARE_EQUAL(expected.size(), actual.size()); - for (size_t i = 0; i < expected.size(); ++i) - { - VERIFY_ARE_EQUAL(expected[i], actual[i]); - } + VERIFY_ARE_EQUAL(expected, actual); } TEST_METHOD(TestExpandEnvironmentStrings) From 9892ade4e04fe04ef2b284f5553cbfcbd9d5859e Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 18 Oct 2023 17:47:19 -0700 Subject: [PATCH 029/167] COOKED_READ: Fix reference counting woes (#16187) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This restores the original code from before 821ae3a where the `.GetMainBuffer()` call was accidentally removed. Closes #16158 ## Validation Steps Performed * Run this Python script: ```py import sys while True: sys.stdout.write("\033[?1049h") sys.stdout.flush() sys.stdin.readline() sys.stdout.write("\033[?1049l") ``` * Press enter repeatedly * Doesn't crash ✅ (cherry picked from commit 08f30330d15e22adf23ee04164fc2474f290348a) Service-Card-Id: 90861143 Service-Version: 1.19 --- .github/actions/spelling/expect/expect.txt | 1 + src/host/ft_host/API_BufferTests.cpp | 37 ++++++++++++++++++++++ src/host/readDataCooked.cpp | 6 +++- 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 7505061fab2..7beb42fe6c9 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -301,6 +301,7 @@ coordnew COPYCOLOR CORESYSTEM cotaskmem +countof CPG cpinfo CPINFOEX diff --git a/src/host/ft_host/API_BufferTests.cpp b/src/host/ft_host/API_BufferTests.cpp index 362b8353aef..242427e2b9a 100644 --- a/src/host/ft_host/API_BufferTests.cpp +++ b/src/host/ft_host/API_BufferTests.cpp @@ -3,6 +3,8 @@ #include "precomp.h" +#include "../../types/inc/IInputEvent.hpp" + extern "C" IMAGE_DOS_HEADER __ImageBase; using namespace WEX::Logging; @@ -17,6 +19,7 @@ class BufferTests END_TEST_CLASS() TEST_METHOD(TestSetConsoleActiveScreenBufferInvalid); + TEST_METHOD(TestCookedReadBufferReferenceCount); TEST_METHOD(TestCookedReadOnNonShareableScreenBuffer); @@ -83,6 +86,40 @@ void BufferTests::TestCookedReadOnNonShareableScreenBuffer() VERIFY_IS_TRUE(IsConsoleStillRunning()); } +// This test ensures that COOKED_READ_DATA properly holds onto the screen buffer it is +// reading from for the whole duration of the read. It's important that we hold a handle +// to the main instead of the alt buffer even if this cooked read targets the latter, +// because alt buffers are fake SCREEN_INFORMATION objects that are owned by the main buffer. +void BufferTests::TestCookedReadBufferReferenceCount() +{ + static constexpr int loops = 5; + + const auto in = GetStdInputHandle(); + const auto out = GetStdOutputHandle(); + + DWORD inMode = 0; + GetConsoleMode(out, &inMode); + inMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_PROCESSED_OUTPUT; + SetConsoleMode(out, inMode); + + INPUT_RECORD newlines[loops]; + DWORD written = 0; + std::fill_n(&newlines[0], loops, SynthesizeKeyEvent(true, 1, L'\r', 0, L'\r', 0)); + WriteConsoleInputW(in, &newlines[0], loops, &written); + + for (int i = 0; i < loops; ++i) + { + VERIFY_SUCCEEDED(WriteConsoleW(out, L"\033[?1049h", 8, nullptr, nullptr)); + + wchar_t buffer[16]; + DWORD read = 0; + VERIFY_SUCCEEDED(ReadConsoleW(in, &buffer[0], _countof(buffer), &read, nullptr)); + VERIFY_ARE_EQUAL(2u, read); + + VERIFY_SUCCEEDED(WriteConsoleW(out, L"\033[?1049l", 8, nullptr, nullptr)); + } +} + void BufferTests::TestWritingInactiveScreenBuffer() { bool useVtOutput; diff --git a/src/host/readDataCooked.cpp b/src/host/readDataCooked.cpp index d05502dc0d0..36d54583fbb 100644 --- a/src/host/readDataCooked.cpp +++ b/src/host/readDataCooked.cpp @@ -62,7 +62,11 @@ COOKED_READ_DATA::COOKED_READ_DATA(_In_ InputBuffer* const pInputBuffer, // We need to ensure that it stays alive for the duration of the read. // Coincidentally this serves another important purpose: It checks whether we're allowed to read from // the given buffer in the first place. If it's missing the FILE_SHARE_READ flag, we can't read from it. - THROW_IF_FAILED(_screenInfo.AllocateIoHandle(ConsoleHandleData::HandleType::Output, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, _tempHandle)); + // + // GH#16158: It's important that we hold a handle to the main instead of the alt buffer + // even if this cooked read targets the latter, because alt buffers are fake + // SCREEN_INFORMATION objects that are owned by the main buffer. + THROW_IF_FAILED(_screenInfo.GetMainBuffer().AllocateIoHandle(ConsoleHandleData::HandleType::Output, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, _tempHandle)); #endif if (!initialData.empty()) From 798ab586f1c83589f679a66900d9a2889a348c58 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 20 Oct 2023 18:10:31 +0200 Subject: [PATCH 030/167] Fix rectangular clipboard copying initiated from the app menu (#16197) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cd6b083 had 2 issues: * Improper testing with Ctrl+M instead of Edit > Mark. * Wrong SelectionState function being used. When the selection is initiated without keyboard or mouse, `IsKeyboardMarkSelection` returns false. The proper function to use is `IsLineSelection`. Closes #15153 ## Validation Steps Performed * Run Far * Start selection via Edit>Mark * Hold Alt while dragging to make a rectangular selection * Right click * Clipboard contains a rectangular copy ✅ (cherry picked from commit d496a5fb80d5ec002077d6be27a35ec7494cb520) Service-Card-Id: 90886368 Service-Version: 1.19 --- src/interactivity/win32/Clipboard.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interactivity/win32/Clipboard.cpp b/src/interactivity/win32/Clipboard.cpp index 30138f6dad6..be97047404c 100644 --- a/src/interactivity/win32/Clipboard.cpp +++ b/src/interactivity/win32/Clipboard.cpp @@ -248,7 +248,7 @@ void Clipboard::StoreSelectionToClipboard(const bool copyFormatting) trimTrailingWhitespace, selectionRects, GetAttributeColors, - selection.IsKeyboardMarkSelection()); + !selection.IsLineSelection()); CopyTextToSystemClipboard(text, copyFormatting); } From c2fde69ff59cd8fd450a745603ce107ac85b1e76 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 23 Oct 2023 17:27:01 -0700 Subject: [PATCH 031/167] Fix UIA and marks regressions due to cooked read (#16105) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The initial cooked read (= conhost readline) rewrite had two flaws: * Using viewport scrolls under ConPTY to avoid emitting newlines resulted in various bugs around marks, coloring, etc. It's still somewhat unclear why this happened, but the next issue is related and much worse. * Rewriting the input line every time causes problems with accessibility tools, as they'll re-announce unchanged parts again and again. The solution to these is to simply stop writing the unchanged parts of the prompt. To do this, code was added to measure the size of text without actually inserting them into the buffer. Since this meant that the "interactive" mode of `WriteCharsLegacy` would need to be duplicated for the new code, I instead moved those parts into `COOKED_READ_DATA`. That way we can now have the interactive transform of the prompt (= Ctrl+C -> ^C) and the two text functions (measure text & actually write text) are now agnostic to this transformation. Closes #16034 Closes #16044 ## Validation Steps Performed * A vision impaired user checked it out and it seemed fine ✅ (cherry picked from commit e1c69a99cef98f293a17100752f67ab7e6584868) Service-Card-Id: 90891693 Service-Version: 1.19 --- .github/actions/spelling/expect/expect.txt | 1 + src/buffer/out/textBuffer.cpp | 82 +++ src/buffer/out/textBuffer.hpp | 1 + .../ConptyRoundtripTests.cpp | 4 +- src/common.build.pre.props | 2 +- src/host/_stream.cpp | 112 ++--- src/host/_stream.h | 2 +- src/host/ft_fuzzer/fuzzmain.cpp | 2 +- src/host/readDataCooked.cpp | 470 ++++++++++++------ src/host/readDataCooked.hpp | 46 +- src/host/ut_host/ScreenBufferTests.cpp | 10 +- 11 files changed, 492 insertions(+), 240 deletions(-) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 7beb42fe6c9..acd73a56a9d 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -1774,6 +1774,7 @@ stdafx STDAPI stdc stdcpp +STDEXT STDMETHODCALLTYPE STDMETHODIMP STGM diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 26c8152ea8e..aa61b63cc29 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -416,6 +416,88 @@ size_t TextBuffer::GraphemePrev(const std::wstring_view& chars, size_t position) return til::utf16_iterate_prev(chars, position); } +// Ever wondered how much space a piece of text needs before inserting it? This function will tell you! +// It fundamentally behaves identical to the various ROW functions around `RowWriteState`. +// +// Set `columnLimit` to the amount of space that's available (e.g. `buffer_width - cursor_position.x`) +// and it'll return the amount of characters that fit into this space. The out parameter `columns` +// will contain the amount of columns this piece of text has actually used. +// +// Just like with `RowWriteState` one special case is when not all text fits into the given space: +// In that case, this function always returns exactly `columnLimit`. This distinction is important when "inserting" +// a wide glyph but there's only 1 column left. That 1 remaining column is supposed to be padded with whitespace. +size_t TextBuffer::FitTextIntoColumns(const std::wstring_view& chars, til::CoordType columnLimit, til::CoordType& columns) noexcept +{ + columnLimit = std::max(0, columnLimit); + + const auto beg = chars.begin(); + const auto end = chars.end(); + auto it = beg; + const auto asciiEnd = beg + std::min(chars.size(), gsl::narrow_cast(columnLimit)); + + // ASCII fast-path: 1 char always corresponds to 1 column. + for (; it != asciiEnd && *it < 0x80; ++it) + { + } + + const auto dist = gsl::narrow_cast(it - beg); + auto col = gsl::narrow_cast(dist); + + if (it == asciiEnd) [[likely]] + { + columns = col; + return dist; + } + + // Unicode slow-path where we need to count text and columns separately. + for (;;) + { + auto ptr = &*it; + const auto wch = *ptr; + size_t len = 1; + + col++; + + // Even in our slow-path we can avoid calling IsGlyphFullWidth if the current character is ASCII. + // It also allows us to skip the surrogate pair decoding at the same time. + if (wch >= 0x80) + { + if (til::is_surrogate(wch)) + { + const auto it2 = it + 1; + if (til::is_leading_surrogate(wch) && it2 != end && til::is_trailing_surrogate(*it2)) + { + len = 2; + } + else + { + ptr = &UNICODE_REPLACEMENT; + } + } + + col += IsGlyphFullWidth({ ptr, len }); + } + + // If we ran out of columns, we need to always return `columnLimit` and not `cols`, + // because if we tried inserting a wide glyph into just 1 remaining column it will + // fail to fit, but that remaining column still has been used up. When the caller sees + // `columns == columnLimit` they will line-wrap and continue inserting into the next row. + if (col > columnLimit) + { + columns = columnLimit; + return gsl::narrow_cast(it - beg); + } + + // But if we simply ran out of text we just need to return the actual number of columns. + it += len; + if (it == end) + { + columns = col; + return chars.size(); + } + } +} + // Pretend as if `position` is a regular cursor in the TextBuffer. // This function will then pretend as if you pressed the left/right arrow // keys `distance` amount of times (negative = left, positive = right). diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp index a9d2e1714b6..8ba16f97e75 100644 --- a/src/buffer/out/textBuffer.hpp +++ b/src/buffer/out/textBuffer.hpp @@ -137,6 +137,7 @@ class TextBuffer final static size_t GraphemeNext(const std::wstring_view& chars, size_t position) noexcept; static size_t GraphemePrev(const std::wstring_view& chars, size_t position) noexcept; + static size_t FitTextIntoColumns(const std::wstring_view& chars, til::CoordType columnLimit, til::CoordType& columns) noexcept; til::point NavigateCursor(til::point position, til::CoordType distance) const; diff --git a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp index 99c2bd02ec9..48eeacf53c6 100644 --- a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp @@ -3243,7 +3243,7 @@ void ConptyRoundtripTests::WrapNewLineAtBottom() } else if (writingMethod == PrintWithWriteCharsLegacy) { - WriteCharsLegacy(si, str, false, nullptr); + WriteCharsLegacy(si, str, nullptr); } }; @@ -3401,7 +3401,7 @@ void ConptyRoundtripTests::WrapNewLineAtBottomLikeMSYS() } else if (writingMethod == PrintWithWriteCharsLegacy) { - WriteCharsLegacy(si, str, false, nullptr); + WriteCharsLegacy(si, str, nullptr); } }; diff --git a/src/common.build.pre.props b/src/common.build.pre.props index 9404c24c4bd..a1cf37adbda 100644 --- a/src/common.build.pre.props +++ b/src/common.build.pre.props @@ -133,7 +133,7 @@ We didn't and it breaks WRL and large parts of conhost code. --> 4201;4312;4467;5105;26434;26445;26456;%(DisableSpecificWarnings) - _WINDOWS;EXTERNAL_BUILD;%(PreprocessorDefinitions) + _WINDOWS;EXTERNAL_BUILD;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;%(PreprocessorDefinitions) true precomp.h ProgramDatabase diff --git a/src/host/_stream.cpp b/src/host/_stream.cpp index bf9e32ed38b..8d884e7136e 100644 --- a/src/host/_stream.cpp +++ b/src/host/_stream.cpp @@ -39,7 +39,7 @@ using Microsoft::Console::VirtualTerminal::StateMachine; // - coordCursor - New location of cursor. // - fKeepCursorVisible - TRUE if changing window origin desirable when hit right edge // Return Value: -static void AdjustCursorPosition(SCREEN_INFORMATION& screenInfo, _In_ til::point coordCursor, const bool interactive, _Inout_opt_ til::CoordType* psScrollY) +static void AdjustCursorPosition(SCREEN_INFORMATION& screenInfo, _In_ til::point coordCursor, _Inout_opt_ til::CoordType* psScrollY) { const auto bufferSize = screenInfo.GetBufferSize().Dimensions(); if (coordCursor.x < 0) @@ -70,42 +70,19 @@ static void AdjustCursorPosition(SCREEN_INFORMATION& screenInfo, _In_ til::point if (coordCursor.y >= bufferSize.height) { - const auto vtIo = ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo(); - const auto renderer = ServiceLocator::LocateGlobals().pRender; - const auto needsConPTYWorkaround = interactive && vtIo->IsUsingVt(); auto& buffer = screenInfo.GetTextBuffer(); - const auto isActiveBuffer = buffer.IsActiveBuffer(); + buffer.IncrementCircularBuffer(buffer.GetCurrentAttributes()); - // ConPTY translates scrolling into newlines. We don't want that during cooked reads (= "cmd.exe prompts") - // because the entire prompt is supposed to fit into the VT viewport, with no scrollback. If we didn't do that, - // any prompt larger than the viewport will cause >1 lines to be added to scrollback, even if typing backspaces. - // You can test this by removing this branch, launch Windows Terminal, and fill the entire viewport with text in cmd.exe. - if (needsConPTYWorkaround) + if (buffer.IsActiveBuffer()) { - buffer.SetAsActiveBuffer(false); - buffer.IncrementCircularBuffer(buffer.GetCurrentAttributes()); - buffer.SetAsActiveBuffer(isActiveBuffer); - - if (isActiveBuffer && renderer) + if (const auto notifier = ServiceLocator::LocateAccessibilityNotifier()) { - renderer->TriggerRedrawAll(); + notifier->NotifyConsoleUpdateScrollEvent(0, -1); } - } - else - { - buffer.IncrementCircularBuffer(buffer.GetCurrentAttributes()); - - if (isActiveBuffer) + if (const auto renderer = ServiceLocator::LocateGlobals().pRender) { - if (const auto notifier = ServiceLocator::LocateAccessibilityNotifier()) - { - notifier->NotifyConsoleUpdateScrollEvent(0, -1); - } - if (renderer) - { - static constexpr til::point delta{ 0, -1 }; - renderer->TriggerScroll(&delta); - } + static constexpr til::point delta{ 0, -1 }; + renderer->TriggerScroll(&delta); } } @@ -128,15 +105,11 @@ static void AdjustCursorPosition(SCREEN_INFORMATION& screenInfo, _In_ til::point LOG_IF_FAILED(screenInfo.SetViewportOrigin(false, WindowOrigin, true)); } - if (interactive) - { - screenInfo.MakeCursorVisible(coordCursor); - } - LOG_IF_FAILED(screenInfo.SetCursorPosition(coordCursor, interactive)); + LOG_IF_FAILED(screenInfo.SetCursorPosition(coordCursor, false)); } // As the name implies, this writes text without processing its control characters. -static void _writeCharsLegacyUnprocessed(SCREEN_INFORMATION& screenInfo, const std::wstring_view& text, const bool interactive, til::CoordType* psScrollY) +void _writeCharsLegacyUnprocessed(SCREEN_INFORMATION& screenInfo, const std::wstring_view& text, til::CoordType* psScrollY) { const auto wrapAtEOL = WI_IsFlagSet(screenInfo.OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT); const auto hasAccessibilityEventing = screenInfo.HasAccessibilityEventing(); @@ -165,18 +138,19 @@ static void _writeCharsLegacyUnprocessed(SCREEN_INFORMATION& screenInfo, const s screenInfo.NotifyAccessibilityEventing(state.columnBegin, cursorPosition.y, state.columnEnd - 1, cursorPosition.y); } - AdjustCursorPosition(screenInfo, cursorPosition, interactive, psScrollY); + AdjustCursorPosition(screenInfo, cursorPosition, psScrollY); } } // This routine writes a string to the screen while handling control characters. // `interactive` exists for COOKED_READ_DATA which uses it to transform control characters into visible text like "^X". // Similarly, `psScrollY` is also used by it to track whether the underlying buffer circled. It requires this information to know where the input line moved to. -void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& text, const bool interactive, til::CoordType* psScrollY) +void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& text, til::CoordType* psScrollY) { static constexpr wchar_t tabSpaces[8]{ L' ', L' ', L' ', L' ', L' ', L' ', L' ', L' ' }; auto& textBuffer = screenInfo.GetTextBuffer(); + const auto width = textBuffer.GetSize().Width(); auto& cursor = textBuffer.GetCursor(); const auto wrapAtEOL = WI_IsFlagSet(screenInfo.OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT); auto it = text.begin(); @@ -197,15 +171,15 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t { pos.x = 0; pos.y++; - AdjustCursorPosition(screenInfo, pos, interactive, psScrollY); + AdjustCursorPosition(screenInfo, pos, psScrollY); } } // If ENABLE_PROCESSED_OUTPUT is set we search for C0 control characters and handle them like backspace, tab, etc. - // If it's not set, we can just straight up give everything to _writeCharsLegacyUnprocessed. + // If it's not set, we can just straight up give everything to WriteCharsLegacyUnprocessed. if (WI_IsFlagClear(screenInfo.OutputMode, ENABLE_PROCESSED_OUTPUT)) { - _writeCharsLegacyUnprocessed(screenInfo, { it, end }, interactive, psScrollY); + _writeCharsLegacyUnprocessed(screenInfo, { it, end }, psScrollY); it = end; } @@ -214,7 +188,7 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t const auto nextControlChar = std::find_if(it, end, [](const auto& wch) { return !IS_GLYPH_CHAR(wch); }); if (nextControlChar != it) { - _writeCharsLegacyUnprocessed(screenInfo, { it, nextControlChar }, interactive, psScrollY); + _writeCharsLegacyUnprocessed(screenInfo, { it, nextControlChar }, psScrollY); it = nextControlChar; } @@ -223,35 +197,24 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t switch (*it) { case UNICODE_NULL: - if (interactive) - { - break; - } - _writeCharsLegacyUnprocessed(screenInfo, { &tabSpaces[0], 1 }, interactive, psScrollY); + _writeCharsLegacyUnprocessed(screenInfo, { &tabSpaces[0], 1 }, psScrollY); continue; case UNICODE_BELL: - if (interactive) - { - break; - } std::ignore = screenInfo.SendNotifyBeep(); continue; case UNICODE_BACKSPACE: { - // Backspace handling for interactive mode should happen in COOKED_READ_DATA - // where it has full control over the text and can delete it directly. - // Otherwise handling backspacing tabs/whitespace can turn up complex and bug-prone. - assert(!interactive); auto pos = cursor.GetPosition(); pos.x = textBuffer.GetRowByOffset(pos.y).NavigateToPrevious(pos.x); - AdjustCursorPosition(screenInfo, pos, interactive, psScrollY); + AdjustCursorPosition(screenInfo, pos, psScrollY); continue; } case UNICODE_TAB: { const auto pos = cursor.GetPosition(); - const auto tabCount = gsl::narrow_cast(8 - (pos.x & 7)); - _writeCharsLegacyUnprocessed(screenInfo, { &tabSpaces[0], tabCount }, interactive, psScrollY); + const auto remaining = width - pos.x; + const auto tabCount = gsl::narrow_cast(std::min(remaining, 8 - (pos.x & 7))); + _writeCharsLegacyUnprocessed(screenInfo, { &tabSpaces[0], tabCount }, psScrollY); continue; } case UNICODE_LINEFEED: @@ -264,38 +227,29 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t textBuffer.GetMutableRowByOffset(pos.y).SetWrapForced(false); pos.y = pos.y + 1; - AdjustCursorPosition(screenInfo, pos, interactive, psScrollY); + AdjustCursorPosition(screenInfo, pos, psScrollY); continue; } case UNICODE_CARRIAGERETURN: { auto pos = cursor.GetPosition(); pos.x = 0; - AdjustCursorPosition(screenInfo, pos, interactive, psScrollY); + AdjustCursorPosition(screenInfo, pos, psScrollY); continue; } default: break; } - // In the interactive mode we replace C0 control characters (0x00-0x1f) with ASCII representations like ^C (= 0x03). - if (interactive && *it < L' ') + // As a special favor to incompetent apps that attempt to display control chars, + // convert to corresponding OEM Glyph Chars + const auto cp = ServiceLocator::LocateGlobals().getConsoleInformation().OutputCP; + const auto ch = gsl::narrow_cast(*it); + wchar_t wch = 0; + const auto result = MultiByteToWideChar(cp, MB_USEGLYPHCHARS, &ch, 1, &wch, 1); + if (result == 1) { - const wchar_t wchs[2]{ L'^', static_cast(*it + L'@') }; - _writeCharsLegacyUnprocessed(screenInfo, { &wchs[0], 2 }, interactive, psScrollY); - } - else - { - // As a special favor to incompetent apps that attempt to display control chars, - // convert to corresponding OEM Glyph Chars - const auto cp = ServiceLocator::LocateGlobals().getConsoleInformation().OutputCP; - const auto ch = gsl::narrow_cast(*it); - wchar_t wch = 0; - const auto result = MultiByteToWideChar(cp, MB_USEGLYPHCHARS, &ch, 1, &wch, 1); - if (result == 1) - { - _writeCharsLegacyUnprocessed(screenInfo, { &wch, 1 }, interactive, psScrollY); - } + _writeCharsLegacyUnprocessed(screenInfo, { &wch, 1 }, psScrollY); } } } @@ -358,7 +312,7 @@ try if (WI_IsAnyFlagClear(screenInfo.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_PROCESSED_OUTPUT)) { - WriteCharsLegacy(screenInfo, str, false, nullptr); + WriteCharsLegacy(screenInfo, str, nullptr); } else { diff --git a/src/host/_stream.h b/src/host/_stream.h index 794591a03f0..79eb84585dc 100644 --- a/src/host/_stream.h +++ b/src/host/_stream.h @@ -19,7 +19,7 @@ Revision History: #include "writeData.hpp" -void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str, bool interactive, til::CoordType* psScrollY); +void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str, til::CoordType* psScrollY); // NOTE: console lock must be held when calling this routine // String has been translated to unicode at this point. diff --git a/src/host/ft_fuzzer/fuzzmain.cpp b/src/host/ft_fuzzer/fuzzmain.cpp index cd3708d59ed..59ee6ce05c8 100644 --- a/src/host/ft_fuzzer/fuzzmain.cpp +++ b/src/host/ft_fuzzer/fuzzmain.cpp @@ -132,6 +132,6 @@ extern "C" __declspec(dllexport) int LLVMFuzzerTestOneInput(const uint8_t* data, til::CoordType scrollY{}; gci.LockConsole(); auto u = wil::scope_exit([&]() { gci.UnlockConsole(); }); - WriteCharsLegacy(gci.GetActiveOutputBuffer(), u16String, true, &scrollY); + WriteCharsLegacy(gci.GetActiveOutputBuffer(), u16String, &scrollY); return 0; } diff --git a/src/host/readDataCooked.cpp b/src/host/readDataCooked.cpp index 36d54583fbb..3644dfe78db 100644 --- a/src/host/readDataCooked.cpp +++ b/src/host/readDataCooked.cpp @@ -28,6 +28,84 @@ constexpr int integerLog10(uint32_t v) 0; } +const std::wstring& COOKED_READ_DATA::BufferState::Get() const noexcept +{ + return _buffer; +} + +void COOKED_READ_DATA::BufferState::Replace(size_t offset, size_t remove, const wchar_t* input, size_t count) +{ + const auto size = _buffer.size(); + offset = std::min(offset, size); + remove = std::min(remove, size - offset); + + _buffer.replace(offset, remove, input, count); + _cursor = offset + count; + _dirtyBeg = std::min(_dirtyBeg, offset); +} + +void COOKED_READ_DATA::BufferState::Replace(const std::wstring_view& str) +{ + _buffer.assign(str); + _cursor = _buffer.size(); + _dirtyBeg = 0; +} + +size_t COOKED_READ_DATA::BufferState::GetCursorPosition() const noexcept +{ + return _cursor; +} + +void COOKED_READ_DATA::BufferState::SetCursorPosition(size_t pos) noexcept +{ + const auto size = _buffer.size(); + _cursor = std::min(pos, size); + // This ensures that _dirtyBeg isn't npos, which ensures that IsClean() returns false. + _dirtyBeg = std::min(_dirtyBeg, size); +} + +bool COOKED_READ_DATA::BufferState::IsClean() const noexcept +{ + return _dirtyBeg == npos; +} + +void COOKED_READ_DATA::BufferState::MarkEverythingDirty() noexcept +{ + _dirtyBeg = 0; +} + +void COOKED_READ_DATA::BufferState::MarkAsClean() noexcept +{ + _dirtyBeg = npos; +} + +std::wstring_view COOKED_READ_DATA::BufferState::GetUnmodifiedTextBeforeCursor() const noexcept +{ + return _slice(0, std::min(_dirtyBeg, _cursor)); +} + +std::wstring_view COOKED_READ_DATA::BufferState::GetUnmodifiedTextAfterCursor() const noexcept +{ + return _slice(_cursor, _dirtyBeg); +} + +std::wstring_view COOKED_READ_DATA::BufferState::GetModifiedTextBeforeCursor() const noexcept +{ + return _slice(_dirtyBeg, _cursor); +} + +std::wstring_view COOKED_READ_DATA::BufferState::GetModifiedTextAfterCursor() const noexcept +{ + return _slice(std::max(_dirtyBeg, _cursor), npos); +} + +std::wstring_view COOKED_READ_DATA::BufferState::_slice(size_t from, size_t to) const noexcept +{ + to = std::min(to, _buffer.size()); + from = std::min(from, to); + return std::wstring_view{ _buffer.data() + from, to - from }; +} + // Routine Description: // - Constructs cooked read data class to hold context across key presses while a user is modifying their 'input line'. // Arguments: @@ -71,9 +149,7 @@ COOKED_READ_DATA::COOKED_READ_DATA(_In_ InputBuffer* const pInputBuffer, if (!initialData.empty()) { - _buffer.assign(initialData); - _bufferCursor = _buffer.size(); - _bufferDirty = !_buffer.empty(); + _buffer.Replace(initialData); // The console API around `nInitialChars` in `CONSOLE_READCONSOLE_CONTROL` is pretty weird. // The way it works is that cmd.exe does a ReadConsole() with a `dwCtrlWakeupMask` that includes \t, @@ -232,9 +308,9 @@ void COOKED_READ_DATA::EraseBeforeResize() if (_distanceEnd) { - _unwindCursorPosition(_distanceCursor); + _offsetCursorPosition(-_distanceCursor); _erase(_distanceEnd); - _unwindCursorPosition(_distanceEnd); + _offsetCursorPosition(-_distanceEnd); _distanceCursor = 0; _distanceEnd = 0; } @@ -243,7 +319,7 @@ void COOKED_READ_DATA::EraseBeforeResize() // The counter-part to EraseBeforeResize(). void COOKED_READ_DATA::RedrawAfterResize() { - _markAsDirty(); + _buffer.MarkEverythingDirty(); _flushBuffer(); } @@ -254,7 +330,7 @@ void COOKED_READ_DATA::SetInsertMode(bool insertMode) noexcept bool COOKED_READ_DATA::IsEmpty() const noexcept { - return _buffer.empty() && _popups.empty(); + return _buffer.Get().empty() && _popups.empty(); } bool COOKED_READ_DATA::PresentingPopup() const noexcept @@ -367,8 +443,7 @@ void COOKED_READ_DATA::_handleChar(wchar_t wch, const DWORD modifiers) // Press tab past the "f" in the string "foo" and you'd get "f\to " (a trailing whitespace; the initial contents of the buffer back then). // It's unclear whether the original intention was to write at the end of the buffer at all times or to implement an insert mode. // I went with insert mode. - _buffer.insert(_bufferCursor, 1, wch); - _bufferCursor++; + _buffer.Replace(_buffer.GetCursorPosition(), 0, &wch, 1); _controlKeyState = modifiers; _transitionState(State::DoneWithWakeupMask); @@ -380,8 +455,7 @@ void COOKED_READ_DATA::_handleChar(wchar_t wch, const DWORD modifiers) case UNICODE_CARRIAGERETURN: { // NOTE: Don't append newlines to the buffer just yet! See _handlePostCharInputLoop for more information. - _bufferCursor = _buffer.size(); - _markAsDirty(); + _buffer.SetCursorPosition(npos); _transitionState(State::DoneWithCarriageReturn); return; } @@ -389,29 +463,9 @@ void COOKED_READ_DATA::_handleChar(wchar_t wch, const DWORD modifiers) case UNICODE_BACKSPACE: if (WI_IsFlagSet(_pInputBuffer->InputMode, ENABLE_PROCESSED_INPUT)) { - size_t pos; - if (wch == EXTKEY_ERASE_PREV_WORD) - { - pos = _wordPrev(_buffer, _bufferCursor); - } - else - { - pos = TextBuffer::GraphemePrev(_buffer, _bufferCursor); - } - - _buffer.erase(pos, _bufferCursor - pos); - _bufferCursor = pos; - _markAsDirty(); - - // Notify accessibility to read the backspaced character. - // See GH:12735, MSFT:31748387 - if (_screenInfo.HasAccessibilityEventing()) - { - if (const auto pConsoleWindow = ServiceLocator::LocateConsoleWindow()) - { - LOG_IF_FAILED(pConsoleWindow->SignalUia(UIA_Text_TextChangedEventId)); - } - } + const auto cursor = _buffer.GetCursorPosition(); + const auto pos = wch == EXTKEY_ERASE_PREV_WORD ? _wordPrev(_buffer.Get(), cursor) : TextBuffer::GraphemePrev(_buffer.Get(), cursor); + _buffer.Replace(pos, cursor - pos, nullptr, 0); return; } // If processed mode is disabled, control characters like backspace are treated like any other character. @@ -420,20 +474,16 @@ void COOKED_READ_DATA::_handleChar(wchar_t wch, const DWORD modifiers) break; } - if (_insertMode) - { - _buffer.insert(_bufferCursor, 1, wch); - } - else + size_t remove = 0; + if (!_insertMode) { // TODO GH#15875: If the input grapheme is >1 char, then this will replace >1 grapheme // --> We should accumulate input text as much as possible and then call _processInput with wstring_view. - const auto nextGraphemeLength = TextBuffer::GraphemeNext(_buffer, _bufferCursor) - _bufferCursor; - _buffer.replace(_bufferCursor, nextGraphemeLength, 1, wch); + const auto cursor = _buffer.GetCursorPosition(); + remove = TextBuffer::GraphemeNext(_buffer.Get(), cursor) - cursor; } - _bufferCursor++; - _markAsDirty(); + _buffer.Replace(_buffer.GetCursorPosition(), remove, &wch, 1); } // Handles non-character input for _readCharInputLoop() when no popups exist. @@ -445,68 +495,62 @@ void COOKED_READ_DATA::_handleVkey(uint16_t vkey, DWORD modifiers) switch (vkey) { case VK_ESCAPE: - if (!_buffer.empty()) + if (!_buffer.Get().empty()) { - _buffer.clear(); - _bufferCursor = 0; - _markAsDirty(); + _buffer.Replace(0, npos, nullptr, 0); } break; case VK_HOME: - if (_bufferCursor > 0) + if (_buffer.GetCursorPosition() > 0) { if (ctrlPressed) { - _buffer.erase(0, _bufferCursor); + _buffer.Replace(0, _buffer.GetCursorPosition(), nullptr, 0); } - _bufferCursor = 0; - _markAsDirty(); + _buffer.SetCursorPosition(0); } break; case VK_END: - if (_bufferCursor < _buffer.size()) + if (_buffer.GetCursorPosition() < _buffer.Get().size()) { if (ctrlPressed) { - _buffer.erase(_bufferCursor); + _buffer.Replace(_buffer.GetCursorPosition(), npos, nullptr, 0); } - _bufferCursor = _buffer.size(); - _markAsDirty(); + _buffer.SetCursorPosition(npos); } break; case VK_LEFT: - if (_bufferCursor != 0) + if (_buffer.GetCursorPosition() != 0) { if (ctrlPressed) { - _bufferCursor = _wordPrev(_buffer, _bufferCursor); + _buffer.SetCursorPosition(_wordPrev(_buffer.Get(), _buffer.GetCursorPosition())); } else { - _bufferCursor = TextBuffer::GraphemePrev(_buffer, _bufferCursor); + _buffer.SetCursorPosition(TextBuffer::GraphemePrev(_buffer.Get(), _buffer.GetCursorPosition())); } - _markAsDirty(); } break; case VK_F1: case VK_RIGHT: - if (_bufferCursor != _buffer.size()) + if (_buffer.GetCursorPosition() != _buffer.Get().size()) { if (ctrlPressed && vkey == VK_RIGHT) { - _bufferCursor = _wordNext(_buffer, _bufferCursor); + _buffer.SetCursorPosition(_wordNext(_buffer.Get(), _buffer.GetCursorPosition())); } else { - _bufferCursor = TextBuffer::GraphemeNext(_buffer, _bufferCursor); + _buffer.SetCursorPosition(TextBuffer::GraphemeNext(_buffer.Get(), _buffer.GetCursorPosition())); } - _markAsDirty(); } else if (_history) { // Traditionally pressing right at the end of an input line would paste characters from the previous command. const auto cmd = _history->GetLastCommand(); - const auto bufferSize = _buffer.size(); + const auto bufferSize = _buffer.Get().size(); const auto cmdSize = cmd.size(); size_t bufferBeg = 0; size_t cmdBeg = 0; @@ -519,13 +563,11 @@ void COOKED_READ_DATA::_handleVkey(uint16_t vkey, DWORD modifiers) if (bufferBeg >= bufferSize) { - _buffer.append(cmd, cmdBeg, cmdEnd - cmdBeg); - _bufferCursor = _buffer.size(); - _markAsDirty(); + _buffer.Replace(npos, 0, cmd.data() + cmdBeg, cmdEnd - cmdBeg); break; } - bufferBeg = TextBuffer::GraphemeNext(_buffer, bufferBeg); + bufferBeg = TextBuffer::GraphemeNext(_buffer.Get(), bufferBeg); cmdBeg = cmdEnd; } } @@ -533,38 +575,38 @@ void COOKED_READ_DATA::_handleVkey(uint16_t vkey, DWORD modifiers) case VK_INSERT: _insertMode = !_insertMode; _screenInfo.SetCursorDBMode(_insertMode != ServiceLocator::LocateGlobals().getConsoleInformation().GetInsertMode()); - _markAsDirty(); break; case VK_DELETE: - if (_bufferCursor < _buffer.size()) + if (_buffer.GetCursorPosition() < _buffer.Get().size()) { - _buffer.erase(_bufferCursor, TextBuffer::GraphemeNext(_buffer, _bufferCursor) - _bufferCursor); - _markAsDirty(); + const auto beg = _buffer.GetCursorPosition(); + const auto end = TextBuffer::GraphemeNext(_buffer.Get(), beg); + _buffer.Replace(beg, end - beg, nullptr, 0); } break; case VK_UP: case VK_F5: if (_history && !_history->AtFirstCommand()) { - _replaceBuffer(_history->Retrieve(CommandHistory::SearchDirection::Previous)); + _buffer.Replace(_history->Retrieve(CommandHistory::SearchDirection::Previous)); } break; case VK_DOWN: if (_history && !_history->AtLastCommand()) { - _replaceBuffer(_history->Retrieve(CommandHistory::SearchDirection::Next)); + _buffer.Replace(_history->Retrieve(CommandHistory::SearchDirection::Next)); } break; case VK_PRIOR: if (_history && !_history->AtFirstCommand()) { - _replaceBuffer(_history->RetrieveNth(0)); + _buffer.Replace(_history->RetrieveNth(0)); } break; case VK_NEXT: if (_history && !_history->AtLastCommand()) { - _replaceBuffer(_history->RetrieveNth(INT_MAX)); + _buffer.Replace(_history->RetrieveNth(INT_MAX)); } break; case VK_F2: @@ -577,12 +619,10 @@ void COOKED_READ_DATA::_handleVkey(uint16_t vkey, DWORD modifiers) if (_history) { const auto last = _history->GetLastCommand(); - if (last.size() > _bufferCursor) + if (last.size() > _buffer.GetCursorPosition()) { - const auto count = last.size() - _bufferCursor; - _buffer.replace(_bufferCursor, count, last, _bufferCursor, count); - _bufferCursor += count; - _markAsDirty(); + const auto count = last.size() - _buffer.GetCursorPosition(); + _buffer.Replace(_buffer.GetCursorPosition(), npos, last.data() + _buffer.GetCursorPosition(), count); } } break; @@ -616,12 +656,12 @@ void COOKED_READ_DATA::_handleVkey(uint16_t vkey, DWORD modifiers) if (_history) { CommandHistory::Index index = 0; - const auto prefix = std::wstring_view{ _buffer }.substr(0, _bufferCursor); + const auto cursorPos = _buffer.GetCursorPosition(); + const auto prefix = std::wstring_view{ _buffer.Get() }.substr(0, cursorPos); if (_history->FindMatchingCommand(prefix, _history->LastDisplayed, index, CommandHistory::MatchOptions::None)) { - _buffer.assign(_history->RetrieveNth(index)); - _bufferCursor = std::min(_bufferCursor, _buffer.size()); - _markAsDirty(); + _buffer.Replace(_history->RetrieveNth(index)); + _buffer.SetCursorPosition(cursorPos); } } break; @@ -649,7 +689,8 @@ void COOKED_READ_DATA::_handleVkey(uint16_t vkey, DWORD modifiers) void COOKED_READ_DATA::_handlePostCharInputLoop(const bool isUnicode, size_t& numBytes, ULONG& controlKeyState) { auto writer = _userBuffer; - std::wstring_view input{ _buffer }; + auto buffer = _buffer.Extract(); + std::wstring_view input{ buffer }; size_t lineCount = 1; if (_state == State::DoneWithCarriageReturn) @@ -676,7 +717,7 @@ void COOKED_READ_DATA::_handlePostCharInputLoop(const bool isUnicode, size_t& nu // It'll print "hello" but the previous prompt will say "echo hello bar" because the _distanceEnd // ended up being well over 14 leading it to believe that "bar" got overwritten during WriteCharsLegacy(). - WriteCharsLegacy(_screenInfo, newlineSuffix, true, nullptr); + WriteCharsLegacy(_screenInfo, newlineSuffix, nullptr); if (WI_IsFlagSet(_pInputBuffer->InputMode, ENABLE_ECHO_INPUT)) { @@ -692,14 +733,14 @@ void COOKED_READ_DATA::_handlePostCharInputLoop(const bool isUnicode, size_t& nu if (!alias.empty()) { - _buffer = std::move(alias); + buffer = std::move(alias); } else { - _buffer.append(newlineSuffix); + buffer.append(newlineSuffix); } - input = std::wstring_view{ _buffer }; + input = std::wstring_view{ buffer }; // doskey aliases may result in multiple lines of output (for instance `doskey test=echo foo$Techo bar$Techo baz`). // We need to emit them as multiple cooked reads as well, so that each read completes at a \r\n. @@ -720,7 +761,7 @@ void COOKED_READ_DATA::_handlePostCharInputLoop(const bool isUnicode, size_t& nu // We've truncated the `input` slice and now we need to restore it. const auto inputSizeAfter = input.size(); const auto amountConsumed = inputSizeBefore - inputSizeAfter; - input = std::wstring_view{ _buffer }; + input = std::wstring_view{ buffer }; input = input.substr(std::min(input.size(), amountConsumed)); GetInputReadHandleData()->SaveMultilinePendingInput(input); } @@ -746,48 +787,70 @@ void COOKED_READ_DATA::_transitionState(State state) noexcept _state = state; } -// Signals to _flushBuffer() that the contents of _buffer are stale and need to be redrawn. -// ALL _buffer and _bufferCursor changes must be flagged with _markAsDirty(). -// -// By using _bufferDirty to avoid redrawing the buffer unless needed, we turn the amortized time complexity of _readCharInputLoop() -// from O(n^2) (n(n+1)/2 redraws) into O(n). Pasting text would quickly turn into "accidentally quadratic" meme material otherwise. -void COOKED_READ_DATA::_markAsDirty() -{ - _bufferDirty = true; -} - // Draws the contents of _buffer onto the screen. +// +// By using _buffer._dirtyBeg to avoid redrawing the buffer unless needed, we turn the amortized +// time complexity of _readCharInputLoop() from O(n^2) (n(n+1)/2 redraws) into O(n). +// Pasting text would quickly turn into "accidentally quadratic" meme material otherwise. +// // NOTE: Don't call _flushBuffer() after appending newlines to the buffer! See _handlePostCharInputLoop for more information. void COOKED_READ_DATA::_flushBuffer() { - // _flushBuffer() is called often and is a good place to assert() that our _bufferCursor is still in bounds. - assert(_bufferCursor <= _buffer.size()); - _bufferCursor = std::min(_bufferCursor, _buffer.size()); - - if (!_bufferDirty) + if (_buffer.IsClean() || WI_IsFlagClear(_pInputBuffer->InputMode, ENABLE_ECHO_INPUT)) { return; } - if (WI_IsFlagSet(_pInputBuffer->InputMode, ENABLE_ECHO_INPUT)) + // `_buffer` is split up by two different indices: + // * `_buffer._cursor`: Text before the `_buffer._cursor` index must be accumulated + // into `distanceBeforeCursor` and the other half into `distanceAfterCursor`. + // This helps us figure out where the cursor is positioned on the screen. + // * `_buffer._dirtyBeg`: Text before `_buffer._dirtyBeg` must be written with SuppressMSAA + // and the other half without. Any text before `_buffer._dirtyBeg` is considered unchanged, + // and this split prevents us from announcing text that hasn't actually changed + // to accessibility tools via MSAA (or UIA, but UIA is robust against this anyways). + // + // This results in 2*2 = 4 writes of which always at least one of the middle two is empty, + // depending on whether _buffer._cursor > _buffer._dirtyBeg or _buffer._cursor < _buffer._dirtyBeg. + // slice() returns an empty string-view when `from` index is greater than the `to` index. + + ptrdiff_t distanceBeforeCursor = 0; + ptrdiff_t distanceAfterCursor = 0; { - _unwindCursorPosition(_distanceCursor); + // _distanceCursor might be larger than the entire viewport (= a really long input line). + // _offsetCursorPosition() with such an offset will end up clamping the cursor position to (0,0). + // To make this implementation behave a little bit more consistent in this case without + // writing a more thorough and complex readline implementation, we pass _measureChars() + // the relative "distance" to the current actual cursor position. That way _measureChars() + // can still figure out what the logical cursor position is, when it handles tabs, etc. + auto dirtyBegDistance = -_distanceCursor; + + distanceBeforeCursor = _measureChars(_buffer.GetUnmodifiedTextBeforeCursor(), dirtyBegDistance); + dirtyBegDistance += distanceBeforeCursor; + distanceAfterCursor = _measureChars(_buffer.GetUnmodifiedTextAfterCursor(), dirtyBegDistance); + dirtyBegDistance += distanceAfterCursor; + + _offsetCursorPosition(dirtyBegDistance); + } - const std::wstring_view view{ _buffer }; - const auto distanceBeforeCursor = _writeChars(view.substr(0, _bufferCursor)); - const auto distanceAfterCursor = _writeChars(view.substr(_bufferCursor)); - const auto distanceEnd = distanceBeforeCursor + distanceAfterCursor; - const auto eraseDistance = std::max(0, _distanceEnd - distanceEnd); + // Now we can finally write the parts of _buffer that have actually changed (or moved). + distanceBeforeCursor += _writeChars(_buffer.GetModifiedTextBeforeCursor()); + distanceAfterCursor += _writeChars(_buffer.GetModifiedTextAfterCursor()); - // If the contents of _buffer became shorter we'll have to erase the previously printed contents. - _erase(eraseDistance); - _unwindCursorPosition(distanceAfterCursor + eraseDistance); + const auto distanceEnd = distanceBeforeCursor + distanceAfterCursor; + const auto eraseDistance = std::max(0, _distanceEnd - distanceEnd); - _distanceCursor = distanceBeforeCursor; - _distanceEnd = distanceEnd; - } + // If the contents of _buffer became shorter we'll have to erase the previously printed contents. + _erase(eraseDistance); + _offsetCursorPosition(-eraseDistance - distanceAfterCursor); + + _buffer.MarkAsClean(); + _distanceCursor = distanceBeforeCursor; + _distanceEnd = distanceEnd; - _bufferDirty = false; + const auto pos = _screenInfo.GetTextBuffer().GetCursor().GetPosition(); + _screenInfo.MakeCursorVisible(pos); + std::ignore = _screenInfo.SetCursorPosition(pos, true); } // This is just a small helper to fill the next N cells starting at the current cursor position with whitespace. @@ -815,10 +878,118 @@ void COOKED_READ_DATA::_erase(ptrdiff_t distance) const } while (remaining != 0); } +// A helper to calculate the number of cells `text` would take up if it were written. +// `cursorOffset` allows the caller to specify a "logical" cursor position relative to the actual cursor position. +// This allows the function to track in which column it currently is, which is needed to implement tabs for instance. +ptrdiff_t COOKED_READ_DATA::_measureChars(const std::wstring_view& text, ptrdiff_t cursorOffset) const +{ + if (text.empty()) + { + return 0; + } + return _writeCharsImpl(text, true, cursorOffset); +} + // A helper to write text and calculate the number of cells we've written. // _unwindCursorPosition then allows us to go that many cells back. Tracking cells instead of explicit // buffer positions allows us to pay no further mind to whether the buffer scrolled up or not. ptrdiff_t COOKED_READ_DATA::_writeChars(const std::wstring_view& text) const +{ + if (text.empty()) + { + return 0; + } + return _writeCharsImpl(text, false, 0); +} + +ptrdiff_t COOKED_READ_DATA::_writeCharsImpl(const std::wstring_view& text, const bool measureOnly, const ptrdiff_t cursorOffset) const +{ + const auto width = _screenInfo.GetTextBuffer().GetSize().Width(); + auto it = text.begin(); + const auto end = text.end(); + ptrdiff_t distance = 0; + + for (;;) + { + const auto nextControlChar = std::find_if(it, end, [](const auto& wch) { return wch < L' '; }); + if (nextControlChar != it) + { + if (measureOnly) + { + distance += _measureCharsUnprocessed({ it, nextControlChar }, distance + cursorOffset); + } + else + { + distance += _writeCharsUnprocessed({ it, nextControlChar }); + } + it = nextControlChar; + } + if (nextControlChar == end) + { + break; + } + + wchar_t buf[2]; + size_t len = 0; + + const auto wch = *it; + if (wch == UNICODE_TAB) + { + const auto col = _getColumnAtRelativeCursorPosition(distance + cursorOffset); + const auto remaining = width - col; + distance += std::min(remaining, 8 - (col & 7)); + buf[0] = L'\t'; + len = 1; + } + else + { + // In the interactive mode we replace C0 control characters (0x00-0x1f) with ASCII representations like ^C (= 0x03). + distance += 2; + buf[0] = L'^'; + buf[1] = gsl::narrow_cast(wch + L'@'); + len = 2; + } + + if (!measureOnly) + { + distance += _writeCharsUnprocessed({ &buf[0], len }); + } + + ++it; + } + + return distance; +} + +ptrdiff_t COOKED_READ_DATA::_measureCharsUnprocessed(const std::wstring_view& text, ptrdiff_t cursorOffset) const +{ + if (text.empty()) + { + return 0; + } + + auto& textBuffer = _screenInfo.GetTextBuffer(); + const auto width = textBuffer.GetSize().Width(); + auto columnLimit = width - _getColumnAtRelativeCursorPosition(cursorOffset); + + size_t offset = 0; + ptrdiff_t distance = 0; + + while (offset < text.size()) + { + til::CoordType columns = 0; + offset += textBuffer.FitTextIntoColumns(text.substr(offset), columnLimit, columns); + distance += columns; + columnLimit = width; + } + + return distance; +} + +// A helper to write text and calculate the number of cells we've written. +// _unwindCursorPosition then allows us to go that many cells back. Tracking cells instead of explicit +// buffer positions allows us to pay no further mind to whether the buffer scrolled up or not. +ptrdiff_t COOKED_READ_DATA::_writeCharsUnprocessed(const std::wstring_view& text) const { if (text.empty()) { @@ -831,7 +1002,7 @@ ptrdiff_t COOKED_READ_DATA::_writeChars(const std::wstring_view& text) const const auto initialCursorPos = cursor.GetPosition(); til::CoordType scrollY = 0; - WriteCharsLegacy(_screenInfo, text, true, &scrollY); + WriteCharsLegacy(_screenInfo, text, &scrollY); const auto finalCursorPos = cursor.GetPosition(); const auto distance = (finalCursorPos.y - initialCursorPos.y + scrollY) * width + finalCursorPos.x - initialCursorPos.x; @@ -842,6 +1013,11 @@ ptrdiff_t COOKED_READ_DATA::_writeChars(const std::wstring_view& text) const // Moves the given point by the given distance inside the text buffer, as if moving a cursor with the left/right arrow keys. til::point COOKED_READ_DATA::_offsetPosition(til::point pos, ptrdiff_t distance) const { + if (distance == 0) + { + return pos; + } + const auto size = _screenInfo.GetTextBuffer().GetSize().Dimensions(); const auto w = static_cast(size.width); const auto h = static_cast(size.height); @@ -859,30 +1035,35 @@ til::point COOKED_READ_DATA::_offsetPosition(til::point pos, ptrdiff_t distance) // This moves the cursor `distance`-many cells back up in the buffer. // It's intended to be used in combination with _writeChars. -void COOKED_READ_DATA::_unwindCursorPosition(ptrdiff_t distance) const +void COOKED_READ_DATA::_offsetCursorPosition(ptrdiff_t distance) const { - if (distance <= 0) + if (distance == 0) { - // If all the code in this file works correctly, negative distances should not occur. - // If they do occur it would indicate a bug that would need to be fixed urgently. - assert(distance == 0); return; } const auto& textBuffer = _screenInfo.GetTextBuffer(); const auto& cursor = textBuffer.GetCursor(); - const auto pos = _offsetPosition(cursor.GetPosition(), -distance); + const auto pos = _offsetPosition(cursor.GetPosition(), distance); std::ignore = _screenInfo.SetCursorPosition(pos, true); _screenInfo.MakeCursorVisible(pos); } -// Just a simple helper to replace the entire buffer contents. -void COOKED_READ_DATA::_replaceBuffer(const std::wstring_view& str) +til::CoordType COOKED_READ_DATA::_getColumnAtRelativeCursorPosition(ptrdiff_t distance) const { - _buffer.assign(str); - _bufferCursor = _buffer.size(); - _markAsDirty(); + const auto& textBuffer = _screenInfo.GetTextBuffer(); + const auto size = _screenInfo.GetTextBuffer().GetSize().Dimensions(); + const auto& cursor = textBuffer.GetCursor(); + const auto pos = cursor.GetPosition(); + + auto x = gsl::narrow_cast((pos.x + distance) % size.width); + if (x < 0) + { + x += size.width; + } + + return x; } // If the viewport is large enough to fit a popup, this function prepares everything for @@ -1109,17 +1290,16 @@ void COOKED_READ_DATA::_popupHandleCopyToCharInput(Popup& /*popup*/, const wchar { // See PopupKind::CopyToChar for more information about this code. const auto cmd = _history->GetLastCommand(); - const auto idx = cmd.find(wch, _bufferCursor); + const auto cursor = _buffer.GetCursorPosition(); + const auto idx = cmd.find(wch, cursor); if (idx != decltype(cmd)::npos) { - // When we enter this if condition it's guaranteed that _bufferCursor must be + // When we enter this if condition it's guaranteed that _buffer.GetCursorPosition() must be // smaller than idx, which in turn implies that it's smaller than cmd.size(). // As such, calculating length is safe and str.size() == length. - const auto count = idx - _bufferCursor; - _buffer.replace(_bufferCursor, count, cmd, _bufferCursor, count); - _bufferCursor += count; - _markAsDirty(); + const auto count = idx - cursor; + _buffer.Replace(cursor, count, cmd.data() + cursor, count); } _popupsDone(); @@ -1138,9 +1318,10 @@ void COOKED_READ_DATA::_popupHandleCopyFromCharInput(Popup& /*popup*/, const wch else { // See PopupKind::CopyFromChar for more information about this code. - const auto idx = _buffer.find(wch, _bufferCursor); - _buffer.erase(_bufferCursor, std::min(idx, _buffer.size()) - _bufferCursor); - _markAsDirty(); + const auto cursor = _buffer.GetCursorPosition(); + auto idx = _buffer.Get().find(wch, cursor); + idx = std::min(idx, _buffer.Get().size()); + _buffer.Replace(cursor, idx - cursor, nullptr, 0); _popupsDone(); } } @@ -1159,7 +1340,7 @@ void COOKED_READ_DATA::_popupHandleCommandNumberInput(Popup& popup, const wchar_ if (wch == UNICODE_CARRIAGERETURN) { popup.commandNumber.buffer[popup.commandNumber.bufferSize++] = L'\0'; - _replaceBuffer(_history->RetrieveNth(std::stoi(popup.commandNumber.buffer.data()))); + _buffer.Replace(_history->RetrieveNth(std::stoi(popup.commandNumber.buffer.data()))); _popupsDone(); return; } @@ -1198,7 +1379,7 @@ void COOKED_READ_DATA::_popupHandleCommandListInput(Popup& popup, const wchar_t if (wch == UNICODE_CARRIAGERETURN) { - _buffer.assign(_history->RetrieveNth(cl.selected)); + _buffer.Replace(_history->RetrieveNth(cl.selected)); _popupsDone(); _handleChar(UNICODE_CARRIAGERETURN, modifiers); return; @@ -1222,7 +1403,7 @@ void COOKED_READ_DATA::_popupHandleCommandListInput(Popup& popup, const wchar_t break; case VK_LEFT: case VK_RIGHT: - _replaceBuffer(_history->RetrieveNth(cl.selected)); + _buffer.Replace(_history->RetrieveNth(cl.selected)); _popupsDone(); return; case VK_UP: @@ -1261,7 +1442,6 @@ void COOKED_READ_DATA::_popupHandleCommandListInput(Popup& popup, const wchar_t } _popupDrawCommandList(popup); - return; } void COOKED_READ_DATA::_popupDrawPrompt(const Popup& popup, const UINT id) const diff --git a/src/host/readDataCooked.hpp b/src/host/readDataCooked.hpp index 803724b6f52..9502eaa5c2d 100644 --- a/src/host/readDataCooked.hpp +++ b/src/host/readDataCooked.hpp @@ -40,6 +40,7 @@ class COOKED_READ_DATA final : public ReadData private: static constexpr uint8_t CommandNumberMaxInputLength = 5; + static constexpr size_t npos = static_cast(-1); enum class State : uint8_t { @@ -48,6 +49,38 @@ class COOKED_READ_DATA final : public ReadData DoneWithCarriageReturn, }; + // A helper struct to ensure we keep track of _dirtyBeg while the + // underlying _buffer is being modified by COOKED_READ_DATA. + struct BufferState + { + const std::wstring& Get() const noexcept; + std::wstring Extract() noexcept + { + return std::move(_buffer); + } + void Replace(size_t offset, size_t remove, const wchar_t* input, size_t count); + void Replace(const std::wstring_view& str); + + size_t GetCursorPosition() const noexcept; + void SetCursorPosition(size_t pos) noexcept; + + bool IsClean() const noexcept; + void MarkEverythingDirty() noexcept; + void MarkAsClean() noexcept; + + std::wstring_view GetUnmodifiedTextBeforeCursor() const noexcept; + std::wstring_view GetUnmodifiedTextAfterCursor() const noexcept; + std::wstring_view GetModifiedTextBeforeCursor() const noexcept; + std::wstring_view GetModifiedTextAfterCursor() const noexcept; + + private: + std::wstring_view _slice(size_t from, size_t to) const noexcept; + + std::wstring _buffer; + size_t _dirtyBeg = npos; + size_t _cursor = 0; + }; + enum class PopupKind { // Copies text from the previous command between the current cursor position and the first instance @@ -118,13 +151,16 @@ class COOKED_READ_DATA final : public ReadData void _handleVkey(uint16_t vkey, DWORD modifiers); void _handlePostCharInputLoop(bool isUnicode, size_t& numBytes, ULONG& controlKeyState); void _transitionState(State state) noexcept; - void _markAsDirty(); void _flushBuffer(); void _erase(ptrdiff_t distance) const; + ptrdiff_t _measureChars(const std::wstring_view& text, ptrdiff_t cursorOffset) const; ptrdiff_t _writeChars(const std::wstring_view& text) const; + ptrdiff_t _writeCharsImpl(const std::wstring_view& text, bool measureOnly, ptrdiff_t cursorOffset) const; + ptrdiff_t _measureCharsUnprocessed(const std::wstring_view& text, ptrdiff_t cursorOffset) const; + ptrdiff_t _writeCharsUnprocessed(const std::wstring_view& text) const; til::point _offsetPosition(til::point pos, ptrdiff_t distance) const; - void _unwindCursorPosition(ptrdiff_t distance) const; - void _replaceBuffer(const std::wstring_view& str); + void _offsetCursorPosition(ptrdiff_t distance) const; + til::CoordType _getColumnAtRelativeCursorPosition(ptrdiff_t distance) const; void _popupPush(PopupKind kind); void _popupsDone(); @@ -145,15 +181,13 @@ class COOKED_READ_DATA final : public ReadData ULONG _controlKeyState = 0; std::unique_ptr _tempHandle; - std::wstring _buffer; - size_t _bufferCursor = 0; + BufferState _buffer; // _distanceCursor is the distance between the start of the prompt and the // current cursor location in columns (including wide glyph padding columns). ptrdiff_t _distanceCursor = 0; // _distanceEnd is the distance between the start of the prompt and its last // glyph at the end in columns (including wide glyph padding columns). ptrdiff_t _distanceEnd = 0; - bool _bufferDirty = false; bool _insertMode = false; State _state = State::Accumulating; diff --git a/src/host/ut_host/ScreenBufferTests.cpp b/src/host/ut_host/ScreenBufferTests.cpp index b7ea13e6dc6..985fcadf343 100644 --- a/src/host/ut_host/ScreenBufferTests.cpp +++ b/src/host/ut_host/ScreenBufferTests.cpp @@ -2927,13 +2927,13 @@ void ScreenBufferTests::BackspaceDefaultAttrsWriteCharsLegacy() if (writeSingly) { - WriteCharsLegacy(si, L"X", false, nullptr); - WriteCharsLegacy(si, L"X", false, nullptr); - WriteCharsLegacy(si, L"\x08", false, nullptr); + WriteCharsLegacy(si, L"X", nullptr); + WriteCharsLegacy(si, L"X", nullptr); + WriteCharsLegacy(si, L"\x08", nullptr); } else { - WriteCharsLegacy(si, L"XX\x08", false, nullptr); + WriteCharsLegacy(si, L"XX\x08", nullptr); } TextAttribute expectedDefaults{}; @@ -7188,7 +7188,7 @@ void ScreenBufferTests::UpdateVirtualBottomWhenCursorMovesBelowIt() Log::Comment(L"Now write several lines of content using WriteCharsLegacy"); const auto content = L"1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n"; - WriteCharsLegacy(si, content, false, nullptr); + WriteCharsLegacy(si, content, nullptr); Log::Comment(L"Confirm that the cursor position has moved down 10 lines"); const auto newCursorPos = til::point{ initialCursorPos.x, initialCursorPos.y + 10 }; From 188a2ae4c8f712cdf98191a77df97db34a8d5e95 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 24 Oct 2023 13:28:59 -0500 Subject: [PATCH 032/167] Clear the system menu when we refrigerate a window (#16225) As in the title. Also fixes a crash for refrigeration with the rainbow border. Closes #16211 Tested by manually forcing us into Windows 10 mode (to refrigerate the window). That immediately repros the bug, which was simple enough to fix. (cherry picked from commit d8c7719bfb4f6ab68e8047c116efb6eeac0b559d) Service-Card-Id: 90928408 Service-Version: 1.19 --- src/cascadia/WindowsTerminal/AppHost.cpp | 2 +- src/cascadia/WindowsTerminal/IslandWindow.cpp | 8 ++++++++ src/cascadia/WindowsTerminal/IslandWindow.h | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 36d47211b79..5af3a80d50a 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -484,7 +484,7 @@ void AppHost::_revokeWindowCallbacks() // I suspect WinUI wouldn't like that very much. As such unregister all event handlers first. _revokers = {}; _showHideWindowThrottler.reset(); - + _stopFrameTimer(); _revokeWindowCallbacks(); // DO NOT CLOSE THE WINDOW diff --git a/src/cascadia/WindowsTerminal/IslandWindow.cpp b/src/cascadia/WindowsTerminal/IslandWindow.cpp index 9bea05acfe9..d7934ea3012 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/IslandWindow.cpp @@ -73,6 +73,8 @@ void IslandWindow::Refrigerate() noexcept // This pointer will get re-set in _warmInitialize SetWindowLongPtr(_window.get(), GWLP_USERDATA, 0); + _resetSystemMenu(); + _pfnCreateCallback = nullptr; _pfnSnapDimensionCallback = nullptr; @@ -1917,6 +1919,12 @@ void IslandWindow::RemoveFromSystemMenu(const winrt::hstring& itemLabel) _systemMenuItems.erase(it->first); } +void IslandWindow::_resetSystemMenu() +{ + // GetSystemMenu(..., true) will revert the menu to the default state. + GetSystemMenu(_window.get(), TRUE); +} + void IslandWindow::UseDarkTheme(const bool v) { const BOOL attribute = v ? TRUE : FALSE; diff --git a/src/cascadia/WindowsTerminal/IslandWindow.h b/src/cascadia/WindowsTerminal/IslandWindow.h index eee013c529a..b0aaf0b0ae3 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.h +++ b/src/cascadia/WindowsTerminal/IslandWindow.h @@ -159,6 +159,7 @@ class IslandWindow : std::unordered_map _systemMenuItems; UINT _systemMenuNextItemId; + void _resetSystemMenu(); private: // This minimum width allows for width the tabs fit From 68b5e587543c8e8901a6d8b829f52d94662321e3 Mon Sep 17 00:00:00 2001 From: Dustin Howett Date: Fri, 27 Oct 2023 22:20:07 +0000 Subject: [PATCH 033/167] Merged PR 9792152: [Git2Git] Build fixes on top of 188a2ae4c Retrieved from https://microsoft.visualstudio.com os.2020 OS official/rs_we_adept_e4d2 3fc4bb99c75451d0ecadd7e4c8fe06ad67217574 Related work items: MSFT-47266988 --- src/host/ft_host/sources | 1 + src/host/ft_integrity/IntegrityTest.cpp | 12 ++++++------ src/host/sources.inc | 1 + src/inc/HostAndPropsheetIncludes.h | 2 ++ src/inc/LibraryIncludes.h | 3 +++ src/interactivity/onecore/precomp.h | 3 +++ .../win32/ut_interactivity_win32/sources | 1 + src/project.inc | 2 +- src/terminal/adapter/ut_adapter/sources | 1 + src/terminal/parser/ut_parser/sources | 1 + src/types/sources.inc | 1 - 11 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/host/ft_host/sources b/src/host/ft_host/sources index beeba4e6753..ba6cf411afa 100644 --- a/src/host/ft_host/sources +++ b/src/host/ft_host/sources @@ -55,4 +55,5 @@ TARGETLIBS = \ DELAYLOAD = \ $(DELAYLOAD) \ + icu.dll; \ ext-ms-win-rtcore-ntuser-dpi-l1.dll; \ diff --git a/src/host/ft_integrity/IntegrityTest.cpp b/src/host/ft_integrity/IntegrityTest.cpp index c5cc24d48d0..0ca45b0b5d1 100644 --- a/src/host/ft_integrity/IntegrityTest.cpp +++ b/src/host/ft_integrity/IntegrityTest.cpp @@ -214,14 +214,14 @@ void IntegrityTest::_TestValidationHelper(const bool fIsBlockExpected, GetConsoleScreenBufferInfoEx(GetStdHandle(STD_OUTPUT_HANDLE), &csbiex); - LOG_OUTPUT(L"Buffer Size X:%d Y:%d", csbiex.dwSize.width, csbiex.dwSize.height); + LOG_OUTPUT(L"Buffer Size X:%d Y:%d", csbiex.dwSize.X, csbiex.dwSize.Y); - size_t cch = csbiex.dwSize.width; + size_t cch = csbiex.dwSize.X; wistd::unique_ptr stringData = wil::make_unique_nothrow(cch); THROW_IF_NULL_ALLOC(stringData); COORD coordRead = { 0 }; - for (coordRead.y = 0; coordRead.y < 8; coordRead.y++) + for (coordRead.Y = 0; coordRead.Y < 8; coordRead.Y++) { ZeroMemory(stringData.get(), sizeof(wchar_t) * cch); @@ -237,7 +237,7 @@ void IntegrityTest::_TestValidationHelper(const bool fIsBlockExpected, WEX::Common::String strActual; // At position 0, check the integrity. - if (coordRead.y == 0) + if (coordRead.Y == 0) { strExpected = pwszIntegrityExpected; } @@ -246,11 +246,11 @@ void IntegrityTest::_TestValidationHelper(const bool fIsBlockExpected, // For the rest, check whether the API call worked. if (fIsBlockExpected) { - strExpected = _rgpwszExpectedFail[coordRead.y - 1]; + strExpected = _rgpwszExpectedFail[coordRead.Y - 1]; } else { - strExpected = _rgpwszExpectedSuccess[coordRead.y - 1]; + strExpected = _rgpwszExpectedSuccess[coordRead.Y - 1]; } } stringData[strExpected.GetLength()] = L'\0'; diff --git a/src/host/sources.inc b/src/host/sources.inc index bf3b16bcae9..e3a878161e0 100644 --- a/src/host/sources.inc +++ b/src/host/sources.inc @@ -198,6 +198,7 @@ DELAYLOAD = \ DXGI.dll; \ OLEAUT32.dll; \ PROPSYS.dll; \ + icu.dll; \ api-ms-win-core-com-l1.dll; \ api-ms-win-core-registry-l2.dll; \ api-ms-win-mm-playsound-l1.dll; \ diff --git a/src/inc/HostAndPropsheetIncludes.h b/src/inc/HostAndPropsheetIncludes.h index 2a8d588b309..d0480421dfc 100644 --- a/src/inc/HostAndPropsheetIncludes.h +++ b/src/inc/HostAndPropsheetIncludes.h @@ -14,7 +14,9 @@ #include #undef WIN32_NO_STATUS +#ifndef NO_WINTERNL_INBOX_BUILD #include +#endif #pragma warning(push) #pragma warning(disable:4430) // Must disable 4430 "default int" warning for C++ because ntstatus.h is inflexible SDK definition. diff --git a/src/inc/LibraryIncludes.h b/src/inc/LibraryIncludes.h index 7a7ec3a6a53..511e4d4d95e 100644 --- a/src/inc/LibraryIncludes.h +++ b/src/inc/LibraryIncludes.h @@ -18,7 +18,9 @@ // Block minwindef.h min/max macros to prevent conflict #define NOMINMAX // Exclude rarely-used stuff from Windows headers +#ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN +#endif #include #include @@ -65,6 +67,7 @@ // GSL // Block GSL Multi Span include because it both has C++17 deprecated iterators // and uses the C-namespaced "max" which conflicts with Windows definitions. +#include #include #include diff --git a/src/interactivity/onecore/precomp.h b/src/interactivity/onecore/precomp.h index 7a19e746439..6883aa0972d 100644 --- a/src/interactivity/onecore/precomp.h +++ b/src/interactivity/onecore/precomp.h @@ -10,13 +10,16 @@ #include #include #include +#define WIN32_NO_STATUS #include +#undef WIN32_NO_STATUS #include "wchar.h" // Extension presence detection #include #define _DDK_INCLUDED +#define NO_WINTERNL_INBOX_BUILD #include "../../host/precomp.h" #else diff --git a/src/interactivity/win32/ut_interactivity_win32/sources b/src/interactivity/win32/ut_interactivity_win32/sources index 4b988d16720..0bfe9148eae 100644 --- a/src/interactivity/win32/ut_interactivity_win32/sources +++ b/src/interactivity/win32/ut_interactivity_win32/sources @@ -110,6 +110,7 @@ DELAYLOAD = \ DXGI.dll; \ D3D11.dll; \ OLEAUT32.dll; \ + icu.dll; \ api-ms-win-mm-playsound-l1.dll; \ api-ms-win-shcore-scaling-l1.dll; \ api-ms-win-shell-dataobject-l1.dll; \ diff --git a/src/project.inc b/src/project.inc index b604f50ea6d..4425c3f804c 100644 --- a/src/project.inc +++ b/src/project.inc @@ -32,7 +32,7 @@ USE_NATIVE_EH = 1 USE_STD_CPP20 = 1 MSC_WARNING_LEVEL = /W4 /WX -USER_C_FLAGS = $(USER_C_FLAGS) /fp:contract /utf-8 +USER_C_FLAGS = $(USER_C_FLAGS) /Zc:preprocessor /fp:contract /utf-8 # ------------------------------------- # Common Console Includes and Libraries diff --git a/src/terminal/adapter/ut_adapter/sources b/src/terminal/adapter/ut_adapter/sources index a0618ffe43b..81cb43dcfaa 100644 --- a/src/terminal/adapter/ut_adapter/sources +++ b/src/terminal/adapter/ut_adapter/sources @@ -105,6 +105,7 @@ DELAYLOAD = \ DXGI.dll; \ D3D11.dll; \ OLEAUT32.dll; \ + icu.dll; \ api-ms-win-mm-playsound-l1.dll; \ api-ms-win-shcore-scaling-l1.dll; \ api-ms-win-shell-dataobject-l1.dll; \ diff --git a/src/terminal/parser/ut_parser/sources b/src/terminal/parser/ut_parser/sources index 1c63146466f..e754a4e01c2 100644 --- a/src/terminal/parser/ut_parser/sources +++ b/src/terminal/parser/ut_parser/sources @@ -96,6 +96,7 @@ DELAYLOAD = \ DXGI.dll; \ D3D11.dll; \ OLEAUT32.dll; \ + icu.dll; \ api-ms-win-mm-playsound-l1.dll; \ api-ms-win-shcore-scaling-l1.dll; \ api-ms-win-shell-dataobject-l1.dll; \ diff --git a/src/types/sources.inc b/src/types/sources.inc index 6527fbed578..44f252a2a17 100644 --- a/src/types/sources.inc +++ b/src/types/sources.inc @@ -31,7 +31,6 @@ SOURCES= \ ..\CodepointWidthDetector.cpp \ ..\ColorFix.cpp \ ..\GlyphWidth.cpp \ - ..\ModifierKeyState.cpp \ ..\Viewport.cpp \ ..\convert.cpp \ ..\colorTable.cpp \ From 19efcfee9d66ae1a1638db4539dbee7e11740541 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Fri, 27 Oct 2023 17:23:57 -0500 Subject: [PATCH 034/167] Fix spelling after inbox merge --- .github/actions/spelling/allow/apis.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/spelling/allow/apis.txt b/.github/actions/spelling/allow/apis.txt index 4ce5fe88d6d..fb5bfd2fe24 100644 --- a/.github/actions/spelling/allow/apis.txt +++ b/.github/actions/spelling/allow/apis.txt @@ -210,6 +210,7 @@ tlg TME tmp tmpdir +tokeninfo tolower toupper TRACKMOUSEEVENT From 0289cb043c3aac0f9199f667550d8811022e9bed Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 31 Oct 2023 14:25:41 +0100 Subject: [PATCH 035/167] AtlasEngine: Minor bug fixes (#16219) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit fixes 4 minor bugs: * Forgot to set the maximum swap chain latency. Without it, it defaults to up to 3 frames of latency. We don't need this, because our renderer is simple and fast and is expected to draw frames within <1ms. * ClearType treats the alpha channel as ignored, whereas custom shaders can manipulate the alpha channel freely. This meant that using both simultaneously would produce weird effects, like text having black background. We now force grayscale AA instead. * The builtin retro shader should not be effected by the previous point. * When the cbuffer is entirely unused in a custom shader, it has so far resulted in constant redraws. This happened because the D3D reflection `GetDesc` call will then return `E_FAIL` in this situation. The new code on the other hand will now assume that a failure to get the description is equal to the variable being unused. Closes #15960 ## Validation Steps Performed * A custom passthrough shader works with grayscale and ClearType AA while also changing the opacity with Ctrl+Shift+Scroll ✅ * Same for the builtin retro shader, but ClearType works ✅ * The passthrough shader doesn't result in constant redrawing ✅ --- src/renderer/atlas/AtlasEngine.api.cpp | 14 +++++++------- src/renderer/atlas/AtlasEngine.r.cpp | 4 +++- src/renderer/atlas/BackendD3D.cpp | 20 ++++++++++++-------- src/renderer/atlas/common.h | 2 +- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/renderer/atlas/AtlasEngine.api.cpp b/src/renderer/atlas/AtlasEngine.api.cpp index 4459c97a8e5..c810dd115f6 100644 --- a/src/renderer/atlas/AtlasEngine.api.cpp +++ b/src/renderer/atlas/AtlasEngine.api.cpp @@ -490,20 +490,20 @@ void AtlasEngine::UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept void AtlasEngine::_resolveTransparencySettings() noexcept { + // An opaque background allows us to use true "independent" flips. See AtlasEngine::_createSwapChain(). + // We can't enable them if custom shaders are specified, because it's unknown, whether they support opaque inputs. + const bool useAlpha = _api.enableTransparentBackground || !_api.s->misc->customPixelShaderPath.empty(); // If the user asks for ClearType, but also for a transparent background // (which our ClearType shader doesn't simultaneously support) // then we need to sneakily force the renderer to grayscale AA. - const auto antialiasingMode = _api.enableTransparentBackground && _api.antialiasingMode == AntialiasingMode::ClearType ? AntialiasingMode::Grayscale : _api.antialiasingMode; - const bool enableTransparentBackground = _api.enableTransparentBackground || !_api.s->misc->customPixelShaderPath.empty() || _api.s->misc->useRetroTerminalEffect; + const auto antialiasingMode = useAlpha && _api.antialiasingMode == AntialiasingMode::ClearType ? AntialiasingMode::Grayscale : _api.antialiasingMode; - if (antialiasingMode != _api.s->font->antialiasingMode || enableTransparentBackground != _api.s->target->enableTransparentBackground) + if (antialiasingMode != _api.s->font->antialiasingMode || useAlpha != _api.s->target->useAlpha) { const auto s = _api.s.write(); s->font.write()->antialiasingMode = antialiasingMode; - // An opaque background allows us to use true "independent" flips. See AtlasEngine::_createSwapChain(). - // We can't enable them if custom shaders are specified, because it's unknown, whether they support opaque inputs. - s->target.write()->enableTransparentBackground = enableTransparentBackground; - _api.backgroundOpaqueMixin = enableTransparentBackground ? 0x00000000 : 0xff000000; + s->target.write()->useAlpha = useAlpha; + _api.backgroundOpaqueMixin = useAlpha ? 0x00000000 : 0xff000000; } } diff --git a/src/renderer/atlas/AtlasEngine.r.cpp b/src/renderer/atlas/AtlasEngine.r.cpp index e7070b708e6..1fac8f0e834 100644 --- a/src/renderer/atlas/AtlasEngine.r.cpp +++ b/src/renderer/atlas/AtlasEngine.r.cpp @@ -329,7 +329,7 @@ void AtlasEngine::_createSwapChain() .SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, // If our background is opaque we can enable "independent" flips by setting DXGI_ALPHA_MODE_IGNORE. // As our swap chain won't have to compose with DWM anymore it reduces the display latency dramatically. - .AlphaMode = _p.s->target->enableTransparentBackground ? DXGI_ALPHA_MODE_PREMULTIPLIED : DXGI_ALPHA_MODE_IGNORE, + .AlphaMode = _p.s->target->useAlpha ? DXGI_ALPHA_MODE_PREMULTIPLIED : DXGI_ALPHA_MODE_IGNORE, .Flags = swapChainFlags, }; @@ -360,6 +360,8 @@ void AtlasEngine::_createSwapChain() _p.swapChain.targetSize = _p.s->targetSize; _p.swapChain.waitForPresentation = true; + LOG_IF_FAILED(_p.swapChain.swapChain->SetMaximumFrameLatency(1)); + WaitUntilCanRender(); if (_p.swapChainChangedCallback) diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index 846567ca613..19334b3a9d4 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -403,23 +403,24 @@ void BackendD3D::_recreateCustomShader(const RenderingPayload& p) /* ppCode */ blob.addressof(), /* ppErrorMsgs */ error.addressof()); - // Unless we can determine otherwise, assume this shader requires evaluation every frame - _requiresContinuousRedraw = true; - if (SUCCEEDED(hr)) { - THROW_IF_FAILED(p.device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, _customPixelShader.put())); + THROW_IF_FAILED(p.device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, _customPixelShader.addressof())); // Try to determine whether the shader uses the Time variable wil::com_ptr reflector; - if (SUCCEEDED_LOG(D3DReflect(blob->GetBufferPointer(), blob->GetBufferSize(), IID_PPV_ARGS(reflector.put())))) + if (SUCCEEDED_LOG(D3DReflect(blob->GetBufferPointer(), blob->GetBufferSize(), IID_PPV_ARGS(reflector.addressof())))) { + // Depending on the version of the d3dcompiler_*.dll, the next two functions either return nullptr + // on failure or an instance of CInvalidSRConstantBuffer or CInvalidSRVariable respectively, + // which cause GetDesc() to return E_FAIL. In other words, we have to assume that any failure in the + // next few lines indicates that the cbuffer is entirely unused (--> _requiresContinuousRedraw=false). if (ID3D11ShaderReflectionConstantBuffer* constantBufferReflector = reflector->GetConstantBufferByIndex(0)) // shader buffer { if (ID3D11ShaderReflectionVariable* variableReflector = constantBufferReflector->GetVariableByIndex(0)) // time { D3D11_SHADER_VARIABLE_DESC variableDescriptor; - if (SUCCEEDED_LOG(variableReflector->GetDesc(&variableDescriptor))) + if (SUCCEEDED(variableReflector->GetDesc(&variableDescriptor))) { // only if time is used _requiresContinuousRedraw = WI_IsFlagSet(variableDescriptor.uFlags, D3D_SVF_USED); @@ -427,6 +428,11 @@ void BackendD3D::_recreateCustomShader(const RenderingPayload& p) } } } + else + { + // Unless we can determine otherwise, assume this shader requires evaluation every frame + _requiresContinuousRedraw = true; + } } else { @@ -447,8 +453,6 @@ void BackendD3D::_recreateCustomShader(const RenderingPayload& p) else if (p.s->misc->useRetroTerminalEffect) { THROW_IF_FAILED(p.device->CreatePixelShader(&custom_shader_ps[0], sizeof(custom_shader_ps), nullptr, _customPixelShader.put())); - // We know the built-in retro shader doesn't require continuous redraw. - _requiresContinuousRedraw = false; } if (_customPixelShader) diff --git a/src/renderer/atlas/common.h b/src/renderer/atlas/common.h index bb46ff61547..94434bffc9c 100644 --- a/src/renderer/atlas/common.h +++ b/src/renderer/atlas/common.h @@ -313,7 +313,7 @@ namespace Microsoft::Console::Render::Atlas struct TargetSettings { HWND hwnd = nullptr; - bool enableTransparentBackground = false; + bool useAlpha = false; bool useSoftwareRendering = false; }; From 4cec7e9b4b6063a910e2db573f81402fe736ed8e Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Mon, 6 Nov 2023 06:01:55 -0600 Subject: [PATCH 036/167] try to remove a few of these but ultimately, eh --- src/cascadia/TerminalApp/TerminalTab.cpp | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp index 0adcca4eb6e..ac48d6d7322 100644 --- a/src/cascadia/TerminalApp/TerminalTab.cpp +++ b/src/cascadia/TerminalApp/TerminalTab.cpp @@ -1686,6 +1686,18 @@ namespace winrt::TerminalApp::implementation return _zoomedPane != nullptr; } + TermControl& _termControlFromPane(const auto& pane) + { + if (const auto content{ pane->GetContent() }) + { + if (const auto termContent{ content.try_as() }) + { + return termContent.GetTerminal(); + } + } + return nullptr; + } + // Method Description: // - Toggle read-only mode on the active pane // - If a parent pane is selected, this will ensure that all children have @@ -1697,14 +1709,14 @@ namespace winrt::TerminalApp::implementation auto hasReadOnly = false; auto allReadOnly = true; _activePane->WalkTree([&](const auto& p) { - if (const auto& control{ p->GetTerminalControl() }) + if (const auto& control{ _termControlFromPane(p) }) { hasReadOnly |= control.ReadOnly(); allReadOnly &= control.ReadOnly(); } }); _activePane->WalkTree([&](const auto& p) { - if (const auto& control{ p->GetTerminalControl() }) + if (const auto& control{ _termControlFromPane(p) }) { // If all controls have the same read only state then just toggle if (allReadOnly || !hasReadOnly) @@ -1729,14 +1741,14 @@ namespace winrt::TerminalApp::implementation auto hasReadOnly = false; auto allReadOnly = true; _activePane->WalkTree([&](const auto& p) { - if (const auto& control{ p->GetTerminalControl() }) + if (const auto& control{ _termControlFromPane(p) }) { hasReadOnly |= control.ReadOnly(); allReadOnly &= control.ReadOnly(); } }); _activePane->WalkTree([&](const auto& p) { - if (const auto& control{ p->GetTerminalControl() }) + if (const auto& control{ _termControlFromPane(p) }) { // If all controls have the same read only state then just disable if (allReadOnly || !hasReadOnly) @@ -1821,7 +1833,7 @@ namespace winrt::TerminalApp::implementation { return; } - if (const auto& control{ p->GetTerminalControl() }) + if (const auto& control{ _termControlFromPane(p) }) { auto it = _contentEvents.find(*paneId); if (it != _contentEvents.end()) From 9e86c9811f2cf0ad53c9c543b886274b1079dd00 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 6 Nov 2023 22:30:03 +0100 Subject: [PATCH 037/167] Fix the fix for the fix of nearby font loading (#16196) I still don't know how to reproduce it properly, but I'm slowly wrapping my head around how and why it happens. The issue isn't that `FindFamilyName` fails with `exists=FALSE`, but rather that any of the followup calls like `GetDesignGlyphMetrics` fails, which results in an exception and subsequently in an orderly fallback to Consolas. I've always thought that the issue is that even with the nearby font collection we get an `exists=FALSE`... I'm not sure why I thought that. This changeset also drops the fallback iteration for Lucida Console and Courier New, because I felt like the code looks neater that way and I think it's a reasonable expectation that Consolas is always installed. Closes #16058 --- src/renderer/atlas/AtlasEngine.api.cpp | 65 ++++++++++++-------------- src/renderer/atlas/common.h | 1 - src/renderer/dx/DxFontInfo.cpp | 18 +++---- 3 files changed, 40 insertions(+), 44 deletions(-) diff --git a/src/renderer/atlas/AtlasEngine.api.cpp b/src/renderer/atlas/AtlasEngine.api.cpp index c810dd115f6..0e2f7941688 100644 --- a/src/renderer/atlas/AtlasEngine.api.cpp +++ b/src/renderer/atlas/AtlasEngine.api.cpp @@ -455,30 +455,34 @@ void AtlasEngine::SetWarningCallback(std::function pfn) noexcept [[nodiscard]] HRESULT AtlasEngine::UpdateFont(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map& features, const std::unordered_map& axes) noexcept { - static constexpr std::array fallbackFaceNames{ static_cast(nullptr), L"Consolas", L"Lucida Console", L"Courier New" }; - auto it = fallbackFaceNames.begin(); - const auto end = fallbackFaceNames.end(); + try + { + _updateFont(fontInfoDesired.GetFaceName().c_str(), fontInfoDesired, fontInfo, features, axes); + return S_OK; + } + CATCH_LOG(); - for (;;) + if constexpr (Feature_NearbyFontLoading::IsEnabled()) { try { - _updateFont(*it, fontInfoDesired, fontInfo, features, axes); + // _resolveFontMetrics() checks `_api.s->font->fontCollection` for a pre-existing font collection, + // before falling back to using the system font collection. This way we can inject our custom one. See GH#9375. + // Doing it this way is a bit hacky, but it does have the benefit that we can cache a font collection + // instance across font changes, like when zooming the font size rapidly using the scroll wheel. + _api.s.write()->font.write()->fontCollection = FontCache::GetCached(); + _updateFont(fontInfoDesired.GetFaceName().c_str(), fontInfoDesired, fontInfo, features, axes); return S_OK; } - catch (...) - { - ++it; - if (it == end) - { - RETURN_CAUGHT_EXCEPTION(); - } - else - { - LOG_CAUGHT_EXCEPTION(); - } - } + CATCH_LOG(); } + + try + { + _updateFont(nullptr, fontInfoDesired, fontInfo, features, axes); + return S_OK; + } + CATCH_RETURN(); } void AtlasEngine::UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept @@ -598,11 +602,7 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo if (!requestedFaceName) { - requestedFaceName = fontInfoDesired.GetFaceName().c_str(); - if (!requestedFaceName) - { - requestedFaceName = L"Consolas"; - } + requestedFaceName = L"Consolas"; } if (!requestedSize.height) { @@ -614,22 +614,19 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo requestedWeight = DWRITE_FONT_WEIGHT_NORMAL; } - wil::com_ptr fontCollection; - THROW_IF_FAILED(_p.dwriteFactory->GetSystemFontCollection(fontCollection.addressof(), FALSE)); + // UpdateFont() (and its NearbyFontLoading feature path specifically) sets `_api.s->font->fontCollection` + // to a custom font collection that includes .ttf files that are bundled with our app package. See GH#9375. + // Doing it this way is a bit hacky, but it does have the benefit that we can cache a font collection + // instance across font changes, like when zooming the font size rapidly using the scroll wheel. + auto fontCollection = _api.s->font->fontCollection; + if (!fontCollection) + { + THROW_IF_FAILED(_p.dwriteFactory->GetSystemFontCollection(fontCollection.addressof(), FALSE)); + } u32 index = 0; BOOL exists = false; THROW_IF_FAILED(fontCollection->FindFamilyName(requestedFaceName, &index, &exists)); - - if constexpr (Feature_NearbyFontLoading::IsEnabled()) - { - if (!exists) - { - fontCollection = FontCache::GetCached(); - THROW_IF_FAILED(fontCollection->FindFamilyName(requestedFaceName, &index, &exists)); - } - } - THROW_HR_IF(DWRITE_E_NOFONT, !exists); wil::com_ptr fontFamily; diff --git a/src/renderer/atlas/common.h b/src/renderer/atlas/common.h index 94434bffc9c..b8fa3ac0d59 100644 --- a/src/renderer/atlas/common.h +++ b/src/renderer/atlas/common.h @@ -470,7 +470,6 @@ namespace Microsoft::Console::Render::Atlas wil::com_ptr systemFontFallback; wil::com_ptr systemFontFallback1; // optional, might be nullptr wil::com_ptr textAnalyzer; - wil::com_ptr renderingParams; std::function warningCallback; std::function swapChainChangedCallback; diff --git a/src/renderer/dx/DxFontInfo.cpp b/src/renderer/dx/DxFontInfo.cpp index 4945939fd86..b1d4844a601 100644 --- a/src/renderer/dx/DxFontInfo.cpp +++ b/src/renderer/dx/DxFontInfo.cpp @@ -127,15 +127,6 @@ void DxFontInfo::SetFromEngine(const std::wstring_view familyName, { face = _FindFontFace(localeName); - if constexpr (Feature_NearbyFontLoading::IsEnabled()) - { - if (!face) - { - _fontCollection = FontCache::GetCached(); - face = _FindFontFace(localeName); - } - } - if (!face) { // If we missed, try looking a little more by trimming the last word off the requested family name a few times. @@ -167,6 +158,15 @@ void DxFontInfo::SetFromEngine(const std::wstring_view familyName, } CATCH_LOG(); + if constexpr (Feature_NearbyFontLoading::IsEnabled()) + { + if (!face) + { + _fontCollection = FontCache::GetCached(); + face = _FindFontFace(localeName); + } + } + // Alright, if our quick shot at trimming didn't work either... // move onto looking up a font from our hard-coded list of fonts // that should really always be available. From 17cc109081bffd37f8727c417a5f981329867280 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 6 Nov 2023 23:00:40 +0100 Subject: [PATCH 038/167] Remove conhost telemetry (#16253) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `Telemetry` class was implemented as a singleton which stood in my long-term goal to remove all global variables from the project. Most telemetry captured by it hasn't been looked at for a long time and just as much is now pointless (e.g.,`_fCtrlPgUpPgDnUsed`). This removes the code. ## Validation Steps Performed * Still compiles ✅ --- .github/actions/spelling/expect/expect.txt | 1 - src/host/consoleInformation.cpp | 2 +- src/host/directio.cpp | 1 - src/host/exe/exemain.cpp | 1 + src/host/getset.cpp | 1 - src/host/init.cpp | 2 +- src/host/input.cpp | 4 +- src/host/screenInfo.cpp | 2 +- src/host/selectionInput.cpp | 6 - src/host/srvinit.cpp | 9 +- src/host/telemetry.cpp | 503 +-------------------- src/host/telemetry.hpp | 183 +------- src/host/tracing.cpp | 3 - src/interactivity/win32/consoleKeyInfo.cpp | 2 +- src/interactivity/win32/window.cpp | 6 +- src/interactivity/win32/windowio.cpp | 83 +--- src/interactivity/win32/windowproc.cpp | 18 - src/server/ApiDispatchers.cpp | 99 ---- src/server/ApiDispatchersInternal.cpp | 3 - src/server/IoDispatchers.cpp | 3 - src/server/ProcessHandle.cpp | 4 - 21 files changed, 23 insertions(+), 913 deletions(-) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 3ea1b7e958f..3a565ee4812 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -1590,7 +1590,6 @@ RIGHTALIGN RIGHTBUTTON riid Rike -RIPMSG RIS roadmap robomac diff --git a/src/host/consoleInformation.cpp b/src/host/consoleInformation.cpp index 4722cebe2b9..a99a8871688 100644 --- a/src/host/consoleInformation.cpp +++ b/src/host/consoleInformation.cpp @@ -104,7 +104,7 @@ ULONG CONSOLE_INFORMATION::GetCSRecursionCount() const noexcept return STATUS_SUCCESS; } - RIPMSG1(RIP_WARNING, "Console init failed with status 0x%x", Status); + LOG_NTSTATUS_MSG(Status, "Console init failed"); delete gci.ScreenBuffers; gci.ScreenBuffers = nullptr; diff --git a/src/host/directio.cpp b/src/host/directio.cpp index 1187bd4faae..0a00fa25f82 100644 --- a/src/host/directio.cpp +++ b/src/host/directio.cpp @@ -841,7 +841,6 @@ CATCH_RETURN(); _In_ PCD_CREATE_OBJECT_INFORMATION Information, _In_ PCONSOLE_CREATESCREENBUFFER_MSG a) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::CreateConsoleScreenBuffer); const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); // If any buffer type except the one we support is set, it's invalid. diff --git a/src/host/exe/exemain.cpp b/src/host/exe/exemain.cpp index 7aaa45271d9..7436cc8cb75 100644 --- a/src/host/exe/exemain.cpp +++ b/src/host/exe/exemain.cpp @@ -222,6 +222,7 @@ int CALLBACK wWinMain( _In_ PWSTR /*pwszCmdLine*/, _In_ int /*nCmdShow*/) { + wil::SetResultLoggingCallback(&Tracing::TraceFailure); Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().hInstance = hInstance; ConsoleCheckDebug(); diff --git a/src/host/getset.cpp b/src/host/getset.cpp index eec9f720db8..3d371d4b24f 100644 --- a/src/host/getset.cpp +++ b/src/host/getset.cpp @@ -41,7 +41,6 @@ void ApiRoutines::GetConsoleInputModeImpl(InputBuffer& context, ULONG& mode) noe { try { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleMode); const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); diff --git a/src/host/init.cpp b/src/host/init.cpp index eb1e554c905..dc4a0c238f5 100644 --- a/src/host/init.cpp +++ b/src/host/init.cpp @@ -50,7 +50,7 @@ void InitSideBySide() // OpenConsole anyways, nothing happens and we get ERROR_SXS_PROCESS_DEFAULT_ALREADY_SET. if (ERROR_SXS_PROCESS_DEFAULT_ALREADY_SET != error) { - RIPMSG1(RIP_WARNING, "InitSideBySide failed create an activation context. Error: %d\r\n", error); + LOG_WIN32_MSG(error, "InitSideBySide failed create an activation context."); } } } diff --git a/src/host/input.cpp b/src/host/input.cpp index ea13a0c0ef5..fd92154020c 100644 --- a/src/host/input.cpp +++ b/src/host/input.cpp @@ -206,7 +206,7 @@ void HandleMenuEvent(const DWORD wParam) EventsWritten = gci.pInputBuffer->Write(SynthesizeMenuEvent(wParam)); if (EventsWritten != 1) { - RIPMSG0(RIP_WARNING, "PutInputInBuffer: EventsWritten != 1, 1 expected"); + LOG_HR_MSG(E_FAIL, "PutInputInBuffer: EventsWritten != 1, 1 expected"); } } catch (...) @@ -230,7 +230,7 @@ void HandleCtrlEvent(const DWORD EventType) gci.CtrlFlags |= CONSOLE_CTRL_CLOSE_FLAG; break; default: - RIPMSG1(RIP_ERROR, "Invalid EventType: 0x%x", EventType); + LOG_HR_MSG(E_INVALIDARG, "Invalid EventType: 0x%x", EventType); } } diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp index d02bbaf3d67..d36cdeab3a9 100644 --- a/src/host/screenInfo.cpp +++ b/src/host/screenInfo.cpp @@ -1391,7 +1391,7 @@ try { if ((USHORT)coordNewScreenSize.width >= SHORT_MAX || (USHORT)coordNewScreenSize.height >= SHORT_MAX) { - RIPMSG2(RIP_WARNING, "Invalid screen buffer size (0x%x, 0x%x)", coordNewScreenSize.width, coordNewScreenSize.height); + LOG_HR_MSG(E_INVALIDARG, "Invalid screen buffer size (0x%x, 0x%x)", coordNewScreenSize.width, coordNewScreenSize.height); return STATUS_INVALID_PARAMETER; } diff --git a/src/host/selectionInput.cpp b/src/host/selectionInput.cpp index f00bbb4b847..ab08d3f21c3 100644 --- a/src/host/selectionInput.cpp +++ b/src/host/selectionInput.cpp @@ -37,8 +37,6 @@ Selection::KeySelectionEventResult Selection::HandleKeySelectionEvent(const INPU // C-c, C-Ins. C-S-c Is also handled by this case. ((ctrlPressed) && (wVirtualKeyCode == 'C' || wVirtualKeyCode == VK_INSERT))) { - Telemetry::Instance().SetKeyboardTextEditingUsed(); - // copy selection return Selection::KeySelectionEventResult::CopyToClipboard; } @@ -291,8 +289,6 @@ bool Selection::HandleKeyboardLineSelectionEvent(const INPUT_KEY_INFO* const pIn return false; } - Telemetry::Instance().SetKeyboardTextSelectionUsed(); - // if we're not currently selecting anything, start a new mouse selection if (!IsInSelectingState()) { @@ -704,8 +700,6 @@ bool Selection::_HandleColorSelection(const INPUT_KEY_INFO* const pInputKeyInfo) // Clear the selection and call the search / mark function. ClearSelection(); - Telemetry::Instance().LogColorSelectionUsed(); - const auto& textBuffer = gci.renderData.GetTextBuffer(); const auto hits = textBuffer.SearchText(str, true); for (const auto& s : hits) diff --git a/src/host/srvinit.cpp b/src/host/srvinit.cpp index 284f4483820..30c1ae6ead7 100644 --- a/src/host/srvinit.cpp +++ b/src/host/srvinit.cpp @@ -428,11 +428,6 @@ HRESULT ConsoleCreateIoThread(_In_ HANDLE Server, [[maybe_unused]] PCONSOLE_API_MSG connectMessage) try { - // Create a telemetry instance here - this singleton is responsible for - // setting up the g_hConhostV2EventTraceProvider, which is otherwise not - // initialized in the defterm handoff at this point. - (void)Telemetry::Instance(); - #if !TIL_FEATURE_RECEIVEINCOMINGHANDOFF_ENABLED TraceLoggingWrite(g_hConhostV2EventTraceProvider, "SrvInit_ReceiveHandoff_Disabled", @@ -865,8 +860,6 @@ PWSTR TranslateConsoleTitle(_In_ PCWSTR pwszConsoleTitle, const BOOL fUnexpand, [[nodiscard]] NTSTATUS ConsoleAllocateConsole(PCONSOLE_API_CONNECTINFO p) { // AllocConsole is outside our codebase, but we should be able to mostly track the call here. - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::AllocConsole); - auto& g = ServiceLocator::LocateGlobals(); auto& gci = g.getConsoleInformation(); @@ -1050,7 +1043,7 @@ DWORD WINAPI ConsoleIoThread(LPVOID lpParameter) // This will not return. Terminate immediately when disconnected. ServiceLocator::RundownAndExit(STATUS_SUCCESS); } - RIPMSG1(RIP_WARNING, "DeviceIoControl failed with Result 0x%x", hr); + LOG_HR_MSG(hr, "DeviceIoControl failed"); ReplyMsg = nullptr; continue; } diff --git a/src/host/telemetry.cpp b/src/host/telemetry.cpp index 21cff15e815..fb25c7f6818 100644 --- a/src/host/telemetry.cpp +++ b/src/host/telemetry.cpp @@ -2,519 +2,26 @@ // Licensed under the MIT license. #include "precomp.h" - -#include -#include "Shlwapi.h" #include "telemetry.hpp" -#include - -#include "history.h" - -#include "../interactivity/inc/ServiceLocator.hpp" TRACELOGGING_DEFINE_PROVIDER(g_hConhostV2EventTraceProvider, "Microsoft.Windows.Console.Host", // {fe1ff234-1f09-50a8-d38d-c44fab43e818} (0xfe1ff234, 0x1f09, 0x50a8, 0xd3, 0x8d, 0xc4, 0x4f, 0xab, 0x43, 0xe8, 0x18), TraceLoggingOptionMicrosoftTelemetry()); -#pragma warning(push) -// Disable 4351 so we can initialize the arrays to 0 without a warning. -#pragma warning(disable : 4351) -Telemetry::Telemetry() : - _uiColorSelectionUsed(0), - _tStartedAt(0), - _wchProcessFileNames(), - // Start at position 1, since the first 2 bytes contain the number of strings. - _iProcessFileNamesNext(1), - _iProcessConnectedCurrently(SIZE_MAX), - _rgiProcessFileNameIndex(), - _rguiProcessFileNamesCount(), - _rgiAlphabeticalIndex(), - _rguiTimesApiUsed(), - _rguiTimesApiUsedAnsi(), - _uiNumberProcessFileNames(0), - _fBashUsed(false), - _fKeyboardTextEditingUsed(false), - _fKeyboardTextSelectionUsed(false), - _fUserInteractiveForTelemetry(false), - _fCtrlPgUpPgDnUsed(false), - _uiCtrlShiftCProcUsed(0), - _uiCtrlShiftCRawUsed(0), - _uiCtrlShiftVProcUsed(0), - _uiCtrlShiftVRawUsed(0), - _uiQuickEditCopyProcUsed(0), - _uiQuickEditCopyRawUsed(0), - _uiQuickEditPasteProcUsed(0), - _uiQuickEditPasteRawUsed(0) + +// This code remains to serve as template if we ever need telemetry for conhost again. +// The git history for this file may prove useful. +#if 0 +Telemetry::Telemetry() { - time(&_tStartedAt); TraceLoggingRegister(g_hConhostV2EventTraceProvider); TraceLoggingWriteStart(_activity, "ActivityStart"); - // initialize wil tracelogging - wil::SetResultLoggingCallback(&Tracing::TraceFailure); } -#pragma warning(pop) Telemetry::~Telemetry() { TraceLoggingWriteStop(_activity, "ActivityStop"); TraceLoggingUnregister(g_hConhostV2EventTraceProvider); } - -void Telemetry::SetUserInteractive() -{ - _fUserInteractiveForTelemetry = true; -} - -void Telemetry::SetCtrlPgUpPgDnUsed() -{ - _fCtrlPgUpPgDnUsed = true; - SetUserInteractive(); -} - -void Telemetry::LogCtrlShiftCProcUsed() -{ - _uiCtrlShiftCProcUsed++; - SetUserInteractive(); -} - -void Telemetry::LogCtrlShiftCRawUsed() -{ - _uiCtrlShiftCRawUsed++; - SetUserInteractive(); -} - -void Telemetry::LogCtrlShiftVProcUsed() -{ - _uiCtrlShiftVProcUsed++; - SetUserInteractive(); -} - -void Telemetry::LogCtrlShiftVRawUsed() -{ - _uiCtrlShiftVRawUsed++; - SetUserInteractive(); -} - -void Telemetry::LogQuickEditCopyProcUsed() -{ - _uiQuickEditCopyProcUsed++; - SetUserInteractive(); -} - -void Telemetry::LogQuickEditCopyRawUsed() -{ - _uiQuickEditCopyRawUsed++; - SetUserInteractive(); -} - -void Telemetry::LogQuickEditPasteProcUsed() -{ - _uiQuickEditPasteProcUsed++; - SetUserInteractive(); -} - -void Telemetry::LogQuickEditPasteRawUsed() -{ - _uiQuickEditPasteRawUsed++; - SetUserInteractive(); -} - -// Log usage of the Color Selection option. -void Telemetry::LogColorSelectionUsed() -{ - _uiColorSelectionUsed++; - SetUserInteractive(); -} - -void Telemetry::SetWindowSizeChanged() -{ - SetUserInteractive(); -} - -void Telemetry::SetContextMenuUsed() -{ - SetUserInteractive(); -} - -void Telemetry::SetKeyboardTextSelectionUsed() -{ - _fKeyboardTextSelectionUsed = true; - SetUserInteractive(); -} - -void Telemetry::SetKeyboardTextEditingUsed() -{ - _fKeyboardTextEditingUsed = true; - SetUserInteractive(); -} - -// Log an API call was used. -void Telemetry::LogApiCall(const ApiCall api, const BOOLEAN fUnicode) -{ - // Initially we thought about passing over a string (ex. "XYZ") 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. - if (fUnicode) - { - _rguiTimesApiUsed[api]++; - } - else - { - _rguiTimesApiUsedAnsi[api]++; - } -} - -// Log an API call was used. -void Telemetry::LogApiCall(const ApiCall api) -{ - _rguiTimesApiUsed[api]++; -} - -// Tries to find the process name amongst our previous process names by doing a binary search. -// The main difference between this and the standard bsearch library call, is that if this -// can't find the string, it returns the position the new string should be inserted at. This saves -// us from having an additional search through the array, and improves performance. -bool Telemetry::FindProcessName(const WCHAR* pszProcessName, _Out_ size_t* iPosition) const -{ - auto iMin = 0; - auto iMid = 0; - auto iMax = _uiNumberProcessFileNames - 1; - auto result = 0; - - while (iMin <= iMax) - { - iMid = (iMax + iMin) / 2; - // Use a case-insensitive comparison. We do support running Linux binaries now, but we haven't seen them connect - // as processes, and even if they did, we don't care about the difference in running emacs vs. Emacs. - result = _wcsnicmp(pszProcessName, _wchProcessFileNames + _rgiProcessFileNameIndex[_rgiAlphabeticalIndex[iMid]], MAX_PATH); - if (result < 0) - { - iMax = iMid - 1; - } - else if (result > 0) - { - iMin = iMid + 1; - } - else - { - // Found the string. - *iPosition = iMid; - return true; - } - } - - // Let them know which position to insert the string at. - *iPosition = (result > 0) ? iMid + 1 : iMid; - return false; -} - -// Log a process name and number of times it has connected to the console in preparation to send through telemetry. -// We were considering sending out a log of telemetry when each process connects, but then the telemetry can get -// complicated and spammy, especially since command line utilities like help.exe and where.exe are considered processes. -// Don't send telemetry for every time a process connects, as this will help reduce the load on our servers. -// Just save the name and count, and send the telemetry before the console exits. -void Telemetry::LogProcessConnected(const HANDLE hProcess) -{ - // This is a bit of processing, so don't do it for the 95% of machines that aren't being sampled. - if (TraceLoggingProviderEnabled(g_hConhostV2EventTraceProvider, 0, MICROSOFT_KEYWORD_MEASURES)) - { - // Don't initialize wszFilePathAndName, QueryFullProcessImageName does that for us. Use QueryFullProcessImageName instead of - // GetProcessImageFileName because we need the path to begin with a drive letter and not a device name. - WCHAR wszFilePathAndName[MAX_PATH]; - DWORD dwSize = ARRAYSIZE(wszFilePathAndName); - if (QueryFullProcessImageName(hProcess, 0, wszFilePathAndName, &dwSize)) - { - // Stripping out the path also helps with PII issues in case they launched the program - // from a path containing their username. - auto pwszFileName = PathFindFileName(wszFilePathAndName); - - size_t iFileName; - if (FindProcessName(pwszFileName, &iFileName)) - { - // We already logged this process name, so just increment the count. - _iProcessConnectedCurrently = _rgiAlphabeticalIndex[iFileName]; - _rguiProcessFileNamesCount[_iProcessConnectedCurrently]++; - } - else if ((_uiNumberProcessFileNames < ARRAYSIZE(_rguiProcessFileNamesCount)) && - (_iProcessFileNamesNext < ARRAYSIZE(_wchProcessFileNames) - 10)) - { - // Check if the MS released bash was used. MS bash is installed under windows\system32, and it's possible somebody else - // could be installing their bash into that directory, but not likely. If the user first runs a non-MS bash, - // and then runs MS bash, we won't detect the MS bash as running, but it's an acceptable compromise. - if (!_fBashUsed && !_wcsnicmp(c_pwszBashExeName, pwszFileName, MAX_PATH)) - { - // We could have gotten the system directory once when this class starts, but we'd have to hold the memory for it - // plus we're not sure we'd ever need it, so just get it when we know we're running bash.exe. - WCHAR wszSystemDirectory[MAX_PATH] = L""; - if (GetSystemDirectory(wszSystemDirectory, ARRAYSIZE(wszSystemDirectory))) - { - _fBashUsed = (PathIsSameRoot(wszFilePathAndName, wszSystemDirectory) == TRUE); - } - } - - // In order to send out a dynamic array of strings through telemetry, we have to pack the strings into a single WCHAR array. - // There currently aren't any helper functions for this, and we have to pack it manually. - // To understand the format of the single string, consult the documentation in the traceloggingprovider.h file. - if (SUCCEEDED(StringCchCopyW(_wchProcessFileNames + _iProcessFileNamesNext, ARRAYSIZE(_wchProcessFileNames) - _iProcessFileNamesNext - 1, pwszFileName))) - { - // As each FileName comes in, it's appended to the end. However to improve searching speed, we have an array of indexes - // that is alphabetically sorted. We could call qsort, but that would be a waste in performance since we're just adding one string - // at a time and we always keep the array sorted, so just shift everything over one. - for (size_t n = _uiNumberProcessFileNames; n > iFileName; n--) - { - _rgiAlphabeticalIndex[n] = _rgiAlphabeticalIndex[n - 1]; - } - - // Now point to the string, and set the count to 1. - _rgiAlphabeticalIndex[iFileName] = _uiNumberProcessFileNames; - _rgiProcessFileNameIndex[_uiNumberProcessFileNames] = _iProcessFileNamesNext; - _rguiProcessFileNamesCount[_uiNumberProcessFileNames] = 1; - _iProcessFileNamesNext += wcslen(pwszFileName) + 1; - _iProcessConnectedCurrently = _uiNumberProcessFileNames++; - - // Packed arrays start with a UINT16 value indicating the number of elements in the array. - auto pbFileNames = reinterpret_cast(_wchProcessFileNames); - pbFileNames[0] = (BYTE)_uiNumberProcessFileNames; - pbFileNames[1] = (BYTE)(_uiNumberProcessFileNames >> 8); - } - } - } - } -} - -// This Function sends final Trace log before session closes. -// We're primarily sending this telemetry once at the end, and only when the user interacted with the console -// so we don't overwhelm our servers by sending a constant stream of telemetry while the console is being used. -void Telemetry::WriteFinalTraceLog() -{ - const auto& gci = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation(); - const auto& renderSettings = gci.GetRenderSettings(); - // This is a bit of processing, so don't do it for the 95% of machines that aren't being sampled. - if (TraceLoggingProviderEnabled(g_hConhostV2EventTraceProvider, 0, MICROSOFT_KEYWORD_MEASURES)) - { - if (_fUserInteractiveForTelemetry) - { - // Send this back using "measures" since we want a good sampling of our entire userbase. - time_t tEndedAt; - time(&tEndedAt); - // clang-format off -#pragma prefast(suppress: __WARNING_NONCONST_LOCAL, "Activity can't be const, since it's set to a random value on startup.") - // clang-format on - TraceLoggingWriteTagged(_activity, - "SessionEnding", - TraceLoggingBool(_fBashUsed, "BashUsed"), - TraceLoggingBool(_fCtrlPgUpPgDnUsed, "CtrlPgUpPgDnUsed"), - TraceLoggingBool(_fKeyboardTextEditingUsed, "KeyboardTextEditingUsed"), - TraceLoggingBool(_fKeyboardTextSelectionUsed, "KeyboardTextSelectionUsed"), - TraceLoggingUInt32(_uiCtrlShiftCProcUsed, "CtrlShiftCProcUsed"), - TraceLoggingUInt32(_uiCtrlShiftCRawUsed, "CtrlShiftCRawUsed"), - TraceLoggingUInt32(_uiCtrlShiftVProcUsed, "CtrlShiftVProcUsed"), - TraceLoggingUInt32(_uiCtrlShiftVRawUsed, "CtrlShiftVRawUsed"), - TraceLoggingUInt32(_uiQuickEditCopyProcUsed, "QuickEditCopyProcUsed"), - TraceLoggingUInt32(_uiQuickEditCopyRawUsed, "QuickEditCopyRawUsed"), - TraceLoggingUInt32(_uiQuickEditPasteProcUsed, "QuickEditPasteProcUsed"), - TraceLoggingUInt32(_uiQuickEditPasteRawUsed, "QuickEditPasteRawUsed"), - TraceLoggingBool(gci.GetLinkTitle().length() == 0, "LaunchedFromShortcut"), - // Normally we would send out a single array containing the name and count, - // but that's difficult to do with our telemetry system, so send out two separate arrays. - // Casting to UINT should be fine, since our array size is only 2K. - TraceLoggingPackedField(_wchProcessFileNames, static_cast(sizeof(WCHAR) * _iProcessFileNamesNext), TlgInUNICODESTRING | TlgInVcount, "ProcessesConnected"), - TraceLoggingUInt32Array(_rguiProcessFileNamesCount, _uiNumberProcessFileNames, "ProcessesConnectedCount"), - // Send back both starting and ending times separately instead just usage time (ending - starting). - // This can help us determine if they were using multiple consoles at the same time. - TraceLoggingInt32(static_cast(_tStartedAt), "StartedUsingAtSeconds"), - TraceLoggingInt32(static_cast(tEndedAt), "EndedUsingAtSeconds"), - TraceLoggingUInt32(_uiColorSelectionUsed, "ColorSelectionUsed"), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), - TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); - - // Always send this back. We could only send this back when they click "OK" in the settings dialog, but sending it - // back every time should give us a good idea of their current, final settings, and not just only when they change a setting. - // clang-format off -#pragma prefast(suppress: __WARNING_NONCONST_LOCAL, "Activity can't be const, since it's set to a random value on startup.") - // clang-format on - TraceLoggingWriteTagged(_activity, - "Settings", - TraceLoggingBool(gci.GetAutoPosition(), "AutoPosition"), - TraceLoggingBool(gci.GetHistoryNoDup(), "HistoryNoDuplicates"), - TraceLoggingBool(gci.GetInsertMode(), "InsertMode"), - TraceLoggingBool(gci.GetLineSelection(), "LineSelection"), - TraceLoggingBool(gci.GetQuickEdit(), "QuickEdit"), - TraceLoggingValue(gci.GetWindowAlpha(), "WindowAlpha"), - TraceLoggingBool(gci.GetWrapText(), "WrapText"), - TraceLoggingUInt32Array((UINT32 const*)renderSettings.GetColorTable().data(), 16, "ColorTable"), - TraceLoggingValue(gci.CP, "CodePageInput"), - TraceLoggingValue(gci.OutputCP, "CodePageOutput"), - TraceLoggingValue(gci.GetFontSize().width, "FontSizeX"), - TraceLoggingValue(gci.GetFontSize().height, "FontSizeY"), - TraceLoggingValue(gci.GetHotKey(), "HotKey"), - TraceLoggingValue(gci.GetScreenBufferSize().width, "ScreenBufferSizeX"), - TraceLoggingValue(gci.GetScreenBufferSize().height, "ScreenBufferSizeY"), - TraceLoggingValue(gci.GetStartupFlags(), "StartupFlags"), - TraceLoggingValue(gci.GetDefaultVirtTermLevel(), "VirtualTerminalLevel"), - TraceLoggingValue(gci.GetWindowSize().width, "WindowSizeX"), - TraceLoggingValue(gci.GetWindowSize().height, "WindowSizeY"), - TraceLoggingValue(gci.GetWindowOrigin().width, "WindowOriginX"), - TraceLoggingValue(gci.GetWindowOrigin().height, "WindowOriginY"), - TraceLoggingValue(gci.GetFaceName(), "FontName"), - TraceLoggingBool(gci.IsAltF4CloseAllowed(), "AllowAltF4Close"), - TraceLoggingBool(gci.GetCtrlKeyShortcutsDisabled(), "ControlKeyShortcutsDisabled"), - TraceLoggingBool(gci.GetEnableColorSelection(), "EnabledColorSelection"), - TraceLoggingBool(gci.GetFilterOnPaste(), "FilterOnPaste"), - TraceLoggingBool(gci.GetTrimLeadingZeros(), "TrimLeadingZeros"), - TraceLoggingValue(gci.GetLaunchFaceName().data(), "LaunchFontName"), - TraceLoggingValue(CommandHistory::s_CountOfHistories(), "CommandHistoriesNumber"), - TraceLoggingValue(gci.GetCodePage(), "CodePage"), - TraceLoggingValue(gci.GetCursorSize(), "CursorSize"), - TraceLoggingValue(gci.GetFontFamily(), "FontFamily"), - TraceLoggingValue(gci.GetFontWeight(), "FontWeight"), - TraceLoggingValue(gci.GetHistoryBufferSize(), "HistoryBufferSize"), - TraceLoggingValue(gci.GetNumberOfHistoryBuffers(), "HistoryBuffersNumber"), - TraceLoggingValue(gci.GetScrollScale(), "ScrollScale"), - TraceLoggingValue(gci.GetFillAttribute(), "FillAttribute"), - TraceLoggingValue(gci.GetPopupFillAttribute(), "PopupFillAttribute"), - TraceLoggingValue(gci.GetShowWindow(), "ShowWindow"), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), - TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); - static_assert(sizeof(UINT32) == sizeof(renderSettings.GetColorTable()[0]), "gci.Get16ColorTable()"); - - // 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. - // clang-format off -#pragma prefast(suppress: __WARNING_NONCONST_LOCAL, "Activity can't be const, since it's set to a random value on startup.") - // clang-format on - TraceLoggingWriteTagged(_activity, - "ApiUsed", - TraceLoggingUInt32(_rguiTimesApiUsed[AddConsoleAlias], "AddConsoleAlias"), - TraceLoggingUInt32(_rguiTimesApiUsed[AllocConsole], "AllocConsole"), - TraceLoggingUInt32(_rguiTimesApiUsed[AttachConsole], "AttachConsole"), - TraceLoggingUInt32(_rguiTimesApiUsed[CreateConsoleScreenBuffer], "CreateConsoleScreenBuffer"), - TraceLoggingUInt32(_rguiTimesApiUsed[GenerateConsoleCtrlEvent], "GenerateConsoleCtrlEvent"), - TraceLoggingUInt32(_rguiTimesApiUsed[FillConsoleOutputAttribute], "FillConsoleOutputAttribute"), - TraceLoggingUInt32(_rguiTimesApiUsed[FillConsoleOutputCharacter], "FillConsoleOutputCharacter"), - TraceLoggingUInt32(_rguiTimesApiUsed[FlushConsoleInputBuffer], "FlushConsoleInputBuffer"), - TraceLoggingUInt32(_rguiTimesApiUsed[FreeConsole], "FreeConsole"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleAlias], "GetConsoleAlias"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleAliases], "GetConsoleAliases"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleAliasExesLength], "GetConsoleAliasExesLength"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleAliasesLength], "GetConsoleAliasesLength"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleAliasExes], "GetConsoleAliasExes"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleCP], "GetConsoleCP"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleCursorInfo], "GetConsoleCursorInfo"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleDisplayMode], "GetConsoleDisplayMode"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleFontSize], "GetConsoleFontSize"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleHistoryInfo], "GetConsoleHistoryInfo"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleLangId], "GetConsoleLangId"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleMode], "GetConsoleMode"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleOriginalTitle], "GetConsoleOriginalTitle"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleOutputCP], "GetConsoleOutputCP"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleProcessList], "GetConsoleProcessList"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleScreenBufferInfoEx], "GetConsoleScreenBufferInfoEx"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleSelectionInfo], "GetConsoleSelectionInfo"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleTitle], "GetConsoleTitle"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleWindow], "GetConsoleWindow"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetCurrentConsoleFontEx], "GetCurrentConsoleFontEx"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetLargestConsoleWindowSize], "GetLargestConsoleWindowSize"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetNumberOfConsoleInputEvents], "GetNumberOfConsoleInputEvents"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetNumberOfConsoleMouseButtons], "GetNumberOfConsoleMouseButtons"), - TraceLoggingUInt32(_rguiTimesApiUsed[PeekConsoleInput], "PeekConsoleInput"), - TraceLoggingUInt32(_rguiTimesApiUsed[ReadConsole], "ReadConsole"), - TraceLoggingUInt32(_rguiTimesApiUsed[ReadConsoleInput], "ReadConsoleInput"), - TraceLoggingUInt32(_rguiTimesApiUsed[ReadConsoleOutput], "ReadConsoleOutput"), - TraceLoggingUInt32(_rguiTimesApiUsed[ReadConsoleOutputAttribute], "ReadConsoleOutputAttribute"), - TraceLoggingUInt32(_rguiTimesApiUsed[ReadConsoleOutputCharacter], "ReadConsoleOutputCharacter"), - TraceLoggingUInt32(_rguiTimesApiUsed[ScrollConsoleScreenBuffer], "ScrollConsoleScreenBuffer"), - TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleActiveScreenBuffer], "SetConsoleActiveScreenBuffer"), - TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleCP], "SetConsoleCP"), - TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleCursorInfo], "SetConsoleCursorInfo"), - TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleCursorPosition], "SetConsoleCursorPosition"), - TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleDisplayMode], "SetConsoleDisplayMode"), - TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleHistoryInfo], "SetConsoleHistoryInfo"), - TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleMode], "SetConsoleMode"), - TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleOutputCP], "SetConsoleOutputCP"), - TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleScreenBufferInfoEx], "SetConsoleScreenBufferInfoEx"), - TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleScreenBufferSize], "SetConsoleScreenBufferSize"), - TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleTextAttribute], "SetConsoleTextAttribute"), - TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleTitle], "SetConsoleTitle"), - TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleWindowInfo], "SetConsoleWindowInfo"), - TraceLoggingUInt32(_rguiTimesApiUsed[SetCurrentConsoleFontEx], "SetCurrentConsoleFontEx"), - TraceLoggingUInt32(_rguiTimesApiUsed[WriteConsole], "WriteConsole"), - TraceLoggingUInt32(_rguiTimesApiUsed[WriteConsoleInput], "WriteConsoleInput"), - TraceLoggingUInt32(_rguiTimesApiUsed[WriteConsoleOutput], "WriteConsoleOutput"), - TraceLoggingUInt32(_rguiTimesApiUsed[WriteConsoleOutputAttribute], "WriteConsoleOutputAttribute"), - TraceLoggingUInt32(_rguiTimesApiUsed[WriteConsoleOutputCharacter], "WriteConsoleOutputCharacter"), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), - TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); - - for (auto n = 0; n < ARRAYSIZE(_rguiTimesApiUsedAnsi); n++) - { - if (_rguiTimesApiUsedAnsi[n]) - { - // Ansi specific API's are used less, so check if we have anything to send back. - // Also breaking it up into a separate TraceLoggingWriteTagged fixes a compilation warning that - // the heap is too small. - // clang-format off -#pragma prefast(suppress: __WARNING_NONCONST_LOCAL, "Activity can't be const, since it's set to a random value on startup.") - // clang-format on - TraceLoggingWriteTagged(_activity, - "ApiAnsiUsed", - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[AddConsoleAlias], "AddConsoleAlias"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[FillConsoleOutputCharacter], "FillConsoleOutputCharacter"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[GetConsoleAlias], "GetConsoleAlias"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[GetConsoleAliases], "GetConsoleAliases"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[GetConsoleAliasesLength], "GetConsoleAliasesLength"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[GetConsoleAliasExes], "GetConsoleAliasExes"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[GetConsoleAliasExesLength], "GetConsoleAliasExesLength"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[GetConsoleOriginalTitle], "GetConsoleOriginalTitle"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[GetConsoleTitle], "GetConsoleTitle"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[PeekConsoleInput], "PeekConsoleInput"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[ReadConsole], "ReadConsole"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[ReadConsoleInput], "ReadConsoleInput"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[ReadConsoleOutput], "ReadConsoleOutput"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[ReadConsoleOutputCharacter], "ReadConsoleOutputCharacter"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[SetConsoleTitle], "SetConsoleTitle"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[WriteConsole], "WriteConsole"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[WriteConsoleInput], "WriteConsoleInput"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[WriteConsoleOutput], "WriteConsoleOutput"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[WriteConsoleOutputCharacter], "WriteConsoleOutputCharacter"), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), - TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); - break; - } - } - } - } -} - -// These are legacy error messages with limited value, so don't send them back as telemetry. -void Telemetry::LogRipMessage(_In_z_ const char* pszMessage, ...) const -{ - // Code needed for passing variable parameters to the vsprintf function. - va_list args; - va_start(args, pszMessage); - char szMessageEvaluated[200] = ""; - auto cCharsWritten = vsprintf_s(szMessageEvaluated, ARRAYSIZE(szMessageEvaluated), pszMessage, args); - va_end(args); - -#if DBG - OutputDebugStringA(szMessageEvaluated); #endif - - if (cCharsWritten > 0) - { - // clang-format off -#pragma prefast(suppress: __WARNING_NONCONST_LOCAL, "Activity can't be const, since it's set to a random value on startup.") - // clang-format on - TraceLoggingWriteTagged(_activity, - "RipMessage", - TraceLoggingString(szMessageEvaluated, "Message")); - } -} - -bool Telemetry::IsUserInteractive() -{ - return _fUserInteractiveForTelemetry; -} diff --git a/src/host/telemetry.hpp b/src/host/telemetry.hpp index 4cb2560a3b5..619205b9f7b 100644 --- a/src/host/telemetry.hpp +++ b/src/host/telemetry.hpp @@ -1,186 +1,17 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- telemetry.hpp - -Abstract: -- This module is used for recording all telemetry feedback from the console - -Author(s): -- Evan Wirt (EvanWi) 09-Jul-2014 -- Kourosh Mehrain (KMehrain) 09-Jul-2014 -- Stephen Somuah (StSomuah) 09-Jul-2014 -- Anup Manandhar (AnupM) 09-Jul-2014 ---*/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. #pragma once -#include - +// This code remains to serve as template if we ever need telemetry for conhost again. +// The git history for this file may prove useful. +#if 0 class Telemetry { public: - // Implement this as a singleton class. - static Telemetry& Instance() - { - static Telemetry s_Instance; - return s_Instance; - } - - void SetUserInteractive(); - void SetWindowSizeChanged(); - void SetContextMenuUsed(); - void SetKeyboardTextSelectionUsed(); - void SetKeyboardTextEditingUsed(); - void SetCtrlPgUpPgDnUsed(); - void LogCtrlShiftCProcUsed(); - void LogCtrlShiftCRawUsed(); - void LogCtrlShiftVProcUsed(); - void LogCtrlShiftVRawUsed(); - void LogQuickEditCopyProcUsed(); - void LogQuickEditCopyRawUsed(); - void LogQuickEditPasteProcUsed(); - void LogQuickEditPasteRawUsed(); - void LogColorSelectionUsed(); - - void LogProcessConnected(const HANDLE hProcess); - void WriteFinalTraceLog(); - - void LogRipMessage(_In_z_ const char* pszMessage, ...) const; - - bool IsUserInteractive(); - - // Names are from the external API call names. Note that some names can be different - // than the internal API calls. - // Don't worry about the following APIs, because they are external to our conhost codebase and hard to track through - // telemetry: GetStdHandle, SetConsoleCtrlHandler, SetStdHandle - // We can't differentiate between these apis, so just log the "-Ex" versions: GetConsoleScreenBufferInfo / GetConsoleScreenBufferInfoEx, - // GetCurrentConsoleFontEx / GetCurrentConsoleFont - enum ApiCall - { - AddConsoleAlias = 0, - AllocConsole, - AttachConsole, - CreateConsoleScreenBuffer, - FillConsoleOutputAttribute, - FillConsoleOutputCharacter, - FlushConsoleInputBuffer, - FreeConsole, - GenerateConsoleCtrlEvent, - GetConsoleAlias, - GetConsoleAliases, - GetConsoleAliasesLength, - GetConsoleAliasExes, - GetConsoleAliasExesLength, - GetConsoleCP, - GetConsoleCursorInfo, - GetConsoleDisplayMode, - GetConsoleFontSize, - GetConsoleHistoryInfo, - GetConsoleMode, - GetConsoleLangId, - GetConsoleOriginalTitle, - GetConsoleOutputCP, - GetConsoleProcessList, - GetConsoleScreenBufferInfoEx, - GetConsoleSelectionInfo, - GetConsoleTitle, - GetConsoleWindow, - GetCurrentConsoleFontEx, - GetLargestConsoleWindowSize, - GetNumberOfConsoleInputEvents, - GetNumberOfConsoleMouseButtons, - PeekConsoleInput, - ReadConsole, - ReadConsoleInput, - ReadConsoleOutput, - ReadConsoleOutputAttribute, - ReadConsoleOutputCharacter, - ScrollConsoleScreenBuffer, - SetConsoleActiveScreenBuffer, - SetConsoleCP, - SetConsoleCursorInfo, - SetConsoleCursorPosition, - SetConsoleDisplayMode, - SetConsoleHistoryInfo, - SetConsoleMode, - SetConsoleOutputCP, - SetConsoleScreenBufferInfoEx, - SetConsoleScreenBufferSize, - SetConsoleTextAttribute, - SetConsoleTitle, - SetConsoleWindowInfo, - SetCurrentConsoleFontEx, - WriteConsole, - WriteConsoleInput, - WriteConsoleOutput, - WriteConsoleOutputAttribute, - WriteConsoleOutputCharacter, - // Only use this last enum as a count of the number of api enums. - NUMBER_OF_APIS - }; - void LogApiCall(const ApiCall api); - void LogApiCall(const ApiCall api, const BOOLEAN fUnicode); - -private: - // Used to prevent multiple instances Telemetry(); ~Telemetry(); - Telemetry(const Telemetry&); - void operator=(const Telemetry&); - - bool FindProcessName(const WCHAR* pszProcessName, _Out_ size_t* iPosition) const; - - static const int c_iMaxProcessesConnected = 100; +private: TraceLoggingActivity _activity; - - unsigned int _uiColorSelectionUsed; - time_t _tStartedAt; - WCHAR const* const c_pwszBashExeName = L"bash.exe"; - - // The current recommendation is to keep telemetry events 4KB or less, so let's keep our array at less than 2KB (1000 * 2 bytes). - WCHAR _wchProcessFileNames[1000]; - // Index into our specially packed string, where to insert the next string. - size_t _iProcessFileNamesNext; - // Index for the currently connected process. - size_t _iProcessConnectedCurrently; - // An array of indexes into the _wchProcessFileNames array, which point to the individual process names. - size_t _rgiProcessFileNameIndex[c_iMaxProcessesConnected]; - // Number of times each process has connected to the console. - unsigned int _rguiProcessFileNamesCount[c_iMaxProcessesConnected]; - // To speed up searching the Process Names, create an alphabetically sorted index. - size_t _rgiAlphabeticalIndex[c_iMaxProcessesConnected]; - unsigned int _rguiTimesApiUsed[NUMBER_OF_APIS]; - // Most of this array will be empty, and is only used if an API has an ansi specific variant. - unsigned int _rguiTimesApiUsedAnsi[NUMBER_OF_APIS]; - // Total number of file names we've added. - UINT16 _uiNumberProcessFileNames; - - bool _fBashUsed; - bool _fKeyboardTextEditingUsed; - bool _fKeyboardTextSelectionUsed; - bool _fUserInteractiveForTelemetry; - bool _fCtrlPgUpPgDnUsed; - - // Linux copy and paste keyboard shortcut telemetry - unsigned int _uiCtrlShiftCProcUsed; - unsigned int _uiCtrlShiftCRawUsed; - unsigned int _uiCtrlShiftVProcUsed; - unsigned int _uiCtrlShiftVRawUsed; - - // Quick edit copy and paste usage telemetry - unsigned int _uiQuickEditCopyProcUsed; - unsigned int _uiQuickEditCopyRawUsed; - unsigned int _uiQuickEditPasteProcUsed; - unsigned int _uiQuickEditPasteRawUsed; }; - -// Log the RIPMSG through telemetry, and also through a normal OutputDebugStringW call. -// These are drop-in substitutes for the RIPMSG0-4 macros from /windows/Core/ntcon2/conhost/consrv.h -#define RIPMSG0(flags, msg) Telemetry::Instance().LogRipMessage(msg); -#define RIPMSG1(flags, msg, a) Telemetry::Instance().LogRipMessage(msg, a); -#define RIPMSG2(flags, msg, a, b) Telemetry::Instance().LogRipMessage(msg, a, b); -#define RIPMSG3(flags, msg, a, b, c) Telemetry::Instance().LogRipMessage(msg, a, b, c); -#define RIPMSG4(flags, msg, a, b, c, d) Telemetry::Instance().LogRipMessage(msg, a, b, c, d); +#endif diff --git a/src/host/tracing.cpp b/src/host/tracing.cpp index 998410cf247..14a4db03e4c 100644 --- a/src/host/tracing.cpp +++ b/src/host/tracing.cpp @@ -194,15 +194,12 @@ void Tracing::s_TraceConsoleAttachDetach(_In_ ConsoleProcessHandle* const pConso { if (TraceLoggingProviderEnabled(g_hConhostV2EventTraceProvider, 0, TraceKeywords::ConsoleAttachDetach)) { - auto bIsUserInteractive = Telemetry::Instance().IsUserInteractive(); - TraceLoggingWrite( g_hConhostV2EventTraceProvider, "ConsoleAttachDetach", TraceLoggingPid(pConsoleProcessHandle->dwProcessId, "AttachedProcessId"), TraceLoggingFileTime(pConsoleProcessHandle->GetProcessCreationTime(), "AttachedProcessCreationTime"), TraceLoggingBool(bIsAttach, "IsAttach"), - TraceLoggingBool(bIsUserInteractive, "IsUserInteractive"), TraceLoggingKeyword(TIL_KEYWORD_TRACE), TraceLoggingKeyword(TraceKeywords::ConsoleAttachDetach)); } diff --git a/src/interactivity/win32/consoleKeyInfo.cpp b/src/interactivity/win32/consoleKeyInfo.cpp index 5aa0f18a85a..87a6231c58e 100644 --- a/src/interactivity/win32/consoleKeyInfo.cpp +++ b/src/interactivity/win32/consoleKeyInfo.cpp @@ -42,7 +42,7 @@ void StoreKeyInfo(_In_ PMSG msg) } else { - RIPMSG0(RIP_WARNING, "ConsoleKeyInfo buffer is full"); + LOG_HR_MSG(E_FAIL, "ConsoleKeyInfo buffer is full"); } } diff --git a/src/interactivity/win32/window.cpp b/src/interactivity/win32/window.cpp index 5b0dde18949..3f802740588 100644 --- a/src/interactivity/win32/window.cpp +++ b/src/interactivity/win32/window.cpp @@ -325,7 +325,7 @@ void Window::_UpdateSystemMetrics() const if (hWnd == nullptr) { const auto gle = GetLastError(); - RIPMSG1(RIP_WARNING, "CreateWindow failed with gle = 0x%x", gle); + LOG_WIN32_MSG(gle, "CreateWindow failed"); status = NTSTATUS_FROM_WIN32(gle); } @@ -738,8 +738,6 @@ void Window::VerticalScroll(const WORD wScrollCommand, const WORD wAbsoluteChang auto& ScreenInfo = GetScreenInfo(); // Log a telemetry event saying the user interacted with the Console - Telemetry::Instance().SetUserInteractive(); - const auto& viewport = ScreenInfo.GetViewport(); NewOrigin = viewport.Origin(); @@ -818,8 +816,6 @@ void Window::VerticalScroll(const WORD wScrollCommand, const WORD wAbsoluteChang void Window::HorizontalScroll(const WORD wScrollCommand, const WORD wAbsoluteChange) { // Log a telemetry event saying the user interacted with the Console - Telemetry::Instance().SetUserInteractive(); - auto& ScreenInfo = GetScreenInfo(); const auto sScreenBufferSizeX = ScreenInfo.GetBufferSize().Width(); const auto& viewport = ScreenInfo.GetViewport(); diff --git a/src/interactivity/win32/windowio.cpp b/src/interactivity/win32/windowio.cpp index 5866400a730..94d7f3b2a86 100644 --- a/src/interactivity/win32/windowio.cpp +++ b/src/interactivity/win32/windowio.cpp @@ -135,16 +135,6 @@ void HandleKeyEvent(const HWND hWnd, const BOOL bKeyDown = WI_IsFlagClear(lParam, KEY_TRANSITION_UP); const bool IsCharacterMessage = (Message == WM_CHAR || Message == WM_SYSCHAR || Message == WM_DEADCHAR || Message == WM_SYSDEADCHAR); - if (bKeyDown) - { - // Log a telemetry flag saying the user interacted with the Console - // Only log when the key is a down press. Otherwise we're getting many calls with - // Message = WM_CHAR, VirtualKeyCode = VK_TAB, with bKeyDown = false - // when nothing is happening, or the user has merely clicked on the title bar, and - // this can incorrectly mark the session as being interactive. - Telemetry::Instance().SetUserInteractive(); - } - // Make sure we retrieve the key info first, or we could chew up // unneeded space in the key info table if we bail out early. if (IsCharacterMessage) @@ -213,38 +203,6 @@ void HandleKeyEvent(const HWND hWnd, const INPUT_KEY_INFO inputKeyInfo(VirtualKeyCode, ControlKeyState); - // Capture telemetry on Ctrl+Shift+ C or V commands - if (IsInProcessedInputMode()) - { - // Capture telemetry data when a user presses ctrl+shift+c or v in processed mode - if (inputKeyInfo.IsShiftAndCtrlOnly()) - { - if (VirtualKeyCode == 'V') - { - Telemetry::Instance().LogCtrlShiftVProcUsed(); - } - else if (VirtualKeyCode == 'C') - { - Telemetry::Instance().LogCtrlShiftCProcUsed(); - } - } - } - else - { - // Capture telemetry data when a user presses ctrl+shift+c or v in raw mode - if (inputKeyInfo.IsShiftAndCtrlOnly()) - { - if (VirtualKeyCode == 'V') - { - Telemetry::Instance().LogCtrlShiftVRawUsed(); - } - else if (VirtualKeyCode == 'C') - { - Telemetry::Instance().LogCtrlShiftCRawUsed(); - } - } - } - // If this is a key up message, should we ignore it? We do this so that if a process reads a line from the input // buffer, the key up event won't get put in the buffer after the read completes. if (gci.Flags & CONSOLE_IGNORE_NEXT_KEYUP) @@ -265,7 +223,6 @@ void HandleKeyEvent(const HWND hWnd, { case 'V': // the user is attempting to paste from the clipboard - Telemetry::Instance().SetKeyboardTextEditingUsed(); Clipboard::Instance().Paste(); return; } @@ -278,8 +235,6 @@ void HandleKeyEvent(const HWND hWnd, switch (VirtualKeyCode) { case 'A': - // Set Text Selection using keyboard to true for telemetry - Telemetry::Instance().SetKeyboardTextSelectionUsed(); // the user is asking to select all pSelection->SelectAll(); return; @@ -293,8 +248,6 @@ void HandleKeyEvent(const HWND hWnd, Selection::Instance().InitializeMarkSelection(); return; case 'V': - // the user is attempting to paste from the clipboard - Telemetry::Instance().SetKeyboardTextEditingUsed(); Clipboard::Instance().Paste(); return; case VK_HOME: @@ -307,10 +260,6 @@ void HandleKeyEvent(const HWND hWnd, return; } break; - case VK_PRIOR: - case VK_NEXT: - Telemetry::Instance().SetCtrlPgUpPgDnUsed(); - break; } } @@ -469,9 +418,6 @@ BOOL HandleSysKeyEvent(const HWND hWnd, const UINT Message, const WPARAM wParam, VirtualKeyCode = LOWORD(wParam); } - // Log a telemetry flag saying the user interacted with the Console - Telemetry::Instance().SetUserInteractive(); - // check for ctrl-esc const auto bCtrlDown = OneCoreSafeGetKeyState(VK_CONTROL) & KEY_PRESSED; @@ -568,12 +514,6 @@ BOOL HandleMouseEvent(const SCREEN_INFORMATION& ScreenInfo, const LPARAM lParam) { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (Message != WM_MOUSEMOVE) - { - // Log a telemetry flag saying the user interacted with the Console - Telemetry::Instance().SetUserInteractive(); - } - const auto pSelection = &Selection::Instance(); if (!(gci.Flags & CONSOLE_HAS_FOCUS) && !pSelection->IsMouseButtonDown()) @@ -804,31 +744,12 @@ BOOL HandleMouseEvent(const SCREEN_INFORMATION& ScreenInfo, { if (pSelection->IsInSelectingState()) { - // Capture data on when quick edit copy is used in proc or raw mode - if (IsInProcessedInputMode()) - { - Telemetry::Instance().LogQuickEditCopyProcUsed(); - } - else - { - Telemetry::Instance().LogQuickEditCopyRawUsed(); - } // If the ALT key is held, also select HTML as well as plain text. const auto fAlsoCopyFormatting = WI_IsFlagSet(OneCoreSafeGetKeyState(VK_MENU), KEY_PRESSED); Clipboard::Instance().Copy(fAlsoCopyFormatting); } else if (gci.Flags & CONSOLE_QUICK_EDIT_MODE) { - // Capture data on when quick edit paste is used in proc or raw mode - if (IsInProcessedInputMode()) - { - Telemetry::Instance().LogQuickEditPasteProcUsed(); - } - else - { - Telemetry::Instance().LogQuickEditPasteRawUsed(); - } - Clipboard::Instance().Paste(); } gci.Flags |= CONSOLE_IGNORE_NEXT_MOUSE_INPUT; @@ -911,7 +832,7 @@ BOOL HandleMouseEvent(const SCREEN_INFORMATION& ScreenInfo, EventFlags = MOUSE_HWHEELED; break; default: - RIPMSG1(RIP_ERROR, "Invalid message 0x%x", Message); + LOG_HR_MSG(E_INVALIDARG, "Invalid message 0x%x", Message); ButtonFlags = 0; EventFlags = 0; break; @@ -968,7 +889,7 @@ NTSTATUS InitWindowsSubsystem(_Out_ HHOOK* phhook) if (FAILED_NTSTATUS(Status)) { - RIPMSG2(RIP_WARNING, "CreateWindowsWindow failed with status 0x%x, gle = 0x%x", Status, GetLastError()); + LOG_NTSTATUS_MSG(Status, "CreateWindowsWindow failed"); return Status; } diff --git a/src/interactivity/win32/windowproc.cpp b/src/interactivity/win32/windowproc.cpp index 376e93fe031..f215a166119 100644 --- a/src/interactivity/win32/windowproc.cpp +++ b/src/interactivity/win32/windowproc.cpp @@ -163,12 +163,6 @@ using namespace Microsoft::Console::Types; case WM_SIZING: { - // Signal that the user changed the window size, so we can return the value later for telemetry. By only - // sending the data back if the size has changed, helps reduce the amount of telemetry being sent back. - // WM_SIZING doesn't fire if they resize the window using Win-UpArrow, so we'll miss that scenario. We could - // listen to the WM_SIZE message instead, but they can fire when the window is being restored from being - // minimized, and not only when they resize the window. - Telemetry::Instance().SetWindowSizeChanged(); goto CallDefWin; break; } @@ -322,11 +316,6 @@ using namespace Microsoft::Console::Types; case WM_CLOSE: { - // Write the final trace log during the WM_CLOSE message while the console process is still fully alive. - // This gives us time to query the process for information. We shouldn't really miss any useful - // telemetry between now and when the process terminates. - Telemetry::Instance().WriteFinalTraceLog(); - _CloseWindow(); break; } @@ -471,7 +460,6 @@ using namespace Microsoft::Console::Types; case WM_CONTEXTMENU: { - Telemetry::Instance().SetContextMenuUsed(); if (DefWindowProcW(hWnd, WM_NCHITTEST, 0, lParam) == HTCLIENT) { auto hHeirMenu = Menu::s_GetHeirMenuHandle(); @@ -865,12 +853,6 @@ void Window::_HandleDrop(const WPARAM wParam) const if (DragQueryFile((HDROP)wParam, 0, szPath, ARRAYSIZE(szPath)) != 0) { - // Log a telemetry flag saying the user interacted with the Console - // Only log when DragQueryFile succeeds, because if we don't when the console starts up, we're seeing - // _HandleDrop get called multiple times (and DragQueryFile fail), - // which can incorrectly mark this console session as interactive. - Telemetry::Instance().SetUserInteractive(); - fAddQuotes = (wcschr(szPath, L' ') != nullptr); if (fAddQuotes) { diff --git a/src/server/ApiDispatchers.cpp b/src/server/ApiDispatchers.cpp index 2449b128742..0aa25057591 100644 --- a/src/server/ApiDispatchers.cpp +++ b/src/server/ApiDispatchers.cpp @@ -42,12 +42,10 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) if (a->Output) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleOutputCP); m->_pApiRoutines->GetConsoleOutputCodePageImpl(a->CodePage); } else { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleCP); m->_pApiRoutines->GetConsoleInputCodePageImpl(a->CodePage); } return S_OK; @@ -56,7 +54,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerGetConsoleMode(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleMode); const auto a = &m->u.consoleMsgL1.GetConsoleMode; const auto pObjectHandle = m->GetObjectHandle(); @@ -84,7 +81,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerSetConsoleMode(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::SetConsoleMode); const auto a = &m->u.consoleMsgL1.SetConsoleMode; const auto pObjectHandle = m->GetObjectHandle(); @@ -112,7 +108,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerGetNumberOfInputEvents(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetNumberOfConsoleInputEvents); const auto a = &m->u.consoleMsgL1.GetNumberOfConsoleInputEvents; const auto pObjectHandle = m->GetObjectHandle(); @@ -130,15 +125,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) *pbReplyPending = FALSE; const auto a = &m->u.consoleMsgL1.GetConsoleInput; - if (WI_IsFlagSet(a->Flags, CONSOLE_READ_NOREMOVE)) - { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::PeekConsoleInput, a->Unicode); - } - else - { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::ReadConsoleInput, a->Unicode); - } - a->NumRecords = 0; // If any flags are set that are not within our enum, it's invalid. @@ -229,7 +215,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) *pbReplyPending = FALSE; const auto a = &m->u.consoleMsgL1.ReadConsole; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::ReadConsole, a->Unicode); a->NumBytes = 0; // we return 0 until proven otherwise. @@ -345,7 +330,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) *pbReplyPending = FALSE; const auto a = &m->u.consoleMsgL1.WriteConsole; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::WriteConsole, a->Unicode); // Make sure we have a valid screen buffer. auto HandleData = m->GetObjectHandle(); @@ -424,21 +408,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) _Inout_ BOOL* const /*pbReplyPending*/) { const auto a = &m->u.consoleMsgL2.FillConsoleOutput; - - switch (a->ElementType) - { - case CONSOLE_ATTRIBUTE: - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::FillConsoleOutputAttribute); - break; - case CONSOLE_ASCII: - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::FillConsoleOutputCharacter, false); - break; - case CONSOLE_REAL_UNICODE: - case CONSOLE_FALSE_UNICODE: - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::FillConsoleOutputCharacter, true); - break; - } - // Capture length of initial fill. size_t fill = a->Length; @@ -498,7 +467,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerSetConsoleActiveScreenBuffer(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::SetConsoleActiveScreenBuffer); const auto pObjectHandle = m->GetObjectHandle(); RETURN_HR_IF_NULL(E_HANDLE, pObjectHandle); @@ -512,7 +480,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerFlushConsoleInputBuffer(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::FlushConsoleInputBuffer); const auto pObjectHandle = m->GetObjectHandle(); RETURN_HR_IF_NULL(E_HANDLE, pObjectHandle); @@ -530,12 +497,10 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) if (a->Output) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::SetConsoleOutputCP); return m->_pApiRoutines->SetConsoleOutputCodePageImpl(a->CodePage); } else { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::SetConsoleCP); return m->_pApiRoutines->SetConsoleInputCodePageImpl(a->CodePage); } } @@ -543,7 +508,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerGetConsoleCursorInfo(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleCursorInfo); const auto a = &m->u.consoleMsgL2.GetConsoleCursorInfo; const auto pObjectHandle = m->GetObjectHandle(); @@ -561,7 +525,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerSetConsoleCursorInfo(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::SetConsoleCursorInfo); const auto a = &m->u.consoleMsgL2.SetConsoleCursorInfo; const auto pObjectHandle = m->GetObjectHandle(); @@ -576,7 +539,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerGetConsoleScreenBufferInfo(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleScreenBufferInfoEx); const auto a = &m->u.consoleMsgL2.GetConsoleScreenBufferInfo; CONSOLE_SCREEN_BUFFER_INFOEX ex = { 0 }; @@ -609,7 +571,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerSetConsoleScreenBufferInfo(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::SetConsoleScreenBufferInfoEx); const auto a = &m->u.consoleMsgL2.SetConsoleScreenBufferInfo; const auto pObjectHandle = m->GetObjectHandle(); @@ -646,7 +607,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerSetConsoleScreenBufferSize(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::SetConsoleScreenBufferSize); const auto a = &m->u.consoleMsgL2.SetConsoleScreenBufferSize; const auto pObjectHandle = m->GetObjectHandle(); @@ -665,7 +625,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerSetConsoleCursorPosition(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::SetConsoleCursorPosition); const auto a = &m->u.consoleMsgL2.SetConsoleCursorPosition; const auto pObjectHandle = m->GetObjectHandle(); @@ -680,7 +639,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerGetLargestConsoleWindowSize(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetLargestConsoleWindowSize); const auto a = &m->u.consoleMsgL2.GetLargestConsoleWindowSize; const auto pObjectHandle = m->GetObjectHandle(); @@ -698,7 +656,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) _Inout_ BOOL* const /*pbReplyPending*/) { const auto a = &m->u.consoleMsgL2.ScrollConsoleScreenBuffer; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::ScrollConsoleScreenBuffer, a->Unicode); const auto pObjectHandle = m->GetObjectHandle(); RETURN_HR_IF_NULL(E_HANDLE, pObjectHandle); @@ -732,7 +689,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerSetConsoleTextAttribute(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::SetConsoleTextAttribute); const auto a = &m->u.consoleMsgL2.SetConsoleTextAttribute; const auto pObjectHandle = m->GetObjectHandle(); @@ -751,7 +707,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerSetConsoleWindowInfo(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::SetConsoleWindowInfo); const auto a = &m->u.consoleMsgL2.SetConsoleWindowInfo; const auto pObjectHandle = m->GetObjectHandle(); @@ -777,21 +732,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) RETURN_HR_IF(E_ACCESSDENIED, !m->GetProcessHandle()->GetPolicy().CanReadOutputBuffer()); const auto a = &m->u.consoleMsgL2.ReadConsoleOutputString; - - switch (a->StringType) - { - case CONSOLE_ATTRIBUTE: - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::ReadConsoleOutputAttribute); - break; - case CONSOLE_ASCII: - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::ReadConsoleOutputCharacter, false); - break; - case CONSOLE_REAL_UNICODE: - case CONSOLE_FALSE_UNICODE: - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::ReadConsoleOutputCharacter, true); - break; - } - a->NumRecords = 0; // Set to 0 records returned in case we have failures. PVOID pvBuffer; @@ -843,8 +783,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) { const auto a = &m->u.consoleMsgL2.WriteConsoleInput; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::WriteConsoleInput, a->Unicode); - a->NumRecords = 0; RETURN_HR_IF(E_ACCESSDENIED, !m->GetProcessHandle()->GetPolicy().CanWriteInputBuffer()); @@ -880,8 +818,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) { const auto a = &m->u.consoleMsgL2.WriteConsoleOutput; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::WriteConsoleOutput, a->Unicode); - // Backup originalRegion and set the written area to a 0 size rectangle in case of failures. const auto originalRegion = Microsoft::Console::Types::Viewport::FromInclusive(til::wrap_small_rect(a->CharRegion)); auto writtenRegion = Microsoft::Console::Types::Viewport::FromDimensions(originalRegion.Origin(), { 0, 0 }); @@ -923,21 +859,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) _Inout_ BOOL* const /*pbReplyPending*/) { const auto a = &m->u.consoleMsgL2.WriteConsoleOutputString; - - switch (a->StringType) - { - case CONSOLE_ATTRIBUTE: - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::WriteConsoleOutputAttribute); - break; - case CONSOLE_ASCII: - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::WriteConsoleOutputCharacter, false); - break; - case CONSOLE_REAL_UNICODE: - case CONSOLE_FALSE_UNICODE: - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::WriteConsoleOutputCharacter, true); - break; - } - // Set written records to 0 in case we early return. a->NumRecords = 0; @@ -1022,8 +943,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) const auto a = &m->u.consoleMsgL2.ReadConsoleOutput; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::ReadConsoleOutput, a->Unicode); - // Backup data region passed and set it to a zero size region in case we exit early for failures. const auto originalRegion = Microsoft::Console::Types::Viewport::FromInclusive(til::wrap_small_rect(a->CharRegion)); const auto zeroRegion = Microsoft::Console::Types::Viewport::FromDimensions(originalRegion.Origin(), { 0, 0 }); @@ -1076,7 +995,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) _Inout_ BOOL* const /*pbReplyPending*/) { const auto a = &m->u.consoleMsgL2.GetConsoleTitle; - Telemetry::Instance().LogApiCall(a->Original ? Telemetry::ApiCall::GetConsoleOriginalTitle : Telemetry::ApiCall::GetConsoleTitle, a->Unicode); PVOID pvBuffer; ULONG cbBuffer; @@ -1133,7 +1051,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) _Inout_ BOOL* const /*pbReplyPending*/) { const auto a = &m->u.consoleMsgL2.SetConsoleTitle; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::SetConsoleTitle, a->Unicode); PVOID pvBuffer; ULONG cbOriginalLength; @@ -1155,7 +1072,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerGetConsoleMouseInfo(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetNumberOfConsoleMouseButtons); const auto a = &m->u.consoleMsgL3.GetConsoleMouseInfo; m->_pApiRoutines->GetNumberOfConsoleMouseButtonsImpl(a->NumButtons); @@ -1165,7 +1081,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerGetConsoleFontSize(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleFontSize); const auto a = &m->u.consoleMsgL3.GetConsoleFontSize; const auto pObjectHandle = m->GetObjectHandle(); @@ -1182,7 +1097,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerGetConsoleCurrentFont(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetCurrentConsoleFontEx); const auto a = &m->u.consoleMsgL3.GetCurrentConsoleFont; const auto pObjectHandle = m->GetObjectHandle(); @@ -1208,7 +1122,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerSetConsoleDisplayMode(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::SetConsoleDisplayMode); const auto a = &m->u.consoleMsgL3.SetConsoleDisplayMode; const auto pObjectHandle = m->GetObjectHandle(); @@ -1225,7 +1138,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerGetConsoleDisplayMode(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleDisplayMode); const auto a = &m->u.consoleMsgL3.GetConsoleDisplayMode; // Historically this has never checked the handles. It just returns global state. @@ -1238,7 +1150,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) _Inout_ BOOL* const /*pbReplyPending*/) { const auto a = &m->u.consoleMsgL3.AddConsoleAliasW; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::AddConsoleAlias, a->Unicode); // Read the input buffer and validate the strings. PVOID pvBuffer; @@ -1286,7 +1197,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) _Inout_ BOOL* const /*pbReplyPending*/) { const auto a = &m->u.consoleMsgL3.GetConsoleAliasW; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleAlias, a->Unicode); PVOID pvInputBuffer; ULONG cbInputBufferSize; @@ -1360,7 +1270,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) _Inout_ BOOL* const /*pbReplyPending*/) { const auto a = &m->u.consoleMsgL3.GetConsoleAliasesLengthW; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleAliasesLength, a->Unicode); ULONG cbExeNameLength; PVOID pvExeName; @@ -1393,7 +1302,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) _Inout_ BOOL* const /*pbReplyPending*/) { const auto a = &m->u.consoleMsgL3.GetConsoleAliasExesLengthW; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleAliasExesLength, a->Unicode); size_t cbAliasExesLength; if (a->Unicode) @@ -1418,7 +1326,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) _Inout_ BOOL* const /*pbReplyPending*/) { const auto a = &m->u.consoleMsgL3.GetConsoleAliasesW; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleAliases, a->Unicode); PVOID pvExeName; ULONG cbExeNameLength; @@ -1462,7 +1369,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) _Inout_ BOOL* const /*pbReplyPending*/) { const auto a = &m->u.consoleMsgL3.GetConsoleAliasExesW; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleAliasExes, a->Unicode); PVOID pvBuffer; ULONG cbAliasExesBufferLength; @@ -1621,7 +1527,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerGetConsoleWindow(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleWindow); const auto a = &m->u.consoleMsgL3.GetConsoleWindow; m->_pApiRoutines->GetConsoleWindowImpl(a->hwnd); @@ -1631,7 +1536,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerGetConsoleSelectionInfo(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleSelectionInfo); const auto a = &m->u.consoleMsgL3.GetConsoleSelectionInfo; m->_pApiRoutines->GetConsoleSelectionInfoImpl(a->SelectionInfo); @@ -1642,7 +1546,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) _Inout_ BOOL* const /*pbReplyPending*/) { const auto a = &m->u.consoleMsgL3.GetConsoleHistory; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleHistoryInfo); CONSOLE_HISTORY_INFO info; info.cbSize = sizeof(info); @@ -1660,7 +1563,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) _Inout_ BOOL* const /*pbReplyPending*/) { const auto a = &m->u.consoleMsgL3.SetConsoleHistory; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::SetConsoleHistoryInfo); CONSOLE_HISTORY_INFO info; info.cbSize = sizeof(info); @@ -1674,7 +1576,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerSetConsoleCurrentFont(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::SetCurrentConsoleFontEx); const auto a = &m->u.consoleMsgL3.SetCurrentConsoleFont; const auto pObjectHandle = m->GetObjectHandle(); diff --git a/src/server/ApiDispatchersInternal.cpp b/src/server/ApiDispatchersInternal.cpp index 062cb5e5aa7..bff2a62e8d2 100644 --- a/src/server/ApiDispatchersInternal.cpp +++ b/src/server/ApiDispatchersInternal.cpp @@ -27,7 +27,6 @@ using Microsoft::Console::Interactivity::ServiceLocator; { const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); const auto a = &m->u.consoleMsgL3.GetConsoleProcessList; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleProcessList); PVOID Buffer; ULONG BufferSize; @@ -60,7 +59,6 @@ using Microsoft::Console::Interactivity::ServiceLocator; _Inout_ BOOL* const /*pbReplyPending*/) { const auto a = &m->u.consoleMsgL1.GetConsoleLangId; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleLangId); // TODO: MSFT: 9115192 - This should probably just ask through GetOutputCP and convert it ourselves on this side. return m->_pApiRoutines->GetConsoleLangIdImpl(a->LangId); @@ -71,7 +69,6 @@ using Microsoft::Console::Interactivity::ServiceLocator; { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); const auto a = &m->u.consoleMsgL2.GenerateConsoleCtrlEvent; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GenerateConsoleCtrlEvent); LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); diff --git a/src/server/IoDispatchers.cpp b/src/server/IoDispatchers.cpp index fb3f025c95c..a2c3b175a38 100644 --- a/src/server/IoDispatchers.cpp +++ b/src/server/IoDispatchers.cpp @@ -413,7 +413,6 @@ PCONSOLE_API_MSG IoDispatchers::ConsoleHandleConnectionRequest(_In_ PCONSOLE_API { auto& Globals = ServiceLocator::LocateGlobals(); auto& gci = Globals.getConsoleInformation(); - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::AttachConsole); ConsoleProcessHandle* ProcessData = nullptr; NTSTATUS Status; @@ -562,8 +561,6 @@ PCONSOLE_API_MSG IoDispatchers::ConsoleHandleConnectionRequest(_In_ PCONSOLE_API // - A pointer to the reply message. PCONSOLE_API_MSG IoDispatchers::ConsoleClientDisconnectRoutine(_In_ PCONSOLE_API_MSG pMessage) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::FreeConsole); - const auto pProcessData = pMessage->GetProcessHandle(); auto pNotifier = ServiceLocator::LocateAccessibilityNotifier(); diff --git a/src/server/ProcessHandle.cpp b/src/server/ProcessHandle.cpp index 51b6f221c6b..bcfd43d1928 100644 --- a/src/server/ProcessHandle.cpp +++ b/src/server/ProcessHandle.cpp @@ -30,10 +30,6 @@ ConsoleProcessHandle::ConsoleProcessHandle(const DWORD dwProcessId, _shimPolicy(_hProcess.get()), _processCreationTime{} { - if (nullptr != _hProcess.get()) - { - Telemetry::Instance().LogProcessConnected(_hProcess.get()); - } } // Routine Description: From a5c269b2801a42f833f7a611ab7c624648f37054 Mon Sep 17 00:00:00 2001 From: Taha Haksal <91548254+TahaHaksal@users.noreply.github.com> Date: Tue, 7 Nov 2023 01:42:56 +0300 Subject: [PATCH 039/167] Added selectionBackground to light color schemes (#16243) Add a selectionBackground property which is set to the scheme's brightBlack too all 3 of the light color schemes. Related to #8716 It does not close the bug because as mentioned in the issue, when you input numbers, they seem to be invisible in the light color schemes and selecting them with the cursor doesn't reveal them. --- src/cascadia/TerminalSettingsModel/defaults.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/cascadia/TerminalSettingsModel/defaults.json b/src/cascadia/TerminalSettingsModel/defaults.json index cdd5d6e78dd..360f37afaad 100644 --- a/src/cascadia/TerminalSettingsModel/defaults.json +++ b/src/cascadia/TerminalSettingsModel/defaults.json @@ -174,6 +174,7 @@ "foreground": "#383A42", "background": "#FAFAFA", "cursorColor": "#4F525D", + "selectionBackground": "#4F525D", "black": "#383A42", "red": "#E45649", "green": "#50A14F", @@ -218,6 +219,7 @@ "foreground": "#657B83", "background": "#FDF6E3", "cursorColor": "#002B36", + "selectionBackground": "#073642", "black": "#002B36", "red": "#DC322F", "green": "#859900", @@ -262,6 +264,7 @@ "foreground": "#555753", "background": "#FFFFFF", "cursorColor": "#000000", + "selectionBackground": "#555753", "black": "#000000", "red": "#CC0000", "green": "#4E9A06", From 59dcbbe0e94814b94a7bfc5ae189d36c4bb0436f Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Mon, 6 Nov 2023 16:45:24 -0600 Subject: [PATCH 040/167] Another theoretical fix for a crash (#16267) For history: > This is MSFT:46763065 internally. Dumps show this repros on 1.19 too. > > This was previously #16061 which had a theoretical fix in #16065. Looks like you're on Terminal Stable v1.18.2822.0, and https://github.com/microsoft/terminal/releases/tag/v1.18.2822.0 is supposed to have had that fix in it. Dang. > well this is embarrassing ... I never actually checked if we _still had a `_window`_. We're alive, yay! But we're still in the middle of refrigerating. So, there's no HWND anymore Attempt to fix this by actually ensuring there's a `_window` in `AppHost::_WindowInitializedHandler` Closes #16235 --- src/cascadia/WindowsTerminal/AppHost.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 5af3a80d50a..7cae9959998 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -979,7 +979,8 @@ winrt::Windows::Foundation::IAsyncOperation AppHost::_GetWindowL co_await wil::resume_foreground(_windowLogic.GetRoot().Dispatcher()); const auto strongThis = weakThis.lock(); - if (!strongThis) + // GH #16235: If we don't have a window logic, we're already refrigerating, and won't have our _window either. + if (!strongThis || _windowLogic == nullptr) { co_return layoutJson; } @@ -1267,7 +1268,8 @@ winrt::fire_and_forget AppHost::_QuitRequested(const winrt::Windows::Foundation: co_await wil::resume_foreground(_windowLogic.GetRoot().Dispatcher()); const auto strongThis = weakThis.lock(); - if (!strongThis) + // GH #16235: If we don't have a window logic, we're already refrigerating, and won't have our _window either. + if (!strongThis || _windowLogic == nullptr) { co_return; } @@ -1431,7 +1433,7 @@ winrt::fire_and_forget AppHost::_WindowInitializedHandler(const winrt::Windows:: // If we're gone on the other side of this co_await, well, that's fine. Just bail. const auto strongThis = weakThis.lock(); - if (!strongThis) + if (!strongThis || _window == nullptr) { co_return; } From 7a8dd90294e53b9d5466ff755611d13f4249ce0b Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 7 Nov 2023 18:51:13 +0100 Subject: [PATCH 041/167] Fix tabs being printed in cmd.exe prompts (#16273) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A late change in #16105 wrapped `_buffer` into a class to better track its dirty state, but I failed to notice that in this one instance we intentionally manipulated `_buffer` without marking it as dirty. This fixes the issue by adding a call to `MarkAsClean()`. This changeset also adds the test instructions from #15783 as a document to this repository. I've extended the list with two bugs we've found in the implementation since then. ## Validation Steps Performed * In cmd.exe, with an empty prompt in an empty directory: Pressing tab produces an audible bing and prints no text ✅ --- doc/COOKED_READ_DATA.md | 90 +++++++++++++++++++++++++++++++++++++ src/host/readDataCooked.cpp | 7 ++- 2 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 doc/COOKED_READ_DATA.md diff --git a/doc/COOKED_READ_DATA.md b/doc/COOKED_READ_DATA.md new file mode 100644 index 00000000000..113a51bb749 --- /dev/null +++ b/doc/COOKED_READ_DATA.md @@ -0,0 +1,90 @@ +# COOKED_READ_DATA, aka conhost's readline implementation + +## Test instructions + +All of the following ✅ marks must be fulfilled during manual testing: +* ASCII input +* Chinese input (中文維基百科) ❔ + * Resizing the window properly wraps/unwraps wide glyphs ❌ + Broken due to `TextBuffer::Reflow` bugs +* Surrogate pair input (🙂) ❔ + * Resizing the window properly wraps/unwraps surrogate pairs ❌ + Broken due to `TextBuffer::Reflow` bugs +* In cmd.exe + * Create 2 file: "a😊b.txt" and "a😟b.txt" + * Press tab: Autocomplete to "a😊b.txt" ✅ + * Navigate the cursor right past the "a" + * Press tab twice: Autocomplete to "a😟b.txt" ✅ +* Backspace deletes preceding glyphs ✅ +* Ctrl+Backspace deletes preceding words ✅ +* Escape clears input ✅ +* Home navigates to start ✅ +* Ctrl+Home deletes text between cursor and start ✅ +* End navigates to end ✅ +* Ctrl+End deletes text between cursor and end ✅ +* Left navigates over previous code points ✅ +* Ctrl+Left navigates to previous word-starts ✅ +* Right and F1 navigate over next code points ✅ + * Pressing right at the end of input copies characters + from the previous command ✅ +* Ctrl+Right navigates to next word-ends ✅ +* Insert toggles overwrite mode ✅ +* Delete deletes next code point ✅ +* Up and F5 cycle through history ✅ + * Doesn't crash with no history ✅ + * Stops at first entry ✅ +* Down cycles through history ✅ + * Doesn't crash with no history ✅ + * Stops at last entry ✅ +* PageUp retrieves the oldest command ✅ +* PageDown retrieves the newest command ✅ +* F2 starts "copy to char" prompt ✅ + * Escape dismisses prompt ✅ + * Typing a character copies text from the previous command up + until that character into the current buffer (acts identical + to F3, but with automatic character search) ✅ +* F3 copies the previous command into the current buffer, + starting at the current cursor position, + for as many characters as possible ✅ + * Doesn't erase trailing text if the current buffer + is longer than the previous command ✅ + * Puts the cursor at the end of the copied text ✅ +* F4 starts "copy from char" prompt ✅ + * Escape dismisses prompt ✅ + * Erases text between the current cursor position and the + first instance of a given char (but not including it) ✅ +* F6 inserts Ctrl+Z ✅ +* F7 without modifiers starts "command list" prompt ✅ + * Escape dismisses prompt ✅ + * Minimum size of 40x10 characters ✅ + * Width expands to fit the widest history command ✅ + * Height expands up to 20 rows with longer histories ✅ + * F9 starts "command number" prompt ✅ + * Left/Right paste replace the buffer with the given command ✅ + * And put cursor at the end of the buffer ✅ + * Up/Down navigate selection through history ✅ + * Stops at start/end with <10 entries ✅ + * Stops at start/end with >20 entries ✅ + * Wide text rendering during pagination with >20 entries ✅ + * Shift+Up/Down moves history items around ✅ + * Home navigates to first entry ✅ + * End navigates to last entry ✅ + * PageUp navigates by 20 items at a time or to first ✅ + * PageDown navigates by 20 items at a time or to last ✅ +* Alt+F7 clears command history ✅ +* F8 cycles through commands that start with the same text as + the current buffer up until the current cursor position ✅ + * Doesn't crash with no history ✅ +* F9 starts "command number" prompt ✅ + * Escape dismisses prompt ✅ + * Ignores non-ASCII-decimal characters ✅ + * Allows entering between 1 and 5 digits ✅ + * Pressing Enter fetches the given command from the history ✅ +* Alt+F10 clears doskey aliases ✅ +* In cmd.exe, with an empty prompt in an empty directory: + Pressing tab produces an audible bing and prints no text ✅ +* When Narrator is enabled, in cmd.exe: + * Typing individual characters announces only + exactly each character that is being typed ✅ + * Backspacing at the end of a prompt announces + only exactly each deleted character ✅ diff --git a/src/host/readDataCooked.cpp b/src/host/readDataCooked.cpp index 3644dfe78db..c842bab312d 100644 --- a/src/host/readDataCooked.cpp +++ b/src/host/readDataCooked.cpp @@ -436,14 +436,17 @@ void COOKED_READ_DATA::_handleChar(wchar_t wch, const DWORD modifiers) if (_ctrlWakeupMask != 0 && wch < L' ' && (_ctrlWakeupMask & (1 << wch))) { - _flushBuffer(); - // The old implementation (all the way since the 90s) overwrote the character at the current cursor position with the given wch. // But simultaneously it incremented the buffer length, which would have only worked if it was written at the end of the buffer. // Press tab past the "f" in the string "foo" and you'd get "f\to " (a trailing whitespace; the initial contents of the buffer back then). // It's unclear whether the original intention was to write at the end of the buffer at all times or to implement an insert mode. // I went with insert mode. + // + // It is important that we don't actually print that character out though, as it's only for the calling application to see. + // That's why we flush the contents before the insertion and then ensure that the _flushBuffer() call in Read() exits early. + _flushBuffer(); _buffer.Replace(_buffer.GetCursorPosition(), 0, &wch, 1); + _buffer.MarkAsClean(); _controlKeyState = modifiers; _transitionState(State::DoneWithWakeupMask); From 646edc7f8f112ea96c6706e230887fcddce5c983 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 31 Oct 2023 14:25:41 +0100 Subject: [PATCH 042/167] AtlasEngine: Minor bug fixes (#16219) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit fixes 4 minor bugs: * Forgot to set the maximum swap chain latency. Without it, it defaults to up to 3 frames of latency. We don't need this, because our renderer is simple and fast and is expected to draw frames within <1ms. * ClearType treats the alpha channel as ignored, whereas custom shaders can manipulate the alpha channel freely. This meant that using both simultaneously would produce weird effects, like text having black background. We now force grayscale AA instead. * The builtin retro shader should not be effected by the previous point. * When the cbuffer is entirely unused in a custom shader, it has so far resulted in constant redraws. This happened because the D3D reflection `GetDesc` call will then return `E_FAIL` in this situation. The new code on the other hand will now assume that a failure to get the description is equal to the variable being unused. Closes #15960 ## Validation Steps Performed * A custom passthrough shader works with grayscale and ClearType AA while also changing the opacity with Ctrl+Shift+Scroll ✅ * Same for the builtin retro shader, but ClearType works ✅ * The passthrough shader doesn't result in constant redrawing ✅ (cherry picked from commit 0289cb043c3aac0f9199f667550d8811022e9bed) Service-Card-Id: 90915277 Service-Version: 1.19 --- src/renderer/atlas/AtlasEngine.api.cpp | 14 +++++++------- src/renderer/atlas/AtlasEngine.r.cpp | 4 +++- src/renderer/atlas/BackendD3D.cpp | 20 ++++++++++++-------- src/renderer/atlas/common.h | 2 +- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/renderer/atlas/AtlasEngine.api.cpp b/src/renderer/atlas/AtlasEngine.api.cpp index 4459c97a8e5..c810dd115f6 100644 --- a/src/renderer/atlas/AtlasEngine.api.cpp +++ b/src/renderer/atlas/AtlasEngine.api.cpp @@ -490,20 +490,20 @@ void AtlasEngine::UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept void AtlasEngine::_resolveTransparencySettings() noexcept { + // An opaque background allows us to use true "independent" flips. See AtlasEngine::_createSwapChain(). + // We can't enable them if custom shaders are specified, because it's unknown, whether they support opaque inputs. + const bool useAlpha = _api.enableTransparentBackground || !_api.s->misc->customPixelShaderPath.empty(); // If the user asks for ClearType, but also for a transparent background // (which our ClearType shader doesn't simultaneously support) // then we need to sneakily force the renderer to grayscale AA. - const auto antialiasingMode = _api.enableTransparentBackground && _api.antialiasingMode == AntialiasingMode::ClearType ? AntialiasingMode::Grayscale : _api.antialiasingMode; - const bool enableTransparentBackground = _api.enableTransparentBackground || !_api.s->misc->customPixelShaderPath.empty() || _api.s->misc->useRetroTerminalEffect; + const auto antialiasingMode = useAlpha && _api.antialiasingMode == AntialiasingMode::ClearType ? AntialiasingMode::Grayscale : _api.antialiasingMode; - if (antialiasingMode != _api.s->font->antialiasingMode || enableTransparentBackground != _api.s->target->enableTransparentBackground) + if (antialiasingMode != _api.s->font->antialiasingMode || useAlpha != _api.s->target->useAlpha) { const auto s = _api.s.write(); s->font.write()->antialiasingMode = antialiasingMode; - // An opaque background allows us to use true "independent" flips. See AtlasEngine::_createSwapChain(). - // We can't enable them if custom shaders are specified, because it's unknown, whether they support opaque inputs. - s->target.write()->enableTransparentBackground = enableTransparentBackground; - _api.backgroundOpaqueMixin = enableTransparentBackground ? 0x00000000 : 0xff000000; + s->target.write()->useAlpha = useAlpha; + _api.backgroundOpaqueMixin = useAlpha ? 0x00000000 : 0xff000000; } } diff --git a/src/renderer/atlas/AtlasEngine.r.cpp b/src/renderer/atlas/AtlasEngine.r.cpp index e7070b708e6..1fac8f0e834 100644 --- a/src/renderer/atlas/AtlasEngine.r.cpp +++ b/src/renderer/atlas/AtlasEngine.r.cpp @@ -329,7 +329,7 @@ void AtlasEngine::_createSwapChain() .SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, // If our background is opaque we can enable "independent" flips by setting DXGI_ALPHA_MODE_IGNORE. // As our swap chain won't have to compose with DWM anymore it reduces the display latency dramatically. - .AlphaMode = _p.s->target->enableTransparentBackground ? DXGI_ALPHA_MODE_PREMULTIPLIED : DXGI_ALPHA_MODE_IGNORE, + .AlphaMode = _p.s->target->useAlpha ? DXGI_ALPHA_MODE_PREMULTIPLIED : DXGI_ALPHA_MODE_IGNORE, .Flags = swapChainFlags, }; @@ -360,6 +360,8 @@ void AtlasEngine::_createSwapChain() _p.swapChain.targetSize = _p.s->targetSize; _p.swapChain.waitForPresentation = true; + LOG_IF_FAILED(_p.swapChain.swapChain->SetMaximumFrameLatency(1)); + WaitUntilCanRender(); if (_p.swapChainChangedCallback) diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index 846567ca613..19334b3a9d4 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -403,23 +403,24 @@ void BackendD3D::_recreateCustomShader(const RenderingPayload& p) /* ppCode */ blob.addressof(), /* ppErrorMsgs */ error.addressof()); - // Unless we can determine otherwise, assume this shader requires evaluation every frame - _requiresContinuousRedraw = true; - if (SUCCEEDED(hr)) { - THROW_IF_FAILED(p.device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, _customPixelShader.put())); + THROW_IF_FAILED(p.device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, _customPixelShader.addressof())); // Try to determine whether the shader uses the Time variable wil::com_ptr reflector; - if (SUCCEEDED_LOG(D3DReflect(blob->GetBufferPointer(), blob->GetBufferSize(), IID_PPV_ARGS(reflector.put())))) + if (SUCCEEDED_LOG(D3DReflect(blob->GetBufferPointer(), blob->GetBufferSize(), IID_PPV_ARGS(reflector.addressof())))) { + // Depending on the version of the d3dcompiler_*.dll, the next two functions either return nullptr + // on failure or an instance of CInvalidSRConstantBuffer or CInvalidSRVariable respectively, + // which cause GetDesc() to return E_FAIL. In other words, we have to assume that any failure in the + // next few lines indicates that the cbuffer is entirely unused (--> _requiresContinuousRedraw=false). if (ID3D11ShaderReflectionConstantBuffer* constantBufferReflector = reflector->GetConstantBufferByIndex(0)) // shader buffer { if (ID3D11ShaderReflectionVariable* variableReflector = constantBufferReflector->GetVariableByIndex(0)) // time { D3D11_SHADER_VARIABLE_DESC variableDescriptor; - if (SUCCEEDED_LOG(variableReflector->GetDesc(&variableDescriptor))) + if (SUCCEEDED(variableReflector->GetDesc(&variableDescriptor))) { // only if time is used _requiresContinuousRedraw = WI_IsFlagSet(variableDescriptor.uFlags, D3D_SVF_USED); @@ -427,6 +428,11 @@ void BackendD3D::_recreateCustomShader(const RenderingPayload& p) } } } + else + { + // Unless we can determine otherwise, assume this shader requires evaluation every frame + _requiresContinuousRedraw = true; + } } else { @@ -447,8 +453,6 @@ void BackendD3D::_recreateCustomShader(const RenderingPayload& p) else if (p.s->misc->useRetroTerminalEffect) { THROW_IF_FAILED(p.device->CreatePixelShader(&custom_shader_ps[0], sizeof(custom_shader_ps), nullptr, _customPixelShader.put())); - // We know the built-in retro shader doesn't require continuous redraw. - _requiresContinuousRedraw = false; } if (_customPixelShader) diff --git a/src/renderer/atlas/common.h b/src/renderer/atlas/common.h index bb46ff61547..94434bffc9c 100644 --- a/src/renderer/atlas/common.h +++ b/src/renderer/atlas/common.h @@ -313,7 +313,7 @@ namespace Microsoft::Console::Render::Atlas struct TargetSettings { HWND hwnd = nullptr; - bool enableTransparentBackground = false; + bool useAlpha = false; bool useSoftwareRendering = false; }; From a608a9157160cf1b57c4d3eb486f5cb85e42bae9 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 6 Nov 2023 22:30:03 +0100 Subject: [PATCH 043/167] Fix the fix for the fix of nearby font loading (#16196) I still don't know how to reproduce it properly, but I'm slowly wrapping my head around how and why it happens. The issue isn't that `FindFamilyName` fails with `exists=FALSE`, but rather that any of the followup calls like `GetDesignGlyphMetrics` fails, which results in an exception and subsequently in an orderly fallback to Consolas. I've always thought that the issue is that even with the nearby font collection we get an `exists=FALSE`... I'm not sure why I thought that. This changeset also drops the fallback iteration for Lucida Console and Courier New, because I felt like the code looks neater that way and I think it's a reasonable expectation that Consolas is always installed. Closes #16058 (cherry picked from commit 9e86c9811f2cf0ad53c9c543b886274b1079dd00) Service-Card-Id: 90885607 Service-Version: 1.19 --- src/renderer/atlas/AtlasEngine.api.cpp | 65 ++++++++++++-------------- src/renderer/atlas/common.h | 1 - src/renderer/dx/DxFontInfo.cpp | 18 +++---- 3 files changed, 40 insertions(+), 44 deletions(-) diff --git a/src/renderer/atlas/AtlasEngine.api.cpp b/src/renderer/atlas/AtlasEngine.api.cpp index c810dd115f6..0e2f7941688 100644 --- a/src/renderer/atlas/AtlasEngine.api.cpp +++ b/src/renderer/atlas/AtlasEngine.api.cpp @@ -455,30 +455,34 @@ void AtlasEngine::SetWarningCallback(std::function pfn) noexcept [[nodiscard]] HRESULT AtlasEngine::UpdateFont(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map& features, const std::unordered_map& axes) noexcept { - static constexpr std::array fallbackFaceNames{ static_cast(nullptr), L"Consolas", L"Lucida Console", L"Courier New" }; - auto it = fallbackFaceNames.begin(); - const auto end = fallbackFaceNames.end(); + try + { + _updateFont(fontInfoDesired.GetFaceName().c_str(), fontInfoDesired, fontInfo, features, axes); + return S_OK; + } + CATCH_LOG(); - for (;;) + if constexpr (Feature_NearbyFontLoading::IsEnabled()) { try { - _updateFont(*it, fontInfoDesired, fontInfo, features, axes); + // _resolveFontMetrics() checks `_api.s->font->fontCollection` for a pre-existing font collection, + // before falling back to using the system font collection. This way we can inject our custom one. See GH#9375. + // Doing it this way is a bit hacky, but it does have the benefit that we can cache a font collection + // instance across font changes, like when zooming the font size rapidly using the scroll wheel. + _api.s.write()->font.write()->fontCollection = FontCache::GetCached(); + _updateFont(fontInfoDesired.GetFaceName().c_str(), fontInfoDesired, fontInfo, features, axes); return S_OK; } - catch (...) - { - ++it; - if (it == end) - { - RETURN_CAUGHT_EXCEPTION(); - } - else - { - LOG_CAUGHT_EXCEPTION(); - } - } + CATCH_LOG(); } + + try + { + _updateFont(nullptr, fontInfoDesired, fontInfo, features, axes); + return S_OK; + } + CATCH_RETURN(); } void AtlasEngine::UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept @@ -598,11 +602,7 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo if (!requestedFaceName) { - requestedFaceName = fontInfoDesired.GetFaceName().c_str(); - if (!requestedFaceName) - { - requestedFaceName = L"Consolas"; - } + requestedFaceName = L"Consolas"; } if (!requestedSize.height) { @@ -614,22 +614,19 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo requestedWeight = DWRITE_FONT_WEIGHT_NORMAL; } - wil::com_ptr fontCollection; - THROW_IF_FAILED(_p.dwriteFactory->GetSystemFontCollection(fontCollection.addressof(), FALSE)); + // UpdateFont() (and its NearbyFontLoading feature path specifically) sets `_api.s->font->fontCollection` + // to a custom font collection that includes .ttf files that are bundled with our app package. See GH#9375. + // Doing it this way is a bit hacky, but it does have the benefit that we can cache a font collection + // instance across font changes, like when zooming the font size rapidly using the scroll wheel. + auto fontCollection = _api.s->font->fontCollection; + if (!fontCollection) + { + THROW_IF_FAILED(_p.dwriteFactory->GetSystemFontCollection(fontCollection.addressof(), FALSE)); + } u32 index = 0; BOOL exists = false; THROW_IF_FAILED(fontCollection->FindFamilyName(requestedFaceName, &index, &exists)); - - if constexpr (Feature_NearbyFontLoading::IsEnabled()) - { - if (!exists) - { - fontCollection = FontCache::GetCached(); - THROW_IF_FAILED(fontCollection->FindFamilyName(requestedFaceName, &index, &exists)); - } - } - THROW_HR_IF(DWRITE_E_NOFONT, !exists); wil::com_ptr fontFamily; diff --git a/src/renderer/atlas/common.h b/src/renderer/atlas/common.h index 94434bffc9c..b8fa3ac0d59 100644 --- a/src/renderer/atlas/common.h +++ b/src/renderer/atlas/common.h @@ -470,7 +470,6 @@ namespace Microsoft::Console::Render::Atlas wil::com_ptr systemFontFallback; wil::com_ptr systemFontFallback1; // optional, might be nullptr wil::com_ptr textAnalyzer; - wil::com_ptr renderingParams; std::function warningCallback; std::function swapChainChangedCallback; diff --git a/src/renderer/dx/DxFontInfo.cpp b/src/renderer/dx/DxFontInfo.cpp index 4945939fd86..b1d4844a601 100644 --- a/src/renderer/dx/DxFontInfo.cpp +++ b/src/renderer/dx/DxFontInfo.cpp @@ -127,15 +127,6 @@ void DxFontInfo::SetFromEngine(const std::wstring_view familyName, { face = _FindFontFace(localeName); - if constexpr (Feature_NearbyFontLoading::IsEnabled()) - { - if (!face) - { - _fontCollection = FontCache::GetCached(); - face = _FindFontFace(localeName); - } - } - if (!face) { // If we missed, try looking a little more by trimming the last word off the requested family name a few times. @@ -167,6 +158,15 @@ void DxFontInfo::SetFromEngine(const std::wstring_view familyName, } CATCH_LOG(); + if constexpr (Feature_NearbyFontLoading::IsEnabled()) + { + if (!face) + { + _fontCollection = FontCache::GetCached(); + face = _FindFontFace(localeName); + } + } + // Alright, if our quick shot at trimming didn't work either... // move onto looking up a font from our hard-coded list of fonts // that should really always be available. From 95e4d0bbe0c633859a0ba288d060ed22d917f64a Mon Sep 17 00:00:00 2001 From: Taha Haksal <91548254+TahaHaksal@users.noreply.github.com> Date: Tue, 7 Nov 2023 01:42:56 +0300 Subject: [PATCH 044/167] Added selectionBackground to light color schemes (#16243) Add a selectionBackground property which is set to the scheme's brightBlack too all 3 of the light color schemes. Related to #8716 It does not close the bug because as mentioned in the issue, when you input numbers, they seem to be invisible in the light color schemes and selecting them with the cursor doesn't reveal them. (cherry picked from commit a5c269b2801a42f833f7a611ab7c624648f37054) Service-Card-Id: 91033167 Service-Version: 1.19 --- src/cascadia/TerminalSettingsModel/defaults.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/cascadia/TerminalSettingsModel/defaults.json b/src/cascadia/TerminalSettingsModel/defaults.json index cdd5d6e78dd..360f37afaad 100644 --- a/src/cascadia/TerminalSettingsModel/defaults.json +++ b/src/cascadia/TerminalSettingsModel/defaults.json @@ -174,6 +174,7 @@ "foreground": "#383A42", "background": "#FAFAFA", "cursorColor": "#4F525D", + "selectionBackground": "#4F525D", "black": "#383A42", "red": "#E45649", "green": "#50A14F", @@ -218,6 +219,7 @@ "foreground": "#657B83", "background": "#FDF6E3", "cursorColor": "#002B36", + "selectionBackground": "#073642", "black": "#002B36", "red": "#DC322F", "green": "#859900", @@ -262,6 +264,7 @@ "foreground": "#555753", "background": "#FFFFFF", "cursorColor": "#000000", + "selectionBackground": "#555753", "black": "#000000", "red": "#CC0000", "green": "#4E9A06", From 83a2bc181a154e744b8b2a76bc1d7301a8d07917 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Mon, 6 Nov 2023 16:45:24 -0600 Subject: [PATCH 045/167] Another theoretical fix for a crash (#16267) For history: > This is MSFT:46763065 internally. Dumps show this repros on 1.19 too. > > This was previously #16061 which had a theoretical fix in #16065. Looks like you're on Terminal Stable v1.18.2822.0, and https://github.com/microsoft/terminal/releases/tag/v1.18.2822.0 is supposed to have had that fix in it. Dang. > well this is embarrassing ... I never actually checked if we _still had a `_window`_. We're alive, yay! But we're still in the middle of refrigerating. So, there's no HWND anymore Attempt to fix this by actually ensuring there's a `_window` in `AppHost::_WindowInitializedHandler` Closes #16235 (cherry picked from commit 59dcbbe0e94814b94a7bfc5ae189d36c4bb0436f) Service-Card-Id: 91041359 Service-Version: 1.19 --- src/cascadia/WindowsTerminal/AppHost.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 5af3a80d50a..7cae9959998 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -979,7 +979,8 @@ winrt::Windows::Foundation::IAsyncOperation AppHost::_GetWindowL co_await wil::resume_foreground(_windowLogic.GetRoot().Dispatcher()); const auto strongThis = weakThis.lock(); - if (!strongThis) + // GH #16235: If we don't have a window logic, we're already refrigerating, and won't have our _window either. + if (!strongThis || _windowLogic == nullptr) { co_return layoutJson; } @@ -1267,7 +1268,8 @@ winrt::fire_and_forget AppHost::_QuitRequested(const winrt::Windows::Foundation: co_await wil::resume_foreground(_windowLogic.GetRoot().Dispatcher()); const auto strongThis = weakThis.lock(); - if (!strongThis) + // GH #16235: If we don't have a window logic, we're already refrigerating, and won't have our _window either. + if (!strongThis || _windowLogic == nullptr) { co_return; } @@ -1431,7 +1433,7 @@ winrt::fire_and_forget AppHost::_WindowInitializedHandler(const winrt::Windows:: // If we're gone on the other side of this co_await, well, that's fine. Just bail. const auto strongThis = weakThis.lock(); - if (!strongThis) + if (!strongThis || _window == nullptr) { co_return; } From db27348e27e9989eaae75c61517aa52921390c12 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 7 Nov 2023 18:51:13 +0100 Subject: [PATCH 046/167] Fix tabs being printed in cmd.exe prompts (#16273) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A late change in #16105 wrapped `_buffer` into a class to better track its dirty state, but I failed to notice that in this one instance we intentionally manipulated `_buffer` without marking it as dirty. This fixes the issue by adding a call to `MarkAsClean()`. This changeset also adds the test instructions from #15783 as a document to this repository. I've extended the list with two bugs we've found in the implementation since then. ## Validation Steps Performed * In cmd.exe, with an empty prompt in an empty directory: Pressing tab produces an audible bing and prints no text ✅ (cherry picked from commit 7a8dd90294e53b9d5466ff755611d13f4249ce0b) Service-Card-Id: 91033502 Service-Version: 1.19 --- doc/COOKED_READ_DATA.md | 90 +++++++++++++++++++++++++++++++++++++ src/host/readDataCooked.cpp | 7 ++- 2 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 doc/COOKED_READ_DATA.md diff --git a/doc/COOKED_READ_DATA.md b/doc/COOKED_READ_DATA.md new file mode 100644 index 00000000000..113a51bb749 --- /dev/null +++ b/doc/COOKED_READ_DATA.md @@ -0,0 +1,90 @@ +# COOKED_READ_DATA, aka conhost's readline implementation + +## Test instructions + +All of the following ✅ marks must be fulfilled during manual testing: +* ASCII input +* Chinese input (中文維基百科) ❔ + * Resizing the window properly wraps/unwraps wide glyphs ❌ + Broken due to `TextBuffer::Reflow` bugs +* Surrogate pair input (🙂) ❔ + * Resizing the window properly wraps/unwraps surrogate pairs ❌ + Broken due to `TextBuffer::Reflow` bugs +* In cmd.exe + * Create 2 file: "a😊b.txt" and "a😟b.txt" + * Press tab: Autocomplete to "a😊b.txt" ✅ + * Navigate the cursor right past the "a" + * Press tab twice: Autocomplete to "a😟b.txt" ✅ +* Backspace deletes preceding glyphs ✅ +* Ctrl+Backspace deletes preceding words ✅ +* Escape clears input ✅ +* Home navigates to start ✅ +* Ctrl+Home deletes text between cursor and start ✅ +* End navigates to end ✅ +* Ctrl+End deletes text between cursor and end ✅ +* Left navigates over previous code points ✅ +* Ctrl+Left navigates to previous word-starts ✅ +* Right and F1 navigate over next code points ✅ + * Pressing right at the end of input copies characters + from the previous command ✅ +* Ctrl+Right navigates to next word-ends ✅ +* Insert toggles overwrite mode ✅ +* Delete deletes next code point ✅ +* Up and F5 cycle through history ✅ + * Doesn't crash with no history ✅ + * Stops at first entry ✅ +* Down cycles through history ✅ + * Doesn't crash with no history ✅ + * Stops at last entry ✅ +* PageUp retrieves the oldest command ✅ +* PageDown retrieves the newest command ✅ +* F2 starts "copy to char" prompt ✅ + * Escape dismisses prompt ✅ + * Typing a character copies text from the previous command up + until that character into the current buffer (acts identical + to F3, but with automatic character search) ✅ +* F3 copies the previous command into the current buffer, + starting at the current cursor position, + for as many characters as possible ✅ + * Doesn't erase trailing text if the current buffer + is longer than the previous command ✅ + * Puts the cursor at the end of the copied text ✅ +* F4 starts "copy from char" prompt ✅ + * Escape dismisses prompt ✅ + * Erases text between the current cursor position and the + first instance of a given char (but not including it) ✅ +* F6 inserts Ctrl+Z ✅ +* F7 without modifiers starts "command list" prompt ✅ + * Escape dismisses prompt ✅ + * Minimum size of 40x10 characters ✅ + * Width expands to fit the widest history command ✅ + * Height expands up to 20 rows with longer histories ✅ + * F9 starts "command number" prompt ✅ + * Left/Right paste replace the buffer with the given command ✅ + * And put cursor at the end of the buffer ✅ + * Up/Down navigate selection through history ✅ + * Stops at start/end with <10 entries ✅ + * Stops at start/end with >20 entries ✅ + * Wide text rendering during pagination with >20 entries ✅ + * Shift+Up/Down moves history items around ✅ + * Home navigates to first entry ✅ + * End navigates to last entry ✅ + * PageUp navigates by 20 items at a time or to first ✅ + * PageDown navigates by 20 items at a time or to last ✅ +* Alt+F7 clears command history ✅ +* F8 cycles through commands that start with the same text as + the current buffer up until the current cursor position ✅ + * Doesn't crash with no history ✅ +* F9 starts "command number" prompt ✅ + * Escape dismisses prompt ✅ + * Ignores non-ASCII-decimal characters ✅ + * Allows entering between 1 and 5 digits ✅ + * Pressing Enter fetches the given command from the history ✅ +* Alt+F10 clears doskey aliases ✅ +* In cmd.exe, with an empty prompt in an empty directory: + Pressing tab produces an audible bing and prints no text ✅ +* When Narrator is enabled, in cmd.exe: + * Typing individual characters announces only + exactly each character that is being typed ✅ + * Backspacing at the end of a prompt announces + only exactly each deleted character ✅ diff --git a/src/host/readDataCooked.cpp b/src/host/readDataCooked.cpp index 3644dfe78db..c842bab312d 100644 --- a/src/host/readDataCooked.cpp +++ b/src/host/readDataCooked.cpp @@ -436,14 +436,17 @@ void COOKED_READ_DATA::_handleChar(wchar_t wch, const DWORD modifiers) if (_ctrlWakeupMask != 0 && wch < L' ' && (_ctrlWakeupMask & (1 << wch))) { - _flushBuffer(); - // The old implementation (all the way since the 90s) overwrote the character at the current cursor position with the given wch. // But simultaneously it incremented the buffer length, which would have only worked if it was written at the end of the buffer. // Press tab past the "f" in the string "foo" and you'd get "f\to " (a trailing whitespace; the initial contents of the buffer back then). // It's unclear whether the original intention was to write at the end of the buffer at all times or to implement an insert mode. // I went with insert mode. + // + // It is important that we don't actually print that character out though, as it's only for the calling application to see. + // That's why we flush the contents before the insertion and then ensure that the _flushBuffer() call in Read() exits early. + _flushBuffer(); _buffer.Replace(_buffer.GetCursorPosition(), 0, &wch, 1); + _buffer.MarkAsClean(); _controlKeyState = modifiers; _transitionState(State::DoneWithWakeupMask); From 077d63e6a34e392bb497d59768c551f7c099baaf Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 7 Nov 2023 14:35:16 -0600 Subject: [PATCH 047/167] Defer package updates while the Terminal is running (#16250) Adds ```xml defer ``` to our `Package.Properties` for all our packages. This was added in the September 2023 OS release of Windows 11. Apparently, this just works now? I did update VS, but I don't _think_ that updated the SDK. I have no idea how it updated the manifest definitions. Closes #3915 Closes #6726 --- src/cascadia/CascadiaPackage/Package-Can.appxmanifest | 4 +++- src/cascadia/CascadiaPackage/Package-Dev.appxmanifest | 4 +++- src/cascadia/CascadiaPackage/Package-Pre.appxmanifest | 4 +++- src/cascadia/CascadiaPackage/Package.appxmanifest | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/cascadia/CascadiaPackage/Package-Can.appxmanifest b/src/cascadia/CascadiaPackage/Package-Can.appxmanifest index 04c7960ce95..66564e799ff 100644 --- a/src/cascadia/CascadiaPackage/Package-Can.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Can.appxmanifest @@ -8,13 +8,14 @@ xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3" xmlns:uap4="http://schemas.microsoft.com/appx/manifest/uap/windows10/4" xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5" + xmlns:uap17="http://schemas.microsoft.com/appx/manifest/uap/windows10/17" xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4" xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5" xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" xmlns:virtualization="http://schemas.microsoft.com/appx/manifest/virtualization/windows10" - IgnorableNamespaces="uap mp rescap uap3 desktop6 virtualization"> + IgnorableNamespaces="uap mp rescap uap3 uap17 desktop6 virtualization"> HKEY_CURRENT_USER\Console\%%Startup + defer diff --git a/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest b/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest index 4a0735bb206..d8ceefaeef6 100644 --- a/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest @@ -7,6 +7,7 @@ xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3" xmlns:uap4="http://schemas.microsoft.com/appx/manifest/uap/windows10/4" + xmlns:uap17="http://schemas.microsoft.com/appx/manifest/uap/windows10/17" xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4" xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5" @@ -14,7 +15,7 @@ xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" xmlns:virtualization="http://schemas.microsoft.com/appx/manifest/virtualization/windows10" xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5" - IgnorableNamespaces="uap mp rescap uap3 desktop6 virtualization"> + IgnorableNamespaces="uap mp rescap uap3 uap17 desktop6 virtualization"> HKEY_CURRENT_USER\Console\%%Startup + defer diff --git a/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest b/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest index 98fb12b9455..3a7bf26e0f7 100644 --- a/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest @@ -9,13 +9,14 @@ xmlns:uap4="http://schemas.microsoft.com/appx/manifest/uap/windows10/4" xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5" xmlns:uap7="http://schemas.microsoft.com/appx/manifest/uap/windows10/7" + xmlns:uap17="http://schemas.microsoft.com/appx/manifest/uap/windows10/17" xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4" xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5" xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" xmlns:virtualization="http://schemas.microsoft.com/appx/manifest/virtualization/windows10" - IgnorableNamespaces="uap mp rescap uap3 desktop6 virtualization"> + IgnorableNamespaces="uap mp rescap uap3 uap17 desktop6 virtualization"> HKEY_CURRENT_USER\Console\%%Startup + defer diff --git a/src/cascadia/CascadiaPackage/Package.appxmanifest b/src/cascadia/CascadiaPackage/Package.appxmanifest index c123778d1e9..f04863da27c 100644 --- a/src/cascadia/CascadiaPackage/Package.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package.appxmanifest @@ -9,13 +9,14 @@ xmlns:uap4="http://schemas.microsoft.com/appx/manifest/uap/windows10/4" xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5" xmlns:uap7="http://schemas.microsoft.com/appx/manifest/uap/windows10/7" + xmlns:uap17="http://schemas.microsoft.com/appx/manifest/uap/windows10/17" xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4" xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5" xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" xmlns:virtualization="http://schemas.microsoft.com/appx/manifest/virtualization/windows10" - IgnorableNamespaces="uap mp rescap uap3 desktop6 virtualization"> + IgnorableNamespaces="uap mp rescap uap3 uap17 desktop6 virtualization"> HKEY_CURRENT_USER\Console\%%Startup + defer From 71a1a97a9aa7b23a0c0f21b2f9e437b051cb5238 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 8 Nov 2023 17:28:07 +0100 Subject: [PATCH 048/167] Fix deadlocks due to holding locks across WriteFile calls (#16224) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes a number of bugs introduced in 4370da9, all of which are of the same kind: Holding the terminal lock across `WriteFile` calls into the ConPTY pipe. This is problematic, because the pipe has a tiny buffer size of just 4KiB and ConPTY may respond on its output pipe, before the entire buffer given to `WriteFile` has been emptied. When the ConPTY output thread then tries to acquire the terminal lock to begin parsing the VT output, we get ourselves a proper deadlock (cross process too!). The solution is to tease `Terminal` further apart into code that is thread-safe and code that isn't. Functions like `SendKeyEvent` so far have mixed them into one, because when they get called by `ControlCore` they both, processed the data (not thread-safe as it accesses VT state) and also sent that data back into `ControlCore` through a callback which then indirectly called into the `ConptyConnection` which calls `WriteFile`. Instead, we now return the data that needs to be sent from these functions, and `ControlCore` is free to release the lock and then call into the connection, which may then block indefinitely. ## Validation Steps Performed * Start nvim in WSL * Press `i` to enter the regular Insert mode * Paste 1MB of text * Doesn't deadlock ✅ --- src/cascadia/TerminalControl/ControlCore.cpp | 162 ++++++++++++------ src/cascadia/TerminalControl/HwndTerminal.cpp | 69 +++++--- src/cascadia/TerminalCore/ITerminalInput.hpp | 9 +- src/cascadia/TerminalCore/Terminal.cpp | 81 ++++----- src/cascadia/TerminalCore/Terminal.hpp | 25 +-- .../ConptyRoundtripTests.cpp | 2 +- .../UnitTests_TerminalCore/InputTest.cpp | 43 ++--- .../ScreenSizeLimitsTest.cpp | 16 +- .../UnitTests_TerminalCore/ScrollTest.cpp | 2 +- .../UnitTests_TerminalCore/SelectionTest.cpp | 44 ++--- .../TerminalApiTest.cpp | 20 +-- .../TerminalBufferTests.cpp | 2 +- 12 files changed, 270 insertions(+), 205 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 7925fc16eb8..734e1b98d2a 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -10,11 +10,11 @@ #include #include +#include #include #include #include "EventArgs.h" -#include "../../types/inc/GlyphWidth.hpp" #include "../../buffer/out/search.h" #include "../../renderer/atlas/AtlasEngine.h" #include "../../renderer/dx/DxRenderer.hpp" @@ -443,6 +443,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - void ControlCore::_sendInputToConnection(std::wstring_view wstr) { + if (wstr.empty()) + { + return; + } + + // The connection may call functions like WriteFile() which may block indefinitely. + // It's important we don't hold any mutexes across such calls. + _terminal->_assertUnlocked(); + if (_isReadOnly) { _raiseReadOnlyWarning(); @@ -492,8 +501,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation _handleControlC(); } - const auto lock = _terminal->LockForWriting(); - return _terminal->SendCharEvent(ch, scanCode, modifiers); + TerminalInput::OutputType out; + { + const auto lock = _terminal->LockForReading(); + out = _terminal->SendCharEvent(ch, scanCode, modifiers); + } + if (out) + { + _sendInputToConnection(*out); + return true; + } + return false; } void ControlCore::_handleControlC() @@ -602,46 +620,56 @@ namespace winrt::Microsoft::Terminal::Control::implementation const ControlKeyStates modifiers, const bool keyDown) { - const auto lock = _terminal->LockForWriting(); + if (!vkey) + { + return true; + } - // Update the selection, if it's present - // GH#8522, GH#3758 - Only modify the selection on key _down_. If we - // modify on key up, then there's chance that we'll immediately dismiss - // a selection created by an action bound to a keydown. - if (_shouldTryUpdateSelection(vkey) && keyDown) + TerminalInput::OutputType out; { - // try to update the selection - if (const auto updateSlnParams{ _terminal->ConvertKeyEventToUpdateSelectionParams(modifiers, vkey) }) - { - _terminal->UpdateSelection(updateSlnParams->first, updateSlnParams->second, modifiers); - _updateSelectionUI(); - return true; - } + const auto lock = _terminal->LockForWriting(); - // GH#8791 - don't dismiss selection if Windows key was also pressed as a key-combination. - if (!modifiers.IsWinPressed()) + // Update the selection, if it's present + // GH#8522, GH#3758 - Only modify the selection on key _down_. If we + // modify on key up, then there's chance that we'll immediately dismiss + // a selection created by an action bound to a keydown. + if (_shouldTryUpdateSelection(vkey) && keyDown) { - _terminal->ClearSelection(); - _updateSelectionUI(); - } + // try to update the selection + if (const auto updateSlnParams{ _terminal->ConvertKeyEventToUpdateSelectionParams(modifiers, vkey) }) + { + _terminal->UpdateSelection(updateSlnParams->first, updateSlnParams->second, modifiers); + _updateSelectionUI(); + return true; + } - // When there is a selection active, escape should clear it and NOT flow through - // to the terminal. With any other keypress, it should clear the selection AND - // flow through to the terminal. - if (vkey == VK_ESCAPE) - { - return true; + // GH#8791 - don't dismiss selection if Windows key was also pressed as a key-combination. + if (!modifiers.IsWinPressed()) + { + _terminal->ClearSelection(); + _updateSelectionUI(); + } + + // When there is a selection active, escape should clear it and NOT flow through + // to the terminal. With any other keypress, it should clear the selection AND + // flow through to the terminal. + if (vkey == VK_ESCAPE) + { + return true; + } } - } - // If the terminal translated the key, mark the event as handled. - // This will prevent the system from trying to get the character out - // of it and sending us a CharacterReceived event. - return vkey ? _terminal->SendKeyEvent(vkey, - scanCode, - modifiers, - keyDown) : - true; + // If the terminal translated the key, mark the event as handled. + // This will prevent the system from trying to get the character out + // of it and sending us a CharacterReceived event. + out = _terminal->SendKeyEvent(vkey, scanCode, modifiers, keyDown); + } + if (out) + { + _sendInputToConnection(*out); + return true; + } + return false; } bool ControlCore::SendMouseEvent(const til::point viewportPos, @@ -650,8 +678,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation const short wheelDelta, const TerminalInput::MouseButtonState state) { - const auto lock = _terminal->LockForWriting(); - return _terminal->SendMouseEvent(viewportPos, uiButton, states, wheelDelta, state); + TerminalInput::OutputType out; + { + const auto lock = _terminal->LockForReading(); + out = _terminal->SendMouseEvent(viewportPos, uiButton, states, wheelDelta, state); + } + if (out) + { + _sendInputToConnection(*out); + return true; + } + return false; } void ControlCore::UserScrollViewport(const int viewTop) @@ -1324,8 +1361,19 @@ namespace winrt::Microsoft::Terminal::Control::implementation // before sending it over the terminal's connection. void ControlCore::PasteText(const winrt::hstring& hstr) { + using namespace ::Microsoft::Console::Utils; + + auto filtered = FilterStringForPaste(hstr, CarriageReturnNewline | ControlCodes); + if (BracketedPasteEnabled()) + { + filtered.insert(0, L"\x1b[200~"); + filtered.append(L"\x1b[201~"); + } + + // It's important to not hold the terminal lock while calling this function as sending the data may take a long time. + _sendInputToConnection(filtered); + const auto lock = _terminal->LockForWriting(); - _terminal->WritePastedText(hstr); _terminal->ClearSelection(); _updateSelectionUI(); _terminal->TrySnapOnInput(); @@ -1894,17 +1942,27 @@ namespace winrt::Microsoft::Terminal::Control::implementation const auto endPoint = goRight ? clampedClick : cursorPos; const auto delta = _terminal->GetTextBuffer().GetCellDistance(startPoint, endPoint); - const WORD key = goRight ? VK_RIGHT : VK_LEFT; + + std::wstring buffer; + const auto append = [&](TerminalInput::OutputType&& out) { + if (out) + { + buffer.append(std::move(*out)); + } + }; + // Send an up and a down once per cell. This won't // accurately handle wide characters, or continuation // prompts, or cases where a single escape character in the // command (e.g. ^[) takes up two cells. for (size_t i = 0u; i < delta; i++) { - _terminal->SendKeyEvent(key, 0, {}, true); - _terminal->SendKeyEvent(key, 0, {}, false); + append(_terminal->SendKeyEvent(key, 0, {}, true)); + append(_terminal->SendKeyEvent(key, 0, {}, false)); } + + _sendInputToConnection(buffer); } } } @@ -1915,7 +1973,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - Updates the renderer's representation of the selection as well as the selection marker overlay in TermControl void ControlCore::_updateSelectionUI() { - const auto lock = _terminal->LockForWriting(); _renderer->TriggerSelection(); // only show the markers if we're doing a keyboard selection or in mark mode const bool showMarkers{ _terminal->SelectionMode() >= ::Microsoft::Terminal::Core::Terminal::SelectionInteractionMode::Keyboard }; @@ -2247,14 +2304,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation void ControlCore::_focusChanged(bool focused) { - // GH#13461 - temporarily turn off read-only mode, send the focus event, - // then turn it back on. Even in focus mode, focus events are fine to - // send. We don't want to pop a warning every time the control is - // focused. - const auto previous = std::exchange(_isReadOnly, false); - const auto restore = wil::scope_exit([&]() { _isReadOnly = previous; }); - const auto lock = _terminal->LockForWriting(); - _terminal->FocusChanged(focused); + TerminalInput::OutputType out; + { + const auto lock = _terminal->LockForReading(); + out = _terminal->FocusChanged(focused); + } + if (out && !out->empty()) + { + // _sendInputToConnection() asserts that we aren't in focus mode, + // but window focus events are always fine to send. + _connection.WriteInput(*out); + } } bool ControlCore::_isBackgroundTransparent() diff --git a/src/cascadia/TerminalControl/HwndTerminal.cpp b/src/cascadia/TerminalControl/HwndTerminal.cpp index a13ac5b3eda..6bec410cda8 100644 --- a/src/cascadia/TerminalControl/HwndTerminal.cpp +++ b/src/cascadia/TerminalControl/HwndTerminal.cpp @@ -272,7 +272,7 @@ void HwndTerminal::RegisterScrollCallback(std::function cal void HwndTerminal::_WriteTextToConnection(const std::wstring_view input) noexcept { - if (!_pfnWriteCallback) + if (input.empty() || !_pfnWriteCallback) { return; } @@ -758,8 +758,17 @@ try WI_IsFlagSet(GetKeyState(VK_RBUTTON), KeyPressed) }; - const auto lock = _terminal->LockForWriting(); - return _terminal->SendMouseEvent(cursorPosition / fontSize, uMsg, getControlKeyState(), wheelDelta, state); + TerminalInput::OutputType out; + { + const auto lock = _terminal->LockForReading(); + out = _terminal->SendMouseEvent(cursorPosition / fontSize, uMsg, getControlKeyState(), wheelDelta, state); + } + if (out) + { + _WriteTextToConnection(*out); + return true; + } + return false; } catch (...) { @@ -784,8 +793,16 @@ try { _uiaProvider->RecordKeyEvent(vkey); } - const auto lock = _terminal->LockForWriting(); - _terminal->SendKeyEvent(vkey, scanCode, modifiers, keyDown); + + TerminalInput::OutputType out; + { + const auto lock = _terminal->LockForReading(); + out = _terminal->SendKeyEvent(vkey, scanCode, modifiers, keyDown); + } + if (out) + { + _WriteTextToConnection(*out); + } } CATCH_LOG(); @@ -797,31 +814,39 @@ try return; } - const auto lock = _terminal->LockForWriting(); - - if (_terminal->IsSelectionActive()) + TerminalInput::OutputType out; { - _ClearSelection(); - if (ch == UNICODE_ESC) + const auto lock = _terminal->LockForWriting(); + + if (_terminal->IsSelectionActive()) { - // ESC should clear any selection before it triggers input. - // Other characters pass through. + _ClearSelection(); + if (ch == UNICODE_ESC) + { + // ESC should clear any selection before it triggers input. + // Other characters pass through. + return; + } + } + + if (ch == UNICODE_TAB) + { + // TAB was handled as a keydown event (cf. Terminal::SendKeyEvent) return; } - } - if (ch == UNICODE_TAB) - { - // TAB was handled as a keydown event (cf. Terminal::SendKeyEvent) - return; - } + auto modifiers = getControlKeyState(); + if (WI_IsFlagSet(flags, ENHANCED_KEY)) + { + modifiers |= ControlKeyStates::EnhancedKey; + } - auto modifiers = getControlKeyState(); - if (WI_IsFlagSet(flags, ENHANCED_KEY)) + out = _terminal->SendCharEvent(ch, scanCode, modifiers); + } + if (out) { - modifiers |= ControlKeyStates::EnhancedKey; + _WriteTextToConnection(*out); } - _terminal->SendCharEvent(ch, scanCode, modifiers); } CATCH_LOG(); diff --git a/src/cascadia/TerminalCore/ITerminalInput.hpp b/src/cascadia/TerminalCore/ITerminalInput.hpp index 480494de1f6..6adbb0374b5 100644 --- a/src/cascadia/TerminalCore/ITerminalInput.hpp +++ b/src/cascadia/TerminalCore/ITerminalInput.hpp @@ -16,9 +16,10 @@ namespace Microsoft::Terminal::Core ITerminalInput& operator=(const ITerminalInput&) = default; ITerminalInput& operator=(ITerminalInput&&) = default; - virtual bool SendKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states, const bool keyDown) = 0; - virtual bool SendMouseEvent(const til::point viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta, const Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state) = 0; - virtual bool SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) = 0; + virtual [[nodiscard]] ::Microsoft::Console::VirtualTerminal::TerminalInput::OutputType SendKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states, const bool keyDown) = 0; + virtual [[nodiscard]] ::Microsoft::Console::VirtualTerminal::TerminalInput::OutputType SendMouseEvent(const til::point viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta, const Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state) = 0; + virtual [[nodiscard]] ::Microsoft::Console::VirtualTerminal::TerminalInput::OutputType SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) = 0; + virtual [[nodiscard]] ::Microsoft::Console::VirtualTerminal::TerminalInput::OutputType FocusChanged(const bool focused) = 0; [[nodiscard]] virtual HRESULT UserResize(const til::size size) noexcept = 0; virtual void UserScrollViewport(const int viewTop) = 0; @@ -26,8 +27,6 @@ namespace Microsoft::Terminal::Core virtual void TrySnapOnInput() = 0; - virtual void FocusChanged(const bool focused) = 0; - protected: ITerminalInput() = default; }; diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index fe2417b1f97..3b086e3c143 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -30,6 +30,15 @@ Terminal::Terminal() _renderSettings.SetColorAlias(ColorAlias::DefaultBackground, TextColor::DEFAULT_BACKGROUND, RGB(0, 0, 0)); } +#pragma warning(suppress : 26455) // default constructor is throwing, too much effort to rearrange at this time. +Terminal::Terminal(TestDummyMarker) : + Terminal{} +{ +#ifndef NDEBUG + _suppressLockChecks = true; +#endif +} + void Terminal::Create(til::size viewportSize, til::CoordType scrollbackLines, Renderer& renderer) { _mutableViewport = Viewport::FromDimensions({ 0, 0 }, viewportSize); @@ -425,24 +434,6 @@ void Terminal::Write(std::wstring_view stringView) } } -void Terminal::WritePastedText(std::wstring_view stringView) -{ - const auto option = ::Microsoft::Console::Utils::FilterOption::CarriageReturnNewline | - ::Microsoft::Console::Utils::FilterOption::ControlCodes; - - auto filtered = ::Microsoft::Console::Utils::FilterStringForPaste(stringView, option); - if (IsXtermBracketedPasteModeEnabled()) - { - filtered.insert(0, L"\x1b[200~"); - filtered.append(L"\x1b[201~"); - } - - if (_pfnWriteInput) - { - _pfnWriteInput(filtered); - } -} - // Method Description: // - Attempts to snap to the bottom of the buffer, if SnapOnInput is true. Does // nothing if SnapOnInput is set to false, or we're already at the bottom of @@ -606,10 +597,10 @@ std::optional Terminal::GetHyperlinkIntervalFromViewportPos // Return Value: // - true if we translated the key event, and it should not be processed any further. // - false if we did not translate the key, and it should be processed into a character. -bool Terminal::SendKeyEvent(const WORD vkey, - const WORD scanCode, - const ControlKeyStates states, - const bool keyDown) +TerminalInput::OutputType Terminal::SendKeyEvent(const WORD vkey, + const WORD scanCode, + const ControlKeyStates states, + const bool keyDown) { // GH#6423 - don't snap on this key if the key that was pressed was a // modifier key. We'll wait for a real keystroke to snap to the bottom. @@ -627,7 +618,7 @@ bool Terminal::SendKeyEvent(const WORD vkey, // GH#7064 if (vkey == 0 || vkey >= 0xff) { - return false; + return {}; } // While not explicitly permitted, a wide range of software, including Windows' own touch keyboard, @@ -637,7 +628,7 @@ bool Terminal::SendKeyEvent(const WORD vkey, const auto sc = scanCode ? scanCode : _ScanCodeFromVirtualKey(vkey); if (sc == 0) { - return false; + return {}; } const auto isAltOnlyPressed = states.IsAltPressed() && !states.IsCtrlPressed(); @@ -665,11 +656,11 @@ bool Terminal::SendKeyEvent(const WORD vkey, // See the method description for more information. if (keyDown && !isAltOnlyPressed && vkey != VK_TAB && ch != UNICODE_NULL) { - return false; + return {}; } const auto keyEv = SynthesizeKeyEvent(keyDown, 1, vkey, sc, ch, states.Value()); - return _handleTerminalInputResult(_getTerminalInput().HandleKey(keyEv)); + return _getTerminalInput().HandleKey(keyEv); } // Method Description: @@ -686,14 +677,14 @@ bool Terminal::SendKeyEvent(const WORD vkey, // Return Value: // - true if we translated the key event, and it should not be processed any further. // - false if we did not translate the key, and it should be processed into a character. -bool Terminal::SendMouseEvent(til::point viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta, const TerminalInput::MouseButtonState state) +TerminalInput::OutputType Terminal::SendMouseEvent(til::point viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta, const TerminalInput::MouseButtonState state) { // GH#6401: VT applications should be able to receive mouse events from outside the // terminal buffer. This is likely to happen when the user drags the cursor offscreen. // We shouldn't throw away perfectly good events when they're offscreen, so we just // clamp them to be within the range [(0, 0), (W, H)]. _GetMutableViewport().ToOrigin().Clamp(viewportPos); - return _handleTerminalInputResult(_getTerminalInput().HandleMouse(viewportPos, uiButton, GET_KEYSTATE_WPARAM(states.Value()), wheelDelta, state)); + return _getTerminalInput().HandleMouse(viewportPos, uiButton, GET_KEYSTATE_WPARAM(states.Value()), wheelDelta, state); } // Method Description: @@ -708,7 +699,7 @@ bool Terminal::SendMouseEvent(til::point viewportPos, const unsigned int uiButto // Return Value: // - true if we translated the character event, and it should not be processed any further. // - false otherwise. -bool Terminal::SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) +TerminalInput::OutputType Terminal::SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) { auto vkey = _TakeVirtualKeyFromLastKeyEvent(scanCode); if (vkey == 0 && scanCode != 0) @@ -746,7 +737,7 @@ bool Terminal::SendCharEvent(const wchar_t ch, const WORD scanCode, const Contro } const auto keyDown = SynthesizeKeyEvent(true, 1, vkey, scanCode, ch, states.Value()); - return _handleTerminalInputResult(_getTerminalInput().HandleKey(keyDown)); + return _getTerminalInput().HandleKey(keyDown); } // Method Description: @@ -757,9 +748,9 @@ bool Terminal::SendCharEvent(const wchar_t ch, const WORD scanCode, const Contro // - focused: true if we're focused, false otherwise. // Return Value: // - none -void Terminal::FocusChanged(const bool focused) +TerminalInput::OutputType Terminal::FocusChanged(const bool focused) { - _handleTerminalInputResult(_getTerminalInput().HandleFocus(focused)); + return _getTerminalInput().HandleFocus(focused); } // Method Description: @@ -882,20 +873,6 @@ catch (...) return UNICODE_INVALID; } -[[maybe_unused]] bool Terminal::_handleTerminalInputResult(TerminalInput::OutputType&& out) const -{ - if (out) - { - const auto& str = *out; - if (_pfnWriteInput && !str.empty()) - { - _pfnWriteInput(str); - } - return true; - } - return false; -} - // Method Description: // - It's possible for a single scan code on a keyboard to // produce different key codes depending on the keyboard state. @@ -933,7 +910,7 @@ WORD Terminal::_TakeVirtualKeyFromLastKeyEvent(const WORD scanCode) noexcept void Terminal::_assertLocked() const noexcept { #ifndef NDEBUG - if (!_readWriteLock.is_locked()) + if (!_suppressLockChecks && !_readWriteLock.is_locked()) { // __debugbreak() has the benefit over assert() that the debugger jumps right here to this line. // That way there's no need to first click any dialogues, etc. The disadvantage of course is that the @@ -943,6 +920,16 @@ void Terminal::_assertLocked() const noexcept #endif } +void Terminal::_assertUnlocked() const noexcept +{ +#ifndef NDEBUG + if (!_suppressLockChecks && _readWriteLock.is_locked()) + { + __debugbreak(); + } +#endif +} + // Method Description: // - Acquire a read lock on the terminal. // Return Value: diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 72c34d2f629..3e2ffe4c083 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -60,6 +60,10 @@ class Microsoft::Terminal::Core::Terminal final : using RenderSettings = Microsoft::Console::Render::RenderSettings; public: + struct TestDummyMarker + { + }; + static constexpr bool IsInputKey(WORD vkey) { return vkey != VK_CONTROL && @@ -77,6 +81,7 @@ class Microsoft::Terminal::Core::Terminal final : } Terminal(); + Terminal(TestDummyMarker); void Create(til::size viewportSize, til::CoordType scrollbackLines, @@ -98,9 +103,8 @@ class Microsoft::Terminal::Core::Terminal final : // Write comes from the PTY and goes to our parser to be stored in the output buffer void Write(std::wstring_view stringView); - // WritePastedText comes from our input and goes back to the PTY's input channel - void WritePastedText(std::wstring_view stringView); - + void _assertLocked() const noexcept; + void _assertUnlocked() const noexcept; [[nodiscard]] std::unique_lock LockForReading() const noexcept; [[nodiscard]] std::unique_lock LockForWriting() noexcept; til::recursive_ticket_lock_suspension SuspendLock() noexcept; @@ -167,9 +171,10 @@ class Microsoft::Terminal::Core::Terminal final : #pragma region ITerminalInput // These methods are defined in Terminal.cpp - bool SendKeyEvent(const WORD vkey, const WORD scanCode, const Microsoft::Terminal::Core::ControlKeyStates states, const bool keyDown) override; - bool SendMouseEvent(const til::point viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta, const Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state) override; - bool SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) override; + [[nodiscard]] ::Microsoft::Console::VirtualTerminal::TerminalInput::OutputType SendKeyEvent(const WORD vkey, const WORD scanCode, const Microsoft::Terminal::Core::ControlKeyStates states, const bool keyDown) override; + [[nodiscard]] ::Microsoft::Console::VirtualTerminal::TerminalInput::OutputType SendMouseEvent(const til::point viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta, const Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state) override; + [[nodiscard]] ::Microsoft::Console::VirtualTerminal::TerminalInput::OutputType SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) override; + [[nodiscard]] ::Microsoft::Console::VirtualTerminal::TerminalInput::OutputType FocusChanged(const bool focused) override; [[nodiscard]] HRESULT UserResize(const til::size viewportSize) noexcept override; void UserScrollViewport(const int viewTop) override; @@ -179,8 +184,6 @@ class Microsoft::Terminal::Core::Terminal final : bool IsTrackingMouseInput() const noexcept; bool ShouldSendAlternateScroll(const unsigned int uiButton, const int32_t delta) const noexcept; - void FocusChanged(const bool focused) override; - std::wstring GetHyperlinkAtViewportPosition(const til::point viewportPos); std::wstring GetHyperlinkAtBufferPosition(const til::point bufferPos); uint16_t GetHyperlinkIdAtViewportPosition(const til::point viewportPos); @@ -309,6 +312,10 @@ class Microsoft::Terminal::Core::Terminal final : const TextBuffer::TextAndColor RetrieveSelectedTextFromBuffer(bool trimTrailingWhitespace); #pragma endregion +#ifndef NDEBUG + bool _suppressLockChecks = false; +#endif + private: std::function _pfnWriteInput; std::function _pfnWarningBell; @@ -431,11 +438,9 @@ class Microsoft::Terminal::Core::Terminal final : static WORD _VirtualKeyFromCharacter(const wchar_t ch) noexcept; static wchar_t _CharacterFromKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states) noexcept; - [[maybe_unused]] bool _handleTerminalInputResult(::Microsoft::Console::VirtualTerminal::TerminalInput::OutputType&& out) const; void _StoreKeyEvent(const WORD vkey, const WORD scanCode) noexcept; WORD _TakeVirtualKeyFromLastKeyEvent(const WORD scanCode) noexcept; - void _assertLocked() const noexcept; Console::VirtualTerminal::TerminalInput& _getTerminalInput() noexcept; const Console::VirtualTerminal::TerminalInput& _getTerminalInput() const noexcept; diff --git a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp index 48eeacf53c6..cf15e3ba191 100644 --- a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp @@ -87,7 +87,7 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final TEST_METHOD_SETUP(MethodSetup) { // STEP 1: Set up the Terminal - term = std::make_unique(); + term = std::make_unique(Terminal::TestDummyMarker{}); emptyRenderer = std::make_unique(term.get()); term->Create({ TerminalViewWidth, TerminalViewHeight }, 100, *emptyRenderer); diff --git a/src/cascadia/UnitTests_TerminalCore/InputTest.cpp b/src/cascadia/UnitTests_TerminalCore/InputTest.cpp index 8c6d3245162..01436fcddf3 100644 --- a/src/cascadia/UnitTests_TerminalCore/InputTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/InputTest.cpp @@ -5,40 +5,33 @@ #include #include "../cascadia/TerminalCore/Terminal.hpp" -#include "../renderer/inc/DummyRenderer.hpp" -#include "consoletaeftemplates.hpp" using namespace WEX::Logging; using namespace WEX::TestExecution; using namespace Microsoft::Terminal::Core; -using namespace Microsoft::Console::Render; + +constexpr Microsoft::Console::VirtualTerminal::TerminalInput::OutputType unhandled() +{ + return {}; +} + +constexpr Microsoft::Console::VirtualTerminal::TerminalInput::OutputType escChar(const wchar_t wch) +{ + const wchar_t buffer[2]{ L'\x1b', wch }; + return { { &buffer[0], 2 } }; +} namespace TerminalCoreUnitTests { class InputTest { TEST_CLASS(InputTest); - TEST_CLASS_SETUP(ClassSetup) - { - DummyRenderer renderer; - term.Create({ 100, 100 }, 0, renderer); - auto inputFn = std::bind(&InputTest::_VerifyExpectedInput, this, std::placeholders::_1); - term.SetWriteInputCallback(inputFn); - return true; - }; TEST_METHOD(AltShiftKey); TEST_METHOD(InvalidKeyEvent); - void _VerifyExpectedInput(std::wstring_view actualInput) - { - VERIFY_ARE_EQUAL(expectedinput.size(), actualInput.size()); - VERIFY_ARE_EQUAL(expectedinput, actualInput); - }; - - Terminal term{}; - std::wstring expectedinput{}; + Terminal term{ Terminal::TestDummyMarker{} }; }; void InputTest::AltShiftKey() @@ -46,21 +39,17 @@ namespace TerminalCoreUnitTests // Tests GH:637 // Verify that Alt+a generates a lowercase a on the input - expectedinput = L"\x1b" - "a"; - VERIFY_IS_TRUE(term.SendCharEvent(L'a', 0, ControlKeyStates::LeftAltPressed)); + VERIFY_ARE_EQUAL(escChar(L'a'), term.SendCharEvent(L'a', 0, ControlKeyStates::LeftAltPressed)); // Verify that Alt+shift+a generates a uppercase a on the input - expectedinput = L"\x1b" - "A"; - VERIFY_IS_TRUE(term.SendCharEvent(L'A', 0, ControlKeyStates::LeftAltPressed | ControlKeyStates::ShiftPressed)); + VERIFY_ARE_EQUAL(escChar(L'A'), term.SendCharEvent(L'A', 0, ControlKeyStates::LeftAltPressed | ControlKeyStates::ShiftPressed)); } void InputTest::InvalidKeyEvent() { // Certain applications like AutoHotKey and its keyboard remapping feature, // send us key events using SendInput() whose values are outside of the valid range. - VERIFY_IS_FALSE(term.SendKeyEvent(0, 123, {}, true)); - VERIFY_IS_FALSE(term.SendKeyEvent(255, 123, {}, true)); + VERIFY_ARE_EQUAL(unhandled(), term.SendKeyEvent(0, 123, {}, true)); + VERIFY_ARE_EQUAL(unhandled(), term.SendKeyEvent(255, 123, {}, true)); } } diff --git a/src/cascadia/UnitTests_TerminalCore/ScreenSizeLimitsTest.cpp b/src/cascadia/UnitTests_TerminalCore/ScreenSizeLimitsTest.cpp index 8e9971abdff..667de3ad469 100644 --- a/src/cascadia/UnitTests_TerminalCore/ScreenSizeLimitsTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ScreenSizeLimitsTest.cpp @@ -38,7 +38,7 @@ void ScreenSizeLimitsTest::ScreenWidthAndHeightAreClampedToBounds() // Negative values for initial visible row count or column count // are clamped to 1. Too-large positive values are clamped to SHRT_MAX. auto negativeColumnsSettings = winrt::make(10000, 9999999, -1234); - Terminal negativeColumnsTerminal; + Terminal negativeColumnsTerminal{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &negativeColumnsTerminal }; negativeColumnsTerminal.CreateFromSettings(negativeColumnsSettings, renderer); auto actualDimensions = negativeColumnsTerminal.GetViewport().Dimensions(); @@ -47,7 +47,7 @@ void ScreenSizeLimitsTest::ScreenWidthAndHeightAreClampedToBounds() // Zero values are clamped to 1 as well. auto zeroRowsSettings = winrt::make(10000, 0, 9999999); - Terminal zeroRowsTerminal; + Terminal zeroRowsTerminal{ Terminal::TestDummyMarker{} }; zeroRowsTerminal.CreateFromSettings(zeroRowsSettings, renderer); actualDimensions = zeroRowsTerminal.GetViewport().Dimensions(); VERIFY_ARE_EQUAL(actualDimensions.height, 1, L"Row count clamped to 1"); @@ -64,32 +64,32 @@ void ScreenSizeLimitsTest::ScrollbackHistorySizeIsClampedToBounds() // Zero history size is acceptable. auto noHistorySettings = winrt::make(0, visibleRowCount, 100); - Terminal noHistoryTerminal; + Terminal noHistoryTerminal{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &noHistoryTerminal }; noHistoryTerminal.CreateFromSettings(noHistorySettings, renderer); VERIFY_ARE_EQUAL(noHistoryTerminal.GetTextBuffer().TotalRowCount(), visibleRowCount, L"History size of 0 is accepted"); // Negative history sizes are clamped to zero. auto negativeHistorySizeSettings = winrt::make(-100, visibleRowCount, 100); - Terminal negativeHistorySizeTerminal; + Terminal negativeHistorySizeTerminal{ Terminal::TestDummyMarker{} }; negativeHistorySizeTerminal.CreateFromSettings(negativeHistorySizeSettings, renderer); VERIFY_ARE_EQUAL(negativeHistorySizeTerminal.GetTextBuffer().TotalRowCount(), visibleRowCount, L"Negative history size is clamped to 0"); // History size + initial visible rows == SHRT_MAX is acceptable. auto maxHistorySizeSettings = winrt::make(SHRT_MAX - visibleRowCount, visibleRowCount, 100); - Terminal maxHistorySizeTerminal; + Terminal maxHistorySizeTerminal{ Terminal::TestDummyMarker{} }; maxHistorySizeTerminal.CreateFromSettings(maxHistorySizeSettings, renderer); VERIFY_ARE_EQUAL(maxHistorySizeTerminal.GetTextBuffer().TotalRowCount(), SHRT_MAX, L"History size == SHRT_MAX - initial row count is accepted"); // History size + initial visible rows == SHRT_MAX + 1 will be clamped slightly. auto justTooBigHistorySizeSettings = winrt::make(SHRT_MAX - visibleRowCount + 1, visibleRowCount, 100); - Terminal justTooBigHistorySizeTerminal; + Terminal justTooBigHistorySizeTerminal{ Terminal::TestDummyMarker{} }; justTooBigHistorySizeTerminal.CreateFromSettings(justTooBigHistorySizeSettings, renderer); VERIFY_ARE_EQUAL(justTooBigHistorySizeTerminal.GetTextBuffer().TotalRowCount(), SHRT_MAX, L"History size == 1 + SHRT_MAX - initial row count is clamped to SHRT_MAX - initial row count"); // Ridiculously large history sizes are also clamped. auto farTooBigHistorySizeSettings = winrt::make(99999999, visibleRowCount, 100); - Terminal farTooBigHistorySizeTerminal; + Terminal farTooBigHistorySizeTerminal{ Terminal::TestDummyMarker{} }; farTooBigHistorySizeTerminal.CreateFromSettings(farTooBigHistorySizeSettings, renderer); VERIFY_ARE_EQUAL(farTooBigHistorySizeTerminal.GetTextBuffer().TotalRowCount(), SHRT_MAX, L"History size that is far too large is clamped to SHRT_MAX - initial row count"); } @@ -111,7 +111,7 @@ void ScreenSizeLimitsTest::ResizeIsClampedToBounds() auto settings = winrt::make(historySize, initialVisibleRowCount, initialVisibleColCount); Log::Comment(L"First create a terminal with fewer than SHRT_MAX lines"); - Terminal terminal; + Terminal terminal{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &terminal }; terminal.CreateFromSettings(settings, renderer); VERIFY_ARE_EQUAL(terminal.GetTextBuffer().TotalRowCount(), historySize + initialVisibleRowCount); diff --git a/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp b/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp index 12223e94a63..4b456e7cd5b 100644 --- a/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp @@ -107,7 +107,7 @@ class TerminalCoreUnitTests::ScrollTest final TEST_METHOD_SETUP(MethodSetup) { - _term = std::make_unique<::Microsoft::Terminal::Core::Terminal>(); + _term = std::make_unique<::Microsoft::Terminal::Core::Terminal>(Terminal::TestDummyMarker{}); _scrollBarNotification = std::make_shared>(); _term->SetScrollPositionChangedCallback([scrollBarNotification = _scrollBarNotification](const int top, const int height, const int bottom) { diff --git a/src/cascadia/UnitTests_TerminalCore/SelectionTest.cpp b/src/cascadia/UnitTests_TerminalCore/SelectionTest.cpp index 894c5b7764f..195eb17fcc3 100644 --- a/src/cascadia/UnitTests_TerminalCore/SelectionTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/SelectionTest.cpp @@ -46,7 +46,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(SelectUnit) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -59,7 +59,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(SelectArea) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -113,7 +113,7 @@ namespace TerminalCoreUnitTests // Test SetSelectionAnchor(til::point) and SetSelectionEnd(til::point) // Behavior: clamp coord to viewport. auto ValidateSingleClickSelection = [&](til::CoordType scrollback, const til::inclusive_rect& expected) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 10, 10 }, scrollback, renderer); @@ -126,7 +126,7 @@ namespace TerminalCoreUnitTests // Behavior: clamp coord to viewport. // Then, do double click selection. auto ValidateDoubleClickSelection = [&](til::CoordType scrollback, const til::inclusive_rect& expected) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 10, 10 }, scrollback, renderer); @@ -138,7 +138,7 @@ namespace TerminalCoreUnitTests // Behavior: clamp coord to viewport. // Then, do triple click selection. auto ValidateTripleClickSelection = [&](til::CoordType scrollback, const til::inclusive_rect& expected) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 10, 10 }, scrollback, renderer); @@ -171,7 +171,7 @@ namespace TerminalCoreUnitTests - All selection expansion functions will operate as if they were performed at the boundary */ - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 10, 10 }, 0, renderer); @@ -213,7 +213,7 @@ namespace TerminalCoreUnitTests - All selection expansion functions will operate as if they were performed at the boundary */ - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 10, 10 }, 0, renderer); @@ -299,7 +299,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(SelectBoxArea) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -335,7 +335,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(SelectAreaAfterScroll) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; til::CoordType scrollbackLines = 5; term.Create({ 100, 100 }, scrollbackLines, renderer); @@ -385,7 +385,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(SelectWideGlyph_Trailing) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -408,7 +408,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(SelectWideGlyph_Leading) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -431,7 +431,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(SelectWideGlyphsInBoxSelection) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -486,7 +486,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(DoubleClick_GeneralCase) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -509,7 +509,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(DoubleClick_Delimiter) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -530,7 +530,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(DoubleClick_DelimiterClass) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -558,7 +558,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(DoubleClickDrag_Right) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -587,7 +587,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(DoubleClickDrag_Left) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -616,7 +616,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(TripleClick_GeneralCase) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -630,7 +630,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(TripleClickDrag_Horizontal) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -647,7 +647,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(TripleClickDrag_Vertical) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -675,7 +675,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(ShiftClick) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -792,7 +792,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(Pivot) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); diff --git a/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp b/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp index 683740c155b..9f002db5098 100644 --- a/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp @@ -48,7 +48,7 @@ using namespace TerminalCoreUnitTests; void TerminalApiTest::SetColorTableEntry() { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -67,7 +67,7 @@ void TerminalApiTest::SetColorTableEntry() // PrintString() is called with more code units than the buffer width. void TerminalApiTest::PrintStringOfSurrogatePairs() { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 3, renderer); @@ -134,7 +134,7 @@ void TerminalApiTest::PrintStringOfSurrogatePairs() void TerminalApiTest::CursorVisibility() { // GH#3093 - Cursor Visibility and On states shouldn't affect each other - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -166,7 +166,7 @@ void TerminalApiTest::CursorVisibility() void TerminalApiTest::CursorVisibilityViaStateMachine() { // This is a nearly literal copy-paste of ScreenBufferTests::TestCursorIsOn, adapted for the Terminal - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -218,7 +218,7 @@ void TerminalApiTest::CursorVisibilityViaStateMachine() void TerminalApiTest::CheckDoubleWidthCursor() { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -262,7 +262,7 @@ void TerminalCoreUnitTests::TerminalApiTest::AddHyperlink() { // This is a nearly literal copy-paste of ScreenBufferTests::TestAddHyperlink, adapted for the Terminal - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -288,7 +288,7 @@ void TerminalCoreUnitTests::TerminalApiTest::AddHyperlinkCustomId() { // This is a nearly literal copy-paste of ScreenBufferTests::TestAddHyperlinkCustomId, adapted for the Terminal - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -316,7 +316,7 @@ void TerminalCoreUnitTests::TerminalApiTest::AddHyperlinkCustomIdDifferentUri() { // This is a nearly literal copy-paste of ScreenBufferTests::TestAddHyperlinkCustomId, adapted for the Terminal - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -344,7 +344,7 @@ void TerminalCoreUnitTests::TerminalApiTest::AddHyperlinkCustomIdDifferentUri() void TerminalCoreUnitTests::TerminalApiTest::SetTaskbarProgress() { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -415,7 +415,7 @@ void TerminalCoreUnitTests::TerminalApiTest::SetTaskbarProgress() void TerminalCoreUnitTests::TerminalApiTest::SetWorkingDirectory() { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); diff --git a/src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp b/src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp index 1ea9e0be168..d789bfc5583 100644 --- a/src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp +++ b/src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp @@ -56,7 +56,7 @@ class TerminalCoreUnitTests::TerminalBufferTests final TEST_METHOD_SETUP(MethodSetup) { // STEP 1: Set up the Terminal - term = std::make_unique(); + term = std::make_unique(Terminal::TestDummyMarker{}); emptyRenderer = std::make_unique(term.get()); term->Create({ TerminalViewWidth, TerminalViewHeight }, TerminalHistoryLength, *emptyRenderer); return true; From 18b0ecbb2a1b22cbbc02961a9166db0e3a865ab1 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Wed, 8 Nov 2023 10:29:01 -0600 Subject: [PATCH 049/167] releng: add --first-parent to the scripts that use git log (#16279) It makes the output less cluttered and more correct (for example: ServicingPipeline no longer tries to service two copies of each commit if there's a merge in the history...) --- tools/Get-OSSConhostLog.ps1 | 2 +- tools/ReleaseEngineering/New-TerminalStackedChangelog.ps1 | 2 +- tools/ReleaseEngineering/ServicingPipeline.ps1 | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/Get-OSSConhostLog.ps1 b/tools/Get-OSSConhostLog.ps1 index 4c206effa8e..46cd8aca1f2 100644 --- a/tools/Get-OSSConhostLog.ps1 +++ b/tools/Get-OSSConhostLog.ps1 @@ -43,7 +43,7 @@ Function Get-Git2GitIgnoresAsExcludes() { $Excludes = Get-Git2GitIgnoresAsExcludes Write-Verbose "IGNORING: $Excludes" -$Entries = & git log $RevisionRange "--pretty=format:%an%x1C%ae%x1C%s" -- $Excludes | +$Entries = & git log $RevisionRange --first-parent "--pretty=format:%an%x1C%ae%x1C%s" -- $Excludes | ConvertFrom-CSV -Delimiter "`u{001C}" -Header Author,Email,Subject Write-Verbose ("{0} unfiltered log entries" -f $Entries.Count) diff --git a/tools/ReleaseEngineering/New-TerminalStackedChangelog.ps1 b/tools/ReleaseEngineering/New-TerminalStackedChangelog.ps1 index 7f442f2ccf7..4d48b18bbf0 100644 --- a/tools/ReleaseEngineering/New-TerminalStackedChangelog.ps1 +++ b/tools/ReleaseEngineering/New-TerminalStackedChangelog.ps1 @@ -62,7 +62,7 @@ ForEach ($RevisionRange in $RevisionRanges) { # - %ae: author email # - %x1C: another FS # - %s: subject, the title of the commit - $NewEntries = & git log $RevisionRange "--pretty=format:%an%x1C%ae%x1C%s" | + $NewEntries = & git log $RevisionRange --first-parent "--pretty=format:%an%x1C%ae%x1C%s" | ConvertFrom-CSV -Delimiter "`u{001C}" -Header Author,Email,Subject $Entries += $NewEntries | % { [PSCustomObject]@{ diff --git a/tools/ReleaseEngineering/ServicingPipeline.ps1 b/tools/ReleaseEngineering/ServicingPipeline.ps1 index d7eb2cfebc2..2adf3843a0a 100644 --- a/tools/ReleaseEngineering/ServicingPipeline.ps1 +++ b/tools/ReleaseEngineering/ServicingPipeline.ps1 @@ -89,7 +89,7 @@ $Cards = Get-GithubProjectCard -ColumnId $ToPickColumn.id & git fetch --all 2>&1 | Out-Null -$Entries = @(& git log $SourceBranch --grep "(#\($($Cards.Number -Join "\|")\))" "--pretty=format:%H%x1C%s" | +$Entries = @(& git log $SourceBranch --first-parent --grep "(#\($($Cards.Number -Join "\|")\))" "--pretty=format:%H%x1C%s" | ConvertFrom-CSV -Delimiter "`u{001C}" -Header CommitID,Subject) [Array]::Reverse($Entries) From 6bc711de065466221738610fb5b9cba40ae1cf60 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Wed, 8 Nov 2023 11:10:58 -0600 Subject: [PATCH 050/167] maybe I'm not that good at coding --- src/cascadia/TerminalApp/TerminalTab.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp index ac48d6d7322..ca4cab21777 100644 --- a/src/cascadia/TerminalApp/TerminalTab.cpp +++ b/src/cascadia/TerminalApp/TerminalTab.cpp @@ -1686,7 +1686,7 @@ namespace winrt::TerminalApp::implementation return _zoomedPane != nullptr; } - TermControl& _termControlFromPane(const auto& pane) + TermControl _termControlFromPane(const auto& pane) { if (const auto content{ pane->GetContent() }) { From 5a9f3529d79d2168021be4b52202f9ad230de203 Mon Sep 17 00:00:00 2001 From: PankajBhojwani Date: Wed, 8 Nov 2023 09:12:13 -0800 Subject: [PATCH 051/167] Update Azure Cloud Shell for their new URI format (#16247) The Azure cloud shell team made some API changes that required us to format our requests a little differently. This PR makes those changes (more info in the comments in the code) Closes #16098 --- .github/actions/spelling/allow/allow.txt | 4 ++ .../TerminalConnection/AzureConnection.cpp | 53 +++++++++++++++++-- .../TerminalConnection/AzureConnection.h | 2 +- 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/.github/actions/spelling/allow/allow.txt b/.github/actions/spelling/allow/allow.txt index 7bff8f0cfa0..bccfe086aeb 100644 --- a/.github/actions/spelling/allow/allow.txt +++ b/.github/actions/spelling/allow/allow.txt @@ -1,3 +1,4 @@ +aci admins allcolors Apc @@ -8,6 +9,7 @@ breadcrumbs bsd calt ccmp +ccon changelog clickable clig @@ -91,6 +93,7 @@ reserialize reserializes rlig runtimes +servicebus shcha slnt Sos @@ -117,6 +120,7 @@ vsdevcmd walkthrough walkthroughs We'd +westus wildcards XBox YBox diff --git a/src/cascadia/TerminalConnection/AzureConnection.cpp b/src/cascadia/TerminalConnection/AzureConnection.cpp index 3844979b488..708518509b5 100644 --- a/src/cascadia/TerminalConnection/AzureConnection.cpp +++ b/src/cascadia/TerminalConnection/AzureConnection.cpp @@ -398,6 +398,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation switch (bufferType) { + case WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE: case WINHTTP_WEB_SOCKET_UTF8_FRAGMENT_BUFFER_TYPE: case WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE: { @@ -797,7 +798,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation // - an optional HTTP method (defaults to POST if content is present, GET otherwise) // Return value: // - the response from the server as a json value - WDJ::JsonObject AzureConnection::_SendRequestReturningJson(std::wstring_view uri, const WWH::IHttpContent& content, WWH::HttpMethod method) + WDJ::JsonObject AzureConnection::_SendRequestReturningJson(std::wstring_view uri, const WWH::IHttpContent& content, WWH::HttpMethod method, const Windows::Foundation::Uri referer) { if (!method) { @@ -810,6 +811,11 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation auto headers{ request.Headers() }; headers.Accept().TryParseAdd(L"application/json"); + if (referer) + { + headers.Referer(referer); + } + const auto response{ _httpClient.SendRequestAsync(request).get() }; const auto string{ response.Content().ReadAsStringAsync().get() }; const auto jsonResult{ WDJ::JsonObject::Parse(string) }; @@ -974,17 +980,56 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation auto uri{ fmt::format(L"{}terminals?cols={}&rows={}&version=2019-01-01&shell={}", _cloudShellUri, _initialCols, _initialRows, shellType) }; WWH::HttpStringContent content{ - L"", + L"{}", WSS::UnicodeEncoding::Utf8, // LOAD-BEARING. the API returns "'content-type' should be 'application/json' or 'multipart/form-data'" L"application/json" }; - const auto terminalResponse = _SendRequestReturningJson(uri, content); + const auto terminalResponse = _SendRequestReturningJson(uri, content, WWH::HttpMethod::Post(), Windows::Foundation::Uri(_cloudShellUri)); _terminalID = terminalResponse.GetNamedString(L"id"); + // we have to do some post-handling to get the proper socket endpoint + // the logic here is based on the way the cloud shell team itself does it + winrt::hstring finalSocketUri; + const std::wstring_view wCloudShellUri{ _cloudShellUri }; + + if (wCloudShellUri.find(L"servicebus") == std::wstring::npos) + { + // wCloudShellUri does not contain the word "servicebus", we can just use it to make the final URI + + // remove the "https" from the cloud shell URI + const auto uriWithoutProtocol = wCloudShellUri.substr(5); + + finalSocketUri = fmt::format(FMT_COMPILE(L"wss{}terminals/{}"), uriWithoutProtocol, _terminalID); + } + else + { + // if wCloudShellUri contains the word "servicebus", that means the returned socketUri is of the form + // wss://ccon-prod-westus-aci-03.servicebus.windows.net/cc-AAAA-AAAAAAAA//aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + // we need to change it to: + // wss://ccon-prod-westus-aci-03.servicebus.windows.net/$hc/cc-AAAA-AAAAAAAA/terminals/aaaaaaaaaaaaaaaaaaaaaa + + const auto socketUri = terminalResponse.GetNamedString(L"socketUri"); + const std::wstring_view wSocketUri{ socketUri }; + + // get the substring up until the ".net" + const auto dotNetStart = wSocketUri.find(L".net"); + THROW_HR_IF(E_UNEXPECTED, dotNetStart == std::wstring::npos); + const auto dotNetEnd = dotNetStart + 4; + const auto wSocketUriBody = wSocketUri.substr(0, dotNetEnd); + + // get the portion between the ".net" and the "//" (this is the cc-AAAA-AAAAAAAA part) + const auto lastDoubleSlashPos = wSocketUri.find_last_of(L"//"); + THROW_HR_IF(E_UNEXPECTED, lastDoubleSlashPos == std::wstring::npos); + const auto wSocketUriMiddle = wSocketUri.substr(dotNetEnd, lastDoubleSlashPos - (dotNetEnd)); + + // piece together the final uri, adding in the "$hc" and "terminals" where needed + finalSocketUri = fmt::format(FMT_COMPILE(L"{}/$hc{}terminals/{}"), wSocketUriBody, wSocketUriMiddle, _terminalID); + } + // Return the uri - return terminalResponse.GetNamedString(L"socketUri"); + return winrt::hstring{ finalSocketUri }; } // Method description: diff --git a/src/cascadia/TerminalConnection/AzureConnection.h b/src/cascadia/TerminalConnection/AzureConnection.h index cedd76757af..9d179073019 100644 --- a/src/cascadia/TerminalConnection/AzureConnection.h +++ b/src/cascadia/TerminalConnection/AzureConnection.h @@ -68,7 +68,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation void _WriteStringWithNewline(const std::wstring_view str); void _WriteCaughtExceptionRecord(); - winrt::Windows::Data::Json::JsonObject _SendRequestReturningJson(std::wstring_view uri, const winrt::Windows::Web::Http::IHttpContent& content = nullptr, winrt::Windows::Web::Http::HttpMethod method = nullptr); + winrt::Windows::Data::Json::JsonObject _SendRequestReturningJson(std::wstring_view uri, const winrt::Windows::Web::Http::IHttpContent& content = nullptr, winrt::Windows::Web::Http::HttpMethod method = nullptr, const winrt::Windows::Foundation::Uri referer = nullptr); void _setAccessToken(std::wstring_view accessToken); winrt::Windows::Data::Json::JsonObject _GetDeviceCode(); winrt::Windows::Data::Json::JsonObject _WaitForUser(const winrt::hstring& deviceCode, int pollInterval, int expiresIn); From eb16eeb29ed6083c6aa713df1abd7bbec7c7c834 Mon Sep 17 00:00:00 2001 From: chausner <15180557+chausner@users.noreply.github.com> Date: Fri, 10 Nov 2023 01:32:58 +0100 Subject: [PATCH 052/167] Change DisclaimerStyle to be non-italic (#16272) This changes the appearance of the disclaimer text that is used on some of the settings pages. The italic text style is replaced with a neutral style that fits better with the rest of the UI. ## Detailed Description of the Pull Request / Additional comments I also tried out these alternative styles but overall preferred the default TextBlock style (BodyTextBlockStyle). Closes #16264. --- src/cascadia/TerminalSettingsEditor/CommonResources.xaml | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cascadia/TerminalSettingsEditor/CommonResources.xaml b/src/cascadia/TerminalSettingsEditor/CommonResources.xaml index b274e3cf58c..509e1782c29 100644 --- a/src/cascadia/TerminalSettingsEditor/CommonResources.xaml +++ b/src/cascadia/TerminalSettingsEditor/CommonResources.xaml @@ -115,7 +115,6 @@ From e268c1c952f21c1b41ceac6ace7778d2b78620bf Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Fri, 10 Nov 2023 06:17:07 +0530 Subject: [PATCH 053/167] Support rendering of underline style and color (#16097) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for underline style and color in the renderer > [!IMPORTANT] > The PR adds underline style and color feature to AtlasEngine (WT) and GDIRenderer (Conhost) only. After the underline style and color feature addition to Conpty, this PR takes it further and add support for rendering them to the screen! Out of five underline styles, we already supported rendering for 3 of those types (Singly, Doubly, Dotted) in some form in our (Atlas) renderer. The PR adds the remaining types, namely, Dashed and Curly underlines support to the renderer. - All renderer engines now receive both gridline and underline color, and the latter is used for drawing the underlines. **When no underline color is set, we use the foreground color.** - Curly underline is rendered using `sin()` within the pixel shader. - To draw underlines for DECDWL and DECDHL, we send the line rendition scale within `QuadInstance`'s texcoord attribute. - In GDI renderer, dashed and dotted underline is drawn using `HPEN` with a desired style. Curly line is a cubic Bezier that draws one wave per cell. ## PR Checklist - ✅ Set the underline color to underlines only, without affecting the gridline color. - ❌ Port to DX renderer. (Not planned as DX renderer soon to be replaced by **AtlasEngine**) - ✅ Port underline coloring and style to GDI renderer (Conhost). - ✅ Wide/Tall `CurlyUnderline` variant for `DECDWL`/`DECDHL`. Closes #7228 --- .github/actions/spelling/expect/expect.txt | 4 + .../UnitTests_TerminalCore/ScrollTest.cpp | 2 +- src/interactivity/onecore/BgfxEngine.cpp | 7 +- src/interactivity/onecore/BgfxEngine.hpp | 2 +- src/renderer/atlas/AtlasEngine.cpp | 7 +- src/renderer/atlas/AtlasEngine.h | 2 +- src/renderer/atlas/BackendD2D.cpp | 28 +++--- src/renderer/atlas/BackendD3D.cpp | 92 +++++++++++++----- src/renderer/atlas/BackendD3D.h | 27 +++--- src/renderer/atlas/common.h | 4 +- src/renderer/atlas/shader_common.hlsl | 5 +- src/renderer/atlas/shader_ps.hlsl | 27 +++++- src/renderer/atlas/shader_vs.hlsl | 1 + src/renderer/base/RenderSettings.cpp | 41 ++++++++ src/renderer/base/renderer.cpp | 17 +++- src/renderer/dx/DxRenderer.cpp | 40 ++++---- src/renderer/dx/DxRenderer.hpp | 2 +- src/renderer/gdi/gdirenderer.hpp | 4 +- src/renderer/gdi/paint.cpp | 95 ++++++++++++++----- src/renderer/gdi/state.cpp | 34 +++++++ src/renderer/inc/IRenderEngine.hpp | 5 +- src/renderer/inc/RenderSettings.hpp | 1 + src/renderer/uia/UiaRenderer.cpp | 10 +- src/renderer/uia/UiaRenderer.hpp | 2 +- src/renderer/vt/paint.cpp | 6 +- src/renderer/vt/vtrenderer.hpp | 2 +- src/renderer/wddmcon/WddmConRenderer.cpp | 7 +- src/renderer/wddmcon/WddmConRenderer.hpp | 2 +- src/types/UiaTextRangeBase.cpp | 30 ++++-- 29 files changed, 376 insertions(+), 130 deletions(-) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 3a565ee4812..f38eddb9d3f 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -344,6 +344,7 @@ CTRLVOLUME Ctxt CUF cupxy +curlyline CURRENTFONT currentmode CURRENTPAGE @@ -579,6 +580,7 @@ elems emacs EMPTYBOX enabledelayedexpansion +ENDCAP endptr endregion ENTIREBUFFER @@ -827,6 +829,7 @@ hostlib HPA hpcon HPCON +hpen hpj HPR HProvider @@ -1011,6 +1014,7 @@ LOBYTE localappdata locsrc Loewen +LOGBRUSH LOGFONT LOGFONTA LOGFONTW diff --git a/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp b/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp index 4b456e7cd5b..d5baebadf26 100644 --- a/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp @@ -57,7 +57,7 @@ namespace HRESULT InvalidateCircling(_Out_ bool* /*pForcePaint*/) noexcept { return S_OK; } HRESULT PaintBackground() noexcept { return S_OK; } HRESULT PaintBufferLine(std::span /*clusters*/, til::point /*coord*/, bool /*fTrimLeft*/, bool /*lineWrapped*/) noexcept { return S_OK; } - HRESULT PaintBufferGridLines(GridLineSet /*lines*/, COLORREF /*color*/, size_t /*cchLine*/, til::point /*coordTarget*/) noexcept { return S_OK; } + HRESULT PaintBufferGridLines(GridLineSet /*lines*/, COLORREF /*gridlineColor*/, COLORREF /*underlineColor*/, size_t /*cchLine*/, til::point /*coordTarget*/) noexcept { return S_OK; } HRESULT PaintSelection(const til::rect& /*rect*/) noexcept { return S_OK; } HRESULT PaintCursor(const CursorOptions& /*options*/) noexcept { return S_OK; } HRESULT UpdateDrawingBrushes(const TextAttribute& /*textAttributes*/, const RenderSettings& /*renderSettings*/, gsl::not_null /*pData*/, bool /*usingSoftFont*/, bool /*isSettingDefaultBrushes*/) noexcept { return S_OK; } diff --git a/src/interactivity/onecore/BgfxEngine.cpp b/src/interactivity/onecore/BgfxEngine.cpp index ce6b903ffd1..d508603d5f9 100644 --- a/src/interactivity/onecore/BgfxEngine.cpp +++ b/src/interactivity/onecore/BgfxEngine.cpp @@ -147,9 +147,10 @@ CATCH_RETURN() CATCH_RETURN() } -[[nodiscard]] HRESULT BgfxEngine::PaintBufferGridLines(GridLineSet const /*lines*/, - COLORREF const /*color*/, - size_t const /*cchLine*/, +[[nodiscard]] HRESULT BgfxEngine::PaintBufferGridLines(const GridLineSet /*lines*/, + const COLORREF /*gridlineColor*/, + const COLORREF /*underlineColor*/, + const size_t /*cchLine*/, const til::point /*coordTarget*/) noexcept { return S_OK; diff --git a/src/interactivity/onecore/BgfxEngine.hpp b/src/interactivity/onecore/BgfxEngine.hpp index 31fa49c8522..e9759ce0306 100644 --- a/src/interactivity/onecore/BgfxEngine.hpp +++ b/src/interactivity/onecore/BgfxEngine.hpp @@ -51,7 +51,7 @@ namespace Microsoft::Console::Render const til::point coord, const bool trimLeft, const bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet const lines, COLORREF const color, size_t const cchLine, til::point const coordTarget) noexcept override; + [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; diff --git a/src/renderer/atlas/AtlasEngine.cpp b/src/renderer/atlas/AtlasEngine.cpp index e46bb250835..7438b93543e 100644 --- a/src/renderer/atlas/AtlasEngine.cpp +++ b/src/renderer/atlas/AtlasEngine.cpp @@ -375,7 +375,7 @@ try } CATCH_RETURN() -[[nodiscard]] HRESULT AtlasEngine::PaintBufferGridLines(const GridLineSet lines, const COLORREF color, const size_t cchLine, const til::point coordTarget) noexcept +[[nodiscard]] HRESULT AtlasEngine::PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept try { const auto shift = gsl::narrow_cast(_api.lineRendition != LineRendition::SingleWidth); @@ -383,8 +383,9 @@ try const auto y = gsl::narrow_cast(clamp(coordTarget.y, 0, _p.s->viewportCellCount.y)); const auto from = gsl::narrow_cast(clamp(x << shift, 0, _p.s->viewportCellCount.x - 1)); const auto to = gsl::narrow_cast(clamp((x + cchLine) << shift, from, _p.s->viewportCellCount.x)); - const auto fg = gsl::narrow_cast(color) | 0xff000000; - _p.rows[y]->gridLineRanges.emplace_back(lines, fg, from, to); + const auto glColor = gsl::narrow_cast(gridlineColor) | 0xff000000; + const auto ulColor = gsl::narrow_cast(underlineColor) | 0xff000000; + _p.rows[y]->gridLineRanges.emplace_back(lines, glColor, ulColor, from, to); return S_OK; } CATCH_RETURN() diff --git a/src/renderer/atlas/AtlasEngine.h b/src/renderer/atlas/AtlasEngine.h index b135a8989f8..4ec09a9e931 100644 --- a/src/renderer/atlas/AtlasEngine.h +++ b/src/renderer/atlas/AtlasEngine.h @@ -43,7 +43,7 @@ namespace Microsoft::Console::Render::Atlas [[nodiscard]] HRESULT PrepareLineTransform(LineRendition lineRendition, til::CoordType targetRow, til::CoordType viewportLeft) noexcept override; [[nodiscard]] HRESULT PaintBackground() noexcept override; [[nodiscard]] HRESULT PaintBufferLine(std::span clusters, til::point coord, bool fTrimLeft, bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet lines, COLORREF color, size_t cchLine, til::point coordTarget) noexcept override; + [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; [[nodiscard]] HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, const RenderSettings& renderSettings, gsl::not_null pData, bool usingSoftFont, bool isSettingDefaultBrushes) noexcept override; diff --git a/src/renderer/atlas/BackendD2D.cpp b/src/renderer/atlas/BackendD2D.cpp index 301738954aa..ea4ea0923eb 100644 --- a/src/renderer/atlas/BackendD2D.cpp +++ b/src/renderer/atlas/BackendD2D.cpp @@ -409,7 +409,7 @@ void BackendD2D::_drawGridlineRow(const RenderingPayload& p, const ShapedRow* ro D2D1_POINT_2F point0{ 0, static_cast(textCellCenter) }; D2D1_POINT_2F point1{ 0, static_cast(textCellCenter + cellSize.y) }; - const auto brush = _brushWithColor(r.color); + const auto brush = _brushWithColor(r.gridlineColor); const f32 w = pos.height; const f32 hw = w * 0.5f; @@ -421,11 +421,11 @@ void BackendD2D::_drawGridlineRow(const RenderingPayload& p, const ShapedRow* ro _renderTarget->DrawLine(point0, point1, brush, w, nullptr); } }; - const auto appendHorizontalLine = [&](const GridLineRange& r, FontDecorationPosition pos, ID2D1StrokeStyle* strokeStyle) { + const auto appendHorizontalLine = [&](const GridLineRange& r, FontDecorationPosition pos, ID2D1StrokeStyle* strokeStyle, const u32 color) { const auto from = r.from >> widthShift; const auto to = r.to >> widthShift; - const auto brush = _brushWithColor(r.color); + const auto brush = _brushWithColor(color); const f32 w = pos.height; const f32 centerY = textCellCenter + pos.position + w * 0.5f; const D2D1_POINT_2F point0{ static_cast(from * cellSize.x), centerY }; @@ -448,32 +448,32 @@ void BackendD2D::_drawGridlineRow(const RenderingPayload& p, const ShapedRow* ro } if (r.lines.test(GridLines::Top)) { - appendHorizontalLine(r, p.s->font->gridTop, nullptr); + appendHorizontalLine(r, p.s->font->gridTop, nullptr, r.gridlineColor); } if (r.lines.test(GridLines::Bottom)) { - appendHorizontalLine(r, p.s->font->gridBottom, nullptr); + appendHorizontalLine(r, p.s->font->gridBottom, nullptr, r.gridlineColor); + } + if (r.lines.test(GridLines::Strikethrough)) + { + appendHorizontalLine(r, p.s->font->strikethrough, nullptr, r.gridlineColor); } if (r.lines.test(GridLines::Underline)) { - appendHorizontalLine(r, p.s->font->underline, nullptr); + appendHorizontalLine(r, p.s->font->underline, nullptr, r.underlineColor); } - if (r.lines.test(GridLines::HyperlinkUnderline)) + else if (r.lines.any(GridLines::DottedUnderline, GridLines::HyperlinkUnderline)) { - appendHorizontalLine(r, p.s->font->underline, _dottedStrokeStyle.get()); + appendHorizontalLine(r, p.s->font->underline, _dottedStrokeStyle.get(), r.underlineColor); } - if (r.lines.test(GridLines::DoubleUnderline)) + else if (r.lines.test(GridLines::DoubleUnderline)) { for (const auto pos : p.s->font->doubleUnderline) { - appendHorizontalLine(r, pos, nullptr); + appendHorizontalLine(r, pos, nullptr, r.underlineColor); } } - if (r.lines.test(GridLines::Strikethrough)) - { - appendHorizontalLine(r, p.s->font->strikethrough, nullptr); - } } } diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index 19334b3a9d4..41fefa6ea2b 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -90,7 +90,8 @@ BackendD3D::BackendD3D(const RenderingPayload& p) { static constexpr D3D11_INPUT_ELEMENT_DESC layout[]{ { "SV_Position", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, - { "shadingType", 0, DXGI_FORMAT_R32_UINT, 1, offsetof(QuadInstance, shadingType), D3D11_INPUT_PER_INSTANCE_DATA, 1 }, + { "shadingType", 0, DXGI_FORMAT_R16_UINT, 1, offsetof(QuadInstance, shadingType), D3D11_INPUT_PER_INSTANCE_DATA, 1 }, + { "renditionScale", 0, DXGI_FORMAT_R8G8_UINT, 1, offsetof(QuadInstance, renditionScale), D3D11_INPUT_PER_INSTANCE_DATA, 1 }, { "position", 0, DXGI_FORMAT_R16G16_SINT, 1, offsetof(QuadInstance, position), D3D11_INPUT_PER_INSTANCE_DATA, 1 }, { "size", 0, DXGI_FORMAT_R16G16_UINT, 1, offsetof(QuadInstance, size), D3D11_INPUT_PER_INSTANCE_DATA, 1 }, { "texcoord", 0, DXGI_FORMAT_R16G16_UINT, 1, offsetof(QuadInstance, texcoord), D3D11_INPUT_PER_INSTANCE_DATA, 1 }, @@ -305,6 +306,37 @@ void BackendD3D::_updateFontDependents(const RenderingPayload& p) { const auto& font = *p.s->font; + // The max height of Curly line peak in `em` units. + const auto maxCurlyLinePeakHeightEm = 0.075f; + // We aim for atleast 1px height, but since we draw 1px smaller curly line, + // we aim for 2px height as a result. + const auto minCurlyLinePeakHeight = 2.0f; + + // Curlyline uses the gap between cell bottom and singly underline position + // as the height of the wave's peak. The baseline for curly-line is at the + // middle of singly underline. The gap could be too big, so we also apply + // a limit on the peak height. + const auto strokeHalfWidth = font.underline.height / 2.0f; + const auto underlineMidY = font.underline.position + strokeHalfWidth; + const auto cellBottomGap = font.cellSize.y - underlineMidY - strokeHalfWidth; + const auto maxCurlyLinePeakHeight = maxCurlyLinePeakHeightEm * font.fontSize; + auto curlyLinePeakHeight = std::min(cellBottomGap, maxCurlyLinePeakHeight); + + // When it's too small to be curly, make it straight. + if (curlyLinePeakHeight < minCurlyLinePeakHeight) + { + curlyLinePeakHeight = 0; + } + + // We draw a smaller curly line (-1px) to avoid clipping due to the rounding. + _curlyLineDrawPeakHeight = std::max(0.0f, curlyLinePeakHeight - 1.0f); + + const auto curlyUnderlinePos = font.underline.position - curlyLinePeakHeight; + const auto curlyUnderlineWidth = 2.0f * (curlyLinePeakHeight + strokeHalfWidth); + const auto curlyUnderlinePosU16 = gsl::narrow_cast(lrintf(curlyUnderlinePos)); + const auto curlyUnderlineWidthU16 = gsl::narrow_cast(lrintf(curlyUnderlineWidth)); + _curlyUnderline = { curlyUnderlinePosU16, curlyUnderlineWidthU16 }; + DWrite_GetRenderParams(p.dwriteFactory.get(), &_gamma, &_cleartypeEnhancedContrast, &_grayscaleEnhancedContrast, _textRenderingParams.put()); // Clearing the atlas requires BeginDraw(), which is expensive. Defer this until we need Direct2D anyways. _fontChangedResetGlyphAtlas = true; @@ -543,6 +575,9 @@ void BackendD3D::_recreateConstBuffer(const RenderingPayload& p) const DWrite_GetGammaRatios(_gamma, data.gammaRatios); data.enhancedContrast = p.s->font->antialiasingMode == AntialiasingMode::ClearType ? _cleartypeEnhancedContrast : _grayscaleEnhancedContrast; data.underlineWidth = p.s->font->underline.height; + data.curlyLineWaveFreq = 2.0f * 3.14f / p.s->font->cellSize.x; + data.curlyLinePeakHeight = _curlyLineDrawPeakHeight; + data.curlyLineCellOffset = p.s->font->underline.position + p.s->font->underline.height / 2.0f; p.deviceContext->UpdateSubresource(_psConstantBuffer.get(), 0, nullptr, &data, 0, 0); } } @@ -1024,7 +1059,7 @@ void BackendD3D::_drawText(RenderingPayload& p) goto drawGlyphRetry; } - if (glyphEntry.data.GetShadingType() != ShadingType::Default) + if (glyphEntry.data.shadingType != ShadingType::Default) { auto l = static_cast(lrintf((baselineX + row->glyphOffsets[x].advanceOffset) * scaleX)); auto t = static_cast(lrintf((baselineY - row->glyphOffsets[x].ascenderOffset) * scaleY)); @@ -1036,7 +1071,7 @@ void BackendD3D::_drawText(RenderingPayload& p) row->dirtyBottom = std::max(row->dirtyBottom, t + glyphEntry.data.size.y); _appendQuad() = { - .shadingType = glyphEntry.data.GetShadingType(), + .shadingType = glyphEntry.data.shadingType, .position = { static_cast(l), static_cast(t) }, .size = glyphEntry.data.size, .texcoord = glyphEntry.data.texcoord, @@ -1458,7 +1493,7 @@ bool BackendD3D::_drawGlyph(const RenderingPayload& p, const AtlasFontFaceEntryI const auto triggerRight = _ligatureOverhangTriggerRight * horizontalScale; const auto overlapSplit = rect.w >= p.s->font->cellSize.x && (bl <= triggerLeft || br >= triggerRight); - glyphEntry.data.shadingType = static_cast(isColorGlyph ? ShadingType::TextPassthrough : _textShadingType); + glyphEntry.data.shadingType = isColorGlyph ? ShadingType::TextPassthrough : _textShadingType; glyphEntry.data.overlapSplit = overlapSplit; glyphEntry.data.offset.x = bl; glyphEntry.data.offset.y = bt; @@ -1527,7 +1562,7 @@ bool BackendD3D::_drawSoftFontGlyph(const RenderingPayload& p, const AtlasFontFa _drawSoftFontGlyphInBitmap(p, glyphEntry); _d2dRenderTarget->DrawBitmap(_softFontBitmap.get(), &dest, 1, interpolation, nullptr, nullptr); - glyphEntry.data.shadingType = static_cast(ShadingType::TextGrayscale); + glyphEntry.data.shadingType = ShadingType::TextGrayscale; glyphEntry.data.overlapSplit = 0; glyphEntry.data.offset.x = 0; glyphEntry.data.offset.y = -baseline; @@ -1631,11 +1666,11 @@ void BackendD3D::_splitDoubleHeightGlyph(const RenderingPayload& p, const AtlasF // double-height row. This effectively turns the other (unneeded) side into whitespace. if (!top.data.size.y) { - top.data.shadingType = static_cast(ShadingType::Default); + top.data.shadingType = ShadingType::Default; } if (!bottom.data.size.y) { - bottom.data.shadingType = static_cast(ShadingType::Default); + bottom.data.shadingType = ShadingType::Default; } } @@ -1647,8 +1682,6 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) const auto verticalShift = static_cast(row->lineRendition >= LineRendition::DoubleHeightTop); const auto cellSize = p.s->font->cellSize; - const auto dottedLineType = horizontalShift ? ShadingType::DottedLineWide : ShadingType::DottedLine; - const auto rowTop = static_cast(cellSize.y * y); const auto rowBottom = static_cast(rowTop + cellSize.y); @@ -1675,11 +1708,11 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) .shadingType = ShadingType::SolidLine, .position = { static_cast(posX), rowTop }, .size = { width, p.s->font->cellSize.y }, - .color = r.color, + .color = r.gridlineColor, }; } }; - const auto appendHorizontalLine = [&](const GridLineRange& r, FontDecorationPosition pos, ShadingType shadingType) { + const auto appendHorizontalLine = [&](const GridLineRange& r, FontDecorationPosition pos, ShadingType shadingType, const u32 color) { const auto offset = pos.position << verticalShift; const auto height = static_cast(pos.height << verticalShift); @@ -1695,9 +1728,10 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) { _appendQuad() = { .shadingType = shadingType, + .renditionScale = { static_cast(1 << horizontalShift), static_cast(1 << verticalShift) }, .position = { left, static_cast(rt) }, .size = { width, static_cast(rb - rt) }, - .color = r.color, + .color = color, }; } }; @@ -1717,32 +1751,40 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) } if (r.lines.test(GridLines::Top)) { - appendHorizontalLine(r, p.s->font->gridTop, ShadingType::SolidLine); + appendHorizontalLine(r, p.s->font->gridTop, ShadingType::SolidLine, r.gridlineColor); } if (r.lines.test(GridLines::Bottom)) { - appendHorizontalLine(r, p.s->font->gridBottom, ShadingType::SolidLine); + appendHorizontalLine(r, p.s->font->gridBottom, ShadingType::SolidLine, r.gridlineColor); + } + if (r.lines.test(GridLines::Strikethrough)) + { + appendHorizontalLine(r, p.s->font->strikethrough, ShadingType::SolidLine, r.gridlineColor); } if (r.lines.test(GridLines::Underline)) { - appendHorizontalLine(r, p.s->font->underline, ShadingType::SolidLine); + appendHorizontalLine(r, p.s->font->underline, ShadingType::SolidLine, r.underlineColor); } - if (r.lines.test(GridLines::HyperlinkUnderline)) + else if (r.lines.any(GridLines::DottedUnderline, GridLines::HyperlinkUnderline)) { - appendHorizontalLine(r, p.s->font->underline, dottedLineType); + appendHorizontalLine(r, p.s->font->underline, ShadingType::DottedLine, r.underlineColor); } - if (r.lines.test(GridLines::DoubleUnderline)) + else if (r.lines.test(GridLines::DashedUnderline)) + { + appendHorizontalLine(r, p.s->font->underline, ShadingType::DashedLine, r.underlineColor); + } + else if (r.lines.test(GridLines::CurlyUnderline)) + { + appendHorizontalLine(r, _curlyUnderline, ShadingType::CurlyLine, r.underlineColor); + } + else if (r.lines.test(GridLines::DoubleUnderline)) { for (const auto pos : p.s->font->doubleUnderline) { - appendHorizontalLine(r, pos, ShadingType::SolidLine); + appendHorizontalLine(r, pos, ShadingType::SolidLine, r.underlineColor); } } - if (r.lines.test(GridLines::Strikethrough)) - { - appendHorizontalLine(r, p.s->font->strikethrough, ShadingType::SolidLine); - } } } @@ -2042,6 +2084,8 @@ size_t BackendD3D::_drawCursorForegroundSlowPath(const CursorRect& c, size_t off auto& target = _instances[offset + i]; target.shadingType = it.shadingType; + target.renditionScale.x = it.renditionScale.x; + target.renditionScale.y = it.renditionScale.y; target.position.x = static_cast(cutout.left); target.position.y = static_cast(cutout.top); target.size.x = static_cast(cutout.right - cutout.left); @@ -2059,6 +2103,8 @@ size_t BackendD3D::_drawCursorForegroundSlowPath(const CursorRect& c, size_t off auto& target = cutoutCount ? _appendQuad() : _instances[offset]; target.shadingType = it.shadingType; + target.renditionScale.x = it.renditionScale.x; + target.renditionScale.y = it.renditionScale.y; target.position.x = static_cast(intersectionL); target.position.y = static_cast(intersectionT); target.size.x = static_cast(intersectionR - intersectionL); diff --git a/src/renderer/atlas/BackendD3D.h b/src/renderer/atlas/BackendD3D.h index 14f4ca2943e..0befe8fe342 100644 --- a/src/renderer/atlas/BackendD3D.h +++ b/src/renderer/atlas/BackendD3D.h @@ -42,6 +42,9 @@ namespace Microsoft::Console::Render::Atlas alignas(sizeof(f32x4)) f32 gammaRatios[4]{}; alignas(sizeof(f32)) f32 enhancedContrast = 0; alignas(sizeof(f32)) f32 underlineWidth = 0; + alignas(sizeof(f32)) f32 curlyLinePeakHeight = 0; + alignas(sizeof(f32)) f32 curlyLineWaveFreq = 0; + alignas(sizeof(f32)) f32 curlyLineCellOffset = 0; #pragma warning(suppress : 4324) // 'PSConstBuffer': structure was padded due to alignment specifier }; @@ -55,7 +58,7 @@ namespace Microsoft::Console::Render::Atlas #pragma warning(suppress : 4324) // 'CustomConstBuffer': structure was padded due to alignment specifier }; - enum class ShadingType : u32 + enum class ShadingType : u16 { Default = 0, Background = 0, @@ -66,12 +69,13 @@ namespace Microsoft::Console::Render::Atlas TextClearType = 2, TextPassthrough = 3, DottedLine = 4, - DottedLineWide = 5, + DashedLine = 5, + CurlyLine = 6, // All items starting here will be drawing as a solid RGBA color - SolidLine = 6, + SolidLine = 7, - Cursor = 7, - Selection = 8, + Cursor = 8, + Selection = 9, TextDrawingFirst = TextGrayscale, TextDrawingLast = SolidLine, @@ -86,7 +90,8 @@ namespace Microsoft::Console::Render::Atlas // impact on performance and power draw. If (when?) displays with >32k resolution make their // appearance in the future, this should be changed to f32x2. But if you do so, please change // all other occurrences of i16x2 positions/offsets throughout the class to keep it consistent. - alignas(u32) ShadingType shadingType; + alignas(u16) ShadingType shadingType; + alignas(u16) u8x2 renditionScale; alignas(u32) i16x2 position; alignas(u32) u16x2 size; alignas(u32) u16x2 texcoord; @@ -95,16 +100,11 @@ namespace Microsoft::Console::Render::Atlas struct alignas(u32) AtlasGlyphEntryData { - u16 shadingType; + ShadingType shadingType; u16 overlapSplit; i16x2 offset; u16x2 size; u16x2 texcoord; - - constexpr ShadingType GetShadingType() const noexcept - { - return static_cast(shadingType); - } }; // NOTE: Don't initialize any members in this struct. This ensures that no @@ -291,6 +291,9 @@ namespace Microsoft::Console::Render::Atlas // The bounding rect of _cursorRects in pixels. til::rect _cursorPosition; + f32 _curlyLineDrawPeakHeight = 0; + FontDecorationPosition _curlyUnderline; + bool _requiresContinuousRedraw = false; #if ATLAS_DEBUG_SHOW_DIRTY diff --git a/src/renderer/atlas/common.h b/src/renderer/atlas/common.h index b8fa3ac0d59..014f5487118 100644 --- a/src/renderer/atlas/common.h +++ b/src/renderer/atlas/common.h @@ -125,6 +125,7 @@ namespace Microsoft::Console::Render::Atlas }; using u8 = uint8_t; + using u8x2 = vec2; using u16 = uint16_t; using u16x2 = vec2; @@ -426,7 +427,8 @@ namespace Microsoft::Console::Render::Atlas struct GridLineRange { GridLineSet lines; - u32 color = 0; + u32 gridlineColor = 0; + u32 underlineColor = 0; u16 from = 0; u16 to = 0; }; diff --git a/src/renderer/atlas/shader_common.hlsl b/src/renderer/atlas/shader_common.hlsl index 957b17ac6ad..d712f081f28 100644 --- a/src/renderer/atlas/shader_common.hlsl +++ b/src/renderer/atlas/shader_common.hlsl @@ -7,13 +7,15 @@ #define SHADING_TYPE_TEXT_CLEARTYPE 2 #define SHADING_TYPE_TEXT_PASSTHROUGH 3 #define SHADING_TYPE_DOTTED_LINE 4 -#define SHADING_TYPE_DOTTED_LINE_WIDE 5 +#define SHADING_TYPE_DASHED_LINE 5 +#define SHADING_TYPE_CURLY_LINE 6 // clang-format on struct VSData { float2 vertex : SV_Position; uint shadingType : shadingType; + uint2 renditionScale : renditionScale; int2 position : position; uint2 size : size; uint2 texcoord : texcoord; @@ -25,6 +27,7 @@ struct PSData float4 position : SV_Position; float2 texcoord : texcoord; nointerpolation uint shadingType : shadingType; + nointerpolation uint2 renditionScale : renditionScale; nointerpolation float4 color : color; }; diff --git a/src/renderer/atlas/shader_ps.hlsl b/src/renderer/atlas/shader_ps.hlsl index 2cb92947ce2..e19ba955fe5 100644 --- a/src/renderer/atlas/shader_ps.hlsl +++ b/src/renderer/atlas/shader_ps.hlsl @@ -12,6 +12,9 @@ cbuffer ConstBuffer : register(b0) float4 gammaRatios; float enhancedContrast; float underlineWidth; + float curlyLinePeakHeight; + float curlyLineWaveFreq; + float curlyLineCellOffset; } Texture2D background : register(t0); @@ -73,18 +76,36 @@ Output main(PSData data) : SV_Target } case SHADING_TYPE_DOTTED_LINE: { - const bool on = frac(data.position.x / (2.0f * underlineWidth)) < 0.5f; + const bool on = frac(data.position.x / (2.0f * underlineWidth * data.renditionScale.x)) < 0.5f; color = on * premultiplyColor(data.color); weights = color.aaaa; break; } - case SHADING_TYPE_DOTTED_LINE_WIDE: + case SHADING_TYPE_DASHED_LINE: { - const bool on = frac(data.position.x / (4.0f * underlineWidth)) < 0.5f; + const bool on = frac(data.position.x / (backgroundCellSize.x * data.renditionScale.x)) < 0.5f; color = on * premultiplyColor(data.color); weights = color.aaaa; break; } + case SHADING_TYPE_CURLY_LINE: + { + uint cellRow = floor(data.position.y / backgroundCellSize.y); + // Use the previous cell when drawing 'Double Height' curly line. + cellRow -= data.renditionScale.y - 1; + const float cellTop = cellRow * backgroundCellSize.y; + const float centerY = cellTop + curlyLineCellOffset * data.renditionScale.y; + const float strokeWidthHalf = underlineWidth * data.renditionScale.y / 2.0f; + const float amp = curlyLinePeakHeight * data.renditionScale.y; + const float freq = curlyLineWaveFreq / data.renditionScale.x; + + const float s = sin(data.position.x * freq); + const float d = abs(centerY - (s * amp) - data.position.y); + const float a = 1 - saturate(d - strokeWidthHalf); + color = a * premultiplyColor(data.color); + weights = color.aaaa; + break; + } default: { color = premultiplyColor(data.color); diff --git a/src/renderer/atlas/shader_vs.hlsl b/src/renderer/atlas/shader_vs.hlsl index 49b9030b156..eb96fcf0e45 100644 --- a/src/renderer/atlas/shader_vs.hlsl +++ b/src/renderer/atlas/shader_vs.hlsl @@ -15,6 +15,7 @@ PSData main(VSData data) PSData output; output.color = data.color; output.shadingType = data.shadingType; + output.renditionScale = data.renditionScale; // positionScale is expected to be float2(2.0f / sizeInPixel.x, -2.0f / sizeInPixel.y). Together with the // addition below this will transform our "position" from pixel into normalized device coordinate (NDC) space. output.position.xy = (data.position + data.vertex.xy * data.size) * positionScale + float2(-1.0f, 1.0f); diff --git a/src/renderer/base/RenderSettings.cpp b/src/renderer/base/RenderSettings.cpp index 03c4795635a..f885957796a 100644 --- a/src/renderer/base/RenderSettings.cpp +++ b/src/renderer/base/RenderSettings.cpp @@ -212,6 +212,47 @@ std::pair RenderSettings::GetAttributeColorsWithAlpha(const return { fg, bg }; } +// Routine Description: +// - Calculates the RGB underline color of a given text attribute, using the +// current color table configuration and active render settings. +// - Returns the current foreground color when the underline color isn't set. +// Arguments: +// - attr - The TextAttribute to retrieve the underline color from. +// Return Value: +// - The color value of the attribute's underline. +COLORREF RenderSettings::GetAttributeUnderlineColor(const TextAttribute& attr) const noexcept +{ + const auto [fg, bg] = GetAttributeColors(attr); + const auto ulTextColor = attr.GetUnderlineColor(); + if (ulTextColor.IsDefault()) + { + return fg; + } + + const auto defaultUlIndex = GetColorAliasIndex(ColorAlias::DefaultForeground); + auto ul = ulTextColor.GetColor(_colorTable, defaultUlIndex, true); + if (attr.IsInvisible()) + { + ul = bg; + } + + // We intentionally aren't _only_ checking for attr.IsInvisible here, because we also want to + // catch the cases where the ul was intentionally set to be the same as the bg. In either case, + // don't adjust the underline color. + if constexpr (Feature_AdjustIndistinguishableText::IsEnabled()) + { + if ( + ul != bg && + (_renderMode.test(Mode::AlwaysDistinguishableColors) || + (_renderMode.test(Mode::IndexedDistinguishableColors) && ulTextColor.IsDefaultOrLegacy() && attr.GetBackground().IsDefaultOrLegacy()))) + { + ul = ColorFix::GetPerceivableColor(ul, bg, 0.5f * 0.5f); + } + } + + return ul; +} + // Routine Description: // - Increments the position in the blink cycle, toggling the blink rendition // state on every second call, potentially triggering a redraw of the given diff --git a/src/renderer/base/renderer.cpp b/src/renderer/base/renderer.cpp index 917c5ba05f9..2b328ee07f9 100644 --- a/src/renderer/base/renderer.cpp +++ b/src/renderer/base/renderer.cpp @@ -955,13 +955,21 @@ GridLineSet Renderer::s_GetGridlines(const TextAttribute& textAttribute) noexcep { case UnderlineStyle::NoUnderline: break; + case UnderlineStyle::SinglyUnderlined: + lines.set(GridLines::Underline); + break; case UnderlineStyle::DoublyUnderlined: lines.set(GridLines::DoubleUnderline); break; - case UnderlineStyle::SinglyUnderlined: case UnderlineStyle::CurlyUnderlined: + lines.set(GridLines::CurlyUnderline); + break; case UnderlineStyle::DottedUnderlined: + lines.set(GridLines::DottedUnderline); + break; case UnderlineStyle::DashedUnderlined: + lines.set(GridLines::DashedUnderline); + break; default: lines.set(GridLines::Underline); break; @@ -1002,10 +1010,11 @@ void Renderer::_PaintBufferOutputGridLineHelper(_In_ IRenderEngine* const pEngin // Return early if there are no lines to paint. if (lines.any()) { - // Get the current foreground color to render the lines. - const auto rgb = _renderSettings.GetAttributeColors(textAttribute).first; + // Get the current foreground and underline colors to render the lines. + const auto fg = _renderSettings.GetAttributeColors(textAttribute).first; + const auto underlineColor = _renderSettings.GetAttributeUnderlineColor(textAttribute); // Draw the lines - LOG_IF_FAILED(pEngine->PaintBufferGridLines(lines, rgb, cchLine, coordTarget)); + LOG_IF_FAILED(pEngine->PaintBufferGridLines(lines, fg, underlineColor, cchLine, coordTarget)); } } diff --git a/src/renderer/dx/DxRenderer.cpp b/src/renderer/dx/DxRenderer.cpp index f74bf528dc1..064d219f72a 100644 --- a/src/renderer/dx/DxRenderer.cpp +++ b/src/renderer/dx/DxRenderer.cpp @@ -1696,14 +1696,16 @@ CATCH_RETURN() // - Paints lines around cells (draws in pieces of the grid) // Arguments: // - lines - Which grid lines (top, left, bottom, right) to draw -// - color - The color to use for drawing the lines +// - gridlineColor - The color to use for drawing the gridlines +// - underlineColor - The color to use for drawing the underlines // - cchLine - Length of the line to draw in character cells // - coordTarget - The X,Y character position in the grid where we should start drawing // - We will draw rightward (+X) from here // Return Value: // - S_OK or relevant DirectX error [[nodiscard]] HRESULT DxEngine::PaintBufferGridLines(const GridLineSet lines, - COLORREF const color, + const COLORREF gridlineColor, + const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept try @@ -1711,8 +1713,6 @@ try const auto existingColor = _d2dBrushForeground->GetColor(); const auto restoreBrushOnExit = wil::scope_exit([&]() noexcept { _d2dBrushForeground->SetColor(existingColor); }); - _d2dBrushForeground->SetColor(_ColorFFromColorRef(color | 0xff000000)); - const auto font = _fontRenderData->GlyphCell().to_d2d_size(); const D2D_POINT_2F target = { coordTarget.x * font.width, coordTarget.y * font.height }; const auto fullRunWidth = font.width * gsl::narrow_cast(cchLine); @@ -1721,10 +1721,12 @@ try _d2dDeviceContext->DrawLine({ x0, y0 }, { x1, y1 }, _d2dBrushForeground.Get(), strokeWidth, _strokeStyle.Get()); }; - const auto DrawHyperlinkLine = [=](const auto x0, const auto y0, const auto x1, const auto y1, const auto strokeWidth) noexcept { + const auto DrawDottedLine = [=](const auto x0, const auto y0, const auto x1, const auto y1, const auto strokeWidth) noexcept { _d2dDeviceContext->DrawLine({ x0, y0 }, { x1, y1 }, _d2dBrushForeground.Get(), strokeWidth, _dashStrokeStyle.Get()); }; + _d2dBrushForeground->SetColor(_ColorFFromColorRef(gridlineColor | 0xff000000)); + // NOTE: Line coordinates are centered within the line, so they need to be // offset by half the stroke width. For the start coordinate we add half // the stroke width, and for the end coordinate we subtract half the width. @@ -1773,10 +1775,22 @@ try } } + if (lines.test(GridLines::Strikethrough)) + { + const auto halfStrikethroughWidth = lineMetrics.strikethroughWidth / 2.0f; + const auto startX = target.x + halfStrikethroughWidth; + const auto endX = target.x + fullRunWidth - halfStrikethroughWidth; + const auto y = target.y + lineMetrics.strikethroughOffset; + + DrawLine(startX, y, endX, y, lineMetrics.strikethroughWidth); + } + + _d2dBrushForeground->SetColor(_ColorFFromColorRef(underlineColor | 0xff000000)); + // In the case of the underline and strikethrough offsets, the stroke width // is already accounted for, so they don't require further adjustments. - if (lines.any(GridLines::Underline, GridLines::DoubleUnderline, GridLines::HyperlinkUnderline)) + if (lines.any(GridLines::Underline, GridLines::DoubleUnderline, GridLines::DottedUnderline, GridLines::HyperlinkUnderline)) { const auto halfUnderlineWidth = lineMetrics.underlineWidth / 2.0f; const auto startX = target.x + halfUnderlineWidth; @@ -1788,9 +1802,9 @@ try DrawLine(startX, y, endX, y, lineMetrics.underlineWidth); } - if (lines.test(GridLines::HyperlinkUnderline)) + if (lines.any(GridLines::DottedUnderline, GridLines::HyperlinkUnderline)) { - DrawHyperlinkLine(startX, y, endX, y, lineMetrics.underlineWidth); + DrawDottedLine(startX, y, endX, y, lineMetrics.underlineWidth); } if (lines.test(GridLines::DoubleUnderline)) @@ -1801,16 +1815,6 @@ try } } - if (lines.test(GridLines::Strikethrough)) - { - const auto halfStrikethroughWidth = lineMetrics.strikethroughWidth / 2.0f; - const auto startX = target.x + halfStrikethroughWidth; - const auto endX = target.x + fullRunWidth - halfStrikethroughWidth; - const auto y = target.y + lineMetrics.strikethroughOffset; - - DrawLine(startX, y, endX, y, lineMetrics.strikethroughWidth); - } - return S_OK; } CATCH_RETURN() diff --git a/src/renderer/dx/DxRenderer.hpp b/src/renderer/dx/DxRenderer.hpp index bfb11205a05..877b3f1adfa 100644 --- a/src/renderer/dx/DxRenderer.hpp +++ b/src/renderer/dx/DxRenderer.hpp @@ -106,7 +106,7 @@ namespace Microsoft::Console::Render const bool fTrimLeft, const bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet const lines, COLORREF const color, size_t const cchLine, til::point const coordTarget) noexcept override; + [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; diff --git a/src/renderer/gdi/gdirenderer.hpp b/src/renderer/gdi/gdirenderer.hpp index a1ab2290a97..2cc5b4dc14c 100644 --- a/src/renderer/gdi/gdirenderer.hpp +++ b/src/renderer/gdi/gdirenderer.hpp @@ -52,7 +52,8 @@ namespace Microsoft::Console::Render const bool trimLeft, const bool lineWrapped) noexcept override; [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, - const COLORREF color, + const COLORREF gridlineColor, + const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; @@ -120,6 +121,7 @@ namespace Microsoft::Console::Render int underlineWidth; int strikethroughOffset; int strikethroughWidth; + int curlylinePeakHeight; }; LineMetrics _lineMetrics; diff --git a/src/renderer/gdi/paint.cpp b/src/renderer/gdi/paint.cpp index 17dc9bddd82..9722703d105 100644 --- a/src/renderer/gdi/paint.cpp +++ b/src/renderer/gdi/paint.cpp @@ -509,27 +509,24 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) // - Draws up to one line worth of grid lines on top of characters. // Arguments: // - lines - Enum defining which edges of the rectangle to draw -// - color - The color to use for drawing the edges. +// - gridlineColor - The color to use for drawing the gridlines. +// - underlineColor - The color to use for drawing the underlines. // - cchLine - How many characters we should draw the grid lines along (left to right in a row) // - coordTarget - The starting X/Y position of the first character to draw on. // Return Value: // - S_OK or suitable GDI HRESULT error or E_FAIL for GDI errors in functions that don't reliably return a specific error code. -[[nodiscard]] HRESULT GdiEngine::PaintBufferGridLines(const GridLineSet lines, const COLORREF color, const size_t cchLine, const til::point coordTarget) noexcept +[[nodiscard]] HRESULT GdiEngine::PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept { LOG_IF_FAILED(_FlushBufferLines()); // Convert the target from characters to pixels. const auto ptTarget = coordTarget * _GetFontSize(); - // Set the brush color as requested and save the previous brush to restore at the end. - wil::unique_hbrush hbr(CreateSolidBrush(color)); - RETURN_HR_IF_NULL(E_FAIL, hbr.get()); - - wil::unique_hbrush hbrPrev(SelectBrush(_hdcMemoryContext, hbr.get())); - RETURN_HR_IF_NULL(E_FAIL, hbrPrev.get()); - hbr.release(); // If SelectBrush was successful, GDI owns the brush. Release for now. - // On exit, be sure we try to put the brush back how it was originally. - auto restoreBrushOnExit = wil::scope_exit([&] { hbr.reset(SelectBrush(_hdcMemoryContext, hbrPrev.get())); }); + // Create a brush with the gridline color, and apply it. + wil::unique_hbrush hbr(CreateSolidBrush(gridlineColor)); + RETURN_HR_IF_NULL(E_FAIL, hbr.get()); + const auto prevBrush = wil::SelectObject(_hdcMemoryContext, hbr.get()); + RETURN_HR_IF_NULL(E_FAIL, prevBrush.get()); // Get the font size so we know the size of the rectangle lines we'll be inscribing. const auto fontWidth = _GetFontSize().width; @@ -539,6 +536,31 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) const auto DrawLine = [=](const auto x, const auto y, const auto w, const auto h) { return PatBlt(_hdcMemoryContext, x, y, w, h, PATCOPY); }; + const auto DrawStrokedLine = [&](const auto x, const auto y, const auto w) { + RETURN_HR_IF(E_FAIL, !MoveToEx(_hdcMemoryContext, x, y, nullptr)); + RETURN_HR_IF(E_FAIL, !LineTo(_hdcMemoryContext, x + w, y)); + return S_OK; + }; + const auto DrawCurlyLine = [&](const auto x, const auto y, const auto cCurlyLines) { + const auto curlyLineWidth = fontWidth; + const auto curlyLineHalfWidth = lrintf(curlyLineWidth / 2.0f); + const auto controlPointHeight = gsl::narrow_cast(std::floor(3.5f * _lineMetrics.curlylinePeakHeight)); + // Each curlyLine requires 3 `POINT`s + const auto cPoints = gsl::narrow(3 * cCurlyLines); + std::vector points; + points.reserve(cPoints); + auto start = x; + for (auto i = 0u; i < cCurlyLines; i++) + { + points.emplace_back(start + curlyLineHalfWidth, y - controlPointHeight); + points.emplace_back(start + curlyLineHalfWidth, y + controlPointHeight); + points.emplace_back(start + curlyLineWidth, y); + start += curlyLineWidth; + } + RETURN_HR_IF(E_FAIL, !MoveToEx(_hdcMemoryContext, x, y, nullptr)); + RETURN_HR_IF(E_FAIL, !PolyBezierTo(_hdcMemoryContext, points.data(), cPoints)); + return S_OK; + }; if (lines.test(GridLines::Left)) { @@ -574,22 +596,51 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y, widthOfAllCells, _lineMetrics.gridlineWidth)); } - if (lines.any(GridLines::Underline, GridLines::DoubleUnderline)) + if (lines.test(GridLines::Strikethrough)) { - const auto y = ptTarget.y + _lineMetrics.underlineOffset; - RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y, widthOfAllCells, _lineMetrics.underlineWidth)); + const auto y = ptTarget.y + _lineMetrics.strikethroughOffset; + RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y, widthOfAllCells, _lineMetrics.strikethroughWidth)); + } - if (lines.test(GridLines::DoubleUnderline)) - { - const auto y2 = ptTarget.y + _lineMetrics.underlineOffset2; - RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y2, widthOfAllCells, _lineMetrics.underlineWidth)); - } + // Create a pen matching the underline style. + DWORD underlinePenType = PS_SOLID; + if (lines.test(GridLines::DottedUnderline)) + { + underlinePenType = PS_DOT; + } + else if (lines.test(GridLines::DashedUnderline)) + { + underlinePenType = PS_DASH; } + const LOGBRUSH brushProp{ .lbStyle = BS_SOLID, .lbColor = underlineColor }; + wil::unique_hpen hpen(ExtCreatePen(underlinePenType | PS_GEOMETRIC | PS_ENDCAP_FLAT, _lineMetrics.underlineWidth, &brushProp, 0, nullptr)); - if (lines.test(GridLines::Strikethrough)) + // Apply the pen. + const auto prevPen = wil::SelectObject(_hdcMemoryContext, hpen.get()); + RETURN_HR_IF_NULL(E_FAIL, prevPen.get()); + + const auto underlineMidY = std::lround(ptTarget.y + _lineMetrics.underlineOffset + _lineMetrics.underlineWidth / 2.0f); + if (lines.test(GridLines::Underline)) { - const auto y = ptTarget.y + _lineMetrics.strikethroughOffset; - RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y, widthOfAllCells, _lineMetrics.strikethroughWidth)); + return DrawStrokedLine(ptTarget.x, underlineMidY, widthOfAllCells); + } + else if (lines.test(GridLines::DoubleUnderline)) + { + const auto doubleUnderlineBottomLineMidY = std::lround(ptTarget.y + _lineMetrics.underlineOffset2 + _lineMetrics.underlineWidth / 2.0f); + RETURN_IF_FAILED(DrawStrokedLine(ptTarget.x, underlineMidY, widthOfAllCells)); + return DrawStrokedLine(ptTarget.x, doubleUnderlineBottomLineMidY, widthOfAllCells); + } + else if (lines.test(GridLines::CurlyUnderline)) + { + return DrawCurlyLine(ptTarget.x, underlineMidY, cchLine); + } + else if (lines.test(GridLines::DottedUnderline)) + { + return DrawStrokedLine(ptTarget.x, underlineMidY, widthOfAllCells); + } + else if (lines.test(GridLines::DashedUnderline)) + { + return DrawStrokedLine(ptTarget.x, underlineMidY, widthOfAllCells); } return S_OK; diff --git a/src/renderer/gdi/state.cpp b/src/renderer/gdi/state.cpp index 556f2df02fb..099c2f43f78 100644 --- a/src/renderer/gdi/state.cpp +++ b/src/renderer/gdi/state.cpp @@ -11,6 +11,15 @@ using namespace Microsoft::Console::Render; +namespace +{ + // The max height of Curly line peak in `em` units. + constexpr auto MaxCurlyLinePeakHeightEm = 0.075f; + + // The min height of Curly line peak. + constexpr auto MinCurlyLinePeakHeight = 2.0f; +} + // Routine Description: // - Creates a new GDI-based rendering engine // - NOTE: Will throw if initialization failure. Caller must catch. @@ -397,6 +406,31 @@ GdiEngine::~GdiEngine() _lineMetrics.underlineOffset2 = _lineMetrics.underlineOffset - _lineMetrics.gridlineWidth; } + // Curly line doesn't render properly below 1px stroke width. Make it a straight line. + if (_lineMetrics.underlineWidth < 1) + { + _lineMetrics.curlylinePeakHeight = 0; + } + else + { + // Curlyline uses the gap between cell bottom and singly underline + // position as the height of the wave's peak. The baseline for curly + // line is at the middle of singly underline. The gap could be too big, + // so we also apply a limit on the peak height. + const auto strokeHalfWidth = _lineMetrics.underlineWidth / 2.0f; + const auto underlineMidY = _lineMetrics.underlineOffset + strokeHalfWidth; + const auto cellBottomGap = Font.GetSize().height - underlineMidY - strokeHalfWidth; + const auto maxCurlyLinePeakHeight = MaxCurlyLinePeakHeightEm * fontSize; + auto curlyLinePeakHeight = std::min(cellBottomGap, maxCurlyLinePeakHeight); + + // When it's too small to be curly, make it a straight line. + if (curlyLinePeakHeight < MinCurlyLinePeakHeight) + { + curlyLinePeakHeight = 0.0f; + } + _lineMetrics.curlylinePeakHeight = gsl::narrow_cast(std::floor(curlyLinePeakHeight)); + } + // Now find the size of a 0 in this current font and save it for conversions done later. _coordFontLast = Font.GetSize(); diff --git a/src/renderer/inc/IRenderEngine.hpp b/src/renderer/inc/IRenderEngine.hpp index 46221ae911d..afcaa5aff59 100644 --- a/src/renderer/inc/IRenderEngine.hpp +++ b/src/renderer/inc/IRenderEngine.hpp @@ -41,6 +41,9 @@ namespace Microsoft::Console::Render Right, Underline, DoubleUnderline, + CurlyUnderline, + DottedUnderline, + DashedUnderline, Strikethrough, HyperlinkUnderline }; @@ -73,7 +76,7 @@ namespace Microsoft::Console::Render [[nodiscard]] virtual HRESULT PrepareLineTransform(LineRendition lineRendition, til::CoordType targetRow, til::CoordType viewportLeft) noexcept = 0; [[nodiscard]] virtual HRESULT PaintBackground() noexcept = 0; [[nodiscard]] virtual HRESULT PaintBufferLine(std::span clusters, til::point coord, bool fTrimLeft, bool lineWrapped) noexcept = 0; - [[nodiscard]] virtual HRESULT PaintBufferGridLines(GridLineSet lines, COLORREF color, size_t cchLine, til::point coordTarget) noexcept = 0; + [[nodiscard]] virtual HRESULT PaintBufferGridLines(GridLineSet lines, COLORREF gridlineColor, COLORREF underlineColor, size_t cchLine, til::point coordTarget) noexcept = 0; [[nodiscard]] virtual HRESULT PaintSelection(const til::rect& rect) noexcept = 0; [[nodiscard]] virtual HRESULT PaintCursor(const CursorOptions& options) noexcept = 0; [[nodiscard]] virtual HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, const RenderSettings& renderSettings, gsl::not_null pData, bool usingSoftFont, bool isSettingDefaultBrushes) noexcept = 0; diff --git a/src/renderer/inc/RenderSettings.hpp b/src/renderer/inc/RenderSettings.hpp index 4b6e7c3981c..c836bdde848 100644 --- a/src/renderer/inc/RenderSettings.hpp +++ b/src/renderer/inc/RenderSettings.hpp @@ -41,6 +41,7 @@ namespace Microsoft::Console::Render size_t GetColorAliasIndex(const ColorAlias alias) const noexcept; std::pair GetAttributeColors(const TextAttribute& attr) const noexcept; std::pair GetAttributeColorsWithAlpha(const TextAttribute& attr) const noexcept; + COLORREF GetAttributeUnderlineColor(const TextAttribute& attr) const noexcept; void ToggleBlinkRendition(class Renderer& renderer) noexcept; private: diff --git a/src/renderer/uia/UiaRenderer.cpp b/src/renderer/uia/UiaRenderer.cpp index e58c227c561..ba09384580b 100644 --- a/src/renderer/uia/UiaRenderer.cpp +++ b/src/renderer/uia/UiaRenderer.cpp @@ -347,14 +347,16 @@ void UiaEngine::WaitUntilCanRender() noexcept // For UIA, this doesn't mean anything. So do nothing. // Arguments: // - lines - -// - color - +// - gridlineColor - +// - underlineColor - // - cchLine - // - coordTarget - // Return Value: // - S_FALSE -[[nodiscard]] HRESULT UiaEngine::PaintBufferGridLines(GridLineSet const /*lines*/, - COLORREF const /*color*/, - size_t const /*cchLine*/, +[[nodiscard]] HRESULT UiaEngine::PaintBufferGridLines(const GridLineSet /*lines*/, + const COLORREF /*gridlineColor*/, + const COLORREF /*underlineColor*/, + const size_t /*cchLine*/, const til::point /*coordTarget*/) noexcept { return S_FALSE; diff --git a/src/renderer/uia/UiaRenderer.hpp b/src/renderer/uia/UiaRenderer.hpp index 5e486b7e0e1..4625ca155cb 100644 --- a/src/renderer/uia/UiaRenderer.hpp +++ b/src/renderer/uia/UiaRenderer.hpp @@ -49,7 +49,7 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT NotifyNewText(const std::wstring_view newText) noexcept override; [[nodiscard]] HRESULT PaintBackground() noexcept override; [[nodiscard]] HRESULT PaintBufferLine(const std::span clusters, const til::point coord, const bool fTrimLeft, const bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF color, const size_t cchLine, const til::point coordTarget) noexcept override; + [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; [[nodiscard]] HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, const RenderSettings& renderSettings, const gsl::not_null pData, const bool usingSoftFont, const bool isSettingDefaultBrushes) noexcept override; diff --git a/src/renderer/vt/paint.cpp b/src/renderer/vt/paint.cpp index 801390085c0..99f8853a98f 100644 --- a/src/renderer/vt/paint.cpp +++ b/src/renderer/vt/paint.cpp @@ -187,13 +187,15 @@ using namespace Microsoft::Console::Types; // - Draws up to one line worth of grid lines on top of characters. // Arguments: // - lines - Enum defining which edges of the rectangle to draw -// - color - The color to use for drawing the edges. +// - gridlineColor - The color to use for drawing the gridlines. +// - underlineColor - The color to use for drawing the underlines. // - cchLine - How many characters we should draw the grid lines along (left to right in a row) // - coordTarget - The starting X/Y position of the first character to draw on. // Return Value: // - S_OK [[nodiscard]] HRESULT VtEngine::PaintBufferGridLines(const GridLineSet /*lines*/, - const COLORREF /*color*/, + const COLORREF /*gridlineColor*/, + const COLORREF /*underlineColor*/, const size_t /*cchLine*/, const til::point /*coordTarget*/) noexcept { diff --git a/src/renderer/vt/vtrenderer.hpp b/src/renderer/vt/vtrenderer.hpp index 15de6e0e760..100e04c549d 100644 --- a/src/renderer/vt/vtrenderer.hpp +++ b/src/renderer/vt/vtrenderer.hpp @@ -62,7 +62,7 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT PrepareLineTransform(const LineRendition lineRendition, const til::CoordType targetRow, const til::CoordType viewportLeft) noexcept override; [[nodiscard]] HRESULT PaintBackground() noexcept override; [[nodiscard]] HRESULT PaintBufferLine(std::span clusters, til::point coord, bool fTrimLeft, bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet lines, COLORREF color, size_t cchLine, til::point coordTarget) noexcept override; + [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) noexcept override; diff --git a/src/renderer/wddmcon/WddmConRenderer.cpp b/src/renderer/wddmcon/WddmConRenderer.cpp index 378b1dfd086..fda8ae6c21b 100644 --- a/src/renderer/wddmcon/WddmConRenderer.cpp +++ b/src/renderer/wddmcon/WddmConRenderer.cpp @@ -284,9 +284,10 @@ CATCH_RETURN() CATCH_RETURN(); } -[[nodiscard]] HRESULT WddmConEngine::PaintBufferGridLines(GridLineSet const /*lines*/, - COLORREF const /*color*/, - size_t const /*cchLine*/, +[[nodiscard]] HRESULT WddmConEngine::PaintBufferGridLines(const GridLineSet /*lines*/, + const COLORREF /*gridlineColor*/, + const COLORREF /*underlineColor*/, + const size_t /*cchLine*/, const til::point /*coordTarget*/) noexcept { return S_OK; diff --git a/src/renderer/wddmcon/WddmConRenderer.hpp b/src/renderer/wddmcon/WddmConRenderer.hpp index a658ef51180..954dc226946 100644 --- a/src/renderer/wddmcon/WddmConRenderer.hpp +++ b/src/renderer/wddmcon/WddmConRenderer.hpp @@ -44,7 +44,7 @@ namespace Microsoft::Console::Render const til::point coord, const bool trimLeft, const bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet const lines, COLORREF const color, size_t const cchLine, til::point const coordTarget) noexcept override; + [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index af5ebb431ea..419acf1ff0b 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -405,16 +405,22 @@ std::optional UiaTextRangeBase::_verifyAttr(TEXTATTRIBUTEID attributeId, V THROW_HR_IF(E_INVALIDARG, val.vt != VT_I4); // The underline style is stored as a TextDecorationLineStyle. - // However, The text buffer doesn't have that many different styles for being underlined. - // Instead, we only have single and double underlined. + // However, The text buffer doesn't have all the different styles for being underlined. + // Instead, we only use a subset of them. switch (val.lVal) { case TextDecorationLineStyle_None: return !attr.IsUnderlined(); + case TextDecorationLineStyle_Single: + return attr.GetUnderlineStyle() == UnderlineStyle::SinglyUnderlined; case TextDecorationLineStyle_Double: return attr.GetUnderlineStyle() == UnderlineStyle::DoublyUnderlined; - case TextDecorationLineStyle_Single: // singly underlined and extended styles are treated the same - return attr.IsUnderlined() && attr.GetUnderlineStyle() != UnderlineStyle::DoublyUnderlined; + case TextDecorationLineStyle_Wavy: + return attr.GetUnderlineStyle() == UnderlineStyle::CurlyUnderlined; + case TextDecorationLineStyle_Dot: + return attr.GetUnderlineStyle() == UnderlineStyle::DottedUnderlined; + case TextDecorationLineStyle_Dash: + return attr.GetUnderlineStyle() == UnderlineStyle::DashedUnderlined; default: return std::nullopt; } @@ -697,18 +703,26 @@ bool UiaTextRangeBase::_initializeAttrQuery(TEXTATTRIBUTEID attributeId, VARIANT const auto style = attr.GetUnderlineStyle(); switch (style) { + case UnderlineStyle::NoUnderline: + pRetVal->lVal = TextDecorationLineStyle_None; + return true; case UnderlineStyle::SinglyUnderlined: pRetVal->lVal = TextDecorationLineStyle_Single; return true; case UnderlineStyle::DoublyUnderlined: pRetVal->lVal = TextDecorationLineStyle_Double; return true; - case UnderlineStyle::NoUnderline: - pRetVal->lVal = TextDecorationLineStyle_None; + case UnderlineStyle::CurlyUnderlined: + pRetVal->lVal = TextDecorationLineStyle_Wavy; + return true; + case UnderlineStyle::DottedUnderlined: + pRetVal->lVal = TextDecorationLineStyle_Dot; + return true; + case UnderlineStyle::DashedUnderlined: + pRetVal->lVal = TextDecorationLineStyle_Dash; return true; + // Out of range styles are treated as singly underlined. default: - // TODO: Handle other underline styles once they're supported in the graphic renderer. - // For now, extended styles are treated (and rendered) as single underline. pRetVal->lVal = TextDecorationLineStyle_Single; return true; } From d14524cd4cc4970bb1b6456f9667e2dd661b9854 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Thu, 9 Nov 2023 19:10:35 -0600 Subject: [PATCH 054/167] Fix leak in buffering text for UIA when unfocused (#16251) Notes in #16217 have the investigation. TL;DR: we'd always buffer text. Even if we're disabled (unfocused). When we're disabled, we'd _never_ clear the buffered text. Oops. Closes #16217 --- src/renderer/uia/UiaRenderer.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/renderer/uia/UiaRenderer.cpp b/src/renderer/uia/UiaRenderer.cpp index ba09384580b..d1e72fbd0eb 100644 --- a/src/renderer/uia/UiaRenderer.cpp +++ b/src/renderer/uia/UiaRenderer.cpp @@ -47,6 +47,12 @@ UiaEngine::UiaEngine(IUiaEventDispatcher* dispatcher) : [[nodiscard]] HRESULT UiaEngine::Disable() noexcept { _isEnabled = false; + + // If we had buffered any text from NotifyNewText, dump it. When we do come + // back around to actually paint, we will just no-op. No sense in keeping + // the data buffered. + _newOutput = std::wstring{}; + return S_OK; } @@ -171,6 +177,10 @@ CATCH_RETURN(); [[nodiscard]] HRESULT UiaEngine::NotifyNewText(const std::wstring_view newText) noexcept try { + // GH#16217 - don't even buffer this text if we're disabled. We may never + // come around to write it out. + RETURN_HR_IF(S_FALSE, !_isEnabled); + if (!newText.empty()) { _newOutput.append(newText); From 2ac5e8670a90b329dda9136b1fe94ef3a17b0a41 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 7 Nov 2023 14:35:16 -0600 Subject: [PATCH 055/167] Defer package updates while the Terminal is running (#16250) Adds ```xml defer ``` to our `Package.Properties` for all our packages. This was added in the September 2023 OS release of Windows 11. Apparently, this just works now? I did update VS, but I don't _think_ that updated the SDK. I have no idea how it updated the manifest definitions. Closes #3915 Closes #6726 (cherry picked from commit 077d63e6a34e392bb497d59768c551f7c099baaf) Service-Card-Id: 91033136 Service-Version: 1.19 --- src/cascadia/CascadiaPackage/Package-Can.appxmanifest | 4 +++- src/cascadia/CascadiaPackage/Package-Dev.appxmanifest | 4 +++- src/cascadia/CascadiaPackage/Package-Pre.appxmanifest | 4 +++- src/cascadia/CascadiaPackage/Package.appxmanifest | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/cascadia/CascadiaPackage/Package-Can.appxmanifest b/src/cascadia/CascadiaPackage/Package-Can.appxmanifest index d72c96e1405..77bf2fc1a0d 100644 --- a/src/cascadia/CascadiaPackage/Package-Can.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Can.appxmanifest @@ -8,13 +8,14 @@ xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3" xmlns:uap4="http://schemas.microsoft.com/appx/manifest/uap/windows10/4" xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5" + xmlns:uap17="http://schemas.microsoft.com/appx/manifest/uap/windows10/17" xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4" xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5" xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" xmlns:virtualization="http://schemas.microsoft.com/appx/manifest/virtualization/windows10" - IgnorableNamespaces="uap mp rescap uap3 desktop6 virtualization"> + IgnorableNamespaces="uap mp rescap uap3 uap17 desktop6 virtualization"> HKEY_CURRENT_USER\Console\%%Startup + defer diff --git a/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest b/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest index 4a0735bb206..d8ceefaeef6 100644 --- a/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest @@ -7,6 +7,7 @@ xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3" xmlns:uap4="http://schemas.microsoft.com/appx/manifest/uap/windows10/4" + xmlns:uap17="http://schemas.microsoft.com/appx/manifest/uap/windows10/17" xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4" xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5" @@ -14,7 +15,7 @@ xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" xmlns:virtualization="http://schemas.microsoft.com/appx/manifest/virtualization/windows10" xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5" - IgnorableNamespaces="uap mp rescap uap3 desktop6 virtualization"> + IgnorableNamespaces="uap mp rescap uap3 uap17 desktop6 virtualization"> HKEY_CURRENT_USER\Console\%%Startup + defer diff --git a/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest b/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest index 98fb12b9455..3a7bf26e0f7 100644 --- a/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest @@ -9,13 +9,14 @@ xmlns:uap4="http://schemas.microsoft.com/appx/manifest/uap/windows10/4" xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5" xmlns:uap7="http://schemas.microsoft.com/appx/manifest/uap/windows10/7" + xmlns:uap17="http://schemas.microsoft.com/appx/manifest/uap/windows10/17" xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4" xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5" xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" xmlns:virtualization="http://schemas.microsoft.com/appx/manifest/virtualization/windows10" - IgnorableNamespaces="uap mp rescap uap3 desktop6 virtualization"> + IgnorableNamespaces="uap mp rescap uap3 uap17 desktop6 virtualization"> HKEY_CURRENT_USER\Console\%%Startup + defer diff --git a/src/cascadia/CascadiaPackage/Package.appxmanifest b/src/cascadia/CascadiaPackage/Package.appxmanifest index c123778d1e9..f04863da27c 100644 --- a/src/cascadia/CascadiaPackage/Package.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package.appxmanifest @@ -9,13 +9,14 @@ xmlns:uap4="http://schemas.microsoft.com/appx/manifest/uap/windows10/4" xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5" xmlns:uap7="http://schemas.microsoft.com/appx/manifest/uap/windows10/7" + xmlns:uap17="http://schemas.microsoft.com/appx/manifest/uap/windows10/17" xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4" xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5" xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" xmlns:virtualization="http://schemas.microsoft.com/appx/manifest/virtualization/windows10" - IgnorableNamespaces="uap mp rescap uap3 desktop6 virtualization"> + IgnorableNamespaces="uap mp rescap uap3 uap17 desktop6 virtualization"> HKEY_CURRENT_USER\Console\%%Startup + defer From 4bbfa0570abf14427fdb1c0a9aecd27852c815bf Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 8 Nov 2023 17:28:07 +0100 Subject: [PATCH 056/167] Fix deadlocks due to holding locks across WriteFile calls (#16224) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes a number of bugs introduced in 4370da9, all of which are of the same kind: Holding the terminal lock across `WriteFile` calls into the ConPTY pipe. This is problematic, because the pipe has a tiny buffer size of just 4KiB and ConPTY may respond on its output pipe, before the entire buffer given to `WriteFile` has been emptied. When the ConPTY output thread then tries to acquire the terminal lock to begin parsing the VT output, we get ourselves a proper deadlock (cross process too!). The solution is to tease `Terminal` further apart into code that is thread-safe and code that isn't. Functions like `SendKeyEvent` so far have mixed them into one, because when they get called by `ControlCore` they both, processed the data (not thread-safe as it accesses VT state) and also sent that data back into `ControlCore` through a callback which then indirectly called into the `ConptyConnection` which calls `WriteFile`. Instead, we now return the data that needs to be sent from these functions, and `ControlCore` is free to release the lock and then call into the connection, which may then block indefinitely. ## Validation Steps Performed * Start nvim in WSL * Press `i` to enter the regular Insert mode * Paste 1MB of text * Doesn't deadlock ✅ (cherry picked from commit 71a1a97a9aa7b23a0c0f21b2f9e437b051cb5238) Service-Card-Id: 91043521 Service-Version: 1.19 --- src/cascadia/TerminalControl/ControlCore.cpp | 162 ++++++++++++------ src/cascadia/TerminalControl/HwndTerminal.cpp | 69 +++++--- src/cascadia/TerminalCore/ITerminalInput.hpp | 9 +- src/cascadia/TerminalCore/Terminal.cpp | 81 ++++----- src/cascadia/TerminalCore/Terminal.hpp | 25 +-- .../ConptyRoundtripTests.cpp | 2 +- .../UnitTests_TerminalCore/InputTest.cpp | 43 ++--- .../ScreenSizeLimitsTest.cpp | 16 +- .../UnitTests_TerminalCore/ScrollTest.cpp | 2 +- .../UnitTests_TerminalCore/SelectionTest.cpp | 44 ++--- .../TerminalApiTest.cpp | 20 +-- .../TerminalBufferTests.cpp | 2 +- 12 files changed, 270 insertions(+), 205 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 7925fc16eb8..734e1b98d2a 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -10,11 +10,11 @@ #include #include +#include #include #include #include "EventArgs.h" -#include "../../types/inc/GlyphWidth.hpp" #include "../../buffer/out/search.h" #include "../../renderer/atlas/AtlasEngine.h" #include "../../renderer/dx/DxRenderer.hpp" @@ -443,6 +443,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - void ControlCore::_sendInputToConnection(std::wstring_view wstr) { + if (wstr.empty()) + { + return; + } + + // The connection may call functions like WriteFile() which may block indefinitely. + // It's important we don't hold any mutexes across such calls. + _terminal->_assertUnlocked(); + if (_isReadOnly) { _raiseReadOnlyWarning(); @@ -492,8 +501,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation _handleControlC(); } - const auto lock = _terminal->LockForWriting(); - return _terminal->SendCharEvent(ch, scanCode, modifiers); + TerminalInput::OutputType out; + { + const auto lock = _terminal->LockForReading(); + out = _terminal->SendCharEvent(ch, scanCode, modifiers); + } + if (out) + { + _sendInputToConnection(*out); + return true; + } + return false; } void ControlCore::_handleControlC() @@ -602,46 +620,56 @@ namespace winrt::Microsoft::Terminal::Control::implementation const ControlKeyStates modifiers, const bool keyDown) { - const auto lock = _terminal->LockForWriting(); + if (!vkey) + { + return true; + } - // Update the selection, if it's present - // GH#8522, GH#3758 - Only modify the selection on key _down_. If we - // modify on key up, then there's chance that we'll immediately dismiss - // a selection created by an action bound to a keydown. - if (_shouldTryUpdateSelection(vkey) && keyDown) + TerminalInput::OutputType out; { - // try to update the selection - if (const auto updateSlnParams{ _terminal->ConvertKeyEventToUpdateSelectionParams(modifiers, vkey) }) - { - _terminal->UpdateSelection(updateSlnParams->first, updateSlnParams->second, modifiers); - _updateSelectionUI(); - return true; - } + const auto lock = _terminal->LockForWriting(); - // GH#8791 - don't dismiss selection if Windows key was also pressed as a key-combination. - if (!modifiers.IsWinPressed()) + // Update the selection, if it's present + // GH#8522, GH#3758 - Only modify the selection on key _down_. If we + // modify on key up, then there's chance that we'll immediately dismiss + // a selection created by an action bound to a keydown. + if (_shouldTryUpdateSelection(vkey) && keyDown) { - _terminal->ClearSelection(); - _updateSelectionUI(); - } + // try to update the selection + if (const auto updateSlnParams{ _terminal->ConvertKeyEventToUpdateSelectionParams(modifiers, vkey) }) + { + _terminal->UpdateSelection(updateSlnParams->first, updateSlnParams->second, modifiers); + _updateSelectionUI(); + return true; + } - // When there is a selection active, escape should clear it and NOT flow through - // to the terminal. With any other keypress, it should clear the selection AND - // flow through to the terminal. - if (vkey == VK_ESCAPE) - { - return true; + // GH#8791 - don't dismiss selection if Windows key was also pressed as a key-combination. + if (!modifiers.IsWinPressed()) + { + _terminal->ClearSelection(); + _updateSelectionUI(); + } + + // When there is a selection active, escape should clear it and NOT flow through + // to the terminal. With any other keypress, it should clear the selection AND + // flow through to the terminal. + if (vkey == VK_ESCAPE) + { + return true; + } } - } - // If the terminal translated the key, mark the event as handled. - // This will prevent the system from trying to get the character out - // of it and sending us a CharacterReceived event. - return vkey ? _terminal->SendKeyEvent(vkey, - scanCode, - modifiers, - keyDown) : - true; + // If the terminal translated the key, mark the event as handled. + // This will prevent the system from trying to get the character out + // of it and sending us a CharacterReceived event. + out = _terminal->SendKeyEvent(vkey, scanCode, modifiers, keyDown); + } + if (out) + { + _sendInputToConnection(*out); + return true; + } + return false; } bool ControlCore::SendMouseEvent(const til::point viewportPos, @@ -650,8 +678,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation const short wheelDelta, const TerminalInput::MouseButtonState state) { - const auto lock = _terminal->LockForWriting(); - return _terminal->SendMouseEvent(viewportPos, uiButton, states, wheelDelta, state); + TerminalInput::OutputType out; + { + const auto lock = _terminal->LockForReading(); + out = _terminal->SendMouseEvent(viewportPos, uiButton, states, wheelDelta, state); + } + if (out) + { + _sendInputToConnection(*out); + return true; + } + return false; } void ControlCore::UserScrollViewport(const int viewTop) @@ -1324,8 +1361,19 @@ namespace winrt::Microsoft::Terminal::Control::implementation // before sending it over the terminal's connection. void ControlCore::PasteText(const winrt::hstring& hstr) { + using namespace ::Microsoft::Console::Utils; + + auto filtered = FilterStringForPaste(hstr, CarriageReturnNewline | ControlCodes); + if (BracketedPasteEnabled()) + { + filtered.insert(0, L"\x1b[200~"); + filtered.append(L"\x1b[201~"); + } + + // It's important to not hold the terminal lock while calling this function as sending the data may take a long time. + _sendInputToConnection(filtered); + const auto lock = _terminal->LockForWriting(); - _terminal->WritePastedText(hstr); _terminal->ClearSelection(); _updateSelectionUI(); _terminal->TrySnapOnInput(); @@ -1894,17 +1942,27 @@ namespace winrt::Microsoft::Terminal::Control::implementation const auto endPoint = goRight ? clampedClick : cursorPos; const auto delta = _terminal->GetTextBuffer().GetCellDistance(startPoint, endPoint); - const WORD key = goRight ? VK_RIGHT : VK_LEFT; + + std::wstring buffer; + const auto append = [&](TerminalInput::OutputType&& out) { + if (out) + { + buffer.append(std::move(*out)); + } + }; + // Send an up and a down once per cell. This won't // accurately handle wide characters, or continuation // prompts, or cases where a single escape character in the // command (e.g. ^[) takes up two cells. for (size_t i = 0u; i < delta; i++) { - _terminal->SendKeyEvent(key, 0, {}, true); - _terminal->SendKeyEvent(key, 0, {}, false); + append(_terminal->SendKeyEvent(key, 0, {}, true)); + append(_terminal->SendKeyEvent(key, 0, {}, false)); } + + _sendInputToConnection(buffer); } } } @@ -1915,7 +1973,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - Updates the renderer's representation of the selection as well as the selection marker overlay in TermControl void ControlCore::_updateSelectionUI() { - const auto lock = _terminal->LockForWriting(); _renderer->TriggerSelection(); // only show the markers if we're doing a keyboard selection or in mark mode const bool showMarkers{ _terminal->SelectionMode() >= ::Microsoft::Terminal::Core::Terminal::SelectionInteractionMode::Keyboard }; @@ -2247,14 +2304,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation void ControlCore::_focusChanged(bool focused) { - // GH#13461 - temporarily turn off read-only mode, send the focus event, - // then turn it back on. Even in focus mode, focus events are fine to - // send. We don't want to pop a warning every time the control is - // focused. - const auto previous = std::exchange(_isReadOnly, false); - const auto restore = wil::scope_exit([&]() { _isReadOnly = previous; }); - const auto lock = _terminal->LockForWriting(); - _terminal->FocusChanged(focused); + TerminalInput::OutputType out; + { + const auto lock = _terminal->LockForReading(); + out = _terminal->FocusChanged(focused); + } + if (out && !out->empty()) + { + // _sendInputToConnection() asserts that we aren't in focus mode, + // but window focus events are always fine to send. + _connection.WriteInput(*out); + } } bool ControlCore::_isBackgroundTransparent() diff --git a/src/cascadia/TerminalControl/HwndTerminal.cpp b/src/cascadia/TerminalControl/HwndTerminal.cpp index a13ac5b3eda..6bec410cda8 100644 --- a/src/cascadia/TerminalControl/HwndTerminal.cpp +++ b/src/cascadia/TerminalControl/HwndTerminal.cpp @@ -272,7 +272,7 @@ void HwndTerminal::RegisterScrollCallback(std::function cal void HwndTerminal::_WriteTextToConnection(const std::wstring_view input) noexcept { - if (!_pfnWriteCallback) + if (input.empty() || !_pfnWriteCallback) { return; } @@ -758,8 +758,17 @@ try WI_IsFlagSet(GetKeyState(VK_RBUTTON), KeyPressed) }; - const auto lock = _terminal->LockForWriting(); - return _terminal->SendMouseEvent(cursorPosition / fontSize, uMsg, getControlKeyState(), wheelDelta, state); + TerminalInput::OutputType out; + { + const auto lock = _terminal->LockForReading(); + out = _terminal->SendMouseEvent(cursorPosition / fontSize, uMsg, getControlKeyState(), wheelDelta, state); + } + if (out) + { + _WriteTextToConnection(*out); + return true; + } + return false; } catch (...) { @@ -784,8 +793,16 @@ try { _uiaProvider->RecordKeyEvent(vkey); } - const auto lock = _terminal->LockForWriting(); - _terminal->SendKeyEvent(vkey, scanCode, modifiers, keyDown); + + TerminalInput::OutputType out; + { + const auto lock = _terminal->LockForReading(); + out = _terminal->SendKeyEvent(vkey, scanCode, modifiers, keyDown); + } + if (out) + { + _WriteTextToConnection(*out); + } } CATCH_LOG(); @@ -797,31 +814,39 @@ try return; } - const auto lock = _terminal->LockForWriting(); - - if (_terminal->IsSelectionActive()) + TerminalInput::OutputType out; { - _ClearSelection(); - if (ch == UNICODE_ESC) + const auto lock = _terminal->LockForWriting(); + + if (_terminal->IsSelectionActive()) { - // ESC should clear any selection before it triggers input. - // Other characters pass through. + _ClearSelection(); + if (ch == UNICODE_ESC) + { + // ESC should clear any selection before it triggers input. + // Other characters pass through. + return; + } + } + + if (ch == UNICODE_TAB) + { + // TAB was handled as a keydown event (cf. Terminal::SendKeyEvent) return; } - } - if (ch == UNICODE_TAB) - { - // TAB was handled as a keydown event (cf. Terminal::SendKeyEvent) - return; - } + auto modifiers = getControlKeyState(); + if (WI_IsFlagSet(flags, ENHANCED_KEY)) + { + modifiers |= ControlKeyStates::EnhancedKey; + } - auto modifiers = getControlKeyState(); - if (WI_IsFlagSet(flags, ENHANCED_KEY)) + out = _terminal->SendCharEvent(ch, scanCode, modifiers); + } + if (out) { - modifiers |= ControlKeyStates::EnhancedKey; + _WriteTextToConnection(*out); } - _terminal->SendCharEvent(ch, scanCode, modifiers); } CATCH_LOG(); diff --git a/src/cascadia/TerminalCore/ITerminalInput.hpp b/src/cascadia/TerminalCore/ITerminalInput.hpp index 480494de1f6..6adbb0374b5 100644 --- a/src/cascadia/TerminalCore/ITerminalInput.hpp +++ b/src/cascadia/TerminalCore/ITerminalInput.hpp @@ -16,9 +16,10 @@ namespace Microsoft::Terminal::Core ITerminalInput& operator=(const ITerminalInput&) = default; ITerminalInput& operator=(ITerminalInput&&) = default; - virtual bool SendKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states, const bool keyDown) = 0; - virtual bool SendMouseEvent(const til::point viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta, const Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state) = 0; - virtual bool SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) = 0; + virtual [[nodiscard]] ::Microsoft::Console::VirtualTerminal::TerminalInput::OutputType SendKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states, const bool keyDown) = 0; + virtual [[nodiscard]] ::Microsoft::Console::VirtualTerminal::TerminalInput::OutputType SendMouseEvent(const til::point viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta, const Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state) = 0; + virtual [[nodiscard]] ::Microsoft::Console::VirtualTerminal::TerminalInput::OutputType SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) = 0; + virtual [[nodiscard]] ::Microsoft::Console::VirtualTerminal::TerminalInput::OutputType FocusChanged(const bool focused) = 0; [[nodiscard]] virtual HRESULT UserResize(const til::size size) noexcept = 0; virtual void UserScrollViewport(const int viewTop) = 0; @@ -26,8 +27,6 @@ namespace Microsoft::Terminal::Core virtual void TrySnapOnInput() = 0; - virtual void FocusChanged(const bool focused) = 0; - protected: ITerminalInput() = default; }; diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index fe2417b1f97..3b086e3c143 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -30,6 +30,15 @@ Terminal::Terminal() _renderSettings.SetColorAlias(ColorAlias::DefaultBackground, TextColor::DEFAULT_BACKGROUND, RGB(0, 0, 0)); } +#pragma warning(suppress : 26455) // default constructor is throwing, too much effort to rearrange at this time. +Terminal::Terminal(TestDummyMarker) : + Terminal{} +{ +#ifndef NDEBUG + _suppressLockChecks = true; +#endif +} + void Terminal::Create(til::size viewportSize, til::CoordType scrollbackLines, Renderer& renderer) { _mutableViewport = Viewport::FromDimensions({ 0, 0 }, viewportSize); @@ -425,24 +434,6 @@ void Terminal::Write(std::wstring_view stringView) } } -void Terminal::WritePastedText(std::wstring_view stringView) -{ - const auto option = ::Microsoft::Console::Utils::FilterOption::CarriageReturnNewline | - ::Microsoft::Console::Utils::FilterOption::ControlCodes; - - auto filtered = ::Microsoft::Console::Utils::FilterStringForPaste(stringView, option); - if (IsXtermBracketedPasteModeEnabled()) - { - filtered.insert(0, L"\x1b[200~"); - filtered.append(L"\x1b[201~"); - } - - if (_pfnWriteInput) - { - _pfnWriteInput(filtered); - } -} - // Method Description: // - Attempts to snap to the bottom of the buffer, if SnapOnInput is true. Does // nothing if SnapOnInput is set to false, or we're already at the bottom of @@ -606,10 +597,10 @@ std::optional Terminal::GetHyperlinkIntervalFromViewportPos // Return Value: // - true if we translated the key event, and it should not be processed any further. // - false if we did not translate the key, and it should be processed into a character. -bool Terminal::SendKeyEvent(const WORD vkey, - const WORD scanCode, - const ControlKeyStates states, - const bool keyDown) +TerminalInput::OutputType Terminal::SendKeyEvent(const WORD vkey, + const WORD scanCode, + const ControlKeyStates states, + const bool keyDown) { // GH#6423 - don't snap on this key if the key that was pressed was a // modifier key. We'll wait for a real keystroke to snap to the bottom. @@ -627,7 +618,7 @@ bool Terminal::SendKeyEvent(const WORD vkey, // GH#7064 if (vkey == 0 || vkey >= 0xff) { - return false; + return {}; } // While not explicitly permitted, a wide range of software, including Windows' own touch keyboard, @@ -637,7 +628,7 @@ bool Terminal::SendKeyEvent(const WORD vkey, const auto sc = scanCode ? scanCode : _ScanCodeFromVirtualKey(vkey); if (sc == 0) { - return false; + return {}; } const auto isAltOnlyPressed = states.IsAltPressed() && !states.IsCtrlPressed(); @@ -665,11 +656,11 @@ bool Terminal::SendKeyEvent(const WORD vkey, // See the method description for more information. if (keyDown && !isAltOnlyPressed && vkey != VK_TAB && ch != UNICODE_NULL) { - return false; + return {}; } const auto keyEv = SynthesizeKeyEvent(keyDown, 1, vkey, sc, ch, states.Value()); - return _handleTerminalInputResult(_getTerminalInput().HandleKey(keyEv)); + return _getTerminalInput().HandleKey(keyEv); } // Method Description: @@ -686,14 +677,14 @@ bool Terminal::SendKeyEvent(const WORD vkey, // Return Value: // - true if we translated the key event, and it should not be processed any further. // - false if we did not translate the key, and it should be processed into a character. -bool Terminal::SendMouseEvent(til::point viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta, const TerminalInput::MouseButtonState state) +TerminalInput::OutputType Terminal::SendMouseEvent(til::point viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta, const TerminalInput::MouseButtonState state) { // GH#6401: VT applications should be able to receive mouse events from outside the // terminal buffer. This is likely to happen when the user drags the cursor offscreen. // We shouldn't throw away perfectly good events when they're offscreen, so we just // clamp them to be within the range [(0, 0), (W, H)]. _GetMutableViewport().ToOrigin().Clamp(viewportPos); - return _handleTerminalInputResult(_getTerminalInput().HandleMouse(viewportPos, uiButton, GET_KEYSTATE_WPARAM(states.Value()), wheelDelta, state)); + return _getTerminalInput().HandleMouse(viewportPos, uiButton, GET_KEYSTATE_WPARAM(states.Value()), wheelDelta, state); } // Method Description: @@ -708,7 +699,7 @@ bool Terminal::SendMouseEvent(til::point viewportPos, const unsigned int uiButto // Return Value: // - true if we translated the character event, and it should not be processed any further. // - false otherwise. -bool Terminal::SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) +TerminalInput::OutputType Terminal::SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) { auto vkey = _TakeVirtualKeyFromLastKeyEvent(scanCode); if (vkey == 0 && scanCode != 0) @@ -746,7 +737,7 @@ bool Terminal::SendCharEvent(const wchar_t ch, const WORD scanCode, const Contro } const auto keyDown = SynthesizeKeyEvent(true, 1, vkey, scanCode, ch, states.Value()); - return _handleTerminalInputResult(_getTerminalInput().HandleKey(keyDown)); + return _getTerminalInput().HandleKey(keyDown); } // Method Description: @@ -757,9 +748,9 @@ bool Terminal::SendCharEvent(const wchar_t ch, const WORD scanCode, const Contro // - focused: true if we're focused, false otherwise. // Return Value: // - none -void Terminal::FocusChanged(const bool focused) +TerminalInput::OutputType Terminal::FocusChanged(const bool focused) { - _handleTerminalInputResult(_getTerminalInput().HandleFocus(focused)); + return _getTerminalInput().HandleFocus(focused); } // Method Description: @@ -882,20 +873,6 @@ catch (...) return UNICODE_INVALID; } -[[maybe_unused]] bool Terminal::_handleTerminalInputResult(TerminalInput::OutputType&& out) const -{ - if (out) - { - const auto& str = *out; - if (_pfnWriteInput && !str.empty()) - { - _pfnWriteInput(str); - } - return true; - } - return false; -} - // Method Description: // - It's possible for a single scan code on a keyboard to // produce different key codes depending on the keyboard state. @@ -933,7 +910,7 @@ WORD Terminal::_TakeVirtualKeyFromLastKeyEvent(const WORD scanCode) noexcept void Terminal::_assertLocked() const noexcept { #ifndef NDEBUG - if (!_readWriteLock.is_locked()) + if (!_suppressLockChecks && !_readWriteLock.is_locked()) { // __debugbreak() has the benefit over assert() that the debugger jumps right here to this line. // That way there's no need to first click any dialogues, etc. The disadvantage of course is that the @@ -943,6 +920,16 @@ void Terminal::_assertLocked() const noexcept #endif } +void Terminal::_assertUnlocked() const noexcept +{ +#ifndef NDEBUG + if (!_suppressLockChecks && _readWriteLock.is_locked()) + { + __debugbreak(); + } +#endif +} + // Method Description: // - Acquire a read lock on the terminal. // Return Value: diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 72c34d2f629..3e2ffe4c083 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -60,6 +60,10 @@ class Microsoft::Terminal::Core::Terminal final : using RenderSettings = Microsoft::Console::Render::RenderSettings; public: + struct TestDummyMarker + { + }; + static constexpr bool IsInputKey(WORD vkey) { return vkey != VK_CONTROL && @@ -77,6 +81,7 @@ class Microsoft::Terminal::Core::Terminal final : } Terminal(); + Terminal(TestDummyMarker); void Create(til::size viewportSize, til::CoordType scrollbackLines, @@ -98,9 +103,8 @@ class Microsoft::Terminal::Core::Terminal final : // Write comes from the PTY and goes to our parser to be stored in the output buffer void Write(std::wstring_view stringView); - // WritePastedText comes from our input and goes back to the PTY's input channel - void WritePastedText(std::wstring_view stringView); - + void _assertLocked() const noexcept; + void _assertUnlocked() const noexcept; [[nodiscard]] std::unique_lock LockForReading() const noexcept; [[nodiscard]] std::unique_lock LockForWriting() noexcept; til::recursive_ticket_lock_suspension SuspendLock() noexcept; @@ -167,9 +171,10 @@ class Microsoft::Terminal::Core::Terminal final : #pragma region ITerminalInput // These methods are defined in Terminal.cpp - bool SendKeyEvent(const WORD vkey, const WORD scanCode, const Microsoft::Terminal::Core::ControlKeyStates states, const bool keyDown) override; - bool SendMouseEvent(const til::point viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta, const Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state) override; - bool SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) override; + [[nodiscard]] ::Microsoft::Console::VirtualTerminal::TerminalInput::OutputType SendKeyEvent(const WORD vkey, const WORD scanCode, const Microsoft::Terminal::Core::ControlKeyStates states, const bool keyDown) override; + [[nodiscard]] ::Microsoft::Console::VirtualTerminal::TerminalInput::OutputType SendMouseEvent(const til::point viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta, const Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state) override; + [[nodiscard]] ::Microsoft::Console::VirtualTerminal::TerminalInput::OutputType SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) override; + [[nodiscard]] ::Microsoft::Console::VirtualTerminal::TerminalInput::OutputType FocusChanged(const bool focused) override; [[nodiscard]] HRESULT UserResize(const til::size viewportSize) noexcept override; void UserScrollViewport(const int viewTop) override; @@ -179,8 +184,6 @@ class Microsoft::Terminal::Core::Terminal final : bool IsTrackingMouseInput() const noexcept; bool ShouldSendAlternateScroll(const unsigned int uiButton, const int32_t delta) const noexcept; - void FocusChanged(const bool focused) override; - std::wstring GetHyperlinkAtViewportPosition(const til::point viewportPos); std::wstring GetHyperlinkAtBufferPosition(const til::point bufferPos); uint16_t GetHyperlinkIdAtViewportPosition(const til::point viewportPos); @@ -309,6 +312,10 @@ class Microsoft::Terminal::Core::Terminal final : const TextBuffer::TextAndColor RetrieveSelectedTextFromBuffer(bool trimTrailingWhitespace); #pragma endregion +#ifndef NDEBUG + bool _suppressLockChecks = false; +#endif + private: std::function _pfnWriteInput; std::function _pfnWarningBell; @@ -431,11 +438,9 @@ class Microsoft::Terminal::Core::Terminal final : static WORD _VirtualKeyFromCharacter(const wchar_t ch) noexcept; static wchar_t _CharacterFromKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states) noexcept; - [[maybe_unused]] bool _handleTerminalInputResult(::Microsoft::Console::VirtualTerminal::TerminalInput::OutputType&& out) const; void _StoreKeyEvent(const WORD vkey, const WORD scanCode) noexcept; WORD _TakeVirtualKeyFromLastKeyEvent(const WORD scanCode) noexcept; - void _assertLocked() const noexcept; Console::VirtualTerminal::TerminalInput& _getTerminalInput() noexcept; const Console::VirtualTerminal::TerminalInput& _getTerminalInput() const noexcept; diff --git a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp index 48eeacf53c6..cf15e3ba191 100644 --- a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp @@ -87,7 +87,7 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final TEST_METHOD_SETUP(MethodSetup) { // STEP 1: Set up the Terminal - term = std::make_unique(); + term = std::make_unique(Terminal::TestDummyMarker{}); emptyRenderer = std::make_unique(term.get()); term->Create({ TerminalViewWidth, TerminalViewHeight }, 100, *emptyRenderer); diff --git a/src/cascadia/UnitTests_TerminalCore/InputTest.cpp b/src/cascadia/UnitTests_TerminalCore/InputTest.cpp index 8c6d3245162..01436fcddf3 100644 --- a/src/cascadia/UnitTests_TerminalCore/InputTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/InputTest.cpp @@ -5,40 +5,33 @@ #include #include "../cascadia/TerminalCore/Terminal.hpp" -#include "../renderer/inc/DummyRenderer.hpp" -#include "consoletaeftemplates.hpp" using namespace WEX::Logging; using namespace WEX::TestExecution; using namespace Microsoft::Terminal::Core; -using namespace Microsoft::Console::Render; + +constexpr Microsoft::Console::VirtualTerminal::TerminalInput::OutputType unhandled() +{ + return {}; +} + +constexpr Microsoft::Console::VirtualTerminal::TerminalInput::OutputType escChar(const wchar_t wch) +{ + const wchar_t buffer[2]{ L'\x1b', wch }; + return { { &buffer[0], 2 } }; +} namespace TerminalCoreUnitTests { class InputTest { TEST_CLASS(InputTest); - TEST_CLASS_SETUP(ClassSetup) - { - DummyRenderer renderer; - term.Create({ 100, 100 }, 0, renderer); - auto inputFn = std::bind(&InputTest::_VerifyExpectedInput, this, std::placeholders::_1); - term.SetWriteInputCallback(inputFn); - return true; - }; TEST_METHOD(AltShiftKey); TEST_METHOD(InvalidKeyEvent); - void _VerifyExpectedInput(std::wstring_view actualInput) - { - VERIFY_ARE_EQUAL(expectedinput.size(), actualInput.size()); - VERIFY_ARE_EQUAL(expectedinput, actualInput); - }; - - Terminal term{}; - std::wstring expectedinput{}; + Terminal term{ Terminal::TestDummyMarker{} }; }; void InputTest::AltShiftKey() @@ -46,21 +39,17 @@ namespace TerminalCoreUnitTests // Tests GH:637 // Verify that Alt+a generates a lowercase a on the input - expectedinput = L"\x1b" - "a"; - VERIFY_IS_TRUE(term.SendCharEvent(L'a', 0, ControlKeyStates::LeftAltPressed)); + VERIFY_ARE_EQUAL(escChar(L'a'), term.SendCharEvent(L'a', 0, ControlKeyStates::LeftAltPressed)); // Verify that Alt+shift+a generates a uppercase a on the input - expectedinput = L"\x1b" - "A"; - VERIFY_IS_TRUE(term.SendCharEvent(L'A', 0, ControlKeyStates::LeftAltPressed | ControlKeyStates::ShiftPressed)); + VERIFY_ARE_EQUAL(escChar(L'A'), term.SendCharEvent(L'A', 0, ControlKeyStates::LeftAltPressed | ControlKeyStates::ShiftPressed)); } void InputTest::InvalidKeyEvent() { // Certain applications like AutoHotKey and its keyboard remapping feature, // send us key events using SendInput() whose values are outside of the valid range. - VERIFY_IS_FALSE(term.SendKeyEvent(0, 123, {}, true)); - VERIFY_IS_FALSE(term.SendKeyEvent(255, 123, {}, true)); + VERIFY_ARE_EQUAL(unhandled(), term.SendKeyEvent(0, 123, {}, true)); + VERIFY_ARE_EQUAL(unhandled(), term.SendKeyEvent(255, 123, {}, true)); } } diff --git a/src/cascadia/UnitTests_TerminalCore/ScreenSizeLimitsTest.cpp b/src/cascadia/UnitTests_TerminalCore/ScreenSizeLimitsTest.cpp index 8e9971abdff..667de3ad469 100644 --- a/src/cascadia/UnitTests_TerminalCore/ScreenSizeLimitsTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ScreenSizeLimitsTest.cpp @@ -38,7 +38,7 @@ void ScreenSizeLimitsTest::ScreenWidthAndHeightAreClampedToBounds() // Negative values for initial visible row count or column count // are clamped to 1. Too-large positive values are clamped to SHRT_MAX. auto negativeColumnsSettings = winrt::make(10000, 9999999, -1234); - Terminal negativeColumnsTerminal; + Terminal negativeColumnsTerminal{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &negativeColumnsTerminal }; negativeColumnsTerminal.CreateFromSettings(negativeColumnsSettings, renderer); auto actualDimensions = negativeColumnsTerminal.GetViewport().Dimensions(); @@ -47,7 +47,7 @@ void ScreenSizeLimitsTest::ScreenWidthAndHeightAreClampedToBounds() // Zero values are clamped to 1 as well. auto zeroRowsSettings = winrt::make(10000, 0, 9999999); - Terminal zeroRowsTerminal; + Terminal zeroRowsTerminal{ Terminal::TestDummyMarker{} }; zeroRowsTerminal.CreateFromSettings(zeroRowsSettings, renderer); actualDimensions = zeroRowsTerminal.GetViewport().Dimensions(); VERIFY_ARE_EQUAL(actualDimensions.height, 1, L"Row count clamped to 1"); @@ -64,32 +64,32 @@ void ScreenSizeLimitsTest::ScrollbackHistorySizeIsClampedToBounds() // Zero history size is acceptable. auto noHistorySettings = winrt::make(0, visibleRowCount, 100); - Terminal noHistoryTerminal; + Terminal noHistoryTerminal{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &noHistoryTerminal }; noHistoryTerminal.CreateFromSettings(noHistorySettings, renderer); VERIFY_ARE_EQUAL(noHistoryTerminal.GetTextBuffer().TotalRowCount(), visibleRowCount, L"History size of 0 is accepted"); // Negative history sizes are clamped to zero. auto negativeHistorySizeSettings = winrt::make(-100, visibleRowCount, 100); - Terminal negativeHistorySizeTerminal; + Terminal negativeHistorySizeTerminal{ Terminal::TestDummyMarker{} }; negativeHistorySizeTerminal.CreateFromSettings(negativeHistorySizeSettings, renderer); VERIFY_ARE_EQUAL(negativeHistorySizeTerminal.GetTextBuffer().TotalRowCount(), visibleRowCount, L"Negative history size is clamped to 0"); // History size + initial visible rows == SHRT_MAX is acceptable. auto maxHistorySizeSettings = winrt::make(SHRT_MAX - visibleRowCount, visibleRowCount, 100); - Terminal maxHistorySizeTerminal; + Terminal maxHistorySizeTerminal{ Terminal::TestDummyMarker{} }; maxHistorySizeTerminal.CreateFromSettings(maxHistorySizeSettings, renderer); VERIFY_ARE_EQUAL(maxHistorySizeTerminal.GetTextBuffer().TotalRowCount(), SHRT_MAX, L"History size == SHRT_MAX - initial row count is accepted"); // History size + initial visible rows == SHRT_MAX + 1 will be clamped slightly. auto justTooBigHistorySizeSettings = winrt::make(SHRT_MAX - visibleRowCount + 1, visibleRowCount, 100); - Terminal justTooBigHistorySizeTerminal; + Terminal justTooBigHistorySizeTerminal{ Terminal::TestDummyMarker{} }; justTooBigHistorySizeTerminal.CreateFromSettings(justTooBigHistorySizeSettings, renderer); VERIFY_ARE_EQUAL(justTooBigHistorySizeTerminal.GetTextBuffer().TotalRowCount(), SHRT_MAX, L"History size == 1 + SHRT_MAX - initial row count is clamped to SHRT_MAX - initial row count"); // Ridiculously large history sizes are also clamped. auto farTooBigHistorySizeSettings = winrt::make(99999999, visibleRowCount, 100); - Terminal farTooBigHistorySizeTerminal; + Terminal farTooBigHistorySizeTerminal{ Terminal::TestDummyMarker{} }; farTooBigHistorySizeTerminal.CreateFromSettings(farTooBigHistorySizeSettings, renderer); VERIFY_ARE_EQUAL(farTooBigHistorySizeTerminal.GetTextBuffer().TotalRowCount(), SHRT_MAX, L"History size that is far too large is clamped to SHRT_MAX - initial row count"); } @@ -111,7 +111,7 @@ void ScreenSizeLimitsTest::ResizeIsClampedToBounds() auto settings = winrt::make(historySize, initialVisibleRowCount, initialVisibleColCount); Log::Comment(L"First create a terminal with fewer than SHRT_MAX lines"); - Terminal terminal; + Terminal terminal{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &terminal }; terminal.CreateFromSettings(settings, renderer); VERIFY_ARE_EQUAL(terminal.GetTextBuffer().TotalRowCount(), historySize + initialVisibleRowCount); diff --git a/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp b/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp index 12223e94a63..4b456e7cd5b 100644 --- a/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp @@ -107,7 +107,7 @@ class TerminalCoreUnitTests::ScrollTest final TEST_METHOD_SETUP(MethodSetup) { - _term = std::make_unique<::Microsoft::Terminal::Core::Terminal>(); + _term = std::make_unique<::Microsoft::Terminal::Core::Terminal>(Terminal::TestDummyMarker{}); _scrollBarNotification = std::make_shared>(); _term->SetScrollPositionChangedCallback([scrollBarNotification = _scrollBarNotification](const int top, const int height, const int bottom) { diff --git a/src/cascadia/UnitTests_TerminalCore/SelectionTest.cpp b/src/cascadia/UnitTests_TerminalCore/SelectionTest.cpp index 894c5b7764f..195eb17fcc3 100644 --- a/src/cascadia/UnitTests_TerminalCore/SelectionTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/SelectionTest.cpp @@ -46,7 +46,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(SelectUnit) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -59,7 +59,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(SelectArea) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -113,7 +113,7 @@ namespace TerminalCoreUnitTests // Test SetSelectionAnchor(til::point) and SetSelectionEnd(til::point) // Behavior: clamp coord to viewport. auto ValidateSingleClickSelection = [&](til::CoordType scrollback, const til::inclusive_rect& expected) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 10, 10 }, scrollback, renderer); @@ -126,7 +126,7 @@ namespace TerminalCoreUnitTests // Behavior: clamp coord to viewport. // Then, do double click selection. auto ValidateDoubleClickSelection = [&](til::CoordType scrollback, const til::inclusive_rect& expected) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 10, 10 }, scrollback, renderer); @@ -138,7 +138,7 @@ namespace TerminalCoreUnitTests // Behavior: clamp coord to viewport. // Then, do triple click selection. auto ValidateTripleClickSelection = [&](til::CoordType scrollback, const til::inclusive_rect& expected) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 10, 10 }, scrollback, renderer); @@ -171,7 +171,7 @@ namespace TerminalCoreUnitTests - All selection expansion functions will operate as if they were performed at the boundary */ - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 10, 10 }, 0, renderer); @@ -213,7 +213,7 @@ namespace TerminalCoreUnitTests - All selection expansion functions will operate as if they were performed at the boundary */ - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 10, 10 }, 0, renderer); @@ -299,7 +299,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(SelectBoxArea) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -335,7 +335,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(SelectAreaAfterScroll) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; til::CoordType scrollbackLines = 5; term.Create({ 100, 100 }, scrollbackLines, renderer); @@ -385,7 +385,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(SelectWideGlyph_Trailing) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -408,7 +408,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(SelectWideGlyph_Leading) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -431,7 +431,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(SelectWideGlyphsInBoxSelection) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -486,7 +486,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(DoubleClick_GeneralCase) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -509,7 +509,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(DoubleClick_Delimiter) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -530,7 +530,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(DoubleClick_DelimiterClass) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -558,7 +558,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(DoubleClickDrag_Right) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -587,7 +587,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(DoubleClickDrag_Left) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -616,7 +616,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(TripleClick_GeneralCase) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -630,7 +630,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(TripleClickDrag_Horizontal) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -647,7 +647,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(TripleClickDrag_Vertical) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -675,7 +675,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(ShiftClick) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -792,7 +792,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(Pivot) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); diff --git a/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp b/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp index 683740c155b..9f002db5098 100644 --- a/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp @@ -48,7 +48,7 @@ using namespace TerminalCoreUnitTests; void TerminalApiTest::SetColorTableEntry() { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -67,7 +67,7 @@ void TerminalApiTest::SetColorTableEntry() // PrintString() is called with more code units than the buffer width. void TerminalApiTest::PrintStringOfSurrogatePairs() { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 3, renderer); @@ -134,7 +134,7 @@ void TerminalApiTest::PrintStringOfSurrogatePairs() void TerminalApiTest::CursorVisibility() { // GH#3093 - Cursor Visibility and On states shouldn't affect each other - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -166,7 +166,7 @@ void TerminalApiTest::CursorVisibility() void TerminalApiTest::CursorVisibilityViaStateMachine() { // This is a nearly literal copy-paste of ScreenBufferTests::TestCursorIsOn, adapted for the Terminal - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -218,7 +218,7 @@ void TerminalApiTest::CursorVisibilityViaStateMachine() void TerminalApiTest::CheckDoubleWidthCursor() { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -262,7 +262,7 @@ void TerminalCoreUnitTests::TerminalApiTest::AddHyperlink() { // This is a nearly literal copy-paste of ScreenBufferTests::TestAddHyperlink, adapted for the Terminal - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -288,7 +288,7 @@ void TerminalCoreUnitTests::TerminalApiTest::AddHyperlinkCustomId() { // This is a nearly literal copy-paste of ScreenBufferTests::TestAddHyperlinkCustomId, adapted for the Terminal - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -316,7 +316,7 @@ void TerminalCoreUnitTests::TerminalApiTest::AddHyperlinkCustomIdDifferentUri() { // This is a nearly literal copy-paste of ScreenBufferTests::TestAddHyperlinkCustomId, adapted for the Terminal - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -344,7 +344,7 @@ void TerminalCoreUnitTests::TerminalApiTest::AddHyperlinkCustomIdDifferentUri() void TerminalCoreUnitTests::TerminalApiTest::SetTaskbarProgress() { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -415,7 +415,7 @@ void TerminalCoreUnitTests::TerminalApiTest::SetTaskbarProgress() void TerminalCoreUnitTests::TerminalApiTest::SetWorkingDirectory() { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); diff --git a/src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp b/src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp index 1ea9e0be168..d789bfc5583 100644 --- a/src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp +++ b/src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp @@ -56,7 +56,7 @@ class TerminalCoreUnitTests::TerminalBufferTests final TEST_METHOD_SETUP(MethodSetup) { // STEP 1: Set up the Terminal - term = std::make_unique(); + term = std::make_unique(Terminal::TestDummyMarker{}); emptyRenderer = std::make_unique(term.get()); term->Create({ TerminalViewWidth, TerminalViewHeight }, TerminalHistoryLength, *emptyRenderer); return true; From 28a1ecbdaadafbd0226ad1ff211433046aa6eba5 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Wed, 8 Nov 2023 10:29:01 -0600 Subject: [PATCH 057/167] releng: add --first-parent to the scripts that use git log (#16279) It makes the output less cluttered and more correct (for example: ServicingPipeline no longer tries to service two copies of each commit if there's a merge in the history...) (cherry picked from commit 18b0ecbb2a1b22cbbc02961a9166db0e3a865ab1) Service-Card-Id: 91042450 Service-Version: 1.19 --- tools/Get-OSSConhostLog.ps1 | 2 +- tools/ReleaseEngineering/New-TerminalStackedChangelog.ps1 | 2 +- tools/ReleaseEngineering/ServicingPipeline.ps1 | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/Get-OSSConhostLog.ps1 b/tools/Get-OSSConhostLog.ps1 index 4c206effa8e..46cd8aca1f2 100644 --- a/tools/Get-OSSConhostLog.ps1 +++ b/tools/Get-OSSConhostLog.ps1 @@ -43,7 +43,7 @@ Function Get-Git2GitIgnoresAsExcludes() { $Excludes = Get-Git2GitIgnoresAsExcludes Write-Verbose "IGNORING: $Excludes" -$Entries = & git log $RevisionRange "--pretty=format:%an%x1C%ae%x1C%s" -- $Excludes | +$Entries = & git log $RevisionRange --first-parent "--pretty=format:%an%x1C%ae%x1C%s" -- $Excludes | ConvertFrom-CSV -Delimiter "`u{001C}" -Header Author,Email,Subject Write-Verbose ("{0} unfiltered log entries" -f $Entries.Count) diff --git a/tools/ReleaseEngineering/New-TerminalStackedChangelog.ps1 b/tools/ReleaseEngineering/New-TerminalStackedChangelog.ps1 index 7f442f2ccf7..4d48b18bbf0 100644 --- a/tools/ReleaseEngineering/New-TerminalStackedChangelog.ps1 +++ b/tools/ReleaseEngineering/New-TerminalStackedChangelog.ps1 @@ -62,7 +62,7 @@ ForEach ($RevisionRange in $RevisionRanges) { # - %ae: author email # - %x1C: another FS # - %s: subject, the title of the commit - $NewEntries = & git log $RevisionRange "--pretty=format:%an%x1C%ae%x1C%s" | + $NewEntries = & git log $RevisionRange --first-parent "--pretty=format:%an%x1C%ae%x1C%s" | ConvertFrom-CSV -Delimiter "`u{001C}" -Header Author,Email,Subject $Entries += $NewEntries | % { [PSCustomObject]@{ diff --git a/tools/ReleaseEngineering/ServicingPipeline.ps1 b/tools/ReleaseEngineering/ServicingPipeline.ps1 index d7eb2cfebc2..2adf3843a0a 100644 --- a/tools/ReleaseEngineering/ServicingPipeline.ps1 +++ b/tools/ReleaseEngineering/ServicingPipeline.ps1 @@ -89,7 +89,7 @@ $Cards = Get-GithubProjectCard -ColumnId $ToPickColumn.id & git fetch --all 2>&1 | Out-Null -$Entries = @(& git log $SourceBranch --grep "(#\($($Cards.Number -Join "\|")\))" "--pretty=format:%H%x1C%s" | +$Entries = @(& git log $SourceBranch --first-parent --grep "(#\($($Cards.Number -Join "\|")\))" "--pretty=format:%H%x1C%s" | ConvertFrom-CSV -Delimiter "`u{001C}" -Header CommitID,Subject) [Array]::Reverse($Entries) From a7409ea4d3f8523ae05201e0e95c087a381bb3a0 Mon Sep 17 00:00:00 2001 From: PankajBhojwani Date: Wed, 8 Nov 2023 09:12:13 -0800 Subject: [PATCH 058/167] Update Azure Cloud Shell for their new URI format (#16247) The Azure cloud shell team made some API changes that required us to format our requests a little differently. This PR makes those changes (more info in the comments in the code) Closes #16098 (cherry picked from commit 5a9f3529d79d2168021be4b52202f9ad230de203) Service-Card-Id: 90985893 Service-Version: 1.19 --- .github/actions/spelling/allow/allow.txt | 4 ++ .../TerminalConnection/AzureConnection.cpp | 53 +++++++++++++++++-- .../TerminalConnection/AzureConnection.h | 2 +- 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/.github/actions/spelling/allow/allow.txt b/.github/actions/spelling/allow/allow.txt index 28c46840207..a5d5bfcab3e 100644 --- a/.github/actions/spelling/allow/allow.txt +++ b/.github/actions/spelling/allow/allow.txt @@ -1,3 +1,4 @@ +aci admins allcolors Apc @@ -8,6 +9,7 @@ breadcrumbs bsd calt ccmp +ccon changelog clickable clig @@ -91,6 +93,7 @@ reserialize reserializes rlig runtimes +servicebus shcha slnt Sos @@ -116,6 +119,7 @@ vsdevcmd walkthrough walkthroughs We'd +westus wildcards XBox YBox diff --git a/src/cascadia/TerminalConnection/AzureConnection.cpp b/src/cascadia/TerminalConnection/AzureConnection.cpp index 3844979b488..708518509b5 100644 --- a/src/cascadia/TerminalConnection/AzureConnection.cpp +++ b/src/cascadia/TerminalConnection/AzureConnection.cpp @@ -398,6 +398,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation switch (bufferType) { + case WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE: case WINHTTP_WEB_SOCKET_UTF8_FRAGMENT_BUFFER_TYPE: case WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE: { @@ -797,7 +798,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation // - an optional HTTP method (defaults to POST if content is present, GET otherwise) // Return value: // - the response from the server as a json value - WDJ::JsonObject AzureConnection::_SendRequestReturningJson(std::wstring_view uri, const WWH::IHttpContent& content, WWH::HttpMethod method) + WDJ::JsonObject AzureConnection::_SendRequestReturningJson(std::wstring_view uri, const WWH::IHttpContent& content, WWH::HttpMethod method, const Windows::Foundation::Uri referer) { if (!method) { @@ -810,6 +811,11 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation auto headers{ request.Headers() }; headers.Accept().TryParseAdd(L"application/json"); + if (referer) + { + headers.Referer(referer); + } + const auto response{ _httpClient.SendRequestAsync(request).get() }; const auto string{ response.Content().ReadAsStringAsync().get() }; const auto jsonResult{ WDJ::JsonObject::Parse(string) }; @@ -974,17 +980,56 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation auto uri{ fmt::format(L"{}terminals?cols={}&rows={}&version=2019-01-01&shell={}", _cloudShellUri, _initialCols, _initialRows, shellType) }; WWH::HttpStringContent content{ - L"", + L"{}", WSS::UnicodeEncoding::Utf8, // LOAD-BEARING. the API returns "'content-type' should be 'application/json' or 'multipart/form-data'" L"application/json" }; - const auto terminalResponse = _SendRequestReturningJson(uri, content); + const auto terminalResponse = _SendRequestReturningJson(uri, content, WWH::HttpMethod::Post(), Windows::Foundation::Uri(_cloudShellUri)); _terminalID = terminalResponse.GetNamedString(L"id"); + // we have to do some post-handling to get the proper socket endpoint + // the logic here is based on the way the cloud shell team itself does it + winrt::hstring finalSocketUri; + const std::wstring_view wCloudShellUri{ _cloudShellUri }; + + if (wCloudShellUri.find(L"servicebus") == std::wstring::npos) + { + // wCloudShellUri does not contain the word "servicebus", we can just use it to make the final URI + + // remove the "https" from the cloud shell URI + const auto uriWithoutProtocol = wCloudShellUri.substr(5); + + finalSocketUri = fmt::format(FMT_COMPILE(L"wss{}terminals/{}"), uriWithoutProtocol, _terminalID); + } + else + { + // if wCloudShellUri contains the word "servicebus", that means the returned socketUri is of the form + // wss://ccon-prod-westus-aci-03.servicebus.windows.net/cc-AAAA-AAAAAAAA//aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + // we need to change it to: + // wss://ccon-prod-westus-aci-03.servicebus.windows.net/$hc/cc-AAAA-AAAAAAAA/terminals/aaaaaaaaaaaaaaaaaaaaaa + + const auto socketUri = terminalResponse.GetNamedString(L"socketUri"); + const std::wstring_view wSocketUri{ socketUri }; + + // get the substring up until the ".net" + const auto dotNetStart = wSocketUri.find(L".net"); + THROW_HR_IF(E_UNEXPECTED, dotNetStart == std::wstring::npos); + const auto dotNetEnd = dotNetStart + 4; + const auto wSocketUriBody = wSocketUri.substr(0, dotNetEnd); + + // get the portion between the ".net" and the "//" (this is the cc-AAAA-AAAAAAAA part) + const auto lastDoubleSlashPos = wSocketUri.find_last_of(L"//"); + THROW_HR_IF(E_UNEXPECTED, lastDoubleSlashPos == std::wstring::npos); + const auto wSocketUriMiddle = wSocketUri.substr(dotNetEnd, lastDoubleSlashPos - (dotNetEnd)); + + // piece together the final uri, adding in the "$hc" and "terminals" where needed + finalSocketUri = fmt::format(FMT_COMPILE(L"{}/$hc{}terminals/{}"), wSocketUriBody, wSocketUriMiddle, _terminalID); + } + // Return the uri - return terminalResponse.GetNamedString(L"socketUri"); + return winrt::hstring{ finalSocketUri }; } // Method description: diff --git a/src/cascadia/TerminalConnection/AzureConnection.h b/src/cascadia/TerminalConnection/AzureConnection.h index cedd76757af..9d179073019 100644 --- a/src/cascadia/TerminalConnection/AzureConnection.h +++ b/src/cascadia/TerminalConnection/AzureConnection.h @@ -68,7 +68,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation void _WriteStringWithNewline(const std::wstring_view str); void _WriteCaughtExceptionRecord(); - winrt::Windows::Data::Json::JsonObject _SendRequestReturningJson(std::wstring_view uri, const winrt::Windows::Web::Http::IHttpContent& content = nullptr, winrt::Windows::Web::Http::HttpMethod method = nullptr); + winrt::Windows::Data::Json::JsonObject _SendRequestReturningJson(std::wstring_view uri, const winrt::Windows::Web::Http::IHttpContent& content = nullptr, winrt::Windows::Web::Http::HttpMethod method = nullptr, const winrt::Windows::Foundation::Uri referer = nullptr); void _setAccessToken(std::wstring_view accessToken); winrt::Windows::Data::Json::JsonObject _GetDeviceCode(); winrt::Windows::Data::Json::JsonObject _WaitForUser(const winrt::hstring& deviceCode, int pollInterval, int expiresIn); From 55a9874d5ca6cb626de42c9c2756f1e3283e988f Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Thu, 9 Nov 2023 19:10:35 -0600 Subject: [PATCH 059/167] Fix leak in buffering text for UIA when unfocused (#16251) Notes in #16217 have the investigation. TL;DR: we'd always buffer text. Even if we're disabled (unfocused). When we're disabled, we'd _never_ clear the buffered text. Oops. Closes #16217 (cherry picked from commit d14524cd4cc4970bb1b6456f9667e2dd661b9854) Service-Card-Id: 91033138 Service-Version: 1.19 --- src/renderer/uia/UiaRenderer.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/renderer/uia/UiaRenderer.cpp b/src/renderer/uia/UiaRenderer.cpp index e58c227c561..f39bcbe4fbf 100644 --- a/src/renderer/uia/UiaRenderer.cpp +++ b/src/renderer/uia/UiaRenderer.cpp @@ -47,6 +47,12 @@ UiaEngine::UiaEngine(IUiaEventDispatcher* dispatcher) : [[nodiscard]] HRESULT UiaEngine::Disable() noexcept { _isEnabled = false; + + // If we had buffered any text from NotifyNewText, dump it. When we do come + // back around to actually paint, we will just no-op. No sense in keeping + // the data buffered. + _newOutput = std::wstring{}; + return S_OK; } @@ -171,6 +177,10 @@ CATCH_RETURN(); [[nodiscard]] HRESULT UiaEngine::NotifyNewText(const std::wstring_view newText) noexcept try { + // GH#16217 - don't even buffer this text if we're disabled. We may never + // come around to write it out. + RETURN_HR_IF(S_FALSE, !_isEnabled); + if (!newText.empty()) { _newOutput.append(newText); From 37100034bf5e3b5fbfadb1f4cb11884c50c9347f Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Wed, 15 Nov 2023 14:48:54 -0600 Subject: [PATCH 060/167] canary: include the correct handoff CLSIDs (#16317) Canary still advertised the Dev CLSIDs, so it didn't work as DefTerm. Closes #16316 --- src/cascadia/CascadiaPackage/Package-Can.appxmanifest | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cascadia/CascadiaPackage/Package-Can.appxmanifest b/src/cascadia/CascadiaPackage/Package-Can.appxmanifest index 66564e799ff..69fd63332bc 100644 --- a/src/cascadia/CascadiaPackage/Package-Can.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Can.appxmanifest @@ -93,7 +93,7 @@ Description="Console host built from microsoft/terminal open source repository" PublicFolder="Public"> - {1F9F2BF5-5BC3-4F17-B0E6-912413F1F451} + {A854D02A-F2FE-44A5-BB24-D03F4CF830D4} @@ -104,7 +104,7 @@ Description="Terminal host built from microsoft/terminal open source repository" PublicFolder="Public"> - {051F34EE-C1FD-4B19-AF75-9BA54648434C} + {1706609C-A4CE-4C0D-B7D2-C19BF66398A5} From 65e19ddaf0edab64b6361a17ecb0f6e67179aaa6 Mon Sep 17 00:00:00 2001 From: Dustin Howett Date: Wed, 15 Nov 2023 21:32:10 +0000 Subject: [PATCH 061/167] Merged PR 9880704: [Git2Git] conhost: remove all EOL velocity keys I've also removed all of the supporting code. Related work items: MSFT-47555635 Retrieved from https://microsoft.visualstudio.com os.2020 OS official/rs_we_adept_e4d2 f8ad0110fd81d1b848224158c8f95724f34b1db2 --- src/host/exe/exemain.cpp | 2 +- src/host/srvinit.cpp | 4 ++-- src/inc/conint.h | 6 ------ src/internal/stubs.cpp | 15 --------------- src/propsheet/console.cpp | 19 +++---------------- 5 files changed, 6 insertions(+), 40 deletions(-) diff --git a/src/host/exe/exemain.cpp b/src/host/exe/exemain.cpp index 7aaa45271d9..a30680a40a5 100644 --- a/src/host/exe/exemain.cpp +++ b/src/host/exe/exemain.cpp @@ -276,7 +276,7 @@ int CALLBACK wWinMain( { // Only try to register as a handoff target if we are NOT a part of Windows. #if TIL_FEATURE_RECEIVEINCOMINGHANDOFF_ENABLED - if (args.ShouldRunAsComServer() && Microsoft::Console::Internal::DefaultApp::CheckDefaultAppPolicy()) + if (args.ShouldRunAsComServer()) { try { diff --git a/src/host/srvinit.cpp b/src/host/srvinit.cpp index 284f4483820..afb505ad144 100644 --- a/src/host/srvinit.cpp +++ b/src/host/srvinit.cpp @@ -64,7 +64,7 @@ try // Check if this conhost is allowed to delegate its activities to another. // If so, look up the registered default console handler. - if (Globals.delegationPair.IsUndecided() && Microsoft::Console::Internal::DefaultApp::CheckDefaultAppPolicy()) + if (Globals.delegationPair.IsUndecided()) { Globals.delegationPair = DelegationConfig::s_GetDelegationPair(); @@ -82,7 +82,7 @@ try // If we looked up the registered defterm pair, and it was left as the default (missing or {0}), // AND velocity is enabled for DxD, then we switch the delegation pair to Terminal and // mark that we should check that class for the marker interface later. - if (Globals.delegationPair.IsDefault() && Microsoft::Console::Internal::DefaultApp::CheckShouldTerminalBeDefault()) + if (Globals.delegationPair.IsDefault()) { Globals.delegationPair = DelegationConfig::TerminalDelegationPair; Globals.defaultTerminalMarkerCheckRequired = true; diff --git a/src/inc/conint.h b/src/inc/conint.h index 2a3c8d0fd54..68d9745fcfb 100644 --- a/src/inc/conint.h +++ b/src/inc/conint.h @@ -44,10 +44,4 @@ namespace Microsoft::Console::Internal { [[nodiscard]] HRESULT TrySetDarkMode(HWND hwnd) noexcept; } - - namespace DefaultApp - { - [[nodiscard]] bool CheckDefaultAppPolicy() noexcept; - [[nodiscard]] bool CheckShouldTerminalBeDefault() noexcept; - } } diff --git a/src/internal/stubs.cpp b/src/internal/stubs.cpp index f0cf811b017..c7b5dcfaff5 100644 --- a/src/internal/stubs.cpp +++ b/src/internal/stubs.cpp @@ -29,18 +29,3 @@ void EdpPolicy::AuditClipboard(const std::wstring_view /*destinationName*/) noex { return S_FALSE; } - -[[nodiscard]] bool DefaultApp::CheckDefaultAppPolicy() noexcept -{ - // True so propsheet will show configuration options but be sure that - // the open one won't attempt handoff from double click of OpenConsole.exe - return true; -} - -[[nodiscard]] bool DefaultApp::CheckShouldTerminalBeDefault() noexcept -{ - // False since setting Terminal as the default app is an OS feature and probably - // should not be done in the open source conhost. We can always decide to turn it - // on in the future though. - return false; -} diff --git a/src/propsheet/console.cpp b/src/propsheet/console.cpp index 68b146c2e11..d6058440590 100644 --- a/src/propsheet/console.cpp +++ b/src/propsheet/console.cpp @@ -96,10 +96,7 @@ void SaveConsoleSettingsIfNeeded(const HWND hwnd) gpStateInfo->FaceName[0] = TEXT('\0'); } - if (Microsoft::Console::Internal::DefaultApp::CheckDefaultAppPolicy()) - { - LOG_IF_FAILED(DelegationConfig::s_SetDefaultByPackage(g_selectedPackage)); - } + LOG_IF_FAILED(DelegationConfig::s_SetDefaultByPackage(g_selectedPackage)); if (gpStateInfo->LinkTitle != nullptr) { @@ -552,14 +549,7 @@ BOOL PopulatePropSheetPageArray(_Out_writes_(cPsps) PROPSHEETPAGE* pPsp, const s { pTerminalPage->dwSize = sizeof(PROPSHEETPAGE); pTerminalPage->hInstance = ghInstance; - if (Microsoft::Console::Internal::DefaultApp::CheckDefaultAppPolicy()) - { - pTerminalPage->pszTemplate = MAKEINTRESOURCE(DID_TERMINAL_WITH_DEFTERM); - } - else - { - pTerminalPage->pszTemplate = MAKEINTRESOURCE(DID_TERMINAL); - } + pTerminalPage->pszTemplate = MAKEINTRESOURCE(DID_TERMINAL_WITH_DEFTERM); pTerminalPage->pfnDlgProc = TerminalDlgProc; pTerminalPage->lParam = TERMINAL_PAGE_INDEX; pTerminalPage->dwFlags = PSP_DEFAULT; @@ -629,10 +619,7 @@ INT_PTR ConsolePropertySheet(__in HWND hWnd, __in PCONSOLE_STATE_INFO pStateInfo // Find the available default console/terminal packages // - if (Microsoft::Console::Internal::DefaultApp::CheckDefaultAppPolicy()) - { - LOG_IF_FAILED(DelegationConfig::s_GetAvailablePackages(g_availablePackages, g_selectedPackage)); - } + LOG_IF_FAILED(DelegationConfig::s_GetAvailablePackages(g_availablePackages, g_selectedPackage)); // // Get the current page number From 86fb9b44787accd09c5943a506e27eb4c8e573c0 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Wed, 15 Nov 2023 19:13:03 -0600 Subject: [PATCH 062/167] Add a magic incantation to tell the Store we support Server (#16306) I find it somewhat silly that (1) this isn't documented anywhere and (2) installing the "desktop experience" packages for Server doesn't automatically add support for the `Windows.Desktop` platform... Oh well. I'm going to roll this one out via Preview first, because if the store blows up on it I would rather it not be during Stable roll-out. --- src/cascadia/CascadiaPackage/Package-Can.appxmanifest | 1 + src/cascadia/CascadiaPackage/Package-Dev.appxmanifest | 1 + src/cascadia/CascadiaPackage/Package-Pre.appxmanifest | 1 + src/cascadia/CascadiaPackage/Package.appxmanifest | 1 + 4 files changed, 4 insertions(+) diff --git a/src/cascadia/CascadiaPackage/Package-Can.appxmanifest b/src/cascadia/CascadiaPackage/Package-Can.appxmanifest index 69fd63332bc..164ce0bca43 100644 --- a/src/cascadia/CascadiaPackage/Package-Can.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Can.appxmanifest @@ -40,6 +40,7 @@ + diff --git a/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest b/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest index d8ceefaeef6..53fc49c7833 100644 --- a/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest @@ -39,6 +39,7 @@ + diff --git a/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest b/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest index 3a7bf26e0f7..c7956c34576 100644 --- a/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest @@ -40,6 +40,7 @@ + diff --git a/src/cascadia/CascadiaPackage/Package.appxmanifest b/src/cascadia/CascadiaPackage/Package.appxmanifest index f04863da27c..98c10c860a0 100644 --- a/src/cascadia/CascadiaPackage/Package.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package.appxmanifest @@ -40,6 +40,7 @@ + From b780b445284597c6fe9d423126e2afd90c329265 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 16 Nov 2023 22:27:33 +0100 Subject: [PATCH 063/167] Fix nearby fonts for DxEngine again (#16323) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The nearby font loading has to be outside of the try/catch of the `_FindFontFace` call, because it'll throw for broken font files. But in my previous PR I had overlooked that the font variant loop modifies the only copy of the face name that we got and was in the same try/catch. That's bad, because once we get to the nearby search code, the face name will be invalid. This commit fixes the issue by wrapping each individual `_FindFontFace` call in a try/catch block. Closes #16322 ## Validation Steps Performed * Remove every single copy of Windows Terminal from your system * Manually clean up Cascadia .ttf files because they aren't gone * Destroy your registry by manually removing appx references (fun!) * Put the 4 Cascadia .ttf files into the Dev app AppX directory * Launch * No warning ✅ --- src/renderer/dx/DxFontInfo.cpp | 71 ++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/src/renderer/dx/DxFontInfo.cpp b/src/renderer/dx/DxFontInfo.cpp index b1d4844a601..b0f79cde39a 100644 --- a/src/renderer/dx/DxFontInfo.cpp +++ b/src/renderer/dx/DxFontInfo.cpp @@ -126,44 +126,52 @@ void DxFontInfo::SetFromEngine(const std::wstring_view familyName, try { face = _FindFontFace(localeName); + } + CATCH_LOG(); - if (!face) + if constexpr (Feature_NearbyFontLoading::IsEnabled()) + { + try { - // If we missed, try looking a little more by trimming the last word off the requested family name a few times. - // Quite often, folks are specifying weights or something in the familyName and it causes failed resolution and - // an unexpected error dialog. We theoretically could detect the weight words and convert them, but this - // is the quick fix for the majority scenario. - // The long/full fix is backlogged to GH#9744 - // Also this doesn't count as a fallback because we don't want to annoy folks with the warning dialog over - // this resolution. - while (!face && !_familyName.empty()) + if (!face) { - const auto lastSpace = _familyName.find_last_of(UNICODE_SPACE); - - // value is unsigned and npos will be greater than size. - // if we didn't find anything to trim, leave. - if (lastSpace >= _familyName.size()) - { - break; - } - - // trim string down to just before the found space - // (space found at 6... trim from 0 for 6 length will give us 0-5 as the new string) - _familyName = _familyName.substr(0, lastSpace); - - // Try to find it with the shortened family name + _fontCollection = FontCache::GetCached(); face = _FindFontFace(localeName); } } + CATCH_LOG(); } - CATCH_LOG(); - if constexpr (Feature_NearbyFontLoading::IsEnabled()) + if (!face) { - if (!face) + // If we missed, try looking a little more by trimming the last word off the requested family name a few times. + // Quite often, folks are specifying weights or something in the familyName and it causes failed resolution and + // an unexpected error dialog. We theoretically could detect the weight words and convert them, but this + // is the quick fix for the majority scenario. + // The long/full fix is backlogged to GH#9744 + // Also this doesn't count as a fallback because we don't want to annoy folks with the warning dialog over + // this resolution. + while (!face && !_familyName.empty()) { - _fontCollection = FontCache::GetCached(); - face = _FindFontFace(localeName); + const auto lastSpace = _familyName.find_last_of(UNICODE_SPACE); + + // value is unsigned and npos will be greater than size. + // if we didn't find anything to trim, leave. + if (lastSpace >= _familyName.size()) + { + break; + } + + // trim string down to just before the found space + // (space found at 6... trim from 0 for 6 length will give us 0-5 as the new string) + _familyName = _familyName.substr(0, lastSpace); + + try + { + // Try to find it with the shortened family name + face = _FindFontFace(localeName); + } + CATCH_LOG(); } } @@ -176,7 +184,12 @@ void DxFontInfo::SetFromEngine(const std::wstring_view familyName, { _familyName = fallbackFace; - face = _FindFontFace(localeName); + try + { + face = _FindFontFace(localeName); + } + CATCH_LOG(); + if (face) { _didFallback = true; From 376737e54afc301ced37d5416bc836eadbc611d2 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 16 Nov 2023 22:28:37 +0100 Subject: [PATCH 064/167] Hotfix recent AuditMode failures on CI (#16325) Our CI seems to have had an update recently to around VS 17.7. That version contains a faulty implementation for C26478 and C26494. The issue has been fixed in VS 17.8 and later. --- src/common.build.pre.props | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/common.build.pre.props b/src/common.build.pre.props index a1cf37adbda..17d4b603927 100644 --- a/src/common.build.pre.props +++ b/src/common.build.pre.props @@ -131,8 +131,12 @@ C26456: Operator 'A' hides a non-virtual operator 'B' (c.128) I think these rules are for when you fully bought into OOP? We didn't and it breaks WRL and large parts of conhost code. + C26478: Don't use std::move on constant variables. (es.56). + This diagnostic is broken in VS 17.7 which our CI currently uses. It's fixed in 17.8. + C26494: Variable 'index' is uninitialized. Always initialize an object (type. 5). + This diagnostic is broken in VS 17.7 which our CI currently uses. It's fixed in 17.8. --> - 4201;4312;4467;5105;26434;26445;26456;%(DisableSpecificWarnings) + 4201;4312;4467;5105;26434;26445;26456;26478;26494;%(DisableSpecificWarnings) _WINDOWS;EXTERNAL_BUILD;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;%(PreprocessorDefinitions) true precomp.h From 12318d97d0d9df09e05b5dd64622ff4f5b09382b Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 21 Nov 2023 12:05:07 -0600 Subject: [PATCH 065/167] test: Add an LLM-powered bot to detect dupes (#16304) Just like in https://github.com/microsoft/WSL/pull/10745 We're working with the WSL team to figure out if we can use a LLM to help us triage. This _should_ just comment on issues, if it finds something similar on the backlog. --- .github/workflows/similarIssues.yml | 31 +++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/workflows/similarIssues.yml diff --git a/.github/workflows/similarIssues.yml b/.github/workflows/similarIssues.yml new file mode 100644 index 00000000000..b0fcc3d306b --- /dev/null +++ b/.github/workflows/similarIssues.yml @@ -0,0 +1,31 @@ +name: GitGudSimilarIssues comments + +on: + issues: + types: [opened] + +jobs: + getsimilarissues: + runs-on: ubuntu-latest + outputs: + message: ${{ steps.getbody.outputs.message }} + steps: + - id: getbody + uses: craigloewen-msft/GitGudSimilarIssues@main + with: + issuetitle: ${{ github.event.issue.title }} + repo: ${{ github.repository }} + similaritytolerance: "0.8" + add-comment: + needs: getsimilarissues + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Add comment + run: gh issue comment "$NUMBER" --repo "$REPO" --body "$BODY" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NUMBER: ${{ github.event.issue.number }} + REPO: ${{ github.repository }} + BODY: ${{ needs.getsimilarissues.outputs.message }} From adb04729bcce7151f6380eded79e9408df9d1e3b Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 21 Nov 2023 21:50:46 +0100 Subject: [PATCH 066/167] ConPTY: Fix a shutdown deadlock with WSL (#16340) Under normal circumstances this bug should be rare as far as I can observe it on my system. However, it does occur randomly. In short, WSL doesn't pass us anonymous pipes, but rather WSA sockets and those signal their graceful shutdown first before being closed later by returning a `lpNumberOfBytesRead` of 0 in the meantime. Additionally, `VtIo` synchronously pumps the input pipe to get the initial cursor position, but fails to check `_exitRequested`. And so even with the pipe handling fixed, `VtIo` will also deadlock, because it will never realize that `VtInputThread` is done reading. ## Validation Steps Performed * Build commit 376737e with this change and replace conhost with it Coincidentally it contains a bug (of as of yet unknown origin) due to which the initial cursor position loop in `VtIo` never completes. Thanks to this, we can easily provoke this issue. * Launch WSL in conhost and run an .exe inside it * Close the conhost window * Task manager shows that all conhost instances exit immediately --- src/host/VtInputThread.cpp | 89 +++++++++++++++----------------------- src/host/VtInputThread.hpp | 5 +-- src/host/VtIo.cpp | 3 +- 3 files changed, 36 insertions(+), 61 deletions(-) diff --git a/src/host/VtInputThread.cpp b/src/host/VtInputThread.cpp index 495a6e51d8b..4a4b773cb02 100644 --- a/src/host/VtInputThread.cpp +++ b/src/host/VtInputThread.cpp @@ -29,7 +29,6 @@ VtInputThread::VtInputThread(_In_ wil::unique_hfile hPipe, _hThread{}, _u8State{}, _dwThreadId{ 0 }, - _exitRequested{ false }, _pfnSetLookingForDSR{} { THROW_HR_IF(E_HANDLE, _hFile.get() == INVALID_HANDLE_VALUE); @@ -50,40 +49,6 @@ VtInputThread::VtInputThread(_In_ wil::unique_hfile hPipe, _pfnSetLookingForDSR = std::bind(&InputStateMachineEngine::SetLookingForDSR, engineRef, std::placeholders::_1); } -// Method Description: -// - Processes a string of input characters. The characters should be UTF-8 -// encoded, and will get converted to wstring to be processed by the -// input state machine. -// Arguments: -// - u8Str - the UTF-8 string received. -// Return Value: -// - S_OK on success, otherwise an appropriate failure. -[[nodiscard]] HRESULT VtInputThread::_HandleRunInput(const std::string_view u8Str) -{ - // Make sure to call the GLOBAL Lock/Unlock, not the gci's lock/unlock. - // Only the global unlock attempts to dispatch ctrl events. If you use the - // gci's unlock, when you press C-c, it won't be dispatched until the - // next console API call. For something like `powershell sleep 60`, - // that won't happen for 60s - LockConsole(); - auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); - - try - { - std::wstring wstr{}; - auto hr = til::u8u16(u8Str, wstr, _u8State); - // If we hit a parsing error, eat it. It's bad utf-8, we can't do anything with it. - if (FAILED(hr)) - { - return S_FALSE; - } - _pInputStateMachine->ProcessString(wstr); - } - CATCH_RETURN(); - - return S_OK; -} - // Function Description: // - Static function used for initializing an instance's ThreadProc. // Arguments: @@ -100,35 +65,50 @@ DWORD WINAPI VtInputThread::StaticVtInputThreadProc(_In_ LPVOID lpParameter) // Method Description: // - Do a single ReadFile from our pipe, and try and handle it. If handling // failed, throw or log, depending on what the caller wants. -// Arguments: -// - throwOnFail: If true, throw an exception if there was an error processing -// the input received. Otherwise, log the error. // Return Value: -// - -void VtInputThread::DoReadInput(const bool throwOnFail) +// - true if you should continue reading +bool VtInputThread::DoReadInput() { char buffer[256]; DWORD dwRead = 0; - auto fSuccess = !!ReadFile(_hFile.get(), buffer, ARRAYSIZE(buffer), &dwRead, nullptr); - - if (!fSuccess) + const auto ok = ReadFile(_hFile.get(), buffer, ARRAYSIZE(buffer), &dwRead, nullptr); + + // The ReadFile() documentations calls out that: + // > If the lpNumberOfBytesRead parameter is zero when ReadFile returns TRUE on a pipe, the other + // > end of the pipe called the WriteFile function with nNumberOfBytesToWrite set to zero. + // But I was unable to replicate any such behavior. I'm not sure it's true anymore. + // + // However, what the documentations fails to mention is that winsock2 (WSA) handles of the \Device\Afd type are + // transparently compatible with ReadFile() and the WSARecv() documentations contains this important information: + // > For byte streams, zero bytes having been read [..] indicates graceful closure and that no more bytes will ever be read. + // In other words, for pipe HANDLE of unknown type you should consider `lpNumberOfBytesRead == 0` as an exit indicator. + // + // Here, `dwRead == 0` fixes a deadlock when exiting conhost while being in use by WSL whose hypervisor pipes are WSA. + if (!ok || dwRead == 0) { - _exitRequested = true; - return; + return false; } - auto hr = _HandleRunInput({ buffer, gsl::narrow_cast(dwRead) }); - if (FAILED(hr)) + try { - if (throwOnFail) - { - _exitRequested = true; - } - else + // Make sure to call the GLOBAL Lock/Unlock, not the gci's lock/unlock. + // Only the global unlock attempts to dispatch ctrl events. If you use the + // gci's unlock, when you press C-c, it won't be dispatched until the + // next console API call. For something like `powershell sleep 60`, + // that won't happen for 60s + LockConsole(); + const auto unlock = wil::scope_exit([&] { UnlockConsole(); }); + + std::wstring wstr; + // If we hit a parsing error, eat it. It's bad utf-8, we can't do anything with it. + if (SUCCEEDED_LOG(til::u8u16({ buffer, gsl::narrow_cast(dwRead) }, wstr, _u8State))) { - LOG_IF_FAILED(hr); + _pInputStateMachine->ProcessString(wstr); } } + CATCH_LOG(); + + return true; } void VtInputThread::SetLookingForDSR(const bool looking) noexcept @@ -145,9 +125,8 @@ void VtInputThread::SetLookingForDSR(const bool looking) noexcept // InputStateMachineEngine. void VtInputThread::_InputThread() { - while (!_exitRequested) + while (DoReadInput()) { - DoReadInput(true); } ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo()->CloseInput(); } diff --git a/src/host/VtInputThread.hpp b/src/host/VtInputThread.hpp index 7c075b70025..7652a53887f 100644 --- a/src/host/VtInputThread.hpp +++ b/src/host/VtInputThread.hpp @@ -25,19 +25,16 @@ namespace Microsoft::Console [[nodiscard]] HRESULT Start(); static DWORD WINAPI StaticVtInputThreadProc(_In_ LPVOID lpParameter); - void DoReadInput(const bool throwOnFail); + bool DoReadInput(); void SetLookingForDSR(const bool looking) noexcept; private: - [[nodiscard]] HRESULT _HandleRunInput(const std::string_view u8Str); void _InputThread(); wil::unique_hfile _hFile; wil::unique_handle _hThread; DWORD _dwThreadId; - bool _exitRequested; - std::function _pfnSetLookingForDSR; std::unique_ptr _pInputStateMachine; diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index dd8b635efd9..c286a5e8aca 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -282,9 +282,8 @@ bool VtIo::IsUsingVt() const if (_lookingForCursorPosition && _pVtRenderEngine && _pVtInputThread) { LOG_IF_FAILED(_pVtRenderEngine->RequestCursor()); - while (_lookingForCursorPosition) + while (_lookingForCursorPosition && _pVtInputThread->DoReadInput()) { - _pVtInputThread->DoReadInput(false); } } From bdf2f6f2740168363bdba898697e28baa8d7beb7 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 21 Nov 2023 21:50:59 +0100 Subject: [PATCH 067/167] Fix chunked soft fonts not working (#16349) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This changeset fixes an issue caused by #15991 where "chunked" escape sequences would get corrupted. The fix is to simply not flush eagerly anymore. I tried my best to keep the input lag reduction from #15991, but unfortunately this isn't possible for console APIs. Closes #16079 ## Validation Steps Performed * `type ascii.com` produces soft font ASCII characters ✅ --- src/host/VtIo.cpp | 4 ---- src/renderer/vt/state.cpp | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index c286a5e8aca..b0c33671727 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -467,10 +467,6 @@ void VtIo::SendCloseEvent() void VtIo::CorkRenderer(bool corked) const noexcept { _pVtRenderEngine->Cork(corked); - if (!corked) - { - LOG_IF_FAILED(ServiceLocator::LocateGlobals().pRender->PaintFrame()); - } } #ifdef UNIT_TESTING diff --git a/src/renderer/vt/state.cpp b/src/renderer/vt/state.cpp index 8408ee3aeb6..36e31463cd9 100644 --- a/src/renderer/vt/state.cpp +++ b/src/renderer/vt/state.cpp @@ -167,6 +167,7 @@ void VtEngine::_flushImpl() noexcept void VtEngine::Cork(bool corked) noexcept { _corked = corked; + _Flush(); } // Method Description: From 264ef4ebda47e3b8f74fe95a9c855629438c279d Mon Sep 17 00:00:00 2001 From: Marcel Wagner Date: Tue, 21 Nov 2023 21:55:15 +0100 Subject: [PATCH 068/167] [colortool] Add new campbell scheme, switch to CRLF endings for themes (#16339) Adds "Campbell Absolute" which has absolute black/white instead of slightly greyish variants as discussed per #35. Also updates line endings to adhere to the default Windows line endings (i.e. CRLF) Closes #35 --- .../ColorTool/schemes/OneHalfDark.itermcolors | 750 +++++++++--------- .../schemes/OneHalfLight.itermcolors | 750 +++++++++--------- .../ColorTool/schemes/campbell-absolute.ini | 29 + .../ColorTool/schemes/campbell-legacy.ini | 58 +- src/tools/ColorTool/schemes/campbell.ini | 58 +- src/tools/ColorTool/schemes/cmd-legacy.ini | 58 +- .../schemes/deuteranopia.itermcolors | 586 +++++++------- .../schemes/solarized_dark.itermcolors | 484 +++++------ .../schemes/solarized_light.itermcolors | 578 +++++++------- .../ColorTool/schemes/tango_dark.itermcolors | 448 +++++------ .../ColorTool/schemes/tango_light.itermcolors | 448 +++++------ 11 files changed, 2138 insertions(+), 2109 deletions(-) create mode 100644 src/tools/ColorTool/schemes/campbell-absolute.ini diff --git a/src/tools/ColorTool/schemes/OneHalfDark.itermcolors b/src/tools/ColorTool/schemes/OneHalfDark.itermcolors index 3dc69aca6a7..115741c8811 100644 --- a/src/tools/ColorTool/schemes/OneHalfDark.itermcolors +++ b/src/tools/ColorTool/schemes/OneHalfDark.itermcolors @@ -1,376 +1,376 @@ - - - - - - Ansi 0 Color - - Alpha Component - 1 - Blue Component - 0.203921568627 - Color Space - Calibrated - Green Component - 0.172549019608 - Red Component - 0.156862745098 - - Ansi 1 Color - - Alpha Component - 1 - Blue Component - 0.458823529412 - Color Space - Calibrated - Green Component - 0.423529411765 - Red Component - 0.878431372549 - - Ansi 10 Color - - Alpha Component - 1 - Blue Component - 0.474509803922 - Color Space - Calibrated - Green Component - 0.764705882353 - Red Component - 0.596078431373 - - Ansi 11 Color - - Alpha Component - 1 - Blue Component - 0.482352941176 - Color Space - Calibrated - Green Component - 0.752941176471 - Red Component - 0.898039215686 - - Ansi 12 Color - - Alpha Component - 1 - Blue Component - 0.937254901961 - Color Space - Calibrated - Green Component - 0.686274509804 - Red Component - 0.380392156863 - - Ansi 13 Color - - Alpha Component - 1 - Blue Component - 0.866666666667 - Color Space - Calibrated - Green Component - 0.470588235294 - Red Component - 0.776470588235 - - Ansi 14 Color - - Alpha Component - 1 - Blue Component - 0.760784313725 - Color Space - Calibrated - Green Component - 0.713725490196 - Red Component - 0.337254901961 - - Ansi 15 Color - - Alpha Component - 1 - Blue Component - 0.894117647059 - Color Space - Calibrated - Green Component - 0.874509803922 - Red Component - 0.862745098039 - - Ansi 2 Color - - Alpha Component - 1 - Blue Component - 0.474509803922 - Color Space - Calibrated - Green Component - 0.764705882353 - Red Component - 0.596078431373 - - Ansi 3 Color - - Alpha Component - 1 - Blue Component - 0.482352941176 - Color Space - Calibrated - Green Component - 0.752941176471 - Red Component - 0.898039215686 - - Ansi 4 Color - - Alpha Component - 1 - Blue Component - 0.937254901961 - Color Space - Calibrated - Green Component - 0.686274509804 - Red Component - 0.380392156863 - - Ansi 5 Color - - Alpha Component - 1 - Blue Component - 0.866666666667 - Color Space - Calibrated - Green Component - 0.470588235294 - Red Component - 0.776470588235 - - Ansi 6 Color - - Alpha Component - 1 - Blue Component - 0.760784313725 - Color Space - Calibrated - Green Component - 0.713725490196 - Red Component - 0.337254901961 - - Ansi 7 Color - - Alpha Component - 1 - Blue Component - 0.894117647059 - Color Space - Calibrated - Green Component - 0.874509803922 - Red Component - 0.862745098039 - - Ansi 8 Color - - Alpha Component - 1 - Blue Component - 0.4549019607843137 - Color Space - Calibrated - Green Component - 0.38823529411764707 - Red Component - 0.35294117647058826 - - Ansi 9 Color - - Alpha Component - 1 - Blue Component - 0.458823529412 - Color Space - Calibrated - Green Component - 0.423529411765 - Red Component - 0.878431372549 - - Background Color - - Alpha Component - 1 - Blue Component - 0.203921568627 - Color Space - Calibrated - Green Component - 0.172549019608 - Red Component - 0.156862745098 - - Badge Color - - Alpha Component - 0.5 - Blue Component - 0.0 - Color Space - Calibrated - Green Component - 0.0 - Red Component - 1 - - Bold Color - - Alpha Component - 1 - Blue Component - 0.74901962280273438 - Color Space - Calibrated - Green Component - 0.69803923368453979 - Red Component - 0.67058825492858887 - - Cursor Color - - Alpha Component - 1 - Blue Component - 0.8 - Color Space - Calibrated - Green Component - 0.701960784314 - Red Component - 0.639215686275 - - Cursor Guide Color - - Alpha Component - 0.25 - Blue Component - 0.250980392157 - Color Space - Calibrated - Green Component - 0.211764705882 - Red Component - 0.192156862745 - - Cursor Text Color - - Alpha Component - 1 - Blue Component - 0.894117647059 - Color Space - Calibrated - Green Component - 0.874509803922 - Red Component - 0.862745098039 - - Foreground Color - - Alpha Component - 1 - Blue Component - 0.894117647059 - Color Space - Calibrated - Green Component - 0.874509803922 - Red Component - 0.862745098039 - - Link Color - - Alpha Component - 1 - Blue Component - 0.937254901961 - Color Space - Calibrated - Green Component - 0.686274509804 - Red Component - 0.380392156863 - - Selected Text Color - - Alpha Component - 1 - Blue Component - 0.894117647059 - Color Space - Calibrated - Green Component - 0.874509803922 - Red Component - 0.862745098039 - - Selection Color - - Alpha Component - 1 - Blue Component - 0.364705882353 - Color Space - Calibrated - Green Component - 0.305882352941 - Red Component - 0.278431372549 - - + + + + + + Ansi 0 Color + + Alpha Component + 1 + Blue Component + 0.203921568627 + Color Space + Calibrated + Green Component + 0.172549019608 + Red Component + 0.156862745098 + + Ansi 1 Color + + Alpha Component + 1 + Blue Component + 0.458823529412 + Color Space + Calibrated + Green Component + 0.423529411765 + Red Component + 0.878431372549 + + Ansi 10 Color + + Alpha Component + 1 + Blue Component + 0.474509803922 + Color Space + Calibrated + Green Component + 0.764705882353 + Red Component + 0.596078431373 + + Ansi 11 Color + + Alpha Component + 1 + Blue Component + 0.482352941176 + Color Space + Calibrated + Green Component + 0.752941176471 + Red Component + 0.898039215686 + + Ansi 12 Color + + Alpha Component + 1 + Blue Component + 0.937254901961 + Color Space + Calibrated + Green Component + 0.686274509804 + Red Component + 0.380392156863 + + Ansi 13 Color + + Alpha Component + 1 + Blue Component + 0.866666666667 + Color Space + Calibrated + Green Component + 0.470588235294 + Red Component + 0.776470588235 + + Ansi 14 Color + + Alpha Component + 1 + Blue Component + 0.760784313725 + Color Space + Calibrated + Green Component + 0.713725490196 + Red Component + 0.337254901961 + + Ansi 15 Color + + Alpha Component + 1 + Blue Component + 0.894117647059 + Color Space + Calibrated + Green Component + 0.874509803922 + Red Component + 0.862745098039 + + Ansi 2 Color + + Alpha Component + 1 + Blue Component + 0.474509803922 + Color Space + Calibrated + Green Component + 0.764705882353 + Red Component + 0.596078431373 + + Ansi 3 Color + + Alpha Component + 1 + Blue Component + 0.482352941176 + Color Space + Calibrated + Green Component + 0.752941176471 + Red Component + 0.898039215686 + + Ansi 4 Color + + Alpha Component + 1 + Blue Component + 0.937254901961 + Color Space + Calibrated + Green Component + 0.686274509804 + Red Component + 0.380392156863 + + Ansi 5 Color + + Alpha Component + 1 + Blue Component + 0.866666666667 + Color Space + Calibrated + Green Component + 0.470588235294 + Red Component + 0.776470588235 + + Ansi 6 Color + + Alpha Component + 1 + Blue Component + 0.760784313725 + Color Space + Calibrated + Green Component + 0.713725490196 + Red Component + 0.337254901961 + + Ansi 7 Color + + Alpha Component + 1 + Blue Component + 0.894117647059 + Color Space + Calibrated + Green Component + 0.874509803922 + Red Component + 0.862745098039 + + Ansi 8 Color + + Alpha Component + 1 + Blue Component + 0.4549019607843137 + Color Space + Calibrated + Green Component + 0.38823529411764707 + Red Component + 0.35294117647058826 + + Ansi 9 Color + + Alpha Component + 1 + Blue Component + 0.458823529412 + Color Space + Calibrated + Green Component + 0.423529411765 + Red Component + 0.878431372549 + + Background Color + + Alpha Component + 1 + Blue Component + 0.203921568627 + Color Space + Calibrated + Green Component + 0.172549019608 + Red Component + 0.156862745098 + + Badge Color + + Alpha Component + 0.5 + Blue Component + 0.0 + Color Space + Calibrated + Green Component + 0.0 + Red Component + 1 + + Bold Color + + Alpha Component + 1 + Blue Component + 0.74901962280273438 + Color Space + Calibrated + Green Component + 0.69803923368453979 + Red Component + 0.67058825492858887 + + Cursor Color + + Alpha Component + 1 + Blue Component + 0.8 + Color Space + Calibrated + Green Component + 0.701960784314 + Red Component + 0.639215686275 + + Cursor Guide Color + + Alpha Component + 0.25 + Blue Component + 0.250980392157 + Color Space + Calibrated + Green Component + 0.211764705882 + Red Component + 0.192156862745 + + Cursor Text Color + + Alpha Component + 1 + Blue Component + 0.894117647059 + Color Space + Calibrated + Green Component + 0.874509803922 + Red Component + 0.862745098039 + + Foreground Color + + Alpha Component + 1 + Blue Component + 0.894117647059 + Color Space + Calibrated + Green Component + 0.874509803922 + Red Component + 0.862745098039 + + Link Color + + Alpha Component + 1 + Blue Component + 0.937254901961 + Color Space + Calibrated + Green Component + 0.686274509804 + Red Component + 0.380392156863 + + Selected Text Color + + Alpha Component + 1 + Blue Component + 0.894117647059 + Color Space + Calibrated + Green Component + 0.874509803922 + Red Component + 0.862745098039 + + Selection Color + + Alpha Component + 1 + Blue Component + 0.364705882353 + Color Space + Calibrated + Green Component + 0.305882352941 + Red Component + 0.278431372549 + + \ No newline at end of file diff --git a/src/tools/ColorTool/schemes/OneHalfLight.itermcolors b/src/tools/ColorTool/schemes/OneHalfLight.itermcolors index 50391960272..893bb4600e8 100644 --- a/src/tools/ColorTool/schemes/OneHalfLight.itermcolors +++ b/src/tools/ColorTool/schemes/OneHalfLight.itermcolors @@ -1,376 +1,376 @@ - - - - - - Ansi 0 Color - - Alpha Component - 1 - Blue Component - 0.258823529412 - Color Space - Calibrated - Green Component - 0.227450980392 - Red Component - 0.219607843137 - - Ansi 1 Color - - Alpha Component - 1 - Blue Component - 0.286274509804 - Color Space - Calibrated - Green Component - 0.337254901961 - Red Component - 0.894117647059 - - Ansi 10 Color - - Alpha Component - 1 - Blue Component - 0.474509803922 - Color Space - Calibrated - Green Component - 0.764705882353 - Red Component - 0.596078431373 - - Ansi 11 Color - - Alpha Component - 1 - Blue Component - 0.482352941176 - Color Space - Calibrated - Green Component - 0.752941176471 - Red Component - 0.898039215686 - - Ansi 12 Color - - Alpha Component - 1 - Blue Component - 0.937254901961 - Color Space - Calibrated - Green Component - 0.686274509804 - Red Component - 0.380392156863 - - Ansi 13 Color - - Alpha Component - 1 - Blue Component - 0.866666666667 - Color Space - Calibrated - Green Component - 0.470588235294 - Red Component - 0.776470588235 - - Ansi 14 Color - - Alpha Component - 1 - Blue Component - 0.760784313725 - Color Space - Calibrated - Green Component - 0.713725490196 - Red Component - 0.337254901961 - - Ansi 15 Color - - Alpha Component - 1 - Blue Component - 1.0 - Color Space - Calibrated - Green Component - 1.0 - Red Component - 1.0 - - Ansi 2 Color - - Alpha Component - 1 - Blue Component - 0.309803921569 - Color Space - Calibrated - Green Component - 0.63137254902 - Red Component - 0.313725490196 - - Ansi 3 Color - - Alpha Component - 1 - Blue Component - 0.00392156862745 - Color Space - Calibrated - Green Component - 0.517647058824 - Red Component - 0.756862745098 - - Ansi 4 Color - - Alpha Component - 1 - Blue Component - 0.737254901961 - Color Space - Calibrated - Green Component - 0.517647058824 - Red Component - 0.00392156862745 - - Ansi 5 Color - - Alpha Component - 1 - Blue Component - 0.643137254902 - Color Space - Calibrated - Green Component - 0.149019607843 - Red Component - 0.650980392157 - - Ansi 6 Color - - Alpha Component - 1 - Blue Component - 0.701960784314 - Color Space - Calibrated - Green Component - 0.592156862745 - Red Component - 0.0352941176471 - - Ansi 7 Color - - Alpha Component - 1 - Blue Component - 0.980392156863 - Color Space - Calibrated - Green Component - 0.980392156863 - Red Component - 0.980392156863 - - Ansi 8 Color - - Alpha Component - 1 - Blue Component - 0.36862745098 - Color Space - Calibrated - Green Component - 0.321568627451 - Red Component - 0.309803921569 - - Ansi 9 Color - - Alpha Component - 1 - Blue Component - 0.458823529412 - Color Space - Calibrated - Green Component - 0.423529411765 - Red Component - 0.878431372549 - - Background Color - - Alpha Component - 1 - Blue Component - 0.980392156863 - Color Space - Calibrated - Green Component - 0.980392156863 - Red Component - 0.980392156863 - - Badge Color - - Alpha Component - 0.5 - Blue Component - 0.0 - Color Space - Calibrated - Green Component - 0.0 - Red Component - 1 - - Bold Color - - Alpha Component - 1 - Blue Component - 0.74901962280273438 - Color Space - Calibrated - Green Component - 0.69803923368453979 - Red Component - 0.67058825492858887 - - Cursor Color - - Alpha Component - 1 - Blue Component - 1.0 - Color Space - Calibrated - Green Component - 0.807843137255 - Red Component - 0.749019607843 - - Cursor Guide Color - - Alpha Component - 0.25 - Blue Component - 0.941176470588 - Color Space - Calibrated - Green Component - 0.941176470588 - Red Component - 0.941176470588 - - Cursor Text Color - - Alpha Component - 1 - Blue Component - 0.258823529412 - Color Space - Calibrated - Green Component - 0.227450980392 - Red Component - 0.219607843137 - - Foreground Color - - Alpha Component - 1 - Blue Component - 0.258823529412 - Color Space - Calibrated - Green Component - 0.227450980392 - Red Component - 0.219607843137 - - Link Color - - Alpha Component - 1 - Blue Component - 0.737254901961 - Color Space - Calibrated - Green Component - 0.517647058824 - Red Component - 0.00392156862745 - - Selected Text Color - - Alpha Component - 1 - Blue Component - 0.258823529412 - Color Space - Calibrated - Green Component - 0.227450980392 - Red Component - 0.219607843137 - - Selection Color - - Alpha Component - 1 - Blue Component - 1.0 - Color Space - Calibrated - Green Component - 0.807843137255 - Red Component - 0.749019607843 - - + + + + + + Ansi 0 Color + + Alpha Component + 1 + Blue Component + 0.258823529412 + Color Space + Calibrated + Green Component + 0.227450980392 + Red Component + 0.219607843137 + + Ansi 1 Color + + Alpha Component + 1 + Blue Component + 0.286274509804 + Color Space + Calibrated + Green Component + 0.337254901961 + Red Component + 0.894117647059 + + Ansi 10 Color + + Alpha Component + 1 + Blue Component + 0.474509803922 + Color Space + Calibrated + Green Component + 0.764705882353 + Red Component + 0.596078431373 + + Ansi 11 Color + + Alpha Component + 1 + Blue Component + 0.482352941176 + Color Space + Calibrated + Green Component + 0.752941176471 + Red Component + 0.898039215686 + + Ansi 12 Color + + Alpha Component + 1 + Blue Component + 0.937254901961 + Color Space + Calibrated + Green Component + 0.686274509804 + Red Component + 0.380392156863 + + Ansi 13 Color + + Alpha Component + 1 + Blue Component + 0.866666666667 + Color Space + Calibrated + Green Component + 0.470588235294 + Red Component + 0.776470588235 + + Ansi 14 Color + + Alpha Component + 1 + Blue Component + 0.760784313725 + Color Space + Calibrated + Green Component + 0.713725490196 + Red Component + 0.337254901961 + + Ansi 15 Color + + Alpha Component + 1 + Blue Component + 1.0 + Color Space + Calibrated + Green Component + 1.0 + Red Component + 1.0 + + Ansi 2 Color + + Alpha Component + 1 + Blue Component + 0.309803921569 + Color Space + Calibrated + Green Component + 0.63137254902 + Red Component + 0.313725490196 + + Ansi 3 Color + + Alpha Component + 1 + Blue Component + 0.00392156862745 + Color Space + Calibrated + Green Component + 0.517647058824 + Red Component + 0.756862745098 + + Ansi 4 Color + + Alpha Component + 1 + Blue Component + 0.737254901961 + Color Space + Calibrated + Green Component + 0.517647058824 + Red Component + 0.00392156862745 + + Ansi 5 Color + + Alpha Component + 1 + Blue Component + 0.643137254902 + Color Space + Calibrated + Green Component + 0.149019607843 + Red Component + 0.650980392157 + + Ansi 6 Color + + Alpha Component + 1 + Blue Component + 0.701960784314 + Color Space + Calibrated + Green Component + 0.592156862745 + Red Component + 0.0352941176471 + + Ansi 7 Color + + Alpha Component + 1 + Blue Component + 0.980392156863 + Color Space + Calibrated + Green Component + 0.980392156863 + Red Component + 0.980392156863 + + Ansi 8 Color + + Alpha Component + 1 + Blue Component + 0.36862745098 + Color Space + Calibrated + Green Component + 0.321568627451 + Red Component + 0.309803921569 + + Ansi 9 Color + + Alpha Component + 1 + Blue Component + 0.458823529412 + Color Space + Calibrated + Green Component + 0.423529411765 + Red Component + 0.878431372549 + + Background Color + + Alpha Component + 1 + Blue Component + 0.980392156863 + Color Space + Calibrated + Green Component + 0.980392156863 + Red Component + 0.980392156863 + + Badge Color + + Alpha Component + 0.5 + Blue Component + 0.0 + Color Space + Calibrated + Green Component + 0.0 + Red Component + 1 + + Bold Color + + Alpha Component + 1 + Blue Component + 0.74901962280273438 + Color Space + Calibrated + Green Component + 0.69803923368453979 + Red Component + 0.67058825492858887 + + Cursor Color + + Alpha Component + 1 + Blue Component + 1.0 + Color Space + Calibrated + Green Component + 0.807843137255 + Red Component + 0.749019607843 + + Cursor Guide Color + + Alpha Component + 0.25 + Blue Component + 0.941176470588 + Color Space + Calibrated + Green Component + 0.941176470588 + Red Component + 0.941176470588 + + Cursor Text Color + + Alpha Component + 1 + Blue Component + 0.258823529412 + Color Space + Calibrated + Green Component + 0.227450980392 + Red Component + 0.219607843137 + + Foreground Color + + Alpha Component + 1 + Blue Component + 0.258823529412 + Color Space + Calibrated + Green Component + 0.227450980392 + Red Component + 0.219607843137 + + Link Color + + Alpha Component + 1 + Blue Component + 0.737254901961 + Color Space + Calibrated + Green Component + 0.517647058824 + Red Component + 0.00392156862745 + + Selected Text Color + + Alpha Component + 1 + Blue Component + 0.258823529412 + Color Space + Calibrated + Green Component + 0.227450980392 + Red Component + 0.219607843137 + + Selection Color + + Alpha Component + 1 + Blue Component + 1.0 + Color Space + Calibrated + Green Component + 0.807843137255 + Red Component + 0.749019607843 + + \ No newline at end of file diff --git a/src/tools/ColorTool/schemes/campbell-absolute.ini b/src/tools/ColorTool/schemes/campbell-absolute.ini new file mode 100644 index 00000000000..3b6dc177af6 --- /dev/null +++ b/src/tools/ColorTool/schemes/campbell-absolute.ini @@ -0,0 +1,29 @@ +[table] +DARK_BLACK = 0,0,0 +DARK_BLUE = 0,55,218 +DARK_GREEN = 19,161,14 +DARK_CYAN = 58,150,221 +DARK_RED = 197,15,31 +DARK_MAGENTA = 136,23,152 +DARK_YELLOW = 193,156,0 +DARK_WHITE = 204,204,204 +BRIGHT_BLACK = 118,118,118 +BRIGHT_BLUE = 59,120,255 +BRIGHT_GREEN = 22,198,12 +BRIGHT_CYAN = 97,214,214 +BRIGHT_RED = 231,72,86 +BRIGHT_MAGENTA = 180,0,158 +BRIGHT_YELLOW = 249,241,165 +BRIGHT_WHITE = 255,255,255 + +[screen] +FOREGROUND = DARK_WHITE +BACKGROUND = DARK_BLACK + +[popup] +FOREGROUND = DARK_MAGENTA +BACKGROUND = BRIGHT_WHITE + +[info] +name = Campbell +author = crloew diff --git a/src/tools/ColorTool/schemes/campbell-legacy.ini b/src/tools/ColorTool/schemes/campbell-legacy.ini index 6f384025c62..d6f06b542ea 100644 --- a/src/tools/ColorTool/schemes/campbell-legacy.ini +++ b/src/tools/ColorTool/schemes/campbell-legacy.ini @@ -1,29 +1,29 @@ -[table] -DARK_BLACK = 19,17,23 -DARK_BLUE = 6,54,222 -DARK_GREEN = 57,151,50 -DARK_CYAN = 48,151,168 -DARK_RED = 185,0,5 -DARK_MAGENTA = 141,2,180 -DARK_YELLOW = 187,182,0 -DARK_WHITE = 192,190,197 -BRIGHT_BLACK = 85,82,92 -BRIGHT_BLUE = 62,109,253 -BRIGHT_GREEN = 11,213,0 -BRIGHT_CYAN = 128,205,253 -BRIGHT_RED = 237,57,96 -BRIGHT_MAGENTA = 198,2,253 -BRIGHT_YELLOW = 255,247,149 -BRIGHT_WHITE = 240,239,241 - -[screen] -FOREGROUND = DARK_WHITE -BACKGROUND = DARK_BLACK - -[popup] -FOREGROUND = DARK_MAGENTA -BACKGROUND = BRIGHT_WHITE - -[info] -name = Campbell-Legacy -author = paulcam +[table] +DARK_BLACK = 19,17,23 +DARK_BLUE = 6,54,222 +DARK_GREEN = 57,151,50 +DARK_CYAN = 48,151,168 +DARK_RED = 185,0,5 +DARK_MAGENTA = 141,2,180 +DARK_YELLOW = 187,182,0 +DARK_WHITE = 192,190,197 +BRIGHT_BLACK = 85,82,92 +BRIGHT_BLUE = 62,109,253 +BRIGHT_GREEN = 11,213,0 +BRIGHT_CYAN = 128,205,253 +BRIGHT_RED = 237,57,96 +BRIGHT_MAGENTA = 198,2,253 +BRIGHT_YELLOW = 255,247,149 +BRIGHT_WHITE = 240,239,241 + +[screen] +FOREGROUND = DARK_WHITE +BACKGROUND = DARK_BLACK + +[popup] +FOREGROUND = DARK_MAGENTA +BACKGROUND = BRIGHT_WHITE + +[info] +name = Campbell-Legacy +author = paulcam diff --git a/src/tools/ColorTool/schemes/campbell.ini b/src/tools/ColorTool/schemes/campbell.ini index 92357ed4e13..f7356c74278 100644 --- a/src/tools/ColorTool/schemes/campbell.ini +++ b/src/tools/ColorTool/schemes/campbell.ini @@ -1,29 +1,29 @@ -[table] -DARK_BLACK = 12,12,12 -DARK_BLUE = 0,55,218 -DARK_GREEN = 19,161,14 -DARK_CYAN = 58,150,221 -DARK_RED = 197,15,31 -DARK_MAGENTA = 136,23,152 -DARK_YELLOW = 193,156,0 -DARK_WHITE = 204,204,204 -BRIGHT_BLACK = 118,118,118 -BRIGHT_BLUE = 59,120,255 -BRIGHT_GREEN = 22,198,12 -BRIGHT_CYAN = 97,214,214 -BRIGHT_RED = 231,72,86 -BRIGHT_MAGENTA = 180,0,158 -BRIGHT_YELLOW = 249,241,165 -BRIGHT_WHITE = 242,242,242 - -[screen] -FOREGROUND = DARK_WHITE -BACKGROUND = DARK_BLACK - -[popup] -FOREGROUND = DARK_MAGENTA -BACKGROUND = BRIGHT_WHITE - -[info] -name = Campbell -author = crloew +[table] +DARK_BLACK = 12,12,12 +DARK_BLUE = 0,55,218 +DARK_GREEN = 19,161,14 +DARK_CYAN = 58,150,221 +DARK_RED = 197,15,31 +DARK_MAGENTA = 136,23,152 +DARK_YELLOW = 193,156,0 +DARK_WHITE = 204,204,204 +BRIGHT_BLACK = 118,118,118 +BRIGHT_BLUE = 59,120,255 +BRIGHT_GREEN = 22,198,12 +BRIGHT_CYAN = 97,214,214 +BRIGHT_RED = 231,72,86 +BRIGHT_MAGENTA = 180,0,158 +BRIGHT_YELLOW = 249,241,165 +BRIGHT_WHITE = 242,242,242 + +[screen] +FOREGROUND = DARK_WHITE +BACKGROUND = DARK_BLACK + +[popup] +FOREGROUND = DARK_MAGENTA +BACKGROUND = BRIGHT_WHITE + +[info] +name = Campbell +author = crloew diff --git a/src/tools/ColorTool/schemes/cmd-legacy.ini b/src/tools/ColorTool/schemes/cmd-legacy.ini index 0173da0bd3e..232350aad55 100644 --- a/src/tools/ColorTool/schemes/cmd-legacy.ini +++ b/src/tools/ColorTool/schemes/cmd-legacy.ini @@ -1,29 +1,29 @@ -[table] -DARK_BLACK = 0, 0, 0 -DARK_BLUE = 0, 0, 128 -DARK_GREEN = 0, 128, 0 -DARK_CYAN = 0, 128, 128 -DARK_RED = 128, 0, 0 -DARK_MAGENTA = 128, 0, 128 -DARK_YELLOW = 128, 128, 0 -DARK_WHITE = 192, 192, 192 -BRIGHT_BLACK = 128, 128, 128 -BRIGHT_BLUE = 0, 0, 255 -BRIGHT_GREEN = 0, 255, 0 -BRIGHT_CYAN = 0, 255, 255 -BRIGHT_RED = 255, 0, 0 -BRIGHT_MAGENTA = 255, 0, 255 -BRIGHT_YELLOW = 255, 255, 0 -BRIGHT_WHITE = 255, 255, 255 - -[screen] -FOREGROUND = DARK_WHITE -BACKGROUND = DARK_BLACK - -[popup] -FOREGROUND = DARK_MAGENTA -BACKGROUND = BRIGHT_WHITE - -[info] -name = cmd-legacy -author = unknown +[table] +DARK_BLACK = 0, 0, 0 +DARK_BLUE = 0, 0, 128 +DARK_GREEN = 0, 128, 0 +DARK_CYAN = 0, 128, 128 +DARK_RED = 128, 0, 0 +DARK_MAGENTA = 128, 0, 128 +DARK_YELLOW = 128, 128, 0 +DARK_WHITE = 192, 192, 192 +BRIGHT_BLACK = 128, 128, 128 +BRIGHT_BLUE = 0, 0, 255 +BRIGHT_GREEN = 0, 255, 0 +BRIGHT_CYAN = 0, 255, 255 +BRIGHT_RED = 255, 0, 0 +BRIGHT_MAGENTA = 255, 0, 255 +BRIGHT_YELLOW = 255, 255, 0 +BRIGHT_WHITE = 255, 255, 255 + +[screen] +FOREGROUND = DARK_WHITE +BACKGROUND = DARK_BLACK + +[popup] +FOREGROUND = DARK_MAGENTA +BACKGROUND = BRIGHT_WHITE + +[info] +name = cmd-legacy +author = unknown diff --git a/src/tools/ColorTool/schemes/deuteranopia.itermcolors b/src/tools/ColorTool/schemes/deuteranopia.itermcolors index 62e9251d205..3fec5451763 100644 --- a/src/tools/ColorTool/schemes/deuteranopia.itermcolors +++ b/src/tools/ColorTool/schemes/deuteranopia.itermcolors @@ -1,293 +1,293 @@ - - - - - - Ansi 0 Color - - Color Space - sRGB - Blue Component - 0 - Green Component - 0 - Red Component - 0 - - Ansi 1 Color - - Color Space - sRGB - Blue Component - 0 - Green Component - 0.6 - Red Component - 0.6 - - Ansi 10 Color - - Color Space - sRGB - Blue Component - 1 - Green Component - 0 - Red Component - 0 - - Ansi 11 Color - - Color Space - sRGB - Blue Component - 0.7490196078431373 - Green Component - 1 - Red Component - 1 - - Ansi 12 Color - - Color Space - sRGB - Blue Component - 1 - Green Component - 0.5019607843137255 - Red Component - 0.5019607843137255 - - Ansi 13 Color - - Color Space - sRGB - Blue Component - 0.5019607843137255 - Green Component - 1 - Red Component - 1 - - Ansi 14 Color - - Color Space - sRGB - Blue Component - 1 - Green Component - 0.7490196078431373 - Red Component - 0.7490196078431373 - - Ansi 15 Color - - Color Space - sRGB - Blue Component - 1 - Green Component - 1 - Red Component - 1 - - Ansi 2 Color - - Color Space - sRGB - Blue Component - 0.6 - Green Component - 0 - Red Component - 0 - - Ansi 3 Color - - Color Space - sRGB - Blue Component - 0.45098039215686275 - Green Component - 0.6 - Red Component - 0.6 - - Ansi 4 Color - - Color Space - sRGB - Blue Component - 0.6 - Green Component - 0.30196078431372547 - Red Component - 0.30196078431372547 - - Ansi 5 Color - - Color Space - sRGB - Blue Component - 0.30196078431372547 - Green Component - 0.6 - Red Component - 0.6 - - Ansi 6 Color - - Color Space - sRGB - Blue Component - 0.6 - Green Component - 0.45098039215686275 - Red Component - 0.45098039215686275 - - Ansi 7 Color - - Color Space - sRGB - Blue Component - 0.7686274509803922 - Green Component - 0.7686274509803922 - Red Component - 0.7686274509803922 - - Ansi 8 Color - - Color Space - sRGB - Blue Component - 0.4235294117647059 - Green Component - 0.4235294117647059 - Red Component - 0.4235294117647059 - - Ansi 9 Color - - Color Space - sRGB - Blue Component - 0 - Green Component - 1 - Red Component - 1 - - Background Color - - Color Space - sRGB - Blue Component - 0 - Green Component - 0 - Red Component - 0 - - Bold Color - - Color Space - sRGB - Blue Component - 0.7686274509803922 - Green Component - 0.7686274509803922 - Red Component - 0.7686274509803922 - - Cursor Color - - Color Space - sRGB - Blue Component - 0.7686274509803922 - Green Component - 0.7686274509803922 - Red Component - 0.7686274509803922 - - Cursor Text Color - - Color Space - sRGB - Blue Component - 0 - Green Component - 0 - Red Component - 0 - - Foreground Color - - Color Space - sRGB - Blue Component - 0.7686274509803922 - Green Component - 0.7686274509803922 - Red Component - 0.7686274509803922 - - Selected Text Color - - Color Space - sRGB - Blue Component - 0.7686274509803922 - Green Component - 0.7686274509803922 - Red Component - 0.7686274509803922 - - Selection Color - - Color Space - sRGB - Blue Component - 0 - Green Component - 0 - Red Component - 0 - - - + + + + + + Ansi 0 Color + + Color Space + sRGB + Blue Component + 0 + Green Component + 0 + Red Component + 0 + + Ansi 1 Color + + Color Space + sRGB + Blue Component + 0 + Green Component + 0.6 + Red Component + 0.6 + + Ansi 10 Color + + Color Space + sRGB + Blue Component + 1 + Green Component + 0 + Red Component + 0 + + Ansi 11 Color + + Color Space + sRGB + Blue Component + 0.7490196078431373 + Green Component + 1 + Red Component + 1 + + Ansi 12 Color + + Color Space + sRGB + Blue Component + 1 + Green Component + 0.5019607843137255 + Red Component + 0.5019607843137255 + + Ansi 13 Color + + Color Space + sRGB + Blue Component + 0.5019607843137255 + Green Component + 1 + Red Component + 1 + + Ansi 14 Color + + Color Space + sRGB + Blue Component + 1 + Green Component + 0.7490196078431373 + Red Component + 0.7490196078431373 + + Ansi 15 Color + + Color Space + sRGB + Blue Component + 1 + Green Component + 1 + Red Component + 1 + + Ansi 2 Color + + Color Space + sRGB + Blue Component + 0.6 + Green Component + 0 + Red Component + 0 + + Ansi 3 Color + + Color Space + sRGB + Blue Component + 0.45098039215686275 + Green Component + 0.6 + Red Component + 0.6 + + Ansi 4 Color + + Color Space + sRGB + Blue Component + 0.6 + Green Component + 0.30196078431372547 + Red Component + 0.30196078431372547 + + Ansi 5 Color + + Color Space + sRGB + Blue Component + 0.30196078431372547 + Green Component + 0.6 + Red Component + 0.6 + + Ansi 6 Color + + Color Space + sRGB + Blue Component + 0.6 + Green Component + 0.45098039215686275 + Red Component + 0.45098039215686275 + + Ansi 7 Color + + Color Space + sRGB + Blue Component + 0.7686274509803922 + Green Component + 0.7686274509803922 + Red Component + 0.7686274509803922 + + Ansi 8 Color + + Color Space + sRGB + Blue Component + 0.4235294117647059 + Green Component + 0.4235294117647059 + Red Component + 0.4235294117647059 + + Ansi 9 Color + + Color Space + sRGB + Blue Component + 0 + Green Component + 1 + Red Component + 1 + + Background Color + + Color Space + sRGB + Blue Component + 0 + Green Component + 0 + Red Component + 0 + + Bold Color + + Color Space + sRGB + Blue Component + 0.7686274509803922 + Green Component + 0.7686274509803922 + Red Component + 0.7686274509803922 + + Cursor Color + + Color Space + sRGB + Blue Component + 0.7686274509803922 + Green Component + 0.7686274509803922 + Red Component + 0.7686274509803922 + + Cursor Text Color + + Color Space + sRGB + Blue Component + 0 + Green Component + 0 + Red Component + 0 + + Foreground Color + + Color Space + sRGB + Blue Component + 0.7686274509803922 + Green Component + 0.7686274509803922 + Red Component + 0.7686274509803922 + + Selected Text Color + + Color Space + sRGB + Blue Component + 0.7686274509803922 + Green Component + 0.7686274509803922 + Red Component + 0.7686274509803922 + + Selection Color + + Color Space + sRGB + Blue Component + 0 + Green Component + 0 + Red Component + 0 + + + diff --git a/src/tools/ColorTool/schemes/solarized_dark.itermcolors b/src/tools/ColorTool/schemes/solarized_dark.itermcolors index 24c76f58199..6555c793f44 100644 --- a/src/tools/ColorTool/schemes/solarized_dark.itermcolors +++ b/src/tools/ColorTool/schemes/solarized_dark.itermcolors @@ -1,243 +1,243 @@ - - - - - - Ansi 0 Color - - Blue Component - 0.19370138645172119 - Green Component - 0.15575926005840302 - Red Component - 0.0 - - Ansi 1 Color - - Blue Component - 0.14145714044570923 - Green Component - 0.10840655118227005 - Red Component - 0.81926977634429932 - - Ansi 10 Color - - Blue Component - 0.38298487663269043 - Green Component - 0.35665956139564514 - Red Component - 0.27671992778778076 - - Ansi 11 Color - - Blue Component - 0.43850564956665039 - Green Component - 0.40717673301696777 - Red Component - 0.32436618208885193 - - Ansi 12 Color - - Blue Component - 0.51685798168182373 - Green Component - 0.50962930917739868 - Red Component - 0.44058024883270264 - - Ansi 13 Color - - Blue Component - 0.72908437252044678 - Green Component - 0.33896297216415405 - Red Component - 0.34798634052276611 - - Ansi 14 Color - - Blue Component - 0.56363654136657715 - Green Component - 0.56485837697982788 - Red Component - 0.50599193572998047 - - Ansi 15 Color - - Blue Component - 0.86405980587005615 - Green Component - 0.95794391632080078 - Red Component - 0.98943418264389038 - - Ansi 2 Color - - Blue Component - 0.020208755508065224 - Green Component - 0.54115492105484009 - Red Component - 0.44977453351020813 - - Ansi 3 Color - - Blue Component - 0.023484811186790466 - Green Component - 0.46751424670219421 - Red Component - 0.64746475219726562 - - Ansi 4 Color - - Blue Component - 0.78231418132781982 - Green Component - 0.46265947818756104 - Red Component - 0.12754884362220764 - - Ansi 5 Color - - Blue Component - 0.43516635894775391 - Green Component - 0.10802463442087173 - Red Component - 0.77738940715789795 - - Ansi 6 Color - - Blue Component - 0.52502274513244629 - Green Component - 0.57082360982894897 - Red Component - 0.14679534733295441 - - Ansi 7 Color - - Blue Component - 0.79781103134155273 - Green Component - 0.89001238346099854 - Red Component - 0.91611063480377197 - - Ansi 8 Color - - Blue Component - 0.39215686274509803 - Green Component - 0.30196078431372547 - Red Component - 0.0 - - Ansi 9 Color - - Blue Component - 0.073530435562133789 - Green Component - 0.21325300633907318 - Red Component - 0.74176257848739624 - - Background Color - - Blue Component - 0.15170273184776306 - Green Component - 0.11783610284328461 - Red Component - 0.0 - - Bold Color - - Blue Component - 0.56363654136657715 - Green Component - 0.56485837697982788 - Red Component - 0.50599193572998047 - - Cursor Color - - Blue Component - 0.51685798168182373 - Green Component - 0.50962930917739868 - Red Component - 0.44058024883270264 - - Cursor Text Color - - Blue Component - 0.19370138645172119 - Green Component - 0.15575926005840302 - Red Component - 0.0 - - Foreground Color - - Blue Component - 0.51685798168182373 - Green Component - 0.50962930917739868 - Red Component - 0.44058024883270264 - - Selected Text Color - - Blue Component - 0.56363654136657715 - Green Component - 0.56485837697982788 - Red Component - 0.50599193572998047 - - Selection Color - - Blue Component - 0.19370138645172119 - Green Component - 0.15575926005840302 - Red Component - 0.0 - - + + + + + + Ansi 0 Color + + Blue Component + 0.19370138645172119 + Green Component + 0.15575926005840302 + Red Component + 0.0 + + Ansi 1 Color + + Blue Component + 0.14145714044570923 + Green Component + 0.10840655118227005 + Red Component + 0.81926977634429932 + + Ansi 10 Color + + Blue Component + 0.38298487663269043 + Green Component + 0.35665956139564514 + Red Component + 0.27671992778778076 + + Ansi 11 Color + + Blue Component + 0.43850564956665039 + Green Component + 0.40717673301696777 + Red Component + 0.32436618208885193 + + Ansi 12 Color + + Blue Component + 0.51685798168182373 + Green Component + 0.50962930917739868 + Red Component + 0.44058024883270264 + + Ansi 13 Color + + Blue Component + 0.72908437252044678 + Green Component + 0.33896297216415405 + Red Component + 0.34798634052276611 + + Ansi 14 Color + + Blue Component + 0.56363654136657715 + Green Component + 0.56485837697982788 + Red Component + 0.50599193572998047 + + Ansi 15 Color + + Blue Component + 0.86405980587005615 + Green Component + 0.95794391632080078 + Red Component + 0.98943418264389038 + + Ansi 2 Color + + Blue Component + 0.020208755508065224 + Green Component + 0.54115492105484009 + Red Component + 0.44977453351020813 + + Ansi 3 Color + + Blue Component + 0.023484811186790466 + Green Component + 0.46751424670219421 + Red Component + 0.64746475219726562 + + Ansi 4 Color + + Blue Component + 0.78231418132781982 + Green Component + 0.46265947818756104 + Red Component + 0.12754884362220764 + + Ansi 5 Color + + Blue Component + 0.43516635894775391 + Green Component + 0.10802463442087173 + Red Component + 0.77738940715789795 + + Ansi 6 Color + + Blue Component + 0.52502274513244629 + Green Component + 0.57082360982894897 + Red Component + 0.14679534733295441 + + Ansi 7 Color + + Blue Component + 0.79781103134155273 + Green Component + 0.89001238346099854 + Red Component + 0.91611063480377197 + + Ansi 8 Color + + Blue Component + 0.39215686274509803 + Green Component + 0.30196078431372547 + Red Component + 0.0 + + Ansi 9 Color + + Blue Component + 0.073530435562133789 + Green Component + 0.21325300633907318 + Red Component + 0.74176257848739624 + + Background Color + + Blue Component + 0.15170273184776306 + Green Component + 0.11783610284328461 + Red Component + 0.0 + + Bold Color + + Blue Component + 0.56363654136657715 + Green Component + 0.56485837697982788 + Red Component + 0.50599193572998047 + + Cursor Color + + Blue Component + 0.51685798168182373 + Green Component + 0.50962930917739868 + Red Component + 0.44058024883270264 + + Cursor Text Color + + Blue Component + 0.19370138645172119 + Green Component + 0.15575926005840302 + Red Component + 0.0 + + Foreground Color + + Blue Component + 0.51685798168182373 + Green Component + 0.50962930917739868 + Red Component + 0.44058024883270264 + + Selected Text Color + + Blue Component + 0.56363654136657715 + Green Component + 0.56485837697982788 + Red Component + 0.50599193572998047 + + Selection Color + + Blue Component + 0.19370138645172119 + Green Component + 0.15575926005840302 + Red Component + 0.0 + + \ No newline at end of file diff --git a/src/tools/ColorTool/schemes/solarized_light.itermcolors b/src/tools/ColorTool/schemes/solarized_light.itermcolors index b39cc7714cd..8dbdaacc527 100644 --- a/src/tools/ColorTool/schemes/solarized_light.itermcolors +++ b/src/tools/ColorTool/schemes/solarized_light.itermcolors @@ -1,289 +1,289 @@ - - - - - - Ansi 0 Color - - Color Space - sRGB - Blue Component - 0.8901960784313725 - Green Component - 0.9647058823529412 - Red Component - 0.9921568627450981 - - Ansi 1 Color - - Color Space - sRGB - Blue Component - 0.1843137254901961 - Green Component - 0.19607843137254902 - Red Component - 0.8627450980392157 - - Ansi 10 Color - - Color Space - sRGB - Blue Component - 0 - Green Component - 0.6 - Red Component - 0.5215686274509804 - - Ansi 11 Color - - Color Space - sRGB - Blue Component - 0 - Green Component - 0.5372549019607843 - Red Component - 0.7098039215686275 - - Ansi 12 Color - - Color Space - sRGB - Blue Component - 0.8235294117647058 - Green Component - 0.5450980392156862 - Red Component - 0.14901960784313725 - - Ansi 13 Color - - Color Space - sRGB - Blue Component - 0.7686274509803922 - Green Component - 0.44313725490196076 - Red Component - 0.4235294117647059 - - Ansi 14 Color - - Color Space - sRGB - Blue Component - 0.596078431372549 - Green Component - 0.6313725490196078 - Red Component - 0.16470588235294117 - - Ansi 15 Color - - Color Space - sRGB - Blue Component - 0.4588235294117647 - Green Component - 0.43137254901960786 - Red Component - 0.34509803921568627 - - Ansi 2 Color - - Color Space - sRGB - Blue Component - 0 - Green Component - 0.6 - Red Component - 0.5215686274509804 - - Ansi 3 Color - - Color Space - sRGB - Blue Component - 0 - Green Component - 0.5372549019607843 - Red Component - 0.7098039215686275 - - Ansi 4 Color - - Color Space - sRGB - Blue Component - 0.8235294117647058 - Green Component - 0.5450980392156862 - Red Component - 0.14901960784313725 - - Ansi 5 Color - - Color Space - sRGB - Blue Component - 0.7686274509803922 - Green Component - 0.44313725490196076 - Red Component - 0.4235294117647059 - - Ansi 6 Color - - Color Space - sRGB - Blue Component - 0.596078431372549 - Green Component - 0.6313725490196078 - Red Component - 0.16470588235294117 - - Ansi 7 Color - - Color Space - sRGB - Blue Component - 0.6313725490196078 - Green Component - 0.6313725490196078 - Red Component - 0.5764705882352941 - - Ansi 8 Color - - Color Space - sRGB - Blue Component - 0.5137254901960784 - Green Component - 0.4823529411764706 - Red Component - 0.396078431372549 - - Ansi 9 Color - - Color Space - sRGB - Blue Component - 0.1843137254901961 - Green Component - 0.19607843137254902 - Red Component - 0.8627450980392157 - - Background Color - - Color Space - sRGB - Blue Component - 0.8901960784313725 - Green Component - 0.9647058823529412 - Red Component - 0.9921568627450981 - - Bold Color - - Color Space - sRGB - Blue Component - 0.4588235294117647 - Green Component - 0.43137254901960786 - Red Component - 0.34509803921568627 - - Cursor Color - - Color Space - sRGB - Blue Component - 0.4588235294117647 - Green Component - 0.43137254901960786 - Red Component - 0.34509803921568627 - - Cursor Text Color - - Color Space - sRGB - Blue Component - 0.8901960784313725 - Green Component - 0.9647058823529412 - Red Component - 0.9921568627450981 - - Foreground Color - - Color Space - sRGB - Blue Component - 0.4588235294117647 - Green Component - 0.43137254901960786 - Red Component - 0.34509803921568627 - - Selected Text Color - - Color Space - sRGB - Blue Component - 0.4588235294117647 - Green Component - 0.43137254901960786 - Red Component - 0.34509803921568627 - - Selection Color - - Color Space - sRGB - Blue Component - 0.8901960784313725 - Green Component - 0.9647058823529412 - Red Component - 0.9921568627450981 - - - + + + + + + Ansi 0 Color + + Color Space + sRGB + Blue Component + 0.8901960784313725 + Green Component + 0.9647058823529412 + Red Component + 0.9921568627450981 + + Ansi 1 Color + + Color Space + sRGB + Blue Component + 0.1843137254901961 + Green Component + 0.19607843137254902 + Red Component + 0.8627450980392157 + + Ansi 10 Color + + Color Space + sRGB + Blue Component + 0 + Green Component + 0.6 + Red Component + 0.5215686274509804 + + Ansi 11 Color + + Color Space + sRGB + Blue Component + 0 + Green Component + 0.5372549019607843 + Red Component + 0.7098039215686275 + + Ansi 12 Color + + Color Space + sRGB + Blue Component + 0.8235294117647058 + Green Component + 0.5450980392156862 + Red Component + 0.14901960784313725 + + Ansi 13 Color + + Color Space + sRGB + Blue Component + 0.7686274509803922 + Green Component + 0.44313725490196076 + Red Component + 0.4235294117647059 + + Ansi 14 Color + + Color Space + sRGB + Blue Component + 0.596078431372549 + Green Component + 0.6313725490196078 + Red Component + 0.16470588235294117 + + Ansi 15 Color + + Color Space + sRGB + Blue Component + 0.4588235294117647 + Green Component + 0.43137254901960786 + Red Component + 0.34509803921568627 + + Ansi 2 Color + + Color Space + sRGB + Blue Component + 0 + Green Component + 0.6 + Red Component + 0.5215686274509804 + + Ansi 3 Color + + Color Space + sRGB + Blue Component + 0 + Green Component + 0.5372549019607843 + Red Component + 0.7098039215686275 + + Ansi 4 Color + + Color Space + sRGB + Blue Component + 0.8235294117647058 + Green Component + 0.5450980392156862 + Red Component + 0.14901960784313725 + + Ansi 5 Color + + Color Space + sRGB + Blue Component + 0.7686274509803922 + Green Component + 0.44313725490196076 + Red Component + 0.4235294117647059 + + Ansi 6 Color + + Color Space + sRGB + Blue Component + 0.596078431372549 + Green Component + 0.6313725490196078 + Red Component + 0.16470588235294117 + + Ansi 7 Color + + Color Space + sRGB + Blue Component + 0.6313725490196078 + Green Component + 0.6313725490196078 + Red Component + 0.5764705882352941 + + Ansi 8 Color + + Color Space + sRGB + Blue Component + 0.5137254901960784 + Green Component + 0.4823529411764706 + Red Component + 0.396078431372549 + + Ansi 9 Color + + Color Space + sRGB + Blue Component + 0.1843137254901961 + Green Component + 0.19607843137254902 + Red Component + 0.8627450980392157 + + Background Color + + Color Space + sRGB + Blue Component + 0.8901960784313725 + Green Component + 0.9647058823529412 + Red Component + 0.9921568627450981 + + Bold Color + + Color Space + sRGB + Blue Component + 0.4588235294117647 + Green Component + 0.43137254901960786 + Red Component + 0.34509803921568627 + + Cursor Color + + Color Space + sRGB + Blue Component + 0.4588235294117647 + Green Component + 0.43137254901960786 + Red Component + 0.34509803921568627 + + Cursor Text Color + + Color Space + sRGB + Blue Component + 0.8901960784313725 + Green Component + 0.9647058823529412 + Red Component + 0.9921568627450981 + + Foreground Color + + Color Space + sRGB + Blue Component + 0.4588235294117647 + Green Component + 0.43137254901960786 + Red Component + 0.34509803921568627 + + Selected Text Color + + Color Space + sRGB + Blue Component + 0.4588235294117647 + Green Component + 0.43137254901960786 + Red Component + 0.34509803921568627 + + Selection Color + + Color Space + sRGB + Blue Component + 0.8901960784313725 + Green Component + 0.9647058823529412 + Red Component + 0.9921568627450981 + + + diff --git a/src/tools/ColorTool/schemes/tango_dark.itermcolors b/src/tools/ColorTool/schemes/tango_dark.itermcolors index 8405cf8f109..2a5080bc8ff 100644 --- a/src/tools/ColorTool/schemes/tango_dark.itermcolors +++ b/src/tools/ColorTool/schemes/tango_dark.itermcolors @@ -1,224 +1,224 @@ - - - - - - Ansi 0 Color - - Blue Component - 0 - Green Component - 0 - Red Component - 0 - - Ansi 1 Color - - Blue Component - 0 - Green Component - 0 - Red Component - 0.8 - - Ansi 10 Color - - Blue Component - 0.2039216 - Green Component - 0.8862745 - Red Component - 0.5411764999999999 - - Ansi 11 Color - - Blue Component - 0.3098039 - Green Component - 0.9137255 - Red Component - 0.9882353 - - Ansi 12 Color - - Blue Component - 0.8117647 - Green Component - 0.6235294 - Red Component - 0.4470588 - - Ansi 13 Color - - Blue Component - 0.6588235 - Green Component - 0.4980392 - Red Component - 0.6784314 - - Ansi 14 Color - - Blue Component - 0.8862745 - Green Component - 0.8862745 - Red Component - 0.2039216 - - Ansi 15 Color - - Blue Component - 0.9254902 - Green Component - 0.9333333 - Red Component - 0.9333333 - - Ansi 2 Color - - Blue Component - 0.02352941 - Green Component - 0.6039215999999999 - Red Component - 0.3058824 - - Ansi 3 Color - - Blue Component - 0 - Green Component - 0.627451 - Red Component - 0.7686275 - - Ansi 4 Color - - Blue Component - 0.6431373 - Green Component - 0.3960784 - Red Component - 0.2039216 - - Ansi 5 Color - - Blue Component - 0.4823529 - Green Component - 0.3137255 - Red Component - 0.4588235 - - Ansi 6 Color - - Blue Component - 0.6039215999999999 - Green Component - 0.5960785 - Red Component - 0.02352941 - - Ansi 7 Color - - Blue Component - 0.8117647 - Green Component - 0.8431373 - Red Component - 0.827451 - - Ansi 8 Color - - Blue Component - 0.3254902 - Green Component - 0.3411765 - Red Component - 0.3333333 - - Ansi 9 Color - - Blue Component - 0.1607843 - Green Component - 0.1607843 - Red Component - 0.9372549 - - Background Color - - Blue Component - 0 - Green Component - 0 - Red Component - 0 - - Bold Color - - Blue Component - 1 - Green Component - 1 - Red Component - 1 - - Cursor Color - - Blue Component - 1 - Green Component - 1 - Red Component - 1 - - Cursor Text Color - - Blue Component - 0 - Green Component - 0 - Red Component - 0 - - Foreground Color - - Blue Component - 0.8117647 - Green Component - 0.8431373 - Red Component - 0.827451 - - Selected Text Color - - Blue Component - 0 - Green Component - 0 - Red Component - 0 - - Selection Color - - Blue Component - 1 - Green Component - 0.8353 - Red Component - 0.7098 - - - + + + + + + Ansi 0 Color + + Blue Component + 0 + Green Component + 0 + Red Component + 0 + + Ansi 1 Color + + Blue Component + 0 + Green Component + 0 + Red Component + 0.8 + + Ansi 10 Color + + Blue Component + 0.2039216 + Green Component + 0.8862745 + Red Component + 0.5411764999999999 + + Ansi 11 Color + + Blue Component + 0.3098039 + Green Component + 0.9137255 + Red Component + 0.9882353 + + Ansi 12 Color + + Blue Component + 0.8117647 + Green Component + 0.6235294 + Red Component + 0.4470588 + + Ansi 13 Color + + Blue Component + 0.6588235 + Green Component + 0.4980392 + Red Component + 0.6784314 + + Ansi 14 Color + + Blue Component + 0.8862745 + Green Component + 0.8862745 + Red Component + 0.2039216 + + Ansi 15 Color + + Blue Component + 0.9254902 + Green Component + 0.9333333 + Red Component + 0.9333333 + + Ansi 2 Color + + Blue Component + 0.02352941 + Green Component + 0.6039215999999999 + Red Component + 0.3058824 + + Ansi 3 Color + + Blue Component + 0 + Green Component + 0.627451 + Red Component + 0.7686275 + + Ansi 4 Color + + Blue Component + 0.6431373 + Green Component + 0.3960784 + Red Component + 0.2039216 + + Ansi 5 Color + + Blue Component + 0.4823529 + Green Component + 0.3137255 + Red Component + 0.4588235 + + Ansi 6 Color + + Blue Component + 0.6039215999999999 + Green Component + 0.5960785 + Red Component + 0.02352941 + + Ansi 7 Color + + Blue Component + 0.8117647 + Green Component + 0.8431373 + Red Component + 0.827451 + + Ansi 8 Color + + Blue Component + 0.3254902 + Green Component + 0.3411765 + Red Component + 0.3333333 + + Ansi 9 Color + + Blue Component + 0.1607843 + Green Component + 0.1607843 + Red Component + 0.9372549 + + Background Color + + Blue Component + 0 + Green Component + 0 + Red Component + 0 + + Bold Color + + Blue Component + 1 + Green Component + 1 + Red Component + 1 + + Cursor Color + + Blue Component + 1 + Green Component + 1 + Red Component + 1 + + Cursor Text Color + + Blue Component + 0 + Green Component + 0 + Red Component + 0 + + Foreground Color + + Blue Component + 0.8117647 + Green Component + 0.8431373 + Red Component + 0.827451 + + Selected Text Color + + Blue Component + 0 + Green Component + 0 + Red Component + 0 + + Selection Color + + Blue Component + 1 + Green Component + 0.8353 + Red Component + 0.7098 + + + diff --git a/src/tools/ColorTool/schemes/tango_light.itermcolors b/src/tools/ColorTool/schemes/tango_light.itermcolors index df041982a10..dc7a7d5140a 100644 --- a/src/tools/ColorTool/schemes/tango_light.itermcolors +++ b/src/tools/ColorTool/schemes/tango_light.itermcolors @@ -1,224 +1,224 @@ - - - - - - Ansi 0 Color - - Blue Component - 0 - Green Component - 0 - Red Component - 0 - - Ansi 1 Color - - Blue Component - 0 - Green Component - 0 - Red Component - 0.8 - - Ansi 10 Color - - Blue Component - 0.2039216 - Green Component - 0.8862745 - Red Component - 0.5411764999999999 - - Ansi 11 Color - - Blue Component - 0.3098039 - Green Component - 0.9137255 - Red Component - 0.9882353 - - Ansi 12 Color - - Blue Component - 0.8117647 - Green Component - 0.6235294 - Red Component - 0.4470588 - - Ansi 13 Color - - Blue Component - 0.6588235 - Green Component - 0.4980392 - Red Component - 0.6784314 - - Ansi 14 Color - - Blue Component - 0.8862745 - Green Component - 0.8862745 - Red Component - 0.2039216 - - Ansi 15 Color - - Blue Component - 0.9254902 - Green Component - 0.9333333 - Red Component - 0.9333333 - - Ansi 2 Color - - Blue Component - 0.02352941 - Green Component - 0.6039215999999999 - Red Component - 0.3058824 - - Ansi 3 Color - - Blue Component - 0 - Green Component - 0.627451 - Red Component - 0.7686275 - - Ansi 4 Color - - Blue Component - 0.6431373 - Green Component - 0.3960784 - Red Component - 0.2039216 - - Ansi 5 Color - - Blue Component - 0.4823529 - Green Component - 0.3137255 - Red Component - 0.4588235 - - Ansi 6 Color - - Blue Component - 0.6039215999999999 - Green Component - 0.5960785 - Red Component - 0.02352941 - - Ansi 7 Color - - Blue Component - 0.8117647 - Green Component - 0.8431373 - Red Component - 0.827451 - - Ansi 8 Color - - Blue Component - 0.3254902 - Green Component - 0.3411765 - Red Component - 0.3333333 - - Ansi 9 Color - - Blue Component - 0.1607843 - Green Component - 0.1607843 - Red Component - 0.9372549 - - Background Color - - Blue Component - 1 - Green Component - 1 - Red Component - 1 - - Bold Color - - Blue Component - 0 - Green Component - 0 - Red Component - 0 - - Cursor Color - - Blue Component - 0 - Green Component - 0 - Red Component - 0 - - Cursor Text Color - - Blue Component - 1 - Green Component - 1 - Red Component - 1 - - Foreground Color - - Blue Component - 0.3254902 - Green Component - 0.3411765 - Red Component - 0.3333333 - - Selected Text Color - - Blue Component - 0 - Green Component - 0 - Red Component - 0 - - Selection Color - - Blue Component - 1 - Green Component - 0.8353 - Red Component - 0.7098 - - - + + + + + + Ansi 0 Color + + Blue Component + 0 + Green Component + 0 + Red Component + 0 + + Ansi 1 Color + + Blue Component + 0 + Green Component + 0 + Red Component + 0.8 + + Ansi 10 Color + + Blue Component + 0.2039216 + Green Component + 0.8862745 + Red Component + 0.5411764999999999 + + Ansi 11 Color + + Blue Component + 0.3098039 + Green Component + 0.9137255 + Red Component + 0.9882353 + + Ansi 12 Color + + Blue Component + 0.8117647 + Green Component + 0.6235294 + Red Component + 0.4470588 + + Ansi 13 Color + + Blue Component + 0.6588235 + Green Component + 0.4980392 + Red Component + 0.6784314 + + Ansi 14 Color + + Blue Component + 0.8862745 + Green Component + 0.8862745 + Red Component + 0.2039216 + + Ansi 15 Color + + Blue Component + 0.9254902 + Green Component + 0.9333333 + Red Component + 0.9333333 + + Ansi 2 Color + + Blue Component + 0.02352941 + Green Component + 0.6039215999999999 + Red Component + 0.3058824 + + Ansi 3 Color + + Blue Component + 0 + Green Component + 0.627451 + Red Component + 0.7686275 + + Ansi 4 Color + + Blue Component + 0.6431373 + Green Component + 0.3960784 + Red Component + 0.2039216 + + Ansi 5 Color + + Blue Component + 0.4823529 + Green Component + 0.3137255 + Red Component + 0.4588235 + + Ansi 6 Color + + Blue Component + 0.6039215999999999 + Green Component + 0.5960785 + Red Component + 0.02352941 + + Ansi 7 Color + + Blue Component + 0.8117647 + Green Component + 0.8431373 + Red Component + 0.827451 + + Ansi 8 Color + + Blue Component + 0.3254902 + Green Component + 0.3411765 + Red Component + 0.3333333 + + Ansi 9 Color + + Blue Component + 0.1607843 + Green Component + 0.1607843 + Red Component + 0.9372549 + + Background Color + + Blue Component + 1 + Green Component + 1 + Red Component + 1 + + Bold Color + + Blue Component + 0 + Green Component + 0 + Red Component + 0 + + Cursor Color + + Blue Component + 0 + Green Component + 0 + Red Component + 0 + + Cursor Text Color + + Blue Component + 1 + Green Component + 1 + Red Component + 1 + + Foreground Color + + Blue Component + 0.3254902 + Green Component + 0.3411765 + Red Component + 0.3333333 + + Selected Text Color + + Blue Component + 0 + Green Component + 0 + Red Component + 0 + + Selection Color + + Blue Component + 1 + Green Component + 0.8353 + Red Component + 0.7098 + + + From 63b3820a18be096a4b1950e335c9605267440734 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 21 Nov 2023 21:57:56 +0100 Subject: [PATCH 069/167] Fix input buffering for A APIs (#16313) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes an issue where character-wise reading of an input like "abc" would return "a" to the caller, store "b" as a partial translation (= wrong) and return "c" for the caller to store it for the next call. Closes #16223 Closes #16299 ## Validation Steps Performed * `ReadFile` with a buffer size of 1 returns inputs character by character without dropping any inputs ✅ --------- Co-authored-by: Dustin L. Howett --- src/host/inputBuffer.cpp | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/host/inputBuffer.cpp b/src/host/inputBuffer.cpp index 3a4e556e440..5150a21427d 100644 --- a/src/host/inputBuffer.cpp +++ b/src/host/inputBuffer.cpp @@ -4,13 +4,13 @@ #include "precomp.h" #include "inputBuffer.hpp" -#include "stream.h" -#include "../types/inc/GlyphWidth.hpp" - #include +#include #include "misc.h" +#include "stream.h" #include "../interactivity/inc/ServiceLocator.hpp" +#include "../types/inc/GlyphWidth.hpp" #define INPUT_BUFFER_DEFAULT_INPUT_MODE (ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT | ENABLE_MOUSE_INPUT) @@ -87,10 +87,10 @@ void InputBuffer::Consume(bool isUnicode, std::wstring_view& source, std::span(s.size()), &buffer[0], sizeof(buffer), nullptr, nullptr); THROW_LAST_ERROR_IF(length <= 0); std::string_view slice{ &buffer[0], gsl::narrow_cast(length) }; @@ -98,10 +98,24 @@ void InputBuffer::Consume(bool isUnicode, std::wstring_view& source, std::span Date: Mon, 27 Nov 2023 14:40:47 -0500 Subject: [PATCH 070/167] Fix scrollbar resetting position on save (#16261) This PR fixes Issue #11875 by introducing a ScrollViewer and some logic for the scrollbar. The ScrollViewer prevents the scrollbar from scrolling to the top whenever "Save" is clicked in the Settings. In addition, the scrollbar is scrolled to the top of the page whenever navigating to another page within Settings. The scrollbar will not reset if attempting to navigate to the same page that is already navigated to. ## Validation Steps Performed Manual testing of the Settings by building the Terminal app. Closes #11875 --- .../TerminalSettingsEditor/Actions.xaml | 50 +- .../TerminalSettingsEditor/AddProfile.xaml | 112 ++- .../EditColorScheme.xaml | 798 +++++++++--------- .../GlobalAppearance.xaml | 184 ++-- .../TerminalSettingsEditor/Interaction.xaml | 122 ++- .../TerminalSettingsEditor/Launch.xaml | 422 +++++---- .../TerminalSettingsEditor/MainPage.cpp | 9 + .../TerminalSettingsEditor/MainPage.xaml | 27 +- .../Profiles_Advanced.xaml | 203 +++-- .../Profiles_Appearance.xaml | 245 +++--- .../TerminalSettingsEditor/Profiles_Base.cpp | 1 - .../TerminalSettingsEditor/Profiles_Base.xaml | 294 ++++--- .../TerminalSettingsEditor/Rendering.xaml | 40 +- 13 files changed, 1247 insertions(+), 1260 deletions(-) diff --git a/src/cascadia/TerminalSettingsEditor/Actions.xaml b/src/cascadia/TerminalSettingsEditor/Actions.xaml index ade657307c4..0c48e710e7f 100644 --- a/src/cascadia/TerminalSettingsEditor/Actions.xaml +++ b/src/cascadia/TerminalSettingsEditor/Actions.xaml @@ -326,31 +326,29 @@ - - - - - + + + + - - - - - + + + + diff --git a/src/cascadia/TerminalSettingsEditor/AddProfile.xaml b/src/cascadia/TerminalSettingsEditor/AddProfile.xaml index 25a6bbc0a81..e586f2eca93 100644 --- a/src/cascadia/TerminalSettingsEditor/AddProfile.xaml +++ b/src/cascadia/TerminalSettingsEditor/AddProfile.xaml @@ -21,72 +21,70 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/src/cascadia/TerminalSettingsEditor/EditColorScheme.xaml b/src/cascadia/TerminalSettingsEditor/EditColorScheme.xaml index 50a0c1357bb..8e68567bb82 100644 --- a/src/cascadia/TerminalSettingsEditor/EditColorScheme.xaml +++ b/src/cascadia/TerminalSettingsEditor/EditColorScheme.xaml @@ -82,421 +82,419 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - - - + + + + + - - + + - - + + - + - - + + - - + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - - - - + + + + + + + + + + + + - - - - - - - - - - + + + + + + diff --git a/src/cascadia/TerminalSettingsEditor/GlobalAppearance.xaml b/src/cascadia/TerminalSettingsEditor/GlobalAppearance.xaml index 700cfc61754..e8a3ea62900 100644 --- a/src/cascadia/TerminalSettingsEditor/GlobalAppearance.xaml +++ b/src/cascadia/TerminalSettingsEditor/GlobalAppearance.xaml @@ -25,109 +25,107 @@ - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - - + + + + + - - - - + + + + - - - - + + + + - - - - - - + + + + + diff --git a/src/cascadia/TerminalSettingsEditor/Interaction.xaml b/src/cascadia/TerminalSettingsEditor/Interaction.xaml index 295a584032b..d682aaf64b3 100644 --- a/src/cascadia/TerminalSettingsEditor/Interaction.xaml +++ b/src/cascadia/TerminalSettingsEditor/Interaction.xaml @@ -24,75 +24,73 @@ - - - - - - + + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - - + + + + diff --git a/src/cascadia/TerminalSettingsEditor/Launch.xaml b/src/cascadia/TerminalSettingsEditor/Launch.xaml index 71d0827e2b3..5a30fa771d8 100644 --- a/src/cascadia/TerminalSettingsEditor/Launch.xaml +++ b/src/cascadia/TerminalSettingsEditor/Launch.xaml @@ -41,244 +41,242 @@ - - - - - - - - - + + + + + + + + - - - - - - + + + + + + - + - + - - - - - + + + + + - - - - - - + + + + + + - - - - - - - - + + + + + + + + - - - - - - + + + + + + - + - + - + - + - - - - - + + + + + - - - - + + + + - - - - + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + - - + + - - + + - - + Width="118" + IsEnabled="{x:Bind local:Converters.InvertBoolean(ViewModel.UseDefaultLaunchPosition), Mode=OneWay}" + Style="{StaticResource LaunchPositionNumberBoxStyle}" + Value="{x:Bind ViewModel.InitialPosY, Mode=TwoWay}" /> + - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - + IsOn="{x:Bind ViewModel.CenterOnLaunch, Mode=TwoWay}" + Style="{StaticResource ToggleSwitchInExpanderStyle}" /> + + - + diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.cpp b/src/cascadia/TerminalSettingsEditor/MainPage.cpp index 2c18ce11c95..fd2ba5107a9 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.cpp +++ b/src/cascadia/TerminalSettingsEditor/MainPage.cpp @@ -278,6 +278,11 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation // Don't navigate to the same page again. return; } + else + { + // If we are navigating to a new page, scroll to the top + SettingsMainPage_ScrollViewer().ScrollToVerticalOffset(0); + } if (const auto navString = clickedItemContainer.Tag().try_as()) { @@ -332,12 +337,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation contentFrame().Navigate(xaml_typename(), winrt::make(profile, *this)); const auto crumb = winrt::make(breadcrumbTag, RS_(L"Profile_Appearance/Header"), BreadcrumbSubPage::Profile_Appearance); _breadcrumbs.Append(crumb); + SettingsMainPage_ScrollViewer().ScrollToVerticalOffset(0); } else if (currentPage == ProfileSubPage::Advanced) { contentFrame().Navigate(xaml_typename(), profile); const auto crumb = winrt::make(breadcrumbTag, RS_(L"Profile_Advanced/Header"), BreadcrumbSubPage::Profile_Advanced); _breadcrumbs.Append(crumb); + SettingsMainPage_ScrollViewer().ScrollToVerticalOffset(0); } } }); @@ -655,6 +662,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { _Navigate(newTag.as(), BreadcrumbSubPage::None); } + // Since we are navigating to a new profile after deletion, scroll up to the top + SettingsMainPage_ScrollViewer().ChangeView(nullptr, 0.0, nullptr); } } diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.xaml b/src/cascadia/TerminalSettingsEditor/MainPage.xaml index ec690e49c9e..92931355ffa 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.xaml +++ b/src/cascadia/TerminalSettingsEditor/MainPage.xaml @@ -170,18 +170,21 @@ - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - + + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - - - - - + + + + + + + + - - - - + + + + - - - - + + + + - - - - - - + + + + + diff --git a/src/cascadia/TerminalSettingsEditor/Profiles_Appearance.xaml b/src/cascadia/TerminalSettingsEditor/Profiles_Appearance.xaml index 710eb62ee16..806125785e8 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles_Appearance.xaml +++ b/src/cascadia/TerminalSettingsEditor/Profiles_Appearance.xaml @@ -41,144 +41,141 @@ Margin="{StaticResource StandardIndentMargin}" Style="{StaticResource DisclaimerStyle}" Visibility="{x:Bind Profile.IsBaseLayer}" /> - - - - - - + + + + + - - - - - + - - - - - - - - - - - - - + + + - - - - - - - - - - - - - + + + - + Value="{x:Bind local:Converters.PercentageToPercentageValue(Profile.Opacity), BindBack=Profile.SetAcrylicOpacityPercentageValue, Mode=TwoWay}" /> + Text="{x:Bind local:Converters.AppendPercentageSign(OpacitySlider.Value), Mode=OneWay}" /> - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/src/cascadia/TerminalSettingsEditor/Profiles_Base.cpp b/src/cascadia/TerminalSettingsEditor/Profiles_Base.cpp index 32570a016b7..b181ef80c9f 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles_Base.cpp +++ b/src/cascadia/TerminalSettingsEditor/Profiles_Base.cpp @@ -52,7 +52,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { DeleteButton().Focus(FocusState::Programmatic); _Profile.FocusDeleteButton(false); - ProfilesBase_ScrollView().ChangeView(nullptr, ProfilesBase_ScrollView().ScrollableHeight(), nullptr); } }); } diff --git a/src/cascadia/TerminalSettingsEditor/Profiles_Base.xaml b/src/cascadia/TerminalSettingsEditor/Profiles_Base.xaml index f660f40ed90..de83675532f 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles_Base.xaml +++ b/src/cascadia/TerminalSettingsEditor/Profiles_Base.xaml @@ -31,165 +31,161 @@ Margin="{StaticResource StandardIndentMargin}" Style="{StaticResource DisclaimerStyle}" Visibility="{x:Bind Profile.IsBaseLayer}" /> - - + - - - - - + + + + + - - - - - - - - - + + + + - - - + + + + + diff --git a/src/cascadia/TerminalSettingsEditor/Rendering.xaml b/src/cascadia/TerminalSettingsEditor/Rendering.xaml index 96a6ee95c6a..f19f33c145e 100644 --- a/src/cascadia/TerminalSettingsEditor/Rendering.xaml +++ b/src/cascadia/TerminalSettingsEditor/Rendering.xaml @@ -18,28 +18,26 @@ - - - + + - - - - + + + + - - - - + + + + - - - - - - + + + + + From 35240f263e0b27109676d23c94c98d00248f1046 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 27 Nov 2023 21:44:50 +0100 Subject: [PATCH 071/167] Fix font preview for conhost (#16324) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After exiting the main loop in this function the invariant `nFont <= NumberOfFonts` still holds true. Additionally, preceding this removed code is this (paraphrased): ```cpp if (nFont < NumberOfFonts) { RtlMoveMemory(...); } ``` It ensures that the given slot `nFont` is always unoccupied by moving it and all following items upwards if needed. As such, the call to `DeleteObject` is always incorrect, as the slot is always "empty", but may contain a copy of the previous occupant due to the `memmove`. This regressed in 154ac2b. Closes #16297 ## Validation Steps Performed * All fonts have a unique look in the preview panel ✅ --- src/propsheet/misc.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/propsheet/misc.cpp b/src/propsheet/misc.cpp index 054eef96121..9d8abcaf05a 100644 --- a/src/propsheet/misc.cpp +++ b/src/propsheet/misc.cpp @@ -398,10 +398,6 @@ int AddFont( /* * Store the font info */ - if (FontInfo[nFont].hFont != nullptr) - { - DeleteObject(FontInfo[nFont].hFont); - } FontInfo[nFont].hFont = hFont; FontInfo[nFont].Family = tmFamily; FontInfo[nFont].Size = SizeActual; From 8747a39a07dd601141a2ce2ee6eb076a8a5599b6 Mon Sep 17 00:00:00 2001 From: Lonny Wong Date: Tue, 28 Nov 2023 05:31:06 +0800 Subject: [PATCH 072/167] Fix Control+Space not sent to program running in terminal (#16298) Converts null byte to specific input event, so that it's properly delivered to the program running in the terminal. Closes #15939 --- src/host/inputBuffer.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/host/inputBuffer.cpp b/src/host/inputBuffer.cpp index 5150a21427d..abdfed1961f 100644 --- a/src/host/inputBuffer.cpp +++ b/src/host/inputBuffer.cpp @@ -830,6 +830,17 @@ void InputBuffer::_HandleTerminalInputCallback(const TerminalInput::StringType& for (const auto& wch : text) { + if (wch == UNICODE_NULL) + { + // Convert null byte back to input event with proper control state + const auto zeroKey = OneCoreSafeVkKeyScanW(0); + uint32_t ctrlState = 0; + WI_SetFlagIf(ctrlState, SHIFT_PRESSED, WI_IsFlagSet(zeroKey, 0x100)); + WI_SetFlagIf(ctrlState, LEFT_CTRL_PRESSED, WI_IsFlagSet(zeroKey, 0x200)); + WI_SetFlagIf(ctrlState, LEFT_ALT_PRESSED, WI_IsFlagSet(zeroKey, 0x400)); + _storage.push_back(SynthesizeKeyEvent(true, 1, LOBYTE(zeroKey), 0, wch, ctrlState)); + continue; + } _storage.push_back(SynthesizeKeyEvent(true, 1, 0, 0, wch, 0)); } From 7a1b6f9d2a9d564375fdc9792c506937b6474e67 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 27 Nov 2023 22:34:13 +0100 Subject: [PATCH 073/167] Fix scrolling with SetConsoleWindowInfo (#16334) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 81b7e54 caused a regression in `SetConsoleWindowInfo` and any other function that used the `WriteToScreen` helper. This is because it assumes that it can place the viewport anywhere randomly and it was written at a time where `TriggerScroll` didn't exist yet (there was no need for that (also not today, but that's being worked on)). Caching the viewport meant that `WriteToScreen`'s call to `TriggerRedraw` would pick up the viewport from the last rendered frame, which would cause the intersection of both to be potentially empty and nothing to be drawn on the screen. This commit reverts 81b7e54 as I found that it has no or negligible impact on performance at this point, likely due to the overall vastly better performance of conhost nowadays. Closes #15932 ## Validation Steps Performed * Scroll the viewport by entire pages worth of content using `SetConsoleWindowInfo` - see #15932 * The screen and scrollbars update immediately ✅ --- src/renderer/base/renderer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/base/renderer.cpp b/src/renderer/base/renderer.cpp index 2b328ee07f9..0b1d6b004cb 100644 --- a/src/renderer/base/renderer.cpp +++ b/src/renderer/base/renderer.cpp @@ -217,7 +217,7 @@ void Renderer::TriggerSystemRedraw(const til::rect* const prcDirtyClient) // - void Renderer::TriggerRedraw(const Viewport& region) { - auto view = _viewport; + auto view = _pData->GetViewport(); auto srUpdateRegion = region.ToExclusive(); // If the dirty region has double width lines, we need to double the size of From 3b5e5cf5f121395b3cacdcb60c3208488c4bd259 Mon Sep 17 00:00:00 2001 From: Marcel Wagner Date: Tue, 28 Nov 2023 00:41:17 +0100 Subject: [PATCH 074/167] Update paths to use linking within repo instead of github URL (#16358) Update paths to use relative linking instead of static GitHub link. Also fixes some dead links Closes #16338 --- CONTRIBUTING.md | 2 +- README.md | 14 +++++++------- SUPPORT.md | 2 +- doc/roadmap-2022.md | 4 ++-- doc/roadmap-2023.md | 4 ++-- .../#1571 - New Tab Menu Customization.md | 2 +- doc/specs/#1595 - Suggestions UI/Suggestions-UI.md | 2 +- ...s and commands, and synthesized action names.md | 2 +- ...032 - Elevation Quality of Life Improvements.md | 6 +++--- .../#4472 - Windows Terminal Session Management.md | 2 +- doc/specs/#653 - Quake Mode/#653 - Quake Mode.md | 2 +- doc/specs/#6899 - Action IDs/#6899 - Action IDs.md | 4 ++-- doc/terminal-v2-roadmap.md | 2 +- 13 files changed, 24 insertions(+), 24 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c7f3b1a710f..18ebdd96011 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,7 +14,7 @@ The point of doing all this work in public is to ensure that we are holding ours The team triages new issues several times a week. During triage, the team uses labels to categorize, manage, and drive the project workflow. -We employ [a bot engine](https://github.com/microsoft/terminal/blob/main/doc/bot.md) to help us automate common processes within our workflow. +We employ [a bot engine](./doc/bot.md) to help us automate common processes within our workflow. We drive the bot by tagging issues with specific labels which cause the bot engine to close issues, merge branches, etc. This bot engine helps us keep the repo clean by automating the process of notifying appropriate parties if/when information/follow-up is needed, and closing stale issues/PRs after reminders have remained unanswered for several days. diff --git a/README.md b/README.md index 88968fa7d22..f06a7053a18 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ This repository contains the source code for: * [Windows Terminal Preview](https://aka.ms/terminal-preview) * The Windows console host (`conhost.exe`) * Components shared between the two projects -* [ColorTool](https://github.com/microsoft/terminal/tree/main/src/tools/ColorTool) -* [Sample projects](https://github.com/microsoft/terminal/tree/main/samples) +* [ColorTool](./src/tools/ColorTool) +* [Sample projects](./samples) that show how to consume the Windows Console APIs Related repositories include: @@ -286,7 +286,7 @@ enhance Windows Terminal\! ***BEFORE you start work on a feature/fix***, please read & follow our [Contributor's -Guide](https://github.com/microsoft/terminal/blob/main/CONTRIBUTING.md) to +Guide](./CONTRIBUTING.md) to help avoid any wasted or duplicate effort. ## Communicating with the Team @@ -387,10 +387,10 @@ Please review these brief docs below about our coding practices. This is a work in progress as we learn what we'll need to provide people in order to be effective contributors to our project. -* [Coding Style](https://github.com/microsoft/terminal/blob/main/doc/STYLE.md) -* [Code Organization](https://github.com/microsoft/terminal/blob/main/doc/ORGANIZATION.md) -* [Exceptions in our legacy codebase](https://github.com/microsoft/terminal/blob/main/doc/EXCEPTIONS.md) -* [Helpful smart pointers and macros for interfacing with Windows in WIL](https://github.com/microsoft/terminal/blob/main/doc/WIL.md) +* [Coding Style](./doc/STYLE.md) +* [Code Organization](./doc/ORGANIZATION.md) +* [Exceptions in our legacy codebase](./doc/EXCEPTIONS.md) +* [Helpful smart pointers and macros for interfacing with Windows in WIL](./doc/WIL.md) --- diff --git a/SUPPORT.md b/SUPPORT.md index ba2389bf86f..275d58a9aed 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -14,4 +14,4 @@ Support for Windows Terminal is limited to the resources listed above. [gh-bug]: https://github.com/microsoft/terminal/issues/new?assignees=&labels=Issue-Bug&template=bug_report.md&title= [gh-feature]: https://github.com/microsoft/terminal/issues/new?assignees=&labels=Issue-Feature&template=Feature_Request.md&title= [docs]: https://docs.microsoft.com/windows/terminal -[contributor]: https://github.com/microsoft/terminal/blob/main/CONTRIBUTING.md +[contributor]: ./CONTRIBUTING.md diff --git a/doc/roadmap-2022.md b/doc/roadmap-2022.md index 5c4d5bf7e70..923ed00bf82 100644 --- a/doc/roadmap-2022.md +++ b/doc/roadmap-2022.md @@ -115,7 +115,7 @@ Incoming issues/asks/etc. are triaged several times a week, labeled appropriatel [Up Next]: https://github.com/microsoft/terminal/milestone/37 [Backlog]: https://github.com/microsoft/terminal/milestone/45 -[Terminal v2 Roadmap]: https://github.com/microsoft/terminal/tree/main/doc/terminal-v2-roadmap.md +[Terminal v2 Roadmap]: ./terminal-v2-roadmap.md [Windows Terminal Preview 1.2 Release]: https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-2-release/ [Windows Terminal Preview 1.3 Release]: https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-3-release/ @@ -131,4 +131,4 @@ Incoming issues/asks/etc. are triaged several times a week, labeled appropriatel [Windows Terminal Preview 1.13 Release]: https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-13-release/ [Windows Terminal Preview 1.14 Release]: https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-14-release/ -[Terminal 2023 Roadmap]: https://github.com/microsoft/terminal/tree/main/doc/roadmap-2023.md +[Terminal 2023 Roadmap]: ./roadmap-2023.md diff --git a/doc/roadmap-2023.md b/doc/roadmap-2023.md index c27faa340e0..33616a59890 100644 --- a/doc/roadmap-2023.md +++ b/doc/roadmap-2023.md @@ -54,9 +54,9 @@ _informative, not normative_ For a more fluid take on what each of the team's personal goals are, head on over to [Core team North Stars]. This has a list of more long-term goals that each team member is working towards, but not things that are necessarily committed work. -[^1]: A conclusive list of these features can be found at https://github.com/microsoft/terminal/blob/main/src/features.xml. Note that this is a raw XML doc used to light up specific parts of the codebase, and not something authored for human consumption. +[^1]: A conclusive list of these features can be found at [../src/features.xml](../src/features.xml). Note that this is a raw XML doc used to light up specific parts of the codebase, and not something authored for human consumption. -[2022 Roadmap]: https://github.com/microsoft/terminal/tree/main/doc/roadmap-2022.md +[2022 Roadmap]: ./roadmap-2022.md [Terminal 1.17]: https://github.com/microsoft/terminal/releases/tag/v1.17.1023 [Terminal 1.18]: https://github.com/microsoft/terminal/releases/tag/v1.18.1462.0 diff --git a/doc/specs/#1571 - New Tab Menu Customization/#1571 - New Tab Menu Customization.md b/doc/specs/#1571 - New Tab Menu Customization/#1571 - New Tab Menu Customization.md index 3d2b6532cd3..45bd669f848 100644 --- a/doc/specs/#1571 - New Tab Menu Customization/#1571 - New Tab Menu Customization.md +++ b/doc/specs/#1571 - New Tab Menu Customization/#1571 - New Tab Menu Customization.md @@ -435,7 +435,7 @@ ultimately deemed it to be out of scope for the initial spec review. [#2046]: https://github.com/microsoft/terminal/issues/2046 -[Command Palette, Addendum 1]: https://github.com/microsoft/terminal/blob/main/doc/specs/%232046%20-%20Unified%20keybindings%20and%20commands%2C%20and%20synthesized%20action%20names.md +[Command Palette, Addendum 1]: ../%232046%20-%20Unified%20keybindings%20and%20commands%2C%20and%20synthesized%20action%20names.md [#3337]: https://github.com/microsoft/terminal/issues/3337 [#6899]: https://github.com/microsoft/terminal/issues/6899 diff --git a/doc/specs/#1595 - Suggestions UI/Suggestions-UI.md b/doc/specs/#1595 - Suggestions UI/Suggestions-UI.md index 84f307c0ce6..b787e7b7bf5 100644 --- a/doc/specs/#1595 - Suggestions UI/Suggestions-UI.md +++ b/doc/specs/#1595 - Suggestions UI/Suggestions-UI.md @@ -734,7 +734,7 @@ shape of extensions will be is very much still to be determined. [#14939]: https://github.com/microsoft/terminal/issues/7285 [#keep]: https://github.com/zadjii/keep -[VsCode Tasks]: https://github.com/microsoft/terminal/blob/main/.vscode/tasks.json +[VsCode Tasks]: ../../../.vscode/tasks.json [Tasks]: https://github.com/microsoft/terminal/issues/12862 diff --git a/doc/specs/#2046 - Unified keybindings and commands, and synthesized action names.md b/doc/specs/#2046 - Unified keybindings and commands, and synthesized action names.md index de7832d7ed3..dffba83d6b6 100644 --- a/doc/specs/#2046 - Unified keybindings and commands, and synthesized action names.md +++ b/doc/specs/#2046 - Unified keybindings and commands, and synthesized action names.md @@ -605,4 +605,4 @@ as well as 3 schemes: "Scheme 1", "Scheme 2", and "Scheme 3". -[Command Palette Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%232046%20-%20Command%20Palette.md +[Command Palette Spec]: ./%232046%20-%20Command%20Palette.md diff --git a/doc/specs/#5000 - Process Model 2.0/#1032 - Elevation Quality of Life Improvements.md b/doc/specs/#5000 - Process Model 2.0/#1032 - Elevation Quality of Life Improvements.md index acac26f4d8a..4535ff304a2 100644 --- a/doc/specs/#5000 - Process Model 2.0/#1032 - Elevation Quality of Life Improvements.md +++ b/doc/specs/#5000 - Process Model 2.0/#1032 - Elevation Quality of Life Improvements.md @@ -612,8 +612,8 @@ You could have a profile that layers on an existing profile, with elevated-speci [#8514]: https://github.com/microsoft/terminal/issues/8514 [#10276]: https://github.com/microsoft/terminal/issues/10276 -[Process Model 2.0 Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%235000%20-%20Process%20Model%202.0.md -[Configuration object for profiles]: https://github.com/microsoft/terminal/blob/main/doc/specs/Configuration%20object%20for%20profiles.md -[Session Management Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%234472%20-%20Windows%20Terminal%20Session%20Management.md +[Process Model 2.0 Spec]: ../%235000%20-%20Process%20Model%202.0.md +[Configuration object for profiles]: ../%233062%20-%20Appearance configuration object for profiles.md +[Session Management Spec]: ./%234472%20-%20Windows%20Terminal%20Session%20Management.md [The Old New Thing: How can I launch an unelevated process from my elevated process, redux]: https://devblogs.microsoft.com/oldnewthing/20190425-00/?p=102443 [Workspace Trust]: https://code.visualstudio.com/docs/editor/workspace-trust diff --git a/doc/specs/#5000 - Process Model 2.0/#4472 - Windows Terminal Session Management.md b/doc/specs/#5000 - Process Model 2.0/#4472 - Windows Terminal Session Management.md index 97409d56be5..538da23725f 100644 --- a/doc/specs/#5000 - Process Model 2.0/#4472 - Windows Terminal Session Management.md +++ b/doc/specs/#5000 - Process Model 2.0/#4472 - Windows Terminal Session Management.md @@ -559,4 +559,4 @@ runtime. [Tab Tear-out in the community toolkit]: https://github.com/windows-toolkit/Sample-TabView-TearOff [Quake mode scenarios]: https://github.com/microsoft/terminal/issues/653#issuecomment-661370107 [`ISwapChainPanelNative2::SetSwapChainHandle`]: https://docs.microsoft.com/en-us/windows/win32/api/windows.ui.xaml.media.dxinterop/nf-windows-ui-xaml-media-dxinterop-iswapchainpanelnative2-setswapchainhandle -[Process Model 2.0 Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%235000%20-%20Process%20Model%202.0/%235000%20-%20Process%20Model%202.0.md +[Process Model 2.0 Spec]: ./doc/specs/%235000%20-%20Process%20Model%202.0/%235000%20-%20Process%20Model%202.0.md diff --git a/doc/specs/#653 - Quake Mode/#653 - Quake Mode.md b/doc/specs/#653 - Quake Mode/#653 - Quake Mode.md index 6b684e6386c..9bf32e33d06 100644 --- a/doc/specs/#653 - Quake Mode/#653 - Quake Mode.md +++ b/doc/specs/#653 - Quake Mode/#653 - Quake Mode.md @@ -730,7 +730,7 @@ user to differentiate between the two behaviors. [#5727]: https://github.com/microsoft/terminal/issues/5727 [#9992]: https://github.com/microsoft/terminal/issues/9992 -[Process Model 2.0 Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%235000%20-%20Process%20Model%202.0/%235000%20-%20Process%20Model%202.0.md +[Process Model 2.0 Spec]: ../%235000%20-%20Process%20Model%202.0/%235000%20-%20Process%20Model%202.0.md [Quake 3 sample]: https://youtu.be/ZmR6HQbuHPA?t=27 [`RegisterHotKey`]: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerhotkey [`dev/migrie/f/653-QUAKE-MODE`]: https://github.com/microsoft/terminal/tree/dev/migrie/f/653-QUAKE-MODE diff --git a/doc/specs/#6899 - Action IDs/#6899 - Action IDs.md b/doc/specs/#6899 - Action IDs/#6899 - Action IDs.md index 50faf9d1efb..c8c76b0d6af 100644 --- a/doc/specs/#6899 - Action IDs/#6899 - Action IDs.md +++ b/doc/specs/#6899 - Action IDs/#6899 - Action IDs.md @@ -215,8 +215,8 @@ actions manually. the tab context menu or the control context menu. -[Command Palette Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%232046%20-%20Command%20Palette.md -[New Tab Menu Customization Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%231571%20-%20New%20Tab%20Menu%20Customization.md +[Command Palette Spec]: ./doc/specs/%232046%20-%20Command%20Palette.md +[New Tab Menu Customization Spec]: ./doc/specs/%231571%20-%20New%20Tab%20Menu%20Customization.md [#1571]: https://github.com/microsoft/terminal/issues/1571 [#1912]: https://github.com/microsoft/terminal/issues/1912 diff --git a/doc/terminal-v2-roadmap.md b/doc/terminal-v2-roadmap.md index a7484bbe7d6..59394da3e5b 100644 --- a/doc/terminal-v2-roadmap.md +++ b/doc/terminal-v2-roadmap.md @@ -142,4 +142,4 @@ Feature Notes: [#4472]: https://github.com/microsoft/terminal/issues/4472 [#8048]: https://github.com/microsoft/terminal/pull/8048 -[Terminal 2022 Roadmap]: https://github.com/microsoft/terminal/tree/main/doc/roadmap-2022.md +[Terminal 2022 Roadmap]: ./roadmap-2022.md From 0c4751ba30333e9a47d0992fe044242867ab86ad Mon Sep 17 00:00:00 2001 From: Adam Reynolds Date: Thu, 30 Nov 2023 01:58:41 -0800 Subject: [PATCH 075/167] Fixed crash when cloud shell provider timed out or was closed waiting for login (#16364) ## Summary of the Pull Request Cloud shell connection calls out to Azure to do a device code login. When the polling interval is exceeded or the tab is closed, the method doing the connection polling returns `nullptr`, and `AzureConnection` immediately tries to `GetNamedString` from it, causing a crash. This doesn't repro on Terminal Stable or Preview, suggesting it's pretty recent related to the update of this azureconnection. This is just a proposed fix, not sure if you want to do more extensive changes to the affected class or not, so marking this as a draft. ## References and Relevant Issues * N/A - encountered this while using the terminal myself ## PR Checklist/Validation Tested out a local dev build: - [x] Terminal doesn't crash when cloudshell polling interval exceeded - [x] Terminal doesn't crash when cloudshell tab closed while polling for Azure login --- src/cascadia/TerminalConnection/AzureConnection.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/cascadia/TerminalConnection/AzureConnection.cpp b/src/cascadia/TerminalConnection/AzureConnection.cpp index 708518509b5..11c49838f95 100644 --- a/src/cascadia/TerminalConnection/AzureConnection.cpp +++ b/src/cascadia/TerminalConnection/AzureConnection.cpp @@ -604,6 +604,15 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation // Wait for user authentication and obtain the access/refresh tokens auto authenticatedResponse = _WaitForUser(devCode, pollInterval, expiresIn); + + // If user closed tab, `_WaitForUser` returns nullptr + // This also occurs if the connection times out, when polling time exceeds the expiry time + if (!authenticatedResponse) + { + _transitionToState(ConnectionState::Failed); + return; + } + _setAccessToken(authenticatedResponse.GetNamedString(L"access_token")); _refreshToken = authenticatedResponse.GetNamedString(L"refresh_token"); From 130c9fbd7621148244bf9aeca6b3cf634475b6a0 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 30 Nov 2023 15:52:39 +0100 Subject: [PATCH 076/167] Remove unused Utf8ToWideCharParser (#16392) I randomly came across this class, that I didn't even remember we had. We don't use this class at the moment and won't need it any time soon. Its current implementation is also fairly questionable. While `til::u16state` isn't "perfect", it's vastly better than this. --- doc/ORGANIZATION.md | 2 - src/host/host-common.vcxitems | 2 - src/host/lib/hostlib.vcxproj.filters | 6 - src/host/sources.inc | 1 - src/host/ut_host/Host.UnitTests.vcxproj | 1 - .../ut_host/Host.UnitTests.vcxproj.filters | 3 - .../ut_host/Utf8ToWideCharParserTests.cpp | 405 -------------- src/host/ut_host/sources | 1 - src/host/utf8ToWideCharParser.cpp | 520 ------------------ src/host/utf8ToWideCharParser.hpp | 64 --- 10 files changed, 1005 deletions(-) delete mode 100644 src/host/ut_host/Utf8ToWideCharParserTests.cpp delete mode 100644 src/host/utf8ToWideCharParser.cpp delete mode 100644 src/host/utf8ToWideCharParser.hpp diff --git a/doc/ORGANIZATION.md b/doc/ORGANIZATION.md index 07a6e41ee7e..5f93639d099 100644 --- a/doc/ORGANIZATION.md +++ b/doc/ORGANIZATION.md @@ -125,8 +125,6 @@ * Private calls into the Windows Window Manager to perform privileged actions related to the console process (working to eliminate) or for High DPI stuff (also working to eliminate) * `Userprivapi.cpp` * `Windowdpiapi.cpp` -* New UTF8 state machine in progress to improve Bash (and other apps) support for UTF-8 in console - * `Utf8ToWideCharParser.cpp` * Window resizing/layout/management/window messaging loops and all that other stuff that has us interact with Windows to create a visual display surface and control the user interaction entry point * `Window.cpp` * `Windowproc.cpp` diff --git a/src/host/host-common.vcxitems b/src/host/host-common.vcxitems index ee482e31a93..74d000c3c56 100644 --- a/src/host/host-common.vcxitems +++ b/src/host/host-common.vcxitems @@ -46,7 +46,6 @@ - @@ -100,7 +99,6 @@ - diff --git a/src/host/lib/hostlib.vcxproj.filters b/src/host/lib/hostlib.vcxproj.filters index c108f2d1411..ff328c21575 100644 --- a/src/host/lib/hostlib.vcxproj.filters +++ b/src/host/lib/hostlib.vcxproj.filters @@ -111,9 +111,6 @@ Source Files - - Source Files - Source Files @@ -266,9 +263,6 @@ Header Files - - Header Files - Header Files diff --git a/src/host/sources.inc b/src/host/sources.inc index e3a878161e0..92b2629aaf8 100644 --- a/src/host/sources.inc +++ b/src/host/sources.inc @@ -84,7 +84,6 @@ SOURCES = \ ..\writeData.cpp \ ..\renderData.cpp \ ..\renderFontDefaults.cpp \ - ..\utf8ToWideCharParser.cpp \ ..\conareainfo.cpp \ ..\conimeinfo.cpp \ ..\ConsoleArguments.cpp \ diff --git a/src/host/ut_host/Host.UnitTests.vcxproj b/src/host/ut_host/Host.UnitTests.vcxproj index f995dea0a44..3592ec1489f 100644 --- a/src/host/ut_host/Host.UnitTests.vcxproj +++ b/src/host/ut_host/Host.UnitTests.vcxproj @@ -28,7 +28,6 @@ - diff --git a/src/host/ut_host/Host.UnitTests.vcxproj.filters b/src/host/ut_host/Host.UnitTests.vcxproj.filters index 4366c712fb9..9e61fb41067 100644 --- a/src/host/ut_host/Host.UnitTests.vcxproj.filters +++ b/src/host/ut_host/Host.UnitTests.vcxproj.filters @@ -39,9 +39,6 @@ Source Files - - Source Files - Source Files diff --git a/src/host/ut_host/Utf8ToWideCharParserTests.cpp b/src/host/ut_host/Utf8ToWideCharParserTests.cpp deleted file mode 100644 index 041e0bf39c2..00000000000 --- a/src/host/ut_host/Utf8ToWideCharParserTests.cpp +++ /dev/null @@ -1,405 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "WexTestClass.h" -#include "../../inc/consoletaeftemplates.hpp" - -#include "utf8ToWideCharParser.hpp" - -#define IsBitSet WI_IsFlagSet - -using namespace WEX::Common; -using namespace WEX::Logging; -using namespace WEX::TestExecution; -using namespace std; - -class Utf8ToWideCharParserTests -{ - static const unsigned int utf8CodePage = 65001; - static const unsigned int USACodePage = 1252; - - TEST_CLASS(Utf8ToWideCharParserTests); - - TEST_METHOD(ConvertsAsciiTest) - { - Log::Comment(L"Testing that ASCII chars are correctly converted to wide chars"); - auto parser = Utf8ToWideCharParser{ utf8CodePage }; - // ascii "hello" - const unsigned char hello[5] = { 0x48, 0x65, 0x6c, 0x6c, 0x6f }; - const unsigned char wideHello[10] = { 0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00 }; - unsigned int count = 5; - unsigned int consumed = 0; - unsigned int generated = 0; - unique_ptr output{ nullptr }; - - VERIFY_SUCCEEDED(parser.Parse(hello, count, consumed, output, generated)); - VERIFY_ARE_EQUAL(consumed, (unsigned int)5); - VERIFY_ARE_EQUAL(generated, (unsigned int)5); - VERIFY_ARE_NOT_EQUAL(output.get(), nullptr); - - auto pReturnedBytes = reinterpret_cast(output.get()); - for (auto i = 0; i < ARRAYSIZE(wideHello); ++i) - { - VERIFY_ARE_EQUAL(wideHello[i], pReturnedBytes[i]); - } - } - - TEST_METHOD(ConvertSimpleUtf8Test) - { - Log::Comment(L"Testing that a simple UTF8 sequence can be converted"); - auto parser = Utf8ToWideCharParser{ utf8CodePage }; - // U+3059, U+3057 (hiragana sushi) - const unsigned char sushi[6] = { 0xe3, 0x81, 0x99, 0xe3, 0x81, 0x97 }; - const unsigned char wideSushi[4] = { 0x59, 0x30, 0x57, 0x30 }; - unsigned int count = 6; - unsigned int consumed = 0; - unsigned int generated = 0; - unique_ptr output{ nullptr }; - - VERIFY_SUCCEEDED(parser.Parse(sushi, count, consumed, output, generated)); - VERIFY_ARE_EQUAL(consumed, (unsigned int)6); - VERIFY_ARE_EQUAL(generated, (unsigned int)2); - VERIFY_ARE_NOT_EQUAL(output.get(), nullptr); - - auto pReturnedBytes = reinterpret_cast(output.get()); - for (auto i = 0; i < ARRAYSIZE(wideSushi); ++i) - { - VERIFY_ARE_EQUAL(wideSushi[i], pReturnedBytes[i]); - } - } - - TEST_METHOD(WaitsForAdditionalInputAfterPartialSequenceTest) - { - Log::Comment(L"Testing that nothing is returned when parsing a partial sequence until the sequence is complete"); - // U+3057 (hiragana shi) - unsigned char shi[3] = { 0xe3, 0x81, 0x97 }; - unsigned char wideShi[2] = { 0x57, 0x30 }; - auto parser = Utf8ToWideCharParser{ utf8CodePage }; - unsigned int count = 1; - unsigned int consumed = 0; - unsigned int generated = 0; - unique_ptr output{ nullptr }; - - for (auto i = 0; i < 2; ++i) - { - VERIFY_SUCCEEDED(parser.Parse(shi + i, count, consumed, output, generated)); - VERIFY_ARE_EQUAL(consumed, (unsigned int)1); - VERIFY_ARE_EQUAL(generated, (unsigned int)0); - VERIFY_ARE_EQUAL(output.get(), nullptr); - count = 1; - } - - VERIFY_SUCCEEDED(parser.Parse(shi + 2, count, consumed, output, generated)); - VERIFY_ARE_EQUAL(consumed, (unsigned int)1); - VERIFY_ARE_EQUAL(generated, (unsigned int)1); - VERIFY_ARE_NOT_EQUAL(output.get(), nullptr); - - auto pReturnedBytes = reinterpret_cast(output.get()); - for (auto i = 0; i < ARRAYSIZE(wideShi); ++i) - { - VERIFY_ARE_EQUAL(wideShi[i], pReturnedBytes[i]); - } - } - - TEST_METHOD(ReturnsInitialPartOfSequenceThatEndsWithPartialTest) - { - Log::Comment(L"Testing that a valid portion of a sequence is returned when it ends with a partial sequence"); - // U+3059, U+3057 (hiragana sushi) - const unsigned char sushi[6] = { 0xe3, 0x81, 0x99, 0xe3, 0x81, 0x97 }; - const unsigned char wideSushi[4] = { 0x59, 0x30, 0x57, 0x30 }; - unsigned int count = 4; - unsigned int consumed = 0; - unsigned int generated = 0; - unique_ptr output{ nullptr }; - auto parser = Utf8ToWideCharParser{ utf8CodePage }; - - VERIFY_SUCCEEDED(parser.Parse(sushi, count, consumed, output, generated)); - // check that we got the first wide char back - VERIFY_ARE_EQUAL(consumed, (unsigned int)4); - VERIFY_ARE_EQUAL(generated, (unsigned int)1); - VERIFY_ARE_NOT_EQUAL(output.get(), nullptr); - - auto pReturnedBytes = reinterpret_cast(output.get()); - for (auto i = 0; i < 2; ++i) - { - VERIFY_ARE_EQUAL(wideSushi[i], pReturnedBytes[i]); - } - - // add byte 2 of 3 to parser - count = 1; - consumed = 0; - generated = 0; - output.reset(nullptr); - VERIFY_SUCCEEDED(parser.Parse(sushi + 4, count, consumed, output, generated)); - VERIFY_ARE_EQUAL(consumed, (unsigned int)1); - VERIFY_ARE_EQUAL(generated, (unsigned int)0); - VERIFY_ARE_EQUAL(output.get(), nullptr); - - // add last byte - count = 1; - consumed = 0; - generated = 0; - output.reset(nullptr); - VERIFY_SUCCEEDED(parser.Parse(sushi + 5, count, consumed, output, generated)); - VERIFY_ARE_EQUAL(consumed, (unsigned int)1); - VERIFY_ARE_EQUAL(generated, (unsigned int)1); - VERIFY_ARE_NOT_EQUAL(output.get(), nullptr); - - pReturnedBytes = reinterpret_cast(output.get()); - for (auto i = 0; i < 2; ++i) - { - VERIFY_ARE_EQUAL(wideSushi[i + 2], pReturnedBytes[i]); - } - } - - TEST_METHOD(MergesMultiplePartialSequencesTest) - { - Log::Comment(L"Testing that partial sequences sent individually will be merged together"); - - // clang-format off - // (hiragana doomo arigatoo) - const unsigned char doomoArigatoo[24] = { - 0xe3, 0x81, 0xa9, // U+3069 - 0xe3, 0x81, 0x86, // U+3046 - 0xe3, 0x82, 0x82, // U+3082 - 0xe3, 0x81, 0x82, // U+3042 - 0xe3, 0x82, 0x8a, // U+308A - 0xe3, 0x81, 0x8c, // U+304C - 0xe3, 0x81, 0xa8, // U+3068 - 0xe3, 0x81, 0x86 // U+3046 - }; - const unsigned char wideDoomoArigatoo[16] = { - 0x69, 0x30, - 0x46, 0x30, - 0x82, 0x30, - 0x42, 0x30, - 0x8a, 0x30, - 0x4c, 0x30, - 0x68, 0x30, - 0x46, 0x30 - }; - // clang-format on - - // send first 4 bytes - unsigned int count = 4; - unsigned int consumed = 0; - unsigned int generated = 0; - unique_ptr output{ nullptr }; - auto parser = Utf8ToWideCharParser{ utf8CodePage }; - - VERIFY_SUCCEEDED(parser.Parse(doomoArigatoo, count, consumed, output, generated)); - VERIFY_ARE_EQUAL(consumed, (unsigned int)4); - VERIFY_ARE_EQUAL(generated, (unsigned int)1); - VERIFY_ARE_NOT_EQUAL(output.get(), nullptr); - - auto pReturnedBytes = reinterpret_cast(output.get()); - for (auto i = 0; i < 2; ++i) - { - VERIFY_ARE_EQUAL(wideDoomoArigatoo[i], pReturnedBytes[i]); - } - - // send next 16 bytes - count = 16; - consumed = 0; - generated = 0; - output.reset(nullptr); - VERIFY_SUCCEEDED(parser.Parse(doomoArigatoo + 4, count, consumed, output, generated)); - VERIFY_ARE_EQUAL(consumed, (unsigned int)16); - VERIFY_ARE_EQUAL(generated, (unsigned int)5); - VERIFY_ARE_NOT_EQUAL(output.get(), nullptr); - - pReturnedBytes = reinterpret_cast(output.get()); - for (auto i = 0; i < 10; ++i) - { - VERIFY_ARE_EQUAL(wideDoomoArigatoo[i + 2], pReturnedBytes[i]); - } - - // send last 4 bytes - count = 4; - consumed = 0; - generated = 0; - output.reset(nullptr); - VERIFY_SUCCEEDED(parser.Parse(doomoArigatoo + 20, count, consumed, output, generated)); - VERIFY_ARE_EQUAL(consumed, (unsigned int)4); - VERIFY_ARE_EQUAL(generated, (unsigned int)2); - VERIFY_ARE_NOT_EQUAL(output.get(), nullptr); - - pReturnedBytes = reinterpret_cast(output.get()); - for (auto i = 0; i < 4; ++i) - { - VERIFY_ARE_EQUAL(wideDoomoArigatoo[i + 12], pReturnedBytes[i]); - } - } - - TEST_METHOD(RemovesInvalidSequencesTest) - { - Log::Comment(L"Testing that invalid sequences are removed and don't stop the parsing of the rest"); - - // clang-format off - // hiragana sushi with junk between japanese characters - const unsigned char sushi[9] = { - 0xe3, 0x81, 0x99, // U+3059 - 0x80, 0x81, 0x82, // junk continuation bytes - 0xe3, 0x81, 0x97 // U+3057 - }; - // clang-format on - - const unsigned char wideSushi[4] = { 0x59, 0x30, 0x57, 0x30 }; - unsigned int count = 9; - unsigned int consumed = 0; - unsigned int generated = 0; - unique_ptr output{ nullptr }; - auto parser = Utf8ToWideCharParser{ utf8CodePage }; - - VERIFY_SUCCEEDED(parser.Parse(sushi, count, consumed, output, generated)); - VERIFY_ARE_EQUAL(consumed, (unsigned int)9); - VERIFY_ARE_EQUAL(generated, (unsigned int)2); - VERIFY_ARE_NOT_EQUAL(output.get(), nullptr); - - auto pReturnedBytes = reinterpret_cast(output.get()); - for (auto i = 0; i < ARRAYSIZE(wideSushi); ++i) - { - VERIFY_ARE_EQUAL(wideSushi[i], pReturnedBytes[i]); - } - } - - TEST_METHOD(NonMinimalFormTest) - { - Log::Comment(L"Testing that non-minimal forms of a character are tolerated don't stop the rest"); - - // clang-format off - - // Test data - const unsigned char data[] = { - 0x60, 0x12, 0x08, 0x7f, // single byte points - 0xc0, 0x80, // U+0000 as a 2-byte sequence (non-minimal) - 0x41, 0x48, 0x06, 0x55, // more single byte points - 0xe0, 0x80, 0x80, // U+0000 as a 3-byte sequence (non-minimal) - 0x18, 0x77, 0x40, 0x31, // more single byte points - 0xf0, 0x80, 0x80, 0x80, // U+0000 as a 4-byte sequence (non-minimal) - 0x59, 0x1f, 0x68, 0x20 // more single byte points - }; - - // Expected conversion - const wchar_t wideData[] = { - 0x0060, 0x0012, 0x0008, 0x007f, - 0xfffd, 0xfffd, // The number of replacements per invalid sequence is not intended to be load-bearing - 0x0041, 0x0048, 0x0006, 0x0055, - 0xfffd, 0xfffd, // It is just representative of what it looked like when fixing this for GH#3380 - 0x0018, 0x0077, 0x0040, 0x0031, - 0xfffd, 0xfffd, 0xfffd, // Change if necessary when completing GH#3378 - 0x0059, 0x001f, 0x0068, 0x0020 - }; - - // clang-format on - - const auto count = gsl::narrow_cast(ARRAYSIZE(data)); - const auto wideCount = gsl::narrow_cast(ARRAYSIZE(wideData)); - unsigned int consumed = 0; - unsigned int generated = 0; - unique_ptr output{ nullptr }; - auto parser = Utf8ToWideCharParser{ utf8CodePage }; - - VERIFY_SUCCEEDED(parser.Parse(data, count, consumed, output, generated)); - VERIFY_ARE_EQUAL(count, consumed); - VERIFY_ARE_EQUAL(wideCount, generated); - VERIFY_IS_NOT_NULL(output.get()); - - const auto expected = WEX::Common::String(wideData, wideCount); - const auto actual = WEX::Common::String(output.get(), generated); - VERIFY_ARE_EQUAL(expected, actual); - } - - TEST_METHOD(PartialBytesAreDroppedOnCodePageChangeTest) - { - Log::Comment(L"Testing that a saved partial sequence is cleared when the codepage changes"); - auto parser = Utf8ToWideCharParser{ utf8CodePage }; - // 2 bytes of a 4 byte sequence - const unsigned int inputSize = 2; - const unsigned char partialSequence[inputSize] = { 0xF0, 0x80 }; - auto count = inputSize; - unsigned int consumed = 0; - unsigned int generated = 0; - unique_ptr output{ nullptr }; - VERIFY_SUCCEEDED(parser.Parse(partialSequence, count, consumed, output, generated)); - VERIFY_ARE_EQUAL(parser._currentState, Utf8ToWideCharParser::_State::BeginPartialParse); - VERIFY_ARE_EQUAL(parser._bytesStored, inputSize); - // set the codepage to the same one it currently is, ensure - // that nothing changes - parser.SetCodePage(utf8CodePage); - VERIFY_ARE_EQUAL(parser._currentState, Utf8ToWideCharParser::_State::BeginPartialParse); - VERIFY_ARE_EQUAL(parser._bytesStored, inputSize); - // change to a different codepage, ensure parser is reset - parser.SetCodePage(USACodePage); - VERIFY_ARE_EQUAL(parser._currentState, Utf8ToWideCharParser::_State::Ready); - VERIFY_ARE_EQUAL(parser._bytesStored, (unsigned int)0); - } - - TEST_METHOD(_IsLeadByteTest) - { - Log::Comment(L"Testing that _IsLeadByte properly differentiates correct from incorrect sequences"); - auto parser = Utf8ToWideCharParser{ utf8CodePage }; - VERIFY_IS_TRUE(parser._IsLeadByte(0xC0)); // 2 byte sequence - VERIFY_IS_TRUE(parser._IsLeadByte(0xE0)); // 3 byte sequence - VERIFY_IS_TRUE(parser._IsLeadByte(0xF0)); // 4 byte sequence - VERIFY_IS_FALSE(parser._IsLeadByte(0x00)); // ASCII char NUL - VERIFY_IS_FALSE(parser._IsLeadByte(0x80)); // continuation byte - VERIFY_IS_FALSE(parser._IsLeadByte(0x83)); // continuation byte - VERIFY_IS_FALSE(parser._IsLeadByte(0x7E)); // ASCII char '~' - VERIFY_IS_FALSE(parser._IsLeadByte(0x21)); // ASCII char '!' - VERIFY_IS_FALSE(parser._IsLeadByte(0xF8)); // invalid 5 byte sequence - VERIFY_IS_FALSE(parser._IsLeadByte(0xFC)); // invalid 6 byte sequence - VERIFY_IS_FALSE(parser._IsLeadByte(0xFE)); // invalid 7 byte sequence - VERIFY_IS_FALSE(parser._IsLeadByte(0xFF)); // all 1's - } - - TEST_METHOD(_IsContinuationByteTest) - { - Log::Comment(L"Testing that _IsContinuationByte properly differentiates correct from incorrect sequences"); - auto parser = Utf8ToWideCharParser{ utf8CodePage }; - for (BYTE i = 0x00; i < 0xFF; ++i) - { - if (IsBitSet(i, 0x80) && !IsBitSet(i, 0x40)) - { - VERIFY_IS_TRUE(parser._IsContinuationByte(i), NoThrowString().Format(L"Byte is 0x%02x", i)); - } - else - { - VERIFY_IS_FALSE(parser._IsContinuationByte(i), NoThrowString().Format(L"Byte is 0x%02x", i)); - } - } - VERIFY_IS_FALSE(parser._IsContinuationByte(0xFF)); - } - - TEST_METHOD(_IsAsciiByteTest) - { - Log::Comment(L"Testing that _IsAsciiByte properly differentiates correct from incorrect sequences"); - auto parser = Utf8ToWideCharParser{ utf8CodePage }; - for (BYTE i = 0x00; i < 0x80; ++i) - { - VERIFY_IS_TRUE(parser._IsAsciiByte(i), NoThrowString().Format(L"Byte is 0x%02x", i)); - } - for (BYTE i = 0xFF; i > 0x7F; --i) - { - VERIFY_IS_FALSE(parser._IsAsciiByte(i), NoThrowString().Format(L"Byte is 0x%02x", i)); - } - } - - TEST_METHOD(_Utf8SequenceSizeTest) - { - Log::Comment(L"Testing that _Utf8SequenceSize correctly counts the number of MSB 1's"); - auto parser = Utf8ToWideCharParser{ utf8CodePage }; - VERIFY_ARE_EQUAL(parser._Utf8SequenceSize(0x00), (unsigned int)0); - VERIFY_ARE_EQUAL(parser._Utf8SequenceSize(0x80), (unsigned int)1); - VERIFY_ARE_EQUAL(parser._Utf8SequenceSize(0xC2), (unsigned int)2); - VERIFY_ARE_EQUAL(parser._Utf8SequenceSize(0xE3), (unsigned int)3); - VERIFY_ARE_EQUAL(parser._Utf8SequenceSize(0xF0), (unsigned int)4); - VERIFY_ARE_EQUAL(parser._Utf8SequenceSize(0xF3), (unsigned int)4); - VERIFY_ARE_EQUAL(parser._Utf8SequenceSize(0xF8), (unsigned int)5); - VERIFY_ARE_EQUAL(parser._Utf8SequenceSize(0xFC), (unsigned int)6); - VERIFY_ARE_EQUAL(parser._Utf8SequenceSize(0xFD), (unsigned int)6); - VERIFY_ARE_EQUAL(parser._Utf8SequenceSize(0xFE), (unsigned int)7); - VERIFY_ARE_EQUAL(parser._Utf8SequenceSize(0xFF), (unsigned int)8); - } -}; diff --git a/src/host/ut_host/sources b/src/host/ut_host/sources index 9b47dded30d..1312b3e63fa 100644 --- a/src/host/ut_host/sources +++ b/src/host/ut_host/sources @@ -27,7 +27,6 @@ SOURCES = \ TextBufferTests.cpp \ ClipboardTests.cpp \ SelectionTests.cpp \ - Utf8ToWideCharParserTests.cpp \ OutputCellIteratorTests.cpp \ InitTests.cpp \ TitleTests.cpp \ diff --git a/src/host/utf8ToWideCharParser.cpp b/src/host/utf8ToWideCharParser.cpp deleted file mode 100644 index 911ddf267cc..00000000000 --- a/src/host/utf8ToWideCharParser.cpp +++ /dev/null @@ -1,520 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" - -#include "utf8ToWideCharParser.hpp" -#include - -#ifndef WIL_ENABLE_EXCEPTIONS -#error WIL exception helpers must be enabled -#endif - -#define IsBitSet WI_IsFlagSet - -const byte NonAsciiBytePrefix = 0x80; - -const byte ContinuationByteMask = 0xC0; -const byte ContinuationBytePrefix = 0x80; - -const byte MostSignificantBitMask = 0x80; - -// Routine Description: -// - Constructs an instance of the parser. -// Arguments: -// - codePage - Starting code page to interpret input with. -// Return Value: -// - A new instance of the parser. -Utf8ToWideCharParser::Utf8ToWideCharParser(const unsigned int codePage) : - _currentCodePage{ codePage }, - _bytesStored{ 0 }, - _currentState{ _State::Ready }, - _convertedWideChars{ nullptr } -{ - std::fill_n(_utf8CodePointPieces, _UTF8_BYTE_SEQUENCE_MAX, 0ui8); -} - -// Routine Description: -// - Set the code page that input sequences will correspond to. Clears -// any saved partial multi-byte sequences if the code page changes -// from the code page the partial sequence is associated with. -// Arguments: -// - codePage - the code page to set to. -// Return Value: -// - -void Utf8ToWideCharParser::SetCodePage(const unsigned int codePage) -{ - if (_currentCodePage != codePage) - { - _currentCodePage = codePage; - // we can't be making any assumptions about the partial - // sequence we were storing now that the codepage has changed - _bytesStored = 0; - _currentState = _State::Ready; - } -} - -// Routine Description: -// - Parses the input multi-byte sequence. -// Arguments: -// - pBytes - The byte sequence to parse. -// - cchBuffer - The amount of bytes in pBytes. This will contain the -// number of wide chars contained by converted after this function is -// run, or 0 if an error occurs (or if pBytes is 0). -// - converted - a valid unique_ptr to store the parsed wide chars -// in. On error this will contain nullptr instead of an array. -// Return Value: -// - -[[nodiscard]] HRESULT Utf8ToWideCharParser::Parse(_In_reads_(cchBuffer) const byte* const pBytes, - _In_ const unsigned int cchBuffer, - _Out_ unsigned int& cchConsumed, - _Inout_ std::unique_ptr& converted, - _Out_ unsigned int& cchConverted) -{ - cchConsumed = 0; - cchConverted = 0; - - // we can't parse anything if we weren't given any data to parse - if (cchBuffer == 0) - { - return S_OK; - } - // we shouldn't be parsing if the current codepage isn't UTF8 - if (_currentCodePage != CP_UTF8) - { - _currentState = _State::Error; - } - auto hr = S_OK; - try - { - auto loop = true; - unsigned int wideCharCount = 0; - _convertedWideChars.reset(nullptr); - while (loop) - { - switch (_currentState) - { - case _State::Ready: - wideCharCount = _ParseFullRange(pBytes, cchBuffer); - break; - case _State::BeginPartialParse: - wideCharCount = _InvolvedParse(pBytes, cchBuffer); - break; - case _State::Error: - hr = E_FAIL; - _Reset(); - wideCharCount = 0; - loop = false; - break; - case _State::Finished: - _currentState = _State::Ready; - cchConsumed = cchBuffer; - loop = false; - break; - case _State::AwaitingMoreBytes: - _currentState = _State::BeginPartialParse; - cchConsumed = cchBuffer; - loop = false; - break; - default: - _currentState = _State::Error; - break; - } - } - converted.swap(_convertedWideChars); - cchConverted = wideCharCount; - } - catch (...) - { - _Reset(); - hr = wil::ResultFromCaughtException(); - } - return hr; -} - -// Routine Description: -// - Determines if ch is a UTF8 lead byte. See _Utf8SequenceSize() for a -// description of how a lead byte is specified. -// Arguments: -// - ch - The byte to test. -// Return Value: -// - True if ch is a lead byte, false otherwise. -bool Utf8ToWideCharParser::_IsLeadByte(_In_ byte ch) -{ - auto sequenceSize = _Utf8SequenceSize(ch); - return !_IsContinuationByte(ch) && - !_IsAsciiByte(ch) && - sequenceSize > 1 && - sequenceSize <= _UTF8_BYTE_SEQUENCE_MAX; -} - -// Routine Description: -// - Determines if ch is a UTF8 continuation byte. A continuation byte -// takes the form 10xx xxxx, so we need to check that the two most -// significant bits are a 1 followed by a 0. -// Arguments: -// - ch - The byte to test -// Return Value: -// - True if ch is a continuation byte, false otherwise. -bool Utf8ToWideCharParser::_IsContinuationByte(_In_ byte ch) -{ - return (ch & ContinuationByteMask) == ContinuationBytePrefix; -} - -// Routine Description: -// - Determines if ch is an ASCII compatible UTF8 byte. A byte is -// ASCII compatible if the most significant bit is a 0. -// Arguments: -// - ch - The byte to test. -// Return Value: -// - True if ch is an ASCII compatible byte, false otherwise. -bool Utf8ToWideCharParser::_IsAsciiByte(_In_ byte ch) -{ - return !IsBitSet(ch, NonAsciiBytePrefix); -} - -// Routine Description: -// - Determines if the sequence starting at pLeadByte is a valid UTF8 -// multi-byte sequence. Note that a single ASCII byte does not count -// as a valid MULTI-byte sequence. -// Arguments: -// - pLeadByte - The start of a possible sequence. -// - cb - The amount of remaining chars in the array that -// pLeadByte points to. -// Return Value: -// - true if the sequence starting at pLeadByte is a multi-byte -// sequence and uses all of the remaining chars, false otherwise. -bool Utf8ToWideCharParser::_IsValidMultiByteSequence(_In_reads_(cb) const byte* const pLeadByte, const unsigned int cb) -{ - if (!_IsLeadByte(*pLeadByte)) - { - return false; - } - const auto sequenceSize = _Utf8SequenceSize(*pLeadByte); - if (sequenceSize > cb) - { - return false; - } - // i starts at 1 so that we skip the lead byte - for (unsigned int i = 1; i < sequenceSize; ++i) - { - const auto ch = *(pLeadByte + i); - if (!_IsContinuationByte(ch)) - { - return false; - } - } - return true; -} - -// Routine Description: -// - Checks if the sequence starting at pLeadByte is a portion of a -// single valid multi-byte sequence. A new sequence must not be -// started within the range provided in order for it to be considered -// a valid partial sequence. -// Arguments: -// - pLeadByte - The start of the possible partial sequence. -// - cb - The amount of remaining chars in the array that -// pLeadByte points to. -// Return Value: -// - true if the sequence is a single partial multi-byte sequence, -// false otherwise. -bool Utf8ToWideCharParser::_IsPartialMultiByteSequence(_In_reads_(cb) const byte* const pLeadByte, const unsigned int cb) -{ - if (!_IsLeadByte(*pLeadByte)) - { - return false; - } - const auto sequenceSize = _Utf8SequenceSize(*pLeadByte); - if (sequenceSize <= cb) - { - return false; - } - // i starts at 1 so that we skip the lead byte - for (unsigned int i = 1; i < cb; ++i) - { - const auto ch = *(pLeadByte + i); - if (!_IsContinuationByte(ch)) - { - return false; - } - } - return true; -} - -// Routine Description: -// - Determines the number of bytes in the UTF8 multi-byte sequence. -// Does not perform any verification that ch is a valid lead byte. A -// lead byte indicates how many bytes are in a sequence by repeating a -// 1 for each byte in the sequence, starting with the most significant -// bit, then a 0 directly after. Ex: -// - 110x xxxx = a two byte sequence -// - 1110 xxxx = a three byte sequence -// -// Note that a byte that has a pattern 10xx xxxx is a continuation -// byte and will be reported as a sequence of one by this function. -// -// A sequence is currently a maximum of four bytes but this function -// will just count the number of consecutive 1 bits (starting with the -// most significant bit) so if the byte is malformed (ex. 1111 110x) a -// number larger than the maximum utf8 byte sequence may be -// returned. It is the responsibility of the calling function to check -// this (and the continuation byte scenario) because we don't do any -// verification here. -// Arguments: -// - ch - the lead byte of a UTF8 multi-byte sequence. -// Return Value: -// - The number of bytes (including the lead byte) that ch indicates -// are in the sequence. -unsigned int Utf8ToWideCharParser::_Utf8SequenceSize(_In_ byte ch) -{ - unsigned int msbOnes = 0; - while (IsBitSet(ch, MostSignificantBitMask)) - { - ++msbOnes; - ch <<= 1; - } - return msbOnes; -} - -// Routine Description: -// - Attempts to parse pInputChars by themselves in wide chars, -// without using any saved partial byte sequences. On success, -// _convertedWideChars will contain the converted wide char sequence -// and _currentState will be set to _State::Finished. On failure, -// _currentState will be set to either _State::Error or -// _State::BeginPartialParse. -// Arguments: -// - pInputChars - The byte sequence to convert to wide chars. -// - cb - The amount of bytes in pInputChars. -// Return Value: -// - The amount of wide chars that are stored in _convertedWideChars, -// or 0 if pInputChars cannot be successfully converted. -unsigned int Utf8ToWideCharParser::_ParseFullRange(_In_reads_(cb) const byte* const pInputChars, const unsigned int cb) -{ - auto bufferSize = MultiByteToWideChar(_currentCodePage, - MB_ERR_INVALID_CHARS, - reinterpret_cast(pInputChars), - cb, - nullptr, - 0); - if (bufferSize == 0) - { - auto err = GetLastError(); - LOG_WIN32(err); - if (err == ERROR_NO_UNICODE_TRANSLATION) - { - _currentState = _State::BeginPartialParse; - } - else - { - _currentState = _State::Error; - } - } - else - { - _convertedWideChars = std::make_unique(bufferSize); - bufferSize = MultiByteToWideChar(_currentCodePage, - 0, - reinterpret_cast(pInputChars), - cb, - _convertedWideChars.get(), - bufferSize); - if (bufferSize == 0) - { - LOG_LAST_ERROR(); - _currentState = _State::Error; - } - else - { - _currentState = _State::Finished; - } - } - return bufferSize; -} - -// Routine Description: -// - Attempts to parse pInputChars in a more complex manner, taking -// into account any previously saved partial byte sequences while -// removing any invalid byte sequences. Will also save a partial byte -// sequence from the end of the sequence if necessary. If the sequence -// can be successfully parsed, _currentState will be set to -// _State::Finished. If more bytes are necessary to form a wide char, -// then _currentState will be set to -// _State::AwaitingMoreBytes. Otherwise, _currentState will be set to -// _State::Error. -// Arguments: -// - pInputChars - The byte sequence to convert to wide chars. -// - cb - The amount of bytes in pInputChars. -// Return Value: -// - The amount of wide chars that are stored in _convertedWideChars, -// or 0 if pInputChars cannot be successfully converted or if the -// parser requires additional bytes before returning a valid wide -// char. -unsigned int Utf8ToWideCharParser::_InvolvedParse(_In_reads_(cb) const byte* const pInputChars, const unsigned int cb) -{ - // Do safe math to add up the count and error if it won't fit. - unsigned int count; - const auto hr = UIntAdd(cb, _bytesStored, &count); - if (FAILED(hr)) - { - LOG_HR(hr); - _currentState = _State::Error; - return 0; - } - - // Allocate space and copy. - auto combinedInputBytes = std::make_unique(count); - std::copy(_utf8CodePointPieces, _utf8CodePointPieces + _bytesStored, combinedInputBytes.get()); - std::copy(pInputChars, pInputChars + cb, combinedInputBytes.get() + _bytesStored); - _bytesStored = 0; - auto validSequence = _RemoveInvalidSequences(combinedInputBytes.get(), count); - // the input may have only been a partial sequence so we need to - // check that there are actually any bytes that we can convert - // right now - if (validSequence.second == 0 && _bytesStored > 0) - { - _currentState = _State::AwaitingMoreBytes; - return 0; - } - - // By this point, all obviously invalid sequences have been removed. - // But non-minimal forms of sequences might still exist. - // MB2WC will fail non-minimal forms with MB_ERR_INVALID_CHARS at this point. - // So we call with flags = 0 such that non-minimal forms get the U+FFFD - // replacement character treatment. - // This issue and related concerns are fully captured in future work item GH#3378 - // for future cleanup and reconciliation. - // The original issue introducing this was GH#3320. - auto bufferSize = MultiByteToWideChar(_currentCodePage, - 0, - reinterpret_cast(validSequence.first.get()), - validSequence.second, - nullptr, - 0); - if (bufferSize == 0) - { - LOG_LAST_ERROR(); - _currentState = _State::Error; - } - else - { - _convertedWideChars = std::make_unique(bufferSize); - bufferSize = MultiByteToWideChar(_currentCodePage, - 0, - reinterpret_cast(validSequence.first.get()), - validSequence.second, - _convertedWideChars.get(), - bufferSize); - if (bufferSize == 0) - { - LOG_LAST_ERROR(); - _currentState = _State::Error; - } - else if (_bytesStored > 0) - { - _currentState = _State::AwaitingMoreBytes; - } - else - { - _currentState = _State::Finished; - } - } - return bufferSize; -} - -// Routine Description: -// - Reads pInputChars byte by byte, removing any invalid UTF8 -// multi-byte sequences. -// Arguments: -// - pInputChars - The byte sequence to fix. -// - cb - The amount of bytes in pInputChars. -// Return Value: -// - A std::pair containing the corrected byte sequence and the number -// of bytes in the sequence. -std::pair, unsigned int> Utf8ToWideCharParser::_RemoveInvalidSequences(_In_reads_(cb) const byte* const pInputChars, const unsigned int cb) -{ - auto validSequence = std::make_unique(cb); - unsigned int validSequenceLocation = 0; // index into validSequence - unsigned int currentByteInput = 0; // index into pInputChars - while (currentByteInput < cb) - { - if (_IsAsciiByte(pInputChars[currentByteInput])) - { - validSequence[validSequenceLocation] = pInputChars[currentByteInput]; - ++validSequenceLocation; - ++currentByteInput; - } - else if (_IsContinuationByte(pInputChars[currentByteInput])) - { - while (currentByteInput < cb && _IsContinuationByte(pInputChars[currentByteInput])) - { - ++currentByteInput; - } - } - else if (_IsLeadByte(pInputChars[currentByteInput])) - { - if (_IsValidMultiByteSequence(&pInputChars[currentByteInput], cb - currentByteInput)) - { - const auto sequenceSize = _Utf8SequenceSize(pInputChars[currentByteInput]); - // min is to guard against static analysis possible buffer overflow - const auto limit = std::min(sequenceSize, cb - currentByteInput); - for (unsigned int i = 0; i < limit; ++i) - { - validSequence[validSequenceLocation] = pInputChars[currentByteInput]; - ++validSequenceLocation; - ++currentByteInput; - } - } - else if (_IsPartialMultiByteSequence(&pInputChars[currentByteInput], cb - currentByteInput)) - { - _StorePartialSequence(&pInputChars[currentByteInput], cb - currentByteInput); - break; - } - else - { - ++currentByteInput; - while (currentByteInput < cb && _IsContinuationByte(pInputChars[currentByteInput])) - { - ++currentByteInput; - } - } - } - else - { - // invalid byte, skip it. - ++currentByteInput; - } - } - return std::make_pair, unsigned int>(std::move(validSequence), std::move(validSequenceLocation)); -} - -// Routine Description: -// - Stores a partial byte sequence for later use. Will overwrite any -// previously saved sequence. Will only store bytes up to the limit -// Utf8ToWideCharParser::_UTF8_BYTE_SEQUENCE_MAX. -// Arguments: -// - pLeadByte - The beginning of the sequence to save. -// - cb - The amount of bytes to save. -// Return Value: -// - -void Utf8ToWideCharParser::_StorePartialSequence(_In_reads_(cb) const byte* const pLeadByte, const unsigned int cb) -{ - const auto maxLength = std::min(cb, _UTF8_BYTE_SEQUENCE_MAX); - std::copy(pLeadByte, pLeadByte + maxLength, _utf8CodePointPieces); - _bytesStored = maxLength; -} - -// Routine Description: -// - Resets the state of the parser to that of a newly initialized -// instance. _currentCodePage is not affected. -// Arguments: -// - -// Return Value: -// - -void Utf8ToWideCharParser::_Reset() -{ - _currentState = _State::Ready; - _bytesStored = 0; - _convertedWideChars.reset(nullptr); -} diff --git a/src/host/utf8ToWideCharParser.hpp b/src/host/utf8ToWideCharParser.hpp deleted file mode 100644 index 500637437b8..00000000000 --- a/src/host/utf8ToWideCharParser.hpp +++ /dev/null @@ -1,64 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- utf8ToWideCharParser.hpp - -Abstract: -- This transforms a multi-byte character sequence into wide chars -- It will attempt to work around invalid byte sequences -- Partial byte sequences are supported - -Author(s): -- Austin Diviness (AustDi) 16-August-2016 ---*/ - -#pragma once - -class Utf8ToWideCharParser final -{ -public: - Utf8ToWideCharParser(const unsigned int codePage); - void SetCodePage(const unsigned int codePage); - [[nodiscard]] HRESULT Parse(_In_reads_(cchBuffer) const byte* const pBytes, - _In_ const unsigned int cchBuffer, - _Out_ unsigned int& cchConsumed, - _Inout_ std::unique_ptr& converted, - _Out_ unsigned int& cchConverted); - -private: - enum class _State - { - Ready, // ready for input, no partially parsed code points - Error, // error in parsing given bytes - BeginPartialParse, // not a clean byte sequence, needs involved parsing - AwaitingMoreBytes, // have a partial sequence saved, waiting for the rest of it - Finished // ready to return a wide char sequence - }; - - bool _IsLeadByte(_In_ byte ch); - bool _IsContinuationByte(_In_ byte ch); - bool _IsAsciiByte(_In_ byte ch); - bool _IsValidMultiByteSequence(_In_reads_(cb) const byte* const pLeadByte, const unsigned int cb); - bool _IsPartialMultiByteSequence(_In_reads_(cb) const byte* const pLeadByte, const unsigned int cb); - unsigned int _Utf8SequenceSize(_In_ byte ch); - unsigned int _ParseFullRange(_In_reads_(cb) const byte* const _InputChars, const unsigned int cb); - unsigned int _InvolvedParse(_In_reads_(cb) const byte* const pInputChars, const unsigned int cb); - std::pair, unsigned int> _RemoveInvalidSequences(_In_reads_(cb) const byte* const pInputChars, - const unsigned int cb); - void _StorePartialSequence(_In_reads_(cb) const byte* const pLeadByte, const unsigned int cb); - void _Reset(); - - static const unsigned int _UTF8_BYTE_SEQUENCE_MAX = 4; - - byte _utf8CodePointPieces[_UTF8_BYTE_SEQUENCE_MAX]; - unsigned int _bytesStored; // bytes stored in utf8CodePointPieces - unsigned int _currentCodePage; - std::unique_ptr _convertedWideChars; - _State _currentState; - -#ifdef UNIT_TESTING - friend class Utf8ToWideCharParserTests; -#endif -}; From be9fc200c7b22c544103ec3304ca31c3b984031e Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 30 Nov 2023 15:55:06 +0100 Subject: [PATCH 077/167] Fix dwControlKeyState always including ENHANCED_KEY (#16335) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since all VT parameters are treated to be at least 1 (and 1 if they're absent or 0), `modifierParam > 0` was always true. This meant that `ENHANCED_KEY` was always being set. It's unclear why `ENHANCED_KEY` was used there, but it's likely not needed in general. Closes #16266 ## Validation Steps Performed * Can't test this unless we fix the win32 input mode issue #16343 ❌ --- src/terminal/parser/InputStateMachineEngine.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/terminal/parser/InputStateMachineEngine.cpp b/src/terminal/parser/InputStateMachineEngine.cpp index 95fb5a3e509..7ea26ce9c84 100644 --- a/src/terminal/parser/InputStateMachineEngine.cpp +++ b/src/terminal/parser/InputStateMachineEngine.cpp @@ -794,7 +794,6 @@ DWORD InputStateMachineEngine::_GetModifier(const size_t modifierParam) noexcept // VT Modifiers are 1+(modifier flags) const auto vtParam = modifierParam - 1; DWORD modifierState = 0; - WI_SetFlagIf(modifierState, ENHANCED_KEY, modifierParam > 0); WI_SetFlagIf(modifierState, SHIFT_PRESSED, WI_IsFlagSet(vtParam, VT_SHIFT)); WI_SetFlagIf(modifierState, LEFT_ALT_PRESSED, WI_IsFlagSet(vtParam, VT_ALT)); WI_SetFlagIf(modifierState, LEFT_CTRL_PRESSED, WI_IsFlagSet(vtParam, VT_CTRL)); From 654b755161f2635a16e2878e54941aee8d552075 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 4 Dec 2023 21:14:26 +0100 Subject: [PATCH 078/167] Fix backspacing over control visualizers (#16400) During `!measureOnly` the old code would increment `distance` twice. Now it doesn't. :) Closes #16356 ## Validation Steps Performed See updated test instructions in `doc/COOKED_READ_DATA.md` --- doc/COOKED_READ_DATA.md | 6 ++++++ src/host/readDataCooked.cpp | 19 ++++++++++++++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/doc/COOKED_READ_DATA.md b/doc/COOKED_READ_DATA.md index 113a51bb749..f4b463f7aa6 100644 --- a/doc/COOKED_READ_DATA.md +++ b/doc/COOKED_READ_DATA.md @@ -15,6 +15,12 @@ All of the following ✅ marks must be fulfilled during manual testing: * Press tab: Autocomplete to "a😊b.txt" ✅ * Navigate the cursor right past the "a" * Press tab twice: Autocomplete to "a😟b.txt" ✅ +* Execute `printf(" "); gets(buffer);` in C (or equivalent) + * Press Tab, A, Ctrl+V, Tab, A ✅ + * The prompt is " A^V A" ✅ + * Cursor navigation works ✅ + * Backspacing/Deleting random parts of it works ✅ + * It never deletes the initial 4 spaces ✅ * Backspace deletes preceding glyphs ✅ * Ctrl+Backspace deletes preceding words ✅ * Escape clears input ✅ diff --git a/src/host/readDataCooked.cpp b/src/host/readDataCooked.cpp index c842bab312d..4869e2ad91a 100644 --- a/src/host/readDataCooked.cpp +++ b/src/host/readDataCooked.cpp @@ -938,22 +938,31 @@ ptrdiff_t COOKED_READ_DATA::_writeCharsImpl(const std::wstring_view& text, const const auto wch = *it; if (wch == UNICODE_TAB) { - const auto col = _getColumnAtRelativeCursorPosition(distance + cursorOffset); - const auto remaining = width - col; - distance += std::min(remaining, 8 - (col & 7)); buf[0] = L'\t'; len = 1; } else { // In the interactive mode we replace C0 control characters (0x00-0x1f) with ASCII representations like ^C (= 0x03). - distance += 2; buf[0] = L'^'; buf[1] = gsl::narrow_cast(wch + L'@'); len = 2; } - if (!measureOnly) + if (measureOnly) + { + if (wch == UNICODE_TAB) + { + const auto col = _getColumnAtRelativeCursorPosition(distance + cursorOffset); + const auto remaining = width - col; + distance += std::min(remaining, 8 - (col & 7)); + } + else + { + distance += 2; + } + } + else { distance += _writeCharsUnprocessed({ &buf[0], len }); } From abab8705fea32854a6b55153b5736f9fd9dacb66 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Wed, 15 Nov 2023 19:13:03 -0600 Subject: [PATCH 079/167] Add a magic incantation to tell the Store we support Server (#16306) I find it somewhat silly that (1) this isn't documented anywhere and (2) installing the "desktop experience" packages for Server doesn't automatically add support for the `Windows.Desktop` platform... Oh well. I'm going to roll this one out via Preview first, because if the store blows up on it I would rather it not be during Stable roll-out. (cherry picked from commit 86fb9b44787accd09c5943a506e27eb4c8e573c0) Service-Card-Id: 91098597 Service-Version: 1.19 --- src/cascadia/CascadiaPackage/Package-Dev.appxmanifest | 1 + src/cascadia/CascadiaPackage/Package-Pre.appxmanifest | 1 + src/cascadia/CascadiaPackage/Package.appxmanifest | 1 + 3 files changed, 3 insertions(+) diff --git a/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest b/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest index d8ceefaeef6..53fc49c7833 100644 --- a/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest @@ -39,6 +39,7 @@ + diff --git a/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest b/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest index 3a7bf26e0f7..c7956c34576 100644 --- a/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest @@ -40,6 +40,7 @@ + diff --git a/src/cascadia/CascadiaPackage/Package.appxmanifest b/src/cascadia/CascadiaPackage/Package.appxmanifest index f04863da27c..98c10c860a0 100644 --- a/src/cascadia/CascadiaPackage/Package.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package.appxmanifest @@ -40,6 +40,7 @@ + From 0d353d8be5e2510d8edfec1238c9f4c6f25b3594 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 16 Nov 2023 22:27:33 +0100 Subject: [PATCH 080/167] Fix nearby fonts for DxEngine again (#16323) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The nearby font loading has to be outside of the try/catch of the `_FindFontFace` call, because it'll throw for broken font files. But in my previous PR I had overlooked that the font variant loop modifies the only copy of the face name that we got and was in the same try/catch. That's bad, because once we get to the nearby search code, the face name will be invalid. This commit fixes the issue by wrapping each individual `_FindFontFace` call in a try/catch block. Closes #16322 ## Validation Steps Performed * Remove every single copy of Windows Terminal from your system * Manually clean up Cascadia .ttf files because they aren't gone * Destroy your registry by manually removing appx references (fun!) * Put the 4 Cascadia .ttf files into the Dev app AppX directory * Launch * No warning ✅ (cherry picked from commit b780b445284597c6fe9d423126e2afd90c329265) Service-Card-Id: 91114951 Service-Version: 1.19 --- src/renderer/dx/DxFontInfo.cpp | 71 ++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/src/renderer/dx/DxFontInfo.cpp b/src/renderer/dx/DxFontInfo.cpp index b1d4844a601..b0f79cde39a 100644 --- a/src/renderer/dx/DxFontInfo.cpp +++ b/src/renderer/dx/DxFontInfo.cpp @@ -126,44 +126,52 @@ void DxFontInfo::SetFromEngine(const std::wstring_view familyName, try { face = _FindFontFace(localeName); + } + CATCH_LOG(); - if (!face) + if constexpr (Feature_NearbyFontLoading::IsEnabled()) + { + try { - // If we missed, try looking a little more by trimming the last word off the requested family name a few times. - // Quite often, folks are specifying weights or something in the familyName and it causes failed resolution and - // an unexpected error dialog. We theoretically could detect the weight words and convert them, but this - // is the quick fix for the majority scenario. - // The long/full fix is backlogged to GH#9744 - // Also this doesn't count as a fallback because we don't want to annoy folks with the warning dialog over - // this resolution. - while (!face && !_familyName.empty()) + if (!face) { - const auto lastSpace = _familyName.find_last_of(UNICODE_SPACE); - - // value is unsigned and npos will be greater than size. - // if we didn't find anything to trim, leave. - if (lastSpace >= _familyName.size()) - { - break; - } - - // trim string down to just before the found space - // (space found at 6... trim from 0 for 6 length will give us 0-5 as the new string) - _familyName = _familyName.substr(0, lastSpace); - - // Try to find it with the shortened family name + _fontCollection = FontCache::GetCached(); face = _FindFontFace(localeName); } } + CATCH_LOG(); } - CATCH_LOG(); - if constexpr (Feature_NearbyFontLoading::IsEnabled()) + if (!face) { - if (!face) + // If we missed, try looking a little more by trimming the last word off the requested family name a few times. + // Quite often, folks are specifying weights or something in the familyName and it causes failed resolution and + // an unexpected error dialog. We theoretically could detect the weight words and convert them, but this + // is the quick fix for the majority scenario. + // The long/full fix is backlogged to GH#9744 + // Also this doesn't count as a fallback because we don't want to annoy folks with the warning dialog over + // this resolution. + while (!face && !_familyName.empty()) { - _fontCollection = FontCache::GetCached(); - face = _FindFontFace(localeName); + const auto lastSpace = _familyName.find_last_of(UNICODE_SPACE); + + // value is unsigned and npos will be greater than size. + // if we didn't find anything to trim, leave. + if (lastSpace >= _familyName.size()) + { + break; + } + + // trim string down to just before the found space + // (space found at 6... trim from 0 for 6 length will give us 0-5 as the new string) + _familyName = _familyName.substr(0, lastSpace); + + try + { + // Try to find it with the shortened family name + face = _FindFontFace(localeName); + } + CATCH_LOG(); } } @@ -176,7 +184,12 @@ void DxFontInfo::SetFromEngine(const std::wstring_view familyName, { _familyName = fallbackFace; - face = _FindFontFace(localeName); + try + { + face = _FindFontFace(localeName); + } + CATCH_LOG(); + if (face) { _didFallback = true; From 5ed7dc2f63a4322f8ef8631f48f378985b049ad1 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 21 Nov 2023 21:50:46 +0100 Subject: [PATCH 081/167] ConPTY: Fix a shutdown deadlock with WSL (#16340) Under normal circumstances this bug should be rare as far as I can observe it on my system. However, it does occur randomly. In short, WSL doesn't pass us anonymous pipes, but rather WSA sockets and those signal their graceful shutdown first before being closed later by returning a `lpNumberOfBytesRead` of 0 in the meantime. Additionally, `VtIo` synchronously pumps the input pipe to get the initial cursor position, but fails to check `_exitRequested`. And so even with the pipe handling fixed, `VtIo` will also deadlock, because it will never realize that `VtInputThread` is done reading. ## Validation Steps Performed * Build commit 376737e with this change and replace conhost with it Coincidentally it contains a bug (of as of yet unknown origin) due to which the initial cursor position loop in `VtIo` never completes. Thanks to this, we can easily provoke this issue. * Launch WSL in conhost and run an .exe inside it * Close the conhost window * Task manager shows that all conhost instances exit immediately (cherry picked from commit adb04729bcce7151f6380eded79e9408df9d1e3b) Service-Card-Id: 91152102 Service-Version: 1.19 --- src/host/VtInputThread.cpp | 89 +++++++++++++++----------------------- src/host/VtInputThread.hpp | 5 +-- src/host/VtIo.cpp | 3 +- 3 files changed, 36 insertions(+), 61 deletions(-) diff --git a/src/host/VtInputThread.cpp b/src/host/VtInputThread.cpp index 495a6e51d8b..4a4b773cb02 100644 --- a/src/host/VtInputThread.cpp +++ b/src/host/VtInputThread.cpp @@ -29,7 +29,6 @@ VtInputThread::VtInputThread(_In_ wil::unique_hfile hPipe, _hThread{}, _u8State{}, _dwThreadId{ 0 }, - _exitRequested{ false }, _pfnSetLookingForDSR{} { THROW_HR_IF(E_HANDLE, _hFile.get() == INVALID_HANDLE_VALUE); @@ -50,40 +49,6 @@ VtInputThread::VtInputThread(_In_ wil::unique_hfile hPipe, _pfnSetLookingForDSR = std::bind(&InputStateMachineEngine::SetLookingForDSR, engineRef, std::placeholders::_1); } -// Method Description: -// - Processes a string of input characters. The characters should be UTF-8 -// encoded, and will get converted to wstring to be processed by the -// input state machine. -// Arguments: -// - u8Str - the UTF-8 string received. -// Return Value: -// - S_OK on success, otherwise an appropriate failure. -[[nodiscard]] HRESULT VtInputThread::_HandleRunInput(const std::string_view u8Str) -{ - // Make sure to call the GLOBAL Lock/Unlock, not the gci's lock/unlock. - // Only the global unlock attempts to dispatch ctrl events. If you use the - // gci's unlock, when you press C-c, it won't be dispatched until the - // next console API call. For something like `powershell sleep 60`, - // that won't happen for 60s - LockConsole(); - auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); - - try - { - std::wstring wstr{}; - auto hr = til::u8u16(u8Str, wstr, _u8State); - // If we hit a parsing error, eat it. It's bad utf-8, we can't do anything with it. - if (FAILED(hr)) - { - return S_FALSE; - } - _pInputStateMachine->ProcessString(wstr); - } - CATCH_RETURN(); - - return S_OK; -} - // Function Description: // - Static function used for initializing an instance's ThreadProc. // Arguments: @@ -100,35 +65,50 @@ DWORD WINAPI VtInputThread::StaticVtInputThreadProc(_In_ LPVOID lpParameter) // Method Description: // - Do a single ReadFile from our pipe, and try and handle it. If handling // failed, throw or log, depending on what the caller wants. -// Arguments: -// - throwOnFail: If true, throw an exception if there was an error processing -// the input received. Otherwise, log the error. // Return Value: -// - -void VtInputThread::DoReadInput(const bool throwOnFail) +// - true if you should continue reading +bool VtInputThread::DoReadInput() { char buffer[256]; DWORD dwRead = 0; - auto fSuccess = !!ReadFile(_hFile.get(), buffer, ARRAYSIZE(buffer), &dwRead, nullptr); - - if (!fSuccess) + const auto ok = ReadFile(_hFile.get(), buffer, ARRAYSIZE(buffer), &dwRead, nullptr); + + // The ReadFile() documentations calls out that: + // > If the lpNumberOfBytesRead parameter is zero when ReadFile returns TRUE on a pipe, the other + // > end of the pipe called the WriteFile function with nNumberOfBytesToWrite set to zero. + // But I was unable to replicate any such behavior. I'm not sure it's true anymore. + // + // However, what the documentations fails to mention is that winsock2 (WSA) handles of the \Device\Afd type are + // transparently compatible with ReadFile() and the WSARecv() documentations contains this important information: + // > For byte streams, zero bytes having been read [..] indicates graceful closure and that no more bytes will ever be read. + // In other words, for pipe HANDLE of unknown type you should consider `lpNumberOfBytesRead == 0` as an exit indicator. + // + // Here, `dwRead == 0` fixes a deadlock when exiting conhost while being in use by WSL whose hypervisor pipes are WSA. + if (!ok || dwRead == 0) { - _exitRequested = true; - return; + return false; } - auto hr = _HandleRunInput({ buffer, gsl::narrow_cast(dwRead) }); - if (FAILED(hr)) + try { - if (throwOnFail) - { - _exitRequested = true; - } - else + // Make sure to call the GLOBAL Lock/Unlock, not the gci's lock/unlock. + // Only the global unlock attempts to dispatch ctrl events. If you use the + // gci's unlock, when you press C-c, it won't be dispatched until the + // next console API call. For something like `powershell sleep 60`, + // that won't happen for 60s + LockConsole(); + const auto unlock = wil::scope_exit([&] { UnlockConsole(); }); + + std::wstring wstr; + // If we hit a parsing error, eat it. It's bad utf-8, we can't do anything with it. + if (SUCCEEDED_LOG(til::u8u16({ buffer, gsl::narrow_cast(dwRead) }, wstr, _u8State))) { - LOG_IF_FAILED(hr); + _pInputStateMachine->ProcessString(wstr); } } + CATCH_LOG(); + + return true; } void VtInputThread::SetLookingForDSR(const bool looking) noexcept @@ -145,9 +125,8 @@ void VtInputThread::SetLookingForDSR(const bool looking) noexcept // InputStateMachineEngine. void VtInputThread::_InputThread() { - while (!_exitRequested) + while (DoReadInput()) { - DoReadInput(true); } ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo()->CloseInput(); } diff --git a/src/host/VtInputThread.hpp b/src/host/VtInputThread.hpp index 7c075b70025..7652a53887f 100644 --- a/src/host/VtInputThread.hpp +++ b/src/host/VtInputThread.hpp @@ -25,19 +25,16 @@ namespace Microsoft::Console [[nodiscard]] HRESULT Start(); static DWORD WINAPI StaticVtInputThreadProc(_In_ LPVOID lpParameter); - void DoReadInput(const bool throwOnFail); + bool DoReadInput(); void SetLookingForDSR(const bool looking) noexcept; private: - [[nodiscard]] HRESULT _HandleRunInput(const std::string_view u8Str); void _InputThread(); wil::unique_hfile _hFile; wil::unique_handle _hThread; DWORD _dwThreadId; - bool _exitRequested; - std::function _pfnSetLookingForDSR; std::unique_ptr _pInputStateMachine; diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index dd8b635efd9..c286a5e8aca 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -282,9 +282,8 @@ bool VtIo::IsUsingVt() const if (_lookingForCursorPosition && _pVtRenderEngine && _pVtInputThread) { LOG_IF_FAILED(_pVtRenderEngine->RequestCursor()); - while (_lookingForCursorPosition) + while (_lookingForCursorPosition && _pVtInputThread->DoReadInput()) { - _pVtInputThread->DoReadInput(false); } } From 3538a9f72b77569b2c60e551fbdb23677b868c1e Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 21 Nov 2023 21:57:56 +0100 Subject: [PATCH 082/167] Fix input buffering for A APIs (#16313) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes an issue where character-wise reading of an input like "abc" would return "a" to the caller, store "b" as a partial translation (= wrong) and return "c" for the caller to store it for the next call. Closes #16223 Closes #16299 ## Validation Steps Performed * `ReadFile` with a buffer size of 1 returns inputs character by character without dropping any inputs ✅ --------- Co-authored-by: Dustin L. Howett (cherry picked from commit 63b3820a18be096a4b1950e335c9605267440734) Service-Card-Id: 91122022 Service-Version: 1.19 --- src/host/inputBuffer.cpp | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/host/inputBuffer.cpp b/src/host/inputBuffer.cpp index 3a4e556e440..5150a21427d 100644 --- a/src/host/inputBuffer.cpp +++ b/src/host/inputBuffer.cpp @@ -4,13 +4,13 @@ #include "precomp.h" #include "inputBuffer.hpp" -#include "stream.h" -#include "../types/inc/GlyphWidth.hpp" - #include +#include #include "misc.h" +#include "stream.h" #include "../interactivity/inc/ServiceLocator.hpp" +#include "../types/inc/GlyphWidth.hpp" #define INPUT_BUFFER_DEFAULT_INPUT_MODE (ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT | ENABLE_MOUSE_INPUT) @@ -87,10 +87,10 @@ void InputBuffer::Consume(bool isUnicode, std::wstring_view& source, std::span(s.size()), &buffer[0], sizeof(buffer), nullptr, nullptr); THROW_LAST_ERROR_IF(length <= 0); std::string_view slice{ &buffer[0], gsl::narrow_cast(length) }; @@ -98,10 +98,24 @@ void InputBuffer::Consume(bool isUnicode, std::wstring_view& source, std::span Date: Tue, 28 Nov 2023 05:31:06 +0800 Subject: [PATCH 083/167] Fix Control+Space not sent to program running in terminal (#16298) Converts null byte to specific input event, so that it's properly delivered to the program running in the terminal. Closes #15939 (cherry picked from commit 8747a39a07dd601141a2ce2ee6eb076a8a5599b6) Service-Card-Id: 91201444 Service-Version: 1.19 --- src/host/inputBuffer.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/host/inputBuffer.cpp b/src/host/inputBuffer.cpp index 5150a21427d..abdfed1961f 100644 --- a/src/host/inputBuffer.cpp +++ b/src/host/inputBuffer.cpp @@ -830,6 +830,17 @@ void InputBuffer::_HandleTerminalInputCallback(const TerminalInput::StringType& for (const auto& wch : text) { + if (wch == UNICODE_NULL) + { + // Convert null byte back to input event with proper control state + const auto zeroKey = OneCoreSafeVkKeyScanW(0); + uint32_t ctrlState = 0; + WI_SetFlagIf(ctrlState, SHIFT_PRESSED, WI_IsFlagSet(zeroKey, 0x100)); + WI_SetFlagIf(ctrlState, LEFT_CTRL_PRESSED, WI_IsFlagSet(zeroKey, 0x200)); + WI_SetFlagIf(ctrlState, LEFT_ALT_PRESSED, WI_IsFlagSet(zeroKey, 0x400)); + _storage.push_back(SynthesizeKeyEvent(true, 1, LOBYTE(zeroKey), 0, wch, ctrlState)); + continue; + } _storage.push_back(SynthesizeKeyEvent(true, 1, 0, 0, wch, 0)); } From d4292d16cc9256283898a93887ceb5246f36132f Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 27 Nov 2023 22:34:13 +0100 Subject: [PATCH 084/167] Fix scrolling with SetConsoleWindowInfo (#16334) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 81b7e54 caused a regression in `SetConsoleWindowInfo` and any other function that used the `WriteToScreen` helper. This is because it assumes that it can place the viewport anywhere randomly and it was written at a time where `TriggerScroll` didn't exist yet (there was no need for that (also not today, but that's being worked on)). Caching the viewport meant that `WriteToScreen`'s call to `TriggerRedraw` would pick up the viewport from the last rendered frame, which would cause the intersection of both to be potentially empty and nothing to be drawn on the screen. This commit reverts 81b7e54 as I found that it has no or negligible impact on performance at this point, likely due to the overall vastly better performance of conhost nowadays. Closes #15932 ## Validation Steps Performed * Scroll the viewport by entire pages worth of content using `SetConsoleWindowInfo` - see #15932 * The screen and scrollbars update immediately ✅ (cherry picked from commit 7a1b6f9d2a9d564375fdc9792c506937b6474e67) Service-Card-Id: 91152167 Service-Version: 1.19 --- src/renderer/base/renderer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/base/renderer.cpp b/src/renderer/base/renderer.cpp index 917c5ba05f9..2bf98404ae2 100644 --- a/src/renderer/base/renderer.cpp +++ b/src/renderer/base/renderer.cpp @@ -217,7 +217,7 @@ void Renderer::TriggerSystemRedraw(const til::rect* const prcDirtyClient) // - void Renderer::TriggerRedraw(const Viewport& region) { - auto view = _viewport; + auto view = _pData->GetViewport(); auto srUpdateRegion = region.ToExclusive(); // If the dirty region has double width lines, we need to double the size of From dd83ba62cfb1591b7e6f509b1ca2cfb0ff933c15 Mon Sep 17 00:00:00 2001 From: Adam Reynolds Date: Thu, 30 Nov 2023 01:58:41 -0800 Subject: [PATCH 085/167] Fixed crash when cloud shell provider timed out or was closed waiting for login (#16364) ## Summary of the Pull Request Cloud shell connection calls out to Azure to do a device code login. When the polling interval is exceeded or the tab is closed, the method doing the connection polling returns `nullptr`, and `AzureConnection` immediately tries to `GetNamedString` from it, causing a crash. This doesn't repro on Terminal Stable or Preview, suggesting it's pretty recent related to the update of this azureconnection. This is just a proposed fix, not sure if you want to do more extensive changes to the affected class or not, so marking this as a draft. ## References and Relevant Issues * N/A - encountered this while using the terminal myself ## PR Checklist/Validation Tested out a local dev build: - [x] Terminal doesn't crash when cloudshell polling interval exceeded - [x] Terminal doesn't crash when cloudshell tab closed while polling for Azure login (cherry picked from commit 0c4751ba30333e9a47d0992fe044242867ab86ad) Service-Card-Id: 91232784 Service-Version: 1.19 --- src/cascadia/TerminalConnection/AzureConnection.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/cascadia/TerminalConnection/AzureConnection.cpp b/src/cascadia/TerminalConnection/AzureConnection.cpp index 708518509b5..11c49838f95 100644 --- a/src/cascadia/TerminalConnection/AzureConnection.cpp +++ b/src/cascadia/TerminalConnection/AzureConnection.cpp @@ -604,6 +604,15 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation // Wait for user authentication and obtain the access/refresh tokens auto authenticatedResponse = _WaitForUser(devCode, pollInterval, expiresIn); + + // If user closed tab, `_WaitForUser` returns nullptr + // This also occurs if the connection times out, when polling time exceeds the expiry time + if (!authenticatedResponse) + { + _transitionToState(ConnectionState::Failed); + return; + } + _setAccessToken(authenticatedResponse.GetNamedString(L"access_token")); _refreshToken = authenticatedResponse.GetNamedString(L"refresh_token"); From 17d24bf0559731f281f17d43e5b19d7e1cb8a845 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 30 Nov 2023 15:55:06 +0100 Subject: [PATCH 086/167] Fix dwControlKeyState always including ENHANCED_KEY (#16335) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since all VT parameters are treated to be at least 1 (and 1 if they're absent or 0), `modifierParam > 0` was always true. This meant that `ENHANCED_KEY` was always being set. It's unclear why `ENHANCED_KEY` was used there, but it's likely not needed in general. Closes #16266 ## Validation Steps Performed * Can't test this unless we fix the win32 input mode issue #16343 ❌ (cherry picked from commit be9fc200c7b22c544103ec3304ca31c3b984031e) Service-Card-Id: 91159301 Service-Version: 1.19 --- src/terminal/parser/InputStateMachineEngine.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/terminal/parser/InputStateMachineEngine.cpp b/src/terminal/parser/InputStateMachineEngine.cpp index 95fb5a3e509..7ea26ce9c84 100644 --- a/src/terminal/parser/InputStateMachineEngine.cpp +++ b/src/terminal/parser/InputStateMachineEngine.cpp @@ -794,7 +794,6 @@ DWORD InputStateMachineEngine::_GetModifier(const size_t modifierParam) noexcept // VT Modifiers are 1+(modifier flags) const auto vtParam = modifierParam - 1; DWORD modifierState = 0; - WI_SetFlagIf(modifierState, ENHANCED_KEY, modifierParam > 0); WI_SetFlagIf(modifierState, SHIFT_PRESSED, WI_IsFlagSet(vtParam, VT_SHIFT)); WI_SetFlagIf(modifierState, LEFT_ALT_PRESSED, WI_IsFlagSet(vtParam, VT_ALT)); WI_SetFlagIf(modifierState, LEFT_CTRL_PRESSED, WI_IsFlagSet(vtParam, VT_CTRL)); From e33de75402bda8e3ace589a938ec762238e68e3d Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 4 Dec 2023 21:14:26 +0100 Subject: [PATCH 087/167] Fix backspacing over control visualizers (#16400) During `!measureOnly` the old code would increment `distance` twice. Now it doesn't. :) Closes #16356 ## Validation Steps Performed See updated test instructions in `doc/COOKED_READ_DATA.md` (cherry picked from commit 654b755161f2635a16e2878e54941aee8d552075) Service-Card-Id: 91232745 Service-Version: 1.19 --- doc/COOKED_READ_DATA.md | 6 ++++++ src/host/readDataCooked.cpp | 19 ++++++++++++++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/doc/COOKED_READ_DATA.md b/doc/COOKED_READ_DATA.md index 113a51bb749..f4b463f7aa6 100644 --- a/doc/COOKED_READ_DATA.md +++ b/doc/COOKED_READ_DATA.md @@ -15,6 +15,12 @@ All of the following ✅ marks must be fulfilled during manual testing: * Press tab: Autocomplete to "a😊b.txt" ✅ * Navigate the cursor right past the "a" * Press tab twice: Autocomplete to "a😟b.txt" ✅ +* Execute `printf(" "); gets(buffer);` in C (or equivalent) + * Press Tab, A, Ctrl+V, Tab, A ✅ + * The prompt is " A^V A" ✅ + * Cursor navigation works ✅ + * Backspacing/Deleting random parts of it works ✅ + * It never deletes the initial 4 spaces ✅ * Backspace deletes preceding glyphs ✅ * Ctrl+Backspace deletes preceding words ✅ * Escape clears input ✅ diff --git a/src/host/readDataCooked.cpp b/src/host/readDataCooked.cpp index c842bab312d..4869e2ad91a 100644 --- a/src/host/readDataCooked.cpp +++ b/src/host/readDataCooked.cpp @@ -938,22 +938,31 @@ ptrdiff_t COOKED_READ_DATA::_writeCharsImpl(const std::wstring_view& text, const const auto wch = *it; if (wch == UNICODE_TAB) { - const auto col = _getColumnAtRelativeCursorPosition(distance + cursorOffset); - const auto remaining = width - col; - distance += std::min(remaining, 8 - (col & 7)); buf[0] = L'\t'; len = 1; } else { // In the interactive mode we replace C0 control characters (0x00-0x1f) with ASCII representations like ^C (= 0x03). - distance += 2; buf[0] = L'^'; buf[1] = gsl::narrow_cast(wch + L'@'); len = 2; } - if (!measureOnly) + if (measureOnly) + { + if (wch == UNICODE_TAB) + { + const auto col = _getColumnAtRelativeCursorPosition(distance + cursorOffset); + const auto remaining = width - col; + distance += std::min(remaining, 8 - (col & 7)); + } + else + { + distance += 2; + } + } + else { distance += _writeCharsUnprocessed({ &buf[0], len }); } From 204ebf3b198a1f1b6ae1f038cc371c5e1326fc5d Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 4 Dec 2023 21:29:34 +0100 Subject: [PATCH 088/167] Enable AtlasEngine by default (#16277) This enables AtlasEngine by default in the 1.19 release branch. A future change will remove the alternative DxEngine entirely. --- .../CascadiaSettingsSerialization.cpp | 19 ------------------- .../TerminalSettingsModel/MTSMSettings.h | 2 +- .../TerminalSettingsModel/TerminalSettings.h | 2 +- .../UnitTests_Control/ControlCoreTests.cpp | 18 ++++++++++++------ .../ControlInteractivityTests.cpp | 9 ++++++--- src/cascadia/inc/ControlProperties.h | 2 +- src/features.xml | 10 ---------- 7 files changed, 21 insertions(+), 41 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index 8964169f207..15e62efd4fa 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -949,25 +949,6 @@ void CascadiaSettings::_researchOnLoad() // Only do this if we're actually being sampled if (TraceLoggingProviderEnabled(g_hSettingsModelProvider, 0, MICROSOFT_KEYWORD_MEASURES)) { - // GH#13936: We're interested in how many users opt out of useAtlasEngine, - // indicating major issues that would require us to disable it by default again. - { - size_t enabled[2]{}; - for (const auto& profile : _activeProfiles) - { - enabled[profile.UseAtlasEngine()]++; - } - - TraceLoggingWrite( - g_hSettingsModelProvider, - "AtlasEngine_Usage", - TraceLoggingDescription("Event emitted upon settings load, containing the number of profiles opted-in/out of useAtlasEngine"), - TraceLoggingUIntPtr(enabled[0], "UseAtlasEngineDisabled", "Number of profiles for which AtlasEngine is disabled"), - TraceLoggingUIntPtr(enabled[1], "UseAtlasEngineEnabled", "Number of profiles for which AtlasEngine is enabled"), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), - TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); - } - // ----------------------------- RE: Themes ---------------------------- const auto numThemes = GlobalSettings().Themes().Size(); const auto themeInUse = GlobalSettings().CurrentTheme().Name(); diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index 15e91a1d7ae..d0dc6bfcca5 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -90,7 +90,7 @@ Author(s): X(hstring, TabTitle, "tabTitle") \ X(Model::BellStyle, BellStyle, "bellStyle", BellStyle::Audible) \ X(IEnvironmentVariableMap, EnvironmentVariables, "environment", nullptr) \ - X(bool, UseAtlasEngine, "useAtlasEngine", Feature_AtlasEngine::IsEnabled()) \ + X(bool, UseAtlasEngine, "useAtlasEngine", true) \ X(bool, RightClickContextMenu, "experimental.rightClickContextMenu", false) \ X(Windows::Foundation::Collections::IVector, BellSound, "bellSound", nullptr) \ X(bool, Elevate, "elevate", false) \ diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.h b/src/cascadia/TerminalSettingsModel/TerminalSettings.h index 60c9b5fb2c2..abc210b9d94 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.h @@ -148,7 +148,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::TerminalSettings, IEnvironmentVariableMap, EnvironmentVariables); INHERITABLE_SETTING(Model::TerminalSettings, Microsoft::Terminal::Control::ScrollbarState, ScrollState, Microsoft::Terminal::Control::ScrollbarState::Visible); - INHERITABLE_SETTING(Model::TerminalSettings, bool, UseAtlasEngine, false); + INHERITABLE_SETTING(Model::TerminalSettings, bool, UseAtlasEngine, true); INHERITABLE_SETTING(Model::TerminalSettings, Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale); diff --git a/src/cascadia/UnitTests_Control/ControlCoreTests.cpp b/src/cascadia/UnitTests_Control/ControlCoreTests.cpp index f34ca5a6bfd..c38c0221c5a 100644 --- a/src/cascadia/UnitTests_Control/ControlCoreTests.cpp +++ b/src/cascadia/UnitTests_Control/ControlCoreTests.cpp @@ -80,9 +80,12 @@ namespace ControlUnitTests void _standardInit(winrt::com_ptr core) { - // "Consolas" ends up with an actual size of 9x21 at 96DPI. So - // let's just arbitrarily start with a 270x420px (30x20 chars) window - core->Initialize(270, 420, 1.0); + // "Consolas" ends up with an actual size of 9x19 at 96DPI. So + // let's just arbitrarily start with a 270x380px (30x20 chars) window + core->Initialize(270, 380, 1.0); +#ifndef NDEBUG + core->_terminal->_suppressLockChecks = true; +#endif VERIFY_IS_TRUE(core->_initializedTerminal); VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height()); } @@ -113,9 +116,12 @@ namespace ControlUnitTests VERIFY_IS_NOT_NULL(core); VERIFY_IS_FALSE(core->_initializedTerminal); - // "Consolas" ends up with an actual size of 9x21 at 96DPI. So - // let's just arbitrarily start with a 270x420px (30x20 chars) window - core->Initialize(270, 420, 1.0); + // "Consolas" ends up with an actual size of 9x19 at 96DPI. So + // let's just arbitrarily start with a 270x380px (30x20 chars) window + core->Initialize(270, 380, 1.0); +#ifndef NDEBUG + core->_terminal->_suppressLockChecks = true; +#endif VERIFY_IS_TRUE(core->_initializedTerminal); VERIFY_ARE_EQUAL(30, core->_terminal->GetViewport().Width()); } diff --git a/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp b/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp index a48aa968944..192cc2ad9d8 100644 --- a/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp +++ b/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp @@ -88,9 +88,12 @@ namespace ControlUnitTests void _standardInit(winrt::com_ptr core, winrt::com_ptr interactivity) { - // "Consolas" ends up with an actual size of 9x21 at 96DPI. So - // let's just arbitrarily start with a 270x420px (30x20 chars) window - core->Initialize(270, 420, 1.0); + // "Consolas" ends up with an actual size of 9x19 at 96DPI. So + // let's just arbitrarily start with a 270x380px (30x20 chars) window + core->Initialize(270, 380, 1.0); +#ifndef NDEBUG + core->_terminal->_suppressLockChecks = true; +#endif VERIFY_IS_TRUE(core->_initializedTerminal); VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height()); interactivity->Initialize(); diff --git a/src/cascadia/inc/ControlProperties.h b/src/cascadia/inc/ControlProperties.h index c18e70e7cb2..2c8c8ad48bb 100644 --- a/src/cascadia/inc/ControlProperties.h +++ b/src/cascadia/inc/ControlProperties.h @@ -72,7 +72,7 @@ X(winrt::Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, winrt::Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale) \ X(bool, ForceFullRepaintRendering, false) \ X(bool, SoftwareRendering, false) \ - X(bool, UseAtlasEngine, false) \ + X(bool, UseAtlasEngine, true) \ X(bool, UseBackgroundImageForWindow, false) \ X(bool, ShowMarks, false) \ X(bool, RightClickContextMenu, false) diff --git a/src/features.xml b/src/features.xml index 4656ef0ffd5..be08ced6932 100644 --- a/src/features.xml +++ b/src/features.xml @@ -76,16 +76,6 @@ - - Feature_AtlasEngine - If enabled, AtlasEngine is used by default - AlwaysEnabled - - Release - WindowsInbox - - - Feature_AtlasEnginePresentFallback We don't feel super confident in our usage of the Present1 API, so this settings adds a fallback to Present on error From 91fd7d01018b2400f416d5ae5ef98fc85dfe8583 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 4 Dec 2023 23:58:57 +0100 Subject: [PATCH 089/167] Fix a coroutine AV crash (#16412) tl;dr: A coroutine lambda does not hold onto captured variables. This causes an AV crash when closing tabs. I randomly noticed this in a Debug build as the memory contents got replaced with 0xCD. In a Release build this bug is probably fairly subtle and not common. --- src/cascadia/TerminalApp/TerminalTab.cpp | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp index c363ec1d9f9..06121ab945d 100644 --- a/src/cascadia/TerminalApp/TerminalTab.cpp +++ b/src/cascadia/TerminalApp/TerminalTab.cpp @@ -933,9 +933,13 @@ namespace winrt::TerminalApp::implementation ControlEventTokens events{}; events.titleToken = control.TitleChanged([dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget { + // The lambda lives in the `std::function`-style container owned by `control`. That is, when the + // `control` gets destroyed the lambda struct also gets destroyed. In other words, we need to + // copy `weakThis` onto the stack, because that's the only thing that gets captured in coroutines. + // See: https://devblogs.microsoft.com/oldnewthing/20211103-00/?p=105870 + const auto weakThisCopy = weakThis; co_await wil::resume_foreground(dispatcher); - // Check if Tab's lifetime has expired - if (auto tab{ weakThis.get() }) + if (auto tab{ weakThisCopy.get() }) { // The title of the control changed, but not necessarily the title of the tab. // Set the tab's text to the active panes' text. @@ -944,8 +948,9 @@ namespace winrt::TerminalApp::implementation }); events.colorToken = control.TabColorChanged([dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget { + const auto weakThisCopy = weakThis; co_await wil::resume_foreground(dispatcher); - if (auto tab{ weakThis.get() }) + if (auto tab{ weakThisCopy.get() }) { // The control's tabColor changed, but it is not necessarily the // active control in this tab. We'll just recalculate the @@ -955,33 +960,36 @@ namespace winrt::TerminalApp::implementation }); events.taskbarToken = control.SetTaskbarProgress([dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget { + const auto weakThisCopy = weakThis; co_await wil::resume_foreground(dispatcher); - // Check if Tab's lifetime has expired - if (auto tab{ weakThis.get() }) + if (auto tab{ weakThisCopy.get() }) { tab->_UpdateProgressState(); } }); events.stateToken = control.ConnectionStateChanged([dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget { + const auto weakThisCopy = weakThis; co_await wil::resume_foreground(dispatcher); - if (auto tab{ weakThis.get() }) + if (auto tab{ weakThisCopy.get() }) { tab->_UpdateConnectionClosedState(); } }); events.readOnlyToken = control.ReadOnlyChanged([dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget { + const auto weakThisCopy = weakThis; co_await wil::resume_foreground(dispatcher); - if (auto tab{ weakThis.get() }) + if (auto tab{ weakThisCopy.get() }) { tab->_RecalculateAndApplyReadOnly(); } }); events.focusToken = control.FocusFollowMouseRequested([dispatcher, weakThis](auto sender, auto) -> winrt::fire_and_forget { + const auto weakThisCopy = weakThis; co_await wil::resume_foreground(dispatcher); - if (const auto tab{ weakThis.get() }) + if (const auto tab{ weakThisCopy.get() }) { if (tab->_focused()) { From 0da37a134a5363080f85a9adc45a79dc6e52ec90 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 5 Dec 2023 00:05:25 +0100 Subject: [PATCH 090/167] Avoid encoding VT via win32 input mode (#16407) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This changeset avoids re-encoding output from `AdaptDispatch` via the win32-input-mode mechanism when VT input is enabled. That is, an `AdaptDispatch` output like `\x1b[C` would otherwise result in dozens of characters of input. Related to #16343 ## Validation Steps Performed * Replace conhost with this * Launch a Win32 application inside WSL * ASCII keyboard inputs are represented as single `INPUT_RECORD`s ✅ --- src/host/inputBuffer.cpp | 56 ++++++++++++++++++++++++++++----------- src/host/inputBuffer.hpp | 2 ++ src/host/outputStream.cpp | 15 +---------- 3 files changed, 44 insertions(+), 29 deletions(-) diff --git a/src/host/inputBuffer.cpp b/src/host/inputBuffer.cpp index abdfed1961f..184670ffb1d 100644 --- a/src/host/inputBuffer.cpp +++ b/src/host/inputBuffer.cpp @@ -602,6 +602,27 @@ size_t InputBuffer::Write(const std::span& inEvents) } } +void InputBuffer::WriteString(const std::wstring_view& text) +try +{ + if (text.empty()) + { + return; + } + + const auto initiallyEmptyQueue = _storage.empty(); + + _writeString(text); + + if (initiallyEmptyQueue && !_storage.empty()) + { + ServiceLocator::LocateGlobals().hInputEvent.SetEvent(); + } + + WakeUpReadersWaitingForData(); +} +CATCH_LOG() + // This can be considered a "privileged" variant of Write() which allows FOCUS_EVENTs to generate focus VT sequences. // If we didn't do this, someone could write a FOCUS_EVENT_RECORD with WriteConsoleInput, exit without flushing the // input buffer and the next application will suddenly get a "\x1b[I" sequence in their input. See GH#13238. @@ -828,21 +849,7 @@ void InputBuffer::_HandleTerminalInputCallback(const TerminalInput::StringType& return; } - for (const auto& wch : text) - { - if (wch == UNICODE_NULL) - { - // Convert null byte back to input event with proper control state - const auto zeroKey = OneCoreSafeVkKeyScanW(0); - uint32_t ctrlState = 0; - WI_SetFlagIf(ctrlState, SHIFT_PRESSED, WI_IsFlagSet(zeroKey, 0x100)); - WI_SetFlagIf(ctrlState, LEFT_CTRL_PRESSED, WI_IsFlagSet(zeroKey, 0x200)); - WI_SetFlagIf(ctrlState, LEFT_ALT_PRESSED, WI_IsFlagSet(zeroKey, 0x400)); - _storage.push_back(SynthesizeKeyEvent(true, 1, LOBYTE(zeroKey), 0, wch, ctrlState)); - continue; - } - _storage.push_back(SynthesizeKeyEvent(true, 1, 0, 0, wch, 0)); - } + _writeString(text); if (!_vtInputShouldSuppress) { @@ -856,6 +863,25 @@ void InputBuffer::_HandleTerminalInputCallback(const TerminalInput::StringType& } } +void InputBuffer::_writeString(const std::wstring_view& text) +{ + for (const auto& wch : text) + { + if (wch == UNICODE_NULL) + { + // Convert null byte back to input event with proper control state + const auto zeroKey = OneCoreSafeVkKeyScanW(0); + uint32_t ctrlState = 0; + WI_SetFlagIf(ctrlState, SHIFT_PRESSED, WI_IsFlagSet(zeroKey, 0x100)); + WI_SetFlagIf(ctrlState, LEFT_CTRL_PRESSED, WI_IsFlagSet(zeroKey, 0x200)); + WI_SetFlagIf(ctrlState, LEFT_ALT_PRESSED, WI_IsFlagSet(zeroKey, 0x400)); + _storage.push_back(SynthesizeKeyEvent(true, 1, LOBYTE(zeroKey), 0, wch, ctrlState)); + continue; + } + _storage.push_back(SynthesizeKeyEvent(true, 1, 0, 0, wch, 0)); + } +} + TerminalInput& InputBuffer::GetTerminalInput() { return _termInput; diff --git a/src/host/inputBuffer.hpp b/src/host/inputBuffer.hpp index ec7cd75b082..019c6e628bd 100644 --- a/src/host/inputBuffer.hpp +++ b/src/host/inputBuffer.hpp @@ -58,6 +58,7 @@ class InputBuffer final : public ConsoleObjectHeader size_t Prepend(const std::span& inEvents); size_t Write(const INPUT_RECORD& inEvent); size_t Write(const std::span& inEvents); + void WriteString(const std::wstring_view& text); void WriteFocusEvent(bool focused) noexcept; bool WriteMouseEvent(til::point position, unsigned int button, short keyState, short wheelDelta); @@ -96,6 +97,7 @@ class InputBuffer final : public ConsoleObjectHeader void _WriteBuffer(const std::span& inRecords, _Out_ size_t& eventsWritten, _Out_ bool& setWaitEvent); bool _CoalesceEvent(const INPUT_RECORD& inEvent) noexcept; void _HandleTerminalInputCallback(const Microsoft::Console::VirtualTerminal::TerminalInput::StringType& text); + void _writeString(const std::wstring_view& text); #ifdef UNIT_TESTING friend class InputBufferTests; diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp index a48e189f00d..24adc7a613f 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -33,24 +33,11 @@ ConhostInternalGetSet::ConhostInternalGetSet(_In_ IIoProvider& io) : // - void ConhostInternalGetSet::ReturnResponse(const std::wstring_view response) { - InputEventQueue inEvents; - - // generate a paired key down and key up event for every - // character to be sent into the console's input buffer - for (const auto& wch : response) - { - // This wasn't from a real keyboard, so we're leaving key/scan codes blank. - auto keyEvent = SynthesizeKeyEvent(true, 1, 0, 0, wch, 0); - inEvents.push_back(keyEvent); - keyEvent.Event.KeyEvent.bKeyDown = false; - inEvents.push_back(keyEvent); - } - // TODO GH#4954 During the input refactor we may want to add a "priority" input list // to make sure that "response" input is spooled directly into the application. // We switched this to an append (vs. a prepend) to fix GH#1637, a bug where two CPR // could collide with each other. - _io.GetActiveInputBuffer()->Write(inEvents); + _io.GetActiveInputBuffer()->WriteString(response); } // Routine Description: From ab7a2f10c5f5cd3d0b45b46d068ae9a70b6a9af5 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 5 Dec 2023 00:07:19 +0100 Subject: [PATCH 091/167] Fix scroll-forward-disable setting (#16411) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The final parameter, `updateBottom`, controls not just whether the `_virtualBottom` is updated, but also whether the position is clamped to be within the existing `_virtualBottom`. Setting this to `false` thus broke scroll-forward as the `_virtualBottom` was now a constant. ## Validation Steps Performed * Disable scroll-foward * Press and hold Ctrl+C * It scrolls past the viewport bottom ✅ --- src/host/outputStream.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp index 24adc7a613f..6cb84a4ef5b 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -82,7 +82,7 @@ til::rect ConhostInternalGetSet::GetViewport() const void ConhostInternalGetSet::SetViewportPosition(const til::point position) { auto& info = _io.GetActiveOutputBuffer(); - THROW_IF_FAILED(info.SetViewportOrigin(true, position, false)); + THROW_IF_FAILED(info.SetViewportOrigin(true, position, true)); // SetViewportOrigin() only updates the virtual bottom (the bottom coordinate of the area // in the text buffer a VT client writes its output into) when it's moving downwards. // But this function is meant to truly move the viewport no matter what. Otherwise `tput reset` breaks. From 85eee09854666a6b67e6fbc1a660e9af7827a6ca Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 4 Dec 2023 21:29:34 +0100 Subject: [PATCH 092/167] Enable AtlasEngine by default (#16277) This enables AtlasEngine by default in the 1.19 release branch. A future change will remove the alternative DxEngine entirely. (cherry picked from commit 204ebf3b198a1f1b6ae1f038cc371c5e1326fc5d) Service-Card-Id: 91158876 Service-Version: 1.19 --- .../CascadiaSettingsSerialization.cpp | 19 ------------------- .../TerminalSettingsModel/MTSMSettings.h | 2 +- .../TerminalSettingsModel/TerminalSettings.h | 2 +- .../UnitTests_Control/ControlCoreTests.cpp | 18 ++++++++++++------ .../ControlInteractivityTests.cpp | 9 ++++++--- src/cascadia/inc/ControlProperties.h | 2 +- src/features.xml | 10 ---------- 7 files changed, 21 insertions(+), 41 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index 8964169f207..15e62efd4fa 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -949,25 +949,6 @@ void CascadiaSettings::_researchOnLoad() // Only do this if we're actually being sampled if (TraceLoggingProviderEnabled(g_hSettingsModelProvider, 0, MICROSOFT_KEYWORD_MEASURES)) { - // GH#13936: We're interested in how many users opt out of useAtlasEngine, - // indicating major issues that would require us to disable it by default again. - { - size_t enabled[2]{}; - for (const auto& profile : _activeProfiles) - { - enabled[profile.UseAtlasEngine()]++; - } - - TraceLoggingWrite( - g_hSettingsModelProvider, - "AtlasEngine_Usage", - TraceLoggingDescription("Event emitted upon settings load, containing the number of profiles opted-in/out of useAtlasEngine"), - TraceLoggingUIntPtr(enabled[0], "UseAtlasEngineDisabled", "Number of profiles for which AtlasEngine is disabled"), - TraceLoggingUIntPtr(enabled[1], "UseAtlasEngineEnabled", "Number of profiles for which AtlasEngine is enabled"), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), - TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); - } - // ----------------------------- RE: Themes ---------------------------- const auto numThemes = GlobalSettings().Themes().Size(); const auto themeInUse = GlobalSettings().CurrentTheme().Name(); diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index 15e91a1d7ae..d0dc6bfcca5 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -90,7 +90,7 @@ Author(s): X(hstring, TabTitle, "tabTitle") \ X(Model::BellStyle, BellStyle, "bellStyle", BellStyle::Audible) \ X(IEnvironmentVariableMap, EnvironmentVariables, "environment", nullptr) \ - X(bool, UseAtlasEngine, "useAtlasEngine", Feature_AtlasEngine::IsEnabled()) \ + X(bool, UseAtlasEngine, "useAtlasEngine", true) \ X(bool, RightClickContextMenu, "experimental.rightClickContextMenu", false) \ X(Windows::Foundation::Collections::IVector, BellSound, "bellSound", nullptr) \ X(bool, Elevate, "elevate", false) \ diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.h b/src/cascadia/TerminalSettingsModel/TerminalSettings.h index 60c9b5fb2c2..abc210b9d94 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.h @@ -148,7 +148,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::TerminalSettings, IEnvironmentVariableMap, EnvironmentVariables); INHERITABLE_SETTING(Model::TerminalSettings, Microsoft::Terminal::Control::ScrollbarState, ScrollState, Microsoft::Terminal::Control::ScrollbarState::Visible); - INHERITABLE_SETTING(Model::TerminalSettings, bool, UseAtlasEngine, false); + INHERITABLE_SETTING(Model::TerminalSettings, bool, UseAtlasEngine, true); INHERITABLE_SETTING(Model::TerminalSettings, Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale); diff --git a/src/cascadia/UnitTests_Control/ControlCoreTests.cpp b/src/cascadia/UnitTests_Control/ControlCoreTests.cpp index f34ca5a6bfd..c38c0221c5a 100644 --- a/src/cascadia/UnitTests_Control/ControlCoreTests.cpp +++ b/src/cascadia/UnitTests_Control/ControlCoreTests.cpp @@ -80,9 +80,12 @@ namespace ControlUnitTests void _standardInit(winrt::com_ptr core) { - // "Consolas" ends up with an actual size of 9x21 at 96DPI. So - // let's just arbitrarily start with a 270x420px (30x20 chars) window - core->Initialize(270, 420, 1.0); + // "Consolas" ends up with an actual size of 9x19 at 96DPI. So + // let's just arbitrarily start with a 270x380px (30x20 chars) window + core->Initialize(270, 380, 1.0); +#ifndef NDEBUG + core->_terminal->_suppressLockChecks = true; +#endif VERIFY_IS_TRUE(core->_initializedTerminal); VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height()); } @@ -113,9 +116,12 @@ namespace ControlUnitTests VERIFY_IS_NOT_NULL(core); VERIFY_IS_FALSE(core->_initializedTerminal); - // "Consolas" ends up with an actual size of 9x21 at 96DPI. So - // let's just arbitrarily start with a 270x420px (30x20 chars) window - core->Initialize(270, 420, 1.0); + // "Consolas" ends up with an actual size of 9x19 at 96DPI. So + // let's just arbitrarily start with a 270x380px (30x20 chars) window + core->Initialize(270, 380, 1.0); +#ifndef NDEBUG + core->_terminal->_suppressLockChecks = true; +#endif VERIFY_IS_TRUE(core->_initializedTerminal); VERIFY_ARE_EQUAL(30, core->_terminal->GetViewport().Width()); } diff --git a/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp b/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp index a48aa968944..192cc2ad9d8 100644 --- a/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp +++ b/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp @@ -88,9 +88,12 @@ namespace ControlUnitTests void _standardInit(winrt::com_ptr core, winrt::com_ptr interactivity) { - // "Consolas" ends up with an actual size of 9x21 at 96DPI. So - // let's just arbitrarily start with a 270x420px (30x20 chars) window - core->Initialize(270, 420, 1.0); + // "Consolas" ends up with an actual size of 9x19 at 96DPI. So + // let's just arbitrarily start with a 270x380px (30x20 chars) window + core->Initialize(270, 380, 1.0); +#ifndef NDEBUG + core->_terminal->_suppressLockChecks = true; +#endif VERIFY_IS_TRUE(core->_initializedTerminal); VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height()); interactivity->Initialize(); diff --git a/src/cascadia/inc/ControlProperties.h b/src/cascadia/inc/ControlProperties.h index c18e70e7cb2..2c8c8ad48bb 100644 --- a/src/cascadia/inc/ControlProperties.h +++ b/src/cascadia/inc/ControlProperties.h @@ -72,7 +72,7 @@ X(winrt::Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, winrt::Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale) \ X(bool, ForceFullRepaintRendering, false) \ X(bool, SoftwareRendering, false) \ - X(bool, UseAtlasEngine, false) \ + X(bool, UseAtlasEngine, true) \ X(bool, UseBackgroundImageForWindow, false) \ X(bool, ShowMarks, false) \ X(bool, RightClickContextMenu, false) diff --git a/src/features.xml b/src/features.xml index 4656ef0ffd5..be08ced6932 100644 --- a/src/features.xml +++ b/src/features.xml @@ -76,16 +76,6 @@ - - Feature_AtlasEngine - If enabled, AtlasEngine is used by default - AlwaysEnabled - - Release - WindowsInbox - - - Feature_AtlasEnginePresentFallback We don't feel super confident in our usage of the Present1 API, so this settings adds a fallback to Present on error From 486f7ef644c74faf77223a54fd0c884a52b66d6c Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 4 Dec 2023 23:58:57 +0100 Subject: [PATCH 093/167] Fix a coroutine AV crash (#16412) tl;dr: A coroutine lambda does not hold onto captured variables. This causes an AV crash when closing tabs. I randomly noticed this in a Debug build as the memory contents got replaced with 0xCD. In a Release build this bug is probably fairly subtle and not common. (cherry picked from commit 91fd7d01018b2400f416d5ae5ef98fc85dfe8583) Service-Card-Id: 91258717 Service-Version: 1.19 --- src/cascadia/TerminalApp/TerminalTab.cpp | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp index c363ec1d9f9..06121ab945d 100644 --- a/src/cascadia/TerminalApp/TerminalTab.cpp +++ b/src/cascadia/TerminalApp/TerminalTab.cpp @@ -933,9 +933,13 @@ namespace winrt::TerminalApp::implementation ControlEventTokens events{}; events.titleToken = control.TitleChanged([dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget { + // The lambda lives in the `std::function`-style container owned by `control`. That is, when the + // `control` gets destroyed the lambda struct also gets destroyed. In other words, we need to + // copy `weakThis` onto the stack, because that's the only thing that gets captured in coroutines. + // See: https://devblogs.microsoft.com/oldnewthing/20211103-00/?p=105870 + const auto weakThisCopy = weakThis; co_await wil::resume_foreground(dispatcher); - // Check if Tab's lifetime has expired - if (auto tab{ weakThis.get() }) + if (auto tab{ weakThisCopy.get() }) { // The title of the control changed, but not necessarily the title of the tab. // Set the tab's text to the active panes' text. @@ -944,8 +948,9 @@ namespace winrt::TerminalApp::implementation }); events.colorToken = control.TabColorChanged([dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget { + const auto weakThisCopy = weakThis; co_await wil::resume_foreground(dispatcher); - if (auto tab{ weakThis.get() }) + if (auto tab{ weakThisCopy.get() }) { // The control's tabColor changed, but it is not necessarily the // active control in this tab. We'll just recalculate the @@ -955,33 +960,36 @@ namespace winrt::TerminalApp::implementation }); events.taskbarToken = control.SetTaskbarProgress([dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget { + const auto weakThisCopy = weakThis; co_await wil::resume_foreground(dispatcher); - // Check if Tab's lifetime has expired - if (auto tab{ weakThis.get() }) + if (auto tab{ weakThisCopy.get() }) { tab->_UpdateProgressState(); } }); events.stateToken = control.ConnectionStateChanged([dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget { + const auto weakThisCopy = weakThis; co_await wil::resume_foreground(dispatcher); - if (auto tab{ weakThis.get() }) + if (auto tab{ weakThisCopy.get() }) { tab->_UpdateConnectionClosedState(); } }); events.readOnlyToken = control.ReadOnlyChanged([dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget { + const auto weakThisCopy = weakThis; co_await wil::resume_foreground(dispatcher); - if (auto tab{ weakThis.get() }) + if (auto tab{ weakThisCopy.get() }) { tab->_RecalculateAndApplyReadOnly(); } }); events.focusToken = control.FocusFollowMouseRequested([dispatcher, weakThis](auto sender, auto) -> winrt::fire_and_forget { + const auto weakThisCopy = weakThis; co_await wil::resume_foreground(dispatcher); - if (const auto tab{ weakThis.get() }) + if (const auto tab{ weakThisCopy.get() }) { if (tab->_focused()) { From 9882154043f33998ee14ca747148be74f2ba8b26 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 5 Dec 2023 00:05:25 +0100 Subject: [PATCH 094/167] Avoid encoding VT via win32 input mode (#16407) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This changeset avoids re-encoding output from `AdaptDispatch` via the win32-input-mode mechanism when VT input is enabled. That is, an `AdaptDispatch` output like `\x1b[C` would otherwise result in dozens of characters of input. Related to #16343 ## Validation Steps Performed * Replace conhost with this * Launch a Win32 application inside WSL * ASCII keyboard inputs are represented as single `INPUT_RECORD`s ✅ (cherry picked from commit 0da37a134a5363080f85a9adc45a79dc6e52ec90) Service-Card-Id: 91246942 Service-Version: 1.19 --- src/host/inputBuffer.cpp | 56 ++++++++++++++++++++++++++++----------- src/host/inputBuffer.hpp | 2 ++ src/host/outputStream.cpp | 15 +---------- 3 files changed, 44 insertions(+), 29 deletions(-) diff --git a/src/host/inputBuffer.cpp b/src/host/inputBuffer.cpp index abdfed1961f..184670ffb1d 100644 --- a/src/host/inputBuffer.cpp +++ b/src/host/inputBuffer.cpp @@ -602,6 +602,27 @@ size_t InputBuffer::Write(const std::span& inEvents) } } +void InputBuffer::WriteString(const std::wstring_view& text) +try +{ + if (text.empty()) + { + return; + } + + const auto initiallyEmptyQueue = _storage.empty(); + + _writeString(text); + + if (initiallyEmptyQueue && !_storage.empty()) + { + ServiceLocator::LocateGlobals().hInputEvent.SetEvent(); + } + + WakeUpReadersWaitingForData(); +} +CATCH_LOG() + // This can be considered a "privileged" variant of Write() which allows FOCUS_EVENTs to generate focus VT sequences. // If we didn't do this, someone could write a FOCUS_EVENT_RECORD with WriteConsoleInput, exit without flushing the // input buffer and the next application will suddenly get a "\x1b[I" sequence in their input. See GH#13238. @@ -828,21 +849,7 @@ void InputBuffer::_HandleTerminalInputCallback(const TerminalInput::StringType& return; } - for (const auto& wch : text) - { - if (wch == UNICODE_NULL) - { - // Convert null byte back to input event with proper control state - const auto zeroKey = OneCoreSafeVkKeyScanW(0); - uint32_t ctrlState = 0; - WI_SetFlagIf(ctrlState, SHIFT_PRESSED, WI_IsFlagSet(zeroKey, 0x100)); - WI_SetFlagIf(ctrlState, LEFT_CTRL_PRESSED, WI_IsFlagSet(zeroKey, 0x200)); - WI_SetFlagIf(ctrlState, LEFT_ALT_PRESSED, WI_IsFlagSet(zeroKey, 0x400)); - _storage.push_back(SynthesizeKeyEvent(true, 1, LOBYTE(zeroKey), 0, wch, ctrlState)); - continue; - } - _storage.push_back(SynthesizeKeyEvent(true, 1, 0, 0, wch, 0)); - } + _writeString(text); if (!_vtInputShouldSuppress) { @@ -856,6 +863,25 @@ void InputBuffer::_HandleTerminalInputCallback(const TerminalInput::StringType& } } +void InputBuffer::_writeString(const std::wstring_view& text) +{ + for (const auto& wch : text) + { + if (wch == UNICODE_NULL) + { + // Convert null byte back to input event with proper control state + const auto zeroKey = OneCoreSafeVkKeyScanW(0); + uint32_t ctrlState = 0; + WI_SetFlagIf(ctrlState, SHIFT_PRESSED, WI_IsFlagSet(zeroKey, 0x100)); + WI_SetFlagIf(ctrlState, LEFT_CTRL_PRESSED, WI_IsFlagSet(zeroKey, 0x200)); + WI_SetFlagIf(ctrlState, LEFT_ALT_PRESSED, WI_IsFlagSet(zeroKey, 0x400)); + _storage.push_back(SynthesizeKeyEvent(true, 1, LOBYTE(zeroKey), 0, wch, ctrlState)); + continue; + } + _storage.push_back(SynthesizeKeyEvent(true, 1, 0, 0, wch, 0)); + } +} + TerminalInput& InputBuffer::GetTerminalInput() { return _termInput; diff --git a/src/host/inputBuffer.hpp b/src/host/inputBuffer.hpp index ec7cd75b082..019c6e628bd 100644 --- a/src/host/inputBuffer.hpp +++ b/src/host/inputBuffer.hpp @@ -58,6 +58,7 @@ class InputBuffer final : public ConsoleObjectHeader size_t Prepend(const std::span& inEvents); size_t Write(const INPUT_RECORD& inEvent); size_t Write(const std::span& inEvents); + void WriteString(const std::wstring_view& text); void WriteFocusEvent(bool focused) noexcept; bool WriteMouseEvent(til::point position, unsigned int button, short keyState, short wheelDelta); @@ -96,6 +97,7 @@ class InputBuffer final : public ConsoleObjectHeader void _WriteBuffer(const std::span& inRecords, _Out_ size_t& eventsWritten, _Out_ bool& setWaitEvent); bool _CoalesceEvent(const INPUT_RECORD& inEvent) noexcept; void _HandleTerminalInputCallback(const Microsoft::Console::VirtualTerminal::TerminalInput::StringType& text); + void _writeString(const std::wstring_view& text); #ifdef UNIT_TESTING friend class InputBufferTests; diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp index a48e189f00d..24adc7a613f 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -33,24 +33,11 @@ ConhostInternalGetSet::ConhostInternalGetSet(_In_ IIoProvider& io) : // - void ConhostInternalGetSet::ReturnResponse(const std::wstring_view response) { - InputEventQueue inEvents; - - // generate a paired key down and key up event for every - // character to be sent into the console's input buffer - for (const auto& wch : response) - { - // This wasn't from a real keyboard, so we're leaving key/scan codes blank. - auto keyEvent = SynthesizeKeyEvent(true, 1, 0, 0, wch, 0); - inEvents.push_back(keyEvent); - keyEvent.Event.KeyEvent.bKeyDown = false; - inEvents.push_back(keyEvent); - } - // TODO GH#4954 During the input refactor we may want to add a "priority" input list // to make sure that "response" input is spooled directly into the application. // We switched this to an append (vs. a prepend) to fix GH#1637, a bug where two CPR // could collide with each other. - _io.GetActiveInputBuffer()->Write(inEvents); + _io.GetActiveInputBuffer()->WriteString(response); } // Routine Description: From ef32e3e487c87865de4f8f9de45447c105af20fe Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 5 Dec 2023 00:07:19 +0100 Subject: [PATCH 095/167] Fix scroll-forward-disable setting (#16411) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The final parameter, `updateBottom`, controls not just whether the `_virtualBottom` is updated, but also whether the position is clamped to be within the existing `_virtualBottom`. Setting this to `false` thus broke scroll-forward as the `_virtualBottom` was now a constant. ## Validation Steps Performed * Disable scroll-foward * Press and hold Ctrl+C * It scrolls past the viewport bottom ✅ (cherry picked from commit ab7a2f10c5f5cd3d0b45b46d068ae9a70b6a9af5) Service-Card-Id: 91258882 Service-Version: 1.19 --- src/host/outputStream.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp index 24adc7a613f..6cb84a4ef5b 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -82,7 +82,7 @@ til::rect ConhostInternalGetSet::GetViewport() const void ConhostInternalGetSet::SetViewportPosition(const til::point position) { auto& info = _io.GetActiveOutputBuffer(); - THROW_IF_FAILED(info.SetViewportOrigin(true, position, false)); + THROW_IF_FAILED(info.SetViewportOrigin(true, position, true)); // SetViewportOrigin() only updates the virtual bottom (the bottom coordinate of the area // in the text buffer a VT client writes its output into) when it's moving downwards. // But this function is meant to truly move the viewport no matter what. Otherwise `tput reset` breaks. From 70e51ae28d305ff9cd7430984119648b9f0b04ef Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 5 Dec 2023 02:53:55 +0100 Subject: [PATCH 096/167] Disable win32 input mode on exit (#16408) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When ConPTY exits it should attempt to restore the state as it was before it started. This is particularly important for the win32 input mode sequences, as Linux shells don't know what to do with it. Related to #16343 ## Validation Steps Performed * Replace conhost with this * Launch a Win32 application inside WSL * Exit that application * Shell prompt doesn't get filled with win32 input mode sequences ✅ --- .../ConptyRoundtripTests.cpp | 2 +- src/renderer/vt/VtSequences.cpp | 19 ------------------- src/renderer/vt/invalidate.cpp | 8 ++++++++ src/renderer/vt/state.cpp | 6 ++++-- src/renderer/vt/vtrenderer.hpp | 5 ----- src/terminal/adapter/adaptDispatch.cpp | 10 ++++++++-- 6 files changed, 21 insertions(+), 29 deletions(-) diff --git a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp index cf15e3ba191..f7948e5c59d 100644 --- a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp @@ -1201,7 +1201,7 @@ void ConptyRoundtripTests::PassthroughHardReset() // Write a Hard Reset VT sequence to the host, it should come through to the Terminal // along with a DECSET sequence to re-enable win32 input and focus events. expectedOutput.push_back("\033c"); - expectedOutput.push_back("\033[?9001;1004h"); + expectedOutput.push_back("\033[?9001h\033[?1004h"); hostSm.ProcessString(L"\033c"); const auto termSecondView = term->GetViewport(); diff --git a/src/renderer/vt/VtSequences.cpp b/src/renderer/vt/VtSequences.cpp index 1b728aae500..612d3c06d06 100644 --- a/src/renderer/vt/VtSequences.cpp +++ b/src/renderer/vt/VtSequences.cpp @@ -476,25 +476,6 @@ using namespace Microsoft::Console::Render; return _Write(isReversed ? "\x1b[7m" : "\x1b[27m"); } -// Method Description: -// - Send a sequence to the connected terminal to request win32-input-mode from -// them. This will enable the connected terminal to send us full INPUT_RECORDs -// as input. If the terminal doesn't understand this sequence, it'll just -// ignore it. -// Arguments: -// - -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_RequestWin32Input() noexcept -{ - return _Write("\x1b[?9001h"); -} - -[[nodiscard]] HRESULT VtEngine::_RequestFocusEventMode() noexcept -{ - return _Write("\x1b[?1004h"); -} - // Method Description: // - Send a sequence to the connected terminal to switch to the alternate or main screen buffer. // Arguments: diff --git a/src/renderer/vt/invalidate.cpp b/src/renderer/vt/invalidate.cpp index 7ef59bc03b0..8f80d3eb8ea 100644 --- a/src/renderer/vt/invalidate.cpp +++ b/src/renderer/vt/invalidate.cpp @@ -126,6 +126,14 @@ CATCH_RETURN(); // - S_OK [[nodiscard]] HRESULT VtEngine::PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept { + // This must be kept in sync with RequestWin32Input(). + // It ensures that we disable the modes that we requested on startup. + // Linux shells for instance don't understand the win32-input-mode 9001. + // + // This can be here, instead of being appended at the end of this final rendering pass, + // because these two states happen to have no influence on the caller's VT parsing. + std::ignore = _Write("\033[?9001l\033[?1004l"); + *pForcePaint = true; return S_OK; } diff --git a/src/renderer/vt/state.cpp b/src/renderer/vt/state.cpp index 36e31463cd9..5bb6b7d694d 100644 --- a/src/renderer/vt/state.cpp +++ b/src/renderer/vt/state.cpp @@ -526,11 +526,13 @@ void VtEngine::SetTerminalCursorTextPosition(const til::point cursor) noexcept // - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. HRESULT VtEngine::RequestWin32Input() noexcept { + // On startup we request the modes we require for optimal functioning + // (namely win32 input mode and focus event mode). + // // It's important that any additional modes set here are also mirrored in // the AdaptDispatch::HardReset method, since that needs to re-enable them // in the connected terminal after passing through an RIS sequence. - RETURN_IF_FAILED(_RequestWin32Input()); - RETURN_IF_FAILED(_RequestFocusEventMode()); + RETURN_IF_FAILED(_Write("\033[?9001h\033[?1004h")); _Flush(); return S_OK; } diff --git a/src/renderer/vt/vtrenderer.hpp b/src/renderer/vt/vtrenderer.hpp index 100e04c549d..676672ee814 100644 --- a/src/renderer/vt/vtrenderer.hpp +++ b/src/renderer/vt/vtrenderer.hpp @@ -80,8 +80,6 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT WriteTerminalUtf8(const std::string_view str) noexcept; [[nodiscard]] virtual HRESULT WriteTerminalW(const std::wstring_view str) noexcept = 0; void SetTerminalOwner(Microsoft::Console::VirtualTerminal::VtIo* const terminalOwner); - void BeginResizeRequest(); - void EndResizeRequest(); void SetResizeQuirk(const bool resizeQuirk); void SetPassthroughMode(const bool passthrough) noexcept; void SetLookingForDSRCallback(std::function pfnLooking) noexcept; @@ -208,11 +206,8 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT _RequestCursor() noexcept; [[nodiscard]] HRESULT _ListenForDSR() noexcept; - [[nodiscard]] HRESULT _RequestWin32Input() noexcept; [[nodiscard]] HRESULT _SwitchScreenBuffer(const bool useAltBuffer) noexcept; - [[nodiscard]] HRESULT _RequestFocusEventMode() noexcept; - [[nodiscard]] virtual HRESULT _MoveCursor(const til::point coord) noexcept = 0; [[nodiscard]] HRESULT _RgbUpdateDrawingBrushes(const TextAttribute& textAttributes) noexcept; [[nodiscard]] HRESULT _16ColorUpdateDrawingBrushes(const TextAttribute& textAttributes) noexcept; diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index 85582542980..31edacae21d 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -1917,7 +1917,13 @@ bool AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con return !_api.IsConsolePty(); case DispatchTypes::ModeParams::W32IM_Win32InputMode: _terminalInput.SetInputMode(TerminalInput::Mode::Win32, enable); - return !_PassThroughInputModes(); + // ConPTY requests the Win32InputMode on startup and disables it on shutdown. When nesting ConPTY inside + // ConPTY then this should not bubble up. Otherwise, when the inner ConPTY exits and the outer ConPTY + // passes the disable sequence up to the hosting terminal, we'd stop getting Win32InputMode entirely! + // It also makes more sense to not bubble it up, because this mode is specifically for INPUT_RECORD interop + // and thus entirely between a PTY's input records and its INPUT_RECORD-aware VT-aware console clients. + // Returning true here will mark this as being handled and avoid this. + return true; default: // If no functions to call, overall dispatch was a failure. return false; @@ -3097,7 +3103,7 @@ bool AdaptDispatch::HardReset() if (stateMachine.FlushToTerminal()) { auto& engine = stateMachine.Engine(); - engine.ActionPassThroughString(L"\033[?9001;1004h"); + engine.ActionPassThroughString(L"\033[?9001h\033[?1004h"); } } return true; From 8a9af94095883bd5ea726ee64b6eec059d60810a Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 5 Dec 2023 02:53:55 +0100 Subject: [PATCH 097/167] Disable win32 input mode on exit (#16408) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When ConPTY exits it should attempt to restore the state as it was before it started. This is particularly important for the win32 input mode sequences, as Linux shells don't know what to do with it. Related to #16343 ## Validation Steps Performed * Replace conhost with this * Launch a Win32 application inside WSL * Exit that application * Shell prompt doesn't get filled with win32 input mode sequences ✅ (cherry picked from commit 70e51ae28d305ff9cd7430984119648b9f0b04ef) Service-Card-Id: 91246943 Service-Version: 1.19 --- .../ConptyRoundtripTests.cpp | 2 +- src/renderer/vt/VtSequences.cpp | 19 ------------------- src/renderer/vt/invalidate.cpp | 8 ++++++++ src/renderer/vt/state.cpp | 6 ++++-- src/renderer/vt/vtrenderer.hpp | 5 ----- src/terminal/adapter/adaptDispatch.cpp | 10 ++++++++-- 6 files changed, 21 insertions(+), 29 deletions(-) diff --git a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp index cf15e3ba191..f7948e5c59d 100644 --- a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp @@ -1201,7 +1201,7 @@ void ConptyRoundtripTests::PassthroughHardReset() // Write a Hard Reset VT sequence to the host, it should come through to the Terminal // along with a DECSET sequence to re-enable win32 input and focus events. expectedOutput.push_back("\033c"); - expectedOutput.push_back("\033[?9001;1004h"); + expectedOutput.push_back("\033[?9001h\033[?1004h"); hostSm.ProcessString(L"\033c"); const auto termSecondView = term->GetViewport(); diff --git a/src/renderer/vt/VtSequences.cpp b/src/renderer/vt/VtSequences.cpp index 1b728aae500..612d3c06d06 100644 --- a/src/renderer/vt/VtSequences.cpp +++ b/src/renderer/vt/VtSequences.cpp @@ -476,25 +476,6 @@ using namespace Microsoft::Console::Render; return _Write(isReversed ? "\x1b[7m" : "\x1b[27m"); } -// Method Description: -// - Send a sequence to the connected terminal to request win32-input-mode from -// them. This will enable the connected terminal to send us full INPUT_RECORDs -// as input. If the terminal doesn't understand this sequence, it'll just -// ignore it. -// Arguments: -// - -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_RequestWin32Input() noexcept -{ - return _Write("\x1b[?9001h"); -} - -[[nodiscard]] HRESULT VtEngine::_RequestFocusEventMode() noexcept -{ - return _Write("\x1b[?1004h"); -} - // Method Description: // - Send a sequence to the connected terminal to switch to the alternate or main screen buffer. // Arguments: diff --git a/src/renderer/vt/invalidate.cpp b/src/renderer/vt/invalidate.cpp index 7ef59bc03b0..8f80d3eb8ea 100644 --- a/src/renderer/vt/invalidate.cpp +++ b/src/renderer/vt/invalidate.cpp @@ -126,6 +126,14 @@ CATCH_RETURN(); // - S_OK [[nodiscard]] HRESULT VtEngine::PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept { + // This must be kept in sync with RequestWin32Input(). + // It ensures that we disable the modes that we requested on startup. + // Linux shells for instance don't understand the win32-input-mode 9001. + // + // This can be here, instead of being appended at the end of this final rendering pass, + // because these two states happen to have no influence on the caller's VT parsing. + std::ignore = _Write("\033[?9001l\033[?1004l"); + *pForcePaint = true; return S_OK; } diff --git a/src/renderer/vt/state.cpp b/src/renderer/vt/state.cpp index 8408ee3aeb6..df35691a361 100644 --- a/src/renderer/vt/state.cpp +++ b/src/renderer/vt/state.cpp @@ -525,11 +525,13 @@ void VtEngine::SetTerminalCursorTextPosition(const til::point cursor) noexcept // - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. HRESULT VtEngine::RequestWin32Input() noexcept { + // On startup we request the modes we require for optimal functioning + // (namely win32 input mode and focus event mode). + // // It's important that any additional modes set here are also mirrored in // the AdaptDispatch::HardReset method, since that needs to re-enable them // in the connected terminal after passing through an RIS sequence. - RETURN_IF_FAILED(_RequestWin32Input()); - RETURN_IF_FAILED(_RequestFocusEventMode()); + RETURN_IF_FAILED(_Write("\033[?9001h\033[?1004h")); _Flush(); return S_OK; } diff --git a/src/renderer/vt/vtrenderer.hpp b/src/renderer/vt/vtrenderer.hpp index 15de6e0e760..094bebec385 100644 --- a/src/renderer/vt/vtrenderer.hpp +++ b/src/renderer/vt/vtrenderer.hpp @@ -80,8 +80,6 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT WriteTerminalUtf8(const std::string_view str) noexcept; [[nodiscard]] virtual HRESULT WriteTerminalW(const std::wstring_view str) noexcept = 0; void SetTerminalOwner(Microsoft::Console::VirtualTerminal::VtIo* const terminalOwner); - void BeginResizeRequest(); - void EndResizeRequest(); void SetResizeQuirk(const bool resizeQuirk); void SetPassthroughMode(const bool passthrough) noexcept; void SetLookingForDSRCallback(std::function pfnLooking) noexcept; @@ -208,11 +206,8 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT _RequestCursor() noexcept; [[nodiscard]] HRESULT _ListenForDSR() noexcept; - [[nodiscard]] HRESULT _RequestWin32Input() noexcept; [[nodiscard]] HRESULT _SwitchScreenBuffer(const bool useAltBuffer) noexcept; - [[nodiscard]] HRESULT _RequestFocusEventMode() noexcept; - [[nodiscard]] virtual HRESULT _MoveCursor(const til::point coord) noexcept = 0; [[nodiscard]] HRESULT _RgbUpdateDrawingBrushes(const TextAttribute& textAttributes) noexcept; [[nodiscard]] HRESULT _16ColorUpdateDrawingBrushes(const TextAttribute& textAttributes) noexcept; diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index 85582542980..31edacae21d 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -1917,7 +1917,13 @@ bool AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con return !_api.IsConsolePty(); case DispatchTypes::ModeParams::W32IM_Win32InputMode: _terminalInput.SetInputMode(TerminalInput::Mode::Win32, enable); - return !_PassThroughInputModes(); + // ConPTY requests the Win32InputMode on startup and disables it on shutdown. When nesting ConPTY inside + // ConPTY then this should not bubble up. Otherwise, when the inner ConPTY exits and the outer ConPTY + // passes the disable sequence up to the hosting terminal, we'd stop getting Win32InputMode entirely! + // It also makes more sense to not bubble it up, because this mode is specifically for INPUT_RECORD interop + // and thus entirely between a PTY's input records and its INPUT_RECORD-aware VT-aware console clients. + // Returning true here will mark this as being handled and avoid this. + return true; default: // If no functions to call, overall dispatch was a failure. return false; @@ -3097,7 +3103,7 @@ bool AdaptDispatch::HardReset() if (stateMachine.FlushToTerminal()) { auto& engine = stateMachine.Engine(); - engine.ActionPassThroughString(L"\033[?9001;1004h"); + engine.ActionPassThroughString(L"\033[?9001h\033[?1004h"); } } return true; From 71a6f26e6ece656084e87de1a528c4a8072eeabd Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 5 Dec 2023 03:02:46 +0100 Subject: [PATCH 098/167] Improve conhost's scrolling performance (#16333) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `EnableScrollbar()` and especially `SetScrollInfo()` are prohibitively expensive functions nowadays. This improves throughput of good old `type` in cmd.exe by ~10x, by briefly releasing the console lock. ## Validation Steps Performed * `type`ing a file in `cmd` is as fast while the window is scrolling as it is while it isn't scrolling ✅ * Scrollbar pops in and out when scroll-forward is disabled ✅ --- src/host/consoleInformation.cpp | 5 ++ src/host/renderData.cpp | 6 ++- src/host/screenInfo.cpp | 54 +++++---------------- src/host/screenInfo.hpp | 9 +++- src/host/server.h | 1 + src/interactivity/inc/IConsoleWindow.hpp | 7 --- src/interactivity/onecore/ConsoleWindow.cpp | 14 ------ src/interactivity/onecore/ConsoleWindow.hpp | 3 -- src/interactivity/win32/window.cpp | 49 ++++++++++--------- src/interactivity/win32/window.hpp | 8 +-- src/interactivity/win32/windowproc.cpp | 13 ++++- 11 files changed, 68 insertions(+), 101 deletions(-) diff --git a/src/host/consoleInformation.cpp b/src/host/consoleInformation.cpp index a99a8871688..074bd56911f 100644 --- a/src/host/consoleInformation.cpp +++ b/src/host/consoleInformation.cpp @@ -35,6 +35,11 @@ void CONSOLE_INFORMATION::UnlockConsole() noexcept _lock.unlock(); } +til::recursive_ticket_lock_suspension CONSOLE_INFORMATION::SuspendLock() noexcept +{ + return _lock.suspend(); +} + ULONG CONSOLE_INFORMATION::GetCSRecursionCount() const noexcept { return _lock.recursion_depth(); diff --git a/src/host/renderData.cpp b/src/host/renderData.cpp index d327c0b9d61..5c89edae9a9 100644 --- a/src/host/renderData.cpp +++ b/src/host/renderData.cpp @@ -87,14 +87,16 @@ std::vector RenderData::GetSelectionRects() noexcept // they're done with any querying they need to do. void RenderData::LockConsole() noexcept { - ::LockConsole(); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + gci.LockConsole(); } // Method Description: // - Unlocks the console after a call to RenderData::LockConsole. void RenderData::UnlockConsole() noexcept { - ::UnlockConsole(); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + gci.UnlockConsole(); } // Method Description: diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp index d36cdeab3a9..2701b4e2072 100644 --- a/src/host/screenInfo.cpp +++ b/src/host/screenInfo.cpp @@ -34,7 +34,6 @@ SCREEN_INFORMATION::SCREEN_INFORMATION( const TextAttribute popupAttributes, const FontInfo fontInfo) : OutputMode{ ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT }, - ResizingWindow{ 0 }, WheelDelta{ 0 }, HWheelDelta{ 0 }, _textBuffer{ nullptr }, @@ -641,64 +640,35 @@ VOID SCREEN_INFORMATION::UpdateScrollBars() return; } - if (gci.Flags & CONSOLE_UPDATING_SCROLL_BARS) + if (gci.Flags & CONSOLE_UPDATING_SCROLL_BARS || ServiceLocator::LocateConsoleWindow() == nullptr) { return; } gci.Flags |= CONSOLE_UPDATING_SCROLL_BARS; - - if (ServiceLocator::LocateConsoleWindow() != nullptr) - { - ServiceLocator::LocateConsoleWindow()->PostUpdateScrollBars(); - } + LOG_IF_WIN32_BOOL_FALSE(ServiceLocator::LocateConsoleWindow()->PostUpdateScrollBars()); } -VOID SCREEN_INFORMATION::InternalUpdateScrollBars() +SCREEN_INFORMATION::ScrollBarState SCREEN_INFORMATION::FetchScrollBarState() { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - const auto pWindow = ServiceLocator::LocateConsoleWindow(); - WI_ClearFlag(gci.Flags, CONSOLE_UPDATING_SCROLL_BARS); - if (!IsActiveScreenBuffer()) - { - return; - } - - ResizingWindow++; - - if (pWindow != nullptr) - { - const auto buffer = GetBufferSize(); - - // If this is the main buffer, make sure we enable both of the scroll bars. - // The alt buffer likely disabled the scroll bars, this is the only - // way to re-enable it. - if (!_IsAltBuffer()) - { - pWindow->EnableBothScrollBars(); - } - - pWindow->UpdateScrollBar(true, - _IsAltBuffer(), - _viewport.Height(), - gci.IsTerminalScrolling() ? _virtualBottom : buffer.BottomInclusive(), - _viewport.Top()); - pWindow->UpdateScrollBar(false, - _IsAltBuffer(), - _viewport.Width(), - buffer.RightInclusive(), - _viewport.Left()); - } - // Fire off an event to let accessibility apps know the layout has changed. if (_pAccessibilityNotifier) { _pAccessibilityNotifier->NotifyConsoleLayoutEvent(); } - ResizingWindow--; + const auto buffer = GetBufferSize(); + const auto isAltBuffer = _IsAltBuffer(); + const auto maxSizeVer = gci.IsTerminalScrolling() ? _virtualBottom : buffer.BottomInclusive(); + const auto maxSizeHor = buffer.RightInclusive(); + return ScrollBarState{ + .maxSize = { maxSizeHor, maxSizeVer }, + .viewport = _viewport.ToExclusive(), + .isAltBuffer = isAltBuffer, + }; } // Routine Description: diff --git a/src/host/screenInfo.hpp b/src/host/screenInfo.hpp index 09fca19809b..c0351050566 100644 --- a/src/host/screenInfo.hpp +++ b/src/host/screenInfo.hpp @@ -99,8 +99,14 @@ class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console bool HasAccessibilityEventing() const noexcept; void NotifyAccessibilityEventing(const til::CoordType sStartX, const til::CoordType sStartY, const til::CoordType sEndX, const til::CoordType sEndY); + struct ScrollBarState + { + til::size maxSize; + til::rect viewport; + bool isAltBuffer = false; + }; void UpdateScrollBars(); - void InternalUpdateScrollBars(); + ScrollBarState FetchScrollBarState(); bool IsMaximizedBoth() const; bool IsMaximizedX() const; @@ -158,7 +164,6 @@ class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console bool CursorIsDoubleWidth() const; DWORD OutputMode; - WORD ResizingWindow; // > 0 if we should ignore WM_SIZE messages short WheelDelta; short HWheelDelta; diff --git a/src/host/server.h b/src/host/server.h index 3332cc6b383..cfa1ba14dc6 100644 --- a/src/host/server.h +++ b/src/host/server.h @@ -102,6 +102,7 @@ class CONSOLE_INFORMATION : void LockConsole() noexcept; void UnlockConsole() noexcept; + til::recursive_ticket_lock_suspension SuspendLock() noexcept; bool IsConsoleLocked() const noexcept; ULONG GetCSRecursionCount() const noexcept; diff --git a/src/interactivity/inc/IConsoleWindow.hpp b/src/interactivity/inc/IConsoleWindow.hpp index 232957ffb6c..fba098ea78d 100644 --- a/src/interactivity/inc/IConsoleWindow.hpp +++ b/src/interactivity/inc/IConsoleWindow.hpp @@ -25,13 +25,6 @@ namespace Microsoft::Console::Types public: virtual ~IConsoleWindow() = default; - virtual BOOL EnableBothScrollBars() = 0; - virtual int UpdateScrollBar(_In_ bool isVertical, - _In_ bool isAltBuffer, - _In_ UINT pageSize, - _In_ int maxSize, - _In_ int viewportPosition) = 0; - virtual bool IsInFullscreen() const = 0; virtual void SetIsFullscreen(const bool fFullscreenEnabled) = 0; diff --git a/src/interactivity/onecore/ConsoleWindow.cpp b/src/interactivity/onecore/ConsoleWindow.cpp index f6b7fac3c89..8d8fb9dcdc4 100644 --- a/src/interactivity/onecore/ConsoleWindow.cpp +++ b/src/interactivity/onecore/ConsoleWindow.cpp @@ -11,20 +11,6 @@ using namespace Microsoft::Console::Interactivity::OneCore; using namespace Microsoft::Console::Types; -BOOL ConsoleWindow::EnableBothScrollBars() noexcept -{ - return FALSE; -} - -int ConsoleWindow::UpdateScrollBar(bool /*isVertical*/, - bool /*isAltBuffer*/, - UINT /*pageSize*/, - int /*maxSize*/, - int /*viewportPosition*/) noexcept -{ - return 0; -} - bool ConsoleWindow::IsInFullscreen() const noexcept { return true; diff --git a/src/interactivity/onecore/ConsoleWindow.hpp b/src/interactivity/onecore/ConsoleWindow.hpp index 4ab28d8626d..530aafd13cb 100644 --- a/src/interactivity/onecore/ConsoleWindow.hpp +++ b/src/interactivity/onecore/ConsoleWindow.hpp @@ -24,9 +24,6 @@ namespace Microsoft::Console::Interactivity::OneCore { public: // Inherited via IConsoleWindow - BOOL EnableBothScrollBars() noexcept override; - int UpdateScrollBar(bool isVertical, bool isAltBuffer, UINT pageSize, int maxSize, int viewportPosition) noexcept override; - bool IsInFullscreen() const noexcept override; void SetIsFullscreen(const bool fFullscreenEnabled) noexcept override; void ChangeViewport(const til::inclusive_rect& NewWindow) override; diff --git a/src/interactivity/win32/window.cpp b/src/interactivity/win32/window.cpp index 3f802740588..599d1f376a7 100644 --- a/src/interactivity/win32/window.cpp +++ b/src/interactivity/win32/window.cpp @@ -438,7 +438,7 @@ void Window::_CloseWindow() const ShowWindow(hWnd, wShowWindow); auto& siAttached = GetScreenInfo(); - siAttached.InternalUpdateScrollBars(); + siAttached.UpdateScrollBars(); } return status; @@ -591,7 +591,7 @@ void Window::_UpdateWindowSize(const til::size sizeNew) if (WI_IsFlagClear(gci.Flags, CONSOLE_IS_ICONIC)) { - ScreenInfo.InternalUpdateScrollBars(); + ScreenInfo.UpdateScrollBars(); SetWindowPos(GetWindowHandle(), nullptr, @@ -621,7 +621,7 @@ void Window::_UpdateWindowSize(const til::size sizeNew) if (!IsInFullscreen() && !IsInMaximized()) { // Figure out how big to make the window, given the desired client area size. - siAttached.ResizingWindow++; + _resizingWindow++; // First get the buffer viewport size const auto WindowDimensions = siAttached.GetViewport().Dimensions(); @@ -691,7 +691,7 @@ void Window::_UpdateWindowSize(const til::size sizeNew) // If the change wasn't substantial, we may still need to update scrollbar positions. Note that PSReadLine // scrolls the window via Console.SetWindowPosition, which ultimately calls down to SetConsoleWindowInfo, // which ends up in this function. - siAttached.InternalUpdateScrollBars(); + siAttached.UpdateScrollBars(); } // MSFT: 12092729 @@ -716,7 +716,7 @@ void Window::_UpdateWindowSize(const til::size sizeNew) // an additional Buffer message with the same size again and do nothing special. ScreenBufferSizeChange(siAttached.GetActiveBuffer().GetBufferSize().Dimensions()); - siAttached.ResizingWindow--; + _resizingWindow--; } LOG_IF_FAILED(ConsoleImeResizeCompStrView()); @@ -875,26 +875,29 @@ void Window::HorizontalScroll(const WORD wScrollCommand, const WORD wAbsoluteCha LOG_IF_FAILED(ScreenInfo.SetViewportOrigin(true, NewOrigin, false)); } -BOOL Window::EnableBothScrollBars() +void Window::UpdateScrollBars(const SCREEN_INFORMATION::ScrollBarState& state) { - return EnableScrollBar(_hWnd, SB_BOTH, ESB_ENABLE_BOTH); -} + // If this is the main buffer, make sure we enable both of the scroll bars. + // The alt buffer likely disabled the scroll bars, this is the only way to re-enable it. + if (!state.isAltBuffer) + { + EnableScrollBar(_hWnd, SB_BOTH, ESB_ENABLE_BOTH); + } -int Window::UpdateScrollBar(bool isVertical, - bool isAltBuffer, - UINT pageSize, - int maxSize, - int viewportPosition) -{ - SCROLLINFO si; - si.cbSize = sizeof(si); - si.fMask = isAltBuffer ? SIF_ALL | SIF_DISABLENOSCROLL : SIF_ALL; - si.nPage = pageSize; - si.nMin = 0; - si.nMax = maxSize; - si.nPos = viewportPosition; - - return SetScrollInfo(_hWnd, isVertical ? SB_VERT : SB_HORZ, &si, TRUE); + SCROLLINFO si{ + .cbSize = sizeof(SCROLLINFO), + .fMask = static_cast(state.isAltBuffer ? SIF_ALL | SIF_DISABLENOSCROLL : SIF_ALL), + }; + + si.nMax = state.maxSize.width; + si.nPage = state.viewport.width(); + si.nPos = state.viewport.left; + SetScrollInfo(_hWnd, SB_HORZ, &si, TRUE); + + si.nMax = state.maxSize.height; + si.nPage = state.viewport.height(); + si.nPos = state.viewport.top; + SetScrollInfo(_hWnd, SB_VERT, &si, TRUE); } // Routine Description: diff --git a/src/interactivity/win32/window.hpp b/src/interactivity/win32/window.hpp index 45db4e7fbe6..7550b18c9d8 100644 --- a/src/interactivity/win32/window.hpp +++ b/src/interactivity/win32/window.hpp @@ -65,12 +65,7 @@ namespace Microsoft::Console::Interactivity::Win32 void HorizontalScroll(const WORD wScrollCommand, const WORD wAbsoluteChange); - BOOL EnableBothScrollBars(); - int UpdateScrollBar(bool isVertical, - bool isAltBuffer, - UINT pageSize, - int maxSize, - int viewportPosition); + void UpdateScrollBars(const SCREEN_INFORMATION::ScrollBarState& state); void UpdateWindowSize(const til::size coordSizeInChars); void UpdateWindowPosition(_In_ const til::point ptNewPos) const; @@ -185,6 +180,7 @@ namespace Microsoft::Console::Interactivity::Win32 static void s_ReinitializeFontsForDPIChange(); + WORD _resizingWindow = 0; // > 0 if we should ignore WM_SIZE messages bool _fInDPIChange = false; static void s_ConvertWindowPosToWindowRect(const LPWINDOWPOS lpWindowPos, diff --git a/src/interactivity/win32/windowproc.cpp b/src/interactivity/win32/windowproc.cpp index f215a166119..37def3100da 100644 --- a/src/interactivity/win32/windowproc.cpp +++ b/src/interactivity/win32/windowproc.cpp @@ -669,7 +669,16 @@ using namespace Microsoft::Console::Types; case CM_UPDATE_SCROLL_BARS: { - ScreenInfo.InternalUpdateScrollBars(); + const auto state = ScreenInfo.FetchScrollBarState(); + + // EnableScrollbar() and especially SetScrollInfo() are prohibitively expensive functions nowadays. + // Unlocking early here improves throughput of good old `type` in cmd.exe by ~10x. + UnlockConsole(); + Unlock = FALSE; + + _resizingWindow++; + UpdateScrollBars(state); + _resizingWindow--; break; } @@ -780,7 +789,7 @@ void Window::_HandleWindowPosChanged(const LPARAM lParam) // CONSOLE_IS_ICONIC bit appropriately. doing so in the WM_SIZE handler is incorrect because the WM_SIZE // comes after the WM_ERASEBKGND during SetWindowPos() processing, and the WM_ERASEBKGND needs to know if // the console window is iconic or not. - if (!ScreenInfo.ResizingWindow && (lpWindowPos->cx || lpWindowPos->cy) && !IsIconic(hWnd)) + if (!_resizingWindow && (lpWindowPos->cx || lpWindowPos->cy) && !IsIconic(hWnd)) { // calculate the dimensions for the newly proposed window rectangle til::rect rcNew; From 5f5ef10571a17af4feeafd6be8729085fe5a4ac0 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 5 Dec 2023 20:37:58 +0100 Subject: [PATCH 099/167] Fix ConPTY inputs incorrectly being treated as plain text (#16352) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is my proposal to avoid aborting ConPTY input parsing because a read accidentally got split up into more than one chunk. This happens a lot with WSL for me, as I often get (for instance) a `\x1b[67;46;99;0;32;` input followed immediately by a `1_` input. The current logic would cause both of these to be flushed out to the client application. This PR fixes the issue by only flushing either a standalone escape character or a escape+character combination. It basically limits the previous code to just `VTStates::Ground` and `VTStates::Escape`. I'm not using the `_state` member, because `VTStates::OscParam` makes no distinction between `\x1b]` and `\x1b]1234` and I only want to flush the former. I felt like checking the contents of `run` directly is easier to understand. Related to #16343 ## Validation Steps Performed * win32-input-mode sequences are now properly buffered ✅ * Standalone alt-key combinations are still being flushed ✅ --- src/terminal/parser/stateMachine.cpp | 70 ++++++------------- .../parser/ut_parser/InputEngineTest.cpp | 14 ++++ 2 files changed, 34 insertions(+), 50 deletions(-) diff --git a/src/terminal/parser/stateMachine.cpp b/src/terminal/parser/stateMachine.cpp index eb22ff95382..643409afda5 100644 --- a/src/terminal/parser/stateMachine.cpp +++ b/src/terminal/parser/stateMachine.cpp @@ -2127,6 +2127,8 @@ void StateMachine::ProcessString(const std::wstring_view string) // If we're at the end of the string and have remaining un-printed characters, if (_state != VTStates::Ground) { + const auto run = _CurrentRun(); + // One of the "weird things" in VT input is the case of something like // alt+[. In VT, that's encoded as `\x1b[`. However, that's // also the start of a CSI, and could be the start of a longer sequence, @@ -2136,60 +2138,28 @@ void StateMachine::ProcessString(const std::wstring_view string) // alt+[, A would be processed like `\x1b[A`, // which is _wrong_). // - // Fortunately, for VT input, each keystroke comes in as an individual - // write operation. So, if at the end of processing a string for the - // InputEngine, we find that we're not in the Ground state, that implies - // that we've processed some input, but not dispatched it yet. This - // block at the end of `ProcessString` will then re-process the - // undispatched string, but it will ensure that it dispatches on the - // last character of the string. For our previous `\x1b[` scenario, that - // means we'll make sure to call `_ActionEscDispatch('[')`., which will - // properly decode the string as alt+[. - const auto run = _CurrentRun(); - + // At the same time, input may be broken up arbitrarily, depending on the pipe's + // buffer size, our read-buffer size, the sender's write-buffer size, and more. + // In fact, with the current WSL, input is broken up in 16 byte chunks (Why? :(), + // which breaks up many of our longer sequences, like our Win32InputMode ones. + // + // As a heuristic, this code specifically checks for a trailing Esc or Alt+key. if (_isEngineForInput) { - // Reset our state, and put all but the last char in again. - ResetState(); - _processingLastCharacter = false; - // Chars to flush are [pwchSequenceStart, pwchCurr) - auto wchIter = run.cbegin(); - while (wchIter < run.cend() - 1) - { - ProcessCharacter(*wchIter); - wchIter++; - } - // Manually execute the last char [pwchCurr] - _processingLastCharacter = true; - switch (_state) + if (run.size() <= 2 && run.front() == L'\x1b') { - case VTStates::Ground: - _ActionExecute(*wchIter); - break; - case VTStates::Escape: - case VTStates::EscapeIntermediate: - _ActionEscDispatch(*wchIter); - break; - case VTStates::CsiEntry: - case VTStates::CsiIntermediate: - case VTStates::CsiIgnore: - case VTStates::CsiParam: - case VTStates::CsiSubParam: - _ActionCsiDispatch(*wchIter); - break; - case VTStates::OscParam: - case VTStates::OscString: - case VTStates::OscTermination: - _ActionOscDispatch(*wchIter); - break; - case VTStates::Ss3Entry: - case VTStates::Ss3Param: - _ActionSs3Dispatch(*wchIter); - break; + _EnterGround(); + if (run.size() == 1) + { + _ActionExecute(L'\x1b'); + } + else + { + _EnterEscape(); + _ActionEscDispatch(run.back()); + } + _EnterGround(); } - // microsoft/terminal#2746: Make sure to return to the ground state - // after dispatching the characters - _EnterGround(); } else if (_state != VTStates::SosPmApcString && _state != VTStates::DcsPassThrough && _state != VTStates::DcsIgnore) { diff --git a/src/terminal/parser/ut_parser/InputEngineTest.cpp b/src/terminal/parser/ut_parser/InputEngineTest.cpp index 2e21c77c74f..fcb23734be5 100644 --- a/src/terminal/parser/ut_parser/InputEngineTest.cpp +++ b/src/terminal/parser/ut_parser/InputEngineTest.cpp @@ -260,6 +260,7 @@ class Microsoft::Console::VirtualTerminal::InputEngineTest TEST_METHOD(AltCtrlDTest); TEST_METHOD(AltIntermediateTest); TEST_METHOD(AltBackspaceEnterTest); + TEST_METHOD(ChunkedSequence); TEST_METHOD(SGRMouseTest_ButtonClick); TEST_METHOD(SGRMouseTest_Modifiers); TEST_METHOD(SGRMouseTest_Movement); @@ -1041,6 +1042,19 @@ void InputEngineTest::AltBackspaceEnterTest() VerifyExpectedInputDrained(); } +void InputEngineTest::ChunkedSequence() +{ + // This test ensures that a DSC sequence that's split up into multiple chunks isn't + // confused with a single Alt+key combination like in the AltBackspaceEnterTest(). + // Basically, it tests the selectivity of the AltBackspaceEnterTest() fix. + + auto dispatch = std::make_unique(nullptr, nullptr); + auto inputEngine = std::make_unique(std::move(dispatch)); + StateMachine stateMachine{ std::move(inputEngine) }; + stateMachine.ProcessString(L"\x1b[1"); + VERIFY_ARE_EQUAL(StateMachine::VTStates::CsiParam, stateMachine._state); +} + // Method Description: // - Writes an SGR VT sequence based on the necessary parameters // Arguments: From f9652983f1e3d5cf44305d571cca2b99bf710d49 Mon Sep 17 00:00:00 2001 From: debghs <145260557+debghs@users.noreply.github.com> Date: Wed, 6 Dec 2023 03:09:00 +0530 Subject: [PATCH 100/167] Minor grammar fixes for the vintage AddASetting.md doc (#16188) ## Summary of the Pull Request Added some Punctuation Marks as Required. ## References and Relevant Issues None. ## Detailed Description of the Pull Request / Additional comments There were some missing Punctuation Marks(Ex: Colon(:) and Full Stop(.)), so I have added them. ## Validation Steps Performed --- doc/AddASetting.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/AddASetting.md b/doc/AddASetting.md index 2d4c414d910..59128125d71 100644 --- a/doc/AddASetting.md +++ b/doc/AddASetting.md @@ -5,10 +5,10 @@ `.../console/published/wincon.w` in the OS repo when you submit the PR. The branch won't build without it. * For now, you can update winconp.h with your consumable changes. - * Define registry name (ex `CONSOLE_REGISTRY_CURSORCOLOR`) - * Add the setting to `CONSOLE_STATE_INFO` + * Define registry name (ex: `CONSOLE_REGISTRY_CURSORCOLOR`) + * Add the setting to `CONSOLE_STATE_INFO`. * Define the property key ID and the property key itself. - - Yes, the large majority of the `DEFINE_PROPERTYKEY` defs are the same, it's only the last byte of the guid that changes + - Yes, the large majority of the `DEFINE_PROPERTYKEY` defs are the same, it's only the last byte of the guid that changes. 2. Add matching fields to Settings.hpp - Add getters, setters, the whole drill. @@ -17,9 +17,9 @@ - We need to add it to *reading and writing* the registry from the propsheet, and *reading* the link from the propsheet. Yes, that's weird, but the propsheet is smart enough to re-use ShortcutSerialization::s_SetLinkValues, but not smart enough to do the same with RegistrySerialization. - `src/propsheet/registry.cpp` - `propsheet/registry.cpp@InitRegistryValues` should initialize the default value for the property. - - `propsheet/registry.cpp@GetRegistryValues` should make sure to read the property from the registry + - `propsheet/registry.cpp@GetRegistryValues` should make sure to read the property from the registry. -4. Add the field to the propslib registry map +4. Add the field to the propslib registry map. 5. Add the value to `ShortcutSerialization.cpp` - Read the value in `ShortcutSerialization::s_PopulateV2Properties` @@ -30,11 +30,11 @@ Now, your new setting should be stored just like all the other properties. 7. Update the feature test properties to get add the setting as well - `ft_uia/Common/NativeMethods.cs@WinConP`: - - `Wtypes.PROPERTYKEY PKEY_Console_` - - `NT_CONSOLE_PROPS` + - `Wtypes.PROPERTYKEY PKEY_Console_`. + - `NT_CONSOLE_PROPS`. 8. Add the default value for the setting to `win32k-settings.man` - If the setting shouldn't default to 0 or `nullptr`, then you'll need to set the default value of the setting in `win32k-settings.man`. -9. Update `Settings::InitFromStateInfo` and `Settings::CreateConsoleStateInfo` to get/set the value in a CONSOLE_STATE_INFO appropriately +9. Update `Settings::InitFromStateInfo` and `Settings::CreateConsoleStateInfo` to get/set the value in a CONSOLE_STATE_INFO appropriately. From 9967851bf83dc1edf1acb510a8535d8b0bca9a6b Mon Sep 17 00:00:00 2001 From: Jvr <109031036+Jvr2022@users.noreply.github.com> Date: Tue, 5 Dec 2023 23:30:21 +0100 Subject: [PATCH 101/167] Update xamlstyler to 3.2311.2 (#16422) Update xalmstyler to 3.2311.2 --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index e56b4d46c55..4479fcd4875 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "XamlStyler.Console": { - "version": "3.2206.4", + "version": "3.2311.2", "commands": [ "xstyler" ] From 65d2d3dcec78e1ff075cc0ae383f385a0771eb20 Mon Sep 17 00:00:00 2001 From: Jvr <109031036+Jvr2022@users.noreply.github.com> Date: Tue, 5 Dec 2023 23:31:52 +0100 Subject: [PATCH 102/167] Update actions/add-to-project to version 0.5.0 (#16084) Update actions/add-to-project to version 0.5.0 --- .github/workflows/addToProject.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/addToProject.yml b/.github/workflows/addToProject.yml index 3fbb2a7b2cb..4baa537dd19 100644 --- a/.github/workflows/addToProject.yml +++ b/.github/workflows/addToProject.yml @@ -7,13 +7,13 @@ on: - labeled - unlabeled -permissions: {} +permissions: {} jobs: add-to-project: name: Add issue to project runs-on: ubuntu-latest steps: - - uses: actions/add-to-project@v0.3.0 + - uses: actions/add-to-project@v0.5.0 with: project-url: https://github.com/orgs/microsoft/projects/159 github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} From dc986e44898c0927010b258dcfc06986a3a9885e Mon Sep 17 00:00:00 2001 From: Josh Soref <2119212+jsoref@users.noreply.github.com> Date: Tue, 5 Dec 2023 18:40:23 -0500 Subject: [PATCH 103/167] Check spelling 0.0.22 (#16127) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Upgrades check-spelling to [v0.0.22](https://github.com/check-spelling/check-spelling/releases/tag/v0.0.22) * refreshes workflow * enables dependabot PRs to trigger CI (so that in the future you'll be able to see breaking changes to the dictionary paths) * refreshes metadata * built-in handling of `\n`/`\r`/`\t` is removed -- This means that the `patterns/0_*.txt` files can be removed. * this specific PR includes some shim content, in `allow/check-spelling-0.0.21.txt` -- once it this PR merges, it can be removed on a branch and the next CI will clean out items from `expect.txt` relating to the `\r` stuff and suggest replacement content. * talking to the bot is enabled for forks (but not the master repository) * SARIF reporting is enabled for PRs w/in a single repository (not across forks) * In job reports, there's a summary table (space permitting) linking to instances (this is a poor man's SARIF report) * When a pattern splits a thing that results in check-spelling finding an unrecognized token, that's reported with a distinct category * When there are items in expect that not longer match anything but more specific items do (e.g. `microsoft` vs. `Microsoft`), there's now a specific category with help/advice * Fancier excludes suggestions (excluding directories, file types, ...) * Refreshed dictionaries * The comment now links to the job summary (which includes SARIF link if available, the details view, and a generated commit that people can use if they're ok w/ the expect changes and don't want to run perl) Validation ---------- 1. the branch was developed in https://github.com/check-spelling-sandbox/terminal/actions?query=branch%3Acheck-spelling-0.0.22 2. ensuring compatibility with 0.0.21 was done in https://github.com/check-spelling-sandbox/terminal/pull/3 3. this version has been in development for a year and has quite a few improvements, we've been actively dogfooding it throughout this period 😄 Additional Fixes ---------------- spelling: the spelling: shouldn't spelling: no spelling: macos spelling: github spelling: fine-grained spelling: coarse-grained Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> --- .github/actions/spelling/advice.md | 6 +- .github/actions/spelling/allow/allow.txt | 3 + .../spelling/allow/check-spelling-0.0.21.txt | 224 +++++++++++++++++ .github/actions/spelling/allow/names.txt | 1 + .github/actions/spelling/candidate.patterns | 200 ++++++++++++--- .github/actions/spelling/excludes.txt | 59 +++-- .github/actions/spelling/expect/expect.txt | 227 ++++++------------ .github/actions/spelling/expect/web.txt | 1 - .../actions/spelling/line_forbidden.patterns | 73 +++++- .github/actions/spelling/patterns/0_n.txt | 2 - .github/actions/spelling/patterns/0_r.txt | 8 - .github/actions/spelling/patterns/0_t.txt | 13 - .github/actions/spelling/patterns/README.md | 4 - .../actions/spelling/patterns/patterns.txt | 128 +++++++--- .github/actions/spelling/reject.txt | 1 + .github/workflows/similarIssues.yml | 10 +- .github/workflows/spelling2.yml | 113 +++++++-- build/pipelines/ci.yml | 2 +- .../templates-v2/job-index-github-codenav.yml | 2 +- doc/Niksa.md | 2 +- .../#2634 - Broadcast Input.md | 2 +- .../#3327 - Application Theming.md | 2 +- src/renderer/atlas/AtlasEngine.r.cpp | 2 +- src/renderer/atlas/BackendD2D.cpp | 2 +- src/renderer/atlas/BackendD3D.cpp | 2 +- 25 files changed, 753 insertions(+), 336 deletions(-) create mode 100644 .github/actions/spelling/allow/check-spelling-0.0.21.txt delete mode 100644 .github/actions/spelling/patterns/0_n.txt delete mode 100644 .github/actions/spelling/patterns/0_r.txt delete mode 100644 .github/actions/spelling/patterns/0_t.txt diff --git a/.github/actions/spelling/advice.md b/.github/actions/spelling/advice.md index d82df49ee22..536c601aec2 100644 --- a/.github/actions/spelling/advice.md +++ b/.github/actions/spelling/advice.md @@ -6,8 +6,6 @@ By default the command suggestion will generate a file named based on your commit. That's generally ok as long as you add the file to your commit. Someone can reorganize it later. -:warning: The command is written for posix shells. If it doesn't work for you, you can manually _add_ (one word per line) / _remove_ items to `expect.txt` and the `excludes.txt` files. - If the listed items are: * ... **misspelled**, then please *correct* them instead of using the command. @@ -36,7 +34,9 @@ https://www.regexplanet.com/advanced/perl/) yours before committing to verify it * well-formed pattern. - If you can write a [pattern](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns) that would match it, + If you can write a [pattern]( +https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns +) that would match it, try adding it to the `patterns.txt` file. Patterns are Perl 5 Regular Expressions - you can [test]( diff --git a/.github/actions/spelling/allow/allow.txt b/.github/actions/spelling/allow/allow.txt index bccfe086aeb..07eb75ab82d 100644 --- a/.github/actions/spelling/allow/allow.txt +++ b/.github/actions/spelling/allow/allow.txt @@ -55,6 +55,7 @@ hyperlinks iconify img inlined +issuetitle It'd kje libfuzzer @@ -95,6 +96,7 @@ rlig runtimes servicebus shcha +similaritytolerance slnt Sos ssh @@ -122,6 +124,7 @@ walkthroughs We'd westus wildcards +workarounds XBox YBox yeru diff --git a/.github/actions/spelling/allow/check-spelling-0.0.21.txt b/.github/actions/spelling/allow/check-spelling-0.0.21.txt new file mode 100644 index 00000000000..9463838f115 --- /dev/null +++ b/.github/actions/spelling/allow/check-spelling-0.0.21.txt @@ -0,0 +1,224 @@ +aarch +abi +activatable +aef +amd +applets +argb +ASDF +ative +ativecxapp +AVX +azzle +BGR +bigints +bignum +bitmask +bitmasks +blackhole +Bopomofo +BOTTOMLEFT +BOTTOMRIGHT +bsr +bstr +cbuffer +cci +changelist +changelists +Checkin +chk +cielab +cls +clz +CMake +cmt +Cmts +cmyk +cname +cnt +CNTRL +codepage +codepages +codepoints +colorspaces +COMDAT +configurability +cpuid +cred +Crt +dbg +distro +distros +django +DLOAD +ect +ectread +edist +egistry +elease +elemetry +elems +emacs +emark +emplates +enderer +endregion +endverbatim +eplace +erm +erminal +erminalcore +erminalinput +esource +esources +esthelper +estlist +estmd +estpasses +ests +esult +esultmacros +etfx +eturn +ewdelete +ewtonsoft +flyout +flyouts +FONTFAMILY +FONTSIZE +FONTWEIGHT +formattable +framebuffer +FSCTL +GAUSSIAN +getch +GETFONTSIZE +GETPOS +GNUC +groupbox +Gsub +GTR +HORZ +hread +hrottled +hrow +hsl +HTMLTo +icket +Iconified +idx +ihilist +Imm +inheritdoc +inlines +ioctl +keydown +keydowns +keyup +keyups +lnk +lss +MMIX +MOUSEMOVE +movl +movsb +msys +nameof +natvis +Newdelete +Newtonsoft +Nop +NOUPDATE +nullability +numpad +oklab +ools +OOM +osign +ote +otepad +ouicompat +passthrough +pcs +pgdn +pgup +pkey +pname +popcnt +prefs +prepopulated +ptrs +Pvf +qos +Rasterizer +RCDATA +rcl +rects +reparent +reparented +RGBCOLOR +roundtrips +RTTI +safearray +sapi +scancode +scancodes +scrollbars +Sdl +SETCOLOR +SETFONT +Shl +SND +srgb +Statusline +stl +stosb +strerror +strrev +subc +subfolders +subkey +subkeys +swappable +taskbar +tdll +testenv +testenvs +texel +TExpected +tioapi +titlebar +titlebars +TOPLEFT +TOPRIGHT +tprivapi +tsf +typelib +typeparam +UDM +uget +ules +umul +umulh +uninitialize +Unserialize +untimes +upkg +utc +vcpkg +vec +vectorized +vtable +vtables +WCAG +WINDOWTITLE +workaround +xchgl +xgetbv +XOFF +XON +YDPI +YOffset +ype +ypes +ZIndex diff --git a/.github/actions/spelling/allow/names.txt b/.github/actions/spelling/allow/names.txt index 0409931d020..11a1f95486b 100644 --- a/.github/actions/spelling/allow/names.txt +++ b/.github/actions/spelling/allow/names.txt @@ -6,6 +6,7 @@ bhoj Bhojwani Bluloco carlos +craigloewen dhowett Diviness dsafa diff --git a/.github/actions/spelling/candidate.patterns b/.github/actions/spelling/candidate.patterns index 400f103ea28..80f31f92e4f 100644 --- a/.github/actions/spelling/candidate.patterns +++ b/.github/actions/spelling/candidate.patterns @@ -1,23 +1,37 @@ # marker to ignore all code on line ^.*/\* #no-spell-check-line \*/.*$ -# marker for ignoring a comment to the end of the line -// #no-spell-check.*$ +# marker to ignore all code on line +^.*\bno-spell-check(?:-line|)(?:\s.*|)$ + +# https://cspell.org/configuration/document-settings/ +# cspell inline +^.*\b[Cc][Ss][Pp][Ee][Ll]{2}:\s*[Dd][Ii][Ss][Aa][Bb][Ll][Ee]-[Ll][Ii][Nn][Ee]\b # patch hunk comments ^\@\@ -\d+(?:,\d+|) \+\d+(?:,\d+|) \@\@ .* # git index header -index [0-9a-z]{7,40}\.\.[0-9a-z]{7,40} +index (?:[0-9a-z]{7,40},|)[0-9a-z]{7,40}\.\.[0-9a-z]{7,40} + +# file permissions +['"`\s][-bcdLlpsw](?:[-r][-w][-Ssx]){2}[-r][-w][-SsTtx]\+?['"`\s] + +# css url wrappings +\burl\([^)]+\) # cid urls (['"])cid:.*?\g{-1} # data url in parens -\(data:[^)]*?(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})[^)]*\) +#\(data:(?:[^) ][^)]*?|)(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})[^)]*\) + # data url in quotes -([`'"])data:.*?(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,}).*\g{-1} +([`'"])data:(?:[^ `'"].*?|)(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,}).*\g{-1} # data url data:[-a-zA-Z=;:/0-9+]*,\S* +# https/http/file urls +(?:\b(?:https?|ftp|file)://)[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|] + # mailto urls mailto:[-a-zA-Z=;:/?%&0-9+@.]{3,} @@ -35,6 +49,9 @@ magnet:[?=:\w]+ # asciinema \basciinema\.org/a/[0-9a-zA-Z]+ +# asciinema v2 +^\[\d+\.\d+, "[io]", ".*"\]$ + # apple \bdeveloper\.apple\.com/[-\w?=/]+ # Apple music @@ -89,7 +106,7 @@ vpc-\w+ # Google Drive \bdrive\.google\.com/(?:file/d/|open)[-0-9a-zA-Z_?=]* # Google Groups -\bgroups\.google\.com/(?:(?:forum/#!|d/)(?:msg|topics?|searchin)|a)/[^/\s"]+/[-a-zA-Z0-9$]+(?:/[-a-zA-Z0-9]+)* +\bgroups\.google\.com(?:/[a-z]+/(?:#!|)[^/\s"]+)* # Google Maps \bmaps\.google\.com/maps\?[\w&;=]* # Google themes @@ -117,6 +134,8 @@ themes\.googleusercontent\.com/static/fonts/[^/\s"]+/v\d+/[^.]+. (?:\[`?[0-9a-f]+`?\]\(https:/|)/(?:www\.|)github\.com(?:/[^/\s"]+){2,}(?:/[^/\s")]+)(?:[0-9a-f]+(?:[-0-9a-zA-Z/#.]*|)\b|) # GitHub SHAs \bgithub\.com(?:/[^/\s"]+){2}[@#][0-9a-f]+\b +# GitHub SHA refs +\[([0-9a-f]+)\]\(https://(?:www\.|)github.com/[-\w]+/[-\w]+/commit/\g{-1}[0-9a-f]* # GitHub wiki \bgithub\.com/(?:[^/]+/){2}wiki/(?:(?:[^/]+/|)_history|[^/]+(?:/_compare|)/[0-9a-f.]{40,})\b # githubusercontent @@ -128,9 +147,9 @@ themes\.googleusercontent\.com/static/fonts/[^/\s"]+/v\d+/[^.]+. # git.io \bgit\.io/[0-9a-zA-Z]+ # GitHub JSON -"node_id": "[-a-zA-Z=;:/0-9+]*" +"node_id": "[-a-zA-Z=;:/0-9+_]*" # Contributor -\[[^\]]+\]\(https://github\.com/[^/\s"]+\) +\[[^\]]+\]\(https://github\.com/[^/\s"]+/?\) # GHSA GHSA(?:-[0-9a-z]{4}){3} @@ -143,8 +162,8 @@ GHSA(?:-[0-9a-z]{4}){3} # GitLab commits \bgitlab\.[^/\s"]*/(?:[^/\s"]+/){2}commits?/[0-9a-f]+\b -# binanace -accounts.binance.com/[a-z/]*oauth/authorize\?[-0-9a-zA-Z&%]* +# binance +accounts\.binance\.com/[a-z/]*oauth/authorize\?[-0-9a-zA-Z&%]* # bitbucket diff \bapi\.bitbucket\.org/\d+\.\d+/repositories/(?:[^/\s"]+/){2}diff(?:stat|)(?:/[^/\s"]+){2}:[0-9a-f]+ @@ -280,9 +299,9 @@ slack://[a-zA-Z0-9?&=]+ \bdropbox\.com/sh?/[^/\s"]+/[-0-9A-Za-z_.%?=&;]+ # ipfs protocol -ipfs://[0-9a-z]* +ipfs://[0-9a-zA-Z]{3,} # ipfs url -/ipfs/[0-9a-z]* +/ipfs/[0-9a-zA-Z]{3,} # w3 \bw3\.org/[-0-9a-zA-Z/#.]+ @@ -359,22 +378,33 @@ ipfs://[0-9a-z]* # tinyurl \btinyurl\.com/\w+ +# codepen +\bcodepen\.io/[\w/]+ + +# registry.npmjs.org +\bregistry\.npmjs\.org/(?:@[^/"']+/|)[^/"']+/-/[-\w@.]+ + # getopts \bgetopts\s+(?:"[^"]+"|'[^']+') # ANSI color codes -(?:\\(?:u00|x)1b|\x1b)\[\d+(?:;\d+|)m +(?:\\(?:u00|x)1[Bb]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+|)m # URL escaped characters -\%[0-9A-F][A-F] +\%[0-9A-F][A-F](?=[A-Za-z]) +# lower URL escaped characters +\%[0-9a-f][a-f](?=[a-z]{2,}) # IPv6 -\b(?:[0-9a-fA-F]{0,4}:){3,7}[0-9a-fA-F]{0,4}\b +#\b(?:[0-9a-fA-F]{0,4}:){3,7}[0-9a-fA-F]{0,4}\b + # c99 hex digits (not the full format, just one I've seen) 0x[0-9a-fA-F](?:\.[0-9a-fA-F]*|)[pP] +# Punycode +\bxn--[-0-9a-z]+ # sha sha\d+:[0-9]*[a-f]{3,}[0-9a-f]* # sha-... -- uses a fancy capture -(['"]|")[0-9a-f]{40,}\g{-1} +(\\?['"]|")[0-9a-f]{40,}\g{-1} # hex runs \b[0-9a-fA-F]{16,}\b # hex in url queries @@ -389,18 +419,21 @@ sha\d+:[0-9]*[a-f]{3,}[0-9a-f]* # Well known gpg keys .well-known/openpgpkey/[\w./]+ +# pki +-----BEGIN.*-----END + # uuid: \b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b # hex digits including css/html color classes: -(?:[\\0][xX]|\\u|[uU]\+|#x?|\%23)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|u\d+)\b +(?:[\\0][xX]|\\u|[uU]\+|#x?|\%23)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|[iu]\d+)\b # integrity -integrity="sha\d+-[-a-zA-Z=;:/0-9+]{40,}" +integrity=(['"])(?:\s*sha\d+-[-a-zA-Z=;:/0-9+]{40,})+\g{-1} # https://www.gnu.org/software/groff/manual/groff.html # man troff content \\f[BCIPR] -# ' -\\\(aq +# '/" +\\\([ad]q # .desktop mime types ^MimeTypes?=.*$ @@ -409,21 +442,33 @@ integrity="sha\d+-[-a-zA-Z=;:/0-9+]{40,}" # Localized .desktop content Name\[[^\]]+\]=.* -# IServiceProvider -\bI(?=(?:[A-Z][a-z]{2,})+\b) +# IServiceProvider / isAThing +\b(?:I|isA)(?=(?:[A-Z][a-z]{2,})+\b) # crypt -"\$2[ayb]\$.{56}" +(['"])\$2[ayb]\$.{56}\g{-1} # scrypt / argon \$(?:scrypt|argon\d+[di]*)\$\S+ +# go.sum +\bh1:\S+ + +# scala modules +("[^"]+"\s*%%?\s*){2,3}"[^"]+" + # Input to GitHub JSON -content: "[-a-zA-Z=;:/0-9+]*=" +content: (['"])[-a-zA-Z=;:/0-9+]*=\g{-1} -# Python stringprefix / binaryprefix +# This does not cover multiline strings, if your repository has them, +# you'll want to remove the `(?=.*?")` suffix. +# The `(?=.*?")` suffix should limit the false positives rate +# printf +#%(?:(?:(?:hh?|ll?|[jzt])?[diuoxn]|l?[cs]|L?[fega]|p)(?=[a-z]{2,})|(?:X|L?[FEGA]|p)(?=[a-zA-Z]{2,}))(?=[_a-zA-Z]+\b)(?!%)(?=.*?['"]) + +# Python string prefix / binary prefix # Note that there's a high false positive rate, remove the `?=` and search for the regex to see if the matches seem like reasonable strings -(?|m([|!/@#,;']).*?\g{-1}) + +# perl qr regex +(?|\(.*?\)|([|!/@#,;']).*?\g{-1}) # Go regular expressions regexp?\.MustCompile\(`[^`]*`\) +# regex choice +\(\?:[^)]+\|[^)]+\) + +# proto +^\s*(\w+)\s\g{-1} = + # sed regular expressions sed 's/(?:[^/]*?[a-zA-Z]{3,}[^/]*?/){2} +# node packages +(["'])\@[^/'" ]+/[^/'" ]+\g{-1} + # go install go install(?:\s+[a-z]+\.[-@\w/.]+)+ +# jetbrains schema https://youtrack.jetbrains.com/issue/RSRP-489571 +urn:shemas-jetbrains-com + # kubernetes pod status lists # https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase \w+(?:-\w+)+\s+\d+/\d+\s+(?:Running|Pending|Succeeded|Failed|Unknown)\s+ @@ -460,19 +524,47 @@ go install(?:\s+[a-z]+\.[-@\w/.]+)+ -[0-9a-f]{10}-\w{5}\s # posthog secrets -posthog\.init\((['"])phc_[^"',]+\g{-1}, +([`'"])phc_[^"',]+\g{-1} # xcode # xcodeproject scenes -(?:Controller|ID|id)="\w{3}-\w{2}-\w{3}" +(?:Controller|destination|ID|id)="\w{3}-\w{2}-\w{3}" # xcode api botches customObjectInstantitationMethod +# configure flags +.* \| --\w{2,}.*?(?=\w+\s\w+) + # font awesome classes \.fa-[-a-z0-9]+ +# bearer auth +(['"])Bear[e][r] .*?\g{-1} + +# basic auth +(['"])Basic [-a-zA-Z=;:/0-9+]{3,}\g{-1} + +# base64 encoded content +#([`'"])[-a-zA-Z=;:/0-9+]+=\g{-1} +# base64 encoded content in xml/sgml +>[-a-zA-Z=;:/0-9+]+== 0.0.22) +\\\w{2,}\{ + +# eslint +"varsIgnorePattern": ".+" + +# Windows short paths +[/\\][^/\\]{5,6}~\d{1,2}[/\\] + +# in check-spelling@v0.0.22+, printf markers aren't automatically consumed +# printf markers +#(?v# (?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_])) -# Compiler flags (Scala) -(?:^|[\t ,>"'`=(])-J-[DPWXY](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}) -# Compiler flags -#(?:^|[\t ,"'`=(])-[DPWXYLlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}) + +# Compiler flags (Unix, Java/Scala) +# Use if you have things like `-Pdocker` and want to treat them as `docker` +#(?:^|[\t ,>"'`=(])-(?:(?:J-|)[DPWXY]|[Llf])(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}) + +# Compiler flags (Windows / PowerShell) +# This is a subset of the more general compiler flags pattern. +# It avoids matching `-Path` to prevent it from being treated as `ath` +#(?:^|[\t ,"'`=(])-(?:[DPL](?=[A-Z]{2,})|[WXYlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})) # Compiler flags (linker) ,-B + # curl arguments \b(?:\\n|)curl(?:\s+-[a-zA-Z]{1,2}\b)*(?:\s+-[a-zA-Z]{3,})(?:\s+-[a-zA-Z]+)* # set arguments diff --git a/.github/actions/spelling/excludes.txt b/.github/actions/spelling/excludes.txt index 441d1f295ca..57d475dc876 100644 --- a/.github/actions/spelling/excludes.txt +++ b/.github/actions/spelling/excludes.txt @@ -1,21 +1,24 @@ # See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-excludes -(?:(?i)\.png$) (?:^|/)(?i)COPYRIGHT (?:^|/)(?i)LICEN[CS]E (?:^|/)3rdparty/ (?:^|/)dirs$ -(?:^|/)go\.mod$ (?:^|/)go\.sum$ (?:^|/)package(?:-lock|)\.json$ +(?:^|/)Pipfile$ +(?:^|/)pyproject.toml +(?:^|/)requirements(?:-dev|-doc|-test|)\.txt$ (?:^|/)sources(?:|\.dep)$ (?:^|/)vendor/ \.a$ \.ai$ +\.all-contributorsrc$ \.avi$ \.bmp$ \.bz2$ \.cer$ \.class$ +\.coveragerc$ \.crl$ \.crt$ \.csr$ @@ -27,11 +30,15 @@ \.eps$ \.exe$ \.gif$ +\.git-blame-ignore-revs$ \.gitattributes$ +\.gitignore$ +\.gitkeep$ \.graffle$ \.gz$ \.icns$ \.ico$ +\.ipynb$ \.jar$ \.jks$ \.jpeg$ @@ -41,61 +48,62 @@ \.lock$ \.map$ \.min\.. +\.mo$ \.mod$ \.mp3$ \.mp4$ \.o$ \.ocf$ \.otf$ +\.p12$ +\.parquet$ \.pbxproj$ \.pdf$ \.pem$ +\.pfx$ \.png$ \.psd$ \.pyc$ +\.pylintrc$ +\.qm$ \.runsettings$ \.s$ \.sig$ \.so$ \.svg$ \.svgz$ -\.svgz?$ +\.sys$ \.tar$ \.tgz$ \.tiff?$ \.ttf$ +\.vcxproj\.filters$ \.vsdx$ \.wav$ \.webm$ \.webp$ \.woff -\.woff2?$ \.xcf$ -\.xls \.xlsx?$ \.xpm$ -\.yml$ +\.xz$ \.zip$ ^\.github/actions/spelling/ -^\.github/fabricbot.json$ -^\.gitignore$ -^\Q.git-blame-ignore-revs\E$ ^\Q.github/workflows/spelling.yml\E$ -^\Qdoc/reference/windows-terminal-logo.ans\E$ -^\Qsamples/ConPTY/EchoCon/EchoCon/EchoCon.vcxproj.filters\E$ -^\Qsrc/host/exe/Host.EXE.vcxproj.filters\E$ +^\Qbuild/config/release.gdnbaselines\E$ ^\Qsrc/host/ft_host/chafa.txt\E$ -^\Qsrc/tools/closetest/CloseTest.vcxproj.filters\E$ -^\XamlStyler.json$ +^\Qsrc/host/ft_uia/run.bat\E$ +^\Qsrc/host/runft.bat\E$ +^\Qsrc/tools/lnkd/lnkd.bat\E$ +^\Qsrc/tools/pixels/pixels.bat\E$ ^build/config/ ^consolegit2gitfilters\.json$ ^dep/ -^doc/reference/master-sequence-list.csv$ +^doc/reference/master-sequence-list\.csv$ ^doc/reference/UTF8-torture-test\.txt$ +^doc/reference/windows-terminal-logo\.ans$ ^oss/ -^src/host/ft_uia/run\.bat$ -^src/host/runft\.bat$ -^src/host/runut\.bat$ +^samples/PixelShaders/Screenshots/ ^src/interactivity/onecore/BgfxEngine\. ^src/renderer/atlas/ ^src/renderer/wddmcon/WddmConRenderer\. @@ -107,14 +115,13 @@ ^src/terminal/parser/ut_parser/Base64Test.cpp$ ^src/terminal/parser/ut_parser/run\.bat$ ^src/tools/benchcat +^src/tools/integrity/dirs$ ^src/tools/integrity/packageuwp/ConsoleUWP\.appxSources$ -^src/tools/lnkd/lnkd\.bat$ -^src/tools/pixels/pixels\.bat$ -^src/tools/RenderingTests/main.cpp$ +^src/tools/RenderingTests/main\.cpp$ ^src/tools/texttests/fira\.txt$ -^src/tools/U8U16Test/(?:fr|ru|zh)\.txt$ -^src/types/ColorFix.cpp -^src/types/ut_types/UtilsTests.cpp$ -^tools/ReleaseEngineering/ServicingPipeline.ps1$ +^src/tools/U8U16Test/(?!en)..\. +^src/types/ColorFix\.cpp$ +^src/types/ut_types/UtilsTests\.cpp$ +^tools/ReleaseEngineering/ServicingPipeline\.ps1$ +^XamlStyler\.json$ ignore$ -SUMS$ diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index f38eddb9d3f..2de1494a028 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -1,18 +1,16 @@ aabbcc -aarch ABANDONFONT abbcc ABCDEFGHIJKLMNOPQRSTUVWXY ABCF abgr -abi ABORTIFHUNG +ACCESSTOKEN acidev ACIOSS ACover actctx ACTCTXW -activatable ADDALIAS ADDREF ADDSTRING @@ -32,7 +30,7 @@ ALTERNATENAME ALTF ALTNUMPAD ALWAYSTIP -amd +aml ansicpg ANSISYS ANSISYSRC @@ -50,25 +48,23 @@ APPBARDATA appcontainer appium appletname -applets applicationmodel APPLMODAL appmodel APPWINDOW +APPXMANIFESTVERSION APrep apsect APSTUDIO archeologists -argb +Argb ARRAYSIZE ARROWKEYS asan ASBSET -ASDF asdfghjkl ASetting ASingle -asmx ASYNCWINDOWPOS atch ATest @@ -85,9 +81,8 @@ AUTORADIOBUTTON autoscrolling Autowrap AVerify -AVX awch -azzle +azurecr backgrounded Backgrounder backgrounding @@ -107,15 +102,16 @@ benchcat bgfx bgidx Bgk -BGR bgra BHID bigobj +binlog binplace binplaced +binskim bitcoin bitcrazed -bitmask +bitmasks BITOPERATION BKCOLOR BKGND @@ -126,10 +122,7 @@ BODGY BOLDFONT BOOLIFY bools -Bopomofo Borland -BOTTOMLEFT -BOTTOMRIGHT boutput boxheader BPBF @@ -139,7 +132,6 @@ branchconfig brandings Browsable Bspace -bstr BTNFACE bufferout buffersize @@ -162,11 +154,9 @@ cbiex CBN CBoolean cbt -cbuffer CCCBB cch CCHAR -cci CCmd ccolor CCom @@ -184,18 +174,15 @@ cfte CFuzz cgscrn chafa -changelist +changelists chaof charinfo CHARSETINFO chcbpat chh -chk chshdng CHT Cic -cielab -Cielab CLE cleartype CLICKACTIVE @@ -205,7 +192,7 @@ CLIPCHILDREN CLIPSIBLINGS closetest cloudconsole -cls +cloudvault CLSCTX clsids CLUSTERMAP @@ -214,23 +201,19 @@ cmder CMDEXT cmh CMOUSEBUTTONS -cmt +Cmts cmw -cmyk CNL cnn -cnt -CNTRL Codeflow -codepage +codenav +codepages codepath -codepoints coinit colorizing COLORMATRIX COLORREFs colorschemes -colorspaces colorspec colortable colortbl @@ -239,7 +222,6 @@ colortool COLR combaseapi comctl -COMDAT commandline commctrl commdlg @@ -256,7 +238,6 @@ conddkrefs condrv conechokey conemu -configurability conhost conime conimeinfo @@ -318,11 +299,10 @@ cpx CREATESCREENBUFFER CREATESTRUCT CREATESTRUCTW -cred +createvpack crisman CRLFs crloew -Crt CRTLIBS csbi csbiex @@ -379,7 +359,6 @@ DATABLOCK DBatch dbcs DBCSFONT -dbg DBGALL DBGCHARS DBGFONTS @@ -507,14 +486,13 @@ DISABLENOSCROLL DISPLAYATTRIBUTE DISPLAYATTRIBUTEPROPERTY DISPLAYCHANGE -distro +distros dlg DLGC DLLGETVERSIONPROC dllinit dllmain DLLVERSIONINFO -DLOAD DLOOK DONTCARE doskey @@ -564,7 +542,6 @@ ECH echokey ecount ECpp -ect Edgium EDITKEYS EDITTEXT @@ -576,13 +553,10 @@ EHsc EINS EJO ELEMENTNOTAVAILABLE -elems -emacs EMPTYBOX enabledelayedexpansion ENDCAP endptr -endregion ENTIREBUFFER entrypoints ENU @@ -590,10 +564,12 @@ ENUMLOGFONT ENUMLOGFONTEX enumranges EOK -eplace EPres EQU ERASEBKGND +ESFCIB +esrp +ESV ETW EUDC EVENTID @@ -648,7 +624,7 @@ FIter FIXEDCONVERTED FIXEDFILEINFO Flg -flyout +flyouts fmodern fmtarg fmtid @@ -658,27 +634,20 @@ fontdlg FONTENUMDATA FONTENUMPROC FONTFACE -FONTFAMILY FONTHEIGHT fontinfo FONTOK -FONTSIZE FONTSTRING -fonttbl FONTTYPE -FONTWEIGHT FONTWIDTH FONTWINDOW -fooo FORCEOFFFEEDBACK FORCEONFEEDBACK -framebuffer FRAMECHANGED fre frontends fsanitize Fscreen -FSCTL FSINFOCLASS fte Ftm @@ -692,12 +661,12 @@ fuzzwrapper fwdecl fwe fwlink -GAUSSIAN gci gcx gdi gdip gdirenderer +gdnbaselines Geddy geopol GETALIAS @@ -707,7 +676,6 @@ GETALIASEXES GETALIASEXESLENGTH GETAUTOHIDEBAREX GETCARETWIDTH -getch GETCLIENTAREAANIMATION GETCOMMANDHISTORY GETCOMMANDHISTORYLENGTH @@ -723,7 +691,6 @@ GETDISPLAYSIZE GETDLGCODE GETDPISCALEDSIZE GETFONTINFO -GETFONTSIZE GETHARDWARESTATE GETHUNGAPPTIMEOUT GETICON @@ -738,7 +705,6 @@ GETMOUSEVANISH GETNUMBEROFFONTS GETNUMBEROFINPUTEVENTS GETOBJECT -GETPOS GETSELECTIONINFO getset GETTEXTLEN @@ -754,13 +720,14 @@ gfx GGI GHIJK GHIJKL +gitcheckin gitfilters +gitlab gitmodules gle GLOBALFOCUS GLYPHENTRY GMEM -GNUC Goldmine gonce goutput @@ -768,11 +735,9 @@ GREENSCROLL Grehan Greyscale gridline -groupbox gset gsl GTP -GTR guc guidatom GValue @@ -823,9 +788,9 @@ hmod hmodule hmon homoglyph -HORZ hostable hostlib +Hostx HPA hpcon HPCON @@ -835,9 +800,7 @@ HPR HProvider HREDRAW hresult -hrottled hscroll -hsl hstr hstring HTBOTTOMLEFT @@ -847,7 +810,6 @@ HTCLIENT HTLEFT HTMAXBUTTON HTMINBUTTON -HTMLTo HTRIGHT HTTOP HTTOPLEFT @@ -858,9 +820,7 @@ hwheel hwnd HWNDPARENT iccex -icket ICONERROR -Iconified ICONINFORMATION IConsole ICONSTOP @@ -873,7 +833,6 @@ idllib IDOK IDR idth -idx IDXGI IEnd IEnum @@ -885,14 +844,12 @@ IHosted iid IIo ime -Imm IMPEXP inbox inclusivity INCONTEXT INFOEX inheritcursor -inheritdoc inheritfrom INITCOMMONCONTROLSEX INITDIALOG @@ -900,7 +857,6 @@ initguid INITMENU inkscape INLINEPREFIX -inlines inproc Inputkeyinfo INPUTPROCESSORPROFILE @@ -914,7 +870,7 @@ Interner intsafe INVALIDARG INVALIDATERECT -ioctl +Ioctl ipch ipp IProperty @@ -933,9 +889,9 @@ ivalid IWIC IXP jconcpp +JLO JOBOBJECT JOBOBJECTINFOCLASS -jpe JPN jsoncpp Jsons @@ -953,13 +909,13 @@ kernelbase kernelbasestaging KEYBDINPUT keychord -keydown +keydowns KEYFIRST KEYLAST Keymapping keyscan keystate -keyup +keyups khome KILLACTIVE KILLFOCUS @@ -993,6 +949,7 @@ LEFTALIGN libpopcnt libsancov libtickit +licate LIMITTEXT LINEDOWN LINESELECTION @@ -1005,7 +962,6 @@ listptrsize lld llx LMENU -lnk lnkd lnkfile LNM @@ -1063,12 +1019,12 @@ lpwstr LRESULT lsb lsconfig -lss lstatus lstrcmp lstrcmpi LTEXT LTLTLTLTL +ltsc LUID luma lval @@ -1113,6 +1069,7 @@ MENUITEMINFO MENUSELECT messageext metaproj +microsoftpublicsymbols midl mii MIIM @@ -1127,7 +1084,6 @@ Mip MMBB mmcc MMCPL -MMIX mmsystem MNC MNOPQ @@ -1141,12 +1097,12 @@ MONITORINFOF MOUSEACTIVATE MOUSEFIRST MOUSEHWHEEL -MOUSEMOVE MOVESTART msb msctf msctls msdata +MSDL msft MSGCMDLINEF MSGF @@ -1160,21 +1116,19 @@ MSIL msix msrc MSVCRTD -msys MTSM munged munges murmurhash muxes myapplet +mybranch mydir MYMAX Mypair Myval NAMELENGTH -nameof namestream -natvis NCCALCSIZE NCCREATE NCLBUTTONDOWN @@ -1187,6 +1141,8 @@ NCRBUTTONUP NCXBUTTONDOWN NCXBUTTONUP NEL +nerf +nerror netcoreapp netstandard NEWCPLINFO @@ -1226,7 +1182,6 @@ NONINFRINGEMENT NONPREROTATED nonspace NOOWNERZORDER -Nop NOPAINT noprofile NOREDRAW @@ -1248,7 +1203,6 @@ NOTRACK NOTSUPPORTED nouicompat nounihan -NOUPDATE NOYIELD NOZORDER nrcs @@ -1272,28 +1226,25 @@ ntuser NTVDM ntverp nugetversions -nullability nullness nullonfailure nullopts numlock -numpad NUMSCROLL +NUnit nupkg NVIDIA OACR objbase ocolor -odl oemcp OEMFONT OEMFORMAT OEMs offboarded -oklab -Oklab OLEAUT OLECHAR +onebranch onecore ONECOREBASE ONECORESDKTOOLS @@ -1304,7 +1255,6 @@ onecoreuuid ONECOREWINDOWS onehalf oneseq -OOM openbash opencode opencon @@ -1313,24 +1263,23 @@ openconsoleproxy openps openvt ORIGINALFILENAME +orking osc OSDEPENDSROOT OSG OSGENG -osign oss -otepad -ouicompat -OUnter outdir OUTOFCONTEXT Outptr outstr OVERLAPPEDWINDOW OWNDC +owneralias OWNERDRAWFIXED packagename packageuwp +PACKAGEVERSIONNUMBER PACKCOORD PACKVERSION pagedown @@ -1341,9 +1290,9 @@ PALPC pankaj parentable parms -passthrough PATCOPY pathcch +Pathto PATTERNID pcat pcb @@ -1362,7 +1311,6 @@ PCONSOLEENDTASK PCONSOLESETFOREGROUND PCONSOLEWINDOWOWNER pcoord -pcs pcshell PCSHORT PCSR @@ -1371,8 +1319,9 @@ PCWCH PCWCHAR PCWSTR pda -Pdbs +pdbs pdbstr +PDPs pdtobj pdw pdx @@ -1389,10 +1338,9 @@ PFONT PFONTENUMDATA PFS pgd -pgdn +pgomgr PGONu pguid -pgup phhook phwnd pidl @@ -1405,7 +1353,6 @@ pipestr pixelheight PIXELSLIST PJOBOBJECT -pkey platforming playsound ploc @@ -1418,6 +1365,7 @@ pntm POBJECT Podcast POINTSLIST +policheck POLYTEXTW poppack POPUPATTR @@ -1446,8 +1394,6 @@ prealigned prect prefast preflighting -prefs -prepopulated presorted PREVENTPINNING PREVIEWLABEL @@ -1459,6 +1405,7 @@ prioritization processenv processhost PROCESSINFOCLASS +PRODEXT PROPERTYID PROPERTYKEY PROPERTYVAL @@ -1490,7 +1437,6 @@ psr PSTR psz ptch -ptrs ptsz PTYIn PUCHAR @@ -1501,7 +1447,6 @@ pwstr pwsz pythonw Qaabbcc -qos QUERYOPEN QUESTIONMARK quickedit @@ -1523,9 +1468,7 @@ RBUTTONDBLCLK RBUTTONDOWN RBUTTONUP rcch -RCDATA rcelms -rcl rclsid RCOA RCOCA @@ -1540,7 +1483,7 @@ READCONSOLEOUTPUTSTRING READMODE reallocs reamapping -rects +rectread redef redefinable Redir @@ -1559,7 +1502,7 @@ remoting renamer renderengine rendersize -reparent +reparented reparenting replatformed Replymessage @@ -1571,12 +1514,12 @@ Resequence RESETCONTENT resheader resmimetype +resultmacros resw resx rfa rfid rftp -RGBCOLOR rgbi rgbs rgci @@ -1587,7 +1530,6 @@ rgn rgp rgpwsz rgrc -rgs rgui rgw RIGHTALIGN @@ -1598,8 +1540,8 @@ RIS roadmap robomac rosetta -roundtrips RRF +rrr RRRGGGBB rsas rtcore @@ -1607,7 +1549,6 @@ RTEXT RTFTo RTLREADING Rtn -RTTI ruleset runas RUNDLL @@ -1625,16 +1566,14 @@ RVERTICAL rvpa RWIN rxvt -safearray safemath -sapi sba SBCS SBCSDBCS sbi sbiex -sbold -scancode +sbom +scancodes scanline schemename SCL @@ -1646,7 +1585,6 @@ screeninfo screenshots scriptload scrollback -scrollbars SCROLLFORWARD SCROLLINFO scrolllock @@ -1655,7 +1593,6 @@ SCROLLSCALE SCROLLSCREENBUFFER scursor sddl -sdeleted SDKDDK securityappcontainer segfault @@ -1667,7 +1604,6 @@ Selfhosters SERVERDLL SETACTIVE SETBUDDYINT -SETCOLOR setcp SETCURSEL SETCURSOR @@ -1675,7 +1611,6 @@ SETCURSORINFO SETCURSORPOSITION SETDISPLAYMODE SETFOCUS -SETFONT SETFOREGROUND SETHARDWARESTATE SETHOTKEY @@ -1693,6 +1628,7 @@ SETSCREENBUFFERSIZE SETSEL SETTEXTATTRIBUTE SETTINGCHANGE +setvariable Setwindow SETWINDOWINFO SFGAO @@ -1710,7 +1646,6 @@ shellscalingapi SHFILEINFO SHGFI SHIFTJIS -Shl shlguid shlobj shlwapi @@ -1726,6 +1661,7 @@ SHOWWINDOW sidebyside SIF SIGDN +Signtool SINGLEFLAG SINGLETHREADED siup @@ -1745,7 +1681,6 @@ slpit SManifest SMARTQUOTE SMTO -SND SOLIDBOX Solutiondir somefile @@ -1758,8 +1693,6 @@ srcsrv SRCSRVTRG srctool srect -srgb -Srgb srv srvinit srvpipe @@ -1773,7 +1706,6 @@ STARTUPINFOW STARTWPARMS STARTWPARMSA STARTWPARMSW -Statusline stb stdafx STDAPI @@ -1783,26 +1715,21 @@ STDEXT STDMETHODCALLTYPE STDMETHODIMP STGM -stl -Stri Stringable STRINGTABLE -strrev strsafe STUBHEAD STUVWX stylecop SUA subcompartment -subfolders -subkey +subkeys SUBLANG subresource subsystemconsole subsystemwindows swapchain swapchainpanel -swappable SWMR SWP SYMED @@ -1828,8 +1755,6 @@ targetentrypoint TARGETLIBS TARGETNAME targetver -taskbar -tbar TBase tbc tbi @@ -1838,8 +1763,8 @@ TBM tchar TCHFORMAT TCI -tcommandline tcommands +tdbuild Tdd TDelegated TDP @@ -1856,7 +1781,7 @@ terminfo TEs testcon testd -testenv +testenvs testlab testlist testmd @@ -1865,7 +1790,6 @@ TESTNULL testpass testpasses TEXCOORD -texel TExpected textattribute TEXTATTRIBUTEID @@ -1878,14 +1802,12 @@ TEXTMETRICW textmode texttests TFCAT -tfoo TFunction -tga THUMBPOSITION THUMBTRACK TIcon tilunittests -titlebar +titlebars TITLEISLINKNAME TJson TLambda @@ -1902,15 +1824,11 @@ toolbars TOOLINFO TOOLWINDOW TOPDOWNDIB -TOPLEFT -TOPRIGHT TOpt tosign touchpad Tpp Tpqrst -tprivapi -tput tracelog tracelogging traceloggingprovider @@ -1927,8 +1845,8 @@ TRIANGLESTRIP Tribool TRIMZEROHEADINGS trx +tsa tsattrs -tsf tsgr tsm TStr @@ -1937,15 +1855,12 @@ TSub TTBITMAP TTFONT TTFONTLIST -tthe -tthis TTM TTo tvpp +tvtseq Txtev typechecked -typelib -typeparam TYUI UAC uap @@ -1956,10 +1871,8 @@ ucd uch UChars udk -UDM uer UError -uget uia UIACCESS uiacore @@ -1968,8 +1881,6 @@ uielem UIELEMENTENABLEDONLY UINTs ulcch -umul -umulh Unadvise unattend UNCPRIORITY @@ -1978,7 +1889,6 @@ unhighlighting unhosted UNICODETEXT UNICRT -uninitialize Unintense Uniscribe unittesting @@ -1989,7 +1899,6 @@ UNORM unparseable unregistering untextured -untimes UPDATEDISPLAY UPDOWN UPKEY @@ -2018,8 +1927,6 @@ USESTDHANDLES usp USRDLL utext -UText -UTEXT utr UVWXY UVWXYZ @@ -2029,12 +1936,9 @@ uxtheme Vanara vararg vclib -vcpkg vcprintf vcxitems -vec vectorize -vectorized VERCTRL VERTBAR VFT @@ -2048,6 +1952,9 @@ vkey VKKEYSCAN VMs VPA +vpack +vpackdirectory +VPACKMANIFESTDIRECTORY VPR VProc VRaw @@ -2057,7 +1964,9 @@ vsconfig vscprintf VSCROLL vsdevshell +vse vsinfo +vsinstalldir vso vspath VSTAMP @@ -2147,16 +2056,17 @@ windowsshell windowsterminal windowsx windowtheme -WINDOWTITLE winevent wingdi winget +wingetcreate WINIDE winioctl winmd winmeta winmgr winmm +WINMSAPP winnt Winperf WInplace @@ -2182,13 +2092,13 @@ WNegative WNull wnwb workarea -workaround WOutside WOWARM WOWx wparam WPartial wpf +wpfdotnet WPR WPrep WPresent @@ -2246,6 +2156,7 @@ xdy XEncoding xes xff +XFG XFile XFORM xin @@ -2273,9 +2184,7 @@ yact YCast YCENTER YCount -YDPI YLimit -YOffset YSubstantial YVIRTUALSCREEN YWalk diff --git a/.github/actions/spelling/expect/web.txt b/.github/actions/spelling/expect/web.txt index 8f5b59ac730..39e8cc78d55 100644 --- a/.github/actions/spelling/expect/web.txt +++ b/.github/actions/spelling/expect/web.txt @@ -1,4 +1,3 @@ -WCAG winui appshellintegration mdtauk diff --git a/.github/actions/spelling/line_forbidden.patterns b/.github/actions/spelling/line_forbidden.patterns index 31ad2ddcd26..41cf9272ed2 100644 --- a/.github/actions/spelling/line_forbidden.patterns +++ b/.github/actions/spelling/line_forbidden.patterns @@ -1,4 +1,6 @@ -# reject `m_data` as there's a certain OS which has evil defines that break things if it's used elsewhere +# reject `m_data` as VxWorks defined it and that breaks things if it's used elsewhere +# see [fprime](https://github.com/nasa/fprime/commit/d589f0a25c59ea9a800d851ea84c2f5df02fb529) +# and [Qt](https://github.com/qtproject/qt-solutions/blame/fb7bc42bfcc578ff3fa3b9ca21a41e96eb37c1c7/qtscriptclassic/src/qscriptbuffer_p.h#L46) # \bm_data\b # If you have a framework that uses `it()` for testing and `fit()` for debugging a specific test, @@ -6,40 +8,72 @@ # to use this: #\bfit\( +# s.b. anymore +\bany more[,.] + # s.b. GitHub -\bGithub\b +(?)[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}(?:[}"]|"'`=(])-(?:D(?=[A-Z])|[WX]|f(?=[ms]))(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}) + +# hit-count: 60 file-count: 35 # version suffix v# (?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_])) -# hit-count: 20 file-count: 9 -# hex runs -\b[0-9a-fA-F]{16,}\b +# hit-count: 2 file-count: 2 +# This does not cover multiline strings, if your repository has them, +# you'll want to remove the `(?=.*?")` suffix. +# The `(?=.*?")` suffix should limit the false positives rate +# printf +%(?:s)(?!ize)(?=[a-z]{2,}) -# hit-count: 10 file-count: 7 +# hit-count: 16 file-count: 10 # uuid: \b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b +# hit-count: 13 file-count: 4 +# Non-English +[a-zA-Z]*[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*|[a-zA-Z]{3,}[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]|[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3,} + +# hit-count: 7 file-count: 5 +# hex digits including css/html color classes: +(?:[\\0][xX]|\\u|[uU]\+|#x?|\%23)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|[iu]\d+)\b + +# hit-count: 7 file-count: 1 +# regex choice +\(\?:[^)]+\|[^)]+\) + # hit-count: 4 file-count: 4 -# mailto urls -mailto:[-a-zA-Z=;:/?%&0-9+@.]{3,} +# tar arguments +\b(?:\\n|)g?tar(?:\.exe|)(?:(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+ # hit-count: 4 file-count: 1 # ANSI color codes -(?:\\(?:u00|x)1b|\x1b)\[\d+(?:;\d+|)m +(?:\\(?:u00|x)1[Bb]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+|)m + +# hit-count: 4 file-count: 1 +# Update Lorem based on your content (requires `ge` and `w` from https://github.com/jsoref/spelling; and `review` from https://github.com/check-spelling/check-spelling/wiki/Looking-for-items-locally ) +# grep '^[^#].*lorem' .github/actions/spelling/patterns.txt|perl -pne 's/.*i..\?://;s/\).*//' |tr '|' "\n"|sort -f |xargs -n1 ge|perl -pne 's/^[^:]*://'|sort -u|w|sed -e 's/ .*//'|w|review - +# Warning, while `(?i)` is very neat and fancy, if you have some binary files that aren't proper unicode, you might run into: +## Operation "substitution (s///)" returns its argument for non-Unicode code point 0x1C19AE (the code point will vary). +## You could manually change `(?i)X...` to use `[Xx]...` +## or you could add the files to your `excludes` file (a version after 0.0.19 should identify the file path) +# Lorem +(?:\w|\s|[,.])*\b(?i)(?:amet|consectetur|cursus|dolor|eros|ipsum|lacus|libero|ligula|lorem|magna|neque|nulla|suscipit|tempus)\b(?:\w|\s|[,.])* + +# hit-count: 3 file-count: 3 +# mailto urls +mailto:[-a-zA-Z=;:/?%&0-9+@.]{3,} # hit-count: 2 file-count: 1 -# latex -\\(?:n(?:ew|ormal|osub)|r(?:enew)|t(?:able(?:of|)|he|itle))(?=[a-z]+) +# Python string prefix / binary prefix +# Note that there's a high false positive rate, remove the `?=` and search for the regex to see if the matches seem like reasonable strings +(?= 0.0.22) +\\\w{2,}\{ # hit-count: 1 file-count: 1 -# French -# This corpus only had capital letters, but you probably want lowercase ones as well. -\b[LN]'+[a-z]{2,}\b +# tput arguments -- https://man7.org/linux/man-pages/man5/terminfo.5.html -- technically they can be more than 5 chars long... +\btput\s+(?:(?:-[SV]|-T\s*\w+)\s+)*\w{3,5}\b + +# Questionably acceptable forms of `in to` +# Personally, I prefer `log into`, but people object +# https://www.tprteaching.com/log-into-log-in-to-login/ +\b(?:[Ll]og|[Ss]ign) in to\b + +# to opt in +\bto opt in\b # acceptable duplicates # ls directory listings -[-bcdlpsw](?:[-r][-w][-sx]){3}\s+\d+\s+(\S+)\s+\g{-1}\s+\d+\s+ -# C/idl types + English ... -\s(Guid|long|LONG|that) \g{-1}\s - -# javadoc / .net -(?:[\\@](?:groupname|param)|(?:public|private)(?:\s+static|\s+readonly)*)\s+(\w+)\s+\g{-1}\s +[-bcdlpsw](?:[-r][-w][-Ssx]){3}\s+\d+\s+\S+\s+\S+\s+\d+\s+ +# mount +\bmount\s+-t\s+(\w+)\s+\g{-1}\b +# C types and repeated CSS values +\s(auto|center|div|Guid|inherit|long|LONG|none|normal|solid|that|thin|transparent|very)(?: \g{-1})+\s +# C struct +\bstruct\s+(\w+)\s+\g{-1}\b +# go templates +\s(\w+)\s+\g{-1}\s+\`(?:graphql|inject|json|yaml): +# doxygen / javadoc / .net +(?:[\\@](?:brief|groupname|t?param|return|retval)|(?:public|private|\[Parameter(?:\(.+\)|)\])(?:\s+static|\s+override|\s+readonly)*)(?:\s+\{\w+\}|)\s+(\w+)\s+\g{-1}\s # Commit message -- Signed-off-by and friends ^\s*(?:(?:Based-on-patch|Co-authored|Helped|Mentored|Reported|Reviewed|Signed-off)-by|Thanks-to): (?:[^<]*<[^>]*>|[^<]*)\s*$ diff --git a/.github/actions/spelling/reject.txt b/.github/actions/spelling/reject.txt index 301719de47e..2ac1670d534 100644 --- a/.github/actions/spelling/reject.txt +++ b/.github/actions/spelling/reject.txt @@ -1,6 +1,7 @@ ^attache$ ^attacher$ ^attachers$ +^bellow$ benefitting occurences? ^dependan.* diff --git a/.github/workflows/similarIssues.yml b/.github/workflows/similarIssues.yml index b0fcc3d306b..f3d17ac11b5 100644 --- a/.github/workflows/similarIssues.yml +++ b/.github/workflows/similarIssues.yml @@ -5,19 +5,19 @@ on: types: [opened] jobs: - getsimilarissues: + getSimilarIssues: runs-on: ubuntu-latest outputs: - message: ${{ steps.getbody.outputs.message }} + message: ${{ steps.getBody.outputs.message }} steps: - - id: getbody + - id: getBody uses: craigloewen-msft/GitGudSimilarIssues@main with: issuetitle: ${{ github.event.issue.title }} repo: ${{ github.repository }} similaritytolerance: "0.8" add-comment: - needs: getsimilarissues + needs: getSimilarIssues runs-on: ubuntu-latest permissions: issues: write @@ -28,4 +28,4 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NUMBER: ${{ github.event.issue.number }} REPO: ${{ github.repository }} - BODY: ${{ needs.getsimilarissues.outputs.message }} + BODY: ${{ needs.getSimilarIssues.outputs.message }} diff --git a/.github/workflows/spelling2.yml b/.github/workflows/spelling2.yml index 446b24343ed..2778a7e9e5d 100644 --- a/.github/workflows/spelling2.yml +++ b/.github/workflows/spelling2.yml @@ -5,7 +5,7 @@ name: Spell checking # https://github.com/check-spelling/check-spelling/wiki/Feature%3A-Restricted-Permissions # # `jobs.comment-push` runs when a push is made to a repository and the `jobs.spelling` job needs to make a comment -# (in odd cases, it might actually run just to collapse a commment, but that's fairly rare) +# (in odd cases, it might actually run just to collapse a comment, but that's fairly rare) # it needs `contents: write` in order to add a comment. # # `jobs.comment-pr` runs when a pull_request is made to a repository and the `jobs.spelling` job needs to make a comment @@ -34,6 +34,29 @@ name: Spell checking # # For background, see: https://github.com/check-spelling/check-spelling/wiki/Feature:-Update-with-deploy-key +# Sarif reporting +# +# Access to Sarif reports is generally restricted (by GitHub) to members of the repository. +# +# Requires enabling `security-events: write` +# and configuring the action with `use_sarif: 1` +# +# For information on the feature, see: https://github.com/check-spelling/check-spelling/wiki/Feature:-Sarif-output + +# Minimal workflow structure: +# +# on: +# push: +# ... +# pull_request_target: +# ... +# jobs: +# # you only want the spelling job, all others should be omitted +# spelling: +# # remove `security-events: write` and `use_sarif: 1` +# # remove `experimental_apply_changes_via_bot: 1` +# ... otherwise adjust the `with:` as you wish + on: push: branches: @@ -43,8 +66,6 @@ on: pull_request_target: branches: - "**" - tags-ignore: - - "**" types: - 'opened' - 'reopened' @@ -60,10 +81,11 @@ jobs: contents: read pull-requests: read actions: read + security-events: write outputs: followup: ${{ steps.spelling.outputs.followup }} runs-on: ubuntu-latest - if: "contains(github.event_name, 'pull_request') || github.event_name == 'push'" + if: ${{ contains(github.event_name, 'pull_request') || github.event_name == 'push' }} concurrency: group: spelling-${{ github.event.pull_request.number || github.ref }} # note: If you use only_check_changed_files, you do not want cancel-in-progress @@ -71,35 +93,50 @@ jobs: steps: - name: check-spelling id: spelling - uses: check-spelling/check-spelling@v0.0.21 + uses: check-spelling/check-spelling@v0.0.22 with: - suppress_push_for_open_pull_request: 1 + suppress_push_for_open_pull_request: ${{ github.actor != 'dependabot[bot]' && 1 }} checkout: true check_file_names: 1 - spell_check_this: check-spelling/spell-check-this@prerelease + spell_check_this: microsoft/terminal@main post_comment: 0 use_magic_file: 1 - extra_dictionary_limit: 10 + report-timing: 1 + warnings: bad-regex,binary-file,deprecated-feature,ignored-expect-variant,large-file,limited-references,no-newline-at-eof,noisy-file,non-alpha-in-dictionary,token-is-substring,unexpected-line-ending,whitespace-in-dictionary,minified-file,unsupported-configuration,no-files-to-check + experimental_apply_changes_via_bot: ${{ github.repository_owner != 'microsoft' && 1 }} + use_sarif: ${{ (!github.event.pull_request || (github.event.pull_request.head.repo.full_name == github.repository)) && 1 }} + extra_dictionary_limit: 20 extra_dictionaries: - cspell:software-terms/src/software-terms.txt - cspell:python/src/python/python-lib.txt - cspell:node/node.txt - cspell:cpp/src/stdlib-c.txt + cspell:software-terms/dict/softwareTerms.txt cspell:cpp/src/stdlib-cpp.txt - cspell:fullstack/fullstack.txt + cspell:lorem-ipsum/dictionary.txt + cspell:cpp/src/stdlib-c.txt + cspell:php/dict/php.txt cspell:filetypes/filetypes.txt - cspell:html/html.txt - cspell:cpp/src/compiler-msvc.txt + cspell:java/src/java.txt cspell:python/src/common/extra.txt - cspell:powershell/powershell.txt + cspell:node/dict/node.txt + cspell:java/src/java-terms.txt cspell:aws/aws.txt + cspell:typescript/dict/typescript.txt + cspell:dotnet/dict/dotnet.txt + cspell:golang/dict/go.txt + cspell:fullstack/dict/fullstack.txt + cspell:cpp/src/compiler-msvc.txt + cspell:python/src/python/python-lib.txt + cspell:mnemonics/src/mnemonics.txt + cspell:cpp/src/stdlib-cmath.txt + cspell:css/dict/css.txt cspell:cpp/src/lang-keywords.txt - cspell:npm/npm.txt - cspell:dotnet/dotnet.txt + cspell:django/dict/django.txt cspell:python/src/python/python.txt - cspell:css/css.txt - cspell:cpp/src/stdlib-cmath.txt - check_extra_dictionaries: '' + cspell:html/dict/html.txt + cspell:cpp/src/ecosystem.txt + cspell:cpp/src/compiler-clang-attributes.txt + cspell:npm/dict/npm.txt + cspell:r/src/r.txt + cspell:powershell/dict/powershell.txt + cspell:csharp/csharp.txt comment-push: name: Report (Push) @@ -111,10 +148,10 @@ jobs: if: (success() || failure()) && needs.spelling.outputs.followup && github.event_name == 'push' steps: - name: comment - uses: check-spelling/check-spelling@v0.0.21 + uses: check-spelling/check-spelling@v0.0.22 with: checkout: true - spell_check_this: check-spelling/spell-check-this@prerelease + spell_check_this: microsoft/terminal@main task: ${{ needs.spelling.outputs.followup }} comment-pr: @@ -123,12 +160,38 @@ jobs: runs-on: ubuntu-latest needs: spelling permissions: + contents: read pull-requests: write if: (success() || failure()) && needs.spelling.outputs.followup && contains(github.event_name, 'pull_request') steps: - name: comment - uses: check-spelling/check-spelling@v0.0.21 + uses: check-spelling/check-spelling@v0.0.22 with: checkout: true - spell_check_this: check-spelling/spell-check-this@prerelease + spell_check_this: microsoft/terminal@main task: ${{ needs.spelling.outputs.followup }} + experimental_apply_changes_via_bot: ${{ github.repository_owner != 'microsoft' && 1 }} + + update: + name: Update PR + permissions: + contents: write + pull-requests: write + actions: read + runs-on: ubuntu-latest + if: ${{ + github.repository_owner != 'microsoft' && + github.event_name == 'issue_comment' && + github.event.issue.pull_request && + contains(github.event.comment.body, '@check-spelling-bot apply') + }} + concurrency: + group: spelling-update-${{ github.event.issue.number }} + cancel-in-progress: false + steps: + - name: apply spelling updates + uses: check-spelling/check-spelling@v0.0.22 + with: + experimental_apply_changes_via_bot: ${{ github.repository_owner != 'microsoft' && 1 }} + checkout: true + ssh_key: "${{ secrets.CHECK_SPELLING }}" diff --git a/build/pipelines/ci.yml b/build/pipelines/ci.yml index 4ad72566a15..ab3490bc58b 100644 --- a/build/pipelines/ci.yml +++ b/build/pipelines/ci.yml @@ -102,7 +102,7 @@ stages: - ${{ if ne(variables['Build.Reason'], 'PullRequest') }}: - stage: CodeIndexer - displayName: Github CodeNav Indexer + displayName: GitHub CodeNav Indexer dependsOn: [] jobs: - template: ./templates-v2/job-index-github-codenav.yml diff --git a/build/pipelines/templates-v2/job-index-github-codenav.yml b/build/pipelines/templates-v2/job-index-github-codenav.yml index e2edf55e651..b59b0a436ef 100644 --- a/build/pipelines/templates-v2/job-index-github-codenav.yml +++ b/build/pipelines/templates-v2/job-index-github-codenav.yml @@ -1,6 +1,6 @@ jobs: - job: CodeNavIndexer - displayName: Run Github CodeNav Indexer + displayName: Run GitHub CodeNav Indexer pool: { vmImage: windows-2022 } steps: diff --git a/doc/Niksa.md b/doc/Niksa.md index 5efcdda0abd..ec6af1f1463 100644 --- a/doc/Niksa.md +++ b/doc/Niksa.md @@ -51,7 +51,7 @@ Will this UI enhancement come to other apps on Windows? Almost certainly not. Th Will we try to keep it from regressing? Yes! Right now it's sort of a manual process. We identify that something is getting slow and then we go haul out [WPR](https://docs.microsoft.com/en-us/windows-hardware/test/wpt/windows-performance-recorder) and start taking traces. We stare down the hot paths and try to reason out what is going on and then improve them. For instance, in the last cycle or two, we focused on heap allocations as a major area where we could improve our end-to-end performance, changing a ton of our code to use stack-constructed iterator-like facades over the underlying request buffer instead of translating and allocating it into a new heap space for each level of processing. -As an aside, @bitcrazed wants us to automate performance tests in some conhost specific way, but I haven't quite figured out a controlled environment to do this in yet. The Windows Engineering System runs performance tests each night that give us a coarse grained way of knowing if we messed something up for the whole operating system, and they technically offer a fine grained way for us to insert our own performance tests... but I just haven't got around to that yet. If you have an idea for a way for us to do this in an automated fashion, I'm all ears. +As an aside, @bitcrazed wants us to automate performance tests in some conhost specific way, but I haven't quite figured out a controlled environment to do this in yet. The Windows Engineering System runs performance tests each night that give us a coarse-grained way of knowing if we messed something up for the whole operating system, and they technically offer a fine-grained way for us to insert our own performance tests... but I just haven't got around to that yet. If you have an idea for a way for us to do this in an automated fashion, I'm all ears. If there's anything else you'd like to know, let me know. I could go on all day. I deleted like 15 tangents from this reply before posting it.... diff --git a/doc/specs/drafts/#2634 - Broadcast Input/#2634 - Broadcast Input.md b/doc/specs/drafts/#2634 - Broadcast Input/#2634 - Broadcast Input.md index 76cc91f5083..01689b8f430 100644 --- a/doc/specs/drafts/#2634 - Broadcast Input/#2634 - Broadcast Input.md +++ b/doc/specs/drafts/#2634 - Broadcast Input/#2634 - Broadcast Input.md @@ -290,7 +290,7 @@ though. **I recommend we ignore this for now, and leave this as a follow-up**. For reference, refer to the following from iTerm2: ![image](https://user-images.githubusercontent.com/2578976/64075757-fa971980-ccee-11e9-9e44-47aaf3bca76c.png) -We don't have a menu bar like on MacOS, but we do have a tab context menu. We +We don't have a menu bar like on macOS, but we do have a tab context menu. We could add these items as a nested entry under each tab. If we wanted to do this, we should also make sure to dynamically change the icon of the MenuItem to reflect the current broadcast state. diff --git a/doc/specs/drafts/#3327 - Application Theming/#3327 - Application Theming.md b/doc/specs/drafts/#3327 - Application Theming/#3327 - Application Theming.md index e0f6d865541..e956b334913 100644 --- a/doc/specs/drafts/#3327 - Application Theming/#3327 - Application Theming.md +++ b/doc/specs/drafts/#3327 - Application Theming/#3327 - Application Theming.md @@ -373,7 +373,7 @@ changes, or the active pane in a tab changes: `TabRowControl` to match. The `tab.cornerRadius` might be a bit trickier to implement. Currently, there's -not a XAML resource that controls this, nor is this something that's exposed by +no XAML resource that controls this, nor is this something that's exposed by the TabView control. Fortunately, this is something that's exposed to us programmatically. We'll need to manually set that value on each `TabViewItem` as we create new tabs. When we reload settings, we'll need to make sure to come diff --git a/src/renderer/atlas/AtlasEngine.r.cpp b/src/renderer/atlas/AtlasEngine.r.cpp index 1fac8f0e834..92649553245 100644 --- a/src/renderer/atlas/AtlasEngine.r.cpp +++ b/src/renderer/atlas/AtlasEngine.r.cpp @@ -217,7 +217,7 @@ void AtlasEngine::_recreateBackend() if (hr == DXGI_ERROR_SDK_COMPONENT_MISSING) { // This might happen if you don't have "Graphics debugger and GPU - // profiler for DirectX" installed in VS. We shouln't just explode if + // profiler for DirectX" installed in VS. We shouldn't just explode if // you don't though - instead, disable debugging and try again. WI_ClearFlag(deviceFlags, D3D11_CREATE_DEVICE_DEBUG); diff --git a/src/renderer/atlas/BackendD2D.cpp b/src/renderer/atlas/BackendD2D.cpp index ea4ea0923eb..05f79c78c20 100644 --- a/src/renderer/atlas/BackendD2D.cpp +++ b/src/renderer/atlas/BackendD2D.cpp @@ -332,7 +332,7 @@ void BackendD2D::_drawTextResetLineRendition(const ShapedRow* row) const noexcep } } -// Returns the theoretical/design design size of the given `DWRITE_GLYPH_RUN`, relative the the given baseline origin. +// Returns the theoretical/design design size of the given `DWRITE_GLYPH_RUN`, relative the given baseline origin. // This algorithm replicates what DirectWrite does internally to provide `IDWriteTextLayout::GetMetrics`. f32r BackendD2D::_getGlyphRunDesignBounds(const DWRITE_GLYPH_RUN& glyphRun, f32 baselineX, f32 baselineY) { diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index 41fefa6ea2b..51494df7b23 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -1932,7 +1932,7 @@ void BackendD3D::_drawCursorForeground() } } // We can also skip any instances (= any rows) at the beginning that are clearly not overlapping with - // the cursor. This reduces the the CPU cost of this function by roughly half (a few microseconds). + // the cursor. This reduces the CPU cost of this function by roughly half (a few microseconds). for (; instancesOffset < instancesCount; ++instancesOffset) { const auto& it = _instances[instancesOffset]; From 306f31acf482ce84701aabee52981575277f16bb Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Wed, 6 Dec 2023 05:20:15 -0600 Subject: [PATCH 104/167] ci: remove the check-spelling-0.0.21 shim (#16424) As noted by @jsoref in #16127, we could eventually remove this and also check-spelling would make suggestions on what patterns to use. --- .../spelling/allow/check-spelling-0.0.21.txt | 224 ------------------ 1 file changed, 224 deletions(-) delete mode 100644 .github/actions/spelling/allow/check-spelling-0.0.21.txt diff --git a/.github/actions/spelling/allow/check-spelling-0.0.21.txt b/.github/actions/spelling/allow/check-spelling-0.0.21.txt deleted file mode 100644 index 9463838f115..00000000000 --- a/.github/actions/spelling/allow/check-spelling-0.0.21.txt +++ /dev/null @@ -1,224 +0,0 @@ -aarch -abi -activatable -aef -amd -applets -argb -ASDF -ative -ativecxapp -AVX -azzle -BGR -bigints -bignum -bitmask -bitmasks -blackhole -Bopomofo -BOTTOMLEFT -BOTTOMRIGHT -bsr -bstr -cbuffer -cci -changelist -changelists -Checkin -chk -cielab -cls -clz -CMake -cmt -Cmts -cmyk -cname -cnt -CNTRL -codepage -codepages -codepoints -colorspaces -COMDAT -configurability -cpuid -cred -Crt -dbg -distro -distros -django -DLOAD -ect -ectread -edist -egistry -elease -elemetry -elems -emacs -emark -emplates -enderer -endregion -endverbatim -eplace -erm -erminal -erminalcore -erminalinput -esource -esources -esthelper -estlist -estmd -estpasses -ests -esult -esultmacros -etfx -eturn -ewdelete -ewtonsoft -flyout -flyouts -FONTFAMILY -FONTSIZE -FONTWEIGHT -formattable -framebuffer -FSCTL -GAUSSIAN -getch -GETFONTSIZE -GETPOS -GNUC -groupbox -Gsub -GTR -HORZ -hread -hrottled -hrow -hsl -HTMLTo -icket -Iconified -idx -ihilist -Imm -inheritdoc -inlines -ioctl -keydown -keydowns -keyup -keyups -lnk -lss -MMIX -MOUSEMOVE -movl -movsb -msys -nameof -natvis -Newdelete -Newtonsoft -Nop -NOUPDATE -nullability -numpad -oklab -ools -OOM -osign -ote -otepad -ouicompat -passthrough -pcs -pgdn -pgup -pkey -pname -popcnt -prefs -prepopulated -ptrs -Pvf -qos -Rasterizer -RCDATA -rcl -rects -reparent -reparented -RGBCOLOR -roundtrips -RTTI -safearray -sapi -scancode -scancodes -scrollbars -Sdl -SETCOLOR -SETFONT -Shl -SND -srgb -Statusline -stl -stosb -strerror -strrev -subc -subfolders -subkey -subkeys -swappable -taskbar -tdll -testenv -testenvs -texel -TExpected -tioapi -titlebar -titlebars -TOPLEFT -TOPRIGHT -tprivapi -tsf -typelib -typeparam -UDM -uget -ules -umul -umulh -uninitialize -Unserialize -untimes -upkg -utc -vcpkg -vec -vectorized -vtable -vtables -WCAG -WINDOWTITLE -workaround -xchgl -xgetbv -XOFF -XON -YDPI -YOffset -ype -ypes -ZIndex From efbfb12145da959f74c368cdfc92f7892ce724d5 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 5 Dec 2023 20:37:58 +0100 Subject: [PATCH 105/167] Fix ConPTY inputs incorrectly being treated as plain text (#16352) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is my proposal to avoid aborting ConPTY input parsing because a read accidentally got split up into more than one chunk. This happens a lot with WSL for me, as I often get (for instance) a `\x1b[67;46;99;0;32;` input followed immediately by a `1_` input. The current logic would cause both of these to be flushed out to the client application. This PR fixes the issue by only flushing either a standalone escape character or a escape+character combination. It basically limits the previous code to just `VTStates::Ground` and `VTStates::Escape`. I'm not using the `_state` member, because `VTStates::OscParam` makes no distinction between `\x1b]` and `\x1b]1234` and I only want to flush the former. I felt like checking the contents of `run` directly is easier to understand. Related to #16343 ## Validation Steps Performed * win32-input-mode sequences are now properly buffered ✅ * Standalone alt-key combinations are still being flushed ✅ (cherry picked from commit 5f5ef10571a17af4feeafd6be8729085fe5a4ac0) Service-Card-Id: 91270261 Service-Version: 1.19 --- src/terminal/parser/stateMachine.cpp | 70 ++++++------------- .../parser/ut_parser/InputEngineTest.cpp | 14 ++++ 2 files changed, 34 insertions(+), 50 deletions(-) diff --git a/src/terminal/parser/stateMachine.cpp b/src/terminal/parser/stateMachine.cpp index eb22ff95382..643409afda5 100644 --- a/src/terminal/parser/stateMachine.cpp +++ b/src/terminal/parser/stateMachine.cpp @@ -2127,6 +2127,8 @@ void StateMachine::ProcessString(const std::wstring_view string) // If we're at the end of the string and have remaining un-printed characters, if (_state != VTStates::Ground) { + const auto run = _CurrentRun(); + // One of the "weird things" in VT input is the case of something like // alt+[. In VT, that's encoded as `\x1b[`. However, that's // also the start of a CSI, and could be the start of a longer sequence, @@ -2136,60 +2138,28 @@ void StateMachine::ProcessString(const std::wstring_view string) // alt+[, A would be processed like `\x1b[A`, // which is _wrong_). // - // Fortunately, for VT input, each keystroke comes in as an individual - // write operation. So, if at the end of processing a string for the - // InputEngine, we find that we're not in the Ground state, that implies - // that we've processed some input, but not dispatched it yet. This - // block at the end of `ProcessString` will then re-process the - // undispatched string, but it will ensure that it dispatches on the - // last character of the string. For our previous `\x1b[` scenario, that - // means we'll make sure to call `_ActionEscDispatch('[')`., which will - // properly decode the string as alt+[. - const auto run = _CurrentRun(); - + // At the same time, input may be broken up arbitrarily, depending on the pipe's + // buffer size, our read-buffer size, the sender's write-buffer size, and more. + // In fact, with the current WSL, input is broken up in 16 byte chunks (Why? :(), + // which breaks up many of our longer sequences, like our Win32InputMode ones. + // + // As a heuristic, this code specifically checks for a trailing Esc or Alt+key. if (_isEngineForInput) { - // Reset our state, and put all but the last char in again. - ResetState(); - _processingLastCharacter = false; - // Chars to flush are [pwchSequenceStart, pwchCurr) - auto wchIter = run.cbegin(); - while (wchIter < run.cend() - 1) - { - ProcessCharacter(*wchIter); - wchIter++; - } - // Manually execute the last char [pwchCurr] - _processingLastCharacter = true; - switch (_state) + if (run.size() <= 2 && run.front() == L'\x1b') { - case VTStates::Ground: - _ActionExecute(*wchIter); - break; - case VTStates::Escape: - case VTStates::EscapeIntermediate: - _ActionEscDispatch(*wchIter); - break; - case VTStates::CsiEntry: - case VTStates::CsiIntermediate: - case VTStates::CsiIgnore: - case VTStates::CsiParam: - case VTStates::CsiSubParam: - _ActionCsiDispatch(*wchIter); - break; - case VTStates::OscParam: - case VTStates::OscString: - case VTStates::OscTermination: - _ActionOscDispatch(*wchIter); - break; - case VTStates::Ss3Entry: - case VTStates::Ss3Param: - _ActionSs3Dispatch(*wchIter); - break; + _EnterGround(); + if (run.size() == 1) + { + _ActionExecute(L'\x1b'); + } + else + { + _EnterEscape(); + _ActionEscDispatch(run.back()); + } + _EnterGround(); } - // microsoft/terminal#2746: Make sure to return to the ground state - // after dispatching the characters - _EnterGround(); } else if (_state != VTStates::SosPmApcString && _state != VTStates::DcsPassThrough && _state != VTStates::DcsIgnore) { diff --git a/src/terminal/parser/ut_parser/InputEngineTest.cpp b/src/terminal/parser/ut_parser/InputEngineTest.cpp index 2e21c77c74f..fcb23734be5 100644 --- a/src/terminal/parser/ut_parser/InputEngineTest.cpp +++ b/src/terminal/parser/ut_parser/InputEngineTest.cpp @@ -260,6 +260,7 @@ class Microsoft::Console::VirtualTerminal::InputEngineTest TEST_METHOD(AltCtrlDTest); TEST_METHOD(AltIntermediateTest); TEST_METHOD(AltBackspaceEnterTest); + TEST_METHOD(ChunkedSequence); TEST_METHOD(SGRMouseTest_ButtonClick); TEST_METHOD(SGRMouseTest_Modifiers); TEST_METHOD(SGRMouseTest_Movement); @@ -1041,6 +1042,19 @@ void InputEngineTest::AltBackspaceEnterTest() VerifyExpectedInputDrained(); } +void InputEngineTest::ChunkedSequence() +{ + // This test ensures that a DSC sequence that's split up into multiple chunks isn't + // confused with a single Alt+key combination like in the AltBackspaceEnterTest(). + // Basically, it tests the selectivity of the AltBackspaceEnterTest() fix. + + auto dispatch = std::make_unique(nullptr, nullptr); + auto inputEngine = std::make_unique(std::move(dispatch)); + StateMachine stateMachine{ std::move(inputEngine) }; + stateMachine.ProcessString(L"\x1b[1"); + VERIFY_ARE_EQUAL(StateMachine::VTStates::CsiParam, stateMachine._state); +} + // Method Description: // - Writes an SGR VT sequence based on the necessary parameters // Arguments: From f63e25b87c9f08a7c51887b9b1e46bf6b9010348 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 21 Nov 2023 21:50:59 +0100 Subject: [PATCH 106/167] Fix chunked soft fonts not working (#16349) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This changeset fixes an issue caused by #15991 where "chunked" escape sequences would get corrupted. The fix is to simply not flush eagerly anymore. I tried my best to keep the input lag reduction from #15991, but unfortunately this isn't possible for console APIs. Closes #16079 ## Validation Steps Performed * `type ascii.com` produces soft font ASCII characters ✅ (cherry picked from commit bdf2f6f2740168363bdba898697e28baa8d7beb7) Service-Card-Id: 91283205 Service-Version: 1.19 --- src/host/VtIo.cpp | 4 ---- src/renderer/vt/state.cpp | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index c286a5e8aca..b0c33671727 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -467,10 +467,6 @@ void VtIo::SendCloseEvent() void VtIo::CorkRenderer(bool corked) const noexcept { _pVtRenderEngine->Cork(corked); - if (!corked) - { - LOG_IF_FAILED(ServiceLocator::LocateGlobals().pRender->PaintFrame()); - } } #ifdef UNIT_TESTING diff --git a/src/renderer/vt/state.cpp b/src/renderer/vt/state.cpp index df35691a361..5bb6b7d694d 100644 --- a/src/renderer/vt/state.cpp +++ b/src/renderer/vt/state.cpp @@ -167,6 +167,7 @@ void VtEngine::_flushImpl() noexcept void VtEngine::Cork(bool corked) noexcept { _corked = corked; + _Flush(); } // Method Description: From 20dad624710249f3a4ee77c47a58d4ca367a820b Mon Sep 17 00:00:00 2001 From: Ryan Luu Date: Wed, 6 Dec 2023 15:31:30 -0800 Subject: [PATCH 107/167] Fix markdown alerts syntax in README (#16434) Changes any references of `> **Note**\` with `> [!NOTE]` to match the new syntax for markdown files in GitHub. Fixes the 14 November 2023 update to the alerts syntax in markdown files: > ## Update - 14 November 2023 > * The initial syntax using e.g. **Note** isn't supported any longer. > > https://github.com/orgs/community/discussions/16925 --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f06a7053a18..462731aa594 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Related repositories include: ## Installing and running Windows Terminal -> **Note**\ +> [!NOTE] > Windows Terminal requires Windows 10 2004 (build 19041) or later ### Microsoft Store [Recommended] @@ -53,7 +53,7 @@ fails for any reason, you can try the following command at a PowerShell prompt: Add-AppxPackage Microsoft.WindowsTerminal_.msixbundle ``` -> **Note**\ +> [!NOTE] > If you install Terminal manually: > > * You may need to install the [VC++ v14 Desktop Framework Package](https://docs.microsoft.com/troubleshoot/cpp/c-runtime-packages-desktop-bridge#how-to-install-and-update-desktop-framework-packages). @@ -72,7 +72,7 @@ package: winget install --id Microsoft.WindowsTerminal -e ``` -> **Note**\ +> [!NOTE] > Dependency support is available in WinGet version [1.6.2631 or later](https://github.com/microsoft/winget-cli/releases). To install the Terminal stable release 1.18 or later, please make sure you have the updated version of the WinGet client. #### Via Chocolatey (unofficial) @@ -262,7 +262,7 @@ Cause: You're launching the incorrect solution in Visual Studio. Solution: Make sure you're building & deploying the `CascadiaPackage` project in Visual Studio. -> **Note**\ +> [!NOTE] > `OpenConsole.exe` is just a locally-built `conhost.exe`, the classic > Windows Console that hosts Windows' command-line infrastructure. OpenConsole > is used by Windows Terminal to connect to and communicate with command-line From 17867af5349a66fa8121d1c930dd9587e45f5117 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Fri, 8 Dec 2023 15:01:55 -0600 Subject: [PATCH 108/167] conpty: request DSR-CPR before Win32 input mode (#16445) This prevents an issue in conhost where older versions of Windows Terminal (including the ones currently inbox in Windows, as well as stable and preview) will *still* cause WSL interop to hang on startup. Since VT input is erroneously re-encoded as Win32 input events on those versions, we need to make sure we request the cursor position *before* enabling Win32 input mode. That way, the CPR we get back is properly encoded. --- src/host/VtIo.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index b0c33671727..f9a6b722767 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -263,12 +263,6 @@ bool VtIo::IsUsingVt() const CATCH_RETURN(); } - // GH#4999 - Send a sequence to the connected terminal to request - // win32-input-mode from them. This will enable the connected terminal to - // send us full INPUT_RECORDs as input. If the terminal doesn't understand - // this sequence, it'll just ignore it. - LOG_IF_FAILED(_pVtRenderEngine->RequestWin32Input()); - // MSFT: 15813316 // If the terminal application wants us to inherit the cursor position, // we're going to emit a VT sequence to ask for the cursor position, then @@ -287,6 +281,12 @@ bool VtIo::IsUsingVt() const } } + // GH#4999 - Send a sequence to the connected terminal to request + // win32-input-mode from them. This will enable the connected terminal to + // send us full INPUT_RECORDs as input. If the terminal doesn't understand + // this sequence, it'll just ignore it. + LOG_IF_FAILED(_pVtRenderEngine->RequestWin32Input()); + if (_pVtInputThread) { LOG_IF_FAILED(_pVtInputThread->Start()); From bcc01d96bf856a043dfc792dad770c4e433fd93f Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Fri, 8 Dec 2023 15:01:55 -0600 Subject: [PATCH 109/167] conpty: request DSR-CPR before Win32 input mode (#16445) This prevents an issue in conhost where older versions of Windows Terminal (including the ones currently inbox in Windows, as well as stable and preview) will *still* cause WSL interop to hang on startup. Since VT input is erroneously re-encoded as Win32 input events on those versions, we need to make sure we request the cursor position *before* enabling Win32 input mode. That way, the CPR we get back is properly encoded. (cherry picked from commit 17867af5349a66fa8121d1c930dd9587e45f5117) Service-Card-Id: 91301135 Service-Version: 1.19 --- src/host/VtIo.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index b0c33671727..f9a6b722767 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -263,12 +263,6 @@ bool VtIo::IsUsingVt() const CATCH_RETURN(); } - // GH#4999 - Send a sequence to the connected terminal to request - // win32-input-mode from them. This will enable the connected terminal to - // send us full INPUT_RECORDs as input. If the terminal doesn't understand - // this sequence, it'll just ignore it. - LOG_IF_FAILED(_pVtRenderEngine->RequestWin32Input()); - // MSFT: 15813316 // If the terminal application wants us to inherit the cursor position, // we're going to emit a VT sequence to ask for the cursor position, then @@ -287,6 +281,12 @@ bool VtIo::IsUsingVt() const } } + // GH#4999 - Send a sequence to the connected terminal to request + // win32-input-mode from them. This will enable the connected terminal to + // send us full INPUT_RECORDs as input. If the terminal doesn't understand + // this sequence, it'll just ignore it. + LOG_IF_FAILED(_pVtRenderEngine->RequestWin32Input()); + if (_pVtInputThread) { LOG_IF_FAILED(_pVtInputThread->Start()); From f5b45c25c9dfe27e03fbea1c7d82a6dc2a009343 Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Fri, 15 Dec 2023 01:17:14 +0530 Subject: [PATCH 110/167] Fix curlyline rendering in `AtlasEngine` and `GDIRenderer` (#16444) Fixes Curlyline being drawn as single underline in some cases **Detailed Description** - Curlyline is drawn at all font sizes. - We might render a curlyline that is clipped in cases where we don't have enough space to draw a full curlyline. This is to give users a consistent view of Curlylines. Previously in those cases, it was drawn as a single underline. - Removed minimum threshold `minCurlyLinePeakHeight` for Curlyline drawing. - GDIRender changes: - Underline offset now points to the (vertical) mid position of the underline. Removes redundant `underlineMidY` calculation inside the draw call. Closes #16288 --- src/renderer/atlas/BackendD3D.cpp | 56 +++++++++++++++---------------- src/renderer/atlas/BackendD3D.h | 2 +- src/renderer/gdi/paint.cpp | 29 ++++++++-------- src/renderer/gdi/state.cpp | 51 ++++++++++++---------------- 4 files changed, 65 insertions(+), 73 deletions(-) diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index 51494df7b23..e68fb2a7a57 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -306,37 +306,35 @@ void BackendD3D::_updateFontDependents(const RenderingPayload& p) { const auto& font = *p.s->font; - // The max height of Curly line peak in `em` units. - const auto maxCurlyLinePeakHeightEm = 0.075f; - // We aim for atleast 1px height, but since we draw 1px smaller curly line, - // we aim for 2px height as a result. - const auto minCurlyLinePeakHeight = 2.0f; - - // Curlyline uses the gap between cell bottom and singly underline position - // as the height of the wave's peak. The baseline for curly-line is at the - // middle of singly underline. The gap could be too big, so we also apply - // a limit on the peak height. - const auto strokeHalfWidth = font.underline.height / 2.0f; - const auto underlineMidY = font.underline.position + strokeHalfWidth; - const auto cellBottomGap = font.cellSize.y - underlineMidY - strokeHalfWidth; - const auto maxCurlyLinePeakHeight = maxCurlyLinePeakHeightEm * font.fontSize; - auto curlyLinePeakHeight = std::min(cellBottomGap, maxCurlyLinePeakHeight); + // Curlyline is drawn with a desired height relative to the font size. The + // baseline of curlyline is at the middle of singly underline. When there's + // limited space to draw a curlyline, we apply a limit on the peak height. + { + // initialize curlyline peak height to a desired value. Clamp it to at + // least 1. + constexpr auto curlyLinePeakHeightEm = 0.075f; + _curlyLinePeakHeight = std::max(1.0f, std::roundf(curlyLinePeakHeightEm * font.fontSize)); + + // calc the limit we need to apply + const auto strokeHalfWidth = std::floor(font.underline.height / 2.0f); + const auto underlineMidY = font.underline.position + strokeHalfWidth; + const auto maxDrawableCurlyLinePeakHeight = font.cellSize.y - underlineMidY - font.underline.height; + + // if the limit is <= 0 (no height at all), stick with the desired height. + // This is how we force a curlyline even when there's no space, though it + // might be clipped at the bottom. + if (maxDrawableCurlyLinePeakHeight > 0.0f) + { + _curlyLinePeakHeight = std::min(_curlyLinePeakHeight, maxDrawableCurlyLinePeakHeight); + } - // When it's too small to be curly, make it straight. - if (curlyLinePeakHeight < minCurlyLinePeakHeight) - { - curlyLinePeakHeight = 0; + const auto curlyUnderlinePos = underlineMidY - _curlyLinePeakHeight - font.underline.height; + const auto curlyUnderlineWidth = 2.0f * (_curlyLinePeakHeight + font.underline.height); + const auto curlyUnderlinePosU16 = gsl::narrow_cast(lrintf(curlyUnderlinePos)); + const auto curlyUnderlineWidthU16 = gsl::narrow_cast(lrintf(curlyUnderlineWidth)); + _curlyUnderline = { curlyUnderlinePosU16, curlyUnderlineWidthU16 }; } - // We draw a smaller curly line (-1px) to avoid clipping due to the rounding. - _curlyLineDrawPeakHeight = std::max(0.0f, curlyLinePeakHeight - 1.0f); - - const auto curlyUnderlinePos = font.underline.position - curlyLinePeakHeight; - const auto curlyUnderlineWidth = 2.0f * (curlyLinePeakHeight + strokeHalfWidth); - const auto curlyUnderlinePosU16 = gsl::narrow_cast(lrintf(curlyUnderlinePos)); - const auto curlyUnderlineWidthU16 = gsl::narrow_cast(lrintf(curlyUnderlineWidth)); - _curlyUnderline = { curlyUnderlinePosU16, curlyUnderlineWidthU16 }; - DWrite_GetRenderParams(p.dwriteFactory.get(), &_gamma, &_cleartypeEnhancedContrast, &_grayscaleEnhancedContrast, _textRenderingParams.put()); // Clearing the atlas requires BeginDraw(), which is expensive. Defer this until we need Direct2D anyways. _fontChangedResetGlyphAtlas = true; @@ -576,7 +574,7 @@ void BackendD3D::_recreateConstBuffer(const RenderingPayload& p) const data.enhancedContrast = p.s->font->antialiasingMode == AntialiasingMode::ClearType ? _cleartypeEnhancedContrast : _grayscaleEnhancedContrast; data.underlineWidth = p.s->font->underline.height; data.curlyLineWaveFreq = 2.0f * 3.14f / p.s->font->cellSize.x; - data.curlyLinePeakHeight = _curlyLineDrawPeakHeight; + data.curlyLinePeakHeight = _curlyLinePeakHeight; data.curlyLineCellOffset = p.s->font->underline.position + p.s->font->underline.height / 2.0f; p.deviceContext->UpdateSubresource(_psConstantBuffer.get(), 0, nullptr, &data, 0, 0); } diff --git a/src/renderer/atlas/BackendD3D.h b/src/renderer/atlas/BackendD3D.h index 0befe8fe342..4c248ed3ff3 100644 --- a/src/renderer/atlas/BackendD3D.h +++ b/src/renderer/atlas/BackendD3D.h @@ -291,7 +291,7 @@ namespace Microsoft::Console::Render::Atlas // The bounding rect of _cursorRects in pixels. til::rect _cursorPosition; - f32 _curlyLineDrawPeakHeight = 0; + f32 _curlyLinePeakHeight = 0.0f; FontDecorationPosition _curlyUnderline; bool _requiresContinuousRedraw = false; diff --git a/src/renderer/gdi/paint.cpp b/src/renderer/gdi/paint.cpp index 9722703d105..222576390e2 100644 --- a/src/renderer/gdi/paint.cpp +++ b/src/renderer/gdi/paint.cpp @@ -536,27 +536,30 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) const auto DrawLine = [=](const auto x, const auto y, const auto w, const auto h) { return PatBlt(_hdcMemoryContext, x, y, w, h, PATCOPY); }; - const auto DrawStrokedLine = [&](const auto x, const auto y, const auto w) { + const auto DrawStrokedLine = [&](const til::CoordType x, const til::CoordType y, const unsigned w) { RETURN_HR_IF(E_FAIL, !MoveToEx(_hdcMemoryContext, x, y, nullptr)); - RETURN_HR_IF(E_FAIL, !LineTo(_hdcMemoryContext, x + w, y)); + RETURN_HR_IF(E_FAIL, !LineTo(_hdcMemoryContext, gsl::narrow_cast(x + w), y)); return S_OK; }; - const auto DrawCurlyLine = [&](const auto x, const auto y, const auto cCurlyLines) { + const auto DrawCurlyLine = [&](const til::CoordType x, const til::CoordType y, const size_t cCurlyLines) { const auto curlyLineWidth = fontWidth; - const auto curlyLineHalfWidth = lrintf(curlyLineWidth / 2.0f); - const auto controlPointHeight = gsl::narrow_cast(std::floor(3.5f * _lineMetrics.curlylinePeakHeight)); + const auto curlyLineHalfWidth = std::lround(curlyLineWidth / 2.0f); + const auto controlPointHeight = std::lround(3.5f * _lineMetrics.curlylinePeakHeight); + // Each curlyLine requires 3 `POINT`s const auto cPoints = gsl::narrow(3 * cCurlyLines); std::vector points; points.reserve(cPoints); + auto start = x; - for (auto i = 0u; i < cCurlyLines; i++) + for (size_t i = 0; i < cCurlyLines; i++) { points.emplace_back(start + curlyLineHalfWidth, y - controlPointHeight); points.emplace_back(start + curlyLineHalfWidth, y + controlPointHeight); points.emplace_back(start + curlyLineWidth, y); start += curlyLineWidth; } + RETURN_HR_IF(E_FAIL, !MoveToEx(_hdcMemoryContext, x, y, nullptr)); RETURN_HR_IF(E_FAIL, !PolyBezierTo(_hdcMemoryContext, points.data(), cPoints)); return S_OK; @@ -619,28 +622,26 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) const auto prevPen = wil::SelectObject(_hdcMemoryContext, hpen.get()); RETURN_HR_IF_NULL(E_FAIL, prevPen.get()); - const auto underlineMidY = std::lround(ptTarget.y + _lineMetrics.underlineOffset + _lineMetrics.underlineWidth / 2.0f); if (lines.test(GridLines::Underline)) { - return DrawStrokedLine(ptTarget.x, underlineMidY, widthOfAllCells); + return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset, widthOfAllCells); } else if (lines.test(GridLines::DoubleUnderline)) { - const auto doubleUnderlineBottomLineMidY = std::lround(ptTarget.y + _lineMetrics.underlineOffset2 + _lineMetrics.underlineWidth / 2.0f); - RETURN_IF_FAILED(DrawStrokedLine(ptTarget.x, underlineMidY, widthOfAllCells)); - return DrawStrokedLine(ptTarget.x, doubleUnderlineBottomLineMidY, widthOfAllCells); + RETURN_IF_FAILED(DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset, widthOfAllCells)); + return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset2, widthOfAllCells); } else if (lines.test(GridLines::CurlyUnderline)) { - return DrawCurlyLine(ptTarget.x, underlineMidY, cchLine); + return DrawCurlyLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset, cchLine); } else if (lines.test(GridLines::DottedUnderline)) { - return DrawStrokedLine(ptTarget.x, underlineMidY, widthOfAllCells); + return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset, widthOfAllCells); } else if (lines.test(GridLines::DashedUnderline)) { - return DrawStrokedLine(ptTarget.x, underlineMidY, widthOfAllCells); + return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset, widthOfAllCells); } return S_OK; diff --git a/src/renderer/gdi/state.cpp b/src/renderer/gdi/state.cpp index 099c2f43f78..13fd0ea59fc 100644 --- a/src/renderer/gdi/state.cpp +++ b/src/renderer/gdi/state.cpp @@ -11,15 +11,6 @@ using namespace Microsoft::Console::Render; -namespace -{ - // The max height of Curly line peak in `em` units. - constexpr auto MaxCurlyLinePeakHeightEm = 0.075f; - - // The min height of Curly line peak. - constexpr auto MinCurlyLinePeakHeight = 2.0f; -} - // Routine Description: // - Creates a new GDI-based rendering engine // - NOTE: Will throw if initialization failure. Caller must catch. @@ -406,29 +397,31 @@ GdiEngine::~GdiEngine() _lineMetrics.underlineOffset2 = _lineMetrics.underlineOffset - _lineMetrics.gridlineWidth; } - // Curly line doesn't render properly below 1px stroke width. Make it a straight line. - if (_lineMetrics.underlineWidth < 1) - { - _lineMetrics.curlylinePeakHeight = 0; - } - else + // Since we use GDI pen for drawing, the underline offset should point to + // the center of the underline. + const auto underlineHalfWidth = gsl::narrow_cast(std::floor(_lineMetrics.underlineWidth / 2.0f)); + _lineMetrics.underlineOffset += underlineHalfWidth; + _lineMetrics.underlineOffset2 += underlineHalfWidth; + + // Curlyline is drawn with a desired height relative to the font size. The + // baseline of curlyline is at the middle of singly underline. When there's + // limited space to draw a curlyline, we apply a limit on the peak height. { - // Curlyline uses the gap between cell bottom and singly underline - // position as the height of the wave's peak. The baseline for curly - // line is at the middle of singly underline. The gap could be too big, - // so we also apply a limit on the peak height. - const auto strokeHalfWidth = _lineMetrics.underlineWidth / 2.0f; - const auto underlineMidY = _lineMetrics.underlineOffset + strokeHalfWidth; - const auto cellBottomGap = Font.GetSize().height - underlineMidY - strokeHalfWidth; - const auto maxCurlyLinePeakHeight = MaxCurlyLinePeakHeightEm * fontSize; - auto curlyLinePeakHeight = std::min(cellBottomGap, maxCurlyLinePeakHeight); - - // When it's too small to be curly, make it a straight line. - if (curlyLinePeakHeight < MinCurlyLinePeakHeight) + // initialize curlyline peak height to a desired value. Clamp it to at + // least 1. + constexpr auto curlyLinePeakHeightEm = 0.075f; + _lineMetrics.curlylinePeakHeight = gsl::narrow_cast(std::max(1L, std::lround(curlyLinePeakHeightEm * fontSize))); + + // calc the limit we need to apply + const auto maxDrawableCurlyLinePeakHeight = Font.GetSize().height - _lineMetrics.underlineOffset - _lineMetrics.underlineWidth; + + // if the limit is <= 0 (no height at all), stick with the desired height. + // This is how we force a curlyline even when there's no space, though it + // might be clipped at the bottom. + if (maxDrawableCurlyLinePeakHeight > 0.0f) { - curlyLinePeakHeight = 0.0f; + _lineMetrics.curlylinePeakHeight = std::min(_lineMetrics.curlylinePeakHeight, maxDrawableCurlyLinePeakHeight); } - _lineMetrics.curlylinePeakHeight = gsl::narrow_cast(std::floor(curlyLinePeakHeight)); } // Now find the size of a 0 in this current font and save it for conversions done later. From 306cb404dcd937ee2548dcbe5edbd2a621c53448 Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Wed, 27 Sep 2023 23:20:09 +0530 Subject: [PATCH 111/167] Use MSWord compatible RTF sequence for background text color (#16035) The `GenRTF(...)` was using `\highlight` control word for sending background text color in the RTF format during a copy command. This doesn't work correctly, since many applications (E.g. MSWord) don't support full RGB with `\highlight`, and instead uses an approximation of what is received. For example, `rgb(197, 15, 31)` becomes `rgb(255, 0, 255)`. Also, the standard way of using background colors is `\cbN` control word, which isn't supported as per the [RTF Spec 1.9.1] in Word. But it briefly mentioned a workaround at Pg. 23, which seems to work on all the RTF editors I tested. The PR makes the changes to use `\chshdng0\chcbpatN` for the background coloring. Also did some refactoring to make the implementation concise. ## Validation Steps Performed Verified that the background is correctly copied on below editors: - MSWord - WordPad - LibreOffice - Outlook [RTF Spec 1.9.1]: https://msopenspecs.azureedge.net/files/Archive_References/[MSFT-RTF].pdf (cherry picked from commit 310814bb30473c4d5310dff60ff040cc3719e075) Service-Card-Id: 91349195 Service-Version: 1.19 --- .github/actions/spelling/expect/expect.txt | 2 + src/buffer/out/textBuffer.cpp | 85 +++++++++------------- 2 files changed, 38 insertions(+), 49 deletions(-) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index acd73a56a9d..3ea1b7e958f 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -188,8 +188,10 @@ changelist chaof charinfo CHARSETINFO +chcbpat chh chk +chshdng CHT Cic cielab diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index aa61b63cc29..52a5c6e3b72 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -2277,6 +2277,7 @@ std::string TextBuffer::GenHTML(const TextAndColor& rows, // Routine Description: // - Generates an RTF document based on the passed in text and color data // RTF 1.5 Spec: https://www.biblioscape.com/rtf15_spec.htm +// RTF 1.9.1 Spec: https://msopenspecs.azureedge.net/files/Archive_References/[MSFT-RTF].pdf // Arguments: // - rows - the text and color data we will format & encapsulate // - backgroundColor - default background color for characters, also used in padding @@ -2296,10 +2297,18 @@ std::string TextBuffer::GenRTF(const TextAndColor& rows, const int fontHeightPoi // Standard RTF header. // This is similar to the header generated by WordPad. - // \ansi - specifies that the ANSI char set is used in the current doc - // \ansicpg1252 - represents the ANSI code page which is used to perform the Unicode to ANSI conversion when writing RTF text - // \deff0 - specifies that the default font for the document is the one at index 0 in the font table - // \nouicompat - ? + // \ansi: + // Specifies that the ANSI char set is used in the current doc. + // \ansicpg1252: + // Represents the ANSI code page which is used to perform + // the Unicode to ANSI conversion when writing RTF text. + // \deff0: + // Specifies that the default font for the document is the one + // at index 0 in the font table. + // \nouicompat: + // Some features are blocked by default to maintain compatibility + // with older programs (Eg. Word 97-2003). `nouicompat` disables this + // behavior, and unblocks these features. See: Spec 1.9.1, Pg. 51. rtfBuilder << "\\rtf1\\ansi\\ansicpg1252\\deff0\\nouicompat"; // font table @@ -2308,17 +2317,25 @@ std::string TextBuffer::GenRTF(const TextAndColor& rows, const int fontHeightPoi // map to keep track of colors: // keys are colors represented by COLORREF // values are indices of the corresponding colors in the color table - std::unordered_map colorMap; - auto nextColorIndex = 1; // leave 0 for the default color and start from 1. + std::unordered_map colorMap; // RTF color table std::ostringstream colorTableBuilder; colorTableBuilder << "{\\colortbl ;"; - colorTableBuilder << "\\red" << static_cast(GetRValue(backgroundColor)) - << "\\green" << static_cast(GetGValue(backgroundColor)) - << "\\blue" << static_cast(GetBValue(backgroundColor)) - << ";"; - colorMap[backgroundColor] = nextColorIndex++; + + const auto getColorTableIndex = [&](const COLORREF color) -> size_t { + // Exclude the 0 index for the default color, and start with 1. + + const auto [it, inserted] = colorMap.emplace(color, colorMap.size() + 1); + if (inserted) + { + colorTableBuilder << "\\red" << static_cast(GetRValue(color)) + << "\\green" << static_cast(GetGValue(color)) + << "\\blue" << static_cast(GetBValue(color)) + << ";"; + } + return it->second; + }; // content std::ostringstream contentBuilder; @@ -2328,7 +2345,12 @@ std::string TextBuffer::GenRTF(const TextAndColor& rows, const int fontHeightPoi // \fs specifies font size in half-points i.e. \fs20 results in a font size // of 10 pts. That's why, font size is multiplied by 2 here. contentBuilder << "\\pard\\slmult1\\f0\\fs" << std::to_string(2 * fontHeightPoints) - << "\\highlight1" + // Set the background color for the page. But, the + // standard way (\cbN) to do this isn't supported in Word. + // However, the following control words sequence works + // in Word (and other RTF editors also) for applying the + // text background color. See: Spec 1.9.1, Pg. 23. + << "\\chshdng0\\chcbpat" << getColorTableIndex(backgroundColor) << " "; std::optional fgColor = std::nullopt; @@ -2378,43 +2400,8 @@ std::string TextBuffer::GenRTF(const TextAndColor& rows, const int fontHeightPoi if (colorChanged) { writeAccumulatedChars(false); - - auto bkColorIndex = 0; - if (colorMap.find(bkColor.value()) != colorMap.end()) - { - // color already exists in the map, just retrieve the index - bkColorIndex = colorMap[bkColor.value()]; - } - else - { - // color not present in the map, so add it - colorTableBuilder << "\\red" << static_cast(GetRValue(bkColor.value())) - << "\\green" << static_cast(GetGValue(bkColor.value())) - << "\\blue" << static_cast(GetBValue(bkColor.value())) - << ";"; - colorMap[bkColor.value()] = nextColorIndex; - bkColorIndex = nextColorIndex++; - } - - auto fgColorIndex = 0; - if (colorMap.find(fgColor.value()) != colorMap.end()) - { - // color already exists in the map, just retrieve the index - fgColorIndex = colorMap[fgColor.value()]; - } - else - { - // color not present in the map, so add it - colorTableBuilder << "\\red" << static_cast(GetRValue(fgColor.value())) - << "\\green" << static_cast(GetGValue(fgColor.value())) - << "\\blue" << static_cast(GetBValue(fgColor.value())) - << ";"; - colorMap[fgColor.value()] = nextColorIndex; - fgColorIndex = nextColorIndex++; - } - - contentBuilder << "\\highlight" << bkColorIndex - << "\\cf" << fgColorIndex + contentBuilder << "\\chshdng0\\chcbpat" << getColorTableIndex(bkColor.value()) + << "\\cf" << getColorTableIndex(fgColor.value()) << " "; } From 171a21ad48eca9f57a3ae5692fe9a5c64e9ad276 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 15 Dec 2023 20:17:42 +0100 Subject: [PATCH 112/167] Increase VtInputThread buffer size (#16470) This makes 3 improvements: * 16x larger input buffer size improves behavior when pasting clipboard contents while the win32-input-mode is enabled, as each input character is roughly 15-20x longer after encoding. * Translate UTF8 to UTF16 outside of the console lock. * Preserve the UTF16 buffer between reads for less mallocs. --- src/host/VtInputThread.cpp | 15 ++++++++------- src/host/VtInputThread.hpp | 1 + 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/host/VtInputThread.cpp b/src/host/VtInputThread.cpp index 4a4b773cb02..b9b2aefb0aa 100644 --- a/src/host/VtInputThread.cpp +++ b/src/host/VtInputThread.cpp @@ -69,7 +69,7 @@ DWORD WINAPI VtInputThread::StaticVtInputThreadProc(_In_ LPVOID lpParameter) // - true if you should continue reading bool VtInputThread::DoReadInput() { - char buffer[256]; + char buffer[4096]; DWORD dwRead = 0; const auto ok = ReadFile(_hFile.get(), buffer, ARRAYSIZE(buffer), &dwRead, nullptr); @@ -89,6 +89,12 @@ bool VtInputThread::DoReadInput() return false; } + // If we hit a parsing error, eat it. It's bad utf-8, we can't do anything with it. + if (FAILED_LOG(til::u8u16({ buffer, gsl::narrow_cast(dwRead) }, _wstr, _u8State))) + { + return true; + } + try { // Make sure to call the GLOBAL Lock/Unlock, not the gci's lock/unlock. @@ -99,12 +105,7 @@ bool VtInputThread::DoReadInput() LockConsole(); const auto unlock = wil::scope_exit([&] { UnlockConsole(); }); - std::wstring wstr; - // If we hit a parsing error, eat it. It's bad utf-8, we can't do anything with it. - if (SUCCEEDED_LOG(til::u8u16({ buffer, gsl::narrow_cast(dwRead) }, wstr, _u8State))) - { - _pInputStateMachine->ProcessString(wstr); - } + _pInputStateMachine->ProcessString(_wstr); } CATCH_LOG(); diff --git a/src/host/VtInputThread.hpp b/src/host/VtInputThread.hpp index 7652a53887f..d058c425bc4 100644 --- a/src/host/VtInputThread.hpp +++ b/src/host/VtInputThread.hpp @@ -39,5 +39,6 @@ namespace Microsoft::Console std::unique_ptr _pInputStateMachine; til::u8state _u8State; + std::wstring _wstr; }; } From 28acc102a502f9e5f2f7e216cf87e7d6f20cbc96 Mon Sep 17 00:00:00 2001 From: e82eric Date: Fri, 15 Dec 2023 16:13:49 -0500 Subject: [PATCH 113/167] Highlight all search results while the search box is open (#16227) **FIRST TIME CONTRIBUTOR** Follows the existing selection code as much as possible. Updated logic that finds selection rectangles to also identify search rectangles. Right now, this feature only works in the new Atlas engine -- it uses the background and foreground color bitmaps to quickly and efficiently set the colors of a whole region of text. Closes #7561 Co-authored-by: Leonard Hecker --- src/buffer/out/search.cpp | 23 ++++++++++ src/buffer/out/search.h | 1 + src/cascadia/TerminalControl/ControlCore.cpp | 3 +- src/cascadia/TerminalCore/Terminal.hpp | 4 ++ .../TerminalCore/TerminalSelection.cpp | 35 ++++++++++++++ .../TerminalCore/terminalrenderdata.cpp | 35 ++++++++++++++ .../UnitTests_TerminalCore/ScrollTest.cpp | 1 + src/host/renderData.cpp | 14 ++++++ src/host/renderData.hpp | 2 + src/host/ut_host/VtIoTests.cpp | 9 ++++ src/interactivity/onecore/BgfxEngine.cpp | 5 ++ src/interactivity/onecore/BgfxEngine.hpp | 1 + src/renderer/atlas/AtlasEngine.cpp | 34 ++++++++++++++ src/renderer/atlas/AtlasEngine.h | 1 + src/renderer/base/renderer.cpp | 46 ++++++++++++++++++- src/renderer/base/renderer.hpp | 2 + src/renderer/dx/DxRenderer.cpp | 8 ++++ src/renderer/dx/DxRenderer.hpp | 1 + src/renderer/gdi/gdirenderer.hpp | 1 + src/renderer/gdi/paint.cpp | 7 +++ src/renderer/inc/IRenderData.hpp | 2 + src/renderer/inc/IRenderEngine.hpp | 1 + src/renderer/uia/UiaRenderer.cpp | 5 ++ src/renderer/uia/UiaRenderer.hpp | 1 + src/renderer/vt/paint.cpp | 5 ++ src/renderer/vt/vtrenderer.hpp | 1 + src/renderer/wddmcon/WddmConRenderer.cpp | 5 ++ src/renderer/wddmcon/WddmConRenderer.hpp | 1 + 28 files changed, 251 insertions(+), 3 deletions(-) diff --git a/src/buffer/out/search.cpp b/src/buffer/out/search.cpp index 9707e8fdeaa..fd8942e0bac 100644 --- a/src/buffer/out/search.cpp +++ b/src/buffer/out/search.cpp @@ -111,6 +111,28 @@ const til::point_span* Search::GetCurrent() const noexcept return nullptr; } +void Search::HighlightResults() const +{ + std::vector toSelect; + const auto& textBuffer = _renderData->GetTextBuffer(); + + for (const auto& r : _results) + { + const auto rbStart = textBuffer.BufferToScreenPosition(r.start); + const auto rbEnd = textBuffer.BufferToScreenPosition(r.end); + + til::inclusive_rect re; + re.top = rbStart.y; + re.bottom = rbEnd.y; + re.left = rbStart.x; + re.right = rbEnd.x; + + toSelect.emplace_back(re); + } + + _renderData->SelectSearchRegions(std::move(toSelect)); +} + // Routine Description: // - Takes the found word and selects it in the screen buffer @@ -127,6 +149,7 @@ bool Search::SelectCurrent() const return true; } + _renderData->ClearSelection(); return false; } diff --git a/src/buffer/out/search.h b/src/buffer/out/search.h index c2d035e3e9c..a338f1272c4 100644 --- a/src/buffer/out/search.h +++ b/src/buffer/out/search.h @@ -33,6 +33,7 @@ class Search final void FindNext() noexcept; const til::point_span* GetCurrent() const noexcept; + void HighlightResults() const; bool SelectCurrent() const; const std::vector& Results() const noexcept; diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 734e1b98d2a..4efcabf711a 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1652,6 +1652,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation if (_searcher.ResetIfStale(*GetRenderData(), text, !goForward, !caseSensitive)) { + _searcher.HighlightResults(); _searcher.MoveToCurrentSelection(); _cachedSearchResultRows = {}; } @@ -1668,7 +1669,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation // DO NOT call _updateSelectionUI() here. // We don't want to show the markers so manually tell it to clear it. _terminal->SetBlockSelection(false); - _renderer->TriggerSelection(); _UpdateSelectionMarkersHandlers(*this, winrt::make(true)); foundResults->TotalMatches(gsl::narrow(_searcher.Results().size())); @@ -1676,6 +1676,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation _terminal->AlwaysNotifyOnBufferRotation(true); } + _renderer->TriggerSelection(); // Raise a FoundMatch event, which the control will use to notify // narrator if there was any results in the buffer diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 3e2ffe4c083..603e0e521ba 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -215,10 +215,12 @@ class Microsoft::Terminal::Core::Terminal final : std::pair GetAttributeColors(const TextAttribute& attr) const noexcept override; std::vector GetSelectionRects() noexcept override; + std::vector GetSearchSelectionRects() noexcept override; const bool IsSelectionActive() const noexcept override; const bool IsBlockSelection() const noexcept override; void ClearSelection() override; void SelectNewRegion(const til::point coordStart, const til::point coordEnd) override; + void SelectSearchRegions(std::vector source) override; const til::point GetSelectionAnchor() const noexcept override; const til::point GetSelectionEnd() const noexcept override; const std::wstring_view GetConsoleTitle() const noexcept override; @@ -377,6 +379,7 @@ class Microsoft::Terminal::Core::Terminal final : til::point pivot; }; std::optional _selection; + std::vector _searchSelections; bool _blockSelection = false; std::wstring _wordDelimiters; SelectionExpansion _multiClickSelectionMode = SelectionExpansion::Char; @@ -464,6 +467,7 @@ class Microsoft::Terminal::Core::Terminal final : #pragma region TextSelection // These methods are defined in TerminalSelection.cpp std::vector _GetSelectionRects() const noexcept; + std::vector _GetSearchSelectionRects(Microsoft::Console::Types::Viewport viewport) const noexcept; std::vector _GetSelectionSpans() const noexcept; std::pair _PivotSelection(const til::point targetPos, bool& targetStart) const noexcept; std::pair _ExpandSelectionAnchors(std::pair anchors) const; diff --git a/src/cascadia/TerminalCore/TerminalSelection.cpp b/src/cascadia/TerminalCore/TerminalSelection.cpp index 1978f5738bc..cfbc004b7d7 100644 --- a/src/cascadia/TerminalCore/TerminalSelection.cpp +++ b/src/cascadia/TerminalCore/TerminalSelection.cpp @@ -63,6 +63,40 @@ std::vector Terminal::_GetSelectionRects() const noexcept return result; } +// Method Description: +// - Helper to determine the selected region of the buffer. Used for rendering. +// Return Value: +// - A vector of rectangles representing the regions to select, line by line. They are absolute coordinates relative to the buffer origin. +std::vector Terminal::_GetSearchSelectionRects(Microsoft::Console::Types::Viewport viewport) const noexcept +{ + std::vector result; + try + { + auto lowerIt = std::lower_bound(_searchSelections.begin(), _searchSelections.end(), viewport.Top(), [](const til::inclusive_rect& rect, til::CoordType value) { + return rect.top < value; + }); + + auto upperIt = std::upper_bound(_searchSelections.begin(), _searchSelections.end(), viewport.BottomExclusive(), [](til::CoordType value, const til::inclusive_rect& rect) { + return value < rect.top; + }); + + for (auto selection = lowerIt; selection != upperIt; ++selection) + { + const auto start = til::point{ selection->left, selection->top }; + const auto end = til::point{ selection->right, selection->top }; + const auto adj = _activeBuffer().GetTextRects(start, end, _blockSelection, false); + for (auto a : adj) + { + result.emplace_back(a); + } + } + + return result; + } + CATCH_LOG(); + return result; +} + // Method Description: // - Identical to GetTextRects if it's a block selection, else returns a single span for the whole selection. // Return Value: @@ -824,6 +858,7 @@ void Terminal::_MoveByBuffer(SelectionDirection direction, til::point& pos) noex void Terminal::ClearSelection() { _assertLocked(); + _searchSelections.clear(); _selection = std::nullopt; _selectionMode = SelectionInteractionMode::None; _selectionIsTargetingUrl = false; diff --git a/src/cascadia/TerminalCore/terminalrenderdata.cpp b/src/cascadia/TerminalCore/terminalrenderdata.cpp index 034a65ab784..8dd9b4dae9a 100644 --- a/src/cascadia/TerminalCore/terminalrenderdata.cpp +++ b/src/cascadia/TerminalCore/terminalrenderdata.cpp @@ -150,6 +150,24 @@ catch (...) return {}; } +std::vector Terminal::GetSearchSelectionRects() noexcept +try +{ + std::vector result; + + for (const auto& lineRect : _GetSearchSelectionRects(_GetVisibleViewport())) + { + result.emplace_back(Viewport::FromInclusive(lineRect)); + } + + return result; +} +catch (...) +{ + LOG_CAUGHT_EXCEPTION(); + return {}; +} + void Terminal::SelectNewRegion(const til::point coordStart, const til::point coordEnd) { #pragma warning(push) @@ -188,6 +206,23 @@ void Terminal::SelectNewRegion(const til::point coordStart, const til::point coo SetSelectionEnd(realCoordEnd, SelectionExpansion::Char); } +void Terminal::SelectSearchRegions(std::vector rects) +{ + _searchSelections.clear(); + for (auto& rect : rects) + { + rect.top -= _VisibleStartIndex(); + rect.bottom -= _VisibleStartIndex(); + + const auto realStart = _ConvertToBufferCell(til::point{ rect.left, rect.top }); + const auto realEnd = _ConvertToBufferCell(til::point{ rect.right, rect.bottom }); + + auto rr = til::inclusive_rect{ realStart.x, realStart.y, realEnd.x, realEnd.y }; + + _searchSelections.emplace_back(rr); + } +} + const std::wstring_view Terminal::GetConsoleTitle() const noexcept { _assertLocked(); diff --git a/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp b/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp index d5baebadf26..21022f30333 100644 --- a/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp @@ -59,6 +59,7 @@ namespace HRESULT PaintBufferLine(std::span /*clusters*/, til::point /*coord*/, bool /*fTrimLeft*/, bool /*lineWrapped*/) noexcept { return S_OK; } HRESULT PaintBufferGridLines(GridLineSet /*lines*/, COLORREF /*gridlineColor*/, COLORREF /*underlineColor*/, size_t /*cchLine*/, til::point /*coordTarget*/) noexcept { return S_OK; } HRESULT PaintSelection(const til::rect& /*rect*/) noexcept { return S_OK; } + HRESULT PaintSelections(const std::vector& /*rects*/) noexcept { return S_OK; } HRESULT PaintCursor(const CursorOptions& /*options*/) noexcept { return S_OK; } HRESULT UpdateDrawingBrushes(const TextAttribute& /*textAttributes*/, const RenderSettings& /*renderSettings*/, gsl::not_null /*pData*/, bool /*usingSoftFont*/, bool /*isSettingDefaultBrushes*/) noexcept { return S_OK; } HRESULT UpdateFont(const FontInfoDesired& /*FontInfoDesired*/, _Out_ FontInfo& /*FontInfo*/) noexcept { return S_OK; } diff --git a/src/host/renderData.cpp b/src/host/renderData.cpp index 5c89edae9a9..d9b094626ba 100644 --- a/src/host/renderData.cpp +++ b/src/host/renderData.cpp @@ -79,6 +79,16 @@ std::vector RenderData::GetSelectionRects() noexcept return result; } +// Method Description: +// - Retrieves one rectangle per line describing the area of the viewport +// that should be highlighted in some way to represent a user-interactive selection +// Return Value: +// - Vector of Viewports describing the area selected +std::vector RenderData::GetSearchSelectionRects() noexcept +{ + return {}; +} + // Method Description: // - Lock the console for reading the contents of the buffer. Ensures that the // contents of the console won't be changed in the middle of a paint @@ -371,6 +381,10 @@ void RenderData::SelectNewRegion(const til::point coordStart, const til::point c Selection::Instance().SelectNewRegion(coordStart, coordEnd); } +void RenderData::SelectSearchRegions(std::vector source) +{ +} + // Routine Description: // - Gets the current selection anchor position // Arguments: diff --git a/src/host/renderData.hpp b/src/host/renderData.hpp index 5e649353cd7..52056d7a6f5 100644 --- a/src/host/renderData.hpp +++ b/src/host/renderData.hpp @@ -26,6 +26,7 @@ class RenderData final : const FontInfo& GetFontInfo() const noexcept override; std::vector GetSelectionRects() noexcept override; + std::vector GetSearchSelectionRects() noexcept override; void LockConsole() noexcept override; void UnlockConsole() noexcept override; @@ -54,6 +55,7 @@ class RenderData final : const bool IsBlockSelection() const noexcept override; void ClearSelection() override; void SelectNewRegion(const til::point coordStart, const til::point coordEnd) override; + void SelectSearchRegions(std::vector source) override; const til::point GetSelectionAnchor() const noexcept override; const til::point GetSelectionEnd() const noexcept override; const bool IsUiaDataInitialized() const noexcept override { return true; } diff --git a/src/host/ut_host/VtIoTests.cpp b/src/host/ut_host/VtIoTests.cpp index 902f711771d..7f4796df60d 100644 --- a/src/host/ut_host/VtIoTests.cpp +++ b/src/host/ut_host/VtIoTests.cpp @@ -282,6 +282,11 @@ class MockRenderData : public IRenderData return std::vector{}; } + std::vector GetSearchSelectionRects() noexcept override + { + return std::vector{}; + } + void LockConsole() noexcept override { } @@ -363,6 +368,10 @@ class MockRenderData : public IRenderData { } + void SelectSearchRegions(std::vector /*source*/) override + { + } + const til::point GetSelectionAnchor() const noexcept { return {}; diff --git a/src/interactivity/onecore/BgfxEngine.cpp b/src/interactivity/onecore/BgfxEngine.cpp index d508603d5f9..a730ae8219e 100644 --- a/src/interactivity/onecore/BgfxEngine.cpp +++ b/src/interactivity/onecore/BgfxEngine.cpp @@ -161,6 +161,11 @@ CATCH_RETURN() return S_OK; } +[[nodiscard]] HRESULT BgfxEngine::PaintSelections(const std::vector& /*rects*/) noexcept +{ + return S_OK; +} + [[nodiscard]] HRESULT BgfxEngine::PaintCursor(const CursorOptions& options) noexcept try { diff --git a/src/interactivity/onecore/BgfxEngine.hpp b/src/interactivity/onecore/BgfxEngine.hpp index e9759ce0306..c787baba392 100644 --- a/src/interactivity/onecore/BgfxEngine.hpp +++ b/src/interactivity/onecore/BgfxEngine.hpp @@ -53,6 +53,7 @@ namespace Microsoft::Console::Render const bool lineWrapped) noexcept override; [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; + [[nodiscard]] HRESULT PaintSelections(const std::vector& rects) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; diff --git a/src/renderer/atlas/AtlasEngine.cpp b/src/renderer/atlas/AtlasEngine.cpp index 7438b93543e..93821394ce1 100644 --- a/src/renderer/atlas/AtlasEngine.cpp +++ b/src/renderer/atlas/AtlasEngine.cpp @@ -414,6 +414,40 @@ try } CATCH_RETURN() +[[nodiscard]] HRESULT AtlasEngine::PaintSelections(const std::vector& rects) noexcept +try +{ + if (rects.empty()) + { + return S_OK; + } + + for (const auto& rect : rects) + { + const auto y = gsl::narrow_cast(clamp(rect.top, 0, _p.s->viewportCellCount.y)); + const auto from = gsl::narrow_cast(clamp(rect.left, 0, _p.s->viewportCellCount.x - 1)); + const auto to = gsl::narrow_cast(clamp(rect.right, from, _p.s->viewportCellCount.x)); + + if (rect.bottom <= 0 || rect.top >= _p.s->viewportCellCount.y) + { + continue; + } + + const auto bg = &_p.backgroundBitmap[_p.colorBitmapRowStride * y]; + const auto fg = &_p.foregroundBitmap[_p.colorBitmapRowStride * y]; + std::fill(bg + from, bg + to, 0xff3296ff); + std::fill(fg + from, fg + to, 0xff000000); + } + + for (int i = 0; i < 2; ++i) + { + _p.colorBitmapGenerations[i].bump(); + } + + return S_OK; +} +CATCH_RETURN() + [[nodiscard]] HRESULT AtlasEngine::PaintCursor(const CursorOptions& options) noexcept try { diff --git a/src/renderer/atlas/AtlasEngine.h b/src/renderer/atlas/AtlasEngine.h index 4ec09a9e931..8d6d76795c0 100644 --- a/src/renderer/atlas/AtlasEngine.h +++ b/src/renderer/atlas/AtlasEngine.h @@ -45,6 +45,7 @@ namespace Microsoft::Console::Render::Atlas [[nodiscard]] HRESULT PaintBufferLine(std::span clusters, til::point coord, bool fTrimLeft, bool lineWrapped) noexcept override; [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; + [[nodiscard]] HRESULT PaintSelections(const std::vector& rects) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; [[nodiscard]] HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, const RenderSettings& renderSettings, gsl::not_null pData, bool usingSoftFont, bool isSettingDefaultBrushes) noexcept override; [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) noexcept override; diff --git a/src/renderer/base/renderer.cpp b/src/renderer/base/renderer.cpp index 0b1d6b004cb..c7c5c491815 100644 --- a/src/renderer/base/renderer.cpp +++ b/src/renderer/base/renderer.cpp @@ -362,6 +362,7 @@ void Renderer::TriggerSelection() { // Get selection rectangles auto rects = _GetSelectionRects(); + auto searchSelections = _GetSearchSelectionRects(); // Make a viewport representing the coordinates that are currently presentable. const til::rect viewport{ _pData->GetViewport().Dimensions() }; @@ -374,11 +375,14 @@ void Renderer::TriggerSelection() FOREACH_ENGINE(pEngine) { + LOG_IF_FAILED(pEngine->InvalidateSelection(_previousSearchSelection)); LOG_IF_FAILED(pEngine->InvalidateSelection(_previousSelection)); + LOG_IF_FAILED(pEngine->InvalidateSelection(searchSelections)); LOG_IF_FAILED(pEngine->InvalidateSelection(rects)); } _previousSelection = std::move(rects); + _previousSearchSelection = std::move(searchSelections); NotifyPaintFrame(); } @@ -1206,9 +1210,20 @@ void Renderer::_PaintSelection(_In_ IRenderEngine* const pEngine) // Get selection rectangles const auto rectangles = _GetSelectionRects(); - for (const auto& rect : rectangles) + const auto searchRectangles = _GetSearchSelectionRects(); + + std::vector dirtySearchRectangles; + for (auto& dirtyRect : dirtyAreas) { - for (auto& dirtyRect : dirtyAreas) + for (const auto& sr : searchRectangles) + { + if (const auto rectCopy = sr & dirtyRect) + { + dirtySearchRectangles.emplace_back(rectCopy); + } + } + + for (const auto& rect : rectangles) { if (const auto rectCopy = rect & dirtyRect) { @@ -1216,6 +1231,11 @@ void Renderer::_PaintSelection(_In_ IRenderEngine* const pEngine) } } } + + if (!dirtySearchRectangles.empty()) + { + LOG_IF_FAILED(pEngine->PaintSelections(std::move(dirtySearchRectangles))); + } } CATCH_LOG(); } @@ -1281,6 +1301,28 @@ std::vector Renderer::_GetSelectionRects() const return result; } +std::vector Renderer::_GetSearchSelectionRects() const +{ + const auto& buffer = _pData->GetTextBuffer(); + auto rects = _pData->GetSearchSelectionRects(); + // Adjust rectangles to viewport + auto view = _pData->GetViewport(); + + std::vector result; + result.reserve(rects.size()); + + for (auto rect : rects) + { + // Convert buffer offsets to the equivalent range of screen cells + // expected by callers, taking line rendition into account. + const auto lineRendition = buffer.GetLineRendition(rect.Top()); + rect = Viewport::FromInclusive(BufferToScreenLine(rect.ToInclusive(), lineRendition)); + result.emplace_back(view.ConvertToOrigin(rect).ToExclusive()); + } + + return result; +} + // Method Description: // - Offsets all of the selection rectangles we might be holding onto // as the previously selected area. If the whole viewport scrolls, diff --git a/src/renderer/base/renderer.hpp b/src/renderer/base/renderer.hpp index ae3ed2cfda9..1cd61799a8f 100644 --- a/src/renderer/base/renderer.hpp +++ b/src/renderer/base/renderer.hpp @@ -110,6 +110,7 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT _UpdateDrawingBrushes(_In_ IRenderEngine* const pEngine, const TextAttribute attr, const bool usingSoftFont, const bool isSettingDefaultBrushes); [[nodiscard]] HRESULT _PerformScrolling(_In_ IRenderEngine* const pEngine); std::vector _GetSelectionRects() const; + std::vector _GetSearchSelectionRects() const; void _ScrollPreviousSelection(const til::point delta); [[nodiscard]] HRESULT _PaintTitle(IRenderEngine* const pEngine); bool _isInHoveredInterval(til::point coordTarget) const noexcept; @@ -127,6 +128,7 @@ namespace Microsoft::Console::Render Microsoft::Console::Types::Viewport _viewport; std::vector _clusterBuffer; std::vector _previousSelection; + std::vector _previousSearchSelection; std::function _pfnBackgroundColorChanged; std::function _pfnFrameColorChanged; std::function _pfnRendererEnteredErrorState; diff --git a/src/renderer/dx/DxRenderer.cpp b/src/renderer/dx/DxRenderer.cpp index 064d219f72a..e03cfb20081 100644 --- a/src/renderer/dx/DxRenderer.cpp +++ b/src/renderer/dx/DxRenderer.cpp @@ -1844,6 +1844,14 @@ try } CATCH_RETURN() +[[nodiscard]] HRESULT DxEngine::PaintSelections(const std::vector& rects) noexcept +try +{ + UNREFERENCED_PARAMETER(rects); + return S_OK; +} +CATCH_RETURN() + // Routine Description: // - Does nothing. Our cursor is drawn in CustomTextRenderer::DrawGlyphRun, // either above or below the text. diff --git a/src/renderer/dx/DxRenderer.hpp b/src/renderer/dx/DxRenderer.hpp index 877b3f1adfa..990c77e18e5 100644 --- a/src/renderer/dx/DxRenderer.hpp +++ b/src/renderer/dx/DxRenderer.hpp @@ -108,6 +108,7 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; + [[nodiscard]] HRESULT PaintSelections(const std::vector& rect) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; diff --git a/src/renderer/gdi/gdirenderer.hpp b/src/renderer/gdi/gdirenderer.hpp index 2cc5b4dc14c..1d855d2746e 100644 --- a/src/renderer/gdi/gdirenderer.hpp +++ b/src/renderer/gdi/gdirenderer.hpp @@ -57,6 +57,7 @@ namespace Microsoft::Console::Render const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; + [[nodiscard]] HRESULT PaintSelections(const std::vector& rects) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; diff --git a/src/renderer/gdi/paint.cpp b/src/renderer/gdi/paint.cpp index 222576390e2..5951c57ef29 100644 --- a/src/renderer/gdi/paint.cpp +++ b/src/renderer/gdi/paint.cpp @@ -815,6 +815,13 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) return S_OK; } +[[nodiscard]] HRESULT GdiEngine::PaintSelections(const std::vector& rects) noexcept +{ + UNREFERENCED_PARAMETER(rects); + + return S_OK; +} + #ifdef DBG void GdiEngine::_CreateDebugWindow() diff --git a/src/renderer/inc/IRenderData.hpp b/src/renderer/inc/IRenderData.hpp index 69e0dc2a0e6..cfc035a7f9b 100644 --- a/src/renderer/inc/IRenderData.hpp +++ b/src/renderer/inc/IRenderData.hpp @@ -47,6 +47,7 @@ namespace Microsoft::Console::Render virtual const TextBuffer& GetTextBuffer() const noexcept = 0; virtual const FontInfo& GetFontInfo() const noexcept = 0; virtual std::vector GetSelectionRects() noexcept = 0; + virtual std::vector GetSearchSelectionRects() noexcept = 0; virtual void LockConsole() noexcept = 0; virtual void UnlockConsole() noexcept = 0; @@ -71,6 +72,7 @@ namespace Microsoft::Console::Render virtual const bool IsBlockSelection() const = 0; virtual void ClearSelection() = 0; virtual void SelectNewRegion(const til::point coordStart, const til::point coordEnd) = 0; + virtual void SelectSearchRegions(std::vector source) = 0; virtual const til::point GetSelectionAnchor() const noexcept = 0; virtual const til::point GetSelectionEnd() const noexcept = 0; virtual const bool IsUiaDataInitialized() const noexcept = 0; diff --git a/src/renderer/inc/IRenderEngine.hpp b/src/renderer/inc/IRenderEngine.hpp index afcaa5aff59..016f0f10baa 100644 --- a/src/renderer/inc/IRenderEngine.hpp +++ b/src/renderer/inc/IRenderEngine.hpp @@ -78,6 +78,7 @@ namespace Microsoft::Console::Render [[nodiscard]] virtual HRESULT PaintBufferLine(std::span clusters, til::point coord, bool fTrimLeft, bool lineWrapped) noexcept = 0; [[nodiscard]] virtual HRESULT PaintBufferGridLines(GridLineSet lines, COLORREF gridlineColor, COLORREF underlineColor, size_t cchLine, til::point coordTarget) noexcept = 0; [[nodiscard]] virtual HRESULT PaintSelection(const til::rect& rect) noexcept = 0; + [[nodiscard]] virtual HRESULT PaintSelections(const std::vector& rects) noexcept = 0; [[nodiscard]] virtual HRESULT PaintCursor(const CursorOptions& options) noexcept = 0; [[nodiscard]] virtual HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, const RenderSettings& renderSettings, gsl::not_null pData, bool usingSoftFont, bool isSettingDefaultBrushes) noexcept = 0; [[nodiscard]] virtual HRESULT UpdateFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) noexcept = 0; diff --git a/src/renderer/uia/UiaRenderer.cpp b/src/renderer/uia/UiaRenderer.cpp index d1e72fbd0eb..a245491dead 100644 --- a/src/renderer/uia/UiaRenderer.cpp +++ b/src/renderer/uia/UiaRenderer.cpp @@ -386,6 +386,11 @@ void UiaEngine::WaitUntilCanRender() noexcept return S_FALSE; } +[[nodiscard]] HRESULT UiaEngine::PaintSelections(const std::vector& /*rect*/) noexcept +{ + return S_FALSE; +} + // Routine Description: // - Draws the cursor on the screen // For UIA, this doesn't mean anything. So do nothing. diff --git a/src/renderer/uia/UiaRenderer.hpp b/src/renderer/uia/UiaRenderer.hpp index 4625ca155cb..bd1734c5d64 100644 --- a/src/renderer/uia/UiaRenderer.hpp +++ b/src/renderer/uia/UiaRenderer.hpp @@ -51,6 +51,7 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT PaintBufferLine(const std::span clusters, const til::point coord, const bool fTrimLeft, const bool lineWrapped) noexcept override; [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; + [[nodiscard]] HRESULT PaintSelections(const std::vector& rects) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; [[nodiscard]] HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, const RenderSettings& renderSettings, const gsl::not_null pData, const bool usingSoftFont, const bool isSettingDefaultBrushes) noexcept override; [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) noexcept override; diff --git a/src/renderer/vt/paint.cpp b/src/renderer/vt/paint.cpp index 99f8853a98f..d8e3e66686b 100644 --- a/src/renderer/vt/paint.cpp +++ b/src/renderer/vt/paint.cpp @@ -233,6 +233,11 @@ using namespace Microsoft::Console::Types; return S_OK; } +[[nodiscard]] HRESULT VtEngine::PaintSelections(const std::vector& /*rect*/) noexcept +{ + return S_OK; +} + // Routine Description: // - Write a VT sequence to change the current colors of text. Writes true RGB // color sequences. diff --git a/src/renderer/vt/vtrenderer.hpp b/src/renderer/vt/vtrenderer.hpp index 676672ee814..7a974850afc 100644 --- a/src/renderer/vt/vtrenderer.hpp +++ b/src/renderer/vt/vtrenderer.hpp @@ -64,6 +64,7 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT PaintBufferLine(std::span clusters, til::point coord, bool fTrimLeft, bool lineWrapped) noexcept override; [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; + [[nodiscard]] HRESULT PaintSelections(const std::vector& rects) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) noexcept override; [[nodiscard]] HRESULT UpdateDpi(int iDpi) noexcept override; diff --git a/src/renderer/wddmcon/WddmConRenderer.cpp b/src/renderer/wddmcon/WddmConRenderer.cpp index fda8ae6c21b..db729467cae 100644 --- a/src/renderer/wddmcon/WddmConRenderer.cpp +++ b/src/renderer/wddmcon/WddmConRenderer.cpp @@ -298,6 +298,11 @@ CATCH_RETURN() return S_OK; } +[[nodiscard]] HRESULT WddmConEngine::PaintSelections(const std::vector& /*rects*/) noexcept +{ + return S_OK; +} + [[nodiscard]] HRESULT WddmConEngine::PaintCursor(const CursorOptions& /*options*/) noexcept { return S_OK; diff --git a/src/renderer/wddmcon/WddmConRenderer.hpp b/src/renderer/wddmcon/WddmConRenderer.hpp index 954dc226946..930fbe6dea6 100644 --- a/src/renderer/wddmcon/WddmConRenderer.hpp +++ b/src/renderer/wddmcon/WddmConRenderer.hpp @@ -46,6 +46,7 @@ namespace Microsoft::Console::Render const bool lineWrapped) noexcept override; [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; + [[nodiscard]] HRESULT PaintSelections(const std::vector& rects) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; From 99193c9a3f462b3177b6f596706f11be2074efa5 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sat, 16 Dec 2023 00:02:24 +0100 Subject: [PATCH 114/167] Put the final touches on GDI's underlines (#16475) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While #16444 left wavy lines in an amazing state already, there were a few more things that could be done to make GDI look more consistent with other well known Windows applications. But before that, a couple unrelated, but helpful changes were made: * `GdiEngine::UpdateFont` was heavily modified to do all calculations in floats. All modern CPUs have fast FPUs and even the fairly slow `lroundf` function is so fast (relatively) nowadays that in a cold path like this, we can liberally call it to convert back to `int`s. This makes intermediate calculation more accurate and consistent. * `GdiEngine::PaintBufferGridLines` was exception-unsafe due to its use of a `std::vector` with catch clause and this PR fixes that. Additionally, the vector was swapped out with a `til::small_vector` to reduce heap allocations. (Arena allocators!) * RenderingTests was updated to cover styled underlines With that in place, these improvements were done: * Word's double-underline algorithm was ported over from `AtlasEngine`. It uses a half underline-width (aka `thinLineWidth`) which will now also be used for wavy lines to make them look a bit more filigrane. * The Bézier curve for wavy/curly underlines was modified to use control points at (0.5,0.5) and (0.5,-0.5) respectively. This results in a maxima at y=0.1414 which is much closer to a sine curve with a maxima at 1/(2pi) = 0.1592. Previously, the maxima was a lot higher (roughly 4x) depending on the aspect ratio of the glyphs. * Wavy underlines don't depend on the aspect ratio of glyphs anymore. This previously led to several problems depending on the exact font. The old renderer would draw exactly 3 periods of the wave into each cell which would also ensure continuity between cells. Unfortunately, this meant that waves could look inconsistent. The new approach always uses the aforementioned sine-like waves. * The wavy underline offset was clamped so that it's never outside of bounds of a line. This avoids clipping. ## Validation Steps Performed * Compile RenderingTests and run it * Using Consolas, MS Gothic and Cascadia Code while Ctrl+Scrolling up and down works as expected without clipping ✅ --- .github/actions/spelling/expect/expect.txt | 15 -- src/renderer/gdi/gdirenderer.hpp | 10 +- src/renderer/gdi/paint.cpp | 85 ++++++----- src/renderer/gdi/state.cpp | 160 ++++++++++++--------- src/tools/RenderingTests/main.cpp | 147 +++++++++++++------ 5 files changed, 256 insertions(+), 161 deletions(-) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 2de1494a028..6ae7a458582 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -294,7 +294,6 @@ CPPCORECHECK cppcorecheckrules cpprestsdk cppwinrt -CProc cpx CREATESCREENBUFFER CREATESTRUCT @@ -324,7 +323,6 @@ CTRLVOLUME Ctxt CUF cupxy -curlyline CURRENTFONT currentmode CURRENTPAGE @@ -583,7 +581,6 @@ EXETYPE exeuwp exewin exitwin -expectedinput EXPUNGECOMMANDHISTORY EXSTYLE EXTENDEDEDITKEY @@ -795,7 +792,6 @@ HPA hpcon HPCON hpen -hpj HPR HProvider HREDRAW @@ -978,7 +974,6 @@ logissue losslessly loword lparam -LPCCH lpch LPCPLINFO LPCREATESTRUCT @@ -1177,7 +1172,6 @@ NOMOVE NONALERT nonbreaking nonclient -NONCONST NONINFRINGEMENT NONPREROTATED nonspace @@ -1525,17 +1519,14 @@ rgbs rgci rgfae rgfte -rgi rgn rgp rgpwsz rgrc -rgui rgw RIGHTALIGN RIGHTBUTTON riid -Rike RIS roadmap robomac @@ -1685,7 +1676,6 @@ SOLIDBOX Solutiondir somefile sourced -spammy SRCCODEPAGE SRCCOPY SRCINVERT @@ -1831,7 +1821,6 @@ Tpp Tpqrst tracelog tracelogging -traceloggingprovider traceviewpp trackbar TRACKCOMPOSITION @@ -1915,7 +1904,6 @@ USEFILLATTRIBUTE USEGLYPHCHARS USEHICON USEPOSITION -userbase USERDATA userdpiapi Userp @@ -1956,8 +1944,6 @@ vpack vpackdirectory VPACKMANIFESTDIRECTORY VPR -VProc -VRaw VREDRAW vsc vsconfig @@ -2001,7 +1987,6 @@ WCIA WCIW WCSHELPER wcsicmp -wcsnicmp wcsrev wddm wddmcon diff --git a/src/renderer/gdi/gdirenderer.hpp b/src/renderer/gdi/gdirenderer.hpp index 1d855d2746e..13ecdd25372 100644 --- a/src/renderer/gdi/gdirenderer.hpp +++ b/src/renderer/gdi/gdirenderer.hpp @@ -117,12 +117,16 @@ namespace Microsoft::Console::Render struct LineMetrics { int gridlineWidth; - int underlineOffset; - int underlineOffset2; + int thinLineWidth; + int underlineCenter; int underlineWidth; + int doubleUnderlinePosTop; + int doubleUnderlinePosBottom; int strikethroughOffset; int strikethroughWidth; - int curlylinePeakHeight; + int curlyLineCenter; + int curlyLinePeriod; + int curlyLineControlPointOffset; }; LineMetrics _lineMetrics; diff --git a/src/renderer/gdi/paint.cpp b/src/renderer/gdi/paint.cpp index 5951c57ef29..4dbb82d9c6d 100644 --- a/src/renderer/gdi/paint.cpp +++ b/src/renderer/gdi/paint.cpp @@ -2,9 +2,10 @@ // Licensed under the MIT license. #include "precomp.h" -#include #include "gdirenderer.hpp" +#include + #include "../inc/unicode.hpp" #pragma hdrstop @@ -516,6 +517,7 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) // Return Value: // - S_OK or suitable GDI HRESULT error or E_FAIL for GDI errors in functions that don't reliably return a specific error code. [[nodiscard]] HRESULT GdiEngine::PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept +try { LOG_IF_FAILED(_FlushBufferLines()); @@ -531,38 +533,50 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) // Get the font size so we know the size of the rectangle lines we'll be inscribing. const auto fontWidth = _GetFontSize().width; const auto fontHeight = _GetFontSize().height; - const auto widthOfAllCells = fontWidth * gsl::narrow_cast(cchLine); + const auto widthOfAllCells = fontWidth * gsl::narrow_cast(cchLine); - const auto DrawLine = [=](const auto x, const auto y, const auto w, const auto h) { + const auto DrawLine = [=](const til::CoordType x, const til::CoordType y, const til::CoordType w, const til::CoordType h) { return PatBlt(_hdcMemoryContext, x, y, w, h, PATCOPY); }; - const auto DrawStrokedLine = [&](const til::CoordType x, const til::CoordType y, const unsigned w) { + const auto DrawStrokedLine = [&](const til::CoordType x, const til::CoordType y, const til::CoordType w) { RETURN_HR_IF(E_FAIL, !MoveToEx(_hdcMemoryContext, x, y, nullptr)); - RETURN_HR_IF(E_FAIL, !LineTo(_hdcMemoryContext, gsl::narrow_cast(x + w), y)); + RETURN_HR_IF(E_FAIL, !LineTo(_hdcMemoryContext, x + w, y)); return S_OK; }; - const auto DrawCurlyLine = [&](const til::CoordType x, const til::CoordType y, const size_t cCurlyLines) { - const auto curlyLineWidth = fontWidth; - const auto curlyLineHalfWidth = std::lround(curlyLineWidth / 2.0f); - const auto controlPointHeight = std::lround(3.5f * _lineMetrics.curlylinePeakHeight); - - // Each curlyLine requires 3 `POINT`s - const auto cPoints = gsl::narrow(3 * cCurlyLines); - std::vector points; - points.reserve(cPoints); - - auto start = x; - for (size_t i = 0; i < cCurlyLines; i++) + const auto DrawCurlyLine = [&](const til::CoordType begX, const til::CoordType y, const til::CoordType width) { + const auto period = _lineMetrics.curlyLinePeriod; + const auto halfPeriod = period / 2; + const auto controlPointOffset = _lineMetrics.curlyLineControlPointOffset; + + // To ensure proper continuity of the wavy line between cells of different line color + // this code starts/ends the line earlier/later than it should and then clips it. + // Clipping in GDI is expensive, but it was the easiest approach. + // I've noticed that subtracting -1px prevents missing pixels when GDI draws. They still + // occur at certain (small) font sizes, but I couldn't figure out how to prevent those. + const auto lineStart = ((begX - 1) / period) * period; + const auto lineEnd = begX + width; + + IntersectClipRect(_hdcMemoryContext, begX, ptTarget.y, begX + width, ptTarget.y + fontHeight); + const auto restoreRegion = wil::scope_exit([&]() { + // Luckily no one else uses clip regions. They're weird to use. + SelectClipRgn(_hdcMemoryContext, nullptr); + }); + + // You can assume that each cell has roughly 5 POINTs on average. 128 POINTs is 1KiB. + til::small_vector points; + + // This is the start point of the Bézier curve. + points.emplace_back(lineStart, y); + + for (auto x = lineStart; x < lineEnd; x += period) { - points.emplace_back(start + curlyLineHalfWidth, y - controlPointHeight); - points.emplace_back(start + curlyLineHalfWidth, y + controlPointHeight); - points.emplace_back(start + curlyLineWidth, y); - start += curlyLineWidth; + points.emplace_back(x + halfPeriod, y - controlPointOffset); + points.emplace_back(x + halfPeriod, y + controlPointOffset); + points.emplace_back(x + period, y); } - RETURN_HR_IF(E_FAIL, !MoveToEx(_hdcMemoryContext, x, y, nullptr)); - RETURN_HR_IF(E_FAIL, !PolyBezierTo(_hdcMemoryContext, points.data(), cPoints)); - return S_OK; + const auto cpt = gsl::narrow_cast(points.size()); + return PolyBezier(_hdcMemoryContext, points.data(), cpt); }; if (lines.test(GridLines::Left)) @@ -605,7 +619,6 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y, widthOfAllCells, _lineMetrics.strikethroughWidth)); } - // Create a pen matching the underline style. DWORD underlinePenType = PS_SOLID; if (lines.test(GridLines::DottedUnderline)) { @@ -615,8 +628,15 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) { underlinePenType = PS_DASH; } + + DWORD underlineWidth = _lineMetrics.underlineWidth; + if (lines.any(GridLines::DoubleUnderline, GridLines::CurlyUnderline)) + { + underlineWidth = _lineMetrics.thinLineWidth; + } + const LOGBRUSH brushProp{ .lbStyle = BS_SOLID, .lbColor = underlineColor }; - wil::unique_hpen hpen(ExtCreatePen(underlinePenType | PS_GEOMETRIC | PS_ENDCAP_FLAT, _lineMetrics.underlineWidth, &brushProp, 0, nullptr)); + wil::unique_hpen hpen(ExtCreatePen(underlinePenType | PS_GEOMETRIC | PS_ENDCAP_FLAT, underlineWidth, &brushProp, 0, nullptr)); // Apply the pen. const auto prevPen = wil::SelectObject(_hdcMemoryContext, hpen.get()); @@ -624,28 +644,29 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) if (lines.test(GridLines::Underline)) { - return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset, widthOfAllCells); + return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineCenter, widthOfAllCells); } else if (lines.test(GridLines::DoubleUnderline)) { - RETURN_IF_FAILED(DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset, widthOfAllCells)); - return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset2, widthOfAllCells); + RETURN_IF_FAILED(DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.doubleUnderlinePosTop, widthOfAllCells)); + return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.doubleUnderlinePosBottom, widthOfAllCells); } else if (lines.test(GridLines::CurlyUnderline)) { - return DrawCurlyLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset, cchLine); + return DrawCurlyLine(ptTarget.x, ptTarget.y + _lineMetrics.curlyLineCenter, widthOfAllCells); } else if (lines.test(GridLines::DottedUnderline)) { - return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset, widthOfAllCells); + return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineCenter, widthOfAllCells); } else if (lines.test(GridLines::DashedUnderline)) { - return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset, widthOfAllCells); + return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineCenter, widthOfAllCells); } return S_OK; } +CATCH_RETURN(); // Routine Description: // - Draws the cursor on the screen diff --git a/src/renderer/gdi/state.cpp b/src/renderer/gdi/state.cpp index 13fd0ea59fc..f993c6f74e6 100644 --- a/src/renderer/gdi/state.cpp +++ b/src/renderer/gdi/state.cpp @@ -344,85 +344,109 @@ GdiEngine::~GdiEngine() // There is no font metric for the grid line width, so we use a small // multiple of the font size, which typically rounds to a pixel. - const auto fontSize = _tmFontMetrics.tmHeight - _tmFontMetrics.tmInternalLeading; - _lineMetrics.gridlineWidth = std::lround(fontSize * 0.025); + const auto cellHeight = static_cast(Font.GetSize().height); + const auto fontSize = static_cast(_tmFontMetrics.tmHeight - _tmFontMetrics.tmInternalLeading); + const auto baseline = static_cast(_tmFontMetrics.tmAscent); + float idealGridlineWidth = std::max(1.0f, fontSize * 0.025f); + float idealUnderlineTop = 0; + float idealUnderlineWidth = 0; + float idealStrikethroughTop = 0; + float idealStrikethroughWidth = 0; OUTLINETEXTMETRICW outlineMetrics; if (GetOutlineTextMetricsW(_hdcMemoryContext, sizeof(outlineMetrics), &outlineMetrics)) { // For TrueType fonts, the other line metrics can be obtained from // the font's outline text metric structure. - _lineMetrics.underlineOffset = outlineMetrics.otmsUnderscorePosition; - _lineMetrics.underlineWidth = outlineMetrics.otmsUnderscoreSize; - _lineMetrics.strikethroughOffset = outlineMetrics.otmsStrikeoutPosition; - _lineMetrics.strikethroughWidth = outlineMetrics.otmsStrikeoutSize; + idealUnderlineTop = static_cast(baseline - outlineMetrics.otmsUnderscorePosition); + idealUnderlineWidth = static_cast(outlineMetrics.otmsUnderscoreSize); + idealStrikethroughWidth = static_cast(outlineMetrics.otmsStrikeoutSize); + idealStrikethroughTop = static_cast(baseline - outlineMetrics.otmsStrikeoutPosition); } else { - // If we can't obtain the outline metrics for the font, we just pick - // some reasonable values for the offsets and widths. - _lineMetrics.underlineOffset = -std::lround(fontSize * 0.05); - _lineMetrics.underlineWidth = _lineMetrics.gridlineWidth; - _lineMetrics.strikethroughOffset = std::lround(_tmFontMetrics.tmAscent / 3.0); - _lineMetrics.strikethroughWidth = _lineMetrics.gridlineWidth; + // If we can't obtain the outline metrics for the font, we just pick some reasonable values for the offsets and widths. + idealUnderlineTop = std::max(1.0f, roundf(baseline - fontSize * 0.05f)); + idealUnderlineWidth = idealGridlineWidth; + idealStrikethroughTop = std::max(1.0f, roundf(baseline * (2.0f / 3.0f))); + idealStrikethroughWidth = idealGridlineWidth; } - // We always want the lines to be visible, so if a stroke width ends - // up being zero, we need to make it at least 1 pixel. - _lineMetrics.gridlineWidth = std::max(_lineMetrics.gridlineWidth, 1); - _lineMetrics.underlineWidth = std::max(_lineMetrics.underlineWidth, 1); - _lineMetrics.strikethroughWidth = std::max(_lineMetrics.strikethroughWidth, 1); - - // Offsets are relative to the base line of the font, so we subtract - // from the ascent to get an offset relative to the top of the cell. - const auto ascent = _tmFontMetrics.tmAscent; - _lineMetrics.underlineOffset = ascent - _lineMetrics.underlineOffset; - _lineMetrics.strikethroughOffset = ascent - _lineMetrics.strikethroughOffset; - - // For double underlines we need a second offset, just below the first, - // but with a bit of a gap (about double the grid line width). - _lineMetrics.underlineOffset2 = _lineMetrics.underlineOffset + - _lineMetrics.underlineWidth + - std::lround(fontSize * 0.05); - - // However, we don't want the underline to extend past the bottom of the - // cell, so we clamp the offset to fit just inside. - const auto maxUnderlineOffset = Font.GetSize().height - _lineMetrics.underlineWidth; - _lineMetrics.underlineOffset2 = std::min(_lineMetrics.underlineOffset2, maxUnderlineOffset); - - // But if the resulting gap isn't big enough even to register as a thicker - // line, it's better to place the second line slightly above the first. - if (_lineMetrics.underlineOffset2 < _lineMetrics.underlineOffset + _lineMetrics.gridlineWidth) - { - _lineMetrics.underlineOffset2 = _lineMetrics.underlineOffset - _lineMetrics.gridlineWidth; - } - - // Since we use GDI pen for drawing, the underline offset should point to - // the center of the underline. - const auto underlineHalfWidth = gsl::narrow_cast(std::floor(_lineMetrics.underlineWidth / 2.0f)); - _lineMetrics.underlineOffset += underlineHalfWidth; - _lineMetrics.underlineOffset2 += underlineHalfWidth; - - // Curlyline is drawn with a desired height relative to the font size. The - // baseline of curlyline is at the middle of singly underline. When there's - // limited space to draw a curlyline, we apply a limit on the peak height. - { - // initialize curlyline peak height to a desired value. Clamp it to at - // least 1. - constexpr auto curlyLinePeakHeightEm = 0.075f; - _lineMetrics.curlylinePeakHeight = gsl::narrow_cast(std::max(1L, std::lround(curlyLinePeakHeightEm * fontSize))); - - // calc the limit we need to apply - const auto maxDrawableCurlyLinePeakHeight = Font.GetSize().height - _lineMetrics.underlineOffset - _lineMetrics.underlineWidth; - - // if the limit is <= 0 (no height at all), stick with the desired height. - // This is how we force a curlyline even when there's no space, though it - // might be clipped at the bottom. - if (maxDrawableCurlyLinePeakHeight > 0.0f) - { - _lineMetrics.curlylinePeakHeight = std::min(_lineMetrics.curlylinePeakHeight, maxDrawableCurlyLinePeakHeight); - } - } + // GdiEngine::PaintBufferGridLines paints underlines using HPEN and LineTo, etc., which draws lines centered on the given coordinates. + // This means we need to shift the limit (cellHeight - underlineWidth) and offset (idealUnderlineTop) by half the width. + const auto underlineWidth = std::max(1.0f, roundf(idealUnderlineWidth)); + const auto underlineCenter = std::min(floorf(cellHeight - underlineWidth / 2.0f), roundf(idealUnderlineTop + underlineWidth / 2.0f)); + + const auto strikethroughWidth = std::max(1.0f, roundf(idealStrikethroughWidth)); + const auto strikethroughOffset = std::min(cellHeight - strikethroughWidth, roundf(idealStrikethroughTop)); + + // For double underlines we loosely follow what Word does: + // 1. The lines are half the width of an underline + // 2. Ideally the bottom line is aligned with the bottom of the underline + // 3. The top underline is vertically in the middle between baseline and ideal bottom underline + // 4. If the top line gets too close to the baseline the underlines are shifted downwards + // 5. The minimum gap between the two lines appears to be similar to Tex (1.2pt) + // (Additional notes below.) + + // 1. + const auto thinLineWidth = std::max(1.0f, roundf(idealUnderlineWidth / 2.0f)); + // 2. + auto doubleUnderlinePosBottom = underlineCenter + underlineWidth - thinLineWidth; + // 3. Since we don't align the center of our two lines, but rather the top borders + // we need to subtract half a line width from our center point. + auto doubleUnderlinePosTop = roundf((baseline + doubleUnderlinePosBottom - thinLineWidth) / 2.0f); + // 4. + doubleUnderlinePosTop = std::max(doubleUnderlinePosTop, baseline + thinLineWidth); + // 5. The gap is only the distance _between_ the lines, but we need the distance from the + // top border of the top and bottom lines, which includes an additional line width. + const auto doubleUnderlineGap = std::max(1.0f, roundf(1.2f / 72.0f * _iCurrentDpi)); + doubleUnderlinePosBottom = std::max(doubleUnderlinePosBottom, doubleUnderlinePosTop + doubleUnderlineGap + thinLineWidth); + // Our cells can't overlap each other so we additionally clamp the bottom line to be inside the cell boundaries. + doubleUnderlinePosBottom = std::min(doubleUnderlinePosBottom, cellHeight - thinLineWidth); + + // The wave line is drawn using a cubic Bézier curve (PolyBezier), because that happens to be cheap with GDI. + // We use a Bézier curve where, if the start (a) and end (c) points are at (0,0) and (1,0), the control points are + // at (0.5,0.5) (b) and (0.5,-0.5) (d) respectively. Like this but a/b/c/d are square and the lines are round: + // + // b + // + // ^ + // / \ here's some text so the compiler ignores the trailing \ character + // a \ c + // \ / + // v + // + // d + // + // If you punch x=0.25 into the cubic bezier formula you get y=0.140625. This constant is + // important to us because it (plus the line width) tells us the amplitude of the wave. + // + // We can use the inverse of the constant to figure out how many px one period of the wave has to be to end up being 1px tall. + // In our case we want the amplitude of the wave to have a peak-to-peak amplitude that matches our double-underline. + const auto doubleUnderlineHalfDistance = 0.5f * (doubleUnderlinePosBottom - doubleUnderlinePosTop); + const auto doubleUnderlineCenter = doubleUnderlinePosTop + doubleUnderlineHalfDistance; + const auto curlyLineIdealAmplitude = std::max(1.0f, doubleUnderlineHalfDistance); + // Since GDI can't deal with fractional pixels, we first calculate the control point offsets (0.5 and -0.5) by multiplying by 0.5 and + // then undo that by multiplying by 2.0 for the period. This ensures that our control points can be at curlyLinePeriod/2, an integer. + const auto curlyLineControlPointOffset = roundf(curlyLineIdealAmplitude * (1.0f / 0.140625f) * 0.5f); + const auto curlyLinePeriod = curlyLineControlPointOffset * 2.0f; + // We can reverse the above to get back the actual amplitude of our Bézier curve. The line + // will be drawn with a width of thinLineWidth in the center of the curve (= 0.5x padding). + const auto curlyLineAmplitude = 0.140625f * curlyLinePeriod + 0.5f * thinLineWidth; + // To make the wavy line with its double-underline amplitude look consistent with the double-underline we position it at its center. + const auto curlyLineOffset = std::min(roundf(doubleUnderlineCenter), floorf(cellHeight - curlyLineAmplitude)); + + _lineMetrics.gridlineWidth = lroundf(idealGridlineWidth); + _lineMetrics.thinLineWidth = lroundf(thinLineWidth); + _lineMetrics.underlineCenter = lroundf(underlineCenter); + _lineMetrics.underlineWidth = lroundf(underlineWidth); + _lineMetrics.doubleUnderlinePosTop = lroundf(doubleUnderlinePosTop); + _lineMetrics.doubleUnderlinePosBottom = lroundf(doubleUnderlinePosBottom); + _lineMetrics.strikethroughOffset = lroundf(strikethroughOffset); + _lineMetrics.strikethroughWidth = lroundf(strikethroughWidth); + _lineMetrics.curlyLineCenter = lroundf(curlyLineOffset); + _lineMetrics.curlyLinePeriod = lroundf(curlyLinePeriod); + _lineMetrics.curlyLineControlPointOffset = lroundf(curlyLineControlPointOffset); // Now find the size of a 0 in this current font and save it for conversions done later. _coordFontLast = Font.GetSize(); diff --git a/src/tools/RenderingTests/main.cpp b/src/tools/RenderingTests/main.cpp index a82e38bb818..45dfcf66ea7 100644 --- a/src/tools/RenderingTests/main.cpp +++ b/src/tools/RenderingTests/main.cpp @@ -5,7 +5,39 @@ #include #include -#include +#include + +// The following list of colors is only used as a debug aid and not part of the final product. +// They're licensed under: +// +// Apache-Style Software License for ColorBrewer software and ColorBrewer Color Schemes +// +// Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and The Pennsylvania State University. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +// +namespace colorbrewer +{ + inline constexpr uint32_t pastel1[]{ + 0xfbb4ae, + 0xb3cde3, + 0xccebc5, + 0xdecbe4, + 0xfed9a6, + 0xffffcc, + 0xe5d8bd, + 0xfddaec, + 0xf2f2f2, + }; +} // Another variant of "defer" for C++. namespace @@ -110,64 +142,93 @@ int main() }; { - struct ConsoleAttributeTest + struct AttributeTest { const wchar_t* text = nullptr; WORD attribute = 0; }; - static constexpr ConsoleAttributeTest consoleAttributeTests[]{ - { L"Console attributes:", 0 }, + + { + static constexpr AttributeTest consoleAttributeTests[]{ + { L"Console attributes:", 0 }, #define MAKE_TEST_FOR_ATTRIBUTE(attr) { L## #attr, attr } - MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_GRID_HORIZONTAL), - MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_GRID_LVERTICAL), - MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_GRID_RVERTICAL), - MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_REVERSE_VIDEO), - MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_UNDERSCORE), + MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_GRID_HORIZONTAL), + MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_GRID_LVERTICAL), + MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_GRID_RVERTICAL), + MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_REVERSE_VIDEO), + MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_UNDERSCORE), #undef MAKE_TEST_FOR_ATTRIBUTE - { L"all gridlines", COMMON_LVB_GRID_HORIZONTAL | COMMON_LVB_GRID_LVERTICAL | COMMON_LVB_GRID_RVERTICAL | COMMON_LVB_UNDERSCORE }, - { L"all attributes", COMMON_LVB_GRID_HORIZONTAL | COMMON_LVB_GRID_LVERTICAL | COMMON_LVB_GRID_RVERTICAL | COMMON_LVB_REVERSE_VIDEO | COMMON_LVB_UNDERSCORE }, - }; + { L"all gridlines", COMMON_LVB_GRID_HORIZONTAL | COMMON_LVB_GRID_LVERTICAL | COMMON_LVB_GRID_RVERTICAL | COMMON_LVB_UNDERSCORE }, + { L"all attributes", COMMON_LVB_GRID_HORIZONTAL | COMMON_LVB_GRID_LVERTICAL | COMMON_LVB_GRID_RVERTICAL | COMMON_LVB_REVERSE_VIDEO | COMMON_LVB_UNDERSCORE }, + }; - SHORT row = 2; - for (const auto& t : consoleAttributeTests) - { - const auto length = static_cast(wcslen(t.text)); - printfUTF16(L"\x1B[%d;5H%s", row + 1, t.text); + SHORT row = 2; + for (const auto& t : consoleAttributeTests) + { + const auto length = static_cast(wcslen(t.text)); + printfUTF16(L"\x1B[%d;5H%s", row + 1, t.text); - WORD attributes[32]; - std::fill_n(&attributes[0], length, static_cast(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | t.attribute)); + WORD attributes[32]; + std::fill_n(&attributes[0], length, static_cast(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | t.attribute)); - DWORD numberOfAttrsWritten; - WriteConsoleOutputAttribute(outputHandle, attributes, length, { 4, row }, &numberOfAttrsWritten); + DWORD numberOfAttrsWritten; + WriteConsoleOutputAttribute(outputHandle, attributes, length, { 4, row }, &numberOfAttrsWritten); - row += 2; + row += 2; + } } - struct VTAttributeTest { - const wchar_t* text = nullptr; - int sgr = 0; - }; - static constexpr VTAttributeTest vtAttributeTests[]{ - { L"ANSI escape SGR:", 0 }, - { L"bold", 1 }, - { L"faint", 2 }, - { L"italic", 3 }, - { L"underline", 4 }, - { L"reverse", 7 }, - { L"strikethrough", 9 }, - { L"double underline", 21 }, - { L"overlined", 53 }, - }; + static constexpr AttributeTest basicSGR[]{ + { L"bold", 1 }, + { L"faint", 2 }, + { L"italic", 3 }, + { L"underline", 4 }, + { L"reverse", 7 }, + { L"strikethrough", 9 }, + { L"double underline", 21 }, + { L"overlined", 53 }, + }; + + printfUTF16(L"\x1B[3;39HANSI escape SGR:"); + + int row = 5; + for (const auto& t : basicSGR) + { + printfUTF16(L"\x1B[%d;39H\x1b[%dm%s\x1b[m", row, t.attribute, t.text); + row += 2; + } - row = 3; - for (const auto& t : vtAttributeTests) - { - printfUTF16(L"\x1B[%d;45H\x1b[%dm%s\x1b[m", row, t.sgr, t.text); - row += 2; + printfUTF16(L"\x1B[%d;39H\x1b]8;;https://example.com\x1b\\hyperlink\x1b]8;;\x1b\\", row); } - printfUTF16(L"\x1B[%d;45H\x1b]8;;https://example.com\x1b\\hyperlink\x1b]8;;\x1b\\", row); + { + static constexpr AttributeTest styledUnderlines[]{ + { L"straight", 1 }, + { L"double", 2 }, + { L"curly", 3 }, + { L"dotted", 4 }, + { L"dashed", 5 }, + }; + + printfUTF16(L"\x1B[3;63HStyled Underlines:"); + + int row = 5; + for (const auto& t : styledUnderlines) + { + printfUTF16(L"\x1B[%d;63H\x1b[4:%dm", row, t.attribute); + + const auto len = wcslen(t.text); + for (size_t i = 0; i < len; ++i) + { + const auto color = colorbrewer::pastel1[i % std::size(colorbrewer::pastel1)]; + printfUTF16(L"\x1B[58:2::%d:%d:%dm%c", (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff, t.text[i]); + } + + printfUTF16(L"\x1b[m"); + row += 2; + } + } wait(); clear(); From a65d5f321f72daaf80d30b2ce4dc8547c1c282b3 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sat, 16 Dec 2023 00:29:09 +0100 Subject: [PATCH 115/167] Add missing TraceLoggingRegister calls (#16467) 17cc109 and e9de646 both made the same mistake: When cleaning up our telemetry code they also removed the calls to `TraceLoggingRegister` which also broke regular tracing. Windows Defender in particular uses the "CookedRead" event to monitor for malicious shell commands. This doesn't fix it the "right way", because destructors of statics aren't executed when DLLs are unloaded. But I felt like that this is fine because we have way more statics than that in conhost land, all of which have the same kind of issue. --- src/host/exe/exemain.cpp | 1 + src/host/telemetry.cpp | 6 ------ src/host/tracing.cpp | 6 ++++++ src/terminal/parser/tracing.cpp | 8 ++++++++ 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/host/exe/exemain.cpp b/src/host/exe/exemain.cpp index 7436cc8cb75..d0d2b81b514 100644 --- a/src/host/exe/exemain.cpp +++ b/src/host/exe/exemain.cpp @@ -222,6 +222,7 @@ int CALLBACK wWinMain( _In_ PWSTR /*pwszCmdLine*/, _In_ int /*nCmdShow*/) { + TraceLoggingRegister(g_hConhostV2EventTraceProvider); wil::SetResultLoggingCallback(&Tracing::TraceFailure); Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().hInstance = hInstance; diff --git a/src/host/telemetry.cpp b/src/host/telemetry.cpp index fb25c7f6818..9b1d7256799 100644 --- a/src/host/telemetry.cpp +++ b/src/host/telemetry.cpp @@ -4,12 +4,6 @@ #include "precomp.h" #include "telemetry.hpp" -TRACELOGGING_DEFINE_PROVIDER(g_hConhostV2EventTraceProvider, - "Microsoft.Windows.Console.Host", - // {fe1ff234-1f09-50a8-d38d-c44fab43e818} - (0xfe1ff234, 0x1f09, 0x50a8, 0xd3, 0x8d, 0xc4, 0x4f, 0xab, 0x43, 0xe8, 0x18), - TraceLoggingOptionMicrosoftTelemetry()); - // This code remains to serve as template if we ever need telemetry for conhost again. // The git history for this file may prove useful. #if 0 diff --git a/src/host/tracing.cpp b/src/host/tracing.cpp index 14a4db03e4c..c3ae744f41f 100644 --- a/src/host/tracing.cpp +++ b/src/host/tracing.cpp @@ -6,6 +6,12 @@ #include "../types/UiaTextRangeBase.hpp" #include "../types/ScreenInfoUiaProviderBase.h" +TRACELOGGING_DEFINE_PROVIDER(g_hConhostV2EventTraceProvider, + "Microsoft.Windows.Console.Host", + // {fe1ff234-1f09-50a8-d38d-c44fab43e818} + (0xfe1ff234, 0x1f09, 0x50a8, 0xd3, 0x8d, 0xc4, 0x4f, 0xab, 0x43, 0xe8, 0x18), + TraceLoggingOptionMicrosoftTelemetry()); + using namespace Microsoft::Console::Types; // NOTE: See `til.h` for which keyword flags are reserved diff --git a/src/terminal/parser/tracing.cpp b/src/terminal/parser/tracing.cpp index 9ab684ac95d..862c9c1ed80 100644 --- a/src/terminal/parser/tracing.cpp +++ b/src/terminal/parser/tracing.cpp @@ -7,6 +7,7 @@ using namespace Microsoft::Console::VirtualTerminal; #pragma warning(push) +#pragma warning(disable : 26426) // Global initializer calls a non-constexpr function '...' (i.22).) #pragma warning(disable : 26447) // The function is declared 'noexcept' but calls function '_tlgWrapBinary()' which may throw exceptions #pragma warning(disable : 26477) // Use 'nullptr' rather than 0 or NULL @@ -15,6 +16,13 @@ TRACELOGGING_DEFINE_PROVIDER(g_hConsoleVirtTermParserEventTraceProvider, // {c9ba2a84-d3ca-5e19-2bd6-776a0910cb9d} (0xc9ba2a84, 0xd3ca, 0x5e19, 0x2b, 0xd6, 0x77, 0x6a, 0x09, 0x10, 0xcb, 0x9d)); +static const auto cleanup = []() noexcept { + TraceLoggingRegister(g_hConsoleVirtTermParserEventTraceProvider); + return wil::scope_exit([]() noexcept { + TraceLoggingUnregister(g_hConsoleVirtTermParserEventTraceProvider); + }); +}(); + void ParserTracing::TraceStateChange(_In_z_ const wchar_t* name) const noexcept { TraceLoggingWrite(g_hConsoleVirtTermParserEventTraceProvider, From 5d85eb3e24f245bc3fb290ee91519dbacdd8d97f Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sat, 16 Dec 2023 00:31:48 +0100 Subject: [PATCH 116/167] COOKED_READ: A minor cleanup (#16463) This is just a minor, unimportant cleanup to remove code duplication in `_flushBuffer`, which called `SetCursorPosition` twice each time the cursor position changed. --- src/host/readDataCooked.cpp | 21 ++++++++++++--------- src/host/readDataCooked.hpp | 1 + 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/host/readDataCooked.cpp b/src/host/readDataCooked.cpp index 4869e2ad91a..a05bed8a46a 100644 --- a/src/host/readDataCooked.cpp +++ b/src/host/readDataCooked.cpp @@ -845,15 +845,12 @@ void COOKED_READ_DATA::_flushBuffer() // If the contents of _buffer became shorter we'll have to erase the previously printed contents. _erase(eraseDistance); - _offsetCursorPosition(-eraseDistance - distanceAfterCursor); + // Using the *Always() variant ensures that we reset the blinking timer, etc., even if the cursor didn't move. + _offsetCursorPositionAlways(-eraseDistance - distanceAfterCursor); _buffer.MarkAsClean(); _distanceCursor = distanceBeforeCursor; _distanceEnd = distanceEnd; - - const auto pos = _screenInfo.GetTextBuffer().GetCursor().GetPosition(); - _screenInfo.MakeCursorVisible(pos); - std::ignore = _screenInfo.SetCursorPosition(pos, true); } // This is just a small helper to fill the next N cells starting at the current cursor position with whitespace. @@ -1045,15 +1042,21 @@ til::point COOKED_READ_DATA::_offsetPosition(til::point pos, ptrdiff_t distance) }; } -// This moves the cursor `distance`-many cells back up in the buffer. -// It's intended to be used in combination with _writeChars. +// See _offsetCursorPositionAlways(). This wrapper is just here to avoid doing +// expensive cursor movements when there's nothing to move. A no-op wrapper. void COOKED_READ_DATA::_offsetCursorPosition(ptrdiff_t distance) const { - if (distance == 0) + if (distance != 0) { - return; + _offsetCursorPositionAlways(distance); } +} +// This moves the cursor `distance`-many cells around in the buffer. +// It's intended to be used in combination with _writeChars. +// Usually you should use _offsetCursorPosition() to no-op distance==0. +void COOKED_READ_DATA::_offsetCursorPositionAlways(ptrdiff_t distance) const +{ const auto& textBuffer = _screenInfo.GetTextBuffer(); const auto& cursor = textBuffer.GetCursor(); const auto pos = _offsetPosition(cursor.GetPosition(), distance); diff --git a/src/host/readDataCooked.hpp b/src/host/readDataCooked.hpp index 9502eaa5c2d..035c3c96679 100644 --- a/src/host/readDataCooked.hpp +++ b/src/host/readDataCooked.hpp @@ -160,6 +160,7 @@ class COOKED_READ_DATA final : public ReadData ptrdiff_t _writeCharsUnprocessed(const std::wstring_view& text) const; til::point _offsetPosition(til::point pos, ptrdiff_t distance) const; void _offsetCursorPosition(ptrdiff_t distance) const; + void _offsetCursorPositionAlways(ptrdiff_t distance) const; til::CoordType _getColumnAtRelativeCursorPosition(ptrdiff_t distance) const; void _popupPush(PopupKind kind); From 91e97c169ee30adb41ac74f0dbb9b8e0e267d1a4 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 15 Dec 2023 20:17:42 +0100 Subject: [PATCH 117/167] Increase VtInputThread buffer size (#16470) This makes 3 improvements: * 16x larger input buffer size improves behavior when pasting clipboard contents while the win32-input-mode is enabled, as each input character is roughly 15-20x longer after encoding. * Translate UTF8 to UTF16 outside of the console lock. * Preserve the UTF16 buffer between reads for less mallocs. (cherry picked from commit 171a21ad48eca9f57a3ae5692fe9a5c64e9ad276) Service-Card-Id: 91347494 Service-Version: 1.19 --- src/host/VtInputThread.cpp | 15 ++++++++------- src/host/VtInputThread.hpp | 1 + 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/host/VtInputThread.cpp b/src/host/VtInputThread.cpp index 4a4b773cb02..b9b2aefb0aa 100644 --- a/src/host/VtInputThread.cpp +++ b/src/host/VtInputThread.cpp @@ -69,7 +69,7 @@ DWORD WINAPI VtInputThread::StaticVtInputThreadProc(_In_ LPVOID lpParameter) // - true if you should continue reading bool VtInputThread::DoReadInput() { - char buffer[256]; + char buffer[4096]; DWORD dwRead = 0; const auto ok = ReadFile(_hFile.get(), buffer, ARRAYSIZE(buffer), &dwRead, nullptr); @@ -89,6 +89,12 @@ bool VtInputThread::DoReadInput() return false; } + // If we hit a parsing error, eat it. It's bad utf-8, we can't do anything with it. + if (FAILED_LOG(til::u8u16({ buffer, gsl::narrow_cast(dwRead) }, _wstr, _u8State))) + { + return true; + } + try { // Make sure to call the GLOBAL Lock/Unlock, not the gci's lock/unlock. @@ -99,12 +105,7 @@ bool VtInputThread::DoReadInput() LockConsole(); const auto unlock = wil::scope_exit([&] { UnlockConsole(); }); - std::wstring wstr; - // If we hit a parsing error, eat it. It's bad utf-8, we can't do anything with it. - if (SUCCEEDED_LOG(til::u8u16({ buffer, gsl::narrow_cast(dwRead) }, wstr, _u8State))) - { - _pInputStateMachine->ProcessString(wstr); - } + _pInputStateMachine->ProcessString(_wstr); } CATCH_LOG(); diff --git a/src/host/VtInputThread.hpp b/src/host/VtInputThread.hpp index 7652a53887f..d058c425bc4 100644 --- a/src/host/VtInputThread.hpp +++ b/src/host/VtInputThread.hpp @@ -39,5 +39,6 @@ namespace Microsoft::Console std::unique_ptr _pInputStateMachine; til::u8state _u8State; + std::wstring _wstr; }; } From bc18348967887ad0412f5eff93d089430cfda679 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sat, 16 Dec 2023 00:50:06 +0100 Subject: [PATCH 118/167] Fix parsing of chunked win32-input-mode sequences (#16466) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Even with the previous fixes we still randomly encounter win32- input-mode sequences that are broken up in exactly such a way that e.g. lone escape keys are encounters. Those for instance clear the current prompt. The remaining parts of the sequence are then visible. This changeset fixes the issue by skipping the entire force-to-ground code whenever we saw at least 1 win32-input-mode sequence. Related to #16343 ## Validation Steps Performed * Host a ConPTY inside ConPTY (= double the trouble) with cmd.exe * Paste random amounts of text * In the old code spurious `[..._` strings are seen * In the new code they're consistently gone ✅ --- src/terminal/parser/IStateMachineEngine.hpp | 2 ++ src/terminal/parser/InputStateMachineEngine.cpp | 6 ++++++ src/terminal/parser/InputStateMachineEngine.hpp | 2 ++ src/terminal/parser/OutputStateMachineEngine.cpp | 5 +++++ src/terminal/parser/OutputStateMachineEngine.hpp | 2 ++ src/terminal/parser/stateMachine.cpp | 8 +++++++- src/terminal/parser/ut_parser/StateMachineTest.cpp | 5 +++++ 7 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/terminal/parser/IStateMachineEngine.hpp b/src/terminal/parser/IStateMachineEngine.hpp index 371ba96c1d5..3f0356ebb84 100644 --- a/src/terminal/parser/IStateMachineEngine.hpp +++ b/src/terminal/parser/IStateMachineEngine.hpp @@ -28,6 +28,8 @@ namespace Microsoft::Console::VirtualTerminal IStateMachineEngine& operator=(const IStateMachineEngine&) = default; IStateMachineEngine& operator=(IStateMachineEngine&&) = default; + virtual bool EncounteredWin32InputModeSequence() const noexcept = 0; + virtual bool ActionExecute(const wchar_t wch) = 0; virtual bool ActionExecuteFromEscape(const wchar_t wch) = 0; virtual bool ActionPrint(const wchar_t wch) = 0; diff --git a/src/terminal/parser/InputStateMachineEngine.cpp b/src/terminal/parser/InputStateMachineEngine.cpp index 7ea26ce9c84..cdc270c1858 100644 --- a/src/terminal/parser/InputStateMachineEngine.cpp +++ b/src/terminal/parser/InputStateMachineEngine.cpp @@ -102,6 +102,11 @@ InputStateMachineEngine::InputStateMachineEngine(std::unique_ptrWriteCtrlKey(key); + _encounteredWin32InputModeSequence = true; break; } default: diff --git a/src/terminal/parser/InputStateMachineEngine.hpp b/src/terminal/parser/InputStateMachineEngine.hpp index 74af20a9b39..b0687f6a0b3 100644 --- a/src/terminal/parser/InputStateMachineEngine.hpp +++ b/src/terminal/parser/InputStateMachineEngine.hpp @@ -132,6 +132,7 @@ namespace Microsoft::Console::VirtualTerminal InputStateMachineEngine(std::unique_ptr pDispatch, const bool lookingForDSR); + bool EncounteredWin32InputModeSequence() const noexcept override; void SetLookingForDSR(const bool looking) noexcept; bool ActionExecute(const wchar_t wch) override; @@ -167,6 +168,7 @@ namespace Microsoft::Console::VirtualTerminal const std::unique_ptr _pDispatch; std::function _pfnFlushToInputQueue; bool _lookingForDSR; + bool _encounteredWin32InputModeSequence = false; DWORD _mouseButtonState = 0; std::chrono::milliseconds _doubleClickTime; std::optional _lastMouseClickPos{}; diff --git a/src/terminal/parser/OutputStateMachineEngine.cpp b/src/terminal/parser/OutputStateMachineEngine.cpp index e744c95ad08..68a45916df4 100644 --- a/src/terminal/parser/OutputStateMachineEngine.cpp +++ b/src/terminal/parser/OutputStateMachineEngine.cpp @@ -23,6 +23,11 @@ OutputStateMachineEngine::OutputStateMachineEngine(std::unique_ptr pDispatch); + bool EncounteredWin32InputModeSequence() const noexcept override; + bool ActionExecute(const wchar_t wch) override; bool ActionExecuteFromEscape(const wchar_t wch) override; diff --git a/src/terminal/parser/stateMachine.cpp b/src/terminal/parser/stateMachine.cpp index 643409afda5..88727ad6ec2 100644 --- a/src/terminal/parser/stateMachine.cpp +++ b/src/terminal/parser/stateMachine.cpp @@ -2144,9 +2144,15 @@ void StateMachine::ProcessString(const std::wstring_view string) // which breaks up many of our longer sequences, like our Win32InputMode ones. // // As a heuristic, this code specifically checks for a trailing Esc or Alt+key. + // If we encountered a win32-input-mode sequence before, we know that our \x1b[?9001h + // request to enable them was successful. While a client may still send \x1b{some char} + // intentionally, it's far more likely now that we're looking at a broken up sequence. + // The most common win32-input-mode is ConPTY itself after all, and we never emit + // \x1b{some char} once it's enabled. if (_isEngineForInput) { - if (run.size() <= 2 && run.front() == L'\x1b') + const auto win32 = _engine->EncounteredWin32InputModeSequence(); + if (!win32 && run.size() <= 2 && run.front() == L'\x1b') { _EnterGround(); if (run.size() == 1) diff --git a/src/terminal/parser/ut_parser/StateMachineTest.cpp b/src/terminal/parser/ut_parser/StateMachineTest.cpp index 4893964c249..d7e49537207 100644 --- a/src/terminal/parser/ut_parser/StateMachineTest.cpp +++ b/src/terminal/parser/ut_parser/StateMachineTest.cpp @@ -40,6 +40,11 @@ class Microsoft::Console::VirtualTerminal::TestStateMachineEngine : public IStat dcsDataString.clear(); } + bool EncounteredWin32InputModeSequence() const noexcept override + { + return false; + } + bool ActionExecute(const wchar_t wch) override { executed += wch; From 63c3573a13cc96bfa3d655f0f1898ae250e2fe35 Mon Sep 17 00:00:00 2001 From: js324 Date: Fri, 15 Dec 2023 18:50:45 -0500 Subject: [PATCH 119/167] Wrap word-wise selection when the word is actually wrapped (#16441) Added wrapping to highlighted selection when selecting a word, added tests for it ## Detailed Description of the Pull Request / Additional comments - Modified GetWordStart and GetWordEnd and their helpers to no longer be bounded by the right and left viewport ranges - Kept same functionality (does not wrap) when selecting wrapped whitespace - Added tests to TextBufferTests.cpp to include cases of wrapping text ## Validation Steps Performed - Ran locally and verified selection works properly - Tests passed locally Closes #4009 --- ...cdb9b77d6827c0202f51acd4205b017015bfff.txt | 5 + src/buffer/out/textBuffer.cpp | 63 ++++++----- src/host/ut_host/TextBufferTests.cpp | 100 ++++++++++++++++-- 3 files changed, 128 insertions(+), 40 deletions(-) create mode 100644 .github/actions/spelling/expect/04cdb9b77d6827c0202f51acd4205b017015bfff.txt diff --git a/.github/actions/spelling/expect/04cdb9b77d6827c0202f51acd4205b017015bfff.txt b/.github/actions/spelling/expect/04cdb9b77d6827c0202f51acd4205b017015bfff.txt new file mode 100644 index 00000000000..f117f5081da --- /dev/null +++ b/.github/actions/spelling/expect/04cdb9b77d6827c0202f51acd4205b017015bfff.txt @@ -0,0 +1,5 @@ +EOB +swrapped +wordi +wordiswrapped +wrappe diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 52a5c6e3b72..528d8fbf4d3 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -1329,36 +1329,32 @@ til::point TextBuffer::_GetWordStartForAccessibility(const til::point target, co { auto result = target; const auto bufferSize = GetSize(); - auto stayAtOrigin = false; // ignore left boundary. Continue until readable text found while (_GetDelimiterClassAt(result, wordDelimiters) != DelimiterClass::RegularChar) { - if (!bufferSize.DecrementInBounds(result)) + if (result == bufferSize.Origin()) { - // first char in buffer is a DelimiterChar or ControlChar - // we can't move any further back - stayAtOrigin = true; - break; + //looped around and hit origin (no word between origin and target) + return result; } + bufferSize.DecrementInBounds(result); } // make sure we expand to the left boundary or the beginning of the word while (_GetDelimiterClassAt(result, wordDelimiters) == DelimiterClass::RegularChar) { - if (!bufferSize.DecrementInBounds(result)) + if (result == bufferSize.Origin()) { // first char in buffer is a RegularChar // we can't move any further back - break; + return result; } + bufferSize.DecrementInBounds(result); } - // move off of delimiter and onto word start - if (!stayAtOrigin && _GetDelimiterClassAt(result, wordDelimiters) != DelimiterClass::RegularChar) - { - bufferSize.IncrementInBounds(result); - } + // move off of delimiter + bufferSize.IncrementInBounds(result); return result; } @@ -1376,10 +1372,16 @@ til::point TextBuffer::_GetWordStartForSelection(const til::point target, const const auto bufferSize = GetSize(); const auto initialDelimiter = _GetDelimiterClassAt(result, wordDelimiters); + const bool isControlChar = initialDelimiter == DelimiterClass::ControlChar; // expand left until we hit the left boundary or a different delimiter class - while (result.x > bufferSize.Left() && (_GetDelimiterClassAt(result, wordDelimiters) == initialDelimiter)) + while (result != bufferSize.Origin() && _GetDelimiterClassAt(result, wordDelimiters) == initialDelimiter) { + //prevent selection wrapping on whitespace selection + if (isControlChar && result.x == bufferSize.Left()) + { + break; + } bufferSize.DecrementInBounds(result); } @@ -1457,25 +1459,21 @@ til::point TextBuffer::_GetWordEndForAccessibility(const til::point target, cons } else { - auto iter{ GetCellDataAt(result, bufferSize) }; - while (iter && iter.Pos() != limit && _GetDelimiterClassAt(iter.Pos(), wordDelimiters) == DelimiterClass::RegularChar) + while (result != limit && result != bufferSize.BottomRightInclusive() && _GetDelimiterClassAt(result, wordDelimiters) == DelimiterClass::RegularChar) { // Iterate through readable text - ++iter; + bufferSize.IncrementInBounds(result); } - while (iter && iter.Pos() != limit && _GetDelimiterClassAt(iter.Pos(), wordDelimiters) != DelimiterClass::RegularChar) + while (result != limit && result != bufferSize.BottomRightInclusive() && _GetDelimiterClassAt(result, wordDelimiters) != DelimiterClass::RegularChar) { // expand to the beginning of the NEXT word - ++iter; + bufferSize.IncrementInBounds(result); } - result = iter.Pos(); - - // Special case: we tried to move one past the end of the buffer, - // but iter prevented that (because that pos doesn't exist). + // Special case: we tried to move one past the end of the buffer // Manually increment onto the EndExclusive point. - if (!iter) + if (result == bufferSize.BottomRightInclusive()) { bufferSize.IncrementInBounds(result, true); } @@ -1495,19 +1493,18 @@ til::point TextBuffer::_GetWordEndForSelection(const til::point target, const st { const auto bufferSize = GetSize(); - // can't expand right - if (target.x == bufferSize.RightInclusive()) - { - return target; - } - auto result = target; const auto initialDelimiter = _GetDelimiterClassAt(result, wordDelimiters); + const bool isControlChar = initialDelimiter == DelimiterClass::ControlChar; - // expand right until we hit the right boundary or a different delimiter class - while (result.x < bufferSize.RightInclusive() && (_GetDelimiterClassAt(result, wordDelimiters) == initialDelimiter)) + // expand right until we hit the right boundary as a ControlChar or a different delimiter class + while (result != bufferSize.BottomRightInclusive() && _GetDelimiterClassAt(result, wordDelimiters) == initialDelimiter) { - bufferSize.IncrementInBounds(result); + if (isControlChar && result.x == bufferSize.RightInclusive()) + { + break; + } + bufferSize.IncrementInBoundsCircular(result); } if (_GetDelimiterClassAt(result, wordDelimiters) != initialDelimiter) diff --git a/src/host/ut_host/TextBufferTests.cpp b/src/host/ut_host/TextBufferTests.cpp index d1c7395635f..9b330e91d86 100644 --- a/src/host/ut_host/TextBufferTests.cpp +++ b/src/host/ut_host/TextBufferTests.cpp @@ -2118,10 +2118,11 @@ void TextBufferTests::TestAppendRTFText() void TextBufferTests::WriteLinesToBuffer(const std::vector& text, TextBuffer& buffer) { const auto bufferSize = buffer.GetSize(); - + int rowsWrapped{}; for (size_t row = 0; row < text.size(); ++row) { auto line = text[row]; + if (!line.empty()) { // TODO GH#780: writing up to (but not past) the end of the line @@ -2133,7 +2134,12 @@ void TextBufferTests::WriteLinesToBuffer(const std::vector& text, } OutputCellIterator iter{ line }; - buffer.Write(iter, { 0, gsl::narrow(row) }, wrap); + buffer.Write(iter, { 0, gsl::narrow(row + rowsWrapped) }, wrap); + //prevent bug that overwrites wrapped rows + if (line.size() > static_cast(bufferSize.RightExclusive())) + { + rowsWrapped += static_cast(line.size()) / bufferSize.RightExclusive(); + } } } } @@ -2245,6 +2251,88 @@ void TextBufferTests::GetWordBoundaries() const auto expected = accessibilityMode ? test.expected.accessibilityModeEnabled : test.expected.accessibilityModeDisabled; VERIFY_ARE_EQUAL(expected, result); } + + _buffer->Reset(); + _buffer->ResizeTraditional({ 10, 5 }); + const std::vector secondText = { L"this wordiswrapped", + L"spaces wrapped reachEOB" }; + //Buffer looks like: + // this wordi + // swrapped + // spaces + // wrappe + // d reachEOB + WriteLinesToBuffer(secondText, *_buffer); + testData = { + { { 0, 0 }, { { 0, 0 }, { 0, 0 } } }, + { { 1, 0 }, { { 0, 0 }, { 0, 0 } } }, + { { 4, 0 }, { { 4, 0 }, { 0, 0 } } }, + { { 5, 0 }, { { 5, 0 }, { 5, 0 } } }, + { { 7, 0 }, { { 5, 0 }, { 5, 0 } } }, + + { { 4, 1 }, { { 5, 0 }, { 5, 0 } } }, + { { 7, 1 }, { { 5, 0 }, { 5, 0 } } }, + { { 9, 1 }, { { 8, 1 }, { 5, 0 } } }, + + { { 0, 2 }, { { 0, 2 }, { 0, 2 } } }, + { { 7, 2 }, { { 6, 2 }, { 0, 2 } } }, + + { { 1, 3 }, { { 0, 3 }, { 0, 2 } } }, + { { 4, 3 }, { { 4, 3 }, { 4, 3 } } }, + { { 8, 3 }, { { 4, 3 }, { 4, 3 } } }, + + { { 0, 4 }, { { 4, 3 }, { 4, 3 } } }, + { { 1, 4 }, { { 1, 4 }, { 4, 3 } } }, + { { 9, 4 }, { { 2, 4 }, { 2, 4 } } }, + }; + for (const auto& test : testData) + { + Log::Comment(NoThrowString().Format(L"Testing til::point (%hd, %hd)", test.startPos.x, test.startPos.y)); + const auto result = _buffer->GetWordStart(test.startPos, delimiters, accessibilityMode); + const auto expected = accessibilityMode ? test.expected.accessibilityModeEnabled : test.expected.accessibilityModeDisabled; + VERIFY_ARE_EQUAL(expected, result); + } + + //GetWordEnd for Wrapping Text + //Buffer looks like: + // this wordi + // swrapped + // spaces + // wrappe + // d reachEOB + testData = { + // tests for first line of text + { { 0, 0 }, { { 3, 0 }, { 5, 0 } } }, + { { 1, 0 }, { { 3, 0 }, { 5, 0 } } }, + { { 4, 0 }, { { 4, 0 }, { 5, 0 } } }, + { { 5, 0 }, { { 7, 1 }, { 0, 2 } } }, + { { 7, 0 }, { { 7, 1 }, { 0, 2 } } }, + + { { 4, 1 }, { { 7, 1 }, { 0, 2 } } }, + { { 7, 1 }, { { 7, 1 }, { 0, 2 } } }, + { { 9, 1 }, { { 9, 1 }, { 0, 2 } } }, + + { { 0, 2 }, { { 5, 2 }, { 4, 3 } } }, + { { 7, 2 }, { { 9, 2 }, { 4, 3 } } }, + + { { 1, 3 }, { { 3, 3 }, { 4, 3 } } }, + { { 4, 3 }, { { 0, 4 }, { 2, 4 } } }, + { { 8, 3 }, { { 0, 4 }, { 2, 4 } } }, + + { { 0, 4 }, { { 0, 4 }, { 2, 4 } } }, + { { 1, 4 }, { { 1, 4 }, { 2, 4 } } }, + { { 4, 4 }, { { 9, 4 }, { 0, 5 } } }, + { { 9, 4 }, { { 9, 4 }, { 0, 5 } } }, + }; + // clang-format on + + for (const auto& test : testData) + { + Log::Comment(NoThrowString().Format(L"TestEnd til::point (%hd, %hd)", test.startPos.x, test.startPos.y)); + auto result = _buffer->GetWordEnd(test.startPos, delimiters, accessibilityMode); + const auto expected = accessibilityMode ? test.expected.accessibilityModeEnabled : test.expected.accessibilityModeDisabled; + VERIFY_ARE_EQUAL(expected, result); + } } void TextBufferTests::MoveByWord() @@ -2571,9 +2659,7 @@ void TextBufferTests::GetText() // Setup: Write lines of text to the buffer const std::vector bufferText = { L"1234567", - L"", - L" 345", - L"123 ", + L" 345123 ", L"" }; WriteLinesToBuffer(bufferText, *_buffer); // buffer should look like this: @@ -2653,7 +2739,7 @@ void TextBufferTests::GetText() Log::Comment(L"Standard Copy to Clipboard"); expectedText += L"12345"; expectedText += L"67\r\n"; - expectedText += L" 345\r\n"; + expectedText += L" 345"; expectedText += L"123 \r\n"; } else @@ -2661,7 +2747,7 @@ void TextBufferTests::GetText() Log::Comment(L"UI Automation"); expectedText += L"12345"; expectedText += L"67 \r\n"; - expectedText += L" 345\r\n"; + expectedText += L" 345"; expectedText += L"123 "; expectedText += L" \r\n"; expectedText += L" "; From 2546c02adb7e596970d7be531aa5e178e9a71b91 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sat, 16 Dec 2023 00:50:06 +0100 Subject: [PATCH 120/167] Fix parsing of chunked win32-input-mode sequences (#16466) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Even with the previous fixes we still randomly encounter win32- input-mode sequences that are broken up in exactly such a way that e.g. lone escape keys are encounters. Those for instance clear the current prompt. The remaining parts of the sequence are then visible. This changeset fixes the issue by skipping the entire force-to-ground code whenever we saw at least 1 win32-input-mode sequence. Related to #16343 ## Validation Steps Performed * Host a ConPTY inside ConPTY (= double the trouble) with cmd.exe * Paste random amounts of text * In the old code spurious `[..._` strings are seen * In the new code they're consistently gone ✅ (cherry picked from commit bc18348967887ad0412f5eff93d089430cfda679) Service-Card-Id: 91337332 Service-Version: 1.19 --- src/terminal/parser/IStateMachineEngine.hpp | 2 ++ src/terminal/parser/InputStateMachineEngine.cpp | 6 ++++++ src/terminal/parser/InputStateMachineEngine.hpp | 2 ++ src/terminal/parser/OutputStateMachineEngine.cpp | 5 +++++ src/terminal/parser/OutputStateMachineEngine.hpp | 2 ++ src/terminal/parser/stateMachine.cpp | 8 +++++++- src/terminal/parser/ut_parser/StateMachineTest.cpp | 5 +++++ 7 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/terminal/parser/IStateMachineEngine.hpp b/src/terminal/parser/IStateMachineEngine.hpp index 371ba96c1d5..3f0356ebb84 100644 --- a/src/terminal/parser/IStateMachineEngine.hpp +++ b/src/terminal/parser/IStateMachineEngine.hpp @@ -28,6 +28,8 @@ namespace Microsoft::Console::VirtualTerminal IStateMachineEngine& operator=(const IStateMachineEngine&) = default; IStateMachineEngine& operator=(IStateMachineEngine&&) = default; + virtual bool EncounteredWin32InputModeSequence() const noexcept = 0; + virtual bool ActionExecute(const wchar_t wch) = 0; virtual bool ActionExecuteFromEscape(const wchar_t wch) = 0; virtual bool ActionPrint(const wchar_t wch) = 0; diff --git a/src/terminal/parser/InputStateMachineEngine.cpp b/src/terminal/parser/InputStateMachineEngine.cpp index 7ea26ce9c84..cdc270c1858 100644 --- a/src/terminal/parser/InputStateMachineEngine.cpp +++ b/src/terminal/parser/InputStateMachineEngine.cpp @@ -102,6 +102,11 @@ InputStateMachineEngine::InputStateMachineEngine(std::unique_ptrWriteCtrlKey(key); + _encounteredWin32InputModeSequence = true; break; } default: diff --git a/src/terminal/parser/InputStateMachineEngine.hpp b/src/terminal/parser/InputStateMachineEngine.hpp index 74af20a9b39..b0687f6a0b3 100644 --- a/src/terminal/parser/InputStateMachineEngine.hpp +++ b/src/terminal/parser/InputStateMachineEngine.hpp @@ -132,6 +132,7 @@ namespace Microsoft::Console::VirtualTerminal InputStateMachineEngine(std::unique_ptr pDispatch, const bool lookingForDSR); + bool EncounteredWin32InputModeSequence() const noexcept override; void SetLookingForDSR(const bool looking) noexcept; bool ActionExecute(const wchar_t wch) override; @@ -167,6 +168,7 @@ namespace Microsoft::Console::VirtualTerminal const std::unique_ptr _pDispatch; std::function _pfnFlushToInputQueue; bool _lookingForDSR; + bool _encounteredWin32InputModeSequence = false; DWORD _mouseButtonState = 0; std::chrono::milliseconds _doubleClickTime; std::optional _lastMouseClickPos{}; diff --git a/src/terminal/parser/OutputStateMachineEngine.cpp b/src/terminal/parser/OutputStateMachineEngine.cpp index e744c95ad08..68a45916df4 100644 --- a/src/terminal/parser/OutputStateMachineEngine.cpp +++ b/src/terminal/parser/OutputStateMachineEngine.cpp @@ -23,6 +23,11 @@ OutputStateMachineEngine::OutputStateMachineEngine(std::unique_ptr pDispatch); + bool EncounteredWin32InputModeSequence() const noexcept override; + bool ActionExecute(const wchar_t wch) override; bool ActionExecuteFromEscape(const wchar_t wch) override; diff --git a/src/terminal/parser/stateMachine.cpp b/src/terminal/parser/stateMachine.cpp index 643409afda5..88727ad6ec2 100644 --- a/src/terminal/parser/stateMachine.cpp +++ b/src/terminal/parser/stateMachine.cpp @@ -2144,9 +2144,15 @@ void StateMachine::ProcessString(const std::wstring_view string) // which breaks up many of our longer sequences, like our Win32InputMode ones. // // As a heuristic, this code specifically checks for a trailing Esc or Alt+key. + // If we encountered a win32-input-mode sequence before, we know that our \x1b[?9001h + // request to enable them was successful. While a client may still send \x1b{some char} + // intentionally, it's far more likely now that we're looking at a broken up sequence. + // The most common win32-input-mode is ConPTY itself after all, and we never emit + // \x1b{some char} once it's enabled. if (_isEngineForInput) { - if (run.size() <= 2 && run.front() == L'\x1b') + const auto win32 = _engine->EncounteredWin32InputModeSequence(); + if (!win32 && run.size() <= 2 && run.front() == L'\x1b') { _EnterGround(); if (run.size() == 1) diff --git a/src/terminal/parser/ut_parser/StateMachineTest.cpp b/src/terminal/parser/ut_parser/StateMachineTest.cpp index 4893964c249..d7e49537207 100644 --- a/src/terminal/parser/ut_parser/StateMachineTest.cpp +++ b/src/terminal/parser/ut_parser/StateMachineTest.cpp @@ -40,6 +40,11 @@ class Microsoft::Console::VirtualTerminal::TestStateMachineEngine : public IStat dcsDataString.clear(); } + bool EncounteredWin32InputModeSequence() const noexcept override + { + return false; + } + bool ActionExecute(const wchar_t wch) override { executed += wch; From 322fdd027c428dafe3e8f43f0f37741577bb10ea Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Fri, 10 Nov 2023 06:17:07 +0530 Subject: [PATCH 121/167] Support rendering of underline style and color (#16097) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for underline style and color in the renderer > [!IMPORTANT] > The PR adds underline style and color feature to AtlasEngine (WT) and GDIRenderer (Conhost) only. After the underline style and color feature addition to Conpty, this PR takes it further and add support for rendering them to the screen! Out of five underline styles, we already supported rendering for 3 of those types (Singly, Doubly, Dotted) in some form in our (Atlas) renderer. The PR adds the remaining types, namely, Dashed and Curly underlines support to the renderer. - All renderer engines now receive both gridline and underline color, and the latter is used for drawing the underlines. **When no underline color is set, we use the foreground color.** - Curly underline is rendered using `sin()` within the pixel shader. - To draw underlines for DECDWL and DECDHL, we send the line rendition scale within `QuadInstance`'s texcoord attribute. - In GDI renderer, dashed and dotted underline is drawn using `HPEN` with a desired style. Curly line is a cubic Bezier that draws one wave per cell. ## PR Checklist - ✅ Set the underline color to underlines only, without affecting the gridline color. - ❌ Port to DX renderer. (Not planned as DX renderer soon to be replaced by **AtlasEngine**) - ✅ Port underline coloring and style to GDI renderer (Conhost). - ✅ Wide/Tall `CurlyUnderline` variant for `DECDWL`/`DECDHL`. Closes #7228 (cherry picked from commit e268c1c952f21c1b41ceac6ace7778d2b78620bf) Service-Card-Id: 91349180 Service-Version: 1.19 --- .github/actions/spelling/expect/expect.txt | 4 + .../UnitTests_TerminalCore/ScrollTest.cpp | 2 +- src/interactivity/onecore/BgfxEngine.cpp | 7 +- src/interactivity/onecore/BgfxEngine.hpp | 2 +- src/renderer/atlas/AtlasEngine.cpp | 7 +- src/renderer/atlas/AtlasEngine.h | 2 +- src/renderer/atlas/BackendD2D.cpp | 28 +++--- src/renderer/atlas/BackendD3D.cpp | 92 +++++++++++++----- src/renderer/atlas/BackendD3D.h | 27 +++--- src/renderer/atlas/common.h | 4 +- src/renderer/atlas/shader_common.hlsl | 5 +- src/renderer/atlas/shader_ps.hlsl | 27 +++++- src/renderer/atlas/shader_vs.hlsl | 1 + src/renderer/base/RenderSettings.cpp | 41 ++++++++ src/renderer/base/renderer.cpp | 17 +++- src/renderer/dx/DxRenderer.cpp | 40 ++++---- src/renderer/dx/DxRenderer.hpp | 2 +- src/renderer/gdi/gdirenderer.hpp | 4 +- src/renderer/gdi/paint.cpp | 95 ++++++++++++++----- src/renderer/gdi/state.cpp | 34 +++++++ src/renderer/inc/IRenderEngine.hpp | 5 +- src/renderer/inc/RenderSettings.hpp | 1 + src/renderer/uia/UiaRenderer.cpp | 10 +- src/renderer/uia/UiaRenderer.hpp | 2 +- src/renderer/vt/paint.cpp | 6 +- src/renderer/vt/vtrenderer.hpp | 2 +- src/renderer/wddmcon/WddmConRenderer.cpp | 7 +- src/renderer/wddmcon/WddmConRenderer.hpp | 2 +- src/types/UiaTextRangeBase.cpp | 30 ++++-- 29 files changed, 376 insertions(+), 130 deletions(-) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 3ea1b7e958f..454a7bfb6f6 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -344,6 +344,7 @@ CTRLVOLUME Ctxt CUF cupxy +curlyline CURRENTFONT currentmode CURRENTPAGE @@ -579,6 +580,7 @@ elems emacs EMPTYBOX enabledelayedexpansion +ENDCAP endptr endregion ENTIREBUFFER @@ -827,6 +829,7 @@ hostlib HPA hpcon HPCON +hpen hpj HPR HProvider @@ -1011,6 +1014,7 @@ LOBYTE localappdata locsrc Loewen +LOGBRUSH LOGFONT LOGFONTA LOGFONTW diff --git a/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp b/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp index 4b456e7cd5b..d5baebadf26 100644 --- a/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp @@ -57,7 +57,7 @@ namespace HRESULT InvalidateCircling(_Out_ bool* /*pForcePaint*/) noexcept { return S_OK; } HRESULT PaintBackground() noexcept { return S_OK; } HRESULT PaintBufferLine(std::span /*clusters*/, til::point /*coord*/, bool /*fTrimLeft*/, bool /*lineWrapped*/) noexcept { return S_OK; } - HRESULT PaintBufferGridLines(GridLineSet /*lines*/, COLORREF /*color*/, size_t /*cchLine*/, til::point /*coordTarget*/) noexcept { return S_OK; } + HRESULT PaintBufferGridLines(GridLineSet /*lines*/, COLORREF /*gridlineColor*/, COLORREF /*underlineColor*/, size_t /*cchLine*/, til::point /*coordTarget*/) noexcept { return S_OK; } HRESULT PaintSelection(const til::rect& /*rect*/) noexcept { return S_OK; } HRESULT PaintCursor(const CursorOptions& /*options*/) noexcept { return S_OK; } HRESULT UpdateDrawingBrushes(const TextAttribute& /*textAttributes*/, const RenderSettings& /*renderSettings*/, gsl::not_null /*pData*/, bool /*usingSoftFont*/, bool /*isSettingDefaultBrushes*/) noexcept { return S_OK; } diff --git a/src/interactivity/onecore/BgfxEngine.cpp b/src/interactivity/onecore/BgfxEngine.cpp index ce6b903ffd1..d508603d5f9 100644 --- a/src/interactivity/onecore/BgfxEngine.cpp +++ b/src/interactivity/onecore/BgfxEngine.cpp @@ -147,9 +147,10 @@ CATCH_RETURN() CATCH_RETURN() } -[[nodiscard]] HRESULT BgfxEngine::PaintBufferGridLines(GridLineSet const /*lines*/, - COLORREF const /*color*/, - size_t const /*cchLine*/, +[[nodiscard]] HRESULT BgfxEngine::PaintBufferGridLines(const GridLineSet /*lines*/, + const COLORREF /*gridlineColor*/, + const COLORREF /*underlineColor*/, + const size_t /*cchLine*/, const til::point /*coordTarget*/) noexcept { return S_OK; diff --git a/src/interactivity/onecore/BgfxEngine.hpp b/src/interactivity/onecore/BgfxEngine.hpp index 31fa49c8522..e9759ce0306 100644 --- a/src/interactivity/onecore/BgfxEngine.hpp +++ b/src/interactivity/onecore/BgfxEngine.hpp @@ -51,7 +51,7 @@ namespace Microsoft::Console::Render const til::point coord, const bool trimLeft, const bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet const lines, COLORREF const color, size_t const cchLine, til::point const coordTarget) noexcept override; + [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; diff --git a/src/renderer/atlas/AtlasEngine.cpp b/src/renderer/atlas/AtlasEngine.cpp index e46bb250835..7438b93543e 100644 --- a/src/renderer/atlas/AtlasEngine.cpp +++ b/src/renderer/atlas/AtlasEngine.cpp @@ -375,7 +375,7 @@ try } CATCH_RETURN() -[[nodiscard]] HRESULT AtlasEngine::PaintBufferGridLines(const GridLineSet lines, const COLORREF color, const size_t cchLine, const til::point coordTarget) noexcept +[[nodiscard]] HRESULT AtlasEngine::PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept try { const auto shift = gsl::narrow_cast(_api.lineRendition != LineRendition::SingleWidth); @@ -383,8 +383,9 @@ try const auto y = gsl::narrow_cast(clamp(coordTarget.y, 0, _p.s->viewportCellCount.y)); const auto from = gsl::narrow_cast(clamp(x << shift, 0, _p.s->viewportCellCount.x - 1)); const auto to = gsl::narrow_cast(clamp((x + cchLine) << shift, from, _p.s->viewportCellCount.x)); - const auto fg = gsl::narrow_cast(color) | 0xff000000; - _p.rows[y]->gridLineRanges.emplace_back(lines, fg, from, to); + const auto glColor = gsl::narrow_cast(gridlineColor) | 0xff000000; + const auto ulColor = gsl::narrow_cast(underlineColor) | 0xff000000; + _p.rows[y]->gridLineRanges.emplace_back(lines, glColor, ulColor, from, to); return S_OK; } CATCH_RETURN() diff --git a/src/renderer/atlas/AtlasEngine.h b/src/renderer/atlas/AtlasEngine.h index b135a8989f8..4ec09a9e931 100644 --- a/src/renderer/atlas/AtlasEngine.h +++ b/src/renderer/atlas/AtlasEngine.h @@ -43,7 +43,7 @@ namespace Microsoft::Console::Render::Atlas [[nodiscard]] HRESULT PrepareLineTransform(LineRendition lineRendition, til::CoordType targetRow, til::CoordType viewportLeft) noexcept override; [[nodiscard]] HRESULT PaintBackground() noexcept override; [[nodiscard]] HRESULT PaintBufferLine(std::span clusters, til::point coord, bool fTrimLeft, bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet lines, COLORREF color, size_t cchLine, til::point coordTarget) noexcept override; + [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; [[nodiscard]] HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, const RenderSettings& renderSettings, gsl::not_null pData, bool usingSoftFont, bool isSettingDefaultBrushes) noexcept override; diff --git a/src/renderer/atlas/BackendD2D.cpp b/src/renderer/atlas/BackendD2D.cpp index 301738954aa..ea4ea0923eb 100644 --- a/src/renderer/atlas/BackendD2D.cpp +++ b/src/renderer/atlas/BackendD2D.cpp @@ -409,7 +409,7 @@ void BackendD2D::_drawGridlineRow(const RenderingPayload& p, const ShapedRow* ro D2D1_POINT_2F point0{ 0, static_cast(textCellCenter) }; D2D1_POINT_2F point1{ 0, static_cast(textCellCenter + cellSize.y) }; - const auto brush = _brushWithColor(r.color); + const auto brush = _brushWithColor(r.gridlineColor); const f32 w = pos.height; const f32 hw = w * 0.5f; @@ -421,11 +421,11 @@ void BackendD2D::_drawGridlineRow(const RenderingPayload& p, const ShapedRow* ro _renderTarget->DrawLine(point0, point1, brush, w, nullptr); } }; - const auto appendHorizontalLine = [&](const GridLineRange& r, FontDecorationPosition pos, ID2D1StrokeStyle* strokeStyle) { + const auto appendHorizontalLine = [&](const GridLineRange& r, FontDecorationPosition pos, ID2D1StrokeStyle* strokeStyle, const u32 color) { const auto from = r.from >> widthShift; const auto to = r.to >> widthShift; - const auto brush = _brushWithColor(r.color); + const auto brush = _brushWithColor(color); const f32 w = pos.height; const f32 centerY = textCellCenter + pos.position + w * 0.5f; const D2D1_POINT_2F point0{ static_cast(from * cellSize.x), centerY }; @@ -448,32 +448,32 @@ void BackendD2D::_drawGridlineRow(const RenderingPayload& p, const ShapedRow* ro } if (r.lines.test(GridLines::Top)) { - appendHorizontalLine(r, p.s->font->gridTop, nullptr); + appendHorizontalLine(r, p.s->font->gridTop, nullptr, r.gridlineColor); } if (r.lines.test(GridLines::Bottom)) { - appendHorizontalLine(r, p.s->font->gridBottom, nullptr); + appendHorizontalLine(r, p.s->font->gridBottom, nullptr, r.gridlineColor); + } + if (r.lines.test(GridLines::Strikethrough)) + { + appendHorizontalLine(r, p.s->font->strikethrough, nullptr, r.gridlineColor); } if (r.lines.test(GridLines::Underline)) { - appendHorizontalLine(r, p.s->font->underline, nullptr); + appendHorizontalLine(r, p.s->font->underline, nullptr, r.underlineColor); } - if (r.lines.test(GridLines::HyperlinkUnderline)) + else if (r.lines.any(GridLines::DottedUnderline, GridLines::HyperlinkUnderline)) { - appendHorizontalLine(r, p.s->font->underline, _dottedStrokeStyle.get()); + appendHorizontalLine(r, p.s->font->underline, _dottedStrokeStyle.get(), r.underlineColor); } - if (r.lines.test(GridLines::DoubleUnderline)) + else if (r.lines.test(GridLines::DoubleUnderline)) { for (const auto pos : p.s->font->doubleUnderline) { - appendHorizontalLine(r, pos, nullptr); + appendHorizontalLine(r, pos, nullptr, r.underlineColor); } } - if (r.lines.test(GridLines::Strikethrough)) - { - appendHorizontalLine(r, p.s->font->strikethrough, nullptr); - } } } diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index 19334b3a9d4..41fefa6ea2b 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -90,7 +90,8 @@ BackendD3D::BackendD3D(const RenderingPayload& p) { static constexpr D3D11_INPUT_ELEMENT_DESC layout[]{ { "SV_Position", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, - { "shadingType", 0, DXGI_FORMAT_R32_UINT, 1, offsetof(QuadInstance, shadingType), D3D11_INPUT_PER_INSTANCE_DATA, 1 }, + { "shadingType", 0, DXGI_FORMAT_R16_UINT, 1, offsetof(QuadInstance, shadingType), D3D11_INPUT_PER_INSTANCE_DATA, 1 }, + { "renditionScale", 0, DXGI_FORMAT_R8G8_UINT, 1, offsetof(QuadInstance, renditionScale), D3D11_INPUT_PER_INSTANCE_DATA, 1 }, { "position", 0, DXGI_FORMAT_R16G16_SINT, 1, offsetof(QuadInstance, position), D3D11_INPUT_PER_INSTANCE_DATA, 1 }, { "size", 0, DXGI_FORMAT_R16G16_UINT, 1, offsetof(QuadInstance, size), D3D11_INPUT_PER_INSTANCE_DATA, 1 }, { "texcoord", 0, DXGI_FORMAT_R16G16_UINT, 1, offsetof(QuadInstance, texcoord), D3D11_INPUT_PER_INSTANCE_DATA, 1 }, @@ -305,6 +306,37 @@ void BackendD3D::_updateFontDependents(const RenderingPayload& p) { const auto& font = *p.s->font; + // The max height of Curly line peak in `em` units. + const auto maxCurlyLinePeakHeightEm = 0.075f; + // We aim for atleast 1px height, but since we draw 1px smaller curly line, + // we aim for 2px height as a result. + const auto minCurlyLinePeakHeight = 2.0f; + + // Curlyline uses the gap between cell bottom and singly underline position + // as the height of the wave's peak. The baseline for curly-line is at the + // middle of singly underline. The gap could be too big, so we also apply + // a limit on the peak height. + const auto strokeHalfWidth = font.underline.height / 2.0f; + const auto underlineMidY = font.underline.position + strokeHalfWidth; + const auto cellBottomGap = font.cellSize.y - underlineMidY - strokeHalfWidth; + const auto maxCurlyLinePeakHeight = maxCurlyLinePeakHeightEm * font.fontSize; + auto curlyLinePeakHeight = std::min(cellBottomGap, maxCurlyLinePeakHeight); + + // When it's too small to be curly, make it straight. + if (curlyLinePeakHeight < minCurlyLinePeakHeight) + { + curlyLinePeakHeight = 0; + } + + // We draw a smaller curly line (-1px) to avoid clipping due to the rounding. + _curlyLineDrawPeakHeight = std::max(0.0f, curlyLinePeakHeight - 1.0f); + + const auto curlyUnderlinePos = font.underline.position - curlyLinePeakHeight; + const auto curlyUnderlineWidth = 2.0f * (curlyLinePeakHeight + strokeHalfWidth); + const auto curlyUnderlinePosU16 = gsl::narrow_cast(lrintf(curlyUnderlinePos)); + const auto curlyUnderlineWidthU16 = gsl::narrow_cast(lrintf(curlyUnderlineWidth)); + _curlyUnderline = { curlyUnderlinePosU16, curlyUnderlineWidthU16 }; + DWrite_GetRenderParams(p.dwriteFactory.get(), &_gamma, &_cleartypeEnhancedContrast, &_grayscaleEnhancedContrast, _textRenderingParams.put()); // Clearing the atlas requires BeginDraw(), which is expensive. Defer this until we need Direct2D anyways. _fontChangedResetGlyphAtlas = true; @@ -543,6 +575,9 @@ void BackendD3D::_recreateConstBuffer(const RenderingPayload& p) const DWrite_GetGammaRatios(_gamma, data.gammaRatios); data.enhancedContrast = p.s->font->antialiasingMode == AntialiasingMode::ClearType ? _cleartypeEnhancedContrast : _grayscaleEnhancedContrast; data.underlineWidth = p.s->font->underline.height; + data.curlyLineWaveFreq = 2.0f * 3.14f / p.s->font->cellSize.x; + data.curlyLinePeakHeight = _curlyLineDrawPeakHeight; + data.curlyLineCellOffset = p.s->font->underline.position + p.s->font->underline.height / 2.0f; p.deviceContext->UpdateSubresource(_psConstantBuffer.get(), 0, nullptr, &data, 0, 0); } } @@ -1024,7 +1059,7 @@ void BackendD3D::_drawText(RenderingPayload& p) goto drawGlyphRetry; } - if (glyphEntry.data.GetShadingType() != ShadingType::Default) + if (glyphEntry.data.shadingType != ShadingType::Default) { auto l = static_cast(lrintf((baselineX + row->glyphOffsets[x].advanceOffset) * scaleX)); auto t = static_cast(lrintf((baselineY - row->glyphOffsets[x].ascenderOffset) * scaleY)); @@ -1036,7 +1071,7 @@ void BackendD3D::_drawText(RenderingPayload& p) row->dirtyBottom = std::max(row->dirtyBottom, t + glyphEntry.data.size.y); _appendQuad() = { - .shadingType = glyphEntry.data.GetShadingType(), + .shadingType = glyphEntry.data.shadingType, .position = { static_cast(l), static_cast(t) }, .size = glyphEntry.data.size, .texcoord = glyphEntry.data.texcoord, @@ -1458,7 +1493,7 @@ bool BackendD3D::_drawGlyph(const RenderingPayload& p, const AtlasFontFaceEntryI const auto triggerRight = _ligatureOverhangTriggerRight * horizontalScale; const auto overlapSplit = rect.w >= p.s->font->cellSize.x && (bl <= triggerLeft || br >= triggerRight); - glyphEntry.data.shadingType = static_cast(isColorGlyph ? ShadingType::TextPassthrough : _textShadingType); + glyphEntry.data.shadingType = isColorGlyph ? ShadingType::TextPassthrough : _textShadingType; glyphEntry.data.overlapSplit = overlapSplit; glyphEntry.data.offset.x = bl; glyphEntry.data.offset.y = bt; @@ -1527,7 +1562,7 @@ bool BackendD3D::_drawSoftFontGlyph(const RenderingPayload& p, const AtlasFontFa _drawSoftFontGlyphInBitmap(p, glyphEntry); _d2dRenderTarget->DrawBitmap(_softFontBitmap.get(), &dest, 1, interpolation, nullptr, nullptr); - glyphEntry.data.shadingType = static_cast(ShadingType::TextGrayscale); + glyphEntry.data.shadingType = ShadingType::TextGrayscale; glyphEntry.data.overlapSplit = 0; glyphEntry.data.offset.x = 0; glyphEntry.data.offset.y = -baseline; @@ -1631,11 +1666,11 @@ void BackendD3D::_splitDoubleHeightGlyph(const RenderingPayload& p, const AtlasF // double-height row. This effectively turns the other (unneeded) side into whitespace. if (!top.data.size.y) { - top.data.shadingType = static_cast(ShadingType::Default); + top.data.shadingType = ShadingType::Default; } if (!bottom.data.size.y) { - bottom.data.shadingType = static_cast(ShadingType::Default); + bottom.data.shadingType = ShadingType::Default; } } @@ -1647,8 +1682,6 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) const auto verticalShift = static_cast(row->lineRendition >= LineRendition::DoubleHeightTop); const auto cellSize = p.s->font->cellSize; - const auto dottedLineType = horizontalShift ? ShadingType::DottedLineWide : ShadingType::DottedLine; - const auto rowTop = static_cast(cellSize.y * y); const auto rowBottom = static_cast(rowTop + cellSize.y); @@ -1675,11 +1708,11 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) .shadingType = ShadingType::SolidLine, .position = { static_cast(posX), rowTop }, .size = { width, p.s->font->cellSize.y }, - .color = r.color, + .color = r.gridlineColor, }; } }; - const auto appendHorizontalLine = [&](const GridLineRange& r, FontDecorationPosition pos, ShadingType shadingType) { + const auto appendHorizontalLine = [&](const GridLineRange& r, FontDecorationPosition pos, ShadingType shadingType, const u32 color) { const auto offset = pos.position << verticalShift; const auto height = static_cast(pos.height << verticalShift); @@ -1695,9 +1728,10 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) { _appendQuad() = { .shadingType = shadingType, + .renditionScale = { static_cast(1 << horizontalShift), static_cast(1 << verticalShift) }, .position = { left, static_cast(rt) }, .size = { width, static_cast(rb - rt) }, - .color = r.color, + .color = color, }; } }; @@ -1717,32 +1751,40 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) } if (r.lines.test(GridLines::Top)) { - appendHorizontalLine(r, p.s->font->gridTop, ShadingType::SolidLine); + appendHorizontalLine(r, p.s->font->gridTop, ShadingType::SolidLine, r.gridlineColor); } if (r.lines.test(GridLines::Bottom)) { - appendHorizontalLine(r, p.s->font->gridBottom, ShadingType::SolidLine); + appendHorizontalLine(r, p.s->font->gridBottom, ShadingType::SolidLine, r.gridlineColor); + } + if (r.lines.test(GridLines::Strikethrough)) + { + appendHorizontalLine(r, p.s->font->strikethrough, ShadingType::SolidLine, r.gridlineColor); } if (r.lines.test(GridLines::Underline)) { - appendHorizontalLine(r, p.s->font->underline, ShadingType::SolidLine); + appendHorizontalLine(r, p.s->font->underline, ShadingType::SolidLine, r.underlineColor); } - if (r.lines.test(GridLines::HyperlinkUnderline)) + else if (r.lines.any(GridLines::DottedUnderline, GridLines::HyperlinkUnderline)) { - appendHorizontalLine(r, p.s->font->underline, dottedLineType); + appendHorizontalLine(r, p.s->font->underline, ShadingType::DottedLine, r.underlineColor); } - if (r.lines.test(GridLines::DoubleUnderline)) + else if (r.lines.test(GridLines::DashedUnderline)) + { + appendHorizontalLine(r, p.s->font->underline, ShadingType::DashedLine, r.underlineColor); + } + else if (r.lines.test(GridLines::CurlyUnderline)) + { + appendHorizontalLine(r, _curlyUnderline, ShadingType::CurlyLine, r.underlineColor); + } + else if (r.lines.test(GridLines::DoubleUnderline)) { for (const auto pos : p.s->font->doubleUnderline) { - appendHorizontalLine(r, pos, ShadingType::SolidLine); + appendHorizontalLine(r, pos, ShadingType::SolidLine, r.underlineColor); } } - if (r.lines.test(GridLines::Strikethrough)) - { - appendHorizontalLine(r, p.s->font->strikethrough, ShadingType::SolidLine); - } } } @@ -2042,6 +2084,8 @@ size_t BackendD3D::_drawCursorForegroundSlowPath(const CursorRect& c, size_t off auto& target = _instances[offset + i]; target.shadingType = it.shadingType; + target.renditionScale.x = it.renditionScale.x; + target.renditionScale.y = it.renditionScale.y; target.position.x = static_cast(cutout.left); target.position.y = static_cast(cutout.top); target.size.x = static_cast(cutout.right - cutout.left); @@ -2059,6 +2103,8 @@ size_t BackendD3D::_drawCursorForegroundSlowPath(const CursorRect& c, size_t off auto& target = cutoutCount ? _appendQuad() : _instances[offset]; target.shadingType = it.shadingType; + target.renditionScale.x = it.renditionScale.x; + target.renditionScale.y = it.renditionScale.y; target.position.x = static_cast(intersectionL); target.position.y = static_cast(intersectionT); target.size.x = static_cast(intersectionR - intersectionL); diff --git a/src/renderer/atlas/BackendD3D.h b/src/renderer/atlas/BackendD3D.h index 14f4ca2943e..0befe8fe342 100644 --- a/src/renderer/atlas/BackendD3D.h +++ b/src/renderer/atlas/BackendD3D.h @@ -42,6 +42,9 @@ namespace Microsoft::Console::Render::Atlas alignas(sizeof(f32x4)) f32 gammaRatios[4]{}; alignas(sizeof(f32)) f32 enhancedContrast = 0; alignas(sizeof(f32)) f32 underlineWidth = 0; + alignas(sizeof(f32)) f32 curlyLinePeakHeight = 0; + alignas(sizeof(f32)) f32 curlyLineWaveFreq = 0; + alignas(sizeof(f32)) f32 curlyLineCellOffset = 0; #pragma warning(suppress : 4324) // 'PSConstBuffer': structure was padded due to alignment specifier }; @@ -55,7 +58,7 @@ namespace Microsoft::Console::Render::Atlas #pragma warning(suppress : 4324) // 'CustomConstBuffer': structure was padded due to alignment specifier }; - enum class ShadingType : u32 + enum class ShadingType : u16 { Default = 0, Background = 0, @@ -66,12 +69,13 @@ namespace Microsoft::Console::Render::Atlas TextClearType = 2, TextPassthrough = 3, DottedLine = 4, - DottedLineWide = 5, + DashedLine = 5, + CurlyLine = 6, // All items starting here will be drawing as a solid RGBA color - SolidLine = 6, + SolidLine = 7, - Cursor = 7, - Selection = 8, + Cursor = 8, + Selection = 9, TextDrawingFirst = TextGrayscale, TextDrawingLast = SolidLine, @@ -86,7 +90,8 @@ namespace Microsoft::Console::Render::Atlas // impact on performance and power draw. If (when?) displays with >32k resolution make their // appearance in the future, this should be changed to f32x2. But if you do so, please change // all other occurrences of i16x2 positions/offsets throughout the class to keep it consistent. - alignas(u32) ShadingType shadingType; + alignas(u16) ShadingType shadingType; + alignas(u16) u8x2 renditionScale; alignas(u32) i16x2 position; alignas(u32) u16x2 size; alignas(u32) u16x2 texcoord; @@ -95,16 +100,11 @@ namespace Microsoft::Console::Render::Atlas struct alignas(u32) AtlasGlyphEntryData { - u16 shadingType; + ShadingType shadingType; u16 overlapSplit; i16x2 offset; u16x2 size; u16x2 texcoord; - - constexpr ShadingType GetShadingType() const noexcept - { - return static_cast(shadingType); - } }; // NOTE: Don't initialize any members in this struct. This ensures that no @@ -291,6 +291,9 @@ namespace Microsoft::Console::Render::Atlas // The bounding rect of _cursorRects in pixels. til::rect _cursorPosition; + f32 _curlyLineDrawPeakHeight = 0; + FontDecorationPosition _curlyUnderline; + bool _requiresContinuousRedraw = false; #if ATLAS_DEBUG_SHOW_DIRTY diff --git a/src/renderer/atlas/common.h b/src/renderer/atlas/common.h index b8fa3ac0d59..014f5487118 100644 --- a/src/renderer/atlas/common.h +++ b/src/renderer/atlas/common.h @@ -125,6 +125,7 @@ namespace Microsoft::Console::Render::Atlas }; using u8 = uint8_t; + using u8x2 = vec2; using u16 = uint16_t; using u16x2 = vec2; @@ -426,7 +427,8 @@ namespace Microsoft::Console::Render::Atlas struct GridLineRange { GridLineSet lines; - u32 color = 0; + u32 gridlineColor = 0; + u32 underlineColor = 0; u16 from = 0; u16 to = 0; }; diff --git a/src/renderer/atlas/shader_common.hlsl b/src/renderer/atlas/shader_common.hlsl index 957b17ac6ad..d712f081f28 100644 --- a/src/renderer/atlas/shader_common.hlsl +++ b/src/renderer/atlas/shader_common.hlsl @@ -7,13 +7,15 @@ #define SHADING_TYPE_TEXT_CLEARTYPE 2 #define SHADING_TYPE_TEXT_PASSTHROUGH 3 #define SHADING_TYPE_DOTTED_LINE 4 -#define SHADING_TYPE_DOTTED_LINE_WIDE 5 +#define SHADING_TYPE_DASHED_LINE 5 +#define SHADING_TYPE_CURLY_LINE 6 // clang-format on struct VSData { float2 vertex : SV_Position; uint shadingType : shadingType; + uint2 renditionScale : renditionScale; int2 position : position; uint2 size : size; uint2 texcoord : texcoord; @@ -25,6 +27,7 @@ struct PSData float4 position : SV_Position; float2 texcoord : texcoord; nointerpolation uint shadingType : shadingType; + nointerpolation uint2 renditionScale : renditionScale; nointerpolation float4 color : color; }; diff --git a/src/renderer/atlas/shader_ps.hlsl b/src/renderer/atlas/shader_ps.hlsl index 2cb92947ce2..e19ba955fe5 100644 --- a/src/renderer/atlas/shader_ps.hlsl +++ b/src/renderer/atlas/shader_ps.hlsl @@ -12,6 +12,9 @@ cbuffer ConstBuffer : register(b0) float4 gammaRatios; float enhancedContrast; float underlineWidth; + float curlyLinePeakHeight; + float curlyLineWaveFreq; + float curlyLineCellOffset; } Texture2D background : register(t0); @@ -73,18 +76,36 @@ Output main(PSData data) : SV_Target } case SHADING_TYPE_DOTTED_LINE: { - const bool on = frac(data.position.x / (2.0f * underlineWidth)) < 0.5f; + const bool on = frac(data.position.x / (2.0f * underlineWidth * data.renditionScale.x)) < 0.5f; color = on * premultiplyColor(data.color); weights = color.aaaa; break; } - case SHADING_TYPE_DOTTED_LINE_WIDE: + case SHADING_TYPE_DASHED_LINE: { - const bool on = frac(data.position.x / (4.0f * underlineWidth)) < 0.5f; + const bool on = frac(data.position.x / (backgroundCellSize.x * data.renditionScale.x)) < 0.5f; color = on * premultiplyColor(data.color); weights = color.aaaa; break; } + case SHADING_TYPE_CURLY_LINE: + { + uint cellRow = floor(data.position.y / backgroundCellSize.y); + // Use the previous cell when drawing 'Double Height' curly line. + cellRow -= data.renditionScale.y - 1; + const float cellTop = cellRow * backgroundCellSize.y; + const float centerY = cellTop + curlyLineCellOffset * data.renditionScale.y; + const float strokeWidthHalf = underlineWidth * data.renditionScale.y / 2.0f; + const float amp = curlyLinePeakHeight * data.renditionScale.y; + const float freq = curlyLineWaveFreq / data.renditionScale.x; + + const float s = sin(data.position.x * freq); + const float d = abs(centerY - (s * amp) - data.position.y); + const float a = 1 - saturate(d - strokeWidthHalf); + color = a * premultiplyColor(data.color); + weights = color.aaaa; + break; + } default: { color = premultiplyColor(data.color); diff --git a/src/renderer/atlas/shader_vs.hlsl b/src/renderer/atlas/shader_vs.hlsl index 49b9030b156..eb96fcf0e45 100644 --- a/src/renderer/atlas/shader_vs.hlsl +++ b/src/renderer/atlas/shader_vs.hlsl @@ -15,6 +15,7 @@ PSData main(VSData data) PSData output; output.color = data.color; output.shadingType = data.shadingType; + output.renditionScale = data.renditionScale; // positionScale is expected to be float2(2.0f / sizeInPixel.x, -2.0f / sizeInPixel.y). Together with the // addition below this will transform our "position" from pixel into normalized device coordinate (NDC) space. output.position.xy = (data.position + data.vertex.xy * data.size) * positionScale + float2(-1.0f, 1.0f); diff --git a/src/renderer/base/RenderSettings.cpp b/src/renderer/base/RenderSettings.cpp index 03c4795635a..f885957796a 100644 --- a/src/renderer/base/RenderSettings.cpp +++ b/src/renderer/base/RenderSettings.cpp @@ -212,6 +212,47 @@ std::pair RenderSettings::GetAttributeColorsWithAlpha(const return { fg, bg }; } +// Routine Description: +// - Calculates the RGB underline color of a given text attribute, using the +// current color table configuration and active render settings. +// - Returns the current foreground color when the underline color isn't set. +// Arguments: +// - attr - The TextAttribute to retrieve the underline color from. +// Return Value: +// - The color value of the attribute's underline. +COLORREF RenderSettings::GetAttributeUnderlineColor(const TextAttribute& attr) const noexcept +{ + const auto [fg, bg] = GetAttributeColors(attr); + const auto ulTextColor = attr.GetUnderlineColor(); + if (ulTextColor.IsDefault()) + { + return fg; + } + + const auto defaultUlIndex = GetColorAliasIndex(ColorAlias::DefaultForeground); + auto ul = ulTextColor.GetColor(_colorTable, defaultUlIndex, true); + if (attr.IsInvisible()) + { + ul = bg; + } + + // We intentionally aren't _only_ checking for attr.IsInvisible here, because we also want to + // catch the cases where the ul was intentionally set to be the same as the bg. In either case, + // don't adjust the underline color. + if constexpr (Feature_AdjustIndistinguishableText::IsEnabled()) + { + if ( + ul != bg && + (_renderMode.test(Mode::AlwaysDistinguishableColors) || + (_renderMode.test(Mode::IndexedDistinguishableColors) && ulTextColor.IsDefaultOrLegacy() && attr.GetBackground().IsDefaultOrLegacy()))) + { + ul = ColorFix::GetPerceivableColor(ul, bg, 0.5f * 0.5f); + } + } + + return ul; +} + // Routine Description: // - Increments the position in the blink cycle, toggling the blink rendition // state on every second call, potentially triggering a redraw of the given diff --git a/src/renderer/base/renderer.cpp b/src/renderer/base/renderer.cpp index 2bf98404ae2..0b1d6b004cb 100644 --- a/src/renderer/base/renderer.cpp +++ b/src/renderer/base/renderer.cpp @@ -955,13 +955,21 @@ GridLineSet Renderer::s_GetGridlines(const TextAttribute& textAttribute) noexcep { case UnderlineStyle::NoUnderline: break; + case UnderlineStyle::SinglyUnderlined: + lines.set(GridLines::Underline); + break; case UnderlineStyle::DoublyUnderlined: lines.set(GridLines::DoubleUnderline); break; - case UnderlineStyle::SinglyUnderlined: case UnderlineStyle::CurlyUnderlined: + lines.set(GridLines::CurlyUnderline); + break; case UnderlineStyle::DottedUnderlined: + lines.set(GridLines::DottedUnderline); + break; case UnderlineStyle::DashedUnderlined: + lines.set(GridLines::DashedUnderline); + break; default: lines.set(GridLines::Underline); break; @@ -1002,10 +1010,11 @@ void Renderer::_PaintBufferOutputGridLineHelper(_In_ IRenderEngine* const pEngin // Return early if there are no lines to paint. if (lines.any()) { - // Get the current foreground color to render the lines. - const auto rgb = _renderSettings.GetAttributeColors(textAttribute).first; + // Get the current foreground and underline colors to render the lines. + const auto fg = _renderSettings.GetAttributeColors(textAttribute).first; + const auto underlineColor = _renderSettings.GetAttributeUnderlineColor(textAttribute); // Draw the lines - LOG_IF_FAILED(pEngine->PaintBufferGridLines(lines, rgb, cchLine, coordTarget)); + LOG_IF_FAILED(pEngine->PaintBufferGridLines(lines, fg, underlineColor, cchLine, coordTarget)); } } diff --git a/src/renderer/dx/DxRenderer.cpp b/src/renderer/dx/DxRenderer.cpp index f74bf528dc1..064d219f72a 100644 --- a/src/renderer/dx/DxRenderer.cpp +++ b/src/renderer/dx/DxRenderer.cpp @@ -1696,14 +1696,16 @@ CATCH_RETURN() // - Paints lines around cells (draws in pieces of the grid) // Arguments: // - lines - Which grid lines (top, left, bottom, right) to draw -// - color - The color to use for drawing the lines +// - gridlineColor - The color to use for drawing the gridlines +// - underlineColor - The color to use for drawing the underlines // - cchLine - Length of the line to draw in character cells // - coordTarget - The X,Y character position in the grid where we should start drawing // - We will draw rightward (+X) from here // Return Value: // - S_OK or relevant DirectX error [[nodiscard]] HRESULT DxEngine::PaintBufferGridLines(const GridLineSet lines, - COLORREF const color, + const COLORREF gridlineColor, + const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept try @@ -1711,8 +1713,6 @@ try const auto existingColor = _d2dBrushForeground->GetColor(); const auto restoreBrushOnExit = wil::scope_exit([&]() noexcept { _d2dBrushForeground->SetColor(existingColor); }); - _d2dBrushForeground->SetColor(_ColorFFromColorRef(color | 0xff000000)); - const auto font = _fontRenderData->GlyphCell().to_d2d_size(); const D2D_POINT_2F target = { coordTarget.x * font.width, coordTarget.y * font.height }; const auto fullRunWidth = font.width * gsl::narrow_cast(cchLine); @@ -1721,10 +1721,12 @@ try _d2dDeviceContext->DrawLine({ x0, y0 }, { x1, y1 }, _d2dBrushForeground.Get(), strokeWidth, _strokeStyle.Get()); }; - const auto DrawHyperlinkLine = [=](const auto x0, const auto y0, const auto x1, const auto y1, const auto strokeWidth) noexcept { + const auto DrawDottedLine = [=](const auto x0, const auto y0, const auto x1, const auto y1, const auto strokeWidth) noexcept { _d2dDeviceContext->DrawLine({ x0, y0 }, { x1, y1 }, _d2dBrushForeground.Get(), strokeWidth, _dashStrokeStyle.Get()); }; + _d2dBrushForeground->SetColor(_ColorFFromColorRef(gridlineColor | 0xff000000)); + // NOTE: Line coordinates are centered within the line, so they need to be // offset by half the stroke width. For the start coordinate we add half // the stroke width, and for the end coordinate we subtract half the width. @@ -1773,10 +1775,22 @@ try } } + if (lines.test(GridLines::Strikethrough)) + { + const auto halfStrikethroughWidth = lineMetrics.strikethroughWidth / 2.0f; + const auto startX = target.x + halfStrikethroughWidth; + const auto endX = target.x + fullRunWidth - halfStrikethroughWidth; + const auto y = target.y + lineMetrics.strikethroughOffset; + + DrawLine(startX, y, endX, y, lineMetrics.strikethroughWidth); + } + + _d2dBrushForeground->SetColor(_ColorFFromColorRef(underlineColor | 0xff000000)); + // In the case of the underline and strikethrough offsets, the stroke width // is already accounted for, so they don't require further adjustments. - if (lines.any(GridLines::Underline, GridLines::DoubleUnderline, GridLines::HyperlinkUnderline)) + if (lines.any(GridLines::Underline, GridLines::DoubleUnderline, GridLines::DottedUnderline, GridLines::HyperlinkUnderline)) { const auto halfUnderlineWidth = lineMetrics.underlineWidth / 2.0f; const auto startX = target.x + halfUnderlineWidth; @@ -1788,9 +1802,9 @@ try DrawLine(startX, y, endX, y, lineMetrics.underlineWidth); } - if (lines.test(GridLines::HyperlinkUnderline)) + if (lines.any(GridLines::DottedUnderline, GridLines::HyperlinkUnderline)) { - DrawHyperlinkLine(startX, y, endX, y, lineMetrics.underlineWidth); + DrawDottedLine(startX, y, endX, y, lineMetrics.underlineWidth); } if (lines.test(GridLines::DoubleUnderline)) @@ -1801,16 +1815,6 @@ try } } - if (lines.test(GridLines::Strikethrough)) - { - const auto halfStrikethroughWidth = lineMetrics.strikethroughWidth / 2.0f; - const auto startX = target.x + halfStrikethroughWidth; - const auto endX = target.x + fullRunWidth - halfStrikethroughWidth; - const auto y = target.y + lineMetrics.strikethroughOffset; - - DrawLine(startX, y, endX, y, lineMetrics.strikethroughWidth); - } - return S_OK; } CATCH_RETURN() diff --git a/src/renderer/dx/DxRenderer.hpp b/src/renderer/dx/DxRenderer.hpp index bfb11205a05..877b3f1adfa 100644 --- a/src/renderer/dx/DxRenderer.hpp +++ b/src/renderer/dx/DxRenderer.hpp @@ -106,7 +106,7 @@ namespace Microsoft::Console::Render const bool fTrimLeft, const bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet const lines, COLORREF const color, size_t const cchLine, til::point const coordTarget) noexcept override; + [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; diff --git a/src/renderer/gdi/gdirenderer.hpp b/src/renderer/gdi/gdirenderer.hpp index a1ab2290a97..2cc5b4dc14c 100644 --- a/src/renderer/gdi/gdirenderer.hpp +++ b/src/renderer/gdi/gdirenderer.hpp @@ -52,7 +52,8 @@ namespace Microsoft::Console::Render const bool trimLeft, const bool lineWrapped) noexcept override; [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, - const COLORREF color, + const COLORREF gridlineColor, + const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; @@ -120,6 +121,7 @@ namespace Microsoft::Console::Render int underlineWidth; int strikethroughOffset; int strikethroughWidth; + int curlylinePeakHeight; }; LineMetrics _lineMetrics; diff --git a/src/renderer/gdi/paint.cpp b/src/renderer/gdi/paint.cpp index 17dc9bddd82..9722703d105 100644 --- a/src/renderer/gdi/paint.cpp +++ b/src/renderer/gdi/paint.cpp @@ -509,27 +509,24 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) // - Draws up to one line worth of grid lines on top of characters. // Arguments: // - lines - Enum defining which edges of the rectangle to draw -// - color - The color to use for drawing the edges. +// - gridlineColor - The color to use for drawing the gridlines. +// - underlineColor - The color to use for drawing the underlines. // - cchLine - How many characters we should draw the grid lines along (left to right in a row) // - coordTarget - The starting X/Y position of the first character to draw on. // Return Value: // - S_OK or suitable GDI HRESULT error or E_FAIL for GDI errors in functions that don't reliably return a specific error code. -[[nodiscard]] HRESULT GdiEngine::PaintBufferGridLines(const GridLineSet lines, const COLORREF color, const size_t cchLine, const til::point coordTarget) noexcept +[[nodiscard]] HRESULT GdiEngine::PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept { LOG_IF_FAILED(_FlushBufferLines()); // Convert the target from characters to pixels. const auto ptTarget = coordTarget * _GetFontSize(); - // Set the brush color as requested and save the previous brush to restore at the end. - wil::unique_hbrush hbr(CreateSolidBrush(color)); - RETURN_HR_IF_NULL(E_FAIL, hbr.get()); - - wil::unique_hbrush hbrPrev(SelectBrush(_hdcMemoryContext, hbr.get())); - RETURN_HR_IF_NULL(E_FAIL, hbrPrev.get()); - hbr.release(); // If SelectBrush was successful, GDI owns the brush. Release for now. - // On exit, be sure we try to put the brush back how it was originally. - auto restoreBrushOnExit = wil::scope_exit([&] { hbr.reset(SelectBrush(_hdcMemoryContext, hbrPrev.get())); }); + // Create a brush with the gridline color, and apply it. + wil::unique_hbrush hbr(CreateSolidBrush(gridlineColor)); + RETURN_HR_IF_NULL(E_FAIL, hbr.get()); + const auto prevBrush = wil::SelectObject(_hdcMemoryContext, hbr.get()); + RETURN_HR_IF_NULL(E_FAIL, prevBrush.get()); // Get the font size so we know the size of the rectangle lines we'll be inscribing. const auto fontWidth = _GetFontSize().width; @@ -539,6 +536,31 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) const auto DrawLine = [=](const auto x, const auto y, const auto w, const auto h) { return PatBlt(_hdcMemoryContext, x, y, w, h, PATCOPY); }; + const auto DrawStrokedLine = [&](const auto x, const auto y, const auto w) { + RETURN_HR_IF(E_FAIL, !MoveToEx(_hdcMemoryContext, x, y, nullptr)); + RETURN_HR_IF(E_FAIL, !LineTo(_hdcMemoryContext, x + w, y)); + return S_OK; + }; + const auto DrawCurlyLine = [&](const auto x, const auto y, const auto cCurlyLines) { + const auto curlyLineWidth = fontWidth; + const auto curlyLineHalfWidth = lrintf(curlyLineWidth / 2.0f); + const auto controlPointHeight = gsl::narrow_cast(std::floor(3.5f * _lineMetrics.curlylinePeakHeight)); + // Each curlyLine requires 3 `POINT`s + const auto cPoints = gsl::narrow(3 * cCurlyLines); + std::vector points; + points.reserve(cPoints); + auto start = x; + for (auto i = 0u; i < cCurlyLines; i++) + { + points.emplace_back(start + curlyLineHalfWidth, y - controlPointHeight); + points.emplace_back(start + curlyLineHalfWidth, y + controlPointHeight); + points.emplace_back(start + curlyLineWidth, y); + start += curlyLineWidth; + } + RETURN_HR_IF(E_FAIL, !MoveToEx(_hdcMemoryContext, x, y, nullptr)); + RETURN_HR_IF(E_FAIL, !PolyBezierTo(_hdcMemoryContext, points.data(), cPoints)); + return S_OK; + }; if (lines.test(GridLines::Left)) { @@ -574,22 +596,51 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y, widthOfAllCells, _lineMetrics.gridlineWidth)); } - if (lines.any(GridLines::Underline, GridLines::DoubleUnderline)) + if (lines.test(GridLines::Strikethrough)) { - const auto y = ptTarget.y + _lineMetrics.underlineOffset; - RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y, widthOfAllCells, _lineMetrics.underlineWidth)); + const auto y = ptTarget.y + _lineMetrics.strikethroughOffset; + RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y, widthOfAllCells, _lineMetrics.strikethroughWidth)); + } - if (lines.test(GridLines::DoubleUnderline)) - { - const auto y2 = ptTarget.y + _lineMetrics.underlineOffset2; - RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y2, widthOfAllCells, _lineMetrics.underlineWidth)); - } + // Create a pen matching the underline style. + DWORD underlinePenType = PS_SOLID; + if (lines.test(GridLines::DottedUnderline)) + { + underlinePenType = PS_DOT; + } + else if (lines.test(GridLines::DashedUnderline)) + { + underlinePenType = PS_DASH; } + const LOGBRUSH brushProp{ .lbStyle = BS_SOLID, .lbColor = underlineColor }; + wil::unique_hpen hpen(ExtCreatePen(underlinePenType | PS_GEOMETRIC | PS_ENDCAP_FLAT, _lineMetrics.underlineWidth, &brushProp, 0, nullptr)); - if (lines.test(GridLines::Strikethrough)) + // Apply the pen. + const auto prevPen = wil::SelectObject(_hdcMemoryContext, hpen.get()); + RETURN_HR_IF_NULL(E_FAIL, prevPen.get()); + + const auto underlineMidY = std::lround(ptTarget.y + _lineMetrics.underlineOffset + _lineMetrics.underlineWidth / 2.0f); + if (lines.test(GridLines::Underline)) { - const auto y = ptTarget.y + _lineMetrics.strikethroughOffset; - RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y, widthOfAllCells, _lineMetrics.strikethroughWidth)); + return DrawStrokedLine(ptTarget.x, underlineMidY, widthOfAllCells); + } + else if (lines.test(GridLines::DoubleUnderline)) + { + const auto doubleUnderlineBottomLineMidY = std::lround(ptTarget.y + _lineMetrics.underlineOffset2 + _lineMetrics.underlineWidth / 2.0f); + RETURN_IF_FAILED(DrawStrokedLine(ptTarget.x, underlineMidY, widthOfAllCells)); + return DrawStrokedLine(ptTarget.x, doubleUnderlineBottomLineMidY, widthOfAllCells); + } + else if (lines.test(GridLines::CurlyUnderline)) + { + return DrawCurlyLine(ptTarget.x, underlineMidY, cchLine); + } + else if (lines.test(GridLines::DottedUnderline)) + { + return DrawStrokedLine(ptTarget.x, underlineMidY, widthOfAllCells); + } + else if (lines.test(GridLines::DashedUnderline)) + { + return DrawStrokedLine(ptTarget.x, underlineMidY, widthOfAllCells); } return S_OK; diff --git a/src/renderer/gdi/state.cpp b/src/renderer/gdi/state.cpp index 556f2df02fb..099c2f43f78 100644 --- a/src/renderer/gdi/state.cpp +++ b/src/renderer/gdi/state.cpp @@ -11,6 +11,15 @@ using namespace Microsoft::Console::Render; +namespace +{ + // The max height of Curly line peak in `em` units. + constexpr auto MaxCurlyLinePeakHeightEm = 0.075f; + + // The min height of Curly line peak. + constexpr auto MinCurlyLinePeakHeight = 2.0f; +} + // Routine Description: // - Creates a new GDI-based rendering engine // - NOTE: Will throw if initialization failure. Caller must catch. @@ -397,6 +406,31 @@ GdiEngine::~GdiEngine() _lineMetrics.underlineOffset2 = _lineMetrics.underlineOffset - _lineMetrics.gridlineWidth; } + // Curly line doesn't render properly below 1px stroke width. Make it a straight line. + if (_lineMetrics.underlineWidth < 1) + { + _lineMetrics.curlylinePeakHeight = 0; + } + else + { + // Curlyline uses the gap between cell bottom and singly underline + // position as the height of the wave's peak. The baseline for curly + // line is at the middle of singly underline. The gap could be too big, + // so we also apply a limit on the peak height. + const auto strokeHalfWidth = _lineMetrics.underlineWidth / 2.0f; + const auto underlineMidY = _lineMetrics.underlineOffset + strokeHalfWidth; + const auto cellBottomGap = Font.GetSize().height - underlineMidY - strokeHalfWidth; + const auto maxCurlyLinePeakHeight = MaxCurlyLinePeakHeightEm * fontSize; + auto curlyLinePeakHeight = std::min(cellBottomGap, maxCurlyLinePeakHeight); + + // When it's too small to be curly, make it a straight line. + if (curlyLinePeakHeight < MinCurlyLinePeakHeight) + { + curlyLinePeakHeight = 0.0f; + } + _lineMetrics.curlylinePeakHeight = gsl::narrow_cast(std::floor(curlyLinePeakHeight)); + } + // Now find the size of a 0 in this current font and save it for conversions done later. _coordFontLast = Font.GetSize(); diff --git a/src/renderer/inc/IRenderEngine.hpp b/src/renderer/inc/IRenderEngine.hpp index 46221ae911d..afcaa5aff59 100644 --- a/src/renderer/inc/IRenderEngine.hpp +++ b/src/renderer/inc/IRenderEngine.hpp @@ -41,6 +41,9 @@ namespace Microsoft::Console::Render Right, Underline, DoubleUnderline, + CurlyUnderline, + DottedUnderline, + DashedUnderline, Strikethrough, HyperlinkUnderline }; @@ -73,7 +76,7 @@ namespace Microsoft::Console::Render [[nodiscard]] virtual HRESULT PrepareLineTransform(LineRendition lineRendition, til::CoordType targetRow, til::CoordType viewportLeft) noexcept = 0; [[nodiscard]] virtual HRESULT PaintBackground() noexcept = 0; [[nodiscard]] virtual HRESULT PaintBufferLine(std::span clusters, til::point coord, bool fTrimLeft, bool lineWrapped) noexcept = 0; - [[nodiscard]] virtual HRESULT PaintBufferGridLines(GridLineSet lines, COLORREF color, size_t cchLine, til::point coordTarget) noexcept = 0; + [[nodiscard]] virtual HRESULT PaintBufferGridLines(GridLineSet lines, COLORREF gridlineColor, COLORREF underlineColor, size_t cchLine, til::point coordTarget) noexcept = 0; [[nodiscard]] virtual HRESULT PaintSelection(const til::rect& rect) noexcept = 0; [[nodiscard]] virtual HRESULT PaintCursor(const CursorOptions& options) noexcept = 0; [[nodiscard]] virtual HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, const RenderSettings& renderSettings, gsl::not_null pData, bool usingSoftFont, bool isSettingDefaultBrushes) noexcept = 0; diff --git a/src/renderer/inc/RenderSettings.hpp b/src/renderer/inc/RenderSettings.hpp index 4b6e7c3981c..c836bdde848 100644 --- a/src/renderer/inc/RenderSettings.hpp +++ b/src/renderer/inc/RenderSettings.hpp @@ -41,6 +41,7 @@ namespace Microsoft::Console::Render size_t GetColorAliasIndex(const ColorAlias alias) const noexcept; std::pair GetAttributeColors(const TextAttribute& attr) const noexcept; std::pair GetAttributeColorsWithAlpha(const TextAttribute& attr) const noexcept; + COLORREF GetAttributeUnderlineColor(const TextAttribute& attr) const noexcept; void ToggleBlinkRendition(class Renderer& renderer) noexcept; private: diff --git a/src/renderer/uia/UiaRenderer.cpp b/src/renderer/uia/UiaRenderer.cpp index f39bcbe4fbf..d1e72fbd0eb 100644 --- a/src/renderer/uia/UiaRenderer.cpp +++ b/src/renderer/uia/UiaRenderer.cpp @@ -357,14 +357,16 @@ void UiaEngine::WaitUntilCanRender() noexcept // For UIA, this doesn't mean anything. So do nothing. // Arguments: // - lines - -// - color - +// - gridlineColor - +// - underlineColor - // - cchLine - // - coordTarget - // Return Value: // - S_FALSE -[[nodiscard]] HRESULT UiaEngine::PaintBufferGridLines(GridLineSet const /*lines*/, - COLORREF const /*color*/, - size_t const /*cchLine*/, +[[nodiscard]] HRESULT UiaEngine::PaintBufferGridLines(const GridLineSet /*lines*/, + const COLORREF /*gridlineColor*/, + const COLORREF /*underlineColor*/, + const size_t /*cchLine*/, const til::point /*coordTarget*/) noexcept { return S_FALSE; diff --git a/src/renderer/uia/UiaRenderer.hpp b/src/renderer/uia/UiaRenderer.hpp index 5e486b7e0e1..4625ca155cb 100644 --- a/src/renderer/uia/UiaRenderer.hpp +++ b/src/renderer/uia/UiaRenderer.hpp @@ -49,7 +49,7 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT NotifyNewText(const std::wstring_view newText) noexcept override; [[nodiscard]] HRESULT PaintBackground() noexcept override; [[nodiscard]] HRESULT PaintBufferLine(const std::span clusters, const til::point coord, const bool fTrimLeft, const bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF color, const size_t cchLine, const til::point coordTarget) noexcept override; + [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; [[nodiscard]] HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, const RenderSettings& renderSettings, const gsl::not_null pData, const bool usingSoftFont, const bool isSettingDefaultBrushes) noexcept override; diff --git a/src/renderer/vt/paint.cpp b/src/renderer/vt/paint.cpp index 801390085c0..99f8853a98f 100644 --- a/src/renderer/vt/paint.cpp +++ b/src/renderer/vt/paint.cpp @@ -187,13 +187,15 @@ using namespace Microsoft::Console::Types; // - Draws up to one line worth of grid lines on top of characters. // Arguments: // - lines - Enum defining which edges of the rectangle to draw -// - color - The color to use for drawing the edges. +// - gridlineColor - The color to use for drawing the gridlines. +// - underlineColor - The color to use for drawing the underlines. // - cchLine - How many characters we should draw the grid lines along (left to right in a row) // - coordTarget - The starting X/Y position of the first character to draw on. // Return Value: // - S_OK [[nodiscard]] HRESULT VtEngine::PaintBufferGridLines(const GridLineSet /*lines*/, - const COLORREF /*color*/, + const COLORREF /*gridlineColor*/, + const COLORREF /*underlineColor*/, const size_t /*cchLine*/, const til::point /*coordTarget*/) noexcept { diff --git a/src/renderer/vt/vtrenderer.hpp b/src/renderer/vt/vtrenderer.hpp index 094bebec385..676672ee814 100644 --- a/src/renderer/vt/vtrenderer.hpp +++ b/src/renderer/vt/vtrenderer.hpp @@ -62,7 +62,7 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT PrepareLineTransform(const LineRendition lineRendition, const til::CoordType targetRow, const til::CoordType viewportLeft) noexcept override; [[nodiscard]] HRESULT PaintBackground() noexcept override; [[nodiscard]] HRESULT PaintBufferLine(std::span clusters, til::point coord, bool fTrimLeft, bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet lines, COLORREF color, size_t cchLine, til::point coordTarget) noexcept override; + [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) noexcept override; diff --git a/src/renderer/wddmcon/WddmConRenderer.cpp b/src/renderer/wddmcon/WddmConRenderer.cpp index 378b1dfd086..fda8ae6c21b 100644 --- a/src/renderer/wddmcon/WddmConRenderer.cpp +++ b/src/renderer/wddmcon/WddmConRenderer.cpp @@ -284,9 +284,10 @@ CATCH_RETURN() CATCH_RETURN(); } -[[nodiscard]] HRESULT WddmConEngine::PaintBufferGridLines(GridLineSet const /*lines*/, - COLORREF const /*color*/, - size_t const /*cchLine*/, +[[nodiscard]] HRESULT WddmConEngine::PaintBufferGridLines(const GridLineSet /*lines*/, + const COLORREF /*gridlineColor*/, + const COLORREF /*underlineColor*/, + const size_t /*cchLine*/, const til::point /*coordTarget*/) noexcept { return S_OK; diff --git a/src/renderer/wddmcon/WddmConRenderer.hpp b/src/renderer/wddmcon/WddmConRenderer.hpp index a658ef51180..954dc226946 100644 --- a/src/renderer/wddmcon/WddmConRenderer.hpp +++ b/src/renderer/wddmcon/WddmConRenderer.hpp @@ -44,7 +44,7 @@ namespace Microsoft::Console::Render const til::point coord, const bool trimLeft, const bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet const lines, COLORREF const color, size_t const cchLine, til::point const coordTarget) noexcept override; + [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index af5ebb431ea..419acf1ff0b 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -405,16 +405,22 @@ std::optional UiaTextRangeBase::_verifyAttr(TEXTATTRIBUTEID attributeId, V THROW_HR_IF(E_INVALIDARG, val.vt != VT_I4); // The underline style is stored as a TextDecorationLineStyle. - // However, The text buffer doesn't have that many different styles for being underlined. - // Instead, we only have single and double underlined. + // However, The text buffer doesn't have all the different styles for being underlined. + // Instead, we only use a subset of them. switch (val.lVal) { case TextDecorationLineStyle_None: return !attr.IsUnderlined(); + case TextDecorationLineStyle_Single: + return attr.GetUnderlineStyle() == UnderlineStyle::SinglyUnderlined; case TextDecorationLineStyle_Double: return attr.GetUnderlineStyle() == UnderlineStyle::DoublyUnderlined; - case TextDecorationLineStyle_Single: // singly underlined and extended styles are treated the same - return attr.IsUnderlined() && attr.GetUnderlineStyle() != UnderlineStyle::DoublyUnderlined; + case TextDecorationLineStyle_Wavy: + return attr.GetUnderlineStyle() == UnderlineStyle::CurlyUnderlined; + case TextDecorationLineStyle_Dot: + return attr.GetUnderlineStyle() == UnderlineStyle::DottedUnderlined; + case TextDecorationLineStyle_Dash: + return attr.GetUnderlineStyle() == UnderlineStyle::DashedUnderlined; default: return std::nullopt; } @@ -697,18 +703,26 @@ bool UiaTextRangeBase::_initializeAttrQuery(TEXTATTRIBUTEID attributeId, VARIANT const auto style = attr.GetUnderlineStyle(); switch (style) { + case UnderlineStyle::NoUnderline: + pRetVal->lVal = TextDecorationLineStyle_None; + return true; case UnderlineStyle::SinglyUnderlined: pRetVal->lVal = TextDecorationLineStyle_Single; return true; case UnderlineStyle::DoublyUnderlined: pRetVal->lVal = TextDecorationLineStyle_Double; return true; - case UnderlineStyle::NoUnderline: - pRetVal->lVal = TextDecorationLineStyle_None; + case UnderlineStyle::CurlyUnderlined: + pRetVal->lVal = TextDecorationLineStyle_Wavy; + return true; + case UnderlineStyle::DottedUnderlined: + pRetVal->lVal = TextDecorationLineStyle_Dot; + return true; + case UnderlineStyle::DashedUnderlined: + pRetVal->lVal = TextDecorationLineStyle_Dash; return true; + // Out of range styles are treated as singly underlined. default: - // TODO: Handle other underline styles once they're supported in the graphic renderer. - // For now, extended styles are treated (and rendered) as single underline. pRetVal->lVal = TextDecorationLineStyle_Single; return true; } From a62fb06b32e7af69dfa4653bab7a9d19dbf369bb Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Fri, 15 Dec 2023 01:17:14 +0530 Subject: [PATCH 122/167] Fix curlyline rendering in `AtlasEngine` and `GDIRenderer` (#16444) Fixes Curlyline being drawn as single underline in some cases **Detailed Description** - Curlyline is drawn at all font sizes. - We might render a curlyline that is clipped in cases where we don't have enough space to draw a full curlyline. This is to give users a consistent view of Curlylines. Previously in those cases, it was drawn as a single underline. - Removed minimum threshold `minCurlyLinePeakHeight` for Curlyline drawing. - GDIRender changes: - Underline offset now points to the (vertical) mid position of the underline. Removes redundant `underlineMidY` calculation inside the draw call. Closes #16288 (cherry picked from commit f5b45c25c9dfe27e03fbea1c7d82a6dc2a009343) Service-Card-Id: 91349182 Service-Version: 1.19 --- src/renderer/atlas/BackendD3D.cpp | 56 +++++++++++++++---------------- src/renderer/atlas/BackendD3D.h | 2 +- src/renderer/gdi/paint.cpp | 29 ++++++++-------- src/renderer/gdi/state.cpp | 51 ++++++++++++---------------- 4 files changed, 65 insertions(+), 73 deletions(-) diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index 41fefa6ea2b..74b64c53571 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -306,37 +306,35 @@ void BackendD3D::_updateFontDependents(const RenderingPayload& p) { const auto& font = *p.s->font; - // The max height of Curly line peak in `em` units. - const auto maxCurlyLinePeakHeightEm = 0.075f; - // We aim for atleast 1px height, but since we draw 1px smaller curly line, - // we aim for 2px height as a result. - const auto minCurlyLinePeakHeight = 2.0f; - - // Curlyline uses the gap between cell bottom and singly underline position - // as the height of the wave's peak. The baseline for curly-line is at the - // middle of singly underline. The gap could be too big, so we also apply - // a limit on the peak height. - const auto strokeHalfWidth = font.underline.height / 2.0f; - const auto underlineMidY = font.underline.position + strokeHalfWidth; - const auto cellBottomGap = font.cellSize.y - underlineMidY - strokeHalfWidth; - const auto maxCurlyLinePeakHeight = maxCurlyLinePeakHeightEm * font.fontSize; - auto curlyLinePeakHeight = std::min(cellBottomGap, maxCurlyLinePeakHeight); + // Curlyline is drawn with a desired height relative to the font size. The + // baseline of curlyline is at the middle of singly underline. When there's + // limited space to draw a curlyline, we apply a limit on the peak height. + { + // initialize curlyline peak height to a desired value. Clamp it to at + // least 1. + constexpr auto curlyLinePeakHeightEm = 0.075f; + _curlyLinePeakHeight = std::max(1.0f, std::roundf(curlyLinePeakHeightEm * font.fontSize)); + + // calc the limit we need to apply + const auto strokeHalfWidth = std::floor(font.underline.height / 2.0f); + const auto underlineMidY = font.underline.position + strokeHalfWidth; + const auto maxDrawableCurlyLinePeakHeight = font.cellSize.y - underlineMidY - font.underline.height; + + // if the limit is <= 0 (no height at all), stick with the desired height. + // This is how we force a curlyline even when there's no space, though it + // might be clipped at the bottom. + if (maxDrawableCurlyLinePeakHeight > 0.0f) + { + _curlyLinePeakHeight = std::min(_curlyLinePeakHeight, maxDrawableCurlyLinePeakHeight); + } - // When it's too small to be curly, make it straight. - if (curlyLinePeakHeight < minCurlyLinePeakHeight) - { - curlyLinePeakHeight = 0; + const auto curlyUnderlinePos = underlineMidY - _curlyLinePeakHeight - font.underline.height; + const auto curlyUnderlineWidth = 2.0f * (_curlyLinePeakHeight + font.underline.height); + const auto curlyUnderlinePosU16 = gsl::narrow_cast(lrintf(curlyUnderlinePos)); + const auto curlyUnderlineWidthU16 = gsl::narrow_cast(lrintf(curlyUnderlineWidth)); + _curlyUnderline = { curlyUnderlinePosU16, curlyUnderlineWidthU16 }; } - // We draw a smaller curly line (-1px) to avoid clipping due to the rounding. - _curlyLineDrawPeakHeight = std::max(0.0f, curlyLinePeakHeight - 1.0f); - - const auto curlyUnderlinePos = font.underline.position - curlyLinePeakHeight; - const auto curlyUnderlineWidth = 2.0f * (curlyLinePeakHeight + strokeHalfWidth); - const auto curlyUnderlinePosU16 = gsl::narrow_cast(lrintf(curlyUnderlinePos)); - const auto curlyUnderlineWidthU16 = gsl::narrow_cast(lrintf(curlyUnderlineWidth)); - _curlyUnderline = { curlyUnderlinePosU16, curlyUnderlineWidthU16 }; - DWrite_GetRenderParams(p.dwriteFactory.get(), &_gamma, &_cleartypeEnhancedContrast, &_grayscaleEnhancedContrast, _textRenderingParams.put()); // Clearing the atlas requires BeginDraw(), which is expensive. Defer this until we need Direct2D anyways. _fontChangedResetGlyphAtlas = true; @@ -576,7 +574,7 @@ void BackendD3D::_recreateConstBuffer(const RenderingPayload& p) const data.enhancedContrast = p.s->font->antialiasingMode == AntialiasingMode::ClearType ? _cleartypeEnhancedContrast : _grayscaleEnhancedContrast; data.underlineWidth = p.s->font->underline.height; data.curlyLineWaveFreq = 2.0f * 3.14f / p.s->font->cellSize.x; - data.curlyLinePeakHeight = _curlyLineDrawPeakHeight; + data.curlyLinePeakHeight = _curlyLinePeakHeight; data.curlyLineCellOffset = p.s->font->underline.position + p.s->font->underline.height / 2.0f; p.deviceContext->UpdateSubresource(_psConstantBuffer.get(), 0, nullptr, &data, 0, 0); } diff --git a/src/renderer/atlas/BackendD3D.h b/src/renderer/atlas/BackendD3D.h index 0befe8fe342..4c248ed3ff3 100644 --- a/src/renderer/atlas/BackendD3D.h +++ b/src/renderer/atlas/BackendD3D.h @@ -291,7 +291,7 @@ namespace Microsoft::Console::Render::Atlas // The bounding rect of _cursorRects in pixels. til::rect _cursorPosition; - f32 _curlyLineDrawPeakHeight = 0; + f32 _curlyLinePeakHeight = 0.0f; FontDecorationPosition _curlyUnderline; bool _requiresContinuousRedraw = false; diff --git a/src/renderer/gdi/paint.cpp b/src/renderer/gdi/paint.cpp index 9722703d105..222576390e2 100644 --- a/src/renderer/gdi/paint.cpp +++ b/src/renderer/gdi/paint.cpp @@ -536,27 +536,30 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) const auto DrawLine = [=](const auto x, const auto y, const auto w, const auto h) { return PatBlt(_hdcMemoryContext, x, y, w, h, PATCOPY); }; - const auto DrawStrokedLine = [&](const auto x, const auto y, const auto w) { + const auto DrawStrokedLine = [&](const til::CoordType x, const til::CoordType y, const unsigned w) { RETURN_HR_IF(E_FAIL, !MoveToEx(_hdcMemoryContext, x, y, nullptr)); - RETURN_HR_IF(E_FAIL, !LineTo(_hdcMemoryContext, x + w, y)); + RETURN_HR_IF(E_FAIL, !LineTo(_hdcMemoryContext, gsl::narrow_cast(x + w), y)); return S_OK; }; - const auto DrawCurlyLine = [&](const auto x, const auto y, const auto cCurlyLines) { + const auto DrawCurlyLine = [&](const til::CoordType x, const til::CoordType y, const size_t cCurlyLines) { const auto curlyLineWidth = fontWidth; - const auto curlyLineHalfWidth = lrintf(curlyLineWidth / 2.0f); - const auto controlPointHeight = gsl::narrow_cast(std::floor(3.5f * _lineMetrics.curlylinePeakHeight)); + const auto curlyLineHalfWidth = std::lround(curlyLineWidth / 2.0f); + const auto controlPointHeight = std::lround(3.5f * _lineMetrics.curlylinePeakHeight); + // Each curlyLine requires 3 `POINT`s const auto cPoints = gsl::narrow(3 * cCurlyLines); std::vector points; points.reserve(cPoints); + auto start = x; - for (auto i = 0u; i < cCurlyLines; i++) + for (size_t i = 0; i < cCurlyLines; i++) { points.emplace_back(start + curlyLineHalfWidth, y - controlPointHeight); points.emplace_back(start + curlyLineHalfWidth, y + controlPointHeight); points.emplace_back(start + curlyLineWidth, y); start += curlyLineWidth; } + RETURN_HR_IF(E_FAIL, !MoveToEx(_hdcMemoryContext, x, y, nullptr)); RETURN_HR_IF(E_FAIL, !PolyBezierTo(_hdcMemoryContext, points.data(), cPoints)); return S_OK; @@ -619,28 +622,26 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) const auto prevPen = wil::SelectObject(_hdcMemoryContext, hpen.get()); RETURN_HR_IF_NULL(E_FAIL, prevPen.get()); - const auto underlineMidY = std::lround(ptTarget.y + _lineMetrics.underlineOffset + _lineMetrics.underlineWidth / 2.0f); if (lines.test(GridLines::Underline)) { - return DrawStrokedLine(ptTarget.x, underlineMidY, widthOfAllCells); + return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset, widthOfAllCells); } else if (lines.test(GridLines::DoubleUnderline)) { - const auto doubleUnderlineBottomLineMidY = std::lround(ptTarget.y + _lineMetrics.underlineOffset2 + _lineMetrics.underlineWidth / 2.0f); - RETURN_IF_FAILED(DrawStrokedLine(ptTarget.x, underlineMidY, widthOfAllCells)); - return DrawStrokedLine(ptTarget.x, doubleUnderlineBottomLineMidY, widthOfAllCells); + RETURN_IF_FAILED(DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset, widthOfAllCells)); + return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset2, widthOfAllCells); } else if (lines.test(GridLines::CurlyUnderline)) { - return DrawCurlyLine(ptTarget.x, underlineMidY, cchLine); + return DrawCurlyLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset, cchLine); } else if (lines.test(GridLines::DottedUnderline)) { - return DrawStrokedLine(ptTarget.x, underlineMidY, widthOfAllCells); + return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset, widthOfAllCells); } else if (lines.test(GridLines::DashedUnderline)) { - return DrawStrokedLine(ptTarget.x, underlineMidY, widthOfAllCells); + return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset, widthOfAllCells); } return S_OK; diff --git a/src/renderer/gdi/state.cpp b/src/renderer/gdi/state.cpp index 099c2f43f78..13fd0ea59fc 100644 --- a/src/renderer/gdi/state.cpp +++ b/src/renderer/gdi/state.cpp @@ -11,15 +11,6 @@ using namespace Microsoft::Console::Render; -namespace -{ - // The max height of Curly line peak in `em` units. - constexpr auto MaxCurlyLinePeakHeightEm = 0.075f; - - // The min height of Curly line peak. - constexpr auto MinCurlyLinePeakHeight = 2.0f; -} - // Routine Description: // - Creates a new GDI-based rendering engine // - NOTE: Will throw if initialization failure. Caller must catch. @@ -406,29 +397,31 @@ GdiEngine::~GdiEngine() _lineMetrics.underlineOffset2 = _lineMetrics.underlineOffset - _lineMetrics.gridlineWidth; } - // Curly line doesn't render properly below 1px stroke width. Make it a straight line. - if (_lineMetrics.underlineWidth < 1) - { - _lineMetrics.curlylinePeakHeight = 0; - } - else + // Since we use GDI pen for drawing, the underline offset should point to + // the center of the underline. + const auto underlineHalfWidth = gsl::narrow_cast(std::floor(_lineMetrics.underlineWidth / 2.0f)); + _lineMetrics.underlineOffset += underlineHalfWidth; + _lineMetrics.underlineOffset2 += underlineHalfWidth; + + // Curlyline is drawn with a desired height relative to the font size. The + // baseline of curlyline is at the middle of singly underline. When there's + // limited space to draw a curlyline, we apply a limit on the peak height. { - // Curlyline uses the gap between cell bottom and singly underline - // position as the height of the wave's peak. The baseline for curly - // line is at the middle of singly underline. The gap could be too big, - // so we also apply a limit on the peak height. - const auto strokeHalfWidth = _lineMetrics.underlineWidth / 2.0f; - const auto underlineMidY = _lineMetrics.underlineOffset + strokeHalfWidth; - const auto cellBottomGap = Font.GetSize().height - underlineMidY - strokeHalfWidth; - const auto maxCurlyLinePeakHeight = MaxCurlyLinePeakHeightEm * fontSize; - auto curlyLinePeakHeight = std::min(cellBottomGap, maxCurlyLinePeakHeight); - - // When it's too small to be curly, make it a straight line. - if (curlyLinePeakHeight < MinCurlyLinePeakHeight) + // initialize curlyline peak height to a desired value. Clamp it to at + // least 1. + constexpr auto curlyLinePeakHeightEm = 0.075f; + _lineMetrics.curlylinePeakHeight = gsl::narrow_cast(std::max(1L, std::lround(curlyLinePeakHeightEm * fontSize))); + + // calc the limit we need to apply + const auto maxDrawableCurlyLinePeakHeight = Font.GetSize().height - _lineMetrics.underlineOffset - _lineMetrics.underlineWidth; + + // if the limit is <= 0 (no height at all), stick with the desired height. + // This is how we force a curlyline even when there's no space, though it + // might be clipped at the bottom. + if (maxDrawableCurlyLinePeakHeight > 0.0f) { - curlyLinePeakHeight = 0.0f; + _lineMetrics.curlylinePeakHeight = std::min(_lineMetrics.curlylinePeakHeight, maxDrawableCurlyLinePeakHeight); } - _lineMetrics.curlylinePeakHeight = gsl::narrow_cast(std::floor(curlyLinePeakHeight)); } // Now find the size of a 0 in this current font and save it for conversions done later. From acb06304c2002af43c321dd88fda3ae80c244c66 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sat, 16 Dec 2023 00:02:24 +0100 Subject: [PATCH 123/167] Put the final touches on GDI's underlines (#16475) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While #16444 left wavy lines in an amazing state already, there were a few more things that could be done to make GDI look more consistent with other well known Windows applications. But before that, a couple unrelated, but helpful changes were made: * `GdiEngine::UpdateFont` was heavily modified to do all calculations in floats. All modern CPUs have fast FPUs and even the fairly slow `lroundf` function is so fast (relatively) nowadays that in a cold path like this, we can liberally call it to convert back to `int`s. This makes intermediate calculation more accurate and consistent. * `GdiEngine::PaintBufferGridLines` was exception-unsafe due to its use of a `std::vector` with catch clause and this PR fixes that. Additionally, the vector was swapped out with a `til::small_vector` to reduce heap allocations. (Arena allocators!) * RenderingTests was updated to cover styled underlines With that in place, these improvements were done: * Word's double-underline algorithm was ported over from `AtlasEngine`. It uses a half underline-width (aka `thinLineWidth`) which will now also be used for wavy lines to make them look a bit more filigrane. * The Bézier curve for wavy/curly underlines was modified to use control points at (0.5,0.5) and (0.5,-0.5) respectively. This results in a maxima at y=0.1414 which is much closer to a sine curve with a maxima at 1/(2pi) = 0.1592. Previously, the maxima was a lot higher (roughly 4x) depending on the aspect ratio of the glyphs. * Wavy underlines don't depend on the aspect ratio of glyphs anymore. This previously led to several problems depending on the exact font. The old renderer would draw exactly 3 periods of the wave into each cell which would also ensure continuity between cells. Unfortunately, this meant that waves could look inconsistent. The new approach always uses the aforementioned sine-like waves. * The wavy underline offset was clamped so that it's never outside of bounds of a line. This avoids clipping. * Compile RenderingTests and run it * Using Consolas, MS Gothic and Cascadia Code while Ctrl+Scrolling up and down works as expected without clipping ✅ (cherry picked from commit 99193c9a3f462b3177b6f596706f11be2074efa5) Service-Card-Id: 91356394 Service-Version: 1.19 --- .github/actions/spelling/expect/expect.txt | 13 -- src/renderer/gdi/gdirenderer.hpp | 10 +- src/renderer/gdi/paint.cpp | 85 ++++++----- src/renderer/gdi/state.cpp | 160 ++++++++++++--------- src/tools/RenderingTests/main.cpp | 147 +++++++++++++------ 5 files changed, 256 insertions(+), 159 deletions(-) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 454a7bfb6f6..b308b81caaf 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -313,7 +313,6 @@ CPPCORECHECK cppcorecheckrules cpprestsdk cppwinrt -CProc cpx CREATESCREENBUFFER CREATESTRUCT @@ -344,7 +343,6 @@ CTRLVOLUME Ctxt CUF cupxy -curlyline CURRENTFONT currentmode CURRENTPAGE @@ -607,7 +605,6 @@ EXETYPE exeuwp exewin exitwin -expectedinput EXPUNGECOMMANDHISTORY EXSTYLE EXTENDEDEDITKEY @@ -830,7 +827,6 @@ HPA hpcon HPCON hpen -hpj HPR HProvider HREDRAW @@ -1022,7 +1018,6 @@ logissue losslessly loword lparam -LPCCH lpch LPCPLINFO LPCREATESTRUCT @@ -1221,7 +1216,6 @@ NOMOVE NONALERT nonbreaking nonclient -NONCONST NONINFRINGEMENT NONPREROTATED nonspace @@ -1582,7 +1576,6 @@ rgbs rgci rgfae rgfte -rgi rgn rgp rgpwsz @@ -1751,7 +1744,6 @@ SOLIDBOX Solutiondir somefile sourced -spammy SRCCODEPAGE SRCCOPY SRCINVERT @@ -1914,7 +1906,6 @@ tprivapi tput tracelog tracelogging -traceloggingprovider traceviewpp trackbar TRACKCOMPOSITION @@ -2007,7 +1998,6 @@ USEFILLATTRIBUTE USEGLYPHCHARS USEHICON USEPOSITION -userbase USERDATA userdpiapi Userp @@ -2050,8 +2040,6 @@ VKKEYSCAN VMs VPA VPR -VProc -VRaw VREDRAW vsc vsconfig @@ -2093,7 +2081,6 @@ WCIA WCIW WCSHELPER wcsicmp -wcsnicmp wcsrev wddm wddmcon diff --git a/src/renderer/gdi/gdirenderer.hpp b/src/renderer/gdi/gdirenderer.hpp index 2cc5b4dc14c..e4bbb029fc2 100644 --- a/src/renderer/gdi/gdirenderer.hpp +++ b/src/renderer/gdi/gdirenderer.hpp @@ -116,12 +116,16 @@ namespace Microsoft::Console::Render struct LineMetrics { int gridlineWidth; - int underlineOffset; - int underlineOffset2; + int thinLineWidth; + int underlineCenter; int underlineWidth; + int doubleUnderlinePosTop; + int doubleUnderlinePosBottom; int strikethroughOffset; int strikethroughWidth; - int curlylinePeakHeight; + int curlyLineCenter; + int curlyLinePeriod; + int curlyLineControlPointOffset; }; LineMetrics _lineMetrics; diff --git a/src/renderer/gdi/paint.cpp b/src/renderer/gdi/paint.cpp index 222576390e2..30c4ad2ad2d 100644 --- a/src/renderer/gdi/paint.cpp +++ b/src/renderer/gdi/paint.cpp @@ -2,9 +2,10 @@ // Licensed under the MIT license. #include "precomp.h" -#include #include "gdirenderer.hpp" +#include + #include "../inc/unicode.hpp" #pragma hdrstop @@ -516,6 +517,7 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) // Return Value: // - S_OK or suitable GDI HRESULT error or E_FAIL for GDI errors in functions that don't reliably return a specific error code. [[nodiscard]] HRESULT GdiEngine::PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept +try { LOG_IF_FAILED(_FlushBufferLines()); @@ -531,38 +533,50 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) // Get the font size so we know the size of the rectangle lines we'll be inscribing. const auto fontWidth = _GetFontSize().width; const auto fontHeight = _GetFontSize().height; - const auto widthOfAllCells = fontWidth * gsl::narrow_cast(cchLine); + const auto widthOfAllCells = fontWidth * gsl::narrow_cast(cchLine); - const auto DrawLine = [=](const auto x, const auto y, const auto w, const auto h) { + const auto DrawLine = [=](const til::CoordType x, const til::CoordType y, const til::CoordType w, const til::CoordType h) { return PatBlt(_hdcMemoryContext, x, y, w, h, PATCOPY); }; - const auto DrawStrokedLine = [&](const til::CoordType x, const til::CoordType y, const unsigned w) { + const auto DrawStrokedLine = [&](const til::CoordType x, const til::CoordType y, const til::CoordType w) { RETURN_HR_IF(E_FAIL, !MoveToEx(_hdcMemoryContext, x, y, nullptr)); - RETURN_HR_IF(E_FAIL, !LineTo(_hdcMemoryContext, gsl::narrow_cast(x + w), y)); + RETURN_HR_IF(E_FAIL, !LineTo(_hdcMemoryContext, x + w, y)); return S_OK; }; - const auto DrawCurlyLine = [&](const til::CoordType x, const til::CoordType y, const size_t cCurlyLines) { - const auto curlyLineWidth = fontWidth; - const auto curlyLineHalfWidth = std::lround(curlyLineWidth / 2.0f); - const auto controlPointHeight = std::lround(3.5f * _lineMetrics.curlylinePeakHeight); - - // Each curlyLine requires 3 `POINT`s - const auto cPoints = gsl::narrow(3 * cCurlyLines); - std::vector points; - points.reserve(cPoints); - - auto start = x; - for (size_t i = 0; i < cCurlyLines; i++) + const auto DrawCurlyLine = [&](const til::CoordType begX, const til::CoordType y, const til::CoordType width) { + const auto period = _lineMetrics.curlyLinePeriod; + const auto halfPeriod = period / 2; + const auto controlPointOffset = _lineMetrics.curlyLineControlPointOffset; + + // To ensure proper continuity of the wavy line between cells of different line color + // this code starts/ends the line earlier/later than it should and then clips it. + // Clipping in GDI is expensive, but it was the easiest approach. + // I've noticed that subtracting -1px prevents missing pixels when GDI draws. They still + // occur at certain (small) font sizes, but I couldn't figure out how to prevent those. + const auto lineStart = ((begX - 1) / period) * period; + const auto lineEnd = begX + width; + + IntersectClipRect(_hdcMemoryContext, begX, ptTarget.y, begX + width, ptTarget.y + fontHeight); + const auto restoreRegion = wil::scope_exit([&]() { + // Luckily no one else uses clip regions. They're weird to use. + SelectClipRgn(_hdcMemoryContext, nullptr); + }); + + // You can assume that each cell has roughly 5 POINTs on average. 128 POINTs is 1KiB. + til::small_vector points; + + // This is the start point of the Bézier curve. + points.emplace_back(lineStart, y); + + for (auto x = lineStart; x < lineEnd; x += period) { - points.emplace_back(start + curlyLineHalfWidth, y - controlPointHeight); - points.emplace_back(start + curlyLineHalfWidth, y + controlPointHeight); - points.emplace_back(start + curlyLineWidth, y); - start += curlyLineWidth; + points.emplace_back(x + halfPeriod, y - controlPointOffset); + points.emplace_back(x + halfPeriod, y + controlPointOffset); + points.emplace_back(x + period, y); } - RETURN_HR_IF(E_FAIL, !MoveToEx(_hdcMemoryContext, x, y, nullptr)); - RETURN_HR_IF(E_FAIL, !PolyBezierTo(_hdcMemoryContext, points.data(), cPoints)); - return S_OK; + const auto cpt = gsl::narrow_cast(points.size()); + return PolyBezier(_hdcMemoryContext, points.data(), cpt); }; if (lines.test(GridLines::Left)) @@ -605,7 +619,6 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y, widthOfAllCells, _lineMetrics.strikethroughWidth)); } - // Create a pen matching the underline style. DWORD underlinePenType = PS_SOLID; if (lines.test(GridLines::DottedUnderline)) { @@ -615,8 +628,15 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) { underlinePenType = PS_DASH; } + + DWORD underlineWidth = _lineMetrics.underlineWidth; + if (lines.any(GridLines::DoubleUnderline, GridLines::CurlyUnderline)) + { + underlineWidth = _lineMetrics.thinLineWidth; + } + const LOGBRUSH brushProp{ .lbStyle = BS_SOLID, .lbColor = underlineColor }; - wil::unique_hpen hpen(ExtCreatePen(underlinePenType | PS_GEOMETRIC | PS_ENDCAP_FLAT, _lineMetrics.underlineWidth, &brushProp, 0, nullptr)); + wil::unique_hpen hpen(ExtCreatePen(underlinePenType | PS_GEOMETRIC | PS_ENDCAP_FLAT, underlineWidth, &brushProp, 0, nullptr)); // Apply the pen. const auto prevPen = wil::SelectObject(_hdcMemoryContext, hpen.get()); @@ -624,28 +644,29 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) if (lines.test(GridLines::Underline)) { - return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset, widthOfAllCells); + return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineCenter, widthOfAllCells); } else if (lines.test(GridLines::DoubleUnderline)) { - RETURN_IF_FAILED(DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset, widthOfAllCells)); - return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset2, widthOfAllCells); + RETURN_IF_FAILED(DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.doubleUnderlinePosTop, widthOfAllCells)); + return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.doubleUnderlinePosBottom, widthOfAllCells); } else if (lines.test(GridLines::CurlyUnderline)) { - return DrawCurlyLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset, cchLine); + return DrawCurlyLine(ptTarget.x, ptTarget.y + _lineMetrics.curlyLineCenter, widthOfAllCells); } else if (lines.test(GridLines::DottedUnderline)) { - return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset, widthOfAllCells); + return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineCenter, widthOfAllCells); } else if (lines.test(GridLines::DashedUnderline)) { - return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset, widthOfAllCells); + return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineCenter, widthOfAllCells); } return S_OK; } +CATCH_RETURN(); // Routine Description: // - Draws the cursor on the screen diff --git a/src/renderer/gdi/state.cpp b/src/renderer/gdi/state.cpp index 13fd0ea59fc..f993c6f74e6 100644 --- a/src/renderer/gdi/state.cpp +++ b/src/renderer/gdi/state.cpp @@ -344,85 +344,109 @@ GdiEngine::~GdiEngine() // There is no font metric for the grid line width, so we use a small // multiple of the font size, which typically rounds to a pixel. - const auto fontSize = _tmFontMetrics.tmHeight - _tmFontMetrics.tmInternalLeading; - _lineMetrics.gridlineWidth = std::lround(fontSize * 0.025); + const auto cellHeight = static_cast(Font.GetSize().height); + const auto fontSize = static_cast(_tmFontMetrics.tmHeight - _tmFontMetrics.tmInternalLeading); + const auto baseline = static_cast(_tmFontMetrics.tmAscent); + float idealGridlineWidth = std::max(1.0f, fontSize * 0.025f); + float idealUnderlineTop = 0; + float idealUnderlineWidth = 0; + float idealStrikethroughTop = 0; + float idealStrikethroughWidth = 0; OUTLINETEXTMETRICW outlineMetrics; if (GetOutlineTextMetricsW(_hdcMemoryContext, sizeof(outlineMetrics), &outlineMetrics)) { // For TrueType fonts, the other line metrics can be obtained from // the font's outline text metric structure. - _lineMetrics.underlineOffset = outlineMetrics.otmsUnderscorePosition; - _lineMetrics.underlineWidth = outlineMetrics.otmsUnderscoreSize; - _lineMetrics.strikethroughOffset = outlineMetrics.otmsStrikeoutPosition; - _lineMetrics.strikethroughWidth = outlineMetrics.otmsStrikeoutSize; + idealUnderlineTop = static_cast(baseline - outlineMetrics.otmsUnderscorePosition); + idealUnderlineWidth = static_cast(outlineMetrics.otmsUnderscoreSize); + idealStrikethroughWidth = static_cast(outlineMetrics.otmsStrikeoutSize); + idealStrikethroughTop = static_cast(baseline - outlineMetrics.otmsStrikeoutPosition); } else { - // If we can't obtain the outline metrics for the font, we just pick - // some reasonable values for the offsets and widths. - _lineMetrics.underlineOffset = -std::lround(fontSize * 0.05); - _lineMetrics.underlineWidth = _lineMetrics.gridlineWidth; - _lineMetrics.strikethroughOffset = std::lround(_tmFontMetrics.tmAscent / 3.0); - _lineMetrics.strikethroughWidth = _lineMetrics.gridlineWidth; + // If we can't obtain the outline metrics for the font, we just pick some reasonable values for the offsets and widths. + idealUnderlineTop = std::max(1.0f, roundf(baseline - fontSize * 0.05f)); + idealUnderlineWidth = idealGridlineWidth; + idealStrikethroughTop = std::max(1.0f, roundf(baseline * (2.0f / 3.0f))); + idealStrikethroughWidth = idealGridlineWidth; } - // We always want the lines to be visible, so if a stroke width ends - // up being zero, we need to make it at least 1 pixel. - _lineMetrics.gridlineWidth = std::max(_lineMetrics.gridlineWidth, 1); - _lineMetrics.underlineWidth = std::max(_lineMetrics.underlineWidth, 1); - _lineMetrics.strikethroughWidth = std::max(_lineMetrics.strikethroughWidth, 1); - - // Offsets are relative to the base line of the font, so we subtract - // from the ascent to get an offset relative to the top of the cell. - const auto ascent = _tmFontMetrics.tmAscent; - _lineMetrics.underlineOffset = ascent - _lineMetrics.underlineOffset; - _lineMetrics.strikethroughOffset = ascent - _lineMetrics.strikethroughOffset; - - // For double underlines we need a second offset, just below the first, - // but with a bit of a gap (about double the grid line width). - _lineMetrics.underlineOffset2 = _lineMetrics.underlineOffset + - _lineMetrics.underlineWidth + - std::lround(fontSize * 0.05); - - // However, we don't want the underline to extend past the bottom of the - // cell, so we clamp the offset to fit just inside. - const auto maxUnderlineOffset = Font.GetSize().height - _lineMetrics.underlineWidth; - _lineMetrics.underlineOffset2 = std::min(_lineMetrics.underlineOffset2, maxUnderlineOffset); - - // But if the resulting gap isn't big enough even to register as a thicker - // line, it's better to place the second line slightly above the first. - if (_lineMetrics.underlineOffset2 < _lineMetrics.underlineOffset + _lineMetrics.gridlineWidth) - { - _lineMetrics.underlineOffset2 = _lineMetrics.underlineOffset - _lineMetrics.gridlineWidth; - } - - // Since we use GDI pen for drawing, the underline offset should point to - // the center of the underline. - const auto underlineHalfWidth = gsl::narrow_cast(std::floor(_lineMetrics.underlineWidth / 2.0f)); - _lineMetrics.underlineOffset += underlineHalfWidth; - _lineMetrics.underlineOffset2 += underlineHalfWidth; - - // Curlyline is drawn with a desired height relative to the font size. The - // baseline of curlyline is at the middle of singly underline. When there's - // limited space to draw a curlyline, we apply a limit on the peak height. - { - // initialize curlyline peak height to a desired value. Clamp it to at - // least 1. - constexpr auto curlyLinePeakHeightEm = 0.075f; - _lineMetrics.curlylinePeakHeight = gsl::narrow_cast(std::max(1L, std::lround(curlyLinePeakHeightEm * fontSize))); - - // calc the limit we need to apply - const auto maxDrawableCurlyLinePeakHeight = Font.GetSize().height - _lineMetrics.underlineOffset - _lineMetrics.underlineWidth; - - // if the limit is <= 0 (no height at all), stick with the desired height. - // This is how we force a curlyline even when there's no space, though it - // might be clipped at the bottom. - if (maxDrawableCurlyLinePeakHeight > 0.0f) - { - _lineMetrics.curlylinePeakHeight = std::min(_lineMetrics.curlylinePeakHeight, maxDrawableCurlyLinePeakHeight); - } - } + // GdiEngine::PaintBufferGridLines paints underlines using HPEN and LineTo, etc., which draws lines centered on the given coordinates. + // This means we need to shift the limit (cellHeight - underlineWidth) and offset (idealUnderlineTop) by half the width. + const auto underlineWidth = std::max(1.0f, roundf(idealUnderlineWidth)); + const auto underlineCenter = std::min(floorf(cellHeight - underlineWidth / 2.0f), roundf(idealUnderlineTop + underlineWidth / 2.0f)); + + const auto strikethroughWidth = std::max(1.0f, roundf(idealStrikethroughWidth)); + const auto strikethroughOffset = std::min(cellHeight - strikethroughWidth, roundf(idealStrikethroughTop)); + + // For double underlines we loosely follow what Word does: + // 1. The lines are half the width of an underline + // 2. Ideally the bottom line is aligned with the bottom of the underline + // 3. The top underline is vertically in the middle between baseline and ideal bottom underline + // 4. If the top line gets too close to the baseline the underlines are shifted downwards + // 5. The minimum gap between the two lines appears to be similar to Tex (1.2pt) + // (Additional notes below.) + + // 1. + const auto thinLineWidth = std::max(1.0f, roundf(idealUnderlineWidth / 2.0f)); + // 2. + auto doubleUnderlinePosBottom = underlineCenter + underlineWidth - thinLineWidth; + // 3. Since we don't align the center of our two lines, but rather the top borders + // we need to subtract half a line width from our center point. + auto doubleUnderlinePosTop = roundf((baseline + doubleUnderlinePosBottom - thinLineWidth) / 2.0f); + // 4. + doubleUnderlinePosTop = std::max(doubleUnderlinePosTop, baseline + thinLineWidth); + // 5. The gap is only the distance _between_ the lines, but we need the distance from the + // top border of the top and bottom lines, which includes an additional line width. + const auto doubleUnderlineGap = std::max(1.0f, roundf(1.2f / 72.0f * _iCurrentDpi)); + doubleUnderlinePosBottom = std::max(doubleUnderlinePosBottom, doubleUnderlinePosTop + doubleUnderlineGap + thinLineWidth); + // Our cells can't overlap each other so we additionally clamp the bottom line to be inside the cell boundaries. + doubleUnderlinePosBottom = std::min(doubleUnderlinePosBottom, cellHeight - thinLineWidth); + + // The wave line is drawn using a cubic Bézier curve (PolyBezier), because that happens to be cheap with GDI. + // We use a Bézier curve where, if the start (a) and end (c) points are at (0,0) and (1,0), the control points are + // at (0.5,0.5) (b) and (0.5,-0.5) (d) respectively. Like this but a/b/c/d are square and the lines are round: + // + // b + // + // ^ + // / \ here's some text so the compiler ignores the trailing \ character + // a \ c + // \ / + // v + // + // d + // + // If you punch x=0.25 into the cubic bezier formula you get y=0.140625. This constant is + // important to us because it (plus the line width) tells us the amplitude of the wave. + // + // We can use the inverse of the constant to figure out how many px one period of the wave has to be to end up being 1px tall. + // In our case we want the amplitude of the wave to have a peak-to-peak amplitude that matches our double-underline. + const auto doubleUnderlineHalfDistance = 0.5f * (doubleUnderlinePosBottom - doubleUnderlinePosTop); + const auto doubleUnderlineCenter = doubleUnderlinePosTop + doubleUnderlineHalfDistance; + const auto curlyLineIdealAmplitude = std::max(1.0f, doubleUnderlineHalfDistance); + // Since GDI can't deal with fractional pixels, we first calculate the control point offsets (0.5 and -0.5) by multiplying by 0.5 and + // then undo that by multiplying by 2.0 for the period. This ensures that our control points can be at curlyLinePeriod/2, an integer. + const auto curlyLineControlPointOffset = roundf(curlyLineIdealAmplitude * (1.0f / 0.140625f) * 0.5f); + const auto curlyLinePeriod = curlyLineControlPointOffset * 2.0f; + // We can reverse the above to get back the actual amplitude of our Bézier curve. The line + // will be drawn with a width of thinLineWidth in the center of the curve (= 0.5x padding). + const auto curlyLineAmplitude = 0.140625f * curlyLinePeriod + 0.5f * thinLineWidth; + // To make the wavy line with its double-underline amplitude look consistent with the double-underline we position it at its center. + const auto curlyLineOffset = std::min(roundf(doubleUnderlineCenter), floorf(cellHeight - curlyLineAmplitude)); + + _lineMetrics.gridlineWidth = lroundf(idealGridlineWidth); + _lineMetrics.thinLineWidth = lroundf(thinLineWidth); + _lineMetrics.underlineCenter = lroundf(underlineCenter); + _lineMetrics.underlineWidth = lroundf(underlineWidth); + _lineMetrics.doubleUnderlinePosTop = lroundf(doubleUnderlinePosTop); + _lineMetrics.doubleUnderlinePosBottom = lroundf(doubleUnderlinePosBottom); + _lineMetrics.strikethroughOffset = lroundf(strikethroughOffset); + _lineMetrics.strikethroughWidth = lroundf(strikethroughWidth); + _lineMetrics.curlyLineCenter = lroundf(curlyLineOffset); + _lineMetrics.curlyLinePeriod = lroundf(curlyLinePeriod); + _lineMetrics.curlyLineControlPointOffset = lroundf(curlyLineControlPointOffset); // Now find the size of a 0 in this current font and save it for conversions done later. _coordFontLast = Font.GetSize(); diff --git a/src/tools/RenderingTests/main.cpp b/src/tools/RenderingTests/main.cpp index a82e38bb818..45dfcf66ea7 100644 --- a/src/tools/RenderingTests/main.cpp +++ b/src/tools/RenderingTests/main.cpp @@ -5,7 +5,39 @@ #include #include -#include +#include + +// The following list of colors is only used as a debug aid and not part of the final product. +// They're licensed under: +// +// Apache-Style Software License for ColorBrewer software and ColorBrewer Color Schemes +// +// Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and The Pennsylvania State University. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +// +namespace colorbrewer +{ + inline constexpr uint32_t pastel1[]{ + 0xfbb4ae, + 0xb3cde3, + 0xccebc5, + 0xdecbe4, + 0xfed9a6, + 0xffffcc, + 0xe5d8bd, + 0xfddaec, + 0xf2f2f2, + }; +} // Another variant of "defer" for C++. namespace @@ -110,64 +142,93 @@ int main() }; { - struct ConsoleAttributeTest + struct AttributeTest { const wchar_t* text = nullptr; WORD attribute = 0; }; - static constexpr ConsoleAttributeTest consoleAttributeTests[]{ - { L"Console attributes:", 0 }, + + { + static constexpr AttributeTest consoleAttributeTests[]{ + { L"Console attributes:", 0 }, #define MAKE_TEST_FOR_ATTRIBUTE(attr) { L## #attr, attr } - MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_GRID_HORIZONTAL), - MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_GRID_LVERTICAL), - MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_GRID_RVERTICAL), - MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_REVERSE_VIDEO), - MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_UNDERSCORE), + MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_GRID_HORIZONTAL), + MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_GRID_LVERTICAL), + MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_GRID_RVERTICAL), + MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_REVERSE_VIDEO), + MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_UNDERSCORE), #undef MAKE_TEST_FOR_ATTRIBUTE - { L"all gridlines", COMMON_LVB_GRID_HORIZONTAL | COMMON_LVB_GRID_LVERTICAL | COMMON_LVB_GRID_RVERTICAL | COMMON_LVB_UNDERSCORE }, - { L"all attributes", COMMON_LVB_GRID_HORIZONTAL | COMMON_LVB_GRID_LVERTICAL | COMMON_LVB_GRID_RVERTICAL | COMMON_LVB_REVERSE_VIDEO | COMMON_LVB_UNDERSCORE }, - }; + { L"all gridlines", COMMON_LVB_GRID_HORIZONTAL | COMMON_LVB_GRID_LVERTICAL | COMMON_LVB_GRID_RVERTICAL | COMMON_LVB_UNDERSCORE }, + { L"all attributes", COMMON_LVB_GRID_HORIZONTAL | COMMON_LVB_GRID_LVERTICAL | COMMON_LVB_GRID_RVERTICAL | COMMON_LVB_REVERSE_VIDEO | COMMON_LVB_UNDERSCORE }, + }; - SHORT row = 2; - for (const auto& t : consoleAttributeTests) - { - const auto length = static_cast(wcslen(t.text)); - printfUTF16(L"\x1B[%d;5H%s", row + 1, t.text); + SHORT row = 2; + for (const auto& t : consoleAttributeTests) + { + const auto length = static_cast(wcslen(t.text)); + printfUTF16(L"\x1B[%d;5H%s", row + 1, t.text); - WORD attributes[32]; - std::fill_n(&attributes[0], length, static_cast(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | t.attribute)); + WORD attributes[32]; + std::fill_n(&attributes[0], length, static_cast(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | t.attribute)); - DWORD numberOfAttrsWritten; - WriteConsoleOutputAttribute(outputHandle, attributes, length, { 4, row }, &numberOfAttrsWritten); + DWORD numberOfAttrsWritten; + WriteConsoleOutputAttribute(outputHandle, attributes, length, { 4, row }, &numberOfAttrsWritten); - row += 2; + row += 2; + } } - struct VTAttributeTest { - const wchar_t* text = nullptr; - int sgr = 0; - }; - static constexpr VTAttributeTest vtAttributeTests[]{ - { L"ANSI escape SGR:", 0 }, - { L"bold", 1 }, - { L"faint", 2 }, - { L"italic", 3 }, - { L"underline", 4 }, - { L"reverse", 7 }, - { L"strikethrough", 9 }, - { L"double underline", 21 }, - { L"overlined", 53 }, - }; + static constexpr AttributeTest basicSGR[]{ + { L"bold", 1 }, + { L"faint", 2 }, + { L"italic", 3 }, + { L"underline", 4 }, + { L"reverse", 7 }, + { L"strikethrough", 9 }, + { L"double underline", 21 }, + { L"overlined", 53 }, + }; + + printfUTF16(L"\x1B[3;39HANSI escape SGR:"); + + int row = 5; + for (const auto& t : basicSGR) + { + printfUTF16(L"\x1B[%d;39H\x1b[%dm%s\x1b[m", row, t.attribute, t.text); + row += 2; + } - row = 3; - for (const auto& t : vtAttributeTests) - { - printfUTF16(L"\x1B[%d;45H\x1b[%dm%s\x1b[m", row, t.sgr, t.text); - row += 2; + printfUTF16(L"\x1B[%d;39H\x1b]8;;https://example.com\x1b\\hyperlink\x1b]8;;\x1b\\", row); } - printfUTF16(L"\x1B[%d;45H\x1b]8;;https://example.com\x1b\\hyperlink\x1b]8;;\x1b\\", row); + { + static constexpr AttributeTest styledUnderlines[]{ + { L"straight", 1 }, + { L"double", 2 }, + { L"curly", 3 }, + { L"dotted", 4 }, + { L"dashed", 5 }, + }; + + printfUTF16(L"\x1B[3;63HStyled Underlines:"); + + int row = 5; + for (const auto& t : styledUnderlines) + { + printfUTF16(L"\x1B[%d;63H\x1b[4:%dm", row, t.attribute); + + const auto len = wcslen(t.text); + for (size_t i = 0; i < len; ++i) + { + const auto color = colorbrewer::pastel1[i % std::size(colorbrewer::pastel1)]; + printfUTF16(L"\x1B[58:2::%d:%d:%dm%c", (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff, t.text[i]); + } + + printfUTF16(L"\x1b[m"); + row += 2; + } + } wait(); clear(); From b02316b37c78fd2efefdfba676a504e816176fdf Mon Sep 17 00:00:00 2001 From: Craig Loewen Date: Mon, 8 Jan 2024 11:20:37 -0500 Subject: [PATCH 124/167] Update similarIssues.yml to have a lower tolerance (#16530) The tolerance value for a similar repo was changed from 0.8 to 0.75. This is because I changed the backend service for this to use pinecone instead of Azure AI search (see here https://github.com/craigloewen-msft/GitGudIssues/commit/f72fa59e23c0b7501b613daea95371cb13831d42 ) and the metric changed as a result of that. They are slightly lower than they were before, so this should offset that. --- .github/workflows/similarIssues.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/similarIssues.yml b/.github/workflows/similarIssues.yml index f3d17ac11b5..b199c3b7a80 100644 --- a/.github/workflows/similarIssues.yml +++ b/.github/workflows/similarIssues.yml @@ -15,7 +15,7 @@ jobs: with: issuetitle: ${{ github.event.issue.title }} repo: ${{ github.repository }} - similaritytolerance: "0.8" + similaritytolerance: "0.75" add-comment: needs: getSimilarIssues runs-on: ubuntu-latest From 375d00d0cd2c2934b0d136dcf7c295a5f99b07d5 Mon Sep 17 00:00:00 2001 From: Craig Loewen Date: Mon, 8 Jan 2024 15:30:41 -0500 Subject: [PATCH 125/167] Fix similarIssues.yml to not fail when no similar issues found (#16542) Added an if statement to similarIssues.yml so that the logic can be updated to not show as 'failure' when no similar issue is found. Related: https://github.com/craigloewen-msft/GitGudSimilarIssues/issues/33 --- .github/workflows/similarIssues.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/similarIssues.yml b/.github/workflows/similarIssues.yml index b199c3b7a80..fa93ad8597d 100644 --- a/.github/workflows/similarIssues.yml +++ b/.github/workflows/similarIssues.yml @@ -21,6 +21,7 @@ jobs: runs-on: ubuntu-latest permissions: issues: write + if: needs.getSimilarIssues.outputs.message != '' steps: - name: Add comment run: gh issue comment "$NUMBER" --repo "$REPO" --body "$BODY" From c4c06dadadcc2b1b722944961bf12826ccad3a67 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Tue, 9 Jan 2024 12:11:14 -0800 Subject: [PATCH 126/167] Remove EDP auditing completely (#16460) This pull request started out very differently. I was going to move all the EDP code from the internal `conint` project into the public, because EDP is [fully documented]! Well, it doesn't have any headers in the SDK. Or import libraries. And it's got a deprecation notice: > [!NOTE] > Starting in July 2022, Microsoft is deprecating Windows Information > Protection (WIP) and the APIs that support WIP. Microsoft will continue > to support WIP on supported versions of Windows. New versions of Windows > won't include new capabilities for WIP, and it won't be supported in > future versions of Windows. So I'm blasting it out the airlock instead. [fully documented]: https://learn.microsoft.com/en-us/windows/win32/devnotes/windows-information-protection-api --- .github/actions/spelling/expect/expect.txt | 1 - src/host/res.rc | 3 --- src/host/resource.h | 1 - src/host/sources.inc | 2 -- src/inc/conint.h | 5 ----- src/interactivity/win32/Clipboard.cpp | 2 -- src/interactivity/win32/resource.h | 1 - src/interactivity/win32/ut_interactivity_win32/sources | 2 -- src/internal/stubs.cpp | 4 ---- src/propsheet/sources | 2 -- src/terminal/adapter/ut_adapter/sources | 2 -- src/terminal/parser/ut_parser/sources | 2 -- 12 files changed, 27 deletions(-) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 6ae7a458582..c3a8eb95f17 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -544,7 +544,6 @@ Edgium EDITKEYS EDITTEXT EDITUPDATE -edputil Efast efghijklmn EHsc diff --git a/src/host/res.rc b/src/host/res.rc index b35e9c87929..6f566147e33 100644 --- a/src/host/res.rc +++ b/src/host/res.rc @@ -61,9 +61,6 @@ BEGIN ID_CONSOLE_FMT_WINDOWTITLE, "%s%s" -/* WIP Audit destination name */ - ID_CONSOLE_WIP_DESTINATIONNAME, "console application" - /* Menu items that replace the standard ones. These don't have the accelerators */ SC_CLOSE, "&Close" diff --git a/src/host/resource.h b/src/host/resource.h index f81be52a827..9ea8cf549b6 100644 --- a/src/host/resource.h +++ b/src/host/resource.h @@ -28,7 +28,6 @@ Author(s): #define ID_CONSOLE_MSGMARKMODE 0x100C #define ID_CONSOLE_MSGSCROLLMODE 0x100D #define ID_CONSOLE_FMT_WINDOWTITLE 0x100E -#define ID_CONSOLE_WIP_DESTINATIONNAME 0x100F // Menu Item strings #define ID_CONSOLE_COPY 0xFFF0 diff --git a/src/host/sources.inc b/src/host/sources.inc index 92b2629aaf8..17fd8f8df14 100644 --- a/src/host/sources.inc +++ b/src/host/sources.inc @@ -138,7 +138,6 @@ TARGETLIBS = \ $(ONECOREUAP_EXTERNAL_SDK_LIB_PATH)\d3d11.lib \ $(MODERNCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\api-ms-win-mm-playsound-l1.lib \ $(ONECORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-dwmapi-ext-l1.lib \ - $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-edputil-policy-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-dc-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-dc-create-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-draw-l1.lib \ @@ -206,7 +205,6 @@ DELAYLOAD = \ api-ms-win-shell-dataobject-l1.dll; \ api-ms-win-shell-namespace-l1.dll; \ ext-ms-win-dwmapi-ext-l1.dll; \ - ext-ms-win-edputil-policy-l1.dll; \ ext-ms-win-usp10-l1.dll; \ ext-ms-win-gdi-dc-l1.dll; \ ext-ms-win-gdi-dc-create-l1.dll; \ diff --git a/src/inc/conint.h b/src/inc/conint.h index 2a3c8d0fd54..495b390f2bb 100644 --- a/src/inc/conint.h +++ b/src/inc/conint.h @@ -35,11 +35,6 @@ namespace Microsoft::Console::Internal } - namespace EdpPolicy - { - void AuditClipboard(const std::wstring_view destinationName) noexcept; - } - namespace Theming { [[nodiscard]] HRESULT TrySetDarkMode(HWND hwnd) noexcept; diff --git a/src/interactivity/win32/Clipboard.cpp b/src/interactivity/win32/Clipboard.cpp index be97047404c..684eb6d70a1 100644 --- a/src/interactivity/win32/Clipboard.cpp +++ b/src/interactivity/win32/Clipboard.cpp @@ -75,8 +75,6 @@ void Clipboard::Paste() StringPaste(pwstr, (ULONG)GlobalSize(ClipboardDataHandle) / sizeof(WCHAR)); // WIP auditing if user is enrolled - static auto DestinationName = _LoadString(ID_CONSOLE_WIP_DESTINATIONNAME); - Microsoft::Console::Internal::EdpPolicy::AuditClipboard(DestinationName); GlobalUnlock(ClipboardDataHandle); diff --git a/src/interactivity/win32/resource.h b/src/interactivity/win32/resource.h index f453557218c..9b82b787ec0 100644 --- a/src/interactivity/win32/resource.h +++ b/src/interactivity/win32/resource.h @@ -25,7 +25,6 @@ Author(s): #define ID_CONSOLE_MSGMARKMODE 0x100C #define ID_CONSOLE_MSGSCROLLMODE 0x100D #define ID_CONSOLE_FMT_WINDOWTITLE 0x100E -#define ID_CONSOLE_WIP_DESTINATIONNAME 0x100F // Menu Item strings #define ID_CONSOLE_COPY 0xFFF0 diff --git a/src/interactivity/win32/ut_interactivity_win32/sources b/src/interactivity/win32/ut_interactivity_win32/sources index 0bfe9148eae..1f417647e44 100644 --- a/src/interactivity/win32/ut_interactivity_win32/sources +++ b/src/interactivity/win32/ut_interactivity_win32/sources @@ -51,7 +51,6 @@ TARGETLIBS = \ $(ONECOREUAP_EXTERNAL_SDK_LIB_PATH)\d3dcompiler.lib \ $(MODERNCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\api-ms-win-mm-playsound-l1.lib \ $(ONECORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-dwmapi-ext-l1.lib \ - $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-edputil-policy-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-dc-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-dc-create-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-draw-l1.lib \ @@ -116,7 +115,6 @@ DELAYLOAD = \ api-ms-win-shell-dataobject-l1.dll; \ api-ms-win-shell-namespace-l1.dll; \ ext-ms-win-dwmapi-ext-l1.dll; \ - ext-ms-win-edputil-policy-l1.dll; \ ext-ms-win-gdi-dc-l1.dll; \ ext-ms-win-gdi-dc-create-l1.dll; \ ext-ms-win-gdi-draw-l1.dll; \ diff --git a/src/internal/stubs.cpp b/src/internal/stubs.cpp index f0cf811b017..3ac946a209a 100644 --- a/src/internal/stubs.cpp +++ b/src/internal/stubs.cpp @@ -21,10 +21,6 @@ using namespace Microsoft::Console::Internal; return S_OK; } -void EdpPolicy::AuditClipboard(const std::wstring_view /*destinationName*/) noexcept -{ -} - [[nodiscard]] HRESULT Theming::TrySetDarkMode(HWND /*hwnd*/) noexcept { return S_FALSE; diff --git a/src/propsheet/sources b/src/propsheet/sources index 648dd1d1c12..033d3a5396d 100644 --- a/src/propsheet/sources +++ b/src/propsheet/sources @@ -84,7 +84,6 @@ TARGETLIBS = \ $(ONECORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\onecoreuap_internal.lib \ $(ONECOREUAP_INTERNAL_SDK_LIB_PATH)\onecoreuapuuid.lib \ $(ONECORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-dwmapi-ext-l1.lib \ - $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-edputil-policy-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-dc-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-dc-create-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-draw-l1.lib \ @@ -109,7 +108,6 @@ TARGETLIBS = \ DELAYLOAD = \ ext-ms-win-dwmapi-ext-l1.dll; \ - ext-ms-win-edputil-policy-l1.dll; \ ext-ms-win-uxtheme-themes-l1.dll; \ ext-ms-win-shell32-shellfolders-l1.dll; \ ext-ms-win-gdi-dc-l1.dll; \ diff --git a/src/terminal/adapter/ut_adapter/sources b/src/terminal/adapter/ut_adapter/sources index 81cb43dcfaa..8b931860d9a 100644 --- a/src/terminal/adapter/ut_adapter/sources +++ b/src/terminal/adapter/ut_adapter/sources @@ -49,7 +49,6 @@ TARGETLIBS = \ $(ONECOREUAP_EXTERNAL_SDK_LIB_PATH)\d3dcompiler.lib \ $(MODERNCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\api-ms-win-mm-playsound-l1.lib \ $(ONECORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-dwmapi-ext-l1.lib \ - $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-edputil-policy-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-dc-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-dc-create-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-draw-l1.lib \ @@ -111,7 +110,6 @@ DELAYLOAD = \ api-ms-win-shell-dataobject-l1.dll; \ api-ms-win-shell-namespace-l1.dll; \ ext-ms-win-dwmapi-ext-l1.dll; \ - ext-ms-win-edputil-policy-l1.dll; \ ext-ms-win-usp10-l1.dll; \ ext-ms-win-gdi-dc-l1.dll; \ ext-ms-win-gdi-dc-create-l1.dll; \ diff --git a/src/terminal/parser/ut_parser/sources b/src/terminal/parser/ut_parser/sources index e754a4e01c2..d4da3cf1d49 100644 --- a/src/terminal/parser/ut_parser/sources +++ b/src/terminal/parser/ut_parser/sources @@ -39,7 +39,6 @@ TARGETLIBS = \ $(ONECOREUAP_EXTERNAL_SDK_LIB_PATH)\d3dcompiler.lib \ $(MODERNCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\api-ms-win-mm-playsound-l1.lib \ $(ONECORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-dwmapi-ext-l1.lib \ - $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-edputil-policy-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-dc-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-dc-create-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-draw-l1.lib \ @@ -102,7 +101,6 @@ DELAYLOAD = \ api-ms-win-shell-dataobject-l1.dll; \ api-ms-win-shell-namespace-l1.dll; \ ext-ms-win-dwmapi-ext-l1.dll; \ - ext-ms-win-edputil-policy-l1.dll; \ ext-ms-win-gdi-dc-l1.dll; \ ext-ms-win-gdi-dc-create-l1.dll; \ ext-ms-win-gdi-draw-l1.dll; \ From d115500cff5637bac27fff0875e19b87e480f826 Mon Sep 17 00:00:00 2001 From: James Holderness Date: Tue, 9 Jan 2024 20:44:27 +0000 Subject: [PATCH 127/167] Enable alternate scroll mode by default (#16535) This PR enables alternate scroll mode by default, and also fixes the precedence so if there is any other mouse tracking mode enabled, that will take priority. ## Validation Steps Performed I've manually tested by viewing a file with `less`, and confirmed that it can now scroll using the mouse wheel by default. Also tested mouse mouse in vim and confirmed that still works. ## PR Checklist Closes #13187 --- src/terminal/input/mouseInput.cpp | 10 +++++----- src/terminal/input/terminalInput.cpp | 2 +- src/terminal/input/terminalInput.hpp | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/terminal/input/mouseInput.cpp b/src/terminal/input/mouseInput.cpp index ddcb81b7e9b..d17482216ca 100644 --- a/src/terminal/input/mouseInput.cpp +++ b/src/terminal/input/mouseInput.cpp @@ -334,11 +334,6 @@ TerminalInput::OutputType TerminalInput::HandleMouse(const til::point position, _mouseInputState.accumulatedDelta = 0; } - if (ShouldSendAlternateScroll(button, delta)) - { - return _makeAlternateScrollOutput(delta); - } - if (IsTrackingMouseInput()) { // isHover is only true for WM_MOUSEMOVE events @@ -392,6 +387,11 @@ TerminalInput::OutputType TerminalInput::HandleMouse(const til::point position, } } + if (ShouldSendAlternateScroll(button, delta)) + { + return _makeAlternateScrollOutput(delta); + } + return {}; } diff --git a/src/terminal/input/terminalInput.cpp b/src/terminal/input/terminalInput.cpp index 855ba74b28a..ae5e94ed22c 100644 --- a/src/terminal/input/terminalInput.cpp +++ b/src/terminal/input/terminalInput.cpp @@ -259,7 +259,7 @@ bool TerminalInput::GetInputMode(const Mode mode) const noexcept void TerminalInput::ResetInputModes() noexcept { - _inputMode = { Mode::Ansi, Mode::AutoRepeat }; + _inputMode = { Mode::Ansi, Mode::AutoRepeat, Mode::AlternateScroll }; _mouseInputState.lastPos = { -1, -1 }; _mouseInputState.lastButton = 0; } diff --git a/src/terminal/input/terminalInput.hpp b/src/terminal/input/terminalInput.hpp index 1bb49cca7b2..17cf2a6fef3 100644 --- a/src/terminal/input/terminalInput.hpp +++ b/src/terminal/input/terminalInput.hpp @@ -70,7 +70,7 @@ namespace Microsoft::Console::VirtualTerminal std::optional _lastVirtualKeyCode; - til::enumset _inputMode{ Mode::Ansi, Mode::AutoRepeat }; + til::enumset _inputMode{ Mode::Ansi, Mode::AutoRepeat, Mode::AlternateScroll }; bool _forceDisableWin32InputMode{ false }; [[nodiscard]] OutputType _makeCharOutput(wchar_t ch); From fb8b1202153890775d7c3e64a0899d5533b20f8e Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 9 Jan 2024 22:00:32 +0100 Subject: [PATCH 128/167] Remove leftover telemetry code (#16468) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This cleans up some leftover unused telemetry skeleton code. ## Validation Steps Performed * A TraceLogging viewing application shows events ✅ --- src/host/host-common.vcxitems | 2 -- src/host/lib/hostlib.vcxproj.filters | 6 ++---- src/host/precomp.h | 2 +- src/host/sources.inc | 1 - src/host/telemetry.cpp | 21 ------------------- src/host/telemetry.hpp | 17 --------------- src/interactivity/win32/menu.cpp | 1 - src/interactivity/win32/window.cpp | 1 - src/server/ApiDispatchers.cpp | 1 - src/server/ApiDispatchersInternal.cpp | 1 - src/server/IoDispatchers.cpp | 1 - src/server/ProcessHandle.cpp | 1 - src/terminal/adapter/lib/adapter.vcxproj | 6 +----- .../adapter/lib/adapter.vcxproj.filters | 13 +----------- src/terminal/adapter/precomp.h | 3 --- src/terminal/adapter/sources.inc | 2 -- src/terminal/adapter/telemetry.cpp | 6 ------ src/terminal/adapter/telemetry.hpp | 20 ------------------ src/terminal/adapter/tracing.cpp | 5 ----- src/terminal/adapter/tracing.hpp | 18 ---------------- src/terminal/parser/tracing.hpp | 2 -- 21 files changed, 5 insertions(+), 125 deletions(-) delete mode 100644 src/host/telemetry.cpp delete mode 100644 src/host/telemetry.hpp delete mode 100644 src/terminal/adapter/telemetry.cpp delete mode 100644 src/terminal/adapter/telemetry.hpp delete mode 100644 src/terminal/adapter/tracing.cpp delete mode 100644 src/terminal/adapter/tracing.hpp diff --git a/src/host/host-common.vcxitems b/src/host/host-common.vcxitems index 74d000c3c56..5ae8d7a0f3b 100644 --- a/src/host/host-common.vcxitems +++ b/src/host/host-common.vcxitems @@ -43,7 +43,6 @@ - @@ -96,7 +95,6 @@ - diff --git a/src/host/lib/hostlib.vcxproj.filters b/src/host/lib/hostlib.vcxproj.filters index ff328c21575..0e8177d4bc4 100644 --- a/src/host/lib/hostlib.vcxproj.filters +++ b/src/host/lib/hostlib.vcxproj.filters @@ -99,9 +99,6 @@ Source Files - - Source Files - Source Files @@ -320,5 +317,6 @@ + - + \ No newline at end of file diff --git a/src/host/precomp.h b/src/host/precomp.h index c7f41e06dfe..e28931ebaf4 100644 --- a/src/host/precomp.h +++ b/src/host/precomp.h @@ -66,7 +66,7 @@ Module Name: TRACELOGGING_DECLARE_PROVIDER(g_hConhostV2EventTraceProvider); #include #include -#include "telemetry.hpp" + #include "tracing.hpp" #ifdef BUILDING_INSIDE_WINIDE diff --git a/src/host/sources.inc b/src/host/sources.inc index 17fd8f8df14..f17c5191ac8 100644 --- a/src/host/sources.inc +++ b/src/host/sources.inc @@ -72,7 +72,6 @@ SOURCES = \ ..\_output.cpp \ ..\_stream.cpp \ ..\utils.cpp \ - ..\telemetry.cpp \ ..\tracing.cpp \ ..\registry.cpp \ ..\settings.cpp \ diff --git a/src/host/telemetry.cpp b/src/host/telemetry.cpp deleted file mode 100644 index 9b1d7256799..00000000000 --- a/src/host/telemetry.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "telemetry.hpp" - -// This code remains to serve as template if we ever need telemetry for conhost again. -// The git history for this file may prove useful. -#if 0 -Telemetry::Telemetry() -{ - TraceLoggingRegister(g_hConhostV2EventTraceProvider); - TraceLoggingWriteStart(_activity, "ActivityStart"); -} - -Telemetry::~Telemetry() -{ - TraceLoggingWriteStop(_activity, "ActivityStop"); - TraceLoggingUnregister(g_hConhostV2EventTraceProvider); -} -#endif diff --git a/src/host/telemetry.hpp b/src/host/telemetry.hpp deleted file mode 100644 index 619205b9f7b..00000000000 --- a/src/host/telemetry.hpp +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -#pragma once - -// This code remains to serve as template if we ever need telemetry for conhost again. -// The git history for this file may prove useful. -#if 0 -class Telemetry -{ -public: - Telemetry(); - ~Telemetry(); - -private: - TraceLoggingActivity _activity; -}; -#endif diff --git a/src/interactivity/win32/menu.cpp b/src/interactivity/win32/menu.cpp index 7b59887daaf..44e803931ff 100644 --- a/src/interactivity/win32/menu.cpp +++ b/src/interactivity/win32/menu.cpp @@ -14,7 +14,6 @@ #include "../../host/misc.h" #include "../../host/server.h" #include "../../host/scrolling.hpp" -#include "../../host/telemetry.hpp" #include "../inc/ServiceLocator.hpp" diff --git a/src/interactivity/win32/window.cpp b/src/interactivity/win32/window.cpp index 599d1f376a7..03eed072c13 100644 --- a/src/interactivity/win32/window.cpp +++ b/src/interactivity/win32/window.cpp @@ -20,7 +20,6 @@ #include "../../host/scrolling.hpp" #include "../../host/srvinit.h" #include "../../host/stream.h" -#include "../../host/telemetry.hpp" #include "../../host/tracing.hpp" #include "../../renderer/base/renderer.hpp" diff --git a/src/server/ApiDispatchers.cpp b/src/server/ApiDispatchers.cpp index 0aa25057591..8039977b5e5 100644 --- a/src/server/ApiDispatchers.cpp +++ b/src/server/ApiDispatchers.cpp @@ -9,7 +9,6 @@ #include "../host/getset.h" #include "../host/stream.h" #include "../host/srvinit.h" -#include "../host/telemetry.hpp" #include "../host/cmdline.h" // Assumes that it will find in the calling environment. diff --git a/src/server/ApiDispatchersInternal.cpp b/src/server/ApiDispatchersInternal.cpp index bff2a62e8d2..cc4e7be3cdb 100644 --- a/src/server/ApiDispatchersInternal.cpp +++ b/src/server/ApiDispatchersInternal.cpp @@ -8,7 +8,6 @@ #include "../host/globals.h" #include "../host/handle.h" #include "../host/server.h" -#include "../host/telemetry.hpp" #include "../host/ntprivapi.hpp" diff --git a/src/server/IoDispatchers.cpp b/src/server/IoDispatchers.cpp index a2c3b175a38..664e8ebab85 100644 --- a/src/server/IoDispatchers.cpp +++ b/src/server/IoDispatchers.cpp @@ -12,7 +12,6 @@ #include "../host/directio.h" #include "../host/handle.h" #include "../host/srvinit.h" -#include "../host/telemetry.hpp" #include "../interactivity/base/HostSignalInputThread.hpp" #include "../interactivity/inc/ServiceLocator.hpp" diff --git a/src/server/ProcessHandle.cpp b/src/server/ProcessHandle.cpp index bcfd43d1928..eb5798d7648 100644 --- a/src/server/ProcessHandle.cpp +++ b/src/server/ProcessHandle.cpp @@ -6,7 +6,6 @@ #include "ProcessHandle.h" #include "../host/globals.h" -#include "../host/telemetry.hpp" // Routine Description: // - Constructs an instance of the ConsoleProcessHandle Class diff --git a/src/terminal/adapter/lib/adapter.vcxproj b/src/terminal/adapter/lib/adapter.vcxproj index 3ecefa5e838..2780c818dfa 100644 --- a/src/terminal/adapter/lib/adapter.vcxproj +++ b/src/terminal/adapter/lib/adapter.vcxproj @@ -16,9 +16,7 @@ - - Create @@ -32,12 +30,10 @@ - - @@ -50,4 +46,4 @@ - + \ No newline at end of file diff --git a/src/terminal/adapter/lib/adapter.vcxproj.filters b/src/terminal/adapter/lib/adapter.vcxproj.filters index 21524f61cd6..798522c651f 100644 --- a/src/terminal/adapter/lib/adapter.vcxproj.filters +++ b/src/terminal/adapter/lib/adapter.vcxproj.filters @@ -18,15 +18,9 @@ Source Files - - Source Files - Source Files - - Source Files - Source Files @@ -53,15 +47,9 @@ Header Files - - Header Files - Header Files - - Header Files - Header Files @@ -89,5 +77,6 @@ + \ No newline at end of file diff --git a/src/terminal/adapter/precomp.h b/src/terminal/adapter/precomp.h index 838240d9b83..abed7d0b9f6 100644 --- a/src/terminal/adapter/precomp.h +++ b/src/terminal/adapter/precomp.h @@ -19,7 +19,4 @@ Module Name: #include -#include "telemetry.hpp" -#include "tracing.hpp" - #include "../../inc/conattrs.hpp" diff --git a/src/terminal/adapter/sources.inc b/src/terminal/adapter/sources.inc index d1e63430bb4..a7a07a29814 100644 --- a/src/terminal/adapter/sources.inc +++ b/src/terminal/adapter/sources.inc @@ -36,8 +36,6 @@ SOURCES= \ ..\MacroBuffer.cpp \ ..\adaptDispatchGraphics.cpp \ ..\terminalOutput.cpp \ - ..\telemetry.cpp \ - ..\tracing.cpp \ INCLUDES = \ $(INCLUDES); \ diff --git a/src/terminal/adapter/telemetry.cpp b/src/terminal/adapter/telemetry.cpp deleted file mode 100644 index f4a63025a0e..00000000000 --- a/src/terminal/adapter/telemetry.cpp +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" - -#include "telemetry.hpp" diff --git a/src/terminal/adapter/telemetry.hpp b/src/terminal/adapter/telemetry.hpp deleted file mode 100644 index 1a2ba41a862..00000000000 --- a/src/terminal/adapter/telemetry.hpp +++ /dev/null @@ -1,20 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- telemetry.hpp - -Abstract: -- This module is used for recording all telemetry feedback from the console virtual terminal parser - ---*/ -#pragma once - -// Including TraceLogging essentials for the binary -#include -#include -#include -#include - -TRACELOGGING_DECLARE_PROVIDER(g_hConsoleVirtTermParserEventTraceProvider); diff --git a/src/terminal/adapter/tracing.cpp b/src/terminal/adapter/tracing.cpp deleted file mode 100644 index b72682fe914..00000000000 --- a/src/terminal/adapter/tracing.cpp +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "tracing.hpp" diff --git a/src/terminal/adapter/tracing.hpp b/src/terminal/adapter/tracing.hpp deleted file mode 100644 index dad93792aed..00000000000 --- a/src/terminal/adapter/tracing.hpp +++ /dev/null @@ -1,18 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- tracing.hpp - -Abstract: -- This module is used for recording tracing/debugging information to the telemetry ETW channel -- The data is not automatically broadcast to telemetry backends as it does not set the TELEMETRY keyword. -- NOTE: Many functions in this file appear to be copy/pastes. This is because the TraceLog documentation warns - to not be "cute" in trying to reduce its macro usages with variables as it can cause unexpected behavior. - ---*/ - -#pragma once - -#include "telemetry.hpp" diff --git a/src/terminal/parser/tracing.hpp b/src/terminal/parser/tracing.hpp index ecc357c3236..56e449f06fe 100644 --- a/src/terminal/parser/tracing.hpp +++ b/src/terminal/parser/tracing.hpp @@ -18,8 +18,6 @@ Module Name: #include #include -TRACELOGGING_DECLARE_PROVIDER(g_hConsoleVirtTermParserEventTraceProvider); - namespace Microsoft::Console::VirtualTerminal { class ParserTracing sealed From 057183b651e254ff29a25a9c3ca4f56032c34048 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Wed, 10 Jan 2024 10:06:14 -0800 Subject: [PATCH 129/167] Update SUI Color Scheme colors' AutoProp.Name and ToolTip (#16544) In the Settings UI's Color Scheme page (where you edit the color scheme itself), update the color chip buttons to include the RGB value in the tooltip and screen reader announcements. Closes #15985 Closes #15983 ## Validation Steps Performed Tooltip and screen reader announcement is updated on launch and when a new value is selected. --- .../ColorSchemeViewModel.cpp | 14 ++++++++++++++ .../TerminalSettingsEditor/ColorSchemeViewModel.h | 7 +++++++ .../ColorSchemeViewModel.idl | 1 + .../TerminalSettingsEditor/EditColorScheme.xaml | 4 ++-- 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/cascadia/TerminalSettingsEditor/ColorSchemeViewModel.cpp b/src/cascadia/TerminalSettingsEditor/ColorSchemeViewModel.cpp index 4bd995b6c4a..04e9433bbba 100644 --- a/src/cascadia/TerminalSettingsEditor/ColorSchemeViewModel.cpp +++ b/src/cascadia/TerminalSettingsEditor/ColorSchemeViewModel.cpp @@ -178,6 +178,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Name(TableColorNames[index]); Tag(winrt::box_value(index)); Color(color); + + PropertyChanged({ get_weak(), &ColorTableEntry::_PropertyChangedHandler }); } ColorTableEntry::ColorTableEntry(std::wstring_view tag, Windows::UI::Color color) @@ -185,5 +187,17 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Name(LocalizedNameForEnumName(L"ColorScheme_", tag, L"Text")); Tag(winrt::box_value(tag)); Color(color); + + PropertyChanged({ get_weak(), &ColorTableEntry::_PropertyChangedHandler }); + } + + void ColorTableEntry::_PropertyChangedHandler(const IInspectable& /*sender*/, const PropertyChangedEventArgs& args) + { + const auto propertyName{ args.PropertyName() }; + if (propertyName == L"Color" || propertyName == L"Name") + { + _PropertyChangedHandlers(*this, PropertyChangedEventArgs{ L"AccessibleName" }); + } } + } diff --git a/src/cascadia/TerminalSettingsEditor/ColorSchemeViewModel.h b/src/cascadia/TerminalSettingsEditor/ColorSchemeViewModel.h index 539b0636c3c..7c45638e709 100644 --- a/src/cascadia/TerminalSettingsEditor/ColorSchemeViewModel.h +++ b/src/cascadia/TerminalSettingsEditor/ColorSchemeViewModel.h @@ -63,6 +63,11 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation ColorTableEntry(uint8_t index, Windows::UI::Color color); ColorTableEntry(std::wstring_view tag, Windows::UI::Color color); + hstring AccessibleName() const + { + return hstring{ fmt::format(FMT_COMPILE(L"{} RGB({}, {}, {})"), _Name, _Color.R, _Color.G, _Color.B) }; + } + WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); WINRT_OBSERVABLE_PROPERTY(Windows::UI::Color, Color, _PropertyChangedHandlers); WINRT_OBSERVABLE_PROPERTY(winrt::hstring, Name, _PropertyChangedHandlers); @@ -70,5 +75,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation private: Windows::UI::Color _color; + + void _PropertyChangedHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args); }; }; diff --git a/src/cascadia/TerminalSettingsEditor/ColorSchemeViewModel.idl b/src/cascadia/TerminalSettingsEditor/ColorSchemeViewModel.idl index 3247ef125e3..b041c2ab93f 100644 --- a/src/cascadia/TerminalSettingsEditor/ColorSchemeViewModel.idl +++ b/src/cascadia/TerminalSettingsEditor/ColorSchemeViewModel.idl @@ -34,5 +34,6 @@ namespace Microsoft.Terminal.Settings.Editor String Name { get; }; IInspectable Tag; Windows.UI.Color Color; + String AccessibleName { get; }; } } diff --git a/src/cascadia/TerminalSettingsEditor/EditColorScheme.xaml b/src/cascadia/TerminalSettingsEditor/EditColorScheme.xaml index 8e68567bb82..c98f5c8d6c4 100644 --- a/src/cascadia/TerminalSettingsEditor/EditColorScheme.xaml +++ b/src/cascadia/TerminalSettingsEditor/EditColorScheme.xaml @@ -55,10 +55,10 @@ -