layer_shika_composition/
shell.rs

1use crate::event_loop::{EventLoopHandle, FromAppState};
2use crate::layer_surface::LayerSurfaceHandle;
3use crate::session_lock::{SessionLock, SessionLockBuilder};
4use crate::shell_config::{CompiledUiSource, ShellConfig};
5use crate::shell_runtime::ShellRuntime;
6use crate::surface_registry::{SurfaceDefinition, SurfaceEntry, SurfaceRegistry};
7use crate::system::{
8    CallbackContext, EventDispatchContext, PopupCommand, SessionLockCommand, ShellCommand,
9    ShellControl, SurfaceCommand, SurfaceTarget,
10};
11use crate::value_conversion::IntoValue;
12use crate::{Error, Result};
13use layer_shika_adapters::errors::EventLoopError;
14use layer_shika_adapters::platform::calloop::channel;
15use layer_shika_adapters::platform::slint_interpreter::{
16    CompilationResult, Compiler, ComponentInstance, Value,
17};
18use layer_shika_adapters::{
19    AppState, ShellSurfaceConfig, SurfaceState, WaylandSurfaceConfig, WaylandSystemOps,
20};
21use layer_shika_domain::config::SurfaceConfig;
22use layer_shika_domain::entities::output_registry::OutputRegistry;
23use layer_shika_domain::errors::DomainError;
24use layer_shika_domain::prelude::{
25    AnchorEdges, KeyboardInteractivity, Layer, Margins, OutputPolicy, ScaleFactor, SurfaceDimension,
26};
27use layer_shika_domain::value_objects::handle::SurfaceHandle;
28use layer_shika_domain::value_objects::output_handle::OutputHandle;
29use layer_shika_domain::value_objects::output_info::OutputInfo;
30use layer_shika_domain::value_objects::surface_instance_id::SurfaceInstanceId;
31use spin_on::spin_on;
32use std::cell::RefCell;
33use std::path::{Path, PathBuf};
34use std::rc::{Rc, Weak};
35
36/// Default Slint component name used when none is specified
37pub const DEFAULT_COMPONENT_NAME: &str = "Main";
38
39enum CompilationSource {
40    File { path: PathBuf, compiler: Compiler },
41    Source { code: String, compiler: Compiler },
42    Compiled(Rc<CompilationResult>),
43}
44
45/// Builder for configuring and creating a Shell with one or more surfaces
46///
47/// Chain `.surface()` calls to configure multiple surfaces, then call `.build()` or `.run()`.
48/// If no surfaces are configured, a default "Main" surface is created.
49pub struct ShellBuilder {
50    compilation: CompilationSource,
51    surfaces: Vec<SurfaceDefinition>,
52}
53
54impl ShellBuilder {
55    /// Starts configuration for a new surface with the given component name
56    pub fn surface(self, component: impl Into<String>) -> SurfaceConfigBuilder {
57        SurfaceConfigBuilder {
58            shell_builder: self,
59            component: component.into(),
60            config: SurfaceConfig::default(),
61        }
62    }
63
64    /// Discovers and registers multiple surfaces by component names
65    #[must_use]
66    pub fn discover_surfaces(
67        mut self,
68        components: impl IntoIterator<Item = impl Into<String>>,
69    ) -> Self {
70        for component in components {
71            self.surfaces.push(SurfaceDefinition {
72                component: component.into(),
73                config: SurfaceConfig::default(),
74            });
75        }
76        self
77    }
78
79    /// Builds the shell from the configured surfaces
80    ///
81    /// If no surfaces are configured, creates a minimal shell without layer surfaces.
82    /// This is useful for lock-only applications that don't need persistent UI surfaces.
83    pub fn build(self) -> Result<Shell> {
84        let surfaces = self.surfaces;
85
86        let compilation_result = match self.compilation {
87            CompilationSource::File { path, compiler } => {
88                let result = spin_on(compiler.build_from_path(&path));
89                let diagnostics: Vec<_> = result.diagnostics().collect();
90                if !diagnostics.is_empty() {
91                    let messages: Vec<String> =
92                        diagnostics.iter().map(ToString::to_string).collect();
93                    return Err(DomainError::Configuration {
94                        message: format!(
95                            "Failed to compile Slint file '{}':\n{}",
96                            path.display(),
97                            messages.join("\n")
98                        ),
99                    }
100                    .into());
101                }
102                Rc::new(result)
103            }
104            CompilationSource::Source { code, compiler } => {
105                let result = spin_on(compiler.build_from_source(code, PathBuf::default()));
106                let diagnostics: Vec<_> = result.diagnostics().collect();
107                if !diagnostics.is_empty() {
108                    let messages: Vec<String> =
109                        diagnostics.iter().map(ToString::to_string).collect();
110                    return Err(DomainError::Configuration {
111                        message: format!(
112                            "Failed to compile Slint source:\n{}",
113                            messages.join("\n")
114                        ),
115                    }
116                    .into());
117                }
118                Rc::new(result)
119            }
120            CompilationSource::Compiled(result) => result,
121        };
122
123        Shell::new(compilation_result, surfaces)
124    }
125}
126
127/// Builder for configuring a single surface within a Shell
128///
129/// Chain configuration methods, then either start a new surface with `.surface()`
130/// or finalize with `.build()` or `.run()`.
131pub struct SurfaceConfigBuilder {
132    shell_builder: ShellBuilder,
133    component: String,
134    config: SurfaceConfig,
135}
136
137impl SurfaceConfigBuilder {
138    /// Sets both width and height for the surface
139    #[must_use]
140    pub fn size(mut self, width: u32, height: u32) -> Self {
141        self.config.dimensions = SurfaceDimension::new(width, height);
142        self
143    }
144
145    /// Sets the height of the surface
146    #[must_use]
147    pub fn height(mut self, height: u32) -> Self {
148        self.config.dimensions = SurfaceDimension::new(self.config.dimensions.width(), height);
149        self
150    }
151
152    /// Sets the width of the surface
153    #[must_use]
154    pub fn width(mut self, width: u32) -> Self {
155        self.config.dimensions = SurfaceDimension::new(width, self.config.dimensions.height());
156        self
157    }
158
159    /// Sets the layer (stacking order) for the surface
160    #[must_use]
161    pub const fn layer(mut self, layer: Layer) -> Self {
162        self.config.layer = layer;
163        self
164    }
165
166    /// Sets the margins around the surface
167    #[must_use]
168    pub fn margin(mut self, margin: impl Into<Margins>) -> Self {
169        self.config.margin = margin.into();
170        self
171    }
172
173    /// Sets the anchor edges for positioning
174    #[must_use]
175    pub const fn anchor(mut self, anchor: AnchorEdges) -> Self {
176        self.config.anchor = anchor;
177        self
178    }
179
180    /// Sets the exclusive zone in pixels
181    ///
182    /// Reserves screen space that other windows avoid. Positive values reserve from anchored edge,
183    /// `0` means no reservation, `-1` lets compositor decide.
184    #[must_use]
185    pub const fn exclusive_zone(mut self, zone: i32) -> Self {
186        self.config.exclusive_zone = zone;
187        self
188    }
189
190    /// Sets the namespace identifier for the surface
191    #[must_use]
192    pub fn namespace(mut self, namespace: impl Into<String>) -> Self {
193        self.config.namespace = namespace.into();
194        self
195    }
196
197    /// Sets the scale factor for the surface
198    #[must_use]
199    pub fn scale_factor(mut self, sf: impl TryInto<ScaleFactor, Error = DomainError>) -> Self {
200        self.config.scale_factor = sf.try_into().unwrap_or_default();
201        self
202    }
203
204    /// Sets the keyboard interactivity mode
205    #[must_use]
206    pub const fn keyboard_interactivity(mut self, mode: KeyboardInteractivity) -> Self {
207        self.config.keyboard_interactivity = mode;
208        self
209    }
210
211    /// Sets the output policy for multi-monitor configuration
212    ///
213    /// Controls which monitors display this surface. Default is `OutputPolicy::All`.
214    #[must_use]
215    pub fn output_policy(mut self, policy: OutputPolicy) -> Self {
216        self.config.output_policy = policy;
217        self
218    }
219
220    /// Starts configuration for another surface
221    #[must_use]
222    pub fn surface(self, component: impl Into<String>) -> SurfaceConfigBuilder {
223        let shell_builder = self.complete();
224        shell_builder.surface(component)
225    }
226
227    /// Builds the shell with all configured surfaces
228    pub fn build(self) -> Result<Shell> {
229        self.complete().build()
230    }
231
232    /// Builds and runs the shell
233    pub fn run(self) -> Result<()> {
234        let mut shell = self.build()?;
235        shell.run()
236    }
237
238    fn complete(mut self) -> ShellBuilder {
239        self.shell_builder.surfaces.push(SurfaceDefinition {
240            component: self.component,
241            config: self.config,
242        });
243        self.shell_builder
244    }
245}
246
247type OutputConnectedHandler = Box<dyn Fn(&OutputInfo)>;
248type OutputDisconnectedHandler = Box<dyn Fn(OutputHandle)>;
249
250/// Main runtime for managing Wayland layer-shell surfaces with Slint UI
251///
252/// Manages surface lifecycle, event loop integration, and component instantiation.
253/// Supports multiple surfaces across monitors, dynamic spawning, and popup windows.
254///
255/// Create via `Shell::from_file()`, `from_source()`, or `from_compilation()`.
256pub struct Shell {
257    inner: Rc<RefCell<dyn WaylandSystemOps>>,
258    registry: SurfaceRegistry,
259    compilation_result: Rc<CompilationResult>,
260    command_sender: channel::Sender<ShellCommand>,
261    output_connected_handlers: Rc<RefCell<Vec<OutputConnectedHandler>>>,
262    output_disconnected_handlers: Rc<RefCell<Vec<OutputDisconnectedHandler>>>,
263}
264
265impl Shell {
266    /// Creates a shell builder from a Slint file path
267    pub fn from_file(path: impl AsRef<Path>) -> ShellBuilder {
268        ShellBuilder {
269            compilation: CompilationSource::File {
270                path: path.as_ref().to_path_buf(),
271                compiler: Compiler::default(),
272            },
273            surfaces: Vec::new(),
274        }
275    }
276
277    /// Creates a shell builder from a Slint file path with a custom compiler
278    ///
279    /// Useful for configuring include paths, style overrides, or compilation settings.
280    pub fn from_file_with_compiler(path: impl AsRef<Path>, compiler: Compiler) -> ShellBuilder {
281        ShellBuilder {
282            compilation: CompilationSource::File {
283                path: path.as_ref().to_path_buf(),
284                compiler,
285            },
286            surfaces: Vec::new(),
287        }
288    }
289
290    /// Creates a shell builder from Slint source code
291    pub fn from_source(code: impl Into<String>) -> ShellBuilder {
292        ShellBuilder {
293            compilation: CompilationSource::Source {
294                code: code.into(),
295                compiler: Compiler::default(),
296            },
297            surfaces: Vec::new(),
298        }
299    }
300
301    /// Creates a shell builder from Slint source code with a custom compiler
302    pub fn from_source_with_compiler(code: impl Into<String>, compiler: Compiler) -> ShellBuilder {
303        ShellBuilder {
304            compilation: CompilationSource::Source {
305                code: code.into(),
306                compiler,
307            },
308            surfaces: Vec::new(),
309        }
310    }
311
312    /// Creates a shell builder from a pre-compiled Slint compilation result
313    pub fn from_compilation(result: Rc<CompilationResult>) -> ShellBuilder {
314        ShellBuilder {
315            compilation: CompilationSource::Compiled(result),
316            surfaces: Vec::new(),
317        }
318    }
319
320    /// Creates an empty shell builder for manual configuration
321    pub fn builder() -> ShellBuilder {
322        ShellBuilder {
323            compilation: CompilationSource::Source {
324                code: String::new(),
325                compiler: Compiler::default(),
326            },
327            surfaces: Vec::new(),
328        }
329    }
330
331    /// Compiles a Slint file and returns the compilation result
332    pub fn compile_file(path: impl AsRef<Path>) -> Result<Rc<CompilationResult>> {
333        let compiler = Compiler::default();
334        let result = spin_on(compiler.build_from_path(path.as_ref()));
335        let diagnostics: Vec<_> = result.diagnostics().collect();
336        if !diagnostics.is_empty() {
337            let messages: Vec<String> = diagnostics.iter().map(ToString::to_string).collect();
338            return Err(DomainError::Configuration {
339                message: format!(
340                    "Failed to compile Slint file '{}':\n{}",
341                    path.as_ref().display(),
342                    messages.join("\n")
343                ),
344            }
345            .into());
346        }
347        Ok(Rc::new(result))
348    }
349
350    /// Compiles Slint source code and returns the compilation result
351    pub fn compile_source(code: impl Into<String>) -> Result<Rc<CompilationResult>> {
352        let compiler = Compiler::default();
353        let result = spin_on(compiler.build_from_source(code.into(), PathBuf::default()));
354        let diagnostics: Vec<_> = result.diagnostics().collect();
355        if !diagnostics.is_empty() {
356            let messages: Vec<String> = diagnostics.iter().map(ToString::to_string).collect();
357            return Err(DomainError::Configuration {
358                message: format!("Failed to compile Slint source:\n{}", messages.join("\n")),
359            }
360            .into());
361        }
362        Ok(Rc::new(result))
363    }
364
365    /// Creates a shell from a complete configuration object
366    pub fn from_config(config: ShellConfig) -> Result<Self> {
367        let compilation_result = match config.ui_source {
368            CompiledUiSource::File(path) => Self::compile_file(&path)?,
369            CompiledUiSource::Source(code) => Self::compile_source(code)?,
370            CompiledUiSource::Compiled(result) => result,
371        };
372
373        let surfaces: Vec<SurfaceDefinition> = if config.surfaces.is_empty() {
374            vec![SurfaceDefinition {
375                component: DEFAULT_COMPONENT_NAME.to_string(),
376                config: SurfaceConfig::default(),
377            }]
378        } else {
379            config
380                .surfaces
381                .into_iter()
382                .map(|s| SurfaceDefinition {
383                    component: s.component,
384                    config: s.config,
385                })
386                .collect()
387        };
388
389        Self::new(compilation_result, surfaces)
390    }
391
392    pub(crate) fn new(
393        compilation_result: Rc<CompilationResult>,
394        definitions: Vec<SurfaceDefinition>,
395    ) -> Result<Self> {
396        log::info!("Creating Shell with {} windows", definitions.len());
397
398        for def in &definitions {
399            def.config.validate().map_err(Error::Domain)?;
400        }
401
402        match definitions.len() {
403            0 => {
404                log::info!("Creating minimal shell without layer surfaces");
405                Self::new_minimal(compilation_result)
406            }
407            1 => {
408                let definition = definitions.into_iter().next().ok_or_else(|| {
409                    Error::Domain(DomainError::Configuration {
410                        message: "Expected at least one window definition".to_string(),
411                    })
412                })?;
413                Self::new_single_window(compilation_result, definition)
414            }
415            _ => Self::new_multi_window(compilation_result, &definitions),
416        }
417    }
418
419    fn new_single_window(
420        compilation_result: Rc<CompilationResult>,
421        definition: SurfaceDefinition,
422    ) -> Result<Self> {
423        let component_definition = compilation_result
424            .component(&definition.component)
425            .ok_or_else(|| {
426                Error::Domain(DomainError::ComponentNotFound {
427                    name: definition.component.clone(),
428                })
429            })?;
430
431        let handle = SurfaceHandle::new();
432        let wayland_config = WaylandSurfaceConfig::from_domain_config(
433            handle,
434            &definition.component,
435            component_definition,
436            Some(Rc::clone(&compilation_result)),
437            definition.config.clone(),
438        );
439
440        let inner = layer_shika_adapters::WaylandShellSystem::new(&wayland_config)?;
441        let inner_rc: Rc<RefCell<dyn WaylandSystemOps>> = Rc::new(RefCell::new(inner));
442
443        let (sender, receiver) = channel::channel();
444
445        let mut registry = SurfaceRegistry::new();
446        let entry = SurfaceEntry::new(handle, definition.component.clone(), definition);
447        registry.insert(entry)?;
448
449        let shell = Self {
450            inner: Rc::clone(&inner_rc),
451            registry,
452            compilation_result,
453            command_sender: sender,
454            output_connected_handlers: Rc::new(RefCell::new(Vec::new())),
455            output_disconnected_handlers: Rc::new(RefCell::new(Vec::new())),
456        };
457
458        shell.setup_command_handler(receiver)?;
459
460        log::info!("Shell created (single-window mode)");
461
462        Ok(shell)
463    }
464
465    fn new_multi_window(
466        compilation_result: Rc<CompilationResult>,
467        definitions: &[SurfaceDefinition],
468    ) -> Result<Self> {
469        let shell_configs_with_handles: Vec<(SurfaceHandle, ShellSurfaceConfig)> = definitions
470            .iter()
471            .map(|def| {
472                let component_definition = compilation_result
473                    .component(&def.component)
474                    .ok_or_else(|| {
475                        Error::Domain(DomainError::ComponentNotFound {
476                            name: def.component.clone(),
477                        })
478                    })?;
479
480                let handle = SurfaceHandle::new();
481                let wayland_config = WaylandSurfaceConfig::from_domain_config(
482                    handle,
483                    &def.component,
484                    component_definition,
485                    Some(Rc::clone(&compilation_result)),
486                    def.config.clone(),
487                );
488
489                Ok((
490                    handle,
491                    ShellSurfaceConfig {
492                        name: def.component.clone(),
493                        config: wayland_config,
494                    },
495                ))
496            })
497            .collect::<Result<Vec<_>>>()?;
498
499        let shell_configs: Vec<ShellSurfaceConfig> = shell_configs_with_handles
500            .iter()
501            .map(|(_, cfg)| cfg.clone())
502            .collect();
503
504        let inner = layer_shika_adapters::WaylandShellSystem::new_multi(&shell_configs)?;
505        let inner_rc: Rc<RefCell<dyn WaylandSystemOps>> = Rc::new(RefCell::new(inner));
506
507        let (sender, receiver) = channel::channel();
508
509        let mut registry = SurfaceRegistry::new();
510        for ((handle, _), definition) in shell_configs_with_handles.iter().zip(definitions.iter()) {
511            let entry =
512                SurfaceEntry::new(*handle, definition.component.clone(), definition.clone());
513            registry.insert(entry)?;
514        }
515
516        let shell = Self {
517            inner: Rc::clone(&inner_rc),
518            registry,
519            compilation_result,
520            command_sender: sender,
521            output_connected_handlers: Rc::new(RefCell::new(Vec::new())),
522            output_disconnected_handlers: Rc::new(RefCell::new(Vec::new())),
523        };
524
525        shell.setup_command_handler(receiver)?;
526
527        log::info!(
528            "Shell created (multi-surface mode) with surfaces: {:?}",
529            shell.surface_names()
530        );
531
532        Ok(shell)
533    }
534
535    fn new_minimal(compilation_result: Rc<CompilationResult>) -> Result<Self> {
536        let inner = layer_shika_adapters::WaylandShellSystem::new_minimal()?;
537        let inner_rc: Rc<RefCell<dyn WaylandSystemOps>> = Rc::new(RefCell::new(inner));
538
539        inner_rc
540            .borrow_mut()
541            .set_compilation_result(Rc::clone(&compilation_result));
542
543        let (sender, receiver) = channel::channel();
544
545        let registry = SurfaceRegistry::new();
546
547        let shell = Self {
548            inner: Rc::clone(&inner_rc),
549            registry,
550            compilation_result,
551            command_sender: sender,
552            output_connected_handlers: Rc::new(RefCell::new(Vec::new())),
553            output_disconnected_handlers: Rc::new(RefCell::new(Vec::new())),
554        };
555
556        shell.setup_command_handler(receiver)?;
557
558        log::info!("Shell created (minimal mode - no layer surfaces)");
559
560        Ok(shell)
561    }
562
563    fn setup_command_handler(&self, receiver: channel::Channel<ShellCommand>) -> Result<()> {
564        let loop_handle = self.inner.borrow().event_loop_handle();
565        let control = self.control();
566        let system = Rc::downgrade(&self.inner);
567
568        loop_handle
569            .insert_source(receiver, move |event, (), app_state| {
570                if let channel::Event::Msg(command) = event {
571                    let mut ctx = crate::system::EventDispatchContext::from_app_state(app_state);
572
573                    match command {
574                        ShellCommand::Popup(popup_cmd) => {
575                            Self::handle_popup_command(popup_cmd, &mut ctx, &control);
576                        }
577                        ShellCommand::Surface(surface_cmd) => {
578                            Self::handle_surface_command(surface_cmd, &mut ctx);
579                        }
580                        ShellCommand::SessionLock(lock_cmd) => {
581                            Self::handle_session_lock_command(&lock_cmd, &mut ctx, &system);
582                        }
583                        ShellCommand::Render => {
584                            if let Err(e) = ctx.render_frame_if_dirty() {
585                                log::error!("Failed to render frame: {}", e);
586                            }
587                        }
588                    }
589                }
590            })
591            .map_err(|e| {
592                Error::Adapter(
593                    EventLoopError::InsertSource {
594                        message: format!("Failed to setup command handler: {e:?}"),
595                    }
596                    .into(),
597                )
598            })?;
599
600        Ok(())
601    }
602
603    fn handle_popup_command(
604        command: PopupCommand,
605        ctx: &mut EventDispatchContext<'_>,
606        _control: &ShellControl,
607    ) {
608        match command {
609            PopupCommand::Show { handle, config } => {
610                if let Err(e) = ctx.show_popup(handle, &config) {
611                    log::error!("Failed to show popup: {}", e);
612                }
613            }
614            PopupCommand::Close(handle) => {
615                if let Err(e) = ctx.close_popup(handle) {
616                    log::error!("Failed to close popup: {}", e);
617                }
618            }
619            PopupCommand::Resize {
620                handle,
621                width,
622                height,
623            } => {
624                if let Err(e) = ctx.resize_popup(handle, width, height) {
625                    log::error!("Failed to resize popup: {}", e);
626                }
627            }
628        }
629    }
630
631    fn handle_session_lock_command(
632        command: &SessionLockCommand,
633        ctx: &mut EventDispatchContext<'_>,
634        _system: &Weak<RefCell<dyn WaylandSystemOps>>,
635    ) {
636        match command {
637            SessionLockCommand::Activate {
638                component_name,
639                config,
640            } => {
641                log::info!("Processing SessionLockCommand::Activate");
642                if let Err(e) = ctx.activate_session_lock(component_name, config.clone()) {
643                    log::error!("Failed to activate session lock: {}", e);
644                } else {
645                    log::info!("Session lock activated successfully");
646                }
647            }
648            SessionLockCommand::Deactivate => {
649                log::info!("Processing SessionLockCommand::Deactivate");
650                if let Err(e) = ctx.deactivate_session_lock() {
651                    log::error!("Failed to deactivate session lock: {}", e);
652                } else {
653                    log::info!("Session lock deactivated successfully");
654                }
655            }
656        }
657    }
658
659    fn resolve_surface_target<'a>(
660        ctx: &'a mut EventDispatchContext<'_>,
661        target: &SurfaceTarget,
662    ) -> Vec<&'a mut SurfaceState> {
663        match target {
664            SurfaceTarget::ByInstance(id) => {
665                if let Some(surface) = ctx.surface_by_instance_mut(id.surface(), id.output()) {
666                    vec![surface]
667                } else {
668                    log::warn!(
669                        "Surface instance not found: handle {:?} on output {:?}",
670                        id.surface(),
671                        id.output()
672                    );
673                    vec![]
674                }
675            }
676            SurfaceTarget::ByHandle(handle) => ctx.surfaces_by_handle_mut(*handle),
677            SurfaceTarget::ByName(name) => ctx.surfaces_by_name_mut(name),
678            SurfaceTarget::ByNameAndOutput { name, output } => {
679                ctx.surfaces_by_name_and_output_mut(name, *output)
680            }
681        }
682    }
683
684    fn apply_surface_resize(
685        ctx: &mut EventDispatchContext<'_>,
686        target: &SurfaceTarget,
687        width: u32,
688        height: u32,
689    ) {
690        log::debug!(
691            "Surface command: Resize {:?} to {}x{}",
692            target,
693            width,
694            height
695        );
696        for surface in Self::resolve_surface_target(ctx, target) {
697            let handle = LayerSurfaceHandle::from_window_state(surface);
698            handle.set_size(width, height);
699            handle.commit();
700            surface.update_size_with_compositor_logic(width, height);
701        }
702    }
703
704    fn apply_surface_config_change<F>(
705        ctx: &mut EventDispatchContext<'_>,
706        target: &SurfaceTarget,
707        operation: &str,
708        apply: F,
709    ) where
710        F: Fn(&LayerSurfaceHandle<'_>),
711    {
712        log::debug!("Surface command: {} {:?}", operation, target);
713        for surface in Self::resolve_surface_target(ctx, target) {
714            let handle = LayerSurfaceHandle::from_window_state(surface);
715            apply(&handle);
716            handle.commit();
717        }
718    }
719
720    fn apply_full_config(
721        ctx: &mut EventDispatchContext<'_>,
722        target: &SurfaceTarget,
723        config: &SurfaceConfig,
724    ) {
725        log::debug!("Surface command: ApplyConfig {:?}", target);
726
727        if let Err(e) = config.validate() {
728            log::error!("Invalid surface configuration: {}", e);
729            return;
730        }
731
732        for surface in Self::resolve_surface_target(ctx, target) {
733            let handle = LayerSurfaceHandle::from_window_state(surface);
734
735            handle.set_size(config.dimensions.width(), config.dimensions.height());
736            handle.set_anchor_edges(config.anchor);
737            handle.set_exclusive_zone(config.exclusive_zone);
738            handle.set_margins(config.margin);
739            handle.set_layer(config.layer);
740            handle.set_keyboard_interactivity(config.keyboard_interactivity);
741            handle.commit();
742
743            surface.update_size_with_compositor_logic(
744                config.dimensions.width(),
745                config.dimensions.height(),
746            );
747        }
748    }
749
750    fn handle_surface_command(command: SurfaceCommand, ctx: &mut EventDispatchContext<'_>) {
751        match command {
752            SurfaceCommand::Resize {
753                target,
754                width,
755                height,
756            } => {
757                Self::apply_surface_resize(ctx, &target, width, height);
758            }
759            SurfaceCommand::SetAnchor { target, anchor } => {
760                Self::apply_surface_config_change(ctx, &target, "SetAnchor", |handle| {
761                    handle.set_anchor_edges(anchor);
762                });
763            }
764            SurfaceCommand::SetExclusiveZone { target, zone } => {
765                Self::apply_surface_config_change(ctx, &target, "SetExclusiveZone", |handle| {
766                    handle.set_exclusive_zone(zone);
767                });
768            }
769            SurfaceCommand::SetMargins { target, margins } => {
770                Self::apply_surface_config_change(ctx, &target, "SetMargins", |handle| {
771                    handle.set_margins(margins);
772                });
773            }
774            SurfaceCommand::SetLayer { target, layer } => {
775                Self::apply_surface_config_change(ctx, &target, "SetLayer", |handle| {
776                    handle.set_layer(layer);
777                });
778            }
779            SurfaceCommand::SetKeyboardInteractivity { target, mode } => {
780                Self::apply_surface_config_change(
781                    ctx,
782                    &target,
783                    "SetKeyboardInteractivity",
784                    |handle| {
785                        handle.set_keyboard_interactivity(mode);
786                    },
787                );
788            }
789            SurfaceCommand::SetOutputPolicy { target, policy } => {
790                log::debug!(
791                    "Surface command: SetOutputPolicy {:?} to {:?}",
792                    target,
793                    policy
794                );
795                log::warn!(
796                    "SetOutputPolicy is not yet implemented - requires runtime surface spawning"
797                );
798            }
799            SurfaceCommand::SetScaleFactor { target, factor } => {
800                log::debug!(
801                    "Surface command: SetScaleFactor {:?} to {:?}",
802                    target,
803                    factor
804                );
805                log::warn!(
806                    "SetScaleFactor is not yet implemented - requires runtime surface property updates"
807                );
808            }
809            SurfaceCommand::ApplyConfig { target, config } => {
810                Self::apply_full_config(ctx, &target, &config);
811            }
812        }
813
814        if let Err(e) = ctx.render_frame_if_dirty() {
815            log::error!("Failed to render frame after surface command: {}", e);
816        }
817    }
818
819    /// Returns a control handle for sending commands to the shell
820    #[must_use]
821    pub fn control(&self) -> ShellControl {
822        ShellControl::new(self.command_sender.clone())
823    }
824
825    /// Access popup management API
826    #[must_use]
827    pub fn popups(&self) -> crate::PopupShell {
828        self.control().popups()
829    }
830
831    pub fn create_session_lock(&self, component: impl Into<String>) -> Result<SessionLock> {
832        self.create_session_lock_with_config(SessionLockBuilder::new(component))
833    }
834
835    pub fn create_session_lock_with_config(
836        &self,
837        builder: SessionLockBuilder,
838    ) -> Result<SessionLock> {
839        let component = builder.component_name().to_string();
840        if self.compilation_result.component(&component).is_none() {
841            return Err(Error::Domain(DomainError::ComponentNotFound {
842                name: component,
843            }));
844        }
845
846        if !self.inner.borrow().is_session_lock_available() {
847            return Err(Error::ProtocolNotAvailable {
848                protocol: "ext-session-lock-v1".to_string(),
849            });
850        }
851
852        Ok(builder.build(Rc::downgrade(&self.inner), self.command_sender.clone()))
853    }
854
855    /// Returns the names of all registered surfaces
856    pub fn surface_names(&self) -> Vec<&str> {
857        self.registry.surface_names()
858    }
859
860    /// Checks if a surface with the given name exists
861    pub fn has_surface(&self, name: &str) -> bool {
862        self.registry.contains_name(name)
863    }
864
865    /// Returns a handle to the event loop for registering custom event sources
866    pub fn event_loop_handle(&self) -> EventLoopHandle {
867        EventLoopHandle::new(Rc::downgrade(&self.inner))
868    }
869
870    /// Starts the event loop and runs the shell until exit
871    pub fn run(&mut self) -> Result<()> {
872        log::info!(
873            "Starting Shell event loop with {} windows",
874            self.registry.len()
875        );
876        self.inner.borrow_mut().run()?;
877        Ok(())
878    }
879
880    /// Spawns a new surface at runtime from the given definition
881    ///
882    /// The surface is instantiated on outputs according to its `OutputPolicy`.
883    pub fn spawn_surface(&mut self, definition: SurfaceDefinition) -> Result<Vec<SurfaceHandle>> {
884        let component_definition = self
885            .compilation_result
886            .component(&definition.component)
887            .ok_or_else(|| {
888                Error::Domain(DomainError::ComponentNotFound {
889                    name: definition.component.clone(),
890                })
891            })?;
892
893        let handle = SurfaceHandle::new();
894        let wayland_config = WaylandSurfaceConfig::from_domain_config(
895            handle,
896            &definition.component,
897            component_definition,
898            Some(Rc::clone(&self.compilation_result)),
899            definition.config.clone(),
900        );
901
902        let shell_config = ShellSurfaceConfig {
903            name: definition.component.clone(),
904            config: wayland_config,
905        };
906
907        let mut system = self.inner.borrow_mut();
908        let handles = system.spawn_surface(&shell_config)?;
909
910        let surface_handle = SurfaceHandle::new();
911        let entry = SurfaceEntry::new(surface_handle, definition.component.clone(), definition);
912        self.registry.insert(entry)?;
913
914        log::info!(
915            "Spawned surface with handle {:?}, created {} output instances",
916            surface_handle,
917            handles.len()
918        );
919
920        Ok(vec![surface_handle])
921    }
922
923    /// Removes and destroys a surface by its handle
924    pub fn despawn_surface(&mut self, handle: SurfaceHandle) -> Result<()> {
925        let entry = self.registry.remove(handle).ok_or_else(|| {
926            Error::Domain(DomainError::SurfaceNotFound {
927                message: format!("Surface handle {:?} not found", handle),
928            })
929        })?;
930
931        let mut system = self.inner.borrow_mut();
932        system.despawn_surface(&entry.name)?;
933
934        log::info!(
935            "Despawned surface '{}' with handle {:?}",
936            entry.name,
937            handle
938        );
939
940        Ok(())
941    }
942
943    /// Registers a handler called when a new output (monitor) is connected
944    ///
945    /// Surfaces with `OutputPolicy::All` spawn automatically on new outputs.
946    pub fn on_output_connected<F>(&mut self, handler: F) -> Result<()>
947    where
948        F: Fn(&OutputInfo) + 'static,
949    {
950        self.output_connected_handlers
951            .borrow_mut()
952            .push(Box::new(handler));
953        Ok(())
954    }
955
956    /// Registers a handler called when an output is disconnected
957    pub fn on_output_disconnected<F>(&mut self, handler: F) -> Result<()>
958    where
959        F: Fn(OutputHandle) + 'static,
960    {
961        self.output_disconnected_handlers
962            .borrow_mut()
963            .push(Box::new(handler));
964        Ok(())
965    }
966
967    /// Returns the handle for a surface by name
968    pub fn get_surface_handle(&self, name: &str) -> Option<SurfaceHandle> {
969        self.registry.handle_by_name(name)
970    }
971
972    /// Returns the name of a surface by its handle
973    pub fn get_surface_name(&self, handle: SurfaceHandle) -> Option<&str> {
974        self.registry.name_by_handle(handle)
975    }
976
977    /// Executes a function with access to a surface component instance by name
978    pub fn with_surface<F, R>(&self, name: &str, f: F) -> Result<R>
979    where
980        F: FnOnce(&ComponentInstance) -> R,
981    {
982        if !self.registry.contains_name(name) {
983            return Err(Error::Domain(DomainError::SurfaceNotFound {
984                message: format!("Window '{}' not found", name),
985            }));
986        }
987
988        let system = self.inner.borrow();
989
990        system
991            .app_state()
992            .surfaces_by_name(name)
993            .first()
994            .map(|surface| f(surface.component_instance()))
995            .ok_or_else(|| {
996                Error::Domain(DomainError::SurfaceNotFound {
997                    message: format!("No instance found for window '{}'", name),
998                })
999            })
1000    }
1001
1002    /// Executes a function with each surface name and component instance
1003    pub fn with_all_surfaces<F>(&self, mut f: F)
1004    where
1005        F: FnMut(&str, &ComponentInstance),
1006    {
1007        let system = self.inner.borrow();
1008
1009        for name in self.registry.surface_names() {
1010            for surface in system.app_state().surfaces_by_name(name) {
1011                f(name, surface.component_instance());
1012            }
1013        }
1014    }
1015
1016    /// Executes a function with access to a surface on a specific output
1017    pub fn with_output<F, R>(&self, handle: OutputHandle, f: F) -> Result<R>
1018    where
1019        F: FnOnce(&ComponentInstance) -> R,
1020    {
1021        let system = self.inner.borrow();
1022        let window = system
1023            .app_state()
1024            .get_output_by_handle(handle)
1025            .ok_or_else(|| {
1026                Error::Domain(DomainError::OutputNotFound {
1027                    message: format!("Output with handle {:?} not found", handle),
1028                })
1029            })?;
1030        Ok(f(window.component_instance()))
1031    }
1032
1033    /// Executes a function with each output handle and component instance
1034    pub fn with_all_outputs<F>(&self, mut f: F)
1035    where
1036        F: FnMut(OutputHandle, &ComponentInstance),
1037    {
1038        let system = self.inner.borrow();
1039        for (handle, surface) in system.app_state().outputs_with_handles() {
1040            f(handle, surface.component_instance());
1041        }
1042    }
1043
1044    /// Returns the Slint compilation result used by this shell
1045    #[must_use]
1046    pub fn compilation_result(&self) -> &Rc<CompilationResult> {
1047        &self.compilation_result
1048    }
1049
1050    /// Returns the registry of all connected outputs
1051    pub fn output_registry(&self) -> OutputRegistry {
1052        let system = self.inner.borrow();
1053        system.app_state().output_registry().clone()
1054    }
1055
1056    /// Returns information about a specific output by handle
1057    pub fn get_output_info(&self, handle: OutputHandle) -> Option<OutputInfo> {
1058        let system = self.inner.borrow();
1059        system.app_state().get_output_info(handle).cloned()
1060    }
1061
1062    /// Returns information about all connected outputs
1063    pub fn all_output_info(&self) -> Vec<OutputInfo> {
1064        let system = self.inner.borrow();
1065        system.app_state().all_output_info().cloned().collect()
1066    }
1067
1068    /// Creates a selection for targeting specific surfaces by criteria
1069    pub fn select(&self, selector: impl Into<crate::Selector>) -> crate::Selection<'_> {
1070        crate::Selection::new(self, selector.into())
1071    }
1072
1073    fn get_output_handles(&self) -> (Option<OutputHandle>, Option<OutputHandle>) {
1074        let registry = &self.output_registry();
1075        (registry.primary_handle(), registry.active_handle())
1076    }
1077
1078    pub(crate) fn on_internal<F, R>(
1079        &self,
1080        selector: &crate::Selector,
1081        callback_name: &str,
1082        handler: F,
1083    ) where
1084        F: Fn(CallbackContext) -> R + Clone + 'static,
1085        R: IntoValue,
1086    {
1087        let control = self.control();
1088        let handler = Rc::new(handler);
1089        let system = self.inner.borrow();
1090        let (primary, active) = self.get_output_handles();
1091
1092        for (key, surface) in system.app_state().surfaces_with_keys() {
1093            let surface_handle = key.surface_handle;
1094            let output_handle = key.output_handle;
1095
1096            let surface_name = self.registry.by_handle(surface_handle).map_or_else(
1097                || format!("Unknown-{}", surface_handle.id()),
1098                |entry| entry.name.clone(),
1099            );
1100
1101            let surface_info = crate::SurfaceInfo {
1102                name: surface_name.clone(),
1103                output: output_handle,
1104                instance_id: SurfaceInstanceId::new(surface_handle, output_handle),
1105            };
1106
1107            let output_info = system.app_state().get_output_info(output_handle);
1108
1109            if selector.matches(&surface_info, output_info, primary, active) {
1110                let instance_id = SurfaceInstanceId::new(surface_handle, output_handle);
1111
1112                let handler_rc = Rc::clone(&handler);
1113                let control_clone = control.clone();
1114                let surface_name_clone = surface_name.clone();
1115
1116                if let Err(e) =
1117                    surface
1118                        .component_instance()
1119                        .set_callback(callback_name, move |_args| {
1120                            let ctx = CallbackContext::new(
1121                                instance_id,
1122                                surface_name_clone.clone(),
1123                                control_clone.clone(),
1124                            );
1125                            handler_rc(ctx).into_value()
1126                        })
1127                {
1128                    log::error!(
1129                        "Failed to register callback '{}' on surface '{}': {}",
1130                        callback_name,
1131                        surface_name,
1132                        e
1133                    );
1134                }
1135            }
1136        }
1137    }
1138
1139    pub(crate) fn on_with_args_internal<F, R>(
1140        &self,
1141        selector: &crate::Selector,
1142        callback_name: &str,
1143        handler: F,
1144    ) where
1145        F: Fn(&[Value], CallbackContext) -> R + Clone + 'static,
1146        R: IntoValue,
1147    {
1148        let control = self.control();
1149        let handler = Rc::new(handler);
1150        let system = self.inner.borrow();
1151        let (primary, active) = self.get_output_handles();
1152
1153        for (key, surface) in system.app_state().surfaces_with_keys() {
1154            let surface_handle = key.surface_handle;
1155            let output_handle = key.output_handle;
1156
1157            let surface_name = self.registry.by_handle(surface_handle).map_or_else(
1158                || format!("Unknown-{}", surface_handle.id()),
1159                |entry| entry.name.clone(),
1160            );
1161
1162            let surface_info = crate::SurfaceInfo {
1163                name: surface_name.clone(),
1164                output: output_handle,
1165                instance_id: SurfaceInstanceId::new(surface_handle, output_handle),
1166            };
1167
1168            let output_info = system.app_state().get_output_info(output_handle);
1169
1170            if selector.matches(&surface_info, output_info, primary, active) {
1171                let instance_id = SurfaceInstanceId::new(surface_handle, output_handle);
1172
1173                let handler_rc = Rc::clone(&handler);
1174                let control_clone = control.clone();
1175                let surface_name_clone = surface_name.clone();
1176
1177                if let Err(e) =
1178                    surface
1179                        .component_instance()
1180                        .set_callback(callback_name, move |args| {
1181                            let ctx = CallbackContext::new(
1182                                instance_id,
1183                                surface_name_clone.clone(),
1184                                control_clone.clone(),
1185                            );
1186                            handler_rc(args, ctx).into_value()
1187                        })
1188                {
1189                    log::error!(
1190                        "Failed to register callback '{}' on surface '{}': {}",
1191                        callback_name,
1192                        surface_name,
1193                        e
1194                    );
1195                }
1196            }
1197        }
1198    }
1199
1200    pub(crate) fn with_selected<F>(&self, selector: &crate::Selector, mut f: F)
1201    where
1202        F: FnMut(&str, &ComponentInstance),
1203    {
1204        let system = self.inner.borrow();
1205        let (primary, active) = self.get_output_handles();
1206
1207        for (key, surface) in system.app_state().surfaces_with_keys() {
1208            let surface_name = system
1209                .app_state()
1210                .get_surface_name(key.surface_handle)
1211                .unwrap_or("unknown");
1212            let surface_info = crate::SurfaceInfo {
1213                name: surface_name.to_string(),
1214                output: key.output_handle,
1215                instance_id: crate::SurfaceInstanceId::new(key.surface_handle, key.output_handle),
1216            };
1217
1218            let output_info = system.app_state().get_output_info(key.output_handle);
1219
1220            if selector.matches(&surface_info, output_info, primary, active) {
1221                f(surface_name, surface.component_instance());
1222            }
1223        }
1224    }
1225
1226    pub(crate) fn with_selected_info<F>(&self, selector: &crate::Selector, mut f: F)
1227    where
1228        F: FnMut(&crate::SurfaceInfo, &ComponentInstance),
1229    {
1230        let system = self.inner.borrow();
1231        let (primary, active) = self.get_output_handles();
1232
1233        for (key, surface) in system.app_state().surfaces_with_keys() {
1234            let surface_name = system
1235                .app_state()
1236                .get_surface_name(key.surface_handle)
1237                .unwrap_or("unknown");
1238            let surface_info = crate::SurfaceInfo {
1239                name: surface_name.to_string(),
1240                output: key.output_handle,
1241                instance_id: crate::SurfaceInstanceId::new(key.surface_handle, key.output_handle),
1242            };
1243
1244            let output_info = system.app_state().get_output_info(key.output_handle);
1245
1246            if selector.matches(&surface_info, output_info, primary, active) {
1247                f(&surface_info, surface.component_instance());
1248            }
1249        }
1250    }
1251
1252    pub(crate) fn configure_selected<F>(&self, selector: &crate::Selector, mut f: F)
1253    where
1254        F: FnMut(&ComponentInstance, LayerSurfaceHandle<'_>),
1255    {
1256        let system = self.inner.borrow();
1257        let (primary, active) = self.get_output_handles();
1258
1259        for (key, surface) in system.app_state().surfaces_with_keys() {
1260            let surface_name = system
1261                .app_state()
1262                .get_surface_name(key.surface_handle)
1263                .unwrap_or("unknown");
1264            let surface_info = crate::SurfaceInfo {
1265                name: surface_name.to_string(),
1266                output: key.output_handle,
1267                instance_id: crate::SurfaceInstanceId::new(key.surface_handle, key.output_handle),
1268            };
1269
1270            let output_info = system.app_state().get_output_info(key.output_handle);
1271
1272            if selector.matches(&surface_info, output_info, primary, active) {
1273                let surface_handle = LayerSurfaceHandle::from_window_state(surface);
1274                f(surface.component_instance(), surface_handle);
1275            }
1276        }
1277    }
1278
1279    pub(crate) fn count_selected(&self, selector: &crate::Selector) -> usize {
1280        let system = self.inner.borrow();
1281        let (primary, active) = self.get_output_handles();
1282
1283        system
1284            .app_state()
1285            .surfaces_with_keys()
1286            .filter(|(key, _)| {
1287                let surface_name = system
1288                    .app_state()
1289                    .get_surface_name(key.surface_handle)
1290                    .unwrap_or("unknown");
1291                let surface_info = crate::SurfaceInfo {
1292                    name: surface_name.to_string(),
1293                    output: key.output_handle,
1294                    instance_id: crate::SurfaceInstanceId::new(
1295                        key.surface_handle,
1296                        key.output_handle,
1297                    ),
1298                };
1299
1300                let output_info = system.app_state().get_output_info(key.output_handle);
1301
1302                selector.matches(&surface_info, output_info, primary, active)
1303            })
1304            .count()
1305    }
1306
1307    pub(crate) fn get_selected_info(&self, selector: &crate::Selector) -> Vec<crate::SurfaceInfo> {
1308        let system = self.inner.borrow();
1309        let (primary, active) = self.get_output_handles();
1310
1311        system
1312            .app_state()
1313            .surfaces_with_keys()
1314            .filter_map(|(key, _)| {
1315                let surface_name = system
1316                    .app_state()
1317                    .get_surface_name(key.surface_handle)
1318                    .unwrap_or("unknown");
1319                let surface_info = crate::SurfaceInfo {
1320                    name: surface_name.to_string(),
1321                    output: key.output_handle,
1322                    instance_id: crate::SurfaceInstanceId::new(
1323                        key.surface_handle,
1324                        key.output_handle,
1325                    ),
1326                };
1327
1328                let output_info = system.app_state().get_output_info(key.output_handle);
1329
1330                if selector.matches(&surface_info, output_info, primary, active) {
1331                    Some(surface_info)
1332                } else {
1333                    None
1334                }
1335            })
1336            .collect()
1337    }
1338
1339    /// Creates a selection for targeting session lock surfaces by criteria
1340    pub fn select_lock(&self, selector: impl Into<crate::Selector>) -> crate::LockSelection<'_> {
1341        crate::LockSelection::new(self, selector.into())
1342    }
1343
1344    fn selector_to_output_filter(selector: &crate::Selector) -> layer_shika_adapters::OutputFilter {
1345        let selector = selector.clone();
1346
1347        Rc::new(
1348            move |component_name: &str,
1349                  output_handle: OutputHandle,
1350                  output_info: Option<&OutputInfo>,
1351                  primary_handle: Option<OutputHandle>,
1352                  active_handle: Option<OutputHandle>| {
1353                let surface_info = crate::SurfaceInfo {
1354                    name: component_name.to_string(),
1355                    output: output_handle,
1356                    instance_id: crate::SurfaceInstanceId::new(
1357                        crate::SurfaceHandle::from_raw(0),
1358                        output_handle,
1359                    ),
1360                };
1361
1362                selector.matches(&surface_info, output_info, primary_handle, active_handle)
1363            },
1364        )
1365    }
1366
1367    pub(crate) fn on_lock_internal<F, R>(
1368        &self,
1369        selector: &crate::Selector,
1370        callback_name: &str,
1371        handler: F,
1372    ) where
1373        F: Fn(CallbackContext) -> R + Clone + 'static,
1374        R: IntoValue,
1375    {
1376        let control = self.control();
1377        let handler = Rc::new(handler);
1378        let callback_name = callback_name.to_string();
1379
1380        let callback_handler = Rc::new(move |_args: &[Value]| {
1381            let handler_rc = Rc::clone(&handler);
1382            let control_clone = control.clone();
1383            let instance_id =
1384                SurfaceInstanceId::new(SurfaceHandle::from_raw(0), OutputHandle::from_raw(0));
1385            let ctx = CallbackContext::new(instance_id, String::new(), control_clone);
1386            handler_rc(ctx).into_value()
1387        });
1388
1389        let filter = Self::selector_to_output_filter(selector);
1390        self.inner
1391            .borrow_mut()
1392            .register_session_lock_callback_with_filter(&callback_name, callback_handler, filter);
1393    }
1394
1395    pub(crate) fn on_lock_with_args_internal<F, R>(
1396        &self,
1397        selector: &crate::Selector,
1398        callback_name: &str,
1399        handler: F,
1400    ) where
1401        F: Fn(&[Value], CallbackContext) -> R + Clone + 'static,
1402        R: IntoValue,
1403    {
1404        let control = self.control();
1405        let handler = Rc::new(handler);
1406        let callback_name = callback_name.to_string();
1407
1408        let callback_handler = Rc::new(move |args: &[Value]| {
1409            let handler_rc = Rc::clone(&handler);
1410            let control_clone = control.clone();
1411            let instance_id =
1412                SurfaceInstanceId::new(SurfaceHandle::from_raw(0), OutputHandle::from_raw(0));
1413            let ctx = CallbackContext::new(instance_id, String::new(), control_clone);
1414            handler_rc(args, ctx).into_value()
1415        });
1416
1417        let filter = Self::selector_to_output_filter(selector);
1418        self.inner
1419            .borrow_mut()
1420            .register_session_lock_callback_with_filter(&callback_name, callback_handler, filter);
1421    }
1422
1423    pub(crate) fn register_lock_property_internal(
1424        &self,
1425        selector: &crate::Selector,
1426        property_name: &str,
1427        value: Value,
1428    ) {
1429        use layer_shika_adapters::create_lock_property_operation_with_output_filter;
1430
1431        let filter = Self::selector_to_output_filter(selector);
1432        let property_operation = create_lock_property_operation_with_output_filter(
1433            property_name,
1434            value,
1435            move |component_name, output_handle, output_info, primary, active| {
1436                filter(component_name, output_handle, output_info, primary, active)
1437            },
1438        );
1439        self.inner
1440            .borrow_mut()
1441            .register_session_lock_property_operation(property_operation);
1442    }
1443
1444    pub(crate) fn with_selected_lock<F>(&self, selector: &crate::Selector, mut f: F)
1445    where
1446        F: FnMut(&str, &ComponentInstance),
1447    {
1448        let system = self.inner.borrow();
1449        let (primary, active) = self.get_output_handles();
1450
1451        let Some(component_name) = system.session_lock_component_name() else {
1452            return;
1453        };
1454
1455        system.iter_lock_surfaces(&mut |output_handle, component| {
1456            let surface_info = crate::SurfaceInfo {
1457                name: component_name.clone(),
1458                output: output_handle,
1459                instance_id: crate::SurfaceInstanceId::new(
1460                    crate::SurfaceHandle::from_raw(0),
1461                    output_handle,
1462                ),
1463            };
1464
1465            let output_info = system.app_state().get_output_info(output_handle);
1466
1467            if selector.matches(&surface_info, output_info, primary, active) {
1468                f(&component_name, component);
1469            }
1470        });
1471    }
1472
1473    pub(crate) fn count_selected_lock(&self, selector: &crate::Selector) -> usize {
1474        let system = self.inner.borrow();
1475        let (primary, active) = self.get_output_handles();
1476
1477        let Some(component_name) = system.session_lock_component_name() else {
1478            return 0;
1479        };
1480
1481        let mut count = 0;
1482        system.iter_lock_surfaces(&mut |output_handle, _component| {
1483            let surface_info = crate::SurfaceInfo {
1484                name: component_name.clone(),
1485                output: output_handle,
1486                instance_id: crate::SurfaceInstanceId::new(
1487                    crate::SurfaceHandle::from_raw(0),
1488                    output_handle,
1489                ),
1490            };
1491
1492            let output_info = system.app_state().get_output_info(output_handle);
1493
1494            if selector.matches(&surface_info, output_info, primary, active) {
1495                count += 1;
1496            }
1497        });
1498        count
1499    }
1500
1501    pub(crate) fn get_selected_lock_info(
1502        &self,
1503        selector: &crate::Selector,
1504    ) -> Vec<crate::SurfaceInfo> {
1505        let system = self.inner.borrow();
1506        let (primary, active) = self.get_output_handles();
1507
1508        let Some(component_name) = system.session_lock_component_name() else {
1509            return Vec::new();
1510        };
1511
1512        let mut info_vec = Vec::new();
1513        system.iter_lock_surfaces(&mut |output_handle, _component| {
1514            let surface_info = crate::SurfaceInfo {
1515                name: component_name.clone(),
1516                output: output_handle,
1517                instance_id: crate::SurfaceInstanceId::new(
1518                    crate::SurfaceHandle::from_raw(0),
1519                    output_handle,
1520                ),
1521            };
1522
1523            let output_info = system.app_state().get_output_info(output_handle);
1524
1525            if selector.matches(&surface_info, output_info, primary, active) {
1526                info_vec.push(surface_info);
1527            }
1528        });
1529        info_vec
1530    }
1531}
1532
1533impl ShellRuntime for Shell {
1534    type LoopHandle = EventLoopHandle;
1535    type Context<'a> = ShellEventContext<'a>;
1536
1537    fn event_loop_handle(&self) -> Self::LoopHandle {
1538        EventLoopHandle::new(Rc::downgrade(&self.inner))
1539    }
1540
1541    fn with_component<F>(&self, name: &str, mut f: F)
1542    where
1543        F: FnMut(&ComponentInstance),
1544    {
1545        let system = self.inner.borrow();
1546
1547        if self.registry.contains_name(name) {
1548            for surface in system.app_state().surfaces_by_name(name) {
1549                f(surface.component_instance());
1550            }
1551        }
1552    }
1553
1554    fn with_all_components<F>(&self, mut f: F)
1555    where
1556        F: FnMut(&str, &ComponentInstance),
1557    {
1558        let system = self.inner.borrow();
1559
1560        for name in self.registry.surface_names() {
1561            for surface in system.app_state().surfaces_by_name(name) {
1562                f(name, surface.component_instance());
1563            }
1564        }
1565    }
1566
1567    fn run(&mut self) -> Result<()> {
1568        self.inner.borrow_mut().run()?;
1569        Ok(())
1570    }
1571}
1572
1573/// Context providing access to shell state within custom event source callbacks
1574///
1575/// Obtained via event source callbacks registered through `EventLoopHandle`.
1576pub struct ShellEventContext<'a> {
1577    app_state: &'a mut AppState,
1578}
1579
1580impl<'a> FromAppState<'a> for ShellEventContext<'a> {
1581    fn from_app_state(app_state: &'a mut AppState) -> Self {
1582        Self { app_state }
1583    }
1584}
1585
1586impl ShellEventContext<'_> {
1587    /// Returns the component instance for a surface by name
1588    pub fn get_surface_component(&self, name: &str) -> Option<&ComponentInstance> {
1589        self.app_state
1590            .surfaces_by_name(name)
1591            .first()
1592            .map(|s| s.component_instance())
1593    }
1594
1595    /// Returns all surface component instances
1596    pub fn all_surface_components(&self) -> impl Iterator<Item = &ComponentInstance> {
1597        self.app_state
1598            .all_outputs()
1599            .map(SurfaceState::component_instance)
1600    }
1601
1602    /// Renders a new frame for all dirty surfaces
1603    pub fn render_frame_if_dirty(&mut self) -> Result<()> {
1604        for surface in self.app_state.all_outputs() {
1605            surface.render_frame_if_dirty()?;
1606        }
1607        Ok(())
1608    }
1609
1610    /// Returns the primary output handle
1611    #[must_use]
1612    pub fn primary_output_handle(&self) -> Option<OutputHandle> {
1613        self.app_state.primary_output_handle()
1614    }
1615
1616    /// Returns the active output handle
1617    #[must_use]
1618    pub fn active_output_handle(&self) -> Option<OutputHandle> {
1619        self.app_state.active_output_handle()
1620    }
1621
1622    /// Returns the output registry
1623    pub fn output_registry(&self) -> &OutputRegistry {
1624        self.app_state.output_registry()
1625    }
1626
1627    /// Returns all outputs with their handles and component instances
1628    pub fn outputs(&self) -> impl Iterator<Item = (OutputHandle, &ComponentInstance)> {
1629        self.app_state
1630            .outputs_with_handles()
1631            .map(|(handle, surface)| (handle, surface.component_instance()))
1632    }
1633
1634    /// Returns the component instance for a specific output
1635    pub fn get_output_component(&self, handle: OutputHandle) -> Option<&ComponentInstance> {
1636        self.app_state
1637            .get_output_by_handle(handle)
1638            .map(SurfaceState::component_instance)
1639    }
1640
1641    /// Returns information about a specific output
1642    pub fn get_output_info(&self, handle: OutputHandle) -> Option<&OutputInfo> {
1643        self.app_state.get_output_info(handle)
1644    }
1645
1646    /// Returns information about all outputs
1647    pub fn all_output_info(&self) -> impl Iterator<Item = &OutputInfo> {
1648        self.app_state.all_output_info()
1649    }
1650
1651    /// Returns all outputs with their info and component instances
1652    pub fn outputs_with_info(&self) -> impl Iterator<Item = (&OutputInfo, &ComponentInstance)> {
1653        self.app_state
1654            .outputs_with_info()
1655            .map(|(info, surface)| (info, surface.component_instance()))
1656    }
1657
1658    /// Returns the compilation result if available
1659    #[must_use]
1660    pub fn compilation_result(&self) -> Option<Rc<CompilationResult>> {
1661        self.app_state
1662            .primary_output()
1663            .and_then(SurfaceState::compilation_result)
1664    }
1665}