Write an applet for Plasma 6

Dhruvesh Surolia
6 min readMar 11, 2024

--

I have used Plasma 5 for a long time. The hype for Plasma 6 was so much, yet it let me down. Reason? Zero applets on the Store. Yes! I remember switching to Plasma 6 RC 1 with no applets whatsoever.

Despite having no knowledge of QML or KDE Frameworks, I took the job upon myself to port multiple applets and ported the Window Title applet, Minimal Desktop Indicator, Arch Update Checker, while also contributing to a couple applets here and there. Github.

Here’s hoping people won’t struggle as much the next time.

What’s in this tutorial

All basics needed to write an applet compiled into one place. Please note, this is a Fast tutorial and not a Detailed one, to get you building asap.

This tutorial also assumes you have at least basic knowledge of frontend, KDE Plasma and how frameworks work.

Setting up

  1. Directory Structure

Set this exact directory structure up with the exact same names, except for the appletName(obviously).

└── appletName
├── contents
│ ├── ui
│ │ ├── main.qml
│ │ └── configGeneral.qml
│ └── config
│ ├── config.qml
│ └── main.xml
└── metadata.json

Short explanation:

  1. metadata.json : contains the metadata for the applet as the name suggests.
  2. contents/config : contains files necessary for user-facing settings. You see them when you right-click on the applet and click on “Configure appletName”.
  3. contents/ui : contains files that you can use to represent ‘things’ in the applet.

2. Testing

Install the plasma-sdk package for your distribution. We will be using plasmoidviewer to test the applet.

To test : cd into the directory containing the metadata.json and run ‘plasmoidviewer -a .’.

To install locally: cd into the same directory, and run:

plasmapkg2 -i .

To reinstall: All your applets are installed in ‘~/.local/share/plasma/plasmoids/’. Remove the folder for your applet and run:

plasmapkg2 -i .

systemctl — user restart plasma-plasmashell

Your applet will not work just yet, so wait.

3. metadata.json

{
"KPlugin": {
"Authors": [
{
"Email": "myemail@gmail.com",
"Name": "My Name"
}
],
"Category": "System Information",
"Description": "A widget to take over the world!",
"Icon": "battery",
"Id": "com.github.zren.helloworld",
"Name": "Hello World",
"Version": "1",
"Website": "https://github.com/user/plasmoid-helloworldplugin"
},
"KPackageStructure": "Plasma/Applet",
"X-Plasma-API-Minimum-Version": "6.0"
}
// source: KDE Documentation

Note: Fields are self-explanatory. Make sure your “Id” stands out, or it might refuse to overwrite the previously existing applet.

Another Note: All pre-installed applets exist in ‘/usr/share/plasma/plasmoids/’ try not to name your applet one these either.

Recommended: Use an icon browser like cuttlefish to look through the available icons.

Some basic QML relating to Plasma

Important: Take a look at QML for Plasma ( <5 minutes ).

Here is a list of all QML types.

Hint: Basic types are in QtQuick and basic layouts are in QtQuick.Layouts.

Now that you are familiar with basic QML, know that Plasma provides with some extensions of the objects that QML provides. Some that would prove useful in developing faster.

Important ones are: CheckBox , RadioButton, ComboBox, SpinBox , Slider , TextField , TextArea , Button and ToolButton.

Example:

//Example
import QtQuick
import org.kde.plasma.components as PlasmaComponents

Layouts.ColumnLayout {
id: columnItem
spacing: 10
PlasmaComponents.Label {
id: label1
text: "bruh"
}
PlasmaComponents.Slider {
id: slider1
from: 0
to: 100
value: 10
stepSize: 5
}
}

Hint: Use Label type instead of Text type, for better styling purposes.

More interesting ones are in PlasmaExtras such as : Heading, Paragraph, PlaceHolderMessage and Menu.

QML has JS

Assigning a value to any property, Javascript is used.

Example:

Label {
id: "label1" // here label1 is JS
text: {
if( 1 == 0 ) return "Bro!";
else return "Bruh!";
} // This entire code block is JS
}

You can also import Javascript files and use them to call functions.

QML property

A property is just a Javascript variable, so naturally they can be accessed inside any JS code too.

Example:

RowLayout {
Item {
id: something
property bool isItNight: false
}
Label {
id: thisIsSomeLabel
text: something.isItNight ? "Its night" : "Its day"
}
}

Let’s say you click a switch in the UI, that changes the isItNight value. By default, the label would also update it’s value automatically.

Also look up QML Signals.(Optional)

PlasmoidItem

  1. This is a special type of an ‘Item’ Component.
  2. Every main.qml file should have this as the parent item.
  3. Contains the following extra direct properties:
  • preferredRepresentation : Preferred representation out of fullRepresentation and compactRepresentation.
  • fullRepresentation : Applet layout when it is expanded.
  • compactRepresentation : Applet layout when it is not expanded, such as in a panel.
  • other elements like tooltip (unnecessary for now).

Keeping the native style

