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.
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 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 : public QWidget { public: Saver(WId window); protected: void timerEvent(QTimerEvent*); // The event handler. private: QPtrListstars; // The list of all stars. };
class Star { public: Star(int x, int y, int c); void paint(QPainter *p); void drawPixel(QPainter *p, int x, int y); private: int sx; // X-coordinate. int sy; // Y-coordinate. int color; // Brightness. Corresponds to RGB-color: (color, color, color). int counter; // Defines the necessity of star's moving. };
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()).
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); }
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.
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.
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.