on xenial help sign in
merge lp:~dandrader/unity8/cursorConfinement
commit15dc8db9d324ba3ef2d42ff036ac815791f302a0 authorLukáš Tinkl <lukas.tinkl@canonical.com> dateFri 2 Sep 2016, 23:48 (10 years ago) parent14bbc752daaabd
124 files changed+3090−552
CMakeLists.txt+1−1 data/unity8-greeter-wrapper+11−3 debian/control+8−4 debian/rules+3−2 debian/unity8-private.install+1 plugins/CMakeLists.txt+1 plugins/Cursor/MousePointer.cpp+42−2 plugins/Cursor/MousePointer.h+8−1 plugins/ImageCache/CMakeLists.txt+8 plugins/ImageCache/ImageCache.cpp+135 plugins/ImageCache/ImageCache.h+53 plugins/ImageCache/plugin.cpp+39 plugins/ImageCache/plugin.h+31 plugins/ImageCache/qmldir+2 plugins/LightDM/IntegratedLightDM/liblightdm/Greeter.cpp−5 plugins/LightDM/IntegratedLightDM/liblightdm/Greeter.h−2 plugins/LightDM/SessionsModel.cpp+22−16 plugins/LightDM/plugin.cpp+1−1 plugins/SessionBroadcast/SessionBroadcast.cpp+35−9 plugins/SessionBroadcast/SessionBroadcast.h+10−3 plugins/Ubuntu/DownloadDaemonListener/CMakeLists.txt+3−1 plugins/Ubuntu/DownloadDaemonListener/DownloadTracker.cpp+2−1 plugins/Ubuntu/DownloadDaemonListener/DownloadTracker.h+2−1 plugins/Ubuntu/DownloadDaemonListener/interface/downloadtrackeradaptor.cpp+2−2 plugins/Ubuntu/DownloadDaemonListener/interface/downloadtrackeradaptor.h+71−4 plugins/Ubuntu/DownloadDaemonListener/interface/metatypes.h+3−6 plugins/Utils/CMakeLists.txt+1 plugins/Utils/URLDispatcher.cpp+77 plugins/Utils/URLDispatcher.h+47 plugins/Utils/plugin.cpp+2 qml/Components/Lockscreen.qml+1−28 qml/Components/PassphraseLockscreen.qml−4 qml/Components/PinLockscreen.qml−4 qml/Components/Wallpaper.qml+44 qml/Components/WallpaperResolver.qml+24−38 qml/Dash/DashBackground.qml+2−2 qml/Dash/Previews/PreviewProgress.qml+5−1 qml/Greeter/CoverPage.qml+6−6 qml/Components/DelayedLockscreen.qml → qml/Greeter/DelayedLockscreen.qml+6−15 qml/Greeter/FullLightDMImpl.qml+2 qml/Greeter/Greeter.qml+60−24 qml/Greeter/GreeterPrompt.qml+21−7 qml/Greeter/IntegratedLightDMImpl.qml+2−1 qml/Greeter/LightDMService.qml+2 qml/Greeter/LoginAreaContainer.qml+38 qml/Greeter/LoginList.qml+96−21 qml/Greeter/NarrowView.qml+147−61 qml/Greeter/SessionIcon.qml+47 qml/Greeter/SessionsList.qml+182 qml/Greeter/WideView.qml+109−38 qml/Greeter/graphics/session_icons/gnome_badge.png qml/Greeter/graphics/session_icons/kde_badge.png qml/Greeter/graphics/session_icons/recovery_console_badge.png qml/Greeter/graphics/session_icons/ubuntu_badge.png qml/Greeter/graphics/session_icons/unknown_badge.png qml/Shell.qml+66−15 qml/Stages/AbstractStage.qml+3 qml/Stages/DecoratedWindow.qml+2 qml/Stages/DesktopStage.qml+7−3 qml/graphics/phone_background.jpg qml/graphics/tablet_background.jpg tests/CMakeLists.txt+8−13 tests/mocks/AccountsService/AccountsService.cpp+19−3 tests/mocks/AccountsService/AccountsService.h+3 tests/mocks/AccountsService/CMakeLists.txt+14 tests/mocks/CMakeLists.txt+1 tests/mocks/LightDM/IntegratedLightDM/MockSessionsModel.cpp+29 tests/mocks/LightDM/IntegratedLightDM/MockSessionsModel.h+12−2 tests/mocks/LightDM/IntegratedLightDM/MockUsersModel.cpp+5 tests/mocks/LightDM/IntegratedLightDM/MockUsersModel.h+2 tests/mocks/LightDM/IntegratedLightDM/liblightdm/Greeter.cpp−5 tests/mocks/LightDM/IntegratedLightDM/liblightdm/Greeter.h−2 tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModel.cpp+22 tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModel.h+3−3 tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModelPrivate.cpp+27−17 tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModelPrivate.h+5−1 tests/mocks/LightDM/IntegratedLightDM/liblightdm/UsersModelPrivate.cpp+15−1 tests/mocks/LightDM/IntegratedLightDM/plugin.cpp+1−1 tests/mocks/SessionBroadcast/CMakeLists.txt+8 tests/mocks/SessionBroadcast/SessionBroadcast.cpp+34 tests/mocks/SessionBroadcast/SessionBroadcast.h+39 tests/mocks/SessionBroadcast/plugin.cpp+31 tests/mocks/SessionBroadcast/plugin.h+32 tests/mocks/SessionBroadcast/qmldir+2 tests/mocks/Ubuntu/DownloadDaemonListener/MockDownloadTracker.cpp+3−1 tests/mocks/Ubuntu/DownloadDaemonListener/MockDownloadTracker.h+2−1 tests/mocks/Ubuntu/Telephony/MockTelepathyHelper.cpp+27 tests/mocks/Ubuntu/Telephony/MockTelepathyHelper.h+22 tests/mocks/Unity/Application/MirSurface.h+2 tests/mocks/Utils/CMakeLists.txt+1 tests/mocks/Utils/URLDispatcher.cpp+38 tests/mocks/Utils/URLDispatcher.h+47 tests/mocks/Utils/plugin.cpp+2 tests/plugins/CMakeLists.txt+2 tests/plugins/ImageCache/CMakeLists.txt+20 tests/plugins/ImageCache/graphics/wide.jpg tests/plugins/ImageCache/test.cpp+214 tests/plugins/ImageCache/test.qml+23 tests/plugins/LightDM/IntegratedLightDM/CMakeLists.txt−3 tests/plugins/LightDM/IntegratedLightDM/sessionsmodel.cpp+32 tests/plugins/LightDM/IntegratedLightDM/usersmodel.cpp+2−2 tests/plugins/SessionBroadcast/BroadcastServer.cpp+33 tests/plugins/SessionBroadcast/BroadcastServer.h+40 tests/plugins/SessionBroadcast/CMakeLists.txt+45 tests/plugins/SessionBroadcast/interfaces.xml+18 tests/plugins/SessionBroadcast/server.cpp+37 tests/plugins/SessionBroadcast/sessionbroadcasttest.cpp+111 tests/plugins/Utils/CMakeLists.txt+9 tests/plugins/Utils/URLDispatcherTest.cpp+78 tests/qmltests/CMakeLists.txt+10−10 tests/qmltests/Components/tst_Lockscreen.qml+1−39 tests/qmltests/Components/tst_WallpaperResolver.qml+21−35 tests/qmltests/Components/tst_ZoomableImage.qml+1−1 tests/qmltests/Dash/Previews/tst_PreviewExpandable.qml+3−3 tests/qmltests/Dash/Previews/tst_PreviewImageGallery.qml+6−6 tests/qmltests/Dash/Previews/tst_PreviewProgress.qml+14−1 tests/qmltests/Dash/Previews/tst_PreviewZoomableImage.qml+7−7 tests/qmltests/Greeter/TestView.qml+4−3 tests/qmltests/Greeter/tst_Greeter.qml+8−1 tests/qmltests/Greeter/tst_NarrowView.qml+47−12 tests/qmltests/Greeter/tst_WideView.qml+154−7 tests/qmltests/Stages/tst_DesktopStage.qml+1−1 tests/qmltests/tst_Shell.qml+157−1 tests/qmltests/tst_ShellWithPin.qml+43−38
CMakeLists.txt+1−1
@@ -57,7 +57,7 @@ find_package(Qt5DBus 5.4 REQUIRED)
5757find_package(Qt5Concurrent 5.4 REQUIRED)
5858find_package(Qt5Sql 5.4 REQUIRED)
5959
60−pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=20)
60+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=21)
6161pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
6262pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
6363pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
data/unity8-greeter-wrapper+11−3
@@ -1,8 +1,7 @@
11#!/bin/sh
22# -*- Mode: sh; indent-tabs-mode: nil; tab-width: 4 -*-
33#
4−# Copyright (C) 2011,2013 Canonical Ltd
5−# Author: Michael Terry <michael.terry@canonical.com>
4+# Copyright (C) 2011,2013,2016 Canonical Ltd
65#
76# This program is free software: you can redistribute it and/or modify it under
87# the terms of the GNU General Public License as published by the Free Software
@@ -42,6 +41,8 @@ set_greeter_var()
4241SUB_SOCKET=$XDG_RUNTIME_DIR/mir_socket
4342rm -f $SUB_SOCKET # clear socket in case we were hard shut down
4443
44+echo "DBUS_SESSION_BUS_ADDRESS=${DBUS_SESSION_BUS_ADDRESS}" >"$XDG_RUNTIME_DIR/dbus-session"
45+
4546# If touch session script (which sets up grid units and mir variables) is available, use it
4647TOUCH_WRAPPER=
4748if [ -x /usr/bin/ubuntu-touch-session ]; then
@@ -65,7 +66,14 @@ set_greeter_var PA_DISABLED 1
6566MIR_SOCKET=$SUB_SOCKET $TOUCH_WRAPPER /sbin/upstart --user --no-startup-event &
6667INIT_PID=$!
6768while [ ! -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
6977
7078# Start any pre-greeter tasks if needed (like wizard)
7179/sbin/initctl emit unity8-greeter-starting
debian/control+8−4
@@ -11,9 +11,9 @@ Build-Depends: cmake,
1111# to g++ so we don't try to run arm g++
1212# on an x86 CPU for example, when cross-compiling.
1313 g++:native,
14− libandroid-properties-dev,
1514 graphviz,
1615 gsettings-ubuntu-schemas (>= 0.0.2+14.10.20140815),
16+ libandroid-properties-dev,
1717 libconnectivity-qt1-dev (>= 0.7.1),
1818 libevdev-dev,
1919 libgeonames-dev (>= 0.2),
@@ -32,10 +32,12 @@ Build-Depends: cmake,
3232 libqt5svg5-dev,
3333 libqt5xmlpatterns5-dev,
3434 libsystemsettings-dev,
35+ libubuntu-download-manager-common-dev,
3536 libubuntugestures5-dev (>= 1.3.2030),
3637 libubuntugestures5-private-dev (>= 1.3.2030),
3738 libudev-dev,
38− libunity-api-dev (>= 7.117),
39+ libudm-common-dev,
40+ libunity-api-dev (>= 7.118),
3941 libusermetricsoutput1-dev,
4042# Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
4143 libx11-dev[!arm64 !armhf],
@@ -133,6 +135,7 @@ Depends: dmz-cursor-theme,
133135 ${shlibs:Depends},
134136Recommends: indicator-keyboard,
135137 indicator-session,
138+ unity-greeter-session-broadcast,
136139 unity-scope-click,
137140 unity-scope-mediascanner2,
138141Breaks: indicator-network (<< 0.5.1+14.10.20141014),
@@ -153,7 +156,8 @@ Depends: qml-module-qtquick-layouts,
153156 qtdeclarative5-ubuntu-settings-components (>= 0.7),
154157 qtdeclarative5-unity-notifications-plugin (>= 0.1.2) | unity-notifications-impl,
155158 ubuntu-thumbnailer-impl-0,
156− unity-application-impl-20,
159+ ubuntu-wallpapers,
160+ unity-application-impl-21,
157161 unity-notifications-impl-3,
158162 unity-plugin-scopes | unity-scopes-impl,
159163 unity-scopes-impl-12,
@@ -199,7 +203,7 @@ Pre-Depends: ${misc:Pre-Depends},
199203Depends: ${misc:Depends},
200204 ${shlibs:Depends},
201205Provides: unity-application-impl,
202− unity-application-impl-20,
206+ unity-application-impl-21,
203207Replaces: unity8-autopilot (<< 8.02+15.04.20150422-0ubuntu1)
204208Description: Fake environment for running Unity 8 shell
205209 Provides fake implementations of some QML modules used by Unity 8 shell
debian/rules+3−2
@@ -37,6 +37,7 @@ override_dh_installdeb:
3737override_dh_makeshlibs:
3838 dh_makeshlibs -Nunity8-private -Nunity8-fake-env
3939
40−# libMockLightDM-qml.so links against liblightdm-qt5-3.so which doesn't exist
4140override_dh_shlibdeps:
42− dh_shlibdeps -XlibMockLightDM-qml.so
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.
43+ dh_shlibdeps -XlibMockLightDM-qml.so -XlibMockAccountsService-qml.so
debian/unity8-private.install+1
@@ -4,6 +4,7 @@ usr/lib/*/unity8/qml/Cursor
44usr/lib/*/unity8/qml/Dash
55usr/lib/*/unity8/qml/GlobalShortcut
66usr/lib/*/unity8/qml/Greeter
7+usr/lib/*/unity8/qml/ImageCache
78usr/lib/*/unity8/qml/LightDM
89usr/lib/*/unity8/qml/Lights
910usr/lib/*/unity8/qml/Powerd
plugins/CMakeLists.txt+1
@@ -15,6 +15,7 @@ add_subdirectory(AccountsService)
1515add_subdirectory(Cursor)
1616add_subdirectory(GlobalShortcut)
1717add_subdirectory(Greeter)
18+add_subdirectory(ImageCache)
1819add_subdirectory(LightDM)
1920add_subdirectory(Lights)
2021add_subdirectory(Dash)
plugins/Cursor/MousePointer.cpp+42−2
@@ -49,8 +49,8 @@ void MousePointer::handleMouseEvent(ulong timestamp, QPointF movement, Qt::Mouse
4949 QPointF appliedMovement(int(m_accumulatedMovement.x()), int(m_accumulatedMovement.y()));
5050 m_accumulatedMovement -= appliedMovement;
5151
52− const qreal newX = x() + appliedMovement.x();
53− const qreal newY = y() + appliedMovement.y();
52+ qreal newX = x() + appliedMovement.x();
53+ qreal newY = y() + appliedMovement.y();
5454 const qreal sceneWidth = parentItem()->width();
5555 const qreal sceneHeight = parentItem()->height();
5656
@@ -86,6 +86,8 @@ void MousePointer::handleMouseEvent(ulong timestamp, QPointF movement, Qt::Mouse
8686 }
8787 }
8888
89+ applyItemConfinement(newX, newY);
90+
8991 setX(qBound(0.0, newX, sceneWidth - 1));
9092 setY(qBound(0.0, newY, sceneHeight - 1));
9193
@@ -94,6 +96,31 @@ void MousePointer::handleMouseEvent(ulong timestamp, QPointF movement, Qt::Mouse
9496 buttons, modifiers);
9597}
9698
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+
97124void MousePointer::handleWheelEvent(ulong timestamp, QPoint angleDelta, Qt::KeyboardModifiers modifiers)
98125{
99126 if (!parentItem()) {
@@ -193,3 +220,16 @@ void MousePointer::setCustomCursor(const QCursor &customCursor)
193220{
194221 CursorImageProvider::instance()->setCustomCursor(customCursor);
195222}
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 @@
2727
2828class MousePointer : public MirMousePointerInterface {
2929 Q_OBJECT
30+ Q_PROPERTY(QQuickItem* confiningItem READ confiningItem WRITE setConfiningItem NOTIFY confiningItemChanged)
3031 Q_PROPERTY(int topBoundaryOffset READ topBoundaryOffset WRITE setTopBoundaryOffset NOTIFY topBoundaryOffsetChanged)
31−
3232public:
3333 MousePointer(QQuickItem *parent = nullptr);
3434
@@ -40,6 +40,9 @@ public:
4040
4141 void setCustomCursor(const QCursor &) override;
4242
43+ QQuickItem* confiningItem() const;
44+ void setConfiningItem(QQuickItem*);
45+
4346 int topBoundaryOffset() const;
4447 void setTopBoundaryOffset(int topBoundaryOffset);
4548
@@ -58,6 +61,7 @@ Q_SIGNALS:
5861 void pushedBottomRightCorner(qreal amount, Qt::MouseButtons buttons);
5962 void pushStopped();
6063 void mouseMoved();
64+ void confiningItemChanged();
6165
6266 void topBoundaryOffsetChanged(int topBoundaryOffset);
6367
@@ -69,6 +73,7 @@ private Q_SLOTS:
6973
7074private:
7175 void registerWindow(QWindow *window);
76+ void applyItemConfinement(qreal &newX, qreal &newY);
7277
7378 QPointer<QWindow> m_registeredWindow;
7479 QPointer<QScreen> m_registeredScreen;
@@ -78,6 +83,8 @@ private:
7883 // Accumulated, unapplied, mouse movement.
7984 QPointF m_accumulatedMovement;
8085
86+ QPointer<QQuickItem> m_confiningItem;
87+
8188 int m_topBoundaryOffset{0};
8289 bool m_pushing{false};
8390};
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
7878 return true;
7979}
8080
81−bool Greeter::lockHint () const
82−{
83− return false;
84−}
85−
8681QString Greeter::selectUserHint() const
8782{
8883 return QLatin1String("");
plugins/LightDM/IntegratedLightDM/liblightdm/Greeter.h−2
@@ -45,7 +45,6 @@ class Q_DECL_EXPORT Greeter : public QObject
4545
4646 Q_PROPERTY(QString hostname READ hostname CONSTANT)
4747 Q_PROPERTY(bool hasGuestAccount READ hasGuestAccountHint CONSTANT)
48− Q_PROPERTY(bool locked READ lockHint CONSTANT)
4948
5049 Q_ENUMS(PromptType MessageType)
5150
@@ -68,7 +67,6 @@ public:
6867 bool hideUsersHint() const;
6968 bool showManualLoginHint() const;
7069 bool showRemoteLoginHint() const;
71− bool lockHint () const;
7270 bool hasGuestAccountHint() const;
7371 QString selectUserHint() const;
7472 bool selectGuestHint() const;
plugins/LightDM/SessionsModel.cpp+22−16
@@ -47,15 +47,15 @@ void SessionsModel::setIconSearchDirectories(const QList<QUrl> searchDirectories
4747 Q_EMIT iconSearchDirectoriesChanged();
4848}
4949
50−QUrl SessionsModel::iconUrl(const QString sessionName) const
50+QUrl SessionsModel::iconUrl(const QString sessionKey) const
5151{
5252 Q_FOREACH(const QUrl& searchDirectory, m_iconSearchDirectories)
5353 {
5454 // This is an established icon naming convention
5555 QString customIconUrl = searchDirectory.toString(QUrl::StripTrailingSlash) +
56− "/custom_" + sessionName + "_badge.png";
56+ "/custom_" + sessionKey + "_badge.png";
5757 QString iconUrl = searchDirectory.toString(QUrl::StripTrailingSlash) +
58− "/" + sessionName + "_badge.png";
58+ "/" + sessionKey + "_badge.png";
5959
6060 QFile customIconFile(customIconUrl);
6161 QFile iconFile(iconUrl);
@@ -63,29 +63,35 @@ QUrl SessionsModel::iconUrl(const QString sessionName) const
6363 return QUrl(customIconUrl);
6464 } else if (iconFile.exists()) {
6565 return QUrl(iconUrl);
66− } else{
66+ } else {
6767 // Search the legacy way
6868 QString path = searchDirectory.toString(QUrl::StripTrailingSlash) + "/";
69− if (sessionName == "ubuntu" || sessionName == "ubuntu-2d") {
69+ bool iconFound = false;
70+ if (sessionKey == "ubuntu" || sessionKey == "ubuntu-2d") {
7071 path += "ubuntu_badge.png";
72+ iconFound = true;
7173 } else if(
72− sessionName == "gnome-classic" ||
73− sessionName == "gnome-flashback-compiz" ||
74− sessionName == "gnome-flashback-metacity" ||
75− sessionName == "gnome-shell" ||
76− sessionName == "gnome-wayland" ||
77− sessionName == "gnome"
74+ sessionKey == "gnome-classic" ||
75+ sessionKey == "gnome-flashback-compiz" ||
76+ sessionKey == "gnome-flashback-metacity" ||
77+ sessionKey == "gnome-shell" ||
78+ sessionKey == "gnome-wayland" ||
79+ sessionKey == "gnome"
7880 ){
7981 path += "gnome_badge.png";
80− } else if (sessionName == "plasma") {
82+ iconFound = true;
83+ } else if (sessionKey == "plasma") {
8184 path += "kde_badge.png";
82− } else if (sessionName == "xterm") {
85+ iconFound = true;
86+ } else if (sessionKey == "xterm") {
8387 path += "recovery_console_badge.png";
84− } else if (sessionName == "remote-login") {
88+ iconFound = true;
89+ } else if (sessionKey == "remote-login") {
8590 path += "remote_login_help.png";
91+ iconFound = true;
8692 }
8793
88− if (QFile(path).exists()) {
94+ if (QFile(path).exists() && iconFound) {
8995 return path;
9096 }
9197 }
@@ -99,7 +105,7 @@ QVariant SessionsModel::data(const QModelIndex& index, int role) const
99105{
100106 switch (role) {
101107 case SessionsModel::IconRole:
102− return iconUrl(m_model->data(index, Qt::DisplayRole).toString());
108+ return iconUrl(m_model->data(index, QLightDM::SessionsModel::KeyRole).toString());
103109 default:
104110 return m_model->data(index, role);
105111 }
plugins/LightDM/plugin.cpp+1−1
@@ -82,7 +82,7 @@ void PLUGIN_CLASSNAME::registerTypes(const char *uri)
8282 qRegisterMetaType<QLightDM::Greeter::PromptType>("QLightDM::Greeter::PromptType");
8383
8484 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"));
8686
8787 qmlRegisterSingletonType<UsersModel>(uri, 0, 1, "Users", users_provider);
8888 qmlRegisterUncreatableType<QLightDM::UsersModel>(uri, 0, 1, "UserRoles", QStringLiteral("Type is not instantiable"));
plugins/SessionBroadcast/SessionBroadcast.cpp+35−9
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013,2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -12,12 +12,12 @@
1212 *
1313 * You should have received a copy of the GNU General Public License
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Author: Michael Terry <michael.terry@canonical.com>
1715 */
1816
1917#include "SessionBroadcast.h"
2018#include <QDBusConnection>
19+#include <QDBusConnectionInterface>
20+#include <QDBusInterface>
2121
2222#include <glib.h>
2323
@@ -27,13 +27,39 @@ SessionBroadcast::SessionBroadcast(QObject* parent)
2727 m_username = QString::fromUtf8(g_get_user_name());
2828
2929 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+}
3054
31− connection.connect(QStringLiteral("com.canonical.Unity.Greeter.Broadcast"),
32− QStringLiteral("/com/canonical/Unity/Greeter/Broadcast"),
33− QStringLiteral("com.canonical.Unity.Greeter.Broadcast"),
34− QStringLiteral("ShowHome"),
35− this,
36− SLOT(onShowHome(const QString &)));
55+void SessionBroadcast::onStartUrl(const QString &username, const QString &url)
56+{
57+ // Since this signal is just used for testing, we don't *really* care if
58+ // username matches, but just in case we do eventually use the signal, we
59+ // should only listen to our own requests.
60+ if (username == m_username) {
61+ Q_EMIT startUrl(url);
62+ }
3763}
3864
3965void SessionBroadcast::onShowHome(const QString &username)
plugins/SessionBroadcast/SessionBroadcast.h+10−3
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013,2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -12,8 +12,6 @@
1212 *
1313 * You should have received a copy of the GNU General Public License
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Authors: Michael Terry <michael.terry@canonical.com>
1715 */
1816
1917#ifndef UNITY_SESSIONBROADCAST_H
@@ -31,14 +29,23 @@ class SessionBroadcast: public QObject
3129public:
3230 explicit SessionBroadcast(QObject *parent = 0);
3331
32+ Q_INVOKABLE void requestUrlStart(const QString &username, const QString &url);
33+ Q_INVOKABLE void requestHomeShown(const QString &username);
34+
3435Q_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);
3540 void showHome();
3641
3742private Q_SLOTS:
43+ void onStartUrl(const QString &username, const QString &url);
3844 void onShowHome(const QString &username);
3945
4046private:
4147 QString m_username;
48+ QDBusInterface *m_broadcaster;
4249};
4350
4451#endif
plugins/Ubuntu/DownloadDaemonListener/CMakeLists.txt+3−1
@@ -12,5 +12,7 @@ set(DOWNLOADDAEMONLISTENER_SOURCES
1212add_library(DownloadDaemonListener MODULE ${DOWNLOADDAEMONLISTENER_SOURCES})
1313
1414qt5_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})
1618add_unity8_plugin(Ubuntu.DownloadDaemonListener 0.1 Ubuntu/DownloadDaemonListener TARGETS DownloadDaemonListener)
plugins/Ubuntu/DownloadDaemonListener/DownloadTracker.cpp+2−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 - Canonical Ltd.
2+ * Copyright (C) 2013-2016 Canonical Ltd.
33 *
44 * This program is free software: you can redistribute it and/or modify it
55 * under the terms of the GNU Lesser General Public License, as
@@ -78,6 +78,7 @@ void DownloadTracker::startService()
7878 connect(m_adaptor, static_cast<void (DownloadTrackerAdaptor::*)(qulonglong, qulonglong)>(&DownloadTrackerAdaptor::progress), this, &DownloadTracker::progress);
7979 connect(m_adaptor, &DownloadTrackerAdaptor::resumed, this, &DownloadTracker::resumed);
8080 connect(m_adaptor, &DownloadTrackerAdaptor::started, this, &DownloadTracker::started);
81+ connect(m_adaptor, &DownloadTrackerAdaptor::processing, this, &DownloadTracker::processing);
8182 }
8283 // FIXME find a better way of determining if the service is ready
8384 Q_EMIT serviceReadyChanged(m_adaptor && m_adaptor->isValid());
plugins/Ubuntu/DownloadDaemonListener/DownloadTracker.h+2−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 - Canonical Ltd.
2+ * Copyright (C) 2013-2016 Canonical Ltd.
33 *
44 * This program is free software: you can redistribute it and/or modify it
55 * under the terms of the GNU Lesser General Public License, as
@@ -54,6 +54,7 @@ Q_SIGNALS:
5454 void error(const QString &error);
5555 void finished(const QString &path);
5656 void paused(bool success);
57+ void processing(const QString &path);
5758 void progress(qulonglong received, qulonglong total);
5859 void resumed(bool success);
5960 void started(bool success);
plugins/Ubuntu/DownloadDaemonListener/interface/downloadtrackeradaptor.cpp+2−2
@@ -1,8 +1,8 @@
11/*
22 * 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
44 *
5− * qdbusxml2cpp is Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
5+ * qdbusxml2cpp is Copyright (C) 2015 The Qt Company Ltd.
66 *
77 * This is an auto-generated file.
88 * This file may have been hand-edited. Look for HAND-EDIT comments
plugins/Ubuntu/DownloadDaemonListener/interface/downloadtrackeradaptor.h+71−4
@@ -1,15 +1,15 @@
11/*
22 * 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
44 *
5− * qdbusxml2cpp is Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
5+ * qdbusxml2cpp is Copyright (C) 2015 The Qt Company Ltd.
66 *
77 * This is an auto-generated file.
88 * Do not edit! All changes made to it will be lost.
99 */
1010
11−#ifndef DOWNLOADTRACKERADAPTOR_H_1374434371
12−#define DOWNLOADTRACKERADAPTOR_H_1374434371
11+#ifndef DOWNLOADTRACKERADAPTOR_H
12+#define DOWNLOADTRACKERADAPTOR_H
1313
1414#include <QtCore/QObject>
1515#include <QtCore/QByteArray>
@@ -36,6 +36,22 @@ public:
3636
3737 ~DownloadTrackerAdaptor();
3838
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+
3955public Q_SLOTS: // METHODS
4056 inline QDBusPendingReply<> allowGSMDownload(bool allowed)
4157 {
@@ -50,6 +66,24 @@ public Q_SLOTS: // METHODS
5066 return asyncCallWithArgumentList(QStringLiteral("cancel"), argumentList);
5167 }
5268
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+
5387 inline QDBusPendingReply<bool> isGSMDownloadAllowed()
5488 {
5589 QList<QVariant> argumentList;
@@ -80,6 +114,27 @@ public Q_SLOTS: // METHODS
80114 return asyncCallWithArgumentList(QStringLiteral("resume"), argumentList);
81115 }
82116
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+
83138 inline QDBusPendingReply<> setThrottle(qulonglong speed)
84139 {
85140 QList<QVariant> argumentList;
@@ -93,6 +148,12 @@ public Q_SLOTS: // METHODS
93148 return asyncCallWithArgumentList(QStringLiteral("start"), argumentList);
94149 }
95150
151+ inline QDBusPendingReply<int> state()
152+ {
153+ QList<QVariant> argumentList;
154+ return asyncCallWithArgumentList(QStringLiteral("state"), argumentList);
155+ }
156+
96157 inline QDBusPendingReply<qulonglong> throttle()
97158 {
98159 QList<QVariant> argumentList;
@@ -106,10 +167,16 @@ public Q_SLOTS: // METHODS
106167 }
107168
108169Q_SIGNALS: // SIGNALS
170+ void authError(AuthErrorStruct error);
109171 void canceled(bool success);
110172 void error(const QString &error);
111173 void finished(const QString &path);
174+ void hashError(HashErrorStruct error);
175+ void httpError(HttpErrorStruct error);
176+ void networkError(NetworkErrorStruct error);
112177 void paused(bool success);
178+ void processError(ProcessErrorStruct error);
179+ void processing(const QString &path);
113180 void progress(qulonglong received, qulonglong total);
114181 void resumed(bool success);
115182 void started(bool success);
plugins/Ubuntu/DownloadDaemonListener/interface/metatypes.h+3−6
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2013 2013 Canonical Ltd.
2+ * Copyright 2013-2016 Canonical Ltd.
33 *
44 * This library is free software; you can redistribute it and/or
55 * modify it under the terms of version 3 of the GNU Lesser General Public
@@ -19,10 +19,7 @@
1919#ifndef DOWNLOADER_LIB_APP_METATYPES_H
2020#define DOWNLOADER_LIB_APP_METATYPES_H
2121
22−#include <QMap>
23−
24−typedef QMap<QString, QString> StringMap;
25−
26−Q_DECLARE_METATYPE(StringMap)
22+// Just include the system file so we're always up to date.
23+#include <ubuntu/download_manager/metatypes.h>
2724
2825#endif // METATYPES_H
plugins/Utils/CMakeLists.txt+1
@@ -29,6 +29,7 @@ set(QMLPLUGIN_SRC
2929 inputeventgenerator.cpp
3030 deviceconfigparser.cpp
3131 globalfunctions.cpp
32+ URLDispatcher.cpp
3233 plugin.cpp
3334 )
3435
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 @@
3838#include "inputeventgenerator.h"
3939#include "deviceconfigparser.h"
4040#include "globalfunctions.h"
41+#include "URLDispatcher.h"
4142
4243static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
4344{
@@ -80,4 +81,5 @@ void UtilsPlugin::registerTypes(const char *uri)
8081 qmlRegisterType<InputEventGenerator>(uri, 0, 1, "InputEventGenerator");
8182 qmlRegisterType<DeviceConfigParser>(uri, 0, 1, "DeviceConfigParser");
8283 qmlRegisterSingletonType<GlobalFunctions>(uri, 0, 1, "Functions", createGlobalFunctions);
84+ qmlRegisterType<URLDispatcher>(uri, 0, 1, "URLDispatcher");
8385}
qml/Components/Lockscreen.qml+1−28
@@ -41,9 +41,6 @@ Showable {
4141 // The text to be displayed in case the login failed
4242 property string errorText: ""
4343
44− // If > 0, a forced delay is happening
45− property int delayMinutes: 0
46−
4744 // Set those to a value greater 0 to restrict the pin length.
4845 // If both are unset, the Lockscreen will show a confirm button and allow typing any length of pin before
4946 // confirming. If minPinLength is set to a value > 0, the confirm button will only become active when the
@@ -91,14 +88,6 @@ Showable {
9188 popup.z = Number.MAX_VALUE
9289 }
9390
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−
10291 Rectangle {
10392 // In case background fails to load
10493 id: backgroundBackup
@@ -107,17 +96,13 @@ Showable {
10796 visible: root.background.toString() !== ""
10897 }
10998
110− Image {
99+ Wallpaper {
111100 id: backgroundImage
112101 objectName: "lockscreenBackground"
113102 anchors {
114103 fill: parent
115104 }
116− // Limit how much memory we'll reserve for this image
117− sourceSize.height: height
118− sourceSize.width: width
119105 source: root.required ? root.background : ""
120− fillMode: Image.PreserveAspectCrop
121106 }
122107
123108 // This is to
@@ -141,8 +126,6 @@ Showable {
141126 source: {
142127 if (resetting || !root.required) {
143128 return ""
144− } else if (root.delayMinutes > 0) {
145− return "DelayedLockscreen.qml"
146129 } else if (root.alphaNumeric) {
147130 return "PassphraseLockscreen.qml"
148131 } else {
@@ -197,16 +180,6 @@ Showable {
197180 property: "entryEnabled"
198181 value: !pinPadLoader.waiting
199182 }
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− }
210183 Binding {
211184 target: pinPadLoader.item
212185 property: "showCancelButton"
qml/Components/PassphraseLockscreen.qml−4
@@ -43,10 +43,6 @@ FocusScope {
4343 }
4444 }
4545
46− function showText(text) {
47− pinentryField.text = text;
48− }
49−
5046 Column {
5147 id: shakeContainer
5248 anchors.horizontalCenter: parent.horizontalCenter
qml/Components/PinLockscreen.qml−4
@@ -46,10 +46,6 @@ FocusScope {
4646 }
4747 }
4848
49− function showText(text) {
50− pinentryField.text = text;
51− }
52−
5349 Keys.onPressed: {
5450 if (pinentryField.text.length == root.maxPinLength)
5551 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 @@
1515 */
1616
1717import QtQuick 2.4
18−import AccountsService 0.1
19−import GSettings 1.0
2018import Ubuntu.Components 1.3
2119
22−/*
23− Defines the background URL based on several factors, such as:
24− - default, fallback, background
25− - Background set in AccountSettings, if any
26− - Background set in GSettings, if any
27− */
28−QtObject {
29− // Users should set their UI width here.
30− property real width
31−
32− property url defaultBackground: Qt.resolvedUrl(width >= units.gu(60) ? "../graphics/tablet_background.jpg"
33− : "../graphics/phone_background.jpg")
20+Item {
21+ id: root
3422
35− // That's the property users of this component are going to consume.
36− readonly property url background: asImageTester.status == Image.Ready ? asImageTester.source
37− : gsImageTester.status == Image.Ready ? gsImageTester.source : defaultBackground
38−
39− // This is a dummy image to detect if the custom AS set wallpaper loads successfully.
40− property var _asImageTester: Image {
41− id: asImageTester
42− source: AccountsService.backgroundFile != undefined && AccountsService.backgroundFile.length > 0 ? AccountsService.backgroundFile : ""
43− height: 0
44− width: 0
45− sourceSize.height: 0
46− sourceSize.width: 0
47− }
23+ // Provide a list of wallpapers to resolve here, preferred ones first
24+ property var candidates: []
4825
49− // This is a dummy image to detect if the custom GSettings set wallpaper loads successfully.
50− property var _gsImageTester: Image {
51− id: gsImageTester
52− source: backgroundSettings.pictureUri && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : ""
53− height: 0
54− width: 0
55− sourceSize.height: 0
56− sourceSize.width: 0
26+ readonly property url background: {
27+ for (var i = 0; i < repeater.count; i++) {
28+ if (repeater.itemAt(i).status === Image.Ready)
29+ return candidates[i];
30+ }
31+ if (i > 0) {
32+ return candidates[i - 1]; // last item is last resort
33+ } else {
34+ return "";
35+ }
5736 }
5837
59− property var _gsettings: GSettings {
60− id: backgroundSettings
61− schema.id: "org.gnome.desktop.background"
38+ Repeater {
39+ id: repeater
40+ model: root.candidates
41+ delegate: Image {
42+ source: modelData
43+ height: 0
44+ width: 0
45+ sourceSize.height: 0
46+ sourceSize.width: 0
47+ }
6248 }
6349}
qml/Dash/DashBackground.qml+2−2
@@ -16,10 +16,10 @@
1616
1717import QtQuick 2.4
1818import QtQuick.Window 2.2
19−import Ubuntu.Thumbnailer 0.1
19+import ImageCache 0.1
2020
2121Image {
22− source: "image://thumbnailer/" + Qt.resolvedUrl("graphics/paper.png")
22+ source: "image://unity8imagecache/" + Qt.resolvedUrl("graphics/paper.png")
2323 fillMode: Image.PreserveAspectCrop
2424 horizontalAlignment: Image.AlignRight
2525 verticalAlignment: Image.AlignTop
qml/Dash/Previews/PreviewProgress.qml+5−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -59,6 +59,10 @@ PreviewWidget {
5959 }
6060 }
6161
62+ onProcessing: {
63+ progressBar.indeterminate = true;
64+ }
65+
6266 onFinished: {
6367 root.triggered(widgetId, "finished", widgetData)
6468 }
qml/Greeter/CoverPage.qml+6−6
@@ -25,13 +25,14 @@ Showable {
2525 property real dragHandleLeftMargin
2626 property real launcherOffset
2727 property alias background: greeterBackground.source
28+ property alias hasCustomBackground: backgroundShade.visible
2829 property real backgroundTopMargin
2930 property var infographicModel
3031 property bool draggable: true
3132
3233 property alias infographics: infographics
3334
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)
3536
3637 signal tease()
3738 signal clicked()
@@ -82,24 +83,23 @@ Showable {
8283 color: "black"
8384 }
8485
85− CrossFadeImage {
86+ Wallpaper {
8687 id: greeterBackground
8788 objectName: "greeterBackground"
8889 anchors {
8990 fill: parent
9091 topMargin: root.backgroundTopMargin
9192 }
92− fillMode: Image.PreserveAspectCrop
93− // Limit how much memory we'll reserve for this image
94− sourceSize.height: height
95− sourceSize.width: width
9693 }
9794
9895 // Darkens wallpaper so that we can read text on it and see infographic
9996 Rectangle {
97+ id: backgroundShade
98+ objectName: "backgroundShade"
10099 anchors.fill: parent
101100 color: "black"
102101 opacity: 0.4
102+ visible: false
103103 }
104104
105105 Infographics {
qml/Components/DelayedLockscreen.qml → qml/Greeter/DelayedLockscreen.qmlrenamed+6−15
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013,2014 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -16,7 +16,6 @@
1616
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3
19−import "../Components"
2019
2120Item {
2221 id: root
@@ -24,12 +23,6 @@ Item {
2423 property int delayMinutes
2524 property bool alphaNumeric
2625
27− signal entered(string passphrase) // unused
28− signal cancel() // unused
29−
30− function clear(playAnimation) {}
31− function showText(text) {}
32−
3326 Column {
3427 anchors.left: parent.left
3528 anchors.right: parent.right
@@ -46,7 +39,7 @@ Item {
4639 horizontalAlignment: Text.AlignHCenter
4740 wrapMode: Text.Wrap
4841 fontSize: "x-large"
49− color: "#f3f3e7"
42+ color: "white"
5043 text: i18n.tr("Device Locked")
5144 }
5245
@@ -60,7 +53,7 @@ Item {
6053 anchors.right: parent.right
6154 horizontalAlignment: Text.AlignHCenter
6255 wrapMode: Text.Wrap
63− color: "#f3f3e7"
56+ color: "white"
6457 text: alphaNumeric ?
6558 i18n.tr("You have been locked out due to too many failed passphrase attempts.") :
6659 i18n.tr("You have been locked out due to too many failed passcode attempts.")
@@ -71,7 +64,7 @@ Item {
7164 anchors.right: parent.right
7265 horizontalAlignment: Text.AlignHCenter
7366 wrapMode: Text.Wrap
74− color: "#f3f3e7"
67+ color: "white"
7568 text: i18n.tr("Please wait %1 minute and then try again…",
7669 "Please wait %1 minutes and then try again…",
7770 root.delayMinutes).arg(root.delayMinutes)
@@ -83,10 +76,8 @@ Item {
8376 }
8477
8578 Icon {
86− // It would be nice to use a less network-specific name,
87− // but this is the only lock icon we have.
88− name: "network-secure"
89− color: "#f3f3e7"
79+ name: "lock"
80+ color: "white"
9081 height: units.gu(4)
9182 width: units.gu(4)
9283 anchors.horizontalCenter: parent.horizontalCenter
qml/Greeter/FullLightDMImpl.qml+2
@@ -22,6 +22,8 @@ Item{
2222
2323 property var greeter: LightDM.Greeter
2424 property var infographic: LightDM.Infographic
25+ property var sessions: LightDM.Sessions
26+ property var sessionRoles: LightDM.SessionRoles
2527 property var users: LightDM.Users
2628 property var userRoles: LightDM.UserRoles
2729
qml/Greeter/Greeter.qml+60−24
@@ -34,6 +34,7 @@ Showable {
3434 property real dragHandleLeftMargin: 0
3535
3636 property url background
37+ property bool hasCustomBackground
3738
3839 // How far to offset the top greeter layer during a launcher left-drag
3940 property real launcherOffset
@@ -72,9 +73,12 @@ Showable {
7273 }
7374 forcedUnlock = false;
7475 if (required) {
76+ if (loader.item) {
77+ loader.item.reset(true /* forceShow */);
78+ }
7579 // Normally loader.onLoaded will select a user, but if we're
7680 // already shown, do it manually.
77− d.selectUser(d.currentIndex, true);
81+ d.selectUser(d.currentIndex, false);
7882 }
7983
8084 // Even though we may already be shown, we want to call show() for its
@@ -105,8 +109,8 @@ Showable {
105109 }
106110 }
107111
108− // Notify that the user has explicitly requested the given app through unity8 GUI.
109− function notifyUserRequestedApp(appId) {
112+ // Notify that the user has explicitly requested an app
113+ function notifyUserRequestedApp() {
110114 if (!active) {
111115 return;
112116 }
@@ -132,6 +136,22 @@ Showable {
132136 return d.startUnlock(true /* toTheRight */);
133137 }
134138
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+
135155 QtObject {
136156 id: d
137157
@@ -178,7 +198,7 @@ Showable {
178198 return;
179199 d.waiting = true;
180200 if (reset) {
181− loader.item.reset();
201+ loader.item.reset(false /* forceShow */);
182202 }
183203 currentIndex = index;
184204 var user = LightDMService.users.data(index, LightDMService.userRoles.NameRole);
@@ -197,7 +217,7 @@ Showable {
197217
198218 function login() {
199219 d.waiting = true;
200− if (LightDMService.greeter.startSessionSync()) {
220+ if (LightDMService.greeter.startSessionSync(root.sessionToStart())) {
201221 sessionStarted();
202222 hideView();
203223 } else if (loader.item) {
@@ -222,6 +242,29 @@ Showable {
222242 }
223243 }
224244 }
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+ }
225268 }
226269
227270 onLauncherOffsetChanged: {
@@ -393,6 +436,12 @@ Showable {
393436 value: root.background
394437 }
395438
439+ Binding {
440+ target: loader.item
441+ property: "hasCustomBackground"
442+ value: root.hasCustomBackground
443+ }
444+
396445 Binding {
397446 target: loader.item
398447 property: "locked"
@@ -436,20 +485,7 @@ Showable {
436485 onShowGreeter: root.forceShow()
437486 onHideGreeter: root.forcedUnlock = true
438487
439− onShowMessage: {
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− }
488+ onShowMessage: d.showPromptMessage(text, isError)
453489
454490 onShowPrompt: {
455491 if (loader.item) {
@@ -557,10 +593,8 @@ Showable {
557593 if (!d.secureFingerprint) {
558594 d.startUnlock(false /* toTheRight */); // use normal login instead
559595 }
560− if (loader.item) {
561− var msg = d.secureFingerprint ? i18n.tr("Try again") : "";
562− loader.item.showErrorMessage(msg);
563− }
596+ var msg = d.secureFingerprint ? i18n.tr("Try again") : "";
597+ d.showFingerprintMessage(msg);
564598 }
565599
566600 Component.onCompleted: restartOperation()
@@ -578,8 +612,10 @@ Showable {
578612 return;
579613 }
580614 console.log("Identified user by fingerprint:", result);
581− if (loader.item)
615+ if (loader.item) {
616+ loader.item.enabled = false;
582617 loader.item.notifyAuthenticationSucceeded(true /* showFakePassword */);
618+ }
583619 if (root.active)
584620 root.forcedUnlock = true;
585621 }
qml/Greeter/GreeterPrompt.qml+21−7
@@ -38,6 +38,16 @@ FocusScope {
3838 d.enabled = true;
3939 }
4040
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+
4151 StyledItem {
4252 id: d
4353
@@ -50,6 +60,12 @@ FocusScope {
5060 : theme.palette.disabled.raisedSecondaryText
5161 readonly property color errorColor: passwordInput.enabled ? theme.palette.normal.negative
5262 : theme.palette.disabled.negative
63+
64+ onEnabledChanged: {
65+ if (!enabled) {
66+ fakeLabel.text = passwordInput.displayText;
67+ }
68+ }
5369 }
5470
5571 Rectangle {
@@ -142,15 +158,13 @@ FocusScope {
142158 }
143159 }
144160
145− onAccepted: {
146− if (d.enabled)
147− respond();
148− }
161+ onAccepted: respond()
149162
150163 function respond() {
151− d.enabled = false;
152− fakeLabel.text = displayText;
153− root.responded(text);
164+ if (d.enabled) {
165+ d.enabled = false;
166+ root.responded(text);
167+ }
154168 }
155169
156170 Keys.onEscapePressed: {
qml/Greeter/IntegratedLightDMImpl.qml+2−1
@@ -22,7 +22,8 @@ Item{
2222
2323 property var greeter: LightDM.Greeter
2424 property var infographic: LightDM.Infographic
25+ property var sessions: LightDM.Sessions
26+ property var sessionRoles: LightDM.SessionRoles
2527 property var users: LightDM.Users
2628 property var userRoles: LightDM.UserRoles
27−
2829}
qml/Greeter/LightDMService.qml+2
@@ -27,6 +27,8 @@ Loader {
2727
2828 property var greeter: d.valid ? loader.item.greeter : null
2929 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
3032 property var users: d.valid ? loader.item.users : null
3133 property var userRoles: d.valid ? loader.item.userRoles : null
3234
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
1818import QtGraphicalEffects 1.0
1919import Ubuntu.Components 1.3
2020import "../Components"
21+import "." 0.1
2122
2223StyledItem {
2324 id: root
@@ -28,16 +29,22 @@ StyledItem {
2829 property int currentIndex
2930 property bool locked
3031 property bool waiting
32+ property alias boxVerticalOffset: highlightItem.y
3133
34+ readonly property alias passwordInput: passwordInput
3235 readonly property int numAboveBelow: 4
3336 readonly property int cellHeight: units.gu(5)
3437 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
3641 readonly property string currentUser: userList.currentItem.username
3742 property bool wasPrompted: false
3843
39− signal selected(int index)
44+ signal loginListSessionChanged(string session)
4045 signal responded(string response)
46+ signal selected(int index)
47+ signal sessionChooserButtonClicked()
4148
4249 function tryToUnlock() {
4350 if (wasPrompted) {
@@ -78,6 +85,10 @@ StyledItem {
7885 root.resetAuthentication();
7986 }
8087
88+ function showFakePassword() {
89+ passwordInput.showFakePassword();
90+ }
91+
8192 QtObject {
8293 id: d
8394
@@ -97,6 +108,7 @@ StyledItem {
97108 name: "Ubuntu.Components.Themes.Ambiance"
98109 }
99110
111+ KeyNavigation.tab: sessionChooser
100112 Keys.onUpPressed: {
101113 selected(currentIndex - 1);
102114 event.accepted = true;
@@ -114,30 +126,17 @@ StyledItem {
114126 userList.currentIndex = currentIndex;
115127 }
116128
117− BorderImage {
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 {
129+ LoginAreaContainer {
130130 id: highlightItem
131+ objectName: "highlightItem"
131132 anchors {
132133 left: parent.left
133134 leftMargin: units.gu(2)
134135 right: parent.right
135136 rightMargin: units.gu(2)
136− verticalCenter: parent.verticalCenter
137137 }
138+
138139 height: root.highlightedHeight
139− aspect: UbuntuShape.Flat
140− backgroundColor: theme.palette.normal.raised
141140 }
142141
143142 ListView {
@@ -148,8 +147,8 @@ StyledItem {
148147 anchors.leftMargin: units.gu(2)
149148 anchors.rightMargin: units.gu(2)
150149
151− preferredHighlightBegin: userList.height / 2 - root.highlightedHeight / 2
152− preferredHighlightEnd: userList.height / 2 - root.highlightedHeight / 2
150+ preferredHighlightBegin: highlightItem.y
151+ preferredHighlightEnd: highlightItem.y
153152 highlightRangeMode: ListView.StrictlyEnforceRange
154153 highlightMoveDuration: root.moveDuration
155154 flickDeceleration: 10000
@@ -173,6 +172,7 @@ StyledItem {
173172
174173 readonly property bool belowHighlight: (userList.currentIndex < 0 && index > 0) || (userList.currentIndex >= 0 && index > userList.currentIndex)
175174 readonly property int belowOffset: root.highlightedHeight - root.cellHeight
175+ readonly property string userSession: session
176176 readonly property string username: name
177177
178178 opacity: {
@@ -206,9 +206,22 @@ StyledItem {
206206 bottomMargin: -(units.gu(4) + (parent.belowHighlight ? parent.belowOffset : 0))
207207 }
208208 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
210211
211212 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+ }
212225 }
213226
214227 MouseArea {
@@ -237,6 +250,67 @@ StyledItem {
237250 }
238251 }
239252
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+
240314 FadingLabel {
241315 id: infoLabel
242316 objectName: "infoLabel"
@@ -283,6 +357,7 @@ StyledItem {
283357
284358 WrongPasswordAnimation {
285359 id: wrongPasswordAnimation
360+ objectName: "wrongPasswordAnimation"
286361 target: passwordInput
287362 }
288363 }
qml/Greeter/NarrowView.qml+147−61
@@ -15,7 +15,9 @@
1515 */
1616
1717import QtQuick 2.4
18+import QtQuick.Window 2.2
1819import Ubuntu.Components 1.3
20+import Ubuntu.Telephony 0.1 as Telephony
1921import "../Components"
2022
2123FocusScope {
@@ -23,34 +25,40 @@ FocusScope {
2325
2426 property alias dragHandleLeftMargin: coverPage.dragHandleLeftMargin
2527 property alias launcherOffset: coverPage.launcherOffset
26− property int currentIndex // unused
27− property alias delayMinutes: lockscreen.delayMinutes
28+ property alias currentIndex: loginList.currentIndex
29+ property alias delayMinutes: delayedLockscreen.delayMinutes
2830 property alias backgroundTopMargin: coverPage.backgroundTopMargin
2931 property url background
32+ property bool hasCustomBackground
3033 property bool locked
31− property bool alphanumeric
32− property var userModel // unused
34+ property alias alphanumeric: loginList.alphanumeric
35+ property alias userModel: loginList.model
3336 property alias infographicModel: coverPage.infographicModel
37+ property string sessionToStart
3438 property bool waiting
3539 readonly property bool fullyShown: coverPage.showProgress === 1 || lockscreen.shown
3640 readonly property bool required: coverPage.required || lockscreen.required
3741 readonly property bool animating: coverPage.showAnimation.running || coverPage.hideAnimation.running
3842
39− signal selected(int index) // unused
43+ // so that it can be replaced in tests with a mock object
44+ property var inputMethod: Qt.inputMethod
45+
46+ signal selected(int index)
4047 signal responded(string response)
4148 signal tease()
4249 signal emergencyCall()
4350
4451 function showMessage(html) {
45− // TODO
52+ loginList.showMessage(html);
4653 }
4754
4855 function showPrompt(text, isSecret, isDefaultPrompt) {
49− lockscreen.promptText = isDefaultPrompt ? "" : text.toLowerCase();
50− lockscreen.maybeShow();
56+ loginList.showPrompt(text, isSecret, isDefaultPrompt);
5157 }
5258
5359 function showLastChance() {
60+ /* TODO: when we finish support for resetting device after too many
61+ failed logins, we should re-add this popup.
5462 var title = lockscreen.alphaNumeric ?
5563 i18n.tr("Sorry, incorrect passphrase.") :
5664 i18n.tr("Sorry, incorrect passcode.");
@@ -59,6 +67,7 @@ FocusScope {
5967 i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
6068 i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."));
6169 lockscreen.showInfoPopup(title, text);
70+ */
6271 }
6372
6473 function hide() {
@@ -67,27 +76,24 @@ FocusScope {
6776 }
6877
6978 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) {
73− lockscreen.showText("...."); // actual text doesn't matter, we show bullets
79+ if (showFakePassword) {
80+ loginList.showFakePassword();
7481 }
7582 }
7683
7784 function notifyAuthenticationFailed() {
78− lockscreen.customError = "";
79− lockscreen.clear(true);
85+ loginList.showError();
8086 }
8187
8288 function showErrorMessage(msg) {
8389 coverPage.showErrorMessage(msg);
84− lockscreen.customError = msg ? msg : " "; // avoid default message
85− lockscreen.clear(true);
8690 }
8791
88− function reset() {
89− lockscreen.customError = "";
90− coverPage.show();
92+ function reset(forceShow) {
93+ loginList.reset();
94+ if (forceShow) {
95+ coverPage.show();
96+ }
9197 }
9298
9399 function tryToUnlock(toTheRight) {
@@ -109,48 +115,59 @@ FocusScope {
109115 }
110116 }
111117
112− Lockscreen {
118+ Showable {
113119 id: lockscreen
114120 objectName: "lockscreen"
115−
121+ anchors.fill: parent
116122 shown: false
123+
117124 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
118125 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
119− anchors.fill: parent
120− visible: required
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();
126+
127+ Wallpaper {
128+ id: lockscreenBackground
129+ objectName: "lockscreenBackground"
130+ anchors {
131+ fill: parent
132+ topMargin: root.backgroundTopMargin
147133 }
134+ source: root.background
148135 }
149136
150− onVisibleChanged: {
151− if (visible) {
152− lockscreen.forceActiveFocus();
137+ // Darken background to match CoverPage
138+ Rectangle {
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
153153 }
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
154171 }
155172
156173 function maybeShow() {
@@ -172,21 +189,16 @@ FocusScope {
172189 height: parent.height
173190 width: parent.width
174191 background: root.background
192+ hasCustomBackground: root.hasCustomBackground
175193 draggable: !root.waiting
176194 onTease: root.tease()
177195 onClicked: hide()
178196
179197 onShowProgressChanged: {
180198 if (showProgress === 1) {
181− lockscreen.reset();
182− }
183−
184− if (showProgress === 0) {
185− if (root.locked) {
186− lockscreen.clear(false); // to reset focus if necessary
187− } else {
188− root.responded("");
189− }
199+ loginList.reset();
200+ } else if (showProgress === 0) {
201+ loginList.tryToUnlock();
190202 }
191203 }
192204
@@ -198,4 +210,78 @@ FocusScope {
198210 }
199211 }
200212 }
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+ }
201287}
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 @@
1616
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3
19+import "." 0.1
1920
2021FocusScope {
2122 id: root
2223 focus: true
2324
25+ property alias background: coverPage.background
26+ property alias backgroundTopMargin: coverPage.backgroundTopMargin
27+ property alias hasCustomBackground: coverPage.hasCustomBackground
2428 property alias dragHandleLeftMargin: coverPage.dragHandleLeftMargin
29+ property alias infographicModel: coverPage.infographicModel
2530 property alias launcherOffset: coverPage.launcherOffset
2631 property alias currentIndex: loginList.currentIndex
2732 property int delayMinutes // TODO
28− property alias backgroundTopMargin: coverPage.backgroundTopMargin
29− property alias background: coverPage.background
30− property bool locked
3133 property alias alphanumeric: loginList.alphanumeric
32− property alias userModel: loginList.model
33− property alias infographicModel: coverPage.infographicModel
34− property bool waiting
34+ property alias locked: loginList.locked
35+ property alias sessionToStart: loginList.currentSession
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
3540 readonly property bool fullyShown: coverPage.showProgress === 1
3641 readonly property bool required: coverPage.required
37− readonly property bool animating: coverPage.showAnimation.running || coverPage.hideAnimation.running
3842
3943 // so that it can be replaced in tests with a mock object
4044 property var inputMethod: Qt.inputMethod
@@ -44,36 +48,20 @@ FocusScope {
4448 signal tease()
4549 signal emergencyCall() // unused
4650
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−
6751 function notifyAuthenticationFailed() {
6852 loginList.showError();
6953 }
7054
71− function showErrorMessage(msg) {
72− coverPage.showErrorMessage(msg);
55+ function reset(forceShow) {
56+ loginList.reset();
7357 }
7458
75− function reset() {
76− loginList.reset();
59+ function showMessage(html) {
60+ loginList.showMessage(html);
61+ }
62+
63+ function showPrompt(text, isSecret, isDefaultPrompt) {
64+ loginList.showPrompt(text, isSecret, isDefaultPrompt);
7765 }
7866
7967 function tryToUnlock(toTheRight) {
@@ -92,6 +80,20 @@ FocusScope {
9280 }
9381 }
9482
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+
9597 Rectangle {
9698 anchors.fill: parent
9799 color: "black"
@@ -104,6 +106,7 @@ FocusScope {
104106 height: parent.height
105107 width: parent.width
106108 draggable: !root.locked && !root.waiting
109+ state: "LoginList"
107110
108111 infographics {
109112 height: 0.75 * parent.height
@@ -122,21 +125,89 @@ FocusScope {
122125 id: loginList
123126 objectName: "loginList"
124127
128+ width: units.gu(40)
125129 anchors {
126130 left: parent.left
127131 leftMargin: Math.min(parent.width * 0.16, units.gu(20))
128132 top: parent.top
133+ bottom: parent.bottom
129134 }
130− width: units.gu(40)
131− height: inputMethod && inputMethod.visible ? parent.height - inputMethod.keyboardRectangle.height
132− : parent.height
133− Behavior on height { UbuntuNumberAnimation {} }
134135
135− locked: root.locked
136− waiting: root.waiting
136+ boxVerticalOffset: (height - highlightedHeight -
137+ (inputMethod && inputMethod.visible ?
138+ inputMethod.keyboardRectangle.height : 0)) / 2
139+ Behavior on boxVerticalOffset { UbuntuNumberAnimation {} }
137140
138− onSelected: root.selected(index)
141+ model: root.userModel
142+ currentSession: LightDMService.greeter.defaultSession
139143 onResponded: root.responded(response)
144+ onSelected: root.selected(index)
145+ onSessionChooserButtonClicked: parent.state = "SessionsList"
146+
147+ Keys.forwardTo: [sessionChooserLoader.item]
140148 }
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+ ]
141212 }
142213}
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
2626import Unity.Launcher 0.1
2727import GlobalShortcut 1.0 // has to be before Utils, because of WindowInputFilter
2828import GSettings 1.0
29+import ImageCache 0.1
2930import Utils 0.1
3031import Powerd 0.1
3132import SessionBroadcast 0.1
@@ -145,12 +146,49 @@ StyledItem {
145146
146147 WallpaperResolver {
147148 id: wallpaperResolver
148− width: shell.width
149+ objectName: "wallpaperResolver"
150+
151+ readonly property url defaultBackground: "file:///usr/share/backgrounds/warty-final-ubuntu.png"
152+ readonly property bool hasCustomBackground: background != defaultBackground
153+
154+ // Use a cached version of the scaled-down wallpaper (as sometimes the
155+ // image can be quite big compared to the device size, including for
156+ // our default wallpaper). We use a name=wallpaper argument here to
157+ // make sure we don't litter our cache with lots of scaled images. We
158+ // only need to bother caching one at a time.
159+ readonly property url cachedBackground: background.toString().indexOf("file:///") === 0 ? "image://unity8imagecache/" + background + "?name=wallpaper" : background
160+
161+ GSettings {
162+ id: backgroundSettings
163+ schema.id: "org.gnome.desktop.background"
164+ }
165+
166+ candidates: [
167+ AccountsService.backgroundFile,
168+ backgroundSettings.pictureUri,
169+ defaultBackground
170+ ]
149171 }
150172
151173 readonly property alias greeter: greeterLoader.item
152174
153175 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) {
154192 if (ApplicationManager.findApplication(appId)) {
155193 ApplicationManager.requestFocusApplication(appId);
156194 } else {
@@ -162,7 +200,7 @@ StyledItem {
162200 if (greeter.locked) {
163201 greeter.lockedApp = app;
164202 }
165− shell.activateApplication(app);
203+ startApp(app); // locked apps are always in our same session
166204 }
167205
168206 Binding {
@@ -259,9 +297,7 @@ StyledItem {
259297 ? "phone"
260298 : shell.usageScenario
261299 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") {
265301 return "Stages/PhoneStage.qml";
266302 } else if (applicationsDisplayLoader.usageScenario === "tablet") {
267303 return "Stages/TabletStage.qml";
@@ -339,7 +375,7 @@ StyledItem {
339375 Binding {
340376 target: applicationsDisplayLoader.item
341377 property: "background"
342− value: wallpaperResolver.background
378+ value: wallpaperResolver.cachedBackground
343379 }
344380 Binding {
345381 target: applicationsDisplayLoader.item
@@ -420,11 +456,13 @@ StyledItem {
420456 id: integratedGreeter
421457 Greeter {
422458
459+ enabled: panel.indicators.fullyClosed // hides OSK when panel is open
423460 hides: [launcher, panel.indicators]
424461 tabletMode: shell.usageScenario != "phone"
425462 launcherOffset: launcher.progress
426463 forcedUnlock: wizard.active || shell.mode === "full-shell"
427− background: wallpaperResolver.background
464+ background: wallpaperResolver.cachedBackground
465+ hasCustomBackground: wallpaperResolver.hasCustomBackground
428466 allowFingerprint: !dialogs.hasActiveDialog &&
429467 !notifications.topmostIsFullscreen &&
430468 !panel.indicators.shown
@@ -499,11 +537,15 @@ StyledItem {
499537 }
500538
501539 function showHome() {
502− greeter.notifyUserRequestedApp("unity8-dash");
540+ greeter.notifyUserRequestedApp();
503541
504− var animate = !LightDMService.greeter.active && !stages.shown
505− dash.setCurrentScope(0, animate, false)
506− ApplicationManager.requestFocusApplication("unity8-dash")
542+ if (shell.mode === "greeter") {
543+ SessionBroadcast.requestHomeShown(AccountsService.user);
544+ } else {
545+ var animate = !LightDMService.greeter.active && !stages.shown;
546+ dash.setCurrentScope(0, animate, false);
547+ ApplicationManager.requestFocusApplication("unity8-dash");
548+ }
507549 }
508550
509551 function showDash() {
@@ -599,7 +641,7 @@ StyledItem {
599641 }
600642 }
601643 onLauncherApplicationSelected: {
602− greeter.notifyUserRequestedApp(appId);
644+ greeter.notifyUserRequestedApp();
603645 shell.activateApplication(appId);
604646 }
605647 onShownChanged: {
@@ -660,7 +702,7 @@ StyledItem {
660702 objectName: "tutorial"
661703 anchors.fill: parent
662704
663− paused: callManager.hasCalls || !greeter || greeter.shown ||
705+ paused: callManager.hasCalls || !greeter || greeter.active ||
664706 wizard.active
665707 delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
666708 inputMethod.visible ||
@@ -701,7 +743,7 @@ StyledItem {
701743 model: NotificationBackend.Model
702744 margin: units.gu(1)
703745 hasMouse: shell.hasMouse
704− background: wallpaperResolver.background
746+ background: wallpaperResolver.cachedBackground
705747
706748 y: topmostIsFullscreen ? 0 : panel.panelHeight
707749 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
@@ -745,7 +787,14 @@ StyledItem {
745787
746788 Connections {
747789 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)
749798 }
750799
751800 ItemGrabber {
@@ -766,6 +815,8 @@ StyledItem {
766815 z: itemGrabber.z + 1
767816 topBoundaryOffset: panel.panelHeight
768817
818+ confiningItem: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.itemConfiningMouseCursor : null
819+
769820 property bool mouseNeverMoved: true
770821 Binding {
771822 target: cursor; property: "x"; value: shell.width / 2
qml/Stages/AbstractStage.qml+3
@@ -55,6 +55,9 @@ FocusScope {
5555 | Qt.InvertedPortraitOrientation
5656 | Qt.InvertedLandscapeOrientation
5757
58+ property Item itemConfiningMouseCursor: null
59+
60+
5861 signal stageAboutToBeUnloaded
5962 signal itemSnapshotRequested(Item item)
6063
qml/Stages/DecoratedWindow.qml+2
@@ -56,6 +56,8 @@ FocusScope {
5656 property alias stageHeight: moveHandler.stageHeight
5757 readonly property alias dragging: moveHandler.dragging
5858
59+ readonly property Item clientAreaItem: applicationWindow
60+
5961 signal closeClicked()
6062 signal maximizeClicked()
6163 signal maximizeHorizontallyClicked()
qml/Stages/DesktopStage.qml+7−3
@@ -55,6 +55,11 @@ AbstractStage {
5555
5656 orientationChangesEnabled: true
5757
58+
59+ itemConfiningMouseCursor: !spreadShown && priv.focusedAppDelegate && priv.focusedAppDelegate.surface &&
60+ priv.focusedAppDelegate.surface.confinesMousePointer ?
61+ priv.focusedAppDelegate.clientAreaItem : null;
62+
5863 GlobalShortcut {
5964 id: showSpreadShortcut
6065 shortcut: Qt.MetaModifier|Qt.Key_W
@@ -237,12 +242,10 @@ AbstractStage {
237242 anchors.fill: parent
238243 focus: spread.state !== "altTab"
239244
240− CrossFadeImage {
245+ Wallpaper {
241246 id: wallpaper
242247 anchors.fill: parent
243248 source: root.background
244− sourceSize { height: root.height; width: root.width }
245− fillMode: Image.PreserveAspectCrop
246249 }
247250
248251 TopLevelSurfaceRepeater {
@@ -350,6 +353,7 @@ AbstractStage {
350353 readonly property bool dragging: touchControls.overlayShown ? touchControls.dragging : decoratedWindow.dragging
351354
352355 readonly property bool isDash: model.application.appId == "unity8-dash"
356+ readonly property alias clientAreaItem: decoratedWindow.clientAreaItem
353357
354358 function claimFocus() {
355359 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 "")
3737
3838macro(unity8_parse_arguments)
3939 cmake_parse_arguments(U8TEST
40− "${QMLTEST_OPTIONS};LIGHTDM"
40+ "${QMLTEST_OPTIONS}"
4141 "${QMLTEST_SINGLE}"
4242 "${QMLTEST_MULTI}"
4343 ${ARGN}
4444 )
4545
4646 set(ld_paths)
47− if(U8TEST_LIGHTDM)
48− list(APPEND ld_paths
49− ${UNITY_MOCKPATH}/libusermetrics
50− ${UNITY_MOCKPATH}/LightDM/IntegratedLightDM/liblightdm
51− )
52− endif()
53−
54− if(ld_paths)
55− string(REPLACE ";" ":" ld_library_path "${ld_paths}")
56− set(ld_library_path LD_LIBRARY_PATH=${ld_library_path})
57− endif()
47+ list(APPEND ld_paths
48+ ${UNITY_MOCKPATH}/libusermetrics
49+ ${UNITY_MOCKPATH}/LightDM/IntegratedLightDM/liblightdm
50+ )
51+
52+ string(REPLACE ";" ":" ld_library_path "${ld_paths}")
53+ set(ld_library_path LD_LIBRARY_PATH=${ld_library_path})
5854
5955 set(U8TEST_ARGN "${ARGN}")
60− list(REMOVE_ITEM U8TEST_ARGN LIGHTDM)
6156
6257 set(environment
6358 UNITY_TESTING=1
tests/mocks/AccountsService/AccountsService.cpp+19−3
@@ -15,7 +15,9 @@
1515 */
1616
1717#include "AccountsService.h"
18+#include "MockUsersModel.h"
1819
20+#include <QLightDM/UsersModel>
1921#include <paths.h>
2022
2123AccountsService::AccountsService(QObject* parent)
@@ -23,15 +25,17 @@ AccountsService::AccountsService(QObject* parent)
2325 m_enableFingerprintIdentification(true),
2426 m_enableLauncherWhileLocked(true),
2527 m_enableIndicatorsWhileLocked(true),
26− m_backgroundFile(qmlDirectory() + "/graphics/phone_background.jpg"),
28+ m_backgroundFile(),
2729 m_statsWelcomeScreen(true),
2830 m_failedLogins(0),
2931 m_failedFingerprintLogins(0),
3032 m_demoEdges(false),
3133 m_demoEdgesCompleted(),
3234 m_hereEnabled(false),
33− m_hereLicensePath("")
35+ m_hereLicensePath(""),
36+ m_usersModel(new MockUsersModel(this))
3437{
38+ m_usersModel->setMockMode("full");
3539}
3640
3741QString AccountsService::user() const
@@ -44,6 +48,7 @@ void AccountsService::setUser(const QString &user)
4448 m_user = user;
4549 Q_EMIT userChanged();
4650 Q_EMIT passwordDisplayHintChanged();
51+ Q_EMIT backgroundFileChanged();
4752}
4853
4954bool AccountsService::demoEdges() const
@@ -111,7 +116,18 @@ void AccountsService::setEnableIndicatorsWhileLocked(bool enableIndicatorsWhileL
111116
112117QString AccountsService::backgroundFile() const
113118{
114− return m_backgroundFile;
119+ if (!m_backgroundFile.isEmpty()) {
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();
115131}
116132
117133void AccountsService::setBackgroundFile(const QString &backgroundFile)
tests/mocks/AccountsService/AccountsService.h+3
@@ -22,6 +22,8 @@
2222#include <QStringList>
2323#include <QVariant>
2424
25+class MockUsersModel;
26+
2527class AccountsService: public QObject
2628{
2729 Q_OBJECT
@@ -163,6 +165,7 @@ private:
163165 QString m_realName;
164166 QStringList m_kbdMap;
165167 QString m_email;
168+ MockUsersModel *m_usersModel;
166169};
167170
168171#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+
18add_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
212 AccountsService.cpp
313 plugin.cpp
414 )
515
616qt5_use_modules(MockAccountsService-qml DBus Qml)
717
18+target_link_libraries(MockAccountsService-qml
19+ MockLightDM
20+ )
21+
822add_unity8_mock(AccountsService 0.1 AccountsService
923 PREFIX mocks
1024 TARGETS MockAccountsService-qml
tests/mocks/CMakeLists.txt+1
@@ -39,6 +39,7 @@ add_subdirectory(Lights)
3939add_subdirectory(MeeGo)
4040add_subdirectory(Powerd)
4141add_subdirectory(QMenuModel)
42+add_subdirectory(SessionBroadcast)
4243add_subdirectory(Ubuntu)
4344add_subdirectory(Unity)
4445add_subdirectory(QtMultimedia)
tests/mocks/LightDM/IntegratedLightDM/MockSessionsModel.cpp+29
@@ -18,6 +18,23 @@
1818#include "MockSessionsModel.h"
1919#include <QLightDM/SessionsModel>
2020
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+
2138QString MockSessionsModel::testScenario() const
2239{
2340 QLightDM::SessionsModel* qSessionsModel =
@@ -26,6 +43,17 @@ QString MockSessionsModel::testScenario() const
2643 return qSessionsModel->testScenario();
2744}
2845
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+
2957void MockSessionsModel::setTestScenario(const QString testScenario)
3058{
3159 QLightDM::SessionsModel* qSessionsModel =
@@ -33,5 +61,6 @@ void MockSessionsModel::setTestScenario(const QString testScenario)
3361
3462 if (qSessionsModel->testScenario() != testScenario) {
3563 qSessionsModel->setTestScenario(testScenario);
64+ Q_EMIT testScenarioChanged();
3665 }
3766}
tests/mocks/LightDM/IntegratedLightDM/MockSessionsModel.h+12−2
@@ -24,11 +24,21 @@ class MockSessionsModel : public SessionsModel
2424{
2525 Q_OBJECT
2626
27− Q_PROPERTY(QString testScenario READ testScenario WRITE setTestScenario)
27+ Q_PROPERTY(int numAvailableSessions READ numAvailableSessions CONSTANT)
28+ Q_PROPERTY(int numSessions READ numSessions WRITE setNumSessions NOTIFY numSessionsChanged)
29+ Q_PROPERTY(QString testScenario READ testScenario WRITE setTestScenario NOTIFY testScenarioChanged)
2830
2931public:
32+ int numAvailableSessions() const;
33+ int numSessions() const;
3034 QString testScenario() const;
31− void setTestScenario(QString testScenario);
35+ void setNumSessions(const int numSessions);
36+ void setTestScenario(const QString testScenario);
37+
38+Q_SIGNALS:
39+ void numSessionsChanged();
40+ void testScenarioChanged();
41+
3242};
3343
3444#endif // MOCK_UNITY_SESSIONSMODEL_H
tests/mocks/LightDM/IntegratedLightDM/MockUsersModel.cpp+5
@@ -19,6 +19,11 @@
1919#include <QLightDM/UsersModel>
2020#include <QSortFilterProxyModel>
2121
22+MockUsersModel::MockUsersModel(QObject* parent)
23+ : UsersModel(parent)
24+{
25+}
26+
2227QString MockUsersModel::mockMode() const
2328{
2429 QLightDM::UsersModel* qUsersModel =
tests/mocks/LightDM/IntegratedLightDM/MockUsersModel.h+2
@@ -27,6 +27,8 @@ class MockUsersModel : public UsersModel
2727 Q_PROPERTY(QString mockMode READ mockMode WRITE setMockMode NOTIFY mockModeChanged)
2828
2929public:
30+ explicit MockUsersModel(QObject* parent=0);
31+
3032 QString mockMode() const;
3133 void setMockMode(QString mockMode);
3234
tests/mocks/LightDM/IntegratedLightDM/liblightdm/Greeter.cpp−5
@@ -69,11 +69,6 @@ bool Greeter::showRemoteLoginHint() const
6969 return true;
7070}
7171
72−bool Greeter::lockHint () const
73−{
74− return false;
75−}
76−
7772QString Greeter::selectUserHint() const
7873{
7974 Q_D(const Greeter);
tests/mocks/LightDM/IntegratedLightDM/liblightdm/Greeter.h−2
@@ -41,7 +41,6 @@ class Q_DECL_EXPORT Greeter : public QObject
4141
4242 Q_PROPERTY(QString hostname READ hostname CONSTANT)
4343 Q_PROPERTY(bool hasGuestAccount READ hasGuestAccountHint CONSTANT)
44− Q_PROPERTY(bool locked READ lockHint CONSTANT)
4544
4645 Q_ENUMS(PromptType MessageType)
4746
@@ -67,7 +66,6 @@ public:
6766 bool hideUsersHint() const;
6867 bool showManualLoginHint() const;
6968 bool showRemoteLoginHint() const;
70− bool lockHint () const;
7169 bool hasGuestAccountHint() const;
7270 QString selectUserHint() const;
7371 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
8080 }
8181}
8282
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+
8395QString SessionsModel::testScenario() const
8496{
8597 Q_D(const SessionsModel);
8698 return d->testScenario;
8799}
88100
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+
89111void SessionsModel::setTestScenario(QString testScenario)
90112{
91113 Q_D(SessionsModel);
tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModel.h+3−3
@@ -31,9 +31,6 @@ class Q_DECL_EXPORT SessionsModel : public QAbstractListModel
3131
3232 Q_ENUMS(SessionModelRoles SessionType)
3333
34− // Mock-only API for testing purposes
35− Q_PROPERTY(QString testScenario READ testScenario WRITE setTestScenario)
36−
3734 public:
3835
3936 enum SessionModelRoles {
@@ -57,7 +54,10 @@ class Q_DECL_EXPORT SessionsModel : public QAbstractListModel
5754 int rowCount(const QModelIndex& parent) const override;
5855 QVariant data(const QModelIndex& index, int role) const override;
5956
57+ int numSessions() const;
58+ int numAvailableSessions() const;
6059 QString testScenario() const;
60+ void setNumSessions(int numSessions);
6161 void setTestScenario(QString testScenario);
6262
6363 protected:
tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModelPrivate.cpp+27−17
@@ -16,24 +16,44 @@
1616
1717#include "SessionsModel.h"
1818#include "SessionsModelPrivate.h"
19−
19+#include <QDebug>
2020namespace QLightDM
2121{
2222
2323SessionsModelPrivate::SessionsModelPrivate(SessionsModel* parent)
2424 : 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+ })
2539 , q_ptr(parent)
2640{
41+ numSessions = numAvailableSessions();
2742 resetEntries();
2843}
2944
45+int SessionsModelPrivate::numAvailableSessions() const
46+{
47+ return m_availableSessions.length();
48+}
49+
3050void SessionsModelPrivate::resetEntries()
3151{
3252 Q_Q(SessionsModel);
3353
3454 q->beginResetModel();
3555 if (testScenario == "multipleSessions") {
36− resetEntries_multipleSessions();
56+ resetEntries_multipleSessions(numSessions);
3757 } else if (testScenario == "noSessions") {
3858 resetEntries_noSessions();
3959 } else {
@@ -42,22 +62,12 @@ void SessionsModelPrivate::resetEntries()
4262 q->endResetModel();
4363}
4464
45−void SessionsModelPrivate::resetEntries_multipleSessions()
65+void SessionsModelPrivate::resetEntries_multipleSessions(int numSessions)
4666{
47− sessionItems =
48− {
49− {"ubuntu", "", "Ubuntu", ""},
50− {"ubuntu-2d", "", "Ubuntu 2D", ""},
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− };
67+ sessionItems.clear();
68+ for (int i = 0; i < numSessions; i++) {
69+ sessionItems.append(m_availableSessions.value(i));
70+ }
6171}
6272
6373void SessionsModelPrivate::resetEntries_noSessions()
tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModelPrivate.h+5−1
@@ -40,7 +40,10 @@ public:
4040 explicit SessionsModelPrivate(SessionsModel* parent=0);
4141 virtual ~SessionsModelPrivate() = default;
4242
43+ int numAvailableSessions() const;
44+
4345 QList<SessionItem> sessionItems;
46+ int numSessions;
4447 QString testScenario;
4548
4649 void resetEntries();
@@ -48,7 +51,8 @@ protected:
4851 SessionsModel* const q_ptr;
4952
5053private:
51− void resetEntries_multipleSessions();
54+ const QList<SessionItem> m_availableSessions;
55+ void resetEntries_multipleSessions(int numSessions);
5256 void resetEntries_noSessions();
5357 void resetEntries_singleSession();
5458 Q_DECLARE_PUBLIC(SessionsModel)
tests/mocks/LightDM/IntegratedLightDM/liblightdm/UsersModelPrivate.cpp+15−1
@@ -17,6 +17,8 @@
1717#include "UsersModelPrivate.h"
1818#include "UsersModel.h"
1919
20+#include <QDir>
21+
2022namespace QLightDM
2123{
2224
@@ -53,6 +55,17 @@ void UsersModelPrivate::resetEntries()
5355 entries[i].uid = i + 1;
5456 }
5557
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+
5669 q->endResetModel();
5770}
5871
@@ -96,7 +109,7 @@ void UsersModelPrivate::resetEntries_full()
96109 { "wide-info-prompt", "Wide Info Prompt", 0, 0, false, false, "ubuntu", 0 },
97110 { "multi-info-prompt", "Multi Info Prompt", 0, 0, false, false, "ubuntu", 0 },
98111 { "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 },
100113 // white and black are a bit redundant, but useful for manually testing if UI is still readable
101114 { "white-background", "White Background", "#ffffff", 0, false, false, "ubuntu", 0 },
102115 { "black-background", "Black Background", "#000000", 0, false, false, "ubuntu", 0 },
@@ -104,6 +117,7 @@ void UsersModelPrivate::resetEntries_full()
104117 { "unicode", "가나다라마", 0, 0, false, false, "ubuntu", 0 },
105118 { "no-response", "No Response", 0, 0, false, false, "ubuntu", 0 },
106119 { "empty-name", "", 0, 0, false, false, "ubuntu", 0 },
120+ { "active", "Active Account", 0, 0, true, false, "ubuntu", 0 },
107121 };
108122}
109123
tests/mocks/LightDM/IntegratedLightDM/plugin.cpp+1−1
@@ -72,7 +72,7 @@ void LightDMPlugin::registerTypes(const char *uri)
7272 qRegisterMetaType<QLightDM::Greeter::PromptType>("QLightDM::Greeter::PromptType");
7373 qmlRegisterSingletonType<MockGreeter>(uri, 0, 1, "Greeter", greeter_provider);
7474 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");
7676 qmlRegisterSingletonType<MockUsersModel>(uri, 0, 1, "Users", users_provider);
7777 qmlRegisterUncreatableType<QLightDM::UsersModel>(uri, 0, 1, "UserRoles", "Type is not instantiable");
7878 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 @@
11/*
2− * Copyright (C) 2013 - Canonical Ltd.
2+ * Copyright (C) 2013-2016 Canonical Ltd.
33 *
44 * This program is free software: you can redistribute it and/or modify it
55 * under the terms of the GNU Lesser General Public License, as
@@ -67,6 +67,8 @@ void MockDownloadTracker::startService()
6767 Q_EMIT finished("downloadComplete");
6868 }else if(m_dbusPath == "error") {
6969 Q_EMIT error("DOWNLOAD ERROR");
70+ }else if(m_dbusPath == "processing") {
71+ Q_EMIT processing(m_dbusPath);
7072 }
7173 }
7274}
tests/mocks/Ubuntu/DownloadDaemonListener/MockDownloadTracker.h+2−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 - Canonical Ltd.
2+ * Copyright (C) 2013-2016 Canonical Ltd.
33 *
44 * This program is free software: you can redistribute it and/or modify it
55 * under the terms of the GNU Lesser General Public License, as
@@ -51,6 +51,7 @@ Q_SIGNALS:
5151 void error(const QString &error);
5252 void finished(const QString &path);
5353 void paused(bool success);
54+ void processing(const QString &path);
5455 void progress(qulonglong received, qulonglong total);
5556 void resumed(bool success);
5657 void started(bool success);
tests/mocks/Ubuntu/Telephony/MockTelepathyHelper.cpp+27
@@ -20,6 +20,8 @@
2020
2121MockTelepathyHelper::MockTelepathyHelper(QObject *parent)
2222 : QObject(parent)
23+ , m_ready(true)
24+ , m_emergencyCallsAvailable(true)
2325{
2426}
2527
@@ -33,3 +35,28 @@ void MockTelepathyHelper::registerChannelObserver(const QString& name)
3335{
3436 Q_UNUSED(name);
3537}
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 @@
2424class MockTelepathyHelper : public QObject
2525{
2626 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)
2735 Q_DISABLE_COPY(MockTelepathyHelper)
2836public:
2937 MockTelepathyHelper(QObject *parent = 0);
@@ -31,6 +39,20 @@ public:
3139 static MockTelepathyHelper *instance();
3240
3341 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;
3456};
3557
3658#endif // MOCKCONTACTWATCHER_H
tests/mocks/Unity/Application/MirSurface.h+2
@@ -104,6 +104,8 @@ public:
104104 bool focused() const override;
105105 QRect inputBounds() const override;
106106
107+ bool confinesMousePointer() const override { return false; }
108+
107109 Q_INVOKABLE void requestFocus() override;
108110
109111 Q_INVOKABLE void close() override;
tests/mocks/Utils/CMakeLists.txt+1
@@ -32,6 +32,7 @@ set(QMLPLUGIN_SRC
3232 constants.cpp
3333 plugin.cpp
3434 windowstatestorage.cpp
35+ URLDispatcher.cpp
3536 )
3637
3738add_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 @@
2525#include "plugin.h"
2626#include "windowstatestorage.h"
2727#include "constants.h"
28+#include "URLDispatcher.h"
2829
2930// plugin
3031#include <activefocuslogger.h>
@@ -80,4 +81,5 @@ void FakeUtilsPlugin::registerTypes(const char *uri)
8081 qmlRegisterType<InputEventGenerator>(uri, 0, 1, "InputEventGenerator");
8182 qmlRegisterType<DeviceConfigParser>(uri, 0, 1, "DeviceConfigParser");
8283 qmlRegisterSingletonType<GlobalFunctions>(uri, 0, 1, "Functions", createGlobalFunctions);
84+ qmlRegisterType<URLDispatcher>(uri, 0, 1, "URLDispatcher");
8385}
tests/plugins/CMakeLists.txt+2
@@ -3,7 +3,9 @@ add_subdirectory(Cursor)
33add_subdirectory(Dash)
44add_subdirectory(GlobalShortcut)
55add_subdirectory(Greeter)
6+add_subdirectory(ImageCache)
67add_subdirectory(LightDM)
8+add_subdirectory(SessionBroadcast)
79add_subdirectory(Ubuntu)
810add_subdirectory(Unity)
911add_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
2626 ARGS --task $<TARGET_FILE:GreeterDBusTestExec>
2727 DEPENDS MockLightDM
2828 IMPORT_PATHS ${UNITY_MOCKPATH}
29− LIGHTDM
3029 )
3130
3231add_executable(GreeterPamTestExec
@@ -63,7 +62,6 @@ target_include_directories(GreeterSessionsModelTestExec PUBLIC
6362add_unity8_uitest(GreeterSessions GreeterSessionsModelTestExec
6463 ENVIRONMENT LIBLIGHTDM_MOCK_MODE=full
6564 DEPENDS MockLightDM
66− LIGHTDM
6765 )
6866
6967# UsersModelTest
@@ -87,7 +85,6 @@ target_include_directories(GreeterUsersModelTestExec PUBLIC
8785add_unity8_uitest(GreeterUsers GreeterUsersModelTestExec
8886 ENVIRONMENT LIBLIGHTDM_MOCK_MODE=full
8987 DEPENDS MockLightDM
90− LIGHTDM
9188 )
9289
9390add_executable(GreeterIntegratedTestExec
tests/plugins/LightDM/IntegratedLightDM/sessionsmodel.cpp+32
@@ -59,6 +59,38 @@ private Q_SLOTS:
5959 }
6060 }
6161
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+
6294 void testMultipleSessionsCountIsCorrect()
6395 {
6496 sourceModel->setTestScenario("multipleSessions");
tests/plugins/LightDM/IntegratedLightDM/usersmodel.cpp+2−2
@@ -59,10 +59,10 @@ private Q_SLOTS:
5959 void testMangleColor()
6060 {
6161 QString background = getStringValue(sourceModel, "color-background", QLightDM::UsersModel::BackgroundPathRole);
62− QVERIFY(background == "#dd4814");
62+ QVERIFY(background == "#E95420");
6363
6464 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>");
6666 }
6767
6868 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()
2323
2424# plain qml test
2525add_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 @@
11
22add_subdirectory(Components)
33
4−add_unity8_qmltest(. OrientedShell LIGHTDM)
4+add_unity8_qmltest(. OrientedShell)
55add_unity8_qmltest(. DisabledScreenNotice)
6−add_unity8_qmltest(. Shell LIGHTDM)
7−add_unity8_qmltest(. ShellWithPin LIGHTDM)
6+add_unity8_qmltest(. Shell)
7+add_unity8_qmltest(. ShellWithPin)
88add_unity8_qmltest(. DeviceConfiguration)
99add_unity8_qmltest(Components Background)
1010add_unity8_qmltest(Components Carousel)
11−add_unity8_qmltest(Components Dialogs LIGHTDM)
11+add_unity8_qmltest(Components Dialogs)
1212add_unity8_qmltest(Components DraggingArea)
1313add_unity8_qmltest(Components LazyImage)
1414add_unity8_qmltest(Components Lockscreen)
@@ -25,7 +25,7 @@ add_unity8_qmltest(Components WallpaperResolver)
2525add_unity8_qmltest(Components ZoomableImage)
2626add_unity8_qmltest(Dash Dash)
2727add_unity8_qmltest(Dash DashContent)
28−add_unity8_qmltest(Dash DashShell LIGHTDM)
28+add_unity8_qmltest(Dash DashShell)
2929add_unity8_qmltest(Dash Card)
3030add_unity8_qmltest(Dash CardCarousel)
3131add_unity8_qmlbenchmark(Dash CardBenchmark 1000)
@@ -66,10 +66,10 @@ add_unity8_qmltest(Dash/ScopeSettings ScopeSettingNumber)
6666add_unity8_qmltest(Dash/ScopeSettings ScopeSettingString)
6767add_unity8_qmltest(Dash/ScopeSettings ScopeSettingsWidgetFactory)
6868add_unity8_qmltest(Greeter Clock)
69−add_unity8_qmltest(Greeter Greeter LIGHTDM)
70−add_unity8_qmltest(Greeter Infographics LIGHTDM)
71−add_unity8_qmltest(Greeter NarrowView LIGHTDM)
72−add_unity8_qmltest(Greeter WideView LIGHTDM)
69+add_unity8_qmltest(Greeter Greeter)
70+add_unity8_qmltest(Greeter Infographics)
71+add_unity8_qmltest(Greeter NarrowView)
72+add_unity8_qmltest(Greeter WideView)
7373add_unity8_qmltest(Launcher Launcher)
7474add_unity8_qmltest(Notifications Notifications)
7575add_unity8_qmltest(Notifications VisualSnapDecisionsQueue)
@@ -94,6 +94,6 @@ add_unity8_qmltest(Stages SurfaceContainer)
9494add_unity8_qmltest(Stages TabletStage)
9595add_unity8_qmltest(Stages WindowResizeArea)
9696add_unity8_qmltest(Stages Splash)
97−add_unity8_qmltest(Tutorial Tutorial LIGHTDM)
97+add_unity8_qmltest(Tutorial Tutorial)
9898add_unity8_qmltest(Wizard Wizard ENVIRONMENT "OXIDE_NO_SANDBOX=1")
9999add_unity8_qmltest(utils/Unity/Test UnityTest)
tests/qmltests/Components/tst_Lockscreen.qml+1−39
@@ -37,8 +37,7 @@ Rectangle {
3737 alphaNumeric: pinPadCheckBox.checked
3838 minPinLength: minPinLengthTextField.text
3939 maxPinLength: maxPinLengthTextField.text
40− delayMinutes: delayMinutesTextField.text
41− background: "../../../qml/graphics/phone_background.jpg"
40+ background: "/usr/share/backgrounds/warty-final-ubuntu.png"
4241 }
4342
4443 Connections {
@@ -109,16 +108,6 @@ Rectangle {
109108 text: "Retries left"
110109 }
111110 }
112− Row {
113− TextField {
114− id: delayMinutesTextField
115− width: units.gu(7)
116− text: "0"
117− }
118− Label {
119− text: "Delay Minutes"
120− }
121− }
122111 Label {
123112 id: pinLabel
124113 anchors.verticalCenter: parent.verticalCenter
@@ -172,7 +161,6 @@ Rectangle {
172161
173162 function cleanup() {
174163 lockscreen.clear(false);
175− delayMinutesTextField.text = "0"
176164 enteredLabel.text = "";
177165
178166 // Reset sizing
@@ -455,32 +443,6 @@ Rectangle {
455443 compare(label.text, data.text);
456444 }
457445
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−
484446 function test_resize_data() {
485447 return [
486448 { 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
1818import QtTest 1.0
1919import Ubuntu.Components 1.3
2020import Unity.Test 0.1
21−import AccountsService 0.1
22−import GSettings 1.0
2321
2422import "../../../qml/Components"
2523
@@ -32,7 +30,6 @@ Image {
3230
3331 WallpaperResolver {
3432 id: wallpaperResolver
35− width: units.gu(70)
3633 }
3734
3835 UnityTestCase {
@@ -42,48 +39,37 @@ Image {
4239
4340 function test_background_data() {
4441 return [
45− {tag: "color",
46− accounts: Qt.resolvedUrl("data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>"),
47− gsettings: "",
48− output: Qt.resolvedUrl("data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>")},
42+ {tag: "none-valid",
43+ list: ["/first", "/middle", "/last"],
44+ output: "file:///last"},
4945
50− {tag: "empty", accounts: "", gsettings: "", output: "defaultBackground"},
51−
52− {tag: "as-specified",
53− accounts: Qt.resolvedUrl("../../data/unity/backgrounds/blue.png"),
54− gsettings: "",
46+ {tag: "first-valid",
47+ list: [Qt.resolvedUrl("../../data/unity/backgrounds/blue.png"),
48+ "/middle", "/last"],
5549 output: Qt.resolvedUrl("../../data/unity/backgrounds/blue.png")},
5650
57− {tag: "gs-specified",
58− accounts: "",
59− gsettings: Qt.resolvedUrl("../../data/unity/backgrounds/red.png"),
51+ {tag: "middle-valid",
52+ list: ["/first",
53+ Qt.resolvedUrl("../../data/unity/backgrounds/red.png"),
54+ "/last"],
6055 output: Qt.resolvedUrl("../../data/unity/backgrounds/red.png")},
6156
62− {tag: "both-specified",
63− accounts: Qt.resolvedUrl("../../data/unity/backgrounds/blue.png"),
64− gsettings: Qt.resolvedUrl("../../data/unity/backgrounds/red.png"),
65− output: Qt.resolvedUrl("../../data/unity/backgrounds/blue.png")},
66−
67− {tag: "invalid-as",
68− accounts: Qt.resolvedUrl("../../data/unity/backgrounds/nope.png"),
69− gsettings: Qt.resolvedUrl("../../data/unity/backgrounds/red.png"),
57+ {tag: "last-valid",
58+ list: ["/first",
59+ "/middle",
60+ Qt.resolvedUrl("../../data/unity/backgrounds/red.png")],
7061 output: Qt.resolvedUrl("../../data/unity/backgrounds/red.png")},
7162
72− {tag: "invalid-both",
73− accounts: Qt.resolvedUrl("../../data/unity/backgrounds/nope.png"),
74− gsettings: Qt.resolvedUrl("../../data/unity/backgrounds/stillnope.png"),
75− output: "defaultBackground"},
63+ {tag: "multiple-valid",
64+ list: [Qt.resolvedUrl("../../data/unity/backgrounds/blue.png"),
65+ Qt.resolvedUrl("../../data/unity/backgrounds/red.png")],
66+ output: Qt.resolvedUrl("../../data/unity/backgrounds/blue.png")},
7667 ]
7768 }
78− function test_background(data) {
79− AccountsService.backgroundFile = data.accounts;
80− GSettingsController.setPictureUri(data.gsettings);
8169
82− if (data.output === "defaultBackground") {
83− tryCompare(wallpaperResolver, "background", wallpaperResolver.defaultBackground);
84− } else {
85− tryCompare(wallpaperResolver, "background", data.output);
86− }
70+ function test_background(data) {
71+ wallpaperResolver.candidates = data.list;
72+ tryCompare(wallpaperResolver, "background", data.output);
8773 }
8874 }
8975}
tests/qmltests/Components/tst_ZoomableImage.qml+1−1
@@ -31,7 +31,7 @@ Rectangle {
3131 }
3232
3333 property var widgetData1: {
34− "source": "../../../qml/graphics/phone_background.jpg",
34+ "source": "../../../tests/graphics/avatars/amanda@12.png",
3535 "zoomable": false
3636 }
3737
tests/qmltests/Dash/Previews/tst_PreviewExpandable.qml+3−3
@@ -54,8 +54,8 @@ Rectangle {
5454
5555 property var galleryData: {
5656 "sources": [
57− "../../graphics/phone_background.jpg",
58− "../../graphics/tablet_background.jpg",
57+ "../../../tests/graphics/avatars/amanda@12.png",
58+ "../../../tests/graphics/avatars/funky@12.png",
5959 "../../../tests/graphics/clock@18.png",
6060 "../../graphics/borked"
6161 ]
@@ -71,7 +71,7 @@ Rectangle {
7171 }
7272
7373 property var imageData: {
74− "source": "../../graphics/phone_background.jpg",
74+ "source": "../../../tests/graphics/avatars/amanda@12.png",
7575 "zoomable": false
7676 }
7777
tests/qmltests/Dash/Previews/tst_PreviewImageGallery.qml+6−6
@@ -31,8 +31,8 @@ Rectangle {
3131
3232 property var sourcesModel1: {
3333 "sources": [
34− "../../graphics/phone_background.jpg",
35− "../../graphics/tablet_background.jpg",
34+ "../../../tests/graphics/avatars/amanda@12.png",
35+ "../../../tests/graphics/avatars/funky@12.png",
3636 "../../../tests/graphics/clock@18.png",
3737 "../../graphics/borked"
3838 ]
@@ -40,8 +40,8 @@ Rectangle {
4040
4141 property var sourcesModel1WithFallback: {
4242 "sources": [
43− "../../graphics/phone_background.jpg",
44− "../../graphics/tablet_background.jpg",
43+ "../../../tests/graphics/avatars/amanda@12.png",
44+ "../../../tests/graphics/avatars/funky@12.png",
4545 "../../../tests/graphics/clock@18.png",
4646 "../../graphics/borked"
4747 ]
@@ -50,8 +50,8 @@ Rectangle {
5050
5151 property var sourcesModelEmptyWithFallback: {
5252 "sources": [
53− "../../graphics/phone_background.jpg",
54− "../../graphics/tablet_background.jpg",
53+ "../../../tests/graphics/avatars/amanda@12.png",
54+ "../../../tests/graphics/avatars/funky@12.png",
5555 "../../../tests/graphics/clock@18.png",
5656 ""
5757 ]
tests/qmltests/Dash/Previews/tst_PreviewProgress.qml+14−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2014 Canonical Ltd.
2+ * Copyright 2014-2016 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -40,6 +40,11 @@ Rectangle {
4040 "source": { "dbus-name" : "somename", "dbus-object": "error" }
4141 }
4242
43+ property var progressjsonProcessing: {
44+ "type": "progress",
45+ "source": { "dbus-name" : "somename", "dbus-object": "processing" }
46+ }
47+
4348 SignalSpy {
4449 id: spy
4550 signalName: "triggered"
@@ -82,6 +87,14 @@ Rectangle {
8287 compare(args[0], "previewProgress");
8388 compare(args[1], "failed");
8489 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);
8598 }
8699 }
87100}
tests/qmltests/Dash/Previews/tst_PreviewZoomableImage.qml+7−7
@@ -26,7 +26,7 @@ Rectangle {
2626 color: "lightgrey"
2727
2828 property var widgetData0: {
29− "source": "../../graphics/phone_background.jpg",
29+ "source": "../../../tests/graphics/avatars/amanda@12.png",
3030 "zoomable": false
3131 }
3232
@@ -36,17 +36,17 @@ Rectangle {
3636
3737 property var widgetData2: {
3838 "source": "fadsasf",
39− "fallback": "../../graphics/phone_background.jpg"
39+ "fallback": "../../../tests/graphics/avatars/amanda@12.png"
4040 }
4141
4242 property var widgetData3: {
4343 "source": "",
44− "fallback": "../../graphics/phone_background.jpg"
44+ "fallback": "../../../tests/graphics/avatars/amanda@12.png"
4545 }
4646
4747 property var widgetData4: {
4848 "source": "fadsasf",
49− "fallback": "../../graphics/phone_background.jpg"
49+ "fallback": "../../../tests/graphics/avatars/amanda@12.png"
5050 }
5151
5252 Loader {
@@ -123,11 +123,11 @@ Rectangle {
123123 waitForRendering(zoomableImage);
124124 waitForRendering(lazyImage);
125125 tryCompare(lazyImage, "state", "ready");
126− verify(lazyImage.sourceImage.source.toString().indexOf("phone_background") > -1);
127− zoomableImage.widgetData["source"] = "../../graphics/tablet_background.jpg"
126+ verify(lazyImage.sourceImage.source.toString().indexOf("amanda") > -1);
127+ zoomableImage.widgetData["source"] = "../../../tests/graphics/avatars/funky@12.png"
128128 zoomableImage.widgetDataChanged();
129129 tryCompare(lazyImage, "state", "ready");
130− verify(lazyImage.sourceImage.source.toString().indexOf("tablet_background") > -1);
130+ verify(lazyImage.sourceImage.source.toString().indexOf("funky") > -1);
131131 }
132132 }
133133}
tests/qmltests/Greeter/TestView.qml+4−3
@@ -27,6 +27,7 @@ Item {
2727 property int delayMinutes
2828 property real backgroundTopMargin
2929 property url background
30+ property bool hasCustomBackground
3031 property bool locked
3132 property bool alphanumeric
3233 property var userModel
@@ -51,7 +52,7 @@ Item {
5152 signal _notifyAuthenticationSucceededCalled(bool showFakePassword)
5253 signal _notifyAuthenticationFailedCalled()
5354 signal _showErrorMessageCalled(string msg)
54− signal _resetCalled()
55+ signal _resetCalled(bool forceShow)
5556 signal _tryToUnlockCalled(bool toTheRight)
5657
5758 function showMessage(html) {
@@ -84,8 +85,8 @@ Item {
8485 _showErrorMessageCalled(msg);
8586 }
8687
87− function reset() {
88− _resetCalled();
88+ function reset(forceShow) {
89+ _resetCalled(forceShow);
8990 }
9091
9192 function tryToUnlock(toTheRight) {
tests/qmltests/Greeter/tst_Greeter.qml+8−1
@@ -29,7 +29,7 @@ Item {
2929 width: units.gu(120)
3030 height: units.gu(80)
3131
32− property url defaultBackground: Qt.resolvedUrl("../../../qml/graphics/tablet_background.jpg")
32+ property url defaultBackground: "/usr/share/backgrounds/warty-final-ubuntu.png"
3333
3434 Component.onCompleted: {
3535 // set the mock mode before loading
@@ -368,6 +368,12 @@ Item {
368368 compare(view.background, Qt.resolvedUrl("testing"));
369369 }
370370
371+ function test_hasCustomBackground() {
372+ verify(!view.hasCustomBackground);
373+ greeter.hasCustomBackground = true;
374+ verify(view.hasCustomBackground);
375+ }
376+
371377 function test_notifyAboutToFocusApp() {
372378 greeter.notifyUserRequestedApp("fake-app");
373379 compare(viewTryToUnlockSpy.count, 1);
@@ -559,6 +565,7 @@ Item {
559565
560566 LightDM.Greeter.showGreeter();
561567 compare(viewResetSpy.count, 1);
568+ compare(viewResetSpy.signalArguments[0][0], true);
562569 tryCompare(viewShowPromptSpy, "count", 1);
563570 }
564571
tests/qmltests/Greeter/tst_NarrowView.qml+47−12
@@ -20,6 +20,7 @@ import ".."
2020import "../../../qml/Greeter"
2121import LightDM.IntegratedLightDM 0.1 as LightDM
2222import Ubuntu.Components 1.3
23+import Ubuntu.Telephony 0.1 as Telephony
2324import Unity.Test 0.1 as UT
2425
2526Item {
@@ -39,7 +40,7 @@ Item {
3940 property bool itemDestroyed: false
4041 sourceComponent: Component {
4142 NarrowView {
42− background: Qt.resolvedUrl("../../../qml/graphics/phone_background.jpg")
43+ background: "/usr/share/backgrounds/warty-final-ubuntu.png"
4344 userModel: LightDM.Users
4445 infographicModel: LightDM.Infographic
4546
@@ -293,6 +294,8 @@ Item {
293294 function init() {
294295 view.currentIndex = 0; // break binding with text field
295296
297+ telepathyHelper.ready = true;
298+ telepathyHelper.emergencyCallsAvailable = true;
296299 selectedSpy.clear();
297300 respondedSpy.clear();
298301 teaseSpy.clear();
@@ -328,14 +331,6 @@ Item {
328331 waitForRendering(view);
329332 }
330333
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−
339334 function test_tease_data() {
340335 return [
341336 {tag: "left", x: 0, offset: 0, count: 1},
@@ -350,10 +345,23 @@ Item {
350345 compare(teaseSpy.count, data.count);
351346 }
352347
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+
353360 function test_respondedWithPin() {
354361 view.locked = true;
362+ view.showPrompt("", true, true);
355363 swipeAwayCover();
356− enterPin("1234");
364+ typeString("1234");
357365 compare(respondedSpy.count, 1);
358366 compare(respondedSpy.signalArguments[0][0], "1234");
359367 }
@@ -361,6 +369,7 @@ Item {
361369 function test_respondedWithPassphrase() {
362370 view.locked = true;
363371 view.alphanumeric = true;
372+ view.showPrompt("", true, true);
364373 swipeAwayCover();
365374 typeString("test");
366375 keyClick(Qt.Key_Enter);
@@ -382,12 +391,38 @@ Item {
382391 compare(respondedSpy.signalArguments[0][0], "");
383392 }
384393
385− function test_emergencyCall() {
394+ function test_emergencyCall_data() {
395+ return [
396+ {tag: "phone", available: true},
397+ {tag: "desktop", available: false},
398+ ];
399+ }
400+ function test_emergencyCall(data) {
401+ telepathyHelper.emergencyCallsAvailable = data.available;
386402 view.locked = true;
387403 swipeAwayCover();
388404 var emergencyCallLabel = findChild(view, "emergencyCallLabel");
389405 tap(emergencyCallLabel);
390− compare(emergencySpy.count, 1);
406+ if (!data.available) {
407+ expectFail("", "Bug 1616538 prevents us supporting conditional emergency button support");
408+ }
409+ compare(emergencyCallLabel.visible, data.available ? true : false);
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);
391426 }
392427
393428 function test_fullyShown() {
tests/qmltests/Greeter/tst_WideView.qml+154−7
@@ -24,6 +24,7 @@ import Unity.Test 0.1 as UT
2424
2525StyledItem {
2626 id: root
27+
2728 width: units.gu(120)
2829 height: units.gu(80)
2930 focus: true
@@ -49,7 +50,7 @@ StyledItem {
4950 WideView {
5051 id: view
5152
52− background: Qt.resolvedUrl("../../../qml/graphics/tablet_background.jpg")
53+ background: "/usr/share/backgrounds/warty-final-ubuntu.png"
5354 userModel: LightDM.Users
5455 infographicModel: LightDM.Infographic
5556
@@ -111,11 +112,9 @@ StyledItem {
111112 color: theme.palette.normal.background
112113 width: units.gu(40)
113114 height: parent.height
114−
115115 Column {
116116 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
117117 spacing: units.gu(1)
118−
119118 Row {
120119 Button {
121120 text: "Show Last Chance"
@@ -266,6 +265,51 @@ StyledItem {
266265 text: "emergency: " + emergencySpy.count
267266 }
268267 }
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+ }
269313 Row {
270314 Button {
271315 text: "Reload View"
@@ -316,6 +360,7 @@ StyledItem {
316360 when: windowShown
317361
318362 property Item view: loader.status === Loader.Ready ? loader.item : null
363+ property url testIconDirectory: Qt.resolvedUrl("../../../qml/Greeter/graphics/session_icons")
319364
320365 function init() {
321366 selectIndex(0); // break binding with text field
@@ -323,6 +368,7 @@ StyledItem {
323368 respondedSpy.clear();
324369 teaseSpy.clear();
325370 emergencySpy.clear();
371+ LightDM.Sessions.testScenario = "multipleSessions"
326372 }
327373
328374 function cleanup() {
@@ -378,6 +424,92 @@ StyledItem {
378424 {tag: "right", x: view.width, offset: 0, count: 1, locked: false},
379425 ]
380426 }
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+
381513 function test_tease(data) {
382514 view.locked = data.locked;
383515 view.dragHandleLeftMargin = data.offset;
@@ -393,6 +525,17 @@ StyledItem {
393525 compare(view.currentIndex, 0); // confirm we didn't change
394526 }
395527
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+
396539 function test_respondedWithPassword() {
397540 view.locked = true;
398541 view.showPrompt("Prompt", true, false);
@@ -509,17 +652,21 @@ StyledItem {
509652
510653 keyboardVisibleCheckBox.checked = true;
511654
512− tryCompare(loginList, "height", view.height - view.inputMethod.keyboardRectangle.height);
655+ var halfway = (view.height - loginList.highlightedHeight) / 2;
656+ var halfwayWithOsk = halfway - view.inputMethod.keyboardRectangle.height / 2;
657+ tryCompare(loginList, "boxVerticalOffset", halfwayWithOsk);
658+
659+ var highlightItem = findChild(loginList, "highlightItem");
513660 tryCompareFunction( function() {
514− var loginListRect = loginList.mapToItem(view, 0, 0, loginList.width, loginList.height);
515− return loginListRect.y + loginListRect.height <= view.inputMethod.keyboardRectangle.y;
661+ var highlightRect = highlightItem.mapToItem(view, 0, 0, highlightItem.width, highlightItem.height);
662+ return highlightRect.y + highlightRect.height <= view.inputMethod.keyboardRectangle.y;
516663 }, true);
517664
518665 // once the vkb goes away, loginList goes back to its full height
519666
520667 keyboardVisibleCheckBox.checked = false;
521668
522− tryCompare(loginList, "height", view.height);
669+ tryCompare(loginList, "boxVerticalOffset", halfway);
523670 }
524671
525672 function test_passphrase() {
tests/qmltests/Stages/tst_DesktopStage.qml+1−1
@@ -75,7 +75,7 @@ Item {
7575 sourceComponent: Component {
7676 DesktopStage {
7777 anchors.fill: parent
78− background: "../../../qml/graphics/tablet_background.jpg"
78+ background: "/usr/share/backgrounds/warty-final-ubuntu.png"
7979 focus: true
8080
8181 Component.onCompleted: {
tests/qmltests/tst_Shell.qml+157−1
@@ -19,6 +19,7 @@ import QtTest 1.0
1919import AccountsService 0.1
2020import GSettings 1.0
2121import LightDM.IntegratedLightDM 0.1 as LightDM
22+import SessionBroadcast 0.1
2223import Ubuntu.Components 1.3
2324import Ubuntu.Components.ListItems 1.3 as ListItem
2425import Ubuntu.Telephony 0.1 as Telephony
@@ -405,6 +406,18 @@ Rectangle {
405406 signalName: "setCurrentScopeCalled"
406407 }
407408
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+
408421 SignalSpy {
409422 id: unlockAllModemsSpy
410423 target: Connectivity
@@ -536,6 +549,7 @@ Rectangle {
536549
537550 AccountsService.demoEdges = false;
538551 AccountsService.demoEdgesCompleted = [];
552+ AccountsService.backgroundFile = "";
539553 Wizard.System.wizardEnabled = false;
540554 shellLoader.mode = "full-greeter";
541555
@@ -546,8 +560,11 @@ Rectangle {
546560 LightDM.Greeter.authenticate(""); // reset greeter
547561
548562 sessionSpy.clear();
563+ broadcastUrlSpy.clear();
564+ broadcastHomeSpy.clear();
549565
550566 GSettingsController.setLifecycleExemptAppids([]);
567+ GSettingsController.setPictureUri("");
551568
552569 // there should be only unity8-dash window over there
553570 tryCompare(ApplicationManager, "count", 1);
@@ -672,7 +689,7 @@ Rectangle {
672689 waitUntilDashIsFocused();
673690 tryCompare(greeter, "shown", false);
674691 } else {
675− compare(greeter.fullyShown, data.greeterShown);
692+ tryCompare(greeter, "fullyShown", data.greeterShown);
676693 }
677694
678695 var launcher = findChild(shell, "launcherPanel");
@@ -1319,6 +1336,32 @@ Rectangle {
13191336 tryCompare(tutorial, "paused", false);
13201337 }
13211338
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+
13221365 function test_tapOnRightEdgeReachesApplicationSurface() {
13231366 loadShell("phone");
13241367 swipeAwayGreeter();
@@ -2420,6 +2463,119 @@ Rectangle {
24202463 compare(topmostSurfaceItem.touchReleaseCount, 2);
24212464 }
24222465
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+
24232579 function test_switchKeymap() {
24242580 // start with phone shell
24252581 loadShell("phone");
tests/qmltests/tst_ShellWithPin.qml+43−38
@@ -154,7 +154,6 @@ Item {
154154 var greeter = findChild(shell, "greeter");
155155 sessionSpy.target = greeter;
156156 swipeAwayGreeter(true);
157− waitForLockscreen()
158157 greeter.failedLoginsDelayAttempts = -1;
159158 greeter.maxFailedLogins = -1;
160159
@@ -214,20 +213,16 @@ Item {
214213 }
215214 }
216215
217− function waitForLockscreen() {
218− var lockscreen = findChild(shell, "lockscreen");
219− var pinPadLoader = findChild(lockscreen, "pinPadLoader");
220− tryCompare(pinPadLoader, "status", Loader.Ready)
221− waitForRendering(lockscreen)
216+ function enterPin(pin) {
217+ typeString(pin);
222218 }
223219
224− function enterPin(pin) {
225− for (var i = 0; i < pin.length; ++i) {
226− var character = pin.charAt(i)
227− var button = findChild(shell, "pinPadButton" + character)
228− tryCompare(button, "enabled", true);
229− tap(button)
230− }
220+ function showGreeter() {
221+ LightDM.Greeter.showGreeter();
222+ tryCompare(shell, "waitingOnGreeter", false);
223+ var coverPage = findChild(shell, "coverPage");
224+ tryCompare(coverPage, "showProgress", 1);
225+ removeTimeConstraintsFromSwipeAreas(shell);
231226 }
232227
233228 function confirmLockedApp(app) {
@@ -248,7 +243,7 @@ Item {
248243 LightDM.Greeter.hideGreeter();
249244 tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile);
250245
251− LightDM.Greeter.showGreeter();
246+ showGreeter();
252247 tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile + "_greeter");
253248
254249 LightDM.Greeter.hideGreeter();
@@ -293,7 +288,7 @@ Item {
293288
294289 // Cancel emergency mode, and go back to normal
295290 waitForRendering(greeter)
296− LightDM.Greeter.showGreeter()
291+ showGreeter()
297292
298293 tryCompare(greeter, "shown", true)
299294 tryCompare(greeter, "lockedApp", "")
@@ -328,6 +323,22 @@ Item {
328323 tryCompare(greeter, "shown", true);
329324 }
330325
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+
331342 function test_failedLoginsCount() {
332343 AccountsService.failedLogins = 0
333344
@@ -342,22 +353,23 @@ Item {
342353 var greeter = findChild(shell, "greeter");
343354 greeter.failedLoginsDelayAttempts = 3;
344355
345− var placeHolder = findChild(shell, "wrongNoticeLabel")
346− tryCompare(placeHolder, "text", "")
356+ var wrongPasswordAnimation = findInvisibleChild(shell, "wrongPasswordAnimation");
347357
348358 enterPin("1111")
349− tryCompare(placeHolder, "text", "Sorry, incorrect passcode")
359+ verify(wrongPasswordAnimation.running);
350360
351361 enterPin("1111")
352− tryCompare(placeHolder, "text", "Sorry, incorrect passcode")
362+ verify(wrongPasswordAnimation.running);
353363
354− var lockscreen = findChild(shell, "lockscreen")
355− tryCompare(lockscreen, "delayMinutes", 0)
364+ var delayedLockscreen = findChild(shell, "delayedLockscreen");
365+ tryCompare(delayedLockscreen, "delayMinutes", 0);
356366 enterPin("1111")
357− tryCompare(lockscreen, "delayMinutes", greeter.failedLoginsDelayMinutes);
367+ tryCompare(delayedLockscreen, "delayMinutes", greeter.failedLoginsDelayMinutes);
358368 }
359369
360370 function test_factoryReset() {
371+ skip("Factory reset support is not finished")
372+
361373 maxRetriesTextField.text = "3"
362374 resetSpy.clear()
363375
@@ -388,8 +400,7 @@ Item {
388400 compare(shell.usageScenario, "phone");
389401 compare(applicationsDisplayLoader.usageScenario, "phone");
390402
391− var lockscreen = findChild(shell, "lockscreen")
392− lockscreen.emergencyCall()
403+ tap(findChild(shell, "emergencyCallLabel"));
393404 confirmLockedApp("dialer-app")
394405
395406 // OK, we're in. Now try (but fail) to switch to tablet mode
@@ -416,16 +427,13 @@ Item {
416427 // - Should be back in emergency mode dialer
417428
418429 var greeter = findChild(shell, "greeter");
419− var lockscreen = findChild(shell, "lockscreen");
420− verify(lockscreen);
421430
422− lockscreen.emergencyCall();
431+ tap(findChild(shell, "emergencyCallLabel"));
423432 confirmLockedApp("dialer-app");
424433 callManager.foregroundCall = phoneCall;
425434
426− LightDM.Greeter.showGreeter();
427− lockscreen = findChild(shell, "lockscreen");
428− verify(lockscreen);
435+ showGreeter();
436+ var lockscreen = findChild(shell, "lockscreen");
429437 tryCompare(lockscreen, "shown", true);
430438 tryCompare(greeter, "hasLockedApp", false);
431439
@@ -468,7 +476,7 @@ Item {
468476 }
469477
470478 function test_focusRequestedHidesCoverPage() {
471− LightDM.Greeter.showGreeter();
479+ showGreeter();
472480
473481 var app = ApplicationManager.startApplication("gallery-app");
474482 // wait until the app is fully loaded (ie, real surface replaces splash screen)
@@ -480,8 +488,7 @@ Item {
480488 tryCompare(coverPage, "showProgress", 0);
481489 tryCompare(greeter, "fullyShown", true);
482490
483− LightDM.Greeter.showGreeter();
484− tryCompare(coverPage, "showProgress", 1);
491+ showGreeter();
485492
486493 // Make sure focusing same app triggers same behavior
487494 ApplicationManager.requestFocusApplication("gallery-app");
@@ -555,9 +562,7 @@ Item {
555562 only after its idle timeout is triggered.
556563 */
557564 function test_shortLeftEdgeSwipeMakesLauncherStayVisible() {
558− LightDM.Greeter.showGreeter();
559− var coverPage = testCase.findChild(shell, "coverPage");
560− tryCompare(coverPage, "showProgress", 1);
565+ showGreeter();
561566
562567 var launcher = testCase.findChild(shell, "launcher")
563568 var launcherPanel = testCase.findChild(launcher, "launcherPanel");
@@ -573,6 +578,7 @@ Item {
573578 tryCompare(launcherPanel, "x", 0);
574579
575580 // Greeter should be displaced
581+ var coverPage = findChild(shell, "coverPage");
576582 tryCompareFunction(function() { return coverPage.mapToItem(shell, 0, 0).x > shell.width*0.2; }, true);
577583
578584 touchRelease(shell, toX, shell.height * 0.5);
@@ -595,8 +601,7 @@ Item {
595601 tryCompare(shell, "mainApp", galleryApp);
596602
597603 // Show greeter
598− LightDM.Greeter.showGreeter();
599− tryCompare(coverPage, "showProgress", 1);
604+ showGreeter();
600605
601606 // Swipe cover page away
602607 touchFlick(shell, 2, shell.height / 2, units.gu(30), shell.height / 2);