mirror of
https://codeberg.org/waydeer/layer-shika.git
synced 2026-01-01 17:35:55 +00:00
feat: add session lock support
This commit is contained in:
parent
f4490ba9a1
commit
91455060c7
23 changed files with 1893 additions and 135 deletions
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<h1 style="font-size: 5em;">🦌</h1>
|
<h1 style="font-size: 5em;">🦌</h1>
|
||||||
<p><i>"A cute layer of abstraction where Slint UIs grow antlers and become cute Wayland shells."</i></p>
|
<p><i>"A cute library where Slint UIs grow antlers and become cute Wayland shells."</i></p>
|
||||||
<p><b><a href="https://codeberg.org/waydeer/layer-shika">Main repo</a> | <a href="https://git.dren.dog/waydeer/layer-shika">Mirror</a> | <a href="https://github.com/waydeerwm/layer-shika">Temp mirror (github)</a></b></p>
|
<p><b><a href="https://codeberg.org/waydeer/layer-shika">Main repo</a> | <a href="https://git.dren.dog/waydeer/layer-shika">Mirror</a> | <a href="https://github.com/waydeerwm/layer-shika">Temp mirror (github)</a></b></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -19,17 +19,18 @@ Oh deer! 🦌 You've stumbled upon `layer-shika`, a Rust library providing Wayla
|
||||||
- **Slint Integration**: Runtime `.slint` file compilation via slint-interpreter or compile-time code generation. Support via pre-compiled is planned.
|
- **Slint Integration**: Runtime `.slint` file compilation via slint-interpreter or compile-time code generation. Support via pre-compiled is planned.
|
||||||
- **Multi-Surface Support**: Create multiple independent layer shell windows, each with its own configuration and lifecycle
|
- **Multi-Surface Support**: Create multiple independent layer shell windows, each with its own configuration and lifecycle
|
||||||
- **Flexible Configuration**: Both fluent builder API and declarative configuration support
|
- **Flexible Configuration**: Both fluent builder API and declarative configuration support
|
||||||
- **Comprehensive Popup System**: Full xdg-popup protocol implementation with multiple positioning modes, grab support, and content-based sizing (rework in progress)
|
- **Comprehensive Popup System**: Full `xdg-popup` protocol implementation with multiple positioning modes, grab support, and content-based sizing (rework in progress)
|
||||||
- **Multi-Output Support**: Per-monitor component instances with flexible output policies (primary only, all outputs, specific outputs)
|
- **Multi-Output Support**: Per-monitor component instances with flexible output policies (primary only, all outputs, specific outputs)
|
||||||
- **Event Loop Integration**: Custom event sources (timers, channels, file descriptors) via calloop integration
|
- **Event Loop Integration**: Custom event sources (timers, channels, file descriptors) via calloop integration
|
||||||
- **Clean-like Architecture**: Organized as a Cargo workspace with clear separation of concerns (domain, adapters, composition)
|
- **Clean-like Architecture**: Organized as a Cargo workspace with clear separation of concerns (domain, adapters, composition)
|
||||||
- **HiDPI Support**: Configurable scale factors for high-resolution displays
|
- **HiDPI Support**: Configurable scale factors for high-resolution displays
|
||||||
|
- **Session Lock Support**: `ext-session-lock-v1` protocol integration for lock screens
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
layer-shika is organized as a **Cargo workspace** with three crates:
|
layer-shika is organized as a **Cargo workspace** with three crates:
|
||||||
|
|
||||||
- **domain** ([crates/domain/](crates/domain/)): Core domain models, value objects, and port trait definitions. No framework dependencies.
|
- **domain** ([crates/domain/](crates/domain/)): Core domain models, value objects, and port trait definitions. No external dependencies.
|
||||||
- **adapters** ([crates/adapters/](crates/adapters/)): Concrete implementations for Wayland (smithay-client-toolkit), rendering (femtovg + EGL), and platform integration.
|
- **adapters** ([crates/adapters/](crates/adapters/)): Concrete implementations for Wayland (smithay-client-toolkit), rendering (femtovg + EGL), and platform integration.
|
||||||
- **composition** ([crates/composition/](crates/composition/)): Public API layer providing Shell-based API, builder patterns, and system integration.
|
- **composition** ([crates/composition/](crates/composition/)): Public API layer providing Shell-based API, builder patterns, and system integration.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,11 @@ use wayland_client::{
|
||||||
wl_surface::WlSurface,
|
wl_surface::WlSurface,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use wayland_protocols::ext::session_lock::v1::client::{
|
||||||
|
ext_session_lock_manager_v1::ExtSessionLockManagerV1,
|
||||||
|
ext_session_lock_surface_v1::{self, ExtSessionLockSurfaceV1},
|
||||||
|
ext_session_lock_v1::{self, ExtSessionLockV1},
|
||||||
|
};
|
||||||
use wayland_protocols::wp::fractional_scale::v1::client::{
|
use wayland_protocols::wp::fractional_scale::v1::client::{
|
||||||
wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1,
|
wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1,
|
||||||
wp_fractional_scale_v1::{self, WpFractionalScaleV1},
|
wp_fractional_scale_v1::{self, WpFractionalScaleV1},
|
||||||
|
|
@ -188,6 +193,137 @@ impl Dispatch<WlOutput, ()> for AppState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_pointer_enter_event(
|
||||||
|
state: &mut AppState,
|
||||||
|
serial: u32,
|
||||||
|
surface: &WlSurface,
|
||||||
|
surface_x: f64,
|
||||||
|
surface_y: f64,
|
||||||
|
) {
|
||||||
|
let surface_id = surface.id();
|
||||||
|
|
||||||
|
if let Some(manager) = state.lock_manager_mut() {
|
||||||
|
if manager.handle_pointer_enter(serial, surface, surface_x, surface_y) {
|
||||||
|
state.set_active_surface_key(None);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(key) = state.get_key_by_surface(&surface_id).cloned() {
|
||||||
|
if let Some(layer_surface) = state.get_surface_by_key_mut(&key) {
|
||||||
|
layer_surface.handle_pointer_enter(serial, surface, surface_x, surface_y);
|
||||||
|
}
|
||||||
|
state.set_active_surface_key(Some(key));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(key) = state.get_key_by_popup(&surface_id).cloned() {
|
||||||
|
if let Some(layer_surface) = state.get_surface_by_key_mut(&key) {
|
||||||
|
layer_surface.handle_pointer_enter(serial, surface, surface_x, surface_y);
|
||||||
|
}
|
||||||
|
state.set_active_surface_key(Some(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_pointer_motion_event(state: &mut AppState, surface_x: f64, surface_y: f64) {
|
||||||
|
if let Some(manager) = state.lock_manager_mut() {
|
||||||
|
if manager.handle_pointer_motion(surface_x, surface_y) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(surface) = state.active_surface_mut() {
|
||||||
|
surface.handle_pointer_motion(surface_x, surface_y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_pointer_leave_event(state: &mut AppState) {
|
||||||
|
if let Some(manager) = state.lock_manager_mut() {
|
||||||
|
if manager.handle_pointer_leave() {
|
||||||
|
state.set_active_surface_key(None);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(surface) = state.active_surface_mut() {
|
||||||
|
surface.handle_pointer_leave();
|
||||||
|
}
|
||||||
|
state.set_active_surface_key(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_pointer_button_event(
|
||||||
|
state: &mut AppState,
|
||||||
|
serial: u32,
|
||||||
|
button: u32,
|
||||||
|
button_state: WEnum<wl_pointer::ButtonState>,
|
||||||
|
) {
|
||||||
|
if let Some(manager) = state.lock_manager_mut() {
|
||||||
|
if manager.handle_pointer_button(serial, button, button_state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(surface) = state.active_surface_mut() {
|
||||||
|
surface.handle_pointer_button(serial, button, button_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_pointer_axis_source_event(state: &mut AppState, axis_source: wl_pointer::AxisSource) {
|
||||||
|
if let Some(manager) = state.lock_manager_mut() {
|
||||||
|
if manager.handle_axis_source(axis_source) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(surface) = state.active_surface_mut() {
|
||||||
|
surface.handle_axis_source(axis_source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_pointer_axis_event(state: &mut AppState, time: u32, axis: wl_pointer::Axis, value: f64) {
|
||||||
|
if let Some(manager) = state.lock_manager_mut() {
|
||||||
|
if manager.handle_axis(axis, value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(surface) = state.active_surface_mut() {
|
||||||
|
surface.handle_axis(time, axis, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_pointer_axis_discrete_event(state: &mut AppState, axis: wl_pointer::Axis, discrete: i32) {
|
||||||
|
if let Some(manager) = state.lock_manager_mut() {
|
||||||
|
if manager.handle_axis_discrete(axis, discrete) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(surface) = state.active_surface_mut() {
|
||||||
|
surface.handle_axis_discrete(axis, discrete);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_pointer_axis_stop_event(state: &mut AppState, time: u32, axis: wl_pointer::Axis) {
|
||||||
|
if let Some(manager) = state.lock_manager_mut() {
|
||||||
|
if manager.handle_axis_stop(axis) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(surface) = state.active_surface_mut() {
|
||||||
|
surface.handle_axis_stop(time, axis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_pointer_frame_event(state: &mut AppState) {
|
||||||
|
if let Some(manager) = state.lock_manager_mut() {
|
||||||
|
if manager.handle_pointer_frame() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(surface) = state.active_surface_mut() {
|
||||||
|
surface.handle_pointer_frame();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Dispatch<WlPointer, ()> for AppState {
|
impl Dispatch<WlPointer, ()> for AppState {
|
||||||
fn event(
|
fn event(
|
||||||
state: &mut Self,
|
state: &mut Self,
|
||||||
|
|
@ -203,76 +339,36 @@ impl Dispatch<WlPointer, ()> for AppState {
|
||||||
surface,
|
surface,
|
||||||
surface_x,
|
surface_x,
|
||||||
surface_y,
|
surface_y,
|
||||||
} => {
|
} => handle_pointer_enter_event(state, serial, &surface, surface_x, surface_y),
|
||||||
let surface_id = surface.id();
|
|
||||||
|
|
||||||
if let Some(key) = state.get_key_by_surface(&surface_id).cloned() {
|
|
||||||
if let Some(layer_surface) = state.get_surface_by_key_mut(&key) {
|
|
||||||
layer_surface.handle_pointer_enter(serial, &surface, surface_x, surface_y);
|
|
||||||
}
|
|
||||||
state.set_active_surface_key(Some(key));
|
|
||||||
} else if let Some(key) = state.get_key_by_popup(&surface_id).cloned() {
|
|
||||||
if let Some(layer_surface) = state.get_surface_by_key_mut(&key) {
|
|
||||||
layer_surface.handle_pointer_enter(serial, &surface, surface_x, surface_y);
|
|
||||||
}
|
|
||||||
state.set_active_surface_key(Some(key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wl_pointer::Event::Motion {
|
wl_pointer::Event::Motion {
|
||||||
surface_x,
|
surface_x,
|
||||||
surface_y,
|
surface_y,
|
||||||
..
|
..
|
||||||
} => {
|
} => handle_pointer_motion_event(state, surface_x, surface_y),
|
||||||
if let Some(surface) = state.active_surface_mut() {
|
wl_pointer::Event::Leave { .. } => handle_pointer_leave_event(state),
|
||||||
surface.handle_pointer_motion(surface_x, surface_y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wl_pointer::Event::Leave { .. } => {
|
|
||||||
if let Some(surface) = state.active_surface_mut() {
|
|
||||||
surface.handle_pointer_leave();
|
|
||||||
}
|
|
||||||
state.set_active_surface_key(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
wl_pointer::Event::Button {
|
wl_pointer::Event::Button {
|
||||||
serial,
|
serial,
|
||||||
button,
|
button,
|
||||||
state: button_state,
|
state: button_state,
|
||||||
..
|
..
|
||||||
} => {
|
} => handle_pointer_button_event(state, serial, button, button_state),
|
||||||
if let Some(surface) = state.active_surface_mut() {
|
wl_pointer::Event::AxisSource {
|
||||||
surface.handle_pointer_button(serial, button, button_state);
|
axis_source: WEnum::Value(axis_source),
|
||||||
}
|
} => handle_pointer_axis_source_event(state, axis_source),
|
||||||
}
|
wl_pointer::Event::Axis {
|
||||||
wl_pointer::Event::AxisSource { axis_source } => {
|
time,
|
||||||
if let (Some(surface), WEnum::Value(axis_source)) =
|
axis: WEnum::Value(axis),
|
||||||
(state.active_surface_mut(), axis_source)
|
value,
|
||||||
{
|
} => handle_pointer_axis_event(state, time, axis, value),
|
||||||
surface.handle_axis_source(axis_source);
|
wl_pointer::Event::AxisDiscrete {
|
||||||
}
|
axis: WEnum::Value(axis),
|
||||||
}
|
discrete,
|
||||||
wl_pointer::Event::Axis { time, axis, value } => {
|
} => handle_pointer_axis_discrete_event(state, axis, discrete),
|
||||||
if let (Some(surface), WEnum::Value(axis)) = (state.active_surface_mut(), axis) {
|
wl_pointer::Event::AxisStop {
|
||||||
surface.handle_axis(time, axis, value);
|
time,
|
||||||
}
|
axis: WEnum::Value(axis),
|
||||||
}
|
} => handle_pointer_axis_stop_event(state, time, axis),
|
||||||
wl_pointer::Event::AxisDiscrete { axis, discrete } => {
|
wl_pointer::Event::Frame => handle_pointer_frame_event(state),
|
||||||
if let (Some(surface), WEnum::Value(axis)) = (state.active_surface_mut(), axis) {
|
|
||||||
surface.handle_axis_discrete(axis, discrete);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wl_pointer::Event::AxisStop { time, axis } => {
|
|
||||||
if let (Some(surface), WEnum::Value(axis)) = (state.active_surface_mut(), axis) {
|
|
||||||
surface.handle_axis_stop(time, axis);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wl_pointer::Event::Frame => {
|
|
||||||
if let Some(surface) = state.active_surface_mut() {
|
|
||||||
surface.handle_pointer_frame();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -346,6 +442,10 @@ impl Dispatch<WpFractionalScaleV1, ()> for AppState {
|
||||||
for surface in state.all_outputs_mut() {
|
for surface in state.all_outputs_mut() {
|
||||||
surface.handle_fractional_scale(proxy, scale);
|
surface.handle_fractional_scale(proxy, scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(manager) = state.lock_manager_mut() {
|
||||||
|
manager.handle_fractional_scale(&proxy.id(), scale);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -471,6 +571,7 @@ impl Dispatch<WlRegistry, GlobalListContents> for AppState {
|
||||||
|
|
||||||
let output = registry.bind::<WlOutput, _, _>(name, 4.min(version), qhandle, ());
|
let output = registry.bind::<WlOutput, _, _>(name, 4.min(version), qhandle, ());
|
||||||
let output_id = output.id();
|
let output_id = output.id();
|
||||||
|
let output_for_lock = output.clone();
|
||||||
|
|
||||||
if let Some(manager) = state.output_manager() {
|
if let Some(manager) = state.output_manager() {
|
||||||
let mut manager_ref = manager.borrow_mut();
|
let mut manager_ref = manager.borrow_mut();
|
||||||
|
|
@ -478,6 +579,11 @@ impl Dispatch<WlRegistry, GlobalListContents> for AppState {
|
||||||
info!("Registered hot-plugged output with handle {handle:?}");
|
info!("Registered hot-plugged output with handle {handle:?}");
|
||||||
|
|
||||||
state.register_registry_name(name, output_id);
|
state.register_registry_name(name, output_id);
|
||||||
|
if let Err(err) =
|
||||||
|
state.handle_output_added_for_lock(&output_for_lock, qhandle)
|
||||||
|
{
|
||||||
|
info!("Failed to add session lock surface for output: {err}");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
info!("No output manager available yet (startup initialization)");
|
info!("No output manager available yet (startup initialization)");
|
||||||
}
|
}
|
||||||
|
|
@ -489,6 +595,8 @@ impl Dispatch<WlRegistry, GlobalListContents> for AppState {
|
||||||
if let Some(output_id) = state.unregister_registry_name(name) {
|
if let Some(output_id) = state.unregister_registry_name(name) {
|
||||||
info!("Output with registry name {name} removed, cleaning up...");
|
info!("Output with registry name {name} removed, cleaning up...");
|
||||||
|
|
||||||
|
state.handle_output_removed_for_lock(&output_id);
|
||||||
|
|
||||||
if let Some(manager) = state.output_manager() {
|
if let Some(manager) = state.output_manager() {
|
||||||
let mut manager_ref = manager.borrow_mut();
|
let mut manager_ref = manager.borrow_mut();
|
||||||
manager_ref.remove_output(&output_id, state);
|
manager_ref.remove_output(&output_id, state);
|
||||||
|
|
@ -500,6 +608,58 @@ impl Dispatch<WlRegistry, GlobalListContents> for AppState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Dispatch<ExtSessionLockV1, ()> for AppState {
|
||||||
|
fn event(
|
||||||
|
state: &mut Self,
|
||||||
|
_proxy: &ExtSessionLockV1,
|
||||||
|
event: ext_session_lock_v1::Event,
|
||||||
|
_data: &(),
|
||||||
|
_conn: &Connection,
|
||||||
|
_queue_handle: &QueueHandle<Self>,
|
||||||
|
) {
|
||||||
|
match event {
|
||||||
|
ext_session_lock_v1::Event::Locked => {
|
||||||
|
if let Some(manager) = state.lock_manager_mut() {
|
||||||
|
manager.handle_locked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ext_session_lock_v1::Event::Finished => {
|
||||||
|
if let Some(manager) = state.lock_manager_mut() {
|
||||||
|
manager.handle_finished();
|
||||||
|
}
|
||||||
|
state.clear_lock_manager();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dispatch<ExtSessionLockSurfaceV1, ()> for AppState {
|
||||||
|
fn event(
|
||||||
|
state: &mut Self,
|
||||||
|
lock_surface: &ExtSessionLockSurfaceV1,
|
||||||
|
event: ext_session_lock_surface_v1::Event,
|
||||||
|
_data: &(),
|
||||||
|
_conn: &Connection,
|
||||||
|
_queue_handle: &QueueHandle<Self>,
|
||||||
|
) {
|
||||||
|
if let ext_session_lock_surface_v1::Event::Configure {
|
||||||
|
serial,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
} = event
|
||||||
|
{
|
||||||
|
let lock_surface_id = lock_surface.id();
|
||||||
|
if let Some(manager) = state.lock_manager_mut() {
|
||||||
|
if let Err(err) = manager.handle_configure(&lock_surface_id, serial, width, height)
|
||||||
|
{
|
||||||
|
info!("Failed to configure session lock surface: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! impl_empty_dispatch_app {
|
macro_rules! impl_empty_dispatch_app {
|
||||||
($(($t:ty, $u:ty)),+) => {
|
($(($t:ty, $u:ty)),+) => {
|
||||||
$(
|
$(
|
||||||
|
|
@ -523,6 +683,7 @@ impl_empty_dispatch_app!(
|
||||||
(WlCompositor, ()),
|
(WlCompositor, ()),
|
||||||
(WlSurface, ()),
|
(WlSurface, ()),
|
||||||
(ZwlrLayerShellV1, ()),
|
(ZwlrLayerShellV1, ()),
|
||||||
|
(ExtSessionLockManagerV1, ()),
|
||||||
(WlSeat, ()),
|
(WlSeat, ()),
|
||||||
(WpFractionalScaleManagerV1, ()),
|
(WpFractionalScaleManagerV1, ()),
|
||||||
(WpViewporter, ()),
|
(WpViewporter, ()),
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
use crate::wayland::surfaces::keyboard_state::KeyboardState;
|
use crate::wayland::surfaces::keyboard_state::{KeyboardState, keysym_to_text};
|
||||||
|
use crate::wayland::surfaces::pointer_utils::wayland_button_to_slint;
|
||||||
use crate::wayland::surfaces::surface_state::SurfaceState;
|
use crate::wayland::surfaces::surface_state::SurfaceState;
|
||||||
use log::info;
|
use log::info;
|
||||||
use slint::{
|
use slint::{
|
||||||
PhysicalSize,
|
PhysicalSize,
|
||||||
platform::{Key, PointerEventButton, WindowEvent},
|
platform::WindowEvent,
|
||||||
};
|
};
|
||||||
use slint::SharedString;
|
use slint::SharedString;
|
||||||
use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::{
|
use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::{
|
||||||
|
|
@ -130,14 +131,7 @@ impl SurfaceState {
|
||||||
) {
|
) {
|
||||||
self.set_last_pointer_serial(serial);
|
self.set_last_pointer_serial(serial);
|
||||||
let position = self.current_pointer_position();
|
let position = self.current_pointer_position();
|
||||||
let slint_button = match button {
|
let slint_button = wayland_button_to_slint(button);
|
||||||
0x110 => PointerEventButton::Left,
|
|
||||||
0x111 => PointerEventButton::Right,
|
|
||||||
0x112 => PointerEventButton::Middle,
|
|
||||||
0x115 => PointerEventButton::Forward,
|
|
||||||
0x116 => PointerEventButton::Back,
|
|
||||||
_ => PointerEventButton::Other,
|
|
||||||
};
|
|
||||||
let event = match button_state {
|
let event = match button_state {
|
||||||
WEnum::Value(wl_pointer::ButtonState::Pressed) => WindowEvent::PointerPressed {
|
WEnum::Value(wl_pointer::ButtonState::Pressed) => WindowEvent::PointerPressed {
|
||||||
button: slint_button,
|
button: slint_button,
|
||||||
|
|
@ -282,48 +276,3 @@ impl SurfaceState {
|
||||||
xdg_wm_base.pong(serial);
|
xdg_wm_base.pong(serial);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keysym_to_text(keysym: xkb::Keysym) -> Option<SharedString> {
|
|
||||||
let key = match keysym.raw() {
|
|
||||||
xkb::keysyms::KEY_Return | xkb::keysyms::KEY_KP_Enter => Key::Return,
|
|
||||||
xkb::keysyms::KEY_BackSpace => Key::Backspace,
|
|
||||||
xkb::keysyms::KEY_Tab => Key::Tab,
|
|
||||||
xkb::keysyms::KEY_BackTab => Key::Backtab,
|
|
||||||
xkb::keysyms::KEY_Escape => Key::Escape,
|
|
||||||
xkb::keysyms::KEY_Delete => Key::Delete,
|
|
||||||
xkb::keysyms::KEY_Insert => Key::Insert,
|
|
||||||
xkb::keysyms::KEY_Home => Key::Home,
|
|
||||||
xkb::keysyms::KEY_End => Key::End,
|
|
||||||
xkb::keysyms::KEY_Page_Up => Key::PageUp,
|
|
||||||
xkb::keysyms::KEY_Page_Down => Key::PageDown,
|
|
||||||
xkb::keysyms::KEY_Left => Key::LeftArrow,
|
|
||||||
xkb::keysyms::KEY_Right => Key::RightArrow,
|
|
||||||
xkb::keysyms::KEY_Up => Key::UpArrow,
|
|
||||||
xkb::keysyms::KEY_Down => Key::DownArrow,
|
|
||||||
xkb::keysyms::KEY_space => Key::Space,
|
|
||||||
xkb::keysyms::KEY_Shift_L => Key::Shift,
|
|
||||||
xkb::keysyms::KEY_Shift_R => Key::ShiftR,
|
|
||||||
xkb::keysyms::KEY_Control_L => Key::Control,
|
|
||||||
xkb::keysyms::KEY_Control_R => Key::ControlR,
|
|
||||||
xkb::keysyms::KEY_Alt_L | xkb::keysyms::KEY_Alt_R => Key::Alt,
|
|
||||||
xkb::keysyms::KEY_Mode_switch => Key::AltGr,
|
|
||||||
xkb::keysyms::KEY_Meta_L => Key::Meta,
|
|
||||||
xkb::keysyms::KEY_Meta_R => Key::MetaR,
|
|
||||||
xkb::keysyms::KEY_Caps_Lock => Key::CapsLock,
|
|
||||||
xkb::keysyms::KEY_F1 => Key::F1,
|
|
||||||
xkb::keysyms::KEY_F2 => Key::F2,
|
|
||||||
xkb::keysyms::KEY_F3 => Key::F3,
|
|
||||||
xkb::keysyms::KEY_F4 => Key::F4,
|
|
||||||
xkb::keysyms::KEY_F5 => Key::F5,
|
|
||||||
xkb::keysyms::KEY_F6 => Key::F6,
|
|
||||||
xkb::keysyms::KEY_F7 => Key::F7,
|
|
||||||
xkb::keysyms::KEY_F8 => Key::F8,
|
|
||||||
xkb::keysyms::KEY_F9 => Key::F9,
|
|
||||||
xkb::keysyms::KEY_F10 => Key::F10,
|
|
||||||
xkb::keysyms::KEY_F11 => Key::F11,
|
|
||||||
xkb::keysyms::KEY_F12 => Key::F12,
|
|
||||||
_ => return None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(SharedString::from(key))
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ use wayland_client::{
|
||||||
use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1;
|
use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1;
|
||||||
use wayland_protocols::wp::viewporter::client::wp_viewporter::WpViewporter;
|
use wayland_protocols::wp::viewporter::client::wp_viewporter::WpViewporter;
|
||||||
use wayland_protocols::xdg::shell::client::xdg_wm_base::XdgWmBase;
|
use wayland_protocols::xdg::shell::client::xdg_wm_base::XdgWmBase;
|
||||||
|
use wayland_protocols::ext::session_lock::v1::client::ext_session_lock_manager_v1::ExtSessionLockManagerV1;
|
||||||
|
|
||||||
use crate::wayland::surfaces::app_state::AppState;
|
use crate::wayland::surfaces::app_state::AppState;
|
||||||
|
|
||||||
|
|
@ -22,6 +23,7 @@ pub struct GlobalContext {
|
||||||
pub layer_shell: ZwlrLayerShellV1,
|
pub layer_shell: ZwlrLayerShellV1,
|
||||||
pub seat: WlSeat,
|
pub seat: WlSeat,
|
||||||
pub xdg_wm_base: Option<XdgWmBase>,
|
pub xdg_wm_base: Option<XdgWmBase>,
|
||||||
|
pub session_lock_manager: Option<ExtSessionLockManagerV1>,
|
||||||
pub fractional_scale_manager: Option<WpFractionalScaleManagerV1>,
|
pub fractional_scale_manager: Option<WpFractionalScaleManagerV1>,
|
||||||
pub viewporter: Option<WpViewporter>,
|
pub viewporter: Option<WpViewporter>,
|
||||||
pub render_context_manager: Rc<RenderContextManager>,
|
pub render_context_manager: Rc<RenderContextManager>,
|
||||||
|
|
@ -85,6 +87,10 @@ impl GlobalContext {
|
||||||
.bind::<XdgWmBase, _, _>(queue_handle, 1..=6, ())
|
.bind::<XdgWmBase, _, _>(queue_handle, 1..=6, ())
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
|
let session_lock_manager = global_list
|
||||||
|
.bind::<ExtSessionLockManagerV1, _, _>(queue_handle, 1..=1, ())
|
||||||
|
.ok();
|
||||||
|
|
||||||
let fractional_scale_manager = global_list
|
let fractional_scale_manager = global_list
|
||||||
.bind::<WpFractionalScaleManagerV1, _, _>(queue_handle, 1..=1, ())
|
.bind::<WpFractionalScaleManagerV1, _, _>(queue_handle, 1..=1, ())
|
||||||
.ok();
|
.ok();
|
||||||
|
|
@ -97,6 +103,10 @@ impl GlobalContext {
|
||||||
info!("xdg-shell protocol not available, popup support disabled");
|
info!("xdg-shell protocol not available, popup support disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if session_lock_manager.is_none() {
|
||||||
|
info!("ext-session-lock protocol not available, session lock disabled");
|
||||||
|
}
|
||||||
|
|
||||||
if fractional_scale_manager.is_none() {
|
if fractional_scale_manager.is_none() {
|
||||||
info!("Fractional scale protocol not available, using integer scaling");
|
info!("Fractional scale protocol not available, using integer scaling");
|
||||||
}
|
}
|
||||||
|
|
@ -113,6 +123,7 @@ impl GlobalContext {
|
||||||
layer_shell,
|
layer_shell,
|
||||||
seat,
|
seat,
|
||||||
xdg_wm_base,
|
xdg_wm_base,
|
||||||
|
session_lock_manager,
|
||||||
fractional_scale_manager,
|
fractional_scale_manager,
|
||||||
viewporter,
|
viewporter,
|
||||||
render_context_manager,
|
render_context_manager,
|
||||||
|
|
|
||||||
|
|
@ -4,5 +4,6 @@ pub(crate) mod globals;
|
||||||
pub(crate) mod managed_proxies;
|
pub(crate) mod managed_proxies;
|
||||||
pub mod ops;
|
pub mod ops;
|
||||||
pub(crate) mod outputs;
|
pub(crate) mod outputs;
|
||||||
|
pub(crate) mod session_lock;
|
||||||
pub(crate) mod shell_adapter;
|
pub(crate) mod shell_adapter;
|
||||||
pub(crate) mod surfaces;
|
pub(crate) mod surfaces;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,15 @@
|
||||||
use crate::errors::Result;
|
use crate::errors::Result;
|
||||||
use crate::wayland::config::ShellSurfaceConfig;
|
use crate::wayland::config::ShellSurfaceConfig;
|
||||||
use crate::wayland::surfaces::app_state::AppState;
|
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 slint_interpreter::Value;
|
||||||
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;
|
||||||
use smithay_client_toolkit::reexports::calloop::LoopHandle;
|
use smithay_client_toolkit::reexports::calloop::LoopHandle;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
type SessionLockCallback = Rc<dyn Fn(&[Value]) -> Value>;
|
||||||
|
|
||||||
pub trait WaylandSystemOps {
|
pub trait WaylandSystemOps {
|
||||||
fn run(&mut self) -> Result<()>;
|
fn run(&mut self) -> Result<()>;
|
||||||
|
|
@ -12,6 +18,20 @@ pub trait WaylandSystemOps {
|
||||||
|
|
||||||
fn despawn_surface(&mut self, name: &str) -> Result<()>;
|
fn despawn_surface(&mut self, name: &str) -> Result<()>;
|
||||||
|
|
||||||
|
fn activate_session_lock(&mut self, component_name: &str, config: LockConfig) -> Result<()>;
|
||||||
|
|
||||||
|
fn deactivate_session_lock(&mut self) -> Result<()>;
|
||||||
|
|
||||||
|
fn is_session_lock_available(&self) -> bool;
|
||||||
|
|
||||||
|
fn session_lock_state(&self) -> Option<LockState>;
|
||||||
|
|
||||||
|
fn register_session_lock_callback(
|
||||||
|
&mut self,
|
||||||
|
callback_name: &str,
|
||||||
|
handler: SessionLockCallback,
|
||||||
|
);
|
||||||
|
|
||||||
fn app_state(&self) -> &AppState;
|
fn app_state(&self) -> &AppState;
|
||||||
|
|
||||||
fn app_state_mut(&mut self) -> &mut AppState;
|
fn app_state_mut(&mut self) -> &mut AppState;
|
||||||
|
|
|
||||||
75
crates/adapters/src/wayland/session_lock/lock_context.rs
Normal file
75
crates/adapters/src/wayland/session_lock/lock_context.rs
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
use crate::rendering::egl::context_factory::RenderContextFactory;
|
||||||
|
use crate::wayland::surfaces::app_state::AppState;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use wayland_client::{
|
||||||
|
QueueHandle,
|
||||||
|
protocol::{wl_compositor::WlCompositor, wl_output::WlOutput, wl_seat::WlSeat},
|
||||||
|
};
|
||||||
|
use wayland_protocols::ext::session_lock::v1::client::ext_session_lock_manager_v1::ExtSessionLockManagerV1;
|
||||||
|
use wayland_protocols::ext::session_lock::v1::client::ext_session_lock_v1::ExtSessionLockV1;
|
||||||
|
use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1;
|
||||||
|
use wayland_protocols::wp::viewporter::client::wp_viewporter::WpViewporter;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SessionLockContext {
|
||||||
|
compositor: WlCompositor,
|
||||||
|
lock_manager: ExtSessionLockManagerV1,
|
||||||
|
seat: WlSeat,
|
||||||
|
fractional_scale_manager: Option<WpFractionalScaleManagerV1>,
|
||||||
|
viewporter: Option<WpViewporter>,
|
||||||
|
render_factory: Rc<RenderContextFactory>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SessionLockContext {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(
|
||||||
|
compositor: WlCompositor,
|
||||||
|
lock_manager: ExtSessionLockManagerV1,
|
||||||
|
seat: WlSeat,
|
||||||
|
fractional_scale_manager: Option<WpFractionalScaleManagerV1>,
|
||||||
|
viewporter: Option<WpViewporter>,
|
||||||
|
render_factory: Rc<RenderContextFactory>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
compositor,
|
||||||
|
lock_manager,
|
||||||
|
seat,
|
||||||
|
fractional_scale_manager,
|
||||||
|
viewporter,
|
||||||
|
render_factory,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn compositor(&self) -> &WlCompositor {
|
||||||
|
&self.compositor
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn lock_manager(&self) -> &ExtSessionLockManagerV1 {
|
||||||
|
&self.lock_manager
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn seat(&self) -> &WlSeat {
|
||||||
|
&self.seat
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn fractional_scale_manager(&self) -> Option<&WpFractionalScaleManagerV1> {
|
||||||
|
self.fractional_scale_manager.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn viewporter(&self) -> Option<&WpViewporter> {
|
||||||
|
self.viewporter.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn render_factory(&self) -> &Rc<RenderContextFactory> {
|
||||||
|
&self.render_factory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LockSurfaceParams<'a> {
|
||||||
|
pub compositor: &'a WlCompositor,
|
||||||
|
pub output: &'a WlOutput,
|
||||||
|
pub session_lock: &'a ExtSessionLockV1,
|
||||||
|
pub fractional_scale_manager: Option<&'a WpFractionalScaleManagerV1>,
|
||||||
|
pub viewporter: Option<&'a WpViewporter>,
|
||||||
|
pub queue_handle: &'a QueueHandle<AppState>,
|
||||||
|
}
|
||||||
763
crates/adapters/src/wayland/session_lock/lock_manager.rs
Normal file
763
crates/adapters/src/wayland/session_lock/lock_manager.rs
Normal file
|
|
@ -0,0 +1,763 @@
|
||||||
|
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 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;
|
||||||
|
|
||||||
|
type LockCallbackHandler = Rc<dyn Fn(&[Value]) -> Value>;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct LockCallback {
|
||||||
|
name: String,
|
||||||
|
handler: LockCallbackHandler,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LockCallback {
|
||||||
|
pub fn new(name: impl Into<String>, handler: LockCallbackHandler) -> Self {
|
||||||
|
Self {
|
||||||
|
name: name.into(),
|
||||||
|
handler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LockConfigureContext {
|
||||||
|
scale_factor: f32,
|
||||||
|
component_definition: ComponentDefinition,
|
||||||
|
compilation_result: Option<Rc<CompilationResult>>,
|
||||||
|
platform: Rc<CustomSlintPlatform>,
|
||||||
|
callbacks: Vec<LockCallback>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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(¶ms);
|
||||||
|
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(¶ms);
|
||||||
|
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 handle_configure(
|
||||||
|
&mut self,
|
||||||
|
lock_surface_id: &ObjectId,
|
||||||
|
serial: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<()> {
|
||||||
|
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(),
|
||||||
|
};
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
118
crates/adapters/src/wayland/session_lock/lock_surface.rs
Normal file
118
crates/adapters/src/wayland/session_lock/lock_surface.rs
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
use crate::wayland::session_lock::lock_context::LockSurfaceParams;
|
||||||
|
use log::info;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use wayland_client::{Proxy, backend::ObjectId, protocol::wl_surface::WlSurface};
|
||||||
|
use wayland_protocols::ext::session_lock::v1::client::ext_session_lock_surface_v1::ExtSessionLockSurfaceV1;
|
||||||
|
use wayland_protocols::wp::fractional_scale::v1::client::{
|
||||||
|
wp_fractional_scale_v1::WpFractionalScaleV1,
|
||||||
|
};
|
||||||
|
use wayland_protocols::wp::viewporter::client::wp_viewport::WpViewport;
|
||||||
|
|
||||||
|
pub struct LockSurface {
|
||||||
|
surface: Rc<WlSurface>,
|
||||||
|
session_surface: Rc<ExtSessionLockSurfaceV1>,
|
||||||
|
fractional_scale: Option<Rc<WpFractionalScaleV1>>,
|
||||||
|
viewport: Option<Rc<WpViewport>>,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
configured: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LockSurface {
|
||||||
|
pub fn create(params: &LockSurfaceParams<'_>) -> Self {
|
||||||
|
let surface = Rc::new(params.compositor.create_surface(params.queue_handle, ()));
|
||||||
|
|
||||||
|
let session_surface = Rc::new(params.session_lock.get_lock_surface(
|
||||||
|
&surface,
|
||||||
|
params.output,
|
||||||
|
params.queue_handle,
|
||||||
|
(),
|
||||||
|
));
|
||||||
|
|
||||||
|
let fractional_scale = params.fractional_scale_manager.map(|manager| {
|
||||||
|
info!("Creating fractional scale object for lock surface");
|
||||||
|
Rc::new(manager.get_fractional_scale(&surface, params.queue_handle, ()))
|
||||||
|
});
|
||||||
|
|
||||||
|
let viewport = params.viewporter.map(|vp| {
|
||||||
|
info!("Creating viewport for lock surface");
|
||||||
|
Rc::new(vp.get_viewport(&surface, params.queue_handle, ()))
|
||||||
|
});
|
||||||
|
|
||||||
|
surface.set_buffer_scale(1);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
surface,
|
||||||
|
session_surface,
|
||||||
|
fractional_scale,
|
||||||
|
viewport,
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
configured: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_configure(&mut self, serial: u32, width: u32, height: u32) {
|
||||||
|
info!("Lock surface configured with compositor size: {width}x{height}");
|
||||||
|
self.session_surface.ack_configure(serial);
|
||||||
|
self.width = width;
|
||||||
|
self.height = height;
|
||||||
|
self.configured = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn width(&self) -> u32 {
|
||||||
|
self.width
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn height(&self) -> u32 {
|
||||||
|
self.height
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn surface_id(&self) -> ObjectId {
|
||||||
|
self.surface.id()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn lock_surface_id(&self) -> ObjectId {
|
||||||
|
self.session_surface.id()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fractional_scale(&self) -> Option<&Rc<WpFractionalScaleV1>> {
|
||||||
|
self.fractional_scale.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn has_fractional_scale(&self) -> bool {
|
||||||
|
self.fractional_scale.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn has_viewport(&self) -> bool {
|
||||||
|
self.viewport.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn configure_fractional_viewport(&self, logical_width: u32, logical_height: u32) {
|
||||||
|
self.surface.set_buffer_scale(1);
|
||||||
|
if let Some(vp) = &self.viewport {
|
||||||
|
let width_i32 = i32::try_from(logical_width).unwrap_or(i32::MAX);
|
||||||
|
let height_i32 = i32::try_from(logical_height).unwrap_or(i32::MAX);
|
||||||
|
vp.set_destination(width_i32, height_i32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn configure_buffer_scale(&self, buffer_scale: i32) {
|
||||||
|
self.surface.set_buffer_scale(buffer_scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(&self) {
|
||||||
|
self.session_surface.destroy();
|
||||||
|
self.surface.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for LockSurface {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
3
crates/adapters/src/wayland/session_lock/mod.rs
Normal file
3
crates/adapters/src/wayland/session_lock/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod lock_context;
|
||||||
|
pub mod lock_manager;
|
||||||
|
pub mod lock_surface;
|
||||||
|
|
@ -26,6 +26,8 @@ use crate::{
|
||||||
use core::result::Result as CoreResult;
|
use core::result::Result as CoreResult;
|
||||||
use layer_shika_domain::errors::DomainError;
|
use layer_shika_domain::errors::DomainError;
|
||||||
use layer_shika_domain::ports::shell::ShellSystemPort;
|
use layer_shika_domain::ports::shell::ShellSystemPort;
|
||||||
|
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_handle::OutputHandle;
|
||||||
use layer_shika_domain::value_objects::output_info::OutputInfo;
|
use layer_shika_domain::value_objects::output_info::OutputInfo;
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
|
|
@ -272,7 +274,10 @@ impl WaylandShellSystem {
|
||||||
connection: &Connection,
|
connection: &Connection,
|
||||||
event_queue: &mut EventQueue<AppState>,
|
event_queue: &mut EventQueue<AppState>,
|
||||||
) -> Result<AppState> {
|
) -> Result<AppState> {
|
||||||
let global_ctx = GlobalContext::initialize(connection, &event_queue.handle())?;
|
let global_ctx = Rc::new(GlobalContext::initialize(
|
||||||
|
connection,
|
||||||
|
&event_queue.handle(),
|
||||||
|
)?);
|
||||||
let layer_surface_config = Self::create_layer_surface_config(config);
|
let layer_surface_config = Self::create_layer_surface_config(config);
|
||||||
|
|
||||||
let pointer = Rc::new(global_ctx.seat.get_pointer(&event_queue.handle(), ()));
|
let pointer = Rc::new(global_ctx.seat.get_pointer(&event_queue.handle(), ()));
|
||||||
|
|
@ -300,7 +305,7 @@ impl WaylandShellSystem {
|
||||||
|
|
||||||
let setups = Self::create_output_setups(
|
let setups = Self::create_output_setups(
|
||||||
config,
|
config,
|
||||||
&global_ctx,
|
global_ctx.as_ref(),
|
||||||
connection,
|
connection,
|
||||||
event_queue,
|
event_queue,
|
||||||
&pointer,
|
&pointer,
|
||||||
|
|
@ -308,6 +313,7 @@ impl WaylandShellSystem {
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let platform = Self::setup_platform(&setups)?;
|
let platform = Self::setup_platform(&setups)?;
|
||||||
|
app_state.set_slint_platform(Rc::clone(&platform));
|
||||||
|
|
||||||
let (popup_managers, layer_surfaces) =
|
let (popup_managers, layer_surfaces) =
|
||||||
Self::create_window_states(setups, &popup_context, &shared_serial, &mut app_state)?;
|
Self::create_window_states(setups, &popup_context, &shared_serial, &mut app_state)?;
|
||||||
|
|
@ -322,7 +328,7 @@ impl WaylandShellSystem {
|
||||||
|
|
||||||
let output_manager = Self::create_output_manager(&OutputManagerParams {
|
let output_manager = Self::create_output_manager(&OutputManagerParams {
|
||||||
config,
|
config,
|
||||||
global_ctx: &global_ctx,
|
global_ctx: global_ctx.as_ref(),
|
||||||
connection,
|
connection,
|
||||||
layer_surface_config,
|
layer_surface_config,
|
||||||
render_factory: &render_factory,
|
render_factory: &render_factory,
|
||||||
|
|
@ -332,6 +338,7 @@ impl WaylandShellSystem {
|
||||||
});
|
});
|
||||||
|
|
||||||
app_state.set_output_manager(Rc::new(RefCell::new(output_manager)));
|
app_state.set_output_manager(Rc::new(RefCell::new(output_manager)));
|
||||||
|
app_state.set_global_context(Rc::clone(&global_ctx));
|
||||||
|
|
||||||
Ok(app_state)
|
Ok(app_state)
|
||||||
}
|
}
|
||||||
|
|
@ -341,7 +348,10 @@ impl WaylandShellSystem {
|
||||||
connection: &Connection,
|
connection: &Connection,
|
||||||
event_queue: &mut EventQueue<AppState>,
|
event_queue: &mut EventQueue<AppState>,
|
||||||
) -> Result<AppState> {
|
) -> Result<AppState> {
|
||||||
let global_ctx = GlobalContext::initialize(connection, &event_queue.handle())?;
|
let global_ctx = Rc::new(GlobalContext::initialize(
|
||||||
|
connection,
|
||||||
|
&event_queue.handle(),
|
||||||
|
)?);
|
||||||
|
|
||||||
let pointer = Rc::new(global_ctx.seat.get_pointer(&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 keyboard = Rc::new(global_ctx.seat.get_keyboard(&event_queue.handle(), ()));
|
||||||
|
|
@ -368,13 +378,14 @@ impl WaylandShellSystem {
|
||||||
|
|
||||||
let setups = Self::create_output_setups_multi(
|
let setups = Self::create_output_setups_multi(
|
||||||
configs,
|
configs,
|
||||||
&global_ctx,
|
global_ctx.as_ref(),
|
||||||
connection,
|
connection,
|
||||||
event_queue,
|
event_queue,
|
||||||
&pointer,
|
&pointer,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let platform = Self::setup_platform(&setups)?;
|
let platform = Self::setup_platform(&setups)?;
|
||||||
|
app_state.set_slint_platform(Rc::clone(&platform));
|
||||||
|
|
||||||
let (popup_managers, layer_surfaces) =
|
let (popup_managers, layer_surfaces) =
|
||||||
Self::create_window_states(setups, &popup_context, &shared_serial, &mut app_state)?;
|
Self::create_window_states(setups, &popup_context, &shared_serial, &mut app_state)?;
|
||||||
|
|
@ -392,7 +403,7 @@ impl WaylandShellSystem {
|
||||||
let layer_surface_config = Self::create_layer_surface_config(config);
|
let layer_surface_config = Self::create_layer_surface_config(config);
|
||||||
let output_manager = Self::create_output_manager(&OutputManagerParams {
|
let output_manager = Self::create_output_manager(&OutputManagerParams {
|
||||||
config,
|
config,
|
||||||
global_ctx: &global_ctx,
|
global_ctx: global_ctx.as_ref(),
|
||||||
connection,
|
connection,
|
||||||
layer_surface_config,
|
layer_surface_config,
|
||||||
render_factory: &render_factory,
|
render_factory: &render_factory,
|
||||||
|
|
@ -404,6 +415,8 @@ impl WaylandShellSystem {
|
||||||
app_state.set_output_manager(Rc::new(RefCell::new(output_manager)));
|
app_state.set_output_manager(Rc::new(RefCell::new(output_manager)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app_state.set_global_context(Rc::clone(&global_ctx));
|
||||||
|
|
||||||
Ok(app_state)
|
Ok(app_state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -611,6 +624,8 @@ impl WaylandShellSystem {
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.state.render_lock_frames()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Initial configuration complete, requesting final render");
|
info!("Initial configuration complete, requesting final render");
|
||||||
|
|
@ -626,6 +641,7 @@ impl WaylandShellSystem {
|
||||||
message: e.to_string(),
|
message: e.to_string(),
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
self.state.render_lock_frames()?;
|
||||||
self.connection
|
self.connection
|
||||||
.flush()
|
.flush()
|
||||||
.map_err(|e| LayerShikaError::WaylandProtocol { source: e })?;
|
.map_err(|e| LayerShikaError::WaylandProtocol { source: e })?;
|
||||||
|
|
@ -694,6 +710,8 @@ impl WaylandShellSystem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shared_data.render_lock_frames()?;
|
||||||
|
|
||||||
connection
|
connection
|
||||||
.flush()
|
.flush()
|
||||||
.map_err(|e| LayerShikaError::WaylandProtocol { source: e })?;
|
.map_err(|e| LayerShikaError::WaylandProtocol { source: e })?;
|
||||||
|
|
@ -780,6 +798,33 @@ impl WaylandSystemOps for WaylandShellSystem {
|
||||||
WaylandShellSystem::despawn_surface(self, name)
|
WaylandShellSystem::despawn_surface(self, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn activate_session_lock(&mut self, component_name: &str, config: LockConfig) -> Result<()> {
|
||||||
|
let queue_handle = self.event_queue.handle();
|
||||||
|
self.state
|
||||||
|
.activate_session_lock(component_name, config, &queue_handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deactivate_session_lock(&mut self) -> Result<()> {
|
||||||
|
self.state.deactivate_session_lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_session_lock_available(&self) -> bool {
|
||||||
|
self.state.is_session_lock_available()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn session_lock_state(&self) -> Option<LockState> {
|
||||||
|
self.state.current_lock_state()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_session_lock_callback(
|
||||||
|
&mut self,
|
||||||
|
callback_name: &str,
|
||||||
|
handler: Rc<dyn Fn(&[slint_interpreter::Value]) -> slint_interpreter::Value>,
|
||||||
|
) {
|
||||||
|
self.state
|
||||||
|
.register_session_lock_callback(callback_name, handler);
|
||||||
|
}
|
||||||
|
|
||||||
fn app_state(&self) -> &AppState {
|
fn app_state(&self) -> &AppState {
|
||||||
WaylandShellSystem::app_state(self)
|
WaylandShellSystem::app_state(self)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,21 @@
|
||||||
use super::event_context::SharedPointerSerial;
|
use super::event_context::SharedPointerSerial;
|
||||||
use super::keyboard_state::KeyboardState;
|
use super::keyboard_state::KeyboardState;
|
||||||
use super::surface_state::SurfaceState;
|
use super::surface_state::SurfaceState;
|
||||||
|
use crate::errors::{LayerShikaError, Result};
|
||||||
|
use crate::rendering::egl::context_factory::RenderContextFactory;
|
||||||
|
use crate::wayland::globals::context::GlobalContext;
|
||||||
use crate::wayland::managed_proxies::{ManagedWlKeyboard, ManagedWlPointer};
|
use crate::wayland::managed_proxies::{ManagedWlKeyboard, ManagedWlPointer};
|
||||||
use crate::wayland::outputs::{OutputManager, OutputMapping};
|
use crate::wayland::outputs::{OutputManager, OutputMapping};
|
||||||
|
use crate::wayland::session_lock::lock_context::SessionLockContext;
|
||||||
|
use crate::wayland::session_lock::lock_manager::{LockCallback, SessionLockManager};
|
||||||
|
use crate::rendering::slint_integration::platform::CustomSlintPlatform;
|
||||||
use layer_shika_domain::entities::output_registry::OutputRegistry;
|
use layer_shika_domain::entities::output_registry::OutputRegistry;
|
||||||
use layer_shika_domain::value_objects::handle::SurfaceHandle;
|
use layer_shika_domain::value_objects::handle::SurfaceHandle;
|
||||||
use layer_shika_domain::value_objects::output_handle::OutputHandle;
|
use layer_shika_domain::value_objects::output_handle::OutputHandle;
|
||||||
|
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_info::OutputInfo;
|
use layer_shika_domain::value_objects::output_info::OutputInfo;
|
||||||
|
use slint_interpreter::{CompilationResult, ComponentDefinition, Value};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::os::fd::BorrowedFd;
|
use std::os::fd::BorrowedFd;
|
||||||
|
|
@ -14,10 +23,11 @@ use std::rc::Rc;
|
||||||
use wayland_client::Proxy;
|
use wayland_client::Proxy;
|
||||||
use wayland_client::backend::ObjectId;
|
use wayland_client::backend::ObjectId;
|
||||||
use wayland_client::protocol::wl_keyboard;
|
use wayland_client::protocol::wl_keyboard;
|
||||||
use wayland_client::protocol::wl_surface::WlSurface;
|
use wayland_client::protocol::{wl_output::WlOutput, wl_surface::WlSurface};
|
||||||
use xkbcommon::xkb;
|
use xkbcommon::xkb;
|
||||||
|
|
||||||
pub type PerOutputSurface = SurfaceState;
|
pub type PerOutputSurface = SurfaceState;
|
||||||
|
type SessionLockCallback = Rc<dyn Fn(&[Value]) -> Value>;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct ShellSurfaceKey {
|
pub struct ShellSurfaceKey {
|
||||||
|
|
@ -35,6 +45,9 @@ impl ShellSurfaceKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
|
global_context: Option<Rc<GlobalContext>>,
|
||||||
|
known_outputs: Vec<WlOutput>,
|
||||||
|
slint_platform: Option<Rc<CustomSlintPlatform>>,
|
||||||
output_registry: OutputRegistry,
|
output_registry: OutputRegistry,
|
||||||
output_mapping: OutputMapping,
|
output_mapping: OutputMapping,
|
||||||
surfaces: HashMap<ShellSurfaceKey, PerOutputSurface>,
|
surfaces: HashMap<ShellSurfaceKey, PerOutputSurface>,
|
||||||
|
|
@ -49,6 +62,8 @@ pub struct AppState {
|
||||||
keyboard_focus_key: Option<ShellSurfaceKey>,
|
keyboard_focus_key: Option<ShellSurfaceKey>,
|
||||||
keyboard_focus_surface_id: Option<ObjectId>,
|
keyboard_focus_surface_id: Option<ObjectId>,
|
||||||
keyboard_state: KeyboardState,
|
keyboard_state: KeyboardState,
|
||||||
|
lock_manager: Option<SessionLockManager>,
|
||||||
|
lock_callbacks: Vec<LockCallback>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
|
|
@ -58,6 +73,9 @@ impl AppState {
|
||||||
shared_serial: Rc<SharedPointerSerial>,
|
shared_serial: Rc<SharedPointerSerial>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
global_context: None,
|
||||||
|
known_outputs: Vec::new(),
|
||||||
|
slint_platform: None,
|
||||||
output_registry: OutputRegistry::new(),
|
output_registry: OutputRegistry::new(),
|
||||||
output_mapping: OutputMapping::new(),
|
output_mapping: OutputMapping::new(),
|
||||||
surfaces: HashMap::new(),
|
surfaces: HashMap::new(),
|
||||||
|
|
@ -72,6 +90,194 @@ impl AppState {
|
||||||
keyboard_focus_key: None,
|
keyboard_focus_key: None,
|
||||||
keyboard_focus_surface_id: None,
|
keyboard_focus_surface_id: None,
|
||||||
keyboard_state: KeyboardState::new(),
|
keyboard_state: KeyboardState::new(),
|
||||||
|
lock_manager: None,
|
||||||
|
lock_callbacks: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_global_context(&mut self, context: Rc<GlobalContext>) {
|
||||||
|
self.known_outputs.clone_from(&context.outputs);
|
||||||
|
self.global_context = Some(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_slint_platform(&mut self, platform: Rc<CustomSlintPlatform>) {
|
||||||
|
self.slint_platform = Some(platform);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lock_manager(&self) -> Option<&SessionLockManager> {
|
||||||
|
self.lock_manager.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lock_manager_mut(&mut self) -> Option<&mut SessionLockManager> {
|
||||||
|
self.lock_manager.as_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_lock_manager(&mut self) {
|
||||||
|
self.lock_manager = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_session_lock_available(&self) -> bool {
|
||||||
|
self.global_context
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|ctx| ctx.session_lock_manager.as_ref())
|
||||||
|
.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current_lock_state(&self) -> Option<LockState> {
|
||||||
|
self.lock_manager.as_ref().map(SessionLockManager::state)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register_session_lock_callback(
|
||||||
|
&mut self,
|
||||||
|
callback_name: impl Into<String>,
|
||||||
|
handler: SessionLockCallback,
|
||||||
|
) {
|
||||||
|
let callback = LockCallback::new(callback_name, handler);
|
||||||
|
if let Some(manager) = self.lock_manager.as_mut() {
|
||||||
|
manager.register_callback(callback.clone());
|
||||||
|
}
|
||||||
|
self.lock_callbacks.push(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn activate_session_lock(
|
||||||
|
&mut self,
|
||||||
|
component_name: &str,
|
||||||
|
config: LockConfig,
|
||||||
|
queue_handle: &wayland_client::QueueHandle<AppState>,
|
||||||
|
) -> Result<()> {
|
||||||
|
if self.lock_manager.is_some() {
|
||||||
|
return Err(LayerShikaError::InvalidInput {
|
||||||
|
message: "Session lock already active".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let context = self.create_lock_context()?;
|
||||||
|
let (definition, compilation_result) =
|
||||||
|
self.resolve_lock_component(component_name)?;
|
||||||
|
let platform = self.slint_platform.as_ref().ok_or_else(|| {
|
||||||
|
LayerShikaError::InvalidInput {
|
||||||
|
message: "Slint platform not initialized".to_string(),
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
let mut manager = SessionLockManager::new(
|
||||||
|
context,
|
||||||
|
definition,
|
||||||
|
compilation_result,
|
||||||
|
Rc::clone(platform),
|
||||||
|
config,
|
||||||
|
);
|
||||||
|
for callback in self.lock_callbacks.iter().cloned() {
|
||||||
|
manager.register_callback(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
let outputs = self.collect_session_lock_outputs();
|
||||||
|
manager.activate(outputs, queue_handle)?;
|
||||||
|
|
||||||
|
self.lock_manager = Some(manager);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deactivate_session_lock(&mut self) -> Result<()> {
|
||||||
|
let Some(manager) = self.lock_manager.as_mut() else {
|
||||||
|
return Err(LayerShikaError::InvalidInput {
|
||||||
|
message: "No session lock active".to_string(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
manager.deactivate()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_lock_frames(&self) -> Result<()> {
|
||||||
|
if let Some(manager) = self.lock_manager.as_ref() {
|
||||||
|
if manager.state() != LockState::Locked && manager.state() != LockState::Locking {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
manager.render_frames()?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_lock_component(
|
||||||
|
&self,
|
||||||
|
component_name: &str,
|
||||||
|
) -> Result<(ComponentDefinition, Option<Rc<CompilationResult>>)> {
|
||||||
|
let compilation_result = self
|
||||||
|
.primary_output()
|
||||||
|
.and_then(SurfaceState::compilation_result)
|
||||||
|
.ok_or_else(|| LayerShikaError::InvalidInput {
|
||||||
|
message: "No compilation result available for session lock".to_string(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let definition = compilation_result.component(component_name).ok_or_else(|| {
|
||||||
|
LayerShikaError::InvalidInput {
|
||||||
|
message: format!(
|
||||||
|
"Component '{component_name}' not found in compilation result"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok((definition, Some(compilation_result)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_lock_context(&self) -> Result<Rc<SessionLockContext>> {
|
||||||
|
let Some(global_ctx) = self.global_context.as_ref() else {
|
||||||
|
return Err(LayerShikaError::InvalidInput {
|
||||||
|
message: "Global context not available for session lock".to_string(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(lock_manager) = global_ctx.session_lock_manager.as_ref() else {
|
||||||
|
return Err(LayerShikaError::InvalidInput {
|
||||||
|
message: "Session lock protocol not available".to_string(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let render_factory =
|
||||||
|
RenderContextFactory::new(Rc::clone(&global_ctx.render_context_manager));
|
||||||
|
|
||||||
|
Ok(Rc::new(SessionLockContext::new(
|
||||||
|
global_ctx.compositor.clone(),
|
||||||
|
lock_manager.clone(),
|
||||||
|
global_ctx.seat.clone(),
|
||||||
|
global_ctx.fractional_scale_manager.clone(),
|
||||||
|
global_ctx.viewporter.clone(),
|
||||||
|
render_factory,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_session_lock_outputs(&self) -> Vec<WlOutput> {
|
||||||
|
self.known_outputs.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_output_added_for_lock(
|
||||||
|
&mut self,
|
||||||
|
output: &WlOutput,
|
||||||
|
queue_handle: &wayland_client::QueueHandle<AppState>,
|
||||||
|
) -> Result<()> {
|
||||||
|
if !self
|
||||||
|
.known_outputs
|
||||||
|
.iter()
|
||||||
|
.any(|known| known.id() == output.id())
|
||||||
|
{
|
||||||
|
self.known_outputs.push(output.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(manager) = self.lock_manager.as_mut() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
if manager.state() == LockState::Locked {
|
||||||
|
manager.add_output(output, queue_handle)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_output_removed_for_lock(&mut self, output_id: &ObjectId) {
|
||||||
|
self.known_outputs
|
||||||
|
.retain(|output| output.id() != *output_id);
|
||||||
|
if let Some(manager) = self.lock_manager.as_mut() {
|
||||||
|
manager.remove_output(output_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -360,6 +566,13 @@ impl AppState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_keyboard_enter(&mut self, _serial: u32, surface: &WlSurface, _keys: &[u8]) {
|
pub fn handle_keyboard_enter(&mut self, _serial: u32, surface: &WlSurface, _keys: &[u8]) {
|
||||||
|
if let Some(manager) = self.lock_manager.as_mut() {
|
||||||
|
if manager.handle_keyboard_enter(surface) {
|
||||||
|
self.set_keyboard_focus(None, None);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let surface_id = surface.id();
|
let surface_id = surface.id();
|
||||||
if let Some(key) = self.get_key_by_surface(&surface_id).cloned() {
|
if let Some(key) = self.get_key_by_surface(&surface_id).cloned() {
|
||||||
self.set_keyboard_focus(Some(key), Some(surface_id));
|
self.set_keyboard_focus(Some(key), Some(surface_id));
|
||||||
|
|
@ -372,12 +585,24 @@ impl AppState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_keyboard_leave(&mut self, _serial: u32, surface: &WlSurface) {
|
pub fn handle_keyboard_leave(&mut self, _serial: u32, surface: &WlSurface) {
|
||||||
|
if let Some(manager) = self.lock_manager.as_mut() {
|
||||||
|
if manager.handle_keyboard_leave(surface) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if self.keyboard_focus_surface_id == Some(surface.id()) {
|
if self.keyboard_focus_surface_id == Some(surface.id()) {
|
||||||
self.set_keyboard_focus(None, None);
|
self.set_keyboard_focus(None, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_key(&mut self, _serial: u32, _time: u32, key: u32, state: wl_keyboard::KeyState) {
|
pub fn handle_key(&mut self, _serial: u32, _time: u32, key: u32, state: wl_keyboard::KeyState) {
|
||||||
|
if let Some(manager) = self.lock_manager.as_mut() {
|
||||||
|
if manager.handle_keyboard_key(key, state, &mut self.keyboard_state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let Some(focus_key) = self.keyboard_focus_key.clone() else {
|
let Some(focus_key) = self.keyboard_focus_key.clone() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
@ -385,9 +610,8 @@ impl AppState {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let keyboard_state = &mut self.keyboard_state;
|
|
||||||
if let Some(surface) = self.surfaces.get_mut(&focus_key) {
|
if let Some(surface) = self.surfaces.get_mut(&focus_key) {
|
||||||
surface.handle_keyboard_key(&surface_id, key, state, keyboard_state);
|
surface.handle_keyboard_key(&surface_id, key, state, &mut self.keyboard_state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use slint::{SharedString, platform::Key};
|
||||||
use xkbcommon::xkb;
|
use xkbcommon::xkb;
|
||||||
|
|
||||||
pub struct KeyboardState {
|
pub struct KeyboardState {
|
||||||
|
|
@ -25,3 +26,48 @@ impl KeyboardState {
|
||||||
self.xkb_keymap = Some(keymap);
|
self.xkb_keymap = Some(keymap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn keysym_to_text(keysym: xkb::Keysym) -> Option<SharedString> {
|
||||||
|
let key = match keysym.raw() {
|
||||||
|
xkb::keysyms::KEY_Return | xkb::keysyms::KEY_KP_Enter => Key::Return,
|
||||||
|
xkb::keysyms::KEY_BackSpace => Key::Backspace,
|
||||||
|
xkb::keysyms::KEY_Tab => Key::Tab,
|
||||||
|
xkb::keysyms::KEY_BackTab => Key::Backtab,
|
||||||
|
xkb::keysyms::KEY_Escape => Key::Escape,
|
||||||
|
xkb::keysyms::KEY_Delete => Key::Delete,
|
||||||
|
xkb::keysyms::KEY_Insert => Key::Insert,
|
||||||
|
xkb::keysyms::KEY_Home => Key::Home,
|
||||||
|
xkb::keysyms::KEY_End => Key::End,
|
||||||
|
xkb::keysyms::KEY_Page_Up => Key::PageUp,
|
||||||
|
xkb::keysyms::KEY_Page_Down => Key::PageDown,
|
||||||
|
xkb::keysyms::KEY_Left => Key::LeftArrow,
|
||||||
|
xkb::keysyms::KEY_Right => Key::RightArrow,
|
||||||
|
xkb::keysyms::KEY_Up => Key::UpArrow,
|
||||||
|
xkb::keysyms::KEY_Down => Key::DownArrow,
|
||||||
|
xkb::keysyms::KEY_space => Key::Space,
|
||||||
|
xkb::keysyms::KEY_Shift_L => Key::Shift,
|
||||||
|
xkb::keysyms::KEY_Shift_R => Key::ShiftR,
|
||||||
|
xkb::keysyms::KEY_Control_L => Key::Control,
|
||||||
|
xkb::keysyms::KEY_Control_R => Key::ControlR,
|
||||||
|
xkb::keysyms::KEY_Alt_L | xkb::keysyms::KEY_Alt_R => Key::Alt,
|
||||||
|
xkb::keysyms::KEY_Mode_switch => Key::AltGr,
|
||||||
|
xkb::keysyms::KEY_Meta_L => Key::Meta,
|
||||||
|
xkb::keysyms::KEY_Meta_R => Key::MetaR,
|
||||||
|
xkb::keysyms::KEY_Caps_Lock => Key::CapsLock,
|
||||||
|
xkb::keysyms::KEY_F1 => Key::F1,
|
||||||
|
xkb::keysyms::KEY_F2 => Key::F2,
|
||||||
|
xkb::keysyms::KEY_F3 => Key::F3,
|
||||||
|
xkb::keysyms::KEY_F4 => Key::F4,
|
||||||
|
xkb::keysyms::KEY_F5 => Key::F5,
|
||||||
|
xkb::keysyms::KEY_F6 => Key::F6,
|
||||||
|
xkb::keysyms::KEY_F7 => Key::F7,
|
||||||
|
xkb::keysyms::KEY_F8 => Key::F8,
|
||||||
|
xkb::keysyms::KEY_F9 => Key::F9,
|
||||||
|
xkb::keysyms::KEY_F10 => Key::F10,
|
||||||
|
xkb::keysyms::KEY_F11 => Key::F11,
|
||||||
|
xkb::keysyms::KEY_F12 => Key::F12,
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(SharedString::from(key))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ pub mod display_metrics;
|
||||||
pub mod event_context;
|
pub mod event_context;
|
||||||
pub mod keyboard_state;
|
pub mod keyboard_state;
|
||||||
pub mod layer_surface;
|
pub mod layer_surface;
|
||||||
|
pub(crate) mod pointer_utils;
|
||||||
pub mod popup_manager;
|
pub mod popup_manager;
|
||||||
pub mod popup_surface;
|
pub mod popup_surface;
|
||||||
pub mod rendering_state;
|
pub mod rendering_state;
|
||||||
|
|
|
||||||
12
crates/adapters/src/wayland/surfaces/pointer_utils.rs
Normal file
12
crates/adapters/src/wayland/surfaces/pointer_utils.rs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
use slint::platform::PointerEventButton;
|
||||||
|
|
||||||
|
pub(crate) fn wayland_button_to_slint(button: u32) -> PointerEventButton {
|
||||||
|
match button {
|
||||||
|
0x110 => PointerEventButton::Left,
|
||||||
|
0x111 => PointerEventButton::Right,
|
||||||
|
0x112 => PointerEventButton::Middle,
|
||||||
|
0x115 => PointerEventButton::Forward,
|
||||||
|
0x116 => PointerEventButton::Back,
|
||||||
|
_ => PointerEventButton::Other,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ mod event_loop;
|
||||||
mod layer_surface;
|
mod layer_surface;
|
||||||
mod popup;
|
mod popup;
|
||||||
mod popup_builder;
|
mod popup_builder;
|
||||||
|
mod session_lock;
|
||||||
mod selection;
|
mod selection;
|
||||||
mod selector;
|
mod selector;
|
||||||
mod shell;
|
mod shell;
|
||||||
|
|
@ -40,6 +41,7 @@ pub use layer_shika_domain::value_objects::{
|
||||||
pub use layer_surface::{LayerSurfaceHandle, ShellSurfaceConfigHandler};
|
pub use layer_surface::{LayerSurfaceHandle, ShellSurfaceConfigHandler};
|
||||||
pub use popup::PopupShell;
|
pub use popup::PopupShell;
|
||||||
pub use popup_builder::PopupBuilder;
|
pub use popup_builder::PopupBuilder;
|
||||||
|
pub use session_lock::{SessionLock, SessionLockBuilder};
|
||||||
pub use selection::Selection;
|
pub use selection::Selection;
|
||||||
pub use selector::{Output, Selector, Surface, SurfaceInfo};
|
pub use selector::{Output, Selector, Surface, SurfaceInfo};
|
||||||
pub use shell_runtime::{DEFAULT_SURFACE_NAME, ShellRuntime};
|
pub use shell_runtime::{DEFAULT_SURFACE_NAME, ShellRuntime};
|
||||||
|
|
@ -75,6 +77,12 @@ pub enum Error {
|
||||||
|
|
||||||
#[error("App has been dropped")]
|
#[error("App has been dropped")]
|
||||||
SystemDropped,
|
SystemDropped,
|
||||||
|
|
||||||
|
#[error("Invalid lock state '{current}' for operation '{operation}'")]
|
||||||
|
InvalidState { current: String, operation: String },
|
||||||
|
|
||||||
|
#[error("Protocol '{protocol}' not available on this compositor")]
|
||||||
|
ProtocolNotAvailable { protocol: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
|
|
@ -83,10 +91,11 @@ pub mod prelude {
|
||||||
DEFAULT_SURFACE_NAME, EventDispatchContext, EventLoopHandle, Handle, IntoValue,
|
DEFAULT_SURFACE_NAME, EventDispatchContext, EventLoopHandle, Handle, IntoValue,
|
||||||
KeyboardInteractivity, Layer, LayerSurfaceHandle, Output, OutputGeometry, OutputHandle,
|
KeyboardInteractivity, Layer, LayerSurfaceHandle, Output, OutputGeometry, OutputHandle,
|
||||||
OutputInfo, OutputPolicy, OutputRegistry, PopupBuilder, PopupConfig, PopupHandle,
|
OutputInfo, OutputPolicy, OutputRegistry, PopupBuilder, PopupConfig, PopupHandle,
|
||||||
PopupPosition, PopupShell, PopupSize, PopupWindow, Result, Selection, Selector, Shell,
|
PopupPosition, PopupShell, PopupSize, PopupWindow, Result, Selection, Selector,
|
||||||
ShellBuilder, ShellConfig, ShellControl, ShellEventContext, ShellEventLoop, ShellRuntime,
|
SessionLock, SessionLockBuilder, Shell, ShellBuilder, ShellConfig, ShellControl,
|
||||||
ShellSurfaceConfigHandler, Surface, SurfaceComponentConfig, SurfaceConfigBuilder,
|
ShellEventContext, ShellEventLoop, ShellRuntime, ShellSurfaceConfigHandler, Surface,
|
||||||
SurfaceControlHandle, SurfaceDefinition, SurfaceEntry, SurfaceHandle, SurfaceInfo,
|
SurfaceComponentConfig, SurfaceConfigBuilder, SurfaceControlHandle, SurfaceDefinition,
|
||||||
|
SurfaceEntry, SurfaceHandle, SurfaceInfo,
|
||||||
SurfaceMetadata, SurfaceRegistry,
|
SurfaceMetadata, SurfaceRegistry,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
190
crates/composition/src/session_lock.rs
Normal file
190
crates/composition/src/session_lock.rs
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
use crate::{Error, Result};
|
||||||
|
use crate::IntoValue;
|
||||||
|
use crate::calloop::channel;
|
||||||
|
use crate::system::{SessionLockCommand, ShellCommand};
|
||||||
|
use layer_shika_adapters::WaylandSystemOps;
|
||||||
|
use layer_shika_domain::dimensions::ScaleFactor;
|
||||||
|
use layer_shika_domain::errors::DomainError;
|
||||||
|
use layer_shika_domain::value_objects::lock_config::LockConfig;
|
||||||
|
use layer_shika_domain::value_objects::lock_state::LockState;
|
||||||
|
use layer_shika_domain::value_objects::margins::Margins;
|
||||||
|
use layer_shika_domain::value_objects::output_policy::OutputPolicy;
|
||||||
|
use std::cell::{Cell, RefCell};
|
||||||
|
use std::rc::Weak;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use crate::slint_interpreter::Value;
|
||||||
|
|
||||||
|
pub struct SessionLock {
|
||||||
|
system: Weak<RefCell<dyn WaylandSystemOps>>,
|
||||||
|
component_name: String,
|
||||||
|
config: LockConfig,
|
||||||
|
state: Cell<LockState>,
|
||||||
|
command_sender: channel::Sender<ShellCommand>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SessionLock {
|
||||||
|
pub(crate) fn new(
|
||||||
|
system: Weak<RefCell<dyn WaylandSystemOps>>,
|
||||||
|
component_name: String,
|
||||||
|
config: LockConfig,
|
||||||
|
command_sender: channel::Sender<ShellCommand>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
system,
|
||||||
|
component_name,
|
||||||
|
config,
|
||||||
|
state: Cell::new(LockState::Inactive),
|
||||||
|
command_sender,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn activate(&self) -> Result<()> {
|
||||||
|
let current = self.state.get();
|
||||||
|
if !current.can_activate() {
|
||||||
|
return Err(Error::InvalidState {
|
||||||
|
current: format!("{current:?}"),
|
||||||
|
operation: "activate".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let system = self.system.upgrade().ok_or(Error::SystemDropped)?;
|
||||||
|
system
|
||||||
|
.borrow_mut()
|
||||||
|
.activate_session_lock(&self.component_name, self.config.clone())?;
|
||||||
|
self.state.set(LockState::Locking);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deactivate(&self) -> Result<()> {
|
||||||
|
log::info!("deactivate() called - queuing SessionLockCommand::Deactivate");
|
||||||
|
|
||||||
|
if let Some(system) = self.system.upgrade() {
|
||||||
|
if let Ok(borrowed) = system.try_borrow() {
|
||||||
|
if let Some(state) = borrowed.session_lock_state() {
|
||||||
|
log::info!("Syncing lock state before deactivate: {:?}", state);
|
||||||
|
self.state.set(state);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log::warn!("Could not borrow system to sync state - already borrowed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let current = self.state.get();
|
||||||
|
log::info!("Current lock state for deactivate check: {:?}", current);
|
||||||
|
if !current.can_deactivate() {
|
||||||
|
return Err(Error::InvalidState {
|
||||||
|
current: format!("{current:?}"),
|
||||||
|
operation: "deactivate".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send deactivate command via channel to be processed outside borrow context
|
||||||
|
self.command_sender
|
||||||
|
.send(ShellCommand::SessionLock(SessionLockCommand::Deactivate))
|
||||||
|
.map_err(|e| Error::Domain(DomainError::InvalidInput {
|
||||||
|
message: format!("Failed to send session lock command: {e:?}"),
|
||||||
|
}))?;
|
||||||
|
|
||||||
|
log::info!("SessionLockCommand::Deactivate queued successfully");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers a callback handler on the lock screen component.
|
||||||
|
///
|
||||||
|
/// The callback must exist in the Slint component, for example:
|
||||||
|
/// `callback unlock_requested(string)`.
|
||||||
|
pub fn on_callback<F, R>(&self, callback_name: &str, handler: F) -> Result<()>
|
||||||
|
where
|
||||||
|
F: Fn() -> R + Clone + 'static,
|
||||||
|
R: IntoValue,
|
||||||
|
{
|
||||||
|
self.on_callback_with_args(callback_name, move |_args| handler().into_value())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers a callback handler that receives Slint arguments.
|
||||||
|
pub fn on_callback_with_args<F, R>(&self, callback_name: &str, handler: F) -> Result<()>
|
||||||
|
where
|
||||||
|
F: Fn(&[Value]) -> R + Clone + 'static,
|
||||||
|
R: IntoValue,
|
||||||
|
{
|
||||||
|
let system = self.system.upgrade().ok_or(Error::SystemDropped)?;
|
||||||
|
let handler = Rc::new(move |args: &[Value]| handler(args).into_value());
|
||||||
|
system
|
||||||
|
.borrow_mut()
|
||||||
|
.register_session_lock_callback(callback_name, handler);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn state(&self) -> LockState {
|
||||||
|
if let Some(system) = self.system.upgrade() {
|
||||||
|
if let Some(state) = system.borrow().session_lock_state() {
|
||||||
|
self.state.set(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.state.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_locked(&self) -> bool {
|
||||||
|
self.state() == LockState::Locked
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn component_name(&self) -> &str {
|
||||||
|
&self.component_name
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SessionLockBuilder {
|
||||||
|
component_name: String,
|
||||||
|
config: LockConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SessionLockBuilder {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(component_name: impl Into<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
component_name: component_name.into(),
|
||||||
|
config: LockConfig::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn scale_factor(mut self, factor: impl TryInto<ScaleFactor, Error = DomainError>) -> Self {
|
||||||
|
self.config.scale_factor = factor.try_into().unwrap_or_default();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn margin(mut self, margin: impl Into<Margins>) -> Self {
|
||||||
|
self.config.margin = margin.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn namespace(mut self, namespace: impl Into<String>) -> Self {
|
||||||
|
self.config.namespace = namespace.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn output_policy(mut self, policy: OutputPolicy) -> Self {
|
||||||
|
self.config.output_policy = policy;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn build(
|
||||||
|
self,
|
||||||
|
system: Weak<RefCell<dyn WaylandSystemOps>>,
|
||||||
|
command_sender: channel::Sender<ShellCommand>,
|
||||||
|
) -> SessionLock {
|
||||||
|
SessionLock::new(system, self.component_name, self.config, command_sender)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn component_name(&self) -> &str {
|
||||||
|
&self.component_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
use crate::event_loop::{EventLoopHandle, FromAppState};
|
use crate::event_loop::{EventLoopHandle, FromAppState};
|
||||||
use crate::layer_surface::LayerSurfaceHandle;
|
use crate::layer_surface::LayerSurfaceHandle;
|
||||||
|
use crate::session_lock::{SessionLock, SessionLockBuilder};
|
||||||
use crate::shell_config::{CompiledUiSource, ShellConfig};
|
use crate::shell_config::{CompiledUiSource, ShellConfig};
|
||||||
use crate::shell_runtime::ShellRuntime;
|
use crate::shell_runtime::ShellRuntime;
|
||||||
use crate::surface_registry::{SurfaceDefinition, SurfaceEntry, SurfaceRegistry};
|
use crate::surface_registry::{SurfaceDefinition, SurfaceEntry, SurfaceRegistry};
|
||||||
use crate::system::{
|
use crate::system::{
|
||||||
CallbackContext, EventDispatchContext, PopupCommand, ShellCommand, ShellControl,
|
CallbackContext, EventDispatchContext, PopupCommand, SessionLockCommand, ShellCommand,
|
||||||
SurfaceCommand, SurfaceTarget,
|
ShellControl, SurfaceCommand, SurfaceTarget,
|
||||||
};
|
};
|
||||||
use crate::value_conversion::IntoValue;
|
use crate::value_conversion::IntoValue;
|
||||||
use crate::{Error, Result};
|
use crate::{Error, Result};
|
||||||
|
|
@ -30,7 +31,7 @@ use layer_shika_domain::value_objects::surface_instance_id::SurfaceInstanceId;
|
||||||
use spin_on::spin_on;
|
use spin_on::spin_on;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::rc::Rc;
|
use std::rc::{Rc, Weak};
|
||||||
|
|
||||||
/// Default Slint component name used when none is specified
|
/// Default Slint component name used when none is specified
|
||||||
pub const DEFAULT_COMPONENT_NAME: &str = "Main";
|
pub const DEFAULT_COMPONENT_NAME: &str = "Main";
|
||||||
|
|
@ -543,6 +544,7 @@ impl 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();
|
||||||
|
let system = Rc::downgrade(&self.inner);
|
||||||
|
|
||||||
loop_handle
|
loop_handle
|
||||||
.insert_source(receiver, move |event, (), app_state| {
|
.insert_source(receiver, move |event, (), app_state| {
|
||||||
|
|
@ -556,6 +558,9 @@ impl Shell {
|
||||||
ShellCommand::Surface(surface_cmd) => {
|
ShellCommand::Surface(surface_cmd) => {
|
||||||
Self::handle_surface_command(surface_cmd, &mut ctx);
|
Self::handle_surface_command(surface_cmd, &mut ctx);
|
||||||
}
|
}
|
||||||
|
ShellCommand::SessionLock(lock_cmd) => {
|
||||||
|
Self::handle_session_lock_command(&lock_cmd, &mut ctx, &system);
|
||||||
|
}
|
||||||
ShellCommand::Render => {
|
ShellCommand::Render => {
|
||||||
if let Err(e) = ctx.render_frame_if_dirty() {
|
if let Err(e) = ctx.render_frame_if_dirty() {
|
||||||
log::error!("Failed to render frame: {}", e);
|
log::error!("Failed to render frame: {}", e);
|
||||||
|
|
@ -604,6 +609,23 @@ impl Shell {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_session_lock_command(
|
||||||
|
command: &SessionLockCommand,
|
||||||
|
ctx: &mut EventDispatchContext<'_>,
|
||||||
|
_system: &Weak<RefCell<dyn WaylandSystemOps>>,
|
||||||
|
) {
|
||||||
|
match command {
|
||||||
|
SessionLockCommand::Deactivate => {
|
||||||
|
log::info!("Processing SessionLockCommand::Deactivate");
|
||||||
|
if let Err(e) = ctx.deactivate_session_lock() {
|
||||||
|
log::error!("Failed to deactivate session lock: {}", e);
|
||||||
|
} else {
|
||||||
|
log::info!("Session lock deactivated successfully");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn resolve_surface_target<'a>(
|
fn resolve_surface_target<'a>(
|
||||||
ctx: &'a mut EventDispatchContext<'_>,
|
ctx: &'a mut EventDispatchContext<'_>,
|
||||||
target: &SurfaceTarget,
|
target: &SurfaceTarget,
|
||||||
|
|
@ -770,6 +792,33 @@ impl Shell {
|
||||||
self.control().popups()
|
self.control().popups()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_session_lock(&self, component: impl Into<String>) -> Result<SessionLock> {
|
||||||
|
self.create_session_lock_with_config(SessionLockBuilder::new(component))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_session_lock_with_config(
|
||||||
|
&self,
|
||||||
|
builder: SessionLockBuilder,
|
||||||
|
) -> Result<SessionLock> {
|
||||||
|
let component = builder.component_name().to_string();
|
||||||
|
if self.compilation_result.component(&component).is_none() {
|
||||||
|
return Err(Error::Domain(DomainError::Configuration {
|
||||||
|
message: format!(
|
||||||
|
"Component '{}' not found in compilation result",
|
||||||
|
component
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.inner.borrow().is_session_lock_available() {
|
||||||
|
return Err(Error::ProtocolNotAvailable {
|
||||||
|
protocol: "ext-session-lock-v1".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(builder.build(Rc::downgrade(&self.inner), self.command_sender.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the names of all registered surfaces
|
/// Returns the names of all registered surfaces
|
||||||
pub fn surface_names(&self) -> Vec<&str> {
|
pub fn surface_names(&self) -> Vec<&str> {
|
||||||
self.registry.surface_names()
|
self.registry.surface_names()
|
||||||
|
|
|
||||||
|
|
@ -86,9 +86,14 @@ pub enum SurfaceCommand {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum SessionLockCommand {
|
||||||
|
Deactivate,
|
||||||
|
}
|
||||||
|
|
||||||
pub enum ShellCommand {
|
pub enum ShellCommand {
|
||||||
Popup(PopupCommand),
|
Popup(PopupCommand),
|
||||||
Surface(SurfaceCommand),
|
Surface(SurfaceCommand),
|
||||||
|
SessionLock(SessionLockCommand),
|
||||||
Render,
|
Render,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -748,6 +753,13 @@ impl EventDispatchContext<'_> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Deactivates the session lock
|
||||||
|
pub(crate) fn deactivate_session_lock(&mut self) -> Result<()> {
|
||||||
|
self.app_state
|
||||||
|
.deactivate_session_lock()
|
||||||
|
.map_err(Error::Adapter)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the compilation result if available
|
/// Returns the compilation result if available
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn compilation_result(&self) -> Option<Rc<CompilationResult>> {
|
pub fn compilation_result(&self) -> Option<Rc<CompilationResult>> {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ pub use crate::value_objects::dimensions::{PopupDimensions, SurfaceDimension};
|
||||||
pub use crate::value_objects::handle::{Handle, OutputHandle, PopupHandle, SurfaceHandle};
|
pub use crate::value_objects::handle::{Handle, OutputHandle, PopupHandle, SurfaceHandle};
|
||||||
pub use crate::value_objects::keyboard_interactivity::KeyboardInteractivity;
|
pub use crate::value_objects::keyboard_interactivity::KeyboardInteractivity;
|
||||||
pub use crate::value_objects::layer::Layer;
|
pub use crate::value_objects::layer::Layer;
|
||||||
|
pub use crate::value_objects::lock_config::LockConfig;
|
||||||
|
pub use crate::value_objects::lock_state::LockState;
|
||||||
pub use crate::value_objects::margins::Margins;
|
pub use crate::value_objects::margins::Margins;
|
||||||
pub use crate::value_objects::output_info::{OutputGeometry, OutputInfo};
|
pub use crate::value_objects::output_info::{OutputGeometry, OutputInfo};
|
||||||
pub use crate::value_objects::output_policy::OutputPolicy;
|
pub use crate::value_objects::output_policy::OutputPolicy;
|
||||||
|
|
|
||||||
40
crates/domain/src/value_objects/lock_config.rs
Normal file
40
crates/domain/src/value_objects/lock_config.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
use crate::dimensions::ScaleFactor;
|
||||||
|
use crate::errors::{DomainError, Result};
|
||||||
|
use crate::value_objects::margins::Margins;
|
||||||
|
use crate::value_objects::output_policy::OutputPolicy;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct LockConfig {
|
||||||
|
pub scale_factor: ScaleFactor,
|
||||||
|
pub margin: Margins,
|
||||||
|
pub namespace: String,
|
||||||
|
pub output_policy: OutputPolicy,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LockConfig {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
scale_factor: ScaleFactor::default(),
|
||||||
|
margin: Margins::default(),
|
||||||
|
namespace: "layer-shika-lock".to_string(),
|
||||||
|
output_policy: OutputPolicy::AllOutputs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate(&self) -> Result<()> {
|
||||||
|
let factor = self.scale_factor.value();
|
||||||
|
if factor <= 0.0 || !factor.is_finite() {
|
||||||
|
return Err(DomainError::InvalidInput {
|
||||||
|
message: format!("Lock scale factor must be positive and finite, got {factor}"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for LockConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
24
crates/domain/src/value_objects/lock_state.rs
Normal file
24
crates/domain/src/value_objects/lock_state.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum LockState {
|
||||||
|
Inactive,
|
||||||
|
Locking,
|
||||||
|
Locked,
|
||||||
|
Unlocking,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LockState {
|
||||||
|
#[must_use]
|
||||||
|
pub const fn can_activate(self) -> bool {
|
||||||
|
matches!(self, Self::Inactive)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn can_deactivate(self) -> bool {
|
||||||
|
matches!(self, Self::Locked | Self::Locking)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn is_transitioning(self) -> bool {
|
||||||
|
matches!(self, Self::Locking | Self::Unlocking)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,8 @@ pub mod dimensions;
|
||||||
pub mod handle;
|
pub mod handle;
|
||||||
pub mod keyboard_interactivity;
|
pub mod keyboard_interactivity;
|
||||||
pub mod layer;
|
pub mod layer;
|
||||||
|
pub mod lock_config;
|
||||||
|
pub mod lock_state;
|
||||||
pub mod margins;
|
pub mod margins;
|
||||||
pub mod output_handle;
|
pub mod output_handle;
|
||||||
pub mod output_info;
|
pub mod output_info;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue