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.
This commit is contained in:
Dustin L. Howett 2025-08-12 19:09:50 -05:00 committed by GitHub
parent 8a05910e3c
commit 0d23624fa9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 143 additions and 9 deletions

View File

@ -5,7 +5,7 @@
<package id="Microsoft.Internal.PGO-Helpers.Cpp" version="0.2.34" targetFramework="native" /> <package id="Microsoft.Internal.PGO-Helpers.Cpp" version="0.2.34" targetFramework="native" />
<package id="Microsoft.Taef" version="10.93.240607003" targetFramework="native" /> <package id="Microsoft.Taef" version="10.93.240607003" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.230207.1" targetFramework="native" /> <package id="Microsoft.Windows.CppWinRT" version="2.0.230207.1" targetFramework="native" />
<package id="Microsoft.Internal.Windows.Terminal.ThemeHelpers" version="0.7.230706001" targetFramework="native" /> <package id="Microsoft.Internal.Windows.Terminal.ThemeHelpers" version="0.8.250811004" targetFramework="native" />
<package id="Microsoft.VisualStudio.Setup.Configuration.Native" version="2.3.2262" targetFramework="native" developmentDependency="true" /> <package id="Microsoft.VisualStudio.Setup.Configuration.Native" version="2.3.2262" targetFramework="native" developmentDependency="true" />
<package id="Microsoft.UI.Xaml" version="2.8.4" targetFramework="native" /> <package id="Microsoft.UI.Xaml" version="2.8.4" targetFramework="native" />
<package id="Microsoft.Web.WebView2" version="1.0.1661.34" targetFramework="native" /> <package id="Microsoft.Web.WebView2" version="1.0.1661.34" targetFramework="native" />

View File

@ -24,6 +24,7 @@
<PropertyGroup Label="NuGet Dependencies"> <PropertyGroup Label="NuGet Dependencies">
<!-- TerminalCppWinrt is intentionally not set --> <!-- TerminalCppWinrt is intentionally not set -->
<TerminalMUX>true</TerminalMUX> <TerminalMUX>true</TerminalMUX>
<TerminalThemeHelpers>true</TerminalThemeHelpers>
</PropertyGroup> </PropertyGroup>
<Import Project="$(SolutionDir)\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" /> <Import Project="$(SolutionDir)\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />

View File

@ -1009,6 +1009,8 @@ namespace winrt::TerminalApp::implementation
auto profile = tabImpl->GetFocusedProfile(); auto profile = tabImpl->GetFocusedProfile();
_UpdateBackground(profile); _UpdateBackground(profile);
} }
_adjustProcessPriorityThrottled->Run();
} }
CATCH_LOG(); CATCH_LOG();
} }

View File

@ -26,6 +26,7 @@
<TerminalCppWinrt>true</TerminalCppWinrt> <TerminalCppWinrt>true</TerminalCppWinrt>
<TerminalMUX>true</TerminalMUX> <TerminalMUX>true</TerminalMUX>
<TerminalWinGetInterop>true</TerminalWinGetInterop> <TerminalWinGetInterop>true</TerminalWinGetInterop>
<TerminalThemeHelpers>true</TerminalThemeHelpers>
</PropertyGroup> </PropertyGroup>
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" /> <Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)src\common.nugetversions.props" /> <Import Project="$(OpenConsoleDir)src\common.nugetversions.props" />

View File

