Model lock ownership explicitly and allow protected crash recovery
This commit is contained in:
1 parent
570984a39b
commit
8ad5293178
22 files changed
+264
-80
No files matched your search
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
@@ -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));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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]) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)) => {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user