Aus Linux-Magazin 12/2009

Plasmoide programmieren in C++

Ozphoto, Fotolia.com

Wer den Plasma-Desktop seines KDE 4 mit eigenen kleinen Anzeigetafeln alias Plasmoiden spicken möchte, die Daten aus den Tiefen des Systems auf den Desktop zaubern, dem gibt dieser Artikel den Stab in die Hand. Vorsicht: Die sich eröffnenden Möglichkeiten entfalten womöglich Sogwirkung. Verzauberungsgefahr !

Bei KDEs Wechsel von Version 3 zu 4 war der Plasma-Desktop [1] eine große Veränderung. Statt eines Hintergrundbilds mit Icons ist der jetzige Desktop eine Heimstatt für Plasmoide, auch Desktop-Widgets genannt. Sie setzen Icons, inszenieren eine Slideshow, zeigen den RSS-Feed an oder twittern. Mit oder ohne Spezialeditor (siehe Kasten “Der Plasmoid-Editor Plasmate”) ist es auch nicht schwer, eigene Plasmoide zu schreiben.

Der Plasmoid-Editor
Plasmate

Dieser Artikel leitet C++-Programmierer zu ihrer ersten Plasma-Desktopapplikation. Plasmoide können aber auch in anderen Sprachen entstehen, zum Beispiel Javascript, Ruby oder Python. Um die Plasmoid-Erstellung zu erleichtern, hat das KDE-Projekt den Plasmoid-Editor Plasmate gestartet (Abbildung 1, [4]). Eine installierte KDE-Version 4.3 vorausgesetzt ist der Code im SVN-Repository von KDE-Playground erhältlich:

svn co svn://anonsvn.kde.org/home/kde/trunk/playground/base/plasma/plasmate 

Der Plasmoid-Editor ist noch nicht als stabil freigegeben. Mit der Beschreibung in der KDE-Dokumentation Techbase können Neugierige ihn jedoch schon mal ausprobieren.

Abbildung 1: Der Editor Plasmate zeigt im oberen Teil die bearbeitete Datei, im mittleren Teil den Programmtext und im unteren Teil außerdem eine Vorschau.

Abbildung 1: Der Editor Plasmate zeigt im oberen Teil die bearbeitete Datei, im mittleren Teil den Programmtext und im unteren Teil außerdem eine Vorschau.

Wer das möchte, braucht eine KDE-4-Entwicklungsumgebung mit den passenden Bibliotheken und Headern sowie GCC [2]. Für diesen Artikel kam ein Kubuntu 9.04 mit KDE 4.2 zum Einsatz. Wer Plasmoide anwendet, braucht einen Plasma-Desktop und das aus den Programmdateien für die eigene KDE-Version erzeugte Binary. Auf dem FTP-Server des Linux-Magazins [3] befinden sich die Programmierbeispiele dieses Artikels in Quellcode-Form, die sich Interessierte – alternativ zum nachprogrammieren – nach der Anleitung dieses Artikels kompilieren und installieren dürfen.

So tickt Plasma

Der Plasma-Desktop besteht grundsätzlich aus zwei Ebenen. Die eine ist für die Datenquellen oder Data Engines zuständig, die andere kümmert sich um die Visualisierung. Das Trennungsgebot ist entscheidend, denn so kann der Plasmoid-Programmierer dieselben Daten auf verschiedene Weisen darstellen, ohne jedes Mal die Datenquelle neu zu implementieren.

Dank der C++-Basisklasse namens »Plasma::DataEngine« beantworten die verarbeiteten Data Engines Anfragen, nehmen Minimalintervalle für Aktualisierungen entgegen und bewältigen asynchrone Ereignisse oder mehrere Werte einer Variablen. In gleicher Weise ist die Klasse »Plasma::Applet« Ausgangspunkt, um die kleinen Programme für den Plasma-Desktop zu erstellen. Aus diesen Applets entstehen zusammen mit einer Desktopdatei und einem grafischen Anstrich später Plasmoide.

