From cac4961595a7fd2f02a021b19c045dd54421416c Mon Sep 17 00:00:00 2001 From: Steve3184 <77789906+Steve3184@users.noreply.github.com> Date: Sat, 28 Feb 2026 15:44:06 +0800 Subject: [PATCH] fix: centralize SESSION_REVOKED sentinel and add retry delay Signed-off-by: Steve3184 <77789906+Steve3184@users.noreply.github.com> --- libs/scrap/src/wayland/pipewire.rs | 23 ++++++++++++++++++----- src/server/wayland.rs | 2 ++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/libs/scrap/src/wayland/pipewire.rs b/libs/scrap/src/wayland/pipewire.rs index 604cdf287..048969977 100644 --- a/libs/scrap/src/wayland/pipewire.rs +++ b/libs/scrap/src/wayland/pipewire.rs @@ -163,6 +163,17 @@ impl std::fmt::Display for GStreamerError { impl Error for GStreamerError {} +#[derive(Debug)] +pub struct SessionRevokedError; + +impl std::fmt::Display for SessionRevokedError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "SESSION_REVOKED") + } +} + +impl Error for SessionRevokedError {} + #[derive(Clone)] pub struct PipeWireCapturable { // connection needs to be kept alive for recording @@ -328,17 +339,19 @@ impl PipeWireRecorder { let _ = pipeline.set_state(gst::State::Null); let is_revoked = if !is_server_running() { - // remote_desktop_portal: always treat sync failure as revocation. + // remote_desktop_portal: the portal opened the fd, so a PLAYING failure + // means the underlying PipeWire node is gone -> session revoked. true } else { - // screencast_portal: only treat as revocation when a stale token exists. + // screencast_portal: only treat as revoked when a persisted restore token + // exists, meaning we were trying to resume a prior session. !config::LocalConfig::get_option(RESTORE_TOKEN_CONF_KEY).is_empty() }; if is_revoked { warn!("[gstreamer] Failed to set PLAYING state, session was likely revoked: {:?}", e); config::LocalConfig::set_option(RESTORE_TOKEN_CONF_KEY.to_owned(), "".to_owned()); - return Err(hbb_common::anyhow::Error::msg(format!("SESSION_REVOKED: GStreamer failed: {:?}", e))); + return Err(hbb_common::anyhow::Error::new(SessionRevokedError)); } else { warn!( "[gstreamer] Failed to set PLAYING state on a fresh screencast session \ @@ -368,11 +381,11 @@ impl PipeWireRecorder { capturable.fd.as_raw_fd(), result, state, pending ); - if let Err(err) = result { + if let Err(_) = result { warn!("[gstreamer] Async pipeline error detected. Session was likely terminated, clearing XDP token..."); config::LocalConfig::set_option(RESTORE_TOKEN_CONF_KEY.to_owned(), "".to_owned()); let _ = pipeline.set_state(gst::State::Null); - return Err(hbb_common::anyhow::Error::msg(format!("SESSION_REVOKED: GStreamer pipeline failed: {:?}", err))); + return Err(hbb_common::anyhow::Error::new(SessionRevokedError)); } } } diff --git a/src/server/wayland.rs b/src/server/wayland.rs index a29ec0bca..bad5eef9d 100644 --- a/src/server/wayland.rs +++ b/src/server/wayland.rs @@ -261,6 +261,8 @@ pub(super) async fn check_init() -> ResultType<()> { if let Err(ref e) = result { if format!("{:?}", e).contains("SESSION_REVOKED") && retry_count < MAX_RETRIES { retry_count += 1; + // Brief pause before re-requesting the portal permission dialog, to avoid back-to-back prompts firing immediately. + tokio::time::sleep(std::time::Duration::from_millis(500)).await; continue; } }