Skip to content

Commit a916704

Browse files
committed
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 0d23624) Service-Card-Id: PVTI_lADOAF3p4s4Axadtzgdh6-Q Service-Version: 1.23
1 parent 9449d0b commit a916704

File tree

12 files changed

+139
-9
lines changed

12 files changed

+139
-9
lines changed

dep/nuget/packages.config

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<package id="Microsoft.Internal.PGO-Helpers.Cpp" version="0.2.34" targetFramework="native" />
66
<package id="Microsoft.Taef" version="10.93.240607003" targetFramework="native" />
77
<package id="Microsoft.Windows.CppWinRT" version="2.0.230207.1" targetFramework="native" />
8-
<package id="Microsoft.Internal.Windows.Terminal.ThemeHelpers" version="0.7.230706001" targetFramework="native" />
8+
<package id="Microsoft.Internal.Windows.Terminal.ThemeHelpers" version="0.8.250811004" targetFramework="native" />
99
<package id="Microsoft.VisualStudio.Setup.Configuration.Native" version="2.3.2262" targetFramework="native" developmentDependency="true" />
1010
<package id="Microsoft.UI.Xaml" version="2.8.4" targetFramework="native" />
1111
<package id="Microsoft.Web.WebView2" version="1.0.1661.34" targetFramework="native" />

src/cascadia/LocalTests_TerminalApp/TerminalApp.LocalTests.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
<PropertyGroup Label="NuGet Dependencies">
2525
<!-- TerminalCppWinrt is intentionally not set -->
2626
<TerminalMUX>true</TerminalMUX>
27+
<TerminalThemeHelpers>true</TerminalThemeHelpers>
2728
</PropertyGroup>
2829

2930
<Import Project="$(SolutionDir)\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />

src/cascadia/TerminalApp/TabManagement.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1013,6 +1013,8 @@ namespace winrt::TerminalApp::implementation
10131013
auto profile = tab_impl->GetFocusedProfile();
10141014
_UpdateBackground(profile);
10151015
}
1016+
1017+
_adjustProcessPriorityThrottled->Run();
10161018
}
10171019
CATCH_LOG();
10181020
}

src/cascadia/TerminalApp/TerminalAppLib.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
<TerminalCppWinrt>true</TerminalCppWinrt>
2727
<TerminalMUX>true</TerminalMUX>
2828
<TerminalWinGetInterop>true</TerminalWinGetInterop>
29+
<TerminalThemeHelpers>true</TerminalThemeHelpers>
2930
</PropertyGroup>
3031
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
3132
<Import Project="$(OpenConsoleDir)src\common.nugetversions.props" />

src/cascadia/TerminalApp/TerminalPage.cpp

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "TerminalPage.h"
77

88
#include <LibraryResources.h>
9+
#include <TerminalThemeHelpers.h>
910
#include <TerminalCore/ControlKeyStates.hpp>
1011
#include <Utils.h>
1112

@@ -102,6 +103,13 @@ namespace winrt::TerminalApp::implementation
102103
// before restoring previous tabs in that scenario.
103104
}
104105
}
106+
107+
_adjustProcessPriorityThrottled = std::make_shared<ThrottledFuncTrailing<>>(
108+
DispatcherQueue::GetForCurrentThread(),
109+
std::chrono::milliseconds{ 100 },
110+
[=]() {
111+
_adjustProcessPriority();
112+
});
105113
_hostingHwnd = hwnd;
106114
return S_OK;
107115
}
@@ -1946,7 +1954,7 @@ namespace winrt::TerminalApp::implementation
19461954
return false;
19471955
}
19481956

1949-
TermControl TerminalPage::_GetActiveControl()
1957+
TermControl TerminalPage::_GetActiveControl() const
19501958
{
19511959
if (const auto terminalTab{ _GetFocusedTabImpl() })
19521960
{
@@ -2410,6 +2418,8 @@ namespace winrt::TerminalApp::implementation
24102418
auto profile = tab->GetFocusedProfile();
24112419
_UpdateBackground(profile);
24122420
}
2421+
2422+
_adjustProcessPriorityThrottled->Run();
24132423
}
24142424

