From 155d4071a5127523a17dd7bd17e0f07dff41694a Mon Sep 17 00:00:00 2001 From: drendog Date: Tue, 13 Aug 2024 22:41:58 +0200 Subject: [PATCH] feat: add init project draft --- src/common.rs | 24 ++ src/lib.rs | 5 + src/rendering/egl_context.rs | 183 +++++++++++++++ src/rendering/femtovg_window.rs | 83 +++++++ src/rendering/mod.rs | 3 + src/rendering/slint_platform.rs | 32 +++ src/windowing/event_handler.rs | 229 ++++++++++++++++++ src/windowing/event_loop.rs | 110 +++++++++ src/windowing/macros.rs | 32 +++ src/windowing/mod.rs | 403 ++++++++++++++++++++++++++++++++ src/windowing/state.rs | 111 +++++++++ 11 files changed, 1215 insertions(+) create mode 100644 src/common.rs create mode 100644 src/lib.rs create mode 100644 src/rendering/egl_context.rs create mode 100644 src/rendering/femtovg_window.rs create mode 100644 src/rendering/mod.rs create mode 100644 src/rendering/slint_platform.rs create mode 100644 src/windowing/event_handler.rs create mode 100644 src/windowing/event_loop.rs create mode 100644 src/windowing/macros.rs create mode 100644 src/windowing/mod.rs create mode 100644 src/windowing/state.rs diff --git a/src/common.rs b/src/common.rs new file mode 100644 index 0000000..714bb0d --- /dev/null +++ b/src/common.rs @@ -0,0 +1,24 @@ +use slint::PhysicalSize; + +#[derive(Debug, Clone, Copy)] +pub struct LayerSize { + size: PhysicalSize, +} + +impl LayerSize { + pub fn new(width: u32, height: u32) -> Self { + Self { + size: PhysicalSize::new(width, height), + } + } + + pub fn physical_size(&self) -> PhysicalSize { + self.size + } +} + +impl Default for LayerSize { + fn default() -> Self { + Self::new(1, 1) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..5dcc92c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,5 @@ +mod common; +mod rendering; +mod windowing; + +pub use windowing::WindowingSystemBuilder; diff --git a/src/rendering/egl_context.rs b/src/rendering/egl_context.rs new file mode 100644 index 0000000..4eeeb27 --- /dev/null +++ b/src/rendering/egl_context.rs @@ -0,0 +1,183 @@ +use anyhow::{anyhow, Result}; +use glutin::{ + api::egl::{context::PossiblyCurrentContext, display::Display, surface::Surface}, + config::ConfigTemplateBuilder, + context::ContextAttributesBuilder, + display::GetGlDisplay, + prelude::*, + surface::{SurfaceAttributesBuilder, WindowSurface}, +}; +use raw_window_handle::{ + RawDisplayHandle, RawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle, +}; +use slint::platform::femtovg_renderer::OpenGLInterface; +use std::{ + ffi::{self, c_void, CStr}, + num::NonZeroU32, + ptr::NonNull, +}; +use wayland_client::backend::ObjectId; + +use crate::common::LayerSize; + +pub struct EGLContext { + context: PossiblyCurrentContext, + surface: Surface, +} +#[derive(Default)] +pub struct EGLContextBuilder { + display_id: Option, + surface_id: Option, + size: Option, + config_template: Option, + context_attributes: Option, +} + +#[allow(dead_code)] +impl EGLContextBuilder { + pub fn new() -> Self { + Default::default() + } + + pub fn with_display_id(mut self, display_id: ObjectId) -> Self { + self.display_id = Some(display_id); + self + } + + pub fn with_surface_id(mut self, surface_id: ObjectId) -> Self { + self.surface_id = Some(surface_id); + self + } + + pub fn with_size(mut self, size: LayerSize) -> Self { + self.size = Some(size); + self + } + + pub fn with_config_template(mut self, config_template: ConfigTemplateBuilder) -> Self { + self.config_template = Some(config_template); + self + } + + pub fn with_context_attributes(mut self, context_attributes: ContextAttributesBuilder) -> Self { + self.context_attributes = Some(context_attributes); + self + } + + pub fn build(self) -> Result { + let display_id = self + .display_id + .ok_or_else(|| anyhow!("Display ID is required"))?; + let surface_id = self + .surface_id + .ok_or_else(|| anyhow!("Surface ID is required"))?; + let size = self.size.ok_or_else(|| anyhow!("Size is required"))?; + + let display_handle = create_wayland_display_handle(display_id); + let glutin_display = unsafe { Display::new(display_handle) }?; + + let config_template = self.config_template.unwrap_or_default(); + + let config = select_config(&glutin_display, config_template)?; + + let context_attributes = self.context_attributes.unwrap_or_default(); + + let context = create_context(&glutin_display, &config, context_attributes)?; + + let surface_handle = create_surface_handle(surface_id); + let surface = create_surface(&glutin_display, &config, surface_handle, size)?; + + let context = context + .make_current(&surface) + .map_err(|e| anyhow!("Unable to activate EGL context: {}. This may indicate a problem with the graphics drivers.", e))?; + + Ok(EGLContext { context, surface }) + } +} + +impl EGLContext { + pub fn builder() -> EGLContextBuilder { + EGLContextBuilder::new() + } + + fn ensure_current(&self) -> Result<()> { + if !self.context.is_current() { + self.context.make_current(&self.surface)?; + } + Ok(()) + } +} + +fn create_wayland_display_handle(display_id: ObjectId) -> RawDisplayHandle { + let display = + NonNull::new(display_id.as_ptr() as *mut c_void).expect("NonNull pointer creation failed"); + let handle = WaylandDisplayHandle::new(display); + RawDisplayHandle::Wayland(handle) +} + +fn select_config( + glutin_display: &Display, + config_template: ConfigTemplateBuilder, +) -> Result { + let mut configs = unsafe { glutin_display.find_configs(config_template.build()) }?; + configs + .next() + .ok_or_else(|| anyhow!("No compatible EGL configurations found.")) +} +fn create_context( + glutin_display: &Display, + config: &glutin::api::egl::config::Config, + context_attributes: ContextAttributesBuilder, +) -> Result { + unsafe { glutin_display.create_context(config, &context_attributes.build(None)) } + .map_err(|e| anyhow!("Failed to create context: {}", e)) +} + +fn create_surface_handle(surface_id: ObjectId) -> RawWindowHandle { + let surface = + NonNull::new(surface_id.as_ptr() as *mut c_void).expect("NonNull pointer creation failed"); + let handle = WaylandWindowHandle::new(surface); + RawWindowHandle::Wayland(handle) +} + +fn create_surface( + glutin_display: &Display, + config: &glutin::api::egl::config::Config, + surface_handle: RawWindowHandle, + size: LayerSize, +) -> Result> { + let attrs = SurfaceAttributesBuilder::::new().build( + surface_handle, + NonZeroU32::new(size.physical_size().width).unwrap(), + NonZeroU32::new(size.physical_size().height).unwrap(), + ); + unsafe { glutin_display.create_window_surface(config, &attrs) } + .map_err(|e| anyhow!("Failed to create window surface: {}", e)) +} + +unsafe impl OpenGLInterface for EGLContext { + fn ensure_current(&self) -> Result<(), Box> { + Ok(self.ensure_current()?) + } + + fn swap_buffers(&self) -> Result<(), Box> { + self.surface + .swap_buffers(&self.context) + .map_err(|e| anyhow!("Failed to swap buffers: {}", e)) + .map_err(Into::into) + } + + fn resize( + &self, + width: NonZeroU32, + height: NonZeroU32, + ) -> Result<(), Box> { + self.ensure_current()?; + self.surface.resize(&self.context, width, height); + Ok(()) + } + + fn get_proc_address(&self, name: &CStr) -> *const ffi::c_void { + self.context.display().get_proc_address(name) + } +} diff --git a/src/rendering/femtovg_window.rs b/src/rendering/femtovg_window.rs new file mode 100644 index 0000000..7f62cb2 --- /dev/null +++ b/src/rendering/femtovg_window.rs @@ -0,0 +1,83 @@ +use log::info; +use slint::{ + platform::{femtovg_renderer::FemtoVGRenderer, Renderer, WindowAdapter, WindowEvent}, + PhysicalSize, Window, WindowSize, +}; +use std::cell::{Cell, RefCell}; +use std::rc::{Rc, Weak}; + +pub struct FemtoVGWindow { + window: RefCell, + renderer: RefCell, + is_dirty: Cell, + size: Cell, + scale_factor: Cell, +} + +impl FemtoVGWindow { + pub fn new(renderer: FemtoVGRenderer) -> Rc { + Rc::new_cyclic(|weak_self| { + let window = Window::new(weak_self.clone() as Weak); + Self { + window: RefCell::new(window), + renderer: RefCell::new(renderer), + is_dirty: Default::default(), + size: Cell::new(PhysicalSize::default()), + scale_factor: Cell::new(1.), + } + }) + } + + pub fn render_frame_if_dirty(&self) { + if self.is_dirty.get() { + match self.renderer.borrow_mut().render() { + Ok(_) => {} //log::debug!("Frame rendered successfully"), + Err(e) => log::error!("Error rendering frame: {}", e), + } + self.is_dirty.set(false); + } + } + + pub fn set_scale_factor(&self, scale_factor: f32) { + info!("Setting scale factor to {}", scale_factor); + self.scale_factor.set(scale_factor); + self.window() + .dispatch_event(WindowEvent::ScaleFactorChanged { scale_factor }); + } + + pub fn scale_factor(&self) -> f32 { + self.scale_factor.get() + } +} + +impl WindowAdapter for FemtoVGWindow { + fn window(&self) -> &Window { + unsafe { self.window.as_ptr().as_ref().unwrap() } + } + + fn renderer(&self) -> &dyn Renderer { + unsafe { &*self.renderer.as_ptr() } + } + + fn size(&self) -> PhysicalSize { + self.size.get() + } + + fn set_size(&self, size: WindowSize) { + self.size.set(size.to_physical(self.scale_factor())); + self.window.borrow().dispatch_event(WindowEvent::Resized { + size: size.to_logical(self.scale_factor()), + }); + } + + fn request_redraw(&self) { + self.is_dirty.set(true); + } +} + +impl core::ops::Deref for FemtoVGWindow { + type Target = Window; + fn deref(&self) -> &Self::Target { + unsafe { self.window.as_ptr().as_ref().unwrap() } + } +} diff --git a/src/rendering/mod.rs b/src/rendering/mod.rs new file mode 100644 index 0000000..d4eef13 --- /dev/null +++ b/src/rendering/mod.rs @@ -0,0 +1,3 @@ +pub mod egl_context; +pub mod femtovg_window; +pub mod slint_platform; diff --git a/src/rendering/slint_platform.rs b/src/rendering/slint_platform.rs new file mode 100644 index 0000000..edac8e2 --- /dev/null +++ b/src/rendering/slint_platform.rs @@ -0,0 +1,32 @@ +use slint::{ + platform::{Platform, WindowAdapter}, + PlatformError, +}; +use std::rc::{Rc, Weak}; + +use super::femtovg_window::FemtoVGWindow; + +pub struct CustomSlintPlatform { + window: Weak, +} + +impl CustomSlintPlatform { + pub fn new(window: &Rc) -> Self { + Self { + window: Rc::downgrade(window), + } + } +} + +impl Platform for CustomSlintPlatform { + fn create_window_adapter(&self) -> Result, PlatformError> { + self.window + .upgrade() + .map(|window| -> Rc { window }) + .ok_or_else(|| { + PlatformError::Other( + "Failed to create window adapter: window no longer exists".into(), + ) + }) + } +} diff --git a/src/windowing/event_handler.rs b/src/windowing/event_handler.rs new file mode 100644 index 0000000..f606507 --- /dev/null +++ b/src/windowing/event_handler.rs @@ -0,0 +1,229 @@ +use crate::impl_empty_dispatch; +use log::info; +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 std::rc::Rc; +use std::{cell::RefCell, rc::Weak}; +use wayland_client::{ + globals::GlobalListContents, + protocol::{ + wl_compositor::WlCompositor, + wl_output::{self, WlOutput}, + wl_pointer::{self, WlPointer}, + wl_registry::WlRegistry, + wl_seat::WlSeat, + wl_surface::WlSurface, + }, + Connection, Dispatch, QueueHandle, +}; + +use super::state::WindowState; + +#[derive(Clone)] +pub struct WindowEventHandler { + state: Weak>, +} + +impl WindowEventHandler { + pub fn new(state: Weak>) -> Self { + Self { state } + } + + pub fn state(&self) -> Rc> { + self.state.upgrade().unwrap() + } + + fn handle_pointer_enter(&mut self, surface_x: f64, surface_y: f64) { + if let Some(state) = self.state.upgrade() { + state + .borrow() + .set_current_pointer_position(surface_x, surface_y); + if let Some(window) = state.borrow().window() { + let logical_position = state.borrow().current_pointer_position(); + window.dispatch_event(WindowEvent::PointerMoved { + position: logical_position, + }); + } + } + } + + fn handle_pointer_leave(&mut self) { + if let Some(state) = self.state.upgrade() { + if let Some(window) = state.borrow().window() { + window.dispatch_event(WindowEvent::PointerExited); + } + } + } + + fn handle_pointer_motion(&mut self, surface_x: f64, surface_y: f64) { + if let Some(state) = self.state.upgrade() { + state + .borrow() + .set_current_pointer_position(surface_x, surface_y); + if let Some(window) = state.borrow().window() { + let logical_position = state.borrow().current_pointer_position(); + window.dispatch_event(WindowEvent::PointerMoved { + position: logical_position, + }); + } + } + } + + fn handle_pointer_button( + &mut self, + button_state: wayland_client::WEnum, + ) { + if let Some(state) = self.state.upgrade() { + let is_press = matches!( + button_state, + wayland_client::WEnum::Value(wl_pointer::ButtonState::Pressed) + ); + let current_position = state.borrow().current_pointer_position(); + if let Some(window) = state.borrow().window() { + let event = if is_press { + WindowEvent::PointerPressed { + button: PointerEventButton::Left, + position: current_position, + } + } else { + WindowEvent::PointerReleased { + button: PointerEventButton::Left, + position: current_position, + } + }; + window.dispatch_event(event); + } + } + } +} + +impl Dispatch for WindowEventHandler { + 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 size: {}x{}", width, height); + layer_surface.ack_configure(serial); + if let Some(state) = state.state.upgrade() { + let state_borrow = state.borrow(); + if width > 0 && height > 0 { + state_borrow + .update_size(state_borrow.output_size().width, state_borrow.height()); + } else { + let current_size = state_borrow.output_size(); + state_borrow.update_size(current_size.width, current_size.height); + } + } + } + zwlr_layer_surface_v1::Event::Closed => { + info!("Layer surface closed"); + } + _ => {} + } + } +} + +impl Dispatch for WindowEventHandler { + 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 {}x{}", width, height); + if let Some(state) = state.state.upgrade() { + let state_borrow = state.borrow(); + state_borrow.set_output_size(width as u32, height as u32); + } + } + 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={}, y={}, physical_width={}, physical_height={}, subpixel={:?}, make={:?}, model={:?}, transform={:?}", x, y, physical_width, physical_height, subpixel, make, model, transform); + } + wl_output::Event::Done => { + info!("WlOutput done"); + } + _ => {} + } + } +} + +impl Dispatch for WindowEventHandler { + fn event( + state: &mut Self, + _proxy: &WlPointer, + event: ::Event, + _data: &(), + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + match event { + wl_pointer::Event::Enter { + surface_x, + surface_y, + .. + } => { + state.handle_pointer_enter(surface_x, surface_y); + } + wl_pointer::Event::Leave { .. } => { + state.handle_pointer_leave(); + } + wl_pointer::Event::Motion { + surface_x, + surface_y, + .. + } => { + state.handle_pointer_motion(surface_x, surface_y); + } + wl_pointer::Event::Button { + state: button_state, + .. + } => { + state.handle_pointer_button(button_state); + } + _ => {} + } + } +} + +impl_empty_dispatch!( + (WlRegistry, GlobalListContents), + (WlCompositor, ()), + (WlSurface, ()), + (ZwlrLayerShellV1, ()), + (WlSeat, ()) +); diff --git a/src/windowing/event_loop.rs b/src/windowing/event_loop.rs new file mode 100644 index 0000000..5b80b43 --- /dev/null +++ b/src/windowing/event_loop.rs @@ -0,0 +1,110 @@ +use anyhow::{anyhow, Result}; +use log::{debug, error, info}; +use smithay_client_toolkit::reexports::calloop::{self, EventLoop, Interest, Mode, PostAction}; +use std::cell::RefCell; +use std::rc::{Rc, Weak}; +use wayland_client::{Connection, EventQueue}; + +use crate::rendering::femtovg_window::FemtoVGWindow; +use crate::windowing::event_handler::WindowEventHandler; + +pub struct EventLoopHandler { + window: Weak, + wayland_queue: Weak>>, + connection: Weak, + event_handler: Weak>, +} + +impl EventLoopHandler { + pub fn new( + window: Weak, + wayland_queue: Weak>>, + connection: Weak, + event_handler: Weak>, + ) -> Self { + debug!("Creating EventLoopHandler"); + Self { + window, + wayland_queue, + connection, + event_handler, + } + } + + pub fn setup_wayland_event_source(&self, loop_handle: &calloop::LoopHandle<()>) -> Result<()> { + debug!("Setting up Wayland event source"); + + let wayland_queue = self.wayland_queue.clone(); + let event_handler = self.event_handler.clone(); + let connection = self.connection.upgrade().ok_or_else(|| { + anyhow!("Failed to get Wayland connection reference in Wayland event source") + })?; + let window = self.window.clone(); + + loop_handle + .insert_source( + calloop::generic::Generic::new(connection, Interest::READ, Mode::Level), + move |_, connection, _| { + let result: Result = (|| { + let wayland_queue = wayland_queue + .upgrade() + .ok_or_else(|| anyhow!("Failed to get Wayland queue reference"))?; + let event_handler = event_handler + .upgrade() + .ok_or_else(|| anyhow!("Failed to get event handler reference"))?; + let window = window + .upgrade() + .ok_or_else(|| anyhow!("Failed to get window reference"))?; + Self::handle_wayland_events( + connection, + &wayland_queue, + &event_handler, + &window, + )?; + Ok(PostAction::Continue) + })(); + + result.map_err(|e| { + error!("Error handling Wayland events: {}", e); + std::io::Error::new(std::io::ErrorKind::Other, e) + }) + }, + ) + .map_err(|e| anyhow!("Failed to insert Wayland event source: {}", e))?; + + Ok(()) + } + + pub fn run(&self, event_loop: &mut EventLoop<()>) -> Result<()> { + info!("Starting event loop"); + event_loop + .run(None, &mut (), |_| {}) + .map_err(|e| anyhow!("Failed to run event loop: {}", e)) + } + + fn handle_wayland_events( + connection: &Connection, + wayland_queue: &Rc>>, + event_handler: &Rc>, + window: &Rc, + ) -> Result<()> { + connection + .flush() + .map_err(|e| anyhow!("Failed to flush connection: {}", e))?; + + let mut event_queue = wayland_queue.borrow_mut(); + if let Some(guard) = event_queue.prepare_read() { + guard + .read() + .map_err(|e| anyhow!("Failed to read Wayland events: {}", e))?; + } + + event_queue + .dispatch_pending(&mut *event_handler.borrow_mut()) + .map_err(|e| anyhow!("Failed to dispatch Wayland events: {}", e))?; + + slint::platform::update_timers_and_animations(); + window.render_frame_if_dirty(); + Ok(()) + } +} diff --git a/src/windowing/macros.rs b/src/windowing/macros.rs new file mode 100644 index 0000000..c19bdde --- /dev/null +++ b/src/windowing/macros.rs @@ -0,0 +1,32 @@ +#[macro_export] +macro_rules! impl_empty_dispatch { + ($(($t:ty, $u:ty)),+) => { + $( + impl Dispatch<$t, $u> for WindowEventHandler { + 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)); + } + } + )+ + }; +} + +#[macro_export] +macro_rules! bind_globals { + ($global_list:expr, $queue_handle:expr, $(($interface:ty, $name:ident, $version:expr)),+) => { + { + $( + let $name: $interface = $global_list.bind($queue_handle, $version, ()) + .with_context(|| format!("Failed to bind {}", stringify!($name)))?; + )+ + Ok(($($name),+)) + } + }; +} diff --git a/src/windowing/mod.rs b/src/windowing/mod.rs new file mode 100644 index 0000000..de9a01c --- /dev/null +++ b/src/windowing/mod.rs @@ -0,0 +1,403 @@ +use anyhow::{Context, Result}; +use log::{debug, info}; +use slint::{platform::femtovg_renderer::FemtoVGRenderer, ComponentHandle, LogicalPosition}; +use slint_interpreter::{ComponentDefinition, ComponentInstance}; +use smithay_client_toolkit::reexports::{ + calloop::{self, EventLoop}, + protocols_wlr::layer_shell::v1::client::{ + zwlr_layer_shell_v1::{self, ZwlrLayerShellV1}, + zwlr_layer_surface_v1::{Anchor, KeyboardInteractivity, ZwlrLayerSurfaceV1}, + }, +}; +use std::{cell::RefCell, rc::Rc}; +use wayland_client::{ + globals::{registry_queue_init, GlobalList}, + protocol::{ + wl_compositor::WlCompositor, wl_display::WlDisplay, wl_output::WlOutput, wl_seat::WlSeat, + wl_surface::WlSurface, + }, + Connection, EventQueue, Proxy, QueueHandle, +}; + +use crate::{ + bind_globals, + common::LayerSize, + rendering::{ + egl_context::EGLContext, femtovg_window::FemtoVGWindow, slint_platform::CustomSlintPlatform, + }, +}; + +mod event_handler; +mod event_loop; +mod macros; +mod state; + +use self::{event_handler::WindowEventHandler, event_loop::EventLoopHandler, state::WindowState}; + +pub struct WindowConfig { + height: u32, + layer: zwlr_layer_shell_v1::Layer, + margin: (i32, i32, i32, i32), + anchor: Anchor, + keyboard_interactivity: KeyboardInteractivity, + exclusive_zone: i32, + scale_factor: f32, + namespace: String, + component_definition: Option>, +} + +impl Default for WindowConfig { + fn default() -> Self { + Self { + height: 30, + layer: zwlr_layer_shell_v1::Layer::Top, + margin: (0, 0, 0, 0), + anchor: Anchor::Top | Anchor::Left | Anchor::Right, + keyboard_interactivity: KeyboardInteractivity::OnDemand, + exclusive_zone: -1, + namespace: "layer-sheeka".to_string(), + scale_factor: 1.0, + component_definition: None, + } + } +} + +pub struct WindowingSystemBuilder { + config: WindowConfig, +} + +impl Default for WindowingSystemBuilder { + fn default() -> Self { + Self::new() + } +} + +impl WindowingSystemBuilder { + pub fn new() -> Self { + Self { + config: WindowConfig::default(), + } + } + + pub fn with_height(mut self, height: u32) -> Self { + self.config.height = height; + self + } + + pub fn with_layer(mut self, layer: zwlr_layer_shell_v1::Layer) -> Self { + self.config.layer = layer; + self + } + + pub fn with_margin(mut self, top: i32, right: i32, bottom: i32, left: i32) -> Self { + self.config.margin = (top, right, bottom, left); + self + } + + pub fn with_anchor(mut self, anchor: Anchor) -> Self { + self.config.anchor = anchor; + self + } + + pub fn with_keyboard_interactivity(mut self, interactivity: KeyboardInteractivity) -> Self { + self.config.keyboard_interactivity = interactivity; + self + } + + pub fn with_exclusive_zone(mut self, zone: i32) -> Self { + self.config.exclusive_zone = zone; + self + } + + pub fn with_namespace(mut self, namespace: String) -> Self { + self.config.namespace = namespace; + self + } + + pub fn with_scale_factor(mut self, scale_factor: f32) -> Self { + self.config.scale_factor = scale_factor; + self + } + + pub fn with_component_definition(mut self, component: Rc) -> Self { + self.config.component_definition = Some(component); + self + } + + pub fn build(self) -> Result> { + if self.config.component_definition.is_none() { + return Err(anyhow::anyhow!("Slint component not set")); + } + + WindowingSystem::new(self.config) + } +} + +pub struct WindowingSystem<'a> { + state: Rc>, + connection: Rc, + event_handler: Rc>, + window: Option>, + event_queue: Rc>>, + component_instance: Option>, + display: WlDisplay, + config: WindowConfig, + event_loop: EventLoop<'a, ()>, + event_loop_handler: Option, +} + +impl<'a> WindowingSystem<'a> { + fn new(config: WindowConfig) -> Result { + info!("Initializing WindowingSystem"); + let connection = Rc::new(Connection::connect_to_env()?); + let state = Rc::new(RefCell::new(WindowState::new(&config))); + let event_handler = Rc::new(RefCell::new(WindowEventHandler::new(Rc::downgrade(&state)))); + let display = connection.display(); + let event_queue = Rc::new(RefCell::new(connection.new_event_queue())); + let global_list = Self::initialize_registry(&connection)?; + let (compositor, output, layer_shell, seat) = + Self::bind_globals(&global_list, &event_queue.borrow().handle())?; + + Self::setup_surface( + &compositor, + &output, + &layer_shell, + &seat, + &event_queue.borrow().handle(), + &event_handler, + &config, + ); + + let event_loop = EventLoop::try_new().context("Failed to create event loop")?; + + let mut system = Self { + state, + connection, + event_handler, + window: None, + event_queue, + component_instance: None, + display, + config, + event_loop, + event_loop_handler: None, + }; + + system.wait_for_configure()?; + system.initialize_renderer_and_ui()?; + system.initialize_event_loop_handler()?; + + Ok(system) + } + + fn initialize_registry(connection: &Connection) -> Result { + registry_queue_init::(connection) + .map(|(global_list, _)| global_list) + .context("Failed to initialize registry") + } + + fn bind_globals( + global_list: &GlobalList, + queue_handle: &QueueHandle, + ) -> Result<(WlCompositor, WlOutput, ZwlrLayerShellV1, WlSeat)> { + bind_globals!( + global_list, + queue_handle, + (WlCompositor, compositor, 1..=1), + (WlOutput, output, 1..=1), + (ZwlrLayerShellV1, layer_shell, 1..=1), + (WlSeat, seat, 1..=1) + ) + } + + fn setup_surface( + compositor: &WlCompositor, + output: &WlOutput, + layer_shell: &ZwlrLayerShellV1, + seat: &WlSeat, + queue_handle: &QueueHandle, + event_handler: &Rc>, + config: &WindowConfig, + ) { + let surface = 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 pointer = seat.get_pointer(queue_handle, ()); + + let binding = event_handler.borrow_mut(); + let binding = binding.state(); + let mut state = binding.borrow_mut(); + state.set_surface(surface.clone()); + state.set_layer_surface(layer_surface.clone()); + state.set_pointer(pointer); + + Self::configure_layer_surface(&layer_surface, &surface, config); + } + + fn configure_layer_surface( + layer_surface: &Rc, + surface: &WlSurface, + config: &WindowConfig, + ) { + layer_surface.set_anchor(config.anchor); + layer_surface.set_margin( + config.margin.0, + config.margin.1, + config.margin.2, + config.margin.3, + ); + println!("Setting exclusive zone: {}", config.exclusive_zone); + layer_surface.set_exclusive_zone(config.exclusive_zone); + layer_surface.set_keyboard_interactivity(config.keyboard_interactivity); + layer_surface.set_size(1, config.height); + surface.commit(); + } + + fn wait_for_configure(&mut self) -> Result<()> { + info!("Waiting for surface to be configured..."); + loop { + self.connection.flush()?; + self.event_queue + .borrow_mut() + .blocking_dispatch(&mut self.event_handler.borrow_mut()) + .context("Failed to dispatch events")?; + + let state = self.state.borrow(); + let size = state.output_size(); + if size.width > 1 && size.height > 1 { + info!("Configured output size: {:?}", size); + break; + } + } + debug!("Surface configuration complete"); + Ok(()) + } + + fn initialize_renderer_and_ui(&mut self) -> Result<()> { + let renderer = self.create_renderer()?; + let component_definition = self + .config + .component_definition + .clone() + .ok_or_else(|| anyhow::anyhow!("Component definition not set"))?; + let (window, component_instance) = + self.initialize_slint_ui(renderer, component_definition)?; + + self.window = Some(window.clone()); + self.state.borrow_mut().set_window(Rc::downgrade(&window)); + self.component_instance = Some(component_instance); + + Ok(()) + } + + fn create_renderer(&self) -> Result { + let size = self.state.borrow().size(); + let binding = self.state.borrow(); + let surface = binding + .surface() + .ok_or_else(|| anyhow::anyhow!("Surface not initialized"))?; + + debug!("Creating EGL context with size: {:?}", size); + let context = EGLContext::builder() + .with_display_id(self.display.id()) + .with_surface_id(surface.id()) + .with_size(LayerSize::new(size.width, size.height)) + .build() + .context("Failed to build EGL context")?; + + debug!("Creating FemtoVGRenderer"); + FemtoVGRenderer::new(context).context("Failed to create FemtoVGRenderer") + } + + fn initialize_slint_ui( + &self, + renderer: FemtoVGRenderer, + component_definition: Rc, + ) -> Result<(Rc, Rc)> { + let femtovg_window = FemtoVGWindow::new(renderer); + let size = self.state.borrow().size(); + info!("Initializing UI with size: {:?}", size); + femtovg_window.set_size(slint::WindowSize::Physical(size)); + femtovg_window.set_scale_factor(self.config.scale_factor); + femtovg_window.set_position(LogicalPosition::new(0., 0.)); + + debug!("Setting up custom Slint platform"); + let platform = CustomSlintPlatform::new(&femtovg_window); + slint::platform::set_platform(Box::new(platform)) + .map_err(|e| anyhow::anyhow!("Failed to set platform: {:?}", e))?; + + debug!("Creating Slint component instance"); + let slint_component: Rc = Rc::new(component_definition.create()?); + + slint_component + .show() + .map_err(|e| anyhow::anyhow!("Failed to show component: {:?}", e))?; + + Ok((femtovg_window, slint_component)) + } + + pub fn initialize_event_loop_handler(&mut self) -> Result<()> { + let event_loop_handler = EventLoopHandler::new( + Rc::downgrade(self.window.as_ref().unwrap()), + Rc::downgrade(&self.event_queue), + Rc::downgrade(&self.connection), + Rc::downgrade(&self.event_handler), + ); + + self.event_loop_handler = Some(event_loop_handler); + Ok(()) + } + + pub fn setup_event_sources(&self) -> Result<()> { + let loop_handle = self.event_loop.handle(); + let event_loop_handler = self + .event_loop_handler + .as_ref() + .ok_or_else(|| anyhow::anyhow!("EventLoopHandler not initialized"))?; + + event_loop_handler.setup_wayland_event_source(&loop_handle)?; + + Ok(()) + } + + pub fn event_loop_handle(&self) -> calloop::LoopHandle<'a, ()> { + self.event_loop.handle() + } + + pub fn run(&mut self) -> Result<()> { + info!("Starting WindowingSystem main loop"); + if let Some(window) = &self.window { + window.render_frame_if_dirty(); + } + + let event_loop_handler = self + .event_loop_handler + .as_ref() + .ok_or_else(|| anyhow::anyhow!("EventLoopHandler not initialized"))?; + + event_loop_handler.run(&mut self.event_loop) + } + + pub fn component_instance(&self) -> Option> { + self.component_instance.clone() + } + + pub fn window(&self) -> Option> { + self.window.clone() + } + + pub fn state(&self) -> Rc> { + self.state.clone() + } + + pub fn display(&self) -> &WlDisplay { + &self.display + } +} diff --git a/src/windowing/state.rs b/src/windowing/state.rs new file mode 100644 index 0000000..a22737a --- /dev/null +++ b/src/windowing/state.rs @@ -0,0 +1,111 @@ +use std::{cell::Cell, rc::Weak}; +use std::rc::Rc; +use log::info; +use slint::{LogicalPosition, PhysicalSize}; +use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1; +use wayland_client::protocol::{wl_pointer::WlPointer, wl_surface::WlSurface}; +use crate::rendering::femtovg_window::FemtoVGWindow; + +use super::WindowConfig; + +pub struct WindowState { + surface: Option, + layer_surface: Option>, + size: Cell, + output_size: Cell, + pointer: Option, + window: Option>, + current_pointer_position: Cell, + scale_factor: f32, + height: u32, + exclusive_zone: i32, +} + +impl WindowState { + pub fn new(config: &WindowConfig) -> Self { + Self { + surface: None, + layer_surface: None, + size: Cell::new(PhysicalSize::default()), + output_size: Cell::new(PhysicalSize::default()), + pointer: None, + window: None, + current_pointer_position: Cell::new(LogicalPosition::default()), + scale_factor: config.scale_factor, + height: config.height, + exclusive_zone: config.exclusive_zone, + } + } + + pub fn update_size(&self, width: u32, height: u32) { + let new_size = PhysicalSize::new(width, height); + self.size.set(new_size); + + if let Some(window) = &self.window() { + info!("Updating window size to {}x{}", width, height); + window.set_size(slint::WindowSize::Physical(new_size)); + window.set_scale_factor(self.scale_factor); + } + + if let Some(layer_surface) = &self.layer_surface() { + info!("Updating layer surface size to {}x{}", width, height); + layer_surface.set_size(width, height); + layer_surface.set_exclusive_zone(self.exclusive_zone); + } + + if let Some(s) = self.surface.as_ref() { + s.commit() + } + } + + pub fn set_current_pointer_position(&self, physical_x: f64, physical_y: f64) { + let scale_factor = self.scale_factor; + let logical_position = LogicalPosition::new( + physical_x as f32 / scale_factor, + physical_y as f32 / scale_factor, + ); + self.current_pointer_position.set(logical_position); + } + + pub fn size(&self) -> PhysicalSize { + self.size.get() + } + pub fn output_size(&self) -> PhysicalSize { + self.output_size.get() + } + pub fn current_pointer_position(&self) -> LogicalPosition { + self.current_pointer_position.get() + } + pub fn window(&self) -> Option> { + self.window.as_ref().and_then(|w| w.upgrade()) + } + + pub fn layer_surface(&self) -> Option> { + self.layer_surface.clone() + } + pub fn surface(&self) -> Option<&WlSurface> { + self.surface.as_ref() + } + + pub fn height(&self) -> u32 { + self.height + } + + pub fn set_output_size(&self, width: u32, height: u32) { + self.output_size.set(PhysicalSize::new(width, height)); + } + pub fn set_window(&mut self, window: Weak) { + self.window = Some(window); + } + + pub fn set_layer_surface(&mut self, layer_surface: Rc) { + self.layer_surface = Some(layer_surface); + } + + pub fn set_surface(&mut self, surface: WlSurface) { + self.surface = Some(surface); + } + pub fn set_pointer(&mut self, pointer: WlPointer) { + self.pointer = Some(pointer); + } +}