on xenial help sign in
Merge greeter-manual-login
commitc9a7716acc0bbcf653184bf1734343ee01916503 authorMichael Terry <mike@mterry.name> dateThu 19 Jan 2017, 09:40 (10 years ago) parent8e933c9e3f492e
13 files changed+327−39
plugins/LightDM/Greeter.cpp+37−3 plugins/LightDM/Greeter.h+3 plugins/LightDM/GreeterPrivate.h+8−6 plugins/LightDM/UsersModel.cpp+123−21 qml/Greeter/GreeterPrompt.qml+1 qml/Greeter/LoginList.qml+4−1 tests/mocks/liblightdm/MockController.cpp+28 tests/mocks/liblightdm/MockController.h+12 tests/mocks/liblightdm/MockGreeter.cpp+16−4 tests/mocks/liblightdm/MockUsersModel.cpp+4 tests/plugins/LightDM/IntegratedLightDM/usersmodel.cpp+53−2 tests/qmltests/Greeter/tst_Greeter.qml+9−1 tests/qmltests/tst_Shell.qml+29−1
plugins/LightDM/Greeter.cpp+37−3
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013-2016 Canonical, Ltd.
2+ * Copyright (C) 2013-2017 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -89,7 +89,16 @@ bool Greeter::isAuthenticated() const
8989QString Greeter::authenticationUser() const
9090{
9191 Q_D(const Greeter);
92− return d->m_greeter->authenticationUser();
92+ return d->cachedAuthUser;
93+}
94+
95+void Greeter::checkAuthenticationUser()
96+{
97+ Q_D(Greeter);
98+ if (d->cachedAuthUser != d->m_greeter->authenticationUser()) {
99+ d->cachedAuthUser = d->m_greeter->authenticationUser();
100+ Q_EMIT authenticationUserChanged();
101+ }
93102}
94103
95104QString Greeter::defaultSessionHint() const
@@ -114,6 +123,18 @@ bool Greeter::hasGuestAccount() const
114123 return d->m_greeter->hasGuestAccountHint();
115124}
116125
126+bool Greeter::showManualLoginHint() const
127+{
128+ Q_D(const Greeter);
129+ return d->m_greeter->showManualLoginHint();
130+}
131+
132+bool Greeter::hideUsersHint() const
133+{
134+ Q_D(const Greeter);
135+ return d->m_greeter->hideUsersHint();
136+}
137+
117138void Greeter::authenticate(const QString &username)
118139{
119140 Q_D(Greeter);
@@ -128,12 +149,14 @@ void Greeter::authenticate(const QString &username)
128149
129150 if (username == QStringLiteral("*guest")) {
130151 d->m_greeter->authenticateAsGuest();
152+ } else if (username == QStringLiteral("*other")) {
153+ d->m_greeter->authenticate(nullptr);
131154 } else {
132155 d->m_greeter->authenticate(username);
133156 }
134157
135158 Q_EMIT isAuthenticatedChanged();
136− Q_EMIT authenticationUserChanged();
159+ checkAuthenticationUser();
137160}
138161
139162void Greeter::respond(const QString &response)
@@ -154,6 +177,8 @@ void Greeter::showPromptFilter(const QString &text, QLightDM::Greeter::PromptTyp
154177{
155178 Q_D(Greeter);
156179
180+ checkAuthenticationUser(); // may have changed in liblightdm
181+
157182 bool isDefaultPrompt = (text == dgettext("Linux-PAM", "Password: "));
158183 bool isSecret = type == QLightDM::Greeter::PromptTypeSecret;
159184
@@ -166,10 +191,17 @@ void Greeter::showPromptFilter(const QString &text, QLightDM::Greeter::PromptTyp
166191 trimmedText.chop(1);
167192 }
168193
194+ if (trimmedText == "login") {
195+ // 'login' is provided untranslated by LightDM when asking for a manual
196+ // login username.
197+ trimmedText = gettext("Username");
198+ }
199+
169200 if (d->responded) {
170201 d->prompts.clear();
171202 d->responded = false;
172203 }
204+
173205 d->prompts.append(trimmedText, isSecret ? PromptsModel::Secret : PromptsModel::Question);
174206}
175207
@@ -177,6 +209,8 @@ void Greeter::showMessageFilter(const QString &text, QLightDM::Greeter::MessageT
177209{
178210 Q_D(Greeter);
179211
212+ checkAuthenticationUser(); // may have changed in liblightdm
213+
180214 bool isError = type == QLightDM::Greeter::MessageTypeError;
181215
182216 if (d->responded) {
plugins/LightDM/Greeter.h+3
@@ -49,6 +49,8 @@ public:
4949 QString defaultSessionHint() const;
5050 QString selectUser() const;
5151 bool hasGuestAccount() const;
52+ bool showManualLoginHint() const;
53+ bool hideUsersHint() const;
5254
5355 PromptsModel *promptsModel();
5456
@@ -82,4 +84,5 @@ private Q_SLOTS:
8284 void showMessageFilter(const QString &text, QLightDM::Greeter::MessageType type);
8385 void showPromptFilter(const QString &text, QLightDM::Greeter::PromptType type);
8486 void authenticationCompleteFilter();
87+ void checkAuthenticationUser();
8588};
plugins/LightDM/GreeterPrivate.h+8−6
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2015-2017 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -14,11 +14,14 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17−#ifndef UNITY_GREETER_PRIVATE_H
18−#define UNITY_GREETER_PRIVATE_H
17+#pragma once
1918
2019#include "PromptsModel.h"
21−#include <QLightDM/Greeter>
20+#include <QObject>
21+
22+namespace QLightDM {
23+ class Greeter;
24+}
2225
2326class GreeterPrivate
2427{
@@ -31,6 +34,7 @@ public:
3134 PromptsModel leftovers; // prompts to show during next auth for same user
3235 bool responded;
3336 bool everResponded;
37+ QString cachedAuthUser;
3438
3539protected:
3640 Greeter * const q_ptr;
@@ -38,5 +42,3 @@ protected:
3842private:
3943 Q_DECLARE_PUBLIC(Greeter)
4044};
41−
42−#endif // UNITY_GREETER_PRIVATE_H
plugins/LightDM/UsersModel.cpp+123−21
@@ -39,29 +39,63 @@ public:
3939 QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
4040
4141private:
42− int guestRow() const;
42+ struct CustomRow {
43+ QString name;
44+ QString realName;
45+ };
46+
47+ int sourceRowCount() const;
48+
49+ void updateGuestRow();
50+ void updateManualRow();
51+ void updateCustomRows();
52+
53+ void addCustomRow(const CustomRow &newRow);
54+ void removeCustomRow(const QString &rowName);
55+
56+ QList<CustomRow> m_customRows;
57+ bool m_updatingCustomRows;
4358};
4459
4560MangleModel::MangleModel(QObject* parent)
4661 : QIdentityProxyModel(parent)
62+ , m_updatingCustomRows(false)
4763{
4864 setSourceModel(new QLightDM::UsersModel(this));
65+
66+ updateCustomRows();
67+
68+ // Would be nice if there were a rowCountChanged signal in the base class.
69+ // We redo all custom rows on any row count change, because (A) some of
70+ // custom rows (manual login) use row count information and (B) when
71+ // testing, we use a modelReset signal as a way to indicate that a custom
72+ // row has been toggled off or on.
73+ connect(this, &QIdentityProxyModel::modelReset,
74+ this, &MangleModel::updateCustomRows);
75+ connect(this, &QIdentityProxyModel::rowsInserted,
76+ this, &MangleModel::updateCustomRows);
77+ connect(this, &QIdentityProxyModel::rowsRemoved,
78+ this, &MangleModel::updateCustomRows);
4979}
5080
5181QVariant MangleModel::data(const QModelIndex &index, int role) const
5282{
5383 QVariant variantData;
5484
55− if (index.row() == guestRow() && index.column() == 0) {
56− switch (role) {
57− case QLightDM::UsersModel::NameRole:
58− variantData = QStringLiteral("*guest"); break;
59− case QLightDM::UsersModel::RealNameRole:
60− variantData = gettext("Guest Session"); break;
61− case QLightDM::UsersModel::LoggedInRole:
62− variantData = false; break;
63− case QLightDM::UsersModel::SessionRole:
64− variantData = Greeter::instance()->defaultSessionHint(); break;
85+ if (index.row() >= rowCount())
86+ return QVariant();
87+
88+ bool isCustomRow = index.row() >= sourceRowCount();
89+ if (isCustomRow && index.column() == 0) {
90+ int customIndex = index.row() - sourceRowCount();
91+ if (role == QLightDM::UsersModel::NameRole) {
92+ variantData = m_customRows[customIndex].name;
93+ } else if (role == QLightDM::UsersModel::RealNameRole) {
94+ variantData = m_customRows[customIndex].realName;
95+ } else if (role == QLightDM::UsersModel::LoggedInRole) {
96+ variantData = false;
97+ } else if (role == QLightDM::UsersModel::SessionRole) {
98+ variantData = Greeter::instance()->defaultSessionHint();
6599 }
66100 } else {
67101 variantData = QIdentityProxyModel::data(index, role);
@@ -69,7 +103,7 @@ QVariant MangleModel::data(const QModelIndex &index, int role) const
69103
70104 // If user's real name is empty, switch to unix name
71105 if (role == QLightDM::UsersModel::RealNameRole && variantData.toString().isEmpty()) {
72− variantData = QIdentityProxyModel::data(index, QLightDM::UsersModel::NameRole);
106+ variantData = data(index, QLightDM::UsersModel::NameRole);
73107 } else if (role == QLightDM::UsersModel::BackgroundPathRole && variantData.toString().startsWith('#')) {
74108 const QString stringData = "data:image/svg+xml,<svg><rect width='100%' height='100%' fill='" + variantData.toString() + "'/></svg>";
75109 variantData = stringData;
@@ -78,25 +112,89 @@ QVariant MangleModel::data(const QModelIndex &index, int role) const
78112 return variantData;
79113}
80114
81−int MangleModel::guestRow() const
115+void MangleModel::addCustomRow(const CustomRow &newRow)
82116{
83− return Greeter::instance()->hasGuestAccount() ?
84− QIdentityProxyModel::rowCount() : -1;
117+ for (int i = 0; i < m_customRows.size(); i++) {
118+ if (m_customRows[i].name == newRow.name) {
119+ return; // we don't have custom rows that change content yet
120+ }
121+ }
122+
123+ beginInsertRows(QModelIndex(), rowCount(), rowCount());
124+ m_customRows << newRow;
125+ endInsertRows();
85126}
86127
87−int MangleModel::rowCount(const QModelIndex &parent) const
128+void MangleModel::removeCustomRow(const QString &rowName)
88129{
89− auto count = QIdentityProxyModel::rowCount(parent);
130+ for (int i = 0; i < m_customRows.size(); i++) {
131+ if (m_customRows[i].name == rowName) {
132+ int rowNum = sourceRowCount() + i;
133+ beginRemoveRows(QModelIndex(), rowNum, rowNum);
134+ m_customRows.removeAt(i);
135+ endRemoveRows();
136+ break;
137+ }
138+ }
139+}
90140
91− if (Greeter::instance()->hasGuestAccount() && !parent.isValid())
92− count++;
141+void MangleModel::updateManualRow()
142+{
143+ bool hasAnotherEntry = sourceRowCount() > 0;
144+ for (int i = 0; !hasAnotherEntry && i < m_customRows.size(); i++) {
145+ if (m_customRows[i].name != QStringLiteral("*other")) {
146+ hasAnotherEntry = true;
147+ }
148+ }
93149
94− return count;
150+ // Show manual login if we are asked to OR if no other entry exists
151+ if (Greeter::instance()->showManualLoginHint() || !hasAnotherEntry)
152+ addCustomRow({QStringLiteral("*other"), gettext("Login")});
153+ else
154+ removeCustomRow(QStringLiteral("*other"));
155+}
156+
157+void MangleModel::updateGuestRow()
158+{
159+ if (Greeter::instance()->hasGuestAccount())
160+ addCustomRow({QStringLiteral("*guest"), gettext("Guest Session")});
161+ else
162+ removeCustomRow(QStringLiteral("*guest"));
163+}
164+
165+void MangleModel::updateCustomRows()
166+{
167+ // We update when rowCount changes, but we also insert/remove rows here.
168+ // So guard this function to avoid recursion.
169+ if (m_updatingCustomRows)
170+ return;
171+
172+ m_updatingCustomRows = true;
173+ updateGuestRow();
174+ updateManualRow();
175+ m_updatingCustomRows = false;
176+}
177+
178+int MangleModel::rowCount(const QModelIndex &parent) const
179+{
180+ if (parent.isValid())
181+ return 0;
182+ else
183+ return sourceRowCount() + m_customRows.size();
184+}
185+
186+int MangleModel::sourceRowCount() const
187+{
188+ return Greeter::instance()->hideUsersHint() ? 0 : sourceModel()->rowCount();
95189}
96190
97191QModelIndex MangleModel::index(int row, int column, const QModelIndex &parent) const
98192{
99− if (row == guestRow() && !parent.isValid()) {
193+ if (row >= rowCount())
194+ return QModelIndex();
195+
196+ bool isCustomRow = row >= sourceRowCount();
197+ if (isCustomRow && !parent.isValid()) {
100198 return createIndex(row, column);
101199 } else {
102200 return QIdentityProxyModel::index(row, column, parent);
@@ -124,6 +222,10 @@ bool UsersModel::lessThan(const QModelIndex &source_left, const QModelIndex &sou
124222 return false;
125223 if (rightName == QStringLiteral("*guest"))
126224 return true;
225+ if (leftName == QStringLiteral("*other"))
226+ return false;
227+ if (rightName == QStringLiteral("*other"))
228+ return true;
127229
128230 return UnitySortFilterProxyModelQML::lessThan(source_left, source_right);
129231}
qml/Greeter/GreeterPrompt.qml+1
@@ -191,6 +191,7 @@ FocusScope {
191191 // palette color, whereas we want raisedSecondaryText.
192192 Label {
193193 id: hint
194+ objectName: "promptHint"
194195 anchors {
195196 left: parent.left
196197 right: parent.right
qml/Greeter/LoginList.qml+4−1
@@ -151,7 +151,10 @@ StyledItem {
151151 // Add an offset to bottomMargin for any items below the highlight
152152 bottomMargin: -(units.gu(4) + (parent.belowHighlight ? parent.belowOffset : 0))
153153 }
154− text: realName
154+ text: userList.currentIndex === index
155+ && name === "*other"
156+ && LightDMService.greeter.authenticationUser !== ""
157+ ? LightDMService.greeter.authenticationUser : realName
155158 color: userList.currentIndex !== index ? theme.palette.normal.raised
156159 : theme.palette.normal.raisedText
157160
tests/mocks/liblightdm/MockController.cpp+28
@@ -70,6 +70,8 @@ void MockController::reset()
7070 setUserMode(userMode);
7171 setSessionMode("full");
7272 setNumSessions(numFullSessions());
73+ setShowManualLoginHint(false);
74+ setHideUsersHint(false);
7375}
7476
7577QString MockController::selectUserHint() const
@@ -111,6 +113,32 @@ void MockController::setHasGuestAccountHint(bool hasGuestAccountHint)
111113 }
112114}
113115
116+bool MockController::showManualLoginHint() const
117+{
118+ return m_showManualLoginHint;
119+}
120+
121+void MockController::setShowManualLoginHint(bool showManualLoginHint)
122+{
123+ if (m_showManualLoginHint != showManualLoginHint) {
124+ m_showManualLoginHint = showManualLoginHint;
125+ Q_EMIT showManualLoginHintChanged();
126+ }
127+}
128+
129+bool MockController::hideUsersHint() const
130+{
131+ return m_hideUsersHint;
132+}
133+
134+void MockController::setHideUsersHint(bool hideUsersHint)
135+{
136+ if (m_hideUsersHint != hideUsersHint) {
137+ m_hideUsersHint = hideUsersHint;
138+ Q_EMIT hideUsersHintChanged();
139+ }
140+}
141+
114142QString MockController::userMode() const
115143{
116144 return m_userMode;
tests/mocks/liblightdm/MockController.h+12
@@ -29,6 +29,8 @@ class Q_DECL_EXPORT MockController : public QObject
2929 Q_PROPERTY(QString selectUserHint READ selectUserHint WRITE setSelectUserHint NOTIFY selectUserHintChanged)
3030 Q_PROPERTY(bool selectGuestHint READ selectGuestHint WRITE setSelectGuestHint NOTIFY selectGuestHintChanged)
3131 Q_PROPERTY(bool hasGuestAccountHint READ hasGuestAccountHint WRITE setHasGuestAccountHint NOTIFY hasGuestAccountHintChanged)
32+ Q_PROPERTY(bool showManualLoginHint READ showManualLoginHint WRITE setShowManualLoginHint NOTIFY showManualLoginHintChanged)
33+ Q_PROPERTY(bool hideUsersHint READ hideUsersHint WRITE setHideUsersHint NOTIFY hideUsersHintChanged)
3234
3335 // single, single-pin, single-passphrase, full
3436 Q_PROPERTY(QString userMode READ userMode WRITE setUserMode NOTIFY userModeChanged)
@@ -54,6 +56,12 @@ public:
5456 bool hasGuestAccountHint() const;
5557 void setHasGuestAccountHint(bool hasGuestAccountHint);
5658
59+ bool showManualLoginHint() const;
60+ void setShowManualLoginHint(bool showManualLoginHint);
61+
62+ bool hideUsersHint() const;
63+ void setHideUsersHint(bool hideUsersHint);
64+
5765 QString userMode() const;
5866 void setUserMode(const QString &userMode);
5967
@@ -76,6 +84,8 @@ Q_SIGNALS:
7684 void selectUserHintChanged();
7785 void selectGuestHintChanged();
7886 void hasGuestAccountHintChanged();
87+ void showManualLoginHintChanged();
88+ void hideUsersHintChanged();
7989 void userModeChanged();
8090 void sessionModeChanged();
8191 void numSessionsChanged();
@@ -86,6 +96,8 @@ private:
8696 QString m_selectUserHint;
8797 bool m_selectGuestHint;
8898 bool m_hasGuestAccountHint;
99+ bool m_showManualLoginHint;
100+ bool m_hideUsersHint;
89101 QString m_userMode;
90102 QString m_sessionMode;
91103 QList<SessionItem> m_fullSessions;
tests/mocks/liblightdm/MockGreeter.cpp+16−4
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014-2016 Canonical, Ltd.
2+ * Copyright (C) 2014-2017 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -73,17 +73,17 @@ QString Greeter::defaultSessionHint() const
7373
7474bool Greeter::hideUsersHint() const
7575{
76− return false;
76+ return MockController::instance()->hideUsersHint();
7777}
7878
7979bool Greeter::showManualLoginHint() const
8080{
81− return true;
81+ return MockController::instance()->showManualLoginHint();
8282}
8383
8484bool Greeter::showRemoteLoginHint() const
8585{
86− return true;
86+ return false;
8787}
8888
8989QString Greeter::selectUserHint() const
@@ -147,6 +147,11 @@ void Greeter::handleAuthenticate()
147147{
148148 Q_D(Greeter);
149149
150+ if (d->authenticationUser.isEmpty()) {
151+ Q_EMIT showPrompt("Username: ", Greeter::PromptTypeQuestion);
152+ return;
153+ }
154+
150155 // Send out any messages we need to
151156 if (d->authenticationUser == "info-prompt")
152157 Q_EMIT showMessage("Welcome to Unity Greeter", Greeter::MessageTypeInfo);
@@ -239,6 +244,13 @@ void Greeter::respond(const QString &response)
239244{
240245 Q_D(Greeter);
241246
247+ if (d->authenticationUser.isEmpty()) {
248+ // A manual login, our first question was which username
249+ d->authenticationUser = response;
250+ handleAuthenticate();
251+ return;
252+ }
253+
242254 if (d->authenticationUser == "no-response") {
243255 return;
244256 } else if (d->authenticationUser == "locked") {
tests/mocks/liblightdm/MockUsersModel.cpp+4
@@ -67,6 +67,10 @@ UsersModel::UsersModel(QObject *parent)
6767
6868 connect(MockController::instance(), &MockController::hasGuestAccountHintChanged,
6969 this, &UsersModel::resetEntries);
70+ connect(MockController::instance(), &MockController::showManualLoginHintChanged,
71+ this, &UsersModel::resetEntries);
72+ connect(MockController::instance(), &MockController::hideUsersHintChanged,
73+ this, &UsersModel::resetEntries);
7074 connect(MockController::instance(), &MockController::userModeChanged,
7175 this, &UsersModel::resetEntries);
7276 resetEntries();
tests/plugins/LightDM/IntegratedLightDM/usersmodel.cpp+53−2
@@ -79,11 +79,9 @@ private Q_SLOTS:
7979 QCOMPARE(findName(model, QStringLiteral("*guest")), -1);
8080
8181 QLightDM::MockController::instance()->setHasGuestAccountHint(true);
82− recreateModel();
8382
8483 int i = findName(model, QStringLiteral("*guest"));
8584 QVERIFY(i >= 0);
86− QCOMPARE(i, model->count() - 1);
8785
8886 auto realName = model->data(i, QLightDM::UsersModel::RealNameRole).toString();
8987 QCOMPARE(realName, QStringLiteral("Guest Session"));
@@ -95,6 +93,59 @@ private Q_SLOTS:
9593 QCOMPARE(session, Greeter::instance()->defaultSessionHint());
9694 }
9795
96+ void testHasManual()
97+ {
98+ // sanity check that it doesn't start already present
99+ QCOMPARE(findName(model, QStringLiteral("*other")), -1);
100+
101+ QLightDM::MockController::instance()->setShowManualLoginHint(true);
102+
103+ int i = findName(model, QStringLiteral("*other"));
104+ QVERIFY(i >= 0);
105+
106+ auto realName = model->data(i, QLightDM::UsersModel::RealNameRole).toString();
107+ QCOMPARE(realName, QStringLiteral("Login"));
108+
109+ auto loggedIn = model->data(i, QLightDM::UsersModel::LoggedInRole).toBool();
110+ QVERIFY(!loggedIn);
111+
112+ auto session = model->data(i, QLightDM::UsersModel::SessionRole).toString();
113+ QCOMPARE(session, Greeter::instance()->defaultSessionHint());
114+ }
115+
116+ void testHideUsers()
117+ {
118+ QLightDM::MockController::instance()->setHideUsersHint(true);
119+
120+ QCOMPARE(model->count(), 1);
121+ auto name = model->data(0, QLightDM::UsersModel::NameRole).toString();
122+ QCOMPARE(name, QStringLiteral("*other"));
123+ }
124+
125+ void testHideUsersWithGuest()
126+ {
127+ QLightDM::MockController::instance()->setHideUsersHint(true);
128+ QLightDM::MockController::instance()->setHasGuestAccountHint(true);
129+
130+ QCOMPARE(model->count(), 1);
131+ auto name = model->data(0, QLightDM::UsersModel::NameRole).toString();
132+ QCOMPARE(name, QStringLiteral("*guest"));
133+ }
134+
135+ void testCustomEntriesAreLast()
136+ {
137+ QLightDM::MockController::instance()->setHasGuestAccountHint(true);
138+ QLightDM::MockController::instance()->setShowManualLoginHint(true);
139+
140+ QVERIFY(model->count() > 2);
141+
142+ int manualIndex = findName(model, QStringLiteral("*other"));
143+ QCOMPARE(manualIndex, model->count() - 2);
144+
145+ int guestIndex = findName(model, QStringLiteral("*guest"));
146+ QCOMPARE(guestIndex, model->count() - 1);
147+ }
148+
98149private:
99150 UsersModel *model = nullptr;
100151};
tests/qmltests/Greeter/tst_Greeter.qml+9−1
@@ -194,7 +194,7 @@ Item {
194194 var i = getIndexOf(name);
195195 compare(view.currentIndex, i);
196196 compare(AccountsService.user, name);
197− if (name === "*guest") // guest has no authenticationUser set
197+ if (name[0] === "*") // custom rows have no authenticationUser set
198198 compare(LightDM.Greeter.authenticationUser, "");
199199 else
200200 compare(LightDM.Greeter.authenticationUser, name);
@@ -532,6 +532,14 @@ Item {
532532 verify(!view.locked);
533533 }
534534
535+ function test_showManualLoginHint() {
536+ LightDMController.showManualLoginHint = true;
537+ resetLoader();
538+ var i = selectUser("*other");
539+ compare(i, LightDM.Users.count - 1); // manual should be last
540+ verify(view.locked);
541+ }
542+
535543 function test_fingerprintSuccess() {
536544 var index = selectUser("has-password");
537545 unlockAndShowGreeter(); // turn on lockscreen mode
tests/qmltests/tst_Shell.qml+29−1
@@ -655,7 +655,8 @@ Rectangle {
655655 // Shell instance gets destroyed.
656656 tryCompare(shellLoader, "itemDestroyed", true);
657657
658− setLightDMMockMode("single"); // back to the default value
658+ LightDMController.reset();
659+ setLightDMMockMode("single"); // these tests default to "single"
659660
660661 AccountsService.demoEdges = false;
661662 AccountsService.demoEdgesCompleted = [];
@@ -1478,6 +1479,33 @@ Rectangle {
14781479 }
14791480 }
14801481
1482+ function test_manualLoginFlow() {
1483+ LightDMController.showManualLoginHint = true;
1484+ setLightDMMockMode("full");
1485+ loadShell("desktop");
1486+
1487+ var i = selectUser("*other");
1488+ var greeter = findChild(shell, "greeter");
1489+ var username = findChild(greeter, "username" + i);
1490+ var promptField = findChild(greeter, "promptField");
1491+ var promptHint = findChild(greeter, "promptHint");
1492+
1493+ compare(username.text, "Login");
1494+ compare(promptHint.text, "Username");
1495+
1496+ tryCompare(promptField, "activeFocus", true);
1497+ typeString("has-password");
1498+ keyClick(Qt.Key_Enter);
1499+
1500+ tryCompare(username, "text", "has-password");
1501+ tryCompare(promptHint, "text", "Passphrase");
1502+
1503+ typeString("password");
1504+ keyClick(Qt.Key_Enter);
1505+
1506+ confirmLoggedIn(true);
1507+ }
1508+
14811509 function test_launcherInverted_data() {
14821510 return [
14831511 {tag: "phone", formFactor: "phone", usageScenario: "phone", launcherInverted: true},