Skip to content

Writing Plasma Applets in C# and Ruby

Saturday, 12 July 2008  |  richard dale

I got some Ruby Plasma bindings working a while ago. They wrapped the complete C++ api and allowed you to write a Plasma KDE plugin entirely in Ruby, which just looked like an ordinary C++ plugin to the Plasma runtime. However, that isn't the preferred way to implement non-C++ language support in Plasma.

Instead, there is an api called the 'ScriptEngine' for doing that. With ScriptEngine based applets you can use the Plasmoid packaging mechanism which should make it easy to download them from the internet via Hot New Stuff. Instead of being loaded directly as plugins, they are loaded via a ScriptEngine plugin which mediates between the scripting applet's api calls and the underlying C++ applet in Plasma which is doing the real work.

The Ruby and C# ScriptEngines application apis are pretty much identical to the C++ one, and both bindings have that wrapped too in case you need to use it. For instance, you can't write Plasma containments in ScriptEngine based applets, but for 95% of use cases it should be just fine. In the C++ based apis the main class is Plasma.Applet for C# or Plasma::Applet for Ruby, whereas the main classes in the ScriptEngine bindings are PlasmaScripting.Applet and PlasmaScripting::Applet respectively. So you just make your applet a subclass of one of those, add some slightly different entries in it .desktop file and use the standard directory structure for Plasmoids for where you put the source code, C# .dll and other resources. Everything else is the same.

Here is an example of the Tiger applet for showing an svg in Ruby, it is always called 'main.rb' with class 'Main' in the module with the same module name as the applet's directory (converted to camel case), under a directory called 'tiger/contents/code':

require 'plasma_applet'

module Tiger
  class Main < PlasmaScripting::Applet

    def initialize(parent, args = nil)
      super
    end

    def init
      @svg = Plasma::Svg.new(self)
      @svg.imagePath = 'widgets/tiger'
    end

    def paintInterface(painter, option, contentsRect)
      @svg.resize(size())
      @svg.paint(painter, 0, 0)
    end
  end
end

The .desktop file for the applet looks like this:

[Desktop Entry]
Name=Tiger
Comment=An example of displaying an SVG
Type=Service
ServiceTypes=Plasma/Applet

X-KDE-PluginInfo-Author=Richard Dale
X-KDE-PluginInfo-Email=panel-devel@kde.org
X-KDE-PluginInfo-Name=tiger
X-KDE-PluginInfo-Version=pre0.1
X-KDE-PluginInfo-Website=http://plasma.kde.org/
X-KDE-PluginInfo-Category=Examples
X-KDE-PluginInfo-Depends=
X-KDE-PluginInfo-License=GPL
X-KDE-PluginInfo-EnabledByDefault=true
X-Plasma-API=ruby-script

The most important entry is the 'X-Plasma-API=ruby-script' which means it uses the the Ruby ScriptEngine api.

In the C# Kimono bindings, a tiger applet looks like this, it is always compiled to 'main' with class 'Main' in the module with the same namespace as the applet, under a directory called 'tiger/contents/code':

namespace Tiger {
    using Qyoto;
    using Plasma;

    public class Main : PlasmaScripting.Applet {
        private Plasma.Svg svg;

        public Main(AppletScript parent) : base(parent) {}

        public override void Init() {
            svg = new Plasma.Svg(this);
            svg.FilePath = "widgets/tiger";
        }

        public override void PaintInterface(    QPainter painter,
                                                QStyleOptionGraphicsItem option,
                                                QRect contentsRect )
        {
            svg.Resize(Size);
            svg.Paint(painter, 0, 0);
        }
    }
}

Note that it uses C# properties like 'svg.FilePath' and 'applet.Size' which map directly onto the Q_PROPERTIES in the original C++ api. Plasma makes good use of properties and it makes the C# api look quite clean. Here is the C# .desktop file for it:

[Desktop Entry]
Name=Tiger
Comment=An example of displaying an SVG
Type=Service
ServiceTypes=Plasma/Applet

X-KDE-PluginInfo-Author=Richard Dale
X-KDE-PluginInfo-Email=panel-devel@kde.org
X-KDE-PluginInfo-Name=tiger
X-KDE-PluginInfo-Version=pre0.1
X-KDE-PluginInfo-Website=http://plasma.kde.org/
X-KDE-PluginInfo-Category=Examples
X-KDE-PluginInfo-Depends=
X-KDE-PluginInfo-License=GPL
X-KDE-PluginInfo-EnabledByDefault=true
X-Plasma-API=mono-script

You can see it is pretty much identical to the Ruby version apart from the 'X-Plasma-API=mono-script' line.

To define a slot for receiving data from a Data Engine, you define a slot like this in Ruby:

require 'plasma_applet'

module Tiger
  class Main < PlasmaScripting::Applet
    slots 'void dataUpdated(QString, Plasma::DataEngine::Data)'

    def initialize(parent, args = nil)
      super
    end

    def init
      timeEngine = dataEngine("time")
      timeEngine.connectSource("Local", self, 6000)
    end

    def dataUpdated(name, data)
      puts ("In DataUpdated name: %s data: %s", [name, data["Time"].toTime.toString]);
    end
...

And the applet will receive the time very 6000 ms.

The same slot in C# looks like this:

namespace Tiger {
    using Qyoto;
    using Plasma;
    using System;
    using System.Collections.Generic;

    public class Main : PlasmaScripting.Applet {
        private Plasma.Svg svg;

        public Main(AppletScript parent) : base(parent) {}

        public override void Init() {
            Plasma.DataEngine timeEngine = DataEngine("time");
            timeEngine.ConnectSource("Local", this, 6000);
        }

        [Q_SLOT("void dataUpdated(QString, Plasma::DataEngine::Data)")]
        public void DataUpdated(string name, Dictionary data) {
            Console.WriteLine("In DataUpdated name: {0} data: {1}", name, data["Time"].ToTime().ToString());
        }
    ...

So that's really all there is to it. You can add Plasma::PushButtons, Plasma::Labels to a Qt::GraphicsLinearLayout in your applet, use the QWebContent widget to make mini-browsers, reimplement the createConfigurationInterface() method to show configure dialogs, override event handlers like mousePressEvent() and generally do what you might expect to be able to do.