on xenial help sign in
Use the default system wallpaper instead of our custom one.
4bd37d6Michael Terry
debian/control+1
@@ -154,6 +154,7 @@ Depends: qml-module-qtquick-layouts,
154 qtdeclarative5-ubuntu-settings-components (>= 0.7),
154 qtdeclarative5-ubuntu-settings-components (>= 0.7),
155 qtdeclarative5-unity-notifications-plugin (>= 0.1.2) | unity-notifications-impl,
155 qtdeclarative5-unity-notifications-plugin (>= 0.1.2) | unity-notifications-impl,
156 ubuntu-thumbnailer-impl-0,
156 ubuntu-thumbnailer-impl-0,
157+ ubuntu-wallpapers,
157 unity-application-impl-21,
158 unity-application-impl-21,
158 unity-notifications-impl-3,
159 unity-notifications-impl-3,
159 unity-plugin-scopes | unity-scopes-impl,
160 unity-plugin-scopes | unity-scopes-impl,
debian/unity8-private.install+1
@@ -4,6 +4,7 @@ usr/lib/*/unity8/qml/Cursor
4usr/lib/*/unity8/qml/Dash
4usr/lib/*/unity8/qml/Dash
5usr/lib/*/unity8/qml/GlobalShortcut
5usr/lib/*/unity8/qml/GlobalShortcut
6usr/lib/*/unity8/qml/Greeter
6usr/lib/*/unity8/qml/Greeter
7+usr/lib/*/unity8/qml/ImageCache
7usr/lib/*/unity8/qml/LightDM
8usr/lib/*/unity8/qml/LightDM
8usr/lib/*/unity8/qml/Lights
9usr/lib/*/unity8/qml/Lights
9usr/lib/*/unity8/qml/Powerd
10usr/lib/*/unity8/qml/Powerd
plugins/CMakeLists.txt+1
@@ -15,6 +15,7 @@ add_subdirectory(AccountsService)
15add_subdirectory(Cursor)
15add_subdirectory(Cursor)
16add_subdirectory(GlobalShortcut)
16add_subdirectory(GlobalShortcut)
17add_subdirectory(Greeter)
17add_subdirectory(Greeter)
18+add_subdirectory(ImageCache)
18add_subdirectory(LightDM)
19add_subdirectory(LightDM)
19add_subdirectory(Lights)
20add_subdirectory(Lights)
20add_subdirectory(Dash)
21add_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 {
96 visible: root.background.toString() !== ""
96 visible: root.background.toString() !== ""
97 }
97 }
98
98
99− Image {
99+ Wallpaper {
100 id: backgroundImage
100 id: backgroundImage
101 objectName: "lockscreenBackground"
101 objectName: "lockscreenBackground"
102 anchors {
102 anchors {
103 fill: parent
103 fill: parent
104 }
104 }
105− // Limit how much memory we'll reserve for this image
106− sourceSize.height: height
107− sourceSize.width: width
108 source: root.required ? root.background : ""
105 source: root.required ? root.background : ""
109− fillMode: Image.PreserveAspectCrop
110 }
106 }
111
107
112 // This is to
108 // 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 @@
15 */
15 */
16
16
17import QtQuick 2.4
17import QtQuick 2.4
18−import AccountsService 0.1
19−import GSettings 1.0
20import Ubuntu.Components 1.3
18import Ubuntu.Components 1.3
21
19
22−/*
20+Item {
23− Defines the background URL based on several factors, such as:
21+ id: root
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
22
35− // That's the property users of this component are going to consume.
23+ // Provide a list of wallpapers to resolve here, preferred ones first
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 {
24+ property var candidates: []
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
25
49− // This is a dummy image to detect if the custom GSettings set wallpaper loads successfully.
26+ readonly property url background: {
50− property var _gsImageTester: Image {
27+ for (var i = 0; i < repeater.count; i++) {
51− id: gsImageTester
28+ if (repeater.itemAt(i).status === Image.Ready)
52− source: backgroundSettings.pictureUri && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : ""
29+ return candidates[i];
53− height: 0
30+ }
54− width: 0
31+ if (i > 0) {
55− sourceSize.height: 0
32+ return candidates[i - 1]; // last item is last resort
56− sourceSize.width: 0
33+ } else {
34+ return "";
35+ }
57 }
36 }
58
37
59− property var _gsettings: GSettings {
38+ Repeater {
60− id: backgroundSettings
39+ id: repeater
61− schema.id: "org.gnome.desktop.background"
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+ }
62 }
48 }
63}
49}
qml/Dash/DashBackground.qml+2−2
@@ -16,10 +16,10 @@
16
16
17import QtQuick 2.4
17import QtQuick 2.4
18import QtQuick.Window 2.2
18import QtQuick.Window 2.2
19−import Ubuntu.Thumbnailer 0.1
19+import ImageCache 0.1
20
20
21Image {
21Image {
22− source: "image://thumbnailer/" + Qt.resolvedUrl("graphics/paper.png")
22+ source: "image://unity8imagecache/" + Qt.resolvedUrl("graphics/paper.png")
23 fillMode: Image.PreserveAspectCrop
23 fillMode: Image.PreserveAspectCrop
24 horizontalAlignment: Image.AlignRight
24 horizontalAlignment: Image.AlignRight
25 verticalAlignment: Image.AlignTop
25 verticalAlignment: Image.AlignTop
qml/Greeter/CoverPage.qml+5−5
@@ -25,6 +25,7 @@ Showable {
25 property real dragHandleLeftMargin
25 property real dragHandleLeftMargin
26 property real launcherOffset
26 property real launcherOffset
27 property alias background: greeterBackground.source
27 property alias background: greeterBackground.source
28+ property alias hasCustomBackground: backgroundShade.visible
28 property real backgroundTopMargin
29 property real backgroundTopMargin
29 property var infographicModel
30 property var infographicModel
30 property bool draggable: true
31 property bool draggable: true
@@ -82,24 +83,23 @@ Showable {
82 color: "black"
83 color: "black"
83 }
84 }
84
85
85− CrossFadeImage {
86+ Wallpaper {
86 id: greeterBackground
87 id: greeterBackground
87 objectName: "greeterBackground"
88 objectName: "greeterBackground"
88 anchors {
89 anchors {
89 fill: parent
90 fill: parent
90 topMargin: root.backgroundTopMargin
91 topMargin: root.backgroundTopMargin
91 }
92 }
92− fillMode: Image.PreserveAspectCrop
93− // Limit how much memory we'll reserve for this image
94− sourceSize.height: height
95− sourceSize.width: width
96 }
93 }
97
94
98 // Darkens wallpaper so that we can read text on it and see infographic
95 // Darkens wallpaper so that we can read text on it and see infographic
99 Rectangle {
96 Rectangle {
97+ id: backgroundShade
98+ objectName: "backgroundShade"
100 anchors.fill: parent
99 anchors.fill: parent
101 color: "black"
100 color: "black"
102 opacity: 0.4
101 opacity: 0.4
102+ visible: false
103 }
103 }
104
104
105 Infographics {
105 Infographics {
qml/Greeter/Greeter.qml+7
@@ -34,6 +34,7 @@ Showable {
34 property real dragHandleLeftMargin: 0
34 property real dragHandleLeftMargin: 0
35
35
36 property url background
36 property url background
37+ property bool hasCustomBackground
37
38
38 // How far to offset the top greeter layer during a launcher left-drag
39 // How far to offset the top greeter layer during a launcher left-drag
39 property real launcherOffset
40 property real launcherOffset
@@ -437,6 +438,12 @@ Showable {
437 value: root.background
438 value: root.background
438 }
439 }
439
440
441+ Binding {
442+ target: loader.item
443+ property: "hasCustomBackground"
444+ value: root.hasCustomBackground
445+ }
446+
440 Binding {
447 Binding {
441 target: loader.item
448 target: loader.item
442 property: "locked"
449 property: "locked"
qml/Greeter/NarrowView.qml+5−6
@@ -29,6 +29,7 @@ FocusScope {
29 property alias delayMinutes: delayedLockscreen.delayMinutes
29 property alias delayMinutes: delayedLockscreen.delayMinutes
30 property alias backgroundTopMargin: coverPage.backgroundTopMargin
30 property alias backgroundTopMargin: coverPage.backgroundTopMargin
31 property url background
31 property url background
32+ property bool hasCustomBackground
32 property bool locked
33 property bool locked
33 property alias alphanumeric: loginList.alphanumeric
34 property alias alphanumeric: loginList.alphanumeric
34 property alias userModel: loginList.model
35 property alias userModel: loginList.model
@@ -123,7 +124,7 @@ FocusScope {
123 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
124 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
124 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
125 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
125
126
126− CrossFadeImage {
127+ Wallpaper {
127 id: lockscreenBackground
128 id: lockscreenBackground
128 objectName: "lockscreenBackground"
129 objectName: "lockscreenBackground"
129 anchors {
130 anchors {
@@ -131,17 +132,14 @@ FocusScope {
131 topMargin: root.backgroundTopMargin
132 topMargin: root.backgroundTopMargin
132 }
133 }
133 source: root.background
134 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
138 }
135 }
139
136
140 // Darken background to match CoverPage
137 // Darken background to match CoverPage
141 Rectangle {
138 Rectangle {
139+ objectName: "lockscreenShade"
142 anchors.fill: parent
140 anchors.fill: parent
143 color: "black"
141 color: "black"
144− opacity: 0.4
142+ opacity: root.hasCustomBackground ? 0.4 : 0
145 }
143 }
146
144
147 LoginList {
145 LoginList {
@@ -191,6 +189,7 @@ FocusScope {
191 height: parent.height
189 height: parent.height
192 width: parent.width
190 width: parent.width
193 background: root.background
191 background: root.background
192+ hasCustomBackground: root.hasCustomBackground
194 draggable: !root.waiting
193 draggable: !root.waiting
195 onTease: root.tease()
194 onTease: root.tease()
196 onClicked: hide()
195 onClicked: hide()
qml/Greeter/WideView.qml+1
@@ -24,6 +24,7 @@ FocusScope {
24
24
25 property alias background: coverPage.background
25 property alias background: coverPage.background
26 property alias backgroundTopMargin: coverPage.backgroundTopMargin
26 property alias backgroundTopMargin: coverPage.backgroundTopMargin
27+ property alias hasCustomBackground: coverPage.hasCustomBackground
27 property alias dragHandleLeftMargin: coverPage.dragHandleLeftMargin
28 property alias dragHandleLeftMargin: coverPage.dragHandleLeftMargin
28 property alias infographicModel: coverPage.infographicModel
29 property alias infographicModel: coverPage.infographicModel
29 property alias launcherOffset: coverPage.launcherOffset
30 property alias launcherOffset: coverPage.launcherOffset
qml/Shell.qml+27−4
@@ -26,6 +26,7 @@ import Unity.Connectivity 0.1
26import Unity.Launcher 0.1
26import Unity.Launcher 0.1
27import GlobalShortcut 1.0 // has to be before Utils, because of WindowInputFilter
27import GlobalShortcut 1.0 // has to be before Utils, because of WindowInputFilter
28import GSettings 1.0
28import GSettings 1.0
29+import ImageCache 0.1
29import Utils 0.1
30import Utils 0.1
30import Powerd 0.1
31import Powerd 0.1
31import SessionBroadcast 0.1
32import SessionBroadcast 0.1
@@ -145,7 +146,28 @@ StyledItem {
145
146
146 WallpaperResolver {
147 WallpaperResolver {
147 id: wallpaperResolver
148 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+ ]
149 }
171 }
150
172
151 readonly property alias greeter: greeterLoader.item
173 readonly property alias greeter: greeterLoader.item
@@ -352,7 +374,7 @@ StyledItem {
352 Binding {
374 Binding {
353 target: applicationsDisplayLoader.item
375 target: applicationsDisplayLoader.item
354 property: "background"
376 property: "background"
355− value: wallpaperResolver.background
377+ value: wallpaperResolver.cachedBackground
356 }
378 }
357 Binding {
379 Binding {
358 target: applicationsDisplayLoader.item
380 target: applicationsDisplayLoader.item
@@ -438,7 +460,8 @@ StyledItem {
438 tabletMode: shell.usageScenario != "phone"
460 tabletMode: shell.usageScenario != "phone"
439 launcherOffset: launcher.progress
461 launcherOffset: launcher.progress
440 forcedUnlock: wizard.active || shell.mode === "full-shell"
462 forcedUnlock: wizard.active || shell.mode === "full-shell"
441− background: wallpaperResolver.background
463+ background: wallpaperResolver.cachedBackground
464+ hasCustomBackground: wallpaperResolver.hasCustomBackground
442 allowFingerprint: !dialogs.hasActiveDialog &&
465 allowFingerprint: !dialogs.hasActiveDialog &&
443 !notifications.topmostIsFullscreen &&
466 !notifications.topmostIsFullscreen &&
444 !panel.indicators.shown
467 !panel.indicators.shown
@@ -719,7 +742,7 @@ StyledItem {
719 model: NotificationBackend.Model
742 model: NotificationBackend.Model
720 margin: units.gu(1)
743 margin: units.gu(1)
721 hasMouse: shell.hasMouse
744 hasMouse: shell.hasMouse
722− background: wallpaperResolver.background
745+ background: wallpaperResolver.cachedBackground
723
746
724 y: topmostIsFullscreen ? 0 : panel.panelHeight
747 y: topmostIsFullscreen ? 0 : panel.panelHeight
725 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
748 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
qml/Stages/DesktopStage.qml+1−3
@@ -240,12 +240,10 @@ AbstractStage {
240 anchors.fill: parent
240 anchors.fill: parent
241 focus: spread.state !== "altTab"
241 focus: spread.state !== "altTab"
242
242
243− CrossFadeImage {
243+ Wallpaper {
244 id: wallpaper
244 id: wallpaper
245 anchors.fill: parent
245 anchors.fill: parent
246 source: root.background
246 source: root.background
247− sourceSize { height: root.height; width: root.width }
248− fillMode: Image.PreserveAspectCrop
249 }
247 }
250
248
251 TopLevelSurfaceRepeater {
249 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)
3add_subdirectory(Dash)
3add_subdirectory(Dash)
4add_subdirectory(GlobalShortcut)
4add_subdirectory(GlobalShortcut)
5add_subdirectory(Greeter)
5add_subdirectory(Greeter)
6+add_subdirectory(ImageCache)
6add_subdirectory(LightDM)
7add_subdirectory(LightDM)
7add_subdirectory(SessionBroadcast)
8add_subdirectory(SessionBroadcast)
8add_subdirectory(Ubuntu)
9add_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 {
37 alphaNumeric: pinPadCheckBox.checked
37 alphaNumeric: pinPadCheckBox.checked
38 minPinLength: minPinLengthTextField.text
38 minPinLength: minPinLengthTextField.text
39 maxPinLength: maxPinLengthTextField.text
39 maxPinLength: maxPinLengthTextField.text
40− background: "../../../qml/graphics/phone_background.jpg"
40+ background: "/usr/share/backgrounds/warty-final-ubuntu.png"
41 }
41 }
42
42
43 Connections {
43 Connections {
tests/qmltests/Components/tst_WallpaperResolver.qml+21−35
@@ -18,8 +18,6 @@ import QtQuick 2.4
18import QtTest 1.0
18import QtTest 1.0
19import Ubuntu.Components 1.3
19import Ubuntu.Components 1.3
20import Unity.Test 0.1
20import Unity.Test 0.1
21−import AccountsService 0.1
22−import GSettings 1.0
23
21
24import "../../../qml/Components"
22import "../../../qml/Components"
25
23
@@ -32,7 +30,6 @@ Image {
32
30
33 WallpaperResolver {
31 WallpaperResolver {
34 id: wallpaperResolver
32 id: wallpaperResolver
35− width: units.gu(70)
36 }
33 }
37
34
38 UnityTestCase {
35 UnityTestCase {
@@ -42,48 +39,37 @@ Image {
42
39
43 function test_background_data() {
40 function test_background_data() {
44 return [
41 return [
45− {tag: "color",
42+ {tag: "none-valid",
46− accounts: Qt.resolvedUrl("data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>"),
43+ list: ["/first", "/middle", "/last"],
47− gsettings: "",
44+ output: "file:///last"},
48− output: Qt.resolvedUrl("data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>")},
49
45
50− {tag: "empty", accounts: "", gsettings: "", output: "defaultBackground"},
46+ {tag: "first-valid",
51−
52− {tag: "as-specified",
53− accounts: Qt.resolvedUrl("../../data/unity/backgrounds/blue.png"),
47+ list: [Qt.resolvedUrl("../../data/unity/backgrounds/blue.png"),
54− gsettings: "",
48+ "/middle", "/last"],
55 output: Qt.resolvedUrl("../../data/unity/backgrounds/blue.png")},
49 output: Qt.resolvedUrl("../../data/unity/backgrounds/blue.png")},
56
50
57− {tag: "gs-specified",
51+ {tag: "middle-valid",
58− accounts: "",
52+ list: ["/first",
59− gsettings: Qt.resolvedUrl("../../data/unity/backgrounds/red.png"),
53+ Qt.resolvedUrl("../../data/unity/backgrounds/red.png"),
54+ "/last"],
60 output: Qt.resolvedUrl("../../data/unity/backgrounds/red.png")},
55 output: Qt.resolvedUrl("../../data/unity/backgrounds/red.png")},
61
56
62− {tag: "both-specified",
57+ {tag: "last-valid",
63− accounts: Qt.resolvedUrl("../../data/unity/backgrounds/blue.png"),
58+ list: ["/first",
64− gsettings: Qt.resolvedUrl("../../data/unity/backgrounds/red.png"),
59+ "/middle",
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"),
60+ Qt.resolvedUrl("../../data/unity/backgrounds/red.png")],
70 output: Qt.resolvedUrl("../../data/unity/backgrounds/red.png")},
61 output: Qt.resolvedUrl("../../data/unity/backgrounds/red.png")},
71
62
72− {tag: "invalid-both",
63+ {tag: "multiple-valid",
73− accounts: Qt.resolvedUrl("../../data/unity/backgrounds/nope.png"),
64+ list: [Qt.resolvedUrl("../../data/unity/backgrounds/blue.png"),
74− gsettings: Qt.resolvedUrl("../../data/unity/backgrounds/stillnope.png"),
65+ Qt.resolvedUrl("../../data/unity/backgrounds/red.png")],
75− output: "defaultBackground"},
66+ output: Qt.resolvedUrl("../../data/unity/backgrounds/blue.png")},
76 ]
67 ]
77 }
68 }
78− function test_background(data) {
79− AccountsService.backgroundFile = data.accounts;
80− GSettingsController.setPictureUri(data.gsettings);
81
69
82− if (data.output === "defaultBackground") {
70+ function test_background(data) {
83− tryCompare(wallpaperResolver, "background", wallpaperResolver.defaultBackground);
71+ wallpaperResolver.candidates = data.list;
84− } else {
85− tryCompare(wallpaperResolver, "background", data.output);
72+ tryCompare(wallpaperResolver, "background", data.output);
86− }
87 }
73 }
88 }
74 }
89}
75}
tests/qmltests/Components/tst_ZoomableImage.qml+1−1
@@ -31,7 +31,7 @@ Rectangle {
31 }
31 }
32
32
33 property var widgetData1: {
33 property var widgetData1: {
34− "source": "../../../qml/graphics/phone_background.jpg",
34+ "source": "../../../tests/graphics/avatars/amanda@12.png",
35 "zoomable": false
35 "zoomable": false
36 }
36 }
37
37
tests/qmltests/Dash/Previews/tst_PreviewExpandable.qml+3−3
@@ -54,8 +54,8 @@ Rectangle {
54
54
55 property var galleryData: {
55 property var galleryData: {
56 "sources": [
56 "sources": [
57− "../../graphics/phone_background.jpg",
57+ "../../../tests/graphics/avatars/amanda@12.png",
58− "../../graphics/tablet_background.jpg",
58+ "../../../tests/graphics/avatars/funky@12.png",
59 "../../../tests/graphics/clock@18.png",
59 "../../../tests/graphics/clock@18.png",
60 "../../graphics/borked"
60 "../../graphics/borked"
61 ]
61 ]
@@ -71,7 +71,7 @@ Rectangle {
71 }
71 }
72
72
73 property var imageData: {
73 property var imageData: {
74− "source": "../../graphics/phone_background.jpg",
74+ "source": "../../../tests/graphics/avatars/amanda@12.png",
75 "zoomable": false
75 "zoomable": false
76 }
76 }
77
77
tests/qmltests/Dash/Previews/tst_PreviewImageGallery.qml+6−6
@@ -31,8 +31,8 @@ Rectangle {
31
31
32 property var sourcesModel1: {
32 property var sourcesModel1: {
33 "sources": [
33 "sources": [
34− "../../graphics/phone_background.jpg",
34+ "../../../tests/graphics/avatars/amanda@12.png",
35− "../../graphics/tablet_background.jpg",
35+ "../../../tests/graphics/avatars/funky@12.png",
36 "../../../tests/graphics/clock@18.png",
36 "../../../tests/graphics/clock@18.png",
37 "../../graphics/borked"
37 "../../graphics/borked"
38 ]
38 ]
@@ -40,8 +40,8 @@ Rectangle {
40
40
41 property var sourcesModel1WithFallback: {
41 property var sourcesModel1WithFallback: {
42 "sources": [
42 "sources": [
43− "../../graphics/phone_background.jpg",
43+ "../../../tests/graphics/avatars/amanda@12.png",
44− "../../graphics/tablet_background.jpg",
44+ "../../../tests/graphics/avatars/funky@12.png",
45 "../../../tests/graphics/clock@18.png",
45 "../../../tests/graphics/clock@18.png",
46 "../../graphics/borked"
46 "../../graphics/borked"
47 ]
47 ]
@@ -50,8 +50,8 @@ Rectangle {
50
50
51 property var sourcesModelEmptyWithFallback: {
51 property var sourcesModelEmptyWithFallback: {
52 "sources": [
52 "sources": [
53− "../../graphics/phone_background.jpg",
53+ "../../../tests/graphics/avatars/amanda@12.png",
54− "../../graphics/tablet_background.jpg",
54+ "../../../tests/graphics/avatars/funky@12.png",
55 "../../../tests/graphics/clock@18.png",
55 "../../../tests/graphics/clock@18.png",
56 ""
56 ""
57 ]
57 ]
tests/qmltests/Dash/Previews/tst_PreviewZoomableImage.qml+7−7
@@ -26,7 +26,7 @@ Rectangle {
26 color: "lightgrey"
26 color: "lightgrey"
27
27
28 property var widgetData0: {
28 property var widgetData0: {
29− "source": "../../graphics/phone_background.jpg",
29+ "source": "../../../tests/graphics/avatars/amanda@12.png",
30 "zoomable": false
30 "zoomable": false
31 }
31 }
32
32
@@ -36,17 +36,17 @@ Rectangle {
36
36
37 property var widgetData2: {
37 property var widgetData2: {
38 "source": "fadsasf",
38 "source": "fadsasf",
39− "fallback": "../../graphics/phone_background.jpg"
39+ "fallback": "../../../tests/graphics/avatars/amanda@12.png"
40 }
40 }
41
41
42 property var widgetData3: {
42 property var widgetData3: {
43 "source": "",
43 "source": "",
44− "fallback": "../../graphics/phone_background.jpg"
44+ "fallback": "../../../tests/graphics/avatars/amanda@12.png"
45 }
45 }
46
46
47 property var widgetData4: {
47 property var widgetData4: {
48 "source": "fadsasf",
48 "source": "fadsasf",
49− "fallback": "../../graphics/phone_background.jpg"
49+ "fallback": "../../../tests/graphics/avatars/amanda@12.png"
50 }
50 }
51
51
52 Loader {
52 Loader {
@@ -123,11 +123,11 @@ Rectangle {
123 waitForRendering(zoomableImage);
123 waitForRendering(zoomableImage);
124 waitForRendering(lazyImage);
124 waitForRendering(lazyImage);
125 tryCompare(lazyImage, "state", "ready");
125 tryCompare(lazyImage, "state", "ready");
126− verify(lazyImage.sourceImage.source.toString().indexOf("phone_background") > -1);
126+ verify(lazyImage.sourceImage.source.toString().indexOf("amanda") > -1);
127− zoomableImage.widgetData["source"] = "../../graphics/tablet_background.jpg"
127+ zoomableImage.widgetData["source"] = "../../../tests/graphics/avatars/funky@12.png"
128 zoomableImage.widgetDataChanged();
128 zoomableImage.widgetDataChanged();
129 tryCompare(lazyImage, "state", "ready");
129 tryCompare(lazyImage, "state", "ready");
130− verify(lazyImage.sourceImage.source.toString().indexOf("tablet_background") > -1);
130+ verify(lazyImage.sourceImage.source.toString().indexOf("funky") > -1);
131 }
131 }
132 }
132 }
133}
133}
tests/qmltests/Greeter/TestView.qml+1
@@ -27,6 +27,7 @@ Item {
27 property int delayMinutes
27 property int delayMinutes
28 property real backgroundTopMargin
28 property real backgroundTopMargin
29 property url background
29 property url background
30+ property bool hasCustomBackground
30 property bool locked
31 property bool locked
31 property bool alphanumeric
32 property bool alphanumeric
32 property var userModel
33 property var userModel
tests/qmltests/Greeter/tst_Greeter.qml+7−1
@@ -29,7 +29,7 @@ Item {
29 width: units.gu(120)
29 width: units.gu(120)
30 height: units.gu(80)
30 height: units.gu(80)
31
31
32− property url defaultBackground: Qt.resolvedUrl("../../../qml/graphics/tablet_background.jpg")
32+ property url defaultBackground: "/usr/share/backgrounds/warty-final-ubuntu.png"
33
33
34 Component.onCompleted: {
34 Component.onCompleted: {
35 // set the mock mode before loading
35 // set the mock mode before loading
@@ -368,6 +368,12 @@ Item {
368 compare(view.background, Qt.resolvedUrl("testing"));
368 compare(view.background, Qt.resolvedUrl("testing"));
369 }
369 }
370
370
371+ function test_hasCustomBackground() {
372+ verify(!view.hasCustomBackground);
373+ greeter.hasCustomBackground = true;
374+ verify(view.hasCustomBackground);
375+ }
376+
371 function test_notifyAboutToFocusApp() {
377 function test_notifyAboutToFocusApp() {
372 greeter.notifyUserRequestedApp("fake-app");
378 greeter.notifyUserRequestedApp("fake-app");
373 compare(viewTryToUnlockSpy.count, 1);
379 compare(viewTryToUnlockSpy.count, 1);
tests/qmltests/Greeter/tst_NarrowView.qml+13−1
@@ -40,7 +40,7 @@ Item {
40 property bool itemDestroyed: false
40 property bool itemDestroyed: false
41 sourceComponent: Component {
41 sourceComponent: Component {
42 NarrowView {
42 NarrowView {
43− background: Qt.resolvedUrl("../../../qml/graphics/phone_background.jpg")
43+ background: "/usr/share/backgrounds/warty-final-ubuntu.png"
44 userModel: LightDM.Users
44 userModel: LightDM.Users
45 infographicModel: LightDM.Infographic
45 infographicModel: LightDM.Infographic
46
46
@@ -345,6 +345,18 @@ Item {
345 compare(teaseSpy.count, data.count);
345 compare(teaseSpy.count, data.count);
346 }
346 }
347
347
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+
348 function test_respondedWithPin() {
360 function test_respondedWithPin() {
349 view.locked = true;
361 view.locked = true;
350 view.showPrompt("", true, true);
362 view.showPrompt("", true, true);
tests/qmltests/Greeter/tst_WideView.qml+12−1
@@ -50,7 +50,7 @@ StyledItem {
50 WideView {
50 WideView {
51 id: view
51 id: view
52
52
53− background: Qt.resolvedUrl("../../../qml/graphics/tablet_background.jpg")
53+ background: "/usr/share/backgrounds/warty-final-ubuntu.png"
54 userModel: LightDM.Users
54 userModel: LightDM.Users
55 infographicModel: LightDM.Infographic
55 infographicModel: LightDM.Infographic
56
56
@@ -525,6 +525,17 @@ StyledItem {
525 compare(view.currentIndex, 0); // confirm we didn't change
525 compare(view.currentIndex, 0); // confirm we didn't change
526 }
526 }
527
527
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+
528 function test_respondedWithPassword() {
539 function test_respondedWithPassword() {
529 view.locked = true;
540 view.locked = true;
530 view.showPrompt("Prompt", true, false);
541 view.showPrompt("Prompt", true, false);
tests/qmltests/Stages/tst_DesktopStage.qml+1−1
@@ -75,7 +75,7 @@ Item {
75 sourceComponent: Component {
75 sourceComponent: Component {
76 DesktopStage {
76 DesktopStage {
77 anchors.fill: parent
77 anchors.fill: parent
78− background: "../../../qml/graphics/tablet_background.jpg"
78+ background: "/usr/share/backgrounds/warty-final-ubuntu.png"
79 focus: true
79 focus: true
80
80
81 Component.onCompleted: {
81 Component.onCompleted: {
tests/qmltests/tst_Shell.qml+81
@@ -549,6 +549,7 @@ Rectangle {
549
549
550 AccountsService.demoEdges = false;
550 AccountsService.demoEdges = false;
551 AccountsService.demoEdgesCompleted = [];
551 AccountsService.demoEdgesCompleted = [];
552+ AccountsService.backgroundFile = "";
552 Wizard.System.wizardEnabled = false;
553 Wizard.System.wizardEnabled = false;
553 shellLoader.mode = "full-greeter";
554 shellLoader.mode = "full-greeter";
554
555
@@ -563,6 +564,7 @@ Rectangle {
563 broadcastHomeSpy.clear();
564 broadcastHomeSpy.clear();
564
565
565 GSettingsController.setLifecycleExemptAppids([]);
566 GSettingsController.setLifecycleExemptAppids([]);
567+ GSettingsController.setPictureUri("");
566
568
567 // there should be only unity8-dash window over there
569 // there should be only unity8-dash window over there
568 tryCompare(ApplicationManager, "count", 1);
570 tryCompare(ApplicationManager, "count", 1);
@@ -1334,6 +1336,32 @@ Rectangle {
1334 tryCompare(tutorial, "paused", false);
1336 tryCompare(tutorial, "paused", false);
1335 }
1337 }
1336
1338
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+
1337 function test_tapOnRightEdgeReachesApplicationSurface() {
1365 function test_tapOnRightEdgeReachesApplicationSurface() {
1338 loadShell("phone");
1366 loadShell("phone");
1339 swipeAwayGreeter();
1367 swipeAwayGreeter();
@@ -2435,6 +2463,59 @@ Rectangle {
2435 compare(topmostSurfaceItem.touchReleaseCount, 2);
2463 compare(topmostSurfaceItem.touchReleaseCount, 2);
2436 }
2464 }
2437
2465
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+
2438 function test_greeterModeBroadcastsApp() {
2519 function test_greeterModeBroadcastsApp() {
2439 setLightDMMockMode("single-pin");
2520 setLightDMMockMode("single-pin");
2440 shellLoader.mode = "greeter";
2521 shellLoader.mode = "greeter";