Cover story KDE Plasma
A developer’s guide to Plasmoids Creating Plasmoids Cosmi, Fotolia
We take a peek at how to create your own plasmoids for the latest KDE desktop, giving you the power to build the perfect active desktop environment. By Johan Thelin
hen the KDE desktop made in a good model-view architecture, but I’ll show you how to lay out some boiler- the transition from version 3 also provides the ability to visualize plate code. Wto 4, many things changed. common data in multiple ways without To start, look at the header file shown One of the larger changes was the intro- having to re-implement the code for the in Listing 1. In the file, the class Plasma duction of the plasma desktop. Instead data source. LoadLight is derived from the Plasma:: of a background image with icons, the In this separation, the data engines Applet class. The constructor, paintInter desktop is now host to plasmoids, also have been equipped with common good- face, and init provide implementations known as desktop widgets. ies, such as support for polling, control of virtual functions. The idea with init is Plasmoids can do everything from of the minimum polling period, the abil- to push complex parts of the initializa- showing icons or presenting a slide show ity to support asynchronous events, a tion from the constructor to init. The of your favorite pictures to monitoring mechanism to provide multiple values, paintInterface constructor is called when your RSS feeds and letting you update and so on. All of these abilities are given the visualization needs to be updated. your Twitter feed. In this article, I will from simply inheriting the Plasma::Da Then follow the two slots sourceAdded look at how to develop plasmoids as taEngine base class. and dataUpdated. A slot is a callback well as the architecture behind plasma. In the same manner, by inheriting function that you can connect to a sig- from Plasma::Applet, you get a good nal. More on this later. The last part of Architecture starting point for creating applets. These the class contains the m_load private The plasma desktop is built from several applets, when combined with a desktop that contains the current load level. components. Two are of particular inter- file, graphics and more, are what make The interesting code follows in the im- est: the visualization and the data up plasmoids. So, I’ll get started and plementation of the class shown in List- sources. The important thing here is not build a plasmoid of my own. ing 2. Starting from the top, lines 5-12 these two parts specifically but the fact show the constructor implementation. that there are A Plasmoid Here, I simply tell plasma to use the two parts. The plan is to build a simple system load standard background and set a reason- The reason monitoring applet. It will show the load for this is that average from the last minute in a traffic INFO the plasmoid light, as shown in Figure 1. The light in- [1] Listings for this article: ftp://ftp. visualization dicates low, high, and too high load lev- linux‑magazin.com/pub/listings/ usually is sep- els with the colors green, yellow, and magazine/114/plasmoid/ arated from red, respectively. Luckily, a data engine [2] KDE TechBase: Figure 1: The load light the data. This is already available for this: the system http://techbase. kde. org plasma applet in action. gives results monitor. Before I go into detail about it,
36 ISSUE 114 May 2010 KDE Plasma Cover story
able starting size. The other part of the nect me to the source by way of the con gine). Also, this controls the period be- class’s initialization takes place in the nectSource method. The arguments of tween requests, not necessarily the pe- init function (lines 14-18). Here, I re- this function are, from left to right: riod between new data. quest the data engine systemmonitor and source name, receiving object, and sam- For instance, imagine an RSS feed- connect the signal sourceAdded to a slot ple period in milliseconds. So, in line 24, monitoring data engine. It might not with the same name in the applet. This I ask to be updated about the cpu/sys make sense for this data engine to re- means that for every source of informa- tem/loadavg1 every second. quest new data at a shorter period than tion that the system monitor data engine The connectSource method only takes 10 seconds. This means that you will not adds, the sourceAdded slot of the applet a receiver object pointer, not a slot to get shorter periods. Also, when making will be called. This brings me to the next connect to. This is because it assumes a request across the network, the reply is function in the implementation: source the receiver to have a slot with the fol- asynchronous, and the time for the re- Added (lines 20-28). The data engine lowing signature present: sult to return is arbitrary. Thus, your emits a signal that will trigger this func- dataUpdated slot might be triggered at tion once for each source it makes avail- dataUpdated(U uneven intervals as the data ticks in. able. The system monitor data engine const QString &sourceName, const U Next in the source code, the dataUp simply lists its sources, whereas other Plasma::DataEngine::Data &data) dated method implementation appears engines could have sources that appear in lines 30-41. Because this slot is called and disappear during its lifetime. Exam- When the requested source has new for all data sources, I start by ensuring ples include data engines supporting re- data available, the dataUpdated slot is that it is indeed the expected source cpu/ movable hardware and networking ser- triggered. It is important to understand system/loadavg1. The data is then given vices that appear and disappear. There- that the sampling period given when as a hash table with key-value pairs. A fore, the sourceRemoved signal is avail- connecting to a able from the data engine class, but I ig- source is a hint nore it in this case. (i.e., the period Listing 1: loadlight.h When the source cpu/system/loadavg1 can be forced lon- 01 #ifndef LOADLIGHT_H is added, I ask the data engine to con- ger by the data en- 02 #define LOADLIGHT_H
03
04 #include
05 #include
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 ); Figure 2: The Plasma Engine Explorer showing the systemmonitor 16 data engine. 17 void init();
18 PlasMate 19 void paintInterface( QPainter *painter,
In this article, I’ve only looked at developing plasmoids with 20 const QStyleOptionGraphicsItem C++. However, it is easy to develop plasmoids using a range of *option,
languages, including JavaScript, Ruby, and Python. To get eve- 21 const QRect &contentsRect ); ryone started with creating plasmoids, the KDE people have 22 started working on an editor called PlasMate. 23 protected slots: Figure 3 shows PlasMate’s main editing window with the com- 24 void sourceAdded( const QString &name ); ponents at the top, the current file in the middle, and a preview at the bottom. The tool is not fully usable yet, but the project is 25 void dataUpdated( const QString &sourceName, described at KDE TechBase, and the existing code can be 26 const Plasma::DataEngine::Data checked out from the KDE playground using Subversion: &data ); 27 svn co svn://anonsvn.kde.org/U 28 private: home/kde/trunk/playground/baseU 29 double m_load; /plasma/plasmate 30 }; Once checked out, you can build the code with CMake. However, the source code requires KDE 4.3, so you might have to upgrade 31 your distributions version of KDE. 32 #endif // LOADLIGHT_H
May 2010 ISSUE 114 37 Cover story KDE Plasma
little oddity of the system monitor data name for the library followed by the • X-KDE-Library: The library name. engine is that it tends to return a couple name of the class to expose. After that, • X-KDE-PluginInfo-Name: The plugin li- of results without any proper data before the .moc file from the meta-object com- brary name. the actual expected data starts coming piler is included in the file because only • X-KDE-PluginInfo-License: The license in. This issue is not really too important, a single class is involved in this case. of the library, for example, GPL. I deleted a figure but to avoid any problems, I also check Exporting the class as an applet is the • X-KDE-PluginInfo-EnabledByDefault: and corrected the that a key actually exists in the data. In first part of building a plasmoid. So that Set this to true. bulleted list. -rls this particular case, I only get one value, the plasma desktop is able to find your • X-KDE-PluginInfo-Author: Contact so if there is a key, I pick the first value applet, you must create a desktop file for name for author and use it to update the m_load member it. The desktop file is a list of key-value • X-KDE-PluginInfo-Email: Contact email variable. pairs and can be downloaded together for author. Having updated m_load, I call update with the source code for this project [1]. Now all that is left is to build and install to trigger a repaint event. This in turn re- The keys used are: the plasmoid in your KDE environment. sults in a call to the paintInterface • Name: The name of the plasmoid The KDE project uses the CMake tool for method, where the data is used to paint shown to the users. In this case, Load building. To control CMake, a CMake a circle with the value as text inside of it. Light. Lists.txt file is used. This file is more or The last step of the implementation is • Comment: A comment describing the less a boilerplate piece of code with the to export the class as a plasma applet in plasmoid. right library name in it and can be line 64. The K_EXPORT_PLASMA_AP • Type: Must be Service. downloaded together with the source PLET macro takes two arguments: a • ServiceTypes: Must be Plasma/Applet. code for this project.
Listing 2: loadlight.cpp
01 #include "loadlight.h" 34 return;
02 35
03 #include
04 37 return;
05 PlasmaLoadLight::PlasmaLoadLight( QObject *parent, 38
06 const QVariantList &args ) 39 m_load = data[data.keys()[0]].toDouble();
07 : Plasma::Applet( parent, args ), 40 update();
08 m_load( 0.75 ) 41 }
09 { 42
10 setBackgroundHints( DefaultBackground ); 43 void PlasmaLoadLight::paintInterface( QPainter *painter,
11 resize( 100, 100 ); 44 const QStyleOptionGraphicsItem *option,
12 } 45 const QRect &contentsRect )
13 46 {
14 void PlasmaLoadLight::init() 47 if( m_load < 0.5 )
15 { 48 painter‑>setBrush( Qt::green );
16 connect( dataEngine( "systemmonitor" ), 49 else if( m_load > 0.95 )
SIGNAL(sourceAdded(QString)), 50 painter‑>setBrush( Qt::red );
17 this, SLOT(sourceAdded(QString)) ); 51 else
18 } 52 painter‑>setBrush( Qt::yellow );
19 53
20 void PlasmaLoadLight::sourceAdded( const QString &name ) 54 painter‑>drawEllipse( contentsRect );
21 { 55
22 if( name == "cpu/system/loadavg1" ) 56 QFont f;
23 { 57 f.setPixelSize( contentsRect.height()/4 );
24 datangine( "systemmonitor" )‑>connectSource( name, 58 painter‑>setFont( f ); this, 1000 ); 59 painter‑>drawText( contentsRect, 25 disconnect( dataEngine( "systemmonitor" ), 60 Qt::AlignCenter, SIGNAL(sourceAdded(QString)), 61 QString::number( m_load, 'f', 2 ) ); 26 this, SLOT(sourceAdded(QString)) ); 62 } 27 } 63 28 } 64 K_EXPORT_PLASMA_APPLET(loadlight, PlasmaLoadLight) 29 65 30 void PlasmaLoadLight::dataUpdated( 66 #include "loadlight.moc" 31 const QString &sourceName,
const Plasma::DataEngine::Data &data )
32 {
33 if( sourceName != "cpu/system/loadavg1" )
38 ISSUE 114 May 2010 KDE Plasma Cover story
Now enter the source directory and run cmake and make to build the project. For plasma‑applet‑loadlight ‑‑ Load U the CMake call, add the prefix as the Light Plasmoid, monitoring the U value to the CMAKE_INSTALL_PREFIX systems load average definition: $ plasmoidviewer U cmake ‑DCMAKE_INSTALL_PREFIX=/usr plasma‑applet‑loadlight make This is all it takes to add an applet to the To install the plasmoid, run make install plasma desktop. As you can tell, it is a with the required privileges, then run fairly straightforward process of harvest- the kbuildsycoca4 utility to force KDE to: ing data, implementing the applet inter- face, and exposing your code to the sudo make install plasma. kbuildsycoca4 However, to add real functionality to the desktop, you must also provide your Then, you can either restart your plasma own data engines. session or use the plasmoidviewer utility Figure 3: PlasMate’s main editing window. to test your plasmoid. To find out about Data Engines known plasmoids, along with each plas- When developing data engines, your To build the project, you must know moid’s comment from the desktop file, best friend will be the plasmaengine the prefix of your KDE installation. To use the ‑‑list argument. Or, simply give explorer utility. This tool allows you to find this, you can use the kde4-config ap- the name of your plasmoid to the viewer browse and query the available data en- plication. In my case, the prefix is /usr. as an argument, and it will show it: gines and gives you a mechanism to ex- plore the available sources of data, as $ kde4‑config ‑‑prefix $ plasmoidviewer U well as test your own implementations. /usr ‑‑list | grep loadlight Before I look at a custom data engine, I’ll
May 2010 ISSUE 114 39 Cover story KDE Plasma
give it a try and explore the available The sources method that follows in from your code where you receive the data engines. For example, Figure 2 lines 10-13 is also quite trivial. Here, data. shows the uptime from the system moni- simply state that the source "Number" is Building and installing the data engine tor data engine. supported. The two functions, sourceRe follows exactly the same pattern as Creating a custom data engine is very questEvent and updateSourceEvent, that building an applet, so I will not go into it similar to the process of creating a plas- follow are where the real work is per- again. To test your engine, you can then moid. For instance, the CMakeLists.txt formed. use the Engine Explorer and request a and desktop files are still there. The only A request event occurs when a source number or two. difference is that desktop file declares is requested the very first time. It is still the service type to be Plasma/DataEn expected to set a value, so the imple- Building a Desktop gine. mentation first initializes the random As you can see, building plasmoid ap- When it comes to the C++ class, the number generator with the use of qs plets and data engines is not very hard base class used is Plasma::DataEngine rand() before it calls the update event to do. The object is to implement a given instead of the Plasma::Applet class; thus, method. API. If you want to look into the art of I have another interface to implement. An update event occurs every time the building plasmoids, the next place to To illustrate this, I will implement a data data are meant to be updated. Note that visit is KDE TechBase [2]. There, you can engine that serves random numbers and the updateSourceEvent method does not find tutorials as well as detailed API ref- is queried by the Engine Explorer utility. return an actual value; instead, it uses erence documentation. n Now look at the class declaration as the setData method shown in Listing 3. It shows one of the to announce that Listing 4: randomnumberengine.cpp most basic data engine interface imple- new data are avail- 01 #include "randomnumberengine.h" mentations possible. Continuing to the able. By using this 02 class implementation, Listing 4, you can approach, you can 03 #include
MinimumPollingInterval is called if you the same mecha- 07 { want to prevent being polled too often. nisms. Simply set 08 }
In this case, however, I can serve an al- your request for 09 most unlimited amount of random num- data from the up- 10 QStringList RandomNumberEngine::sources() const bers per second, so I do not set a limit date event and 11 { here. then call setData 12 return QStringList() << "Number";
13 } Listing 3: randomnumberengine.h 14 15 bool RandomNumberEngine::sourceRequestEvent( const 01 #ifndef RANDOMNUMBERENGINE_H QString &name ) 02 #define RANDOMNUMBERENGINE_H 16 { 03 17 if( name != "Number" ) 04 #include
12 25 {
13 public: 26 if( name != "Number" )
14 RandomNumberEngine( QObject *parent, const 27 return false; QVariantList &args ); 28 15 QStringList sources() const; 29 setData( name, qrand() ); 16 30 return true; 17 protected: 31 } 18 bool sourceRequestEvent( const QString &name ); 32 19 bool updateSourceEvent( const QString &name ); 33 K_EXPORT_PLASMA_DATAENGINE(randomnumber, 20 }; RandomNumberEngine)
21 34
22 #endif // RANDOMNUMBERENGINE_H 35 #include "randomnumberengine.moc"
40 ISSUE 114 May 2010