C++ API
Most widgets need only QML. If yours needs models backed by C++ libraries, file I/O or other things QML can't do, you can compile it. Most of SonicDE's own widgets are built this way.
A compiled widget is a plugin with its QML embedded, installed to plasma/applets in Qt's plugin directory.
Warning: A compiled widget can't be installed from a package with
kpackagetool6, so it can't be offered as a plain download on the store. Distribute it through your distribution's packaging instead.
Compiling with CMake
The cpp-plasmoid6 template is a complete starting point. The steps below build the same kind of widget.
plasma-widgetname/
├── CMakeLists.txt
├── metadata.json
├── qml/
│ └── main.qml
├── widgetitem.cpp
└── widgetitem.h
The CMake function plasma_add_applet() creates the plugin and embeds the QML. Its first argument is the widget's ID, which is also the name of the plugin file.
GENERATE_APPLET_CLASS generates the plugin class, which is all you need unless you want your own applet class. The macro turns the ID into the QML module plasma.applet.<id>.
CMakeLists.txt:
cmake_minimum_required(VERSION 3.16)
project(plasma-widgetname)
find_package(ECM 6.0.0 REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDECompilerSettings NO_POLICY_SCOPE)
find_package(Qt6 REQUIRED COMPONENTS Core Qml Quick)
find_package(KF6 REQUIRED COMPONENTS I18n Config)
find_package(Plasma REQUIRED)
add_definitions(-DTRANSLATION_DOMAIN=\"plasma_applet_org.example.widgetname\")
plasma_add_applet(org.example.widgetname
QML_SOURCES
qml/main.qml
CPP_SOURCES
widgetitem.cpp
GENERATE_APPLET_CLASS
)
target_link_libraries(org.example.widgetname PRIVATE
Qt::Core
Qt::Qml
Qt::Quick
KF6::I18n
)
KF6 Config is listed because Plasma::Plasma links to it without finding it.
metadata.json is embedded in the plugin. Leave out KPlugin.Id: the ID comes from the plugin's file name, and the loader warns if the key is present.
{
"KPlugin": {
"Name": "widgetname",
"Version": "1.0"
},
"X-Plasma-API-Minimum-Version": "6.0",
"KPackageStructure": "Plasma/Applet"
}
Adding a QML type
Let's add a WidgetItem type with a number property, and a randomize() function that sets it to a random number.
qml/main.qml imports the widget's own module:
import QtQuick
import org.kde.plasma.components as PlasmaComponents
import org.kde.plasma.plasmoid
import plasma.applet.org.example.widgetname
PlasmoidItem {
id: widget
WidgetItem {
id: widgetItem
number: 123
}
fullRepresentation: PlasmaComponents.Button {
text: widgetItem.number
onClicked: widgetItem.randomize()
}
}
QML_ELEMENT registers the class in that module, so there is no plugin class or qmldir to write. The number property isn't read-only, so it has a WRITE setter and a NOTIFY signal to tell the interface when it changes. randomize() needs Q_INVOKABLE, or QML couldn't call it.
widgetitem.h:
#pragma once
#include <QObject>
#include <QtQml/qqmlregistration.h>
class WidgetItem : public QObject
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(int number READ number WRITE setNumber NOTIFY numberChanged)
public:
explicit WidgetItem(QObject *parent = nullptr);
int number() const;
void setNumber(int number);
Q_INVOKABLE void randomize();
Q_SIGNALS:
void numberChanged();
private:
int m_number = 0;
};
The setter doesn't emit the signal unless the value actually changes. qDebug() logs to the terminal, which helps during development.
widgetitem.cpp:
#include "widgetitem.h"
#include <QDebug>
#include <QRandomGenerator>
WidgetItem::WidgetItem(QObject *parent)
: QObject(parent)
{
}
int WidgetItem::number() const
{
return m_number;
}
void WidgetItem::setNumber(int number)
{
if (number != m_number) {
m_number = number;
qDebug() << "setNumber" << m_number;
Q_EMIT numberChanged();
}
}
void WidgetItem::randomize()
{
setNumber(QRandomGenerator::global()->bounded(10001));
}
Building and testing
The plugin has to be somewhere Qt looks for plugins, so install it to the same prefix as SonicDE. Then run it as described on the testing page:
cmake -B build -DCMAKE_INSTALL_PREFIX=/usr
cmake --build build
sudo cmake --install build
plasmawindowed org.example.widgetname
Warning: Don't reuse the ID of a widget you installed as a package with
kpackagetool6. When a package and a plugin have the same ID, the loader combines them, and the package in your home directory supplies the widget's files.
Your own applet class
To give QML access to C++ on the widget object itself, subclass Plasma::Applet. Properties and invokable functions of the subclass are available through the Plasmoid attached property. This is how the system tray hands its model to QML, as Plasmoid.systemTrayModel.
Remove GENERATE_APPLET_CLASS from plasma_add_applet(), add the new file to CPP_SOURCES, and link to Plasma::Plasma.
widgetapplet.h:
#pragma once
#include <Plasma/Applet>
class WidgetApplet : public Plasma::Applet
{
Q_OBJECT
Q_PROPERTY(QString greeting READ greeting CONSTANT)
public:
WidgetApplet(QObject *parent, const KPluginMetaData &data, const QVariantList &args);
QString greeting() const;
};
widgetapplet.cpp:
#include "widgetapplet.h"
#include <KLocalizedString>
#include <KPluginFactory>
WidgetApplet::WidgetApplet(QObject *parent, const KPluginMetaData &data, const QVariantList &args)
: Plasma::Applet(parent, data, args)
{
}
QString WidgetApplet::greeting() const
{
return i18n("Hello from C++");
}
K_PLUGIN_CLASS_WITH_JSON(WidgetApplet, "metadata.json")
#include "widgetapplet.moc"
QML then reads Plasmoid.greeting.
For a real example, see systemtray.h and systemtray.cpp.
Examples
SonicDE's widgets are good references.
- Media Frame is a fairly simple one: one C++ class, imported as
plasma.applet.org.kde.plasma.mediaframe. - The Application Menu and Application Launcher share a separate QML module from
sonic-workspace/applets/kicker, built withecm_add_qml_module().
import org.kde.plasma.private.kicker as Kicker
Note: A
.privatemodule can be imported by any widget, but its API can change with any update. Using someone else's private module may break your widget.
Containments
A containment is a widget that holds other widgets. Use GENERATE_CONTAINMENT_CLASS, or subclass Plasma::Containment, to write one.
- The Grouping widget shows one child widget at a time, using tabs.
- The system tray can show several compact representations at a time, but only one full representation in its popup.
- The panel (
containments/panelanddesktoppackage/contents/views/Panel.qml) can show compact and full representations next to each other, and is the most complicated of the three to read.
Translating C++ strings
To translate messages in your C++ code, use KF6::I18n and define the translation domain in CMakeLists.txt, as the example above does. The translations page explains how the domain is used.
find_package(KF6 REQUIRED COMPONENTS I18n Config)
add_definitions(-DTRANSLATION_DOMAIN=\"plasma_applet_org.example.widgetname\")
target_link_libraries(org.example.widgetname PRIVATE
KF6::I18n
)