refactor: split lock manager

This commit is contained in:
drendog 2026-01-07 15:10:05 +01:00
parent 4c54feacbd
commit 6cbbce773f
Signed by: dwenya
GPG key ID: 8DD77074645332D0
13 changed files with 1103 additions and 873 deletions

View file

@ -8,7 +8,7 @@ pub use rendering::femtovg::popup_window::PopupWindow;
pub use wayland::config::{MultiSurfaceConfig, ShellSurfaceConfig, WaylandSurfaceConfig};
pub use wayland::ops::WaylandSystemOps;
pub use wayland::session_lock::lock_manager::{LockSurfaceOutputContext, OutputFilter};
pub use wayland::session_lock::{LockSurfaceOutputContext, OutputFilter};
pub use wayland::shell_adapter::WaylandShellSystem;
pub use wayland::surfaces::app_state::AppState;
pub use wayland::surfaces::popup_manager::PopupManager;

View file

@ -1,4 +1,4 @@
use crate::wayland::session_lock::lock_manager::LockSurfaceOutputContext;
use crate::wayland::session_lock::LockSurfaceOutputContext;
use crate::wayland::surfaces::app_state::AppState;
use crate::wayland::surfaces::display_metrics::DisplayMetrics;
use crate::wayland::surfaces::surface_state::SurfaceState;

View file

@ -1,6 +1,6 @@
use crate::errors::Result;
use crate::wayland::config::ShellSurfaceConfig;
use crate::wayland::session_lock::lock_manager::OutputFilter;
use crate::wayland::session_lock::OutputFilter;
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;

View file

