Electron TouchBarOtherItemsProxy 完全指南:定位 Chromium 继承控件的“其他项代理”
Electron(macOS Touch Bar API)中的
TouchBarOtherItemsProxy是一个特殊占位控件:它并不渲染任何自定义内容,而是为 Chromium 通过系统机制注入 Touch Bar 的“继承自浏览器本身”的触控栏元素(如输入框联想、播放控制等原生 NSTouchBarItem)指定一个存放位置。本文以官方 API 文档为骨架,结合 lib/browser/api/touch-bar.ts、shell/browser/ui/cocoa/electron_touch_bar.mm 与 spec/api-touch-bar-spec.ts 中的实现与测试,讲清它的创建方式、单实例约束、默认行为以及如何在自定义 Touch Bar 布局中精确安放这一占位符。
TouchBarOtherItemsProxy 是什么
当应用为 BrowserWindow 设置自定义 Touch Bar 时,Touch Bar 上实际显示的并不只有开发者通过 TouchBar 显式声明的那些按钮、标签和滑杆。Chromium / AppKit 系统中的某些交互(例如文本输入框的联想候选条、媒体相关的原生控件)会以系统托管的 NSTouchBarItem 形式出现在响应链上。这些由 Chromium 继承而来、非 Electron API 显式创建的条目,需要通过一个特殊占位符——Other Items Proxy——才能决定其落位。
TouchBarOtherItemsProxy 类实例化的正是这样一个“其他项代理”。在 AppKit 层面,它对应系统常量 NSTouchBarItemIdentifierOtherItemsProxy,即 Apple 官方文档中所说的 “Other Items Proxy” 标识符:凡是系统想往当前 Touch Bar 里塞的额外条目,都会统一被摆放到该代理所占据的位置。
与核心 TouchBar 体系的关系
TouchBarOtherItemsProxy 是 Electron Touch Bar 控件体系的一员。在 TouchBar 类静态属性 中,它与 TouchBarButton、TouchBarLabel、TouchBarColorPicker、TouchBarGroup、TouchBarPopover、TouchBarSlider、TouchBarSpacer、TouchBarSegmentedControl、TouchBarScrubber 并列,共同通过 TouchBar 上的静态引用对外暴露。
关键属性一览
| 项目 | 说明 |
|---|---|
| 类名 | TouchBarOtherItemsProxy |
| 构造签名 | new TouchBarOtherItemsProxy()(无任何 options 参数) |
| 运行进程 | Main(主进程) |
| 作用 | 为 Chromium / AppKit 继承的 Touch Bar 条目指定显示位置 |
| 单例约束 | 每个 TouchBar 只能添加一个实例 |
| 默认位置 | 每个 TouchBar 的条目列表末尾(未显式添加时自动补入) |
从 'electron' 模块获取
该代理类不是 require('electron') 的顶层导出。正确的取用方式是解构 TouchBar 的静态属性:
const { TouchBar, TouchBarButton } = require('electron')
const { TouchBarOtherItemsProxy } = TouchBar
之所以走这条路,是因为在 lib/browser/api/touch-bar.ts 的源码实现中,TouchBar 类以静态字段的方式挂载了全部子类:
static TouchBarButton = TouchBarButton;
static TouchBarSpacer = TouchBarSpacer;
static TouchBarSegmentedControl = TouchBarSegmentedControl;
static TouchBarScrubber = TouchBarScrubber;
static TouchBarOtherItemsProxy = TouchBarOtherItemsProxy;
从 TS 源码可以确认,TouchBarOtherItemsProxy 本身是一个最小化的空壳子类,它继承自抽象基类 TouchBarItem,不携带任何配置项与交互回调(touch-bar.ts):
class TouchBarOtherItemsProxy extends TouchBarItem<null> implements Electron.TouchBarOtherItemsProxy {
@ImmutableProperty(() => 'other_items_proxy') type!: string;
onInteraction = null;
}
它的 type 固定为字符串 'other_items_proxy',该标识贯穿 JS 层与原生层,用于识别代理条目。
构造与基本用法
new TouchBarOtherItemsProxy() 不接受参数,直接实例化后放进 TouchBar 的 items 数组即可:
const { app, BrowserWindow, TouchBar } = require('electron')
const { TouchBarButton, TouchBarLabel, TouchBarOtherItemsProxy } = TouchBar
const touchBar = new TouchBar({
items: [
new TouchBarLabel({ label: '我的自定义按钮区' }),
new TouchBarButton({ label: '确定' }),
// 告诉系统:Chromium 继承来的控件显示在这个占位处
new TouchBarOtherItemsProxy()
]
})
let window
app.whenReady().then(() => {
window = new BrowserWindow({ width: 600, height: 400 })
window.setTouchBar(touchBar)
})
将代理放在 items 的哪个下标位置,Chromium 继承的条目就会渲染在哪个位置。例如把它放在按钮之前,那么系统注入的控件会出现在 Touch Bar 的左侧;放在中间,则被自定义按钮夹在中央。
核心约束:每个 TouchBar 只能有一个代理
[!NOTE] 每个 TouchBar 只能添加一个
TouchBarOtherItemsProxy实例。
这条约束既有 JS 层校验,也有对应单元测试覆盖。在 touch-bar.ts 中,TouchBar 构造函数会遍历所有条目并对 type === 'other_items_proxy' 计数:
let hasOtherItemsProxy = false;
const idSet = new Set();
for (const item of items) {
// 类型合法性校验
if (!(item instanceof TouchBarItem)) {
throw new TypeError('Each item must be an instance of TouchBarItem');
}
if (item.type === 'other_items_proxy') {
if (!hasOtherItemsProxy) {
hasOtherItemsProxy = true;
} else {
throw new Error('Must only have one OtherItemsProxy per TouchBar');
}
}
// 同一实例不可重复加入
if (!idSet.has(item.id)) {
idSet.add(item.id);
} else {
throw new Error('Cannot add a single instance of TouchBarItem multiple times in a TouchBar');
}
}
也就是说,往同一个 TouchBar 的 items 里放两个代理会直接抛出运行时错误。对应测试位于 spec/api-touch-bar-spec.ts:
it('throws an error if multiple OtherItemProxy items are added', () => {
expect(() => {
const touchBar = new TouchBar({ items: [new TouchBarOtherItemsProxy(), new TouchBarOtherItemsProxy()] });
touchBar.toString();
}).to.throw('Must only have one OtherItemsProxy per TouchBar');
});
提示:同一 TouchBarItem 实例重复加入
items也会抛错(测试见 spec/api-touch-bar-spec.ts),因此即使每个 TouchBar 只需要一个代理,也应各自new一个实例。
默认行为:自动追加到条目末尾
即使开发者完全不创建代理,Electron 也会默认在每条 TouchBar 的末尾追加一个 Other Items Proxy。这条规则写在原生实现 electron_touch_bar.mm 中——identifiersFromSettings 遍历开发者提供的条目字典,将 type == "other_items_proxy" 的条目翻译成系统常量:
} else if (type == "other_items_proxy") {
identifier = NSTouchBarItemIdentifierOtherItemsProxy;
has_other_items_proxy = true;
}
遍历结束后,若发现没有任何代理,则自动在尾部补上系统代理标识符:
if (!has_other_items_proxy)
[identifiers addObject:NSTouchBarItemIdentifierOtherItemsProxy];
因此有两种等效结果:
- 不写代理:Chromium 继承条目被追加到 Touch Bar 最右侧;
- 显式写代理:继承条目出现在你所指定的位置,而自定义条目按顺序排在其两侧。
「默认在末尾」与「可被显式代理改写位置」这两个行为共同解释了为什么代理适合被当作布局中的“系统区锚点”。
源码层面的实现细节
识别符映射与分层 Touch Bar
每个 TouchBar 最终在原生层被构造成一个 NSTouchBar,其 defaultItemIdentifiers 即为上述映射后的标识符数组(electron_touch_bar.mm)。由于代理条目并非可交互的自定义控件,makeItemForIdentifier: 中没有任何分支会为 NSTouchBarItemIdentifierOtherItemsProxy 构造真实 item——它只作为位置标记由 AppKit 识别并填入系统条目。
值得注意的是分层(Group/Popover)场景下代理的处理存在差异:
- makeGroupForID: 在手工组装
NSGroupTouchBarItem时显式跳过NSTouchBarItemIdentifierOtherItemsProxy标识符,避免将其作为普通 item 生成; - 而 updateGroup: 仍经由
identifiersFromSettings重建组内 Touch Bar,因此代理的“自动末尾补齐”逻辑在组内同样生效。
从源码结构可以推断:组内与顶层 Touch Bar 各自独立决定是否追加代理,这条约束是逐 TouchBar 实例生效的。
与“继承元素”的关系再强调
Apple 的 NSTouchBarItemIdentifierOtherItemsProxy 之所以命名带 “Other Items”,是指那些不属于当前 Touch Bar defaultItemIdentifiers 显式声明、却由 AppKit/Chromium 视上下文自动提供(例如系统级功能)的条目。Electron 将这套机制原样桥接:代理即“Chromium 条目与自定义条目同框排版”的唯一衔接点。
完整可运行示例
将下面的代码保存为 touchbar-proxy.js(macOS + Touch Bar 环境,含模拟器)并运行:
const { app, BrowserWindow, TouchBar } = require('electron')
// 注意:代理并非顶层导出,必须从 TouchBar 静态属性解构
const { TouchBarButton, TouchBarLabel, TouchBarOtherItemsProxy } = TouchBar
app.whenReady().then(() => {
const window = new BrowserWindow({ width: 500, height: 300 })
// 显式把“其他项代理”夹在两组自定义按钮中间,
// 让 Chromium 注入的控件呈现在 Touch Bar 中部
const touchBar = new TouchBar({
items: [
new TouchBarLabel({ label: '左侧' }),
new TouchBarButton({ label: '上一页' }),
new TouchBarOtherItemsProxy(),
new TouchBarLabel({ label: '右侧' }),
new TouchBarButton({ label: '下一页' })
]
})
window.setTouchBar(touchBar)
})
运行方式与 Touch Bar 的其他示例一致(详见 touch-bar.md 中的示例章节):
- 将文件保存到本机;
- 安装 Electron:
npm install electron; - 执行:
./node_modules/.bin/electron touchbar-proxy.js。
如果改用无代理版本(仅保留前后两组自定义按钮),Chromium 继承条目会被自动排到整条 Touch Bar 的最右端——两者对比即可直观感受到显式代理对“继承区位置”的控制力。
参考文档
- 本类完整 API 定义:docs/api/touch-bar-other-items-proxy.md
- TouchBar 容器类、静态属性与老虎机示例:docs/api/touch-bar.md
- JS 层实现(类型映射、单实例校验、静态导出):lib/browser/api/touch-bar.ts
- 原生层实现(标识符映射、自动末尾补齐、分组跳过逻辑):shell/browser/ui/cocoa/electron_touch_bar.mm
- 行为验证测试:spec/api-touch-bar-spec.ts
- 其他 Touch Bar 控件:
TouchBarButton(touch-bar-button.md)、TouchBarLabel(touch-bar-label.md)、TouchBarGroup(touch-bar-group.md)等
提醒:与整个 Touch Bar API 一致,该能力属于 macOS 专有,且 Touch Bar API 目前仍标注为实验性,可能在未来的 Electron 版本中调整或移除(参见 touch-bar.md 的说明),设计新界面时应预留在回归测试上的余量。
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 StartedRust0627
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