# C++ API

### Compiling With CMake

A template can be found in `plasma-framework`:

{% embed url="<https://invent.kde.org/frameworks/plasma-framework/-/tree/master/templates/qml-plasmoid>" %}

{% hint style="warning" %}
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.
{% endhint %}

```bash
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:

```bash
(cd ./build && cmake .. -DCMAKE_INSTALL_PREFIX=/usr && make && sudo make install) && plasmoidviewer -a com.github.zren.helloworld2
```

{% hint style="warning" %}
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/>).
{% endhint %}

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

CMakeLists.txt

```cmake
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`:

{% embed url="<https://invent.kde.org/frameworks/plasma-framework/-/tree/master/templates/qml-plasmoid-with-qml-extension>" %}

Download KDevelop Template

{% embed url="<https://invent.kde.org/frameworks/plasma-framework/-/archive/master/plasma-framework-master.zip?path=templates/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`](https://invent.kde.org/plasma/kdeplasma-addons/-/tree/master/applets/mediaframe/plugin)

```qml
import org.kde.plasma.private.mediaframe 2.0
```

{% hint style="info" %}
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.
{% endhint %}

Another example is the "Kicker" plugin for the "Application Menu" widget which is reused by the kickoff "Application Launcher" widget.

```qml
import org.kde.plasma.private.kicker 0.1 as Kicker
```

* [`plasma-workspace` / `applets/kicker/plugin`](https://invent.kde.org/plasma/plasma-workspace/-/tree/master/applets/kicker/plugin)
* [`plasma-desktop` / `applets/kicker/package/contents/ui/main.qml`](https://invent.kde.org/plasma/plasma-desktop/-/blob/master/applets/kicker/package/contents/ui/main.qml#L14)
* [`plasma-desktop` / `applets/kickoff/package/contents/ui/Kickoff.qml`](https://invent.kde.org/plasma/plasma-desktop/-/blob/master/applets/kickoff/package/contents/ui/Kickoff.qml)

```txt
└── ~/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](https://github.com/Zren/plasmoid-helloworldplugin/archive/refs/heads/master.zip) or Git Clone.

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

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 [invokable function](https://doc.qt.io/qt-6/qtqml-cppintegration-exposecppattributes.html) called `randomize()` which will set the `number` property to a random number.

package/contents/ui/main.qml

```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`.&#x20;

package/metadata.json

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

```cmake
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.

```cmake
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.

```txt
/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`](https://github.com/qt/qtdeclarative/blob/dev/src/qml/qml/qqmlextensionplugin.h) and use [`qmlRegisterType()`](https://doc.qt.io/qt-5/qqmlengine.html#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.

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

CMakeLists.txt

```cmake
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](https://invent.kde.org/frameworks/plasma-framework/-/blob/master/src/declarativeimports/plasmacomponents3/qmldir) does, this will just define the namespace of the plugin and the plugin library name.

Inside `widgetnameplugin.h` we extend [`QQmlExtensionPlugin`](https://github.com/qt/qtdeclarative/blob/dev/src/qml/qml/qqmlextensionplugin.h) 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.

plugin/qmldir

```txt
module com.github.zren.widgetname
plugin widgetnameplugin
```

plugin/widgetnameplugin.h

```cpp
#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

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

The `randomize()` method needs `Q_INVOKABLE` otherwise it [cannot be called from QML](https://doc.qt.io/qt-6/qtqml-cppintegration-exposecppattributes.html).

plugin/widgetitem.h

```cpp
#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()`](https://doc.qt.io/qt-6/qdebug.html#basic-use) which lets us log to the terminal.

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

plugin/widgetitem.cpp

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

To compile, install and test this plugin follow the instructions from the previous [Compiling With CMake](#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.

```bash
cd ~/Code/plasmoid-widgetname
mkdir -p ./build
(cd ./build && cmake .. -DCMAKE_INSTALL_PREFIX=/usr && make && sudo make install)
plasmoidviewer -a com.github.zren.widgetname
```

```bash
(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-workspace`/applets/systemtray/container](https://invent.kde.org/plasma/plasma-workspace/-/tree/master/applets/systemtray/container)
* [`plasma-workspace`/.../container/CMakeLists.txt](https://invent.kde.org/plasma/plasma-workspace/-/blob/master/applets/systemtray/container/CMakeLists.txt)
* [`plasma-workspace`/.../container/systemtraycontainer.h](https://invent.kde.org/plasma/plasma-workspace/-/blob/master/applets/systemtray/container/systemtraycontainer.h)
* [`plasma-workspace`/.../container/systemtraycontainer.cpp](https://invent.kde.org/plasma/plasma-workspace/-/blob/master/applets/systemtray/container/systemtraycontainer.cpp)
* [`plasma-workspace`/.../container/package/contents/ui/main.qml](https://invent.kde.org/plasma/plasma-workspace/-/blob/master/applets/systemtray/container/package/contents/ui/main.qml)

CMakeLists.txt

```cmake
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

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

* [`kdeplasma-addons` / `applets/grouping`](https://invent.kde.org/plasma/kdeplasma-addons/-/tree/master/applets/grouping)
* [`plasma-workspace` / `applets/systemtray`](https://invent.kde.org/plasma/plasma-workspace/-/tree/master/applets/systemtray/)
* Panel
  * [`plasma-desktop` / `containments/panel`](https://invent.kde.org/plasma/plasma-desktop/-/tree/master/containments/panel)
  * [`plasma-desktop` / `desktoppackage/contents/views/Panel.qml`](https://invent.kde.org/plasma/plasma-desktop/-/blob/master/desktoppackage/contents/views/Panel.qml)

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

```cmake
find_package(KF5 REQUIRED COMPONENTS
    I18n
)

add_definitions(-DTRANSLATION_DOMAIN=\"plasma_applet_com.github.zren.widgetname\")

target_link_libraries(widgetnameplugin
    KF5::I18n
)
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.novaflowos.com/start/kde-developer-platform/readme/plasma/widget/c-api.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