@ -1,867 +0,0 @@
use crate::errors::{LayerShikaError, Result};
use crate::rendering::femtovg::main_window::FemtoVGWindow;
use crate::rendering::femtovg::renderable_window::RenderableWindow;
use crate::rendering::slint_integration::platform::CustomSlintPlatform;
use crate::wayland::session_lock::lock_context::{LockSurfaceParams, SessionLockContext};
use crate::wayland::session_lock::lock_surface::LockSurface;
use crate::wayland::surfaces::app_state::AppState;
use crate::wayland::surfaces::component_state::ComponentState;
use crate::wayland::surfaces::display_metrics::DisplayMetrics;
use crate::wayland::surfaces::keyboard_state::{KeyboardState, keysym_to_text};
use crate::wayland::surfaces::pointer_utils::wayland_button_to_slint;
use layer_shika_domain::surface_dimensions::SurfaceDimensions;
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 layer_shika_domain::value_objects::output_info::OutputInfo;
use log::info;
use slint::{
LogicalPosition, LogicalSize, SharedString, WindowPosition, WindowSize,
platform::{WindowAdapter, WindowEvent, femtovg_renderer::FemtoVGRenderer},
};
use slint_interpreter::{CompilationResult, ComponentDefinition, ComponentInstance, Value};
use std::collections::HashMap;
use std::rc::Rc;
use wayland_client::{
Proxy, QueueHandle, WEnum,
backend::ObjectId,
protocol::{wl_keyboard, wl_output::WlOutput, wl_pointer, wl_surface::WlSurface},
};
use wayland_protocols::ext::session_lock::v1::client::ext_session_lock_v1::ExtSessionLockV1;
use xkbcommon::xkb;
pub type LockCallbackHandler = Rc<dyn Fn(&[Value]) -> Value>;
pub type OutputFilter = Rc<
dyn Fn(
&str,
OutputHandle,
Option<&OutputInfo>,
Option<OutputHandle>,
Option<OutputHandle>,
) -> bool,
>;
#[derive(Clone)]
pub(crate) struct LockCallback {
name: String,
handler: LockCallbackHandler,
filter: Option<OutputFilter>,
}
impl LockCallback {
pub fn new(name: impl Into<String>, handler: LockCallbackHandler) -> Self {
Self {
name: name.into(),
handler,
filter: None,
}
}
pub fn with_filter(
name: impl Into<String>,
handler: LockCallbackHandler,
filter: OutputFilter,
) -> Self {
Self {
name: name.into(),
handler,
filter: Some(filter),
}
}
pub fn should_apply(
&self,
component_name: &str,
output_handle: OutputHandle,
output_info: Option<&OutputInfo>,
primary_handle: Option<OutputHandle>,
active_handle: Option<OutputHandle>,
) -> bool {
self.filter.as_ref().map_or_else(
|| true,
|f| {
f(
component_name,
output_handle,
output_info,
primary_handle,
active_handle,
)
},
)
}
pub fn apply_to(&self, component: &ComponentInstance) -> Result<()> {
let handler = Rc::clone(&self.handler);
component
.set_callback(&self.name, move |args| handler(args))
.map_err(|e| LayerShikaError::InvalidInput {
message: format!("Failed to register callback '{}': {e}", self.name),
})
}
}
struct ActiveLockSurface {
surface: LockSurface,
window: Rc<FemtoVGWindow>,
component: Option<ComponentState>,
scale_factor: f32,
has_fractional_scale: bool,
}
pub struct LockSurfaceOutputContext {
pub output_handle: OutputHandle,
pub output_info: Option<OutputInfo>,
pub primary_handle: Option<OutputHandle>,
pub active_handle: Option<OutputHandle>,
}
struct LockConfigureContext {
scale_factor: f32,
component_definition: ComponentDefinition,
compilation_result: Option<Rc<CompilationResult>>,
platform: Rc<CustomSlintPlatform>,
callbacks: Vec<LockCallback>,
component_name: String,
output_handle: OutputHandle,
output_info: Option<OutputInfo>,
primary_handle: Option<OutputHandle>,
active_handle: Option<OutputHandle>,
}
impl ActiveLockSurface {
fn new(surface: LockSurface, window: Rc<FemtoVGWindow>) -> Self {
Self {
has_fractional_scale: surface.fractional_scale().is_some(),
surface,
window,
component: None,
scale_factor: 1.0,
}
}
fn handle_configure(
&mut self,
serial: u32,
width: u32,
height: u32,
context: &LockConfigureContext,
) -> Result<()> {
self.surface.handle_configure(serial, width, height);
self.scale_factor = context.scale_factor;
let dimensions = match SurfaceDimensions::calculate(width, height, context.scale_factor) {
Ok(dimensions) => dimensions,
Err(err) => {
info!("Failed to calculate lock surface dimensions: {err}");
return Ok(());
}
};
let scaling_mode = self.scaling_mode();
info!(
"Lock surface dimensions: logical {}x{}, physical {}x{}, scale {}, mode {:?}",
dimensions.logical_width(),
dimensions.logical_height(),
dimensions.physical_width(),
dimensions.physical_height(),
context.scale_factor,
scaling_mode
);
self.configure_window(&dimensions, scaling_mode, context.scale_factor);
self.configure_surface(&dimensions, scaling_mode);
if self.component.is_none() {
context.platform.add_window(Rc::clone(&self.window));
let component = ComponentState::new(
context.component_definition.clone(),
context.compilation_result.clone(),
&self.window,
)?;
self.window
.window()
.dispatch_event(WindowEvent::WindowActiveChanged(true));
for callback in &context.callbacks {
if callback.should_apply(
&context.component_name,
context.output_handle,
context.output_info.as_ref(),
context.primary_handle,
context.active_handle,
) {
if let Err(err) = callback.apply_to(component.component_instance()) {
info!(
"Failed to register lock callback '{}': {err}",
callback.name
);
} else {
info!("Registered lock callback '{}'", callback.name);
}
} else {
info!(
"Skipping callback '{}' due to selector filter (output {:?})",
callback.name, context.output_handle
);
}
}
self.component = Some(component);
}
RenderableWindow::request_redraw(self.window.as_ref());
Ok(())
}
fn render_frame_if_dirty(&self) -> Result<()> {
self.window.render_frame_if_dirty()
}
fn handle_fractional_scale(&mut self, scale_120ths: u32) {
let scale_factor = DisplayMetrics::scale_factor_from_120ths(scale_120ths);
self.scale_factor = scale_factor;
if self.surface.width() == 0 || self.surface.height() == 0 {
return;
}
let Ok(dimensions) =
SurfaceDimensions::calculate(self.surface.width(), self.surface.height(), scale_factor)
else {
return;
};
let scaling_mode = self.scaling_mode();
self.configure_window(&dimensions, scaling_mode, scale_factor);
self.configure_surface(&dimensions, scaling_mode);
RenderableWindow::request_redraw(self.window.as_ref());
}
fn apply_callback(&self, callback: &LockCallback) {
if let Some(component) = self.component.as_ref() {
if let Err(err) = callback.apply_to(component.component_instance()) {
info!(
"Failed to register lock callback '{}': {err}",
callback.name
);
}
}
}
fn scaling_mode(&self) -> LockScalingMode {
if self.surface.has_fractional_scale() && self.surface.has_viewport() {
LockScalingMode::FractionalWithViewport
} else if self.surface.has_fractional_scale() {
LockScalingMode::FractionalOnly
} else {
LockScalingMode::Integer
}
}
#[allow(clippy::cast_precision_loss)]
fn configure_window(
&self,
dimensions: &SurfaceDimensions,
mode: LockScalingMode,
scale_factor: f32,
) {
match mode {
LockScalingMode::FractionalWithViewport | LockScalingMode::FractionalOnly => {
RenderableWindow::set_scale_factor(self.window.as_ref(), scale_factor);
self.window.set_size(WindowSize::Logical(LogicalSize::new(
dimensions.logical_width() as f32,
dimensions.logical_height() as f32,
)));
}
LockScalingMode::Integer => {
RenderableWindow::set_scale_factor(self.window.as_ref(), scale_factor);
self.window
.set_size(WindowSize::Physical(slint::PhysicalSize::new(
dimensions.physical_width(),
dimensions.physical_height(),
)));
}
}
}
fn configure_surface(&self, dimensions: &SurfaceDimensions, mode: LockScalingMode) {
match mode {
LockScalingMode::FractionalWithViewport => {
self.surface.configure_fractional_viewport(
dimensions.logical_width(),
dimensions.logical_height(),
);
}
LockScalingMode::FractionalOnly | LockScalingMode::Integer => {
self.surface
.configure_buffer_scale(dimensions.buffer_scale());
}
}
}
#[allow(clippy::cast_possible_truncation)]
fn to_logical_position(&self, surface_x: f64, surface_y: f64) -> LogicalPosition {
if self.has_fractional_scale {
let x = surface_x as f32;
let y = surface_y as f32;
LogicalPosition::new(x, y)
} else {
let x = (surface_x / f64::from(self.scale_factor)) as f32;
let y = (surface_y / f64::from(self.scale_factor)) as f32;
LogicalPosition::new(x, y)
}
}
fn dispatch_event(&self, event: WindowEvent) {
self.window.window().dispatch_event(event);
}
fn window_rc(&self) -> Rc<FemtoVGWindow> {
Rc::clone(&self.window)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum LockScalingMode {
FractionalWithViewport,
FractionalOnly,
Integer,
}
pub struct SessionLockManager {
context: Rc<SessionLockContext>,
session_lock: Option<ExtSessionLockV1>,
lock_surfaces: HashMap<ObjectId, ActiveLockSurface>,
state: LockState,
config: LockConfig,
component_definition: ComponentDefinition,
compilation_result: Option<Rc<CompilationResult>>,
platform: Rc<CustomSlintPlatform>,
callbacks: Vec<LockCallback>,
active_pointer_surface_id: Option<ObjectId>,
keyboard_focus_surface_id: Option<ObjectId>,
current_pointer_position: LogicalPosition,
accumulated_axis_x: f32,
accumulated_axis_y: f32,
}
impl SessionLockManager {
#[must_use]
pub fn new(
context: Rc<SessionLockContext>,
component_definition: ComponentDefinition,
compilation_result: Option<Rc<CompilationResult>>,
platform: Rc<CustomSlintPlatform>,
config: LockConfig,
) -> Self {
Self {
context,
session_lock: None,
lock_surfaces: HashMap::new(),
state: LockState::Inactive,
config,
component_definition,
compilation_result,
platform,
callbacks: Vec::new(),
active_pointer_surface_id: None,
keyboard_focus_surface_id: None,
current_pointer_position: LogicalPosition::new(0.0, 0.0),
accumulated_axis_x: 0.0,
accumulated_axis_y: 0.0,
}
}
#[must_use]
pub const fn state(&self) -> LockState {
self.state
}
pub fn activate(
&mut self,
outputs: impl IntoIterator<Item = WlOutput>,
queue_handle: &QueueHandle<AppState>,
) -> Result<()> {
if !self.state.can_activate() {
return Err(LayerShikaError::InvalidInput {
message: format!("Session lock cannot activate in state {:?}", self.state),
});
}
self.config.validate()?;
let session_lock = self.context.lock_manager().lock(queue_handle, ());
self.session_lock = Some(session_lock.clone());
self.state = LockState::Locking;
for output in outputs {
let params = LockSurfaceParams {
compositor: self.context.compositor(),
output: &output,
session_lock: &session_lock,
fractional_scale_manager: self.context.fractional_scale_manager(),
viewporter: self.context.viewporter(),
queue_handle,
};
let surface = LockSurface::create(&params);
let surface_id = surface.surface_id();
let window = self.create_window(&surface_id)?;
self.lock_surfaces
.insert(output.id(), ActiveLockSurface::new(surface, window));
}
Ok(())
}
pub fn handle_locked(&mut self) {
if self.state == LockState::Locking {
info!("Session lock transitioned to Locked");
self.state = LockState::Locked;
}
}
pub fn deactivate(&mut self) -> Result<()> {
if !self.state.can_deactivate() {
return Err(LayerShikaError::InvalidInput {
message: format!("Session lock cannot deactivate in state {:?}", self.state),
});
}
let Some(session_lock) = self.session_lock.take() else {
return Err(LayerShikaError::InvalidInput {
message: "Session lock object missing during deactivate".to_string(),
});
};
for surface in self.lock_surfaces.values() {
surface.surface.destroy();
}
session_lock.unlock_and_destroy();
self.lock_surfaces.clear();
self.active_pointer_surface_id = None;
self.keyboard_focus_surface_id = None;
self.state = LockState::Unlocking;
Ok(())
}
pub fn handle_finished(&mut self) {
info!("Session lock finished");
self.lock_surfaces.clear();
self.session_lock = None;
self.state = LockState::Inactive;
self.active_pointer_surface_id = None;
self.keyboard_focus_surface_id = None;
}
pub fn add_output(
&mut self,
output: &WlOutput,
queue_handle: &QueueHandle<AppState>,
) -> Result<()> {
if self.state != LockState::Locked {
return Ok(());
}
let output_id = output.id();
if self.lock_surfaces.contains_key(&output_id) {
return Ok(());
}
let Some(session_lock) = self.session_lock.as_ref() else {
return Err(LayerShikaError::InvalidInput {
message: "Session lock object missing during output hotplug".to_string(),
});
};
info!("Adding lock surface for output {output_id:?}");
let params = LockSurfaceParams {
compositor: self.context.compositor(),
output,
session_lock,
fractional_scale_manager: self.context.fractional_scale_manager(),
viewporter: self.context.viewporter(),
queue_handle,
};
let surface = LockSurface::create(&params);
let surface_id = surface.surface_id();
let window = self.create_window(&surface_id)?;
self.lock_surfaces
.insert(output_id, ActiveLockSurface::new(surface, window));
Ok(())
}
pub fn remove_output(&mut self, output_id: &ObjectId) {
if let Some(surface) = self.lock_surfaces.remove(output_id) {
let surface_id = surface.surface.surface_id();
if self.active_pointer_surface_id.as_ref() == Some(&surface_id) {
self.active_pointer_surface_id = None;
}
if self.keyboard_focus_surface_id.as_ref() == Some(&surface_id) {
self.keyboard_focus_surface_id = None;
}
drop(surface);
}
}
fn find_surface_by_lock_surface_id_mut(
&mut self,
lock_surface_id: &ObjectId,
) -> Option<&mut ActiveLockSurface> {
self.lock_surfaces
.values_mut()
.find(|surface| surface.surface.lock_surface_id() == *lock_surface_id)
}
fn find_surface_by_surface_id(&self, surface_id: &ObjectId) -> Option<&ActiveLockSurface> {
self.lock_surfaces
.values()
.find(|surface| surface.surface.surface_id() == *surface_id)
}
pub fn find_output_id_for_lock_surface(&self, lock_surface_id: &ObjectId) -> Option<ObjectId> {
self.lock_surfaces
.iter()
.find(|(_, surface)| surface.surface.surface_id() == *lock_surface_id)
.map(|(id, _)| id.clone())
}
pub fn handle_configure(
&mut self,
lock_surface_id: &ObjectId,
serial: u32,
width: u32,
height: u32,
output_ctx: LockSurfaceOutputContext,
) -> Result<()> {
let component_name = self.component_definition.name().to_string();
let context = LockConfigureContext {
scale_factor: self.config.scale_factor.value(),
component_definition: self.component_definition.clone(),
compilation_result: self.compilation_result.clone(),
platform: Rc::clone(&self.platform),
callbacks: self.callbacks.clone(),
component_name,
output_handle: output_ctx.output_handle,
output_info: output_ctx.output_info,
primary_handle: output_ctx.primary_handle,
active_handle: output_ctx.active_handle,
};
let Some(surface) = self.find_surface_by_lock_surface_id_mut(lock_surface_id) else {
return Ok(());
};
surface.handle_configure(serial, width, height, &context)
}
pub fn render_frames(&self) -> Result<()> {
for surface in self.lock_surfaces.values() {
surface.render_frame_if_dirty()?;
}
Ok(())
}
pub(crate) fn register_callback(&mut self, callback: LockCallback) {
for surface in self.lock_surfaces.values() {
surface.apply_callback(&callback);
}
self.callbacks.push(callback);
}
pub fn handle_fractional_scale(&mut self, fractional_scale_id: &ObjectId, scale_120ths: u32) {
for surface in self.lock_surfaces.values_mut() {
let matches = surface
.surface
.fractional_scale()
.is_some_and(|fs| fs.id() == *fractional_scale_id);
if matches {
surface.handle_fractional_scale(scale_120ths);
}
}
}
pub fn is_lock_surface(&self, surface_id: &ObjectId) -> bool {
self.find_surface_by_surface_id(surface_id).is_some()
}
pub const fn has_active_pointer(&self) -> bool {
self.active_pointer_surface_id.is_some()
}
pub const fn has_keyboard_focus(&self) -> bool {
self.keyboard_focus_surface_id.is_some()
}
pub fn handle_pointer_enter(
&mut self,
_serial: u32,
surface: &WlSurface,
surface_x: f64,
surface_y: f64,
) -> bool {
let surface_id = surface.id();
let (position, window) = {
let Some(active_surface) = self.find_surface_by_surface_id(&surface_id) else {
return false;
};
(
active_surface.to_logical_position(surface_x, surface_y),
active_surface.window_rc(),
)
};
self.active_pointer_surface_id = Some(surface_id.clone());
self.current_pointer_position = position;
info!("Lock pointer enter on {:?}", surface_id);
window
.window()
.dispatch_event(WindowEvent::PointerMoved { position });
true
}
pub fn handle_pointer_motion(&mut self, surface_x: f64, surface_y: f64) -> bool {
let Some(surface_id) = self.active_pointer_surface_id.clone() else {
return false;
};
let (position, window) = {
let Some(active_surface) = self.find_surface_by_surface_id(&surface_id) else {
return false;
};
(
active_surface.to_logical_position(surface_x, surface_y),
active_surface.window_rc(),
)
};
self.current_pointer_position = position;
window
.window()
.dispatch_event(WindowEvent::PointerMoved { position });
true
}
pub fn handle_pointer_leave(&mut self) -> bool {
let Some(surface_id) = self.active_pointer_surface_id.take() else {
return false;
};
if let Some(active_surface) = self.find_surface_by_surface_id(&surface_id) {
active_surface.dispatch_event(WindowEvent::PointerExited);
}
true
}
pub fn handle_pointer_button(
&mut self,
_serial: u32,
button: u32,
button_state: WEnum<wl_pointer::ButtonState>,
) -> bool {
let Some(surface_id) = self.active_pointer_surface_id.clone() else {
return false;
};
let window = {
let Some(active_surface) = self.find_surface_by_surface_id(&surface_id) else {
return false;
};
active_surface.window_rc()
};
let position = self.current_pointer_position;
let slint_button = wayland_button_to_slint(button);
let event = match button_state {
WEnum::Value(wl_pointer::ButtonState::Pressed) => WindowEvent::PointerPressed {
button: slint_button,
position,
},
WEnum::Value(wl_pointer::ButtonState::Released) => WindowEvent::PointerReleased {
button: slint_button,
position,
},
_ => return true,
};
info!(
"Lock pointer button {:?} at {:?} (scale {})",
button_state,
position,
self.config.scale_factor.value()
);
window.window().dispatch_event(event);
true
}
pub fn handle_axis_source(&mut self, _axis_source: wl_pointer::AxisSource) -> bool {
if self.active_pointer_surface_id.is_none() {
return false;
}
true
}
pub fn handle_axis(&mut self, axis: wl_pointer::Axis, value: f64) -> bool {
if self.active_pointer_surface_id.is_none() {
return false;
}
match axis {
wl_pointer::Axis::HorizontalScroll => {
#[allow(clippy::cast_possible_truncation)]
let delta = value as f32;
self.accumulated_axis_x += delta;
}
wl_pointer::Axis::VerticalScroll => {
#[allow(clippy::cast_possible_truncation)]
let delta = value as f32;
self.accumulated_axis_y += delta;
}
_ => {}
}
true
}
pub fn handle_axis_discrete(&mut self, axis: wl_pointer::Axis, discrete: i32) -> bool {
if self.active_pointer_surface_id.is_none() {
return false;
}
#[allow(clippy::cast_precision_loss)]
let delta = (discrete as f32) * 60.0;
match axis {
wl_pointer::Axis::HorizontalScroll => {
self.accumulated_axis_x += delta;
}
wl_pointer::Axis::VerticalScroll => {
self.accumulated_axis_y += delta;
}
_ => {}
}
true
}
pub fn handle_axis_stop(&mut self, _axis: wl_pointer::Axis) -> bool {
self.active_pointer_surface_id.is_some()
}
pub fn handle_pointer_frame(&mut self) -> bool {
let Some(surface_id) = self.active_pointer_surface_id.clone() else {
return false;
};
let delta_x = self.accumulated_axis_x;
let delta_y = self.accumulated_axis_y;
self.accumulated_axis_x = 0.0;
self.accumulated_axis_y = 0.0;
let window = {
let Some(active_surface) = self.find_surface_by_surface_id(&surface_id) else {
return false;
};
active_surface.window_rc()
};
if delta_x.abs() > f32::EPSILON || delta_y.abs() > f32::EPSILON {
let position = self.current_pointer_position;
window
.window()
.dispatch_event(WindowEvent::PointerScrolled {
position,
delta_x,
delta_y,
});
}
true
}
pub fn handle_keyboard_enter(&mut self, surface: &WlSurface) -> bool {
let surface_id = surface.id();
if self.find_surface_by_surface_id(&surface_id).is_some() {
self.keyboard_focus_surface_id = Some(surface_id);
return true;
}
false
}
pub fn handle_keyboard_leave(&mut self, surface: &WlSurface) -> bool {
let surface_id = surface.id();
if self.keyboard_focus_surface_id.as_ref() == Some(&surface_id) {
self.keyboard_focus_surface_id = None;
return true;
}
false
}
pub fn handle_keyboard_key(
&mut self,
key: u32,
state: wl_keyboard::KeyState,
keyboard_state: &mut KeyboardState,
) -> bool {
let Some(surface_id) = self.keyboard_focus_surface_id.clone() else {
return false;
};
let Some(active_surface) = self.find_surface_by_surface_id(&surface_id) else {
return false;
};
let Some(xkb_state) = keyboard_state.xkb_state.as_mut() else {
return true;
};
let keycode = xkb::Keycode::new(key + 8);
let direction = match state {
wl_keyboard::KeyState::Pressed => xkb::KeyDirection::Down,
wl_keyboard::KeyState::Released => xkb::KeyDirection::Up,
_ => return true,
};
xkb_state.update_key(keycode, direction);
let text = xkb_state.key_get_utf8(keycode);
let text = if text.is_empty() {
let keysym = xkb_state.key_get_one_sym(keycode);
keysym_to_text(keysym)
} else {
Some(SharedString::from(text.as_str()))
};
let Some(text) = text else {
return true;
};
let event = match state {
wl_keyboard::KeyState::Pressed => WindowEvent::KeyPressed { text },
wl_keyboard::KeyState::Released => WindowEvent::KeyReleased { text },
_ => return true,
};
info!("Lock key event {:?}", state);
active_surface.dispatch_event(event);
true
}
fn create_window(&self, surface_id: &ObjectId) -> Result<Rc<FemtoVGWindow>> {
let init_size = LogicalSize::new(1.0, 1.0);
let context = self.context.render_factory().create_context(
surface_id,
init_size.to_physical(self.config.scale_factor.value()),
)?;
let renderer = FemtoVGRenderer::new(context)
.map_err(|e| LayerShikaError::FemtoVGRendererCreation { source: e })?;
let window = FemtoVGWindow::new(renderer);
RenderableWindow::set_scale_factor(window.as_ref(), self.config.scale_factor.value());
window.set_size(WindowSize::Logical(init_size));
window.set_position(WindowPosition::Logical(LogicalPosition::new(0., 0.)));
Ok(window)
}
pub fn iter_lock_surfaces(&self, f: &mut dyn FnMut(&ObjectId, &ComponentInstance)) {
for (output_id, active_surface) in &self.lock_surfaces {
if let Some(component) = active_surface.component.as_ref() {
f(output_id, component.component_instance());
}
}
}
pub const fn component_name(&self) -> &ComponentDefinition {
&self.component_definition
}
pub fn count_lock_surfaces(&self) -> usize {
self.lock_surfaces
.values()
.filter(|s| s.component.is_some())
.count()
}
}

View file

@ -0,0 +1,80 @@
use crate::errors::{LayerShikaError, Result};
use layer_shika_domain::value_objects::output_handle::OutputHandle;
use layer_shika_domain::value_objects::output_info::OutputInfo;
use slint_interpreter::{ComponentInstance, Value};
use std::rc::Rc;
pub type LockCallbackHandler = Rc<dyn Fn(&[Value]) -> Value>;
pub type OutputFilter = Rc<
dyn Fn(
&str,
OutputHandle,
Option<&OutputInfo>,
Option<OutputHandle>,
Option<OutputHandle>,
) -> bool,
>;
#[derive(Clone)]
pub struct LockCallback {
name: String,
handler: LockCallbackHandler,
filter: Option<OutputFilter>,
}
impl LockCallback {
pub fn new(name: impl Into<String>, handler: LockCallbackHandler) -> Self {
Self {
name: name.into(),
handler,
filter: None,
}
}
pub fn with_filter(
name: impl Into<String>,
handler: LockCallbackHandler,
filter: OutputFilter,
) -> Self {
Self {
name: name.into(),
handler,
filter: Some(filter),
}
}
pub fn should_apply(
&self,
component_name: &str,
output_handle: OutputHandle,
output_info: Option<&OutputInfo>,
primary_handle: Option<OutputHandle>,
active_handle: Option<OutputHandle>,
) -> bool {
self.filter.as_ref().map_or_else(
|| true,
|f| {
f(
component_name,
output_handle,
output_info,
primary_handle,
active_handle,
)
},
)
}
pub fn apply_to(&self, component: &ComponentInstance) -> Result<()> {
let handler = Rc::clone(&self.handler);
component
.set_callback(&self.name, move |args| handler(args))
.map_err(|e| LayerShikaError::InvalidInput {
message: format!("Failed to register callback '{}': {e}", self.name),
})
}
pub const fn name(&self) -> &String {
&self.name
}
}

View file

@ -0,0 +1,331 @@
use crate::wayland::surfaces::keyboard_state::{KeyboardState, keysym_to_text};
use crate::wayland::surfaces::pointer_utils::wayland_button_to_slint;
use log::info;
use slint::{
LogicalPosition, SharedString,
platform::{WindowAdapter, WindowEvent},
};
use wayland_client::{
Proxy, WEnum,
backend::ObjectId,
protocol::{wl_keyboard, wl_pointer, wl_surface::WlSurface},
};
use xkbcommon::xkb;
use super::state::ActiveLockSurface;
pub(super) struct InputState {
pub active_pointer_surface_id: Option<ObjectId>,
pub keyboard_focus_surface_id: Option<ObjectId>,
pub current_pointer_position: LogicalPosition,
pub accumulated_axis_x: f32,
pub accumulated_axis_y: f32,
}
impl InputState {
pub fn new() -> Self {
Self {
active_pointer_surface_id: None,
keyboard_focus_surface_id: None,
current_pointer_position: LogicalPosition::new(0.0, 0.0),
accumulated_axis_x: 0.0,
accumulated_axis_y: 0.0,
}
}
pub fn reset(&mut self) {
self.active_pointer_surface_id = None;
self.keyboard_focus_surface_id = None;
self.current_pointer_position = LogicalPosition::new(0.0, 0.0);
self.accumulated_axis_x = 0.0;
self.accumulated_axis_y = 0.0;
}
pub fn clear_surface_refs(&mut self, surface_id: &ObjectId) {
if self.active_pointer_surface_id.as_ref() == Some(surface_id) {
self.active_pointer_surface_id = None;
}
if self.keyboard_focus_surface_id.as_ref() == Some(surface_id) {
self.keyboard_focus_surface_id = None;
}
}
pub const fn has_active_pointer(&self) -> bool {
self.active_pointer_surface_id.is_some()
}
pub const fn has_keyboard_focus(&self) -> bool {
self.keyboard_focus_surface_id.is_some()
}
}
pub(super) fn handle_pointer_enter(
input_state: &mut InputState,
lock_surfaces: &[(ObjectId, ActiveLockSurface)],
_serial: u32,
surface: &WlSurface,
surface_x: f64,
surface_y: f64,
) -> bool {
let surface_id = surface.id();
let Some(active_surface) = find_surface_by_surface_id(lock_surfaces, &surface_id) else {
return false;
};
let position = active_surface.to_logical_position(surface_x, surface_y);
let window = active_surface.window_rc();
input_state.active_pointer_surface_id = Some(surface_id.clone());
input_state.current_pointer_position = position;
info!("Lock pointer enter on {:?}", surface_id);
window
.window()
.dispatch_event(WindowEvent::PointerMoved { position });
true
}
pub(super) fn handle_pointer_motion(
input_state: &mut InputState,
lock_surfaces: &[(ObjectId, ActiveLockSurface)],
surface_x: f64,
surface_y: f64,
) -> bool {
let Some(surface_id) = input_state.active_pointer_surface_id.clone() else {
return false;
};
let Some(active_surface) = find_surface_by_surface_id(lock_surfaces, &surface_id) else {
return false;
};
let position = active_surface.to_logical_position(surface_x, surface_y);
let window = active_surface.window_rc();
input_state.current_pointer_position = position;
window
.window()
.dispatch_event(WindowEvent::PointerMoved { position });
true
}
pub(super) fn handle_pointer_leave(
input_state: &mut InputState,
lock_surfaces: &[(ObjectId, ActiveLockSurface)],
) -> bool {
let Some(surface_id) = input_state.active_pointer_surface_id.take() else {
return false;
};
if let Some(active_surface) = find_surface_by_surface_id(lock_surfaces, &surface_id) {
active_surface.dispatch_event(WindowEvent::PointerExited);
}
true
}
pub(super) fn handle_pointer_button(
input_state: &mut InputState,
lock_surfaces: &[(ObjectId, ActiveLockSurface)],
scale_factor: f32,
_serial: u32,
button: u32,
button_state: WEnum<wl_pointer::ButtonState>,
) -> bool {
let Some(surface_id) = input_state.active_pointer_surface_id.clone() else {
return false;
};
let Some(active_surface) = find_surface_by_surface_id(lock_surfaces, &surface_id) else {
return false;
};
let window = active_surface.window_rc();
let position = input_state.current_pointer_position;
let slint_button = wayland_button_to_slint(button);
let event = match button_state {
WEnum::Value(wl_pointer::ButtonState::Pressed) => WindowEvent::PointerPressed {
button: slint_button,
position,
},
WEnum::Value(wl_pointer::ButtonState::Released) => WindowEvent::PointerReleased {
button: slint_button,
position,
},
_ => return true,
};
info!(
"Lock pointer button {:?} at {:?} (scale {})",
button_state, position, scale_factor
);
window.window().dispatch_event(event);
true
}
pub(super) fn handle_axis_source(
input_state: &InputState,
_axis_source: wl_pointer::AxisSource,
) -> bool {
input_state.active_pointer_surface_id.is_some()
}
pub(super) fn handle_axis(
input_state: &mut InputState,
axis: wl_pointer::Axis,
value: f64,
) -> bool {
if input_state.active_pointer_surface_id.is_none() {
return false;
}
match axis {
wl_pointer::Axis::HorizontalScroll => {
#[allow(clippy::cast_possible_truncation)]
let delta = value as f32;
input_state.accumulated_axis_x += delta;
}
wl_pointer::Axis::VerticalScroll => {
#[allow(clippy::cast_possible_truncation)]
let delta = value as f32;
input_state.accumulated_axis_y += delta;
}
_ => {}
}
true
}
pub(super) fn handle_axis_discrete(
input_state: &mut InputState,
axis: wl_pointer::Axis,
discrete: i32,
) -> bool {
if input_state.active_pointer_surface_id.is_none() {
return false;
}
#[allow(clippy::cast_precision_loss)]
let delta = (discrete as f32) * 60.0;
match axis {
wl_pointer::Axis::HorizontalScroll => {
input_state.accumulated_axis_x += delta;
}
wl_pointer::Axis::VerticalScroll => {
input_state.accumulated_axis_y += delta;
}
_ => {}
}
true
}
pub(super) fn handle_axis_stop(input_state: &InputState, _axis: wl_pointer::Axis) -> bool {
input_state.active_pointer_surface_id.is_some()
}
pub(super) fn handle_pointer_frame(
input_state: &mut InputState,
lock_surfaces: &[(ObjectId, ActiveLockSurface)],
) -> bool {
let Some(surface_id) = input_state.active_pointer_surface_id.clone() else {
return false;
};
let delta_x = input_state.accumulated_axis_x;
let delta_y = input_state.accumulated_axis_y;
input_state.accumulated_axis_x = 0.0;
input_state.accumulated_axis_y = 0.0;
let Some(active_surface) = find_surface_by_surface_id(lock_surfaces, &surface_id) else {
return false;
};
let window = active_surface.window_rc();
if delta_x.abs() > f32::EPSILON || delta_y.abs() > f32::EPSILON {
let position = input_state.current_pointer_position;
window
.window()
.dispatch_event(WindowEvent::PointerScrolled {
position,
delta_x,
delta_y,
});
}
true
}
pub(super) fn handle_keyboard_enter(
input_state: &mut InputState,
lock_surfaces: &[(ObjectId, ActiveLockSurface)],
surface: &WlSurface,
) -> bool {
let surface_id = surface.id();
if find_surface_by_surface_id(lock_surfaces, &surface_id).is_some() {
input_state.keyboard_focus_surface_id = Some(surface_id);
return true;
}
false
}
pub(super) fn handle_keyboard_leave(input_state: &mut InputState, surface: &WlSurface) -> bool {
let surface_id = surface.id();
if input_state.keyboard_focus_surface_id.as_ref() == Some(&surface_id) {
input_state.keyboard_focus_surface_id = None;
return true;
}
false
}
pub(super) fn handle_keyboard_key(
input_state: &InputState,
lock_surfaces: &[(ObjectId, ActiveLockSurface)],
key: u32,
state: wl_keyboard::KeyState,
keyboard_state: &mut KeyboardState,
) -> bool {
let Some(surface_id) = input_state.keyboard_focus_surface_id.clone() else {
return false;
};
let Some(active_surface) = find_surface_by_surface_id(lock_surfaces, &surface_id) else {
return false;
};
let Some(xkb_state) = keyboard_state.xkb_state.as_mut() else {
return true;
};
let keycode = xkb::Keycode::new(key + 8);
let direction = match state {
wl_keyboard::KeyState::Pressed => xkb::KeyDirection::Down,
wl_keyboard::KeyState::Released => xkb::KeyDirection::Up,
_ => return true,
};
xkb_state.update_key(keycode, direction);
let text = xkb_state.key_get_utf8(keycode);
let text = if text.is_empty() {
let keysym = xkb_state.key_get_one_sym(keycode);
keysym_to_text(keysym)
} else {
Some(SharedString::from(text.as_str()))
};
let Some(text) = text else {
return true;
};
let event = match state {
wl_keyboard::KeyState::Pressed => WindowEvent::KeyPressed { text },
wl_keyboard::KeyState::Released => WindowEvent::KeyReleased { text },
_ => return true,
};
info!("Lock key event {:?}", state);
active_surface.dispatch_event(event);
true
}
fn find_surface_by_surface_id<'a>(
lock_surfaces: &'a [(ObjectId, ActiveLockSurface)],
surface_id: &ObjectId,
) -> Option<&'a ActiveLockSurface> {
lock_surfaces
.iter()
.find(|(_, surface)| surface.surface().surface_id() == *surface_id)
.map(|(_, surface)| surface)
}

