feat: add minimal mode for lock surfaces only

This commit is contained in:
drendog 2026-01-18 04:55:32 +01:00
parent e6f425842b
commit e025cb008c
Signed by: dwenya
GPG key ID: 8DD77074645332D0
7 changed files with 155 additions and 39 deletions

View file

@ -23,6 +23,14 @@ impl CustomSlintPlatform {
}) })
} }
#[must_use]
pub fn new_empty() -> Rc<Self> {
Rc::new(Self {
pending_windows: RefCell::new(Vec::new()),
popup_creator: OnceCell::new(),
})
}
pub fn add_window(&self, window: Rc<FemtoVGWindow>) { pub fn add_window(&self, window: Rc<FemtoVGWindow>) {
self.pending_windows.borrow_mut().push(window); self.pending_windows.borrow_mut().push(window);
} }

View file

@ -20,7 +20,7 @@ use crate::wayland::surfaces::app_state::AppState;
pub struct GlobalContext { pub struct GlobalContext {
pub compositor: WlCompositor, pub compositor: WlCompositor,
pub outputs: Vec<WlOutput>, pub outputs: Vec<WlOutput>,
pub layer_shell: ZwlrLayerShellV1, pub layer_shell: Option<ZwlrLayerShellV1>,
pub seat: WlSeat, pub seat: WlSeat,
pub xdg_wm_base: Option<XdgWmBase>, pub xdg_wm_base: Option<XdgWmBase>,
pub session_lock_manager: Option<ExtSessionLockManagerV1>, pub session_lock_manager: Option<ExtSessionLockManagerV1>,
@ -38,14 +38,17 @@ impl GlobalContext {
.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, layer_shell, seat) = bind_globals!( let (compositor, seat) = bind_globals!(
&global_list, &global_list,
queue_handle, queue_handle,
(WlCompositor, compositor, 3..=6), (WlCompositor, compositor, 3..=6),
(ZwlrLayerShellV1, layer_shell, 1..=5),
(WlSeat, seat, 1..=9) (WlSeat, seat, 1..=9)
)?; )?;
let layer_shell = global_list
.bind::<ZwlrLayerShellV1, _, _>(queue_handle, 1..=5, ())
.ok();
let output_names: Vec<u32> = global_list let output_names: Vec<u32> = global_list
.contents() .contents()
.clone_list() .clone_list()
@ -115,6 +118,10 @@ impl GlobalContext {
info!("Viewporter protocol not available"); info!("Viewporter protocol not available");
} }
if layer_shell.is_none() {
info!("wlr-layer-shell protocol not available, layer surfaces disabled");
}
let render_context_manager = RenderContextManager::new(&connection.display().id())?; let render_context_manager = RenderContextManager::new(&connection.display().id())?;
Ok(Self { Ok(Self {

View file

@ -5,7 +5,7 @@ use crate::wayland::surfaces::app_state::AppState;
use layer_shika_domain::value_objects::lock_config::LockConfig; use layer_shika_domain::value_objects::lock_config::LockConfig;
use layer_shika_domain::value_objects::lock_state::LockState; use layer_shika_domain::value_objects::lock_state::LockState;
use layer_shika_domain::value_objects::output_handle::OutputHandle; use layer_shika_domain::value_objects::output_handle::OutputHandle;
use slint_interpreter::ComponentInstance; use slint_interpreter::{ComponentInstance, CompilationResult};
use slint_interpreter::Value; use slint_interpreter::Value;
use smithay_client_toolkit::reexports::calloop::LoopHandle; use smithay_client_toolkit::reexports::calloop::LoopHandle;
use std::rc::Rc; use std::rc::Rc;
@ -19,6 +19,8 @@ pub trait WaylandSystemOps {
fn despawn_surface(&mut self, name: &str) -> Result<()>; fn despawn_surface(&mut self, name: &str) -> Result<()>;
fn set_compilation_result(&mut self, compilation_result: Rc<CompilationResult>);
fn activate_session_lock(&mut self, component_name: &str, config: LockConfig) -> Result<()>; fn activate_session_lock(&mut self, component_name: &str, config: LockConfig) -> Result<()>;
fn deactivate_session_lock(&mut self) -> Result<()>; fn deactivate_session_lock(&mut self) -> Result<()>;

View file

@ -35,7 +35,7 @@ use super::OutputMapping;
pub struct OutputManagerContext { pub struct OutputManagerContext {
pub compositor: WlCompositor, pub compositor: WlCompositor,
pub layer_shell: ZwlrLayerShellV1, pub layer_shell: Option<ZwlrLayerShellV1>,
pub fractional_scale_manager: Option<WpFractionalScaleManagerV1>, pub fractional_scale_manager: Option<WpFractionalScaleManagerV1>,
pub viewporter: Option<WpViewporter>, pub viewporter: Option<WpViewporter>,
pub render_factory: Rc<RenderContextFactory>, pub render_factory: Rc<RenderContextFactory>,
@ -161,10 +161,16 @@ impl OutputManager {
_output_id: &ObjectId, _output_id: &ObjectId,
queue_handle: &QueueHandle<AppState>, queue_handle: &QueueHandle<AppState>,
) -> Result<(SurfaceState, ObjectId)> { ) -> Result<(SurfaceState, ObjectId)> {
let layer_shell = self.context.layer_shell.as_ref().ok_or_else(|| {
LayerShikaError::InvalidInput {
message: "wlr-layer-shell protocol not available - cannot create layer surfaces".into(),
}
})?;
let setup_params = SurfaceSetupParams { let setup_params = SurfaceSetupParams {
compositor: &self.context.compositor, compositor: &self.context.compositor,
output, output,
layer_shell: &self.context.layer_shell, layer_shell,
fractional_scale_manager: self.context.fractional_scale_manager.as_ref(), fractional_scale_manager: self.context.fractional_scale_manager.as_ref(),
viewporter: self.context.viewporter.as_ref(), viewporter: self.context.viewporter.as_ref(),
queue_handle, queue_handle,

View file

@ -37,7 +37,7 @@ use slint::{
LogicalPosition, PhysicalSize, PlatformError, WindowPosition, LogicalPosition, PhysicalSize, PlatformError, WindowPosition,
platform::{WindowAdapter, femtovg_renderer::FemtoVGRenderer, set_platform, update_timers_and_animations}, platform::{WindowAdapter, femtovg_renderer::FemtoVGRenderer, set_platform, update_timers_and_animations},
}; };
use slint_interpreter::ComponentInstance; use slint_interpreter::{ComponentInstance, CompilationResult};
use smithay_client_toolkit::reexports::calloop::{ use smithay_client_toolkit::reexports::calloop::{
EventLoop, Interest, LoopHandle, Mode, PostAction, generic::Generic, EventLoop, Interest, LoopHandle, Mode, PostAction, generic::Generic,
}; };
@ -97,9 +97,7 @@ impl WaylandShellSystem {
pub fn new_multi(configs: &[ShellSurfaceConfig]) -> Result<Self> { pub fn new_multi(configs: &[ShellSurfaceConfig]) -> Result<Self> {
if configs.is_empty() { if configs.is_empty() {
return Err(LayerShikaError::InvalidInput { return Self::new_minimal();
message: "At least one surface config is required".into(),
});
} }
info!( info!(
@ -120,6 +118,22 @@ impl WaylandShellSystem {
}) })
} }
pub fn new_minimal() -> Result<Self> {
info!("Initializing WindowingSystem in minimal mode (no layer surfaces)");
let (connection, mut event_queue) = Self::init_wayland_connection()?;
let event_loop =
EventLoop::try_new().map_err(|e| EventLoopError::Creation { source: e })?;
let state = Self::init_state_minimal(&connection, &mut event_queue)?;
Ok(Self {
state,
connection,
event_queue,
event_loop,
})
}
fn init_wayland_connection() -> Result<(Rc<Connection>, EventQueue<AppState>)> { 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();
@ -145,6 +159,12 @@ impl WaylandShellSystem {
pointer: &Rc<WlPointer>, pointer: &Rc<WlPointer>,
layer_surface_config: &LayerSurfaceConfig, layer_surface_config: &LayerSurfaceConfig,
) -> Result<Vec<OutputSetup>> { ) -> Result<Vec<OutputSetup>> {
let layer_shell = global_ctx.layer_shell.as_ref().ok_or_else(|| {
LayerShikaError::InvalidInput {
message: "wlr-layer-shell protocol not available - cannot create layer surfaces".into(),
}
})?;
let mut setups = Vec::new(); let mut setups = Vec::new();
for (index, output) in global_ctx.outputs.iter().enumerate() { for (index, output) in global_ctx.outputs.iter().enumerate() {
@ -163,7 +183,7 @@ impl WaylandShellSystem {
let setup_params = SurfaceSetupParams { let setup_params = SurfaceSetupParams {
compositor: &global_ctx.compositor, compositor: &global_ctx.compositor,
output, output,
layer_shell: &global_ctx.layer_shell, layer_shell,
fractional_scale_manager: global_ctx.fractional_scale_manager.as_ref(), fractional_scale_manager: global_ctx.fractional_scale_manager.as_ref(),
viewporter: global_ctx.viewporter.as_ref(), viewporter: global_ctx.viewporter.as_ref(),
queue_handle: &event_queue.handle(), queue_handle: &event_queue.handle(),
@ -426,6 +446,38 @@ impl WaylandShellSystem {
Ok(app_state) Ok(app_state)
} }
fn init_state_minimal(
connection: &Connection,
event_queue: &mut EventQueue<AppState>,
) -> Result<AppState> {
let global_ctx = Rc::new(GlobalContext::initialize(
connection,
&event_queue.handle(),
)?);
let pointer = Rc::new(global_ctx.seat.get_pointer(&event_queue.handle(), ()));
let keyboard = Rc::new(global_ctx.seat.get_keyboard(&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())),
ManagedWlKeyboard::new(Rc::clone(&keyboard), Rc::new(connection.clone())),
Rc::clone(&shared_serial),
);
app_state.set_queue_handle(event_queue.handle());
app_state.set_global_context(Rc::clone(&global_ctx));
let platform = CustomSlintPlatform::new_empty();
set_platform(Box::new(PlatformWrapper(Rc::clone(&platform))))
.map_err(|e| LayerShikaError::PlatformSetup { source: e })?;
app_state.set_slint_platform(Rc::clone(&platform));
info!("Minimal state initialized successfully (no layer surfaces, empty Slint platform for session locks)");
Ok(app_state)
}
fn create_output_setups_multi( fn create_output_setups_multi(
configs: &[ShellSurfaceConfig], configs: &[ShellSurfaceConfig],
global_ctx: &GlobalContext, global_ctx: &GlobalContext,
@ -433,6 +485,12 @@ impl WaylandShellSystem {
event_queue: &mut EventQueue<AppState>, event_queue: &mut EventQueue<AppState>,
pointer: &Rc<WlPointer>, pointer: &Rc<WlPointer>,
) -> Result<Vec<OutputSetup>> { ) -> Result<Vec<OutputSetup>> {
let layer_shell = global_ctx.layer_shell.as_ref().ok_or_else(|| {
LayerShikaError::InvalidInput {
message: "wlr-layer-shell protocol not available - cannot create layer surfaces".into(),
}
})?;
let mut setups = Vec::new(); let mut setups = Vec::new();
for (output_index, output) in global_ctx.outputs.iter().enumerate() { for (output_index, output) in global_ctx.outputs.iter().enumerate() {
@ -458,7 +516,7 @@ impl WaylandShellSystem {
let setup_params = SurfaceSetupParams { let setup_params = SurfaceSetupParams {
compositor: &global_ctx.compositor, compositor: &global_ctx.compositor,
output, output,
layer_shell: &global_ctx.layer_shell, layer_shell,
fractional_scale_manager: global_ctx.fractional_scale_manager.as_ref(), fractional_scale_manager: global_ctx.fractional_scale_manager.as_ref(),
viewporter: global_ctx.viewporter.as_ref(), viewporter: global_ctx.viewporter.as_ref(),
queue_handle: &event_queue.handle(), queue_handle: &event_queue.handle(),
@ -805,6 +863,10 @@ impl WaylandSystemOps for WaylandShellSystem {
WaylandShellSystem::despawn_surface(self, name) WaylandShellSystem::despawn_surface(self, name)
} }
fn set_compilation_result(&mut self, compilation_result: Rc<CompilationResult>) {
self.state.set_compilation_result(compilation_result);
}
fn activate_session_lock(&mut self, component_name: &str, config: LockConfig) -> Result<()> { fn activate_session_lock(&mut self, component_name: &str, config: LockConfig) -> Result<()> {
self.state.activate_session_lock(component_name, config) self.state.activate_session_lock(component_name, config)
} }

View file

@ -54,6 +54,7 @@ pub struct AppState {
global_context: Option<Rc<GlobalContext>>, global_context: Option<Rc<GlobalContext>>,
known_outputs: Vec<WlOutput>, known_outputs: Vec<WlOutput>,
slint_platform: Option<Rc<CustomSlintPlatform>>, slint_platform: Option<Rc<CustomSlintPlatform>>,
compilation_result: Option<Rc<CompilationResult>>,
output_registry: OutputRegistry, output_registry: OutputRegistry,
output_mapping: OutputMapping, output_mapping: OutputMapping,
surfaces: HashMap<ShellSurfaceKey, PerOutputSurface>, surfaces: HashMap<ShellSurfaceKey, PerOutputSurface>,
@ -83,6 +84,7 @@ impl AppState {
global_context: None, global_context: None,
known_outputs: Vec::new(), known_outputs: Vec::new(),
slint_platform: None, slint_platform: None,
compilation_result: None,
output_registry: OutputRegistry::new(), output_registry: OutputRegistry::new(),
output_mapping: OutputMapping::new(), output_mapping: OutputMapping::new(),
surfaces: HashMap::new(), surfaces: HashMap::new(),
@ -112,6 +114,10 @@ impl AppState {
self.slint_platform = Some(platform); self.slint_platform = Some(platform);
} }
pub fn set_compilation_result(&mut self, compilation_result: Rc<CompilationResult>) {
self.compilation_result = Some(compilation_result);
}
pub fn set_queue_handle(&mut self, queue_handle: wayland_client::QueueHandle<AppState>) { pub fn set_queue_handle(&mut self, queue_handle: wayland_client::QueueHandle<AppState>) {
self.queue_handle = Some(queue_handle); self.queue_handle = Some(queue_handle);
} }
@ -255,8 +261,12 @@ impl AppState {
component_name: &str, component_name: &str,
) -> Result<(ComponentDefinition, Option<Rc<CompilationResult>>)> { ) -> Result<(ComponentDefinition, Option<Rc<CompilationResult>>)> {
let compilation_result = self let compilation_result = self
.primary_output() .compilation_result
.clone()
.or_else(|| {
self.primary_output()
.and_then(SurfaceState::compilation_result) .and_then(SurfaceState::compilation_result)
})
.ok_or_else(|| LayerShikaError::InvalidInput { .ok_or_else(|| LayerShikaError::InvalidInput {
message: "No compilation result available for session lock".to_string(), message: "No compilation result available for session lock".to_string(),
})?; })?;

View file

@ -77,15 +77,11 @@ impl ShellBuilder {
} }
/// Builds the shell from the configured surfaces /// Builds the shell from the configured surfaces
///
/// If no surfaces are configured, creates a minimal shell without layer surfaces.
/// This is useful for lock-only applications that don't need persistent UI surfaces.
pub fn build(self) -> Result<Shell> { pub fn build(self) -> Result<Shell> {
let surfaces = if self.surfaces.is_empty() { let surfaces = self.surfaces;
vec![SurfaceDefinition {
component: DEFAULT_COMPONENT_NAME.to_string(),
config: SurfaceConfig::default(),
}]
} else {
self.surfaces
};
let compilation_result = match self.compilation { let compilation_result = match self.compilation {
CompilationSource::File { path, compiler } => { CompilationSource::File { path, compiler } => {
@ -399,27 +395,24 @@ impl Shell {
) -> Result<Self> { ) -> Result<Self> {
log::info!("Creating Shell with {} windows", definitions.len()); log::info!("Creating Shell with {} windows", definitions.len());
if definitions.is_empty() {
return Err(Error::Domain(DomainError::Configuration {
message: "At least one window definition is required".to_string(),
}));
}
for def in &definitions { for def in &definitions {
def.config.validate().map_err(Error::Domain)?; def.config.validate().map_err(Error::Domain)?;
} }
let is_single_window = definitions.len() == 1; match definitions.len() {
0 => {
if is_single_window { log::info!("Creating minimal shell without layer surfaces");
Self::new_minimal(compilation_result)
}
1 => {
let definition = definitions.into_iter().next().ok_or_else(|| { let definition = definitions.into_iter().next().ok_or_else(|| {
Error::Domain(DomainError::Configuration { Error::Domain(DomainError::Configuration {
message: "Expected at least one window definition".to_string(), message: "Expected at least one window definition".to_string(),
}) })
})?; })?;
Self::new_single_window(compilation_result, definition) Self::new_single_window(compilation_result, definition)
} else { }
Self::new_multi_window(compilation_result, &definitions) _ => Self::new_multi_window(compilation_result, &definitions),
} }
} }
@ -545,6 +538,34 @@ impl Shell {
Ok(shell) Ok(shell)
} }
fn new_minimal(compilation_result: Rc<CompilationResult>) -> Result<Self> {
let inner = layer_shika_adapters::WaylandShellSystem::new_minimal()?;
let inner_rc: Rc<RefCell<dyn WaylandSystemOps>> = Rc::new(RefCell::new(inner));
inner_rc
.borrow_mut()
.set_compilation_result(Rc::clone(&compilation_result));
let (sender, receiver) = channel::channel();
let registry = SurfaceRegistry::new();
let shell = Self {
inner: Rc::clone(&inner_rc),
registry,
compilation_result,
command_sender: sender,
output_connected_handlers: Rc::new(RefCell::new(Vec::new())),
output_disconnected_handlers: Rc::new(RefCell::new(Vec::new())),
};
shell.setup_command_handler(receiver)?;
log::info!("Shell created (minimal mode - no layer surfaces)");
Ok(shell)
}
fn setup_command_handler(&self, receiver: channel::Channel<ShellCommand>) -> Result<()> { fn setup_command_handler(&self, receiver: channel::Channel<ShellCommand>) -> Result<()> {
let loop_handle = self.inner.borrow().event_loop_handle(); let loop_handle = self.inner.borrow().event_loop_handle();
let control = self.control(); let control = self.control();