diff --git a/core/src/event/wayland/mod.rs b/core/src/event/wayland/mod.rs index 95b7626f84..1e4dd1a66a 100644 --- a/core/src/event/wayland/mod.rs +++ b/core/src/event/wayland/mod.rs @@ -38,4 +38,6 @@ pub enum Event { SelectionOffer(SelectionOfferEvent), /// Frame events Frame(Instant, WlSurface, Id), + /// Activation event + ActivationToken(String, Option), } diff --git a/runtime/src/command/platform_specific/wayland/activation.rs b/runtime/src/command/platform_specific/wayland/activation.rs new file mode 100644 index 0000000000..82ed66f872 --- /dev/null +++ b/runtime/src/command/platform_specific/wayland/activation.rs @@ -0,0 +1,66 @@ +use iced_core::window::Id; +use iced_futures::MaybeSend; + +use std::{fmt, marker::PhantomData}; + +/// xdg-activation Actions +#[derive(Clone)] +pub enum Action { + /// request an activation token + RequestToken { + /// application id + app_id: Option, + /// window, if provided + window: Option, + /// phantom + _phantom: PhantomData, + }, + /// request a window to be activated + Activate { + /// window to activate + window: Id, + /// activation token + token: String, + }, +} + +impl Action { + /// Maps the output of a window [`Action`] using the provided closure. + pub fn map( + self, + _: impl Fn(T) -> A + 'static + MaybeSend + Sync, + ) -> Action + where + T: 'static, + { + match self { + Action::RequestToken { app_id, window, .. } => { + Action::RequestToken { + app_id, + window, + _phantom: PhantomData, + } + } + Action::Activate { window, token } => { + Action::Activate { window, token } + } + } + } +} + +impl fmt::Debug for Action { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Action::RequestToken { app_id, window, .. } => write!( + f, + "Action::ActivationAction::RequestToken {{ app_id: {:?}, window: {:?} }}", + app_id, window, + ), + Action::Activate { window, token } => write!( + f, + "Action::ActivationAction::Activate {{ window: {:?}, token: {:?} }}", + window, token, + ) + } + } +} diff --git a/runtime/src/command/platform_specific/wayland/mod.rs b/runtime/src/command/platform_specific/wayland/mod.rs index 7556a93942..c5114aca7a 100644 --- a/runtime/src/command/platform_specific/wayland/mod.rs +++ b/runtime/src/command/platform_specific/wayland/mod.rs @@ -2,6 +2,8 @@ use std::fmt::Debug; use iced_futures::MaybeSend; +/// activation Actions +pub mod activation; /// data device Actions pub mod data_device; /// layer surface actions @@ -21,6 +23,8 @@ pub enum Action { Popup(popup::Action), /// data device DataDevice(data_device::Action), + /// activation + Activation(activation::Action), } impl Action { @@ -38,6 +42,7 @@ impl Action { Action::Window(a) => Action::Window(a.map(f)), Action::Popup(a) => Action::Popup(a.map(f)), Action::DataDevice(a) => Action::DataDevice(a.map(f)), + Action::Activation(a) => Action::Activation(a.map(f)), } } } @@ -53,6 +58,9 @@ impl Debug for Action { Self::DataDevice(arg0) => { f.debug_tuple("DataDevice").field(arg0).finish() } + Self::Activation(arg0) => { + f.debug_tuple("Activation").field(arg0).finish() + } } } } diff --git a/sctk/src/application.rs b/sctk/src/application.rs index 796f773da8..78297aacf0 100644 --- a/sctk/src/application.rs +++ b/sctk/src/application.rs @@ -43,8 +43,7 @@ use sctk::{ seat::{keyboard::Modifiers, pointer::PointerEventKind}, }; use std::{ - collections::HashMap, ffi::c_void, hash::Hash, marker::PhantomData, - time::Duration, + collections::HashMap, hash::Hash, marker::PhantomData, time::Duration, }; use wayland_backend::client::ObjectId; use wayland_protocols::wp::viewporter::client::wp_viewport::WpViewport; @@ -92,6 +91,8 @@ pub enum Event { Popup(platform_specific::wayland::popup::Action), /// data device requests from the client DataDevice(platform_specific::wayland::data_device::Action), + /// xdg-activation request from the client + Activation(platform_specific::wayland::activation::Action), /// request sctk to set the cursor of the active pointer SetCursor(Interaction), /// Application Message @@ -2063,6 +2064,13 @@ where command::Action::PlatformSpecific(platform_specific::Action::Wayland(platform_specific::wayland::Action::DataDevice(data_device_action))) => { proxy.send_event(Event::DataDevice(data_device_action)); } + command::Action::PlatformSpecific( + platform_specific::Action::Wayland( + platform_specific::wayland::Action::Activation(activation_action) + ) + ) => { + proxy.send_event(Event::Activation(activation_action)); + } _ => {} }; None @@ -2136,5 +2144,9 @@ fn event_is_for_surface( SctkEvent::DndOffer { surface, .. } => &surface.id() == object_id, SctkEvent::SelectionOffer(_) => true, SctkEvent::DataSource(_) => true, + SctkEvent::Activation { window, .. } => window + .as_ref() + .map(|w| &w.id() == object_id) + .unwrap_or(true), } } diff --git a/sctk/src/commands/activation.rs b/sctk/src/commands/activation.rs new file mode 100644 index 0000000000..28161dc60d --- /dev/null +++ b/sctk/src/commands/activation.rs @@ -0,0 +1,31 @@ +use iced_runtime::command::Command; +use iced_runtime::command::{ + self, + platform_specific::{self, wayland}, +}; +use iced_runtime::window::Id as SurfaceId; + +use std::marker::PhantomData; + +pub fn request_token( + app_id: Option, + window: Option, +) -> Command { + Command::single(command::Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::Activation( + wayland::activation::Action::RequestToken { + app_id, + window, + _phantom: PhantomData, + }, + )), + )) +} + +pub fn activate(window: SurfaceId, token: String) -> Command { + Command::single(command::Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::Activation( + wayland::activation::Action::Activate { window, token }, + )), + )) +} diff --git a/sctk/src/commands/mod.rs b/sctk/src/commands/mod.rs index 3c40a938cd..87f53d9b3e 100644 --- a/sctk/src/commands/mod.rs +++ b/sctk/src/commands/mod.rs @@ -1,5 +1,6 @@ //! Interact with the wayland objects of your application. +pub mod activation; pub mod data_device; pub mod layer_surface; pub mod popup; diff --git a/sctk/src/event_loop/mod.rs b/sctk/src/event_loop/mod.rs index a8422d694b..fc658bbf06 100644 --- a/sctk/src/event_loop/mod.rs +++ b/sctk/src/event_loop/mod.rs @@ -30,6 +30,7 @@ use iced_runtime::command::platform_specific::{ }, }; use sctk::{ + activation::{ActivationState, RequestData}, compositor::CompositorState, data_device_manager::DataDeviceManagerState, output::OutputState, @@ -179,6 +180,7 @@ where &globals, &qh, ) .expect("data device manager is not available"), + activation_state: ActivationState::bind(&globals, &qh).ok(), queue_handle: qh, loop_handle, @@ -1297,7 +1299,43 @@ where } } } - } + }, + Event::Activation(activation_event) => match activation_event { + platform_specific::wayland::activation::Action::RequestToken { app_id, window, _phantom } => { + if let Some(activation_state) = self.state.activation_state.as_ref() { + let (seat_and_serial, surface) = if let Some(id) = window { + let window = self.state.windows.iter().find(|w| w.id == id); + let surface = window.as_ref().map(|w| w.window.wl_surface().clone()); + let seat_and_serial = surface.as_ref().and_then(|surface| { + self.state.seats.first().and_then(|seat| if seat.kbd_focus.as_ref().map(|focus| focus == surface).unwrap_or(false) { + seat.last_kbd_press.as_ref().map(|(_, serial)| (seat.seat.clone(), *serial)) + } else if seat.ptr_focus.as_ref().map(|focus| focus == surface).unwrap_or(false) { + seat.last_ptr_press.as_ref().map(|(_, _, serial)| (seat.seat.clone(), *serial)) + } else { + None + }) + }); + + (seat_and_serial, surface) + } else { + (None, None) + }; + + activation_state.request_token(&self.state.queue_handle, RequestData { + app_id, + seat_and_serial, + surface, + }); + } + }, + platform_specific::wayland::activation::Action::Activate { window, token } => { + if let Some(activation_state) = self.state.activation_state.as_ref() { + if let Some(surface) = self.state.windows.iter().find(|w| w.id == window).map(|w| w.window.wl_surface()) { + activation_state.activate::>(surface, token) + } + } + }, + }, } } diff --git a/sctk/src/event_loop/state.rs b/sctk/src/event_loop/state.rs index 5521f00b54..04db9b3ca6 100644 --- a/sctk/src/event_loop/state.rs +++ b/sctk/src/event_loop/state.rs @@ -30,6 +30,7 @@ use iced_runtime::{ window, }; use sctk::{ + activation::ActivationState, compositor::CompositorState, data_device_manager::{ data_device::DataDevice, @@ -329,6 +330,7 @@ pub struct SctkState { pub(crate) xdg_shell_state: XdgShell, pub(crate) layer_shell: Option, pub(crate) data_device_manager_state: DataDeviceManagerState, + pub(crate) activation_state: Option, pub(crate) token_ctr: u32, } diff --git a/sctk/src/handlers/activation.rs b/sctk/src/handlers/activation.rs new file mode 100644 index 0000000000..50f31bae14 --- /dev/null +++ b/sctk/src/handlers/activation.rs @@ -0,0 +1,20 @@ +use sctk::{ + activation::{ActivationHandler, RequestData, RequestDataExt}, + delegate_activation, +}; + +use crate::event_loop::state::SctkState; + +impl ActivationHandler for SctkState { + type RequestData = RequestData; + + fn new_token(&mut self, token: String, data: &Self::RequestData) { + self.sctk_events + .push(crate::sctk_event::SctkEvent::Activation { + token, + window: data.surface().cloned(), + }) + } +} + +delegate_activation!(@ SctkState); diff --git a/sctk/src/handlers/mod.rs b/sctk/src/handlers/mod.rs index 2e66ee3e7c..f2720046bd 100644 --- a/sctk/src/handlers/mod.rs +++ b/sctk/src/handlers/mod.rs @@ -1,4 +1,5 @@ // handlers +pub mod activation; pub mod compositor; pub mod data_device; pub mod output; diff --git a/sctk/src/sctk_event.rs b/sctk/src/sctk_event.rs index ba10fed6b1..9c40087b2f 100755 --- a/sctk/src/sctk_event.rs +++ b/sctk/src/sctk_event.rs @@ -190,6 +190,10 @@ pub enum SctkEvent { surface: WlSurface, }, SelectionOffer(SelectionOfferEvent), + Activation { + token: String, + window: Option, + }, } #[derive(Debug, Clone)] @@ -956,6 +960,18 @@ impl SctkEvent { .collect() } }, + SctkEvent::Activation { token, window } => { + Some(iced_runtime::core::Event::PlatformSpecific( + PlatformSpecific::Wayland(wayland::Event::ActivationToken( + token, + window + .and_then(|w| surface_ids.get(&w.id())) + .map(SurfaceIdWrapper::inner), + )), + )) + .into_iter() + .collect() + } } } }