on xenial help sign in
Initial multi-monitor support

When an external screen appears, move the shell to it

commit385d92ae3811a59dd672258b068f8c47e408c687 authorDaniel d'Andrada <daniel.dandrada@canonical.com> dateWed 21 Oct 2015, 11:50 (11 years ago) parenteb75df38d89183
29 files changed+748−169
debian/unity8.install+1 qml/Components/WallpaperResolver.qml+63 qml/DeviceConfiguration.qml+1 qml/DisabledScreenNotice.qml+49 qml/Notifications/Notification.qml+2 qml/Notifications/NotificationMenuItemFactory.qml+2−1 qml/Notifications/Notifications.qml+2 qml/OrientedShell.qml+10−1 qml/Rotation/RotationStates.qml+2−2 qml/Shell.qml+9−32 src/ApplicationArguments.h+10−2 src/CMakeLists.txt+3 src/SecondaryWindow.cpp+31 src/SecondaryWindow.h+30 src/ShellApplication.cpp+197 src/ShellApplication.h+55 src/ShellView.cpp+59 src/ShellView.h+34 src/main.cpp+4−101 tests/autopilot/unity8/fixture_setup.py+1−1 tests/autopilot/unity8/greeter/tests/__init__.py+1−1 tests/autopilot/unity8/launcher.py+1−1 tests/autopilot/unity8/shell/__init__.py+1−1 tests/autopilot/unity8/shell/tests/__init__.py+1−1 tests/qmltests/CMakeLists.txt+2 tests/qmltests/Components/tst_WallpaperResolver.qml+89 tests/qmltests/tst_DisabledScreenNotice.qml+37 tests/qmltests/tst_OrientedShell.qml+51−1 tests/qmltests/tst_Shell.qml−24
debian/unity8.install+1
@@ -12,6 +12,7 @@ usr/share/unity8/Panel
1212usr/share/unity8/Rotation
1313usr/share/unity8/DeviceConfiguration.qml
1414usr/share/unity8/OrientedShell.qml
15+usr/share/unity8/DisabledScreenNotice.qml
1516usr/share/unity8/Shell.qml
1617usr/share/unity8/Stages
1718usr/share/unity8/Tutorial
qml/Components/WallpaperResolver.qmlnew file+63
@@ -0,0 +1,63 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import AccountsService 0.1
19+import GSettings 1.0
20+import Ubuntu.Components 1.3
21+
22+/*
23+ Defines the background URL based on several factors, such as:
24+ - default, fallback, background
25+ - Background set in AccountSettings, if any
26+ - Background set in GSettings, if any
27+ */
28+QtObject {
29+ // Users should set their UI width here.
30+ property real width
31+
32+ property url defaultBackground: Qt.resolvedUrl(width >= units.gu(60) ? "../graphics/tablet_background.jpg"
33+ : "../graphics/phone_background.jpg")
34+
35+ // That's the property users of this component are going to consume.
36+ readonly property url background: asImageTester.status == Image.Ready ? asImageTester.source
37+ : gsImageTester.status == Image.Ready ? gsImageTester.source : defaultBackground
38+
39+ // This is a dummy image to detect if the custom AS set wallpaper loads successfully.
40+ property var _asImageTester: Image {
41+ id: asImageTester
42+ source: AccountsService.backgroundFile != undefined && AccountsService.backgroundFile.length > 0 ? AccountsService.backgroundFile : ""
43+ height: 0
44+ width: 0
45+ sourceSize.height: 0
46+ sourceSize.width: 0
47+ }
48+
49+ // This is a dummy image to detect if the custom GSettings set wallpaper loads successfully.
50+ property var _gsImageTester: Image {
51+ id: gsImageTester
52+ source: backgroundSettings.pictureUri && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : ""
53+ height: 0
54+ width: 0
55+ sourceSize.height: 0
56+ sourceSize.width: 0
57+ }
58+
59+ property var _gsettings: GSettings {
60+ id: backgroundSettings
61+ schema.id: "org.gnome.desktop.background"
62+ }
63+}
qml/DeviceConfiguration.qml+1
@@ -86,6 +86,7 @@ StateGroup {
8686 PropertyChanges {
8787 target: root
8888 category: "desktop"
89+ supportedOrientations: root.useNativeOrientation
8990 }
9091 }
9192 ]
qml/DisabledScreenNotice.qmlnew file+49
@@ -0,0 +1,49 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+import "Components"
20+
21+Image {
22+ id: root
23+
24+ WallpaperResolver {
25+ width: root.width
26+ id: wallpaperResolver
27+ }
28+
29+ source: wallpaperResolver.background
30+
31+ UbuntuShape {
32+ anchors.fill: text
33+ anchors.margins: -units.gu(2)
34+ backgroundColor: "black"
35+ opacity: 0.4
36+ }
37+
38+ Label {
39+ id: text
40+ anchors.centerIn: parent
41+ width: parent.width / 2
42+ text: i18n.tr("Your device is now connected to an external display.")
43+ color: "white"
44+ horizontalAlignment: Text.AlignHCenter
45+ verticalAlignment: Text.AlignVCenter
46+ fontSize: "x-large"
47+ wrapMode: Text.Wrap
48+ }
49+}
qml/Notifications/Notification.qml+2
@@ -50,6 +50,7 @@ Item {
5050 readonly property real contentSpacing: units.gu(2)
5151 readonly property bool canBeClosed: type === Notification.Ephemeral
5252 property bool hasMouse
53+ property url background: ""
5354
5455 objectName: "background"
5556 implicitHeight: type !== Notification.PlaceHolder ? (fullscreen ? maxHeight : outterColumn.height - shapedBack.anchors.topMargin + contentSpacing * 2) : 0
@@ -401,6 +402,7 @@ Item {
401402 menuData: model
402403 menuIndex: index
403404 maxHeight: notification.maxHeight
405+ background: notification.background
404406
405407 onLoaded: {
406408 notification.fullscreen = Qt.binding(function() { return fullscreen; });
qml/Notifications/NotificationMenuItemFactory.qml+2−1
@@ -30,6 +30,7 @@ Loader {
3030 property int menuIndex : -1
3131 property int maxHeight
3232 readonly property bool fullscreen: menuData.type === "com.canonical.snapdecision.pinlock"
33+ property url background: ""
3334
3435 signal accepted()
3536
@@ -149,7 +150,7 @@ Loader {
149150 infoText: notification.summary
150151 errorText: errorAction.valid ? errorAction.state : ""
151152 retryText: notification.body
152− background: shell.background
153+ background: menuFactory.background
153154 darkenBackground: 0.4
154155
155156 onEntered: {
qml/Notifications/Notifications.qml+2
@@ -29,6 +29,7 @@ ListView {
2929 property real margin
3030 property bool useModal: snapDecisionProxyModel.count > 0
3131 property bool hasMouse
32+ property url background: ""
3233
3334 UnitySortFilterProxyModel {
3435 id: snapDecisionProxyModel
@@ -60,6 +61,7 @@ ListView {
6061 maxHeight: notificationList.height
6162 margins: notificationList.margin
6263 hasMouse: notificationList.hasMouse
64+ background: notificationList.background
6365
6466 // make sure there's no opacity-difference between the several
6567 // elements in a notification
qml/OrientedShell.qml+10−1
@@ -76,6 +76,12 @@ Rectangle {
7676 oskSettings.disableHeight = shell.usageScenario == "desktop"
7777 }
7878
79+ // we must rotate to a supported orientation regardless of shell's preference
80+ property bool orientationChangesEnabled:
81+ (orientation & supportedOrientations) === 0 ? true
82+ : shell.orientationChangesEnabled
83+
84+
7985 Binding {
8086 target: oskSettings
8187 property: "stayHidden"
@@ -89,7 +95,10 @@ Rectangle {
8995 }
9096
9197 readonly property int supportedOrientations: shell.supportedOrientations
92− & deviceConfiguration.supportedOrientations
98+ & (deviceConfiguration.supportedOrientations == deviceConfiguration.useNativeOrientation
99+ ? nativeOrientation
100+ : deviceConfiguration.supportedOrientations)
101+
93102 property int acceptedOrientationAngle: {
94103 if (orientation & supportedOrientations) {
95104 return Screen.angleBetween(nativeOrientation, orientation);
qml/Rotation/RotationStates.qml+2−2
@@ -83,7 +83,7 @@ StateGroup {
8383 }
8484
8585 function tryUpdateState() {
86− if (d.transitioning || (!d.startingUp && !root.shell.orientationChangesEnabled)) {
86+ if (d.transitioning || (!d.startingUp && !root.orientedShell.orientationChangesEnabled)) {
8787 return;
8888 }
8989
@@ -95,7 +95,7 @@ StateGroup {
9595 }
9696
9797 property Connections shellConnections: Connections {
98− target: root.shell
98+ target: root.orientedShell
9999 onOrientationChangesEnabledChanged: {
100100 d.tryUpdateState();
101101 }
qml/Shell.qml+9−32
@@ -17,7 +17,6 @@
1717import QtQuick 2.0
1818import QtQuick.Window 2.0
1919import AccountsService 0.1
20−import GSettings 1.0
2120import Unity.Application 0.1
2221import Ubuntu.Components 0.1
2322import Ubuntu.Components.Popups 1.0
@@ -108,9 +107,11 @@ Item {
108107 enabled: greeter && !greeter.waiting
109108
110109 property real edgeSize: units.gu(2)
111− property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
112− property url background: asImageTester.status == Image.Ready ? asImageTester.source
113− : gsImageTester.status == Image.Ready ? gsImageTester.source : defaultBackground
110+
111+ WallpaperResolver {
112+ id: wallpaperResolver
113+ width: shell.width
114+ }
114115
115116 readonly property alias greeter: greeterLoader.item
116117
@@ -131,31 +132,6 @@ Item {
131132 shell.activateApplication(app);
132133 }
133134
134− // This is a dummy image to detect if the custom AS set wallpaper loads successfully.
135− Image {
136− id: asImageTester
137− source: AccountsService.backgroundFile != undefined && AccountsService.backgroundFile.length > 0 ? AccountsService.backgroundFile : ""
138− height: 0
139− width: 0
140− sourceSize.height: 0
141− sourceSize.width: 0
142− }
143−
144− GSettings {
145− id: backgroundSettings
146− schema.id: "org.gnome.desktop.background"
147− }
148−
149− // This is a dummy image to detect if the custom GSettings set wallpaper loads successfully.
150− Image {
151− id: gsImageTester
152− source: backgroundSettings.pictureUri && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : ""
153− height: 0
154− width: 0
155− sourceSize.height: 0
156− sourceSize.width: 0
157− }
158−
159135 Binding {
160136 target: LauncherModel
161137 property: "applicationManager"
@@ -323,7 +299,7 @@ Item {
323299 Binding {
324300 target: applicationsDisplayLoader.item
325301 property: "background"
326− value: shell.background
302+ value: wallpaperResolver.background
327303 }
328304 Binding {
329305 target: applicationsDisplayLoader.item
@@ -428,7 +404,7 @@ Item {
428404 tabletMode: shell.usageScenario != "phone"
429405 launcherOffset: launcher.progress
430406 forcedUnlock: tutorial.running
431− background: shell.background
407+ background: wallpaperResolver.background
432408
433409 // avoid overlapping with Launcher's edge drag area
434410 // FIXME: Fix TouchRegistry & friends and remove this workaround
@@ -603,7 +579,7 @@ Item {
603579 id: wizard
604580 objectName: "wizard"
605581 anchors.fill: parent
606− background: shell.background
582+ background: wallpaperResolver.background
607583
608584 function unlockWhenDoneWithWizard() {
609585 if (!active) {
@@ -634,6 +610,7 @@ Item {
634610 model: NotificationBackend.Model
635611 margin: units.gu(1)
636612 hasMouse: shell.hasMouse
613+ background: wallpaperResolver.background
637614
638615 y: topmostIsFullscreen ? 0 : panel.panelHeight
639616 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
src/ApplicationArguments.h+10−2
@@ -26,17 +26,25 @@
2626class ApplicationArguments : public QObject
2727{
2828 Q_OBJECT
29− Q_PROPERTY(QString deviceName READ deviceName CONSTANT)
29+ Q_PROPERTY(QString deviceName READ deviceName NOTIFY deviceNameChanged)
3030 Q_PROPERTY(QString mode READ mode CONSTANT)
3131public:
3232 ApplicationArguments(QObject *parent = nullptr);
3333
34− void setDeviceName(const QString &deviceName) { m_deviceName = deviceName; }
34+ void setDeviceName(const QString &deviceName) {
35+ if (deviceName != m_deviceName) {
36+ m_deviceName = deviceName;
37+ Q_EMIT deviceNameChanged(m_deviceName);
38+ }
39+ }
3540 QString deviceName() const { return m_deviceName; }
3641
3742 void setMode(const QString &mode) { m_mode = mode; }
3843 QString mode() const { return m_mode; }
3944
45+Q_SIGNALS:
46+ void deviceNameChanged(const QString&);
47+
4048private:
4149 QString m_deviceName;
4250 QString m_mode;
src/CMakeLists.txt+3
@@ -21,6 +21,9 @@ add_executable(${SHELL_APP}
2121 main.cpp
2222 MouseTouchAdaptor.cpp
2323 CachingNetworkManagerFactory.cpp
24+ SecondaryWindow.cpp
25+ ShellApplication.cpp
26+ ShellView.cpp
2427 UnityCommandLineParser.cpp
2528 ${QML_FILES} # This is to make qml and image files appear in the IDE's project tree
2629)
src/SecondaryWindow.cppnew file+31
@@ -0,0 +1,31 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "SecondaryWindow.h"
18+
19+// local
20+#include <paths.h>
21+
22+SecondaryWindow::SecondaryWindow(QQmlEngine *engine)
23+ : QQuickView(engine, nullptr)
24+{
25+ setResizeMode(QQuickView::SizeRootObjectToView);
26+ setColor("black");
27+ setTitle(QStringLiteral("Unity8 Shell - Secondary Screen"));
28+
29+ QUrl source(::qmlDirectory() + "/DisabledScreenNotice.qml");
30+ setSource(source);
31+}
src/SecondaryWindow.hnew file+30
@@ -0,0 +1,30 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef UNITY_SECONDARY_WINDOW_H
18+#define UNITY_SECONDARY_WINDOW_H
19+
20+#include <QQuickView>
21+
22+class SecondaryWindow : public QQuickView
23+{
24+ Q_OBJECT
25+
26+public:
27+ SecondaryWindow(QQmlEngine *engine);
28+};
29+
30+#endif // UNITY_SECONDARY_WINDOW_H
src/ShellApplication.cppnew file+197
@@ -0,0 +1,197 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "ShellApplication.h"
18+
19+// Qt
20+#include <QLibrary>
21+#include <QScreen>
22+
23+#include <libintl.h>
24+
25+// libandroid-properties
26+#include <hybris/properties/properties.h>
27+
28+// local
29+#include <paths.h>
30+#include "CachingNetworkManagerFactory.h"
31+#include "MouseTouchAdaptor.h"
32+#include "UnityCommandLineParser.h"
33+
34+ShellApplication::ShellApplication(int & argc, char ** argv, bool isMirServer)
35+ : QGuiApplication(argc, argv)
36+ , m_shellView(nullptr)
37+ , m_secondaryWindow(nullptr)
38+ , m_mouseTouchAdaptor(nullptr)
39+ , m_qmlEngine(nullptr)
40+{
41+
42+ setApplicationName(QStringLiteral("unity8"));
43+
44+ connect(this, &QGuiApplication::screenAdded, this, &ShellApplication::onScreenAdded);
45+
46+ setupQmlEngine(isMirServer);
47+
48+ UnityCommandLineParser parser(*this);
49+
50+ if (!parser.deviceName().isEmpty()) {
51+ m_deviceName = parser.deviceName();
52+ } else {
53+ char buffer[200];
54+ property_get("ro.product.device", buffer /* value */, "desktop" /* default_value*/);
55+ m_deviceName = QString(buffer);
56+ }
57+ m_qmlArgs.setDeviceName(m_deviceName);
58+
59+ m_qmlArgs.setMode(parser.mode());
60+
61+ // The testability driver is only loaded by QApplication but not by QGuiApplication.
62+ // However, QApplication depends on QWidget which would add some unneeded overhead => Let's load the testability driver on our own.
63+ if (parser.hasTestability() || getenv("QT_LOAD_TESTABILITY")) {
64+ QLibrary testLib(QStringLiteral("qttestability"));
65+ if (testLib.load()) {
66+ typedef void (*TasInitialize)(void);
67+ TasInitialize initFunction = (TasInitialize)testLib.resolve("qt_testability_init");
68+ if (initFunction) {
69+ initFunction();
70+ } else {
71+ qCritical("Library qttestability resolve failed!");
72+ }
73+ } else {
74+ qCritical("Library qttestability load failed!");
75+ }
76+ }
77+
78+ bindtextdomain("unity8", translationDirectory().toUtf8().data());
79+ textdomain("unity8");
80+
81+ m_shellView = new ShellView(m_qmlEngine, &m_qmlArgs);
82+
83+ if (parser.windowGeometry().isValid()) {
84+ m_shellView->setWidth(parser.windowGeometry().width());
85+ m_shellView->setHeight(parser.windowGeometry().height());
86+ }
87+
88+ if (parser.hasFrameless()) {
89+ m_shellView->setFlags(Qt::FramelessWindowHint);
90+ }
91+
92+ // You will need this if you want to interact with touch-only components using a mouse
93+ // Needed only when manually testing on a desktop.
94+ if (parser.hasMouseToTouch()) {
95+ m_mouseTouchAdaptor = MouseTouchAdaptor::instance();
96+ }
97+
98+
99+ // Some hard-coded policy for now.
100+ // NB: We don't support more than two screens at the moment
101+ //
102+ // TODO: Support an arbitrary number of screens and different policies
103+ // (eg cloned desktop, several desktops, etc)
104+ if (isMirServer && screens().count() == 2) {
105+ m_shellView->setScreen(screens().at(1));
106+ m_qmlArgs.setDeviceName("desktop");
107+
108+ m_secondaryWindow = new SecondaryWindow(m_qmlEngine);
109+ m_secondaryWindow->setScreen(screens().at(0));
110+ // QWindow::showFullScreen() also calls QWindow::requestActivate() and we don't want that!
111+ m_secondaryWindow->setWindowState(Qt::WindowFullScreen);
112+ m_secondaryWindow->setVisible(true);
113+ }
114+
115+ if (isMirServer || parser.hasFullscreen()) {
116+ m_shellView->showFullScreen();
117+ } else {
118+ m_shellView->show();
119+ }
120+}
121+
122+ShellApplication::~ShellApplication()
123+{
124+ destroyResources();
125+}
126+
127+void ShellApplication::destroyResources()
128+{
129+ // Deletion order is important. Don't use QScopedPointers and the like
130+ // Otherwise the process will hang on shutdown (bug somewhere I guess).
131+ delete m_shellView;
132+ m_shellView = nullptr;
133+
134+ delete m_secondaryWindow;
135+ m_secondaryWindow = nullptr;
136+
137+ delete m_mouseTouchAdaptor;
138+ m_mouseTouchAdaptor = nullptr;
139+
140+ delete m_qmlEngine;
141+ m_qmlEngine = nullptr;
142+}
143+
144+void ShellApplication::setupQmlEngine(bool isMirServer)
145+{
146+ m_qmlEngine = new QQmlEngine(this);
147+
148+ m_qmlEngine->setBaseUrl(QUrl::fromLocalFile(::qmlDirectory()));
149+
150+ prependImportPaths(m_qmlEngine, ::overrideImportPaths());
151+ if (!isMirServer) {
152+ prependImportPaths(m_qmlEngine, ::nonMirImportPaths());
153+ }
154+ appendImportPaths(m_qmlEngine, ::fallbackImportPaths());
155+
156+ m_qmlEngine->setNetworkAccessManagerFactory(new CachingNetworkManagerFactory);
157+
158+ QObject::connect(m_qmlEngine, &QQmlEngine::quit, this, &QGuiApplication::quit);
159+}
160+
161+void ShellApplication::onScreenAdded(QScreen * /*screen*/)
162+{
163+ // TODO: Support an arbitrary number of screens and different policies
164+ // (eg cloned desktop, several desktops, etc)
165+ if (screens().count() == 2) {
166+ m_shellView->setScreen(screens().at(1));
167+ m_qmlArgs.setDeviceName("desktop");
168+ // Changing the QScreen where a QWindow is drawn makes it also lose focus (besides having
169+ // its backing QPlatformWindow recreated). So lets refocus it.
170+ m_shellView->requestActivate();
171+
172+ m_secondaryWindow = new SecondaryWindow(m_qmlEngine);
173+ m_secondaryWindow->setScreen(screens().at(0));
174+
175+ // QWindow::showFullScreen() also calls QWindow::requestActivate() and we don't want that!
176+ m_secondaryWindow->setWindowState(Qt::WindowFullScreen);
177+ m_secondaryWindow->setVisible(true);
178+ }
179+}
180+
181+void ShellApplication::onScreenAboutToBeRemoved(QScreen *screen)
182+{
183+ // TODO: Support an arbitrary number of screens and different policies
184+ // (eg cloned desktop, several desktops, etc)
185+ if (screen == m_shellView->screen()) {
186+ Q_ASSERT(screens().count() > 1);
187+ Q_ASSERT(screens().at(0) != screen);
188+ Q_ASSERT(m_secondaryWindow);
189+ delete m_secondaryWindow;
190+ m_secondaryWindow = nullptr;
191+ m_shellView->setScreen(screens().first());
192+ m_qmlArgs.setDeviceName(m_deviceName);
193+ // Changing the QScreen where a QWindow is drawn makes it also lose focus (besides having
194+ // its backing QPlatformWindow recreated). So lets refocus it.
195+ m_shellView->requestActivate();
196+ }
197+}
src/ShellApplication.hnew file+55
@@ -0,0 +1,55 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef SHELLAPPLICATION_H
18+#define SHELLAPPLICATION_H
19+
20+#include <QGuiApplication>
21+#include <QQmlEngine>
22+#include <QQuickView>
23+#include <QScopedPointer>
24+
25+#include "ApplicationArguments.h"
26+#include "MouseTouchAdaptor.h"
27+#include "SecondaryWindow.h"
28+#include "ShellView.h"
29+
30+class ShellApplication : public QGuiApplication
31+{
32+ Q_OBJECT
33+public:
34+ ShellApplication(int & argc, char ** argv, bool isMirServer);
35+ virtual ~ShellApplication();
36+
37+ void destroyResources();
38+public Q_SLOTS:
39+ // called by qtmir
40+ void onScreenAboutToBeRemoved(QScreen *screen);
41+
42+private Q_SLOTS:
43+ void onScreenAdded(QScreen*);
44+
45+private:
46+ void setupQmlEngine(bool isMirServer);
47+ QString m_deviceName;
48+ ApplicationArguments m_qmlArgs;
49+ ShellView *m_shellView;
50+ SecondaryWindow *m_secondaryWindow;
51+ MouseTouchAdaptor *m_mouseTouchAdaptor;
52+ QQmlEngine *m_qmlEngine;
53+};
54+
55+#endif // SHELLAPPLICATION_H
src/ShellView.cppnew file+59
@@ -0,0 +1,59 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "ShellView.h"
18+
19+// Qt
20+#include <QQmlContext>
21+#include <QQuickItem>
22+
23+// local
24+#include <paths.h>
25+
26+ShellView::ShellView(QQmlEngine *engine, QObject *qmlArgs)
27+ : QQuickView(engine, nullptr)
28+{
29+ setResizeMode(QQuickView::SizeRootObjectToView);
30+ setColor("black");
31+ setTitle(QStringLiteral("Unity8"));
32+
33+ rootContext()->setContextProperty(QStringLiteral("applicationArguments"), qmlArgs);
34+
35+ QUrl source(::qmlDirectory() + "/OrientedShell.qml");
36+ setSource(source);
37+
38+ connect(this, &QWindow::widthChanged, this, &ShellView::onWidthChanged);
39+ connect(this, &QWindow::heightChanged, this, &ShellView::onHeightChanged);
40+}
41+
42+void ShellView::onWidthChanged(int w)
43+{
44+ // For good measure in case SizeRootObjectToView doesn't fulfill its promise.
45+ //
46+ // There's at least one situation that's know to leave the root object with an outdated size.
47+ // (really looks like Qt bug)
48+ // Happens when starting unity8 with an external monitor already connected.
49+ // The QResizeEvent we get still has the size of the first screen and since the resize move is triggered
50+ // from the resize event handler, the root item doesn't get resized.
51+ // TODO: Confirm the Qt bug and submit a patch upstream
52+ rootObject()->setWidth(w);
53+}
54+
55+void ShellView::onHeightChanged(int h)
56+{
57+ // See comment in ShellView::onWidthChanged()
58+ rootObject()->setHeight(h);
59+}
src/ShellView.hnew file+34
@@ -0,0 +1,34 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef UNITY_SHELL_VIEW_H
18+#define UNITY_SHELL_VIEW_H
19+
20+#include <QQuickView>
21+
22+class ShellView : public QQuickView
23+{
24+ Q_OBJECT
25+
26+public:
27+ ShellView(QQmlEngine *engine, QObject *qmlArgs);
28+
29+private Q_SLOTS:
30+ void onWidthChanged(int);
31+ void onHeightChanged(int);
32+};
33+
34+#endif // UNITY_SHELL_VIEW_H
src/main.cpp+4−101
@@ -14,26 +14,8 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17−// Qt
18−#include <QCommandLineParser>
19−#include <QtQuick/QQuickView>
20−#include <QtGui/QGuiApplication>
21−#include <QtQml/QQmlEngine>
22−#include <QtQml/QQmlContext>
23−#include <QLibrary>
24−#include <QDebug>
25−#include <csignal>
26−#include <libintl.h>
27−
28−// libandroid-properties
29−#include <hybris/properties/properties.h>
30−
3117// local
32−#include <paths.h>
33−#include "MouseTouchAdaptor.h"
34−#include "ApplicationArguments.h"
35−#include "CachingNetworkManagerFactory.h"
36−#include "UnityCommandLineParser.h"
18+#include "ShellApplication.h"
3719
3820int main(int argc, const char *argv[])
3921{
@@ -43,91 +25,12 @@ int main(int argc, const char *argv[])
4325 isMirServer = true;
4426 }
4527
46− QGuiApplication::setApplicationName(QStringLiteral("unity8"));
47− QGuiApplication *application;
48−
49− application = new QGuiApplication(argc, (char**)argv);
50−
51− UnityCommandLineParser parser(*application);
52−
53− ApplicationArguments qmlArgs;
54−
55− if (!parser.deviceName().isEmpty()) {
56− qmlArgs.setDeviceName(parser.deviceName());
57− } else {
58− char buffer[200];
59− property_get("ro.product.device", buffer /* value */, "desktop" /* default_value*/);
60− qmlArgs.setDeviceName(QString(buffer));
61− }
62−
63− qmlArgs.setMode(parser.mode());
64−
65− // The testability driver is only loaded by QApplication but not by QGuiApplication.
66− // However, QApplication depends on QWidget which would add some unneeded overhead => Let's load the testability driver on our own.
67− if (parser.hasTestability() || getenv("QT_LOAD_TESTABILITY")) {
68− QLibrary testLib(QStringLiteral("qttestability"));
69− if (testLib.load()) {
70− typedef void (*TasInitialize)(void);
71− TasInitialize initFunction = (TasInitialize)testLib.resolve("qt_testability_init");
72− if (initFunction) {
73− initFunction();
74− } else {
75− qCritical("Library qttestability resolve failed!");
76− }
77− } else {
78− qCritical("Library qttestability load failed!");
79− }
80− }
81−
82− bindtextdomain("unity8", translationDirectory().toUtf8().data());
83− textdomain("unity8");
84−
85− QQuickView* view = new QQuickView();
86− view->setResizeMode(QQuickView::SizeRootObjectToView);
87− view->setColor("black");
88− view->setTitle(QStringLiteral("Unity8 Shell"));
89−
90− if (parser.windowGeometry().isValid()) {
91− view->setWidth(parser.windowGeometry().width());
92− view->setHeight(parser.windowGeometry().height());
93− }
94−
95− view->engine()->setBaseUrl(QUrl::fromLocalFile(::qmlDirectory()));
96− view->rootContext()->setContextProperty(QStringLiteral("applicationArguments"), &qmlArgs);
97− if (parser.hasFrameless()) {
98− view->setFlags(Qt::FramelessWindowHint);
99− }
100−
101− // You will need this if you want to interact with touch-only components using a mouse
102− // Needed only when manually testing on a desktop.
103− MouseTouchAdaptor *mouseTouchAdaptor = 0;
104− if (parser.hasMouseToTouch()) {
105− mouseTouchAdaptor = MouseTouchAdaptor::instance();
106− }
107−
108− QUrl source(::qmlDirectory() + "/OrientedShell.qml");
109− prependImportPaths(view->engine(), ::overrideImportPaths());
110− if (!isMirServer) {
111− prependImportPaths(view->engine(), ::nonMirImportPaths());
112− }
113− appendImportPaths(view->engine(), ::fallbackImportPaths());
114−
115− CachingNetworkManagerFactory *managerFactory = new CachingNetworkManagerFactory();
116− view->engine()->setNetworkAccessManagerFactory(managerFactory);
117−
118− view->setSource(source);
119− QObject::connect(view->engine(), &QQmlEngine::quit, application, &QGuiApplication::quit);
120−
121− if (isMirServer || parser.hasFullscreen()) {
122− view->showFullScreen();
123− } else {
124− view->show();
125− }
28+ ShellApplication *application = new ShellApplication(argc, (char**)argv, isMirServer);
12629
12730 int result = application->exec();
12831
129− delete view;
130− delete mouseTouchAdaptor;
32+ application->destroyResources();
33+
13134 delete application;
13235
13336 return result;
tests/autopilot/unity8/fixture_setup.py+1−1
@@ -133,7 +133,7 @@ class LaunchUnityWithFakeSensors(fixtures.Fixture):
133133 args = [binary_arg] + env_args
134134 self.unity_proxy = process_helpers.restart_unity_with_testability(
135135 *args)
136− self.main_win = self.unity_proxy.select_single(shell.QQuickView)
136+ self.main_win = self.unity_proxy.select_single(shell.ShellView)
137137
138138 def _create_sensors(self):
139139 # Wait for unity to start running.
tests/autopilot/unity8/greeter/tests/__init__.py+1−1
@@ -24,5 +24,5 @@ from unity8.shell.tests import UnityTestCase
2424class GreeterTestCase(UnityTestCase):
2525 def get_shell(self, unity_proxy):
2626 main_window = (
27− unity_proxy.select_single(shell.QQuickView))
27+ unity_proxy.select_single(shell.ShellView))
2828 return main_window.select_single('Shell')
tests/autopilot/unity8/launcher.py+1−1
@@ -42,7 +42,7 @@ class Launcher(ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
4242 logger.debug('The launcher is already opened.')
4343
4444 def _swipe_to_show_launcher(self):
45− view = self.get_root_instance().select_single('QQuickView')
45+ view = self.get_root_instance().select_single('ShellView')
4646 start_y = stop_y = view.y + view.height // 2
4747
4848 start_x = view.x + 1
tests/autopilot/unity8/shell/__init__.py+1−1
@@ -93,7 +93,7 @@ def _get_urgency(urgency):
9393 return _urgency_enums.get(urgency.upper())
9494
9595
96−class QQuickView(ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
96+class ShellView(ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
9797 """An helper class that makes it easy to interact with the shell"""
9898
9999 def get_greeter(self):
tests/autopilot/unity8/shell/tests/__init__.py+1−1
@@ -297,7 +297,7 @@ class UnityTestCase(AutopilotTestCase):
297297
298298 @property
299299 def main_window(self):
300− return self._proxy.select_single(shell.QQuickView)
300+ return self._proxy.select_single(shell.ShellView)
301301
302302
303303class DashBaseTestCase(AutopilotTestCase):
tests/qmltests/CMakeLists.txt+2
@@ -2,6 +2,7 @@
22add_subdirectory(Components)
33
44add_unity8_qmltest(. OrientedShell LIGHTDM)
5+add_unity8_qmltest(. DisabledScreenNotice)
56add_unity8_qmltest(. Shell LIGHTDM)
67add_unity8_qmltest(. ShellWithPin LIGHTDM)
78add_unity8_qmltest(Components Background)
@@ -15,6 +16,7 @@ add_unity8_qmltest(Components Rating)
1516add_unity8_qmltest(Components ResponsiveGridView)
1617add_unity8_qmltest(Components ResponsiveVerticalJournal)
1718add_unity8_qmltest(Components Showable)
19+add_unity8_qmltest(Components WallpaperResolver)
1820add_unity8_qmltest(Components ZoomableImage)
1921add_unity8_qmltest(Dash Dash)
2022add_unity8_qmltest(Dash DashContent)
tests/qmltests/Components/tst_WallpaperResolver.qmlnew file+89
@@ -0,0 +1,89 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import QtTest 1.0
19+import Ubuntu.Components 1.3
20+import Unity.Test 0.1
21+import AccountsService 0.1
22+import GSettings 1.0
23+
24+import "../../../qml/Components"
25+
26+
27+Image {
28+ width: units.gu(70)
29+ height: units.gu(70)
30+
31+ source: wallpaperResolver.background
32+
33+ WallpaperResolver {
34+ id: wallpaperResolver
35+ width: units.gu(70)
36+ }
37+
38+ UnityTestCase {
39+ id: testCase
40+ name: "WallpaperResolver"
41+ when: windowShown
42+
43+ function test_background_data() {
44+ return [
45+ {tag: "color",
46+ accounts: Qt.resolvedUrl("data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>"),
47+ gsettings: "",
48+ output: Qt.resolvedUrl("data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>")},
49+
50+ {tag: "empty", accounts: "", gsettings: "", output: "defaultBackground"},
51+
52+ {tag: "as-specified",
53+ accounts: Qt.resolvedUrl("../../data/unity/backgrounds/blue.png"),
54+ gsettings: "",
55+ output: Qt.resolvedUrl("../../data/unity/backgrounds/blue.png")},
56+
57+ {tag: "gs-specified",
58+ accounts: "",
59+ gsettings: Qt.resolvedUrl("../../data/unity/backgrounds/red.png"),
60+ output: Qt.resolvedUrl("../../data/unity/backgrounds/red.png")},
61+
62+ {tag: "both-specified",
63+ accounts: Qt.resolvedUrl("../../data/unity/backgrounds/blue.png"),
64+ gsettings: Qt.resolvedUrl("../../data/unity/backgrounds/red.png"),
65+ output: Qt.resolvedUrl("../../data/unity/backgrounds/blue.png")},
66+
67+ {tag: "invalid-as",
68+ accounts: Qt.resolvedUrl("../../data/unity/backgrounds/nope.png"),
69+ gsettings: Qt.resolvedUrl("../../data/unity/backgrounds/red.png"),
70+ output: Qt.resolvedUrl("../../data/unity/backgrounds/red.png")},
71+
72+ {tag: "invalid-both",
73+ accounts: Qt.resolvedUrl("../../data/unity/backgrounds/nope.png"),
74+ gsettings: Qt.resolvedUrl("../../data/unity/backgrounds/stillnope.png"),
75+ output: "defaultBackground"},
76+ ]
77+ }
78+ function test_background(data) {
79+ AccountsService.backgroundFile = data.accounts;
80+ GSettingsController.setPictureUri(data.gsettings);
81+
82+ if (data.output === "defaultBackground") {
83+ tryCompare(wallpaperResolver, "background", wallpaperResolver.defaultBackground);
84+ } else {
85+ tryCompare(wallpaperResolver, "background", data.output);
86+ }
87+ }
88+ }
89+}
tests/qmltests/tst_DisabledScreenNotice.qmlnew file+37
@@ -0,0 +1,37 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import QtTest 1.0
19+import Unity.Test 0.1
20+import "../../qml"
21+
22+
23+Item {
24+ id: root
25+ width: units.gu(70)
26+ height: units.gu(70)
27+
28+ DisabledScreenNotice {
29+ anchors.fill: parent
30+ }
31+
32+ UnityTestCase {
33+ id: testCase
34+ name: "DisabledScreenNotice"
35+ when: windowShown
36+ }
37+}
tests/qmltests/tst_OrientedShell.qml+51−1
@@ -110,6 +110,22 @@ Rectangle {
110110 physicalOrientation180: Qt.LandscapeOrientation
111111 primaryOrientationAngle: 90
112112 }
113+ },
114+ State {
115+ name: "desktop"
116+ PropertyChanges {
117+ target: orientedShellLoader
118+ width: units.gu(100)
119+ height: units.gu(56)
120+ }
121+ PropertyChanges {
122+ target: root
123+ physicalOrientation270: Qt.InvertedPortraitOrientation
124+ physicalOrientation0: Qt.LandscapeOrientation
125+ physicalOrientation90: Qt.PortraitOrientation
126+ physicalOrientation180: Qt.InvertedLandscapeOrientation
127+ primaryOrientationAngle: 0
128+ }
113129 }
114130 ]
115131
@@ -250,7 +266,7 @@ Rectangle {
250266 anchors { left: parent.left; right: parent.right }
251267 activeFocusOnPress: false
252268 text: "Device Name"
253− model: ["mako", "manta", "flo"]
269+ model: ["mako", "manta", "flo", "desktop"]
254270 onSelectedIndexChanged: {
255271 testCase.tearDown();
256272 applicationArguments.deviceName = model[selectedIndex];
@@ -310,12 +326,14 @@ Rectangle {
310326 Row {
311327 Button {
312328 text: "Add mouse"
329+ activeFocusOnPress: false
313330 onClicked: {
314331 UnityInputInfo.inputInfo.addMockMouse()
315332 }
316333 }
317334 Button {
318335 text: "Remove mouse"
336+ activeFocusOnPress: false
319337 onClicked: {
320338 UnityInputInfo.inputInfo.removeMockMouse()
321339 }
@@ -324,17 +342,37 @@ Rectangle {
324342 Row {
325343 Button {
326344 text: "Add kbd"
345+ activeFocusOnPress: false
327346 onClicked: {
328347 UnityInputInfo.inputInfo.addMockKeyboard()
329348 }
330349 }
331350 Button {
351+ activeFocusOnPress: false
332352 text: "Remove kbd"
333353 onClicked: {
334354 UnityInputInfo.inputInfo.removeMockKeyboard()
335355 }
336356 }
337357 }
358+
359+ // Simulates what happens when the shell is moved to an external monitor and back
360+ Button {
361+ id: moveToFromMonitorButton
362+ text: applicationArguments.deviceName === "desktop" ? "Move to " + prevDevName + " screen" : "Move to desktop screen"
363+ activeFocusOnPress: false
364+ property string prevDevName: "mako"
365+ onClicked: {
366+ usageModeSelector.selectedIndex = 2; // "Automatic"
367+
368+ if (applicationArguments.deviceName === "desktop") {
369+ applicationArguments.deviceName = prevDevName;
370+ } else {
371+ prevDevName = applicationArguments.deviceName;
372+ applicationArguments.deviceName = "desktop"
373+ }
374+ }
375+ }
338376 }
339377 }
340378
@@ -1054,6 +1092,18 @@ Rectangle {
10541092 compare(signalSpy.count, 0);
10551093 }
10561094
1095+ function test_moveToExternalMonitor() {
1096+ loadShell("flo");
1097+
1098+ compare(orientedShell.orientation, Qt.InvertedLandscapeOrientation);
1099+ compare(shell.transformRotationAngle, 90);
1100+
1101+ moveToFromMonitorButton.clicked();
1102+
1103+ tryCompare(orientedShell, "orientation", Qt.LandscapeOrientation);
1104+ tryCompare(shell, "transformRotationAngle" , 0);
1105+ }
1106+
10571107 // angle - rotation angle in degrees clockwise, relative to the primary orientation.
10581108 function rotateTo(angle) {
10591109 switch (angle) {
tests/qmltests/tst_Shell.qml−24
@@ -1240,30 +1240,6 @@ Rectangle {
12401240 tryCompare(launcherPanel, "x", -launcherPanel.width);
12411241 }
12421242
1243− function test_background_data() {
1244− return [
1245− {tag: "color", accounts: Qt.resolvedUrl("data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>"), gsettings: "", output: Qt.resolvedUrl("data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>")},
1246− {tag: "empty", accounts: "", gsettings: "", output: "defaultBackground"},
1247− {tag: "as-specified", accounts: Qt.resolvedUrl("../data/unity/backgrounds/blue.png"), gsettings: "", output: Qt.resolvedUrl("../data/unity/backgrounds/blue.png")},
1248− {tag: "gs-specified", accounts: "", gsettings: Qt.resolvedUrl("../data/unity/backgrounds/red.png"), output: Qt.resolvedUrl("../data/unity/backgrounds/red.png")},
1249− {tag: "both-specified", accounts: Qt.resolvedUrl("../data/unity/backgrounds/blue.png"), gsettings: Qt.resolvedUrl("../data/unity/backgrounds/red.png"), output: Qt.resolvedUrl("../data/unity/backgrounds/blue.png")},
1250− {tag: "invalid-as", accounts: Qt.resolvedUrl("../data/unity/backgrounds/nope.png"), gsettings: Qt.resolvedUrl("../data/unity/backgrounds/red.png"), output: Qt.resolvedUrl("../data/unity/backgrounds/red.png")},
1251− {tag: "invalid-both", accounts: Qt.resolvedUrl("../data/unity/backgrounds/nope.png"), gsettings: Qt.resolvedUrl("../data/unity/backgrounds/stillnope.png"), output: "defaultBackground"},
1252− ]
1253− }
1254− function test_background(data) {
1255− loadShell("phone");
1256− swipeAwayGreeter();
1257− AccountsService.backgroundFile = data.accounts;
1258− GSettingsController.setPictureUri(data.gsettings);
1259−
1260− if (data.output === "defaultBackground") {
1261− tryCompare(shell, "background", shell.defaultBackground);
1262− } else {
1263− tryCompare(shell, "background", data.output);
1264− }
1265− }
1266−
12671243 function test_tabletLogin_data() {
12681244 return [
12691245 {tag: "auth error", user: "auth-error", loggedIn: false, password: ""},