Compare commits

...

14 Commits

Author SHA1 Message Date
Gordon Lam (SH)
43c89141a4 fix(HttpClient): take Uri by value in request() to prevent dangling reference
The coroutine suspends at co_await, so a const-ref to a temporary Uri
would dangle. Take by value like the download() overloads already do.
2026-02-11 18:23:43 +08:00
Gordon Lam (SH)
f2f17eb0f0 fix(ZoomIt): replace utils::async_task with wil::task in CaptureScreenshotAsync
The include for async_task.h was removed but the type usage remained.
Change utils::async_task<T> to wil::task<T> and add wil/coroutine.h to pch.
2026-02-11 18:19:59 +08:00
Gordon Lam
13cc6fd13e Update src/modules/ZoomIt/ZoomIt/Zoomit.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-11 18:15:02 +08:00
Gordon Lam (SH)
eedc6ceccc Replace custom utils::async_task with wil::task
- Delete src/common/utils/async_task.h (custom coroutine type)
- Use wil::task<T> from wil/coroutine.h (WIL already a dependency)
- wil::task provides WaitOnAddress-based .get(), co_await support,
  COM error preservation, and apartment awareness
- Add #include <wil/coroutine.h> to pch.h files (updating, runner)
- Update callers to std::move(task).get() (wil::task::get is &&-qualified)
- Remove AsyncTask.Tests.cpp (no need to test third-party wil::task)
2026-02-11 17:55:59 +08:00
Gordon Lam (SH)
3b53810727 fix(async_task): fix use-after-free race and add unit tests
Fix a race condition in final_awaiter::await_suspend where
p.ready.release() allows the .get() caller to destroy the coroutine
frame while await_suspend is still accessing p.continuation. Fix by
saving continuation to a local variable before releasing the semaphore.

Add 13 unit tests covering:
- Immediate co_return with .get() (int, string, zero, negative)
- Exception propagation from coroutine body
- Delayed completion on a background thread (via thread_delay_awaitable)
- co_await chaining (async_task awaiting async_task, both immediate and delayed)
- Move-only return types
- Move construction and move assignment of async_task
- .get() called from a worker thread
- Multiple independent tasks

Tests use a pure C++20 thread_delay_awaitable instead of WinRT
resume_after to avoid WinRT apartment dependencies in the test DLL.
2026-02-11 14:19:22 +08:00
Gordon Lam (SH)
b1f933c0b3 fix(updating): revert accidental filename pattern change and improve async_task
- Revert INSTALLER_FILENAME_PATTERN_USER back to 'powertoysusersetup'
  (was accidentally changed to 'powertoyssetup-user' which would break
  installer asset matching on GitHub releases)
- Replace busy-wait spin loop in async_task::get() with
  std::binary_semaphore for efficient blocking
- Add operator co_await() so async_task can be awaited by other
  coroutines, not just consumed synchronously via .get()
- Add trailing newline to updating.h
2026-02-11 13:23:46 +08:00
Gordon Lam (SH)
876b03595f refactor: address review comments - use async_task<T>, fix deref-before-check, fix coroutine param lifetimes
Address all 5 Copilot review comments on PR #45522:

1. updating.h/cpp: Replace IAsyncAction + out-parameter pattern with new
   utils::async_task<T> that supports co_return of arbitrary (non-WinRT) types.
   IAsyncOperation<T> cannot be used because github_version_result and
   std::optional<fs::path> are not WinRT-projected types.

2. PowerToys.Update.cpp: Fix undefined behavior where *new_version_info was
   dereferenced before checking if the expected had a value. Now checks
   !new_version_info first.

3. Zoomit.cpp: Same IAsyncAction -> async_task<com_ptr<ID3D11Texture2D>>
   conversion, eliminating the fragile out-parameter pattern.

4. HttpClient.h download(): Change dstFilePath and progressUpdateCallback
   from const reference to value parameters to prevent dangling references
   after coroutine suspension.

5. HttpClient.h: Add missing #include <string> (was transitively included
   via <future> which was removed).
2026-02-11 11:11:24 +08:00
Gordon Lam (SH)
2f829a0a15 docs: add IAsyncAction out-parameter contract comments and fix trailing newline
Address PR review findings:
- Add documentation comments explaining the caller contract for IAsyncAction
  functions that use out-parameter references (updating.h, Zoomit.cpp)
- Fix missing trailing newline in installer.h
2026-02-11 10:23:15 +08:00
Gordon Lam (SH)
ac1a551605 refactor(updating): make get_github_version_info_async and download_new_version_async true coroutines
Both functions now return IAsyncAction and use co_await internally instead
of blocking with .get(). Output is returned via reference parameters since
std::expected and std::optional<path> are not WinRT-projectable types.

