latchshot 0.1.0

A lightweight yet intelligent window-aware screenshot tool
Documentation
# overlay/session.rs 详解 —— 事件循环与输入处理

> 对应代码:`src/overlay/session.rs`。本文件是交互会话的**运行时**:连接 Wayland、为每个输出建遮罩层、跑事件循环、把协议事件翻译成对纯逻辑模块的调用。它自己几乎不产生数据——是"导电层"。

## 分层回顾

```text
render.rs     像素计算(纯函数)
highlight.rs  高亮动画状态机(纯逻辑)
selection.rs  选择状态机(纯逻辑)     ← 以上三个都可单测
surfaces.rs   Wayland surface 机械(buffer/viewport/提交)
session.rs   事件循环 + 输入处理      ← 本文件,把上面四个串起来
```

## select():整个会话的生命周期

```rust
pub fn select(
    scene: Scene,
    frame: DesktopFrame,
    animations: bool,
) -> Result<(SelectionResult, DesktopFrame)> {
    // 1. 连接 + 绑定协议
    let connection = Connection::connect_to_env()...;
    let (globals, mut event_queue) = registry_queue_init(&connection)...;
    //    compositor / subcompositor / layer_shell / shm / viewporter 逐个 bind

    // 2. 组装 State(见下节)

    // 3. roundtrip:等 compositor 把输出信息发完
    event_queue.roundtrip(&mut state)...;
    state.create_outputs(...)?;   // 为每个 Scene 输出建一块遮罩层

    // 4. 事件循环(见下节)
    loop { ... }
}
```

`select` 在 `main` 眼里是**阻塞调用**:跑完整段交互才返回 `(结果, 冻结帧)`。循环在这里,不在 main。

## State:会话期间的全部状态

```rust
pub(super) struct State {
    // Wayland 协议句柄
    registry_state: RegistryState,
    output_state: OutputState,
    seat_state: SeatState,
    compositor: CompositorState,
    shm: Shm,
    viewporter: SimpleGlobal<WpViewporter, 1>,
    // 纯逻辑模块
    selector: Selector,
    highlight: Highlight,
    // 会话数据
    frame: DesktopFrame,
    outputs: Vec<OutputOverlay>,          // 每输出一块遮罩
    // 输入设备
    keyboard: Option<wl_keyboard::WlKeyboard>,
    pointer: Option<ThemedPointer>,
    // 会话终态(FFI 回调不能 return,见下)
    result: Option<SelectionResult>,
    failure: Option<anyhow::Error>,
}
```

## 事件循环三件套

```rust
loop {
    event_queue.blocking_dispatch(&mut state)...?;   // 阻塞等 compositor 发事件

    if let Some(error) = state.failure.take() {
        return Err(error);                            // 出错 → 结束会话
    }
    if let Some(result) = state.result.take() {
        return Ok((result, state.frame));             // 用户确认/取消 → 结束会话
    }

    state.redraw(&qh);                                // 每轮事件后重绘
}
```

**为什么 handler 不直接 return**:handler 是协议回调,运行在 compositor 的 FFI 调用栈里,直接返回错误/panic 会穿过 C 边界。所以 handler 只做两件事——更新状态、把结果写进 `result`/`failure` 字段;循环体在回调返回后统一处理。

## redraw → frame_plan:每帧画什么

```rust
fn redraw(&mut self, qh: &QueueHandle<Self>) {
    let now = Instant::now();
    let plan = self.frame_plan(now);

    for output in &mut self.outputs {
        let frame = &self.frame.outputs[output.frame_index];
        output.present(frame, plan.selection, plan.reveal, plan.pending_reveal, plan.animating, qh);
    }
}

fn frame_plan(&mut self, now: Instant) -> FramePlan {
    // 拖拽优先:拖拽时选区来自 selector.region(),绕过 Highlight
    if let Some(region) = self.selector.region() {
        self.highlight.clear();

        return FramePlan {
            selection: Some(region),
            reveal: 1.0,          // 拖拽不渐入,直接亮
            animating: false,
            pending_reveal: None,
        };
    }

    // 窗口吸附路径:Highlight 驱动一切
    self.highlight.set_target(self.selector.target_geometry(), now);
    let pending_reveal = self.acknowledge_reveal(now);
    let (selection, reveal, animating) = self.highlight.sample(now);

    FramePlan { selection, reveal, animating, pending_reveal }
}
```

