跳转至

08 · 插件系统

插件是 Omarchy Shell 的代码扩展单位。manifest 描述身份和入口,PluginRegistry 负责发现、校验、启用与解析,宿主负责实例化和注入依赖。

manifest 是最小契约

{
  "schemaVersion": 1,
  "id": "acme.weather",
  "name": "Weather",
  "version": "1.0.0",
  "kinds": ["bar-widget", "panel"],
  "entryPoints": {
    "barWidget": "BarWidget.qml",
    "panel": "Panel.qml"
  }
}

支持六种 kind:

kind 宿主语义
bar 替换整个 active bar,同一时刻一个
bar-widget 放入 bar section,可声明是否多实例
service 无 UI 的常驻 singleton
panel 浮动窗口,按需召唤
overlay 全屏或覆盖型 surface
menu 可路由召唤的菜单 surface

一个 manifest 可声明多个 kind,并为每种 kind 映射对应 entrypoint。

发现范围

PluginRegistry 扫描两类根:

  • first-party:$OMARCHY_PATH/shell/plugins,允许嵌套到有限层级,也识别 sibling *.manifest.json bar widgets;
  • third-party:~/.config/omarchy/plugins/<id>,每个插件根含 manifest.json。

固定快照有 37 份 manifest。限制扫描深度避免把插件资源树中的任意 JSON 都误当成 manifest,也让发现成本可控。

校验分两层

运行时校验

Registry 拒绝:

  • 非 schemaVersion: 1;
  • 缺少必需 ID、name、version、kinds 或 entryPoints;
  • entrypoint 为空、绝对路径或含 ..;
  • 非法 kind / entrypoint 映射;
  • third-party 使用保留的 omarchy.* ID;
  • third-party 试图覆盖 first-party ID。

entryPointUrl 再做路径解析防御,避免 manifest 验证和真正 URL 拼接之间出现穿越差异。

CLI 安装前校验

omarchy-plugin-validate 更严格:验证入口文件存在、kind 与 key 匹配,并拒绝插件树中(排除 .git)的任何符号链接。安装前阻断比等 QML Loader 报错更易理解,也防止 repo 内链接逃出插件根。

flowchart LR
    Repo[插件 git repo] --> CLI[plugin validate]
    CLI -->|拒绝 symlink/坏 manifest| Stop[不启用]
    CLI --> Root[~/.config/omarchy/plugins/id]
    Root --> Registry[运行时 parse/merge]
    Registry --> URL[安全 entryPointUrl]
    URL --> Loader[QML Loader]

enabled 不是一个布尔规则

启用语义依 kind 与来源不同:

插件 启用条件
full bar shell.json 的 bar.id 指向它
bar widget 出现在某个 bar.layout.<section>
first-party 非 bar 默认启用,除非在 disabledPlugins[]
third-party 非 bar 明确出现在 plugins[]

first-party service 默认开,是因为通知、锁屏、idle 等属于桌面基础设施;third-party 默认关,是因为插件在 Shell 进程内以未沙箱化代码运行。

安装流程为何要求人工确认

omarchy plugin add clone repo 后保持禁用,让用户先审代码。update 会展示 diff,再 fast-forward。即使参数完整,交互路径也倾向在终端中展示警告、编辑器和确认;自动化必须显式 --yes。

这不是完整供应链沙箱,但清楚表达了授权时刻:clone 只是取得代码,enable 才是允许它在桌面进程中执行。

first-party ID 不能被抢占

omarchy.* 是保留命名空间,且 first-party 合并优先。否则用户目录中一个 omarchy.lock 就可能冒充锁屏服务,造成隐蔽而严重的信任替换。

内置插件的 clone 走另一条显式机制:新插件使用用户名命名空间,并用 clonedFrom 保留原有 IPC 身份/路由。当 active clone 删除时,系统可以退回原内置来源。

热重载

Registry 对用户插件目录使用 inotify。保存 QML/JS 触发重新发现与组件更新,开发时无需重启整个 Shell。

热加载的边界是“代码身份重新解析”,不是任意状态迁移保证:

  • 旧 QML 实例可能被销毁;
  • 插件应把必要持久状态放在明确文件或服务中;
  • first-party package 内容不需要同样的开发热重载路径;
  • manifest ID 与 kind 变化可能改变宿主路由。

宿主注入的能力

panel/overlay/menu entrypoint 作为 QML Item 加载,宿主注入:

  • omarchyPath;
  • shell 根对象;
  • manifest;
  • pluginRegistry 与 barWidgetRegistry。

surface 约定暴露 open(payloadJson) 与 close()。这是一种轻量协议:插件不用继承一个庞大基类,宿主也不用理解插件内部 UI。

威胁模型小结

风险 防线 仍需用户承担
路径穿越 relative entrypoint 校验 + URL 防御 审核其他代码行为
symlink 逃逸 CLI validator 拒绝所有 symlink 手工安装可绕过 CLI
冒充内置插件 保留命名空间 + first-party 优先 不启用未知第三方代码
更新夹带代码 更新前展示 diff 真正阅读关键 diff
插件破坏 Shell custom bar fallback;其他加载错误隔离 插件仍与 Shell 同进程、无沙箱

关键源码索引

下一章看一个内置插件如何把复杂交互压成数据模型:09 · 菜单的数据模型。