C++ API
Compiling With CMake
A template can be found in plasma-framework
:plasma-framework
/ template/qml-plasmoid
Do not reuse the same Id
/namespace you used with a widget installed with kpackagetool5
. If a user installed it to their home directory, the code in the home directory will be loaded instead of the code in the root directory.
mkdir -p ~/Code/plasmoid-helloworld2
cd ~/Code/plasmoid-helloworld2
mkdir -p ./build
cd ./build
cmake .. -DCMAKE_INSTALL_PREFIX=/usr
make
sudo make install
plasmoidviewer -a com.github.zren.helloworld2
You can run all build and test commands in a single line like so:
(cd ./build && cmake .. -DCMAKE_INSTALL_PREFIX=/usr && make && sudo make install) && plasmoidviewer -a com.github.zren.helloworld2
You cannot ship a widget that needs to be compiled on the KDE Store. You will need to publish it in an Ubuntu PPA, on the Arch AUR, or with OpenSUSE OBS.
└── ~/Code/plasmoid-helloworld2/
├── package
│ ├── contents
│ │ └── ...
│ └── metadata.json
└── CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(plasmoid-helloworld2)
find_package(ECM 1.4.0 REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
find_package(KF5 REQUIRED COMPONENTS
Plasma # Required for plasma_install_package()
)
plasma_install_package(package com.github.zren.helloworld2)
Private C++ QML Plugin
Plasma ships with a number of useful QML plugins like PlasmaCore, PlasmaComponents, PlasmaExtras. Your widget might need more complicated models that interact with C++ libraries or File I/O requiring you to create a QML plugin.
Example Plugins
plasma-framework
:plasma-framework
/ template/qml-plasmoid-with-qml-extension
The mediaframe widget in kdeplasma-addons
is a fairly simple example. The plugin has one C++ class to define the plugin, and only defines a single QML Item type.
kdeplasma-addons
/ applets/mediaframe
import org.kde.plasma.private.mediaframe 2.0
While KDE puts .private
in the namespace of these plugins, they can be accessed by any QML widget / application. If you plan on using someone else's "private" plugin, your widget may experience bugs when Plasma updates.
Another example is the "Kicker" plugin for the "Application Menu" widget which is reused by the kickoff "Application Launcher" widget.
import org.kde.plasma.private.kicker 0.1 as Kicker
└── ~/Code/plasmoid-mediaframe/
├── package
│ ├── contents
│ │ └── ui
│ │ └── main.qml
│ └── metadata.json
├── plugin
│ ├── mediaframe.cpp
│ ├── mediaframe.h
│ ├── mediaframeplugin.cpp
│ ├── mediaframeplugin.h
│ └── qmldir
└── CMakeLists.txt
Writing a Plugin
Lets use mediaframe as an example and create our own widget with a plugin.
A full copy of this example can be downloaded as a ZIP or cloned from GitHub.
Download ZIP or Git Clone
mkdir -p ~/Code
cd ~/Code
git clone https://github.com/Zren/plasmoid-helloworldplugin plasmoid-widgetname
cd ~/Code/plasmoid-widgetname
WidgetItem
type, which has a property named number
and has an invokable function called randomize()
which will set the number
property to a random number.import QtQuick 2.4
import org.kde.plasma.components 3.0 as PlasmaComponents3
import org.kde.plasma.plasmoid 2.0
import com.github.zren.widgetname 1.0 as WidgetName
Item {
id: widget
WidgetName.WidgetItem {
id: widgetItem
number: 123
}
Plasmoid.fullRepresentation: PlasmaComponents3.Button {
text: widgetItem.number
onClicked: widgetItem.randomize()
}
}
metadata.json
.{
"KPlugin": {
"Id": "com.github.zren.widgetname",
"Name": "widgetname",
"Version": "1.0",
"Website": "https://github.com/Zren/plasmoid-helloworldplugin"
},
"X-Plasma-API": "declarativeappletscript",
"X-Plasma-MainScript": "ui/main.qml",
"KPackageStructure": "Plasma/Applet"
}
First new things added to our CMakeLists.txt
is listing all our .cpp
files that we need to compile. We also define the plugin library name used in the binary filename.
set(widgetnameplugin_SRCS
plugin/widgetitem.cpp
plugin/widgetnameplugin.cpp
)
add_library(widgetnameplugin SHARED ${widgetnameplugin_SRCS})
In our CMakeLists.txt
, we need to include a few KDE variables so that we compile and install files to the right location. Make sure the folder names match the plugin namespace.
include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDECompilerSettings NO_POLICY_SCOPE)
# ...
install(TARGETS widgetnameplugin DESTINATION ${KDE_INSTALL_QMLDIR}/com/github/zren/widgetname)
install(FILES plugin/qmldir DESTINATION ${KDE_INSTALL_QMLDIR}/com/github/zren/widgetname)
In OpenSUSE, we'll end up installing the following files. Other distros might not use /usr/lib64/qt5
so just use
locate qmldir
if you are curious where the files are installed to.
/usr/lib64/qt5/qml/com/github/zren/widgetname/libwidgetnameplugin.so
/usr/lib64/qt5/qml/com/github/zren/widgetname/qmldir
Since we're now compiling Qt C++ code, we need to use find_package()
to indicate that it's required for compilation. We will need Qt5::Qml
to import QQmlExtensionPlugin
and use qmlRegisterType()
. Since we are sticking to a simple QObject
in our new type, we will only need Qt5::Core
.
Don't forget to link the components as well.
find_package(Qt5 REQUIRED COMPONENTS
Core
Qml
)
# ...
target_link_libraries(widgetnameplugin
Qt::Core
Qt::Qml
)
cmake_minimum_required(VERSION 3.16)
project(plasmoid-widgetname)
find_package(ECM 1.4.0 REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH})
include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDECompilerSettings NO_POLICY_SCOPE)
find_package(Qt5 REQUIRED COMPONENTS
Core
Qml
)
find_package(KF5 REQUIRED COMPONENTS
Plasma # Required for cmake plasma_install_package()
# I18n
)
plasma_install_package(package com.github.zren.widgetname)
add_definitions(-DTRANSLATION_DOMAIN=\"plasma_applet_com.github.zren.widgetname\")
set(widgetnameplugin_SRCS
plugin/widgetitem.cpp
plugin/widgetnameplugin.cpp
)
add_library(widgetnameplugin SHARED ${widgetnameplugin_SRCS})
target_link_libraries(widgetnameplugin
Qt::Core
Qt::Qml
# KF5::Plasma
# KF5::I18n
)
install(TARGETS widgetnameplugin DESTINATION ${KDE_INSTALL_QMLDIR}/com/github/zren/widgetname)
install(FILES plugin/qmldir DESTINATION ${KDE_INSTALL_QMLDIR}/com/github/zren/widgetname)
The qmldir
file is basically the qml plugin metadata file. Since we don't bundle any .qml
files in the plugin itself like PlasmaComponents does, this will just define the namespace of the plugin and the plugin library name.
Inside widgetnameplugin.h
we extend QQmlExtensionPlugin
and indicate we implement the QQmlExtensionInterface
which somehow tells it to call registerTypes()
.
In the .cpp
file we register the new QML type. Don't forget to edit the namespace in the assert.
module com.github.zren.widgetname
plugin widgetnameplugin
#pragma once
#include <QQmlEngine>
#include <QQmlExtensionPlugin>
class WidgetNamePlugin : public QQmlExtensionPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
public:
void registerTypes(const char *uri) override;
};
#include "widgetnameplugin.h"
#include "widgetitem.h"
void WidgetNamePlugin::registerTypes(const char *uri)
{
Q_ASSERT(QLatin1String(uri) == QLatin1String("com.github.zren.widgetname"));
qmlRegisterType<WidgetItem>(uri, 1, 0, "WidgetItem");
}
Finally we write our new WidgetItem
type. In the header file we extend QObject
and define the number
property. Since we want do not want the number
property to be readonly, we define WRITE
and a setter function. We also define the numberChanged
signal to NOTIFY
the GUI when it's modified.
The randomize()
method needs Q_INVOKABLE
otherwise it cannot be called from QML.
#pragma once
#include <QObject>
class WidgetItem : public QObject
{
Q_OBJECT
Q_PROPERTY(int number READ number WRITE setNumber NOTIFY numberChanged)
public:
explicit WidgetItem(QObject *parent = nullptr);
~WidgetItem() override;
int number() const;
void setNumber(int number);
Q_INVOKABLE void randomize();
Q_SIGNALS:
void numberChanged();
private:
int m_number;
};
To make development easier, we've imported qDebug()
which lets us log to the terminal.
In the setter, we do not emit the signal if the property does not actually change.
#include "widgetitem.h"
#include <QDebug>
#include <QObject>
#include <QRandomGenerator>
WidgetItem::WidgetItem(QObject *parent)
: QObject(parent)
, m_number(0)
{
qDebug() << "WidgetItem() constructor";
}
WidgetItem::~WidgetItem() = default;
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()
{
const int min = 0;
const int max = 10000;
int val = (QRandomGenerator::global()->bounded((max - min + 1)) + min);
qDebug() << "randomize(" << min << "," << max << ") =" << val;
setNumber(val);
}
To compile, install and test this plugin follow the instructions from the previous Compiling With CMake section and the Widget Testing page.
When writing your widget's README.md
, you'll want to add uninstall instructions as well.
cd ~/Code/plasmoid-widgetname
mkdir -p ./build
(cd ./build && cmake .. -DCMAKE_INSTALL_PREFIX=/usr && make && sudo make install)
plasmoidviewer -a com.github.zren.widgetname
(cd ./build && sudo make uninstall)
plasmoid.nativeInterface
The plasmoid.nativeInterface
property allows you to directly access C++ objects or functions in the Plasma::Applet
instance. You need to extend the Plasma::Applet
class first however. The plasmoid.nativeInterface
cannot be accessed by another widget namespace, so this code is private.
See the SystemTray container for an example.
plasma_install_package(package org.kde.plasma.systemtray)
set(systemtraycontainer_SRCS
systemtraycontainer.cpp
systemtraycontainer.h
)
ecm_qt_declare_logging_category(systemtraycontainer_SRCS
HEADER debug.h
IDENTIFIER SYSTEM_TRAY_CONTAINER
CATEGORY_NAME kde.systemtraycontainer
DEFAULT_SEVERITY Info
)
kcoreaddons_add_plugin(org.kde.plasma.systemtray
SOURCES ${systemtraycontainer_SRCS}
INSTALL_NAMESPACE "plasma/applets"
)
target_link_libraries(org.kde.plasma.systemtray
Qt::Gui
Qt::Quick
KF5::Plasma
KF5::XmlGui
KF5::I18n
)
#pragma once
#include <QQuickItem>
#include <Plasma/Applet>
class SystemTrayContainer : public Plasma::Applet
{
Q_OBJECT
Q_PROPERTY(QQuickItem *internalSystray READ internalSystray NOTIFY internalSystrayChanged)
public:
SystemTrayContainer(QObject *parent, const KPluginMetaData &data, const QVariantList &args);
~SystemTrayContainer() override;
void init() override;
QQuickItem *internalSystray();
protected:
void constraintsEvent(Plasma::Types::Constraints constraints) override;
void ensureSystrayExists();
Q_SIGNALS:
void internalSystrayChanged();
private:
QPointer<Plasma::Containment> m_innerContainment;
QPointer<QQuickItem> m_internalSystray;
};
Containment (SystemTray, Panel, Grouping)
Note:
- The Grouping widget only displays one child widget (aka Applet) at a time.
- The SystemTray can display multiple CompactRepresentations at a time, but only one FullRepresentation in the main popup.
- The Panel can display Compact or Full representations next to each other but is the most complicated codebase to read.
Examples:
Translate C++ Strings
If you want to messages translated in your C++ code, you will need to import KF5::I18n
and define the translation domain in your CMakeLists.txt
.
find_package(KF5 REQUIRED COMPONENTS
I18n
)
add_definitions(-DTRANSLATION_DOMAIN=\"plasma_applet_com.github.zren.widgetname\")
target_link_libraries(widgetnameplugin
KF5::I18n
)