feat: bare multi-output support

This commit is contained in:
drendog 2025-11-16 23:54:47 +01:00
parent f7cd653605
commit 84121a2162
Signed by: dwenya
GPG key ID: 8DD77074645332D0
16 changed files with 1026 additions and 197 deletions

View file

@ -9,6 +9,7 @@ pub use rendering::femtovg::popup_window::PopupWindow;
pub use wayland::config::WaylandWindowConfig; pub use wayland::config::WaylandWindowConfig;
pub use wayland::facade::{PopupManagerFacade, RuntimeStateFacade, WindowingSystemFacade}; pub use wayland::facade::{PopupManagerFacade, RuntimeStateFacade, WindowingSystemFacade};
pub use wayland::shell_adapter::WaylandWindowingSystem; pub use wayland::shell_adapter::WaylandWindowingSystem;
pub use wayland::surfaces::app_state::AppState;
pub use wayland::surfaces::popup_manager::PopupManager; pub use wayland::surfaces::popup_manager::PopupManager;
pub use wayland::surfaces::surface_state::WindowState; pub use wayland::surfaces::surface_state::WindowState;

View file

@ -2,29 +2,31 @@ use slint::{
PlatformError, PlatformError,
platform::{Platform, WindowAdapter}, platform::{Platform, WindowAdapter},
}; };
use std::cell::{Cell, OnceCell}; use std::cell::{OnceCell, RefCell};
use std::rc::{Rc, Weak}; use std::rc::Rc;
use crate::rendering::femtovg::main_window::FemtoVGWindow; use crate::rendering::femtovg::main_window::FemtoVGWindow;
type PopupCreator = dyn Fn() -> Result<Rc<dyn WindowAdapter>, PlatformError>; type PopupCreator = dyn Fn() -> Result<Rc<dyn WindowAdapter>, PlatformError>;
pub struct CustomSlintPlatform { pub struct CustomSlintPlatform {
main_window: Weak<FemtoVGWindow>, pending_windows: RefCell<Vec<Rc<FemtoVGWindow>>>,
popup_creator: OnceCell<Rc<PopupCreator>>, popup_creator: OnceCell<Rc<PopupCreator>>,
first_call: Cell<bool>,
} }
impl CustomSlintPlatform { impl CustomSlintPlatform {
#[must_use] #[must_use]
pub fn new(window: &Rc<FemtoVGWindow>) -> Rc<Self> { pub fn new(window: &Rc<FemtoVGWindow>) -> Rc<Self> {
Rc::new(Self { Rc::new(Self {
main_window: Rc::downgrade(window), pending_windows: RefCell::new(vec![Rc::clone(window)]),
popup_creator: OnceCell::new(), popup_creator: OnceCell::new(),
first_call: Cell::new(true),
}) })
} }
pub fn add_window(&self, window: Rc<FemtoVGWindow>) {
self.pending_windows.borrow_mut().push(window);
}
pub fn set_popup_creator<F>(&self, creator: F) pub fn set_popup_creator<F>(&self, creator: F)
where where
F: Fn() -> Result<Rc<dyn WindowAdapter>, PlatformError> + 'static, F: Fn() -> Result<Rc<dyn WindowAdapter>, PlatformError> + 'static,
@ -37,12 +39,10 @@ impl CustomSlintPlatform {
impl Platform for CustomSlintPlatform { impl Platform for CustomSlintPlatform {
fn create_window_adapter(&self) -> Result<Rc<dyn WindowAdapter + 'static>, PlatformError> { fn create_window_adapter(&self) -> Result<Rc<dyn WindowAdapter + 'static>, PlatformError> {
if self.first_call.get() { let mut windows = self.pending_windows.borrow_mut();
self.first_call.set(false); if !windows.is_empty() {
self.main_window let window = windows.remove(0);
.upgrade() Ok(window as Rc<dyn WindowAdapter>)
.ok_or(PlatformError::NoPlatform)
.map(|w| w as Rc<dyn WindowAdapter>)
} else if let Some(creator) = self.popup_creator.get() { } else if let Some(creator) = self.popup_creator.get() {
creator() creator()
} else { } else {

View file

@ -0,0 +1,415 @@
use crate::wayland::surfaces::app_state::AppState;
use crate::wayland::surfaces::display_metrics::DisplayMetrics;
use log::info;
use slint::PhysicalSize;
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 wayland_client::WEnum;
use wayland_client::{
Connection, Dispatch, Proxy, QueueHandle,
globals::GlobalListContents,
protocol::{
wl_compositor::WlCompositor,
wl_output::{self, WlOutput},
wl_pointer::{self, WlPointer},
wl_registry::WlRegistry,
wl_seat::WlSeat,
wl_surface::WlSurface,
},
};
use wayland_protocols::wp::fractional_scale::v1::client::{
wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1,
wp_fractional_scale_v1::{self, WpFractionalScaleV1},
};
use wayland_protocols::wp::viewporter::client::{
wp_viewport::WpViewport, wp_viewporter::WpViewporter,
};
use wayland_protocols::xdg::shell::client::{
xdg_popup::{self, XdgPopup},
xdg_positioner::XdgPositioner,
xdg_surface::{self, XdgSurface},
xdg_wm_base::{self, XdgWmBase},
};
impl Dispatch<ZwlrLayerSurfaceV1, ()> for AppState {
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_sign_loss)]
#[allow(clippy::cast_precision_loss)]
fn event(
state: &mut Self,
layer_surface: &ZwlrLayerSurfaceV1,
event: zwlr_layer_surface_v1::Event,
_data: &(),
_conn: &Connection,
_queue_handle: &QueueHandle<Self>,
) {
match event {
zwlr_layer_surface_v1::Event::Configure {
serial,
width,
height,
} => {
info!("Layer surface configured with compositor size: {width}x{height}");
layer_surface.ack_configure(serial);
let layer_surface_id = layer_surface.id();
let Some(window) = state.get_output_by_layer_surface_mut(&layer_surface_id) else {
info!(
"Could not find window for layer surface {:?}",
layer_surface_id
);
return;
};
let output_width = window.output_size().width;
let scale_factor = window.scale_factor();
let target_width = if width == 0 || (width == 1 && output_width > 1) {
if scale_factor > 1.0 {
(output_width as f32 / scale_factor).round() as u32
} else {
output_width
}
} else {
width
};
let target_height = if height > 0 {
height
} else {
let h = window.height();
if scale_factor > 1.0 {
(h as f32 / scale_factor).round() as u32
} else {
h
}
};
let clamped_width = target_width.min(output_width);
info!(
"Using dimensions: {}x{} (clamped from {}x{}, output: {}x{})",
clamped_width,
target_height,
target_width,
target_height,
output_width,
window.output_size().height
);
window.update_size(clamped_width, target_height);
}
zwlr_layer_surface_v1::Event::Closed => {
info!("Layer surface closed");
}
_ => {}
}
}
}
impl Dispatch<WlOutput, ()> for AppState {
fn event(
state: &mut Self,
proxy: &WlOutput,
event: <WlOutput as Proxy>::Event,
_data: &(),
_conn: &Connection,
_qhandle: &QueueHandle<Self>,
) {
match event {
wl_output::Event::Mode { width, height, .. } => {
info!("WlOutput size changed to {width}x{height}");
let width = width.try_into().unwrap_or_default();
let height = height.try_into().unwrap_or_default();
let output_id = proxy.id();
if let Some(window) = state.get_output_by_output_id_mut(&output_id) {
window.set_output_size(PhysicalSize::new(width, height));
}
}
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={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");
}
_ => {}
}
}
}
impl Dispatch<WlPointer, ()> for AppState {
fn event(
state: &mut Self,
_proxy: &WlPointer,
event: <WlPointer as Proxy>::Event,
_data: &(),
_conn: &Connection,
_qhandle: &QueueHandle<Self>,
) {
match event {
wl_pointer::Event::Enter {
serial,
surface,
surface_x,
surface_y,
} => {
info!("Pointer entered surface {:?}", surface.id());
let surface_id = surface.id();
if let Some(window) = state.get_output_by_surface_mut(&surface_id) {
window.set_last_pointer_serial(serial);
window.set_current_pointer_position(surface_x, surface_y);
window.set_entered_surface(&surface);
let position = window.current_pointer_position();
window.dispatch_to_active_window(WindowEvent::PointerMoved { position });
if let Some(key) = state.get_key_by_surface(&surface_id).cloned() {
state.set_active_output(Some(key));
}
} else {
let key = state.get_key_by_popup(&surface_id);
if let Some(window) = state.find_output_by_popup_mut(&surface_id) {
window.set_last_pointer_serial(serial);
window.set_current_pointer_position(surface_x, surface_y);
window.set_entered_surface(&surface);
let position = window.current_pointer_position();
window.dispatch_to_active_window(WindowEvent::PointerMoved { position });
if let Some(key) = key {
state.set_active_output(Some(key));
}
}
}
}
wl_pointer::Event::Motion {
surface_x,
surface_y,
..
} => {
if let Some(output_key) = state.active_output().cloned() {
if let Some(window) = state.get_output_by_key_mut(&output_key) {
window.set_current_pointer_position(surface_x, surface_y);
let position = window.current_pointer_position();
window.dispatch_to_active_window(WindowEvent::PointerMoved { position });
}
}
}
wl_pointer::Event::Leave { .. } => {
if let Some(output_key) = state.active_output().cloned() {
if let Some(window) = state.get_output_by_key_mut(&output_key) {
window.dispatch_to_active_window(WindowEvent::PointerExited);
window.clear_entered_surface();
}
}
state.set_active_output(None);
}
wl_pointer::Event::Button {
serial,
state: button_state,
..
} => {
if let Some(output_key) = state.active_output().cloned() {
if let Some(window) = state.get_output_by_key_mut(&output_key) {
window.set_last_pointer_serial(serial);
let position = window.current_pointer_position();
let event = match button_state {
WEnum::Value(wl_pointer::ButtonState::Pressed) => {
WindowEvent::PointerPressed {
button: PointerEventButton::Left,
position,
}
}
_ => WindowEvent::PointerReleased {
button: PointerEventButton::Left,
position,
},
};
window.dispatch_to_active_window(event);
}
}
}
_ => {}
}
}
}
impl Dispatch<WpFractionalScaleV1, ()> for AppState {
fn event(
state: &mut Self,
proxy: &WpFractionalScaleV1,
event: wp_fractional_scale_v1::Event,
_data: &(),
_conn: &Connection,
_qhandle: &QueueHandle<Self>,
) {
if let wp_fractional_scale_v1::Event::PreferredScale { scale } = event {
let scale_float = DisplayMetrics::scale_factor_from_120ths(scale);
info!("Fractional scale received: {scale_float} ({scale}x)");
for window in state.all_outputs_mut() {
window.update_scale_for_fractional_scale_object(proxy, scale);
}
}
}
}
impl Dispatch<XdgWmBase, ()> for AppState {
fn event(
_state: &mut Self,
xdg_wm_base: &XdgWmBase,
event: xdg_wm_base::Event,
_data: &(),
_conn: &Connection,
_qhandle: &QueueHandle<Self>,
) {
if let xdg_wm_base::Event::Ping { serial } = event {
info!("XdgWmBase ping received, sending pong with serial {serial}");
xdg_wm_base.pong(serial);
}
}
}
impl Dispatch<XdgPopup, ()> for AppState {
fn event(
state: &mut Self,
xdg_popup: &XdgPopup,
event: xdg_popup::Event,
_data: &(),
_conn: &Connection,
_qhandle: &QueueHandle<Self>,
) {
match event {
xdg_popup::Event::Configure {
x,
y,
width,
height,
} => {
info!("XdgPopup Configure: position=({x}, {y}), size=({width}x{height})");
let popup_id = xdg_popup.id();
for window in state.all_outputs_mut() {
if let Some(popup_manager) = window.popup_manager() {
if let Some(handle) = popup_manager.find_by_xdg_popup(&popup_id) {
info!(
"Marking popup with handle {handle:?} as configured after XdgPopup::Configure"
);
popup_manager.mark_popup_configured(handle.key());
popup_manager.mark_all_popups_dirty();
break;
}
}
}
}
xdg_popup::Event::PopupDone => {
info!("XdgPopup dismissed by compositor");
let popup_id = xdg_popup.id();
for window in state.all_outputs_mut() {
let popup_handle = window
.popup_manager()
.as_ref()
.and_then(|pm| pm.find_by_xdg_popup(&popup_id));
if let Some(handle) = popup_handle {
info!("Destroying popup with handle {handle:?}");
if let Some(popup_manager) = window.popup_manager() {
let _result = popup_manager.close(handle);
}
break;
}
}
}
xdg_popup::Event::Repositioned { token } => {
info!("XdgPopup repositioned with token {token}");
}
_ => {}
}
}
}
impl Dispatch<XdgSurface, ()> for AppState {
fn event(
state: &mut Self,
xdg_surface: &XdgSurface,
event: xdg_surface::Event,
_data: &(),
_conn: &Connection,
_qhandle: &QueueHandle<Self>,
) {
if let xdg_surface::Event::Configure { serial } = event {
info!("XdgSurface Configure received, sending ack with serial {serial}");
xdg_surface.ack_configure(serial);
let xdg_surface_id = xdg_surface.id();
for window in state.all_outputs_mut() {
if let Some(popup_manager) = window.popup_manager() {
if popup_manager.find_by_xdg_surface(&xdg_surface_id).is_some() {
info!("Marking all popups as dirty after Configure");
popup_manager.mark_all_popups_dirty();
break;
}
}
}
}
}
}
macro_rules! impl_empty_dispatch_app {
($(($t:ty, $u:ty)),+) => {
$(
impl Dispatch<$t, $u> for AppState {
fn event(
_state: &mut Self,
_proxy: &$t,
_event: <$t as wayland_client::Proxy>::Event,
_data: &$u,
_conn: &Connection,
_qhandle: &QueueHandle<Self>,
) {
info!("Implement empty dispatch event for {:?}", stringify!($t));
}
}
)+
};
}
impl_empty_dispatch_app!(
(WlRegistry, GlobalListContents),
(WlCompositor, ()),
(WlSurface, ()),
(ZwlrLayerShellV1, ()),
(WlSeat, ()),
(WpFractionalScaleManagerV1, ()),
(WpViewporter, ()),
(WpViewport, ()),
(XdgPositioner, ())
);

View file

@ -1,2 +1,3 @@
pub mod app_dispatcher;
pub mod event_dispatcher; pub mod event_dispatcher;
pub mod event_macros; pub mod event_macros;

View file

@ -25,7 +25,7 @@ impl WindowingSystemFacade {
&mut self.inner &mut self.inner
} }
pub fn component_instance(&self) -> &ComponentInstance { pub fn component_instance(&self) -> Result<&ComponentInstance> {
self.inner.component_instance() self.inner.component_instance()
} }

View file

@ -10,11 +10,11 @@ use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_man
use wayland_protocols::wp::viewporter::client::wp_viewporter::WpViewporter; use wayland_protocols::wp::viewporter::client::wp_viewporter::WpViewporter;
use wayland_protocols::xdg::shell::client::xdg_wm_base::XdgWmBase; use wayland_protocols::xdg::shell::client::xdg_wm_base::XdgWmBase;
use crate::wayland::surfaces::surface_state::WindowState; use crate::wayland::surfaces::app_state::AppState;
pub struct GlobalContext { pub struct GlobalContext {
pub compositor: WlCompositor, pub compositor: WlCompositor,
pub output: WlOutput, pub outputs: Vec<WlOutput>,
pub layer_shell: ZwlrLayerShellV1, pub layer_shell: ZwlrLayerShellV1,
pub seat: WlSeat, pub seat: WlSeat,
pub xdg_wm_base: Option<XdgWmBase>, pub xdg_wm_base: Option<XdgWmBase>,
@ -25,21 +25,57 @@ pub struct GlobalContext {
impl GlobalContext { impl GlobalContext {
pub fn initialize( pub fn initialize(
connection: &Connection, connection: &Connection,
queue_handle: &QueueHandle<WindowState>, queue_handle: &QueueHandle<AppState>,
) -> Result<Self, LayerShikaError> { ) -> Result<Self, LayerShikaError> {
let global_list = registry_queue_init::<WindowState>(connection) let global_list = registry_queue_init::<AppState>(connection)
.map(|(global_list, _)| global_list) .map(|(global_list, _)| global_list)
.map_err(|e| LayerShikaError::GlobalInitialization { source: e })?; .map_err(|e| LayerShikaError::GlobalInitialization { source: e })?;
let (compositor, output, layer_shell, seat) = bind_globals!( let (compositor, layer_shell, seat) = bind_globals!(
&global_list, &global_list,
queue_handle, queue_handle,
(WlCompositor, compositor, 3..=6), (WlCompositor, compositor, 3..=6),
(WlOutput, output, 1..=4),
(ZwlrLayerShellV1, layer_shell, 1..=5), (ZwlrLayerShellV1, layer_shell, 1..=5),
(WlSeat, seat, 1..=9) (WlSeat, seat, 1..=9)
)?; )?;
let output_names: Vec<u32> = global_list
.contents()
.clone_list()
.into_iter()
.filter(|global| global.interface == "wl_output")
.map(|global| {
info!(
"Found wl_output global with name: {} at version {}",
global.name, global.version
);
global.name
})
.collect();
info!(
"Total unique wl_output globals found: {}",
output_names.len()
);
let outputs: Vec<WlOutput> = output_names
.iter()
.map(|&name| {
info!("Binding wl_output with name: {}", name);
global_list
.registry()
.bind::<WlOutput, _, _>(name, 4, queue_handle, ())
})
.collect();
if outputs.is_empty() {
return Err(LayerShikaError::InvalidInput {
message: "No outputs found".into(),
});
}
info!("Discovered {} output(s)", outputs.len());
let xdg_wm_base = global_list let xdg_wm_base = global_list
.bind::<XdgWmBase, _, _>(queue_handle, 1..=6, ()) .bind::<XdgWmBase, _, _>(queue_handle, 1..=6, ())
.ok(); .ok();
@ -66,7 +102,7 @@ impl GlobalContext {
Ok(Self { Ok(Self {
compositor, compositor,
output, outputs,
layer_shell, layer_shell,
seat, seat,
xdg_wm_base, xdg_wm_base,

View file

@ -3,5 +3,6 @@ pub(crate) mod event_handling;
pub(crate) mod facade; pub(crate) mod facade;
pub(crate) mod globals; pub(crate) mod globals;
pub(crate) mod managed_proxies; pub(crate) mod managed_proxies;
pub(crate) mod outputs;
pub(crate) mod shell_adapter; pub(crate) mod shell_adapter;
pub(crate) mod surfaces; pub(crate) mod surfaces;

View file

@ -0,0 +1,20 @@
use wayland_client::backend::ObjectId;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct OutputKey(ObjectId);
impl OutputKey {
pub const fn new(id: ObjectId) -> Self {
Self(id)
}
pub const fn id(&self) -> &ObjectId {
&self.0
}
}
impl From<ObjectId> for OutputKey {
fn from(id: ObjectId) -> Self {
Self::new(id)
}
}

View file

@ -1,13 +1,17 @@
use crate::wayland::{ use crate::wayland::{
config::{LayerSurfaceConfig, WaylandWindowConfig}, config::{LayerSurfaceConfig, WaylandWindowConfig},
globals::context::GlobalContext, globals::context::GlobalContext,
managed_proxies::ManagedWlPointer,
surfaces::layer_surface::{SurfaceCtx, SurfaceSetupParams}, surfaces::layer_surface::{SurfaceCtx, SurfaceSetupParams},
surfaces::popup_manager::{PopupContext, PopupManager}, surfaces::popup_manager::{PopupContext, PopupManager},
surfaces::{ surfaces::{
event_context::SharedPointerSerial, surface_builder::WindowStateBuilder, app_state::AppState,
event_context::SharedPointerSerial,
surface_builder::{PlatformWrapper, WindowStateBuilder},
surface_state::WindowState, surface_state::WindowState,
}, },
}; };
use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1;
use crate::{ use crate::{
errors::{EventLoopError, LayerShikaError, RenderingError, Result}, errors::{EventLoopError, LayerShikaError, RenderingError, Result},
rendering::{ rendering::{
@ -22,7 +26,7 @@ use layer_shika_domain::ports::windowing::WindowingSystemPort;
use log::{error, info}; use log::{error, info};
use slint::{ use slint::{
LogicalPosition, PhysicalSize, PlatformError, WindowPosition, LogicalPosition, PhysicalSize, PlatformError, WindowPosition,
platform::{WindowAdapter, femtovg_renderer::FemtoVGRenderer, update_timers_and_animations}, platform::{WindowAdapter, femtovg_renderer::FemtoVGRenderer, set_platform, update_timers_and_animations},
}; };
use slint_interpreter::ComponentInstance; use slint_interpreter::ComponentInstance;
use smithay_client_toolkit::reexports::calloop::{ use smithay_client_toolkit::reexports::calloop::{
@ -30,171 +34,274 @@ use smithay_client_toolkit::reexports::calloop::{
}; };
use std::rc::Rc; use std::rc::Rc;
use wayland_client::{ use wayland_client::{
Connection, EventQueue, Proxy, Connection, EventQueue, Proxy, QueueHandle,
protocol::{wl_display::WlDisplay, wl_surface::WlSurface}, backend::ObjectId,
protocol::{wl_display::WlDisplay, wl_pointer::WlPointer, wl_surface::WlSurface},
}; };
type PopupManagersAndSurfaces = (Vec<Rc<PopupManager>>, Vec<Rc<ZwlrLayerSurfaceV1>>);
struct OutputSetup {
output_id: ObjectId,
main_surface_id: ObjectId,
window: Rc<FemtoVGWindow>,
builder: WindowStateBuilder,
}
pub struct WaylandWindowingSystem { pub struct WaylandWindowingSystem {
state: WindowState, state: AppState,
connection: Rc<Connection>, connection: Rc<Connection>,
event_queue: EventQueue<WindowState>, event_queue: EventQueue<AppState>,
event_loop: EventLoop<'static, WindowState>, event_loop: EventLoop<'static, AppState>,
popup_manager: Rc<PopupManager>,
} }
impl WaylandWindowingSystem { impl WaylandWindowingSystem {
pub fn new(config: WaylandWindowConfig) -> Result<Self> { pub fn new(config: &WaylandWindowConfig) -> Result<Self> {
info!("Initializing WindowingSystem"); info!("Initializing WindowingSystem");
let (connection, event_queue) = Self::init_wayland_connection()?; let (connection, mut event_queue) = Self::init_wayland_connection()?;
let (state, global_ctx, platform) = Self::init_state(config, &connection, &event_queue)?;
let event_loop = let event_loop =
EventLoop::try_new().map_err(|e| EventLoopError::Creation { source: e })?; EventLoop::try_new().map_err(|e| EventLoopError::Creation { source: e })?;
let popup_context = PopupContext::new( let state = Self::init_state(config, &connection, &mut event_queue)?;
global_ctx.compositor,
global_ctx.xdg_wm_base,
global_ctx.seat,
global_ctx.fractional_scale_manager,
global_ctx.viewporter,
connection.display(),
Rc::clone(&connection),
);
let popup_manager = Rc::new(PopupManager::new(
popup_context,
Rc::clone(state.display_metrics()),
));
let shared_serial = Rc::new(SharedPointerSerial::new());
Self::setup_popup_creator(
&popup_manager,
&platform,
&state,
&event_queue,
&shared_serial,
);
Ok(Self { Ok(Self {
state, state,
connection, connection,
event_queue, event_queue,
event_loop, event_loop,
popup_manager,
})
.map(|mut system| {
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>)> { fn init_wayland_connection() -> Result<(Rc<Connection>, EventQueue<AppState>)> {
let connection = Rc::new(Connection::connect_to_env()?); let connection = Rc::new(Connection::connect_to_env()?);
let event_queue = connection.new_event_queue(); let event_queue = connection.new_event_queue();
Ok((connection, event_queue)) Ok((connection, event_queue))
} }
fn init_state( fn create_layer_surface_config(config: &WaylandWindowConfig) -> LayerSurfaceConfig {
config: WaylandWindowConfig, LayerSurfaceConfig {
connection: &Connection,
event_queue: &EventQueue<WindowState>,
) -> Result<(WindowState, GlobalContext, Rc<CustomSlintPlatform>)> {
let global_ctx = GlobalContext::initialize(connection, &event_queue.handle())?;
let layer_surface_config = LayerSurfaceConfig {
anchor: config.anchor, anchor: config.anchor,
margin: config.margin, margin: config.margin,
exclusive_zone: config.exclusive_zone, exclusive_zone: config.exclusive_zone,
keyboard_interactivity: config.keyboard_interactivity, keyboard_interactivity: config.keyboard_interactivity,
height: config.height, 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_config);
let window =
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_compilation_result(config.compilation_result)
.with_surface(Rc::clone(&surface_ctx.surface))
.with_layer_surface(Rc::clone(&surface_ctx.layer_surface))
.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 {
builder = builder.with_fractional_scale(Rc::clone(fs));
} }
if let Some(vp) = &surface_ctx.viewport {
builder = builder.with_viewport(Rc::clone(vp));
}
let (state, platform) =
builder
.build()
.map_err(|e| LayerShikaError::WindowConfiguration {
message: e.to_string(),
})?;
Ok((state, global_ctx, platform))
} }
fn setup_popup_creator( fn create_output_setups(
popup_manager: &Rc<PopupManager>, config: &WaylandWindowConfig,
global_ctx: &GlobalContext,
connection: &Connection,
event_queue: &mut EventQueue<AppState>,
pointer: &Rc<WlPointer>,
layer_surface_config: &LayerSurfaceConfig,
) -> Result<Vec<OutputSetup>> {
let mut setups = Vec::new();
for output in &global_ctx.outputs {
let output_id = output.id();
let setup_params = SurfaceSetupParams {
compositor: &global_ctx.compositor,
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_config);
let main_surface_id = surface_ctx.surface.id();
let window =
Self::initialize_renderer(&surface_ctx.surface, &connection.display(), config)?;
let mut builder = WindowStateBuilder::new()
.with_component_definition(config.component_definition.clone())
.with_compilation_result(config.compilation_result.clone())
.with_surface(Rc::clone(&surface_ctx.surface))
.with_layer_surface(Rc::clone(&surface_ctx.layer_surface))
.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(Rc::clone(&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));
}
setups.push(OutputSetup {
output_id,
main_surface_id,
window,
builder,
});
}
Ok(setups)
}
fn setup_platform(setups: &[OutputSetup]) -> Result<Rc<CustomSlintPlatform>> {
let first_setup = setups
.first()
.ok_or_else(|| LayerShikaError::InvalidInput {
message: "No outputs available".into(),
})?;
let platform = CustomSlintPlatform::new(&first_setup.window);
for setup in setups.iter().skip(1) {
platform.add_window(Rc::clone(&setup.window));
}
set_platform(Box::new(PlatformWrapper(Rc::clone(&platform))))
.map_err(|e| LayerShikaError::PlatformSetup { source: e })?;
Ok(platform)
}
fn create_window_states(
setups: Vec<OutputSetup>,
popup_context: &PopupContext,
shared_serial: &Rc<SharedPointerSerial>,
app_state: &mut AppState,
) -> Result<PopupManagersAndSurfaces> {
let mut popup_managers = Vec::new();
let mut layer_surfaces = Vec::new();
for setup in setups {
let mut per_output_window = WindowState::new(setup.builder).map_err(|e| {
LayerShikaError::WindowConfiguration {
message: e.to_string(),
}
})?;
let popup_manager = Rc::new(PopupManager::new(
popup_context.clone(),
Rc::clone(per_output_window.display_metrics()),
));
per_output_window.set_popup_manager(Rc::clone(&popup_manager));
per_output_window.set_shared_pointer_serial(Rc::clone(shared_serial));
popup_managers.push(Rc::clone(&popup_manager));
layer_surfaces.push(per_output_window.layer_surface());
app_state.add_output(setup.output_id, setup.main_surface_id, per_output_window);
}
Ok((popup_managers, layer_surfaces))
}
fn init_state(
config: &WaylandWindowConfig,
connection: &Connection,
event_queue: &mut EventQueue<AppState>,
) -> Result<AppState> {
let global_ctx = GlobalContext::initialize(connection, &event_queue.handle())?;
let layer_surface_config = Self::create_layer_surface_config(config);
let pointer = Rc::new(global_ctx.seat.get_pointer(&event_queue.handle(), ()));
let shared_serial = Rc::new(SharedPointerSerial::new());
let mut app_state = AppState::new(
ManagedWlPointer::new(Rc::clone(&pointer), Rc::new(connection.clone())),
Rc::clone(&shared_serial),
);
let popup_context = PopupContext::new(
global_ctx.compositor.clone(),
global_ctx.xdg_wm_base.clone(),
global_ctx.seat.clone(),
global_ctx.fractional_scale_manager.clone(),
global_ctx.viewporter.clone(),
connection.display(),
Rc::new(connection.clone()),
);
let setups = Self::create_output_setups(
config,
&global_ctx,
connection,
event_queue,
&pointer,
&layer_surface_config,
)?;
let platform = Self::setup_platform(&setups)?;
let (popup_managers, layer_surfaces) =
Self::create_window_states(setups, &popup_context, &shared_serial, &mut app_state)?;
Self::setup_shared_popup_creator(
popup_managers,
layer_surfaces,
&platform,
&event_queue.handle(),
&shared_serial,
);
Ok(app_state)
}
fn setup_shared_popup_creator(
popup_managers: Vec<Rc<PopupManager>>,
layer_surfaces: Vec<Rc<ZwlrLayerSurfaceV1>>,
platform: &Rc<CustomSlintPlatform>, platform: &Rc<CustomSlintPlatform>,
state: &WindowState, queue_handle: &QueueHandle<AppState>,
event_queue: &EventQueue<WindowState>,
shared_serial: &Rc<SharedPointerSerial>, shared_serial: &Rc<SharedPointerSerial>,
) { ) {
if !popup_manager.has_xdg_shell() { let Some(first_manager) = popup_managers.first() else {
info!("No popup managers available");
return;
};
if !first_manager.has_xdg_shell() {
info!("xdg-shell not available, popups will not be supported"); info!("xdg-shell not available, popups will not be supported");
return; return;
} }
info!("Setting up popup creator with xdg-shell support"); info!(
"Setting up shared popup creator for {} output(s)",
popup_managers.len()
);
let popup_manager_clone = Rc::clone(popup_manager); let queue_handle_clone = queue_handle.clone();
let layer_surface = state.layer_surface();
let queue_handle = event_queue.handle();
let serial_holder = Rc::clone(shared_serial); let serial_holder = Rc::clone(shared_serial);
platform.set_popup_creator(move || { platform.set_popup_creator(move || {
info!("Popup creator called! Creating popup window..."); info!("Popup creator called! Searching for pending popup...");
let serial = serial_holder.get(); let serial = serial_holder.get();
let popup_window = popup_manager_clone for (idx, (popup_manager, layer_surface)) in
.create_pending_popup(&queue_handle, &layer_surface, serial) popup_managers.iter().zip(layer_surfaces.iter()).enumerate()
.map_err(|e| PlatformError::Other(format!("Failed to create popup: {e}")))?; {
if popup_manager.has_pending_popup() {
info!("Found pending popup in output #{}", idx);
let result = Ok(popup_window as Rc<dyn WindowAdapter>); let popup_window = popup_manager
.create_pending_popup(&queue_handle_clone, layer_surface, serial)
.map_err(|e| {
PlatformError::Other(format!("Failed to create popup: {e}"))
})?;
match &result { info!("Popup created successfully for output #{}", idx);
Ok(_) => info!("Popup created successfully"), return Ok(popup_window as Rc<dyn WindowAdapter>);
Err(e) => info!("Popup creation failed: {e:?}"), }
} }
result Err(PlatformError::Other(
"No pending popup request found in any output".into(),
))
}); });
} }
@ -222,7 +329,7 @@ impl WaylandWindowingSystem {
Ok(femtovg_window) Ok(femtovg_window)
} }
pub fn event_loop_handle(&self) -> LoopHandle<'static, WindowState> { pub fn event_loop_handle(&self) -> LoopHandle<'static, AppState> {
self.event_loop.handle() self.event_loop.handle()
} }
@ -236,25 +343,25 @@ impl WaylandWindowingSystem {
.map_err(|e| LayerShikaError::WaylandProtocol { source: e })?; .map_err(|e| LayerShikaError::WaylandProtocol { source: e })?;
update_timers_and_animations(); update_timers_and_animations();
self.state
.window() for window in self.state.all_outputs() {
.render_frame_if_dirty() window
.map_err(|e| RenderingError::Operation { .window()
message: e.to_string(), .render_frame_if_dirty()
})?; .map_err(|e| RenderingError::Operation {
message: e.to_string(),
})?;
}
} }
self.setup_wayland_event_source()?; self.setup_wayland_event_source()?;
let event_queue = &mut self.event_queue; let event_queue = &mut self.event_queue;
let connection = &self.connection; let connection = &self.connection;
let popup_manager = Rc::clone(&self.popup_manager);
self.event_loop self.event_loop
.run(None, &mut self.state, move |shared_data| { .run(None, &mut self.state, move |shared_data| {
if let Err(e) = if let Err(e) = Self::process_events(connection, event_queue, shared_data) {
Self::process_events(connection, event_queue, shared_data, &popup_manager)
{
error!("Error processing events: {e}"); error!("Error processing events: {e}");
} }
}) })
@ -281,9 +388,8 @@ impl WaylandWindowingSystem {
fn process_events( fn process_events(
connection: &Connection, connection: &Connection,
event_queue: &mut EventQueue<WindowState>, event_queue: &mut EventQueue<AppState>,
shared_data: &mut WindowState, shared_data: &mut AppState,
popup_manager: &PopupManager,
) -> Result<()> { ) -> Result<()> {
if let Some(guard) = event_queue.prepare_read() { if let Some(guard) = event_queue.prepare_read() {
guard guard
@ -295,18 +401,22 @@ impl WaylandWindowingSystem {
update_timers_and_animations(); update_timers_and_animations();
shared_data for window in shared_data.all_outputs() {
.window() window
.render_frame_if_dirty() .window()
.map_err(|e| RenderingError::Operation { .render_frame_if_dirty()
message: e.to_string(), .map_err(|e| RenderingError::Operation {
})?; message: e.to_string(),
})?;
popup_manager if let Some(popup_manager) = window.popup_manager() {
.render_popups() popup_manager
.map_err(|e| RenderingError::Operation { .render_popups()
message: e.to_string(), .map_err(|e| RenderingError::Operation {
})?; message: e.to_string(),
})?;
}
}
connection connection
.flush() .flush()
@ -315,11 +425,24 @@ impl WaylandWindowingSystem {
Ok(()) Ok(())
} }
pub const fn component_instance(&self) -> &ComponentInstance { pub fn component_instance(&self) -> Result<&ComponentInstance> {
self.state.component_instance() self.state
.primary_output()
.ok_or_else(|| LayerShikaError::InvalidInput {
message: "No outputs available".into(),
})
.map(WindowState::component_instance)
} }
pub const fn state(&self) -> &WindowState { pub fn state(&self) -> Result<&WindowState> {
self.state
.primary_output()
.ok_or_else(|| LayerShikaError::InvalidInput {
message: "No outputs available".into(),
})
}
pub fn app_state(&self) -> &AppState {
&self.state &self.state
} }
} }

View file

@ -0,0 +1,161 @@
use super::surface_state::WindowState;
use super::event_context::SharedPointerSerial;
use crate::wayland::managed_proxies::ManagedWlPointer;
use crate::wayland::outputs::OutputKey;
use std::collections::HashMap;
use std::rc::Rc;
use wayland_client::backend::ObjectId;
use wayland_client::Proxy;
pub type PerOutputWindow = WindowState;
pub struct AppState {
outputs: HashMap<OutputKey, PerOutputWindow>,
surface_to_output: HashMap<ObjectId, OutputKey>,
output_to_key: HashMap<ObjectId, OutputKey>,
_pointer: ManagedWlPointer,
shared_pointer_serial: Rc<SharedPointerSerial>,
active_output: Option<OutputKey>,
}
impl AppState {
pub fn new(pointer: ManagedWlPointer, shared_serial: Rc<SharedPointerSerial>) -> Self {
Self {
outputs: HashMap::new(),
surface_to_output: HashMap::new(),
output_to_key: HashMap::new(),
_pointer: pointer,
shared_pointer_serial: shared_serial,
active_output: None,
}
}
pub fn add_output(
&mut self,
output_id: ObjectId,
main_surface_id: ObjectId,
window: PerOutputWindow,
) {
let key = OutputKey::new(output_id.clone());
self.output_to_key.insert(output_id, key.clone());
self.surface_to_output
.insert(main_surface_id, key.clone());
self.outputs.insert(key, window);
}
pub fn get_output_by_key(&self, key: &OutputKey) -> Option<&PerOutputWindow> {
self.outputs.get(key)
}
pub fn get_output_by_key_mut(&mut self, key: &OutputKey) -> Option<&mut PerOutputWindow> {
self.outputs.get_mut(key)
}
pub fn get_output_by_output_id(&self, output_id: &ObjectId) -> Option<&PerOutputWindow> {
self.output_to_key
.get(output_id)
.and_then(|key| self.outputs.get(key))
}
pub fn get_output_by_output_id_mut(
&mut self,
output_id: &ObjectId,
) -> Option<&mut PerOutputWindow> {
self.output_to_key
.get(output_id)
.and_then(|key| self.outputs.get_mut(key))
}
pub fn get_output_by_surface(&self, surface_id: &ObjectId) -> Option<&PerOutputWindow> {
self.surface_to_output
.get(surface_id)
.and_then(|key| self.outputs.get(key))
}
pub fn get_output_by_surface_mut(
&mut self,
surface_id: &ObjectId,
) -> Option<&mut PerOutputWindow> {
self.surface_to_output
.get(surface_id)
.and_then(|key| self.outputs.get_mut(key))
}
pub fn get_output_by_layer_surface_mut(
&mut self,
layer_surface_id: &ObjectId,
) -> Option<&mut PerOutputWindow> {
self.outputs.values_mut().find(|window| {
window.layer_surface().as_ref().id() == *layer_surface_id
})
}
pub fn get_key_by_surface(&self, surface_id: &ObjectId) -> Option<&OutputKey> {
self.surface_to_output.get(surface_id)
}
pub fn get_key_by_output_id(&self, output_id: &ObjectId) -> Option<&OutputKey> {
self.output_to_key.get(output_id)
}
pub fn register_popup_surface(&mut self, popup_surface_id: ObjectId, output_key: OutputKey) {
self.surface_to_output.insert(popup_surface_id, output_key);
}
pub fn set_active_output(&mut self, key: Option<OutputKey>) {
self.active_output = key;
}
pub const fn active_output(&self) -> Option<&OutputKey> {
self.active_output.as_ref()
}
pub fn primary_output(&self) -> Option<&PerOutputWindow> {
self.outputs.values().next()
}
pub fn all_outputs(&self) -> impl Iterator<Item = &PerOutputWindow> {
self.outputs.values()
}
pub fn all_outputs_mut(&mut self) -> impl Iterator<Item = &mut PerOutputWindow> {
self.outputs.values_mut()
}
pub const fn shared_pointer_serial(&self) -> &Rc<SharedPointerSerial> {
&self.shared_pointer_serial
}
pub fn find_output_by_popup(&self, popup_surface_id: &ObjectId) -> Option<&PerOutputWindow> {
self.outputs.values().find(|window| {
window
.popup_manager()
.as_ref()
.and_then(|pm| pm.find_by_surface(popup_surface_id))
.is_some()
})
}
pub fn find_output_by_popup_mut(
&mut self,
popup_surface_id: &ObjectId,
) -> Option<&mut PerOutputWindow> {
self.outputs.values_mut().find(|window| {
window
.popup_manager()
.as_ref()
.and_then(|pm| pm.find_by_surface(popup_surface_id))
.is_some()
})
}
pub fn get_key_by_popup(&self, popup_surface_id: &ObjectId) -> Option<OutputKey> {
self.outputs.iter().find_map(|(key, window)| {
window
.popup_manager()
.as_ref()
.and_then(|pm| pm.find_by_surface(popup_surface_id))
.map(|_| key.clone())
})
}
}

View file

@ -1,4 +1,4 @@
use crate::wayland::{config::LayerSurfaceConfig, surfaces::surface_state::WindowState}; use crate::wayland::{config::LayerSurfaceConfig, surfaces::app_state::AppState};
use log::info; use log::info;
use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::{ use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::{
zwlr_layer_shell_v1::{Layer, ZwlrLayerShellV1}, zwlr_layer_shell_v1::{Layer, ZwlrLayerShellV1},
@ -23,7 +23,7 @@ pub struct SurfaceSetupParams<'a> {
pub layer_shell: &'a ZwlrLayerShellV1, pub layer_shell: &'a ZwlrLayerShellV1,
pub fractional_scale_manager: Option<&'a WpFractionalScaleManagerV1>, pub fractional_scale_manager: Option<&'a WpFractionalScaleManagerV1>,
pub viewporter: Option<&'a WpViewporter>, pub viewporter: Option<&'a WpViewporter>,
pub queue_handle: &'a QueueHandle<WindowState>, pub queue_handle: &'a QueueHandle<AppState>,
pub layer: Layer, pub layer: Layer,
pub namespace: String, pub namespace: String,
} }

View file

@ -1,3 +1,4 @@
pub mod app_state;
pub mod component_state; pub mod component_state;
pub mod dimensions; pub mod dimensions;
pub mod display_metrics; pub mod display_metrics;

View file

@ -23,8 +23,8 @@ use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1:
use wayland_protocols::wp::viewporter::client::wp_viewporter::WpViewporter; use wayland_protocols::wp::viewporter::client::wp_viewporter::WpViewporter;
use wayland_protocols::xdg::shell::client::xdg_wm_base::XdgWmBase; use wayland_protocols::xdg::shell::client::xdg_wm_base::XdgWmBase;
use super::app_state::AppState;
use super::popup_surface::PopupSurface; use super::popup_surface::PopupSurface;
use super::surface_state::WindowState;
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ActiveWindow { pub enum ActiveWindow {
@ -66,6 +66,7 @@ pub struct CreatePopupParams {
pub grab: bool, pub grab: bool,
} }
#[derive(Clone)]
pub struct PopupContext { pub struct PopupContext {
compositor: WlCompositor, compositor: WlCompositor,
xdg_wm_base: Option<XdgWmBase>, xdg_wm_base: Option<XdgWmBase>,
@ -184,6 +185,11 @@ impl PopupManager {
.map(|p| (p.id, p.request, p.width, p.height)) .map(|p| (p.id, p.request, p.width, p.height))
} }
#[must_use]
pub fn has_pending_popup(&self) -> bool {
self.state.borrow().pending_popup.is_some()
}
#[must_use] #[must_use]
pub fn scale_factor(&self) -> f32 { pub fn scale_factor(&self) -> f32 {
self.scale_factor.get() self.scale_factor.get()
@ -225,7 +231,7 @@ impl PopupManager {
pub fn create_pending_popup( pub fn create_pending_popup(
self: &Rc<Self>, self: &Rc<Self>,
queue_handle: &QueueHandle<WindowState>, queue_handle: &QueueHandle<AppState>,
parent_layer_surface: &ZwlrLayerSurfaceV1, parent_layer_surface: &ZwlrLayerSurfaceV1,
last_pointer_serial: u32, last_pointer_serial: u32,
) -> Result<Rc<PopupWindow>> { ) -> Result<Rc<PopupWindow>> {
@ -250,7 +256,7 @@ impl PopupManager {
fn create_popup_internal( fn create_popup_internal(
self: &Rc<Self>, self: &Rc<Self>,
queue_handle: &QueueHandle<WindowState>, queue_handle: &QueueHandle<AppState>,
parent_layer_surface: &ZwlrLayerSurfaceV1, parent_layer_surface: &ZwlrLayerSurfaceV1,
params: CreatePopupParams, params: CreatePopupParams,
request: PopupRequest, request: PopupRequest,

View file

@ -21,7 +21,7 @@ use wayland_protocols::xdg::shell::client::{
xdg_wm_base::XdgWmBase, xdg_wm_base::XdgWmBase,
}; };
use super::surface_state::WindowState; use super::app_state::AppState;
pub struct PopupSurfaceParams<'a> { pub struct PopupSurfaceParams<'a> {
pub compositor: &'a WlCompositor, pub compositor: &'a WlCompositor,
@ -29,7 +29,7 @@ pub struct PopupSurfaceParams<'a> {
pub parent_layer_surface: &'a ZwlrLayerSurfaceV1, pub parent_layer_surface: &'a ZwlrLayerSurfaceV1,
pub fractional_scale_manager: Option<&'a WpFractionalScaleManagerV1>, pub fractional_scale_manager: Option<&'a WpFractionalScaleManagerV1>,
pub viewporter: Option<&'a WpViewporter>, pub viewporter: Option<&'a WpViewporter>,
pub queue_handle: &'a QueueHandle<WindowState>, pub queue_handle: &'a QueueHandle<AppState>,
pub popup_config: PopupConfig, pub popup_config: PopupConfig,
pub physical_size: PhysicalSize, pub physical_size: PhysicalSize,
pub scale_factor: f32, pub scale_factor: f32,

View file

@ -15,7 +15,7 @@ use crate::rendering::slint_integration::platform::CustomSlintPlatform;
use super::surface_state::WindowState; use super::surface_state::WindowState;
struct PlatformWrapper(Rc<CustomSlintPlatform>); pub struct PlatformWrapper(pub Rc<CustomSlintPlatform>);
impl Platform for PlatformWrapper { impl Platform for PlatformWrapper {
fn create_window_adapter(&self) -> StdResult<Rc<dyn WindowAdapter>, PlatformError> { fn create_window_adapter(&self) -> StdResult<Rc<dyn WindowAdapter>, PlatformError> {
@ -107,7 +107,10 @@ impl WindowStateBuilder {
} }
#[must_use] #[must_use]
pub fn with_compilation_result(mut self, compilation_result: Option<Rc<CompilationResult>>) -> Self { pub fn with_compilation_result(
mut self,
compilation_result: Option<Rc<CompilationResult>>,
) -> Self {
self.compilation_result = compilation_result; self.compilation_result = compilation_result;
self self
} }

View file

@ -9,7 +9,9 @@ use layer_shika_adapters::platform::slint::ComponentHandle;
use layer_shika_adapters::platform::slint_interpreter::{ use layer_shika_adapters::platform::slint_interpreter::{
CompilationResult, ComponentDefinition, ComponentInstance, CompilationResult, ComponentDefinition, ComponentInstance,
}; };
use layer_shika_adapters::{PopupManager, WaylandWindowConfig, WindowState, WindowingSystemFacade}; use layer_shika_adapters::{
AppState, PopupManager, WaylandWindowConfig, WindowState, WindowingSystemFacade,
};
use layer_shika_domain::config::WindowConfig; use layer_shika_domain::config::WindowConfig;
use layer_shika_domain::errors::DomainError; use layer_shika_domain::errors::DomainError;
use layer_shika_domain::value_objects::popup_positioning_mode::PopupPositioningMode; use layer_shika_domain::value_objects::popup_positioning_mode::PopupPositioningMode;
@ -49,8 +51,8 @@ impl EventLoopHandle {
let loop_handle = system.borrow().inner_ref().event_loop_handle(); let loop_handle = system.borrow().inner_ref().event_loop_handle();
loop_handle loop_handle
.insert_source(source, move |event, metadata, window_state| { .insert_source(source, move |event, metadata, app_state| {
let runtime_state = RuntimeState { window_state }; let runtime_state = RuntimeState { app_state };
callback(event, metadata, runtime_state) callback(event, metadata, runtime_state)
}) })
.map_err(|e| { .map_err(|e| {
@ -120,22 +122,42 @@ impl EventLoopHandle {
} }
pub struct RuntimeState<'a> { pub struct RuntimeState<'a> {
window_state: &'a mut WindowState, app_state: &'a mut AppState,
} }
impl RuntimeState<'_> { impl RuntimeState<'_> {
#[must_use] #[must_use]
pub fn component_instance(&self) -> &ComponentInstance { pub fn component_instance(&self) -> Option<&ComponentInstance> {
self.window_state.component_instance() self.app_state
.primary_output()
.map(WindowState::component_instance)
}
pub fn all_component_instances(&self) -> impl Iterator<Item = &ComponentInstance> {
self.app_state
.all_outputs()
.map(WindowState::component_instance)
}
fn active_or_primary_output(&self) -> Option<&WindowState> {
self.app_state
.active_output()
.and_then(|key| self.app_state.get_output_by_key(key))
.or_else(|| self.app_state.primary_output())
} }
pub fn render_frame_if_dirty(&mut self) -> Result<()> { pub fn render_frame_if_dirty(&mut self) -> Result<()> {
Ok(self.window_state.render_frame_if_dirty()?) for window in self.app_state.all_outputs() {
window.render_frame_if_dirty()?;
}
Ok(())
} }
#[must_use] #[must_use]
pub fn compilation_result(&self) -> Option<Rc<CompilationResult>> { pub fn compilation_result(&self) -> Option<Rc<CompilationResult>> {
self.window_state.compilation_result() self.app_state
.primary_output()
.and_then(WindowState::compilation_result)
} }
pub fn show_popup( pub fn show_popup(
@ -162,7 +184,19 @@ impl RuntimeState<'_> {
self.close_current_popup()?; self.close_current_popup()?;
let popup_manager = self.window_state.popup_manager().ok_or_else(|| { let is_using_active = self.app_state.active_output().is_some();
let active_window = self.active_or_primary_output().ok_or_else(|| {
Error::Domain(DomainError::Configuration {
message: "No active or primary output available".to_string(),
})
})?;
log::info!(
"Creating popup on {} output",
if is_using_active { "active" } else { "primary" }
);
let popup_manager = active_window.popup_manager().ok_or_else(|| {
Error::Domain(DomainError::Configuration { Error::Domain(DomainError::Configuration {
message: "No popup manager available".to_string(), message: "No popup manager available".to_string(),
}) })
@ -209,15 +243,19 @@ impl RuntimeState<'_> {
} }
pub fn close_popup(&mut self, handle: PopupHandle) -> Result<()> { pub fn close_popup(&mut self, handle: PopupHandle) -> Result<()> {
if let Some(popup_manager) = self.window_state.popup_manager() { if let Some(active_window) = self.active_or_primary_output() {
popup_manager.close(handle)?; if let Some(popup_manager) = active_window.popup_manager() {
popup_manager.close(handle)?;
}
} }
Ok(()) Ok(())
} }
pub fn close_current_popup(&mut self) -> Result<()> { pub fn close_current_popup(&mut self) -> Result<()> {
if let Some(popup_manager) = self.window_state.popup_manager() { if let Some(active_window) = self.active_or_primary_output() {
popup_manager.close_current_popup(); if let Some(popup_manager) = active_window.popup_manager() {
popup_manager.close_current_popup();
}
} }
Ok(()) Ok(())
} }
@ -229,7 +267,13 @@ impl RuntimeState<'_> {
height: f32, height: f32,
resize_sender: Option<channel::Sender<PopupCommand>>, resize_sender: Option<channel::Sender<PopupCommand>>,
) -> Result<()> { ) -> Result<()> {
let popup_manager = self.window_state.popup_manager().ok_or_else(|| { let active_window = self.active_or_primary_output().ok_or_else(|| {
Error::Domain(DomainError::Configuration {
message: "No active or primary output available".to_string(),
})
})?;
let popup_manager = active_window.popup_manager().ok_or_else(|| {
Error::Domain(DomainError::Configuration { Error::Domain(DomainError::Configuration {
message: "No popup manager available".to_string(), message: "No popup manager available".to_string(),
}) })
@ -322,7 +366,7 @@ impl WindowingSystem {
compilation_result, compilation_result,
config, config,
); );
let inner = layer_shika_adapters::WaylandWindowingSystem::new(wayland_config)?; let inner = layer_shika_adapters::WaylandWindowingSystem::new(&wayland_config)?;
let facade = WindowingSystemFacade::new(inner); let facade = WindowingSystemFacade::new(inner);
let inner_rc = Rc::new(RefCell::new(facade)); let inner_rc = Rc::new(RefCell::new(facade));
@ -339,7 +383,7 @@ impl WindowingSystem {
}; };
system.setup_popup_command_handler(receiver)?; system.setup_popup_command_handler(receiver)?;
system.register_popup_callbacks()?; system.register_popup_callbacks();
Ok(system) Ok(system)
} }
@ -349,9 +393,9 @@ impl WindowingSystem {
let sender_for_handler = self.popup_command_sender.clone(); let sender_for_handler = self.popup_command_sender.clone();
loop_handle loop_handle
.insert_source(receiver, move |event, (), window_state| { .insert_source(receiver, move |event, (), app_state| {
if let channel::Event::Msg(command) = event { if let channel::Event::Msg(command) = event {
let mut runtime_state = RuntimeState { window_state }; let mut runtime_state = RuntimeState { app_state };
match command { match command {
PopupCommand::Show(request) => { PopupCommand::Show(request) => {
@ -395,11 +439,15 @@ impl WindowingSystem {
Ok(()) Ok(())
} }
fn register_popup_callbacks(&self) -> Result<()> { fn register_popup_callbacks(&self) {
self.with_component_instance(|component_instance| { self.with_all_component_instances(|component_instance| {
self.callback_contract if let Err(e) = self
.callback_contract
.register_on_main_component(component_instance) .register_on_main_component(component_instance)
}) {
log::error!("Failed to register popup callbacks on output: {}", e);
}
});
} }
#[must_use] #[must_use]
@ -434,10 +482,23 @@ impl WindowingSystem {
Ok(()) Ok(())
} }
pub fn with_component_instance<F, R>(&self, f: F) -> R pub fn with_component_instance<F, R>(&self, f: F) -> Result<R>
where where
F: FnOnce(&ComponentInstance) -> R, F: FnOnce(&ComponentInstance) -> R,
{ {
f(self.inner.borrow().component_instance()) let facade = self.inner.borrow();
let instance = facade.component_instance()?;
Ok(f(instance))
}
pub fn with_all_component_instances<F>(&self, mut f: F)
where
F: FnMut(&ComponentInstance),
{
let facade = self.inner.borrow();
let system = facade.inner_ref();
for window in system.app_state().all_outputs() {
f(window.component_instance());
}
} }
} }