# 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 对比是省什么的?