I Want My Screensaver Settings!

Screensavers are no longer what they used to be… I guess that’s because people now use their computers more than before so there’s no time for those colorful and fun assortment of random animations to show up.

But I’ve always been a fan of screensavers. Some of them are truly works of art, some are simply amazing works of coding and some are, well, just fun to watch.

Who doesn’t remember the AfterDark screensavers pack!
Man I loved those and one of the things I loved the most is that they came with tons of configuration options. Actually, I remember spending a lot more time configuring them than actually watching them.

One of the things I noticed when I started toying with Linux distros was the huge amount of screensavers that were available. Some I had already seen but many others were just amazing pieces of coding and art. And as if they were actually meant to satisfy my geeky needs, they had tons and tons of settings. I was a happy lad.

Unfortunately, at some point, some smartass had the bright idea to remove the “settings” button that gave us access to the settings on those screensavers. I was like, what the FUUUUUU?!?!

I’ve always wanted to be the one in control of my computer — I hate it when developers remove options and settings for the plain and simple reason to simplify the “user experience”. I think that in doing so, you are simply moronizing the users. Probably that’s one of the main reasons why, although I use one on a daily basis, I’ve never liked Macs.

I know that moronizing is not actually a word but it looks like these days, if you use a made-up-word enough it becomes one.

So after upgrading to an Ubuntu distro (I think it was 7.10) that contained the new crippled gnome-screensaver application, I decided that I would “try” to bring back the “settings” button.
It wasn’t going to be easy, basically because:

  • I hadn’t coded in C/C++ for over a decade
  • I had never coded under Linux
  • I had no idea where the settings were stored or how they were passed on to the screensavers

So the first thing I did was search on the Ubuntu volume looking for every single file containing the name of one of the screensavers: 4D Hypertorus

xavier@xavier-ubuntu:/$ locate hypertorus
/usr/lib/xscreensaver/hypertorus
/usr/share/applications/screensavers/hypertorus.desktop
/usr/share/man/man6/hypertorus.6x.gz
/usr/share/xscreensaver/config/hypertorus.xml

The search returned more matches than I would’ve expected:

  • The .desktop file contained the information to launch the screensaver. It is something like a shortcut under Windows.
  • The /usr/lib/xscreensaver/hypertorus was the actual binary for the screensaver.
  • The one under /man/ contains the manual pages for the screensaver explaining all its features and settings, which we were no longer able to modify (through a GUI of course).
  • And finally, the hypertorus.xml was the thing I was looking for!
    This file contains the description, in XML format, of how the settings dialog should look. The XML files for the screensavers under Linux are actually text-based descriptions of the settings GUI. Jackpot!

Here’s how one of these XML files looks like:

<?xml version="1.0" encoding="ISO-8859-1"?>
<screensaver name="xanalogtv" _label="XAnalogTV">
  <command arg="-root"/>
  <xscreensaver-image />
  <boolean id="showfps" _label="Show frame rate" arg-set="-fps"/>
  <_description>

XAnalogTV shows a detailed simulation of an old TV set showing various
test patterns, with various picture artifacts like snow, bloom,
distortion, ghosting, and hash noise. It also simulates the TV warming
up. It will cycle through 12 channels, some with images you give it,
and some with color bars or nothing but static.

Written by Trevor Blackwell; 2003.
  </_description>
</screensaver>

Notice this particular one has a single option “Show frame rate” of boolean type. This, of course, could be represented as a checkbox.

So I analyzed several XML files and quite rapidly was able to determine their structure and syntax. Enough to create an interpreter that would, somehow, create a dialog that allow me (us) to change the screensaver settings and then write those options in the .desktop file as command line parameters.

This was actually easier than I thought, I thought to myself. And it actually was.

The main problem now was, what language, editor (IDE?) and compiler I was going to use to create the application and the first thing that occurred to me was to use RealBasic. Huge mistake!
Anyway, I started coding the XML interpreter and it was a pure nightmare. That was the last day I used RealBasic and to this date, I hate the RealBasic IDE with all my guts.

I had toyed with Mono before (without much success) but just as an interpreter for applications I had written in Visual Studio. Of course, writing this application in Visual Studio and testing it under Linux/Mono was out of the question so I downloaded Monodevelop’s sources (as at the time, it wasn’t included in that version of Ubuntu) and compiled it. It took me almost a week to be able to do so… but that’s another story.

Development went quite fast, as I was already quite fluent in C#, and things worked quite well. In just a couple of days I had the first prototype (not even alpha) version of the application. But one major thing was missing: the preview!

