I needed to step away from my laptop while a large download was running. I wanted:
- The screen to go black (I have an OLED, burn-in is real)
- The system to not suspend and kill the download
- No lock screen so I could come straight back to my desktop
Simple enough, right? Turns out there's a reason this isn't solved out of the box.
The Wayland Problem
Ubuntu 22.04 switched to Wayland by default. If you're on 22.04 or later, you're
almost certainly on Wayland unless you manually switched back to X11 at login.
The classic screensaver toolkit — xscreensaver, xset, xdg-screensaver, is X11-only.
Run any of these on a Wayland session and you get either silent failure or errors like:
server does not have extension for dpms option
xset: unknown option off
These tools talk directly to the X display server. On Wayland, that server doesn't exist.
The OLED Problem
On an OLED display, each pixel is its own light source. When a pixel displays black,
it is physically switched off. No backlight. No power draw on that pixel.
This means a pure black screen on OLED is functionally equivalent to the screen being
off — except the display circuitry is still active, so the lock screen doesn't trigger.
That's exactly what I wanted. But leaving any static element on an OLED panel long enough
risks burn-in, including the lock screen itself, a bright clock widget, or even a static
cursor in the same spot. So the screensaver needs to move.
What Wayland Actually Uses
On GNOME Wayland, screen lock and power management are controlled through gsettings
the GNOME configuration system. The relevant keys are:
# How long before idle triggers (0 = never)
gsettings set org.gnome.desktop.session idle-delay 0
# Disable screensaver activation
gsettings set org.gnome.screensaver idle-activation-enabled false
# Disable the lock screen
gsettings set org.gnome.screensaver lock-enabled false
# Stop the screen from dimming
gsettings set org.gnome.settings-daemon.plugins.power idle-dim false
# Disable sleep on AC and battery
gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-timeout 0
gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-battery-timeout 0
These work on both Wayland and X11. No display server dependency.
Blocking System Suspend
Even with the screen settings fixed, Ubuntu can still suspend the system after a period
of inactivity on battery, killing any background process.
The right tool for this is systemd-inhibit. It holds a lock that prevents
suspend/sleep for the duration of a given command:
systemd-inhibit \
--what=sleep:idle \
--who="oled-safe" \
--why="Download in progress" \
--mode=block \
sleep infinity &
Running sleep infinity in the background keeps the inhibitor alive until we kill it.
This is the correct, systemd-native way to block suspend — no hacks, no polling.
Drawing the Black Window
For the actual black fullscreen window, I needed something that:
- Works on Wayland natively
- Ships with every Ubuntu GNOME install (no apt install)
- Supports absolute positioning so I can drift the text
GTK via python3-gi fits all three. It's pre-installed on every Ubuntu GNOME system
as a core dependency of the desktop environment itself.
import gi, random
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GLib
win = Gtk.Window()
win.fullscreen()
css = b"window { background-color: black; } label { color: #1e1e1e; font-family: monospace; font-size: 11pt; }"
provider = Gtk.CssProvider()
provider.load_from_data(css)
win.get_style_context().add_provider(provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
The label color #1e1e1e is intentionally dim — dark enough that it won't cause
burn-in, but visible up close so you know what's happening.
The Drift Bug I Hit on Wayland
My first approach to drifting the text used the classic GTK screen size API:
sw = win.get_screen().get_width() # DON'T do this
sh = win.get_screen().get_height() # DON'T do this
On X11, this works fine. On Wayland, Gdk.Screen.get_width() is deprecated and
returns unreliable values — sometimes 0, sometimes the combined width of all monitors,
sometimes just wrong. The label would end up placed off-screen.
The fix is to use the window's own allocated size, which on a fullscreen window
accurately reflects the screen dimensions and works correctly on both Wayland and X11:
sw = win.get_allocated_width() # correct — use this
sh = win.get_allocated_height() # correct — use this
There's a timing consideration too: get_allocated_width() returns 1 until the window
has actually been drawn and allocated. So the drift timer checks for this:
def move_label():
sw, sh = win.get_allocated_width(), win.get_allocated_height()
if sw <= 1 or sh <= 1:
return True # not allocated yet, try again next tick
# ... drift logic
Hiding the Cursor
A mouse cursor sitting in the same spot on an OLED screen is a burn-in risk.
Hide it at the GTK level once the window is realized:
def on_realize(widget):
try:
blank = Gdk.Cursor.new_from_name(win.get_display(), "none")
if blank is None:
raise Exception
except Exception:
blank = Gdk.Cursor.new_for_display(win.get_display(), Gdk.CursorType.BLANK_CURSOR)
win.get_window().set_cursor(blank)
win.connect('realize', on_realize)
The try/except handles the difference between newer and older GTK 3 versions.
Putting It Together
The full flow:
bash oled-safe.sh
│
├── Snapshot all GNOME settings
├── Apply Wayland-safe gsettings (lock off, sleep off, dim off)
├── systemd-inhibit sleep infinity & (suspend blocked)
│
├── GTK fullscreen black window
│ ├── Label starts at fixed visible position (100, 100)
│ ├── After 100ms: re-centres once window is allocated
│ ├── Every 5s: drifts ±25px (clamped to screen bounds)
│ └── Cursor hidden on realize
│
└── On ESC / click / Ctrl+C:
└── Restore all saved gsettings exactly
└── Kill inhibitor PID
The whole thing is a single bash script, no install needed:
bash oled-safe.sh
# press ESC or click to exit
Source
GitHub: https://github.com/simonpanigrahi/Ubuntu-Screensaver
One file. Feedback and PRs welcome — especially if you want to add a --timeout flag
or a system tray indicator.
Summary
| Problem | Solution |
|---|---|
| xset/xscreensaver broken on Wayland | Use gsettings instead |
| System suspends and kills download | systemd-inhibit |
| OLED burn-in from static content | GTK Fixed layout + drift timer |
| Gdk.Screen.get_width() wrong on Wayland | window.get_allocated_width() |
| Cursor burns into OLED | Gdk blank cursor on realize |
If you're building anything that needs to interact with GNOME power management on a
modern Ubuntu system, the gsettings + systemd-inhibit combination is the right stack.
The old X11 tools are effectively dead on Wayland.
Top comments (0)