Applets must follow the native look and feel of the desktop, thus Plasma provides with a list of variables to get the colors and units for the current theme for the user.

All theming and unit measurements were moved to Kirigami, in org.kde.kirigami.

You can find all the required material here ( < 2 minutes ).

VERY IMPORTANT: Replace ‘PlasmaCore.Theme’ with ‘Kirigami.Theme’ and ‘PlasmaCore.Units’ with ‘Kirigami.Units’. Refer to this when theming.

Finally, Start Building…

Open main.qml.

All applets must begin with a PlasmoidItem element, so declare it like so.

// main.qml
import QtQuick
import org.kde.plasma.components
import org.kde.plasma.core

PlasmoidItem {
id: root
// Your further code
}

Here is a basic example of an applet that shows an icon on compact representation but a text Label with a button to change them on the full representation.

//        contents/ui/main.qml

import QtQuick
import org.kde.plasma.components

PlasmoidItem {
id: root
preferredRepresentation: compactRepresentation
compactRepresentation: CompactItem {}
fullRepresentation: FulItem{}

property int someInt: 69
property string buttontext: "Click me!"
}
//       contents/ui/CompactItem.qml

import QtQuick
import org.kde.plasma.components
import org.kde.ksvg as KSVG

Rectangle {
//Notice id is not necessary
Kirigami.Icon {
id: itemIconImage
anchors.fill: parent
source: "update-none"
}
MouseArea { //Provided by QML to handle mouse events
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onClicked: (mouse) => {
root.expanded = !root.expanded
// This expands the compact representation into full and back
}
}
}
//       contents/ui/FullItem.qml

import QtQuick
import org.kde.plasma.components

RowLayout {
height: 100
width: 300
Label {
id: num
text: root.someInt
}
Button {
text: root.buttontext
onClicked: root.someInt += 1
}
}

This applet increments a number and shows it in the fullRepresentation.

User Facing Configuration

This code should be very simple to understand, so I won’t waste your time.

// content/config/config.qml
import org.kde.plasma.configuration

ConfigModel {
ConfigCategory {
name: i18n("General") //Name given to the tab in config
icon: "configure" //self-explanatory
source: "configGeneral.qml" //path inside ui folder
} //which would represent
} //the layout of this config page
<!-- content/config/main.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
<kcfgfile name=""/>
<group name="General">
<entry name="amount" type="Int">
<label>Amount</label>
<whatsthis>Amount of something (in thousands).</whatsthis>
<default>30</default>
</entry>
<entry name="name" type="String">
<label>Name</label>
<whatsthis>Stores the name of the user</whatsthis>
<default>This is a default value.</default>
</entry>
</group>
</kcfg>

Similarly, you can add multiple ConfigCategory elements and <group> elements to access and use the configuration on the user side.

Since we mentioned configGeneral.qml file here as the source, the ui folder must now contain a configGeneral.qml.

Here it is,

// contents/ui/configGeneral.qml
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as QQC2

ColumnLayout {
id: configItem
property alias cfg_amount: amount.value //cfg_ prefix necessary
property alias cfg_name: textbox.value //to the name in .xml file
QQC2.SpinBox {
id: amount
from: 1
to: 1000
}
QQC2.TextField {
id: textbox
placeholderText: "Enter your name"
}
}

Important: The variables to be used as configuration variables must named by adding “cfg_” as a prefix to the variable name used in the main.xml file.

These variable are then accessible anywhere inside ui within plasmoid.configuration.

Example:

// content/ui/FullItem.qml ( Editing the same one as above )
import QtQuick
import org.kde.plasma.components

RowLayout {
height: 100
width: 300
Label {
id: num
text: { //Using a block instead of a line
if( plasmoid.configuration.name === "Dhruvesh" ) //See this
return root.someInt;
else if( plasmoid.configuration.amount > 60 ) //and this
return root.someInt / 2;
else return 0;
}
}
Button {
text: root.buttontext
onClicked: root.someInt += 1
}
}

More things you might need…

  • ToolTip items and properties(provided by QML).
  • Look at Kirigami.FormLayout to get an easier way to build form style layouts. Especially useful in building config pages.
  • KSvg framework for icons.
  • Timer item(provided by QML) does what it says and is pretty useful.
  • plasmoid.formFactor represents the formFactor of the applet. Types are PlasmaCore.Types.(Vertical/Horizontal/Planar).
  • For better sizing / spacing look at : Layout property, anchor property, spacing property(only for QtQuick.Layouts).
  • Inside org.kde.plasma.plasma5support, DataSource and DataCore use different types of engines to fetch and send data through to the rest of the system. Used in building clocks, executing shell commands and such ( Look for DataSource dataengine types ).

What next?

  • It is highly recommended to look at the applet already available on the store.
  • Try to rebuild an applet that already exists to get a feel for things.
  • Build an awesome applet and publish it on the store, or consider contributing to existing ones.
  • Look at the Plasma Human Interface Guidelines to make a more intuitive applet.

Sources

--

--