File gnome-shell-grd-ask-logout-for-existing-session.patch of Package gnome-shell
From 3517749537ba322b174bc6167b4dea861f1f47d9 Mon Sep 17 00:00:00 2001
From: Joan Torres <joan.torres@suse.com>
Date: Wed, 7 Feb 2024 12:59:09 +0100
Subject: [PATCH 1/4] loginManager: Add session-removed signal and getSession
method
These changes will be used by the next commit when displaying a
conflicting session dialog.
session-removed signal will be used to close the conflicting session dialog
if it's not needed anymore.
getSession method will be used when a session is opened, to check if
there's already a conflicting opened session.
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3134>
---
js/misc/loginManager.js | 14 ++++++++++++++
1 file changed, 14 insertions(+)
Index: gnome-shell-45.3/js/misc/loginManager.js
===================================================================
--- gnome-shell-45.3.orig/js/misc/loginManager.js
+++ gnome-shell-45.3/js/misc/loginManager.js
@@ -101,6 +101,8 @@ class LoginManagerSystemd extends Signal
'/org/freedesktop/login1/user/self');
this._proxy.connectSignal('PrepareForSleep',
this._prepareForSleep.bind(this));
+ this._proxy.connectSignal('SessionRemoved',
+ this._sessionRemoved.bind(this));
}
async getCurrentSessionProxy() {
@@ -189,6 +191,10 @@ class LoginManagerSystemd extends Signal
}
}
+ getSession(objectPath) {
+ return new SystemdLoginSession(Gio.DBus.system, 'org.freedesktop.login1', objectPath);
+ }
+
suspend() {
this._proxy.SuspendAsync(true);
}
@@ -206,6 +212,10 @@ class LoginManagerSystemd extends Signal
_prepareForSleep(proxy, sender, [aboutToSuspend]) {
this.emit('prepare-for-sleep', aboutToSuspend);
}
+
+ _sessionRemoved(proxy, sender, [sessionId]) {
+ this.emit('session-removed', sessionId);
+ }
}
class LoginManagerDummy extends Signals.EventEmitter {
@@ -237,6 +247,10 @@ class LoginManagerDummy extends Signals.
return new Promise(resolve => resolve([]));
}
+ getSession(_objectPath) {
+ return null;
+ }
+
suspend() {
this.emit('prepare-for-sleep', true);
this.emit('prepare-for-sleep', false);
Index: gnome-shell-45.3/data/theme/gnome-shell-sass/widgets/_login-lock.scss
===================================================================
--- gnome-shell-45.3.orig/data/theme/gnome-shell-sass/widgets/_login-lock.scss
+++ gnome-shell-45.3/data/theme/gnome-shell-sass/widgets/_login-lock.scss
@@ -37,6 +37,25 @@ $_gdm_dialog_width: 23em;
spacing: $base_padding*2;
}
+.conflicting-session-dialog-content {
+ spacing: 20px;
+
+ .conflicting-session-dialog-title {
+ text-align: center;
+ @extend %title_2;
+ margin-bottom: 5px;
+ }
+
+ .conflicting-session-dialog-desc {
+ text-align: center;
+ }
+
+ .conflicting-session-dialog-desc-warning {
+ text-align: center;
+ color: $warning_color;
+ }
+}
+
.login-dialog-logo-bin {
margin:3em 0;
}
Index: gnome-shell-45.3/js/gdm/loginDialog.js
===================================================================
--- gnome-shell-45.3.orig/js/gdm/loginDialog.js
+++ gnome-shell-45.3/js/gdm/loginDialog.js
@@ -36,6 +36,8 @@ import * as GdmUtil from './util.js';
import * as Layout from '../ui/layout.js';
import * as LoginManager from '../misc/loginManager.js';
import * as Main from '../ui/main.js';
+import * as MessageTray from '../ui/messageTray.js';
+import * as ModalDialog from '../ui/modalDialog.js';
import * as PopupMenu from '../ui/popupMenu.js';
import * as Realmd from './realmd.js';
import * as UserWidget from '../ui/userWidget.js';
@@ -43,6 +45,7 @@ import * as UserWidget from '../ui/userW
const _FADE_ANIMATION_TIME = 250;
const _SCROLL_ANIMATION_TIME = 500;
const _TIMED_LOGIN_IDLE_THRESHOLD = 5.0;
+const _CONFLICTING_SESSION_DIALOG_TIMEOUT = 60;
export const UserListItem = GObject.registerClass({
Signals: {'activate': {}},
@@ -408,6 +411,76 @@ const SessionMenuButton = GObject.regist
}
});
+export const ConflictingSessionDialog = GObject.registerClass({
+ Signals: {
+ 'cancel': {},
+ 'force-stop': {},
+ },
+}, class ConflictingSessionDialog extends ModalDialog.ModalDialog {
+ _init(conflictingSession, greeterSession, userName) {
+ super._init();
+
+ let bannerText;
+ if (greeterSession.Remote && conflictingSession.Remote)
+ /* Translators: is running for <username> */
+ bannerText = _('Remote login is not possible because a remote session is already running for %s. To login remotely, you must log out from the remote session or force stop it.').format(userName);
+ else if (!greeterSession.Remote && conflictingSession.Remote)
+ /* Translators: is running for <username> */
+ bannerText = _('Login is not possible because a remote session is already running for %s. To login, you must log out from the remote session or force stop it.').format(userName);
+ else if (greeterSession.Remote && !conflictingSession.Remote)
+ /* Translators: is running for <username> */
+ bannerText = _('Remote login is not possible because a local session is already running for %s. To login remotely, you must log out from the local session or force stop it.').format(userName);
+ else
+ /* Translators: is running for <username> */
+ bannerText = _('Login is not possible because a session is already running for %s. To login, you must log out from the session or force stop it.').format(userName);
+
+ let textLayout = new St.BoxLayout({
+ style_class: 'conflicting-session-dialog-content',
+ vertical: true,
+ x_expand: true,
+ });
+
+ let title = new St.Label({
+ text: _('Session Already Running'),
+ style_class: 'conflicting-session-dialog-title',
+ });
+
+ let banner = new St.Label({
+ text: bannerText,
+ style_class: 'conflicting-session-dialog-desc',
+ });
+ banner.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
+ banner.clutter_text.line_wrap = true;
+
+ let warningBanner = new St.Label({
+ text: _('Force stopping will quit any running apps and processes, and could result in data loss.'),
+ style_class: 'conflicting-session-dialog-desc-warning',
+ });
+ warningBanner.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
+ warningBanner.clutter_text.line_wrap = true;
+
+ textLayout.add_child(title);
+ textLayout.add_child(banner);
+ textLayout.add_child(warningBanner);
+ this.contentLayout.add_child(textLayout);
+
+ this.addButton({
+ label: _('Cancel'),
+ action: () => {
+ this.emit('cancel');
+ },
+ key: Clutter.KEY_Escape,
+ default: true,
+ });
+ this.addButton({
+ label: _('Force Stop'),
+ action: () => {
+ this.emit('force-stop');
+ },
+ });
+ }
+});
+
export const LoginDialog = GObject.registerClass({
Signals: {
'failed': {},
@@ -1004,6 +1077,57 @@ export const LoginDialog = GObject.regis
}, this);
}
+ _notifyConflictingSessionDialogClosed(userName) {
+ const source = new MessageTray.SystemNotificationSource();
+ Main.messageTray.add(source);
+
+ this._conflictingSessionNotification = new MessageTray.Notification(source,
+ _('Stop conflicting session dialog closed'),
+ _('Try to login again to start a session for user %s.').format(userName));
+ this._conflictingSessionNotification.setUrgency(MessageTray.Urgency.CRITICAL);
+ this._conflictingSessionNotification.setTransient(true);
+ this._conflictingSessionNotification.connect('destroy', () => {
+ this._conflictingSessionNotification = null;
+ });
+
+ source.showNotification(this._conflictingSessionNotification);
+ }
+
+ _showConflictingSessionDialog(serviceName, conflictingSession) {
+ let conflictingSessionDialog = new ConflictingSessionDialog(conflictingSession,
+ this._greeterSessionProxy,
+ this._user.get_user_name());
+
+ conflictingSessionDialog.connect('cancel', () => {
+ this._authPrompt.reset();
+ conflictingSessionDialog.close();
+ });
+ conflictingSessionDialog.connect('force-stop', () => {
+ this._greeter.call_stop_conflicting_session_sync(null);
+ });
+
+ const loginManager = LoginManager.getLoginManager();
+ loginManager.connectObject('session-removed', (lm, sessionId) => {
+ if (sessionId === conflictingSession.Id) {
+ conflictingSessionDialog.close();
+ this._authPrompt.finish(() => this._startSession(serviceName));
+ }
+ }, conflictingSessionDialog);
+
+ const closeDialogTimeoutId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, _CONFLICTING_SESSION_DIALOG_TIMEOUT, () => {
+ this._notifyConflictingSessionDialogClosed(this._user.get_user_name());
+ conflictingSessionDialog.close();
+ this._authPrompt.reset();
+ return GLib.SOURCE_REMOVE;
+ });
+
+ conflictingSessionDialog.connect('closed', () => {
+ GLib.source_remove(closeDialogTimeoutId);
+ });
+
+ conflictingSessionDialog.open();
+ }
+
_startSession(serviceName) {
this._bindOpacity();
this.ease({
@@ -1017,7 +1141,40 @@ export const LoginDialog = GObject.regis
});
}
- _onSessionOpened(client, serviceName) {
+ async _findConflictingSession(ignoreSessionId) {
+ const userName = this._user.get_user_name();
+ const loginManager = LoginManager.getLoginManager();
+ const sessions = await loginManager.listSessions();
+ for (const session of sessions.map(([id, , user, , path]) => ({id, user, path}))) {
+ if (ignoreSessionId === session.id)
+ continue;
+
+ if (userName !== session.user)
+ continue;
+
+ const sessionProxy = loginManager.getSession(session.path);
+
+ if (sessionProxy.Type !== 'wayland' && sessionProxy.Type !== 'x11')
+ continue;
+
+ if (sessionProxy.State !== 'active' && sessionProxy.State !== 'online')
+ continue;
+
+ return sessionProxy;
+ }
+
+ return null;
+ }
+
+ async _onSessionOpened(client, serviceName, sessionId) {
+ if (sessionId) {
+ const conflictingSession = await this._findConflictingSession(sessionId);
+ if (conflictingSession) {
+ this._showConflictingSessionDialog(serviceName, conflictingSession);
+ return;
+ }
+ }
+
this._authPrompt.finish(() => this._startSession(serviceName));
}
@@ -1209,6 +1366,9 @@ export const LoginDialog = GObject.regis
this._updateCancelButton();
+ if (this._conflictingSessionNotification)
+ this._conflictingSessionNotification.destroy();
+
const batch = new Batch.ConcurrentBatch(this, [
GdmUtil.cloneAndFadeOutActor(this._userSelectionBox),
this._beginVerificationForItem(activatedItem),