Screen saver with Qt

Andi Peredri

The purpose of this article - to show, that development of the screen saver with Qt is a simple task, which can be forced by beginners. The questions of integration of applications with desktop-files, command line's arguments and identifiers of X Window System's windows on an example of QStars program are also considered in this article.

KDesktop

The KDE-program kdesktop provide the starting of the screen savers and screen locking functions. For this purpose it carries out the following actions:

The developer of screen saver must do:

These steps are considered in more detail below.

The screen saver QStars

The screen saver QStars simulates flight of the spacecrafts in space. The library Qt 3.2 was used for its development. The testing was carried under KDE 2.2. QStars sources can be loaded from: http://qt.osdn.org.ua/qstars-0.1.tar.gz.

For realization of the screen saver we use two classes:

Class Saver

Constructor Saver::Saver uses the existing window with specified X Window System identifier WId. WId is platform-dependent identifier of a window. It is determined in qwindowdefs.h as unsigned long for X11. Function create() is used for window's initialization:

    void QWidget::create( WId window = 0, bool initializeWindow = TRUE, 
					  bool destroyOldWindow = TRUE );

Default arguments inform, that it is necessary to initialize a new window and to destroy the old window. If the identifier of a window is equal 0, Qt will create a new window for the widget. This is a source of class's constructor:

    static int w = 600;		// Default window's size.
    static int h = 420;
    static int border = 200;	// Area behind the left border of a window.
    static QImage background;	// Background image of spacecraft.

    Saver::Saver(WId window) : QWidget()
    {
        if(window)	// It is necessary to use an existing window?
        {
	    create(window);		// We initialize a window.
	    w = width();
	    h = height();
	}
	else setFixedSize(w,h);		// Set window's size as 600x420.

	QPixmap pixmap(w, h);		// Create the background image.
	QPainter p(&pixmap);
	p.fillRect(pixmap.rect(), black);

    	srand(time(0));		// Initialization of the generator of random numbers.
	QSettings settings;	// Loading of kept configuration.

	// Initialization of the speed of stars.
	// A range of possible values: 0 - 3.
	unsigned int speed = settings.readNumEntry("/QStars/Speed", 2);

	// It is necessary to show a spacecrafts?
	bool showships = settings.readBoolEntry("/QStars/ShowShips", true);

	// If the size of a window is allow, we show a spacecraft.
	if(showships && w > 300 && h > 200)
	{
	    // We choose the random image of the ships ...
	    QString file = "ship" + QString::number(rand()%5+1) + ".png";
	    QPixmap ship = QPixmap::fromMimeSource(file);
	    
	    // ... also we place it in the centre of the background image.
	    int px = (w - ship.width()) / 2;
	    int py = (h - ship.height()) / 2;
	    p.drawPixmap(px, py, ship);
	}
	// We initialize the background image for a window.
	background = pixmap;
	setErasePixmap(pixmap);

	// We create necessary amount of stars ...
	int numstars = (w+border) * h / 300;
        for(int i=0; i < numstars; i++)
	{
	    // ... And allocate them in irregular intervals on window's area.
	    int x = rand() % (w+border) - border;
	    int y = rand() % h;
	    
	    // Brightness of stars is in a range from 30 up to 255 units.
	    // Bright stars should be less.
    	    int color = (rand()%16) * (rand()%16) + 30;
	    stars.append(new Star(x, y, color));
        }
	// We start the timer with an interval from 20 up to 50 ms.
	startTimer(50/(speed+2));
    }

Before the end of the work the constructor calls the function startTimer():

    int QObject::startTimer( int interval );

Thus the timer events with an interval interval ms will be created. If an interval is small (approximately < 20 ms), Qt will not have time to generate event, and some of them will be missed. It is necessary to redefine the virtual function timerEvent() for processing timer's events:

    void Saver::timerEvent(QTimerEvent*)
    {
	QPainter p(this);

        for(Star* star = stars.first(); star; star = stars.next())
	    star->paint(&p);
    }

Function timerEvent() must inform all stars, that it is necassary to redraw itself in a new position.

At renewal of the user activity the work of screen saver will be finished. Therefore we do not need to redefine next event handlers in class Saver: (keyPressEvent()) and (mousePressEvent()).

