layer-shika/src/windowing/mod.rs
2025-10-25 15:24:52 +02:00

336 lines
10 KiB
Rust

use self::state::WindowState;
use crate::{
bind_globals,
errors::{LayerShikaError, Result},
rendering::{egl_context::EGLContext, femtovg_window::FemtoVGWindow},
};
use config::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 macros;
mod state;
type GlobalObjects = (
WlCompositor,
WlOutput,
ZwlrLayerShellV1,
WlSeat,
Option<WpFractionalScaleManagerV1>,
Option<WpViewporter>,
);
type SurfaceObjects = (
Rc<WlSurface>,
Rc<ZwlrLayerSurfaceV1>,
Option<Rc<WpFractionalScaleV1>>,
Option<Rc<WpViewport>>,
);
pub struct WindowingSystem {
state: WindowState,
connection: Rc<Connection>,
event_queue: EventQueue<WindowState>,
event_loop: EventLoop<'static, WindowState>,
}
impl WindowingSystem {
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 (compositor, output, layer_shell, seat, fractional_scale_manager, viewporter) =
Self::initialize_globals(&connection, &event_queue.handle())
.map_err(|e| LayerShikaError::GlobalInitialization(e.to_string()))?;
let (surface, layer_surface, fractional_scale, viewport) = Self::setup_surface(
&compositor,
&output,
&layer_shell,
fractional_scale_manager.as_ref(),
viewporter.as_ref(),
&event_queue.handle(),
config,
);
let pointer = Rc::new(seat.get_pointer(&event_queue.handle(), ()));
let window = Self::initialize_renderer(&surface, &connection.display(), config)
.map_err(|e| LayerShikaError::EGLContextCreation(e.to_string()))?;
let mut builder = WindowStateBuilder::new()
.with_component_definition(config.component_definition.clone())
.with_surface(Rc::clone(&surface))
.with_layer_surface(Rc::clone(&layer_surface))
.with_pointer(Rc::clone(&pointer))
.with_scale_factor(config.scale_factor)
.with_height(config.height)
.with_exclusive_zone(config.exclusive_zone)
.with_window(window);
if let Some(fs) = fractional_scale {
builder = builder.with_fractional_scale(fs);
}
if let Some(vp) = 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<WindowState>,
) -> Result<GlobalObjects> {
let global_list = registry_queue_init::<WindowState>(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::<WpFractionalScaleManagerV1, _, _>(queue_handle, 1..=1, ())
.ok();
let viewporter = global_list
.bind::<WpViewporter, _, _>(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((
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<WindowState>,
config: &WindowConfig,
) -> SurfaceObjects {
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, ()))
});
Self::configure_layer_surface(&layer_surface, &surface, config);
surface.set_buffer_scale(1);
(surface, layer_surface, fractional_scale, viewport)
}
fn configure_layer_surface(
layer_surface: &Rc<ZwlrLayerSurfaceV1>,
surface: &WlSurface,
config: &WindowConfig,
) {
layer_surface.set_anchor(config.anchor);
layer_surface.set_margin(
config.margin.top,
config.margin.right,
config.margin.bottom,
config.margin.left,
);
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 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");
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<WindowState>,
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<FemtoVGWindow> {
self.state.window()
}
pub const fn state(&self) -> &WindowState {
&self.state
}
}