Nova Flow OS
KDE Developer Platform
KDE Developer Platform
  • KDE Developer Platform
    • Getting started
      • Building KDE software
        • KDE software
        • Where to find the development team
        • Learning more
        • Choose what to work on
        • Source code cross-referencing
        • Installing build dependencies
        • Set up a development environment
        • Building KDE software with kdesrc-build
        • Basic troubleshooting
        • Tips and tricks
        • IDE Configuration
          • Setting up an IDE for KDE development
          • Visual Studio Code
          • Qt Creator
          • Kate
          • KDevelop
          • CLion
          • Sublime Text
        • Building KDE software manually
        • Building KDE software with distrobox and podman
      • Kirigami
        • KDE is ours
        • Setting up and getting started
        • Explaining pages
        • Layouts, ListViews, and Cards
        • Adding actions
        • Adding a dialog
        • Using separate files
        • Next steps
        • Colors and themes in Kirigami
        • Typography
        • Actions based components
        • Page rows and page stacks
        • Scrollable pages and list views
        • Cards
        • Drawers
        • Chips
        • Dialog types
        • Controls and interactive elements
        • Form layouts
        • Inline messages
        • Action toolbars
        • Progress bars and indicators
        • List views
        • Understanding CMakeLists
        • Figuring out main.cpp
        • Connect logic to your QML user interface
        • Connect models to your QML user interface
        • About page
        • Introduction to Kirigami Addons
        • FormCard About pages
        • Form delegates in your settings pages
      • KXmlGui
        • Getting started with KXmlGui
        • Hello World!
        • Creating the main window
        • Using actions
        • Saving and loading
        • Command line interface
      • Python with Kirigami
        • Apps with QML and Python
        • Your first Python + Kirigami application
        • Creating a Python package
        • Creating a Flatpak
      • Common programming mistakes
      • Adding a new KDE project
    • Features
      • Icons
      • Configuration
        • The KConfig Framework
        • Introduction to KConfig
        • Using KConfig XT
        • KDE Frameworks 6 porting guide
        • Settings module (KCM) development
        • KConfigDialog
      • D-Bus
        • What is D-Bus practically useful for?
        • Introduction to D-Bus
        • Accessing D-Bus interfaces
        • Intermediate D-Bus
        • Creating D-Bus interfaces
        • Using custom types with D-Bus
        • D-Bus autostart services
      • Create your own mouse cursor theme
      • Session management
      • Archives
      • Desktop file
      • KAuth
        • Privilege Escalation
        • Using actions in your applications
      • KIdleTime
      • Akonadi: personal information management
        • Debugging Akonadi Resources
        • Using Akonadi in applications
      • Concurrent programming
      • Solid
      • Sonnet
    • Plasma themes and plugins
      • Getting started
      • Plasma Widget tutorial
        • How to create a plasmoid
        • Setup
        • Porting Plasmoids to KF6
        • Testing
        • QML
        • Plasma's QML API
        • Widget Properties
        • Configuration
        • Translations / i18n
        • Examples
        • C++ API
      • KWin Effects
      • Plasma Desktop scripting
        • Javascript Interaction With Plasma Shells
        • Templates
        • Examples
        • API documentation
        • Configuration keys
      • Plasma Style tutorial
        • Creating a Plasma Style quickstart
        • Understanding Plasma Styles
        • SVG elements and Inkscape
        • Background SVG format
        • System and accent colors
        • Theme elements reference
        • Porting themes to Plasma 5
        • Porting themes to Plasma 6
      • Aurorae window decorations
      • KWin scripting tutorial
        • Quick start
        • KWin scripting API
      • Wallpapers
      • Plasma comic
        • Tutorial
        • Testing and debugging
        • Examples
      • Create a custom Window Switcher
      • KRunner C++ Plugin
        • Basic Anatomy of a Runner
        • KRunner metadata format
    • Applications
      • Creating sensor faces
      • Dolphin
        • Creating Dolphin service menus
      • Kate
        • Kate plugin tutorial
      • KMines
        • Making a KMines theme
      • Writing tests
        • Appium automation testing
    • Packaging
      • Android
        • KDE on Android
        • Building applications for Android
        • Packaging and publishing applications for Android
        • Publishing on Google Play
          • Introduction
          • Packaging your app
          • Adding your app to Google Play
          • Publishing your app
          • Releasing new versions of old apps
        • Porting applications to Android
          • Basic porting
          • Making applications run well on Android
          • Metadata
      • Windows
        • Packaging and publishing applications for Windows
        • Publish your app in the Microsoft Store
          • Packaging your app for the Microsoft Store
          • Submitting your app to the Microsoft Store
      • Plasma Mobile
        • KDE on mobile devices
        • Porting a new device to Plasma Mobile
        • KDE Telephony stack
          • General Overview
          • Kernel layer
          • System daemons
            • General overview
            • Developing Telephony functionality
            • ModemManager Telephony functions
          • Session daemons
          • QML declarative plugin layer
          • KDE application layer
        • Execute applications
      • Distributing KDE software as Flatpak
        • Your first Flatpak
        • Extending your package
        • Nightly Flatpaks and Flathub
        • Testing your Flatpak
    • System administration
      • Shell scripting with KDE dialogs
      • Kiosk: Simple configuration management for large deployment
        • Abstract
        • Introduction to Kiosk
        • Kiosk keys
    • Contribute to the documentation
    • About
      • Readme
      • License
        • Creative Commons Attribution-ShareAlike 4.0 International
        • GNU General Public License 3.0 or later