Beispiel-Plasmoid: Belastungsampel

Als Beispiel zeigt ein einfaches Applet die durchschnittliche Systemauslastung pro Minute in Form einer Ampel (Abbildung 2). Ihre Lichter signalisieren leichte, hohe oder zu hohe Belastung mit den Farben Grün, Gelb und Rot. Eine Schnittstelle zu den benötigten Daten gibt es bereits: den KDE-Systemmonitor.

Abbildung 2: Das Beispiel-Plasmoid Loadlight in Aktion. Es indiziert minütlich die Systemauslastung mittels Ampelfarben.

Abbildung 2: Das Beispiel-Plasmoid Loadlight in Aktion. Es indiziert minütlich die Systemauslastung mittels Ampelfarben.

Der erste Schritt ist die Headerdatei (Listing 1). Zeile 9 leitet die Klasse »PlasmaLoadLight« von der Klasse »Plasma::Applet« ab. Die Konstruktoren »paintInterface()« und »init()« implementieren virtuelle Funktionen. Das entlastet den Programmierer von aufwändigen Initialisierungen in »init()«. Die Methode »paintInterface()« dient später dazu, die Visualisierung neu aufzubauen.

Listing 1:
Loadlight.h

01 #ifndef LOADLIGHT_H
02 #define LOADLIGHT_H
03 
04 #include <Plasma/Applet> 
05 #include <Plasma/DataEngine>
06 
07 class QSizeF;
08 
09 class PlasmaLoadLight : public Plasma::Applet
10 { 
11     Q_OBJECT
12 
13 public:
14     PlasmaLoadLight( QObject *parent,
15                      const QVariantList &args ); 
16     void init();
17     void paintInterface( QPainter *painter,
18        const QStyleOptionGraphicsItem *option,
19        const QRect &contentsRect ); 
20 
21 protected slots:
22     void sourceAdded( const QString &name ); 
23     void dataUpdated( const QString &sourceName, 
24         const Plasma::DataEngine::Data &data ); 
25
26 private:
27     double m_load;
28 };
29 
30 #endif // LOADLIGHT_H

Es folgen die zwei Slots »sourceAdded()« und »dataUpdated()«. Slots sind Rückfrage-Funktionen, die sich mit einem Signal verbinden. Zeile 27 deklariert die Private-Variable »m_load« dieser Klasse, die den zuletzt abgeholten Belastungsstand des Systems speichert.

In Listing 2 implementieren die Zeilen 4 bis 11 den Konstruktor. Der Einfachheit halber sind der Standardhintergrund und eine Anfangsgröße gesetzt. Der andere Teil der Klasseninitialisierung erfolgt in den Zeilen 13 bis 17 in der »init()«-Methode. Sie fordert die Datenquelle »systemmonitor« an und verbindet das Signal »sourceAdded()« mit dem gleichnamigen Slot. Das bewirkt, dass bei jeder vom Systemmonitor gemeldeten Information der Slot »sourceAdded()« in Aktion tritt: Die Datenquelle sendet dann ein Signal und ruft damit die Funktion »sourceAdded()« in den Zeilen 19 bis 29 auf.

Listing 2:
Loadlight.cpp

