PowerToys Run(PowerLauncher)插件体系解析:IPlugin 接口、生命周期与插件配置机制
PowerToys Run(源码中称 PowerLauncher / PT Run)是 Microsoft PowerToys 中的启动器模块,其可扩展性的核心在于一套统一的插件契约:所有插件(计算器、文件索引器、窗口切换、Web 搜索等)都实现同一接口,由宿主 PluginManager 统一加载、初始化、分发查询与同步设置。本文基于仓库中的开发文档与 src/modules/launcher 下的真实源码,完整梳理每个插件共同遵循的生命周期函数(Init、Query、UpdateSettings、ThemeChanged、Save)、上下文菜单图标、结果打分(Score)机制,以及 plugin.json 与 settings.json 两级配置的落地方式,帮助开发者理解并编写符合 PowerToys Run 规范的插件。
IPlugin:每个插件必须实现的统一契约
文档 doc/devdocs/modules/launcher/plugins/overview.md 指出:每个插件都实现 IPlugin 接口,该接口由 Init() 和 Query() 两个核心函数构成。在当前仓库中,接口定义位于 IPlugin.cs,其完整形态如下:
namespace Wox.Plugin
{
public interface IPlugin
{
List<Result> Query(Query query);
void Init(PluginInitContext context);
// Localized name
string Name { get; }
// Localized description
string Description { get; }
}
}
从源码结构看,接口比文档描述还多了两个属性:
Name/Description:本地化的插件名称与描述,供设置界面展示。源文件中保留了被注释掉的public static abstract string PluginID属性——注释说明其为plugin.json条目校验之用,且必须为静态以便在加载插件前访问,但当前因单元测试所依赖的 Moq 包尚不支持 .NET 7 的static abstract特性而被注释。Query(query)返回List<Result>:插件根据用户查询词返回结果集合,这是插件对外提供价值的唯一出口。Init(context):插件初始化入口,见下文。
以计算器插件为例,Calculator/Main.cs 中的入口类声明为 public class Main : IPlugin, IPluginI18n, IDisposable, ISettingProvider,体现了文档中“Init() 是 Main.cs 中第一个被调用的函数”的约定:每个插件项目都有一个名为 Main.cs 的入口文件,宿主按 DLL 加载后调用其中的 Main 实例。此外,插件还会按需扩展其他可选契约:IPluginI18n(提供翻译后的标题/描述)、IDisposable(宿主侧的资源释放,如取消订阅主题事件)、ISettingProvider(实现 UpdateSettings,见下文)。
Init:插件的“构造函数”
Init() 负责初始化插件的上下文、存储与设置,等价于构造函数。它的签名接收一个 PluginInitContext,该类的定义在 PluginInitContext.cs:
public class PluginInitContext
{
public PluginMetadata CurrentPluginMetadata { get; internal set; }
/// <summary>
/// Gets or sets public APIs for plugin invocation
/// </summary>
public IPublicAPI API { get; set; }
}
也就是说,初始化时插件拿到两样东西:
CurrentPluginMetadata:该插件plugin.json解析出的元数据;API(IPublicAPI):宿主暴露给插件的公共 API 门面,插件通过它调用查询改写、主题订阅等能力。
计算器插件的 Init 实现是教科书式的示范(见 Calculator/Main.cs):
public void Init(PluginInitContext context)
{
Context = context ?? throw new ArgumentNullException(paramName: nameof(context));
Context.API.ThemeChanged += OnThemeChanged;
UpdateIconPath(Context.API.GetCurrentTheme());
}
它在初始化时做了两件事:订阅宿主的 ThemeChanged 事件,并立即根据当前主题设置图标路径。对应的 Dispose 实现中会执行 Context.API.ThemeChanged -= OnThemeChanged 取消订阅——这解释了为什么插件入口类要实现 IDisposable:防止宿主重复加载/卸载插件时事件委托泄漏。
Query:每次用户输入都触发的查询执行
对于用户在 PT Run 中键入的每一次查询,宿主都会执行每个(被路由命中的)插件 Main.cs 中的 Query() 函数。查询的载体是 Query 对象,其中携带 Search(去掉动作关键词后的实际查询词)、RawQuery(原始输入)与 ActionKeyword(命中的动作关键词,若为空则表示这是一次“全局查询”——即用户未输入任何前缀关键词)。
计算器插件的 Query 实现(Calculator/Main.cs)展示了几个典型的插件编写模式:
public List<Result> Query(Query query)
{
ArgumentNullException.ThrowIfNull(query);
bool isGlobalQuery = string.IsNullOrEmpty(query.ActionKeyword);
bool replaceInput = _replaceInput && !isGlobalQuery && query.Search.EndsWith('=');
...
// Happens if the user has only typed the action key so far
if (string.IsNullOrEmpty(query.Search))
{
return new List<Result>();
}
...
}
- 空查询快速返回:用户仅输入了动作关键词时直接返回空列表;
- 通过 API 改写用户输入:当启用了“替换输入”选项且输入以
=结尾时,插件调用Context.API.ChangeQuery($"{query.ActionKeyword} {pluginResult.QueryTextDisplay}")把输入替换为计算结果,实现“输入=2+3得到=5”的交互; - 异常兜底:捕获
ParseException、OverflowException与通用Exception,通过ErrorHandler.OnError将错误作为结果返回,确保任何插件崩溃都不会拖垮整个宿主进程。
Score:结果排序依据相关性打分
文档明确说明:用户查询会针对每个插件执行,结果列表视图由所有插件的结果共同填充,而结果的排列顺序基于每个 Result 的 Score。每个插件根据自身判断的相关性给结果赋分——分数越高,在列表视图中位置越靠前,反之越靠后。换言之,宿主并不硬编码任何模块间的优先级,跨插件的排序完全由各插件自报的分数驱动;插件开发者应保证分数与“结果对当前查询的匹配程度”单调一致,这是结果列表可读性的关键。
上下文菜单图标
每条结果还可以附带上下文菜单(ContextMenus),按结果类型加载。文档列举了仓库中常见的上下文菜单功能类型:
- Open containing folder(打开所在文件夹)
- Run as Administrator(以管理员身份运行)
- Open in console(在控制台打开)
- Copy path(复制路径)
这类菜单项在文件索引器、程序搜索等插件中最为典型,例如程序搜索插件会为命中的可执行文件挂载“打开文件位置 / 以管理员身份运行”等菜单项,让用户无需先打开程序即可完成二级操作。
UpdateSettings:设置 UI 变更的落地点
UpdateSettings 负责把用户在 PowerToys 设置界面中所做的更改同步进插件运行时。文档给出的例子是:在文件索引器插件中禁用磁盘检测——当用户勾选或取消“驱动检测”复选框时,UpdateSettings() 会把复选框的变更分发到插件实例。
从源码看,宿主在设置变更时调用插件入口类上的 UpdateSettings(PowerLauncherPluginSettings settings) 方法(需实现 ISettingProvider 契约)。计算器插件的实现(Calculator/Main.cs)展示了标准做法:
- 先为本插件支持的每个选项声明带默认值的局部变量(如
replaceInput = true、trigMode = Radians); - 从
settings.AdditionalOptions中按Key逐一查找,存在则以设置值覆盖默认值; - 对可能解析失败的选项(如下拉框的整型值)单独
try/catch,失败时记录日志并保留默认值,保证单个选项损坏不影响其他选项; - 最后将解析结果写入插件私有字段(
_inputUseEnglishFormat等),供后续Query()使用。
插件声明自己支持哪些设置项的方式是实现 AdditionalOptions 属性:计算器在 Calculator/Main.cs 中声明了“输入/输出使用英文格式”“替换输入”以及“三角函数单位(弧度/角度/梯,Combobox 类型)”四个选项,设置 UI 会自动据此渲染复选框与下拉框,用户改动后触发上面的 UpdateSettings 流程——这与文档中“设置从 UI 变更分发到插件”的描述完全对应。
ThemeChanged 与 IconPath:主题切换时的图标更新
当 PT Run 的主题发生变化时,宿主触发主题变更事件,插件据此更新自身的 IconPath。计算器插件的实现非常直观:
private void UpdateIconPath(Theme theme)
{
if (theme == Theme.Light || theme == Theme.HighContrastWhite)
{
IconPath = "Images/calculator.light.png";
}
else
{
IconPath = "Images/calculator.dark.png";
}
}
注意这里的双通道设计:运行时主题切换走 ThemeChanged 事件回调;而 plugin.json 中的 IcoPathDark / IcoPathLight 字段则服务于插件加载阶段(设置面板、插件列表等 UI 在调用 Init 前就需要展示图标),两者互补。
Save:持久化插件配置
Save 用于把插件当前的配置落盘,以便下次启动时恢复。宿主 PluginManager 中提供了静态的 Save() 入口(见 PluginManager.cs),在插件集合或相关状态变化时被调用,将全部插件的当前设置统一写出;这与下文“插件设置存储于 PowerToys Run\settings.json”的机制相衔接。
插件的宿主侧管理:PluginManager
文档中提到的“PluginManager.cs 执行每个插件的 Query()”对应源码 PluginManager.cs(位于 src/modules/launcher/PowerLauncher/Plugin/,共 338 行)。从源码结构看,它承担了插件体系的全部宿主侧职责:
- 插件发现与去重:
AllPlugins属性从Constant.PreinstalledDirectory(预装目录)与Constant.PluginsDirectory(用户插件目录)两处解析plugin.json,只保留Language为 C# 的插件,并按插件 ID 分组——同一 ID 存在多份 DLL 时(如升级未清理旧版本),选取产品版本最高的一份; - 全局 / 非全局划分:
GlobalPlugins返回Metadata.IsGlobal == true的插件(任何输入都会参与查询);NonGlobalPlugins返回配置了非空ActionKeyword的插件(仅当输入以该前缀触发时才参与查询); - 测试支持:暴露
SetAllPlugins静态方法,仅供测试注入替身插件列表(源码注释“should be only used in tests”);配套的单测位于 Wox.Test/PluginManagerTest.cs。
插件设置:plugin.json 与 settings.json 两级结构
文档“Plugin settings”一节的关键结论有三点,均可在仓库中得到印证:
- 可编辑设置存储在
PowerToys Run\settings.json:即各插件在设置 UI 中被用户修改后的AdditionalOptions值最终落在这里,并在下次启动时由UpdateSettings读取; - 首次运行时,设置从插件的
plugin.json填充:plugin.json是插件的“出厂默认 + 元数据”清单,首次启动后宿主以其为种子生成settings.json中对应条目; - 不支持多个动作关键词:与上游 Wox 不同,PowerToys Run 每个插件只有一个
ActionKeyword与一个IsGlobal开关,没有多关键词列表。
以计算器插件的 plugin.json 为例:
{
"ID": "CEA0FDFC6D3B4085823D60DC76F28855",
"ActionKeyword": "=",
"IsGlobal": true,
"Name": "Calculator",
"Author": "cxfksword",
"Version": "1.0.0",
"Language": "csharp",
"Website": "https://aka.ms/PowerToys",
"ExecuteFileName": "Microsoft.PowerToys.Run.Plugin.Calculator.dll",
"IcoPathDark": "Images\\calculator.dark.png",
"IcoPathLight": "Images\\calculator.light.png"
}
各字段的作用:ID 为插件唯一标识(也是源码中 Main.PluginID 常量与之保持一致、用于校验 plugin.json 的依据);ActionKeyword: "=" 表示用户以 = 开头时触发该插件;IsGlobal: true 表示它同时参与全局查询——这正是计算器既能写 =2+3 又能直接写 2+3 的原因;ExecuteFileName 指明宿主要加载的 DLL;IcoPathDark / IcoPathLight 指明两种主题下的插件图标。
仓库内置插件一览
按上述契约,仓库中预装了约二十个 C# 插件,均位于 src/modules/launcher/Plugins/,与文档 doc/devdocs/modules/launcher/plugins/ 下的逐插件说明文档一一对应:
| 插件项目 | 对应文档 |
|---|---|
Microsoft.PowerToys.Run.Plugin.Calculator |
calculator.md |
Microsoft.Plugin.Indexer |
indexer.md |
Microsoft.Plugin.Program |
program.md |
Microsoft.Plugin.Folder |
folder.md |
Microsoft.Plugin.WindowWalker |
windowwalker.md |
Microsoft.PowerToys.Run.Plugin.WebSearch |
websearch.md |
Microsoft.PowerToys.Run.Plugin.TimeDate |
timedate.md |
Microsoft.PowerToys.Run.Plugin.WindowsSettings |
windowssettings.md |
Microsoft.PowerToys.Run.Plugin.Registry |
registry.md |
Microsoft.PowerToys.Run.Plugin.System |
system.md |
Community.PowerToys.Run.Plugin.UnitConverter |
community.unitconverter.md |
Community.PowerToys.Run.Plugin.ValueGenerator |
community.valuegenerator.md |
Microsoft.Plugin.Shell / Microsoft.Plugin.Uri / Microsoft.PowerToys.Run.Plugin.History 等 |
shell.md / uri.md / history.md |
如需扩展插件体系,仓库还提供了 new-plugin-checklist.md、architecture.md 与 debugging.md 三份配套文档,分别覆盖新插件开发清单、整体架构与调试方法。
小结
PowerToys Run 的插件模型可以浓缩为一条清晰的生命周期链:PluginManager 扫描两级插件目录并解析 plugin.json(按 ID 去重、按版本择优)→ 为插件构造 PluginInitContext 并调用 Init()(插件在此订阅主题事件、读取存储与设置)→ 用户每次输入时按 ActionKeyword / IsGlobal 路由并调用 Query()(插件返回带 Score 的 Result 列表,宿主据此排序渲染)→ 设置界面变更触发 UpdateSettings(),主题切换触发 ThemeChanged,配置通过 Save() 写入 PowerToys Run\settings.json 持久化。理解这条链路,并参照计算器插件中 Init / Query / UpdateSettings / 主题回调的完整实现,就具备了为 PowerToys Run 编写行为正确、设置可持久、主题可适配的插件的全部基础。
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 StartedRust0625
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