on xenial help sign in
Add support for LightDM hints for manual logins and hiding normal users.
f94aa28Michael Terry
plugins/LightDM/Greeter.cpp+34−2
@@ -88,7 +88,16 @@ bool Greeter::isAuthenticated() const
88QString Greeter::authenticationUser() const
88QString Greeter::authenticationUser() const
89{
89{
90 Q_D(const Greeter);
90 Q_D(const Greeter);
91+ return d->cachedAuthUser;
92+}
93+
94+void Greeter::checkAuthenticationUser()
95+{
96+ Q_D(Greeter);
97+ if (d->cachedAuthUser != d->m_greeter->authenticationUser()) {
91− return d->m_greeter->authenticationUser();
98+ d->cachedAuthUser = d->m_greeter->authenticationUser();
99+ Q_EMIT authenticationUserChanged();
100+ }
92}
101}
93
102
94QString Greeter::defaultSessionHint() const
103QString Greeter::defaultSessionHint() const
@@ -119,6 +128,18 @@ bool Greeter::hasGuestAccount() const
119 return d->m_greeter->hasGuestAccountHint();
128 return d->m_greeter->hasGuestAccountHint();
120}
129}
121
130
131+bool Greeter::showManualLoginHint() const
132+{
133+ Q_D(const Greeter);
134+ return d->m_greeter->showManualLoginHint();
135+}
136+
137+bool Greeter::hideUsersHint() const
138+{
139+ Q_D(const Greeter);
140+ return d->m_greeter->hideUsersHint();
141+}
142+
122void Greeter::authenticate(const QString &username)
143void Greeter::authenticate(const QString &username)
123{
144{
124 Q_D(Greeter);
145 Q_D(Greeter);
@@ -130,12 +151,14 @@ void Greeter::authenticate(const QString &username)
130
151
131 if (username == QStringLiteral("*guest")) {
152 if (username == QStringLiteral("*guest")) {
132 d->m_greeter->authenticateAsGuest();
153 d->m_greeter->authenticateAsGuest();
154+ } else if (username == QStringLiteral("*other")) {
155+ d->m_greeter->authenticate(nullptr);
133 } else {
156 } else {
134 d->m_greeter->authenticate(username);
157 d->m_greeter->authenticate(username);
135 }
158 }
136
159
137 Q_EMIT isAuthenticatedChanged();
160 Q_EMIT isAuthenticatedChanged();
138− Q_EMIT authenticationUserChanged();
161+ checkAuthenticationUser();
139}
162}
140
163
141void Greeter::respond(const QString &response)
164void Greeter::respond(const QString &response)
@@ -155,6 +178,8 @@ void Greeter::showPromptFilter(const QString &text, QLightDM::Greeter::PromptTyp
155 Q_D(Greeter);
178 Q_D(Greeter);
156 d->wasPrompted = true;
179 d->wasPrompted = true;
157
180
181+ checkAuthenticationUser(); // may have changed in liblightdm
182+
158 bool isDefaultPrompt = (text == dgettext("Linux-PAM", "Password: "));
183 bool isDefaultPrompt = (text == dgettext("Linux-PAM", "Password: "));
159
184
160 // Strip prompt of any colons at the end
185 // Strip prompt of any colons at the end
@@ -163,11 +188,18 @@ void Greeter::showPromptFilter(const QString &text, QLightDM::Greeter::PromptTyp
163 trimmedText.chop(1);
188 trimmedText.chop(1);
164 }
189 }
165
190
191+ if (trimmedText == "login") {
192+ // 'login' is provided untranslated by LightDM when asking for a manual
193+ // login username.
194+ trimmedText = gettext("Username");
195+ }
196+
166 Q_EMIT showPrompt(trimmedText, type == QLightDM::Greeter::PromptTypeSecret, isDefaultPrompt);
197 Q_EMIT showPrompt(trimmedText, type == QLightDM::Greeter::PromptTypeSecret, isDefaultPrompt);
167}
198}
168
199
169void Greeter::showMessageFilter(const QString &text, QLightDM::Greeter::MessageType type)
200void Greeter::showMessageFilter(const QString &text, QLightDM::Greeter::MessageType type)
170{
201{
202+ checkAuthenticationUser(); // may have changed in liblightdm
171 Q_EMIT showMessage(text, type == QLightDM::Greeter::MessageTypeError);
203 Q_EMIT showMessage(text, type == QLightDM::Greeter::MessageTypeError);
172}
204}
173
205
plugins/LightDM/Greeter.h+3
@@ -50,6 +50,8 @@ public:
50 bool promptless() const;
50 bool promptless() const;
51 QString selectUser() const;
51 QString selectUser() const;
52 bool hasGuestAccount() const;
52 bool hasGuestAccount() const;
53+ bool showManualLoginHint() const;
54+ bool hideUsersHint() const;
53
55
54public Q_SLOTS:
56public Q_SLOTS:
55 void authenticate(const QString &username=QString());
57 void authenticate(const QString &username=QString());
@@ -83,4 +85,5 @@ private Q_SLOTS:
83 void showMessageFilter(const QString &text, QLightDM::Greeter::MessageType type);
85 void showMessageFilter(const QString &text, QLightDM::Greeter::MessageType type);
84 void showPromptFilter(const QString &text, QLightDM::Greeter::PromptType type);
86 void showPromptFilter(const QString &text, QLightDM::Greeter::PromptType type);
85 void authenticationCompleteFilter();
87 void authenticationCompleteFilter();
88+ void checkAuthenticationUser();
86};
89};
plugins/LightDM/GreeterPrivate.h+8−6
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2015-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
@@ -14,10 +14,13 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
15 */
16
16
17−#ifndef UNITY_GREETER_PRIVATE_H
17+#pragma once
18−#define UNITY_GREETER_PRIVATE_H
19
18
19+#include <QObject>
20+
20−#include <QLightDM/Greeter>
21+namespace QLightDM {
22+ class Greeter;
23+}
21
24
22class GreeterPrivate
25class GreeterPrivate
23{
26{
@@ -28,6 +31,7 @@ public:
28 bool m_active;
31 bool m_active;
29 bool wasPrompted;
32 bool wasPrompted;
30 bool promptless;
33 bool promptless;
34+ QString cachedAuthUser;
31
35
32protected:
36protected:
33 Greeter * const q_ptr;
37 Greeter * const q_ptr;
@@ -35,5 +39,3 @@ protected:
35private:
39private:
36 Q_DECLARE_PUBLIC(Greeter)
40 Q_DECLARE_PUBLIC(Greeter)
37};
41};
38−
39−#endif // UNITY_GREETER_PRIVATE_H
plugins/LightDM/UsersModel.cpp+123−21
@@ -39,29 +39,63 @@ public:
39 QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
39 QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
40
40
41private:
41private:
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;
43};
58};
44
59
45MangleModel::MangleModel(QObject* parent)
60MangleModel::MangleModel(QObject* parent)
46 : QIdentityProxyModel(parent)
61 : QIdentityProxyModel(parent)
62+ , m_updatingCustomRows(false)
47{
63{
48 setSourceModel(new QLightDM::UsersModel(this));
64 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);
49}
79}
50
80
51QVariant MangleModel::data(const QModelIndex &index, int role) const
81QVariant MangleModel::data(const QModelIndex &index, int role) const
52{
82{
53 QVariant variantData;
83 QVariant variantData;
54
84
85+ if (index.row() >= rowCount())
86+ return QVariant();
87+
88+ bool isCustomRow = index.row() >= sourceRowCount();
55− if (index.row() == guestRow() && index.column() == 0) {
89+ if (isCustomRow && index.column() == 0) {
56− switch (role) {
90+ int customIndex = index.row() - sourceRowCount();
57− case QLightDM::UsersModel::NameRole:
91+ if (role == QLightDM::UsersModel::NameRole) {
58− variantData = QStringLiteral("*guest"); break;
92+ variantData = m_customRows[customIndex].name;
59− case QLightDM::UsersModel::RealNameRole:
93+ } else if (role == QLightDM::UsersModel::RealNameRole) {
60− variantData = gettext("Guest Session"); break;
94+ variantData = m_customRows[customIndex].realName;
61− case QLightDM::UsersModel::LoggedInRole:
95+ } else if (role == QLightDM::UsersModel::LoggedInRole) {
62− variantData = false; break;
96+ variantData = false;
63− case QLightDM::UsersModel::SessionRole:
97+ } else if (role == QLightDM::UsersModel::SessionRole) {
64− variantData = Greeter::instance()->defaultSessionHint(); break;
98+ variantData = Greeter::instance()->defaultSessionHint();
65 }
99 }
66 } else {
100 } else {
67 variantData = QIdentityProxyModel::data(index, role);
101 variantData = QIdentityProxyModel::data(index, role);
@@ -69,7 +103,7 @@ QVariant MangleModel::data(const QModelIndex &index, int role) const
69
103
70 // If user's real name is empty, switch to unix name
104 // If user's real name is empty, switch to unix name
71 if (role == QLightDM::UsersModel::RealNameRole && variantData.toString().isEmpty()) {
105 if (role == QLightDM::UsersModel::RealNameRole && variantData.toString().isEmpty()) {
72− variantData = QIdentityProxyModel::data(index, QLightDM::UsersModel::NameRole);
106+ variantData = data(index, QLightDM::UsersModel::NameRole);
73 } else if (role == QLightDM::UsersModel::BackgroundPathRole && variantData.toString().startsWith('#')) {
107 } else if (role == QLightDM::UsersModel::BackgroundPathRole && variantData.toString().startsWith('#')) {
74 const QString stringData = "data:image/svg+xml,<svg><rect width='100%' height='100%' fill='" + variantData.toString() + "'/></svg>";
108 const QString stringData = "data:image/svg+xml,<svg><rect width='100%' height='100%' fill='" + variantData.toString() + "'/></svg>";
75 variantData = stringData;
109 variantData = stringData;
@@ -78,25 +112,89 @@ QVariant MangleModel::data(const QModelIndex &index, int role) const
78 return variantData;
112 return variantData;
79}
113}
80
114
81−int MangleModel::guestRow() const
115+void MangleModel::addCustomRow(const CustomRow &newRow)
82{
116{
83− return Greeter::instance()->hasGuestAccount() ?
117+ for (int i = 0; i < m_customRows.size(); i++) {
84− QIdentityProxyModel::rowCount() : -1;
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();
85}
126}
86
127
87−int MangleModel::rowCount(const QModelIndex &parent) const
128+void MangleModel::removeCustomRow(const QString &rowName)
88{
129{
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+}
90
140
91− if (Greeter::instance()->hasGuestAccount() && !parent.isValid())
141+void MangleModel::updateManualRow()
92− count++;
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+ }
93
149
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)
94− return count;
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();
95}
189}
96
190
97QModelIndex MangleModel::index(int row, int column, const QModelIndex &parent) const
191QModelIndex MangleModel::index(int row, int column, const QModelIndex &parent) const
98{
192{
193+ if (row >= rowCount())
194+ return QModelIndex();
195+
196+ bool isCustomRow = row >= sourceRowCount();
99− if (row == guestRow() && !parent.isValid()) {
197+ if (isCustomRow && !parent.isValid()) {
100 return createIndex(row, column);
198 return createIndex(row, column);
101 } else {
199 } else {
102 return QIdentityProxyModel::index(row, column, parent);
200 return QIdentityProxyModel::index(row, column, parent);
@@ -124,6 +222,10 @@ bool UsersModel::lessThan(const QModelIndex &source_left, const QModelIndex &sou
124 return false;
222 return false;
125 if (rightName == QStringLiteral("*guest"))
223 if (rightName == QStringLiteral("*guest"))
126 return true;
224 return true;
225+ if (leftName == QStringLiteral("*other"))
226+ return false;
227+ if (rightName == QStringLiteral("*other"))
228+ return true;
127
229
128 return UnitySortFilterProxyModelQML::lessThan(source_left, source_right);
230 return UnitySortFilterProxyModelQML::lessThan(source_left, source_right);
129}
231}
qml/Greeter/GreeterPrompt.qml+1
@@ -194,6 +194,7 @@ FocusScope {
194 // palette color, whereas we want raisedSecondaryText.
194 // palette color, whereas we want raisedSecondaryText.
195 Label {
195 Label {
196 id: hint
196 id: hint
197+ objectName: "promptHint"
197 anchors {
198 anchors {
198 left: parent.left
199 left: parent.left
199 right: parent.right
200 right: parent.right
qml/Greeter/LoginList.qml+4−1
@@ -201,7 +201,10 @@ StyledItem {
201 // Add an offset to bottomMargin for any items below the highlight
201 // Add an offset to bottomMargin for any items below the highlight
202 bottomMargin: -(units.gu(4) + (parent.belowHighlight ? parent.belowOffset : 0))
202 bottomMargin: -(units.gu(4) + (parent.belowHighlight ? parent.belowOffset : 0))
203 }
203 }
204− text: realName
204+ text: userList.currentIndex === index
205+ && name === "*other"
206+ && LightDMService.greeter.authenticationUser !== ""
207+ ? LightDMService.greeter.authenticationUser : realName
205 color: userList.currentIndex !== index ? theme.palette.normal.raised
208 color: userList.currentIndex !== index ? theme.palette.normal.raised
206 : theme.palette.normal.raisedText
209 : theme.palette.normal.raisedText
207
210
tests/mocks/liblightdm/MockController.cpp+28
@@ -70,6 +70,8 @@ void MockController::reset()
70 setUserMode(userMode);
70 setUserMode(userMode);
71 setSessionMode("full");
71 setSessionMode("full");
72 setNumSessions(numFullSessions());
72 setNumSessions(numFullSessions());
73+ setShowManualLoginHint(false);
74+ setHideUsersHint(false);
73}
75}
74
76
75QString MockController::selectUserHint() const
77QString MockController::selectUserHint() const
@@ -111,6 +113,32 @@ void MockController::setHasGuestAccountHint(bool hasGuestAccountHint)
111 }
113 }
112}
114}
113
115
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+
114QString MockController::userMode() const
142QString MockController::userMode() const
115{
143{
116 return m_userMode;
144 return m_userMode;
tests/mocks/liblightdm/MockController.h+12
@@ -29,6 +29,8 @@ class Q_DECL_EXPORT MockController : public QObject
29 Q_PROPERTY(QString selectUserHint READ selectUserHint WRITE setSelectUserHint NOTIFY selectUserHintChanged)
29 Q_PROPERTY(QString selectUserHint READ selectUserHint WRITE setSelectUserHint NOTIFY selectUserHintChanged)
30 Q_PROPERTY(bool selectGuestHint READ selectGuestHint WRITE setSelectGuestHint NOTIFY selectGuestHintChanged)
30 Q_PROPERTY(bool selectGuestHint READ selectGuestHint WRITE setSelectGuestHint NOTIFY selectGuestHintChanged)
31 Q_PROPERTY(bool hasGuestAccountHint READ hasGuestAccountHint WRITE setHasGuestAccountHint NOTIFY hasGuestAccountHintChanged)
31 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)
32
34
33 // single, single-pin, single-passphrase, full
35 // single, single-pin, single-passphrase, full
34 Q_PROPERTY(QString userMode READ userMode WRITE setUserMode NOTIFY userModeChanged)
36 Q_PROPERTY(QString userMode READ userMode WRITE setUserMode NOTIFY userModeChanged)
@@ -59,6 +61,12 @@ public:
59 bool hasGuestAccountHint() const;
61 bool hasGuestAccountHint() const;
60 void setHasGuestAccountHint(bool hasGuestAccountHint);
62 void setHasGuestAccountHint(bool hasGuestAccountHint);
61
63
64+ bool showManualLoginHint() const;
65+ void setShowManualLoginHint(bool showManualLoginHint);
66+
67+ bool hideUsersHint() const;
68+ void setHideUsersHint(bool hideUsersHint);
69+
62 QString userMode() const;
70 QString userMode() const;
63 void setUserMode(const QString &userMode);
71 void setUserMode(const QString &userMode);
64
72
@@ -88,6 +96,8 @@ Q_SIGNALS:
88 void selectUserHintChanged();
96 void selectUserHintChanged();
89 void selectGuestHintChanged();
97 void selectGuestHintChanged();
90 void hasGuestAccountHintChanged();
98 void hasGuestAccountHintChanged();
99+ void showManualLoginHintChanged();
100+ void hideUsersHintChanged();
91 void userModeChanged();
101 void userModeChanged();
92 void sessionModeChanged();
102 void sessionModeChanged();
93 void numSessionsChanged();
103 void numSessionsChanged();
@@ -101,6 +111,8 @@ private:
101 QString m_selectUserHint;
111 QString m_selectUserHint;
102 bool m_selectGuestHint;
112 bool m_selectGuestHint;
103 bool m_hasGuestAccountHint;
113 bool m_hasGuestAccountHint;
114+ bool m_showManualLoginHint;
115+ bool m_hideUsersHint;
104 QString m_userMode;
116 QString m_userMode;
105 QString m_sessionMode;
117 QString m_sessionMode;
106 QList<SessionItem> m_fullSessions;
118 QList<SessionItem> m_fullSessions;
tests/mocks/liblightdm/MockGreeter.cpp+15−3
@@ -67,17 +67,17 @@ QString Greeter::defaultSessionHint() const
67
67
68bool Greeter::hideUsersHint() const
68bool Greeter::hideUsersHint() const
69{
69{
70− return false;
70+ return MockController::instance()->hideUsersHint();
71}
71}
72
72
73bool Greeter::showManualLoginHint() const
73bool Greeter::showManualLoginHint() const
74{
74{
75− return true;
75+ return MockController::instance()->showManualLoginHint();
76}
76}
77
77
78bool Greeter::showRemoteLoginHint() const
78bool Greeter::showRemoteLoginHint() const
79{
79{
80− return true;
80+ return false;
81}
81}
82
82
83QString Greeter::selectUserHint() const
83QString Greeter::selectUserHint() const
@@ -141,6 +141,11 @@ void Greeter::handleAuthenticate()
141{
141{
142 Q_D(Greeter);
142 Q_D(Greeter);
143
143
144+ if (d->authenticationUser.isEmpty()) {
145+ Q_EMIT showPrompt("Username: ", Greeter::PromptTypeQuestion);
146+ return;
147+ }
148+
144 // Send out any messages we need to
149 // Send out any messages we need to
145 if (d->authenticationUser == "info-prompt")
150 if (d->authenticationUser == "info-prompt")
146 Q_EMIT showMessage("Welcome to Unity Greeter", Greeter::MessageTypeInfo);
151 Q_EMIT showMessage("Welcome to Unity Greeter", Greeter::MessageTypeInfo);
@@ -219,6 +224,13 @@ void Greeter::respond(const QString &response)
219{
224{
220 Q_D(Greeter);
225 Q_D(Greeter);
221
226
227+ if (d->authenticationUser.isEmpty()) {
228+ // A manual login, our first question was which username
229+ d->authenticationUser = response;
230+ handleAuthenticate();
231+ return;
232+ }
233+
222 if (d->authenticationUser == "no-response")
234 if (d->authenticationUser == "no-response")
223 return;
235 return;
224 else if (d->authenticationUser == "two-factor") {
236 else if (d->authenticationUser == "two-factor") {
tests/mocks/liblightdm/MockUsersModel.cpp+4
@@ -67,6 +67,10 @@ UsersModel::UsersModel(QObject *parent)
67
67
68 connect(MockController::instance(), &MockController::hasGuestAccountHintChanged,
68 connect(MockController::instance(), &MockController::hasGuestAccountHintChanged,
69 this, &UsersModel::resetEntries);
69 this, &UsersModel::resetEntries);
70+ connect(MockController::instance(), &MockController::showManualLoginHintChanged,
71+ this, &UsersModel::resetEntries);
72+ connect(MockController::instance(), &MockController::hideUsersHintChanged,
73+ this, &UsersModel::resetEntries);
70 connect(MockController::instance(), &MockController::userModeChanged,
74 connect(MockController::instance(), &MockController::userModeChanged,
71 this, &UsersModel::resetEntries);
75 this, &UsersModel::resetEntries);
72 connect(MockController::instance(), &MockController::sessionNameChanged,
76 connect(MockController::instance(), &MockController::sessionNameChanged,
tests/plugins/LightDM/IntegratedLightDM/usersmodel.cpp+53−2
@@ -79,11 +79,9 @@ private Q_SLOTS:
79 QCOMPARE(findName(model, QStringLiteral("*guest")), -1);
79 QCOMPARE(findName(model, QStringLiteral("*guest")), -1);
80
80
81 QLightDM::MockController::instance()->setHasGuestAccountHint(true);
81 QLightDM::MockController::instance()->setHasGuestAccountHint(true);
82− recreateModel();
83
82
84 int i = findName(model, QStringLiteral("*guest"));
83 int i = findName(model, QStringLiteral("*guest"));
85 QVERIFY(i >= 0);
84 QVERIFY(i >= 0);
86− QCOMPARE(i, model->count() - 1);
87
85
88 auto realName = model->data(i, QLightDM::UsersModel::RealNameRole).toString();
86 auto realName = model->data(i, QLightDM::UsersModel::RealNameRole).toString();
89 QCOMPARE(realName, QStringLiteral("Guest Session"));
87 QCOMPARE(realName, QStringLiteral("Guest Session"));
@@ -95,6 +93,59 @@ private Q_SLOTS:
95 QCOMPARE(session, Greeter::instance()->defaultSessionHint());
93 QCOMPARE(session, Greeter::instance()->defaultSessionHint());
96 }
94 }
97
95
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+
98private:
149private:
99 UsersModel *model = nullptr;
150 UsersModel *model = nullptr;
100};
151};
tests/qmltests/Greeter/tst_Greeter.qml+9−1
@@ -201,7 +201,7 @@ Item {
201 var i = getIndexOf(name);
201 var i = getIndexOf(name);
202 compare(view.currentIndex, i);
202 compare(view.currentIndex, i);
203 compare(AccountsService.user, name);
203 compare(AccountsService.user, name);
204− if (name === "*guest") // guest has no authenticationUser set
204+ if (name[0] === "*") // custom rows have no authenticationUser set
205 compare(LightDM.Greeter.authenticationUser, "");
205 compare(LightDM.Greeter.authenticationUser, "");
206 else
206 else
207 compare(LightDM.Greeter.authenticationUser, name);
207 compare(LightDM.Greeter.authenticationUser, name);
@@ -612,6 +612,14 @@ Item {
612 verify(!view.locked);
612 verify(!view.locked);
613 }
613 }
614
614
615+ function test_showManualLoginHint() {
616+ LightDMController.showManualLoginHint = true;
617+ resetLoader();
618+ var i = selectUser("*other");
619+ compare(i, LightDM.Users.count - 1); // manual should be last
620+ verify(view.locked);
621+ }
622+
615 function test_fingerprintSuccess() {
623 function test_fingerprintSuccess() {
616 var index = selectUser("has-password");
624 var index = selectUser("has-password");
617 unlockAndShowGreeter(); // turn on lockscreen mode
625 unlockAndShowGreeter(); // turn on lockscreen mode
tests/qmltests/tst_Shell.qml+29−1
@@ -654,7 +654,8 @@ Rectangle {
654 // Shell instance gets destroyed.
654 // Shell instance gets destroyed.
655 tryCompare(shellLoader, "itemDestroyed", true);
655 tryCompare(shellLoader, "itemDestroyed", true);
656
656
657+ LightDMController.reset();
657− setLightDMMockMode("single"); // back to the default value
658+ setLightDMMockMode("single"); // these tests default to "single"
658
659
659 AccountsService.demoEdges = false;
660 AccountsService.demoEdges = false;
660 AccountsService.demoEdgesCompleted = [];
661 AccountsService.demoEdgesCompleted = [];
@@ -1477,6 +1478,33 @@ Rectangle {
1477 }
1478 }
1478 }
1479 }
1479
1480
1481+ function test_manualLoginFlow() {
1482+ LightDMController.showManualLoginHint = true;
1483+ setLightDMMockMode("full");
1484+ loadShell("desktop");
1485+
1486+ var i = selectUser("*other");
1487+ var greeter = findChild(shell, "greeter");
1488+ var username = findChild(greeter, "username" + i);
1489+ var promptField = findChild(greeter, "promptField");
1490+ var promptHint = findChild(greeter, "promptHint");
1491+
1492+ compare(username.text, "Login");
1493+ compare(promptHint.text, "Username");
1494+
1495+ tryCompare(promptField, "activeFocus", true);
1496+ typeString("has-password");
1497+ keyClick(Qt.Key_Enter);
1498+
1499+ tryCompare(username, "text", "has-password");
1500+ tryCompare(promptHint, "text", "Passphrase");
1501+
1502+ typeString("password");
1503+ keyClick(Qt.Key_Enter);
1504+
1505+ confirmLoggedIn(true);
1506+ }
1507+
1480 function test_launcherInverted_data() {
1508 function test_launcherInverted_data() {
1481 return [
1509 return [
1482 {tag: "phone", formFactor: "phone", usageScenario: "phone", launcherInverted: true},
1510 {tag: "phone", formFactor: "phone", usageScenario: "phone", launcherInverted: true},