From 6cbbce773f25391eeee1ba3dd9ed81210a777a5e Mon Sep 17 00:00:00 2001 From: drendog Date: Wed, 7 Jan 2026 15:10:05 +0100 Subject: [PATCH] refactor: split lock manager --- crates/adapters/src/lib.rs | 2 +- .../wayland/event_handling/app_dispatcher.rs | 2 +- crates/adapters/src/wayland/ops.rs | 2 +- .../src/wayland/session_lock/lock_manager.rs | 867 ------------------ .../wayland/session_lock/manager/callbacks.rs | 80 ++ .../session_lock/manager/input_handling.rs | 331 +++++++ .../wayland/session_lock/manager/lifecycle.rs | 28 + .../src/wayland/session_lock/manager/mod.rs | 397 ++++++++ .../wayland/session_lock/manager/rendering.rs | 11 + .../src/wayland/session_lock/manager/state.rs | 248 +++++ .../adapters/src/wayland/session_lock/mod.rs | 4 +- crates/adapters/src/wayland/shell_adapter.rs | 2 +- .../src/wayland/surfaces/app_state.rs | 2 +- 13 files changed, 1103 insertions(+), 873 deletions(-) delete mode 100644 crates/adapters/src/wayland/session_lock/lock_manager.rs create mode 100644 crates/adapters/src/wayland/session_lock/manager/callbacks.rs create mode 100644 crates/adapters/src/wayland/session_lock/manager/input_handling.rs create mode 100644 crates/adapters/src/wayland/session_lock/manager/lifecycle.rs create mode 100644 crates/adapters/src/wayland/session_lock/manager/mod.rs create mode 100644 crates/adapters/src/wayland/session_lock/manager/rendering.rs create mode 100644 crates/adapters/src/wayland/session_lock/manager/state.rs diff --git a/crates/adapters/src/lib.rs b/crates/adapters/src/lib.rs index 5ba1a78..9583629 100644 --- a/crates/adapters/src/lib.rs +++ b/crates/adapters/src/lib.rs @@ -8,7 +8,7 @@ pub use rendering::femtovg::popup_window::PopupWindow; pub use wayland::config::{MultiSurfaceConfig, ShellSurfaceConfig, WaylandSurfaceConfig}; pub use wayland::ops::WaylandSystemOps; -pub use wayland::session_lock::lock_manager::{LockSurfaceOutputContext, OutputFilter}; +pub use wayland::session_lock::{LockSurfaceOutputContext, OutputFilter}; pub use wayland::shell_adapter::WaylandShellSystem; pub use wayland::surfaces::app_state::AppState; pub use wayland::surfaces::popup_manager::PopupManager; diff --git a/crates/adapters/src/wayland/event_handling/app_dispatcher.rs b/crates/adapters/src/wayland/event_handling/app_dispatcher.rs index 8c65b99..dcfb6cc 100644 --- a/crates/adapters/src/wayland/event_handling/app_dispatcher.rs +++ b/crates/adapters/src/wayland/event_handling/app_dispatcher.rs @@ -1,4 +1,4 @@ -use crate::wayland::session_lock::lock_manager::LockSurfaceOutputContext; +use crate::wayland::session_lock::LockSurfaceOutputContext; use crate::wayland::surfaces::app_state::AppState; use crate::wayland::surfaces::display_metrics::DisplayMetrics; use crate::wayland::surfaces::surface_state::SurfaceState; diff --git a/crates/adapters/src/wayland/ops.rs b/crates/adapters/src/wayland/ops.rs index 43b132e..0229b62 100644 --- a/crates/adapters/src/wayland/ops.rs +++ b/crates/adapters/src/wayland/ops.rs @@ -1,6 +1,6 @@ use crate::errors::Result; use crate::wayland::config::ShellSurfaceConfig; -use crate::wayland::session_lock::lock_manager::OutputFilter; +use crate::wayland::session_lock::OutputFilter; use crate::wayland::surfaces::app_state::AppState; use layer_shika_domain::value_objects::lock_config::LockConfig; use layer_shika_domain::value_objects::lock_state::LockState; diff --git a/crates/adapters/src/wayland/session_lock/lock_manager.rs b/crates/adapters/src/wayland/session_lock/lock_manager.rs deleted file mode 100644 index c077212..0000000 --- a/crates/adapters/src/wayland/session_lock/lock_manager.rs +++ /dev/null @@ -1,867 +0,0 @@ -use crate::errors::{LayerShikaError, Result}; -use crate::rendering::femtovg::main_window::FemtoVGWindow; -use crate::rendering::femtovg::renderable_window::RenderableWindow; -use crate::rendering::slint_integration::platform::CustomSlintPlatform; -use crate::wayland::session_lock::lock_context::{LockSurfaceParams, SessionLockContext}; -use crate::wayland::session_lock::lock_surface::LockSurface; -use crate::wayland::surfaces::app_state::AppState; -use crate::wayland::surfaces::component_state::ComponentState; -use crate::wayland::surfaces::display_metrics::DisplayMetrics; -use crate::wayland::surfaces::keyboard_state::{KeyboardState, keysym_to_text}; -use crate::wayland::surfaces::pointer_utils::wayland_button_to_slint; -use layer_shika_domain::surface_dimensions::SurfaceDimensions; -use layer_shika_domain::value_objects::lock_config::LockConfig; -use layer_shika_domain::value_objects::lock_state::LockState; -use layer_shika_domain::value_objects::output_handle::OutputHandle; -use layer_shika_domain::value_objects::output_info::OutputInfo; -use log::info; -use slint::{ - LogicalPosition, LogicalSize, SharedString, WindowPosition, WindowSize, - platform::{WindowAdapter, WindowEvent, femtovg_renderer::FemtoVGRenderer}, -}; -use slint_interpreter::{CompilationResult, ComponentDefinition, ComponentInstance, Value}; -use std::collections::HashMap; -use std::rc::Rc; -use wayland_client::{ - Proxy, QueueHandle, WEnum, - backend::ObjectId, - protocol::{wl_keyboard, wl_output::WlOutput, wl_pointer, wl_surface::WlSurface}, -}; -use wayland_protocols::ext::session_lock::v1::client::ext_session_lock_v1::ExtSessionLockV1; -use xkbcommon::xkb; - -pub type LockCallbackHandler = Rc Value>; -pub type OutputFilter = Rc< - dyn Fn( - &str, - OutputHandle, - Option<&OutputInfo>, - Option, - Option, - ) -> bool, ->; - -#[derive(Clone)] -pub(crate) struct LockCallback { - name: String, - handler: LockCallbackHandler, - filter: Option, -} - -impl LockCallback { - pub fn new(name: impl Into, handler: LockCallbackHandler) -> Self { - Self { - name: name.into(), - handler, - filter: None, - } - } - - pub fn with_filter( - name: impl Into, - handler: LockCallbackHandler, - filter: OutputFilter, - ) -> Self { - Self { - name: name.into(), - handler, - filter: Some(filter), - } - } - - pub fn should_apply( - &self, - component_name: &str, - output_handle: OutputHandle, - output_info: Option<&OutputInfo>, - primary_handle: Option, - active_handle: Option, - ) -> bool { - self.filter.as_ref().map_or_else( - || true, - |f| { - f( - component_name, - output_handle, - output_info, - primary_handle, - active_handle, - ) - }, - ) - } - - pub fn apply_to(&self, component: &ComponentInstance) -> Result<()> { - let handler = Rc::clone(&self.handler); - component - .set_callback(&self.name, move |args| handler(args)) - .map_err(|e| LayerShikaError::InvalidInput { - message: format!("Failed to register callback '{}': {e}", self.name), - }) - } -} - -struct ActiveLockSurface { - surface: LockSurface, - window: Rc, - component: Option, - scale_factor: f32, - has_fractional_scale: bool, -} - -pub struct LockSurfaceOutputContext { - pub output_handle: OutputHandle, - pub output_info: Option, - pub primary_handle: Option, - pub active_handle: Option, -} - -struct LockConfigureContext { - scale_factor: f32, - component_definition: ComponentDefinition, - compilation_result: Option>, - platform: Rc, - callbacks: Vec, - component_name: String, - output_handle: OutputHandle, - output_info: Option, - primary_handle: Option, - active_handle: Option, -} - -impl ActiveLockSurface { - fn new(surface: LockSurface, window: Rc) -> Self { - Self { - has_fractional_scale: surface.fractional_scale().is_some(), - surface, - window, - component: None, - scale_factor: 1.0, - } - } - - fn handle_configure( - &mut self, - serial: u32, - width: u32, - height: u32, - context: &LockConfigureContext, - ) -> Result<()> { - self.surface.handle_configure(serial, width, height); - self.scale_factor = context.scale_factor; - let dimensions = match SurfaceDimensions::calculate(width, height, context.scale_factor) { - Ok(dimensions) => dimensions, - Err(err) => { - info!("Failed to calculate lock surface dimensions: {err}"); - return Ok(()); - } - }; - let scaling_mode = self.scaling_mode(); - info!( - "Lock surface dimensions: logical {}x{}, physical {}x{}, scale {}, mode {:?}", - dimensions.logical_width(), - dimensions.logical_height(), - dimensions.physical_width(), - dimensions.physical_height(), - context.scale_factor, - scaling_mode - ); - self.configure_window(&dimensions, scaling_mode, context.scale_factor); - self.configure_surface(&dimensions, scaling_mode); - - if self.component.is_none() { - context.platform.add_window(Rc::clone(&self.window)); - let component = ComponentState::new( - context.component_definition.clone(), - context.compilation_result.clone(), - &self.window, - )?; - self.window - .window() - .dispatch_event(WindowEvent::WindowActiveChanged(true)); - for callback in &context.callbacks { - if callback.should_apply( - &context.component_name, - context.output_handle, - context.output_info.as_ref(), - context.primary_handle, - context.active_handle, - ) { - if let Err(err) = callback.apply_to(component.component_instance()) { - info!( - "Failed to register lock callback '{}': {err}", - callback.name - ); - } else { - info!("Registered lock callback '{}'", callback.name); - } - } else { - info!( - "Skipping callback '{}' due to selector filter (output {:?})", - callback.name, context.output_handle - ); - } - } - self.component = Some(component); - } - - RenderableWindow::request_redraw(self.window.as_ref()); - Ok(()) - } - - fn render_frame_if_dirty(&self) -> Result<()> { - self.window.render_frame_if_dirty() - } - - fn handle_fractional_scale(&mut self, scale_120ths: u32) { - let scale_factor = DisplayMetrics::scale_factor_from_120ths(scale_120ths); - self.scale_factor = scale_factor; - if self.surface.width() == 0 || self.surface.height() == 0 { - return; - } - let Ok(dimensions) = - SurfaceDimensions::calculate(self.surface.width(), self.surface.height(), scale_factor) - else { - return; - }; - let scaling_mode = self.scaling_mode(); - self.configure_window(&dimensions, scaling_mode, scale_factor); - self.configure_surface(&dimensions, scaling_mode); - RenderableWindow::request_redraw(self.window.as_ref()); - } - - fn apply_callback(&self, callback: &LockCallback) { - if let Some(component) = self.component.as_ref() { - if let Err(err) = callback.apply_to(component.component_instance()) { - info!( - "Failed to register lock callback '{}': {err}", - callback.name - ); - } - } - } - - fn scaling_mode(&self) -> LockScalingMode { - if self.surface.has_fractional_scale() && self.surface.has_viewport() { - LockScalingMode::FractionalWithViewport - } else if self.surface.has_fractional_scale() { - LockScalingMode::FractionalOnly - } else { - LockScalingMode::Integer - } - } - - #[allow(clippy::cast_precision_loss)] - fn configure_window( - &self, - dimensions: &SurfaceDimensions, - mode: LockScalingMode, - scale_factor: f32, - ) { - match mode { - LockScalingMode::FractionalWithViewport | LockScalingMode::FractionalOnly => { - RenderableWindow::set_scale_factor(self.window.as_ref(), scale_factor); - self.window.set_size(WindowSize::Logical(LogicalSize::new( - dimensions.logical_width() as f32, - dimensions.logical_height() as f32, - ))); - } - LockScalingMode::Integer => { - RenderableWindow::set_scale_factor(self.window.as_ref(), scale_factor); - self.window - .set_size(WindowSize::Physical(slint::PhysicalSize::new( - dimensions.physical_width(), - dimensions.physical_height(), - ))); - } - } - } - - fn configure_surface(&self, dimensions: &SurfaceDimensions, mode: LockScalingMode) { - match mode { - LockScalingMode::FractionalWithViewport => { - self.surface.configure_fractional_viewport( - dimensions.logical_width(), - dimensions.logical_height(), - ); - } - LockScalingMode::FractionalOnly | LockScalingMode::Integer => { - self.surface - .configure_buffer_scale(dimensions.buffer_scale()); - } - } - } - - #[allow(clippy::cast_possible_truncation)] - fn to_logical_position(&self, surface_x: f64, surface_y: f64) -> LogicalPosition { - if self.has_fractional_scale { - let x = surface_x as f32; - let y = surface_y as f32; - LogicalPosition::new(x, y) - } else { - let x = (surface_x / f64::from(self.scale_factor)) as f32; - let y = (surface_y / f64::from(self.scale_factor)) as f32; - LogicalPosition::new(x, y) - } - } - - fn dispatch_event(&self, event: WindowEvent) { - self.window.window().dispatch_event(event); - } - - fn window_rc(&self) -> Rc { - Rc::clone(&self.window) - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum LockScalingMode { - FractionalWithViewport, - FractionalOnly, - Integer, -} - -pub struct SessionLockManager { - context: Rc, - session_lock: Option, - lock_surfaces: HashMap, - state: LockState, - config: LockConfig, - component_definition: ComponentDefinition, - compilation_result: Option>, - platform: Rc, - callbacks: Vec, - active_pointer_surface_id: Option, - keyboard_focus_surface_id: Option, - current_pointer_position: LogicalPosition, - accumulated_axis_x: f32, - accumulated_axis_y: f32, -} - -impl SessionLockManager { - #[must_use] - pub fn new( - context: Rc, - component_definition: ComponentDefinition, - compilation_result: Option>, - platform: Rc, - config: LockConfig, - ) -> Self { - Self { - context, - session_lock: None, - lock_surfaces: HashMap::new(), - state: LockState::Inactive, - config, - component_definition, - compilation_result, - platform, - callbacks: Vec::new(), - active_pointer_surface_id: None, - keyboard_focus_surface_id: None, - current_pointer_position: LogicalPosition::new(0.0, 0.0), - accumulated_axis_x: 0.0, - accumulated_axis_y: 0.0, - } - } - - #[must_use] - pub const fn state(&self) -> LockState { - self.state - } - - pub fn activate( - &mut self, - outputs: impl IntoIterator, - queue_handle: &QueueHandle, - ) -> Result<()> { - if !self.state.can_activate() { - return Err(LayerShikaError::InvalidInput { - message: format!("Session lock cannot activate in state {:?}", self.state), - }); - } - - self.config.validate()?; - - let session_lock = self.context.lock_manager().lock(queue_handle, ()); - self.session_lock = Some(session_lock.clone()); - self.state = LockState::Locking; - - for output in outputs { - let params = LockSurfaceParams { - compositor: self.context.compositor(), - output: &output, - session_lock: &session_lock, - fractional_scale_manager: self.context.fractional_scale_manager(), - viewporter: self.context.viewporter(), - queue_handle, - }; - let surface = LockSurface::create(¶ms); - let surface_id = surface.surface_id(); - let window = self.create_window(&surface_id)?; - self.lock_surfaces - .insert(output.id(), ActiveLockSurface::new(surface, window)); - } - - Ok(()) - } - - pub fn handle_locked(&mut self) { - if self.state == LockState::Locking { - info!("Session lock transitioned to Locked"); - self.state = LockState::Locked; - } - } - - pub fn deactivate(&mut self) -> Result<()> { - if !self.state.can_deactivate() { - return Err(LayerShikaError::InvalidInput { - message: format!("Session lock cannot deactivate in state {:?}", self.state), - }); - } - - let Some(session_lock) = self.session_lock.take() else { - return Err(LayerShikaError::InvalidInput { - message: "Session lock object missing during deactivate".to_string(), - }); - }; - - for surface in self.lock_surfaces.values() { - surface.surface.destroy(); - } - session_lock.unlock_and_destroy(); - self.lock_surfaces.clear(); - self.active_pointer_surface_id = None; - self.keyboard_focus_surface_id = None; - self.state = LockState::Unlocking; - Ok(()) - } - - pub fn handle_finished(&mut self) { - info!("Session lock finished"); - self.lock_surfaces.clear(); - self.session_lock = None; - self.state = LockState::Inactive; - self.active_pointer_surface_id = None; - self.keyboard_focus_surface_id = None; - } - - pub fn add_output( - &mut self, - output: &WlOutput, - queue_handle: &QueueHandle, - ) -> Result<()> { - if self.state != LockState::Locked { - return Ok(()); - } - - let output_id = output.id(); - if self.lock_surfaces.contains_key(&output_id) { - return Ok(()); - } - - let Some(session_lock) = self.session_lock.as_ref() else { - return Err(LayerShikaError::InvalidInput { - message: "Session lock object missing during output hotplug".to_string(), - }); - }; - - info!("Adding lock surface for output {output_id:?}"); - let params = LockSurfaceParams { - compositor: self.context.compositor(), - output, - session_lock, - fractional_scale_manager: self.context.fractional_scale_manager(), - viewporter: self.context.viewporter(), - queue_handle, - }; - let surface = LockSurface::create(¶ms); - let surface_id = surface.surface_id(); - let window = self.create_window(&surface_id)?; - self.lock_surfaces - .insert(output_id, ActiveLockSurface::new(surface, window)); - Ok(()) - } - - pub fn remove_output(&mut self, output_id: &ObjectId) { - if let Some(surface) = self.lock_surfaces.remove(output_id) { - let surface_id = surface.surface.surface_id(); - if self.active_pointer_surface_id.as_ref() == Some(&surface_id) { - self.active_pointer_surface_id = None; - } - if self.keyboard_focus_surface_id.as_ref() == Some(&surface_id) { - self.keyboard_focus_surface_id = None; - } - drop(surface); - } - } - - fn find_surface_by_lock_surface_id_mut( - &mut self, - lock_surface_id: &ObjectId, - ) -> Option<&mut ActiveLockSurface> { - self.lock_surfaces - .values_mut() - .find(|surface| surface.surface.lock_surface_id() == *lock_surface_id) - } - - fn find_surface_by_surface_id(&self, surface_id: &ObjectId) -> Option<&ActiveLockSurface> { - self.lock_surfaces - .values() - .find(|surface| surface.surface.surface_id() == *surface_id) - } - - pub fn find_output_id_for_lock_surface(&self, lock_surface_id: &ObjectId) -> Option { - self.lock_surfaces - .iter() - .find(|(_, surface)| surface.surface.surface_id() == *lock_surface_id) - .map(|(id, _)| id.clone()) - } - - pub fn handle_configure( - &mut self, - lock_surface_id: &ObjectId, - serial: u32, - width: u32, - height: u32, - output_ctx: LockSurfaceOutputContext, - ) -> Result<()> { - let component_name = self.component_definition.name().to_string(); - - let context = LockConfigureContext { - scale_factor: self.config.scale_factor.value(), - component_definition: self.component_definition.clone(), - compilation_result: self.compilation_result.clone(), - platform: Rc::clone(&self.platform), - callbacks: self.callbacks.clone(), - component_name, - output_handle: output_ctx.output_handle, - output_info: output_ctx.output_info, - primary_handle: output_ctx.primary_handle, - active_handle: output_ctx.active_handle, - }; - - let Some(surface) = self.find_surface_by_lock_surface_id_mut(lock_surface_id) else { - return Ok(()); - }; - - surface.handle_configure(serial, width, height, &context) - } - - pub fn render_frames(&self) -> Result<()> { - for surface in self.lock_surfaces.values() { - surface.render_frame_if_dirty()?; - } - Ok(()) - } - - pub(crate) fn register_callback(&mut self, callback: LockCallback) { - for surface in self.lock_surfaces.values() { - surface.apply_callback(&callback); - } - self.callbacks.push(callback); - } - - pub fn handle_fractional_scale(&mut self, fractional_scale_id: &ObjectId, scale_120ths: u32) { - for surface in self.lock_surfaces.values_mut() { - let matches = surface - .surface - .fractional_scale() - .is_some_and(|fs| fs.id() == *fractional_scale_id); - if matches { - surface.handle_fractional_scale(scale_120ths); - } - } - } - - pub fn is_lock_surface(&self, surface_id: &ObjectId) -> bool { - self.find_surface_by_surface_id(surface_id).is_some() - } - - pub const fn has_active_pointer(&self) -> bool { - self.active_pointer_surface_id.is_some() - } - - pub const fn has_keyboard_focus(&self) -> bool { - self.keyboard_focus_surface_id.is_some() - } - - pub fn handle_pointer_enter( - &mut self, - _serial: u32, - surface: &WlSurface, - surface_x: f64, - surface_y: f64, - ) -> bool { - let surface_id = surface.id(); - let (position, window) = { - let Some(active_surface) = self.find_surface_by_surface_id(&surface_id) else { - return false; - }; - ( - active_surface.to_logical_position(surface_x, surface_y), - active_surface.window_rc(), - ) - }; - - self.active_pointer_surface_id = Some(surface_id.clone()); - self.current_pointer_position = position; - info!("Lock pointer enter on {:?}", surface_id); - window - .window() - .dispatch_event(WindowEvent::PointerMoved { position }); - true - } - - pub fn handle_pointer_motion(&mut self, surface_x: f64, surface_y: f64) -> bool { - let Some(surface_id) = self.active_pointer_surface_id.clone() else { - return false; - }; - let (position, window) = { - let Some(active_surface) = self.find_surface_by_surface_id(&surface_id) else { - return false; - }; - ( - active_surface.to_logical_position(surface_x, surface_y), - active_surface.window_rc(), - ) - }; - - self.current_pointer_position = position; - window - .window() - .dispatch_event(WindowEvent::PointerMoved { position }); - true - } - - pub fn handle_pointer_leave(&mut self) -> bool { - let Some(surface_id) = self.active_pointer_surface_id.take() else { - return false; - }; - - if let Some(active_surface) = self.find_surface_by_surface_id(&surface_id) { - active_surface.dispatch_event(WindowEvent::PointerExited); - } - true - } - - pub fn handle_pointer_button( - &mut self, - _serial: u32, - button: u32, - button_state: WEnum, - ) -> bool { - let Some(surface_id) = self.active_pointer_surface_id.clone() else { - return false; - }; - let window = { - let Some(active_surface) = self.find_surface_by_surface_id(&surface_id) else { - return false; - }; - active_surface.window_rc() - }; - - let position = self.current_pointer_position; - let slint_button = wayland_button_to_slint(button); - let event = match button_state { - WEnum::Value(wl_pointer::ButtonState::Pressed) => WindowEvent::PointerPressed { - button: slint_button, - position, - }, - WEnum::Value(wl_pointer::ButtonState::Released) => WindowEvent::PointerReleased { - button: slint_button, - position, - }, - _ => return true, - }; - - info!( - "Lock pointer button {:?} at {:?} (scale {})", - button_state, - position, - self.config.scale_factor.value() - ); - window.window().dispatch_event(event); - true - } - - pub fn handle_axis_source(&mut self, _axis_source: wl_pointer::AxisSource) -> bool { - if self.active_pointer_surface_id.is_none() { - return false; - } - true - } - - pub fn handle_axis(&mut self, axis: wl_pointer::Axis, value: f64) -> bool { - if self.active_pointer_surface_id.is_none() { - return false; - } - - match axis { - wl_pointer::Axis::HorizontalScroll => { - #[allow(clippy::cast_possible_truncation)] - let delta = value as f32; - self.accumulated_axis_x += delta; - } - wl_pointer::Axis::VerticalScroll => { - #[allow(clippy::cast_possible_truncation)] - let delta = value as f32; - self.accumulated_axis_y += delta; - } - _ => {} - } - true - } - - pub fn handle_axis_discrete(&mut self, axis: wl_pointer::Axis, discrete: i32) -> bool { - if self.active_pointer_surface_id.is_none() { - return false; - } - - #[allow(clippy::cast_precision_loss)] - let delta = (discrete as f32) * 60.0; - match axis { - wl_pointer::Axis::HorizontalScroll => { - self.accumulated_axis_x += delta; - } - wl_pointer::Axis::VerticalScroll => { - self.accumulated_axis_y += delta; - } - _ => {} - } - true - } - - pub fn handle_axis_stop(&mut self, _axis: wl_pointer::Axis) -> bool { - self.active_pointer_surface_id.is_some() - } - - pub fn handle_pointer_frame(&mut self) -> bool { - let Some(surface_id) = self.active_pointer_surface_id.clone() else { - return false; - }; - let delta_x = self.accumulated_axis_x; - let delta_y = self.accumulated_axis_y; - self.accumulated_axis_x = 0.0; - self.accumulated_axis_y = 0.0; - - let window = { - let Some(active_surface) = self.find_surface_by_surface_id(&surface_id) else { - return false; - }; - active_surface.window_rc() - }; - - if delta_x.abs() > f32::EPSILON || delta_y.abs() > f32::EPSILON { - let position = self.current_pointer_position; - window - .window() - .dispatch_event(WindowEvent::PointerScrolled { - position, - delta_x, - delta_y, - }); - } - - true - } - - pub fn handle_keyboard_enter(&mut self, surface: &WlSurface) -> bool { - let surface_id = surface.id(); - if self.find_surface_by_surface_id(&surface_id).is_some() { - self.keyboard_focus_surface_id = Some(surface_id); - return true; - } - false - } - - pub fn handle_keyboard_leave(&mut self, surface: &WlSurface) -> bool { - let surface_id = surface.id(); - if self.keyboard_focus_surface_id.as_ref() == Some(&surface_id) { - self.keyboard_focus_surface_id = None; - return true; - } - false - } - - pub fn handle_keyboard_key( - &mut self, - key: u32, - state: wl_keyboard::KeyState, - keyboard_state: &mut KeyboardState, - ) -> bool { - let Some(surface_id) = self.keyboard_focus_surface_id.clone() else { - return false; - }; - let Some(active_surface) = self.find_surface_by_surface_id(&surface_id) else { - return false; - }; - let Some(xkb_state) = keyboard_state.xkb_state.as_mut() else { - return true; - }; - - let keycode = xkb::Keycode::new(key + 8); - let direction = match state { - wl_keyboard::KeyState::Pressed => xkb::KeyDirection::Down, - wl_keyboard::KeyState::Released => xkb::KeyDirection::Up, - _ => return true, - }; - - xkb_state.update_key(keycode, direction); - - let text = xkb_state.key_get_utf8(keycode); - let text = if text.is_empty() { - let keysym = xkb_state.key_get_one_sym(keycode); - keysym_to_text(keysym) - } else { - Some(SharedString::from(text.as_str())) - }; - - let Some(text) = text else { - return true; - }; - - let event = match state { - wl_keyboard::KeyState::Pressed => WindowEvent::KeyPressed { text }, - wl_keyboard::KeyState::Released => WindowEvent::KeyReleased { text }, - _ => return true, - }; - info!("Lock key event {:?}", state); - active_surface.dispatch_event(event); - true - } - - fn create_window(&self, surface_id: &ObjectId) -> Result> { - let init_size = LogicalSize::new(1.0, 1.0); - let context = self.context.render_factory().create_context( - surface_id, - init_size.to_physical(self.config.scale_factor.value()), - )?; - let renderer = FemtoVGRenderer::new(context) - .map_err(|e| LayerShikaError::FemtoVGRendererCreation { source: e })?; - let window = FemtoVGWindow::new(renderer); - RenderableWindow::set_scale_factor(window.as_ref(), self.config.scale_factor.value()); - window.set_size(WindowSize::Logical(init_size)); - window.set_position(WindowPosition::Logical(LogicalPosition::new(0., 0.))); - Ok(window) - } - - pub fn iter_lock_surfaces(&self, f: &mut dyn FnMut(&ObjectId, &ComponentInstance)) { - for (output_id, active_surface) in &self.lock_surfaces { - if let Some(component) = active_surface.component.as_ref() { - f(output_id, component.component_instance()); - } - } - } - - pub const fn component_name(&self) -> &ComponentDefinition { - &self.component_definition - } - - pub fn count_lock_surfaces(&self) -> usize { - self.lock_surfaces - .values() - .filter(|s| s.component.is_some()) - .count() - } -} diff --git a/crates/adapters/src/wayland/session_lock/manager/callbacks.rs b/crates/adapters/src/wayland/session_lock/manager/callbacks.rs new file mode 100644 index 0000000..644ad8d --- /dev/null +++ b/crates/adapters/src/wayland/session_lock/manager/callbacks.rs @@ -0,0 +1,80 @@ +use crate::errors::{LayerShikaError, Result}; +use layer_shika_domain::value_objects::output_handle::OutputHandle; +use layer_shika_domain::value_objects::output_info::OutputInfo; +use slint_interpreter::{ComponentInstance, Value}; +use std::rc::Rc; + +pub type LockCallbackHandler = Rc Value>; +pub type OutputFilter = Rc< + dyn Fn( + &str, + OutputHandle, + Option<&OutputInfo>, + Option, + Option, + ) -> bool, +>; + +#[derive(Clone)] +pub struct LockCallback { + name: String, + handler: LockCallbackHandler, + filter: Option, +} + +impl LockCallback { + pub fn new(name: impl Into, handler: LockCallbackHandler) -> Self { + Self { + name: name.into(), + handler, + filter: None, + } + } + + pub fn with_filter( + name: impl Into, + handler: LockCallbackHandler, + filter: OutputFilter, + ) -> Self { + Self { + name: name.into(), + handler, + filter: Some(filter), + } + } + + pub fn should_apply( + &self, + component_name: &str, + output_handle: OutputHandle, + output_info: Option<&OutputInfo>, + primary_handle: Option, + active_handle: Option, + ) -> bool { + self.filter.as_ref().map_or_else( + || true, + |f| { + f( + component_name, + output_handle, + output_info, + primary_handle, + active_handle, + ) + }, + ) + } + + pub fn apply_to(&self, component: &ComponentInstance) -> Result<()> { + let handler = Rc::clone(&self.handler); + component + .set_callback(&self.name, move |args| handler(args)) + .map_err(|e| LayerShikaError::InvalidInput { + message: format!("Failed to register callback '{}': {e}", self.name), + }) + } + + pub const fn name(&self) -> &String { + &self.name + } +} diff --git a/crates/adapters/src/wayland/session_lock/manager/input_handling.rs b/crates/adapters/src/wayland/session_lock/manager/input_handling.rs new file mode 100644 index 0000000..5b58900 --- /dev/null +++ b/crates/adapters/src/wayland/session_lock/manager/input_handling.rs @@ -0,0 +1,331 @@ +use crate::wayland::surfaces::keyboard_state::{KeyboardState, keysym_to_text}; +use crate::wayland::surfaces::pointer_utils::wayland_button_to_slint; +use log::info; +use slint::{ + LogicalPosition, SharedString, + platform::{WindowAdapter, WindowEvent}, +}; +use wayland_client::{ + Proxy, WEnum, + backend::ObjectId, + protocol::{wl_keyboard, wl_pointer, wl_surface::WlSurface}, +}; +use xkbcommon::xkb; + +use super::state::ActiveLockSurface; + +pub(super) struct InputState { + pub active_pointer_surface_id: Option, + pub keyboard_focus_surface_id: Option, + pub current_pointer_position: LogicalPosition, + pub accumulated_axis_x: f32, + pub accumulated_axis_y: f32, +} + +impl InputState { + pub fn new() -> Self { + Self { + active_pointer_surface_id: None, + keyboard_focus_surface_id: None, + current_pointer_position: LogicalPosition::new(0.0, 0.0), + accumulated_axis_x: 0.0, + accumulated_axis_y: 0.0, + } + } + + pub fn reset(&mut self) { + self.active_pointer_surface_id = None; + self.keyboard_focus_surface_id = None; + self.current_pointer_position = LogicalPosition::new(0.0, 0.0); + self.accumulated_axis_x = 0.0; + self.accumulated_axis_y = 0.0; + } + + pub fn clear_surface_refs(&mut self, surface_id: &ObjectId) { + if self.active_pointer_surface_id.as_ref() == Some(surface_id) { + self.active_pointer_surface_id = None; + } + if self.keyboard_focus_surface_id.as_ref() == Some(surface_id) { + self.keyboard_focus_surface_id = None; + } + } + + pub const fn has_active_pointer(&self) -> bool { + self.active_pointer_surface_id.is_some() + } + + pub const fn has_keyboard_focus(&self) -> bool { + self.keyboard_focus_surface_id.is_some() + } +} + +pub(super) fn handle_pointer_enter( + input_state: &mut InputState, + lock_surfaces: &[(ObjectId, ActiveLockSurface)], + _serial: u32, + surface: &WlSurface, + surface_x: f64, + surface_y: f64, +) -> bool { + let surface_id = surface.id(); + let Some(active_surface) = find_surface_by_surface_id(lock_surfaces, &surface_id) else { + return false; + }; + + let position = active_surface.to_logical_position(surface_x, surface_y); + let window = active_surface.window_rc(); + + input_state.active_pointer_surface_id = Some(surface_id.clone()); + input_state.current_pointer_position = position; + info!("Lock pointer enter on {:?}", surface_id); + window + .window() + .dispatch_event(WindowEvent::PointerMoved { position }); + true +} + +pub(super) fn handle_pointer_motion( + input_state: &mut InputState, + lock_surfaces: &[(ObjectId, ActiveLockSurface)], + surface_x: f64, + surface_y: f64, +) -> bool { + let Some(surface_id) = input_state.active_pointer_surface_id.clone() else { + return false; + }; + let Some(active_surface) = find_surface_by_surface_id(lock_surfaces, &surface_id) else { + return false; + }; + + let position = active_surface.to_logical_position(surface_x, surface_y); + let window = active_surface.window_rc(); + + input_state.current_pointer_position = position; + window + .window() + .dispatch_event(WindowEvent::PointerMoved { position }); + true +} + +pub(super) fn handle_pointer_leave( + input_state: &mut InputState, + lock_surfaces: &[(ObjectId, ActiveLockSurface)], +) -> bool { + let Some(surface_id) = input_state.active_pointer_surface_id.take() else { + return false; + }; + + if let Some(active_surface) = find_surface_by_surface_id(lock_surfaces, &surface_id) { + active_surface.dispatch_event(WindowEvent::PointerExited); + } + true +} + +pub(super) fn handle_pointer_button( + input_state: &mut InputState, + lock_surfaces: &[(ObjectId, ActiveLockSurface)], + scale_factor: f32, + _serial: u32, + button: u32, + button_state: WEnum, +) -> bool { + let Some(surface_id) = input_state.active_pointer_surface_id.clone() else { + return false; + }; + let Some(active_surface) = find_surface_by_surface_id(lock_surfaces, &surface_id) else { + return false; + }; + + let window = active_surface.window_rc(); + let position = input_state.current_pointer_position; + let slint_button = wayland_button_to_slint(button); + let event = match button_state { + WEnum::Value(wl_pointer::ButtonState::Pressed) => WindowEvent::PointerPressed { + button: slint_button, + position, + }, + WEnum::Value(wl_pointer::ButtonState::Released) => WindowEvent::PointerReleased { + button: slint_button, + position, + }, + _ => return true, + }; + + info!( + "Lock pointer button {:?} at {:?} (scale {})", + button_state, position, scale_factor + ); + window.window().dispatch_event(event); + true +} + +pub(super) fn handle_axis_source( + input_state: &InputState, + _axis_source: wl_pointer::AxisSource, +) -> bool { + input_state.active_pointer_surface_id.is_some() +} + +pub(super) fn handle_axis( + input_state: &mut InputState, + axis: wl_pointer::Axis, + value: f64, +) -> bool { + if input_state.active_pointer_surface_id.is_none() { + return false; + } + + match axis { + wl_pointer::Axis::HorizontalScroll => { + #[allow(clippy::cast_possible_truncation)] + let delta = value as f32; + input_state.accumulated_axis_x += delta; + } + wl_pointer::Axis::VerticalScroll => { + #[allow(clippy::cast_possible_truncation)] + let delta = value as f32; + input_state.accumulated_axis_y += delta; + } + _ => {} + } + true +} + +pub(super) fn handle_axis_discrete( + input_state: &mut InputState, + axis: wl_pointer::Axis, + discrete: i32, +) -> bool { + if input_state.active_pointer_surface_id.is_none() { + return false; + } + + #[allow(clippy::cast_precision_loss)] + let delta = (discrete as f32) * 60.0; + match axis { + wl_pointer::Axis::HorizontalScroll => { + input_state.accumulated_axis_x += delta; + } + wl_pointer::Axis::VerticalScroll => { + input_state.accumulated_axis_y += delta; + } + _ => {} + } + true +} + +pub(super) fn handle_axis_stop(input_state: &InputState, _axis: wl_pointer::Axis) -> bool { + input_state.active_pointer_surface_id.is_some() +} + +pub(super) fn handle_pointer_frame( + input_state: &mut InputState, + lock_surfaces: &[(ObjectId, ActiveLockSurface)], +) -> bool { + let Some(surface_id) = input_state.active_pointer_surface_id.clone() else { + return false; + }; + let delta_x = input_state.accumulated_axis_x; + let delta_y = input_state.accumulated_axis_y; + input_state.accumulated_axis_x = 0.0; + input_state.accumulated_axis_y = 0.0; + + let Some(active_surface) = find_surface_by_surface_id(lock_surfaces, &surface_id) else { + return false; + }; + + let window = active_surface.window_rc(); + + if delta_x.abs() > f32::EPSILON || delta_y.abs() > f32::EPSILON { + let position = input_state.current_pointer_position; + window + .window() + .dispatch_event(WindowEvent::PointerScrolled { + position, + delta_x, + delta_y, + }); + } + + true +} + +pub(super) fn handle_keyboard_enter( + input_state: &mut InputState, + lock_surfaces: &[(ObjectId, ActiveLockSurface)], + surface: &WlSurface, +) -> bool { + let surface_id = surface.id(); + if find_surface_by_surface_id(lock_surfaces, &surface_id).is_some() { + input_state.keyboard_focus_surface_id = Some(surface_id); + return true; + } + false +} + +pub(super) fn handle_keyboard_leave(input_state: &mut InputState, surface: &WlSurface) -> bool { + let surface_id = surface.id(); + if input_state.keyboard_focus_surface_id.as_ref() == Some(&surface_id) { + input_state.keyboard_focus_surface_id = None; + return true; + } + false +} + +pub(super) fn handle_keyboard_key( + input_state: &InputState, + lock_surfaces: &[(ObjectId, ActiveLockSurface)], + key: u32, + state: wl_keyboard::KeyState, + keyboard_state: &mut KeyboardState, +) -> bool { + let Some(surface_id) = input_state.keyboard_focus_surface_id.clone() else { + return false; + }; + let Some(active_surface) = find_surface_by_surface_id(lock_surfaces, &surface_id) else { + return false; + }; + let Some(xkb_state) = keyboard_state.xkb_state.as_mut() else { + return true; + }; + + let keycode = xkb::Keycode::new(key + 8); + let direction = match state { + wl_keyboard::KeyState::Pressed => xkb::KeyDirection::Down, + wl_keyboard::KeyState::Released => xkb::KeyDirection::Up, + _ => return true, + }; + + xkb_state.update_key(keycode, direction); + + let text = xkb_state.key_get_utf8(keycode); + let text = if text.is_empty() { + let keysym = xkb_state.key_get_one_sym(keycode); + keysym_to_text(keysym) + } else { + Some(SharedString::from(text.as_str())) + }; + + let Some(text) = text else { + return true; + }; + + let event = match state { + wl_keyboard::KeyState::Pressed => WindowEvent::KeyPressed { text }, + wl_keyboard::KeyState::Released => WindowEvent::KeyReleased { text }, + _ => return true, + }; + info!("Lock key event {:?}", state); + active_surface.dispatch_event(event); + true +} + +fn find_surface_by_surface_id<'a>( + lock_surfaces: &'a [(ObjectId, ActiveLockSurface)], + surface_id: &ObjectId, +) -> Option<&'a ActiveLockSurface> { + lock_surfaces + .iter() + .find(|(_, surface)| surface.surface().surface_id() == *surface_id) + .map(|(_, surface)| surface) +} diff --git a/crates/adapters/src/wayland/session_lock/manager/lifecycle.rs b/crates/adapters/src/wayland/session_lock/manager/lifecycle.rs new file mode 100644 index 0000000..1e1cbc9 --- /dev/null +++ b/crates/adapters/src/wayland/session_lock/manager/lifecycle.rs @@ -0,0 +1,28 @@ +use crate::errors::{LayerShikaError, Result}; +use crate::rendering::femtovg::main_window::FemtoVGWindow; +use crate::rendering::femtovg::renderable_window::RenderableWindow; +use crate::wayland::session_lock::lock_context::SessionLockContext; +use slint::{ + LogicalPosition, LogicalSize, WindowPosition, WindowSize, platform::WindowAdapter, + platform::femtovg_renderer::FemtoVGRenderer, +}; +use std::rc::Rc; +use wayland_client::backend::ObjectId; + +pub(super) fn create_window( + context: &SessionLockContext, + surface_id: &ObjectId, + scale_factor: f32, +) -> Result> { + let init_size = LogicalSize::new(1.0, 1.0); + let render_context = context + .render_factory() + .create_context(surface_id, init_size.to_physical(scale_factor))?; + let renderer = FemtoVGRenderer::new(render_context) + .map_err(|e| LayerShikaError::FemtoVGRendererCreation { source: e })?; + let window = FemtoVGWindow::new(renderer); + RenderableWindow::set_scale_factor(window.as_ref(), scale_factor); + window.set_size(WindowSize::Logical(init_size)); + window.set_position(WindowPosition::Logical(LogicalPosition::new(0., 0.))); + Ok(window) +} diff --git a/crates/adapters/src/wayland/session_lock/manager/mod.rs b/crates/adapters/src/wayland/session_lock/manager/mod.rs new file mode 100644 index 0000000..8c568d3 --- /dev/null +++ b/crates/adapters/src/wayland/session_lock/manager/mod.rs @@ -0,0 +1,397 @@ +pub mod callbacks; +pub mod input_handling; +pub mod lifecycle; +pub mod rendering; +pub mod state; + +use crate::errors::{LayerShikaError, Result}; +use crate::rendering::slint_integration::platform::CustomSlintPlatform; +use crate::wayland::session_lock::lock_context::SessionLockContext; +use crate::wayland::session_lock::lock_surface::LockSurface; +use crate::wayland::surfaces::app_state::AppState; +use crate::wayland::surfaces::keyboard_state::KeyboardState; +use layer_shika_domain::value_objects::lock_config::LockConfig; +use layer_shika_domain::value_objects::lock_state::LockState; +use log::info; +use slint_interpreter::{CompilationResult, ComponentDefinition, ComponentInstance}; +use std::rc::Rc; +use wayland_client::{ + Proxy, QueueHandle, WEnum, + backend::ObjectId, + protocol::{wl_keyboard, wl_output::WlOutput, wl_pointer, wl_surface::WlSurface}, +}; +use wayland_protocols::ext::session_lock::v1::client::ext_session_lock_v1::ExtSessionLockV1; + +pub use callbacks::{LockCallback, OutputFilter}; +pub use state::{ActiveLockSurface, LockConfigureContext, LockSurfaceOutputContext}; + +use self::input_handling::InputState; +use crate::wayland::session_lock::lock_context::LockSurfaceParams; + +pub struct SessionLockManager { + context: Rc, + session_lock: Option, + lock_surfaces: Vec<(ObjectId, ActiveLockSurface)>, + state: LockState, + config: LockConfig, + component_definition: ComponentDefinition, + compilation_result: Option>, + platform: Rc, + callbacks: Vec, + input_state: InputState, +} + +impl SessionLockManager { + #[must_use] + pub fn new( + context: Rc, + component_definition: ComponentDefinition, + compilation_result: Option>, + platform: Rc, + config: LockConfig, + ) -> Self { + Self { + context, + session_lock: None, + lock_surfaces: Vec::new(), + state: LockState::Inactive, + config, + component_definition, + compilation_result, + platform, + callbacks: Vec::new(), + input_state: InputState::new(), + } + } + + #[must_use] + pub const fn state(&self) -> LockState { + self.state + } + + pub fn activate( + &mut self, + outputs: impl IntoIterator, + queue_handle: &QueueHandle, + ) -> Result<()> { + if !self.state.can_activate() { + return Err(LayerShikaError::InvalidInput { + message: format!("Session lock cannot activate in state {:?}", self.state), + }); + } + + self.config.validate()?; + + let session_lock = self.context.lock_manager().lock(queue_handle, ()); + self.session_lock = Some(session_lock.clone()); + self.state = LockState::Locking; + + for output in outputs { + let params = LockSurfaceParams { + compositor: self.context.compositor(), + output: &output, + session_lock: &session_lock, + fractional_scale_manager: self.context.fractional_scale_manager(), + viewporter: self.context.viewporter(), + queue_handle, + }; + let surface = LockSurface::create(¶ms); + let surface_id = surface.surface_id(); + let window = lifecycle::create_window( + &self.context, + &surface_id, + self.config.scale_factor.value(), + )?; + self.lock_surfaces + .push((output.id(), ActiveLockSurface::new(surface, window))); + } + + Ok(()) + } + + pub fn handle_locked(&mut self) { + if self.state == LockState::Locking { + info!("Session lock transitioned to Locked"); + self.state = LockState::Locked; + } + } + + pub fn deactivate(&mut self) -> Result<()> { + if !self.state.can_deactivate() { + return Err(LayerShikaError::InvalidInput { + message: format!("Session lock cannot deactivate in state {:?}", self.state), + }); + } + + let Some(session_lock) = self.session_lock.take() else { + return Err(LayerShikaError::InvalidInput { + message: "Session lock object missing during deactivate".to_string(), + }); + }; + + for (_, surface) in &self.lock_surfaces { + surface.surface().destroy(); + } + session_lock.unlock_and_destroy(); + self.lock_surfaces.clear(); + self.input_state.reset(); + self.state = LockState::Unlocking; + Ok(()) + } + + pub fn handle_finished(&mut self) { + info!("Session lock finished"); + self.lock_surfaces.clear(); + self.session_lock = None; + self.state = LockState::Inactive; + self.input_state.reset(); + } + + pub fn add_output( + &mut self, + output: &WlOutput, + queue_handle: &QueueHandle, + ) -> Result<()> { + if self.state != LockState::Locked { + return Ok(()); + } + + let output_id = output.id(); + if self.lock_surfaces.iter().any(|(id, _)| *id == output_id) { + return Ok(()); + } + + let Some(session_lock) = self.session_lock.as_ref() else { + return Err(LayerShikaError::InvalidInput { + message: "Session lock object missing during output hotplug".to_string(), + }); + }; + + info!("Adding lock surface for output {output_id:?}"); + let params = LockSurfaceParams { + compositor: self.context.compositor(), + output, + session_lock, + fractional_scale_manager: self.context.fractional_scale_manager(), + viewporter: self.context.viewporter(), + queue_handle, + }; + let surface = LockSurface::create(¶ms); + let surface_id = surface.surface_id(); + let window = + lifecycle::create_window(&self.context, &surface_id, self.config.scale_factor.value())?; + self.lock_surfaces + .push((output_id, ActiveLockSurface::new(surface, window))); + Ok(()) + } + + pub fn remove_output(&mut self, output_id: &ObjectId) { + if let Some(pos) = self + .lock_surfaces + .iter() + .position(|(id, _)| id == output_id) + { + let (_, surface) = self.lock_surfaces.remove(pos); + let surface_id = surface.surface().surface_id(); + self.input_state.clear_surface_refs(&surface_id); + drop(surface); + } + } + + fn find_surface_by_lock_surface_id_mut( + &mut self, + lock_surface_id: &ObjectId, + ) -> Option<&mut ActiveLockSurface> { + self.lock_surfaces + .iter_mut() + .find(|(_, surface)| surface.surface().lock_surface_id() == *lock_surface_id) + .map(|(_, surface)| surface) + } + + fn find_surface_by_surface_id(&self, surface_id: &ObjectId) -> Option<&ActiveLockSurface> { + self.lock_surfaces + .iter() + .find(|(_, surface)| surface.surface().surface_id() == *surface_id) + .map(|(_, surface)| surface) + } + + pub fn find_output_id_for_lock_surface(&self, lock_surface_id: &ObjectId) -> Option { + self.lock_surfaces + .iter() + .find(|(_, surface)| surface.surface().surface_id() == *lock_surface_id) + .map(|(id, _)| id.clone()) + } + + pub fn handle_configure( + &mut self, + lock_surface_id: &ObjectId, + serial: u32, + width: u32, + height: u32, + output_ctx: LockSurfaceOutputContext, + ) -> Result<()> { + let component_name = self.component_definition.name().to_string(); + + let context = LockConfigureContext { + scale_factor: self.config.scale_factor.value(), + component_definition: self.component_definition.clone(), + compilation_result: self.compilation_result.clone(), + platform: Rc::clone(&self.platform), + callbacks: self.callbacks.clone(), + component_name, + output_handle: output_ctx.output_handle, + output_info: output_ctx.output_info, + primary_handle: output_ctx.primary_handle, + active_handle: output_ctx.active_handle, + }; + + let Some(surface) = self.find_surface_by_lock_surface_id_mut(lock_surface_id) else { + return Ok(()); + }; + + surface.handle_configure(serial, width, height, &context) + } + + pub fn render_frames(&self) -> Result<()> { + rendering::render_frames(&self.lock_surfaces) + } + + pub(crate) fn register_callback(&mut self, callback: LockCallback) { + for (_, surface) in &self.lock_surfaces { + surface.apply_callback(&callback); + } + self.callbacks.push(callback); + } + + pub fn handle_fractional_scale(&mut self, fractional_scale_id: &ObjectId, scale_120ths: u32) { + for (_, surface) in &mut self.lock_surfaces { + let matches = surface + .surface() + .fractional_scale() + .is_some_and(|fs| fs.id() == *fractional_scale_id); + if matches { + surface.handle_fractional_scale(scale_120ths); + } + } + } + + pub fn is_lock_surface(&self, surface_id: &ObjectId) -> bool { + self.find_surface_by_surface_id(surface_id).is_some() + } + + pub const fn has_active_pointer(&self) -> bool { + self.input_state.has_active_pointer() + } + + pub const fn has_keyboard_focus(&self) -> bool { + self.input_state.has_keyboard_focus() + } + + pub fn handle_pointer_enter( + &mut self, + serial: u32, + surface: &WlSurface, + surface_x: f64, + surface_y: f64, + ) -> bool { + input_handling::handle_pointer_enter( + &mut self.input_state, + &self.lock_surfaces, + serial, + surface, + surface_x, + surface_y, + ) + } + + pub fn handle_pointer_motion(&mut self, surface_x: f64, surface_y: f64) -> bool { + input_handling::handle_pointer_motion( + &mut self.input_state, + &self.lock_surfaces, + surface_x, + surface_y, + ) + } + + pub fn handle_pointer_leave(&mut self) -> bool { + input_handling::handle_pointer_leave(&mut self.input_state, &self.lock_surfaces) + } + + pub fn handle_pointer_button( + &mut self, + serial: u32, + button: u32, + button_state: WEnum, + ) -> bool { + input_handling::handle_pointer_button( + &mut self.input_state, + &self.lock_surfaces, + self.config.scale_factor.value(), + serial, + button, + button_state, + ) + } + + pub fn handle_axis_source(&mut self, axis_source: wl_pointer::AxisSource) -> bool { + input_handling::handle_axis_source(&self.input_state, axis_source) + } + + pub fn handle_axis(&mut self, axis: wl_pointer::Axis, value: f64) -> bool { + input_handling::handle_axis(&mut self.input_state, axis, value) + } + + pub fn handle_axis_discrete(&mut self, axis: wl_pointer::Axis, discrete: i32) -> bool { + input_handling::handle_axis_discrete(&mut self.input_state, axis, discrete) + } + + pub fn handle_axis_stop(&mut self, axis: wl_pointer::Axis) -> bool { + input_handling::handle_axis_stop(&self.input_state, axis) + } + + pub fn handle_pointer_frame(&mut self) -> bool { + input_handling::handle_pointer_frame(&mut self.input_state, &self.lock_surfaces) + } + + pub fn handle_keyboard_enter(&mut self, surface: &WlSurface) -> bool { + input_handling::handle_keyboard_enter(&mut self.input_state, &self.lock_surfaces, surface) + } + + pub fn handle_keyboard_leave(&mut self, surface: &WlSurface) -> bool { + input_handling::handle_keyboard_leave(&mut self.input_state, surface) + } + + pub fn handle_keyboard_key( + &mut self, + key: u32, + state: wl_keyboard::KeyState, + keyboard_state: &mut KeyboardState, + ) -> bool { + input_handling::handle_keyboard_key( + &self.input_state, + &self.lock_surfaces, + key, + state, + keyboard_state, + ) + } + + pub fn iter_lock_surfaces(&self, f: &mut dyn FnMut(&ObjectId, &ComponentInstance)) { + for (output_id, active_surface) in &self.lock_surfaces { + if let Some(component) = active_surface.component() { + f(output_id, component.component_instance()); + } + } + } + + pub const fn component_name(&self) -> &ComponentDefinition { + &self.component_definition + } + + pub fn count_lock_surfaces(&self) -> usize { + self.lock_surfaces + .iter() + .filter(|(_, s)| s.component().is_some()) + .count() + } +} diff --git a/crates/adapters/src/wayland/session_lock/manager/rendering.rs b/crates/adapters/src/wayland/session_lock/manager/rendering.rs new file mode 100644 index 0000000..c3cc13d --- /dev/null +++ b/crates/adapters/src/wayland/session_lock/manager/rendering.rs @@ -0,0 +1,11 @@ +use crate::errors::Result; +use wayland_client::backend::ObjectId; + +use super::state::ActiveLockSurface; + +pub(super) fn render_frames(lock_surfaces: &[(ObjectId, ActiveLockSurface)]) -> Result<()> { + for (_, surface) in lock_surfaces { + surface.render_frame_if_dirty()?; + } + Ok(()) +} diff --git a/crates/adapters/src/wayland/session_lock/manager/state.rs b/crates/adapters/src/wayland/session_lock/manager/state.rs new file mode 100644 index 0000000..717b6bf --- /dev/null +++ b/crates/adapters/src/wayland/session_lock/manager/state.rs @@ -0,0 +1,248 @@ +use crate::errors::Result; +use crate::rendering::femtovg::main_window::FemtoVGWindow; +use crate::rendering::femtovg::renderable_window::RenderableWindow; +use crate::rendering::slint_integration::platform::CustomSlintPlatform; +use crate::wayland::session_lock::lock_surface::LockSurface; +use crate::wayland::surfaces::component_state::ComponentState; +use crate::wayland::surfaces::display_metrics::DisplayMetrics; +use layer_shika_domain::surface_dimensions::SurfaceDimensions; +use layer_shika_domain::value_objects::output_handle::OutputHandle; +use layer_shika_domain::value_objects::output_info::OutputInfo; +use log::info; +use slint::{ + LogicalPosition, LogicalSize, WindowSize, + platform::{WindowAdapter, WindowEvent}, +}; +use slint_interpreter::{CompilationResult, ComponentDefinition}; +use std::rc::Rc; + +use super::callbacks::LockCallback; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum LockScalingMode { + FractionalWithViewport, + FractionalOnly, + Integer, +} + +pub struct LockSurfaceOutputContext { + pub output_handle: OutputHandle, + pub output_info: Option, + pub primary_handle: Option, + pub active_handle: Option, +} + +pub struct LockConfigureContext { + pub scale_factor: f32, + pub component_definition: ComponentDefinition, + pub compilation_result: Option>, + pub platform: Rc, + pub callbacks: Vec, + pub component_name: String, + pub output_handle: OutputHandle, + pub output_info: Option, + pub primary_handle: Option, + pub active_handle: Option, +} + +pub struct ActiveLockSurface { + surface: LockSurface, + window: Rc, + component: Option, + scale_factor: f32, + has_fractional_scale: bool, +} + +impl ActiveLockSurface { + pub fn new(surface: LockSurface, window: Rc) -> Self { + Self { + has_fractional_scale: surface.fractional_scale().is_some(), + surface, + window, + component: None, + scale_factor: 1.0, + } + } + + pub fn handle_configure( + &mut self, + serial: u32, + width: u32, + height: u32, + context: &LockConfigureContext, + ) -> Result<()> { + self.surface.handle_configure(serial, width, height); + self.scale_factor = context.scale_factor; + let dimensions = match SurfaceDimensions::calculate(width, height, context.scale_factor) { + Ok(dimensions) => dimensions, + Err(err) => { + info!("Failed to calculate lock surface dimensions: {err}"); + return Ok(()); + } + }; + let scaling_mode = self.scaling_mode(); + info!( + "Lock surface dimensions: logical {}x{}, physical {}x{}, scale {}, mode {:?}", + dimensions.logical_width(), + dimensions.logical_height(), + dimensions.physical_width(), + dimensions.physical_height(), + context.scale_factor, + scaling_mode + ); + self.configure_window(&dimensions, scaling_mode, context.scale_factor); + self.configure_surface(&dimensions, scaling_mode); + + if self.component.is_none() { + context.platform.add_window(Rc::clone(&self.window)); + let component = ComponentState::new( + context.component_definition.clone(), + context.compilation_result.clone(), + &self.window, + )?; + self.window + .window() + .dispatch_event(WindowEvent::WindowActiveChanged(true)); + for callback in &context.callbacks { + if callback.should_apply( + &context.component_name, + context.output_handle, + context.output_info.as_ref(), + context.primary_handle, + context.active_handle, + ) { + if let Err(err) = callback.apply_to(component.component_instance()) { + info!( + "Failed to register lock callback '{}': {err}", + callback.name() + ); + } else { + info!("Registered lock callback '{}'", callback.name()); + } + } else { + info!( + "Skipping callback '{}' due to selector filter (output {:?})", + callback.name(), + context.output_handle + ); + } + } + self.component = Some(component); + } + + RenderableWindow::request_redraw(self.window.as_ref()); + Ok(()) + } + + pub fn render_frame_if_dirty(&self) -> Result<()> { + self.window.render_frame_if_dirty() + } + + pub fn handle_fractional_scale(&mut self, scale_120ths: u32) { + let scale_factor = DisplayMetrics::scale_factor_from_120ths(scale_120ths); + self.scale_factor = scale_factor; + if self.surface.width() == 0 || self.surface.height() == 0 { + return; + } + let Ok(dimensions) = + SurfaceDimensions::calculate(self.surface.width(), self.surface.height(), scale_factor) + else { + return; + }; + let scaling_mode = self.scaling_mode(); + self.configure_window(&dimensions, scaling_mode, scale_factor); + self.configure_surface(&dimensions, scaling_mode); + RenderableWindow::request_redraw(self.window.as_ref()); + } + + pub fn apply_callback(&self, callback: &LockCallback) { + if let Some(component) = self.component.as_ref() { + if let Err(err) = callback.apply_to(component.component_instance()) { + info!( + "Failed to register lock callback '{}': {err}", + callback.name() + ); + } + } + } + + fn scaling_mode(&self) -> LockScalingMode { + if self.surface.has_fractional_scale() && self.surface.has_viewport() { + LockScalingMode::FractionalWithViewport + } else if self.surface.has_fractional_scale() { + LockScalingMode::FractionalOnly + } else { + LockScalingMode::Integer + } + } + + #[allow(clippy::cast_precision_loss)] + fn configure_window( + &self, + dimensions: &SurfaceDimensions, + mode: LockScalingMode, + scale_factor: f32, + ) { + match mode { + LockScalingMode::FractionalWithViewport | LockScalingMode::FractionalOnly => { + RenderableWindow::set_scale_factor(self.window.as_ref(), scale_factor); + self.window.set_size(WindowSize::Logical(LogicalSize::new( + dimensions.logical_width() as f32, + dimensions.logical_height() as f32, + ))); + } + LockScalingMode::Integer => { + RenderableWindow::set_scale_factor(self.window.as_ref(), scale_factor); + self.window + .set_size(WindowSize::Physical(slint::PhysicalSize::new( + dimensions.physical_width(), + dimensions.physical_height(), + ))); + } + } + } + + fn configure_surface(&self, dimensions: &SurfaceDimensions, mode: LockScalingMode) { + match mode { + LockScalingMode::FractionalWithViewport => { + self.surface.configure_fractional_viewport( + dimensions.logical_width(), + dimensions.logical_height(), + ); + } + LockScalingMode::FractionalOnly | LockScalingMode::Integer => { + self.surface + .configure_buffer_scale(dimensions.buffer_scale()); + } + } + } + + #[allow(clippy::cast_possible_truncation)] + pub fn to_logical_position(&self, surface_x: f64, surface_y: f64) -> LogicalPosition { + if self.has_fractional_scale { + let x = surface_x as f32; + let y = surface_y as f32; + LogicalPosition::new(x, y) + } else { + let x = (surface_x / f64::from(self.scale_factor)) as f32; + let y = (surface_y / f64::from(self.scale_factor)) as f32; + LogicalPosition::new(x, y) + } + } + + pub fn dispatch_event(&self, event: WindowEvent) { + self.window.window().dispatch_event(event); + } + + pub fn window_rc(&self) -> Rc { + Rc::clone(&self.window) + } + + pub const fn surface(&self) -> &LockSurface { + &self.surface + } + + pub const fn component(&self) -> Option<&ComponentState> { + self.component.as_ref() + } +} diff --git a/crates/adapters/src/wayland/session_lock/mod.rs b/crates/adapters/src/wayland/session_lock/mod.rs index 0a68455..f4a98e8 100644 --- a/crates/adapters/src/wayland/session_lock/mod.rs +++ b/crates/adapters/src/wayland/session_lock/mod.rs @@ -1,3 +1,5 @@ pub mod lock_context; -pub mod lock_manager; pub mod lock_surface; +pub mod manager; + +pub use manager::{LockCallback, LockSurfaceOutputContext, OutputFilter, SessionLockManager}; diff --git a/crates/adapters/src/wayland/shell_adapter.rs b/crates/adapters/src/wayland/shell_adapter.rs index bf28425..dc9d7d6 100644 --- a/crates/adapters/src/wayland/shell_adapter.rs +++ b/crates/adapters/src/wayland/shell_adapter.rs @@ -4,7 +4,7 @@ use crate::wayland::{ managed_proxies::{ManagedWlKeyboard, ManagedWlPointer}, ops::WaylandSystemOps, outputs::{OutputManager, OutputManagerContext}, - session_lock::lock_manager::OutputFilter, + session_lock::OutputFilter, surfaces::layer_surface::{SurfaceCtx, SurfaceSetupParams}, surfaces::popup_manager::{PopupContext, PopupManager}, surfaces::{ diff --git a/crates/adapters/src/wayland/surfaces/app_state.rs b/crates/adapters/src/wayland/surfaces/app_state.rs index 3ec4325..3cc080e 100644 --- a/crates/adapters/src/wayland/surfaces/app_state.rs +++ b/crates/adapters/src/wayland/surfaces/app_state.rs @@ -8,7 +8,7 @@ use crate::wayland::globals::context::GlobalContext; use crate::wayland::managed_proxies::{ManagedWlKeyboard, ManagedWlPointer}; use crate::wayland::outputs::{OutputManager, OutputMapping}; use crate::wayland::session_lock::lock_context::SessionLockContext; -use crate::wayland::session_lock::lock_manager::{LockCallback, OutputFilter, SessionLockManager}; +use crate::wayland::session_lock::{LockCallback, OutputFilter, SessionLockManager}; use layer_shika_domain::entities::output_registry::OutputRegistry; use layer_shika_domain::value_objects::handle::SurfaceHandle; use layer_shika_domain::value_objects::lock_config::LockConfig;