on xenial help sign in
Use the default system wallpaper instead of our custom one.

Additionally, crop the default wallpaper instead of rotating it, only darken the wallpaper if it's not the default one, and avoid reloading the wallpaper when the device is rotated.

commit4bd37d640c46d45038d8e4cd4eb44a8b4121ce24 authorMichael Terry <michael.terry@canonical.com> dateTue 13 Sep 2016, 06:54 (10 years ago) parent859496554614d3
38 files changed+800−120
debian/control+1 debian/unity8-private.install+1 plugins/CMakeLists.txt+1 plugins/ImageCache/CMakeLists.txt+8 plugins/ImageCache/ImageCache.cpp+135 plugins/ImageCache/ImageCache.h+53 plugins/ImageCache/plugin.cpp+39 plugins/ImageCache/plugin.h+31 plugins/ImageCache/qmldir+2 qml/Components/Lockscreen.qml+1−5 qml/Components/Wallpaper.qml+44 qml/Components/WallpaperResolver.qml+24−38 qml/Dash/DashBackground.qml+2−2 qml/Greeter/CoverPage.qml+5−5 qml/Greeter/Greeter.qml+7 qml/Greeter/NarrowView.qml+5−6 qml/Greeter/WideView.qml+1 qml/Shell.qml+27−4 qml/Stages/DesktopStage.qml+1−3 qml/graphics/phone_background.jpg qml/graphics/tablet_background.jpg tests/plugins/CMakeLists.txt+1 tests/plugins/ImageCache/CMakeLists.txt+20 tests/plugins/ImageCache/graphics/wide.jpg tests/plugins/ImageCache/test.cpp+214 tests/plugins/ImageCache/test.qml+23 tests/qmltests/Components/tst_Lockscreen.qml+1−1 tests/qmltests/Components/tst_WallpaperResolver.qml+21−35 tests/qmltests/Components/tst_ZoomableImage.qml+1−1 tests/qmltests/Dash/Previews/tst_PreviewExpandable.qml+3−3 tests/qmltests/Dash/Previews/tst_PreviewImageGallery.qml+6−6 tests/qmltests/Dash/Previews/tst_PreviewZoomableImage.qml+7−7 tests/qmltests/Greeter/TestView.qml+1 tests/qmltests/Greeter/tst_Greeter.qml+7−1 tests/qmltests/Greeter/tst_NarrowView.qml+13−1 tests/qmltests/Greeter/tst_WideView.qml+12−1 tests/qmltests/Stages/tst_DesktopStage.qml+1−1 tests/qmltests/tst_Shell.qml+81
debian/control+1
@@ -154,6 +154,7 @@ Depends: qml-module-qtquick-layouts,
154154 qtdeclarative5-ubuntu-settings-components (>= 0.7),
155155 qtdeclarative5-unity-notifications-plugin (>= 0.1.2) | unity-notifications-impl,
156156 ubuntu-thumbnailer-impl-0,
157+ ubuntu-wallpapers,
157158 unity-application-impl-21,
158159 unity-notifications-impl-3,
159160 unity-plugin-scopes | unity-scopes-impl,
debian/unity8-private.install+1
@@ -4,6 +4,7 @@ usr/lib/*/unity8/qml/Cursor
44usr/lib/*/unity8/qml/Dash
55usr/lib/*/unity8/qml/GlobalShortcut
66usr/lib/*/unity8/qml/Greeter
7+usr/lib/*/unity8/qml/ImageCache
78usr/lib/*/unity8/qml/LightDM
89usr/lib/*/unity8/qml/Lights
910usr/lib/*/unity8/qml/Powerd
plugins/CMakeLists.txt+1
@@ -15,6 +15,7 @@ add_subdirectory(AccountsService)
1515add_subdirectory(Cursor)
1616add_subdirectory(GlobalShortcut)
1717add_subdirectory(Greeter)
18+add_subdirectory(ImageCache)
1819add_subdirectory(LightDM)
1920add_subdirectory(Lights)
2021add_subdirectory(Dash)
plugins/ImageCache/CMakeLists.txtnew file+8
@@ -0,0 +1,8 @@
1+add_library(ImageCache-qml MODULE
2+ ImageCache.cpp
3+ plugin.cpp
4+ )
5+
6+qt5_use_modules(ImageCache-qml Gui Qml Quick)
7+
8+add_unity8_plugin(ImageCache 0.1 ImageCache TARGETS ImageCache-qml)
plugins/ImageCache/ImageCache.cppnew file+135
@@ -0,0 +1,135 @@
1+/*
2+ * Copyright (C) 2016 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 <QDateTime>
18+#include <QDebug>
19+#include <QDir>
20+#include <QUrl>
21+#include <QUrlQuery>
22+
23+#include "ImageCache.h"
24+
25+ImageCache::ImageCache()
26+ : QQuickImageProvider(QQmlImageProviderBase::Image,
27+ QQmlImageProviderBase::ForceAsynchronousImageLoading)
28+{
29+}
30+
31+QString ImageCache::imageCacheRoot()
32+{
33+ QString xdgCache(qgetenv("XDG_CACHE_HOME"));
34+ if (xdgCache.isEmpty()) {
35+ xdgCache = QDir::homePath() + QStringLiteral("/.cache");
36+ }
37+
38+ return QDir::cleanPath(xdgCache) + QStringLiteral("/unity8/imagecache");
39+}
40+
41+QFileInfo ImageCache::imagePath(const QUrl &image)
42+{
43+ QUrlQuery query(image);
44+
45+ auto name = query.queryItemValue(QStringLiteral("name"));
46+ if (name.isEmpty()) {
47+ name = QStringLiteral("/paths") + image.toLocalFile();
48+ } else {
49+ name = QStringLiteral("/names/") + name;
50+ }
51+
52+ return QFileInfo(imageCacheRoot() + name);
53+}
54+
55+bool ImageCache::needsUpdate(const QUrl &image, const QFileInfo &cachePath, const QSize &imageSize, const QSize &requestedSize, QSize &finalSize)
56+{
57+ if (!cachePath.exists())
58+ return true;
59+
60+ QFileInfo imageInfo(image.toLocalFile());
61+ if (imageInfo.lastModified() > cachePath.lastModified())
62+ return true;
63+
64+ QSize cacheSize(QImageReader(cachePath.filePath()).size());
65+ finalSize = calculateSize(imageSize, requestedSize);
66+ if (finalSize.isValid() && cacheSize != finalSize)
67+ return true;
68+
69+ return false;
70+}
71+
72+QSize ImageCache::calculateSize(const QSize &imageSize, const QSize &requestedSize)
73+{
74+ QSize finalSize(requestedSize);
75+
76+ if (finalSize.width() == 0) {
77+ finalSize.setWidth(imageSize.width() * (((double)finalSize.height()) / imageSize.height()));
78+ } else if (finalSize.height() == 0) {
79+ finalSize.setHeight(imageSize.height() * (((double)finalSize.width()) / imageSize.width()));
80+ }
81+
82+ return finalSize;
83+}
84+
85+QImage ImageCache::loadAndCacheImage(QImageReader &reader, const QFileInfo &cachePath, const QSize &finalSize)
86+{
87+ reader.setQuality(100);
88+ reader.setScaledSize(finalSize);
89+ auto format = reader.format(); // can't get this after reading
90+
91+ QImage loadedImage(reader.read());
92+ if (loadedImage.isNull()) {
93+ qWarning() << "ImageCache could not read image" << reader.fileName() << ":" << reader.errorString();
94+ return QImage();
95+ }
96+
97+ cachePath.dir().mkpath(QStringLiteral("."));
98+ loadedImage.save(cachePath.filePath(), format, 100);
99+
100+ return loadedImage;
101+}
102+
103+QImage ImageCache::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
104+{
105+ QUrl image(id);
106+ QImageReader imageReader(image.toLocalFile());
107+ QSize imageSize(imageReader.size());
108+ QImage result;
109+
110+ // Early exit here, with no sourceSize, scaled-up sourceSize, or bad source image
111+ if ((requestedSize.width() <= 0 && requestedSize.height() <= 0) ||
112+ imageSize.isEmpty() ||
113+ requestedSize.height() >= imageSize.height() ||
114+ requestedSize.width() >= imageSize.width()) {
115+ // We're only interested in scaling down, not up.
116+ result = imageReader.read();
117+ *size = result.size();
118+ return result;
119+ }
120+
121+ auto cachePath = imagePath(image);
122+ QSize finalSize;
123+
124+ if (needsUpdate(image, cachePath, imageSize, requestedSize, finalSize)) {
125+ if (finalSize.isEmpty()) {
126+ finalSize = calculateSize(imageSize, requestedSize);
127+ }
128+ result = loadAndCacheImage(imageReader, cachePath, finalSize);
129+ } else {
130+ result = QImage(cachePath.filePath());
131+ }
132+
133+ *size = result.size();
134+ return result;
135+}
plugins/ImageCache/ImageCache.hnew file+53
@@ -0,0 +1,53 @@
1+/*
2+ * Copyright (C) 2016 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+#pragma once
18+
19+#include <QFileInfo>
20+#include <QImageReader>
21+#include <QQuickImageProvider>
22+#include <QSize>
23+
24+/**
25+ * This class accepts an id formulated like a URL. So you'd use something like:
26+ *
27+ * Image {
28+ * source: "image://unity8imagecache/file:///usr/share/..."
29+ * }
30+ *
31+ * Right now, we only support file:/// URLs. We do accept some flags though:
32+ *
33+ * ?name=NAME
34+ * - This will use NAME as the cache lookup key instead of the provided URL.
35+ *
36+ * We don't do any cleaning of old cached files yet. So you may want to always
37+ * provide a name to avoid leaving lots of files around.
38+ */
39+
40+class ImageCache: public QQuickImageProvider
41+{
42+public:
43+ ImageCache();
44+
45+ QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override;
46+
47+private:
48+ static QString imageCacheRoot();
49+ static QFileInfo imagePath(const QUrl &image);
50+ static bool needsUpdate(const QUrl &image, const QFileInfo &cachePath, const QSize &imageSize, const QSize &requestedSize, QSize &finalSize);
51+ static QSize calculateSize(const QSize &imageSize, const QSize &requestedSize);
52+ static QImage loadAndCacheImage(QImageReader &reader, const QFileInfo &cachePath, const QSize &finalSize);
53+};
plugins/ImageCache/plugin.cppnew file+39
@@ -0,0 +1,39 @@
1+/*
2+ * Copyright (C) 2016 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 <QQmlEngine>
18+
19+#include "ImageCache.h"
20+#include "plugin.h"
21+
22+#include <QtQml>
23+
24+void ImageCachePlugin::registerTypes(const char *uri)
25+{
26+ Q_ASSERT(uri == QLatin1String("ImageCache"));
27+
28+ qmlRegisterTypeNotAvailable(uri, 0, 1, "__ImageCacheIgnoreMe",
29+ QStringLiteral("Ignore this: QML plugins must contain at least one type"));
30+}
31+
32+void ImageCachePlugin::initializeEngine(QQmlEngine* engine, const char* uri)
33+{
34+ Q_ASSERT(uri == QLatin1String("ImageCache"));
35+
36+ QQmlExtensionPlugin::initializeEngine(engine, uri);
37+
38+ engine->addImageProvider("unity8imagecache", new ImageCache);
39+}
plugins/ImageCache/plugin.hnew file+31
@@ -0,0 +1,31 @@
1+/*
2+ * Copyright (C) 2016 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+#pragma once
18+
19+#include <QQmlExtensionPlugin>
20+
21+class QQmlEngine;
22+
23+class ImageCachePlugin : public QQmlExtensionPlugin
24+{
25+ Q_OBJECT
26+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
27+
28+public:
29+ void registerTypes(const char* uri) override;
30+ void initializeEngine(QQmlEngine* engine, const char* uri) override;
31+};
plugins/ImageCache/qmldirnew file+2
@@ -0,0 +1,2 @@
1+module ImageCache
2+plugin ImageCache-qml
qml/Components/Lockscreen.qml+1−5
@@ -96,17 +96,13 @@ Showable {
9696 visible: root.background.toString() !== ""
9797 }
9898
99− Image {
99+ Wallpaper {
100100 id: backgroundImage
101101 objectName: "lockscreenBackground"
102102 anchors {
103103 fill: parent
104104 }
105− // Limit how much memory we'll reserve for this image
106− sourceSize.height: height
107− sourceSize.width: width
108105 source: root.required ? root.background : ""
109− fillMode: Image.PreserveAspectCrop
110106 }
111107
112108 // This is to
qml/Components/Wallpaper.qmlnew file+44
@@ -0,0 +1,44 @@
1+/*
2+ * Copyright (C) 2013-2016 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 QtQuick.Window 2.2
19+import Ubuntu.Components 1.3
20+
21+Item {
22+ id: root
23+ property url source
24+
25+ CrossFadeImage {
26+ anchors.fill: parent
27+ fillMode: Image.PreserveAspectCrop
28+
29+ // Limit how much memory we reserve and avoid reloading when item size
30+ // changes or is rotated by specifying sourceSize.
31+ //
32+ // FIXME: If the source image has a portrait aspect ratio, we should swap
33+ // sourceSize.width and sourceSize.height to prevent blurriness from double
34+ // scaling. We could easily do that with a tiny image loader to check
35+ // the aspect ratio first, but when we change sourceSize, we lose all
36+ // the benefits of CrossFadeImage. So we need to fix that component
37+ // first to gracefully handle sourceSize changes (LP: #1599203).
38+ readonly property int maxSize: Math.max(Screen.width, Screen.height)
39+ sourceSize.width: 0
40+ sourceSize.height: maxSize
41+
42+ source: root.source
43+ }
44+}
qml/Components/WallpaperResolver.qml+24−38
@@ -15,49 +15,35 @@
1515 */
1616
1717import QtQuick 2.4
18−import AccountsService 0.1
19−import GSettings 1.0
2018import Ubuntu.Components 1.3
2119
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")
20+Item {
21+ id: root
3422
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− }
23+ // Provide a list of wallpapers to resolve here, preferred ones first
24+ property var candidates: []
4825
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
26+ readonly property url background: {
27+ for (var i = 0; i < repeater.count; i++) {
28+ if (repeater.itemAt(i).status === Image.Ready)
29+ return candidates[i];
30+ }
31+ if (i > 0) {
32+ return candidates[i - 1]; // last item is last resort
33+ } else {
34+ return "";
35+ }
5736 }
5837
59− property var _gsettings: GSettings {
60− id: backgroundSettings
61− schema.id: "org.gnome.desktop.background"
38+ Repeater {
39+ id: repeater
40+ model: root.candidates
41+ delegate: Image {
42+ source: modelData
43+ height: 0
44+ width: 0
45+ sourceSize.height: 0
46+ sourceSize.width: 0
47+ }
6248 }
6349}
qml/Dash/DashBackground.qml+2−2
@@ -16,10 +16,10 @@
1616
1717import QtQuick 2.4
1818import QtQuick.Window 2.2
19−import Ubuntu.Thumbnailer 0.1
19+import ImageCache 0.1
2020
2121Image {
22− source: "image://thumbnailer/" + Qt.resolvedUrl("graphics/paper.png")
22+ source: "image://unity8imagecache/" + Qt.resolvedUrl("graphics/paper.png")
2323 fillMode: Image.PreserveAspectCrop
2424 horizontalAlignment: Image.AlignRight
2525 verticalAlignment: Image.AlignTop
qml/Greeter/CoverPage.qml+5−5
@@ -25,6 +25,7 @@ Showable {
2525 property real dragHandleLeftMargin
2626 property real launcherOffset
2727 property alias background: greeterBackground.source
28+ property alias hasCustomBackground: backgroundShade.visible
2829 property real backgroundTopMargin
2930 property var infographicModel
3031 property bool draggable: true
@@ -82,24 +83,23 @@ Showable {
8283 color: "black"
8384 }
8485
85− CrossFadeImage {
86+ Wallpaper {
8687 id: greeterBackground
8788 objectName: "greeterBackground"
8889 anchors {
8990 fill: parent
9091 topMargin: root.backgroundTopMargin
9192 }
92− fillMode: Image.PreserveAspectCrop
93− // Limit how much memory we'll reserve for this image
94− sourceSize.height: height
95− sourceSize.width: width
9693 }
9794
9895 // Darkens wallpaper so that we can read text on it and see infographic
9996 Rectangle {
97+ id: backgroundShade
98+ objectName: "backgroundShade"
10099 anchors.fill: parent
101100 color: "black"
102101 opacity: 0.4
102+ visible: false
103103 }
104104
105105 Infographics {
qml/Greeter/Greeter.qml+7
@@ -34,6 +34,7 @@ Showable {
3434 property real dragHandleLeftMargin: 0
3535
3636 property url background
37+ property bool hasCustomBackground
3738
3839 // How far to offset the top greeter layer during a launcher left-drag
3940 property real launcherOffset
@@ -437,6 +438,12 @@ Showable {
437438 value: root.background
438439 }
439440
441+ Binding {
442+ target: loader.item
443+ property: "hasCustomBackground"
444+ value: root.hasCustomBackground
445+ }
446+
440447 Binding {
441448 target: loader.item
442449 property: "locked"
qml/Greeter/NarrowView.qml+5−6
@@ -29,6 +29,7 @@ FocusScope {
2929 property alias delayMinutes: delayedLockscreen.delayMinutes
3030 property alias backgroundTopMargin: coverPage.backgroundTopMargin
3131 property url background
32+ property bool hasCustomBackground
3233 property bool locked
3334 property alias alphanumeric: loginList.alphanumeric
3435 property alias userModel: loginList.model
@@ -123,7 +124,7 @@ FocusScope {
123124 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
124125 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
125126
126− CrossFadeImage {
127+ Wallpaper {
127128 id: lockscreenBackground
128129 objectName: "lockscreenBackground"
129130 anchors {
@@ -131,17 +132,14 @@ FocusScope {
131132 topMargin: root.backgroundTopMargin
132133 }
133134 source: root.background
134− fillMode: Image.PreserveAspectCrop
135− // Limit how much memory we'll reserve for this image
136− sourceSize.height: Math.max(Screen.width, Screen.height)
137− sourceSize.width: 0
138135 }
139136
140137 // Darken background to match CoverPage
141138 Rectangle {
139+ objectName: "lockscreenShade"
142140 anchors.fill: parent
143141 color: "black"
144− opacity: 0.4
142+ opacity: root.hasCustomBackground ? 0.4 : 0
145143 }
146144
147145 LoginList {
@@ -191,6 +189,7 @@ FocusScope {
191189 height: parent.height
192190 width: parent.width
193191 background: root.background
192+ hasCustomBackground: root.hasCustomBackground
194193 draggable: !root.waiting
195194 onTease: root.tease()
196195 onClicked: hide()
qml/Greeter/WideView.qml+1
@@ -24,6 +24,7 @@ FocusScope {
2424
2525 property alias background: coverPage.background
2626 property alias backgroundTopMargin: coverPage.backgroundTopMargin
27+ property alias hasCustomBackground: coverPage.hasCustomBackground
2728 property alias dragHandleLeftMargin: coverPage.dragHandleLeftMargin
2829 property alias infographicModel: coverPage.infographicModel
2930 property alias launcherOffset: coverPage.launcherOffset
qml/Shell.qml+27−4
@@ -26,6 +26,7 @@ import Unity.Connectivity 0.1
2626import Unity.Launcher 0.1
2727import GlobalShortcut 1.0 // has to be before Utils, because of WindowInputFilter
2828import GSettings 1.0
29+import ImageCache 0.1
2930import Utils 0.1
3031import Powerd 0.1
3132import SessionBroadcast 0.1
@@ -145,7 +146,28 @@ StyledItem {
145146
146147 WallpaperResolver {
147148 id: wallpaperResolver
148− width: shell.width
149+ objectName: "wallpaperResolver"
150+
151+ readonly property url defaultBackground: "file:///usr/share/backgrounds/warty-final-ubuntu.png"
152+ readonly property bool hasCustomBackground: background != defaultBackground
153+
154+ // Use a cached version of the scaled-down wallpaper (as sometimes the
155+ // image can be quite big compared to the device size, including for
156+ // our default wallpaper). We use a name=wallpaper argument here to
157+ // make sure we don't litter our cache with lots of scaled images. We
158+ // only need to bother caching one at a time.
159+ readonly property url cachedBackground: background.toString().indexOf("file:///") === 0 ? "image://unity8imagecache/" + background + "?name=wallpaper" : background
160+
161+ GSettings {
162+ id: backgroundSettings
163+ schema.id: "org.gnome.desktop.background"
164+ }
165+
166+ candidates: [
167+ AccountsService.backgroundFile,
168+ backgroundSettings.pictureUri,
169+ defaultBackground
170+ ]
149171 }
150172
151173 readonly property alias greeter: greeterLoader.item
@@ -352,7 +374,7 @@ StyledItem {
352374 Binding {
353375 target: applicationsDisplayLoader.item
354376 property: "background"
355− value: wallpaperResolver.background
377+ value: wallpaperResolver.cachedBackground
356378 }
357379 Binding {
358380 target: applicationsDisplayLoader.item
@@ -438,7 +460,8 @@ StyledItem {
438460 tabletMode: shell.usageScenario != "phone"
439461 launcherOffset: launcher.progress
440462 forcedUnlock: wizard.active || shell.mode === "full-shell"
441− background: wallpaperResolver.background
463+ background: wallpaperResolver.cachedBackground
464+ hasCustomBackground: wallpaperResolver.hasCustomBackground
442465 allowFingerprint: !dialogs.hasActiveDialog &&
443466 !notifications.topmostIsFullscreen &&
444467 !panel.indicators.shown
@@ -719,7 +742,7 @@ StyledItem {
719742 model: NotificationBackend.Model
720743 margin: units.gu(1)
721744 hasMouse: shell.hasMouse
722− background: wallpaperResolver.background
745+ background: wallpaperResolver.cachedBackground
723746
724747 y: topmostIsFullscreen ? 0 : panel.panelHeight
725748 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
qml/Stages/DesktopStage.qml+1−3
@@ -240,12 +240,10 @@ AbstractStage {
240240 anchors.fill: parent
241241 focus: spread.state !== "altTab"
242242
243− CrossFadeImage {
243+ Wallpaper {
244244 id: wallpaper
245245 anchors.fill: parent
246246 source: root.background
247− sourceSize { height: root.height; width: root.width }
248− fillMode: Image.PreserveAspectCrop
249247 }
250248
251249 TopLevelSurfaceRepeater {
qml/graphics/phone_background.jpgdeleted

Binary file not shown.

qml/graphics/tablet_background.jpgdeleted

Binary file not shown.

tests/plugins/CMakeLists.txt+1
@@ -3,6 +3,7 @@ add_subdirectory(Cursor)
33add_subdirectory(Dash)
44add_subdirectory(GlobalShortcut)
55add_subdirectory(Greeter)
6+add_subdirectory(ImageCache)
67add_subdirectory(LightDM)
78add_subdirectory(SessionBroadcast)
89add_subdirectory(Ubuntu)
tests/plugins/ImageCache/CMakeLists.txtnew file+20
@@ -0,0 +1,20 @@
1+include_directories(
2+ ${CMAKE_CURRENT_BINARY_DIR}
3+ )
4+
5+include_directories(
6+ SYSTEM
7+ ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
8+ )
9+
10+add_definitions(-DCURRENT_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
11+remove_definitions(-DQT_NO_KEYWORDS)
12+
13+add_executable(ImageCacheTestExec
14+ test.cpp
15+ )
16+qt5_use_modules(ImageCacheTestExec Core Quick Test)
17+add_unity8_uitest(ImageCache ImageCacheTestExec
18+ DEPENDS ImageCache-qml
19+ IMPORT_PATHS ${UNITY_PLUGINPATH}
20+ )
tests/plugins/ImageCache/graphics/wide.jpgnew file

Binary file not shown.

tests/plugins/ImageCache/test.cppnew file+214
@@ -0,0 +1,214 @@
1+/*
2+ * Copyright (C) 2016 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 <QCoreApplication>
18+#include <QFileInfo>
19+#include <QImageReader>
20+#include <QQuickView>
21+#include <QTemporaryDir>
22+#include <QtTestGui>
23+#include <private/qquickimage_p.h>
24+#include <time.h>
25+#include <utime.h>
26+
27+Q_DECLARE_METATYPE(QQuickImage::Status)
28+
29+class ImageCacheTest : public QObject
30+{
31+ Q_OBJECT
32+
33+private:
34+
35+ void setUpImage(const QString &name, const QSize &sourceSize = QSize())
36+ {
37+ QString url = "image://unity8imagecache/file://" + sourceFile(name);
38+ image->setProperty("sourceSize", sourceSize);
39+ image->setProperty("source", url);
40+ }
41+
42+ void waitForImage(QQuickImage::Status status = QQuickImage::Ready)
43+ {
44+ QTRY_COMPARE(image->property("status"), QVariant((int)status));
45+ }
46+
47+ QSize cachedImageSize(const QString &filename)
48+ {
49+ QImageReader reader(filename);
50+ return reader.size();
51+ }
52+
53+ QString sourceFile(const QString &name)
54+ {
55+ return CURRENT_SOURCE_DIR "/graphics/" + name;
56+ }
57+
58+ QString cachedFile(bool isPath, const QString &name)
59+ {
60+ if (isPath)
61+ return home->path() + "/.cache/unity8/imagecache/paths" + sourceFile(name);
62+ else
63+ return home->path() + "/.cache/unity8/imagecache/names/" + name;
64+ }
65+
66+ void createCachedImage(const QString &name, const QString &cachePath, const QSize &cacheSize, time_t mtime_in = 0)
67+ {
68+ QString origPath = sourceFile(name);
69+ QImageReader origImage(origPath);
70+ origImage.setScaledSize(cacheSize);
71+ auto format = origImage.format(); // can't get this after reading
72+
73+ QImage cacheImage(origImage.read());
74+ QVERIFY(!cacheImage.isNull());
75+
76+ QFileInfo(cachePath).dir().mkpath(QStringLiteral("."));
77+ QVERIFY(cacheImage.save(cachePath, format, 100));
78+
79+ // Make file a few seconds old, so we can more easily tell if it gets updated
80+ auto mtime = mtime_in == 0 ? QFileInfo(cachePath).lastModified().toTime_t() - 5 : mtime_in;
81+ struct utimbuf timebuffer;
82+ timebuffer.modtime = mtime;
83+ timebuffer.actime = timebuffer.modtime;
84+ QCOMPARE(utime(cachePath.toUtf8().data(), &timebuffer), 0);
85+ }
86+
87+private Q_SLOTS:
88+
89+ void init()
90+ {
91+ home = new QTemporaryDir();
92+ QVERIFY(home->isValid());
93+ qputenv("HOME", home->path().toUtf8());
94+
95+ view = new QQuickView();
96+ view->setSource(QUrl::fromLocalFile(CURRENT_SOURCE_DIR "/test.qml"));
97+ image = view->rootObject();
98+
99+ view->show();
100+ QTest::qWaitForWindowExposed(view);
101+
102+ waitForImage(QQuickImage::Null);
103+ }
104+
105+ void cleanup()
106+ {
107+ delete view;
108+ delete home;
109+ }
110+
111+ void testFileNotFound()
112+ {
113+ setUpImage("NOTHERE");
114+ waitForImage(QQuickImage::Error);
115+ QVERIFY(!QFile::exists(cachedFile(true, "NOTHERE")));
116+ }
117+
118+ void testNoSourceSize()
119+ {
120+ setUpImage("wide.jpg");
121+ waitForImage();
122+ QVERIFY(!QFile::exists(cachedFile(true, "wide.jpg")));
123+ }
124+
125+ void testNoScalingUp()
126+ {
127+ setUpImage("wide.jpg", QSize(1000, 1000));
128+ waitForImage();
129+ QVERIFY(!QFile::exists(cachedFile(true, "wide.jpg")));
130+ }
131+
132+ void testFullSourceSize()
133+ {
134+ setUpImage("wide.jpg", QSize(100, 100));
135+ waitForImage();
136+ QCOMPARE(cachedImageSize(cachedFile(true, "wide.jpg")), QSize(100, 100));
137+ }
138+
139+ void testWidthSourceSize()
140+ {
141+ setUpImage("wide.jpg", QSize(100, 0));
142+ waitForImage();
143+ // wide.jpg is 500x200
144+ QCOMPARE(cachedImageSize(cachedFile(true, "wide.jpg")), QSize(100, 40));
145+ }
146+
147+ void testHeightSourceSize()
148+ {
149+ setUpImage("wide.jpg", QSize(0, 100));
150+ waitForImage();
151+ // wide.jpg is 500x200
152+ QCOMPARE(cachedImageSize(cachedFile(true, "wide.jpg")), QSize(250, 100));
153+ }
154+
155+ void testNameArg()
156+ {
157+ setUpImage("wide.jpg?name=foo", QSize(0, 100));
158+ waitForImage();
159+ QVERIFY(!QFile::exists(cachedFile(true, ""))); // check for dir itself
160+ QVERIFY(QFile::exists(cachedFile(false, "foo")));
161+ }
162+
163+ void testLoadCache()
164+ {
165+ auto cacheName = cachedFile(false, "foo");
166+ createCachedImage("wide.jpg", cacheName, QSize(250, 100));
167+ auto mtime = QFileInfo(cacheName).lastModified();
168+
169+ auto now = time(NULL);
170+ QVERIFY(QFileInfo(cacheName).lastModified().toTime_t() < now); // sanity check
171+
172+ setUpImage("wide.jpg?name=foo", QSize(0, 100));
173+ waitForImage();
174+ QCOMPARE(QFileInfo(cacheName).lastModified(), mtime); // wasn't recreated
175+ }
176+
177+ void testDifferentSize()
178+ {
179+ auto cacheName = cachedFile(false, "foo");
180+ createCachedImage("wide.jpg", cacheName, QSize(250, 100));
181+
182+ auto now = time(NULL);
183+ QVERIFY(QFileInfo(cacheName).lastModified().toTime_t() < now); // sanity check
184+
185+ setUpImage("wide.jpg?name=foo", QSize(100, 0));
186+ waitForImage();
187+ QVERIFY(QFileInfo(cacheName).lastModified().toTime_t() >= now); // was recreated
188+ }
189+
190+ void testStaleCache()
191+ {
192+ auto sourceName = sourceFile("wide.jpg");
193+ auto mtime = QFileInfo(sourceName).lastModified().toTime_t() - 5;
194+
195+ auto cacheName = cachedFile(false, "foo");
196+ createCachedImage("wide.jpg", cacheName, QSize(250, 100), mtime);
197+
198+ auto now = time(NULL);
199+ QVERIFY(QFileInfo(cacheName).lastModified().toTime_t() < now); // sanity check
200+
201+ setUpImage("wide.jpg?name=foo", QSize(0, 100));
202+ waitForImage();
203+ QVERIFY(QFileInfo(cacheName).lastModified().toTime_t() >= now); // was recreated
204+ }
205+
206+private:
207+ QQuickView *view;
208+ QObject *image;
209+ QTemporaryDir *home;
210+};
211+
212+QTEST_MAIN(ImageCacheTest)
213+
214+#include "test.moc"
tests/plugins/ImageCache/test.qmlnew file+23
@@ -0,0 +1,23 @@
1+/*
2+ * Copyright 2016 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 ImageCache 0.1
19+
20+Image {
21+ width: 400
22+ height: 400
23+}
tests/qmltests/Components/tst_Lockscreen.qml+1−1
@@ -37,7 +37,7 @@ Rectangle {
3737 alphaNumeric: pinPadCheckBox.checked
3838 minPinLength: minPinLengthTextField.text
3939 maxPinLength: maxPinLengthTextField.text
40− background: "../../../qml/graphics/phone_background.jpg"
40+ background: "/usr/share/backgrounds/warty-final-ubuntu.png"
4141 }
4242
4343 Connections {
tests/qmltests/Components/tst_WallpaperResolver.qml+21−35
@@ -18,8 +18,6 @@ import QtQuick 2.4
1818import QtTest 1.0
1919import Ubuntu.Components 1.3
2020import Unity.Test 0.1
21−import AccountsService 0.1
22−import GSettings 1.0
2321
2422import "../../../qml/Components"
2523
@@ -32,7 +30,6 @@ Image {
3230
3331 WallpaperResolver {
3432 id: wallpaperResolver
35− width: units.gu(70)
3633 }
3734
3835 UnityTestCase {
@@ -42,48 +39,37 @@ Image {
4239
4340 function test_background_data() {
4441 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>")},
42+ {tag: "none-valid",
43+ list: ["/first", "/middle", "/last"],
44+ output: "file:///last"},
4945
50− {tag: "empty", accounts: "", gsettings: "", output: "defaultBackground"},
51−
52− {tag: "as-specified",
53− accounts: Qt.resolvedUrl("../../data/unity/backgrounds/blue.png"),
54− gsettings: "",
46+ {tag: "first-valid",
47+ list: [Qt.resolvedUrl("../../data/unity/backgrounds/blue.png"),
48+ "/middle", "/last"],
5549 output: Qt.resolvedUrl("../../data/unity/backgrounds/blue.png")},
5650
57− {tag: "gs-specified",
58− accounts: "",
59− gsettings: Qt.resolvedUrl("../../data/unity/backgrounds/red.png"),
51+ {tag: "middle-valid",
52+ list: ["/first",
53+ Qt.resolvedUrl("../../data/unity/backgrounds/red.png"),
54+ "/last"],
6055 output: Qt.resolvedUrl("../../data/unity/backgrounds/red.png")},
6156
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"),
57+ {tag: "last-valid",
58+ list: ["/first",
59+ "/middle",
60+ Qt.resolvedUrl("../../data/unity/backgrounds/red.png")],
7061 output: Qt.resolvedUrl("../../data/unity/backgrounds/red.png")},
7162
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"},
63+ {tag: "multiple-valid",
64+ list: [Qt.resolvedUrl("../../data/unity/backgrounds/blue.png"),
65+ Qt.resolvedUrl("../../data/unity/backgrounds/red.png")],
66+ output: Qt.resolvedUrl("../../data/unity/backgrounds/blue.png")},
7667 ]
7768 }
78− function test_background(data) {
79− AccountsService.backgroundFile = data.accounts;
80− GSettingsController.setPictureUri(data.gsettings);
8169
82− if (data.output === "defaultBackground") {
83− tryCompare(wallpaperResolver, "background", wallpaperResolver.defaultBackground);
84− } else {
85− tryCompare(wallpaperResolver, "background", data.output);
86− }
70+ function test_background(data) {
71+ wallpaperResolver.candidates = data.list;
72+ tryCompare(wallpaperResolver, "background", data.output);
8773 }
8874 }
8975}
tests/qmltests/Components/tst_ZoomableImage.qml+1−1
@@ -31,7 +31,7 @@ Rectangle {
3131 }
3232
3333 property var widgetData1: {
34− "source": "../../../qml/graphics/phone_background.jpg",
34+ "source": "../../../tests/graphics/avatars/amanda@12.png",
3535 "zoomable": false
3636 }
3737
tests/qmltests/Dash/Previews/tst_PreviewExpandable.qml+3−3
@@ -54,8 +54,8 @@ Rectangle {
5454
5555 property var galleryData: {
5656 "sources": [
57− "../../graphics/phone_background.jpg",
58− "../../graphics/tablet_background.jpg",
57+ "../../../tests/graphics/avatars/amanda@12.png",
58+ "../../../tests/graphics/avatars/funky@12.png",
5959 "../../../tests/graphics/clock@18.png",
6060 "../../graphics/borked"
6161 ]
@@ -71,7 +71,7 @@ Rectangle {
7171 }
7272
7373 property var imageData: {
74− "source": "../../graphics/phone_background.jpg",
74+ "source": "../../../tests/graphics/avatars/amanda@12.png",
7575 "zoomable": false
7676 }
7777
tests/qmltests/Dash/Previews/tst_PreviewImageGallery.qml+6−6
@@ -31,8 +31,8 @@ Rectangle {
3131
3232 property var sourcesModel1: {
3333 "sources": [
34− "../../graphics/phone_background.jpg",
35− "../../graphics/tablet_background.jpg",
34+ "../../../tests/graphics/avatars/amanda@12.png",
35+ "../../../tests/graphics/avatars/funky@12.png",
3636 "../../../tests/graphics/clock@18.png",
3737 "../../graphics/borked"
3838 ]
@@ -40,8 +40,8 @@ Rectangle {
4040
4141 property var sourcesModel1WithFallback: {
4242 "sources": [
43− "../../graphics/phone_background.jpg",
44− "../../graphics/tablet_background.jpg",
43+ "../../../tests/graphics/avatars/amanda@12.png",
44+ "../../../tests/graphics/avatars/funky@12.png",
4545 "../../../tests/graphics/clock@18.png",
4646 "../../graphics/borked"
4747 ]
@@ -50,8 +50,8 @@ Rectangle {
5050
5151 property var sourcesModelEmptyWithFallback: {
5252 "sources": [
53− "../../graphics/phone_background.jpg",
54− "../../graphics/tablet_background.jpg",
53+ "../../../tests/graphics/avatars/amanda@12.png",
54+ "../../../tests/graphics/avatars/funky@12.png",
5555 "../../../tests/graphics/clock@18.png",
5656 ""
5757 ]
tests/qmltests/Dash/Previews/tst_PreviewZoomableImage.qml+7−7
@@ -26,7 +26,7 @@ Rectangle {
2626 color: "lightgrey"
2727
2828 property var widgetData0: {
29− "source": "../../graphics/phone_background.jpg",
29+ "source": "../../../tests/graphics/avatars/amanda@12.png",
3030 "zoomable": false
3131 }
3232
@@ -36,17 +36,17 @@ Rectangle {
3636
3737 property var widgetData2: {
3838 "source": "fadsasf",
39− "fallback": "../../graphics/phone_background.jpg"
39+ "fallback": "../../../tests/graphics/avatars/amanda@12.png"
4040 }
4141
4242 property var widgetData3: {
4343 "source": "",
44− "fallback": "../../graphics/phone_background.jpg"
44+ "fallback": "../../../tests/graphics/avatars/amanda@12.png"
4545 }
4646
4747 property var widgetData4: {
4848 "source": "fadsasf",
49− "fallback": "../../graphics/phone_background.jpg"
49+ "fallback": "../../../tests/graphics/avatars/amanda@12.png"
5050 }
5151
5252 Loader {
@@ -123,11 +123,11 @@ Rectangle {
123123 waitForRendering(zoomableImage);
124124 waitForRendering(lazyImage);
125125 tryCompare(lazyImage, "state", "ready");
126− verify(lazyImage.sourceImage.source.toString().indexOf("phone_background") > -1);
127− zoomableImage.widgetData["source"] = "../../graphics/tablet_background.jpg"
126+ verify(lazyImage.sourceImage.source.toString().indexOf("amanda") > -1);
127+ zoomableImage.widgetData["source"] = "../../../tests/graphics/avatars/funky@12.png"
128128 zoomableImage.widgetDataChanged();
129129 tryCompare(lazyImage, "state", "ready");
130− verify(lazyImage.sourceImage.source.toString().indexOf("tablet_background") > -1);
130+ verify(lazyImage.sourceImage.source.toString().indexOf("funky") > -1);
131131 }
132132 }
133133}
tests/qmltests/Greeter/TestView.qml+1
@@ -27,6 +27,7 @@ Item {
2727 property int delayMinutes
2828 property real backgroundTopMargin
2929 property url background
30+ property bool hasCustomBackground
3031 property bool locked
3132 property bool alphanumeric
3233 property var userModel
tests/qmltests/Greeter/tst_Greeter.qml+7−1
@@ -29,7 +29,7 @@ Item {
2929 width: units.gu(120)
3030 height: units.gu(80)
3131
32− property url defaultBackground: Qt.resolvedUrl("../../../qml/graphics/tablet_background.jpg")
32+ property url defaultBackground: "/usr/share/backgrounds/warty-final-ubuntu.png"
3333
3434 Component.onCompleted: {
3535 // set the mock mode before loading
@@ -368,6 +368,12 @@ Item {
368368 compare(view.background, Qt.resolvedUrl("testing"));
369369 }
370370
371+ function test_hasCustomBackground() {
372+ verify(!view.hasCustomBackground);
373+ greeter.hasCustomBackground = true;
374+ verify(view.hasCustomBackground);
375+ }
376+
371377 function test_notifyAboutToFocusApp() {
372378 greeter.notifyUserRequestedApp("fake-app");
373379 compare(viewTryToUnlockSpy.count, 1);
tests/qmltests/Greeter/tst_NarrowView.qml+13−1
@@ -40,7 +40,7 @@ Item {
4040 property bool itemDestroyed: false
4141 sourceComponent: Component {
4242 NarrowView {
43− background: Qt.resolvedUrl("../../../qml/graphics/phone_background.jpg")
43+ background: "/usr/share/backgrounds/warty-final-ubuntu.png"
4444 userModel: LightDM.Users
4545 infographicModel: LightDM.Infographic
4646
@@ -345,6 +345,18 @@ Item {
345345 compare(teaseSpy.count, data.count);
346346 }
347347
348+ function test_customBackground() {
349+ var lockscreenShade = findChild(view, "lockscreenShade");
350+ var backgroundShade = findChild(view, "backgroundShade");
351+
352+ compare(lockscreenShade.opacity, 0);
353+ verify(!backgroundShade.visible);
354+
355+ view.hasCustomBackground = true;
356+ compare(lockscreenShade.opacity, 0.4);
357+ verify(backgroundShade.visible);
358+ }
359+
348360 function test_respondedWithPin() {
349361 view.locked = true;
350362 view.showPrompt("", true, true);
tests/qmltests/Greeter/tst_WideView.qml+12−1
@@ -50,7 +50,7 @@ StyledItem {
5050 WideView {
5151 id: view
5252
53− background: Qt.resolvedUrl("../../../qml/graphics/tablet_background.jpg")
53+ background: "/usr/share/backgrounds/warty-final-ubuntu.png"
5454 userModel: LightDM.Users
5555 infographicModel: LightDM.Infographic
5656
@@ -525,6 +525,17 @@ StyledItem {
525525 compare(view.currentIndex, 0); // confirm we didn't change
526526 }
527527
528+ function test_customBackground() {
529+ var coverPage = findChild(view, "coverPage");
530+ var backgroundShade = findChild(coverPage, "backgroundShade");
531+
532+ verify(!view.hasCustomBackground);
533+ verify(!backgroundShade.visible);
534+
535+ view.hasCustomBackground = true;
536+ verify(backgroundShade.visible);
537+ }
538+
528539 function test_respondedWithPassword() {
529540 view.locked = true;
530541 view.showPrompt("Prompt", true, false);
tests/qmltests/Stages/tst_DesktopStage.qml+1−1
@@ -75,7 +75,7 @@ Item {
7575 sourceComponent: Component {
7676 DesktopStage {
7777 anchors.fill: parent
78− background: "../../../qml/graphics/tablet_background.jpg"
78+ background: "/usr/share/backgrounds/warty-final-ubuntu.png"
7979 focus: true
8080
8181 Component.onCompleted: {
tests/qmltests/tst_Shell.qml+81
@@ -549,6 +549,7 @@ Rectangle {
549549
550550 AccountsService.demoEdges = false;
551551 AccountsService.demoEdgesCompleted = [];
552+ AccountsService.backgroundFile = "";
552553 Wizard.System.wizardEnabled = false;
553554 shellLoader.mode = "full-greeter";
554555
@@ -563,6 +564,7 @@ Rectangle {
563564 broadcastHomeSpy.clear();
564565
565566 GSettingsController.setLifecycleExemptAppids([]);
567+ GSettingsController.setPictureUri("");
566568
567569 // there should be only unity8-dash window over there
568570 tryCompare(ApplicationManager, "count", 1);
@@ -1334,6 +1336,32 @@ Rectangle {
13341336 tryCompare(tutorial, "paused", false);
13351337 }
13361338
1339+ function test_customBackground() {
1340+ loadShell("desktop");
1341+ shell.usageScenario = "desktop";
1342+ waitForRendering(shell);
1343+
1344+ var wallpaperResolver = findInvisibleChild(shell, "wallpaperResolver");
1345+ var greeter = findChild(shell, "greeter");
1346+ verify(!greeter.hasCustomBackground);
1347+ compare(wallpaperResolver.background, wallpaperResolver.defaultBackground);
1348+
1349+ AccountsService.backgroundFile = Qt.resolvedUrl("../graphics/applicationIcons/dash.png");
1350+ tryCompare(greeter, "hasCustomBackground", true);
1351+ compare(wallpaperResolver.background, AccountsService.backgroundFile);
1352+ }
1353+
1354+ function test_cachedBackground() {
1355+ loadShell("desktop");
1356+ shell.usageScenario = "desktop";
1357+ waitForRendering(shell);
1358+
1359+ var greeter = findChild(shell, "greeter");
1360+ verify(!greeter.hasCustomBackground);
1361+ compare(greeter.background.toString().indexOf("image://unity8imagecache/file:///"), 0);
1362+ verify(greeter.background.toString().indexOf("?name=wallpaper") > 0);
1363+ }
1364+
13371365 function test_tapOnRightEdgeReachesApplicationSurface() {
13381366 loadShell("phone");
13391367 swipeAwayGreeter();
@@ -2435,6 +2463,59 @@ Rectangle {
24352463 compare(topmostSurfaceItem.touchReleaseCount, 2);
24362464 }
24372465
2466+ function test_background_data() {
2467+ return [
2468+ {tag: "color",
2469+ accounts: Qt.resolvedUrl("data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>"),
2470+ gsettings: "",
2471+ output: Qt.resolvedUrl("data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>")},
2472+
2473+ {tag: "empty", accounts: "", gsettings: "", output: "defaultBackground"},
2474+
2475+ {tag: "as-specified",
2476+ accounts: Qt.resolvedUrl("../data/unity/backgrounds/blue.png"),
2477+ gsettings: "",
2478+ output: Qt.resolvedUrl("../data/unity/backgrounds/blue.png")},
2479+
2480+ {tag: "gs-specified",
2481+ accounts: "",
2482+ gsettings: Qt.resolvedUrl("../data/unity/backgrounds/red.png"),
2483+ output: Qt.resolvedUrl("../data/unity/backgrounds/red.png")},
2484+
2485+ {tag: "both-specified",
2486+ accounts: Qt.resolvedUrl("../data/unity/backgrounds/blue.png"),
2487+ gsettings: Qt.resolvedUrl("../data/unity/backgrounds/red.png"),
2488+ output: Qt.resolvedUrl("../data/unity/backgrounds/blue.png")},
2489+
2490+ {tag: "invalid-as",
2491+ accounts: Qt.resolvedUrl("../data/unity/backgrounds/nope.png"),
2492+ gsettings: Qt.resolvedUrl("../data/unity/backgrounds/red.png"),
2493+ output: Qt.resolvedUrl("../data/unity/backgrounds/red.png")},
2494+
2495+ {tag: "invalid-both",
2496+ accounts: Qt.resolvedUrl("../data/unity/backgrounds/nope.png"),
2497+ gsettings: Qt.resolvedUrl("../data/unity/backgrounds/stillnope.png"),
2498+ output: "defaultBackground"},
2499+ ]
2500+ }
2501+ function test_background(data) {
2502+ loadShell("phone");
2503+ shell.usageScenario = "phone";
2504+ waitForRendering(shell);
2505+
2506+ AccountsService.backgroundFile = data.accounts;
2507+ GSettingsController.setPictureUri(data.gsettings);
2508+
2509+ var wallpaperResolver = findChild(shell, "wallpaperResolver");
2510+ if (data.output === "defaultBackground") {
2511+ tryCompare(wallpaperResolver, "background", wallpaperResolver.defaultBackground);
2512+ verify(!wallpaperResolver.hasCustomBackground);
2513+ } else {
2514+ tryCompare(wallpaperResolver, "background", data.output);
2515+ verify(wallpaperResolver.hasCustomBackground);
2516+ }
2517+ }
2518+
24382519 function test_greeterModeBroadcastsApp() {
24392520 setLightDMMockMode("single-pin");
24402521 shellLoader.mode = "greeter";