LJ Archive

Re-invent Your Desktop with Plasma!

Riccardo Iaconelli

Issue #190, February 2010

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.

Build Environment

For your build to succeed, you must have the Qt4 and KDE development libraries, cmake and the usual compilation tools, such as make, g++ and so on, installed onto your system. Note that this tutorial assumes a KDE version greater than or equal to 4.3.

To build your plasmoid, change directory to your plasmoid, and issue the following commands:

mkdir build
cmake -DCMAKE_INSTALL_PREFIX=`kde4-config --prefix` ..
make
sudo make install

Note the backticks (``) in the cmake command, which mean “substitute here the result of the evaluation of the quoted command”.

The first time you test the plasmoid, you'll have to refresh the KDE configuration cache by issuing the command:

kbuildsycoca4 --noincremental

You'll also need to run this command any time you make modifications to the plasmoid desktop file.

To test the plasmoid, run the following command:

plasmoidviewer plasmoid

Plasmoidviewer is a little test application that makes it easy to load and play with a single plasmoid without having to restart your desktop every time.

Writing Other Types of Plasmoids

In this tutorial, I've explained how to write a simple plasmoid from scratch, but I've barely scratched the surface. As I mentioned previously, Plasma is all about plugins, and writing other types of plasmoids is just as easy. You can, for example, write your own “containment”. Containments are a subset of applets, which are built to contain other applets. The panel, the desktop, or the “folder view” are all examples of containments. Being applets, they also can be used like regular applets wherever that makes sense.

You can write wallpapers, which are plugins that draw a containment's background. With these, you can draw a simple image or a more complex rendering, such as a Mandelbrot fractal or even an animated (and intractable) Earth globe!

You also can create a plasma theme, which changes the look and feel of all the applets through the usage of SVGs.

Apart from visualization, you can write a DataEngine and/or an associated Service to be able to gather information from the rest of the world. You can make a Runner, which is a plugin that responds to user queries—from “3+2” to “run command x” to “google for x” to “shut down the PC”. These are then accessible from Krunner, from menus, or even from your own applet!

All of this also is accessible through scripted languages, be it JavaScript, Ruby, Python or many others. And obviously, you can invent your own binding to Plasma by re-implementing a ScriptEngine, which has been done, for example, to be able to load simple Apple Dashboard or Edje widgets.

As you can see, there are almost infinite possibilities for expressing your creativity, and all this goodness is exposed through an intuitive and powerful API.

A Quick Look into the Future

This article was written in October 2009, when the stable version of KDE was 4.3, and Qt was at version 4.5. By the time you read this, however, both Qt 4.6 and KDE 4.4 should be available, so let's briefly outline what possibilities these new versions will offer:

  • First, Qt introduces a new animation framework, sometimes referred to as Qt Kinetic, which makes animations in our plasmoids trivial. You will be able to decide what should be animated, how that element should look at the start of the animation (for example, be at position 0,0) and how it should look at the end of it (for example, be at position 100,20, rotated 20 degrees and scaled by a factor 2), and everything will be taken care of for you. For more information, take a look at the Qt documentation (see Resources).

  • Anchor layouts, a minor improvement in Qt that might nonetheless save you from major headaches, are designed to make it possible to achieve many visual presentations with less effort.

  • Remote plasmoids is one of the most interesting things that KDE 4.4 will bring. This technology has been developed as a Google Summer of Code Project, and it allows you to transport applets, data engines and services over the network transparently. Without any more code than what you would write for a local component, you will be able (because of compression and caching algorithms) to distribute services and content between any HTTP-capable machine efficiently. Ideas include writing a lightweight server application that distributes plasmoids and/or content directly from a Web server, without requiring any graphical interface on it or perhaps built directly into your Web 2.0 application. The API to interact with this technology is, in fact, extremely simple and has a very small runtime requirement.

Conclusion

There's so much more to say about Plasma and its technologies, but I'm out of space. The Resources for this article include many useful references to help make your desktop (or whatever your Plasma is running on) a better place.

Riccardo Iaconelli has been a KDE developer since 2005. He is one of the core Plasma developers, and he is part of the team that gave birth to the project. Riccardo also is a member of the Oxygen team, the project that strives to bring beauty to KDE 4, working both on the code and the artwork. He currently is attending his last year of high school in Milano, Italy.

LJ Archive