# 对话输入框截图与编辑

## 当前实现（2026-09-21）

新客户端通过 `arkmeScreenshot.version === 1` 优先启用 Electron `desktopCapturer`。客户端主进程保持发起窗口可见、捕获各屏静态 PNG，再创建无边框覆盖窗口；插件在 `?arkmeScreenshot=1` 页面只挂载截图编辑器。每屏独立框选，首次选区确认后关闭其他屏覆盖窗口，不跨屏拼接。

原始图像像素尺寸来自 NativeImage；编辑坐标按显示区域与图像宽高分别换算，适用于高 DPI 和负坐标副屏。框选后、绘图前可移动或调整选区。

工具栏仅显示 SVG 图标，hover/focus tooltip 和 aria-label 提供中文名称。支持矩形、圆形/椭圆、箭头、画笔、马赛克、文本、撤销/重做、重选、保存、取消、完成。选中对应工具才显示颜色、粗细、字号或马赛克大小。马赛克直接合成进 PNG；导出文件不附带原图或编辑历史。

- 保存：客户端系统保存对话框，浏览器下载；取消保存不退出编辑。
- 完成：合成选区后通过原有附件流程加入发起会话草稿，不自动发送，不替换文字或已有附件。
- 取消：Esc 或取消按钮，恢复发起窗口；会话/账号切换丢弃迟到结果。
- 快捷键：Cmd/Ctrl+Z 撤销、Cmd/Ctrl+Shift+Z 重做、Cmd/Ctrl+S 保存、Enter 完成；文本中保留原生编辑快捷键，Cmd/Ctrl+Enter 确认文本。
- 捕获/加载限时 20 秒，静态编辑不限时。原窗口导航/销毁、账号 scope 变化、显示器新增/移除/尺寸变化会取消并销毁覆盖窗口。

## 宿主边界与兼容

客户端主进程验证顶层 frame、当前可信 origin、主窗口/有效会话窗口身份。截图窗口仅可读取本屏图像、选择、保存、完成或取消；发起者的 cancel 必须匹配本次 requestId。保存路径只来自系统对话框，插件不传入本地路径；PNG 解码与尺寸限制在主进程校验。

普通浏览器继续 `getDisplayMedia({video:true,audio:false})`，抓取一帧后立即停流，使用同一编辑器。新客户端与旧插件组合仍由旧插件使用旧服务；新插件与旧客户端组合保留旧 macOS `screencapture` 流程（没有新原生编辑桥接）。无需改动既有后端截图 API。

系统屏幕录制权限仍由 OS 管理，不绕过授权；多屏来源匹配失败明确报错，不猜测屏幕。截图内容不写日志。Windows/macOS 原生路径需在实际设备完成权限与多屏验收。

## 可重复验证

相关测试：`tests/*screenshot*.test.*`。新增 `native-screenshot`、编辑模型和输入框桥接回归。客户端对应 `desktop-screenshot-session`、`desktop-screenshot-ipc` 测试。

真实 Electron 渲染使用合成屏幕，不读取实际桌面。插件仓中执行：

```sh
mkdir -p /tmp/arkme-screenshot-qa
./node_modules/.bin/rolldown tests/fixtures/screenshot-editor-preview.tsx --platform browser --format iife --file /tmp/arkme-screenshot-qa/editor.js
```

在该目录创建 `index.html`，内容为 `<meta charset="utf-8"><div id="root"></div><script src="editor.js"></script>`；使用客户端安装的 Electron 运行：

```sh
/path/to/electron tests/fixtures/screenshot-editor-electron.cjs /tmp/arkme-screenshot-qa
```

夹具验证图形像素、马赛克区域变化、裁剪后的 980×500 PNG、全部编辑工具、文本、撤销/重做、保存和完成，产出 editor.png、export.png。系统授权、实际桌面捕获和跨平台多屏并未被合成图验证替代。

## 真实设备验收清单

