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>) {
self.pending_windows.borrow_mut().push(window);
}

View file

@ -20,7 +20,7 @@ use crate::wayland::surfaces::app_state::AppState;
pub struct GlobalContext {
pub compositor: WlCompositor,
pub outputs: Vec<WlOutput>,
pub layer_shell: ZwlrLayerShellV1,
pub layer_shell: Option<ZwlrLayerShellV1>,
pub seat: WlSeat,
pub xdg_wm_base: Option<XdgWmBase>,
pub session_lock_manager: Option<ExtSessionLockManagerV1>,
@ -38,14 +38,17 @@ impl GlobalContext {
.map(|(global_list, _)| global_list)
.map_err(|e| LayerShikaError::GlobalInitialization { source: e })?;
let (compositor, layer_shell, seat) = bind_globals!(
let (compositor, seat) = bind_globals!(
&global_list,
queue_handle,
(WlCompositor, compositor, 3..=6),
(ZwlrLayerShellV1, layer_shell, 1..=5),
(WlSeat, seat, 1..=9)
)?;
let layer_shell = global_list
.bind::<ZwlrLayerShellV1, _, _>(queue_handle, 1..=5, ())
.ok();
let output_names: Vec<u32> = global_list
.contents()
.clone_list()
@ -115,6 +118,10 @@ impl GlobalContext {
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())?;
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_state::LockState;
use layer_shika_domain::value_objects::output_handle::OutputHandle;
use slint_interpreter::ComponentInstance;
use slint_interpreter::{ComponentInstance, CompilationResult};
use slint_interpreter::Value;
use smithay_client_toolkit::reexports::calloop::LoopHandle;
use std::rc::Rc;
@ -19,6 +19,8 @@ pub trait WaylandSystemOps {
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 deactivate_session_lock(&mut self) -> Result<()>;

View file

@ -35,7 +35,7 @@ use super::OutputMapping;
pub struct OutputManagerContext {
pub compositor: WlCompositor,
pub layer_shell: ZwlrLayerShellV1,
pub layer_shell: Option<ZwlrLayerShellV1>,
pub fractional_scale_manager: Option<WpFractionalScaleManagerV1>,
pub viewporter: Option<WpViewporter>,
pub render_factory: Rc<RenderContextFactory>,
@ -161,10 +161,16 @@ impl OutputManager {
_output_id: &ObjectId,
queue_handle: &QueueHandle<AppState>,
) -> 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 {
compositor: &self.context.compositor,
output,
layer_shell: &self.context.layer_shell,
layer_shell,
fractional_scale_manager: self.context.fractional_scale_manager.as_ref(),
viewporter: self.context.viewporter.as_ref(),
queue_handle,

View file

@ -37,7 +37,7 @@ use slint::{
LogicalPosition, PhysicalSize, PlatformError, WindowPosition,
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::{
EventLoop, Interest, LoopHandle, Mode, PostAction, generic::Generic,
};
@ -97,9 +97,7 @@ impl WaylandShellSystem {
pub fn new_multi(configs: &[ShellSurfaceConfig]) -> Result<Self> {
if configs.is_empty() {
return Err(LayerShikaError::InvalidInput {
message: "At least one surface config is required".into(),
});
return Self::new_minimal();
}
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>)> {
let connection = Rc::new(Connection::connect_to_env()?);
let event_queue = connection.new_event_queue();
@ -145,6 +159,12 @@ impl WaylandShellSystem {
pointer: &Rc<WlPointer>,
layer_surface_config: &LayerSurfaceConfig,
) -> 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();
for (index, output) in global_ctx.outputs.iter().enumerate() {
@ -163,7 +183,7 @@ impl WaylandShellSystem {
let setup_params = SurfaceSetupParams {
compositor: &global_ctx.compositor,
output,
layer_shell: &global_ctx.layer_shell,
layer_shell,
fractional_scale_manager: global_ctx.fractional_scale_manager.as_ref(),
viewporter: global_ctx.viewporter.as_ref(),
queue_handle: &event_queue.handle(),
@ -426,6 +446,38 @@ impl WaylandShellSystem {
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(
configs: &[ShellSurfaceConfig],
global_ctx: &GlobalContext,
@ -433,6 +485,12 @@ impl WaylandShellSystem {
event_queue: &mut EventQueue<AppState>,
pointer: &Rc<WlPointer>,
) -> 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();
for (output_index, output) in global_ctx.outputs.iter().enumerate() {
@ -458,7 +516,7 @@ impl WaylandShellSystem {
let setup_params = SurfaceSetupParams {
compositor: &global_ctx.compositor,
output,
layer_shell: &global_ctx.layer_shell,
layer_shell,
fractional_scale_manager: global_ctx.fractional_scale_manager.as_ref(),
viewporter: global_ctx.viewporter.as_ref(),
queue_handle: &event_queue.handle(),
@ -805,6 +863,10 @@ impl WaylandSystemOps for WaylandShellSystem {
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<()> {
self.state.activate_session_lock(component_name, config)
}

View file

@ -54,6 +54,7 @@ pub struct AppState {
global_context: Option<Rc<GlobalContext>>,
known_outputs: Vec<WlOutput>,
slint_platform: Option<Rc<CustomSlintPlatform>>,
compilation_result: Option<Rc<CompilationResult>>,
output_registry: OutputRegistry,
output_mapping: OutputMapping,
surfaces: HashMap<ShellSurfaceKey, PerOutputSurface>,
@ -83,6 +84,7 @@ impl AppState {
global_context: None,
known_outputs: Vec::new(),
slint_platform: None,
compilation_result: None,
output_registry: OutputRegistry::new(),
output_mapping: OutputMapping::new(),
surfaces: HashMap::new(),
@ -112,6 +114,10 @@ impl AppState {
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>) {
self.queue_handle = Some(queue_handle);
}
@ -255,8 +261,12 @@ impl AppState {
component_name: &str,
) -> Result<(ComponentDefinition, Option<Rc<CompilationResult>>)> {
let compilation_result = self
.primary_output()
.and_then(SurfaceState::compilation_result)
.compilation_result
.clone()
.or_else(|| {
self.primary_output()
.and_then(SurfaceState::compilation_result)
})
.ok_or_else(|| LayerShikaError::InvalidInput {
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
///
/// 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> {
let surfaces = if self.surfaces.is_empty() {
vec![SurfaceDefinition {
component: DEFAULT_COMPONENT_NAME.to_string(),
config: SurfaceConfig::default(),
}]
} else {
self.surfaces
};
let surfaces = self.surfaces;
let compilation_result = match self.compilation {
CompilationSource::File { path, compiler } => {
@ -399,27 +395,24 @@ impl Shell {
) -> Result<Self> {
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 {
def.config.validate().map_err(Error::Domain)?;
}
let is_single_window = definitions.len() == 1;
if is_single_window {
let definition = definitions.into_iter().next().ok_or_else(|| {
Error::Domain(DomainError::Configuration {
message: "Expected at least one window definition".to_string(),
})
})?;
Self::new_single_window(compilation_result, definition)
} else {
Self::new_multi_window(compilation_result, &definitions)
match definitions.len() {
0 => {
log::info!("Creating minimal shell without layer surfaces");
Self::new_minimal(compilation_result)
}
1 => {
let definition = definitions.into_iter().next().ok_or_else(|| {
Error::Domain(DomainError::Configuration {
message: "Expected at least one window definition".to_string(),
})
})?;
Self::new_single_window(compilation_result, definition)
}
_ => Self::new_multi_window(compilation_result, &definitions),
}
}
@ -545,6 +538,34 @@ impl 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<()> {
let loop_handle = self.inner.borrow().event_loop_handle();
let control = self.control();