2093 lines
104 KiB
Rust
2093 lines
104 KiB
Rust
//! 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(输出列表)、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<T, anyhow::Error>;用 `?` 传播多种错误类型很方便。
|
||
// 类比 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<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_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<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-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<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 给出,但有些 compositor(niri)需要从 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 指针;调用方用它分配硬件 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<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 name(Wayland 用 u32 标识 global),与 outputs 索引对齐。
|
||
output_names: Vec<u32>,
|
||
// 截屏协议管理器,bound 后用来 capture_output;None 表示 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 硬件设备上下文,封装 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<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 视频 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: CaptureSource> 表示截屏后端的具体类型。S 作为值类型字段(cap: S)直接持有时,
|
||
// 必须 Sized;又因为 State<S> 需要进入 Wayland Dispatch 路径,S 还需要 'static。
|
||
// 所有字段都 pub(crate 内可见),少数几个只在本文件用的字段例外。
|
||
pub struct State<S: CaptureSource> {
|
||
// 阶段机:构造时为 ProbingOutputs,negotiate_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 channel;poll_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_else:None 时调用闭包;这里给出兜底默认值。
|
||
.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> {
|
||
// 构造函数:从 GlobalList(registry 已 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() 拷贝出所有 global;Vec<Global> 拥有所有权。
|
||
let globals: Vec<Global> = 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<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 是临时 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<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 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<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 表示已切到 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<WlRegistry, GlobalListContents>
|
||
// ---------------------------------------------------------------------------
|
||
|
||
// 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<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-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<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-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<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 {
|
||
// 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<ZxdgOutputV1, OutputId>
|
||
// ---------------------------------------------------------------------------
|
||
|
||
// 处理 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<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 {
|
||
// 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<ZwpLinuxDmabufV1, ()>
|
||
// ---------------------------------------------------------------------------
|
||
|
||
// 处理 linux-dmabuf v1 基础协议事件(Format/Modifier 广播)。
|
||
// 故意全部留空:我们用的是更现代的 dmabuf feedback(ZwpLinuxDmabufFeedbackV1),
|
||
// 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 {
|
||
// 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<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 {
|
||
// 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<ZwpLinuxBufferParamsV1, ()>
|
||
// ---------------------------------------------------------------------------
|
||
|
||
// 处理 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<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 {
|
||
// 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<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");
|
||
}
|
||
}
|
||
}
|