gnome-shell/js/misc/loginManager.js
Sebastian Keller 0e304100ab loginManager: Use signal argument to detect preparing for sleep
The DBus PreparingForSleep property on org.freedesktop.login1.Manager
does not trigger PropertyChanged signals, leading to wrong values due to
gdbus caching. In most cases it would have always been false.

Additionally it was not included in the XML interface description file
included in gnome-shell. So it was actually undefined.

Since this property is used in _calculateUserStateFromLogind() to
determine that a user is not active when closing the lid on a laptop,
the user was considered still active.

Fix this by storing the "start" argument from the PrepareForSleep signal
instead of trying to read from the property.

Fixes: 6a43b6f55 ("timeLimitsManager: Store screen time state on suspend/resume")
Closes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/8185
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3679>
2025-04-28 15:52:14 +00:00

310 lines
9.4 KiB
JavaScript

import GLib from 'gi://GLib';
import Gio from 'gi://Gio';
import GioUnix from 'gi://GioUnix';
import Shell from 'gi://Shell';
import * as Signals from './signals.js';
import {loadInterfaceXML} from './fileUtils.js';
const SystemdLoginManagerIface = loadInterfaceXML('org.freedesktop.login1.Manager');
const SystemdLoginSessionIface = loadInterfaceXML('org.freedesktop.login1.Session');
const SystemdLoginUserIface = loadInterfaceXML('org.freedesktop.login1.User');
const SystemdLoginManager = Gio.DBusProxy.makeProxyWrapper(SystemdLoginManagerIface);
const SystemdLoginSession = Gio.DBusProxy.makeProxyWrapper(SystemdLoginSessionIface);
const SystemdLoginUser = Gio.DBusProxy.makeProxyWrapper(SystemdLoginUserIface);
function haveSystemd() {
return GLib.access('/run/systemd/seats', 0) >= 0;
}
function versionCompare(required, reference) {
required = required.split('.');
reference = reference.split('.');
for (let i = 0; i < required.length; i++) {
let requiredInt = parseInt(required[i]);
let referenceInt = parseInt(reference[i]);
if (requiredInt !== referenceInt)
return requiredInt < referenceInt;
}
return true;
}
/**
* @returns {boolean}
*/
export function canLock() {
try {
let params = GLib.Variant.new('(ss)', ['org.gnome.DisplayManager.Manager', 'Version']);
let result = Gio.DBus.system.call_sync(
'org.gnome.DisplayManager',
'/org/gnome/DisplayManager/Manager',
'org.freedesktop.DBus.Properties',
'Get', params, null,
Gio.DBusCallFlags.NONE,
-1, null);
let version = result.deepUnpack()[0].deepUnpack();
return haveSystemd() && versionCompare('3.5.91', version);
} catch (e) {
return false;
}
}
export async function registerSessionWithGDM() {
log('Registering session with GDM');
try {
await Gio.DBus.system.call(
'org.gnome.DisplayManager',
'/org/gnome/DisplayManager/Manager',
'org.gnome.DisplayManager.Manager',
'RegisterSession',
GLib.Variant.new('(a{sv})', [{}]), null,
Gio.DBusCallFlags.NONE, -1, null);
} catch (e) {
if (!e.matches(Gio.DBusError, Gio.DBusError.UNKNOWN_METHOD))
log(`Error registering session with GDM: ${e.message}`);
else
log('Not calling RegisterSession(): method not exported, GDM too old?');
}
}
let _loginManager = null;
/**
* An abstraction over systemd/logind and ConsoleKit.
*
* @returns {LoginManagerSystemd | LoginManagerDummy} - the LoginManager singleton
*/
export function getLoginManager() {
if (_loginManager == null) {
if (haveSystemd())
_loginManager = new LoginManagerSystemd();
else
_loginManager = new LoginManagerDummy();
}
return _loginManager;
}
class LoginManagerSystemd extends Signals.EventEmitter {
constructor() {
super();
this._preparingForSleep = false;
this._proxy = new SystemdLoginManager(Gio.DBus.system,
'org.freedesktop.login1',
'/org/freedesktop/login1');
this._proxy.connectSignal('PrepareForSleep',
this._prepareForSleep.bind(this));
this._proxy.connectSignal('SessionRemoved',
this._sessionRemoved.bind(this));
}
async getCurrentUserProxy() {
if (this._userProxy)
return this._userProxy;
const uid = Shell.util_get_uid();
try {
const [objectPath] = await this._proxy.GetUserAsync(uid);
this._userProxy = await SystemdLoginUser.newAsync(
Gio.DBus.system, 'org.freedesktop.login1', objectPath);
return this._userProxy;
} catch (error) {
logError(error, `Could not get a proxy for user ${uid}`);
return null;
}
}
async getCurrentSessionProxy() {
if (this._currentSession)
return this._currentSession;
let sessionId = GLib.getenv('XDG_SESSION_ID');
if (!sessionId) {
log('Unset XDG_SESSION_ID, getCurrentSessionProxy() called outside a user session. Asking logind directly.');
const userProxy = await this.getCurrentUserProxy();
let [session, objectPath] = userProxy.Display;
if (session) {
log(`Will monitor session ${session}`);
sessionId = session;
} else {
log('Failed to find "Display" session; are we the greeter?');
for ([session, objectPath] of userProxy.Sessions) {
let sessionProxy = new SystemdLoginSession(Gio.DBus.system,
'org.freedesktop.login1',
objectPath);
log(`Considering ${session}, class=${sessionProxy.Class}`);
if (sessionProxy.Class === 'greeter') {
log(`Yes, will monitor session ${session}`);
sessionId = session;
break;
}
}
if (!sessionId) {
log('No, failed to get session from logind.');
return null;
}
}
}
try {
const [objectPath] = await this._proxy.GetSessionAsync(sessionId);
this._currentSession = await SystemdLoginSession.newAsync(
Gio.DBus.system, 'org.freedesktop.login1', objectPath);
return this._currentSession;
} catch (error) {
logError(error, 'Could not get a proxy for the current session');
return null;
}
}
async canSuspend() {
let canSuspend, needsAuth;
try {
const [result] = await this._proxy.CanSuspendAsync();
needsAuth = result === 'challenge';
canSuspend = needsAuth || result === 'yes';
} catch (error) {
canSuspend = false;
needsAuth = false;
}
return {canSuspend, needsAuth};
}
async canRebootToBootLoaderMenu() {
let canRebootToBootLoaderMenu, needsAuth;
try {
const [result] = await this._proxy.CanRebootToBootLoaderMenuAsync();
needsAuth = result === 'challenge';
canRebootToBootLoaderMenu = needsAuth || result === 'yes';
} catch (error) {
canRebootToBootLoaderMenu = false;
needsAuth = false;
}
return {canRebootToBootLoaderMenu, needsAuth};
}
setRebootToBootLoaderMenu() {
/* Parameter is timeout in usec, show to menu for 60 seconds */
this._proxy.SetRebootToBootLoaderMenuAsync(60000000);
}
async listSessions() {
try {
const [sessions] = await this._proxy.ListSessionsAsync();
return sessions;
} catch (e) {
return [];
}
}
getSession(objectPath) {
return new SystemdLoginSession(Gio.DBus.system, 'org.freedesktop.login1', objectPath);
}
suspend() {
this._proxy.SuspendAsync(true);
}
async inhibit(reason, cancellable) {
const inVariant = new GLib.Variant('(ssss)',
['sleep', 'GNOME Shell', reason, 'delay']);
const [outVariant_, fdList] =
await this._proxy.call_with_unix_fd_list('Inhibit',
inVariant, 0, -1, null, cancellable);
const [fd] = fdList.steal_fds();
return new GioUnix.InputStream({fd});
}
_prepareForSleep(proxy, sender, [aboutToSuspend]) {
this._preparingForSleep = aboutToSuspend;
this.emit('prepare-for-sleep', aboutToSuspend);
}
/**
* Whether the machine is preparing to sleep.
*
* This is true between paired emissions of `prepare-for-sleep`.
*
* @type {boolean}
*/
get preparingForSleep() {
return this._preparingForSleep;
}
_sessionRemoved(proxy, sender, [sessionId]) {
this.emit('session-removed', sessionId);
}
}
class LoginManagerDummy extends Signals.EventEmitter {
constructor() {
super();
this._preparingForSleep = false;
}
getCurrentUserProxy() {
// we could return a DummyUser object that fakes whatever callers
// expect, but just never settling the promise should be safer
return new Promise(() => {});
}
getCurrentSessionProxy() {
// we could return a DummySession object that fakes whatever callers
// expect (at the time of writing: connect() and connectSignal()
// methods), but just never settling the promise should be safer
return new Promise(() => {});
}
canSuspend() {
return new Promise(resolve => resolve({
canSuspend: false,
needsAuth: false,
}));
}
canRebootToBootLoaderMenu() {
return new Promise(resolve => resolve({
canRebootToBootLoaderMenu: false,
needsAuth: false,
}));
}
setRebootToBootLoaderMenu() {
}
listSessions() {
return new Promise(resolve => resolve([]));
}
getSession(_objectPath) {
return null;
}
suspend() {
this._preparingForSleep = true;
this.emit('prepare-for-sleep', true);
this._preparingForSleep = false;
this.emit('prepare-for-sleep', false);
}
get preparingForSleep() {
return this._preparingForSleep;
}
/* eslint-disable-next-line require-await */
async inhibit() {
return null;
}
}