mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-08-23 22:56:41 +00:00
fix(linux): stop losing every inhibitor when the ScreenSaver name is absent (#15772)
On Linux, keeping the host awake during an incoming session asks keepawake for three things at once: the display through org.freedesktop.ScreenSaver on the session bus, and idle plus sleep through logind on the system bus. keepawake takes the ScreenSaver one FIRST and abandons the whole request if it fails, and WakeLock::new discarded the error with .ok(). So on any session where that name is missing, RustDesk silently holds NOTHING - not the display inhibit it could not take, and not the logind inhibits it never got to. On a host whose logind IdleAction is not the default, that means the machine can suspend in the middle of an active remote session, with any capture backend. The name is missing on a GNOME login screen. Measured on a GNOME/Wayland GDM greeter: org.freedesktop.ScreenSaver answers "was not provided by any .service files" and cannot be activated, while org.gnome.SessionManager is on the same bus and its idle inhibit works there. Same machine, same state: with it held the output was still lit at 129.9 s of idle, without it the compositor disabled the output after 30.3 s. Disabled, not blanked - an idle compositor releases the CRTC, so there is no scanout left for anything to read. So on the failure path, take both halves separately instead of neither: - ask keepawake again without the display part, which restores the logind idle/sleep inhibits that have nothing to do with the missing session name; - and get the display half from whichever session interface this desktop has, trying org.gnome.SessionManager and then org.freedesktop.PowerManagement. Only the failure path changes: a session where the ScreenSaver inhibit works is untouched. Where no session interface answers, the log now names every one that was tried and the error each returned, which is the whole diagnostic for a desktop nobody here can test on. Verified on a GNOME/Wayland greeter with a live client: the inhibit is taken 86 ms before anything else happens on the connection, and appears to gnome-session as "RustDesk: incoming session (idle)". The PowerManagement entry is NOT verified - it is the interface KDE and XFCE implement, it costs one extra failed call where it is absent, and the log is what will tell us whether it is the right one.
This commit is contained in:
+158
-9
@@ -1982,18 +1982,167 @@ mod desktop {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WakeLock(Option<keepawake::AwakeHandle>);
|
||||
/// A session-bus idle-inhibit interface, tried in order; the first that answers wins.
|
||||
/// `org.freedesktop.ScreenSaver` is absent on purpose: that is the one `keepawake` already tried.
|
||||
struct SessionInhibitTarget {
|
||||
dest: &'static str,
|
||||
path: &'static str,
|
||||
iface: &'static str,
|
||||
/// GNOME takes `(app_id, xid, reason, flags)`; PowerManagement takes `(app, reason)`.
|
||||
gnome_shape: bool,
|
||||
uninhibit: &'static str,
|
||||
}
|
||||
|
||||
/// `org.gnome.SessionManager.Inhibit` flag 8 = idle only; logout/switch-user/suspend would take
|
||||
/// away actions the person at the machine should keep.
|
||||
const GNOME_INHIBIT_IDLE: u32 = 8;
|
||||
|
||||
const SESSION_INHIBIT_TARGETS: &[SessionInhibitTarget] = &[
|
||||
// Measured on a GDM greeter: output held 129.9 s with the inhibit, 30.3 s without.
|
||||
SessionInhibitTarget {
|
||||
dest: "org.gnome.SessionManager",
|
||||
path: "/org/gnome/SessionManager",
|
||||
iface: "org.gnome.SessionManager",
|
||||
gnome_shape: true,
|
||||
uninhibit: "Uninhibit",
|
||||
},
|
||||
// What powerdevil and xfce4-power-manager implement. NOT tested here; costs one failed call
|
||||
// where absent, and the log below names every interface tried.
|
||||
SessionInhibitTarget {
|
||||
dest: "org.freedesktop.PowerManagement",
|
||||
path: "/org/freedesktop/PowerManagement/Inhibit",
|
||||
iface: "org.freedesktop.PowerManagement.Inhibit",
|
||||
gnome_shape: false,
|
||||
uninhibit: "UnInhibit",
|
||||
},
|
||||
];
|
||||
|
||||
/// Idle inhibit for the case `keepawake` cannot serve: it inhibits `org.freedesktop.ScreenSaver`,
|
||||
/// which a GDM greeter bus neither provides nor can activate, so its `create()` fails outright.
|
||||
/// The connection is kept because the inhibit is bound to it: dropping it releases the inhibit.
|
||||
struct SessionIdleInhibit {
|
||||
conn: dbus::blocking::Connection,
|
||||
target: &'static SessionInhibitTarget,
|
||||
cookie: u32,
|
||||
}
|
||||
|
||||
impl SessionIdleInhibit {
|
||||
fn new(reason: &str) -> Option<Self> {
|
||||
let conn = match dbus::blocking::Connection::new_session() {
|
||||
Ok(conn) => conn,
|
||||
Err(err) => {
|
||||
log::info!("wakelock: no session bus for the idle inhibit fallback ({err})");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let app = crate::get_app_name();
|
||||
let mut refused = Vec::new();
|
||||
for target in SESSION_INHIBIT_TARGETS {
|
||||
let res: Result<(u32,), dbus::Error> = {
|
||||
let proxy = conn.with_proxy(
|
||||
target.dest,
|
||||
target.path,
|
||||
std::time::Duration::from_secs(3),
|
||||
);
|
||||
if target.gnome_shape {
|
||||
// Inhibit(s app_id, u xid, s reason, u flags) -> u cookie; xid 0 = no window.
|
||||
proxy.method_call(
|
||||
target.iface,
|
||||
"Inhibit",
|
||||
(app.clone(), 0u32, reason.to_owned(), GNOME_INHIBIT_IDLE),
|
||||
)
|
||||
} else {
|
||||
proxy.method_call(target.iface, "Inhibit", (app.clone(), reason.to_owned()))
|
||||
}
|
||||
};
|
||||
match res {
|
||||
Ok((cookie,)) => {
|
||||
log::info!(
|
||||
"wakelock: holding a {} idle inhibit (cookie {cookie})",
|
||||
target.dest
|
||||
);
|
||||
return Some(Self {
|
||||
conn,
|
||||
target,
|
||||
cookie,
|
||||
});
|
||||
}
|
||||
Err(err) => refused.push(format!("{}: {err}", target.dest)),
|
||||
}
|
||||
}
|
||||
// Name every interface tried and why it failed: on an untested desktop this log is what
|
||||
// turns "the screen still blanks" into a report naming the missing interface.
|
||||
log::info!(
|
||||
"wakelock: no session idle inhibitor answered, so the compositor may still blank this \
|
||||
screen ({})",
|
||||
refused.join("; ")
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SessionIdleInhibit {
|
||||
fn drop(&mut self) {
|
||||
let proxy = self.conn.with_proxy(
|
||||
self.target.dest,
|
||||
self.target.path,
|
||||
std::time::Duration::from_secs(3),
|
||||
);
|
||||
// Best effort: the session manager ties the inhibit to the caller's bus name, so dropping
|
||||
// `conn` below releases it even if this call does not get through.
|
||||
let res: Result<(), dbus::Error> =
|
||||
proxy.method_call(self.target.iface, self.target.uninhibit, (self.cookie,));
|
||||
if let Err(err) = res {
|
||||
log::debug!("wakelock: releasing the idle inhibit by closing the bus instead ({err})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WakeLock(Option<keepawake::AwakeHandle>, Option<SessionIdleInhibit>);
|
||||
|
||||
impl WakeLock {
|
||||
pub fn new(display: bool, idle: bool, sleep: bool) -> Self {
|
||||
WakeLock(
|
||||
keepawake::Builder::new()
|
||||
.display(display)
|
||||
.idle(idle)
|
||||
.sleep(sleep)
|
||||
.create()
|
||||
.ok(),
|
||||
)
|
||||
match keepawake::Builder::new()
|
||||
.display(display)
|
||||
.idle(idle)
|
||||
.sleep(sleep)
|
||||
.create()
|
||||
{
|
||||
Ok(handle) => WakeLock(Some(handle), None),
|
||||
Err(err) => {
|
||||
// Not `.ok()`: a discarded error is how a login screen ran with no inhibitor at
|
||||
// all and nobody noticed.
|
||||
log::info!("wakelock: keepawake could not take the inhibit ({err})");
|
||||
// keepawake asks for the ScreenSaver inhibit first and abandons the whole request
|
||||
// if it fails, losing the logind idle/sleep inhibits that stop the HOST suspending
|
||||
// mid-session. Re-ask without the display part: those are on the system bus.
|
||||
let system = if idle || sleep {
|
||||
match keepawake::Builder::new()
|
||||
.display(false)
|
||||
.idle(idle)
|
||||
.sleep(sleep)
|
||||
.create()
|
||||
{
|
||||
Ok(handle) => Some(handle),
|
||||
Err(err) => {
|
||||
log::info!(
|
||||
"wakelock: the logind idle/sleep inhibit did not come back \
|
||||
either ({err})"
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let session = if display {
|
||||
SessionIdleInhibit::new("incoming session")
|
||||
} else {
|
||||
None
|
||||
};
|
||||
WakeLock(system, session)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user