Application Features Example

Showcases client applications with various features and QML modularization.

Introduction

This example demonstrates how to implement some particular features you may require in an application, such as:

  • how to implement a nested compositor
  • how to simulate a crash and recover from it
  • how to show multiple top-level windows simultaneously
  • how to use the native runtime, with no QML

Most of these features are only supported properly in multi-process mode.

The example focuses on the application (client) side. The System UI (compositor/server) is based on the Desktop System UI Example with some modifications. Refer to that example for more details on how to implement a System UI.

In addition, this example shows how to write and use QML modules in the application manager context, see below.

Nested Compositor

The nested compositor application shows how to implement a Wayland compositor inside an application (Wayland client) window. The compositor is implemented in pure QML and kept to a minimum. To display Wayland clients within this compositor, you need to set the WAYLAND_DISPLAY environment variable appropriately.

To start a client with this environment variable set via command line:

WAYLAND_DISPLAY=qtam-wayland-nested qml client.qml -platform wayland

This command only works in multi-process mode, since the nested compositor needs a real window as its root element.

The QML code for the nested compositor application is as follows:

import QtQuick
import QtApplicationManager.Application
import QtWayland.Compositor
import QtWayland.Compositor.XdgShell
import QtWayland.Compositor.WlShell

ApplicationManagerWindow {
    id: root
    color: "lightgrey"

    property ListModel shellSurfaces: ListModel {}

    Text {
        anchors.fill: parent
        anchors.margins: 8
        font.pointSize: 14
        wrapMode: Text.Wrap
        textFormat: Text.RichText
        text: "This Wayland<sup>*</sup> client window implements a Wayland compositor (nested compositor). " +
              "To display Wayland clients here, set:<br><br><b>WAYLAND_DISPLAY=qtam-wayland-nested</b>" +
              "<br><br>For instance:<br>WAYLAND_DISPLAY=qtam-wayland-nested qml client.qml -platform wayland" +
              "<br><br><small>* in multi-process mode</small>"
    }

    WaylandCompositor {
        socketName: "qtam-wayland-nested"

        WaylandOutput {
            window: root.backingObject
            sizeFollowsWindow: true
        }

        WlShell {
            onWlShellSurfaceCreated: (shellSurface) => root.shellSurfaces.append({shellSurface: shellSurface});
        }

        XdgShell {
            onToplevelCreated: (toplevel, xdgSurface) => root.shellSurfaces.append({shellSurface: xdgSurface});
        }
    }

    Repeater {
        model: root.shellSurfaces
        ShellSurfaceItem {
            required property var modelData
            required property int index
            shellSurface: modelData
            onSurfaceDestroyed: root.shellSurfaces.remove(index)
        }
    }

    Component.onCompleted: console.info("Start a client application in the nested compositor for instance with:\n  " +
                                        "WAYLAND_DISPLAY=qtam-wayland-nested QT_WAYLAND_DISABLE_WINDOWDECORATION=1 " +
                                        "QT_WAYLAND_SHELL_INTEGRATION=xdg-shell qml client.qml -platform wayland");
}
Crash Simulation and Recovery

This application provides various means to force a crash in an application, such as a segmentation fault. It utilizes QML modules that include C++ and QML code; in particular, the C++ provided QML types Terminator1 and Terminator2 are used to trigger crashes. The application manager then prints the cause of the crash and related information, like a backtrace. The System UI implements a basic form of crash recovery: restarting the application. Of course, crash recovery only works in multi-process mode. In single-process mode, a crash affects the entire program (the System UI).

The QML code for the crash simulation and recovery application is as follows:

import QtQuick
import QtApplicationManager.Application
import Crash
import Sequel

