Refresh lid observations before rendering after resume
This commit is contained in:
1 parent
0420e59cbe
commit
428267a714
6 files changed
+407
-53
No files matched your search
@@ -6,6 +6,7 @@ use std::path::Path;
|
|||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
mod capture;
|
mod capture;
|
||||||
|
mod lid;
|
||||||
mod output_power;
|
mod output_power;
|
||||||
mod planes;
|
mod planes;
|
||||||
mod redraw;
|
mod redraw;
|
||||||
@@ -53,7 +54,8 @@ use crate::render::{frame_effect_metrics, sampled_output_elements};
|
|||||||
pub struct DirectBackendState {
|
pub struct DirectBackendState {
|
||||||
pub session: LibSeatSession,
|
pub session: LibSeatSession,
|
||||||
pub active: bool,
|
pub active: bool,
|
||||||
lid_closed: bool,
|
lid: lid::LidState,
|
||||||
|
lid_reader: lid::Reader,
|
||||||
topology: Topology,
|
topology: Topology,
|
||||||
pub(crate) low_power: bool,
|
pub(crate) low_power: bool,
|
||||||
devices: HashMap<DrmNode, DirectDevice>,
|
devices: HashMap<DrmNode, DirectDevice>,
|
||||||
@@ -69,7 +71,7 @@ pub struct DirectBackendState {
|
|||||||
|
|
||||||
impl DirectBackendState {
|
impl DirectBackendState {
|
||||||
fn can_render(&self) -> bool {
|
fn can_render(&self) -> bool {
|
||||||
self.active && !self.topology.reconciling
|
self.active && !self.topology.reconciling && !self.lid.pending()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn dump_scheduling_metrics(&self) {
|
pub(crate) fn dump_scheduling_metrics(&self) {
|
||||||
@@ -239,10 +241,12 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
|
|||||||
libinput_context.suspend();
|
libinput_context.suspend();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let lid_reader = lid::Reader::start(state)?;
|
||||||
state.direct_backend = Some(DirectBackendState {
|
state.direct_backend = Some(DirectBackendState {
|
||||||
session,
|
session,
|
||||||
active: session_active,
|
active: session_active,
|
||||||
lid_closed: initial_lid_closed(),
|
lid: lid::LidState::default(),
|
||||||
|
lid_reader,
|
||||||
topology: Topology::default(),
|
topology: Topology::default(),
|
||||||
low_power: super::power::low_power(false, super::power::battery_percent()),
|
low_power: super::power::low_power(false, super::power::battery_percent()),
|
||||||
devices: HashMap::new(),
|
devices: HashMap::new(),
|
||||||
@@ -263,8 +267,7 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
|
|||||||
.topology
|
.topology
|
||||||
.devices
|
.devices
|
||||||
.insert(primary_path, Default::default());
|
.insert(primary_path, Default::default());
|
||||||
reconcile_outputs(state, false);
|
lid::request_refresh(state, false);
|
||||||
sync_battery_timer(state);
|
|
||||||
|
|
||||||
event_loop
|
event_loop
|
||||||
.handle()
|
.handle()
|
||||||
@@ -291,6 +294,10 @@ 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;
|
||||||
|
backend.lid.pause();
|
||||||
|
if let Some(token) = backend.lid_reader.deadline.take() {
|
||||||
|
handle.remove(token);
|
||||||
|
}
|
||||||
backend.topology.dirty = true;
|
backend.topology.dirty = true;
|
||||||
if let Some(token) = backend.topology.retry_timer.take() {
|
if let Some(token) = backend.topology.retry_timer.take() {
|
||||||
handle.remove(token);
|
handle.remove(token);
|
||||||
@@ -336,12 +343,8 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
|
|||||||
|
|
||||||
let seat = state.seat.clone();
|
let seat = state.seat.clone();
|
||||||
state.idle_notifier_state.notify_activity(&seat);
|
state.idle_notifier_state.notify_activity(&seat);
|
||||||
reconcile_outputs(state, true);
|
lid::request_refresh(state, true);
|
||||||
sync_battery_timer(state);
|
tracing::info!("direct session reacquired; refreshing external state");
|
||||||
state.reset_animation_clock();
|
|
||||||
tracing::info!("direct session activated");
|
|
||||||
state.lock_input_activity();
|
|
||||||
render_all(state);
|
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
event_loop.handle().insert_source(udev_backend, |event, _, state| {
|
event_loop.handle().insert_source(udev_backend, |event, _, state| {
|
||||||
@@ -468,7 +471,7 @@ pub(crate) fn validate_live_outputs(state: &Ferese, profiles: &[OutputProfile])
|
|||||||
let mut usable = 0;
|
let mut usable = 0;
|
||||||
for device in backend.devices.values() {
|
for device in backend.devices.values() {
|
||||||
let mut scan = select_outputs(&device.drm, profiles, backend.low_power).map_err(|e| e.to_string())?;
|
let mut scan = select_outputs(&device.drm, profiles, backend.low_power).map_err(|e| e.to_string())?;
|
||||||
apply_lid_policy(&mut scan, backend.lid_closed);
|
apply_lid_policy(&mut scan, backend.lid.closed());
|
||||||
usable += scan.selections.len();
|
usable += scan.selections.len();
|
||||||
}
|
}
|
||||||
if usable == 0 {
|
if usable == 0 {
|
||||||
@@ -864,22 +867,19 @@ pub(crate) fn set_lid_closed(state: &mut Ferese, closed: bool) {
|
|||||||
let Some(backend) = state.direct_backend.as_mut() else {
|
let Some(backend) = state.direct_backend.as_mut() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if backend.lid_closed == closed {
|
// Even an unchanged switch observation supersedes an in-flight DBus read.
|
||||||
return;
|
if backend.lid.observe(closed) {
|
||||||
|
tracing::info!(closed, "laptop lid state changed");
|
||||||
|
reconcile_outputs(state, false);
|
||||||
}
|
}
|
||||||
backend.lid_closed = closed;
|
|
||||||
tracing::info!(closed, "laptop lid state changed");
|
|
||||||
reconcile_outputs(state, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initial_lid_closed() -> bool {
|
pub(crate) fn system_resumed(state: &mut Ferese) {
|
||||||
std::fs::read_dir("/proc/acpi/button/lid")
|
if state.direct_backend.as_ref().is_some_and(|backend| backend.active) {
|
||||||
.ok()
|
let seat = state.seat.clone();
|
||||||
.into_iter()
|
state.idle_notifier_state.notify_activity(&seat);
|
||||||
.flatten()
|
}
|
||||||
.filter_map(Result::ok)
|
lid::request_refresh(state, true);
|
||||||
.filter_map(|entry| std::fs::read_to_string(entry.path().join("state")).ok())
|
|
||||||
.any(|state| state.split_whitespace().last() == Some("closed"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn internal_connector(interface: connector::Interface) -> bool {
|
fn internal_connector(interface: connector::Interface) -> bool {
|
||||||
@@ -1122,7 +1122,10 @@ fn rescan_device(state: &mut Ferese, node: DrmNode) -> Result<bool, ()> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let lid_closed = state.direct_backend.as_ref().is_some_and(|backend| backend.lid_closed);
|
let lid_closed = state
|
||||||
|
.direct_backend
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|backend| backend.lid.closed());
|
||||||
apply_lid_policy(&mut scan, lid_closed);
|
apply_lid_policy(&mut scan, lid_closed);
|
||||||
device.connected_outputs = scan.connected_outputs;
|
device.connected_outputs = scan.connected_outputs;
|
||||||
let mut complete = true;
|
let mut complete = true;
|
||||||
@@ -1562,6 +1565,7 @@ fn deliver_frame_callbacks(state: &mut Ferese, node: DrmNode, crtc: crtc::Handle
|
|||||||
output.callback_timer = None;
|
output.callback_timer = None;
|
||||||
if !backend.active
|
if !backend.active
|
||||||
|| backend.topology.reconciling
|
|| backend.topology.reconciling
|
||||||
|
|| backend.lid.pending()
|
||||||
|| !device.drm.is_active()
|
|| !device.drm.is_active()
|
||||||
|| !output.power.can_render()
|
|| !output.power.can_render()
|
||||||
|| state.session_lock.sleeping
|
|| state.session_lock.sleeping
|
||||||
|
|||||||
@@ -0,0 +1,304 @@
|
|||||||
|
//! Resume reads are asynchronous; newer switch events win over old replies.
|
||||||
|
use std::sync::mpsc;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub(super) struct Probe {
|
||||||
|
serial: u64,
|
||||||
|
revision: u64,
|
||||||
|
reactivate: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub(super) struct LidState {
|
||||||
|
value: Option<bool>,
|
||||||
|
revision: u64,
|
||||||
|
serial: u64,
|
||||||
|
pending: Option<Probe>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LidState {
|
||||||
|
pub fn closed(&self) -> bool {
|
||||||
|
self.value == Some(true)
|
||||||
|
}
|
||||||
|
pub fn pending(&self) -> bool {
|
||||||
|
self.pending.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn observe(&mut self, closed: bool) -> bool {
|
||||||
|
let changed = self.value != Some(closed);
|
||||||
|
self.value = Some(closed);
|
||||||
|
self.revision = self.revision.wrapping_add(1);
|
||||||
|
changed
|
||||||
|
}
|
||||||
|
|
||||||
|
fn begin(&mut self, reactivate: bool) -> Probe {
|
||||||
|
self.serial = self.serial.wrapping_add(1);
|
||||||
|
let probe = Probe {
|
||||||
|
serial: self.serial,
|
||||||
|
revision: self.revision,
|
||||||
|
reactivate: reactivate || self.pending.is_some_and(|pending| pending.reactivate),
|
||||||
|
};
|
||||||
|
self.pending = Some(probe);
|
||||||
|
probe
|
||||||
|
}
|
||||||
|
|
||||||
|
fn complete(&mut self, probe: Probe, value: Option<bool>) -> Option<bool> {
|
||||||
|
if self.pending != Some(probe) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.pending = None;
|
||||||
|
if probe.revision == self.revision
|
||||||
|
&& let Some(value) = value
|
||||||
|
{
|
||||||
|
self.observe(value);
|
||||||
|
}
|
||||||
|
Some(probe.reactivate)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pause(&mut self) {
|
||||||
|
self.pending = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct Reader {
|
||||||
|
requests: mpsc::Sender<Probe>,
|
||||||
|
pub deadline: Option<RegistrationToken>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_logind(connection: &zbus::blocking::Connection) -> zbus::Result<bool> {
|
||||||
|
// A direct Get avoids retaining a property cache across suspend.
|
||||||
|
let value: zbus::zvariant::OwnedValue = connection
|
||||||
|
.call_method(
|
||||||
|
Some("org.freedesktop.login1"),
|
||||||
|
"/org/freedesktop/login1",
|
||||||
|
Some("org.freedesktop.DBus.Properties"),
|
||||||
|
"Get",
|
||||||
|
&("org.freedesktop.login1.Manager", "LidClosed"),
|
||||||
|
)?
|
||||||
|
.body()
|
||||||
|
.deserialize()?;
|
||||||
|
bool::try_from(value).map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_acpi() -> Option<bool> {
|
||||||
|
let mut state = None;
|
||||||
|
for entry in std::fs::read_dir("/proc/acpi/button/lid").ok()?.flatten() {
|
||||||
|
let Ok(text) = std::fs::read_to_string(entry.path().join("state")) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
match text.split_whitespace().last() {
|
||||||
|
Some("closed") => return Some(true),
|
||||||
|
Some("open") => state = Some(false),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Reader {
|
||||||
|
pub fn start(state: &Ferese) -> Result<Self, Box<dyn Error>> {
|
||||||
|
let (requests, receiver) = mpsc::channel::<Probe>();
|
||||||
|
let (replies, events) = smithay::reexports::calloop::channel::channel();
|
||||||
|
state.loop_handle.insert_source(events, |event, _, state| {
|
||||||
|
if let smithay::reexports::calloop::channel::Event::Msg((probe, value)) = event {
|
||||||
|
complete_refresh(state, probe, value);
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
std::thread::Builder::new()
|
||||||
|
.name("ferese-lid-reader".into())
|
||||||
|
.spawn(move || {
|
||||||
|
while let Ok(mut probe) = receiver.recv() {
|
||||||
|
for newer in receiver.try_iter() {
|
||||||
|
probe = newer;
|
||||||
|
}
|
||||||
|
let value = zbus::blocking::connection::Builder::system()
|
||||||
|
.and_then(|builder| builder.method_timeout(Duration::from_secs(2)).build())
|
||||||
|
.and_then(|connection| read_logind(&connection))
|
||||||
|
.ok()
|
||||||
|
.or_else(read_acpi);
|
||||||
|
if replies.send((probe, value)).is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
requests,
|
||||||
|
deadline: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn request_refresh(state: &mut Ferese, reactivate: bool) {
|
||||||
|
let Some(backend) = state.direct_backend.as_mut() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if !backend.active {
|
||||||
|
backend.topology.dirty = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(token) = backend.lid_reader.deadline.take() {
|
||||||
|
state.loop_handle.remove(token);
|
||||||
|
}
|
||||||
|
// A timer that fires while readiness is gated would otherwise leave a
|
||||||
|
// stale registration token on an unchanged output.
|
||||||
|
for device in backend.devices.values_mut() {
|
||||||
|
for output in device.outputs.values_mut() {
|
||||||
|
cancel_output_timers(&state.loop_handle, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let probe = backend.lid.begin(reactivate);
|
||||||
|
let sent = backend.lid_reader.requests.send(probe).is_ok();
|
||||||
|
let timer = state
|
||||||
|
.loop_handle
|
||||||
|
.insert_source(Timer::from_duration(Duration::from_secs(3)), move |_, _, state| {
|
||||||
|
if let Some(backend) = state.direct_backend.as_mut() {
|
||||||
|
backend.lid_reader.deadline = None;
|
||||||
|
}
|
||||||
|
tracing::warn!("lid refresh deadline elapsed; retaining the latest observed state");
|
||||||
|
complete_refresh(state, probe, None);
|
||||||
|
TimeoutAction::Drop
|
||||||
|
});
|
||||||
|
|
||||||
|
if sent && let Ok(token) = timer {
|
||||||
|
state.direct_backend.as_mut().unwrap().lid_reader.deadline = Some(token);
|
||||||
|
} else {
|
||||||
|
if let Ok(token) = timer {
|
||||||
|
state.loop_handle.remove(token);
|
||||||
|
}
|
||||||
|
complete_refresh(state, probe, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn complete_refresh(state: &mut Ferese, probe: Probe, value: Option<bool>) {
|
||||||
|
let Some(backend) = state.direct_backend.as_mut() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(reactivate) = backend.lid.complete(probe, value) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if let Some(token) = backend.lid_reader.deadline.take() {
|
||||||
|
state.loop_handle.remove(token);
|
||||||
|
}
|
||||||
|
if !backend.active {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Waking a locked session may request redraws. Hold them until the fresh
|
||||||
|
// device/connector snapshot has been reconciled.
|
||||||
|
backend.topology.reconciling = true;
|
||||||
|
state.reset_animation_clock();
|
||||||
|
state.lock_input_activity();
|
||||||
|
state.direct_backend.as_mut().unwrap().topology.reconciling = false;
|
||||||
|
reconcile_outputs(state, reactivate);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore = "requires dbus-daemon and private test sockets"]
|
||||||
|
fn logind_reads_are_fresh_without_property_change_signals() {
|
||||||
|
use std::io::{BufRead, BufReader};
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
|
struct Bus(std::process::Child);
|
||||||
|
impl Drop for Bus {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let _ = self.0.kill();
|
||||||
|
let _ = self.0.wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
struct Manager(Arc<AtomicBool>);
|
||||||
|
#[zbus::interface(name = "org.freedesktop.login1.Manager")]
|
||||||
|
impl Manager {
|
||||||
|
#[zbus(property)]
|
||||||
|
fn lid_closed(&self) -> bool {
|
||||||
|
self.0.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut bus = Bus(Command::new("dbus-daemon")
|
||||||
|
.args(["--session", "--nofork", "--print-address=1"])
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.unwrap());
|
||||||
|
let mut address = String::new();
|
||||||
|
BufReader::new(bus.0.stdout.take().unwrap())
|
||||||
|
.read_line(&mut address)
|
||||||
|
.unwrap();
|
||||||
|
let closed = Arc::new(AtomicBool::new(true));
|
||||||
|
let server = zbus::blocking::connection::Builder::address(address.trim())
|
||||||
|
.unwrap()
|
||||||
|
.name("org.freedesktop.login1")
|
||||||
|
.unwrap()
|
||||||
|
.serve_at("/org/freedesktop/login1", Manager(closed.clone()))
|
||||||
|
.unwrap()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
let client = zbus::blocking::connection::Builder::address(address.trim())
|
||||||
|
.unwrap()
|
||||||
|
.method_timeout(Duration::from_secs(1))
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
assert!(read_logind(&client).unwrap());
|
||||||
|
closed.store(false, Ordering::Relaxed);
|
||||||
|
assert!(!read_logind(&client).unwrap());
|
||||||
|
server.release_name("org.freedesktop.login1").unwrap();
|
||||||
|
assert!(
|
||||||
|
read_logind(&client).is_err(),
|
||||||
|
"missing logind must not masquerade as an open lid"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn newer_input_wins_over_a_delayed_resume_read() {
|
||||||
|
let mut lid = LidState::default();
|
||||||
|
lid.observe(true);
|
||||||
|
let probe = lid.begin(true);
|
||||||
|
lid.observe(false);
|
||||||
|
assert_eq!(lid.complete(probe, Some(true)), Some(true));
|
||||||
|
assert!(!lid.closed());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn failed_reads_preserve_known_state_and_unknown_is_not_an_open_observation() {
|
||||||
|
let mut lid = LidState::default();
|
||||||
|
let probe = lid.begin(false);
|
||||||
|
lid.complete(probe, None);
|
||||||
|
assert_eq!(lid.value, None);
|
||||||
|
lid.observe(true);
|
||||||
|
let probe = lid.begin(true);
|
||||||
|
lid.complete(probe, None);
|
||||||
|
assert_eq!(lid.value, Some(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pause_and_newer_resume_invalidate_old_replies() {
|
||||||
|
let mut lid = LidState::default();
|
||||||
|
let old = lid.begin(true);
|
||||||
|
lid.pause();
|
||||||
|
assert_eq!(lid.complete(old, Some(true)), None);
|
||||||
|
let current = lid.begin(true);
|
||||||
|
assert_eq!(lid.complete(old, Some(true)), None);
|
||||||
|
assert!(lid.pending());
|
||||||
|
assert_eq!(lid.complete(current, Some(false)), Some(true));
|
||||||
|
assert!(!lid.pending());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn system_wake_cannot_cancel_pending_session_reactivation() {
|
||||||
|
let mut lid = LidState::default();
|
||||||
|
lid.begin(true);
|
||||||
|
let probe = lid.begin(false);
|
||||||
|
assert_eq!(lid.complete(probe, Some(false)), Some(true));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -114,12 +114,15 @@ pub(super) fn reconcile_outputs(state: &mut Ferese, reactivate: bool) {
|
|||||||
let Some(backend) = state.direct_backend.as_mut() else {
|
let Some(backend) = state.direct_backend.as_mut() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if !backend.topology.request(backend.active) {
|
if !backend.topology.request(backend.active && !backend.lid.pending()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
backend.topology.reconciling = true;
|
backend.topology.reconciling = true;
|
||||||
backend.topology.dirty = false;
|
backend.topology.dirty = false;
|
||||||
|
if reactivate {
|
||||||
|
backend.presentation.values_mut().for_each(PresentationClock::reset_timing);
|
||||||
|
}
|
||||||
if let Some(token) = backend.topology.retry_timer.take() {
|
if let Some(token) = backend.topology.retry_timer.take() {
|
||||||
state.loop_handle.remove(token);
|
state.loop_handle.remove(token);
|
||||||
}
|
}
|
||||||
@@ -198,6 +201,15 @@ pub(super) fn reconcile_outputs(state: &mut Ferese, reactivate: bool) {
|
|||||||
let device = state.direct_backend.as_mut().unwrap().devices.get_mut(&node).unwrap();
|
let device = state.direct_backend.as_mut().unwrap().devices.get_mut(&node).unwrap();
|
||||||
device.drm.activate(true).map(|()| {
|
device.drm.activate(true).map(|()| {
|
||||||
for (crtc, output) in &mut device.outputs {
|
for (crtc, output) in &mut device.outputs {
|
||||||
|
cancel_output_timers(&state.loop_handle, output);
|
||||||
|
output.surface.reset_buffers();
|
||||||
|
output.primary_commit = None;
|
||||||
|
output.capture_texture = None;
|
||||||
|
output.frame_pending = false;
|
||||||
|
output.lock_frame_pending = false;
|
||||||
|
output.power.suspended();
|
||||||
|
state.display_presentation.remove_output(&output.output);
|
||||||
|
|
||||||
if let Err(error) = output.surface.clear().and_then(|()| output.surface.reset_state()) {
|
if let Err(error) = output.surface.clear().and_then(|()| output.surface.reset_state()) {
|
||||||
tracing::warn!(%error, ?node, ?crtc, "output reset failed; recreating output");
|
tracing::warn!(%error, ?node, ?crtc, "output reset failed; recreating output");
|
||||||
failed_outputs.push(*crtc);
|
failed_outputs.push(*crtc);
|
||||||
@@ -291,6 +303,7 @@ pub(super) fn reconcile_outputs(state: &mut Ferese, reactivate: bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.refresh_idle_inhibition();
|
||||||
sync_battery_timer(state);
|
sync_battery_timer(state);
|
||||||
state.restore_output_focus();
|
state.restore_output_focus();
|
||||||
state.relayout();
|
state.relayout();
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ mod render;
|
|||||||
#[cfg(feature = "resize-metrics")]
|
#[cfg(feature = "resize-metrics")]
|
||||||
mod resize_metrics;
|
mod resize_metrics;
|
||||||
mod resize_transaction;
|
mod resize_transaction;
|
||||||
|
mod resume;
|
||||||
mod session_lock;
|
mod session_lock;
|
||||||
mod shell_control;
|
mod shell_control;
|
||||||
mod stacking;
|
mod stacking;
|
||||||
@@ -93,6 +94,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
let mut state = Ferese::new(&mut event_loop, display, runtime)?;
|
let mut state = Ferese::new(&mut event_loop, display, runtime)?;
|
||||||
state.config_source = initial_source.filter(|source| source.len() <= 60 * 1024);
|
state.config_source = initial_source.filter(|source| source.len() <= 60 * 1024);
|
||||||
theme::init(&mut event_loop, &mut state, candidate)?;
|
theme::init(&mut event_loop, &mut state, candidate)?;
|
||||||
|
resume::init(&mut event_loop)?;
|
||||||
overview::init_font_loader(&mut event_loop, &mut state)?;
|
overview::init_font_loader(&mut event_loop, &mut state)?;
|
||||||
backends::init(launch.backend, &mut event_loop, &mut state)?;
|
backends::init(launch.backend, &mut event_loop, &mut state)?;
|
||||||
info!(socket = ?state.socket_name, backend = ?launch.backend, "Ferese is accepting Wayland clients");
|
info!(socket = ?state.socket_name, backend = ?launch.backend, "Ferese is accepting Wayland clients");
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
//! System wake is shared by hardware reconciliation and time-dependent UI state.
|
||||||
|
use std::error::Error;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use calloop::EventLoop;
|
||||||
|
use calloop::channel::{Event, channel};
|
||||||
|
|
||||||
|
use crate::Ferese;
|
||||||
|
|
||||||
|
pub(crate) fn init(event_loop: &mut EventLoop<Ferese>) -> Result<(), Box<dyn Error>> {
|
||||||
|
let (sender, receiver) = channel();
|
||||||
|
event_loop.handle().insert_source(receiver, |event, _, state| {
|
||||||
|
if let Event::Msg(()) = event {
|
||||||
|
crate::backends::direct::system_resumed(state);
|
||||||
|
state.refresh_theme();
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
std::thread::Builder::new()
|
||||||
|
.name("ferese-system-resume".into())
|
||||||
|
.spawn(move || {
|
||||||
|
let mut warned = false;
|
||||||
|
loop {
|
||||||
|
let monitor = || -> zbus::Result<bool> {
|
||||||
|
let connection = zbus::blocking::connection::Builder::system()?
|
||||||
|
.method_timeout(Duration::from_secs(2))
|
||||||
|
.build()?;
|
||||||
|
let proxy = zbus::blocking::Proxy::new(
|
||||||
|
&connection,
|
||||||
|
"org.freedesktop.login1",
|
||||||
|
"/org/freedesktop/login1",
|
||||||
|
"org.freedesktop.login1.Manager",
|
||||||
|
)?;
|
||||||
|
for message in proxy.receive_signal("PrepareForSleep")? {
|
||||||
|
let (sleeping,): (bool,) = message.body().deserialize()?;
|
||||||
|
if !sleeping && sender.send(()).is_err() {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(true)
|
||||||
|
};
|
||||||
|
|
||||||
|
match monitor() {
|
||||||
|
Ok(false) => break,
|
||||||
|
Ok(true) => warned = false,
|
||||||
|
Err(error) if !warned => {
|
||||||
|
tracing::warn!(%error, "system resume monitor unavailable; retrying connection");
|
||||||
|
warned = true;
|
||||||
|
}
|
||||||
|
Err(_) => {}
|
||||||
|
}
|
||||||
|
// Recovery only: a healthy listener blocks on DBus signals.
|
||||||
|
std::thread::sleep(Duration::from_secs(5));
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -265,32 +265,6 @@ pub(crate) fn init(
|
|||||||
}
|
}
|
||||||
paths.lock().unwrap().insert(PathBuf::from("/etc/localtime"));
|
paths.lock().unwrap().insert(PathBuf::from("/etc/localtime"));
|
||||||
let wakeup = state.loop_signal.clone();
|
let wakeup = state.loop_signal.clone();
|
||||||
let resume_sender = sender.clone();
|
|
||||||
let resume_wakeup = wakeup.clone();
|
|
||||||
std::thread::Builder::new()
|
|
||||||
.name("ferese-theme-resume".into())
|
|
||||||
.spawn(move || {
|
|
||||||
let monitor = || -> zbus::Result<()> {
|
|
||||||
let connection = zbus::blocking::Connection::system()?;
|
|
||||||
let proxy = zbus::blocking::Proxy::new(
|
|
||||||
&connection,
|
|
||||||
"org.freedesktop.login1",
|
|
||||||
"/org/freedesktop/login1",
|
|
||||||
"org.freedesktop.login1.Manager",
|
|
||||||
)?;
|
|
||||||
for message in proxy.receive_signal("PrepareForSleep")? {
|
|
||||||
let (sleeping,): (bool,) = message.body().deserialize()?;
|
|
||||||
if !sleeping {
|
|
||||||
let _ = resume_sender.try_send(());
|
|
||||||
resume_wakeup.wakeup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
};
|
|
||||||
if let Err(error) = monitor() {
|
|
||||||
tracing::warn!(%error, "theme resume monitor unavailable");
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
let system_sender = sender.clone();
|
let system_sender = sender.clone();
|
||||||
let system_wakeup = wakeup.clone();
|
let system_wakeup = wakeup.clone();
|
||||||
std::thread::Builder::new()
|
std::thread::Builder::new()
|
||||||
|
|||||||
Reference in new issue
Block a user