@ -6,6 +6,7 @@
#include "TerminalPage.h" #include "TerminalPage.h"
#include <LibraryResources.h> #include <LibraryResources.h>
#include <TerminalThemeHelpers.h>
#include <Utils.h> #include <Utils.h>
#include <TerminalCore/ControlKeyStates.hpp> #include <TerminalCore/ControlKeyStates.hpp>
@ -217,6 +218,17 @@ namespace winrt::TerminalApp::implementation
// before restoring previous tabs in that scenario. // before restoring previous tabs in that scenario.
} }
} }
_adjustProcessPriorityThrottled = std::make_shared<ThrottledFunc<>>(
DispatcherQueue::GetForCurrentThread(),
til::throttled_func_options{
.delay = std::chrono::milliseconds{ 100 },
.debounce = true,
.trailing = true,
},
[=]() {
_adjustProcessPriority();
});
_hostingHwnd = hwnd; _hostingHwnd = hwnd;
return S_OK; return S_OK;
} }
@ -2076,7 +2088,7 @@ namespace winrt::TerminalApp::implementation
return false; return false;
} }
TermControl TerminalPage::_GetActiveControl() TermControl TerminalPage::_GetActiveControl() const
{ {
if (const auto tabImpl{ _GetFocusedTabImpl() }) if (const auto tabImpl{ _GetFocusedTabImpl() })
{ {
@ -2537,6 +2549,8 @@ namespace winrt::TerminalApp::implementation
auto profile = tab->GetFocusedProfile(); auto profile = tab->GetFocusedProfile();
_UpdateBackground(profile); _UpdateBackground(profile);
} }
_adjustProcessPriorityThrottled->Run();
} }
uint32_t TerminalPage::NumberOfTabs() const uint32_t TerminalPage::NumberOfTabs() const
@ -4669,9 +4683,12 @@ namespace winrt::TerminalApp::implementation
if (const auto coreState{ sender.try_as<winrt::Microsoft::Terminal::Control::ICoreState>() }) if (const auto coreState{ sender.try_as<winrt::Microsoft::Terminal::Control::ICoreState>() })
{ {
const auto newConnectionState = coreState.ConnectionState(); const auto newConnectionState = coreState.ConnectionState();
co_await wil::resume_foreground(Dispatcher());
_adjustProcessPriorityThrottled->Run();
if (newConnectionState == ConnectionState::Failed && !_IsMessageDismissed(InfoBarMessage::CloseOnExitInfo)) if (newConnectionState == ConnectionState::Failed && !_IsMessageDismissed(InfoBarMessage::CloseOnExitInfo))
{ {
co_await wil::resume_foreground(Dispatcher());
if (const auto infoBar = FindName(L"CloseOnExitInfoBar").try_as<MUX::Controls::InfoBar>()) if (const auto infoBar = FindName(L"CloseOnExitInfoBar").try_as<MUX::Controls::InfoBar>())
{ {
infoBar.IsOpen(true); infoBar.IsOpen(true);
@ -4936,6 +4953,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<HANDLE, 32> 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<winrt::Microsoft::Terminal::TerminalConnection::ConptyConnection>() })
{
if (const uint64_t process{ pty.RootProcessHandle() }; process != 0)
{
*it++ = reinterpret_cast<HANDLE>(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{ _GetTabImpl(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<DWORD>(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<uintptr_t>(_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) void TerminalPage::WindowActivated(const bool activated)
{ {
// Stash if we're activated. Use that when we reload // Stash if we're activated. Use that when we reload
@ -4943,6 +5048,8 @@ namespace winrt::TerminalApp::implementation
_activated = activated; _activated = activated;
_updateThemeColors(); _updateThemeColors();
_adjustProcessPriorityThrottled->Run();
if (const auto& tab{ _GetFocusedTabImpl() }) if (const auto& tab{ _GetFocusedTabImpl() })
{ {
if (tab->TabStatus().IsInputBroadcastActive()) if (tab->TabStatus().IsInputBroadcastActive())

View File

@ -3,6 +3,8 @@
#pragma once #pragma once
#include <ThrottledFunc.h>
#include "TerminalPage.g.h" #include "TerminalPage.g.h"
#include "Tab.h" #include "Tab.h"
#include "AppKeyBindings.h" #include "AppKeyBindings.h"
@ -359,8 +361,11 @@ namespace winrt::TerminalApp::implementation
bool _MovePane(const Microsoft::Terminal::Settings::Model::MovePaneArgs args); bool _MovePane(const Microsoft::Terminal::Settings::Model::MovePaneArgs args);
bool _MoveTab(winrt::com_ptr<Tab> tab, const Microsoft::Terminal::Settings::Model::MoveTabArgs args); bool _MoveTab(winrt::com_ptr<Tab> tab, const Microsoft::Terminal::Settings::Model::MoveTabArgs args);
std::shared_ptr<ThrottledFunc<>> _adjustProcessPriorityThrottled;
void _adjustProcessPriority() const;
template<typename F> template<typename F>
bool _ApplyToActiveControls(F f) bool _ApplyToActiveControls(F f) const
{ {
if (const auto tab{ _GetFocusedTabImpl() }) if (const auto tab{ _GetFocusedTabImpl() })
{ {
@ -379,7 +384,7 @@ namespace winrt::TerminalApp::implementation
return false; return false;
} }
winrt::Microsoft::Terminal::Control::TermControl _GetActiveControl(); winrt::Microsoft::Terminal::Control::TermControl _GetActiveControl() const;
std::optional<uint32_t> _GetFocusedTabIndex() const noexcept; std::optional<uint32_t> _GetFocusedTabIndex() const noexcept;
std::optional<uint32_t> _GetTabIndex(const TerminalApp::Tab& tab) const noexcept; std::optional<uint32_t> _GetTabIndex(const TerminalApp::Tab& tab) const noexcept;
TerminalApp::Tab _GetFocusedTab() const noexcept; TerminalApp::Tab _GetFocusedTab() const noexcept;

View File

@ -16,6 +16,7 @@
<PropertyGroup Label="NuGet Dependencies"> <PropertyGroup Label="NuGet Dependencies">
<TerminalCppWinrt>true</TerminalCppWinrt> <TerminalCppWinrt>true</TerminalCppWinrt>
<TerminalMUX>true</TerminalMUX> <TerminalMUX>true</TerminalMUX>
<TerminalThemeHelpers>true</TerminalThemeHelpers>
</PropertyGroup> </PropertyGroup>
<Import Project="..\..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" /> <Import Project="..\..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)src\common.nugetversions.props" /> <Import Project="$(OpenConsoleDir)src\common.nugetversions.props" />

View File

@ -340,7 +340,13 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
auto ownedSignal = duplicateHandle(signal); auto ownedSignal = duplicateHandle(signal);
auto ownedReference = duplicateHandle(reference); auto ownedReference = duplicateHandle(reference);
auto ownedServer = duplicateHandle(server); 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)); THROW_IF_FAILED(ConptyPackPseudoConsole(ownedServer.get(), ownedReference.get(), ownedSignal.get(), &_hPC));
ownedServer.release(); ownedServer.release();
@ -533,6 +539,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
DWORD exitCode{ 0 }; DWORD exitCode{ 0 };
GetExitCodeProcess(_piClient.hProcess, &exitCode); GetExitCodeProcess(_piClient.hProcess, &exitCode);
_piClient.reset();
// Signal the closing or failure of the process. // Signal the closing or failure of the process.
// exitCode might be STILL_ACTIVE if a client has called FreeConsole() and // 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. // 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<uint64_t>(_piClient.hProcess);
}
void ConptyConnection::Close() noexcept void ConptyConnection::Close() noexcept
try try
{ {

View File

@ -30,6 +30,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
void ShowHide(const bool show); void ShowHide(const bool show);
void ReparentWindow(const uint64_t newParent); void ReparentWindow(const uint64_t newParent);
uint64_t RootProcessHandle() noexcept;
winrt::hstring Commandline() const; winrt::hstring Commandline() const;
winrt::hstring StartingTitle() const; winrt::hstring StartingTitle() const;

View File

@ -21,6 +21,8 @@ namespace Microsoft.Terminal.TerminalConnection
void ReparentWindow(UInt64 newParent); void ReparentWindow(UInt64 newParent);
UInt64 RootProcessHandle();
static event NewConnectionHandler NewConnection; static event NewConnectionHandler NewConnection;
static void StartInboundListener(); static void StartInboundListener();

View File

@ -47,7 +47,7 @@
<Import Project="$(MSBuildThisFileDirectory)..\packages\Microsoft.Taef.10.93.240607003\build\Microsoft.Taef.targets" Condition="'$(TerminalTAEF)' == 'true' and Exists('$(MSBuildThisFileDirectory)..\packages\Microsoft.Taef.10.93.240607003\build\Microsoft.Taef.targets')" /> <Import Project="$(MSBuildThisFileDirectory)..\packages\Microsoft.Taef.10.93.240607003\build\Microsoft.Taef.targets" Condition="'$(TerminalTAEF)' == 'true' and Exists('$(MSBuildThisFileDirectory)..\packages\Microsoft.Taef.10.93.240607003\build\Microsoft.Taef.targets')" />
<!-- TerminalThemeHelpers --> <!-- TerminalThemeHelpers -->
<Import Project="$(MSBuildThisFileDirectory)..\packages\Microsoft.Internal.Windows.Terminal.ThemeHelpers.0.7.230706001\build\native\Microsoft.Internal.Windows.Terminal.ThemeHelpers.targets" Condition="'$(TerminalThemeHelpers)' == 'true' and Exists('$(MSBuildThisFileDirectory)..\packages\Microsoft.Internal.Windows.Terminal.ThemeHelpers.0.7.230706001\build\native\Microsoft.Internal.Windows.Terminal.ThemeHelpers.targets')" /> <Import Project="$(MSBuildThisFileDirectory)..\packages\Microsoft.Internal.Windows.Terminal.ThemeHelpers.0.8.250811004\build\native\Microsoft.Internal.Windows.Terminal.ThemeHelpers.targets" Condition="'$(TerminalThemeHelpers)' == 'true' and Exists('$(MSBuildThisFileDirectory)..\packages\Microsoft.Internal.Windows.Terminal.ThemeHelpers.0.8.250811004\build\native\Microsoft.Internal.Windows.Terminal.ThemeHelpers.targets')" />
<!-- VisualStudioSetup --> <!-- VisualStudioSetup -->
<Import Project="$(MSBuildThisFileDirectory)..\packages\Microsoft.VisualStudio.Setup.Configuration.Native.2.3.2262\build\native\Microsoft.VisualStudio.Setup.Configuration.Native.targets" Condition="'$(TerminalVisualStudioSetup)' == 'true' and Exists('$(MSBuildThisFileDirectory)..\packages\Microsoft.VisualStudio.Setup.Configuration.Native.2.3.2262\build\native\Microsoft.VisualStudio.Setup.Configuration.Native.targets')" /> <Import Project="$(MSBuildThisFileDirectory)..\packages\Microsoft.VisualStudio.Setup.Configuration.Native.2.3.2262\build\native\Microsoft.VisualStudio.Setup.Configuration.Native.targets" Condition="'$(TerminalVisualStudioSetup)' == 'true' and Exists('$(MSBuildThisFileDirectory)..\packages\Microsoft.VisualStudio.Setup.Configuration.Native.2.3.2262\build\native\Microsoft.VisualStudio.Setup.Configuration.Native.targets')" />
@ -85,7 +85,7 @@
<Error Condition="'$(TerminalTAEF)' == 'true' AND !Exists('$(MSBuildThisFileDirectory)..\packages\Microsoft.Taef.10.93.240607003\build\Microsoft.Taef.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(MSBuildThisFileDirectory)..\packages\Microsoft.Taef.10.93.240607003\build\Microsoft.Taef.targets'))" /> <Error Condition="'$(TerminalTAEF)' == 'true' AND !Exists('$(MSBuildThisFileDirectory)..\packages\Microsoft.Taef.10.93.240607003\build\Microsoft.Taef.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(MSBuildThisFileDirectory)..\packages\Microsoft.Taef.10.93.240607003\build\Microsoft.Taef.targets'))" />
<!-- TerminalThemeHelpers --> <!-- TerminalThemeHelpers -->
<Error Condition="'$(TerminalThemeHelpers)' == 'true' AND !Exists('$(MSBuildThisFileDirectory)..\packages\Microsoft.Internal.Windows.Terminal.ThemeHelpers.0.7.230706001\build\native\Microsoft.Internal.Windows.Terminal.ThemeHelpers.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(MSBuildThisFileDirectory)..\packages\Microsoft.Internal.Windows.Terminal.ThemeHelpers.0.7.230706001\build\native\Microsoft.Internal.Windows.Terminal.ThemeHelpers.targets'))" /> <Error Condition="'$(TerminalThemeHelpers)' == 'true' AND !Exists('$(MSBuildThisFileDirectory)..\packages\Microsoft.Internal.Windows.Terminal.ThemeHelpers.0.8.250811004\build\native\Microsoft.Internal.Windows.Terminal.ThemeHelpers.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(MSBuildThisFileDirectory)..\packages\Microsoft.Internal.Windows.Terminal.ThemeHelpers.0.8.250811004\build\native\Microsoft.Internal.Windows.Terminal.ThemeHelpers.targets'))" />
<!-- VisualStudioSetup --> <!-- VisualStudioSetup -->
<Error Condition="'$(TerminalVisualStudioSetup)' == 'true' AND !Exists('$(MSBuildThisFileDirectory)..\packages\Microsoft.VisualStudio.Setup.Configuration.Native.2.3.2262\build\native\Microsoft.VisualStudio.Setup.Configuration.Native.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(MSBuildThisFileDirectory)..\packages\Microsoft.VisualStudio.Setup.Configuration.Native.2.3.2262\build\native\Microsoft.VisualStudio.Setup.Configuration.Native.targets'))" /> <Error Condition="'$(TerminalVisualStudioSetup)' == 'true' AND !Exists('$(MSBuildThisFileDirectory)..\packages\Microsoft.VisualStudio.Setup.Configuration.Native.2.3.2262\build\native\Microsoft.VisualStudio.Setup.Configuration.Native.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(MSBuildThisFileDirectory)..\packages\Microsoft.VisualStudio.Setup.Configuration.Native.2.3.2262\build\native\Microsoft.VisualStudio.Setup.Configuration.Native.targets'))" />

View File

@ -463,7 +463,7 @@ try
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE)); TraceLoggingKeyword(TIL_KEYWORD_TRACE));
wil::unique_handle clientProcess{ OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | SYNCHRONIZE, TRUE, static_cast<DWORD>(connectMessage->Descriptor.Process)) }; wil::unique_handle clientProcess{ OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_SET_INFORMATION | SYNCHRONIZE, TRUE, static_cast<DWORD>(connectMessage->Descriptor.Process)) };
RETURN_LAST_ERROR_IF_NULL(clientProcess.get()); RETURN_LAST_ERROR_IF_NULL(clientProcess.get());
TraceLoggingWrite(g_hConhostV2EventTraceProvider, TraceLoggingWrite(g_hConhostV2EventTraceProvider,