ApplicationManagerWindow {
    id: root

    readonly property var modes: ({
           illegalMemory: [ "Illegal memory access", root.accessIllegalMemory ],
           illegalMemoryInThread: [ "Illegal memory access in a thread", t2.accessIllegalMemoryInThread ],
           stackOverflow: [ "Force stack overflow", t2.forceStackOverflow ],
           divideByZero: [ "Divide by zero", t2.divideByZero ],
           raise: [ "Raise signal 7", t2.raise ],
           abort: [ "Call abort", Terminator1.abort ],
           unhandledException: [ "Throw unhandled exception", Terminator1.throwUnhandledException ],
           gracefully: [ "Exit gracefully", Terminator1.exitGracefully ]
    })

    property var accessIllegalMemory: (function() {
        let count = 0;
        return function recursive() {
            if (++count > 9)
                t2.accessIllegalMemory();
            else
                root.accessIllegalMemory();
        }
    })()

    color: "black"

    Terminator2 {
        id: t2
        signum: 7
    }

    CrashAnimation {
        id: crashAnimation;
        scaleTarget: content
        colorTarget: root
        onFinished: root.modes[mode][1]();
    }

    Grid {
        id: content
        anchors.centerIn: parent
        columns: 2
        Repeater {
            model: Object.keys(root.modes)
            Rectangle {
                required property var modelData
                width: root.width / 2
                height: root.height / 4
                border.width: 1
                color: "lightgrey"

                Text {
                    anchors.fill: parent
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    wrapMode: Text.Wrap
                    font.pointSize: 14
                    text: modes[parent.modelData][0]
                }

                MouseArea {
                    anchors.fill: parent
                    onClicked: crashAnimation.mode = parent.modelData;
                }
            }
        }
    }
}
Two Top-Level Windows

This application illustrates how you can display multiple top-level windows by having a QtObject as the application's root element.

The QML code for the two top-level windows application is as follows:

import QtQuick
import QtApplicationManager.Application

QtObject {
    property var win1: ApplicationManagerWindow {
        color: "lightsteelblue"

        Rectangle {
            width: 80; height: 80; radius: 40
            color: "orange"

            MouseArea {
                anchors.fill: parent
                drag.target: parent
            }
        }
    }

    property var win2: ApplicationManagerWindow {
        color: "transparent"

        Rectangle {
            id: rect
            anchors.fill: parent
            color: "orange"
            opacity: 0.4
        }

        ApplicationManagerWindow {
            id: popup
            width: 300; height: 100

            Rectangle {
                anchors.fill: parent
                border.width: 1
                color: "orangered"
            }

            Text {
                anchors.centerIn: parent
                text: "Click to hide!"
            }

            MouseArea {
                anchors.fill: parent
                onClicked: popup.visible = false;
            }

            Component.onCompleted: setWindowProperty("type", "pop-up");
        }
    }
}
Native Widgets

This application is based on QWidgets. Compared to the other applications in this example, which are QML applications, this one uses the native runtime. Consequently, the application's entry point isn't a main.qml file, but an executable. This application is a basic application that still adheres to this particular System UI. It's meant to illustrate the concept: the System UI needs a type window property to differentiate between normal windows and popups.

This application only works in multi-process mode, as application processes cannot be started in single-process mode.

Linking against the private application manager modules is prohibited by default to prevent potential problems with duplicate symbols coming from QML plugins. However here building against them is both intended and required, so we need to set the define AM_COMPILING_LAUNCHER:

target_compile_definitions(widgets PRIVATE AM_COMPILING_LAUNCHER)

The C++ code for the native widgets application is as follows:

#include <QApplication>
#include <QPushButton>
#include <QDialog>
#include <QVBoxLayout>

#include <QtAppManCommon/logging.h>
#include <QtAppManApplicationMain/applicationmain.h>
#include <QtAppManSharedMain/notification.h>

