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

Approved by: Michael Terry

commitfaa4d33f500912fc6572670b12ffa5df94312759 authorJosh Arenson <joshua.arenson@canonical.com> dateTue 13 Sep 2016, 06:52 (10 years ago) parent5ca186e99e25eb
30 files changed+759−114
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+19−1 qml/Greeter/IntegratedLightDMImpl.qml+2−1 qml/Greeter/LightDMService.qml+2 qml/Greeter/LoginAreaContainer.qml+38 qml/Greeter/LoginList.qml+74−16 qml/Greeter/NarrowView.qml+1 qml/Greeter/SessionIcon.qml+47 qml/Greeter/SessionsList.qml+199 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+19−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
@@ -196,8 +212,9 @@ Showable {
196212 }
197213
198214 function login() {
215+ console.log("JOSH-> Entering Greeter::login()")
199216 d.waiting = true;
200− if (LightDMService.greeter.startSessionSync()) {
217+ if (LightDMService.greeter.startSessionSync(root.sessionToStart())) {
201218 sessionStarted();
202219 hideView();
203220 } else if (loader.item) {
@@ -348,6 +365,7 @@ Showable {
348365 d.selectUser(index, true);
349366 }
350367 onResponded: {
368+ console.log("JOSH-> Greeter:onResponded handler called ->" + root.locked)
351369 if (root.locked) {
352370 LightDMService.greeter.respond(response);
353371 } else {
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+74−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) {
@@ -82,6 +88,7 @@ StyledItem {
8288 id: d
8389
8490 function checkIfPromptless() {
91+ console.log("JOSH-> Entering LoginList:checkIfPromptless(): " + waiting + " : " + wasPrompted);
8592 if (!waiting && !wasPrompted) {
8693 passwordInput.isPrompt = false;
8794 passwordInput.text = root.locked ? i18n.tr("Retry")
@@ -97,6 +104,7 @@ StyledItem {
97104 name: "Ubuntu.Components.Themes.Ambiance"
98105 }
99106
107+ KeyNavigation.tab: sessionChooser
100108 Keys.onUpPressed: {
101109 selected(currentIndex - 1);
102110 event.accepted = true;
@@ -114,19 +122,7 @@ StyledItem {
114122 userList.currentIndex = currentIndex;
115123 }
116124
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 {
125+ LoginAreaContainer {
130126 id: highlightItem
131127 anchors {
132128 left: parent.left
@@ -135,9 +131,8 @@ StyledItem {
135131 rightMargin: units.gu(2)
136132 verticalCenter: parent.verticalCenter
137133 }
134+
138135 height: root.highlightedHeight
139− aspect: UbuntuShape.Flat
140− backgroundColor: theme.palette.normal.raised
141136 }
142137
143138 ListView {
@@ -166,12 +161,14 @@ StyledItem {
166161 moveTimer.start();
167162 }
168163
164+ onCurrentItemChanged: console.log("JOSH-> LoginList currentItem.height = " + currentItem.height);
169165 delegate: Item {
170166 width: parent.width
171167 height: root.cellHeight
172168
173169 readonly property bool belowHighlight: (userList.currentIndex < 0 && index > 0) || (userList.currentIndex >= 0 && index > userList.currentIndex)
174170 readonly property int belowOffset: root.highlightedHeight - root.cellHeight
171+ readonly property string userSession: session
175172 readonly property string username: name
176173
177174 opacity: {
@@ -236,6 +233,67 @@ StyledItem {
236233 }
237234 }
238235
236+ // Use an AbstractButton due to icon limitations with Button
237+ AbstractButton {
238+ id: sessionChooser
239+ objectName: "sessionChooserButton"
240+
241+ readonly property alias icon: badge.source
242+
243+ visible: LightDMService.sessions.count > 1 &&
244+ !LightDMService.users.data(userList.currentIndex, LightDMService.userRoles.LoggedInRole)
245+
246+ height: units.gu(3.5)
247+ width: units.gu(3.5)
248+
249+ anchors {
250+ right: highlightItem.right
251+ rightMargin: units.gu(2)
252+
253+ top: highlightItem.top
254+ topMargin: units.gu(1.5)
255+ }
256+
257+ Rectangle {
258+ id: badgeHighlight
259+
260+ anchors.fill: parent
261+ visible: parent.activeFocus
262+ color: "transparent"
263+ border.color: theme.palette.normal.focus
264+ border.width: units.dp(1)
265+ radius: 3
266+ }
267+
268+ Icon {
269+ id: badge
270+ anchors.fill: parent
271+ anchors.margins: units.dp(3)
272+ keyColor: "#ffffff" // icon providers give us white icons
273+ color: theme.palette.normal.raisedSecondaryText
274+ source: LightDMService.sessions.iconUrl(root.currentSession)
275+ }
276+
277+ KeyNavigation.tab: passwordInput
278+ Keys.onReturnPressed: {
279+ sessionChooserButtonClicked();
280+ event.accepted = true;
281+ }
282+
283+ onClicked: {
284+ sessionChooserButtonClicked();
285+ }
286+
287+ // Refresh the icon path if looking at different places at runtime
288+ // this is mainly for testing
289+ Connections {
290+ target: LightDMService.sessions
291+ onIconSearchDirectoriesChanged: {
292+ badge.source = LightDMService.sessions.iconUrl(root.currentSession)
293+ }
294+ }
295+ }
296+
239297 FadingLabel {
240298 id: infoLabel
241299 objectName: "infoLabel"
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+199
@@ -0,0 +1,199 @@
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+ QtObject {
52+ id: d
53+ // Track if the session has changed and the list is still shown.
54+ // This is currently only possible/reasonable with keyboard navigation
55+ property bool sessionChanged: false
56+ function sessionSelected(currentKey) {
57+ d.sessionChanged = true;
58+ root.sessionSelected(currentKey)
59+ }
60+ }
61+
62+ Keys.onEnterPressed: {
63+ showLoginList(); // Session is already selected
64+ event.accepted = true;
65+ }
66+
67+ Keys.onEscapePressed: {
68+ showLoginList();
69+ event.accepted = true;
70+ }
71+
72+ Keys.onReturnPressed: {
73+ showLoginList();
74+ event.accepted = true;
75+ }
76+
77+ Keys.onDownPressed: {
78+ if (sessionsList.currentIndex < sessionsList.model.count - 1)
79+ sessionsList.currentIndex++;
80+ d.sessionSelected(currentKey());
81+ event.accepted = true;
82+ }
83+
84+ Keys.onUpPressed: {
85+ if (sessionsList.currentIndex > 0)
86+ sessionsList.currentIndex--;
87+ d.sessionSelected(currentKey());
88+ event.accepted = true;
89+ }
90+
91+ LoginAreaContainer {
92+ readonly property real margins: sessionsList.anchors.margins
93+ readonly property real prefferedHeight: {
94+ if (sessionsList.currentItem) {
95+ return (sessionsList.currentItem.height *
96+ (1 + sessionsList.model.count)) + 2 * margins
97+ } else {
98+ return sessionsList.headerItem.height + 2 * margins
99+ }
100+ }
101+
102+ height: prefferedHeight < parent.height ? prefferedHeight : parent.height - units.gu(4)
103+ width: parent.width
104+
105+ anchors {
106+ left: parent.left
107+ right: parent.right
108+ verticalCenter: parent.verticalCenter
109+ }
110+
111+ UbuntuListView {
112+ id: sessionsList
113+
114+ anchors {
115+ top: parent.top
116+ left: parent.left
117+ right: parent.right
118+ margins: units.gu(2)
119+ }
120+
121+ height: parent.height - headerItem.height
122+
123+ boundsBehavior: Flickable.StopAtBounds
124+
125+ model: LightDMService.sessions
126+ header: ListItemLayout {
127+ id: header
128+
129+ padding.leading: 0 // handled by parent's margins
130+
131+ title.color: theme.palette.normal.raisedText
132+ title.font.pixelSize: units.gu(2.1)
133+ title.text: i18n.tr("Select desktop environment")
134+
135+ Icon {
136+ id: icon
137+ width: units.gu(3)
138+ SlotsLayout.position: SlotsLayout.Leading
139+ name: "go-previous"
140+
141+ MouseArea {
142+ anchors.fill: parent
143+ onClicked: showLoginList()
144+ }
145+ }
146+ }
147+
148+ headerPositioning: ListView.OverlayHeader
149+ highlightFollowsCurrentItem: false
150+
151+ delegate: ListItem {
152+ id: delegate
153+ objectName: "sessionDelegate" + index
154+
155+ divider.visible: false
156+
157+ visible: y > sessionsList.headerItem.y
158+ + sessionsList.headerItem.height
159+ - sessionsList.anchors.margins
160+
161+ MouseArea {
162+ anchors.fill: parent
163+ onClicked: {
164+ sessionsList.currentIndex = index
165+ sessionSelected(key)
166+ showLoginList()
167+ }
168+ }
169+
170+ Rectangle {
171+ height: parent.height
172+ width: parent.width
173+ color: "transparent"
174+ border {
175+ color: d.sessionChanged ? theme.palette.normal.focus
176+ : theme.palette.normal.positionText
177+ width: units.gu(0.2)
178+ }
179+
180+ visible: index === sessionsList.currentIndex
181+ }
182+
183+ ListItemLayout {
184+ id: layout
185+
186+ SessionIcon {
187+ id: sessionIcon
188+ source: icon_url
189+ SlotsLayout.position: SlotsLayout.Leading
190+ color: theme.palette.normal.raisedSecondaryText
191+ }
192+
193+ title.text: display
194+ title.color: theme.palette.normal.raisedText
195+ }
196+ }
197+ }
198+ }
199+}
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;