From f3c0a83a9a52eac36f2cb40d58ef187068d67b7e Mon Sep 17 00:00:00 2001 From: dailz Date: Mon, 22 Jun 2026 17:27:05 +0800 Subject: [PATCH] =?UTF-8?q?docs(state):=20[3/3]=20=E4=B8=AD=E6=96=87?= =?UTF-8?q?=E6=B3=A8=E9=87=8A=20state.rs=20=E5=B8=A7=E6=8D=95=E8=8E=B7?= =?UTF-8?q?=E4=B8=8E=E8=BE=93=E5=87=BA=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/state.rs | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/src/state.rs b/src/state.rs index 7d871f0..eb423b9 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1854,7 +1854,18 @@ impl Dispatch for State { // Dispatch for CapWlrScreencopy // --------------------------------------------------------------------------- +// 注意本 impl 的 receiver 是 `State`(具体类型)而非 `State` +// (泛型)——wlr-screencopy 协议是 wlroots 后端独有,Portal/PipeWire 路径不会触发本事件。 +// 把 Dispatch 限定到具体 State 后,编译器只在 wlr 路径编译此分支, +// Portal 路径不会误捕获 ZwlrScreencopyFrameV1 事件(类型系统层面的后端隔离)。 +// 协议语义:截屏一帧从 capture_destruction 之外的所有事件都通过本 protocol 对象回送: +// Buffer/LinuxDmabuf(compositor 列出支持的 buffer 类型)→ BufferDone(v3 终止事件) +// → Ready(成功,附 PTS)/ Failed(失败)/ Damage(脏矩形,本实现不消费)。 +// 状态机交互:LinuxDmabuf 触发 negotiate_format + on_frame_allocd(分配 AVHwFrames); +// Ready 触发 on_copy_complete(送入编码器);BufferDone 收到却没 LinuxDmabuf → 报错。 impl Dispatch for State { + // 参数 `proxy: &ZwlrScreencopyFrameV1`:本帧的 protocol 对象,ready 后用 proxy.destroy() 释放。 + // 参数 `_qhandle`:本 impl 内不创建子对象,所以队列句柄未用(前缀 `_`)。 fn event( state: &mut Self, proxy: &ZwlrScreencopyFrameV1, @@ -1863,13 +1874,18 @@ impl Dispatch for State { _conn: &wayland_client::Connection, _qhandle: &QueueHandle>, ) { + // ScreencopyFrameEvent 是 wayland-scanner 生成的 enum,包含本帧生命周期所有事件。 match event { // SHM buffer offer — in v3 the compositor enumerates supported buffer // types (buffer and/or linux_dmabuf) before buffer_done. We only // support DMA-BUF, so just log and wait for linux_dmabuf / buffer_done. + // 上方英文注释保留:compositor 在 v3 协议中先广播所有可用 buffer 类型,再发 BufferDone。 + // 本实现只支持 DMA-BUF(零拷贝),SHM 路径只用 debug 日志记录后忽略。 ScreencopyFrameEvent::Buffer { .. } => { tracing::debug!("Received SHM Buffer offer — only DMA-BUF capture is supported"); } + // LinuxDmabuf:compositor 愿意把帧写到 DMA-BUF。format 是 DRM fourcc(如 XRGB8888), + // width/height 是 buffer 尺寸(物理像素)。 ScreencopyFrameEvent::LinuxDmabuf { format, width, @@ -1877,51 +1893,74 @@ impl Dispatch for State { } => { tracing::debug!("Screencopy LinuxDmabuf: format={format}, {width}x{height}"); + // 守卫:若 in_flight_surface 不是 AllocQueued,说明这个 LinuxDmabuf 是迟到的 + // 或重复的事件,按协议不该再处理。直接 return 不更新状态。 if !matches!(state.in_flight_surface, InFlightSurface::AllocQueued) { tracing::warn!("Received LinuxDmabuf while no frame allocation was queued"); return; } + // EverythingButFmt 阶段:第一帧需要 negotiate_format 完成 EncConstructionStage + // 切换(建编码器、配 VAAPI 上下文)。后续帧直接复用已建好的 cap。 if matches!(state.stage, EncConstructionStage::EverythingButFmt { .. }) { state.negotiate_format(format, width, height); + // negotiate_format 内部失败会把 state.errored 置 true,提前 return 避免后续 + // on_frame_allocd 访问未初始化的硬件帧上下文。 if state.errored { return; } } + // Streaming 阶段:把当前帧的 proxy 存入 cap.current_frame,ready 后用它 destroy。 + // proxy.clone() 增加 wayland proxy 引用计数(不复制底层数据)。 if let EncConstructionStage::Streaming { cap, .. } = &mut state.stage { cap.current_frame = Some(proxy.clone()); } + // 调度硬件帧分配(AVHwFramesContext + av_hwframe_get_buffer),完成后 in_flight_surface + // 切换为 AllocatedDma。wlr 路径的 user_data 是 () (无 PipeWire serial)。 state.on_frame_allocd((), format, width, height); } // v3 terminal event: all buffer offers have been enumerated. // If still AllocQueued, the compositor never sent linux_dmabuf — // DMA-BUF screencopy is unsupported, so we must error out. + // 上方英文注释保留:BufferDone 是 v3 新增的终止事件。如果到这里 in_flight_surface 还是 + // AllocQueued,说明 compositor 没发 LinuxDmabuf(不支持 DMA-BUF 截屏),必须报错退出。 ScreencopyFrameEvent::BufferDone => { if matches!(state.in_flight_surface, InFlightSurface::AllocQueued) { tracing::error!( "Compositor did not offer DMA-BUF screencopy (only SHM); \ DMA-BUF capture is required" ); + // 状态机回滚:清空 in_flight_surface,销毁 proxy,置 errored 让主循环退出。 state.in_flight_surface = InFlightSurface::None; proxy.destroy(); state.errored = true; } } + // Ready:compositor 已完成本帧 copy。tv_sec_hi/tv_sec_lo 是 64 位时间戳被拆成两个 + // 32 位字段(Wayland 协议原生只支持 32 位 u32,所以 u64 必须拆 hi/lo)。 + // tv_nsec 是纳秒分量(0..1_000_000_000)。 ScreencopyFrameEvent::Ready { tv_sec_hi, tv_sec_lo, tv_nsec, } => { + // 重组 u64:高 32 位左移 32 + 低 32 位 OR(Wayland 协议常用模式)。 let tv_sec = (tv_sec_hi as u64) << 32 | tv_sec_lo as u64; + // 转 microsecond(FFmpeg PTS 用 μs),向下取整不影响 90kHz 时钟精度。 let tv_usec = tv_nsec / 1000; tracing::trace!("Screencopy ready: tv_sec={tv_sec}, tv_usec={tv_usec}"); + // on_copy_complete:取出已分配的 dmabuf 帧、计算 PTS、送入编码器、调度下一帧。 state.on_copy_complete(tv_sec, tv_usec); } + // Failed:compositor 报告本帧 copy 失败(如资源争用、buffer 不可用)。 + // on_copy_fail 会重置 in_flight_surface 并安排重试(不直接终止流水线)。 ScreencopyFrameEvent::Failed => { tracing::error!("Screencopy frame failed"); state.on_copy_fail(); } + // Damage 事件:compositor 报告本帧脏矩形区域。截屏全帧采集,无需增量更新,忽略。 ScreencopyFrameEvent::Damage { .. } => {} + // 兜底 arm:未来协议版本新增事件时不会编译失败(向前兼容)。 _ => {} } } @@ -1931,6 +1970,10 @@ impl Dispatch for State { // Dispatch // --------------------------------------------------------------------------- +// ZxdgOutputManagerV1 是 xdg-output 协议的工厂对象:客户端调用 get_xdg_output(wl_output) +// 创建 ZxdgOutputV1 子对象。manager 自身不发任何事件(仅响应请求),所以本 event() 空实现。 +// 必须实现 Dispatch trait:wayland-client 要求每个 bind 的 protocol 都有对应 Dispatch impl, +// 否则编译期类型错误(即使 event 体为空,签名也要满足 trait contract)。 impl Dispatch for State { fn event( _state: &mut Self, @@ -1947,6 +1990,12 @@ impl Dispatch for State { // Dispatch // --------------------------------------------------------------------------- +// ZwlrOutputManagerV1:wlr-output-management-unstable-v1 协议的顶层 manager。 +// 协议语义:compositor 在 wl_registry 全局里广播本 manager;客户端 bind 后, +// compositor 会发出 Head(每个显示器一个 head proxy)/ Done(一轮快照完成)/ Finished +// 事件。客户端据此构建多显示器拓扑(位置、分辨率、命名)。 +// 本实现用途:probing 阶段通过 head 的 Name/Position 事件为每个输出建立 WlrHeadInfo; +// Done 收齐后逐个 try_finalize_output 选定目标输出。运行期不再消费本协议事件。 impl Dispatch for State { fn event( state: &mut Self, @@ -1957,11 +2006,17 @@ impl Dispatch for State { qhandle: &QueueHandle>, ) { match event { + // Head 事件:compositor 新建一个 ZwlrOutputHeadV1 子对象。`head` 是 wayland-client + // 通过 event_created_child! 宏(见本 impl 末尾)自动 dispatch 到 Dispatch 的。 WlrOutputManagerEvent::Head { head } => { + // 显式类型注解 + let _ 绑定:wayland-client 自动生成的 head 类型在编译期才确定, + // 显式注解让读者知道类型;这里仅记录日志,不持有引用(head 已被 child dispatch 接管)。 let _head: ZwlrOutputHeadV1 = head; tracing::debug!("wlr output head advertised"); } + // Done 事件:本轮 head 增删已全部上报,可以基于稳定快照做输出选择。 WlrOutputManagerEvent::Done { .. } => { + // 仅 ProbingOutputs 阶段响应 Done(运行期 Done 说明配置变更,本实现不支持热插拔)。 if let EncConstructionStage::ProbingOutputs { wlr_manager_done, outputs, @@ -1969,12 +2024,14 @@ impl Dispatch for State { } = &mut state.stage { *wlr_manager_done = true; + // 遍历每个输出索引,尝试 finalize。try_finalize_output 内部会判断输出是否就绪。 let count = outputs.len(); for idx in 0..count { state.try_finalize_output(idx); } } } + // Finished:compositor 主动关闭本 manager(通常 compositor 退出)。本实现仅 warn。 WlrOutputManagerEvent::Finished { .. } => { tracing::warn!("zwlr_output_manager_v1::Finished received during probing"); } @@ -1982,6 +2039,11 @@ impl Dispatch for State { } } + // event_created_child! 是 wayland-client 提供的过程宏:当 manager 收到 Head 事件时, + // 自动把 EVT_HEAD_OPCODE 对应的子对象(ZwlrOutputHeadV1)注册到事件队列, + // 让 Dispatch::event 接管子对象事件。 + // 类比 Go 的 http.Handle 注册 handler,但这里是编译期生成的 dispatcher。 + // 元组第二项 (ZwlrOutputHeadV1, ()) = (子对象类型, 子对象 user_data 类型)。 event_created_child!(State, ZwlrOutputManagerV1, [ zwlr_output_manager_v1::EVT_HEAD_OPCODE => (ZwlrOutputHeadV1, ()), ]); @@ -1991,6 +2053,14 @@ impl Dispatch for State { // Dispatch // --------------------------------------------------------------------------- +// ZwlrOutputHeadV1:单个显示器的 metadata 子对象(由 ZwlrOutputManagerV1::Head 事件创建)。 +// 协议事件:Name(输出名,如 "eDP-1")/ Description / Position(x,y 偏移)/ Mode / +// Geometry / Finished(compositor 移除本输出)。本实现只消费 Name + Position。 +// 状态交互:Name 写入 ProbingOutputs.wlr_heads(HashMap), +// Position 更新对应 head 的 position 字段;try_finalize_output 用 head position 判断 +// 多显示器场景下目标输出在虚拟桌面上的位置(决定截屏 ROI 起点)。 +// 注意 Name 先到、Position 后到的事件顺序假设:实现用 wlr_head_proxy_to_name 临时映射 +// proxy.id() → name,Position 事件到来时反查 name 再更新 wlr_heads。 impl Dispatch for State { fn event( state: &mut Self, @@ -2001,19 +2071,26 @@ impl Dispatch for State { _qhandle: &QueueHandle>, ) { match event { + // Name 事件:compositor 告知本输出的名字(如 "eDP-1"、"HDMI-A-1")。 WlrHeadEvent::Name { name } => { + // 仅 ProbingOutputs 阶段消费 head 事件;其它阶段忽略(防御式编程)。 if let EncConstructionStage::ProbingOutputs { wlr_heads, wlr_head_proxy_to_name, .. } = &mut state.stage { + // HashMap entry API:若 name 不存在则插入空 WlrHeadInfo,已存在则不动。 + // .or_insert() 返回 &mut value,便于后续更新字段。 wlr_heads .entry(name.clone()) .or_insert(WlrHeadInfo { position: None }); + // 反向索引:proxy.id()(wayland 对象 id) → name,供后续 Position 事件反查。 + // 注意这里 name 已被 entry() 消费,所以上面 .clone()。 wlr_head_proxy_to_name.insert(proxy.id(), name); } } + // Position 事件:本输出在虚拟桌面上的 (x, y) 偏移(像素)。 WlrHeadEvent::Position { x, y } => { if let EncConstructionStage::ProbingOutputs { wlr_heads, @@ -2021,6 +2098,8 @@ impl Dispatch for State { .. } = &mut state.stage { + // 双层 Option 反查:proxy.id() → name → &mut WlrHeadInfo。 + // 任何一环失败(事件乱序/compositor bug)→ 静默忽略,不 panic。 if let Some(name) = wlr_head_proxy_to_name.get(&proxy.id()) { if let Some(head) = wlr_heads.get_mut(name) { head.position = Some((x, y)); @@ -2028,6 +2107,7 @@ impl Dispatch for State { } } } + // Finished:head 被移除。本实现仅日志(不清理 wlr_heads,因为 probing 期已快照)。 WlrHeadEvent::Finished { .. } => { tracing::debug!("zwlr_output_head_v1::Finished received"); } @@ -2035,6 +2115,9 @@ impl Dispatch for State { } } + // event_created_child! 注册子对象 dispatcher:head 收到 Mode 事件时,把 ZwlrOutputModeV1 + // 子对象路由到 Dispatch。本实现不消费 Mode 详情(分辨率/刷新率), + // 但仍需注册,否则 wayland-client 在收到 Mode 事件时会 panic(unknown child opcode)。 event_created_child!(State, ZwlrOutputHeadV1, [ zwlr_output_head_v1::EVT_MODE_OPCODE => (ZwlrOutputModeV1, ()), ]); @@ -2044,6 +2127,9 @@ impl Dispatch for State { // Dispatch // --------------------------------------------------------------------------- +// ZwlrOutputModeV1:head 的一个分辨率/刷新率组合(head 可有多个 mode)。 +// 本实现不消费 mode 详情(截屏只关心 head 名字 + 位置,分辨率从 dmabuf feedback 拿), +// 但必须实现 Dispatch trait,否则 wayland-client 收到 EVT_MODE_OPCODE 子对象会 panic。 impl Dispatch for State { fn event( _state: &mut Self, @@ -2060,6 +2146,9 @@ impl Dispatch for State { // Dispatch // --------------------------------------------------------------------------- +// ZwlrScreencopyManagerV1:截屏协议的工厂对象。客户端调用 capture_output 创建 +// ZwlrScreencopyFrameV1 子对象(一帧一个)。manager 自身不发任何事件,所以空实现。 +// 必须实现 Dispatch trait 以满足 wayland-client 类型 contract(同 ZxdgOutputManagerV1)。 impl Dispatch for State { fn event( _state: &mut Self, @@ -2076,6 +2165,10 @@ impl Dispatch for State { // Dispatch // --------------------------------------------------------------------------- +// WlBuffer:Wayland 核心 buffer 协议。本 impl 处理 buffer::Release 事件——compositor +// 完成读后通知客户端可以复用或释放该 buffer(生命周期管理的核心信号)。 +// 截屏流水线交互:本 impl 只 trace 日志,不持有 buffer(实际 buffer 生命周期由 +// state.rs on_copy_complete 通过 AVFrame/AVHwFramesContext 管理)。 impl Dispatch for State { fn event( _state: &mut Self, @@ -2085,6 +2178,9 @@ impl Dispatch for State { _conn: &wayland_client::Connection, _qhandle: &QueueHandle>, ) { + // if let 模式匹配:WlBuffer 只有一个事件 Release,但协议 enum 仍可能扩展, + // 用 if let 而非 match 让未来新增事件编译期不报错(向前兼容)。 + // `wayland_client::protocol::wl_buffer::Event::Release` 是 wayland-scanner 生成的全路径枚举。 if let wayland_client::protocol::wl_buffer::Event::Release = event { tracing::trace!("WlBuffer released"); }