Class Star

It is a simple class which provides moving and painting a star to a new position. The speed of moving of a star depends on its brightness: bright stars move faster. The class supports stars with sizes 1x1 and 2x2 (with brightness 255).

    Star::Star(int x, int y, int c)
    {
        sx = x;
        sy = y;
        color = c;
        counter = (255 - color) >> 4;
    }

    void Star::drawPixel(QPainter *p, int x, int y)
    {
	// We do not draw atop of a spacecraft.
        if(background.valid(x, y) && !qGray(background.pixel(x, y)))
	    p->drawPoint(x, y);
    }

    void Star::paint(QPainter *p)
    {
        if(sx < -border)	// If the star left for the edge of the screen ...
        {
	    sx = w;		// ... we relocate it in extreme right position
	    sy = rand() % h;	// also we change its Y-coordinate.
    	    return;
        }

        if(counter--) return;	// If the counter is distinct from 0, we do not move a star.
        counter = (255 - color) >> 4;	// We select by practical consideration.

        p->setPen(Qt::black);
	if(color==255)		// The star with brightness 255 has the size 2x2.
        {			// We clean a trace.
	    drawPixel(p, sx+1, sy  );
	    drawPixel(p, sx+1, sy+1);
        }
	else drawPixel(p, sx, sy);

        sx--;			// We move a star to the left on one pixel ...
	p->setPen(QColor(color, color, color));
        drawPixel(p, sx, sy);	// ... Also we display it in new position.
	if(color==255) drawPixel(p, sx, sy+1);
    }

Starting and work

The screen saver should support four modes which are specified as command line arguments:

Function main is engaged in analysis of arguments of the command line. Also this function defines the identifier of a root window, shows the usage information, provides setting and saving of the screensaver's parameters, creates and shows the main window of the application. The source code of function main() it is presented below:

    int main(int argc, char** argv)
    {
        QApplication app(argc, argv);
    
        QString mode = "-demo";		// Default mode.
	if(app.argc() > 1) mode = app.argv()[1];

        WId window = 0;			// X Window System window's identifier.

	if(mode == "-window-id" && app.argc() > 2)
	    // We use the identifier of an existing window.
	    window = QString(app.argv()[2]).toULong();
	else if(mode == "-root")
	    // We use the identifier of a root window.
	    window = DefaultRootWindow(qt_xdisplay());

        if(mode == "-help")	// Show usage information.
	{
	    qWarning("Usage: " +QString(app.argv()[0])+ " [options]\n"
		"QStars Screensaver\n"
		"Options:\n"
		"  -help            Show help about options.\n"
		"  -setup           Setup screen saver.\n"
		"  -window-id wid   Run in the specified XWindow.\n"
		"  -root            Run in the root XWindow.\n"
		"  -demo            Start screen saver in demo mode. [default]\n");
	}
	else if(mode == "-setup")	// Setup dialogue.
	{
	    // We read the kept configuration.
	    QSettings settings;
	    unsigned int speed = settings.readNumEntry("/QStars/Speed", 2);
	    bool showships = settings.readBoolEntry("/QStars/ShowShips", true);

	    // We create a setup dialogue.
	    QDialog dialog;
	    dialog.setCaption("QStars Setup");
	    QVBoxLayout vbox(&dialog,5);

	    QVGroupBox box1("Setup", &dialog);
	    QLabel label1("Speed:", &box1);
	    
	    // A slider for setting the star's speed.
	    QSlider slider(0, 3, 1, speed, Qt::Horizontal, &box1);
	    slider.setTickmarks(QSlider::Left);

	    QCheckBox check("Show Ships", &box1);
    	    check.setChecked(showships);
    	    vbox.addWidget(&box1);
	    QVGroupBox box2("About", &dialog);
	    QLabel label2("QStars Screen Saver Version 0.1\n"
		"Copyright (C) 2004 Andi Peredri <andi@ukr.net>\n\n"
		"Homepage: \thttp://qt.osdn.org.ua\n"
		"Graphics: \tXShipWars Project\n"
		"License:  \tGNU General Public License", &box2);
            vbox.addWidget(&box2);

	    QHBoxLayout hbox(&vbox);
	    hbox.addStretch();
	    QPushButton button1("OK", &dialog);
    	    button1.setFocus();
	    dialog.connect(&button1, SIGNAL(clicked()), &dialog, SLOT(accept()));
	    hbox.addWidget(&button1);
	    QPushButton button2("Cancel", &dialog);
	    dialog.connect(&button2, SIGNAL(clicked()), &dialog, SLOT(reject()));
	    hbox.addWidget(&button2);

	    // The user has confirmed the made settings?
	    if(dialog.exec() == QDialog::Accepted)
	    {
		// ... then we keep a configuration.
		settings.writeEntry("/QStars/ShowShips", check.isChecked());
		settings.writeEntry("/QStars/Speed", slider.value());
	    }
	    // ... An exit ...
	}
	else if(mode == "-window-id" || mode == "-demo" || mode == "-root")
	{
	    // We show the screen saver in one of modes.
	    Saver saver(window);
    	    saver.show();
	    app.setMainWidget(&saver);
	    return app.exec();
        }
	else
	{
	    // Invalid command line's argument.
	    qWarning("Unknown option: " + mode +
		"\nUse -help to get a list of available command line options.\n");
	}
        return 0;
    }

