# sleep-stopper: A simple GTK4 app to stop Linux from sleeping.

Hello,

I came up with a simple GUI for stopping Linux from sleeping when idle using Python for fun. This project came to be because of my comment:

[https://social.linux.pizza/@shanmukhateja/115843701361619857](https://social.linux.pizza/@shanmukhateja/115843701361619857)

You can find GitHub link for this project below:

[https://github.com/shanmukhateja/sleep-stopper/](https://github.com/shanmukhateja/sleep-stopper/)

# Theory

We use DBus interface `org.freedesktop.ScreenSaver` on Session Bus to tell Linux to disallow going to sleep on idle.

This feature is very useful when waiting on long-running tasks like system updates, media playback, etc. It is already part of all major applications like web browsers, system tools, media players, etc. and now, this project is also part of that list :)

I wrote this in Python as I’m very comfortable with it but it should be doable in any programming language.

The idea is to have 2 buttons - **block sleep** and **unblock sleep** shown to user by checking whether we have a valid cookie. This is an `int` value provided to us by `Inhibit` method from DBus interface when our app has successfully requested sleep inhibition.

In order to request inhibiting sleep, we need to provide 2 arguments:

1. **App Title**  
    Shows the app calling for sleep inhibition to user
    
2. **Reason**  
    Shows a label for reason behind sleep inhibition for user’s understanding.
    

We will need the following Python packages for this project:

1. GTK4 (for GUI)
    
2. dbus-fast (for communicating with DBus)
    

# Implementation

> **Note:** This code is hacked together in about 3 hours and it’s only goal is to work as expected. It is not meant to deliver a polished experience.

Let’s look at `app.py` which contains the GUI and the main logic:

```python
from dbus import SleepStopperDBusClient

import gi

gi.require_version("Gtk", "4.0")
from gi.repository import Gtk


class SleepStopperApplication(Gtk.ApplicationWindow):
    def __init__(self, **kwargs):
        super().__init__(**kwargs, title="Sleep Stopper")
        self.set_size_request(300, 150)

        self.vbox = Gtk.Box()
        self.set_child(self.vbox)

        self.dbus = SleepStopperDBusClient()

        self.block_button = Gtk.Button.new_with_label("Block sleep")
        self.unblock_button = Gtk.Button.new_with_label("Unblock Sleep")

        self.block_button.set_hexpand(True)
        self.unblock_button.set_hexpand(True)

        self.vbox.append(self.block_button)
        self.vbox.append(self.unblock_button)

        self.block_button.connect("clicked", self.on_block_clicked)
        self.unblock_button.connect("clicked", self.on_unblock_clicked)

        self.update_ui_state()

    def on_block_clicked(self, _btn):
        result = self.dbus.inhibit()
        if result:
            # show toast 
            pass
        else:
            # show error toast
            pass
        self.update_ui_state()

    def on_unblock_clicked(self, _btn):
        result = self.dbus.uninhibit()
        if result:
            # show toast 
            pass
        else:
            # show error toast
            pass
        self.update_ui_state()

    def update_ui_state(self):
        is_active = self.dbus.is_active()

        if is_active:
            # Inhibit not on?
            self.block_button.set_visible(True)
            self.unblock_button.set_visible(False)
        else:
            # Inhibit IS SET!!
            self.block_button.set_visible(False)
            self.unblock_button.set_visible(True)

def on_activate(app):
    win = SleepStopperApplication(application=app)
    win.present()

app = Gtk.Application(application_id="in.suryatejak.sleepstopper")
app.connect("activate", on_activate)
app.run(None)
```

This is a pretty straight-forward Python code. We initialize the app, create a GtkWindow and show 2 buttons as discussed above.

The GUI logic is driven by a handy function `update_ui_state` which talks to our DBus client `SleepStopperDBusClient` (see `dbus.py` for its code).

I initially planned to show a Toast message of sorts but I realized that means more work which is not necessary for a simple tool.

Now, let’s look at `dbus.py` which sets up communication with DBus interface and provides handy functions for GUI to use.

**dbus.py**

```python
import asyncio
from dbus_fast.aio import MessageBus

class SleepStopperDBusClient(object):

    def __init__(self):
        self.cookie = None
        self.runner = asyncio.get_event_loop()
        self.runner.run_until_complete(self.init())

    async def init(self):
        self.bus = await MessageBus().connect()

        introspection = await self.bus.introspect('org.freedesktop.ScreenSaver', '/org/freedesktop/ScreenSaver')

        self.obj = self.bus.get_proxy_object('org.freedesktop.ScreenSaver', '/org/freedesktop/ScreenSaver', introspection)

        self.screensaver = self.obj.get_interface('org.freedesktop.ScreenSaver')

    def is_active(self):
       return self.cookie is None
    
    def inhibit(self):
        self.runner.run_until_complete(self._set_inhibit())
        return True

    def uninhibit(self):
        if not self.cookie:
            return False
        self.runner.run_until_complete(self._set_uninhibit())

    async def _set_inhibit(self):
        self.cookie = await self.screensaver.call_inhibit("Sleep Stopper", "User requested inhibit!")
        print(self.cookie)

    async def _set_uninhibit(self):
        if not self.cookie:
            print("self.cookie not set, ignoring request..")
            return False
        await self.screensaver.call_un_inhibit(self.cookie)
        self.cookie = None
        return True
```

Here, we grab a reference to `org.freedesktop.ScreenSaver` from DBus so that we can call `Inhibit` and `UnInhibit` methods.

In order to make GTK work with async-await, I grabbed instance of asyncio’s Event loop in `self.runner` and use `run_until_complete` function to call the DBus methods.

> You may have noticed that I have added logic to ignore UnInhibit requests if `self.cookie` doesn’t exist and I feel this is probably unnecessary.
> 
> If an app requests to inhibit sleep BUT after sometime, it is either killed or the process exits without un-inhibiting sleep, the sleep inhibition request is gone.
> 
> *Pretty cool, isn’t it?*

# Screenshots

**Launch screen**

![sleep-stopper startup UI](https://cdn.hashnode.com/res/hashnode/image/upload/v1767804604108/4db10694-8e5d-4036-a491-d38bbbe5a247.png align="center")

**Sleep inhibit is active**

![sleep-stopper "Unblock Sleep" button is shown as sleep inhibit is active.](https://cdn.hashnode.com/res/hashnode/image/upload/v1767804615638/755ffc07-b90d-4b66-aaba-88e69579d784.png align="center")

**KDE Plasma shows inhibit request along with the app title and reason.**

![KDE Plasma power management UI showing sleep inhibit request by sleep-stopper app](https://cdn.hashnode.com/res/hashnode/image/upload/v1767804669136/50ab19b5-667a-4ff6-8f7d-8859075e31fc.png align="center")

# Conclusion

I hope this was informative :)

I built this tool for fun - to simply stretch my wings by building simple stuff. I wrote all this in around 3 hours (~2.5hrs) and I was fun project.

The challenge in here, was to code that dealt with separating synchronous code with `async-await` stuff. My current workaround works because I do not do anything complex with the return result.

Anyway, I hope you liked this post. Please give it a Like to show your appreciation. If you feel there’s something I missed or can do better, let me know in the comments.

Follow me on [Mastodon](https://social.linux.pizza/@shanmukhateja/).

Bye for now :)
