PowerToys 新模块开发端到端指南:从模块模板到设置集成、调试与打包
本文基于 PowerToys 官方开发者文档 Creating a new PowerToy: end-to-end developer guide 整理扩写,完整覆盖从零构建一个 PowerToys 工具模块的全流程:模块类型选型、模块接口(Module Interface)的关键方法、服务工程搭建、设置系统集成、调试技巧、WiX 安装包集成以及测试与 OOBE 收尾。读完后,你可以独立完成一个新模块从模板初始化、runner 注册、设置页面接线到可打包交付的全部工作,并理解 runner 与模块 DLL 之间的加载机制。
1. 概览与前置条件
PowerToy 模块是一个自包含(self-contained)的工具单元,集成在 PowerToys 生态内,可以是纯 UI 型、纯后台服务型,或者两者兼有。
1.1 环境要求
- 先按照 Getting Started 指南配置好开发环境,然后参照 调试文档 验证自己能够构建并运行
PowerToys.slnx。 - 可选:WiX v5 工具集(用于制作安装包)。
1.2 标准目录结构
原文档约定的模块目录布局如下,所有模块逻辑都应隔离在 src/modules/<YourModule> 之下:
src/
modules/
your_module/
YourModule.sln
YourModuleInterface/
YourModuleUI/ (if needed)
YourModuleService/ (if needed)
从源码结构看,ModuleInterface 工程产出的是 ModuleInterface.dll,供 runner 在运行时加载并调用;UI 与 Service 工程则按需拆分。
2. 设计与规划:先确定模块类型,再写接口
2.1 选择模块类型
写代码前先想清楚三件事:需要什么样的 UI、生命周期如何、它是常驻服务还是事件驱动。文档给出了四类典型场景,并推荐对照相似的现有模块来研究:
- UI-only(纯界面):例如 ColorPicker,可参考 src/modules/colorPicker;
- Background service(后台服务):例如 LightSwitch、Awake,可参考 src/modules/LightSwitch 与 src/modules/awake;
- Hybrid(UI + 后台逻辑混合):例如 ShortcutGuide,可参考 src/modules/ShortcutGuide;
- C++/C# 互操作:例如 PowerRename,可参考 src/modules/powerrename。
模块主体既可以用 C++ 编写,也可以用 C# 编写。
2.2 模块接口的关键成员
模块入口是 ModuleInterface(模板中为 dllmain.cpp),核心类继承自 PowertoyModuleIface。当前仓库中的接口定义位于 powertoy_module_interface.h,其中声明了 get_name、get_key、get_config、set_config、enable、disable、is_enabled、destroy 等纯虚函数,以及带默认实现的 get_hotkeys、on_hotkey、is_enabled_by_default、gpo_policy_enabled_configuration 等方法(见该文件约 89–156 行)。文档要求你在模板中理解并改造以下九组关键成员:
(1)设置结构体 ModuleSettings
这是模块设置项的存放处,值类型可以是字符串、bool、int,甚至自定义枚举:
struct ModuleSettings {};
(2)接口类声明
完整类定义继承 PowertoyModuleIface,私有成员通常包含启用状态、事件处理逻辑或热键相关字段,公有部分包含构造函数与初始化逻辑:
class ModuleInterface : public PowertoyModuleIface
{
private:
// the private members of the class
// Can include the enabled variable, logic for event handlers, or hotkeys.
public:
// the public members of the class
// Will include the constructor and initialization logic.
}
注意:类中许多函数是样板代码,只需把模块名做简单的字符串替换;下面列出的其余函数才需要较大改动。
(3)GPO 组策略支持
GPO(Group Policy Object)允许管理员在一组机器上统一下发策略。你的模块必须出现在 GPO 的设置列表中,并实现 gpo_policy_enabled_configuration 返回对应模块的策略值。实现上可以右键 powertoys_gpo 对象跳转到定义,为模块配置 getConfiguredModuleEnabledValue:
virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override
{
return powertoys_gpo::getConfiguredModuleEnabledValue();
}
(4)init_settings():初始化设置
从已存在的 settings.json 读取配置,文件不存在时保留默认值:
void ModuleInterface::init_settings()
(5)get_config:向设置面板描述配置
Runner 调用它获取 settings.json 中该模块的配置描述(序列化为 JSON 写入缓冲):
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
(6)set_config:接收新设置
设置面板提交的新值会以序列化 JSON 传入,模块负责解析并持久化:
virtual void set_config(const wchar_t* config) override
(7)call_custom_action:自定义动作
当模块使用 custom_action 类型的设置项时,设置面板点击按钮会触发该方法:
void call_custom_action(const wchar_t* action) override
(8)生命周期函数
控制模块的启用/禁用状态以及默认是否启用:
virtual void enable() // starts the module
virtual void disable() // terminates the module and performs any cleanup
virtual bool is_enabled() // returns if the module is currently enabled
virtual bool is_enabled_by_default() const override // allows the module to dictate whether it should be enabled by default in the PowerToys app.
(9)热键函数
负责热键的解析、上报与响应:
// takes the hotkey from settings into a format that the interface can understand
void parse_hotkey(PowerToysSettings::PowerToyValues& settings)
// returns the hotkeys from settings
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
// performs logic when the hotkey event is fired
virtual bool on_hotkey(size_t hotkeyId) override
2.3 设计原则
- 模块逻辑隔离在
/modules/<YourModule>下,禁止跨模块直接依赖; - 优先复用 src/common 中的共享工具库(如 DPI 辅助
dpi_aware.h、显示器枚举monitors.h等); - init/set/get config 都通过预设函数访问设置,核心实现在
src/common/SettingsAPI的 settings_helpers.h 与 settings_objects.h 中,PowerToysSettings::Settings支持add_bool_toggle、add_int_spinner、add_string、add_color_picker、add_custom_action等控件类型,PowerToyValues提供load_from_settings_file/save_to_settings_file持久化方法。
3. 模块脚手架(Bootstrapping)
-
使用 PowerToy 模块模板 生成模块接口的起始代码。模板安装方式见 tools/project_template/README.md:把
ModuleTemplate.zip放入%USERPROFILE%\Documents\Visual Studio 2022\Templates\ProjectTemplates\(VS 2026 对应Visual Studio 18目录),之后在 Visual Studio 新建工程时于 Visual C++ 选项卡下即可看到。 -
把全部工程与命名空间替换为你的模块名。模板文件 dllmain.cpp 中使用
$projectname$/$safeprojectname$占位符,例如const static wchar_t* MODULE_NAME = L"$projectname$";。 -
更新
.vcxproj与解决方案文件中的 GUID。 -
用你自己的逻辑实现第 2 节提到的各函数。
-
注册模块——这是模块能被 runner 检测到的必要步骤。原文档列出的注册清单包括:
src/runner/modules.hsrc/runner/modules.cppsrc/runner/resource.hsrc/runner/settings_window.hsrc/runner/settings_window.cppsrc/runner/main.cppsrc/common/logger.h(日志)
小技巧:在 runner 代码中搜索已有模块名(如
LightSwitch)可以快速定位这些清单。从当前仓库源码可以确认:runner 在 src/runner/main.cpp 中维护了knownModules列表,逐项形如L"PowerToys.LightSwitchModuleInterface.dll",随后在循环中调用load_powertoy(moduleSubdir)加载并以pt_module->get_key()为键存入modules();设置页映射在 src/runner/settings_window.h 的ESettingsWindowNames枚举与 src/runner/settings_window.cpp 中成对出现;模板 README 还补充说明模块 DLL 名需加入 src/runner/main.cpp 的known_dlls映射才能在运行时被加载。 -
ModuleInterface工程必须产出ModuleInterface.dll,这样 runner 才能与服务交互。
经验提示:模块 ID 不一致(manifest、注册表、服务之间)是最常见的加载失败原因之一,务必保持一致。
4. 编写服务(Service)
每个 PowerToy 的服务形态都不同。建议先在独立工程中开发应用主体,再接入 PowerToys 的设置逻辑;但服务必须先于 runner 接线完成。
要点:
- 服务是与 Module Interface 相互独立的项目,可用 C# 或 C++ 编写;
- 服务图标通过
.rc文件设置; - 服务名在
.vcxproj中通过<TargetName>设置,例如:
<PropertyGroup>
<OutDir>..\..\..\..\$(Platform)\$(Configuration)\$(MSBuildProjectName)\</OutDir>
<TargetName>PowerToys.LightSwitchService</TargetName>
</PropertyGroup>
- 需要查看
.vcxproj内容时,右键工程选择 Unload project; - 服务内读取设置的推荐写法(
ModuleSettings单例随服务代码提供,可参考 src/modules/LightSwitch 中的实现并按需裁剪):
ModuleSettings::instance().InitFileWatcher();
ModuleSettings::instance().LoadSettings();
auto& settings = ModuleSettings::instance().settings();
如果模块带用户界面:
- 使用 WinUI Blank App 模板建工程;
- 遵循 Windows 设计最佳实践;
- 借助 WinUI 3 Gallery 应用辅助 UI 编码。
5. 设置系统集成
PowerToys 的设置按模块以 JSON 形式存放在:
%LOCALAPPDATA%\Microsoft\PowerToys\<module>\settings.json
5.1 C# 侧实现步骤
- 在
src\settings-ui\Settings.UI.Library\下创建<module>Properties.cs与<module>Settings.cs。Properties中定义所有设置的默认值,需与 Module Interface 中声明的设置项一一对应; <module>Settings.cs负责构建settings.json对象,结构应匹配:
public ModuleSettings()
{
Name = ModuleName;
Version = Assembly.GetExecutingAssembly().GetName().Version.ToString();
Properties = new ModuleProperties(); // settings properties you set above.
}
- 在
src\settings-ui\Settings.UI\ViewModels下创建<module>ViewModel.cs——它是 PowerToys 应用内设置页与磁盘设置文件之间的交互层。此处的变更会通过NotifyPropertyChanged事件触发设置监听器; - 在
src\settings-ui\Settings.UI\SettingsXAML\Views创建SettingsPage.xaml,即用户与模块设置交互的页面; - 面向用户的字符串必须走资源串以便本地化(
x:Uid关联 Resources.resw):
// LightSwitch.xaml
<ComboBoxItem
x:Uid="LightSwitch_ModeOff"
AutomationProperties.AutomationId="OffCBItem_LightSwitch"
Tag="Off" />
// Resources.resw
<data name="LightSwitch_ModeOff.Content" xml:space="preserve">
<value>Off</value>
</data>
重要:上面示例用
.Content定位 ComboBox 的内容,这个后缀会随控件类型变化(例如.Text、.Header等)。提醒:通过外部编辑器(VS Code、记事本)的手工修改不会触发设置监听器,只有通过 PowerToys 写入的变更才会触发重载。这一行为与 C++ 侧的文件监听机制(
src/common/SettingsAPI中的 FileWatcher.h)相对应。
5.2 常见坑
- 只使用 WinUI 3 框架,不要使用 UWP;
- 从非 UI 线程更新 UI 时必须使用
DispatcherQueue。
6. 构建与调试
6.1 调试步骤
- 首次调试 PowerToys 的开发者,请先完成 调试文档 中的预调试准备;
- 将 runner 设为启动项目,并确认构建配置与系统架构(ARM64/x64)一致;
- 按 F5 或点击 Local Windows Debugger 按钮开始调试,runner 会随之启动;
- 若要为服务设断点,按 Ctrl+Alt+P 搜索你的服务进程并附加到 runner;
- 用日志记录改动。日志位置:
- Runner 日志:
%LOCALAPPDATA%\Microsoft\PowerToys\RunnerLogs - 模块日志:
%LOCALAPPDATA%\Microsoft\PowerToys\Module\Service\<version>
- Runner 日志:
提示:PowerToys 会激进缓存
.nuget产物,构建行为异常时可用git clean -xfd清理。
runner 侧的加载行为可以在 src/runner/main.cpp 中看到佐证:Debug 模式下某个模块加载失败只会 Logger::warn 记录并继续执行,便于开发者快速迭代,不必为调试单个模块而构建全部模块。
7. 安装包与打包(WiX)
7.1 把模块加入安装器
- 通过 NuGet 为 WiX5 安装
WixToolset.Heat; - 在
installer\PowerToysInstallerVNext目录为你的模块新增一个Module.wxs文件(例如 installer/LightSwitch.wxs 可作为参照格式); - 拷贝其他模块(如 Light Switch)的 wxs 格式,替换字符串与 GUID 值;
- 关键占位符是
<!--ModuleNameFiles_Component_Def-->——它会被generateFileComponents.ps1生成的组件代码替换;当前仓库中对应的生成脚本为 installer/generateAllFileComponents.ps1; - 在 installer/Product.wxs 的
<Feature Id="CoreFeature" ... >段落中加入一行<ComponentGroupRef Id="ModuleComponentGroup" />; - 在文件组件生成脚本末尾按如下格式为新模块追加条目(
-fileListName <Module>Files需与Module.wxs中设置的字符串一致,<ModuleServiceName>需与服务 exe 名一致):
# Module Name
Generate-FileList -fileDepsJson "" -fileListName <Module>Files -wxsFilePath $PSScriptRoot\<Module>.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\<ModuleServiceName>"
Generate-FileComponents -fileListName "<Module>Files" -wxsFilePath $PSScriptRoot\<Module>.wxs -regroot $registryroot
8. 测试与验证
8.1 UI 测试
- 测试工程放在
/modules/<YourModule>/Tests; - 新建 WinUI Unit Test App;
- 参照现有模块(如 Light Switch)的测试写法,可测试独立的 UI(如 Color Picker 类模块),也可以验证 PowerToys 应用内的设置 UI 是否真正控制到了你的服务。
8.2 手动验证清单
- 在 PowerToys Settings 中启用/禁用模块;
- 检查日志中的初始化记录;
- 确认图标、工具提示(tooltips)与 OOBE 页面正确显示。
8.3 实用技巧
- 验证睡眠/唤醒与提权(elevation)状态。后台模块若在事件句柄未在恢复后重建,唤醒后往往静默失效;
- 用 Windows Sandbox 模拟干净安装环境;
- 想模拟“新用户”,可删除
%LOCALAPPDATA%\Microsoft下的 PowerToys 文件夹。
8.4 快捷键冲突检测
如果模块带快捷键,必须按设置实现文档中 Shortcut conflict detection 章节的步骤正确注册,以获得冲突检测能力。runner 侧的冲突检测实现可参考 src/runner/hotkey_conflict_detector.cpp 与集中式热键管理 src/runner/centralized_hotkeys.cpp。
9. 收尾工作
9.1 OOBE(Out-of-Box Experience)页面
OOBE 页面是一个自定义设置页,在新用户首次使用以及更新之后、正式设置应用打开之前显示,让用户一眼了解各模块用途。需要:
- 在
src\settings-ui\Settings.UI\SettingsXAML\OOBE\Views创建OOBE<ModuleName>.xaml; - 把模块名加入
src\settings-ui\Settings.UI\OOBE\Enums\PowerToysModules.cs中的枚举。
9.2 模块资源(Assets)
模块功能完成后需要规划对外展示的资源:
- Module Icon:显示在 OOBE 页面、README、PowerToys 主页、模块设置页等多处;
- Module Image:各模块设置页顶部的图片;
- OOBE Image:OOBE 页面上每个模块的头部图。
说明:图标与截图的具体设计由设计团队在应用内部保证一致性。如果你有关图标或截图的构想,可以写在 PR 的 "Additional Comments" 部分供团队参考。
9.3 文档
提交新 PowerToy 需要两类文档:
- 开发者文档:放在仓库
/doc/devdocs/modules/(如 doc/devdocs/modules/readme.md 目录),面向开发者,说明如何接手维护你的模块,应涵盖架构、关键文件、测试与调试技巧; - Microsoft Learn 文档:当模块准备合入 PowerToys 仓库时,由内部团队成员编写面向用户的 Learn 文档。开发者在此步骤工作量不大,但需留意 PR 动态,及时补充团队索要的信息。
10. 小结:新模块接入检查单
| 阶段 | 关键动作 | 验证方式 |
|---|---|---|
| 设计 | 确定 UI-only / 服务 / 混合 / 互操作类型 | 找到最相似的现有模块作参照 |
| 脚手架 | 模板生成、替换占位符与 GUID | ModuleTemplate.dll 工程可编译 |
| 注册 | 加入 runner 的模块 DLL 清单与设置窗口映射 | 在 src/runner/main.cpp 的 knownModules 与 settings_window.h 中搜到模块名 |
| 服务 | 独立工程先行,TargetName 命名,ModuleSettings 读设置 |
服务可独立运行并读写 %LOCALAPPDATA%\Microsoft\PowerToys\<module>\settings.json |
| 设置集成 | Properties/Settings/ViewModel/XAML/resw 五件套 | 设置面板改动触发文件监听并写回 JSON |
| 调试 | runner 为启动项目,Ctrl+Alt+P 附加服务 | RunnerLogs 与模块 Module\Service 日志正常 |
| 打包 | Module.wxs + Product.wxs 引用 + 生成脚本条目 |
安装包生成成功,干净环境(Windows Sandbox)验证 |
| 测试 | WinUI 单元测试 + 手动验证 + OOBE 检查 | 启用/禁用、唤醒恢复、新用户场景均通过 |
如果你在使用过程中需要帮助,按文档建议:提一个带 Needs-Team-Response 标签的 issue 以获得团队关注。
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 StartedRust0623
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