If the program is started with parameter -setup, QStars will open a dialogue for parameter's settings (speed of moving of stars and necessity of display of a spacecraft):

If the screen saver is started with parameter -root, then for defining of the root window's identifier macros DefaultRootWindow() will be used. As a parameter macros accepts the pointer on the display, which can be received with function qt_xdisplay():

    Q_EXPORT Display *qt_xdisplay();	// This function is defined in qwindowdefs.h

Initially macros DefaultRootWindow() is defined in file X11/Xlib.h. But its existing realization doesn't support work with a virtual root window which is used by kdesktop for starting of screen saver. Therefore for detection of a virtual root window it is necessary to use macros DefaultRootWindow() from a file vroot.h. The file vroot.h can be found in a package kdelibs-dev under /usr/include/kde/kscreensaver_vroot.h. It does not require the KDE-libraries for its compilation. So it is included in the QStars's distributive.

If you use KDE as a desktop environment, that, having started QStars from the command line with parameter -root, you will not see any visual effects. Temporarily suspend the process kdesktop.

QStars.desktop

Before adding the information about the screen saver to KDE Control Center, it is necessary to create a description file (desktop entry file). There are some specified points in this file: type of an element (the application, shortcut, the device or the directory), the starting command, supported MIME-types and about 25 others parameters which are in more detail considered in document Desktop Entry Standard. The desktop entry file QStars.desktop for QStars screen saver is presented below:

    [Desktop Entry]
    Type=Application
    Name=QStars
    Exec=qstars
    Icon=kscreensaver
    Actions=Setup;InWindow;Root

    [Desktop Action Setup]
    Name=Setup...
    Exec=qstars -setup
    Icon=kscreensaver
 
    [Desktop Action InWindow]
    Name=Display in specified window
    Exec=qstars -window-id %w
    NoDisplay=true
 
    [Desktop Action Root]
    Name=Display in root window
    Exec=qstars -root
    NoDisplay=true

The file begins with obligatory section [Desktop Entry]. We shall explain used parameters:

To inform KDE Control Center about our screen saver, it is necessary to place a desktop-file in one of the following directories:

For this purpose the directory of installation is defined in the project-file qstars.pro:

    share.path  = /usr/share/applnk/System/ScreenSavers
    share.files = QStars.desktop
    INSTALLS   += share

After running of make install command QStars will appear in the list of known screen savers. QStars will be started in a preview window of screen savers with parameter like -window-id 37749288. In reply to pressing of button Setup QStars will start with parameter -setup and setup dialogue will be opened. After saving of configuration parameters the screen saver will be automatically restarted in preview window.

The conclusion

Thus, for creating of screen saver with Qt we needed to write about 200 lines. Using QStars as a pattern, you can create the own screen saver easily and quickly. Simplicity of integration with KDE is obliged to modularity of KDE Control Center. For this purpose we had to create a desktop-file, to provide command line support, and to use X Window System's identifiers. This techniques also can be used for integration Gtk/Gnome and Qt/KDE applications with success.