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..50f2c44b75 --- /dev/null +++ b/runtime/src/command/platform_specific/wayland/activation.rs @@ -0,0 +1,67 @@ +use iced_core::window::Id; +use iced_futures::MaybeSend; + +use std::fmt; + +/// xdg-activation Actions +pub enum Action { + /// request an activation token + RequestToken { + /// application id + app_id: Option, + /// window, if provided + window: Option, + /// message generation + message: Box) -> T + Send + Sync + 'static>, + }, + /// 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, + mapper: impl Fn(T) -> A + 'static + MaybeSend + Sync, + ) -> Action + where + T: 'static, + { + match self { + Action::RequestToken { + app_id, + window, + message, + } => Action::RequestToken { + app_id, + window, + message: Box::new(move |token| mapper(message(token))), + }, + 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..98958705cc 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 diff --git a/sctk/src/commands/activation.rs b/sctk/src/commands/activation.rs new file mode 100644 index 0000000000..0efe1236cf --- /dev/null +++ b/sctk/src/commands/activation.rs @@ -0,0 +1,30 @@ +use iced_runtime::command::Command; +use iced_runtime::command::{ + self, + platform_specific::{self, wayland}, +}; +use iced_runtime::window::Id as SurfaceId; + +pub fn request_token( + app_id: Option, + window: Option, + to_message: impl FnOnce(Option) -> Message + Send + Sync + 'static, +) -> Command { + Command::single(command::Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::Activation( + wayland::activation::Action::RequestToken { + app_id, + window, + message: Box::new(to_message), + }, + )), + )) +} + +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..3412ead655 100644 --- a/sctk/src/event_loop/mod.rs +++ b/sctk/src/event_loop/mod.rs @@ -11,6 +11,7 @@ use crate::{ conversion, dpi::LogicalSize, handlers::{ + activation::IcedRequestData, wp_fractional_scaling::FractionalScalingManager, wp_viewporter::ViewporterState, }, @@ -30,6 +31,7 @@ use iced_runtime::command::platform_specific::{ }, }; use sctk::{ + activation::{ActivationState, RequestData}, compositor::CompositorState, data_device_manager::DataDeviceManagerState, output::OutputState, @@ -179,6 +181,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 +1300,54 @@ where } } } - } + }, + Event::Activation(activation_event) => match activation_event { + platform_specific::wayland::activation::Action::RequestToken { app_id, window, message } => { + 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_with_data(&self.state.queue_handle, IcedRequestData::new( + RequestData { + app_id, + seat_and_serial, + surface, + }, + message, + )); + } else { + // if we don't have the global, we don't want to stall the app + sticky_exit_callback( + IcedSctkEvent::UserEvent(message(None)), + &self.state, + &mut control_flow, + &mut callback, + ) + } + }, + 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..e310d1a979 --- /dev/null +++ b/sctk/src/handlers/activation.rs @@ -0,0 +1,60 @@ +use std::sync::Mutex; + +use sctk::{ + activation::{ActivationHandler, RequestData, RequestDataExt}, + delegate_activation, + reexports::client::protocol::{wl_seat::WlSeat, wl_surface::WlSurface}, +}; + +use crate::event_loop::state::SctkState; + +pub struct IcedRequestData { + data: RequestData, + message: Mutex< + Option) -> T + Send + Sync + 'static>>, + >, +} + +impl IcedRequestData { + pub fn new( + data: RequestData, + message: Box) -> T + Send + Sync + 'static>, + ) -> IcedRequestData { + IcedRequestData { + data, + message: Mutex::new(Some(message)), + } + } +} + +impl RequestDataExt for IcedRequestData { + fn app_id(&self) -> Option<&str> { + self.data.app_id() + } + + fn seat_and_serial(&self) -> Option<(&WlSeat, u32)> { + self.data.seat_and_serial() + } + + fn surface(&self) -> Option<&WlSurface> { + self.data.surface() + } +} + +impl ActivationHandler for SctkState { + type RequestData = IcedRequestData; + + fn new_token(&mut self, token: String, data: &Self::RequestData) { + if let Some(message) = data.message.lock().unwrap().take() { + self.pending_user_events.push( + crate::application::Event::SctkEvent( + crate::sctk_event::IcedSctkEvent::UserEvent(message(Some( + token, + ))), + ), + ); + } // else the compositor send two tokens??? + } +} + +delegate_activation!(@ SctkState, IcedRequestData); 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;