Fix lock-screen input and wallpaper handling

This commit is contained in:
Samuel Onoja committed 2026-10-07 07:19:06 +01:00
1 parent bbc5b27c25
commit cfdd2a148a
7 files changed
+350 -57

No files matched your search

+28 -12
View File
@@ -120,18 +120,7 @@ impl Ferese {
}
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() {
keyboard.unset_grab(self);
}
if let Some(pointer) = seat.get_pointer() {
pointer.unset_grab(self, SERIAL_COUNTER.next_serial(), 0);
}
if let Some(touch) = seat.get_touch() {
touch.unset_grab(self);
}
self.focus_lock_surface();
self.prepare_lock_input();
}
if self.input_capture.active()
@@ -514,6 +503,33 @@ impl Ferese {
}
let pointer = self.seat.get_pointer().expect("seat has a pointer");
let serial = SERIAL_COUNTER.next_serial();
if self.session_lock.active() {
if event.state() == ButtonState::Pressed {
self.focus_window_at(pointer.current_location(), serial, true);
if !pointer.is_grabbed() {
pointer.motion(
self,
self.lock_surface_under(pointer.current_location()),
&MotionEvent {
location: pointer.current_location(),
serial,
time: event.time_msec(),
},
);
}
}
pointer.button(
self,
&ButtonEvent {
button: event.button_code(),
state: event.state(),
serial,
time: event.time_msec(),
},
);
pointer.frame(self);
return;
}
// Native popup_done destroys Iced's window immediately. For
// effects-capable shell popups, release input now but defer
+2
View File
@@ -2,6 +2,8 @@
//! logical pixels. Content, clips and decorations share those exact edges.
use std::time::{Duration, Instant};
use ferese_animation::AnimatedRect;
use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, RenderElementStates};
use smithay::backend::renderer::gles::{GlesError, GlesFrame, GlesRenderer, GlesTexProgram, GlesTexture, Uniform};
use smithay::backend::renderer::utils::{CommitCounter, DamageSet};
+56 -4
View File
@@ -273,6 +273,58 @@ impl Ferese {
})
}
pub(crate) fn prepare_lock_input(&mut self) {
// Reject desktop grabs installed after acquisition, but keep the lock
// owner's implicit click grab intact until its button release.
if let Some(keyboard) = self.seat.get_keyboard()
&& keyboard.is_grabbed()
{
keyboard.unset_grab(self);
}
if let Some(pointer) = self.seat.get_pointer() {
let lock_click = pointer
.with_grab(|_, grab| {
grab.is::<smithay::input::pointer::ClickGrab<Ferese>>()
&& grab.start_data().focus.as_ref().is_some_and(|(surface, _)| {
self.session_lock
.surfaces
.values()
.any(|lock| lock.alive() && lock.wl_surface() == surface)
})
})
.unwrap_or(false);
if pointer.is_grabbed() && !lock_click {
pointer.unset_grab(self, SERIAL_COUNTER.next_serial(), self.last_pointer_time);
}
let stale_focus = pointer.current_focus().is_some_and(|surface| {
!self
.session_lock
.surfaces
.values()
.any(|lock| lock.alive() && lock.wl_surface() == &surface)
});
if stale_focus {
let location = pointer.current_location();
pointer.motion(
self,
self.lock_surface_under(location),
&smithay::input::pointer::MotionEvent {
location,
serial: SERIAL_COUNTER.next_serial(),
time: self.last_pointer_time,
},
);
pointer.frame(self);
}
}
if let Some(touch) = self.seat.get_touch()
&& touch.is_grabbed()
{
touch.unset_grab(self);
}
self.focus_lock_surface();
}
pub(crate) fn focus_lock_surface(&mut self) {
let surface = self
.focused_output()
@@ -289,10 +341,10 @@ impl Ferese {
.map(|(_, surface)| surface)
})
.map(|surface| surface.wl_surface().clone());
self.seat
.get_keyboard()
.unwrap()
.set_focus(self, surface, SERIAL_COUNTER.next_serial());
let keyboard = self.seat.get_keyboard().unwrap();
if keyboard.current_focus() != surface {
keyboard.set_focus(self, surface, SERIAL_COUNTER.next_serial());
}
}
pub(crate) fn lock_frame_presented(&mut self, output: &Output) {
+63
View File
@@ -626,3 +626,66 @@ fn acquiring_owner_disconnect_keeps_lock_and_replacement_waits_for_frame() {
assert!(matches!(fixture.state.session_lock.lifecycle, Lifecycle::Locked(_)));
replacement.expect_locked();
}
#[test]
fn lock_input_preserves_owner_click_grabs_and_rejects_retired_surfaces() {
let Some(mut fixture) =
Fixture::new("session_lock::tests::lock_input_preserves_owner_click_grabs_and_rejects_retired_surfaces")
else {
return;
};
let output = fixture.output("lock-pointer");
let mut owner = fixture.acquire();
let surface = owner.surface(&mut fixture, &output);
let pointer = fixture.state.seat.get_pointer().unwrap();
let serial = SERIAL_COUNTER.next_serial();
pointer.motion(
&mut fixture.state,
Some((surface.wl_surface().clone(), (0.0, 0.0).into())),
&smithay::input::pointer::MotionEvent {
location: (10.0, 10.0).into(),
serial,
time: 1,
},
);
pointer.button(
&mut fixture.state,
&smithay::input::pointer::ButtonEvent {
button: 0x110,
state: smithay::backend::input::ButtonState::Pressed,
serial,
time: 2,
},
);
assert!(pointer.is_grabbed());
fixture.state.prepare_lock_input();
assert!(
pointer.is_grabbed(),
"the lock owner's click must survive until release"
);
pointer.button(
&mut fixture.state,
&smithay::input::pointer::ButtonEvent {
button: 0x110,
state: smithay::backend::input::ButtonState::Released,
serial: SERIAL_COUNTER.next_serial(),
time: 3,
},
);
assert!(!pointer.is_grabbed());
pointer.button(
&mut fixture.state,
&smithay::input::pointer::ButtonEvent {
button: 0x110,
state: smithay::backend::input::ButtonState::Pressed,
serial: SERIAL_COUNTER.next_serial(),
time: 4,
},
);
assert!(pointer.is_grabbed());
fixture.state.session_lock.surfaces.clear();
fixture.state.prepare_lock_input();
assert!(!pointer.is_grabbed(), "a retired surface cannot keep a lock grab");
assert_eq!(pointer.current_focus(), None);
assert_eq!(fixture.state.seat.get_keyboard().unwrap().current_focus(), None);
}
+18 -5
View File
@@ -1,5 +1,6 @@
//! One decoded image and one texture per GPU context, not double-buffered
//! full-screen UI surfaces per monitor. Resize changes sampling only.
//! One wallpaper texture per GPU context. Decoded pixels are staging data,
//! released after upload and decoded again when a new context needs them.
//! Resize changes sampling only.
use std::cell::Cell;
use std::collections::HashMap;
use std::path::PathBuf;
@@ -279,7 +280,7 @@ impl WallpaperState {
width = pixels.width(),
height = pixels.height(),
bytes = pixels.as_raw().len(),
"decoded compositor wallpaper once"
"decoded compositor wallpaper for texture upload"
);
self.pixels = Some(pixels);
@@ -540,6 +541,10 @@ mod tests {
fn decoder_wakes_an_idle_event_loop() {
use smithay::reexports::calloop::EventLoop;
use smithay::reexports::calloop::timer::Timer;
let directory = tempfile::tempdir().unwrap();
let path = directory.path().join("wallpaper.png");
let expected = image::RgbaImage::from_pixel(2, 2, image::Rgba([32, 80, 160, 255]));
expected.save(&path).unwrap();
let mut event_loop = EventLoop::<WallpaperState>::try_new().unwrap();
event_loop
.handle()
@@ -548,11 +553,19 @@ mod tests {
})
.unwrap();
let signal = event_loop.get_signal();
let mut state = WallpaperState::with_wakeup(WallpaperConfig::default(), Some(signal.clone()));
let mut state = WallpaperState::with_wakeup(
WallpaperConfig {
path: Some(path),
mode: WallpaperMode::Fill,
},
Some(signal.clone()),
);
event_loop
.run(None, &mut state, |state| {
if state.poll() {
assert!(state.pixels.is_some());
let decoded = state.pixels.as_ref().unwrap();
assert_eq!(decoded.dimensions(), expected.dimensions());
assert_eq!(&decoded.as_raw()[..], expected.as_raw());
signal.stop();
}
})