refactor: split surface, globals, system

This commit is contained in:
drendog 2025-10-25 20:04:59 +02:00
parent 18d1827415
commit 6a72d83d0f
Signed by: dwenya
GPG key ID: 8DD77074645332D0
4 changed files with 378 additions and 347 deletions

66
src/windowing/globals.rs Normal file
View file

@ -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<WpFractionalScaleManagerV1>,
pub viewporter: Option<WpViewporter>,
}
impl GlobalCtx {
pub fn initialize(
connection: &Connection,
queue_handle: &QueueHandle<WindowState>,
) -> Result<Self, LayerShikaError> {
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(Self {
compositor,
output,
layer_shell,
seat,
fractional_scale_manager,
viewporter,
})
}
}

View file

@ -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<WpFractionalScaleManagerV1>,
pub viewporter: Option<WpViewporter>,
}
pub struct SurfaceCtx {
pub surface: Rc<WlSurface>,
pub layer_surface: Rc<ZwlrLayerSurfaceV1>,
pub fractional_scale: Option<Rc<WpFractionalScaleV1>>,
pub viewport: 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 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<WindowState>,
) -> Result<GlobalCtx> {
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(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<WindowState>,
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, &params);
surface.set_buffer_scale(1);
SurfaceCtx {
surface,
layer_surface,
fractional_scale,
viewport,
}
}
fn configure_layer_surface(
layer_surface: &Rc<ZwlrLayerSurfaceV1>,
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<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
}
}
pub use system::WindowingSystem;

93
src/windowing/surface.rs Normal file
View file

@ -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<WindowState>,
pub layer: Layer,
pub namespace: String,
}
pub struct SurfaceCtx {
pub surface: Rc<WlSurface>,
pub layer_surface: Rc<ZwlrLayerSurfaceV1>,
pub fractional_scale: Option<Rc<WpFractionalScaleV1>>,
pub viewport: Option<Rc<WpViewport>>,
}
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<ZwlrLayerSurfaceV1>,
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();
}
}

215
src/windowing/system.rs Normal file
View file

@ -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<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");
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<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
}
}