LobeHub Desktop 全屏 Overlay 截图方案:Electron 窗口高亮、点击截窗与区域截图设计与实践
导读
本文基于 LobeHub 仓库中沉淀的 WindowOverlayCapture.md 技术预研文档展开。该文档记录了 LobeHub Desktop(Electron 桌面端)如何实现"类系统级截图"能力:用一个覆盖整块屏幕(含 macOS 菜单栏与 Dock)的透明 overlay 窗口,实时高亮系统窗口边框、点击窗口即截图、拖拽区域截图并写入剪贴板。阅读完本文,你将掌握其选型结论(node-screenshots + get-windows + Electron 分工)、全屏 overlay 的窗口参数与层级配置、窗口枚举与过滤策略、两条截图路径的取舍,以及从技术预研到生产代码落点的完整接线蓝图。
1. 需求与目标:为什么需要"覆盖菜单栏的截图 overlay"
LobeHub 需要一个"全屏截图"入口,其本质是一个覆盖整块屏幕的透明 overlay 窗口,并且需求清单中有几项对技术选型影响极大:
| 需求项 | 结论 |
|---|---|
| 新增一个"全屏"入口 | 需要,但本质上是一个覆盖整块屏幕的透明 overlay 窗口 |
| 覆盖用户整个 screen | 需要,且在 macOS 上要覆盖菜单栏与 Dock 所在区域 |
| 获取系统窗口几何信息 | 需要,至少需要 appName + bounds + windowId |
| 在 overlay 上高亮窗口边框并显示 Tag | 需要 |
| 点击高亮窗口即截图该窗口 | 需要 |
| 拖拽任意区域截图 | 需要 |
| 输出先写入剪贴板 | 需要,作为 MVP |
| 避免自研 native addon | 明确要求避免 |
| 跨平台预留 | 需要,至少不能被 macOS-only 自研方案锁死 |
该文档的定位是技术预研的固化基线:记录从纯 Electron、自研 native、开源库到最终 demo 的决策过程,明确哪些能力 Electron 可做、哪些必须借助额外库,并为后续接入 apps/desktop 主业务的开发者提供模块边界、IPC 设计与 UI 接入蓝图,从而降低重复调研成本。
1.1 一个关键术语澄清:"压住 macOS 菜单栏与 Dock"
这里的含义不是调用系统 fullscreen API,而有三层准确含义:
| 项目 | 含义 |
|---|---|
| 覆盖范围 | 窗口尺寸必须基于 display.bounds,而不是 display.workArea |
| Z 轴层级 | 窗口需要位于普通应用窗口之上,并且进入菜单栏所在区域 |
| 视觉效果 | 用户看到的是整块屏幕都被半透明遮罩覆盖 |
同时必须区分两个易混概念:
| 易混概念 | 实际含义 |
|---|---|
app.dock.hide() |
仅隐藏应用在 Dock 中的图标,不会隐藏系统 Dock 栏本身 |
BrowserWindow.setFullScreen(true) |
更接近原生全屏行为,未必适合作为截图 overlay |
2. 方案对比与最终选型
2.1 预研结论总览
| 方案 | 覆盖菜单栏 / Dock | 拿到系统窗口 bounds | 按窗口截图 | 跨平台性 | 结论 |
|---|---|---|---|---|---|
纯 Electron desktopCapturer |
是 | 否 | 部分可做,但不精确 | 高 | 不足以满足需求 |
| 自研 native addon | 是 | 是 | 是 | 中 | 能做,但被明确拒绝 |
| 参考 Claude.app 的 native quick entry | 是 | 是 | 是 | 低到中 | 可借鉴思路,不适合直接照搬 |
node-screenshots 单库 |
是 | 是 | 是 | 中到高 | 核心方案成立 |
node-screenshots + get-windows |
是 | 是 | 是 | 中到高 | 当前最终方案 |
2.2 最终选型分工
| 能力 | 最终实现 |
|---|---|
| 全屏 overlay 窗口 | Electron BrowserWindow |
| 系统窗口枚举 | node-screenshots |
| 指定窗口截图 | node-screenshots |
| 隐藏 / 伪关闭窗口过滤 | get-windows 作为白名单 |
| 区域截图 | Electron desktopCapturer |
| 输出介质 | clipboard.writeImage() |
这里需要特别强调的是选型的两个约束前提:
- 这不是"纯 Electron"方案(纯 Electron 拿不到稳定的窗口 bounds);
- 这也不是"自研 native addon"方案(产品约束明确拒绝自研原生扩展);
- 当前依赖的是开源原生库(
node-screenshots与get-windows),这使跨平台性有了保障。
2.3 对 Claude.app 的观察结论(参考依据)
文档记录了曾直接检查本机解包后的 Claude.app 产物得到的结论:
| 观察对象 | 结论 |
|---|---|
quick_window |
不是全屏 overlay;它是小尺寸 panel 弹窗 |
nativeQuickEntry |
Claude.app 存在原生 quick entry 能力,说明其真实覆盖式入口并不完全依赖纯 Electron |
cu-glow |
这是最接近本需求的 Electron overlay 实现:使用 display.bounds、透明窗、screen-saver 置顶层级 |
由此得出两个重要判断:Electron 可以做"整屏遮罩"(成立),以及 Claude 的"整屏入口"并不等于 quick_window(成立)。
3. 全屏 overlay 的实现参数与层级
3.1 必要窗口参数
| 参数 / 调用 | 用途 | 必要性 |
|---|---|---|
x/y/width/height = display.bounds |
覆盖整块屏幕,包括菜单栏区域 | 必需 |
transparent: true |
允许渲染半透明遮罩 | 必需 |
frame: false |
去除系统边框 | 必需 |
skipTaskbar: true |
避免出现在任务栏 / Dock 窗口列表中 | 建议 |
hasShadow: false |
避免覆盖层产生自身投影 | 建议 |
focusable: true |
允许接收鼠标交互 | 必需 |
fullscreenable: false |
避免进入原生 fullscreen 流程 | 建议 |
enableLargerThanScreen: true |
提升跨平台稳健性 | 建议 |
type: 'panel'(macOS) |
更接近工具层窗口行为 | 建议 |
这些参数已经在生产代码中得到逐一印证:在 ScreenCaptureManager.ts 的 createOverlayWindow 中,创建 overlay 窗口时设置了 transparent: true、frame: false、hasShadow: false、focusable: true、fullscreenable: false、enableLargerThanScreen: true、resizable: false、skipTaskbar: true,并在 macOS 上追加 type: 'panel';窗口尺寸直接取 bounds.x / y / width / height,即 Electron screen 模块返回的物理 bounds(而非 workArea)。
3.2 必要层级调用
| 调用 | 作用 |
|---|---|
setAlwaysOnTop(true, 'screen-saver') |
让窗口位于更高层级 |
setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true }) |
避免 Space / 全屏窗口场景下不可见 |
setHiddenInMissionControl(true) |
降低该窗口对系统窗口管理的干扰 |
3.3 重要结论
| 结论 | 说明 |
|---|---|
display.workArea 不可用 |
它会排除菜单栏 / Dock 区域 |
display.bounds 必须使用 |
只有它能覆盖整个 display |
screen-saver 层级有效 |
这是当前 macOS 上最接近需求的 Electron 方案 |
在生产实现中同样调用了 win.setAlwaysOnTop(true, 'screen-saver') 与 win.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true, ...(isMac ? { skipTransformProcessType: true } : {}) }),macOS 下再追加 win.setHiddenInMissionControl(true),与文档结论一致。
4. 系统窗口枚举与过滤策略
4.1 为什么不能只用 Electron
| Electron 能力 | 缺口 |
|---|---|
desktopCapturer.getSources({ types: ['window'] }) |
能列出可捕获源,但没有稳定的窗口 bounds 用于 overlay 画框 |
DesktopCapturerSource.thumbnail |
可截图缩略图,但不适合"按原窗口精确高亮 + 点击即截" |
因此纯 Electron 不足以完成"系统窗口高亮 + 点击截窗"。
4.2 node-screenshots 的职责
| API | 用途 |
|---|---|
Window.all() |
枚举系统窗口 |
window.id() |
稳定识别窗口 |
window.appName() |
获取应用名 |
window.title() |
获取标题 |
window.x()/y()/width()/height() |
获取几何信息 |
window.captureImage() |
截取该窗口图像 |
4.3 get-windows 的职责
get-windows 在当前方案中不负责截图,只负责"第二层白名单过滤":
| 问题 | 处理方式 |
|---|---|
| 某些应用逻辑上已隐藏,但底层枚举仍可能残留 | 只保留同时出现在 get-windows 与 node-screenshots 中的窗口 |
| Electron 自身的假关闭 / hide 行为 | 该白名单对这类情况更稳 |
4.4 当前过滤规则
| 规则 | 目的 |
|---|---|
isMinimized() === false |
排除最小化窗口 |
最小尺寸阈值:80x60 |
排除菜单栏控件、过小悬浮面板 |
排除 Dock / Window Server / Control Centre |
排除系统 UI |
| 排除 demo 自身窗口 | 避免 overlay 自我高亮 |
| 必须与目标 display 相交 | 只画当前屏幕可见窗口 |
必须出现在 get-windows 白名单中 |
排除隐藏 / 伪关闭残留窗口 |
这些规则在生产代码 WindowSourceService.ts 的 enumerateWindows 中有完整对应的实现:
- 常量
MIN_WIDTH = 80、MIN_HEIGHT = 60即文档中的尺寸阈值; SYSTEM_APP_BLACKLIST集合中包含Dock、Window Server、Control Centre、SystemUIServer、Notification Centre等系统应用名;- 通过
app.getName()得到自身应用名并跳过同名窗口(避免 overlay 自我高亮); - 通过
win.isMinimized()排除最小化窗口; intersects(normalizedBounds, displayBounds)判断窗口是否与目标 display 相交;- 白名单使用
get-windows的openWindowsSync/openWindowsForCapture枚举,对每个窗口的owner.processId建集合并做 PID 级交集判断。
值得补充的一个跨平台细节:WindowSourceService 中还有针对 Windows 的 normalizeWindowBounds,当进程平台为 win32 且 scaleFactor > 1 时,会把 node-screenshots 返回的物理像素坐标除以 scaleFactor 归一到 DIP 坐标;同时其注释说明,在打包后的 macOS 应用中为了避免加载 get-windows 的 Windows 安装依赖链,会通过 execFileSync 直接调用 app.asar.unpacked 下解包出的可执行二进制(--no-accessibility-permission / --no-screen-recording-permission / --open-windows-list),JSON 解析后取得可见窗口 PID 列表。
5. 两条截图路径的设计
5.1 点击窗口截图
点击高亮框
└───> renderer 发送 windowId
└───> main 查找对应 node-screenshots Window
└───> overlay.hide()
└───> captureImage()
└───> PNG Buffer
└───> nativeImage
└───> clipboard.writeImage()
5.2 拖拽区域截图
拖拽区域
└───> renderer 发送全局 rect
└───> main 隐藏 overlay
└───> desktopCapturer 获取目标 display 图像
└───> 按 scaleFactor 计算 cropRect
└───> clipboard.writeImage()
5.3 为什么两条路径采用不同技术
| 路径 | 技术 | 原因 |
|---|---|---|
| 按窗口截图 | node-screenshots |
它天然理解"窗口"这一对象 |
| 按区域截图 | desktopCapturer |
区域本质上是 display 上的矩形裁剪 |
值得注意的是,生产实现 CaptureService.ts 对两条路径做了收敛与增强:
- 窗口截图
captureWindow(windowId):调用findWindowById在Window.all()中定位窗口,再win.captureImage()得到图像,最后toPng()转为 PNG Buffer。 - 区域截图
captureRect(absoluteRect, scaleFactor, displayBounds):先把 overlay 局部坐标由主进程换算为绝对 DIP 坐标,再按scaleFactor换算为物理像素,并据displayBounds在Monitor.all()中做最小误差匹配(findMonitorByDisplayBounds)定位显示器,随后monitor.captureImage()(带 2 次重试、间隔 120ms)并调用image.crop()裁剪出目标矩形。 - 实际生产中区域截图同样走
node-screenshots的Monitor.captureImage() + crop路径,而不再单独依赖 ElectrondesktopCapturer;这比 demo 阶段进一步统一了底层依赖。
6. 权限与平台边界
6.1 macOS 权限
| 权限 | 是否需要 | 用途 |
|---|---|---|
| Screen Recording | 需要 | 窗口截图、区域截图 |
| Accessibility | 当前方案不强依赖 | get-windows 已使用 accessibilityPermission: false |
主进程侧还提供了 prewarmPermissionCheck()(提前预热权限查询,因为 macOS 的 TCC XPC 权限查询可能耗时数秒且授权在进程存活期内稳定、可安全缓存)与 ensureScreenCaptureAccess()(弹出授权引导对话框,点击后调用 requestScreenCaptureAccess() 打开系统设置),这些逻辑位于 ScreenCaptureManager.ts,权限工具来自 @/utils/permissions。
6.2 当前已知平台边界
| 平台 / 场景 | 状态 | 说明 |
|---|---|---|
| macOS | 已验证 | 当前主要验证平台 |
| Windows | 理论可行 | node-screenshots / get-windows 均支持,但尚未在本仓库内做实机验证 |
| Linux X11 | 理论可行 | 需要单独验证打包与权限 |
| Linux Wayland | 风险较高 | 上游库虽宣称支持,但必须做专项验证 |
6.3 特殊窗口风险
| 风险类型 | 当前处理 |
|---|---|
| 菜单栏状态窗 / 面板 | 通过尺寸阈值与排除名单降低噪音 |
| 系统 UI | 通过应用名黑名单排除 |
| 某些应用截图结果为黑图 | 已观察到个别状态面板存在此现象,应在业务层继续限制候选窗口类别 |
7. overlay 会话生命周期与主进程设计
demo 阶段的架构图非常清晰地划分了三层职责:
┌──────────────────────────────┐
│ Tray / Menu / Future Action │
└──────────────┬───────────────┘
│ startOverlaySession
▼
┌────────────────────────────────────────────┐
│ Main Process │
│ │
│ 1. 选定当前光标所在 display │
│ 2. 枚举窗口:node-screenshots │
│ 3. 过滤隐藏窗口:get-windows 白名单 │
│ 4. 创建整屏 overlay BrowserWindow │
└──────────────┬─────────────────────────────┘
│ preload / IPC
▼
┌────────────────────────────────────────────┐
│ Overlay Renderer │
│ │
│ 1. 渲染窗口高亮框与左上角 tag │
│ 2. 点击窗口 => captureWindow(windowId) │
│ 3. 拖拽区域 => captureRect(rect) │
└──────────────┬─────────────────────────────┘
│ IPC
▼
┌────────────────────────────────────────────┐
│ Main Process Capture Path │
│ │
│ Window: node-screenshots.captureImage() │
│ Region: desktopCapturer + crop │
│ Output: clipboard.writeImage() │
└────────────────────────────────────────────┘
在生产实现中,一次 overlay 会话(session)的完整流程可以在 ScreenCaptureManager.ts 的 startSession 中看到:
ensureScreenCaptureAccess()校验 / 引导 macOS 屏幕录制权限;- 通过
screen.getCursorScreenPoint()取得当前光标位置,再screen.getDisplayNearestPoint(cursor)定位目标 display,得到其bounds与scaleFactor(这正是"当前活动屏幕启动 overlay / 只覆盖当前目标 display"的实现); - 调用
enumerateWindows(bounds, scaleFactor)获取窗口列表,组装出ScreenCaptureSession(含 displayBounds、scaleFactor、windows 列表,以及主 renderer 通过publishOverlaySnapshot推来的 agent / model 快照与主题); createOverlayWindow(bounds)创建透明整屏窗口并加载/overlay路由,did-finish-load后把 session 通过screenCaptureSession事件推给 overlay。
有两个实现细节非常值得一提:
- 截图前隐藏 overlay 的方式:
withOverlayHidden不是hide/show,而是把窗口setOpacity(0)、等待 40ms(HIDE_SETTLE_MS),执行截图后再恢复setOpacity(1)。注释说明这样能避免 hide/show 引起的焦点与 z-order 抖动,同时保证截图管线拿到干净的底层像素。 - 捕获结果先于 UI 上传:
handlePreviewWindow/handlePreviewRect会立刻为 PNG Buffer 分配captureId与文件名,通过dispatchUpload以ArrayBuffer(结构化克隆,规避约 33% 的 base64 开销)把字节推给主 renderer,由其走 TRPC + hash 去重 + 对象存储上传管线,并通过overlayCaptureUploadStatus事件把uploading / ready / failed状态实时回传 overlay 以驱动发送按钮。提交时(handleSubmit)主进程关闭 overlay、唤起主窗口,并通过overlayDispatchMessage广播,renderer 侧依据captureId → fileId映射把已上传文件作为附件发送进会话。
7.1 IPC 控制器
主进程通过 ScreenCaptureCtr.ts 暴露给 renderer 的 IPC 组为 screenCapture,主要方法包括:
| 方法 | 作用 |
|---|---|
previewWindow(windowId) |
点击窗口后的"预览 + 上传" |
previewRect(params) |
拖拽区域后的"预览 + 上传" |
submit(params) |
提交(携带 prompt / agent / model / captureIds) |
close() |
关闭 overlay 会话 |
reportUploadStatus(payload) |
回传上传状态 |
publishOverlaySnapshot(payload) |
主 renderer 推送 agent / model / 主题快照 |
traceOverlayEvent(payload) |
overlay 事件追踪 |
该 controller 通过 ControllerModule 与 IpcMethod 装饰器注册进 controller 体系(见 registry.ts),并被组装为 app.screenCaptureManager 供主进程内部调用。
8. IPC 类型设计
生产代码中与 overlay 相关的类型被集中定义在 packages/electron-client-ipc/src/types/screenCapture.ts:
| 类型名 | 用途 |
|---|---|
ScreenCaptureDisplayInfo |
display id / bounds / scaleFactor(生产实现中直接并入 ScreenCaptureSession 的 displayBounds + scaleFactor) |
ScreenCaptureWindowInfo |
windowId / appName / title / bounds / overlayBounds / order |
ScreenCaptureSession |
display + windows,含 scaleFactor、agents / models / theme 快照 |
CaptureRectParams |
全局屏幕坐标的矩形(overlay 局部 DIP 坐标) |
ScreenCaptureStartResult |
权限状态、会话状态、错误信息 |
ScreenCaptureOutput |
clipboard、后续可扩展 file、attachment |
文档对类型语义做了精确定义,例如:
ScreenCaptureWindowInfo同时包含全局bounds与相对目标 display 原点的overlayBounds(后者用于 overlay 内直接以 DIP 坐标画高亮框);CaptureRectParams注释明确其为 overlay 局部 DIP 坐标,换算到绝对坐标是主进程(ScreenCaptureManager.handlePreviewRect)的职责;CapturePreviewResult中的captureId用于在 overlay 与主 renderer 之间跟踪上传状态与提交引用;ScreenCaptureSubmitParams.captureIds是"提交前已预上传"的捕获 ID 列表,renderer 据此从captureId → UploadFileItem映射解析文件并直接sendMessage,无需二次上传;OverlayCaptureUploadStatus取值为uploading / ready / failed;OverlayUploadRequestPayload以ArrayBuffer传递原始字节(mimeType: 'image/png')。
此外 ScreenCaptureSession 还预留了 agent / model 选择器的数据通道:ScreenCaptureAgentOption 与 ScreenCaptureModelOption 由 renderer 数据层(TRPC store)填充,主进程只做缓存与转发、不去主动拉取(源码注释明确 "Populated by the renderer data layer (TRPC), not the IPC service")。
9. 业务接入建议与代码落点
9.1 总体建议:不直接复用 BrowserManager
| 维度 | 建议 |
|---|---|
| overlay 窗口生命周期 | 不建议直接挂进现有 BrowserManager 的常规窗口体系 |
| 原因 | overlay 是瞬态、全屏、平台特化、不可持久化的工具窗口,与主业务窗口生命周期明显不同 |
| 推荐做法 | 新增独立主进程模块管理 overlay;渲染内容仍建议走现有 SPA 路由体系 |
不直接复用 BrowserManager 的理由是结构性的:
| 观察 | 影响 |
|---|---|
Browser 默认承担普通业务窗口职责 |
overlay 并非普通业务窗口 |
WindowStateManager 倾向保存窗口状态 |
overlay 不应持久化位置与大小 |
BrowserManager 以"可复用业务窗口"建模 |
overlay 更接近"一次性工具会话" |
因此更合理的模块边界是:
┌────────────────────────────┐
│ BrowserManager │ 负责常规业务窗口
└────────────────────────────┘
┌────────────────────────────┐
│ CaptureOverlayManager │ 负责全屏截图 overlay 会话
└────────────────────────────┘
9.2 生产代码的实际落点
从源码结构看,该蓝图提出的模块边界已经落地为 apps/desktop/src/main/modules/screenCapture/ 目录下的真实文件(模块命名从文档建议的 CaptureOverlayManager 演进为 ScreenCaptureManager):
| 建议职责 | 当前仓库中的实现 |
|---|---|
| 创建 / 销毁 overlay 窗口、管理截图会话 | ScreenCaptureManager.ts |
| 封装窗口枚举与过滤 | WindowSourceService.ts |
| 封装窗口截图、区域截图、输出 | CaptureService.ts |
| 对 renderer 暴露 IPC | ScreenCaptureCtr.ts(经 registry.ts 注册) |
| IPC 类型定义 | packages/electron-client-ipc/src/types/screenCapture.ts |
| 权限检查 / 引导 | @/utils/permissions 配合 ScreenCaptureManager.ensureScreenCaptureAccess() |
该模块还配有单元测试 ScreenCaptureManager.test.ts 与 WindowSourceService.test.ts,与"测试建议"一节要求的"过滤逻辑 / 会话生命周期行为测试"一一对应。
9.3 Renderer 路由
生产环境存在两种可选实现:
| 方案 | 优点 | 缺点 | 建议 |
|---|---|---|---|
| 独立静态 HTML 页面 | 轻量、与业务隔离、最接近 demo | 与现有 React / i18n / 业务状态脱节 | 仅适合 spike |
| 独立桌面 SPA 路由 | 可复用现有构建、i18n、业务事件链 | 需要声明清晰的平台差分 | 推荐生产使用 |
若采用 SPA 路由,建议新增 src/routes/(desktop)/screen-capture-overlay/index.tsx(overlay 页面入口,只挂载 UI 组件)、src/features/DesktopScreenCaptureOverlay/*(业务组件 / hooks / 样式),并在 src/spa/router/desktopRouter.shared.tsx(Web/Electron 共用路由)与 src/spa/router/desktopRouter.config.desktop.tsx(Electron 独有路由差分)中登记路由。两条硬性规则:
- 共用路由只在 shared 文件注册,避免 Web/Electron 路由树漂移;
- overlay route 应保持极薄,不在 route 文件中堆叠业务逻辑。
从源码看,主进程实际加载的是 /overlay URL(buildRendererUrl('/overlay')),即以独立桌面路由承载 overlay 渲染层。
9.4 托盘入口
若要从托盘启动 overlay,会涉及以下文件:
- apps/desktop/src/main/menus/impls/macOS.ts、windows.ts、linux.ts —— 三平台托盘菜单模板;
apps/desktop/src/main/locales/default/menu.ts—— 托盘菜单文案。
推荐新增文案键:tray.captureScreen(启动截图 overlay)、tray.captureScreenWindow(启动窗口截图模式,可选)。
10. 依赖落点与版本建议
依赖应加入 apps/desktop/package.json(Electron 桌面运行时的真实依赖落点):
| 包名 | 用途 | 文档记录的 demo 使用版本 |
|---|---|---|
node-screenshots |
枚举窗口 + 窗口截图 | ^0.2.8 |
get-windows |
白名单过滤隐藏 / 伪关闭窗口 | ^9.3.0 |
注意:node-screenshots 在较新的大版本中 API 从 captureScreen 演进为 Window.all() / Monitor.all(),当前仓库源码使用的即是后者;接入时以实际安装版本对应文档(README)为准。
11. 测试建议与手工验证清单
文档建议避免写"窗口列表快照"这类低信号测试,优先做行为测试:
| 测试层级 | 建议内容 |
|---|---|
| 单元测试 | 过滤逻辑:尺寸阈值、系统应用排除、自身窗口排除、白名单交集 |
| 主进程集成测试 | 权限失败、overlay 会话生命周期、错误分支 |
| 手工验证 | 菜单栏覆盖、点击截窗、拖拽截区、隐藏窗口过滤 |
建议手工验证清单:
| 检查项 | 期望 |
|---|---|
| 当前活动屏幕启动 overlay | 只覆盖当前目标 display |
| 已隐藏的 Electron 子窗口 | 不再出现边框 |
| 点击普通应用窗口 | 剪贴板中得到该窗口图像 |
| 拖拽区域截图 | 剪贴板中得到对应裁剪区域 |
| 取消操作 | Esc 可关闭 overlay |
12. 当前已确认的非目标
| 非目标 | 说明 |
|---|---|
| 当前阶段支持全平台一致体验 | 尚未完成 |
| 当前阶段支持窗口标题绝对准确 | get-windows 在无额外权限时标题可为空;当前主要依赖 node-screenshots |
| 当前阶段支持多 display 同时 overlay | 尚未实现 |
| 当前阶段支持标注编辑器 | 未实现 |
13. 业务接入分阶段计划
阶段一:桌面主进程能力落地
- 将
node-screenshots、get-windows加入apps/desktop/package.json#dependencies; - 新建
screenCapture主进程模块与 controller(当前已落地为 modules/screenCapture + ScreenCaptureCtr.ts); - 跑通托盘菜单触发 overlay;
- 继续以剪贴板为唯一输出。
阶段二:接回现有业务 UI
- 新增桌面专用 overlay route / feature;
- 将截图结果从"仅写剪贴板"升级为"回传 attachment";
- 支持从 chat 输入区触发;
- 支持截图后自动插入当前会话。
(当前实现已演进到"预览即上传 + 提交回传附件 + 从 chat 输入区发起"的能力形态,见 7.1 节所述的上传状态机与 handleSubmit 分发链路。)
阶段三:体验完善
- 多 display 支持;
- Hover 高亮 / 文案优化;
- 保存文件、编辑器标注、OCR 等增强能力;
- 平台差异补齐(尤其 Windows / Linux)。
14. 后续实现时的推荐决策一览
| 决策点 | 推荐 |
|---|---|
overlay 窗口是否复用 BrowserManager |
不推荐 |
| renderer 是否走 SPA route | 推荐 |
| 主进程是否继续保留"剪贴板优先"输出 | 推荐,先保持最小可用闭环 |
是否继续保留 desktopCapturer 作为区域截图路径 |
推荐(生产实现已进一步统一到 node-screenshots 的 Monitor crop,接入时可二选一) |
是否用 get-windows 继续做白名单过滤 |
推荐 |
15. 实施摘要:已验证的技术事实
┌──────────────────────────────────────────────┐
│ 已验证的技术事实 │
├──────────────────────────────────────────────┤
│ 1. Electron 可以创建覆盖整块 display 的窗体 │
│ 2. 纯 Electron 无法独立完成系统窗口高亮 │
│ 3. node-screenshots 可完成窗口枚举与截窗 │
│ 4. get-windows 可帮助过滤隐藏 / 残留窗口 │
│ 5. 最终可形成"点击窗口即截图 + 拖拽截区"闭环 │
└──────────────────────────────────────────────┘
这份文档可作为将该能力正式接入 apps/desktop 主业务的实施基线;从当前仓库的 screenCapture 主进程模块、ScreenCaptureCtr 控制器与 electron-client-ipc 类型定义看,文档设计的模块边界、IPC 面与类型契约均已具备生产实现,后续开发者可直接在此基线上继续补齐多 display、标注与平台差异等工作。
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 StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00