Fix lock-screen input and wallpaper handling
This commit is contained in:
1 parent
bbc5b27c25
commit
cfdd2a148a
7 files changed
+350
-57
No files matched your search
+28
-12
@@ -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,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};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
})
|
||||
|
||||
Reference in new issue
Block a user