Desktop System UI Example
Illustrates a minimal Desktop System UI in pure QML.
Note: Please read this if you want to build the example on a Linux machine.
Introduction
This example showcases the application manager API in a simple way, as a classic desktop with server-side window decorations. The example focuses more on the concepts, but less on elegance or completeness. For example, there's no error checking done. Some features in this minimal Desktop System only print debug messages.
The following features are supported:
- Start applications by clicking on an icon in the top left
- Stop an application by clicking on the icon in the top left again
- Close an application window by clicking on the window's close icon
- Raise a window and set focus by pressing on the window (focus window is blue)
- Drag a window by pressing on the top window decoration and moving them
- The System UI sends a 'propA' change when an app starts
- The System UI and App2 react to window property changes with a debug message
- Stop or restart App1 animations with a click
- App1 sends rotation angle as a window property to System UI on stop
- App1 shows a pop up on the System UI when it is paused
- App2 logs the document URL that started it
- App2 triggers a notification in the System UI, when the bulb icon is clicked
- Show Wayland client windows that originate from processes outside of
appman
Note: This example can be run in single-process or multi-process mode. In the walkthrough below, we use multi-process and its corresponding terminology. The terms client and application; server and System UI are used interchangeably.
Walkthrough
System UI Window
import QtQuick import QtApplicationManager.SystemUI Window { title: "Minidesk - QtApplicationManager Example" width: 1024 height: 640 color: "whitesmoke" Readme {} Text { anchors.bottom: parent.bottom text: `${ApplicationManager.singleProcess ? "Single" : "Multi"}-process mode | ${PackageManager.architecture}` } ...
The QtApplicationManager.SystemUI module has to be imported to access the application manager APIs. The System UI window has a fixed size and "whitesmoke" background color. Instead of a Window, the root element could also be a regular item, like a Rectangle. The application manager would wrap it in a window for you. On top of the background, we display a Readme
element with information on the features available. At the bottom left there is a textual indication for whether the application manager runs in single-process or multi-process mode.
Launcher
// Application launcher panel Column { Repeater { model: ApplicationManager Image { required property string icon required property bool isRunning required property ApplicationObject application source: icon opacity: isRunning ? 0.3 : 1.0 MouseArea { anchors.fill: parent onClicked: parent.isRunning ? parent.application.stop() : parent.application.start("documentUrl"); } } } }
A Repeater provides the application icons arranged in a Column on the top left corner of the System UI; the ApplicationManager element is the model. Among others, the ApplicationManager provides the icon
role which is used as the Image source URL. The icon
URL is defined in the application's info.yaml file. To indicate that an application has launched, the corresponding application icon's opacity is decreased by binding it to the isRunning
role.
Clicking on an application icon launches the corresponding application through a call to ApplicationObject.start(). This function is accessible through the application
role in the ApplicationManager model. Both applications start with the (optional) document URL, documentUrl
. If the application is already running, ApplicationObject.stop() is called instead.
Application Windows in the System UI
// System UI chrome for applications Repeater { model: ListModel { id: topLevelWindowsModel } delegate: FocusScope { id: winChrome required property int index required property WindowObject window width: chromeImg.width height: chromeImg.height z: index Image { id: chromeImg source: winChrome.activeFocus ? "chrome-active.png" : "chrome-bg.png" Text { anchors.horizontalCenter: parent.horizontalCenter y: (25 - contentHeight) / 2 color: "white" text: "Decoration: " + (winChrome.window.application?.names["en"] ?? 'External Application') } MouseArea { width: chromeImg.width; height: 25 drag.target: winChrome onPressed: winItem.forceActiveFocus(); } Image { source: "close.png" MouseArea { anchors.fill: parent onClicked: winChrome.window.close(); } } } WindowItem { id: winItem anchors.fill: parent anchors.margins: 3 anchors.topMargin: 25 window: winChrome.window Connections { target: winChrome.window function onContentStateChanged() { if (winChrome.window.contentState === WindowObject.NoSurface) topLevelWindowsModel.remove(winChrome.index, 1); } } } onFocusChanged: (focus) => { if (focus) topLevelWindowsModel.move(winChrome.index, topLevelWindowsModel.count - 1, 1); } Component.onCompleted: { x = 300 + winChrome.index * 50; y = 10 + winChrome.index * 30; winItem.forceActiveFocus(); } } }
This second Repeater provides the window chrome for the application windows in its delegate. The model is a plain ListModel fed with window objects as they are created by the WindowManager. The code that populates the window role of this ListModel is shown below. For now let's focus on what this Repeater's delegate consists of:
- The enclosing type is a FocusScope that allows to track focus changes.
- A mostly transparent Image that acts as the window decoration (border). A blue border indicates the focus window, otherwise the border is grey. The location depends on the
model.index
, hence each application window has a different initial location. - The name of the application that created that window, prefixed with "Decoration" on top. This name is from the related ApplicationObject, defined in the application's info.yaml file.
- A MouseArea for dragging and raising the window. Only the top decoration bar is covered by that area and will hence handle dragging.
- A close icon on the top left corner for closing the window (see WindowObject.close()). Since our sample applications only have one top-level window, closing it causes the corresponding application to quit.
- The centerpiece: a WindowItem to render the
WindowObject
in the System UI; similar to the relationship between image files and QML's Image component. - And finally code to remove a row from the ListModel once its window has been destroyed from the application (client) side - either because it was closed, made invisible, or the application itself quit or crashed. Any of these cases results in the WindowObject losing its surface. A more sophisticated System UI could animate the disappearance of a window, as illustrated in the Animated Windows System UI Example.
Pop-ups
Two approaches are implemented to display pop-ups in the System UI:
- Through a window rendered by the client application
- Through the notification API provided by the application manager
This is the corresponding System UI code:
// System UI for a pop-up WindowItem { id: popUpContainer z: 9998 width: 200; height: 60 anchors.centerIn: parent Connections { target: popUpContainer.window function onContentStateChanged() { if (popUpContainer.window.contentState === WindowObject.NoSurface) popUpContainer.window = null; } } } // System UI for a notification Text { z: 9999 font.pixelSize: 46 anchors.centerIn: parent text: NotificationManager.count > 0 ? NotificationManager.get(0).summary : "" }
Client Application Rendering
App1 instantiates another ApplicationManagerWindow for the pop-up within its ApplicationManagerWindow root element, as shown here:
ApplicationManagerWindow { id: popUp visible: rotation.paused color: "orangered" Text { anchors.centerIn: parent text: "App1 paused!" } Component.onCompleted: setWindowProperty("type", "pop-up"); }
The ApplicationManagerWindow.setWindowProperty() method is used to set a freely selectable shared property. Here we chose type: "pop-up"
to indicate that the window is supposed to be shown as a pop-up.
In the WindowManager::onWindowAdded() signal handler below, the System UI checks this property and handles the window appropriately as a pop-up.
A pop-up window will be set as the content window of the popUpContainer
WindowItem in the System UI code above. For demonstration purposes, the implementation supports only one pop-up at a time. This is sufficient, since only App1 will display a single pop-up when its animation is paused. It is essential to understand, that there has to be an agreement between the System UI and applications, in terms of how windows are mapped. In contrast to regular application windows that are freely draggable and have title bars and borders, the pop-up window is just centered and has no decoration at all. Note also how the WindowObject.contentStateChanged signal is handled in the popUpContainer
: the window is released when it has no surface associated any more. This is important to free any resources that the window object is using. Note that this is done implicitly when the WindowManager model is used directly. This approach is recommended as it's more convenient.
Notification API Usage
An alternative to the window property approach is to use the application manager's Notification API on the application (client) side and the NotificationManager API on the System UI (server) side. The following code is invoked when you click on the bulb icon in App2:
let notification = ApplicationInterface.createNotification(); notification.summary = "Let there be light!" notification.show();
App2 creates a new Notification element, sets its summary property and calls show() on it. This call increases the NotificationManager.count on the System UI side, and subsequently the Text element's text property will be set to the summary
string of the first notification. For brevity, the example only presents the first notification.
WindowManager Signal Handler
// Handler for WindowManager signals Connections { target: WindowManager function onWindowAdded(window) { if (window.windowProperty("type") === "pop-up") { popUpContainer.window = window; } else { topLevelWindowsModel.append({"window": window}); window.setWindowProperty("propA", 42); } } function onWindowPropertyChanged(window, name, value) { console.log("SystemUI: OnWindowPropertyChanged [" + window + "] - " + name + ": " + value); } }
This is the vital part of the System UI, where the window (surfaces) of the applications are mapped to WindowItems in the System UI. When a new application window is available (becomes visible), the onWindowAdded handler is invoked.
Only App1's "pop-up" ApplicationManagerWindow has the user-defined type
property set. Such a window is placed in the popUpContainer
WindowItem. All other windows don't have a type
property; they are added to the topLevelWindowsModel
. This model is used in the System UI chrome Repeater above. Consequently, the window object passed as an argument to onWindowAdded is set as the window property of the WindowItem (within the Repeater's delegate).
Incidentally, any Wayland client window from a process started outside of the application manager will also be displayed since in the configuration file, "flags/noSecurity
: yes"
is set, for instance in KDE's Calculator:
$ QT_WAYLAND_DISABLE_WINDOWDECORATION=1 WAYLAND_DISPLAY=qtam-wayland-0 kcalc -platform wayland
Application Termination
When an application is stopped from the System UI through ApplicationManager.stopApplication(), it is sent the ApplicationInterface.quit() signal. Then, the application can do some clean-up and it must subsequently confirm with ApplicationInterface.acknowledgeQuit(), like App2 does:
Connections { target: ApplicationInterface function onOpenDocument(documentUrl, mimeType) { console.log("App2: onOpenDocument - " + documentUrl); } function onQuit() { ApplicationInterface.acknowledgeQuit(); } }
Note that App1 is not well-behaved: it does not acknowledge the quit
signal and will hence simply be terminated by the application manager.
© 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.