`FramePlan` 四个字段的含义:

- `selection`:本轮要画的全局选区(拖拽矩形或高亮矩形);
- `reveal`:渐入进度 0..1(遮罩透明度);
- `animating`:动画是否还在动——决定 `present` 是否继续挂 frame callback 续帧;
- `pending_reveal`:待确认的 reveal 请求(generation 机制,见 highlight.rs 详解)。

## acknowledge_reveal:多屏渐入的同步

```rust
fn acknowledge_reveal(&mut self, now: Instant) -> Option<PendingReveal> {
    let reveal = self.highlight.pending_reveal()?;
    let mut intersecting = 0;
    let mut acknowledged = 0;
    let mut configured = true;

    for output in &self.outputs {
        // 有输出还没 configure 完 → 本轮无法确认
        let Some(size) = output.configured_size() else {
            configured = false;
            break;
        };
        // 只统计与 reveal 目标相交的输出
        if projected_selection(&self.frame.outputs[output.frame_index], reveal.target, size).is_some() {
            intersecting += 1;
            acknowledged += usize::from(output.has_acknowledged_reveal(reveal.generation));
        }
    }

    if configured && acknowledged == intersecting {
        self.highlight.start_reveal(now);   // 全部相交输出都画完了 → 开始渐入
        None
    } else if configured {
        Some(reveal)                        // 还没齐 → 继续等
    } else {
        None                                // 有输出没配置 → 不发新请求
    }
}
```

三个计数器的语义:

- `configured`:所有输出都收到过 configure 事件(否则"是否相交"都算不准);
- `intersecting`:与 reveal 目标相交的输出数——渐入必须等它们**全部**上屏;
- `acknowledged`:其中已经通过 frame callback 确认了当前 generation 的数量。

## pointer_position:局部坐标 → 全局坐标

```rust
fn pointer_position(&self, event: &PointerEvent) -> Point {
    let output = self.outputs.iter()
        .find(|output| output.layer.wl_surface() == &event.surface)
        .unwrap();                                  // 事件必然来自某块遮罩
    let geometry = self.frame.outputs[output.frame_index].logical_geometry;

    Point::new(
        geometry.left() + event.position.0,         // 输出局部 + 输出全局原点
        geometry.top() + event.position.1,
    )
}
```

事件坐标是"落在哪块遮罩层上的局部坐标",Selector 要全局坐标——加该输出的全局原点即可。

## update_pointer:脏标记技巧

```rust
fn update_pointer(&mut self, event: &PointerEvent) {
    let before = (self.selector.target_geometry(), self.selector.region());
    self.selector.pointer_moved(self.pointer_position(event));
    let after = (self.selector.target_geometry(), self.selector.region());

    if before != after {
        self.mark_all_dirty();   // 只有画面真的会变才标脏
    }
}
```

移动事件高频到达,但只有"高亮目标变了 / 选区变了"才需要重画——前后对比避免无谓的每事件重绘。

## 六个 handler

### CompositorHandler::frame —— 垂直同步与续帧

```rust
fn frame(...) {
    let output = self.outputs.iter_mut()
        .find(|output| output.layer.wl_surface() == surface)
        .unwrap();
    // Defer acknowledgement until this dispatch has updated the target.
    output.frame_done();
}
```

