From e006f75f6c175542760ee4d338ee894b82acff5f Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Fri, 23 Aug 2024 10:13:50 -0500 Subject: [PATCH 01/14] During Alt+Numpad composition, stash keys in case we bail out (#17774) We were erroneously eating Alt followed by VK_ADD. This change makes sure we cache key presses and releases that happen once a numpad composition is active so that we can send them when you release Alt. Right now, we only send them when you release Alt after composing Alt and VK_ADD (entering hex mode) and only if you haven't inserted an actual hex numpad code. This does mean that `Alt VK_ADD 0 0 H I` will result in an input of "+hi". That... seems like a small price to pay for Alt VK_ADD working again. Closes #17762 --- src/cascadia/TerminalControl/TermControl.cpp | 90 ++++++++++++-------- src/cascadia/TerminalControl/TermControl.h | 8 ++ 2 files changed, 62 insertions(+), 36 deletions(-) diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 8f8f9f0df6..3e447d5784 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -1547,11 +1547,24 @@ namespace winrt::Microsoft::Terminal::Control::implementation auto encoding = s.encoding; wchar_t buf[4]{}; size_t buf_len = 0; + bool handled = true; if (encoding == AltNumpadEncoding::Unicode) { // UTF-32 -> UTF-16 - if (s.accumulator <= 0xffff) + if (s.accumulator == 0) + { + // If the user pressed Alt + VK_ADD, then released Alt, they probably didn't intend to insert a numpad character at all. + // Send any accumulated key events instead. + for (auto&& e : _altNumpadState.cachedKeyEvents) + { + handled = handled && _TrySendKeyEvent(e.vkey, e.scanCode, e.modifiers, e.keyDown); + } + // Send the alt keyup we are currently processing + handled = handled && _TrySendKeyEvent(vkey, scanCode, modifiers, keyDown); + // do not accumulate into the buffer + } + else if (s.accumulator <= 0xffff) { buf[buf_len++] = static_cast(s.accumulator); } @@ -1595,7 +1608,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation } s = {}; - return true; + return handled; } // As a continuation of the above, this handles the key-down case. if (modifiers.IsAltPressed()) @@ -1609,53 +1622,58 @@ namespace winrt::Microsoft::Terminal::Control::implementation SCROLLLOCK_ON | CAPSLOCK_ON; - if (keyDown && (modifiers.Value() & ~permittedModifiers) == 0) + if ((modifiers.Value() & ~permittedModifiers) == 0) { auto& s = _altNumpadState; - if (vkey == VK_ADD) + if (keyDown) { - // Alt '+' is used to input Unicode code points. - // Every time you press + it resets the entire state - // in the original OS implementation as well. - s.encoding = AltNumpadEncoding::Unicode; - s.accumulator = 0; - s.active = true; - } - else if (vkey == VK_NUMPAD0 && s.encoding == AltNumpadEncoding::OEM && s.accumulator == 0) - { - // Alt '0' is used to input ANSI code points. - // Otherwise, they're OEM codepoints. - s.encoding = AltNumpadEncoding::ANSI; - s.active = true; - } - else - { - // Otherwise, append the pressed key to the accumulator. - const uint32_t base = s.encoding == AltNumpadEncoding::Unicode ? 16 : 10; - uint32_t add = 0xffffff; - - if (vkey >= VK_NUMPAD0 && vkey <= VK_NUMPAD9) + if (vkey == VK_ADD) { - add = vkey - VK_NUMPAD0; - } - else if (vkey >= 'A' && vkey <= 'F') - { - add = vkey - 'A' + 10; - } - - // Pressing Alt + should not activate the Alt+Numpad input, however. - if (add < base) - { - s.accumulator = std::min(s.accumulator * base + add, 0x10FFFFu); + // Alt '+' is used to input Unicode code points. + // Every time you press + it resets the entire state + // in the original OS implementation as well. + s.encoding = AltNumpadEncoding::Unicode; + s.accumulator = 0; s.active = true; } + else if (vkey == VK_NUMPAD0 && s.encoding == AltNumpadEncoding::OEM && s.accumulator == 0) + { + // Alt '0' is used to input ANSI code points. + // Otherwise, they're OEM codepoints. + s.encoding = AltNumpadEncoding::ANSI; + s.active = true; + } + else + { + // Otherwise, append the pressed key to the accumulator. + const uint32_t base = s.encoding == AltNumpadEncoding::Unicode ? 16 : 10; + uint32_t add = 0xffffff; + + if (vkey >= VK_NUMPAD0 && vkey <= VK_NUMPAD9) + { + add = vkey - VK_NUMPAD0; + } + else if (vkey >= 'A' && vkey <= 'F') + { + add = vkey - 'A' + 10; + } + + // Pressing Alt + should not activate the Alt+Numpad input, however. + if (add < base) + { + s.accumulator = std::min(s.accumulator * base + add, 0x10FFFFu); + s.active = true; + } + } } // If someone pressed Alt + , we'll skip the early // return and send the Alt key combination as per usual. if (s.active) { + // Cache it in case we have to emit it after alt is released + _altNumpadState.cachedKeyEvents.emplace_back(vkey, scanCode, modifiers, keyDown); return true; } diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index f60a72ea79..ab5b0a1ae7 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -256,12 +256,20 @@ namespace winrt::Microsoft::Terminal::Control::implementation }; struct AltNumpadState { + struct CachedKey + { + WORD vkey; + WORD scanCode; + ::Microsoft::Terminal::Core::ControlKeyStates modifiers; + bool keyDown; + }; AltNumpadEncoding encoding = AltNumpadEncoding::OEM; uint32_t accumulator = 0; // Checking for accumulator != 0 to see if we have an ongoing Alt+Numpad composition is insufficient. // The state can be active, while the accumulator is 0, if the user pressed Alt+Numpad0 which enabled // the OEM encoding mode (= active), and then pressed Numpad0 again (= accumulator is still 0). bool active = false; + til::small_vector cachedKeyEvents; }; AltNumpadState _altNumpadState; From d1a1f9836ebbcdffc76d2f13384ee2fb7c060c9d Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Fri, 23 Aug 2024 11:43:39 -0500 Subject: [PATCH 02/14] Restore off-the-top behavior for VT mouse mode (#17779) PR #10642 and #11290 introduced an adjustment for the cursor position used to generate VT mouse mode events. One of the decisions made in those PRs was to only send coordinates where Y was >= 0, so if you were off the top of the screen you wouldn't get any events. However, terminal emulators are expected to send _clamped_ events when the mouse is off the screen. This decision broke clamping Y to 0 when the mouse was above the screen. The other decision was to only adjust the Y coordinate if the core's `ScrollOffset` was greater than 0. It turns out that `ScrollOffset` _is 0_ when you are scrolled all the way back in teh buffer. With this check, we would clamp coordinates properly _until the top line of the scrollback was visible_, at which point we would send those coordinates over directly. This resulted in the same weird behavior as observed in #10190. I've fixed both of those things. Core is expected to receive negative coordinates and clamp them to the viewport. ScrollOffset should never be below 0, as it refers to the top visible buffer line. In addition to that, #17744 uncovered that we were allowing autoscrolling to happen even when VT mouse events were being generated. I added a way for `ControlInteractivity` to halt further event processing. It's crude. Refs #10190 Closes #17744 --- .../TerminalControl/ControlInteractivity.cpp | 17 +++++++++-------- .../TerminalControl/ControlInteractivity.h | 2 +- .../TerminalControl/ControlInteractivity.idl | 12 ++++++------ src/cascadia/TerminalControl/TermControl.cpp | 14 +++++++------- .../ControlInteractivityTests.cpp | 5 ++--- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlInteractivity.cpp b/src/cascadia/TerminalControl/ControlInteractivity.cpp index 7b59babd2e..f18851c2fb 100644 --- a/src/cascadia/TerminalControl/ControlInteractivity.cpp +++ b/src/cascadia/TerminalControl/ControlInteractivity.cpp @@ -329,7 +329,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation _touchAnchor = contactPoint; } - void ControlInteractivity::PointerMoved(Control::MouseButtonState buttonState, + bool ControlInteractivity::PointerMoved(Control::MouseButtonState buttonState, const unsigned int pointerUpdateKind, const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const bool focused, @@ -337,11 +337,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation const bool pointerPressedInBounds) { const auto terminalPosition = _getTerminalPosition(til::point{ pixelPosition }); + // Returning true from this function indicates that the caller should do no further processing of this movement. + bool handledCompletely = false; // Short-circuit isReadOnly check to avoid warning dialog if (focused && !_core->IsInReadOnlyMode() && _canSendVTMouseInput(modifiers)) { _sendMouseEventHelper(terminalPosition, pointerUpdateKind, modifiers, 0, buttonState); + handledCompletely = true; } // GH#4603 - don't modify the selection if the pointer press didn't // actually start _in_ the control bounds. Case in point - someone drags @@ -378,6 +381,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation } _core->SetHoveredCell(terminalPosition.to_core_point()); + return handledCompletely; } void ControlInteractivity::TouchMoved(const Core::Point newTouchPoint, @@ -682,13 +686,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation const SHORT wheelDelta, Control::MouseButtonState buttonState) { - const auto adjustment = _core->ScrollOffset() > 0 ? _core->BufferHeight() - _core->ScrollOffset() - _core->ViewHeight() : 0; - // If the click happened outside the active region, just don't send any mouse event - if (const auto adjustedY = terminalPosition.y - adjustment; adjustedY >= 0) - { - return _core->SendMouseEvent({ terminalPosition.x, adjustedY }, pointerUpdateKind, modifiers, wheelDelta, toInternalMouseState(buttonState)); - } - return false; + const auto adjustment = _core->BufferHeight() - _core->ScrollOffset() - _core->ViewHeight(); + // If the click happened outside the active region, core should get a chance to filter it out or clamp it. + const auto adjustedY = terminalPosition.y - adjustment; + return _core->SendMouseEvent({ terminalPosition.x, adjustedY }, pointerUpdateKind, modifiers, wheelDelta, toInternalMouseState(buttonState)); } // Method Description: diff --git a/src/cascadia/TerminalControl/ControlInteractivity.h b/src/cascadia/TerminalControl/ControlInteractivity.h index 38789827ec..22eaa95c39 100644 --- a/src/cascadia/TerminalControl/ControlInteractivity.h +++ b/src/cascadia/TerminalControl/ControlInteractivity.h @@ -58,7 +58,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation const Core::Point pixelPosition); void TouchPressed(const Core::Point contactPoint); - void PointerMoved(Control::MouseButtonState buttonState, + bool PointerMoved(Control::MouseButtonState buttonState, const unsigned int pointerUpdateKind, const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const bool focused, diff --git a/src/cascadia/TerminalControl/ControlInteractivity.idl b/src/cascadia/TerminalControl/ControlInteractivity.idl index d1ca9720dc..926c1e3b0a 100644 --- a/src/cascadia/TerminalControl/ControlInteractivity.idl +++ b/src/cascadia/TerminalControl/ControlInteractivity.idl @@ -43,12 +43,12 @@ namespace Microsoft.Terminal.Control Microsoft.Terminal.Core.Point pixelPosition); void TouchPressed(Microsoft.Terminal.Core.Point contactPoint); - void PointerMoved(MouseButtonState buttonState, - UInt32 pointerUpdateKind, - Microsoft.Terminal.Core.ControlKeyStates modifiers, - Boolean focused, - Microsoft.Terminal.Core.Point pixelPosition, - Boolean pointerPressedInBounds); + Boolean PointerMoved(MouseButtonState buttonState, + UInt32 pointerUpdateKind, + Microsoft.Terminal.Core.ControlKeyStates modifiers, + Boolean focused, + Microsoft.Terminal.Core.Point pixelPosition, + Boolean pointerPressedInBounds); void TouchMoved(Microsoft.Terminal.Core.Point newTouchPoint, Boolean focused); diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 3e447d5784..8c2a37e7d2 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -1936,18 +1936,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation if (type == Windows::Devices::Input::PointerDeviceType::Mouse || type == Windows::Devices::Input::PointerDeviceType::Pen) { - _interactivity.PointerMoved(TermControl::GetPressedMouseButtons(point), - TermControl::GetPointerUpdateKind(point), - ControlKeyStates(args.KeyModifiers()), - _focused, - pixelPosition.to_core_point(), - _pointerPressedInBounds); + auto suppressFurtherHandling = _interactivity.PointerMoved(TermControl::GetPressedMouseButtons(point), + TermControl::GetPointerUpdateKind(point), + ControlKeyStates(args.KeyModifiers()), + _focused, + pixelPosition.to_core_point(), + _pointerPressedInBounds); // GH#9109 - Only start an auto-scroll when the drag actually // started within our bounds. Otherwise, someone could start a drag // outside the terminal control, drag into the padding, and trick us // into starting to scroll. - if (_focused && _pointerPressedInBounds && point.Properties().IsLeftButtonPressed()) + if (!suppressFurtherHandling && _focused && _pointerPressedInBounds && point.Properties().IsLeftButtonPressed()) { // We want to find the distance relative to the bounds of the // SwapChainPanel, not the entire control. If they drag out of diff --git a/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp b/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp index a5d443b915..49fcd6d21e 100644 --- a/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp +++ b/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp @@ -998,16 +998,15 @@ namespace ControlUnitTests VERIFY_IS_GREATER_THAN(core->ScrollOffset(), 0); // Viewport is now above the mutable viewport, so the mouse event - // straight up won't be sent to the terminal. + // will be clamped to the top line. - expectedOutput.push_back(L"sentinel"); // Clearly, it won't be this string + expectedOutput.push_back(L"\x1b[M &!"); // 5, 1 interactivity->PointerPressed(leftMouseDown, WM_LBUTTONDOWN, //pointerUpdateKind 0, // timestamp modifiers, cursorPosition0.to_core_point()); // Flush it out. - conn->WriteInput(winrt_wstring_to_array_view(L"sentinel")); VERIFY_ARE_EQUAL(0u, expectedOutput.size(), L"Validate we drained all the expected output"); // This is the part as mentioned in GH#12719 From 1de142b4b16b8e048b0bb82d8a6cd10c2c18b8ea Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Fri, 23 Aug 2024 12:12:00 -0500 Subject: [PATCH 03/14] Dismiss the snippet preview when there is no suggestions (#17777) Pretty obvious in retrospect. If there's no results, then we need to preview _nothing_ to make sure that we clear out any old previews. Closes #17773 Additionally, while I was here: I realized why it seems like the selected item is so wacky when you first open the sxnui: * on launch we're not scrolling to the bottom item (which makes it awkward in bottom up mode) * when we filter the list, we're maintaining the selection _index_, not the selection _item_. Alas, that second part is... shockingly bodgy. --- .../TerminalApp/SuggestionsControl.cpp | 42 +++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/src/cascadia/TerminalApp/SuggestionsControl.cpp b/src/cascadia/TerminalApp/SuggestionsControl.cpp index e6f482ac19..80206ea5dd 100644 --- a/src/cascadia/TerminalApp/SuggestionsControl.cpp +++ b/src/cascadia/TerminalApp/SuggestionsControl.cpp @@ -269,6 +269,8 @@ namespace winrt::TerminalApp::implementation const auto selectedCommand = _filteredActionsView().SelectedItem(); const auto filteredCommand{ selectedCommand.try_as() }; + _filteredActionsView().ScrollIntoView(selectedCommand); + PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"SelectedItem" }); // Make sure to not send the preview if we're collapsed. This can @@ -795,14 +797,46 @@ namespace winrt::TerminalApp::implementation // here will ensure that we can check this case appropriately. _lastFilterTextWasEmpty = _searchBox().Text().empty(); - const auto lastSelectedIndex = _filteredActionsView().SelectedIndex(); + const auto lastSelectedIndex = std::max(0, _filteredActionsView().SelectedIndex()); // SelectedIndex will return -1 for "nothing" _updateFilteredActions(); - // In the command line mode we want the user to explicitly select the command - _filteredActionsView().SelectedIndex(std::min(lastSelectedIndex, _filteredActionsView().Items().Size() - 1)); + if (const auto newSelectedIndex = _filteredActionsView().SelectedIndex(); + newSelectedIndex == -1) + { + // Make sure something stays selected + _scrollToIndex(lastSelectedIndex); + } + else + { + // BODGY: Calling ScrollIntoView on a ListView doesn't always work + // immediately after a change to the items. See: + // https://stackoverflow.com/questions/16942580/why-doesnt-listview-scrollintoview-ever-work + // The SelectionChanged thing we do (in _selectedCommandChanged), + // but because we're also not changing the actual selected item when + // the size of the list grows (it _stays_ selected, so it never + // _changes_), we never get a SelectionChanged. + // + // To mitigate, only in the case of totally clearing out the filter + // (like hitting `esc`), we want to briefly select the 0th item, + // then immediately select the one we want to make visible. That + // will make sure we get a SelectionChanged when the ListView is + // ready, and we can use that to scroll to the right item. + // + // If we do this on _every_ change, then the preview text flickers + // between the 0th item and the correct one. + if (_lastFilterTextWasEmpty) + { + _filteredActionsView().SelectedIndex(0); + } + _scrollToIndex(newSelectedIndex); + } const auto currentNeedleHasResults{ _filteredActions.Size() > 0 }; + if (!currentNeedleHasResults) + { + PreviewAction.raise(*this, nullptr); + } _noMatchesText().Visibility(currentNeedleHasResults ? Visibility::Collapsed : Visibility::Visible); if (auto automationPeer{ Automation::Peers::FrameworkElementAutomationPeer::FromElement(_searchBox()) }) { @@ -1203,7 +1237,7 @@ namespace winrt::TerminalApp::implementation if (_direction == TerminalApp::SuggestionsDirection::BottomUp) { const auto last = _filteredActionsView().Items().Size() - 1; - _filteredActionsView().SelectedIndex(last); + _scrollToIndex(last); } // Move the cursor to the very last position, so it starts immediately // after the text. This is apparently done by starting a 0-wide From dbbc581154eb4e6f0086963eecf4983d7196caae Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Fri, 23 Aug 2024 10:20:29 -0700 Subject: [PATCH 04/14] Use WinGet API to improve Quick Fix results (#17614) ## Summary of the Pull Request Improves Quick Fix's suggestions to use WinGet API and actually query winget for packages based on the missing command. To interact with the WinGet API, we need the `Microsoft.WindowsPackageManager.ComInterop` NuGet package. `Microsoft.WindowsPackageManager.ComInterop.Additional.targets` is used to copy over the winmd into CascadiaPackage. The build variable `TerminalWinGetInterop` is used to import the package properly. `WindowsPackageManagerFactory` is used as a centralized way to generate the winget objects. Long-term, we may need to do manual activation for elevated sessions, which this class can easily be extended to support. In the meantime, we'll just use the normal `winrt::create_instance` on all sessions. In `TerminalPage`, we conduct the search asynchronously when a missing command was found. Search results are limited to 20 packages. We try to retrieve packages with the following filters set, then fallback into the next step: 1. `PackageMatchField::Command`, `PackageFieldMatchOption::StartsWithCaseInsensitive` 2. `PackageMatchField::Name`, `PackageFieldMatchOption::ContainsCaseInsensitive` 3. `PackageMatchField::Moniker`, `PackageFieldMatchOption::ContainsCaseInsensitive` This aligns with the Microsoft.WinGet.CommandNotFound PowerShell module ([link to relevant code](https://github.com/microsoft/winget-command-not-found/blob/9bc83617b94f6dc88e1fc9599e1c859bc3adf96f/src/WinGetCommandNotFoundFeedbackPredictor.cs#L165-L202)). Closes #17378 Closes #17631 Support for elevated sessions tracked in #17677 ## References - https://github.com/microsoft/winget-cli/blob/master/src/Microsoft.Management.Deployment/PackageManager.idl: winget object documentation ## Validation Steps Performed - [X] unelevated sessions --> winget query performed and presented - [X] elevated sessions --> nothing happens (got rid of `winget install {}` suggestion) --- ...ckageManager.ComInterop.Additional.targets | 22 +++++ dep/nuget/packages.config | 1 + .../TerminalApp/SuggestionsControl.cpp | 14 ++++ .../TerminalApp/TerminalAppLib.vcxproj | 4 +- src/cascadia/TerminalApp/TerminalPage.cpp | 82 ++++++++++++++++++- src/cascadia/TerminalApp/TerminalPage.h | 11 ++- .../WindowsPackageManagerFactory.h | 61 ++++++++++++++ src/cascadia/TerminalControl/ControlCore.cpp | 6 +- src/cascadia/TerminalControl/ControlCore.h | 2 +- src/cascadia/TerminalControl/EventArgs.h | 6 +- src/cascadia/TerminalControl/EventArgs.idl | 1 + src/cascadia/TerminalControl/TermControl.cpp | 8 +- src/cascadia/TerminalControl/TermControl.h | 1 + src/cascadia/TerminalCore/Terminal.cpp | 2 +- src/cascadia/TerminalCore/Terminal.hpp | 4 +- src/cascadia/TerminalCore/TerminalApi.cpp | 3 +- .../WindowsTerminal/WindowsTerminal.vcxproj | 1 + src/common.nugetversions.props | 5 ++ src/common.nugetversions.targets | 2 + 19 files changed, 216 insertions(+), 20 deletions(-) create mode 100644 build/rules/Microsoft.WindowsPackageManager.ComInterop.Additional.targets create mode 100644 src/cascadia/TerminalApp/WindowsPackageManagerFactory.h diff --git a/build/rules/Microsoft.WindowsPackageManager.ComInterop.Additional.targets b/build/rules/Microsoft.WindowsPackageManager.ComInterop.Additional.targets new file mode 100644 index 0000000000..be2a691bb2 --- /dev/null +++ b/build/rules/Microsoft.WindowsPackageManager.ComInterop.Additional.targets @@ -0,0 +1,22 @@ + + + + + x86 + $(Platform) + + + + true + + + + + + CustomOutputGroupForPackaging + $(ProjectName) + Microsoft.Management.Deployment.winmd + + + + \ No newline at end of file diff --git a/dep/nuget/packages.config b/dep/nuget/packages.config index a02190c6bc..a641da7c51 100644 --- a/dep/nuget/packages.config +++ b/dep/nuget/packages.config @@ -10,6 +10,7 @@ + diff --git a/src/cascadia/TerminalApp/SuggestionsControl.cpp b/src/cascadia/TerminalApp/SuggestionsControl.cpp index 80206ea5dd..442b80243c 100644 --- a/src/cascadia/TerminalApp/SuggestionsControl.cpp +++ b/src/cascadia/TerminalApp/SuggestionsControl.cpp @@ -724,6 +724,20 @@ namespace winrt::TerminalApp::implementation // ever allow non-sendInput actions. DispatchCommandRequested.raise(*this, actionPaletteItem.Command()); + if (const auto& sendInputCmd = actionPaletteItem.Command().ActionAndArgs().Args().try_as()) + { + if (til::starts_with(sendInputCmd.Input(), L"winget")) + { + TraceLoggingWrite( + g_hTerminalAppProvider, + "QuickFixSuggestionUsed", + TraceLoggingDescription("Event emitted when a winget suggestion from is used"), + TraceLoggingValue("SuggestionsUI", "Source"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); + } + } + TraceLoggingWrite( g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider "SuggestionsControlDispatchedAction", diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj index 8549c32cd4..99ccf110ff 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj @@ -25,6 +25,7 @@ true true + true @@ -177,6 +178,7 @@ SuggestionsControl.xaml + @@ -361,7 +363,7 @@ - + TaskPaneContent.xaml Code diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 002886fd3b..aae32e336c 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -26,6 +26,7 @@ #include "LaunchPositionRequest.g.cpp" using namespace winrt; +using namespace winrt::Microsoft::Management::Deployment; using namespace winrt::Microsoft::Terminal::Control; using namespace winrt::Microsoft::Terminal::Settings::Model; using namespace winrt::Microsoft::Terminal::TerminalConnection; @@ -3001,18 +3002,82 @@ namespace winrt::TerminalApp::implementation ShowWindowChanged.raise(*this, args); } - winrt::fire_and_forget TerminalPage::_SearchMissingCommandHandler(const IInspectable /*sender*/, const Microsoft::Terminal::Control::SearchMissingCommandEventArgs args) + Windows::Foundation::IAsyncOperation> TerminalPage::_FindPackageAsync(hstring query) { - assert(!Dispatcher().HasThreadAccess()); + const PackageManager packageManager = WindowsPackageManagerFactory::CreatePackageManager(); + PackageCatalogReference catalogRef{ + packageManager.GetPredefinedPackageCatalog(PredefinedPackageCatalog::OpenWindowsCatalog) + }; + catalogRef.PackageCatalogBackgroundUpdateInterval(std::chrono::hours(24)); + ConnectResult connectResult{ nullptr }; + for (int retries = 0;;) + { + connectResult = catalogRef.Connect(); + if (connectResult.Status() == ConnectResultStatus::Ok) + { + break; + } + + if (++retries == 3) + { + co_return nullptr; + } + } + + PackageCatalog catalog = connectResult.PackageCatalog(); + // clang-format off + static constexpr std::array searches{ { + { .Field = PackageMatchField::Command, .MatchOption = PackageFieldMatchOption::StartsWithCaseInsensitive }, + { .Field = PackageMatchField::Name, .MatchOption = PackageFieldMatchOption::ContainsCaseInsensitive }, + { .Field = PackageMatchField::Moniker, .MatchOption = PackageFieldMatchOption::ContainsCaseInsensitive } } }; + // clang-format on + + PackageMatchFilter filter = WindowsPackageManagerFactory::CreatePackageMatchFilter(); + filter.Value(query); + + FindPackagesOptions options = WindowsPackageManagerFactory::CreateFindPackagesOptions(); + options.Filters().Append(filter); + options.ResultLimit(20); + + IVectorView pkgList; + for (const auto& search : searches) + { + filter.Field(search.Field); + filter.Option(search.MatchOption); + + const auto result = co_await catalog.FindPackagesAsync(options); + pkgList = result.Matches(); + if (pkgList.Size() > 0) + { + break; + } + } + co_return pkgList; + } + + Windows::Foundation::IAsyncAction TerminalPage::_SearchMissingCommandHandler(const IInspectable /*sender*/, const Microsoft::Terminal::Control::SearchMissingCommandEventArgs args) + { if (!Feature_QuickFix::IsEnabled()) { co_return; } + co_await winrt::resume_background(); + + // no packages were found, nothing to suggest + const auto pkgList = co_await _FindPackageAsync(args.MissingCommand()); + if (!pkgList || pkgList.Size() == 0) + { + co_return; + } std::vector suggestions; - suggestions.reserve(1); - suggestions.emplace_back(fmt::format(FMT_COMPILE(L"winget install {}"), args.MissingCommand())); + suggestions.reserve(pkgList.Size()); + for (const auto& pkg : pkgList) + { + // --id and --source ensure we don't collide with another package catalog + suggestions.emplace_back(fmt::format(FMT_COMPILE(L"winget install --id {} -s winget"), pkg.CatalogPackage().Id())); + } co_await wil::resume_foreground(Dispatcher()); @@ -5006,6 +5071,14 @@ namespace winrt::TerminalApp::implementation { ctrl.ClearQuickFix(); } + + TraceLoggingWrite( + g_hTerminalAppProvider, + "QuickFixSuggestionUsed", + TraceLoggingDescription("Event emitted when a winget suggestion from is used"), + TraceLoggingValue("QuickFixMenu", "Source"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); } }; }; @@ -5027,6 +5100,7 @@ namespace winrt::TerminalApp::implementation item.Text(qf); item.Click(makeCallback(qf)); + ToolTipService::SetToolTip(item, box_value(qf)); menu.Items().Append(item); } } diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 65bfa9bad7..449ffa1fff 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -13,6 +13,8 @@ #include "LaunchPositionRequest.g.h" #include "Toast.h" +#include "WindowsPackageManagerFactory.h" + #define DECLARE_ACTION_HANDLER(action) void _Handle##action(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args); namespace TerminalAppLocalTests @@ -87,6 +89,12 @@ namespace winrt::TerminalApp::implementation til::property Position; }; + struct WinGetSearchParams + { + winrt::Microsoft::Management::Deployment::PackageMatchField Field; + winrt::Microsoft::Management::Deployment::PackageFieldMatchOption MatchOption; + }; + struct TerminalPage : TerminalPageT { public: @@ -530,7 +538,8 @@ namespace winrt::TerminalApp::implementation void _OpenSuggestions(const Microsoft::Terminal::Control::TermControl& sender, Windows::Foundation::Collections::IVector commandsCollection, winrt::TerminalApp::SuggestionsMode mode, winrt::hstring filterText); void _ShowWindowChangedHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::ShowWindowArgs args); - winrt::fire_and_forget _SearchMissingCommandHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::SearchMissingCommandEventArgs args); + Windows::Foundation::IAsyncAction _SearchMissingCommandHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::SearchMissingCommandEventArgs args); + Windows::Foundation::IAsyncOperation> _FindPackageAsync(hstring query); winrt::fire_and_forget _windowPropertyChanged(const IInspectable& sender, const winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs& args); diff --git a/src/cascadia/TerminalApp/WindowsPackageManagerFactory.h b/src/cascadia/TerminalApp/WindowsPackageManagerFactory.h new file mode 100644 index 0000000000..1032eb2076 --- /dev/null +++ b/src/cascadia/TerminalApp/WindowsPackageManagerFactory.h @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// Module Name: +// - WindowsPackageManagerFactory.h +// +// Abstract: +// - This factory is designed to create production-level instances of WinGet objects. +// Elevated sessions require manual activation of WinGet objects and are not currently supported, +// while non-elevated sessions can use the standard WinRT activation system. +// Author: +// - Carlos Zamora (carlos-zamora) 23-Jul-2024 + +#pragma once + +#include + +namespace winrt::TerminalApp::implementation +{ + struct WindowsPackageManagerFactory + { + public: + static winrt::Microsoft::Management::Deployment::PackageManager CreatePackageManager() + { + return winrt::create_instance(PackageManagerGuid, CLSCTX_ALL); + } + + static winrt::Microsoft::Management::Deployment::FindPackagesOptions CreateFindPackagesOptions() + { + return winrt::create_instance(FindPackageOptionsGuid, CLSCTX_ALL); + } + + static winrt::Microsoft::Management::Deployment::CreateCompositePackageCatalogOptions CreateCreateCompositePackageCatalogOptions() + { + return winrt::create_instance(CreateCompositePackageCatalogOptionsGuid, CLSCTX_ALL); + } + + static winrt::Microsoft::Management::Deployment::InstallOptions CreateInstallOptions() + { + return winrt::create_instance(InstallOptionsGuid, CLSCTX_ALL); + } + + static winrt::Microsoft::Management::Deployment::UninstallOptions CreateUninstallOptions() + { + return winrt::create_instance(UninstallOptionsGuid, CLSCTX_ALL); + } + + static winrt::Microsoft::Management::Deployment::PackageMatchFilter CreatePackageMatchFilter() + { + return winrt::create_instance(PackageMatchFilterGuid, CLSCTX_ALL); + } + + private: + static constexpr winrt::guid PackageManagerGuid{ 0xC53A4F16, 0x787E, 0x42A4, { 0xB3, 0x04, 0x29, 0xEF, 0xFB, 0x4B, 0xF5, 0x97 } }; + static constexpr winrt::guid FindPackageOptionsGuid{ 0x572DED96, 0x9C60, 0x4526, { 0x8F, 0x92, 0xEE, 0x7D, 0x91, 0xD3, 0x8C, 0x1A } }; + static constexpr winrt::guid CreateCompositePackageCatalogOptionsGuid{ 0x526534B8, 0x7E46, 0x47C8, { 0x84, 0x16, 0xB1, 0x68, 0x5C, 0x32, 0x7D, 0x37 } }; + static constexpr winrt::guid InstallOptionsGuid{ 0x1095F097, 0xEB96, 0x453B, { 0xB4, 0xE6, 0x16, 0x13, 0x63, 0x7F, 0x3B, 0x14 } }; + static constexpr winrt::guid UninstallOptionsGuid{ 0xE1D9A11E, 0x9F85, 0x4D87, { 0x9C, 0x17, 0x2B, 0x93, 0x14, 0x3A, 0xDB, 0x8D } }; + static constexpr winrt::guid PackageMatchFilterGuid{ 0xD02C9DAF, 0x99DC, 0x429C, { 0xB5, 0x03, 0x4E, 0x50, 0x4E, 0x4A, 0xB0, 0x00 } }; + }; +} diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index d8d975f667..ac306b69c6 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -128,7 +128,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation auto pfnCompletionsChanged = [=](auto&& menuJson, auto&& replaceLength) { _terminalCompletionsChanged(menuJson, replaceLength); }; _terminal->CompletionsChangedCallback(pfnCompletionsChanged); - auto pfnSearchMissingCommand = [this](auto&& PH1) { _terminalSearchMissingCommand(std::forward(PH1)); }; + auto pfnSearchMissingCommand = [this](auto&& PH1, auto&& PH2) { _terminalSearchMissingCommand(std::forward(PH1), std::forward(PH2)); }; _terminal->SetSearchMissingCommandCallback(pfnSearchMissingCommand); auto pfnClearQuickFix = [this] { ClearQuickFix(); }; @@ -1629,9 +1629,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation _midiAudio.PlayNote(reinterpret_cast(_owningHwnd), noteNumber, velocity, std::chrono::duration_cast(duration)); } - void ControlCore::_terminalSearchMissingCommand(std::wstring_view missingCommand) + void ControlCore::_terminalSearchMissingCommand(std::wstring_view missingCommand, const til::CoordType& bufferRow) { - SearchMissingCommand.raise(*this, make(hstring{ missingCommand })); + SearchMissingCommand.raise(*this, make(hstring{ missingCommand }, bufferRow)); } void ControlCore::ClearQuickFix() diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 938d048243..32fa4c0253 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -389,7 +389,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation void _terminalPlayMidiNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration); - void _terminalSearchMissingCommand(std::wstring_view missingCommand); + void _terminalSearchMissingCommand(std::wstring_view missingCommand, const til::CoordType& bufferRow); winrt::fire_and_forget _terminalCompletionsChanged(std::wstring_view menuJson, unsigned int replaceLength); diff --git a/src/cascadia/TerminalControl/EventArgs.h b/src/cascadia/TerminalControl/EventArgs.h index 6770da9f62..0e98cefd67 100644 --- a/src/cascadia/TerminalControl/EventArgs.h +++ b/src/cascadia/TerminalControl/EventArgs.h @@ -216,10 +216,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation struct SearchMissingCommandEventArgs : public SearchMissingCommandEventArgsT { public: - SearchMissingCommandEventArgs(const winrt::hstring& missingCommand) : - MissingCommand(missingCommand) {} + SearchMissingCommandEventArgs(const winrt::hstring& missingCommand, const til::CoordType& bufferRow) : + MissingCommand(missingCommand), + BufferRow(bufferRow) {} til::property MissingCommand; + til::property BufferRow; }; } diff --git a/src/cascadia/TerminalControl/EventArgs.idl b/src/cascadia/TerminalControl/EventArgs.idl index 6cad9ccff7..c3255bc667 100644 --- a/src/cascadia/TerminalControl/EventArgs.idl +++ b/src/cascadia/TerminalControl/EventArgs.idl @@ -130,5 +130,6 @@ namespace Microsoft.Terminal.Control runtimeclass SearchMissingCommandEventArgs { String MissingCommand { get; }; + Int32 BufferRow { get; }; } } diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 8c2a37e7d2..aa38206d10 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -4059,18 +4059,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation const auto rd = get_self(_core)->GetRenderData(); rd->LockConsole(); const auto viewportBufferPosition = rd->GetViewport(); - const auto cursorBufferPosition = rd->GetCursorPosition(); rd->UnlockConsole(); - if (cursorBufferPosition.y < viewportBufferPosition.Top() || cursorBufferPosition.y > viewportBufferPosition.BottomInclusive()) + if (_quickFixBufferPos < viewportBufferPosition.Top() || _quickFixBufferPos > viewportBufferPosition.BottomInclusive()) { quickFixBtn.Visibility(Visibility::Collapsed); return; } // draw the button in the gutter - const auto& cursorPosInDips = CursorPositionInDips(); + const auto& quickFixBtnPosInDips = _toPosInDips({ 0, _quickFixBufferPos }); Controls::Canvas::SetLeft(quickFixBtn, -termPadding.Left); - Controls::Canvas::SetTop(quickFixBtn, cursorPosInDips.Y - termPadding.Top); + Controls::Canvas::SetTop(quickFixBtn, quickFixBtnPosInDips.y - termPadding.Top); quickFixBtn.Visibility(Visibility::Visible); if (auto automationPeer{ FrameworkElementAutomationPeer::FromElement(*this) }) @@ -4085,6 +4084,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation void TermControl::_bubbleSearchMissingCommand(const IInspectable& /*sender*/, const Control::SearchMissingCommandEventArgs& args) { + _quickFixBufferPos = args.BufferRow(); SearchMissingCommand.raise(*this, args); } diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index ab5b0a1ae7..c4fbdb40d9 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -278,6 +278,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation bool _initializedTerminal{ false }; bool _quickFixButtonCollapsible{ false }; bool _quickFixesAvailable{ false }; + til::CoordType _quickFixBufferPos{}; std::shared_ptr _playWarningBell; diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index b7e343998d..8a82d4d91a 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -1233,7 +1233,7 @@ void Microsoft::Terminal::Core::Terminal::CompletionsChangedCallback(std::functi _pfnCompletionsChanged.swap(pfn); } -void Microsoft::Terminal::Core::Terminal::SetSearchMissingCommandCallback(std::function pfn) noexcept +void Microsoft::Terminal::Core::Terminal::SetSearchMissingCommandCallback(std::function pfn) noexcept { _pfnSearchMissingCommand.swap(pfn); } diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index cf8dea0a16..34d92213d8 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -230,7 +230,7 @@ public: void SetShowWindowCallback(std::function pfn) noexcept; void SetPlayMidiNoteCallback(std::function pfn) noexcept; void CompletionsChangedCallback(std::function pfn) noexcept; - void SetSearchMissingCommandCallback(std::function pfn) noexcept; + void SetSearchMissingCommandCallback(std::function pfn) noexcept; void SetClearQuickFixCallback(std::function pfn) noexcept; void SetSearchHighlights(const std::vector& highlights) noexcept; void SetSearchHighlightFocused(const size_t focusedIdx, til::CoordType searchScrollOffset); @@ -342,7 +342,7 @@ private: std::function _pfnShowWindowChanged; std::function _pfnPlayMidiNote; std::function _pfnCompletionsChanged; - std::function _pfnSearchMissingCommand; + std::function _pfnSearchMissingCommand; std::function _pfnClearQuickFix; RenderSettings _renderSettings; diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index 26daf24e5d..9114d7866e 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -338,7 +338,8 @@ void Terminal::SearchMissingCommand(const std::wstring_view command) { if (_pfnSearchMissingCommand) { - _pfnSearchMissingCommand(command); + const auto bufferRow = GetCursorPosition().y; + _pfnSearchMissingCommand(command, bufferRow); } } diff --git a/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj b/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj index 68a52a67fd..21fea0cf5f 100644 --- a/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj +++ b/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj @@ -20,6 +20,7 @@ true true true + true diff --git a/src/common.nugetversions.props b/src/common.nugetversions.props index 7989c7ce0e..6e53f2cde2 100644 --- a/src/common.nugetversions.props +++ b/src/common.nugetversions.props @@ -39,4 +39,9 @@ + + + $(MSBuildThisFileDirectory)..\packages\Microsoft.WindowsPackageManager.ComInterop.1.8.1911\ + + diff --git a/src/common.nugetversions.targets b/src/common.nugetversions.targets index 375bfd2c39..837c575fd8 100644 --- a/src/common.nugetversions.targets +++ b/src/common.nugetversions.targets @@ -64,6 +64,8 @@ + + From 9ec8584f8604368a444e57898eb1f7798444a609 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Fri, 23 Aug 2024 12:48:38 -0500 Subject: [PATCH 05/14] Don't emit raw escape sequences in the test log output (#17790) I used this very bad regex to try and find all the `\x1b`'s in ScreenBufferTests that weren't in a ProcessString call: ``` (? Date: Fri, 23 Aug 2024 12:49:11 -0500 Subject: [PATCH 06/14] Prevent a crash when holding enter when creating a tab (#17788) I guess I didn't realize that `SendCharEvent` could get called before `Create`. In that scenario, `enter` would hit the automark codepath (due to #17761), then crash because there was no text buffer. Pretty easy to prevent. Closes #17776 --- src/cascadia/TerminalCore/Terminal.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index 8a82d4d91a..165af0e3b0 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -711,7 +711,7 @@ TerminalInput::OutputType Terminal::SendCharEvent(const wchar_t ch, const WORD s // * AND we're not in the alt buffer // // Then treat this line like it's a prompt mark. - if (_autoMarkPrompts) + if (_autoMarkPrompts && _mainBuffer && !_inAltBuffer()) { // We need to be a little tricky here, to try and support folks that are // auto-marking prompts, but don't necessarily have the rest of shell @@ -725,10 +725,10 @@ TerminalInput::OutputType Terminal::SendCharEvent(const wchar_t ch, const WORD s // // (TextBuffer::_createPromptMarkIfNeeded does that work for us) - const bool createdMark = _activeBuffer().StartOutput(); + const bool createdMark = _mainBuffer->StartOutput(); if (createdMark) { - _activeBuffer().ManuallyMarkRowAsPrompt(_activeBuffer().GetCursor().GetPosition().y); + _mainBuffer->ManuallyMarkRowAsPrompt(_mainBuffer->GetCursor().GetPosition().y); // This changed the scrollbar marks - raise a notification to update them _NotifyScrollEvent(); From b07589e7a87e1d7a2f6144744a59c866c672a78e Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 23 Aug 2024 19:54:27 +0200 Subject: [PATCH 07/14] Improve reliability of VT responses (#17786) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Repurposes `_sendInputToConnection` to send output to the connection no matter whether the terminal is read-only or not. Now `SendInput` is the function responsible for the UI handling. * Buffers responses in a VT string into a single string before sending it as a response all at once. This reduces the chances for the UI thread to insert cursor positions and similar into the input pipe, because we're not constantly unlocking the terminal lock anymore for every response. The only way now that unrelated inputs are inserted into the input pipe is because the VT requests (e.g. DA1, DSR, etc.) are broken up across >1 reads. This also fixes VT responses in read-only panes. Closes #17775 ## Validation Steps Performed * Repeatedly run `echo ^[[c` in cmd. DA1 responses don't stack & always stay the same ✅ * Run nvim in WSL. Doesn't deadlock when pasting 1MB. ✅ * Run the repro from #17775, which requests a ton of OSC 4 (color palette) responses. Jiggle the cursor on top of the window. Responses never get split up. ✅ --- src/cascadia/TerminalControl/ControlCore.cpp | 46 +++++++++++--------- src/cascadia/TerminalControl/ControlCore.h | 1 + src/cascadia/TerminalCore/TerminalApi.cpp | 1 - 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index ac306b69c6..43d54f39b1 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -98,7 +98,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation Connection(connection); _terminal->SetWriteInputCallback([this](std::wstring_view wstr) { - _sendInputToConnection(wstr); + _pendingResponses.append(wstr); }); // GH#8969: pre-seed working directory to prevent potential races @@ -419,6 +419,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation // Return Value: // - void ControlCore::_sendInputToConnection(std::wstring_view wstr) + { + _connection.WriteInput(winrt_wstring_to_array_view(wstr)); + } + + // Method Description: + // - Writes the given sequence as input to the active terminal connection, + // Arguments: + // - wstr: the string of characters to write to the terminal connection. + // Return Value: + // - + void ControlCore::SendInput(const std::wstring_view wstr) { if (wstr.empty()) { @@ -435,21 +446,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation } else { - _connection.WriteInput(winrt_wstring_to_array_view(wstr)); + _sendInputToConnection(wstr); } } - // Method Description: - // - Writes the given sequence as input to the active terminal connection, - // Arguments: - // - wstr: the string of characters to write to the terminal connection. - // Return Value: - // - - void ControlCore::SendInput(const std::wstring_view wstr) - { - _sendInputToConnection(wstr); - } - bool ControlCore::SendCharEvent(const wchar_t ch, const WORD scanCode, const ::Microsoft::Terminal::Core::ControlKeyStates modifiers) @@ -485,7 +485,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation } if (out) { - _sendInputToConnection(*out); + SendInput(*out); return true; } return false; @@ -643,7 +643,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation } if (out) { - _sendInputToConnection(*out); + SendInput(*out); return true; } return false; @@ -662,7 +662,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation } if (out) { - _sendInputToConnection(*out); + SendInput(*out); return true; } return false; @@ -1412,7 +1412,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation } // It's important to not hold the terminal lock while calling this function as sending the data may take a long time. - _sendInputToConnection(filtered); + SendInput(filtered); const auto lock = _terminal->LockForWriting(); _terminal->ClearSelection(); @@ -2107,7 +2107,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // Sending input requires that we're unlocked, because // writing the input pipe may block indefinitely. const auto suspension = _terminal->SuspendLock(); - _sendInputToConnection(buffer); + SendInput(buffer); } } } @@ -2164,6 +2164,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation _terminal->Write(hstr); } + if (!_pendingResponses.empty()) + { + _sendInputToConnection(_pendingResponses); + _pendingResponses.clear(); + } + // Start the throttled update of where our hyperlinks are. const auto shared = _shared.lock_shared(); if (shared->outputIdle) @@ -2480,9 +2486,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation } if (out && !out->empty()) { - // _sendInputToConnection() asserts that we aren't in focus mode, - // but window focus events are always fine to send. - _connection.WriteInput(winrt_wstring_to_array_view(*out)); + _sendInputToConnection(*out); } } diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 32fa4c0253..d40fbcfca5 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -319,6 +319,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation winrt::com_ptr _settings{ nullptr }; std::shared_ptr<::Microsoft::Terminal::Core::Terminal> _terminal{ nullptr }; + std::wstring _pendingResponses; // NOTE: _renderEngine must be ordered before _renderer. // diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index 9114d7866e..9f51aadf00 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -24,7 +24,6 @@ void Terminal::ReturnResponse(const std::wstring_view response) { if (_pfnWriteInput && !response.empty()) { - const auto suspension = _readWriteLock.suspend(); _pfnWriteInput(response); } } From 47d9a87a23a06d20b2fb8d0df9b551938016ada8 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 23 Aug 2024 21:19:42 +0200 Subject: [PATCH 08/14] Make fire_and_forget exception safe (#17783) This PR clones `winrt::fire_and_forget` and replaces the uncaught exception handler with one that logs instead of terminating. My hope is that this removes one source of random crashes. ## Validation Steps Performed I added a `THROW_HR` to `TermControl::UpdateControlSettings` before and after the suspension point and ensured the application won't crash anymore. --- src/cascadia/Remoting/WindowManager.cpp | 10 ++-- src/cascadia/Remoting/WindowManager.h | 4 +- src/cascadia/TerminalApp/AboutDialog.cpp | 2 +- src/cascadia/TerminalApp/AboutDialog.h | 2 +- .../TerminalApp/AppActionHandlers.cpp | 4 +- src/cascadia/TerminalApp/AppLogic.cpp | 2 +- src/cascadia/TerminalApp/AppLogic.h | 4 +- .../TerminalApp/DebugTapConnection.cpp | 2 +- src/cascadia/TerminalApp/Jumplist.cpp | 2 +- src/cascadia/TerminalApp/Jumplist.h | 2 +- .../TerminalApp/SnippetsPaneContent.cpp | 2 +- .../TerminalApp/SnippetsPaneContent.h | 2 +- src/cascadia/TerminalApp/TabManagement.cpp | 8 +-- src/cascadia/TerminalApp/TerminalPage.cpp | 56 +++++++++--------- src/cascadia/TerminalApp/TerminalPage.h | 58 +++++++++---------- .../TerminalApp/TerminalPaneContent.cpp | 6 +- .../TerminalApp/TerminalPaneContent.h | 4 +- src/cascadia/TerminalApp/TerminalTab.cpp | 14 ++--- src/cascadia/TerminalApp/TerminalWindow.cpp | 2 +- src/cascadia/TerminalApp/TerminalWindow.h | 2 +- src/cascadia/TerminalApp/TitlebarControl.cpp | 2 +- src/cascadia/TerminalApp/TitlebarControl.h | 2 +- .../TerminalConnection/ConptyConnection.cpp | 2 +- .../TerminalConnection/ConptyConnection.h | 2 +- src/cascadia/TerminalControl/ControlCore.cpp | 6 +- src/cascadia/TerminalControl/ControlCore.h | 4 +- src/cascadia/TerminalControl/TermControl.cpp | 36 ++++++------ src/cascadia/TerminalControl/TermControl.h | 24 ++++---- .../TerminalSettingsEditor/Appearances.cpp | 2 +- .../TerminalSettingsEditor/Appearances.h | 2 +- .../TerminalSettingsEditor/Profiles_Base.cpp | 6 +- .../TerminalSettingsEditor/Profiles_Base.h | 6 +- .../CascadiaSettings.cpp | 2 +- .../CascadiaSettingsSerialization.cpp | 2 +- src/cascadia/WindowsTerminal/AppHost.cpp | 16 ++--- src/cascadia/WindowsTerminal/AppHost.h | 16 ++--- src/cascadia/WindowsTerminal/IslandWindow.cpp | 2 +- src/cascadia/WindowsTerminal/IslandWindow.h | 2 +- .../WindowsTerminal/WindowEmperor.cpp | 12 ++-- src/cascadia/WindowsTerminal/WindowEmperor.h | 8 +-- src/cascadia/inc/cppwinrt_utils.h | 48 +++++++++++++++ 41 files changed, 219 insertions(+), 171 deletions(-) diff --git a/src/cascadia/Remoting/WindowManager.cpp b/src/cascadia/Remoting/WindowManager.cpp index a07a3960b4..11ddbf45f4 100644 --- a/src/cascadia/Remoting/WindowManager.cpp +++ b/src/cascadia/Remoting/WindowManager.cpp @@ -442,16 +442,16 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation winrt::get_self(peasant)->ActiveTabTitle(title); } - winrt::fire_and_forget WindowManager::RequestMoveContent(winrt::hstring window, - winrt::hstring content, - uint32_t tabIndex, - Windows::Foundation::IReference windowBounds) + safe_void_coroutine WindowManager::RequestMoveContent(winrt::hstring window, + winrt::hstring content, + uint32_t tabIndex, + Windows::Foundation::IReference windowBounds) { co_await winrt::resume_background(); _monarch.RequestMoveContent(window, content, tabIndex, windowBounds); } - winrt::fire_and_forget WindowManager::RequestSendContent(Remoting::RequestReceiveContentArgs args) + safe_void_coroutine WindowManager::RequestSendContent(Remoting::RequestReceiveContentArgs args) { co_await winrt::resume_background(); _monarch.RequestSendContent(args); diff --git a/src/cascadia/Remoting/WindowManager.h b/src/cascadia/Remoting/WindowManager.h index 936e13bc68..b2aa219aee 100644 --- a/src/cascadia/Remoting/WindowManager.h +++ b/src/cascadia/Remoting/WindowManager.h @@ -43,8 +43,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation bool DoesQuakeWindowExist(); - winrt::fire_and_forget RequestMoveContent(winrt::hstring window, winrt::hstring content, uint32_t tabIndex, Windows::Foundation::IReference windowBounds); - winrt::fire_and_forget RequestSendContent(Remoting::RequestReceiveContentArgs args); + safe_void_coroutine RequestMoveContent(winrt::hstring window, winrt::hstring content, uint32_t tabIndex, Windows::Foundation::IReference windowBounds); + safe_void_coroutine RequestSendContent(Remoting::RequestReceiveContentArgs args); til::typed_event FindTargetWindowRequested; diff --git a/src/cascadia/TerminalApp/AboutDialog.cpp b/src/cascadia/TerminalApp/AboutDialog.cpp index c443b73e09..40b415871f 100644 --- a/src/cascadia/TerminalApp/AboutDialog.cpp +++ b/src/cascadia/TerminalApp/AboutDialog.cpp @@ -58,7 +58,7 @@ namespace winrt::TerminalApp::implementation ShellExecute(nullptr, nullptr, currentPath.c_str(), nullptr, nullptr, SW_SHOW); } - winrt::fire_and_forget AboutDialog::_queueUpdateCheck() + safe_void_coroutine AboutDialog::_queueUpdateCheck() { auto strongThis = get_strong(); auto now{ std::chrono::system_clock::now() }; diff --git a/src/cascadia/TerminalApp/AboutDialog.h b/src/cascadia/TerminalApp/AboutDialog.h index c8cda18c24..0c8c174b4f 100644 --- a/src/cascadia/TerminalApp/AboutDialog.h +++ b/src/cascadia/TerminalApp/AboutDialog.h @@ -26,7 +26,7 @@ namespace winrt::TerminalApp::implementation void _ThirdPartyNoticesOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs); void _SendFeedbackOnClick(const IInspectable& sender, const Windows::UI::Xaml::Controls::ContentDialogButtonClickEventArgs& eventArgs); - winrt::fire_and_forget _queueUpdateCheck(); + safe_void_coroutine _queueUpdateCheck(); }; } diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index bc2f5f4fff..8675fb98ba 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -883,7 +883,7 @@ namespace winrt::TerminalApp::implementation // - // Important: Don't take the param by reference, since we'll be doing work // on another thread. - fire_and_forget TerminalPage::_OpenNewWindow(const INewContentArgs newContentArgs) + safe_void_coroutine TerminalPage::_OpenNewWindow(const INewContentArgs newContentArgs) { auto terminalArgs{ newContentArgs.try_as() }; @@ -1441,7 +1441,7 @@ namespace winrt::TerminalApp::implementation } } - winrt::fire_and_forget TerminalPage::_doHandleSuggestions(SuggestionsArgs realArgs) + safe_void_coroutine TerminalPage::_doHandleSuggestions(SuggestionsArgs realArgs) { const auto source = realArgs.Source(); std::vector commandsCollection; diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 2225708786..18b9a1ffea 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -355,7 +355,7 @@ namespace winrt::TerminalApp::implementation } CATCH_LOG() - fire_and_forget AppLogic::_ApplyStartupTaskStateChange() + safe_void_coroutine AppLogic::_ApplyStartupTaskStateChange() try { // First, make sure we're running in a packaged context. This method diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index 43d64184de..42f89942ad 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -103,12 +103,12 @@ namespace winrt::TerminalApp::implementation const Microsoft::Terminal::Settings::Model::WindowingMode& windowingBehavior); void _ApplyLanguageSettingChange() noexcept; - fire_and_forget _ApplyStartupTaskStateChange(); + safe_void_coroutine _ApplyStartupTaskStateChange(); [[nodiscard]] HRESULT _TryLoadSettings() noexcept; void _ProcessLazySettingsChanges(); void _RegisterSettingsChange(); - fire_and_forget _DispatchReloadSettings(); + safe_void_coroutine _DispatchReloadSettings(); void _setupFolderPathEnvVar(); diff --git a/src/cascadia/TerminalApp/DebugTapConnection.cpp b/src/cascadia/TerminalApp/DebugTapConnection.cpp index 6d2ccc0b75..3a4f670c38 100644 --- a/src/cascadia/TerminalApp/DebugTapConnection.cpp +++ b/src/cascadia/TerminalApp/DebugTapConnection.cpp @@ -21,7 +21,7 @@ namespace winrt::Microsoft::TerminalApp::implementation } void Initialize(const Windows::Foundation::Collections::ValueSet& /*settings*/) {} ~DebugInputTapConnection() = default; - winrt::fire_and_forget Start() + safe_void_coroutine Start() { // GH#11282: It's possible that we're about to be started, _before_ // our paired connection is started. Both will get Start()'ed when diff --git a/src/cascadia/TerminalApp/Jumplist.cpp b/src/cascadia/TerminalApp/Jumplist.cpp index 5f3e3f12f7..6ca3483775 100644 --- a/src/cascadia/TerminalApp/Jumplist.cpp +++ b/src/cascadia/TerminalApp/Jumplist.cpp @@ -60,7 +60,7 @@ static std::wstring _normalizeIconPath(std::wstring_view path) // - settings - The settings object to update the jumplist with. // Return Value: // - -winrt::fire_and_forget Jumplist::UpdateJumplist(const CascadiaSettings& settings) noexcept +safe_void_coroutine Jumplist::UpdateJumplist(const CascadiaSettings& settings) noexcept { if (!settings) { diff --git a/src/cascadia/TerminalApp/Jumplist.h b/src/cascadia/TerminalApp/Jumplist.h index ea7797431e..af0f3f77c1 100644 --- a/src/cascadia/TerminalApp/Jumplist.h +++ b/src/cascadia/TerminalApp/Jumplist.h @@ -18,7 +18,7 @@ struct IShellLinkW; class Jumplist { public: - static winrt::fire_and_forget UpdateJumplist(const winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings& settings) noexcept; + static safe_void_coroutine UpdateJumplist(const winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings& settings) noexcept; private: static void _updateProfiles(IObjectCollection* jumplistItems, winrt::Windows::Foundation::Collections::IVectorView profiles); diff --git a/src/cascadia/TerminalApp/SnippetsPaneContent.cpp b/src/cascadia/TerminalApp/SnippetsPaneContent.cpp index b6415c6ba7..9e8b27db5e 100644 --- a/src/cascadia/TerminalApp/SnippetsPaneContent.cpp +++ b/src/cascadia/TerminalApp/SnippetsPaneContent.cpp @@ -42,7 +42,7 @@ namespace winrt::TerminalApp::implementation } } - winrt::fire_and_forget SnippetsPaneContent::UpdateSettings(const CascadiaSettings& settings) + safe_void_coroutine SnippetsPaneContent::UpdateSettings(const CascadiaSettings& settings) { _settings = settings; diff --git a/src/cascadia/TerminalApp/SnippetsPaneContent.h b/src/cascadia/TerminalApp/SnippetsPaneContent.h index b394f94b4e..c469e83fcc 100644 --- a/src/cascadia/TerminalApp/SnippetsPaneContent.h +++ b/src/cascadia/TerminalApp/SnippetsPaneContent.h @@ -17,7 +17,7 @@ namespace winrt::TerminalApp::implementation winrt::Windows::UI::Xaml::FrameworkElement GetRoot(); - winrt::fire_and_forget UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings& settings); + safe_void_coroutine UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings& settings); winrt::Windows::Foundation::Size MinimumSize(); void Focus(winrt::Windows::UI::Xaml::FocusState reason = winrt::Windows::UI::Xaml::FocusState::Programmatic); diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index 2348e70f73..103f46a4cb 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -308,7 +308,7 @@ namespace winrt::TerminalApp::implementation // - Exports the content of the Terminal Buffer inside the tab // Arguments: // - tab: tab to export - winrt::fire_and_forget TerminalPage::_ExportTab(const TerminalTab& tab, winrt::hstring filepath) + safe_void_coroutine TerminalPage::_ExportTab(const TerminalTab& tab, winrt::hstring filepath) { // This will be used to set up the file picker "filter", to select .txt // files by default. @@ -680,7 +680,7 @@ namespace winrt::TerminalApp::implementation // - tab: tab to focus. // Return Value: // - - winrt::fire_and_forget TerminalPage::_SetFocusedTab(const winrt::TerminalApp::TabBase tab) + safe_void_coroutine TerminalPage::_SetFocusedTab(const winrt::TerminalApp::TabBase tab) { // GH#1117: This is a workaround because _tabView.SelectedIndex(tabIndex) // sometimes set focus to an incorrect tab after removing some tabs @@ -765,7 +765,7 @@ namespace winrt::TerminalApp::implementation // - Close the currently focused pane. If the pane is the last pane in the // tab, the tab will also be closed. This will happen when we handle the // tab's Closed event. - winrt::fire_and_forget TerminalPage::_CloseFocusedPane() + safe_void_coroutine TerminalPage::_CloseFocusedPane() { if (const auto terminalTab{ _GetFocusedTabImpl() }) { @@ -831,7 +831,7 @@ namespace winrt::TerminalApp::implementation // - Closes provided tabs one by one // Arguments: // - tabs - tabs to remove - winrt::fire_and_forget TerminalPage::_RemoveTabs(const std::vector tabs) + safe_void_coroutine TerminalPage::_RemoveTabs(const std::vector tabs) { for (auto& tab : tabs) { diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index aae32e336c..8b43dbbfe6 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -405,7 +405,7 @@ namespace winrt::TerminalApp::implementation } } - winrt::fire_and_forget TerminalPage::_NewTerminalByDrop(const Windows::Foundation::IInspectable&, winrt::Windows::UI::Xaml::DragEventArgs e) + safe_void_coroutine TerminalPage::_NewTerminalByDrop(const Windows::Foundation::IInspectable&, winrt::Windows::UI::Xaml::DragEventArgs e) try { const auto data = e.DataView(); @@ -556,10 +556,10 @@ namespace winrt::TerminalApp::implementation // nt -d .` from inside another directory to work as expected. // Return Value: // - - winrt::fire_and_forget TerminalPage::ProcessStartupActions(Windows::Foundation::Collections::IVector actions, - const bool initial, - const winrt::hstring cwd, - const winrt::hstring env) + safe_void_coroutine TerminalPage::ProcessStartupActions(Windows::Foundation::Collections::IVector actions, + const bool initial, + const winrt::hstring cwd, + const winrt::hstring env) { auto weakThis{ get_weak() }; @@ -644,7 +644,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - winrt::fire_and_forget TerminalPage::_CompleteInitialization() + safe_void_coroutine TerminalPage::_CompleteInitialization() { _startupState = StartupState::Initialized; @@ -1960,7 +1960,7 @@ namespace winrt::TerminalApp::implementation // Method Description: // - Warn the user that they are about to close all open windows, then // signal that we want to close everything. - fire_and_forget TerminalPage::RequestQuit() + safe_void_coroutine TerminalPage::RequestQuit() { if (!_displayingCloseDialog) { @@ -2048,7 +2048,7 @@ namespace winrt::TerminalApp::implementation // Method Description: // - Close the terminal app. If there is more // than one tab opened, show a warning dialog. - fire_and_forget TerminalPage::CloseWindow() + safe_void_coroutine TerminalPage::CloseWindow() { if (_HasMultipleTabs() && _settings.GlobalSettings().ConfirmCloseAllTabs() && @@ -2355,8 +2355,8 @@ namespace winrt::TerminalApp::implementation // reattach instead of create new content, so this method simply needs to // parse the JSON and pump it into our action handler. Almost the same as // doing something like `wt -w 0 nt`. - winrt::fire_and_forget TerminalPage::AttachContent(IVector args, - uint32_t tabIndex) + safe_void_coroutine TerminalPage::AttachContent(IVector args, + uint32_t tabIndex) { if (args == nullptr || args.Size() == 0) @@ -2745,7 +2745,7 @@ namespace winrt::TerminalApp::implementation // - Does some of this in a background thread, as to not hang/crash the UI thread. // Arguments: // - eventArgs: the PasteFromClipboard event sent from the TermControl - fire_and_forget TerminalPage::_PasteFromClipboardHandler(const IInspectable /*sender*/, const PasteFromClipboardEventArgs eventArgs) + safe_void_coroutine TerminalPage::_PasteFromClipboardHandler(const IInspectable /*sender*/, const PasteFromClipboardEventArgs eventArgs) try { // The old Win32 clipboard API as used below is somewhere in the order of 300-1000x faster than @@ -2916,8 +2916,8 @@ namespace winrt::TerminalApp::implementation // Important! Don't take this eventArgs by reference, we need to extend the // lifetime of it to the other side of the co_await! - winrt::fire_and_forget TerminalPage::_ControlNoticeRaisedHandler(const IInspectable /*sender*/, - const Microsoft::Terminal::Control::NoticeEventArgs eventArgs) + safe_void_coroutine TerminalPage::_ControlNoticeRaisedHandler(const IInspectable /*sender*/, + const Microsoft::Terminal::Control::NoticeEventArgs eventArgs) { auto weakThis = get_weak(); co_await wil::resume_foreground(Dispatcher()); @@ -2986,7 +2986,7 @@ namespace winrt::TerminalApp::implementation // Arguments: // - sender (not used) // - eventArgs: the arguments specifying how to set the progress indicator - winrt::fire_and_forget TerminalPage::_SetTaskbarProgressHandler(const IInspectable /*sender*/, const IInspectable /*eventArgs*/) + safe_void_coroutine TerminalPage::_SetTaskbarProgressHandler(const IInspectable /*sender*/, const IInspectable /*eventArgs*/) { co_await wil::resume_foreground(Dispatcher()); SetTaskbarProgress.raise(*this, nullptr); @@ -3122,7 +3122,7 @@ namespace winrt::TerminalApp::implementation // - Called when the settings button is clicked. ShellExecutes the settings // file, as to open it in the default editor for .json files. Does this in // a background thread, as to not hang/crash the UI thread. - fire_and_forget TerminalPage::_LaunchSettings(const SettingsTarget target) + safe_void_coroutine TerminalPage::_LaunchSettings(const SettingsTarget target) { if (target == SettingsTarget::SettingsUI) { @@ -4315,7 +4315,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - winrt::fire_and_forget TerminalPage::IdentifyWindow() + safe_void_coroutine TerminalPage::IdentifyWindow() { auto weakThis{ get_weak() }; co_await wil::resume_foreground(Dispatcher()); @@ -4351,7 +4351,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - winrt::fire_and_forget TerminalPage::RenameFailed() + safe_void_coroutine TerminalPage::RenameFailed() { auto weakThis{ get_weak() }; co_await wil::resume_foreground(Dispatcher()); @@ -4378,7 +4378,7 @@ namespace winrt::TerminalApp::implementation } } - winrt::fire_and_forget TerminalPage::ShowTerminalWorkingDirectory() + safe_void_coroutine TerminalPage::ShowTerminalWorkingDirectory() { auto weakThis{ get_weak() }; co_await wil::resume_foreground(Dispatcher()); @@ -4604,7 +4604,7 @@ namespace winrt::TerminalApp::implementation // - sender: the ICoreState instance containing the connection state // Return Value: // - - winrt::fire_and_forget TerminalPage::_ConnectionStateChangedHandler(const IInspectable& sender, const IInspectable& /*args*/) const + safe_void_coroutine TerminalPage::_ConnectionStateChangedHandler(const IInspectable& sender, const IInspectable& /*args*/) const { if (const auto coreState{ sender.try_as() }) { @@ -4899,8 +4899,8 @@ namespace winrt::TerminalApp::implementation } } - winrt::fire_and_forget TerminalPage::_ControlCompletionsChangedHandler(const IInspectable sender, - const CompletionsChangedEventArgs args) + safe_void_coroutine TerminalPage::_ControlCompletionsChangedHandler(const IInspectable sender, + const CompletionsChangedEventArgs args) { // This will come in on a background (not-UI, not output) thread. @@ -5107,8 +5107,8 @@ namespace winrt::TerminalApp::implementation // Handler for our WindowProperties's PropertyChanged event. We'll use this // to pop the "Identify Window" toast when the user renames our window. - winrt::fire_and_forget TerminalPage::_windowPropertyChanged(const IInspectable& /*sender*/, - const WUX::Data::PropertyChangedEventArgs& args) + safe_void_coroutine TerminalPage::_windowPropertyChanged(const IInspectable& /*sender*/, + const WUX::Data::PropertyChangedEventArgs& args) { if (args.PropertyName() != L"WindowName") { @@ -5204,8 +5204,8 @@ namespace winrt::TerminalApp::implementation // - Called on the TARGET of a tab drag/drop. We'll unpack the DataPackage // to find who the tab came from. We'll then ask the Monarch to ask the // sender to move that tab to us. - winrt::fire_and_forget TerminalPage::_onTabStripDrop(winrt::Windows::Foundation::IInspectable /*sender*/, - winrt::Windows::UI::Xaml::DragEventArgs e) + safe_void_coroutine TerminalPage::_onTabStripDrop(winrt::Windows::Foundation::IInspectable /*sender*/, + winrt::Windows::UI::Xaml::DragEventArgs e) { // Get the PID and make sure it is the same as ours. if (const auto& pidObj{ e.DataView().Properties().TryLookup(L"pid") }) @@ -5276,7 +5276,7 @@ namespace winrt::TerminalApp::implementation // the destination window. // - Fortunately, sending the tab is basically just a MoveTab action, so we // can largely reuse that. - winrt::fire_and_forget TerminalPage::SendContentToOther(winrt::TerminalApp::RequestReceiveContentArgs args) + safe_void_coroutine TerminalPage::SendContentToOther(winrt::TerminalApp::RequestReceiveContentArgs args) { // validate that we're the source window of the tab in this request if (args.SourceWindow() != _WindowProperties.WindowId()) @@ -5301,8 +5301,8 @@ namespace winrt::TerminalApp::implementation } } - winrt::fire_and_forget TerminalPage::_onTabDroppedOutside(winrt::IInspectable sender, - winrt::MUX::Controls::TabViewTabDroppedOutsideEventArgs e) + safe_void_coroutine TerminalPage::_onTabDroppedOutside(winrt::IInspectable sender, + winrt::MUX::Controls::TabViewTabDroppedOutsideEventArgs e) { // Get the current pointer point from the CoreWindow const auto& pointerPoint{ CoreWindow::GetForCurrentThread().PointerPosition() }; diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 449ffa1fff..a957690a96 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -125,8 +125,8 @@ namespace winrt::TerminalApp::implementation CommandPalette LoadCommandPalette(); SuggestionsControl LoadSuggestionsUI(); - winrt::fire_and_forget RequestQuit(); - winrt::fire_and_forget CloseWindow(); + safe_void_coroutine RequestQuit(); + safe_void_coroutine CloseWindow(); void PersistState(); void ToggleFocusMode(); @@ -153,16 +153,16 @@ namespace winrt::TerminalApp::implementation void ShowKeyboardServiceWarning() const; winrt::hstring KeyboardServiceDisabledText(); - winrt::fire_and_forget IdentifyWindow(); + safe_void_coroutine IdentifyWindow(); void ActionSaved(winrt::hstring input, winrt::hstring name, winrt::hstring keyChord); void ActionSaveFailed(winrt::hstring message); - winrt::fire_and_forget RenameFailed(); - winrt::fire_and_forget ShowTerminalWorkingDirectory(); + safe_void_coroutine RenameFailed(); + safe_void_coroutine ShowTerminalWorkingDirectory(); - winrt::fire_and_forget ProcessStartupActions(Windows::Foundation::Collections::IVector actions, - const bool initial, - const winrt::hstring cwd = winrt::hstring{}, - const winrt::hstring env = winrt::hstring{}); + safe_void_coroutine ProcessStartupActions(Windows::Foundation::Collections::IVector actions, + const bool initial, + const winrt::hstring cwd = winrt::hstring{}, + const winrt::hstring env = winrt::hstring{}); TerminalApp::WindowProperties WindowProperties() const noexcept { return _WindowProperties; }; @@ -174,8 +174,8 @@ namespace winrt::TerminalApp::implementation bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down); - winrt::fire_and_forget AttachContent(Windows::Foundation::Collections::IVector args, uint32_t tabIndex); - winrt::fire_and_forget SendContentToOther(winrt::TerminalApp::RequestReceiveContentArgs args); + safe_void_coroutine AttachContent(Windows::Foundation::Collections::IVector args, uint32_t tabIndex); + safe_void_coroutine SendContentToOther(winrt::TerminalApp::RequestReceiveContentArgs args); uint32_t NumberOfTabs() const; @@ -296,7 +296,7 @@ namespace winrt::TerminalApp::implementation winrt::Microsoft::Terminal::TerminalConnection::ConptyConnection::NewConnection_revoker _newConnectionRevoker; - winrt::fire_and_forget _NewTerminalByDrop(const Windows::Foundation::IInspectable&, winrt::Windows::UI::Xaml::DragEventArgs e); + safe_void_coroutine _NewTerminalByDrop(const Windows::Foundation::IInspectable&, winrt::Windows::UI::Xaml::DragEventArgs e); __declspec(noinline) CommandPalette _loadCommandPaletteSlowPath(); bool _commandPaletteIs(winrt::Windows::UI::Xaml::Visibility visibility); @@ -328,7 +328,7 @@ namespace winrt::TerminalApp::implementation winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _duplicateConnectionForRestart(const TerminalApp::TerminalPaneContent& paneContent); void _restartPaneConnection(const TerminalApp::TerminalPaneContent&, const winrt::Windows::Foundation::IInspectable&); - winrt::fire_and_forget _OpenNewWindow(const Microsoft::Terminal::Settings::Model::INewContentArgs newContentArgs); + safe_void_coroutine _OpenNewWindow(const Microsoft::Terminal::Settings::Model::INewContentArgs newContentArgs); void _OpenNewTerminalViaDropdown(const Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs); @@ -352,12 +352,12 @@ namespace winrt::TerminalApp::implementation void _DuplicateFocusedTab(); void _DuplicateTab(const TerminalTab& tab); - winrt::fire_and_forget _ExportTab(const TerminalTab& tab, winrt::hstring filepath); + safe_void_coroutine _ExportTab(const TerminalTab& tab, winrt::hstring filepath); winrt::Windows::Foundation::IAsyncAction _HandleCloseTabRequested(winrt::TerminalApp::TabBase tab); void _CloseTabAtIndex(uint32_t index); void _RemoveTab(const winrt::TerminalApp::TabBase& tab); - winrt::fire_and_forget _RemoveTabs(const std::vector tabs); + safe_void_coroutine _RemoveTabs(const std::vector tabs); void _InitializeTab(winrt::com_ptr newTabImpl, uint32_t insertPosition = -1); void _RegisterTerminalEvents(Microsoft::Terminal::Control::TermControl term); @@ -402,8 +402,8 @@ namespace winrt::TerminalApp::implementation TerminalApp::TabBase _GetTabByTabViewItem(const Microsoft::UI::Xaml::Controls::TabViewItem& tabViewItem) const noexcept; void _HandleClosePaneRequested(std::shared_ptr pane); - winrt::fire_and_forget _SetFocusedTab(const winrt::TerminalApp::TabBase tab); - winrt::fire_and_forget _CloseFocusedPane(); + safe_void_coroutine _SetFocusedTab(const winrt::TerminalApp::TabBase tab); + safe_void_coroutine _CloseFocusedPane(); void _ClosePanes(weak_ref weakTab, std::vector paneIds); winrt::Windows::Foundation::IAsyncOperation _PaneConfirmCloseReadOnly(std::shared_ptr pane); void _AddPreviouslyClosedPaneOrTab(std::vector&& args); @@ -421,8 +421,8 @@ namespace winrt::TerminalApp::implementation void _ScrollToBufferEdge(ScrollDirection scrollDirection); void _SetAcceleratorForMenuItem(Windows::UI::Xaml::Controls::MenuFlyoutItem& menuItem, const winrt::Microsoft::Terminal::Control::KeyChord& keyChord); - winrt::fire_and_forget _PasteFromClipboardHandler(const IInspectable sender, - const Microsoft::Terminal::Control::PasteFromClipboardEventArgs eventArgs); + safe_void_coroutine _PasteFromClipboardHandler(const IInspectable sender, + const Microsoft::Terminal::Control::PasteFromClipboardEventArgs eventArgs); void _OpenHyperlinkHandler(const IInspectable sender, const Microsoft::Terminal::Control::OpenHyperlinkEventArgs eventArgs); bool _IsUriSupported(const winrt::Windows::Foundation::Uri& parsedUri); @@ -430,14 +430,14 @@ namespace winrt::TerminalApp::implementation void _ShowCouldNotOpenDialog(winrt::hstring reason, winrt::hstring uri); bool _CopyText(const bool dismissSelection, const bool singleLine, const Windows::Foundation::IReference& formats); - winrt::fire_and_forget _SetTaskbarProgressHandler(const IInspectable sender, const IInspectable eventArgs); + safe_void_coroutine _SetTaskbarProgressHandler(const IInspectable sender, const IInspectable eventArgs); void _PasteText(); - winrt::fire_and_forget _ControlNoticeRaisedHandler(const IInspectable sender, const Microsoft::Terminal::Control::NoticeEventArgs eventArgs); + safe_void_coroutine _ControlNoticeRaisedHandler(const IInspectable sender, const Microsoft::Terminal::Control::NoticeEventArgs eventArgs); void _ShowControlNoticeDialog(const winrt::hstring& title, const winrt::hstring& message); - fire_and_forget _LaunchSettings(const Microsoft::Terminal::Settings::Model::SettingsTarget target); + safe_void_coroutine _LaunchSettings(const Microsoft::Terminal::Settings::Model::SettingsTarget target); void _TabDragStarted(const IInspectable& sender, const IInspectable& eventArgs); void _TabDragCompleted(const IInspectable& sender, const IInspectable& eventArgs); @@ -476,7 +476,7 @@ namespace winrt::TerminalApp::implementation void _StartInboundListener(); - winrt::fire_and_forget _CompleteInitialization(); + safe_void_coroutine _CompleteInitialization(); void _FocusActiveControl(IInspectable sender, IInspectable eventArgs); @@ -523,7 +523,7 @@ namespace winrt::TerminalApp::implementation const winrt::Microsoft::Terminal::Settings::Model::Profile& profile); void _OpenElevatedWT(winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs); - winrt::fire_and_forget _ConnectionStateChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args) const; + safe_void_coroutine _ConnectionStateChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args) const; void _CloseOnExitInfoDismissHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args) const; void _KeyboardServiceWarningInfoDismissHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args) const; static bool _IsMessageDismissed(const winrt::Microsoft::Terminal::Settings::Model::InfoBarMessage& message); @@ -533,7 +533,7 @@ namespace winrt::TerminalApp::implementation void _updateAllTabCloseButtons(); void _updatePaneResources(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme); - winrt::fire_and_forget _ControlCompletionsChangedHandler(const winrt::Windows::Foundation::IInspectable sender, const winrt::Microsoft::Terminal::Control::CompletionsChangedEventArgs args); + safe_void_coroutine _ControlCompletionsChangedHandler(const winrt::Windows::Foundation::IInspectable sender, const winrt::Microsoft::Terminal::Control::CompletionsChangedEventArgs args); void _OpenSuggestions(const Microsoft::Terminal::Control::TermControl& sender, Windows::Foundation::Collections::IVector commandsCollection, winrt::TerminalApp::SuggestionsMode mode, winrt::hstring filterText); @@ -541,12 +541,12 @@ namespace winrt::TerminalApp::implementation Windows::Foundation::IAsyncAction _SearchMissingCommandHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::SearchMissingCommandEventArgs args); Windows::Foundation::IAsyncOperation> _FindPackageAsync(hstring query); - winrt::fire_and_forget _windowPropertyChanged(const IInspectable& sender, const winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs& args); + safe_void_coroutine _windowPropertyChanged(const IInspectable& sender, const winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs& args); void _onTabDragStarting(const winrt::Microsoft::UI::Xaml::Controls::TabView& sender, const winrt::Microsoft::UI::Xaml::Controls::TabViewTabDragStartingEventArgs& e); void _onTabStripDragOver(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::DragEventArgs& e); - winrt::fire_and_forget _onTabStripDrop(winrt::Windows::Foundation::IInspectable sender, winrt::Windows::UI::Xaml::DragEventArgs e); - winrt::fire_and_forget _onTabDroppedOutside(winrt::Windows::Foundation::IInspectable sender, winrt::Microsoft::UI::Xaml::Controls::TabViewTabDroppedOutsideEventArgs e); + safe_void_coroutine _onTabStripDrop(winrt::Windows::Foundation::IInspectable sender, winrt::Windows::UI::Xaml::DragEventArgs e); + safe_void_coroutine _onTabDroppedOutside(winrt::Windows::Foundation::IInspectable sender, winrt::Microsoft::UI::Xaml::Controls::TabViewTabDroppedOutsideEventArgs e); void _DetachPaneFromWindow(std::shared_ptr pane); void _DetachTabFromWindow(const winrt::com_ptr& terminalTab); @@ -564,7 +564,7 @@ namespace winrt::TerminalApp::implementation winrt::com_ptr _senderOrFocusedTab(const IInspectable& sender); void _activePaneChanged(winrt::TerminalApp::TerminalTab tab, Windows::Foundation::IInspectable args); - winrt::fire_and_forget _doHandleSuggestions(Microsoft::Terminal::Settings::Model::SuggestionsArgs realArgs); + safe_void_coroutine _doHandleSuggestions(Microsoft::Terminal::Settings::Model::SuggestionsArgs realArgs); #pragma region ActionHandlers // These are all defined in AppActionHandlers.cpp diff --git a/src/cascadia/TerminalApp/TerminalPaneContent.cpp b/src/cascadia/TerminalApp/TerminalPaneContent.cpp index 5ecdb4bf56..1ab5cd013c 100644 --- a/src/cascadia/TerminalApp/TerminalPaneContent.cpp +++ b/src/cascadia/TerminalApp/TerminalPaneContent.cpp @@ -194,8 +194,8 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - winrt::fire_and_forget TerminalPaneContent::_controlConnectionStateChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, - const winrt::Windows::Foundation::IInspectable& args) + safe_void_coroutine TerminalPaneContent::_controlConnectionStateChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, + const winrt::Windows::Foundation::IInspectable& args) { ConnectionStateChanged.raise(sender, args); auto newConnectionState = ConnectionState::Closed; @@ -300,7 +300,7 @@ namespace winrt::TerminalApp::implementation } } - winrt::fire_and_forget TerminalPaneContent::_playBellSound(winrt::Windows::Foundation::Uri uri) + safe_void_coroutine TerminalPaneContent::_playBellSound(winrt::Windows::Foundation::Uri uri) { auto weakThis{ get_weak() }; co_await wil::resume_foreground(_control.Dispatcher()); diff --git a/src/cascadia/TerminalApp/TerminalPaneContent.h b/src/cascadia/TerminalApp/TerminalPaneContent.h index 0cfa90043c..aa5f4da498 100644 --- a/src/cascadia/TerminalApp/TerminalPaneContent.h +++ b/src/cascadia/TerminalApp/TerminalPaneContent.h @@ -82,9 +82,9 @@ namespace winrt::TerminalApp::implementation void _setupControlEvents(); void _removeControlEvents(); - winrt::fire_and_forget _playBellSound(winrt::Windows::Foundation::Uri uri); + safe_void_coroutine _playBellSound(winrt::Windows::Foundation::Uri uri); - winrt::fire_and_forget _controlConnectionStateChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& /*args*/); + safe_void_coroutine _controlConnectionStateChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& /*args*/); void _controlWarningBellHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& e); void _controlReadOnlyChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& e); diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp index 566e6b6fbd..42339d2fcb 100644 --- a/src/cascadia/TerminalApp/TerminalTab.cpp +++ b/src/cascadia/TerminalApp/TerminalTab.cpp @@ -949,7 +949,7 @@ namespace winrt::TerminalApp::implementation events.TitleChanged = content.TitleChanged( winrt::auto_revoke, - [dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget { + [dispatcher, weakThis](auto&&, auto&&) -> safe_void_coroutine { // 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. @@ -967,7 +967,7 @@ namespace winrt::TerminalApp::implementation events.TabColorChanged = content.TabColorChanged( winrt::auto_revoke, - [dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget { + [dispatcher, weakThis](auto&&, auto&&) -> safe_void_coroutine { const auto weakThisCopy = weakThis; co_await wil::resume_foreground(dispatcher); if (auto tab{ weakThisCopy.get() }) @@ -981,7 +981,7 @@ namespace winrt::TerminalApp::implementation events.TaskbarProgressChanged = content.TaskbarProgressChanged( winrt::auto_revoke, - [dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget { + [dispatcher, weakThis](auto&&, auto&&) -> safe_void_coroutine { const auto weakThisCopy = weakThis; co_await wil::resume_foreground(dispatcher); // Check if Tab's lifetime has expired @@ -993,7 +993,7 @@ namespace winrt::TerminalApp::implementation events.ConnectionStateChanged = content.ConnectionStateChanged( winrt::auto_revoke, - [dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget { + [dispatcher, weakThis](auto&&, auto&&) -> safe_void_coroutine { const auto weakThisCopy = weakThis; co_await wil::resume_foreground(dispatcher); if (auto tab{ weakThisCopy.get() }) @@ -1004,7 +1004,7 @@ namespace winrt::TerminalApp::implementation events.ReadOnlyChanged = content.ReadOnlyChanged( winrt::auto_revoke, - [dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget { + [dispatcher, weakThis](auto&&, auto&&) -> safe_void_coroutine { const auto weakThisCopy = weakThis; co_await wil::resume_foreground(dispatcher); if (auto tab{ weakThis.get() }) @@ -1015,7 +1015,7 @@ namespace winrt::TerminalApp::implementation events.FocusRequested = content.FocusRequested( winrt::auto_revoke, - [dispatcher, weakThis](TerminalApp::IPaneContent sender, auto) -> winrt::fire_and_forget { + [dispatcher, weakThis](TerminalApp::IPaneContent sender, auto) -> safe_void_coroutine { const auto weakThisCopy = weakThis; co_await wil::resume_foreground(dispatcher); if (const auto tab{ weakThisCopy.get() }) @@ -1029,7 +1029,7 @@ namespace winrt::TerminalApp::implementation events.BellRequested = content.BellRequested( winrt::auto_revoke, - [dispatcher, weakThis](TerminalApp::IPaneContent sender, auto bellArgs) -> winrt::fire_and_forget { + [dispatcher, weakThis](TerminalApp::IPaneContent sender, auto bellArgs) -> safe_void_coroutine { const auto weakThisCopy = weakThis; co_await wil::resume_foreground(dispatcher); if (const auto tab{ weakThisCopy.get() }) diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp index 1865cf4547..9d08855a67 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.cpp +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -765,7 +765,7 @@ namespace winrt::TerminalApp::implementation // This may be called on a background thread, or the main thread, but almost // definitely not on OUR UI thread. - winrt::fire_and_forget TerminalWindow::UpdateSettings(winrt::TerminalApp::SettingsLoadEventArgs args) + safe_void_coroutine TerminalWindow::UpdateSettings(winrt::TerminalApp::SettingsLoadEventArgs args) { // GH#17620: We have a bug somewhere where a window doesn't get unregistered from the window list. // This causes UpdateSettings calls where the thread dispatcher is already null. diff --git a/src/cascadia/TerminalApp/TerminalWindow.h b/src/cascadia/TerminalApp/TerminalWindow.h index 40e94a3920..ee731f2ac3 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.h +++ b/src/cascadia/TerminalApp/TerminalWindow.h @@ -75,7 +75,7 @@ namespace winrt::TerminalApp::implementation void PersistState(); - winrt::fire_and_forget UpdateSettings(winrt::TerminalApp::SettingsLoadEventArgs args); + safe_void_coroutine UpdateSettings(winrt::TerminalApp::SettingsLoadEventArgs args); bool HasCommandlineArguments() const noexcept; diff --git a/src/cascadia/TerminalApp/TitlebarControl.cpp b/src/cascadia/TerminalApp/TitlebarControl.cpp index 76ff3cc7d6..1debd03054 100644 --- a/src/cascadia/TerminalApp/TitlebarControl.cpp +++ b/src/cascadia/TerminalApp/TitlebarControl.cpp @@ -134,7 +134,7 @@ namespace winrt::TerminalApp::implementation { MinMaxCloseControl().PressButton(button); } - winrt::fire_and_forget TitlebarControl::ClickButton(CaptionButton button) + safe_void_coroutine TitlebarControl::ClickButton(CaptionButton button) { // GH#8587: Handle this on the _next_ pass of the UI thread. If we // handle this immediately, then we'll accidentally leave the button in diff --git a/src/cascadia/TerminalApp/TitlebarControl.h b/src/cascadia/TerminalApp/TitlebarControl.h index 4dee4fb730..8020ff9095 100644 --- a/src/cascadia/TerminalApp/TitlebarControl.h +++ b/src/cascadia/TerminalApp/TitlebarControl.h @@ -13,7 +13,7 @@ namespace winrt::TerminalApp::implementation void HoverButton(CaptionButton button); void PressButton(CaptionButton button); - winrt::fire_and_forget ClickButton(CaptionButton button); + safe_void_coroutine ClickButton(CaptionButton button); void ReleaseButtons(); float CaptionButtonWidth(); diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index de5fb8c8be..9ef8abdc23 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -793,7 +793,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation // be awaiting our destruction breaks the deadlock. // Arguments: // - connection: the final living reference to an outgoing connection - winrt::fire_and_forget ConptyConnection::final_release(std::unique_ptr connection) + safe_void_coroutine ConptyConnection::final_release(std::unique_ptr connection) { co_await winrt::resume_background(); // move to background connection.reset(); // explicitly destruct diff --git a/src/cascadia/TerminalConnection/ConptyConnection.h b/src/cascadia/TerminalConnection/ConptyConnection.h index 083005e70e..ec11f4a709 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.h +++ b/src/cascadia/TerminalConnection/ConptyConnection.h @@ -18,7 +18,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation void Initialize(const Windows::Foundation::Collections::ValueSet& settings); void InitializeFromHandoff(HANDLE* in, HANDLE* out, HANDLE signal, HANDLE reference, HANDLE server, HANDLE client, const TERMINAL_STARTUP_INFO* startupInfo); - static winrt::fire_and_forget final_release(std::unique_ptr connection); + static safe_void_coroutine final_release(std::unique_ptr connection); void Start(); void WriteInput(const winrt::array_view buffer); diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 43d54f39b1..f433e69140 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1862,7 +1862,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation RendererWarning.raise(*this, winrt::make(hr, winrt::hstring{ parameter })); } - winrt::fire_and_forget ControlCore::_renderEngineSwapChainChanged(const HANDLE sourceHandle) + safe_void_coroutine ControlCore::_renderEngineSwapChainChanged(const HANDLE sourceHandle) { // `sourceHandle` is a weak ref to a HANDLE that's ultimately owned by the // render engine's own unique_handle. We'll add another ref to it here. @@ -2663,8 +2663,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation } } - winrt::fire_and_forget ControlCore::_terminalCompletionsChanged(std::wstring_view menuJson, - unsigned int replaceLength) + safe_void_coroutine ControlCore::_terminalCompletionsChanged(std::wstring_view menuJson, + unsigned int replaceLength) { auto args = winrt::make_self(winrt::hstring{ menuJson }, replaceLength); diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index d40fbcfca5..648cee1591 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -392,7 +392,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation const std::chrono::microseconds duration); void _terminalSearchMissingCommand(std::wstring_view missingCommand, const til::CoordType& bufferRow); - winrt::fire_and_forget _terminalCompletionsChanged(std::wstring_view menuJson, unsigned int replaceLength); + safe_void_coroutine _terminalCompletionsChanged(std::wstring_view menuJson, unsigned int replaceLength); #pragma endregion @@ -401,7 +401,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation #pragma region RendererCallbacks void _rendererWarning(const HRESULT hr, wil::zwstring_view parameter); - winrt::fire_and_forget _renderEngineSwapChainChanged(const HANDLE handle); + safe_void_coroutine _renderEngineSwapChainChanged(const HANDLE handle); void _rendererBackgroundColorChanged(); void _rendererTabColorChanged(); #pragma endregion diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index aa38206d10..765eb811fd 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -700,16 +700,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation this->Focus(FocusState::Programmatic); } - winrt::fire_and_forget TermControl::UpdateControlSettings(IControlSettings settings) + void TermControl::UpdateControlSettings(IControlSettings settings) { - return UpdateControlSettings(settings, _core.UnfocusedAppearance()); + UpdateControlSettings(settings, _core.UnfocusedAppearance()); } // Method Description: // - Given Settings having been updated, applies the settings to the current terminal. // Return Value: // - - winrt::fire_and_forget TermControl::UpdateControlSettings(IControlSettings settings, - IControlAppearance unfocusedAppearance) + safe_void_coroutine TermControl::UpdateControlSettings(IControlSettings settings, + IControlAppearance unfocusedAppearance) { auto weakThis{ get_weak() }; @@ -731,7 +731,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - Dispatches a call to the UI thread and updates the appearance // Arguments: // - newAppearance: the new appearance to set - winrt::fire_and_forget TermControl::UpdateAppearance(IControlAppearance newAppearance) + safe_void_coroutine TermControl::UpdateAppearance(IControlAppearance newAppearance) { auto weakThis{ get_weak() }; @@ -1012,8 +1012,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation // // Return Value: // - - winrt::fire_and_forget TermControl::_coreBackgroundColorChanged(const IInspectable& /*sender*/, - const IInspectable& /*args*/) + safe_void_coroutine TermControl::_coreBackgroundColorChanged(const IInspectable& /*sender*/, + const IInspectable& /*args*/) { auto weakThis{ get_weak() }; co_await wil::resume_foreground(Dispatcher()); @@ -1190,7 +1190,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - hr: an HRESULT describing the warning // Return Value: // - - winrt::fire_and_forget TermControl::_RendererWarning(IInspectable /*sender*/, Control::RendererWarningArgs args) + safe_void_coroutine TermControl::_RendererWarning(IInspectable /*sender*/, Control::RendererWarningArgs args) { auto weakThis{ get_weak() }; co_await wil::resume_foreground(Dispatcher()); @@ -1375,7 +1375,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation return true; } - winrt::fire_and_forget TermControl::_restoreInBackground() + safe_void_coroutine TermControl::_restoreInBackground() { const auto path = std::exchange(_restorePath, {}); const auto weakSelf = get_weak(); @@ -2101,8 +2101,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - // Return Value: // - - winrt::fire_and_forget TermControl::_coreTransparencyChanged(IInspectable /*sender*/, - Control::TransparencyChangedEventArgs /*args*/) + safe_void_coroutine TermControl::_coreTransparencyChanged(IInspectable /*sender*/, + Control::TransparencyChangedEventArgs /*args*/) { co_await wil::resume_foreground(Dispatcher()); try @@ -3073,8 +3073,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - e: The DragEventArgs from the Drop event // Return Value: // - - winrt::fire_and_forget TermControl::_DragDropHandler(Windows::Foundation::IInspectable /*sender*/, - DragEventArgs e) + safe_void_coroutine TermControl::_DragDropHandler(Windows::Foundation::IInspectable /*sender*/, + DragEventArgs e) { if (_IsClosing()) { @@ -3309,8 +3309,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - Checks if the uri is valid and sends an event if so // Arguments: // - The uri - winrt::fire_and_forget TermControl::_HyperlinkHandler(IInspectable /*sender*/, - Control::OpenHyperlinkEventArgs args) + safe_void_coroutine TermControl::_HyperlinkHandler(IInspectable /*sender*/, + Control::OpenHyperlinkEventArgs args) { // Save things we need to resume later. auto strongThis{ get_strong() }; @@ -3326,8 +3326,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation // Method Description: // - Produces the error dialog that notifies the user that rendering cannot proceed. - winrt::fire_and_forget TermControl::_RendererEnteredErrorState(IInspectable /*sender*/, - IInspectable /*args*/) + safe_void_coroutine TermControl::_RendererEnteredErrorState(IInspectable /*sender*/, + IInspectable /*args*/) { auto strongThis{ get_strong() }; co_await winrt::resume_foreground(Dispatcher()); // pop up onto the UI thread @@ -3550,7 +3550,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation OverlayCanvas().SetTop(HyperlinkTooltipBorder(), locationInDIPs.y - offset.y); } - winrt::fire_and_forget TermControl::_updateSelectionMarkers(IInspectable /*sender*/, Control::UpdateSelectionMarkersEventArgs args) + safe_void_coroutine TermControl::_updateSelectionMarkers(IInspectable /*sender*/, Control::UpdateSelectionMarkersEventArgs args) { auto weakThis{ get_weak() }; co_await resume_foreground(Dispatcher()); diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index c4fbdb40d9..caeadba45f 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -52,8 +52,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation static Control::TermControl NewControlByAttachingContent(Control::ControlInteractivity content, const Microsoft::Terminal::Control::IKeyBindings& keyBindings); - winrt::fire_and_forget UpdateControlSettings(Control::IControlSettings settings); - winrt::fire_and_forget UpdateControlSettings(Control::IControlSettings settings, Control::IControlAppearance unfocusedAppearance); + void UpdateControlSettings(Control::IControlSettings settings); + safe_void_coroutine UpdateControlSettings(Control::IControlSettings settings, Control::IControlAppearance unfocusedAppearance); IControlSettings Settings() const; uint64_t ContentId() const; @@ -132,11 +132,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation void RenderEngineSwapChainChanged(IInspectable sender, IInspectable args); void _AttachDxgiSwapChainToXaml(HANDLE swapChainHandle); - winrt::fire_and_forget _RendererEnteredErrorState(IInspectable sender, IInspectable args); + safe_void_coroutine _RendererEnteredErrorState(IInspectable sender, IInspectable args); void _RenderRetryButton_Click(const IInspectable& button, const IInspectable& args); - winrt::fire_and_forget _RendererWarning(IInspectable sender, - Control::RendererWarningArgs args); + safe_void_coroutine _RendererWarning(IInspectable sender, + Control::RendererWarningArgs args); void CreateSearchBoxControl(); @@ -342,11 +342,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation void _UpdateAppearanceFromUIThread(Control::IControlAppearance newAppearance); void _ApplyUISettings(); - winrt::fire_and_forget UpdateAppearance(Control::IControlAppearance newAppearance); + safe_void_coroutine UpdateAppearance(Control::IControlAppearance newAppearance); void _SetBackgroundImage(const IControlAppearance& newAppearance); void _InitializeBackgroundBrush(); - winrt::fire_and_forget _coreBackgroundColorChanged(const IInspectable& sender, const IInspectable& args); + safe_void_coroutine _coreBackgroundColorChanged(const IInspectable& sender, const IInspectable& args); void _changeBackgroundColor(til::color bg); static bool _isColorLight(til::color bg) noexcept; void _changeBackgroundOpacity(); @@ -357,7 +357,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation Reattach }; bool _InitializeTerminal(const InitializeReason reason); - winrt::fire_and_forget _restoreInBackground(); + safe_void_coroutine _restoreInBackground(); void _SetFontSize(int fontSize); void _TappedHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::TappedRoutedEventArgs& e); void _KeyDownHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::KeyRoutedEventArgs& e); @@ -376,10 +376,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation void _GotFocusHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); void _LostFocusHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); - winrt::fire_and_forget _DragDropHandler(Windows::Foundation::IInspectable sender, Windows::UI::Xaml::DragEventArgs e); + safe_void_coroutine _DragDropHandler(Windows::Foundation::IInspectable sender, Windows::UI::Xaml::DragEventArgs e); void _DragOverHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::DragEventArgs& e); - winrt::fire_and_forget _HyperlinkHandler(Windows::Foundation::IInspectable sender, Control::OpenHyperlinkEventArgs e); + safe_void_coroutine _HyperlinkHandler(Windows::Foundation::IInspectable sender, Control::OpenHyperlinkEventArgs e); void _CursorTimerTick(const Windows::Foundation::IInspectable& sender, const Windows::Foundation::IInspectable& e); void _BlinkTimerTick(const Windows::Foundation::IInspectable& sender, const Windows::Foundation::IInspectable& e); @@ -420,10 +420,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation void _handleSearchResults(SearchResults results); void _hoveredHyperlinkChanged(const IInspectable& sender, const IInspectable& args); - winrt::fire_and_forget _updateSelectionMarkers(IInspectable sender, Control::UpdateSelectionMarkersEventArgs args); + safe_void_coroutine _updateSelectionMarkers(IInspectable sender, Control::UpdateSelectionMarkersEventArgs args); void _coreFontSizeChanged(const IInspectable& s, const Control::FontSizeChangedArgs& args); - winrt::fire_and_forget _coreTransparencyChanged(IInspectable sender, Control::TransparencyChangedEventArgs args); + safe_void_coroutine _coreTransparencyChanged(IInspectable sender, Control::TransparencyChangedEventArgs args); void _coreRaisedNotice(const IInspectable& s, const Control::NoticeEventArgs& args); void _coreWarningBell(const IInspectable& sender, const IInspectable& args); void _coreOutputIdle(const IInspectable& sender, const IInspectable& args); diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.cpp b/src/cascadia/TerminalSettingsEditor/Appearances.cpp index fc9f1dc337..3fcbfb58a2 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.cpp +++ b/src/cascadia/TerminalSettingsEditor/Appearances.cpp @@ -1240,7 +1240,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } } - fire_and_forget Appearances::BackgroundImage_Click(const IInspectable&, const RoutedEventArgs&) + safe_void_coroutine Appearances::BackgroundImage_Click(const IInspectable&, const RoutedEventArgs&) { auto lifetime = get_strong(); diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.h b/src/cascadia/TerminalSettingsEditor/Appearances.h index 7ad8a03ce4..27a58073a8 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.h +++ b/src/cascadia/TerminalSettingsEditor/Appearances.h @@ -182,7 +182,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void FontFaceBox_SuggestionChosen(const winrt::Windows::UI::Xaml::Controls::AutoSuggestBox&, const winrt::Windows::UI::Xaml::Controls::AutoSuggestBoxSuggestionChosenEventArgs&); void FontFaceBox_TextChanged(const winrt::Windows::UI::Xaml::Controls::AutoSuggestBox&, const winrt::Windows::UI::Xaml::Controls::AutoSuggestBoxTextChangedEventArgs&); void DeleteFontKeyValuePair_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); - fire_and_forget BackgroundImage_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); + safe_void_coroutine BackgroundImage_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); void BIAlignment_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); // manually bind FontWeight diff --git a/src/cascadia/TerminalSettingsEditor/Profiles_Base.cpp b/src/cascadia/TerminalSettingsEditor/Profiles_Base.cpp index b181ef80c9..7b562cbca7 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles_Base.cpp +++ b/src/cascadia/TerminalSettingsEditor/Profiles_Base.cpp @@ -76,7 +76,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation winrt::get_self(_Profile)->DeleteProfile(); } - fire_and_forget Profiles_Base::Commandline_Click(const IInspectable&, const RoutedEventArgs&) + safe_void_coroutine Profiles_Base::Commandline_Click(const IInspectable&, const RoutedEventArgs&) { auto lifetime = get_strong(); @@ -106,7 +106,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } } - fire_and_forget Profiles_Base::Icon_Click(const IInspectable&, const RoutedEventArgs&) + safe_void_coroutine Profiles_Base::Icon_Click(const IInspectable&, const RoutedEventArgs&) { auto lifetime = get_strong(); @@ -118,7 +118,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } } - fire_and_forget Profiles_Base::StartingDirectory_Click(const IInspectable&, const RoutedEventArgs&) + safe_void_coroutine Profiles_Base::StartingDirectory_Click(const IInspectable&, const RoutedEventArgs&) { auto lifetime = get_strong(); const auto parentHwnd{ reinterpret_cast(_windowRoot.GetHostingWindow()) }; diff --git a/src/cascadia/TerminalSettingsEditor/Profiles_Base.h b/src/cascadia/TerminalSettingsEditor/Profiles_Base.h index 71cbd8d78d..9b507912f6 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles_Base.h +++ b/src/cascadia/TerminalSettingsEditor/Profiles_Base.h @@ -17,9 +17,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void OnNavigatedTo(const Windows::UI::Xaml::Navigation::NavigationEventArgs& e); void OnNavigatedFrom(const Windows::UI::Xaml::Navigation::NavigationEventArgs& e); - fire_and_forget StartingDirectory_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); - fire_and_forget Icon_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); - fire_and_forget Commandline_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); + safe_void_coroutine StartingDirectory_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); + safe_void_coroutine Icon_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); + safe_void_coroutine Commandline_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); void Appearance_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); void Advanced_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); void DeleteConfirmation_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp index 5a9fb2a736..d665176f2c 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp @@ -1074,7 +1074,7 @@ void CascadiaSettings::_refreshDefaultTerminals() std::pair, Model::DefaultTerminal> result{ {}, nullptr }; til::latch latch{ 1 }; - std::ignore = [&]() -> winrt::fire_and_forget { + std::ignore = [&]() -> safe_void_coroutine { const auto cleanup = wil::scope_exit([&]() { latch.count_down(); }); diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index 6febe041d6..f5cc10fced 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -78,7 +78,7 @@ static auto extractValueFromTaskWithoutMainThreadAwait(TTask&& task) -> decltype std::optional finalVal; til::latch latch{ 1 }; - const auto _ = [&]() -> winrt::fire_and_forget { + const auto _ = [&]() -> safe_void_coroutine { const auto cleanup = wil::scope_exit([&]() { latch.count_down(); }); diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 05f405d98d..9656d7d8fc 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -455,7 +455,7 @@ void AppHost::Close() } } -winrt::fire_and_forget AppHost::_quit() +safe_void_coroutine AppHost::_quit() { const auto peasant = _peasant; @@ -903,7 +903,7 @@ void AppHost::_WindowActivated(bool activated) } } -winrt::fire_and_forget AppHost::_peasantNotifyActivateWindow() +safe_void_coroutine AppHost::_peasantNotifyActivateWindow() { const auto desktopManager = _desktopManager; const auto peasant = _peasant; @@ -980,8 +980,8 @@ void AppHost::_HandleSummon(const winrt::Windows::Foundation::IInspectable& /*se // - // Return Value: // - -winrt::fire_and_forget AppHost::_IdentifyWindowsRequested(const winrt::Windows::Foundation::IInspectable /*sender*/, - const winrt::Windows::Foundation::IInspectable /*args*/) +safe_void_coroutine AppHost::_IdentifyWindowsRequested(const winrt::Windows::Foundation::IInspectable /*sender*/, + const winrt::Windows::Foundation::IInspectable /*args*/) { auto weakThis{ weak_from_this() }; @@ -1015,8 +1015,8 @@ void AppHost::_DisplayWindowId(const winrt::Windows::Foundation::IInspectable& / _windowLogic.IdentifyWindow(); } -winrt::fire_and_forget AppHost::_RenameWindowRequested(const winrt::Windows::Foundation::IInspectable /*sender*/, - const winrt::TerminalApp::RenameWindowRequestedArgs args) +safe_void_coroutine AppHost::_RenameWindowRequested(const winrt::Windows::Foundation::IInspectable /*sender*/, + const winrt::TerminalApp::RenameWindowRequestedArgs args) { // Switch to the BG thread - anything x-proc must happen on a BG thread co_await winrt::resume_background(); @@ -1350,8 +1350,8 @@ void AppHost::_PropertyChangedHandler(const winrt::Windows::Foundation::IInspect } } -winrt::fire_and_forget AppHost::_WindowInitializedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/, - const winrt::Windows::Foundation::IInspectable& /*arg*/) +safe_void_coroutine AppHost::_WindowInitializedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/, + const winrt::Windows::Foundation::IInspectable& /*arg*/) { _isWindowInitialized = WindowInitializedState::Initializing; diff --git a/src/cascadia/WindowsTerminal/AppHost.h b/src/cascadia/WindowsTerminal/AppHost.h index 1d3331d3ba..3991bc51b1 100644 --- a/src/cascadia/WindowsTerminal/AppHost.h +++ b/src/cascadia/WindowsTerminal/AppHost.h @@ -63,7 +63,7 @@ private: uint32_t _launchShowWindowCommand{ SW_NORMAL }; - winrt::fire_and_forget _quit(); + safe_void_coroutine _quit(); void _revokeWindowCallbacks(); void _HandleCommandlineArgs(const winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs& args); @@ -85,14 +85,14 @@ private: const winrt::Windows::Foundation::IInspectable& arg); void _AlwaysOnTopChanged(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& arg); - winrt::fire_and_forget _WindowInitializedHandler(const winrt::Windows::Foundation::IInspectable& sender, - const winrt::Windows::Foundation::IInspectable& arg); + safe_void_coroutine _WindowInitializedHandler(const winrt::Windows::Foundation::IInspectable& sender, + const winrt::Windows::Foundation::IInspectable& arg); void _RaiseVisualBell(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& arg); void _WindowMouseWheeled(const til::point coord, const int32_t delta); void _WindowActivated(bool activated); - winrt::fire_and_forget _peasantNotifyActivateWindow(); + safe_void_coroutine _peasantNotifyActivateWindow(); void _WindowMoved(); void _DispatchCommandline(winrt::Windows::Foundation::IInspectable sender, @@ -101,12 +101,12 @@ private: void _HandleSummon(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Microsoft::Terminal::Remoting::SummonWindowBehavior& args); - winrt::fire_and_forget _IdentifyWindowsRequested(const winrt::Windows::Foundation::IInspectable sender, - const winrt::Windows::Foundation::IInspectable args); + safe_void_coroutine _IdentifyWindowsRequested(const winrt::Windows::Foundation::IInspectable sender, + const winrt::Windows::Foundation::IInspectable args); void _DisplayWindowId(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args); - winrt::fire_and_forget _RenameWindowRequested(const winrt::Windows::Foundation::IInspectable sender, - const winrt::TerminalApp::RenameWindowRequestedArgs args); + safe_void_coroutine _RenameWindowRequested(const winrt::Windows::Foundation::IInspectable sender, + const winrt::TerminalApp::RenameWindowRequestedArgs args); void _HandleSettingsChanged(const winrt::Windows::Foundation::IInspectable& sender, const winrt::TerminalApp::SettingsLoadEventArgs& args); diff --git a/src/cascadia/WindowsTerminal/IslandWindow.cpp b/src/cascadia/WindowsTerminal/IslandWindow.cpp index 44e8fb3745..2bfe9d1dd9 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/IslandWindow.cpp @@ -1324,7 +1324,7 @@ void IslandWindow::_SetIsFullscreen(const bool fullscreenEnabled) // - toggleVisibility: controls how we should behave when already in the foreground. // Return Value: // - -winrt::fire_and_forget IslandWindow::SummonWindow(Remoting::SummonWindowBehavior args) +safe_void_coroutine IslandWindow::SummonWindow(Remoting::SummonWindowBehavior args) { // On the foreground thread: co_await wil::resume_foreground(_rootGrid.Dispatcher()); diff --git a/src/cascadia/WindowsTerminal/IslandWindow.h b/src/cascadia/WindowsTerminal/IslandWindow.h index ba9183847a..7a67211ad6 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.h +++ b/src/cascadia/WindowsTerminal/IslandWindow.h @@ -55,7 +55,7 @@ public: void FlashTaskbar(); void SetTaskbarProgress(const size_t state, const size_t progress); - winrt::fire_and_forget SummonWindow(winrt::Microsoft::Terminal::Remoting::SummonWindowBehavior args); + safe_void_coroutine SummonWindow(winrt::Microsoft::Terminal::Remoting::SummonWindowBehavior args); bool IsQuakeWindow() const noexcept; void IsQuakeWindow(bool isQuakeWindow) noexcept; diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.cpp b/src/cascadia/WindowsTerminal/WindowEmperor.cpp index 0891cf451a..ff22b8e53e 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.cpp +++ b/src/cascadia/WindowsTerminal/WindowEmperor.cpp @@ -479,7 +479,7 @@ LRESULT WindowEmperor::_messageHandler(UINT const message, WPARAM const wParam, // windows left, and we don't want to keep running anymore. This will discard // all our refrigerated windows. If we try to use XAML on Windows 10 after this, // we'll undoubtedly crash. -winrt::fire_and_forget WindowEmperor::_close() +safe_void_coroutine WindowEmperor::_close() { // Important! Switch back to the main thread for the emperor. That way, the // quit will go to the emperor's message pump. @@ -582,7 +582,7 @@ void WindowEmperor::_finalizeSessionPersistence() const // - args: Contains information on how we should name the window // Return Value: // - -static winrt::fire_and_forget _createNewTerminalWindow(Settings::Model::GlobalSummonArgs args) +static safe_void_coroutine _createNewTerminalWindow(Settings::Model::GlobalSummonArgs args) { // Hop to the BG thread co_await winrt::resume_background(); @@ -704,7 +704,7 @@ void WindowEmperor::_unregisterHotKey(const int index) noexcept LOG_IF_WIN32_BOOL_FALSE(::UnregisterHotKey(_window.get(), index)); } -winrt::fire_and_forget WindowEmperor::_setupGlobalHotkeys() +safe_void_coroutine WindowEmperor::_setupGlobalHotkeys() { // The hotkey MUST be registered on the main thread. It will fail otherwise! co_await wil::resume_foreground(_dispatcher); @@ -844,13 +844,13 @@ void WindowEmperor::_hideNotificationIconRequested() // A callback to the window's logic to let us know when the window's // quake mode state changes. We'll use this to check if we need to add // or remove the notification icon. -winrt::fire_and_forget WindowEmperor::_windowIsQuakeWindowChanged(winrt::Windows::Foundation::IInspectable sender, - winrt::Windows::Foundation::IInspectable args) +safe_void_coroutine WindowEmperor::_windowIsQuakeWindowChanged(winrt::Windows::Foundation::IInspectable sender, + winrt::Windows::Foundation::IInspectable args) { co_await wil::resume_foreground(this->_dispatcher); _checkWindowsForNotificationIcon(); } -winrt::fire_and_forget WindowEmperor::_windowRequestUpdateSettings() +safe_void_coroutine WindowEmperor::_windowRequestUpdateSettings() { // We MUST be on the main thread to update the settings. We will crash when trying to enumerate fragment extensions otherwise. co_await wil::resume_foreground(this->_dispatcher); diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.h b/src/cascadia/WindowsTerminal/WindowEmperor.h index d4186cbbb0..6a7f0a0fda 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.h +++ b/src/cascadia/WindowsTerminal/WindowEmperor.h @@ -61,17 +61,17 @@ private: void _becomeMonarch(); void _numberOfWindowsChanged(const winrt::Windows::Foundation::IInspectable&, const winrt::Windows::Foundation::IInspectable&); - winrt::fire_and_forget _windowIsQuakeWindowChanged(winrt::Windows::Foundation::IInspectable sender, winrt::Windows::Foundation::IInspectable args); - winrt::fire_and_forget _windowRequestUpdateSettings(); + safe_void_coroutine _windowIsQuakeWindowChanged(winrt::Windows::Foundation::IInspectable sender, winrt::Windows::Foundation::IInspectable args); + safe_void_coroutine _windowRequestUpdateSettings(); void _createMessageWindow(); void _hotkeyPressed(const long hotkeyIndex); bool _registerHotKey(const int index, const winrt::Microsoft::Terminal::Control::KeyChord& hotkey) noexcept; void _unregisterHotKey(const int index) noexcept; - winrt::fire_and_forget _setupGlobalHotkeys(); + safe_void_coroutine _setupGlobalHotkeys(); - winrt::fire_and_forget _close(); + safe_void_coroutine _close(); void _finalizeSessionPersistence() const; void _createNotificationIcon(); diff --git a/src/cascadia/inc/cppwinrt_utils.h b/src/cascadia/inc/cppwinrt_utils.h index 851156ef48..8276b7325e 100644 --- a/src/cascadia/inc/cppwinrt_utils.h +++ b/src/cascadia/inc/cppwinrt_utils.h @@ -17,6 +17,54 @@ Revision History: #pragma once +// This type is identical to winrt::fire_and_forget, but its unhandled_exception +// handler logs the exception instead of terminating the application. +// +// Ideally, we'd just use wil::com_task, but it currently crashes +// with an AV if an exception is thrown after the first suspension point. +struct safe_void_coroutine +{ +}; + +namespace std +{ + template + struct coroutine_traits + { + struct promise_type + { + safe_void_coroutine get_return_object() const noexcept + { + return {}; + } + + void return_void() const noexcept + { + } + + suspend_never initial_suspend() const noexcept + { + return {}; + } + + suspend_never final_suspend() const noexcept + { + return {}; + } + + void unhandled_exception() const noexcept + { + LOG_CAUGHT_EXCEPTION(); + // If you get here, an unhandled exception was thrown. + // In a Release build this would get silently swallowed. + // You should probably fix the source of the exception, because it may have + // unintended side effects, in particular with exception-unsafe logic. + assert(false); + } + }; + }; +} + template<> struct fmt::formatter : fmt::formatter { From ef960558b3e12cd1ae251d05917d3f285c0c35a3 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Fri, 23 Aug 2024 14:21:44 -0500 Subject: [PATCH 09/14] A trio of snippets pane fixes (#17794) 1. Don't crash on a cmdpal "duplicate pane" of a snippets pane * Found while trying to solve bug the third. * "Duplicate pane" with a snippets pane would crash. This was due to us attempting to `PreviewText` when there was no buffer yet. (`_activeBuffer()` strikes again) 2. dismiss the preview from cmdpal correctly too * Again while looking for part the third, I hit this * I have a `sendInput(input: "a")` command. This is the first command in the palette. And opening a new pane would... preview that command in the new pane? weird. Moving the line in `CommandPalette::_close` fixes this 3. Don't crash when we're restoring a snippets pane and there's a bunch of windows * This was the real bug I was trying to fix * Looks like if you have enough panes & windows, there's enough of a delay between ctoring a snippets pane and actually calling `_UpdateSettings` on it, that the XAML loads and tries to bind to `_allTasks`, which _hadn't been constructed yet_ * closes #17793 --- src/cascadia/TerminalApp/CommandPalette.cpp | 3 +-- src/cascadia/TerminalApp/SnippetsPaneContent.cpp | 5 +++-- src/cascadia/TerminalCore/Terminal.cpp | 6 ++++++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/cascadia/TerminalApp/CommandPalette.cpp b/src/cascadia/TerminalApp/CommandPalette.cpp index b54ee9a5c6..4817cd5242 100644 --- a/src/cascadia/TerminalApp/CommandPalette.cpp +++ b/src/cascadia/TerminalApp/CommandPalette.cpp @@ -1204,8 +1204,6 @@ namespace winrt::TerminalApp::implementation { Visibility(Visibility::Collapsed); - PreviewAction.raise(*this, nullptr); - // Reset visibility in case anchor mode tab switcher just finished. _searchBox().Visibility(Visibility::Visible); @@ -1216,6 +1214,7 @@ namespace winrt::TerminalApp::implementation ParentCommandName(L""); _currentNestedCommands.Clear(); + PreviewAction.raise(*this, nullptr); } void CommandPalette::EnableTabSwitcherMode(const uint32_t startIdx, TabSwitcherMode tabSwitcherMode) diff --git a/src/cascadia/TerminalApp/SnippetsPaneContent.cpp b/src/cascadia/TerminalApp/SnippetsPaneContent.cpp index 9e8b27db5e..415e5d8201 100644 --- a/src/cascadia/TerminalApp/SnippetsPaneContent.cpp +++ b/src/cascadia/TerminalApp/SnippetsPaneContent.cpp @@ -21,7 +21,8 @@ namespace winrt namespace winrt::TerminalApp::implementation { - SnippetsPaneContent::SnippetsPaneContent() + SnippetsPaneContent::SnippetsPaneContent() : + _allTasks{ winrt::single_threaded_observable_vector() } { InitializeComponent(); @@ -54,7 +55,7 @@ namespace winrt::TerminalApp::implementation const auto tasks = co_await _settings.GlobalSettings().ActionMap().FilterToSnippets(winrt::hstring{}, winrt::hstring{}); // IVector co_await wil::resume_foreground(Dispatcher()); - _allTasks = winrt::single_threaded_observable_vector(); + _allTasks.Clear(); for (const auto& t : tasks) { const auto& filtered{ winrt::make(t) }; diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index 165af0e3b0..b745d66f70 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -1615,6 +1615,12 @@ void Terminal::PreviewText(std::wstring_view input) static constexpr TextAttribute previewAttrs{ CharacterAttributes::Italics, TextColor{}, TextColor{}, 0u, TextColor{} }; auto lock = LockForWriting(); + + if (_mainBuffer == nullptr) + { + return; + } + if (input.empty()) { snippetPreview.text = L""; From cd8c12586b130bf9143a1f58d02ef0629d4cc4a5 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Fri, 23 Aug 2024 14:24:34 -0500 Subject: [PATCH 10/14] Clip the "current commandline" at the cursor position (#17781) This is particularly relevant to pwsh with the "ghost text" enabled. In that scenario, pwsh writes out the predicted command to the right of the cursor. With `showSuggestions(useCommandline=true)`, we'd auto-include that text in the filter, and that was effectively useless. This instead defaults us to not use anything to the right of the cursor (inclusive) for what we consider "the current commandline" closes #17772 --- .wt.json | 6 ++ src/buffer/out/textBuffer.cpp | 19 +++++-- src/buffer/out/textBuffer.hpp | 2 +- src/cascadia/TerminalControl/ControlCore.cpp | 5 +- .../UnitTests_Control/ControlCoreTests.cpp | 57 +++++++++++++++++++ src/host/ut_host/ScreenBufferTests.cpp | 4 ++ 6 files changed, 87 insertions(+), 6 deletions(-) diff --git a/.wt.json b/.wt.json index 44f718f42e..f0091b5e32 100644 --- a/.wt.json +++ b/.wt.json @@ -23,6 +23,12 @@ "name": "Upload package to nuget feed", "icon": "\uE898", "description": "Go download a .nupkg, put it in ~/Downloads, and use this to push to our private feed." + }, + { + "input": "runut /name:**\u001b[D", + "name": "Run a test", + "icon": "", + "description": "Enter the name of a test to run" } ] } diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index c4f38d85d9..9e1e70e3d0 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -3266,23 +3266,30 @@ MarkExtents TextBuffer::_scrollMarkExtentForRow(const til::CoordType rowOffset, return mark; } -std::wstring TextBuffer::_commandForRow(const til::CoordType rowOffset, const til::CoordType bottomInclusive) const +std::wstring TextBuffer::_commandForRow(const til::CoordType rowOffset, + const til::CoordType bottomInclusive, + const bool clipAtCursor) const { std::wstring commandBuilder; MarkKind lastMarkKind = MarkKind::Prompt; + const auto cursorPosition = GetCursor().GetPosition(); for (auto y = rowOffset; y <= bottomInclusive; y++) { + const bool onCursorRow = clipAtCursor && y == cursorPosition.y; // Now we need to iterate over text attributes. We need to find a // segment of Prompt attributes, we'll skip those. Then there should be // Command attributes. Collect up all of those, till we get to the next // Output attribute. - const auto& row = GetRowByOffset(y); const auto runs = row.Attributes().runs(); auto x = 0; for (const auto& [attr, length] : runs) { - const auto nextX = gsl::narrow_cast(x + length); + auto nextX = gsl::narrow_cast(x + length); + if (onCursorRow) + { + nextX = std::min(nextX, gsl::narrow_cast(cursorPosition.x)); + } const auto markKind{ attr.GetMarkAttributes() }; if (markKind != lastMarkKind) { @@ -3302,6 +3309,10 @@ std::wstring TextBuffer::_commandForRow(const til::CoordType rowOffset, const ti } // advance to next run of text x = nextX; + if (onCursorRow && x == cursorPosition.x) + { + return commandBuilder; + } } // we went over all the runs in this row, but we're not done yet. Keep iterating on the next row. } @@ -3325,7 +3336,7 @@ std::wstring TextBuffer::CurrentCommand() const // This row did start a prompt! Find the prompt that starts here. // Presumably, no rows below us will have prompts, so pass in the last // row with text as the bottom - return _commandForRow(promptY, _estimateOffsetOfLastCommittedRow()); + return _commandForRow(promptY, _estimateOffsetOfLastCommittedRow(), true); } return L""; } diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp index a6a4ea88f7..a39ebd4db4 100644 --- a/src/buffer/out/textBuffer.hpp +++ b/src/buffer/out/textBuffer.hpp @@ -326,7 +326,7 @@ private: til::point _GetWordEndForSelection(const til::point target, const std::wstring_view wordDelimiters) const; void _PruneHyperlinks(); - std::wstring _commandForRow(const til::CoordType rowOffset, const til::CoordType bottomInclusive) const; + std::wstring _commandForRow(const til::CoordType rowOffset, const til::CoordType bottomInclusive, const bool clipAtCursor = false) const; MarkExtents _scrollMarkExtentForRow(const til::CoordType rowOffset, const til::CoordType bottomInclusive) const; bool _createPromptMarkIfNeeded(); diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index f433e69140..9be62aad0c 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -2291,7 +2291,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation // If the very last thing in the list of recent commands, is exactly the // same as the current command, then let's not include it in the // history. It's literally the thing the user has typed, RIGHT now. - if (!commands.empty() && commands.back() == trimmedCurrentCommand) + // (also account for the fact that the cursor may be in the middle of a commandline) + if (!commands.empty() && + !trimmedCurrentCommand.empty() && + std::wstring_view{ commands.back() }.substr(0, trimmedCurrentCommand.size()) == trimmedCurrentCommand) { commands.pop_back(); } diff --git a/src/cascadia/UnitTests_Control/ControlCoreTests.cpp b/src/cascadia/UnitTests_Control/ControlCoreTests.cpp index 8c76135d4e..4620afae09 100644 --- a/src/cascadia/UnitTests_Control/ControlCoreTests.cpp +++ b/src/cascadia/UnitTests_Control/ControlCoreTests.cpp @@ -41,6 +41,8 @@ namespace ControlUnitTests TEST_METHOD(TestSelectCommandSimple); TEST_METHOD(TestSelectOutputSimple); TEST_METHOD(TestCommandContext); + TEST_METHOD(TestCommandContextWithPwshGhostText); + TEST_METHOD(TestSelectOutputScrolling); TEST_METHOD(TestSelectOutputExactWrap); @@ -556,6 +558,61 @@ namespace ControlUnitTests } } + void ControlCoreTests::TestCommandContextWithPwshGhostText() + { + auto [settings, conn] = _createSettingsAndConnection(); + Log::Comment(L"Create ControlCore object"); + auto core = createCore(*settings, *conn); + VERIFY_IS_NOT_NULL(core); + _standardInit(core); + + Log::Comment(L"Print some text"); + + _writePrompt(conn, L"C:\\Windows"); + conn->WriteInput(winrt_wstring_to_array_view(L"Foo-bar")); + conn->WriteInput(winrt_wstring_to_array_view(L"\x1b]133;C\x7")); + + conn->WriteInput(winrt_wstring_to_array_view(L"\r\n")); + conn->WriteInput(winrt_wstring_to_array_view(L"This is some text \r\n")); + conn->WriteInput(winrt_wstring_to_array_view(L"with varying amounts \r\n")); + conn->WriteInput(winrt_wstring_to_array_view(L"of whitespace \r\n")); + + _writePrompt(conn, L"C:\\Windows"); + + Log::Comment(L"Check the command context"); + + const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope; + { + auto historyContext{ core->CommandHistory() }; + VERIFY_ARE_EQUAL(1u, historyContext.History().Size()); + VERIFY_ARE_EQUAL(L"", historyContext.CurrentCommandline()); + } + + Log::Comment(L"Write 'BarBar' to the command..."); + conn->WriteInput(winrt_wstring_to_array_view(L"BarBar")); + { + auto historyContext{ core->CommandHistory() }; + // BarBar shouldn't be in the history, it should be the current command + VERIFY_ARE_EQUAL(1u, historyContext.History().Size()); + VERIFY_ARE_EQUAL(L"BarBar", historyContext.CurrentCommandline()); + } + + Log::Comment(L"then move the cursor to the left"); + // This emulates the state the buffer is in when pwsh does it's "ghost + // text" thing. We don't want to include all that ghost text in the + // current commandline. + conn->WriteInput(winrt_wstring_to_array_view(L"\x1b[D")); + conn->WriteInput(winrt_wstring_to_array_view(L"\x1b[D")); + { + auto historyContext{ core->CommandHistory() }; + VERIFY_ARE_EQUAL(1u, historyContext.History().Size()); + // The current commandline is only the text to the left of the cursor + auto curr{ historyContext.CurrentCommandline() }; + VERIFY_ARE_EQUAL(4u, curr.size()); + VERIFY_ARE_EQUAL(L"BarB", curr); + } + } + void ControlCoreTests::TestSelectOutputScrolling() { auto [settings, conn] = _createSettingsAndConnection(); diff --git a/src/host/ut_host/ScreenBufferTests.cpp b/src/host/ut_host/ScreenBufferTests.cpp index beaab2c304..f4aa82ac42 100644 --- a/src/host/ut_host/ScreenBufferTests.cpp +++ b/src/host/ut_host/ScreenBufferTests.cpp @@ -8337,6 +8337,10 @@ void ScreenBufferTests::SimpleMarkCommand() void ScreenBufferTests::SimpleWrappedCommand() { + BEGIN_TEST_METHOD_PROPERTIES() + TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method") + END_TEST_METHOD_PROPERTIES() + auto& g = ServiceLocator::LocateGlobals(); auto& gci = g.getConsoleInformation(); auto& si = gci.GetActiveOutputBuffer(); From 0a9cbd09d8665861d73dcdf5b8d8f40b9599acff Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Fri, 23 Aug 2024 12:28:19 -0700 Subject: [PATCH 11/14] Track and log changes to settings (#17678) Adds functionality throughout the settings model to keep track of which settings have been set. There are two entry points: - AppLogic.cpp: this is where we perform a settings reload by loading the JSON - MainPage.cpp: this is where the Save button is clicked in the settings UI Both of these entry points call into `CascadiaSettings::LogSettingChanges()` where we aggregate the list of changes (specifically, _which_ settings changed, not _what_ their value is). Just about all of the settings model objects now have a `LogSettingChanges(std::set& changes, std::string_view context)` on them. - `changes` is where we aggregate all of the changes to. In it being a set, we don't need to worry about duplicates and can do things like iterate across all of the profiles. - `context` prepends a string to the setting. This'll allow us to better identify where a setting was changes (i.e. "global.X" are global settings). We also use this to distinguish between settings set in the ~base layer~ profile defaults vs individual profiles. The change log in each object is modified via two ways: - `LayerJson()` changes: this is useful for detecting JSON changes! All we're doing is checking if the setting has a value (due to inheritance, just about everything is an optional here!). If the value is set, we add the json key to the change log - `INHERITABLE_SETTING_WITH_LOGGING` in IInheritable.h: we already use this macro to define getters and setters. This new macro updates the setter to check if the value was set to something different. If so, log it! Other notes: - We're not distinguishing between `defaultAppearance` and `unfocusedAppearance` - We are distinguishing between `profileDefaults` and `profile` (any other profile) - New Tab Menu Customization: - we really just care about the entry types. Handled in `GlobalAppSettings` - Font: - We still have support for legacy values here. We still want to track them, but just use the modern keys. - `Theme`: - We don't do inheritance here, so we have to approach it differently. During the JSON load, we log each setting. However, we don't have `LayerJson`! So instead, do the work in `CascadiaSettings` and store the changes there. Note that we don't track any changes made via setters. This is fine for now since themes aren't even in the settings UI, so we wouldn't get much use out of it anyways. - Actions: - Actions are weird because we can have nested and iterable actions too, but `ActionsAndArgs` as a whole add a ton of functionality. I handled it over in `Command::LogSettingChanges` and we generally just serialize it to JSON to get the keys. It's a lot easier than dealing with the object model. Epic: #10000 Auto-Save (ish): #12424 --- .github/actions/spelling/allow/allow.txt | 2 + src/cascadia/TerminalApp/AppLogic.cpp | 4 + .../TerminalSettingsEditor/MainPage.cpp | 1 + .../TerminalSettingsModel/ActionMap.cpp | 1 + .../TerminalSettingsModel/ActionMap.h | 3 + .../ActionMapSerialization.cpp | 34 +++++- .../AppearanceConfig.cpp | 38 ++++++- .../TerminalSettingsModel/AppearanceConfig.h | 5 + .../TerminalSettingsModel/CascadiaSettings.h | 5 + .../CascadiaSettings.idl | 1 + .../CascadiaSettingsSerialization.cpp | 78 +++++++++++++ .../TerminalSettingsModel/Command.cpp | 53 +++++++++ src/cascadia/TerminalSettingsModel/Command.h | 1 + .../TerminalSettingsModel/FontConfig.cpp | 50 ++++++++- .../TerminalSettingsModel/FontConfig.h | 5 + .../GlobalAppSettings.cpp | 106 +++++++++++++++++- .../TerminalSettingsModel/GlobalAppSettings.h | 8 +- .../TerminalSettingsModel/IInheritable.h | 21 ++++ .../TerminalSettingsModel/Profile.cpp | 69 +++++++++++- src/cascadia/TerminalSettingsModel/Profile.h | 7 +- src/cascadia/TerminalSettingsModel/Theme.cpp | 47 ++++++++ src/cascadia/TerminalSettingsModel/Theme.h | 2 +- 22 files changed, 531 insertions(+), 10 deletions(-) diff --git a/.github/actions/spelling/allow/allow.txt b/.github/actions/spelling/allow/allow.txt index ef4f666f47..3b54d30497 100644 --- a/.github/actions/spelling/allow/allow.txt +++ b/.github/actions/spelling/allow/allow.txt @@ -27,6 +27,7 @@ gje godbolt hyperlinking hyperlinks +Kbds kje libfuzzer liga @@ -43,6 +44,7 @@ mkmk mnt mru nje +NTMTo notwrapped ogonek overlined diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 18b9a1ffea..812a1cf1b7 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -432,6 +432,10 @@ namespace winrt::TerminalApp::implementation return; } } + else + { + _settings.LogSettingChanges(true); + } if (initialLoad) { diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.cpp b/src/cascadia/TerminalSettingsEditor/MainPage.cpp index ea98ee83f1..76bbeacb24 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.cpp +++ b/src/cascadia/TerminalSettingsEditor/MainPage.cpp @@ -481,6 +481,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void MainPage::SaveButton_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*args*/) { + _settingsClone.LogSettingChanges(false); _settingsClone.WriteSettingsToDisk(); } diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 3d80220f79..bebe9ac76b 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -570,6 +570,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation const auto action = cmd.ActionAndArgs().Action(); const auto id = action == ShortcutAction::Invalid ? hstring{} : cmd.ID(); _KeyMap.insert_or_assign(keys, id); + _changeLog.emplace(KeysKey); } // Method Description: diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index e3ceb3f518..8da139a30d 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -73,6 +73,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation Json::Value ToJson() const; Json::Value KeyBindingsToJson() const; bool FixupsAppliedDuringLoad() const; + void LogSettingChanges(std::set& changes, const std::string_view& context) const; // modification bool RebindKeys(const Control::KeyChord& oldKeys, const Control::KeyChord& newKeys); @@ -138,6 +139,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation til::shared_mutex>> _cwdLocalSnippetsCache{}; + std::set _changeLog; + friend class SettingsModelUnitTests::KeyBindingsTests; friend class SettingsModelUnitTests::DeserializationTests; friend class SettingsModelUnitTests::TerminalSettingsTests; diff --git a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp index b6216971ce..11b6fe649b 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp @@ -78,7 +78,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // Now check if this is a command block if (jsonBlock.isMember(JsonKey(CommandsKey)) || jsonBlock.isMember(JsonKey(ActionKey))) { - AddAction(*Command::FromJson(jsonBlock, warnings, origin), keys); + auto command = Command::FromJson(jsonBlock, warnings, origin); + command->LogSettingChanges(_changeLog); + AddAction(*command, keys); if (jsonBlock.isMember(JsonKey(KeysKey))) { @@ -105,6 +107,28 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // any existing keybinding with the same keychord in this layer will get overwritten _KeyMap.insert_or_assign(keys, idJson); + + if (!_changeLog.contains(KeysKey.data())) + { + // Log "keys" field, but only if it's one that isn't in userDefaults.json + static constexpr std::array, 3> userDefaultKbds{ { { L"Terminal.CopyToClipboard", "ctrl+c" }, + { L"Terminal.PasteFromClipboard", "ctrl+v" }, + { L"Terminal.DuplicatePaneAuto", "alt+shift+d" } } }; + bool isUserDefaultKbd = false; + for (const auto& [id, kbd] : userDefaultKbds) + { + const auto keyJson{ jsonBlock.find(&*KeysKey.cbegin(), (&*KeysKey.cbegin()) + KeysKey.size()) }; + if (idJson == id && keyJson->asString() == kbd) + { + isUserDefaultKbd = true; + break; + } + } + if (!isUserDefaultKbd) + { + _changeLog.emplace(KeysKey); + } + } } } @@ -156,4 +180,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return keybindingsList; } + + void ActionMap::LogSettingChanges(std::set& changes, const std::string_view& context) const + { + for (const auto& setting : _changeLog) + { + changes.emplace(fmt::format(FMT_COMPILE("{}.{}"), context, setting)); + } + } } diff --git a/src/cascadia/TerminalSettingsModel/AppearanceConfig.cpp b/src/cascadia/TerminalSettingsModel/AppearanceConfig.cpp index f513f66fa2..4abfc97f5d 100644 --- a/src/cascadia/TerminalSettingsModel/AppearanceConfig.cpp +++ b/src/cascadia/TerminalSettingsModel/AppearanceConfig.cpp @@ -90,27 +90,42 @@ Json::Value AppearanceConfig::ToJson() const void AppearanceConfig::LayerJson(const Json::Value& json) { JsonUtils::GetValueForKey(json, ForegroundKey, _Foreground); + _logSettingIfSet(ForegroundKey, _Foreground.has_value()); + JsonUtils::GetValueForKey(json, BackgroundKey, _Background); + _logSettingIfSet(BackgroundKey, _Background.has_value()); + JsonUtils::GetValueForKey(json, SelectionBackgroundKey, _SelectionBackground); + _logSettingIfSet(SelectionBackgroundKey, _SelectionBackground.has_value()); + JsonUtils::GetValueForKey(json, CursorColorKey, _CursorColor); + _logSettingIfSet(CursorColorKey, _CursorColor.has_value()); JsonUtils::GetValueForKey(json, LegacyAcrylicTransparencyKey, _Opacity); JsonUtils::GetValueForKey(json, OpacityKey, _Opacity, JsonUtils::OptionalConverter{}); + _logSettingIfSet(OpacityKey, _Opacity.has_value()); + if (json["colorScheme"].isString()) { // to make the UI happy, set ColorSchemeName. JsonUtils::GetValueForKey(json, ColorSchemeKey, _DarkColorSchemeName); _LightColorSchemeName = _DarkColorSchemeName; + _logSettingSet(ColorSchemeKey); } else if (json["colorScheme"].isObject()) { // to make the UI happy, set ColorSchemeName to whatever the dark value is. JsonUtils::GetValueForKey(json["colorScheme"], "dark", _DarkColorSchemeName); JsonUtils::GetValueForKey(json["colorScheme"], "light", _LightColorSchemeName); + + _logSettingSet("colorScheme.dark"); + _logSettingSet("colorScheme.light"); } #define APPEARANCE_SETTINGS_LAYER_JSON(type, name, jsonKey, ...) \ - JsonUtils::GetValueForKey(json, jsonKey, _##name); + JsonUtils::GetValueForKey(json, jsonKey, _##name); \ + _logSettingIfSet(jsonKey, _##name.has_value()); + MTSM_APPEARANCE_SETTINGS(APPEARANCE_SETTINGS_LAYER_JSON) #undef APPEARANCE_SETTINGS_LAYER_JSON } @@ -156,3 +171,24 @@ winrt::hstring AppearanceConfig::ExpandedBackgroundImagePath() return winrt::hstring{ wil::ExpandEnvironmentStringsW(path.c_str()) }; } } + +void AppearanceConfig::_logSettingSet(const std::string_view& setting) +{ + _changeLog.emplace(setting); +} + +void AppearanceConfig::_logSettingIfSet(const std::string_view& setting, const bool isSet) +{ + if (isSet) + { + _logSettingSet(setting); + } +} + +void AppearanceConfig::LogSettingChanges(std::set& changes, const std::string_view& context) const +{ + for (const auto& setting : _changeLog) + { + changes.emplace(fmt::format(FMT_COMPILE("{}.{}"), context, setting)); + } +} diff --git a/src/cascadia/TerminalSettingsModel/AppearanceConfig.h b/src/cascadia/TerminalSettingsModel/AppearanceConfig.h index cffbdede6d..87beb7ea1b 100644 --- a/src/cascadia/TerminalSettingsModel/AppearanceConfig.h +++ b/src/cascadia/TerminalSettingsModel/AppearanceConfig.h @@ -31,6 +31,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation static winrt::com_ptr CopyAppearance(const AppearanceConfig* source, winrt::weak_ref sourceProfile); Json::Value ToJson() const; void LayerJson(const Json::Value& json); + void LogSettingChanges(std::set& changes, const std::string_view& context) const; Model::Profile SourceProfile(); @@ -52,5 +53,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation private: winrt::weak_ref _sourceProfile; + std::set _changeLog; + + void _logSettingSet(const std::string_view& setting); + void _logSettingIfSet(const std::string_view& setting, const bool isSet); }; } diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h index becc6749c9..75eb9ee96e 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h @@ -48,6 +48,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation std::unordered_map> colorSchemes; std::unordered_map colorSchemeRemappings; bool fixupsAppliedDuringLoad{ false }; + std::set themesChangeLog; void clear(); }; @@ -96,6 +97,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation void _executeGenerator(const IDynamicProfileGenerator& generator); std::unordered_set _ignoredNamespaces; + std::set themesChangeLog; // See _getNonUserOriginProfiles(). size_t _userProfileCount = 0; }; @@ -150,6 +152,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation void ExpandCommands(); + void LogSettingChanges(bool isJsonLoad) const; + private: static const std::filesystem::path& _settingsPath(); static const std::filesystem::path& _releaseSettingsPath(); @@ -180,6 +184,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation winrt::com_ptr _baseLayerProfile = winrt::make_self(); winrt::Windows::Foundation::Collections::IObservableVector _allProfiles = winrt::single_threaded_observable_vector(); winrt::Windows::Foundation::Collections::IObservableVector _activeProfiles = winrt::single_threaded_observable_vector(); + std::set _themesChangeLog{}; // load errors winrt::Windows::Foundation::Collections::IVector _warnings = winrt::single_threaded_vector(); diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl b/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl index 0d8c0c5b15..2fa41941d6 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl @@ -24,6 +24,7 @@ namespace Microsoft.Terminal.Settings.Model CascadiaSettings Copy(); void WriteSettingsToDisk(); + void LogSettingChanges(Boolean isJsonLoad); String Hash { get; }; diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index f5cc10fced..5ec7275b05 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -109,6 +109,7 @@ void ParsedSettings::clear() profilesByGuid.clear(); colorSchemes.clear(); fixupsAppliedDuringLoad = false; + themesChangeLog.clear(); } // This is a convenience method used by the CascadiaSettings constructor. @@ -658,6 +659,12 @@ void SettingsLoader::_parse(const OriginTag origin, const winrt::hstring& source // versions of these themes overriding the built-in ones. continue; } + + if (origin != OriginTag::InBox) + { + static std::string themesContext{ "themes" }; + theme->LogSettingChanges(settings.themesChangeLog, themesContext); + } settings.globals->AddTheme(*theme); } } @@ -1222,6 +1229,7 @@ CascadiaSettings::CascadiaSettings(SettingsLoader&& loader) : _allProfiles = winrt::single_threaded_observable_vector(std::move(allProfiles)); _activeProfiles = winrt::single_threaded_observable_vector(std::move(activeProfiles)); _warnings = winrt::single_threaded_vector(std::move(warnings)); + _themesChangeLog = std::move(loader.userSettings.themesChangeLog); _resolveDefaultProfile(); _resolveNewTabMenuProfiles(); @@ -1596,3 +1604,73 @@ void CascadiaSettings::_resolveNewTabMenuProfilesSet(const IVector changes; + static constexpr std::string_view globalContext{ "global" }; + _globals->LogSettingChanges(changes, globalContext); + + // Actions are not expected to change when loaded from the settings UI + static constexpr std::string_view actionContext{ "action" }; + winrt::get_self(_globals->ActionMap())->LogSettingChanges(changes, actionContext); + + static constexpr std::string_view profileContext{ "profile" }; + for (const auto& profile : _allProfiles) + { + winrt::get_self(profile)->LogSettingChanges(changes, profileContext); + } + + static constexpr std::string_view profileDefaultsContext{ "profileDefaults" }; + _baseLayerProfile->LogSettingChanges(changes, profileDefaultsContext); + + // Themes are not expected to change when loaded from the settings UI + // DO NOT CALL Theme::LogSettingChanges!! + // We already collected the changes when we loaded the JSON + for (const auto& change : _themesChangeLog) + { + changes.insert(change); + } + + // report changes + for (const auto& change : changes) + { +#ifndef _DEBUG + // A `isJsonLoad ? "JsonSettingsChanged" : "UISettingsChanged"` + // would be nice, but that apparently isn't allowed in the macro below. + // Also, there's guidance to not send too much data all in one event, + // so we'll be sending a ton of events here. + if (isJsonLoad) + { + TraceLoggingWrite(g_hSettingsModelProvider, + "JsonSettingsChanged", + TraceLoggingDescription("Event emitted when settings.json change"), + TraceLoggingValue(change.data()), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); + } + else + { + TraceLoggingWrite(g_hSettingsModelProvider, + "UISettingsChanged", + TraceLoggingDescription("Event emitted when settings change via the UI"), + TraceLoggingValue(change.data()), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); + } +#else + OutputDebugStringA(isJsonLoad ? "JsonSettingsChanged - " : "UISettingsChanged - "); + OutputDebugStringA(change.data()); + OutputDebugStringA("\n"); +#endif // !_DEBUG + } +} diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 730d883463..2f4cc17f70 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -741,4 +741,57 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return winrt::single_threaded_vector(std::move(result)); } + + void Command::LogSettingChanges(std::set& changes) + { + if (_IterateOn != ExpandCommandType::None) + { + switch (_IterateOn) + { + case ExpandCommandType::Profiles: + changes.emplace(fmt::format(FMT_COMPILE("{}.{}"), IterateOnKey, "profiles")); + break; + case ExpandCommandType::ColorSchemes: + changes.emplace(fmt::format(FMT_COMPILE("{}.{}"), IterateOnKey, "schemes")); + break; + } + } + + if (!_Description.empty()) + { + changes.emplace(DescriptionKey); + } + + if (IsNestedCommand()) + { + changes.emplace(CommandsKey); + } + else + { + const auto json{ ActionAndArgs::ToJson(ActionAndArgs()) }; + if (json.isString()) + { + // covers actions w/out args + // - "command": "unbound" --> "unbound" + // - "command": "copy" --> "copy" + changes.emplace(fmt::format(FMT_COMPILE("{}"), json.asString())); + } + else + { + // covers actions w/ args + // - "command": { "action": "copy", "singleLine": true } --> "copy.singleLine" + // - "command": { "action": "copy", "singleLine": true, "dismissSelection": true } --> "copy.singleLine", "copy.dismissSelection" + + const std::string shortcutActionName{ json[JsonKey("action")].asString() }; + + auto members = json.getMemberNames(); + members.erase(std::remove_if(members.begin(), members.end(), [](const auto& member) { return member == "action"; }), members.end()); + + for (const auto& actionArg : members) + { + changes.emplace(fmt::format(FMT_COMPILE("{}.{}"), shortcutActionName, actionArg)); + } + } + } + } } diff --git a/src/cascadia/TerminalSettingsModel/Command.h b/src/cascadia/TerminalSettingsModel/Command.h index 1bc92220ca..4d372bea9c 100644 --- a/src/cascadia/TerminalSettingsModel/Command.h +++ b/src/cascadia/TerminalSettingsModel/Command.h @@ -61,6 +61,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation const Json::Value& json, const OriginTag origin); Json::Value ToJson() const; + void LogSettingChanges(std::set& changes); bool HasNestedCommands() const; bool IsNestedCommand() const noexcept; diff --git a/src/cascadia/TerminalSettingsModel/FontConfig.cpp b/src/cascadia/TerminalSettingsModel/FontConfig.cpp index 746ceee649..0fe0da5439 100644 --- a/src/cascadia/TerminalSettingsModel/FontConfig.cpp +++ b/src/cascadia/TerminalSettingsModel/FontConfig.cpp @@ -83,17 +83,25 @@ void FontConfig::LayerJson(const Json::Value& json) { // A font object is defined, use that const auto fontInfoJson = json[JsonKey(FontInfoKey)]; -#define FONT_SETTINGS_LAYER_JSON(type, name, jsonKey, ...) \ - JsonUtils::GetValueForKey(fontInfoJson, jsonKey, _##name); +#define FONT_SETTINGS_LAYER_JSON(type, name, jsonKey, ...) \ + JsonUtils::GetValueForKey(fontInfoJson, jsonKey, _##name); \ + _logSettingIfSet(jsonKey, _##name.has_value()); + MTSM_FONT_SETTINGS(FONT_SETTINGS_LAYER_JSON) #undef FONT_SETTINGS_LAYER_JSON } else { // No font object is defined + // Log settings as if they were a part of the font object JsonUtils::GetValueForKey(json, LegacyFontFaceKey, _FontFace); + _logSettingIfSet("face", _FontFace.has_value()); + JsonUtils::GetValueForKey(json, LegacyFontSizeKey, _FontSize); + _logSettingIfSet("size", _FontSize.has_value()); + JsonUtils::GetValueForKey(json, LegacyFontWeightKey, _FontWeight); + _logSettingIfSet("weight", _FontWeight.has_value()); } } @@ -101,3 +109,41 @@ winrt::Microsoft::Terminal::Settings::Model::Profile FontConfig::SourceProfile() { return _sourceProfile.get(); } + +void FontConfig::_logSettingSet(const std::string_view& setting) +{ + if (setting == "axes" && _FontAxes.has_value()) + { + for (const auto& [mapKey, _] : _FontAxes.value()) + { + _changeLog.emplace(fmt::format(FMT_COMPILE("{}.{}"), setting, til::u16u8(mapKey))); + } + } + else if (setting == "features" && _FontFeatures.has_value()) + { + for (const auto& [mapKey, _] : _FontFeatures.value()) + { + _changeLog.emplace(fmt::format(FMT_COMPILE("{}.{}"), setting, til::u16u8(mapKey))); + } + } + else + { + _changeLog.emplace(setting); + } +} + +void FontConfig::_logSettingIfSet(const std::string_view& setting, const bool isSet) +{ + if (isSet) + { + _logSettingSet(setting); + } +} + +void FontConfig::LogSettingChanges(std::set& changes, const std::string_view& context) const +{ + for (const auto& setting : _changeLog) + { + changes.emplace(fmt::format(FMT_COMPILE("{}.{}"), context, setting)); + } +} diff --git a/src/cascadia/TerminalSettingsModel/FontConfig.h b/src/cascadia/TerminalSettingsModel/FontConfig.h index c249f5a1b4..99d503d56d 100644 --- a/src/cascadia/TerminalSettingsModel/FontConfig.h +++ b/src/cascadia/TerminalSettingsModel/FontConfig.h @@ -35,6 +35,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation static winrt::com_ptr CopyFontInfo(const FontConfig* source, winrt::weak_ref sourceProfile); Json::Value ToJson() const; void LayerJson(const Json::Value& json); + void LogSettingChanges(std::set& changes, const std::string_view& context) const; Model::Profile SourceProfile(); @@ -45,5 +46,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation private: winrt::weak_ref _sourceProfile; + std::set _changeLog; + + void _logSettingSet(const std::string_view& setting); + void _logSettingIfSet(const std::string_view& setting, const bool isSet); }; } diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index f51ece15e1..e7f930c175 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp @@ -128,13 +128,16 @@ winrt::com_ptr GlobalAppSettings::FromJson(const Json::Value& void GlobalAppSettings::LayerJson(const Json::Value& json, const OriginTag origin) { JsonUtils::GetValueForKey(json, DefaultProfileKey, _UnparsedDefaultProfile); + // GH#8076 - when adding enum values to this key, we also changed it from // "useTabSwitcher" to "tabSwitcherMode". Continue supporting // "useTabSwitcher", but prefer "tabSwitcherMode" JsonUtils::GetValueForKey(json, LegacyUseTabSwitcherModeKey, _TabSwitcherMode); #define GLOBAL_SETTINGS_LAYER_JSON(type, name, jsonKey, ...) \ - JsonUtils::GetValueForKey(json, jsonKey, _##name); + JsonUtils::GetValueForKey(json, jsonKey, _##name); \ + _logSettingIfSet(jsonKey, _##name.has_value()); + MTSM_GLOBAL_SETTINGS(GLOBAL_SETTINGS_LAYER_JSON) #undef GLOBAL_SETTINGS_LAYER_JSON @@ -152,6 +155,25 @@ void GlobalAppSettings::LayerJson(const Json::Value& json, const OriginTag origi LayerActionsFrom(json, origin, true); JsonUtils::GetValueForKey(json, LegacyReloadEnvironmentVariablesKey, _legacyReloadEnvironmentVariables); + if (json[LegacyReloadEnvironmentVariablesKey.data()]) + { + _logSettingSet(LegacyReloadEnvironmentVariablesKey); + } + + // Remove settings included in userDefaults + static constexpr std::array, 2> userDefaultSettings{ { { "copyOnSelect", "false" }, + { "copyFormatting", "false" } } }; + for (const auto& [setting, val] : userDefaultSettings) + { + if (const auto settingJson{ json.find(&*setting.cbegin(), (&*setting.cbegin()) + setting.size()) }) + { + if (settingJson->asString() == val) + { + // false positive! + _changeLog.erase(std::string{ setting }); + } + } + } } void GlobalAppSettings::LayerActionsFrom(const Json::Value& json, const OriginTag origin, const bool withKeybindings) @@ -317,3 +339,85 @@ bool GlobalAppSettings::ShouldUsePersistedLayout() const { return FirstWindowPreference() == FirstWindowPreference::PersistedWindowLayout && !IsolatedMode(); } + +void GlobalAppSettings::_logSettingSet(const std::string_view& setting) +{ + if (setting == "theme") + { + if (_Theme.has_value()) + { + // ThemePair always has a Dark/Light value, + // so we need to check if they were explicitly set + if (_Theme->DarkName() == _Theme->LightName()) + { + _changeLog.emplace(setting); + } + else + { + _changeLog.emplace(fmt::format(FMT_COMPILE("{}.{}"), setting, "dark")); + _changeLog.emplace(fmt::format(FMT_COMPILE("{}.{}"), setting, "light")); + } + } + } + else if (setting == "newTabMenu") + { + if (_NewTabMenu.has_value()) + { + for (const auto& entry : *_NewTabMenu) + { + std::string entryType; + switch (entry.Type()) + { + case NewTabMenuEntryType::Profile: + entryType = "profile"; + break; + case NewTabMenuEntryType::Separator: + entryType = "separator"; + break; + case NewTabMenuEntryType::Folder: + entryType = "folder"; + break; + case NewTabMenuEntryType::RemainingProfiles: + entryType = "remainingProfiles"; + break; + case NewTabMenuEntryType::MatchProfiles: + entryType = "matchProfiles"; + break; + case NewTabMenuEntryType::Action: + entryType = "action"; + break; + case NewTabMenuEntryType::Invalid: + // ignore invalid + continue; + } + _changeLog.emplace(fmt::format(FMT_COMPILE("{}.{}"), setting, entryType)); + } + } + } + else + { + _changeLog.emplace(setting); + } +} + +void GlobalAppSettings::_logSettingIfSet(const std::string_view& setting, const bool isSet) +{ + if (isSet) + { + // Exclude some false positives from userDefaults.json + const bool settingCopyFormattingToDefault = til::equals_insensitive_ascii(setting, "copyFormatting") && _CopyFormatting.has_value() && _CopyFormatting.value() == static_cast(0); + const bool settingNTMToDefault = til::equals_insensitive_ascii(setting, "newTabMenu") && _NewTabMenu.has_value() && _NewTabMenu->Size() == 1 && _NewTabMenu->GetAt(0).Type() == NewTabMenuEntryType::RemainingProfiles; + if (!settingCopyFormattingToDefault && !settingNTMToDefault) + { + _logSettingSet(setting); + } + } +} + +void GlobalAppSettings::LogSettingChanges(std::set& changes, const std::string_view& context) const +{ + for (const auto& setting : _changeLog) + { + changes.emplace(fmt::format(FMT_COMPILE("{}.{}"), context, setting)); + } +} diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h index 7b55b7007b..fe60347cfc 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h @@ -72,10 +72,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation bool LegacyReloadEnvironmentVariables() const noexcept { return _legacyReloadEnvironmentVariables; } + void LogSettingChanges(std::set& changes, const std::string_view& context) const; + INHERITABLE_SETTING(Model::GlobalAppSettings, hstring, UnparsedDefaultProfile, L""); #define GLOBAL_SETTINGS_INITIALIZE(type, name, jsonKey, ...) \ - INHERITABLE_SETTING(Model::GlobalAppSettings, type, name, ##__VA_ARGS__) + INHERITABLE_SETTING_WITH_LOGGING(Model::GlobalAppSettings, type, name, jsonKey, ##__VA_ARGS__) MTSM_GLOBAL_SETTINGS(GLOBAL_SETTINGS_INITIALIZE) #undef GLOBAL_SETTINGS_INITIALIZE @@ -89,9 +91,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation winrt::guid _defaultProfile{}; bool _legacyReloadEnvironmentVariables{ true }; winrt::com_ptr _actionMap{ winrt::make_self() }; + std::set _changeLog; std::vector _keybindingsWarnings; Windows::Foundation::Collections::IMap _colorSchemes{ winrt::single_threaded_map() }; Windows::Foundation::Collections::IMap _themes{ winrt::single_threaded_map() }; + + void _logSettingSet(const std::string_view& setting); + void _logSettingIfSet(const std::string_view& setting, const bool isSet); }; } diff --git a/src/cascadia/TerminalSettingsModel/IInheritable.h b/src/cascadia/TerminalSettingsModel/IInheritable.h index 091cd653a1..1099c128bc 100644 --- a/src/cascadia/TerminalSettingsModel/IInheritable.h +++ b/src/cascadia/TerminalSettingsModel/IInheritable.h @@ -185,6 +185,27 @@ public: \ _##name = value; \ } +#define INHERITABLE_SETTING_WITH_LOGGING(projectedType, type, name, jsonKey, ...) \ + _BASE_INHERITABLE_SETTING(projectedType, std::optional, name, ...) \ +public: \ + /* Returns the resolved value for this setting */ \ + /* fallback: user set value --> inherited value --> system set value */ \ + type name() const \ + { \ + const auto val{ _get##name##Impl() }; \ + return val ? *val : type{ __VA_ARGS__ }; \ + } \ + \ + /* Overwrite the user set value */ \ + void name(const type& value) \ + { \ + if (!_##name.has_value() || _##name.value() != value) \ + { \ + _logSettingSet(jsonKey); \ + } \ + _##name = value; \ + } + // This macro is similar to the one above, but is reserved for optional settings // like Profile.Foreground (where null is interpreted // as an acceptable value, rather than "inherit") diff --git a/src/cascadia/TerminalSettingsModel/Profile.cpp b/src/cascadia/TerminalSettingsModel/Profile.cpp index 66382c857b..3d1d278247 100644 --- a/src/cascadia/TerminalSettingsModel/Profile.cpp +++ b/src/cascadia/TerminalSettingsModel/Profile.cpp @@ -175,15 +175,22 @@ void Profile::LayerJson(const Json::Value& json) JsonUtils::GetValueForKey(json, NameKey, _Name); JsonUtils::GetValueForKey(json, UpdatesKey, _Updates); JsonUtils::GetValueForKey(json, GuidKey, _Guid); - JsonUtils::GetValueForKey(json, HiddenKey, _Hidden); + + // Make sure Source is before Hidden! We use that to exclude false positives from the settings logger! JsonUtils::GetValueForKey(json, SourceKey, _Source); + JsonUtils::GetValueForKey(json, HiddenKey, _Hidden); + _logSettingIfSet(HiddenKey, _Hidden.has_value()); + JsonUtils::GetValueForKey(json, IconKey, _Icon); + _logSettingIfSet(IconKey, _Icon.has_value()); // Padding was never specified as an integer, but it was a common working mistake. // Allow it to be permissive. JsonUtils::GetValueForKey(json, PaddingKey, _Padding, JsonUtils::OptionalConverter>{}); + _logSettingIfSet(PaddingKey, _Padding.has_value()); JsonUtils::GetValueForKey(json, TabColorKey, _TabColor); + _logSettingIfSet(TabColorKey, _TabColor.has_value()); // Try to load some legacy keys, to migrate them. // Done _before_ the MTSM_PROFILE_SETTINGS, which have the updated keys. @@ -191,7 +198,8 @@ void Profile::LayerJson(const Json::Value& json) JsonUtils::GetValueForKey(json, LegacyAutoMarkPromptsKey, _AutoMarkPrompts); #define PROFILE_SETTINGS_LAYER_JSON(type, name, jsonKey, ...) \ - JsonUtils::GetValueForKey(json, jsonKey, _##name); + JsonUtils::GetValueForKey(json, jsonKey, _##name); \ + _logSettingIfSet(jsonKey, _##name.has_value()); MTSM_PROFILE_SETTINGS(PROFILE_SETTINGS_LAYER_JSON) #undef PROFILE_SETTINGS_LAYER_JSON @@ -208,6 +216,8 @@ void Profile::LayerJson(const Json::Value& json) unfocusedAppearance->LayerJson(json[JsonKey(UnfocusedAppearanceKey)]); _UnfocusedAppearance = *unfocusedAppearance; + + _logSettingSet(UnfocusedAppearanceKey); } } @@ -518,3 +528,58 @@ std::wstring Profile::NormalizeCommandLine(LPCWSTR commandLine) return normalized; } + +void Profile::_logSettingSet(const std::string_view& setting) +{ + _changeLog.emplace(setting); +} + +void Profile::_logSettingIfSet(const std::string_view& setting, const bool isSet) +{ + if (isSet) + { + // make sure this matches defaults.json. + static constexpr winrt::guid DEFAULT_WINDOWS_POWERSHELL_GUID{ 0x61c54bbd, 0xc2c6, 0x5271, { 0x96, 0xe7, 0x00, 0x9a, 0x87, 0xff, 0x44, 0xbf } }; + static constexpr winrt::guid DEFAULT_COMMAND_PROMPT_GUID{ 0x0caa0dad, 0x35be, 0x5f56, { 0xa8, 0xff, 0xaf, 0xce, 0xee, 0xaa, 0x61, 0x01 } }; + + // Exclude some false positives from userDefaults.json + // NOTE: we can't use the OriginTag here because it hasn't been set yet! + const bool isWinPow = _Guid.has_value() && *_Guid == DEFAULT_WINDOWS_POWERSHELL_GUID; //_Name.has_value() && til::equals_insensitive_ascii(*_Name, L"Windows PowerShell"); + const bool isCmd = _Guid.has_value() && *_Guid == DEFAULT_COMMAND_PROMPT_GUID; //_Name.has_value() && til::equals_insensitive_ascii(*_Name, L"Command Prompt"); + const bool isACS = _Name.has_value() && til::equals_insensitive_ascii(*_Name, L"Azure Cloud Shell"); + const bool isWTDynamicProfile = _Source.has_value() && til::starts_with(*_Source, L"Windows.Terminal"); + const bool settingHiddenToFalse = til::equals_insensitive_ascii(setting, HiddenKey) && _Hidden.has_value() && _Hidden == false; + const bool settingCommandlineToWinPow = til::equals_insensitive_ascii(setting, "commandline") && _Commandline.has_value() && til::equals_insensitive_ascii(*_Commandline, L"%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"); + const bool settingCommandlineToCmd = til::equals_insensitive_ascii(setting, "commandline") && _Commandline.has_value() && til::equals_insensitive_ascii(*_Commandline, L"%SystemRoot%\\System32\\cmd.exe"); + // clang-format off + if (!(isWinPow && (settingHiddenToFalse || settingCommandlineToWinPow)) + && !(isCmd && (settingHiddenToFalse || settingCommandlineToCmd)) + && !(isACS && settingHiddenToFalse) + && !(isWTDynamicProfile && settingHiddenToFalse)) + { + // clang-format on + _logSettingSet(setting); + } + } +} + +void Profile::LogSettingChanges(std::set& changes, const std::string_view& context) const +{ + for (const auto& setting : _changeLog) + { + changes.emplace(fmt::format(FMT_COMPILE("{}.{}"), context, setting)); + } + + std::string fontContext{ fmt::format(FMT_COMPILE("{}.{}"), context, FontInfoKey) }; + winrt::get_self(_FontInfo)->LogSettingChanges(changes, fontContext); + + // We don't want to distinguish between "profile.defaultAppearance.*" and "profile.unfocusedAppearance.*" settings, + // but we still want to aggregate all of the appearance settings from both appearances. + // Log them as "profile.appearance.*" + std::string appContext{ fmt::format(FMT_COMPILE("{}.{}"), context, "appearance") }; + winrt::get_self(_DefaultAppearance)->LogSettingChanges(changes, appContext); + if (_UnfocusedAppearance) + { + winrt::get_self(*_UnfocusedAppearance)->LogSettingChanges(changes, appContext); + } +} diff --git a/src/cascadia/TerminalSettingsModel/Profile.h b/src/cascadia/TerminalSettingsModel/Profile.h index 6fdd5c3ed5..24fbb7d1f2 100644 --- a/src/cascadia/TerminalSettingsModel/Profile.h +++ b/src/cascadia/TerminalSettingsModel/Profile.h @@ -108,6 +108,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation void _FinalizeInheritance() override; + void LogSettingChanges(std::set& changes, const std::string_view& context) const; + // Special fields hstring Icon() const; void Icon(const hstring& value); @@ -131,7 +133,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation public: #define PROFILE_SETTINGS_INITIALIZE(type, name, jsonKey, ...) \ - INHERITABLE_SETTING(Model::Profile, type, name, ##__VA_ARGS__) + INHERITABLE_SETTING_WITH_LOGGING(Model::Profile, type, name, jsonKey, ##__VA_ARGS__) MTSM_PROFILE_SETTINGS(PROFILE_SETTINGS_INITIALIZE) #undef PROFILE_SETTINGS_INITIALIZE @@ -140,12 +142,15 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation Model::FontConfig _FontInfo{ winrt::make(weak_ref(*this)) }; std::optional _evaluatedIcon{ std::nullopt }; + std::set _changeLog; static std::wstring EvaluateStartingDirectory(const std::wstring& directory); static guid _GenerateGuidForProfile(const std::wstring_view& name, const std::wstring_view& source) noexcept; winrt::hstring _evaluateIcon() const; + void _logSettingSet(const std::string_view& setting); + void _logSettingIfSet(const std::string_view& setting, const bool isSet); friend class SettingsModelUnitTests::DeserializationTests; friend class SettingsModelUnitTests::ProfileTests; diff --git a/src/cascadia/TerminalSettingsModel/Theme.cpp b/src/cascadia/TerminalSettingsModel/Theme.cpp index 52c283a743..90fd1d1a76 100644 --- a/src/cascadia/TerminalSettingsModel/Theme.cpp +++ b/src/cascadia/TerminalSettingsModel/Theme.cpp @@ -292,6 +292,53 @@ winrt::com_ptr Theme::FromJson(const Json::Value& json) return result; } +void Theme::LogSettingChanges(std::set& changes, const std::string_view& context) +{ +#pragma warning(push) +#pragma warning(disable : 5103) // pasting '{' and 'winrt' does not result in a valid preprocessing token + +#define GENERATE_SET_CHECK_AND_JSON_KEYS(type, name, jsonKey, ...) \ + const bool is##name##Set = _##name != nullptr; \ + std::string_view outer##name##JsonKey = jsonKey; + + MTSM_THEME_SETTINGS(GENERATE_SET_CHECK_AND_JSON_KEYS) + +#define LOG_IF_SET(type, name, jsonKey, ...) \ + if (obj.name() != type{##__VA_ARGS__ }) \ + changes.emplace(fmt::format(FMT_COMPILE("{}.{}.{}"), context, outerJsonKey, jsonKey)); + + if (isWindowSet) + { + const auto obj = _Window; + const auto outerJsonKey = outerWindowJsonKey; + MTSM_THEME_WINDOW_SETTINGS(LOG_IF_SET) + } + + if (isSettingsSet) + { + const auto obj = _Settings; + const auto outerJsonKey = outerSettingsJsonKey; + MTSM_THEME_SETTINGS_SETTINGS(LOG_IF_SET) + } + + if (isTabRowSet) + { + const auto obj = _TabRow; + const auto outerJsonKey = outerTabRowJsonKey; + MTSM_THEME_TABROW_SETTINGS(LOG_IF_SET) + } + + if (isTabSet) + { + const auto obj = _Tab; + const auto outerJsonKey = outerTabJsonKey; + MTSM_THEME_TAB_SETTINGS(LOG_IF_SET) + } +#undef LOG_IF_SET +#undef GENERATE_SET_CHECK_AND_JSON_KEYS +#pragma warning(pop) +} + // Method Description: // - Create a new serialized JsonObject from an instance of this class // Arguments: diff --git a/src/cascadia/TerminalSettingsModel/Theme.h b/src/cascadia/TerminalSettingsModel/Theme.h index 2c0ac139aa..e0892733ae 100644 --- a/src/cascadia/TerminalSettingsModel/Theme.h +++ b/src/cascadia/TerminalSettingsModel/Theme.h @@ -97,8 +97,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation hstring ToString(); static com_ptr FromJson(const Json::Value& json); - void LayerJson(const Json::Value& json); Json::Value ToJson() const; + void LogSettingChanges(std::set& changes, const std::string_view& context); winrt::Windows::UI::Xaml::ElementTheme RequestedTheme() const noexcept; From 0a91023df85ed37b838a661cc229be49f1fa0839 Mon Sep 17 00:00:00 2001 From: Windows Console Service Bot <14666831+consvc@users.noreply.github.com> Date: Fri, 23 Aug 2024 14:30:13 -0500 Subject: [PATCH 12/14] Localization Updates - main - 08/22/2024 03:06:16 (#17767) --- .../Resources/de-DE/Resources.resw | 19 ++++++++----------- .../Resources/es-ES/Resources.resw | 17 +++++++---------- .../Resources/fr-FR/Resources.resw | 19 ++++++++----------- .../Resources/it-IT/Resources.resw | 19 ++++++++----------- .../Resources/ja-JP/Resources.resw | 17 +++++++---------- .../Resources/ko-KR/Resources.resw | 15 ++++++--------- .../Resources/pt-BR/Resources.resw | 19 ++++++++----------- .../Resources/qps-ploc/Resources.resw | 19 ++++++++----------- .../Resources/qps-ploca/Resources.resw | 19 ++++++++----------- .../Resources/qps-plocm/Resources.resw | 19 ++++++++----------- .../Resources/ru-RU/Resources.resw | 19 ++++++++----------- .../Resources/zh-CN/Resources.resw | 19 ++++++++----------- .../Resources/zh-TW/Resources.resw | 19 ++++++++----------- 13 files changed, 100 insertions(+), 139 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/Resources/de-DE/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/de-DE/Resources.resw index df03095d72..69b12a1f3b 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/de-DE/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/de-DE/Resources.resw @@ -118,13 +118,13 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Farbschema auswählen + Farbschema auswählen... - Neue Registerkarte + Neue Registerkarte... - Bereich teilen + Bereich teilen... Bereich "Codeausschnitte" öffnen @@ -329,7 +329,7 @@ {Locked="JSON"}. "JSON" is the extension of the file that will be opened. - Registerkartenfarbe festlegen + Registerkartenfarbe festlegen... Einfügen @@ -351,7 +351,7 @@ Den Titel der Registerkarte zurücksetzen - Registerkartentitel umbenennen + Registerkarte umbenennen Bereich skalieren @@ -445,7 +445,7 @@ Zur letzten Registerkarte wechseln - Registerkarte suchen + Registerkarte suchen... Modus "Immer im Vordergrund" umschalten @@ -453,9 +453,6 @@ Befehlspalette ein/aus - - Zuletzt verwendete Befehle - Vorschläge öffnen @@ -519,7 +516,7 @@ Fensternamen zurücksetzen - Fenster umbenennen + Fenster umbenennen... Aktuelles Arbeitsverzeichnis des Terminals anzeigen @@ -570,7 +567,7 @@ Terminal beenden - Hintergrunddeckkraft festlegen + Hintergrunddeckkraft festlegen... Hintergrunddeckkraft um {0} % erhöhen diff --git a/src/cascadia/TerminalSettingsModel/Resources/es-ES/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/es-ES/Resources.resw index 8384f26787..3f3300b6a2 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/es-ES/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/es-ES/Resources.resw @@ -118,13 +118,13 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Seleccionar combinación de colores + Seleccionar combinación de colores... - Nueva pestaña + Nueva pestaña... - Dividir panel + Dividir panel... Apertura del panel de fragmentos @@ -329,7 +329,7 @@ {Locked="JSON"}. "JSON" is the extension of the file that will be opened. - Establecer el color de la pestaña + Establecer color de pestaña... Pegar @@ -351,7 +351,7 @@ Restablecer título de la pestaña - Cambiar título de la pestaña + Cambiar el nombre de la pestaña Cambiar el tamaño del panel @@ -445,7 +445,7 @@ Cambiar a la última pestaña - Buscar pestaña + Buscar pestañas... Alternar siempre en el modo superior @@ -453,9 +453,6 @@ Alternar paleta de comandos - - Comandos recientes - Abrir sugerencias @@ -570,7 +567,7 @@ Salir del terminal - Establecer la opacidad del fondo + Establecer la opacidad del fondo... Aumentar la opacidad del fondo en un {0} % diff --git a/src/cascadia/TerminalSettingsModel/Resources/fr-FR/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/fr-FR/Resources.resw index 72b556f66f..8924fb98cb 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/fr-FR/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/fr-FR/Resources.resw @@ -118,13 +118,13 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Sélectionner un modèle de couleurs + Sélectionner le modèle de couleurs... - Nouvel onglet + Nouvel onglet... - Fractionner le volet + Fractionner le volet... Ouvrir le volet des extraits de code @@ -329,7 +329,7 @@ {Locked="JSON"}. "JSON" is the extension of the file that will be opened. - Définir la couleur de l’onglet + Définir la couleur de l’onglet... Coller @@ -351,7 +351,7 @@ Rétablir le titre de l’onglet - Renommer le titre de l'onglet + Renommer l'onglet Redimensionner le volet @@ -445,7 +445,7 @@ Basculer vers le dernier onglet - Recherche de l’onglet + Rechercher l’onglet... Activer le mode Toujours visible @@ -453,9 +453,6 @@ Activer/désactiver la palette de commandes - - Commandes récentes - Ouvrir les suggestions @@ -519,7 +516,7 @@ Réinitialiser le nom de la fenêtre - Fenêtre pour renommer + Renommer la fenêtre... Afficher le répertoire de travail actuel du terminal @@ -570,7 +567,7 @@ Quitter le terminal - Définir l’opacité de l’arrière-plan + Définir l’opacité de l’arrière-plan... Augmenter l’opacité de l’arrière-plan de {0} % diff --git a/src/cascadia/TerminalSettingsModel/Resources/it-IT/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/it-IT/Resources.resw index d9521f4bee..8ef5d1a50a 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/it-IT/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/it-IT/Resources.resw @@ -118,13 +118,13 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Seleziona combinazione colori + Seleziona combinazione colori... - Nuova scheda + Nuova scheda... - Suddividi riquadro + Suddividi riquadro... Apri riquadro frammenti di codice @@ -329,7 +329,7 @@ {Locked="JSON"}. "JSON" is the extension of the file that will be opened. - Imposta il colore della scheda + Imposta il colore della scheda... Incolla @@ -351,7 +351,7 @@ Reimposta il titolo della scheda - Rinomina il titolo della scheda + Rinomina scheda Ridimensiona riquadro @@ -445,7 +445,7 @@ Passa all'ultima scheda - Cerca scheda + Cerca scheda... Modalità attiva/disattiva sempre in alto @@ -453,9 +453,6 @@ Attiva/disattiva riquadro dei comandi - - Comandi recenti - Apri i suggerimenti @@ -519,7 +516,7 @@ Reimposta nome finestra - Rinomina finestra + Rinomina finestra... Visualizza la directory di lavoro corrente del terminale @@ -570,7 +567,7 @@ Esci da Terminale - Imposta l'opacità di sfondo + Imposta l'opacità di sfondo... Aumenta l'opacità di sfondo del {0}% diff --git a/src/cascadia/TerminalSettingsModel/Resources/ja-JP/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/ja-JP/Resources.resw index dd1b4fd460..ff28cda964 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/ja-JP/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/ja-JP/Resources.resw @@ -118,13 +118,13 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - 配色パターンの選択 + 配色を選択... - 新しいタブ + 新しいタブ... - ウィンドウを分割する + ウィンドウの分割... スニペット ウィンドウを開く @@ -329,7 +329,7 @@ {Locked="JSON"}. "JSON" is the extension of the file that will be opened. - タブの色の設定 + タブの色を設定... 貼り付け @@ -351,7 +351,7 @@ タブのタイトルをリセット - タブ タイトル名の変更 + [名前の変更] タブ ウィンドウのサイズの変更する @@ -445,7 +445,7 @@ 最後のタブに切り替える - タブの検索 + タブを検索... 常に手前に表示するモードに切り替える @@ -453,9 +453,6 @@ コマンド パレットに切り替える - - 最近使ったコマンド - 候補を開く @@ -570,7 +567,7 @@ ターミナルの終了 - 背景の不透明度の設定 + 背景の不透明度を設定... 背景の不透明度を {0}% 上げる diff --git a/src/cascadia/TerminalSettingsModel/Resources/ko-KR/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/ko-KR/Resources.resw index 320017c8f4..b211bfd292 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/ko-KR/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/ko-KR/Resources.resw @@ -118,13 +118,13 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - 색 구성표 선택 + 색 구성표 선택... - 새 탭 + 새 탭... - 분할 창 + 분할 창... 코드 조각 창 열기 @@ -329,7 +329,7 @@ {Locked="JSON"}. "JSON" is the extension of the file that will be opened. - 탭 색 설정 + 탭 색 설정... 붙여넣기 @@ -351,7 +351,7 @@ 탭 제목 재설정 - 탭 제목 이름 바꾸기 + 탭 이름 바꾸기 창 크기 조정 @@ -453,9 +453,6 @@ 토글 명령 팔레트 - - 최근 명령 - 제안 사항 열기 @@ -570,7 +567,7 @@ 터미널 종료 - 배경 불투명도 설정 + 배경 불투명도 설정... 배경 불투명도를 {0}% 증가 diff --git a/src/cascadia/TerminalSettingsModel/Resources/pt-BR/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/pt-BR/Resources.resw index 331df68181..569cc37af9 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/pt-BR/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/pt-BR/Resources.resw @@ -118,13 +118,13 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Selecionar esquema de cores + Selecionar esquema de cores... - Nova guia + Nova guia... - Dividir painel + Dividir painel... Abrir painel de snippets @@ -329,7 +329,7 @@ {Locked="JSON"}. "JSON" is the extension of the file that will be opened. - Definir a cor da guia + Definir cor da guia... Colar @@ -351,7 +351,7 @@ Redefinir título da guia - Renomear título da guia + Renomear guia Redimensionar painel @@ -445,7 +445,7 @@ Alternar para a última guia - Pesquisar guia + Buscar guia... Alternar sempre no modo superior @@ -453,9 +453,6 @@ Ativar/desativar paleta de comandos - - Comandos recentes - Abrir sugestões @@ -519,7 +516,7 @@ Definir nome da janela - Renomear janela + Renomear janela... Exibir o diretório de trabalho atual do Terminal @@ -570,7 +567,7 @@ Saia do Terminal - Definir opacidade da tela de fundo + Definir a opacidade da tela de fundo... Aumentar a opacidade da tela de fundo em {0}% diff --git a/src/cascadia/TerminalSettingsModel/Resources/qps-ploc/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/qps-ploc/Resources.resw index a5b7ea83e1..4f95d0529f 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/qps-ploc/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/qps-ploc/Resources.resw @@ -118,13 +118,13 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Ŝêĺĕćţ ćŏĺбŕ ѕćнêmē !!! !!! + Ŝêĺĕćţ ćŏĺбŕ ѕćнêmē... !!! !!! - Ŋéŵ тάъ !! + Ŋéŵ тάъ... !!! - Śрĺíŧ ρāлë !!! + Śрĺíŧ ρāлë... !!! Òρëй ѕήїρφέţś ρªʼné !!! !! @@ -329,7 +329,7 @@ {Locked="JSON"}. "JSON" is the extension of the file that will be opened. - Şėŧ ŧнę ťáь ċσŀőґ !!! !! + Şėŧ ŧăь ĉõľοґ... !!! ! Ρášτé ! @@ -351,7 +351,7 @@ Řėś℮ŧ тαв τįтℓę !!! ! - Гęйªm℮ тåъ ŧīτļĕ !!! ! + Гęйªm℮ тåъ !!! Ґëśîžє рãле !!! @@ -445,7 +445,7 @@ Ѕẃітčћ ţõ ťħέ ľάšť ţаь !!! !!! - Ѕėàřĉħ ƒôґ ŧâь !!! ! + Ѕėàřĉħ ƒôґ ŧâь... !!! !! Ŧōĝġļě αŀώªÿŝ òⁿ тοр мöðέ !!! !!! ! @@ -453,9 +453,6 @@ Ţōĝğļė čσmmάήđ рåŀęŧŧз !!! !!! - - Γë¢єйť ćøмmåηđŝ !!! ! - Ǿрέʼn şµġġеšŧìŏπѕ !!! ! @@ -519,7 +516,7 @@ Ŗεšзť ωĩйδōẁ ñâмé !!! !! - Ґёʼnάmë шϊйďθŵ !!! + Ґёʼnάmë шϊйďθŵ... !!! ! Ďіŝρłάỳ Τ℮ѓmìйаĺ'š čûŗяēʼnτ ώоřκìņĝ ďιяęĉтσґỳ !!! !!! !!! !!! ! @@ -570,7 +567,7 @@ Qυϊτ ŧħз Τέяmίŋăŀ !!! !! - Ѕěт ьªċķĝґøūņð óφǻĉїτў !!! !!! + Ѕěт ьªċķĝґøūņð óφǻĉїτў... !!! !!! ! Ĭñçŕ℮àŝę ваċкġřŏųлď ǿφāςíτу ьỳ {0}% !!! !!! !!! ! diff --git a/src/cascadia/TerminalSettingsModel/Resources/qps-ploca/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/qps-ploca/Resources.resw index a5b7ea83e1..4f95d0529f 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/qps-ploca/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/qps-ploca/Resources.resw @@ -118,13 +118,13 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Ŝêĺĕćţ ćŏĺбŕ ѕćнêmē !!! !!! + Ŝêĺĕćţ ćŏĺбŕ ѕćнêmē... !!! !!! - Ŋéŵ тάъ !! + Ŋéŵ тάъ... !!! - Śрĺíŧ ρāлë !!! + Śрĺíŧ ρāлë... !!! Òρëй ѕήїρφέţś ρªʼné !!! !! @@ -329,7 +329,7 @@ {Locked="JSON"}. "JSON" is the extension of the file that will be opened. - Şėŧ ŧнę ťáь ċσŀőґ !!! !! + Şėŧ ŧăь ĉõľοґ... !!! ! Ρášτé ! @@ -351,7 +351,7 @@ Řėś℮ŧ тαв τįтℓę !!! ! - Гęйªm℮ тåъ ŧīτļĕ !!! ! + Гęйªm℮ тåъ !!! Ґëśîžє рãле !!! @@ -445,7 +445,7 @@ Ѕẃітčћ ţõ ťħέ ľάšť ţаь !!! !!! - Ѕėàřĉħ ƒôґ ŧâь !!! ! + Ѕėàřĉħ ƒôґ ŧâь... !!! !! Ŧōĝġļě αŀώªÿŝ òⁿ тοр мöðέ !!! !!! ! @@ -453,9 +453,6 @@ Ţōĝğļė čσmmάήđ рåŀęŧŧз !!! !!! - - Γë¢єйť ćøмmåηđŝ !!! ! - Ǿрέʼn şµġġеšŧìŏπѕ !!! ! @@ -519,7 +516,7 @@ Ŗεšзť ωĩйδōẁ ñâмé !!! !! - Ґёʼnάmë шϊйďθŵ !!! + Ґёʼnάmë шϊйďθŵ... !!! ! Ďіŝρłάỳ Τ℮ѓmìйаĺ'š čûŗяēʼnτ ώоřκìņĝ ďιяęĉтσґỳ !!! !!! !!! !!! ! @@ -570,7 +567,7 @@ Qυϊτ ŧħз Τέяmίŋăŀ !!! !! - Ѕěт ьªċķĝґøūņð óφǻĉїτў !!! !!! + Ѕěт ьªċķĝґøūņð óφǻĉїτў... !!! !!! ! Ĭñçŕ℮àŝę ваċкġřŏųлď ǿφāςíτу ьỳ {0}% !!! !!! !!! ! diff --git a/src/cascadia/TerminalSettingsModel/Resources/qps-plocm/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/qps-plocm/Resources.resw index a5b7ea83e1..4f95d0529f 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/qps-plocm/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/qps-plocm/Resources.resw @@ -118,13 +118,13 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Ŝêĺĕćţ ćŏĺбŕ ѕćнêmē !!! !!! + Ŝêĺĕćţ ćŏĺбŕ ѕćнêmē... !!! !!! - Ŋéŵ тάъ !! + Ŋéŵ тάъ... !!! - Śрĺíŧ ρāлë !!! + Śрĺíŧ ρāлë... !!! Òρëй ѕήїρφέţś ρªʼné !!! !! @@ -329,7 +329,7 @@ {Locked="JSON"}. "JSON" is the extension of the file that will be opened. - Şėŧ ŧнę ťáь ċσŀőґ !!! !! + Şėŧ ŧăь ĉõľοґ... !!! ! Ρášτé ! @@ -351,7 +351,7 @@ Řėś℮ŧ тαв τįтℓę !!! ! - Гęйªm℮ тåъ ŧīτļĕ !!! ! + Гęйªm℮ тåъ !!! Ґëśîžє рãле !!! @@ -445,7 +445,7 @@ Ѕẃітčћ ţõ ťħέ ľάšť ţаь !!! !!! - Ѕėàřĉħ ƒôґ ŧâь !!! ! + Ѕėàřĉħ ƒôґ ŧâь... !!! !! Ŧōĝġļě αŀώªÿŝ òⁿ тοр мöðέ !!! !!! ! @@ -453,9 +453,6 @@ Ţōĝğļė čσmmάήđ рåŀęŧŧз !!! !!! - - Γë¢єйť ćøмmåηđŝ !!! ! - Ǿрέʼn şµġġеšŧìŏπѕ !!! ! @@ -519,7 +516,7 @@ Ŗεšзť ωĩйδōẁ ñâмé !!! !! - Ґёʼnάmë шϊйďθŵ !!! + Ґёʼnάmë шϊйďθŵ... !!! ! Ďіŝρłάỳ Τ℮ѓmìйаĺ'š čûŗяēʼnτ ώоřκìņĝ ďιяęĉтσґỳ !!! !!! !!! !!! ! @@ -570,7 +567,7 @@ Qυϊτ ŧħз Τέяmίŋăŀ !!! !! - Ѕěт ьªċķĝґøūņð óφǻĉїτў !!! !!! + Ѕěт ьªċķĝґøūņð óφǻĉїτў... !!! !!! ! Ĭñçŕ℮àŝę ваċкġřŏųлď ǿφāςíτу ьỳ {0}% !!! !!! !!! ! diff --git a/src/cascadia/TerminalSettingsModel/Resources/ru-RU/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/ru-RU/Resources.resw index 9baa9036bb..2076dac9a4 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/ru-RU/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/ru-RU/Resources.resw @@ -118,13 +118,13 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Выбрать цветовую схему + Выбрать цветовую схему... - Новая вкладка + Новая вкладка... - Разделить область + Разделить область... Открыть панель фрагментов @@ -329,7 +329,7 @@ {Locked="JSON"}. "JSON" is the extension of the file that will be opened. - Задать цвет вкладки + Установить цвет вкладки... Вставить @@ -351,7 +351,7 @@ Сбросить заголовок вкладки - Переименовать заголовок вкладки + Переименовать вкладку Изменить размер области @@ -445,7 +445,7 @@ Переключиться на последнюю вкладку - Поиск вкладки + Поиск вкладки... Режим "поверх других окон" @@ -453,9 +453,6 @@ Показать или скрыть палитру команд - - Последние команды - Открыть предложения @@ -519,7 +516,7 @@ Сбросить имя окна - Переименовать окно + Переименовать окно... Отобразить текущий рабочий каталог Терминала @@ -570,7 +567,7 @@ Выйти из терминала - Установить непрозрачность фона + Установить непрозрачность фона... Увеличить непрозрачность фона на {0}% diff --git a/src/cascadia/TerminalSettingsModel/Resources/zh-CN/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/zh-CN/Resources.resw index cbe6a265b3..437b1a2566 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/zh-CN/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/zh-CN/Resources.resw @@ -118,13 +118,13 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - 选择配色方案 + 选择配色方案... - 新建标签页 + 新建选项卡... - 拆分窗格 + 拆分窗格... 打开代码片段窗格 @@ -329,7 +329,7 @@ {Locked="JSON"}. "JSON" is the extension of the file that will be opened. - 设置选项卡颜色 + 设置选项卡颜色... 粘贴 @@ -351,7 +351,7 @@ 重置标签页标题 - 重命名选项卡标题 + 重命名选项卡 调整窗格大小 @@ -445,7 +445,7 @@ 切换到最后一个选项卡 - 搜索选项卡 + 搜索选项卡... 切换“总在最前面”模式 @@ -453,9 +453,6 @@ 切换命令面板 - - 最近使用的命令 - 打开建议 @@ -519,7 +516,7 @@ 重置窗口名称 - 重命名窗口 + 重命名窗口... 显示终端的当前工作目录 @@ -570,7 +567,7 @@ 退出终端 - 设置背景不透明度 + 设置背景不透明度... 将背景不透明度增加 {0}% diff --git a/src/cascadia/TerminalSettingsModel/Resources/zh-TW/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/zh-TW/Resources.resw index 56bf4218cb..b5bd224165 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/zh-TW/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/zh-TW/Resources.resw @@ -118,13 +118,13 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - 選取彩色配置 + 選取彩色配置... - 新索引標籤 + 新索引標籤... - 分割窗格 + 分割窗格... 開啟程式碼片段窗格 @@ -329,7 +329,7 @@ {Locked="JSON"}. "JSON" is the extension of the file that will be opened. - 設定索引標籤色彩 + 設定索引標籤色彩... 貼上 @@ -351,7 +351,7 @@ 重設索引標籤標題 - 重新命名索引標籤標題 + 重新命名索引標籤 調整頁面大小 @@ -445,7 +445,7 @@ 切換到最後一個索引標籤 - 搜尋索引標籤 + 搜尋索引標籤... 啟用 [最上層顯示] 模式 @@ -453,9 +453,6 @@ 切換命令選擇區 - - 最近使用的命令 - 開啟建議 @@ -519,7 +516,7 @@ 重設視窗名稱 - 重新命名視窗 + 重新命名視窗... 顯示終端機目前的工作目錄 @@ -570,7 +567,7 @@ 結束 [終端機] - 設定背景不透明度 + 設定背景不透明度... 增加背景不透明度為 {0}% From 040f26175f6f2b200fe3d920d9354c05962c7a92 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 23 Aug 2024 22:02:01 +0200 Subject: [PATCH 13/14] Use DECCRA/DECFRA for ScrollConsoleScreenBuffer (#17747) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds logic to get the DA1 report from the hosting terminal on startup. We then use the information to figure out if it supports rectangular area operations. If so, we can use DECCRA/DECFRA to implement ScrollConsoleScreenBuffer. This additionally changes `ScrollConsoleScreenBuffer` to always forbid control characters as the fill character, even in conhost (via `VtIo::SanitizeUCS2`). My hope is that this makes the API more consistent and robust as it avoids another source for invisible control characters in the text buffer. Part of #17643 ## Validation Steps Performed * New tests ✅ --- .github/actions/spelling/expect/expect.txt | 21 +- src/host/VtInputThread.cpp | 4 +- src/host/VtInputThread.hpp | 7 +- src/host/VtIo.cpp | 111 +++++---- src/host/VtIo.hpp | 5 +- src/host/getset.cpp | 140 ++++++++--- src/host/ut_host/VtIoTests.cpp | 223 +++++++++++++++++- src/inc/til/enumset.h | 7 + .../parser/InputStateMachineEngine.cpp | 54 ++++- .../parser/InputStateMachineEngine.hpp | 36 ++- src/types/viewport.cpp | 6 +- src/winconpty/ft_pty/ConPtyTests.cpp | 80 ++----- tools/OpenConsole.psm1 | 2 +- tools/tests.xml | 1 + 14 files changed, 510 insertions(+), 187 deletions(-) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index fc5bb6d902..1621856787 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -9,7 +9,6 @@ ABORTIFHUNG ACCESSTOKEN acidev ACIOSS -ACover acp actctx ACTCTXW @@ -87,6 +86,7 @@ Autowrap AVerify awch azurecr +AZZ backgrounded Backgrounder backgrounding @@ -180,7 +180,6 @@ CFuzz cgscrn chafa changelists -charinfo CHARSETINFO chh chshdng @@ -264,7 +263,6 @@ consolegit consolehost CONSOLEIME consoleinternal -Consoleroot CONSOLESETFOREGROUND consoletaeftemplates consoleuwp @@ -386,7 +384,7 @@ DECCIR DECCKM DECCKSR DECCOLM -DECCRA +deccra DECCTR DECDC DECDHL @@ -398,7 +396,7 @@ DECEKBD DECERA DECFI DECFNK -DECFRA +decfra DECGCI DECGCR DECGNL @@ -727,7 +725,6 @@ GHIJKL gitcheckin gitfilters gitlab -gitmodules gle GLOBALFOCUS GLYPHENTRY @@ -1021,7 +1018,6 @@ lstatus lstrcmp lstrcmpi LTEXT -LTLTLTLTL ltsc LUID luma @@ -1116,7 +1112,6 @@ msrc MSVCRTD MTSM Munged -munges murmurhash muxes myapplet @@ -1218,7 +1213,6 @@ ntlpcapi ntm ntrtl ntstatus -NTSYSCALLAPI nttree nturtl ntuser @@ -1526,7 +1520,6 @@ rftp rgbi RGBQUAD rgbs -rgci rgfae rgfte rgn @@ -1604,6 +1597,7 @@ SELECTALL SELECTEDFONT SELECTSTRING Selfhosters +Serbo SERVERDLL SETACTIVE SETBUDDYINT @@ -1832,8 +1826,6 @@ TOPDOWNDIB TOpt tosign touchpad -Tpp -Tpqrst tracelogging traceviewpp trackbar @@ -1958,7 +1950,6 @@ VPACKMANIFESTDIRECTORY VPR VREDRAW vsc -vsconfig vscprintf VSCROLL vsdevshell @@ -2000,7 +1991,6 @@ wcswidth wddm wddmcon WDDMCONSOLECONTEXT -WDK wdm webpage websites @@ -2074,7 +2064,6 @@ winuserp WINVER wistd wmain -wmemory WMSZ wnd WNDALLOC @@ -2173,6 +2162,7 @@ yact YCast YCENTER YCount +yizz YLimit YPan YSubstantial @@ -2186,3 +2176,4 @@ ZCtrl ZWJs ZYXWVU ZYXWVUTd +zzf diff --git a/src/host/VtInputThread.cpp b/src/host/VtInputThread.cpp index efd2fd4e51..3c359a3c8e 100644 --- a/src/host/VtInputThread.cpp +++ b/src/host/VtInputThread.cpp @@ -185,8 +185,8 @@ void VtInputThread::_InputThread() return S_OK; } -void VtInputThread::WaitUntilDSR(DWORD timeout) const noexcept +til::enumset VtInputThread::WaitUntilDA1(DWORD timeout) const noexcept { const auto& engine = static_cast(_pInputStateMachine->Engine()); - engine.WaitUntilDSR(timeout); + return engine.WaitUntilDA1(timeout); } diff --git a/src/host/VtInputThread.hpp b/src/host/VtInputThread.hpp index e6f15e2ab6..50405b5a25 100644 --- a/src/host/VtInputThread.hpp +++ b/src/host/VtInputThread.hpp @@ -18,13 +18,18 @@ Author(s): namespace Microsoft::Console { + namespace VirtualTerminal + { + enum class DeviceAttribute : uint64_t; + } + class VtInputThread { public: VtInputThread(_In_ wil::unique_hfile hPipe, const bool inheritCursor); [[nodiscard]] HRESULT Start(); - void WaitUntilDSR(DWORD timeout) const noexcept; + til::enumset WaitUntilDA1(DWORD timeout) const noexcept; private: static DWORD WINAPI StaticVtInputThreadProc(_In_ LPVOID lpParameter); diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index a45ec8260a..026010b2f7 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -163,38 +163,37 @@ bool VtIo::IsUsingVt() const { Writer writer{ this }; - // 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. - - writer.WriteUTF8( - "\x1b[?1004h" // Focus Event Mode - "\x1b[?9001h" // Win32 Input Mode - ); - // 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 - // wait 1s until we get a response. + // we're going to emit a VT sequence to ask for the cursor position. // If we get a response, the InteractDispatch will call SetCursorPosition, - // which will call to our VtIo::SetCursorPosition method. + // which will call to our VtIo::SetCursorPosition method. + // + // By sending the request before sending the DA1 one, we can simply + // wait for the DA1 response below and effectively wait for both. if (_lookingForCursorPosition) { writer.WriteUTF8("\x1b[6n"); // Cursor Position Report (DSR CPR) } + // 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. + writer.WriteUTF8( + "\x1b[c" // DA1 Report (Primary Device Attributes) + "\x1b[?1004h" // Focus Event Mode + "\x1b[?9001h" // Win32 Input Mode + ); + writer.Submit(); } - if (_lookingForCursorPosition) { - _lookingForCursorPosition = false; - // Allow the input thread to momentarily gain the console lock. auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); const auto suspension = gci.SuspendLock(); - _pVtInputThread->WaitUntilDSR(3000); + _deviceAttributes = _pVtInputThread->WaitUntilDA1(3000); } if (_pPtySignalInputThread) @@ -211,6 +210,16 @@ bool VtIo::IsUsingVt() const return S_OK; } +void VtIo::SetDeviceAttributes(const til::enumset attributes) noexcept +{ + _deviceAttributes = attributes; +} + +til::enumset VtIo::GetDeviceAttributes() const noexcept +{ + return _deviceAttributes; +} + // Method Description: // - Create our pseudo window. This is exclusively called by // ConsoleInputThreadProcWin32 on the console input thread. @@ -359,6 +368,40 @@ void VtIo::FormatAttributes(std::wstring& target, const TextAttribute& attribute target.append(bufW, len); } +wchar_t VtIo::SanitizeUCS2(wchar_t ch) +{ + // If any of the values in the buffer are C0 or C1 controls, we need to + // convert them to printable codepoints, otherwise they'll end up being + // evaluated as control characters by the receiving terminal. We use the + // DOS 437 code page for the C0 controls and DEL, and just a `?` for the + // C1 controls, since that's what you would most likely have seen in the + // legacy v1 console with raster fonts. + if (ch < 0x20) + { + static constexpr wchar_t lut[] = { + // clang-format off + L' ', L'☺', L'☻', L'♥', L'♦', L'♣', L'♠', L'•', L'◘', L'○', L'◙', L'♂', L'♀', L'♪', L'♫', L'☼', + L'►', L'◄', L'↕', L'‼', L'¶', L'§', L'▬', L'↨', L'↑', L'↓', L'→', L'←', L'∟', L'↔', L'▲', L'▼', + // clang-format on + }; + ch = lut[ch]; + } + else if (ch == 0x7F) + { + ch = L'⌂'; + } + else if (ch > 0x7F && ch < 0xA0) + { + ch = L'?'; + } + else if (til::is_surrogate(ch)) + { + ch = UNICODE_REPLACEMENT; + } + + return ch; +} + VtIo::Writer::Writer(VtIo* io) noexcept : _io{ io } { @@ -592,7 +635,7 @@ void VtIo::Writer::WriteUTF16StripControlChars(std::wstring_view str) const for (it = begControlChars; it != end && IsControlCharacter(*it); ++it) { - WriteUCS2StripControlChars(*it); + WriteUCS2(SanitizeUCS2(*it)); } } } @@ -626,36 +669,6 @@ void VtIo::Writer::WriteUCS2(wchar_t ch) const _io->_back.append(buf, len); } -void VtIo::Writer::WriteUCS2StripControlChars(wchar_t ch) const -{ - // If any of the values in the buffer are C0 or C1 controls, we need to - // convert them to printable codepoints, otherwise they'll end up being - // evaluated as control characters by the receiving terminal. We use the - // DOS 437 code page for the C0 controls and DEL, and just a `?` for the - // C1 controls, since that's what you would most likely have seen in the - // legacy v1 console with raster fonts. - if (ch < 0x20) - { - static constexpr wchar_t lut[] = { - // clang-format off - L' ', L'☺', L'☻', L'♥', L'♦', L'♣', L'♠', L'•', L'◘', L'○', L'◙', L'♂', L'♀', L'♪', L'♫', L'☼', - L'►', L'◄', L'↕', L'‼', L'¶', L'§', L'▬', L'↨', L'↑', L'↓', L'→', L'←', L'∟', L'↔', L'▲', L'▼', - // clang-format on - }; - ch = lut[ch]; - } - else if (ch == 0x7F) - { - ch = L'⌂'; - } - else if (ch > 0x7F && ch < 0xA0) - { - ch = L'?'; - } - - WriteUCS2(ch); -} - // CUP: Cursor Position void VtIo::Writer::WriteCUP(til::point position) const { @@ -773,7 +786,7 @@ void VtIo::Writer::WriteInfos(til::point target, std::span info do { - WriteUCS2StripControlChars(ch); + WriteUCS2(SanitizeUCS2(ch)); } while (--repeat); } } diff --git a/src/host/VtIo.hpp b/src/host/VtIo.hpp index 35a0a13bc2..a5d1376426 100644 --- a/src/host/VtIo.hpp +++ b/src/host/VtIo.hpp @@ -35,7 +35,6 @@ namespace Microsoft::Console::VirtualTerminal void WriteUTF16TranslateCRLF(std::wstring_view str) const; void WriteUTF16StripControlChars(std::wstring_view str) const; void WriteUCS2(wchar_t ch) const; - void WriteUCS2StripControlChars(wchar_t ch) const; void WriteCUP(til::point position) const; void WriteDECTCEM(bool enabled) const; void WriteSGR1006(bool enabled) const; @@ -54,6 +53,7 @@ namespace Microsoft::Console::VirtualTerminal static void FormatAttributes(std::string& target, const TextAttribute& attributes); static void FormatAttributes(std::wstring& target, const TextAttribute& attributes); + static wchar_t SanitizeUCS2(wchar_t ch); [[nodiscard]] HRESULT Initialize(const ConsoleArguments* const pArgs); [[nodiscard]] HRESULT CreateAndStartSignalThread() noexcept; @@ -62,6 +62,8 @@ namespace Microsoft::Console::VirtualTerminal bool IsUsingVt() const; [[nodiscard]] HRESULT StartIfNeeded(); + void SetDeviceAttributes(til::enumset attributes) noexcept; + til::enumset GetDeviceAttributes() const noexcept; void SendCloseEvent(); void CreatePseudoWindow(); @@ -79,6 +81,7 @@ namespace Microsoft::Console::VirtualTerminal std::unique_ptr _pVtInputThread; std::unique_ptr _pPtySignalInputThread; + til::enumset _deviceAttributes; // We use two buffers: A front and a back buffer. The front buffer is the one we're currently // sending to the terminal (it's being "presented" = it's on the "front" & "visible"). diff --git a/src/host/getset.cpp b/src/host/getset.cpp index 80e3431d21..cb3c80d7bb 100644 --- a/src/host/getset.cpp +++ b/src/host/getset.cpp @@ -13,6 +13,7 @@ #include "_stream.h" #include "../interactivity/inc/ServiceLocator.hpp" +#include "../terminal/parser/InputStateMachineEngine.hpp" #include "../types/inc/convert.hpp" #include "../types/inc/viewport.hpp" @@ -993,29 +994,36 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont { try { + // Just in case if the client application didn't check if this request is useless. + if (source.left == target.x && source.top == target.y) + { + return S_OK; + } + LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (auto writer = gci.GetVtWriterForBuffer(&context)) + auto& buffer = context.GetActiveBuffer(); + const auto bufferSize = buffer.GetBufferSize(); + auto writer = gci.GetVtWriterForBuffer(&context); + + // Applications like to pass 0/0 for the fill char/attribute. + // What they want is the whitespace and current attributes. + if (fillCharacter == UNICODE_NULL && fillAttribute == 0) { - auto& buffer = context.GetActiveBuffer(); + fillAttribute = buffer.GetAttributes().GetLegacyAttributes(); + } - // However, if the character is null and we were given a null attribute (represented as legacy 0), - // then we'll just fill with spaces and whatever the buffer's default colors are. - if (fillCharacter == UNICODE_NULL && fillAttribute == 0) - { - fillCharacter = UNICODE_SPACE; - fillAttribute = buffer.GetAttributes().GetLegacyAttributes(); - } + // Avoid writing control characters into the buffer. + // A null character will get translated to whitespace. + fillCharacter = Microsoft::Console::VirtualTerminal::VtIo::SanitizeUCS2(fillCharacter); + if (writer) + { // GH#3126 - This is a shim for cmd's `cls` function. In the - // legacy console, `cls` is supposed to clear the entire buffer. In - // conpty however, there's no difference between the viewport and the - // entirety of the buffer. We're going to see if this API call exactly - // matched the way we expect cmd to call it. If it does, then - // let's manually emit a Full Reset (RIS). - const auto bufferSize = buffer.GetBufferSize(); + // legacy console, `cls` is supposed to clear the entire buffer. + // We always use a VT sequence, even if ConPTY isn't used, because those are faster nowadays. if (enableCmdShim && source.left <= 0 && source.top <= 0 && source.right >= bufferSize.RightInclusive() && source.bottom >= bufferSize.BottomInclusive() && @@ -1028,36 +1036,96 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont return S_OK; } - const auto clipViewport = clip ? Viewport::FromInclusive(*clip) : bufferSize; + const auto clipViewport = clip ? Viewport::FromInclusive(*clip).Clamp(bufferSize) : bufferSize; const auto sourceViewport = Viewport::FromInclusive(source); - Viewport readViewport; - Viewport writtenViewport; - - const auto w = std::max(0, sourceViewport.Width()); - const auto h = std::max(0, sourceViewport.Height()); - const auto a = static_cast(w * h); - if (a == 0) - { - return S_OK; - } - - til::small_vector backup; - til::small_vector fill; - - backup.resize(a, CHAR_INFO{ fillCharacter, fillAttribute }); - fill.resize(a, CHAR_INFO{ fillCharacter, fillAttribute }); + const auto fillViewport = sourceViewport.Clamp(clipViewport); writer.BackupCursor(); - RETURN_IF_FAILED(ReadConsoleOutputWImplHelper(context, backup, sourceViewport, readViewport)); - RETURN_IF_FAILED(WriteConsoleOutputWImplHelper(context, fill, w, sourceViewport.Clamp(clipViewport), writtenViewport)); - RETURN_IF_FAILED(WriteConsoleOutputWImplHelper(context, backup, w, Viewport::FromDimensions(target, readViewport.Dimensions()).Clamp(clipViewport), writtenViewport)); + if (gci.GetVtIo()->GetDeviceAttributes().test(Microsoft::Console::VirtualTerminal::DeviceAttribute::RectangularAreaOperations)) + { + // This calculates just the positive offsets caused by out-of-bounds (OOB) source and target coordinates. + // + // If the source rectangle is OOB to the bottom-right, then the size of the rectangle that can + // be copied shrinks, but its origin stays the same. However, if the rectangle is OOB to the + // top-left then the origin of the to-be-copied rectangle will be offset by an inverse amount. + // Similarly, if the *target* rectangle is OOB to the bottom-right, its size shrinks while + // the origin stays the same, and if it's OOB to the top-left, then the origin is offset. + // + // In other words, this calculates the total offset that needs to be applied to the to-be-copied rectangle. + // Later down below we'll then clamp that rectangle which will cause its size to shrink as needed. + const til::point offset{ + std::max(0, -source.left) + std::max(0, clipViewport.Left() - target.x), + std::max(0, -source.top) + std::max(0, clipViewport.Top() - target.y), + }; + + const auto copyTargetViewport = Viewport::FromDimensions(target + offset, sourceViewport.Dimensions()).Clamp(clipViewport); + const auto copySourceViewport = Viewport::FromDimensions(sourceViewport.Origin() + offset, copyTargetViewport.Dimensions()).Clamp(bufferSize); + const auto fills = Viewport::Subtract(fillViewport, copyTargetViewport); + std::wstring buf; + + if (!fills.empty()) + { + Microsoft::Console::VirtualTerminal::VtIo::FormatAttributes(buf, TextAttribute{ fillAttribute }); + } + + if (copySourceViewport.IsValid() && copyTargetViewport.IsValid()) + { + // DECCRA: Copy Rectangular Area + fmt::format_to( + std::back_inserter(buf), + FMT_COMPILE(L"\x1b[{};{};{};{};;{};{}$v"), + copySourceViewport.Top() + 1, + copySourceViewport.Left() + 1, + copySourceViewport.BottomExclusive(), + copySourceViewport.RightExclusive(), + copyTargetViewport.Top() + 1, + copyTargetViewport.Left() + 1); + } + + for (const auto& fill : fills) + { + // DECFRA: Fill Rectangular Area + fmt::format_to( + std::back_inserter(buf), + FMT_COMPILE(L"\x1b[{};{};{};{};{}$x"), + static_cast(fillCharacter), + fill.Top() + 1, + fill.Left() + 1, + fill.BottomExclusive(), + fill.RightExclusive()); + } + + WriteCharsVT(context, buf); + } + else + { + const auto w = std::max(0, sourceViewport.Width()); + const auto h = std::max(0, sourceViewport.Height()); + const auto a = static_cast(w * h); + if (a == 0) + { + return S_OK; + } + + til::small_vector backup; + til::small_vector fill; + + backup.resize(a, CHAR_INFO{ fillCharacter, fillAttribute }); + fill.resize(a, CHAR_INFO{ fillCharacter, fillAttribute }); + + Viewport readViewport; + Viewport writtenViewport; + + RETURN_IF_FAILED(ReadConsoleOutputWImplHelper(context, backup, sourceViewport, readViewport)); + RETURN_IF_FAILED(WriteConsoleOutputWImplHelper(context, fill, w, fillViewport, writtenViewport)); + RETURN_IF_FAILED(WriteConsoleOutputWImplHelper(context, backup, w, Viewport::FromDimensions(target, readViewport.Dimensions()).Clamp(clipViewport), writtenViewport)); + } writer.Submit(); } else { - auto& buffer = context.GetActiveBuffer(); TextAttribute useThisAttr(fillAttribute); ScrollRegion(buffer, source, clip, target, fillCharacter, useThisAttr); } diff --git a/src/host/ut_host/VtIoTests.cpp b/src/host/ut_host/VtIoTests.cpp index f557905bf1..3ad87891a2 100644 --- a/src/host/ut_host/VtIoTests.cpp +++ b/src/host/ut_host/VtIoTests.cpp @@ -4,6 +4,7 @@ #include "precomp.h" #include "CommonState.hpp" +#include "../../terminal/parser/InputStateMachineEngine.hpp" using namespace WEX::Common; using namespace WEX::Logging; @@ -29,6 +30,8 @@ constexpr CHAR_INFO ci_blu(wchar_t ch) noexcept } #define cup(y, x) "\x1b[" #y ";" #x "H" // CUP: Cursor Position +#define deccra(t, l, b, r, y, x) "\x1b[" #t ";" #l ";" #b ";" #r ";;" #y ";" #x "$v" // DECCRA: Copy Rectangular Area +#define decfra(ch, t, l, b, r) "\x1b[" #ch ";" #t ";" #l ";" #b ";" #r "$x" #define decawm(h) "\x1b[?7" #h // DECAWM: Autowrap Mode #define decsc() "\x1b\x37" // DECSC: DEC Save Cursor (+ attributes) #define decrc() "\x1b\x38" // DECRC: DEC Restore Cursor (+ attributes) @@ -554,12 +557,224 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests actual = readOutput(); VERIFY_ARE_EQUAL(expected, actual); + // Copying from a partially out-of-bounds source to a partially out-of-bounds target, + // while source and target overlap and there's a partially out-of-bounds clip rect. + // + // Before: + // clip rect + // +~~~~~~~~~~~~~~~~~~~~~+ + // +--------------$--------+ $ + // | A Z Z$ b C | D c Y $ + // | $+-------+------------$--+ + // | E z z$| f G | H g Y $ | + // | src $| | $ | + // | i z z$| J d | B E L $ | + // | $| | dst $ | + // | m n M$| N h | F i P $ | + // +--------------$+-------+ $ | + // +~e~~~~~~~~~~~~~~~~~~~+ | + // +-----------------------+ + // + // After: + // + // +-----------------------+ + // | A Z Z y y | D c Y + // | +-------+---------------+ + // | E z z | y A | Z Z b | + // | | | | + // | i z z | y E | z z f | + // | | | | + // | m n M | y i | z z J | + // +---------------+-------+ | + // | | + // +-----------------------+ + THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { -1, 0, 4, 3 }, { 3, 1 }, til::inclusive_rect{ 3, -1, 7, 9 }, L'y', blu, false)); + expected = + decsc() // + cup(1, 4) sgr_blu("yy") // + cup(2, 4) sgr_blu("yy") // + cup(3, 4) sgr_blu("yy") // + cup(4, 4) sgr_blu("yy") // + cup(2, 4) sgr_blu("y") sgr_red("AZZ") sgr_blu("b") // + cup(3, 4) sgr_blu("y") sgr_red("E") sgr_blu("zzf") // + cup(4, 4) sgr_blu("yizz") sgr_red("J") // + decrc(); + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + static constexpr std::array expectedContents{ { // clang-format off - ci_red('A'), ci_red('Z'), ci_red('Z'), ci_blu('b'), ci_red('C'), ci_red('D'), ci_blu('c'), ci_red('Y'), - ci_red('E'), ci_blu('z'), ci_blu('z'), ci_blu('f'), ci_red('G'), ci_red('H'), ci_blu('g'), ci_red('Y'), - ci_blu('i'), ci_blu('z'), ci_blu('z'), ci_red('J'), ci_blu('d'), ci_red('B'), ci_red('E'), ci_red('L'), - ci_blu('m'), ci_blu('n'), ci_red('M'), ci_red('N'), ci_blu('h'), ci_red('F'), ci_blu('i'), ci_red('P'), + ci_red('A'), ci_red('Z'), ci_red('Z'), ci_blu('y'), ci_blu('y'), ci_red('D'), ci_blu('c'), ci_red('Y'), + ci_red('E'), ci_blu('z'), ci_blu('z'), ci_blu('y'), ci_red('A'), ci_red('Z'), ci_red('Z'), ci_blu('b'), + ci_blu('i'), ci_blu('z'), ci_blu('z'), ci_blu('y'), ci_red('E'), ci_blu('z'), ci_blu('z'), ci_blu('f'), + ci_blu('m'), ci_blu('n'), ci_red('M'), ci_blu('y'), ci_blu('i'), ci_blu('z'), ci_blu('z'), ci_red('J'), + // clang-format on + } }; + std::array actualContents{}; + Viewport actualContentsRead; + THROW_IF_FAILED(routines.ReadConsoleOutputWImpl(*screenInfo, actualContents, Viewport::FromDimensions({}, { 8, 4 }), actualContentsRead)); + VERIFY_IS_TRUE(memcmp(expectedContents.data(), actualContents.data(), sizeof(actualContents)) == 0); + } + + TEST_METHOD(ScrollConsoleScreenBufferW_DECCRA) + { + ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo()->SetDeviceAttributes({ DeviceAttribute::RectangularAreaOperations }); + const auto cleanup = wil::scope_exit([]() { + ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo()->SetDeviceAttributes({}); + }); + + std::string_view expected; + std::string_view actual; + + setupInitialContents(); + + // Scrolling from nowhere to somewhere are no-ops and should not emit anything. + THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 0, 0, -1, -1 }, {}, std::nullopt, L' ', 0, false)); + THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { -10, -10, -9, -9 }, {}, std::nullopt, L' ', 0, false)); + expected = ""; + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + + // Scrolling from somewhere to nowhere should clear the area. + THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 0, 0, 1, 1 }, { 10, 10 }, std::nullopt, L' ', red, false)); + expected = + decsc() // + sgr_red() // + decfra(32, 1, 1, 2, 2) // ' ' = 32 + decrc(); + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + + // cmd uses ScrollConsoleScreenBuffer to clear the buffer contents and that gets translated to a clear screen sequence. + THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 0, 0, 7, 3 }, { 0, -4 }, std::nullopt, 0, 0, true)); + expected = "\x1b[H\x1b[2J\x1b[3J"; + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + + // + // A B a b C D c d + // + // E F e f G H g h + // + // i j I J k l K L + // + // m n M N o p O P + // + setupInitialContents(); + + // Scrolling from somewhere to somewhere. + // + // +-------+ + // A | Z Z | b C D c d + // | src | + // E | Z Z | f G H g h + // +-------+ +-------+ + // i j I J k | B a | L + // | dst | + // m n M N o | F e | P + // +-------+ + THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 1, 0, 2, 1 }, { 5, 2 }, std::nullopt, L'Z', red, false)); + expected = + decsc() // + sgr_red() // + deccra(1, 2, 2, 3, 3, 6) // + decfra(90, 1, 2, 2, 3) // 'Z' = 90 + decrc(); + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + + // Same, but with a partially out-of-bounds target and clip rect. Clip rects affect both + // the source area that gets filled and the target area that gets a copy of the source contents. + // + // A Z Z b C D c d + // +---+~~~~~~~~~~~~~~~~~~~~~~~+ + // | E $ z z | f G H g $ h + // | $ src | +---$-------+ + // | i $ z z | J k B | E $ L | + // +---$-------+ | $ dst | + // m $ n M N o F | i $ P | + // +~~~~~~~~~~~~~~~~~~~~~~~+-------+ + // clip rect + THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 0, 1, 2, 2 }, { 6, 2 }, til::inclusive_rect{ 1, 1, 6, 3 }, L'z', blu, false)); + expected = + decsc() // + sgr_blu() // + deccra(2, 1, 3, 1, 3, 7) // + decfra(122, 2, 2, 3, 3) // 'z' = 122 + decrc(); + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + + // Same, but with a partially out-of-bounds source. + // The boundaries of the buffer act as a clip rect for reading and so only 2 cells get copied. + // + // +-------+ + // A Z Z b C D c | Y | + // | src | + // E z z f G H g | Y | + // +---+ +-------+ + // i z z J | d | B E L + // |dst| + // m n M N | h | F i P + // +---+ + THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 7, 0, 8, 1 }, { 4, 2 }, std::nullopt, L'Y', red, false)); + expected = + decsc() // + sgr_red() // + deccra(1, 8, 2, 8, 3, 5) // + decfra(89, 1, 8, 2, 8) // 'Y' = 89 + decrc(); + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + + // Copying from a partially out-of-bounds source to a partially out-of-bounds target, + // while source and target overlap and there's a partially out-of-bounds clip rect. + // + // Before: + // clip rect + // +~~~~~~~~~~~~~~~~~~~~~+ + // +--------------$--------+ $ + // | A Z Z$ b C | D c Y $ + // | $+-------+------------$--+ + // | E z z$| f G | H g Y $ | + // | src $| | $ | + // | i z z$| J d | B E L $ | + // | $| | dst $ | + // | m n M$| N h | F i P $ | + // +--------------$+-------+ $ | + // +~e~~~~~~~~~~~~~~~~~~~+ | + // +-----------------------+ + // + // After: + // + // +-----------------------+ + // | A Z Z y y | D c Y + // | +-------+---------------+ + // | E z z | y A | Z Z b | + // | | | | + // | i z z | y E | z z f | + // | | | | + // | m n M | y i | z z J | + // +---------------+-------+ | + // | | + // +-----------------------+ + THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { -1, 0, 4, 3 }, { 3, 1 }, til::inclusive_rect{ 3, -1, 7, 9 }, L'y', blu, false)); + expected = + decsc() // + sgr_blu() // + deccra(1, 1, 3, 4, 2, 5) // + decfra(121, 1, 4, 1, 5) // 'y' = 121 + decfra(121, 2, 4, 4, 4) // + decrc(); + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + + static constexpr std::array expectedContents{ { + // clang-format off + ci_red('A'), ci_red('Z'), ci_red('Z'), ci_blu('y'), ci_blu('y'), ci_red('D'), ci_blu('c'), ci_red('Y'), + ci_red('E'), ci_blu('z'), ci_blu('z'), ci_blu('y'), ci_red('A'), ci_red('Z'), ci_red('Z'), ci_blu('b'), + ci_blu('i'), ci_blu('z'), ci_blu('z'), ci_blu('y'), ci_red('E'), ci_blu('z'), ci_blu('z'), ci_blu('f'), + ci_blu('m'), ci_blu('n'), ci_red('M'), ci_blu('y'), ci_blu('i'), ci_blu('z'), ci_blu('z'), ci_red('J'), // clang-format on } }; std::array actualContents{}; diff --git a/src/inc/til/enumset.h b/src/inc/til/enumset.h index 5ee47a7167..7e7c4bc0cc 100644 --- a/src/inc/til/enumset.h +++ b/src/inc/til/enumset.h @@ -24,6 +24,13 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" static_assert(std::is_unsigned_v); public: + static constexpr enumset from_bits(UnderlyingType data) noexcept + { + enumset result; + result._data = data; + return result; + } + // Method Description: // - Constructs a new bitset with the given list of positions set to true. TIL_ENUMSET_VARARG diff --git a/src/terminal/parser/InputStateMachineEngine.cpp b/src/terminal/parser/InputStateMachineEngine.cpp index 1111e74d37..70e3991ee4 100644 --- a/src/terminal/parser/InputStateMachineEngine.cpp +++ b/src/terminal/parser/InputStateMachineEngine.cpp @@ -89,11 +89,6 @@ static bool operator==(const Ss3ToVkey& pair, const Ss3ActionCodes code) noexcep return pair.action == code; } -InputStateMachineEngine::InputStateMachineEngine(std::unique_ptr pDispatch) : - InputStateMachineEngine(std::move(pDispatch), false) -{ -} - InputStateMachineEngine::InputStateMachineEngine(std::unique_ptr pDispatch, const bool lookingForDSR) : _pDispatch(std::move(pDispatch)), _lookingForDSR(lookingForDSR), @@ -102,14 +97,28 @@ InputStateMachineEngine::InputStateMachineEngine(std::unique_ptr InputStateMachineEngine::WaitUntilDA1(DWORD timeout) const noexcept { + uint64_t val = 0; + // atomic_wait() returns false when the timeout expires. // Technically we should decrement the timeout with each iteration, // but I suspect infinite spurious wake-ups are a theoretical problem. - while (_lookingForDSR.load(std::memory_order::relaxed) && til::atomic_wait(_lookingForDSR, true, timeout)) + for (;;) { + val = _deviceAttributes.load(std::memory_order::relaxed); + if (val) + { + break; + } + + if (!til::atomic_wait(_deviceAttributes, val, timeout)) + { + break; + } } + + return til::enumset::from_bits(val); } bool InputStateMachineEngine::EncounteredWin32InputModeSequence() const noexcept @@ -411,13 +420,12 @@ bool InputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParameter // The F3 case is special - it shares a code with the DeviceStatusResponse. // If we're looking for that response, then do that, and break out. // Else, fall though to the _GetCursorKeysModifierState handler. - if (_lookingForDSR.load(std::memory_order::relaxed)) + if (_lookingForDSR) { _pDispatch->MoveCursor(parameters.at(0), parameters.at(1)); // Right now we're only looking for on initial cursor // position response. After that, only look for F3. - _lookingForDSR.store(false, std::memory_order::relaxed); - til::atomic_notify_all(_lookingForDSR); + _lookingForDSR = false; return true; } // Heuristic: If the hosting terminal used the win32 input mode, chances are high @@ -464,6 +472,32 @@ bool InputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParameter case CsiActionCodes::FocusOut: _pDispatch->FocusChanged(false); return true; + case CsiActionCodes::DA_DeviceAttributes: + // This assumes that InputStateMachineEngine is tightly coupled with VtInputThread and the rest of the ConPTY system (VtIo). + // On startup, ConPTY will send a DA1 request to get more information about the hosting terminal. + // We catch it here and store the information for later retrieval. + if (_deviceAttributes.load(std::memory_order_relaxed) == 0) + { + til::enumset attributes; + + // The first parameter denotes the conformance level. + if (parameters.at(0).value() >= 61) + { + parameters.subspan(1).for_each([&](auto p) { + attributes.set(static_cast(p)); + return true; + }); + } + + _deviceAttributes.fetch_or(attributes.bits(), std::memory_order_relaxed); + til::atomic_notify_all(_deviceAttributes); + + // VtIo first sends a DSR CPR and then a DA1 request. + // If we encountered a DA1 response here, the DSR request is definitely done now. + _lookingForDSR = false; + return true; + } + return false; case CsiActionCodes::Win32KeyboardInput: { // Use WriteCtrlKey here, even for keys that _aren't_ control keys, diff --git a/src/terminal/parser/InputStateMachineEngine.hpp b/src/terminal/parser/InputStateMachineEngine.hpp index b37b67d4b6..98f031a3be 100644 --- a/src/terminal/parser/InputStateMachineEngine.hpp +++ b/src/terminal/parser/InputStateMachineEngine.hpp @@ -49,6 +49,32 @@ namespace Microsoft::Console::VirtualTerminal // CAPSLOCK_ON 0x0080 // ENHANCED_KEY 0x0100 + enum class DeviceAttribute : uint64_t + { + Columns132 = 1, + PrinterPort = 2, + Sixel = 4, + SelectiveErase = 6, + SoftCharacterSet = 7, + UserDefinedKeys = 8, + NationalReplacementCharacterSets = 9, + SerboCroatianCharacterSet = 12, + EightBitInterfaceArchitecture = 14, + TechnicalCharacterSet = 15, + WindowingCapability = 18, + Sessions = 19, + HorizontalScrolling = 21, + Color = 22, + GreekCharacterSet = 23, + TurkishCharacterSet = 24, + RectangularAreaOperations = 28, + TextMacros = 32, + Latin2CharacterSet = 42, + PCTerm = 44, + SoftKeyMapping = 45, + AsciiTerminalEmulation = 46, + }; + enum CsiActionCodes : uint64_t { ArrowUp = VTID("A"), @@ -67,6 +93,7 @@ namespace Microsoft::Console::VirtualTerminal CSI_F3 = VTID("R"), // Both F3 and DSR are on R. // DSR_DeviceStatusReportResponse = VTID("R"), CSI_F4 = VTID("S"), + DA_DeviceAttributes = VTID("?c"), DTTERM_WindowManipulation = VTID("t"), CursorBackTab = VTID("Z"), Win32KeyboardInput = VTID("_") @@ -128,11 +155,9 @@ namespace Microsoft::Console::VirtualTerminal class InputStateMachineEngine : public IStateMachineEngine { public: - InputStateMachineEngine(std::unique_ptr pDispatch); - InputStateMachineEngine(std::unique_ptr pDispatch, - const bool lookingForDSR); + InputStateMachineEngine(std::unique_ptr pDispatch, const bool lookingForDSR = false); - void WaitUntilDSR(DWORD timeout) const noexcept; + til::enumset WaitUntilDA1(DWORD timeout) const noexcept; bool EncounteredWin32InputModeSequence() const noexcept override; @@ -159,7 +184,8 @@ namespace Microsoft::Console::VirtualTerminal private: const std::unique_ptr _pDispatch; - std::atomic _lookingForDSR{ false }; + std::atomic _deviceAttributes{ 0 }; + bool _lookingForDSR = false; bool _encounteredWin32InputModeSequence = false; DWORD _mouseButtonState = 0; std::chrono::milliseconds _doubleClickTime; diff --git a/src/types/viewport.cpp b/src/types/viewport.cpp index ad276f8ecb..018212c216 100644 --- a/src/types/viewport.cpp +++ b/src/types/viewport.cpp @@ -603,7 +603,11 @@ try const auto intersection = Viewport::Intersect(original, removeMe); // If there's no intersection, there's nothing to remove. - if (!intersection.IsValid()) + if (!original.IsValid()) + { + // Nothing to do here. + } + else if (!intersection.IsValid()) { // Just put the original rectangle into the results and return early. result.push_back(original); diff --git a/src/winconpty/ft_pty/ConPtyTests.cpp b/src/winconpty/ft_pty/ConPtyTests.cpp index 27cfdecf83..5b2fb67208 100644 --- a/src/winconpty/ft_pty/ConPtyTests.cpp +++ b/src/winconpty/ft_pty/ConPtyTests.cpp @@ -38,6 +38,9 @@ static Pipes createPipes() VERIFY_IS_TRUE(SetHandleInformation(p.our.in.get(), HANDLE_FLAG_INHERIT, 0)); VERIFY_IS_TRUE(SetHandleInformation(p.our.out.get(), HANDLE_FLAG_INHERIT, 0)); + // ConPTY requests a DA1 report on startup. Emulate the response from the terminal. + WriteFile(p.our.in.get(), "\x1b[?61c", 6, nullptr, nullptr); + return p; } @@ -189,25 +192,12 @@ void ConPtyTests::CreateConPtyBadSize() void ConPtyTests::GoodCreate() { PseudoConsole pcon{}; - wil::unique_handle outPipeOurSide; - wil::unique_handle inPipeOurSide; - wil::unique_handle outPipePseudoConsoleSide; - wil::unique_handle inPipePseudoConsoleSide; - - SECURITY_ATTRIBUTES sa; - sa.nLength = sizeof(sa); - sa.bInheritHandle = TRUE; - sa.lpSecurityDescriptor = nullptr; - - VERIFY_IS_TRUE(CreatePipe(inPipePseudoConsoleSide.addressof(), inPipeOurSide.addressof(), &sa, 0)); - VERIFY_IS_TRUE(CreatePipe(outPipeOurSide.addressof(), outPipePseudoConsoleSide.addressof(), &sa, 0)); - VERIFY_IS_TRUE(SetHandleInformation(inPipeOurSide.get(), HANDLE_FLAG_INHERIT, 0)); - VERIFY_IS_TRUE(SetHandleInformation(outPipeOurSide.get(), HANDLE_FLAG_INHERIT, 0)); + auto pipes = createPipes(); VERIFY_SUCCEEDED( _CreatePseudoConsole(defaultSize, - inPipePseudoConsoleSide.get(), - outPipePseudoConsoleSide.get(), + pipes.conpty.in.get(), + pipes.conpty.out.get(), 0, &pcon)); @@ -220,24 +210,12 @@ void ConPtyTests::GoodCreateMultiple() { PseudoConsole pcon1{}; PseudoConsole pcon2{}; - wil::unique_handle outPipeOurSide; - wil::unique_handle inPipeOurSide; - wil::unique_handle outPipePseudoConsoleSide; - wil::unique_handle inPipePseudoConsoleSide; - - SECURITY_ATTRIBUTES sa; - sa.nLength = sizeof(sa); - sa.bInheritHandle = TRUE; - sa.lpSecurityDescriptor = nullptr; - VERIFY_IS_TRUE(CreatePipe(inPipePseudoConsoleSide.addressof(), inPipeOurSide.addressof(), &sa, 0)); - VERIFY_IS_TRUE(CreatePipe(outPipeOurSide.addressof(), outPipePseudoConsoleSide.addressof(), &sa, 0)); - VERIFY_IS_TRUE(SetHandleInformation(inPipeOurSide.get(), HANDLE_FLAG_INHERIT, 0)); - VERIFY_IS_TRUE(SetHandleInformation(outPipeOurSide.get(), HANDLE_FLAG_INHERIT, 0)); + auto pipes = createPipes(); VERIFY_SUCCEEDED( _CreatePseudoConsole(defaultSize, - inPipePseudoConsoleSide.get(), - outPipePseudoConsoleSide.get(), + pipes.conpty.in.get(), + pipes.conpty.out.get(), 0, &pcon1)); auto closePty1 = wil::scope_exit([&] { @@ -246,8 +224,8 @@ void ConPtyTests::GoodCreateMultiple() VERIFY_SUCCEEDED( _CreatePseudoConsole(defaultSize, - inPipePseudoConsoleSide.get(), - outPipePseudoConsoleSide.get(), + pipes.conpty.in.get(), + pipes.conpty.out.get(), 0, &pcon2)); auto closePty2 = wil::scope_exit([&] { @@ -258,23 +236,12 @@ void ConPtyTests::GoodCreateMultiple() void ConPtyTests::SurvivesOnBreakOutput() { PseudoConsole pty = { 0 }; - wil::unique_handle outPipeOurSide; - wil::unique_handle inPipeOurSide; - wil::unique_handle outPipePseudoConsoleSide; - wil::unique_handle inPipePseudoConsoleSide; - SECURITY_ATTRIBUTES sa; - sa.nLength = sizeof(sa); - sa.bInheritHandle = TRUE; - sa.lpSecurityDescriptor = nullptr; - VERIFY_IS_TRUE(CreatePipe(inPipePseudoConsoleSide.addressof(), inPipeOurSide.addressof(), &sa, 0)); - VERIFY_IS_TRUE(CreatePipe(outPipeOurSide.addressof(), outPipePseudoConsoleSide.addressof(), &sa, 0)); - VERIFY_IS_TRUE(SetHandleInformation(inPipeOurSide.get(), HANDLE_FLAG_INHERIT, 0)); - VERIFY_IS_TRUE(SetHandleInformation(outPipeOurSide.get(), HANDLE_FLAG_INHERIT, 0)); + auto pipes = createPipes(); VERIFY_SUCCEEDED( _CreatePseudoConsole(defaultSize, - inPipePseudoConsoleSide.get(), - outPipePseudoConsoleSide.get(), + pipes.conpty.in.get(), + pipes.conpty.out.get(), 0, &pty)); auto closePty1 = wil::scope_exit([&] { @@ -292,7 +259,7 @@ void ConPtyTests::SurvivesOnBreakOutput() VERIFY_IS_TRUE(GetExitCodeProcess(piClient.hProcess, &dwExit)); VERIFY_ARE_EQUAL(dwExit, (DWORD)STILL_ACTIVE); - VERIFY_IS_TRUE(CloseHandle(outPipeOurSide.get())); + pipes.our.out.reset(); // Wait for a couple seconds, make sure the child is still alive. VERIFY_ARE_EQUAL(WaitForSingleObject(pty.hConPtyProcess, 2000), (DWORD)WAIT_TIMEOUT); @@ -317,23 +284,12 @@ void ConPtyTests::DiesOnClose() VERIFY_SUCCEEDED(TestData::TryGetValue(L"commandline", testCommandline), L"Get a commandline to test"); PseudoConsole pty = { 0 }; - wil::unique_handle outPipeOurSide; - wil::unique_handle inPipeOurSide; - wil::unique_handle outPipePseudoConsoleSide; - wil::unique_handle inPipePseudoConsoleSide; - SECURITY_ATTRIBUTES sa; - sa.nLength = sizeof(sa); - sa.bInheritHandle = TRUE; - sa.lpSecurityDescriptor = nullptr; - VERIFY_IS_TRUE(CreatePipe(inPipePseudoConsoleSide.addressof(), inPipeOurSide.addressof(), &sa, 0)); - VERIFY_IS_TRUE(CreatePipe(outPipeOurSide.addressof(), outPipePseudoConsoleSide.addressof(), &sa, 0)); - VERIFY_IS_TRUE(SetHandleInformation(inPipeOurSide.get(), HANDLE_FLAG_INHERIT, 0)); - VERIFY_IS_TRUE(SetHandleInformation(outPipeOurSide.get(), HANDLE_FLAG_INHERIT, 0)); + auto pipes = createPipes(); VERIFY_SUCCEEDED( _CreatePseudoConsole(defaultSize, - inPipePseudoConsoleSide.get(), - outPipePseudoConsoleSide.get(), + pipes.conpty.in.get(), + pipes.conpty.out.get(), 0, &pty)); auto closePty1 = wil::scope_exit([&] { diff --git a/tools/OpenConsole.psm1 b/tools/OpenConsole.psm1 index bbcbc88993..ba87033007 100644 --- a/tools/OpenConsole.psm1 +++ b/tools/OpenConsole.psm1 @@ -169,7 +169,7 @@ function Invoke-OpenConsoleTests() [switch]$FTOnly, [parameter(Mandatory=$false)] - [ValidateSet('host', 'interactivityWin32', 'terminal', 'adapter', 'feature', 'uia', 'textbuffer', 'til', 'types', 'terminalCore', 'terminalApp', 'localTerminalApp', 'unitSettingsModel', 'unitRemoting', 'unitControl')] + [ValidateSet('host', 'interactivityWin32', 'terminal', 'adapter', 'feature', 'uia', 'textbuffer', 'til', 'types', 'terminalCore', 'terminalApp', 'localTerminalApp', 'unitSettingsModel', 'unitRemoting', 'unitControl', 'winconpty')] [string]$Test, [parameter(Mandatory=$false)] diff --git a/tools/tests.xml b/tools/tests.xml index 4301c09b2a..9ec3585de6 100644 --- a/tools/tests.xml +++ b/tools/tests.xml @@ -15,4 +15,5 @@ + From f93347ed4bbeea0d9e663f2069994d9e2d069f10 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Fri, 23 Aug 2024 15:10:12 -0500 Subject: [PATCH 14/14] version: bump to 1.23 on main --- custom.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom.props b/custom.props index 3227f85d7e..e25b9e8e97 100644 --- a/custom.props +++ b/custom.props @@ -5,7 +5,7 @@ true 2024 1 - 22 + 23 Windows Terminal