on xenial help sign in
merge lp:~dandrader/unity8/cursorConfinement
15dc8dbLukáš Tinkl
CMakeLists.txt+1−1
@@ -57,7 +57,7 @@ find_package(Qt5DBus 5.4 REQUIRED)
57find_package(Qt5Concurrent 5.4 REQUIRED)
57find_package(Qt5Concurrent 5.4 REQUIRED)
58find_package(Qt5Sql 5.4 REQUIRED)
58find_package(Qt5Sql 5.4 REQUIRED)
59
59
60−pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=20)
60+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=21)
61pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
61pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
62pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
62pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
63pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
63pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
data/unity8-greeter-wrapper+11−3
@@ -1,8 +1,7 @@
1#!/bin/sh
1#!/bin/sh
2# -*- Mode: sh; indent-tabs-mode: nil; tab-width: 4 -*-
2# -*- Mode: sh; indent-tabs-mode: nil; tab-width: 4 -*-
3#
3#
4−# Copyright (C) 2011,2013 Canonical Ltd
4+# Copyright (C) 2011,2013,2016 Canonical Ltd
5−# Author: Michael Terry <michael.terry@canonical.com>
6#
5#
7# This program is free software: you can redistribute it and/or modify it under
6# This program is free software: you can redistribute it and/or modify it under
8# the terms of the GNU General Public License as published by the Free Software
7# the terms of the GNU General Public License as published by the Free Software
@@ -42,6 +41,8 @@ set_greeter_var()
42SUB_SOCKET=$XDG_RUNTIME_DIR/mir_socket
41SUB_SOCKET=$XDG_RUNTIME_DIR/mir_socket
43rm -f $SUB_SOCKET # clear socket in case we were hard shut down
42rm -f $SUB_SOCKET # clear socket in case we were hard shut down
44
43
44+echo "DBUS_SESSION_BUS_ADDRESS=${DBUS_SESSION_BUS_ADDRESS}" >"$XDG_RUNTIME_DIR/dbus-session"
45+
45# If touch session script (which sets up grid units and mir variables) is available, use it
46# If touch session script (which sets up grid units and mir variables) is available, use it
46TOUCH_WRAPPER=
47TOUCH_WRAPPER=
47if [ -x /usr/bin/ubuntu-touch-session ]; then
48if [ -x /usr/bin/ubuntu-touch-session ]; then
@@ -65,7 +66,14 @@ set_greeter_var PA_DISABLED 1
65MIR_SOCKET=$SUB_SOCKET $TOUCH_WRAPPER /sbin/upstart --user --no-startup-event &
66MIR_SOCKET=$SUB_SOCKET $TOUCH_WRAPPER /sbin/upstart --user --no-startup-event &
66INIT_PID=$!
67INIT_PID=$!
67while [ ! -e "$XDG_RUNTIME_DIR/upstart/sessions/$INIT_PID.session" ]; do sleep 0.1; done
68while [ ! -e "$XDG_RUNTIME_DIR/upstart/sessions/$INIT_PID.session" ]; do sleep 0.1; done
68−export UPSTART_SESSION=$(/sbin/initctl list-sessions | grep "^$INIT_PID " | cut -d' ' -f2)
69+. "$XDG_RUNTIME_DIR/upstart/sessions/$INIT_PID.session"
70+export UPSTART_SESSION
71+
72+# Connect upstart to system, but do first command in a loop to avoid any
73+# race condition between the session file above being written and upstart being
74+# ready to handle requests.
75+while ! /sbin/initctl notify-dbus-address "$DBUS_SESSION_BUS_ADDRESS"; do sleep 0.1; done
76+/sbin/initctl start notify-cgmanager
69
77
70# Start any pre-greeter tasks if needed (like wizard)
78# Start any pre-greeter tasks if needed (like wizard)
71/sbin/initctl emit unity8-greeter-starting
79/sbin/initctl emit unity8-greeter-starting
debian/control+8−4
@@ -11,9 +11,9 @@ Build-Depends: cmake,
11# to g++ so we don't try to run arm g++
11# to g++ so we don't try to run arm g++
12# on an x86 CPU for example, when cross-compiling.
12# on an x86 CPU for example, when cross-compiling.
13 g++:native,
13 g++:native,
14− libandroid-properties-dev,
15 graphviz,
14 graphviz,
16 gsettings-ubuntu-schemas (>= 0.0.2+14.10.20140815),
15 gsettings-ubuntu-schemas (>= 0.0.2+14.10.20140815),
16+ libandroid-properties-dev,
17 libconnectivity-qt1-dev (>= 0.7.1),
17 libconnectivity-qt1-dev (>= 0.7.1),
18 libevdev-dev,
18 libevdev-dev,
19 libgeonames-dev (>= 0.2),
19 libgeonames-dev (>= 0.2),
@@ -32,10 +32,12 @@ Build-Depends: cmake,
32 libqt5svg5-dev,
32 libqt5svg5-dev,
33 libqt5xmlpatterns5-dev,
33 libqt5xmlpatterns5-dev,
34 libsystemsettings-dev,
34 libsystemsettings-dev,
35+ libubuntu-download-manager-common-dev,
35 libubuntugestures5-dev (>= 1.3.2030),
36 libubuntugestures5-dev (>= 1.3.2030),
36 libubuntugestures5-private-dev (>= 1.3.2030),
37 libubuntugestures5-private-dev (>= 1.3.2030),
37 libudev-dev,
38 libudev-dev,
39+ libudm-common-dev,
38− libunity-api-dev (>= 7.117),
40+ libunity-api-dev (>= 7.118),
39 libusermetricsoutput1-dev,
41 libusermetricsoutput1-dev,
40# Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
42# Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
41 libx11-dev[!arm64 !armhf],
43 libx11-dev[!arm64 !armhf],
@@ -133,6 +135,7 @@ Depends: dmz-cursor-theme,
133 ${shlibs:Depends},
135 ${shlibs:Depends},
134Recommends: indicator-keyboard,
136Recommends: indicator-keyboard,
135 indicator-session,
137 indicator-session,
138+ unity-greeter-session-broadcast,
136 unity-scope-click,
139 unity-scope-click,
137 unity-scope-mediascanner2,
140 unity-scope-mediascanner2,
138Breaks: indicator-network (<< 0.5.1+14.10.20141014),
141Breaks: indicator-network (<< 0.5.1+14.10.20141014),
@@ -153,7 +156,8 @@ Depends: qml-module-qtquick-layouts,
153 qtdeclarative5-ubuntu-settings-components (>= 0.7),
156 qtdeclarative5-ubuntu-settings-components (>= 0.7),
154 qtdeclarative5-unity-notifications-plugin (>= 0.1.2) | unity-notifications-impl,
157 qtdeclarative5-unity-notifications-plugin (>= 0.1.2) | unity-notifications-impl,
155 ubuntu-thumbnailer-impl-0,
158 ubuntu-thumbnailer-impl-0,
159+ ubuntu-wallpapers,
156− unity-application-impl-20,
160+ unity-application-impl-21,
157 unity-notifications-impl-3,
161 unity-notifications-impl-3,
158 unity-plugin-scopes | unity-scopes-impl,
162 unity-plugin-scopes | unity-scopes-impl,
159 unity-scopes-impl-12,
163 unity-scopes-impl-12,
@@ -199,7 +203,7 @@ Pre-Depends: ${misc:Pre-Depends},
199Depends: ${misc:Depends},
203Depends: ${misc:Depends},
200 ${shlibs:Depends},
204 ${shlibs:Depends},
201Provides: unity-application-impl,
205Provides: unity-application-impl,
202− unity-application-impl-20,
206+ unity-application-impl-21,
203Replaces: unity8-autopilot (<< 8.02+15.04.20150422-0ubuntu1)
207Replaces: unity8-autopilot (<< 8.02+15.04.20150422-0ubuntu1)
204Description: Fake environment for running Unity 8 shell
208Description: Fake environment for running Unity 8 shell
205 Provides fake implementations of some QML modules used by Unity 8 shell
209 Provides fake implementations of some QML modules used by Unity 8 shell
debian/rules+3−2
@@ -37,6 +37,7 @@ override_dh_installdeb:
37override_dh_makeshlibs:
37override_dh_makeshlibs:
38 dh_makeshlibs -Nunity8-private -Nunity8-fake-env
38 dh_makeshlibs -Nunity8-private -Nunity8-fake-env
39
39
40−# libMockLightDM-qml.so links against liblightdm-qt5-3.so which doesn't exist
41override_dh_shlibdeps:
40override_dh_shlibdeps:
41+ # Some mock libraries link against liblightdm-qt5-3.so which we want to
42+ # avoid, since we only really link against our mock one, not the system one.
42− dh_shlibdeps -XlibMockLightDM-qml.so
43+ dh_shlibdeps -XlibMockLightDM-qml.so -XlibMockAccountsService-qml.so
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/Cursor/MousePointer.cpp+42−2
@@ -49,8 +49,8 @@ void MousePointer::handleMouseEvent(ulong timestamp, QPointF movement, Qt::Mouse
49 QPointF appliedMovement(int(m_accumulatedMovement.x()), int(m_accumulatedMovement.y()));
49 QPointF appliedMovement(int(m_accumulatedMovement.x()), int(m_accumulatedMovement.y()));
50 m_accumulatedMovement -= appliedMovement;
50 m_accumulatedMovement -= appliedMovement;
51
51
52− const qreal newX = x() + appliedMovement.x();
52+ qreal newX = x() + appliedMovement.x();
53− const qreal newY = y() + appliedMovement.y();
53+ qreal newY = y() + appliedMovement.y();
54 const qreal sceneWidth = parentItem()->width();
54 const qreal sceneWidth = parentItem()->width();
55 const qreal sceneHeight = parentItem()->height();
55 const qreal sceneHeight = parentItem()->height();
56
56
@@ -86,6 +86,8 @@ void MousePointer::handleMouseEvent(ulong timestamp, QPointF movement, Qt::Mouse
86 }
86 }
87 }
87 }
88
88
89+ applyItemConfinement(newX, newY);
90+
89 setX(qBound(0.0, newX, sceneWidth - 1));
91 setX(qBound(0.0, newX, sceneWidth - 1));
90 setY(qBound(0.0, newY, sceneHeight - 1));
92 setY(qBound(0.0, newY, sceneHeight - 1));
91
93
@@ -94,6 +96,31 @@ void MousePointer::handleMouseEvent(ulong timestamp, QPointF movement, Qt::Mouse
94 buttons, modifiers);
96 buttons, modifiers);
95}
97}
96
98
99+void MousePointer::applyItemConfinement(qreal &newX, qreal &newY)
100+{
101+ Q_ASSERT(parentItem() != nullptr);
102+
103+ if (m_confiningItem.isNull()) {
104+ return;
105+ }
106+
107+ QRectF confiningItemGeometry(0, 0, m_confiningItem->width(), m_confiningItem->height());
108+
109+ QRectF confiningRect = m_confiningItem->mapRectToItem(parentItem(), confiningItemGeometry);
110+
111+ if (newX < confiningRect.x()) {
112+ newX = confiningRect.x();
113+ } else if (newX > confiningRect.right()) {
114+ newX = confiningRect.right();
115+ }
116+
117+ if (newY < confiningRect.y()) {
118+ newY = confiningRect.y();
119+ } else if (newY > confiningRect.bottom()) {
120+ newY = confiningRect.bottom();
121+ }
122+}
123+
97void MousePointer::handleWheelEvent(ulong timestamp, QPoint angleDelta, Qt::KeyboardModifiers modifiers)
124void MousePointer::handleWheelEvent(ulong timestamp, QPoint angleDelta, Qt::KeyboardModifiers modifiers)
98{
125{
99 if (!parentItem()) {
126 if (!parentItem()) {
@@ -193,3 +220,16 @@ void MousePointer::setCustomCursor(const QCursor &customCursor)
193{
220{
194 CursorImageProvider::instance()->setCustomCursor(customCursor);
221 CursorImageProvider::instance()->setCustomCursor(customCursor);
195}
222}
223+
224+QQuickItem* MousePointer::confiningItem() const
225+{
226+ return m_confiningItem.data();
227+}
228+
229+void MousePointer::setConfiningItem(QQuickItem *item)
230+{
231+ if (item != m_confiningItem) {
232+ m_confiningItem = item;
233+ Q_EMIT confiningItemChanged();
234+ }
235+}
plugins/Cursor/MousePointer.h+8−1
@@ -27,8 +27,8 @@
27
27
28class MousePointer : public MirMousePointerInterface {
28class MousePointer : public MirMousePointerInterface {
29 Q_OBJECT
29 Q_OBJECT
30+ Q_PROPERTY(QQuickItem* confiningItem READ confiningItem WRITE setConfiningItem NOTIFY confiningItemChanged)
30 Q_PROPERTY(int topBoundaryOffset READ topBoundaryOffset WRITE setTopBoundaryOffset NOTIFY topBoundaryOffsetChanged)
31 Q_PROPERTY(int topBoundaryOffset READ topBoundaryOffset WRITE setTopBoundaryOffset NOTIFY topBoundaryOffsetChanged)
31−
32public:
32public:
33 MousePointer(QQuickItem *parent = nullptr);
33 MousePointer(QQuickItem *parent = nullptr);
34
34
@@ -40,6 +40,9 @@ public:
40
40
41 void setCustomCursor(const QCursor &) override;
41 void setCustomCursor(const QCursor &) override;
42
42
43+ QQuickItem* confiningItem() const;
44+ void setConfiningItem(QQuickItem*);
45+
43 int topBoundaryOffset() const;
46 int topBoundaryOffset() const;
44 void setTopBoundaryOffset(int topBoundaryOffset);
47 void setTopBoundaryOffset(int topBoundaryOffset);
45
48
@@ -58,6 +61,7 @@ Q_SIGNALS:
58 void pushedBottomRightCorner(qreal amount, Qt::MouseButtons buttons);
61 void pushedBottomRightCorner(qreal amount, Qt::MouseButtons buttons);
59 void pushStopped();
62 void pushStopped();
60 void mouseMoved();
63 void mouseMoved();
64+ void confiningItemChanged();
61
65
62 void topBoundaryOffsetChanged(int topBoundaryOffset);
66 void topBoundaryOffsetChanged(int topBoundaryOffset);
63
67
@@ -69,6 +73,7 @@ private Q_SLOTS:
69
73
70private:
74private:
71 void registerWindow(QWindow *window);
75 void registerWindow(QWindow *window);
76+ void applyItemConfinement(qreal &newX, qreal &newY);
72
77
73 QPointer<QWindow> m_registeredWindow;
78 QPointer<QWindow> m_registeredWindow;
74 QPointer<QScreen> m_registeredScreen;
79 QPointer<QScreen> m_registeredScreen;
@@ -78,6 +83,8 @@ private:
78 // Accumulated, unapplied, mouse movement.
83 // Accumulated, unapplied, mouse movement.
79 QPointF m_accumulatedMovement;
84 QPointF m_accumulatedMovement;
80
85
86+ QPointer<QQuickItem> m_confiningItem;
87+
81 int m_topBoundaryOffset{0};
88 int m_topBoundaryOffset{0};
82 bool m_pushing{false};
89 bool m_pushing{false};
83};
90};
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
plugins/LightDM/IntegratedLightDM/liblightdm/Greeter.cpp−5
@@ -78,11 +78,6 @@ bool Greeter::showRemoteLoginHint() const
78 return true;
78 return true;
79}
79}
80
80
81−bool Greeter::lockHint () const
82−{
83− return false;
84−}
85−
86QString Greeter::selectUserHint() const
81QString Greeter::selectUserHint() const
87{
82{
88 return QLatin1String("");
83 return QLatin1String("");
plugins/LightDM/IntegratedLightDM/liblightdm/Greeter.h−2
@@ -45,7 +45,6 @@ class Q_DECL_EXPORT Greeter : public QObject
45
45
46 Q_PROPERTY(QString hostname READ hostname CONSTANT)
46 Q_PROPERTY(QString hostname READ hostname CONSTANT)
47 Q_PROPERTY(bool hasGuestAccount READ hasGuestAccountHint CONSTANT)
47 Q_PROPERTY(bool hasGuestAccount READ hasGuestAccountHint CONSTANT)
48− Q_PROPERTY(bool locked READ lockHint CONSTANT)
49
48
50 Q_ENUMS(PromptType MessageType)
49 Q_ENUMS(PromptType MessageType)
51
50
@@ -68,7 +67,6 @@ public:
68 bool hideUsersHint() const;
67 bool hideUsersHint() const;
69 bool showManualLoginHint() const;
68 bool showManualLoginHint() const;
70 bool showRemoteLoginHint() const;
69 bool showRemoteLoginHint() const;
71− bool lockHint () const;
72 bool hasGuestAccountHint() const;
70 bool hasGuestAccountHint() const;
73 QString selectUserHint() const;
71 QString selectUserHint() const;
74 bool selectGuestHint() const;
72 bool selectGuestHint() const;
plugins/LightDM/SessionsModel.cpp+22−16
@@ -47,15 +47,15 @@ void SessionsModel::setIconSearchDirectories(const QList<QUrl> searchDirectories
47 Q_EMIT iconSearchDirectoriesChanged();
47 Q_EMIT iconSearchDirectoriesChanged();
48}
48}
49
49
50−QUrl SessionsModel::iconUrl(const QString sessionName) const
50+QUrl SessionsModel::iconUrl(const QString sessionKey) const
51{
51{
52 Q_FOREACH(const QUrl& searchDirectory, m_iconSearchDirectories)
52 Q_FOREACH(const QUrl& searchDirectory, m_iconSearchDirectories)
53 {
53 {
54 // This is an established icon naming convention
54 // This is an established icon naming convention
55 QString customIconUrl = searchDirectory.toString(QUrl::StripTrailingSlash) +
55 QString customIconUrl = searchDirectory.toString(QUrl::StripTrailingSlash) +
56− "/custom_" + sessionName + "_badge.png";
56+ "/custom_" + sessionKey + "_badge.png";
57 QString iconUrl = searchDirectory.toString(QUrl::StripTrailingSlash) +
57 QString iconUrl = searchDirectory.toString(QUrl::StripTrailingSlash) +
58− "/" + sessionName + "_badge.png";
58+ "/" + sessionKey + "_badge.png";
59
59
60 QFile customIconFile(customIconUrl);
60 QFile customIconFile(customIconUrl);
61 QFile iconFile(iconUrl);
61 QFile iconFile(iconUrl);
@@ -63,29 +63,35 @@ QUrl SessionsModel::iconUrl(const QString sessionName) const
63 return QUrl(customIconUrl);
63 return QUrl(customIconUrl);
64 } else if (iconFile.exists()) {
64 } else if (iconFile.exists()) {
65 return QUrl(iconUrl);
65 return QUrl(iconUrl);
66− } else{
66+ } else {
67 // Search the legacy way
67 // Search the legacy way
68 QString path = searchDirectory.toString(QUrl::StripTrailingSlash) + "/";
68 QString path = searchDirectory.toString(QUrl::StripTrailingSlash) + "/";
69+ bool iconFound = false;
69− if (sessionName == "ubuntu" || sessionName == "ubuntu-2d") {
70+ if (sessionKey == "ubuntu" || sessionKey == "ubuntu-2d") {
70 path += "ubuntu_badge.png";
71 path += "ubuntu_badge.png";
72+ iconFound = true;
71 } else if(
73 } else if(
72− sessionName == "gnome-classic" ||
74+ sessionKey == "gnome-classic" ||
73− sessionName == "gnome-flashback-compiz" ||
75+ sessionKey == "gnome-flashback-compiz" ||
74− sessionName == "gnome-flashback-metacity" ||
76+ sessionKey == "gnome-flashback-metacity" ||
75− sessionName == "gnome-shell" ||
77+ sessionKey == "gnome-shell" ||
76− sessionName == "gnome-wayland" ||
78+ sessionKey == "gnome-wayland" ||
77− sessionName == "gnome"
79+ sessionKey == "gnome"
78 ){
80 ){
79 path += "gnome_badge.png";
81 path += "gnome_badge.png";
82+ iconFound = true;
80− } else if (sessionName == "plasma") {
83+ } else if (sessionKey == "plasma") {
81 path += "kde_badge.png";
84 path += "kde_badge.png";
85+ iconFound = true;
82− } else if (sessionName == "xterm") {
86+ } else if (sessionKey == "xterm") {
83 path += "recovery_console_badge.png";
87 path += "recovery_console_badge.png";
88+ iconFound = true;
84− } else if (sessionName == "remote-login") {
89+ } else if (sessionKey == "remote-login") {
85 path += "remote_login_help.png";
90 path += "remote_login_help.png";
91+ iconFound = true;
86 }
92 }
87
93
88− if (QFile(path).exists()) {
94+ if (QFile(path).exists() && iconFound) {
89 return path;
95 return path;
90 }
96 }
91 }
97 }
@@ -99,7 +105,7 @@ QVariant SessionsModel::data(const QModelIndex& index, int role) const
99{
105{
100 switch (role) {
106 switch (role) {
101 case SessionsModel::IconRole:
107 case SessionsModel::IconRole:
102− return iconUrl(m_model->data(index, Qt::DisplayRole).toString());
108+ return iconUrl(m_model->data(index, QLightDM::SessionsModel::KeyRole).toString());
103 default:
109 default:
104 return m_model->data(index, role);
110 return m_model->data(index, role);
105 }
111 }
plugins/LightDM/plugin.cpp+1−1
@@ -82,7 +82,7 @@ void PLUGIN_CLASSNAME::registerTypes(const char *uri)
82 qRegisterMetaType<QLightDM::Greeter::PromptType>("QLightDM::Greeter::PromptType");
82 qRegisterMetaType<QLightDM::Greeter::PromptType>("QLightDM::Greeter::PromptType");
83
83
84 qmlRegisterSingletonType<SessionsModel>(uri, 0, 1, "Sessions", sessions_provider);
84 qmlRegisterSingletonType<SessionsModel>(uri, 0, 1, "Sessions", sessions_provider);
85− qmlRegisterUncreatableType<SessionsModel>(uri, 0, 1, "SessionRoles", QStringLiteral("Type is not instantiable"));
85+ qmlRegisterUncreatableType<QLightDM::SessionsModel>(uri, 0, 1, "SessionRoles", QStringLiteral("Type is not instantiable"));
86
86
87 qmlRegisterSingletonType<UsersModel>(uri, 0, 1, "Users", users_provider);
87 qmlRegisterSingletonType<UsersModel>(uri, 0, 1, "Users", users_provider);
88 qmlRegisterUncreatableType<QLightDM::UsersModel>(uri, 0, 1, "UserRoles", QStringLiteral("Type is not instantiable"));
88 qmlRegisterUncreatableType<QLightDM::UsersModel>(uri, 0, 1, "UserRoles", QStringLiteral("Type is not instantiable"));
plugins/SessionBroadcast/SessionBroadcast.cpp+35−9
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013,2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -12,12 +12,12 @@
12 *
12 *
13 * You should have received a copy of the GNU General Public License
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/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Author: Michael Terry <michael.terry@canonical.com>
17 */
15 */
18
16
19#include "SessionBroadcast.h"
17#include "SessionBroadcast.h"
20#include <QDBusConnection>
18#include <QDBusConnection>
19+#include <QDBusConnectionInterface>
20+#include <QDBusInterface>
21
21
22#include <glib.h>
22#include <glib.h>
23
23
@@ -27,13 +27,39 @@ SessionBroadcast::SessionBroadcast(QObject* parent)
27 m_username = QString::fromUtf8(g_get_user_name());
27 m_username = QString::fromUtf8(g_get_user_name());
28
28
29 auto connection = QDBusConnection::SM_BUSNAME();
29 auto connection = QDBusConnection::SM_BUSNAME();
30+ auto interface = connection.interface();
31+ interface->startService(QStringLiteral("com.canonical.Unity.Greeter.Broadcast"));
32+
33+ m_broadcaster = new QDBusInterface(QStringLiteral("com.canonical.Unity.Greeter.Broadcast"),
34+ QStringLiteral("/com/canonical/Unity/Greeter/Broadcast"),
35+ QStringLiteral("com.canonical.Unity.Greeter.Broadcast"),
36+ connection, this);
37+
38+ connect(m_broadcaster, SIGNAL(StartUrl(const QString &, const QString &)),
39+ this, SLOT(onStartUrl(const QString &, const QString &)));
40+
41+ connect(m_broadcaster, SIGNAL(ShowHome(const QString &)),
42+ this, SLOT(onShowHome(const QString &)));
43+}
44+
45+void SessionBroadcast::requestUrlStart(const QString &username, const QString &url)
46+{
47+ m_broadcaster->asyncCall(QStringLiteral("RequestUrlStart"), username, url);
48+}
49+
50+void SessionBroadcast::requestHomeShown(const QString &username)
51+{
52+ m_broadcaster->asyncCall(QStringLiteral("RequestHomeShown"), username);
53+}
30
54
31− connection.connect(QStringLiteral("com.canonical.Unity.Greeter.Broadcast"),
55+void SessionBroadcast::onStartUrl(const QString &username, const QString &url)
32− QStringLiteral("/com/canonical/Unity/Greeter/Broadcast"),
56+{
33− QStringLiteral("com.canonical.Unity.Greeter.Broadcast"),
57+ // Since this signal is just used for testing, we don't *really* care if
34− QStringLiteral("ShowHome"),
58+ // username matches, but just in case we do eventually use the signal, we
35− this,
59+ // should only listen to our own requests.
36− SLOT(onShowHome(const QString &)));
60+ if (username == m_username) {
61+ Q_EMIT startUrl(url);
62+ }
37}
63}
38
64
39void SessionBroadcast::onShowHome(const QString &username)
65void SessionBroadcast::onShowHome(const QString &username)
plugins/SessionBroadcast/SessionBroadcast.h+10−3
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013,2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -12,8 +12,6 @@
12 *
12 *
13 * You should have received a copy of the GNU General Public License
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/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Authors: Michael Terry <michael.terry@canonical.com>
17 */
15 */
18
16
19#ifndef UNITY_SESSIONBROADCAST_H
17#ifndef UNITY_SESSIONBROADCAST_H
@@ -31,14 +29,23 @@ class SessionBroadcast: public QObject
31public:
29public:
32 explicit SessionBroadcast(QObject *parent = 0);
30 explicit SessionBroadcast(QObject *parent = 0);
33
31
32+ Q_INVOKABLE void requestUrlStart(const QString &username, const QString &url);
33+ Q_INVOKABLE void requestHomeShown(const QString &username);
34+
34Q_SIGNALS:
35Q_SIGNALS:
36+ // This signal isn't actually used by the shell
37+ // (unity-greeter-session-broadcast handles launching an app for us), but
38+ // it's useful for testing the plugin.
39+ void startUrl(const QString &url);
35 void showHome();
40 void showHome();
36
41
37private Q_SLOTS:
42private Q_SLOTS:
43+ void onStartUrl(const QString &username, const QString &url);
38 void onShowHome(const QString &username);
44 void onShowHome(const QString &username);
39
45
40private:
46private:
41 QString m_username;
47 QString m_username;
48+ QDBusInterface *m_broadcaster;
42};
49};
43
50
44#endif
51#endif
plugins/Ubuntu/DownloadDaemonListener/CMakeLists.txt+3−1
@@ -12,5 +12,7 @@ set(DOWNLOADDAEMONLISTENER_SOURCES
12add_library(DownloadDaemonListener MODULE ${DOWNLOADDAEMONLISTENER_SOURCES})
12add_library(DownloadDaemonListener MODULE ${DOWNLOADDAEMONLISTENER_SOURCES})
13
13
14qt5_use_modules(DownloadDaemonListener Qml Quick DBus Core)
14qt5_use_modules(DownloadDaemonListener Qml Quick DBus Core)
15−
15+pkg_check_modules(DDLADAPTER REQUIRED udm-common)
16+add_definitions(${DDLADAPTER_CFLAGS} ${DDLADAPTER_CFLAGS_OTHER})
17+target_link_libraries(DownloadDaemonListener ${DDLADAPTER_LIBRARIES})
16add_unity8_plugin(Ubuntu.DownloadDaemonListener 0.1 Ubuntu/DownloadDaemonListener TARGETS DownloadDaemonListener)
18add_unity8_plugin(Ubuntu.DownloadDaemonListener 0.1 Ubuntu/DownloadDaemonListener TARGETS DownloadDaemonListener)
plugins/Ubuntu/DownloadDaemonListener/DownloadTracker.cpp+2−1
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 - Canonical Ltd.
2+ * Copyright (C) 2013-2016 Canonical Ltd.
3 *
3 *
4 * This program is free software: you can redistribute it and/or modify it
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License, as
5 * under the terms of the GNU Lesser General Public License, as
@@ -78,6 +78,7 @@ void DownloadTracker::startService()
78 connect(m_adaptor, static_cast<void (DownloadTrackerAdaptor::*)(qulonglong, qulonglong)>(&DownloadTrackerAdaptor::progress), this, &DownloadTracker::progress);
78 connect(m_adaptor, static_cast<void (DownloadTrackerAdaptor::*)(qulonglong, qulonglong)>(&DownloadTrackerAdaptor::progress), this, &DownloadTracker::progress);
79 connect(m_adaptor, &DownloadTrackerAdaptor::resumed, this, &DownloadTracker::resumed);
79 connect(m_adaptor, &DownloadTrackerAdaptor::resumed, this, &DownloadTracker::resumed);
80 connect(m_adaptor, &DownloadTrackerAdaptor::started, this, &DownloadTracker::started);
80 connect(m_adaptor, &DownloadTrackerAdaptor::started, this, &DownloadTracker::started);
81+ connect(m_adaptor, &DownloadTrackerAdaptor::processing, this, &DownloadTracker::processing);
81 }
82 }
82 // FIXME find a better way of determining if the service is ready
83 // FIXME find a better way of determining if the service is ready
83 Q_EMIT serviceReadyChanged(m_adaptor && m_adaptor->isValid());
84 Q_EMIT serviceReadyChanged(m_adaptor && m_adaptor->isValid());
plugins/Ubuntu/DownloadDaemonListener/DownloadTracker.h+2−1
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 - Canonical Ltd.
2+ * Copyright (C) 2013-2016 Canonical Ltd.
3 *
3 *
4 * This program is free software: you can redistribute it and/or modify it
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License, as
5 * under the terms of the GNU Lesser General Public License, as
@@ -54,6 +54,7 @@ Q_SIGNALS:
54 void error(const QString &error);
54 void error(const QString &error);
55 void finished(const QString &path);
55 void finished(const QString &path);
56 void paused(bool success);
56 void paused(bool success);
57+ void processing(const QString &path);
57 void progress(qulonglong received, qulonglong total);
58 void progress(qulonglong received, qulonglong total);
58 void resumed(bool success);
59 void resumed(bool success);
59 void started(bool success);
60 void started(bool success);
plugins/Ubuntu/DownloadDaemonListener/interface/downloadtrackeradaptor.cpp+2−2
@@ -1,8 +1,8 @@
1/*
1/*
2 * This file was generated by qdbusxml2cpp version 0.8
2 * This file was generated by qdbusxml2cpp version 0.8
3− * Command line was: qdbusxml2cpp -v -c DownloadTrackerAdaptor -p downloadtrackeradaptor.h:downloadtrackeradaptor.cpp -i metatypes.h com.canonical.applications.download.xml
3+ * Command line was: qdbusxml2cpp -v -c DownloadTrackerAdaptor -p downloadtrackeradaptor.h:downloadtrackeradaptor.cpp -i metatypes.h ../../../../../../ubuntu-download-manager/trunk/docs/dbus/com.canonical.applications.download.xml
4 *
4 *
5− * qdbusxml2cpp is Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
5+ * qdbusxml2cpp is Copyright (C) 2015 The Qt Company Ltd.
6 *
6 *
7 * This is an auto-generated file.
7 * This is an auto-generated file.
8 * This file may have been hand-edited. Look for HAND-EDIT comments
8 * This file may have been hand-edited. Look for HAND-EDIT comments
plugins/Ubuntu/DownloadDaemonListener/interface/downloadtrackeradaptor.h+71−4
@@ -1,15 +1,15 @@
1/*
1/*
2 * This file was generated by qdbusxml2cpp version 0.8
2 * This file was generated by qdbusxml2cpp version 0.8
3− * Command line was: qdbusxml2cpp -v -c DownloadTrackerAdaptor -p downloadtrackeradaptor.h:downloadtrackeradaptor.cpp -i metatypes.h com.canonical.applications.download.xml
3+ * Command line was: qdbusxml2cpp -v -c DownloadTrackerAdaptor -p downloadtrackeradaptor.h:downloadtrackeradaptor.cpp -i metatypes.h ../../../../../../ubuntu-download-manager/trunk/docs/dbus/com.canonical.applications.download.xml
4 *
4 *
5− * qdbusxml2cpp is Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
5+ * qdbusxml2cpp is Copyright (C) 2015 The Qt Company Ltd.
6 *
6 *
7 * This is an auto-generated file.
7 * This is an auto-generated file.
8 * Do not edit! All changes made to it will be lost.
8 * Do not edit! All changes made to it will be lost.
9 */
9 */
10
10
11−#ifndef DOWNLOADTRACKERADAPTOR_H_1374434371
11+#ifndef DOWNLOADTRACKERADAPTOR_H
12−#define DOWNLOADTRACKERADAPTOR_H_1374434371
12+#define DOWNLOADTRACKERADAPTOR_H
13
13
14#include <QtCore/QObject>
14#include <QtCore/QObject>
15#include <QtCore/QByteArray>
15#include <QtCore/QByteArray>
@@ -36,6 +36,22 @@ public:
36
36
37 ~DownloadTrackerAdaptor();
37 ~DownloadTrackerAdaptor();
38
38
39+ Q_PROPERTY(QString ClickPackage READ clickPackage)
40+ inline QString clickPackage() const
41+ { return qvariant_cast< QString >(property("ClickPackage")); }
42+
43+ Q_PROPERTY(QString DestinationApp READ destinationApp)
44+ inline QString destinationApp() const
45+ { return qvariant_cast< QString >(property("DestinationApp")); }
46+
47+ Q_PROPERTY(bool ShowInIndicator READ showInIndicator)
48+ inline bool showInIndicator() const
49+ { return qvariant_cast< bool >(property("ShowInIndicator")); }
50+
51+ Q_PROPERTY(QString Title READ title)
52+ inline QString title() const
53+ { return qvariant_cast< QString >(property("Title")); }
54+
39public Q_SLOTS: // METHODS
55public Q_SLOTS: // METHODS
40 inline QDBusPendingReply<> allowGSMDownload(bool allowed)
56 inline QDBusPendingReply<> allowGSMDownload(bool allowed)
41 {
57 {
@@ -50,6 +66,24 @@ public Q_SLOTS: // METHODS
50 return asyncCallWithArgumentList(QStringLiteral("cancel"), argumentList);
66 return asyncCallWithArgumentList(QStringLiteral("cancel"), argumentList);
51 }
67 }
52
68
69+ inline QDBusPendingReply<> collected()
70+ {
71+ QList<QVariant> argumentList;
72+ return asyncCallWithArgumentList(QStringLiteral("collected"), argumentList);
73+ }
74+
75+ inline QDBusPendingReply<QString> filePath()
76+ {
77+ QList<QVariant> argumentList;
78+ return asyncCallWithArgumentList(QStringLiteral("filePath"), argumentList);
79+ }
80+
81+ inline QDBusPendingReply<StringMap> headers()
82+ {
83+ QList<QVariant> argumentList;
84+ return asyncCallWithArgumentList(QStringLiteral("headers"), argumentList);
85+ }
86+
53 inline QDBusPendingReply<bool> isGSMDownloadAllowed()
87 inline QDBusPendingReply<bool> isGSMDownloadAllowed()
54 {
88 {
55 QList<QVariant> argumentList;
89 QList<QVariant> argumentList;
@@ -80,6 +114,27 @@ public Q_SLOTS: // METHODS
80 return asyncCallWithArgumentList(QStringLiteral("resume"), argumentList);
114 return asyncCallWithArgumentList(QStringLiteral("resume"), argumentList);
81 }
115 }
82
116
117+ inline QDBusPendingReply<> setDestinationDir(const QString &path)
118+ {
119+ QList<QVariant> argumentList;
120+ argumentList << QVariant::fromValue(path);
121+ return asyncCallWithArgumentList(QStringLiteral("setDestinationDir"), argumentList);
122+ }
123+
124+ inline QDBusPendingReply<> setHeaders(StringMap headers)
125+ {
126+ QList<QVariant> argumentList;
127+ argumentList << QVariant::fromValue(headers);
128+ return asyncCallWithArgumentList(QStringLiteral("setHeaders"), argumentList);
129+ }
130+
131+ inline QDBusPendingReply<> setMetadata(const QVariantMap &data)
132+ {
133+ QList<QVariant> argumentList;
134+ argumentList << QVariant::fromValue(data);
135+ return asyncCallWithArgumentList(QStringLiteral("setMetadata"), argumentList);
136+ }
137+
83 inline QDBusPendingReply<> setThrottle(qulonglong speed)
138 inline QDBusPendingReply<> setThrottle(qulonglong speed)
84 {
139 {
85 QList<QVariant> argumentList;
140 QList<QVariant> argumentList;
@@ -93,6 +148,12 @@ public Q_SLOTS: // METHODS
93 return asyncCallWithArgumentList(QStringLiteral("start"), argumentList);
148 return asyncCallWithArgumentList(QStringLiteral("start"), argumentList);
94 }
149 }
95
150
151+ inline QDBusPendingReply<int> state()
152+ {
153+ QList<QVariant> argumentList;
154+ return asyncCallWithArgumentList(QStringLiteral("state"), argumentList);
155+ }
156+
96 inline QDBusPendingReply<qulonglong> throttle()
157 inline QDBusPendingReply<qulonglong> throttle()
97 {
158 {
98 QList<QVariant> argumentList;
159 QList<QVariant> argumentList;
@@ -106,10 +167,16 @@ public Q_SLOTS: // METHODS
106 }
167 }
107
168
108Q_SIGNALS: // SIGNALS
169Q_SIGNALS: // SIGNALS
170+ void authError(AuthErrorStruct error);
109 void canceled(bool success);
171 void canceled(bool success);
110 void error(const QString &error);
172 void error(const QString &error);
111 void finished(const QString &path);
173 void finished(const QString &path);
174+ void hashError(HashErrorStruct error);
175+ void httpError(HttpErrorStruct error);
176+ void networkError(NetworkErrorStruct error);
112 void paused(bool success);
177 void paused(bool success);
178+ void processError(ProcessErrorStruct error);
179+ void processing(const QString &path);
113 void progress(qulonglong received, qulonglong total);
180 void progress(qulonglong received, qulonglong total);
114 void resumed(bool success);
181 void resumed(bool success);
115 void started(bool success);
182 void started(bool success);
plugins/Ubuntu/DownloadDaemonListener/interface/metatypes.h+3−6
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright 2013 2013 Canonical Ltd.
2+ * Copyright 2013-2016 Canonical Ltd.
3 *
3 *
4 * This library is free software; you can redistribute it and/or
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of version 3 of the GNU Lesser General Public
5 * modify it under the terms of version 3 of the GNU Lesser General Public
@@ -19,10 +19,7 @@
19#ifndef DOWNLOADER_LIB_APP_METATYPES_H
19#ifndef DOWNLOADER_LIB_APP_METATYPES_H
20#define DOWNLOADER_LIB_APP_METATYPES_H
20#define DOWNLOADER_LIB_APP_METATYPES_H
21
21
22−#include <QMap>
22+// Just include the system file so we're always up to date.
23−
23+#include <ubuntu/download_manager/metatypes.h>
24−typedef QMap<QString, QString> StringMap;
25−
26−Q_DECLARE_METATYPE(StringMap)
27
24
28#endif // METATYPES_H
25#endif // METATYPES_H
plugins/Utils/CMakeLists.txt+1
@@ -29,6 +29,7 @@ set(QMLPLUGIN_SRC
29 inputeventgenerator.cpp
29 inputeventgenerator.cpp
30 deviceconfigparser.cpp
30 deviceconfigparser.cpp
31 globalfunctions.cpp
31 globalfunctions.cpp
32+ URLDispatcher.cpp
32 plugin.cpp
33 plugin.cpp
33 )
34 )
34
35
plugins/Utils/URLDispatcher.cppnew file+77
@@ -0,0 +1,77 @@
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 "URLDispatcher.h"
18+
19+#include <QDBusConnection>
20+
21+class URLDispatcherInterface : public QObject
22+{
23+ Q_OBJECT
24+ Q_CLASSINFO("D-Bus Interface", "com.canonical.URLDispatcher")
25+
26+public:
27+ explicit URLDispatcherInterface(URLDispatcher *parent);
28+
29+ Q_SCRIPTABLE void DispatchURL(const QString &url, const QString &package);
30+};
31+
32+URLDispatcherInterface::URLDispatcherInterface(URLDispatcher *parent)
33+ : QObject(parent)
34+{
35+}
36+
37+void URLDispatcherInterface::DispatchURL(const QString &url, const QString &package)
38+{
39+ Q_UNUSED(package);
40+ Q_EMIT static_cast<URLDispatcher *>(parent())->urlRequested(url);
41+}
42+
43+URLDispatcher::URLDispatcher(QObject *parent)
44+ : QObject(parent)
45+ , m_dispatcher(nullptr)
46+{
47+}
48+
49+bool URLDispatcher::active() const
50+{
51+ return m_dispatcher != nullptr;
52+}
53+
54+void URLDispatcher::setActive(bool value)
55+{
56+ if (value == active())
57+ return;
58+
59+ QDBusConnection connection = QDBusConnection::sessionBus();
60+
61+ if (value) {
62+ URLDispatcherInterface *dispatcher = new URLDispatcherInterface(this);
63+ connection.registerObject(QStringLiteral("/com/canonical/URLDispatcher"),
64+ dispatcher,
65+ QDBusConnection::ExportScriptableContents);
66+ connection.registerService(QStringLiteral("com.canonical.URLDispatcher"));
67+ m_dispatcher = dispatcher;
68+ } else {
69+ connection.unregisterService(QStringLiteral("com.canonical.URLDispatcher"));
70+ delete m_dispatcher;
71+ m_dispatcher = nullptr;
72+ }
73+
74+ Q_EMIT activeChanged();
75+}
76+
77+#include "URLDispatcher.moc"
plugins/Utils/URLDispatcher.hnew file+47
@@ -0,0 +1,47 @@
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+#ifndef UNITY_URLDISPATCHER_H
18+#define UNITY_URLDISPATCHER_H
19+
20+#include <QObject>
21+#include <QString>
22+
23+// This class manages our url-dispatcher interception. We intercept
24+// url-dispatcher because rather than spawning the handler for the URL
25+// in our own session, we want to notify the user session to do it for us
26+// (and start an unlock in the process).
27+
28+class URLDispatcher : public QObject
29+{
30+ Q_OBJECT
31+ Q_PROPERTY(bool active READ active WRITE setActive NOTIFY activeChanged)
32+
33+public:
34+ explicit URLDispatcher(QObject *parent=0);
35+
36+ bool active() const;
37+ void setActive(bool active);
38+
39+Q_SIGNALS:
40+ void urlRequested(const QString &url);
41+ void activeChanged();
42+
43+private:
44+ QObject *m_dispatcher;
45+};
46+
47+#endif
plugins/Utils/plugin.cpp+2
@@ -38,6 +38,7 @@
38#include "inputeventgenerator.h"
38#include "inputeventgenerator.h"
39#include "deviceconfigparser.h"
39#include "deviceconfigparser.h"
40#include "globalfunctions.h"
40#include "globalfunctions.h"
41+#include "URLDispatcher.h"
41
42
42static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
43static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
43{
44{
@@ -80,4 +81,5 @@ void UtilsPlugin::registerTypes(const char *uri)
80 qmlRegisterType<InputEventGenerator>(uri, 0, 1, "InputEventGenerator");
81 qmlRegisterType<InputEventGenerator>(uri, 0, 1, "InputEventGenerator");
81 qmlRegisterType<DeviceConfigParser>(uri, 0, 1, "DeviceConfigParser");
82 qmlRegisterType<DeviceConfigParser>(uri, 0, 1, "DeviceConfigParser");
82 qmlRegisterSingletonType<GlobalFunctions>(uri, 0, 1, "Functions", createGlobalFunctions);
83 qmlRegisterSingletonType<GlobalFunctions>(uri, 0, 1, "Functions", createGlobalFunctions);
84+ qmlRegisterType<URLDispatcher>(uri, 0, 1, "URLDispatcher");
83}
85}
qml/Components/Lockscreen.qml+1−28
@@ -41,9 +41,6 @@ Showable {
41 // The text to be displayed in case the login failed
41 // The text to be displayed in case the login failed
42 property string errorText: ""
42 property string errorText: ""
43
43
44− // If > 0, a forced delay is happening
45− property int delayMinutes: 0
46−
47 // Set those to a value greater 0 to restrict the pin length.
44 // Set those to a value greater 0 to restrict the pin length.
48 // If both are unset, the Lockscreen will show a confirm button and allow typing any length of pin before
45 // If both are unset, the Lockscreen will show a confirm button and allow typing any length of pin before
49 // confirming. If minPinLength is set to a value > 0, the confirm button will only become active when the
46 // confirming. If minPinLength is set to a value > 0, the confirm button will only become active when the
@@ -91,14 +88,6 @@ Showable {
91 popup.z = Number.MAX_VALUE
88 popup.z = Number.MAX_VALUE
92 }
89 }
93
90
94− function showText(text) {
95− // Programmatically enters the given text into the lockscreen
96− if (pinPadLoader.item) {
97− pinPadLoader.item.showText(text);
98− pinPadLoader.waiting = true;
99− }
100− }
101−
102 Rectangle {
91 Rectangle {
103 // In case background fails to load
92 // In case background fails to load
104 id: backgroundBackup
93 id: backgroundBackup
@@ -107,17 +96,13 @@ Showable {
107 visible: root.background.toString() !== ""
96 visible: root.background.toString() !== ""
108 }
97 }
109
98
110− Image {
99+ Wallpaper {
111 id: backgroundImage
100 id: backgroundImage
112 objectName: "lockscreenBackground"
101 objectName: "lockscreenBackground"
113 anchors {
102 anchors {
114 fill: parent
103 fill: parent
115 }
104 }
116− // Limit how much memory we'll reserve for this image
117− sourceSize.height: height
118− sourceSize.width: width
119 source: root.required ? root.background : ""
105 source: root.required ? root.background : ""
120− fillMode: Image.PreserveAspectCrop
121 }
106 }
122
107
123 // This is to
108 // This is to
@@ -141,8 +126,6 @@ Showable {
141 source: {
126 source: {
142 if (resetting || !root.required) {
127 if (resetting || !root.required) {
143 return ""
128 return ""
144− } else if (root.delayMinutes > 0) {
145− return "DelayedLockscreen.qml"
146 } else if (root.alphaNumeric) {
129 } else if (root.alphaNumeric) {
147 return "PassphraseLockscreen.qml"
130 return "PassphraseLockscreen.qml"
148 } else {
131 } else {
@@ -197,16 +180,6 @@ Showable {
197 property: "entryEnabled"
180 property: "entryEnabled"
198 value: !pinPadLoader.waiting
181 value: !pinPadLoader.waiting
199 }
182 }
200− Binding {
201− target: pinPadLoader.item
202− property: "alphaNumeric"
203− value: root.alphaNumeric
204− }
205− Binding {
206− target: pinPadLoader.item
207− property: "delayMinutes"
208− value: root.delayMinutes
209− }
210 Binding {
183 Binding {
211 target: pinPadLoader.item
184 target: pinPadLoader.item
212 property: "showCancelButton"
185 property: "showCancelButton"
qml/Components/PassphraseLockscreen.qml−4
@@ -43,10 +43,6 @@ FocusScope {
43 }
43 }
44 }
44 }
45
45
46− function showText(text) {
47− pinentryField.text = text;
48− }
49−
50 Column {
46 Column {
51 id: shakeContainer
47 id: shakeContainer
52 anchors.horizontalCenter: parent.horizontalCenter
48 anchors.horizontalCenter: parent.horizontalCenter
qml/Components/PinLockscreen.qml−4
@@ -46,10 +46,6 @@ FocusScope {
46 }
46 }
47 }
47 }
48
48
49− function showText(text) {
50− pinentryField.text = text;
51− }
52−
53 Keys.onPressed: {
49 Keys.onPressed: {
54 if (pinentryField.text.length == root.maxPinLength)
50 if (pinentryField.text.length == root.maxPinLength)
55 return;
51 return;
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/Dash/Previews/PreviewProgress.qml+5−1
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -59,6 +59,10 @@ PreviewWidget {
59 }
59 }
60 }
60 }
61
61
62+ onProcessing: {
63+ progressBar.indeterminate = true;
64+ }
65+
62 onFinished: {
66 onFinished: {
63 root.triggered(widgetId, "finished", widgetData)
67 root.triggered(widgetId, "finished", widgetData)
64 }
68 }
qml/Greeter/CoverPage.qml+6−6
@@ -25,13 +25,14 @@ 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
31
32
32 property alias infographics: infographics
33 property alias infographics: infographics
33
34
34− readonly property real showProgress: MathUtils.clamp((width - Math.abs(x)) / width, 0, 1)
35+ readonly property real showProgress: MathUtils.clamp((width - Math.abs(x + launcherOffset)) / width, 0, 1)
35
36
36 signal tease()
37 signal tease()
37 signal clicked()
38 signal clicked()
@@ -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/Components/DelayedLockscreen.qml → qml/Greeter/DelayedLockscreen.qmlrenamed+6−15
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013,2014 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -16,7 +16,6 @@
16
16
17import QtQuick 2.4
17import QtQuick 2.4
18import Ubuntu.Components 1.3
18import Ubuntu.Components 1.3
19−import "../Components"
20
19
21Item {
20Item {
22 id: root
21 id: root
@@ -24,12 +23,6 @@ Item {
24 property int delayMinutes
23 property int delayMinutes
25 property bool alphaNumeric
24 property bool alphaNumeric
26
25
27− signal entered(string passphrase) // unused
28− signal cancel() // unused
29−
30− function clear(playAnimation) {}
31− function showText(text) {}
32−
33 Column {
26 Column {
34 anchors.left: parent.left
27 anchors.left: parent.left
35 anchors.right: parent.right
28 anchors.right: parent.right
@@ -46,7 +39,7 @@ Item {
46 horizontalAlignment: Text.AlignHCenter
39 horizontalAlignment: Text.AlignHCenter
47 wrapMode: Text.Wrap
40 wrapMode: Text.Wrap
48 fontSize: "x-large"
41 fontSize: "x-large"
49− color: "#f3f3e7"
42+ color: "white"
50 text: i18n.tr("Device Locked")
43 text: i18n.tr("Device Locked")
51 }
44 }
52
45
@@ -60,7 +53,7 @@ Item {
60 anchors.right: parent.right
53 anchors.right: parent.right
61 horizontalAlignment: Text.AlignHCenter
54 horizontalAlignment: Text.AlignHCenter
62 wrapMode: Text.Wrap
55 wrapMode: Text.Wrap
63− color: "#f3f3e7"
56+ color: "white"
64 text: alphaNumeric ?
57 text: alphaNumeric ?
65 i18n.tr("You have been locked out due to too many failed passphrase attempts.") :
58 i18n.tr("You have been locked out due to too many failed passphrase attempts.") :
66 i18n.tr("You have been locked out due to too many failed passcode attempts.")
59 i18n.tr("You have been locked out due to too many failed passcode attempts.")
@@ -71,7 +64,7 @@ Item {
71 anchors.right: parent.right
64 anchors.right: parent.right
72 horizontalAlignment: Text.AlignHCenter
65 horizontalAlignment: Text.AlignHCenter
73 wrapMode: Text.Wrap
66 wrapMode: Text.Wrap
74− color: "#f3f3e7"
67+ color: "white"
75 text: i18n.tr("Please wait %1 minute and then try again…",
68 text: i18n.tr("Please wait %1 minute and then try again…",
76 "Please wait %1 minutes and then try again…",
69 "Please wait %1 minutes and then try again…",
77 root.delayMinutes).arg(root.delayMinutes)
70 root.delayMinutes).arg(root.delayMinutes)
@@ -83,10 +76,8 @@ Item {
83 }
76 }
84
77
85 Icon {
78 Icon {
86− // It would be nice to use a less network-specific name,
79+ name: "lock"
87− // but this is the only lock icon we have.
88− name: "network-secure"
89− color: "#f3f3e7"
80+ color: "white"
90 height: units.gu(4)
81 height: units.gu(4)
91 width: units.gu(4)
82 width: units.gu(4)
92 anchors.horizontalCenter: parent.horizontalCenter
83 anchors.horizontalCenter: parent.horizontalCenter
qml/Greeter/FullLightDMImpl.qml+2
@@ -22,6 +22,8 @@ Item{
22
22
23 property var greeter: LightDM.Greeter
23 property var greeter: LightDM.Greeter
24 property var infographic: LightDM.Infographic
24 property var infographic: LightDM.Infographic
25+ property var sessions: LightDM.Sessions
26+ property var sessionRoles: LightDM.SessionRoles
25 property var users: LightDM.Users
27 property var users: LightDM.Users
26 property var userRoles: LightDM.UserRoles
28 property var userRoles: LightDM.UserRoles
27
29
qml/Greeter/Greeter.qml+60−24
@@ -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
@@ -72,9 +73,12 @@ Showable {
72 }
73 }
73 forcedUnlock = false;
74 forcedUnlock = false;
74 if (required) {
75 if (required) {
76+ if (loader.item) {
77+ loader.item.reset(true /* forceShow */);
78+ }
75 // Normally loader.onLoaded will select a user, but if we're
79 // Normally loader.onLoaded will select a user, but if we're
76 // already shown, do it manually.
80 // already shown, do it manually.
77− d.selectUser(d.currentIndex, true);
81+ d.selectUser(d.currentIndex, false);
78 }
82 }
79
83
80 // Even though we may already be shown, we want to call show() for its
84 // Even though we may already be shown, we want to call show() for its
@@ -105,8 +109,8 @@ Showable {
105 }
109 }
106 }
110 }
107
111
108− // Notify that the user has explicitly requested the given app through unity8 GUI.
112+ // Notify that the user has explicitly requested an app
109− function notifyUserRequestedApp(appId) {
113+ function notifyUserRequestedApp() {
110 if (!active) {
114 if (!active) {
111 return;
115 return;
112 }
116 }
@@ -132,6 +136,22 @@ Showable {
132 return d.startUnlock(true /* toTheRight */);
136 return d.startUnlock(true /* toTheRight */);
133 }
137 }
134
138
139+ function sessionToStart() {
140+ for (var i = 0; i < LightDMService.sessions.count; i++) {
141+ var session = LightDMService.sessions.data(i,
142+ LightDMService.sessionRoles.KeyRole);
143+ if (loader.item.sessionToStart === session) {
144+ return session;
145+ }
146+ }
147+
148+ if (loader.item.sessionToStart === LightDMService.greeter.defaultSession) {
149+ return LightDMService.greeter.defaultSession;
150+ } else {
151+ return "ubuntu"; // The default / fallback
152+ }
153+ }
154+
135 QtObject {
155 QtObject {
136 id: d
156 id: d
137
157
@@ -178,7 +198,7 @@ Showable {
178 return;
198 return;
179 d.waiting = true;
199 d.waiting = true;
180 if (reset) {
200 if (reset) {
181− loader.item.reset();
201+ loader.item.reset(false /* forceShow */);
182 }
202 }
183 currentIndex = index;
203 currentIndex = index;
184 var user = LightDMService.users.data(index, LightDMService.userRoles.NameRole);
204 var user = LightDMService.users.data(index, LightDMService.userRoles.NameRole);
@@ -197,7 +217,7 @@ Showable {
197
217
198 function login() {
218 function login() {
199 d.waiting = true;
219 d.waiting = true;
200− if (LightDMService.greeter.startSessionSync()) {
220+ if (LightDMService.greeter.startSessionSync(root.sessionToStart())) {
201 sessionStarted();
221 sessionStarted();
202 hideView();
222 hideView();
203 } else if (loader.item) {
223 } else if (loader.item) {
@@ -222,6 +242,29 @@ Showable {
222 }
242 }
223 }
243 }
224 }
244 }
245+
246+ function showPromptMessage(text, isError) {
247+ // inefficient, but we only rarely deal with messages
248+ var html = text.replace(/&/g, "&amp;")
249+ .replace(/</g, "&lt;")
250+ .replace(/>/g, "&gt;")
251+ .replace(/\n/g, "<br>");
252+ if (isError) {
253+ html = "<font color=\"#df382c\">" + html + "</font>";
254+ }
255+
256+ if (loader.item) {
257+ loader.item.showMessage(html);
258+ }
259+ }
260+
261+ function showFingerprintMessage(msg) {
262+ if (loader.item) {
263+ loader.item.reset(false /* forceShow */);
264+ loader.item.showErrorMessage(msg);
265+ }
266+ showPromptMessage(msg, true);
267+ }
225 }
268 }
226
269
227 onLauncherOffsetChanged: {
270 onLauncherOffsetChanged: {
@@ -393,6 +436,12 @@ Showable {
393 value: root.background
436 value: root.background
394 }
437 }
395
438
439+ Binding {
440+ target: loader.item
441+ property: "hasCustomBackground"
442+ value: root.hasCustomBackground
443+ }
444+
396 Binding {
445 Binding {
397 target: loader.item
446 target: loader.item
398 property: "locked"
447 property: "locked"
@@ -436,20 +485,7 @@ Showable {
436 onShowGreeter: root.forceShow()
485 onShowGreeter: root.forceShow()
437 onHideGreeter: root.forcedUnlock = true
486 onHideGreeter: root.forcedUnlock = true
438
487
439− onShowMessage: {
488+ onShowMessage: d.showPromptMessage(text, isError)
440− // inefficient, but we only rarely deal with messages
441− var html = text.replace(/&/g, "&amp;")
442− .replace(/</g, "&lt;")
443− .replace(/>/g, "&gt;")
444− .replace(/\n/g, "<br>");
445− if (isError) {
446− html = "<font color=\"#df382c\">" + html + "</font>";
447− }
448−
449− if (loader.item) {
450− loader.item.showMessage(html);
451− }
452− }
453
489
454 onShowPrompt: {
490 onShowPrompt: {
455 if (loader.item) {
491 if (loader.item) {
@@ -557,10 +593,8 @@ Showable {
557 if (!d.secureFingerprint) {
593 if (!d.secureFingerprint) {
558 d.startUnlock(false /* toTheRight */); // use normal login instead
594 d.startUnlock(false /* toTheRight */); // use normal login instead
559 }
595 }
560− if (loader.item) {
561− var msg = d.secureFingerprint ? i18n.tr("Try again") : "";
596+ var msg = d.secureFingerprint ? i18n.tr("Try again") : "";
562− loader.item.showErrorMessage(msg);
597+ d.showFingerprintMessage(msg);
563− }
564 }
598 }
565
599
566 Component.onCompleted: restartOperation()
600 Component.onCompleted: restartOperation()
@@ -578,8 +612,10 @@ Showable {
578 return;
612 return;
579 }
613 }
580 console.log("Identified user by fingerprint:", result);
614 console.log("Identified user by fingerprint:", result);
581− if (loader.item)
615+ if (loader.item) {
616+ loader.item.enabled = false;
582 loader.item.notifyAuthenticationSucceeded(true /* showFakePassword */);
617 loader.item.notifyAuthenticationSucceeded(true /* showFakePassword */);
618+ }
583 if (root.active)
619 if (root.active)
584 root.forcedUnlock = true;
620 root.forcedUnlock = true;
585 }
621 }
qml/Greeter/GreeterPrompt.qml+21−7
@@ -38,6 +38,16 @@ FocusScope {
38 d.enabled = true;
38 d.enabled = true;
39 }
39 }
40
40
41+ function showFakePassword() {
42+ // Just a silly hack for looking like 4 pin numbers got entered, if
43+ // a fingerprint was used and we happen to be using a pin. This was
44+ // a request from Design.
45+ if (isSecret && isPrompt && !isAlphanumeric) {
46+ d.enabled = false;
47+ text = "...."; // actual text doesn't matter
48+ }
49+ }
50+
41 StyledItem {
51 StyledItem {
42 id: d
52 id: d
43
53
@@ -50,6 +60,12 @@ FocusScope {
50 : theme.palette.disabled.raisedSecondaryText
60 : theme.palette.disabled.raisedSecondaryText
51 readonly property color errorColor: passwordInput.enabled ? theme.palette.normal.negative
61 readonly property color errorColor: passwordInput.enabled ? theme.palette.normal.negative
52 : theme.palette.disabled.negative
62 : theme.palette.disabled.negative
63+
64+ onEnabledChanged: {
65+ if (!enabled) {
66+ fakeLabel.text = passwordInput.displayText;
67+ }
68+ }
53 }
69 }
54
70
55 Rectangle {
71 Rectangle {
@@ -142,15 +158,13 @@ FocusScope {
142 }
158 }
143 }
159 }
144
160
145− onAccepted: {
161+ onAccepted: respond()
146− if (d.enabled)
147− respond();
148− }
149
162
150 function respond() {
163 function respond() {
164+ if (d.enabled) {
151− d.enabled = false;
165+ d.enabled = false;
152− fakeLabel.text = displayText;
153− root.responded(text);
166+ root.responded(text);
167+ }
154 }
168 }
155
169
156 Keys.onEscapePressed: {
170 Keys.onEscapePressed: {
qml/Greeter/IntegratedLightDMImpl.qml+2−1
@@ -22,7 +22,8 @@ Item{
22
22
23 property var greeter: LightDM.Greeter
23 property var greeter: LightDM.Greeter
24 property var infographic: LightDM.Infographic
24 property var infographic: LightDM.Infographic
25+ property var sessions: LightDM.Sessions
26+ property var sessionRoles: LightDM.SessionRoles
25 property var users: LightDM.Users
27 property var users: LightDM.Users
26 property var userRoles: LightDM.UserRoles
28 property var userRoles: LightDM.UserRoles
27−
28}
29}
qml/Greeter/LightDMService.qml+2
@@ -27,6 +27,8 @@ Loader {
27
27
28 property var greeter: d.valid ? loader.item.greeter : null
28 property var greeter: d.valid ? loader.item.greeter : null
29 property var infographic: d.valid ? loader.item.infographic : null
29 property var infographic: d.valid ? loader.item.infographic : null
30+ property var sessions: d.valid ? loader.item.sessions : null
31+ property var sessionRoles: d.valid ? loader.item.sessionRoles : null
30 property var users: d.valid ? loader.item.users : null
32 property var users: d.valid ? loader.item.users : null
31 property var userRoles: d.valid ? loader.item.userRoles : null
33 property var userRoles: d.valid ? loader.item.userRoles : null
32
34
qml/Greeter/LoginAreaContainer.qmlnew file+38
@@ -0,0 +1,38 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+
20+Item {
21+ BorderImage {
22+ anchors {
23+ fill: parent
24+ topMargin: -units.gu(1)
25+ leftMargin: -units.gu(1.5)
26+ rightMargin: -units.gu(1.5)
27+ bottomMargin: -units.gu(1.5)
28+ }
29+ source: "../Stages/graphics/dropshadow2gu.sci"
30+ opacity: 0.35
31+ }
32+
33+ UbuntuShape {
34+ anchors.fill: parent
35+ aspect: UbuntuShape.Flat
36+ backgroundColor: theme.palette.normal.raised
37+ }
38+}
qml/Greeter/LoginList.qml+96−21
@@ -18,6 +18,7 @@ import QtQuick 2.4
18import QtGraphicalEffects 1.0
18import QtGraphicalEffects 1.0
19import Ubuntu.Components 1.3
19import Ubuntu.Components 1.3
20import "../Components"
20import "../Components"
21+import "." 0.1
21
22
22StyledItem {
23StyledItem {
23 id: root
24 id: root
@@ -28,16 +29,22 @@ StyledItem {
28 property int currentIndex
29 property int currentIndex
29 property bool locked
30 property bool locked
30 property bool waiting
31 property bool waiting
32+ property alias boxVerticalOffset: highlightItem.y
31
33
34+ readonly property alias passwordInput: passwordInput
32 readonly property int numAboveBelow: 4
35 readonly property int numAboveBelow: 4
33 readonly property int cellHeight: units.gu(5)
36 readonly property int cellHeight: units.gu(5)
34 readonly property int highlightedHeight: units.gu(15)
37 readonly property int highlightedHeight: units.gu(15)
35− readonly property int moveDuration: 200
38+ readonly property int moveDuration: UbuntuAnimation.FastDuration
39+ property string selectedSession
40+ property string currentSession
36 readonly property string currentUser: userList.currentItem.username
41 readonly property string currentUser: userList.currentItem.username
37 property bool wasPrompted: false
42 property bool wasPrompted: false
38
43
39− signal selected(int index)
44+ signal loginListSessionChanged(string session)
40 signal responded(string response)
45 signal responded(string response)
46+ signal selected(int index)
47+ signal sessionChooserButtonClicked()
41
48
42 function tryToUnlock() {
49 function tryToUnlock() {
43 if (wasPrompted) {
50 if (wasPrompted) {
@@ -78,6 +85,10 @@ StyledItem {
78 root.resetAuthentication();
85 root.resetAuthentication();
79 }
86 }
80
87
88+ function showFakePassword() {
89+ passwordInput.showFakePassword();
90+ }
91+
81 QtObject {
92 QtObject {
82 id: d
93 id: d
83
94
@@ -97,6 +108,7 @@ StyledItem {
97 name: "Ubuntu.Components.Themes.Ambiance"
108 name: "Ubuntu.Components.Themes.Ambiance"
98 }
109 }
99
110
111+ KeyNavigation.tab: sessionChooser
100 Keys.onUpPressed: {
112 Keys.onUpPressed: {
101 selected(currentIndex - 1);
113 selected(currentIndex - 1);
102 event.accepted = true;
114 event.accepted = true;
@@ -114,30 +126,17 @@ StyledItem {
114 userList.currentIndex = currentIndex;
126 userList.currentIndex = currentIndex;
115 }
127 }
116
128
117− BorderImage {
129+ LoginAreaContainer {
118− anchors {
119− fill: highlightItem
120− topMargin: -units.gu(1)
121− leftMargin: -units.gu(1.5)
122− rightMargin: -units.gu(1.5)
123− bottomMargin: -units.gu(1.5)
124− }
125− source: "../Stages/graphics/dropshadow2gu.sci"
126− opacity: 0.35
127− }
128−
129− UbuntuShape {
130 id: highlightItem
130 id: highlightItem
131+ objectName: "highlightItem"
131 anchors {
132 anchors {
132 left: parent.left
133 left: parent.left
133 leftMargin: units.gu(2)
134 leftMargin: units.gu(2)
134 right: parent.right
135 right: parent.right
135 rightMargin: units.gu(2)
136 rightMargin: units.gu(2)
136− verticalCenter: parent.verticalCenter
137 }
137 }
138+
138 height: root.highlightedHeight
139 height: root.highlightedHeight
139− aspect: UbuntuShape.Flat
140− backgroundColor: theme.palette.normal.raised
141 }
140 }
142
141
143 ListView {
142 ListView {
@@ -148,8 +147,8 @@ StyledItem {
148 anchors.leftMargin: units.gu(2)
147 anchors.leftMargin: units.gu(2)
149 anchors.rightMargin: units.gu(2)
148 anchors.rightMargin: units.gu(2)
150
149
151− preferredHighlightBegin: userList.height / 2 - root.highlightedHeight / 2
150+ preferredHighlightBegin: highlightItem.y
152− preferredHighlightEnd: userList.height / 2 - root.highlightedHeight / 2
151+ preferredHighlightEnd: highlightItem.y
153 highlightRangeMode: ListView.StrictlyEnforceRange
152 highlightRangeMode: ListView.StrictlyEnforceRange
154 highlightMoveDuration: root.moveDuration
153 highlightMoveDuration: root.moveDuration
155 flickDeceleration: 10000
154 flickDeceleration: 10000
@@ -173,6 +172,7 @@ StyledItem {
173
172
174 readonly property bool belowHighlight: (userList.currentIndex < 0 && index > 0) || (userList.currentIndex >= 0 && index > userList.currentIndex)
173 readonly property bool belowHighlight: (userList.currentIndex < 0 && index > 0) || (userList.currentIndex >= 0 && index > userList.currentIndex)
175 readonly property int belowOffset: root.highlightedHeight - root.cellHeight
174 readonly property int belowOffset: root.highlightedHeight - root.cellHeight
175+ readonly property string userSession: session
176 readonly property string username: name
176 readonly property string username: name
177
177
178 opacity: {
178 opacity: {
@@ -206,9 +206,22 @@ StyledItem {
206 bottomMargin: -(units.gu(4) + (parent.belowHighlight ? parent.belowOffset : 0))
206 bottomMargin: -(units.gu(4) + (parent.belowHighlight ? parent.belowOffset : 0))
207 }
207 }
208 text: realName
208 text: realName
209− color: userList.currentIndex !== index ? theme.palette.normal.raised : theme.palette.normal.raisedText
209+ color: userList.currentIndex !== index ? theme.palette.normal.raised
210+ : theme.palette.normal.raisedText
210
211
211 Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
212 Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
213+
214+ Rectangle {
215+ id: activeIndicator
216+ anchors.horizontalCenter: parent.left
217+ anchors.horizontalCenterOffset: -units.gu(1)
218+ anchors.verticalCenter: parent.verticalCenter
219+ color: userList.currentIndex !== index ? theme.palette.normal.raised
220+ : theme.palette.normal.focus
221+ visible: userList.count > 1 && loggedIn
222+ height: units.gu(0.5)
223+ width: height
224+ }
212 }
225 }
213
226
214 MouseArea {
227 MouseArea {
@@ -237,6 +250,67 @@ StyledItem {
237 }
250 }
238 }
251 }
239
252
253+ // Use an AbstractButton due to icon limitations with Button
254+ AbstractButton {
255+ id: sessionChooser
256+ objectName: "sessionChooserButton"
257+
258+ readonly property alias icon: badge.source
259+
260+ visible: LightDMService.sessions.count > 1 &&
261+ !LightDMService.users.data(userList.currentIndex, LightDMService.userRoles.LoggedInRole)
262+
263+ height: units.gu(3.5)
264+ width: units.gu(3.5)
265+
266+ anchors {
267+ right: highlightItem.right
268+ rightMargin: units.gu(2)
269+
270+ top: highlightItem.top
271+ topMargin: units.gu(1.5)
272+ }
273+
274+ Rectangle {
275+ id: badgeHighlight
276+
277+ anchors.fill: parent
278+ visible: parent.activeFocus
279+ color: "transparent"
280+ border.color: theme.palette.normal.focus
281+ border.width: units.dp(1)
282+ radius: 3
283+ }
284+
285+ Icon {
286+ id: badge
287+ anchors.fill: parent
288+ anchors.margins: units.dp(3)
289+ keyColor: "#ffffff" // icon providers give us white icons
290+ color: theme.palette.normal.raisedSecondaryText
291+ source: LightDMService.sessions.iconUrl(root.currentSession)
292+ }
293+
294+ KeyNavigation.tab: passwordInput
295+ Keys.onReturnPressed: {
296+ sessionChooserButtonClicked();
297+ event.accepted = true;
298+ }
299+
300+ onClicked: {
301+ sessionChooserButtonClicked();
302+ }
303+
304+ // Refresh the icon path if looking at different places at runtime
305+ // this is mainly for testing
306+ Connections {
307+ target: LightDMService.sessions
308+ onIconSearchDirectoriesChanged: {
309+ badge.source = LightDMService.sessions.iconUrl(root.currentSession)
310+ }
311+ }
312+ }
313+
240 FadingLabel {
314 FadingLabel {
241 id: infoLabel
315 id: infoLabel
242 objectName: "infoLabel"
316 objectName: "infoLabel"
@@ -283,6 +357,7 @@ StyledItem {
283
357
284 WrongPasswordAnimation {
358 WrongPasswordAnimation {
285 id: wrongPasswordAnimation
359 id: wrongPasswordAnimation
360+ objectName: "wrongPasswordAnimation"
286 target: passwordInput
361 target: passwordInput
287 }
362 }
288 }
363 }
qml/Greeter/NarrowView.qml+147−61
@@ -15,7 +15,9 @@
15 */
15 */
16
16
17import QtQuick 2.4
17import QtQuick 2.4
18+import QtQuick.Window 2.2
18import Ubuntu.Components 1.3
19import Ubuntu.Components 1.3
20+import Ubuntu.Telephony 0.1 as Telephony
19import "../Components"
21import "../Components"
20
22
21FocusScope {
23FocusScope {
@@ -23,34 +25,40 @@ FocusScope {
23
25
24 property alias dragHandleLeftMargin: coverPage.dragHandleLeftMargin
26 property alias dragHandleLeftMargin: coverPage.dragHandleLeftMargin
25 property alias launcherOffset: coverPage.launcherOffset
27 property alias launcherOffset: coverPage.launcherOffset
26− property int currentIndex // unused
28+ property alias currentIndex: loginList.currentIndex
27− property alias delayMinutes: lockscreen.delayMinutes
29+ property alias delayMinutes: delayedLockscreen.delayMinutes
28 property alias backgroundTopMargin: coverPage.backgroundTopMargin
30 property alias backgroundTopMargin: coverPage.backgroundTopMargin
29 property url background
31 property url background
32+ property bool hasCustomBackground
30 property bool locked
33 property bool locked
31− property bool alphanumeric
34+ property alias alphanumeric: loginList.alphanumeric
32− property var userModel // unused
35+ property alias userModel: loginList.model
33 property alias infographicModel: coverPage.infographicModel
36 property alias infographicModel: coverPage.infographicModel
37+ property string sessionToStart
34 property bool waiting
38 property bool waiting
35 readonly property bool fullyShown: coverPage.showProgress === 1 || lockscreen.shown
39 readonly property bool fullyShown: coverPage.showProgress === 1 || lockscreen.shown
36 readonly property bool required: coverPage.required || lockscreen.required
40 readonly property bool required: coverPage.required || lockscreen.required
37 readonly property bool animating: coverPage.showAnimation.running || coverPage.hideAnimation.running
41 readonly property bool animating: coverPage.showAnimation.running || coverPage.hideAnimation.running
38
42
43+ // so that it can be replaced in tests with a mock object
44+ property var inputMethod: Qt.inputMethod
45+
39− signal selected(int index) // unused
46+ signal selected(int index)
40 signal responded(string response)
47 signal responded(string response)
41 signal tease()
48 signal tease()
42 signal emergencyCall()
49 signal emergencyCall()
43
50
44 function showMessage(html) {
51 function showMessage(html) {
45− // TODO
52+ loginList.showMessage(html);
46 }
53 }
47
54
48 function showPrompt(text, isSecret, isDefaultPrompt) {
55 function showPrompt(text, isSecret, isDefaultPrompt) {
49− lockscreen.promptText = isDefaultPrompt ? "" : text.toLowerCase();
56+ loginList.showPrompt(text, isSecret, isDefaultPrompt);
50− lockscreen.maybeShow();
51 }
57 }
52
58
53 function showLastChance() {
59 function showLastChance() {
60+ /* TODO: when we finish support for resetting device after too many
61+ failed logins, we should re-add this popup.
54 var title = lockscreen.alphaNumeric ?
62 var title = lockscreen.alphaNumeric ?
55 i18n.tr("Sorry, incorrect passphrase.") :
63 i18n.tr("Sorry, incorrect passphrase.") :
56 i18n.tr("Sorry, incorrect passcode.");
64 i18n.tr("Sorry, incorrect passcode.");
@@ -59,6 +67,7 @@ FocusScope {
59 i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
67 i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
60 i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."));
68 i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."));
61 lockscreen.showInfoPopup(title, text);
69 lockscreen.showInfoPopup(title, text);
70+ */
62 }
71 }
63
72
64 function hide() {
73 function hide() {
@@ -67,27 +76,24 @@ FocusScope {
67 }
76 }
68
77
69 function notifyAuthenticationSucceeded(showFakePassword) {
78 function notifyAuthenticationSucceeded(showFakePassword) {
70− // When using an alternate log in mechanism like fingerprints, the
71− // design is it looks like the user entered a passcode.
72− if (!alphanumeric && showFakePassword) {
79+ if (showFakePassword) {
73− lockscreen.showText("...."); // actual text doesn't matter, we show bullets
80+ loginList.showFakePassword();
74 }
81 }
75 }
82 }
76
83
77 function notifyAuthenticationFailed() {
84 function notifyAuthenticationFailed() {
78− lockscreen.customError = "";
85+ loginList.showError();
79− lockscreen.clear(true);
80 }
86 }
81
87
82 function showErrorMessage(msg) {
88 function showErrorMessage(msg) {
83 coverPage.showErrorMessage(msg);
89 coverPage.showErrorMessage(msg);
84− lockscreen.customError = msg ? msg : " "; // avoid default message
85− lockscreen.clear(true);
86 }
90 }
87
91
88− function reset() {
92+ function reset(forceShow) {
89− lockscreen.customError = "";
93+ loginList.reset();
94+ if (forceShow) {
90− coverPage.show();
95+ coverPage.show();
96+ }
91 }
97 }
92
98
93 function tryToUnlock(toTheRight) {
99 function tryToUnlock(toTheRight) {
@@ -109,48 +115,59 @@ FocusScope {
109 }
115 }
110 }
116 }
111
117
112− Lockscreen {
118+ Showable {
113 id: lockscreen
119 id: lockscreen
114 objectName: "lockscreen"
120 objectName: "lockscreen"
115−
121+ anchors.fill: parent
116 shown: false
122 shown: false
123+
117 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
124 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
118 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
125 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
126+
127+ Wallpaper {
128+ id: lockscreenBackground
129+ objectName: "lockscreenBackground"
130+ anchors {
119− anchors.fill: parent
131+ fill: parent
120− visible: required
132+ topMargin: root.backgroundTopMargin
121− enabled: !coverPage.shown
122− background: root.background
123− darkenBackground: 0.4
124− alphaNumeric: root.alphanumeric
125− minPinLength: 4
126− maxPinLength: 4
127−
128− property string promptText
129− infoText: promptText !== "" ? i18n.tr("Enter %1").arg(promptText) :
130− alphaNumeric ? i18n.tr("Enter passphrase") :
131− i18n.tr("Enter passcode")
132−
133− property string customError
134− errorText: customError !== "" ? customError :
135− promptText !== "" ? i18n.tr("Sorry, incorrect %1").arg(promptText) :
136− alphaNumeric ? i18n.tr("Sorry, incorrect passphrase") + "\n" +
137− i18n.ctr("passphrase", "Please re-enter") :
138− i18n.tr("Sorry, incorrect passcode")
139−
140− onEntered: root.responded(passphrase)
141− onCancel: coverPage.show()
142− onEmergencyCall: root.emergencyCall()
143−
144− onEnabledChanged: {
145− if (enabled) {
146− lockscreen.forceActiveFocus();
147 }
133 }
134+ source: root.background
148 }
135 }
149
136
150− onVisibleChanged: {
137+ // Darken background to match CoverPage
151− if (visible) {
138+ Rectangle {
152− lockscreen.forceActiveFocus();
139+ objectName: "lockscreenShade"
140+ anchors.fill: parent
141+ color: "black"
142+ opacity: root.hasCustomBackground ? 0.4 : 0
143+ }
144+
145+ LoginList {
146+ id: loginList
147+ objectName: "loginList"
148+
149+ anchors {
150+ horizontalCenter: parent.horizontalCenter
151+ top: parent.top
152+ bottom: parent.bottom
153 }
153 }
154+ width: units.gu(40)
155+ boxVerticalOffset: units.gu(14)
156+ enabled: !coverPage.shown && visible
157+ visible: !delayedLockscreen.visible
158+
159+ locked: root.locked
160+
161+ onSelected: if (enabled) root.selected(index)
162+ onResponded: root.responded(response)
163+ }
164+
165+ DelayedLockscreen {
166+ id: delayedLockscreen
167+ objectName: "delayedLockscreen"
168+ anchors.fill: parent
169+ visible: delayMinutes > 0
170+ alphaNumeric: loginList.alphanumeric
154 }
171 }
155
172
156 function maybeShow() {
173 function maybeShow() {
@@ -172,21 +189,16 @@ FocusScope {
172 height: parent.height
189 height: parent.height
173 width: parent.width
190 width: parent.width
174 background: root.background
191 background: root.background
192+ hasCustomBackground: root.hasCustomBackground
175 draggable: !root.waiting
193 draggable: !root.waiting
176 onTease: root.tease()
194 onTease: root.tease()
177 onClicked: hide()
195 onClicked: hide()
178
196
179 onShowProgressChanged: {
197 onShowProgressChanged: {
180 if (showProgress === 1) {
198 if (showProgress === 1) {
181− lockscreen.reset();
199+ loginList.reset();
182− }
183−
184− if (showProgress === 0) {
200+ } else if (showProgress === 0) {
185− if (root.locked) {
201+ loginList.tryToUnlock();
186− lockscreen.clear(false); // to reset focus if necessary
187− } else {
188− root.responded("");
189− }
190 }
202 }
191 }
203 }
192
204
@@ -198,4 +210,78 @@ FocusScope {
198 }
210 }
199 }
211 }
200 }
212 }
213+
214+ StyledItem {
215+ id: bottomBar
216+ visible: lockscreen.shown
217+ height: units.gu(4)
218+
219+ anchors.left: parent.left
220+ anchors.right: parent.right
221+ anchors.top: parent.bottom
222+ anchors.topMargin: - height * (1 - coverPage.showProgress)
223+ - (inputMethod && inputMethod.visible ?
224+ inputMethod.keyboardRectangle.height : 0)
225+
226+ Rectangle {
227+ color: UbuntuColors.porcelain // matches OSK background
228+ anchors.fill: parent
229+ }
230+
231+ Label {
232+ text: i18n.tr("Cancel")
233+ anchors.left: parent.left
234+ anchors.leftMargin: units.gu(2)
235+ anchors.top: parent.top
236+ anchors.bottom: parent.bottom
237+ verticalAlignment: Text.AlignVCenter
238+ font.weight: Font.Light
239+ fontSize: "small"
240+ color: UbuntuColors.slate
241+
242+ AbstractButton {
243+ anchors.fill: parent
244+ anchors.leftMargin: -units.gu(2)
245+ anchors.rightMargin: -units.gu(2)
246+ onClicked: coverPage.show()
247+ }
248+ }
249+
250+ Label {
251+ objectName: "emergencyCallLabel"
252+ text: callManager.hasCalls ? i18n.tr("Return to Call") : i18n.tr("Emergency")
253+ anchors.right: parent.right
254+ anchors.rightMargin: units.gu(2)
255+ anchors.top: parent.top
256+ anchors.bottom: parent.bottom
257+ verticalAlignment: Text.AlignVCenter
258+ font.weight: Font.Light
259+ fontSize: "small"
260+ color: UbuntuColors.slate
261+ // TODO: uncomment once bug 1616538 is fixed
262+ // visible: telepathyHelper.ready && telepathyHelper.emergencyCallsAvailable
263+ enabled: visible
264+
265+ AbstractButton {
266+ anchors.fill: parent
267+ anchors.leftMargin: -units.gu(2)
268+ anchors.rightMargin: -units.gu(2)
269+ onClicked: root.emergencyCall()
270+ }
271+ }
272+ }
273+
274+ // FIXME: It's difficult to keep something tied closely to the OSK (bug
275+ // 1616163). But as a hack to avoid the background peeking out,
276+ // we add an extra Rectangle that just serves to hide the background
277+ // during OSK animations.
278+ Rectangle {
279+ visible: bottomBar.visible
280+ height: inputMethod && inputMethod.visible ?
281+ inputMethod.keyboardRectangle.height : 0
282+ anchors.bottom: parent.bottom
283+ anchors.left: parent.left
284+ anchors.right: parent.right
285+ color: UbuntuColors.porcelain
286+ }
201}
287}
qml/Greeter/SessionIcon.qmlnew file+47
@@ -0,0 +1,47 @@
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+import QtQuick 2.4
18+
19+Image {
20+ id: root
21+
22+ property color color
23+ readonly property string fragColorShader:
24+ "
25+ varying highp vec2 qt_TexCoord0;
26+ uniform sampler2D source;
27+ uniform lowp vec4 color;
28+ uniform lowp float qt_Opacity;
29+
30+ void main()
31+ {
32+ lowp vec4 sourceColor = texture2D(source, qt_TexCoord0);
33+ gl_FragColor = color * sourceColor.a * qt_Opacity;
34+ }
35+ "
36+
37+ fillMode: Image.PreserveAspectFit
38+ height: sourceSize.height
39+
40+ ShaderEffect {
41+ readonly property color color: root.color
42+ readonly property Image source: parent
43+
44+ height: source.height; width: source.width
45+ fragmentShader: fragColorShader
46+ }
47+}
qml/Greeter/SessionsList.qmlnew file+182
@@ -0,0 +1,182 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+import Ubuntu.Components.ListItems 1.3
20+import "." 0.1
21+import "../Components"
22+
23+Item {
24+ id: root
25+ objectName: "sessionsList"
26+
27+ property string initiallySelectedSession
28+ signal sessionSelected(string sessionKey)
29+ signal showLoginList()
30+
31+ onInitiallySelectedSessionChanged: {
32+ sessionsList.currentIndex = getSelectedIndex();
33+ sessionsList.positionViewAtIndex(sessionsList.currentIndex, ListView.Contain);
34+ }
35+
36+ function getSelectedIndex() {
37+ for (var i = 0; i < sessionsList.model.count; i++) {
38+ var key = sessionsList.model.get(i).key
39+ if (key === initiallySelectedSession) {
40+ return i;
41+ }
42+ }
43+ }
44+
45+ function currentKey() {
46+ var session = LightDMService.sessions.data(
47+ sessionsList.currentIndex, LightDMService.sessionRoles.KeyRole)
48+ return session;
49+ }
50+
51+ Keys.onEnterPressed: {
52+ showLoginList(); // Session is already selected
53+ event.accepted = true;
54+ }
55+
56+ Keys.onReturnPressed: {
57+ showLoginList();
58+ event.accepted = true;
59+ }
60+
61+ Keys.onDownPressed: {
62+ if (sessionsList.currentIndex < sessionsList.model.count - 1)
63+ sessionsList.currentIndex++;
64+ sessionSelected(currentKey());
65+ event.accepted = true;
66+ }
67+
68+ Keys.onUpPressed: {
69+ if (sessionsList.currentIndex > 0)
70+ sessionsList.currentIndex--;
71+ sessionSelected(currentKey());
72+ event.accepted = true;
73+ }
74+
75+ LoginAreaContainer {
76+ readonly property real margins: sessionsList.anchors.margins
77+ readonly property real prefferedHeight: {
78+ if (sessionsList.currentItem) {
79+ return ((sessionsList.currentItem.height + margins) *
80+ sessionsList.model.count) + sessionsList.headerItem.height + 2 * margins
81+ } else {
82+ return sessionsList.headerItem.height + 2 * margins
83+ }
84+ }
85+
86+ height: prefferedHeight < parent.height ? prefferedHeight : parent.height - units.gu(4)
87+ width: parent.width
88+
89+ anchors {
90+ left: parent.left
91+ right: parent.right
92+ verticalCenter: parent.verticalCenter
93+ }
94+
95+ UbuntuListView {
96+ id: sessionsList
97+
98+ anchors {
99+ top: parent.top
100+ left: parent.left
101+ right: parent.right
102+ margins: units.gu(2)
103+ }
104+
105+ height: parent.height - headerItem.height
106+
107+ boundsBehavior: Flickable.StopAtBounds
108+
109+ model: LightDMService.sessions
110+ header: ListItemLayout {
111+ id: header
112+
113+ padding.leading: 0 // handled by parent's margins
114+
115+ title.color: theme.palette.normal.raisedText
116+ title.font.pixelSize: units.gu(2.1)
117+ title.text: i18n.tr("Select desktop environment")
118+
119+ Icon {
120+ id: icon
121+ width: units.gu(3)
122+ SlotsLayout.position: SlotsLayout.Leading
123+ name: "go-previous"
124+
125+ MouseArea {
126+ anchors.fill: parent
127+ onClicked: showLoginList()
128+ }
129+ }
130+ }
131+
132+ headerPositioning: ListView.OverlayHeader
133+ highlightFollowsCurrentItem: false
134+
135+ delegate: ListItem {
136+ id: delegate
137+ objectName: "sessionDelegate" + index
138+
139+ divider.visible: false
140+
141+ visible: y > sessionsList.headerItem.y
142+ + sessionsList.headerItem.height
143+ - sessionsList.anchors.margins
144+
145+ MouseArea {
146+ anchors.fill: parent
147+ onClicked: {
148+ sessionsList.currentIndex = index
149+ sessionSelected(key)
150+ showLoginList()
151+ }
152+ }
153+
154+ Rectangle {
155+ height: parent.height
156+ width: parent.width
157+ color: "transparent"
158+ border {
159+ color: theme.palette.normal.positionText
160+ width: units.gu(0.2)
161+ }
162+
163+ visible: index === sessionsList.currentIndex
164+ }
165+
166+ ListItemLayout {
167+ id: layout
168+
169+ SessionIcon {
170+ id: sessionIcon
171+ source: icon_url
172+ SlotsLayout.position: SlotsLayout.Leading
173+ color: theme.palette.normal.raisedSecondaryText
174+ }
175+
176+ title.text: display
177+ title.color: theme.palette.normal.raisedText
178+ }
179+ }
180+ }
181+ }
182+}
qml/Greeter/WideView.qml+109−38
@@ -16,25 +16,29 @@
16
16
17import QtQuick 2.4
17import QtQuick 2.4
18import Ubuntu.Components 1.3
18import Ubuntu.Components 1.3
19+import "." 0.1
19
20
20FocusScope {
21FocusScope {
21 id: root
22 id: root
22 focus: true
23 focus: true
23
24
25+ property alias background: coverPage.background
26+ property alias backgroundTopMargin: coverPage.backgroundTopMargin
27+ property alias hasCustomBackground: coverPage.hasCustomBackground
24 property alias dragHandleLeftMargin: coverPage.dragHandleLeftMargin
28 property alias dragHandleLeftMargin: coverPage.dragHandleLeftMargin
29+ property alias infographicModel: coverPage.infographicModel
25 property alias launcherOffset: coverPage.launcherOffset
30 property alias launcherOffset: coverPage.launcherOffset
26 property alias currentIndex: loginList.currentIndex
31 property alias currentIndex: loginList.currentIndex
27 property int delayMinutes // TODO
32 property int delayMinutes // TODO
28− property alias backgroundTopMargin: coverPage.backgroundTopMargin
29− property alias background: coverPage.background
30− property bool locked
31 property alias alphanumeric: loginList.alphanumeric
33 property alias alphanumeric: loginList.alphanumeric
32− property alias userModel: loginList.model
34+ property alias locked: loginList.locked
33− property alias infographicModel: coverPage.infographicModel
35+ property alias sessionToStart: loginList.currentSession
34− property bool waiting
36+ property alias waiting: loginList.waiting
37+ property var userModel // Set from outside
38+
39+ readonly property bool animating: coverPage.showAnimation.running || coverPage.hideAnimation.running
35 readonly property bool fullyShown: coverPage.showProgress === 1
40 readonly property bool fullyShown: coverPage.showProgress === 1
36 readonly property bool required: coverPage.required
41 readonly property bool required: coverPage.required
37− readonly property bool animating: coverPage.showAnimation.running || coverPage.hideAnimation.running
38
42
39 // so that it can be replaced in tests with a mock object
43 // so that it can be replaced in tests with a mock object
40 property var inputMethod: Qt.inputMethod
44 property var inputMethod: Qt.inputMethod
@@ -44,36 +48,20 @@ FocusScope {
44 signal tease()
48 signal tease()
45 signal emergencyCall() // unused
49 signal emergencyCall() // unused
46
50
47− function showMessage(html) {
48− loginList.showMessage(html);
49− }
50−
51− function showPrompt(text, isSecret, isDefaultPrompt) {
52− loginList.showPrompt(text, isSecret, isDefaultPrompt);
53− }
54−
55− function showLastChance() {
56− // TODO
57− }
58−
59− function hide() {
60− coverPage.hide();
61− }
62−
63− function notifyAuthenticationSucceeded(showFakePassword) {
64− // Nothing needed
65− }
66−
67 function notifyAuthenticationFailed() {
51 function notifyAuthenticationFailed() {
68 loginList.showError();
52 loginList.showError();
69 }
53 }
70
54
71− function showErrorMessage(msg) {
55+ function reset(forceShow) {
72− coverPage.showErrorMessage(msg);
56+ loginList.reset();
73 }
57 }
74
58
75− function reset() {
59+ function showMessage(html) {
76− loginList.reset();
60+ loginList.showMessage(html);
61+ }
62+
63+ function showPrompt(text, isSecret, isDefaultPrompt) {
64+ loginList.showPrompt(text, isSecret, isDefaultPrompt);
77 }
65 }
78
66
79 function tryToUnlock(toTheRight) {
67 function tryToUnlock(toTheRight) {
@@ -92,6 +80,20 @@ FocusScope {
92 }
80 }
93 }
81 }
94
82
83+ function hide() {
84+ coverPage.hide();
85+ }
86+
87+ function notifyAuthenticationSucceeded(showFakePassword) {
88+ if (showFakePassword) {
89+ loginList.showFakePassword();
90+ }
91+ }
92+
93+ function showLastChance() {
94+ // TODO
95+ }
96+
95 Rectangle {
97 Rectangle {
96 anchors.fill: parent
98 anchors.fill: parent
97 color: "black"
99 color: "black"
@@ -104,6 +106,7 @@ FocusScope {
104 height: parent.height
106 height: parent.height
105 width: parent.width
107 width: parent.width
106 draggable: !root.locked && !root.waiting
108 draggable: !root.locked && !root.waiting
109+ state: "LoginList"
107
110
108 infographics {
111 infographics {
109 height: 0.75 * parent.height
112 height: 0.75 * parent.height
@@ -122,21 +125,89 @@ FocusScope {
122 id: loginList
125 id: loginList
123 objectName: "loginList"
126 objectName: "loginList"
124
127
128+ width: units.gu(40)
125 anchors {
129 anchors {
126 left: parent.left
130 left: parent.left
127 leftMargin: Math.min(parent.width * 0.16, units.gu(20))
131 leftMargin: Math.min(parent.width * 0.16, units.gu(20))
128 top: parent.top
132 top: parent.top
133+ bottom: parent.bottom
129 }
134 }
130− width: units.gu(40)
131− height: inputMethod && inputMethod.visible ? parent.height - inputMethod.keyboardRectangle.height
132− : parent.height
133− Behavior on height { UbuntuNumberAnimation {} }
134
135
135− locked: root.locked
136+ boxVerticalOffset: (height - highlightedHeight -
136− waiting: root.waiting
137+ (inputMethod && inputMethod.visible ?
138+ inputMethod.keyboardRectangle.height : 0)) / 2
139+ Behavior on boxVerticalOffset { UbuntuNumberAnimation {} }
137
140
138− onSelected: root.selected(index)
141+ model: root.userModel
142+ currentSession: LightDMService.greeter.defaultSession
139 onResponded: root.responded(response)
143 onResponded: root.responded(response)
144+ onSelected: root.selected(index)
145+ onSessionChooserButtonClicked: parent.state = "SessionsList"
146+
147+ Keys.forwardTo: [sessionChooserLoader.item]
140 }
148 }
149+
150+ Loader {
151+ id: sessionChooserLoader
152+
153+ height: loginList.height
154+ width: loginList.width
155+ anchors {
156+ left: parent.left
157+ leftMargin: Math.min(parent.width * 0.16, units.gu(20))
158+ top: parent.top
159+ }
160+
161+ active: false
162+
163+ onLoaded: sessionChooserLoader.item.forceActiveFocus();
164+ Binding {
165+ target: sessionChooserLoader.item
166+ property: "initiallySelectedSession"
167+ value: loginList.currentSession
168+ }
169+
170+ Connections {
171+ target: sessionChooserLoader.item
172+ onSessionSelected: loginList.currentSession = sessionKey
173+ onShowLoginList: {
174+ coverPage.state = "LoginList"
175+ loginList.passwordInput.forceActiveFocus();
176+ }
177+ ignoreUnknownSignals: true
178+ }
179+ }
180+
181+ states: [
182+ State {
183+ name: "SessionsList"
184+ PropertyChanges { target: loginList; opacity: 0 }
185+ PropertyChanges { target: sessionChooserLoader;
186+ active: true;
187+ opacity: 1
188+ source: "SessionsList.qml"
189+ }
190+ },
191+
192+ State {
193+ name: "LoginList"
194+ PropertyChanges { target: loginList; opacity: 1 }
195+ PropertyChanges { target: sessionChooserLoader;
196+ active: false;
197+ opacity: 0
198+ source: "";
199+ }
200+ }
201+ ]
202+
203+ transitions: [
204+ Transition {
205+ from: "*"
206+ to: "*"
207+ UbuntuNumberAnimation {
208+ property: "opacity";
209+ }
210+ }
211+ ]
141 }
212 }
142}
213}
qml/Greeter/graphics/session_icons/gnome_badge.pngnew file

Binary file not shown.

qml/Greeter/graphics/session_icons/kde_badge.pngnew file

Binary file not shown.

qml/Greeter/graphics/session_icons/recovery_console_badge.pngnew file

Binary file not shown.

qml/Greeter/graphics/session_icons/ubuntu_badge.pngnew file

Binary file not shown.

qml/Greeter/graphics/session_icons/unknown_badge.pngnew file

Binary file not shown.

qml/Shell.qml+66−15
@@ -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,12 +146,49 @@ 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
152
174
153 function activateApplication(appId) {
175 function activateApplication(appId) {
176+ // Either open the app in our own session, or -- if we're acting as a
177+ // greeter -- ask the user's session to open it for us.
178+ if (shell.mode === "greeter") {
179+ activateURL("application:///" + appId + ".desktop");
180+ } else {
181+ startApp(appId);
182+ }
183+ }
184+
185+ function activateURL(url) {
186+ SessionBroadcast.requestUrlStart(AccountsService.user, url);
187+ greeter.notifyUserRequestedApp();
188+ panel.indicators.hide();
189+ }
190+
191+ function startApp(appId) {
154 if (ApplicationManager.findApplication(appId)) {
192 if (ApplicationManager.findApplication(appId)) {
155 ApplicationManager.requestFocusApplication(appId);
193 ApplicationManager.requestFocusApplication(appId);
156 } else {
194 } else {
@@ -162,7 +200,7 @@ StyledItem {
162 if (greeter.locked) {
200 if (greeter.locked) {
163 greeter.lockedApp = app;
201 greeter.lockedApp = app;
164 }
202 }
165− shell.activateApplication(app);
203+ startApp(app); // locked apps are always in our same session
166 }
204 }
167
205
168 Binding {
206 Binding {
@@ -259,9 +297,7 @@ StyledItem {
259 ? "phone"
297 ? "phone"
260 : shell.usageScenario
298 : shell.usageScenario
261 readonly property string qmlComponent: {
299 readonly property string qmlComponent: {
262− if (shell.mode === "greeter") {
263− return "Stages/AbstractStage.qml"
264− } else if (applicationsDisplayLoader.usageScenario === "phone") {
300+ if (applicationsDisplayLoader.usageScenario === "phone") {
265 return "Stages/PhoneStage.qml";
301 return "Stages/PhoneStage.qml";
266 } else if (applicationsDisplayLoader.usageScenario === "tablet") {
302 } else if (applicationsDisplayLoader.usageScenario === "tablet") {
267 return "Stages/TabletStage.qml";
303 return "Stages/TabletStage.qml";
@@ -339,7 +375,7 @@ StyledItem {
339 Binding {
375 Binding {
340 target: applicationsDisplayLoader.item
376 target: applicationsDisplayLoader.item
341 property: "background"
377 property: "background"
342− value: wallpaperResolver.background
378+ value: wallpaperResolver.cachedBackground
343 }
379 }
344 Binding {
380 Binding {
345 target: applicationsDisplayLoader.item
381 target: applicationsDisplayLoader.item
@@ -420,11 +456,13 @@ StyledItem {
420 id: integratedGreeter
456 id: integratedGreeter
421 Greeter {
457 Greeter {
422
458
459+ enabled: panel.indicators.fullyClosed // hides OSK when panel is open
423 hides: [launcher, panel.indicators]
460 hides: [launcher, panel.indicators]
424 tabletMode: shell.usageScenario != "phone"
461 tabletMode: shell.usageScenario != "phone"
425 launcherOffset: launcher.progress
462 launcherOffset: launcher.progress
426 forcedUnlock: wizard.active || shell.mode === "full-shell"
463 forcedUnlock: wizard.active || shell.mode === "full-shell"
427− background: wallpaperResolver.background
464+ background: wallpaperResolver.cachedBackground
465+ hasCustomBackground: wallpaperResolver.hasCustomBackground
428 allowFingerprint: !dialogs.hasActiveDialog &&
466 allowFingerprint: !dialogs.hasActiveDialog &&
429 !notifications.topmostIsFullscreen &&
467 !notifications.topmostIsFullscreen &&
430 !panel.indicators.shown
468 !panel.indicators.shown
@@ -499,11 +537,15 @@ StyledItem {
499 }
537 }
500
538
501 function showHome() {
539 function showHome() {
502− greeter.notifyUserRequestedApp("unity8-dash");
540+ greeter.notifyUserRequestedApp();
503
541
542+ if (shell.mode === "greeter") {
543+ SessionBroadcast.requestHomeShown(AccountsService.user);
544+ } else {
504− var animate = !LightDMService.greeter.active && !stages.shown
545+ var animate = !LightDMService.greeter.active && !stages.shown;
505− dash.setCurrentScope(0, animate, false)
546+ dash.setCurrentScope(0, animate, false);
506− ApplicationManager.requestFocusApplication("unity8-dash")
547+ ApplicationManager.requestFocusApplication("unity8-dash");
548+ }
507 }
549 }
508
550
509 function showDash() {
551 function showDash() {
@@ -599,7 +641,7 @@ StyledItem {
599 }
641 }
600 }
642 }
601 onLauncherApplicationSelected: {
643 onLauncherApplicationSelected: {
602− greeter.notifyUserRequestedApp(appId);
644+ greeter.notifyUserRequestedApp();
603 shell.activateApplication(appId);
645 shell.activateApplication(appId);
604 }
646 }
605 onShownChanged: {
647 onShownChanged: {
@@ -660,7 +702,7 @@ StyledItem {
660 objectName: "tutorial"
702 objectName: "tutorial"
661 anchors.fill: parent
703 anchors.fill: parent
662
704
663− paused: callManager.hasCalls || !greeter || greeter.shown ||
705+ paused: callManager.hasCalls || !greeter || greeter.active ||
664 wizard.active
706 wizard.active
665 delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
707 delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
666 inputMethod.visible ||
708 inputMethod.visible ||
@@ -701,7 +743,7 @@ StyledItem {
701 model: NotificationBackend.Model
743 model: NotificationBackend.Model
702 margin: units.gu(1)
744 margin: units.gu(1)
703 hasMouse: shell.hasMouse
745 hasMouse: shell.hasMouse
704− background: wallpaperResolver.background
746+ background: wallpaperResolver.cachedBackground
705
747
706 y: topmostIsFullscreen ? 0 : panel.panelHeight
748 y: topmostIsFullscreen ? 0 : panel.panelHeight
707 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
749 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
@@ -745,7 +787,14 @@ StyledItem {
745
787
746 Connections {
788 Connections {
747 target: SessionBroadcast
789 target: SessionBroadcast
748− onShowHome: showHome()
790+ onShowHome: if (shell.mode !== "greeter") showHome()
791+ }
792+
793+ URLDispatcher {
794+ id: urlDispatcher
795+ objectName: "urlDispatcher"
796+ active: shell.mode === "greeter"
797+ onUrlRequested: shell.activateURL(url)
749 }
798 }
750
799
751 ItemGrabber {
800 ItemGrabber {
@@ -766,6 +815,8 @@ StyledItem {
766 z: itemGrabber.z + 1
815 z: itemGrabber.z + 1
767 topBoundaryOffset: panel.panelHeight
816 topBoundaryOffset: panel.panelHeight
768
817
818+ confiningItem: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.itemConfiningMouseCursor : null
819+
769 property bool mouseNeverMoved: true
820 property bool mouseNeverMoved: true
770 Binding {
821 Binding {
771 target: cursor; property: "x"; value: shell.width / 2
822 target: cursor; property: "x"; value: shell.width / 2
qml/Stages/AbstractStage.qml+3
@@ -55,6 +55,9 @@ FocusScope {
55 | Qt.InvertedPortraitOrientation
55 | Qt.InvertedPortraitOrientation
56 | Qt.InvertedLandscapeOrientation
56 | Qt.InvertedLandscapeOrientation
57
57
58+ property Item itemConfiningMouseCursor: null
59+
60+
58 signal stageAboutToBeUnloaded
61 signal stageAboutToBeUnloaded
59 signal itemSnapshotRequested(Item item)
62 signal itemSnapshotRequested(Item item)
60
63
qml/Stages/DecoratedWindow.qml+2
@@ -56,6 +56,8 @@ FocusScope {
56 property alias stageHeight: moveHandler.stageHeight
56 property alias stageHeight: moveHandler.stageHeight
57 readonly property alias dragging: moveHandler.dragging
57 readonly property alias dragging: moveHandler.dragging
58
58
59+ readonly property Item clientAreaItem: applicationWindow
60+
59 signal closeClicked()
61 signal closeClicked()
60 signal maximizeClicked()
62 signal maximizeClicked()
61 signal maximizeHorizontallyClicked()
63 signal maximizeHorizontallyClicked()
qml/Stages/DesktopStage.qml+7−3
@@ -55,6 +55,11 @@ AbstractStage {
55
55
56 orientationChangesEnabled: true
56 orientationChangesEnabled: true
57
57
58+
59+ itemConfiningMouseCursor: !spreadShown && priv.focusedAppDelegate && priv.focusedAppDelegate.surface &&
60+ priv.focusedAppDelegate.surface.confinesMousePointer ?
61+ priv.focusedAppDelegate.clientAreaItem : null;
62+
58 GlobalShortcut {
63 GlobalShortcut {
59 id: showSpreadShortcut
64 id: showSpreadShortcut
60 shortcut: Qt.MetaModifier|Qt.Key_W
65 shortcut: Qt.MetaModifier|Qt.Key_W
@@ -237,12 +242,10 @@ AbstractStage {
237 anchors.fill: parent
242 anchors.fill: parent
238 focus: spread.state !== "altTab"
243 focus: spread.state !== "altTab"
239
244
240− CrossFadeImage {
245+ Wallpaper {
241 id: wallpaper
246 id: wallpaper
242 anchors.fill: parent
247 anchors.fill: parent
243 source: root.background
248 source: root.background
244− sourceSize { height: root.height; width: root.width }
245− fillMode: Image.PreserveAspectCrop
246 }
249 }
247
250
248 TopLevelSurfaceRepeater {
251 TopLevelSurfaceRepeater {
@@ -350,6 +353,7 @@ AbstractStage {
350 readonly property bool dragging: touchControls.overlayShown ? touchControls.dragging : decoratedWindow.dragging
353 readonly property bool dragging: touchControls.overlayShown ? touchControls.dragging : decoratedWindow.dragging
351
354
352 readonly property bool isDash: model.application.appId == "unity8-dash"
355 readonly property bool isDash: model.application.appId == "unity8-dash"
356+ readonly property alias clientAreaItem: decoratedWindow.clientAreaItem
353
357
354 function claimFocus() {
358 function claimFocus() {
355 if (spread.state == "altTab") {
359 if (spread.state == "altTab") {
qml/graphics/phone_background.jpgdeleted

Binary file not shown.

qml/graphics/tablet_background.jpgdeleted

Binary file not shown.

tests/CMakeLists.txt+8−13
@@ -37,27 +37,22 @@ set(UNITY_IMPORT_PATHS ${UNITY_IMPORT_PATHS} CACHE INTERNAL "")
37
37
38macro(unity8_parse_arguments)
38macro(unity8_parse_arguments)
39 cmake_parse_arguments(U8TEST
39 cmake_parse_arguments(U8TEST
40− "${QMLTEST_OPTIONS};LIGHTDM"
40+ "${QMLTEST_OPTIONS}"
41 "${QMLTEST_SINGLE}"
41 "${QMLTEST_SINGLE}"
42 "${QMLTEST_MULTI}"
42 "${QMLTEST_MULTI}"
43 ${ARGN}
43 ${ARGN}
44 )
44 )
45
45
46 set(ld_paths)
46 set(ld_paths)
47− if(U8TEST_LIGHTDM)
48− list(APPEND ld_paths
47+ list(APPEND ld_paths
49− ${UNITY_MOCKPATH}/libusermetrics
48+ ${UNITY_MOCKPATH}/libusermetrics
50− ${UNITY_MOCKPATH}/LightDM/IntegratedLightDM/liblightdm
49+ ${UNITY_MOCKPATH}/LightDM/IntegratedLightDM/liblightdm
51− )
50+ )
52− endif()
51+
53−
54− if(ld_paths)
55− string(REPLACE ";" ":" ld_library_path "${ld_paths}")
52+ string(REPLACE ";" ":" ld_library_path "${ld_paths}")
56− set(ld_library_path LD_LIBRARY_PATH=${ld_library_path})
53+ set(ld_library_path LD_LIBRARY_PATH=${ld_library_path})
57− endif()
58
54
59 set(U8TEST_ARGN "${ARGN}")
55 set(U8TEST_ARGN "${ARGN}")
60− list(REMOVE_ITEM U8TEST_ARGN LIGHTDM)
61
56
62 set(environment
57 set(environment
63 UNITY_TESTING=1
58 UNITY_TESTING=1
tests/mocks/AccountsService/AccountsService.cpp+19−3
@@ -15,7 +15,9 @@
15 */
15 */
16
16
17#include "AccountsService.h"
17#include "AccountsService.h"
18+#include "MockUsersModel.h"
18
19
20+#include <QLightDM/UsersModel>
19#include <paths.h>
21#include <paths.h>
20
22
21AccountsService::AccountsService(QObject* parent)
23AccountsService::AccountsService(QObject* parent)
@@ -23,15 +25,17 @@ AccountsService::AccountsService(QObject* parent)
23 m_enableFingerprintIdentification(true),
25 m_enableFingerprintIdentification(true),
24 m_enableLauncherWhileLocked(true),
26 m_enableLauncherWhileLocked(true),
25 m_enableIndicatorsWhileLocked(true),
27 m_enableIndicatorsWhileLocked(true),
26− m_backgroundFile(qmlDirectory() + "/graphics/phone_background.jpg"),
28+ m_backgroundFile(),
27 m_statsWelcomeScreen(true),
29 m_statsWelcomeScreen(true),
28 m_failedLogins(0),
30 m_failedLogins(0),
29 m_failedFingerprintLogins(0),
31 m_failedFingerprintLogins(0),
30 m_demoEdges(false),
32 m_demoEdges(false),
31 m_demoEdgesCompleted(),
33 m_demoEdgesCompleted(),
32 m_hereEnabled(false),
34 m_hereEnabled(false),
33− m_hereLicensePath("")
35+ m_hereLicensePath(""),
36+ m_usersModel(new MockUsersModel(this))
34{
37{
38+ m_usersModel->setMockMode("full");
35}
39}
36
40
37QString AccountsService::user() const
41QString AccountsService::user() const
@@ -44,6 +48,7 @@ void AccountsService::setUser(const QString &user)
44 m_user = user;
48 m_user = user;
45 Q_EMIT userChanged();
49 Q_EMIT userChanged();
46 Q_EMIT passwordDisplayHintChanged();
50 Q_EMIT passwordDisplayHintChanged();
51+ Q_EMIT backgroundFileChanged();
47}
52}
48
53
49bool AccountsService::demoEdges() const
54bool AccountsService::demoEdges() const
@@ -111,7 +116,18 @@ void AccountsService::setEnableIndicatorsWhileLocked(bool enableIndicatorsWhileL
111
116
112QString AccountsService::backgroundFile() const
117QString AccountsService::backgroundFile() const
113{
118{
119+ if (!m_backgroundFile.isEmpty()) {
114− return m_backgroundFile;
120+ return m_backgroundFile;
121+ }
122+
123+ // Check if our mock user has a background set in liblightdm
124+ for (int i = 0; i < m_usersModel->count(); i++) {
125+ if (m_usersModel->data(i, QLightDM::UsersModel::NameRole) == m_user) {
126+ return m_usersModel->data(i, QLightDM::UsersModel::BackgroundPathRole).toString();
127+ }
128+ }
129+
130+ return QString();
115}
131}
116
132
117void AccountsService::setBackgroundFile(const QString &backgroundFile)
133void AccountsService::setBackgroundFile(const QString &backgroundFile)
tests/mocks/AccountsService/AccountsService.h+3
@@ -22,6 +22,8 @@
22#include <QStringList>
22#include <QStringList>
23#include <QVariant>
23#include <QVariant>
24
24
25+class MockUsersModel;
26+
25class AccountsService: public QObject
27class AccountsService: public QObject
26{
28{
27 Q_OBJECT
29 Q_OBJECT
@@ -163,6 +165,7 @@ private:
163 QString m_realName;
165 QString m_realName;
164 QStringList m_kbdMap;
166 QStringList m_kbdMap;
165 QString m_email;
167 QString m_email;
168+ MockUsersModel *m_usersModel;
166};
169};
167
170
168#endif
171#endif
tests/mocks/AccountsService/CMakeLists.txt+14
@@ -1,10 +1,24 @@
1+include_directories(
2+ ${CMAKE_CURRENT_BINARY_DIR}
3+ ${CMAKE_SOURCE_DIR}/plugins/LightDM
4+ ${CMAKE_SOURCE_DIR}/plugins/Utils
5+ ${CMAKE_SOURCE_DIR}/tests/mocks/LightDM/IntegratedLightDM
6+ )
7+
1add_library(MockAccountsService-qml MODULE
8add_library(MockAccountsService-qml MODULE
9+ ${CMAKE_SOURCE_DIR}/plugins/LightDM/UsersModel.cpp
10+ ${CMAKE_SOURCE_DIR}/plugins/Utils/unitysortfilterproxymodelqml.cpp
11+ ${CMAKE_SOURCE_DIR}/tests/mocks/LightDM/IntegratedLightDM/MockUsersModel.cpp
2 AccountsService.cpp
12 AccountsService.cpp
3 plugin.cpp
13 plugin.cpp
4 )
14 )
5
15
6qt5_use_modules(MockAccountsService-qml DBus Qml)
16qt5_use_modules(MockAccountsService-qml DBus Qml)
7
17
18+target_link_libraries(MockAccountsService-qml
19+ MockLightDM
20+ )
21+
8add_unity8_mock(AccountsService 0.1 AccountsService
22add_unity8_mock(AccountsService 0.1 AccountsService
9 PREFIX mocks
23 PREFIX mocks
10 TARGETS MockAccountsService-qml
24 TARGETS MockAccountsService-qml
tests/mocks/CMakeLists.txt+1
@@ -39,6 +39,7 @@ add_subdirectory(Lights)
39add_subdirectory(MeeGo)
39add_subdirectory(MeeGo)
40add_subdirectory(Powerd)
40add_subdirectory(Powerd)
41add_subdirectory(QMenuModel)
41add_subdirectory(QMenuModel)
42+add_subdirectory(SessionBroadcast)
42add_subdirectory(Ubuntu)
43add_subdirectory(Ubuntu)
43add_subdirectory(Unity)
44add_subdirectory(Unity)
44add_subdirectory(QtMultimedia)
45add_subdirectory(QtMultimedia)
tests/mocks/LightDM/IntegratedLightDM/MockSessionsModel.cpp+29
@@ -18,6 +18,23 @@
18#include "MockSessionsModel.h"
18#include "MockSessionsModel.h"
19#include <QLightDM/SessionsModel>
19#include <QLightDM/SessionsModel>
20
20
21+
22+int MockSessionsModel::numSessions() const
23+{
24+ QLightDM::SessionsModel* qSessionsModel =
25+ static_cast<QLightDM::SessionsModel*>(sourceModel());
26+
27+ return qSessionsModel->numSessions();
28+}
29+
30+int MockSessionsModel::numAvailableSessions() const
31+{
32+ QLightDM::SessionsModel* qSessionsModel =
33+ static_cast<QLightDM::SessionsModel*>(sourceModel());
34+
35+ return qSessionsModel->numAvailableSessions();
36+}
37+
21QString MockSessionsModel::testScenario() const
38QString MockSessionsModel::testScenario() const
22{
39{
23 QLightDM::SessionsModel* qSessionsModel =
40 QLightDM::SessionsModel* qSessionsModel =
@@ -26,6 +43,17 @@ QString MockSessionsModel::testScenario() const
26 return qSessionsModel->testScenario();
43 return qSessionsModel->testScenario();
27}
44}
28
45
46+void MockSessionsModel::setNumSessions(const int numSessions)
47+{
48+ QLightDM::SessionsModel* qSessionsModel =
49+ static_cast<QLightDM::SessionsModel*>(sourceModel());
50+
51+ if (qSessionsModel->numSessions() != numSessions) {
52+ qSessionsModel->setNumSessions(numSessions);
53+ Q_EMIT numSessionsChanged();
54+ }
55+}
56+
29void MockSessionsModel::setTestScenario(const QString testScenario)
57void MockSessionsModel::setTestScenario(const QString testScenario)
30{
58{
31 QLightDM::SessionsModel* qSessionsModel =
59 QLightDM::SessionsModel* qSessionsModel =
@@ -33,5 +61,6 @@ void MockSessionsModel::setTestScenario(const QString testScenario)
33
61
34 if (qSessionsModel->testScenario() != testScenario) {
62 if (qSessionsModel->testScenario() != testScenario) {
35 qSessionsModel->setTestScenario(testScenario);
63 qSessionsModel->setTestScenario(testScenario);
64+ Q_EMIT testScenarioChanged();
36 }
65 }
37}
66}
tests/mocks/LightDM/IntegratedLightDM/MockSessionsModel.h+12−2
@@ -24,11 +24,21 @@ class MockSessionsModel : public SessionsModel
24{
24{
25 Q_OBJECT
25 Q_OBJECT
26
26
27+ Q_PROPERTY(int numAvailableSessions READ numAvailableSessions CONSTANT)
28+ Q_PROPERTY(int numSessions READ numSessions WRITE setNumSessions NOTIFY numSessionsChanged)
27− Q_PROPERTY(QString testScenario READ testScenario WRITE setTestScenario)
29+ Q_PROPERTY(QString testScenario READ testScenario WRITE setTestScenario NOTIFY testScenarioChanged)
28
30
29public:
31public:
32+ int numAvailableSessions() const;
33+ int numSessions() const;
30 QString testScenario() const;
34 QString testScenario() const;
35+ void setNumSessions(const int numSessions);
31− void setTestScenario(QString testScenario);
36+ void setTestScenario(const QString testScenario);
37+
38+Q_SIGNALS:
39+ void numSessionsChanged();
40+ void testScenarioChanged();
41+
32};
42};
33
43
34#endif // MOCK_UNITY_SESSIONSMODEL_H
44#endif // MOCK_UNITY_SESSIONSMODEL_H
tests/mocks/LightDM/IntegratedLightDM/MockUsersModel.cpp+5
@@ -19,6 +19,11 @@
19#include <QLightDM/UsersModel>
19#include <QLightDM/UsersModel>
20#include <QSortFilterProxyModel>
20#include <QSortFilterProxyModel>
21
21
22+MockUsersModel::MockUsersModel(QObject* parent)
23+ : UsersModel(parent)
24+{
25+}
26+
22QString MockUsersModel::mockMode() const
27QString MockUsersModel::mockMode() const
23{
28{
24 QLightDM::UsersModel* qUsersModel =
29 QLightDM::UsersModel* qUsersModel =
tests/mocks/LightDM/IntegratedLightDM/MockUsersModel.h+2
@@ -27,6 +27,8 @@ class MockUsersModel : public UsersModel
27 Q_PROPERTY(QString mockMode READ mockMode WRITE setMockMode NOTIFY mockModeChanged)
27 Q_PROPERTY(QString mockMode READ mockMode WRITE setMockMode NOTIFY mockModeChanged)
28
28
29public:
29public:
30+ explicit MockUsersModel(QObject* parent=0);
31+
30 QString mockMode() const;
32 QString mockMode() const;
31 void setMockMode(QString mockMode);
33 void setMockMode(QString mockMode);
32
34
tests/mocks/LightDM/IntegratedLightDM/liblightdm/Greeter.cpp−5
@@ -69,11 +69,6 @@ bool Greeter::showRemoteLoginHint() const
69 return true;
69 return true;
70}
70}
71
71
72−bool Greeter::lockHint () const
73−{
74− return false;
75−}
76−
77QString Greeter::selectUserHint() const
72QString Greeter::selectUserHint() const
78{
73{
79 Q_D(const Greeter);
74 Q_D(const Greeter);
tests/mocks/LightDM/IntegratedLightDM/liblightdm/Greeter.h−2
@@ -41,7 +41,6 @@ class Q_DECL_EXPORT Greeter : public QObject
41
41
42 Q_PROPERTY(QString hostname READ hostname CONSTANT)
42 Q_PROPERTY(QString hostname READ hostname CONSTANT)
43 Q_PROPERTY(bool hasGuestAccount READ hasGuestAccountHint CONSTANT)
43 Q_PROPERTY(bool hasGuestAccount READ hasGuestAccountHint CONSTANT)
44− Q_PROPERTY(bool locked READ lockHint CONSTANT)
45
44
46 Q_ENUMS(PromptType MessageType)
45 Q_ENUMS(PromptType MessageType)
47
46
@@ -67,7 +66,6 @@ public:
67 bool hideUsersHint() const;
66 bool hideUsersHint() const;
68 bool showManualLoginHint() const;
67 bool showManualLoginHint() const;
69 bool showRemoteLoginHint() const;
68 bool showRemoteLoginHint() const;
70− bool lockHint () const;
71 bool hasGuestAccountHint() const;
69 bool hasGuestAccountHint() const;
72 QString selectUserHint() const;
70 QString selectUserHint() const;
73 void setSelectUserHint(const QString &selectUserHint); // only in mock
71 void setSelectUserHint(const QString &selectUserHint); // only in mock
tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModel.cpp+22
@@ -80,12 +80,34 @@ int SessionsModel::rowCount(const QModelIndex& parent) const
80 }
80 }
81}
81}
82
82
83+int SessionsModel::numSessions() const
84+{
85+ Q_D(const SessionsModel);
86+ return d->numSessions;
87+}
88+
89+int SessionsModel::numAvailableSessions() const
90+{
91+ Q_D(const SessionsModel);
92+ return d->numAvailableSessions();
93+}
94+
83QString SessionsModel::testScenario() const
95QString SessionsModel::testScenario() const
84{
96{
85 Q_D(const SessionsModel);
97 Q_D(const SessionsModel);
86 return d->testScenario;
98 return d->testScenario;
87}
99}
88
100
101+void SessionsModel::setNumSessions(int numSessions)
102+{
103+ Q_D(SessionsModel);
104+
105+ if (d->numSessions != numSessions) {
106+ d->numSessions = numSessions;
107+ d->resetEntries();
108+ }
109+}
110+
89void SessionsModel::setTestScenario(QString testScenario)
111void SessionsModel::setTestScenario(QString testScenario)
90{
112{
91 Q_D(SessionsModel);
113 Q_D(SessionsModel);
tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModel.h+3−3
@@ -31,9 +31,6 @@ class Q_DECL_EXPORT SessionsModel : public QAbstractListModel
31
31
32 Q_ENUMS(SessionModelRoles SessionType)
32 Q_ENUMS(SessionModelRoles SessionType)
33
33
34− // Mock-only API for testing purposes
35− Q_PROPERTY(QString testScenario READ testScenario WRITE setTestScenario)
36−
37 public:
34 public:
38
35
39 enum SessionModelRoles {
36 enum SessionModelRoles {
@@ -57,7 +54,10 @@ class Q_DECL_EXPORT SessionsModel : public QAbstractListModel
57 int rowCount(const QModelIndex& parent) const override;
54 int rowCount(const QModelIndex& parent) const override;
58 QVariant data(const QModelIndex& index, int role) const override;
55 QVariant data(const QModelIndex& index, int role) const override;
59
56
57+ int numSessions() const;
58+ int numAvailableSessions() const;
60 QString testScenario() const;
59 QString testScenario() const;
60+ void setNumSessions(int numSessions);
61 void setTestScenario(QString testScenario);
61 void setTestScenario(QString testScenario);
62
62
63 protected:
63 protected:
tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModelPrivate.cpp+27−17
@@ -16,24 +16,44 @@
16
16
17#include "SessionsModel.h"
17#include "SessionsModel.h"
18#include "SessionsModelPrivate.h"
18#include "SessionsModelPrivate.h"
19−
19+#include <QDebug>
20namespace QLightDM
20namespace QLightDM
21{
21{
22
22
23SessionsModelPrivate::SessionsModelPrivate(SessionsModel* parent)
23SessionsModelPrivate::SessionsModelPrivate(SessionsModel* parent)
24 : testScenario("singleSession")
24 : testScenario("singleSession")
25+ , m_availableSessions(
26+ {
27+ {"ubuntu", "", "Ubuntu", ""},
28+ {"ubuntu-2d", "", "Ubuntu 2D", ""},
29+ {"gnome", "", "GNOME", ""},
30+ {"gnome-classic", "", "GNOME Classic", ""},
31+ {"gnome-flashback-compiz", "", "GNOME Flashback (Compiz)", ""},
32+ {"gnome-flashback-metacity", "", "GNOME Flashback (Metacity)", ""},
33+ {"gnome-wayland", "", "GNOME on Wayland", ""},
34+ {"plasma", "", "Plasma", ""},
35+ {"kde", "", "KDE" , ""},
36+ {"xterm", "", "Recovery Console", ""},
37+ {"", "", "Unknown?", ""}
38+ })
25 , q_ptr(parent)
39 , q_ptr(parent)
26{
40{
41+ numSessions = numAvailableSessions();
27 resetEntries();
42 resetEntries();
28}
43}
29
44
45+int SessionsModelPrivate::numAvailableSessions() const
46+{
47+ return m_availableSessions.length();
48+}
49+
30void SessionsModelPrivate::resetEntries()
50void SessionsModelPrivate::resetEntries()
31{
51{
32 Q_Q(SessionsModel);
52 Q_Q(SessionsModel);
33
53
34 q->beginResetModel();
54 q->beginResetModel();
35 if (testScenario == "multipleSessions") {
55 if (testScenario == "multipleSessions") {
36− resetEntries_multipleSessions();
56+ resetEntries_multipleSessions(numSessions);
37 } else if (testScenario == "noSessions") {
57 } else if (testScenario == "noSessions") {
38 resetEntries_noSessions();
58 resetEntries_noSessions();
39 } else {
59 } else {
@@ -42,22 +62,12 @@ void SessionsModelPrivate::resetEntries()
42 q->endResetModel();
62 q->endResetModel();
43}
63}
44
64
45−void SessionsModelPrivate::resetEntries_multipleSessions()
65+void SessionsModelPrivate::resetEntries_multipleSessions(int numSessions)
46{
66{
47− sessionItems =
67+ sessionItems.clear();
48− {
68+ for (int i = 0; i < numSessions; i++) {
49− {"ubuntu", "", "Ubuntu", ""},
69+ sessionItems.append(m_availableSessions.value(i));
50− {"ubuntu-2d", "", "Ubuntu 2D", ""},
70+ }
51− {"gnome", "", "GNOME", ""},
52− {"gnome-classic", "", "GNOME Classic", ""},
53− {"gnome-flashback-compiz", "", "GNOME Flashback (Compiz)", ""},
54− {"gnome-flashback-metacity", "", "GNOME Flashback (Metacity)", ""},
55− {"gnome-wayland", "", "GNOME on Wayland", ""},
56− {"plasma", "", "Plasma", ""},
57− {"kde", "", "KDE" , ""},
58− {"xterm", "", "Recovery Console", ""},
59− {"", "", "Unknown?", ""}
60− };
61}
71}
62
72
63void SessionsModelPrivate::resetEntries_noSessions()
73void SessionsModelPrivate::resetEntries_noSessions()
tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModelPrivate.h+5−1
@@ -40,7 +40,10 @@ public:
40 explicit SessionsModelPrivate(SessionsModel* parent=0);
40 explicit SessionsModelPrivate(SessionsModel* parent=0);
41 virtual ~SessionsModelPrivate() = default;
41 virtual ~SessionsModelPrivate() = default;
42
42
43+ int numAvailableSessions() const;
44+
43 QList<SessionItem> sessionItems;
45 QList<SessionItem> sessionItems;
46+ int numSessions;
44 QString testScenario;
47 QString testScenario;
45
48
46 void resetEntries();
49 void resetEntries();
@@ -48,7 +51,8 @@ protected:
48 SessionsModel* const q_ptr;
51 SessionsModel* const q_ptr;
49
52
50private:
53private:
54+ const QList<SessionItem> m_availableSessions;
51− void resetEntries_multipleSessions();
55+ void resetEntries_multipleSessions(int numSessions);
52 void resetEntries_noSessions();
56 void resetEntries_noSessions();
53 void resetEntries_singleSession();
57 void resetEntries_singleSession();
54 Q_DECLARE_PUBLIC(SessionsModel)
58 Q_DECLARE_PUBLIC(SessionsModel)
tests/mocks/LightDM/IntegratedLightDM/liblightdm/UsersModelPrivate.cpp+15−1
@@ -17,6 +17,8 @@
17#include "UsersModelPrivate.h"
17#include "UsersModelPrivate.h"
18#include "UsersModel.h"
18#include "UsersModel.h"
19
19
20+#include <QDir>
21+
20namespace QLightDM
22namespace QLightDM
21{
23{
22
24
@@ -53,6 +55,17 @@ void UsersModelPrivate::resetEntries()
53 entries[i].uid = i + 1;
55 entries[i].uid = i + 1;
54 }
56 }
55
57
58+ // Assign backgrounds
59+ QDir backgroundDir("/usr/share/backgrounds");
60+ QStringList backgrounds = backgroundDir.entryList(QDir::Files);
61+ if (!backgrounds.empty()) {
62+ for (int i = 0; i < entries.size(); i++) {
63+ if (entries[i].background.isNull()) {
64+ entries[i].background = backgroundDir.filePath(backgrounds[i % backgrounds.size()]);
65+ }
66+ }
67+ }
68+
56 q->endResetModel();
69 q->endResetModel();
57}
70}
58
71
@@ -96,7 +109,7 @@ void UsersModelPrivate::resetEntries_full()
96 { "wide-info-prompt", "Wide Info Prompt", 0, 0, false, false, "ubuntu", 0 },
109 { "wide-info-prompt", "Wide Info Prompt", 0, 0, false, false, "ubuntu", 0 },
97 { "multi-info-prompt", "Multi Info Prompt", 0, 0, false, false, "ubuntu", 0 },
110 { "multi-info-prompt", "Multi Info Prompt", 0, 0, false, false, "ubuntu", 0 },
98 { "long-name", "Long name (far far too long to fit, seriously this would never fit on the screen, you will never see this part of the name)", 0, 0, false, false, "ubuntu", 0 },
111 { "long-name", "Long name (far far too long to fit, seriously this would never fit on the screen, you will never see this part of the name)", 0, 0, false, false, "ubuntu", 0 },
99− { "color-background", "Color Background", "#dd4814", 0, false, false, "ubuntu", 0 },
112+ { "color-background", "Color Background", "#E95420", 0, false, false, "ubuntu", 0 },
100 // white and black are a bit redundant, but useful for manually testing if UI is still readable
113 // white and black are a bit redundant, but useful for manually testing if UI is still readable
101 { "white-background", "White Background", "#ffffff", 0, false, false, "ubuntu", 0 },
114 { "white-background", "White Background", "#ffffff", 0, false, false, "ubuntu", 0 },
102 { "black-background", "Black Background", "#000000", 0, false, false, "ubuntu", 0 },
115 { "black-background", "Black Background", "#000000", 0, false, false, "ubuntu", 0 },
@@ -104,6 +117,7 @@ void UsersModelPrivate::resetEntries_full()
104 { "unicode", "가나다라마", 0, 0, false, false, "ubuntu", 0 },
117 { "unicode", "가나다라마", 0, 0, false, false, "ubuntu", 0 },
105 { "no-response", "No Response", 0, 0, false, false, "ubuntu", 0 },
118 { "no-response", "No Response", 0, 0, false, false, "ubuntu", 0 },
106 { "empty-name", "", 0, 0, false, false, "ubuntu", 0 },
119 { "empty-name", "", 0, 0, false, false, "ubuntu", 0 },
120+ { "active", "Active Account", 0, 0, true, false, "ubuntu", 0 },
107 };
121 };
108}
122}
109
123
tests/mocks/LightDM/IntegratedLightDM/plugin.cpp+1−1
@@ -72,7 +72,7 @@ void LightDMPlugin::registerTypes(const char *uri)
72 qRegisterMetaType<QLightDM::Greeter::PromptType>("QLightDM::Greeter::PromptType");
72 qRegisterMetaType<QLightDM::Greeter::PromptType>("QLightDM::Greeter::PromptType");
73 qmlRegisterSingletonType<MockGreeter>(uri, 0, 1, "Greeter", greeter_provider);
73 qmlRegisterSingletonType<MockGreeter>(uri, 0, 1, "Greeter", greeter_provider);
74 qmlRegisterSingletonType<MockSessionsModel>(uri, 0, 1, "Sessions", sessions_provider);
74 qmlRegisterSingletonType<MockSessionsModel>(uri, 0, 1, "Sessions", sessions_provider);
75− qmlRegisterUncreatableType<SessionsModel>(uri, 0, 1, "SessionRoles", "Type is not instantiable");
75+ qmlRegisterUncreatableType<QLightDM::SessionsModel>(uri, 0, 1, "SessionRoles", "Type is not instantiable");
76 qmlRegisterSingletonType<MockUsersModel>(uri, 0, 1, "Users", users_provider);
76 qmlRegisterSingletonType<MockUsersModel>(uri, 0, 1, "Users", users_provider);
77 qmlRegisterUncreatableType<QLightDM::UsersModel>(uri, 0, 1, "UserRoles", "Type is not instantiable");
77 qmlRegisterUncreatableType<QLightDM::UsersModel>(uri, 0, 1, "UserRoles", "Type is not instantiable");
78 qmlRegisterSingletonType<UserMetricsOutput::UserMetrics>(uri, 0, 1, "Infographic", infographic_provider);
78 qmlRegisterSingletonType<UserMetricsOutput::UserMetrics>(uri, 0, 1, "Infographic", infographic_provider);
tests/mocks/SessionBroadcast/CMakeLists.txtnew file+8
@@ -0,0 +1,8 @@
1+add_library(MockSessionBroadcast-qml MODULE
2+ plugin.cpp
3+ SessionBroadcast.cpp
4+ )
5+
6+qt5_use_modules(MockSessionBroadcast-qml Qml)
7+
8+add_unity8_mock(SessionBroadcast 0.1 SessionBroadcast TARGETS MockSessionBroadcast-qml)
tests/mocks/SessionBroadcast/SessionBroadcast.cppnew file+34
@@ -0,0 +1,34 @@
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 "SessionBroadcast.h"
18+
19+SessionBroadcast::SessionBroadcast(QObject* parent)
20+ : QObject(parent)
21+{
22+}
23+
24+void SessionBroadcast::requestUrlStart(const QString &, const QString &url)
25+{
26+ // No user name guards, we won't worry about that aspect of the plugin here
27+ Q_EMIT startUrl(url);
28+}
29+
30+void SessionBroadcast::requestHomeShown(const QString &)
31+{
32+ // No user name guards, we won't worry about that aspect of the plugin here
33+ Q_EMIT showHome();
34+}
tests/mocks/SessionBroadcast/SessionBroadcast.hnew file+39
@@ -0,0 +1,39 @@
1+/*
2+ * Copyright (C) 2012,2013 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+ * Authors: Michael Terry <michael.terry@canonical.com>
17+ */
18+
19+#ifndef UNITY_MOCK_SESSIONBROADCAST_H
20+#define UNITY_MOCK_SESSIONBROADCAST_H
21+
22+#include <QObject>
23+
24+class SessionBroadcast: public QObject
25+{
26+ Q_OBJECT
27+
28+public:
29+ explicit SessionBroadcast(QObject *parent = 0);
30+
31+ Q_INVOKABLE void requestUrlStart(const QString &username, const QString &url);
32+ Q_INVOKABLE void requestHomeShown(const QString &username);
33+
34+Q_SIGNALS:
35+ void startUrl(const QString &url);
36+ void showHome();
37+};
38+
39+#endif
tests/mocks/SessionBroadcast/plugin.cppnew 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+#include "plugin.h"
18+#include "SessionBroadcast.h"
19+
20+#include <QtQml/qqml.h>
21+
22+static QObject *broadcast_provider(QQmlEngine *, QJSEngine *)
23+{
24+ return new SessionBroadcast();
25+}
26+
27+void PowerdPlugin::registerTypes(const char *uri)
28+{
29+ Q_ASSERT(uri == QLatin1String("SessionBroadcast"));
30+ qmlRegisterSingletonType<SessionBroadcast>(uri, 0, 1, "SessionBroadcast", broadcast_provider);
31+}
tests/mocks/SessionBroadcast/plugin.hnew file+32
@@ -0,0 +1,32 @@
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+#ifndef MOCK_SESSIONBROADCAST_PLUGIN_H
18+#define MOCK_SESSIONBROADCAST_PLUGIN_H
19+
20+#include <QtQml/QQmlEngine>
21+#include <QtQml/QQmlExtensionPlugin>
22+
23+class PowerdPlugin : 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+};
31+
32+#endif
tests/mocks/SessionBroadcast/qmldirnew file+2
@@ -0,0 +1,2 @@
1+module SessionBroadcast
2+plugin MockSessionBroadcast-qml
tests/mocks/Ubuntu/DownloadDaemonListener/MockDownloadTracker.cpp+3−1
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 - Canonical Ltd.
2+ * Copyright (C) 2013-2016 Canonical Ltd.
3 *
3 *
4 * This program is free software: you can redistribute it and/or modify it
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License, as
5 * under the terms of the GNU Lesser General Public License, as
@@ -67,6 +67,8 @@ void MockDownloadTracker::startService()
67 Q_EMIT finished("downloadComplete");
67 Q_EMIT finished("downloadComplete");
68 }else if(m_dbusPath == "error") {
68 }else if(m_dbusPath == "error") {
69 Q_EMIT error("DOWNLOAD ERROR");
69 Q_EMIT error("DOWNLOAD ERROR");
70+ }else if(m_dbusPath == "processing") {
71+ Q_EMIT processing(m_dbusPath);
70 }
72 }
71 }
73 }
72}
74}
tests/mocks/Ubuntu/DownloadDaemonListener/MockDownloadTracker.h+2−1
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 - Canonical Ltd.
2+ * Copyright (C) 2013-2016 Canonical Ltd.
3 *
3 *
4 * This program is free software: you can redistribute it and/or modify it
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License, as
5 * under the terms of the GNU Lesser General Public License, as
@@ -51,6 +51,7 @@ Q_SIGNALS:
51 void error(const QString &error);
51 void error(const QString &error);
52 void finished(const QString &path);
52 void finished(const QString &path);
53 void paused(bool success);
53 void paused(bool success);
54+ void processing(const QString &path);
54 void progress(qulonglong received, qulonglong total);
55 void progress(qulonglong received, qulonglong total);
55 void resumed(bool success);
56 void resumed(bool success);
56 void started(bool success);
57 void started(bool success);
tests/mocks/Ubuntu/Telephony/MockTelepathyHelper.cpp+27
@@ -20,6 +20,8 @@
20
20
21MockTelepathyHelper::MockTelepathyHelper(QObject *parent)
21MockTelepathyHelper::MockTelepathyHelper(QObject *parent)
22 : QObject(parent)
22 : QObject(parent)
23+ , m_ready(true)
24+ , m_emergencyCallsAvailable(true)
23{
25{
24}
26}
25
27
@@ -33,3 +35,28 @@ void MockTelepathyHelper::registerChannelObserver(const QString& name)
33{
35{
34 Q_UNUSED(name);
36 Q_UNUSED(name);
35}
37}
38+
39+bool MockTelepathyHelper::ready() const
40+{
41+ return m_ready;
42+}
43+
44+void MockTelepathyHelper::setReady(bool value)
45+{
46+ if (m_ready != value) {
47+ m_ready = value;
48+ Q_EMIT readyChanged();
49+ }
50+}
51+bool MockTelepathyHelper::emergencyCallsAvailable() const
52+{
53+ return m_emergencyCallsAvailable;
54+}
55+
56+void MockTelepathyHelper::setEmergencyCallsAvailable(bool value)
57+{
58+ if (m_emergencyCallsAvailable != value) {
59+ m_emergencyCallsAvailable = value;
60+ Q_EMIT emergencyCallsAvailableChanged();
61+ }
62+}
tests/mocks/Ubuntu/Telephony/MockTelepathyHelper.h+22
@@ -24,6 +24,14 @@
24class MockTelepathyHelper : public QObject
24class MockTelepathyHelper : public QObject
25{
25{
26 Q_OBJECT
26 Q_OBJECT
27+ Q_PROPERTY(bool ready
28+ READ ready
29+ WRITE setReady // only in mock
30+ NOTIFY readyChanged)
31+ Q_PROPERTY(bool emergencyCallsAvailable
32+ READ emergencyCallsAvailable
33+ WRITE setEmergencyCallsAvailable // only in mock
34+ NOTIFY emergencyCallsAvailableChanged)
27 Q_DISABLE_COPY(MockTelepathyHelper)
35 Q_DISABLE_COPY(MockTelepathyHelper)
28public:
36public:
29 MockTelepathyHelper(QObject *parent = 0);
37 MockTelepathyHelper(QObject *parent = 0);
@@ -31,6 +39,20 @@ public:
31 static MockTelepathyHelper *instance();
39 static MockTelepathyHelper *instance();
32
40
33 Q_INVOKABLE void registerChannelObserver(const QString& name);
41 Q_INVOKABLE void registerChannelObserver(const QString& name);
42+
43+ bool ready() const;
44+ void setReady(bool value);
45+
46+ bool emergencyCallsAvailable() const;
47+ void setEmergencyCallsAvailable(bool value);
48+
49+Q_SIGNALS:
50+ void readyChanged();
51+ void emergencyCallsAvailableChanged();
52+
53+private:
54+ bool m_ready;
55+ bool m_emergencyCallsAvailable;
34};
56};
35
57
36#endif // MOCKCONTACTWATCHER_H
58#endif // MOCKCONTACTWATCHER_H
tests/mocks/Unity/Application/MirSurface.h+2
@@ -104,6 +104,8 @@ public:
104 bool focused() const override;
104 bool focused() const override;
105 QRect inputBounds() const override;
105 QRect inputBounds() const override;
106
106
107+ bool confinesMousePointer() const override { return false; }
108+
107 Q_INVOKABLE void requestFocus() override;
109 Q_INVOKABLE void requestFocus() override;
108
110
109 Q_INVOKABLE void close() override;
111 Q_INVOKABLE void close() override;
tests/mocks/Utils/CMakeLists.txt+1
@@ -32,6 +32,7 @@ set(QMLPLUGIN_SRC
32 constants.cpp
32 constants.cpp
33 plugin.cpp
33 plugin.cpp
34 windowstatestorage.cpp
34 windowstatestorage.cpp
35+ URLDispatcher.cpp
35 )
36 )
36
37
37add_library(FakeUtils-qml SHARED
38add_library(FakeUtils-qml SHARED
tests/mocks/Utils/URLDispatcher.cppnew file+38
@@ -0,0 +1,38 @@
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 "URLDispatcher.h"
18+
19+#include <QDBusConnection>
20+
21+URLDispatcher::URLDispatcher(QObject *parent)
22+ : QObject(parent)
23+ , m_active(false)
24+{
25+}
26+
27+bool URLDispatcher::active() const
28+{
29+ return m_active;
30+}
31+
32+void URLDispatcher::setActive(bool value)
33+{
34+ if (m_active != value) {
35+ m_active = value;
36+ Q_EMIT activeChanged();
37+ }
38+}
tests/mocks/Utils/URLDispatcher.hnew file+47
@@ -0,0 +1,47 @@
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+#ifndef UNITY_URLDISPATCHER_H
18+#define UNITY_URLDISPATCHER_H
19+
20+#include <QObject>
21+#include <QString>
22+
23+// This class manages our url-dispatcher interception. We intercept
24+// url-dispatcher because rather than spawning the handler for the URL
25+// in our own session, we want to notify the user session to do it for us
26+// (and start an unlock in the process).
27+
28+class URLDispatcher : public QObject
29+{
30+ Q_OBJECT
31+ Q_PROPERTY(bool active READ active WRITE setActive NOTIFY activeChanged)
32+
33+public:
34+ explicit URLDispatcher(QObject *parent=0);
35+
36+ bool active() const;
37+ void setActive(bool active);
38+
39+Q_SIGNALS:
40+ void urlRequested(const QString &url);
41+ void activeChanged();
42+
43+private:
44+ bool m_active;
45+};
46+
47+#endif
tests/mocks/Utils/plugin.cpp+2
@@ -25,6 +25,7 @@
25#include "plugin.h"
25#include "plugin.h"
26#include "windowstatestorage.h"
26#include "windowstatestorage.h"
27#include "constants.h"
27#include "constants.h"
28+#include "URLDispatcher.h"
28
29
29// plugin
30// plugin
30#include <activefocuslogger.h>
31#include <activefocuslogger.h>
@@ -80,4 +81,5 @@ void FakeUtilsPlugin::registerTypes(const char *uri)
80 qmlRegisterType<InputEventGenerator>(uri, 0, 1, "InputEventGenerator");
81 qmlRegisterType<InputEventGenerator>(uri, 0, 1, "InputEventGenerator");
81 qmlRegisterType<DeviceConfigParser>(uri, 0, 1, "DeviceConfigParser");
82 qmlRegisterType<DeviceConfigParser>(uri, 0, 1, "DeviceConfigParser");
82 qmlRegisterSingletonType<GlobalFunctions>(uri, 0, 1, "Functions", createGlobalFunctions);
83 qmlRegisterSingletonType<GlobalFunctions>(uri, 0, 1, "Functions", createGlobalFunctions);
84+ qmlRegisterType<URLDispatcher>(uri, 0, 1, "URLDispatcher");
83}
85}
tests/plugins/CMakeLists.txt+2
@@ -3,7 +3,9 @@ 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)
8+add_subdirectory(SessionBroadcast)
7add_subdirectory(Ubuntu)
9add_subdirectory(Ubuntu)
8add_subdirectory(Unity)
10add_subdirectory(Unity)
9add_subdirectory(Utils)
11add_subdirectory(Utils)
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/plugins/LightDM/IntegratedLightDM/CMakeLists.txt−3
@@ -26,7 +26,6 @@ add_unity8_uitest(GreeterDBus dbus-test-runner
26 ARGS --task $<TARGET_FILE:GreeterDBusTestExec>
26 ARGS --task $<TARGET_FILE:GreeterDBusTestExec>
27 DEPENDS MockLightDM
27 DEPENDS MockLightDM
28 IMPORT_PATHS ${UNITY_MOCKPATH}
28 IMPORT_PATHS ${UNITY_MOCKPATH}
29− LIGHTDM
30 )
29 )
31
30
32add_executable(GreeterPamTestExec
31add_executable(GreeterPamTestExec
@@ -63,7 +62,6 @@ target_include_directories(GreeterSessionsModelTestExec PUBLIC
63add_unity8_uitest(GreeterSessions GreeterSessionsModelTestExec
62add_unity8_uitest(GreeterSessions GreeterSessionsModelTestExec
64 ENVIRONMENT LIBLIGHTDM_MOCK_MODE=full
63 ENVIRONMENT LIBLIGHTDM_MOCK_MODE=full
65 DEPENDS MockLightDM
64 DEPENDS MockLightDM
66− LIGHTDM
67 )
65 )
68
66
69# UsersModelTest
67# UsersModelTest
@@ -87,7 +85,6 @@ target_include_directories(GreeterUsersModelTestExec PUBLIC
87add_unity8_uitest(GreeterUsers GreeterUsersModelTestExec
85add_unity8_uitest(GreeterUsers GreeterUsersModelTestExec
88 ENVIRONMENT LIBLIGHTDM_MOCK_MODE=full
86 ENVIRONMENT LIBLIGHTDM_MOCK_MODE=full
89 DEPENDS MockLightDM
87 DEPENDS MockLightDM
90− LIGHTDM
91 )
88 )
92
89
93add_executable(GreeterIntegratedTestExec
90add_executable(GreeterIntegratedTestExec
tests/plugins/LightDM/IntegratedLightDM/sessionsmodel.cpp+32
@@ -59,6 +59,38 @@ private Q_SLOTS:
59 }
59 }
60 }
60 }
61
61
62+ void testIconLookupLogic()
63+ {
64+ const QString data[][2] = {
65+ //{SESSION KEY, ICON NAME}
66+ {"gnome-classic", "gnome_badge.png"},
67+ {"gnome-flashback-compiz", "gnome_badge.png"},
68+ {"gnome-flashback-metacity", "gnome_badge.png"},
69+ {"gnome-shell", "gnome_badge.png"},
70+ {"gnome-wayland", "gnome_badge.png"},
71+ {"gnome", "gnome_badge.png"},
72+ {"kde", "kde_badge.png"},
73+ {"plasma", "kde_badge.png"},
74+ {"recovery_console", "recovery_console_badge.png"},
75+ {"ubuntu","ubuntu_badge.png"},
76+ {"ubuntu-2d", "ubuntu_badge.png"},
77+ {"xterm", "recovery_console_badge.png"},
78+ {"made up session", "unknown_badge.png"},
79+ {"", "unknown_badge.png"}
80+ };
81+
82+ const short length = sizeof(data) / sizeof(data[0]);
83+ for (int i = 0; i < length; i++) {
84+ std::string key = data[i][0].toStdString();
85+ std::string icon = data[i][1].toStdString();
86+ std::string url = model->iconUrl(data[i][0]).toString().toStdString();
87+ std::string errorMessage = "Session icon url for " + key +
88+ " should contain " + icon + " but " + url + " was returned";
89+ QVERIFY2(model->iconUrl(data[i][0]).toString().contains(data[i][1]),
90+ errorMessage.c_str());
91+ }
92+ }
93+
62 void testMultipleSessionsCountIsCorrect()
94 void testMultipleSessionsCountIsCorrect()
63 {
95 {
64 sourceModel->setTestScenario("multipleSessions");
96 sourceModel->setTestScenario("multipleSessions");
tests/plugins/LightDM/IntegratedLightDM/usersmodel.cpp+2−2
@@ -59,10 +59,10 @@ private Q_SLOTS:
59 void testMangleColor()
59 void testMangleColor()
60 {
60 {
61 QString background = getStringValue(sourceModel, "color-background", QLightDM::UsersModel::BackgroundPathRole);
61 QString background = getStringValue(sourceModel, "color-background", QLightDM::UsersModel::BackgroundPathRole);
62− QVERIFY(background == "#dd4814");
62+ QVERIFY(background == "#E95420");
63
63
64 background = getStringValue(model, "color-background", QLightDM::UsersModel::BackgroundPathRole);
64 background = getStringValue(model, "color-background", QLightDM::UsersModel::BackgroundPathRole);
65− QVERIFY(background == "data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>");
65+ QVERIFY(background == "data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#E95420'/></svg>");
66 }
66 }
67
67
68 void testMangleEmptyName()
68 void testMangleEmptyName()
tests/plugins/SessionBroadcast/BroadcastServer.cppnew file+33
@@ -0,0 +1,33 @@
1+/*
2+ * Copyright 2016 Canonical Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it
5+ * under the terms of the GNU General Public License version 3, as published
6+ * by the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but
9+ * WITHOUT ANY WARRANTY; without even the implied warranties of
10+ * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
11+ * PURPOSE. See the GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * version 3 along with this program. If not, see
15+ * <http://www.gnu.org/licenses/>
16+ */
17+
18+#include "BroadcastServer.h"
19+
20+BroadcastServer::BroadcastServer(QObject *parent)
21+ : QObject(parent)
22+{
23+}
24+
25+void BroadcastServer::RequestUrlStart(const QString &username, const QString &url)
26+{
27+ Q_EMIT StartUrl(username, url);
28+}
29+
30+void BroadcastServer::RequestHomeShown(const QString &username)
31+{
32+ Q_EMIT ShowHome(username);
33+}
tests/plugins/SessionBroadcast/BroadcastServer.hnew file+40
@@ -0,0 +1,40 @@
1+/*
2+ * Copyright 2016 Canonical Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it
5+ * under the terms of the GNU General Public License version 3, as published
6+ * by the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but
9+ * WITHOUT ANY WARRANTY; without even the implied warranties of
10+ * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
11+ * PURPOSE. See the GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * version 3 along with this program. If not, see
15+ * <http://www.gnu.org/licenses/>
16+ */
17+
18+#ifndef UNITY_BROADCASTSERVER_H
19+#define UNITY_BROADCASTSERVER_H
20+
21+#include <QDBusContext>
22+#include <QObject>
23+
24+class BroadcastServer: public QObject, protected QDBusContext
25+{
26+ Q_OBJECT
27+
28+public:
29+ explicit BroadcastServer(QObject *parent = nullptr);
30+
31+public Q_SLOTS:
32+ void RequestUrlStart(const QString &username, const QString &url);
33+ void RequestHomeShown(const QString &username);
34+
35+Q_SIGNALS:
36+ void StartUrl(const QString &username, const QString &url);
37+ void ShowHome(const QString &username);
38+};
39+
40+#endif
tests/plugins/SessionBroadcast/CMakeLists.txtnew file+45
@@ -0,0 +1,45 @@
1+macro(make_dbus_class NAME INTERFACE)
2+ if(${CMAKE_CURRENT_SOURCE_DIR}/interfaces.xml IS_NEWER_THAN ${CMAKE_CURRENT_BINARY_DIR}/${NAME}Adaptor.h)
3+ execute_process(COMMAND ${QDBUSXML2CPP_EXECUTABLE} -c ${NAME}Adaptor -a ${CMAKE_CURRENT_BINARY_DIR}/${NAME}Adaptor ${CMAKE_CURRENT_SOURCE_DIR}/interfaces.xml ${INTERFACE})
4+ endif()
5+endmacro(make_dbus_class)
6+
7+make_dbus_class(Broadcast com.canonical.Unity.Greeter.Broadcast)
8+
9+add_definitions(-DSM_BUSNAME=sessionBus)
10+
11+include_directories(
12+ ${GLIB_INCLUDE_DIRS}
13+ ${CMAKE_CURRENT_BINARY_DIR}
14+ ${CMAKE_CURRENT_SOURCE_DIR}
15+ ${CMAKE_SOURCE_DIR}/plugins/SessionBroadcast
16+)
17+
18+link_libraries(
19+ ${GLIB_LIBRARIES}
20+)
21+
22+add_executable(MockBroadcastServer
23+ ${CMAKE_CURRENT_BINARY_DIR}/BroadcastAdaptor.cpp
24+ server.cpp
25+ BroadcastServer.cpp
26+)
27+qt5_use_modules(MockBroadcastServer Core DBus)
28+
29+### SessionBroadcastTest
30+add_executable(sessionbroadcasttestExec
31+ ${CMAKE_SOURCE_DIR}/plugins/SessionBroadcast/SessionBroadcast.cpp
32+ sessionbroadcasttest.cpp
33+)
34+qt5_use_modules(sessionbroadcasttestExec Test Core Qml DBus)
35+
36+add_unity8_unittest(SessionBroadcast dbus-test-runner
37+ ARG_PREFIX "--parameter"
38+ ARGS
39+ --task $<TARGET_FILE:MockBroadcastServer>
40+ --task-name server
41+ --ignore-return
42+ --task $<TARGET_FILE:sessionbroadcasttestExec>
43+ --task-name client
44+ --wait-for com.canonical.Unity.Greeter.Broadcast
45+)
tests/plugins/SessionBroadcast/interfaces.xmlnew file+18
@@ -0,0 +1,18 @@
1+<node>
2+ <interface name="com.canonical.Unity.Greeter.Broadcast">
3+ <method name="RequestUrlStart">
4+ <arg name="username" type="s" direction="in" />
5+ <arg name="url" type="s" direction="in" />
6+ </method>
7+ <method name="RequestHomeShown">
8+ <arg name="username" type="s" direction="in" />
9+ </method>
10+ <signal name="StartUrl">
11+ <arg name="username" type="s" direction="out" />
12+ <arg name="url" type="s" direction="out" />
13+ </signal>
14+ <signal name="ShowHome">
15+ <arg name="username" type="s" direction="out" />
16+ </signal>
17+ </interface>
18+</node>
tests/plugins/SessionBroadcast/server.cppnew file+37
@@ -0,0 +1,37 @@
1+/*
2+ * Copyright 2016 Canonical Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it
5+ * under the terms of the GNU General Public License version 3, as published
6+ * by the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but
9+ * WITHOUT ANY WARRANTY; without even the implied warranties of
10+ * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
11+ * PURPOSE. See the GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * version 3 along with this program. If not, see
15+ * <http://www.gnu.org/licenses/>
16+ */
17+
18+#include "BroadcastAdaptor.h"
19+#include "BroadcastServer.h"
20+#include <QCoreApplication>
21+
22+int main(int argc, char *argv[])
23+{
24+ QCoreApplication a(argc, argv);
25+
26+ auto broadcast = new BroadcastServer(&a);
27+ new BroadcastAdaptor(broadcast);
28+
29+ // We use the session bus for testing. The real plugin uses the system bus
30+ auto connection = QDBusConnection::sessionBus();
31+ if (!connection.registerObject("/com/canonical/Unity/Greeter/Broadcast", broadcast))
32+ return 1;
33+ if (!connection.registerService("com.canonical.Unity.Greeter.Broadcast"))
34+ return 1;
35+
36+ return a.exec();
37+}
tests/plugins/SessionBroadcast/sessionbroadcasttest.cppnew file+111
@@ -0,0 +1,111 @@
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 Lesser 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 Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include <QtTest>
18+#include <QDBusInterface>
19+#include <QSignalSpy>
20+
21+#include <glib.h>
22+
23+#include "SessionBroadcast.h"
24+
25+class SessionBroadcastTest : public QObject
26+{
27+ Q_OBJECT
28+
29+private Q_SLOTS:
30+
31+ void initTestCase() {
32+ broadcastServer = new QDBusInterface("com.canonical.Unity.Greeter.Broadcast",
33+ "/com/canonical/Unity/Greeter/Broadcast",
34+ "com.canonical.Unity.Greeter.Broadcast",
35+ QDBusConnection::sessionBus(), this);
36+ serverStartUrlSpy = new QSignalSpy(broadcastServer,
37+ SIGNAL(StartUrl(const QString &, const QString &)));
38+ serverShowHomeSpy = new QSignalSpy(broadcastServer,
39+ SIGNAL(ShowHome(const QString &)));
40+ user = QString(g_get_user_name());
41+ otherUser = user + "2";
42+ }
43+
44+ void init()
45+ {
46+ serverStartUrlSpy->clear();
47+ serverShowHomeSpy->clear();
48+
49+ broadcastPlugin = new SessionBroadcast(this);
50+ pluginStartUrlSpy = new QSignalSpy(broadcastPlugin,
51+ SIGNAL(startUrl(const QString &)));
52+ pluginShowHomeSpy = new QSignalSpy(broadcastPlugin,
53+ SIGNAL(showHome()));
54+ }
55+
56+ void cleanup()
57+ {
58+ delete broadcastPlugin;
59+ }
60+
61+ void testReceiveUrlStart() {
62+ broadcastServer->call("RequestUrlStart", user, "test:");
63+ QTRY_COMPARE(serverStartUrlSpy->count(), 1);
64+ QCOMPARE(pluginStartUrlSpy->count(), 1);
65+ QCOMPARE((*pluginStartUrlSpy)[0][0], QVariant("test:"));
66+ }
67+
68+ void testReceiveUrlStartOtherUser() {
69+ broadcastServer->call("RequestUrlStart", otherUser, "test:");
70+ QTRY_COMPARE(serverStartUrlSpy->count(), 1);
71+ QCOMPARE(pluginStartUrlSpy->count(), 0);
72+ }
73+
74+ void testReceiveShowHome() {
75+ broadcastServer->call("RequestHomeShown", user);
76+ QTRY_COMPARE(serverShowHomeSpy->count(), 1);
77+ QCOMPARE(pluginShowHomeSpy->count(), 1);
78+ }
79+
80+ void testReceiveShowHomeOtherUser() {
81+ broadcastServer->call("RequestHomeShown", otherUser);
82+ QTRY_COMPARE(serverShowHomeSpy->count(), 1);
83+ QCOMPARE(pluginShowHomeSpy->count(), 0);
84+ }
85+
86+ void testSendUrlStart() {
87+ broadcastPlugin->requestUrlStart(otherUser, "test:");
88+ QTRY_COMPARE(serverStartUrlSpy->count(), 1);
89+ QCOMPARE((*serverStartUrlSpy)[0][0], QVariant(otherUser));
90+ QCOMPARE((*serverStartUrlSpy)[0][1], QVariant("test:"));
91+ }
92+
93+ void testSendShowHome() {
94+ broadcastPlugin->requestHomeShown(otherUser);
95+ QTRY_COMPARE(serverShowHomeSpy->count(), 1);
96+ QCOMPARE((*serverShowHomeSpy)[0][0], QVariant(otherUser));
97+ }
98+
99+private:
100+ QDBusInterface *broadcastServer;
101+ SessionBroadcast *broadcastPlugin;
102+ QSignalSpy *serverStartUrlSpy;
103+ QSignalSpy *serverShowHomeSpy;
104+ QSignalSpy *pluginStartUrlSpy;
105+ QSignalSpy *pluginShowHomeSpy;
106+ QString user;
107+ QString otherUser;
108+};
109+
110+QTEST_GUILESS_MAIN(SessionBroadcastTest)
111+#include "sessionbroadcasttest.moc"
tests/plugins/Utils/CMakeLists.txt+9
@@ -23,3 +23,12 @@ endforeach()
23
23
24# plain qml test
24# plain qml test
25add_unity8_qmlunittest(. UtilsStyle)
25add_unity8_qmlunittest(. UtilsStyle)
26+
27+# URLDispatcher test needs dbus-test-runner because it claims a dbus name
28+add_executable(URLDispatcherTestExec URLDispatcherTest.cpp)
29+qt5_use_modules(URLDispatcherTestExec Test Core Qml)
30+target_link_libraries(URLDispatcherTestExec Utils-qml)
31+add_unity8_unittest(URLDispatcher dbus-test-runner
32+ ARG_PREFIX "--parameter"
33+ ARGS --task $<TARGET_FILE:URLDispatcherTestExec>
34+)
tests/plugins/Utils/URLDispatcherTest.cppnew file+78
@@ -0,0 +1,78 @@
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 "URLDispatcher.h"
18+
19+#include <QDBusInterface>
20+#include <QSignalSpy>
21+#include <QTest>
22+
23+class URLDispatcherTest : public QObject
24+{
25+ Q_OBJECT
26+
27+private Q_SLOTS:
28+
29+ void initTestCase() {
30+ dispatchServer = new QDBusInterface("com.canonical.URLDispatcher",
31+ "/com/canonical/URLDispatcher",
32+ "com.canonical.URLDispatcher",
33+ QDBusConnection::sessionBus(), this);
34+ }
35+
36+ void init()
37+ {
38+ dispatcher = new URLDispatcher(this);
39+ dispatchSpy = new QSignalSpy(dispatcher,
40+ SIGNAL(urlRequested(const QString &)));
41+ }
42+
43+ void cleanup()
44+ {
45+ delete dispatcher;
46+ }
47+
48+ void testInactiveByDefault() {
49+ QVERIFY(!dispatcher->active());
50+ }
51+
52+ void testActiveRequest() {
53+ dispatcher->setActive(true);
54+ dispatchServer->call("DispatchURL", "test:", "package");
55+ QCOMPARE(dispatchSpy->count(), 1);
56+ QCOMPARE((*dispatchSpy)[0][0], QVariant("test:"));
57+ }
58+
59+ void testInactiveRequest() {
60+ dispatchServer->call("DispatchURL", "test:", "package");
61+ QCOMPARE(dispatchSpy->count(), 0);
62+ }
63+
64+ void testInactiveAfterActiveRequest() {
65+ dispatcher->setActive(true);
66+ dispatcher->setActive(false);
67+ dispatchServer->call("DispatchURL", "test:", "package");
68+ QCOMPARE(dispatchSpy->count(), 0);
69+ }
70+
71+private:
72+ QDBusInterface *dispatchServer;
73+ URLDispatcher *dispatcher;
74+ QSignalSpy *dispatchSpy;
75+};
76+
77+QTEST_GUILESS_MAIN(URLDispatcherTest)
78+#include "URLDispatcherTest.moc"
tests/qmltests/CMakeLists.txt+10−10
@@ -1,14 +1,14 @@
1
1
2add_subdirectory(Components)
2add_subdirectory(Components)
3
3
4−add_unity8_qmltest(. OrientedShell LIGHTDM)
4+add_unity8_qmltest(. OrientedShell)
5add_unity8_qmltest(. DisabledScreenNotice)
5add_unity8_qmltest(. DisabledScreenNotice)
6−add_unity8_qmltest(. Shell LIGHTDM)
6+add_unity8_qmltest(. Shell)
7−add_unity8_qmltest(. ShellWithPin LIGHTDM)
7+add_unity8_qmltest(. ShellWithPin)
8add_unity8_qmltest(. DeviceConfiguration)
8add_unity8_qmltest(. DeviceConfiguration)
9add_unity8_qmltest(Components Background)
9add_unity8_qmltest(Components Background)
10add_unity8_qmltest(Components Carousel)
10add_unity8_qmltest(Components Carousel)
11−add_unity8_qmltest(Components Dialogs LIGHTDM)
11+add_unity8_qmltest(Components Dialogs)
12add_unity8_qmltest(Components DraggingArea)
12add_unity8_qmltest(Components DraggingArea)
13add_unity8_qmltest(Components LazyImage)
13add_unity8_qmltest(Components LazyImage)
14add_unity8_qmltest(Components Lockscreen)
14add_unity8_qmltest(Components Lockscreen)
@@ -25,7 +25,7 @@ add_unity8_qmltest(Components WallpaperResolver)
25add_unity8_qmltest(Components ZoomableImage)
25add_unity8_qmltest(Components ZoomableImage)
26add_unity8_qmltest(Dash Dash)
26add_unity8_qmltest(Dash Dash)
27add_unity8_qmltest(Dash DashContent)
27add_unity8_qmltest(Dash DashContent)
28−add_unity8_qmltest(Dash DashShell LIGHTDM)
28+add_unity8_qmltest(Dash DashShell)
29add_unity8_qmltest(Dash Card)
29add_unity8_qmltest(Dash Card)
30add_unity8_qmltest(Dash CardCarousel)
30add_unity8_qmltest(Dash CardCarousel)
31add_unity8_qmlbenchmark(Dash CardBenchmark 1000)
31add_unity8_qmlbenchmark(Dash CardBenchmark 1000)
@@ -66,10 +66,10 @@ add_unity8_qmltest(Dash/ScopeSettings ScopeSettingNumber)
66add_unity8_qmltest(Dash/ScopeSettings ScopeSettingString)
66add_unity8_qmltest(Dash/ScopeSettings ScopeSettingString)
67add_unity8_qmltest(Dash/ScopeSettings ScopeSettingsWidgetFactory)
67add_unity8_qmltest(Dash/ScopeSettings ScopeSettingsWidgetFactory)
68add_unity8_qmltest(Greeter Clock)
68add_unity8_qmltest(Greeter Clock)
69−add_unity8_qmltest(Greeter Greeter LIGHTDM)
69+add_unity8_qmltest(Greeter Greeter)
70−add_unity8_qmltest(Greeter Infographics LIGHTDM)
70+add_unity8_qmltest(Greeter Infographics)
71−add_unity8_qmltest(Greeter NarrowView LIGHTDM)
71+add_unity8_qmltest(Greeter NarrowView)
72−add_unity8_qmltest(Greeter WideView LIGHTDM)
72+add_unity8_qmltest(Greeter WideView)
73add_unity8_qmltest(Launcher Launcher)
73add_unity8_qmltest(Launcher Launcher)
74add_unity8_qmltest(Notifications Notifications)
74add_unity8_qmltest(Notifications Notifications)
75add_unity8_qmltest(Notifications VisualSnapDecisionsQueue)
75add_unity8_qmltest(Notifications VisualSnapDecisionsQueue)
@@ -94,6 +94,6 @@ add_unity8_qmltest(Stages SurfaceContainer)
94add_unity8_qmltest(Stages TabletStage)
94add_unity8_qmltest(Stages TabletStage)
95add_unity8_qmltest(Stages WindowResizeArea)
95add_unity8_qmltest(Stages WindowResizeArea)
96add_unity8_qmltest(Stages Splash)
96add_unity8_qmltest(Stages Splash)
97−add_unity8_qmltest(Tutorial Tutorial LIGHTDM)
97+add_unity8_qmltest(Tutorial Tutorial)
98add_unity8_qmltest(Wizard Wizard ENVIRONMENT "OXIDE_NO_SANDBOX=1")
98add_unity8_qmltest(Wizard Wizard ENVIRONMENT "OXIDE_NO_SANDBOX=1")
99add_unity8_qmltest(utils/Unity/Test UnityTest)
99add_unity8_qmltest(utils/Unity/Test UnityTest)
tests/qmltests/Components/tst_Lockscreen.qml+1−39
@@ -37,8 +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− delayMinutes: delayMinutesTextField.text
40+ background: "/usr/share/backgrounds/warty-final-ubuntu.png"
41− background: "../../../qml/graphics/phone_background.jpg"
42 }
41 }
43
42
44 Connections {
43 Connections {
@@ -109,16 +108,6 @@ Rectangle {
109 text: "Retries left"
108 text: "Retries left"
110 }
109 }
111 }
110 }
112− Row {
113− TextField {
114− id: delayMinutesTextField
115− width: units.gu(7)
116− text: "0"
117− }
118− Label {
119− text: "Delay Minutes"
120− }
121− }
122 Label {
111 Label {
123 id: pinLabel
112 id: pinLabel
124 anchors.verticalCenter: parent.verticalCenter
113 anchors.verticalCenter: parent.verticalCenter
@@ -172,7 +161,6 @@ Rectangle {
172
161
173 function cleanup() {
162 function cleanup() {
174 lockscreen.clear(false);
163 lockscreen.clear(false);
175− delayMinutesTextField.text = "0"
176 enteredLabel.text = "";
164 enteredLabel.text = "";
177
165
178 // Reset sizing
166 // Reset sizing
@@ -455,32 +443,6 @@ Rectangle {
455 compare(label.text, data.text);
443 compare(label.text, data.text);
456 }
444 }
457
445
458− function test_delayMinutes() {
459− delayMinutesTextField.text = "4"
460− waitForLockscreenReady()
461− var label = findChild(lockscreen, "deviceLockedLabel")
462− compare(label.text, "Device Locked")
463− }
464−
465− function test_showText_data() {
466− return [
467− { tag: "alphanumeric", alphanumeric: true },
468− { tag: "pinPad", alphanumeric: false },
469− ]
470− }
471−
472− function test_showText(data) {
473− pinPadCheckBox.checked = data.alphanumeric;
474− waitForLockscreenReady();
475−
476− lockscreen.showText("test");
477−
478− var pinPadLoader = findChild(lockscreen, "pinPadLoader");
479− verify(pinPadLoader.waiting);
480− compare(enteredLabel.text, ""); // no entered signal should occur
481− compare(lockscreen.passphrase, "test");
482− }
483−
484 function test_resize_data() {
446 function test_resize_data() {
485 return [
447 return [
486 { tag: "small", width: units.gu(40), height: units.gu(76) },
448 { tag: "small", width: units.gu(40), height: units.gu(76) },
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_PreviewProgress.qml+14−1
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright 2014 Canonical Ltd.
2+ * Copyright 2014-2016 Canonical Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -40,6 +40,11 @@ Rectangle {
40 "source": { "dbus-name" : "somename", "dbus-object": "error" }
40 "source": { "dbus-name" : "somename", "dbus-object": "error" }
41 }
41 }
42
42
43+ property var progressjsonProcessing: {
44+ "type": "progress",
45+ "source": { "dbus-name" : "somename", "dbus-object": "processing" }
46+ }
47+
43 SignalSpy {
48 SignalSpy {
44 id: spy
49 id: spy
45 signalName: "triggered"
50 signalName: "triggered"
@@ -82,6 +87,14 @@ Rectangle {
82 compare(args[0], "previewProgress");
87 compare(args[0], "previewProgress");
83 compare(args[1], "failed");
88 compare(args[1], "failed");
84 compare(args[2], progressjsonError);
89 compare(args[2], progressjsonError);
90+
91+ spy.clear();
92+
93+ var progressBar = findChild(previewProgress, "progressBar");
94+ compare(progressBar.indeterminate, false);
95+ previewProgress.widgetData = progressjsonProcessing;
96+ progressBar = findChild(previewProgress, "progressBar");
97+ tryCompare(progressBar, "indeterminate", true);
85 }
98 }
86 }
99 }
87}
100}
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+4−3
@@ -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
@@ -51,7 +52,7 @@ Item {
51 signal _notifyAuthenticationSucceededCalled(bool showFakePassword)
52 signal _notifyAuthenticationSucceededCalled(bool showFakePassword)
52 signal _notifyAuthenticationFailedCalled()
53 signal _notifyAuthenticationFailedCalled()
53 signal _showErrorMessageCalled(string msg)
54 signal _showErrorMessageCalled(string msg)
54− signal _resetCalled()
55+ signal _resetCalled(bool forceShow)
55 signal _tryToUnlockCalled(bool toTheRight)
56 signal _tryToUnlockCalled(bool toTheRight)
56
57
57 function showMessage(html) {
58 function showMessage(html) {
@@ -84,8 +85,8 @@ Item {
84 _showErrorMessageCalled(msg);
85 _showErrorMessageCalled(msg);
85 }
86 }
86
87
87− function reset() {
88+ function reset(forceShow) {
88− _resetCalled();
89+ _resetCalled(forceShow);
89 }
90 }
90
91
91 function tryToUnlock(toTheRight) {
92 function tryToUnlock(toTheRight) {
tests/qmltests/Greeter/tst_Greeter.qml+8−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);
@@ -559,6 +565,7 @@ Item {
559
565
560 LightDM.Greeter.showGreeter();
566 LightDM.Greeter.showGreeter();
561 compare(viewResetSpy.count, 1);
567 compare(viewResetSpy.count, 1);
568+ compare(viewResetSpy.signalArguments[0][0], true);
562 tryCompare(viewShowPromptSpy, "count", 1);
569 tryCompare(viewShowPromptSpy, "count", 1);
563 }
570 }
564
571
tests/qmltests/Greeter/tst_NarrowView.qml+47−12
@@ -20,6 +20,7 @@ import ".."
20import "../../../qml/Greeter"
20import "../../../qml/Greeter"
21import LightDM.IntegratedLightDM 0.1 as LightDM
21import LightDM.IntegratedLightDM 0.1 as LightDM
22import Ubuntu.Components 1.3
22import Ubuntu.Components 1.3
23+import Ubuntu.Telephony 0.1 as Telephony
23import Unity.Test 0.1 as UT
24import Unity.Test 0.1 as UT
24
25
25Item {
26Item {
@@ -39,7 +40,7 @@ Item {
39 property bool itemDestroyed: false
40 property bool itemDestroyed: false
40 sourceComponent: Component {
41 sourceComponent: Component {
41 NarrowView {
42 NarrowView {
42− background: Qt.resolvedUrl("../../../qml/graphics/phone_background.jpg")
43+ background: "/usr/share/backgrounds/warty-final-ubuntu.png"
43 userModel: LightDM.Users
44 userModel: LightDM.Users
44 infographicModel: LightDM.Infographic
45 infographicModel: LightDM.Infographic
45
46
@@ -293,6 +294,8 @@ Item {
293 function init() {
294 function init() {
294 view.currentIndex = 0; // break binding with text field
295 view.currentIndex = 0; // break binding with text field
295
296
297+ telepathyHelper.ready = true;
298+ telepathyHelper.emergencyCallsAvailable = true;
296 selectedSpy.clear();
299 selectedSpy.clear();
297 respondedSpy.clear();
300 respondedSpy.clear();
298 teaseSpy.clear();
301 teaseSpy.clear();
@@ -328,14 +331,6 @@ Item {
328 waitForRendering(view);
331 waitForRendering(view);
329 }
332 }
330
333
331− function enterPin(pin) {
332− for (var i = 0; i < pin.length; ++i) {
333− var character = pin.charAt(i);
334− var button = findChild(view, "pinPadButton" + character);
335− tap(button);
336− }
337− }
338−
339 function test_tease_data() {
334 function test_tease_data() {
340 return [
335 return [
341 {tag: "left", x: 0, offset: 0, count: 1},
336 {tag: "left", x: 0, offset: 0, count: 1},
@@ -350,10 +345,23 @@ Item {
350 compare(teaseSpy.count, data.count);
345 compare(teaseSpy.count, data.count);
351 }
346 }
352
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+
353 function test_respondedWithPin() {
360 function test_respondedWithPin() {
354 view.locked = true;
361 view.locked = true;
362+ view.showPrompt("", true, true);
355 swipeAwayCover();
363 swipeAwayCover();
356− enterPin("1234");
364+ typeString("1234");
357 compare(respondedSpy.count, 1);
365 compare(respondedSpy.count, 1);
358 compare(respondedSpy.signalArguments[0][0], "1234");
366 compare(respondedSpy.signalArguments[0][0], "1234");
359 }
367 }
@@ -361,6 +369,7 @@ Item {
361 function test_respondedWithPassphrase() {
369 function test_respondedWithPassphrase() {
362 view.locked = true;
370 view.locked = true;
363 view.alphanumeric = true;
371 view.alphanumeric = true;
372+ view.showPrompt("", true, true);
364 swipeAwayCover();
373 swipeAwayCover();
365 typeString("test");
374 typeString("test");
366 keyClick(Qt.Key_Enter);
375 keyClick(Qt.Key_Enter);
@@ -382,12 +391,38 @@ Item {
382 compare(respondedSpy.signalArguments[0][0], "");
391 compare(respondedSpy.signalArguments[0][0], "");
383 }
392 }
384
393
394+ function test_emergencyCall_data() {
395+ return [
396+ {tag: "phone", available: true},
397+ {tag: "desktop", available: false},
398+ ];
399+ }
385− function test_emergencyCall() {
400+ function test_emergencyCall(data) {
401+ telepathyHelper.emergencyCallsAvailable = data.available;
386 view.locked = true;
402 view.locked = true;
387 swipeAwayCover();
403 swipeAwayCover();
388 var emergencyCallLabel = findChild(view, "emergencyCallLabel");
404 var emergencyCallLabel = findChild(view, "emergencyCallLabel");
389 tap(emergencyCallLabel);
405 tap(emergencyCallLabel);
406+ if (!data.available) {
407+ expectFail("", "Bug 1616538 prevents us supporting conditional emergency button support");
408+ }
409+ compare(emergencyCallLabel.visible, data.available ? true : false);
390− compare(emergencySpy.count, 1);
410+ compare(emergencySpy.count, data.available ? 1 : 0);
411+ }
412+
413+ function test_emergencyCallAvailability() {
414+ view.locked = true;
415+ var emergencyCallLabel = findChild(view, "emergencyCallLabel");
416+ verify(emergencyCallLabel.visible);
417+
418+ telepathyHelper.emergencyCallsAvailable = false;
419+ telepathyHelper.ready = true;
420+ expectFail("", "Bug 1616538 prevents us supporting conditional emergency button support");
421+ verify(!emergencyCallLabel.visible);
422+
423+ telepathyHelper.emergencyCallsAvailable = true;
424+ telepathyHelper.ready = false;
425+ verify(!emergencyCallLabel.visible);
391 }
426 }
392
427
393 function test_fullyShown() {
428 function test_fullyShown() {
tests/qmltests/Greeter/tst_WideView.qml+154−7
@@ -24,6 +24,7 @@ import Unity.Test 0.1 as UT
24
24
25StyledItem {
25StyledItem {
26 id: root
26 id: root
27+
27 width: units.gu(120)
28 width: units.gu(120)
28 height: units.gu(80)
29 height: units.gu(80)
29 focus: true
30 focus: true
@@ -49,7 +50,7 @@ StyledItem {
49 WideView {
50 WideView {
50 id: view
51 id: view
51
52
52− background: Qt.resolvedUrl("../../../qml/graphics/tablet_background.jpg")
53+ background: "/usr/share/backgrounds/warty-final-ubuntu.png"
53 userModel: LightDM.Users
54 userModel: LightDM.Users
54 infographicModel: LightDM.Infographic
55 infographicModel: LightDM.Infographic
55
56
@@ -111,11 +112,9 @@ StyledItem {
111 color: theme.palette.normal.background
112 color: theme.palette.normal.background
112 width: units.gu(40)
113 width: units.gu(40)
113 height: parent.height
114 height: parent.height
114−
115 Column {
115 Column {
116 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
116 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
117 spacing: units.gu(1)
117 spacing: units.gu(1)
118−
119 Row {
118 Row {
120 Button {
119 Button {
121 text: "Show Last Chance"
120 text: "Show Last Chance"
@@ -266,6 +265,51 @@ StyledItem {
266 text: "emergency: " + emergencySpy.count
265 text: "emergency: " + emergencySpy.count
267 }
266 }
268 }
267 }
268+ Row {
269+ id: multipleSessions
270+ CheckBox {
271+ id: multipleSessionsCheckbox
272+ onClicked: {
273+ if (checked) {
274+ LightDM.Sessions.testScenario = "multipleSessions"
275+ } else {
276+ LightDM.Sessions.testScenario = "singleSession"
277+ }
278+ }
279+ Connections {
280+ target: LightDM.Sessions
281+ onTestScenarioChanged: {
282+ if (LightDM.Sessions.testScenario === "multipleSessions") {
283+ multipleSessionsCheckbox.checked = true;
284+ } else {
285+ multipleSessionsCheckbox.checked = false;
286+ }
287+ }
288+ }
289+ }
290+ Label {
291+ text: "Multiple Sessions"
292+ }
293+ }
294+ Row {
295+ Slider {
296+ id: numSessionsSlider
297+
298+ width: units.gu(10)
299+ minimumValue: 0
300+ maximumValue: LightDM.Sessions.numAvailableSessions
301+ value: LightDM.Sessions.numSessions
302+ visible: LightDM.Sessions.testScenario === "multipleSessions"
303+ Binding {
304+ target: LightDM.Sessions
305+ property: "numSessions"
306+ value: numSessionsSlider.value
307+ }
308+ }
309+ Label {
310+ text: "Available Sessions"
311+ }
312+ }
269 Row {
313 Row {
270 Button {
314 Button {
271 text: "Reload View"
315 text: "Reload View"
@@ -316,6 +360,7 @@ StyledItem {
316 when: windowShown
360 when: windowShown
317
361
318 property Item view: loader.status === Loader.Ready ? loader.item : null
362 property Item view: loader.status === Loader.Ready ? loader.item : null
363+ property url testIconDirectory: Qt.resolvedUrl("../../../qml/Greeter/graphics/session_icons")
319
364
320 function init() {
365 function init() {
321 selectIndex(0); // break binding with text field
366 selectIndex(0); // break binding with text field
@@ -323,6 +368,7 @@ StyledItem {
323 respondedSpy.clear();
368 respondedSpy.clear();
324 teaseSpy.clear();
369 teaseSpy.clear();
325 emergencySpy.clear();
370 emergencySpy.clear();
371+ LightDM.Sessions.testScenario = "multipleSessions"
326 }
372 }
327
373
328 function cleanup() {
374 function cleanup() {
@@ -378,6 +424,92 @@ StyledItem {
378 {tag: "right", x: view.width, offset: 0, count: 1, locked: false},
424 {tag: "right", x: view.width, offset: 0, count: 1, locked: false},
379 ]
425 ]
380 }
426 }
427+
428+ function test_sessionIconsAreValid() {
429+ LightDM.Sessions.testScenario = "multipleSessions"
430+ var originalDirectories = LightDM.Sessions.iconSearchDirectories
431+ LightDM.Sessions.iconSearchDirectories = [testIconDirectory]
432+
433+ selectUser("has-password");
434+
435+ // Test the login list icon is valid
436+ var sessionChooserButton = findChild(view, "sessionChooserButton");
437+ compare(sessionChooserButton.visible, true);
438+
439+ var session = String(view.sessionToStart).toLowerCase();
440+ var icon = String(sessionChooserButton.icon);
441+ compare(icon.indexOf(session) > -1, true);
442+
443+ // Test the session list icons are valid
444+ var coverPage = findChild(view, "coverPage");
445+ coverPage.state = "SessionsList"
446+ var sessionsList = findChild(view, "sessionsList");
447+ tryCompare(sessionsList, "visible", true);
448+ }
449+
450+ function test_choosingNewSessionChangesLoginListIcon() {
451+ // Ensure the default session is selected (Ubuntu)
452+ loader.active = false;
453+ loader.active = true;
454+
455+ selectUser("has-password");
456+
457+ LightDM.Sessions.testScenario = "multipleSessions";
458+ var sessionChooserButton = findChild(view, "sessionChooserButton");
459+ var icon = String(sessionChooserButton.icon);
460+ compare(icon.indexOf("ubuntu") > -1, true);
461+
462+ tap(sessionChooserButton)
463+ for(var i = 0; i < LightDM.Sessions.count; i++) {
464+ var delegateName = "sessionDelegate" + String(i);
465+ var currentDelegate = findChild(view, delegateName);
466+ var sessionKey = LightDM.Sessions.data(i,LightDM.SessionRoles.KeyRole);
467+ if (sessionKey === "gnome-classic") {
468+ tap(currentDelegate);
469+ var sessionChooserButton = findChild(view, "sessionChooserButton");
470+ waitForRendering(sessionChooserButton);
471+ var icon = String(sessionChooserButton.icon);
472+ break;
473+ }
474+ }
475+
476+ compare(icon.indexOf("gnome") > -1, true,
477+ "Expected icon to contain gnome but it was " + icon);
478+ }
479+
480+ function test_noSessionsDoesntBreakView() {
481+ LightDM.Sessions.testScenario = "noSessions"
482+ compare(LightDM.Sessions.count, 0)
483+ }
484+
485+ function test_sessionIconNotShownWithOneSession() {
486+ LightDM.Sessions.testScenario = "singleSession"
487+ compare(LightDM.Sessions.count, 1);
488+
489+ var sessionChooserButton = findChild(view, "sessionChooserButton");
490+ tryCompare(sessionChooserButton, "visible", false);
491+ }
492+
493+ function test_sessionIconNotShownWithActiveUser() {
494+ LightDM.Sessions.testScenario = "multipleSessions";
495+ compare(LightDM.Sessions.count > 1, true);
496+
497+ selectUser("active");
498+
499+ var sessionChooserButton = findChild(view, "sessionChooserButton");
500+ tryCompare(sessionChooserButton, "visible", false);
501+ }
502+
503+ function test_sessionIconShownWithMultipleSessions() {
504+ LightDM.Sessions.testScenario = "multipleSessions"
505+ compare(LightDM.Sessions.count > 1, true);
506+
507+ selectUser("has-password");
508+
509+ var sessionChooserButton = findChild(view, "sessionChooserButton");
510+ tryCompare(sessionChooserButton, "visible", true);
511+ }
512+
381 function test_tease(data) {
513 function test_tease(data) {
382 view.locked = data.locked;
514 view.locked = data.locked;
383 view.dragHandleLeftMargin = data.offset;
515 view.dragHandleLeftMargin = data.offset;
@@ -393,6 +525,17 @@ StyledItem {
393 compare(view.currentIndex, 0); // confirm we didn't change
525 compare(view.currentIndex, 0); // confirm we didn't change
394 }
526 }
395
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+
396 function test_respondedWithPassword() {
539 function test_respondedWithPassword() {
397 view.locked = true;
540 view.locked = true;
398 view.showPrompt("Prompt", true, false);
541 view.showPrompt("Prompt", true, false);
@@ -509,17 +652,21 @@ StyledItem {
509
652
510 keyboardVisibleCheckBox.checked = true;
653 keyboardVisibleCheckBox.checked = true;
511
654
655+ var halfway = (view.height - loginList.highlightedHeight) / 2;
512− tryCompare(loginList, "height", view.height - view.inputMethod.keyboardRectangle.height);
656+ var halfwayWithOsk = halfway - view.inputMethod.keyboardRectangle.height / 2;
657+ tryCompare(loginList, "boxVerticalOffset", halfwayWithOsk);
658+
659+ var highlightItem = findChild(loginList, "highlightItem");
513 tryCompareFunction( function() {
660 tryCompareFunction( function() {
514− var loginListRect = loginList.mapToItem(view, 0, 0, loginList.width, loginList.height);
661+ var highlightRect = highlightItem.mapToItem(view, 0, 0, highlightItem.width, highlightItem.height);
515− return loginListRect.y + loginListRect.height <= view.inputMethod.keyboardRectangle.y;
662+ return highlightRect.y + highlightRect.height <= view.inputMethod.keyboardRectangle.y;
516 }, true);
663 }, true);
517
664
518 // once the vkb goes away, loginList goes back to its full height
665 // once the vkb goes away, loginList goes back to its full height
519
666
520 keyboardVisibleCheckBox.checked = false;
667 keyboardVisibleCheckBox.checked = false;
521
668
522− tryCompare(loginList, "height", view.height);
669+ tryCompare(loginList, "boxVerticalOffset", halfway);
523 }
670 }
524
671
525 function test_passphrase() {
672 function test_passphrase() {
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+157−1
@@ -19,6 +19,7 @@ import QtTest 1.0
19import AccountsService 0.1
19import AccountsService 0.1
20import GSettings 1.0
20import GSettings 1.0
21import LightDM.IntegratedLightDM 0.1 as LightDM
21import LightDM.IntegratedLightDM 0.1 as LightDM
22+import SessionBroadcast 0.1
22import Ubuntu.Components 1.3
23import Ubuntu.Components 1.3
23import Ubuntu.Components.ListItems 1.3 as ListItem
24import Ubuntu.Components.ListItems 1.3 as ListItem
24import Ubuntu.Telephony 0.1 as Telephony
25import Ubuntu.Telephony 0.1 as Telephony
@@ -405,6 +406,18 @@ Rectangle {
405 signalName: "setCurrentScopeCalled"
406 signalName: "setCurrentScopeCalled"
406 }
407 }
407
408
409+ SignalSpy {
410+ id: broadcastUrlSpy
411+ target: SessionBroadcast
412+ signalName: "startUrl"
413+ }
414+
415+ SignalSpy {
416+ id: broadcastHomeSpy
417+ target: SessionBroadcast
418+ signalName: "showHome"
419+ }
420+
408 SignalSpy {
421 SignalSpy {
409 id: unlockAllModemsSpy
422 id: unlockAllModemsSpy
410 target: Connectivity
423 target: Connectivity
@@ -536,6 +549,7 @@ Rectangle {
536
549
537 AccountsService.demoEdges = false;
550 AccountsService.demoEdges = false;
538 AccountsService.demoEdgesCompleted = [];
551 AccountsService.demoEdgesCompleted = [];
552+ AccountsService.backgroundFile = "";
539 Wizard.System.wizardEnabled = false;
553 Wizard.System.wizardEnabled = false;
540 shellLoader.mode = "full-greeter";
554 shellLoader.mode = "full-greeter";
541
555
@@ -546,8 +560,11 @@ Rectangle {
546 LightDM.Greeter.authenticate(""); // reset greeter
560 LightDM.Greeter.authenticate(""); // reset greeter
547
561
548 sessionSpy.clear();
562 sessionSpy.clear();
563+ broadcastUrlSpy.clear();
564+ broadcastHomeSpy.clear();
549
565
550 GSettingsController.setLifecycleExemptAppids([]);
566 GSettingsController.setLifecycleExemptAppids([]);
567+ GSettingsController.setPictureUri("");
551
568
552 // there should be only unity8-dash window over there
569 // there should be only unity8-dash window over there
553 tryCompare(ApplicationManager, "count", 1);
570 tryCompare(ApplicationManager, "count", 1);
@@ -672,7 +689,7 @@ Rectangle {
672 waitUntilDashIsFocused();
689 waitUntilDashIsFocused();
673 tryCompare(greeter, "shown", false);
690 tryCompare(greeter, "shown", false);
674 } else {
691 } else {
675− compare(greeter.fullyShown, data.greeterShown);
692+ tryCompare(greeter, "fullyShown", data.greeterShown);
676 }
693 }
677
694
678 var launcher = findChild(shell, "launcherPanel");
695 var launcher = findChild(shell, "launcherPanel");
@@ -1319,6 +1336,32 @@ Rectangle {
1319 tryCompare(tutorial, "paused", false);
1336 tryCompare(tutorial, "paused", false);
1320 }
1337 }
1321
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+
1322 function test_tapOnRightEdgeReachesApplicationSurface() {
1365 function test_tapOnRightEdgeReachesApplicationSurface() {
1323 loadShell("phone");
1366 loadShell("phone");
1324 swipeAwayGreeter();
1367 swipeAwayGreeter();
@@ -2420,6 +2463,119 @@ Rectangle {
2420 compare(topmostSurfaceItem.touchReleaseCount, 2);
2463 compare(topmostSurfaceItem.touchReleaseCount, 2);
2421 }
2464 }
2422
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+
2519+ function test_greeterModeBroadcastsApp() {
2520+ setLightDMMockMode("single-pin");
2521+ shellLoader.mode = "greeter";
2522+ loadShell("phone");
2523+ shell.usageScenario = "phone";
2524+ waitForRendering(shell);
2525+
2526+ dragLauncherIntoView();
2527+ var appIcon = findChild(shell, "launcherDelegate0")
2528+ tap(appIcon);
2529+
2530+ tryCompare(broadcastUrlSpy, "count", 1);
2531+ compare(broadcastUrlSpy.signalArguments[0][0], "application:///" + appIcon.appId + ".desktop");
2532+ compare(ApplicationManager.count, 1); // confirm only dash is open, we didn't start new app
2533+
2534+ var coverPage = findChild(shell, "coverPage");
2535+ tryCompare(coverPage, "showProgress", 0);
2536+ }
2537+
2538+ function test_greeterModeBroadcastsHome() {
2539+ setLightDMMockMode("single-pin");
2540+ shellLoader.mode = "greeter";
2541+ loadShell("phone");
2542+ shell.usageScenario = "phone";
2543+ waitForRendering(shell);
2544+
2545+ var gallerySurfaceId = topLevelSurfaceList.nextId;
2546+ var galleryApp = ApplicationManager.startApplication("gallery-app");
2547+ waitUntilAppWindowIsFullyLoaded(gallerySurfaceId);
2548+ compare(ApplicationManager.focusedApplicationId, "gallery-app");
2549+
2550+ dragLauncherIntoView();
2551+ tap(findChild(shell, "buttonShowDashHome"));
2552+
2553+ tryCompare(broadcastHomeSpy, "count", 1);
2554+ compare(ApplicationManager.focusedApplicationId, "gallery-app"); // confirm we didn't focus dash
2555+
2556+ var coverPage = findChild(shell, "coverPage");
2557+ tryCompare(coverPage, "showProgress", 0);
2558+ }
2559+
2560+ function test_greeterModeDispatchesURL() {
2561+ setLightDMMockMode("single-pin");
2562+ shellLoader.mode = "greeter";
2563+ loadShell("phone");
2564+ shell.usageScenario = "phone";
2565+ waitForRendering(shell);
2566+
2567+ var urlDispatcher = findInvisibleChild(shell, "urlDispatcher");
2568+ verify(urlDispatcher.active);
2569+ urlDispatcher.urlRequested("test:"); // force signal emission
2570+
2571+ tryCompare(broadcastUrlSpy, "count", 1);
2572+ compare(broadcastUrlSpy.signalArguments[0][0], "test:");
2573+ compare(ApplicationManager.count, 1); // confirm only dash is open, we didn't start new app
2574+
2575+ var coverPage = findChild(shell, "coverPage");
2576+ tryCompare(coverPage, "showProgress", 0);
2577+ }
2578+
2423 function test_switchKeymap() {
2579 function test_switchKeymap() {
2424 // start with phone shell
2580 // start with phone shell
2425 loadShell("phone");
2581 loadShell("phone");
tests/qmltests/tst_ShellWithPin.qml+43−38
@@ -154,7 +154,6 @@ Item {
154 var greeter = findChild(shell, "greeter");
154 var greeter = findChild(shell, "greeter");
155 sessionSpy.target = greeter;
155 sessionSpy.target = greeter;
156 swipeAwayGreeter(true);
156 swipeAwayGreeter(true);
157− waitForLockscreen()
158 greeter.failedLoginsDelayAttempts = -1;
157 greeter.failedLoginsDelayAttempts = -1;
159 greeter.maxFailedLogins = -1;
158 greeter.maxFailedLogins = -1;
160
159
@@ -214,20 +213,16 @@ Item {
214 }
213 }
215 }
214 }
216
215
217− function waitForLockscreen() {
216+ function enterPin(pin) {
218− var lockscreen = findChild(shell, "lockscreen");
217+ typeString(pin);
219− var pinPadLoader = findChild(lockscreen, "pinPadLoader");
220− tryCompare(pinPadLoader, "status", Loader.Ready)
221− waitForRendering(lockscreen)
222 }
218 }
223
219
224− function enterPin(pin) {
220+ function showGreeter() {
225− for (var i = 0; i < pin.length; ++i) {
221+ LightDM.Greeter.showGreeter();
226− var character = pin.charAt(i)
222+ tryCompare(shell, "waitingOnGreeter", false);
227− var button = findChild(shell, "pinPadButton" + character)
223+ var coverPage = findChild(shell, "coverPage");
228− tryCompare(button, "enabled", true);
224+ tryCompare(coverPage, "showProgress", 1);
229− tap(button)
225+ removeTimeConstraintsFromSwipeAreas(shell);
230− }
231 }
226 }
232
227
233 function confirmLockedApp(app) {
228 function confirmLockedApp(app) {
@@ -248,7 +243,7 @@ Item {
248 LightDM.Greeter.hideGreeter();
243 LightDM.Greeter.hideGreeter();
249 tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile);
244 tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile);
250
245
251− LightDM.Greeter.showGreeter();
246+ showGreeter();
252 tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile + "_greeter");
247 tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile + "_greeter");
253
248
254 LightDM.Greeter.hideGreeter();
249 LightDM.Greeter.hideGreeter();
@@ -293,7 +288,7 @@ Item {
293
288
294 // Cancel emergency mode, and go back to normal
289 // Cancel emergency mode, and go back to normal
295 waitForRendering(greeter)
290 waitForRendering(greeter)
296− LightDM.Greeter.showGreeter()
291+ showGreeter()
297
292
298 tryCompare(greeter, "shown", true)
293 tryCompare(greeter, "shown", true)
299 tryCompare(greeter, "lockedApp", "")
294 tryCompare(greeter, "lockedApp", "")
@@ -328,6 +323,22 @@ Item {
328 tryCompare(greeter, "shown", true);
323 tryCompare(greeter, "shown", true);
329 }
324 }
330
325
326+ function test_emergencyCallPausesTutorial() {
327+ var greeter = findChild(shell, "greeter");
328+ var tutorial = findChild(shell, "tutorial");
329+
330+ AccountsService.demoEdges = true;
331+ enterPin("1234");
332+ tryCompare(tutorial, "paused", false);
333+
334+ showGreeter();
335+ verify(tutorial.paused);
336+
337+ swipeAwayGreeter(true);
338+ tap(findChild(greeter, "emergencyCallLabel"));
339+ verify(tutorial.paused);
340+ }
341+
331 function test_failedLoginsCount() {
342 function test_failedLoginsCount() {
332 AccountsService.failedLogins = 0
343 AccountsService.failedLogins = 0
333
344
@@ -342,22 +353,23 @@ Item {
342 var greeter = findChild(shell, "greeter");
353 var greeter = findChild(shell, "greeter");
343 greeter.failedLoginsDelayAttempts = 3;
354 greeter.failedLoginsDelayAttempts = 3;
344
355
345− var placeHolder = findChild(shell, "wrongNoticeLabel")
356+ var wrongPasswordAnimation = findInvisibleChild(shell, "wrongPasswordAnimation");
346− tryCompare(placeHolder, "text", "")
347
357
348 enterPin("1111")
358 enterPin("1111")
349− tryCompare(placeHolder, "text", "Sorry, incorrect passcode")
359+ verify(wrongPasswordAnimation.running);
350
360
351 enterPin("1111")
361 enterPin("1111")
352− tryCompare(placeHolder, "text", "Sorry, incorrect passcode")
362+ verify(wrongPasswordAnimation.running);
353
363
354− var lockscreen = findChild(shell, "lockscreen")
364+ var delayedLockscreen = findChild(shell, "delayedLockscreen");
355− tryCompare(lockscreen, "delayMinutes", 0)
365+ tryCompare(delayedLockscreen, "delayMinutes", 0);
356 enterPin("1111")
366 enterPin("1111")
357− tryCompare(lockscreen, "delayMinutes", greeter.failedLoginsDelayMinutes);
367+ tryCompare(delayedLockscreen, "delayMinutes", greeter.failedLoginsDelayMinutes);
358 }
368 }
359
369
360 function test_factoryReset() {
370 function test_factoryReset() {
371+ skip("Factory reset support is not finished")
372+
361 maxRetriesTextField.text = "3"
373 maxRetriesTextField.text = "3"
362 resetSpy.clear()
374 resetSpy.clear()
363
375
@@ -388,8 +400,7 @@ Item {
388 compare(shell.usageScenario, "phone");
400 compare(shell.usageScenario, "phone");
389 compare(applicationsDisplayLoader.usageScenario, "phone");
401 compare(applicationsDisplayLoader.usageScenario, "phone");
390
402
391− var lockscreen = findChild(shell, "lockscreen")
403+ tap(findChild(shell, "emergencyCallLabel"));
392− lockscreen.emergencyCall()
393 confirmLockedApp("dialer-app")
404 confirmLockedApp("dialer-app")
394
405
395 // OK, we're in. Now try (but fail) to switch to tablet mode
406 // OK, we're in. Now try (but fail) to switch to tablet mode
@@ -416,16 +427,13 @@ Item {
416 // - Should be back in emergency mode dialer
427 // - Should be back in emergency mode dialer
417
428
418 var greeter = findChild(shell, "greeter");
429 var greeter = findChild(shell, "greeter");
419− var lockscreen = findChild(shell, "lockscreen");
420− verify(lockscreen);
421
430
422− lockscreen.emergencyCall();
431+ tap(findChild(shell, "emergencyCallLabel"));
423 confirmLockedApp("dialer-app");
432 confirmLockedApp("dialer-app");
424 callManager.foregroundCall = phoneCall;
433 callManager.foregroundCall = phoneCall;
425
434
426− LightDM.Greeter.showGreeter();
435+ showGreeter();
427− lockscreen = findChild(shell, "lockscreen");
436+ var lockscreen = findChild(shell, "lockscreen");
428− verify(lockscreen);
429 tryCompare(lockscreen, "shown", true);
437 tryCompare(lockscreen, "shown", true);
430 tryCompare(greeter, "hasLockedApp", false);
438 tryCompare(greeter, "hasLockedApp", false);
431
439
@@ -468,7 +476,7 @@ Item {
468 }
476 }
469
477
470 function test_focusRequestedHidesCoverPage() {
478 function test_focusRequestedHidesCoverPage() {
471− LightDM.Greeter.showGreeter();
479+ showGreeter();
472
480
473 var app = ApplicationManager.startApplication("gallery-app");
481 var app = ApplicationManager.startApplication("gallery-app");
474 // wait until the app is fully loaded (ie, real surface replaces splash screen)
482 // wait until the app is fully loaded (ie, real surface replaces splash screen)
@@ -480,8 +488,7 @@ Item {
480 tryCompare(coverPage, "showProgress", 0);
488 tryCompare(coverPage, "showProgress", 0);
481 tryCompare(greeter, "fullyShown", true);
489 tryCompare(greeter, "fullyShown", true);
482
490
483− LightDM.Greeter.showGreeter();
491+ showGreeter();
484− tryCompare(coverPage, "showProgress", 1);
485
492
486 // Make sure focusing same app triggers same behavior
493 // Make sure focusing same app triggers same behavior
487 ApplicationManager.requestFocusApplication("gallery-app");
494 ApplicationManager.requestFocusApplication("gallery-app");
@@ -555,9 +562,7 @@ Item {
555 only after its idle timeout is triggered.
562 only after its idle timeout is triggered.
556 */
563 */
557 function test_shortLeftEdgeSwipeMakesLauncherStayVisible() {
564 function test_shortLeftEdgeSwipeMakesLauncherStayVisible() {
558− LightDM.Greeter.showGreeter();
565+ showGreeter();
559− var coverPage = testCase.findChild(shell, "coverPage");
560− tryCompare(coverPage, "showProgress", 1);
561
566
562 var launcher = testCase.findChild(shell, "launcher")
567 var launcher = testCase.findChild(shell, "launcher")
563 var launcherPanel = testCase.findChild(launcher, "launcherPanel");
568 var launcherPanel = testCase.findChild(launcher, "launcherPanel");
@@ -573,6 +578,7 @@ Item {
573 tryCompare(launcherPanel, "x", 0);
578 tryCompare(launcherPanel, "x", 0);
574
579
575 // Greeter should be displaced
580 // Greeter should be displaced
581+ var coverPage = findChild(shell, "coverPage");
576 tryCompareFunction(function() { return coverPage.mapToItem(shell, 0, 0).x > shell.width*0.2; }, true);
582 tryCompareFunction(function() { return coverPage.mapToItem(shell, 0, 0).x > shell.width*0.2; }, true);
577
583
578 touchRelease(shell, toX, shell.height * 0.5);
584 touchRelease(shell, toX, shell.height * 0.5);
@@ -595,8 +601,7 @@ Item {
595 tryCompare(shell, "mainApp", galleryApp);
601 tryCompare(shell, "mainApp", galleryApp);
596
602
597 // Show greeter
603 // Show greeter
598− LightDM.Greeter.showGreeter();
604+ showGreeter();
599− tryCompare(coverPage, "showProgress", 1);
600
605
601 // Swipe cover page away
606 // Swipe cover page away
602 touchFlick(shell, 2, shell.height / 2, units.gu(30), shell.height / 2);
607 touchFlick(shell, 2, shell.height / 2, units.gu(30), shell.height / 2);