Package Installation Example
Learn how to implement dynamic package installations and removals.
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)
© 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.