I remembered seeing somewhere what you could display a screensaver as the background under Linux quite easily and I thought that I could perhaps use this same “hack” to redirect the output of the screensaver to a control (I mean, widget) in my GUI. And it happens that it was actually possible and quite easy to do, thanks to the “-window-id” command line option available on all (well, not all of them… continue reading) screensavers which allows you to redirect the output to any window-based control (I mean, widget), as long as you know its ID.

After some (actually, a lot) more Googling I found a nice little command that can report the ID of every single widget on a root window: xwininfo.
With some trials and lots of reading I was able to implement a function in the application that would run this command against the application’s root window to determine the ID of the widget that was going to be used as the screensavers viewport.

I did find it quite odd that Mono’s framework nor GTK#’s provided any means to do this!

I wanted to include the function’s code here but I have lost the original sources (from that time) so, instead, I’ll post the current version which is considerably more efficient (thanks to Chris Halse Rogers) and it also allows screensavers that do not support the “-window-id” command line parameter to appear in the preview viewport.

This is the function used to obtain the ID of the widget

public static ulong GetPreviewWindowID(DrawingArea da) {
	if(da.IsRealized) {
		ulong id = gdk_x11_drawable_get_xid(da.GdkWindow.Handle);
		string hex = id.ToString("X");
		hex = hex.Substring(hex.Length - 8);
		id = ulong.Parse(hex, System.Globalization.NumberStyles.HexNumber);
		return id;
	}
	throw new ApplicationException ("Attempted to get preview window ID before realizing it");
}

And this is the function that generates/starts the preview:
Notice the use of the OnScreenWithPipes() function. I got the idea of using it after reviewing the source code of the actual gnome-screensaver application.

private void StartPreview() {
	StopPreview();

	try {
		previewProcess = new Process();
		previewProcess.StartInfo.FileName = selectedScreenSaver.Binary;
		previewProcess.StartInfo.RedirectStandardOutput = true;
		previewProcess.StartInfo.RedirectStandardError = true;
		previewProcess.StartInfo.UseShellExecute = false;
		previewProcess.StartInfo.Arguments = "--help";
		previewProcess.Start();

		string output = previewProcess.StandardOutput.ReadToEnd() + previewProcess.StandardError.ReadToEnd();
		previewProcess.WaitForExit();

		// TODO: All screensavers should be started using the OnScreenWithPipes method.
		// Need to investigate why those that support -window-id do not like that method...
		if(output.Contains(MainClass.WIN_REDIR)) {
			previewProcess.StartInfo.Arguments = String.Format("{0} {1} {2}", selectedScreenSaver.CommandLineOptions, MainClass.WIN_REDIR, previewWindowID);
			previewProcess.Start();
		} else {
			bool result;
			int childPId;
			int stdOutput;
			int stdError;
			int stdInput;

			List<string> arg = new List<string>();
			arg.Add(selectedScreenSaver.Binary);
			if(selectedScreenSaver.CommandLineOptions != "") arg.Add(selectedScreenSaver.CommandLineOptions);

			result = Gdk.Spawn.OnScreenWithPipes(ssPreviewArea.Screen,
													null,
													arg.ToArray(),
													GetEnvVars(),
													GLib.SpawnFlags.SearchPath | GLib.SpawnFlags.DoNotReapChild,
													null,
													out childPId,
													out stdInput,
													out stdOutput,
													out stdError);

			if(result) previewProcess = Process.GetProcessById(childPId);

		}

		if(fullScreenWin != null) {
			fullScreenWin.ScreensaverTitle = selectedScreenSaver.Title;
		}

	} catch(Exception e) {Console.WriteLine(e.Message);}
}

So, after successfully implementing the preview functionality I had a fully functional replacement for the crippled gnome-screensaver application. Yey!

xFX Screensaver Settings

xFX Screensaver Settings

Many things have changed since then, specially thanks to the contributions made by Chris Halse, but the main purpose of the application remains the same: to give back to the user what once was his.

Oh, and after all the fuss about a “settings” button you will notice that the application doesn’t actually have a “settings” button, instead, it has an “options” button. The button was renamed to “options” based on a suggestion from one of the package maintainers for Ubuntu.

xFX Screensaver Settings for Gnome (x86 DEB Package) (2186 downloads )
xFX Screensaver Settings for Gnome (x64 DEB Package) (1798 downloads )
xFX Screensaver Settings Source Code (Mono/C#/Gtk#) (1595 downloads )
xFX Screensaver Settings for Gnome (Binary Package) (1711 downloads )

Update: Please always refer to the latest post about Screensaver Settings to make sure you always obtain the latest version.