- 桌面应用
- AI 应用
- 插件系统
【免费下载链接】Wox
A cross-platform launcher that simply works
菜单插件(Menus Plugin)是 Wox 内置的系统插件之一,它把当前 macOS 应用的菜单栏变成一个可全文搜索的命令集:只要让目标应用保持焦点,在 Wox 中输入菜单项名称的一部分,即可定位并直接执行对应的菜单命令。本篇文章将基于 Wox 仓库中的官方文档与插件源码,完整讲解该插件的使用方式、触发机制、菜单解析原理与底层实现,帮助你快速上手,也能理解它背后的 macOS 辅助功能(Accessibility)调用链。
快速开始
菜单插件的使用方式非常简单,遵循"聚焦应用 → 打开 Wox → 输入菜单名"三步流程:
- 让目标应用保持焦点(例如 Safari 或其他任意 Mac 应用);
- 打开 Wox,直接输入菜单项名称的一部分;
- 在结果列表中选中目标菜单项,回车即可执行对应菜单命令。
官方文档给出的两个典型搜索词:
export preferences例如在 Safari 聚焦时输入preferences,Wox 会匹配到「Safari → Settings…」这类位于应用菜单下的菜单项,选中后等效于用鼠标点击该菜单。这个插件只在 macOS 上提供,其元数据中SupportedOS字段明确限定为Macos(见 menus_darwin.go)。
如何触发:全局搜索与「menus」关键词
从插件元数据可以看出,该插件注册了两种触发方式:
TriggerKeywords: []string{ "*", "menus", },*表示全局搜索:不依赖任何前缀,直接在 Wox 中输入菜单项文字即可命中;menus作为显式关键词:输入menus后再接菜单名,可以更精确地在菜单插件范围内过滤。
由于全局匹配到的是形如父菜单->子菜单的完整路径,当目标应用的菜单层级较深时,建议直接输入目标菜单项名称的片段(如export),模糊匹配会自动定位。
匹配规则与结果呈现
插件拿到菜单项后,通过 Wox 的模糊匹配引擎进行过滤(见 menus_darwin.go):
filteredMenus := lo.Filter(menuNames, func(menu string, _ int) bool { match, score := plugin.IsStringMatchScore(ctx, menu, query.Search) return (match && score > 20) || (!query.IsGlobalQuery() && query.Search == "") })两个细节值得注意:
- 分数阈值:只有匹配且得分大于 20 的菜单项才会出现在结果中,避免把无关菜单项当作搜索结果;
- 空搜索兜底:当不是全局查询(即通过
menus关键词进入)且搜索词为空时,会列出当前应用的全部菜单项,方便你浏览整棵菜单树。
每个结果项都带有当前活动窗口应用的图标(当活动窗口图标不可用时回退到内置的菜单图标,图标定义见 default_plugin.go),执行动作名为plugin_menus_execute,回车后触发对应菜单命令。
插件如何获得"当前应用":QueryEnv 特性声明
菜单插件必须知道当前哪个应用处于焦点,这依赖 Wox 的 QueryEnv 机制。插件在元数据中显式声明了两个环境变量需求(见 menus_darwin.go):
Features: []plugin.MetadataFeature{ { Name: plugin.MetadataFeatureQueryEnv, Params: map[string]any{ "requireActiveWindowPid": "true", "requireActiveWindowIcon": "true", }, }, },Wox 主程序在 metadata.go 中解析这些参数:requireActiveWindowPid要求查询环境附带当前活动窗口的进程 ID,requireActiveWindowIcon要求附带活动窗口图标。在查询回调中,插件据此拿到query.Env.ActiveWindowPid:
if query.Env.ActiveWindowPid == 0 { i.api.Log(ctx, plugin.LogLevelError, "Active window pid is not available") return plugin.QueryResponse{} }若拿不到 PID(例如 Wox 自身获得焦点、或当前窗口不可枚举),插件会直接返回空结果并记录错误日志。这也是为什么使用前必须保持目标应用为焦点窗口。
菜单是怎么读出来的:Accessibility API 调用链
从源码结构看,菜单的读取与执行横跨三层:
- 插件层menus_darwin.go:负责查询编排、结果过滤、缓存与动作分发;
- cgo 桥接层util/menus/menus_darwin.go:通过 cgo 声明对 Objective-C 函数的调用:
/* #cgo CFLAGS: -x objective-c #cgo LDFLAGS: -framework Foundation -framework Cocoa -framework AppKit char** getMenuItems(int pid, int* count); void performMenuAction(int pid, const char* title); */Go 侧提供GetAppMenuTitles(pid)读取菜单标题列表、ExecuteActiveAppMenu(pid, title)执行菜单动作,并负责C.CString、C.GoString与内存释放等跨语言转换。
- Objective-C 实现层util/menus/menus_darwin.m:核心是 macOS 的 Accessibility 框架,调用链如下:
AXUIElementCreateApplication(pid)依据进程 PID 创建应用的可访问性元素;AXUIElementCopyAttributeValue(app, kAXMenuBarAttribute, ...)获取应用菜单栏;- 递归遍历
kAXChildrenAttribute展开每一级菜单,同时读取kAXTitleAttribute组装父菜单->子菜单的完整路径,最终生成可搜索的标题列表。
其中getMenuItemTitles的递归逻辑决定了标题的拼接规则:叶子菜单项(无子项)输出parentTitle->title,根级菜单输出裸标题;当标题为空时只传递父级路径。此外实现中会跳过名为Apple的菜单(即系统菜单),避免把系统级菜单混入搜索结果。
执行菜单动作时,performMenuAction再次遍历菜单树,找到完整路径与目标一致的菜单项后调用:
AXUIElementPerformAction(menuItem, kAXPressAction);即向该菜单项发送"按下"动作,等效于用户真实点击该菜单。
从上述实现可以推断,该功能依赖 macOS 的 Accessibility 辅助功能接口,因此在系统设置中需要为 Wox 授予「辅助功能」权限;若读取失败,插件会记录错误日志(如Failed to get menu bar for app with pid ...)并返回空结果,这通常就是权限未授予或目标应用不暴露菜单栏的典型表现。
性能设计:一分钟菜单缓存
菜单栏解析是较为昂贵的操作(每次都要递归遍历整棵菜单树),因此插件引入了按 PID 隔离的缓存(见 menus_darwin.go):
var menusCacheTTL = time.Minute var menusCache = util.NewHashMap[int, menusCacheEntry]() type menusCacheEntry struct { titles []string expiresAt time.Time }查询时先查缓存,命中且未过期则直接使用;否则调用menus.GetAppMenuTitles重新解析并写入缓存,TTL 为 1 分钟。getMenusFromCache在过期时会主动删除旧条目,避免内存中堆积已退出应用的菜单数据。这意味着:菜单变更后最多一分钟内即可看到新菜单项,同时重复查询不会重复触发昂贵的 AX 遍历。
使用提示与常见问题排查
- 确认焦点:如果输入菜单名没有任何结果,先确认目标应用确实是当前焦点窗口(点击一下应用窗口再回到 Wox 输入)。
- 使用更完整的路径片段:层级深的菜单(如
文件->导出->导出为 PDF…)建议输入末级菜单名或中间片段,模糊匹配得分更高。 - 通过
menus关键词浏览:输入menus加空格后不输入其他内容,可以看到当前应用的全部菜单项列表。 - 权限问题:如果插件完全无输出且日志出现 AX 相关错误,请检查系统「隐私与安全性 → 辅助功能」中是否已为 Wox 开启权限,并重启 Wox 使其生效。
- 系统菜单排除:
Apple菜单被实现层主动跳过,这是设计行为,不属于缺陷。
小结
Wox 的 macOS 菜单插件以极小的交互成本,把"点菜单"变成了"打字搜索",其实现是 Wox 插件体系的一个典型范例:通过TriggerKeywords与MetadataFeatureQueryEnv声明触发方式和环境依赖,利用 cgo 桥接 Go 与 Objective-C,再借助 macOS Accessibility API 完成菜单树的读取与点击动作。如果你想深入阅读实现细节,推荐依次查看官方文档、插件主逻辑与Objective-C 实现三个文件。
- 桌面应用
- AI 应用
- 插件系统
【免费下载链接】Wox
A cross-platform launcher that simply works
相关推荐
如何使用NW.js创建原生体验应用菜单:从基础到高级的完整指南
如何使用NW.js创建原生体验应用菜单:从基础到高级的完整指南 NW.js(Node Webkit)是一个强大的框架,允许开发者使用Web技术(HTML、CSS
桌面应用跨平台Unity MCP 菜单项执行指南:用 execute_menu_item 工具让 AI 精确操控 Unity 编辑器菜单
Unity MCP 菜单项执行指南:用 execute_menu_item 工具让 AI 精确操控 Unity 编辑器菜单 导读 execute_menu_it
MCP 服务AI 应用游戏开发工具调用x64dbg GuiMenuClear 接口详解:清空菜单项的跨进程实现与插件菜单生命周期
x64dbg GuiMenuClear 接口详解:清空菜单项的跨进程实现与插件菜单生命周期 本文讲解 x64dbg GUI 插件接口中的 GuiMenuClea
逆向工程调试器开发工具应用安全
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考