app: client-side xdg-activation token requests
Some checks failed
CI / build + test (push) Has been cancelled
CI / cargo audit (push) Has been cancelled

Adds an outbound path so an app can obtain an `xdg-activation-v1` token from the compositor and hand it to a child it is about to launch (the `$XDG_ACTIVATION_TOKEN` convention). Until now ltk only honoured inbound activation (self-activating the main surface from an inherited token); requesting a token for another app was out of scope.
Two new `App` trait methods, both defaulting to no-op: `take_activation_requests` returns the tags the app wants tokens for this iteration, and `on_activation_token` delivers the issued token paired with its tag. The run loop drains the requests right after `poll_external` and calls `ActivationState::request_token`, carrying the tag in `RequestData::app_id`; `ActivationHandler::new_token` reads the tag back out and routes the token to the app through `on_activation_token`. When the compositor never advertised the activation global, each request is answered immediately with an empty token so the caller still proceeds and can fall back to its own matching instead of stalling.
This commit is contained in:
2026-06-09 23:53:31 +02:00
parent cfa0faff26
commit df8fcbf757
3 changed files with 55 additions and 6 deletions

View File

@@ -456,6 +456,34 @@ pub( crate ) fn try_run<A: App>( app: A ) -> Result<(), RunError>
let ext: Vec<_> = data.app.poll_external();
data.pending_msgs.extend( ext );
// Issue any xdg-activation tokens the app asked for. The tag rides
// in `RequestData::app_id` and comes back through
// `ActivationHandler::new_token` (handlers.rs) as the token. When the
// compositor never advertised the global, answer with an empty token
// so the caller still proceeds (falling back to its own matching).
for tag in data.app.take_activation_requests()
{
match data.activation_state
{
Some( ref activation ) =>
{
let req = smithay_client_toolkit::activation::RequestData {
app_id: Some( tag ),
seat_and_serial: None,
surface: data.main.surface.try_wl_surface().cloned(),
};
activation.request_token::<AppData<A>>( &data.qh, req );
}
None =>
{
if let Some( msg ) = data.app.on_activation_token( tag, String::new() )
{
data.pending_msgs.push( msg );
}
}
}
}
// Drain any inbound clipboard payloads delivered by the
// data-device worker thread. Latest writer wins; the channel
// is unbounded but selection payloads are small (capped at