1. 客户端安装当前客户端和插件产物。主窗口及独立会话窗口均点击截图，验证发起窗口保持可见、覆盖层不进入截图。
2. 在各屏分别框选，测试 Retina/混合缩放/负坐标副屏；不支持跨屏拼接。
3. 六种工具绘制、撤销/重做、重选；保存与取消保存；完成后仅增加草稿图片，原文字/附件保留。
4. 拒绝屏幕权限、捕获中取消、加载失败、切换账号/会话、关闭原窗口、接入/拔出显示器，验证恢复和资源清理。
5. 浏览器截图复用相同工具，编辑时屏幕共享已停止；旧客户端截图仍可用。

## 历史记录

以下记录针对旧流程，不代表本次原生编辑功能已完成真实设备验收。

## 原生路径历史验证记录（2026-09-19）

- 类型检查、插件构建通过；325 项相关接口/输入框测试、158 项服务及登录测试通过。
- 本机接口和截图控制器测试覆盖显式点击、重复点击、正常返回、Esc、权限错误、超时、断连、账号切换、PNG 校验和临时目录清理。
- 实际浏览器使用真实输入框与附件组件、合成截图响应验证：明暗主题、表情右侧按钮、光标位置恢复、保留文字、不自动发送、取消、错误提示、迟到结果丢弃，360/640 像素无横向溢出。
- 隔离 Web 预览固定为 `http://127.0.0.1:3098/`。未新增端口或钥匙串记录。
- 初次验证时隔离预览未登录。09-19 补测已使用现有登录，截屏能力接口成功且 `available: true`；macOS 系统权限和真实框选仍未端到端验收，浏览器模拟成功不能替代。
- 扩大回归发现两项已有发送测试在 `mention-read-status.tsx:12` 触发空值访问；09-19 集中回归经用户确认已修复并新增用例，相关 351 项通过。见 [集中回归记录](desktop-regression-2026-09-19.md)。

## 浏览器路径验证记录（2026-09-19）

- 类型检查、插件构建通过，15 个测试文件共 357 项截图/输入框/附件相关回归通过。
- 使用已登录的隔离 3098 预览与真实输入框、裁剪和附件组件测试；选屏 API 返回合成画布流，附件暂存响应由测试拦截，不截取真实桌面，不发送消息。
- 已验证：发给自己/私聊/群聊确认后加图，1600×900 整图及缩放预览上的反向裁剪，抓帧后立即停流，取消、Esc、拒绝授权、待授权时切换会话与迟到结果停流。
- 原文字及既有附件保留；从文字中间打开截图后恢复到原光标位置。截图按钮鼠标按下不抢走编辑区选择，裁剪弹窗内部交互不会被误判为外部焦点变更。
- 明暗主题截图检查通过；360 像素视口下弹窗无横向溢出；浏览器未捕获到页面运行异常。
- 原生路径仅保留既有实现，未修改或重启已安装 Arkme 客户端。本次没有新开端口或迁移登录信息，只更新隔离预览的前端产物。
- **尚未验收：真实浏览器/内嵌宿主的系统选屏授权，以及 macOS 原生框选权限。** 合成流测试不能代替这些宿主能力；不支持时应按界面提示使用兼容浏览器或系统截屏后粘贴。

### 双平台窗口吸附（2026-09-21）

