Inhibit suspending the computer with GtkApplication

A deep dive into the topic of "system sleep inhibition", or how to prevent the computer from suspending while your application is running. This is a post for developers who write GTK applications for GNU/Linux. In here I'll try to cover pretty much everything there is to know on the topic. Or at least, everything I know.

Just a little precision regarding the terms we use here: preventing the "computer/session/system" from "suspending/sleeping" are the same things: different words, same meaning.

Introduction

The assumption is that you're developing an application for the GNU/Linux desktop, and that for some reason you want to make sure that the computer doesn't go to sleep while your application is running. A typical example is a video player: in all evidence, if you watch a 2-hour long movie, you don't want your computer to suspend in the middle.

Another use-case (the one I'm concerned about) is a radio player, or more generally a music player. Goodvibes is this little radio player that I've been maintaining for a while, and it provides an option to prevent the system from going to sleep while a radio is playing.

There are other use-cases of course.

So how do you do that? The thing to understand is that it's not really your application which does that, but rather a component of the Desktop Environment. Your application just communicates with this component, and asks it to prevent the system from suspending.

Now, what is this mysterious "component"? Answer is: it depends.

If you're familiar with the wild world of Linux Desktop, you know that there's a whole bunch of different Desktop Environments out there. GNOME (and GNOME related like Cinnamon, MATE, Elementary, etc...), KDE, XFCE, and even more "barebone" systems without even a Desktop Environment per se, like Sway. Another situation to consider is that your application might be running in a sandboxed environment, like Flatpak, Snap or AppImage.

Depending on the environment in which your application is running, the way to inhibit will be different. In all likelihood, your application will have to talk to some API over D-Bus, but the API will differ.

Does it mean that, if you want to ensure that your application works for all of those environments, you will have to support all of them by yourself? Answer is: no!

Don't even try! First it's a pain to do so, then it takes a lot of time to test your solution on all of these environments, and finally, it will also require maintenance over the years (disclaimer: I've been there).

The right answer is: leverage the toolkit! GTK, through the GtkApplication class, provides the two methods gtk_application_inhibit () and gtk_application_uninhibit (). These methods do all the job for you, they do all the painful job of supporting the various D-Bus APIs available out there, and the GTK maintainers are very aware when new APIs are introduced.

So that's really the only sane way to go. And if ever your application does not use GtkApplication yet, then it's about time to give it a try!

A dive into the GtkApplication implementation

Now let's be curious a bit, and let's have a look at the implementation in GTK (version 3.x), to understand what really happens when your application calls gtk_application_inhibit ().

It all starts in the file gtk/gtkapplication.c, it is where you can find the method gtk_application_inhibit (). It doesn't tell us much though. The actual implementation can be found in the file gtk/gtkapplication-dbus.c, and that's where we can see exactly what GTK does in order to inhibit.

At first, GTK will try to reach the Session Manager.

For GNOME and GNOME-derived desktop environments, it means calling the following D-Bus method:

For XFCE, GTK also supports the XFCE Session Manager:

If none of these Session Managers are found, GTK will try the Freedesktop Portal, ie. the following D-Bus method:

If it's not found, then it's the end of the game... Or is it?

For GTK3, yes, but it's interesting to note that GTK4 supports yet another way of inhibiting. This time, it's not through D-Bus. It goes through a Wayland protocol, meaning that it only works for Wayland compositors. It seems that this support was added for minimalist desktops like Sway.

Also, this protocol is not exactly about what we're talking about here, it's not about inhibiting system sleep. Instead it's about « inhibiting the idle behavior such as screen blanking, locking, and screensaving ». This is an important detail that I didn't mention yet, because it's a bit unclear to me. But basically, you can inhibit various things, and "inhibiting the idle behavior" is more about the screen. Does that also prevent the computer from going to sleep, or is it completely unrelated? I have to admit I don't know...

So, yeah, this new thing in GTK4 might not be relevant for this blog post, but I thought I'd mention it anyway, just in case you find it interesting. Details can be found at:

Testing it

This post wouldn't be complete without actually trying to use gtk_application_inhibit () in various environments, and seeing it at work, and feeling good about it, right?

So, let's come back to my personal life for a while. A few days ago I reworked how Goodvibes inhibits the system sleep, and I started to use gtk_application_inhibit (), instead of rolling my own solution. Then I went on to try it with different environments. Here are some results.

GNOME 3.36

Under GNOME, it works as expected, no surprise. GTK goes through the D-Bus service org.gnome.SessionManager. I couldn't see anything in the logs, but it's possible to verify programatically that suspend is inhibited, thanks to the method IsInhibited provided by the GNOME Session Manager.

$ busctl --user call \
    org.gnome.SessionManager \
    /org/gnome/SessionManager \
    org.gnome.SessionManager \
    IsInhibited u 4
b true

Note that the parameter, an unsigned integer, is the flag that says what exactly we're checking, among the various things that can be inhibited. 4 is for suspending the session or computer.

KDE 5.18

Under KDE, it also works as expected. We can see some logs in the systemd journal when we inhibit:

org.freedesktop.impl.portal.desktop.kde[9956]: xdp-kde-inhibit:
  Inhibit called with parameters:
    handle:  "/org/freedesktop/portal/desktop/request/1_149/t"
    app_id:  ""
    window:  ""
    flags:  4
    options:  QMap(("reason", QVariant(QString, "Playing")))

In all evidence, GTK goes through the Freedesktop Portal org.freedesktop.portal.Desktop to do the job.

XFCE 4.14

Now is the surprise, and the reward you get for testing!

On XFCE it does NOT work:

(goodvibes:12772): Gtk-WARNING **: 10:23:57.000:
  Calling org.xfce.Session.Manager.Inhibit failed:
  GDBus.Error:org.freedesktop.DBus.Error.UnknownMethod:
  No such method “Inhibit”

No such method? Indeed. Interestingly, after looking at the XFCE Session Manager git repository, it seems that the Inhibit method never existed, so basically gtk_application_inhibit () has never worked on XFCE. I opened an issue just to see if I can get more crunchy details.

XFCE Workaround

So in this situation, either you live with it, either you go to some lengths to implement some kind of workaround. I went for the latter, basically because I have too much time on my hands.

It just happens that, on XFCE, there's a D-Bus service sitting there and which provides an Inhibit method as well:

Hey. Wait a second. So there's yet another way to inhibit? Answer is: yes!

Now to be fair, this D-Bus API seems to be a relic of the past. I did a bit of search, and here's what I found:

Thing is, on XFCE, it's there, and it works. You can use it and inhibit the system sleep. It's functional. I'm not sure if it's "the right way" to do it for XFCE, but I couldn't find any other way anyway.

So ultimately, in Goodvibes I added a workaround to support this D-Bus API. If ever gtk_application_inhibit () fails, Goodvibes falls back to using this old API, if available. XFCE users are covered.

By the way, this API also provides a method to check if inhibition is enabled:

$ busctl --user call \
    org.freedesktop.PowerManagement \
    /org/freedesktop/PowerManagement/Inhibit \
    org.freedesktop.PowerManagement.Inhibit \
    HasInhibit
b true

Conclusion

There's a few conclusions I can draw from this adventure.

Interoperability for Linux desktop applications never fails to be a timesink. Learning how things work deep down, digging up bits of history from the Internet to understand how things came to be, actually testing on different desktop environments, hitting some issues here and there... These kind of things take forever, but...

It's also a great way to learn how things work, and to understand better how Linux desktops are actually developed. I mean, if you're into this kind of things, and willing to spend time doing research on the Internet.

Ultimately, the only sane way to go is to leverage well-known toolkits (GTK, Qt) or libraries (GLib) to get your stuff done. More than often, the specs from freedesktop.org have an implementation in these libraries, so look hard for your solution in there, and stick to it if you can.

I stress that last point because initially, I implemented my own solution simply because I didn't know about gtk_application_inhibit (). Then when I found out, I didn't want to ditch all the work I had done, so I kept using it in my application for a few years. Actually it worked well, but finally a bug was reported, and I just didn't want to debug all that stuff I made years before. So I agreed with myself that it was time to get rid of it, but the price to pay was a bit of refactoring.

Now I'm at peace with that, but I can't help but think that a good deal of time could have been saved, if only from the beginning I had looked harder for an existing solution, rather than rushing into implementing something. A mistake done too often.