From 4389687d9d08b8957675b923b8a1564fc57e32e4 Mon Sep 17 00:00:00 2001 From: xPrimeTime <101987372+xPrimeTime@users.noreply.github.com> Date: Tue, 4 Aug 2026 05:46:09 +0100 Subject: [PATCH] fix(wayland): subscribe to portal Response before making the request (#15726) `request_remote_desktop` and its response handlers call the portal method first and only then subscribe to the resulting Request's `Response` signal, using the object path returned by the call. The comment above `create_session` already describes why that is wrong: > To avoid a race condition between the caller subscribing to the signal > after receiving the reply for the method call and the signal getting > emitted, a convention for Request object paths has been established that > allows the caller to subscribe to the signal before making the method > call. The code then does the opposite of what the comment says. When the portal emits `Response` before our match rule is installed, the signal is dropped and the flow stalls: `request_remote_desktop` spins its 3-minute wait loop and gives up, so the user sees the screen picker again (or a failure) even when a valid restore token would have restored the session silently. Build the request path from our unique bus name plus the `handle_token` we pass in the call arguments, per the Request documentation, and subscribe before calling. Applied to all five portal calls: CreateSession, SelectSources (both the ScreenCast and post-SelectDevices paths), SelectDevices, and Start. The `handle_token` values are unchanged; they are now named locals so the path and the argument cannot drift apart. Co-authored-by: Claude Opus 5 --- libs/scrap/src/wayland/pipewire.rs | 77 +++++++++++++++++++----------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/libs/scrap/src/wayland/pipewire.rs b/libs/scrap/src/wayland/pipewire.rs index 8859d0d3b..f0852e564 100644 --- a/libs/scrap/src/wayland/pipewire.rs +++ b/libs/scrap/src/wayland/pipewire.rs @@ -507,6 +507,22 @@ where }) } +// The request object path a portal method call will use, derived from our unique +// bus name and the `handle_token` we pass in the call arguments. Knowing it up +// front lets us subscribe to the `Response` signal *before* making the call. +// https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Request.html +fn get_request_path( + conn: &SyncConnection, + handle_token: &str, +) -> Result, dbus::Error> { + let sender = conn.unique_name().trim_start_matches(':').replace('.', "_"); + dbus::Path::new(format!( + "/org/freedesktop/portal/desktop/request/{}/{}", + sender, handle_token + )) + .map_err(|_| dbus::Error::new_failed("Failed to construct portal request path")) +} + pub fn get_portal(conn: &SyncConnection) -> Proxy<&SyncConnection> { conn.with_proxy( "org.freedesktop.portal.Desktop", @@ -632,13 +648,14 @@ pub fn request_remote_desktop( let failure_res = failure.clone(); let session: Arc>> = Arc::new(Mutex::new(None)); let session_res = session.clone(); + let create_session_handle_token = "u1"; args.insert( "session_handle_token".to_string(), - Variant(Box::new("u1".to_string())), + Variant(Box::new(create_session_handle_token.to_string())), ); args.insert( "handle_token".to_string(), - Variant(Box::new("u1".to_string())), + Variant(Box::new(create_session_handle_token.to_string())), ); let mut is_support_restore_token = false; @@ -654,15 +671,9 @@ pub fn request_remote_desktop( // between the caller subscribing to the signal after receiving the reply for the method call and the signal getting emitted, // a convention for Request object paths has been established that allows // the caller to subscribe to the signal before making the method call. - let path; - if is_server_running() { - path = screencast_portal::create_session(&portal, args)?; - } else { - path = remote_desktop_portal::create_session(&portal, args)?; - } handle_response( &conn, - path, + get_request_path(&conn, create_session_handle_token)?, on_create_session_response( fd.clone(), streams.clone(), @@ -673,6 +684,11 @@ pub fn request_remote_desktop( ), failure_res.clone(), )?; + if is_server_running() { + let _ = screencast_portal::create_session(&portal, args)?; + } else { + let _ = remote_desktop_portal::create_session(&portal, args)?; + } // wait 3 minutes for user interaction for _ in 0..1800 { @@ -751,9 +767,10 @@ fn on_create_session_response( // persist_mode may be configured by the user. args.insert("persist_mode".to_string(), Variant(Box::new(2u32))); } + let select_sources_handle_token = "u3"; args.insert( "handle_token".to_string(), - Variant(Box::new("u3".to_string())), + Variant(Box::new(select_sources_handle_token.to_string())), ); // https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.ScreenCast.html if is_server_running() { @@ -769,42 +786,43 @@ fn on_create_session_response( }); } - let path = portal.select_sources(ses.clone(), args)?; handle_response( c, - path, + get_request_path(c, select_sources_handle_token)?, on_select_sources_response( fd.clone(), streams.clone(), failure.clone(), - ses, + ses.clone(), is_support_restore_token, ), failure.clone(), )?; + let _ = portal.select_sources(ses.clone(), args)?; } else { // TODO: support persist_mode for remote_desktop_portal // https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.RemoteDesktop.html + let select_devices_handle_token = "u2"; args.insert( "handle_token".to_string(), - Variant(Box::new("u2".to_string())), + Variant(Box::new(select_devices_handle_token.to_string())), ); args.insert("types".to_string(), Variant(Box::new(7u32))); - let path = portal.select_devices(ses.clone(), args)?; handle_response( c, - path, + get_request_path(c, select_devices_handle_token)?, on_select_devices_response( fd.clone(), streams.clone(), failure.clone(), - ses, + ses.clone(), is_support_restore_token, ), failure.clone(), )?; + let _ = portal.select_devices(ses.clone(), args)?; } Ok(()) @@ -825,9 +843,10 @@ fn on_select_devices_response( move |_: OrgFreedesktopPortalRequestResponse, c, _| { let portal = get_portal(c); let mut args: PropMap = HashMap::new(); + let select_sources_handle_token = "u3"; args.insert( "handle_token".to_string(), - Variant(Box::new("u3".to_string())), + Variant(Box::new(select_sources_handle_token.to_string())), ); // https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.ScreenCast.html if is_server_running() { @@ -836,19 +855,19 @@ fn on_select_devices_response( args.insert("types".into(), Variant(Box::new(1u32))); //| 2u32))); let session = session.clone(); - let path = portal.select_sources(session.clone(), args)?; handle_response( c, - path, + get_request_path(c, select_sources_handle_token)?, on_select_sources_response( fd.clone(), streams.clone(), failure.clone(), - session, + session.clone(), is_support_restore_token, ), failure.clone(), )?; + let _ = portal.select_sources(session.clone(), args)?; Ok(()) } @@ -868,19 +887,14 @@ fn on_select_sources_response( move |_: OrgFreedesktopPortalRequestResponse, c, _| { let portal = get_portal(c); let mut args: PropMap = HashMap::new(); + let start_handle_token = "u4"; args.insert( "handle_token".to_string(), - Variant(Box::new("u4".to_string())), + Variant(Box::new(start_handle_token.to_string())), ); - let path; - if is_server_running() { - path = screencast_portal::start(&portal, session.clone(), "", args)?; - } else { - path = remote_desktop_portal::start(&portal, session.clone(), "", args)?; - } handle_response( c, - path, + get_request_path(c, start_handle_token)?, on_start_response( fd.clone(), streams.clone(), @@ -889,6 +903,11 @@ fn on_select_sources_response( ), failure.clone(), )?; + if is_server_running() { + let _ = screencast_portal::start(&portal, session.clone(), "", args)?; + } else { + let _ = remote_desktop_portal::start(&portal, session.clone(), "", args)?; + } Ok(()) }