use crate::wayland::{ config::{LayerSurfaceParams, WaylandWindowConfig}, globals::context::GlobalContext, surfaces::layer_surface::{SurfaceCtx, SurfaceSetupParams}, surfaces::popup_manager::{CreatePopupParams, PopupContext, PopupManager}, surfaces::{ surface_builder::WindowStateBuilder, surface_state::{SharedPointerSerial, WindowState}, }, }; use crate::{ errors::{EventLoopError, LayerShikaError, RenderingError, Result}, rendering::{ egl::context::EGLContext, femtovg::main_window::FemtoVGWindow, slint_integration::platform::CustomSlintPlatform, }, }; 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::popup_positioning_mode::PopupPositioningMode; use log::{error, info}; use slint::{ LogicalPosition, PhysicalSize, PlatformError, WindowPosition, platform::{WindowAdapter, femtovg_renderer::FemtoVGRenderer, update_timers_and_animations}, }; use slint_interpreter::ComponentInstance; use smithay_client_toolkit::reexports::calloop::{ EventLoop, Interest, LoopHandle, Mode, PostAction, generic::Generic, }; use std::rc::Rc; use wayland_client::{ Connection, EventQueue, Proxy, protocol::{wl_display::WlDisplay, wl_surface::WlSurface}, }; pub struct WaylandWindowingSystem { state: WindowState, connection: Rc, event_queue: EventQueue, event_loop: EventLoop<'static, WindowState>, popup_manager: Rc, } impl WaylandWindowingSystem { 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 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, state.scale_factor())); let shared_serial = Rc::new(SharedPointerSerial::new()); Self::setup_popup_creator( &popup_manager, &platform, &state, &event_queue, &shared_serial, ); 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)> { 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_params = LayerSurfaceParams { 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_params); 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, platform: &Rc, state: &WindowState, event_queue: &EventQueue, shared_serial: &Rc, ) { if !popup_manager.has_xdg_shell() { info!("xdg-shell not available, popups will not be supported"); return; } info!("Setting up popup creator with xdg-shell support"); let popup_manager_clone = Rc::clone(popup_manager); let layer_surface = state.layer_surface(); let queue_handle = event_queue.handle(); let serial_holder = Rc::clone(shared_serial); platform.set_popup_creator(move || { info!("Popup creator called! Creating popup window..."); let serial = serial_holder.get(); let params = if let Some((request, width, height)) = popup_manager_clone.take_pending_popup_request() { log::info!( "Using popup request: component='{}', position=({}, {}), size={}x{}, mode={:?}", request.component, request.at.position().0, request.at.position().1, width, height, request.mode ); CreatePopupParams { last_pointer_serial: serial, reference_x: request.at.position().0, reference_y: request.at.position().1, width, height, positioning_mode: request.mode, } } else { let output_size = popup_manager_clone.output_size(); #[allow(clippy::cast_precision_loss)] let default_width = output_size.width as f32; #[allow(clippy::cast_precision_loss)] let default_height = output_size.height as f32; log::warn!("No popup request provided, using output size ({default_width}x{default_height}) as defaults"); CreatePopupParams { last_pointer_serial: serial, reference_x: 0.0, reference_y: 0.0, width: default_width, height: default_height, positioning_mode: PopupPositioningMode::TopLeft, } }; let popup_window = popup_manager_clone .create_popup( &queue_handle, &layer_surface, params, ) .map_err(|e| PlatformError::Other(format!("Failed to create popup: {e}")))?; let result = Ok(popup_window as Rc); match &result { Ok(_) => info!("Popup created successfully"), Err(e) => info!("Popup creation failed: {e:?}"), } result }); } fn initialize_renderer( surface: &Rc, display: &WlDisplay, config: &WaylandWindowConfig, ) -> Result> { let init_size = PhysicalSize::new(1, 1); let context = EGLContext::builder() .with_display_id(display.id()) .with_surface_id(surface.id()) .with_size(init_size) .build()?; let renderer = FemtoVGRenderer::new(context) .map_err(|e| LayerShikaError::FemtoVGRendererCreation { source: e })?; let femtovg_window = FemtoVGWindow::new(renderer); femtovg_window.set_size(slint::WindowSize::Physical(init_size)); femtovg_window.set_scale_factor(config.scale_factor); femtovg_window.set_position(WindowPosition::Logical(LogicalPosition::new(0., 0.))); Ok(femtovg_window) } pub fn event_loop_handle(&self) -> LoopHandle<'static, WindowState> { self.event_loop.handle() } pub fn run(&mut self) -> Result<()> { info!("Starting WindowingSystem main loop"); info!("Processing initial Wayland configuration events"); while self.event_queue.blocking_dispatch(&mut self.state)? > 0 { self.connection .flush() .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(), })?; } 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) { error!("Error processing events: {e}"); } }) .map_err(|e| EventLoopError::Execution { source: e })?; Ok(()) } fn setup_wayland_event_source(&self) -> Result<()> { let connection = Rc::clone(&self.connection); self.event_loop .handle() .insert_source( Generic::new(connection, Interest::READ, Mode::Level), move |_, _connection, _shared_data| Ok(PostAction::Continue), ) .map_err(|e| EventLoopError::InsertSource { message: format!("{e:?}"), })?; Ok(()) } fn process_events( connection: &Connection, event_queue: &mut EventQueue, shared_data: &mut WindowState, popup_manager: &PopupManager, ) -> Result<()> { if let Some(guard) = event_queue.prepare_read() { guard .read() .map_err(|e| LayerShikaError::WaylandProtocol { source: e })?; } event_queue.dispatch_pending(shared_data)?; update_timers_and_animations(); shared_data .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(), })?; connection .flush() .map_err(|e| LayerShikaError::WaylandProtocol { source: e })?; Ok(()) } pub const fn component_instance(&self) -> &ComponentInstance { self.state.component_instance() } pub const fn state(&self) -> &WindowState { &self.state } } impl WindowingSystemPort for WaylandWindowingSystem { fn run(&mut self) -> CoreResult<(), DomainError> { WaylandWindowingSystem::run(self).map_err(|e| DomainError::Adapter { source: Box::new(e), }) } }