"上一帧画完了"的回调。`frame_done` 消费 pending 请求、记录 reveal 确认、动画未完成则标脏续帧。注释说明的时序:**本 dispatch 批次内的所有指针事件先于确认被处理**——否则高亮可能在指针已移动后还基于旧目标确认。

### LayerShellHandler —— 遮罩层的生死

- `configure`:compositor 通告遮罩层尺寸 → `OutputOverlay::configure`(记 `configured_size` + 标脏);
- `closed`:compositor 关闭了遮罩层 → 写 `failure`,会话结束。

### SeatHandler —— 输入设备管理

- `new_capability`:键盘出现 → 绑定 `wl_keyboard`;指针出现 → 建一个光标主题 surface 并绑定 `ThemedPointer`(十字光标用);
- `remove_capability`:键盘消失 → release;指针消失 → 置 None。

### KeyboardHandler —— 三个键

- `press_key`**Esc → Cancelled**——写 `result` 字段,循环体消费;
- `update_modifiers`:空实现(修改键不影响选择)。

### PointerHandler —— 鼠标路径

```rust
fn pointer_frame(..., events: &[PointerEvent]) {
    for event in events {
        match event.kind {
            Enter { .. } => {
                self.pointer.as_ref().unwrap()
                    .set_cursor(connection, CursorIcon::Crosshair).unwrap();
                self.update_pointer(event);
            }
            Motion { .. } => self.update_pointer(event),
            Press { button: BTN_LEFT, .. } => {
                self.update_pointer(event);
                self.selector.press();
                // 按在空处:清高亮(拖拽即将开始,高亮不再有意义)
                if self.selector.target_geometry().is_none() {
                    self.highlight.clear();
                    self.mark_all_dirty();
                }
            }
            Press { button: BTN_RIGHT, .. } => {
                // 右键取消会话(同 Esc)
                self.result = Some(SelectionResult::Cancelled);
            }
            Release { button: BTN_LEFT, .. } => {
                self.update_pointer(event);
                let before = (self.selector.target_geometry(), self.selector.region());
                self.result = self.selector.release();
                let after = (self.selector.target_geometry(), self.selector.region());

                if before != after {
                    self.mark_all_dirty();
                }
            }
            Leave | Axis | Press { .. } | Release { .. } => {}
        }

        if self.result.is_some() {
            break;   // 结果已定,批次内剩余事件不再处理
        }
    }
}
```

注意两点:

1. 一个 `pointer_frame` 可能带**一批**事件(SCTK 聚合),release 之后同批次剩余事件已无意义——`break`2. `set_cursor` 在 Enter 时做——进入遮罩层就把光标变成十字。

### OutputHandler —— 输出热插拔

- `output_destroyed`:会话期间输出消失 → `failure`("output X disappeared during selection")——这是**外部失败**,干净报错而不是 panic。

## 样板代码(可略读)

文件末尾的这几段是 SCTK/wayland-rs 的委托样板:

```rust
wayland_client::delegate_noop!(State: WpViewporter);   // viewporter v1 无事件
impl Dispatch<WpViewport, ()> for State { ... unreachable!(...) }
impl AsMut<SimpleGlobal<WpViewporter, 1>> for State { ... }
delegate_registry!(State);
impl ProvidesRegistryState for State { ... registry_handlers![OutputState, SeatState]; }
smithay_client_toolkit::delegate_dispatch2!(State);
```

它们把各种协议对象的事件分发到 `State` 上——机制性样板,不影响理解会话逻辑。

## 审阅检查点

- 为什么 handler 不能直接 return 错误,而要写 `result`/`failure` 字段?
- 拖拽(`region`)为什么绕过 Highlight 直接返回 `reveal: 1.0`- `frame` 回调里"先处理本批次指针事件、再确认"的顺序为什么重要?
- `acknowledge_reveal` 的三个计数器分别挡什么?(未配置 / 不相交 / 未确认)
- `update_pointer` 的 before/after 对比是省什么的?