diff --git a/src/windowing/globals.rs b/src/windowing/globals.rs new file mode 100644 index 0000000..7199e9f --- /dev/null +++ b/src/windowing/globals.rs @@ -0,0 +1,66 @@ +use crate::{bind_globals, errors::LayerShikaError}; +use log::info; +use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_shell_v1::ZwlrLayerShellV1; +use wayland_client::{ + globals::registry_queue_init, + protocol::{wl_compositor::WlCompositor, wl_output::WlOutput, wl_seat::WlSeat}, + Connection, QueueHandle, +}; +use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1; +use wayland_protocols::wp::viewporter::client::wp_viewporter::WpViewporter; + +use super::state::WindowState; + +pub struct GlobalCtx { + pub compositor: WlCompositor, + pub output: WlOutput, + pub layer_shell: ZwlrLayerShellV1, + pub seat: WlSeat, + pub fractional_scale_manager: Option, + pub viewporter: Option, +} + +impl GlobalCtx { + pub fn initialize( + connection: &Connection, + queue_handle: &QueueHandle, + ) -> Result { + let global_list = registry_queue_init::(connection) + .map(|(global_list, _)| global_list) + .map_err(|e| LayerShikaError::GlobalInitialization(e.to_string()))?; + + let (compositor, output, 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 fractional_scale_manager = global_list + .bind::(queue_handle, 1..=1, ()) + .ok(); + + let viewporter = global_list + .bind::(queue_handle, 1..=1, ()) + .ok(); + + if fractional_scale_manager.is_none() { + info!("Fractional scale protocol not available, using integer scaling"); + } + + if viewporter.is_none() { + info!("Viewporter protocol not available"); + } + + Ok(Self { + compositor, + output, + layer_shell, + seat, + fractional_scale_manager, + viewporter, + }) + } +} diff --git a/src/windowing/mod.rs b/src/windowing/mod.rs index 3231852..4e3d4aa 100644 --- a/src/windowing/mod.rs +++ b/src/windowing/mod.rs @@ -1,352 +1,9 @@ -use self::state::WindowState; -use crate::{ - bind_globals, - errors::{LayerShikaError, Result}, - rendering::{egl_context::EGLContext, femtovg_window::FemtoVGWindow}, -}; -use config::{LayerSurfaceParams, WindowConfig}; -use log::{debug, error, info}; -use slint::{ - platform::{femtovg_renderer::FemtoVGRenderer, update_timers_and_animations}, - LogicalPosition, PhysicalSize, -}; -use slint_interpreter::ComponentInstance; -use smithay_client_toolkit::reexports::{ - calloop::{generic::Generic, EventLoop, Interest, LoopHandle, Mode, PostAction}, - protocols_wlr::layer_shell::v1::client::{ - zwlr_layer_shell_v1::ZwlrLayerShellV1, zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, - }, -}; -use state::builder::WindowStateBuilder; -use std::rc::Rc; -use wayland_client::{ - globals::registry_queue_init, - protocol::{ - wl_compositor::WlCompositor, wl_display::WlDisplay, wl_output::WlOutput, wl_seat::WlSeat, - wl_surface::WlSurface, - }, - Connection, EventQueue, Proxy, QueueHandle, -}; -use wayland_protocols::wp::fractional_scale::v1::client::{ - wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1, - wp_fractional_scale_v1::WpFractionalScaleV1, -}; -use wayland_protocols::wp::viewporter::client::{ - wp_viewport::WpViewport, wp_viewporter::WpViewporter, -}; - pub mod builder; mod config; +mod globals; mod macros; mod state; +mod surface; +mod system; -pub struct GlobalCtx { - pub compositor: WlCompositor, - pub output: WlOutput, - pub layer_shell: ZwlrLayerShellV1, - pub seat: WlSeat, - pub fractional_scale_manager: Option, - pub viewporter: Option, -} - -pub struct SurfaceCtx { - pub surface: Rc, - pub layer_surface: Rc, - pub fractional_scale: Option>, - pub viewport: Option>, -} - -pub struct WindowingSystem { - state: WindowState, - connection: Rc, - event_queue: EventQueue, - event_loop: EventLoop<'static, WindowState>, -} - -impl WindowingSystem { - fn new(config: WindowConfig) -> Result { - info!("Initializing WindowingSystem"); - let connection = - Rc::new(Connection::connect_to_env().map_err(LayerShikaError::WaylandConnection)?); - let event_queue = connection.new_event_queue(); - - let global_ctx = Self::initialize_globals(&connection, &event_queue.handle()) - .map_err(|e| LayerShikaError::GlobalInitialization(e.to_string()))?; - - let output_ref = &global_ctx.output; - let surface_ctx = Self::setup_surface( - &global_ctx.compositor, - output_ref, - &global_ctx.layer_shell, - global_ctx.fractional_scale_manager.as_ref(), - global_ctx.viewporter.as_ref(), - &event_queue.handle(), - &config, - ); - - let pointer = Rc::new(global_ctx.seat.get_pointer(&event_queue.handle(), ())); - let output = Rc::new(global_ctx.output); - let window = - Self::initialize_renderer(&surface_ctx.surface, &connection.display(), &config) - .map_err(|e| LayerShikaError::EGLContextCreation(e.to_string()))?; - - let mut builder = WindowStateBuilder::new() - .with_component_definition(config.component_definition) - .with_surface(Rc::clone(&surface_ctx.surface)) - .with_layer_surface(Rc::clone(&surface_ctx.layer_surface)) - .with_pointer(Rc::clone(&pointer)) - .with_output(Rc::clone(&output)) - .with_scale_factor(config.scale_factor) - .with_height(config.height) - .with_exclusive_zone(config.exclusive_zone) - .with_window(window); - - if let Some(fs) = surface_ctx.fractional_scale { - builder = builder.with_fractional_scale(fs); - } - - if let Some(vp) = surface_ctx.viewport { - builder = builder.with_viewport(vp); - } - - let state = builder - .build() - .map_err(|e| LayerShikaError::WindowConfiguration(e.to_string()))?; - - let event_loop = - EventLoop::try_new().map_err(|e| LayerShikaError::EventLoop(e.to_string()))?; - - Ok(Self { - state, - connection, - event_queue, - event_loop, - }) - } - - fn initialize_globals( - connection: &Connection, - queue_handle: &QueueHandle, - ) -> Result { - let global_list = registry_queue_init::(connection) - .map(|(global_list, _)| global_list) - .map_err(|e| LayerShikaError::GlobalInitialization(e.to_string()))?; - - let (compositor, output, 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 fractional_scale_manager = global_list - .bind::(queue_handle, 1..=1, ()) - .ok(); - - let viewporter = global_list - .bind::(queue_handle, 1..=1, ()) - .ok(); - - if fractional_scale_manager.is_none() { - info!("Fractional scale protocol not available, using integer scaling"); - } - - if viewporter.is_none() { - info!("Viewporter protocol not available"); - } - - Ok(GlobalCtx { - compositor, - output, - layer_shell, - seat, - fractional_scale_manager, - viewporter, - }) - } - - fn setup_surface( - compositor: &WlCompositor, - output: &WlOutput, - layer_shell: &ZwlrLayerShellV1, - fractional_scale_manager: Option<&WpFractionalScaleManagerV1>, - viewporter: Option<&WpViewporter>, - queue_handle: &QueueHandle, - config: &WindowConfig, - ) -> SurfaceCtx { - let surface = Rc::new(compositor.create_surface(queue_handle, ())); - let layer_surface = Rc::new(layer_shell.get_layer_surface( - &surface, - Some(output), - config.layer, - config.namespace.clone(), - queue_handle, - (), - )); - - let fractional_scale = fractional_scale_manager.map(|manager| { - info!("Creating fractional scale object for surface"); - Rc::new(manager.get_fractional_scale(&surface, queue_handle, ())) - }); - - let viewport = viewporter.map(|vp| { - info!("Creating viewport for surface"); - Rc::new(vp.get_viewport(&surface, queue_handle, ())) - }); - - let params = LayerSurfaceParams { - anchor: config.anchor, - margin: config.margin, - exclusive_zone: config.exclusive_zone, - keyboard_interactivity: config.keyboard_interactivity, - height: config.height, - }; - - Self::configure_layer_surface(&layer_surface, &surface, ¶ms); - - surface.set_buffer_scale(1); - - SurfaceCtx { - surface, - layer_surface, - fractional_scale, - viewport, - } - } - - fn configure_layer_surface( - layer_surface: &Rc, - surface: &WlSurface, - params: &LayerSurfaceParams, - ) { - layer_surface.set_anchor(params.anchor); - layer_surface.set_margin( - params.margin.top, - params.margin.right, - params.margin.bottom, - params.margin.left, - ); - - layer_surface.set_exclusive_zone(params.exclusive_zone); - layer_surface.set_keyboard_interactivity(params.keyboard_interactivity); - layer_surface.set_size(1, params.height); - surface.commit(); - } - - fn initialize_renderer( - surface: &Rc, - display: &WlDisplay, - config: &WindowConfig, - ) -> 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() - .map_err(|e| LayerShikaError::EGLContextCreation(e.to_string()))?; - - let renderer = FemtoVGRenderer::new(context) - .map_err(|e| LayerShikaError::FemtoVGRendererCreation(e.to_string()))?; - - 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(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"); - - while self - .event_queue - .blocking_dispatch(&mut self.state) - .map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))? - > 0 - { - self.connection - .flush() - .map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?; - self.state - .window() - .render_frame_if_dirty() - .map_err(|e| LayerShikaError::Rendering(e.to_string()))?; - } - - self.setup_wayland_event_source()?; - - let event_queue = &mut self.event_queue; - let connection = &self.connection; - - self.event_loop - .run(None, &mut self.state, move |shared_data| { - if let Err(e) = Self::process_events(connection, event_queue, shared_data) { - error!("Error processing events: {e}"); - } - }) - .map_err(|e| LayerShikaError::EventLoop(e.to_string())) - } - - fn setup_wayland_event_source(&self) -> Result<()> { - debug!("Setting up Wayland event source"); - - 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| LayerShikaError::EventLoop(e.to_string()))?; - - Ok(()) - } - - fn process_events( - connection: &Connection, - event_queue: &mut EventQueue, - shared_data: &mut WindowState, - ) -> Result<()> { - if let Some(guard) = event_queue.prepare_read() { - guard - .read() - .map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?; - } - connection.flush()?; - - event_queue - .dispatch_pending(shared_data) - .map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?; - - update_timers_and_animations(); - - shared_data - .window() - .render_frame_if_dirty() - .map_err(|e| LayerShikaError::Rendering(e.to_string()))?; - - Ok(()) - } - - pub const fn component_instance(&self) -> &ComponentInstance { - self.state.component_instance() - } - - pub fn window(&self) -> Rc { - self.state.window() - } - - pub const fn state(&self) -> &WindowState { - &self.state - } -} +pub use system::WindowingSystem; diff --git a/src/windowing/surface.rs b/src/windowing/surface.rs new file mode 100644 index 0000000..5cff145 --- /dev/null +++ b/src/windowing/surface.rs @@ -0,0 +1,93 @@ +use super::{config::LayerSurfaceParams, state::WindowState}; +use log::info; +use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::{ + zwlr_layer_shell_v1::{Layer, ZwlrLayerShellV1}, + zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, +}; +use std::rc::Rc; +use wayland_client::{ + protocol::{wl_compositor::WlCompositor, wl_output::WlOutput, wl_surface::WlSurface}, + QueueHandle, +}; +use wayland_protocols::wp::fractional_scale::v1::client::{ + wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1, + wp_fractional_scale_v1::WpFractionalScaleV1, +}; +use wayland_protocols::wp::viewporter::client::{ + wp_viewport::WpViewport, wp_viewporter::WpViewporter, +}; + +pub struct SurfaceSetupParams<'a> { + pub compositor: &'a WlCompositor, + pub output: &'a WlOutput, + pub layer_shell: &'a ZwlrLayerShellV1, + pub fractional_scale_manager: Option<&'a WpFractionalScaleManagerV1>, + pub viewporter: Option<&'a WpViewporter>, + pub queue_handle: &'a QueueHandle, + pub layer: Layer, + pub namespace: String, +} + +pub struct SurfaceCtx { + pub surface: Rc, + pub layer_surface: Rc, + pub fractional_scale: Option>, + pub viewport: Option>, +} + +impl SurfaceCtx { + pub fn setup(setup_params: &SurfaceSetupParams<'_>, params: &LayerSurfaceParams) -> Self { + let surface = Rc::new( + setup_params + .compositor + .create_surface(setup_params.queue_handle, ()), + ); + let layer_surface = Rc::new(setup_params.layer_shell.get_layer_surface( + &surface, + Some(setup_params.output), + setup_params.layer, + setup_params.namespace.clone(), + setup_params.queue_handle, + (), + )); + + let fractional_scale = setup_params.fractional_scale_manager.map(|manager| { + info!("Creating fractional scale object for surface"); + Rc::new(manager.get_fractional_scale(&surface, setup_params.queue_handle, ())) + }); + + let viewport = setup_params.viewporter.map(|vp| { + info!("Creating viewport for surface"); + Rc::new(vp.get_viewport(&surface, setup_params.queue_handle, ())) + }); + + Self::configure_layer_surface(&layer_surface, &surface, params); + surface.set_buffer_scale(1); + + Self { + surface, + layer_surface, + fractional_scale, + viewport, + } + } + + fn configure_layer_surface( + layer_surface: &Rc, + surface: &WlSurface, + params: &LayerSurfaceParams, + ) { + layer_surface.set_anchor(params.anchor); + layer_surface.set_margin( + params.margin.top, + params.margin.right, + params.margin.bottom, + params.margin.left, + ); + + layer_surface.set_exclusive_zone(params.exclusive_zone); + layer_surface.set_keyboard_interactivity(params.keyboard_interactivity); + layer_surface.set_size(1, params.height); + surface.commit(); + } +} diff --git a/src/windowing/system.rs b/src/windowing/system.rs new file mode 100644 index 0000000..6f5006d --- /dev/null +++ b/src/windowing/system.rs @@ -0,0 +1,215 @@ +use super::{ + config::{LayerSurfaceParams, WindowConfig}, + globals::GlobalCtx, + state::{builder::WindowStateBuilder, WindowState}, + surface::{SurfaceCtx, SurfaceSetupParams}, +}; +use crate::{ + errors::{LayerShikaError, Result}, + rendering::{egl_context::EGLContext, femtovg_window::FemtoVGWindow}, +}; +use log::{error, info}; +use slint::{ + platform::{femtovg_renderer::FemtoVGRenderer, update_timers_and_animations}, + LogicalPosition, PhysicalSize, +}; +use slint_interpreter::ComponentInstance; +use smithay_client_toolkit::reexports::calloop::{ + generic::Generic, EventLoop, Interest, LoopHandle, Mode, PostAction, +}; +use std::rc::Rc; +use wayland_client::{ + protocol::{wl_display::WlDisplay, wl_surface::WlSurface}, + Connection, EventQueue, Proxy, +}; + +pub struct WindowingSystem { + state: WindowState, + connection: Rc, + event_queue: EventQueue, + event_loop: EventLoop<'static, WindowState>, +} + +impl WindowingSystem { + pub(super) fn new(config: WindowConfig) -> Result { + info!("Initializing WindowingSystem"); + let connection = + Rc::new(Connection::connect_to_env().map_err(LayerShikaError::WaylandConnection)?); + let event_queue = connection.new_event_queue(); + + let global_ctx = GlobalCtx::initialize(&connection, &event_queue.handle()) + .map_err(|e| LayerShikaError::GlobalInitialization(e.to_string()))?; + + 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 pointer = Rc::new(global_ctx.seat.get_pointer(&event_queue.handle(), ())); + let output = Rc::new(global_ctx.output); + let window = Self::initialize_renderer(&surface_ctx.surface, &connection.display(), &config) + .map_err(|e| LayerShikaError::EGLContextCreation(e.to_string()))?; + + let mut builder = WindowStateBuilder::new() + .with_component_definition(config.component_definition) + .with_surface(Rc::clone(&surface_ctx.surface)) + .with_layer_surface(Rc::clone(&surface_ctx.layer_surface)) + .with_pointer(Rc::clone(&pointer)) + .with_output(Rc::clone(&output)) + .with_scale_factor(config.scale_factor) + .with_height(config.height) + .with_exclusive_zone(config.exclusive_zone) + .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 = builder + .build() + .map_err(|e| LayerShikaError::WindowConfiguration(e.to_string()))?; + + let event_loop = + EventLoop::try_new().map_err(|e| LayerShikaError::EventLoop(e.to_string()))?; + + Ok(Self { + state, + connection, + event_queue, + event_loop, + }) + } + + fn initialize_renderer( + surface: &Rc, + display: &WlDisplay, + config: &WindowConfig, + ) -> 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() + .map_err(|e| LayerShikaError::EGLContextCreation(e.to_string()))?; + + let renderer = FemtoVGRenderer::new(context) + .map_err(|e| LayerShikaError::FemtoVGRendererCreation(e.to_string()))?; + + 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(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"); + + while self + .event_queue + .blocking_dispatch(&mut self.state) + .map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))? + > 0 + { + self.connection + .flush() + .map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?; + self.state + .window() + .render_frame_if_dirty() + .map_err(|e| LayerShikaError::Rendering(e.to_string()))?; + } + + self.setup_wayland_event_source()?; + + let event_queue = &mut self.event_queue; + let connection = &self.connection; + + self.event_loop + .run(None, &mut self.state, move |shared_data| { + if let Err(e) = Self::process_events(connection, event_queue, shared_data) { + error!("Error processing events: {e}"); + } + }) + .map_err(|e| LayerShikaError::EventLoop(e.to_string())) + } + + 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| LayerShikaError::EventLoop(e.to_string()))?; + + Ok(()) + } + + fn process_events( + connection: &Connection, + event_queue: &mut EventQueue, + shared_data: &mut WindowState, + ) -> Result<()> { + if let Some(guard) = event_queue.prepare_read() { + guard + .read() + .map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?; + } + connection.flush()?; + + event_queue + .dispatch_pending(shared_data) + .map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?; + + update_timers_and_animations(); + + shared_data + .window() + .render_frame_if_dirty() + .map_err(|e| LayerShikaError::Rendering(e.to_string()))?; + + Ok(()) + } + + pub const fn component_instance(&self) -> &ComponentInstance { + self.state.component_instance() + } + + pub fn window(&self) -> Rc { + self.state.window() + } + + pub const fn state(&self) -> &WindowState { + &self.state + } +}