Use lock idle deadlines and stop polling after display sleep

This commit is contained in:
Samuel Onoja committed 2026-10-02 08:18:01 +01:00
1 parent 96b0a9d097
commit 79a968b0a6
3 files changed
+98 -33

No files matched your search

+35
View File
@@ -57,6 +57,7 @@ pub struct DirectBackendState {
input_devices: Vec<LibinputDevice>, input_devices: Vec<LibinputDevice>,
presentation: HashMap<(DrmNode, crtc::Handle), PresentationClock>, presentation: HashMap<(DrmNode, crtc::Handle), PresentationClock>,
pub(crate) connected_outputs: Vec<ConnectedOutputInfo>, pub(crate) connected_outputs: Vec<ConnectedOutputInfo>,
power_retry: Option<RegistrationToken>,
animation_timer: Option<RegistrationToken>, animation_timer: Option<RegistrationToken>,
animation_deadline: Option<Instant>, animation_deadline: Option<Instant>,
scheduling_metrics: SchedulingMetrics, scheduling_metrics: SchedulingMetrics,
@@ -236,6 +237,7 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
input_devices: Vec::new(), input_devices: Vec::new(),
presentation: HashMap::new(), presentation: HashMap::new(),
connected_outputs: Vec::new(), connected_outputs: Vec::new(),
power_retry: None,
animation_timer: None, animation_timer: None,
animation_deadline: None, animation_deadline: None,
scheduling_metrics: SchedulingMetrics::new(), scheduling_metrics: SchedulingMetrics::new(),
@@ -274,6 +276,9 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
let handle = state.loop_handle.clone(); let handle = state.loop_handle.clone();
if let Some(backend) = state.direct_backend.as_mut() { if let Some(backend) = state.direct_backend.as_mut() {
backend.active = false; backend.active = false;
if let Some(token) = backend.power_retry.take() {
handle.remove(token);
}
backend backend
.presentation .presentation
.values_mut() .values_mut()
@@ -702,6 +707,35 @@ fn apply_output_power(state: &mut Ferese) {
} }
} }
} }
let retry = backend.active
&& backend
.devices
.values()
.filter(|device| device.drm.is_active())
.any(|device| {
device
.outputs
.values()
.any(|output| output.power.needs_power_off() && !output.frame_pending)
});
if !retry {
if let Some(token) = backend.power_retry.take() {
state.loop_handle.remove(token);
}
} else if backend.power_retry.is_none() {
match state
.loop_handle
.insert_source(Timer::from_duration(Duration::from_secs(1)), |_, _, state| {
if let Some(backend) = state.direct_backend.as_mut() {
backend.power_retry = None;
}
apply_output_power(state);
TimeoutAction::Drop
}) {
Ok(token) => backend.power_retry = Some(token),
Err(error) => tracing::warn!(%error, "could not arm failed power-off retry"),
}
}
state.refresh_idle_inhibition(); state.refresh_idle_inhibition();
} }
@@ -717,6 +751,7 @@ pub(crate) fn wake_locked_outputs(state: &mut Ferese) {
} }
} }
} }
apply_output_power(state);
render_all(state); render_all(state);
} }
+62 -33
View File
@@ -44,14 +44,17 @@ impl IdleSettings {
Ok(self) Ok(self)
} }
fn next_poll(self, elapsed: Duration, fade_duration: Duration) -> Duration { fn next_deadline(self, elapsed: Duration, fade_duration: Duration) -> Option<Duration> {
if self.sleep_after_seconds != 0 && elapsed >= Duration::from_secs(self.sleep_after_seconds) {
return None;
}
let dim_at = Duration::from_secs(self.dim_after_seconds); let dim_at = Duration::from_secs(self.dim_after_seconds);
if !fade_duration.is_zero() if !fade_duration.is_zero()
&& self.dim_after_seconds != 0 && self.dim_after_seconds != 0
&& elapsed >= dim_at && elapsed >= dim_at
&& elapsed < dim_at + fade_duration && elapsed < dim_at + fade_duration
{ {
return Duration::from_millis(16); return Some(Duration::from_millis(16));
} }
[self.dim_after_seconds, self.sleep_after_seconds] [self.dim_after_seconds, self.sleep_after_seconds]
.into_iter() .into_iter()
@@ -59,8 +62,6 @@ impl IdleSettings {
.filter_map(|delay| Duration::from_secs(delay).checked_sub(elapsed)) .filter_map(|delay| Duration::from_secs(delay).checked_sub(elapsed))
.filter(|delay| !delay.is_zero()) .filter(|delay| !delay.is_zero())
.min() .min()
.unwrap_or(Duration::from_secs(1))
.min(Duration::from_secs(1))
} }
fn appearance(self, elapsed: Duration, fade_duration: Duration) -> (f32, bool) { fn appearance(self, elapsed: Duration, fade_duration: Duration) -> (f32, bool) {
@@ -125,6 +126,42 @@ impl Ferese {
crate::backends::direct::wake_locked_outputs(self); crate::backends::direct::wake_locked_outputs(self);
self.cursor_redraw_pending = true; self.cursor_redraw_pending = true;
} }
self.rearm_lock_idle();
}
pub(crate) fn refresh_lock_idle_policy(&mut self) {
self.update_lock_idle(Instant::now());
self.rearm_lock_idle();
}
fn rearm_lock_idle(&mut self) {
if let Some(token) = self.session_lock.idle_timer.take() {
self.loop_handle.remove(token);
}
if !self.session_lock.ready_for_idle(self.space.outputs()) {
return;
}
let elapsed = self
.session_lock
.idle_since
.map_or(Duration::ZERO, |since| since.elapsed());
let Some(delay) = self
.lock_idle
.next_deadline(elapsed, self.animation_duration(Duration::from_millis(500)))
else {
return;
};
match self
.loop_handle
.insert_source(Timer::from_duration(delay), |_, _, state| {
state.session_lock.idle_timer = None;
state.refresh_lock_idle_policy();
TimeoutAction::Drop
}) {
Ok(token) => self.session_lock.idle_timer = Some(token),
Err(error) => tracing::warn!(%error, "could not arm lock idle deadline"),
}
} }
fn update_lock_idle(&mut self, now: Instant) { fn update_lock_idle(&mut self, now: Instant) {
@@ -186,7 +223,7 @@ impl Ferese {
if !self.session_lock.active { if !self.session_lock.active {
return; return;
} }
self.session_lock.presented.insert(output.clone()); let newly_presented = self.session_lock.presented.insert(output.clone());
if self if self
.space .space
.outputs() .outputs()
@@ -198,6 +235,9 @@ impl Ferese {
self.session_lock.confirmed = true; self.session_lock.confirmed = true;
tracing::info!("session lock confirmed after safe output frames"); tracing::info!("session lock confirmed after safe output frames");
} }
if newly_presented {
self.refresh_lock_idle_policy();
}
} }
pub(crate) fn configure_lock_surfaces(&mut self) { pub(crate) fn configure_lock_surfaces(&mut self) {
@@ -239,26 +279,6 @@ impl SessionLockHandler for Ferese {
self.session_lock.active = true; self.session_lock.active = true;
self.refresh_idle_inhibition(); self.refresh_idle_inhibition();
self.session_lock.idle_since = Some(Instant::now()); self.session_lock.idle_since = Some(Instant::now());
match self
.loop_handle
.insert_source(Timer::from_duration(Duration::from_secs(1)), |_, _, state| {
if !state.session_lock.active {
return TimeoutAction::Drop;
}
state.update_lock_idle(Instant::now());
let elapsed = state
.session_lock
.idle_since
.map_or(Duration::ZERO, |since| since.elapsed());
TimeoutAction::ToDuration(
state
.lock_idle
.next_poll(elapsed, state.animation_duration(Duration::from_millis(500))),
)
}) {
Ok(token) => self.session_lock.idle_timer = Some(token),
Err(error) => tracing::warn!(%error, "could not schedule lock screen inactivity"),
}
self.input_capture.disable_all(); self.input_capture.disable_all();
self.portal_session.set_locked(true); self.portal_session.set_locked(true);
self.session_lock.owner = Some(confirmation.ext_session_lock().clone()); self.session_lock.owner = Some(confirmation.ext_session_lock().clone());
@@ -375,8 +395,8 @@ mod tests {
let halfway = policy.appearance(Duration::from_millis(half_at), fade); let halfway = policy.appearance(Duration::from_millis(half_at), fade);
assert!((halfway.0 - 0.325).abs() < 0.001 && !halfway.1); assert!((halfway.0 - 0.325).abs() < 0.001 && !halfway.1);
assert_eq!( assert_eq!(
policy.next_poll(Duration::from_millis(half_at), fade), policy.next_deadline(Duration::from_millis(half_at), fade),
Duration::from_millis(16) Some(Duration::from_millis(16))
); );
assert_eq!(policy.appearance(Duration::from_secs(120), fade), (1.0, true)); assert_eq!(policy.appearance(Duration::from_secs(120), fade), (1.0, true));
} }
@@ -409,16 +429,25 @@ mod tests {
(0.0, false) (0.0, false)
); );
assert_eq!( assert_eq!(
policy.next_poll(Duration::from_millis(29_998), Duration::from_millis(500)), policy.next_deadline(Duration::from_millis(29_998), Duration::from_millis(500)),
Duration::from_millis(2) Some(Duration::from_millis(2))
); );
assert_eq!( assert_eq!(
policy.next_poll(Duration::from_secs(30), Duration::from_millis(500)), policy.next_deadline(Duration::from_secs(30), Duration::from_millis(500)),
Duration::from_millis(16) Some(Duration::from_millis(16))
); );
assert_eq!( assert_eq!(
policy.next_poll(Duration::from_secs(30), Duration::ZERO), policy.next_deadline(Duration::from_secs(30), Duration::ZERO),
Duration::from_secs(1) Some(Duration::from_secs(90))
);
assert_eq!(policy.next_deadline(Duration::from_secs(120), Duration::ZERO), None);
assert_eq!(
IdleSettings {
dim_after_seconds: 0,
sleep_after_seconds: 0
}
.next_deadline(Duration::ZERO, Duration::ZERO),
None
); );
} }
+1
View File
@@ -699,6 +699,7 @@ impl Ferese {
} }
self.lock_idle = config.lock_idle; self.lock_idle = config.lock_idle;
self.refresh_lock_idle_policy();
self.advance_animations(Instant::now()); self.advance_animations(Instant::now());
let touchpad_changed = self.input_settings.touchpad != config.input_settings.touchpad; let touchpad_changed = self.input_settings.touchpad != config.input_settings.touchpad;