feat(backend_detect): add portal/screencopy availability pre-checks
Add check_portal_available() and check_screencopy_available() to probe each backend independently before committing. This enables smarter fallback logic and better diagnostics when no backend is found. Includes Chinese documentation comments.
This commit is contained in:
@@ -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<WlRegistry, GlobalListContents> for RegistryLs {
|
||||
fn event(
|
||||
_state: &mut Self,
|
||||
@@ -30,37 +37,49 @@ impl Dispatch<WlRegistry, GlobalListContents> 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<CaptureBackend> {
|
||||
// 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<bool> {
|
||||
let conn = Connection::connect_to_env()?;
|
||||
let (globals, _queue) = registry_queue_init::<RegistryLs>(&conn)?;
|
||||
|
||||
@@ -73,14 +92,73 @@ pub fn detect_backend(args: &Args) -> Result<CaptureBackend> {
|
||||
// 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<CaptureBackend> {
|
||||
// 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<CaptureBackend> {
|
||||
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"));
|
||||
|
||||
Reference in New Issue
Block a user