Sync callers block at the call site with .get(), while future async
callers can co_await these functions directly.
2026-02-11 07:51:11 +08:00
Gordon Lam (SH)
9194bae1ee refactor(updating): use WinRT async types 2026-02-09 21:07:58 +08:00
Gordon Lam (SH)
8f57522228 refactor(updating): update async methods to use std::expected and co_await 2026-02-09 21:07:53 +08:00
Gordon Lam (SH)
7578c77877 refactor(updating): replace std::future with IAsyncOperation for async methods 2026-02-09 21:07:49 +08:00
Gordon Lam (SH)
64ac14faa6 build(modules): remove /await option from multiple project files 2026-02-09 21:07:45 +08:00
Gordon Lam (SH)
1a396dfd85 build(updating): add WinRT coroutine support and adjust output directories 2026-02-09 21:07:28 +08:00
20 changed files with 35 additions and 41 deletions

View File

@@ -64,7 +64,6 @@
<TreatWarningAsError>true</TreatWarningAsError>
<LanguageStandard>stdcpplatest</LanguageStandard>
<BuildStlModules>false</BuildStlModules>
<AdditionalOptions>/await %(AdditionalOptions)</AdditionalOptions>
<!-- TODO: _SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING for compatibility with VS 17.8. Check if we can remove. -->
<PreprocessorDefinitions>_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<!-- CLR + CFG are not compatible >:{ -->

View File

@@ -88,7 +88,7 @@
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>inc;..\..\src\;..\..\src\common\Telemetry;telemetry;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalOptions>/await /Zc:twoPhase- /Wv:18 %(AdditionalOptions)</AdditionalOptions>
<AdditionalOptions>/Zc:twoPhase- /Wv:18 %(AdditionalOptions)</AdditionalOptions>
<WarningLevel>Level4</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
</ClCompile>

View File

@@ -57,7 +57,7 @@ std::optional<fs::path> ObtainInstaller(bool& isUpToDate)
auto state = UpdateState::read();
const auto new_version_info = get_github_version_info_async().get();
const auto new_version_info = std::move(get_github_version_info_async()).get();
if (std::holds_alternative<version_up_to_date>(*new_version_info))
{
isUpToDate = true;
@@ -76,7 +76,7 @@ std::optional<fs::path> ObtainInstaller(bool& isUpToDate)
// Cleanup old updates before downloading the latest
updating::cleanup_updates();
auto downloaded_installer = download_new_version(std::get<new_version_download_info>(*new_version_info)).get();
auto downloaded_installer = std::move(download_new_version_async(std::get<new_version_download_info>(*new_version_info))).get();
if (!downloaded_installer)
{
Logger::error("Couldn't download new installer");

View File

@@ -18,7 +18,7 @@ namespace // Strings in this namespace should not be localized
namespace updating
{
std::future<bool> uninstall_previous_msix_version_async()
winrt::Windows::Foundation::IAsyncOperation<bool> uninstall_previous_msix_version_async()
{
winrt::Windows::Management::Deployment::PackageManager package_manager;

View File

@@ -2,11 +2,11 @@
#include <string>
#include <optional>
#include <future>
#include <winrt/Windows.Foundation.h>
#include <common/version/helper.h>
namespace updating
{
std::future<bool> uninstall_previous_msix_version_async();
}
winrt::Windows::Foundation::IAsyncOperation<bool> uninstall_previous_msix_version_async();
}

View File

@@ -33,6 +33,7 @@
#include <winrt/Windows.System.h>
#include <wil/resource.h>
#include <wil/coroutine.h>
#endif //PCH_H

View File

@@ -82,11 +82,7 @@ namespace updating
// prevent the warning that may show up depend on the value of the constants (#defines)
#pragma warning(push)
#pragma warning(disable : 4702)
#if USE_STD_EXPECTED
std::future<std::expected<github_version_info, std::wstring>> get_github_version_info_async(const bool prerelease)
#else
std::future<nonstd::expected<github_version_info, std::wstring>> get_github_version_info_async(const bool prerelease)
#endif
wil::task<github_version_result> get_github_version_info_async(const bool prerelease)
{
// If the current version starts with 0.0.*, it means we're on a local build from a farm and shouldn't check for updates.
if constexpr (VERSION_MAJOR == 0 && VERSION_MINOR == 0)
@@ -170,7 +166,7 @@ namespace updating
return !ec ? std::optional{ installer_download_path } : std::nullopt;
}
std::future<std::optional<std::filesystem::path>> download_new_version(const new_version_download_info& new_version)
wil::task<std::optional<std::filesystem::path>> download_new_version_async(new_version_download_info new_version)
{
auto installer_download_path = create_download_path();
if (!installer_download_path)

View File

@@ -2,7 +2,6 @@
#include <optional>
#include <string>
#include <future>
#include <filesystem>
#include <variant>
#include <winrt/Windows.Foundation.h>
@@ -16,6 +15,7 @@
#endif
#include <common/version/helper.h>
#include <wil/coroutine.h>
namespace updating
{
@@ -32,13 +32,15 @@ namespace updating
};
using github_version_info = std::variant<new_version_download_info, version_up_to_date>;
std::future<std::optional<std::filesystem::path>> download_new_version(const new_version_download_info& new_version);
std::filesystem::path get_pending_updates_path();
#if USE_STD_EXPECTED
std::future<std::expected<github_version_info, std::wstring>> get_github_version_info_async(const bool prerelease = false);
using github_version_result = std::expected<github_version_info, std::wstring>;
#else
std::future<nonstd::expected<github_version_info, std::wstring>> get_github_version_info_async(const bool prerelease = false);
using github_version_result = nonstd::expected<github_version_info, std::wstring>;
#endif
wil::task<github_version_result> get_github_version_info_async(bool prerelease = false);
wil::task<std::optional<std::filesystem::path>> download_new_version_async(new_version_download_info new_version);
std::filesystem::path get_pending_updates_path();
void cleanup_updates();
// non-localized

View File

@@ -1,6 +1,7 @@
#pragma once
#include <future>
#include <functional>
#include <string>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Storage.Streams.h>
#include <winrt/Windows.Web.Http.h>
@@ -21,15 +22,15 @@ namespace http
headers.UserAgent().TryParseAdd(USER_AGENT);
}
std::future<std::wstring> request(const winrt::Windows::Foundation::Uri& url)
winrt::Windows::Foundation::IAsyncOperation<winrt::hstring> request(winrt::Windows::Foundation::Uri url)
{
auto response = co_await m_client.GetAsync(url);
(void)response.EnsureSuccessStatusCode();
auto body = co_await response.Content().ReadAsStringAsync();
co_return std::wstring(body);
co_return body;
}
std::future<void> download(const winrt::Windows::Foundation::Uri& url, const std::wstring& dstFilePath)
winrt::Windows::Foundation::IAsyncAction download(winrt::Windows::Foundation::Uri url, std::wstring dstFilePath)
{
auto response = co_await m_client.GetAsync(url);
(void)response.EnsureSuccessStatusCode();
@@ -38,7 +39,7 @@ namespace http
file_stream.Close();
}
std::future<void> download(const winrt::Windows::Foundation::Uri& url, const std::wstring& dstFilePath, const std::function<void(float)>& progressUpdateCallback)
winrt::Windows::Foundation::IAsyncAction download(winrt::Windows::Foundation::Uri url, std::wstring dstFilePath, std::function<void(float)> progressUpdateCallback)
{
auto response = co_await m_client.GetAsync(url, HttpCompletionOption::ResponseHeadersRead);
response.EnsureSuccessStatusCode();

View File

@@ -13,7 +13,6 @@
<ConformanceMode>false</ConformanceMode>
<TreatWarningAsError>true</TreatWarningAsError>
<LanguageStandard>stdcpplatest</LanguageStandard>
<AdditionalOptions>/await %(AdditionalOptions)</AdditionalOptions>
<PreprocessorDefinitions>_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>

View File

@@ -13,7 +13,6 @@
<ConformanceMode>false</ConformanceMode>
<TreatWarningAsError>true</TreatWarningAsError>
<LanguageStandard>stdcpplatest</LanguageStandard>
<AdditionalOptions>/await %(AdditionalOptions)</AdditionalOptions>
<PreprocessorDefinitions>_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>

View File

@@ -13,7 +13,6 @@
<ConformanceMode>false</ConformanceMode>
<TreatWarningAsError>true</TreatWarningAsError>
<LanguageStandard>stdcpplatest</LanguageStandard>
<AdditionalOptions>/await %(AdditionalOptions)</AdditionalOptions>
<PreprocessorDefinitions>_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>

View File

@@ -5140,7 +5140,7 @@ bool IsPenInverted( WPARAM wParam )
// Captures the specified screen using the capture APIs
//
//----------------------------------------------------------------------------
std::future<winrt::com_ptr<ID3D11Texture2D>> CaptureScreenshotAsync(winrt::IDirect3DDevice const& device, winrt::GraphicsCaptureItem const& item, winrt::DirectXPixelFormat const& pixelFormat)
wil::task<winrt::com_ptr<ID3D11Texture2D>> CaptureScreenshotAsync(winrt::IDirect3DDevice const& device, winrt::GraphicsCaptureItem const& item, winrt::DirectXPixelFormat const& pixelFormat)
{
auto d3dDevice = GetDXGIInterfaceFromObject<ID3D11Device>(device);
winrt::com_ptr<ID3D11DeviceContext> d3dContext;
@@ -5176,9 +5176,7 @@ std::future<winrt::com_ptr<ID3D11Texture2D>> CaptureScreenshotAsync(winrt::IDire
framePool.Close();
auto texture = GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame.Surface());
auto result = util::CopyD3DTexture(d3dDevice, texture, true);
co_return result;
co_return util::CopyD3DTexture(d3dDevice, texture, true);
}
//----------------------------------------------------------------------------
@@ -5205,10 +5203,7 @@ winrt::com_ptr<ID3D11Texture2D>CaptureScreenshot(winrt::DirectXPixelFormat const
auto item = util::CreateCaptureItemForMonitor(hMon);
auto capture = CaptureScreenshotAsync(device, item, pixelFormat);
capture.wait();
return capture.get();
return CaptureScreenshotAsync(device, item, pixelFormat).get();
}

View File

@@ -69,6 +69,7 @@
// WIL
#include <wil/com.h>
#include <wil/resource.h>
#include <wil/coroutine.h>
// DirectX
#include <d3d11_4.h>

View File

@@ -201,7 +201,7 @@
<None Include="Microsoft.Terminal.UI.def" />
</ItemGroup>
<PropertyGroup>
<OutDir>..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\CmdPal\</OutDir>
<OutDir>$(RepoRoot)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal\</OutDir>
<IntDir>obj\$(Platform)\$(Configuration)\</IntDir>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />

View File

@@ -10,7 +10,6 @@
<ConformanceMode>false</ConformanceMode>
<TreatWarningAsError>true</TreatWarningAsError>
<LanguageStandard>stdcpplatest</LanguageStandard>
<AdditionalOptions>/await %(AdditionalOptions)</AdditionalOptions>
<PreprocessorDefinitions>_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>

View File

@@ -14,7 +14,6 @@
<ConformanceMode>false</ConformanceMode>
<TreatWarningAsError>true</TreatWarningAsError>
<LanguageStandard>stdcpplatest</LanguageStandard>
<AdditionalOptions>/await %(AdditionalOptions)</AdditionalOptions>
<PreprocessorDefinitions>_UNICODE;UNICODE;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>

View File

@@ -15,6 +15,8 @@
<Nullable>enable</Nullable>
<ProjectGuid>{AF2349B8-E5B6-4004-9502-687C1C7730B1}</ProjectGuid>
<AssemblyName>PowerToys.PreviewHandlerCommon</AssemblyName>
<!-- AutoUnify resolves WindowsBase version conflict (WebView2.Wpf refs .NET 5 version, we target .NET 9) -->
<AutoUnifyAssemblyReferences>true</AutoUnifyAssemblyReferences>
</PropertyGroup>
<ItemGroup>

View File

@@ -173,7 +173,8 @@ void ProcessNewVersionInfo(const github_version_info& version_info,
// Cleanup old updates before downloading the latest
updating::cleanup_updates();
if (download_new_version(new_version_info).get())
auto downloaded_installer = std::move(download_new_version_async(new_version_info)).get();
if (downloaded_installer)
{
state.state = UpdateState::readyToInstall;
state.downloadedInstallerFilename = new_version_info.installer_filename;
@@ -232,7 +233,7 @@ void PeriodicUpdateWorker()
bool version_info_obtained = false;
try
{
const auto new_version_info = get_github_version_info_async().get();
const auto new_version_info = std::move(get_github_version_info_async()).get();
if (new_version_info.has_value())
{
version_info_obtained = true;
@@ -272,7 +273,7 @@ void CheckForUpdatesCallback()
auto state = UpdateState::read();
try
{
auto new_version_info = get_github_version_info_async().get();
auto new_version_info = std::move(get_github_version_info_async()).get();
if (!new_version_info)
{
// We couldn't get a new version from github for some reason, log error

View File

@@ -32,3 +32,4 @@
#include <winrt/Windows.Storage.h>
#include <wil/resource.h>
#include <wil/coroutine.h>