01 #include "loadlight.h"
02 #include <QPainter>
03 
04 PlasmaLoadLight::PlasmaLoadLight( QObject *parent,
05   const QVariantList &args )
06     : Plasma::Applet( parent, args ),
07     m_load( 0.75 )
08 {
09  setBackgroundHints( DefaultBackground );
10  resize( 100, 100 );
11 }
12 
13 void PlasmaLoadLight::init()
14 {
15  connect( dataEngine( "systemmonitor" ), SIGNAL(sourceAdded(QString)),
16              this, SLOT(sourceAdded(QString)) );
17 }
18 
19 void PlasmaLoadLight::sourceAdded( const QString &name )
20 {
21  if( name == "cpu/system/loadavg1" )
22  {
23    dataEngine( "systemmonitor" )->connectSource( name, this, 1000 );
24    disconnect( dataEngine( "systemmonitor" )
25                SIGNAL(sourceAdded(QString)),
26                this,
27                SLOT(sourceAdded(QString)) );
28   }
29 }
30 
31 void PlasmaLoadLight::dataUpdated( const QString &sourceName,
32 const Plasma::DataEngine::Data &data )
33 {
34  if( sourceName != "cpu/system/loadavg1" )
35      return;
36  if( data.keys().count() == 0 )
37      return;
38  m_load = data[data.keys()[0]].toDouble();
39  update();
40 }
41 
42 void PlasmaLoadLight::paintInterface( QPainter *painter,
43   const QStyleOptionGraphicsItem *option,
44   const QRect &contentsRect )
45 {
46  if( m_load < 0.5 )
47    painter->setBrush( Qt::green );
48  else if( m_load > 0.95 )
49    painter->setBrush( Qt::red );
50  else
51    painter->setBrush( Qt::yellow );
52    painter->drawEllipse( contentsRect );
53 
54   QFont f;
55   f.setPixelSize( contentsRect.height()/4 );
56   painter->setFont( f );
57   painter->drawText( contentsRect,
58                      Qt::AlignCenter,
59                      QString::number( m_load, 'f', 2 ) );
60 }
61 
62 K_EXPORT_PLASMA_APPLET(loadlight, PlasmaLoadLight)
63 
64 #include "loadlight.moc"

Hin und weg

Die hier verwendete Datenquelle des Systemmonitors listet die Informationen einfach auf. Bei anderen Datenquellen passiert es, dass ihr Ursprung auftaucht und wieder verschwindet, zum Beispiel ein Wechseldatenträger oder eine Netzwerkressource. Daher stellt die Data-Engine-Klasse auch das Signal »sourceRemoved()« bereit. Im Fall des Systemmonitors ist es jedoch überflüssig.

Sobald der Systemmonitor die Information »cpu/system/loadavg1« durchgibt (Zeile 21), verbindet sich das Plasmoid mit der Methode “connectSource()” zu seiner Datenquelle (Zeile 23). Die Argumente der Verbindung stehen von links nach rechts für den Namen der gewünschten Information (also »cpu/system/loadavg1«), dann für das Objekt, das die Information erhält, und für das Verbindungsintervall in Millisekunden. Zeile 23 besagt also, dass diese Funktion das Plasmoid jede volle Sekunde über den Index der durchschnittlichen Systembelastung auf dem Laufenden hält.

»connectSource()« enthält nur einen Zeiger auf das Plasmoid, keinen Slot für die Verbindung. Das setzt voraus, dass das empfangende Objekt einen Slot der folgenden Form anbietet:

dataUpdated(const QString &sourceName, const Plasma::DataEngine::Data &data) 

Wenn die angefragte Datenquelle neue Informationen parat hat, ruft sie im Plasmoid »dataUpdated()« auf (Zeilen 31 bis 40). Wichtig ist, dass das Anfrage-Intervall nur Hinweischarakter hat. Die Datenquelle selbst kann dieses Intervall auch vergrößern. Ein RSS-Feeder würde zum Beispiel nicht öfter als alle 10 Sekunden nach neuen Daten suchen. Auch wenn die angefragten Daten erst über ein Netzwerk wandern, ist die verstreichende Zeit bis zu ihrem Eintreffen variabel. Der Slot »dataUpdated()« kann also zu anderen Zeitpunkten als dem angegebenen aktiv werden.

Das ist zum Beispiel zu berücksichtigen, wenn die abgefragten Informationen in ein Diagramm wandern, dessen eine Achse die Zeit darstellt: Die verstrichene Zeit zwischen den Anfragen ist nicht unbedingt die gleiche wie zwischen den ankommenden Daten. Die Datenquelle ruft somit den Slot zu einer ganz anderen Zeit auf als angegeben, je nachdem, wie sie arbeitet.

