diff --git a/core/Cargo.toml b/core/Cargo.toml index cf183fb19b..5a872a29cc 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -16,7 +16,7 @@ bitflags = "1.2" thiserror = "1" log = "0.4.17" twox-hash = { version = "1.5", default-features = false } -sctk = { package = "smithay-client-toolkit", git = "https://github.com/smithay/client-toolkit", rev = "dc8c4a0", optional = true } +sctk = { package = "smithay-client-toolkit", git = "https://github.com/smithay/client-toolkit", rev = "2e9bf9f", optional = true } [dependencies.palette] version = "0.7" diff --git a/examples/sctk_drag/Cargo.toml b/examples/sctk_drag/Cargo.toml index 49422ca62b..79b984f16d 100644 --- a/examples/sctk_drag/Cargo.toml +++ b/examples/sctk_drag/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -sctk = { package = "smithay-client-toolkit", git = "https://github.com/smithay/client-toolkit", rev = "dc8c4a0" } +sctk = { package = "smithay-client-toolkit", git = "https://github.com/smithay/client-toolkit", rev = "2e9bf9f" } iced = { path = "../..", default-features = false, features = ["wayland", "debug", "a11y"] } iced_style = { path = "../../style" } env_logger = "0.10" diff --git a/examples/sctk_todos/Cargo.toml b/examples/sctk_todos/Cargo.toml index b779f6026e..ec6fa0437a 100644 --- a/examples/sctk_todos/Cargo.toml +++ b/examples/sctk_todos/Cargo.toml @@ -10,7 +10,7 @@ iced = { path = "../..", default-features=false, features = ["async-std", "wayla serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" once_cell = "1.15" -sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "dc8c4a0" } +sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", rev = "2e9bf9f" } iced_style = { path = "../../style" } log = "0.4.17" diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 861dd8f557..e92305a5ea 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -14,7 +14,7 @@ wayland = ["iced_accessibility?/accesskit_unix", "iced_core/wayland", "sctk"] [dependencies] thiserror = "1" -sctk = { package = "smithay-client-toolkit", git = "https://github.com/smithay/client-toolkit", rev = "dc8c4a0", optional = true } +sctk = { package = "smithay-client-toolkit", git = "https://github.com/smithay/client-toolkit", rev = "2e9bf9f", optional = true } [dependencies.iced_core] version = "0.10" 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/Cargo.toml b/sctk/Cargo.toml index e462dc7a36..4325a2382c 100644 --- a/sctk/Cargo.toml +++ b/sctk/Cargo.toml @@ -15,7 +15,7 @@ a11y = ["iced_accessibility", "iced_runtime/a11y"] [dependencies] tracing = "0.1" thiserror = "1.0" -sctk = { package = "smithay-client-toolkit", git = "https://github.com/smithay/client-toolkit", rev = "dc8c4a0" } +sctk = { package = "smithay-client-toolkit", git = "https://github.com/smithay/client-toolkit", rev = "2e9bf9f" } wayland-protocols = { version = "0.31.0", features = [ "staging"]} # sctk = { package = "smithay-client-toolkit", path = "../../fork/client-toolkit/" } raw-window-handle = "0.5" 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 46d1b83228..2ea07963a0 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,11 +31,12 @@ use iced_runtime::command::platform_specific::{ }, }; use sctk::{ + activation::{ActivationState, RequestData}, compositor::CompositorState, data_device_manager::DataDeviceManagerState, output::OutputState, reexports::{ - calloop::{self, EventLoop}, + calloop::{self, EventLoop, PostAction}, client::{ globals::registry_queue_init, protocol::wl_surface::WlSurface, ConnectError, Connection, DispatchError, Proxy, @@ -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, @@ -1157,11 +1160,11 @@ where match self.event_loop.handle().insert_source(read_pipe, move |_, f, state| { let mut dnd_offer = match state.dnd_offer.take() { Some(s) => s, - None => return, + None => return PostAction::Continue, }; let (mime_type, data, token) = match dnd_offer.cur_read.take() { Some(s) => s, - None => return, + None => return PostAction::Continue, }; let mut reader = BufReader::new(f.as_ref()); let consumed = match reader.fill_buf() { @@ -1185,18 +1188,18 @@ where Err(e) if matches!(e.kind(), std::io::ErrorKind::Interrupted) => { dnd_offer.cur_read = Some((mime_type, data, token)); state.dnd_offer = Some(dnd_offer); - return; + return PostAction::Continue; }, Err(e) => { error!("Error reading selection data: {}", e); - loop_handle.remove(token); if !dnd_offer.dropped { state.dnd_offer = Some(dnd_offer); } - return; + return PostAction::Remove; }, }; reader.consume(consumed); + PostAction::Continue }) { Ok(token) => { dnd_offer.cur_read = Some((mime_type.clone(), Vec::new(), token)); @@ -1215,11 +1218,11 @@ where match self.event_loop.handle().insert_source(read_pipe, move |_, f, state| { let selection_offer = match state.selection_offer.as_mut() { Some(s) => s, - None => return, + None => return PostAction::Continue, }; let (mime_type, data, token) = match selection_offer.cur_read.take() { Some(s) => s, - None => return, + None => return PostAction::Continue, }; let mut reader = BufReader::new(f.as_ref()); let consumed = match reader.fill_buf() { @@ -1236,15 +1239,15 @@ where }, Err(e) if matches!(e.kind(), std::io::ErrorKind::Interrupted) => { selection_offer.cur_read = Some((mime_type, data, token)); - return; + return PostAction::Continue; }, Err(e) => { error!("Error reading selection data: {}", e); - loop_handle.remove(token); - return; + return PostAction::Continue; }, }; reader.consume(consumed); + PostAction::Continue }) { Ok(token) => { selection_offer.cur_read = Some((mime_type.clone(), Vec::new(), token)); @@ -1297,7 +1300,57 @@ 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 surface = self.state.windows.iter().find(|w| w.id == id) + .map(|w| w.window.wl_surface().clone()) + .or_else(|| self.state.layer_surfaces.iter().find(|l| l.id == id) + .map(|l| l.surface.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/data_device/data_source.rs b/sctk/src/handlers/data_device/data_source.rs index 4de6343359..2ad4550218 100644 --- a/sctk/src/handlers/data_device/data_source.rs +++ b/sctk/src/handlers/data_device/data_source.rs @@ -3,11 +3,14 @@ use crate::sctk_event::{DataSourceEvent, SctkEvent}; use sctk::data_device_manager::WritePipe; use sctk::{ data_device_manager::data_source::DataSourceHandler, - reexports::client::{ - protocol::{ - wl_data_device_manager::DndAction, wl_data_source::WlDataSource, + reexports::{ + calloop::PostAction, + client::{ + protocol::{ + wl_data_device_manager::DndAction, wl_data_source::WlDataSource, + }, + Connection, QueueHandle, }, - Connection, QueueHandle, }, }; use std::io::{BufWriter, Write}; @@ -62,50 +65,62 @@ impl DataSourceHandler for SctkState { .as_mut() .filter(|s| s.source.inner() == source) { - match self.loop_handle.insert_source(pipe, move |_, f, state| { - let loop_handle = &state.loop_handle; - let selection_source = match state.selection_source.as_mut() { - Some(s) => s, - None => return, - }; - let (data, mut cur_index, token) = - match selection_source.cur_write.take() { + match self.loop_handle.insert_source( + pipe, + move |_, f, state| -> PostAction { + let selection_source = match state.selection_source.as_mut() + { Some(s) => s, - None => return, + None => { + return PostAction::Continue; + } }; - let mut writer = BufWriter::new(f.as_ref()); - let slice = &data.as_slice()[cur_index - ..(cur_index + writer.capacity()).min(data.len())]; - match writer.write(slice) { - Ok(num_written) => { - cur_index += num_written; - if cur_index == data.len() { - loop_handle.remove(token); - } else { + let (data, mut cur_index, token) = + match selection_source.cur_write.take() { + Some(s) => s, + None => { + return PostAction::Continue; + } + }; + let mut writer = BufWriter::new(f.as_ref()); + let slice = &data.as_slice()[cur_index + ..(cur_index + writer.capacity()).min(data.len())]; + match writer.write(slice) { + Ok(num_written) => { + cur_index += num_written; + let done = cur_index == data.len(); + if !done { + selection_source.cur_write = + Some((data, cur_index, token)); + } + if let Err(err) = writer.flush() { + error!("Failed to flush pipe: {}", err); + return PostAction::Remove; + } + if done { + PostAction::Remove + } else { + PostAction::Continue + } + } + Err(e) + if matches!( + e.kind(), + std::io::ErrorKind::Interrupted + ) => + { + // try again selection_source.cur_write = Some((data, cur_index, token)); + PostAction::Continue } - if let Err(err) = writer.flush() { - loop_handle.remove(token); - error!("Failed to flush pipe: {}", err); + Err(_) => { + error!("Failed to write to pipe"); + PostAction::Remove } } - Err(e) - if matches!( - e.kind(), - std::io::ErrorKind::Interrupted - ) => - { - // try again - selection_source.cur_write = - Some((data, cur_index, token)); - } - Err(_) => { - loop_handle.remove(token); - error!("Failed to write to pipe"); - } - }; - }) { + }, + ) { Ok(s) => { my_source.cur_write = Some(( my_source @@ -130,49 +145,54 @@ impl DataSourceHandler for SctkState { Some((source, data)) => (source, data), None => return, }; - match self.loop_handle.insert_source(pipe, move |_, f, state| { - let loop_handle = &state.loop_handle; - let dnd_source = match state.dnd_source.as_mut() { - Some(s) => s, - None => return, - }; - let (data, mut cur_index, token) = - match dnd_source.cur_write.take() { + match self.loop_handle.insert_source( + pipe, + move |_, f, state| -> PostAction { + let loop_handle = &state.loop_handle; + let dnd_source = match state.dnd_source.as_mut() { Some(s) => s, - None => return, + None => return PostAction::Continue, }; - let mut writer = BufWriter::new(f.as_ref()); - let slice = &data.as_slice()[cur_index - ..(cur_index + writer.capacity()).min(data.len())]; - match writer.write(slice) { - Ok(num_written) => { - cur_index += num_written; - if cur_index == data.len() { - loop_handle.remove(token); - } else { + let (data, mut cur_index, token) = + match dnd_source.cur_write.take() { + Some(s) => s, + None => return PostAction::Continue, + }; + let mut writer = BufWriter::new(f.as_ref()); + let slice = &data.as_slice()[cur_index + ..(cur_index + writer.capacity()).min(data.len())]; + match writer.write(slice) { + Ok(num_written) => { + cur_index += num_written; + if cur_index == data.len() { + loop_handle.remove(token); + } else { + dnd_source.cur_write = + Some((data, cur_index, token)); + } + if let Err(err) = writer.flush() { + loop_handle.remove(token); + error!("Failed to flush pipe: {}", err); + } + } + Err(e) + if matches!( + e.kind(), + std::io::ErrorKind::Interrupted + ) => + { + // try again dnd_source.cur_write = Some((data, cur_index, token)); } - if let Err(err) = writer.flush() { + Err(_) => { loop_handle.remove(token); - error!("Failed to flush pipe: {}", err); + error!("Failed to write to pipe"); } - } - Err(e) - if matches!( - e.kind(), - std::io::ErrorKind::Interrupted - ) => - { - // try again - dnd_source.cur_write = Some((data, cur_index, token)); - } - Err(_) => { - loop_handle.remove(token); - error!("Failed to write to pipe"); - } - }; - }) { + }; + PostAction::Continue + }, + ) { Ok(s) => { source.cur_write = Some(( data.from_mime_type(&mime).unwrap_or_default(), 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/widget/Cargo.toml b/widget/Cargo.toml index 1170b18758..11f50a1bd5 100644 --- a/widget/Cargo.toml +++ b/widget/Cargo.toml @@ -22,7 +22,7 @@ wayland = ["sctk"] [dependencies] unicode-segmentation = "1.6" num-traits = "0.2" -sctk = { package = "smithay-client-toolkit", git = "https://github.com/smithay/client-toolkit", rev = "dc8c4a0", optional = true } +sctk = { package = "smithay-client-toolkit", git = "https://github.com/smithay/client-toolkit", rev = "2e9bf9f", optional = true } thiserror = "1" [dependencies.iced_runtime]