首页
/ PowerToys 设置系统实现详解:JSON 持久化、Runner IPC 分发与快捷键冲突检测

PowerToys 设置系统实现详解:JSON 持久化、Runner IPC 分发与快捷键冲突检测

2026-09-04 17:06:36作者:裘晴惠Vivianne

本文围绕 PowerToys 官方开发文档《Settings Implementation》,完整拆解 PowerToys 各模块(PowerToy)的设置是如何读取、写入、在 UI 与 Runner 之间流转的,并覆盖快捷键冲突检测接入、设置问题调试方法以及新增带设置模块的分步集成清单。读完本文,你将能够独立为 C++ 或 C# 模块实现读写设置,理解 get_config/set_config 在 Runner 分发链中的位置,并掌握设置不生效时的排查路径。

PowerToys 设置界面架构:MainWindow.xaml 通过 WindowsXamlHost 承载 ShellPage.xaml 与模块页面

1. 设置系统的整体分层

PowerToys 的设置系统横跨三个进程边界:设置 UI 进程src/settings-ui/Settings.UI)、Runner 主进程src/runner)和各模块 DLL。文档定义的完整链路是:

  1. 用户在设置界面修改某项设置;
  2. 设置 UI 将设置序列化为 JSON;
  3. JSON 通过 IPC 发送给 PowerToys Runner;
  4. Runner 调用对应模块的 set_config 函数;
  5. 模块解析 JSON 并应用新设置。

所有持久化数据统一存放在 %LOCALAPPDATA%\Microsoft\PowerToys\ 目录下,每个模块拥有各自的 settings JSON 文件。C++ 侧的读写入口集中在 SettingsAPI 目录,C# 侧则统一走 SettingsUtils 类。下面按“C++ 模块、C# 模块、模块接口、快捷键冲突检测、调试、新模块集成”的顺序展开。

2. C++ 模块的设置实现

2.1 核心文件

文档指出,C++ 模块的设置系统由以下文件构成:

  • settings_objects.h / settings_objects.cpp:定义基本设置对象(设置描述、值读取/序列化);
  • settings_helpers.h / settings_helpers.cpp:设置文件的读取/写入辅助函数;
  • 文档中提到的 settings_manager.h / settings_settings.cpp(管理设置的主接口)。

从当前仓库源码结构看,这些文件实际位于 src/common/SettingsAPI,同目录下的 FileWatcher.cpp/.h 还负责监听设置文件变化,供跨进程感知配置更新。

2.2 读取设置

文档给出的简化写法是:

#include <common/settings_objects.h>
#include <common/settings_helpers.h>

auto settings = PowerToysSettings::Settings::LoadSettings(L"ModuleName");
bool enabled = settings.GetValue(L"enabled", true);

结合仓库源码,当前实际的读取入口是 PowerToyValues 类。在 settings_objects.h 中可以看到:

class PowerToyValues
{
public:
    PowerToyValues(std::wstring_view powertoy_name, std::wstring_view powertoy_key);
    static PowerToyValues from_json_string(std::wstring_view json, std::wstring_view powertoy_key);
    static PowerToyValues load_from_settings_file(std::wstring_view powertoy_key);
    // ...
    std::optional<bool> get_bool_value(std::wstring_view property_name) const;
    std::optional<int> get_int_value(std::wstring_view property_name) const;
    std::optional<unsigned int> get_uint_value(std::wstring_view property_name) const;
    std::optional<std::wstring> get_string_value(std::wstring_view property_name) const;
    std::optional<json::JsonObject> get_json(std::wstring_view property_name) const;
    json::JsonObject get_raw_json();

    std::wstring serialize();
    void save_to_settings_file();
};

即:PowerToyValues::load_from_settings_file(L"ModuleName") 从磁盘加载该模块的 settings JSON,之后按属性名取出带类型的值(返回 std::optional,属性缺失时有明确的可判空语义)。各模块的实际用法可参考 AlwaysOnTop 的 Settings.cppMeasureTool 的 Settings.cpp

2.3 写入设置

文档给出的简化写法:

PowerToysSettings::Settings settings(L"ModuleName");
settings.SetValue(L"setting_name", true);
settings.Save();

