use crate::event_loop::{EventLoopHandle, FromAppState}; use crate::layer_surface::LayerSurfaceHandle; use crate::shell_config::{CompiledUiSource, ShellConfig}; use crate::shell_runtime::ShellRuntime; use crate::surface_registry::{SurfaceDefinition, SurfaceEntry, SurfaceRegistry}; use crate::system::{ CallbackContext, EventDispatchContext, PopupCommand, ShellCommand, ShellControl, SurfaceCommand, SurfaceTarget, }; use crate::value_conversion::IntoValue; use crate::{Error, Result}; use layer_shika_adapters::errors::EventLoopError; use layer_shika_adapters::platform::calloop::channel; use layer_shika_adapters::platform::slint_interpreter::{ CompilationResult, Compiler, ComponentInstance, Value, }; use layer_shika_adapters::{ AppState, ShellSurfaceConfig, SurfaceState, WaylandSurfaceConfig, WaylandSystemOps, }; use layer_shika_domain::config::SurfaceConfig; use layer_shika_domain::entities::output_registry::OutputRegistry; use layer_shika_domain::errors::DomainError; use layer_shika_domain::prelude::{ AnchorEdges, KeyboardInteractivity, Layer, Margins, OutputPolicy, ScaleFactor, SurfaceDimension, }; use layer_shika_domain::value_objects::handle::SurfaceHandle; use layer_shika_domain::value_objects::output_handle::OutputHandle; use layer_shika_domain::value_objects::output_info::OutputInfo; use layer_shika_domain::value_objects::surface_instance_id::SurfaceInstanceId; use spin_on::spin_on; use std::cell::RefCell; use std::path::{Path, PathBuf}; use std::rc::Rc; /// Default Slint component name used when none is specified pub const DEFAULT_COMPONENT_NAME: &str = "Main"; enum CompilationSource { File { path: PathBuf, compiler: Compiler }, Source { code: String, compiler: Compiler }, Compiled(Rc), } /// Builder for configuring and creating a Shell with one or more surfaces /// /// Chain `.surface()` calls to configure multiple surfaces, then call `.build()` or `.run()`. /// If no surfaces are configured, a default "Main" surface is created. pub struct ShellBuilder { compilation: CompilationSource, surfaces: Vec, } impl ShellBuilder { /// Starts configuration for a new surface with the given component name pub fn surface(self, component: impl Into) -> SurfaceConfigBuilder { SurfaceConfigBuilder { shell_builder: self, component: component.into(), config: SurfaceConfig::default(), } } /// Discovers and registers multiple surfaces by component names #[must_use] pub fn discover_surfaces( mut self, components: impl IntoIterator>, ) -> Self { for component in components { self.surfaces.push(SurfaceDefinition { component: component.into(), config: SurfaceConfig::default(), }); } self } /// Builds the shell from the configured 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 compilation_result = match self.compilation { CompilationSource::File { path, compiler } => { let result = spin_on(compiler.build_from_path(&path)); let diagnostics: Vec<_> = result.diagnostics().collect(); if !diagnostics.is_empty() { let messages: Vec = diagnostics.iter().map(ToString::to_string).collect(); return Err(DomainError::Configuration { message: format!( "Failed to compile Slint file '{}':\n{}", path.display(), messages.join("\n") ), } .into()); } Rc::new(result) } CompilationSource::Source { code, compiler } => { let result = spin_on(compiler.build_from_source(code, PathBuf::default())); let diagnostics: Vec<_> = result.diagnostics().collect(); if !diagnostics.is_empty() { let messages: Vec = diagnostics.iter().map(ToString::to_string).collect(); return Err(DomainError::Configuration { message: format!( "Failed to compile Slint source:\n{}", messages.join("\n") ), } .into()); } Rc::new(result) } CompilationSource::Compiled(result) => result, }; Shell::new(compilation_result, surfaces) } } /// Builder for configuring a single surface within a Shell /// /// Chain configuration methods, then either start a new surface with `.surface()` /// or finalize with `.build()` or `.run()`. pub struct SurfaceConfigBuilder { shell_builder: ShellBuilder, component: String, config: SurfaceConfig, } impl SurfaceConfigBuilder { /// Sets both width and height for the surface #[must_use] pub fn size(mut self, width: u32, height: u32) -> Self { self.config.dimensions = SurfaceDimension::new(width, height); self } /// Sets the height of the surface #[must_use] pub fn height(mut self, height: u32) -> Self { self.config.dimensions = SurfaceDimension::new(self.config.dimensions.width(), height); self } /// Sets the width of the surface #[must_use] pub fn width(mut self, width: u32) -> Self { self.config.dimensions = SurfaceDimension::new(width, self.config.dimensions.height()); self } /// Sets the layer (stacking order) for the surface #[must_use] pub const fn layer(mut self, layer: Layer) -> Self { self.config.layer = layer; self } /// Sets the margins around the surface #[must_use] pub fn margin(mut self, margin: impl Into) -> Self { self.config.margin = margin.into(); self } /// Sets the anchor edges for positioning #[must_use] pub const fn anchor(mut self, anchor: AnchorEdges) -> Self { self.config.anchor = anchor; self } /// Sets the exclusive zone in pixels /// /// Reserves screen space that other windows avoid. Positive values reserve from anchored edge, /// `0` means no reservation, `-1` lets compositor decide. #[must_use] pub const fn exclusive_zone(mut self, zone: i32) -> Self { self.config.exclusive_zone = zone; self } /// Sets the namespace identifier for the surface #[must_use] pub fn namespace(mut self, namespace: impl Into) -> Self { self.config.namespace = namespace.into(); self } /// Sets the scale factor for the surface #[must_use] pub fn scale_factor(mut self, sf: impl TryInto) -> Self { self.config.scale_factor = sf.try_into().unwrap_or_default(); self } /// Sets the keyboard interactivity mode #[must_use] pub const fn keyboard_interactivity(mut self, mode: KeyboardInteractivity) -> Self { self.config.keyboard_interactivity = mode; self } /// Sets the output policy for multi-monitor configuration /// /// Controls which monitors display this surface. Default is `OutputPolicy::All`. #[must_use] pub fn output_policy(mut self, policy: OutputPolicy) -> Self { self.config.output_policy = policy; self } /// Starts configuration for another surface #[must_use] pub fn surface(self, component: impl Into) -> SurfaceConfigBuilder { let shell_builder = self.complete(); shell_builder.surface(component) } /// Builds the shell with all configured surfaces pub fn build(self) -> Result { self.complete().build() } /// Builds and runs the shell pub fn run(self) -> Result<()> { let mut shell = self.build()?; shell.run() } fn complete(mut self) -> ShellBuilder { self.shell_builder.surfaces.push(SurfaceDefinition { component: self.component, config: self.config, }); self.shell_builder } } type OutputConnectedHandler = Box; type OutputDisconnectedHandler = Box; /// Main runtime for managing Wayland layer-shell surfaces with Slint UI /// /// Manages surface lifecycle, event loop integration, and component instantiation. /// Supports multiple surfaces across monitors, dynamic spawning, and popup windows. /// /// Create via `Shell::from_file()`, `from_source()`, or `from_compilation()`. pub struct Shell { inner: Rc>, registry: SurfaceRegistry, compilation_result: Rc, command_sender: channel::Sender, output_connected_handlers: Rc>>, output_disconnected_handlers: Rc>>, } impl Shell { /// Creates a shell builder from a Slint file path pub fn from_file(path: impl AsRef) -> ShellBuilder { ShellBuilder { compilation: CompilationSource::File { path: path.as_ref().to_path_buf(), compiler: Compiler::default(), }, surfaces: Vec::new(), } } /// Creates a shell builder from a Slint file path with a custom compiler /// /// Useful for configuring include paths, style overrides, or compilation settings. pub fn from_file_with_compiler(path: impl AsRef, compiler: Compiler) -> ShellBuilder { ShellBuilder { compilation: CompilationSource::File { path: path.as_ref().to_path_buf(), compiler, }, surfaces: Vec::new(), } } /// Creates a shell builder from Slint source code pub fn from_source(code: impl Into) -> ShellBuilder { ShellBuilder { compilation: CompilationSource::Source { code: code.into(), compiler: Compiler::default(), }, surfaces: Vec::new(), } } /// Creates a shell builder from Slint source code with a custom compiler pub fn from_source_with_compiler(code: impl Into, compiler: Compiler) -> ShellBuilder { ShellBuilder { compilation: CompilationSource::Source { code: code.into(), compiler, }, surfaces: Vec::new(), } } /// Creates a shell builder from a pre-compiled Slint compilation result pub fn from_compilation(result: Rc) -> ShellBuilder { ShellBuilder { compilation: CompilationSource::Compiled(result), surfaces: Vec::new(), } } /// Creates an empty shell builder for manual configuration pub fn builder() -> ShellBuilder { ShellBuilder { compilation: CompilationSource::Source { code: String::new(), compiler: Compiler::default(), }, surfaces: Vec::new(), } } /// Compiles a Slint file and returns the compilation result pub fn compile_file(path: impl AsRef) -> Result> { let compiler = Compiler::default(); let result = spin_on(compiler.build_from_path(path.as_ref())); let diagnostics: Vec<_> = result.diagnostics().collect(); if !diagnostics.is_empty() { let messages: Vec = diagnostics.iter().map(ToString::to_string).collect(); return Err(DomainError::Configuration { message: format!( "Failed to compile Slint file '{}':\n{}", path.as_ref().display(), messages.join("\n") ), } .into()); } Ok(Rc::new(result)) } /// Compiles Slint source code and returns the compilation result pub fn compile_source(code: impl Into) -> Result> { let compiler = Compiler::default(); let result = spin_on(compiler.build_from_source(code.into(), PathBuf::default())); let diagnostics: Vec<_> = result.diagnostics().collect(); if !diagnostics.is_empty() { let messages: Vec = diagnostics.iter().map(ToString::to_string).collect(); return Err(DomainError::Configuration { message: format!("Failed to compile Slint source:\n{}", messages.join("\n")), } .into()); } Ok(Rc::new(result)) } /// Creates a shell from a complete configuration object pub fn from_config(config: ShellConfig) -> Result { let compilation_result = match config.ui_source { CompiledUiSource::File(path) => Self::compile_file(&path)?, CompiledUiSource::Source(code) => Self::compile_source(code)?, CompiledUiSource::Compiled(result) => result, }; let surfaces: Vec = if config.surfaces.is_empty() { vec![SurfaceDefinition { component: DEFAULT_COMPONENT_NAME.to_string(), config: SurfaceConfig::default(), }] } else { config .surfaces .into_iter() .map(|s| SurfaceDefinition { component: s.component, config: s.config, }) .collect() }; Self::new(compilation_result, surfaces) } pub(crate) fn new( compilation_result: Rc, definitions: Vec, ) -> 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(), })); } 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) } } fn new_single_window( compilation_result: Rc, definition: SurfaceDefinition, ) -> Result { let component_definition = compilation_result .component(&definition.component) .ok_or_else(|| { Error::Domain(DomainError::Configuration { message: format!( "Component '{}' not found in compilation result", definition.component ), }) })?; let handle = SurfaceHandle::new(); let wayland_config = WaylandSurfaceConfig::from_domain_config( handle, &definition.component, component_definition, Some(Rc::clone(&compilation_result)), definition.config.clone(), ); let inner = layer_shika_adapters::WaylandShellSystem::new(&wayland_config)?; let inner_rc: Rc> = Rc::new(RefCell::new(inner)); let (sender, receiver) = channel::channel(); let mut registry = SurfaceRegistry::new(); let entry = SurfaceEntry::new(handle, definition.component.clone(), definition); registry.insert(entry)?; 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 (single-window mode)"); Ok(shell) } fn new_multi_window( compilation_result: Rc, definitions: &[SurfaceDefinition], ) -> Result { let shell_configs_with_handles: Vec<(SurfaceHandle, ShellSurfaceConfig)> = definitions .iter() .map(|def| { let component_definition = compilation_result .component(&def.component) .ok_or_else(|| { Error::Domain(DomainError::Configuration { message: format!( "Component '{}' not found in compilation result", def.component ), }) })?; let handle = SurfaceHandle::new(); let wayland_config = WaylandSurfaceConfig::from_domain_config( handle, &def.component, component_definition, Some(Rc::clone(&compilation_result)), def.config.clone(), ); Ok(( handle, ShellSurfaceConfig { name: def.component.clone(), config: wayland_config, }, )) }) .collect::>>()?; let shell_configs: Vec = shell_configs_with_handles .iter() .map(|(_, cfg)| cfg.clone()) .collect(); let inner = layer_shika_adapters::WaylandShellSystem::new_multi(&shell_configs)?; let inner_rc: Rc> = Rc::new(RefCell::new(inner)); let (sender, receiver) = channel::channel(); let mut registry = SurfaceRegistry::new(); for ((handle, _), definition) in shell_configs_with_handles.iter().zip(definitions.iter()) { let entry = SurfaceEntry::new(*handle, definition.component.clone(), definition.clone()); registry.insert(entry)?; } 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 (multi-surface mode) with surfaces: {:?}", shell.surface_names() ); Ok(shell) } fn setup_command_handler(&self, receiver: channel::Channel) -> Result<()> { let loop_handle = self.inner.borrow().event_loop_handle(); let control = self.control(); loop_handle .insert_source(receiver, move |event, (), app_state| { if let channel::Event::Msg(command) = event { let mut ctx = crate::system::EventDispatchContext::from_app_state(app_state); match command { ShellCommand::Popup(popup_cmd) => { Self::handle_popup_command(popup_cmd, &mut ctx, &control); } ShellCommand::Surface(surface_cmd) => { Self::handle_surface_command(surface_cmd, &mut ctx); } ShellCommand::Render => { if let Err(e) = ctx.render_frame_if_dirty() { log::error!("Failed to render frame: {}", e); } } } } }) .map_err(|e| { Error::Adapter( EventLoopError::InsertSource { message: format!("Failed to setup command handler: {e:?}"), } .into(), ) })?; Ok(()) } fn handle_popup_command( command: PopupCommand, ctx: &mut EventDispatchContext<'_>, _control: &ShellControl, ) { match command { PopupCommand::Show(request) => { if let Err(e) = ctx.show_popup(&request) { log::error!("Failed to show popup: {}", e); } } PopupCommand::Close(handle) => { if let Err(e) = ctx.close_popup(handle) { log::error!("Failed to close popup: {}", e); } } PopupCommand::Resize { handle, width, height, } => { if let Err(e) = ctx.resize_popup(handle, width, height) { log::error!("Failed to resize popup: {}", e); } } } } fn resolve_surface_target<'a>( ctx: &'a mut EventDispatchContext<'_>, target: &SurfaceTarget, ) -> Vec<&'a mut SurfaceState> { match target { SurfaceTarget::ByInstance(id) => { if let Some(surface) = ctx.surface_by_instance_mut(id.surface(), id.output()) { vec![surface] } else { log::warn!( "Surface instance not found: handle {:?} on output {:?}", id.surface(), id.output() ); vec![] } } SurfaceTarget::ByHandle(handle) => ctx.surfaces_by_handle_mut(*handle), SurfaceTarget::ByName(name) => ctx.surfaces_by_name_mut(name), SurfaceTarget::ByNameAndOutput { name, output } => { ctx.surfaces_by_name_and_output_mut(name, *output) } } } fn apply_surface_resize( ctx: &mut EventDispatchContext<'_>, target: &SurfaceTarget, width: u32, height: u32, ) { log::debug!( "Surface command: Resize {:?} to {}x{}", target, width, height ); for surface in Self::resolve_surface_target(ctx, target) { let handle = LayerSurfaceHandle::from_window_state(surface); handle.set_size(width, height); handle.commit(); surface.update_size_with_compositor_logic(width, height); } } fn apply_surface_config_change( ctx: &mut EventDispatchContext<'_>, target: &SurfaceTarget, operation: &str, apply: F, ) where F: Fn(&LayerSurfaceHandle<'_>), { log::debug!("Surface command: {} {:?}", operation, target); for surface in Self::resolve_surface_target(ctx, target) { let handle = LayerSurfaceHandle::from_window_state(surface); apply(&handle); handle.commit(); } } fn apply_full_config( ctx: &mut EventDispatchContext<'_>, target: &SurfaceTarget, config: &SurfaceConfig, ) { log::debug!("Surface command: ApplyConfig {:?}", target); for surface in Self::resolve_surface_target(ctx, target) { let handle = LayerSurfaceHandle::from_window_state(surface); handle.set_size(config.dimensions.width(), config.dimensions.height()); handle.set_anchor_edges(config.anchor); handle.set_exclusive_zone(config.exclusive_zone); handle.set_margins(config.margin); handle.set_layer(config.layer); handle.set_keyboard_interactivity(config.keyboard_interactivity); handle.commit(); surface.update_size_with_compositor_logic( config.dimensions.width(), config.dimensions.height(), ); } } fn handle_surface_command(command: SurfaceCommand, ctx: &mut EventDispatchContext<'_>) { match command { SurfaceCommand::Resize { target, width, height, } => { Self::apply_surface_resize(ctx, &target, width, height); } SurfaceCommand::SetAnchor { target, anchor } => { Self::apply_surface_config_change(ctx, &target, "SetAnchor", |handle| { handle.set_anchor_edges(anchor); }); } SurfaceCommand::SetExclusiveZone { target, zone } => { Self::apply_surface_config_change(ctx, &target, "SetExclusiveZone", |handle| { handle.set_exclusive_zone(zone); }); } SurfaceCommand::SetMargins { target, margins } => { Self::apply_surface_config_change(ctx, &target, "SetMargins", |handle| { handle.set_margins(margins); }); } SurfaceCommand::SetLayer { target, layer } => { Self::apply_surface_config_change(ctx, &target, "SetLayer", |handle| { handle.set_layer(layer); }); } SurfaceCommand::SetKeyboardInteractivity { target, mode } => { Self::apply_surface_config_change( ctx, &target, "SetKeyboardInteractivity", |handle| { handle.set_keyboard_interactivity(mode); }, ); } SurfaceCommand::SetOutputPolicy { target, policy } => { log::debug!( "Surface command: SetOutputPolicy {:?} to {:?}", target, policy ); log::warn!( "SetOutputPolicy is not yet implemented - requires runtime surface spawning" ); } SurfaceCommand::SetScaleFactor { target, factor } => { log::debug!( "Surface command: SetScaleFactor {:?} to {:?}", target, factor ); log::warn!( "SetScaleFactor is not yet implemented - requires runtime surface property updates" ); } SurfaceCommand::ApplyConfig { target, config } => { Self::apply_full_config(ctx, &target, &config); } } if let Err(e) = ctx.render_frame_if_dirty() { log::error!("Failed to render frame after surface command: {}", e); } } /// Returns a control handle for sending commands to the shell #[must_use] pub fn control(&self) -> ShellControl { ShellControl::new(self.command_sender.clone()) } /// Returns the names of all registered surfaces pub fn surface_names(&self) -> Vec<&str> { self.registry.surface_names() } /// Checks if a surface with the given name exists pub fn has_surface(&self, name: &str) -> bool { self.registry.contains_name(name) } /// Returns a handle to the event loop for registering custom event sources pub fn event_loop_handle(&self) -> EventLoopHandle { EventLoopHandle::new(Rc::downgrade(&self.inner)) } /// Starts the event loop and runs the shell until exit pub fn run(&mut self) -> Result<()> { log::info!( "Starting Shell event loop with {} windows", self.registry.len() ); self.inner.borrow_mut().run()?; Ok(()) } /// Spawns a new surface at runtime from the given definition /// /// The surface is instantiated on outputs according to its `OutputPolicy`. pub fn spawn_surface(&mut self, definition: SurfaceDefinition) -> Result> { let component_definition = self .compilation_result .component(&definition.component) .ok_or_else(|| { Error::Domain(DomainError::Configuration { message: format!( "Component '{}' not found in compilation result", definition.component ), }) })?; let handle = SurfaceHandle::new(); let wayland_config = WaylandSurfaceConfig::from_domain_config( handle, &definition.component, component_definition, Some(Rc::clone(&self.compilation_result)), definition.config.clone(), ); let shell_config = ShellSurfaceConfig { name: definition.component.clone(), config: wayland_config, }; let mut system = self.inner.borrow_mut(); let handles = system.spawn_surface(&shell_config)?; let surface_handle = SurfaceHandle::new(); let entry = SurfaceEntry::new(surface_handle, definition.component.clone(), definition); self.registry.insert(entry)?; log::info!( "Spawned surface with handle {:?}, created {} output instances", surface_handle, handles.len() ); Ok(vec![surface_handle]) } /// Removes and destroys a surface by its handle pub fn despawn_surface(&mut self, handle: SurfaceHandle) -> Result<()> { let entry = self.registry.remove(handle).ok_or_else(|| { Error::Domain(DomainError::Configuration { message: format!("Surface handle {:?} not found", handle), }) })?; let mut system = self.inner.borrow_mut(); system.despawn_surface(&entry.name)?; log::info!( "Despawned surface '{}' with handle {:?}", entry.name, handle ); Ok(()) } /// Registers a handler called when a new output (monitor) is connected /// /// Surfaces with `OutputPolicy::All` spawn automatically on new outputs. pub fn on_output_connected(&mut self, handler: F) -> Result<()> where F: Fn(&OutputInfo) + 'static, { self.output_connected_handlers .borrow_mut() .push(Box::new(handler)); Ok(()) } /// Registers a handler called when an output is disconnected pub fn on_output_disconnected(&mut self, handler: F) -> Result<()> where F: Fn(OutputHandle) + 'static, { self.output_disconnected_handlers .borrow_mut() .push(Box::new(handler)); Ok(()) } /// Returns the handle for a surface by name pub fn get_surface_handle(&self, name: &str) -> Option { self.registry.handle_by_name(name) } /// Returns the name of a surface by its handle pub fn get_surface_name(&self, handle: SurfaceHandle) -> Option<&str> { self.registry.name_by_handle(handle) } /// Executes a function with access to a surface component instance by name pub fn with_surface(&self, name: &str, f: F) -> Result where F: FnOnce(&ComponentInstance) -> R, { if !self.registry.contains_name(name) { return Err(Error::Domain(DomainError::Configuration { message: format!("Window '{}' not found", name), })); } let system = self.inner.borrow(); system .app_state() .surfaces_by_name(name) .first() .map(|surface| f(surface.component_instance())) .ok_or_else(|| { Error::Domain(DomainError::Configuration { message: format!("No instance found for window '{}'", name), }) }) } /// Executes a function with each surface name and component instance pub fn with_all_surfaces(&self, mut f: F) where F: FnMut(&str, &ComponentInstance), { let system = self.inner.borrow(); for name in self.registry.surface_names() { for surface in system.app_state().surfaces_by_name(name) { f(name, surface.component_instance()); } } } /// Executes a function with access to a surface on a specific output pub fn with_output(&self, handle: OutputHandle, f: F) -> Result where F: FnOnce(&ComponentInstance) -> R, { let system = self.inner.borrow(); let window = system .app_state() .get_output_by_handle(handle) .ok_or_else(|| { Error::Domain(DomainError::Configuration { message: format!("Output with handle {:?} not found", handle), }) })?; Ok(f(window.component_instance())) } /// Executes a function with each output handle and component instance pub fn with_all_outputs(&self, mut f: F) where F: FnMut(OutputHandle, &ComponentInstance), { let system = self.inner.borrow(); for (handle, surface) in system.app_state().outputs_with_handles() { f(handle, surface.component_instance()); } } /// Returns the Slint compilation result used by this shell #[must_use] pub fn compilation_result(&self) -> &Rc { &self.compilation_result } /// Returns the registry of all connected outputs pub fn output_registry(&self) -> OutputRegistry { let system = self.inner.borrow(); system.app_state().output_registry().clone() } /// Returns information about a specific output by handle pub fn get_output_info(&self, handle: OutputHandle) -> Option { let system = self.inner.borrow(); system.app_state().get_output_info(handle).cloned() } /// Returns information about all connected outputs pub fn all_output_info(&self) -> Vec { let system = self.inner.borrow(); system.app_state().all_output_info().cloned().collect() } /// Creates a selection for targeting specific surfaces by criteria pub fn select(&self, selector: impl Into) -> crate::Selection<'_> { crate::Selection::new(self, selector.into()) } fn get_output_handles(&self) -> (Option, Option) { let registry = &self.output_registry(); (registry.primary_handle(), registry.active_handle()) } pub(crate) fn on_internal( &self, selector: &crate::Selector, callback_name: &str, handler: F, ) where F: Fn(CallbackContext) -> R + Clone + 'static, R: IntoValue, { let control = self.control(); let handler = Rc::new(handler); let system = self.inner.borrow(); let (primary, active) = self.get_output_handles(); for (key, surface) in system.app_state().surfaces_with_keys() { let surface_handle = key.surface_handle; let output_handle = key.output_handle; let surface_name = self.registry.by_handle(surface_handle).map_or_else( || format!("Unknown-{}", surface_handle.id()), |entry| entry.name.clone(), ); let surface_info = crate::SurfaceInfo { name: surface_name.clone(), output: output_handle, }; let output_info = system.app_state().get_output_info(output_handle); if selector.matches(&surface_info, output_info, primary, active) { let instance_id = SurfaceInstanceId::new(surface_handle, output_handle); let handler_rc = Rc::clone(&handler); let control_clone = control.clone(); let surface_name_clone = surface_name.clone(); if let Err(e) = surface .component_instance() .set_callback(callback_name, move |_args| { let ctx = CallbackContext::new( instance_id, surface_name_clone.clone(), control_clone.clone(), ); handler_rc(ctx).into_value() }) { log::error!( "Failed to register callback '{}' on surface '{}': {}", callback_name, surface_name, e ); } } } } pub(crate) fn on_with_args_internal( &self, selector: &crate::Selector, callback_name: &str, handler: F, ) where F: Fn(&[Value], CallbackContext) -> R + Clone + 'static, R: IntoValue, { let control = self.control(); let handler = Rc::new(handler); let system = self.inner.borrow(); let (primary, active) = self.get_output_handles(); for (key, surface) in system.app_state().surfaces_with_keys() { let surface_handle = key.surface_handle; let output_handle = key.output_handle; let surface_name = self.registry.by_handle(surface_handle).map_or_else( || format!("Unknown-{}", surface_handle.id()), |entry| entry.name.clone(), ); let surface_info = crate::SurfaceInfo { name: surface_name.clone(), output: output_handle, }; let output_info = system.app_state().get_output_info(output_handle); if selector.matches(&surface_info, output_info, primary, active) { let instance_id = SurfaceInstanceId::new(surface_handle, output_handle); let handler_rc = Rc::clone(&handler); let control_clone = control.clone(); let surface_name_clone = surface_name.clone(); if let Err(e) = surface .component_instance() .set_callback(callback_name, move |args| { let ctx = CallbackContext::new( instance_id, surface_name_clone.clone(), control_clone.clone(), ); handler_rc(args, ctx).into_value() }) { log::error!( "Failed to register callback '{}' on surface '{}': {}", callback_name, surface_name, e ); } } } } pub(crate) fn with_selected(&self, selector: &crate::Selector, mut f: F) where F: FnMut(&str, &ComponentInstance), { let system = self.inner.borrow(); let (primary, active) = self.get_output_handles(); for (key, surface) in system.app_state().surfaces_with_keys() { let surface_name = system .app_state() .get_surface_name(key.surface_handle) .unwrap_or("unknown"); let surface_info = crate::SurfaceInfo { name: surface_name.to_string(), output: key.output_handle, }; let output_info = system.app_state().get_output_info(key.output_handle); if selector.matches(&surface_info, output_info, primary, active) { f(surface_name, surface.component_instance()); } } } pub(crate) fn configure_selected(&self, selector: &crate::Selector, mut f: F) where F: FnMut(&ComponentInstance, LayerSurfaceHandle<'_>), { let system = self.inner.borrow(); let (primary, active) = self.get_output_handles(); for (key, surface) in system.app_state().surfaces_with_keys() { let surface_name = system .app_state() .get_surface_name(key.surface_handle) .unwrap_or("unknown"); let surface_info = crate::SurfaceInfo { name: surface_name.to_string(), output: key.output_handle, }; let output_info = system.app_state().get_output_info(key.output_handle); if selector.matches(&surface_info, output_info, primary, active) { let surface_handle = LayerSurfaceHandle::from_window_state(surface); f(surface.component_instance(), surface_handle); } } } pub(crate) fn count_selected(&self, selector: &crate::Selector) -> usize { let system = self.inner.borrow(); let (primary, active) = self.get_output_handles(); system .app_state() .surfaces_with_keys() .filter(|(key, _)| { let surface_name = system .app_state() .get_surface_name(key.surface_handle) .unwrap_or("unknown"); let surface_info = crate::SurfaceInfo { name: surface_name.to_string(), output: key.output_handle, }; let output_info = system.app_state().get_output_info(key.output_handle); selector.matches(&surface_info, output_info, primary, active) }) .count() } pub(crate) fn get_selected_info(&self, selector: &crate::Selector) -> Vec { let system = self.inner.borrow(); let (primary, active) = self.get_output_handles(); system .app_state() .surfaces_with_keys() .filter_map(|(key, _)| { let surface_name = system .app_state() .get_surface_name(key.surface_handle) .unwrap_or("unknown"); let surface_info = crate::SurfaceInfo { name: surface_name.to_string(), output: key.output_handle, }; let output_info = system.app_state().get_output_info(key.output_handle); if selector.matches(&surface_info, output_info, primary, active) { Some(surface_info) } else { None } }) .collect() } } impl ShellRuntime for Shell { type LoopHandle = EventLoopHandle; type Context<'a> = ShellEventContext<'a>; fn event_loop_handle(&self) -> Self::LoopHandle { EventLoopHandle::new(Rc::downgrade(&self.inner)) } fn with_component(&self, name: &str, mut f: F) where F: FnMut(&ComponentInstance), { let system = self.inner.borrow(); if self.registry.contains_name(name) { for surface in system.app_state().surfaces_by_name(name) { f(surface.component_instance()); } } } fn with_all_components(&self, mut f: F) where F: FnMut(&str, &ComponentInstance), { let system = self.inner.borrow(); for name in self.registry.surface_names() { for surface in system.app_state().surfaces_by_name(name) { f(name, surface.component_instance()); } } } fn run(&mut self) -> Result<()> { self.inner.borrow_mut().run()?; Ok(()) } } /// Context providing access to shell state within custom event source callbacks /// /// Obtained via event source callbacks registered through `EventLoopHandle`. pub struct ShellEventContext<'a> { app_state: &'a mut AppState, } impl<'a> FromAppState<'a> for ShellEventContext<'a> { fn from_app_state(app_state: &'a mut AppState) -> Self { Self { app_state } } } impl ShellEventContext<'_> { /// Returns the component instance for a surface by name pub fn get_surface_component(&self, name: &str) -> Option<&ComponentInstance> { self.app_state .surfaces_by_name(name) .first() .map(|s| s.component_instance()) } /// Returns all surface component instances pub fn all_surface_components(&self) -> impl Iterator { self.app_state .all_outputs() .map(SurfaceState::component_instance) } /// Renders a new frame for all dirty surfaces pub fn render_frame_if_dirty(&mut self) -> Result<()> { for surface in self.app_state.all_outputs() { surface.render_frame_if_dirty()?; } Ok(()) } /// Returns the primary output handle #[must_use] pub fn primary_output_handle(&self) -> Option { self.app_state.primary_output_handle() } /// Returns the active output handle #[must_use] pub fn active_output_handle(&self) -> Option { self.app_state.active_output_handle() } /// Returns the output registry pub fn output_registry(&self) -> &OutputRegistry { self.app_state.output_registry() } /// Returns all outputs with their handles and component instances pub fn outputs(&self) -> impl Iterator { self.app_state .outputs_with_handles() .map(|(handle, surface)| (handle, surface.component_instance())) } /// Returns the component instance for a specific output pub fn get_output_component(&self, handle: OutputHandle) -> Option<&ComponentInstance> { self.app_state .get_output_by_handle(handle) .map(SurfaceState::component_instance) } /// Returns information about a specific output pub fn get_output_info(&self, handle: OutputHandle) -> Option<&OutputInfo> { self.app_state.get_output_info(handle) } /// Returns information about all outputs pub fn all_output_info(&self) -> impl Iterator { self.app_state.all_output_info() } /// Returns all outputs with their info and component instances pub fn outputs_with_info(&self) -> impl Iterator { self.app_state .outputs_with_info() .map(|(info, surface)| (info, surface.component_instance())) } /// Returns the compilation result if available #[must_use] pub fn compilation_result(&self) -> Option> { self.app_state .primary_output() .and_then(SurfaceState::compilation_result) } }