首页
/ PowerToys Runner 深入解析:PowerToys.exe 如何加载、管理与驱动全部模块

PowerToys Runner 深入解析:PowerToys.exe 如何加载、管理与驱动全部模块

2026-09-05 19:48:52作者:韦蓉瑛

PowerToys Runner 是整个 PowerToys 产品的主可执行文件(PowerToys.exe),负责加载并管理所有 PowerToys 模块。本文基于仓库中的 runner 开发文档src/runner/ 下的真实源码,完整拆解 Runner 的架构职责、启动流程、托盘图标实现、与 Settings UI 的命名管道 IPC 通信机制、集中式键盘钩子的性能优化,以及模块 DLL 的加载与热键注册链路。读完后,你将掌握 PowerToys 从进程启动、模块加载、托盘交互到跨进程设置分发的完整工作机制,并能定位每个关键行为的源码位置。

Runner 是做什么的

Runner 的职责可以归纳为五项(见 runner 文档):

  • 管理系统托盘图标;
  • 加载并管理各模块的接口对象;
  • 处理模块的启用/禁用(enable/disable);
  • 处理全局热键(global hotkeys);
  • 管理更新(updates)与设置(settings)。

main.cpprunner() 函数(L181-L365)可以看到其运行时形态:Runner 以 Windows 消息循环(message loop)的方式持续运行,托盘图标窗口就是这个消息循环的宿主窗口;同时它还启动了 Quick Access 宿主、后台更新检查线程(PeriodicUpdateWorker)、AI 能力检测(调用 ImageResizer 的 --detect-ai 参数)等旁路线程。

Runner 的关键组成(Key Components)包括:

  • 主 C++ 文件管理托盘图标与模块集合;
  • 维护一份模块 DLL 路径列表(即 knownModules 数组);
  • 对 WinUI 3 应用做特殊处理——这些模块的 DLL 被拆分放在独立的 WinUI3Apps 子目录中;
  • 对依赖 DLL 做“拍平”(flattening)处理,保证各模块依赖的版本一致;
  • 整个生命周期运行在 Windows 消息循环中;
  • 创建一个具有特定类名的窗口,并把自己注册为需要窗口句柄的组件(如集中式热键)的消息接收方。

启动流程:从 WinMain 到消息循环

