on xenial help sign in
Use a model for PAM prompts, supporting more possible interactions.
6d9907eMichael Terry
plugins/LightDM/CMakeLists.txt+1
@@ -17,6 +17,7 @@ set(QMLPLUGIN_SRC
17 DBusGreeterList.cpp
17 DBusGreeterList.cpp
18 Greeter.cpp
18 Greeter.cpp
19 plugin.cpp
19 plugin.cpp
20+ PromptsModel.cpp
20 SessionsModel.cpp
21 SessionsModel.cpp
21 UsersModel.cpp
22 UsersModel.cpp
22 )
23 )
plugins/LightDM/DBusGreeterList.cpp+3−3
@@ -25,7 +25,7 @@ DBusGreeterList::DBusGreeterList(Greeter *greeter, const QString &path)
25 m_greeter(greeter)
25 m_greeter(greeter)
26{
26{
27 connect(m_greeter, &Greeter::authenticationUserChanged, this, &DBusGreeterList::authenticationUserChangedHandler);
27 connect(m_greeter, &Greeter::authenticationUserChanged, this, &DBusGreeterList::authenticationUserChangedHandler);
28− connect(m_greeter, &Greeter::promptlessChanged, this, &DBusGreeterList::promptlessChangedHandler);
28+ connect(m_greeter, &Greeter::isAuthenticatedChanged, this, &DBusGreeterList::authenticatedChangedHandler);
29}
29}
30
30
31QString DBusGreeterList::GetActiveEntry() const
31QString DBusGreeterList::GetActiveEntry() const
@@ -40,7 +40,7 @@ void DBusGreeterList::SetActiveEntry(const QString &entry)
40
40
41bool DBusGreeterList::entryIsLocked() const
41bool DBusGreeterList::entryIsLocked() const
42{
42{
43− return !m_greeter->promptless();
43+ return !m_greeter->isAuthenticated();
44}
44}
45
45
46void DBusGreeterList::authenticationUserChangedHandler()
46void DBusGreeterList::authenticationUserChangedHandler()
@@ -49,7 +49,7 @@ void DBusGreeterList::authenticationUserChangedHandler()
49 Q_EMIT EntrySelected(m_greeter->authenticationUser());
49 Q_EMIT EntrySelected(m_greeter->authenticationUser());
50}
50}
51
51
52−void DBusGreeterList::promptlessChangedHandler()
52+void DBusGreeterList::authenticatedChangedHandler()
53{
53{
54 notifyPropertyChanged(QStringLiteral("EntryIsLocked"), entryIsLocked());
54 notifyPropertyChanged(QStringLiteral("EntryIsLocked"), entryIsLocked());
55 Q_EMIT entryIsLockedChanged();
55 Q_EMIT entryIsLockedChanged();
plugins/LightDM/DBusGreeterList.h+1−1
@@ -48,7 +48,7 @@ Q_SIGNALS:
48
48
49private Q_SLOTS:
49private Q_SLOTS:
50 void authenticationUserChangedHandler();
50 void authenticationUserChangedHandler();
51− void promptlessChangedHandler();
51+ void authenticatedChangedHandler();
52
52
53private:
53private:
54 Greeter *m_greeter;
54 Greeter *m_greeter;
plugins/LightDM/Greeter.cpp+69−22
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013-2016 Canonical, Ltd.
2+ * Copyright (C) 2013-2017 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -25,8 +25,8 @@ static Greeter *singleton = nullptr;
25GreeterPrivate::GreeterPrivate(Greeter* parent)
25GreeterPrivate::GreeterPrivate(Greeter* parent)
26 : m_greeter(new QLightDM::Greeter(parent)),
26 : m_greeter(new QLightDM::Greeter(parent)),
27 m_active(false),
27 m_active(false),
28− wasPrompted(false),
28+ responded(false),
29− promptless(false),
29+ everResponded(false),
30 q_ptr(parent)
30 q_ptr(parent)
31{
31{
32}
32}
@@ -64,6 +64,12 @@ Greeter *Greeter::instance()
64 return singleton;
64 return singleton;
65}
65}
66
66
67+PromptsModel *Greeter::promptsModel()
68+{
69+ Q_D(Greeter);
70+ return &d->prompts;
71+}
72+
67bool Greeter::isActive() const
73bool Greeter::isActive() const
68{
74{
69 Q_D(const Greeter);
75 Q_D(const Greeter);
@@ -106,12 +112,6 @@ QString Greeter::defaultSessionHint() const
106 return d->m_greeter->defaultSessionHint();
112 return d->m_greeter->defaultSessionHint();
107}
113}
108
114
109−bool Greeter::promptless() const
110−{
111− Q_D(const Greeter);
112− return d->promptless;
113−}
114−
115QString Greeter::selectUser() const
115QString Greeter::selectUser() const
116{
116{
117 Q_D(const Greeter);
117 Q_D(const Greeter);
@@ -143,11 +143,14 @@ bool Greeter::hideUsersHint() const
143void Greeter::authenticate(const QString &username)
143void Greeter::authenticate(const QString &username)
144{
144{
145 Q_D(Greeter);
145 Q_D(Greeter);
146− d->wasPrompted = false;
146+ d->prompts.clear();
147− if (d->promptless) {
147+ d->responded = false;
148− d->promptless = false;
148+ d->everResponded = false;
149− Q_EMIT promptlessChanged();
149+
150+ if (authenticationUser() == username) {
151+ d->prompts = d->leftovers;
150 }
152 }
153+ d->leftovers.clear();
151
154
152 if (username == QStringLiteral("*guest")) {
155 if (username == QStringLiteral("*guest")) {
153 d->m_greeter->authenticateAsGuest();
156 d->m_greeter->authenticateAsGuest();
@@ -157,6 +160,7 @@ void Greeter::authenticate(const QString &username)
157 d->m_greeter->authenticate(username);
160 d->m_greeter->authenticate(username);
158 }
161 }
159
162
163+ Q_EMIT authenticationStarted();
160 Q_EMIT isAuthenticatedChanged();
164 Q_EMIT isAuthenticatedChanged();
161 checkAuthenticationUser();
165 checkAuthenticationUser();
162}
166}
@@ -164,6 +168,8 @@ void Greeter::authenticate(const QString &username)
164void Greeter::respond(const QString &response)
168void Greeter::respond(const QString &response)
165{
169{
166 Q_D(Greeter);
170 Q_D(Greeter);
171+ d->responded = true;
172+ d->everResponded = true;
167 d->m_greeter->respond(response);
173 d->m_greeter->respond(response);
168}
174}
169
175
@@ -176,14 +182,17 @@ bool Greeter::startSessionSync(const QString &session)
176void Greeter::showPromptFilter(const QString &text, QLightDM::Greeter::PromptType type)
182void Greeter::showPromptFilter(const QString &text, QLightDM::Greeter::PromptType type)
177{
183{
178 Q_D(Greeter);
184 Q_D(Greeter);
179− d->wasPrompted = true;
180
185
181 checkAuthenticationUser(); // may have changed in liblightdm
186 checkAuthenticationUser(); // may have changed in liblightdm
182
187
183 bool isDefaultPrompt = (text == dgettext("Linux-PAM", "Password: "));
188 bool isDefaultPrompt = (text == dgettext("Linux-PAM", "Password: "));
189+ bool isSecret = type == QLightDM::Greeter::PromptTypeSecret;
190+
191+ QString trimmedText;
192+ if (!isDefaultPrompt)
193+ trimmedText = text.trimmed();
184
194
185 // Strip prompt of any colons at the end
195 // Strip prompt of any colons at the end
186− QString trimmedText = text.trimmed();
187 if (trimmedText.endsWith(':') || trimmedText.endsWith(QStringLiteral(":"))) {
196 if (trimmedText.endsWith(':') || trimmedText.endsWith(QStringLiteral(":"))) {
188 trimmedText.chop(1);
197 trimmedText.chop(1);
189 }
198 }
@@ -194,23 +203,61 @@ void Greeter::showPromptFilter(const QString &text, QLightDM::Greeter::PromptTyp
194 trimmedText = gettext("Username");
203 trimmedText = gettext("Username");
195 }
204 }
196
205
197− Q_EMIT showPrompt(trimmedText, type == QLightDM::Greeter::PromptTypeSecret, isDefaultPrompt);
206+ if (d->responded) {
207+ d->prompts.clear();
208+ d->responded = false;
209+ }
210+
211+ d->prompts.append(trimmedText, isSecret ? PromptsModel::Secret : PromptsModel::Question);
198}
212}
199
213
200void Greeter::showMessageFilter(const QString &text, QLightDM::Greeter::MessageType type)
214void Greeter::showMessageFilter(const QString &text, QLightDM::Greeter::MessageType type)
201{
215{
216+ Q_D(Greeter);
217+
202 checkAuthenticationUser(); // may have changed in liblightdm
218 checkAuthenticationUser(); // may have changed in liblightdm
219+
203− Q_EMIT showMessage(text, type == QLightDM::Greeter::MessageTypeError);
220+ bool isError = type == QLightDM::Greeter::MessageTypeError;
221+
222+ if (d->responded) {
223+ d->prompts.clear();
224+ d->responded = false;
225+ }
226+ d->prompts.append(text, isError? PromptsModel::Error : PromptsModel::Message);
204}
227}
205
228
206void Greeter::authenticationCompleteFilter()
229void Greeter::authenticationCompleteFilter()
207{
230{
208 Q_D(Greeter);
231 Q_D(Greeter);
209− if (!d->wasPrompted) {
210− d->promptless = true;
211− Q_EMIT promptlessChanged();
212− }
213
232
214 Q_EMIT isAuthenticatedChanged();
233 Q_EMIT isAuthenticatedChanged();
215− Q_EMIT authenticationComplete();
234+
235+ bool automatic = !d->everResponded;
236+ bool pamHasLeftoverMessages = !d->prompts.hasPrompt() && d->prompts.rowCount() > 0;
237+
238+ if (!isAuthenticated()) {
239+ if (pamHasLeftoverMessages) {
240+ d->leftovers = d->prompts; // Prefer PAM's messages
241+ } else if (automatic) {
242+ d->leftovers.append(gettext("Failed to authenticate"), PromptsModel::Error);
243+ } else {
244+ d->leftovers.append(gettext("Invalid password, please try again"), PromptsModel::Error);
245+ }
246+ } else if (pamHasLeftoverMessages) {
247+ automatic = true; // treat this successful login as automatic, so user sees message
248+ d->leftovers = d->prompts;
249+ }
250+
251+ if (automatic) {
252+ d->prompts = d->leftovers; // OK, we'll just use these now
253+ d->leftovers.clear();
254+ d->prompts.append(isAuthenticated() ? gettext("Log In") : gettext("Retry"),
255+ PromptsModel::Button);
256+ }
257+
258+ if (isAuthenticated()) {
259+ Q_EMIT loginSuccess(automatic);
260+ } else {
261+ Q_EMIT loginError(automatic);
262+ }
216}
263}
plugins/LightDM/Greeter.h+6−6
@@ -27,6 +27,7 @@
27#include <QtCore/QObject>
27#include <QtCore/QObject>
28
28
29class GreeterPrivate;
29class GreeterPrivate;
30+class PromptsModel;
30
31
31class Greeter : public QObject
32class Greeter : public QObject
32{
33{
@@ -36,7 +37,6 @@ class Greeter : public QObject
36 Q_PROPERTY(bool authenticated READ isAuthenticated NOTIFY isAuthenticatedChanged)
37 Q_PROPERTY(bool authenticated READ isAuthenticated NOTIFY isAuthenticatedChanged)
37 Q_PROPERTY(QString authenticationUser READ authenticationUser NOTIFY authenticationUserChanged)
38 Q_PROPERTY(QString authenticationUser READ authenticationUser NOTIFY authenticationUserChanged)
38 Q_PROPERTY(QString defaultSession READ defaultSessionHint CONSTANT)
39 Q_PROPERTY(QString defaultSession READ defaultSessionHint CONSTANT)
39− Q_PROPERTY(bool promptless READ promptless NOTIFY promptlessChanged)
40 Q_PROPERTY(QString selectUser READ selectUser CONSTANT)
40 Q_PROPERTY(QString selectUser READ selectUser CONSTANT)
41
41
42public:
42public:
@@ -47,12 +47,13 @@ public:
47 bool isAuthenticated() const;
47 bool isAuthenticated() const;
48 QString authenticationUser() const;
48 QString authenticationUser() const;
49 QString defaultSessionHint() const;
49 QString defaultSessionHint() const;
50− bool promptless() const;
51 QString selectUser() const;
50 QString selectUser() const;
52 bool hasGuestAccount() const;
51 bool hasGuestAccount() const;
53 bool showManualLoginHint() const;
52 bool showManualLoginHint() const;
54 bool hideUsersHint() const;
53 bool hideUsersHint() const;
55
54
55+ PromptsModel *promptsModel();
56+
56public Q_SLOTS:
57public Q_SLOTS:
57 void authenticate(const QString &username=QString());
58 void authenticate(const QString &username=QString());
58 void respond(const QString &response);
59 void respond(const QString &response);
@@ -60,15 +61,14 @@ public Q_SLOTS:
60 void setIsActive(bool isActive);
61 void setIsActive(bool isActive);
61
62
62Q_SIGNALS:
63Q_SIGNALS:
63− void showMessage(const QString &text, bool isError);
64− void showPrompt(const QString &text, bool isSecret, bool isDefaultPrompt);
65− void authenticationComplete();
66 void authenticationUserChanged();
64 void authenticationUserChanged();
67 void isActiveChanged();
65 void isActiveChanged();
68 void isAuthenticatedChanged();
66 void isAuthenticatedChanged();
69− void promptlessChanged();
70 void showGreeter();
67 void showGreeter();
71 void hideGreeter();
68 void hideGreeter();
69+ void loginError(bool automatic);
70+ void loginSuccess(bool automatic);
71+ void authenticationStarted(); // useful for testing
72
72
73 // This signal is emitted by external agents like indicators, and the UI
73 // This signal is emitted by external agents like indicators, and the UI
74 // should switch to this user if possible.
74 // should switch to this user if possible.
plugins/LightDM/GreeterPrivate.h+5−2
@@ -16,6 +16,7 @@
16
16
17#pragma once
17#pragma once
18
18
19+#include "PromptsModel.h"
19#include <QObject>
20#include <QObject>
20
21
21namespace QLightDM {
22namespace QLightDM {
@@ -29,8 +30,10 @@ public:
29
30
30 QLightDM::Greeter *m_greeter;
31 QLightDM::Greeter *m_greeter;
31 bool m_active;
32 bool m_active;
32− bool wasPrompted;
33+ PromptsModel prompts;
33− bool promptless;
34+ PromptsModel leftovers; // prompts to show during next auth for same user
35+ bool responded;
36+ bool everResponded;
34 QString cachedAuthUser;
37 QString cachedAuthUser;
35
38
36protected:
39protected:
plugins/LightDM/PromptsModel.cppnew file+97
@@ -0,0 +1,97 @@
1+/*
2+ * Copyright (C) 2017 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+
18+#include "PromptsModel.h"
19+
20+PromptsModel::PromptsModel(QObject* parent)
21+ : QAbstractListModel(parent)
22+{
23+ m_roleNames[TypeRole] = "type";
24+ m_roleNames[TextRole] = "text";
25+}
26+
27+PromptsModel& PromptsModel::operator=(const PromptsModel &other)
28+{
29+ beginResetModel();
30+ m_prompts = other.m_prompts;
31+ endResetModel();
32+ Q_EMIT countChanged();
33+ return *this;
34+}
35+
36+int PromptsModel::rowCount(const QModelIndex &parent) const
37+{
38+ if (parent.isValid())
39+ return 0;
40+
41+ return m_prompts.size();
42+}
43+
44+QVariant PromptsModel::data(const QModelIndex &index, int role) const
45+{
46+ if (!index.isValid() || index.column() > 0 || index.row() >= m_prompts.size())
47+ return QVariant();
48+
49+ switch (role) {
50+ case Qt::DisplayRole: // fallthrough
51+ case TextRole: return m_prompts[index.row()].prompt;
52+ case TypeRole: return m_prompts[index.row()].type;
53+ default: return QVariant();
54+ }
55+}
56+
57+QHash<int, QByteArray> PromptsModel::roleNames() const
58+{
59+ return m_roleNames;
60+}
61+
62+void PromptsModel::prepend(const QString &text, PromptType type)
63+{
64+ beginInsertRows(QModelIndex(), 0, 0);
65+ m_prompts.prepend(PromptInfo{text, type});
66+ endInsertRows();
67+
68+ Q_EMIT countChanged();
69+}
70+
71+void PromptsModel::append(const QString &text, PromptType type)
72+{
73+ beginInsertRows(QModelIndex(), m_prompts.size(), m_prompts.size());
74+ m_prompts.append(PromptInfo{text, type});
75+ endInsertRows();
76+
77+ Q_EMIT countChanged();
78+}
79+
80+void PromptsModel::clear()
81+{
82+ beginResetModel();
83+ m_prompts.clear();
84+ endResetModel();
85+
86+ Q_EMIT countChanged();
87+}
88+
89+bool PromptsModel::hasPrompt() const
90+{
91+ Q_FOREACH(const PromptInfo &info, m_prompts) {
92+ if (info.type == PromptType::Secret || info.type == PromptType::Question) {
93+ return true;
94+ }
95+ }
96+ return false;
97+}
plugins/LightDM/PromptsModel.hnew file+70
@@ -0,0 +1,70 @@
1+/*
2+ * Copyright (C) 2017 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+
18+#pragma once
19+
20+#include <QAbstractListModel>
21+
22+class PromptsModel : public QAbstractListModel
23+{
24+ Q_OBJECT
25+
26+ Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
27+
28+public:
29+ enum PromptsModelRoles {
30+ TypeRole = Qt::UserRole,
31+ TextRole,
32+ };
33+ Q_ENUM(PromptsModelRoles)
34+
35+ enum PromptType {
36+ Message,
37+ Error,
38+ Secret,
39+ Question,
40+ Button,
41+ };
42+ Q_ENUM(PromptType)
43+
44+ explicit PromptsModel(QObject* parent=0);
45+
46+ PromptsModel& operator=(const PromptsModel &other);
47+
48+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
49+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
50+ QHash<int, QByteArray> roleNames() const override;
51+
52+ Q_INVOKABLE void prepend(const QString &text, PromptType type);
53+ Q_INVOKABLE void append(const QString &text, PromptType type);
54+
55+ void clear();
56+
57+ bool hasPrompt() const;
58+
59+Q_SIGNALS:
60+ void countChanged();
61+
62+private:
63+ struct PromptInfo {
64+ QString prompt;
65+ PromptType type;
66+ };
67+
68+ QList<PromptInfo> m_prompts;
69+ QHash<int, QByteArray> m_roleNames;
70+};
plugins/LightDM/plugin.cpp+9−2
@@ -19,6 +19,7 @@
19#include "DBusGreeter.h"
19#include "DBusGreeter.h"
20#include "DBusGreeterList.h"
20#include "DBusGreeterList.h"
21#include "Greeter.h"
21#include "Greeter.h"
22+#include "PromptsModel.h"
22#include "SessionsModel.h"
23#include "SessionsModel.h"
23#include "UsersModel.h"
24#include "UsersModel.h"
24#include <libusermetricsoutput/ColorTheme.h>
25#include <libusermetricsoutput/ColorTheme.h>
@@ -43,6 +44,13 @@ static QObject *greeter_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
43 return greeter;
44 return greeter;
44}
45}
45
46
47+static QObject *prompts_provider(QQmlEngine *engine, QJSEngine *)
48+{
49+ auto model = Greeter::instance()->promptsModel();
50+ engine->setObjectOwnership(model, QQmlEngine::CppOwnership);
51+ return model;
52+}
53+
46static QObject *sessions_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
54static QObject *sessions_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
47{
55{
48 Q_UNUSED(engine)
56 Q_UNUSED(engine)
@@ -79,8 +87,7 @@ void PLUGIN_CLASSNAME::registerTypes(const char *uri)
79 #error No library defined in LightDM plugin
87 #error No library defined in LightDM plugin
80#endif
88#endif
81
89
82− qRegisterMetaType<QLightDM::Greeter::MessageType>("QLightDM::Greeter::MessageType");
90+ qmlRegisterSingletonType<PromptsModel>(uri, 0, 1, "Prompts", prompts_provider);
83− qRegisterMetaType<QLightDM::Greeter::PromptType>("QLightDM::Greeter::PromptType");
84
91
85 qmlRegisterSingletonType<SessionsModel>(uri, 0, 1, "Sessions", sessions_provider);
92 qmlRegisterSingletonType<SessionsModel>(uri, 0, 1, "Sessions", sessions_provider);
86 qmlRegisterUncreatableType<QLightDM::SessionsModel>(uri, 0, 1, "SessionRoles", QStringLiteral("Type is not instantiable"));
93 qmlRegisterUncreatableType<QLightDM::SessionsModel>(uri, 0, 1, "SessionRoles", QStringLiteral("Type is not instantiable"));
qml/Greeter/Circle.qml+1−1
@@ -27,7 +27,7 @@ Canvas {
27 onCenterCircleChanged: requestPaint()
27 onCenterCircleChanged: requestPaint()
28
28
29 onPaint: {
29 onPaint: {
30− if (circleScale <= 0) {
30+ if (circleScale <= 0 || width <= 0 || height <= 0) {
31 return;
31 return;
32 }
32 }
33
33
qml/Greeter/FullLightDMImpl.qml+1
@@ -22,6 +22,7 @@ Item{
22
22
23 property var greeter: LightDM.Greeter
23 property var greeter: LightDM.Greeter
24 property var infographic: LightDM.Infographic
24 property var infographic: LightDM.Infographic
25+ property var prompts: LightDM.Prompts
25 property var sessions: LightDM.Sessions
26 property var sessions: LightDM.Sessions
26 property var sessionRoles: LightDM.SessionRoles
27 property var sessionRoles: LightDM.SessionRoles
27 property var users: LightDM.Users
28 property var users: LightDM.Users
qml/Greeter/Greeter.qml+27−57
@@ -74,11 +74,11 @@ Showable {
74 forcedUnlock = false;
74 forcedUnlock = false;
75 if (required) {
75 if (required) {
76 if (loader.item) {
76 if (loader.item) {
77− loader.item.reset(true /* forceShow */);
77+ loader.item.forceShow();
78 }
78 }
79 // Normally loader.onLoaded will select a user, but if we're
79 // Normally loader.onLoaded will select a user, but if we're
80 // already shown, do it manually.
80 // already shown, do it manually.
81− d.selectUser(d.currentIndex, false);
81+ d.selectUser(d.currentIndex);
82 }
82 }
83
83
84 // Even though we may already be shown, we want to call show() for its
84 // Even though we may already be shown, we want to call show() for its
@@ -154,7 +154,7 @@ Showable {
154 readonly property bool multiUser: LightDMService.users.count > 1
154 readonly property bool multiUser: LightDMService.users.count > 1
155 readonly property int selectUserIndex: d.getUserIndex(LightDMService.greeter.selectUser)
155 readonly property int selectUserIndex: d.getUserIndex(LightDMService.greeter.selectUser)
156 property int currentIndex: Math.max(selectUserIndex, 0)
156 property int currentIndex: Math.max(selectUserIndex, 0)
157− property bool waiting
157+ readonly property bool waiting: LightDMService.prompts.count == 0 && !root.forcedUnlock
158 property bool isLockscreen // true when we are locking an active session, rather than first user login
158 property bool isLockscreen // true when we are locking an active session, rather than first user login
159 readonly property bool secureFingerprint: isLockscreen &&
159 readonly property bool secureFingerprint: isLockscreen &&
160 AccountsService.failedFingerprintLogins <
160 AccountsService.failedFingerprintLogins <
@@ -189,13 +189,9 @@ Showable {
189 return -1;
189 return -1;
190 }
190 }
191
191
192− function selectUser(index, reset) {
192+ function selectUser(index) {
193 if (index < 0 || index >= LightDMService.users.count)
193 if (index < 0 || index >= LightDMService.users.count)
194 return;
194 return;
195− d.waiting = true;
196− if (reset) {
197− loader.item.reset(false /* forceShow */);
198− }
199 currentIndex = index;
195 currentIndex = index;
200 var user = LightDMService.users.data(index, LightDMService.userRoles.NameRole);
196 var user = LightDMService.users.data(index, LightDMService.userRoles.NameRole);
201 AccountsService.user = user;
197 AccountsService.user = user;
@@ -206,20 +202,17 @@ Showable {
206 function hideView() {
202 function hideView() {
207 if (loader.item) {
203 if (loader.item) {
208 loader.item.enabled = false; // drop OSK and prevent interaction
204 loader.item.enabled = false; // drop OSK and prevent interaction
209− loader.item.notifyAuthenticationSucceeded(false /* showFakePassword */);
210 loader.item.hide();
205 loader.item.hide();
211 }
206 }
212 }
207 }
213
208
214 function login() {
209 function login() {
215− d.waiting = true;
216 if (LightDMService.greeter.startSessionSync(root.sessionToStart())) {
210 if (LightDMService.greeter.startSessionSync(root.sessionToStart())) {
217 sessionStarted();
211 sessionStarted();
218 hideView();
212 hideView();
219 } else if (loader.item) {
213 } else if (loader.item) {
220 loader.item.notifyAuthenticationFailed();
214 loader.item.notifyAuthenticationFailed();
221 }
215 }
222− d.waiting = false;
223 }
216 }
224
217
225 function startUnlock(toTheRight) {
218 function startUnlock(toTheRight) {
@@ -239,27 +232,13 @@ Showable {
239 }
232 }
240 }
233 }
241
234
242− function showPromptMessage(text, isError) {
243− // inefficient, but we only rarely deal with messages
244− var html = text.replace(/&/g, "&amp;")
245− .replace(/</g, "&lt;")
246− .replace(/>/g, "&gt;")
247− .replace(/\n/g, "<br>");
248− if (isError) {
249− html = "<font color=\"#df382c\">" + html + "</font>";
250− }
251−
252− if (loader.item) {
253− loader.item.showMessage(html);
254− }
255− }
256−
257 function showFingerprintMessage(msg) {
235 function showFingerprintMessage(msg) {
236+ d.selectUser(d.currentIndex);
237+ LightDMService.prompts.prepend(msg, LightDMService.prompts.Error);
258 if (loader.item) {
238 if (loader.item) {
259− loader.item.reset(false /* forceShow */);
260 loader.item.showErrorMessage(msg);
239 loader.item.showErrorMessage(msg);
240+ loader.item.notifyAuthenticationFailed();
261 }
241 }
262− showPromptMessage(msg, true);
263 }
242 }
264 }
243 }
265
244
@@ -285,7 +264,6 @@ Showable {
285
264
286 onRequiredChanged: {
265 onRequiredChanged: {
287 if (required) {
266 if (required) {
288− d.waiting = true;
289 lockedApp = "";
267 lockedApp = "";
290 }
268 }
291 }
269 }
@@ -377,14 +355,14 @@ Showable {
377 onLoaded: {
355 onLoaded: {
378 root.lockedApp = "";
356 root.lockedApp = "";
379 item.forceActiveFocus();
357 item.forceActiveFocus();
380− d.selectUser(d.currentIndex, true);
358+ d.selectUser(d.currentIndex);
381 LightDMService.infographic.readyForDataChange();
359 LightDMService.infographic.readyForDataChange();
382 }
360 }
383
361
384 Connections {
362 Connections {
385 target: loader.item
363 target: loader.item
386 onSelected: {
364 onSelected: {
387− d.selectUser(index, true);
365+ d.selectUser(index);
388 }
366 }
389 onResponded: {
367 onResponded: {
390 if (root.locked) {
368 if (root.locked) {
@@ -481,27 +459,15 @@ Showable {
481 onShowGreeter: root.forceShow()
459 onShowGreeter: root.forceShow()
482 onHideGreeter: root.forcedUnlock = true
460 onHideGreeter: root.forcedUnlock = true
483
461
484− onShowMessage: d.showPromptMessage(text, isError)
462+ onLoginError: {
485−
486− onShowPrompt: {
487− if (loader.item) {
463+ if (!loader.item) {
488− loader.item.showPrompt(text, isSecret, isDefaultPrompt);
464+ return;
489 }
465 }
490
466
491− d.waiting = false;
467+ loader.item.notifyAuthenticationFailed();
492− }
493−
494− onAuthenticationComplete: {
495− d.waiting = false;
496
468
497− if (LightDMService.greeter.authenticated) {
469+ if (!automatic) {
498− if (!LightDMService.greeter.promptless) {
499− d.login();
500− }
501− } else {
502− if (!LightDMService.greeter.promptless) {
503− AccountsService.failedLogins++;
470+ AccountsService.failedLogins++;
504− }
505
471
506 // Check if we should initiate a factory reset
472 // Check if we should initiate a factory reset
507 if (maxFailedLogins >= 2) { // require at least a warning
473 if (maxFailedLogins >= 2) { // require at least a warning
@@ -519,14 +485,17 @@ Showable {
519 forcedDelayTimer.forceDelay();
485 forcedDelayTimer.forceDelay();
520 }
486 }
521
487
522− loader.item.notifyAuthenticationFailed();
523− if (!LightDMService.greeter.promptless) {
524− d.selectUser(d.currentIndex, false);
488+ d.selectUser(d.currentIndex);
525− }
489+ }
490+ }
491+
492+ onLoginSuccess: {
493+ if (!automatic) {
494+ d.login();
526 }
495 }
527 }
496 }
528
497
529− onRequestAuthenticationUser: d.selectUser(d.getUserIndex(user), true)
498+ onRequestAuthenticationUser: d.selectUser(d.getUserIndex(user))
530 }
499 }
531
500
532 Connections {
501 Connections {
@@ -589,7 +558,9 @@ Showable {
589 if (!d.secureFingerprint) {
558 if (!d.secureFingerprint) {
590 d.startUnlock(false /* toTheRight */); // use normal login instead
559 d.startUnlock(false /* toTheRight */); // use normal login instead
591 }
560 }
592− var msg = d.secureFingerprint ? i18n.tr("Try again") : "";
561+ var msg = d.secureFingerprint ? i18n.tr("Try again") :
562+ d.alphanumeric ? i18n.tr("Enter passphrase to unlock") :
563+ i18n.tr("Enter passcode to unlock");
593 d.showFingerprintMessage(msg);
564 d.showFingerprintMessage(msg);
594 }
565 }
595
566
@@ -609,8 +580,7 @@ Showable {
609 }
580 }
610 console.log("Identified user by fingerprint:", result);
581 console.log("Identified user by fingerprint:", result);
611 if (loader.item) {
582 if (loader.item) {
612− loader.item.enabled = false;
583+ loader.item.showFakePassword();
613− loader.item.notifyAuthenticationSucceeded(true /* showFakePassword */);
614 }
584 }
615 if (root.active)
585 if (root.active)
616 root.forcedUnlock = true;
586 root.forcedUnlock = true;
qml/Greeter/GreeterPrompt.qml+31−31
@@ -27,31 +27,25 @@ FocusScope {
27 property bool isAlphanumeric
27 property bool isAlphanumeric
28 property string text
28 property string text
29 property bool isSecret
29 property bool isSecret
30+ property bool interactive: true
31+ readonly property alias enteredText: passwordInput.text
30
32
31 signal clicked()
33 signal clicked()
32 signal canceled()
34 signal canceled()
33− signal responded(string text)
35+ signal accepted()
34−
35− function reset() {
36− passwordInput.text = "";
37− fakeLabel.text = "";
38− d.enabled = true;
39− }
40
36
41 function showFakePassword() {
37 function showFakePassword() {
42 // Just a silly hack for looking like 4 pin numbers got entered, if
38 // 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
39 // a fingerprint was used and we happen to be using a pin. This was
44 // a request from Design.
40 // a request from Design.
45 if (isSecret && isPrompt && !isAlphanumeric) {
41 if (isSecret && isPrompt && !isAlphanumeric) {
46− d.enabled = false;
47− text = "...."; // actual text doesn't matter
42+ passwordInput.text = "...."; // actual text doesn't matter
48 }
43 }
49 }
44 }
50
45
51 StyledItem {
46 StyledItem {
52 id: d
47 id: d
53
48
54− property bool enabled: true
55 readonly property color textColor: passwordInput.enabled ? theme.palette.normal.raisedText
49 readonly property color textColor: passwordInput.enabled ? theme.palette.normal.raisedText
56 : theme.palette.disabled.raisedText
50 : theme.palette.disabled.raisedText
57 readonly property color selectedColor: passwordInput.enabled ? theme.palette.normal.raised
51 readonly property color selectedColor: passwordInput.enabled ? theme.palette.normal.raised
@@ -60,12 +54,6 @@ FocusScope {
60 : theme.palette.disabled.raisedSecondaryText
54 : theme.palette.disabled.raisedSecondaryText
61 readonly property color errorColor: passwordInput.enabled ? theme.palette.normal.negative
55 readonly property color errorColor: passwordInput.enabled ? theme.palette.normal.negative
62 : theme.palette.disabled.negative
56 : theme.palette.disabled.negative
63−
64− onEnabledChanged: {
65− if (!enabled) {
66− fakeLabel.text = passwordInput.displayText;
67− }
68− }
69 }
57 }
70
58
71 Rectangle {
59 Rectangle {
@@ -86,15 +74,17 @@ FocusScope {
86 }
74 }
87 }
75 }
88
76
89− Rectangle {
77+ StyledItem {
90 id: promptButton
78 id: promptButton
91 objectName: "promptButton"
79 objectName: "promptButton"
92 anchors.fill: parent
80 anchors.fill: parent
93 visible: !root.isPrompt
81 visible: !root.isPrompt
82+ activeFocusOnTab: true
83+
84+ styleName: "FocusShape"
94
85
95 function triggered() {
86 function triggered() {
96− if (d.enabled) {
87+ if (root.interactive) {
97− d.enabled = false;
98 root.clicked();
88 root.clicked();
99 }
89 }
100 }
90 }
@@ -109,6 +99,7 @@ FocusScope {
109 }
99 }
110 }
100 }
111
101
102+ Keys.onSpacePressed: triggered();
112 Keys.onReturnPressed: triggered();
103 Keys.onReturnPressed: triggered();
113 Keys.onEnterPressed: triggered();
104 Keys.onEnterPressed: triggered();
114 MouseArea {
105 MouseArea {
@@ -129,6 +120,7 @@ FocusScope {
129 anchors.fill: parent
120 anchors.fill: parent
130 visible: root.isPrompt
121 visible: root.isPrompt
131 opacity: fakeLabel.visible ? 0 : 1
122 opacity: fakeLabel.visible ? 0 : 1
123+ activeFocusOnTab: true
132
124
133 validator: RegExpValidator {
125 validator: RegExpValidator {
134 regExp: root.isAlphanumeric ? /^.*$/ : /^\d{4}$/
126 regExp: root.isAlphanumeric ? /^.*$/ : /^\d{4}$/
@@ -142,15 +134,23 @@ FocusScope {
142
134
143 readonly property real frameSpacing: units.gu(0.5)
135 readonly property real frameSpacing: units.gu(0.5)
144
136
145− style: Item {
137+ style: StyledItem {
146− property color color: d.textColor
147− property color selectedTextColor: d.selectedColor
148− property color selectionColor: d.textColor
149− property color borderColor: "transparent"
150− property color backgroundColor: "transparent"
151− property color errorColor: d.errorColor
152− property real frameSpacing: passwordInput.frameSpacing
153 anchors.fill: parent
138 anchors.fill: parent
139+ styleName: "FocusShape"
140+
141+ // Properties needed by TextField
142+ readonly property color color: d.textColor
143+ readonly property color selectedTextColor: d.selectedColor
144+ readonly property color selectionColor: d.textColor
145+ readonly property color borderColor: "transparent"
146+ readonly property color backgroundColor: "transparent"
147+ readonly property color errorColor: d.errorColor
148+ readonly property real frameSpacing: styledItem.frameSpacing
149+
150+ // Properties needed by FocusShape
151+ readonly property bool enabled: styledItem.enabled
152+ readonly property bool keyNavigationFocus: styledItem.keyNavigationFocus
153+ property bool activeFocusOnTab
154 }
154 }
155
155
156 secondaryItem: [
156 secondaryItem: [
@@ -178,9 +178,8 @@ FocusScope {
178 onAccepted: respond()
178 onAccepted: respond()
179
179
180 function respond() {
180 function respond() {
181− if (d.enabled) {
181+ if (root.interactive) {
182− d.enabled = false;
182+ root.accepted();
183− root.responded(text);
184 }
183 }
185 }
184 }
186
185
@@ -224,6 +223,7 @@ FocusScope {
224 anchors.leftMargin: passwordInput.frameSpacing * 2
223 anchors.leftMargin: passwordInput.frameSpacing * 2
225 anchors.rightMargin: passwordInput.frameSpacing * 2 + capsIcon.visibleWidth
224 anchors.rightMargin: passwordInput.frameSpacing * 2 + capsIcon.visibleWidth
226 color: d.drawColor
225 color: d.drawColor
226+ text: passwordInput.displayText
227− visible: root.isPrompt && !d.enabled
227+ visible: root.isPrompt && !root.interactive
228 }
228 }
229}
229}
qml/Greeter/IntegratedLightDMImpl.qml+1
@@ -22,6 +22,7 @@ Item{
22
22
23 property var greeter: LightDM.Greeter
23 property var greeter: LightDM.Greeter
24 property var infographic: LightDM.Infographic
24 property var infographic: LightDM.Infographic
25+ property var prompts: LightDM.Prompts
25 property var sessions: LightDM.Sessions
26 property var sessions: LightDM.Sessions
26 property var sessionRoles: LightDM.SessionRoles
27 property var sessionRoles: LightDM.SessionRoles
27 property var users: LightDM.Users
28 property var users: LightDM.Users
qml/Greeter/LightDMService.qml+1
@@ -27,6 +27,7 @@ Loader {
27
27
28 property var greeter: d.valid ? loader.item.greeter : null
28 property var greeter: d.valid ? loader.item.greeter : null
29 property var infographic: d.valid ? loader.item.infographic : null
29 property var infographic: d.valid ? loader.item.infographic : null
30+ property var prompts: d.valid ? loader.item.prompts : null
30 property var sessions: d.valid ? loader.item.sessions : null
31 property var sessions: d.valid ? loader.item.sessions : null
31 property var sessionRoles: d.valid ? loader.item.sessionRoles : null
32 property var sessionRoles: d.valid ? loader.item.sessionRoles : null
32 property var users: d.valid ? loader.item.users : null
33 property var users: d.valid ? loader.item.users : null
qml/Greeter/LoginList.qml+32−106
@@ -25,83 +25,36 @@ StyledItem {
25 focus: true
25 focus: true
26
26
27 property alias model: userList.model
27 property alias model: userList.model
28− property bool alphanumeric: true
28+ property alias alphanumeric: promptList.alphanumeric
29 property int currentIndex
29 property int currentIndex
30 property bool locked
30 property bool locked
31 property bool waiting
31 property bool waiting
32 property alias boxVerticalOffset: highlightItem.y
32 property alias boxVerticalOffset: highlightItem.y
33
33
34− readonly property alias passwordInput: passwordInput
35 readonly property int numAboveBelow: 4
34 readonly property int numAboveBelow: 4
36 readonly property int cellHeight: units.gu(5)
35 readonly property int cellHeight: units.gu(5)
37− readonly property int highlightedHeight: units.gu(15)
36+ readonly property int highlightedHeight: highlightItem.height
38 readonly property int moveDuration: UbuntuAnimation.FastDuration
37 readonly property int moveDuration: UbuntuAnimation.FastDuration
39 property string currentSession
38 property string currentSession
40 readonly property string currentUser: userList.currentItem.username
39 readonly property string currentUser: userList.currentItem.username
41− property bool wasPrompted: false
42
40
43 signal responded(string response)
41 signal responded(string response)
44 signal selected(int index)
42 signal selected(int index)
45 signal sessionChooserButtonClicked()
43 signal sessionChooserButtonClicked()
46
44
47 function tryToUnlock() {
45 function tryToUnlock() {
48− if (wasPrompted) {
49− passwordInput.forceActiveFocus();
46+ promptList.forceActiveFocus();
50− } else {
51− if (root.locked) {
52− root.selected(currentIndex);
53− } else {
54− root.responded("");
55− }
56− }
57− }
58−
59− function showMessage(html) {
60− if (infoLabel.text === "") {
61− infoLabel.text = html;
62− } else {
63− infoLabel.text += "<br>" + html;
64− }
65− }
66−
67− function showPrompt(text, isSecret, isDefaultPrompt) {
68− passwordInput.text = isDefaultPrompt ? alphanumeric ? i18n.tr("Passphrase")
69− : i18n.tr("Passcode")
70− : text;
71− passwordInput.isPrompt = true;
72− passwordInput.isSecret = isSecret;
73− passwordInput.reset();
74− wasPrompted = true;
75 }
47 }
76
48
77 function showError() {
49 function showError() {
78 wrongPasswordAnimation.start();
50 wrongPasswordAnimation.start();
79− root.resetAuthentication();
80− }
81−
82− function reset() {
83− root.resetAuthentication();
84 }
51 }
85
52
86 function showFakePassword() {
53 function showFakePassword() {
54+ promptList.interactive = false;
87− passwordInput.showFakePassword();
55+ promptList.showFakePassword();
88 }
56 }
89
57
90− QtObject {
91− id: d
92−
93− function checkIfPromptless() {
94− if (!waiting && !wasPrompted) {
95− passwordInput.isPrompt = false;
96− passwordInput.text = root.locked ? i18n.tr("Retry")
97− : i18n.tr("Log In")
98− }
99− }
100− }
101−
102− onWaitingChanged: d.checkIfPromptless()
103− onLockedChanged: d.checkIfPromptless()
104−
105 theme: ThemeSettings {
58 theme: ThemeSettings {
106 name: "Ubuntu.Components.Themes.Ambiance"
59 name: "Ubuntu.Components.Themes.Ambiance"
107 }
60 }
@@ -133,7 +86,8 @@ StyledItem {
133 rightMargin: units.gu(2)
86 rightMargin: units.gu(2)
134 }
87 }
135
88
136− height: root.highlightedHeight
89+ height: Math.max(units.gu(15), promptList.height + units.gu(8))
90+ Behavior on height { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
137 }
91 }
138
92
139 ListView {
93 ListView {
@@ -151,14 +105,8 @@ StyledItem {
151 interactive: count > 1
105 interactive: count > 1
152
106
153 readonly property bool movingInternally: moveTimer.running || userList.moving
107 readonly property bool movingInternally: moveTimer.running || userList.moving
154− onMovingInternallyChanged: {
155− if (!movingInternally) {
156− root.selected(currentIndex);
157− }
158− }
159
108
160 onCurrentIndexChanged: {
109 onCurrentIndexChanged: {
161− root.resetAuthentication();
162 moveTimer.start();
110 moveTimer.start();
163 }
111 }
164
112
@@ -310,64 +258,42 @@ StyledItem {
310 }
258 }
311 }
259 }
312
260
313− FadingLabel {
261+ PromptList {
314− id: infoLabel
262+ id: promptList
315− objectName: "infoLabel"
263+ objectName: "promptList"
316− anchors {
317− bottom: passwordInput.top
318− left: highlightItem.left
319− topMargin: units.gu(1)
320− bottomMargin: units.gu(1)
321− leftMargin: units.gu(2)
322− rightMargin: units.gu(1)
323− }
324−
325− color: theme.palette.normal.raisedText
326− width: root.width - anchors.leftMargin - anchors.rightMargin
327− fontSize: "small"
328− textFormat: Text.StyledText
329−
330− opacity: (userList.movingInternally || text == "") ? 0 : 1
331− Behavior on opacity {
332− NumberAnimation { duration: 100 }
333− }
334− }
335−
336− GreeterPrompt {
337− id: passwordInput
338− objectName: "passwordInput"
339 anchors {
264 anchors {
340 bottom: highlightItem.bottom
265 bottom: highlightItem.bottom
341 horizontalCenter: highlightItem.horizontalCenter
266 horizontalCenter: highlightItem.horizontalCenter
342 margins: units.gu(2)
267 margins: units.gu(2)
343 }
268 }
344 width: highlightItem.width - anchors.margins * 2
269 width: highlightItem.width - anchors.margins * 2
345− opacity: userList.movingInternally ? 0 : 1
346−
347− activeFocusOnTab: true
348− isAlphanumeric: root.alphanumeric
349−
350− onClicked: root.tryToUnlock()
351− onResponded: root.responded(text)
352− onCanceled: root.selected(currentIndex)
353
270
354− Behavior on opacity {
271+ onClicked: {
355− NumberAnimation { duration: 100 }
272+ interactive = false;
273+ if (root.locked) {
274+ root.selected(currentIndex);
275+ } else {
276+ root.responded("");
277+ }
278+ }
279+ onResponded: {
280+ interactive = false;
281+ root.responded(text);
282+ }
283+ onCanceled: {
284+ interactive = false;
285+ root.selected(currentIndex);
356 }
286 }
357
287
358− WrongPasswordAnimation {
288+ Connections {
359− id: wrongPasswordAnimation
289+ target: LightDMService.prompts
360− objectName: "wrongPasswordAnimation"
290+ onModelReset: promptList.interactive = true
361− target: passwordInput
362 }
291 }
363 }
292 }
364
293
365− function resetAuthentication() {
294+ WrongPasswordAnimation {
366− if (!userList.currentItem) {
295+ id: wrongPasswordAnimation
367− return;
296+ objectName: "wrongPasswordAnimation"
368− }
297+ target: promptList
369− infoLabel.text = "";
370− passwordInput.reset();
371− root.wasPrompted = false;
372 }
298 }
373}
299}
qml/Greeter/NarrowView.qml+11−21
@@ -48,14 +48,6 @@ FocusScope {
48 signal tease()
48 signal tease()
49 signal emergencyCall()
49 signal emergencyCall()
50
50
51− function showMessage(html) {
52− loginList.showMessage(html);
53− }
54−
55− function showPrompt(text, isSecret, isDefaultPrompt) {
56− loginList.showPrompt(text, isSecret, isDefaultPrompt);
57− }
58−
59 function showLastChance() {
51 function showLastChance() {
60 /* TODO: when we finish support for resetting device after too many
52 /* TODO: when we finish support for resetting device after too many
61 failed logins, we should re-add this popup.
53 failed logins, we should re-add this popup.
@@ -75,10 +67,8 @@ FocusScope {
75 coverPage.hide();
67 coverPage.hide();
76 }
68 }
77
69
78− function notifyAuthenticationSucceeded(showFakePassword) {
70+ function showFakePassword() {
79− if (showFakePassword) {
80− loginList.showFakePassword();
71+ loginList.showFakePassword();
81− }
82 }
72 }
83
73
84 function notifyAuthenticationFailed() {
74 function notifyAuthenticationFailed() {
@@ -89,11 +79,8 @@ FocusScope {
89 coverPage.showErrorMessage(msg);
79 coverPage.showErrorMessage(msg);
90 }
80 }
91
81
92− function reset(forceShow) {
82+ function forceShow() {
93− loginList.reset();
94− if (forceShow) {
95− coverPage.show();
83+ coverPage.show();
96− }
97 }
84 }
98
85
99 function tryToUnlock(toTheRight) {
86 function tryToUnlock(toTheRight) {
@@ -120,6 +107,7 @@ FocusScope {
120 objectName: "lockscreen"
107 objectName: "lockscreen"
121 anchors.fill: parent
108 anchors.fill: parent
122 shown: false
109 shown: false
110+ opacity: 0
123
111
124 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
112 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
125 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
113 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
@@ -195,10 +183,12 @@ FocusScope {
195 onClicked: hide()
183 onClicked: hide()
196
184
197 onShowProgressChanged: {
185 onShowProgressChanged: {
198− if (showProgress === 1) {
186+ if (showProgress === 0) {
199− loginList.reset();
187+ if (lockscreen.shown) {
200− } else if (showProgress === 0) {
201− loginList.tryToUnlock();
188+ loginList.tryToUnlock();
189+ } else {
190+ root.responded("");
191+ }
202 }
192 }
203 }
193 }
204
194
qml/Greeter/PromptList.qmlnew file+148
@@ -0,0 +1,148 @@
1+/*
2+ * Copyright (C) 2017 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+import "../Components"
20+import "." 0.1
21+
22+FocusScope {
23+ id: root
24+ height: childrenRect.height
25+
26+ property bool alphanumeric: true
27+ property bool interactive: true
28+
29+ signal responded(string text)
30+ signal clicked()
31+ signal canceled()
32+
33+ function showFakePassword() {
34+ for (var i = 0; i < repeater.count; i++) {
35+ var item = repeater.itemAt(i).item;
36+ if (item.isPrompt) {
37+ item.showFakePassword();
38+ }
39+ }
40+ }
41+
42+ QtObject {
43+ id: d
44+
45+ function sendResponse() {
46+ for (var i = 0; i < repeater.count; i++) {
47+ var item = repeater.itemAt(i).item;
48+ if (item.isPrompt) {
49+ root.responded(item.enteredText);
50+ }
51+ }
52+ }
53+ }
54+
55+ Column {
56+ width: parent.width
57+ spacing: units.gu(0.5)
58+
59+ Repeater {
60+ id: repeater
61+ model: LightDMService.prompts
62+
63+ delegate: Loader {
64+ id: loader
65+
66+ readonly property bool isLabel: model.type == LightDMService.prompts.Message ||
67+ model.type == LightDMService.prompts.Error
68+ readonly property var modelData: model
69+
70+ sourceComponent: isLabel ? infoLabel : greeterPrompt
71+
72+ onLoaded: {
73+ for (var i = 0; i < repeater.count; i++) {
74+ var item = repeater.itemAt(i);
75+ if (item && !item.isLabel) {
76+ item.focus = true;
77+ break;
78+ }
79+ }
80+ loader.item.opacity = 1;
81+ }
82+
83+ Binding {
84+ target: loader.item
85+ property: "model"
86+ value: loader.modelData
87+ }
88+ }
89+ }
90+ }
91+
92+ Component {
93+ id: infoLabel
94+
95+ FadingLabel {
96+ objectName: "infoLabel" + model.index
97+ width: root.width
98+
99+ property var model
100+ readonly property bool isPrompt: false
101+
102+ color: model.type === LightDMService.prompts.Message ? theme.palette.normal.raisedText
103+ : theme.palette.normal.negative
104+ fontSize: "small"
105+ textFormat: Text.PlainText
106+ text: model.text
107+
108+ Behavior on opacity { UbuntuNumberAnimation {} }
109+ opacity: 0
110+ }
111+ }
112+
113+ Component {
114+ id: greeterPrompt
115+
116+ GreeterPrompt {
117+ objectName: "greeterPrompt" + model.index
118+ width: root.width
119+
120+ property var model
121+
122+ interactive: root.interactive
123+ isAlphanumeric: model.text !== "" || root.alphanumeric
124+ isPrompt: model.type !== LightDMService.prompts.Button
125+ isSecret: model.type === LightDMService.prompts.Secret
126+ text: model.text ? model.text : (isAlphanumeric ? i18n.tr("Passphrase") : i18n.tr("Passcode"))
127+
128+ onClicked: root.clicked()
129+ onAccepted: {
130+ // If there is another GreeterPrompt, focus it.
131+ for (var i = model.index + 1; i < repeater.count; i++) {
132+ var item = repeater.itemAt(i).item;
133+ if (item.isPrompt) {
134+ item.forceActiveFocus();
135+ return;
136+ }
137+ }
138+
139+ // Nope we're the last one; just send our response.
140+ d.sendResponse();
141+ }
142+ onCanceled: root.canceled()
143+
144+ Behavior on opacity { UbuntuNumberAnimation {} }
145+ opacity: 0
146+ }
147+ }
148+}
qml/Greeter/WideView.qml+13−19
@@ -52,16 +52,8 @@ FocusScope {
52 loginList.showError();
52 loginList.showError();
53 }
53 }
54
54
55− function reset(forceShow) {
55+ function forceShow() {
56− loginList.reset();
56+ // Nothing to do, we are always fully shown
57− }
58−
59− function showMessage(html) {
60− loginList.showMessage(html);
61− }
62−
63− function showPrompt(text, isSecret, isDefaultPrompt) {
64− loginList.showPrompt(text, isSecret, isDefaultPrompt);
65 }
57 }
66
58
67 function tryToUnlock(toTheRight) {
59 function tryToUnlock(toTheRight) {
@@ -84,10 +76,8 @@ FocusScope {
84 coverPage.hide();
76 coverPage.hide();
85 }
77 }
86
78
87− function notifyAuthenticationSucceeded(showFakePassword) {
79+ function showFakePassword() {
88− if (showFakePassword) {
89− loginList.showFakePassword();
80+ loginList.showFakePassword();
90− }
91 }
81 }
92
82
93 function showLastChance() {
83 function showLastChance() {
@@ -140,13 +130,17 @@ FocusScope {
140
130
141 model: root.userModel
131 model: root.userModel
142 onResponded: root.responded(response)
132 onResponded: root.responded(response)
143− onSelected: {
144− root.selected(index)
133+ onSelected: root.selected(index)
145− currentSession = LightDMService.users.data(index, LightDMService.userRoles.SessionRole);
146− }
147 onSessionChooserButtonClicked: parent.state = "SessionsList"
134 onSessionChooserButtonClicked: parent.state = "SessionsList"
135+ onCurrentIndexChanged: setCurrentSession()
148
136
149 Keys.forwardTo: [sessionChooserLoader.item]
137 Keys.forwardTo: [sessionChooserLoader.item]
138+
139+ Component.onCompleted: setCurrentSession()
140+
141+ function setCurrentSession() {
142+ currentSession = LightDMService.users.data(currentIndex, LightDMService.userRoles.SessionRole);
143+ }
150 }
144 }
151
145
152 Loader {
146 Loader {
@@ -174,7 +168,7 @@ FocusScope {
174 onSessionSelected: loginList.currentSession = sessionKey
168 onSessionSelected: loginList.currentSession = sessionKey
175 onShowLoginList: {
169 onShowLoginList: {
176 coverPage.state = "LoginList"
170 coverPage.state = "LoginList"
177− loginList.passwordInput.forceActiveFocus();
171+ loginList.tryToUnlock();
178 }
172 }
179 ignoreUnknownSignals: true
173 ignoreUnknownSignals: true
180 }
174 }
tests/mocks/AccountsService/CMakeLists.txt+1
@@ -7,6 +7,7 @@ include_directories(
7
7
8add_library(MockAccountsService-qml MODULE
8add_library(MockAccountsService-qml MODULE
9 ${CMAKE_SOURCE_DIR}/plugins/LightDM/Greeter.cpp
9 ${CMAKE_SOURCE_DIR}/plugins/LightDM/Greeter.cpp
10+ ${CMAKE_SOURCE_DIR}/plugins/LightDM/PromptsModel.cpp
10 ${CMAKE_SOURCE_DIR}/plugins/LightDM/UsersModel.cpp
11 ${CMAKE_SOURCE_DIR}/plugins/LightDM/UsersModel.cpp
11 ${CMAKE_SOURCE_DIR}/plugins/Utils/unitysortfilterproxymodelqml.cpp
12 ${CMAKE_SOURCE_DIR}/plugins/Utils/unitysortfilterproxymodelqml.cpp
12 AccountsService.cpp
13 AccountsService.cpp
tests/mocks/liblightdm/MockController.cpp−24
@@ -152,30 +152,6 @@ void MockController::setUserMode(const QString &userMode)
152 }
152 }
153}
153}
154
154
155−QString MockController::sessionName() const
156−{
157− return QString("INVALID - Please read directly from the UserModel");
158−}
159−
160−void MockController::setSessionName(const QString &sessionName)
161−{
162− // Let the user model deal with this
163− Q_EMIT sessionNameChanged(sessionName, m_currentUsername);
164−}
165−
166−QString MockController::currentUsername() const
167−{
168− return m_currentUsername;
169−}
170−
171−void MockController::setCurrentUsername(const QString &username)
172−{
173− if (m_currentUsername != username) {
174− m_currentUsername = username;
175− Q_EMIT currentUsernameChanged();
176− }
177−}
178−
179QString MockController::sessionMode() const
155QString MockController::sessionMode() const
180{
156{
181 return m_sessionMode;
157 return m_sessionMode;
tests/mocks/liblightdm/MockController.h−15
@@ -38,11 +38,6 @@ class Q_DECL_EXPORT MockController : public QObject
38 // single, none, full
38 // single, none, full
39 Q_PROPERTY(QString sessionMode READ sessionMode WRITE setSessionMode NOTIFY sessionModeChanged)
39 Q_PROPERTY(QString sessionMode READ sessionMode WRITE setSessionMode NOTIFY sessionModeChanged)
40
40
41− // This would be best as a Q_INVOKABLE, but using a property allows for
42− // keeping the mock cleaner
43− Q_PROPERTY(QString sessionName READ sessionName WRITE setSessionName NOTIFY sessionNameChanged)
44− Q_PROPERTY(QString currentUsername READ currentUsername WRITE setCurrentUsername NOTIFY currentUsernameChanged)
45−
46 Q_PROPERTY(int numAvailableSessions READ numFullSessions CONSTANT)
41 Q_PROPERTY(int numAvailableSessions READ numFullSessions CONSTANT)
47 Q_PROPERTY(int numSessions READ numSessions WRITE setNumSessions NOTIFY numSessionsChanged)
42 Q_PROPERTY(int numSessions READ numSessions WRITE setNumSessions NOTIFY numSessionsChanged)
48
43
@@ -73,12 +68,6 @@ public:
73 QString sessionMode() const;
68 QString sessionMode() const;
74 void setSessionMode(const QString &sessionMode);
69 void setSessionMode(const QString &sessionMode);
75
70
76− QString sessionName() const;
77− void setSessionName(const QString &userIndex);
78−
79− QString currentUsername() const;
80− void setCurrentUsername(const QString &username);
81−
82 class SessionItem
71 class SessionItem
83 {
72 {
84 public:
73 public:
@@ -92,7 +81,6 @@ public:
92 void setNumSessions(int numSessions);
81 void setNumSessions(int numSessions);
93
82
94Q_SIGNALS:
83Q_SIGNALS:
95− void currentUsernameChanged();
96 void selectUserHintChanged();
84 void selectUserHintChanged();
97 void selectGuestHintChanged();
85 void selectGuestHintChanged();
98 void hasGuestAccountHintChanged();
86 void hasGuestAccountHintChanged();
@@ -101,13 +89,10 @@ Q_SIGNALS:
101 void userModeChanged();
89 void userModeChanged();
102 void sessionModeChanged();
90 void sessionModeChanged();
103 void numSessionsChanged();
91 void numSessionsChanged();
104− void sessionNameChanged(const QString &sessionName, const QString &username);
105
92
106private:
93private:
107 explicit MockController(QObject* parent=0);
94 explicit MockController(QObject* parent=0);
108
95
109− QString m_currentUsername;
110− QString m_sessionName;
111 QString m_selectUserHint;
96 QString m_selectUserHint;
112 bool m_selectGuestHint;
97 bool m_selectGuestHint;
113 bool m_hasGuestAccountHint;
98 bool m_hasGuestAccountHint;
tests/mocks/liblightdm/MockGreeter.cpp+53−7
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2014-2016 Canonical, Ltd.
2+ * Copyright (C) 2014-2017 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -29,12 +29,18 @@ public:
29 bool authenticated = false;
29 bool authenticated = false;
30 QString authenticationUser;
30 QString authenticationUser;
31 bool twoFactorDone = false;
31 bool twoFactorDone = false;
32+ QTimer authCallbackTimer;
32};
33};
33
34
34Greeter::Greeter(QObject *parent)
35Greeter::Greeter(QObject *parent)
35 : QObject(parent)
36 : QObject(parent)
36 , d_ptr(new GreeterPrivate)
37 , d_ptr(new GreeterPrivate)
37{
38{
39+ Q_D(Greeter);
40+
41+ d->authCallbackTimer.setSingleShot(true);
42+ connect(&d->authCallbackTimer, &QTimer::timeout,
43+ this, &QLightDM::Greeter::handleAuthenticate);
38}
44}
39
45
40Greeter::~Greeter()
46Greeter::~Greeter()
@@ -134,13 +140,19 @@ void Greeter::authenticate(const QString &username)
134 d->authenticated = false;
140 d->authenticated = false;
135 d->authenticationUser = username;
141 d->authenticationUser = username;
136 d->twoFactorDone = false;
142 d->twoFactorDone = false;
137− QTimer::singleShot(0, this, &Greeter::handleAuthenticate);
143+ d->authCallbackTimer.start();
138}
144}
139
145
140void Greeter::handleAuthenticate()
146void Greeter::handleAuthenticate()
141{
147{
142 Q_D(Greeter);
148 Q_D(Greeter);
143
149
150+ if (d->authenticated) {
151+ // This can happen from the guest authentication flow
152+ Q_EMIT authenticationComplete();
153+ return;
154+ }
155+
144 if (d->authenticationUser.isEmpty()) {
156 if (d->authenticationUser.isEmpty()) {
145 Q_EMIT showPrompt("Username: ", Greeter::PromptTypeQuestion);
157 Q_EMIT showPrompt("Username: ", Greeter::PromptTypeQuestion);
146 return;
158 return;
@@ -165,13 +177,24 @@ void Greeter::handleAuthenticate()
165 if (d->authenticationUser == "no-password") {
177 if (d->authenticationUser == "no-password") {
166 d->authenticated = true;
178 d->authenticated = true;
167 Q_EMIT authenticationComplete();
179 Q_EMIT authenticationComplete();
168− } else if (d->authenticationUser == "has-pin"){
180+ } else if (d->authenticationUser == "has-pin") {
169 Q_EMIT showPrompt("Password: ", Greeter::PromptTypeSecret);
181 Q_EMIT showPrompt("Password: ", Greeter::PromptTypeSecret);
170 } else if (d->authenticationUser == "auth-error") {
182 } else if (d->authenticationUser == "auth-error") {
171 d->authenticated = false;
183 d->authenticated = false;
172 Q_EMIT authenticationComplete();
184 Q_EMIT authenticationComplete();
173 } else if (d->authenticationUser == "different-prompt") {
185 } else if (d->authenticationUser == "different-prompt") {
174 Q_EMIT showPrompt("Secret word: ", Greeter::PromptTypeSecret);
186 Q_EMIT showPrompt("Secret word: ", Greeter::PromptTypeSecret);
187+ } else if (d->authenticationUser == "question-prompt") {
188+ Q_EMIT showPrompt("Favorite Color (blue): ", Greeter::PromptTypeQuestion);
189+ } else if (d->authenticationUser == "two-prompts") {
190+ Q_EMIT showPrompt("Favorite Color (blue):", Greeter::PromptTypeQuestion);
191+ Q_EMIT showPrompt("Password: ", Greeter::PromptTypeSecret);
192+ } else if (d->authenticationUser == "wacky-prompts") {
193+ Q_EMIT showMessage("First message", Greeter::MessageTypeInfo);
194+ Q_EMIT showPrompt("Favorite Color (blue)", Greeter::PromptTypeQuestion);
195+ Q_EMIT showMessage("Second message", Greeter::MessageTypeError);
196+ Q_EMIT showPrompt("Password: ", Greeter::PromptTypeSecret);
197+ Q_EMIT showMessage("Last message", Greeter::MessageTypeInfo);
175 } else {
198 } else {
176 Q_EMIT showPrompt("Password: ", Greeter::PromptTypeSecret);
199 Q_EMIT showPrompt("Password: ", Greeter::PromptTypeSecret);
177 }
200 }
@@ -184,7 +207,7 @@ void Greeter::authenticateAsGuest()
184 d->authenticated = true;
207 d->authenticated = true;
185 d->authenticationUser = QString(); // this is what the real liblightdm does
208 d->authenticationUser = QString(); // this is what the real liblightdm does
186 d->twoFactorDone = false;
209 d->twoFactorDone = false;
187− QTimer::singleShot(0, this, &Greeter::authenticationComplete);
210+ d->authCallbackTimer.start();
188}
211}
189
212
190void Greeter::authenticateAutologin()
213void Greeter::authenticateAutologin()
@@ -197,7 +220,10 @@ void Greeter::authenticateRemote(const QString &session, const QString &username
197}
220}
198
221
199void Greeter::cancelAuthentication()
222void Greeter::cancelAuthentication()
200−{}
223+{
224+ Q_D(Greeter);
225+ d->authCallbackTimer.stop();
226+}
201
227
202void Greeter::setLanguage (const QString &language)
228void Greeter::setLanguage (const QString &language)
203{
229{
@@ -231,9 +257,13 @@ void Greeter::respond(const QString &response)
231 return;
257 return;
232 }
258 }
233
259
234− if (d->authenticationUser == "no-response")
260+ if (d->authenticationUser == "no-response") {
261+ return;
262+ } else if (d->authenticationUser == "locked") {
263+ Q_EMIT showMessage("Account is locked", Greeter::MessageTypeError);
264+ sendAuthenticationComplete();
235 return;
265 return;
236− else if (d->authenticationUser == "two-factor") {
266+ } else if (d->authenticationUser == "two-factor") {
237 if (!d->twoFactorDone) {
267 if (!d->twoFactorDone) {
238 if (response == "password") {
268 if (response == "password") {
239 d->twoFactorDone = true;
269 d->twoFactorDone = true;
@@ -247,13 +277,29 @@ void Greeter::respond(const QString &response)
247 sendAuthenticationComplete();
277 sendAuthenticationComplete();
248 }
278 }
249 return;
279 return;
280+ } else if (d->authenticationUser == "two-prompts" || d->authenticationUser == "wacky-prompts") {
281+ if (!d->twoFactorDone) {
282+ d->authenticated = (response == "blue");
283+ d->twoFactorDone = true;
284+ } else {
285+ d->authenticated = d->authenticated && (response == "password");
286+ sendAuthenticationComplete();
287+ }
288+ return;
250 }
289 }
251
290
252 if (d->authenticationUser == "has-pin") {
291 if (d->authenticationUser == "has-pin") {
253 d->authenticated = (response == "1234");
292 d->authenticated = (response == "1234");
293+ } else if (d->authenticationUser == "question-prompt") {
294+ d->authenticated = (response == "blue");
254 } else {
295 } else {
255 d->authenticated = (response == "password");
296 d->authenticated = (response == "password");
256 }
297 }
298+
299+ if (d->authenticationUser == "info-after-login" && d->authenticated) {
300+ Q_EMIT showMessage("Congratulations on logging in!", Greeter::MessageTypeInfo);
301+ }
302+
257 sendAuthenticationComplete();
303 sendAuthenticationComplete();
258}
304}
259
305
tests/mocks/liblightdm/MockUsersModel.cpp+8−4
@@ -73,8 +73,6 @@ UsersModel::UsersModel(QObject *parent)
73 this, &UsersModel::resetEntries);
73 this, &UsersModel::resetEntries);
74 connect(MockController::instance(), &MockController::userModeChanged,
74 connect(MockController::instance(), &MockController::userModeChanged,
75 this, &UsersModel::resetEntries);
75 this, &UsersModel::resetEntries);
76− connect(MockController::instance(), &MockController::sessionNameChanged,
77− this, &UsersModel::setCurrentSessionName);
78 resetEntries();
76 resetEntries();
79}
77}
80
78
@@ -177,18 +175,24 @@ void UsersModel::resetEntries()
177 { "has-password", "Has Password", 0, 0, false, false, "ubuntu", 0 },
175 { "has-password", "Has Password", 0, 0, false, false, "ubuntu", 0 },
178 { "has-pin", "Has PIN", 0, 0, false, false, "ubuntu", 0 },
176 { "has-pin", "Has PIN", 0, 0, false, false, "ubuntu", 0 },
179 { "html-info-prompt", "HTML Info Prompt", 0, 0, false, false, "ubuntu", 0 },
177 { "html-info-prompt", "HTML Info Prompt", 0, 0, false, false, "ubuntu", 0 },
178+ { "info-after-login", "Info After Login", 0, 0, false, false, "ubuntu", 0 },
180 { "info-prompt", "Info Prompt", 0, 0, false, false, "ubuntu", 0 },
179 { "info-prompt", "Info Prompt", 0, 0, false, false, "ubuntu", 0 },
180+ { "wacky-prompts", "Interspersed Prompts", 0, 0, false, false, "ubuntu", 0 },
181+ { "invalid-session", "Invalid Session", 0, 0, false, false, "invalid", 0 },
182+ { "locked", "Locked", 0, 0, false, false, "ubuntu", 0 },
181 { "long-info-prompt", "Long Info Prompt", 0, 0, false, false, "ubuntu", 0 },
183 { "long-info-prompt", "Long Info Prompt", 0, 0, false, false, "ubuntu", 0 },
182 { "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 },
184 { "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 },
183 { "multi-info-prompt", "Multi Info Prompt", 0, 0, false, false, "ubuntu", 0 },
185 { "multi-info-prompt", "Multi Info Prompt", 0, 0, false, false, "ubuntu", 0 },
184 { "no-background", "No Background", "", 0, false, false, "ubuntu", 0 },
186 { "no-background", "No Background", "", 0, false, false, "ubuntu", 0 },
185 { "no-password", "No Password", 0, 0, false, false, "ubuntu", 0 },
187 { "no-password", "No Password", 0, 0, false, false, "ubuntu", 0 },
186 { "no-response", "No Response", 0, 0, false, false, "ubuntu", 0 },
188 { "no-response", "No Response", 0, 0, false, false, "ubuntu", 0 },
189+ { "question-prompt", "Question Prompt", 0, 0, false, false, "ubuntu", 0 },
187 { "two-factor", "Two Factor", 0, 0, false, false, "ubuntu", 0 },
190 { "two-factor", "Two Factor", 0, 0, false, false, "ubuntu", 0 },
188− { "unicode", "가나다라마", 0, 0, false, false, "ubuntu", 0 },
191+ { "two-prompts", "Two Prompts", 0, 0, false, false, "ubuntu", 0 },
189 // white and black are a bit redundant, but useful for manually testing if UI is still readable
192 // white and black are a bit redundant, but useful for manually testing if UI is still readable
190 { "white-background", "White Background", "#ffffff", 0, false, false, "ubuntu", 0 },
193 { "white-background", "White Background", "#ffffff", 0, false, false, "ubuntu", 0 },
191− { "wide-info-prompt", "Wide Info Prompt", 0, 0, false, false, "ubuntu", 0 }
194+ { "wide-info-prompt", "Wide Info Prompt", 0, 0, false, false, "ubuntu", 0 },
195+ { "unicode", "가나다라마", 0, 0, false, false, "ubuntu", 0 }
192 };
196 };
193 }
197 }
194
198
tests/plugins/LightDM/IntegratedLightDM/CMakeLists.txt+29
@@ -7,6 +7,7 @@ include_directories(
7add_executable(GreeterDBusTestExec
7add_executable(GreeterDBusTestExec
8 dbus.cpp
8 dbus.cpp
9 ${CMAKE_SOURCE_DIR}/plugins/LightDM/Greeter.cpp
9 ${CMAKE_SOURCE_DIR}/plugins/LightDM/Greeter.cpp
10+ ${CMAKE_SOURCE_DIR}/plugins/LightDM/PromptsModel.cpp
10 )
11 )
11# This add_dependencies is needed since we're linking with with -L and -l below
12# This add_dependencies is needed since we're linking with with -L and -l below
12# make seems to work but ninja breaks without it
13# make seems to work but ninja breaks without it
@@ -76,6 +77,7 @@ add_unity8_uitest(GreeterSessions GreeterSessionsModelTestExec
76add_executable(GreeterUsersModelTestExec
77add_executable(GreeterUsersModelTestExec
77 usersmodel.cpp
78 usersmodel.cpp
78 ${CMAKE_SOURCE_DIR}/plugins/LightDM/Greeter.cpp
79 ${CMAKE_SOURCE_DIR}/plugins/LightDM/Greeter.cpp
80+ ${CMAKE_SOURCE_DIR}/plugins/LightDM/PromptsModel.cpp
79 ${CMAKE_SOURCE_DIR}/plugins/LightDM/UsersModel.cpp
81 ${CMAKE_SOURCE_DIR}/plugins/LightDM/UsersModel.cpp
80 ${CMAKE_SOURCE_DIR}/plugins/Utils/unitysortfilterproxymodelqml.cpp
82 ${CMAKE_SOURCE_DIR}/plugins/Utils/unitysortfilterproxymodelqml.cpp
81 )
83 )
@@ -99,6 +101,33 @@ add_unity8_uitest(GreeterUsers GreeterUsersModelTestExec
99 DEPENDS MockLightDM
101 DEPENDS MockLightDM
100 )
102 )
101
103
104+# PromptsModelTest
105+add_executable(GreeterPromptsModelTestExec
106+ promptsmodel.cpp
107+ ${CMAKE_SOURCE_DIR}/plugins/LightDM/Greeter.cpp
108+ ${CMAKE_SOURCE_DIR}/plugins/LightDM/PromptsModel.cpp
109+ ${CMAKE_SOURCE_DIR}/plugins/Utils/unitysortfilterproxymodelqml.cpp
110+ )
111+# This add_dependencies is needed since we're linking with with -L and -l below
112+# make seems to work but ninja breaks without it
113+add_dependencies(GreeterPromptsModelTestExec MockLightDM)
114+qt5_use_modules(GreeterPromptsModelTestExec Core Test)
115+target_link_libraries(GreeterPromptsModelTestExec
116+ MockLightDM
117+ )
118+target_include_directories(GreeterPromptsModelTestExec PUBLIC
119+ ${CMAKE_SOURCE_DIR}/plugins/LightDM
120+ ${CMAKE_SOURCE_DIR}/plugins/Utils
121+ ${CMAKE_SOURCE_DIR}/tests/mocks/liblightdm
122+ )
123+install(TARGETS GreeterPromptsModelTestExec
124+ DESTINATION "${SHELL_PRIVATE_LIBDIR}/tests/plugins/LightDM/IntegratedLightDM"
125+ )
126+add_unity8_uitest(GreeterPrompts GreeterPromptsModelTestExec
127+ DEPENDS MockLightDM
128+ )
129+
130+# IntegratedTest
102add_executable(GreeterIntegratedTestExec
131add_executable(GreeterIntegratedTestExec
103 integrated.cpp
132 integrated.cpp
104 )
133 )
tests/plugins/LightDM/IntegratedLightDM/dbus.cpp+6−11
@@ -138,10 +138,10 @@ private Q_SLOTS:
138 {
138 {
139 QSignalSpy spy(this, &GreeterDBusTest::PropertiesChangedRelay);
139 QSignalSpy spy(this, &GreeterDBusTest::PropertiesChangedRelay);
140 greeter->authenticate("has-password");
140 greeter->authenticate("has-password");
141− spy.wait();
142
141
143− QVERIFY(spy.count() > 0);
142+ QTRY_COMPARE(spy.count(), 2); // EntryIsLocked then ActiveEntry
143+
144− QList<QVariant> arguments = spy.takeFirst();
144+ QList<QVariant> arguments = spy[1];
145 QVERIFY(arguments.at(0).toString() == "com.canonical.UnityGreeter.List");
145 QVERIFY(arguments.at(0).toString() == "com.canonical.UnityGreeter.List");
146 QVERIFY(arguments.at(1).toMap().contains("ActiveEntry"));
146 QVERIFY(arguments.at(1).toMap().contains("ActiveEntry"));
147 QVERIFY(arguments.at(1).toMap()["ActiveEntry"] == "has-password");
147 QVERIFY(arguments.at(1).toMap()["ActiveEntry"] == "has-password");
@@ -163,15 +163,10 @@ private Q_SLOTS:
163 QSignalSpy spy(this, &GreeterDBusTest::PropertiesChangedRelay);
163 QSignalSpy spy(this, &GreeterDBusTest::PropertiesChangedRelay);
164 greeter->authenticate("no-password");
164 greeter->authenticate("no-password");
165
165
166− // Two property changed signals will be emitted, one for the IsLocked
166+ // EntryIsLocked=true, ActiveEntry, then EntryIsLocked=false
167− // property, one for the ActiveEntry; the first will be IsLocked.
168− spy.wait();
169− if (spy.count() < 2) {
170− spy.wait();
171− }
172− QCOMPARE(spy.count(), 2);
167+ QTRY_COMPARE(spy.count(), 3);
173
168
174− QList<QVariant> arguments = spy.takeLast();
169+ QList<QVariant> arguments = spy[2];
175 QVERIFY(arguments.at(0).toString() == "com.canonical.UnityGreeter.List");
170 QVERIFY(arguments.at(0).toString() == "com.canonical.UnityGreeter.List");
176 QVERIFY(arguments.at(1).toMap().contains("EntryIsLocked"));
171 QVERIFY(arguments.at(1).toMap().contains("EntryIsLocked"));
177 QVERIFY(arguments.at(1).toMap()["EntryIsLocked"] == false);
172 QVERIFY(arguments.at(1).toMap()["EntryIsLocked"] == false);
tests/plugins/LightDM/IntegratedLightDM/promptsmodel.cppnew file+175
@@ -0,0 +1,175 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "Greeter.h"
18+#include "PromptsModel.h"
19+
20+#include <QtTest>
21+
22+class GreeterPromptsModelTest : public QObject
23+{
24+ Q_OBJECT
25+
26+private:
27+ class PromptInfo {
28+ public:
29+ PromptInfo(PromptsModel::PromptType t, const QString &p)
30+ : type(t), text(p) {}
31+ PromptsModel::PromptType type;
32+ QString text;
33+ };
34+
35+ void selectUser(const QString &user)
36+ {
37+ Greeter::instance()->authenticate(user);
38+ QCOMPARE(Greeter::instance()->authenticationUser(), user);
39+ }
40+
41+ void respond(const QString &response, bool authenticated)
42+ {
43+ Greeter::instance()->respond(response);
44+ QCOMPARE(Greeter::instance()->isAuthenticated(), authenticated);
45+ }
46+
47+ void comparePrompts(const QList<PromptInfo> &expected)
48+ {
49+ QTRY_COMPARE(prompts->rowCount(), expected.size());
50+ for (int i = 0; i < prompts->rowCount(); i++) {
51+ QCOMPARE(prompts->data(prompts->index(i, 0), PromptsModel::TypeRole).toInt(), (int)expected[i].type);
52+ QCOMPARE(prompts->data(prompts->index(i, 0), PromptsModel::TextRole).toString(), expected[i].text);
53+ }
54+ }
55+
56+private Q_SLOTS:
57+
58+ void initTestCase()
59+ {
60+ prompts = Greeter::instance()->promptsModel();
61+ QVERIFY(prompts);
62+ }
63+
64+ void init()
65+ {
66+ QCOMPARE(prompts->rowCount(), 0);
67+ }
68+
69+ void cleanup()
70+ {
71+ prompts->clear();
72+ }
73+
74+ void testSimpleFailure()
75+ {
76+ selectUser("has-password");
77+ comparePrompts({{PromptsModel::Secret, ""}});
78+ respond("nope", false);
79+ comparePrompts({{PromptsModel::Secret, ""}}); // prompts don't change immediately
80+ selectUser("has-password");
81+ comparePrompts({{PromptsModel::Error, "Invalid password, please try again"},
82+ {PromptsModel::Secret, ""}});
83+ }
84+
85+ void testSimpleSuccess()
86+ {
87+ selectUser("has-password");
88+ comparePrompts({{PromptsModel::Secret, ""}});
89+ respond("password", true);
90+ comparePrompts({{PromptsModel::Secret, ""}}); // prompts don't change
91+ }
92+
93+ void testHasPassword()
94+ {
95+ selectUser("has-password");
96+ comparePrompts({{PromptsModel::Secret, ""}});
97+ }
98+
99+ void testDifferentPrompt()
100+ {
101+ selectUser("different-prompt");
102+ comparePrompts({{PromptsModel::Secret, "Secret word"}});
103+ }
104+
105+ void testNoPassword()
106+ {
107+ selectUser("no-password");
108+ comparePrompts({{PromptsModel::Button, "Log In"}});
109+ }
110+
111+ void testAuthError()
112+ {
113+ selectUser("auth-error");
114+ comparePrompts({{PromptsModel::Error, "Failed to authenticate"},
115+ {PromptsModel::Button, "Retry"}});
116+ }
117+
118+ void testTwoFactor()
119+ {
120+ selectUser("two-factor");
121+ comparePrompts({{PromptsModel::Secret, ""}});
122+ respond("password", false);
123+ comparePrompts({{PromptsModel::Question, "otp"}});
124+ }
125+
126+ void testTwoPrompts()
127+ {
128+ selectUser("two-prompts");
129+ comparePrompts({{PromptsModel::Question, "Favorite Color (blue)"},
130+ {PromptsModel::Secret, ""}});
131+ }
132+
133+ void testWackyPrompts()
134+ {
135+ selectUser("wacky-prompts");
136+ comparePrompts({{PromptsModel::Message, "First message"},
137+ {PromptsModel::Question, "Favorite Color (blue)"},
138+ {PromptsModel::Error, "Second message"},
139+ {PromptsModel::Secret, ""},
140+ {PromptsModel::Message, "Last message"}});
141+ }
142+
143+ void testHtmlInfoPrompts()
144+ {
145+ selectUser("html-info-prompt");
146+ comparePrompts({{PromptsModel::Message, "<b>&</b>"},
147+ {PromptsModel::Secret, ""}});
148+ }
149+
150+ void testInfoAfterLogin()
151+ {
152+ selectUser("info-after-login");
153+ comparePrompts({{PromptsModel::Secret, ""}});
154+ respond("password", true);
155+ comparePrompts({{PromptsModel::Message, "Congratulations on logging in!"},
156+ {PromptsModel::Button, "Log In"}});
157+ }
158+
159+ void testLocked()
160+ {
161+ selectUser("locked");
162+ comparePrompts({{PromptsModel::Secret, ""}});
163+ respond("nope", false);
164+ selectUser("locked");
165+ comparePrompts({{PromptsModel::Error, "Account is locked"},
166+ {PromptsModel::Secret, ""}});
167+ }
168+
169+private:
170+ PromptsModel *prompts;
171+};
172+
173+QTEST_GUILESS_MAIN(GreeterPromptsModelTest)
174+
175+#include "promptsmodel.moc"
tests/qmltests/Greeter/TestView.qml+6−16
@@ -46,24 +46,14 @@ Item {
46 signal tease()
46 signal tease()
47 signal emergencyCall()
47 signal emergencyCall()
48
48
49− signal _showMessageCalled(string html)
50− signal _showPromptCalled(string text, bool isSecret, bool isDefaultPrompt)
51 signal _showLastChanceCalled()
49 signal _showLastChanceCalled()
52 signal _hideCalled()
50 signal _hideCalled()
53− signal _notifyAuthenticationSucceededCalled(bool showFakePassword)
51+ signal _showFakePasswordCalled()
54 signal _notifyAuthenticationFailedCalled()
52 signal _notifyAuthenticationFailedCalled()
55 signal _showErrorMessageCalled(string msg)
53 signal _showErrorMessageCalled(string msg)
56− signal _resetCalled(bool forceShow)
54+ signal _forceShowCalled()
57 signal _tryToUnlockCalled(bool toTheRight)
55 signal _tryToUnlockCalled(bool toTheRight)
58
56
59− function showMessage(html) {
60− _showMessageCalled(html);
61− }
62−
63− function showPrompt(text, isSecret, isDefaultPrompt) {
64− _showPromptCalled(text, isSecret, isDefaultPrompt);
65− }
66−
67 function showLastChance() {
57 function showLastChance() {
68 _showLastChanceCalled();
58 _showLastChanceCalled();
69 }
59 }
@@ -74,8 +64,8 @@ Item {
74 _fullyShown = false;
64 _fullyShown = false;
75 }
65 }
76
66
77− function notifyAuthenticationSucceeded(showFakePassword) {
67+ function showFakePassword() {
78− _notifyAuthenticationSucceededCalled(showFakePassword);
68+ _showFakePasswordCalled();
79 }
69 }
80
70
81 function notifyAuthenticationFailed() {
71 function notifyAuthenticationFailed() {
@@ -86,8 +76,8 @@ Item {
86 _showErrorMessageCalled(msg);
76 _showErrorMessageCalled(msg);
87 }
77 }
88
78
89− function reset(forceShow) {
79+ function forceShow() {
90− _resetCalled(forceShow);
80+ _forceShowCalled();
91 }
81 }
92
82
93 function tryToUnlock(toTheRight) {
83 function tryToUnlock(toTheRight) {
tests/qmltests/Greeter/tst_Greeter.qml+21−101
@@ -75,6 +75,12 @@ Item {
75 signalName: "activeChanged"
75 signalName: "activeChanged"
76 }
76 }
77
77
78+ SignalSpy {
79+ id: authStartedSpy
80+ target: LightDM.Greeter
81+ signalName: "authenticationStarted"
82+ }
83+
78 GSettings {
84 GSettings {
79 id: greeterSettings
85 id: greeterSettings
80 schema.id: "com.canonical.Unity8.Greeter"
86 schema.id: "com.canonical.Unity8.Greeter"
@@ -88,18 +94,6 @@ Item {
88 property Item greeter: loader.status === Loader.Ready ? loader.item : null
94 property Item greeter: loader.status === Loader.Ready ? loader.item : null
89 property Item view
95 property Item view
90
96
91− SignalSpy {
92− id: viewShowMessageSpy
93− target: testCase.view
94− signalName: "_showMessageCalled"
95− }
96−
97− SignalSpy {
98− id: viewShowPromptSpy
99− target: testCase.view
100− signalName: "_showPromptCalled"
101− }
102−
103 SignalSpy {
97 SignalSpy {
104 id: viewShowLastChanceSpy
98 id: viewShowLastChanceSpy
105 target: testCase.view
99 target: testCase.view
@@ -113,9 +107,9 @@ Item {
113 }
107 }
114
108
115 SignalSpy {
109 SignalSpy {
116− id: viewAuthenticationSucceededSpy
110+ id: viewShowFakePasswordSpy
117 target: testCase.view
111 target: testCase.view
118− signalName: "_notifyAuthenticationSucceededCalled"
112+ signalName: "_showFakePasswordCalled"
119 }
113 }
120
114
121 SignalSpy {
115 SignalSpy {
@@ -131,9 +125,9 @@ Item {
131 }
125 }
132
126
133 SignalSpy {
127 SignalSpy {
134− id: viewResetSpy
128+ id: viewForceShowSpy
135 target: testCase.view
129 target: testCase.view
136− signalName: "_resetCalled"
130+ signalName: "_forceShowCalled"
137 }
131 }
138
132
139 SignalSpy {
133 SignalSpy {
@@ -153,16 +147,15 @@ Item {
153 Biometryd.available = true;
147 Biometryd.available = true;
154 AccountsService.enableFingerprintIdentification = true;
148 AccountsService.enableFingerprintIdentification = true;
155 AccountsService.failedFingerprintLogins = 0;
149 AccountsService.failedFingerprintLogins = 0;
156− viewShowMessageSpy.clear();
157− viewShowPromptSpy.clear();
158 viewShowLastChanceSpy.clear();
150 viewShowLastChanceSpy.clear();
159 viewHideSpy.clear();
151 viewHideSpy.clear();
160− viewAuthenticationSucceededSpy.clear();
152+ viewShowFakePasswordSpy.clear();
161 viewAuthenticationFailedSpy.clear();
153 viewAuthenticationFailedSpy.clear();
162 viewShowErrorMessageSpy.clear();
154 viewShowErrorMessageSpy.clear();
163− viewResetSpy.clear();
155+ viewForceShowSpy.clear();
164 viewTryToUnlockSpy.clear();
156 viewTryToUnlockSpy.clear();
165 resetLoader();
157 resetLoader();
158+ authStartedSpy.clear();
166 }
159 }
167
160
168 function resetLoader() {
161 function resetLoader() {
@@ -210,7 +203,6 @@ Item {
210
203
211 function verifyLoggedIn() {
204 function verifyLoggedIn() {
212 tryCompare(sessionStartedSpy, "count", 1);
205 tryCompare(sessionStartedSpy, "count", 1);
213− verify(viewAuthenticationSucceededSpy.count > 0);
214 compare(LightDM.Greeter.authenticated, true);
206 compare(LightDM.Greeter.authenticated, true);
215 compare(greeter.shown, false);
207 compare(greeter.shown, false);
216 }
208 }
@@ -225,107 +217,52 @@ Item {
225
217
226 function test_unlockPass() {
218 function test_unlockPass() {
227 selectUser("has-password");
219 selectUser("has-password");
228− tryCompare(viewShowPromptSpy, "count", 1);
229− compare(viewShowPromptSpy.signalArguments[0][0], "Password");
230− compare(viewShowPromptSpy.signalArguments[0][1], true);
231− compare(viewShowPromptSpy.signalArguments[0][2], true);
232−
233 view.responded("password");
220 view.responded("password");
234 verifyLoggedIn();
221 verifyLoggedIn();
235 }
222 }
236
223
237 function test_unlockFail() {
224 function test_unlockFail() {
238 selectUser("has-password");
225 selectUser("has-password");
239− tryCompare(viewShowPromptSpy, "count", 1);
226+ tryCompare(authStartedSpy, "count", 1);
240− compare(viewShowPromptSpy.signalArguments[0][0], "Password");
241− compare(viewShowPromptSpy.signalArguments[0][1], true);
242− compare(viewShowPromptSpy.signalArguments[0][2], true);
243
227
244 view.responded("wr0ng p4ssw0rd");
228 view.responded("wr0ng p4ssw0rd");
245 tryCompare(viewAuthenticationFailedSpy, "count", 1);
229 tryCompare(viewAuthenticationFailedSpy, "count", 1);
246
230
247− tryCompare(viewShowPromptSpy, "count", 2);
231+ tryCompare(authStartedSpy, "count", 2);
248− compare(viewShowPromptSpy.signalArguments[0][0], "Password");
249− compare(viewShowPromptSpy.signalArguments[0][1], true);
250− compare(viewShowPromptSpy.signalArguments[0][2], true);
251 }
232 }
252
233
253 function test_promptless() {
234 function test_promptless() {
254 selectUser("no-password");
235 selectUser("no-password");
255 tryCompare(view, "locked", false);
236 tryCompare(view, "locked", false);
256− compare(viewShowPromptSpy.count, 0);
257 compare(viewHideSpy.count, 0);
237 compare(viewHideSpy.count, 0);
258 }
238 }
259
239
260 function test_twoFactorPass() {
240 function test_twoFactorPass() {
261 selectUser("two-factor");
241 selectUser("two-factor");
262− tryCompare(viewShowPromptSpy, "count", 1);
263− compare(viewShowPromptSpy.signalArguments[0][0], "Password");
264− compare(viewShowPromptSpy.signalArguments[0][1], true);
265− compare(viewShowPromptSpy.signalArguments[0][2], true);
266−
267 view.responded("password");
242 view.responded("password");
268− tryCompare(viewShowPromptSpy, "count", 2);
269− compare(viewShowPromptSpy.signalArguments[1][0], "otp");
270− compare(viewShowPromptSpy.signalArguments[1][1], false);
271− compare(viewShowPromptSpy.signalArguments[1][2], false);
272−
273 view.responded("otp");
243 view.responded("otp");
274 verifyLoggedIn();
244 verifyLoggedIn();
275 }
245 }
276
246
277 function test_twoFactorFailOnFirst() {
247 function test_twoFactorFailOnFirst() {
278 selectUser("two-factor");
248 selectUser("two-factor");
279− tryCompare(viewShowPromptSpy, "count", 1);
249+ tryCompare(authStartedSpy, "count", 1);
280− compare(viewShowPromptSpy.signalArguments[0][0], "Password");
281− compare(viewShowPromptSpy.signalArguments[0][1], true);
282− compare(viewShowPromptSpy.signalArguments[0][2], true);
283
250
284 view.responded("wr0ng p4ssw0rd");
251 view.responded("wr0ng p4ssw0rd");
285 tryCompare(viewAuthenticationFailedSpy, "count", 1);
252 tryCompare(viewAuthenticationFailedSpy, "count", 1);
286
253
287− tryCompare(viewShowPromptSpy, "count", 2);
254+ tryCompare(authStartedSpy, "count", 2);
288− compare(viewShowPromptSpy.signalArguments[0][0], "Password");
289− compare(viewShowPromptSpy.signalArguments[0][1], true);
290− compare(viewShowPromptSpy.signalArguments[0][2], true);
291 }
255 }
292
256
293 function test_twoFactorFailOnSecond() {
257 function test_twoFactorFailOnSecond() {
294 selectUser("two-factor");
258 selectUser("two-factor");
295− tryCompare(viewShowPromptSpy, "count", 1);
259+ tryCompare(authStartedSpy, "count", 1);
296− compare(viewShowPromptSpy.signalArguments[0][0], "Password");
297− compare(viewShowPromptSpy.signalArguments[0][1], true);
298− compare(viewShowPromptSpy.signalArguments[0][2], true);
299
260
300 view.responded("password");
261 view.responded("password");
301− tryCompare(viewShowPromptSpy, "count", 2);
302− compare(viewShowPromptSpy.signalArguments[1][0], "otp");
303− compare(viewShowPromptSpy.signalArguments[1][1], false);
304− compare(viewShowPromptSpy.signalArguments[1][2], false);
305−
306 view.responded("wr0ng p4ssw0rd");
262 view.responded("wr0ng p4ssw0rd");
307 tryCompare(viewAuthenticationFailedSpy, "count", 1);
263 tryCompare(viewAuthenticationFailedSpy, "count", 1);
308
264
309− tryCompare(viewShowPromptSpy, "count", 3);
265+ tryCompare(authStartedSpy, "count", 2);
310− compare(viewShowPromptSpy.signalArguments[0][0], "Password");
311− compare(viewShowPromptSpy.signalArguments[0][1], true);
312− compare(viewShowPromptSpy.signalArguments[0][2], true);
313− }
314−
315− function test_htmlInfoPrompt() {
316− selectUser("html-info-prompt");
317− tryCompare(viewShowPromptSpy, "count", 1);
318− compare(viewShowMessageSpy.count, 1);
319− compare(viewShowMessageSpy.signalArguments[0][0], "&lt;b&gt;&amp;&lt;/b&gt;");
320− }
321−
322− function test_multiInfoPrompt() {
323− selectUser("multi-info-prompt");
324− tryCompare(viewShowPromptSpy, "count", 1);
325− compare(viewShowMessageSpy.count, 3);
326− compare(viewShowMessageSpy.signalArguments[0][0], "Welcome to Unity Greeter");
327− compare(viewShowMessageSpy.signalArguments[1][0], "<font color=\"#df382c\">This is an error</font>");
328− compare(viewShowMessageSpy.signalArguments[2][0], "You should have seen three messages");
329 }
266 }
330
267
331 function test_locked() {
268 function test_locked() {
@@ -435,18 +372,9 @@ Item {
435 compare(view.backgroundTopMargin, -5);
372 compare(view.backgroundTopMargin, -5);
436 }
373 }
437
374
438− function test_differentPrompt() {
439− selectUser("different-prompt");
440− tryCompare(viewShowPromptSpy, "count", 1);
441− compare(viewShowPromptSpy.signalArguments[0][0], "Secret word");
442− compare(viewShowPromptSpy.signalArguments[0][1], true);
443− compare(viewShowPromptSpy.signalArguments[0][2], false);
444− }
445−
446 function test_authError() {
375 function test_authError() {
447 selectUser("auth-error");
376 selectUser("auth-error");
448 tryCompare(viewAuthenticationFailedSpy, "count", 1);
377 tryCompare(viewAuthenticationFailedSpy, "count", 1);
449− compare(viewShowPromptSpy.count, 0);
450 compare(view.locked, true);
378 compare(view.locked, true);
451 }
379 }
452
380
@@ -464,7 +392,6 @@ Item {
464 greeter.failedLoginsDelayAttempts = 1;
392 greeter.failedLoginsDelayAttempts = 1;
465 greeter.failedLoginsDelayMinutes = 1;
393 greeter.failedLoginsDelayMinutes = 1;
466 selectUser("has-password");
394 selectUser("has-password");
467− tryCompare(viewShowPromptSpy, "count", 1);
468 compare(greeterSettings.lockedOutTime, 0);
395 compare(greeterSettings.lockedOutTime, 0);
469 view.responded("wr0ng p4ssw0rd");
396 view.responded("wr0ng p4ssw0rd");
470
397
@@ -498,7 +425,6 @@ Item {
498 greeter.failedLoginsDelayMinutes = 0.001; // make delay very short
425 greeter.failedLoginsDelayMinutes = 0.001; // make delay very short
499
426
500 selectUser("has-password");
427 selectUser("has-password");
501− tryCompare(viewShowPromptSpy, "count", 1);
502
428
503 compare(view.delayMinutes, 0);
429 compare(view.delayMinutes, 0);
504 view.responded("wr0ng p4ssw0rd");
430 view.responded("wr0ng p4ssw0rd");
@@ -558,16 +484,10 @@ Item {
558
484
559 function test_dbusShowGreeterFromShownState() {
485 function test_dbusShowGreeterFromShownState() {
560 selectUser("has-password");
486 selectUser("has-password");
561− compare(viewResetSpy.count, 1);
562− tryCompare(viewShowPromptSpy, "count", 1);
563−
564− viewResetSpy.clear();
565− viewShowPromptSpy.clear();
566
487
488+ compare(viewForceShowSpy.count, 0);
567 LightDM.Greeter.showGreeter();
489 LightDM.Greeter.showGreeter();
568− compare(viewResetSpy.count, 1);
490+ compare(viewForceShowSpy.count, 1);
569− compare(viewResetSpy.signalArguments[0][0], true);
570− tryCompare(viewShowPromptSpy, "count", 1);
571 }
491 }
572
492
573 function test_selectUserHint() {
493 function test_selectUserHint() {
tests/qmltests/Greeter/tst_NarrowView.qml+11−41
@@ -79,28 +79,16 @@ Item {
79 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
79 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
80 spacing: units.gu(1)
80 spacing: units.gu(1)
81
81
82− Row {
83− Button {
84− text: "Show Last Chance"
85− onClicked: loader.item.showLastChance()
86− }
87− }
88 Row {
82 Row {
89 Button {
83 Button {
90 text: "Hide"
84 text: "Hide"
91 onClicked: loader.item.hide()
85 onClicked: loader.item.hide()
92 }
86 }
93 }
87 }
94− Row {
95− Button {
96− text: "Reset"
97− onClicked: loader.item.reset()
98− }
99− }
100 Row {
88 Row {
101 Button {
89 Button {
102 text: "Show Message"
90 text: "Show Message"
103− onClicked: loader.item.showMessage(messageField.text)
91+ onClicked: LightDMService.prompts.append(messageField.text, LightDMService.prompts.Message)
104 }
92 }
105 TextField {
93 TextField {
106 id: messageField
94 id: messageField
@@ -111,7 +99,7 @@ Item {
111 Row {
99 Row {
112 Button {
100 Button {
113 text: "Show Prompt"
101 text: "Show Prompt"
114− onClicked: loader.item.showPrompt(promptField.text, isSecretCheckBox.checked, isDefaultPromptCheckBox.checked)
102+ onClicked: LightDMService.prompts.append(promptField.text, isSecretCheckBox.checked ? LightDMService.prompts.Secret : LightDMService.prompts.Question)
115 }
103 }
116 TextField {
104 TextField {
117 id: promptField
105 id: promptField
@@ -124,36 +112,14 @@ Item {
124 Label {
112 Label {
125 text: "secret"
113 text: "secret"
126 }
114 }
127− CheckBox {
128− id: isDefaultPromptCheckBox
129− }
130− Label {
131− text: "default"
132− }
133 }
115 }
134 Row {
116 Row {
135 Button {
117 Button {
136− text: "Authenticated"
118+ text: "Notify Auth Failure"
137 onClicked: {
119 onClicked: {
138− if (successCheckBox.checked) {
139− loader.item.notifyAuthenticationSucceeded(fakePasswordCheckBox.checked);
140− } else {
141− loader.item.notifyAuthenticationFailed();
120+ loader.item.notifyAuthenticationFailed();
142− }
143 }
121 }
144 }
122 }
145− CheckBox {
146− id: successCheckBox
147− }
148− Label {
149− text: "success"
150− }
151− CheckBox {
152− id: fakePasswordCheckBox
153− }
154− Label {
155− text: "fake password"
156− }
157 }
123 }
158 Row {
124 Row {
159 Button {
125 Button {
@@ -218,6 +184,7 @@ Item {
218 Row {
184 Row {
219 CheckBox {
185 CheckBox {
220 id: alphanumericCheckBox
186 id: alphanumericCheckBox
187+ checked: true
221 }
188 }
222 Label {
189 Label {
223 text: "alphanumeric"
190 text: "alphanumeric"
@@ -301,6 +268,9 @@ Item {
301 function init() {
268 function init() {
302 view.currentIndex = 0; // break binding with text field
269 view.currentIndex = 0; // break binding with text field
303
270
271+ LightDM.Greeter.authenticate("no-password");
272+ tryCompare(LightDMService.prompts, "count", 1);
273+
304 telepathyHelper.ready = true;
274 telepathyHelper.ready = true;
305 telepathyHelper.emergencyCallsAvailable = true;
275 telepathyHelper.emergencyCallsAvailable = true;
306 selectedSpy.clear();
276 selectedSpy.clear();
@@ -365,8 +335,9 @@ Item {
365 }
335 }
366
336
367 function test_respondedWithPin() {
337 function test_respondedWithPin() {
338+ LightDM.Greeter.authenticate("has-pin");
368 view.locked = true;
339 view.locked = true;
369− view.showPrompt("", true, true);
340+ view.alphanumeric = false;
370 swipeAwayCover();
341 swipeAwayCover();
371 typeString("1234");
342 typeString("1234");
372 compare(respondedSpy.count, 1);
343 compare(respondedSpy.count, 1);
@@ -374,9 +345,8 @@ Item {
374 }
345 }
375
346
376 function test_respondedWithPassphrase() {
347 function test_respondedWithPassphrase() {
348+ LightDM.Greeter.authenticate("has-password");
377 view.locked = true;
349 view.locked = true;
378− view.alphanumeric = true;
379− view.showPrompt("", true, true);
380 swipeAwayCover();
350 swipeAwayCover();
381 typeString("test");
351 typeString("test");
382 keyClick(Qt.Key_Enter);
352 keyClick(Qt.Key_Enter);
tests/qmltests/Greeter/tst_WideView.qml+96−116
@@ -110,28 +110,16 @@ StyledItem {
110 Column {
110 Column {
111 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
111 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
112 spacing: units.gu(1)
112 spacing: units.gu(1)
113− Row {
114− Button {
115− text: "Show Last Chance"
116− onClicked: loader.item.showLastChance()
117− }
118− }
119 Row {
113 Row {
120 Button {
114 Button {
121 text: "Hide"
115 text: "Hide"
122 onClicked: loader.item.hide()
116 onClicked: loader.item.hide()
123 }
117 }
124 }
118 }
125− Row {
126− Button {
127− text: "Reset"
128− onClicked: loader.item.reset()
129− }
130− }
131 Row {
119 Row {
132 Button {
120 Button {
133 text: "Show Message"
121 text: "Show Message"
134− onClicked: loader.item.showMessage(messageField.text)
122+ onClicked: LightDMService.prompts.append(messageField.text, LightDMService.prompts.Message)
135 }
123 }
136 TextField {
124 TextField {
137 id: messageField
125 id: messageField
@@ -142,7 +130,7 @@ StyledItem {
142 Row {
130 Row {
143 Button {
131 Button {
144 text: "Show Prompt"
132 text: "Show Prompt"
145− onClicked: loader.item.showPrompt(promptField.text, isSecretCheckBox.checked, isDefaultPromptCheckBox.checked)
133+ onClicked: LightDMService.prompts.append(promptField.text, isSecretCheckBox.checked ? LightDMService.prompts.Secret : LightDMService.prompts.Question)
146 }
134 }
147 TextField {
135 TextField {
148 id: promptField
136 id: promptField
@@ -155,30 +143,14 @@ StyledItem {
155 Label {
143 Label {
156 text: "secret"
144 text: "secret"
157 }
145 }
158− CheckBox {
159− id: isDefaultPromptCheckBox
160− }
161− Label {
162− text: "default"
163− }
164 }
146 }
165 Row {
147 Row {
166 Button {
148 Button {
167− text: "Authenticated"
149+ text: "Notify Auth Failure"
168 onClicked: {
150 onClicked: {
169− if (successCheckBox.checked) {
170− loader.item.notifyAuthenticationSucceeded(false);
171− } else {
172− loader.item.notifyAuthenticationFailed();
151+ loader.item.notifyAuthenticationFailed();
173− }
174 }
152 }
175 }
153 }
176− CheckBox {
177− id: successCheckBox
178− }
179− Label {
180− text: "success"
181− }
182 }
154 }
183 Row {
155 Row {
184 Button {
156 Button {
@@ -359,12 +331,17 @@ StyledItem {
359
331
360 function init() {
332 function init() {
361 selectIndex(0); // break binding with text field
333 selectIndex(0); // break binding with text field
334+ tryCompare(LightDMService.prompts, "count", 1);
362 selectedSpy.clear();
335 selectedSpy.clear();
363 respondedSpy.clear();
336 respondedSpy.clear();
364 teaseSpy.clear();
337 teaseSpy.clear();
365 emergencySpy.clear();
338 emergencySpy.clear();
366− LightDMController.sessionMode = "full";
339+ LightDMController.reset();
367 LightDM.Sessions.iconSearchDirectories = [testIconDirectory];
340 LightDM.Sessions.iconSearchDirectories = [testIconDirectory];
341+
342+ waitForRendering(view);
343+ var userList = findChild(view, "userList");
344+ tryCompare(userList, "movingInternally", false);
368 }
345 }
369
346
370 function cleanup() {
347 function cleanup() {
@@ -391,8 +368,14 @@ StyledItem {
391 }
368 }
392
369
393 function selectIndex(i) {
370 function selectIndex(i) {
394− view.currentIndex = i;
371+ var user = LightDM.Users.data(i, LightDM.UserRoles.NameRole);
372+ LightDM.Greeter.authenticate(user);
373+
395 var userList = findChild(view, "userList");
374 var userList = findChild(view, "userList");
375+ var promptList = findChild(view, "promptList");
376+
377+ view.currentIndex = i;
378+ tryCompare(promptList, "opacity", 1);
396 tryCompare(userList, "movingInternally", false);
379 tryCompare(userList, "movingInternally", false);
397 }
380 }
398
381
@@ -412,34 +395,16 @@ StyledItem {
412 }
395 }
413
396
414 function test_changingSessionSticksToUser() {
397 function test_changingSessionSticksToUser() {
415− LightDMController.sessionMode = "full";
416−
417 var loginList = findChild(view, "loginList");
398 var loginList = findChild(view, "loginList");
418− var fakeSession1 = "ASessionWillNeverBeCalledThis";
419− var fakeSession2 = "ASessionWillAlsoNeverBeCalledThis";
420−
421− // For this test to work, a user has to be selected and the
422− // mock must be made aware of that user
423− selectUser("has-password")
424− LightDMController.currentUsername = "has-password"
425− compare(LightDM.Sessions.count > 1, true);
426− compare(loginList.currentSession != fakeSession1, true);
427− LightDMController.sessionName = fakeSession1;
428−
429− selectUser("has-pin")
430− LightDMController.currentUsername = "has-pin"
431− compare(loginList.currentSession != fakeSession2, true);
432− LightDMController.sessionName = fakeSession2;
433
399
434− selectUser("has-password")
400+ selectUser("invalid-session");
435− tryCompare(loginList, "currentSession", fakeSession1);
401+ tryCompare(loginList, "currentSession", "invalid");
436
402
437− selectUser("has-pin")
403+ selectUser("has-password");
438− tryCompare(loginList, "currentSession", fakeSession2);
404+ tryCompare(loginList, "currentSession", "ubuntu");
439
405
440− // Force a model reset
406+ selectUser("invalid-session")
441− LightDMController.userMode = "single";
407+ tryCompare(loginList, "currentSession", "invalid");
442− LightDMController.userMode = "full";
443 }
408 }
444
409
445 function test_tease_data() {
410 function test_tease_data() {
@@ -512,9 +477,6 @@ StyledItem {
512 }
477 }
513
478
514 function test_sessionIconNotShownWithActiveUser() {
479 function test_sessionIconNotShownWithActiveUser() {
515− LightDMController.sessionMode = "full";
516− compare(LightDM.Sessions.count > 1, true);
517−
518 selectUser("active");
480 selectUser("active");
519
481
520 var sessionChooserButton = findChild(view, "sessionChooserButton");
482 var sessionChooserButton = findChild(view, "sessionChooserButton");
@@ -522,9 +484,6 @@ StyledItem {
522 }
484 }
523
485
524 function test_sessionIconShownWithMultipleSessions() {
486 function test_sessionIconShownWithMultipleSessions() {
525− LightDMController.sessionMode = "full";
526− compare(LightDM.Sessions.count > 1, true);
527−
528 selectUser("has-password");
487 selectUser("has-password");
529
488
530 var sessionChooserButton = findChild(view, "sessionChooserButton");
489 var sessionChooserButton = findChild(view, "sessionChooserButton");
@@ -558,12 +517,9 @@ StyledItem {
558 }
517 }
559
518
560 function test_respondedWithPassword() {
519 function test_respondedWithPassword() {
561− view.locked = true;
520+ selectUser("has-password");
562− view.showPrompt("Prompt", true, false);
521+ var greeterPrompt = findChild(view, "greeterPrompt0");
563− var passwordInput = findChild(view, "passwordInput");
564− compare(passwordInput.text, "Prompt");
565− verify(passwordInput.isSecret);
522+ verify(greeterPrompt.isSecret);
566− tap(passwordInput);
567 typeString("password");
523 typeString("password");
568 keyClick(Qt.Key_Enter);
524 keyClick(Qt.Key_Enter);
569 compare(respondedSpy.count, 1);
525 compare(respondedSpy.count, 1);
@@ -571,12 +527,9 @@ StyledItem {
571 }
527 }
572
528
573 function test_respondedWithNonSecret() {
529 function test_respondedWithNonSecret() {
574− view.locked = true;
530+ selectUser("question-prompt");
575− view.showPrompt("otp", false, false);
531+ var greeterPrompt = findChild(view, "greeterPrompt0");
576− var passwordInput = findChild(view, "passwordInput");
577− compare(passwordInput.text, "otp");
578− verify(!passwordInput.isSecret);
532+ verify(!greeterPrompt.isSecret);
579− tap(passwordInput);
580 typeString("foo");
533 typeString("foo");
581 keyClick(Qt.Key_Enter);
534 keyClick(Qt.Key_Enter);
582 compare(respondedSpy.count, 1);
535 compare(respondedSpy.count, 1);
@@ -601,24 +554,47 @@ StyledItem {
601 tryCompare(view, "required", false);
554 tryCompare(view, "required", false);
602 }
555 }
603
556
604− function test_showMessage() {
557+ function test_infoPrompt() {
605− view.showMessage("Welcome to Unity Greeter");
558+ selectUser("info-prompt");
606− view.showMessage("<font color=\"#df382c\">This is an error</font>");
559+
607− view.showMessage("You should have seen three messages and this is a really long message too. wow so long much length");
608− var infoLabel = findChild(view, "infoLabel");
560+ var infoLabel = findChild(view, "infoLabel0");
609− compare(infoLabel.text, "Welcome to Unity Greeter<br><font color=\"#df382c\">This is an error</font><br>You should have seen three messages and this is a really long message too. wow so long much length");
561+ compare(infoLabel.text, "Welcome to Unity Greeter");
610− compare(infoLabel.textFormat, Text.StyledText);
562+ compare(infoLabel.textFormat, Text.PlainText);
563+
564+ verify(findChild(view, "greeterPrompt1") != null);
565+ }
566+
567+ function test_longInfoPrompt() {
568+ selectUser("long-info-prompt");
569+
570+ var infoLabel = findChild(view, "infoLabel0");
571+ compare(infoLabel.text, "Welcome to Unity Greeter\n\nWe like to annoy you with super ridiculously long messages.\nLike this one\n\nThis is the last line of a multiple line message.");
611 verify(infoLabel.contentWidth > infoLabel.width);
572 verify(infoLabel.contentWidth > infoLabel.width);
612− verify(infoLabel.opacity < 1);
573+
613− tryCompare(infoLabel, "opacity", 1);
574+ verify(findChild(view, "greeterPrompt1") != null);
575+ }
576+
577+ function test_multiInfoPrompt() {
578+ selectUser("multi-info-prompt");
579+
580+ var infoLabel0 = findChild(view, "infoLabel0");
581+ compare(infoLabel0.text, "Welcome to Unity Greeter");
582+
583+ var infoLabel1 = findChild(view, "infoLabel1");
584+ compare(infoLabel1.text, "This is an error");
585+ compare(infoLabel1.color, theme.palette.normal.negative);
586+
587+ var infoLabel2 = findChild(view, "infoLabel2");
588+ compare(infoLabel2.text, "You should have seen three messages");
589+
590+ verify(findChild(view, "greeterPrompt3") != null);
614 }
591 }
615
592
616 // Escape is used to reset the authentication, especially if PAM is unresponsive
593 // Escape is used to reset the authentication, especially if PAM is unresponsive
617 function test_escape() {
594 function test_escape() {
618− selectIndex(1);
595+ var index = selectUser("has-password");
619 selectedSpy.clear();
596 selectedSpy.clear();
620 view.locked = true;
597 view.locked = true;
621− view.showPrompt("Prompt", true, true);
622 var promptField = findChild(view, "promptField");
598 var promptField = findChild(view, "promptField");
623 tap(promptField);
599 tap(promptField);
624 verify(promptField.activeFocus);
600 verify(promptField.activeFocus);
@@ -632,9 +608,10 @@ StyledItem {
632 compare(selectedSpy.count, 0);
608 compare(selectedSpy.count, 0);
633 keyClick(Qt.Key_Escape);
609 keyClick(Qt.Key_Escape);
634 compare(selectedSpy.count, 1);
610 compare(selectedSpy.count, 1);
635− compare(selectedSpy.signalArguments[0][0], 1);
611+ compare(selectedSpy.signalArguments[0][0], index);
636
612
637− view.reset();
613+ selectIndex(index);
614+ var promptField = findChild(view, "promptField");
638 verify(promptField.activeFocus);
615 verify(promptField.activeFocus);
639 compare(promptField.opacity, 1);
616 compare(promptField.opacity, 1);
640 }
617 }
@@ -645,21 +622,22 @@ StyledItem {
645 tryCompare(label, "text", "가나다라마");
622 tryCompare(label, "text", "가나다라마");
646 }
623 }
647
624
648− function test_promptless() {
625+ function test_authError() {
649− var passwordInput = findChild(view, "passwordInput");
626+ var index = selectUser("auth-error");
650−
627+ var greeterPrompt = findChild(view, "greeterPrompt1"); // after error message
651 view.locked = true;
628 view.locked = true;
652− compare(passwordInput.text, "Retry");
629+ compare(greeterPrompt.text, "Retry");
653− tap(passwordInput);
630+ tap(greeterPrompt);
654 compare(respondedSpy.count, 0);
631 compare(respondedSpy.count, 0);
655 compare(selectedSpy.count, 1);
632 compare(selectedSpy.count, 1);
656− compare(selectedSpy.signalArguments[0][0], 0);
633+ compare(selectedSpy.signalArguments[0][0], index);
657− selectedSpy.clear();
634+ }
658
635
659− view.reset();
636+ function test_noPassword() {
660− view.locked = false;
637+ selectUser("no-password");
638+ var greeterPrompt = findChild(view, "greeterPrompt0");
661− compare(passwordInput.text, "Log In");
639+ compare(greeterPrompt.text, "Log In");
662− tap(passwordInput);
640+ tap(greeterPrompt);
663 compare(selectedSpy.count, 0);
641 compare(selectedSpy.count, 0);
664 compare(respondedSpy.count, 1);
642 compare(respondedSpy.count, 1);
665 compare(respondedSpy.signalArguments[0][0], "");
643 compare(respondedSpy.signalArguments[0][0], "");
@@ -691,8 +669,8 @@ StyledItem {
691 }
669 }
692
670
693 function test_passphrase() {
671 function test_passphrase() {
672+ var index = selectUser("has-password");
694 var promptField = findChild(view, "promptField");
673 var promptField = findChild(view, "promptField");
695− view.showPrompt("", true, true);
696
674
697 verify(view.alphanumeric);
675 verify(view.alphanumeric);
698 compare(promptField.inputMethodHints & Qt.ImhDigitsOnly, 0);
676 compare(promptField.inputMethodHints & Qt.ImhDigitsOnly, 0);
@@ -702,8 +680,8 @@ StyledItem {
702 }
680 }
703
681
704 function test_passcode() {
682 function test_passcode() {
683+ var index = selectUser("has-pin");
705 var promptField = findChild(view, "promptField");
684 var promptField = findChild(view, "promptField");
706− view.showPrompt("", true, true);
707
685
708 view.alphanumeric = false;
686 view.alphanumeric = false;
709 compare(promptField.inputMethodHints & Qt.ImhDigitsOnly, Qt.ImhDigitsOnly);
687 compare(promptField.inputMethodHints & Qt.ImhDigitsOnly, Qt.ImhDigitsOnly);
@@ -737,42 +715,44 @@ StyledItem {
737 }
715 }
738
716
739 function test_focusStaysActive() {
717 function test_focusStaysActive() {
740− var promptField = findChild(view, "promptField");
718+ selectUser("no-password");
741− var promptButton = findChild(view, "promptButton");
719+ var greeterPrompt = findChild(view, "greeterPrompt0");
742−
743− verify(promptButton.activeFocus);
720+ verify(greeterPrompt.activeFocus);
744 keyClick(Qt.Key_Enter);
721 keyClick(Qt.Key_Enter);
745 compare(selectedSpy.count, 0);
722 compare(selectedSpy.count, 0);
746 compare(respondedSpy.count, 1);
723 compare(respondedSpy.count, 1);
747 compare(respondedSpy.signalArguments[0][0], "");
724 compare(respondedSpy.signalArguments[0][0], "");
748− verify(promptButton.activeFocus);
725+ verify(greeterPrompt.activeFocus);
749 keyClick(Qt.Key_Enter);
726 keyClick(Qt.Key_Enter);
750 compare(respondedSpy.count, 1);
727 compare(respondedSpy.count, 1);
751
728
752− view.showPrompt("", true, true);
729+ selectUser("has-password");
730+ var greeterPrompt = findChild(view, "greeterPrompt0");
753− verify(promptField.activeFocus);
731+ verify(greeterPrompt.activeFocus);
754 keyClick(Qt.Key_D);
732 keyClick(Qt.Key_D);
755 keyClick(Qt.Key_Enter);
733 keyClick(Qt.Key_Enter);
756 compare(selectedSpy.count, 0);
734 compare(selectedSpy.count, 0);
757 compare(respondedSpy.count, 2);
735 compare(respondedSpy.count, 2);
758 compare(respondedSpy.signalArguments[1][0], "d");
736 compare(respondedSpy.signalArguments[1][0], "d");
759− verify(promptField.activeFocus);
737+ verify(greeterPrompt.activeFocus);
760 keyClick(Qt.Key_Enter);
738 keyClick(Qt.Key_Enter);
761 compare(respondedSpy.count, 2);
739 compare(respondedSpy.count, 2);
762
740
763− view.reset();
741+ var index = selectUser("no-password");
742+ var greeterPrompt = findChild(view, "greeterPrompt0");
764 view.locked = true;
743 view.locked = true;
765− verify(promptButton.activeFocus);
744+ verify(greeterPrompt.activeFocus);
766 keyClick(Qt.Key_Enter);
745 keyClick(Qt.Key_Enter);
767 compare(respondedSpy.count, 2);
746 compare(respondedSpy.count, 2);
768 compare(selectedSpy.count, 1);
747 compare(selectedSpy.count, 1);
769− compare(selectedSpy.signalArguments[0][0], 0);
748+ compare(selectedSpy.signalArguments[0][0], index);
770− verify(promptButton.activeFocus);
749+ verify(greeterPrompt.activeFocus);
771 keyClick(Qt.Key_Enter);
750 keyClick(Qt.Key_Enter);
772 compare(selectedSpy.count, 1);
751 compare(selectedSpy.count, 1);
773
752
774− view.showPrompt("", true, true);
753+ selectUser("no-password");
754+ var greeterPrompt = findChild(view, "greeterPrompt0");
775− verify(promptField.activeFocus);
755+ verify(greeterPrompt.activeFocus);
776 }
756 }
777 }
757 }
778}
758}
tests/qmltests/tst_Shell.qml+9−8
@@ -833,13 +833,15 @@ Rectangle {
833 keyClick(Qt.Key_Escape) // Reset state if we're not moving
833 keyClick(Qt.Key_Escape) // Reset state if we're not moving
834 while (userlist.currentIndex != i) {
834 while (userlist.currentIndex != i) {
835 var next = userlist.currentIndex + 1
835 var next = userlist.currentIndex + 1
836+ var key = Qt.Key_Down;
836 if (userlist.currentIndex > i) {
837 if (userlist.currentIndex > i) {
837 next = userlist.currentIndex - 1
838 next = userlist.currentIndex - 1
839+ key = Qt.Key_Up;
838 }
840 }
839− tap(findChild(greeter, "username"+next));
841+ keyPress(key);
840 tryCompare(userlist, "currentIndex", next)
842 tryCompare(userlist, "currentIndex", next)
841− tryCompare(userlist, "movingInternally", false)
842 }
843 }
844+ tryCompare(userlist, "movingInternally", false);
843 tryCompare(shell, "waitingOnGreeter", false); // wait for PAM to settle
845 tryCompare(shell, "waitingOnGreeter", false); // wait for PAM to settle
844 }
846 }
845
847
@@ -862,15 +864,13 @@ Rectangle {
862 var greeter = findChild(shell, "greeter")
864 var greeter = findChild(shell, "greeter")
863 tryCompare(greeter, "fullyShown", true);
865 tryCompare(greeter, "fullyShown", true);
864
866
865− var passwordInput = findChild(shell, "passwordInput");
866−
867− var promptButton = findChild(passwordInput, "promptButton");
867+ var promptButton = findChild(greeter, "promptButton");
868 tryCompare(promptButton, "visible", isButton);
868 tryCompare(promptButton, "visible", isButton);
869
869
870− var promptField = findChild(passwordInput, "promptField");
870+ var promptField = findChild(greeter, "promptField");
871 tryCompare(promptField, "visible", !isButton);
871 tryCompare(promptField, "visible", !isButton);
872
872
873− mouseClick(passwordInput)
873+ mouseClick(promptButton);
874 }
874 }
875
875
876 function confirmLoggedIn(loggedIn) {
876 function confirmLoggedIn(loggedIn) {
@@ -1473,7 +1473,7 @@ Rectangle {
1473 confirmLoggedIn(data.loggedIn)
1473 confirmLoggedIn(data.loggedIn)
1474
1474
1475 if (data.passwordFocus) {
1475 if (data.passwordFocus) {
1476− var passwordInput = findChild(greeter, "passwordInput")
1476+ var passwordInput = findChild(greeter, "promptField");
1477 tryCompare(passwordInput, "focus", true)
1477 tryCompare(passwordInput, "focus", true)
1478 }
1478 }
1479 }
1479 }
@@ -1496,6 +1496,7 @@ Rectangle {
1496 typeString("has-password");
1496 typeString("has-password");
1497 keyClick(Qt.Key_Enter);
1497 keyClick(Qt.Key_Enter);
1498
1498
1499+ promptHint = findChild(greeter, "promptHint");
1499 tryCompare(username, "text", "has-password");
1500 tryCompare(username, "text", "has-password");
1500 tryCompare(promptHint, "text", "Passphrase");
1501 tryCompare(promptHint, "text", "Passphrase");
1501
1502
tests/qmltests/tst_ShellWithPin.qml+2−10
@@ -117,12 +117,6 @@ Item {
117 signalName: "resettingDevice"
117 signalName: "resettingDevice"
118 }
118 }
119
119
120− SignalSpy {
121− id: promptSpy
122− target: LightDM.Greeter
123− signalName: "showPrompt"
124− }
125−
126 Telephony.CallEntry {
120 Telephony.CallEntry {
127 id: phoneCall
121 id: phoneCall
128 phoneNumber: "+447812221111"
122 phoneNumber: "+447812221111"
@@ -539,22 +533,20 @@ Item {
539 shellLoader.itemDestroyed = false;
533 shellLoader.itemDestroyed = false;
540 shellLoader.active = false;
534 shellLoader.active = false;
541 tryCompare(shellLoader, "itemDestroyed", true);
535 tryCompare(shellLoader, "itemDestroyed", true);
542− LightDM.Greeter.authenticate(""); // reset greeter
543
536
544 // Create new shell
537 // Create new shell
545− promptSpy.clear();
546 shellLoader.active = true;
538 shellLoader.active = true;
547 tryCompareFunction(function() {return shell !== null}, true);
539 tryCompareFunction(function() {return shell !== null}, true);
548
540
549 // Confirm that we start disabled
541 // Confirm that we start disabled
550− compare(promptSpy.count, 0);
542+ compare(LightDM.Prompts.count, 0);
551 verify(shell.waitingOnGreeter);
543 verify(shell.waitingOnGreeter);
552 var coverPageDragHandle = findChild(shell, "coverPageDragHandle");
544 var coverPageDragHandle = findChild(shell, "coverPageDragHandle");
553 verify(!coverPageDragHandle.enabled);
545 verify(!coverPageDragHandle.enabled);
554
546
555 // And that we only become enabled once the lockscreen is up
547 // And that we only become enabled once the lockscreen is up
556 tryCompare(shell, "waitingOnGreeter", false);
548 tryCompare(shell, "waitingOnGreeter", false);
557− verify(promptSpy.count > 0);
549+ verify(LightDM.Prompts.count > 0);
558 var lockscreen = findChild(shell, "lockscreen");
550 var lockscreen = findChild(shell, "lockscreen");
559 verify(lockscreen.shown);
551 verify(lockscreen.shown);
560 }
552 }