十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Omarchy Shell 插件架构深度解析:单进程 Quickshell 宿主、Manifest 契约与可插拔桌面

Omarchy Shell 插件架构深度解析:单进程 Quickshell 宿主、Manifest 契约与可插拔桌面 Omarchy Shell 插件架构深度解析单进程 Quickshell 宿主、Manifest 契约与可插拔桌面【免费下载链接】omarchyBeautiful, Modern Opinionated Linux项目地址: https://gitcode.com/GitHub_Trending/om/omarchy导读Omarchy 是一套美观、现代且个性鲜明的 Linux 桌面体验而其桌面外壳omarchy-shell采用了一种与多数 Wayland 合成器方案截然不同的架构整个桌面由单个长驻的 Quickshell 进程承载——顶栏bar、面板panels、悬浮菜单、OSD、锁屏、Polkit 认证对话框乃至无 UI 的后台服务全部以内嵌插件plugin的形式运行在同一个进程中。本文以仓库中的 shell/README.md 为骨架结合 shell.qml、PluginRegistry.qml 等源码完整讲解插件的 manifest 清单契约、omarchy plugin安装 / 克隆工作流、shell IPC 协议与shell.json持久化规则帮助你理解这套架构的设计动机并掌握编写或接入一个第三方插件所需的关键知识。单进程架构为什么 Omarchy 把所有桌面组件装进一个 Shellomarchy-shell是一个运行整个 Omarchy 桌面的Quickshell 长驻实例。Hyprland 在 autostart 阶段为每个图形会话启动一个 shell之后桌面里的一切——顶栏、背景切换器、各类面板与覆盖层overlay——都以插件身份运行在这个 shell内部。把一切托管在单一进程内带来三个直接收益原文 shell/README.md 的明确表述共享服务与单例只存活一次而不是每个进程各持一份召唤面板只是一次对已运行进程的 IPC 调用不再是quickshell -p ...的冷启动第三方插件可以不加修改任何 Omarchy 源码、直接从磁盘加载。运行时布局shell/目录的代码结构直接对应这套运行时模型shell/ shell.qml entry point (ShellRoot) services/ PluginRegistry.qml 发现、校验插件并在 shell.json 中查询启用状态 BarWidgetRegistry.qml 顶栏部件的统一注册表第一方 第三方 plugins/ bar/ 第一方插件详见 plugins/README.md image-picker/ menu/ notifications/ panels/ audio/ bluetooth/ monitor/ network/ power/ weather/ agents/ services/ battery/ idle/ osd/ polkit/其中 shell.qml 是宿主入口ShellRootPluginRegistry.qml 负责插件的发现、校验与启用状态查询BarWidgetRegistry.qml 则是顶栏部件的统一注册表——内置部件与第三方部件走同一条登记路径。插件发现机制的完整说明见 shell/plugins/README.md。关于单例与注入还有一个容易踩坑的实现细节宿主在 shell.qml 中把pluginRegistry、barWidgetRegistry、appLibrary声明为共享属性再通过属性注入分发给插件而不是让插件以单例方式重复 import。源码注释明确指出相对路径 import 不会共享单例状态会静默地让消费者拿到各自为空的副本——这是插件看着是单例、用起来却是空的的经典陷阱。Plugin manifest每个插件的身份证与装载说明书每个插件都随附一个manifest.json向 shell 说明我是谁、属于哪一类、应该怎样被加载。最小示例完整版含 bar 部件声明{ schemaVersion: 1, id: my.org.cool-clock, name: Cool clock, version: 1.0.0, author: You, description: A clock that does cool things, kinds: [bar-widget], entryPoints: { barWidget: Widget.qml }, barWidget: { displayName: Cool clock, category: Time, allowMultiple: false, defaultSection: left, defaults: { format: HH:mm }, schema: [ { key: format, type: string, label: Format } ] } }kinds插件能扮演的六种角色一个 manifest 可以声明多种kinds仓库中 docs/omarchy-shell.md 与 shell/README 均如此表述Kind角色说明bar-widget可被当前顶栏放入某个 section左/中/右的组件panel持久或按需召唤的浮动窗口例如 OSDoverlay全屏覆盖层例如背景切换器menu被召唤的菜单表面service无 UI 的后台单例bar可整体替换内置omarchy.bar的完整顶栏方案kinds是可组合的——例如 Omarchy 主菜单omarchy.menu同时声明menu与bar-widget两种 kind见 shell/plugins/README.md 的插件清单表。装载语义何时加载、何时常驻同一时刻只能有一个bar插件处于激活状态。当所选 bar 缺失或非法时自动回退到内置omarchy.bar——源码 shell.qml 中activeBarId的计算逻辑保证了永远有安全的回家路径一旦第三方 bar 加载失败Loader.ErrorfailedBarId会被记录并回退内置 barshell.qml。panel、overlay、menu 在被召唤时才加载。需要比单次召唤活得更久的插件可以设置顶层键keepLoaded: true。其典型场景是 image-picker它的 overlay 窗口要在多次召唤之间保持挂载。该标志还有一个安全层面的用途——让service 在插件热重载期间保持挂载如果重载时销毁了omarchy.lockHyprland 仍持有会话锁时锁屏客户端却已消失就会触发崩溃锁屏回退。不过被保留的实例不会被替换因此keepLoadedservice 自身的代码改动要等 shell 重启才生效第一方 service 在启动阶段加载源码 shell.qml 的unloadPluginServices()正是据此在重载时跳过 keepLoaded 服务。宿主注入与能力隔离Facade插件的 entry pointQMLItem在被加载后宿主会把一批属性注入进来omarchyPath、shell、manifest、pluginRegistry、barWidgetRegistry。内置插件拿到的是受信任的宿主对象本体而第三方插件拿到的是一套按能力裁剪的 Facade外观代理普通插件只能查询与控制自己的service 与生命周期内置插件的克隆clone保留来源插件狭窄的配置与 UI 兼容面menu类插件会额外获得一个应用库application-libraryFacade插件可以读取分离的标量顶栏状态bar 状态快照完整的bar插件还会收到分离的顶栏配置与部件目录快照、内置顶栏部件所用非认证服务的窄代理以及对已配置的非认证 UI 插件的生命周期控制权。认证authentication类能力的戳记stamp只来自受信任的第一方 manifest认证服务被保留在宿主的公共 service 映射与 QML 对象树之外。以 shell.qml 的实现为例ensureService()会先判断isAuthenticationService()认证服务通过私有 JS import 的AuthServiceStore持有其生命周期既不出现在ShellRoot._services公共映射里也没有指向宿主的可遍历 QML 父对象同时第三方对 registry 或配置快照的本地改动不会反向变异宿主状态。需要特别强调Facade 是 API 边界不是同进程 QML 沙箱。视觉部件共享宿主的 QML 场景仍可沿父级层级遍历到普通宿主对象。因此敏感状态绝不能只依赖 Facade 做隔离——这一点在 shell/README.md、docs/omarchy-shell.md 以及 shell.qml 的注释中反复出现。还有一个细节规则由第三方替换 bar 渲染的部件收到的是无 service的条目 Facade只带目标作用域的生命周期与设置操作。其 live service 对象只在受信任的内置 bar 托管它们时才可用——否则替换 bar 可以借制造任意部件的 own-service Facade来取得该部件插件的真实 service。也就是说service-backed 的第三方部件只有在内置 bar 下才保有完整集成能力替换 bar 仍能提供其目标作用域的操作。完整的 schema 与校验规则以 shell/services/PluginRegistry.qml 为准。源码级manifest 如何被校验PluginRegistry.validateManifest()shell/services/PluginRegistry.qml把上述契约固化为硬校验写作第三方插件时这些都是红线schemaVersion必须等于1必填字段为id、name、version、kinds、entryPoints缺一即拒绝id不能包含/、..也不能以/开头——这直接封堵了路径穿越kinds必须是非空数组若声明barWidget.defaultSection只允许left/center/right三者之一所有entryPoints必须是插件源码目录内部的相对路径一旦越界整个 manifest 被拒。而entryPointUrl()PluginRegistry.qml在解析时还会再做一次防御纵深确认拼接后的路径仍落在sourceDir前缀之内。仓库自带的真实清单可以对照阅读shell/plugins/bar/manifest.jsonomarchy.barkind 为bar与 shell/plugins/panels/clock/manifest.jsonomarchy.clockkind 为bar-widget带barWidget.displayName/category/allowMultiple。内置 widget 的 manifest 就放在其 QML 文件旁边例如bar/widgets/*.manifest.json。用omarchy plugin安装、更新与移除第三方插件在 Omarchy 中一个插件就是一个 git 仓库其根目录放有manifest.json。omarchy plugin add会把仓库克隆进~/.config/omarchy/plugins/id/目录以 manifest 中的id命名更新则是对该 checkout 做一次 fast-forward pullomarchy plugin add https://github.com/acme/omarchy-weather.git omarchy plugin update acme.weather # fetch、展示 diff、fast-forward omarchy plugin update # 更新所有 git 管理的插件 omarchy plugin remove acme.weatheromarchy plugin系列命令的底层都封装了 shell IPC见下文的 IPC 小节对应仓库 bin/ 下存在omarchy-plugin-add、omarchy-plugin-update、omarchy-plugin-enable、omarchy-plugin-clone、omarchy-plugin-validate等独立包装命令并通过统一的omarchyCLI 入口路由bin/omarchy。交互模式与无交互模式在终端裸运行时每个命令都是交互式的gum 选择器、确认提示、可审阅的 diff带参数运行时完全非交互再加--yes则跳过全部提示——这正是脚本与 AI Agent 推荐的调用路径omarchy plugin add https://github.com/acme/omarchy-weather.git --enable --yes omarchy plugin update --yes--enable让插件在克隆完成后直接启用。安装器从不执行插件代码、安装 hook 或 sudo它只做克隆文件、校验 manifest、并通过 shell IPC 翻转启用状态。既然已安装插件就是一个普通 git checkout那么 add/update 之外的任何操作固定某个 ref、切换分支……都只是在该插件目录里做普通 git 操作。安全须知非沙箱执行模型⚠️插件以非沙箱代码的身份运行在omarchy-shell内部。add在克隆前会警告你插件落地时默认处于禁用状态让你先审阅代码再启用update在改动任何东西之前会先展示 diff。作用域化的 QML 接口移除了直接的认证服务与通用替换 bar 服务查询但视觉插件仍共享且可遍历普通宿主场景。只添加你愿意运行其代码的仓库。这条警告的措辞原文值得逐句记住它同时概括了安全模型的两面API 层的能力裁剪能挡住类型级的越权拿不到认证服务、拿不到跨插件服务工厂但代码执行层面 shell 对插件代码没有沙箱。手工安装不经过 git 也行你可以完全不经过 git 把插件放进目录将插件放入~/.config/omarchy/plugins/plugin-id/内含manifest.json及其entryPoints引用的 QML 文件执行omarchy-shell shell rescanPlugins执行omarchy plugin enable id。此后bar widget 会出现在barWidget.defaultSection声明的 section未声明时默认center此细节在 docs/omarchy-shell.md 有明确补充之后可用omarchy bar move移动而完整 bar 插件则会直接替换当前顶栏。手工安装对应的底层 IPC 是omarchy-shell shell rescanPlugins、omarchy-shell shell enablePlugin id {}、omarchy-shell shell listPluginsomarchy bar move与omarchy bar set则是直接编辑shell.json里持久化的 widget 布局。克隆内置插件来安全地改造想改内置插件别改仓库源码——把它克隆到用户配置里更安全omarchy plugin clone omarchy.clock会把完整插件目录含声明的每个 kind 与本地依赖复制到用户配置并将内置 id 如omarchy.clock变为username.clock例如dhh.clock显示名为My Clock。用户名前缀的意义在于让共享机器上的不同克隆互不冲突、也不会与其他插件作者撞名。克隆还会保留既有 bar widget 的位置与设置把旧的内置 id 的快捷键与 shell IPC 调用路由到已启用的克隆——调用方无需改动移除激活中的克隆则自动切回内置源在~/.config/omarchy/plugins/下保存任意文件都会自动触发插件代码重载也可随时omarchy-shell shell rescanPlugins强制重载。GUI 途径对应 Setup Plugins Clone提供交互式选择器克隆完成会在$EDITOR中打开新的username.*目录。启用/禁用语义随 kind 而异第一方插件位于 shell/plugins/ 下以同样方式被发现并默认加载。差异只在状态记录禁用非 widget记录进disabledPlugins[]禁用 widget把它从顶栏布局中移除但组件仍可再次添加完整 bar 没有 off 状态——通过启用另一个 bar 插件来替换它这也呼应了listPluginsIPC 中canDisable: !isBarOption的字段设计见 shell.qml。IPC 契约CLI、快捷键与 shell 之间的会话协议shell 对外暴露一个shellIPC target外加各插件自行注册的 target例如顶栏部件面板的实例路由、image-picker 的image-selectortarget。omarchy-menu正是借助shelltarget 召唤第一方omarchy.menu插件而不再启动第二个 Quickshell 实例——这是单进程架构在用户可感知层面的典型体现。shelltarget 的核心方法方法返回作用pingok健康检查summon id payloadJsonok/unknown加载并打开一个 panel/overlay 插件hide id—关闭之前被召唤的插件toggle id payloadJson—已关闭则召唤已打开则隐藏call id method argstring调用已加载插件上的方法rescanPlugins—重扫插件目录并热重载插件代码reloadConfigok重新加载~/.config/omarchy/shell.jsonsetPluginEnabled id enabledok/unknown翻转持久化的启用位注意参数是字符串见下listPluginsJSON按名称排序返回所有已发现插件此外 shell.qml 中的IpcHandler target: shell还实现了更丰富的编排方法applyTheme推送主题色与 shell 配置、toggleBarTransparency、enablePlugin id placementJson一次变异完成启用放置、putBarWidget仅当部件不在 bar 上时放置、moveBarWidget、setBarWidget、togglePanelAt section index按 section 位置而非 id 切换面板、listShellConfig导出生效中的 shell.json与debugBarGeometry。更完整的对照表见 docs/omarchy-shell.md。直接调用与便捷包装绕过包装、直接用 Quickshell IPC 发起调用quickshell ipc -p $OMARCHY_PATH/shell call shell pingHyprland autostart 用quickshell -p $OMARCHY_PATH/shell直接启动 shell需要重启时使用omarchy-restart-shell——它会停止该配置的所有运行实例并启动一个全新 shell 进程。而 bin/omarchy-shell 只是一个转发包装它把 IPC 调用转发给正在运行的 shell自己不会启动 shell。omarchy-shell shell ping omarchy-shell shell toggle omarchy.menu {menu:root} omarchy-shell shell listPlugins omarchy-shell shell rescanPlugins包装脚本 bin/omarchy-shell 还有几个值得知道的工程细节-q静默尽力而为模式抑制输出即使 shell/target/method 不可用也返回成功便于集成方探一下有没有环境变量OMARCHY_SHELL_IPC_TIMEOUT默认2s限定等待上限无WAYLAND_DISPLAY时例如 SSH/TTY 里跑迁移脚本会从 compositor socket 恢复它对shell summon/toggle且只有一个参数时会自动补{}作为空 payloadqs ipc的 IPC 级失败target/function 未知、参数错误会打印到 stdout 且 exit 0因此脚本用 case 匹配 Target not found.、Function not found. 等输出再做失败判定。setPluginEnabled的字符串陷阱注意enabled参数是字符串不是布尔值。只有字面量true会启用插件其他任何值包括True、1、yes乃至缺省都会禁用它。这样设计是为了在 QML 仅支持string型 IPC 参数的前提下保持接口类型稳定实现见 shell.qmlsetPluginEnabled(id, enabled)内部只做enabled true的比较。持久化状态唯一配置文件shell.jsonOmarchy 的用户态定制收敛到一个文件里——能把你与出厂默认区分开的一切都存于此路径属主用途~/.config/omarchy/shell.jsonshell完整布局 每个条目的设置 已启用插件清单~/.config/omarchy/plugins/id/用户直接投放的第三方插件源码文件仓库里的 config/omarchy/shell.json 描述全新安装状态。当用户没有shell.json时shell 原样使用默认值一旦用户定制了任何东西shell.json就是权威文件——shell 不会把默认值深度合并回去。源码 shell.qml 的applyShellConfig()直接体现了该语义有效用户配置JSON 可解析、version 1整体覆盖 defaults解析失败或版本未知则回退默认。作为最后兜底shell.qml 还内嵌了一份builtinShellConfig即使默认shell.json缺失或不可读shell 也能渲染出可用的 bar。shell.json 的形态{ version: 1, idle: { screensaver: 150, lock: 300 }, bar: { id: omarchy.bar, position: top, transparent: false, centerAnchor: omarchy.clock, layout: { left: [ { id: omarchy.menu }, { id: omarchy.workspaces } ], center: [ { id: omarchy.clock, format: HH:mm } ], right: [ { id: omarchy.audio } ] } }, plugins: [] }真实出厂文件 config/omarchy/shell.json 的中栏还配置了omarchy.indicators、omarchy.clock带format/formatAlt/verticalFormat、omarchy.keyboard-layout、omarchy.weather、omarchy.system-update右栏则有 tray、agents、bluetooth、network、audio、monitor、power 等——可作为排布 widget 的现实参考。八条存储规则激活的 bar 方案由bar.id决定。省略它或设为omarchy.bar即使用内置 bar设为某个 manifest 声明了kind: bar的插件 id 即替换整条顶栏。每个插件实例对应一条记录。bar widget 记录在bar.layout.section其余panel、overlay、service、menu 等非 bar 插件记录在plugins[]。设置内联在条目上。没有config:子对象、没有独立的分插件设置文件、没有合并层。条目上的字段就是插件最终看到的取值。源码updateEntryInline()shell.qml正是合并设置到 shell.json 对应条目的实现——它先在本地克隆里计算拟议配置只有实际发生变化才落盘避免响应式绑定把 shell.json 无谓写脏。内置 widget 的 id 带命名空间。例如omarchy.clock、omarchy.audio、omarchy.network迁移逻辑会把老的Clock、AudioPanel之类 id 向前重写对应仓库 migrations/ 中大量 shell 相关迁移脚本的存在意义。第三方启用 ⇔ 出现在 shell.json 中。对完整 bar 而言即bar.id对 bar widgetenable/disable 就是增删布局条目其他 kind 同理。第一方非 bar 插件默认启用除非列入disabledPlugins[]这是白名单反过来记的设计PluginRegistry.qml 的注释解释得很清楚若要求第一方基础设施也进plugins[]才能被召唤出厂plugins: []会让omarchy launch bar-settings静默失效。允许多实例当 manifest 声明allowMultiple: true。每个实例相互独立——两个不同时区的时钟就是两条各自带值的{id:omarchy.clock, timezone: ...}条目。idle 计时是顶层字段。idle.screensaver与idle.lock的单位是距用户开始空闲的秒数因此默认 150s 的屏保先触发时300s 的锁屏依然能按计划在 300s 触发。顶层必须带version: 1。版本未知时 shell 回退到默认值而非加载该文件。源码视角插件的发现、装载与热重载链路把上述文档规则落回实现可以串出一条完整链路均在 shell/shell.qml 内启动Component.onCompletedshell.qml登记firstPartyDir、配置的读写回调然后pluginRegistry.rescan()并_syncServices()。插件注册表PluginRegistry.isEnabled()PluginRegistry.qml统一实现规则 5——先看 manifest 是否为barkind比对bar.id非 bar 场景继续查布局/plugins[]/disabledPlugins[]。widget 装载syncPluginWidgets()shell.qml把每个启用的bar-widget插件建成Component并注册进BarWidgetRegistry。这里处理了一类并发陷阱同一 URL 已有进行中的加载时不会重复创建 Component——否则两个同名部件会同时运行并各自注册 IPC handler。按需面板panelEntriesshell.qml为每个可发现的 panel/overlay/menu 插件创建一个Loader只有keepLoaded或被标记打开时才激活。summon()会先把 payload 排入pendingPayloads队列等 Loader 解析后由deliverIfLoaded()依到达顺序投递给open()——两次紧挨的 summon 不会互相覆盖。热重载reloadPlugins()shell.qml先unloadPanels()、unloadPluginServices()、unloadPluginWidgets()再Qt.clearComponentCache()后重新rescan()。正因为unloadPluginServices()会跳过keepLoaded的服务omarchy.lock、omarchy.idle、omarchy.polkit才能在插件代码变动时存活下来。重载期间再次触发的 reload 会被pluginReloadPending合并为一次。bar 部件面板的召唤路由纯 bar-widget不同时是 panel/overlay/menu的 summon/hide/toggle 会被路由到当前 bar 实例shell.qml而不是某个固定 IPC target——否则部件重建后热键只会指向 per-monitor 实例之一。实现历史与演进边界架构是按阶段逐步收敛成型的对应 git 历史中的提交说明Phase 1 —omarchy-shell phase 1: host the existing bar in a single shellPhase 2 —omarchy-shell phase 2: plugin registry and bar widget registryPhase 3 —omarchy-shell phase 3: fold bar-settings into the shell as a panel pluginPhase 4 —omarchy-shell phase 4: absorb background-switcher as a pluginPhase 5 —omarchy-shell phase 5: docs, cleanup, and migration crumbsPhase 6 —omarchy-shell phase 6: reviewer cleanup (path traversal, collision, races)Phase 7 —omarchy-shell phase 7: replace socket with IpcHandler, rename to image-pickerPhase 8a —omarchy-shell phase 8a: unified shell.json with inline plugin settings可以看到评审清理路径穿越、冲突、竞态是一个独立阶段——前文所述 manifest id 校验、entry point 越界防护、双 Component 竞态处理等正是该阶段沉淀下来的产物。边界同样明确共享服务与 Pipewire/UPower/Hyprland 的整合不在当前范围文档原文写明这些被推迟到评审通过后的后续工作。延伸阅读插件发现机制与第一方插件清单shell/plugins/README.md内置顶栏的部件目录与定制 schemashell/plugins/bar/README.mdmanifest 完整 schema 与校验实现shell/services/PluginRegistry.qml顶栏部件统一注册表shell/services/BarWidgetRegistry.qml宿主入口与全部 IPC 实现shell/shell.qmlIPC 转发包装脚本bin/omarchy-shell出厂默认 shell.jsonconfig/omarchy/shell.json更完整的方法对照表与主题令牌Theme tokens说明docs/omarchy-shell.md【免费下载链接】omarchyBeautiful, Modern Opinionated Linux项目地址: https://gitcode.com/GitHub_Trending/om/omarchy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表