对应的源码级实现是 PowerToyValues 的模板方法 add_property:它把任意类型 T 的值包进 {"value": ...} 对象并写入 JSON 的 properties 节点(见 settings_objects.h 中的 add_property),最终通过 save_to_settings_file() 落盘,内部固定写入 "version": "1.0" 字段。

2.4 向 UI 描述设置项(Settings 类)

同一个头文件中的 PowerToysSettings::Settings 类承担另一职责:构造模块的配置描述 JSON,供 Runner 通过 get_config 交给设置 UI 渲染控件。它提供了一整套带资源 ID 或字面量描述的添加器(见 settings_objects.h#L26-L53):

  • add_bool_toggle(name, description, value):布尔开关;
  • add_int_spinner(name, description, value, min, max, step):整数步进框,带取值范围与步长;
  • add_string / add_multiline_string:单行/多行文本;
  • add_color_picker:颜色选择器;
  • add_hotkey(name, description, const HotkeyObject&):快捷键控件;
  • add_choice_group / add_dropdown:单选组与下拉框;
  • add_custom_action:自定义动作按钮;
  • 最终 serialize() / serialize_to_buffer() 输出 JSON 字符串或写入调用方缓冲区。

内部用 m_curr_priority 维护控件顺序。这意味着 C++ 模块的“设置界面”本质上是一份声明式 JSON,而非 UI 代码本身。

2.5 HotkeyObject:快捷键的序列化形态

HotkeyObjectsettings_objects.h#L119-L263)以 JSON 保存 {win, ctrl, alt, shift, code, key} 六个字段,并提供:

  • from_settings(win, ctrl, alt, shift, vk_code):从系统钩子捕获的按键状态构造对象;
  • get_modifiers() / get_modifiers_repeat():合成 RegisterHotKey 所需的修饰键掩码(含 MOD_NOREPEAT 选项);
  • to_string():生成 shift+ctrl+win+alt+Key 形式的人类可读文本;
  • key_from_code():基于当前键盘布局用 ToUnicodeEx/GetKeyNameTextW 把 VK 码解析为按键名(F1–F12 等 ToUnicodeEx 失败的键会回退处理)。

2.6 文件位置与通用设置的辅助函数

settings_helpers.h 定义了路径与读写辅助 API,是理解磁盘布局的关键:

std::wstring get_powertoys_general_save_file_location();   // 通用设置(general_settings.json)
std::wstring get_module_save_file_location(std::wstring_view powertoy_key);   // 单模块 settings.json
std::wstring get_module_save_folder_location(std::wstring_view powertoy_name);
std::wstring get_root_save_folder_location();              // %LOCALAPPDATA%\Microsoft\PowerToys\
void save_module_settings(std::wstring_view powertoy_name, json::JsonObject& settings);
json::JsonObject load_module_settings(std::wstring_view powertoy_name);
void save_general_settings(const json::JsonObject& settings);
json::JsonObject load_general_settings();

此外还提供 OOBE 打开状态(get_oobe_opened_state/save_oobe_opened_state)、最后运行版本记录(get_last_version_run/save_last_version_run)与日志设置文件名(log_settings.json)等辅助能力。

3. C# 模块的设置实现

C# 模块通过 Microsoft.PowerToys.Settings.UI.Library 命名空间下的 SettingsUtils 类访问设置,实际实现见 SettingsUtils.cs

3.1 读取设置

文档示例:

using Microsoft.PowerToys.Settings.UI.Library;

// Read settings
var settings = SettingsUtils.Default.GetSettings<ModuleSettings>("ModuleName");
bool enabled = settings.Enabled;

源码中 SettingsUtils 是一个单例(SettingsUtils.Default,见 SettingsUtils.cs#L32)。GetSettings<T>(powertoy, fileName) 的默认文件名是 settings.jsonSettingsUtils.cs#L67):文件不存在时会按 T 的默认值创建并落盘,因此“读取”隐含了“首次初始化”语义。

3.2 写入设置

using Microsoft.PowerToys.Settings.UI.Library;

// Write settings
settings.Enabled = true;
SettingsUtils.Default.SaveSettings(settings.ToJsonString(), "ModuleName");

SaveSettings 负责把 JSON 字符串写回 %LOCALAPPDATA%\Microsoft\PowerToys\<模块名>\settings.jsonSettingsUtils.cs#L208)。

3.3 版本升级:GetSettingsOrDefault

源码还提供了带迁移器的重载 GetSettingsOrDefault<T, T2>(powertoy, fileName, settingsUpgrader)SettingsUtils.cs#L123):先读旧类型 T2 的旧格式设置,若与 T 的默认值不同则调用 settingsUpgrader 做字段迁移后再保存新格式——这是 PowerToys 跨版本升级时设置项不丢失的机制,扩展设置模型时应优先使用该入口。

另外,SettingsUtils 还内置了 BackupSettings() / RestoreSettings() 静态方法,用于设置备份与还原,可在“设置损坏”场景中恢复用户数据。

4. 模块接口:get_config 与 set_config 的真实签名

文档以接口形式说明每个模块必须实现设置相关函数:

// Get the module's settings
virtual PowertoyModuleSettings get_settings() = 0;
// Called when settings are changed
virtual void set_config(const wchar_t* config_string) = 0;

这是文档的简化表述。当前仓库中的实际定义在 powertoy_module_interface.h

class PowertoyModuleIface
{
public:
    /* Fills a buffer with the available configuration settings.
    * If 'buffer' is a null ptr or the buffer size is not large enough
    * sets the required buffer size in 'buffer_size' and return false.
    * Returns true if successful.
    */
    virtual bool get_config(wchar_t* buffer, int* buffer_size) = 0;
    /* Sets the configuration values. */
    virtual void set_config(const wchar_t* config) = 0;
    /* Call custom action from settings screen. */
    virtual void call_custom_action(const wchar_t* /*action*/){};
    // ...
};

头文件注释同时说明 Runner 对各模块 DLL 的完整生命周期:加载 DLL、powertoy_create() 创建实例、get_key() 取非本地化 ID、enable() 初始化,运行期间按需调用 get_config/set_config/call_custom_action/get_hotkeys/on_hotkey,退出时 destroy() 并卸载 DLL(powertoy_module_interface.h#L12-L35)。get_config 采用“先探测长度再填充缓冲区”的两段式协议;而 set_config 接收的 config 字符串正是第 1 节链路中 UI 侧序列化后经 Runner 转发的 JSON。Runner 侧的分发实现可参考 powertoy_module.cpp

5. 快捷键冲突检测的接入(四步)

当模块注册了全局快捷键时,需要接入集中化的冲突检测机制。文档给出四步流程,参考实现均指向 AdvancedPaste:

步骤 1:模块接口实现快捷键上报

确保模块接口提供 size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size)std::optional<HotkeyEx> GetHotkeyEx(),返回模块使用的全部快捷键。

关键点:返回顺序即标识。从 powertoy_module_interface.hHotkey 结构可以印证这一点:

struct Hotkey
{
    bool win = false, ctrl = false, shift = false, alt = false;
    unsigned char key = 0;
    // The id is used to identify the hotkey in the module.
    // The order in module interface should be the same as in the settings.
    int id = 0;
    bool isShown = true;
};

注释明确“模块接口中的顺序必须与设置界面中的顺序一致”,id 就是靠顺序索引来唯一定位每个快捷键的。参考:AdvancedPasteModuleInterface/dllmain.cpp

步骤 2:设置模型实现 IHotkeyConfig

模块的设置文件需继承 IHotkeyConfig 并实现 HotkeyAccessor[] GetAllHotkeyAccessors()

  • 返回模块用到的全部快捷键访问器,顺序必须与步骤 1 一致
  • HotkeyAccessorHotkeySettings 的包装,同时提供 getter/setter 用于读取与更新对应快捷键;
  • 每个 HotkeyAccessor 需要一个描述该快捷键用途的资源字符串,通常定义在 Resources.resw

参考:AdvancedPasteSettings.cs

步骤 3:ViewModel 上报快捷键集合

对应 ViewModel 应继承 PageViewModelBase 并实现 Dictionary<string, HotkeySettings[]> GetAllHotkeySettings(),返回全部快捷键,顺序与步骤 1、2 保持一致。参考:AdvancedPasteViewModel.cs

设置界面中的快捷键控件示例(FancyZones 的 Open zones editor)

步骤 4:视图触发 OnPageLoaded()

模块页面加载完成后调用 ViewModel 的 OnPageLoaded()

Loaded += (s, e) => ViewModel.OnPageLoaded();

该调用使冲突检测在页面可见时执行一轮检测。至此,C++ 侧的快捷键列表、C# 侧的模型与 ViewModel 通过“顺序约定”完成对齐,Runner 与设置 UI 才能把冲突提示准确映射到具体模块的具体快捷键上。

6. 调试设置问题

文档给出的调试路径,按当前仓库结构可直接落地:

  1. 检查设置文件:查看 %LOCALAPPDATA%\Microsoft\PowerToys\ 下的 settings JSON(路径规则由 settings_helpers.h 中的 get_module_save_file_location 等函数决定,每个模块一个子目录);
  2. 确认 JSON 格式合法:手动用 JSON 校验器打开,检查是否有未闭合括号、尾随逗号;
  3. 用断点跟踪 IPC 链路,在三个关键位置打断点:
    • 设置 UI 发送配置变更处(SettingsUtils.SaveSettings 及之后的 IPC 发送逻辑);
    • Runner 接收并分发变更处(对应模块的 set_config 入口,见 powertoy_module_interface.h);
    • 模块内应用变更处(模块自己解析 JSON 的代码);
  4. 查看日志:在 PowerToys 日志中搜索与设置变更相关的消息。

常见问题对照表

症状 文档给出的排查方向
设置保存不上 检查文件权限,或其他进程与该文件的访问冲突
设置未生效 验证 IPC 通信是否正常、模块是否正确处理了配置
设置值不正确 检查模块代码中的 JSON 解析与类型转换

7. 新增带设置模块的集成清单

文档“Adding a New Module with Settings”章节给出了跨多项目改动的步骤与推荐实现顺序,这里完整保留并结合仓库结构补充说明:

  1. Settings UI Library(数据模型):在 Settings UI Library 项目(Settings.UI.Library)中定义模块设置的数据模型,它们将被序列化为 %LOCALAPPDATA%\Microsoft\PowerToys\ 下的 JSON 配置。模型类需实现文档第 5 节要求的 IHotkeyConfig(如有快捷键)。
  2. Settings UI(用户界面)
    • 在 ShellPage.xaml 的导航项中为新模块增加一个导航入口(ShellPage.xaml 是设置窗口的导航容器,对应上文架构图中的中间层);
    • 创建包含全部设置控件的新 XAML 设置页;
    • 实现 ViewModel,负责设置数据的读写与业务操作。
  3. 模块实现
    • 在模块 dllmain.cpp 中实现 PowertoyModuleIface,使 Runner 能与之交互,定义见 powertoy_module_interface.h
    • 模块自身 UI 可按技术选型采用 WPF(如 ColorPicker)或 WinUI3(如 Advanced Paste)。
  4. Runner 集成:把模块加入 Runner 的已知模块列表,使其能被加载和初始化。
  5. 测试与调试
    • 逐个验证:设置序列化/反序列化、模块启用/停用、IPC 通信;
    • 涉及快捷键的模块,先保证各模块独立工作正常,再排查信号/冲突处理;
    • 可在 Visual Studio 中直接调试单个模块,或附加到运行中的进程。

推荐实现顺序

  1. 模块/模块 UI 实现;
  2. 模块接口(dllmain.cpp);
  3. Runner 集成;
  4. Settings UI 实现;
  5. OOBE(开箱体验)集成;
  6. 其他组件。

这个顺序的意义在于:先把模块 DLL 通过 powertoy_create/enable/set_config 与 Runner 打通(第 3 步即可在 Runner 中验证设置下发链路),再回头补设置界面,能尽早暴露接口协议问题,避免 UI 与模块两端同时返工。

8. 小结

PowerToys 的设置系统可以用一条主线概括:声明式 JSON 是唯一的交换格式。C++ 模块用 PowerToysSettings::PowerToyValues 读写、用 PowerToysSettings::Settings 声明控件;C# 侧统一经 SettingsUtils 落地到 %LOCALAPPDATA%\Microsoft\PowerToys\ 下同构的 JSON 文件;Runner 通过 PowertoyModuleIface::get_config/set_config 缓冲区分隔 UI 与模块;快捷键冲突检测则依赖“C++ 接口、设置模型、ViewModel 三处返回顺序一致”的隐式契约。掌握这套约定后,无论是排查“设置保存不上/未生效”这类问题,还是为新模块接入设置界面与快捷键检测,都有了明确的落点与源码参照。

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