Electron ShareMenu 详解:在 macOS 桌面应用中调用系统分享菜单(Share Menu)
Electron 的 ShareMenu 类让 macOS 应用能够直接唤起系统级的分享菜单(Share Menu),把文本、文件或 URL 从当前上下文分享到 App、社交账号和其他服务。本文基于 Electron 仓库中的 API 文档 docs/api/share-menu.md、配套的 SharingItem 结构 以及主进程 JS/C++ 源码,完整讲解其用法、参数默认值、底层实现链路,以及如何把它以菜单子项(shareMenu role)的方式嵌入应用菜单。
什么是 ShareMenu
ShareMenu 是一个主进程(Main process)类,用于在 macOS 上创建系统分享菜单。这个概念对应 Apple 的 Share Extensions(HIG 中的 "Share Menu")机制:用户点击分享后,由系统枚举本机可用的分享目标(信息、备忘录、AirDrop、各社交 App 等),开发者不需要自己实现任何分享渠道。
从 docs/api/share-menu.md 的 API history 注释看,该 API 由上游 PR electron/electron#25629 引入。文档同时给出两条使用路径:
- 独立类:
new ShareMenu(sharingItem),然后popup()以右键上下文菜单的形式弹出——适合"在页面某处点一下直接分享"的交互; - 菜单 role:作为其他菜单的子菜单时,改用
MenuItem的shareMenurole(见下文第五节)。
文档还沿用了 Electron 内置类的通用约束,值得原样保留:
警告:Electron 的内置类不能在用户代码中被继承(subclassed),详见 FAQ。
分享内容的载体:SharingItem 结构
ShareMenu 构造函数唯一参数是一个 SharingItem 对象,它描述"要分享什么"。按 docs/api/structures/sharing-item.md 的定义,它只有三个可选字段,且都是数组:
| 字段 | 类型 | 说明 |
|---|---|---|
texts |
string[] (optional) |
要分享的文本数组 |
filePaths |
string[] (optional) |
要分享的文件路径数组 |
urls |
string[] (optional) |
要分享的 URL 数组 |
三者可以任意组合,例如"一段文字 + 一个链接 + 两个图片文件":
const sharingItem = {
texts: ['这是我在 My Electron App 中选中的内容'],
urls: ['https://example.com/article/42'],
filePaths: ['/tmp/screenshot-1.png', '/tmp/screenshot-2.png']
};
这个结构不只是 ShareMenu 专用——它同样被 MenuItem 的 sharingItem 属性复用(当 role 为 shareMenu 时生效),两种入口共享同一份数据模型。
创建与弹出:new ShareMenu() 与 popup()
new ShareMenu(sharingItem)
const { ShareMenu } = require('electron');
const sharingItem = { texts: ['Hello from Electron'] };
const shareMenu = new ShareMenu(sharingItem);
从源码看,JS 层的 ShareMenu 是一个非常薄的包装,见 lib/browser/api/share-menu.ts:
class ShareMenu implements Electron.ShareMenu {
private menu: Menu;
constructor(sharingItem: SharingItem) {
this.menu = new (Menu as any)({ sharingItem });
}
popup(options?: PopupOptions) {
this.menu.popup(options);
}
closePopup(browserWindow?: BrowserWindow) {
this.menu.closePopup(browserWindow);
}
}
也就是说 ShareMenu 本质上是一个携带了 sharingItem 构造参数的 Menu,其弹出与关闭逻辑完全复用了 Menu 的既有实现。
shareMenu.popup([options])
把分享菜单作为上下文菜单弹出到 BrowserWindow 中。PopupOptions 的参数及默认值如下(完整继承自 docs/api/share-menu.md):
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
browserWindow |
BrowserWindow (optional) | 当前聚焦窗口 | 指定弹出所在的窗口 |
x |
number (optional) | 当前鼠标光标位置 | 若声明了 y 则必须同时声明 |
y |
number (optional) | 当前鼠标光标位置 | 若声明了 x 则必须同时声明 |
positioningItem |
number (optional, macOS) | -1 |
指定哪个菜单项索引被定位到鼠标光标下方 |
callback |
Function (optional) | — | 菜单关闭时被调用 |
这些默认值不是文档的"口头承诺",在 lib/browser/api/menu.ts 的 Menu.prototype.popup 实现中可以逐一验证:
x、y未传时默认置为-1,由原生层解释为"跟随鼠标光标"(第 114-115 行);positioningItem未传时默认为-1(第 116 行);browserWindow的解析有一条回退链:先校验传入窗口是否存在于BaseWindow.getAllWindows()中,不存在则取getFocusedWindow();仍没有则取窗口列表第一个;一个窗口都没有时直接抛出Cannot open Menu without a BaseWindow present(第 120-129 行)。
典型用法(例如在渲染进程右键后由主进程弹出):
// 在主进程中
const shareMenu = new ShareMenu({
urls: [currentArticleUrl],
texts: [currentTitle]
});
// 在指定坐标弹出
shareMenu.popup({
browserWindow: mainWindow,
x: 100,
y: 200,
callback: () => console.log('分享菜单已关闭')
});
// 或者:跟随鼠标位置弹出(不传 x/y)
shareMenu.popup();
注意 x/y 的成对约束:声明其中一个就必须同时声明另一个,否则坐标语义不完整,应按文档约定成对传入。
shareMenu.closePopup([browserWindow])
关闭在 browserWindow(默认聚焦窗口)中弹出的该分享菜单:
shareMenu.closePopup(mainWindow); // 或 shareMenu.closePopup() 关闭聚焦窗口中的菜单
底层实现见 lib/browser/api/menu.ts:传入的窗口必须是 BaseWindow 实例,此时按窗口 ID 精确关闭对应 runner;若不传(或不是 BaseWindow),则以 -1 作为参数,表示关闭属于该菜单的所有 runner。
源码实现链路:从 JS 选项到原生 NSMenu
从源码结构看,SharingItem 的流转路径横跨 JS 与 C++ 两层,且整条链路被 macOS 编译条件严格限定:
- JS 层:lib/browser/api/share-menu.ts 把
sharingItem塞进Menu的构造参数对象中; - C++ 构造解析:在 shell/browser/api/electron_api_menu.cc 中,
Menu::Menu的构造函数只在#if BUILDFLAG(IS_MAC)分支内读取options.Get("sharingItem", &item),然后调用model_->SetSharingItem(std::move(item))写入ElectronMenuModel。这从编译层面确认了 ShareMenu 仅支持 macOS——非 macOS 平台上传入的sharingItem会被静默忽略; - 菜单模型回传 JS:lib/browser/api/menu.ts 中,
Menu.prototype._getSharingItemForCommandId这一回调方法同样被process.platform === 'darwin'包裹,即原生菜单在构建某个带分享能力的项时,会通过 commandId 反查 JS 侧缓存的sharingItem; - 原生 UI 构建:最终消费发生在 shell/browser/ui/cocoa/electron_menu_controller.mm(Cocoa 平台实现)。其中
ConvertSharingItemToNS负责把SharingItem转换为NSObjects数组(对应系统分享框架所需的NSItemProvider数据源),createShareMenuForItem:再用该数组创建真正的原生分享菜单。
这条链路解释了前文的两个行为特征:为什么 ShareMenu 只是 Menu 的薄包装,以及为什么平台差异被固化在编译分支而非运行时判断中。
另一种用法:作为菜单子项(shareMenu role)
docs/api/share-menu.md 明确指出:想把分享菜单作为其他菜单的子菜单时,不应使用 ShareMenu 类,而应使用 MenuItem 的 shareMenu role。
在 docs/api/menu-item.md 中,role 的可选值包含 shareMenu,并配套一个专用属性:
sharingItemSharingItem (optional) macOS —— "当role为shareMenu时要分享的内容"(见 docs/api/menu-item.md 第 48、183 行的字段定义);- docs/tutorial/menus.md 的 role 列表中同样说明:
shareMenu对应的子菜单就是 share menu,且必须同时设置sharingItem属性指明分享对象。
示例——把分享项挂到"文件"菜单下:
const { Menu } = require('electron');
const template = [
{
label: '文件',
submenu: [
{
label: '分享',
role: 'shareMenu',
sharingItem: {
texts: ['来自 Electron 的分享内容'],
urls: ['https://example.com']
}
},
{ type: 'separator' },
{ role: 'quit' }
]
}
];
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
两种用法的选型原则:需要跟随鼠标弹出、交互即开即关的场景用 ShareMenu 类 + popup();需要常驻在应用菜单/右键菜单结构中时,用 shareMenu role 声明为子菜单。
使用注意事项
- 平台限制:
ShareMenu与sharingItem均为 macOS 专属能力。C++ 侧的解析代码位于BUILDFLAG(IS_MAC)编译分支(shell/browser/api/electron_api_menu.cc),在 Windows/Linux 上该 API 不会产生系统分享菜单,跨平台应用应自行做process.platform === 'darwin'判断并提供降级交互; - 不可继承:与所有 Electron 内置类一样,
ShareMenu不能被用户代码extends(见 docs/faq.md 的类继承说明); - 坐标成对约束:
popup()的x与y必须同时声明或同时省略,省略时回落到鼠标光标位置(lib/browser/api/menu.ts 中默认值-1即此语义); - 回调时机:
popup()的callback在菜单关闭时触发(而非弹出时),适合用于清理状态或恢复焦点; closePopup()的作用域:不传窗口参数时会关闭该菜单实例对应的所有 runner(lib/browser/api/menu.ts),多窗口同时弹出同一ShareMenu时需注意这一点;positioningItem用于多行菜单定位:指定菜单打开后哪一行(索引)停在鼠标下方,默认-1表示不做特殊定位。
参考文件
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 StartedRust0624
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