on xenial help sign in
Add a frontend to the sessions model and enable a session chooser in the greeter.

Approved by: Michael Terry

commit3e9025fd780ec8600b5c383a5d096967bf840b2d authorJosh Arenson <joshua.arenson@canonical.com> dateThu 22 Sep 2016, 07:38 (10 years ago) parent8a74a8eeec4744
31 files changed+762−116
plugins/LightDM/IntegratedLightDM/liblightdm/Greeter.cpp−5 plugins/LightDM/IntegratedLightDM/liblightdm/Greeter.h−2 plugins/LightDM/SessionsModel.cpp+22−16 plugins/LightDM/plugin.cpp+1−1 qml/Greeter/FullLightDMImpl.qml+2 qml/Greeter/Greeter.qml+17−1 qml/Greeter/GreeterPrompt.qml+19−2 qml/Greeter/IntegratedLightDMImpl.qml+2−1 qml/Greeter/LightDMService.qml+2 qml/Greeter/LoginAreaContainer.qml+38 qml/Greeter/LoginList.qml+72−16 qml/Greeter/NarrowView.qml+1 qml/Greeter/SessionIcon.qml+47 qml/Greeter/SessionsList.qml+187 qml/Greeter/WideView.qml+103−39 qml/Greeter/graphics/session_icons/gnome_badge.png qml/Greeter/graphics/session_icons/kde_badge.png qml/Greeter/graphics/session_icons/recovery_console_badge.png qml/Greeter/graphics/session_icons/ubuntu_badge.png qml/Greeter/graphics/session_icons/unknown_badge.png tests/mocks/LightDM/IntegratedLightDM/MockSessionsModel.cpp+29 tests/mocks/LightDM/IntegratedLightDM/MockSessionsModel.h+12−2 tests/mocks/LightDM/IntegratedLightDM/liblightdm/Greeter.cpp−5 tests/mocks/LightDM/IntegratedLightDM/liblightdm/Greeter.h−2 tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModel.cpp+22 tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModel.h+3−3 tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModelPrivate.cpp+27−17 tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModelPrivate.h+5−1 tests/mocks/LightDM/IntegratedLightDM/plugin.cpp+1−1 tests/plugins/LightDM/IntegratedLightDM/sessionsmodel.cpp+32 tests/qmltests/Greeter/tst_WideView.qml+118−2
plugins/LightDM/IntegratedLightDM/liblightdm/Greeter.cpp−5
@@ -78,11 +78,6 @@ bool Greeter::showRemoteLoginHint() const
7878 return true;
7979}
8080
81−bool Greeter::lockHint () const
82−{
83− return false;
84−}
85−
8681QString Greeter::selectUserHint() const
8782{
8883 return QLatin1String("");
plugins/LightDM/IntegratedLightDM/liblightdm/Greeter.h−2
@@ -45,7 +45,6 @@ class Q_DECL_EXPORT Greeter : public QObject
4545
4646 Q_PROPERTY(QString hostname READ hostname CONSTANT)
4747 Q_PROPERTY(bool hasGuestAccount READ hasGuestAccountHint CONSTANT)
48− Q_PROPERTY(bool locked READ lockHint CONSTANT)
4948
5049 Q_ENUMS(PromptType MessageType)
5150
@@ -68,7 +67,6 @@ public:
6867 bool hideUsersHint() const;
6968 bool showManualLoginHint() const;
7069 bool showRemoteLoginHint() const;
71− bool lockHint () const;
7270 bool hasGuestAccountHint() const;
7371 QString selectUserHint() const;
7472 bool selectGuestHint() const;
plugins/LightDM/SessionsModel.cpp+22−16
@@ -47,15 +47,15 @@ void SessionsModel::setIconSearchDirectories(const QList<QUrl> searchDirectories
4747 Q_EMIT iconSearchDirectoriesChanged();
4848}
4949
50−QUrl SessionsModel::iconUrl(const QString sessionName) const
50+QUrl SessionsModel::iconUrl(const QString sessionKey) const
5151{
5252 Q_FOREACH(const QUrl& searchDirectory, m_iconSearchDirectories)
5353 {
5454 // This is an established icon naming convention
5555 QString customIconUrl = searchDirectory.toString(QUrl::StripTrailingSlash) +
56− "/custom_" + sessionName + "_badge.png";
56+ "/custom_" + sessionKey + "_badge.png";
5757 QString iconUrl = searchDirectory.toString(QUrl::StripTrailingSlash) +
58− "/" + sessionName + "_badge.png";
58+ "/" + sessionKey + "_badge.png";
5959
6060 QFile customIconFile(customIconUrl);
6161 QFile iconFile(iconUrl);
@@ -63,29 +63,35 @@ QUrl SessionsModel::iconUrl(const QString sessionName) const
6363 return QUrl(customIconUrl);
6464 } else if (iconFile.exists()) {
6565 return QUrl(iconUrl);
66− } else{
66+ } else {
6767 // Search the legacy way
6868 QString path = searchDirectory.toString(QUrl::StripTrailingSlash) + "/";
69− if (sessionName == "ubuntu" || sessionName == "ubuntu-2d") {
69+ bool iconFound = false;
70+ if (sessionKey == "ubuntu" || sessionKey == "ubuntu-2d") {
7071 path += "ubuntu_badge.png";
72+ iconFound = true;
7173 } else if(
72− sessionName == "gnome-classic" ||
73− sessionName == "gnome-flashback-compiz" ||
74− sessionName == "gnome-flashback-metacity" ||
75− sessionName == "gnome-shell" ||
76− sessionName == "gnome-wayland" ||
77− sessionName == "gnome"
74+ sessionKey == "gnome-classic" ||
75+ sessionKey == "gnome-flashback-compiz" ||
76+ sessionKey == "gnome-flashback-metacity" ||
77+ sessionKey == "gnome-shell" ||
78+ sessionKey == "gnome-wayland" ||
79+ sessionKey == "gnome"
7880 ){
7981 path += "gnome_badge.png";
80− } else if (sessionName == "plasma") {
82+ iconFound = true;
83+ } else if (sessionKey == "plasma") {
8184 path += "kde_badge.png";
82− } else if (sessionName == "xterm") {
85+ iconFound = true;
86+ } else if (sessionKey == "xterm") {
8387 path += "recovery_console_badge.png";
84− } else if (sessionName == "remote-login") {
88+ iconFound = true;
89+ } else if (sessionKey == "remote-login") {
8590 path += "remote_login_help.png";
91+ iconFound = true;
8692 }
8793
88− if (QFile(path).exists()) {
94+ if (QFile(path).exists() && iconFound) {
8995 return path;
9096 }
9197 }
@@ -99,7 +105,7 @@ QVariant SessionsModel::data(const QModelIndex& index, int role) const
99105{
100106 switch (role) {
101107 case SessionsModel::IconRole:
102− return iconUrl(m_model->data(index, Qt::DisplayRole).toString());
108+ return iconUrl(m_model->data(index, QLightDM::SessionsModel::KeyRole).toString());
103109 default:
104110 return m_model->data(index, role);
105111 }
plugins/LightDM/plugin.cpp+1−1
@@ -82,7 +82,7 @@ void PLUGIN_CLASSNAME::registerTypes(const char *uri)
8282 qRegisterMetaType<QLightDM::Greeter::PromptType>("QLightDM::Greeter::PromptType");
8383
8484 qmlRegisterSingletonType<SessionsModel>(uri, 0, 1, "Sessions", sessions_provider);
85− qmlRegisterUncreatableType<SessionsModel>(uri, 0, 1, "SessionRoles", QStringLiteral("Type is not instantiable"));
85+ qmlRegisterUncreatableType<QLightDM::SessionsModel>(uri, 0, 1, "SessionRoles", QStringLiteral("Type is not instantiable"));
8686
8787 qmlRegisterSingletonType<UsersModel>(uri, 0, 1, "Users", users_provider);
8888 qmlRegisterUncreatableType<QLightDM::UsersModel>(uri, 0, 1, "UserRoles", QStringLiteral("Type is not instantiable"));
qml/Greeter/FullLightDMImpl.qml+2
@@ -22,6 +22,8 @@ Item{
2222
2323 property var greeter: LightDM.Greeter
2424 property var infographic: LightDM.Infographic
25+ property var sessions: LightDM.Sessions
26+ property var sessionRoles: LightDM.SessionRoles
2527 property var users: LightDM.Users
2628 property var userRoles: LightDM.UserRoles
2729
qml/Greeter/Greeter.qml+17−1
@@ -132,6 +132,22 @@ Showable {
132132 return d.startUnlock(true /* toTheRight */);
133133 }
134134
135+ function sessionToStart() {
136+ for (var i = 0; i < LightDMService.sessions.count; i++) {
137+ var session = LightDMService.sessions.data(i,
138+ LightDMService.sessionRoles.KeyRole);
139+ if (loader.item.sessionToStart === session) {
140+ return session;
141+ }
142+ }
143+
144+ if (loader.item.sessionToStart === LightDMService.greeter.defaultSession) {
145+ return LightDMService.greeter.defaultSession;
146+ } else {
147+ return "ubuntu"; // The default / fallback
148+ }
149+ }
150+
135151 QtObject {
136152 id: d
137153
@@ -197,7 +213,7 @@ Showable {
197213
198214 function login() {
199215 d.waiting = true;
200− if (LightDMService.greeter.startSessionSync()) {
216+ if (LightDMService.greeter.startSessionSync(root.sessionToStart())) {
201217 sessionStarted();
202218 hideView();
203219 } else if (loader.item) {
qml/Greeter/GreeterPrompt.qml+19−2
@@ -70,19 +70,36 @@ FocusScope {
7070 }
7171 }
7272
73− AbstractButton {
73+ Rectangle {
7474 id: promptButton
7575 objectName: "promptButton"
7676 anchors.fill: parent
7777 visible: !root.isPrompt
7878
79− onClicked: {
79+ function triggered() {
8080 if (d.enabled) {
8181 d.enabled = false;
8282 root.clicked();
8383 }
8484 }
8585
86+ Rectangle {
87+ height: parent.height;
88+ width: parent.width
89+ color: "transparent"
90+ border {
91+ color: d.textColor
92+ width: units.dp(1)
93+ }
94+ }
95+
96+ Keys.onReturnPressed: triggered();
97+ Keys.onEnterPressed: triggered();
98+ MouseArea {
99+ anchors.fill: parent
100+ onClicked: parent.triggered();
101+ }
102+
86103 Label {
87104 anchors.centerIn: parent
88105 color: d.textColor
qml/Greeter/IntegratedLightDMImpl.qml+2−1
@@ -22,7 +22,8 @@ Item{
2222
2323 property var greeter: LightDM.Greeter
2424 property var infographic: LightDM.Infographic
25+ property var sessions: LightDM.Sessions
26+ property var sessionRoles: LightDM.SessionRoles
2527 property var users: LightDM.Users
2628 property var userRoles: LightDM.UserRoles
27−
2829}
qml/Greeter/LightDMService.qml+2
@@ -27,6 +27,8 @@ Loader {
2727
2828 property var greeter: d.valid ? loader.item.greeter : null
2929 property var infographic: d.valid ? loader.item.infographic : null
30+ property var sessions: d.valid ? loader.item.sessions : null
31+ property var sessionRoles: d.valid ? loader.item.sessionRoles : null
3032 property var users: d.valid ? loader.item.users : null
3133 property var userRoles: d.valid ? loader.item.userRoles : null
3234
qml/Greeter/LoginAreaContainer.qmlnew file+38
@@ -0,0 +1,38 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+
20+Item {
21+ BorderImage {
22+ anchors {
23+ fill: parent
24+ topMargin: -units.gu(1)
25+ leftMargin: -units.gu(1.5)
26+ rightMargin: -units.gu(1.5)
27+ bottomMargin: -units.gu(1.5)
28+ }
29+ source: "../Stages/graphics/dropshadow2gu.sci"
30+ opacity: 0.35
31+ }
32+
33+ UbuntuShape {
34+ anchors.fill: parent
35+ aspect: UbuntuShape.Flat
36+ backgroundColor: theme.palette.normal.raised
37+ }
38+}
qml/Greeter/LoginList.qml+72−16
@@ -18,6 +18,7 @@ import QtQuick 2.4
1818import QtGraphicalEffects 1.0
1919import Ubuntu.Components 1.3
2020import "../Components"
21+import "." 0.1
2122
2223StyledItem {
2324 id: root
@@ -29,15 +30,20 @@ StyledItem {
2930 property bool locked
3031 property bool waiting
3132
33+ readonly property alias passwordInput: passwordInput
3234 readonly property int numAboveBelow: 4
3335 readonly property int cellHeight: units.gu(5)
3436 readonly property int highlightedHeight: units.gu(15)
3537 readonly property int moveDuration: 200
38+ property string selectedSession
39+ property string currentSession
3640 readonly property string currentUser: userList.currentItem.username
3741 property bool wasPrompted: false
3842
39− signal selected(int index)
43+ signal loginListSessionChanged(string session)
4044 signal responded(string response)
45+ signal selected(int index)
46+ signal sessionChooserButtonClicked()
4147
4248 function tryToUnlock() {
4349 if (wasPrompted) {
@@ -114,19 +120,7 @@ StyledItem {
114120 userList.currentIndex = currentIndex;
115121 }
116122
117− BorderImage {
118− anchors {
119− fill: highlightItem
120− topMargin: -units.gu(1)
121− leftMargin: -units.gu(1.5)
122− rightMargin: -units.gu(1.5)
123− bottomMargin: -units.gu(1.5)
124− }
125− source: "../Stages/graphics/dropshadow2gu.sci"
126− opacity: 0.35
127− }
128−
129− UbuntuShape {
123+ LoginAreaContainer {
130124 id: highlightItem
131125 anchors {
132126 left: parent.left
@@ -135,9 +129,8 @@ StyledItem {
135129 rightMargin: units.gu(2)
136130 verticalCenter: parent.verticalCenter
137131 }
132+
138133 height: root.highlightedHeight
139− aspect: UbuntuShape.Flat
140− backgroundColor: theme.palette.normal.raised
141134 }
142135
143136 ListView {
@@ -172,6 +165,7 @@ StyledItem {
172165
173166 readonly property bool belowHighlight: (userList.currentIndex < 0 && index > 0) || (userList.currentIndex >= 0 && index > userList.currentIndex)
174167 readonly property int belowOffset: root.highlightedHeight - root.cellHeight
168+ readonly property string userSession: session
175169 readonly property string username: name
176170
177171 opacity: {
@@ -236,6 +230,67 @@ StyledItem {
236230 }
237231 }
238232
233+ // Use an AbstractButton due to icon limitations with Button
234+ AbstractButton {
235+ id: sessionChooser
236+ objectName: "sessionChooserButton"
237+
238+ readonly property alias icon: badge.source
239+
240+ visible: LightDMService.sessions.count > 1 &&
241+ !LightDMService.users.data(userList.currentIndex, LightDMService.userRoles.LoggedInRole)
242+
243+ height: units.gu(3.5)
244+ width: units.gu(3.5)
245+
246+ activeFocusOnTab: true
247+ anchors {
248+ right: highlightItem.right
249+ rightMargin: units.gu(2)
250+
251+ top: highlightItem.top
252+ topMargin: units.gu(1.5)
253+ }
254+
255+ Rectangle {
256+ id: badgeHighlight
257+
258+ anchors.fill: parent
259+ visible: parent.activeFocus
260+ color: "transparent"
261+ border.color: theme.palette.normal.focus
262+ border.width: units.dp(1)
263+ radius: 3
264+ }
265+
266+ Icon {
267+ id: badge
268+ anchors.fill: parent
269+ anchors.margins: units.dp(3)
270+ keyColor: "#ffffff" // icon providers give us white icons
271+ color: theme.palette.normal.raisedSecondaryText
272+ source: LightDMService.sessions.iconUrl(root.currentSession)
273+ }
274+
275+ Keys.onReturnPressed: {
276+ sessionChooserButtonClicked();
277+ event.accepted = true;
278+ }
279+
280+ onClicked: {
281+ sessionChooserButtonClicked();
282+ }
283+
284+ // Refresh the icon path if looking at different places at runtime
285+ // this is mainly for testing
286+ Connections {
287+ target: LightDMService.sessions
288+ onIconSearchDirectoriesChanged: {
289+ badge.source = LightDMService.sessions.iconUrl(root.currentSession)
290+ }
291+ }
292+ }
293+
239294 FadingLabel {
240295 id: infoLabel
241296 objectName: "infoLabel"
@@ -270,6 +325,7 @@ StyledItem {
270325 width: highlightItem.width - anchors.margins * 2
271326 opacity: userList.movingInternally ? 0 : 1
272327
328+ activeFocusOnTab: true
273329 isAlphanumeric: root.alphanumeric
274330
275331 onClicked: root.tryToUnlock()
qml/Greeter/NarrowView.qml+1
@@ -31,6 +31,7 @@ FocusScope {
3131 property bool alphanumeric
3232 property var userModel // unused
3333 property alias infographicModel: coverPage.infographicModel
34+ property string sessionToStart
3435 property bool waiting
3536 readonly property bool fullyShown: coverPage.showProgress === 1 || lockscreen.shown
3637 readonly property bool required: coverPage.required || lockscreen.required
qml/Greeter/SessionIcon.qmlnew file+47
@@ -0,0 +1,47 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+
19+Image {
20+ id: root
21+
22+ property color color
23+ readonly property string fragColorShader:
24+ "
25+ varying highp vec2 qt_TexCoord0;
26+ uniform sampler2D source;
27+ uniform lowp vec4 color;
28+ uniform lowp float qt_Opacity;
29+
30+ void main()
31+ {
32+ lowp vec4 sourceColor = texture2D(source, qt_TexCoord0);
33+ gl_FragColor = color * sourceColor.a * qt_Opacity;
34+ }
35+ "
36+
37+ fillMode: Image.PreserveAspectFit
38+ height: sourceSize.height
39+
40+ ShaderEffect {
41+ readonly property color color: root.color
42+ readonly property Image source: parent
43+
44+ height: source.height; width: source.width
45+ fragmentShader: fragColorShader
46+ }
47+}
qml/Greeter/SessionsList.qmlnew file+187
@@ -0,0 +1,187 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+import Ubuntu.Components.ListItems 1.3
20+import "." 0.1
21+import "../Components"
22+
23+Item {
24+ id: root
25+ objectName: "sessionsList"
26+
27+ property string initiallySelectedSession
28+ signal sessionSelected(string sessionKey)
29+ signal showLoginList()
30+
31+ onInitiallySelectedSessionChanged: {
32+ sessionsList.currentIndex = getSelectedIndex();
33+ sessionsList.positionViewAtIndex(sessionsList.currentIndex, ListView.Contain);
34+ }
35+
36+ function getSelectedIndex() {
37+ for (var i = 0; i < sessionsList.model.count; i++) {
38+ var key = sessionsList.model.get(i).key
39+ if (key === initiallySelectedSession) {
40+ return i;
41+ }
42+ }
43+ }
44+
45+ function currentKey() {
46+ var session = LightDMService.sessions.data(
47+ sessionsList.currentIndex, LightDMService.sessionRoles.KeyRole)
48+ return session;
49+ }
50+
51+ Keys.onEnterPressed: {
52+ showLoginList(); // Session is already selected
53+ event.accepted = true;
54+ }
55+
56+ Keys.onEscapePressed: {
57+ showLoginList();
58+ event.accepted = true;
59+ }
60+
61+ Keys.onReturnPressed: {
62+ showLoginList();
63+ event.accepted = true;
64+ }
65+
66+ Keys.onDownPressed: {
67+ if (sessionsList.currentIndex < sessionsList.model.count - 1)
68+ sessionsList.currentIndex++;
69+ sessionSelected(currentKey());
70+ event.accepted = true;
71+ }
72+
73+ Keys.onUpPressed: {
74+ if (sessionsList.currentIndex > 0)
75+ sessionsList.currentIndex--;
76+ sessionSelected(currentKey());
77+ event.accepted = true;
78+ }
79+
80+ LoginAreaContainer {
81+ readonly property real margins: sessionsList.anchors.margins
82+ readonly property real prefferedHeight: {
83+ if (sessionsList.currentItem) {
84+ return (sessionsList.currentItem.height *
85+ (1 + sessionsList.model.count)) + 2 * margins
86+ } else {
87+ return sessionsList.headerItem.height + 2 * margins
88+ }
89+ }
90+
91+ height: prefferedHeight < parent.height ? prefferedHeight : parent.height - units.gu(4)
92+ width: parent.width
93+
94+ anchors {
95+ left: parent.left
96+ right: parent.right
97+ verticalCenter: parent.verticalCenter
98+ }
99+
100+ UbuntuListView {
101+ id: sessionsList
102+
103+ anchors {
104+ top: parent.top
105+ left: parent.left
106+ right: parent.right
107+ margins: units.gu(2)
108+ }
109+
110+ height: parent.height - headerItem.height
111+
112+ boundsBehavior: Flickable.StopAtBounds
113+
114+ model: LightDMService.sessions
115+ header: ListItemLayout {
116+ id: header
117+
118+ padding.leading: 0 // handled by parent's margins
119+
120+ title.color: theme.palette.normal.raisedText
121+ title.font.pixelSize: units.gu(2.1)
122+ title.text: i18n.tr("Select desktop environment")
123+
124+ Icon {
125+ id: icon
126+ width: units.gu(3)
127+ SlotsLayout.position: SlotsLayout.Leading
128+ name: "go-previous"
129+
130+ MouseArea {
131+ anchors.fill: parent
132+ onClicked: showLoginList()
133+ }
134+ }
135+ }
136+
137+ headerPositioning: ListView.OverlayHeader
138+ highlightFollowsCurrentItem: false
139+
140+ delegate: ListItem {
141+ id: delegate
142+ objectName: "sessionDelegate" + index
143+
144+ divider.visible: false
145+
146+ visible: y > sessionsList.headerItem.y
147+ + sessionsList.headerItem.height
148+ - sessionsList.anchors.margins
149+
150+ MouseArea {
151+ anchors.fill: parent
152+ onClicked: {
153+ sessionsList.currentIndex = index
154+ sessionSelected(key)
155+ showLoginList()
156+ }
157+ }
158+
159+ Rectangle {
160+ height: parent.height
161+ width: parent.width
162+ color: "transparent"
163+ border {
164+ color: theme.palette.normal.focus
165+ width: units.gu(0.2)
166+ }
167+
168+ visible: index === sessionsList.currentIndex
169+ }
170+
171+ ListItemLayout {
172+ id: layout
173+
174+ SessionIcon {
175+ id: sessionIcon
176+ source: icon_url
177+ SlotsLayout.position: SlotsLayout.Leading
178+ color: theme.palette.normal.raisedSecondaryText
179+ }
180+
181+ title.text: display
182+ title.color: theme.palette.normal.raisedText
183+ }
184+ }
185+ }
186+ }
187+}
qml/Greeter/WideView.qml+103−39
@@ -16,25 +16,28 @@
1616
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3
19+import "." 0.1
1920
2021FocusScope {
2122 id: root
2223 focus: true
2324
25+ property alias background: coverPage.background
26+ property alias backgroundTopMargin: coverPage.backgroundTopMargin
2427 property alias dragHandleLeftMargin: coverPage.dragHandleLeftMargin
28+ property alias infographicModel: coverPage.infographicModel
2529 property alias launcherOffset: coverPage.launcherOffset
2630 property alias currentIndex: loginList.currentIndex
2731 property int delayMinutes // TODO
28− property alias backgroundTopMargin: coverPage.backgroundTopMargin
29− property alias background: coverPage.background
30− property bool locked
3132 property alias alphanumeric: loginList.alphanumeric
32− property alias userModel: loginList.model
33− property alias infographicModel: coverPage.infographicModel
34− property bool waiting
33+ property alias locked: loginList.locked
34+ property alias sessionToStart: loginList.currentSession
35+ property alias waiting: loginList.waiting
36+ property var userModel // Set from outside
37+
38+ readonly property bool animating: coverPage.showAnimation.running || coverPage.hideAnimation.running
3539 readonly property bool fullyShown: coverPage.showProgress === 1
3640 readonly property bool required: coverPage.required
37− readonly property bool animating: coverPage.showAnimation.running || coverPage.hideAnimation.running
3841
3942 // so that it can be replaced in tests with a mock object
4043 property var inputMethod: Qt.inputMethod
@@ -44,38 +47,22 @@ FocusScope {
4447 signal tease()
4548 signal emergencyCall() // unused
4649
47− function showMessage(html) {
48− loginList.showMessage(html);
49− }
50−
51− function showPrompt(text, isSecret, isDefaultPrompt) {
52− loginList.showPrompt(text, isSecret, isDefaultPrompt);
53− }
54−
55− function showLastChance() {
56− // TODO
57− }
58−
59− function hide() {
60− coverPage.hide();
61− }
62−
63− function notifyAuthenticationSucceeded(showFakePassword) {
64− // Nothing needed
65− }
66−
6750 function notifyAuthenticationFailed() {
6851 loginList.showError();
6952 }
7053
71− function showErrorMessage(msg) {
72− coverPage.showErrorMessage(msg);
73− }
74−
7554 function reset() {
7655 loginList.reset();
7756 }
7857
58+ function showMessage(html) {
59+ loginList.showMessage(html);
60+ }
61+
62+ function showPrompt(text, isSecret, isDefaultPrompt) {
63+ loginList.showPrompt(text, isSecret, isDefaultPrompt);
64+ }
65+
7966 function tryToUnlock(toTheRight) {
8067 if (root.locked) {
8168 coverPage.show();
@@ -92,6 +79,18 @@ FocusScope {
9279 }
9380 }
9481
82+ function hide() {
83+ coverPage.hide();
84+ }
85+
86+ function notifyAuthenticationSucceeded() {
87+ // Nothing needed
88+ }
89+
90+ function showLastChance() {
91+ // TODO
92+ }
93+
9594 Rectangle {
9695 anchors.fill: parent
9796 color: "black"
@@ -104,6 +103,7 @@ FocusScope {
104103 height: parent.height
105104 width: parent.width
106105 draggable: !root.locked && !root.waiting
106+ state: "LoginList"
107107
108108 infographics {
109109 height: 0.75 * parent.height
@@ -122,21 +122,85 @@ FocusScope {
122122 id: loginList
123123 objectName: "loginList"
124124
125+ height: inputMethod && inputMethod.visible ?
126+ parent.height - inputMethod.keyboardRectangle.height : parent.height
127+ width: units.gu(40)
125128 anchors {
126129 left: parent.left
127130 leftMargin: Math.min(parent.width * 0.16, units.gu(20))
128131 top: parent.top
129132 }
130− width: units.gu(40)
131− height: inputMethod && inputMethod.visible ? parent.height - inputMethod.keyboardRectangle.height
132− : parent.height
133− Behavior on height { UbuntuNumberAnimation {} }
134−
135− locked: root.locked
136− waiting: root.waiting
137133
138− onSelected: root.selected(index)
134+ model: root.userModel
135+ currentSession: LightDMService.greeter.defaultSession
139136 onResponded: root.responded(response)
137+ onSelected: root.selected(index)
138+ onSessionChooserButtonClicked: parent.state = "SessionsList"
139+
140+ Keys.forwardTo: [sessionChooserLoader.item]
140141 }
142+
143+ Loader {
144+ id: sessionChooserLoader
145+
146+ height: loginList.height
147+ width: loginList.width
148+ anchors {
149+ left: parent.left
150+ leftMargin: Math.min(parent.width * 0.16, units.gu(20))
151+ top: parent.top
152+ }
153+
154+ active: false
155+
156+ onLoaded: sessionChooserLoader.item.forceActiveFocus();
157+ Binding {
158+ target: sessionChooserLoader.item
159+ property: "initiallySelectedSession"
160+ value: loginList.currentSession
161+ }
162+
163+ Connections {
164+ target: sessionChooserLoader.item
165+ onSessionSelected: loginList.currentSession = sessionKey
166+ onShowLoginList: {
167+ coverPage.state = "LoginList"
168+ loginList.passwordInput.forceActiveFocus();
169+ }
170+ ignoreUnknownSignals: true
171+ }
172+ }
173+
174+ states: [
175+ State {
176+ name: "SessionsList"
177+ PropertyChanges { target: loginList; opacity: 0 }
178+ PropertyChanges { target: sessionChooserLoader;
179+ active: true;
180+ opacity: 1
181+ source: "SessionsList.qml"
182+ }
183+ },
184+
185+ State {
186+ name: "LoginList"
187+ PropertyChanges { target: loginList; opacity: 1 }
188+ PropertyChanges { target: sessionChooserLoader;
189+ active: false;
190+ opacity: 0
191+ source: "";
192+ }
193+ }
194+ ]
195+
196+ transitions: [
197+ Transition {
198+ from: "*"
199+ to: "*"
200+ UbuntuNumberAnimation {
201+ property: "opacity";
202+ }
203+ }
204+ ]
141205 }
142206}
qml/Greeter/graphics/session_icons/gnome_badge.pngnew file

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

tests/mocks/LightDM/IntegratedLightDM/MockSessionsModel.cpp+29
@@ -18,6 +18,23 @@
1818#include "MockSessionsModel.h"
1919#include <QLightDM/SessionsModel>
2020
21+
22+int MockSessionsModel::numSessions() const
23+{
24+ QLightDM::SessionsModel* qSessionsModel =
25+ static_cast<QLightDM::SessionsModel*>(sourceModel());
26+
27+ return qSessionsModel->numSessions();
28+}
29+
30+int MockSessionsModel::numAvailableSessions() const
31+{
32+ QLightDM::SessionsModel* qSessionsModel =
33+ static_cast<QLightDM::SessionsModel*>(sourceModel());
34+
35+ return qSessionsModel->numAvailableSessions();
36+}
37+
2138QString MockSessionsModel::testScenario() const
2239{
2340 QLightDM::SessionsModel* qSessionsModel =
@@ -26,6 +43,17 @@ QString MockSessionsModel::testScenario() const
2643 return qSessionsModel->testScenario();
2744}
2845
46+void MockSessionsModel::setNumSessions(const int numSessions)
47+{
48+ QLightDM::SessionsModel* qSessionsModel =
49+ static_cast<QLightDM::SessionsModel*>(sourceModel());
50+
51+ if (qSessionsModel->numSessions() != numSessions) {
52+ qSessionsModel->setNumSessions(numSessions);
53+ Q_EMIT numSessionsChanged();
54+ }
55+}
56+
2957void MockSessionsModel::setTestScenario(const QString testScenario)
3058{
3159 QLightDM::SessionsModel* qSessionsModel =
@@ -33,5 +61,6 @@ void MockSessionsModel::setTestScenario(const QString testScenario)
3361
3462 if (qSessionsModel->testScenario() != testScenario) {
3563 qSessionsModel->setTestScenario(testScenario);
64+ Q_EMIT testScenarioChanged();
3665 }
3766}
tests/mocks/LightDM/IntegratedLightDM/MockSessionsModel.h+12−2
@@ -24,11 +24,21 @@ class MockSessionsModel : public SessionsModel
2424{
2525 Q_OBJECT
2626
27− Q_PROPERTY(QString testScenario READ testScenario WRITE setTestScenario)
27+ Q_PROPERTY(int numAvailableSessions READ numAvailableSessions CONSTANT)
28+ Q_PROPERTY(int numSessions READ numSessions WRITE setNumSessions NOTIFY numSessionsChanged)
29+ Q_PROPERTY(QString testScenario READ testScenario WRITE setTestScenario NOTIFY testScenarioChanged)
2830
2931public:
32+ int numAvailableSessions() const;
33+ int numSessions() const;
3034 QString testScenario() const;
31− void setTestScenario(QString testScenario);
35+ void setNumSessions(const int numSessions);
36+ void setTestScenario(const QString testScenario);
37+
38+Q_SIGNALS:
39+ void numSessionsChanged();
40+ void testScenarioChanged();
41+
3242};
3343
3444#endif // MOCK_UNITY_SESSIONSMODEL_H
tests/mocks/LightDM/IntegratedLightDM/liblightdm/Greeter.cpp−5
@@ -69,11 +69,6 @@ bool Greeter::showRemoteLoginHint() const
6969 return true;
7070}
7171
72−bool Greeter::lockHint () const
73−{
74− return false;
75−}
76−
7772QString Greeter::selectUserHint() const
7873{
7974 Q_D(const Greeter);
tests/mocks/LightDM/IntegratedLightDM/liblightdm/Greeter.h−2
@@ -41,7 +41,6 @@ class Q_DECL_EXPORT Greeter : public QObject
4141
4242 Q_PROPERTY(QString hostname READ hostname CONSTANT)
4343 Q_PROPERTY(bool hasGuestAccount READ hasGuestAccountHint CONSTANT)
44− Q_PROPERTY(bool locked READ lockHint CONSTANT)
4544
4645 Q_ENUMS(PromptType MessageType)
4746
@@ -67,7 +66,6 @@ public:
6766 bool hideUsersHint() const;
6867 bool showManualLoginHint() const;
6968 bool showRemoteLoginHint() const;
70− bool lockHint () const;
7169 bool hasGuestAccountHint() const;
7270 QString selectUserHint() const;
7371 void setSelectUserHint(const QString &selectUserHint); // only in mock
tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModel.cpp+22
@@ -80,12 +80,34 @@ int SessionsModel::rowCount(const QModelIndex& parent) const
8080 }
8181}
8282
83+int SessionsModel::numSessions() const
84+{
85+ Q_D(const SessionsModel);
86+ return d->numSessions;
87+}
88+
89+int SessionsModel::numAvailableSessions() const
90+{
91+ Q_D(const SessionsModel);
92+ return d->numAvailableSessions();
93+}
94+
8395QString SessionsModel::testScenario() const
8496{
8597 Q_D(const SessionsModel);
8698 return d->testScenario;
8799}
88100
101+void SessionsModel::setNumSessions(int numSessions)
102+{
103+ Q_D(SessionsModel);
104+
105+ if (d->numSessions != numSessions) {
106+ d->numSessions = numSessions;
107+ d->resetEntries();
108+ }
109+}
110+
89111void SessionsModel::setTestScenario(QString testScenario)
90112{
91113 Q_D(SessionsModel);
tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModel.h+3−3
@@ -31,9 +31,6 @@ class Q_DECL_EXPORT SessionsModel : public QAbstractListModel
3131
3232 Q_ENUMS(SessionModelRoles SessionType)
3333
34− // Mock-only API for testing purposes
35− Q_PROPERTY(QString testScenario READ testScenario WRITE setTestScenario)
36−
3734 public:
3835
3936 enum SessionModelRoles {
@@ -57,7 +54,10 @@ class Q_DECL_EXPORT SessionsModel : public QAbstractListModel
5754 int rowCount(const QModelIndex& parent) const override;
5855 QVariant data(const QModelIndex& index, int role) const override;
5956
57+ int numSessions() const;
58+ int numAvailableSessions() const;
6059 QString testScenario() const;
60+ void setNumSessions(int numSessions);
6161 void setTestScenario(QString testScenario);
6262
6363 protected:
tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModelPrivate.cpp+27−17
@@ -16,24 +16,44 @@
1616
1717#include "SessionsModel.h"
1818#include "SessionsModelPrivate.h"
19−
19+#include <QDebug>
2020namespace QLightDM
2121{
2222
2323SessionsModelPrivate::SessionsModelPrivate(SessionsModel* parent)
2424 : testScenario("singleSession")
25+ , m_availableSessions(
26+ {
27+ {"ubuntu", "", "Ubuntu", ""},
28+ {"ubuntu-2d", "", "Ubuntu 2D", ""},
29+ {"gnome", "", "GNOME", ""},
30+ {"gnome-classic", "", "GNOME Classic", ""},
31+ {"gnome-flashback-compiz", "", "GNOME Flashback (Compiz)", ""},
32+ {"gnome-flashback-metacity", "", "GNOME Flashback (Metacity)", ""},
33+ {"gnome-wayland", "", "GNOME on Wayland", ""},
34+ {"plasma", "", "Plasma", ""},
35+ {"kde", "", "KDE" , ""},
36+ {"xterm", "", "Recovery Console", ""},
37+ {"", "", "Unknown?", ""}
38+ })
2539 , q_ptr(parent)
2640{
41+ numSessions = numAvailableSessions();
2742 resetEntries();
2843}
2944
45+int SessionsModelPrivate::numAvailableSessions() const
46+{
47+ return m_availableSessions.length();
48+}
49+
3050void SessionsModelPrivate::resetEntries()
3151{
3252 Q_Q(SessionsModel);
3353
3454 q->beginResetModel();
3555 if (testScenario == "multipleSessions") {
36− resetEntries_multipleSessions();
56+ resetEntries_multipleSessions(numSessions);
3757 } else if (testScenario == "noSessions") {
3858 resetEntries_noSessions();
3959 } else {
@@ -42,22 +62,12 @@ void SessionsModelPrivate::resetEntries()
4262 q->endResetModel();
4363}
4464
45−void SessionsModelPrivate::resetEntries_multipleSessions()
65+void SessionsModelPrivate::resetEntries_multipleSessions(int numSessions)
4666{
47− sessionItems =
48− {
49− {"ubuntu", "", "Ubuntu", ""},
50− {"ubuntu-2d", "", "Ubuntu 2D", ""},
51− {"gnome", "", "GNOME", ""},
52− {"gnome-classic", "", "GNOME Classic", ""},
53− {"gnome-flashback-compiz", "", "GNOME Flashback (Compiz)", ""},
54− {"gnome-flashback-metacity", "", "GNOME Flashback (Metacity)", ""},
55− {"gnome-wayland", "", "GNOME on Wayland", ""},
56− {"plasma", "", "Plasma", ""},
57− {"kde", "", "KDE" , ""},
58− {"xterm", "", "Recovery Console", ""},
59− {"", "", "Unknown?", ""}
60− };
67+ sessionItems.clear();
68+ for (int i = 0; i < numSessions; i++) {
69+ sessionItems.append(m_availableSessions.value(i));
70+ }
6171}
6272
6373void SessionsModelPrivate::resetEntries_noSessions()
tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModelPrivate.h+5−1
@@ -40,7 +40,10 @@ public:
4040 explicit SessionsModelPrivate(SessionsModel* parent=0);
4141 virtual ~SessionsModelPrivate() = default;
4242
43+ int numAvailableSessions() const;
44+
4345 QList<SessionItem> sessionItems;
46+ int numSessions;
4447 QString testScenario;
4548
4649 void resetEntries();
@@ -48,7 +51,8 @@ protected:
4851 SessionsModel* const q_ptr;
4952
5053private:
51− void resetEntries_multipleSessions();
54+ const QList<SessionItem> m_availableSessions;
55+ void resetEntries_multipleSessions(int numSessions);
5256 void resetEntries_noSessions();
5357 void resetEntries_singleSession();
5458 Q_DECLARE_PUBLIC(SessionsModel)
tests/mocks/LightDM/IntegratedLightDM/plugin.cpp+1−1
@@ -72,7 +72,7 @@ void LightDMPlugin::registerTypes(const char *uri)
7272 qRegisterMetaType<QLightDM::Greeter::PromptType>("QLightDM::Greeter::PromptType");
7373 qmlRegisterSingletonType<MockGreeter>(uri, 0, 1, "Greeter", greeter_provider);
7474 qmlRegisterSingletonType<MockSessionsModel>(uri, 0, 1, "Sessions", sessions_provider);
75− qmlRegisterUncreatableType<SessionsModel>(uri, 0, 1, "SessionRoles", "Type is not instantiable");
75+ qmlRegisterUncreatableType<QLightDM::SessionsModel>(uri, 0, 1, "SessionRoles", "Type is not instantiable");
7676 qmlRegisterSingletonType<MockUsersModel>(uri, 0, 1, "Users", users_provider);
7777 qmlRegisterUncreatableType<QLightDM::UsersModel>(uri, 0, 1, "UserRoles", "Type is not instantiable");
7878 qmlRegisterSingletonType<UserMetricsOutput::UserMetrics>(uri, 0, 1, "Infographic", infographic_provider);
tests/plugins/LightDM/IntegratedLightDM/sessionsmodel.cpp+32
@@ -59,6 +59,38 @@ private Q_SLOTS:
5959 }
6060 }
6161
62+ void testIconLookupLogic()
63+ {
64+ const QString data[][2] = {
65+ //{SESSION KEY, ICON NAME}
66+ {"gnome-classic", "gnome_badge.png"},
67+ {"gnome-flashback-compiz", "gnome_badge.png"},
68+ {"gnome-flashback-metacity", "gnome_badge.png"},
69+ {"gnome-shell", "gnome_badge.png"},
70+ {"gnome-wayland", "gnome_badge.png"},
71+ {"gnome", "gnome_badge.png"},
72+ {"kde", "kde_badge.png"},
73+ {"plasma", "kde_badge.png"},
74+ {"recovery_console", "recovery_console_badge.png"},
75+ {"ubuntu","ubuntu_badge.png"},
76+ {"ubuntu-2d", "ubuntu_badge.png"},
77+ {"xterm", "recovery_console_badge.png"},
78+ {"made up session", "unknown_badge.png"},
79+ {"", "unknown_badge.png"}
80+ };
81+
82+ const short length = sizeof(data) / sizeof(data[0]);
83+ for (int i = 0; i < length; i++) {
84+ std::string key = data[i][0].toStdString();
85+ std::string icon = data[i][1].toStdString();
86+ std::string url = model->iconUrl(data[i][0]).toString().toStdString();
87+ std::string errorMessage = "Session icon url for " + key +
88+ " should contain " + icon + " but " + url + " was returned";
89+ QVERIFY2(model->iconUrl(data[i][0]).toString().contains(data[i][1]),
90+ errorMessage.c_str());
91+ }
92+ }
93+
6294 void testMultipleSessionsCountIsCorrect()
6395 {
6496 sourceModel->setTestScenario("multipleSessions");
tests/qmltests/Greeter/tst_WideView.qml+118−2
@@ -24,6 +24,7 @@ import Unity.Test 0.1 as UT
2424
2525StyledItem {
2626 id: root
27+
2728 width: units.gu(120)
2829 height: units.gu(80)
2930 focus: true
@@ -111,11 +112,9 @@ StyledItem {
111112 color: theme.palette.normal.background
112113 width: units.gu(40)
113114 height: parent.height
114−
115115 Column {
116116 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
117117 spacing: units.gu(1)
118−
119118 Row {
120119 Button {
121120 text: "Show Last Chance"
@@ -266,6 +265,51 @@ StyledItem {
266265 text: "emergency: " + emergencySpy.count
267266 }
268267 }
268+ Row {
269+ id: multipleSessions
270+ CheckBox {
271+ id: multipleSessionsCheckbox
272+ onClicked: {
273+ if (checked) {
274+ LightDM.Sessions.testScenario = "multipleSessions"
275+ } else {
276+ LightDM.Sessions.testScenario = "singleSession"
277+ }
278+ }
279+ Connections {
280+ target: LightDM.Sessions
281+ onTestScenarioChanged: {
282+ if (LightDM.Sessions.testScenario === "multipleSessions") {
283+ multipleSessionsCheckbox.checked = true;
284+ } else {
285+ multipleSessionsCheckbox.checked = false;
286+ }
287+ }
288+ }
289+ }
290+ Label {
291+ text: "Multiple Sessions"
292+ }
293+ }
294+ Row {
295+ Slider {
296+ id: numSessionsSlider
297+
298+ width: units.gu(10)
299+ minimumValue: 0
300+ maximumValue: LightDM.Sessions.numAvailableSessions
301+ value: LightDM.Sessions.numSessions
302+ visible: LightDM.Sessions.testScenario === "multipleSessions"
303+ Binding {
304+ target: LightDM.Sessions
305+ property: "numSessions"
306+ value: numSessionsSlider.value
307+ }
308+ }
309+ Label {
310+ text: "Available Sessions"
311+ }
312+ }
269313 Row {
270314 Button {
271315 text: "Reload View"
@@ -316,6 +360,7 @@ StyledItem {
316360 when: windowShown
317361
318362 property Item view: loader.status === Loader.Ready ? loader.item : null
363+ property url testIconDirectory: Qt.resolvedUrl("../../../qml/Greeter/graphics/session_icons")
319364
320365 function init() {
321366 selectIndex(0); // break binding with text field
@@ -323,6 +368,7 @@ StyledItem {
323368 respondedSpy.clear();
324369 teaseSpy.clear();
325370 emergencySpy.clear();
371+ LightDM.Sessions.testScenario = "multipleSessions"
326372 }
327373
328374 function cleanup() {
@@ -378,6 +424,76 @@ StyledItem {
378424 {tag: "right", x: view.width, offset: 0, count: 1, locked: false},
379425 ]
380426 }
427+
428+ function test_sessionIconsAreValid() {
429+ LightDM.Sessions.testScenario = "multipleSessions"
430+ var originalDirectories = LightDM.Sessions.iconSearchDirectories
431+ LightDM.Sessions.iconSearchDirectories = [testIconDirectory]
432+
433+ // Test the login list icon is valid
434+ var sessionChooserButton = findChild(view, "sessionChooserButton");
435+ compare(sessionChooserButton.visible, true);
436+
437+ var session = String(view.sessionToStart).toLowerCase();
438+ var icon = String(sessionChooserButton.icon);
439+ compare(icon.indexOf(session) > -1, true);
440+
441+ // Test the session list icons are valid
442+ var coverPage = findChild(view, "coverPage");
443+ coverPage.state = "SessionsList"
444+ var sessionsList = findChild(view, "sessionsList");
445+ tryCompare(sessionsList, "visible", true);
446+ }
447+
448+ function test_choosingNewSessionChangesLoginListIcon() {
449+ // Ensure the default session is selected (Ubuntu)
450+ loader.active = false;
451+ loader.active = true;
452+
453+ LightDM.Sessions.testScenario = "multipleSessions";
454+ var sessionChooserButton = findChild(view, "sessionChooserButton");
455+ var icon = String(sessionChooserButton.icon);
456+ compare(icon.indexOf("ubuntu") > -1, true);
457+
458+ tap(sessionChooserButton)
459+ for(var i = 0; i < LightDM.Sessions.count; i++) {
460+ var delegateName = "sessionDelegate" + String(i);
461+ var currentDelegate = findChild(view, delegateName);
462+ var sessionKey = LightDM.Sessions.data(i,LightDM.SessionRoles.KeyRole);
463+ if (sessionKey === "gnome-classic") {
464+ tap(currentDelegate);
465+ var sessionChooserButton = findChild(view, "sessionChooserButton");
466+ waitForRendering(sessionChooserButton);
467+ var icon = String(sessionChooserButton.icon);
468+ break;
469+ }
470+ }
471+
472+ compare(icon.indexOf("gnome") > -1, true,
473+ "Expected icon to contain gnome but it was " + icon);
474+ }
475+
476+ function test_noSessionsDoesntBreakView() {
477+ LightDM.Sessions.testScenario = "noSessions"
478+ compare(LightDM.Sessions.count, 0)
479+ }
480+
481+ function test_sessionIconNotShownWithOneSession() {
482+ LightDM.Sessions.testScenario = "singleSession"
483+ compare(LightDM.Sessions.count, 1);
484+
485+ var sessionChooserButton = findChild(view, "sessionChooserButton");
486+ tryCompare(sessionChooserButton, "visible", false);
487+ }
488+
489+ function test_sessionIconShownWithMultipleSessions() {
490+ LightDM.Sessions.testScenario = "multipleSessions"
491+ compare(LightDM.Sessions.count > 1, true);
492+
493+ var sessionChooserButton = findChild(view, "sessionChooserButton");
494+ tryCompare(sessionChooserButton, "visible", true);
495+ }
496+
381497 function test_tease(data) {
382498 view.locked = data.locked;
383499 view.dragHandleLeftMargin = data.offset;