In den Zeilen 31 bis 40 fragt die Funktion alle Datenquellen ab, die erreichbar sind. Die Informationen wandern als Hashtabelle mit Key-Value-Paaren zurück. Eine Eigenart der Systemmonitor-Datenquelle ist, dass einige unbrauchbare Daten zurückkommen, bevor die erwarteten eintreffen. Um Probleme zu vermeiden, schaut das Programm also zunächst nach, ob sich ein Key in den ankommenden Daten befindet. In diesem Fall ist ein Wert ausreichend. Wenn also ein Key zurückkommt, aktualisiert der erste Wert die Member-Variable »m_load«. Die Update-Funktion löst anschließend ein Repaint-Ereignis aus. Dies führt zum Aufruf der Methode »paintInterface()«, die ab Zeile 42 je nach ankommendem Wert verschiedenfarbige Kreise malt und den Wert als Text hineinschreibt.

Es werde Plasma

Schließlich ist das Geschehen noch als Plasma-Applet zu exportieren (Zeile 62). Das Makro »K_EXPORT_PLASMA_APPLET« nimmt zwei Argumente entgegen: einen Namen für die Bibliothek und einen Namen für die Klasse. Daran schließt sich noch die Moc-Datei des Meta Object Compilers an, denn im Beispiel liegt nur eine einzige Klasse vor. Gibt es mehrere Moc-Dateien, übernimmt meist Cmake ihr Handling. Die Moc-Datei enthält C++-Programmtext, der Signale, Slots und andere Qobjekt-Eigenschaften umsetzt. Näheres zum Moc, der Qt-Klassen nach C++ übersetzt, unter [5].

Der erste Schritt – der Programmtext – ist erledigt. Die Klasse als Applet zu exportieren, ist Schritt zwei auf dem Weg zum Plasmoid. Damit der Plasma-Desktop das Progrämmchen findet, braucht es eine Desktopdatei – das ist Schritt drei. Die Desktopdatei ist eine Liste von Schlüssel-Wert-Paaren (Tabelle 1).


Die Desktopdateien für die hier besprochenen Projekte liegen dem Quellcode auf dem FTP-Server bei. Jetzt fehlt nur noch, das Plasmoid zu bauen und in KDE zu installieren.

Nur noch installieren

KDE benutzt Cmake zum Bauen [6]. Die Datei »CMakeLists.txt« steuert Cmakes Verhalten. Diese Datei entspricht weitgehend einem Grundgerüst, in dem lediglich die richtige Bibliothek stehen muss. Sie ist ebenfalls beim Quellcode dieses Projekts auf dem Server dabei.

Um das Plasmoid zu bauen, muss der Entwickler das Präfix seiner KDE-Installation wissen. Er erfährt es mittels:

kde4-config --prefix

Im so gefundenen Installationsverzeichnis treten Cmake und Make in Aktion:

cmake -DCMAKE_INSTALL_PREFIX=KDE-Präfix
make

Um das Plasmoid zu installieren, gilt es, »make install« mit Root-Rechten und anschließend »kbuildsycoca4« auszuführen, damit KDE die Liste der Plasmoide aktualisiert:

sudo make install
kbuildsycoca4

Den Neuzugang installiert und ordnungsgemäß in KDEs Buchführung aufgenommen zu haben, hat jedoch noch keine Auswirkungen auf den derzeitigen Plasma-Desktop. Um das Plasmoid auf den Desktop zu bringen, bedarf es einer neuen Plasma-Session. Dazu startet der Anwender die KDE-Sitzung neu. Auf der Kommandozeile dient dazu bei KDE 4.2 der Befehl:

kquitapp plasma && plasma

Bei KDE 4.3:

kquitapp plasma-desktop && plasma-desktop

Um alle aktuellen Plasmoide samt ihrer Beschreibung anzuzeigen, hilft die »list«-Option des Plasmoidviewers [7]. Das hier besprochene Loadlight erscheint bei folgendem Aufruf:

plasmoidviewer --list | grep loadlight