文档描述的 10 步启动流程,在当前源码中可以逐步对应上。入口是 main.cpp 中的 WinMainL448):

  1. 初始化日志Logger::init 把 Runner 日志写入 PTSettingsHelper::get_root_save_folder_location() 下(日志路径由 LogSettings::runnerLogPath 决定),日志级别由 get_log_settings_file_location() 指向的配置驱动。
  2. 创建单实例互斥量create_msi_mutex() 基于 POWERTOYS_MSI_MUTEX_NAME 创建应用互斥量(main.cpp L164-L167)。如果互斥量拿不到,说明已有 Runner 在运行,此时调用 open_menu_from_another_instance():它用 FindWindowW(L"PToyTrayIconWindow", nullptr) 找到已存在的托盘窗口,向其 PostMessageW(hwnd, WM_COMMAND, ID_SETTINGS_MENU_COMMAND, ...) 转发“打开设置”的请求后直接退出(L169-L179)。
  3. 初始化通用工具代码,包括 COM 安全描述符初始化、winrt::init_apartment() 与 DPI 感知设置(DPIAware::EnableDPIAwarenessForThisProcess())。
  4. 解析命令行参数。当前源码中实际支持以下参数:
    • --open-settings:启动后打开设置窗口;
    • --open-settings=<窗口名>:打开指定模块的设置页(如 Overview),窗口名通过 ESettingsWindowNames_from_string 解析;
    • --dont-elevate:请求非提权运行;若当前已提权且设置中也未要求提权,则调用 schedule_restart_as_non_elevated() 以普通权限重启自身;
    • --restartedElevated:标记“提权重启已完成”,用于打破提权重启死循环(对应源码注释中提到的 issue #19307)。 此外还有特殊启动模式:powertoys:// 协议 URI(处理 Toast 通知的激活动作,如 open_settings/open_overview/update_now/)、Toast 通知 COM 服务模式、以及 ReportSuccessfulUpdate 模式,均由 should_run_in_special_mode() 判定(L367-L396)。
  5. 启动托盘图标start_tray_icon(isProcessElevated, settings.showThemeAdaptiveTrayIcon)
  6. 初始化低级键盘钩子CentralizedKeyboardHook::Start()(见后文“集中式键盘钩子”一节)。
  7. 从 DLL 加载模块接口:遍历 knownModules 列表逐一 load_powertoy()
  8. 启动已启用的模块start_enabled_powertoys() 根据 %LOCALAPPDATA%\Microsoft\PowerToys\settings.jsonenabled 字段的标记,调用各模块的 enable()
  9. 进入 Windows 消息循环result = run_message_loop()
  10. 退出时停止模块并清理资源:消息循环结束后,WinMain 依次执行释放互斥量、检查并执行计划中的重启(restart_if_scheduled())、stop_tray_icon() 清理。

值得注意的是,runner() 中在加载模块前还会先做两件事:chdir_current_executable()(把工作目录切到可执行文件所在目录,保证模块相对路径可解析)以及加载并应用通用设置 load_general_settings() + apply_general_settings()

knownModules:一份硬编码的模块清单

与文档描述的“扫描模块目录”不同,当前实现采用的是显式清单方式:knownModules 数组列出了全部 30 余个模块 DLL 的相对路径(main.cpp L256-L292),例如:

std::vector<std::wstring_view> knownModules = {
    L"PowerToys.FancyZonesModuleInterface.dll",
    L"PowerToys.powerpreview.dll",
    L"WinUI3Apps/PowerToys.ImageResizerExt.dll",   // WinUI 3 模块放在独立子目录
    L"PowerToys.KeyboardManager.dll",
    L"PowerToys.Launcher.dll",
    L"WinUI3Apps/PowerToys.PowerRenameExt.dll",
    /* ... 其余模块 ... */
};

这里印证了文档提到的“WinUI 3 应用被分离到不同文件夹”:以 WinUI3Apps/ 前缀开头的条目(ImageResizer、PowerRename、ShortcutGuide、MouseJump、FileLocksmith、RegistryPreview、MeasureTool、NewPlus、Hosts、Peek、EnvironmentVariables)全部位于 WinUI3Apps 子目录。加载失败时,Debug 构建只记录警告(方便开发迭代),Release 构建则弹出错误对话框(L301-L318)。

模块加载完成后,start_enabled_powertoys() 负责按 settings.json 的启用状态启动它们;文档提到的“检查 GPO 策略决定哪些模块可以启动”对应于模块接口上的 gpo_policy_enabled_configuration() 虚函数(见 powertoy_module_interface.h L156-L159),由 GPO 强制禁用的模块即使本地开启也不会启动。

模块接口:powertoy_create 与 RAII 包装

Runner 与模块之间的契约定义在 powertoy_module_interface.hPowertoyModuleIface 类中。头文件注释完整描述了 Runner 对每个模块 DLL 的生命周期约定:

  • 加载 DLL 并调用其导出的 powertoy_create() 工厂函数;
  • 对返回对象调用 get_key()(非本地化 ID)、enable()(初始化)、get_hotkeys()(注册热键);
  • 运行期间可能调用 disable()/enable()/is_enabled()get_config()/set_config()call_custom_action()on_hotkey()
  • 终止时调用 destroy() 释放全部内存,然后卸载 DLL;
  • 即使模块处于禁用状态,Runner 也会调用 on_hotkey()(由各模块自行决定是否响应)。

DLL 必须以如下方式导出工厂函数:

extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()

Runner 侧的加载实现在 powertoy_module.cpp L14-L30

PowertoyModule load_powertoy(const std::wstring_view filename)
{
    auto handle = winrt::check_pointer(LoadLibraryW(filename.data()));
    auto create = reinterpret_cast<powertoy_create_func>(GetProcAddress(handle, "powertoy_create"));
    if (!create)
    {
        FreeLibrary(handle);
        winrt::throw_last_error();
    }
    auto pt_module = create();
    if (!pt_module)
    {
        FreeLibrary(handle);
        winrt::throw_hresult(winrt::hresult(E_POINTER));
    }
    return PowertoyModule(pt_module, handle);
}

PowertoyModule 类(powertoy_module.h L32-L59)是一个 RAII 风格的持有者:它用两个自定义删除器(PowertoyModuleDeleter 调用 pt_module->destroy()PowertoyModuleDLLDeleter 调用 FreeLibrary(handle))分别管理接口指针与 DLL 句柄,保证对象析构时模块内存与 DLL 一定被正确释放。所有模块实例存放在 modules() 返回的 std::map<std::wstring, PowertoyModule> 单例中(powertoy_module.cpp L8-L12),以模块的 get_key() 为键。

模块构造函数中还会顺带完成热键注册:先清除旧的冲突记录,再执行 update_hotkeys()(遍历 get_hotkeys() 返回的每个 isShown 热键,写入冲突检测器 HotkeyConflictManager,并向 CentralizedKeyboardHook::SetHotkeyAction 注册回调 modulePtr->on_hotkey(i))和 UpdateHotkeyEx()(处理 GetHotkeyEx() 单热键扩展,以及 Shortcut Guide 遗留的“长按 Windows 键”行为,见 powertoy_module.cpp L55-L119)。

系统托盘图标实现

托盘图标是 Runner 最先启动的组件之一。核心实现在 tray_icon.h / tray_icon.cpp

  • 窗口类名固定为 PToyTrayIconWindow,这是一个跨进程约定的常量,注释明确要求它与 Settings UI 侧(Settings.UI/Views/ShellPage.xaml.cs)的 ptTrayIconWindowClass 保持一致(tray_icon.h L28-L29);
  • start_tray_icon() 负责初始化;
  • tray_icon_window_proc() 处理窗口消息;
  • 通过 WM_COMMAND 与托盘图标通知消息处理菜单选项;
  • 区分左键单击/双击;
  • 监听任务栏重建(TaskbarCreated)以在需要时重新注册图标;
  • 使用 Shell_NotifyIcon(文档中的 shell_notify_icon)注册到系统托盘。

start_tray_icon:从注册窗口类到 NIM_ADD

start_tray_icon()tray_icon.cpp L409-L479)的执行序列:

  1. 读取 UpdateState,判断是否有可用更新(readyToDownload/readyToInstall),决定使用带更新标记的图标;
  2. 选择图标:主题自适应模式下按系统主题从 svgs/ 目录加载 PowerToysWhite.ico / PowerToysDark.ico(对应 svgs 资源目录),否则从资源加载 APPICON
  3. 注册窗口类并创建 WS_OVERLAPPEDWINDOW | WS_POPUP 风格的隐藏窗口,把 tray_icon_window_proc 设为回调;
  4. 调用 CentralizedHotkeys::RegisterWindow(hwnd)CentralizedKeyboardHook::RegisterWindow(hwnd),让托盘窗口成为全局热键与键盘钩子的宿主句柄;
  5. 填充 NOTIFYICONDATAW 结构(uID = 1uCallbackMessage = wm_icon_notify,即 WM_APP),写入含版本号(以及“以管理员运行”提示)的 tooltip,并调用 Shell_NotifyIcon(NIM_ADD, ...) 完成注册;
  6. 通过 ThemeListener 注册系统主题变化回调,并注册 Bug 报告状态回调(用 dispatch_run_on_main_ui_thread 把回调切回主 UI 线程)。

