<<

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 . 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- ­‑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.​­ .​­ 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- data­Updated 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 , 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 source­Removed 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 ++. 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 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 36  if( data.keys().count() == 0 )

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( ::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 voi 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 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 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 see that the implementation is just as implement asyn- 04  simple. chronous data en- 05 RandomNumberEngine::RandomNumberEngine( QObject *parent, It starts with a straightforward con- gines easily with const QVariantList &args ) structor (lines 5-8). Commonly, the set­ the use of exactly 06  : Plasma::DataEngine( parent, args )

MinimumPolling​Interval 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 18  return false; 05  19  06 /** 20  qsrand( QDateTime::currentDateTime().toTime_t() ); 07  * An engine that provides random numbers. 21  return updateSourceEvent( name ); 08  */ 22 } 09 class RandomNumberEngine : public Plasma::DataEngine 23  10 { 24 bool RandomNumberEngine::updateSourceEvent( const QString 11  Q_OBJECT &name )

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