//! wlroots 后端的核心状态机:屏幕采集 → DMA-BUF 协商 → 硬件编码 整条流水线的状态载体。 //! //! ## 整体角色 //! //! `State` 由外层 `main.rs` 中的 `mio` 事件循环驱动(类比 Go 的 netpoller, //! 但 mio 是手工版——需要自己 `Poll::new()`、`registry.register()`、`events.iter()`)。 //! 当 Wayland fd 可读时,main 调用 `wayland_client::Connection::prepare_read` + //! `dispatch_pending`,事件最终被路由到本文件中的 `impl Dispatch<...> for State` 块。 //! //! ## 泛型 `` //! //! `State` 是泛型,类型参数 `S` 表示具体的截屏后端: //! - 当前唯一实现是 `CapWlrScreencopy`(wlr-screencopy 协议绑定,见 `cap_wlr_screencopy.rs`) //! - trait bound `CaptureSource` 类比 Go interface,但 Rust 要求显式 `impl CaptureSource for CapWlrScreencopy` //! - 加上 `Sized + 'static` 限定:`Sized` 表示编译期已知大小(不能是 `?Sized` 的 trait object), //! `'static` 表示无借用、可活任意长(方便存入 `State` 结构体) //! //! ## 编码流水线的阶段机 //! //! `EncConstructionStage` 是一条阶段流水线: //! `ProbingOutputs` → `EverythingButFmt` → `Streaming`(运行期)。 //! 每个阶段持有不同的 Wayland 对象与硬件上下文,运行期进入 `Streaming` 后才开始拉帧编码。 //! //! ## 注意 //! //! - T7a(本注释块)覆盖文件头、类型定义、`State` 方法;T7b 覆盖各 `Dispatch` trait 实现; //! T7c 覆盖帧捕获相关的 `Dispatch` 与 `ZwlrScreencopyFrameV1` 处理。 //! - 大量 `unsafe` 块调用 FFmpeg / `libc::dup` / DMA-BUF FFI;现有英文 `// SAFETY:` 注释务必保留。 //! - `edition = "2021"`(非 2024 默认值);请勿改动任何代码字符,只新增中文注释。 // std 标准库导入:HashMap(输出列表)、mem(take/replace)、AsFd/OwnedFd/FromRawFd(DMA-BUF fd 桥接)、 // Path/PathBuf(DRM 设备路径)、AtomicBool/Ordering(WebRTC 暂停标志,跨线程无锁同步)、Arc(共享所有权)、 // Instant(帧时间戳/统计)。 use std::collections::HashMap; use std::mem; use std::os::fd::{AsFd, OwnedFd}; use std::os::unix::io::FromRawFd; use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Instant; // anyhow::Result 是 anyhow 提供的错误聚合 Result;用 `?` 传播多种错误类型很方便。 // 类比 Go 的 `if err := ...; err != nil { return err }`,但 Rust 的 `?` 是单字符运算符。 use anyhow::Result; // wayland_client:Wayland 协议客户端核心库。 // - ObjectId:Wayland 对象的服务端 ID(用于在 Dispatch user data 中查找映射)。 // - GlobalList/GlobalListContents:registry bind 后的全局对象列表(compositor 公告的服务)。 // - WlBuffer/WlOutput/WlRegistry:wl_* 是 Wayland 核心协议对象,由 wayland-scanner 自动生成。 // - Dispatch:核心 trait,类似 Go interface——实现 `Dispatch for State` 表示 State 能处理 I 的事件。 // - QueueHandle:事件队列句柄(类比 Go 的 channel 引用,但是 Wayland 协议层的)。 // - Proxy:所有 Wayland 客户端对象的 trait(提供 id()、version() 等)。 // - event_created_child:工厂函数,从现有对象创建子对象(用于 wl_registry.bind 后的 multi-version 兼容)。 use wayland_client::backend::ObjectId; use wayland_client::globals::{GlobalList, GlobalListContents}; use wayland_client::protocol::wl_buffer::WlBuffer; use wayland_client::protocol::wl_output::WlOutput; use wayland_client::protocol::wl_registry::WlRegistry; use wayland_client::{event_created_child, Dispatch, Proxy, QueueHandle}; // linux_dmabuf 协议(zero-copy GPU buffer 传递):BufferParams 用于构建 wl_buffer,Feedback 用于接收默认格式偏好。 use wayland_protocols::wp::linux_dmabuf::zv1::client::zwp_linux_buffer_params_v1::{ Event as BufferParamsEvent, Flags as BufferParamsFlags, ZwpLinuxBufferParamsV1, }; use wayland_protocols::wp::linux_dmabuf::zv1::client::zwp_linux_dmabuf_feedback_v1::{ Event as DmabufFeedbackEvent, ZwpLinuxDmabufFeedbackV1, }; use wayland_protocols::wp::linux_dmabuf::zv1::client::zwp_linux_dmabuf_v1::{ Event as DmabufEvent, ZwpLinuxDmabufV1, }; // xdg-output 协议:扩展 wl_output,提供逻辑坐标 / 子像素布局 / 名称等详细信息(Sway/Hyprland 路径)。 use wayland_protocols::xdg::xdg_output::zv1::client::zxdg_output_manager_v1::ZxdgOutputManagerV1; use wayland_protocols::xdg::xdg_output::zv1::client::zxdg_output_v1::{ Event as XdgOutputEvent, ZxdgOutputV1, }; // wlr-output-management 协议(wlroots 私有扩展):枚举输出及其几何信息(niri 路径,xdg-output 不可用时使用)。 use wayland_protocols_wlr::output_management::v1::client::zwlr_output_head_v1::{ self, Event as WlrHeadEvent, ZwlrOutputHeadV1, }; use wayland_protocols_wlr::output_management::v1::client::zwlr_output_manager_v1::{ self, Event as WlrOutputManagerEvent, ZwlrOutputManagerV1, }; use wayland_protocols_wlr::output_management::v1::client::zwlr_output_mode_v1::ZwlrOutputModeV1; // wlr-screencopy 协议(wlroots 私有扩展):核心截屏协议;Frame 对象每次 capture_output 时创建, // 用来接收 buffer 信息与 ready/failed 事件。 use wayland_protocols_wlr::screencopy::v1::client::zwlr_screencopy_frame_v1::{ Event as ScreencopyFrameEvent, ZwlrScreencopyFrameV1, }; use wayland_protocols_wlr::screencopy::v1::client::zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1; // ffmpeg_next as ff:FFmpeg 的 Rust 绑定(别名 ff 简化书写);ffi 是 FFmpeg C 原始 FFI 类型层。 use ffmpeg_next as ff; use ffmpeg_next::ffi; // 本 crate 内部模块: // - args::Args:CLI 参数(见 args.rs)。 // - avhw:硬件加速(VAAPI)+ 软件 H.264 编码器;EncState=MP4,SwEncState=WebRTC,EncodedH264Frame=NALU。 // - cap_wlr_screencopy::CapWlrScreencopy:当前唯一的 CaptureSource 具体实现。 // - fps_limit::FpsLimit:基于令牌桶的帧率限制器。 // - stats:流水线统计(采集/编码/发送耗时与队列深度)。 // - transform:屏幕方向变换(旋转/翻转);transpose_if_transform_transposed 在 90°/270° 时交换宽高。 // - webrtc::WebRtcState:内嵌的 str0m WebRTC 信令+媒体服务器状态机。 use crate::args::Args; use crate::avhw::{AvHwDevCtx, EncState, EncodedH264Frame, SwEncState}; use crate::cap_wlr_screencopy::CapWlrScreencopy; use crate::fps_limit::FpsLimit; use crate::stats::{FrameTimings, PipelineStats}; use crate::transform::{transpose_if_transform_transposed, Transform}; use crate::webrtc::WebRtcState; // --------------------------------------------------------------------------- // CaptureSource trait // --------------------------------------------------------------------------- // 截屏后端抽象。Rust trait 类比 Go interface,但需显式 `impl CaptureSource for CapWlrScreencopy`, // 而 Go 是隐式 duck-typed。当前唯一实现见 `cap_wlr_screencopy.rs`。 // // trait bound `Sized + 'static` 含义: // - `Sized`:编译期已知大小。Rust 默认所有类型 Sized;trait 对象默认 `?Sized` 才能用作 `dyn Trait`, // 这里要求 Sized 是因为 `State` 要直接持有 `cap: S`(值类型,不是 Box)。 // - `'static`:S 中不含任何非 `'static` 的借用引用;这样 S 才能被存入 `State` 并自由跨越线程/调用栈。 /// Screen capture backend trait. pub trait CaptureSource: Sized + 'static { // 关联类型(associated type)类比 Go interface 中的 type parameter: // `type Frame: Send;` 表示 "实现方需要指定一个 Frame 类型,且该类型必须可跨线程传递 (Send)"。 // CapWlrScreencopy 的 Frame 是封装好的 dmabuf fd + 元数据;用关联类型而非泛型参数让每个实现只有一种 Frame。 type Frame: Send; // 构造函数:在选定 output 后创建截屏后端。参数都是借用引用(&T),所以 trait bound 只需 Sized + 'static, // 不需要 'a 生命周期参数。QueueHandle> 中 Self 是实现 trait 的具体类型—— // 这是 Rust 的 Self 类型多态:trait 内可用 Self 指代"实现本 trait 的类型"。 fn new( gm: &GlobalList, output: &WlOutput, output_info: &OutputInfo, qh: &QueueHandle>, ) -> Result; // 分配一个新的截屏帧对象。返回 Option:分配失败时返回 None(例如 GPU 内存不足)。 // &mut self:独占可变借用(Rust 的 aliasing 规则:同一时刻只能有一个 &mut 或任意多个 &)。 fn alloc_frame(&mut self) -> Option; // 把 wl_buffer 提交给 compositor 请求截屏(非阻塞,结果通过 Dispatch 回调返回)。 // qh 用来在新创建的 frame proxy 上挂事件回调(类比 Go 的 channel 注册)。 fn queue_copy(&mut self, buffer: &WlBuffer, qh: &QueueHandle>); // 截屏完成(成功或失败)后回收 Frame 资源;放回池中或释放。frame 参数 by-value(move 语义), // 调用方此后不能再用 frame。 fn on_done_with_frame(&mut self, frame: Self::Frame); } // --------------------------------------------------------------------------- // Output info types // --------------------------------------------------------------------------- // 已选定 output 的最终信息(probe 阶段完成后才构造)。所有字段都非 Option,因为到了这一步必须确定。 // pub 字段表示对外可见(同级 crate 内可读写,例如 Dispatch 实现里需要读取 transform/physical_size)。 pub struct OutputInfo { // output 的友好名称(如 "eDP-1" / "HDMI-A-1");用于 --output-name 匹配与日志展示。 pub name: String, // 屏幕方向(旋转/翻转);编码时若 90°/270° 需要交换宽高,见 transpose_if_transform_transposed。 pub transform: Transform, // 物理尺寸(毫米),(width, height);用于计算 DPI,部分 compositor 可能给 0。 pub physical_size: (i32, i32), // 逻辑坐标 (x, y);多屏布局中 output 的左上角位置,用于区分不同 output。 pub logical_position: (i32, i32), } // Probe 阶段的 output 信息:每个字段都 Option,因为不同 Wayland 协议事件可能给不同字段、且到达时机不同。 // 字段会在 Dispatch / Dispatch / Dispatch 事件中逐步填充。 pub struct PartialOutputInfo { // 来自 xdg_output.name 事件(如果 compositor 支持 xdg-output);Sway/Hyprland 提供。 pub name: Option, /// Name from wl_output::Name (v4) — used to match wlr-output-management heads pub wl_name: Option, // 旋转/翻转信息;wl_output.geometry 给出,但有些 compositor(niri)需要从 wlr head 取。 pub transform: Option, // 物理尺寸(毫米),wl_output.geometry 提供。 pub physical_size: Option<(i32, i32)>, // 逻辑坐标;xdg_output.position 提供,或 wlr head position 提供。 pub logical_position: Option<(i32, i32)>, // Pixel dimensions from Mode event — preparatory for Phase 2 resolution logic pub mode_size: Option<(i32, i32)>, // 同一 output 的 wl_output 事件可能分多批到达;done 事件计数器,用于判断是否已收到完整信息。 pub done_count: u32, } // Default trait 实现类比 Go 的零值结构体初始化;这里所有字段都是 None 或 0。 // Rust 没有"构造函数",Default 是约定俗成的"默认值" trait,可用 PartialOutputInfo::default() 调用。 impl Default for PartialOutputInfo { fn default() -> Self { Self { name: None, wl_name: None, transform: None, physical_size: None, logical_position: None, mode_size: None, done_count: 0, } } } /// Stores head info from wlr-output-management for name-based matching with wl_output. // niri 不支持 xdg-output,只能用 wlr-output-management 协议拿 output 几何信息;这里只缓存 position。 struct WlrHeadInfo { position: Option<(i32, i32)>, } /// User data for XdgOutput dispatch to identify which WlOutput it belongs to. // Wayland Dispatch trait 的 user data(第二个泛型参数 UDATA)需要 'static + Send + Sync; // 这里 newtype 包裹 u32 是因为 Rust 不允许直接对外部类型(u32 是 i32/u32 不算外部)做 trait 实现, // 但用 newtype 模式可以让它有独立的类型标识(类比 Go 的 `type OutputId uint32`)。 pub struct OutputId(pub u32); // --------------------------------------------------------------------------- // StreamingEncoder // --------------------------------------------------------------------------- /// Wraps the two possible encoder backends for the streaming stage. /// /// - `Mp4(EncState)` — hardware VAAPI encoder writing to an MP4 file /// - `WebRtc(SwEncState)` — software encoder feeding H.264 NALUs into a WebRTC channel pub enum StreamingEncoder { Mp4(EncState), WebRtc(SwEncState), } impl StreamingEncoder { // 返回底层编码器的 AVHWFramesContext 指针;调用方用它分配硬件 frame(av_hwframe_get_buffer)。 // &self 表示共享借用(不可变);match 是 Rust 的模式匹配(类比 Go 的 type switch)。 fn frames_rgb(&self) -> &crate::avhw::AvHwFrameCtx { match self { // StreamingEncoder::Mp4(enc) 是 enum variant 模式;变量 enc 绑定内部 EncState 的引用。 StreamingEncoder::Mp4(enc) => enc.frames_rgb(), StreamingEncoder::WebRtc(enc) => enc.frames_rgb(), } } // 把硬件 frame 送给编码器;anyhow::Result<()> 是简写的错误类型。 // &mut self 因为编码器有内部状态(剩余比特率、参考帧队列等)。 fn encode_frame(&mut self, hw_frame: &ffmpeg_next::frame::Video) -> anyhow::Result<()> { match self { StreamingEncoder::Mp4(enc) => enc.encode_frame(hw_frame), StreamingEncoder::WebRtc(enc) => enc.encode_frame(hw_frame), } } // 编码器刷新(flush):把内部缓冲的剩余帧推到输出(MP4 文件结尾必须的步骤,否则 moov atom 不写)。 // pub 暴露给外层 main loop 在退出前调用。 pub fn flush(&mut self) -> anyhow::Result<()> { match self { StreamingEncoder::Mp4(enc) => enc.flush(), StreamingEncoder::WebRtc(enc) => enc.flush(), } } } // --------------------------------------------------------------------------- // EncConstructionStage // --------------------------------------------------------------------------- pub enum EncConstructionStage { // 第一阶段:枚举所有 wl_output,等待 done 事件收齐信息。 // 字段都 Option<...> 因为 Wayland global 可能未公告(例如老 compositor 没 wlr-output-management)。 ProbingOutputs { outputs: Vec, // 已绑定(bound)的 WlOutput proxy 列表;索引与 outputs 一一对应。 bound_outputs: Vec, // registry 中每个 global 的 u32 name(Wayland 用 u32 标识 global),与 outputs 索引对齐。 output_names: Vec, // 截屏协议管理器,bound 后用来 capture_output;None 表示 compositor 不支持 wlr-screencopy。 screencopy_manager: Option, // dmabuf 协议管理器;None 表示 compositor 不支持 zero-copy dmabuf(只能用 wl_shm,本项目不支持)。 dmabuf: Option, // dmabuf 默认格式偏好(v4+),用于接收 main_device 与 format_table。 dmabuf_feedback: Option, // xdg-output 管理器(Sway/Hyprland 路径);None 时改走 wlr-output-management 路径(niri)。 xdg_output_manager: Option, // wlr-output-management 管理器(niri 路径)。 wlr_output_manager: Option, // wlr-output-manager 的 done 事件是否已收到(head 信息完整)。 wlr_manager_done: bool, // name → WlrHeadInfo 的映射;从 wlr head 事件收集,用于按名称与 wl_output 配对。 wlr_heads: HashMap, // head proxy ObjectId → name 的反向映射;Dispatch 中根据 proxy id 查 name。 wlr_head_proxy_to_name: HashMap, }, // 第二阶段:output 已选定,硬件设备上下文(VAAPI)已构造,截屏后端 S 已创建;但编码器未构造, // 因为编码器需要捕获第一帧拿到 dmabuf format 后才能初始化。negotiate_format() 推进到下一阶段。 EverythingButFmt { output_info: OutputInfo, output: WlOutput, // VAAPI 硬件设备上下文,封装 AVHWDeviceContext;MP4 模式下被 EncState 消费。 hw_device_ctx: AvHwDevCtx, // 截屏后端实例(CapWlrScreencopy 或未来其它实现)。 cap: S, screencopy_manager: ZwlrScreencopyManagerV1, dmabuf: ZwpLinuxDmabufV1, }, // 第三阶段(运行态):编码器已构造,开始 capture→encode→send 循环。 Streaming { output_info: OutputInfo, output: WlOutput, // 编码器封装(MP4 硬件编码器 或 WebRTC 软件编码器,二选一)。 enc: StreamingEncoder, cap: S, screencopy_manager: ZwlrScreencopyManagerV1, dmabuf: ZwpLinuxDmabufV1, }, // 阶段转换中的临时状态(mem::replace 把旧值取出、塞入 Intermediate,再构造新阶段)。 // 类比 Go 的 "transition" 占位值;调用方必须在同一调用内把它替换为真正的下一阶段。 Intermediate, } // --------------------------------------------------------------------------- // InFlightSurface // --------------------------------------------------------------------------- // 跟踪"当前正在飞的"截屏帧的状态机。从 None 开始,经过 AllocQueued→Allocd→CopyQueued→(回到 None)。 // enum variant 中带数据的(如 CopyQueued)类似 Go 的 struct,但 Rust 中 enum 可以持有任意类型。 pub enum InFlightSurface { // 空闲态,可以发起新一帧。 None, // 已请求 compositor 创建 frame proxy,等待 frame 的 buffer 信息事件。 AllocQueued, // 收到 buffer 信息,已分配 S::Frame;下一步要分配 wl_buffer。 Allocd(S::Frame), // 已构造 wl_buffer 并发起了 capture_output 请求;等待 Ready/Failed 事件。 // 持有所有必须的资源(surface、drm_map、frame、buffer),事件到达后用来释放/编码。 CopyQueued { // FFmpeg 视频 frame(GPU surface),编码器从这里读。 surface: ff::frame::Video, // DMA-BUF 描述符(layer/plane/fd/modifier),保留它以便 wl_buffer 销毁前 desc 中数据有效。 drm_map: ff::ffi::AVDRMFrameDescriptor, // 截屏后端自己的 frame 资源(CapWlrScreencopy 的 Dmabuf),完成后归还给池。 frame: S::Frame, // 已提交给 compositor 的 wl_buffer;事件回来后必须 destroy。 buffer: WlBuffer, }, } // --------------------------------------------------------------------------- // State // --------------------------------------------------------------------------- // 主状态机:泛型 表示截屏后端的具体类型。S 作为值类型字段(cap: S)直接持有时, // 必须 Sized;又因为 State 需要进入 Wayland Dispatch 路径,S 还需要 'static。 // 所有字段都 pub(crate 内可见),少数几个只在本文件用的字段例外。 pub struct State { // 阶段机:构造时为 ProbingOutputs,negotiate_format 后切到 Streaming。 pub stage: EncConstructionStage, // 当前截屏帧的状态机(None/AllocQueued/Allocd/CopyQueued)。 pub in_flight_surface: InFlightSurface, // 第一帧的捕获时间戳,用于后续帧的 PTS 偏移计算(保留 None 直到第一帧 ready)。 pub starting_timestamp: Option, // 统计区间的起始时间(用于 10 秒一次的 fps 报告)。 pub stats_start_time: Option, // 上次报告统计的时间;elapsed() >= 10s 时触发新一次报告。 pub stats_last_time: Option, // 自上次报告以来编码的帧数;每次报告后清零。 pub stats_frames: u64, // 是否为第一帧的标志;首帧一定送编码器(不受 fps_limit 限制),之后按 fps_limit 节流。 pub first_frame: bool, // CLI 参数(fps, bitrate, output_name, transform, ...);运行期只读。 pub args: Args, // 致命错误标志;任意一处检测到后置 true,外层 main loop 退出。 pub errored: bool, // Wayland global 列表(registry bind 后的全局对象快照),用来再次 bind 单输出或新协议。 pub gm: GlobalList, // 帧率限制器,缓存跳过的帧以备用;泛型 因为限制器需要"占位"帧来调度。 pub fps_limit: FpsLimit, // Wayland 事件队列句柄,所有 bind/create 调用都需要它挂回调。 pub qhandle: QueueHandle>, // 用户通过 --drm-device 指定的 DRM 设备路径;None 时优先用 compositor 提供的,再回退到自动扫描。 pub drm_device: Option, // 从 dmabuf feedback.main_device 推断的 DRM 设备路径(compositor 推荐的 GPU)。 pub drm_device_from_compositor: Option, // WebRTC 服务器状态机(仅在 --port > 0 时构造);None 表示 MP4 模式。 pub webrtc: Option, // 编码后的 H.264 NALU 通过此 channel 发送给 WebRTC 线程;None 表示 MP4 模式。 pub webrtc_tx: Option>, // WebRTC 线程编码后回传的 NALU channel;poll_webrtc() 中 try_recv 取出并送 str0m。 // 字段无 pub 前缀:本模块外不可访问(信息隐藏)。 webrtc_rx: Option>, // 已发送到 WebRTC 的帧数;用于日志/统计。saturating_add 防止 u64 溢出。 webrtc_frames_sent: u64, // WebRTC 暂停标志,跨线程共享(Arc)。 // - 编码线程在没客户端时 set true(跳过昂贵编码) // - WebRTC 信令线程在 ICE 连接成功后 set false // Ordering::Relaxed:用于"best-effort 暂停",不需要严格内存序(性能优先)。 webrtc_paused: Option>, // 流水线统计(采集/编码/发送的耗时与队列深度),每 N 秒 snapshot 一次写日志。 stats: PipelineStats, } // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- /// Scan /dev/dri for all available DRM render nodes (renderD*), sorted by node number. // pub(crate) 表示对 crate 内可见(类比 Go 的大写首字母导出,但更细粒度:crate 是 module 模型中的"包")。 // 返回 Vec 是拥有所有权的动态数组(Vec类比 Go 的 []T 切片但 Rust 中长度+容量都是字段)。 pub(crate) fn find_drm_render_nodes() -> Vec { // let-else 模式(Rust 2021 引入):匹配失败时执行 else 块(这里直接 return)。 // 类比 Go 的 `if err != nil { return }` 但更紧凑。read_dir 返回 io::Result。 let Ok(entries) = std::fs::read_dir("/dev/dri") else { return Vec::new(); }; // entries 是 ReadDir 迭代器;filter_map 同时做 filter(去除 None)和 map(提取值)。 // 每一步 ? 都是 Option 的早期返回(None 表示"这不是 renderD 节点")。 // Result::ok 把 io::Result 转为 Option(错误也跳过)。 let mut nodes: Vec<(u32, PathBuf)> = entries .filter_map(Result::ok) .filter_map(|entry| { let path = entry.path(); // ? 在 Option 上下文里:file_name() 返回 Option,to_str() 返回 Option<&str>。 let name = path.file_name()?.to_str()?; // strip_prefix("renderD") 返回 Option<&str>,再 parse::() 转 u32;任意一步失败返回 None。 let number = name.strip_prefix("renderD")?.parse::().ok()?; // 跳过不存在的(例如已被 hot-unplug 的设备)。 std::fs::metadata(&path).ok()?; Some((number, path)) }) .collect(); // 按节点号升序排序(renderD128 在前,renderD129 在后)。 nodes.sort_by_key(|(number, _)| *number); // 元组解构 + into_iter 把 Vec<(u32, PathBuf)> 转成迭代器,map 抹掉 u32 后 collect 成 Vec。 nodes.into_iter().map(|(_, path)| path).collect() } /// Scan /dev/dri for the first available DRM render node (renderD*). // 包装 find_drm_render_nodes(),取第一个(即节点号最小的,通常是 renderD128)。 // 返回 Option:None 表示 /dev/dri 下没有 renderD*(极少见,说明系统无 GPU)。 fn find_drm_render_node() -> Option { find_drm_render_nodes().into_iter().next() } // 第一个 impl 块:DRM 设备路径解析。impl State 表示"为所有实现了 // CaptureSource 的类型 S,定义 State 的方法"——这是 Rust 泛型 impl 语法(类比 Go 的泛型 receiver)。 impl State { // 解析最终使用的 DRM 设备路径,优先级:用户 --drm-device > compositor 推荐 > 自动扫描 > 默认 /dev/dri/renderD128。 // &self 表示共享借用(不可变);返回 PathBuf 是拥有所有权的路径。 fn resolve_drm_path(&self) -> PathBuf { self.drm_device .clone() // or_else 是 Option 的方法:当前是 None 时调用闭包产生新值;不会浪费已有的 Some。 .or_else(|| self.drm_device_from_compositor.clone()) // or_else 传入函数(惰性求值):仅在 None 时调用 find_drm_render_node()。 .or_else(find_drm_render_node) // unwrap_or_else:None 时调用闭包;这里给出兜底默认值。 .unwrap_or_else(|| PathBuf::from("/dev/dri/renderD128")) } } // --------------------------------------------------------------------------- // State methods // --------------------------------------------------------------------------- // 第二个 impl 块:State 的主方法集合(构造、Wayland global 绑定、帧生命周期、WebRTC polling、 // 格式协商、output 终结)。这里的方法都被 main loop 或 Dispatch 实现调用。 impl State { // 构造函数:从 GlobalList(registry 已 roundtrip 完成的快照)+ CLI Args + Wayland QueueHandle 开始。 // 返回 Result:WebRtcState::new 失败时(端口被占用等)通过 ? 传播错误。 pub fn new(gm: GlobalList, args: Args, qhandle: QueueHandle>) -> Result { // 取出 fps 字段值(u32 Copy 类型,直接拷贝);后面 FpsLimit::new(fps) 需要。 let fps = args.fps; // args.drm_device 是 Option(来自 CLI)。as_ref() 把 Option 借用为 Option<&String>, // map(PathBuf::from) 把 &String 转成拥有所有权的 PathBuf;保留原 args 不动。 let drm_device = args.drm_device.as_ref().map(PathBuf::from); // 根据 args.port 决定模式:> 0 为 WebRTC(推流),= 0 为 MP4(写文件)。 // 元组解构把 4 个变量一次性取出(Rust 元组类比 Go 的多返回值)。 let (webrtc, webrtc_tx, webrtc_rx, webrtc_paused) = if args.port > 0 { // crossbeam_channel::bounded(32):MPMC channel,容量 32(背压控制)。 // 类比 Go 的 `make(chan EncodedH264Frame, 32)`。 let (tx, rx) = crossbeam_channel::bounded(32); // ? 传播 WebRTC 服务器构造错误(例如端口绑定失败)。 let wrtc = WebRtcState::new(args.port, args.fps)?; // paused=true until first WebRTC client connects // AtomicBool::new(true) 初始暂停;Arc 共享所有权给编码线程与信令线程。 let paused = Arc::new(AtomicBool::new(true)); (Some(wrtc), Some(tx), Some(rx), Some(paused)) } else { (None, None, None, None) }; // struct 字面量构造 Self(这里 Self = State)。mut 让后续 bind_initial_globals 能改 self。 let mut state = Self { stage: EncConstructionStage::ProbingOutputs { outputs: Vec::new(), bound_outputs: Vec::new(), output_names: Vec::new(), screencopy_manager: None, dmabuf: None, dmabuf_feedback: None, xdg_output_manager: None, wlr_output_manager: None, wlr_manager_done: false, wlr_heads: HashMap::new(), wlr_head_proxy_to_name: HashMap::new(), }, in_flight_surface: InFlightSurface::None, starting_timestamp: None, stats_start_time: None, stats_last_time: None, stats_frames: 0, first_frame: true, fps_limit: FpsLimit::new(fps), args, errored: false, gm, qhandle, drm_device, drm_device_from_compositor: None, webrtc, webrtc_tx, webrtc_rx, webrtc_frames_sent: 0, webrtc_paused, stats: PipelineStats::new(), }; // registry_queue_init consumes registry events internally during its // initial roundtrip and does NOT forward them to our Dispatch impl. // We must manually bind the initial globals here. // 关键架构点:wayland-client 的 registry_queue_init 在内部 roundtrip 中消费了 global 公告事件, // 不会传给我们自己的 Dispatch;因此必须在此手动遍历 GlobalList 绑定所需协议。 state.bind_initial_globals(); // Ok(state) 把 state 包成 Result;构造成功。 Ok(state) } /// Iterate over the GlobalList from registry_queue_init and bind all /// globals we care about. This is necessary because registry_queue_init /// consumes registry events during its internal roundtrip without forwarding /// them to our Dispatch handler. // 遍历 GlobalList,按依赖顺序绑定 Wayland 协议对象。&mut self 因为要更新 stage 字段。 fn bind_initial_globals(&mut self) { // 函数内 use 把 trait 引入局部作用域(Rust 必须显式导入 trait 才能调用其方法)。 use wayland_client::globals::Global; // clone_list() 拷贝出所有 global;Vec 拥有所有权。 let globals: Vec = self.gm.contents().clone_list(); // registry 是 wl_registry proxy,bind() 用来创建具体的协议对象。 let registry = self.gm.registry(); // &self.qhandle 借用 QueueHandle;所有 bind 调用都需要它来挂事件回调。 let qhandle = &self.qhandle; // Sort globals so that managers are bound BEFORE wl_output. // This ensures xdg_output_manager and zwlr_output_manager are available // when we bind wl_output, so we can immediately get xdg_output / wlr head. // 关键排序:manager 必须先于 wl_output 绑定,否则绑定 wl_output 时还没法 get_xdg_output。 // 块表达式 { ... } 让 priority 函数是局部的,外层 globals 在块结束时被 g 覆写。 let globals = { // 局部 priority 函数:根据接口名返回排序优先级(0=最先,3=最后)。 fn priority(interface: &str) -> u8 { match interface { "zwlr_screencopy_manager_v1" => 0, "zwp_linux_dmabuf_v1" => 0, "zxdg_output_manager_v1" => 1, "zwlr_output_manager_v1" => 1, "wl_output" => 2, _ => 3, } } let mut g = globals; // sort_by_key 是稳定排序;闭包 |g| priority(&g.interface) 提取排序键。 g.sort_by_key(|g| priority(&g.interface)); g }; // for 循环解构 Global struct 的字段(Rust 支持模式匹配中的 struct 解构)。 for Global { name, interface, version, } in globals { // match interface.as_str():&interface 是 String,as_str() 借用为 &str 才能用模式匹配字面量。 match interface.as_str() { "zwlr_screencopy_manager_v1" => { // 取 min(协议支持版本, 我们想要的版本)——避免版本不兼容。 let v = version.min(3); tracing::debug!("Init: binding zwlr_screencopy_manager_v1 v{v} (name={name})"); // registry.bind(name, version, qh, user_data) 创建协议 proxy; // 类型注解 : ZwlrScreencopyManagerV1 让 Rust 推断 bind 的返回类型。 let mgr: ZwlrScreencopyManagerV1 = registry.bind(name, v, qhandle, ()); // 模式匹配 + .. 通配符忽略其他字段;*screencopy_manager = Some(mgr) 写回 stage。 if let EncConstructionStage::ProbingOutputs { screencopy_manager, .. } = &mut self.stage { *screencopy_manager = Some(mgr); } } "zwp_linux_dmabuf_v1" => { let v = version.min(4); tracing::debug!("Init: binding zwp_linux_dmabuf_v1 v{v} (name={name})"); let proxy: ZwpLinuxDmabufV1 = registry.bind(name, v, qhandle, ()); if let EncConstructionStage::ProbingOutputs { dmabuf, dmabuf_feedback, .. } = &mut self.stage { *dmabuf = Some(proxy.clone()); // v4+ 支持 feedback(默认格式偏好);旧版本只能用 main_surface 协商。 if v >= 4 { let feedback = proxy.get_default_feedback(qhandle, ()); *dmabuf_feedback = Some(feedback); } } } "zxdg_output_manager_v1" => { let v = version.min(3); tracing::debug!("Init: binding zxdg_output_manager_v1 v{v} (name={name})"); let xdg_mgr: ZxdgOutputManagerV1 = registry.bind(name, v, qhandle, ()); if let EncConstructionStage::ProbingOutputs { bound_outputs, xdg_output_manager, output_names, .. } = &mut self.stage { // 对之前已绑定的每个 wl_output,立即获取 xdg_output(拿到详细几何信息)。 for (i, output) in bound_outputs.iter().enumerate() { // output_names.get(i).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); } *xdg_output_manager = Some(xdg_mgr); } } "zwlr_output_manager_v1" => { let v = version.min(4); tracing::debug!("Init: binding zwlr_output_manager_v1 v{v} (name={name})"); let mgr: ZwlrOutputManagerV1 = registry.bind(name, v, qhandle, ()); if let EncConstructionStage::ProbingOutputs { wlr_output_manager, .. } = &mut self.stage { *wlr_output_manager = Some(mgr); } } "wl_output" => { let v = version.min(4); tracing::debug!("Init: binding wl_output v{v} (name={name})"); // OutputId(name) 是 newtype 构造(包一层 user data),让 Dispatch 能识别这是哪个 output。 let output: WlOutput = registry.bind(name, v, qhandle, OutputId(name)); if let EncConstructionStage::ProbingOutputs { outputs, bound_outputs, output_names, xdg_output_manager, .. } = &mut self.stage { outputs.push(PartialOutputInfo::default()); // output.clone() 因为后面还要在 self.stage 里 push,所有权不能转移走。 bound_outputs.push(output.clone()); output_names.push(name); // 如果 xdg-output manager 已经绑定,立即给新 output 注册 xdg_output。 if let Some(xdg_mgr) = xdg_output_manager { let output_id = OutputId(name); xdg_mgr.get_xdg_output(&output, qhandle, output_id); } } } // _ => {} 是 match 的 wildcard arm:忽略未识别的协议(不需要 compositor 不支持的)。 _ => {} } } } // 请求 compositor 创建一个截屏 frame proxy。要求 `State: Dispatch` —— // 这是 trait bound 在泛型方法上的 where 子句约束(类比 Go 泛型的 `T where T implements X`)。 // 只有 State 的具体类型(State)实际实现了 Dispatch。 pub fn queue_alloc_frame(&mut self) where State: Dispatch, { // 从 stage 中取 manager 与 output。match 返回元组,若不在 Streaming/EverythingButFmt 阶段直接 return。 let (manager, output) = match &self.stage { EncConstructionStage::Streaming { screencopy_manager, output, .. } => (screencopy_manager.clone(), output.clone()), EncConstructionStage::EverythingButFmt { screencopy_manager, output, .. } => (screencopy_manager.clone(), output.clone()), // 其他阶段直接 return(用 match 的 _ arm 表达"否则")。 _ => return, }; // 若当前已有帧在飞,跳过这次 alloc(防重入)。 match &self.in_flight_surface { InFlightSurface::None => {} _ => return, } // capture_output(overlay, output, qh, user_data):发起 capture 请求。 // 第一个参数 1 是 overlay 模式标志(wlr-screencopy v1 协议常量)。 // _frame_proxy 是临时 proxy;Rust 中下划线前缀表示"故意丢弃",对象在帧处理完会被 destroy。 let _frame_proxy = manager.capture_output(1, &output, &self.qhandle, ()); self.in_flight_surface = InFlightSurface::AllocQueued; } // 收到 frame 的 buffer 信息事件后调用。构造 wl_buffer(dma-buf 描述符)并触发 compositor copy。 // format/width/height 是 compositor 报告的可用 dmabuf 格式(如 DRM_FORMAT_XRGB8888)。 pub fn on_frame_allocd(&mut self, frame: S::Frame, format: u32, width: u32, height: u32) { // 从 Streaming 阶段取三个资源:硬件 frame ctx(用来 alloc GPU surface)、dmabuf 协议、cap 后端。 let (frames_rgb_ctx, dmabuf, cap) = match &mut self.stage { EncConstructionStage::Streaming { output_info: _, output: _, enc, dmabuf, cap, screencopy_manager: _, } => (enc.frames_rgb().as_ptr(), dmabuf, cap), _ => { tracing::warn!("on_frame_allocd: not in Streaming stage"); return; } }; // 分配空的 FFmpeg Video frame;后续 av_hwframe_get_buffer 会填充 GPU 数据。 let mut surface = ff::frame::Video::empty(); // 中文概述:调用 FFmpeg FFI 在硬件 frames 池中分配一个 GPU surface。 // SAFETY: frames_rgb_ctx is a valid AVHWFramesContext pointer; surface // is a freshly allocated empty Video frame. let ret = unsafe { ffi::av_hwframe_get_buffer(frames_rgb_ctx, surface.as_mut_ptr(), 0) }; if ret < 0 { tracing::error!("av_hwframe_get_buffer failed: {}", crate::avhw::ff_err(ret)); self.errored = true; return; } let mut map_frame = ff::frame::Video::empty(); // 中文概述:把 surface 格式设为 DRM_PRIME,然后用 av_hwframe_map 把 GPU surface 映射成 // 含 dma-buf fd 的 frame(dma-buf 是 Linux 的跨进程 GPU 缓冲共享机制)。 // SAFETY: Setting format to DRM_PRIME and calling av_hwframe_map creates // a mapped view of the GPU surface with DMA-BUF file descriptors. unsafe { (*map_frame.as_mut_ptr()).format = ffi::AVPixelFormat::AV_PIX_FMT_DRM_PRIME as i32; } let ret = unsafe { ffi::av_hwframe_map(map_frame.as_mut_ptr(), surface.as_ptr(), 0) }; if ret < 0 { tracing::error!("av_hwframe_map failed: {}", crate::avhw::ff_err(ret)); self.errored = true; return; } // 中文概述:从 map_frame 中读出 AVDRMFrameDescriptor(含 layer/plane/fd/modifier 结构)。 // SAFETY: After av_hwframe_map with DRM_PRIME format, data[0] points to // a valid AVDRMFrameDescriptor. let desc: ff::ffi::AVDRMFrameDescriptor = unsafe { let desc_ptr = (*map_frame.as_ptr()).data[0] as *const ff::ffi::AVDRMFrameDescriptor; // std::ptr::read 是 unsafe 的"按位复制"——绕过 Drop 语义;这里 read 出来的 desc 是栈值, // map_frame Drop 时不会影响 desc 的内容(fd 仍是 desc 的副本)。 std::ptr::read(desc_ptr) }; // 创建 dmabuf params 对象(用于构造 wl_buffer)。 let params = dmabuf.create_params(&self.qhandle, ()); // 双层循环:遍历所有 layer(多平面格式如 NV12 有 2 个 layer)和 plane(每层若干 plane)。 for layer_idx in 0..desc.nb_layers as usize { let layer = &desc.layers[layer_idx]; for p in 0..layer.nb_planes as usize { let plane = &layer.planes[p]; let obj = &desc.objects[plane.object_index as usize]; // modifier 是 64 位的 tiling/compression 标识;split 成两个 u32 给 wayland 协议(32 位 RPC)。 let mod_hi = (obj.format_modifier >> 32) as u32; let mod_lo = (obj.format_modifier & 0xFFFF_FFFF) as u32; // 中文概述:libc::dup 复制 dma-buf fd,因为 params.add 会接管 fd 所有权, // 而原 fd 属于 map_frame(不能让它被 Drop 走)。 // SAFETY: obj.fd is a valid DMA-BUF fd. We dup because params.add() // takes ownership of the fd, and the original fd is owned by map_frame. let fd_dup = unsafe { libc::dup(obj.fd) }; if fd_dup < 0 { tracing::error!( "failed to dup dma-buf fd: {}", std::io::Error::last_os_error() ); // wayland-client does not auto-destroy params on Drop. params.destroy(); self.errored = true; return; } // 中文概述:把 raw i32 fd 包装成 OwnedFd(RAII,离开作用域自动 close)。 // SAFETY: fd_dup is valid freshly-duped fd. let fd_owned = unsafe { OwnedFd::from_raw_fd(fd_dup) }; params.add( // as_fd() 借用为 BorrowedFd(类比 Rust 的 AsFd trait,零成本)。 fd_owned.as_fd(), p as u32, plane.offset as u32, plane.pitch as u32, mod_hi, mod_lo, ); } } // create_immed 立即构造 wl_buffer(不通过事件回调);BufferParamsFlags::empty() 表示无标志位。 let wl_buffer = params.create_immed( width as i32, height as i32, format, BufferParamsFlags::empty(), &self.qhandle, (), ); // 保存 surface/desc/frame/buffer 到状态机,等 Ready 事件回来后用来编码。 self.in_flight_surface = InFlightSurface::CopyQueued { surface, drm_map: desc, frame, buffer: wl_buffer, }; // 模式匹配拿出 buffer 引用(借用,不 move),交给 cap.queue_copy 发起 capture 请求。 let buffer_ref = match &self.in_flight_surface { InFlightSurface::CopyQueued { buffer, .. } => buffer, // unreachable! 是"不可达"宏;调用即 panic。这里因为刚刚赋值,不可能落到别的 arm。 _ => unreachable!("just set to CopyQueued"), }; cap.queue_copy(buffer_ref, &self.qhandle); } // compositor 报告 capture 完成后调用(tv_sec/tv_usec 是捕获时刻)。 // where S::Frame: Default:trait bound 在方法上,因为 fps_limit 需要构造 placeholder frame。 pub fn on_copy_complete(&mut self, tv_sec: u64, tv_usec: u32) where S::Frame: Default, { // 记录采集耗时到统计。 self.stats.record_capture(); // mem::replace 取走 in_flight_surface 的值并塞入 None(语义上类似 Go 的 "swap")。 // 这里因为 in_flight_surface 必须 reset 回 None,且我们要消费旧值(包括 WlBuffer destroy)。 let (mut surface, _drm_map, frame, buffer) = match mem::replace(&mut self.in_flight_surface, InFlightSurface::None) { InFlightSurface::CopyQueued { surface, drm_map, frame, buffer, } => (surface, drm_map, frame, buffer), // 不是 CopyQueued 时把原值放回,直接 return(防御式编程)。 other => { tracing::warn!("on_copy_complete: unexpected state"); self.in_flight_surface = other; return; } }; // PTS in 90kHz media-clock ticks (WebRTC encoder time_base = 1/90000). // Must match Portal path's compute_capture_pts unit. See issue #25. // PTS 是 90kHz 时钟的 tick(WebRTC encoder 的 time_base 是 1/90000 秒)。 // 必须与 Portal 路径的 PTS 单位保持一致(参见 issue #25)。 let pts = (tv_sec as i64) * 90_000 + (tv_usec as i64) * 90_000 / 1_000_000; surface.set_pts(Some(pts)); // drop(buffer) 显式销毁 wl_buffer(libwayland-client 的 proxy 不会自动 destroy)。 // 类比 Go 的 `defer buffer.Destroy()`;但 Rust 的 drop 是即时的。 drop(buffer); // 再次 match stage 拿 cap(因为前一次 match 用了 &mut self.stage 已结束借用)。 let cap = match &mut self.stage { EncConstructionStage::Streaming { cap, .. } => cap, _ => { tracing::warn!("on_copy_complete: not in Streaming stage"); return; } }; // 把 frame 归还给 cap(回到池中复用,避免反复 GPU 内存分配)。 cap.on_done_with_frame(frame); // 再拿 enc(同理,独立 match 因为前面的 &mut self.stage 已结束)。 let enc = match &mut self.stage { EncConstructionStage::Streaming { enc, .. } => enc, _ => unreachable!("already checked Streaming above"), }; // 决定是否真编码这一帧:第一帧必编(初始化编码器状态),之后按 fps_limit 决定。 let should_encode = if self.first_frame { self.first_frame = false; true } else { // on_new_frame 返回 Option——Some 表示该帧通过节流,None 表示跳过。 self.fps_limit .on_new_frame(S::Frame::default(), Instant::now()) .is_some() }; if should_encode { let encode_start = Instant::now(); // if let Err(e) 模式:仅匹配 Err 分支,绑定错误到 e。 if let Err(e) = enc.encode_frame(&surface) { tracing::error!("encode_frame failed: {}", e); self.errored = true; } let encode_elapsed = encode_start.elapsed().as_micros() as u64; // ..Default::default() 是结构体更新语法:其它字段用 Default::default() 填充。 self.stats.record_encode(&FrameTimings { total_us: encode_elapsed, ..Default::default() }); } self.stats_frames += 1; // 每 10 秒报告一次 fps 统计。 if let Some(last) = self.stats_last_time { if last.elapsed() >= std::time::Duration::from_secs(10) { let delta = self.stats_frames; let fps = delta as f64 / last.elapsed().as_secs_f64(); // tracing::info! 宏的 key=value 参数:日志结构化字段(类比 Go slog 的 attr)。 tracing::info!( frames = self.stats_frames, fps = format!("{fps:.1}"), "encoding stats" ); self.stats_last_time = Some(std::time::Instant::now()); self.stats_frames = 0; } } else { self.stats_start_time = Some(std::time::Instant::now()); self.stats_last_time = Some(std::time::Instant::now()); } } // compositor 报告 capture 失败时调用。释放 in_flight_surface 资源并把 frame 还给 cap。 pub fn on_copy_fail(&mut self) where S::Frame: Default, { tracing::error!("compositor copy failed"); // 取走 in_flight_surface;用 .. 忽略其它字段,只关心 buffer 和 frame。 let taken = mem::replace(&mut self.in_flight_surface, InFlightSurface::None); match taken { InFlightSurface::CopyQueued { buffer, frame, .. } => { drop(buffer); // 仅在 Streaming 阶段归还 frame(其它阶段 frame 可能不属于当前 cap)。 if let EncConstructionStage::Streaming { cap, .. } = &mut self.stage { cap.on_done_with_frame(frame); } } // 不是 CopyQueued 时把原值放回(防御式:不该发生但保护数据)。 other => { self.in_flight_surface = other; } } self.errored = true; } // 主循环每次迭代调用一次:推进 WebRTC 信令、推送 NALU、维护连接状态。 // 返回 Result<()>:错误会让主循环退出。 pub fn poll_webrtc(&mut self) -> Result<()> { // let-else 在 Option 上下文:self.webrtc 是 None 时直接 return Ok(())(MP4 模式无需 WebRTC)。 let Some(ref mut wrtc) = self.webrtc else { return Ok(()); }; // 推进 SDP/ICE 信令(str0m 内部状态机),把待发媒体 packet 发给客户端。 wrtc.handle_signaling()?; wrtc.poll_and_feed()?; // 是否有 ICE 连接成功的客户端(决定是否暂停编码以省 CPU)。 let connected = wrtc.is_connected(); if let Some(ref paused) = self.webrtc_paused { // load/store 配 Ordering::Relaxed:本字段是 best-effort 暂停标志,不需要严格同步。 let was_paused = paused.load(Ordering::Relaxed); let now_paused = !connected; if was_paused && !now_paused { tracing::info!("WebRTC client connected, resuming encoding"); } else if !was_paused && now_paused { tracing::warn!("WebRTC client disconnected, pausing encoding"); } paused.store(now_paused, Ordering::Relaxed); } // 从 webrtc_rx 拉出所有可用的编码 NALU,转发给 str0m。 if let Some(ref rx) = self.webrtc_rx { let mut count = 0u32; // while let Ok(...) try_recv:非阻塞循环,直到 channel 空时退出。 while let Ok(enc_frame) = rx.try_recv() { if !connected { // 客户端断开时丢弃 frame(避免 backlog 堆积)。 continue; } count += 1; if let Err(e) = wrtc .write_h264_frame(&enc_frame.data, enc_frame.pts_ticks) { tracing::debug!("WebRTC write frame error: {e}"); } self.stats.record_send(0.0, None); // saturating_add:u64 溢出时停在 MAX(避免回绕,统计更可靠)。 self.webrtc_frames_sent = self.webrtc_frames_sent.saturating_add(1); } if count > 0 { tracing::debug!("WebRTC forwarded {count} frames from channel"); } } // 周期性 snapshot 统计(PipelineStats 内部判断是否到了报告时刻)。 if self.args.stats && self.stats.should_snapshot() { self.stats .set_queue_depths(0, self.webrtc_rx.as_ref().map(|r| r.len()).unwrap_or(0)); let snap = self.stats.snapshot_and_reset(); tracing::info!("stats: {snap}"); } Ok(()) } // 收到第一帧的 dmabuf format 后,构造编码器并把 stage 切到 Streaming。 // format/width/height 来自 Dispatch::Buffer 事件。 pub fn negotiate_format(&mut self, format: u32, width: u32, height: u32) { // mem::replace 把 stage 切到 Intermediate 暂存值;这是 Rust 中"取走 enum 旧值再构造新值"的标准模式, // 避免在 match arm 内同时持有 &mut self.stage 的可变借用与匹配出的字段引用。 let stage_data = match mem::replace(&mut self.stage, EncConstructionStage::Intermediate) { EncConstructionStage::EverythingButFmt { output_info, output, hw_device_ctx, cap, screencopy_manager, dmabuf, } => ( output_info, output, hw_device_ctx, cap, screencopy_manager, dmabuf, ), other => { tracing::warn!("negotiate_format: not in EverythingButFmt stage"); // 把 stage 还原回去(否则 Intermediate 会留下数据丢失)。 self.stage = other; return; } }; let (output_info, output, hw_device_ctx, cap, screencopy_manager, dmabuf) = stage_data; let drm_path = self.resolve_drm_path(); let fps = self.args.fps; // bitrate 默认值:2 × 像素数 × fps / 100(启发式:约 1.6Mbps @ 1080p30)。 let bitrate = self .args .bitrate .unwrap_or_else(|| 2 * (width as u64) * (height as u64) * (fps as u64) / 100); // 根据 webrtc_tx 是否存在,构造软件(WebRTC)或硬件(MP4)编码器。 let enc = if let Some(ref tx) = self.webrtc_tx { // 若屏幕旋转了 90/270 度,编码器输出宽高需要交换。 let (enc_w, enc_h) = transpose_if_transform_transposed( output_info.transform, width as i32, height as i32, ); // GOP 默认 = 2×fps(每两秒一个 I 帧)。 let actual_gop_size = self.args.gop_size.unwrap_or((fps * 2).max(20)); match SwEncState::new_webrtc( &drm_path, width, height, enc_w as u32, enc_h as u32, fps, bitrate, actual_gop_size, // tx.clone() 克隆 channel sender(Arc 共享底层 channel)。 tx.clone(), // expect() 在 None 时 panic;这里运行期不变量保证 webrtc_tx 存在时 webrtc_paused 也存在。 self.webrtc_paused .as_ref() .expect("webrtc_paused must exist when webrtc_tx exists") .clone(), ) { Ok(enc) => StreamingEncoder::WebRtc(enc), Err(e) => { tracing::error!("SwEncState::new_webrtc failed: {}", e); self.errored = true; return; } } } else { // as_deref() 把 Option 转 Option<&str>(零成本借用)。 let output_path = self .args .output .as_deref() .expect("output required for MP4 mode"); match crate::avhw::create_encoder( &drm_path, Path::new(output_path), width, height, fps, output_info.transform, self.args.bitrate, self.args.gop_size, Some(hw_device_ctx), ) { Ok(enc) => StreamingEncoder::Mp4(enc), Err(e) => { tracing::error!("EncState::new failed: {}", e); self.errored = true; return; } } }; tracing::info!( "Encoder initialized: {}x{} format={} bitrate={}", width, height, format, bitrate ); // 把 stage 切到 Streaming,开始 capture→encode 循环。 self.stage = EncConstructionStage::Streaming { output_info, output, enc, cap, screencopy_manager, dmabuf, }; } // 尝试从 ProbingOutputs 阶段选出一个 output,构造 VAAPI 设备 + 截屏后端,切到 EverythingButFmt。 // _idx 是预留参数(不使用),返回 bool:true 表示已切到 EverythingButFmt,false 表示还需继续等待事件。 fn try_finalize_output(&mut self, _idx: usize) -> bool { // Merge wlr head position info into outputs (needed for niri path) // 先把 wlr_heads 中的 position 合并到 outputs(niri 路径用 wlr-output-management 而非 xdg-output)。 if let EncConstructionStage::ProbingOutputs { outputs, wlr_heads, .. } = &mut self.stage { for info in outputs.iter_mut() { if info.logical_position.is_none() { if let Some(ref wl_name) = info.wl_name { if let Some(head_info) = wlr_heads.get(wl_name) { info.logical_position = head_info.position; } } } } } // 选出 target_idx:若用户指定 --output-name,按名匹配;否则默认取第一个。 let (target_idx, output_count) = match &self.stage { EncConstructionStage::ProbingOutputs { outputs, xdg_output_manager, wlr_manager_done, .. } => { let has_xdg = xdg_output_manager.is_some(); let output_count = outputs.len(); let idx = if let Some(ref name) = self.args.output_name { // .iter().position(...) 返回第一个匹配的索引;类比 Go 的 for-loop 找索引。 let pos = outputs .iter() .position(|o| o.name.as_deref() == Some(name.as_str())); match pos { Some(i) => Some(i), None => { // 所有 output 的 done_count >= 1 才认为 probe 完成; // 否则等待更多事件到达(return None 后 outer 逻辑会继续等待)。 let all_probed = outputs.iter().all(|o| o.done_count >= 1); if all_probed { let available: Vec<&str> = outputs.iter().filter_map(|o| o.name.as_deref()).collect(); tracing::error!( "Output '{}' not found. Available outputs: {:?}", name, available ); self.errored = true; } None } } } else if outputs.iter().all(|o| o.done_count >= 1) { // 没指定 output_name:等所有 output done 后取第一个。 if outputs.is_empty() { return false; } Some(0) } else { None }; match idx { Some(i) => { let info = &outputs[i]; if has_xdg { // xdg-output path (Sway/Hyprland) — strict checks // xdg-output 需要 done_count >= 2(geometry + done 各一次)。 if info.done_count < 2 || info.name.is_none() || info.transform.is_none() || info.physical_size.is_none() || info.logical_position.is_none() { return false; } } else { // wlr-output-management path (niri) — relaxed checks // wlr-output-management 路径只要求 done_count >= 1 + manager done。 if info.done_count < 1 || !wlr_manager_done { return false; } if info.transform.is_none() || info.physical_size.is_none() { return false; } // name and logical_position can use defaults } (i, output_count) } None => return false, } } _ => return false, }; // 取出 ProbingOutputs 内部所有字段,准备构造 EverythingButFmt。 let probing = match mem::replace(&mut self.stage, EncConstructionStage::Intermediate) { // s @ 模式:绑定整个 enum 值到 s(再 match variant)。 s @ EncConstructionStage::ProbingOutputs { .. } => s, other => { self.stage = other; return false; } }; // 解构 probing 拿出所有字段;下划线前缀变量是"故意丢弃"(这些字段在 EverythingButFmt 中不再需要)。 let ( outputs, bound_outputs, output_names, screencopy_manager, dmabuf, dmabuf_feedback, _xdg_output_manager, _wlr_output_manager, _wlr_manager_done, _wlr_heads, _wlr_head_proxy_to_name, ) = match probing { EncConstructionStage::ProbingOutputs { outputs, bound_outputs, output_names, screencopy_manager, dmabuf, dmabuf_feedback, xdg_output_manager, wlr_output_manager, wlr_manager_done, wlr_heads, wlr_head_proxy_to_name, } => ( outputs, bound_outputs, output_names, screencopy_manager, dmabuf, dmabuf_feedback, xdg_output_manager, wlr_output_manager, wlr_manager_done, wlr_heads, wlr_head_proxy_to_name, ), _ => unreachable!(), }; // Destroy feedback object — prevents server-side resource leak // 销毁 dmabuf feedback 对象(不再需要),避免 compositor 端资源泄漏。 if let Some(feedback) = dmabuf_feedback { feedback.destroy(); } // 构造最终的 OutputInfo(把 Partial 的 Option 字段解包为确定值)。 let info = &outputs[target_idx]; let output_info = OutputInfo { // name 优先级:xdg-output name > wl_name > fallback "output-N"。 name: info .name .clone() .or(info.wl_name.clone()) .unwrap_or_else(|| format!("output-{}", output_names[target_idx])), // unwrap() 在 None 时 panic;这里前面 has_xdg 分支已经检查过 is_none,确保安全。 transform: info.transform.unwrap(), physical_size: info.physical_size.unwrap(), // logical_position 在 niri 路径下可能仍为 None,给默认 (0,0)。 logical_position: info.logical_position.unwrap_or((0, 0)), }; let output = bound_outputs[target_idx].clone(); // 解包 screencopy_manager:None 说明 compositor 不支持 wlr-screencopy,致命错误。 let screencopy_manager = match screencopy_manager { Some(m) => m, None => { tracing::error!("No screencopy manager bound"); self.errored = true; return false; } }; // 解包 dmabuf:None 说明 compositor 不支持 dmabuf(无法 zero-copy GPU buffer)。 let dmabuf = match dmabuf { Some(d) => d, None => { tracing::error!("No dmabuf manager bound"); self.errored = true; return false; } }; // 解析 DRM 设备路径(用户指定 > compositor 推荐 > 自动扫描 > 默认)。 let drm_path = self.resolve_drm_path(); // 创建 VAAPI 硬件设备上下文(封装 AVHWDeviceContext + AVHWFramesContext)。 let hw_device_ctx = match AvHwDevCtx::new_vaapi(&drm_path) { Ok(ctx) => ctx, Err(e) => { tracing::error!("Failed to create VAAPI device: {}", e); self.errored = true; return false; } }; // 调用 CaptureSource trait 的关联函数 new 构造截屏后端(CapWlrScreencopy::new)。 // S::new 是 trait method 的静态调用(无需 &self,类似 Go 的构造函数)。 let cap = match S::new(&self.gm, &output, &output_info, &self.qhandle) { Ok(c) => c, Err(e) => { tracing::error!("Failed to create capture source: {}", e); self.errored = true; return false; } }; tracing::info!("Selected output: {}", output_info.name); // 多 output 警告:用户没指定 --output-name 时默认选第一个,提示其它可用 output。 if self.args.output_name.is_none() && output_count > 1 { tracing::warn!( "Multiple outputs found, using '{}'. Use --output-name to select.", output_info.name ); } // 把 stage 切到 EverythingButFmt,等下一帧 capture 来协商格式才能进 Streaming。 self.stage = EncConstructionStage::EverythingButFmt { output_info, output, hw_device_ctx, cap, screencopy_manager, dmabuf, }; // 返回 true:已成功切到 EverythingButFmt,外层可以开始 capture 第一帧。 true } } // --------------------------------------------------------------------------- // 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, event: wayland_client::protocol::wl_registry::Event, _data: &GlobalListContents, _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})"); let proxy: ZwpLinuxDmabufV1 = registry.bind(name, v, qhandle, ()); if let EncConstructionStage::ProbingOutputs { dmabuf, dmabuf_feedback, .. } = &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, bound_outputs, output_names, xdg_output_manager, .. } = &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})"); let xdg_mgr: ZxdgOutputManagerV1 = registry.bind(name, v, qhandle, ()); if let EncConstructionStage::ProbingOutputs { bound_outputs, xdg_output_manager, output_names, .. } = &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); } *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})"); let mgr: ZwlrOutputManagerV1 = registry.bind(name, v, qhandle, ()); if let EncConstructionStage::ProbingOutputs { wlr_output_manager, .. } = &mut state.stage { *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 兼容性预留)。 _ => {} } } } // --------------------------------------------------------------------------- // 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, _proxy: &WlOutput, event: wayland_client::protocol::wl_output::Event, data: &OutputId, _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, wayland_client::WEnum::Value(WlTransform::_180) => Transform::Normal180, wayland_client::WEnum::Value(WlTransform::_270) => Transform::Normal270, wayland_client::WEnum::Value(WlTransform::Flipped) => Transform::Flipped, wayland_client::WEnum::Value(WlTransform::Flipped90) => Transform::Flipped90, wayland_client::WEnum::Value(WlTransform::Flipped180) => Transform::Flipped180, wayland_client::WEnum::Value(WlTransform::Flipped270) => Transform::Flipped270, _ => 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 { if let Some(info) = outputs.get_mut(idx) { info.mode_size = Some((width, height)); } } } } // 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) { info.wl_name = Some(name); } } } _ => {} } } } // --------------------------------------------------------------------------- // 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, _proxy: &ZxdgOutputV1, event: ::Event, data: &OutputId, _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) } _ => None, }; let idx = match idx { Some(i) => i, None => return, }; 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) { info.name = Some(name); } } } // LogicalPosition:scaled 后的逻辑坐标(用于多显示器拼接顺序)。 XdgOutputEvent::LogicalPosition { x, y } => { if let EncConstructionStage::ProbingOutputs { outputs, .. } = &mut state.stage { if let Some(info) = outputs.get_mut(idx) { info.logical_position = Some((x, y)); } } } // 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) { info.done_count += 1; if info.done_count >= 1 { state.try_finalize_output(idx); } } } } _ => {} } } } // --------------------------------------------------------------------------- // 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, _proxy: &ZwpLinuxDmabufV1, event: ::Event, _data: &(), _conn: &wayland_client::Connection, _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, _proxy: &ZwpLinuxDmabufFeedbackV1, event: ::Event, _data: &(), _conn: &wayland_client::Connection, _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!( "Compositor DRM device: {} (dev_t: {})", path.display(), dev ); state.drm_device_from_compositor = Some(path); } else { tracing::warn!( "Compositor reported DRM device {} (dev_t: {}) but path does not exist", path.display(), dev ); } } else { tracing::warn!( "main_device event with unexpected data length: {}", device.len() ); } } // 其它 feedback 事件(格式表 / tranche)暂不消费:我们只用主设备做启发式。 DmabufFeedbackEvent::FormatTable { .. } => {} DmabufFeedbackEvent::Done => {} DmabufFeedbackEvent::TrancheDone => {} DmabufFeedbackEvent::TrancheTargetDevice { .. } => {} DmabufFeedbackEvent::TrancheFormats { .. } => {} DmabufFeedbackEvent::TrancheFlags { .. } => {} _ => {} } } } // --------------------------------------------------------------------------- // 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, proxy: &ZwpLinuxBufferParamsV1, event: ::Event, _data: &(), _conn: &wayland_client::Connection, _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; } _ => {} } } } // --------------------------------------------------------------------------- // Dispatch for CapWlrScreencopy // --------------------------------------------------------------------------- impl Dispatch for State { fn event( state: &mut Self, proxy: &ZwlrScreencopyFrameV1, event: ::Event, _data: &(), _conn: &wayland_client::Connection, _qhandle: &QueueHandle>, ) { 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. ScreencopyFrameEvent::Buffer { .. } => { tracing::debug!("Received SHM Buffer offer — only DMA-BUF capture is supported"); } ScreencopyFrameEvent::LinuxDmabuf { format, width, height, } => { tracing::debug!("Screencopy LinuxDmabuf: format={format}, {width}x{height}"); if !matches!(state.in_flight_surface, InFlightSurface::AllocQueued) { tracing::warn!("Received LinuxDmabuf while no frame allocation was queued"); return; } if matches!(state.stage, EncConstructionStage::EverythingButFmt { .. }) { state.negotiate_format(format, width, height); if state.errored { return; } } if let EncConstructionStage::Streaming { cap, .. } = &mut state.stage { cap.current_frame = Some(proxy.clone()); } 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. 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" ); state.in_flight_surface = InFlightSurface::None; proxy.destroy(); state.errored = true; } } ScreencopyFrameEvent::Ready { tv_sec_hi, tv_sec_lo, tv_nsec, } => { let tv_sec = (tv_sec_hi as u64) << 32 | tv_sec_lo as u64; let tv_usec = tv_nsec / 1000; tracing::trace!("Screencopy ready: tv_sec={tv_sec}, tv_usec={tv_usec}"); state.on_copy_complete(tv_sec, tv_usec); } ScreencopyFrameEvent::Failed => { tracing::error!("Screencopy frame failed"); state.on_copy_fail(); } ScreencopyFrameEvent::Damage { .. } => {} _ => {} } } } // --------------------------------------------------------------------------- // Dispatch // --------------------------------------------------------------------------- impl Dispatch for State { fn event( _state: &mut Self, _proxy: &ZxdgOutputManagerV1, _event: ::Event, _data: &(), _conn: &wayland_client::Connection, _qhandle: &QueueHandle>, ) { } } // --------------------------------------------------------------------------- // Dispatch // --------------------------------------------------------------------------- impl Dispatch for State { fn event( state: &mut Self, _proxy: &ZwlrOutputManagerV1, event: ::Event, _data: &(), _conn: &wayland_client::Connection, qhandle: &QueueHandle>, ) { match event { WlrOutputManagerEvent::Head { head } => { let _head: ZwlrOutputHeadV1 = head; tracing::debug!("wlr output head advertised"); } WlrOutputManagerEvent::Done { .. } => { if let EncConstructionStage::ProbingOutputs { wlr_manager_done, outputs, .. } = &mut state.stage { *wlr_manager_done = true; let count = outputs.len(); for idx in 0..count { state.try_finalize_output(idx); } } } WlrOutputManagerEvent::Finished { .. } => { tracing::warn!("zwlr_output_manager_v1::Finished received during probing"); } _ => {} } } event_created_child!(State, ZwlrOutputManagerV1, [ zwlr_output_manager_v1::EVT_HEAD_OPCODE => (ZwlrOutputHeadV1, ()), ]); } // --------------------------------------------------------------------------- // Dispatch // --------------------------------------------------------------------------- impl Dispatch for State { fn event( state: &mut Self, proxy: &ZwlrOutputHeadV1, event: ::Event, _data: &(), _conn: &wayland_client::Connection, _qhandle: &QueueHandle>, ) { match event { WlrHeadEvent::Name { name } => { if let EncConstructionStage::ProbingOutputs { wlr_heads, wlr_head_proxy_to_name, .. } = &mut state.stage { wlr_heads .entry(name.clone()) .or_insert(WlrHeadInfo { position: None }); wlr_head_proxy_to_name.insert(proxy.id(), name); } } WlrHeadEvent::Position { x, y } => { if let EncConstructionStage::ProbingOutputs { wlr_heads, wlr_head_proxy_to_name, .. } = &mut state.stage { 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)); } } } } WlrHeadEvent::Finished { .. } => { tracing::debug!("zwlr_output_head_v1::Finished received"); } _ => {} } } event_created_child!(State, ZwlrOutputHeadV1, [ zwlr_output_head_v1::EVT_MODE_OPCODE => (ZwlrOutputModeV1, ()), ]); } // --------------------------------------------------------------------------- // Dispatch // --------------------------------------------------------------------------- impl Dispatch for State { fn event( _state: &mut Self, _proxy: &ZwlrOutputModeV1, _event: ::Event, _data: &(), _conn: &wayland_client::Connection, _qhandle: &QueueHandle>, ) { } } // --------------------------------------------------------------------------- // Dispatch // --------------------------------------------------------------------------- impl Dispatch for State { fn event( _state: &mut Self, _proxy: &ZwlrScreencopyManagerV1, _event: ::Event, _data: &(), _conn: &wayland_client::Connection, _qhandle: &QueueHandle>, ) { } } // --------------------------------------------------------------------------- // Dispatch // --------------------------------------------------------------------------- impl Dispatch for State { fn event( _state: &mut Self, _proxy: &WlBuffer, event: ::Event, _data: &(), _conn: &wayland_client::Connection, _qhandle: &QueueHandle>, ) { if let wayland_client::protocol::wl_buffer::Event::Release = event { tracing::trace!("WlBuffer released"); } } }