Merge branch 'user/migrie/per-window-prequel' of https://github.com/microsoft/terminal into user/migrie/per-window-prequel

This commit is contained in:
Mike Griese
2026-06-24 11:02:00 -05:00
10 changed files with 121 additions and 55 deletions

View File

@@ -78,4 +78,5 @@ wtl
wtt
wttlog
Xamarin
XBOX
xfgcheck

View File

@@ -1139,7 +1139,7 @@ nullopts
NUMSCROLL
NUnit
nupkg
NVIDIA
nvidia
NVT
OACR
ocolor

View File

@@ -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)

View File

@@ -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<winrt::MUX::Controls::TabViewItem>() &&
args.NewFocusedElement().try_as<winrt::MUX::Controls::TabViewItem>())
{
args.Cancel(true);
args.Handled(true);
}
});
UpdateTitle();
_RecalculateAndApplyTabColor();
}

View File

@@ -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<int32_t>(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

View File

@@ -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)

View File

@@ -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<IAppExtensionCatalog2>() })
{
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<IAppExtension3>() })
{
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

View File

@@ -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();

View File

@@ -32,6 +32,7 @@ Abstract:
#include <shellscalingapi.h>
#include <windowsx.h>
#include <ShObjIdl.h>
#include <shlobj_core.h>
// Manually include til after we include Windows.Foundation to give it winrt superpowers
#define BLOCK_TIL

View File

@@ -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",