docs(state): [3/3] 中文注释 state.rs 帧捕获与输出管理

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