Model lock ownership explicitly and allow protected crash recovery

This commit is contained in:
Samuel Onoja committed 2026-10-02 08:48:01 +01:00
1 parent 570984a39b
commit 8ad5293178
22 files changed
+264 -80

No files matched your search

+5 -5
View File
@@ -702,7 +702,7 @@ fn open_primary_device(
}
pub(crate) fn sleep_locked_outputs(state: &mut Ferese) {
if !state.session_lock.active || !state.session_lock.sleeping {
if !state.session_lock.active() || !state.session_lock.sleeping {
return;
}
@@ -972,7 +972,7 @@ fn render_output(
crtc: crtc::Handle,
plan: crate::frame_scheduler::FramePlan,
) -> bool {
if state.session_lock.active && state.session_lock.sleeping {
if state.session_lock.active() && state.session_lock.sleeping {
sleep_locked_outputs(state);
let asleep = state
.direct_backend
@@ -1025,7 +1025,7 @@ fn render_output(
let flags = planes::frame_flags(
state.output_has_fullscreen_for_frame(&output.output, frame.overview.is_presenting()),
frame.animating,
state.session_lock.active,
state.session_lock.active(),
);
let result =
output
@@ -1096,7 +1096,7 @@ fn render_output(
.render_metrics
.record_frame(render_started.elapsed(), &damage, missed_deadlines, effects);
output.frame_pending = true;
output.lock_frame_pending = state.session_lock.active;
output.lock_frame_pending = state.session_lock.active();
Ok(true)
})()
};
@@ -1633,7 +1633,7 @@ fn deliver_frame_callbacks(state: &mut Ferese, node: DrmNode, crtc: crtc::Handle
fn send_frame_callbacks(state: &mut Ferese, output: &Output) {
let eligible = state.callback_outputs();
if state.session_lock.active {
if state.session_lock.active() {
state.lock_frame_callbacks(output);
return;
}
+1 -1
View File
@@ -20,7 +20,7 @@ impl XdgActivationHandler for Ferese {
}
fn request_activation(&mut self, token: XdgActivationToken, data: XdgActivationTokenData, surface: WlSurface) {
if self.session_lock.active || self.input_capture.captures(1) {
if self.session_lock.active() || self.input_capture.captures(1) {
return;
}
let valid = data.serial.is_some() && data.timestamp.elapsed() <= TOKEN_MAX_AGE;
+1 -1
View File
@@ -79,7 +79,7 @@ impl CompositorHandler for Ferese {
}
fn destroyed(&mut self, surface: &WlSurface) {
if self.session_lock.active {
if self.session_lock.active() {
self.session_lock
.surfaces
.retain(|_, lock| lock.wl_surface() != surface);
+1 -1
View File
@@ -141,7 +141,7 @@ pub fn handle_commit(state: &mut Ferese, surface: &WlSurface) {
layer.layer_surface().send_pending_configure();
let policy = layer.cached_state();
if !state.session_lock.active
if !state.session_lock.active()
&& !state.input_capture.captures(1)
&& policy.keyboard_interactivity == KeyboardInteractivity::Exclusive
&& matches!(policy.layer, Layer::Top | Layer::Overlay)
+1 -1
View File
@@ -254,7 +254,7 @@ impl Ferese {
return;
}
let mut used = data.used.lock().unwrap();
if self.session_lock.active {
if self.session_lock.active() {
frame.failed();
return;
}
+3 -3
View File
@@ -157,7 +157,7 @@ fn finish_conversion(state: &Ferese, result: Conversion) {
if !frame.is_alive() || *data.used.lock().unwrap() {
return;
}
if state.session_lock.active
if state.session_lock.active()
|| !super::screencopy::capture_allowed()
|| !state.windows.ids().values().any(|id| *id == snapshot.id)
{
@@ -263,7 +263,7 @@ impl Ferese {
budget: &Arc<Mutex<Budget>>,
busy: &mut bool,
) -> Option<Snapshot> {
if self.session_lock.active || !super::screencopy::capture_allowed() {
if self.session_lock.active() || !super::screencopy::capture_allowed() {
return None;
}
let window = self
@@ -363,7 +363,7 @@ pub(super) fn copy_snapshot(state: &Ferese, resource: &ZwlrScreencopyFrameV1, da
resource.failed();
return;
};
if state.session_lock.active
if state.session_lock.active()
|| !super::screencopy::capture_allowed()
|| !state.windows.ids().values().any(|id| *id == snapshot.id)
{
+3 -3
View File
@@ -112,7 +112,7 @@ impl XdgShellHandler for Ferese {
}
fn move_request(&mut self, surface: ToplevelSurface, seat: wl_seat::WlSeat, serial: Serial) {
if self.session_lock.active {
if self.session_lock.active() {
return;
}
let Some(seat) = Seat::from_resource(&seat) else {
@@ -165,7 +165,7 @@ impl XdgShellHandler for Ferese {
serial: Serial,
edges: smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::ResizeEdge,
) {
if self.session_lock.active {
if self.session_lock.active() {
return;
}
let Some(seat) = Seat::from_resource(&seat) else {
@@ -240,7 +240,7 @@ impl XdgShellHandler for Ferese {
}
fn grab(&mut self, surface: PopupSurface, seat: wl_seat::WlSeat, serial: Serial) {
if self.session_lock.active || self.input_capture.active() {
if self.session_lock.active() || self.input_capture.active() {
return;
}
let Some(seat) = Seat::from_resource(&seat) else {
+10 -10
View File
@@ -70,7 +70,7 @@ impl Ferese {
let Some(hint) = self.locked_pointer_hint.take() else {
return;
};
if !hint.surface.is_alive() || self.session_lock.active || self.input_capture.active() {
if !hint.surface.is_alive() || self.session_lock.active() || self.input_capture.active() {
return;
}
let Some(pointer) = self.seat.get_pointer() else {
@@ -120,7 +120,7 @@ impl Ferese {
self.lock_input_activity();
}
if self.session_lock.active {
if self.session_lock.active() {
// An already-bound IME or drag client may install a grab after the
// lock request. Never let that grab receive subsequent lock input.
if let Some(keyboard) = seat.get_keyboard() {
@@ -276,7 +276,7 @@ impl Ferese {
event.time_msec(),
|data, modifiers, keysym| {
if data.focus_cycle.is_some() {
if data.session_lock.active || data.input_capture.captures(1) {
if data.session_lock.active() || data.input_capture.captures(1) {
data.cancel_focus_cycle();
} else if !modifiers.alt {
data.finish_focus_cycle();
@@ -288,7 +288,7 @@ impl Ferese {
return FilterResult::Intercept(());
}
}
if data.session_lock.active {
if data.session_lock.active() {
return FilterResult::Forward;
}
let symbol = keysym.modified_sym().raw();
@@ -695,7 +695,7 @@ impl Ferese {
}
fn start_floating_pointer_grab(&mut self, pointer: &PointerHandle<Self>, button: u32, serial: Serial) -> bool {
if self.session_lock.active
if self.session_lock.active()
|| pointer.is_grabbed()
|| !matches!(button, BTN_LEFT | BTN_RIGHT)
|| !self
@@ -767,7 +767,7 @@ impl Ferese {
}
fn swipe_navigation_blocked(&self) -> bool {
self.session_lock.active
self.session_lock.active()
|| self.active_shortcuts_inhibitor.is_some()
|| self.seat.get_pointer().is_some_and(|pointer| pointer.is_grabbed())
|| self.seat.get_keyboard().is_some_and(|keyboard| keyboard.is_grabbed())
@@ -801,7 +801,7 @@ impl Ferese {
if self.input_capture.captures(1) {
return;
}
if self.session_lock.active {
if self.session_lock.active() {
self.focus_output_at(position);
self.focus_lock_surface();
return;
@@ -908,7 +908,7 @@ impl Ferese {
}
fn focus_window_under_pointer(&mut self, pointer: &PointerHandle<Self>, position: Point<f64, Logical>) {
if self.session_lock.active {
if self.session_lock.active() {
return;
}
if self.overview.is_active() {
@@ -963,7 +963,7 @@ impl Ferese {
pointer: &PointerHandle<Self>,
requested: Point<f64, Logical>,
) -> Point<f64, Logical> {
if self.session_lock.active {
if self.session_lock.active() {
return requested;
}
let current = pointer.current_location();
@@ -1003,7 +1003,7 @@ impl Ferese {
}
pub fn activate_focused_pointer_constraint(&self, pointer: &PointerHandle<Self>) {
if self.session_lock.active {
if self.session_lock.active() {
return;
}
let position = pointer.current_location();
+2 -2
View File
@@ -449,7 +449,7 @@ impl crate::Ferese {
pub(crate) fn restore_input_capture_focus(&mut self) {
self.input_capture.restore_focus = false;
self.cursor_redraw_pending = true;
if !self.session_lock.active {
if !self.session_lock.active() {
self.restore_keyboard_focus();
if let Some(pointer) = self.seat.get_pointer() {
let location = pointer.current_location();
@@ -475,7 +475,7 @@ impl crate::Ferese {
) -> bool {
let pointer = self.seat.get_pointer().expect("seat has pointer");
let active = self.input_capture.active();
let allowed = !self.session_lock.active
let allowed = !self.session_lock.active()
&& !self.overview.is_presenting()
&& !pointer.is_grabbed()
&& !self.swipe.active()
+5 -5
View File
@@ -431,7 +431,7 @@ fn discard_undelivered_screenshot(response: &Response) {
impl Ferese {
fn handle_ipc_request(&mut self, owner: u64, request: Request) -> Response {
if self.session_lock.active
if self.session_lock.active()
&& !matches!(
request.command.as_str(),
"theme-get"
@@ -544,10 +544,10 @@ impl Ferese {
return Ok(json!({}));
}
"portal-shortcuts-poll" => {
return Ok(self.portal_shortcuts.poll(owner, self.session_lock.active));
return Ok(self.portal_shortcuts.poll(owner, self.session_lock.active()));
}
"input-capture-register" => {
if self.session_lock.active {
if self.session_lock.active() {
return Err(CommandError::new("session_locked", "Session is locked"));
}
let id = self
@@ -716,7 +716,7 @@ impl Ferese {
let _ = response.try_send(Response::error(request.id, $code, $message));
};
}
if self.session_lock.active {
if self.session_lock.active() {
reject!("session_locked", "IPC unavailable while session is locked");
return;
}
@@ -813,7 +813,7 @@ impl Ferese {
let reject = |message: String| {
let _ = response.try_send(Response::error(request.id, "window_capture_failed", message));
};
if self.session_lock.active || !screencopy::capture_allowed() {
if self.session_lock.active() || !screencopy::capture_allowed() {
reject("Screen capture is unavailable".into());
return;
}
+1 -1
View File
@@ -106,7 +106,7 @@ fn main() -> Result<(), Box<dyn Error>> {
// when no output has a pending frame.
let wallpaper_changed = state.wallpaper.poll();
// All input/Wayland callbacks have returned, releasing seat locks.
if state.focus_cycle.is_some() && (state.session_lock.active || state.input_capture.captures(1)) {
if state.focus_cycle.is_some() && (state.session_lock.active() || state.input_capture.captures(1)) {
state.cancel_focus_cycle();
}
// Coalesce cursor changes and redraw here, never inside cursor_image.
+1 -1
View File
@@ -27,7 +27,7 @@ fn mapped_tree(surface: &WlSurface) -> bool {
impl Ferese {
pub(crate) fn callback_outputs(&self) -> HashMap<Id, Output> {
let mut surfaces = Vec::new();
if self.session_lock.active {
if self.session_lock.active() {
for lock in self.session_lock.surfaces.values() {
smithay::desktop::utils::with_surfaces_surface_tree(lock.wl_surface(), |surface, _| {
surfaces.push(surface.clone());
+1 -1
View File
@@ -32,7 +32,7 @@ pub(crate) fn sampled_output_elements(
include_cursor: bool,
frame: &crate::state::FrameScene,
) -> Vec<AnimatedWindowRenderElement> {
if state.session_lock.active {
if state.session_lock.active() {
let Some(geometry) = state.space.output_geometry(output) else {
return Vec::new();
};
+208 -31
View File
@@ -78,15 +78,35 @@ impl IdleSettings {
}
}
#[derive(Default)]
enum Lifecycle {
#[default]
Unlocked,
Acquiring(SessionLocker),
Locked(ExtSessionLockV1),
Orphaned,
}
impl Lifecycle {
fn owner(&self) -> Option<&ExtSessionLockV1> {
match self {
Self::Acquiring(confirmation) => Some(confirmation.ext_session_lock()),
Self::Locked(owner) => Some(owner),
Self::Unlocked | Self::Orphaned => None,
}
}
fn can_acquire(&self) -> bool {
self.owner().is_none_or(|owner| !owner.is_alive())
}
}
#[derive(Default)]
pub(crate) struct Lock {
pub active: bool,
owner: Option<ExtSessionLockV1>,
confirmation: Option<SessionLocker>,
lifecycle: Lifecycle,
pub surfaces: HashMap<Output, LockSurface>,
pub backgrounds: HashMap<Output, smithay::backend::renderer::element::solid::SolidColorBuffer>,
presented: HashSet<Output>,
confirmed: bool,
idle_since: Option<Instant>,
pub(crate) idle_opacity: f32,
pub(crate) sleeping: bool,
@@ -95,8 +115,12 @@ pub(crate) struct Lock {
}
impl Lock {
pub(crate) fn active(&self) -> bool {
!matches!(self.lifecycle, Lifecycle::Unlocked)
}
fn ready_for_idle<'a>(&self, outputs: impl Iterator<Item = &'a Output>) -> bool {
self.active && outputs.into_iter().all(|output| self.presented.contains(output))
self.active() && outputs.into_iter().all(|output| self.presented.contains(output))
}
pub(crate) fn output_added(&mut self, output: &Output) {
@@ -109,7 +133,7 @@ impl Lock {
}
fn activity(&mut self, now: Instant) -> bool {
if !self.active {
if !self.active() {
return false;
}
let changed = self.idle_opacity != 0.0 || self.sleeping;
@@ -220,26 +244,39 @@ impl Ferese {
}
pub(crate) fn lock_frame_presented(&mut self, output: &Output) {
if !self.session_lock.active {
if !self.session_lock.active() {
return;
}
let newly_presented = self.session_lock.presented.insert(output.clone());
if self
.space
.outputs()
.all(|output| self.session_lock.presented.contains(output))
&& let Some(confirmation) = self.session_lock.confirmation.take()
&& confirmation.ext_session_lock().is_alive()
{
confirmation.lock();
self.session_lock.confirmed = true;
tracing::info!("session lock confirmed after safe output frames");
}
self.confirm_lock_if_ready();
if newly_presented {
self.refresh_lock_idle_policy();
}
}
pub(crate) fn confirm_lock_if_ready(&mut self) {
// With no outputs there is no visible content to protect. Re-evaluate
// here after acquisition and output removal as well as presentation.
if !self.session_lock.ready_for_idle(self.space.outputs()) {
return;
}
if matches!(self.session_lock.lifecycle, Lifecycle::Acquiring(_)) {
let Lifecycle::Acquiring(confirmation) =
std::mem::replace(&mut self.session_lock.lifecycle, Lifecycle::Orphaned)
else {
unreachable!()
};
let owner = confirmation.ext_session_lock().clone();
if owner.is_alive() {
confirmation.lock();
self.session_lock.lifecycle = Lifecycle::Locked(owner);
tracing::info!("session lock confirmed after safe output frames");
}
}
}
pub(crate) fn configure_lock_surfaces(&mut self) {
for (output, surface) in &self.session_lock.surfaces {
crate::handlers::set_surface_tree_output(surface.wl_surface(), output);
@@ -272,17 +309,19 @@ impl SessionLockHandler for Ferese {
}
fn lock(&mut self, confirmation: SessionLocker) {
if self.session_lock.active {
if !self.session_lock.lifecycle.can_acquire() {
return;
}
// Replacing an orphan never passes through Unlocked. Existing protected
// presentation facts remain valid, but old surfaces must not own input.
self.session_lock.lifecycle = Lifecycle::Acquiring(confirmation);
self.session_lock.surfaces.clear();
self.cancel_logout_confirmation();
self.session_lock.active = true;
self.refresh_idle_inhibition();
self.session_lock.idle_since = Some(Instant::now());
self.input_capture.disable_all();
self.portal_session.set_locked(true);
self.session_lock.owner = Some(confirmation.ext_session_lock().clone());
self.session_lock.confirmation = Some(confirmation);
for capture in self.pending_screencopies.drain(..) {
capture.fail();
}
@@ -315,6 +354,8 @@ impl SessionLockHandler for Ferese {
keyboard.unset_grab(self);
keyboard.set_focus(self, None, serial);
self.intercepted_keys.clear();
self.lock_input_activity();
self.confirm_lock_if_ready();
crate::backends::direct::render_all(self);
}
@@ -359,9 +400,9 @@ impl Dispatch<ExtSessionLockV1, SessionLockState> for Ferese {
display: &DisplayHandle,
data_init: &mut DataInit<'_, Self>,
) {
let owner = state.session_lock.owner.as_ref() == Some(lock);
let owner = state.session_lock.lifecycle.owner() == Some(lock);
if matches!(request, ext_session_lock_v1::Request::UnlockAndDestroy)
&& (!owner || !state.session_lock.confirmed)
&& (!owner || !matches!(state.session_lock.lifecycle, Lifecycle::Locked(_)))
{
lock.post_error(
ext_session_lock_v1::Error::InvalidUnlock,
@@ -377,6 +418,20 @@ impl Dispatch<ExtSessionLockV1, SessionLockState> for Ferese {
state, client, lock, request, data, display, data_init,
);
}
fn destroyed(
state: &mut Self,
_: smithay::reexports::wayland_server::backend::ClientId,
lock: &ExtSessionLockV1,
_: &SessionLockState,
) {
if state.session_lock.lifecycle.owner() == Some(lock) {
state.session_lock.lifecycle = Lifecycle::Orphaned;
state.session_lock.surfaces.clear();
state.focus_lock_surface();
crate::backends::direct::render_all(state);
}
}
}
smithay::reexports::wayland_server::delegate_global_dispatch!(Ferese: [ExtSessionLockManagerV1: smithay::wayland::session_lock::SessionLockManagerGlobalData] => SessionLockManagerState);
smithay::reexports::wayland_server::delegate_dispatch!(Ferese: [ExtSessionLockManagerV1: ()] => SessionLockManagerState);
@@ -386,6 +441,129 @@ smithay::reexports::wayland_server::delegate_dispatch!(Ferese: [ExtSessionLockSu
mod tests {
use super::*;
#[test]
#[ignore = "requires a private XDG_RUNTIME_DIR and permission to bind test sockets"]
fn zero_outputs_confirm_and_dead_owner_can_be_replaced_without_unlocking() {
use std::io::{Read, Write};
use std::os::unix::net::UnixStream;
use std::sync::Arc;
use smithay::reexports::calloop::EventLoop;
use smithay::reexports::wayland_server::Display;
let runtime = std::path::PathBuf::from(std::env::var_os("XDG_RUNTIME_DIR").unwrap());
assert!(runtime.starts_with(std::env::temp_dir()));
let mut event_loop = EventLoop::try_new().unwrap();
let config = crate::config::Config::default().runtime_config().unwrap();
let mut state = Ferese::new(&mut event_loop, Display::new().unwrap(), config).unwrap();
let acquire = |state: &mut Ferese| {
let (server, mut wire) = UnixStream::pair().unwrap();
let client = state
.display_handle
.insert_client(server, Arc::new(crate::state::ClientState::default()))
.unwrap();
let manager = client
.create_resource::<ExtSessionLockManagerV1, (), Ferese>(&state.display_handle, 1, ())
.unwrap();
// ext_session_lock_manager_v1.lock(new_id=2).
for word in [manager.id().protocol_id(), (12u32 << 16) | 1, 2] {
wire.write_all(&word.to_ne_bytes()).unwrap();
}
wire
};
let dispatch = |event_loop: &mut EventLoop<'static, Ferese>, state: &mut Ferese| {
event_loop.dispatch(Duration::from_millis(20), state).unwrap();
state.display_handle.flush_clients().unwrap();
};
let mut first = acquire(&mut state);
dispatch(&mut event_loop, &mut state);
assert!(
matches!(state.session_lock.lifecycle, Lifecycle::Locked(_)),
"no outputs must confirm immediately"
);
first.set_read_timeout(Some(Duration::from_secs(1))).unwrap();
let mut event = [0u8; 8];
first.read_exact(&mut event).unwrap();
assert_eq!(u32::from_ne_bytes(event[..4].try_into().unwrap()), 2);
assert_eq!(
u32::from_ne_bytes(event[4..].try_into().unwrap()),
8u32 << 16,
"protocol locked event"
);
let owner = state.session_lock.lifecycle.owner().cloned();
let mut competitor = acquire(&mut state);
dispatch(&mut event_loop, &mut state);
assert_eq!(
state.session_lock.lifecycle.owner(),
owner.as_ref(),
"a live owner cannot be replaced"
);
// A rejected client must not unlock the confirmed owner.
for word in [2u32, (8u32 << 16) | 2] {
competitor.write_all(&word.to_ne_bytes()).unwrap();
}
dispatch(&mut event_loop, &mut state);
assert_eq!(state.session_lock.lifecycle.owner(), owner.as_ref());
drop(competitor);
drop(first);
dispatch(&mut event_loop, &mut state);
assert!(matches!(state.session_lock.lifecycle, Lifecycle::Orphaned));
assert!(state.session_lock.active());
let replacement = acquire(&mut state);
dispatch(&mut event_loop, &mut state);
assert!(matches!(state.session_lock.lifecycle, Lifecycle::Locked(_)));
assert_ne!(state.session_lock.lifecycle.owner(), owner.as_ref());
drop(replacement);
dispatch(&mut event_loop, &mut state);
assert!(state.session_lock.active());
let output = Output::new(
"last-output".into(),
smithay::output::PhysicalProperties {
size: (0, 0).into(),
subpixel: smithay::output::Subpixel::Unknown,
make: "test".into(),
model: "test".into(),
},
);
output.change_current_state(
Some(smithay::output::Mode {
size: (800, 600).into(),
refresh: 60_000,
}),
None,
None,
None,
);
state.space.map_output(&output, (0, 0));
state.register_output(&output, "last-output".into());
let pending = acquire(&mut state);
dispatch(&mut event_loop, &mut state);
assert!(
matches!(state.session_lock.lifecycle, Lifecycle::Acquiring(_)),
"a newly added output requires a protected frame"
);
drop(pending);
dispatch(&mut event_loop, &mut state);
assert!(matches!(state.session_lock.lifecycle, Lifecycle::Orphaned));
let _replacement = acquire(&mut state);
dispatch(&mut event_loop, &mut state);
assert!(matches!(state.session_lock.lifecycle, Lifecycle::Acquiring(_)));
state.unregister_output(&output);
assert!(
matches!(state.session_lock.lifecycle, Lifecycle::Locked(_)),
"removing the last pending output must confirm"
);
}
#[test]
fn dim_fade_respects_speed_without_changing_idle_deadlines() {
let policy = IdleSettings::default();
@@ -463,30 +641,29 @@ mod tests {
},
);
let mut lock = Lock {
active: true,
lifecycle: Lifecycle::Orphaned,
..Lock::default()
};
assert!(!lock.ready_for_idle(std::iter::once(&output)));
lock.presented.insert(output.clone());
assert!(lock.ready_for_idle(std::iter::once(&output)));
assert!(!lock.confirmed);
assert!(matches!(lock.lifecycle, Lifecycle::Orphaned));
lock.output_added(&output);
assert!(!lock.ready_for_idle(std::iter::once(&output)));
assert!(!lock.confirmed);
assert!(matches!(lock.lifecycle, Lifecycle::Orphaned));
}
#[test]
fn input_wakes_a_sleeping_lock_without_unlocking_or_losing_confirmation() {
fn input_wakes_a_sleeping_orphan_without_unlocking() {
let mut lock = Lock {
active: true,
confirmed: true,
lifecycle: Lifecycle::Orphaned,
sleeping: true,
idle_opacity: 1.0,
..Lock::default()
};
let now = Instant::now();
assert!(lock.activity(now));
assert!(lock.active && lock.confirmed);
assert!(lock.active() && matches!(lock.lifecycle, Lifecycle::Orphaned));
assert!(!lock.sleeping);
assert_eq!(lock.idle_opacity, 0.0);
assert_eq!(lock.idle_since, Some(now));
+3 -3
View File
@@ -94,7 +94,7 @@ impl Dispatch<FereseShellV1, ()> for Ferese {
_display: &DisplayHandle,
_data_init: &mut DataInit<'_, Self>,
) {
if state.session_lock.active {
if state.session_lock.active() {
return;
}
match request {
@@ -207,7 +207,7 @@ impl Ferese {
}
pub(crate) fn toggle_keybinding_guide(&mut self) -> bool {
if self.session_lock.active {
if self.session_lock.active() {
return false;
}
let Some(shell) = self
@@ -225,7 +225,7 @@ impl Ferese {
}
pub(crate) fn request_logout_confirmation(&mut self) {
if self.session_lock.active {
if self.session_lock.active() {
return;
}
let shells = self
+3 -3
View File
@@ -28,7 +28,7 @@ impl Ferese {
}
pub(crate) fn focus_last_window(&mut self) {
if self.session_lock.active || self.input_capture.captures(1) {
if self.session_lock.active() || self.input_capture.captures(1) {
return;
}
self.cancel_focus_cycle();
@@ -43,7 +43,7 @@ impl Ferese {
}
pub(crate) fn cycle_focus(&mut self, reverse: bool, preview: bool) {
if self.session_lock.active || self.input_capture.captures(1) {
if self.session_lock.active() || self.input_capture.captures(1) {
return;
}
let available = self.focus_candidates();
@@ -85,7 +85,7 @@ impl Ferese {
let Some(mut cycle) = self.focus_cycle.take() else {
return;
};
if !self.session_lock.active
if !self.session_lock.active()
&& !self.input_capture.captures(1)
&& let Some(id) = cycle.reconcile(&self.focus_candidates())
{
+2 -2
View File
@@ -2,7 +2,7 @@ use super::*;
impl Ferese {
pub fn surface_under(&self, position: Point<f64, Logical>) -> Option<(WlSurface, Point<f64, Logical>)> {
if self.session_lock.active {
if self.session_lock.active() {
return self.lock_surface_under(position);
}
if self.input_capture.active() {
@@ -18,7 +18,7 @@ impl Ferese {
}
pub fn layer_under(&self, position: Point<f64, Logical>) -> Option<(LayerSurface, WlSurface, Point<f64, Logical>)> {
if self.session_lock.active {
if self.session_lock.active() {
return None;
}
if let Some(layer) = self.layer_surface_under(position, &[Layer::Overlay, Layer::Top]) {
+1 -1
View File
@@ -84,7 +84,7 @@ impl Ferese {
self.refresh_input_capture_zones();
let visible_workspaces = self.visible_workspace_ids();
self.reconcile_workspaces(&visible_workspaces);
if self.session_lock.active {
if self.session_lock.active() {
self.configure_lock_surfaces();
}
// Consume idle time before setting new targets; it must not become a
+2 -2
View File
@@ -7,7 +7,7 @@ impl Ferese {
pub(super) fn focus_swipe_is_current(&self, swipe: &FocusSwipe) -> bool {
self.animations_enabled
&& !self.session_lock.active
&& !self.session_lock.active()
&& !self.overview.is_presenting()
&& self.workspaces.active_id() == swipe.workspace
&& self.focused_window == Some(swipe.from)
@@ -552,7 +552,7 @@ impl Ferese {
if self.input_capture.captures(1) {
return;
}
if self.session_lock.active {
if self.session_lock.active() {
self.focus_lock_surface();
return;
}
+3 -1
View File
@@ -12,7 +12,7 @@ impl Ferese {
self.reset_animation_clock();
}
if self.session_lock.active {
if self.session_lock.active() {
self.session_lock.output_added(output);
self.lock_input_activity();
}
@@ -77,6 +77,8 @@ impl Ferese {
self.session_lock.surfaces.remove(output);
self.session_lock.backgrounds.remove(output);
self.session_lock.output_removed(output);
self.confirm_lock_if_ready();
self.refresh_lock_idle_policy();
match self.output_workspaces.disconnect(output_id) {
Ok(Some(target)) => {
+2 -2
View File
@@ -187,7 +187,7 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
state.display_presentation.presented(&output);
state.refresh_idle_inhibition();
if state.session_lock.active {
if state.session_lock.active() {
state.lock_frame_presented(&output);
}
let elapsed = render_started.elapsed();
@@ -226,7 +226,7 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
fn send_nested_frame_callbacks(state: &mut Ferese, output: &Output) {
let eligible = state.callback_outputs();
if state.session_lock.active {
if state.session_lock.active() {
state.lock_frame_callbacks(output);
return;
}
+5
View File
@@ -54,6 +54,11 @@ class NativeLockTest(unittest.TestCase):
result=capture(root/'after-crash.png')
assert result.returncode != 0, 'Capture unexpectedly succeeded after locker crash'
print('Capture remains denied after locker crash',flush=True)
locker=subprocess.Popen(['bwrap','--bind','/','/','--dev-bind','/dev','/dev','--ro-bind',str(pam),'/etc/pam.d','--unshare-user','--',str(binary_dir / 'ferese-lock')],env=childenv,stdout=subprocess.DEVNULL,stderr=locker_log,start_new_session=True)
assert locker.wait(timeout=20) == 0, (root/'locker.log').read_text()
result=capture(root/'replacement.png')
assert result.returncode != 0, 'Capture unexpectedly succeeded after locker takeover'
print('Replacement locker confirmed; capture remains denied',flush=True)
finally:
if locker is not None:
try: os.killpg(locker.pid, signal.SIGKILL)