一个值得注意的工程细节:Shell_NotifyIcon 在 explorer.exe 尚未就绪时会失败,且此时永远收不到 TaskbarCreated 消息,因此窗口过程额外处理 WM_WINDOWPOSCHANGING 消息(explorer 启动序列中必然收到),借此机会补做 NIM_ADDtray_icon.cpp L213-L223);同时 WM_CREATE 中通过 RegisterWindowMessageW(L"TaskbarCreated") 订阅任务栏重建消息,收到后重新 NIM_ADDL356-L359)。

tray_icon_window_proc:消息分发与单击/双击判定

tray_icon_window_proc()tray_icon.cpp L161-L363)是托盘交互的总入口,主要分支包括:

  • WM_HOTKEY:利用托盘窗口避免为热键另建窗口,直接把修饰键掩码与虚拟键码交给 CentralizedHotkeys::PopulateHotkey 分发给对应模块;
  • WM_CLOSE / WM_DESTROYWM_CLOSE 触发 DestroyWindowWM_DESTROY 中删除托盘图标(Shell_NotifyIcon(NIM_DELETE, ...))、关闭 Settings 子进程(close_settings_window())并 PostQuitMessage(0) 结束消息循环。源码中还专门优化了“系统关机”场景:当 g_system_session_ending 为真时跳过跨进程清理,避免在 Windows 的 quiesce 时间预算内等待 Settings 进程而被判定为挂起;
  • WM_COMMAND:交给 handle_tray_command()L92-L140),按菜单 ID 分发:ID_SETTINGS_MENU_COMMAND 打开指定设置页、ID_CLOSE_MENU_COMMAND 关闭窗口、ID_ABOUT_MENU_COMMAND 显示关于框(含产品版本号)、ID_REPORT_BUG_COMMAND 启动 Bug 报告工具、ID_DOCUMENTATION_MENU_COMMAND 打开文档链接、ID_QUICK_ACCESS_MENU_COMMAND 打开 Quick Access 弹出窗口、ID_UPDATE_MENU_COMMAND 跳到 Overview 页;
  • wm_icon_notify(WM_APP)自定义消息:托盘图标交互的实际载荷,按 lparam 区分:
    • 右键/上下文菜单:动态构建菜单(含“Update available”项的插入/移除、Quick Access 项随设置的显隐、Bug 报告项的置灰),用 TrackPopupMenu 展示;
    • WM_LBUTTONUP:启动一个延时 GetDoubleClickTime() 毫秒的定时器线程,若期间未发生双击则按单击处理——若开启了 Quick Access 则打开快速访问浮窗,否则打开设置窗口(click_timer_elapsed()L142-L159);
    • WM_LBUTTONDBLCLK:标记 double_clicked = true 抵消单击定时器,并直接打开设置窗口;
  • wm_run_on_main_ui_thread:执行 dispatch_run_on_main_ui_thread() 投递来的回调(供 IPC 等后台线程把 JSON 消息切回主线程)。

