Desktop System-UI Example
Introduction
The Desktop System-UI Example showcases the application-manager API in a simple fashion. The focus is on the concepts, less on elegance or completeness, for instance no error checking is done. Some features will only print debug messages without further functionality. A classic desktop with server-side window decorations is emulated.
The following features are supported:
- Start applications by clicking an icon on the top left
- Stop applications by clicking on the top/left window decoration rectangle
- Raise applications by clicking on the decoration
- Drag windows by pressing on the window decoration and moving
- System-UI sends 'propA' change when an app is started
- System-UI and App2 react on window property changes with a debug message
- App1 animation can be stopped and restarted by a click
- App1 sends 'rotation' property to System-UI on stop
- App1 shows a pop-up on the System-UI while it is paused
- App2 will make use of an IPC extension when it is started
- App2 logs the document URL it has been started with
- App2 triggers a notification in the System-UI when the bulb icon is clicked
- A separate ("wayland") process started outside of appman will be shown as App1
Note: The example can be run in single- and multi-process mode. In the following, multi-process is assumed and corresponding terminology is used. The terms client and application, respectively server and System-UI are used interchangeably. The System-UI comprises compositing and generic inter-process communication (IPC).
Invocation
The example can be started from within the minidesk folder with:
path/to/bin/appman -c am-config.yaml -r
Note: The application-manager attempts to register a freedesktop.org
compliant notification server. DBus errors might occur if it conflicts with the server running on the host desktop environment. In this case, a private session bus needs to be started by adding the --start-session-dbus
option:
path/to/bin/appman -c am-config.yaml -r --start-session-dbus
Walkthrough
System-UI Window
import QtQuick 2.4 import QtApplicationManager 1.0 Rectangle { property int zorder: 1 width: 1024 height: 640 color: "linen" Readme {} Text { anchors.bottom: parent.bottom text: (ApplicationManager.singleProcess ? "Single" : "Multi") + "-Process Mode" } ...
The QtApplicationManager module has to be imported to be able to access the application-manager APIs. The System-UI window has a fixed size and linen background color. Instead of a Rectangle, the root element could as well be a Window. On top of the background the Readme element is shown that displays a usage how-to. At the bottom left there is a textual indication for whether the application-manager runs in single- or multi-process mode.
Launcher
// Application launcher panel Column { id: launcher Repeater { id: menuItems model: ApplicationManager Image { source: icon opacity: isRunning ? 0.3 : 1.0 MouseArea { anchors.fill: parent onClicked: ApplicationManager.startApplication(applicationId, "documentUrl"); } } } }
A Repeater provides the application icons arranged in a Column on the top-left corner of the System-UI. This is achieved by using the ApplicationManager element as the model. Amongst others, it provides the icon
role which is used as the Image source URL. The icon
URL is defined in the info.yaml file of the application. To indicate that an application has been launched, the opacity of the corresponding application's icon is decreased through a binding to the isRunning
role.
Clicking on an application icon will launch the corresponding application through a call to ApplicationManager.startApplication(). The first argument, applicationId
, is provided by the ApplicationManager model (e.g. "tld.minidesk.app1" for the first application). Both applications will be started with the (optional) document URL "documentUrl". Subsequent clicks on an already launched icon will still call startApplication(), but will be silently ignored. The content of the appContainter
will be set below in the onWindowReady
handler. The content is provided by the two applications in an ApplicationManagerWindow root element.
Application Windows in the System-UI
// System-UI chrome for applications Repeater { id: windows model: menuItems.model Rectangle { id: winChrome property alias appContainer: appContainer width: 400; height: 320 x: 300 + model.index * 50; y: 10 + model.index * 30 color: "tan" visible: false Text { anchors.horizontalCenter: parent.horizontalCenter text: "Decoration: " + name } MouseArea { anchors.fill: parent drag.target: parent onPressed: parent.z = zorder++; // for demo purposes only } Rectangle { width: 25; height: 25 color: "chocolate" MouseArea { anchors.fill: parent onClicked: ApplicationManager.stopApplication(applicationId, false); } } Item { id: appContainer anchors.fill: parent anchors.margins: 3 anchors.topMargin: 25 } } }
This second Repeater provides the window chrome for the actual applications in its delegate. The model is the same as for the first Repeater, essentially the ApplicationManager element. The chrome consists of:
- A fixed size window Rectangle with a "tan" color. The default location depends on the
model.index
, hence each application has a different location. - The name of the application, prefixed with "Decoration" on the top horizontal center. The name is also provided by the ApplicationManager model and read from the application's info.yaml file.
- A MouseArea for dragging the window and setting the z-order. The MouseArea fills the entire window, though the application container is placed on top of it and hence it will not handle dragging. Simply increasing the z-order on each click is only done to keep the code to a minimum.
- A small chocolate-colored Rectangle on the top left corner for closing the window chrome and stopping the actual application (see stopApplication()).
- A container Item where the actual application will be placed in. The
appContainer
is exposed, so it can later be referenced to place the actual application window in it.
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 and notification Item { id: popUpContainer z: 30000 width: 200; height: 60 anchors.centerIn: parent } Text { z: 30001 font.pixelSize: 46 anchors.centerIn: parent text: NotificationManager.count > 0 ? NotificationManager.get(0).summary : "" }
Client Application Rendering
The popUpContainer
Item provides a System-UI recipient for the pop-up rendered by App1. App1 instantiates another ApplicationManagerWindow for the pop-up within its ApplicationManagerWindow root element, as shown here:
ApplicationManagerWindow { id: popUp visible: false color: "lightcoral" 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 choose type: "pop-up"
to indicate that the window is supposed to be shown as a pop-up. In the onWindowReady
handler below the System-UI checks for this property and handles the window appropriately as a pop-up.
Notification API Usage
An alternative to the above 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 the bulb icon of App2 is clicked:
var 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 will increase 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. Presenting the first notification only is a simplification to keep the code short.
WindowManager Signal Handler
// Handler for WindowManager signals Connections { target: WindowManager onWindowReady: { var appIndex = ApplicationManager.indexOfApplication(WindowManager.get(index).applicationId); var type = WindowManager.windowProperty(window, "type"); console.log("SystemUI: onWindowReady [" + window + "] - index: " + index + ", appIndex: " + appIndex + ", type: " + type); if (type !== "pop-up") { if (appIndex === -1) { console.log("Allowing a single app started outside of appman instead of App1 ..."); appIndex = 0; } var chrome = windows.itemAt(appIndex); window.parent = chrome.appContainer; window.anchors.fill = chrome.appContainer; chrome.visible = true; WindowManager.setWindowProperty(window, "propA", 42) } else { window.parent = popUpContainer; window.anchors.fill = popUpContainer; } } onWindowPropertyChanged: console.log("SystemUI: OnWindowPropertyChanged [" + window + "] - " + name + ": " + value); onWindowClosing: { console.log("SystemUI: onWindowClosing [" + window + "] - index: " + index); if (WindowManager.windowProperty(window, "type") !== "pop-up") { var appIndex = ApplicationManager.indexOfApplication(WindowManager.get(index).applicationId); if (appIndex === -1) appIndex = 0; windows.itemAt(appIndex).visible = false; } } onWindowLost: { console.log("SystemUI: onWindowLost [" + window + "] - index: " + index); WindowManager.releaseWindow(window); } }
This is the vital part of the System-UI, where the window surfaces of the applications are mapped to items in the System-UI. The onWindowReady handler is invoked when a new application window is shown (visibility set to true). The index
parameter references into the WindowManager model, which holds the window surfaces. The first line translates this index to the application index of the ApplicationManager model and assigns it to appIndex
.
Only the "pop-up" ApplicationManagerWindow of App1 has the user-defined type
property set. All other windows don't have the type
property. In the latter case, the application's window
passed to onWindowReady
is re-parented to the System-UI's winChrome
. Also the size of the window is set to fill the entire appContainer
.
There is a special treatment for an invalid appIndex
for demonstration purposes: it is assumed that the window surface is from an external application that follows the Wayland protocol. For example a Qt application could be started on the command line as follows:
QT_WAYLAND_DISABLE_WINDOWDECORATION=1 ./qtapp -platform wayland
This application will then be shown inside the App1 container. QT_WAYLAND_DISABLE_WINDOWDECORATION
is set to disable client side window decorations, since they are provided by the System-UI. Note, that an application started in this way, can just as well be closed only from the command line.
Pop-ups (windows with type "pop-up") are re-parented to their respective popUpContainer
.
The onWindowClosing
handler determines if the window is a top-level application window. If so, the applications's winChrome
is set to invisible and the corresponding application icon is set to fully opaque to indicate that the application is not running. The onWindowLost
handler calls WindowManager.releaseWindow() to free all resources associated with the window
.
IPC Extension
The following snippet demonstrates how the ApplicationIPCInterface can be used to define an IPC extension. The IPC interface has to be defined in the System-UI, for instance:
// IPC extension ApplicationIPCInterface { id: extension property double pi signal computed(string what) readonly property var _decltype_circumference: { "double": [ "double", "string" ] } function circumference(radius, thing) { console.log("SystemUI: circumference(" + radius + ", \"" + thing + "\") has been called"); pi = 3.14; var circ = 2 * pi * radius; computed("circumference for " + thing); return circ; } Component.onCompleted: ApplicationIPCManager.registerInterface(extension, "tld.minidesk.interface", {}); }
Here, a property pi
is defined, as well as a signal computed
and a function circumference
. After registering this interface with ApplicationIPCManager.registerInterface(), it can be used from the application processes.
On the application side, the ApplicationInterfaceExtension type has to be used. Here is how App2 makes use of this interface extension:
ApplicationInterfaceExtension { id: extension name: "tld.minidesk.interface" onReadyChanged: console.log("App2: circumference function returned: " + object.circumference(2.0, "plate") + ", it used pi = " + object.pi); } Connections { target: extension.object onComputed: console.log("App2: " + what + " has been computed"); onPiChanged: console.log("App2: pi changed: " + target.pi); }
The interface is used here immediately when it becomes ready. It can, of course, be accessed from elsewhere, too. The ApplicationInterfaceExtension.name has to match the name it was registered with in ApplicationIPCManager.registerInterface().
Application Termination
When an application is stopped from the System-UI through ApplicationManager.stopApplication(), it will be sent the ApplicationInterface.quit() signal. The application can do some clean-up and must subsequently confirm with ApplicationInterface.acknowledgeQuit(), like App2 does:
Connections { target: ApplicationInterface onOpenDocument: console.log("App2: onOpenDocument - " + documentUrl); onQuit: target.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.
Files:
- minidesk/am-config.yaml
- minidesk/apps/tld.minidesk.app1/app1.qml
- minidesk/apps/tld.minidesk.app1/info.yaml
- minidesk/apps/tld.minidesk.app2/app2.qml
- minidesk/apps/tld.minidesk.app2/info.yaml
- minidesk/minidesk.pro
- minidesk/minidesk.qmlproject
- minidesk/system-ui/Readme.qml
- minidesk/system-ui/main.qml
Images:
© 2018 Pelagicore AG. 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.