[X11] Writing X11 screensaver with C++ & OpenGL

tl;dr

Download XScreenSaver. In your binary you can’t use glfw to create window, use GLX instead, because you have to hook up to the virtual root window.
https://github.com/dbeef/x11-opengl-screensaver/

Prerequisites

As of Ubuntu 11.10 screensaver server isn’t placed in the distro (from that moment on it supports only screen blanking), to enjoy graphical screensavers we’ve got to install it for ourselves:

xscreensaver

After installing, place where screensavers are stored is /usr/lib/xscreensaver,
where listing shows some default ones:

Screenshot from 2018-12-26 23-12-16

They’re ordinary executable files:

penrose

…and when running one of them they create a window with a screensaver displayed:

penrose

Development

That’s great, or that’s more what I thought, because as I dropped there one of my OpenGL programs (I put them on my Github) naively thinking that every arbitrary binary can be set up as a screensaver I ran:

xscreensaver-demo

Which launches tool for choosing & setting up a screensaver from /usr/lib/xscreensaver:

properties

As I eventually selected my program (it’s called Screensaver on the list above), it occured to me that there are 2 problems.

  1. My program does not show in the little squared window of xscreensaver-demo when selected; it just runs in a new window, unlike screensavers shipped with package.
  2. When 1 minute passes and XScreenSaver launches my own screensaver, all what I see are logs from my screensaver on some black screen, not the window it was supposed to create (as I’ve seen for a splitsecond when moved a mouse, window was indeed created, but it wasn’t floating on top of the others despite hints I passed to glfw, this black screen was shadowing it).

What these problems have in common?

There must be some parent-window to hook up when launching my screensaver, so it wouldn’t just run in a new window but rather take a handle from another process. Looks like I can’t just select any arbitrary program and expect it to work as a screensaver, pity.

A look at the ‘Root window’ Wikipedia article confirmed my assumptions:

The virtual root window is also used by XScreenSaver: when the screensaver is activated, this program creates a virtual root window, places it at the top of all other windows, and calls one of its hacks (modules), which finds the virtual root window and draws in it.

Down the rabbit hole

I needed to get some example code of screensavers that are shipped within the XScreenSaver or any other working artifacts. I got a clever, concise example from Github:

https://github.com/RobertZenz/xscreensavers

Compiled it:

gcc

and it actually worked as other examples!

lavanet

So that’s the way that RobertZenz did it in his lavanet screensaver:

  1. He included a header called vroot.h, which is an abbreviation from virtual root window. Root window is the window which is the bottom-most, it’s a parent to every other window. As Wikipedia states:

    In the X Window System, every window is contained within another window, called its parent. This makes the windows form a hierarchy. The root window is the root of this hierarchy. It is as large as the screen, and all other windows are either children or descendants of it.

    File’s content is 106 lines, more than a half of it is  description which I’ll just put here for clarity because it describes what it does better than I would:
    carbon (5)

  2. In lavanet.c, vroot.h is used it this way:carbon (4)
    Rest of the code is GLX calls and lavanet logics which is not important for us.

OK! Looks like I can’t just make a new window with glfw.
I need to get that root window first.

At this point I hoped that there’s a way in glfw to create a native X11 Window, configure it (with vroot.h) and pass it to the glfw, since glfw exposes some native calls:

https://www.glfw.org/docs/latest/group__native.html

…but I was wrong. There’s just no way. To get GLFWwindow object, you’ve got to call glfwCreateWindow and it’s the only way.
There’s even an issue opened on Github in 2013 which was active through the years and the last answer is from 20 days before I wrote this post:

https://github.com/glfw/glfw/issues/25

It was exactly the same problem I was facing, but feature that would provide passing native handle is still not shipped.

What’s left?

Since I used glfw only for convenience (it abstracts creating window so developer wouldn’t bother writing platform-specific branches) I could use GLX to get that native window handle.

GLX is like an interface for OpenGL calls that can talk with an X11 server.
As Windows has its own windowing system, it has its own equivalent of GLX, called WGL.
If you’re getting confused, reffer to the Wikipedia and this answer on StackOverflow:

https://stackoverflow.com/questions/40543176/does-opengl-use-xlib-to-draw-windows-and-render-things-or-is-it-the-other-way-a

The glfw way (code before)

My previous code I used to create windows with glfw looked like this:

carbon (2)

Not much, right? There were some glfw calls on program start:

carbon (3)

But that’s all.

The GLX way (code after)

I got window handle exactly the same way as in the lavanet example.
Then, in my main loop, I couldn’t do anymore:

swapbuffers

So I replaced it with:

swapx

(I also change classname to WrapperWindow so it wouldn’t conflict with X11 window).

I had a window, but I still needed to register a graphical context. And that’s where this very helpful post came:
http://apoorvaj.io/creating-a-modern-opengl-context.html

So the class after changes looked like that:

carbon (1)

Building

As it was inside my opengl-playground CMake project that displayed textured cubes, I simply built it and copied resulting binary into /usr/lib/xscreensaver/.

Then, typed xscreensaver-demo, selected my screensaver and could preview it – it worked.

Conclusion

Looks like it’s not that hard to make a screensaver for X11, just make sure you create a native window by your own, the rest is just ordinary OpenGL.

I created a separate CMake project with this X11 screensaver afterwards and put it on Github, so you could try it for yourself. For clarity, I cut the logics, so it only fills screen with colours every some amount of time. OpenGL and X11 are the only dependencies.

https://github.com/dbeef/x11-opengl-screensaver/

By the way, this time I used a service called carbon:
https://carbon.now.sh
It saved my time – generates images from source code.

Sources (or just interesting related stuff to read)

How to make an X11 screensaver with python:
https://alvinalexander.com/python/python-screensaver-xscreensaver-linux

https://unix.stackexchange.com/questions/220389/x11-controlling-root-window-or-setting-a-window-to-be-the-background-window-wal

https://stackoverflow.com/questions/2431535/top-level-window-on-x-window-system

https://en.wikipedia.org/wiki/GNOME_Screensaver
https://en.wikipedia.org/wiki/Wayland_(display_server_protocol)
https://en.wikipedia.org/wiki/Screensaver
https://en.wikipedia.org/wiki/Root_window
https://pl.wikipedia.org/wiki/Mesa_3D
https://pl.wikipedia.org/wiki/GLX
https://www.khronos.org/opengl/wiki/Programming_OpenGL_in_Linux:_GLX_and_Xlib
https://softwareengineering.stackexchange.com/questions/162486/linux-opengl-programming-should-i-use-glx-or-any-other
https://stackoverflow.com/questions/40543176/does-opengl-use-xlib-to-draw-windows-and-render-things-or-is-it-the-other-way-a

https://github.com/porridge/xscreensaver/blob/debian-5.10-3ubuntu4/README.hacking
http://www.dis.uniroma1.it/~liberato/screensaver/
https://github.com/gamedevtech/X11OpenGLWindow

Leave a comment