docs(main): 中文注释 src/main.rs 入口与事件循环

This commit is contained in:
dailz
2026-06-22 16:44:20 +08:00
parent 13b7466c57
commit 3d314a35aa
+83
View File
@@ -1,11 +1,48 @@
//! # wl-webrtc 程序入口(main 函数所在文件)
//!
//! 本文件是 `wl-webrtc` 二进制 crate 的入口,等价于 Go 的 `func main()`。
//! 由于 Rust 的 `main()` 不允许返回错误(`Result`),本项目采用通用模式:
//! 真正的业务逻辑写在 `fn run() -> Result<()>`,而 `main()` 直接 `run()` 完成所有工作。
//!
//! 整体执行流程:
//! 1. 通过 `clap` 解析命令行参数(`Args`,包含分辨率、编码格式、帧率等)
//! 2. 初始化 `tracing` 日志系统(受 `RUST_LOG` 环境变量或 `-v` 参数控制)
//! 3. MVP 阶段拒绝非 H.264 编码格式
//! 4. 要求至少提供 `--output`(输出到文件)或 `--port`(启动 WebRTC 信号服务器)
//! 5. 调用 `backend_detect::detect_backend` 自动检测当前 Wayland 桌面支持的截屏后端
//! 6. 根据检测结果进入对应的事件循环:
//! - 支持 `zwlr_screencopy_manager_v1` 的合成器(Sway/Hyprland)→ `run_wlr_screencopy`
//! - 仅支持 XDG Portal ScreenCast 的桌面(GNOME/KDE)→ `run_portal_pipewire`
//!
//! 两个事件循环都基于 `mio`(一个手动驱动的事件循环库,类似 Go runtime netpoller 的手动版),
//! 底层在 Linux 上使用 epoll。
// 获取 Unix 原始文件描述符所需的 trait // 获取 Unix 原始文件描述符所需的 trait
// AsRawFd 提供了 as_raw_fd() 方法,用于从 std::io::Read/Write 等 Rust 抽象中
// 取出底层的 libc::c_intPOSIX 文件描述符),mio 注册 fd 监听时需要它
use std::os::unix::io::AsRawFd; use std::os::unix::io::AsRawFd;
// anyhow::Result<T, anyhow::Error> 是一个简化的错误类型,等价于 Go 的 (T, error)
// ? 操作符会将任何实现了 std::error::Error 的错误转换为 anyhow::Error
use anyhow::Result; use anyhow::Result;
// clap::Parser 是一个 derive 宏,实现后 args.parse() 即可从 std::env::args() 解析 CLI 参数
// 类比 Go 的 flag.Parse(),但 clap 自动生成 --help 文本和错误处理
use clap::Parser; use clap::Parser;
// mio::unix::SourceFd 是一个 bridge:将裸 fd 包装为实现 mio::Evented 的对象
// 这样 mio 的 epoll 可以监听任意 Unix fd,而不局限于 std::net::TcpStream 等标准类型
use mio::unix::SourceFd; use mio::unix::SourceFd;
// mio 是一个手动驱动的事件循环库(与 tokio 的异步运行时不同,mio 不调度 future)
// - Pollepoll/kqueue 的 Rust 封装,poll.poll() 会阻塞直到 fd 就绪
// - Interest:注册时的关注事件类型(READABLE / WRITABLE
// - Token:用户自定义的事件源标识(u64 包装),用于在 poll 返回时区分是哪个 fd 触发的
// - Events:poll 返回的事件集合(一个容量固定的 Vec)
// 类比 Go runtime 的 netpoller,但 Go runtime 自动调度,mio 需要用户手动循环
use mio::{Events, Interest, Poll, Token}; use mio::{Events, Interest, Poll, Token};
// registry_queue_init 是 wayland-client 的便捷函数:连接到合成器并初始化全局注册表队列
// 它会在内部调用 Connection::connect_to_env() 并 roundtrip 一次拿到全局对象列表
use wayland_client::globals::registry_queue_init; use wayland_client::globals::registry_queue_init;
// Connection 是与 Wayland 合成器的会话连接,封装了 Unix socket 的读写和协议解析
// 类比 Go 中的 net.Conn,但 Wayland 协议是有状态的消息流而非字节流
use wayland_client::Connection; use wayland_client::Connection;
// 各功能模块声明 // 各功能模块声明
@@ -21,6 +58,8 @@ mod stats; // 管道性能统计(卡顿诊断)
mod transform; // 图像变换(旋转/翻转) mod transform; // 图像变换(旋转/翻转)
mod webrtc; // WebRTC 传输(str0m Sans-IO mod webrtc; // WebRTC 传输(str0m Sans-IO
// 引入本 crate 内部模块,crate:: 前缀表示从 crate root 开始的绝对路径
// 类比 Go 中的 import "<module>/args" 写法
use crate::args::Args; use crate::args::Args;
use crate::cap_wlr_screencopy::CapWlrScreencopy; use crate::cap_wlr_screencopy::CapWlrScreencopy;
use crate::state::EncConstructionStage; use crate::state::EncConstructionStage;
@@ -46,6 +85,11 @@ fn main() -> Result<()> {
// 根据 verbose 模式或 RUST_LOG 环境变量设置日志级别 // 根据 verbose 模式或 RUST_LOG 环境变量设置日志级别
// 支持 RUST_LOG 粒度控制(如 RUST_LOG=wl_webrtc::webrtc=trace // 支持 RUST_LOG 粒度控制(如 RUST_LOG=wl_webrtc::webrtc=trace
// 详细解释:
// - try_from_default_env() 返回 Result<EnvFilter>,读取 RUST_LOG 环境变量
// - unwrap_or_else(|_| {...}) 是 Result 的方法:成功则返回内部值,失败时调用闭包
// - |_| 是闭包参数语法:|参数| 表达式,单个 _ 表示忽略参数(这里是 Err 类型)
// 类比 Go 的 if err != nil { fallback },但 Rust 用闭包传递 fallback 逻辑
let env_filter = tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| { let env_filter = tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
if args.verbose { if args.verbose {
tracing_subscriber::EnvFilter::new("debug") tracing_subscriber::EnvFilter::new("debug")
@@ -53,6 +97,11 @@ fn main() -> Result<()> {
tracing_subscriber::EnvFilter::new("info") tracing_subscriber::EnvFilter::new("info")
} }
}); });
// tracing_subscriber::fmt() 是 Builder 模式:链式调用配置,最后 .init() 消费 builder
// 完成全局订阅注册。再次调用 .init() 会 panic,因此只能初始化一次。
// - with_env_filter: 设置过滤规则
// - with_writer: 设置日志输出目标(这里为 stderr,避免污染 stdout 用于视频流)
// - init(): 消费 self,注册全局默认 subscriber,无返回值
tracing_subscriber::fmt() tracing_subscriber::fmt()
.with_env_filter(env_filter) .with_env_filter(env_filter)
.with_writer(std::io::stderr) .with_writer(std::io::stderr)
@@ -69,6 +118,8 @@ fn main() -> Result<()> {
); );
// MVP 阶段仅支持 H.264 编码,不支持 HEVC // MVP 阶段仅支持 H.264 编码,不支持 HEVC
// anyhow::bail! 是一个宏(注意感叹号 !),立即返回 Err(anyhow::Error)
// 类比 Go 的 fmt.Errorf("...") + return err,但是 Rust 用宏实现
if args.codec != "h264" { if args.codec != "h264" {
anyhow::bail!("HEVC not supported in MVP. Use --codec h264"); anyhow::bail!("HEVC not supported in MVP. Use --codec h264");
} }
@@ -79,9 +130,14 @@ fn main() -> Result<()> {
// 自动检测当前桌面环境可用的截屏后端 // 自动检测当前桌面环境可用的截屏后端
// 会尝试列举 Wayland 全局对象,判断合成器是否支持 wlr-screencopy 协议 // 会尝试列举 Wayland 全局对象,判断合成器是否支持 wlr-screencopy 协议
// 行尾的 ? 是错误传播操作符:若 detect_backend 返回 Err,立即将该错误作为 fn main 的返回值
// 等价于 Go 的 if err != nil { return err },但 Rust 中 ? 适用于任何 Result/Option
let backend = crate::backend_detect::detect_backend(&args)?; let backend = crate::backend_detect::detect_backend(&args)?;
// 根据检测结果进入对应的事件循环 // 根据检测结果进入对应的事件循环
// match 是 Rust 的模式匹配表达式(类比 Go 的 switch 但更强大)
// 每个 => 左侧是模式(这里是枚举变体),右侧是返回 Result<()> 的函数调用
// 由于 fn main 返回 Result<()>,这里直接把 match 表达式作为函数返回值(无分号 + 无 return)
match backend { match backend {
crate::backend_detect::CaptureBackend::WlrScreencopy => run_wlr_screencopy(args), crate::backend_detect::CaptureBackend::WlrScreencopy => run_wlr_screencopy(args),
crate::backend_detect::CaptureBackend::PortalPipeWire => run_portal_pipewire(args), crate::backend_detect::CaptureBackend::PortalPipeWire => run_portal_pipewire(args),
@@ -104,9 +160,13 @@ fn run_wlr_screencopy(args: Args) -> Result<()> {
// Connect to Wayland compositor // Connect to Wayland compositor
// 建立 Wayland 连接并初始化全局注册表 // 建立 Wayland 连接并初始化全局注册表
// 通过环境变量 $WAYLAND_DISPLAY 找到合成器的 Unix socket // 通过环境变量 $WAYLAND_DISPLAY 找到合成器的 Unix socket
// 行尾的 ? 是 fn run_wlr_screencopy 内首次出现的错误传播操作符:
// 若 connect_to_env 返回 Err,立即作为函数返回值向上抛出(类比 Go 的 return err
let conn = Connection::connect_to_env()?; let conn = Connection::connect_to_env()?;
// registry_queue_init 会绑定全局注册表回调, // registry_queue_init 会绑定全局注册表回调,
// 当合成器广播其全局对象(输出、截屏管理器等)时,State 会收到通知 // 当合成器广播其全局对象(输出、截屏管理器等)时,State 会收到通知
// 返回值是元组 (GlobalManager, EventQueue),用 let 解构模式匹配赋值
// mut queue 表示 queue 在后续代码中会被修改(Rust 默认不可变,需 mut 显式声明)
let (gm, mut queue) = registry_queue_init::<State<CapWlrScreencopy>>(&conn)?; let (gm, mut queue) = registry_queue_init::<State<CapWlrScreencopy>>(&conn)?;
let qhandle = queue.handle(); let qhandle = queue.handle();
@@ -119,14 +179,19 @@ fn run_wlr_screencopy(args: Args) -> Result<()> {
// compositor has already sent (may be EAGAIN if nothing yet). // compositor has already sent (may be EAGAIN if nothing yet).
// 获取 Wayland socket 的文件描述符,并消费合成器已发送的事件 // 获取 Wayland socket 的文件描述符,并消费合成器已发送的事件
// 这个 fd 是后续 mio epoll 监听的对象,当合成器写入数据时变为可读 // 这个 fd 是后续 mio epoll 监听的对象,当合成器写入数据时变为可读
// 用 { ... } 块表达式将临时变量 guard 限制在作用域内,作用域结束自动 drop
let wayland_fd = { let wayland_fd = {
let guard = queue let guard = queue
.prepare_read() .prepare_read()
// ok_or_else 是 Option 的方法:None 时调用闭包生成 Err,得到 Result
// || anyhow::anyhow!(...) 是无参数闭包语法(类比 JS 的 () => ...
// 行尾 ? 将 Result<_, Err> 解开为 Err 时立即从函数返回
.ok_or_else(|| anyhow::anyhow!("Failed to prepare Wayland read"))?; .ok_or_else(|| anyhow::anyhow!("Failed to prepare Wayland read"))?;
// 从 prepare_read 的 guard 中获取底层 socket 的原始文件描述符 // 从 prepare_read 的 guard 中获取底层 socket 的原始文件描述符
let fd = guard.connection_fd().as_raw_fd(); let fd = guard.connection_fd().as_raw_fd();
// 尝试非阻塞读取合成器已发送但尚未消费的数据 // 尝试非阻塞读取合成器已发送但尚未消费的数据
// 如果没有数据会返回 EAGAIN,这里用 let _ 忽略 // 如果没有数据会返回 EAGAIN,这里用 let _ 忽略
// let _ = expr 是显式忽略表达式返回值的惯用法,等价于 Go 的 _ = expr
let _ = guard.read(); let _ = guard.read();
fd fd
}; };
@@ -148,6 +213,9 @@ fn run_wlr_screencopy(args: Args) -> Result<()> {
revents: 0, revents: 0,
}; };
// timeout=0 表示非阻塞,立即返回当前 fd 状态 // timeout=0 表示非阻塞,立即返回当前 fd 状态
// unsafe { ... } 是 Rust 的不安全块:内部调用 C 库 libc::poll,需要程序员
// 手动保证 &mut pfd 是有效的可变引用、fd 合法、不并发访问等不变量。
// unsafe 不关闭 Rust 借用检查,只是声明"我对外部 FFI 调用负责"。
let ret = unsafe { libc::poll(&mut pfd, 1, 0) }; let ret = unsafe { libc::poll(&mut pfd, 1, 0) };
tracing::info!( tracing::info!(
"Raw poll on wayland fd={wayland_fd}: ret={ret}, revents={}", "Raw poll on wayland fd={wayland_fd}: ret={ret}, revents={}",
@@ -225,6 +293,8 @@ fn run_wlr_screencopy(args: Args) -> Result<()> {
}); });
// 检查是否收到退出信号 // 检查是否收到退出信号
// for x in &collection 是 Rust 的迭代语法,&events 表示借用 Events(不消费)
// 类比 Go 的 for _, ev := range events {}
for event in &events { for event in &events {
if event.token() == TOKEN_QUIT { if event.token() == TOKEN_QUIT {
tracing::info!("Received quit signal"); tracing::info!("Received quit signal");
@@ -234,8 +304,12 @@ fn run_wlr_screencopy(args: Args) -> Result<()> {
// Wayland fd 可读时,读取并分发合成器事件 // Wayland fd 可读时,读取并分发合成器事件
// 合成器可能发来多种事件:帧数据就绪、输出信息变化、协议错误等 // 合成器可能发来多种事件:帧数据就绪、输出信息变化、协议错误等
// events.iter().any(|e| ...) 是迭代器方法,|e| 是单参数闭包
if events.iter().any(|e| e.token() == TOKEN_WAYLAND) { if events.iter().any(|e| e.token() == TOKEN_WAYLAND) {
// if let Some(x) = opt 是 Option 的模式匹配简写(类比 Go 的 if v, ok := m[k]; ok
if let Some(guard) = read_guard { if let Some(guard) = read_guard {
// match 是本函数内首次出现的多分支模式匹配
// Ok(_) 中下划线表示忽略成功值的具体内容(只关心成功/失败本身)
match guard.read() { match guard.read() {
Ok(_) => { Ok(_) => {
// 读取成功后,dispatch_pending 会将合成器事件 // 读取成功后,dispatch_pending 会将合成器事件
@@ -276,7 +350,10 @@ fn run_wlr_screencopy(args: Args) -> Result<()> {
tracing::info!("Shutting down, flushing encoder..."); tracing::info!("Shutting down, flushing encoder...");
state.fps_limit.flush(); state.fps_limit.flush();
// 仅在编码器已构建完成(Streaming 阶段)时才需要刷新 // 仅在编码器已构建完成(Streaming 阶段)时才需要刷新
// if let 枚举变体模式匹配:Streaming { enc, .. } 解构出内部字段 enc,.. 忽略其他字段
// &mut state.stage 表示可变借用(类比 Go 的指针,但 Rust 编译期保证独占)
if let crate::state::EncConstructionStage::Streaming { enc, .. } = &mut state.stage { if let crate::state::EncConstructionStage::Streaming { enc, .. } = &mut state.stage {
// if let Err(e) = result 只关心失败分支,成功值用 _ 隐式忽略
if let Err(e) = enc.flush() { if let Err(e) = enc.flush() {
tracing::error!("Failed to flush encoder: {e}"); tracing::error!("Failed to flush encoder: {e}");
} }
@@ -297,6 +374,8 @@ fn run_wlr_screencopy(args: Args) -> Result<()> {
/// - 收到退出信号时停止 /// - 收到退出信号时停止
/// 4. 退出时关闭 Portal 连接并释放 PipeWire 资源 /// 4. 退出时关闭 Portal 连接并释放 PipeWire 资源
fn run_portal_pipewire(args: Args) -> Result<()> { fn run_portal_pipewire(args: Args) -> Result<()> {
// 函数内 use 声明:将长路径名简化为局部短名,仅在该函数作用域内生效
// 类比 Go 函数内的局部 import 别名
use crate::state_portal::StatePortal; use crate::state_portal::StatePortal;
tracing::info!("Using Portal/PipeWire backend (KWin/KDE/GNOME)"); tracing::info!("Using Portal/PipeWire backend (KWin/KDE/GNOME)");
@@ -305,6 +384,7 @@ fn run_portal_pipewire(args: Args) -> Result<()> {
// 1. 通过 D-Bus 连接到 XDG Portal 的 ScreenCast 接口 // 1. 通过 D-Bus 连接到 XDG Portal 的 ScreenCast 接口
// 2. 请求用户授权屏幕录制权限 // 2. 请求用户授权屏幕录制权限
// 3. 建立 PipeWire 流连接,准备接收帧数据 // 3. 建立 PipeWire 流连接,准备接收帧数据
// 行尾 ? 是本函数内首次出现的错误传播操作符:失败时立即从 fn run_portal_pipewire 返回 Err
let mut state = StatePortal::new(args)?; let mut state = StatePortal::new(args)?;
// Set up signal handling only (no Wayland fd needed) // Set up signal handling only (no Wayland fd needed)
@@ -352,6 +432,9 @@ fn run_portal_pipewire(args: Args) -> Result<()> {
// poll_and_encode 会从 PipeWire 缓冲区取出帧, // poll_and_encode 会从 PipeWire 缓冲区取出帧,
// 编码为 H.264 并推送。返回 true 表示还有更多帧待处理, // 编码为 H.264 并推送。返回 true 表示还有更多帧待处理,
// 返回 false 表示当前没有帧了,while 循环退出等待下一轮 poll // 返回 false 表示当前没有帧了,while 循环退出等待下一轮 poll
// 外层 if 触发首次取帧(drain_first=true 表示允许阻塞等待),
// 内层 while state.poll_and_encode(false)? {} 是空循环体语法:
// 循环条件持续求值,只要返回 true 就重复,循环体 {} 不做额外事
if state.poll_and_encode(true)? { if state.poll_and_encode(true)? {
while state.poll_and_encode(false)? {} while state.poll_and_encode(false)? {}
} }