# Latchshot 设计草案
> A lightweight yet intelligent window-aware screenshot tool.
本文档记录 Latchshot 的产品目标、交互原则和第一版架构约束。它不是一份已经冻结的规格;尚未决定的部分会继续在这里收敛。
## 项目目标
Latchshot 希望让最常见的截图操作足够自然:默认识别并吸附到光标下的窗口,需要精确控制时则直接拖动进入自由选区。它应该启动迅速、反馈清楚,并且在完成截图前尽量少打扰用户。
第一版需要支持:
- 光标下窗口的自动吸附。
- 单击或按 Enter 截取当前候选窗口。
- 拖动超过一个很小的阈值后进入自由选区。
- Shift 临时禁用吸附,Esc 取消。
- 多输出、缩放和输出变换下正确的坐标与裁切。
- 将结果写入文件、剪贴板或 stdout。
- 通过 Niri 后端获得窗口信息。
窗口截图在第一版中的含义是:裁切冻结画面中窗口几何范围内的实际像素。因此,窗口被其他内容遮挡时,遮挡也会进入截图。捕获独立且完整的窗口内容需要 compositor 提供额外能力,未来可以作为可选 capability 加入。
## 后端必须从第一版抽象
Niri 是第一个后端,但不能成为核心架构的一部分。后端抽象不是未来重构项,而是 MVP 的起点。
核心代码不得:
- 直接依赖 `niri-ipc` 类型。
- 根据 Niri 的 workspace、output 或 window ID 组织状态机。
- 在选择、动画、裁切或输出代码中调用 Niri IPC。
- 假设所有 compositor 都能提供完全相同的能力。
项目自己的领域模型至少需要表达:
- 输出的逻辑几何、像素尺寸、scale 和 transform。
- 窗口的稳定标识、可见几何、所属输出与焦点状态。
- 可用时的窗口层叠顺序或等价的命中测试信息。
- 后端当前具备的 capability。
坐标和尺寸在核心层保留足够精度,只在最终像素裁切或协议边界处舍入,避免 fractional scaling 产生一两个像素的漂移。
建议的代码边界是:
```text
Application
├── Core
│ ├── selection state machine
│ ├── hit testing
│ ├── animation
│ └── crop and output pipeline
├── Backend API
│ ├── scene/window discovery
│ ├── frame capture
│ ├── pointer/output information
│ └── capability reporting
├── Backends
│ ├── niri
│ └── future compositor implementations
└── Overlay renderer
```
窗口发现与画面捕获是不同的能力。某个后端可以用 compositor IPC 获取窗口几何,同时通过通用 Wayland 协议、portal 或外部工具捕获画面。因此实现上应使用小而独立的接口组合,而不是一个不断膨胀的 `Backend` trait。
后端应返回一次性的场景快照,核心只消费 Latchshot 自己的类型。窗口命中、重叠窗口优先级、多输出坐标转换和降级行为直接针对核心状态机做单元测试;当出现必须注入后端的集成测试时,再为测试提供最小 mock 实现。
后端选择最终应支持显式指定和自动探测。自动探测失败时应给出简短明确的错误,不能静默假装没有窗口并退化为自由选区。
## 截图流程
理想流程如下:
1. 获取输出画面,并取得与其时间尽量接近的场景快照。
2. 显示冻结画面和选择 overlay,避免 overlay 自己进入截图。
3. 根据场景快照确定光标下的候选窗口。
4. 用户确认窗口,或拖动进入自由选区。
5. 将逻辑选择区域转换为对应输出上的物理像素区域并裁切。
6. 按请求写入文件、剪贴板或 stdout,然后立即退出。
场景快照和捕获画面之间可能存在短暂竞态。核心接口应允许将来使用带同步保证的 compositor 后端,但 MVP 不需要为此引入复杂的持续追踪机制。
## 吸附动画
动画的目标是帮助用户理解当前选中了什么,而不是展示动画本身。
- 候选窗口使用细、清晰的圆角轮廓表示。
- 候选从无到有时做一次很短的淡入;窗口之间切换不重复淡入。
- 光标短暂经过窗口间隙时保留当前候选约 80 ms,以维持连续过渡;实际点击命中不使用这段宽限。
- 切换候选窗口时,轮廓在约 100–140 ms 内移动并改变尺寸。
- 使用接近临界阻尼的运动,不弹跳、不呼吸、不循环发光。
- 快速跨过多个窗口时始终追踪最新目标,不排队播放中间动画。
- 一旦进入自由选区,吸附动画立刻停止,选择框严格跟手。
- 完成时只提供很短的收束或亮度反馈,不使用刺眼的全屏闪光。
- 尊重 reduced-motion 设置;启用后改为极短淡入或直接切换。
如果运动中的边框让候选范围产生歧义,点击命中应以最新目标几何为准,而不是动画当时显示的中间几何。
## 暂不进入 MVP
- 截图后的完整标注编辑器。
- OCR、上传和云端历史。
- 长截图或滚动拼接。
- 为每个 compositor 复制一套 UI 或选择逻辑。
- 为了动画效果引入常驻后台进程。
## 待讨论
- overlay 和渲染层使用哪套 Wayland/Rust 组件。
- 第一版画面捕获使用通用协议、portal,还是可配置的外部 helper。
- 自由选区是否允许跨输出,以及不同 scale 输出交界处的像素语义。
- 重叠或浮动窗口缺少可靠层叠顺序时,候选窗口如何切换。
- 默认输出策略是“剪贴板优先”还是“文件与剪贴板同时写入”。