Sitemap
Ronny Rentner

Ronny Rentner

Entrepreneur, Athlete, Artist, Nerd

GNOME Usability Nightmares

7 min readOct 21, 2026

--

Or: how I needed three workarounds and a daemon of my own to make the Restart button restart.

Machine: a ThinkPad running Debian 13, GNOME 50.5, gnome-session 51.0, systemd 262, a self-built Linux 7.3. Everything below happened on it, and the logs are quoted from its journal.

Act one: the lid

I close the notebook, slide it into the bag, and leave.

When I open the bag again, the fans are screaming. The inside of the bag is hot; a running notebook in a closed bag has no airflow at all. The laptop never went to sleep.

GNOME’s power daemon, gsd-power, does not trust the lid. Whenever an external monitor was attached, or the display configuration changed “recently”, it takes a lock in logind named handle-lid-switch. While that lock exists, logind ignores the lid entirely. This is what the lock looks like on a machine where it is allowed, taken from a Debian bug report (gnome-session issue 188):

WHO      UID  USER     PID   COMM       WHAT                WHY                                                          MODE
eastwan 1000 eastwan 3027 gsd-power handle-lid-switch External monitor attached or configuration changed recently block

“Recently” is GNOME’s word, not mine. The monitor was gone, the lid was closed, and GNOME decided that it knew better than the lid.

Until systemd 256 a user could override a lock held inside their own session. systemd 257 changed that: logind now enforces block locks even against the user who owns the session. The commit is “logind: always check for inhibitor locks”. From then on, any program in my session can veto my own power actions, and GNOME ships several that do.

There is exactly one switch that stops all of them. logind can be told to hand out no locks at all:

# /etc/systemd/logind.conf.d/disable-inhibitors.conf
# Applications must not be able to veto explicit user power actions.
# systemd 257 makes block inhibitors mandatory even for their owner, so disable inhibitor allocation entirely.
[Login]
InhibitorsMax=0

With that in place, gsd-power asks for its lid lock at every login and is refused:

Oct 21 06:02:04.432934 yuna gsd-power[4104]: Unable to inhibit lid switch: GDBus.Error:org.freedesktop.DBus.Error.LimitsExceeded: Maximum number of inhibitors (0) reached, refusing further inhibitors.

Good. The lid is a lid again. Closing it suspends the machine. Curtain.

Act two: the menu

Months later I need a reboot. Top right, power icon, Restart. The familiar dialog comes up, “The system will restart automatically in 60 seconds”, with a Restart button. I press it.

The dialog fades. The desktop is still there. Nothing restarts.

I try again. Same thing. No error, no notification, no hint. The journal has exactly this to say about it:

Oct 21 05:58:13.059998 yuna gnome-shell[3567507]: endSessionDialog: No XDG_SESSION_ID, fetched from logind: 17
Oct 21 05:58:14.606747 yuna gnome-session-service[3567495]: Failed to get delay inhibitor: GDBus.Error:org.freedesktop.DBus.Error.LimitsExceeded: Maximum number of inhibitors (0) reached, refusing further inhibitors.
Oct 21 05:58:14.631605 yuna gnome-session-service[3567495]: Entering running state

“Entering running state.” That is gnome-session saying: I have decided not to reboot, and I am going back to what I was doing.

Why? Because gnome-session wanted a lock of its own. Here is the code, from gnome-session 51.0, gnome-session/gsm-systemd.c:

