diff --git a/src/state.rs b/src/state.rs index c5a1cdb..7d871f0 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1368,7 +1368,24 @@ impl State { // Dispatch // --------------------------------------------------------------------------- +// wayland-client 的 Dispatch trait:处理 Wayland 协议对象的事件回调。 +// 泛型参数 1 = 协议对象类型(这里是 WlRegistry,compositor 全局对象广播器), +// 泛型参数 2 = 用户数据类型(GlobalListContents,附加到 registry 的 user_data, +// 类比 Go context.Context 但只读、附着在协议对象上)。 +// trait method `event` 由 wayland-client 在 dispatch 阶段调用:每收到一个事件就 +// 把 (&mut State, &Connection, &ProtocolObj, &UserData, Event, ...) 传进来。 +// 类比 Go 的隐式 interface + callback,但 Rust 用泛型 + 显式 impl Trait for Type +// 实现编译期静态分发(每个 protocol 对应一份独立代码,零虚函数开销)。 +// 本 impl 处理 wl_registry::Global / GlobalRemove 事件:当 compositor 广播新协议 +// 对象时,按 interface 名选择性 bind(screencopy/dmabuf/wl_output/xdg-output/ +// wlr-output-management),把 proxy 存入 ProbingOutputs stage 字段。 impl Dispatch for State { + // 注意 Dispatch::event 的 receiver 是 `state: &mut Self`(不是 self): + // wayland-client 设计为 free fn + 第一个参数 &mut State,便于事件循环统一调度。 + // &mut Self 在 trait 内等价于 &mut State,是当前类型的独占可变借用。 + // 参数 `_data: &GlobalListContents`:registry 附带的 user_data,这里不读,前缀 `_` 标记未用。 + // 参数 `qhandle: &QueueHandle>`:事件队列句柄,bind 子对象时需传入 + // (让 wayland-client 知道新 proxy 的事件继续回到这个队列)。 fn event( state: &mut Self, registry: &WlRegistry, @@ -1377,25 +1394,37 @@ impl Dispatch for State { _conn: &wayland_client::Connection, qhandle: &QueueHandle>, ) { + // 局部 use 把长路径重命名为短名,下面 match 用 RegistryEvent::Xxx 更清晰。 + // 这是 Rust 惯用法,零运行时开销(仅作用域内类型别名)。 use wayland_client::protocol::wl_registry::Event as RegistryEvent; + // match 枚举事件:Wayland 协议事件都是 exhaustive enum(编译期保证穷尽所有变体)。 + // RegistryEvent 有两个变体:Global(新增协议对象)/ GlobalRemove(移除)。 match event { + // Global 事件:compositor 广播一个新协议对象(name=u32 ID, interface=协议名, version=版本号)。 RegistryEvent::Global { name, interface, version, } => match interface.as_str() { + // wlr-screencopy-manager-unstable-v1:截屏协议入口。版本取 min(server, 3)。 "zwlr_screencopy_manager_v1" => { let v = version.min(3); tracing::debug!("Binding zwlr_screencopy_manager_v1 v{v} (name={name})"); + // registry.bind(name, version, qhandle, user_data):创建 protocol proxy。 + // 返回值类型在 let mgr: 显式标注,Rust 推断不出 wayland 自动生成的类型。 let mgr: ZwlrScreencopyManagerV1 = registry.bind(name, v, qhandle, ()); + // if let 模式匹配 + &mut 借用:只关心 ProbingOutputs 阶段,其它阶段忽略。 if let EncConstructionStage::ProbingOutputs { screencopy_manager, .. } = &mut state.stage { + // *screencopy_manager = Some(mgr):把 Option 字段从 None 填成 Some。 *screencopy_manager = Some(mgr); } } + // linux-dmabuf-unstable-v1:DMA-BUF 零拷贝 buffer 协议。版本取 min(server, 4)。 + // v4 起 get_default_feedback 可用,能拿到 compositor 推荐的 DRM 设备。 "zwp_linux_dmabuf_v1" => { let v = version.min(4); tracing::debug!("Binding zwp_linux_dmabuf_v1 v{v} (name={name})"); @@ -1406,16 +1435,22 @@ impl Dispatch for State { .. } = &mut state.stage { + // proxy.clone() 增加 wayland proxy 的引用计数(compositor 端不动)。 *dmabuf = Some(proxy.clone()); if v >= 4 { + // 仅 v4+ 支持 dmabuf feedback(compositor 主动告知格式 + 设备)。 let feedback = proxy.get_default_feedback(qhandle, ()); *dmabuf_feedback = Some(feedback); } } } + // wl_output:Wayland 核心输出(显示器)协议。每个 monitor 一个 WlOutput proxy。 "wl_output" => { let v = version.min(4); tracing::debug!("Binding wl_output v{v} (name={name})"); + // user_data = OutputId(name):把 registry 分配的 u32 name 作为 user_data + // 附到 output proxy 上,后续 Dispatch::event 中通过 + // &OutputId 拿回 name 来定位 stage.outputs 索引。 let output: WlOutput = registry.bind(name, v, qhandle, OutputId(name)); if let EncConstructionStage::ProbingOutputs { outputs, @@ -1425,15 +1460,19 @@ impl Dispatch for State { .. } = &mut state.stage { + // 4 个并行 Vec 用相同下标对齐:第 i 个 output 的 info/proxy/name 共享 idx i。 outputs.push(PartialOutputInfo::default()); bound_outputs.push(output.clone()); output_names.push(name); + // 若 xdg-output manager 已 bind(顺序无关),立刻请求 xdg-output 信息 + // (logical_position / name 等高元数据只有 xdg-output 才有)。 if let Some(xdg_mgr) = xdg_output_manager { let output_id = OutputId(name); xdg_mgr.get_xdg_output(&output, qhandle, output_id); } } } + // xdg-output-unstable-v1:扩展 wl_output 的逻辑坐标/名称(Sway/Hyprland 用)。 "zxdg_output_manager_v1" => { let v = version.min(3); tracing::debug!("Binding zxdg_output_manager_v1 v{v} (name={name})"); @@ -1445,7 +1484,10 @@ impl Dispatch for State { .. } = &mut state.stage { + // 回填:之前已 bind 的 wl_output 现在补请 xdg-output(处理乱序到达)。 + // .enumerate() 把 iter 转成 (idx, &item),类比 Go 的 for i, o := range。 for (i, output) in bound_outputs.iter().enumerate() { + // .copied() 把 Option<&u32> 转 Option(Copy 类型专用,零开销)。 let oname = output_names.get(i).copied().unwrap_or(0); let output_id = OutputId(oname); xdg_mgr.get_xdg_output(output, qhandle, output_id); @@ -1453,6 +1495,7 @@ impl Dispatch for State { *xdg_output_manager = Some(xdg_mgr); } } + // wlr-output-management-unstable-v1:niri 用的输出管理协议(替代 xdg-output)。 "zwlr_output_manager_v1" => { let v = version.min(4); tracing::debug!("Binding zwlr_output_manager_v1 v{v} (name={name})"); @@ -1464,11 +1507,14 @@ impl Dispatch for State { *wlr_output_manager = Some(mgr); } } + // 其它未关心的协议:忽略(compositor 还会广播 wl_seat/wl_data_device 等)。 _ => {} }, + // GlobalRemove:compositor 移除某协议对象(如显示器热插拔)。截屏启动期忽略。 RegistryEvent::GlobalRemove { name } => { tracing::debug!("Global removed: name={name}"); } + // 兜底 arm:未来 Wayland 新增事件变体时不会编译失败(trait 兼容性预留)。 _ => {} } } @@ -1478,6 +1524,10 @@ impl Dispatch for State { // Dispatch // --------------------------------------------------------------------------- +// 处理 wl_output 协议事件:compositor 推送显示器元数据(geometry/mode/name/done)。 +// user_data = OutputId(name):bind 时传入的 registry name,这里用来反查 stage 中的索引。 +// 本 impl 把事件数据累计到 PartialOutputInfo(在 ProbingOutputs 阶段), +// Done 事件触发 try_finalize_output 尝试切到 EverythingButFmt。 impl Dispatch for State { fn event( state: &mut Self, @@ -1487,29 +1537,40 @@ impl Dispatch for State { _conn: &wayland_client::Connection, _qhandle: &QueueHandle>, ) { + // 引入 wl_output 的三个 enum 别名:事件 / 模式 flag / 旋转 transform。 + // WEnum 是 wayland-client 的 enum 包装:协议允许 Unknown 值,所以是 Value + Unknown。 use wayland_client::protocol::wl_output::Event as OutputEvent; use wayland_client::protocol::wl_output::Mode as WlMode; use wayland_client::protocol::wl_output::Transform as WlTransform; + // 解构 OutputId tuple struct:拿回 u32 name。`data` 是 &OutputId,所以这里 target_name 是 &u32。 let OutputId(target_name) = data; + // 在 output_names 中找索引:用 iter().position(...) 类似 Go 的 for-loop + index。 + // 只在 ProbingOutputs 阶段处理;其它阶段(Streaming)的 wl_output 事件忽略。 let idx = match &state.stage { EncConstructionStage::ProbingOutputs { output_names, .. } => { output_names.iter().position(|&n| n == *target_name) } _ => None, }; + // 早期 return:找不到 idx 说明该 output 已被丢弃或阶段已过,直接退出。 + // Rust 中 fn 内 `return;` 等价于返回 ()(本函数返回类型就是 ())。 let idx = match idx { Some(i) => i, None => return, }; match event { + // Geometry:compositor 推送显示器物理属性 + 旋转方向。 + // `..` 表示忽略其它字段(protocol 可能有 x/y/subpixel/manufacturer 等)。 OutputEvent::Geometry { transform, physical_width, physical_height, .. } => { + // WEnum::Value(...) 模式:取出协议枚举的已知值;Unknown 值落到 `_` 默认分支。 + // Transform 是本程序自定义的 enum(去掉 wayland 的 WEnum 包装,方便后续匹配)。 let t = match transform { wayland_client::WEnum::Value(WlTransform::Normal) => Transform::Normal, wayland_client::WEnum::Value(WlTransform::_90) => Transform::Normal90, @@ -1522,18 +1583,22 @@ impl Dispatch for State { _ => Transform::Normal, }; if let EncConstructionStage::ProbingOutputs { outputs, .. } = &mut state.stage { + // .get_mut(idx) 返回 Option<&mut T>:越界时返回 None 而非 panic。 if let Some(info) = outputs.get_mut(idx) { info.transform = Some(t); info.physical_size = Some((physical_width, physical_height)); } } } + // Mode:显示器分辨率模式。flags 标识 Current/Preferred(位掩码)。 OutputEvent::Mode { width, height, flags, .. } => { + // matches! 宏:等价于 if let + bool,简洁判断 flags 是不是 Current。 + // 不用 == 是因为 WEnum 是 enum,需要模式匹配。 let is_current = matches!(flags, wayland_client::WEnum::Value(WlMode::Current)); if is_current { if let EncConstructionStage::ProbingOutputs { outputs, .. } = &mut state.stage { @@ -1543,16 +1608,21 @@ impl Dispatch for State { } } } + // Done:compositor 一次性告知所有元数据已发完(wl_output v2+)。 + // done_count += 1 后立即尝试 finalize——若所有必填字段就绪,切到 EverythingButFmt。 OutputEvent::Done => { if let EncConstructionStage::ProbingOutputs { outputs, .. } = &mut state.stage { if let Some(info) = outputs.get_mut(idx) { info.done_count += 1; if info.done_count >= 1 { + // try_finalize_output 内部会判断阶段 + 字段完备性,可能 return false。 state.try_finalize_output(idx); } } } } + // Name:显示器可读名(v4+,如 "eDP-1" / "HDMI-A-1")。 + // 注意:本字段 info.wl_name 与 bind 时的 OutputId(name) 是同一来源(registry name 的字符串化版本)。 OutputEvent::Name { name } => { if let EncConstructionStage::ProbingOutputs { outputs, .. } = &mut state.stage { if let Some(info) = outputs.get_mut(idx) { @@ -1569,6 +1639,11 @@ impl Dispatch for State { // Dispatch // --------------------------------------------------------------------------- +// 处理 xdg-output 协议事件:compositor 推送显示器的逻辑坐标 + 名称(Sway/Hyprland)。 +// xdg-output 是 wl_output 的扩展协议:wl_output 只给物理像素, +// xdg-output 补充 logical_position / logical_size(HiDPI scaled 后的逻辑值)+ name。 +// user_data = OutputId(name):与 Dispatch 同源,用 registry name 反查索引。 +// Done 事件与 wl_output::Done 二选一触发 try_finalize_output(双向保险)。 impl Dispatch for State { fn event( state: &mut Self, @@ -1578,7 +1653,9 @@ impl Dispatch for State { _conn: &wayland_client::Connection, _qhandle: &QueueHandle>, ) { + // data.0 直接取出 OutputId 内的 u32(不用解构,因为 OutputId 是 transparent tuple struct)。 let target_name = data.0; + // 同 Dispatch:找索引,找不到就 return(不在 ProbingOutputs 阶段时也忽略)。 let idx = match &state.stage { EncConstructionStage::ProbingOutputs { output_names, .. } => { output_names.iter().position(|&n| n == target_name) @@ -1591,6 +1668,7 @@ impl Dispatch for State { }; match event { + // Name:xdg-output 提供的可读名(比 wl_output::Name 更可靠,v3+ 必有)。 XdgOutputEvent::Name { name } => { if let EncConstructionStage::ProbingOutputs { outputs, .. } = &mut state.stage { if let Some(info) = outputs.get_mut(idx) { @@ -1598,6 +1676,7 @@ impl Dispatch for State { } } } + // LogicalPosition:scaled 后的逻辑坐标(用于多显示器拼接顺序)。 XdgOutputEvent::LogicalPosition { x, y } => { if let EncConstructionStage::ProbingOutputs { outputs, .. } = &mut state.stage { if let Some(info) = outputs.get_mut(idx) { @@ -1605,7 +1684,10 @@ impl Dispatch for State { } } } + // LogicalSize 故意忽略:截屏用物理像素 size(mode_size),不用 logical(会被 scale 缩放)。 XdgOutputEvent::LogicalSize { .. } => {} + // Done:xdg-output 的元数据批次结束。done_count 在 wl_output 和 xdg-output 两边都自增, + // try_finalize_output 内部 has_xdg 分支要求 done_count >= 2(wl_output + xdg-output 各一次)。 XdgOutputEvent::Done => { if let EncConstructionStage::ProbingOutputs { outputs, .. } = &mut state.stage { if let Some(info) = outputs.get_mut(idx) { @@ -1625,6 +1707,11 @@ impl Dispatch for State { // Dispatch // --------------------------------------------------------------------------- +// 处理 linux-dmabuf v1 基础协议事件(Format/Modifier 广播)。 +// 故意全部留空:我们用的是更现代的 dmabuf feedback(ZwpLinuxDmabufFeedbackV1), +// feedback 协议在 v4+ 才有,更准确(compositor 主动告知主设备 + tranche 优先级)。 +// 这里只实现空 match 是因为 wayland-client 要求每个 bind 的协议都实现 Dispatch; +// legacy Format/Modifier 广播在 feedback 存在时是冗余信息,忽略以省 log。 impl Dispatch for State { fn event( _state: &mut Self, @@ -1635,13 +1722,19 @@ impl Dispatch for State { _qhandle: &QueueHandle>, ) { match event { + // Format:legacy "支持 DRM format X" 广播(无 modifier 信息,已被 feedback 取代)。 DmabufEvent::Format { .. } => {} + // Modifier:v3+ 的 "支持 format X with modifier Y" 广播(仍然没有设备/tranche 优先级)。 DmabufEvent::Modifier { .. } => {} _ => {} } } } +// 处理 linux-dmabuf feedback 事件(v4+):compositor 主动告知 DMA-BUF 偏好。 +// 关键事件 MainDevice:compositor 推荐的 DRM 设备(dev_t 编码), +// 解析为 /dev/dri/renderD{minor} 路径存入 state.drm_device_from_compositor, +// 供后续 VAAPI 初始化用作硬件设备上下文。 impl Dispatch for State { fn event( state: &mut Self, @@ -1652,11 +1745,20 @@ impl Dispatch for State { _qhandle: &QueueHandle>, ) { match event { + // MainDevice:dev 是 8 字节 dev_t(Linux 内核设备号编码,little-endian)。 DmabufFeedbackEvent::MainDevice { device } => { + // 防御性长度检查:协议规定 8 字节,但服务器实现可能违反。 if device.len() >= 8 { + // device[..8].try_into() 把 &[u8] 转 [u8; 8](固定大小数组)。 + // unwrap_or([0u8; 8]):try_into 失败时回退到 0(理论上不会触发,因已检查 len)。 let dev_bytes: [u8; 8] = device[..8].try_into().unwrap_or([0u8; 8]); + // u64::from_ne_bytes:本机字节序(little-endian on x86/ARM)解析为 u64。 let dev = u64::from_ne_bytes(dev_bytes); + // 解码 Linux dev_t:低 8 位 + 高 12 位组合成 minor(renderD{minor} 用 minor+128)。 + // dev_t 编码:bits 0-7 = minor low, bits 8-19 = major, bits 20-31 = minor high. + // 这里用位掩码重组 minor = (dev & 0xFF) | ((dev >> 12) & 0xFFFFFF00)。 let minor = ((dev & 0xFF) | ((dev >> 12) & 0xFFFFFF00)) as u32; + // renderD{minor}:Linux DRM render 节点命名规则(renderD128 = card0, renderD129 = card1 ...)。 let path = PathBuf::from(format!("/dev/dri/renderD{}", minor)); if path.exists() { tracing::info!( @@ -1679,6 +1781,7 @@ impl Dispatch for State { ); } } + // 其它 feedback 事件(格式表 / tranche)暂不消费:我们只用主设备做启发式。 DmabufFeedbackEvent::FormatTable { .. } => {} DmabufFeedbackEvent::Done => {} DmabufFeedbackEvent::TrancheDone => {} @@ -1694,6 +1797,12 @@ impl Dispatch for State { // Dispatch // --------------------------------------------------------------------------- +// 处理 linux-dmabuf buffer_params 协议事件:compositor 反馈 DMA-BUF buffer 创建结果。 +// 每次 capture 创建一个 ZwpLinuxBufferParamsV1 proxy,add 添加 plane 后 create_immed +// 触发 buffer 创建;Created 成功 / Failed 失败。 +// Failed 分支是关键的错误恢复点:把 in_flight_surface 状态机回退到 None, +// 释放持有的 dmabuf buffer(drop 释放文件描述符),并通过 cap.on_done_with_frame +// 把 frame 还给 capture 后端(让 CaptureSource 内部状态保持一致),最后 errored=true。 impl Dispatch for State { fn event( state: &mut Self, @@ -1704,23 +1813,35 @@ impl Dispatch for State { _qhandle: &QueueHandle>, ) { match event { + // Created:buffer 成功创建。本程序路径里不消费这个事件 + // (streaming 后立即用 on_frame_allocd 路径,不依赖 Created 触发后续动作)。 BufferParamsEvent::Created { .. } => { tracing::debug!("DMA-BUF buffer created"); } + // Failed:buffer 创建失败(format/modifier 不支持、内存不足等)。 BufferParamsEvent::Failed => { tracing::error!("DMA-BUF buffer creation failed"); + // mem::replace:把 in_flight_surface 当前值取出(替换为 None), + // 类比 Go 的 swap pattern —— 先拿走所有权再处理,避免后续逻辑还看到旧状态。 let taken = mem::replace(&mut state.in_flight_surface, InFlightSurface::None); match taken { + // CopyQueued:截屏已排队但 buffer 失败。需要回收 frame 资源。 InFlightSurface::CopyQueued { buffer, frame, .. } => { + // drop(buffer):显式释放 dmabuf buffer(关闭文件描述符)。 + // Rust 默认会在作用域结束 drop,这里提前 drop 释放 FD。 drop(buffer); if let EncConstructionStage::Streaming { cap, .. } = &mut state.stage { + // cap.on_done_with_frame(frame):把失败但未释放的 frame 还给 capture 后端, + // CaptureSource 内部状态机才知道这帧"结束了"。 cap.on_done_with_frame(frame); } } + // 其它 in_flight 状态(AllocQueued/None):原样放回,不动状态机。 other => { state.in_flight_surface = other; } } + // proxy.destroy():销毁 buffer_params 对象,避免 compositor 端资源泄漏。 proxy.destroy(); state.errored = true; }