Package Installation Example

Learn how to implement dynamic package installations and removals.

The Package Installation example installing a package.

Note: Please read this if you want to build the example on a Linux machine.

Introduction

This example demonstrates how to install and remove packages dynamically, using the appman-package-server. The System UI comes with one built-in application (Builtin Blue), but gives you the option to install as many applications as you want dynamically. The Packages button in the lower left corner opens a dialog, where you can first connect to a package server and then install or remove packages.

You have to run an actual package server to test this example. You can find detailed instructions on how to do this in the appman-package-server documentation.

For your convenience, the example comes with two pre-packaged applications (hello-world.red and hello-world.green from the installable-apps folder). The actual packaging happens in CMakeLists.txt, using the qt6_am_create_installable_package function. Please note that this function is currently in the technical preview state and may change in the future.

In addition, this example will also print out a start command to run the package server on these packages in the application output: you have to copy and paste that into a shell to run the server.

Walkthrough

The Basic UI

The main.qml file contains a very basic compositor UI, as seen in other examples: a list of icons (showing the installed applications) on the left and a window compositing area on the right.

In addition, it also creates and initializes an instance of the PackageServerInterface component (see below for more information). Whenever the list of packages available on the server changes, packageModel within the packagesDialog is updated accordingly.

The PackageServerInterface

The appman-package-server is a separate process that communicates with the System UI via a HTTP REST interface. We are using QML's XMLHttpRequest mechanism to talk to the server, but we also encapsulate the communication in a separate PackageServerInterface component to make it nicer to use.

This component expects a few required properties to be set in order to connect to a server instance:

    required property string url
    required property string projectId
    required property string architecture

To communicate with the server, the following simple QML API is available. The packages property is actually a list that will be populated with the package descriptions available on the server.

    property string statusText
    property bool isCompatible
    property var packages

    function reload() { _serverHello() }

    function install(id) {
        let dlurl = url + "/package/download?id=" + id + "&architecture=" + architecture
        let taskId = PackageManager.startPackageInstallation(dlurl)
        return taskId
    }

    function remove(id) {
        let taskId = PackageManager.removePackage(id, true)
        return taskId
    }
The Packages Dialog

The packagesDialog is opened when the user clicks on the Packages button in the main window. It lets you set the URL of the package server and the acknowledgeMode (see The Acknowledge Dialog below. If the server URL is valid and a connection to an appman-package-server instance is possible, the lower part of the dialog will show a list of available packages on the server. The user can then select packages to install or remove.

The PackageServerInterface component is used to communicate with the server and install and remove the packages:

                            onClicked: {
                                if (isInstalled)
                                    packageServerInterface.remove(id)
                                else
                                    packageServerInterface.install(id)
                            }
The Acknowledge Dialog

The application manager expects the System UI to acknowledge package installations for security reasons. This is done by calling the acknowledgePackageInstallation in response to the taskRequestingInstallationAcknowledge signal.

This example shows you three different approaches on how to handle this in regards to user interaction:

    enum AcknowledgeMode {
        Always,                // always ask the user
        Never,                 // never ask the user
        CapabilitiesOnly       // only ask the user, if the package needs capabilities
    }

Depending on the acknowledgeMode property, the component will either automatically acknowledge any installation request, always ask the user for confirmation, or ask the user for confirmation if the package requests capabilities (see the manifest definition for more information). As an added complexity, a package can contain multiple applications, which can request different capabilities. This example will simply collect all capabilities into a flat list.

    property int mode: AcknowledgeDialog.AcknowledgeMode.Always

    property Connections connections: Connections {
        target: PackageManager
        function onTaskRequestingInstallationAcknowledge(taskId, pkg, extraMetaData, extraSignedMetaData) {
            // reduce the capabilities of all applications down to a set of unique values
            let capsSet = new Set()
            pkg.applications.forEach((app) => app.capabilities.forEach((cap) => capsSet.add(cap)))
            let capabilities = Array.from(capsSet)

            if ((mode === AcknowledgeDialog.Never)
                    || ((mode === AcknowledgeDialog.CapabilitiesOnly) && !capabilities.length)) {
                PackageManager.acknowledgePackageInstallation(taskId)

            } else if ((mode === AcknowledgeDialog.Always)
                    || ((mode === AcknowledgeDialog.CapabilitiesOnly) && capabilities.length)) {
                let d = acknowledgeDialog.createObject(root.contentItem, {
                                                           taskId: taskId,
                                                           packageName: pkg.name,
                                                           capabilities: capabilities
                                                       })
                d.open()
            }
        }
    }

The actual dialog is a standard MessageDialog, which is instantiated dynamically. Depending on whether the user accepts or rejects the dialog, the PackageManager is notified accordingly.

            onAccepted: if (!acknowledged) PackageManager.acknowledgePackageInstallation(taskId)
            onRejected: if (!acknowledged) PackageManager.cancelTask(taskId)

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.