int main(int argc, char *argv[])
{
    QtAM::Logging::initialize(argc, argv);
    QtAM::Logging::setApplicationId("widgets");

    try {
        QtAM::ApplicationMain am(argc, argv);

        am.setup();

        QWidget window;
        QVBoxLayout layout(&window);

        // Popup using application manager window property
        QPushButton button1(QStringLiteral("Click to open/close a popup"));
        button1.setStyleSheet(QStringLiteral("QPushButton { background-color : limegreen; font-size: 36px; }"));
        layout.addWidget(&button1);

        QDialog *popup1 = new QDialog(&window);
        (new QPushButton(QStringLiteral("I'm a popup!"), popup1))->resize(340, 140);
        popup1->setStyleSheet(QStringLiteral("QPushButton { background-color : limegreen; color : white; font-size: 24px; }"));
        QObject::connect(&button1, &QPushButton::clicked, popup1, [&popup1, &am] () {
            popup1->setVisible(!popup1->isVisible());
            am.setWindowProperty(popup1->windowHandle(), QStringLiteral("type"), QStringLiteral("pop-up"));
        });

        // Notification
        QPushButton button2(QStringLiteral("Click to open a notification"));
        button2.setStyleSheet(QStringLiteral("QPushButton { background-color : darkgrey; font-size: 36px; }"));
        layout.addWidget(&button2);

        QtAM::Notification *notification = am.createNotification(&am);
        notification->setSummary(QStringLiteral("I'm a notification"));
        QObject::connect(&button2, &QPushButton::clicked, notification, &QtAM::Notification::show);

        // Application interface for handling quit
        QObject::connect(&am, &QtAM::ApplicationMain::quit, &am, &QCoreApplication::quit);

        am.processEvents();
        window.showNormal();

        return am.exec();

    } catch (const std::exception &e) {
        qWarning() << "ERROR:" << e.what();
    }
}

Code Structure

Compared to the other Qt Application Manager Examples, which are purely based on QML, this example requires you to build it explicitly. The code is structured in a way where the resulting application folders only contain the artifacts necessary to run the application. Consequently, you can package these applications and install them as well.

To build Qt Application Manager, including its examples, you need to pass -DQT_BUILD_EXAMPLES=ON to CMake. For more details, see Build.

The System UI and the Crash application produce QML modules. These modules are generated as libraries that include all the C++ and QML code and other resources, like images. Consequently, only the libraries need to be loaded from the native file system instead of individual QML files, images, or other such assets. As an additional bonus, you get things like pre-compiled QML, linting checks, and auto-generation of files like qmldir.

System UI QML Module

In the SystemUi/CMakeLists.txt file, the systemuimodule is defined:

qt_policy(SET QTP0001 NEW)   # "qt/qml/" default resource prefix

qt6_add_qml_module(
    systemuimodule
    URI "SystemUi"
    NO_PLUGIN
    RESOURCES grab.png close.png
    QML_FILES main.qml
)

The default "qt/qml" resource prefix is used here. By providing the NO_PLUGIN keyword, only a dynamic backing library is created. A QML plugin is unnecessary since the library will be loaded explicitly (see below). To keep it simple, the module is kept in a directory with the same name as its URI (SystemUi). Two images and the main.qml file is added to the module (there is no C++ code used, though).

The systemuimodule dynamic library is added to the list of resources that are loaded at startup in the am-config.yaml file (key ui/resources):

ui:
  mainQml: ":/qt/qml/SystemUi/main.qml"
  resources: [ "${CONFIG_PWD}/SystemUi/libsystemuimodule" ]

The library is loaded from the native file system, but the main.qml file can subsequently be loaded from the resource file system (key ui/mainQml). The path starts with the resource prefix, extended with the module URI. Since the images used in main.qml have relative URLs they are also found in the resource file system.

A general overview how to add and use resources in the application manager context can be found in Using Qt Resources.

Crash Application QML Modules

The Crash application consists of two QML modules: crashmodule and sequelmoduleplugin. The first is analogous to the systemuimodule above, except that it also includes C++ code (terminator1.cpp). The generated library is loaded explicitly when the application starts through the following lines in apps/Crash/info.yaml:

runtimeParameters:
  resources: [ "libcrashmodule" ]

It provides the crashapp.qml file, which is the application's main QML file and the Terminator1 QML singleton type.

The second module, sequelmoduleplugin is defined as a QML plugin in apps/Crash/Sequel/CMakeLists.txt:

qt6_add_qml_module(
    sequelmoduleplugin
    URI "Sequel"
    PLUGIN_TARGET sequelmoduleplugin
    SOURCES terminator2.cpp
    QML_FILES CrashAnimation.qml
)

For convenience, PLUGIN_TARGET is defined with the same argument as the module target name. This creates a plugin that already includes the backing target (see qt_add_qml_module). The plugin is loaded implicitly at runtime through the "import Sequel" statement in crashapp.qml. It provides the CrashAnimation and Terminator2 QML types.

Example project @ code.qt.io

© 2024 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.