static void
gsm_systemd_prepare_shutdown (GsmSystem *system,
gboolean restart)
{
...
res = g_dbus_proxy_call_with_unix_fd_list_sync (systemd->sd_proxy,
"Inhibit",
g_variant_new ("(ssss)",
"shutdown",
g_get_user_name (),
"Preparing to end the session",
"delay"),
...);
if (res == NULL) {
g_warning ("Failed to get delay inhibitor: %s", error->message);
g_error_free (error);
g_signal_emit_by_name (systemd, "shutdown-prepared", FALSE);
gsm_systemd_reacquire_inhibitors (systemd);
return;
}
...
g_dbus_proxy_call (systemd->sd_proxy,
restart ? "Reboot" : "PowerOff",
g_variant_new ("(b)", TRUE),
...);

And the receiving end in gsm-manager.c:

static void
on_shutdown_prepared (GsmSystem *system,
gboolean success,
GsmManager *manager)
{
if (success) {
/* move to end-session phase */
manager->phase++;
start_phase (manager);
} else {
disconnect_shell_dialog_signals (manager);
gsm_shell_close_end_session_dialog (manager->shell);
/* back to running phase */
cancel_end_session (manager);
}
}

Read that sequence once more, slowly. gnome-session’s plan for a reboot is:

  1. Take a “delay” lock, which tells logind: when the reboot comes, hold it for up to five seconds.
  2. Call logind’s Reboot. Now. Immediately.
  3. Use the five seconds the lock buys to end the session, ask apps to save, close the shell.
  4. Release the lock, reboot proceeds.

So gnome-session asks for the reboot first and then applies a brake to its own request. And if it cannot get the brake, it does not make the request. The one configuration that guarantees nobody can veto my reboot is the one configuration in which GNOME refuses to reboot.

It could end the session first and call Reboot afterwards. It could wait five seconds on its own. It could at least put a line on the screen. Instead the dialog closes and the desktop sits there, smug. The upstream tracker has an open issue (gnome-session 145) asking for a notification when this happens. Open since April 2025. It asks for a message, not for a reboot.

Act three: the console

Fine. GNOME will not reboot. Linux will. I switch to a text console and press Ctrl-Alt-Del. systemd gets the signal at once:

Oct 21 05:59:37.851461 yuna systemd[1]: Started getty@tty2.service - Getty on tty2.
Oct 21 05:59:38.959302 yuna systemd[1]: Received SIGINT.
Oct 21 05:59:38.962167 yuna systemd[1]: Activating special unit reboot.target...
Oct 21 05:59:38.970882 yuna systemd[1]: Stopping gdm.service - GNOME Display Manager...
Oct 21 05:59:39.000616 yuna systemd[1]: Stopping user@60582.service - User Manager for UID 60582...
Oct 21 05:59:39.014006 yuna systemd[1]: Stopping docker.service - Docker Application Container Engine...
Oct 21 05:59:41.455729 yuna systemd[1]: Stopped containerd.service - containerd container runtime.

Three seconds in, Docker is down, the display manager is down. And then the screen sits there. A minute passes. I press Ctrl-Alt-Del again. And again.

Oct 21 06:01:01.613117 yuna systemd[1]: Received SIGINT.
Oct 21 06:01:02.649448 yuna systemd[1]: Received SIGINT.
Oct 21 06:01:02.874430 yuna systemd[1]: Received SIGINT.
Oct 21 06:01:04.830864 yuna systemd[1]: Received SIGINT.
Oct 21 06:01:05.077272 yuna systemd[1]: Received SIGINT.
Oct 21 06:01:05.324446 yuna systemd[1]: Received SIGINT.
Oct 21 06:01:05.539794 yuna systemd[1]: Received SIGINT.

Seven presses. Each one re-queues the reboot that is already running. Then, at exactly ninety seconds after the first press:

Oct 21 06:01:09.167014 yuna systemd[2397371]: xdg-document-portal.service: State 'stop-sigterm' timed out. Killing.
Oct 21 06:01:09.167085 yuna systemd[2397371]: xdg-document-portal.service: Killing process 2398297 (xdg-document-po) with signal SIGKILL.
Oct 21 06:01:09.167221 yuna systemd[2397371]: xdg-document-portal.service: Killing process 2398303 (fusermount3) with signal SIGKILL.
Oct 21 06:01:09.168661 yuna systemd[2397371]: Stopped xdg-document-portal.service - flatpak document portal service.
...
Oct 21 06:01:09.833330 yuna systemd[1]: Finished systemd-reboot.service - System Reboot.

Killed at 06:01:09.167. Rebooting at 06:01:09.833. The whole ninety seconds was one process.

Get Ronny Rentner’s stories in your inbox

Join Medium for free to get updates from this writer.

Who is UID 60582? Not me. It is gdm-greeter, the login screen. The moment my session ended, GDM had started a fresh greeter, and the greeter's gnome-shell had started the desktop portal stack, which pulls in the Flatpak document portal, which mounts a FUSE filesystem at /run/user/60582/doc. When the reboot came, that FUSE unmount hung, and systemd waited its default ninety seconds before killing it.

A login screen. It shows a password field. It has no documents, it runs no apps, and this machine has no Flatpak and no Snap installed. The portal exists on it for no reason, and it got to hold my reboot hostage.

GNOME did not reboot when I asked. Then GNOME’s login screen delayed the reboot I forced past it.

Epilogue: what it takes

This is the full list of what this laptop now carries so that “Restart” means restart and a closed lid means sleep.

1. No locks for anybody

/etc/systemd/logind.conf.d/disable-inhibitors.conf, shown above. Removes every application's ability to veto a power action, including the lid.

2. No document portal for anybody

sudo systemctl --global mask xdg-document-portal.service

The portal is part of the xdg-desktop-portal package and cannot be uninstalled on its own. Masked, it cannot be started by systemd or by D-Bus activation; the activation file names the systemd unit, so D-Bus gets “Unit xdg-document-portal.service is masked” and gives up. The only thing lost is file access for sandboxed Flatpak and Snap apps, of which there are none here.

3. Twenty seconds, for everybody

systemd lets any service stall a shutdown for 90 seconds by default, and a user manager as a whole for 120. One drop-in caps all of them, user managers included, so no service of any user can hold a reboot longer:

# /etc/systemd/system/service.d/stop-timeout.conf
[Service]
TimeoutStopSec=20s

A type drop-in under service.d applies after every packaged unit file, so a package cannot raise it again.

4. A daemon that does what gnome-session will not

When I confirm the dialog, gnome-shell announces it on the session bus before gnome-session gets a chance to change its mind. The signal is ConfirmedReboot or ConfirmedShutdown on the object /org/gnome/SessionManager/EndSessionDialog. A thirty-line Python script listens for it and calls logind directly.

~/Projects/system/end-session-dialog-confirm.py:

#!/usr/bin/env python3
"""Reboot or power off the moment the GNOME end-session dialog is confirmed.
gnome-session refuses to act on the confirmation when logind denies it a delay
inhibitor (InhibitorsMax=0 in /etc/systemd/logind.conf.d/disable-inhibitors.conf),
so the dialog's D-Bus signal is answered here with a direct logind request instead.
"""
import subprocessfrom gi.repository import Gio, GLib# Signal member emitted by gnome-shell -> systemctl verb to run.
ACTIONS = {"ConfirmedReboot": "reboot", "ConfirmedShutdown": "poweroff"}
def on_signal(connection, sender, path, interface, member, parameters, user_data):
subprocess.run(["systemctl", ACTIONS[member]], check=True)
bus = Gio.bus_get_sync(Gio.BusType.SESSION)
for member in ACTIONS:
bus.signal_subscribe(
None,
"org.gnome.SessionManager.EndSessionDialog",
member,
"/org/gnome/SessionManager/EndSessionDialog",
None,
Gio.DBusSignalFlags.NONE,
on_signal,
None,
)
GLib.MainLoop().run()

Its user unit, ~/Projects/system/end-session-dialog-confirm.service, linked into ~/.config/systemd/user/:

[Unit]
Description=Reboot or power off directly when the GNOME end-session dialog is confirmed
PartOf=graphical-session.target
After=graphical-session.target
[Service]
ExecStart=/home/ronny/Projects/system/end-session-dialog-confirm.py
[Install]
WantedBy=graphical-session.target

Installed with:

ln -s ~/Projects/system/end-session-dialog-confirm.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now end-session-dialog-confirm.service

The GNOME dialog still appears, with its countdown and its Cancel button. The difference is that Restart now restarts. gnome-session still goes through its little routine afterwards, fails to get its lock, and “enters running state”, but by then logind has already begun the reboot and nobody is listening.

Needs the Python GLib bindings (python3-gi), which GNOME installs anyway.

Keeping score

Two drop-ins. One masked unit. One daemon written at home. All of it so that a desktop environment, when told “shut down”, shuts down, and when the lid is closed, sleeps.

--

--

Ronny Rentner
Ronny Rentner
0 followers

Entrepreneur, Athlete, Artist, Nerd