Files
wl-webrtc/src/state.rs
T

2093 lines
104 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! wlroots 后端的核心状态机:屏幕采集 → DMA-BUF 协商 → 硬件编码 整条流水线的状态载体。
//!
//! ## 整体角色
//!
//! `State<S>` 由外层 `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<S>` 块。
//!
//! ## 泛型 `<S: CaptureSource>`
//!
//! `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<S>` 是一条阶段流水线:
//! `ProbingOutputs` → `EverythingButFmt` → `Streaming`(运行期)。
//! 每个阶段持有不同的 Wayland 对象与硬件上下文,运行期进入 `Streaming` 后才开始拉帧编码。
//!
//! ## 注意
//!
//! - T7a(本注释块)覆盖文件头、类型定义、`State<S>` 方法;T7b 覆盖各 `Dispatch` trait 实现;
//! T7c 覆盖帧捕获相关的 `Dispatch` 与 `ZwlrScreencopyFrameV1` 处理。
//! - 大量 `unsafe` 块调用 FFmpeg / `libc::dup` / DMA-BUF FFI;现有英文 `// SAFETY:` 注释务必保留。
//! - `edition = "2021"`(非 2024 默认值);请勿改动任何代码字符,只新增中文注释。
// std 标准库导入:HashMap(输出列表)、memtake/replace)、AsFd/OwnedFd/FromRawFdDMA-BUF fd 桥接)、
// Path/PathBufDRM 设备路径)、AtomicBool/OrderingWebRTC 暂停标志,跨线程无锁同步)、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<T, anyhow::Error>;用 `?` 传播多种错误类型很方便。
// 类比 Go 的 `if err := ...; err != nil { return err }`,但 Rust 的 `?` 是单字符运算符。
use anyhow::Result;
// wayland_clientWayland 协议客户端核心库。
// - ObjectIdWayland 对象的服务端 ID(用于在 Dispatch user data 中查找映射)。
// - GlobalList/GlobalListContentsregistry bind 后的全局对象列表(compositor 公告的服务)。
// - WlBuffer/WlOutput/WlRegistrywl_* 是 Wayland 核心协议对象,由 wayland-scanner 自动生成。
// - Dispatch:核心 trait,类似 Go interface——实现 `Dispatch<I, UDATA> 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_bufferFeedback 用于接收默认格式偏好。
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 ffFFmpeg 的 Rust 绑定(别名 ff 简化书写);ffi 是 FFmpeg C 原始 FFI 类型层。
use ffmpeg_next as ff;
use ffmpeg_next::ffi;
// 本 crate 内部模块:
// - args::ArgsCLI 参数(见 args.rs)。
// - avhw:硬件加速(VAAPI+ 软件 H.264 编码器;EncState=MP4SwEncState=WebRTCEncodedH264Frame=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 默认所有类型 Sizedtrait 对象默认 `?Sized` 才能用作 `dyn Trait`
// 这里要求 Sized 是因为 `State<S>` 要直接持有 `cap: S`(值类型,不是 Box<dyn ...>)。
// - `'static`S 中不含任何非 `'static` 的借用引用;这样 S 才能被存入 `State<S>` 并自由跨越线程/调用栈。
/// 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<State<Self>> 中 Self 是实现 trait 的具体类型——
// 这是 Rust 的 Self 类型多态:trait 内可用 Self 指代"实现本 trait 的类型"。
fn new(
gm: &GlobalList,
output: &WlOutput,
output_info: &OutputInfo,
qh: &QueueHandle<State<Self>>,
) -> Result<Self>;
// 分配一个新的截屏帧对象。返回 Option:分配失败时返回 None(例如 GPU 内存不足)。
// &mut self:独占可变借用(Rust 的 aliasing 规则:同一时刻只能有一个 &mut 或任意多个 &)。
fn alloc_frame(&mut self) -> Option<Self::Frame>;
// 把 wl_buffer 提交给 compositor 请求截屏(非阻塞,结果通过 Dispatch<ZwlrScreencopyFrameV1> 回调返回)。
// qh 用来在新创建的 frame proxy 上挂事件回调(类比 Go 的 channel 注册)。
fn queue_copy(&mut self, buffer: &WlBuffer, qh: &QueueHandle<State<Self>>);
// 截屏完成(成功或失败)后回收 Frame 资源;放回池中或释放。frame 参数 by-valuemove 语义),
// 调用方此后不能再用 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<WlOutput> / Dispatch<ZxdgOutputV1> / Dispatch<ZwlrOutputHeadV1> 事件中逐步填充。
pub struct PartialOutputInfo {
// 来自 xdg_output.name 事件(如果 compositor 支持 xdg-output);Sway/Hyprland 提供。
pub name: Option<String>,
/// Name from wl_output::Name (v4) — used to match wlr-output-management heads
pub wl_name: Option<String>,
// 旋转/翻转信息;wl_output.geometry 给出,但有些 compositorniri)需要从 wlr head 取。
pub transform: Option<Transform>,
// 物理尺寸(毫米),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 指针;调用方用它分配硬件 frameav_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<S: CaptureSource> {
// 第一阶段:枚举所有 wl_output,等待 done 事件收齐信息。
// 字段都 Option<...> 因为 Wayland global 可能未公告(例如老 compositor 没 wlr-output-management)。
ProbingOutputs {
outputs: Vec<PartialOutputInfo>,
// 已绑定(bound)的 WlOutput proxy 列表;索引与 outputs 一一对应。
bound_outputs: Vec<WlOutput>,
// registry 中每个 global 的 u32 nameWayland 用 u32 标识 global),与 outputs 索引对齐。
output_names: Vec<u32>,
// 截屏协议管理器,bound 后用来 capture_outputNone 表示 compositor 不支持 wlr-screencopy。
screencopy_manager: Option<ZwlrScreencopyManagerV1>,
// dmabuf 协议管理器;None 表示 compositor 不支持 zero-copy dmabuf(只能用 wl_shm,本项目不支持)。
dmabuf: Option<ZwpLinuxDmabufV1>,
// dmabuf 默认格式偏好(v4+),用于接收 main_device 与 format_table。
dmabuf_feedback: Option<ZwpLinuxDmabufFeedbackV1>,
// xdg-output 管理器(Sway/Hyprland 路径);None 时改走 wlr-output-management 路径(niri)。
xdg_output_manager: Option<ZxdgOutputManagerV1>,
// wlr-output-management 管理器(niri 路径)。
wlr_output_manager: Option<ZwlrOutputManagerV1>,
// wlr-output-manager 的 done 事件是否已收到(head 信息完整)。
wlr_manager_done: bool,
// name → WlrHeadInfo 的映射;从 wlr head 事件收集,用于按名称与 wl_output 配对。
wlr_heads: HashMap<String, WlrHeadInfo>,
// head proxy ObjectId → name 的反向映射;Dispatch<ZwlrOutputHeadV1> 中根据 proxy id 查 name。
wlr_head_proxy_to_name: HashMap<ObjectId, String>,
},
// 第二阶段:output 已选定,硬件设备上下文(VAAPI)已构造,截屏后端 S 已创建;但编码器未构造,
// 因为编码器需要捕获第一帧拿到 dmabuf format 后才能初始化。negotiate_format() 推进到下一阶段。
EverythingButFmt {
output_info: OutputInfo,
output: WlOutput,
// VAAPI 硬件设备上下文,封装 AVHWDeviceContextMP4 模式下被 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<S: CaptureSource> {
// 空闲态,可以发起新一帧。
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 视频 frameGPU 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: CaptureSource> 表示截屏后端的具体类型。S 作为值类型字段(cap: S)直接持有时,
// 必须 Sized;又因为 State<S> 需要进入 Wayland Dispatch 路径,S 还需要 'static。
// 所有字段都 pub(crate 内可见),少数几个只在本文件用的字段例外。
pub struct State<S: CaptureSource> {
// 阶段机:构造时为 ProbingOutputsnegotiate_format 后切到 Streaming。
pub stage: EncConstructionStage<S>,
// 当前截屏帧的状态机(None/AllocQueued/Allocd/CopyQueued)。
pub in_flight_surface: InFlightSurface<S>,
// 第一帧的捕获时间戳,用于后续帧的 PTS 偏移计算(保留 None 直到第一帧 ready)。
pub starting_timestamp: Option<i64>,
// 统计区间的起始时间(用于 10 秒一次的 fps 报告)。
pub stats_start_time: Option<Instant>,
// 上次报告统计的时间;elapsed() >= 10s 时触发新一次报告。
pub stats_last_time: Option<Instant>,
// 自上次报告以来编码的帧数;每次报告后清零。
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,
// 帧率限制器,缓存跳过的帧以备用;泛型 <S::Frame> 因为限制器需要"占位"帧来调度。
pub fps_limit: FpsLimit<S::Frame>,
// Wayland 事件队列句柄,所有 bind/create 调用都需要它挂回调。
pub qhandle: QueueHandle<State<S>>,
// 用户通过 --drm-device 指定的 DRM 设备路径;None 时优先用 compositor 提供的,再回退到自动扫描。
pub drm_device: Option<PathBuf>,
// 从 dmabuf feedback.main_device 推断的 DRM 设备路径(compositor 推荐的 GPU)。
pub drm_device_from_compositor: Option<PathBuf>,
// WebRTC 服务器状态机(仅在 --port > 0 时构造);None 表示 MP4 模式。
pub webrtc: Option<WebRtcState>,
// 编码后的 H.264 NALU 通过此 channel 发送给 WebRTC 线程;None 表示 MP4 模式。
pub webrtc_tx: Option<crossbeam_channel::Sender<EncodedH264Frame>>,
// WebRTC 线程编码后回传的 NALU channelpoll_webrtc() 中 try_recv 取出并送 str0m。
// 字段无 pub 前缀:本模块外不可访问(信息隐藏)。
webrtc_rx: Option<crossbeam_channel::Receiver<EncodedH264Frame>>,
// 已发送到 WebRTC 的帧数;用于日志/统计。saturating_add 防止 u64 溢出。
webrtc_frames_sent: u64,
// WebRTC 暂停标志,跨线程共享(Arc<AtomicBool>)。
// - 编码线程在没客户端时 set true(跳过昂贵编码)
// - WebRTC 信令线程在 ICE 连接成功后 set false
// Ordering::Relaxed:用于"best-effort 暂停",不需要严格内存序(性能优先)。
webrtc_paused: Option<Arc<AtomicBool>>,
// 流水线统计(采集/编码/发送的耗时与队列深度),每 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<PathBuf> 是拥有所有权的动态数组(Vec类比 Go 的 []T 切片但 Rust 中长度+容量都是字段)。
pub(crate) fn find_drm_render_nodes() -> Vec<PathBuf> {
// let-else 模式(Rust 2021 引入):匹配失败时执行 else 块(这里直接 return)。
// 类比 Go 的 `if err != nil { return }` 但更紧凑。read_dir 返回 io::Result<ReadDir>。
let Ok(entries) = std::fs::read_dir("/dev/dri") else {
return Vec::new();
};
// entries 是 ReadDir 迭代器;filter_map 同时做 filter(去除 None)和 map(提取值)。
// 每一步 ? 都是 Option<T> 的早期返回(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<OsString>to_str() 返回 Option<&str>。
let name = path.file_name()?.to_str()?;
// strip_prefix("renderD") 返回 Option<&str>,再 parse::<u32>() 转 u32;任意一步失败返回 None。
let number = name.strip_prefix("renderD")?.parse::<u32>().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<PathBuf>。
nodes.into_iter().map(|(_, path)| path).collect()
}
/// Scan /dev/dri for the first available DRM render node (renderD*).
// 包装 find_drm_render_nodes(),取第一个(即节点号最小的,通常是 renderD128)。
// 返回 Option<PathBuf>None 表示 /dev/dri 下没有 renderD*(极少见,说明系统无 GPU)。
fn find_drm_render_node() -> Option<PathBuf> {
find_drm_render_nodes().into_iter().next()
}
// 第一个 impl 块:DRM 设备路径解析。impl<S: CaptureSource> State<S> 表示"为所有实现了
// CaptureSource 的类型 S,定义 State<S> 的方法"——这是 Rust 泛型 impl 语法(类比 Go 的泛型 receiver)。
impl<S: CaptureSource> State<S> {
// 解析最终使用的 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_elseNone 时调用闭包;这里给出兜底默认值。
.unwrap_or_else(|| PathBuf::from("/dev/dri/renderD128"))
}
}
// ---------------------------------------------------------------------------
// State<S> methods
// ---------------------------------------------------------------------------
// 第二个 impl 块:State<S> 的主方法集合(构造、Wayland global 绑定、帧生命周期、WebRTC polling、
// 格式协商、output 终结)。这里的方法都被 main loop 或 Dispatch 实现调用。
impl<S: CaptureSource> State<S> {
// 构造函数:从 GlobalListregistry 已 roundtrip 完成的快照)+ CLI Args + Wayland QueueHandle 开始。
// 返回 Result<Self>WebRtcState::new 失败时(端口被占用等)通过 ? 传播错误。
pub fn new(gm: GlobalList, args: Args, qhandle: QueueHandle<State<S>>) -> Result<Self> {
// 取出 fps 字段值(u32 Copy 类型,直接拷贝);后面 FpsLimit::new(fps) 需要。
let fps = args.fps;
// args.drm_device 是 Option<String>(来自 CLI)。as_ref() 把 Option<String> 借用为 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<S>)。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<WlRegistry>;因此必须在此手动遍历 GlobalList 绑定所需协议。
state.bind_initial_globals();
// Ok(state) 把 state 包成 Result<Self, anyhow::Error>;构造成功。
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<WlRegistry> handler.
// 遍历 GlobalList,按依赖顺序绑定 Wayland 协议对象。&mut self 因为要更新 stage 字段。
fn bind_initial_globals(&mut self) {
// 函数内 use 把 trait 引入局部作用域(Rust 必须显式导入 trait 才能调用其方法)。
use wayland_client::globals::Global;
// clone_list() 拷贝出所有 globalVec<Global> 拥有所有权。
let globals: Vec<Global> = self.gm.contents().clone_list();
// registry 是 wl_registry proxybind() 用来创建具体的协议对象。
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 是 Stringas_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<u32>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<S>: Dispatch<ZwlrScreencopyFrameV1>` ——
// 这是 trait bound 在泛型方法上的 where 子句约束(类比 Go 泛型的 `T where T implements X`)。
// 只有 State 的具体类型(State<CapWlrScreencopy>)实际实现了 Dispatch<ZwlrScreencopyFrameV1>。
pub fn queue_alloc_frame(&mut self)
where
State<S>: Dispatch<ZwlrScreencopyFrameV1, ()>,
{
// 从 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 是临时 proxyRust 中下划线前缀表示"故意丢弃",对象在帧处理完会被 destroy。
let _frame_proxy = manager.capture_output(1, &output, &self.qhandle, ());
self.in_flight_surface = InFlightSurface::AllocQueued;
}
// 收到 frame 的 buffer 信息事件后调用。构造 wl_bufferdma-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 的 framedma-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 包装成 OwnedFdRAII,离开作用域自动 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: Defaulttrait 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 时钟的 tickWebRTC 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_bufferlibwayland-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_addu64 溢出时停在 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<ZwlrScreencopyFrameV1>::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 senderArc 共享底层 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<String> 转 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 表示已切到 EverythingButFmtfalse 表示还需继续等待事件。
fn try_finalize_output(&mut self, _idx: usize) -> bool {
// Merge wlr head position info into outputs (needed for niri path)
// 先把 wlr_heads 中的 position 合并到 outputsniri 路径用 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 >= 2geometry + 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_managerNone 说明 compositor 不支持 wlr-screencopy,致命错误。
let screencopy_manager = match screencopy_manager {
Some(m) => m,
None => {
tracing::error!("No screencopy manager bound");
self.errored = true;
return false;
}
};
// 解包 dmabufNone 说明 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<WlRegistry, GlobalListContents>
// ---------------------------------------------------------------------------
// wayland-client 的 Dispatch trait:处理 Wayland 协议对象的事件回调。
// 泛型参数 1 = 协议对象类型(这里是 WlRegistrycompositor 全局对象广播器),
// 泛型参数 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 名选择性 bindscreencopy/dmabuf/wl_output/xdg-output/
// wlr-output-management),把 proxy 存入 ProbingOutputs stage 字段。
impl<S: CaptureSource> Dispatch<WlRegistry, GlobalListContents> for State<S> {
// 注意 Dispatch::event 的 receiver 是 `state: &mut Self`(不是 self):
// wayland-client 设计为 free fn + 第一个参数 &mut State,便于事件循环统一调度。
// &mut Self 在 trait 内等价于 &mut State<S>,是当前类型的独占可变借用。
// 参数 `_data: &GlobalListContents`registry 附带的 user_data,这里不读,前缀 `_` 标记未用。
// 参数 `qhandle: &QueueHandle<State<S>>`:事件队列句柄,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<State<S>>,
) {
// 局部 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-v1DMA-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 feedbackcompositor 主动告知格式 + 设备)。
let feedback = proxy.get_default_feedback(qhandle, ());
*dmabuf_feedback = Some(feedback);
}
}
}
// wl_outputWayland 核心输出(显示器)协议。每个 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<WlOutput, OutputId>::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<u32>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-v1niri 用的输出管理协议(替代 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 等)。
_ => {}
},
// GlobalRemovecompositor 移除某协议对象(如显示器热插拔)。截屏启动期忽略。
RegistryEvent::GlobalRemove { name } => {
tracing::debug!("Global removed: name={name}");
}
// 兜底 arm:未来 Wayland 新增事件变体时不会编译失败(trait 兼容性预留)。
_ => {}
}
}
}
// ---------------------------------------------------------------------------
// Dispatch<WlOutput, ()>
// ---------------------------------------------------------------------------
// 处理 wl_output 协议事件:compositor 推送显示器元数据(geometry/mode/name/done)。
// user_data = OutputId(name)bind 时传入的 registry name,这里用来反查 stage 中的索引。
// 本 impl 把事件数据累计到 PartialOutputInfo(在 ProbingOutputs 阶段),
// Done 事件触发 try_finalize_output 尝试切到 EverythingButFmt。
impl<S: CaptureSource> Dispatch<WlOutput, OutputId> for State<S> {
fn event(
state: &mut Self,
_proxy: &WlOutput,
event: wayland_client::protocol::wl_output::Event,
data: &OutputId,
_conn: &wayland_client::Connection,
_qhandle: &QueueHandle<State<S>>,
) {
// 引入 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 {
// Geometrycompositor 推送显示器物理属性 + 旋转方向。
// `..` 表示忽略其它字段(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));
}
}
}
}
// Donecompositor 一次性告知所有元数据已发完(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<ZxdgOutputV1, OutputId>
// ---------------------------------------------------------------------------
// 处理 xdg-output 协议事件:compositor 推送显示器的逻辑坐标 + 名称(Sway/Hyprland)。
// xdg-output 是 wl_output 的扩展协议:wl_output 只给物理像素,
// xdg-output 补充 logical_position / logical_sizeHiDPI scaled 后的逻辑值)+ name。
// user_data = OutputId(name):与 Dispatch<WlOutput> 同源,用 registry name 反查索引。
// Done 事件与 wl_output::Done 二选一触发 try_finalize_output(双向保险)。
impl<S: CaptureSource> Dispatch<ZxdgOutputV1, OutputId> for State<S> {
fn event(
state: &mut Self,
_proxy: &ZxdgOutputV1,
event: <ZxdgOutputV1 as Proxy>::Event,
data: &OutputId,
_conn: &wayland_client::Connection,
_qhandle: &QueueHandle<State<S>>,
) {
// data.0 直接取出 OutputId 内的 u32(不用解构,因为 OutputId 是 transparent tuple struct)。
let target_name = data.0;
// 同 Dispatch<WlOutput>:找索引,找不到就 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 {
// Namexdg-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);
}
}
}
// LogicalPositionscaled 后的逻辑坐标(用于多显示器拼接顺序)。
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 故意忽略:截屏用物理像素 sizemode_size),不用 logical(会被 scale 缩放)。
XdgOutputEvent::LogicalSize { .. } => {}
// Donexdg-output 的元数据批次结束。done_count 在 wl_output 和 xdg-output 两边都自增,
// try_finalize_output 内部 has_xdg 分支要求 done_count >= 2wl_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<ZwpLinuxDmabufV1, ()>
// ---------------------------------------------------------------------------
// 处理 linux-dmabuf v1 基础协议事件(Format/Modifier 广播)。
// 故意全部留空:我们用的是更现代的 dmabuf feedbackZwpLinuxDmabufFeedbackV1),
// feedback 协议在 v4+ 才有,更准确(compositor 主动告知主设备 + tranche 优先级)。
// 这里只实现空 match 是因为 wayland-client 要求每个 bind 的协议都实现 Dispatch
// legacy Format/Modifier 广播在 feedback 存在时是冗余信息,忽略以省 log。
impl<S: CaptureSource> Dispatch<ZwpLinuxDmabufV1, ()> for State<S> {
fn event(
_state: &mut Self,
_proxy: &ZwpLinuxDmabufV1,
event: <ZwpLinuxDmabufV1 as Proxy>::Event,
_data: &(),
_conn: &wayland_client::Connection,
_qhandle: &QueueHandle<State<S>>,
) {
match event {
// Formatlegacy "支持 DRM format X" 广播(无 modifier 信息,已被 feedback 取代)。
DmabufEvent::Format { .. } => {}
// Modifierv3+ 的 "支持 format X with modifier Y" 广播(仍然没有设备/tranche 优先级)。
DmabufEvent::Modifier { .. } => {}
_ => {}
}
}
}
// 处理 linux-dmabuf feedback 事件(v4+):compositor 主动告知 DMA-BUF 偏好。
// 关键事件 MainDevicecompositor 推荐的 DRM 设备(dev_t 编码),
// 解析为 /dev/dri/renderD{minor} 路径存入 state.drm_device_from_compositor
// 供后续 VAAPI 初始化用作硬件设备上下文。
impl<S: CaptureSource> Dispatch<ZwpLinuxDmabufFeedbackV1, ()> for State<S> {
fn event(
state: &mut Self,
_proxy: &ZwpLinuxDmabufFeedbackV1,
event: <ZwpLinuxDmabufFeedbackV1 as Proxy>::Event,
_data: &(),
_conn: &wayland_client::Connection,
_qhandle: &QueueHandle<State<S>>,
) {
match event {
// MainDevicedev 是 8 字节 dev_tLinux 内核设备号编码,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 位组合成 minorrenderD{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<ZwpLinuxBufferParamsV1, ()>
// ---------------------------------------------------------------------------
// 处理 linux-dmabuf buffer_params 协议事件:compositor 反馈 DMA-BUF buffer 创建结果。
// 每次 capture 创建一个 ZwpLinuxBufferParamsV1 proxyadd 添加 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<S: CaptureSource> Dispatch<ZwpLinuxBufferParamsV1, ()> for State<S> {
fn event(
state: &mut Self,
proxy: &ZwpLinuxBufferParamsV1,
event: <ZwpLinuxBufferParamsV1 as Proxy>::Event,
_data: &(),
_conn: &wayland_client::Connection,
_qhandle: &QueueHandle<State<S>>,
) {
match event {
// Createdbuffer 成功创建。本程序路径里不消费这个事件
// streaming 后立即用 on_frame_allocd 路径,不依赖 Created 触发后续动作)。
BufferParamsEvent::Created { .. } => {
tracing::debug!("DMA-BUF buffer created");
}
// Failedbuffer 创建失败(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<ZwlrScreencopyFrameV1, ()> for CapWlrScreencopy
// ---------------------------------------------------------------------------
impl Dispatch<ZwlrScreencopyFrameV1, ()> for State<CapWlrScreencopy> {
fn event(
state: &mut Self,
proxy: &ZwlrScreencopyFrameV1,
event: <ZwlrScreencopyFrameV1 as Proxy>::Event,
_data: &(),
_conn: &wayland_client::Connection,
_qhandle: &QueueHandle<State<CapWlrScreencopy>>,
) {
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<ZxdgOutputManagerV1, ()>
// ---------------------------------------------------------------------------
impl<S: CaptureSource> Dispatch<ZxdgOutputManagerV1, ()> for State<S> {
fn event(
_state: &mut Self,
_proxy: &ZxdgOutputManagerV1,
_event: <ZxdgOutputManagerV1 as Proxy>::Event,
_data: &(),
_conn: &wayland_client::Connection,
_qhandle: &QueueHandle<State<S>>,
) {
}
}
// ---------------------------------------------------------------------------
// Dispatch<ZwlrOutputManagerV1, ()>
// ---------------------------------------------------------------------------
impl<S: CaptureSource> Dispatch<ZwlrOutputManagerV1, ()> for State<S> {
fn event(
state: &mut Self,
_proxy: &ZwlrOutputManagerV1,
event: <ZwlrOutputManagerV1 as Proxy>::Event,
_data: &(),
_conn: &wayland_client::Connection,
qhandle: &QueueHandle<State<S>>,
) {
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<S>, ZwlrOutputManagerV1, [
zwlr_output_manager_v1::EVT_HEAD_OPCODE => (ZwlrOutputHeadV1, ()),
]);
}
// ---------------------------------------------------------------------------
// Dispatch<ZwlrOutputHeadV1, ()>
// ---------------------------------------------------------------------------
impl<S: CaptureSource> Dispatch<ZwlrOutputHeadV1, ()> for State<S> {
fn event(
state: &mut Self,
proxy: &ZwlrOutputHeadV1,
event: <ZwlrOutputHeadV1 as Proxy>::Event,
_data: &(),
_conn: &wayland_client::Connection,
_qhandle: &QueueHandle<State<S>>,
) {
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<S>, ZwlrOutputHeadV1, [
zwlr_output_head_v1::EVT_MODE_OPCODE => (ZwlrOutputModeV1, ()),
]);
}
// ---------------------------------------------------------------------------
// Dispatch<ZwlrOutputModeV1, ()>
// ---------------------------------------------------------------------------
impl<S: CaptureSource> Dispatch<ZwlrOutputModeV1, ()> for State<S> {
fn event(
_state: &mut Self,
_proxy: &ZwlrOutputModeV1,
_event: <ZwlrOutputModeV1 as Proxy>::Event,
_data: &(),
_conn: &wayland_client::Connection,
_qhandle: &QueueHandle<State<S>>,
) {
}
}
// ---------------------------------------------------------------------------
// Dispatch<ZwlrScreencopyManagerV1, ()>
// ---------------------------------------------------------------------------
impl<S: CaptureSource> Dispatch<ZwlrScreencopyManagerV1, ()> for State<S> {
fn event(
_state: &mut Self,
_proxy: &ZwlrScreencopyManagerV1,
_event: <ZwlrScreencopyManagerV1 as Proxy>::Event,
_data: &(),
_conn: &wayland_client::Connection,
_qhandle: &QueueHandle<State<S>>,
) {
}
}
// ---------------------------------------------------------------------------
// Dispatch<WlBuffer, ()>
// ---------------------------------------------------------------------------
impl<S: CaptureSource> Dispatch<WlBuffer, ()> for State<S> {
fn event(
_state: &mut Self,
_proxy: &WlBuffer,
event: <WlBuffer as Proxy>::Event,
_data: &(),
_conn: &wayland_client::Connection,
_qhandle: &QueueHandle<State<S>>,
) {
if let wayland_client::protocol::wl_buffer::Event::Release = event {
tracing::trace!("WlBuffer released");
}
}
}