托盘菜单:RC 资源 + 本地化字符串

与文档描述一致,菜单结构定义在资源脚本 runner.base.rc 中(ID_TRAY_MENU MENU,含 "Settings" 等 MENUITEM),菜单命令 ID 由头文件定义,本地化文本存放在 Resources.resxIDS_SETTINGS_MENU_TEXTIDS_CLOSE_MENU_TEXT 等资源字符串在 tray_icon_window_proc 中按需加载并写回菜单项)。菜单还会在每次弹出时根据 Quick Access 开关状态动态更新“Settings”菜单项的文案。

与 Settings UI 的 IPC 通信机制

Runner 与设置窗口是两个独立进程,二者通过 Windows 命名管道(Named Pipes)传输 JSON 消息:

  • Runner 侧使用 TwoWayPipeMessageIPC(双向管道)建立连接;
  • Settings UI 启动时初始化自己的管道连接,文档中给出的 C# 侧初始化代码为:
ipcmanager = new TwoWayPipeMessageIPCManaged(cmdArgs[(int)Arguments.SettingsPipeName],
                                            cmdArgs[(int)Arguments.PTPipeName],
                                            (string message) => {
  if (IPCMessageReceivedCallback != null && message.Length > 0) {
      IPCMessageReceivedCallback(message);
  }
});

Runner 侧:管道命名、进程创建与客户端鉴权

run_settings_window()settings_window.cpp L435)负责拉起 WinUI3Apps\PowerToys.Settings.exe 子进程。管道命名采用“固定前缀 + 随机 UUID”的方式保证每次会话唯一(L454-L476):

std::wstring powertoys_pipe_name(L"\\\\.\\pipe\\powertoys_runner_");
std::wstring settings_pipe_name(L"\\\\.\\pipe\\powertoys_settings_");
// UuidCreate 成功后追加 uuid 字符串

子进程参数依次为:两个管道名、Runner 的 PID、主题(dark/system 归一化)、是否提权、当前用户是否管理员、是否显示 OOBE/Scoobe 窗口、是否携带指定设置窗口名。

管道建立后,当前源码在 current_settings_ipc->start() 之前还有一道管道客户端鉴权interop_auth::CallerPolicyL590-L602):只接受位于 Runner 自身 WinUI3Apps 目录下、版本与 Runner 一致、且带 Microsoft 签名的 PowerToys.Settings.exe(Debug 构建下签名检查被编译排除),拒绝的客户端会记录日志。这为文档描述的 JSON 管道通信补充了一层安全边界。

Runner 收到消息后并不直接在管道线程处理,而是 receive_json_send_to_main_thread()dispatch_run_on_main_ui_thread() 切回主 UI 线程执行 dispatch_received_json(),这正是文档中 dispatch_run_on_main_ui_thread 的作用——Settings 窗口是从专用线程通信的,JSON 消息必须安全地转移到主线程。