View file

@ -0,0 +1,28 @@
use crate::errors::{LayerShikaError, Result};
use crate::rendering::femtovg::main_window::FemtoVGWindow;
use crate::rendering::femtovg::renderable_window::RenderableWindow;
use crate::wayland::session_lock::lock_context::SessionLockContext;
use slint::{
LogicalPosition, LogicalSize, WindowPosition, WindowSize, platform::WindowAdapter,
platform::femtovg_renderer::FemtoVGRenderer,
};
use std::rc::Rc;
use wayland_client::backend::ObjectId;
pub(super) fn create_window(
context: &SessionLockContext,
surface_id: &ObjectId,
scale_factor: f32,
) -> Result<Rc<FemtoVGWindow>> {
let init_size = LogicalSize::new(1.0, 1.0);
let render_context = context
.render_factory()
.create_context(surface_id, init_size.to_physical(scale_factor))?;
let renderer = FemtoVGRenderer::new(render_context)
.map_err(|e| LayerShikaError::FemtoVGRendererCreation { source: e })?;
let window = FemtoVGWindow::new(renderer);
RenderableWindow::set_scale_factor(window.as_ref(), scale_factor);
window.set_size(WindowSize::Logical(init_size));
window.set_position(WindowPosition::Logical(LogicalPosition::new(0., 0.)));
Ok(window)
}

