diff --git a/crates/adapters/src/wayland/config.rs b/crates/adapters/src/wayland/config.rs index 206c057..4197560 100644 --- a/crates/adapters/src/wayland/config.rs +++ b/crates/adapters/src/wayland/config.rs @@ -3,6 +3,7 @@ use layer_shika_domain::value_objects::anchor::AnchorEdges; use layer_shika_domain::value_objects::keyboard_interactivity::KeyboardInteractivity as DomainKeyboardInteractivity; use layer_shika_domain::value_objects::layer::Layer; use layer_shika_domain::value_objects::margins::Margins; +use layer_shika_domain::value_objects::output_policy::OutputPolicy; use slint_interpreter::{CompilationResult, ComponentDefinition}; use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::{ zwlr_layer_shell_v1::{self}, @@ -31,6 +32,7 @@ pub struct WaylandWindowConfig { pub namespace: String, pub component_definition: ComponentDefinition, pub compilation_result: Option>, + pub output_policy: OutputPolicy, } impl WaylandWindowConfig { @@ -53,6 +55,7 @@ impl WaylandWindowConfig { namespace: domain_config.namespace, component_definition, compilation_result, + output_policy: domain_config.output_policy, } } } diff --git a/crates/adapters/src/wayland/event_handling/app_dispatcher.rs b/crates/adapters/src/wayland/event_handling/app_dispatcher.rs index 9d37978..028b2f3 100644 --- a/crates/adapters/src/wayland/event_handling/app_dispatcher.rs +++ b/crates/adapters/src/wayland/event_handling/app_dispatcher.rs @@ -1,5 +1,6 @@ use crate::wayland::surfaces::app_state::AppState; use crate::wayland::surfaces::display_metrics::DisplayMetrics; +use layer_shika_domain::value_objects::output_info::OutputGeometry; use log::info; use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::{ zwlr_layer_shell_v1::ZwlrLayerShellV1, @@ -77,21 +78,38 @@ impl Dispatch for AppState { _conn: &Connection, _qhandle: &QueueHandle, ) { + let output_id = proxy.id(); + let handle = state.get_handle_by_output_id(&output_id); + match event { wl_output::Event::Mode { width, height, .. } => { - let output_id = proxy.id(); if let Some(window) = state.get_output_by_output_id_mut(&output_id) { window.handle_output_mode(width, height); } } wl_output::Event::Description { ref description } => { info!("WlOutput description: {description:?}"); + if let Some(handle) = handle { + if let Some(info) = state.get_output_info_mut(handle) { + info.set_description(description.clone()); + } + } } wl_output::Event::Scale { ref factor } => { info!("WlOutput factor scale: {factor:?}"); + if let Some(handle) = handle { + if let Some(info) = state.get_output_info_mut(handle) { + info.set_scale(*factor); + } + } } wl_output::Event::Name { ref name } => { info!("WlOutput name: {name:?}"); + if let Some(handle) = handle { + if let Some(info) = state.get_output_info_mut(handle) { + info.set_name(name.clone()); + } + } } wl_output::Event::Geometry { x, @@ -106,6 +124,19 @@ impl Dispatch for AppState { info!( "WlOutput geometry: x={x}, y={y}, physical_width={physical_width}, physical_height={physical_height}, subpixel={subpixel:?}, make={make:?}, model={model:?}, transform={transform:?}" ); + if let Some(handle) = handle { + if let Some(info) = state.get_output_info_mut(handle) { + let mut geometry = + OutputGeometry::new(x, y, physical_width, physical_height); + if !make.is_empty() { + geometry = geometry.with_make(make); + } + if !model.is_empty() { + geometry = geometry.with_model(model); + } + info.set_geometry(geometry); + } + } } wl_output::Event::Done => { info!("WlOutput done"); diff --git a/crates/adapters/src/wayland/shell_adapter.rs b/crates/adapters/src/wayland/shell_adapter.rs index 8c5d826..33f65cf 100644 --- a/crates/adapters/src/wayland/shell_adapter.rs +++ b/crates/adapters/src/wayland/shell_adapter.rs @@ -23,6 +23,8 @@ use crate::{ use core::result::Result as CoreResult; use layer_shika_domain::errors::DomainError; use layer_shika_domain::ports::windowing::WindowingSystemPort; +use layer_shika_domain::value_objects::output_handle::OutputHandle; +use layer_shika_domain::value_objects::output_info::OutputInfo; use log::{error, info}; use slint::{ LogicalPosition, PhysicalSize, PlatformError, WindowPosition, @@ -98,7 +100,17 @@ impl WaylandWindowingSystem { ) -> Result> { let mut setups = Vec::new(); - for output in &global_ctx.outputs { + for (index, output) in global_ctx.outputs.iter().enumerate() { + let is_primary = index == 0; + + let mut temp_info = OutputInfo::new(OutputHandle::new()); + temp_info.set_primary(is_primary); + + if !config.output_policy.should_render(&temp_info) { + info!("Skipping output {} due to output policy", index); + continue; + } + let output_id = output.id(); let setup_params = SurfaceSetupParams { diff --git a/crates/adapters/src/wayland/surfaces/app_state.rs b/crates/adapters/src/wayland/surfaces/app_state.rs index 26cc457..fc6e18d 100644 --- a/crates/adapters/src/wayland/surfaces/app_state.rs +++ b/crates/adapters/src/wayland/surfaces/app_state.rs @@ -3,6 +3,7 @@ use super::surface_state::WindowState; use crate::wayland::managed_proxies::ManagedWlPointer; use crate::wayland::outputs::OutputKey; use layer_shika_domain::value_objects::output_handle::OutputHandle; +use layer_shika_domain::value_objects::output_info::OutputInfo; use std::collections::HashMap; use std::rc::Rc; use wayland_client::Proxy; @@ -12,6 +13,7 @@ pub type PerOutputWindow = WindowState; pub struct AppState { outputs: HashMap, + output_info: HashMap, surface_to_output: HashMap, output_to_handle: HashMap, _pointer: ManagedWlPointer, @@ -24,6 +26,7 @@ impl AppState { pub fn new(pointer: ManagedWlPointer, shared_serial: Rc) -> Self { Self { outputs: HashMap::new(), + output_info: HashMap::new(), surface_to_output: HashMap::new(), output_to_handle: HashMap::new(), _pointer: pointer, @@ -44,10 +47,15 @@ impl AppState { self.output_to_handle.insert(output_id, handle); self.surface_to_output.insert(main_surface_id, handle); - if self.primary_output.is_none() { + let is_primary = self.primary_output.is_none(); + if is_primary { self.primary_output = Some(handle); } + let mut info = OutputInfo::new(handle); + info.set_primary(is_primary); + self.output_info.insert(handle, info); + self.outputs.insert(handle, window); } @@ -219,4 +227,22 @@ impl AppState { .map(|_| handle) }) } + + pub fn get_output_info(&self, handle: OutputHandle) -> Option<&OutputInfo> { + self.output_info.get(&handle) + } + + pub fn get_output_info_mut(&mut self, handle: OutputHandle) -> Option<&mut OutputInfo> { + self.output_info.get_mut(&handle) + } + + pub fn all_output_info(&self) -> impl Iterator { + self.output_info.values() + } + + pub fn outputs_with_info(&self) -> impl Iterator { + self.output_info + .iter() + .filter_map(|(handle, info)| self.outputs.get(handle).map(|window| (info, window))) + } } diff --git a/crates/composition/src/builder.rs b/crates/composition/src/builder.rs index dc1c3e7..572fcc9 100644 --- a/crates/composition/src/builder.rs +++ b/crates/composition/src/builder.rs @@ -3,7 +3,8 @@ use crate::system::WindowingSystem; use layer_shika_adapters::platform::slint_interpreter::{CompilationResult, Compiler}; use layer_shika_domain::errors::DomainError; use layer_shika_domain::prelude::{ - AnchorEdges, KeyboardInteractivity, Layer, Margins, ScaleFactor, WindowConfig, WindowHeight, + AnchorEdges, KeyboardInteractivity, Layer, Margins, OutputPolicy, ScaleFactor, WindowConfig, + WindowHeight, }; use spin_on::spin_on; use std::path::{Path, PathBuf}; @@ -187,6 +188,12 @@ impl LayerShika { self } + #[must_use] + pub fn with_output_policy(mut self, policy: OutputPolicy) -> Self { + self.config.output_policy = policy; + self + } + pub fn build(self) -> Result { let component_definition = self .state diff --git a/crates/composition/src/lib.rs b/crates/composition/src/lib.rs index 06a1973..a3653cc 100644 --- a/crates/composition/src/lib.rs +++ b/crates/composition/src/lib.rs @@ -15,6 +15,8 @@ pub use layer_shika_domain::value_objects::anchor::AnchorEdges; pub use layer_shika_domain::value_objects::keyboard_interactivity::KeyboardInteractivity; pub use layer_shika_domain::value_objects::layer::Layer; pub use layer_shika_domain::value_objects::output_handle::OutputHandle; +pub use layer_shika_domain::value_objects::output_info::{OutputGeometry, OutputInfo}; +pub use layer_shika_domain::value_objects::output_policy::OutputPolicy; pub use layer_shika_domain::value_objects::popup_positioning_mode::PopupPositioningMode; pub use layer_shika_domain::value_objects::popup_request::{ PopupAt, PopupHandle, PopupRequest, PopupSize, diff --git a/crates/composition/src/system.rs b/crates/composition/src/system.rs index 7312727..480f85c 100644 --- a/crates/composition/src/system.rs +++ b/crates/composition/src/system.rs @@ -15,6 +15,7 @@ use layer_shika_adapters::{ use layer_shika_domain::config::WindowConfig; use layer_shika_domain::errors::DomainError; use layer_shika_domain::value_objects::output_handle::OutputHandle; +use layer_shika_domain::value_objects::output_info::OutputInfo; use layer_shika_domain::value_objects::popup_positioning_mode::PopupPositioningMode; use layer_shika_domain::value_objects::popup_request::{PopupHandle, PopupRequest, PopupSize}; use std::cell::Cell; @@ -162,6 +163,20 @@ impl RuntimeState<'_> { .map(WindowState::component_instance) } + pub fn get_output_info(&self, handle: OutputHandle) -> Option<&OutputInfo> { + self.app_state.get_output_info(handle) + } + + pub fn all_output_info(&self) -> impl Iterator { + self.app_state.all_output_info() + } + + pub fn outputs_with_info(&self) -> impl Iterator { + self.app_state + .outputs_with_info() + .map(|(info, window)| (info, window.component_instance())) + } + fn active_or_primary_output(&self) -> Option<&WindowState> { self.app_state .active_output() @@ -524,4 +539,44 @@ impl WindowingSystem { f(window.component_instance()); } } + + pub fn with_output(&self, handle: OutputHandle, f: F) -> Result + where + F: FnOnce(&ComponentInstance) -> R, + { + let facade = self.inner.borrow(); + let system = facade.inner_ref(); + 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())) + } + + pub fn with_all_outputs(&self, mut f: F) + where + F: FnMut(OutputHandle, &ComponentInstance), + { + let facade = self.inner.borrow(); + let system = facade.inner_ref(); + for (handle, window) in system.app_state().outputs_with_handles() { + f(handle, window.component_instance()); + } + } + + pub fn get_output_info(&self, handle: OutputHandle) -> Option { + let facade = self.inner.borrow(); + let system = facade.inner_ref(); + system.app_state().get_output_info(handle).cloned() + } + + pub fn all_output_info(&self) -> Vec { + let facade = self.inner.borrow(); + let system = facade.inner_ref(); + system.app_state().all_output_info().cloned().collect() + } } diff --git a/crates/domain/src/config.rs b/crates/domain/src/config.rs index 92ce3ec..0acbe04 100644 --- a/crates/domain/src/config.rs +++ b/crates/domain/src/config.rs @@ -4,6 +4,7 @@ use crate::value_objects::dimensions::WindowHeight; use crate::value_objects::keyboard_interactivity::KeyboardInteractivity; use crate::value_objects::layer::Layer; use crate::value_objects::margins::Margins; +use crate::value_objects::output_policy::OutputPolicy; #[derive(Debug, Clone)] pub struct WindowConfig { @@ -15,6 +16,7 @@ pub struct WindowConfig { pub layer: Layer, pub anchor: AnchorEdges, pub keyboard_interactivity: KeyboardInteractivity, + pub output_policy: OutputPolicy, } impl WindowConfig { @@ -29,6 +31,7 @@ impl WindowConfig { layer: Layer::default(), anchor: AnchorEdges::default(), keyboard_interactivity: KeyboardInteractivity::default(), + output_policy: OutputPolicy::default(), } } } diff --git a/crates/domain/src/prelude.rs b/crates/domain/src/prelude.rs index 738e274..0066db9 100644 --- a/crates/domain/src/prelude.rs +++ b/crates/domain/src/prelude.rs @@ -14,3 +14,6 @@ pub use crate::value_objects::dimensions::WindowHeight; pub use crate::value_objects::keyboard_interactivity::KeyboardInteractivity; pub use crate::value_objects::layer::Layer; pub use crate::value_objects::margins::Margins; +pub use crate::value_objects::output_handle::OutputHandle; +pub use crate::value_objects::output_info::{OutputGeometry, OutputInfo}; +pub use crate::value_objects::output_policy::OutputPolicy; diff --git a/crates/domain/src/value_objects/mod.rs b/crates/domain/src/value_objects/mod.rs index f84a742..12a0fd6 100644 --- a/crates/domain/src/value_objects/mod.rs +++ b/crates/domain/src/value_objects/mod.rs @@ -4,6 +4,8 @@ pub mod keyboard_interactivity; pub mod layer; pub mod margins; pub mod output_handle; +pub mod output_info; +pub mod output_policy; pub mod popup_config; pub mod popup_positioning_mode; pub mod popup_request; diff --git a/crates/domain/src/value_objects/output_info.rs b/crates/domain/src/value_objects/output_info.rs new file mode 100644 index 0000000..f0089a0 --- /dev/null +++ b/crates/domain/src/value_objects/output_info.rs @@ -0,0 +1,103 @@ +use crate::value_objects::output_handle::OutputHandle; + +#[derive(Debug, Clone, PartialEq)] +pub struct OutputInfo { + handle: OutputHandle, + name: Option, + description: Option, + geometry: Option, + scale: Option, + is_primary: bool, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct OutputGeometry { + pub x: i32, + pub y: i32, + pub physical_width: i32, + pub physical_height: i32, + pub make: Option, + pub model: Option, +} + +impl OutputInfo { + pub fn new(handle: OutputHandle) -> Self { + Self { + handle, + name: None, + description: None, + geometry: None, + scale: None, + is_primary: false, + } + } + + pub const fn handle(&self) -> OutputHandle { + self.handle + } + + pub fn name(&self) -> Option<&str> { + self.name.as_deref() + } + + pub fn description(&self) -> Option<&str> { + self.description.as_deref() + } + + pub const fn geometry(&self) -> Option<&OutputGeometry> { + self.geometry.as_ref() + } + + pub const fn scale(&self) -> Option { + self.scale + } + + pub const fn is_primary(&self) -> bool { + self.is_primary + } + + pub fn set_name(&mut self, name: String) { + self.name = Some(name); + } + + pub fn set_description(&mut self, description: String) { + self.description = Some(description); + } + + pub fn set_geometry(&mut self, geometry: OutputGeometry) { + self.geometry = Some(geometry); + } + + pub fn set_scale(&mut self, scale: i32) { + self.scale = Some(scale); + } + + pub fn set_primary(&mut self, is_primary: bool) { + self.is_primary = is_primary; + } +} + +impl OutputGeometry { + pub const fn new(x: i32, y: i32, physical_width: i32, physical_height: i32) -> Self { + Self { + x, + y, + physical_width, + physical_height, + make: None, + model: None, + } + } + + #[must_use] + pub fn with_make(mut self, make: String) -> Self { + self.make = Some(make); + self + } + + #[must_use] + pub fn with_model(mut self, model: String) -> Self { + self.model = Some(model); + self + } +} diff --git a/crates/domain/src/value_objects/output_policy.rs b/crates/domain/src/value_objects/output_policy.rs new file mode 100644 index 0000000..62633da --- /dev/null +++ b/crates/domain/src/value_objects/output_policy.rs @@ -0,0 +1,58 @@ +use crate::value_objects::output_info::OutputInfo; +use std::fmt; + +pub enum OutputPolicy { + AllOutputs, + PrimaryOnly, + Custom(Box bool>), +} + +impl OutputPolicy { + pub fn should_render(&self, info: &OutputInfo) -> bool { + match self { + OutputPolicy::AllOutputs => true, + OutputPolicy::PrimaryOnly => info.is_primary(), + OutputPolicy::Custom(filter) => filter(info), + } + } + + pub fn primary_only() -> Self { + Self::PrimaryOnly + } + + pub fn all_outputs() -> Self { + Self::AllOutputs + } + + pub fn custom(filter: F) -> Self + where + F: Fn(&OutputInfo) -> bool + 'static, + { + Self::Custom(Box::new(filter)) + } +} + +impl Default for OutputPolicy { + fn default() -> Self { + Self::AllOutputs + } +} + +impl Clone for OutputPolicy { + fn clone(&self) -> Self { + match self { + OutputPolicy::AllOutputs | OutputPolicy::Custom(_) => OutputPolicy::AllOutputs, + OutputPolicy::PrimaryOnly => OutputPolicy::PrimaryOnly, + } + } +} + +impl fmt::Debug for OutputPolicy { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + OutputPolicy::AllOutputs => write!(f, "OutputPolicy::AllOutputs"), + OutputPolicy::PrimaryOnly => write!(f, "OutputPolicy::PrimaryOnly"), + OutputPolicy::Custom(_) => write!(f, "OutputPolicy::Custom()"), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 1443535..3d64908 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,12 +22,13 @@ //! ```rust,no_run //! use layer_shika::prelude::*; //! -//! let (window, system) = LayerShika::from_file("ui/main.slint", "AppWindow")? +//! let system = LayerShika::from_file("ui/main.slint", Some("AppWindow"))? //! .with_height(42) //! .with_anchor(AnchorEdges::top_bar()) //! .with_exclusive_zone(42) //! .build()?; //! +//! let mut system = system; //! system.run()?; //! # Ok::<(), layer_shika::Error>(()) //! ``` @@ -44,9 +45,10 @@ pub mod prelude; pub use layer_shika_composition::{ - AnchorEdges, Error, EventLoopHandle, KeyboardInteractivity, Layer, LayerShika, OutputHandle, - PopupAt, PopupHandle, PopupPositioningMode, PopupRequest, PopupSize, PopupWindow, Result, - RuntimeState, SlintCallbackContract, SlintCallbackNames, WindowingSystem, + AnchorEdges, Error, EventLoopHandle, KeyboardInteractivity, Layer, LayerShika, OutputGeometry, + OutputHandle, OutputInfo, OutputPolicy, PopupAt, PopupHandle, PopupPositioningMode, + PopupRequest, PopupSize, PopupWindow, Result, RuntimeState, SlintCallbackContract, + SlintCallbackNames, WindowingSystem, }; pub use layer_shika_composition::{slint, slint_interpreter}; diff --git a/src/prelude.rs b/src/prelude.rs index f02cdd2..da5ac50 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -16,8 +16,8 @@ pub use crate::{ // Domain value objects pub use crate::{ - AnchorEdges, KeyboardInteractivity, Layer, OutputHandle, PopupAt, PopupHandle, - PopupPositioningMode, PopupRequest, PopupSize, + AnchorEdges, KeyboardInteractivity, Layer, OutputGeometry, OutputHandle, OutputInfo, + OutputPolicy, PopupAt, PopupHandle, PopupPositioningMode, PopupRequest, PopupSize, }; // Event loop types