
Omarchy 菜单系统深入解析基于 JSONC 的 Quickshell 命令菜单架构与 dmenu 扩展实践【免费下载链接】omarchyBeautiful, Modern Opinionated Linux项目地址: https://gitcode.com/GitHub_Trending/om/omarchyOmarchyBeautiful, Modern Opinionated Linux将系统中最常用的操作——从开关机、截屏、主题切换、安装卸载软件到默认浏览器/编辑器设置——统一收敛进一个全键盘可驱动的命令菜单。该菜单不是一个静态工具列表而是以两份 JSONC 数据文件为源码、由 Quickshell 桌面插件omarchy.menu在运行时动态渲染、并通过 CLI 与 IPC 对外提供完整控制面的可扩展系统。读完本文你将掌握 Omarchy 菜单的条目数据模型Entry Schema、合并与守卫机制、Provider 动态行源、omarchy menu系列命令的用法以及如何写出能正确接入该系统的自定义菜单扩展。架构总览菜单是怎么长出来的菜单本身是 Quickshell 桌面下的omarchy.menu插件对应 插件清单 中声明的omarchy.menu同时提供menu与bar-widget两种入口。它的全部菜单内容被建模为纯数据而不是散落在 QML/JS 里的硬编码出厂默认菜单数据写在 default/omarchy/omarchy-menu.jsonc运行时从$OMARCHY_PATH读取用户扩展覆盖层用户自己的条目写在~/.config/omarchy/extensions/omarchy-menu.jsonc仓库中的示例/文档模板在安装时会被刷新到用户目录渲染与交互层界面逻辑集中在 shell/plugins/menu/Menu.qml纯逻辑层shell/plugins/menu/MenuModel.js 是不依赖 Qt/QML 的纯 JavaScriptNode 也能直接加载——这正是测试能低成本覆盖它的原因。Shell 在启动时一次性解析这两份文件并做合并同时监视它们的变化任何一次编辑都能即时生效无需重启桌面 shell。更关键的是菜单打开路径上永远不需要再 shell out 去解析 JSON——快捷键 → IPC → 显示这一整条链是常驻内存的打开菜单的延迟因此只取决于渲染本身。在 Menu.qml 中可以看到refresh()的实现重新reload()两个文件即可完成一次菜单重载。JSONC 语法比带注释的 JSON更窄Omarchy 菜单用 JSONCJSON with Comments作为文件格式本意是获得 Neovim 友好高亮、注释与尾逗号。但它的解析方式与完整 JSONC 规范并不相同——见 MenuModel.js 的stripJsoncfunction stripJsonc(raw) { return String(raw || ) .replace(/^\s*\/\/[^\n]*(\n|$)/gm, ) .replace(/,(\s*[}\]])/g, $1) }实际行为是只剥离整行//注释、并清理]/}前的尾逗号。这意味着注释必须独占整行或至少以行首//开始某行文字后紧跟// 注释会破坏解析内联注释、/* */块注释都不被支持解析失败的文件贡献 0 个条目——一个坏掉的用户扩展会静默丢弃所有用户条目但出厂菜单照常工作不会整体崩掉。解析入口parseMenuJsonc在JSON.parse失败或顶层不是对象时都返回空数组这是坏文件 无条目约定在代码层的落实。条目数据模型id 即树kind 靠推断JSONC 文件的顶层是一个对象每个键就是一条菜单条目Entry的 id。树形结构由 id 中的点表达而不是由独立的parent字段维系trigger.share.file是trigger.share的子条目不含点的 id如apps、system挂在根菜单上normalizeItem中父级由id.split(.).slice(0, -1).join(.)推算见 MenuModel.jsid root则无父级。这样设计消除了父子失同步这一类数据错误条目出现在哪完全由它叫什么决定。parent字段被接受但仓库出货的菜单里没有条目使用它。条目的种类kind同样是推断而非声明特征推断结果有action字段action动作选中即运行命令有target字段link指向另一个已存在的子菜单呈现为跳转链接两者皆无menu子菜单容器各字段含义如下与仓库出货文件中的实际用法一一对应字段含义备注 / 源码佐证icon图标列的字符通常是 Nerd Font 字形空字符串表示无图标iconFont图标所用字体族与菜单默认字体不同时使用私有omarchy字体中的品牌字形就是靠它渲染的例如install.ai.chatgpt条目label可见行标题缺省回退为 idtitle打开该子菜单时顶栏显示的标题缺省回退为label允许一行在列表里显示 Browser打开后标题却是 Default Browsersetup.default.browser正是如此action选中时要执行的 shell 命令分离detached运行如action:omarchy-system-locktarget要打开的既有子菜单 id使该行成为链接provider本子菜单的动态行源名字见 Providers 一节名字由 shell 定义JSONC 无法声明新 provideraliasesomarchy menu summon name的备用路由名同时参与搜索新条目不应添加见下description搜索时的副标题文字同时作为按整词匹配的额外搜索文本when/checked/disabled三种 shell 条件守卫详见 Guards 一节需要特别强调不要为新条目添加aliases。它们被保留给用户已经习惯输入的既有名称如power-menu、settings仅作兼容用途仓库AGENTS.md有相应说明。搜索并不依赖别名——label、id 的最后一段和description都已纳入搜索文本nameSearchText与matchesQuery会拼接 label、叶子 id 段与别名统一建索引。加载与合并覆盖而非复制两份文件靠 mergeMenuSources 合并用户条目按键覆盖默认条目遵循先默认、后用户的顺序复用reuse一个出厂 id则只覆盖你声明的那些字段——扩展可以只改标题或图标而不重新声明 action被覆盖的条目保持在列表中的原始位置全新 id 一律追加到菜单尾部若两份文件都没有声明root则自动注入一个根节点rootlabel 为 Go作为容器。这一设计让微调式扩展非常轻量。以仓库自带的扩展示例文件为例它的头部注释完整记录了格式正文只有注释、不带任何条目因此默认状态下用户层不改变任何东西。文件注释里的示例演示了两种典型用法personal: {icon:,label:Personal}, personal.notes: {icon:,label:Notes,action:omarchy-launch-editor ~/notes},以及一个替换出厂 About 行为的例子——复用同一个aboutid未声明的字段保持原值about: {icon:,label:About,action:omarchy-launch-or-focus-tui \zsh -c fastfetch; read -k 1\},Guardswhen/checked/disabled三种守卫字段都是bash 条件表达式用于让菜单反映机器当前的真实状态。Omarchy 在何时评估、如何评估上做了非常讲究的设计shell 绝不在打开路径上逐个评估守卫。每次重加载和每次打开菜单时全部守卫会被批量打进一个 bash 进程里执行输出形如id:w|c|d:0|1的行菜单直接按上一次评估的答案立即打开。换句话说单次批处理耗时的上限就是某一行可能与它所描述状态矛盾的窗口时长——这正是守卫求值被极致优化的原因包/命令存在性在进程内用一个pacman -Q快照回答见 guardHelpers 生成的__omarchy_pkgs关联数组与omarchy-pkg-present/omarchy-pkg-missing/omarchy-cmd-present/omarchy-cmd-missing阴影函数而不是每行 fork 一次 pacman。快照还通过pacman -Qi的 Provides 解析provides所以装了 gvim 时vim也被认为已存在install.editor.vim不会重复推销。被多行共享读取的命令只跑一次比如 Defaults Browser 的每一行都要与$(omarchy-default-browser)比较。GUARD_READERSMenuModel.js列出了所有这类 readeromarchy-channel-current、omarchy-default-agent、omarchy-default-browser、omarchy-default-editor、omarchy-default-terminal、omarchy-dns。批处理先把它们各执行一次、捕获输出再用占位槽把捕获值代入每条表达式substituteGuardReaders。任何一个$(omarchy-...)reader 若被多行使用就必须登记进GUARD_READERS否则menu-guards-test.sh会令构建失败。三种守卫的失败语义截然不同when失败即隐藏该行。若某子菜单所有可见后代都被隐藏该子菜单本身也随之消失但 provider 支撑的子菜单始终保留——因为它们的行是运行时按需加载的静态判定不可靠。checked成功即追加 ✓——这正是 Defaults、DNS、Channel 等当前选择项行的对勾来源labelFor中实现。disabled让行保持列出但变灰置灰、打 ✓、且不可选中光标、指针、回车都会跳过它搜索也跳过它见 MenuModel.js 与菜单测试displayRow(...).disabled断言。Install 子菜单用它来表达软件已在机器上——已装软件不会从它被装进来的那个列表里消失列表始终保持为Omarchy 能装什么的完整目录。既然置灰意味着你已经拥有它它与其他地方的checked一样值得同一枚 ✓labelFor的注释直接说明了这一约定。由此得出重要约定Install 行应使用disabled: 存在性检查而不是when:Remove 行则相反用when:把不存在的东西隐藏掉。在仓库的 default/omarchy/omarchy-menu.jsonc 中一眼可见该约定——所有install.*行几乎都带disabled:omarchy-pkg-present ...或等价的文件/目录存在检查如install.gaming.battlenet检查$HOME/Games/battlenet、install.development.rust检查$HOME/.rustup而remove.*行则统一用when:omarchy-pkg-present ...或systemctl is-enabled --quiet ...反查。test/shell.d/menu-test.sh会强制检查 Install 侧约定既有行在软件已装后必须依然可见disabled !when不允许 Install 行因软件存在而凭空消失。Providers子菜单行也可以来自运行时带provider: name的子菜单其行不在 JSONC 中声明而是运行时生成。名字空间由 shell而非菜单文件拥有——扩展可以把某子菜单指向一个既有 provider但无法声明新 provider。从 Menu.qml 的providers映射可以看到已内置的种类appsQML 原生行来自共享的 AppLibrary桌面条目 desktop entries携带图片图标、启动反馈和卸载支持与独立的 App launcher 体验一致。应用行可按其桌面Keywords搜索但永远不可路由——已安装应用绝不能抢占菜单路由htop 自带Keywordssystem;...而 SUPERESCAPE 必须依旧打开系统菜单。这正是 resolveRoute 跳过kind app的原因。fonts与power-profilesbash 一行流契约是每行一条 tab 分隔记录label\tvalue\tcurrent。值等于current的行显示 ✓ 图标选中后运行该 provider 的actionFor(value)字体行生成omarchy-font-set value电源配置行生成omarchy-powerprofiles-set autodetect value。行 id 形如menuId.slugify(value)碰撞时追加-后缀保证两个 slugify 后同名取值不会静默吞掉某一行slug 规则见 slugify。标记为volatile的 provider 会在每次进入其子菜单时重新运行——自 shell 启动后才安装的字体无需重启就能出现但不会在每次搜索按键时重跑那会把同一枚举在每次击键时重启一遍。fonts正是volatile: true的实例所以Style Font列表始终是最新系统字体集。Provider 结果的合并由swapProviderRowsMenuModel.js完成每行都携带产生它的子菜单 idproviderMenu因此 provider 再次运行时会丢弃自己上一次的批次却不惊动 JSONC 里静态声明的兄弟子条目例如刚启用的插件会从 Enable 列表中消失。它与 app 合并mergeAppRows一样都返回全新的 items/itemOrder 对象交给调用方一次性赋值——绝不能原地改写 QMLvar属性持有的 map否则引擎偶尔会丢掉那次写入历史上这曾导致 launcher 行重复。若你要新增 provider在Menu.qml的providers映射里加入(script, icon, actionFor, 可选 volatile)再用provider:把某子菜单指向它即可。用 CLI 驱动菜单omarchy menu子命令bin/omarchy-menu是对标准插件 IPC 接口的一层薄封装支持如下命令面omarchy menu # 切换根菜单开关 omarchy menu toggle system # 打开到某路由若已打开则关闭 omarchy menu summon style.theme # 总是打开没有已可见则关闭行为 omarchy menu close omarchy menu refresh # 重新解析两份 JSONC 文件 omarchy menu ping路由route解析规则resolveRoute路由可以是条目 id 或已声明别名大小写不敏感_会被归一化为-精确 id 优先于任何别名空输入、go、menu均表示根菜单未知字符串作为字面 id 兜底放行——拼写错误仍会尝试打开该 idsummon到解析为动作action的路由如叶子动作的别名screenrecord-stop会直接执行该动作而不是打开一个没有子项的动作框link 则被跟随到它的 target。Omarchy 的默认 Hyprland 绑定全部经由这一层 IPC 表面见 default/hypr/bindings/utilities.lua快捷键动作SUPER SPACEomarchy-menu toggle根菜单SUPER ESCAPEomarchy-menu toggle system系统菜单SUPER ALT SPACEomarchy-menu toggle appsSUPER CTRL Eomarchy-menu toggle capture等快捷入口所以无论你按哪个快捷键最终都是把一串文本交给omarchy menu再由它走 IPC 触达菜单插件——行为完全一致、可被脚本复用。Select / Input 模式同一个插件兼职系统 dmenuomarchy.menu插件还承担了系统级 dmenu 的角色。bin/omarchy-menu-select与bin/omarchy-menu-input两个脚本以mode: select/mode: input的载荷把菜单召唤出来然后阻塞在一个临时文件握手上shell 把选择写入selectionFile并 touchdoneFile调用方读到后返回。取消空选择时退出码为 1。实现上脚本创建mktemp文件、trap ... EXIT清理再把文件路径作为参数传进 Quickshell IPC 调用selectionFile ...、doneFile ...随后轮询/等待文件内容。select 模式每行选项的规格为label、glyph\tlabel或glyph\tlabel\tsubtextglyph 只用于展示永远不随结果返回subtext 渲染在 label 下方、与 label 一起参与过滤返回时以label\tsubtext拼接——这样拥有同名行的调用方也能拿到稳定可区分的 key。正是这种机制让菜单 action 背后的各类选择器omarchy-menu-plugin、omarchy-menu-timezone等可以纯命令行地弹出一个自带 UI 的列表而无需各自实现一套界面一个菜单既是导航系统又是即取即用的选择器与文本输入框。从一条真实命令看完整链路以仓库菜单中最具代表性的动作之一为例出厂根菜单里的style.theme条目style.theme: {icon:,label:Theme,aliases:[theme,themes], action:theme$(omarchy-theme-switcher); [[ -n $theme ]] omarchy-theme-set \$theme\}它说明了一个贯穿全章的要点菜单不捆绑任何 UI 逻辑——omarchy-menu系列小工具theme switcher、background switcher、share picker……负责呈现交互结果菜单只负责把它们编排进可搜索、可键盘导航、可按机器状态显隐的层级结构中。定义数据在 default/omarchy/omarchy-menu.jsonc解析/合并/路由的纯逻辑在 MenuModel.js渲染与 provider 在 Menu.qml测试落在test/shell.d/menu-test.sh与menu-guards-test.sh——整条链路的数据、逻辑、界面与验证被干净地切分这也是它既能热重载、又能被单元测试直接驱动的原因。结语把菜单当作一份活的文档对 Omarchy 用户与开发者而言最实用的心智模型是菜单 描述系统能力的声明式数据 反映系统状态的运行时守卫 按需生成清单的 provider。想了解这台机器能做什么就读omarchy-menu.jsonc想让它多一个入口就在~/.config/omarchy/extensions/omarchy-menu.jsonc中按条目 schema 追加一个带点号的 id想让行随环境显隐就选择when:、checked:、disabled:中语义正确的那一种。理解这些约定之后你不仅能熟练自定义自己的 Omarchy 菜单也能在阅读任何 Quickshell 插件时快速定位数据—逻辑—渲染三者之间的边界。【免费下载链接】omarchyBeautiful, Modern Opinionated Linux项目地址: https://gitcode.com/GitHub_Trending/om/omarchy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考