View file

@ -0,0 +1,397 @@
pub mod callbacks;
pub mod input_handling;
pub mod lifecycle;
pub mod rendering;
pub mod state;
use crate::errors::{LayerShikaError, Result};
use crate::rendering::slint_integration::platform::CustomSlintPlatform;
use crate::wayland::session_lock::lock_context::SessionLockContext;
use crate::wayland::session_lock::lock_surface::LockSurface;
use crate::wayland::surfaces::app_state::AppState;
use crate::wayland::surfaces::keyboard_state::KeyboardState;
use layer_shika_domain::value_objects::lock_config::LockConfig;
use layer_shika_domain::value_objects::lock_state::LockState;
use log::info;
use slint_interpreter::{CompilationResult, ComponentDefinition, ComponentInstance};
use std::rc::Rc;
use wayland_client::{
Proxy, QueueHandle, WEnum,
backend::ObjectId,
protocol::{wl_keyboard, wl_output::WlOutput, wl_pointer, wl_surface::WlSurface},
};
use wayland_protocols::ext::session_lock::v1::client::ext_session_lock_v1::ExtSessionLockV1;
pub use callbacks::{LockCallback, OutputFilter};
pub use state::{ActiveLockSurface, LockConfigureContext, LockSurfaceOutputContext};
use self::input_handling::InputState;
use crate::wayland::session_lock::lock_context::LockSurfaceParams;
pub struct SessionLockManager {
context: Rc<SessionLockContext>,
session_lock: Option<ExtSessionLockV1>,
lock_surfaces: Vec<(ObjectId, ActiveLockSurface)>,
state: LockState,
config: LockConfig,
component_definition: ComponentDefinition,
compilation_result: Option<Rc<CompilationResult>>,
platform: Rc<CustomSlintPlatform>,
callbacks: Vec<LockCallback>,
input_state: InputState,
}
impl SessionLockManager {
#[must_use]
pub fn new(
context: Rc<SessionLockContext>,
component_definition: ComponentDefinition,
compilation_result: Option<Rc<CompilationResult>>,
platform: Rc<CustomSlintPlatform>,
config: LockConfig,
) -> Self {
Self {
context,
session_lock: None,
lock_surfaces: Vec::new(),
state: LockState::Inactive,
config,
component_definition,
compilation_result,
platform,
callbacks: Vec::new(),
input_state: InputState::new(),
}
}
#[must_use]
pub const fn state(&self) -> LockState {
self.state
}
pub fn activate(
&mut self,
outputs: impl IntoIterator<Item = WlOutput>,
queue_handle: &QueueHandle<AppState>,
) -> Result<()> {
if !self.state.can_activate() {
return Err(LayerShikaError::InvalidInput {
message: format!("Session lock cannot activate in state {:?}", self.state),
});
}
self.config.validate()?;
let session_lock = self.context.lock_manager().lock(queue_handle, ());
self.session_lock = Some(session_lock.clone());
self.state = LockState::Locking;
for output in outputs {
let params = LockSurfaceParams {
compositor: self.context.compositor(),
output: &output,
session_lock: &session_lock,
fractional_scale_manager: self.context.fractional_scale_manager(),
viewporter: self.context.viewporter(),
queue_handle,
};
let surface = LockSurface::create(&params);
let surface_id = surface.surface_id();
let window = lifecycle::create_window(
&self.context,
&surface_id,
self.config.scale_factor.value(),
)?;
self.lock_surfaces
.push((output.id(), ActiveLockSurface::new(surface, window)));
}
Ok(())
}
pub fn handle_locked(&mut self) {
if self.state == LockState::Locking {
info!("Session lock transitioned to Locked");
self.state = LockState::Locked;
}
}
pub fn deactivate(&mut self) -> Result<()> {
if !self.state.can_deactivate() {
return Err(LayerShikaError::InvalidInput {
message: format!("Session lock cannot deactivate in state {:?}", self.state),
});
}
let Some(session_lock) = self.session_lock.take() else {
return Err(LayerShikaError::InvalidInput {
message: "Session lock object missing during deactivate".to_string(),
});
};
for (_, surface) in &self.lock_surfaces {
surface.surface().destroy();
}
session_lock.unlock_and_destroy();
self.lock_surfaces.clear();
self.input_state.reset();
self.state = LockState::Unlocking;
Ok(())
}
pub fn handle_finished(&mut self) {
info!("Session lock finished");
self.lock_surfaces.clear();
self.session_lock = None;
self.state = LockState::Inactive;
self.input_state.reset();
}
pub fn add_output(
&mut self,
output: &WlOutput,
queue_handle: &QueueHandle<AppState>,
) -> Result<()> {
if self.state != LockState::Locked {
return Ok(());
}
let output_id = output.id();
if self.lock_surfaces.iter().any(|(id, _)| *id == output_id) {
return Ok(());
}
let Some(session_lock) = self.session_lock.as_ref() else {
return Err(LayerShikaError::InvalidInput {
message: "Session lock object missing during output hotplug".to_string(),
});
};
info!("Adding lock surface for output {output_id:?}");
let params = LockSurfaceParams {
compositor: self.context.compositor(),
output,
session_lock,
fractional_scale_manager: self.context.fractional_scale_manager(),
viewporter: self.context.viewporter(),
queue_handle,
};
let surface = LockSurface::create(&params);
let surface_id = surface.surface_id();
let window =
lifecycle::create_window(&self.context, &surface_id, self.config.scale_factor.value())?;
self.lock_surfaces
.push((output_id, ActiveLockSurface::new(surface, window)));
Ok(())
}
pub fn remove_output(&mut self, output_id: &ObjectId) {
if let Some(pos) = self
.lock_surfaces
.iter()
.position(|(id, _)| id == output_id)
{
let (_, surface) = self.lock_surfaces.remove(pos);
let surface_id = surface.surface().surface_id();
self.input_state.clear_surface_refs(&surface_id);
drop(surface);
}
}
fn find_surface_by_lock_surface_id_mut(
&mut self,
lock_surface_id: &ObjectId,
) -> Option<&mut ActiveLockSurface> {
self.lock_surfaces
.iter_mut()
.find(|(_, surface)| surface.surface().lock_surface_id() == *lock_surface_id)
.map(|(_, surface)| surface)
}
fn find_surface_by_surface_id(&self, surface_id: &ObjectId) -> Option<&ActiveLockSurface> {
self.lock_surfaces
.iter()
.find(|(_, surface)| surface.surface().surface_id() == *surface_id)
.map(|(_, surface)| surface)
}
pub fn find_output_id_for_lock_surface(&self, lock_surface_id: &ObjectId) -> Option<ObjectId> {
self.lock_surfaces
.iter()
.find(|(_, surface)| surface.surface().surface_id() == *lock_surface_id)
.map(|(id, _)| id.clone())
}
pub fn handle_configure(
&mut self,
lock_surface_id: &ObjectId,
serial: u32,
width: u32,
height: u32,
output_ctx: LockSurfaceOutputContext,
) -> Result<()> {
let component_name = self.component_definition.name().to_string();
let context = LockConfigureContext {
scale_factor: self.config.scale_factor.value(),
component_definition: self.component_definition.clone(),
compilation_result: self.compilation_result.clone(),
platform: Rc::clone(&self.platform),
callbacks: self.callbacks.clone(),
component_name,
output_handle: output_ctx.output_handle,
output_info: output_ctx.output_info,
primary_handle: output_ctx.primary_handle,
active_handle: output_ctx.active_handle,
};
let Some(surface) = self.find_surface_by_lock_surface_id_mut(lock_surface_id) else {
return Ok(());
};
surface.handle_configure(serial, width, height, &context)
}
pub fn render_frames(&self) -> Result<()> {
rendering::render_frames(&self.lock_surfaces)
}
pub(crate) fn register_callback(&mut self, callback: LockCallback) {
for (_, surface) in &self.lock_surfaces {
surface.apply_callback(&callback);
}
self.callbacks.push(callback);
}
pub fn handle_fractional_scale(&mut self, fractional_scale_id: &ObjectId, scale_120ths: u32) {
for (_, surface) in &mut self.lock_surfaces {
let matches = surface
.surface()
.fractional_scale()
.is_some_and(|fs| fs.id() == *fractional_scale_id);
if matches {
surface.handle_fractional_scale(scale_120ths);
}
}
}
pub fn is_lock_surface(&self, surface_id: &ObjectId) -> bool {
self.find_surface_by_surface_id(surface_id).is_some()
}
pub const fn has_active_pointer(&self) -> bool {
self.input_state.has_active_pointer()
}
pub const fn has_keyboard_focus(&self) -> bool {
self.input_state.has_keyboard_focus()
}
pub fn handle_pointer_enter(
&mut self,
serial: u32,
surface: &WlSurface,
surface_x: f64,
surface_y: f64,
) -> bool {
input_handling::handle_pointer_enter(
&mut self.input_state,
&self.lock_surfaces,
serial,
surface,
surface_x,
surface_y,
)
}
pub fn handle_pointer_motion(&mut self, surface_x: f64, surface_y: f64) -> bool {
input_handling::handle_pointer_motion(
&mut self.input_state,
&self.lock_surfaces,
surface_x,
surface_y,
)
}
pub fn handle_pointer_leave(&mut self) -> bool {
input_handling::handle_pointer_leave(&mut self.input_state, &self.lock_surfaces)
}
pub fn handle_pointer_button(
&mut self,
serial: u32,
button: u32,
button_state: WEnum<wl_pointer::ButtonState>,
) -> bool {
input_handling::handle_pointer_button(
&mut self.input_state,
&self.lock_surfaces,
self.config.scale_factor.value(),
serial,
button,
button_state,
)
}
pub fn handle_axis_source(&mut self, axis_source: wl_pointer::AxisSource) -> bool {
input_handling::handle_axis_source(&self.input_state, axis_source)
}
pub fn handle_axis(&mut self, axis: wl_pointer::Axis, value: f64) -> bool {
input_handling::handle_axis(&mut self.input_state, axis, value)
}
pub fn handle_axis_discrete(&mut self, axis: wl_pointer::Axis, discrete: i32) -> bool {
input_handling::handle_axis_discrete(&mut self.input_state, axis, discrete)
}
pub fn handle_axis_stop(&mut self, axis: wl_pointer::Axis) -> bool {
input_handling::handle_axis_stop(&self.input_state, axis)
}
pub fn handle_pointer_frame(&mut self) -> bool {
input_handling::handle_pointer_frame(&mut self.input_state, &self.lock_surfaces)
}
pub fn handle_keyboard_enter(&mut self, surface: &WlSurface) -> bool {
input_handling::handle_keyboard_enter(&mut self.input_state, &self.lock_surfaces, surface)
}
pub fn handle_keyboard_leave(&mut self, surface: &WlSurface) -> bool {
input_handling::handle_keyboard_leave(&mut self.input_state, surface)
}
pub fn handle_keyboard_key(
&mut self,
key: u32,
state: wl_keyboard::KeyState,
keyboard_state: &mut KeyboardState,
) -> bool {
input_handling::handle_keyboard_key(
&self.input_state,
&self.lock_surfaces,
key,
state,
keyboard_state,
)
}
pub fn iter_lock_surfaces(&self, f: &mut dyn FnMut(&ObjectId, &ComponentInstance)) {
for (output_id, active_surface) in &self.lock_surfaces {
if let Some(component) = active_surface.component() {
f(output_id, component.component_instance());
}
}
}
pub const fn component_name(&self) -> &ComponentDefinition {
&self.component_definition
}
pub fn count_lock_surfaces(&self) -> usize {
self.lock_surfaces
.iter()
.filter(|(_, s)| s.component().is_some())
.count()
}
}

