use rustc_hash::FxHashMap;
use crate::app_state::with_app_state;
use crate::outputs::{self, OutputId, OutputInfo};
use crate::platform::LockEvent;
use crate::reactive::global::GlobalSignal;
use crate::reactive::owner::with_owner;
use crate::reactive::{RwSignal, Signal};
use crate::surface::{SurfaceConfig, SurfaceId};
use crate::surface_manager::{ManagedSurface, SurfaceManager};
use crate::tree::Tree;
use crate::widgets::Widget;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum LockState {
#[default]
Unlocked,
Locking,
Locked,
}
type LockWidgetFn = Box<dyn Fn(OutputInfo) -> Box<dyn Widget>>;
#[derive(Default)]
pub(crate) struct LockData {
factory: Option<LockWidgetFn>,
surfaces: FxHashMap<OutputId, SurfaceId>,
}
pub(crate) enum LockRequest {
Lock(LockWidgetFn),
Unlock,
}
fn with_lock<R>(f: impl FnOnce(&mut LockData) -> R) -> R {
with_app_state(|app| f(&mut app.lock.borrow_mut()))
}
fn forget_factory() {
with_lock(|lock| lock.factory = None);
}
static STATE: GlobalSignal<LockState> = GlobalSignal::new(LockState::default);
fn state_signal() -> RwSignal<LockState> {
STATE.get()
}
fn set_state(state: LockState) {
state_signal().set(state);
}
pub fn lock_state() -> Signal<LockState> {
state_signal().read_only()
}
pub fn session_locked() -> bool {
lock_state().get() == LockState::Locked
}
pub fn lock_session<W, F>(widget_fn: F)
where
W: Widget + 'static,
F: Fn(OutputInfo) -> W + 'static,
{
if matches!(
STATE.get().get_untracked(),
LockState::Locked | LockState::Locking
) {
log::warn!("lock_session() called while already locked or locking");
return;
}
with_app_state(|app| {
app.lock_request
.set(LockRequest::Lock(Box::new(move |info| {
Box::new(widget_fn(info))
})))
});
}
pub fn unlock_session() {
with_app_state(|app| app.lock_request.set(LockRequest::Unlock));
}
pub(crate) fn process_session_lock<P: crate::Platform>(
surface_manager: &mut SurfaceManager,
wayland_state: &mut P,
tree: &mut Tree,
gpu: &mut crate::renderer::GpuSlot,
) {
let mut unlock_requested = false;
match with_app_state(|app| app.lock_request.take()) {
Some(LockRequest::Lock(factory)) => {
with_lock(|lock| lock.factory = Some(factory));
if gpu.get().is_some() && wayland_state.start_session_lock() {
set_state(LockState::Locking);
} else {
forget_factory();
set_state(LockState::Unlocked);
}
}
Some(LockRequest::Unlock) => unlock_requested = true,
None => {}
}
for event in wayland_state.take_lock_events() {
match event {
LockEvent::Locked => set_state(LockState::Locked),
LockEvent::Finished => {
teardown_lock_surfaces(surface_manager, wayland_state, tree);
forget_factory();
set_state(LockState::Unlocked);
}
}
}
if unlock_requested && state_signal().get_untracked() != LockState::Unlocked {
wayland_state.unlock_session();
teardown_lock_surfaces(surface_manager, wayland_state, tree);
forget_factory();
set_state(LockState::Unlocked);
}
if state_signal().get_untracked() != LockState::Unlocked {
let current = outputs::current_outputs();
let stale: Vec<(OutputId, SurfaceId)> = with_lock(|lock| {
let stale: Vec<(OutputId, SurfaceId)> = lock
.surfaces
.iter()
.filter(|(out, _)| !current.iter().any(|o| o.id == **out))
.map(|(out, sid)| (*out, *sid))
.collect();
for (out, _) in &stale {
lock.surfaces.remove(out);
}
stale
});
for (_, sid) in stale {
if let Some(managed) = surface_manager.remove(sid) {
crate::surface_manager::teardown_widget_subtree(tree, managed.widget_id);
}
wayland_state.destroy_surface(sid);
}
for info in current {
if with_lock(|lock| lock.surfaces.contains_key(&info.id)) {
continue;
}
let id = SurfaceId::next();
if !wayland_state.create_lock_surface(id, info.id) {
continue;
}
let output_id = info.id;
let widget = with_app_state(|app| {
app.lock
.borrow()
.factory
.as_ref()
.map(|f| with_owner(|| f(info.clone())))
});
let Some((widget, owner_id)) = widget else {
log::error!("Lock held but no lock widget factory is set");
wayland_state.destroy_surface(id);
continue;
};
let config = SurfaceConfig::new().width(0).height(0);
let managed = ManagedSurface::new(id, config, widget, owner_id, tree);
surface_manager.add(managed);
with_lock(|lock| lock.surfaces.insert(output_id, id));
}
}
}
fn teardown_lock_surfaces<P: crate::Platform>(
surface_manager: &mut SurfaceManager,
wayland_state: &mut P,
tree: &mut crate::tree::Tree,
) {
let surfaces: Vec<SurfaceId> =
with_lock(|lock| lock.surfaces.drain().map(|(_, sid)| sid).collect());
for sid in surfaces {
if let Some(managed) = surface_manager.remove(sid) {
crate::surface_manager::teardown_widget_subtree(tree, managed.widget_id);
}
wayland_state.destroy_surface(sid);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::reactive::owner::{create_root_owner, dispose_owner_now, with_owner};
#[test]
fn the_last_request_of_a_frame_is_the_one_that_counts() {
create_root_owner();
crate::app_state::reset();
set_state(LockState::Unlocked);
lock_session(|_| crate::widgets::container());
unlock_session();
assert!(
matches!(
with_app_state(|app| app.lock_request.take()),
Some(LockRequest::Unlock)
),
"the later request replaces the earlier one rather than joining it"
);
assert!(
with_app_state(|app| app.lock_request.take()).is_none(),
"and there is only ever the one"
);
crate::app_state::reset();
}
#[test]
fn the_lock_state_outlives_whoever_reads_it_first() {
create_root_owner();
let ((), first_reader) = with_owner(|| {
let _ = lock_state().get_untracked();
});
dispose_owner_now(first_reader);
assert_eq!(lock_state().get_untracked(), LockState::default());
}
}