Runner 侧处理的 JSON 消息类型

dispatch_received_json()settings_window.cpp L194-L357)按顶层键名分发,覆盖以下消息:

消息键 行为
general apply_general_settings() 应用通用设置
module_status 单个模块的启用/禁用更新,格式 {"module_status": {"ModuleName": true/false}}
powertoys 对每个模块调用 set_config(),若 properties.hotkey_changed 为真则同步重建热键;随后把全量设置回写给 Settings
refresh 立即回写 get_all_settings()
action 分发自定义动作:restart_elevation(切换提权并重启)、restart_maintain_elevation(保持提权重启,跳过落盘设置)、check_for_updates(后台检查更新)、request_update_state_date(返回上次检查日期)
bugreport / bug_report_status 启动 Bug 报告 / 回报运行状态
killrunner 对托盘窗口发送 WM_CLOSE 关闭 Runner
language 将语言 JSON 落盘到 language.json
check_hotkey_conflict / get_all_hotkey_conflicts 热键冲突查询,返回 has_conflict 与冲突详情

回写给 Settings 的完整设置由 get_all_settings() 组装:general 段加每个模块 get_config() 返回的 JSON(L41-L65)。

Runner 主动打开/指示设置窗口

当托盘图标被点击或菜单选项被选中时,Runner 通过 IPC 向 Settings 发送 JSON 指令(open_settings_window()L699-L727):

  • 快速访问浮窗(左键单击,Settings 已运行时):current_settings_ipc->send(L"{\"ShowYourself\":\"flyout\"}");
  • 主仪表盘(菜单项或双击):current_settings_ipc->send(L"{\"ShowYourself\":\"Dashboard\"}");
  • 打开特定模块页:{"ShowYourself":"<窗口名>"},窗口名来自 ESettingsWindowNames_to_string 枚举(覆盖 Overview、FancyZones、KBM、PowerRename、Peek 等全部模块页,L757-L836)。

如果 Settings 进程尚未启动,则在线程中执行 run_settings_window() 拉起它;若正在启动中(g_isLaunchInProgress)则直接返回,避免重复创建。

Settings UI 侧的消息处理链

Settings UI 侧的处理流程(与文档一致):

  1. 来自 Runner 的消息经 IPC 回调接收;
  2. 消息被解析为 JSON 对象;
  3. 注册在 ShellPage.ShellHandler.IPCResponseHandleList 中的处理器逐个处理消息;
  4. ShellPageReceiveMessage 方法解释命令:"ShowYourself": "flyout" 显示浮窗;"ShowYourself": "Dashboard" 或其他页名则打开对应页面(Settings UI 侧实现位于 Settings.UI/Views/ShellPage.xaml.cs)。

浮窗还支持携带坐标,让浮窗出现在托盘图标附近:

{
  "ShowYourself": "flyout",
  "x_position": 1234,
  "y_position": 567
}

浮窗窗口随后通过原生 Windows API 激活并置于前台以保证可见。

集中式键盘钩子

全局快捷键统一由 centralized_kb_hook.cpp 中的 CentralizedKeyboardHook 处理——为多个模块集中处理热键,避免每个模块各自挂钩子带来的性能问题。核心钩子回调 KeyboardHookProcL88)包含文档所列的多个提前退出(early exit)优化:

  • 忽略 PowerToys 自身产生的按键KBDLLHOOKSTRUCT.dwExtraInfo 等于 PowertoyModuleIface::CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG(0x110)时直接 CallNextHookEx 放行——该标志专门用于 AdvancedPaste 这类会产生新输入的动作,防止自身输出被再次捕获;
  • 忽略没有按键真正按下的事件:用 vkCodePressed 原子变量跟踪“当前是否已有按键被按住”,只有第一个物理按键按下时才会为“长按触发”类行为(pressedKeyDescriptors,如 Shortcut Guide 的长按 Win 键)启动计时器;
  • 利用按键元数据避免重复处理修改态输入vkCodePressed 记录了上一次按下的虚拟键码,用于识别重复键与多键同时按下的情况,且刻意选择虚拟键码作为 multiset 排序键,因为“按键命中查找”发生在时间最敏感的钩子路径上。

文档特别强调的性能约束在这里体现得很直白:该回调在每一次击键时都会运行,任何耗时逻辑(如启动长按计时器)都被推到 SetTimer 回调(PressedKeyTimerProc,触发前还会用 GetAsyncKeyState 复核按键仍被物理按住,防止“幽灵触发”)中异步执行,钩子内部持锁时间被压缩到最短。

热键的注册与注销逻辑在 centralized_hotkeys.cpp,Runner 通过托盘窗口的 WM_HOTKEY 消息把命中分发给 CentralizedHotkeys::PopulateHotkey。设置中修改热键后,send_json_config_to_module() 会调用 remove_hotkey_records() + update_hotkeys() + UpdateHotkeyEx() 完成整套热键的重建(settings_window.cpp L151-L165),并由 hotkey_conflict_detector.cpp 维护跨模块冲突检测。

如何找到并“发消息给”托盘窗口

文档指出,托盘窗口类同时是 Runner 的进程间控制句柄。因为窗口类名 PToyTrayIconWindow 是跨进程公开约定,任何组件(包括 Runner 自身的第二实例、Settings UI 的 killrunner 消息处理)都可以:

// 找到 PToyTrayIconWindow 类的窗口并发送消息
HWND hwnd = FindWindowW(L"PToyTrayIconWindow", nullptr);
PostMessageW(hwnd, WM_COMMAND, ID_SETTINGS_MENU_COMMAND, 0); // 请求打开设置
SendMessageW(hwnd, WM_CLOSE, 0, 0);                         // 请求关闭 Runner

源码中有三处实际用例:单实例转发 open_menu_from_another_instance()main.cpp L169-L179)、IPC killrunner 消息处理(settings_window.cpp L270-L277)、以及 stop_tray_icon() 内部的 SendMessage(tray_icon_hwnd, WM_CLOSE, 0, 0)tray_icon.cpp L556-L564)。

关键文件速查

以下为 runner 文档 列出的关键文件与当前仓库路径对照:

文件 职责
main.cpp 可执行文件入口:初始化顺序、模块清单与加载、消息循环;单例初始化也在此完成
powertoy_module.h / powertoy_module.cpp 模块的初始化与管理;PowertoyModule 是对 PowertoyModuleIface 指针的 RAII 持有,接口由 powertoy_create() 创建
tray_icon.cpp 托盘图标与菜单命令管理;dispatch_run_on_main_ui_thread 用于把 Settings 传来的 JSON 消息切回主线程
settings_window.cpp 启动设置窗口进程并维持命名管道 JSON 通信,分发设置/动作/冲突查询等消息
general_settings.cpp 通用设置的加载、保存与应用
auto_start_helper.cpp 注册/注销用户登录时自启动
unhandled_exception_handler.cpp 在构建中获取堆栈跟踪的辅助代码(init_global_error_handlers 仅在 Debug 构建被引用)
trace.cpp 遥测(ETW/EventLaunch 等事件)
svgs/ 托盘图标等资源文件
bug_report.cpp 启动 Bug 报告工具
centralized_hotkeys.cpp 全局热键的注册与注销
centralized_kb_hook.cpp 集中式低级键盘钩子与所有模块快捷键的匹配/分发
restart_elevated.cpp 以不同提权级别重启当前进程
RestartManagement.cpp 基于 Restart Manager 的进程重启实现
settings_telemetry.cpp 周期性触发各模块设置遥测的投递,管理时序与错误处理
UpdateUtils.cpp 自动更新检查、通知与安装流程

小结:Runner 在 PowerToys 架构中的位置

把上述源码证据串起来,Runner 的角色可以概括为“模块宿主 + 托盘前端 + 设置总机”三层:

  1. 模块宿主:通过 knownModules 清单加载模块 DLL,以 powertoy_create() + RAII 包装完成模块生命周期管理,用 settings.json 与 GPO 策略共同决定谁被 enable()
  2. 托盘前端PToyTrayIconWindow 类窗口承载托盘图标、菜单、单击/双击交互与全局热键分发,同时充当跨进程消息的“收件地址”;
  3. 设置总机:以带 UUID 的命名管道承载 Runner ↔ Settings UI 的双向 JSON 协议(ShowYourselfpowertoys 配置、action、热键冲突查询、killrunner 等),并用管道客户端鉴权约束对端身份。

理解这套机制后,无论是排查“模块没启动”“热键不生效”“托盘图标消失”,还是开发新模块(可参考 tools/project_template/ModuleTemplate 的模块模板与 powertoy_module_interface.h 的接口约定),都能沿着本文的调用链快速定位到对应源码。

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