View file

@ -0,0 +1,11 @@
use crate::errors::Result;
use wayland_client::backend::ObjectId;
use super::state::ActiveLockSurface;
pub(super) fn render_frames(lock_surfaces: &[(ObjectId, ActiveLockSurface)]) -> Result<()> {
for (_, surface) in lock_surfaces {
surface.render_frame_if_dirty()?;
}
Ok(())
}

View file

@ -0,0 +1,248 @@
use crate::errors::Result;
use crate::rendering::femtovg::main_window::FemtoVGWindow;
use crate::rendering::femtovg::renderable_window::RenderableWindow;
use crate::rendering::slint_integration::platform::CustomSlintPlatform;
use crate::wayland::session_lock::lock_surface::LockSurface;
use crate::wayland::surfaces::component_state::ComponentState;
use crate::wayland::surfaces::display_metrics::DisplayMetrics;
use layer_shika_domain::surface_dimensions::SurfaceDimensions;
use layer_shika_domain::value_objects::output_handle::OutputHandle;
use layer_shika_domain::value_objects::output_info::OutputInfo;
use log::info;
use slint::{
LogicalPosition, LogicalSize, WindowSize,
platform::{WindowAdapter, WindowEvent},
};
use slint_interpreter::{CompilationResult, ComponentDefinition};
use std::rc::Rc;
use super::callbacks::LockCallback;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LockScalingMode {
FractionalWithViewport,
FractionalOnly,
Integer,
}
pub struct LockSurfaceOutputContext {
pub output_handle: OutputHandle,
pub output_info: Option<OutputInfo>,
pub primary_handle: Option<OutputHandle>,
pub active_handle: Option<OutputHandle>,
}
pub struct LockConfigureContext {
pub scale_factor: f32,
pub component_definition: ComponentDefinition,
pub compilation_result: Option<Rc<CompilationResult>>,
pub platform: Rc<CustomSlintPlatform>,
pub callbacks: Vec<LockCallback>,
pub component_name: String,
pub output_handle: OutputHandle,
pub output_info: Option<OutputInfo>,
pub primary_handle: Option<OutputHandle>,
pub active_handle: Option<OutputHandle>,
}
pub struct ActiveLockSurface {
surface: LockSurface,
window: Rc<FemtoVGWindow>,
component: Option<ComponentState>,
scale_factor: f32,
has_fractional_scale: bool,
}
impl ActiveLockSurface {
pub fn new(surface: LockSurface, window: Rc<FemtoVGWindow>) -> Self {
Self {
has_fractional_scale: surface.fractional_scale().is_some(),
surface,
window,
component: None,
scale_factor: 1.0,
}
}
pub fn handle_configure(
&mut self,
serial: u32,
width: u32,
height: u32,
context: &LockConfigureContext,
) -> Result<()> {
self.surface.handle_configure(serial, width, height);
self.scale_factor = context.scale_factor;
let dimensions = match SurfaceDimensions::calculate(width, height, context.scale_factor) {
Ok(dimensions) => dimensions,
Err(err) => {
info!("Failed to calculate lock surface dimensions: {err}");
return Ok(());
}
};
let scaling_mode = self.scaling_mode();
info!(
"Lock surface dimensions: logical {}x{}, physical {}x{}, scale {}, mode {:?}",
dimensions.logical_width(),
dimensions.logical_height(),
dimensions.physical_width(),
dimensions.physical_height(),
context.scale_factor,
scaling_mode
);
self.configure_window(&dimensions, scaling_mode, context.scale_factor);
self.configure_surface(&dimensions, scaling_mode);
if self.component.is_none() {
context.platform.add_window(Rc::clone(&self.window));
let component = ComponentState::new(
context.component_definition.clone(),
context.compilation_result.clone(),
&self.window,
)?;
self.window
.window()
.dispatch_event(WindowEvent::WindowActiveChanged(true));
for callback in &context.callbacks {
if callback.should_apply(
&context.component_name,
context.output_handle,
context.output_info.as_ref(),
context.primary_handle,
context.active_handle,
) {
if let Err(err) = callback.apply_to(component.component_instance()) {
info!(
"Failed to register lock callback '{}': {err}",
callback.name()
);
} else {
info!("Registered lock callback '{}'", callback.name());
}
} else {
info!(
"Skipping callback '{}' due to selector filter (output {:?})",
callback.name(),
context.output_handle
);
}
}
self.component = Some(component);
}
RenderableWindow::request_redraw(self.window.as_ref());
Ok(())
}
pub fn render_frame_if_dirty(&self) -> Result<()> {
self.window.render_frame_if_dirty()
}
pub fn handle_fractional_scale(&mut self, scale_120ths: u32) {
let scale_factor = DisplayMetrics::scale_factor_from_120ths(scale_120ths);
self.scale_factor = scale_factor;
if self.surface.width() == 0 || self.surface.height() == 0 {
return;
}
let Ok(dimensions) =
SurfaceDimensions::calculate(self.surface.width(), self.surface.height(), scale_factor)
else {
return;
};
let scaling_mode = self.scaling_mode();
self.configure_window(&dimensions, scaling_mode, scale_factor);
self.configure_surface(&dimensions, scaling_mode);
RenderableWindow::request_redraw(self.window.as_ref());
}
pub fn apply_callback(&self, callback: &LockCallback) {
if let Some(component) = self.component.as_ref() {
if let Err(err) = callback.apply_to(component.component_instance()) {
info!(
"Failed to register lock callback '{}': {err}",
callback.name()
);
}
}
}
fn scaling_mode(&self) -> LockScalingMode {
if self.surface.has_fractional_scale() && self.surface.has_viewport() {
LockScalingMode::FractionalWithViewport
} else if self.surface.has_fractional_scale() {
LockScalingMode::FractionalOnly
} else {
LockScalingMode::Integer
}
}
#[allow(clippy::cast_precision_loss)]
fn configure_window(
&self,
dimensions: &SurfaceDimensions,
mode: LockScalingMode,
scale_factor: f32,
) {
match mode {
LockScalingMode::FractionalWithViewport | LockScalingMode::FractionalOnly => {
RenderableWindow::set_scale_factor(self.window.as_ref(), scale_factor);
self.window.set_size(WindowSize::Logical(LogicalSize::new(
dimensions.logical_width() as f32,
dimensions.logical_height() as f32,
)));
}
LockScalingMode::Integer => {
RenderableWindow::set_scale_factor(self.window.as_ref(), scale_factor);
self.window
.set_size(WindowSize::Physical(slint::PhysicalSize::new(
dimensions.physical_width(),
dimensions.physical_height(),
)));
}
}
}
fn configure_surface(&self, dimensions: &SurfaceDimensions, mode: LockScalingMode) {
match mode {
LockScalingMode::FractionalWithViewport => {
self.surface.configure_fractional_viewport(
dimensions.logical_width(),
dimensions.logical_height(),
);
}
LockScalingMode::FractionalOnly | LockScalingMode::Integer => {
self.surface
.configure_buffer_scale(dimensions.buffer_scale());
}
}
}
#[allow(clippy::cast_possible_truncation)]
pub fn to_logical_position(&self, surface_x: f64, surface_y: f64) -> LogicalPosition {
if self.has_fractional_scale {
let x = surface_x as f32;
let y = surface_y as f32;
LogicalPosition::new(x, y)
} else {
let x = (surface_x / f64::from(self.scale_factor)) as f32;
let y = (surface_y / f64::from(self.scale_factor)) as f32;
LogicalPosition::new(x, y)
}
}
pub fn dispatch_event(&self, event: WindowEvent) {
self.window.window().dispatch_event(event);
}
pub fn window_rc(&self) -> Rc<FemtoVGWindow> {
Rc::clone(&self.window)
}
pub const fn surface(&self) -> &LockSurface {
&self.surface
}
pub const fn component(&self) -> Option<&ComponentState> {
self.component.as_ref()
}
}

