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 <noreply@anthropic.com>
This commit is contained in:
xPrimeTime
2026-08-04 05:46:09 +01:00
committed by GitHub
parent a84bad4639
commit 4389687d9d

View File

@@ -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::Path<'static>, 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<Mutex<Option<dbus::Path>>> = 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(())
}