PowerToys C++ 公共基础设施解析:通用工具类、Helpers 与 Toast 通知 API
本文基于仓库开发文档 doc/devdocs/common/common.md 撰写,系统讲解 Microsoft PowerToys 为各 PowerToy 模块提供的 C++ 公共层:DPI 感知、显示器信息、任务栏定位、设置对象序列化、异步消息队列与双向管道 IPC 等工具类,以及 UWP 风格的 Toast 通知 API。读完本文,你将了解每个公共组件的职责与当前在 src/common 中的真实落位,并掌握如何在模块中接入 Toast 通知(含按钮回调/后台激活机制的完整流程),以及文档中历史路径与现有代码结构的对应关系。
1. 文档定位:PowerToys 的 C++ 公共层是什么
doc/devdocs/common/common.md 是面向 PowerToys 贡献者的开发文档,描述 src/common 下可被各 PowerToy 模块(C++ 宿主、Runner 设置窗口等)复用的类与结构。文档本身标注了 "This document is outdated and will soon be renewed",即它记录的是历史路径下的 API 快照;在梳理时已确认,仓库经过目录重组,原文所列文件大多已迁移到 src/common 下的功能子目录中。本文按原文档骨架(Classes and structures → Helpers → Toast Notifications)展开,并在每一节给出经核实的当前文件路径与源码级细节。
2. 通用类与结构体(Classes and structures)
原文档以条目形式列出了 8 组公共组件,下表演述时均保留原条目,并补充了当前仓库中的实际位置与实现要点。
| 原文档条目 | 原文路径 | 当前仓库路径(已核实) |
|---|---|---|
| class Animation | src/common/animation.h |
该文件在当前 src/common 中已不存在,属于历史遗留条目 |
| class AsyncMessageQueue | src/common/async_message_queue.h |
src/common/interop/async_message_queue.h |
| class TwoWayPipeMessageIPC | src/common/two_way_pipe_message_ipc.h |
src/common/interop/two_way_pipe_message_ipc.h |
| class DPIAware | src/common/dpi_aware.h |
src/common/Display/dpi_aware.h |
| struct MonitorInfo | src/common/monitors.h |
src/common/Display/monitors.h |
| class Settings / PowerToyValues / CustomActionObject | src/common/settings_objects.h |
src/common/SettingsAPI/settings_objects.h |
| class Tasklist | src/common/tasklist_positions.h |
src/common/interop/tasklist_positions.h |
| struct WindowsColors | src/common/windows_colors.h |
src/common/Themes/windows_colors.h |
下面按组件深入说明。
2.1 AsyncMessageQueue:Header-only 异步消息队列
原描述:"Header-only asynchronous message queue. Used by TwoWayPipeMessageIPC。" 当前实现位于 src/common/interop/async_message_queue.h,是一个单文件头文件类,核心成员与行为如下(源码见 该文件):
std::queue<std::wstring> message_queue:消息队列,元素为宽字符串;std::mutex queue_mutex+std::condition_variable message_ready:线程安全的入队/出队同步;bool interrupted:中断标记。队列被中断时pop_message()返回空字符串,供消费线程优雅退出;- 类禁用了拷贝构造与拷贝赋值(
AsyncMessageQueue(const AsyncMessageQueue&);被声明而未定义),保证队列实例唯一的消费者语义。
接口极简:生产者调用 queue_message(std::wstring) 入队并 notify_one(),消费者调用 pop_message() 阻塞取消息。这种"条件变量等待 + 空队列轮询保护"的写法避免了虚假唤醒导致的忙等,是典型的阻塞队列模式。
2.2 TwoWayPipeMessageIPC:Runner 与设置窗口的双向管道通信
原描述:"Header-only asynchronous IPC messaging class. Used by the runner to communicate with the settings window。" 当前类定义位于 src/common/interop/two_way_pipe_message_ipc.h,采用 PIMPL 模式(class TwoWayPipeMessageIPCImpl; 前向声明 + 裸指针成员),对外接口为:
typedef std::function<void(const std::wstring&)> callback_function;
TwoWayPipeMessageIPC(std::wstring _input_pipe_name,
std::wstring _output_pipe_name,
callback_function p_func);
void send(std::wstring msg); // 向管道另一端发送消息
void start(HANDLE _restricted_pipe_token);
void end();
值得注意的两处源码细节:
- 安全姿态:头文件明确约束"出站客户端绝不能授予服务器可模拟身份的令牌",并内联定义了
ClientOpenFlags = FILE_FLAG_OVERLAPPED | SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION(src/common/interop/two_way_pipe_message_ipc.h),即客户端以SECURITY_IDENTIFICATION级别打开命名管道,降低被利用做身份模拟的风险。 - fail-closed 调用方校验:新增了重载
start(HANDLE, const interop_auth::CallerPolicy&),注释说明其用于 Runner 的特权服务端管道——每个连接的客户端在派发前都要经过认证,认证策略"失败即关闭"。
TwoWayPipeMessageIPC 依赖上节的 AsyncMessageQueue 作为接收缓冲,实现文件在 src/common/interop/two_way_pipe_message_ipc.cpp。该组件有专门的故障注入测试钩子(TWO_WAY_PIPE_MESSAGE_IPC_TESTS 宏下的 FailThreadStartAfter、SetWaitNamedPipeEnteredEvent 等,见 头文件),测试用例位于 src/common/UnitTests-CommonUtils/TwoWayPipeMessageIPC.Tests.cpp,覆盖线程启动失败、监听替换等竞态场景——从源码结构看,这是该组件在 Runner 关键路径上稳定性的重要保障。
2.3 DPIAware:跨 DPI 缩放的坐标换算
原描述:"Helper class for creating DPI-aware applications。" 当前位于 src/common/Display/dpi_aware.h,以 namespace DPIAware 提供一组查询与换算函数(源码):
DEFAULT_DPI = 96:默认 DPI 常量,换算的基准;- 按监视器/窗口/点/光标查询 DPI:
GetScreenDPIForMonitor、GetScreenDPIForWindow、GetScreenDPIForPoint、GetScreenDPIForCursor; - 逻辑↔物理坐标双向换算:
Convert(width/height 或 RECT 重载)、ConvertByCursorPosition、InverseConvert。悬浮窗、覆盖层类模块(如 MeasureTool、FancyZones 一类需要精确落位的 UI)通常依赖这组函数在多显示器、多缩放比环境下保持尺寸正确; EnableDPIAwarenessForThisProcess()与GetAwarenessLevel(...):后者把系统返回的DPI_AWARENESS_CONTEXT归一为UNAWARE / SYSTEM_AWARE / PER_MONITOR_AWARE / PER_MONITOR_AWARE_V2 / UNAWARE_GDISCALED五个枚举级别。
2.4 MonitorInfo 与 Box:物理显示器信息
原描述:"Class for obtaining information about physical displays connected to the machine。" 当前位于 src/common/Display/monitors.h。该文件包含两个关键类型:
struct Box(源码):对RECT的轻量封装,提供left()/right()/top()/bottom()/width()/height()以及top_left()、middle()、bottom_right()等九个锚点、inside(POINT)命中判断,并实现了三路比较运算符(C++20<=>)便于排序。源码注释中还留有一行// TODO: merge with FZ::Rect,说明它与 FancyZones 的FZ::Rect存在功能重叠,从源码结构看后续可能合并;class MonitorInfo:以HMONITOR+MONITORINFOEX为核心(源码),定义Size结构同时携带逻辑分辨率(width_logical/height_logical)与物理尺寸(width_physical/height_physical、width_mm/height_mm),是 FancyZones、ZoomIt 等显示器相关模块获取屏幕拓扑的基础。
2.5 Settings / PowerToyValues / CustomActionObject:设置页的数据契约
原描述:"Classes used to define settings screens for the PowerToys modules。" 当前位于 src/common/SettingsAPI/settings_objects.h。这一组类定义了 C++ PowerToy 模块与 WinUI 设置界面之间交换 JSON 的契约:
class Settings(源码):以模块 HINSTANCE 和 PowerToy 名称构造,随后通过链式add_*方法声明设置项。每种控件都有一对重载——一个接收资源 ID(UINT description_resource_id,从模块资源 DLL 取本地化文本),一个直接接收std::wstring_view文本:add_bool_toggle:开关;add_int_spinner:整数步进器(带min/max/step取值范围);add_string/add_multiline_string:单行/多行文本;add_color_picker:颜色选择;add_hotkey:快捷键(接收HotkeyObject);add_choice_group/add_dropdown:单选组与下拉框,选项为<key, text>对向量;add_custom_action:自定义按钮动作;add_header_szLarge:大标题分组。 最终由serialize()/serialize_to_buffer()把内部json::JsonObject m_json导出为字符串,内部字段m_curr_priority用于保持添加顺序稳定;
class PowerToyValues(源码):反方向的数据读取器。可通过PowerToyValues::from_json_string(json, powertoy_key)从 JSON 字符串解析,或PowerToyValues::load_from_settings_file(powertoy_key)直接从设置文件加载;模板方法add_property<T>(name, value)把设置项绑定为强类型属性,供模块读取当前配置。
2.6 Tasklist:任务栏按钮定位
原描述:"Class that can detect the position of the windows buttons on the taskbar. It also detects which window will react to pressing WinKey + number。" 当前位于 src/common/interop/tasklist_positions.h,核心为:
struct TasklistButton
{
wchar_t name[256]; // 按钮名称
int x, y, width, height; // 屏幕坐标与尺寸
int keynum; // 对应 Win+N 的序号
};
extern "C"
{
HWND GetTaskbarHwndForCursorMonitor(HMONITOR monitor); // 取光标所在显示器的任务栏 HWND
bool update_buttons(std::vector<TasklistButton>& buttons);
__declspec(dllexport) TasklistButton* get_buttons(HMONITOR monitor, int* size);
}
从源码结构看,这类"枚举任务栏子窗口 → 记录每个按钮的屏幕矩形与 Win+N 序号"的能力是 MouseJump(鼠标定位跳转)类功能的定位数据源;get_buttons 被 __declspec(dllexport) 导出,说明它同时服务于以 DLL 形式被外部(托管代码)加载的场景。
2.7 WindowsColors:跟随 Windows 配色方案
原描述:"Class for detecting the current Windows color scheme。" 当前位于 src/common/Themes/windows_colors.h,是一个静态方法 + 少量成员的状态结构(源码):
- 调色取值:
get_accent_color()、get_accent_light_1_color()、get_accent_dark_1_color()、get_button_face_color()、get_highlight_color()、get_background_color()等,返回winrt::Windows::UI::Color; - 模式判断:
is_dark_mode(); bool update():刷新一次颜色,返回值表示"是否有值变化",便于模块仅在配色实际改变时重绘;- 成员
accent_color_menu、start_color_menu、desktop_fill_color、light_mode缓存最近一次取到的颜色。
3. 辅助函数(Helpers)
原文档还列出了三组 helpers,当前仓库中的对应情况如下:
- Common helpers(原
src/common/common.h/.cpp,"Various helper functions"):该文件已不在src/common顶层。从各模块的pch.h仍广泛包含common.h的路径(如 src/modules/fancyzones/FancyZonesModuleInterface/pch.h、src/modules/CropAndLock/CropAndLockModuleInterface/pch.h)可以推断,通用头文件被拆分/重组到了src/common/utils/等子目录; - Settings helpers(原
src/common/settings_helpers.h):现位于 src/common/SettingsAPI/settings_helpers.h,与上节settings_objects同目录,提供设置 JSON 的读写辅助; - Start visible helper(原
src/common/start_visible.h/.cpp):包含"检测开始菜单是否可见"的函数,该文件在当前src/common中未找到,属于已被移除或迁移的历史条目,引用它的场景(如按 Start 键定位)可能已由其他实现替代。
4. Toast Notifications API:从文档示例到当前实现
这是原文档最详实的部分。文档给出的原始 API 为:
void show_toast(std::wstring_view message); // #1 简单通知,无回调无按钮
void show_toast_background_activated( // #2 带多按钮 + 后台激活
std::wstring_view message,
std::wstring_view background_handler_id,
std::vector<std::wstring_view> button_labels);
其中 #1 用于发送无回调的纯文本通知,#2 支持显示多个按钮并绑定后台激活处理器;文档也预告了未来可能增加 show_toast_xml 之类的富定制接口。
4.1 当前 API:更丰富的结构化接口
当前实现位于 src/common/notifications/notifications.h,API 已演进为结构化参数形式(源码):
void show_toast(std::wstring plaintext_message, std::wstring title, toast_params params = {});
void show_toast_with_activations(std::wstring plaintext_message,
std::wstring title,
std::wstring_view background_handler_id,
std::vector<action_t> actions,
toast_params params = {},
std::wstring launch_uri = L"");
void update_toast_progress_bar(std::wstring_view tag, progress_bar_params params);
void remove_toasts_by_tag(std::wstring_view tag);
void remove_all_scheduled_toasts();
相比文档快照,新增了若干能力(notifications.h):
action_t = std::variant<link_button, background_activated_button, snooze_button>:按钮从"纯文本标签数组"升级为三种类型——协议链接按钮(可放入右键上下文菜单)、后台激活按钮、以及系统级"稍后提醒(snooze)"按钮(可带最多 5 个时长选项);toast_params:支持tag(配合remove_toasts_by_tag按标签撤销)、resend_if_scheduled、progress_bar进度条参数;update_toast_progress_bar/remove_toasts_by_tag/remove_all_scheduled_toasts:对已发送通知进行更新与清理;override_application_id/run_desktop_app_activator_loop:AppUserModelID 覆写与桌面版激活循环(见 4.3)。
一个真实的调用示例来自 Runner 的更新通知 src/runner/UpdateUtils.cpp,以及 src/runner/main.cpp 中的 notifications::show_toast(GET_RESOURCE_STRING(...).c_str(), L"PowerToys")。
4.2 实现要点:XML 组装与 Toast 分组
notifications.cpp 中的 show_toast_with_activations 展示了完整生成链路(源码):
- 按 Windows toast XML schema 拼接
<toast><visual><binding template="ToastGeneric">,title 与 message 分别绑定text id="1"/"2"(源码注释强调这些 XML 标签字符串不得本地化); - 可选附加
launch属性与activationType="protocol"(用于点击后通过 URI 启动应用); - 遍历
actions,用std::visit+ 重载 lambda 为三类按钮生成对应<action>元素。后台激活按钮的 arguments 会写入button_id=<i>&handler=<handler_id>,即原文档"按钮按下后回调拿到 button_id"的机制在 XML 层的落点; - 进度条用
<progress title="{progressTitle}" value="{progressValue}" .../>占位,随后以NotificationData的 StringMap 注入真实数值,progress被std::clamp到[0,1],并自动生成百分比字符串; - 所有通知统一归入
DEFAULT_TOAST_GROUP = L"PowerToysToastTag"分组;带tag(长度 < 64)时可去重:若resend_if_scheduled为 false 且计划队列中已存在同 tag 通知则直接跳过; - 通过
ToastNotificationManager::CreateToastNotifier(APPLICATION_ID)的Show(notification)发出,异常被静默吞掉——从源码结构看,这是为了保证通知失败不影响主流程。
4.3 后台激活:文档的注册模式与当前实现
原文档给出的接入范式(在 handler_functions.cpp 实现处理函数并经 handlers_map 注册)在当前仓库中依然成立,且与实现一一对应。
文档示例(保留原文骨架,仅把 handler 签名对齐当前实现):
// 某个模块的 .cpp
#include <common/notifications.h>
void some_func() {
// ...
notifications::show_toast_background_activated(
L"Toast message!", // 通知文本
L"awesome_toast", // 激活处理器 id
{ L"Press me!", L"Also could press me!" }); // 按钮
}
// handler_functions.cpp
void awesome_toast_handler(const size_t button_id)
{
switch (button_id)
{
case 0: /* "Press me!" */ break;
case 1: /* "Also could press me!" */ break;
}
}
namespace
{
const std::unordered_map<std::wstring_view, handler_function_t> handlers_map = {
{ L"awesome_toast", awesome_toast_handler }
};
}
当前实现的三块拼图:
- 处理器注册表:src/common/notifications/BackgroundActivator/handler_functions.cpp。注意当前签名已从文档中的
void(IBackgroundTaskInstance, const size_t)简化为void(const size_t button_id)(handler_function_t定义在 handler_functions.h)。dispatch_to_background_handler(argument)用WwwFormUrlDecoder解析button_id与handler两个参数,在handlers_map中查不到时直接返回; - COM 激活对象:notifications.cpp 中的
NotificationActivator实现了INotificationActivationCallback(DECLSPEC_UUID("DD5CACDA-7C2E-4997-A62A-04A597B58F76")),其Activate()会动态LoadLibraryW(L"PowerToys.BackgroundActivatorDLL.dll")并取dispatch_to_background_handler入口完成派发;run_desktop_app_activator_loop()则通过CoRegisterClassObject注册该 COM 对象并运行消息循环。Runner 主入口在 src/runner/main.cpp 调用了这一循环; - 进程边界:后台激活意味着处理器可能在独立进程(BackgroundActivatorDLL)中执行,无法与 PT 进程直接共享内存数据。此外由于 PT 曾是 Desktop Bridge 应用,前台激活按后台激活同等处理,因此没有为前台单独设计 API——这是原文档 Note 中说明的设计动机,理解它对正确使用该 API 很重要(例如不要试图在 handler 中依赖宿主进程的堆数据)。
4.4 相关测试
IPC 相关行为有单元覆盖:src/common/UnitTests-CommonUtils/TwoWayPipeMessageIPC.Tests.cpp;设置对象的序列化行为有 src/common/UnitTests-CommonLib/Settings.Tests.cpp。从源码结构看,Toast API 本身依赖 WinRT 通知子系统,未在当前仓库内发现独立的单元测试工程,验证主要依赖 Runner 内的实际调用路径(如 4.1 节所列的 UpdateUtils.cpp 用例)。
5. 使用指引:在模块中如何引用这些公共组件
结合上述落位,开发新 PowerToy 模块时的实践路径是:
- 通知:包含
common/notifications.h并在模块代码中调用notifications::show_toast(...);若需要按钮回调,在BackgroundActivator的handlers_map中登记处理函数; - 显示器/DPI:包含
common/Display/monitors.h与common/Display/dpi_aware.h(项目 src/common/Display/Display.vcxproj); - 主题色:包含
common/Themes/windows_colors.h(项目 src/common/Themes/Themes.vcxproj); - 设置:模块侧用
PowerToysSettings::Settings声明控件并serialize(),运行时用PowerToyValues::load_from_settings_file(...)读取,均见 src/common/SettingsAPI/settings_objects.h; - Runner↔设置窗口的消息通道使用
TwoWayPipeMessageIPC,注意新代码应评估是否需要带CallerPolicy的认证重载。
6. 小结
doc/devdocs/common/common.md 勾勒出 PowerToys C++ 公共层的三块内容:工具类(DPI、显示器、任务栏、主题色、设置对象)、helpers,以及一套以 XML 组装 + 后台激活回调解耦为核心的 Toast 通知 API。需要提醒的是,该文档自述已过时:文中 /src/common/*.h 顶层路径大多已迁移为 src/common/{Display,Interop/interop,SettingsAPI,Themes,notifications}/ 下的子目录文件,Animation、start_visible 等条目对应的文件在当前仓库中已不可见。以本文给出的当前路径为准查阅源码,能更准确地对接这些公共能力。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00