窗口、菜单与托盘
自定义标题栏、窗口状态、原生菜单与系统托盘。
主进程掌管着桌面外壳——窗口本身、其持久化的边界、原生应用菜单以及可选的系统
托盘。本页将这些部件映射到 lib/main/,并指出各平台的差异。
窗口
lib/main/app.ts(createAppWindow)构建唯一的主 BrowserWindow。它带有
自定义标题栏,以及来自架构的安全默认值:
sandbox: true、contextIsolation: true、nodeIntegration: false、
webSecurity: true。
标题栏样式因平台而异:
titleBarStyle: 'hiddenInset' 搭配 trafficLightPosition: { x: 12, y: 12 }——
原生的红绿灯按钮浮在应用自绘的头部之上。在 dev 模式下会显式设置 dock 图标
(app.dock.setIcon),因为正在运行的二进制是 Electron 自身。
titleBarStyle: 'hidden' 外加一个 titleBarOverlay,其颜色与符号色根据启动主题
计算得出,高度固定为 39。头部其余部分与窗口控件由渲染进程绘制。
titleBarStyle: 'default'——标准的系统边框。autoHideMenuBar 让菜单栏在按下
Alt 前保持隐藏。
窗口以 show: false 创建,在 ready-to-show 时、缩放与最大化状态恢复之后才显示,
因此不会出现未加样式或尺寸错误的窗口闪现。出于同样的原因,backgroundColor 会
预先根据持久化的主题设置。
渲染进程通过 Conveyor 读取窗口元数据:WindowContextProvider
(app/components/window/)调用一次 useConveyor('window').windowInit(),暴露
宽/高、平台,以及可最小化/可最大化标志,使自定义头部能渲染出正确的控件。窗口
操作(windowMinimize、windowMaximize、windowClose、windowMaximizeToggle)
同样是 Conveyor 调用——参见 IPC。
窗口状态
lib/main/window-state.ts(WindowStateStore)将窗口边界与最大化标志持久化到
Electron userData 目录下的 window-state.json。启动时恢复它们;在
移动/缩放/关闭时写回。
它在恢复时很谨慎:
- 加载时对边界做 Zod 校验;文件损坏时回退到默认值。
isBoundsValid检查保存的矩形是否仍与某个已连接的显示器重叠,因此拔掉外接 显示器不会把窗口困在屏幕之外。- 强制一个最小尺寸(700×500),缺失或无效的矩形会在主显示器的工作区居中。
同一存储还保存着 lastOpenedPath,供文件演示的"打开最近文件"操作使用。
原生菜单
lib/main/menu.ts 用 Menu.buildFromTemplate 构建应用菜单。菜单文案来自
主进程的 i18n 层(t('common:menu.*')),并在语言切换时重建。
- macOS 的第一项是标准的应用菜单(关于/服务/隐藏/退出);其他平台以"文件" 开头。
- 视图(View)暴露缩放(实际大小 / 放大 / 缩小,接到主进程的缩放辅助函数)
与全屏。重新加载/开发者工具项仅在未打包(
dev)构建中出现。 - 帮助(Help)链接到日志、关于与主页(通过受审计的外部 URL 收口点打开), 外加一个在未提供更新操作时禁用的"检查更新"项。
模板(Template)菜单是受标志开关控制的演示内容:
// lib/main/menu.ts — [template-demo]
const templateMenu = showcaseEnabled
? { label: t('common:menu.template'), submenu: [/* 能力展示、文件演示 */] }
: undefinedshowcaseEnabled 来自 lib/main/feature-flags.ts 中的 mainFeatures.showcase。
请让它与渲染进程的 showcase 标志保持一致,使模板菜单恰好与 Showcase 界面同进同
退——参见能力展示。
系统托盘
lib/main/tray.ts(TrayService)是可选启用的:只有当用户开启最小化到
托盘(通用设置,minimizeToTray,默认关闭)时托盘才存在。每当该设置变化,
applySettings() 会创建或销毁托盘。
- 托盘图标会适配操作系统。macOS 使用一张单色模板图(并带
@2x表示),由系统 为菜单栏重新着色;Windows 与 Linux 则根据nativeTheme切换明/暗 PNG。 - 上下文菜单提供:打开主窗口、设置、检查/就绪更新(后台检查发现更新时文案会 切换)、关于与退出。它会在语言切换与更新状态变化时重建。
- 托盘导航派发类型化的 app intent(
appIntentService.dispatch)——与深度链接 使用的同一套经校验的路由机制。
关闭到托盘的行为有保护:在 Windows/Linux 上,当 minimizeToTray 开启且托盘存在
时,关闭窗口会隐藏窗口而非退出。在 macOS 上则遵循平台自身的隐藏/退出惯例,而真正
的退出(before-quit)总会关闭。
导航与外部 URL
两个小模块保障导航安全,且每一个对外链接都经由同一个受审计的收口点:
| 关注点 | 文件 | 行为 |
|---|---|---|
| 应用内导航 | navigation-guard.ts | will-navigate 仅在目标与已加载渲染进程同源(或 dev 下与开发服务器同源)时放行;其余一律拦截。 |
| 外部链接 | external-url.ts | openExternal 只接受 https: 与 mailto: URL,并通过 shell.openExternal 在系统浏览器中打开;其他协议会抛出。 |
| 新窗口 | app.ts | setWindowOpenHandler 拒绝每一次 window.open,转而把 URL 交给 openExternal。 |
综合效果是:渲染进程既无法自行导航到任意源,也无法生成一个不受管控的窗口。产品 链接会在用户的真实浏览器中打开。
缩放与权限
- 缩放(
zoom.ts)将 webContents 的缩放级别夹在设置范围内并持久化,因此视图 菜单、命令面板与恢复后的窗口都对齐到同一个值。 - 权限(
permissions.ts)安装了一个默认拒绝的权限处理器:ALLOWED_RENDERER_PERMISSIONS为空,因此在你显式添加之前,渲染进程会被拒绝 摄像头、麦克风、地理位置及其他所有浏览器权限。这与electron-builder.yml中的 macOSNS*UsageDescription字符串不同,后者是在打包 时声明的操作系统级提示——把你的应用用不到的那些删掉。
