srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/wayland/src/lock.rs
blob: 828fe980287b7a104b4f79b1b944573aaa329897 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
//! Session lock (`ext-session-lock-v1`).
//!
//! Kept as its own module because the security-relevant invariant spans
//! state, protocol handling *and* rendering: while [`SessionLock::locked`]
//! is set, no client surface other than the lock surface may be drawn or
//! receive input. The render helpers here are the "drawn" half; the input
//! half is the locked-session branches in [`crate::input`], and the focus
//! half is the guard in [`crate::state::CompState::set_keyboard_focus`].

use smithay::backend::renderer::element::surface::{render_elements_from_surface_tree, WaylandSurfaceRenderElement};
use smithay::backend::renderer::element::Kind;
use smithay::output::Output;
use smithay::reexports::wayland_server::protocol::wl_output::WlOutput;
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
use smithay::wayland::session_lock::{LockSurface, SessionLockHandler, SessionLockManagerState, SessionLocker};

use crate::input::dwindow_wl_surface;
use std::collections::{HashMap, HashSet};

use crate::state::CompState;

/// Session-lock runtime state (`ext-session-lock-v1`).
///
/// The security-relevant invariant is that `locked` gates *both* rendering
/// and input: while it is set, no client surface other than a lock surface
/// is drawn or receives events. `pending_confirm` exists because the
/// protocol requires the compositor to confirm the lock only *after* a frame
/// with no client content has actually been presented - confirming earlier
/// would tell the locker "the screen is safe" while the user's windows were
/// still on screen. So `lock()` only stashes the confirmation here, and the
/// render paths call `confirm_lock_if_presented()` once such a frame is out.
///
/// With multiple monitors the locker creates **one lock surface per
/// output**, and the confirmation must wait for *every* output to have both
/// a surface and a presented frame - otherwise a second monitor could still
/// be showing the desktop at the moment the locker is told the session is
/// locked.
#[derive(Default)]
pub(crate) struct SessionLock {
    pub(crate) locked: bool,
    /// Lock surface per output, keyed by `Output::name()`.
    pub(crate) surfaces: HashMap<String, LockSurface>,
    pub(crate) pending_confirm: Option<SessionLocker>,
    /// Outputs that have presented a client-content-free frame since the
    /// lock request, keyed the same way.
    pub(crate) presented: HashSet<String>,
    /// `Some` while srdwm is its own locker - see `native_lock.rs`'s
    /// module doc comment for why this is a separate mode from the fields
    /// above rather than folded into them: those exist for an *external*
    /// locker client talking `ext-session-lock-v1`; this is srdwm drawing
    /// and authenticating its own lock UI directly, with no client
    /// surface involved at all. The two are mutually exclusive by
    /// construction - `srd dispatch lock` never creates a `LockSurface`,
    /// and nothing here stops a *real* external locker from also
    /// connecting, but nothing in this session's scope needed to reconcile
    /// that edge case, and `locked` alone still gates input/rendering
    /// regardless of which mode set it.
    pub(crate) native: Option<crate::native_lock::NativeLock>,
}

impl CompState {
    /// Records that `output` has presented a lock frame, and confirms the
    /// lock once *every* output has done so. Called from both backends right
    /// after a frame is presented. See `SessionLock::pending_confirm`.
    pub(crate) fn confirm_lock_if_presented(&mut self, output: &Output) {
        if !self.lock.locked || self.lock.pending_confirm.is_none() {
            return;
        }
        self.lock.presented.insert(output.name());

        // Every output must be both covered by a lock surface and have shown
        // a frame of it. An output still missing its surface means the locker
        // hasn't got to it yet - keep waiting rather than confirm early.
        let all_covered = self
            .outputs()
            .all(|o| self.lock.surfaces.contains_key(&o.name()) && self.lock.presented.contains(&o.name()));
        if !all_covered {
            return;
        }
        if let Some(confirm) = self.lock.pending_confirm.take() {
            log::info!("session lock: all {} output(s) presented a cleared frame, confirming lock", self.outputs.len());
            confirm.lock();
        }
    }

    /// The lock surface covering `output`, if we are locked and the locker
    /// has created one for it. Used by the render paths and input routing.
    pub(crate) fn lock_surface_for(&self, output: &Output) -> Option<&WlSurface> {
        if !self.lock.locked {
            return None;
        }
        self.lock.surfaces.get(&output.name()).map(|s| s.wl_surface())
    }

