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..ab408af550 --- /dev/null +++ b/core/src/event/wayland/session_lock.rs @@ -0,0 +1,17 @@ +use crate::window::Id; +use sctk::reexports::client::protocol::wl_surface::WlSurface; + +/// 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, + /// Session lock surface focused + Focused(WlSurface, Id), + /// Session lock surface unfocused + Unfocused(WlSurface, Id), +} 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..2f55c99abf --- /dev/null +++ b/examples/sctk_session_lock/src/main.rs @@ -0,0 +1,101 @@ +use iced::wayland::session_lock; +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; + +fn main() { + let mut settings = iced::Settings::default(); + settings.initial_surface = InitialSurface::None; + Locker::run(settings).unwrap(); +} + +#[derive(Debug, Clone, Default)] +struct Locker { + max_surface_id: u128, +} + +#[derive(Debug, Clone)] +pub enum Message { + WaylandEvent(WaylandEvent), + Ignore, +} + +impl Locker { + fn next_surface_id(&mut self) -> SurfaceId { + self.max_surface_id += 1; + SurfaceId(self.max_surface_id) + } +} + +impl Application for Locker { + type Executor = iced::executor::Default; + type Message = Message; + type Flags = (); + type Theme = Theme; + + fn new(_flags: ()) -> (Locker, Command) { + ( + Locker { + ..Locker::default() + }, + session_lock::lock(), + ) + } + + fn title(&self) -> String { + String::from("Locker") + } + + 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..759c7bc5e5 --- /dev/null +++ b/runtime/src/command/platform_specific/wayland/session_lock.rs @@ -0,0 +1,83 @@ +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, + }, + /// Destroy lock surface + DestroyLockSurface { + /// unique id for surface + id: Id, + }, + +} + +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, + }, + Action::DestroyLockSurface { + id + } => Action::DestroyLockSurface { id } + } + } +} + +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::LockSurface {{ id: {:?}, output: {:?} }}", + id, output + ), + Action::DestroyLockSurface { + id + } => write!( + f, + "Action::SessionLock::DestroyLockSurface {{ id: {:?} }}", + id + ), + } + } +} diff --git a/sctk/src/application.rs b/sctk/src/application.rs index 796f773da8..a5d48a8d1a 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,42 @@ where } }) } + SctkEvent::SessionLockSurfaceConfigure { id, configure } => { + let wl_surface = &id; + 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, + 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 +937,8 @@ where SctkEvent::NewOutput { .. } | SctkEvent::UpdateOutput { .. } | SctkEvent::RemovedOutput(_) + | SctkEvent::SessionLocked + | SctkEvent::SessionLockFinished ); if remove { let event = sctk_events.remove(i); @@ -1229,7 +1269,7 @@ where // Otherwise cpu goes up in the running application as well as in cosmic-comp if let Some(surface) = state.frame.take() { surface.frame(&queue_handle, surface.clone()); - surface.commit(); + //surface.commit(); } debug.render_started(); @@ -1442,6 +1482,7 @@ pub enum SurfaceIdWrapper { Window(SurfaceId), Popup(SurfaceId), Dnd(SurfaceId), + SessionLock(SurfaceId), } impl SurfaceIdWrapper { @@ -1451,6 +1492,7 @@ impl SurfaceIdWrapper { SurfaceIdWrapper::Window(id) => *id, SurfaceIdWrapper::Popup(id) => *id, SurfaceIdWrapper::Dnd(id) => *id, + SurfaceIdWrapper::SessionLock(id) => *id, } } } @@ -1525,6 +1567,7 @@ where ); } SurfaceIdWrapper::Dnd(_) => {} + SurfaceIdWrapper::SessionLock(_) => {} }; } @@ -2063,6 +2106,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 +2182,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..ed90dbf8fc --- /dev/null +++ b/sctk/src/commands/session_lock.rs @@ -0,0 +1,51 @@ +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, + }, + )), + )) +} + +pub fn destroy_lock_surface(id: SurfaceId) -> Command { + Command::single(command::Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::SessionLock( + wayland::session_lock::Action::DestroyLockSurface { id }, + )), + )) +} diff --git a/sctk/src/event_loop/mod.rs b/sctk/src/event_loop/mod.rs index a8422d694b..7ce861be0f 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, }; @@ -124,7 +125,8 @@ where calloop::channel::Event::Closed => {} }) .unwrap(); - let wayland_source = WaylandSource::new(connection, event_queue); + let wayland_source = + WaylandSource::new(connection.clone(), event_queue); let wayland_dispatcher = calloop::Dispatcher::new( wayland_source, @@ -165,6 +167,7 @@ where event_loop, wayland_dispatcher, state: SctkState { + connection, registry_state, seat_state: SeatState::new(&globals, &qh), output_state: OutputState::new(&globals, &qh), @@ -179,6 +182,9 @@ where &globals, &qh, ) .expect("data device manager is not available"), + session_lock_state: SessionLockState::new(&globals, &qh), + session_lock: None, + session_lock_surfaces: Vec::new(), queue_handle: qh, loop_handle, @@ -1298,6 +1304,40 @@ where } } } + Event::SessionLock(action) => match action { + platform_specific::wayland::session_lock::Action::Lock => { + 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_surfaces.clear(); + self.state.session_lock.take(); + // Make sure server processes unlock before client exits + let _ = self.state.connection.roundtrip(); + } + platform_specific::wayland::session_lock::Action::LockSurface { id, output, _phantom } => { + // TODO how to handle this when there's no lock? + 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.clone(), &output, &self.state.queue_handle); + self.state.session_lock_surfaces.push(lock_surface); + let object_id = wl_surface.id(); + sticky_exit_callback( + IcedSctkEvent::SctkEvent(SctkEvent::WindowEvent { + variant: WindowEventVariant::Created(object_id.clone(), id), + id: wl_surface.clone() }), + &self.state, + &mut control_flow, + &mut callback, + ); + } + } + platform_specific::wayland::session_lock::Action::DestroyLockSurface { id } => { + // TODO + } + } } } diff --git a/sctk/src/event_loop/state.rs b/sctk/src/event_loop/state.rs index 5521f00b54..9f7400f060 100644 --- a/sctk/src/event_loop/state.rs +++ b/sctk/src/event_loop/state.rs @@ -49,7 +49,7 @@ use sctk::{ wl_surface::{self, WlSurface}, wl_touch::WlTouch, }, - QueueHandle, + Connection, QueueHandle, }, }, registry::RegistryState, @@ -58,6 +58,7 @@ use sctk::{ pointer::{CursorIcon, ThemedPointer}, SeatState, }, + session_lock::{SessionLock, SessionLockState, SessionLockSurface}, shell::{ wlr_layer::{ Anchor, KeyboardInteractivity, Layer, LayerShell, LayerSurface, @@ -274,6 +275,8 @@ impl Debug for SctkCopyPasteSource { /// Wrapper to carry sctk state. pub struct SctkState { + pub(crate) connection: Connection, + /// the cursor wl_surface pub(crate) _cursor_surface: Option, /// a memory pool @@ -329,6 +332,9 @@ 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) session_lock_surfaces: Vec, pub(crate) token_ctr: u32, } @@ -822,4 +828,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..040c76a4a4 --- /dev/null +++ b/sctk/src/handlers/session_lock.rs @@ -0,0 +1,49 @@ +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); + } + + fn finished( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _session_lock: SessionLock, + ) { + self.sctk_events.push(SctkEvent::SessionLockFinished); + } + + 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..8a0f712d7f 100755 --- a/sctk/src/sctk_event.rs +++ b/sctk/src/sctk_event.rs @@ -8,7 +8,7 @@ use crate::{ }; use iced_futures::core::event::{ - wayland::{LayerEvent, PopupEvent}, + wayland::{LayerEvent, PopupEvent, SessionLockEvent}, PlatformSpecific, }; use iced_runtime::{ @@ -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)] @@ -495,6 +503,18 @@ impl SctkEvent { )) } SurfaceIdWrapper::Dnd(_) => None, + SurfaceIdWrapper::SessionLock(_) => { + Some(iced_runtime::core::Event::PlatformSpecific( + PlatformSpecific::Wayland( + wayland::Event::SessionLock( + SessionLockEvent::Focused( + surface, + id.inner(), + ), + ), + ), + )) + } }) .into_iter() .chain([iced_runtime::core::Event::PlatformSpecific( @@ -536,6 +556,18 @@ impl SctkEvent { )) } SurfaceIdWrapper::Dnd(_) => None, + SurfaceIdWrapper::SessionLock(_) => { + Some(iced_runtime::core::Event::PlatformSpecific( + PlatformSpecific::Wayland( + wayland::Event::SessionLock( + SessionLockEvent::Focused( + surface, + id.inner(), + ), + ), + ), + )) + } }) .into_iter() .chain([iced_runtime::core::Event::PlatformSpecific( @@ -956,6 +988,25 @@ impl SctkEvent { .collect() } }, + SctkEvent::SessionLocked => { + 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![], } } }