mirror of
https://codeberg.org/waydeer/layer-shika.git
synced 2025-10-28 14:14:23 +00:00
236 lines
8.3 KiB
Rust
236 lines
8.3 KiB
Rust
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<Connection>,
|
|
event_queue: EventQueue<WindowState>,
|
|
event_loop: EventLoop<'static, WindowState>,
|
|
}
|
|
|
|
impl WindowingSystem {
|
|
pub(super) fn new(config: WindowConfig) -> Result<Self> {
|
|
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<WlSurface>,
|
|
display: &WlDisplay,
|
|
config: &WindowConfig,
|
|
) -> Result<Rc<FemtoVGWindow>> {
|
|
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");
|
|
|
|
// Process all initial configuration events by repeatedly dispatching until queue is empty.
|
|
// After each batch, flush and render to allow compositor to respond with additional
|
|
// configure events (e.g., after fractional scale is applied).
|
|
// This ensures proper initialization.
|
|
info!("Processing initial Wayland configuration events");
|
|
while self
|
|
.event_queue
|
|
.blocking_dispatch(&mut self.state)
|
|
.map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?
|
|
> 0
|
|
{
|
|
// Flush requests to compositor after processing each event batch
|
|
self.connection
|
|
.flush()
|
|
.map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?;
|
|
|
|
// Render if window was marked dirty by the configure events
|
|
update_timers_and_animations();
|
|
self.state
|
|
.window()
|
|
.render_frame_if_dirty()
|
|
.map_err(|e| LayerShikaError::Rendering(e.to_string()))?;
|
|
}
|
|
|
|
// Setup Wayland file descriptor as event source for ongoing events
|
|
self.setup_wayland_event_source()?;
|
|
|
|
let event_queue = &mut self.event_queue;
|
|
let connection = &self.connection;
|
|
|
|
// Enter the main event loop with consistent event processing:
|
|
// read -> dispatch -> animate -> render -> flush
|
|
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<WindowState>,
|
|
shared_data: &mut WindowState,
|
|
) -> Result<()> {
|
|
// 1. READ: Read pending events from Wayland socket if available
|
|
if let Some(guard) = event_queue.prepare_read() {
|
|
guard
|
|
.read()
|
|
.map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?;
|
|
}
|
|
|
|
// 2. DISPATCH: Process all queued Wayland events
|
|
event_queue
|
|
.dispatch_pending(shared_data)
|
|
.map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?;
|
|
|
|
// 3. ANIMATE: Update Slint timers and animations
|
|
update_timers_and_animations();
|
|
|
|
// 4. RENDER: Render frame if window was marked dirty
|
|
shared_data
|
|
.window()
|
|
.render_frame_if_dirty()
|
|
.map_err(|e| LayerShikaError::Rendering(e.to_string()))?;
|
|
|
|
// 5. FLUSH: Send buffered Wayland requests to compositor
|
|
connection
|
|
.flush()
|
|
.map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub const fn component_instance(&self) -> &ComponentInstance {
|
|
self.state.component_instance()
|
|
}
|
|
|
|
pub fn window(&self) -> Rc<FemtoVGWindow> {
|
|
self.state.window()
|
|
}
|
|
|
|
pub const fn state(&self) -> &WindowState {
|
|
&self.state
|
|
}
|
|
}
|