Repository navigation
Capture Pipeline
一次截图动作只有一个所有者:CaptureSession。会话守卫保证并发的快捷键连按不会产生两个互相覆盖的会话;后端核对 session / monitor / 物理尺寸后才接受提交。
截图只有覆盖层一个窗口,没有独立的编辑器窗口。
Wayland 上按顺序回退,任一层成功即停:
1. Mutter 的 PipeWire 屏幕流 org.gnome.Mutter.ScreenCast
2. 自带 GNOME Shell 扩展逐屏 CaptureArea,协议 v5
3. 同一扩展的整屏舞台图 兜底
4. wlroots
5. Portal(非交互)
6. GNOME
7. xcap
返回的临时文件一律由 TemporaryScreenshotFile 兜底删除。
这是本项目里归因最清楚的一次性能优化,值得完整记录。
测量:取一次冻结帧的时间绝大部分不是合成器绘制,而是合成器自己 deflate PNG。同一批像素的对照实验(拍下来再用同一个 gdk-pixbuf 重编一次)显示 4K 那块屏 1704 ms 里有 1607 ms(94%)是 PNG 编码,合成器绘制加读回只有约 100 ms。
因此两个直觉方向都是错的:
- 「少拍像素」无效:逐屏两次 1830 ms vs 整屏一次 1900 ms,本来就没差
- 「在扩展里绕开 PNG 直接读像素」在 GJS 上根本不成立:没有长度标注的
array<uint8>一律是复制入参,别名实验 3 MB 直接段错误
真正的修法是换数据源:不从合成器要图片,改从 Mutter 要视频帧。org.gnome.Mutter.ScreenCast 同一用户直接可调,不经 Portal、不弹对话框、不需要扩展也不需要注销。本机双屏实测全程 280 ms(此前 1900 ms),且每块屏都是原生像素——原生分辨率与十倍速度出自同一个修改。
一个必需的细节:取流期间顶栏会闪一下录制指示,必须传 is-recording: true,否则会落到有 5 秒最短显示时间的「停止共享」胶囊上。
GNOME Wayland 下有两件事只能以扩展身份进合成器做:
- 没有面向普通应用的窗口几何接口
- Portal 截图要求「聚焦的应用」才能弹授权对话框,而快捷键触发时必然不满足
扩展的安装只由设置页显式触发,启动时只做 reconcile_on_startup,绝不擅自安装。装了要注销一次生效,升级同样要(因此有 stale 状态),卸载即时。
调试时不必每次注销:scripts/probe-shell-capture.sh 借 org.gnome.Shell.Eval 在活着的会话里量同一段取像素代码。
整屏舞台图在混合缩放的多屏上会把低缩放那块上采样,画面发糊。逐屏是首选。
三态由选区推导,不额外存状态:
| 状态 | 条件 | 行为 |
|---|---|---|
| idle | 没有选区 | 悬停高亮可速选窗口 |
| dragging | 按住不放 | 框选/移动/缩放选区,或画一笔标注 |
| editing | 有选区 | 工具条贴在选区旁,选区仍可拖动缩放 |
点击空白 = 整屏,点击窗口 = 该窗口,拖动 = 自由区域。
铺满全屏的选区没有「移动」余量,所以 geometry.ts::coversBounds 让它内部的拖拽回到重新框选——否则点一下取了整屏之后就再也框不出小区域。缩放手柄不受当前工具影响,标注工具激活时仍可改框。
工具条落点由 toolbarPlacement 计算:优先贴选区下方,放不下翻到上方,都放不下压在视口底部。
get_capture_frame 用二进制 IPC 直传原始 RGBA(payload 只带几何),前端 putImageData 铺进离屏 canvas。因此标注渲染层的底图类型是 FrameImage = HTMLImageElement | HTMLCanvasElement。
像素改回 PNG + base64 会让覆盖层出现的时间翻倍。
覆盖层未编辑时直接显示资源协议已解码的原图,第一次标注或调色时才分配合成 Canvas;双屏 4K/1600p 会话因此避免约 49 MiB 重复 Canvas 像素,并省去首帧整图复制。
每次会话由 StageTimings 记一条分段耗时日志。
点对钩
→ 前端只交出逻辑选区 + renderer v2 操作层
→ 后端核对 session / monitor / 物理尺寸
→ 从可信冻结帧渲染完整画面
→ 通过同一 SVG viewBox 只输出选区
→ copy / save / pin
先渲染完整画面再裁选区,所以跨选区的路径与模糊邻域不会丢失,WebView 也不再上传另一份最终 PNG。
合成在 blocking worker 执行。渲染期间取消会让后续 session 认领失败,不会产生剪贴板、文件或窗口副作用。
选区翻译直接由后端裁原始像素给本地 OCR,不经过前端画布。
依赖 xcap::Window::all()(本质是 X11/xcb 枚举),按堆叠顺序下发候选(sort_windows_by_stacking / X11 _NET_CLIENT_LIST_STACKING)。
枚举失败或返回空时后端记 log::info,覆盖层显示 "Window picking unavailable in this session"——让 Wayland 下的退化可见,而不是静默给出空列表。
截图链路的失败分类必须彼此独立,不能互相吞掉:
- 输出失败保留编辑内容或同一产物,按已确认的副作用提供重试
- 连续的忙错误不会解除恢复限制
- 输出期间右键不清空选区
- 长截图接管失败可以回到普通截图
- 初始化失败的页面必须能退出
覆盖层窗口本身必须由合成器摆放。在多屏逐个 fullscreen_on_monitor 的循环里,任何非主线程的 GTK 调用都会 panic(GTK may only be used from the main thread),而 panic 发生在第一屏定位之前就会导致:第一屏失去正确定位,第二屏的建窗循环根本不执行。
症状是「覆盖层偏移、只有一屏能截图」。单屏无头环境和独立 GTK fixture 都覆盖不到这条路径——这类问题只能靠真实多屏桌面验收。