From a916704a6f5eaa8e646e94e84dbd25d869f32891 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Tue, 12 Aug 2025 19:09:50 -0500 Subject: [PATCH] Use a new API to propagate foreground state to child processes (#19192) Windows 11 uses some additional signals to determine what the user cares about and give it a bit of a QoS boost. One of those signals is whether it is associated with a window that is in the foreground or which has input focus. Association today takes two forms: - Process has a window which is in the foreground or which has input focus - Process has a *parent* that meets the above criterion. Console applications that are spawned "inside" terminal by handoff do not fall into either bucket. They don't have a window. Their parent is `dllhost` or `explorer`, who is definitely not in focus. We are piloting a new API that allows us to associate those processes with Terminal's window. When Terminal is in focus, it will attach every process from the active tab to its QoS group. This means that whatever is running in that tab is put into the "foreground" bucket, and everything running in other background tabs is not. When Terminal is out of focus, it attaches every process to its QoS group. This ensures that they all go into the "background" bucket together, following the window. (cherry picked from commit 0d23624fa9d620b13155f491c8b1c5d91dbf0ba4) Service-Card-Id: PVTI_lADOAF3p4s4Axadtzgdh6-Q Service-Version: 1.23 --- dep/nuget/packages.config | 2 +- .../TerminalApp.LocalTests.vcxproj | 1 + src/cascadia/TerminalApp/TabManagement.cpp | 2 + .../TerminalApp/TerminalAppLib.vcxproj | 1 + src/cascadia/TerminalApp/TerminalPage.cpp | 107 +++++++++++++++++- src/cascadia/TerminalApp/TerminalPage.h | 9 +- .../TerminalApp/dll/TerminalApp.vcxproj | 1 + .../TerminalConnection/ConptyConnection.cpp | 16 ++- .../TerminalConnection/ConptyConnection.h | 1 + .../TerminalConnection/ConptyConnection.idl | 2 + src/common.nugetversions.targets | 4 +- src/host/srvinit.cpp | 2 +- 12 files changed, 139 insertions(+), 9 deletions(-) diff --git a/dep/nuget/packages.config b/dep/nuget/packages.config index a641da7c51..f28aa77888 100644 --- a/dep/nuget/packages.config +++ b/dep/nuget/packages.config @@ -5,7 +5,7 @@ - + diff --git a/src/cascadia/LocalTests_TerminalApp/TerminalApp.LocalTests.vcxproj b/src/cascadia/LocalTests_TerminalApp/TerminalApp.LocalTests.vcxproj index 91134e3f08..8ab41a6eca 100644 --- a/src/cascadia/LocalTests_TerminalApp/TerminalApp.LocalTests.vcxproj +++ b/src/cascadia/LocalTests_TerminalApp/TerminalApp.LocalTests.vcxproj @@ -24,6 +24,7 @@ true + true diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index 3b2691df93..adc3cf4150 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -1013,6 +1013,8 @@ namespace winrt::TerminalApp::implementation auto profile = tab_impl->GetFocusedProfile(); _UpdateBackground(profile); } + + _adjustProcessPriorityThrottled->Run(); } CATCH_LOG(); } diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj index 0896bc114a..3230bf625f 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj @@ -26,6 +26,7 @@ true true true + true diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 56d710d192..9d24ef8466 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -6,6 +6,7 @@ #include "TerminalPage.h" #include +#include #include #include @@ -102,6 +103,13 @@ namespace winrt::TerminalApp::implementation // before restoring previous tabs in that scenario. } } + + _adjustProcessPriorityThrottled = std::make_shared>( + DispatcherQueue::GetForCurrentThread(), + std::chrono::milliseconds{ 100 }, + [=]() { + _adjustProcessPriority(); + }); _hostingHwnd = hwnd; return S_OK; } @@ -1946,7 +1954,7 @@ namespace winrt::TerminalApp::implementation return false; } - TermControl TerminalPage::_GetActiveControl() + TermControl TerminalPage::_GetActiveControl() const { if (const auto terminalTab{ _GetFocusedTabImpl() }) { @@ -2410,6 +2418,8 @@ namespace winrt::TerminalApp::implementation auto profile = tab->GetFocusedProfile(); _UpdateBackground(profile); } + + _adjustProcessPriorityThrottled->Run(); } uint32_t TerminalPage::NumberOfTabs() const @@ -4611,9 +4621,12 @@ namespace winrt::TerminalApp::implementation if (const auto coreState{ sender.try_as() }) { const auto newConnectionState = coreState.ConnectionState(); + co_await wil::resume_foreground(Dispatcher()); + + _adjustProcessPriorityThrottled->Run(); + if (newConnectionState == ConnectionState::Failed && !_IsMessageDismissed(InfoBarMessage::CloseOnExitInfo)) { - co_await wil::resume_foreground(Dispatcher()); if (const auto infoBar = FindName(L"CloseOnExitInfoBar").try_as()) { infoBar.IsOpen(true); @@ -4878,6 +4891,94 @@ namespace winrt::TerminalApp::implementation } } + void TerminalPage::_adjustProcessPriority() const + { + // Windowing is single-threaded, so this will not cause a race condition. + static bool supported{ true }; + + if (!supported || !_hostingHwnd.has_value()) + { + return; + } + + std::array processes; + auto it = processes.begin(); + const auto end = processes.end(); + + auto&& appendFromControl = [&](auto&& control) { + if (it == end) + { + return; + } + if (control) + { + if (const auto conn{ control.Connection() }) + { + if (const auto pty{ conn.try_as() }) + { + if (const uint64_t process{ pty.RootProcessHandle() }; process != 0) + { + *it++ = reinterpret_cast(process); + } + } + } + } + }; + + auto&& appendFromTab = [&](auto&& tabImpl) { + if (const auto pane{ tabImpl->GetRootPane() }) + { + pane->WalkTree([&](auto&& child) { + if (const auto& control{ child->GetTerminalControl() }) + { + appendFromControl(control); + } + }); + } + }; + + if (!_activated) + { + // When a window is out of focus, we want to attach all of the processes + // under it to the window so they all go into the background at the same time. + for (auto&& tab : _tabs) + { + if (auto tabImpl{ _GetTerminalTabImpl(tab) }) + { + appendFromTab(tabImpl); + } + } + } + else + { + // When a window is in focus, propagate our foreground boost (if we have one) + // to current all panes in the current tab. + if (auto tabImpl{ _GetFocusedTabImpl() }) + { + appendFromTab(tabImpl); + } + } + + const auto count{ gsl::narrow_cast(it - processes.begin()) }; + const auto hr = TerminalTrySetWindowAssociatedProcesses(_hostingHwnd.value(), count, count ? processes.data() : nullptr); + if (S_FALSE == hr) + { + // Don't bother trying again or logging. The wrapper tells us it's unsupported. + supported = false; + return; + } + + TraceLoggingWrite( + g_hTerminalAppProvider, + "CalledNewQoSAPI", + TraceLoggingValue(reinterpret_cast(_hostingHwnd.value()), "hwnd"), + TraceLoggingValue(count), + TraceLoggingHResult(hr)); +#ifdef _DEBUG + OutputDebugStringW(fmt::format(FMT_COMPILE(L"Submitted {} processes to TerminalTrySetWindowAssociatedProcesses; return=0x{:08x}\n"), count, hr).c_str()); +#endif + } + void TerminalPage::WindowActivated(const bool activated) { // Stash if we're activated. Use that when we reload @@ -4885,6 +4986,8 @@ namespace winrt::TerminalApp::implementation _activated = activated; _updateThemeColors(); + _adjustProcessPriorityThrottled->Run(); + if (const auto& tab{ _GetFocusedTabImpl() }) { if (tab->TabStatus().IsInputBroadcastActive()) diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 9c54d66248..e0b8ded229 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -3,6 +3,8 @@ #pragma once +#include + #include "TerminalPage.g.h" #include "TerminalTab.h" #include "AppKeyBindings.h" @@ -359,8 +361,11 @@ namespace winrt::TerminalApp::implementation bool _MovePane(const Microsoft::Terminal::Settings::Model::MovePaneArgs args); bool _MoveTab(winrt::com_ptr tab, const Microsoft::Terminal::Settings::Model::MoveTabArgs args); + std::shared_ptr> _adjustProcessPriorityThrottled; + void _adjustProcessPriority() const; + template - bool _ApplyToActiveControls(F f) + bool _ApplyToActiveControls(F f) const { if (const auto tab{ _GetFocusedTabImpl() }) { @@ -379,7 +384,7 @@ namespace winrt::TerminalApp::implementation return false; } - winrt::Microsoft::Terminal::Control::TermControl _GetActiveControl(); + winrt::Microsoft::Terminal::Control::TermControl _GetActiveControl() const; std::optional _GetFocusedTabIndex() const noexcept; std::optional _GetTabIndex(const TerminalApp::TabBase& tab) const noexcept; TerminalApp::TabBase _GetFocusedTab() const noexcept; diff --git a/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj b/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj index 6a20f0ac9b..4bc996863a 100644 --- a/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj +++ b/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj @@ -16,6 +16,7 @@ true true + true diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index f511ff8afa..ba14ee618c 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -340,7 +340,13 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation auto ownedSignal = duplicateHandle(signal); auto ownedReference = duplicateHandle(reference); auto ownedServer = duplicateHandle(server); - auto ownedClient = duplicateHandle(client); + wil::unique_hfile ownedClient; + LOG_IF_WIN32_BOOL_FALSE(DuplicateHandle(GetCurrentProcess(), client, GetCurrentProcess(), ownedClient.addressof(), PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_SET_INFORMATION | SYNCHRONIZE, FALSE, 0)); + if (!ownedClient) + { + // If we couldn't reopen the handle with SET_INFORMATION, which may be required to do things like QoS management, fall back. + ownedClient = duplicateHandle(client); + } THROW_IF_FAILED(ConptyPackPseudoConsole(ownedServer.get(), ownedReference.get(), ownedSignal.get(), &_hPC)); ownedServer.release(); @@ -533,6 +539,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation DWORD exitCode{ 0 }; GetExitCodeProcess(_piClient.hProcess, &exitCode); + _piClient.reset(); + // Signal the closing or failure of the process. // exitCode might be STILL_ACTIVE if a client has called FreeConsole() and // thus caused the tab to close, even though the CLI app is still running. @@ -649,6 +657,12 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation } } + uint64_t ConptyConnection::RootProcessHandle() noexcept + { +#pragma warning(disable : 26490) // Don't use reinterpret_cast (type.1). + return reinterpret_cast(_piClient.hProcess); + } + void ConptyConnection::Close() noexcept try { diff --git a/src/cascadia/TerminalConnection/ConptyConnection.h b/src/cascadia/TerminalConnection/ConptyConnection.h index cdcd99981f..f54c92e547 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.h +++ b/src/cascadia/TerminalConnection/ConptyConnection.h @@ -30,6 +30,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation void ShowHide(const bool show); void ReparentWindow(const uint64_t newParent); + uint64_t RootProcessHandle() noexcept; winrt::hstring Commandline() const; winrt::hstring StartingTitle() const; diff --git a/src/cascadia/TerminalConnection/ConptyConnection.idl b/src/cascadia/TerminalConnection/ConptyConnection.idl index 5264238c79..f0f074b01d 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.idl +++ b/src/cascadia/TerminalConnection/ConptyConnection.idl @@ -21,6 +21,8 @@ namespace Microsoft.Terminal.TerminalConnection void ReparentWindow(UInt64 newParent); + UInt64 RootProcessHandle(); + static event NewConnectionHandler NewConnection; static void StartInboundListener(); diff --git a/src/common.nugetversions.targets b/src/common.nugetversions.targets index 837c575fd8..fc2ee146e7 100644 --- a/src/common.nugetversions.targets +++ b/src/common.nugetversions.targets @@ -47,7 +47,7 @@ - + @@ -85,7 +85,7 @@ - + diff --git a/src/host/srvinit.cpp b/src/host/srvinit.cpp index cd68c921e9..49551a25bd 100644 --- a/src/host/srvinit.cpp +++ b/src/host/srvinit.cpp @@ -463,7 +463,7 @@ try TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TIL_KEYWORD_TRACE)); - wil::unique_handle clientProcess{ OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | SYNCHRONIZE, TRUE, static_cast(connectMessage->Descriptor.Process)) }; + wil::unique_handle clientProcess{ OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_SET_INFORMATION | SYNCHRONIZE, TRUE, static_cast(connectMessage->Descriptor.Process)) }; RETURN_LAST_ERROR_IF_NULL(clientProcess.get()); TraceLoggingWrite(g_hConhostV2EventTraceProvider,