Re-invent Your Desktop with Plasma!

Don't settle for a desktop that came out of a box; find out how to write your own Plasma widgets (aka plasmoids) and give your desktop a shot in the arm or a kick in the you-know-what.

Plasma is one of the most exciting technologies KDE 4 has brought to life. It often is considered to be merely the desktop shell of KDE 4, but in reality, it is so much more.

We are just starting to see the full potential of Plasma, but it's already being used by some of the best KDE applications. Amarok, for example, uses it in so-called context view, and the Plasma packaging system is widely used in conjunction with GetHotNewStuff technology. Plasma's main goal is to provide a powerful framework you can use to build your own UIs. All of this is possible because the Plasma libraries are not tied to a specific use case. Instead of housing all the desktop-related code in the plasma-desktop binary, the majority of it is contained in desktop-specific plugins. That is why the desktop shell itself is just a few hundred lines of code, but it uses thousands of lines of code located in the Plasma libraries.

Why C++ and Not JavaScript?

Although JavaScript bindings are the official language for widget authoring, for security reasons, its subset of functions is limited to the Plasma API, and it gives the programmer no access to pure Qt or KDE components. JavaScript plasmoids are great if you want your widget to be installable as an add-on easily and securely, in which case the bindings are complete enough to give you access to most of the functions you will ever need. JavaScript bindings also offer an extremely easy approach to the API. There are less problems related to packaging, and JavaScript is (usually) a less difficult language to use.

However, I'm going to introduce you to a C++ plasmoid, because the JavaScript API is quite new, and most widgets to date have been written in C++. For this reason, you're likely to find more code examples or help for C++ plasmoids.

Nonetheless, we expect more and more JavaScript components to be produced over time, especially after the release of KDE 4.4. And, as the API remains very similar, it shouldn't be difficult to migrate your knowledge of C++ to JavaScript.

Ruby, Python (including Edje support) and C# bindings are also production-ready and offer access to the full C++ API, but if you use them for your component, only users having the kdebindings package installed will be able to use it. JavaScript bindings are the only ones shipped with the Plasma libraries and are currently the only ones officially suggested.

Understanding Plasma's Design

We created Plasma with development flexibility in mind. The basic idea is that to write simple things, you shouldn't need more than what is necessary for actual functionality.

This approach should be very scalable and not limiting—meaning you should be able to extend, tweak and experiment with user interaction without destroying any work previously done or re-inventing the wheel. To achieve this, Plasma has separated the mechanisms of getting the data from the visualization itself. This approach is commonly known as the Model View approach.

In this tutorial, I create a plasmoid for providing a visualization of an RSS source. The C++ class that represents the base class for the visualization will be called Plasma::Applet. I also briefly overview the other classes later in the article, and I assume that you know some basic Qt programming techniques. If that is not the case, however, check out some of the many tutorials you can find on-line. I make references mainly to basic things like QString or the Signal/Slot mechanism in this article.

Writing Your First Lines of Code

I'll get to the instructions to build the plasmoid later; first, I want to give a brief overview of the most significant parts of the code. You can download the full source code from ftp.linuxjournal.com/pub/lj/listings/issue190/10638.tgz. I don't cover the CMake configuration file in this article, but you can find it in the tarball complete with comments.

All KDE plugins and extensions, as well as application launchers, are described in the files with the .desktop extension. First, you need a .desktop file for this tutorial. This file contains the data used by KDE to show the applet to the user, and it contains the plasmoid's name, description and credits. A minimal desktop file for this example plasmoid looks like this:

# plasma-applet-exampleplasmoid.desktop

[Desktop Entry]
Name=RSS
Comment=An RSS Plasmoid
Icon=application-x-plasma
Type=Service
X-KDE-ServiceTypes=Plasma/Applet
X-KDE-Library=plasma_applet_plasmoid
X-KDE-PluginInfo-Name=plasmoid

The first lines are fairly obvious. They provide a human-readable name and description and an icon. The following lines tell the system what kind of plugin it is. The important line here is the X-KDE-PluginInfo-Name. This line tells the KDE internals what the plugin's name is (this is used as an argument to plasmoidviewer to preview your plasmoid). Note that this name cannot contain any special characters. Make sure the name of this file matches the pattern plasma-applet-*.desktop.

Next, let's look at the source code for a very simple plasmoid, with the minimum amount of code needed to make it valid, such that it actually will compile and load.

First, the header:


// plasmoid.h
#ifndef PLASMOID_HEADER
#define PLASMOID_HEADER

#include <Plasma/Applet>

class Plasmoid: public Plasma::Applet
{
    Q_OBJECT
    public:
        Plasmoid(QObject *parent, const QVariantList &args);
        ~Plasmoid();
};

// This is the command that links your applet to the .desktop file
K_EXPORT_PLASMA_APPLET(plasmoid, Plasmoid)

#endif

