From 218146d5bdb4a3514e8390b3703666c4cef46945 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 20 Oct 2023 16:41:42 -0700 Subject: [PATCH] sctk: WIP session lock surface support --- core/src/event/wayland/mod.rs | 4 + core/src/event/wayland/session_lock.rs | 10 ++ examples/sctk_session_lock/Cargo.toml | 10 ++ examples/sctk_session_lock/src/main.rs | 99 +++++++++++++++++++ .../command/platform_specific/wayland/mod.rs | 8 ++ .../platform_specific/wayland/session_lock.rs | 67 +++++++++++++ sctk/src/application.rs | 47 +++++++++ sctk/src/commands/mod.rs | 1 + sctk/src/commands/session_lock.rs | 43 ++++++++ sctk/src/event_loop/mod.rs | 24 +++++ sctk/src/event_loop/state.rs | 7 ++ sctk/src/handlers/mod.rs | 1 + sctk/src/handlers/session_lock.rs | 51 ++++++++++ sctk/src/sctk_event.rs | 28 ++++++ 14 files changed, 400 insertions(+) create mode 100644 core/src/event/wayland/session_lock.rs create mode 100644 examples/sctk_session_lock/Cargo.toml create mode 100644 examples/sctk_session_lock/src/main.rs create mode 100644 runtime/src/command/platform_specific/wayland/session_lock.rs create mode 100644 sctk/src/commands/session_lock.rs create mode 100644 sctk/src/handlers/session_lock.rs diff --git a/core/src/event/wayland/mod.rs b/core/src/event/wayland/mod.rs index 95b7626f84..bf3ddcb1b0 100644 --- a/core/src/event/wayland/mod.rs +++ b/core/src/event/wayland/mod.rs @@ -3,6 +3,7 @@ mod layer; mod output; mod popup; mod seat; +mod session_lock; mod window; use crate::{time::Instant, window::Id}; @@ -15,6 +16,7 @@ pub use layer::*; pub use output::*; pub use popup::*; pub use seat::*; +pub use session_lock::*; pub use window::*; /// wayland events @@ -36,6 +38,8 @@ pub enum Event { DndOffer(DndOfferEvent), /// Selection Offer events SelectionOffer(SelectionOfferEvent), + /// Session lock events + SessionLock(SessionLockEvent), /// Frame events Frame(Instant, WlSurface, Id), } diff --git a/core/src/event/wayland/session_lock.rs b/core/src/event/wayland/session_lock.rs new file mode 100644 index 0000000000..b0a3fb09a0 --- /dev/null +++ b/core/src/event/wayland/session_lock.rs @@ -0,0 +1,10 @@ +/// session lock events +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SessionLockEvent { + /// Compositor has activated lock + Locked, + /// Lock rejected / canceled by compositor + Finished, + /// Session lock protocol not supported + NotSupported, +} diff --git a/examples/sctk_session_lock/Cargo.toml b/examples/sctk_session_lock/Cargo.toml new file mode 100644 index 0000000000..4a90f5369c --- /dev/null +++ b/examples/sctk_session_lock/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "sctk_session_lock" +version = "0.1.0" +edition = "2021" + +[dependencies] +sctk = { package = "smithay-client-toolkit", git = "https://github.com/smithay/client-toolkit", rev = "828b1eb" } +iced = { path = "../..", default-features = false, features = ["wayland", "debug", "a11y"] } +iced_runtime = { path = "../../runtime" } +env_logger = "0.10" diff --git a/examples/sctk_session_lock/src/main.rs b/examples/sctk_session_lock/src/main.rs new file mode 100644 index 0000000000..2b223f08ee --- /dev/null +++ b/examples/sctk_session_lock/src/main.rs @@ -0,0 +1,99 @@ +use iced::{ + event::wayland::{Event as WaylandEvent, OutputEvent, SessionLockEvent}, + subscription, + wayland::InitialSurface, + widget::{column, container, text}, + window, Application, Color, Command, Element, Length, Subscription, Theme, +}; +use iced_runtime::{ + window::Id as SurfaceId, +}; +use iced::wayland::session_lock; + +fn main() { + let mut settings = iced::Settings::default(); + settings.initial_surface = InitialSurface::None; + DndTest::run(settings).unwrap(); +} + +#[derive(Debug, Clone, Default)] +struct DndTest { + max_surface_id: u128, +} + +#[derive(Debug, Clone)] +pub enum Message { + WaylandEvent(WaylandEvent), + Ignore, +} + +impl DndTest { + fn next_surface_id(&mut self) -> SurfaceId { + self.max_surface_id += 1; + SurfaceId(self.max_surface_id) + } +} + +impl Application for DndTest { + type Executor = iced::executor::Default; + type Message = Message; + type Flags = (); + type Theme = Theme; + + fn new(_flags: ()) -> (DndTest, Command) { + ( + DndTest { + ..DndTest::default() + }, + session_lock::lock(), + ) + } + + fn title(&self) -> String { + String::from("DndTest") + } + + fn update(&mut self, message: Self::Message) -> Command { + match message { + Message::WaylandEvent(evt) => { + match evt { + // TODO handle creation/removal after lock + WaylandEvent::Output(evt, output) => match evt { + OutputEvent::Created(_) => { + return session_lock::get_lock_surface(self.next_surface_id(), output); + } + OutputEvent::Removed => {} + _ => {} + } + WaylandEvent::SessionLock(evt) => match evt { + SessionLockEvent::Locked => { + } + _ => {} // TODO + } + _ => {} + } + } + Message::Ignore => {} + } + Command::none() + } + + fn view(&self, id: window::Id) -> Element { + text("Lock").into() + } + + fn subscription(&self) -> Subscription { + iced::subscription::events_with(|evt, _| { + if let iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(evt)) = evt + { + Some(Message::WaylandEvent(evt)) + } else { + None + } + }) + } + + fn close_requested(&self, _id: window::Id) -> Self::Message { + Message::Ignore + } +} diff --git a/runtime/src/command/platform_specific/wayland/mod.rs b/runtime/src/command/platform_specific/wayland/mod.rs index 7556a93942..1d3b370278 100644 --- a/runtime/src/command/platform_specific/wayland/mod.rs +++ b/runtime/src/command/platform_specific/wayland/mod.rs @@ -8,6 +8,8 @@ pub mod data_device; pub mod layer_surface; /// popup actions pub mod popup; +/// session locks +pub mod session_lock; /// window actions pub mod window; @@ -21,6 +23,8 @@ pub enum Action { Popup(popup::Action), /// data device DataDevice(data_device::Action), + /// session lock + SessionLock(session_lock::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::SessionLock(a) => Action::SessionLock(a.map(f)), } } } @@ -53,6 +58,9 @@ impl Debug for Action { Self::DataDevice(arg0) => { f.debug_tuple("DataDevice").field(arg0).finish() } + Self::SessionLock(arg0) => { + f.debug_tuple("SessionLock").field(arg0).finish() + } } } } diff --git a/runtime/src/command/platform_specific/wayland/session_lock.rs b/runtime/src/command/platform_specific/wayland/session_lock.rs new file mode 100644 index 0000000000..9b8c211679 --- /dev/null +++ b/runtime/src/command/platform_specific/wayland/session_lock.rs @@ -0,0 +1,67 @@ +use std::{fmt, marker::PhantomData}; + +use iced_core::window::Id; +use iced_futures::MaybeSend; + +use sctk::reexports::client::protocol::wl_output::WlOutput; + +/// Session lock action +#[derive(Clone)] +pub enum Action { + /// Request a session lock + Lock, + /// Destroy lock + Unlock, + /// Create lock surface for output + LockSurface { + /// unique id for surface + id: Id, + /// output + output: WlOutput, + /// phantom + _phantom: PhantomData, + }, +} + +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::Lock => Action::Lock, + Action::Unlock => Action::Unlock, + Action::LockSurface { + id, + output, + _phantom, + } => Action::LockSurface { + id, + output, + _phantom: PhantomData, + }, + } + } +} + +impl fmt::Debug for Action { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Action::Lock => write!(f, "Action::SessionLock::Lock"), + Action::Unlock => write!(f, "Action::SessionLock::Unlock"), + Action::LockSurface { + id, + output, + _phantom, + } => write!( + f, + "Action::SessionLock::Unlock {{ id: {:?}, output: {:?} }}", + id, output + ), + } + } +} diff --git a/sctk/src/application.rs b/sctk/src/application.rs index 796f773da8..aba82055bd 100644 --- a/sctk/src/application.rs +++ b/sctk/src/application.rs @@ -92,6 +92,8 @@ pub enum Event { Popup(platform_specific::wayland::popup::Action), /// data device requests from the client DataDevice(platform_specific::wayland::data_device::Action), + /// data session lock requests from the client + SessionLock(platform_specific::wayland::session_lock::Action), /// request sctk to set the cursor of the active pointer SetCursor(Interaction), /// Application Message @@ -768,6 +770,43 @@ where } }) } + SctkEvent::SessionLockSurfaceConfigure { id, configure } => { + let wl_surface = &id; // XXX avoid duplication elsewhere? + if let Some(id) = surface_ids.get(&id.id()) { + compositor_surfaces.entry(id.inner()).or_insert_with(|| { + let mut wrapper = SurfaceDisplayWrapper { + comp_surface: None, + backend: backend.clone(), + wl_surface: wl_surface.clone() + }; + let c_surface = compositor.create_surface(&wrapper, configure.new_size.0, configure.new_size.1); + wrapper.comp_surface.replace(c_surface); + wrapper + }); + + let Some(state) = states.get_mut(&id.inner()) else { + continue; + }; + + // XXX if first configure + let user_interface = build_user_interface( + &application, + user_interface::Cache::default(), + &mut renderer, + // XXX state.logical_size + state.logical_size(), + &state.title, + &mut debug, + *id, + &mut auto_size_surfaces, + &mut ev_proxy + ); + interfaces.insert(id.inner(), user_interface); + + state.set_logical_size(configure.new_size.0 as f64 , configure.new_size.1 as f64); + } + + } _ => {} } } @@ -899,6 +938,8 @@ where SctkEvent::NewOutput { .. } | SctkEvent::UpdateOutput { .. } | SctkEvent::RemovedOutput(_) + | SctkEvent::SessionLocked + | SctkEvent::SessionLockFinished ); if remove { let event = sctk_events.remove(i); @@ -2063,6 +2104,9 @@ 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::SessionLock(session_lock_action))) => { + proxy.send_event(Event::SessionLock(session_lock_action)); + } _ => {} }; None @@ -2136,5 +2180,8 @@ fn event_is_for_surface( SctkEvent::DndOffer { surface, .. } => &surface.id() == object_id, SctkEvent::SelectionOffer(_) => true, SctkEvent::DataSource(_) => true, + SctkEvent::SessionLocked => false, + SctkEvent::SessionLockFinished => false, + SctkEvent::SessionLockSurfaceConfigure { .. } => false, } } diff --git a/sctk/src/commands/mod.rs b/sctk/src/commands/mod.rs index 3c40a938cd..e037c9ed55 100644 --- a/sctk/src/commands/mod.rs +++ b/sctk/src/commands/mod.rs @@ -3,4 +3,5 @@ pub mod data_device; pub mod layer_surface; pub mod popup; +pub mod session_lock; pub mod window; diff --git a/sctk/src/commands/session_lock.rs b/sctk/src/commands/session_lock.rs new file mode 100644 index 0000000000..b93fac9928 --- /dev/null +++ b/sctk/src/commands/session_lock.rs @@ -0,0 +1,43 @@ +use iced_runtime::command::Command; +use iced_runtime::command::{ + self, + platform_specific::{ + self, + wayland::{self, popup::SctkPopupSettings}, + }, +}; +use iced_runtime::window::Id as SurfaceId; +use sctk::reexports::client::protocol::wl_output::WlOutput; + +use std::marker::PhantomData; + +pub fn lock() -> Command { + Command::single(command::Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::SessionLock( + wayland::session_lock::Action::Lock, + )), + )) +} + +pub fn unlock() -> Command { + Command::single(command::Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::SessionLock( + wayland::session_lock::Action::Unlock, + )), + )) +} + +pub fn get_lock_surface( + id: SurfaceId, + output: WlOutput, +) -> Command { + Command::single(command::Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::SessionLock( + wayland::session_lock::Action::LockSurface { + id, + output, + _phantom: PhantomData, + }, + )), + )) +} diff --git a/sctk/src/event_loop/mod.rs b/sctk/src/event_loop/mod.rs index a8422d694b..ae21c9183f 100644 --- a/sctk/src/event_loop/mod.rs +++ b/sctk/src/event_loop/mod.rs @@ -42,6 +42,7 @@ use sctk::{ }, registry::RegistryState, seat::SeatState, + session_lock::SessionLockState, shell::{wlr_layer::LayerShell, xdg::XdgShell, WaylandSurface}, shm::Shm, }; @@ -179,6 +180,8 @@ where &globals, &qh, ) .expect("data device manager is not available"), + session_lock_state: SessionLockState::new(&globals, &qh), + session_lock: None, queue_handle: qh, loop_handle, @@ -1298,6 +1301,27 @@ where } } } + Event::SessionLock(action) => match action { + platform_specific::wayland::session_lock::Action::Lock => { + println!("FOO"); + if self.state.session_lock.is_none() { + // TODO send message on error? When protocol doesn't exist. + self.state.session_lock = self.state.session_lock_state.lock(&self.state.queue_handle).ok(); + } + } + platform_specific::wayland::session_lock::Action::Unlock => { + self.state.session_lock.take(); + } + platform_specific::wayland::session_lock::Action::LockSurface { id, output, _phantom } => { + // TODO how to handle this when no lock? + // Or if we've not gotten `locked` from compositor? + if let Some(lock) = self.state.session_lock.as_ref() { + let wl_surface = self.state.compositor_state.create_surface(&self.state.queue_handle); + let lock_surface = lock.create_lock_surface(wl_surface, &output, &self.state.queue_handle); + std::mem::forget(lock_surface); // TODO + } + } + } } } diff --git a/sctk/src/event_loop/state.rs b/sctk/src/event_loop/state.rs index 5521f00b54..5043f52ded 100644 --- a/sctk/src/event_loop/state.rs +++ b/sctk/src/event_loop/state.rs @@ -58,6 +58,7 @@ use sctk::{ pointer::{CursorIcon, ThemedPointer}, SeatState, }, + session_lock::{SessionLock, SessionLockState}, shell::{ wlr_layer::{ Anchor, KeyboardInteractivity, Layer, LayerShell, LayerSurface, @@ -329,6 +330,8 @@ pub struct SctkState { pub(crate) xdg_shell_state: XdgShell, pub(crate) layer_shell: Option, pub(crate) data_device_manager_state: DataDeviceManagerState, + pub(crate) session_lock_state: SessionLockState, + pub(crate) session_lock: Option, pub(crate) token_ctr: u32, } @@ -822,4 +825,8 @@ where }); Ok((id, wl_surface)) } + + pub fn get_lock_surface() { + todo!() + } } diff --git a/sctk/src/handlers/mod.rs b/sctk/src/handlers/mod.rs index 2e66ee3e7c..dccc221f85 100644 --- a/sctk/src/handlers/mod.rs +++ b/sctk/src/handlers/mod.rs @@ -3,6 +3,7 @@ pub mod compositor; pub mod data_device; pub mod output; pub mod seat; +pub mod session_lock; pub mod shell; pub mod wp_fractional_scaling; pub mod wp_viewporter; diff --git a/sctk/src/handlers/session_lock.rs b/sctk/src/handlers/session_lock.rs new file mode 100644 index 0000000000..2c5ae617ee --- /dev/null +++ b/sctk/src/handlers/session_lock.rs @@ -0,0 +1,51 @@ +use crate::{handlers::SctkState, sctk_event::SctkEvent}; +use sctk::{ + delegate_session_lock, + reexports::client::{Connection, QueueHandle}, + session_lock::{ + SessionLock, SessionLockHandler, SessionLockSurface, + SessionLockSurfaceConfigure, + }, +}; +use std::fmt::Debug; + +impl SessionLockHandler for SctkState { + fn locked( + &mut self, + _conn: &Connection, + qh: &QueueHandle, + session_lock: SessionLock, + ) { + self.sctk_events.push(SctkEvent::SessionLocked); + // TODO + } + + fn finished( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _session_lock: SessionLock, + ) { + self.sctk_events.push(SctkEvent::SessionLockFinished); + // TODO + } + + fn configure( + &mut self, + _conn: &Connection, + qh: &QueueHandle, + session_lock_surface: SessionLockSurface, + configure: SessionLockSurfaceConfigure, + _serial: u32, + ) { + self.sctk_events + .push(SctkEvent::SessionLockSurfaceConfigure { + id: session_lock_surface.wl_surface().clone(), + configure, + }); + self.frame_events + .push(session_lock_surface.wl_surface().clone()); + } +} + +delegate_session_lock!(@ SctkState); diff --git a/sctk/src/sctk_event.rs b/sctk/src/sctk_event.rs index ba10fed6b1..b8c914de78 100755 --- a/sctk/src/sctk_event.rs +++ b/sctk/src/sctk_event.rs @@ -34,6 +34,7 @@ use sctk::{ pointer::{PointerEvent, PointerEventKind}, Capability, }, + session_lock::SessionLockSurfaceConfigure, shell::{ wlr_layer::LayerSurfaceConfigure, xdg::{popup::PopupConfigure, window::WindowConfigure}, @@ -190,6 +191,13 @@ pub enum SctkEvent { surface: WlSurface, }, SelectionOffer(SelectionOfferEvent), + /// session lock events + SessionLocked, + SessionLockFinished, + SessionLockSurfaceConfigure { + id: WlSurface, + configure: SessionLockSurfaceConfigure, + }, } #[derive(Debug, Clone)] @@ -956,6 +964,26 @@ impl SctkEvent { .collect() } }, + SctkEvent::SessionLocked => { + println!("BAR"); + Some(iced_runtime::core::Event::PlatformSpecific( + PlatformSpecific::Wayland(wayland::Event::SessionLock( + wayland::SessionLockEvent::Locked, + )), + )) + .into_iter() + .collect() + } + SctkEvent::SessionLockFinished => { + Some(iced_runtime::core::Event::PlatformSpecific( + PlatformSpecific::Wayland(wayland::Event::SessionLock( + wayland::SessionLockEvent::Finished, + )), + )) + .into_iter() + .collect() + } + SctkEvent::SessionLockSurfaceConfigure { .. } => vec![], } } }