diff --git a/crates/adapters/src/rendering/slint_integration/platform.rs b/crates/adapters/src/rendering/slint_integration/platform.rs index 86f055b..2810565 100644 --- a/crates/adapters/src/rendering/slint_integration/platform.rs +++ b/crates/adapters/src/rendering/slint_integration/platform.rs @@ -23,6 +23,14 @@ impl CustomSlintPlatform { }) } + #[must_use] + pub fn new_empty() -> Rc { + Rc::new(Self { + pending_windows: RefCell::new(Vec::new()), + popup_creator: OnceCell::new(), + }) + } + pub fn add_window(&self, window: Rc) { self.pending_windows.borrow_mut().push(window); } diff --git a/crates/adapters/src/wayland/globals/context.rs b/crates/adapters/src/wayland/globals/context.rs index 5687e9a..62b7afc 100644 --- a/crates/adapters/src/wayland/globals/context.rs +++ b/crates/adapters/src/wayland/globals/context.rs @@ -20,7 +20,7 @@ use crate::wayland::surfaces::app_state::AppState; pub struct GlobalContext { pub compositor: WlCompositor, pub outputs: Vec, - pub layer_shell: ZwlrLayerShellV1, + pub layer_shell: Option, pub seat: WlSeat, pub xdg_wm_base: Option, pub session_lock_manager: Option, @@ -38,14 +38,17 @@ impl GlobalContext { .map(|(global_list, _)| global_list) .map_err(|e| LayerShikaError::GlobalInitialization { source: e })?; - let (compositor, layer_shell, seat) = bind_globals!( + let (compositor, seat) = bind_globals!( &global_list, queue_handle, (WlCompositor, compositor, 3..=6), - (ZwlrLayerShellV1, layer_shell, 1..=5), (WlSeat, seat, 1..=9) )?; + let layer_shell = global_list + .bind::(queue_handle, 1..=5, ()) + .ok(); + let output_names: Vec = global_list .contents() .clone_list() @@ -115,6 +118,10 @@ impl GlobalContext { info!("Viewporter protocol not available"); } + if layer_shell.is_none() { + info!("wlr-layer-shell protocol not available, layer surfaces disabled"); + } + let render_context_manager = RenderContextManager::new(&connection.display().id())?; Ok(Self { diff --git a/crates/adapters/src/wayland/ops.rs b/crates/adapters/src/wayland/ops.rs index 0229b62..19ae3a5 100644 --- a/crates/adapters/src/wayland/ops.rs +++ b/crates/adapters/src/wayland/ops.rs @@ -5,7 +5,7 @@ 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; use layer_shika_domain::value_objects::output_handle::OutputHandle; -use slint_interpreter::ComponentInstance; +use slint_interpreter::{ComponentInstance, CompilationResult}; use slint_interpreter::Value; use smithay_client_toolkit::reexports::calloop::LoopHandle; use std::rc::Rc; @@ -19,6 +19,8 @@ pub trait WaylandSystemOps { fn despawn_surface(&mut self, name: &str) -> Result<()>; + fn set_compilation_result(&mut self, compilation_result: Rc); + fn activate_session_lock(&mut self, component_name: &str, config: LockConfig) -> Result<()>; fn deactivate_session_lock(&mut self) -> Result<()>; diff --git a/crates/adapters/src/wayland/outputs/output_manager.rs b/crates/adapters/src/wayland/outputs/output_manager.rs index cddec68..a215a49 100644 --- a/crates/adapters/src/wayland/outputs/output_manager.rs +++ b/crates/adapters/src/wayland/outputs/output_manager.rs @@ -35,7 +35,7 @@ use super::OutputMapping; pub struct OutputManagerContext { pub compositor: WlCompositor, - pub layer_shell: ZwlrLayerShellV1, + pub layer_shell: Option, pub fractional_scale_manager: Option, pub viewporter: Option, pub render_factory: Rc, @@ -161,10 +161,16 @@ impl OutputManager { _output_id: &ObjectId, queue_handle: &QueueHandle, ) -> Result<(SurfaceState, ObjectId)> { + let layer_shell = self.context.layer_shell.as_ref().ok_or_else(|| { + LayerShikaError::InvalidInput { + message: "wlr-layer-shell protocol not available - cannot create layer surfaces".into(), + } + })?; + let setup_params = SurfaceSetupParams { compositor: &self.context.compositor, output, - layer_shell: &self.context.layer_shell, + layer_shell, fractional_scale_manager: self.context.fractional_scale_manager.as_ref(), viewporter: self.context.viewporter.as_ref(), queue_handle, diff --git a/crates/adapters/src/wayland/shell_adapter.rs b/crates/adapters/src/wayland/shell_adapter.rs index 46f0bc8..29199ce 100644 --- a/crates/adapters/src/wayland/shell_adapter.rs +++ b/crates/adapters/src/wayland/shell_adapter.rs @@ -37,7 +37,7 @@ use slint::{ LogicalPosition, PhysicalSize, PlatformError, WindowPosition, platform::{WindowAdapter, femtovg_renderer::FemtoVGRenderer, set_platform, update_timers_and_animations}, }; -use slint_interpreter::ComponentInstance; +use slint_interpreter::{ComponentInstance, CompilationResult}; use smithay_client_toolkit::reexports::calloop::{ EventLoop, Interest, LoopHandle, Mode, PostAction, generic::Generic, }; @@ -97,9 +97,7 @@ impl WaylandShellSystem { pub fn new_multi(configs: &[ShellSurfaceConfig]) -> Result { if configs.is_empty() { - return Err(LayerShikaError::InvalidInput { - message: "At least one surface config is required".into(), - }); + return Self::new_minimal(); } info!( @@ -120,6 +118,22 @@ impl WaylandShellSystem { }) } + pub fn new_minimal() -> Result { + info!("Initializing WindowingSystem in minimal mode (no layer surfaces)"); + let (connection, mut event_queue) = Self::init_wayland_connection()?; + let event_loop = + EventLoop::try_new().map_err(|e| EventLoopError::Creation { source: e })?; + + let state = Self::init_state_minimal(&connection, &mut event_queue)?; + + Ok(Self { + state, + connection, + event_queue, + event_loop, + }) + } + fn init_wayland_connection() -> Result<(Rc, EventQueue)> { let connection = Rc::new(Connection::connect_to_env()?); let event_queue = connection.new_event_queue(); @@ -145,6 +159,12 @@ impl WaylandShellSystem { pointer: &Rc, layer_surface_config: &LayerSurfaceConfig, ) -> Result> { + let layer_shell = global_ctx.layer_shell.as_ref().ok_or_else(|| { + LayerShikaError::InvalidInput { + message: "wlr-layer-shell protocol not available - cannot create layer surfaces".into(), + } + })?; + let mut setups = Vec::new(); for (index, output) in global_ctx.outputs.iter().enumerate() { @@ -163,7 +183,7 @@ impl WaylandShellSystem { let setup_params = SurfaceSetupParams { compositor: &global_ctx.compositor, output, - layer_shell: &global_ctx.layer_shell, + layer_shell, fractional_scale_manager: global_ctx.fractional_scale_manager.as_ref(), viewporter: global_ctx.viewporter.as_ref(), queue_handle: &event_queue.handle(), @@ -426,6 +446,38 @@ impl WaylandShellSystem { Ok(app_state) } + fn init_state_minimal( + connection: &Connection, + event_queue: &mut EventQueue, + ) -> Result { + let global_ctx = Rc::new(GlobalContext::initialize( + connection, + &event_queue.handle(), + )?); + + let pointer = Rc::new(global_ctx.seat.get_pointer(&event_queue.handle(), ())); + let keyboard = Rc::new(global_ctx.seat.get_keyboard(&event_queue.handle(), ())); + let shared_serial = Rc::new(SharedPointerSerial::new()); + + let mut app_state = AppState::new( + ManagedWlPointer::new(Rc::clone(&pointer), Rc::new(connection.clone())), + ManagedWlKeyboard::new(Rc::clone(&keyboard), Rc::new(connection.clone())), + Rc::clone(&shared_serial), + ); + + app_state.set_queue_handle(event_queue.handle()); + app_state.set_global_context(Rc::clone(&global_ctx)); + + let platform = CustomSlintPlatform::new_empty(); + set_platform(Box::new(PlatformWrapper(Rc::clone(&platform)))) + .map_err(|e| LayerShikaError::PlatformSetup { source: e })?; + app_state.set_slint_platform(Rc::clone(&platform)); + + info!("Minimal state initialized successfully (no layer surfaces, empty Slint platform for session locks)"); + + Ok(app_state) + } + fn create_output_setups_multi( configs: &[ShellSurfaceConfig], global_ctx: &GlobalContext, @@ -433,6 +485,12 @@ impl WaylandShellSystem { event_queue: &mut EventQueue, pointer: &Rc, ) -> Result> { + let layer_shell = global_ctx.layer_shell.as_ref().ok_or_else(|| { + LayerShikaError::InvalidInput { + message: "wlr-layer-shell protocol not available - cannot create layer surfaces".into(), + } + })?; + let mut setups = Vec::new(); for (output_index, output) in global_ctx.outputs.iter().enumerate() { @@ -458,7 +516,7 @@ impl WaylandShellSystem { let setup_params = SurfaceSetupParams { compositor: &global_ctx.compositor, output, - layer_shell: &global_ctx.layer_shell, + layer_shell, fractional_scale_manager: global_ctx.fractional_scale_manager.as_ref(), viewporter: global_ctx.viewporter.as_ref(), queue_handle: &event_queue.handle(), @@ -805,6 +863,10 @@ impl WaylandSystemOps for WaylandShellSystem { WaylandShellSystem::despawn_surface(self, name) } + fn set_compilation_result(&mut self, compilation_result: Rc) { + self.state.set_compilation_result(compilation_result); + } + fn activate_session_lock(&mut self, component_name: &str, config: LockConfig) -> Result<()> { self.state.activate_session_lock(component_name, config) } diff --git a/crates/adapters/src/wayland/surfaces/app_state.rs b/crates/adapters/src/wayland/surfaces/app_state.rs index 7bac1d3..2dbfb8a 100644 --- a/crates/adapters/src/wayland/surfaces/app_state.rs +++ b/crates/adapters/src/wayland/surfaces/app_state.rs @@ -54,6 +54,7 @@ pub struct AppState { global_context: Option>, known_outputs: Vec, slint_platform: Option>, + compilation_result: Option>, output_registry: OutputRegistry, output_mapping: OutputMapping, surfaces: HashMap, @@ -83,6 +84,7 @@ impl AppState { global_context: None, known_outputs: Vec::new(), slint_platform: None, + compilation_result: None, output_registry: OutputRegistry::new(), output_mapping: OutputMapping::new(), surfaces: HashMap::new(), @@ -112,6 +114,10 @@ impl AppState { self.slint_platform = Some(platform); } + pub fn set_compilation_result(&mut self, compilation_result: Rc) { + self.compilation_result = Some(compilation_result); + } + pub fn set_queue_handle(&mut self, queue_handle: wayland_client::QueueHandle) { self.queue_handle = Some(queue_handle); } @@ -255,8 +261,12 @@ impl AppState { component_name: &str, ) -> Result<(ComponentDefinition, Option>)> { let compilation_result = self - .primary_output() - .and_then(SurfaceState::compilation_result) + .compilation_result + .clone() + .or_else(|| { + self.primary_output() + .and_then(SurfaceState::compilation_result) + }) .ok_or_else(|| LayerShikaError::InvalidInput { message: "No compilation result available for session lock".to_string(), })?; diff --git a/crates/composition/src/shell.rs b/crates/composition/src/shell.rs index 1d3e0a2..e09af96 100644 --- a/crates/composition/src/shell.rs +++ b/crates/composition/src/shell.rs @@ -77,15 +77,11 @@ impl ShellBuilder { } /// Builds the shell from the configured surfaces + /// + /// If no surfaces are configured, creates a minimal shell without layer surfaces. + /// This is useful for lock-only applications that don't need persistent UI surfaces. pub fn build(self) -> Result { - let surfaces = if self.surfaces.is_empty() { - vec![SurfaceDefinition { - component: DEFAULT_COMPONENT_NAME.to_string(), - config: SurfaceConfig::default(), - }] - } else { - self.surfaces - }; + let surfaces = self.surfaces; let compilation_result = match self.compilation { CompilationSource::File { path, compiler } => { @@ -399,27 +395,24 @@ impl Shell { ) -> Result { log::info!("Creating Shell with {} windows", definitions.len()); - if definitions.is_empty() { - return Err(Error::Domain(DomainError::Configuration { - message: "At least one window definition is required".to_string(), - })); - } - for def in &definitions { def.config.validate().map_err(Error::Domain)?; } - let is_single_window = definitions.len() == 1; - - if is_single_window { - let definition = definitions.into_iter().next().ok_or_else(|| { - Error::Domain(DomainError::Configuration { - message: "Expected at least one window definition".to_string(), - }) - })?; - Self::new_single_window(compilation_result, definition) - } else { - Self::new_multi_window(compilation_result, &definitions) + match definitions.len() { + 0 => { + log::info!("Creating minimal shell without layer surfaces"); + Self::new_minimal(compilation_result) + } + 1 => { + let definition = definitions.into_iter().next().ok_or_else(|| { + Error::Domain(DomainError::Configuration { + message: "Expected at least one window definition".to_string(), + }) + })?; + Self::new_single_window(compilation_result, definition) + } + _ => Self::new_multi_window(compilation_result, &definitions), } } @@ -545,6 +538,34 @@ impl Shell { Ok(shell) } + fn new_minimal(compilation_result: Rc) -> Result { + let inner = layer_shika_adapters::WaylandShellSystem::new_minimal()?; + let inner_rc: Rc> = Rc::new(RefCell::new(inner)); + + inner_rc + .borrow_mut() + .set_compilation_result(Rc::clone(&compilation_result)); + + let (sender, receiver) = channel::channel(); + + let registry = SurfaceRegistry::new(); + + let shell = Self { + inner: Rc::clone(&inner_rc), + registry, + compilation_result, + command_sender: sender, + output_connected_handlers: Rc::new(RefCell::new(Vec::new())), + output_disconnected_handlers: Rc::new(RefCell::new(Vec::new())), + }; + + shell.setup_command_handler(receiver)?; + + log::info!("Shell created (minimal mode - no layer surfaces)"); + + Ok(shell) + } + fn setup_command_handler(&self, receiver: channel::Channel) -> Result<()> { let loop_handle = self.inner.borrow().event_loop_handle(); let control = self.control();