Alternativ nimmt der Viewer den Namen des gewünschten Plasmoids als Argument entgegen, in diesem Fall also:

plasmoidviewer plasma-applet-loadlight

Der bisher beschriebene Weg ist ein recht geradliniger Prozess, Daten einzusammeln, das Programm-Interface zu schreiben und das Ganze an den Desktop zu übermitteln. Wer sich darüber hinausgehende Funktionen auf den Desktop holen möchte, erstellt sich seine eigenen Datenquellen. Der beste Weg dahin führt über den Plasma Engine Explorer. Er zeigt die vorhandenen Datenquellen an (Abbildung 3).

Abbildung 3: Der Plasma Engine Explorer zeigt die vorhandenen Datenquellen an, hier zum Beispiel die Betriebszeit des Systems.

Abbildung 3: Der Plasma Engine Explorer zeigt die vorhandenen Datenquellen an, hier zum Beispiel die Betriebszeit des Systems.

Beispiel-Datenquelle: Zufällige Zahl

Eine Datenquelle zu schreiben ähnelt dem Vorgehen, ein Plasmoid zu schreiben. Die »CMakeLists.txt« und die Desktopdatei sind bis auf zwei Unterschiede die gleichen: Der Service-Typ der Desktopdatei lautet »Plasma/DataEngine«, die C++-Basisklasse »Plasma::DataEngine« statt »Plasma::Applet«. Es ist also ein zweites Plasma-Interface zu schreiben. Als Beispiel dient eine Datenquelle, die eine zufällige Zahl ausgibt.

Die Klassendeklaration in Listing 3 zeigt das einfachste vorstellbare Gerüst für Datenquellen. Die Anforderungen von Seiten Plasmas sind gering. Der Programmierer hält sich darum nicht mit Plasma auf, sondern konzentriert sich auf die Schnittstelleneigenschaften seiner gewünschten Daten.

Listing 3:
Randomnumberengine.h

01 #ifndef RANDOMNUMBERENGINE_H
02 #define RANDOMNUMBERENGINE_H
03 
04 #include <Plasma/DataEngine>
05 
06 class RandomNumberEngine : public Plasma::DataEngine
07 {
08     Q_OBJECT
09 
10 public:
11     RandomNumberEngine( QObject *parent,
12        const QVariantList &args );
13     QStringList sources() const;
14 
15 protected:
16     bool sourceRequestEvent(
17        const QString &name );
18     bool updateSourceEvent(
19        const QString &name );
20 };
21 
22 #endif // RANDOMNUMBERENGINE_H

Listing 4 beginnt mit dem Konstruktor der neuen Data-Engine-Klasse (Zeilen 4 bis 8). An dieser Stelle gibt der Programmierer das Mindestintervall an, wenn er die Datenquelle nicht zu oft anzapfen möchte. Zum Beispiel würden nur zweimal pro Sekunde Anfragen an die Data Engine ergehen, wenn er folgende Zeile einfügt:

setMinimumPollingInterval(500); 

Im Fall der zufälligen Zahlen ist das jedoch nicht nötig. Die Datenquelle in den Zeilen 10 bis 14 ist eine zufällige Zahl, die von keinem konkreten Ort stammt. Der Programmtext deklariert stattdessen einfach, dass die folgenden Funktionen mit der Nachfrage nach »Number« umgehen können.

Listing 4:
Randomnumberengine.cpp

01 #include "randomnumberengine.h"
02 #include <QDateTime>
03 
04 RandomNumberEngine::RandomNumberEngine(
05   QObject *parent, const QVariantList &args )
06   : Plasma::DataEngine( parent, args )
07 {
08 }
09 
10 QStringList RandomNumberEngine::sources()
11    const
12 {
13     return QStringList() << "Number";
14 }
15 
16 bool RandomNumberEngine::sourceRequestEvent(
17 const QString &name )
18 {
19     if( name != "Number" )
20        return false;
21 
22     qsrand( QDateTime::currentDateTime()
23             toTime_t() );
24     return updateSourceEvent( name );
25 }
26 
27 bool RandomNumberEngine::updateSourceEvent(
28    const QString &name )
29 {
30     if( name != "Number" )
31        return false;
32     setData( name, qrand() );
33     return true;
34 }
35 
36 K_EXPORT_PLASMA_DATAENGINE(randomnumber,
37    RandomNumberEngine)
38 
39 #include "randomnumberengine.moc"