Powered by GitBook
On this page
  • Compiling With CMake
  • Private C++ QML Plugin
  • plasmoid.nativeInterface
  • Containment (SystemTray, Panel, Grouping)
  • Translate C++ Strings
  1. KDE Developer Platform
  2. Plasma themes and plugins
  3. Plasma Widget tutorial

C++ API

Compiling advanced C++ widgets with CMake

PreviousExamplesNextKWin Effects

Last updated 8 months ago

Compiling With CMake

A template can be found in plasma-framework:

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](https://store.kde.org). You will need to publish it in an [Ubuntu PPA](https://help.ubuntu.com/community/PPA), on the [Arch AUR](https://aur.archlinux.org/), or with [OpenSUSE OBS](https://build.opensuse.org/).

└── ~/Code/plasmoid-helloworld2/
    ├── package
    │   ├── contents
    │   │   └── ...
    │   └── metadata.json
    └── CMakeLists.txt

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

You can find a template in plasma-framework:

Download KDevelop Template

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.

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.

mkdir -p ~/Code
cd ~/Code
git clone https://github.com/Zren/plasmoid-helloworldplugin plasmoid-widgetname
cd ~/Code/plasmoid-widgetname

package/contents/ui/main.qml

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()
    }
}

Before moving on to the C++ code, don't forget to create the metadata.json.

package/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

Don't forget to link the components as well.

find_package(Qt5 REQUIRED COMPONENTS
    Core
    Qml
)
# ...
target_link_libraries(widgetnameplugin
    Qt::Core
    Qt::Qml
)

CMakeLists.txt

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)

In the .cpp file we register the new QML type. Don't forget to edit the namespace in the assert.

plugin/qmldir

module com.github.zren.widgetname
plugin widgetnameplugin

plugin/widgetnameplugin.h

#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;
};

plugin/widgetnameplugin.cpp

#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.

plugin/widgetitem.h

#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;
};

In the setter, we do not emit the signal if the property does not actually change.

plugin/widgetitem.cpp

#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);
}

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.

CMakeLists.txt

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
)

systemtraycontainer.cpp

#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:

  • Panel

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
)

A full copy of this example can be downloaded as a ZIP or cloned from GitHub. or Git Clone.

To start off, let's work out what we want in the QML code. For this simple example, we will import a new WidgetItem type, which has a property named number and has an called randomize() which will set the number property to a random number.

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 and use . Since we are sticking to a simple QObject in our new type, we will only need Qt5::Core.

The qmldir file is basically the qml plugin metadata file. Since we don't bundle any .qml files in the plugin itself does, this will just define the namespace of the plugin and the plugin library name.

Inside widgetnameplugin.h we extend and indicate we implement the QQmlExtensionInterface which somehow tells it to call registerTypes().

The randomize() method needs Q_INVOKABLE otherwise it .

To make development easier, we've imported which lets us log to the terminal.

To compile, install and test this plugin follow the instructions from the previous section and the Widget Testing page.

kdeplasma-addons / applets/mediaframe
plasma-workspace / applets/kicker/plugin
plasma-desktop / applets/kicker/package/contents/ui/main.qml
plasma-desktop / applets/kickoff/package/contents/ui/Kickoff.qml
Download ZIP
invokable function
QQmlExtensionPlugin
qmlRegisterType()
like PlasmaComponents
QQmlExtensionPlugin
cannot be called from QML
qDebug()
plasma-workspace/applets/systemtray/container
plasma-workspace/.../container/CMakeLists.txt
plasma-workspace/.../container/systemtraycontainer.h
plasma-workspace/.../container/systemtraycontainer.cpp
plasma-workspace/.../container/package/contents/ui/main.qml
kdeplasma-addons / applets/grouping
plasma-workspace / applets/systemtray
plasma-desktop / containments/panel
plasma-desktop / desktoppackage/contents/views/Panel.qml
Compiling With CMake
Files · master · Plasma / libplasma · GitLabGitLab
Files · master · Plasma / libplasma · GitLabGitLab
https://invent.kde.org/frameworks/plasma-framework/-/archive/master/plasma-framework-master.zip?path=templates/qml-plasmoid-with-qml-extension
Logo
Logo