Inhibit suspending the computer with GtkApplication
Fri 25 September 2020A 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:
- bus name:
org.gnome.SessionManager
- object path:
/org/gnome/SessionManager
- method:
org.gnome.SessionManager.Inhibit
- API details → org.gnome.SessionManager.xml
For XFCE, GTK also supports the XFCE Session Manager:
- bus name:
org.xfce.SessionManager
- object path:
/org/xfce/SessionManager
- method:
org.xfce.Session.Manager.Inhibit
- API details → xfsm-manager-dbus.xml
If none of these Session Managers are found, GTK will try the Freedesktop Portal, ie. the following D-Bus method:
- bus name:
org.freedesktop.portal.Desktop
- object path:
/org/freedesktop/portal/desktop
- method:
org.freedesktop.portal.Inhibit.Inhibit
- API documentation → Portal for inhibiting session transitions
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:
- Original GitLab Issue #2202
- GitLab Merge Request #2226
- Wayland protocol → idle-inhibit-unstable-v1.xml
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:
- bus name:
org.freedesktop.PowerManagement
- object path:
/org/freedesktop/PowerManagement/Inhibit
- method:
org.freedesktop.PowerManagement.Inhibit.Inhibit()
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:
- The
org.freedesktop.PowerManagement
spec was announced in March 2007 - There seems to be an official specification ...
- ... which is obsolete. All of the links to the actual spec are dead.
- And I couldn't find much more information on this mysterious D-Bus API.
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.