As you can see, this is a pretty simple class derived from Plasma::Applet. A few interesting things to note:

  • The first two lines (and the last one) are the common trick for ensuring that the header file doesn't get loaded multiple times—meaning you will have no compiler errors due to that.

  • The Q_OBJECT macro is inserted because you need to make use of slots later on (the signal/slot paradigm is a Qt feature, and any introduction to Qt should explain it if you're not familiar with it).

  • The K_EXPORT_PLASMA_APPLET macro is what actually exports the plasmoid (and, therefore, makes it displayable). You can find more information on this macro on TechBase (see Resources).

Now, here's the actual implementation:


#include "plasmoid.h"

Plasmoid::Plasmoid(QObject *parent, const QVariantList &args)
    : Plasma::Applet(parent, args)
{
}

Plasmoid::~Plasmoid()
{
}

#include "plasmoid.moc"

For now, this plasmoid doesn't do anything beyond displaying its own background. If you were to compile it and preview your work with plasmoidviewer, you would see what looks like Figure 1.

Figure 1. First Run of the Applet

It's a bit boring perhaps, but on the other hand, it compiles and runs. Now, let's inject some cool features.

To get some new data, you should be familiar with another Plasma component, the DataEngine. This class is the base class that the applet uses to interact with the rest of the world. It mainly is used to fetch data from different sources. DataEngine is a read-only object. Its read/write counterpart is the Service class, which is used in cases where user input can modify the environment outside the plasmoid itself (think of Web services, for example). Just like Applets, additional DataEngines and Services can be written and installed by the user. It's simple to connect to a data engine from within an applet; you just need to add the following line:

Plasma::Applet::dataEngine("rss")->
           connectSource("http://dot.kde.org/rss.xml", this);

In this case, rss is the name of the data engine you want to invoke. The first argument of connectSource() is the source name (in this case, the URL of the feed you want), and the second argument is the object that should be updated when the data engine receives new data. Another data engine example would be the Time data engine, and you would connect it with the following code:

timeEngine->connectSource("Local", this);

Local, in this case, means the local time zone. This, however, will update only once. To make it update itself automatically (and update your plasmoid) every second, you would have to write something like this:

timeEngine->connectSource("Local", this, 1000);

The third parameter, if present, specifies how often (in milliseconds) you should request an update from the source. Note that the source also can decide to update itself independently.

You also can connect several data engines to one single object; just make sure to check the sourceName in the dataUpdated function (see below) when the update occurs.

You can get a list of the available engines and their structure with a plasma tool called PlasmaEngineExplorer. Run the following command inside your terminal:

plasmaengineexplorer

This will show you a rather large list of engines from which to choose for building your plasmoid. Find one that inspires you and start hacking on it. To know what structure the data engine gives you, you either can use the engine explorer, look at another applet's source code, or even explore the data engine source code itself.

To give your plasmoid the data, the DataEngine tries to call a slot with the following signature:


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

Add this function (method) to your Plasmoid class with the following code (don't forget the declaration in the header file):


void Plasmoid::dataUpdated(const QString &sourceName,
                           const Plasma::DataEngine::Data &data)
{
    QMap<QString, QVariant>
             item      = data["items"].toList().first().toMap();
    QString  title     = item["title"].toString();
    QString  feedtitle = item["feed_title"].toString();
}

DataEngine is type-agnostic; it stores all of its data in QVariants. It's your responsibility to know what data to expect and to convert it accordingly. In this case, the data is a QList of QVariants (each item) that in reality is a map. You take the first element and convert it to a QMap. Then, you extract the title and the feed title and store them in QStrings. That is all you need to get the data from any RSS feed.

So now, you've got the data, but how do you display it? With Plasma, it's a piece of cake!

The Plasma team has created several useful widgets that can be utilized in an applet. What you need in this case is a label. First, create a simple linear layout for the plasmoid in the constructor, like this:

QGraphicsLinearLayout *layout = new QGraphicsLinearLayout(this);
setLayout(layout);

A QGraphicsLayout will resize and align all the widgets it contains automatically. Linear and Grid layouts also are available. Now, include a Plasma::Label as a member in the header, initialize it and add it to the layout, like so:

m_rssTitle = new Plasma::Label(this);
layout->addItem(m_rssTitle);

Finally, add the following lines to dataUpdated():


QString text = "Most recent news from <b>"+feedtitle+"</b>:";
text += "<br /><br />"+title;
    m_rssTitle->setText(text);

Note that you don't need to destroy/free any objects, because every object that has a parent (assigned on creation by passing an argument to the constructor) is deleted automatically by Qt's garbage-collection system.

If you compile your applet now, and everything has gone well, you should see something like Figure 2 when launching the plasmoidviewer.

Figure 2. Plasmoid Widget in Its Full Glory

When the DataEngine fetches new data, your plasmoid will be updated automatically.

______________________

Webinar
One Click, Universal Protection: Implementing Centralized Security Policies on Linux Systems

As Linux continues to play an ever increasing role in corporate data centers and institutions, ensuring the integrity and protection of these systems must be a priority. With 60% of the world's websites and an increasing share of organization's mission-critical workloads running on Linux, failing to stop malware and other advanced threats on Linux can increasingly impact an organization's reputation and bottom line.

Learn More

Sponsored by Bit9

Webinar
Linux Backup and Recovery Webinar

Most companies incorporate backup procedures for critical data, which can be restored quickly if a loss occurs. However, fewer companies are prepared for catastrophic system failures, in which they lose all data, the entire operating system, applications, settings, patches and more, reducing their system(s) to “bare metal.” After all, before data can be restored to a system, there must be a system to restore it to.

In this one hour webinar, learn how to enhance your existing backup strategies for better disaster recovery preparedness using Storix System Backup Administrator (SBAdmin), a highly flexible bare-metal recovery solution for UNIX and Linux systems.

Learn More

Sponsored by Storix