首页
/ PowerToys C++ 公共基础设施解析:通用工具类、Helpers 与 Toast 通知 API

PowerToys C++ 公共基础设施解析:通用工具类、Helpers 与 Toast 通知 API

2026-09-06 17:22:52作者:傅爽业Veleda

本文基于仓库开发文档 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();

值得注意的两处源码细节:

  1. 安全姿态:头文件明确约束"出站客户端绝不能授予服务器可模拟身份的令牌",并内联定义了 ClientOpenFlags = FILE_FLAG_OVERLAPPED | SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATIONsrc/common/interop/two_way_pipe_message_ipc.h),即客户端以 SECURITY_IDENTIFICATION 级别打开命名管道,降低被利用做身份模拟的风险。
  2. 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 宏下的 FailThreadStartAfterSetWaitNamedPipeEnteredEvent 等,见 头文件),测试用例位于 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:GetScreenDPIForMonitorGetScreenDPIForWindowGetScreenDPIForPointGetScreenDPIForCursor
  • 逻辑↔物理坐标双向换算:Convert(width/height 或 RECT 重载)、ConvertByCursorPositionInverseConvert。悬浮窗、覆盖层类模块(如 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_physicalwidth_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_menustart_color_menudesktop_fill_colorlight_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.hsrc/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_scheduledprogress_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 展示了完整生成链路(源码):

  1. Windows toast XML schema 拼接 <toast><visual><binding template="ToastGeneric">,title 与 message 分别绑定 text id="1"/"2"(源码注释强调这些 XML 标签字符串不得本地化);
  2. 可选附加 launch 属性与 activationType="protocol"(用于点击后通过 URI 启动应用);
  3. 遍历 actions,用 std::visit + 重载 lambda 为三类按钮生成对应 <action> 元素。后台激活按钮的 arguments 会写入 button_id=<i>&handler=<handler_id>,即原文档"按钮按下后回调拿到 button_id"的机制在 XML 层的落点;
  4. 进度条用 <progress title="{progressTitle}" value="{progressValue}" .../> 占位,随后以 NotificationData 的 StringMap 注入真实数值,progressstd::clamp[0,1],并自动生成百分比字符串;
  5. 所有通知统一归入 DEFAULT_TOAST_GROUP = L"PowerToysToastTag" 分组;带 tag(长度 < 64)时可去重:若 resend_if_scheduled 为 false 且计划队列中已存在同 tag 通知则直接跳过;
  6. 通过 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 }
    };
}

当前实现的三块拼图:

  1. 处理器注册表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_idhandler 两个参数,在 handlers_map 中查不到时直接返回;
  2. COM 激活对象notifications.cpp 中的 NotificationActivator 实现了 INotificationActivationCallbackDECLSPEC_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 调用了这一循环;
  3. 进程边界:后台激活意味着处理器可能在独立进程(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(...);若需要按钮回调,在 BackgroundActivatorhandlers_map 中登记处理函数;
  • 显示器/DPI:包含 common/Display/monitors.hcommon/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}/ 下的子目录文件,Animationstart_visible 等条目对应的文件在当前仓库中已不可见。以本文给出的当前路径为准查阅源码,能更准确地对接这些公共能力。

登录后查看全文
热门项目推荐
相关项目推荐