View file

@ -1,3 +1,5 @@
pub mod lock_context;
pub mod lock_manager;
pub mod lock_surface;
pub mod manager;
pub use manager::{LockCallback, LockSurfaceOutputContext, OutputFilter, SessionLockManager};

View file

@ -4,7 +4,7 @@ use crate::wayland::{
managed_proxies::{ManagedWlKeyboard, ManagedWlPointer},
ops::WaylandSystemOps,
outputs::{OutputManager, OutputManagerContext},
session_lock::lock_manager::OutputFilter,
session_lock::OutputFilter,
surfaces::layer_surface::{SurfaceCtx, SurfaceSetupParams},
surfaces::popup_manager::{PopupContext, PopupManager},
surfaces::{

View file

@ -8,7 +8,7 @@ use crate::wayland::globals::context::GlobalContext;
use crate::wayland::managed_proxies::{ManagedWlKeyboard, ManagedWlPointer};
use crate::wayland::outputs::{OutputManager, OutputMapping};
use crate::wayland::session_lock::lock_context::SessionLockContext;
use crate::wayland::session_lock::lock_manager::{LockCallback, OutputFilter, SessionLockManager};
use crate::wayland::session_lock::{LockCallback, OutputFilter, SessionLockManager};
use layer_shika_domain::entities::output_registry::OutputRegistry;
use layer_shika_domain::value_objects::handle::SurfaceHandle;
use layer_shika_domain::value_objects::lock_config::LockConfig;