layer_shika_adapters/wayland/session_lock/manager/
state.rs1use super::callbacks::{LockCallbackContext, LockCallbackExt, LockPropertyOperationExt};
2use crate::errors::Result;
3use crate::rendering::femtovg::main_window::FemtoVGWindow;
4use crate::rendering::femtovg::renderable_window::{FractionalScaleConfig, RenderableWindow};
5use crate::rendering::slint_integration::platform::CustomSlintPlatform;
6use crate::wayland::session_lock::lock_surface::LockSurface;
7use crate::wayland::surfaces::component_state::ComponentState;
8use crate::wayland::surfaces::display_metrics::DisplayMetrics;
9use layer_shika_domain::surface_dimensions::SurfaceDimensions;
10use layer_shika_domain::value_objects::output_handle::OutputHandle;
11use layer_shika_domain::value_objects::output_info::OutputInfo;
12use log::info;
13use slint::{
14 LogicalPosition, LogicalSize, WindowSize,
15 platform::{WindowAdapter, WindowEvent},
16};
17use slint_interpreter::{CompilationResult, ComponentDefinition};
18use std::rc::Rc;
19
20use super::callbacks::LockCallback;
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub enum LockScalingMode {
24 FractionalWithViewport,
25 FractionalOnly,
26 Integer,
27}
28
29pub struct LockSurfaceOutputContext {
30 pub output_handle: OutputHandle,
31 pub output_info: Option<OutputInfo>,
32 pub primary_handle: Option<OutputHandle>,
33 pub active_handle: Option<OutputHandle>,
34}
35
36pub struct LockConfigureContext {
37 pub scale_factor: f32,
38 pub component_definition: ComponentDefinition,
39 pub compilation_result: Option<Rc<CompilationResult>>,
40 pub platform: Rc<CustomSlintPlatform>,
41 pub callbacks: Vec<LockCallback>,
42 pub property_operations: Vec<super::callbacks::LockPropertyOperation>,
43 pub component_name: String,
44 pub output_handle: OutputHandle,
45 pub output_info: Option<OutputInfo>,
46 pub primary_handle: Option<OutputHandle>,
47 pub active_handle: Option<OutputHandle>,
48}
49
50pub struct ActiveLockSurface {
51 surface: LockSurface,
52 window: Rc<FemtoVGWindow>,
53 component: Option<ComponentState>,
54 scale_factor: f32,
55 has_fractional_scale: bool,
56 component_name: Option<String>,
57 pub(super) output_handle: Option<OutputHandle>,
58 pub(super) output_info: Option<OutputInfo>,
59 pub(super) primary_handle: Option<OutputHandle>,
60 pub(super) active_handle: Option<OutputHandle>,
61 pending_component_initialization: bool,
62}
63
64impl ActiveLockSurface {
65 pub fn new(surface: LockSurface, window: Rc<FemtoVGWindow>) -> Self {
66 Self {
67 has_fractional_scale: surface.fractional_scale().is_some(),
68 surface,
69 window,
70 component: None,
71 scale_factor: 1.0,
72 output_handle: None,
73 component_name: None,
74 output_info: None,
75 primary_handle: None,
76 active_handle: None,
77 pending_component_initialization: false,
78 }
79 }
80
81 pub fn handle_surface_configured(
82 &mut self,
83 serial: u32,
84 width: u32,
85 height: u32,
86 context: &LockConfigureContext,
87 ) {
88 self.surface.handle_configure(serial, width, height);
89 self.scale_factor = context.scale_factor;
90 self.output_handle = Some(context.output_handle);
91 self.component_name = Some(context.component_name.clone());
92 self.output_info.clone_from(&context.output_info);
93 self.primary_handle = context.primary_handle;
94 self.active_handle = context.active_handle;
95 let dimensions = match SurfaceDimensions::calculate(width, height, context.scale_factor) {
96 Ok(dimensions) => dimensions,
97 Err(err) => {
98 info!("Failed to calculate lock surface dimensions: {err}");
99 return;
100 }
101 };
102 let scaling_mode = self.scaling_mode();
103 info!(
104 "Lock surface dimensions: logical {}x{}, physical {}x{}, scale {}, mode {:?}",
105 dimensions.logical_width(),
106 dimensions.logical_height(),
107 dimensions.physical_width(),
108 dimensions.physical_height(),
109 context.scale_factor,
110 scaling_mode
111 );
112 self.configure_window(&dimensions, scaling_mode, context.scale_factor);
113 self.configure_surface(&dimensions, scaling_mode);
114
115 if self.component.is_none() {
116 self.pending_component_initialization = true;
117 }
118
119 RenderableWindow::request_redraw(self.window.as_ref());
120 }
121
122 pub fn handle_configure(
123 &mut self,
124 serial: u32,
125 width: u32,
126 height: u32,
127 context: &LockConfigureContext,
128 ) -> Result<()> {
129 self.handle_surface_configured(serial, width, height, context);
130
131 if self.pending_component_initialization {
132 self.initialize_component(context)?;
133 }
134
135 Ok(())
136 }
137
138 fn initialize_component(&mut self, context: &LockConfigureContext) -> Result<()> {
139 if self.component.is_some() {
140 return Ok(());
141 }
142
143 context.platform.add_window(Rc::clone(&self.window));
144 let component = ComponentState::new(
145 context.component_definition.clone(),
146 context.compilation_result.clone(),
147 &self.window,
148 )?;
149 self.window
150 .window()
151 .dispatch_event(WindowEvent::WindowActiveChanged(true));
152
153 let callback_context = LockCallbackContext::new(
154 context.component_name.clone(),
155 context.output_handle,
156 context.output_info.clone(),
157 context.primary_handle,
158 context.active_handle,
159 );
160
161 for callback in &context.callbacks {
162 if let Err(err) =
163 callback.apply_with_context(component.component_instance(), &callback_context)
164 {
165 info!(
166 "Failed to register lock callback '{}': {err}",
167 callback.name()
168 );
169 } else if callback.should_apply(&callback_context) {
170 info!("Registered lock callback '{}'", callback.name());
171 } else {
172 info!(
173 "Skipping callback '{}' due to selector filter (output {:?})",
174 callback.name(),
175 context.output_handle
176 );
177 }
178 }
179
180 for property_op in &context.property_operations {
181 if property_op.should_apply(&callback_context) {
182 if let Err(err) = property_op.apply_to_component(component.component_instance()) {
183 info!(
184 "Failed to set lock property '{}': {err}",
185 property_op.name()
186 );
187 } else {
188 info!(
189 "Set lock property '{}' on output {:?}",
190 property_op.name(),
191 context.output_handle
192 );
193 }
194 } else {
195 info!(
196 "Skipping property '{}' due to selector filter (output {:?}, primary={:?})",
197 property_op.name(),
198 context.output_handle,
199 context.primary_handle
200 );
201 }
202 }
203
204 self.component = Some(component);
205 self.pending_component_initialization = false;
206
207 Ok(())
208 }
209
210 pub fn render_frame_if_dirty(&self) -> Result<()> {
211 self.window.render_frame_if_dirty()
212 }
213
214 pub fn handle_fractional_scale(&mut self, scale_120ths: u32) {
215 let scale_factor = DisplayMetrics::scale_factor_from_120ths(scale_120ths);
216 self.scale_factor = scale_factor;
217 if self.surface.width() == 0 || self.surface.height() == 0 {
218 return;
219 }
220 let Ok(dimensions) =
221 SurfaceDimensions::calculate(self.surface.width(), self.surface.height(), scale_factor)
222 else {
223 return;
224 };
225 let scaling_mode = self.scaling_mode();
226 self.configure_window(&dimensions, scaling_mode, scale_factor);
227 self.configure_surface(&dimensions, scaling_mode);
228 RenderableWindow::request_redraw(self.window.as_ref());
229 }
230
231 pub fn apply_callback(&self, callback: &LockCallback) {
232 let Some(component) = self.component.as_ref() else {
233 return;
234 };
235
236 let Some(component_name) = &self.component_name else {
237 return;
238 };
239
240 let Some(output_handle) = self.output_handle else {
241 return;
242 };
243
244 let callback_context = LockCallbackContext::new(
245 component_name.clone(),
246 output_handle,
247 self.output_info.clone(),
248 self.primary_handle,
249 self.active_handle,
250 );
251
252 if let Err(err) =
253 callback.apply_with_context(component.component_instance(), &callback_context)
254 {
255 info!(
256 "Failed to register lock callback '{}': {err}",
257 callback.name()
258 );
259 }
260 }
261
262 pub fn apply_property_operation(&self, property_op: &super::callbacks::LockPropertyOperation) {
263 let Some(component) = self.component.as_ref() else {
264 return;
265 };
266
267 let Some(component_name) = &self.component_name else {
268 return;
269 };
270
271 let Some(output_handle) = self.output_handle else {
272 return;
273 };
274
275 let callback_context = LockCallbackContext::new(
276 component_name.clone(),
277 output_handle,
278 self.output_info.clone(),
279 self.primary_handle,
280 self.active_handle,
281 );
282
283 if let Err(err) =
284 property_op.apply_with_context(component.component_instance(), &callback_context)
285 {
286 info!(
287 "Failed to set lock property '{}': {err}",
288 property_op.name()
289 );
290 }
291 }
292
293 fn scaling_mode(&self) -> LockScalingMode {
294 if self.surface.has_fractional_scale() && self.surface.has_viewport() {
295 LockScalingMode::FractionalWithViewport
296 } else if self.surface.has_fractional_scale() {
297 LockScalingMode::FractionalOnly
298 } else {
299 LockScalingMode::Integer
300 }
301 }
302
303 #[allow(clippy::cast_precision_loss)]
304 fn configure_window(
305 &self,
306 dimensions: &SurfaceDimensions,
307 mode: LockScalingMode,
308 scale_factor: f32,
309 ) {
310 match mode {
311 LockScalingMode::FractionalWithViewport => {
312 let config = FractionalScaleConfig::new(
313 dimensions.logical_width() as f32,
314 dimensions.logical_height() as f32,
315 scale_factor,
316 );
317 info!(
318 "Lock FractionalWithViewport: render scale {} (from {}), physical {}x{}",
319 config.render_scale,
320 scale_factor,
321 config.render_physical_size.width,
322 config.render_physical_size.height
323 );
324 config.apply_to(self.window.as_ref());
325 }
326 LockScalingMode::FractionalOnly => {
327 RenderableWindow::set_scale_factor(self.window.as_ref(), scale_factor);
328 self.window.set_size(WindowSize::Logical(LogicalSize::new(
329 dimensions.logical_width() as f32,
330 dimensions.logical_height() as f32,
331 )));
332 }
333 LockScalingMode::Integer => {
334 RenderableWindow::set_scale_factor(self.window.as_ref(), scale_factor);
335 self.window
336 .set_size(WindowSize::Physical(slint::PhysicalSize::new(
337 dimensions.physical_width(),
338 dimensions.physical_height(),
339 )));
340 }
341 }
342 }
343
344 fn configure_surface(&self, dimensions: &SurfaceDimensions, mode: LockScalingMode) {
345 match mode {
346 LockScalingMode::FractionalWithViewport => {
347 self.surface.configure_fractional_viewport(
348 dimensions.logical_width(),
349 dimensions.logical_height(),
350 );
351 }
352 LockScalingMode::FractionalOnly | LockScalingMode::Integer => {
353 self.surface
354 .configure_buffer_scale(dimensions.buffer_scale());
355 }
356 }
357 }
358
359 #[allow(clippy::cast_possible_truncation)]
360 pub fn to_logical_position(&self, surface_x: f64, surface_y: f64) -> LogicalPosition {
361 if self.has_fractional_scale {
362 let x = surface_x as f32;
363 let y = surface_y as f32;
364 LogicalPosition::new(x, y)
365 } else {
366 let x = (surface_x / f64::from(self.scale_factor)) as f32;
367 let y = (surface_y / f64::from(self.scale_factor)) as f32;
368 LogicalPosition::new(x, y)
369 }
370 }
371
372 pub fn dispatch_event(&self, event: WindowEvent) {
373 self.window.window().dispatch_event(event);
374 }
375
376 pub const fn surface(&self) -> &LockSurface {
377 &self.surface
378 }
379
380 pub const fn component(&self) -> Option<&ComponentState> {
381 self.component.as_ref()
382 }
383
384 pub const fn has_pending_initialization(&self) -> bool {
385 self.pending_component_initialization
386 }
387
388 pub fn initialize_pending_component(&mut self, context: &LockConfigureContext) -> Result<()> {
389 if self.pending_component_initialization {
390 self.initialize_component(context)?;
391 }
392 Ok(())
393 }
394}