diff --git a/crates/adapters/src/lib.rs b/crates/adapters/src/lib.rs index 4d224e6..eb83f27 100644 --- a/crates/adapters/src/lib.rs +++ b/crates/adapters/src/lib.rs @@ -9,6 +9,7 @@ pub use rendering::femtovg::popup_window::PopupWindow; pub use wayland::config::WaylandWindowConfig; pub use wayland::facade::{PopupManagerFacade, RuntimeStateFacade, WindowingSystemFacade}; pub use wayland::shell_adapter::WaylandWindowingSystem; +pub use wayland::surfaces::app_state::AppState; pub use wayland::surfaces::popup_manager::PopupManager; pub use wayland::surfaces::surface_state::WindowState; diff --git a/crates/adapters/src/rendering/slint_integration/platform.rs b/crates/adapters/src/rendering/slint_integration/platform.rs index 50267b3..86f055b 100644 --- a/crates/adapters/src/rendering/slint_integration/platform.rs +++ b/crates/adapters/src/rendering/slint_integration/platform.rs @@ -2,29 +2,31 @@ use slint::{ PlatformError, platform::{Platform, WindowAdapter}, }; -use std::cell::{Cell, OnceCell}; -use std::rc::{Rc, Weak}; +use std::cell::{OnceCell, RefCell}; +use std::rc::Rc; use crate::rendering::femtovg::main_window::FemtoVGWindow; type PopupCreator = dyn Fn() -> Result, PlatformError>; pub struct CustomSlintPlatform { - main_window: Weak, + pending_windows: RefCell>>, popup_creator: OnceCell>, - first_call: Cell, } impl CustomSlintPlatform { #[must_use] pub fn new(window: &Rc) -> Rc { Rc::new(Self { - main_window: Rc::downgrade(window), + pending_windows: RefCell::new(vec![Rc::clone(window)]), popup_creator: OnceCell::new(), - first_call: Cell::new(true), }) } + pub fn add_window(&self, window: Rc) { + self.pending_windows.borrow_mut().push(window); + } + pub fn set_popup_creator(&self, creator: F) where F: Fn() -> Result, PlatformError> + 'static, @@ -37,12 +39,10 @@ impl CustomSlintPlatform { impl Platform for CustomSlintPlatform { fn create_window_adapter(&self) -> Result, PlatformError> { - if self.first_call.get() { - self.first_call.set(false); - self.main_window - .upgrade() - .ok_or(PlatformError::NoPlatform) - .map(|w| w as Rc) + let mut windows = self.pending_windows.borrow_mut(); + if !windows.is_empty() { + let window = windows.remove(0); + Ok(window as Rc) } else if let Some(creator) = self.popup_creator.get() { creator() } else { diff --git a/crates/adapters/src/wayland/event_handling/app_dispatcher.rs b/crates/adapters/src/wayland/event_handling/app_dispatcher.rs new file mode 100644 index 0000000..d52dfc9 --- /dev/null +++ b/crates/adapters/src/wayland/event_handling/app_dispatcher.rs @@ -0,0 +1,415 @@ +use crate::wayland::surfaces::app_state::AppState; +use crate::wayland::surfaces::display_metrics::DisplayMetrics; +use log::info; +use slint::PhysicalSize; +use slint::platform::{PointerEventButton, WindowEvent}; +use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::{ + zwlr_layer_shell_v1::ZwlrLayerShellV1, + zwlr_layer_surface_v1::{self, ZwlrLayerSurfaceV1}, +}; +use wayland_client::WEnum; +use wayland_client::{ + Connection, Dispatch, Proxy, QueueHandle, + globals::GlobalListContents, + protocol::{ + wl_compositor::WlCompositor, + wl_output::{self, WlOutput}, + wl_pointer::{self, WlPointer}, + wl_registry::WlRegistry, + wl_seat::WlSeat, + wl_surface::WlSurface, + }, +}; +use wayland_protocols::wp::fractional_scale::v1::client::{ + wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1, + wp_fractional_scale_v1::{self, WpFractionalScaleV1}, +}; +use wayland_protocols::wp::viewporter::client::{ + wp_viewport::WpViewport, wp_viewporter::WpViewporter, +}; +use wayland_protocols::xdg::shell::client::{ + xdg_popup::{self, XdgPopup}, + xdg_positioner::XdgPositioner, + xdg_surface::{self, XdgSurface}, + xdg_wm_base::{self, XdgWmBase}, +}; + +impl Dispatch for AppState { + #[allow(clippy::cast_possible_truncation)] + #[allow(clippy::cast_sign_loss)] + #[allow(clippy::cast_precision_loss)] + fn event( + state: &mut Self, + layer_surface: &ZwlrLayerSurfaceV1, + event: zwlr_layer_surface_v1::Event, + _data: &(), + _conn: &Connection, + _queue_handle: &QueueHandle, + ) { + match event { + zwlr_layer_surface_v1::Event::Configure { + serial, + width, + height, + } => { + info!("Layer surface configured with compositor size: {width}x{height}"); + layer_surface.ack_configure(serial); + + let layer_surface_id = layer_surface.id(); + let Some(window) = state.get_output_by_layer_surface_mut(&layer_surface_id) else { + info!( + "Could not find window for layer surface {:?}", + layer_surface_id + ); + return; + }; + + let output_width = window.output_size().width; + let scale_factor = window.scale_factor(); + + let target_width = if width == 0 || (width == 1 && output_width > 1) { + if scale_factor > 1.0 { + (output_width as f32 / scale_factor).round() as u32 + } else { + output_width + } + } else { + width + }; + + let target_height = if height > 0 { + height + } else { + let h = window.height(); + if scale_factor > 1.0 { + (h as f32 / scale_factor).round() as u32 + } else { + h + } + }; + + let clamped_width = target_width.min(output_width); + + info!( + "Using dimensions: {}x{} (clamped from {}x{}, output: {}x{})", + clamped_width, + target_height, + target_width, + target_height, + output_width, + window.output_size().height + ); + + window.update_size(clamped_width, target_height); + } + zwlr_layer_surface_v1::Event::Closed => { + info!("Layer surface closed"); + } + _ => {} + } + } +} + +impl Dispatch for AppState { + fn event( + state: &mut Self, + proxy: &WlOutput, + event: ::Event, + _data: &(), + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + match event { + wl_output::Event::Mode { width, height, .. } => { + info!("WlOutput size changed to {width}x{height}"); + let width = width.try_into().unwrap_or_default(); + let height = height.try_into().unwrap_or_default(); + + let output_id = proxy.id(); + if let Some(window) = state.get_output_by_output_id_mut(&output_id) { + window.set_output_size(PhysicalSize::new(width, height)); + } + } + wl_output::Event::Description { ref description } => { + info!("WlOutput description: {description:?}"); + } + wl_output::Event::Scale { ref factor } => { + info!("WlOutput factor scale: {factor:?}"); + } + wl_output::Event::Name { ref name } => { + info!("WlOutput name: {name:?}"); + } + wl_output::Event::Geometry { + x, + y, + physical_width, + physical_height, + subpixel, + make, + model, + transform, + } => { + info!( + "WlOutput geometry: x={x}, y={y}, physical_width={physical_width}, physical_height={physical_height}, subpixel={subpixel:?}, make={make:?}, model={model:?}, transform={transform:?}" + ); + } + wl_output::Event::Done => { + info!("WlOutput done"); + } + _ => {} + } + } +} + +impl Dispatch for AppState { + fn event( + state: &mut Self, + _proxy: &WlPointer, + event: ::Event, + _data: &(), + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + match event { + wl_pointer::Event::Enter { + serial, + surface, + surface_x, + surface_y, + } => { + info!("Pointer entered surface {:?}", surface.id()); + + let surface_id = surface.id(); + + if let Some(window) = state.get_output_by_surface_mut(&surface_id) { + window.set_last_pointer_serial(serial); + window.set_current_pointer_position(surface_x, surface_y); + window.set_entered_surface(&surface); + let position = window.current_pointer_position(); + window.dispatch_to_active_window(WindowEvent::PointerMoved { position }); + + if let Some(key) = state.get_key_by_surface(&surface_id).cloned() { + state.set_active_output(Some(key)); + } + } else { + let key = state.get_key_by_popup(&surface_id); + if let Some(window) = state.find_output_by_popup_mut(&surface_id) { + window.set_last_pointer_serial(serial); + window.set_current_pointer_position(surface_x, surface_y); + window.set_entered_surface(&surface); + let position = window.current_pointer_position(); + window.dispatch_to_active_window(WindowEvent::PointerMoved { position }); + + if let Some(key) = key { + state.set_active_output(Some(key)); + } + } + } + } + + wl_pointer::Event::Motion { + surface_x, + surface_y, + .. + } => { + if let Some(output_key) = state.active_output().cloned() { + if let Some(window) = state.get_output_by_key_mut(&output_key) { + window.set_current_pointer_position(surface_x, surface_y); + let position = window.current_pointer_position(); + window.dispatch_to_active_window(WindowEvent::PointerMoved { position }); + } + } + } + + wl_pointer::Event::Leave { .. } => { + if let Some(output_key) = state.active_output().cloned() { + if let Some(window) = state.get_output_by_key_mut(&output_key) { + window.dispatch_to_active_window(WindowEvent::PointerExited); + window.clear_entered_surface(); + } + } + state.set_active_output(None); + } + + wl_pointer::Event::Button { + serial, + state: button_state, + .. + } => { + if let Some(output_key) = state.active_output().cloned() { + if let Some(window) = state.get_output_by_key_mut(&output_key) { + window.set_last_pointer_serial(serial); + let position = window.current_pointer_position(); + let event = match button_state { + WEnum::Value(wl_pointer::ButtonState::Pressed) => { + WindowEvent::PointerPressed { + button: PointerEventButton::Left, + position, + } + } + _ => WindowEvent::PointerReleased { + button: PointerEventButton::Left, + position, + }, + }; + window.dispatch_to_active_window(event); + } + } + } + _ => {} + } + } +} + +impl Dispatch for AppState { + fn event( + state: &mut Self, + proxy: &WpFractionalScaleV1, + event: wp_fractional_scale_v1::Event, + _data: &(), + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + if let wp_fractional_scale_v1::Event::PreferredScale { scale } = event { + let scale_float = DisplayMetrics::scale_factor_from_120ths(scale); + info!("Fractional scale received: {scale_float} ({scale}x)"); + + for window in state.all_outputs_mut() { + window.update_scale_for_fractional_scale_object(proxy, scale); + } + } + } +} + +impl Dispatch for AppState { + fn event( + _state: &mut Self, + xdg_wm_base: &XdgWmBase, + event: xdg_wm_base::Event, + _data: &(), + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + if let xdg_wm_base::Event::Ping { serial } = event { + info!("XdgWmBase ping received, sending pong with serial {serial}"); + xdg_wm_base.pong(serial); + } + } +} + +impl Dispatch for AppState { + fn event( + state: &mut Self, + xdg_popup: &XdgPopup, + event: xdg_popup::Event, + _data: &(), + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + match event { + xdg_popup::Event::Configure { + x, + y, + width, + height, + } => { + info!("XdgPopup Configure: position=({x}, {y}), size=({width}x{height})"); + + let popup_id = xdg_popup.id(); + for window in state.all_outputs_mut() { + if let Some(popup_manager) = window.popup_manager() { + if let Some(handle) = popup_manager.find_by_xdg_popup(&popup_id) { + info!( + "Marking popup with handle {handle:?} as configured after XdgPopup::Configure" + ); + popup_manager.mark_popup_configured(handle.key()); + popup_manager.mark_all_popups_dirty(); + break; + } + } + } + } + xdg_popup::Event::PopupDone => { + info!("XdgPopup dismissed by compositor"); + let popup_id = xdg_popup.id(); + + for window in state.all_outputs_mut() { + let popup_handle = window + .popup_manager() + .as_ref() + .and_then(|pm| pm.find_by_xdg_popup(&popup_id)); + + if let Some(handle) = popup_handle { + info!("Destroying popup with handle {handle:?}"); + if let Some(popup_manager) = window.popup_manager() { + let _result = popup_manager.close(handle); + } + break; + } + } + } + xdg_popup::Event::Repositioned { token } => { + info!("XdgPopup repositioned with token {token}"); + } + _ => {} + } + } +} + +impl Dispatch for AppState { + fn event( + state: &mut Self, + xdg_surface: &XdgSurface, + event: xdg_surface::Event, + _data: &(), + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + if let xdg_surface::Event::Configure { serial } = event { + info!("XdgSurface Configure received, sending ack with serial {serial}"); + xdg_surface.ack_configure(serial); + + let xdg_surface_id = xdg_surface.id(); + for window in state.all_outputs_mut() { + if let Some(popup_manager) = window.popup_manager() { + if popup_manager.find_by_xdg_surface(&xdg_surface_id).is_some() { + info!("Marking all popups as dirty after Configure"); + popup_manager.mark_all_popups_dirty(); + break; + } + } + } + } + } +} + +macro_rules! impl_empty_dispatch_app { + ($(($t:ty, $u:ty)),+) => { + $( + impl Dispatch<$t, $u> for AppState { + fn event( + _state: &mut Self, + _proxy: &$t, + _event: <$t as wayland_client::Proxy>::Event, + _data: &$u, + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + info!("Implement empty dispatch event for {:?}", stringify!($t)); + } + } + )+ + }; +} + +impl_empty_dispatch_app!( + (WlRegistry, GlobalListContents), + (WlCompositor, ()), + (WlSurface, ()), + (ZwlrLayerShellV1, ()), + (WlSeat, ()), + (WpFractionalScaleManagerV1, ()), + (WpViewporter, ()), + (WpViewport, ()), + (XdgPositioner, ()) +); diff --git a/crates/adapters/src/wayland/event_handling/mod.rs b/crates/adapters/src/wayland/event_handling/mod.rs index 17df0d7..8d8d114 100644 --- a/crates/adapters/src/wayland/event_handling/mod.rs +++ b/crates/adapters/src/wayland/event_handling/mod.rs @@ -1,2 +1,3 @@ +pub mod app_dispatcher; pub mod event_dispatcher; pub mod event_macros; diff --git a/crates/adapters/src/wayland/facade.rs b/crates/adapters/src/wayland/facade.rs index 4a30973..47b9b68 100644 --- a/crates/adapters/src/wayland/facade.rs +++ b/crates/adapters/src/wayland/facade.rs @@ -25,7 +25,7 @@ impl WindowingSystemFacade { &mut self.inner } - pub fn component_instance(&self) -> &ComponentInstance { + pub fn component_instance(&self) -> Result<&ComponentInstance> { self.inner.component_instance() } diff --git a/crates/adapters/src/wayland/globals/context.rs b/crates/adapters/src/wayland/globals/context.rs index e18e5ad..8a3df01 100644 --- a/crates/adapters/src/wayland/globals/context.rs +++ b/crates/adapters/src/wayland/globals/context.rs @@ -10,11 +10,11 @@ use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_man use wayland_protocols::wp::viewporter::client::wp_viewporter::WpViewporter; use wayland_protocols::xdg::shell::client::xdg_wm_base::XdgWmBase; -use crate::wayland::surfaces::surface_state::WindowState; +use crate::wayland::surfaces::app_state::AppState; pub struct GlobalContext { pub compositor: WlCompositor, - pub output: WlOutput, + pub outputs: Vec, pub layer_shell: ZwlrLayerShellV1, pub seat: WlSeat, pub xdg_wm_base: Option, @@ -25,21 +25,57 @@ pub struct GlobalContext { impl GlobalContext { pub fn initialize( connection: &Connection, - queue_handle: &QueueHandle, + queue_handle: &QueueHandle, ) -> Result { - let global_list = registry_queue_init::(connection) + let global_list = registry_queue_init::(connection) .map(|(global_list, _)| global_list) .map_err(|e| LayerShikaError::GlobalInitialization { source: e })?; - let (compositor, output, layer_shell, seat) = bind_globals!( + let (compositor, layer_shell, seat) = bind_globals!( &global_list, queue_handle, (WlCompositor, compositor, 3..=6), - (WlOutput, output, 1..=4), (ZwlrLayerShellV1, layer_shell, 1..=5), (WlSeat, seat, 1..=9) )?; + let output_names: Vec = global_list + .contents() + .clone_list() + .into_iter() + .filter(|global| global.interface == "wl_output") + .map(|global| { + info!( + "Found wl_output global with name: {} at version {}", + global.name, global.version + ); + global.name + }) + .collect(); + + info!( + "Total unique wl_output globals found: {}", + output_names.len() + ); + + let outputs: Vec = output_names + .iter() + .map(|&name| { + info!("Binding wl_output with name: {}", name); + global_list + .registry() + .bind::(name, 4, queue_handle, ()) + }) + .collect(); + + if outputs.is_empty() { + return Err(LayerShikaError::InvalidInput { + message: "No outputs found".into(), + }); + } + + info!("Discovered {} output(s)", outputs.len()); + let xdg_wm_base = global_list .bind::(queue_handle, 1..=6, ()) .ok(); @@ -66,7 +102,7 @@ impl GlobalContext { Ok(Self { compositor, - output, + outputs, layer_shell, seat, xdg_wm_base, diff --git a/crates/adapters/src/wayland/mod.rs b/crates/adapters/src/wayland/mod.rs index b6f2102..6daee68 100644 --- a/crates/adapters/src/wayland/mod.rs +++ b/crates/adapters/src/wayland/mod.rs @@ -3,5 +3,6 @@ pub(crate) mod event_handling; pub(crate) mod facade; pub(crate) mod globals; pub(crate) mod managed_proxies; +pub(crate) mod outputs; pub(crate) mod shell_adapter; pub(crate) mod surfaces; diff --git a/crates/adapters/src/wayland/outputs.rs b/crates/adapters/src/wayland/outputs.rs new file mode 100644 index 0000000..8a6a165 --- /dev/null +++ b/crates/adapters/src/wayland/outputs.rs @@ -0,0 +1,20 @@ +use wayland_client::backend::ObjectId; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct OutputKey(ObjectId); + +impl OutputKey { + pub const fn new(id: ObjectId) -> Self { + Self(id) + } + + pub const fn id(&self) -> &ObjectId { + &self.0 + } +} + +impl From for OutputKey { + fn from(id: ObjectId) -> Self { + Self::new(id) + } +} diff --git a/crates/adapters/src/wayland/shell_adapter.rs b/crates/adapters/src/wayland/shell_adapter.rs index 513c178..8c5d826 100644 --- a/crates/adapters/src/wayland/shell_adapter.rs +++ b/crates/adapters/src/wayland/shell_adapter.rs @@ -1,13 +1,17 @@ use crate::wayland::{ config::{LayerSurfaceConfig, WaylandWindowConfig}, globals::context::GlobalContext, + managed_proxies::ManagedWlPointer, surfaces::layer_surface::{SurfaceCtx, SurfaceSetupParams}, surfaces::popup_manager::{PopupContext, PopupManager}, surfaces::{ - event_context::SharedPointerSerial, surface_builder::WindowStateBuilder, + app_state::AppState, + event_context::SharedPointerSerial, + surface_builder::{PlatformWrapper, WindowStateBuilder}, surface_state::WindowState, }, }; +use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1; use crate::{ errors::{EventLoopError, LayerShikaError, RenderingError, Result}, rendering::{ @@ -22,7 +26,7 @@ use layer_shika_domain::ports::windowing::WindowingSystemPort; use log::{error, info}; use slint::{ LogicalPosition, PhysicalSize, PlatformError, WindowPosition, - platform::{WindowAdapter, femtovg_renderer::FemtoVGRenderer, update_timers_and_animations}, + platform::{WindowAdapter, femtovg_renderer::FemtoVGRenderer, set_platform, update_timers_and_animations}, }; use slint_interpreter::ComponentInstance; use smithay_client_toolkit::reexports::calloop::{ @@ -30,171 +34,274 @@ use smithay_client_toolkit::reexports::calloop::{ }; use std::rc::Rc; use wayland_client::{ - Connection, EventQueue, Proxy, - protocol::{wl_display::WlDisplay, wl_surface::WlSurface}, + Connection, EventQueue, Proxy, QueueHandle, + backend::ObjectId, + protocol::{wl_display::WlDisplay, wl_pointer::WlPointer, wl_surface::WlSurface}, }; +type PopupManagersAndSurfaces = (Vec>, Vec>); + +struct OutputSetup { + output_id: ObjectId, + main_surface_id: ObjectId, + window: Rc, + builder: WindowStateBuilder, +} + pub struct WaylandWindowingSystem { - state: WindowState, + state: AppState, connection: Rc, - event_queue: EventQueue, - event_loop: EventLoop<'static, WindowState>, - popup_manager: Rc, + event_queue: EventQueue, + event_loop: EventLoop<'static, AppState>, } impl WaylandWindowingSystem { - pub fn new(config: WaylandWindowConfig) -> Result { + pub fn new(config: &WaylandWindowConfig) -> Result { info!("Initializing WindowingSystem"); - let (connection, event_queue) = Self::init_wayland_connection()?; - let (state, global_ctx, platform) = Self::init_state(config, &connection, &event_queue)?; + let (connection, mut event_queue) = Self::init_wayland_connection()?; let event_loop = EventLoop::try_new().map_err(|e| EventLoopError::Creation { source: e })?; - let popup_context = PopupContext::new( - global_ctx.compositor, - global_ctx.xdg_wm_base, - global_ctx.seat, - global_ctx.fractional_scale_manager, - global_ctx.viewporter, - connection.display(), - Rc::clone(&connection), - ); - - let popup_manager = Rc::new(PopupManager::new( - popup_context, - Rc::clone(state.display_metrics()), - )); - let shared_serial = Rc::new(SharedPointerSerial::new()); - - Self::setup_popup_creator( - &popup_manager, - &platform, - &state, - &event_queue, - &shared_serial, - ); + let state = Self::init_state(config, &connection, &mut event_queue)?; Ok(Self { state, connection, event_queue, event_loop, - popup_manager, - }) - .map(|mut system| { - system - .state - .set_popup_manager(Rc::clone(&system.popup_manager)); - system.state.set_shared_pointer_serial(shared_serial); - system }) } - fn init_wayland_connection() -> Result<(Rc, EventQueue)> { + fn init_wayland_connection() -> Result<(Rc, EventQueue)> { let connection = Rc::new(Connection::connect_to_env()?); let event_queue = connection.new_event_queue(); Ok((connection, event_queue)) } - fn init_state( - config: WaylandWindowConfig, - connection: &Connection, - event_queue: &EventQueue, - ) -> Result<(WindowState, GlobalContext, Rc)> { - let global_ctx = GlobalContext::initialize(connection, &event_queue.handle())?; - - let layer_surface_config = LayerSurfaceConfig { + fn create_layer_surface_config(config: &WaylandWindowConfig) -> LayerSurfaceConfig { + LayerSurfaceConfig { anchor: config.anchor, margin: config.margin, exclusive_zone: config.exclusive_zone, keyboard_interactivity: config.keyboard_interactivity, height: config.height, - }; - - let setup_params = SurfaceSetupParams { - compositor: &global_ctx.compositor, - output: &global_ctx.output, - layer_shell: &global_ctx.layer_shell, - fractional_scale_manager: global_ctx.fractional_scale_manager.as_ref(), - viewporter: global_ctx.viewporter.as_ref(), - queue_handle: &event_queue.handle(), - layer: config.layer, - namespace: config.namespace.clone(), - }; - - let surface_ctx = SurfaceCtx::setup(&setup_params, &layer_surface_config); - - let window = - Self::initialize_renderer(&surface_ctx.surface, &connection.display(), &config)?; - - let pointer = Rc::new(global_ctx.seat.get_pointer(&event_queue.handle(), ())); - - let mut builder = WindowStateBuilder::new() - .with_component_definition(config.component_definition) - .with_compilation_result(config.compilation_result) - .with_surface(Rc::clone(&surface_ctx.surface)) - .with_layer_surface(Rc::clone(&surface_ctx.layer_surface)) - .with_scale_factor(config.scale_factor) - .with_height(config.height) - .with_exclusive_zone(config.exclusive_zone) - .with_connection(Rc::new(connection.clone())) - .with_pointer(Rc::clone(&pointer)) - .with_window(window); - - if let Some(fs) = &surface_ctx.fractional_scale { - builder = builder.with_fractional_scale(Rc::clone(fs)); } - - if let Some(vp) = &surface_ctx.viewport { - builder = builder.with_viewport(Rc::clone(vp)); - } - - let (state, platform) = - builder - .build() - .map_err(|e| LayerShikaError::WindowConfiguration { - message: e.to_string(), - })?; - - Ok((state, global_ctx, platform)) } - fn setup_popup_creator( - popup_manager: &Rc, + fn create_output_setups( + config: &WaylandWindowConfig, + global_ctx: &GlobalContext, + connection: &Connection, + event_queue: &mut EventQueue, + pointer: &Rc, + layer_surface_config: &LayerSurfaceConfig, + ) -> Result> { + let mut setups = Vec::new(); + + for output in &global_ctx.outputs { + let output_id = output.id(); + + let setup_params = SurfaceSetupParams { + compositor: &global_ctx.compositor, + output, + layer_shell: &global_ctx.layer_shell, + fractional_scale_manager: global_ctx.fractional_scale_manager.as_ref(), + viewporter: global_ctx.viewporter.as_ref(), + queue_handle: &event_queue.handle(), + layer: config.layer, + namespace: config.namespace.clone(), + }; + + let surface_ctx = SurfaceCtx::setup(&setup_params, layer_surface_config); + let main_surface_id = surface_ctx.surface.id(); + + let window = + Self::initialize_renderer(&surface_ctx.surface, &connection.display(), config)?; + + let mut builder = WindowStateBuilder::new() + .with_component_definition(config.component_definition.clone()) + .with_compilation_result(config.compilation_result.clone()) + .with_surface(Rc::clone(&surface_ctx.surface)) + .with_layer_surface(Rc::clone(&surface_ctx.layer_surface)) + .with_scale_factor(config.scale_factor) + .with_height(config.height) + .with_exclusive_zone(config.exclusive_zone) + .with_connection(Rc::new(connection.clone())) + .with_pointer(Rc::clone(pointer)) + .with_window(Rc::clone(&window)); + + if let Some(fs) = &surface_ctx.fractional_scale { + builder = builder.with_fractional_scale(Rc::clone(fs)); + } + + if let Some(vp) = &surface_ctx.viewport { + builder = builder.with_viewport(Rc::clone(vp)); + } + + setups.push(OutputSetup { + output_id, + main_surface_id, + window, + builder, + }); + } + + Ok(setups) + } + + fn setup_platform(setups: &[OutputSetup]) -> Result> { + let first_setup = setups + .first() + .ok_or_else(|| LayerShikaError::InvalidInput { + message: "No outputs available".into(), + })?; + + let platform = CustomSlintPlatform::new(&first_setup.window); + + for setup in setups.iter().skip(1) { + platform.add_window(Rc::clone(&setup.window)); + } + + set_platform(Box::new(PlatformWrapper(Rc::clone(&platform)))) + .map_err(|e| LayerShikaError::PlatformSetup { source: e })?; + + Ok(platform) + } + + fn create_window_states( + setups: Vec, + popup_context: &PopupContext, + shared_serial: &Rc, + app_state: &mut AppState, + ) -> Result { + let mut popup_managers = Vec::new(); + let mut layer_surfaces = Vec::new(); + + for setup in setups { + let mut per_output_window = WindowState::new(setup.builder).map_err(|e| { + LayerShikaError::WindowConfiguration { + message: e.to_string(), + } + })?; + + let popup_manager = Rc::new(PopupManager::new( + popup_context.clone(), + Rc::clone(per_output_window.display_metrics()), + )); + + per_output_window.set_popup_manager(Rc::clone(&popup_manager)); + per_output_window.set_shared_pointer_serial(Rc::clone(shared_serial)); + + popup_managers.push(Rc::clone(&popup_manager)); + layer_surfaces.push(per_output_window.layer_surface()); + + app_state.add_output(setup.output_id, setup.main_surface_id, per_output_window); + } + + Ok((popup_managers, layer_surfaces)) + } + + fn init_state( + config: &WaylandWindowConfig, + connection: &Connection, + event_queue: &mut EventQueue, + ) -> Result { + let global_ctx = GlobalContext::initialize(connection, &event_queue.handle())?; + let layer_surface_config = Self::create_layer_surface_config(config); + + let pointer = Rc::new(global_ctx.seat.get_pointer(&event_queue.handle(), ())); + let shared_serial = Rc::new(SharedPointerSerial::new()); + + let mut app_state = AppState::new( + ManagedWlPointer::new(Rc::clone(&pointer), Rc::new(connection.clone())), + Rc::clone(&shared_serial), + ); + + let popup_context = PopupContext::new( + global_ctx.compositor.clone(), + global_ctx.xdg_wm_base.clone(), + global_ctx.seat.clone(), + global_ctx.fractional_scale_manager.clone(), + global_ctx.viewporter.clone(), + connection.display(), + Rc::new(connection.clone()), + ); + + let setups = Self::create_output_setups( + config, + &global_ctx, + connection, + event_queue, + &pointer, + &layer_surface_config, + )?; + + let platform = Self::setup_platform(&setups)?; + + let (popup_managers, layer_surfaces) = + Self::create_window_states(setups, &popup_context, &shared_serial, &mut app_state)?; + + Self::setup_shared_popup_creator( + popup_managers, + layer_surfaces, + &platform, + &event_queue.handle(), + &shared_serial, + ); + + Ok(app_state) + } + + fn setup_shared_popup_creator( + popup_managers: Vec>, + layer_surfaces: Vec>, platform: &Rc, - state: &WindowState, - event_queue: &EventQueue, + queue_handle: &QueueHandle, shared_serial: &Rc, ) { - if !popup_manager.has_xdg_shell() { + let Some(first_manager) = popup_managers.first() else { + info!("No popup managers available"); + return; + }; + + if !first_manager.has_xdg_shell() { info!("xdg-shell not available, popups will not be supported"); return; } - info!("Setting up popup creator with xdg-shell support"); + info!( + "Setting up shared popup creator for {} output(s)", + popup_managers.len() + ); - let popup_manager_clone = Rc::clone(popup_manager); - let layer_surface = state.layer_surface(); - let queue_handle = event_queue.handle(); + let queue_handle_clone = queue_handle.clone(); let serial_holder = Rc::clone(shared_serial); platform.set_popup_creator(move || { - info!("Popup creator called! Creating popup window..."); + info!("Popup creator called! Searching for pending popup..."); let serial = serial_holder.get(); - let popup_window = popup_manager_clone - .create_pending_popup(&queue_handle, &layer_surface, serial) - .map_err(|e| PlatformError::Other(format!("Failed to create popup: {e}")))?; + for (idx, (popup_manager, layer_surface)) in + popup_managers.iter().zip(layer_surfaces.iter()).enumerate() + { + if popup_manager.has_pending_popup() { + info!("Found pending popup in output #{}", idx); - let result = Ok(popup_window as Rc); + let popup_window = popup_manager + .create_pending_popup(&queue_handle_clone, layer_surface, serial) + .map_err(|e| { + PlatformError::Other(format!("Failed to create popup: {e}")) + })?; - match &result { - Ok(_) => info!("Popup created successfully"), - Err(e) => info!("Popup creation failed: {e:?}"), + info!("Popup created successfully for output #{}", idx); + return Ok(popup_window as Rc); + } } - result + Err(PlatformError::Other( + "No pending popup request found in any output".into(), + )) }); } @@ -222,7 +329,7 @@ impl WaylandWindowingSystem { Ok(femtovg_window) } - pub fn event_loop_handle(&self) -> LoopHandle<'static, WindowState> { + pub fn event_loop_handle(&self) -> LoopHandle<'static, AppState> { self.event_loop.handle() } @@ -236,25 +343,25 @@ impl WaylandWindowingSystem { .map_err(|e| LayerShikaError::WaylandProtocol { source: e })?; update_timers_and_animations(); - self.state - .window() - .render_frame_if_dirty() - .map_err(|e| RenderingError::Operation { - message: e.to_string(), - })?; + + for window in self.state.all_outputs() { + window + .window() + .render_frame_if_dirty() + .map_err(|e| RenderingError::Operation { + message: e.to_string(), + })?; + } } self.setup_wayland_event_source()?; let event_queue = &mut self.event_queue; let connection = &self.connection; - let popup_manager = Rc::clone(&self.popup_manager); self.event_loop .run(None, &mut self.state, move |shared_data| { - if let Err(e) = - Self::process_events(connection, event_queue, shared_data, &popup_manager) - { + if let Err(e) = Self::process_events(connection, event_queue, shared_data) { error!("Error processing events: {e}"); } }) @@ -281,9 +388,8 @@ impl WaylandWindowingSystem { fn process_events( connection: &Connection, - event_queue: &mut EventQueue, - shared_data: &mut WindowState, - popup_manager: &PopupManager, + event_queue: &mut EventQueue, + shared_data: &mut AppState, ) -> Result<()> { if let Some(guard) = event_queue.prepare_read() { guard @@ -295,18 +401,22 @@ impl WaylandWindowingSystem { update_timers_and_animations(); - shared_data - .window() - .render_frame_if_dirty() - .map_err(|e| RenderingError::Operation { - message: e.to_string(), - })?; + for window in shared_data.all_outputs() { + window + .window() + .render_frame_if_dirty() + .map_err(|e| RenderingError::Operation { + message: e.to_string(), + })?; - popup_manager - .render_popups() - .map_err(|e| RenderingError::Operation { - message: e.to_string(), - })?; + if let Some(popup_manager) = window.popup_manager() { + popup_manager + .render_popups() + .map_err(|e| RenderingError::Operation { + message: e.to_string(), + })?; + } + } connection .flush() @@ -315,11 +425,24 @@ impl WaylandWindowingSystem { Ok(()) } - pub const fn component_instance(&self) -> &ComponentInstance { - self.state.component_instance() + pub fn component_instance(&self) -> Result<&ComponentInstance> { + self.state + .primary_output() + .ok_or_else(|| LayerShikaError::InvalidInput { + message: "No outputs available".into(), + }) + .map(WindowState::component_instance) } - pub const fn state(&self) -> &WindowState { + pub fn state(&self) -> Result<&WindowState> { + self.state + .primary_output() + .ok_or_else(|| LayerShikaError::InvalidInput { + message: "No outputs available".into(), + }) + } + + pub fn app_state(&self) -> &AppState { &self.state } } diff --git a/crates/adapters/src/wayland/surfaces/app_state.rs b/crates/adapters/src/wayland/surfaces/app_state.rs new file mode 100644 index 0000000..a6e4eb3 --- /dev/null +++ b/crates/adapters/src/wayland/surfaces/app_state.rs @@ -0,0 +1,161 @@ +use super::surface_state::WindowState; +use super::event_context::SharedPointerSerial; +use crate::wayland::managed_proxies::ManagedWlPointer; +use crate::wayland::outputs::OutputKey; +use std::collections::HashMap; +use std::rc::Rc; +use wayland_client::backend::ObjectId; +use wayland_client::Proxy; + +pub type PerOutputWindow = WindowState; + +pub struct AppState { + outputs: HashMap, + surface_to_output: HashMap, + output_to_key: HashMap, + _pointer: ManagedWlPointer, + shared_pointer_serial: Rc, + active_output: Option, +} + +impl AppState { + pub fn new(pointer: ManagedWlPointer, shared_serial: Rc) -> Self { + Self { + outputs: HashMap::new(), + surface_to_output: HashMap::new(), + output_to_key: HashMap::new(), + _pointer: pointer, + shared_pointer_serial: shared_serial, + active_output: None, + } + } + + pub fn add_output( + &mut self, + output_id: ObjectId, + main_surface_id: ObjectId, + window: PerOutputWindow, + ) { + let key = OutputKey::new(output_id.clone()); + self.output_to_key.insert(output_id, key.clone()); + self.surface_to_output + .insert(main_surface_id, key.clone()); + self.outputs.insert(key, window); + } + + pub fn get_output_by_key(&self, key: &OutputKey) -> Option<&PerOutputWindow> { + self.outputs.get(key) + } + + pub fn get_output_by_key_mut(&mut self, key: &OutputKey) -> Option<&mut PerOutputWindow> { + self.outputs.get_mut(key) + } + + pub fn get_output_by_output_id(&self, output_id: &ObjectId) -> Option<&PerOutputWindow> { + self.output_to_key + .get(output_id) + .and_then(|key| self.outputs.get(key)) + } + + pub fn get_output_by_output_id_mut( + &mut self, + output_id: &ObjectId, + ) -> Option<&mut PerOutputWindow> { + self.output_to_key + .get(output_id) + .and_then(|key| self.outputs.get_mut(key)) + } + + pub fn get_output_by_surface(&self, surface_id: &ObjectId) -> Option<&PerOutputWindow> { + self.surface_to_output + .get(surface_id) + .and_then(|key| self.outputs.get(key)) + } + + pub fn get_output_by_surface_mut( + &mut self, + surface_id: &ObjectId, + ) -> Option<&mut PerOutputWindow> { + self.surface_to_output + .get(surface_id) + .and_then(|key| self.outputs.get_mut(key)) + } + + pub fn get_output_by_layer_surface_mut( + &mut self, + layer_surface_id: &ObjectId, + ) -> Option<&mut PerOutputWindow> { + self.outputs.values_mut().find(|window| { + window.layer_surface().as_ref().id() == *layer_surface_id + }) + } + + pub fn get_key_by_surface(&self, surface_id: &ObjectId) -> Option<&OutputKey> { + self.surface_to_output.get(surface_id) + } + + pub fn get_key_by_output_id(&self, output_id: &ObjectId) -> Option<&OutputKey> { + self.output_to_key.get(output_id) + } + + pub fn register_popup_surface(&mut self, popup_surface_id: ObjectId, output_key: OutputKey) { + self.surface_to_output.insert(popup_surface_id, output_key); + } + + pub fn set_active_output(&mut self, key: Option) { + self.active_output = key; + } + + pub const fn active_output(&self) -> Option<&OutputKey> { + self.active_output.as_ref() + } + + pub fn primary_output(&self) -> Option<&PerOutputWindow> { + self.outputs.values().next() + } + + pub fn all_outputs(&self) -> impl Iterator { + self.outputs.values() + } + + pub fn all_outputs_mut(&mut self) -> impl Iterator { + self.outputs.values_mut() + } + + pub const fn shared_pointer_serial(&self) -> &Rc { + &self.shared_pointer_serial + } + + pub fn find_output_by_popup(&self, popup_surface_id: &ObjectId) -> Option<&PerOutputWindow> { + self.outputs.values().find(|window| { + window + .popup_manager() + .as_ref() + .and_then(|pm| pm.find_by_surface(popup_surface_id)) + .is_some() + }) + } + + pub fn find_output_by_popup_mut( + &mut self, + popup_surface_id: &ObjectId, + ) -> Option<&mut PerOutputWindow> { + self.outputs.values_mut().find(|window| { + window + .popup_manager() + .as_ref() + .and_then(|pm| pm.find_by_surface(popup_surface_id)) + .is_some() + }) + } + + pub fn get_key_by_popup(&self, popup_surface_id: &ObjectId) -> Option { + self.outputs.iter().find_map(|(key, window)| { + window + .popup_manager() + .as_ref() + .and_then(|pm| pm.find_by_surface(popup_surface_id)) + .map(|_| key.clone()) + }) + } +} diff --git a/crates/adapters/src/wayland/surfaces/layer_surface.rs b/crates/adapters/src/wayland/surfaces/layer_surface.rs index 02f118c..f586a46 100644 --- a/crates/adapters/src/wayland/surfaces/layer_surface.rs +++ b/crates/adapters/src/wayland/surfaces/layer_surface.rs @@ -1,4 +1,4 @@ -use crate::wayland::{config::LayerSurfaceConfig, surfaces::surface_state::WindowState}; +use crate::wayland::{config::LayerSurfaceConfig, surfaces::app_state::AppState}; use log::info; use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::{ zwlr_layer_shell_v1::{Layer, ZwlrLayerShellV1}, @@ -23,7 +23,7 @@ pub struct SurfaceSetupParams<'a> { pub layer_shell: &'a ZwlrLayerShellV1, pub fractional_scale_manager: Option<&'a WpFractionalScaleManagerV1>, pub viewporter: Option<&'a WpViewporter>, - pub queue_handle: &'a QueueHandle, + pub queue_handle: &'a QueueHandle, pub layer: Layer, pub namespace: String, } diff --git a/crates/adapters/src/wayland/surfaces/mod.rs b/crates/adapters/src/wayland/surfaces/mod.rs index 48c0fd6..8445cf3 100644 --- a/crates/adapters/src/wayland/surfaces/mod.rs +++ b/crates/adapters/src/wayland/surfaces/mod.rs @@ -1,3 +1,4 @@ +pub mod app_state; pub mod component_state; pub mod dimensions; pub mod display_metrics; diff --git a/crates/adapters/src/wayland/surfaces/popup_manager.rs b/crates/adapters/src/wayland/surfaces/popup_manager.rs index e742e41..35b5544 100644 --- a/crates/adapters/src/wayland/surfaces/popup_manager.rs +++ b/crates/adapters/src/wayland/surfaces/popup_manager.rs @@ -23,8 +23,8 @@ use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1: use wayland_protocols::wp::viewporter::client::wp_viewporter::WpViewporter; use wayland_protocols::xdg::shell::client::xdg_wm_base::XdgWmBase; +use super::app_state::AppState; use super::popup_surface::PopupSurface; -use super::surface_state::WindowState; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ActiveWindow { @@ -66,6 +66,7 @@ pub struct CreatePopupParams { pub grab: bool, } +#[derive(Clone)] pub struct PopupContext { compositor: WlCompositor, xdg_wm_base: Option, @@ -184,6 +185,11 @@ impl PopupManager { .map(|p| (p.id, p.request, p.width, p.height)) } + #[must_use] + pub fn has_pending_popup(&self) -> bool { + self.state.borrow().pending_popup.is_some() + } + #[must_use] pub fn scale_factor(&self) -> f32 { self.scale_factor.get() @@ -225,7 +231,7 @@ impl PopupManager { pub fn create_pending_popup( self: &Rc, - queue_handle: &QueueHandle, + queue_handle: &QueueHandle, parent_layer_surface: &ZwlrLayerSurfaceV1, last_pointer_serial: u32, ) -> Result> { @@ -250,7 +256,7 @@ impl PopupManager { fn create_popup_internal( self: &Rc, - queue_handle: &QueueHandle, + queue_handle: &QueueHandle, parent_layer_surface: &ZwlrLayerSurfaceV1, params: CreatePopupParams, request: PopupRequest, diff --git a/crates/adapters/src/wayland/surfaces/popup_surface.rs b/crates/adapters/src/wayland/surfaces/popup_surface.rs index c0206b3..8619465 100644 --- a/crates/adapters/src/wayland/surfaces/popup_surface.rs +++ b/crates/adapters/src/wayland/surfaces/popup_surface.rs @@ -21,7 +21,7 @@ use wayland_protocols::xdg::shell::client::{ xdg_wm_base::XdgWmBase, }; -use super::surface_state::WindowState; +use super::app_state::AppState; pub struct PopupSurfaceParams<'a> { pub compositor: &'a WlCompositor, @@ -29,7 +29,7 @@ pub struct PopupSurfaceParams<'a> { pub parent_layer_surface: &'a ZwlrLayerSurfaceV1, pub fractional_scale_manager: Option<&'a WpFractionalScaleManagerV1>, pub viewporter: Option<&'a WpViewporter>, - pub queue_handle: &'a QueueHandle, + pub queue_handle: &'a QueueHandle, pub popup_config: PopupConfig, pub physical_size: PhysicalSize, pub scale_factor: f32, diff --git a/crates/adapters/src/wayland/surfaces/surface_builder.rs b/crates/adapters/src/wayland/surfaces/surface_builder.rs index e623f45..d4f4f5a 100644 --- a/crates/adapters/src/wayland/surfaces/surface_builder.rs +++ b/crates/adapters/src/wayland/surfaces/surface_builder.rs @@ -15,7 +15,7 @@ use crate::rendering::slint_integration::platform::CustomSlintPlatform; use super::surface_state::WindowState; -struct PlatformWrapper(Rc); +pub struct PlatformWrapper(pub Rc); impl Platform for PlatformWrapper { fn create_window_adapter(&self) -> StdResult, PlatformError> { @@ -107,7 +107,10 @@ impl WindowStateBuilder { } #[must_use] - pub fn with_compilation_result(mut self, compilation_result: Option>) -> Self { + pub fn with_compilation_result( + mut self, + compilation_result: Option>, + ) -> Self { self.compilation_result = compilation_result; self } diff --git a/crates/composition/src/system.rs b/crates/composition/src/system.rs index 8204738..f931875 100644 --- a/crates/composition/src/system.rs +++ b/crates/composition/src/system.rs @@ -9,7 +9,9 @@ use layer_shika_adapters::platform::slint::ComponentHandle; use layer_shika_adapters::platform::slint_interpreter::{ CompilationResult, ComponentDefinition, ComponentInstance, }; -use layer_shika_adapters::{PopupManager, WaylandWindowConfig, WindowState, WindowingSystemFacade}; +use layer_shika_adapters::{ + AppState, PopupManager, WaylandWindowConfig, WindowState, WindowingSystemFacade, +}; use layer_shika_domain::config::WindowConfig; use layer_shika_domain::errors::DomainError; use layer_shika_domain::value_objects::popup_positioning_mode::PopupPositioningMode; @@ -49,8 +51,8 @@ impl EventLoopHandle { let loop_handle = system.borrow().inner_ref().event_loop_handle(); loop_handle - .insert_source(source, move |event, metadata, window_state| { - let runtime_state = RuntimeState { window_state }; + .insert_source(source, move |event, metadata, app_state| { + let runtime_state = RuntimeState { app_state }; callback(event, metadata, runtime_state) }) .map_err(|e| { @@ -120,22 +122,42 @@ impl EventLoopHandle { } pub struct RuntimeState<'a> { - window_state: &'a mut WindowState, + app_state: &'a mut AppState, } impl RuntimeState<'_> { #[must_use] - pub fn component_instance(&self) -> &ComponentInstance { - self.window_state.component_instance() + pub fn component_instance(&self) -> Option<&ComponentInstance> { + self.app_state + .primary_output() + .map(WindowState::component_instance) + } + + pub fn all_component_instances(&self) -> impl Iterator { + self.app_state + .all_outputs() + .map(WindowState::component_instance) + } + + fn active_or_primary_output(&self) -> Option<&WindowState> { + self.app_state + .active_output() + .and_then(|key| self.app_state.get_output_by_key(key)) + .or_else(|| self.app_state.primary_output()) } pub fn render_frame_if_dirty(&mut self) -> Result<()> { - Ok(self.window_state.render_frame_if_dirty()?) + for window in self.app_state.all_outputs() { + window.render_frame_if_dirty()?; + } + Ok(()) } #[must_use] pub fn compilation_result(&self) -> Option> { - self.window_state.compilation_result() + self.app_state + .primary_output() + .and_then(WindowState::compilation_result) } pub fn show_popup( @@ -162,7 +184,19 @@ impl RuntimeState<'_> { self.close_current_popup()?; - let popup_manager = self.window_state.popup_manager().ok_or_else(|| { + let is_using_active = self.app_state.active_output().is_some(); + let active_window = self.active_or_primary_output().ok_or_else(|| { + Error::Domain(DomainError::Configuration { + message: "No active or primary output available".to_string(), + }) + })?; + + log::info!( + "Creating popup on {} output", + if is_using_active { "active" } else { "primary" } + ); + + let popup_manager = active_window.popup_manager().ok_or_else(|| { Error::Domain(DomainError::Configuration { message: "No popup manager available".to_string(), }) @@ -209,15 +243,19 @@ impl RuntimeState<'_> { } pub fn close_popup(&mut self, handle: PopupHandle) -> Result<()> { - if let Some(popup_manager) = self.window_state.popup_manager() { - popup_manager.close(handle)?; + if let Some(active_window) = self.active_or_primary_output() { + if let Some(popup_manager) = active_window.popup_manager() { + popup_manager.close(handle)?; + } } Ok(()) } pub fn close_current_popup(&mut self) -> Result<()> { - if let Some(popup_manager) = self.window_state.popup_manager() { - popup_manager.close_current_popup(); + if let Some(active_window) = self.active_or_primary_output() { + if let Some(popup_manager) = active_window.popup_manager() { + popup_manager.close_current_popup(); + } } Ok(()) } @@ -229,7 +267,13 @@ impl RuntimeState<'_> { height: f32, resize_sender: Option>, ) -> Result<()> { - let popup_manager = self.window_state.popup_manager().ok_or_else(|| { + let active_window = self.active_or_primary_output().ok_or_else(|| { + Error::Domain(DomainError::Configuration { + message: "No active or primary output available".to_string(), + }) + })?; + + let popup_manager = active_window.popup_manager().ok_or_else(|| { Error::Domain(DomainError::Configuration { message: "No popup manager available".to_string(), }) @@ -322,7 +366,7 @@ impl WindowingSystem { compilation_result, config, ); - let inner = layer_shika_adapters::WaylandWindowingSystem::new(wayland_config)?; + let inner = layer_shika_adapters::WaylandWindowingSystem::new(&wayland_config)?; let facade = WindowingSystemFacade::new(inner); let inner_rc = Rc::new(RefCell::new(facade)); @@ -339,7 +383,7 @@ impl WindowingSystem { }; system.setup_popup_command_handler(receiver)?; - system.register_popup_callbacks()?; + system.register_popup_callbacks(); Ok(system) } @@ -349,9 +393,9 @@ impl WindowingSystem { let sender_for_handler = self.popup_command_sender.clone(); loop_handle - .insert_source(receiver, move |event, (), window_state| { + .insert_source(receiver, move |event, (), app_state| { if let channel::Event::Msg(command) = event { - let mut runtime_state = RuntimeState { window_state }; + let mut runtime_state = RuntimeState { app_state }; match command { PopupCommand::Show(request) => { @@ -395,11 +439,15 @@ impl WindowingSystem { Ok(()) } - fn register_popup_callbacks(&self) -> Result<()> { - self.with_component_instance(|component_instance| { - self.callback_contract + fn register_popup_callbacks(&self) { + self.with_all_component_instances(|component_instance| { + if let Err(e) = self + .callback_contract .register_on_main_component(component_instance) - }) + { + log::error!("Failed to register popup callbacks on output: {}", e); + } + }); } #[must_use] @@ -434,10 +482,23 @@ impl WindowingSystem { Ok(()) } - pub fn with_component_instance(&self, f: F) -> R + pub fn with_component_instance(&self, f: F) -> Result where F: FnOnce(&ComponentInstance) -> R, { - f(self.inner.borrow().component_instance()) + let facade = self.inner.borrow(); + let instance = facade.component_instance()?; + Ok(f(instance)) + } + + pub fn with_all_component_instances(&self, mut f: F) + where + F: FnMut(&ComponentInstance), + { + let facade = self.inner.borrow(); + let system = facade.inner_ref(); + for window in system.app_state().all_outputs() { + f(window.component_instance()); + } } }