refactor: minor architecture improvement

This commit is contained in:
drendog 2025-11-02 05:35:11 +01:00
parent e8d636798d
commit 18d1391dee
Signed by: dwenya
GPG key ID: 8DD77074645332D0
33 changed files with 642 additions and 420 deletions

1
Cargo.lock generated
View file

@ -1868,6 +1868,7 @@ dependencies = [
"layer-shika-domain",
"log",
"raw-window-handle",
"slab",
"slint",
"slint-interpreter",
"smithay-client-toolkit 0.20.0",

View file

@ -17,6 +17,7 @@ glutin = { version = "0.32.3", default-features = false, features = [
] }
log = "0.4.28"
raw-window-handle = "0.6.2"
slab = "0.4"
slint = { version = "1.14.1", default-features = false, features = [
"compat-1-2",
"renderer-femtovg",

View file

@ -18,6 +18,7 @@ glutin.workspace = true
layer-shika-domain.workspace = true
log.workspace = true
raw-window-handle.workspace = true
slab.workspace = true
slint.workspace = true
slint-interpreter.workspace = true
smithay-client-toolkit.workspace = true

View file

@ -1,51 +1,172 @@
use layer_shika_domain::errors::DomainError;
use slint::{PlatformError, platform::SetPlatformError};
use smithay_client_toolkit::reexports::calloop;
use std::error::Error;
use std::result::Result as StdResult;
use thiserror::Error;
use wayland_client::backend::WaylandError;
use wayland_client::{
ConnectError, DispatchError,
globals::{BindError, GlobalError},
};
pub type Result<T> = StdResult<T, LayerShikaError>;
#[derive(Error, Debug)]
pub enum LayerShikaError {
#[error("Domain error: {0}")]
Domain(#[from] DomainError),
pub enum RenderingError {
#[error("failed to swap buffers")]
SwapBuffers {
#[source]
source: Box<dyn Error + Send + Sync>,
},
#[error("Failed to connect to Wayland: {0}")]
WaylandConnection(#[from] wayland_client::ConnectError),
#[error("failed to ensure context current")]
EnsureContextCurrent {
#[source]
source: Box<dyn Error + Send + Sync>,
},
#[error("Failed to initialize Wayland globals: {0}")]
GlobalInitialization(String),
#[error("Failed to dispatch Wayland event: {0}")]
WaylandDispatch(String),
#[error("Failed to create EGL context: {0}")]
EGLContextCreation(String),
#[error("Failed to create FemtoVG renderer: {0}")]
FemtoVGRendererCreation(String),
#[error("Failed to create Slint component: {0}")]
SlintComponentCreation(String),
#[error("Failed to run event loop: {0}")]
EventLoop(String),
#[error("Window configuration error: {0}")]
WindowConfiguration(String),
#[error("Rendering error: {0}")]
Rendering(String),
#[error("Invalid input: {0}")]
InvalidInput(String),
#[error("Wayland protocol error: {0}")]
WaylandProtocol(String),
#[error("Failed to set platform: {0}")]
PlatformSetup(String),
#[error("Failed to flush connection: {0}")]
ConnectionFlush(#[from] WaylandError),
#[error("rendering operation failed: {message}")]
Operation { message: String },
}
#[derive(Error, Debug)]
pub enum EGLError {
#[error("failed to create EGL display")]
DisplayCreation {
#[source]
source: Box<dyn Error + Send + Sync>,
},
#[error("failed to find EGL configurations")]
ConfigSelection {
#[source]
source: Box<dyn Error + Send + Sync>,
},
#[error("no compatible EGL configurations found")]
NoCompatibleConfig,
#[error("failed to create EGL context")]
ContextCreation {
#[source]
source: Box<dyn Error + Send + Sync>,
},
#[error("failed to create window surface")]
SurfaceCreation {
#[source]
source: Box<dyn Error + Send + Sync>,
},
#[error("failed to make context current")]
MakeCurrent {
#[source]
source: Box<dyn Error + Send + Sync>,
},
#[error("failed to swap buffers")]
SwapBuffers {
#[source]
source: Box<dyn Error + Send + Sync>,
},
}
#[derive(Error, Debug)]
pub enum EventLoopError {
#[error("failed to create event loop")]
Creation {
#[source]
source: calloop::Error,
},
#[error("failed to insert event source: {message}")]
InsertSource { message: String },
#[error("event loop execution failed")]
Execution {
#[source]
source: calloop::Error,
},
}
#[derive(Error, Debug)]
pub enum LayerShikaError {
#[error("domain error")]
Domain {
#[from]
source: DomainError,
},
#[error("failed to connect to Wayland display")]
WaylandConnection {
#[from]
source: ConnectError,
},
#[error("failed to initialize Wayland globals")]
GlobalInitialization {
#[source]
source: GlobalError,
},
#[error("Wayland dispatch error")]
WaylandDispatch {
#[from]
source: DispatchError,
},
#[error("failed to bind Wayland global")]
GlobalBind {
#[from]
source: BindError,
},
#[error("EGL context error")]
EGLContext {
#[from]
source: EGLError,
},
#[error("failed to create FemtoVG renderer")]
FemtoVGRendererCreation {
#[source]
source: PlatformError,
},
#[error("failed to create Slint component")]
SlintComponentCreation {
#[source]
source: PlatformError,
},
#[error("event loop error")]
EventLoop {
#[from]
source: EventLoopError,
},
#[error("window configuration error: {message}")]
WindowConfiguration { message: String },
#[error("rendering error")]
Rendering {
#[from]
source: RenderingError,
},
#[error("invalid input: {message}")]
InvalidInput { message: String },
#[error("Wayland protocol error")]
WaylandProtocol {
#[source]
source: WaylandError,
},
#[error("failed to set up Slint platform")]
PlatformSetup {
#[source]
source: SetPlatformError,
},
}

View file

@ -1,79 +0,0 @@
use crate::wayland::{surfaces::surface_state::WindowState, shell_adapter::WaylandWindowingSystem};
use crate::{
errors::Result,
platform::calloop::{EventSource, InsertError, RegistrationToken},
};
use slint_interpreter::ComponentInstance;
use std::result::Result as StdResult;
pub struct SystemAdapter {
inner: WaylandWindowingSystem,
}
impl SystemAdapter {
#[must_use]
pub fn new(inner: WaylandWindowingSystem) -> Self {
Self { inner }
}
#[must_use]
pub fn event_loop_handle(&self) -> EventLoopAdapter {
EventLoopAdapter {
inner_system: std::ptr::addr_of!(self.inner),
}
}
pub fn run(&mut self) -> Result<()> {
self.inner.run()
}
pub const fn component_instance(&self) -> &ComponentInstance {
self.inner.component_instance()
}
}
pub struct EventLoopAdapter {
inner_system: *const WaylandWindowingSystem,
}
unsafe impl Send for EventLoopAdapter {}
unsafe impl Sync for EventLoopAdapter {}
impl EventLoopAdapter {
pub fn insert_source_with_adapter<S, F, R>(
&self,
source: S,
mut callback: F,
) -> StdResult<RegistrationToken, InsertError<S>>
where
S: EventSource<Ret = R> + 'static,
F: FnMut(S::Event, &mut S::Metadata, RuntimeStateAdapter) -> R + 'static,
{
let inner_system = unsafe { &*self.inner_system };
let loop_handle = inner_system.event_loop_handle();
loop_handle.insert_source(source, move |event, metadata, window_state| {
let runtime_state = RuntimeStateAdapter {
window_state: std::ptr::addr_of_mut!(*window_state),
};
callback(event, metadata, runtime_state)
})
}
}
pub struct RuntimeStateAdapter {
window_state: *mut WindowState,
}
impl RuntimeStateAdapter {
#[must_use]
pub fn component_instance(&self) -> &ComponentInstance {
let window_state = unsafe { &*self.window_state };
window_state.component_instance()
}
pub fn render_frame_if_dirty(&self) -> Result<()> {
let window_state = unsafe { &*self.window_state };
window_state.render_frame_if_dirty()
}
}

View file

@ -1 +0,0 @@
pub mod calloop_adapter;

View file

@ -1,10 +1,12 @@
#![allow(clippy::pub_use)]
pub mod errors;
pub mod event_loop;
pub mod rendering;
pub mod wayland;
pub use rendering::femtovg::popup_window::PopupWindow;
pub use rendering::slint_integration::platform::close_current_popup;
pub mod platform {
pub use slint;
pub use slint_interpreter;

View file

@ -1,4 +1,4 @@
use crate::errors::{LayerShikaError, Result};
use crate::errors::{EGLError, LayerShikaError, Result};
use glutin::{
api::egl::{
config::Config,
@ -15,10 +15,10 @@ use glutin::{
use raw_window_handle::{
RawDisplayHandle, RawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle,
};
use slint::{platform::femtovg_renderer::OpenGLInterface, PhysicalSize};
use slint::{PhysicalSize, platform::femtovg_renderer::OpenGLInterface};
use std::{
error::Error,
ffi::{self, c_void, CStr},
ffi::{self, CStr, c_void},
num::NonZeroU32,
ptr::NonNull,
result::Result as StdResult,
@ -63,38 +63,24 @@ impl EGLContextBuilder {
self
}
#[must_use]
#[allow(dead_code)]
pub const fn with_config_template(mut self, config_template: ConfigTemplateBuilder) -> Self {
self.config_template = Some(config_template);
self
}
#[must_use]
#[allow(dead_code)]
pub const fn with_context_attributes(
mut self,
context_attributes: ContextAttributesBuilder,
) -> Self {
self.context_attributes = Some(context_attributes);
self
}
pub fn build(self) -> Result<EGLContext> {
let display_id = self
.display_id
.ok_or_else(|| LayerShikaError::InvalidInput("Display ID is required".into()))?;
.ok_or_else(|| LayerShikaError::InvalidInput {
message: "Display ID is required".into(),
})?;
let surface_id = self
.surface_id
.ok_or_else(|| LayerShikaError::InvalidInput("Surface ID is required".into()))?;
let size = self
.size
.ok_or_else(|| LayerShikaError::InvalidInput("Size is required".into()))?;
.ok_or_else(|| LayerShikaError::InvalidInput {
message: "Surface ID is required".into(),
})?;
let size = self.size.ok_or_else(|| LayerShikaError::InvalidInput {
message: "Size is required".into(),
})?;
let display_handle = create_wayland_display_handle(&display_id)?;
let glutin_display = unsafe { Display::new(display_handle) }.map_err(|e| {
LayerShikaError::EGLContextCreation(format!("Failed to create display: {e}"))
})?;
let glutin_display = unsafe { Display::new(display_handle) }
.map_err(|e| EGLError::DisplayCreation { source: e.into() })?;
let config_template = self.config_template.unwrap_or_default();
@ -109,7 +95,7 @@ impl EGLContextBuilder {
let context = context
.make_current(&surface)
.map_err(|e| LayerShikaError::EGLContextCreation(format!("Unable to activate EGL context: {e}. This may indicate a problem with the graphics drivers.")))?;
.map_err(|e| EGLError::MakeCurrent { source: e.into() })?;
Ok(EGLContext { surface, context })
}
@ -123,9 +109,9 @@ impl EGLContext {
fn ensure_current(&self) -> Result<()> {
if !self.context.is_current() {
self.context.make_current(&self.surface).map_err(|e| {
LayerShikaError::EGLContextCreation(format!("Failed to make context current: {e}"))
})?;
self.context
.make_current(&self.surface)
.map_err(|e| EGLError::MakeCurrent { source: e.into() })?;
}
Ok(())
}
@ -145,7 +131,9 @@ impl Drop for EGLContext {
fn create_wayland_display_handle(display_id: &ObjectId) -> Result<RawDisplayHandle> {
let display = NonNull::new(display_id.as_ptr().cast::<c_void>()).ok_or_else(|| {
LayerShikaError::InvalidInput("Failed to create NonNull pointer for display".into())
LayerShikaError::InvalidInput {
message: "Failed to create NonNull pointer for display".into(),
}
})?;
let handle = WaylandDisplayHandle::new(display);
Ok(RawDisplayHandle::Wayland(handle))
@ -156,10 +144,10 @@ fn select_config(
config_template: ConfigTemplateBuilder,
) -> Result<Config> {
let mut configs = unsafe { glutin_display.find_configs(config_template.build()) }
.map_err(|e| LayerShikaError::EGLContextCreation(format!("Failed to find configs: {e}")))?;
configs.next().ok_or_else(|| {
LayerShikaError::EGLContextCreation("No compatible EGL configurations found.".into())
})
.map_err(|e| EGLError::ConfigSelection { source: e.into() })?;
configs
.next()
.ok_or_else(|| EGLError::NoCompatibleConfig.into())
}
fn create_context(
@ -168,12 +156,14 @@ fn create_context(
context_attributes: ContextAttributesBuilder,
) -> Result<NotCurrentContext> {
unsafe { glutin_display.create_context(config, &context_attributes.build(None)) }
.map_err(|e| LayerShikaError::EGLContextCreation(format!("Failed to create context: {e}")))
.map_err(|e| EGLError::ContextCreation { source: e.into() }.into())
}
fn create_surface_handle(surface_id: &ObjectId) -> Result<RawWindowHandle> {
let surface = NonNull::new(surface_id.as_ptr().cast::<c_void>()).ok_or_else(|| {
LayerShikaError::InvalidInput("Failed to create NonNull pointer for surface".into())
LayerShikaError::InvalidInput {
message: "Failed to create NonNull pointer for surface".into(),
}
})?;
let handle = WaylandWindowHandle::new(surface);
Ok(RawWindowHandle::Wayland(handle))
@ -185,18 +175,19 @@ fn create_surface(
surface_handle: RawWindowHandle,
size: PhysicalSize,
) -> Result<Surface<WindowSurface>> {
let width = NonZeroU32::new(size.width)
.ok_or_else(|| LayerShikaError::InvalidInput("Width cannot be zero".into()))?;
let width = NonZeroU32::new(size.width).ok_or_else(|| LayerShikaError::InvalidInput {
message: "Width cannot be zero".into(),
})?;
let height = NonZeroU32::new(size.height)
.ok_or_else(|| LayerShikaError::InvalidInput("Height cannot be zero".into()))?;
let height = NonZeroU32::new(size.height).ok_or_else(|| LayerShikaError::InvalidInput {
message: "Height cannot be zero".into(),
})?;
let attrs =
SurfaceAttributesBuilder::<WindowSurface>::new().build(surface_handle, width, height);
unsafe { glutin_display.create_window_surface(config, &attrs) }.map_err(|e| {
LayerShikaError::EGLContextCreation(format!("Failed to create window surface: {e}"))
})
unsafe { glutin_display.create_window_surface(config, &attrs) }
.map_err(|e| EGLError::SurfaceCreation { source: e.into() }.into())
}
unsafe impl OpenGLInterface for EGLContext {
@ -206,9 +197,9 @@ unsafe impl OpenGLInterface for EGLContext {
}
fn swap_buffers(&self) -> StdResult<(), Box<dyn Error + Send + Sync>> {
self.surface.swap_buffers(&self.context).map_err(|e| {
LayerShikaError::EGLContextCreation(format!("Failed to swap buffers: {e}")).into()
})
self.surface
.swap_buffers(&self.context)
.map_err(|e| EGLError::SwapBuffers { source: e.into() }.into())
}
fn resize(

View file

@ -1,9 +1,9 @@
use crate::errors::{LayerShikaError, Result};
use crate::errors::{RenderingError, Result};
use core::ops::Deref;
use log::info;
use slint::{
platform::{femtovg_renderer::FemtoVGRenderer, Renderer, WindowAdapter, WindowEvent},
PhysicalSize, Window, WindowSize,
platform::{Renderer, WindowAdapter, WindowEvent, femtovg_renderer::FemtoVGRenderer},
};
use std::cell::Cell;
use std::rc::{Rc, Weak};
@ -43,7 +43,9 @@ impl FemtoVGWindow {
) {
self.renderer
.render()
.map_err(|e| LayerShikaError::Rendering(format!("Error rendering frame: {e}")))?;
.map_err(|e| RenderingError::Operation {
message: format!("Error rendering frame: {e}"),
})?;
}
Ok(())
}

View file

@ -1,11 +1,12 @@
use crate::errors::{LayerShikaError, Result};
use crate::errors::{RenderingError, Result};
use crate::wayland::surfaces::popup_manager::PopupManager;
use core::ops::Deref;
use log::info;
use slint::{
platform::{femtovg_renderer::FemtoVGRenderer, Renderer, WindowAdapter, WindowEvent},
PhysicalSize, Window, WindowSize,
platform::{Renderer, WindowAdapter, WindowEvent, femtovg_renderer::FemtoVGRenderer},
};
use std::cell::Cell;
use std::cell::{Cell, RefCell};
use std::rc::{Rc, Weak};
use super::main_window::RenderState;
@ -17,6 +18,8 @@ pub struct PopupWindow {
render_state: Cell<RenderState>,
size: Cell<PhysicalSize>,
scale_factor: Cell<f32>,
popup_manager: RefCell<Weak<PopupManager>>,
popup_key: Cell<Option<usize>>,
}
#[allow(dead_code)]
@ -31,10 +34,37 @@ impl PopupWindow {
render_state: Cell::new(RenderState::Clean),
size: Cell::new(PhysicalSize::default()),
scale_factor: Cell::new(1.),
popup_manager: RefCell::new(Weak::new()),
popup_key: Cell::new(None),
}
})
}
pub fn set_popup_manager(&self, popup_manager: Weak<PopupManager>, key: usize) {
*self.popup_manager.borrow_mut() = popup_manager;
self.popup_key.set(Some(key));
}
pub fn close_popup(&self) {
info!("Closing popup window - cleaning up resources");
if let Err(e) = self.window.hide() {
info!("Failed to hide popup window: {e}");
}
if let Some(popup_manager) = self.popup_manager.borrow().upgrade() {
if let Some(key) = self.popup_key.get() {
info!("Destroying popup with key {key}");
popup_manager.destroy_popup(key);
}
}
*self.popup_manager.borrow_mut() = Weak::new();
self.popup_key.set(None);
info!("Popup window cleanup complete");
}
pub fn render_frame_if_dirty(&self) -> Result<()> {
if matches!(
self.render_state.replace(RenderState::Clean),
@ -45,9 +75,11 @@ impl PopupWindow {
self.size.get(),
self.scale_factor.get()
);
self.renderer.render().map_err(|e| {
LayerShikaError::Rendering(format!("Error rendering popup frame: {e}"))
})?;
self.renderer
.render()
.map_err(|e| RenderingError::Operation {
message: format!("Error rendering popup frame: {e}"),
})?;
info!("Popup frame rendered successfully");
}
Ok(())
@ -96,3 +128,9 @@ impl Deref for PopupWindow {
&self.window
}
}
impl Drop for PopupWindow {
fn drop(&mut self) {
info!("PopupWindow being dropped - resources will be released");
}
}

View file

@ -1,28 +1,51 @@
use slint::{
platform::{Platform, WindowAdapter},
PlatformError,
platform::{Platform, WindowAdapter},
};
use std::cell::{Cell, RefCell};
use std::rc::{Rc, Weak};
use crate::rendering::femtovg::main_window::FemtoVGWindow;
use crate::rendering::femtovg::popup_window::PopupWindow;
type PopupCreator = dyn Fn() -> Result<Rc<dyn WindowAdapter>, PlatformError>;
thread_local! {
static CURRENT_PLATFORM: RefCell<Option<Weak<CustomSlintPlatform>>> = const { RefCell::new(None) };
}
pub fn close_current_popup() {
CURRENT_PLATFORM.with(|platform| {
if let Some(weak_platform) = platform.borrow().as_ref() {
if let Some(strong_platform) = weak_platform.upgrade() {
strong_platform.close_current_popup();
}
}
});
}
pub struct CustomSlintPlatform {
main_window: Weak<FemtoVGWindow>,
popup_creator: RefCell<Option<Rc<PopupCreator>>>,
first_call: Cell<bool>,
last_popup: RefCell<Option<Weak<PopupWindow>>>,
}
impl CustomSlintPlatform {
#[must_use]
pub fn new(window: &Rc<FemtoVGWindow>) -> Self {
Self {
pub fn new(window: &Rc<FemtoVGWindow>) -> Rc<Self> {
let platform = Rc::new(Self {
main_window: Rc::downgrade(window),
popup_creator: RefCell::new(None),
first_call: Cell::new(true),
}
last_popup: RefCell::new(None),
});
CURRENT_PLATFORM.with(|current| {
*current.borrow_mut() = Some(Rc::downgrade(&platform));
});
platform
}
#[allow(dead_code)]
@ -32,6 +55,19 @@ impl CustomSlintPlatform {
{
*self.popup_creator.borrow_mut() = Some(Rc::new(creator));
}
pub fn set_last_popup(&self, popup: &Rc<PopupWindow>) {
*self.last_popup.borrow_mut() = Some(Rc::downgrade(popup));
}
pub fn close_current_popup(&self) {
if let Some(weak_popup) = self.last_popup.borrow().as_ref() {
if let Some(popup) = weak_popup.upgrade() {
popup.close_popup();
}
}
*self.last_popup.borrow_mut() = None;
}
}
impl Platform for CustomSlintPlatform {

View file

@ -1,4 +1,6 @@
use layer_shika_domain::config::{AnchorEdges, Layer, Margins, WindowConfig as DomainWindowConfig};
use layer_shika_domain::prelude::{
AnchorEdges, Layer, Margins, WindowConfig as DomainWindowConfig,
};
use slint_interpreter::ComponentDefinition;
use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::{
zwlr_layer_shell_v1::{self},

View file

@ -1,10 +1,9 @@
use crate::errors::{LayerShikaError, Result};
use crate::errors::Result;
use std::rc::Rc;
use wayland_client::{Connection, EventQueue};
pub fn initialize_wayland<S>() -> Result<(Rc<Connection>, EventQueue<S>)> {
let connection =
Rc::new(Connection::connect_to_env().map_err(LayerShikaError::WaylandConnection)?);
let connection = Rc::new(Connection::connect_to_env()?);
let event_queue = connection.new_event_queue();
Ok((connection, event_queue))
}

View file

@ -1,9 +1,9 @@
use crate::wayland::surfaces::surface_state::WindowState;
use crate::impl_empty_dispatch;
use crate::wayland::surfaces::surface_state::WindowState;
use log::info;
use slint::{
platform::{PointerEventButton, WindowEvent},
PhysicalSize,
platform::{PointerEventButton, WindowEvent},
};
use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::{
zwlr_layer_shell_v1::ZwlrLayerShellV1,
@ -11,6 +11,7 @@ use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::{
};
use wayland_client::WEnum;
use wayland_client::{
Connection, Dispatch, Proxy, QueueHandle,
globals::GlobalListContents,
protocol::{
wl_compositor::WlCompositor,
@ -20,7 +21,6 @@ use wayland_client::{
wl_seat::WlSeat,
wl_surface::WlSurface,
},
Connection, Dispatch, Proxy, QueueHandle,
};
use wayland_protocols::wp::fractional_scale::v1::client::{
wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1,
@ -138,7 +138,9 @@ impl Dispatch<WlOutput, ()> for WindowState {
model,
transform,
} => {
info!("WlOutput geometry: x={x}, y={y}, physical_width={physical_width}, physical_height={physical_height}, subpixel={subpixel:?}, make={make:?}, model={model:?}, transform={transform:?}");
info!(
"WlOutput geometry: x={x}, y={y}, physical_width={physical_width}, physical_height={physical_height}, subpixel={subpixel:?}, make={make:?}, model={model:?}, transform={transform:?}"
);
}
wl_output::Event::Done => {
info!("WlOutput done");
@ -270,16 +272,16 @@ impl Dispatch<XdgPopup, ()> for WindowState {
xdg_popup::Event::PopupDone => {
info!("XdgPopup dismissed by compositor");
let popup_id = xdg_popup.id();
let popup_index = state
let popup_key = state
.popup_manager()
.as_ref()
.and_then(|pm| pm.find_popup_index_by_xdg_popup_id(&popup_id));
.and_then(|pm| pm.find_popup_key_by_xdg_popup_id(&popup_id));
if let Some(index) = popup_index {
info!("Destroying popup at index {index}");
state.clear_active_window_if_popup(index);
if let Some(key) = popup_key {
info!("Destroying popup with key {key}");
state.clear_active_window_if_popup(key);
if let Some(popup_manager) = &state.popup_manager() {
popup_manager.destroy_popup(index);
popup_manager.destroy_popup(key);
}
}
}

View file

@ -23,8 +23,7 @@ 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, ())
.map_err(|e| LayerShikaError::WaylandDispatch(e.to_string()))?;
let $name: $interface = $global_list.bind($queue_handle, $version, ())?;
)+
Ok::<($($interface,)+), LayerShikaError>(($($name,)+))
}

View file

@ -29,7 +29,7 @@ impl GlobalContext {
) -> Result<Self, LayerShikaError> {
let global_list = registry_queue_init::<WindowState>(connection)
.map(|(global_list, _)| global_list)
.map_err(|e| LayerShikaError::GlobalInitialization(e.to_string()))?;
.map_err(|e| LayerShikaError::GlobalInitialization { source: e })?;
let (compositor, output, layer_shell, seat) = bind_globals!(
&global_list,

View file

@ -1,29 +1,36 @@
use crate::wayland::{
config::{LayerSurfaceParams, WaylandWindowConfig},
globals::context::GlobalContext,
surfaces::popup_manager::{PopupContext, PopupManager},
surfaces::{surface_builder::WindowStateBuilder, surface_state::WindowState},
surfaces::layer_surface::{SurfaceCtx, SurfaceSetupParams},
};
use crate::{
errors::{LayerShikaError, Result},
rendering::{
egl::context::EGLContext, femtovg::main_window::FemtoVGWindow, slint_integration::platform::CustomSlintPlatform,
surfaces::popup_manager::{PopupContext, PopupManager},
surfaces::{
surface_builder::WindowStateBuilder,
surface_state::{SharedPointerSerial, WindowState},
},
};
use crate::{
errors::{EventLoopError, LayerShikaError, RenderingError, Result},
rendering::{
egl::context::EGLContext, femtovg::main_window::FemtoVGWindow,
slint_integration::platform::CustomSlintPlatform,
},
};
use core::result::Result as CoreResult;
use layer_shika_domain::errors::DomainError;
use layer_shika_domain::ports::windowing::WindowingSystemPort;
use log::{error, info};
use slint::{
platform::{femtovg_renderer::FemtoVGRenderer, update_timers_and_animations, WindowAdapter},
LogicalPosition, PhysicalSize, PlatformError, WindowPosition,
platform::{WindowAdapter, femtovg_renderer::FemtoVGRenderer, update_timers_and_animations},
};
use slint_interpreter::ComponentInstance;
use smithay_client_toolkit::reexports::calloop::{
generic::Generic, EventLoop, Interest, LoopHandle, Mode, PostAction,
EventLoop, Interest, LoopHandle, Mode, PostAction, generic::Generic,
};
use std::rc::Rc;
use wayland_client::{
protocol::{wl_display::WlDisplay, wl_surface::WlSurface},
Connection, EventQueue, Proxy,
protocol::{wl_display::WlDisplay, wl_surface::WlSurface},
};
pub struct WaylandWindowingSystem {
@ -40,7 +47,7 @@ impl WaylandWindowingSystem {
let (connection, event_queue) = Self::init_wayland_connection()?;
let (state, global_ctx, platform) = Self::init_state(config, &connection, &event_queue)?;
let event_loop =
EventLoop::try_new().map_err(|e| LayerShikaError::EventLoop(e.to_string()))?;
EventLoop::try_new().map_err(|e| EventLoopError::Creation { source: e })?;
let popup_context = PopupContext::new(
global_ctx.compositor,
@ -53,8 +60,15 @@ impl WaylandWindowingSystem {
);
let popup_manager = Rc::new(PopupManager::new(popup_context, state.scale_factor()));
let shared_serial = Rc::new(SharedPointerSerial::new());
Self::setup_popup_creator(&popup_manager, &platform, &state, &event_queue);
Self::setup_popup_creator(
&popup_manager,
&platform,
&state,
&event_queue,
&shared_serial,
);
Ok(Self {
state,
@ -67,13 +81,13 @@ impl WaylandWindowingSystem {
system
.state
.set_popup_manager(Rc::clone(&system.popup_manager));
system.state.set_shared_pointer_serial(shared_serial);
system
})
}
fn init_wayland_connection() -> Result<(Rc<Connection>, EventQueue<WindowState>)> {
let connection =
Rc::new(Connection::connect_to_env().map_err(LayerShikaError::WaylandConnection)?);
let connection = Rc::new(Connection::connect_to_env()?);
let event_queue = connection.new_event_queue();
Ok((connection, event_queue))
}
@ -83,8 +97,7 @@ impl WaylandWindowingSystem {
connection: &Connection,
event_queue: &EventQueue<WindowState>,
) -> Result<(WindowState, GlobalContext, Rc<CustomSlintPlatform>)> {
let global_ctx = GlobalContext::initialize(connection, &event_queue.handle())
.map_err(|e| LayerShikaError::GlobalInitialization(e.to_string()))?;
let global_ctx = GlobalContext::initialize(connection, &event_queue.handle())?;
let layer_surface_params = LayerSurfaceParams {
anchor: config.anchor,
@ -107,22 +120,20 @@ impl WaylandWindowingSystem {
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.clone());
let window =
Self::initialize_renderer(&surface_ctx.surface, &connection.display(), &config)
.map_err(|e| LayerShikaError::EGLContextCreation(e.to_string()))?;
Self::initialize_renderer(&surface_ctx.surface, &connection.display(), &config)?;
let pointer = Rc::new(global_ctx.seat.get_pointer(&event_queue.handle(), ()));
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_connection(Rc::new(connection.clone()))
.with_pointer(Rc::clone(&pointer))
.with_window(window);
if let Some(fs) = &surface_ctx.fractional_scale {
@ -133,9 +144,12 @@ impl WaylandWindowingSystem {
builder = builder.with_viewport(Rc::clone(vp));
}
let (state, platform) = builder
.build()
.map_err(|e| LayerShikaError::WindowConfiguration(e.to_string()))?;
let (state, platform) =
builder
.build()
.map_err(|e| LayerShikaError::WindowConfiguration {
message: e.to_string(),
})?;
Ok((state, global_ctx, platform))
}
@ -145,6 +159,7 @@ impl WaylandWindowingSystem {
platform: &Rc<CustomSlintPlatform>,
state: &WindowState,
event_queue: &EventQueue<WindowState>,
shared_serial: &Rc<SharedPointerSerial>,
) {
if !popup_manager.has_xdg_shell() {
info!("xdg-shell not available, popups will not be supported");
@ -154,16 +169,25 @@ impl WaylandWindowingSystem {
info!("Setting up popup creator with xdg-shell support");
let popup_manager_clone = Rc::clone(popup_manager);
let platform_weak = Rc::downgrade(platform);
let layer_surface = state.layer_surface();
let queue_handle = event_queue.handle();
let serial_holder = Rc::clone(shared_serial);
platform.set_popup_creator(move || {
info!("Popup creator called! Creating popup window...");
let result = popup_manager_clone
.create_popup(&queue_handle, &layer_surface, 0)
.map(|w| w as Rc<dyn WindowAdapter>)
.map_err(|e| PlatformError::Other(format!("Failed to create popup: {e}")));
let serial = serial_holder.get();
let popup_window = popup_manager_clone
.create_popup(&queue_handle, &layer_surface, serial)
.map_err(|e| PlatformError::Other(format!("Failed to create popup: {e}")))?;
if let Some(platform) = platform_weak.upgrade() {
platform.set_last_popup(&popup_window);
}
let result = Ok(popup_window as Rc<dyn WindowAdapter>);
match &result {
Ok(_) => info!("Popup created successfully"),
@ -185,11 +209,10 @@ impl WaylandWindowingSystem {
.with_display_id(display.id())
.with_surface_id(surface.id())
.with_size(init_size)
.build()
.map_err(|e| LayerShikaError::EGLContextCreation(e.to_string()))?;
.build()?;
let renderer = FemtoVGRenderer::new(context)
.map_err(|e| LayerShikaError::FemtoVGRendererCreation(e.to_string()))?;
.map_err(|e| LayerShikaError::FemtoVGRendererCreation { source: e })?;
let femtovg_window = FemtoVGWindow::new(renderer);
femtovg_window.set_size(slint::WindowSize::Physical(init_size));
@ -207,21 +230,18 @@ impl WaylandWindowingSystem {
info!("Starting WindowingSystem main loop");
info!("Processing initial Wayland configuration events");
while self
.event_queue
.blocking_dispatch(&mut self.state)
.map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?
> 0
{
while self.event_queue.blocking_dispatch(&mut self.state)? > 0 {
self.connection
.flush()
.map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?;
.map_err(|e| LayerShikaError::WaylandProtocol { source: e })?;
update_timers_and_animations();
self.state
.window()
.render_frame_if_dirty()
.map_err(|e| LayerShikaError::Rendering(e.to_string()))?;
.map_err(|e| RenderingError::Operation {
message: e.to_string(),
})?;
}
self.setup_wayland_event_source()?;
@ -238,7 +258,9 @@ impl WaylandWindowingSystem {
error!("Error processing events: {e}");
}
})
.map_err(|e| LayerShikaError::EventLoop(e.to_string()))
.map_err(|e| EventLoopError::Execution { source: e })?;
Ok(())
}
fn setup_wayland_event_source(&self) -> Result<()> {
@ -250,7 +272,9 @@ impl WaylandWindowingSystem {
Generic::new(connection, Interest::READ, Mode::Level),
move |_, _connection, _shared_data| Ok(PostAction::Continue),
)
.map_err(|e| LayerShikaError::EventLoop(e.to_string()))?;
.map_err(|e| EventLoopError::InsertSource {
message: format!("{e:?}"),
})?;
Ok(())
}
@ -264,27 +288,29 @@ impl WaylandWindowingSystem {
if let Some(guard) = event_queue.prepare_read() {
guard
.read()
.map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?;
.map_err(|e| LayerShikaError::WaylandProtocol { source: e })?;
}
event_queue
.dispatch_pending(shared_data)
.map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?;
event_queue.dispatch_pending(shared_data)?;
update_timers_and_animations();
shared_data
.window()
.render_frame_if_dirty()
.map_err(|e| LayerShikaError::Rendering(e.to_string()))?;
.map_err(|e| RenderingError::Operation {
message: e.to_string(),
})?;
popup_manager
.render_popups()
.map_err(|e| LayerShikaError::Rendering(e.to_string()))?;
.map_err(|e| RenderingError::Operation {
message: e.to_string(),
})?;
connection
.flush()
.map_err(|e| LayerShikaError::WaylandProtocol(e.to_string()))?;
.map_err(|e| LayerShikaError::WaylandProtocol { source: e })?;
Ok(())
}
@ -293,12 +319,15 @@ impl WaylandWindowingSystem {
self.state.component_instance()
}
#[allow(dead_code)]
pub(crate) fn window(&self) -> Rc<FemtoVGWindow> {
self.state.window()
}
pub const fn state(&self) -> &WindowState {
&self.state
}
}
impl WindowingSystemPort for WaylandWindowingSystem {
fn run(&mut self) -> CoreResult<(), DomainError> {
WaylandWindowingSystem::run(self).map_err(|e| DomainError::Adapter {
source: Box::new(e),
})
}
}

View file

@ -6,8 +6,8 @@ use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::{
};
use std::rc::Rc;
use wayland_client::{
protocol::{wl_compositor::WlCompositor, wl_output::WlOutput, wl_surface::WlSurface},
QueueHandle,
protocol::{wl_compositor::WlCompositor, wl_output::WlOutput, wl_surface::WlSurface},
};
use wayland_protocols::wp::fractional_scale::v1::client::{
wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1,

View file

@ -2,6 +2,7 @@ use crate::errors::{LayerShikaError, Result};
use crate::rendering::egl::context::EGLContext;
use crate::rendering::femtovg::popup_window::PopupWindow;
use log::info;
use slab::Slab;
use slint::{platform::femtovg_renderer::FemtoVGRenderer, PhysicalSize, WindowSize};
use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1;
use std::cell::RefCell;
@ -25,20 +26,18 @@ pub struct PopupContext {
fractional_scale_manager: Option<WpFractionalScaleManagerV1>,
viewporter: Option<WpViewporter>,
display: WlDisplay,
#[allow(dead_code)]
connection: Rc<Connection>,
}
impl PopupContext {
#[must_use]
pub const fn new(
pub fn new(
compositor: WlCompositor,
xdg_wm_base: Option<XdgWmBase>,
seat: WlSeat,
fractional_scale_manager: Option<WpFractionalScaleManagerV1>,
viewporter: Option<WpViewporter>,
display: WlDisplay,
connection: Rc<Connection>,
_connection: Rc<Connection>,
) -> Self {
Self {
compositor,
@ -47,7 +46,6 @@ impl PopupContext {
fractional_scale_manager,
viewporter,
display,
connection,
}
}
}
@ -59,7 +57,7 @@ struct ActivePopup {
pub struct PopupManager {
context: PopupContext,
popups: RefCell<Vec<ActivePopup>>,
popups: RefCell<Slab<ActivePopup>>,
current_scale_factor: RefCell<f32>,
current_output_size: RefCell<PhysicalSize>,
}
@ -69,7 +67,7 @@ impl PopupManager {
pub const fn new(context: PopupContext, initial_scale_factor: f32) -> Self {
Self {
context,
popups: RefCell::new(Vec::new()),
popups: RefCell::new(Slab::new()),
current_scale_factor: RefCell::new(initial_scale_factor),
current_output_size: RefCell::new(PhysicalSize::new(0, 0)),
}
@ -84,18 +82,22 @@ impl PopupManager {
}
pub fn create_popup(
&self,
self: &Rc<Self>,
queue_handle: &QueueHandle<WindowState>,
parent_layer_surface: &ZwlrLayerSurfaceV1,
last_pointer_serial: u32,
) -> Result<Rc<PopupWindow>> {
let xdg_wm_base = self.context.xdg_wm_base.as_ref().ok_or_else(|| {
LayerShikaError::WaylandProtocol("xdg-shell not available for popups".into())
LayerShikaError::WindowConfiguration {
message: "xdg-shell not available for popups".into(),
}
})?;
let scale_factor = *self.current_scale_factor.borrow();
let output_size = *self.current_output_size.borrow();
info!("Creating popup window with scale factor {scale_factor} and output size {output_size:?}");
info!(
"Creating popup window with scale factor {scale_factor} and output size {output_size:?}"
);
#[allow(clippy::cast_precision_loss)]
let logical_size = slint::LogicalSize::new(
@ -129,28 +131,28 @@ impl PopupManager {
.with_display_id(self.context.display.id())
.with_surface_id(popup_surface.surface.id())
.with_size(popup_size)
.build()
.map_err(|e| LayerShikaError::EGLContextCreation(e.to_string()))?;
.build()?;
let renderer = FemtoVGRenderer::new(context)
.map_err(|e| LayerShikaError::FemtoVGRendererCreation(e.to_string()))?;
.map_err(|e| LayerShikaError::FemtoVGRendererCreation { source: e })?;
let popup_window = PopupWindow::new(renderer);
popup_window.set_scale_factor(scale_factor);
popup_window.set_size(WindowSize::Logical(logical_size));
info!("Popup window created successfully");
self.popups.borrow_mut().push(ActivePopup {
let key = self.popups.borrow_mut().insert(ActivePopup {
surface: popup_surface,
window: Rc::clone(&popup_window),
});
popup_window.set_popup_manager(Rc::downgrade(self), key);
info!("Popup window created successfully with key {key}");
Ok(popup_window)
}
pub fn render_popups(&self) -> Result<()> {
for popup in self.popups.borrow().iter() {
for (_key, popup) in self.popups.borrow().iter() {
popup.window.render_frame_if_dirty()?;
}
Ok(())
@ -160,61 +162,51 @@ impl PopupManager {
self.context.xdg_wm_base.is_some()
}
#[allow(dead_code)]
pub fn popup_count(&self) -> usize {
self.popups.borrow().len()
}
pub fn mark_all_popups_dirty(&self) {
for popup in self.popups.borrow().iter() {
for (_key, popup) in self.popups.borrow().iter() {
popup.window.request_redraw();
}
}
pub fn find_popup_index_by_surface_id(&self, surface_id: &ObjectId) -> Option<usize> {
for (index, popup) in self.popups.borrow().iter().enumerate() {
if popup.surface.surface.id() == *surface_id {
return Some(index);
}
}
None
pub fn find_popup_key_by_surface_id(&self, surface_id: &ObjectId) -> Option<usize> {
self.popups
.borrow()
.iter()
.find_map(|(key, popup)| (popup.surface.surface.id() == *surface_id).then_some(key))
}
pub fn find_popup_index_by_fractional_scale_id(
pub fn find_popup_key_by_fractional_scale_id(
&self,
fractional_scale_id: &ObjectId,
) -> Option<usize> {
for (index, popup) in self.popups.borrow().iter().enumerate() {
if let Some(ref fs) = popup.surface.fractional_scale {
if fs.id() == *fractional_scale_id {
return Some(index);
}
}
}
None
self.popups.borrow().iter().find_map(|(key, popup)| {
popup
.surface
.fractional_scale
.as_ref()
.filter(|fs| fs.id() == *fractional_scale_id)
.map(|_| key)
})
}
pub fn get_popup_window(&self, index: usize) -> Option<Rc<PopupWindow>> {
pub fn get_popup_window(&self, key: usize) -> Option<Rc<PopupWindow>> {
self.popups
.borrow()
.get(index)
.get(key)
.map(|popup| Rc::clone(&popup.window))
}
pub fn destroy_popup(&self, index: usize) {
let mut popups = self.popups.borrow_mut();
if index < popups.len() {
info!("Destroying popup at index {index}");
popups.remove(index);
pub fn destroy_popup(&self, key: usize) {
if let Some(popup) = self.popups.borrow_mut().try_remove(key) {
info!("Destroying popup with key {key}");
popup.surface.destroy();
}
}
pub fn find_popup_index_by_xdg_popup_id(&self, xdg_popup_id: &ObjectId) -> Option<usize> {
for (index, popup) in self.popups.borrow().iter().enumerate() {
if popup.surface.xdg_popup.id() == *xdg_popup_id {
return Some(index);
}
}
None
pub fn find_popup_key_by_xdg_popup_id(&self, xdg_popup_id: &ObjectId) -> Option<usize> {
self.popups
.borrow()
.iter()
.find_map(|(key, popup)| (popup.surface.xdg_popup.id() == *xdg_popup_id).then_some(key))
}
}

View file

@ -133,4 +133,11 @@ impl PopupSurface {
info!("Grabbing popup with serial {serial}");
self.xdg_popup.grab(seat, serial);
}
pub fn destroy(&self) {
info!("Destroying popup surface");
self.xdg_popup.destroy();
self.xdg_surface.destroy();
self.surface.destroy();
}
}

View file

@ -6,7 +6,7 @@ use slint::{
};
use slint_interpreter::ComponentDefinition;
use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1;
use wayland_client::{protocol::{wl_output::WlOutput, wl_pointer::WlPointer, wl_surface::WlSurface}, Connection};
use wayland_client::{protocol::{wl_pointer::WlPointer, wl_surface::WlSurface}, Connection};
use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1;
use wayland_protocols::wp::viewporter::client::wp_viewport::WpViewport;
use crate::errors::{LayerShikaError, Result};
@ -32,7 +32,6 @@ pub struct WindowStateBuilder {
pub size: Option<PhysicalSize>,
pub output_size: Option<PhysicalSize>,
pub pointer: Option<Rc<WlPointer>>,
pub output: Option<Rc<WlOutput>>,
pub window: Option<Rc<FemtoVGWindow>>,
pub connection: Option<Rc<Connection>>,
pub scale_factor: f32,
@ -76,12 +75,6 @@ impl WindowStateBuilder {
self
}
#[must_use]
pub fn with_output(mut self, output: Rc<WlOutput>) -> Self {
self.output = Some(output);
self
}
#[must_use]
pub fn with_window(mut self, window: Rc<FemtoVGWindow>) -> Self {
self.window = Some(window);
@ -131,13 +124,13 @@ impl WindowStateBuilder {
}
pub fn build(self) -> Result<(WindowState, Rc<CustomSlintPlatform>)> {
let platform =
Rc::new(CustomSlintPlatform::new(self.window.as_ref().ok_or_else(
|| LayerShikaError::InvalidInput("Window is required".into()),
)?));
set_platform(Box::new(PlatformWrapper(Rc::clone(&platform)))).map_err(|e| {
LayerShikaError::PlatformSetup(format!("Failed to set platform: {e:?}"))
})?;
let platform = CustomSlintPlatform::new(self.window.as_ref().ok_or_else(|| {
LayerShikaError::InvalidInput {
message: "Window is required".into(),
}
})?);
set_platform(Box::new(PlatformWrapper(Rc::clone(&platform))))
.map_err(|e| LayerShikaError::PlatformSetup { source: e })?;
let state = WindowState::new(self)?;
Ok((state, platform))
@ -155,7 +148,6 @@ impl Default for WindowStateBuilder {
size: None,
output_size: None,
pointer: None,
output: None,
window: None,
connection: None,
scale_factor: 1.0,

View file

@ -1,4 +1,5 @@
use std::rc::Rc;
use std::cell::RefCell;
use super::surface_builder::WindowStateBuilder;
use super::dimensions::SurfaceDimensionsExt;
use super::popup_manager::PopupManager;
@ -8,16 +9,45 @@ use crate::wayland::managed_proxies::{
};
use crate::rendering::femtovg::main_window::FemtoVGWindow;
use crate::errors::{LayerShikaError, Result};
use core::result::Result as CoreResult;
use layer_shika_domain::errors::DomainError;
use layer_shika_domain::ports::windowing::RuntimeStatePort;
use layer_shika_domain::surface_dimensions::SurfaceDimensions;
use log::info;
use slint::{LogicalPosition, PhysicalSize, ComponentHandle};
use slint::platform::{WindowAdapter, WindowEvent};
use slint_interpreter::ComponentInstance;
use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1;
use wayland_client::{protocol::{wl_output::WlOutput, wl_surface::WlSurface}, Proxy};
use wayland_client::{protocol::wl_surface::WlSurface, Proxy};
use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1;
#[derive(Debug)]
pub struct SharedPointerSerial {
serial: RefCell<u32>,
}
impl Default for SharedPointerSerial {
fn default() -> Self {
Self::new()
}
}
impl SharedPointerSerial {
pub const fn new() -> Self {
Self {
serial: RefCell::new(0),
}
}
pub fn update(&self, serial: u32) {
*self.serial.borrow_mut() = serial;
}
pub fn get(&self) -> u32 {
*self.serial.borrow()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ScalingMode {
FractionalWithViewport,
FractionalOnly,
@ -38,14 +68,13 @@ pub struct WindowState {
surface: ManagedWlSurface,
#[allow(dead_code)]
pointer: ManagedWlPointer,
#[allow(dead_code)]
output: Rc<WlOutput>,
size: PhysicalSize,
logical_size: PhysicalSize,
output_size: PhysicalSize,
window: Rc<FemtoVGWindow>,
current_pointer_position: LogicalPosition,
last_pointer_serial: u32,
shared_pointer_serial: Option<Rc<SharedPointerSerial>>,
scale_factor: f32,
height: u32,
exclusive_zone: i32,
@ -55,34 +84,48 @@ pub struct WindowState {
impl WindowState {
pub fn new(builder: WindowStateBuilder) -> Result<Self> {
let component_definition = builder.component_definition.ok_or_else(|| {
LayerShikaError::InvalidInput("Component definition is required".into())
})?;
let component_definition =
builder
.component_definition
.ok_or_else(|| LayerShikaError::InvalidInput {
message: "Component definition is required".into(),
})?;
let window = builder
.window
.ok_or_else(|| LayerShikaError::InvalidInput("Window is required".into()))?;
.ok_or_else(|| LayerShikaError::InvalidInput {
message: "Window is required".into(),
})?;
let component_instance = component_definition
.create()
.map_err(|e| LayerShikaError::SlintComponentCreation(e.to_string()))?;
.map_err(|e| LayerShikaError::SlintComponentCreation { source: e })?;
component_instance
.show()
.map_err(|e| LayerShikaError::SlintComponentCreation(e.to_string()))?;
.map_err(|e| LayerShikaError::SlintComponentCreation { source: e })?;
window.request_redraw();
let connection = builder
.connection
.ok_or_else(|| LayerShikaError::InvalidInput("Connection is required".into()))?;
.ok_or_else(|| LayerShikaError::InvalidInput {
message: "Connection is required".into(),
})?;
let surface_rc = builder
.surface
.ok_or_else(|| LayerShikaError::InvalidInput("Surface is required".into()))?;
let layer_surface_rc = builder
.layer_surface
.ok_or_else(|| LayerShikaError::InvalidInput("Layer surface is required".into()))?;
.ok_or_else(|| LayerShikaError::InvalidInput {
message: "Surface is required".into(),
})?;
let layer_surface_rc =
builder
.layer_surface
.ok_or_else(|| LayerShikaError::InvalidInput {
message: "Layer surface is required".into(),
})?;
let pointer_rc = builder
.pointer
.ok_or_else(|| LayerShikaError::InvalidInput("Pointer is required".into()))?;
.ok_or_else(|| LayerShikaError::InvalidInput {
message: "Pointer is required".into(),
})?;
let viewport = builder
.viewport
@ -102,15 +145,13 @@ impl WindowState {
layer_surface,
surface,
pointer,
output: builder
.output
.ok_or_else(|| LayerShikaError::InvalidInput("Output is required".into()))?,
size: builder.size.unwrap_or_default(),
logical_size: PhysicalSize::default(),
output_size: builder.output_size.unwrap_or_default(),
window,
current_pointer_position: LogicalPosition::default(),
last_pointer_serial: 0,
shared_pointer_serial: None,
scale_factor: builder.scale_factor,
height: builder.height,
exclusive_zone: builder.exclusive_zone,
@ -130,7 +171,7 @@ impl WindowState {
}
#[allow(clippy::cast_precision_loss)]
fn configure_slint_window(&self, dimensions: &SurfaceDimensions, mode: &ScalingMode) {
fn configure_slint_window(&self, dimensions: &SurfaceDimensions, mode: ScalingMode) {
match mode {
ScalingMode::FractionalWithViewport => {
self.window
@ -157,7 +198,7 @@ impl WindowState {
}
#[allow(clippy::cast_possible_wrap)]
fn configure_wayland_surface(&self, dimensions: &SurfaceDimensions, mode: &ScalingMode) {
fn configure_wayland_surface(&self, dimensions: &SurfaceDimensions, mode: ScalingMode) {
match mode {
ScalingMode::FractionalWithViewport => {
self.surface.set_buffer_scale(1);
@ -199,8 +240,8 @@ impl WindowState {
scaling_mode
);
self.configure_slint_window(&dimensions, &scaling_mode);
self.configure_wayland_surface(&dimensions, &scaling_mode);
self.configure_slint_window(&dimensions, scaling_mode);
self.configure_wayland_surface(&dimensions, scaling_mode);
info!("Window physical size: {:?}", self.window.size());
@ -289,8 +330,15 @@ impl WindowState {
self.last_pointer_serial
}
pub const fn set_last_pointer_serial(&mut self, serial: u32) {
pub fn set_last_pointer_serial(&mut self, serial: u32) {
self.last_pointer_serial = serial;
if let Some(ref shared_serial) = self.shared_pointer_serial {
shared_serial.update(serial);
}
}
pub fn set_shared_pointer_serial(&mut self, shared_serial: Rc<SharedPointerSerial>) {
self.shared_pointer_serial = Some(shared_serial);
}
pub fn set_popup_manager(&mut self, popup_manager: Rc<PopupManager>) {
@ -306,8 +354,8 @@ impl WindowState {
}
if let Some(popup_manager) = &self.popup_manager {
if let Some(popup_index) = popup_manager.find_popup_index_by_surface_id(&surface_id) {
self.active_window = Some(ActiveWindow::Popup(popup_index));
if let Some(popup_key) = popup_manager.find_popup_key_by_surface_id(&surface_id) {
self.active_window = Some(ActiveWindow::Popup(popup_key));
return;
}
}
@ -347,10 +395,10 @@ impl WindowState {
}
if let Some(popup_manager) = &self.popup_manager {
if let Some(popup_index) =
popup_manager.find_popup_index_by_fractional_scale_id(&fractional_scale_id)
if let Some(popup_key) =
popup_manager.find_popup_key_by_fractional_scale_id(&fractional_scale_id)
{
if let Some(popup_window) = popup_manager.get_popup_window(popup_index) {
if let Some(popup_window) = popup_manager.get_popup_window(popup_key) {
let new_scale_factor = scale_120ths as f32 / 120.0;
info!("Updating popup scale factor to {new_scale_factor} ({scale_120ths}x)");
popup_window.set_scale_factor(new_scale_factor);
@ -364,8 +412,8 @@ impl WindowState {
self.active_window = None;
}
pub fn clear_active_window_if_popup(&mut self, popup_index: usize) {
if self.active_window == Some(ActiveWindow::Popup(popup_index)) {
pub fn clear_active_window_if_popup(&mut self, popup_key: usize) {
if self.active_window == Some(ActiveWindow::Popup(popup_key)) {
self.active_window = None;
}
}
@ -374,3 +422,11 @@ impl WindowState {
&self.popup_manager
}
}
impl RuntimeStatePort for WindowState {
fn render_frame_if_dirty(&self) -> CoreResult<(), DomainError> {
WindowState::render_frame_if_dirty(self).map_err(|e| DomainError::Adapter {
source: Box::new(e),
})
}
}

View file

@ -1,7 +1,7 @@
use crate::system::WindowingSystem;
use crate::Result;
use crate::system::WindowingSystem;
use layer_shika_adapters::platform::slint_interpreter::ComponentDefinition;
use layer_shika_domain::config::{AnchorEdges, Layer, Margins, WindowConfig};
use layer_shika_domain::prelude::{AnchorEdges, Layer, Margins, WindowConfig};
pub struct NeedsComponent;
pub struct HasComponent {

View file

@ -8,8 +8,10 @@ use layer_shika_domain::errors::DomainError;
use std::result::Result as StdResult;
pub use builder::LayerShika;
pub use layer_shika_adapters::PopupWindow;
pub use layer_shika_adapters::close_current_popup;
pub use layer_shika_adapters::platform::{calloop, slint, slint_interpreter};
pub use layer_shika_domain::config::AnchorEdges;
pub use layer_shika_domain::value_objects::anchor::AnchorEdges;
pub type Result<T> = StdResult<T, Error>;
@ -20,4 +22,10 @@ pub enum Error {
#[error("Domain error: {0}")]
Domain(#[from] DomainError),
#[error("WindowingSystem has been dropped")]
SystemDropped,
#[error("Cannot run while EventLoopHandle exists")]
EventLoopHandleExists,
}

View file

@ -1,18 +1,18 @@
use crate::Result;
use layer_shika_adapters::platform::calloop::{EventSource, InsertError, RegistrationToken};
use crate::{Error, Result};
use layer_shika_adapters::errors::EventLoopError;
use layer_shika_adapters::platform::calloop::{EventSource, RegistrationToken};
use layer_shika_adapters::platform::slint_interpreter::{ComponentDefinition, ComponentInstance};
use layer_shika_adapters::wayland::{
config::WaylandWindowConfig,
shell_adapter::WaylandWindowingSystem,
};
use layer_shika_adapters::event_loop::calloop_adapter::{
EventLoopAdapter, RuntimeStateAdapter, SystemAdapter,
config::WaylandWindowConfig, shell_adapter::WaylandWindowingSystem,
surfaces::surface_state::WindowState,
};
use layer_shika_domain::config::WindowConfig;
use std::cell::{Ref, RefCell};
use std::rc::{Rc, Weak};
use std::result::Result as StdResult;
pub struct EventLoopHandle {
adapter: EventLoopAdapter,
system: Weak<RefCell<WaylandWindowingSystem>>,
}
impl EventLoopHandle {
@ -20,36 +20,47 @@ impl EventLoopHandle {
&self,
source: S,
mut callback: F,
) -> StdResult<RegistrationToken, InsertError<S>>
) -> StdResult<RegistrationToken, Error>
where
S: EventSource<Ret = R> + 'static,
F: FnMut(S::Event, &mut S::Metadata, &mut RuntimeState) -> R + 'static,
F: FnMut(S::Event, &mut S::Metadata, RuntimeState<'_>) -> R + 'static,
{
self.adapter
.insert_source_with_adapter(source, move |event, metadata, adapter| {
let mut runtime_state = RuntimeState { adapter };
callback(event, metadata, &mut runtime_state)
let system = self.system.upgrade().ok_or(Error::SystemDropped)?;
let loop_handle = system.borrow().event_loop_handle();
loop_handle
.insert_source(source, move |event, metadata, window_state| {
let runtime_state = RuntimeState { window_state };
callback(event, metadata, runtime_state)
})
.map_err(|e| {
Error::Adapter(
EventLoopError::InsertSource {
message: format!("{e:?}"),
}
.into(),
)
})
}
}
pub struct RuntimeState {
adapter: RuntimeStateAdapter,
pub struct RuntimeState<'a> {
window_state: &'a mut WindowState,
}
impl RuntimeState {
impl RuntimeState<'_> {
#[must_use]
pub fn component_instance(&self) -> &ComponentInstance {
self.adapter.component_instance()
self.window_state.component_instance()
}
pub fn render_frame_if_dirty(&self) -> Result<()> {
Ok(self.adapter.render_frame_if_dirty()?)
pub fn render_frame_if_dirty(&mut self) -> Result<()> {
Ok(self.window_state.render_frame_if_dirty()?)
}
}
pub struct WindowingSystem {
adapter: SystemAdapter,
inner: Rc<RefCell<WaylandWindowingSystem>>,
}
impl WindowingSystem {
@ -58,26 +69,27 @@ impl WindowingSystem {
config: WindowConfig,
) -> Result<Self> {
let wayland_config = WaylandWindowConfig::from_domain_config(component_definition, config);
let inner_system = WaylandWindowingSystem::new(wayland_config)?;
let adapter = SystemAdapter::new(inner_system);
let inner = WaylandWindowingSystem::new(wayland_config)?;
Ok(Self { adapter })
Ok(Self {
inner: Rc::new(RefCell::new(inner)),
})
}
#[must_use]
pub fn event_loop_handle(&self) -> EventLoopHandle {
EventLoopHandle {
adapter: self.adapter.event_loop_handle(),
system: Rc::downgrade(&self.inner),
}
}
pub fn run(&mut self) -> Result<()> {
self.adapter.run()?;
self.inner.borrow_mut().run()?;
Ok(())
}
#[must_use]
pub const fn component_instance(&self) -> &ComponentInstance {
self.adapter.component_instance()
pub fn component_instance(&self) -> Ref<'_, ComponentInstance> {
Ref::map(self.inner.borrow(), |system| system.component_instance())
}
}

View file

@ -1,10 +1,6 @@
#![allow(clippy::pub_use)]
pub use crate::entities::component::UiComponentHandle;
pub use crate::value_objects::anchor::AnchorEdges;
pub use crate::value_objects::dimensions::WindowHeight;
pub use crate::value_objects::layer::Layer;
pub use crate::value_objects::margins::Margins;
use crate::value_objects::anchor::AnchorEdges;
use crate::value_objects::layer::Layer;
use crate::value_objects::margins::Margins;
#[derive(Debug, Clone)]
pub struct WindowConfig {

View file

@ -1,19 +1,26 @@
use std::error::Error;
use std::result::Result as StdResult;
use thiserror::Error;
use thiserror::Error as ThisError;
pub type Result<T> = StdResult<T, DomainError>;
#[derive(Error, Debug)]
#[derive(ThisError, Debug)]
pub enum DomainError {
#[error("Configuration error: {0}")]
Configuration(String),
#[error("invalid configuration: {message}")]
Configuration { message: String },
#[error("Invalid dimensions: {0}")]
InvalidDimensions(String),
#[error("invalid dimensions {width}x{height}")]
InvalidDimensions { width: u32, height: u32 },
#[error("Invalid input: {0}")]
InvalidInput(String),
#[error("invalid input: {message}")]
InvalidInput { message: String },
#[error("Calculation error: {0}")]
Calculation(String),
#[error("calculation error: {operation} failed - {reason}")]
Calculation { operation: String, reason: String },
#[error("adapter error")]
Adapter {
#[source]
source: Box<dyn Error + Send + Sync>,
},
}

View file

@ -1 +1 @@
pub mod window_events;

View file

@ -1,15 +0,0 @@
#[derive(Debug, Clone)]
pub enum WindowEvent {
Resized { width: u32, height: u32 },
ScaleChanged { scale: f32 },
CloseRequested,
Focused,
Unfocused,
CursorMoved { x: f64, y: f64 },
CursorEntered,
CursorLeft,
MouseButtonPressed { button: u32 },
MouseButtonReleased { button: u32 },
KeyPressed { key: u32 },
KeyReleased { key: u32 },
}

View file

@ -1,6 +1,7 @@
pub mod config;
pub mod entities;
pub mod errors;
pub mod events;
pub mod ports;
pub mod prelude;
pub mod surface_dimensions;
pub mod value_objects;

1
domain/src/ports/mod.rs Normal file
View file

@ -0,0 +1 @@
pub mod windowing;

View file

@ -0,0 +1,9 @@
use crate::errors::DomainError;
pub trait WindowingSystemPort {
fn run(&mut self) -> Result<(), DomainError>;
}
pub trait RuntimeStatePort {
fn render_frame_if_dirty(&self) -> Result<(), DomainError>;
}

12
domain/src/prelude.rs Normal file
View file

@ -0,0 +1,12 @@
#![allow(clippy::pub_use)]
pub use crate::config::WindowConfig;
pub use crate::entities::component::UiComponentHandle;
pub use crate::entities::surface::SurfaceHandle;
pub use crate::entities::window::WindowHandle;
pub use crate::errors::{DomainError, Result};
pub use crate::surface_dimensions::SurfaceDimensions;
pub use crate::value_objects::anchor::AnchorEdges;
pub use crate::value_objects::dimensions::WindowHeight;
pub use crate::value_objects::layer::Layer;
pub use crate::value_objects::margins::Margins;