    /// Any lock surface at all - used by input routing, which needs a
    /// keyboard-focus target rather than a per-output one.
    pub(crate) fn any_lock_surface(&self) -> Option<&WlSurface> {
        if !self.lock.locked {
            return None;
        }
        self.lock.surfaces.values().next().map(|s| s.wl_surface())
    }
}

impl SessionLockHandler for CompState {
    fn lock_state(&mut self) -> &mut SessionLockManagerState {
        &mut self.session_lock_state
    }

    fn lock(&mut self, confirmation: SessionLocker) {
        log::info!("session lock: locking");
        self.lock.locked = true;
        self.lock.pending_confirm = Some(confirmation);
        // Drop keyboard focus off whatever client had it immediately, so no
        // keystroke can reach a normal client in the window between the lock
        // request and the lock surface being mapped. Focus moves to the lock
        // surface in `new_surface` once it exists.
        self.set_keyboard_focus(None);
    }

    fn unlock(&mut self) {
        log::info!("session lock: unlocking");
        self.lock.locked = false;
        self.lock.surfaces.clear();
        self.lock.presented.clear();
        self.lock.pending_confirm = None;
        // udev backend only: the lock scene was rendered through the same
        // per-head damage tracker as the normal desktop (see
        // `render_udev_frame`), always with a forced full redraw, so its
        // element-state history now reflects the lock surface rather than
        // whatever was on screen before locking. Reset each head's buffer
        // ages so the next normal-scene render is a full redraw too,
        // instead of asking the tracker to diff the desktop against a
        // now-irrelevant lock-scene history.
        if let Some(udev) = self.udev.as_mut() {
            for head in &mut udev.heads {
                head.ages = [0, 0];
            }
        }
        // Hand focus back to whatever srdwm considers the focused window.
        let surface = self
            .wm
            .borrow()
            .focused_id()
            .and_then(|id| self.id_to_window.get(&id).cloned())
            .and_then(|w| dwindow_wl_surface(&w));
        self.set_keyboard_focus(surface);
    }

    fn new_surface(&mut self, surface: LockSurface, wl_output: WlOutput) {
        // The locker creates one surface per output and names which; size it
        // to that output specifically, since monitors differ in resolution.
        let Some(entry) = self.output_for_wl(&wl_output) else {
            log::warn!("session lock: lock surface for an output we don't drive; ignoring");
            return;
        };
        let name = entry.output.name();
        let size = entry.size();
        surface.with_pending_state(|state| {
            state.size = Some((size.w as u32, size.h as u32).into());
        });
        surface.send_configure();
        // Store before focusing: `set_keyboard_focus`'s locked-session guard
        // checks the focus target *against* the stored lock surfaces, so
        // setting it afterwards would make the guard reject this very
        // surface.
        let wl_surface = surface.wl_surface().clone();
        self.lock.surfaces.insert(name, surface);
        self.set_keyboard_focus(Some(wl_surface));
    }
}

/// Render elements for a locked session: the lock surface alone, at the
/// output origin. Deliberately *not* built from the `Space` or the
/// `LayerMap` - while locked, nothing else may reach the screen, so the
/// caller pairs this with an opaque black clear color. Shared by both
/// backends (winit and udev), which differ only in their renderer type.
/// Takes the surface rather than `&CompState` so the udev backend can call
/// it while already holding a `&mut` borrow of its own `UdevOutput`.
pub(crate) fn lock_render_elements<R>(lock_surface: Option<&WlSurface>, renderer: &mut R) -> Vec<WaylandSurfaceRenderElement<R>>
where
    R: smithay::backend::renderer::Renderer + smithay::backend::renderer::ImportAll,
    R::TextureId: Clone + 'static,
{
    let Some(surface) = lock_surface else { return Vec::new() };
    render_elements_from_surface_tree(renderer, surface, (0, 0), 1.0, 1.0, Kind::Unspecified)
}

/// Frame callbacks for the lock surface. Without these the locker never
/// gets to draw a second frame - no cursor blink, no password-dot
/// feedback, no failed-attempt shake.
pub(crate) fn send_lock_frame(lock_surface: Option<&WlSurface>, output: &Output, time: std::time::Duration) {
    if let Some(surface) = lock_surface {
        smithay::desktop::utils::send_frames_surface_tree(surface, output, time, None, |_, _| Some(output.clone()));
    }
}