- macOS：通过现有 Koffi 调用 CGWindowListCopyWindowInfo/CoreFoundation，读取当前可见窗口矩形和顺序；保留正常及浮动应用窗口，过滤桌面、菜单层、全透明和不可见窗口。原生窗口列表在截图覆盖层创建前读取，不激活目标应用。
- Windows：EnumWindows 获取句柄快照，GW_HWNDPREV 计算前后顺序（带循环/次数限制），过滤隐藏、最小化、cloaked、全透明 layered 和桌面/任务栏窗口；DWM extended frame bounds 获取不含不可见调整边框的物理矩形，失败退到 GetWindowRect。
- 窗口信息只包含矩形，不返回标题/进程名。Windows 用 Electron dipToScreenRect 将显示器边界转为物理坐标，macOS 使用 Quartz 逻辑坐标；每屏分别裁剪并按实际截图尺寸映射像素。跨屏窗口只选择当前屏部分。
- 桥接 context 可选 windows 字段，旧版本缺少字段或原生 API 失败时继续手动框选。悬浮匹配最前方窗口；单击（4 CSS px 内抖动）设为选区，拖动自由框选；选中后可移动/调整/标注。重选回到窗口识别。
- 已验证：macOS 原生窗口边界、重叠顺序、隐藏过滤；真实 Electron 合成画面的原生鼠标悬浮、点击吸附、框选、标注、撤销重做、保存、完成。Windows 适配过滤/排序与几何逻辑通过模拟 API 测试，尚无 Windows 真机验收。
- 客户端可复验：构建后 `electron tests/fixtures/screenshot-window-native.cjs`，同一夹具支持 macOS/Windows，不捕获用户桌面像素。Windows 验收应额外覆盖 100%/125%/150% 双屏、负坐标、cloaked 虚拟桌面及全透明置顶窗口。
- 独立审查发现全透明 Windows 窗口过滤缺口，补 GetLayeredWindowAttributes 和回归后通过。手势复验发现 pointerup 最终坐标会被旧 selection 覆盖，已按实际释放坐标计算并补回归。

### 截图启动延迟排查与优化（2026-09-21）

排查流程：跟踪 capture → 原生窗口列表 → 各屏 getSources → toPNG → 编辑窗口创建/loadURL → context 解码 → ready/show；通过只加计时的原实现副本和同环境改进版本做对照。

根因：逐个取不同尺寸显示器，导致重复全屏采集等待串行叠加；主进程同步编码 PNG；编辑窗口也串行加载；渲染端 Uint8Array.from(atob(...), callback) 对每字节执行 JS 回调。新增的窗口吸附并非主要瓶颈（首次原生库初始化约 28ms，后续枚举约 0.7ms）。一次分段采样的两次 getSources 合计约 459ms，PNG 编码约 196ms。

已改为按尺寸缓存 pending Promise 并发取图（保留各屏原生尺寸、无重采样），所有帧采集完毕才创建覆盖层，再并行加载各编辑器。全部 ready 后才显示，取消/异常依然清理租约。渲染端采用 Chromium Uint8Array.fromBase64；旧版使用预分配数组+循环，保持精确字节，不用有损格式。

验证：同设备双屏3024×1964/3840×2160、同一个独立编辑器 HTTP 页面、真实桌面仅在内存读取且不落盘。旧版本从 invoke 到全部 overlay visible 的3次耗时983/683/657ms，新版576/544/547ms。该夹具隔离宿主业务初始化，不应作为完整客户端每次固定耗时的承诺。另以3MB合成数据验证旧解码87.6ms，原生解码1.3ms且逐字节一致。系统截图、PNG 编码和首次进程初始化仍有成本，尚未实现预热/驻留或原生低延迟渲染路径。

性能修复最终回归：客户端33项、插件79项相关测试通过，两仓类型检查和构建通过。独立审查发现后续窗口初始化同步失败时先前 loadURL 的 rejection 未接管；改为 Promise.all(async map) 并补故障注入回归，已验证无 unhandled rejection。Dev 已更新。

### 箭头尖端凸起修复（2026-09-21）
对照用户图片检查 arrow 绘制路径，再以真实 Electron Canvas 复现：线杆直接画至箭头尖端且 lineCap=round，圆帽伸出尖端约线宽的一半。现将线杆截于三角底部，短箭头按手势长度缩短且省略不适合的线杆，零长度不绘制。新增36组方向/粗细/长短组合的真实像素回归，检查没有越过尖端或向后溢出，并保留整体编辑/导出烟测；全部通过。类型检查与打包通过。

## 截图快捷键

客户端注册全局快捷键：macOS 默认 `Command+Shift+A`，Windows 默认 `Control+Shift+A`。快捷键由新版客户端主进程直接启动截图，完成后仅复制到系统剪贴板；不受对话、录音或设置板块影响，后台触发不主动激活主窗口。截图按钮仍调用输入框的附件草稿流程。

在设置 → 通用设置 → 通用中编辑截图快捷键。弹窗显示当前组合并接收键盘录入；确认后由客户端原子保存到用户数据目录，重新启动继续生效。取消不修改设置。录入期间暂停当前全局注册，退出弹窗、页面导航或窗口销毁时恢复。组合无效、注册被占用或保存失败时保留旧设置并显示错误。设置为此设备客户端级别，不跨设备同步。旧客户端和浏览器不显示该设置。

验证：快捷键模型、IPC 来源校验/恢复/广播、弹窗录入/确认/取消/冲突、截图触发去重和 tooltip 同步；真实 Electron 原生键盘输入与弹窗保存检查。Windows 原生快捷键与多显示器操作仍需 Windows 实机验收。

### 截图按钮与快捷键的完成行为（2026-09-22）

私聊中点击截图按钮，完成后将 PNG 加入当前私聊的待发送附件，不自动发送。通过系统快捷键触发，无论当前是否在私聊，完成后都只复制到剪贴板。

插件输入框移除了 `shortcut-trigger` 的截图订阅，仅订阅快捷键配置变更用于 tooltip 和录入暂停状态。截图快捷键的全局启动、完成复制和来源校验由客户端主进程负责；发布时应与支持 `captureClipboard` 的客户端配套，不单独向旧客户端发布本交互变更。浏览器和旧原生截图按钮沿用既有行为。

排查路径：快捷键注册 → 客户端窗口广播 → 输入框订阅 → 活跃状态检查/附件回调。根因是快捷键复用了对话组件生命周期和附件目的地。回归覆盖快捷键不启动附件流程、按钮生成附件、取消/切换账号不追加附件、快捷键配置提示同步。插件相关 69 项测试、类型检查和构建通过；本轮没有完整客户端实机验收。

### 截图工具栏拖动（2026-09-22）

确认选区后，工具栏左侧显示六点拖动手柄。通过 pointer capture 移动整组工具栏，独立保存当前位置，不修改截图选区、标注或导出结果。工具按钮保留点击行为；松手后保持位置，取消拖动/丢失捕获结束拖动。根据 viewport 和 ResizeObserver 得到的工具栏实际尺寸限制边界；新截图和重选恢复默认定位。

验证：编辑器/输入框 27 项回归通过；插件类型声明构建与打包通过。隔离 Electron 使用合成图片完成实际鼠标拖动，确认位移正确、选区不变，后续标注/撤销重做/保存/完成及窄窗口布局烟测通过。实测初次向右拖动命中了屏幕边界，确认边界限制生效；位移回归改为向左拖动后通过。检查了 toolbar-drag.png，手柄与按钮布局正常。
## 截图问 dsh

工具栏在保存与取消之间复用 AskDshIcon，tooltip 为「问dsh」。按钮和快捷键发起的原生截图均可使用：导出当前选区及标注为 PNG，通过主窗口既有 HarnessDraftBridge.prepare 创建 DSH 会话并准备图片附件，等待用户手动输入并发送。

附件准备成功后才关闭截图编辑器并显示 DSH；失败保留选区，同一 PNG 重试复用 operationId。取消、账号切换、窗口失效和超时终止等待。旧客户端没有 askDsh 能力时隐藏按钮。本轮修改涉及客户端和插件，需重启客户端加载新增 preload/IPC。

验证：客户端相关 44 项、插件相关 44 项测试通过，两仓类型检查及构建通过。Electron 合成画面验证 tooltip、点击导出、标注像素、工具栏拖动和窄屏布局；尚未执行真实账号端到端创建会话验收。
