use crate::wayland::{ config::{LayerSurfaceConfig, ShellSurfaceConfig, WaylandSurfaceConfig}, globals::context::GlobalContext, managed_proxies::{ManagedWlKeyboard, ManagedWlPointer}, ops::WaylandSystemOps, outputs::{OutputManager, OutputManagerContext}, rendering::RenderableSet, session_lock::OutputFilter, surfaces::layer_surface::{SurfaceCtx, SurfaceSetupParams}, surfaces::popup_manager::{PopupContext, PopupManager}, surfaces::{ app_state::AppState, event_context::SharedPointerSerial, surface_builder::{PlatformWrapper, SurfaceStateBuilder}, surface_state::SurfaceState, }, }; use layer_shika_domain::value_objects::handle::SurfaceHandle; use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1; use crate::{ errors::{EventLoopError, LayerShikaError, RenderingError, Result}, rendering::{ egl::context_factory::RenderContextFactory, femtovg::{main_window::FemtoVGWindow, renderable_window::RenderableWindow}, slint_integration::platform::CustomSlintPlatform, }, }; use core::result::Result as CoreResult; use layer_shika_domain::errors::DomainError; use layer_shika_domain::ports::shell::ShellSystemPort; 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::{error, info}; use slint::{ LogicalPosition, PhysicalSize, PlatformError, WindowPosition, platform::{WindowAdapter, femtovg_renderer::FemtoVGRenderer, set_platform, update_timers_and_animations}, }; use slint_interpreter::ComponentInstance; use smithay_client_toolkit::reexports::calloop::{ EventLoop, Interest, LoopHandle, Mode, PostAction, generic::Generic, }; use std::cell::RefCell; use std::rc::Rc; use wayland_client::{ Connection, EventQueue, Proxy, QueueHandle, backend::ObjectId, protocol::{wl_pointer::WlPointer, wl_surface::WlSurface}, }; type PopupManagersAndSurfaces = (Vec>, Vec>); struct OutputSetup { output_id: ObjectId, main_surface_id: ObjectId, window: Rc, builder: SurfaceStateBuilder, surface_handle: SurfaceHandle, shell_surface_name: String, } struct OutputManagerParams<'a> { config: &'a WaylandSurfaceConfig, global_ctx: &'a GlobalContext, connection: &'a Connection, layer_surface_config: LayerSurfaceConfig, render_factory: &'a Rc, popup_context: &'a PopupContext, pointer: &'a Rc, shared_serial: &'a Rc, } pub struct WaylandShellSystem { state: AppState, connection: Rc, event_queue: EventQueue, event_loop: EventLoop<'static, AppState>, } impl WaylandShellSystem { pub fn new(config: &WaylandSurfaceConfig) -> Result { info!("Initializing WindowingSystem"); 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(config, &connection, &mut event_queue)?; Ok(Self { state, connection, event_queue, event_loop, }) } pub fn new_multi(configs: &[ShellSurfaceConfig]) -> Result { if configs.is_empty() { return Err(LayerShikaError::InvalidInput { message: "At least one surface config is required".into(), }); } info!( "Initializing WindowingSystem with {} surface configs", configs.len() ); 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_multi(configs, &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(); Ok((connection, event_queue)) } fn create_layer_surface_config(config: &WaylandSurfaceConfig) -> LayerSurfaceConfig { LayerSurfaceConfig { anchor: config.anchor, margin: config.margin, exclusive_zone: config.exclusive_zone, keyboard_interactivity: config.keyboard_interactivity, height: config.height, width: config.width, } } fn create_output_setups( config: &WaylandSurfaceConfig, global_ctx: &GlobalContext, connection: &Connection, event_queue: &mut EventQueue, pointer: &Rc, layer_surface_config: &LayerSurfaceConfig, ) -> Result> { let mut setups = Vec::new(); for (index, output) in global_ctx.outputs.iter().enumerate() { let is_primary = index == 0; let mut temp_info = OutputInfo::new(OutputHandle::new()); temp_info.set_primary(is_primary); if !config.output_policy.should_render(&temp_info) { info!("Skipping output {} due to output policy", index); continue; } let output_id = output.id(); let setup_params = SurfaceSetupParams { compositor: &global_ctx.compositor, output, layer_shell: &global_ctx.layer_shell, fractional_scale_manager: global_ctx.fractional_scale_manager.as_ref(), viewporter: global_ctx.viewporter.as_ref(), queue_handle: &event_queue.handle(), layer: config.layer, namespace: config.namespace.clone(), }; let surface_ctx = SurfaceCtx::setup(&setup_params, layer_surface_config); let main_surface_id = surface_ctx.surface.id(); let render_factory = RenderContextFactory::new(Rc::clone(&global_ctx.render_context_manager)); let window = Self::initialize_renderer(&surface_ctx.surface, config, &render_factory)?; let mut builder = SurfaceStateBuilder::new() .with_component_definition(config.component_definition.clone()) .with_compilation_result(config.compilation_result.clone()) .with_surface(Rc::clone(&surface_ctx.surface)) .with_layer_surface(Rc::clone(&surface_ctx.layer_surface)) .with_scale_factor(config.scale_factor) .with_height(config.height) .with_width(config.width) .with_exclusive_zone(config.exclusive_zone) .with_connection(Rc::new(connection.clone())) .with_pointer(Rc::clone(pointer)) .with_window(Rc::clone(&window)); if let Some(fs) = &surface_ctx.fractional_scale { builder = builder.with_fractional_scale(Rc::clone(fs)); } if let Some(vp) = &surface_ctx.viewport { builder = builder.with_viewport(Rc::clone(vp)); } setups.push(OutputSetup { output_id, main_surface_id, window, builder, surface_handle: config.surface_handle, shell_surface_name: config.surface_name.clone(), }); } Ok(setups) } fn setup_platform(setups: &[OutputSetup]) -> Result> { let first_setup = setups .first() .ok_or_else(|| LayerShikaError::InvalidInput { message: "No outputs available".into(), })?; let platform = CustomSlintPlatform::new(&first_setup.window); for setup in setups.iter().skip(1) { platform.add_window(Rc::clone(&setup.window)); } set_platform(Box::new(PlatformWrapper(Rc::clone(&platform)))) .map_err(|e| LayerShikaError::PlatformSetup { source: e })?; Ok(platform) } fn create_window_states( setups: Vec, popup_context: &PopupContext, shared_serial: &Rc, app_state: &mut AppState, ) -> Result { let mut popup_managers = Vec::new(); let mut layer_surfaces = Vec::new(); for setup in setups { let mut per_output_surface = SurfaceState::new(setup.builder).map_err(|e| { LayerShikaError::WindowConfiguration { message: e.to_string(), } })?; let popup_manager = Rc::new(PopupManager::new( popup_context.clone(), Rc::clone(per_output_surface.display_metrics()), )); per_output_surface.set_popup_manager(Rc::clone(&popup_manager)); per_output_surface.set_shared_pointer_serial(Rc::clone(shared_serial)); popup_managers.push(Rc::clone(&popup_manager)); layer_surfaces.push(per_output_surface.layer_surface()); app_state.add_shell_surface( &setup.output_id, setup.surface_handle, &setup.shell_surface_name, setup.main_surface_id, per_output_surface, ); } Ok((popup_managers, layer_surfaces)) } fn init_state( config: &WaylandSurfaceConfig, connection: &Connection, event_queue: &mut EventQueue, ) -> Result { let global_ctx = Rc::new(GlobalContext::initialize( connection, &event_queue.handle(), )?); let layer_surface_config = Self::create_layer_surface_config(config); 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()); let render_factory = RenderContextFactory::new(Rc::clone(&global_ctx.render_context_manager)); let popup_context = PopupContext::new( global_ctx.compositor.clone(), global_ctx.xdg_wm_base.clone(), global_ctx.seat.clone(), global_ctx.fractional_scale_manager.clone(), global_ctx.viewporter.clone(), Rc::new(connection.clone()), Rc::clone(&render_factory), ); let setups = Self::create_output_setups( config, global_ctx.as_ref(), connection, event_queue, &pointer, &layer_surface_config, )?; let platform = Self::setup_platform(&setups)?; app_state.set_slint_platform(Rc::clone(&platform)); let (popup_managers, layer_surfaces) = Self::create_window_states(setups, &popup_context, &shared_serial, &mut app_state)?; Self::setup_shared_popup_creator( popup_managers, layer_surfaces, &platform, &event_queue.handle(), &shared_serial, ); let output_manager = Self::create_output_manager(&OutputManagerParams { config, global_ctx: global_ctx.as_ref(), connection, layer_surface_config, render_factory: &render_factory, popup_context: &popup_context, pointer: &pointer, shared_serial: &shared_serial, }); app_state.set_output_manager(Rc::new(RefCell::new(output_manager))); app_state.set_global_context(Rc::clone(&global_ctx)); Ok(app_state) } fn init_state_multi( configs: &[ShellSurfaceConfig], 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()); let render_factory = RenderContextFactory::new(Rc::clone(&global_ctx.render_context_manager)); let popup_context = PopupContext::new( global_ctx.compositor.clone(), global_ctx.xdg_wm_base.clone(), global_ctx.seat.clone(), global_ctx.fractional_scale_manager.clone(), global_ctx.viewporter.clone(), Rc::new(connection.clone()), Rc::clone(&render_factory), ); let setups = Self::create_output_setups_multi( configs, global_ctx.as_ref(), connection, event_queue, &pointer, )?; let platform = Self::setup_platform(&setups)?; app_state.set_slint_platform(Rc::clone(&platform)); let (popup_managers, layer_surfaces) = Self::create_window_states(setups, &popup_context, &shared_serial, &mut app_state)?; Self::setup_shared_popup_creator( popup_managers, layer_surfaces, &platform, &event_queue.handle(), &shared_serial, ); let primary_config = configs.first().map(|c| &c.config); if let Some(config) = primary_config { let layer_surface_config = Self::create_layer_surface_config(config); let output_manager = Self::create_output_manager(&OutputManagerParams { config, global_ctx: global_ctx.as_ref(), connection, layer_surface_config, render_factory: &render_factory, popup_context: &popup_context, pointer: &pointer, shared_serial: &shared_serial, }); app_state.set_output_manager(Rc::new(RefCell::new(output_manager))); } app_state.set_global_context(Rc::clone(&global_ctx)); Ok(app_state) } fn create_output_setups_multi( configs: &[ShellSurfaceConfig], global_ctx: &GlobalContext, connection: &Connection, event_queue: &mut EventQueue, pointer: &Rc, ) -> Result> { let mut setups = Vec::new(); for (output_index, output) in global_ctx.outputs.iter().enumerate() { let is_primary = output_index == 0; let output_id = output.id(); for shell_config in configs { let config = &shell_config.config; let mut temp_info = OutputInfo::new(OutputHandle::new()); temp_info.set_primary(is_primary); if !config.output_policy.should_render(&temp_info) { info!( "Skipping shell surface '{}' on output {} due to output policy", shell_config.name, output_index ); continue; } let layer_surface_config = Self::create_layer_surface_config(config); let setup_params = SurfaceSetupParams { compositor: &global_ctx.compositor, output, layer_shell: &global_ctx.layer_shell, fractional_scale_manager: global_ctx.fractional_scale_manager.as_ref(), viewporter: global_ctx.viewporter.as_ref(), queue_handle: &event_queue.handle(), layer: config.layer, namespace: config.namespace.clone(), }; let surface_ctx = SurfaceCtx::setup(&setup_params, &layer_surface_config); let main_surface_id = surface_ctx.surface.id(); let render_factory = RenderContextFactory::new(Rc::clone(&global_ctx.render_context_manager)); let window = Self::initialize_renderer(&surface_ctx.surface, config, &render_factory)?; let mut builder = SurfaceStateBuilder::new() .with_component_definition(config.component_definition.clone()) .with_compilation_result(config.compilation_result.clone()) .with_surface(Rc::clone(&surface_ctx.surface)) .with_layer_surface(Rc::clone(&surface_ctx.layer_surface)) .with_scale_factor(config.scale_factor) .with_height(config.height) .with_width(config.width) .with_exclusive_zone(config.exclusive_zone) .with_connection(Rc::new(connection.clone())) .with_pointer(Rc::clone(pointer)) .with_window(Rc::clone(&window)); if let Some(fs) = &surface_ctx.fractional_scale { builder = builder.with_fractional_scale(Rc::clone(fs)); } if let Some(vp) = &surface_ctx.viewport { builder = builder.with_viewport(Rc::clone(vp)); } info!( "Created setup for shell surface '{}' on output {}", shell_config.name, output_index ); setups.push(OutputSetup { output_id: output_id.clone(), main_surface_id, window, builder, surface_handle: shell_config.config.surface_handle, shell_surface_name: shell_config.name.clone(), }); } } Ok(setups) } fn create_output_manager(params: &OutputManagerParams<'_>) -> OutputManager { let manager_context = OutputManagerContext { compositor: params.global_ctx.compositor.clone(), layer_shell: params.global_ctx.layer_shell.clone(), fractional_scale_manager: params.global_ctx.fractional_scale_manager.clone(), viewporter: params.global_ctx.viewporter.clone(), render_factory: Rc::clone(params.render_factory), popup_context: params.popup_context.clone(), pointer: Rc::clone(params.pointer), shared_serial: Rc::clone(params.shared_serial), connection: Rc::new(params.connection.clone()), }; OutputManager::new( manager_context, params.config.clone(), params.layer_surface_config, ) } fn setup_shared_popup_creator( popup_managers: Vec>, layer_surfaces: Vec>, platform: &Rc, queue_handle: &QueueHandle, shared_serial: &Rc, ) { let Some(first_manager) = popup_managers.first() else { info!("No popup managers available"); return; }; if !first_manager.has_xdg_shell() { info!("xdg-shell not available, popups will not be supported"); return; } info!( "Setting up shared popup creator for {} output(s)", popup_managers.len() ); let queue_handle_clone = queue_handle.clone(); let serial_holder = Rc::clone(shared_serial); platform.set_popup_creator(move || { info!("Popup creator called! Searching for pending popup..."); let serial = serial_holder.get(); for (idx, (popup_manager, layer_surface)) in popup_managers.iter().zip(layer_surfaces.iter()).enumerate() { if popup_manager.has_pending_popup() { info!("Found pending popup in output #{}", idx); let popup_surface = popup_manager .create_pending_popup(&queue_handle_clone, layer_surface, serial) .map_err(|e| { PlatformError::Other(format!("Failed to create popup: {e}")) })?; info!("Popup created successfully for output #{}", idx); return Ok(popup_surface as Rc); } } Err(PlatformError::Other( "No pending popup request found in any output".into(), )) }); } pub(crate) fn initialize_renderer( surface: &Rc, config: &WaylandSurfaceConfig, render_factory: &Rc, ) -> Result> { let init_size = PhysicalSize::new(1, 1); let context = render_factory.create_context(&surface.id(), init_size)?; let renderer = FemtoVGRenderer::new(context) .map_err(|e| LayerShikaError::FemtoVGRendererCreation { source: e })?; let femtovg_window = FemtoVGWindow::new(renderer); femtovg_window.set_size(slint::WindowSize::Physical(init_size)); femtovg_window.set_scale_factor(config.scale_factor); femtovg_window.set_position(WindowPosition::Logical(LogicalPosition::new(0., 0.))); Ok(femtovg_window) } pub fn event_loop_handle(&self) -> LoopHandle<'static, AppState> { self.event_loop.handle() } pub fn run(&mut self) -> Result<()> { info!("Starting WindowingSystem main loop"); info!("Processing initial Wayland configuration events"); // first roundtrip to receive initial output, globals, and surface configure events // second roundtrip handles any cascading configure events like fractional scaling and layer surface configures for i in 0..2 { let dispatched = self.event_queue.roundtrip(&mut self.state)?; info!("Roundtrip {} dispatched {} events", i + 1, dispatched); self.connection .flush() .map_err(|e| LayerShikaError::WaylandProtocol { source: e })?; update_timers_and_animations(); self.state.render_all_dirty()?; if let Some(lock_manager) = self.state.lock_manager() { lock_manager.render_all_dirty()?; } } info!("Initial configuration complete, requesting final render"); for surface in self.state.all_outputs() { RenderableWindow::request_redraw(surface.window().as_ref()); } update_timers_and_animations(); self.state.render_all_dirty()?; if let Some(lock_manager) = self.state.lock_manager() { lock_manager.render_all_dirty()?; } self.connection .flush() .map_err(|e| LayerShikaError::WaylandProtocol { source: e })?; self.setup_wayland_event_source()?; let event_queue = &mut self.event_queue; let connection = &self.connection; self.event_loop .run(None, &mut self.state, move |shared_data| { if let Err(e) = Self::process_events(connection, event_queue, shared_data) { error!("Error processing events: {e}"); } }) .map_err(|e| EventLoopError::Execution { source: e })?; Ok(()) } fn setup_wayland_event_source(&self) -> Result<()> { let connection = Rc::clone(&self.connection); self.event_loop .handle() .insert_source( Generic::new(connection, Interest::READ, Mode::Level), move |_, _connection, _shared_data| Ok(PostAction::Continue), ) .map_err(|e| EventLoopError::InsertSource { message: format!("{e:?}"), })?; Ok(()) } fn process_events( connection: &Connection, event_queue: &mut EventQueue, shared_data: &mut AppState, ) -> Result<()> { if let Some(guard) = event_queue.prepare_read() { guard .read() .map_err(|e| LayerShikaError::WaylandProtocol { source: e })?; } event_queue.dispatch_pending(shared_data)?; update_timers_and_animations(); for surface in shared_data.all_outputs() { surface .window() .render_frame_if_dirty() .map_err(|e| RenderingError::Operation { message: e.to_string(), })?; if let Some(popup_manager) = surface.popup_manager() { popup_manager .render_popups() .map_err(|e| RenderingError::Operation { message: e.to_string(), })?; } } if let Some(lock_manager) = shared_data.lock_manager() { lock_manager.render_all_dirty()?; } connection .flush() .map_err(|e| LayerShikaError::WaylandProtocol { source: e })?; Ok(()) } pub fn component_instance(&self) -> Result<&ComponentInstance> { self.state .primary_output() .ok_or_else(|| LayerShikaError::InvalidInput { message: "No outputs available".into(), }) .map(SurfaceState::component_instance) } pub fn state(&self) -> Result<&SurfaceState> { self.state .primary_output() .ok_or_else(|| LayerShikaError::InvalidInput { message: "No outputs available".into(), }) } pub fn app_state(&self) -> &AppState { &self.state } pub fn app_state_mut(&mut self) -> &mut AppState { &mut self.state } pub fn spawn_surface(&mut self, config: &ShellSurfaceConfig) -> Result> { log::info!("Spawning new surface '{}'", config.name); let mut handles = Vec::new(); for (output_handle, _surface) in self.state.outputs_with_handles() { handles.push(output_handle); } log::info!( "Surface '{}' would spawn on {} outputs (dynamic spawning not yet fully implemented)", config.name, handles.len() ); Ok(handles) } pub fn despawn_surface(&mut self, surface_name: &str) -> Result<()> { log::info!("Despawning surface '{}'", surface_name); let removed = self.state.remove_surfaces_by_name(surface_name); log::info!( "Removed {} surface instances for '{}'", removed.len(), surface_name ); Ok(()) } } impl ShellSystemPort for WaylandShellSystem { fn run(&mut self) -> CoreResult<(), DomainError> { WaylandShellSystem::run(self).map_err(|e| DomainError::Adapter { source: Box::new(e), }) } } impl WaylandSystemOps for WaylandShellSystem { fn run(&mut self) -> Result<()> { WaylandShellSystem::run(self) } fn spawn_surface(&mut self, config: &ShellSurfaceConfig) -> Result> { WaylandShellSystem::spawn_surface(self, config) } fn despawn_surface(&mut self, name: &str) -> Result<()> { WaylandShellSystem::despawn_surface(self, name) } fn activate_session_lock(&mut self, component_name: &str, config: LockConfig) -> Result<()> { self.state.activate_session_lock(component_name, config) } fn deactivate_session_lock(&mut self) -> Result<()> { self.state.deactivate_session_lock() } fn is_session_lock_available(&self) -> bool { self.state.is_session_lock_available() } fn session_lock_state(&self) -> Option { self.state.current_lock_state() } fn register_session_lock_callback( &mut self, callback_name: &str, handler: Rc slint_interpreter::Value>, ) { self.state .register_session_lock_callback(callback_name, handler); } fn register_session_lock_callback_with_filter( &mut self, callback_name: &str, handler: Rc slint_interpreter::Value>, filter: OutputFilter, ) { self.state .register_session_lock_callback_with_filter(callback_name, handler, filter); } fn session_lock_component_name(&self) -> Option { self.state.session_lock_component_name() } fn iter_lock_surfaces(&self, f: &mut dyn FnMut(OutputHandle, &ComponentInstance)) { self.state.iter_lock_surfaces(f); } fn count_lock_surfaces(&self) -> usize { self.state.count_lock_surfaces() } fn app_state(&self) -> &AppState { WaylandShellSystem::app_state(self) } fn app_state_mut(&mut self) -> &mut AppState { WaylandShellSystem::app_state_mut(self) } fn event_loop_handle(&self) -> LoopHandle<'static, AppState> { WaylandShellSystem::event_loop_handle(self) } fn component_instance(&self) -> Result<&ComponentInstance> { WaylandShellSystem::component_instance(self) } }