Alle Arbeit erledigen die beiden Funktionen »sourceRequestEvent()« und »updateSourceEvent()«. Wenn das Plasmoid erstmals eine zufällige Zahl anfragt, ist dies das Request-Ereignis. Die Methode »qrand()« initialisiert noch den Zahlengenerator. Danach finden nur noch Update-Ereignisse statt.

Die Funktion »updateSourceEvent()« (Zeile 27) gibt keinen Wert direkt an das aufrufende Plasmoid zurück. Stattdessen nutzt sie »setData()«, um neue Daten bekanntzugeben. Damit erleichtert es die Funktion »updateSourceEvent()«, auch asynchrone Data Engines zu schreiben: Der Programmierer macht die Anfrage vom Update-Event abhängig und ruft »setData()« auf, um an die Daten zu kommen.

Die Datenquelle zu bauen und zu installieren, läuft genauso ab wie beim Plasmoid. Als Testwerkzeug dient der Data Engine Explorer (Abbildung 4).

Abbildung 4: Die neue Datenquelle, die eine zufällige Zahl generiert, beweist ihr Können im Data Engine Explorer.

Abbildung 4: Die neue Datenquelle, die eine zufällige Zahl generiert, beweist ihr Können im Data Engine Explorer.

Der eigene Desktop

Wem es jetzt plasmagisch in den Fingern prickelt, der sollte für die erfolgreiche Magierlaufbahn zunächst etwas Einfaches wie eine Zeitquelle schreiben oder weitere Monitoringdaten einsammeln. Zu denken wäre zum Beispiel an Checks, ob das Netzwerk intakt ist, der Drucker angeschlossen ist oder auch, ob eine bestimmte IP-Nummer auf Pings reagiert. Wer einen Webserver laufen hat, könnte darauf seine Daten anderen KDE-Nutzern zur Verfügung stellen.

Das Plasma-Interface ist einfach zu implementieren, seinen Möglichkeiten setzt nur die Fantasie Grenzen. Weitere Anregungen stecken in einer Plasmoid-Sammlung beim KDE-Projekt [8]. Die KDE Techbase hilft beim weiteren Erschließen der plasmoiden Zauberkunst [9]. Das Lieblings-Plasmoid des Autors ist übrigens der Device Notifier. (ake)

Infos

[1] Plasma: [http://plasma.kde.org]

[2] KDE vorbereiten: [http://techbase.kde.org/Getting_Started/Build/KDE4]

[3] Beispielprogramme dieses Artikels: [ftp://ftp.linux-magazin.de/pub/listings/magazin/2009/12/plasmoid]

[4] Plasmoid-Editor Plasmate: [http://techbase.kde.org/Projects/Plasma/PlasMate]

[5] Meta Object Compiler: [http://techbase.kde.org/Development/Tools/Automoc4]

[6] Cmake: [http://techbase.kde.org/Development/CMake]

[7] Plasmoidviewer: [http://aseigo.blogspot.com/2009/04/plasmoidviewer.html]

[8] Plasmoid-Sammlung beim KDE-Projekt: [http://techbase.kde.org/Projects/Plasma/Plasmoids]

[9] Weitere Plasma-Tutorials: [http://techbase.kde.org/Development/Tutorials/Plasma]

Der Autor

Johan Thelin arbeitet als Berater und freiberuflicher Autor. Seine Leidenschaft gilt eingebetteten Systemen und Qt. Er spielt jedoch mit allem zwischen Elektronik und Webentwicklung herum.

LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben