diff --git a/src/backend_detect.rs b/src/backend_detect.rs index 9eadb3c..f11e3c3 100644 --- a/src/backend_detect.rs +++ b/src/backend_detect.rs @@ -6,18 +6,25 @@ use wayland_client::{Connection, Dispatch, QueueHandle}; use crate::args::Args; +// 屏幕捕获后端类型 + /// Capture backend to use for screen capture. +/// 屏幕捕获后端枚举 #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CaptureBackend { /// wlroots wlr-screencopy protocol (Sway, Hyprland, etc.) + /// wlroots 合成器的 wlr-screencopy 协议(适用于 Sway、Hyprland 等) WlrScreencopy, /// xdg-desktop-portal with PipeWire (KWin/KDE, GNOME, etc.) + /// XDG 桌面门户 + PipeWire 方式(适用于 KWin/KDE、GNOME 等) PortalPipeWire, } /// Minimal dispatch type for listing Wayland globals during backend detection. +/// 用于后端检测期间列举 Wayland 全局对象的最小化分发类型(无需实际处理事件) struct RegistryLs; +// 为 RegistryLs 实现 Wayland 注册表事件分发(空实现,仅需类型满足 trait 约束) impl Dispatch for RegistryLs { fn event( _state: &mut Self, @@ -30,37 +37,49 @@ impl Dispatch for RegistryLs { } } -/// Detect which capture backend to use. -/// -/// Priority: -/// 1. Explicit `--backend` override from CLI args -/// 2. Auto-detect by checking for `zwlr_screencopy_manager_v1` in Wayland globals -/// -/// The detection Wayland connection is dropped before returning so the actual -/// capture backend can create its own connection without holding two simultaneously. -pub fn detect_backend(args: &Args) -> Result { - // 1. Check explicit override - if let Some(ref backend) = args.backend { - return match backend.as_str() { - "portal" => { - tracing::info!("Backend override: Portal/PipeWire"); - Ok(CaptureBackend::PortalPipeWire) - } - "screencopy" => { - tracing::info!("Backend override: wlr-screencopy"); - Ok(CaptureBackend::WlrScreencopy) - } - other => { - anyhow::bail!( - "Unknown backend '{}'. Use 'screencopy' or 'portal'.", - other - ); +// 通过 D-Bus 检测 XDG Desktop Portal 的 ScreenCast 接口是否可用 +// 尝试创建 Screencast proxy,如果 Portal 服务未运行则返回 false +fn check_portal_available() -> bool { + use ashpd::desktop::screencast::Screencast; + + let rt = match tokio::runtime::Runtime::new() { + Ok(rt) => rt, + Err(e) => { + tracing::warn!("Failed to create tokio runtime for portal check: {e}"); + return false; + } + }; + + rt.block_on(async { + let proxy = match Screencast::new().await { + Ok(p) => p, + Err(e) => { + tracing::info!("Portal not available: {e}"); + return false; } }; - } - // 2. Auto-detect: check if zwlr_screencopy_manager_v1 is available - tracing::info!("Auto-detecting capture backend..."); + // Verify the portal actually exposes ScreenCast capabilities, + // not just that the D-Bus service is running. + match proxy.available_source_types().await { + Ok(types) if !types.is_empty() => { + tracing::info!("Portal ScreenCast available (source types: {types:?})"); + true + } + Ok(types) => { + tracing::info!("Portal ScreenCast proxy exists but no source types available ({types:?})"); + false + } + Err(e) => { + tracing::info!("Portal ScreenCast available_source_types query failed: {e}"); + false + } + } + }) +} + +// 通过 Wayland globals 检测 wlr-screencopy 协议是否可用 +fn check_screencopy_available() -> Result { let conn = Connection::connect_to_env()?; let (globals, _queue) = registry_queue_init::(&conn)?; @@ -73,14 +92,73 @@ pub fn detect_backend(args: &Args) -> Result { // Drop the Wayland connection explicitly before returning. // The screencopy path creates its own connection. Holding two connections // simultaneously is wasteful and may cause issues on some compositors. + // 显式释放检测用的 Wayland 连接,避免与后续捕获后端同时占用两个连接 drop(conn); - if has_screencopy { - tracing::info!("Detected wlr-screencopy support → using WlrScreencopy backend"); - Ok(CaptureBackend::WlrScreencopy) - } else { - tracing::info!("No wlr-screencopy support → using Portal/PipeWire backend"); - Ok(CaptureBackend::PortalPipeWire) + Ok(has_screencopy) +} + +/// Detect which capture backend to use. +/// 检测应使用哪种屏幕捕获后端 +/// +/// Priority: +/// 优先级: +/// 1. Explicit `--backend` override from CLI args +/// 用户通过 `--backend` 命令行参数显式指定 +/// 2. Auto-detect: check wlr-screencopy (Wayland) and Portal (D-Bus) respectively +/// 自动检测:分别通过 Wayland globals 和 D-Bus 检测两个后端 +/// +/// Both backends are checked independently. If neither is available, returns an error. +/// 两个后端独立检测,都不可用时返回错误。 +pub fn detect_backend(args: &Args) -> Result { + // 1. Check explicit override + // 步骤 1:检查用户是否通过命令行参数显式指定了后端 + if let Some(ref backend) = args.backend { + return match backend.as_str() { + "portal" => { + tracing::info!("Backend override: Portal/PipeWire"); + Ok(CaptureBackend::PortalPipeWire) + } + "screencopy" => { + tracing::info!("Backend override: wlr-screencopy"); + Ok(CaptureBackend::WlrScreencopy) + } + other => { + // 未知后端名称,返回错误 + anyhow::bail!( + "Unknown backend '{}'. Use 'screencopy' or 'portal'.", + other + ); + } + }; + } + + // 2. Auto-detect: check both backends independently + // 步骤 2:自动检测 — 分别检测两个后端的可用性 + tracing::info!("Auto-detecting capture backend..."); + + // 检测 wlr-screencopy(通过 Wayland globals) + let has_screencopy = check_screencopy_available()?; + // 检测 Portal(通过 D-Bus) + let has_portal = check_portal_available(); + + // 根据检测结果选择后端,screencopy 优先(性能更好、延迟更低) + match (has_screencopy, has_portal) { + (true, _) => { + tracing::info!("Detected wlr-screencopy support → using WlrScreencopy backend"); + Ok(CaptureBackend::WlrScreencopy) + } + (false, true) => { + tracing::info!("No wlr-screencopy, Portal available → using Portal/PipeWire backend"); + Ok(CaptureBackend::PortalPipeWire) + } + (false, false) => { + anyhow::bail!( + "No supported capture backend found. \ + Install a wlroots compositor (for wlr-screencopy) \ + or xdg-desktop-portal (for Portal/PipeWire)." + ); + } } } @@ -88,6 +166,7 @@ pub fn detect_backend(args: &Args) -> Result { mod tests { use super::*; + // 测试辅助函数:构造指定后端参数的 Args 实例 fn make_args(backend: Option<&str>) -> Args { Args { output: "test.mp4".to_string(), @@ -104,6 +183,7 @@ mod tests { } } + // 测试:显式指定 portal 后端 #[test] fn explicit_portal_backend() { let args = make_args(Some("portal")); @@ -112,6 +192,7 @@ mod tests { assert_eq!(result.unwrap(), CaptureBackend::PortalPipeWire); } + // 测试:显式指定 screencopy 后端 #[test] fn explicit_screencopy_backend() { let args = make_args(Some("screencopy")); @@ -120,6 +201,7 @@ mod tests { assert_eq!(result.unwrap(), CaptureBackend::WlrScreencopy); } + // 测试:无效的后端名称应返回错误 #[test] fn invalid_backend_name_returns_error() { let args = make_args(Some("magic"));