diff --git a/.github/actions/spelling/allow/microsoft.txt b/.github/actions/spelling/allow/microsoft.txt index f43408388b..782754a012 100644 --- a/.github/actions/spelling/allow/microsoft.txt +++ b/.github/actions/spelling/allow/microsoft.txt @@ -78,4 +78,5 @@ wtl wtt wttlog Xamarin +XBOX xfgcheck diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index e93e898c01..2fd76fc989 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -1139,7 +1139,7 @@ nullopts NUMSCROLL NUnit nupkg -NVIDIA +nvidia NVT OACR ocolor diff --git a/README.md b/README.md index de45a7c5ff..608358e615 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![terminal-logos](https://github.com/microsoft/terminal/assets/91625426/333ddc76-8ab2-4eb4-a8c0-4d7b953b1179) +![Windows Terminal project logos and branding image](https://github.com/microsoft/terminal/assets/91625426/333ddc76-8ab2-4eb4-a8c0-4d7b953b1179) [![Terminal Build Status](https://dev.azure.com/shine-oss/terminal/_apis/build/status%2FTerminal%20CI?branchName=main)](https://dev.azure.com/shine-oss/terminal/_build/latest?definitionId=1&branchName=main) diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp index 9c045d0312..c5843a17f3 100644 --- a/src/cascadia/TerminalApp/Tab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -161,6 +161,40 @@ namespace winrt::TerminalApp::implementation } }); + // BODGY: Work around a fail-fast crash in WinUI 2's TabView. When a + // game controller (e.g. an XBOX controller's d-pad/stick) moves focus + // Up or Down between two TabViewItems, MUX's TabView::OnListViewGettingFocus + // runs a GameController-only code path that calls + // DispatcherQueue.TryEnqueue, which fails with E_INVALIDARG. That + // unhandled error inside a GettingFocus callback makes XAML fail-fast, + // and the whole window vanishes. See + // microsoft-ui-xaml/blob/840c6cd4ac9d16d9aa99b24f0bd43997fe21bef8/dev/TabView/TabView.cpp#L246 + // + // This GettingFocus handler is on the TabViewItem, which is closer to + // the focus source than the TabViewListView that MUX's handler lives + // on, so it runs first. Marking the event Handled - and cancelling the + // focus move, which is exactly what MUX itself does for the keyboard + // case - preempts MUX's broken handler. Keyboard navigation is left + // untouched; MUX handles that path safely. + TabViewItem().GettingFocus([](auto&&, const winrt::WUX::Input::GettingFocusEventArgs& args) { + const auto direction{ args.Direction() }; + if (direction != winrt::WUX::Input::FocusNavigationDirection::Up && + direction != winrt::WUX::Input::FocusNavigationDirection::Down) + { + return; + } + if (args.InputDevice() != winrt::WUX::Input::FocusInputDeviceKind::GameController) + { + return; + } + if (args.OldFocusedElement().try_as() && + args.NewFocusedElement().try_as()) + { + args.Cancel(true); + args.Handled(true); + } + }); + UpdateTitle(); _RecalculateAndApplyTabColor(); } diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index c178de553f..c5e1794830 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -547,40 +547,10 @@ namespace winrt::TerminalApp::implementation // 1. We want to customize this behavior (e.g., use MRU logic) // 2. In fullscreen (GH#5799) and focus (GH#7916) modes the _OnTabItemsChanged is not fired // 3. When rearranging tabs (GH#7916) _OnTabItemsChanged is suppressed - const auto tabSwitchMode = _currentWindowSettings().TabSwitcherMode(); - if (tabSwitchMode == TabSwitcherMode::MostRecentlyUsed) - { - const auto newSelectedTab = _mruTabs.GetAt(0); - _UpdatedSelectedTab(newSelectedTab); - _tabView.SelectedItem(newSelectedTab.TabViewItem()); - } - else - { - // We can't use - // auto selectedIndex = _tabView.SelectedIndex(); - // Because this will always return -1 in this scenario unfortunately. - // - // So, what we're going to try to do is move the focus to the tab - // to the right, within the bounds of how many tabs we have. - // - // EX: we have 4 tabs: [A, B, C, D]. If we close: - // * A (tabIndex=0): We'll want to focus tab B (now in index 0) - // * B (tabIndex=1): We'll want to focus tab C (now in index 1) - // * C (tabIndex=2): We'll want to focus tab D (now in index 2) - // * D (tabIndex=3): We'll want to focus tab C (now in index 2) - const auto newSelectedIndex = std::clamp(tabIndex, 0, _tabs.Size() - 1); - // _UpdatedSelectedTab will do the work of setting up the new tab as - // the focused one, and unfocusing all the others. - auto newSelectedTab{ _tabs.GetAt(newSelectedIndex) }; - _UpdatedSelectedTab(newSelectedTab); - - // Also, we need to _manually_ set the SelectedItem of the tabView - // here. If we don't, then the TabView will technically not have a - // selected item at all, which can make things like ClosePane not - // work correctly. - _tabView.SelectedItem(newSelectedTab.TabViewItem()); - } + const auto newSelectedTab = _mruTabs.GetAt(0); + _UpdatedSelectedTab(newSelectedTab); + _tabView.SelectedItem(newSelectedTab.TabViewItem()); } // GH#5559 - If we were in the middle of a drag/drop, end it by clearing diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.cpp b/src/cascadia/TerminalSettingsEditor/Appearances.cpp index ec66af8cd0..0cc3ed8bdf 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.cpp +++ b/src/cascadia/TerminalSettingsEditor/Appearances.cpp @@ -12,6 +12,7 @@ #include "Appearances.g.cpp" using namespace winrt::Windows::UI::Text; +using namespace winrt::Windows::UI::Core; using namespace winrt::Windows::UI::Xaml; using namespace winrt::Windows::UI::Xaml::Controls; using namespace winrt::Windows::UI::Xaml::Data; @@ -1221,8 +1222,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation fontSpec = fontName; } - sender.Text(fontSpec); - // Normally we'd just update the model property in LostFocus above, but because WinUI is the Ralph Wiggum // among the UI frameworks, it raises the LostFocus event _before_ the QuerySubmitted event. // So, when you press Save, the model will have the wrong font face string, because LostFocus was raised too early. @@ -1233,11 +1232,20 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation // You can't just do IsSuggestionListOpen(false) either, because you can show the list with that property but not hide it. // So, we update the model manually and assign focus to the parent container. // - // BUT you can't just focus the parent container, because of a weird interaction with AutoSuggestBox where it'll refuse to lose - // focus if you picked a suggestion that matches the current fontSpec. So, we unfocus it first and then focus the parent container. - _updateFontName(fontSpec); - sender.Focus(FocusState::Unfocused); - FontFaceContainer().Focus(FocusState::Programmatic); + // Queue the selected-suggestion commit so AutoSuggestBox can finish processing Enter before we change its text/model. + // Do not manually unfocus the AutoSuggestBox here. Its Focus(FocusState::Unfocused) path crashes during keyboard commits. + Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [weakThis{ get_weak() }, weakSender{ winrt::make_weak(sender) }, fontSpec{ std::move(fontSpec) }]() { + if (const auto self{ weakThis.get() }) + { + if (const auto box{ weakSender.get() }) + { + box.Text(fontSpec); + } + + self->_updateFontName(fontSpec); + self->FontFaceContainer().Focus(FocusState::Programmatic); + } + }); } void Appearances::FontFaceBox_TextChanged(const AutoSuggestBox& sender, const AutoSuggestBoxTextChangedEventArgs& args) diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index 22ec4a4fe6..3573cbbf3b 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -395,7 +395,14 @@ void SettingsLoader::FindFragmentsAndMergeIntoUserSettings(bool generateExtensio try { const auto catalog = AppExtensionCatalog::Open(AppExtensionHostName); - extensions = extractValueFromTaskWithoutMainThreadAwait(catalog.FindAllAsync()); + if (auto catalog2{ catalog.try_as() }) + { + extensions = catalog2.FindAll(); + } + else + { + extensions = extractValueFromTaskWithoutMainThreadAwait(catalog.FindAllAsync()); + } } CATCH_LOG(); @@ -417,20 +424,37 @@ void SettingsLoader::FindFragmentsAndMergeIntoUserSettings(bool generateExtensio continue; } - // Likewise, getting the public folder from an extension is an async operation. - auto foundFolder = extractValueFromTaskWithoutMainThreadAwait(ext.GetPublicFolderAsync()); - if (!foundFolder) + winrt::hstring publicFolderPath; + if (auto ext3{ ext.try_as() }) { - continue; + // Windows 11 24H2 and above support a much faster, much less + // Windows.Storage-y API. + publicFolderPath = ext3.GetPublicPath(); + if (publicFolderPath.empty()) + { + // No point in falling through to GetPublicFolderAsync; + // it won't work. + continue; + } + } + else + { + // Likewise, getting the public folder from an extension is an async operation. + auto foundFolder = extractValueFromTaskWithoutMainThreadAwait(ext.GetPublicFolderAsync()); + if (!foundFolder) + { + continue; + } + + // the StorageFolder class has its own methods for obtaining the files within the folder + // however, all those methods are Async methods + // you may have noticed that we need to resort to clunky implementations for async operations + // (they are in extractValueFromTaskWithoutMainThreadAwait) + // so for now we will just take the folder path and access the files that way + publicFolderPath = foundFolder.Path(); } - // the StorageFolder class has its own methods for obtaining the files within the folder - // however, all those methods are Async methods - // you may have noticed that we need to resort to clunky implementations for async operations - // (they are in extractValueFromTaskWithoutMainThreadAwait) - // so for now we will just take the folder path and access the files that way - const auto path = buildPath(foundFolder.Path(), FragmentsSubDirectory); - + const auto path = buildPath(publicFolderPath, FragmentsSubDirectory); if (std::filesystem::is_directory(path)) { // MSIX does not support machine-wide scope diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.cpp b/src/cascadia/WindowsTerminal/WindowEmperor.cpp index c714c5e6e4..70a3988d80 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.cpp +++ b/src/cascadia/WindowsTerminal/WindowEmperor.cpp @@ -544,6 +544,15 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow) __assume(false); } + // !! LOAD BEARING !! + // This prevents loader lock contention with some versions of the nvidia + // driver, which calls SHGetKnownFolderPath triggering a delay load while + // under lock during application startup. See GH#20348. + { + wil::unique_cotaskmem_string localAppDataFolder; + SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &localAppDataFolder); + } + _app = winrt::TerminalApp::App{}; _app.Logic().ReloadSettings(); diff --git a/src/cascadia/WindowsTerminal/pch.h b/src/cascadia/WindowsTerminal/pch.h index 2b48aae8c9..10640ef474 100644 --- a/src/cascadia/WindowsTerminal/pch.h +++ b/src/cascadia/WindowsTerminal/pch.h @@ -32,6 +32,7 @@ Abstract: #include #include #include +#include // Manually include til after we include Windows.Foundation to give it winrt superpowers #define BLOCK_TIL diff --git a/src/host/ft_host/testmd.definition b/src/host/ft_host/testmd.definition index 1eb5c9eeb2..27b0545090 100644 --- a/src/host/ft_host/testmd.definition +++ b/src/host/ft_host/testmd.definition @@ -10,6 +10,10 @@ }, "Dependencies": { "Files": [ + { + "SourcePath": "$(PROJECT_ROOT)\\core\\console\\open\\src\\Terminal.wprp", + "DestinationFolderPath": "$$(TEST_DEPLOY_BIN)" + }, { "SourcePath": "$(PROJECT_ROOT)\\core\\console\\open\\src\\ConsolePerf.wprp", "DestinationFolderPath": "$$(TEST_DEPLOY_BIN)" @@ -23,7 +27,22 @@ "Packages": [ "Microsoft.Console.Tools.Nihilist" ] }, "Logs": [ ], - "Plugins": [ ], + "Plugins": [ + { + "Type": "Microsoft.TestInfrastructure.UniversalTest.TestMD.Plugins.Etw.EtwPlugin", + "Name": "Collecting conhost logs during feature tests", + "Parameters": { + "$schema": "Microsoft.TestInfrastructure.UniversalTest.TestMD.Plugins.Etw.EtwPlugin-1.json", + "Profiles": [ + { + "ProfileFile": "Terminal.wprp", + "Profile": "DefTerm" + } + ], + "InstanceName": "conhost_FeatureTests" + } + } + ], "Profiles": [ { "Name": "Performance",