From 55abb5e56d0eeb2866b8e9f5fd105fc98934e05b Mon Sep 17 00:00:00 2001 From: dailz Date: Wed, 27 May 2026 22:07:11 +0800 Subject: [PATCH] fix(backend_detect): use raw zbus for portal check to avoid OnceLock connection poisoning ashpd caches zbus::Connection in a global OnceLock. When check_portal_available() created a Screencast proxy, the connection was cached there. When the function returned and its tokio Runtime dropped, the cached connection became dead. Subsequent setup_portal() calls reused this dead connection and hung forever. Fix: replace ashpd Screencast proxy with direct zbus D-Bus interface check, which does not touch the ashpd global connection cache. Add examples/test_portal.rs for minimal Portal ScreenCast testing. --- Cargo.lock | 1 + Cargo.toml | 1 + examples/test_portal.rs | 68 +++++++++++++++++++++++++++++++++++++++++ src/backend_detect.rs | 48 ++++++++++++++++++----------- 4 files changed, 100 insertions(+), 18 deletions(-) create mode 100644 examples/test_portal.rs diff --git a/Cargo.lock b/Cargo.lock index 43969a6..41665ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1788,6 +1788,7 @@ dependencies = [ "wayland-client", "wayland-protocols", "wayland-protocols-wlr", + "zbus", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 091eb8c..431d9d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ drm = "0.12" drm-fourcc = "2" libc = "0.2" ashpd = { version = "0.13", features = ["tokio", "screencast"] } +zbus = { version = "5", default-features = false, features = ["tokio"] } tokio = { version = "1", features = ["rt"] } pipewire = "0.9" libspa = "0.9" diff --git a/examples/test_portal.rs b/examples/test_portal.rs new file mode 100644 index 0000000..4630a5f --- /dev/null +++ b/examples/test_portal.rs @@ -0,0 +1,68 @@ +use ashpd::desktop::screencast::{CursorMode, Screencast, SelectSourcesOptions, SourceType}; +use ashpd::desktop::PersistMode; +use ashpd::enumflags2::BitFlags; + +fn main() { + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(async { + eprintln!("1. Creating Screencast proxy..."); + let proxy = match Screencast::new().await { + Ok(p) => { + eprintln!(" OK"); + p + } + Err(e) => { + eprintln!(" FAIL: {e}"); + return; + } + }; + + eprintln!("2. Creating session..."); + let session = match proxy.create_session(Default::default()).await { + Ok(s) => { + eprintln!(" OK"); + s + } + Err(e) => { + eprintln!(" FAIL: {e}"); + return; + } + }; + + eprintln!("3. Selecting sources..."); + let sources: BitFlags = SourceType::Monitor.into(); + let result = proxy + .select_sources( + &session, + SelectSourcesOptions::default() + .set_cursor_mode(CursorMode::Embedded) + .set_sources(sources) + .set_multiple(false) + .set_persist_mode(PersistMode::DoNot), + ) + .await; + match result { + Ok(_) => eprintln!(" OK"), + Err(e) => { + eprintln!(" FAIL: {e}"); + return; + } + } + + eprintln!("4. Starting (should show dialog)..."); + let response = match proxy.start(&session, None, Default::default()).await { + Ok(r) => { + eprintln!(" OK"); + r + } + Err(e) => { + eprintln!(" FAIL: {e}"); + return; + } + }; + match response.response() { + Ok(r) => eprintln!(" Got {} stream(s)", r.streams().len()), + Err(e) => eprintln!(" Response error: {e}"), + } + }); +} diff --git a/src/backend_detect.rs b/src/backend_detect.rs index f11e3c3..eb31996 100644 --- a/src/backend_detect.rs +++ b/src/backend_detect.rs @@ -37,11 +37,10 @@ impl Dispatch for RegistryLs { } } -// 通过 D-Bus 检测 XDG Desktop Portal 的 ScreenCast 接口是否可用 -// 尝试创建 Screencast proxy,如果 Portal 服务未运行则返回 false +// CAUTION: must NOT use ashpd here — ashpd caches zbus::Connection in a global +// OnceLock; if the tokio runtime owning that connection is dropped before +// setup_portal() runs, the cached connection becomes dead and hangs forever. fn check_portal_available() -> bool { - use ashpd::desktop::screencast::Screencast; - let rt = match tokio::runtime::Runtime::new() { Ok(rt) => rt, Err(e) => { @@ -51,30 +50,43 @@ fn check_portal_available() -> bool { }; rt.block_on(async { - let proxy = match Screencast::new().await { - Ok(p) => p, + let conn = match zbus::Connection::session().await { + Ok(c) => c, Err(e) => { - tracing::info!("Portal not available: {e}"); + tracing::info!("D-Bus session bus unavailable: {e}"); return false; } }; - // 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:?})"); + let inner: zbus::Proxy = match zbus::proxy::Builder::new(&conn) + .destination("org.freedesktop.portal.Desktop") + .and_then(|b| b.path("/org/freedesktop/portal/desktop")) + .and_then(|b| b.interface("org.freedesktop.portal.ScreenCast")) + { + Ok(b) => match b.build().await { + Ok(p) => p, + Err(e) => { + tracing::info!("Portal ScreenCast interface not available: {e}"); + return false; + } + }, + Err(e) => { + tracing::info!("Portal ScreenCast proxy build failed: {e}"); + return false; + } + }; + + let version = match inner.get_property::("version").await { + Ok(version) => { + tracing::info!("Portal ScreenCast available (version: {version})"); 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}"); + tracing::info!("Portal ScreenCast version query failed: {e}"); false } - } + }; + version }) }