24152425
uint32_t TerminalPage::NumberOfTabs() const
@@ -4611,9 +4621,12 @@ namespace winrt::TerminalApp::implementation
46114621
if (const auto coreState{ sender.try_as<winrt::Microsoft::Terminal::Control::ICoreState>() })
46124622
{
46134623
const auto newConnectionState = coreState.ConnectionState();
4624+
co_await wil::resume_foreground(Dispatcher());
4625+
4626+
_adjustProcessPriorityThrottled->Run();
4627+
46144628
if (newConnectionState == ConnectionState::Failed && !_IsMessageDismissed(InfoBarMessage::CloseOnExitInfo))
46154629
{
4616-
co_await wil::resume_foreground(Dispatcher());
46174630
if (const auto infoBar = FindName(L"CloseOnExitInfoBar").try_as<MUX::Controls::InfoBar>())
46184631
{
46194632
infoBar.IsOpen(true);
@@ -4878,13 +4891,103 @@ namespace winrt::TerminalApp::implementation
48784891
}
48794892
}
48804893

4894+
void TerminalPage::_adjustProcessPriority() const
4895+
{
4896+
// Windowing is single-threaded, so this will not cause a race condition.
4897+
static bool supported{ true };
4898+
4899+
if (!supported || !_hostingHwnd.has_value())
4900+
{
4901+
return;
4902+
}
4903+
4904+
std::array<HANDLE, 32> processes;
4905+
auto it = processes.begin();
4906+
const auto end = processes.end();
4907+
4908+
auto&& appendFromControl = [&](auto&& control) {
4909+
if (it == end)
4910+
{
4911+
return;
4912+
}
4913+
if (control)
4914+
{
4915+
if (const auto conn{ control.Connection() })
4916+
{
4917+
if (const auto pty{ conn.try_as<winrt::Microsoft::Terminal::TerminalConnection::ConptyConnection>() })
4918+
{
4919+
if (const uint64_t process{ pty.RootProcessHandle() }; process != 0)
4920+
{
4921+
*it++ = reinterpret_cast<HANDLE>(process);
4922+
}
4923+
}
4924+
}
4925+
}
4926+
};
4927+
4928+
auto&& appendFromTab = [&](auto&& tabImpl) {
4929+
if (const auto pane{ tabImpl->GetRootPane() })
4930+
{
4931+
pane->WalkTree([&](auto&& child) {
4932+
if (const auto& control{ child->GetTerminalControl() })
4933+
{
4934+
appendFromControl(control);
4935+
}
4936+
});
4937+
}
4938+
};
4939+
4940+
if (!_activated)
4941+
{
4942+
// When a window is out of focus, we want to attach all of the processes
4943+
// under it to the window so they all go into the background at the same time.
4944+
for (auto&& tab : _tabs)
4945+
{
4946+
if (auto tabImpl{ _GetTerminalTabImpl(tab) })
4947+
{
4948+
appendFromTab(tabImpl);
4949+
}
4950+
}
4951+
}
4952+
else
4953+
{
4954+
// When a window is in focus, propagate our foreground boost (if we have one)
4955+
// to current all panes in the current tab.
4956+
if (auto tabImpl{ _GetFocusedTabImpl() })
4957+
{
4958+
appendFromTab(tabImpl);
4959+
}
4960+
}
4961+
4962+
const auto count{ gsl::narrow_cast<DWORD>(it - processes.begin()) };
4963+
const auto hr = TerminalTrySetWindowAssociatedProcesses(_hostingHwnd.value(), count, count ? processes.data() : nullptr);
4964+
if (S_FALSE == hr)
4965+
{
4966+
// Don't bother trying again or logging. The wrapper tells us it's unsupported.
4967+
supported = false;
4968+
return;
4969+
}
4970+
4971+
TraceLoggingWrite(
4972+
g_hTerminalAppProvider,
4973+
"CalledNewQoSAPI",
4974+
TraceLoggingValue(reinterpret_cast<uintptr_t>(_hostingHwnd.value()), "hwnd"),
4975+
TraceLoggingValue(count),
4976+
TraceLoggingHResult(hr));
4977+
#ifdef _DEBUG
4978+
OutputDebugStringW(fmt::format(FMT_COMPILE(L"Submitted {} processes to TerminalTrySetWindowAssociatedProcesses; return=0x{:08x}\n"), count, hr).c_str());
4979+
#endif
4980+
}
4981+
48814982
void TerminalPage::WindowActivated(const bool activated)
48824983
{
48834984
// Stash if we're activated. Use that when we reload
48844985
// the settings, change active panes, etc.
48854986
_activated = activated;
48864987
_updateThemeColors();
48874988

4989+
_adjustProcessPriorityThrottled->Run();
4990+
48884991
if (const auto& tab{ _GetFocusedTabImpl() })
48894992
{
48904993
if (tab->TabStatus().IsInputBroadcastActive())

src/cascadia/TerminalApp/TerminalPage.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
#pragma once
55

6+
#include <ThrottledFunc.h>
7+
68
#include "TerminalPage.g.h"
79
#include "TerminalTab.h"
810
#include "AppKeyBindings.h"
@@ -359,8 +361,11 @@ namespace winrt::TerminalApp::implementation
359361
bool _MovePane(const Microsoft::Terminal::Settings::Model::MovePaneArgs args);
360362
bool _MoveTab(winrt::com_ptr<TerminalTab> tab, const Microsoft::Terminal::Settings::Model::MoveTabArgs args);
361363

364+
std::shared_ptr<ThrottledFuncTrailing<>> _adjustProcessPriorityThrottled;
365+
void _adjustProcessPriority() const;
366+
362367
template<typename F>
363-
bool _ApplyToActiveControls(F f)
368+
bool _ApplyToActiveControls(F f) const
364369
{
365370
if (const auto tab{ _GetFocusedTabImpl() })
366371
{
@@ -379,7 +384,7 @@ namespace winrt::TerminalApp::implementation
379384
return false;
380385
}
381386

382-
winrt::Microsoft::Terminal::Control::TermControl _GetActiveControl();
387+
winrt::Microsoft::Terminal::Control::TermControl _GetActiveControl() const;
383388
std::optional<uint32_t> _GetFocusedTabIndex() const noexcept;
384389
std::optional<uint32_t> _GetTabIndex(const TerminalApp::TabBase& tab) const noexcept;
385390
TerminalApp::TabBase _GetFocusedTab() const noexcept;

src/cascadia/TerminalApp/dll/TerminalApp.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
<PropertyGroup Label="NuGet Dependencies">
1717
<TerminalCppWinrt>true</TerminalCppWinrt>
1818
<TerminalMUX>true</TerminalMUX>
19+
<TerminalThemeHelpers>true</TerminalThemeHelpers>
1920
</PropertyGroup>
2021
<Import Project="..\..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
2122
<Import Project="$(OpenConsoleDir)src\common.nugetversions.props" />

src/cascadia/TerminalConnection/ConptyConnection.cpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,13 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
340340
auto ownedSignal = duplicateHandle(signal);
341341
auto ownedReference = duplicateHandle(reference);
342342
auto ownedServer = duplicateHandle(server);
343-
auto ownedClient = duplicateHandle(client);
343+
wil::unique_hfile ownedClient;
344+
LOG_IF_WIN32_BOOL_FALSE(DuplicateHandle(GetCurrentProcess(), client, GetCurrentProcess(), ownedClient.addressof(), PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_SET_INFORMATION | SYNCHRONIZE, FALSE, 0));
345+
if (!ownedClient)
346+
{
347+
// If we couldn't reopen the handle with SET_INFORMATION, which may be required to do things like QoS management, fall back.
348+
ownedClient = duplicateHandle(client);
349+
}
344350

345351
THROW_IF_FAILED(ConptyPackPseudoConsole(ownedServer.get(), ownedReference.get(), ownedSignal.get(), &_hPC));
346352
ownedServer.release();
@@ -533,6 +539,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
533539
DWORD exitCode{ 0 };
534540
GetExitCodeProcess(_piClient.hProcess, &exitCode);
535541

542+
_piClient.reset();
543+
536544
// Signal the closing or failure of the process.
537545
// exitCode might be STILL_ACTIVE if a client has called FreeConsole() and
538546
// thus caused the tab to close, even though the CLI app is still running.
@@ -649,6 +657,12 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
649657
}
650658
}
651659

660+
uint64_t ConptyConnection::RootProcessHandle() noexcept
661+
{
662+
#pragma warning(disable : 26490) // Don't use reinterpret_cast (type.1).
663+
return reinterpret_cast<uint64_t>(_piClient.hProcess);
664+
}
665+
652666
void ConptyConnection::Close() noexcept
653667
try
654668
{

src/cascadia/TerminalConnection/ConptyConnection.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
3030
void ShowHide(const bool show);
3131

3232
void ReparentWindow(const uint64_t newParent);
33+
uint64_t RootProcessHandle() noexcept;
3334

3435
winrt::hstring Commandline() const;
3536
winrt::hstring StartingTitle() const;

src/cascadia/TerminalConnection/ConptyConnection.idl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ namespace Microsoft.Terminal.TerminalConnection
2121

2222
void ReparentWindow(UInt64 newParent);
2323

24+
UInt64 RootProcessHandle();
25+
2426
static event NewConnectionHandler NewConnection;
2527
static void StartInboundListener();
2628

0 commit comments

Comments
 (0)