on xenial help sign in
Refactor the greeter code to be more compartmented and isolatable.
a720b0bMichael Terry
cmake/modules/QmlTest.cmake+1−1
@@ -28,7 +28,7 @@ endif()
28
28
29set(qmlscene_exe ${CMAKE_BINARY_DIR}/tests/uqmlscene/uqmlscene)
29set(qmlscene_exe ${CMAKE_BINARY_DIR}/tests/uqmlscene/uqmlscene)
30
30
31−set(test_env UNITY_TESTING=1)
31+set(test_env LC_ALL=C UNITY_TESTING=1)
32
32
33macro(add_manual_qml_test SUBPATH COMPONENT_NAME)
33macro(add_manual_qml_test SUBPATH COMPONENT_NAME)
34 set(options NO_ADD_TEST NO_TARGETS)
34 set(options NO_ADD_TEST NO_TARGETS)
qml/Components/DelayedLockscreen.qml+3−1
@@ -20,11 +20,13 @@ import "../Components"
20
20
21Item {
21Item {
22 id: root
22 id: root
23− anchors.fill: parent
24
23
25 property int delayMinutes
24 property int delayMinutes
26 property bool alphaNumeric
25 property bool alphaNumeric
27
26
27+ signal entered(string passphrase) // unused
28+ signal cancel() // unused
29+
28 function clear(playAnimation) {}
30 function clear(playAnimation) {}
29
31
30 Column {
32 Column {
qml/Components/PassphraseLockscreen.qml+2−3
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013,2014,2015 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
@@ -20,8 +20,7 @@ import "../Components"
20
20
21Item {
21Item {
22 id: root
22 id: root
23− anchors.top: parent.top
24− anchors.topMargin: units.gu(4)
23+ y: units.gu(4)
25 height: shakeContainer.height
24 height: shakeContainer.height
26
25
27 property string infoText
26 property string infoText
qml/Components/PinLockscreen.qml+3−4
@@ -21,9 +21,7 @@ import "../Components"
21
21
22Column {
22Column {
23 id: root
23 id: root
24− anchors.top: parent.top
25− anchors.topMargin: units.gu(4)
24+ y: units.gu(4)
26− anchors.horizontalCenter: parent.horizontalCenter
27 spacing: units.gu(2)
25 spacing: units.gu(2)
28
26
29 property string infoText
27 property string infoText
@@ -123,6 +121,7 @@ Column {
123 objectName: "backspaceIcon"
121 objectName: "backspaceIcon"
124 anchors { right: parent.right; top: parent.top; bottom: parent.bottom }
122 anchors { right: parent.right; top: parent.top; bottom: parent.bottom }
125 width: height
123 width: height
124+ enabled: root.entryEnabled
126
125
127 Icon {
126 Icon {
128 anchors.fill: parent
127 anchors.fill: parent
@@ -130,7 +129,7 @@ Column {
130 color: "#f3f3e7"
129 color: "#f3f3e7"
131 }
130 }
132
131
133− opacity: (pinentryField.text.length && !pinentryField.incorrectOverride) > 0 ? 1 : 0
132+ opacity: (pinentryField.text.length > 0 && !pinentryField.incorrectOverride) ? 1 : 0
134
133
135 Behavior on opacity {
134 Behavior on opacity {
136 UbuntuNumberAnimation {}
135 UbuntuNumberAnimation {}
qml/Greeter/CoverPage.qmlnew file+216
@@ -0,0 +1,216 @@
1+/*
2+ * Copyright (C) 2013,2014,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.3
18+import Ubuntu.Components 1.1
19+import Ubuntu.Gestures 0.1
20+import "../Components"
21+
22+Showable {
23+ id: root
24+
25+ property real dragHandleLeftMargin
26+ property real launcherOffset
27+ property alias background: greeterBackground.source
28+ property real backgroundTopMargin
29+ property var infographicModel
30+ property bool draggable: true
31+
32+ property alias infographics: infographics
33+
34+ readonly property real showProgress: MathUtils.clamp((width - Math.abs(x)) / width, 0, 1)
35+
36+ signal tease()
37+
38+ function hideRight() {
39+ d.forceRightOnNextHideAnimation = true;
40+ hide();
41+ }
42+
43+ QtObject {
44+ id: d
45+ property bool forceRightOnNextHideAnimation: false
46+ }
47+
48+ prepareToHide: function () {
49+ hideTranslation.from = root.x + translation.x
50+ hideTranslation.to = root.x > 0 || d.forceRightOnNextHideAnimation ? root.width : -root.width;
51+ d.forceRightOnNextHideAnimation = false;
52+ }
53+
54+ // We don't directly bind "x" because that's owned by the DragHandle. So
55+ // instead, we can get a little extra horizontal push by using transforms.
56+ transform: Translate { id: translation; x: root.draggable ? launcherOffset : 0 }
57+
58+ MouseArea { anchors.fill: parent; }
59+
60+ Rectangle {
61+ // In case background fails to load
62+ id: backgroundBackup
63+ anchors.fill: parent
64+ color: "black"
65+ }
66+
67+ CrossFadeImage {
68+ id: greeterBackground
69+ objectName: "greeterBackground"
70+ anchors {
71+ fill: parent
72+ topMargin: root.backgroundTopMargin
73+ }
74+ fillMode: Image.PreserveAspectCrop
75+ // Limit how much memory we'll reserve for this image
76+ sourceSize.height: height
77+ sourceSize.width: width
78+ }
79+
80+ Rectangle {
81+ anchors.fill: parent
82+ color: "black"
83+ opacity: 0.4
84+ }
85+
86+ Infographics {
87+ id: infographics
88+ objectName: "infographics"
89+ height: parent.height
90+ model: root.infographicModel
91+ clip: true // clip large data bubbles
92+
93+ anchors {
94+ verticalCenter: parent.verticalCenter
95+ left: parent.left
96+ right: parent.right
97+ }
98+ }
99+
100+ Label {
101+ id: swipeHint
102+ property real baseOpacity: 0.5
103+ opacity: 0.0
104+ anchors.horizontalCenter: parent.horizontalCenter
105+ anchors.bottom: parent.bottom
106+ anchors.bottomMargin: units.gu(5)
107+ text: "《 " + i18n.tr("Unlock") + " 》"
108+ color: "white"
109+ font.weight: Font.Light
110+
111+ SequentialAnimation on opacity {
112+ id: showLabelAnimation
113+ running: false
114+ loops: 2
115+
116+ StandardAnimation {
117+ from: 0.0
118+ to: swipeHint.baseOpacity
119+ duration: UbuntuAnimation.SleepyDuration
120+ }
121+ PauseAnimation { duration: UbuntuAnimation.BriskDuration }
122+ StandardAnimation {
123+ from: swipeHint.baseOpacity
124+ to: 0.0
125+ duration: UbuntuAnimation.SleepyDuration
126+ }
127+ }
128+ }
129+
130+ DragHandle {
131+ id: dragHandle
132+ anchors.fill: parent
133+ anchors.leftMargin: root.dragHandleLeftMargin
134+ enabled: root.draggable
135+ direction: Direction.Horizontal
136+
137+ onDraggingChanged: {
138+ if (dragging) {
139+ root.tease();
140+ showLabelAnimation.start();
141+ }
142+ }
143+ }
144+
145+ // right side shadow
146+ Image {
147+ anchors.left: parent.right
148+ anchors.top: parent.top
149+ anchors.bottom: parent.bottom
150+ fillMode: Image.Tile
151+ source: "../graphics/dropshadow_right.png"
152+ }
153+
154+ // left side shadow
155+ Image {
156+ anchors.right: parent.left
157+ anchors.top: parent.top
158+ anchors.bottom: parent.bottom
159+ fillMode: Image.Tile
160+ source: "../graphics/dropshadow_left.png"
161+ }
162+
163+ Binding {
164+ id: positionLock
165+
166+ property bool enabled: false
167+ onEnabledChanged: {
168+ if (enabled === __enabled) {
169+ return;
170+ }
171+
172+ if (enabled) {
173+ if (root.x > 0) {
174+ value = Qt.binding(function() { return root.width; })
175+ } else {
176+ value = Qt.binding(function() { return -root.width; })
177+ }
178+ }
179+
180+ __enabled = enabled;
181+ }
182+
183+ property bool __enabled: false
184+
185+ target: root
186+ when: __enabled
187+ property: "x"
188+ }
189+
190+ hideAnimation: SequentialAnimation {
191+ id: hideAnimation
192+ objectName: "hideAnimation"
193+ property var target // unused, here to silence Showable warning
194+ StandardAnimation {
195+ id: hideTranslation
196+ property: "x"
197+ target: root
198+ }
199+ PropertyAction { target: root; property: "visible"; value: false }
200+ PropertyAction { target: positionLock; property: "enabled"; value: true }
201+ }
202+
203+ showAnimation: SequentialAnimation {
204+ id: showAnimation
205+ objectName: "showAnimation"
206+ property var target // unused, here to silence Showable warning
207+ PropertyAction { target: root; property: "visible"; value: true }
208+ PropertyAction { target: positionLock; property: "enabled"; value: false }
209+ StandardAnimation {
210+ property: "x"
211+ target: root
212+ to: 0
213+ duration: UbuntuAnimation.FastDuration
214+ }
215+ }
216+}
qml/Greeter/Greeter.qml+319−147
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013,2014,2015 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,66 +14,185 @@
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−import QtQuick 2.0
17+import QtQuick 2.3
18−import Ubuntu.Components 0.1
18+import AccountsService 0.1
19−import Ubuntu.Gestures 0.1
20import LightDM 0.1 as LightDM
19import LightDM 0.1 as LightDM
20+import Ubuntu.Components 1.1
21+import Ubuntu.SystemImage 0.1
22+import Unity.Connectivity 0.1
23+import Unity.Launcher 0.1
21import "../Components"
24import "../Components"
22
25
23Showable {
26Showable {
24− id: greeter
27+ id: root
25− enabled: shown
26− created: greeterContentLoader.status == Loader.Ready && greeterContentLoader.item.ready
28+ created: loader.status == Loader.Ready
27
29
28 property real dragHandleLeftMargin: 0
30 property real dragHandleLeftMargin: 0
29− property alias dragging: dragHandle.dragging
30
31
31 property url background
32 property url background
32
33
33− // so that it can be replaced in tests with a mock object
34+ // How far to offset the top greeter layer during a launcher left-drag
34− property var inputMethod: Qt.inputMethod
35+ property real launcherOffset
35
36
36− prepareToHide: function () {
37+ readonly property bool active: shown || hasLockedApp
37− hideTranslation.to = greeter.x > 0 || d.forceRightOnNextHideAnimation ? greeter.width : -greeter.width;
38+ readonly property bool fullyShown: loader.item ? loader.item.fullyShown : false
38− d.forceRightOnNextHideAnimation = false;
39+
40+ // True when the greeter is waiting for PAM or other setup process
41+ readonly property alias waiting: d.waiting
42+
43+ property string lockedApp: ""
44+ readonly property bool hasLockedApp: lockedApp !== ""
45+
46+ property bool forcedUnlock
47+ readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated && !forcedUnlock
48+
49+ property bool tabletMode
50+ property url viewSource // only used for testing
51+
52+ property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
53+ property int failedLoginsDelayAttempts: 7 // number of failed logins
54+ property int failedLoginsDelayMinutes: 5 // minutes of forced waiting
55+
56+ signal tease()
57+ signal sessionStarted()
58+ signal emergencyCall()
59+
60+ function forceShow() {
61+ showNow();
62+ loader.item.reset();
39 }
63 }
40
64
41− QtObject {
65+ function notifyAppFocused(appId) {
42− id: d
66+ if (!active) {
43− property bool forceRightOnNextHideAnimation: false
67+ return;
68+ }
69+
70+ if (hasLockedApp) {
71+ if (appId === lockedApp) {
72+ hide(); // show locked app
73+ } else {
74+ show();
75+ d.startUnlock(false /* toTheRight */);
76+ }
77+ } else if (appId !== "unity8-dash") { // dash isn't started by user
78+ d.startUnlock(false /* toTheRight */);
79+ }
44 }
80 }
45
81
46− property bool loadContent: required
82+ function notifyAboutToFocusApp(appId) {
83+ if (!active) {
84+ return;
85+ }
47
86
48− // 1 when fully shown and 0 when fully hidden
87+ // A hint that we're about to focus an app. This way we can look
49− property real showProgress: visible ? MathUtils.clamp((width - Math.abs(x)) / width, 0, 1) : 0
88+ // a little more responsive, rather than waiting for the above
89+ // notifyAppFocused call. We also need this in case we have a locked
90+ // app, in order to show lockscreen instead of new app.
91+ d.startUnlock(false /* toTheRight */);
92+ }
50
93
51− property alias model: greeterContentLoader.model
94+ // This is a just a glorified notifyAboutToFocusApp(), but it does one
52− property bool locked: true
95+ // other thing: it hides any cover pages to the RIGHT, because the user
96+ // just came from a launcher drag starting on the left.
97+ // It also returns a boolean value, indicating whether there was a visual
98+ // change or not (the shell only wants to hide the launcher if there was
99+ // a change).
100+ function notifyShowingDashFromDrag() {
101+ if (!active) {
102+ return false;
103+ }
53
104
54− readonly property bool narrowMode: !multiUser && height > width
105+ return d.startUnlock(true /* toTheRight */);
55− readonly property bool multiUser: LightDM.Users.count > 1
106+ }
56
107
57− readonly property int currentIndex: greeterContentLoader.currentIndex
108+ QtObject {
109+ id: d
58
110
59− signal selected(int uid)
111+ readonly property bool multiUser: LightDM.Users.count > 1
60− signal unlocked(int uid)
112+ property int currentIndex
61− signal tapped()
113+ property int delayMinutes
114+ property bool waiting
115+
116+ // We want 'launcherOffset' to animate down to zero. But not to animate
117+ // while being dragged. So ideally we change this only when the user
118+ // lets go and launcherOffset drops to zero. But we need to wait for
119+ // the behavior to be enabled first. So we cache the last known good
120+ // launcherOffset value to cover us during that brief gap between
121+ // release and the behavior turning on.
122+ property real lastKnownPositiveOffset // set in a launcherOffsetChanged below
123+ property real launcherOffsetProxy: (shown && !launcherOffsetProxyBehavior.enabled) ? lastKnownPositiveOffset : 0
124+ Behavior on launcherOffsetProxy {
125+ id: launcherOffsetProxyBehavior
126+ enabled: launcherOffset === 0
127+ UbuntuNumberAnimation {}
128+ }
129+
130+ function selectUser(uid, reset) {
131+ d.waiting = true;
132+ if (reset) {
133+ loader.item.reset();
134+ }
135+ currentIndex = uid;
136+ var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
137+ AccountsService.user = user;
138+ LauncherModel.setUser(user);
139+ LightDM.Greeter.authenticate(user); // always resets auth state
140+ }
141+
142+ function login() {
143+ enabled = false;
144+ if (LightDM.Greeter.startSessionSync()) {
145+ sessionStarted();
146+ loader.item.notifyAuthenticationSucceeded();
147+ } else {
148+ loader.item.notifyAuthenticationFailed();
149+ }
150+ enabled = true;
151+ }
62
152
63− function hideRight() {
153+ function startUnlock(toTheRight) {
64− d.forceRightOnNextHideAnimation = true;
154+ if (loader.item) {
65− hide();
155+ return loader.item.tryToUnlock(toTheRight);
156+ } else {
157+ return false;
158+ }
159+ }
66 }
160 }
67
161
68− function tryToUnlock() {
162+ onLauncherOffsetChanged: {
69− if (created) {
163+ if (launcherOffset > 0) {
70− greeterContentLoader.item.tryToUnlock()
164+ d.lastKnownPositiveOffset = launcherOffset;
71 }
165 }
72 }
166 }
73
167
74− function reset() {
168+ onForcedUnlockChanged: {
75− if (created) {
169+ if (forcedUnlock && shown) {
76− greeterContentLoader.item.reset()
170+ // pretend we were just authenticated
171+ loader.item.notifyAuthenticationSucceeded();
172+ }
173+ }
174+
175+ onRequiredChanged: {
176+ if (required) {
177+ d.waiting = true;
178+ lockedApp = "";
179+ }
180+ }
181+
182+ Component.onCompleted: {
183+ Connectivity.unlockAllModems();
184+ }
185+
186+ Timer {
187+ id: forcedDelayTimer
188+ interval: 1000 * 60
189+ onTriggered: {
190+ if (d.delayMinutes > 0) {
191+ d.delayMinutes -= 1;
192+ if (d.delayMinutes > 0) {
193+ start(); // go again
194+ }
195+ }
77 }
196 }
78 }
197 }
79
198
@@ -82,152 +201,205 @@ Showable {
82 MouseArea { anchors.fill: parent }
201 MouseArea { anchors.fill: parent }
83
202
84 Loader {
203 Loader {
85− id: greeterContentLoader
204+ id: loader
86− objectName: "greeterContentLoader"
205+ objectName: "loader"
206+
87 anchors.fill: parent
207 anchors.fill: parent
88− property var model: LightDM.Users
89− property int currentIndex: 0
90− property var infographicModel: LightDM.Infographic
91− readonly property int backgroundTopMargin: -greeter.y
92
208
93− source: loadContent ? "GreeterContent.qml" : ""
209+ active: root.required
210+ source: root.viewSource.toString() ? root.viewSource :
211+ (d.multiUser || root.tabletMode) ? "WideView.qml" : "NarrowView.qml"
94
212
95 onLoaded: {
213 onLoaded: {
214+ root.lockedApp = "";
215+ root.forceActiveFocus();
96− selected(currentIndex);
216+ d.selectUser(d.currentIndex, true);
217+ LightDM.Infographic.readyForDataChange();
97 }
218 }
98
219
99 Connections {
220 Connections {
100− target: greeterContentLoader.item
221+ target: loader.item
101−
102 onSelected: {
222 onSelected: {
103− greeter.selected(uid);
223+ d.selectUser(index, true);
104− greeterContentLoader.currentIndex = uid;
224+ }
225+ onResponded: {
226+ if (root.locked) {
227+ LightDM.Greeter.respond(response);
228+ } else {
229+ if (LightDM.Greeter.active && !LightDM.Greeter.authenticated) { // could happen if forcedUnlock
230+ d.login();
231+ }
232+ loader.item.hide();
233+ }
234+ }
235+ onTease: root.tease()
236+ onEmergencyCall: root.emergencyCall()
237+ onRequiredChanged: {
238+ if (!loader.item.required) {
239+ root.hide();
240+ }
105 }
241 }
106− onUnlocked: greeter.unlocked(uid);
107 }
242 }
243+
108 Binding {
244 Binding {
109− target: greeterContentLoader.item
245+ target: loader.item
110− property: "inputMethod"
246+ property: "backgroundTopMargin"
111− value: greeter.inputMethod
247+ value: -root.y
112 }
248 }
113− }
114
249
115− DragHandle {
250+ Binding {
116− id: dragHandle
251+ target: loader.item
117− anchors.fill: parent
252+ property: "launcherOffset"
118− anchors.leftMargin: greeter.dragHandleLeftMargin
253+ value: d.launcherOffsetProxy
119− enabled: (greeter.narrowMode || !greeter.locked) && greeter.enabled && greeter.shown
254+ }
120− direction: Direction.Horizontal
121
255
122− onTapped: {
256+ Binding {
123− greeter.tapped();
257+ target: loader.item
124− showLabelAnimation.start();
258+ property: "dragHandleLeftMargin"
259+ value: root.dragHandleLeftMargin
125 }
260 }
126
261
127− onDraggingChanged: {
262+ Binding {
128− if (dragging) {
263+ target: loader.item
129− showLabelAnimation.start();
264+ property: "delayMinutes"
265+ value: d.delayMinutes
266+ }
267+
268+ Binding {
269+ target: loader.item
270+ property: "background"
271+ value: root.background
272+ }
273+
274+ Binding {
275+ target: loader.item
276+ property: "locked"
277+ value: root.locked
130− }
278+ }
279+
280+ Binding {
281+ target: loader.item
282+ property: "alphanumeric"
283+ value: AccountsService.passwordDisplayHint === AccountsService.Keyboard
284+ }
285+
286+ Binding {
287+ target: loader.item
288+ property: "currentIndex"
289+ value: d.currentIndex
290+ }
291+
292+ Binding {
293+ target: loader.item
294+ property: "userModel"
295+ value: LightDM.Users
296+ }
297+
298+ Binding {
299+ target: loader.item
300+ property: "infographicModel"
301+ value: LightDM.Infographic
131 }
302 }
132 }
303 }
133
304
134− Label {
305+ Connections {
135− id: swipeHint
306+ target: LightDM.Greeter
136− property real baseOpacity: 0.5
307+
137− opacity: 0.0
308+ onShowGreeter: root.forceShow()
138− anchors.horizontalCenter: parent.horizontalCenter
309+
139− anchors.bottom: parent.bottom
310+ onHideGreeter: {
140− anchors.bottomMargin: units.gu(5)
311+ d.login();
141− text: "《 " + i18n.tr("Unlock") + " 》"
312+ loader.item.hide();
142− color: "white"
313+ }
143− font.weight: Font.Light
314+
144−
315+ onShowMessage: {
145− SequentialAnimation on opacity {
316+ if (!LightDM.Greeter.active) {
146− id: showLabelAnimation
317+ return; // could happen if hideGreeter() comes in before we prompt
147− running: false
318+ }
148− loops: 2
319+
149−
320+ // inefficient, but we only rarely deal with messages
150− StandardAnimation {
321+ var html = text.replace(/&/g, "&amp;")
151− from: 0.0
322+ .replace(/</g, "&lt;")
152− to: swipeHint.baseOpacity
323+ .replace(/>/g, "&gt;")
153− duration: UbuntuAnimation.SleepyDuration
324+ .replace(/\n/g, "<br>");
325+ if (isError) {
326+ html = "<font color=\"#df382c\">" + html + "</font>";
154 }
327 }
155− PauseAnimation { duration: UbuntuAnimation.BriskDuration }
328+
156− StandardAnimation {
329+ loader.item.showMessage(html);
157− from: swipeHint.baseOpacity
330+ }
158− to: 0.0
331+
159− duration: UbuntuAnimation.SleepyDuration
332+ onShowPrompt: {
333+ d.waiting = false;
334+
335+ if (!LightDM.Greeter.active) {
336+ return; // could happen if hideGreeter() comes in before we prompt
160 }
337 }
338+
339+ loader.item.showPrompt(text, isSecret, isDefaultPrompt);
161 }
340 }
162− }
163
341
164− // right side shadow
342+ onAuthenticationComplete: {
165− Image {
343+ d.waiting = false;
166− anchors.left: parent.right
167− anchors.top: parent.top
168− anchors.bottom: parent.bottom
169− fillMode: Image.Tile
170− source: "../graphics/dropshadow_right.png"
171− }
172
344
173− // left side shadow
345+ if (LightDM.Greeter.authenticated) {
174− Image {
346+ AccountsService.failedLogins = 0;
175− anchors.right: parent.left
347+ d.login();
176− anchors.top: parent.top
348+ if (!LightDM.Greeter.promptless) {
177− anchors.bottom: parent.bottom
349+ loader.item.hide();
178− fillMode: Image.Tile
350+ }
179− source: "../graphics/dropshadow_left.png"
351+ } else {
352+ if (!LightDM.Greeter.promptless) {
353+ AccountsService.failedLogins++;
180− }
354+ }
181
355
182− Binding {
356+ // Check if we should initiate a factory reset
183− id: positionLock
357+ if (maxFailedLogins >= 2) { // require at least a warning
358+ if (AccountsService.failedLogins === maxFailedLogins - 1) {
359+ loader.item.showLastChance();
360+ } else if (AccountsService.failedLogins >= maxFailedLogins) {
361+ SystemImage.factoryReset(); // Ouch!
362+ }
363+ }
184
364
185− property bool enabled: false
365+ // Check if we should initiate a forced login delay
186− onEnabledChanged: {
366+ if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
187− if (enabled === __enabled) {
367+ d.delayMinutes = failedLoginsDelayMinutes;
188− return;
368+ forcedDelayTimer.start();
189− }
369+ }
190
370
191− if (enabled) {
371+ loader.item.notifyAuthenticationFailed();
192− if (greeter.x > 0) {
372+ if (!LightDM.Greeter.promptless) {
193− value = Qt.binding(function() { return greeter.width; })
373+ d.selectUser(d.currentIndex, false);
194− } else {
195− value = Qt.binding(function() { return -greeter.width; })
196 }
374 }
197 }
375 }
198−
199− __enabled = enabled;
200 }
376 }
201
377
202− property bool __enabled: false
378+ onRequestAuthenticationUser: {
379+ // Find index for requested user, if it exists
380+ for (var i = 0; i < LightDM.Users.count; i++) {
381+ if (user === LightDM.Users.data(i, LightDM.UserRoles.NameRole)) {
382+ d.selectUser(i, true);
383+ return;
384+ }
385+ }
386+ }
387+ }
203
388
204− target: greeter
389+ Binding {
205− when: __enabled
390+ target: LightDM.Greeter
206− property: "x"
391+ property: "active"
392+ value: root.active
207 }
393 }
208
394
209− hideAnimation: SequentialAnimation {
395+ Binding {
210− id: hideAnimation
396+ target: LightDM.Infographic
211− objectName: "hideAnimation"
212− StandardAnimation {
213− id: hideTranslation
214− property: "x"
397+ property: "username"
215− target: greeter
398+ value: AccountsService.statsWelcomeScreen ? LightDM.Users.data(d.currentIndex, LightDM.UserRoles.NameRole) : ""
216− }
217− PropertyAction { target: greeter; property: "visible"; value: false }
218− PropertyAction { target: positionLock; property: "enabled"; value: true }
219 }
399 }
220
400
221− showAnimation: SequentialAnimation {
401+ Connections {
222− id: showAnimation
402+ target: i18n
223− objectName: "showAnimation"
403+ onLanguageChanged: LightDM.Infographic.readyForDataChange()
224− PropertyAction { target: greeter; property: "visible"; value: true }
225− PropertyAction { target: positionLock; property: "enabled"; value: false }
226− StandardAnimation {
227− property: "x"
228− target: greeter
229− to: 0
230− duration: UbuntuAnimation.FastDuration
231− }
232 }
404 }
233}
405}
qml/Greeter/GreeterContent.qmldeleted−163
@@ -1,163 +0,0 @@
1−/*
2− * Copyright (C) 2013 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.0
18−import AccountsService 0.1
19−import Ubuntu.Components 0.1
20−import LightDM 0.1 as LightDM
21−import "../Components"
22−
23−Item {
24− id: root
25− anchors.fill: parent
26−
27− property var inputMethod
28−
29− property bool ready: background.source == "" || background.status == Image.Ready || background.status == Image.Error
30−
31− signal selected(int uid)
32− signal unlocked(int uid)
33−
34− function tryToUnlock() {
35− if (loginLoader.item) {
36− loginLoader.item.tryToUnlock()
37− }
38− }
39−
40− function reset() {
41− if (loginLoader.item) {
42− loginLoader.item.reset()
43− }
44− }
45−
46− Rectangle {
47− // In case background fails to load
48− id: backgroundBackup
49− anchors.fill: parent
50− color: "black"
51− }
52−
53− CrossFadeImage {
54− id: background
55− objectName: "greeterBackground"
56− anchors {
57− fill: parent
58− topMargin: backgroundTopMargin
59− }
60− fillMode: Image.PreserveAspectCrop
61− // Limit how much memory we'll reserve for this image
62− sourceSize.height: height
63− sourceSize.width: width
64− source: greeter.background
65− }
66−
67− Rectangle {
68− anchors.fill: parent
69− color: "black"
70− opacity: 0.4
71− }
72−
73− Loader {
74− id: loginLoader
75− objectName: "loginLoader"
76− anchors {
77− left: parent.left
78− leftMargin: Math.min(parent.width * 0.16, units.gu(20))
79− top: parent.top
80− }
81− width: units.gu(29)
82− height: inputMethod && inputMethod.visible ? parent.height - inputMethod.keyboardRectangle.height
83− : parent.height
84− Behavior on height { UbuntuNumberAnimation {} }
85−
86− // TODO: Once we have a system API for determining which mode we are
87− // in, tablet/phone/desktop, that should be used instead of narrowMode.
88− source: greeter.narrowMode ? "" : "LoginList.qml"
89−
90− onLoaded: {
91− item.currentIndex = greeterContentLoader.currentIndex;
92− }
93−
94− Binding {
95− target: loginLoader.item
96− property: "model"
97− value: greeterContentLoader.model
98− }
99−
100− Connections {
101− target: loginLoader.item
102−
103− onSelected: {
104− root.selected(uid);
105− }
106−
107− onUnlocked: {
108− root.unlocked(uid);
109− }
110−
111− onCurrentIndexChanged: {
112− if (greeterContentLoader.currentIndex !== loginLoader.item.currentIndex) {
113− greeterContentLoader.currentIndex = loginLoader.item.currentIndex;
114− }
115− }
116− }
117− }
118−
119− Infographics {
120− id: infographics
121− objectName: "infographics"
122− height: narrowMode ? parent.height : 0.75 * parent.height
123− model: greeterContentLoader.infographicModel
124− clip: true // clip large data bubbles
125−
126− property string selectedUser
127− property string infographicUser: AccountsService.statsWelcomeScreen ? selectedUser : ""
128− onInfographicUserChanged: greeterContentLoader.infographicModel.username = infographicUser
129−
130− Component.onCompleted: {
131− selectedUser = greeterContentLoader.model.data(greeterContentLoader.currentIndex, LightDM.UserRoles.NameRole)
132− greeterContentLoader.infographicModel.username = infographicUser
133− greeterContentLoader.infographicModel.readyForDataChange()
134− }
135−
136− Connections {
137− target: root
138− onSelected: infographics.selectedUser = greeterContentLoader.model.data(uid, LightDM.UserRoles.NameRole)
139− }
140−
141− Connections {
142− target: i18n
143− onLanguageChanged: greeterContentLoader.infographicModel.readyForDataChange()
144− }
145−
146− anchors {
147− verticalCenter: parent.verticalCenter
148− left: narrowMode ? root.left : loginLoader.right
149− right: root.right
150− }
151− }
152−
153− Clock {
154− id: clock
155− visible: narrowMode
156−
157− anchors {
158− top: parent.top
159− topMargin: units.gu(2)
160− horizontalCenter: parent.horizontalCenter
161− }
162− }
163−}
qml/Greeter/Infographics.qml+1
@@ -38,6 +38,7 @@ Item {
38
38
39 Connections {
39 Connections {
40 target: model
40 target: model
41+ ignoreUnknownSignals: model === undefined
41
42
42 onDataAboutToAppear: startHideAnimation() // hide "no data" label
43 onDataAboutToAppear: startHideAnimation() // hide "no data" label
43 onDataAppeared: startShowAnimation()
44 onDataAppeared: startShowAnimation()
qml/Greeter/LoginList.qml+67−85
@@ -14,44 +14,76 @@
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−import QtQuick 2.0
17+import QtQuick 2.3
18−import Ubuntu.Components 0.1
18+import Ubuntu.Components 1.1
19−import LightDM 0.1 as LightDM
20import "../Components"
19import "../Components"
21
20
22Item {
21Item {
23 id: root
22 id: root
24
23
25− property alias userList: userList
26 property alias model: userList.model
24 property alias model: userList.model
27− property alias currentIndex: userList.currentIndex
25+ property int currentIndex
26+ property bool locked
28
27
29 readonly property int numAboveBelow: 4
28 readonly property int numAboveBelow: 4
30 readonly property int cellHeight: units.gu(5)
29 readonly property int cellHeight: units.gu(5)
31 readonly property int highlightedHeight: units.gu(10)
30 readonly property int highlightedHeight: units.gu(10)
32 readonly property int moveDuration: 200
31 readonly property int moveDuration: 200
32+ readonly property string currentUser: userList.currentItem.username
33 property bool wasPrompted: false
33 property bool wasPrompted: false
34
34
35− signal selected(int uid)
35+ signal selected(int index)
36− signal unlocked(int uid)
36+ signal responded(string response)
37
37
38 function tryToUnlock() {
38 function tryToUnlock() {
39− if (LightDM.Greeter.promptless) {
39+ if (wasPrompted) {
40− if (LightDM.Greeter.authenticated) {
40+ passwordInput.forceActiveFocus();
41+ } else {
42+ if (root.locked) {
41− root.unlocked(userList.currentIndex)
43+ root.selected(currentIndex);
42 } else {
44 } else {
43− root.resetAuthentication()
45+ root.responded("");
44 }
46 }
47+ }
48+ }
49+
50+ function showMessage(html) {
51+ if (infoLabel.text === "") {
52+ infoLabel.text = html;
45 } else {
53 } else {
54+ infoLabel.text += "<br>" + html;
55+ }
56+ }
57+
58+ function showPrompt(text, isSecret, isDefaultPrompt) {
59+ passwordInput.text = "";
60+ passwordInput.promptText = text;
61+ passwordInput.enabled = true;
62+ passwordInput.echoMode = isSecret ? TextInput.Password : TextInput.Normal;
63+ if (wasPrompted) // stay in text field if second prompt
46− passwordInput.forceActiveFocus()
64+ passwordInput.focus = true;
65+ wasPrompted = true;
66+ }
67+
68+ function showError() {
69+ wrongPasswordAnimation.start();
70+ root.resetAuthentication();
71+ if (wasPrompted) {
72+ passwordInput.focus = true;
47 }
73 }
48 }
74 }
49
75
50 function reset() {
76 function reset() {
51− root.resetAuthentication()
77+ root.resetAuthentication();
52 }
78 }
53
79
54− Keys.onEscapePressed: root.resetAuthentication()
80+ Keys.onEscapePressed: {
81+ selected(currentIndex);
82+ }
83+
84+ onCurrentIndexChanged: {
85+ userList.currentIndex = currentIndex;
86+ }
55
87
56 Rectangle {
88 Rectangle {
57 id: highlightItem
89 id: highlightItem
@@ -81,26 +113,24 @@ Item {
81 flickDeceleration: 10000
113 flickDeceleration: 10000
82
114
83 readonly property bool movingInternally: moveTimer.running || userList.moving
115 readonly property bool movingInternally: moveTimer.running || userList.moving
84−
85− onCurrentIndexChanged: {
86− if (LightDM.Greeter.authenticationUser != userList.model.data(currentIndex, LightDM.UserRoles.NameRole)) {
87− root.resetAuthentication();
88− }
89− }
90−
91 onMovingInternallyChanged: {
116 onMovingInternallyChanged: {
92− // Only emit the selected signal once we stop moving to avoid frequent background changes
93 if (!movingInternally) {
117 if (!movingInternally) {
94− root.selected(userList.currentIndex);
118+ root.selected(currentIndex);
95 }
119 }
96 }
120 }
97
121
122+ onCurrentIndexChanged: {
123+ root.resetAuthentication();
124+ moveTimer.start();
125+ }
126+
98 delegate: Item {
127 delegate: Item {
99 width: parent.width
128 width: parent.width
100 height: root.cellHeight
129 height: root.cellHeight
101
130
102 readonly property bool belowHighlight: (userList.currentIndex < 0 && index > 0) || (userList.currentIndex >= 0 && index > userList.currentIndex)
131 readonly property bool belowHighlight: (userList.currentIndex < 0 && index > 0) || (userList.currentIndex >= 0 && index > userList.currentIndex)
103 readonly property int belowOffset: root.highlightedHeight - root.cellHeight
132 readonly property int belowOffset: root.highlightedHeight - root.cellHeight
133+ readonly property string username: name
104
134
105 opacity: {
135 opacity: {
106 // The goal here is to make names less and less opaque as they
136 // The goal here is to make names less and less opaque as they
@@ -148,11 +178,8 @@ Item {
148 topMargin: parent.belowHighlight ? parent.belowOffset : 0
178 topMargin: parent.belowHighlight ? parent.belowOffset : 0
149 }
179 }
150 height: parent.height
180 height: parent.height
151− enabled: userList.currentIndex !== index
181+ enabled: userList.currentIndex !== index && parent.opacity > 0
152− onClicked: {
182+ onClicked: root.selected(index)
153− moveTimer.start();
154− userList.currentIndex = index;
155− }
156
183
157 Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
184 Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
158 }
185 }
@@ -204,6 +231,11 @@ Item {
204 width: parent.width - anchors.margins * 2
231 width: parent.width - anchors.margins * 2
205 opacity: userList.movingInternally ? 0 : 1
232 opacity: userList.movingInternally ? 0 : 1
206
233
234+ property string promptText
235+ placeholderText: root.wasPrompted ? promptText
236+ : (root.locked ? i18n.tr("Retry")
237+ : i18n.tr("Tap to unlock"))
238+
207 Behavior on opacity {
239 Behavior on opacity {
208 NumberAnimation { duration: 100 }
240 NumberAnimation { duration: 100 }
209 }
241 }
@@ -213,9 +245,11 @@ Item {
213 return;
245 return;
214 root.focus = true; // so that it can handle Escape presses for us
246 root.focus = true; // so that it can handle Escape presses for us
215 enabled = false;
247 enabled = false;
216− LightDM.Greeter.respond(text);
248+ root.responded(text);
249+ }
250+ Keys.onEscapePressed: {
251+ root.selected(currentIndex);
217 }
252 }
218− Keys.onEscapePressed: root.resetAuthentication()
219
253
220 Image {
254 Image {
221 anchors {
255 anchors {
@@ -223,7 +257,7 @@ Item {
223 rightMargin: units.gu(2)
257 rightMargin: units.gu(2)
224 verticalCenter: parent.verticalCenter
258 verticalCenter: parent.verticalCenter
225 }
259 }
226− visible: LightDM.Greeter.promptless
260+ visible: !root.wasPrompted
227 source: "graphics/icon_arrow.png"
261 source: "graphics/icon_arrow.png"
228 }
262 }
229
263
@@ -247,7 +281,7 @@ Item {
247 id: passwordMouseArea
281 id: passwordMouseArea
248 objectName: "passwordMouseArea"
282 objectName: "passwordMouseArea"
249 anchors.fill: passwordInput
283 anchors.fill: passwordInput
250− enabled: LightDM.Greeter.promptless
284+ enabled: !root.wasPrompted
251 onClicked: root.tryToUnlock()
285 onClicked: root.tryToUnlock()
252 }
286 }
253
287
@@ -256,62 +290,10 @@ Item {
256 return;
290 return;
257 }
291 }
258 infoLabel.text = "";
292 infoLabel.text = "";
259− passwordInput.placeholderText = "";
293+ passwordInput.promptText = "";
260 passwordInput.text = "";
294 passwordInput.text = "";
261 passwordInput.focus = false;
295 passwordInput.focus = false;
262 passwordInput.enabled = true;
296 passwordInput.enabled = true;
263 root.wasPrompted = false;
297 root.wasPrompted = false;
264− LightDM.Greeter.authenticate(userList.model.data(currentIndex, LightDM.UserRoles.NameRole));
265− }
266−
267− Connections {
268− target: LightDM.Greeter
269−
270− onShowMessage: {
271− // inefficient, but we only rarely deal with messages
272− var html = text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\n/g, "<br>")
273− if (isError)
274− html = "<font color=\"#df382c\">" + html + "</font>"
275− if (infoLabel.text == "")
276− infoLabel.text = html
277− else
278− infoLabel.text = infoLabel.text + "<br>" + html
279− }
280−
281− onShowPrompt: {
282− passwordInput.text = "";
283− passwordInput.placeholderText = text;
284− passwordInput.enabled = true;
285− passwordInput.echoMode = isSecret ? TextInput.Password : TextInput.Normal;
286− if (root.wasPrompted) // stay in text field if second prompt
287− passwordInput.focus = true;
288− root.wasPrompted = true;
289− }
290−
291− onAuthenticationComplete: {
292− if (LightDM.Greeter.promptless) {
293− passwordInput.placeholderText = LightDM.Greeter.authenticated ? "Tap to unlock" : "Retry";
294− return;
295− }
296− if (LightDM.Greeter.authenticated) {
297− root.unlocked(userList.currentIndex);
298− } else {
299− wrongPasswordAnimation.start();
300− root.resetAuthentication();
301− passwordInput.focus = true;
302− }
303− passwordInput.text = "";
304− }
305−
306− onRequestAuthenticationUser: {
307− // Find index for requested user, if it exists
308− for (var i = 0; i < userList.model.count; i++) {
309− if (user == userList.model.data(i, LightDM.UserRoles.NameRole)) {
310− moveTimer.start();
311− userList.currentIndex = i;
312− return;
313− }
314− }
315− }
316 }
298 }
317}
299}
qml/Greeter/NarrowView.qmlnew file+170
@@ -0,0 +1,170 @@
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.3
18+import Ubuntu.Components 1.1
19+import "../Components"
20+
21+FocusScope {
22+ id: root
23+
24+ property alias dragHandleLeftMargin: coverPage.dragHandleLeftMargin
25+ property alias launcherOffset: coverPage.launcherOffset
26+ property int currentIndex // unused
27+ property alias delayMinutes: lockscreen.delayMinutes
28+ property alias backgroundTopMargin: coverPage.backgroundTopMargin
29+ property url background
30+ property bool locked
31+ property bool alphanumeric
32+ property var userModel // unused
33+ property alias infographicModel: coverPage.infographicModel
34+ readonly property bool fullyShown: coverPage.showProgress === 1 || lockscreen.shown
35+ readonly property bool required: coverPage.required || lockscreen.required
36+
37+ signal selected(int index) // unused
38+ signal responded(string response)
39+ signal tease()
40+ signal emergencyCall()
41+
42+ function showMessage(html) {
43+ // TODO
44+ }
45+
46+ function showPrompt(text, isSecret, isDefaultPrompt) {
47+ lockscreen.promptText = isDefaultPrompt ? "" : text.toLowerCase();
48+ lockscreen.maybeShow();
49+ }
50+
51+ function showLastChance() {
52+ var title = lockscreen.alphaNumeric ?
53+ i18n.tr("Sorry, incorrect passphrase.") :
54+ i18n.tr("Sorry, incorrect passcode.");
55+ var text = i18n.tr("This will be your last attempt.") + " " +
56+ (lockscreen.alphaNumeric ?
57+ i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
58+ i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."));
59+ lockscreen.showInfoPopup(title, text);
60+ }
61+
62+ function hide() {
63+ lockscreen.hide();
64+ coverPage.hide();
65+ }
66+
67+ function notifyAuthenticationSucceeded() {
68+ lockscreen.hide();
69+ }
70+
71+ function notifyAuthenticationFailed() {
72+ lockscreen.clear(true);
73+ }
74+
75+ function reset() {
76+ coverPage.show();
77+ }
78+
79+ function tryToUnlock(toTheRight) {
80+ var coverChanged = coverPage.shown;
81+ lockscreen.maybeShow();
82+ if (toTheRight) {
83+ coverPage.hideRight();
84+ } else {
85+ coverPage.hide();
86+ }
87+ return coverChanged;
88+ }
89+
90+ onLockedChanged: {
91+ if (locked) {
92+ lockscreen.maybeShow();
93+ } else {
94+ lockscreen.hide();
95+ }
96+ }
97+
98+ Lockscreen {
99+ id: lockscreen
100+ objectName: "lockscreen"
101+
102+ shown: false
103+ showAnimation: StandardAnimation { property: "opacity"; to: 1 }
104+ hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
105+ anchors.fill: parent
106+ visible: required
107+ enabled: !coverPage.shown
108+ background: root.background
109+ darkenBackground: 0.4
110+ alphaNumeric: root.alphanumeric
111+ minPinLength: 4
112+ maxPinLength: 4
113+
114+ property string promptText
115+ infoText: promptText !== "" ? i18n.tr("Enter %1").arg(promptText) :
116+ alphaNumeric ? i18n.tr("Enter passphrase") :
117+ i18n.tr("Enter passcode")
118+ errorText: promptText !== "" ? i18n.tr("Sorry, incorrect %1").arg(promptText) :
119+ alphaNumeric ? i18n.tr("Sorry, incorrect passphrase") + "\n" +
120+ i18n.tr("Please re-enter") :
121+ i18n.tr("Sorry, incorrect passcode")
122+
123+ onEntered: root.responded(passphrase)
124+ onCancel: coverPage.show()
125+ onEmergencyCall: root.emergencyCall()
126+
127+ function maybeShow() {
128+ if (root.locked && !shown) {
129+ showNow();
130+ }
131+ }
132+ }
133+
134+ Rectangle {
135+ anchors.fill: parent
136+ color: "black"
137+ opacity: coverPage.showProgress * 0.8
138+ }
139+
140+ CoverPage {
141+ id: coverPage
142+ objectName: "coverPage"
143+ height: parent.height
144+ width: parent.width
145+ background: root.background
146+ onTease: root.tease()
147+
148+ onShowProgressChanged: {
149+ if (showProgress === 1) {
150+ lockscreen.reset();
151+ }
152+
153+ if (showProgress === 0) {
154+ if (root.locked) {
155+ lockscreen.clear(false); // to reset focus if necessary
156+ } else {
157+ root.responded("");
158+ }
159+ }
160+ }
161+
162+ Clock {
163+ anchors {
164+ top: parent.top
165+ topMargin: units.gu(2)
166+ horizontalCenter: parent.horizontalCenter
167+ }
168+ }
169+ }
170+}
qml/Greeter/WideView.qmlnew file+134
@@ -0,0 +1,134 @@
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.3
18+import Ubuntu.Components 1.1
19+
20+FocusScope {
21+ id: root
22+
23+ property alias dragHandleLeftMargin: coverPage.dragHandleLeftMargin
24+ property alias launcherOffset: coverPage.launcherOffset
25+ property alias currentIndex: loginList.currentIndex
26+ property int delayMinutes // TODO
27+ property alias backgroundTopMargin: coverPage.backgroundTopMargin
28+ property alias background: coverPage.background
29+ property bool locked
30+ property bool alphanumeric // unused
31+ property alias userModel: loginList.model
32+ property alias infographicModel: coverPage.infographicModel
33+ readonly property bool fullyShown: coverPage.showProgress === 1
34+ readonly property bool required: coverPage.required
35+
36+ // so that it can be replaced in tests with a mock object
37+ property var inputMethod: Qt.inputMethod
38+
39+ signal selected(int index)
40+ signal responded(string response)
41+ signal tease()
42+ signal emergencyCall() // unused
43+
44+ function showMessage(html) {
45+ loginList.showMessage(html);
46+ }
47+
48+ function showPrompt(text, isSecret, isDefaultPrompt) {
49+ loginList.showPrompt(text, isSecret, isDefaultPrompt);
50+ }
51+
52+ function showLastChance() {
53+ // TODO
54+ }
55+
56+ function hide() {
57+ coverPage.hide();
58+ }
59+
60+ function notifyAuthenticationSucceeded() {
61+ // Nothing needed
62+ }
63+
64+ function notifyAuthenticationFailed() {
65+ loginList.showError();
66+ }
67+
68+ function reset() {
69+ loginList.reset();
70+ }
71+
72+ function tryToUnlock(toTheRight) {
73+ if (root.locked) {
74+ coverPage.show();
75+ loginList.tryToUnlock();
76+ return false;
77+ } else {
78+ var coverChanged = coverPage.shown;
79+ if (toTheRight) {
80+ coverPage.hideRight();
81+ } else {
82+ coverPage.hide();
83+ }
84+ return coverChanged;
85+ }
86+ }
87+
88+ Rectangle {
89+ anchors.fill: parent
90+ color: "black"
91+ opacity: coverPage.showProgress * 0.8
92+ }
93+
94+ CoverPage {
95+ id: coverPage
96+ objectName: "coverPage"
97+ height: parent.height
98+ width: parent.width
99+ draggable: !root.locked
100+
101+ infographics {
102+ height: 0.75 * parent.height
103+ anchors.leftMargin: loginList.x + loginList.width
104+ }
105+
106+ onTease: root.tease()
107+
108+ onShowProgressChanged: {
109+ if (showProgress === 0 && !root.locked) {
110+ root.responded("");
111+ }
112+ }
113+
114+ LoginList {
115+ id: loginList
116+ objectName: "loginList"
117+
118+ anchors {
119+ left: parent.left
120+ leftMargin: Math.min(parent.width * 0.16, units.gu(20))
121+ top: parent.top
122+ }
123+ width: units.gu(29)
124+ height: inputMethod && inputMethod.visible ? parent.height - inputMethod.keyboardRectangle.height
125+ : parent.height
126+ Behavior on height { UbuntuNumberAnimation {} }
127+
128+ locked: root.locked
129+
130+ onSelected: root.selected(index)
131+ onResponded: root.responded(response)
132+ }
133+ }
134+}
qml/Shell.qml+67−353
@@ -22,9 +22,7 @@ import Unity.Application 0.1
22import Ubuntu.Components 0.1
22import Ubuntu.Components 0.1
23import Ubuntu.Components.Popups 1.0
23import Ubuntu.Components.Popups 1.0
24import Ubuntu.Gestures 0.1
24import Ubuntu.Gestures 0.1
25−import Ubuntu.SystemImage 0.1
26import Ubuntu.Telephony 0.1 as Telephony
25import Ubuntu.Telephony 0.1 as Telephony
27−import Unity.Connectivity 0.1
28import Unity.Launcher 0.1
26import Unity.Launcher 0.1
29import Utils 0.1
27import Utils 0.1
30import LightDM 0.1 as LightDM
28import LightDM 0.1 as LightDM
@@ -46,9 +44,9 @@ import Unity.Indicators 0.1 as Indicators
46Item {
44Item {
47 id: shell
45 id: shell
48
46
49− // Disable everything so that user can't swipe greeter or launcher until
47+ // Disable everything while greeter is waiting, so that the user can't swipe
50− // we get first prompt/authenticate, which will re-enable the shell.
48+ // the greeter or launcher until we know whether the session is locked.
51− enabled: false
49+ enabled: !greeter.waiting
52
50
53 // this is only here to select the width / height of the window if not running fullscreen
51 // this is only here to select the width / height of the window if not running fullscreen
54 property bool tablet: false
52 property bool tablet: false
@@ -61,18 +59,9 @@ Item {
61 : gsImageTester.status == Image.Ready ? gsImageTester.source : defaultBackground
59 : gsImageTester.status == Image.Ready ? gsImageTester.source : defaultBackground
62 readonly property real panelHeight: panel.panelHeight
60 readonly property real panelHeight: panel.panelHeight
63
61
64− readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated && !forcedUnlock
65− readonly property alias hasLockedApp: greeter.hasLockedApp
66− readonly property bool forcedUnlock: tutorial.running
67− onForcedUnlockChanged: if (forcedUnlock) lockscreen.hide()
68−
69 property bool sideStageEnabled: shell.width >= units.gu(100)
62 property bool sideStageEnabled: shell.width >= units.gu(100)
70 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
63 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
71
64
72− property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
73− property int failedLoginsDelayAttempts: 7 // number of failed logins
74− property int failedLoginsDelayMinutes: 5 // minutes of forced waiting
75−
76 property int orientation
65 property int orientation
77 readonly property int deviceOrientationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
66 readonly property int deviceOrientationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
78 onDeviceOrientationAngleChanged: {
67 onDeviceOrientationAngleChanged: {
@@ -99,7 +88,7 @@ Item {
99 }
88 }
100
89
101 function startLockedApp(app) {
90 function startLockedApp(app) {
102− if (shell.locked) {
91+ if (greeter.locked) {
103 greeter.lockedApp = app;
92 greeter.lockedApp = app;
104 }
93 }
105 shell.activateApplication(app);
94 shell.activateApplication(app);
@@ -117,6 +106,7 @@ Item {
117
106
118 GSettings {
107 GSettings {
119 id: backgroundSettings
108 id: backgroundSettings
109+ objectName: "backgroundSettings"
120 schema.id: "org.gnome.desktop.background"
110 schema.id: "org.gnome.desktop.background"
121 }
111 }
122
112
@@ -222,53 +212,32 @@ Item {
222
212
223 Connections {
213 Connections {
224 target: ApplicationManager
214 target: ApplicationManager
225− onFocusRequested: {
226− if (greeter.narrowMode) {
227− if (appId === "dialer-app" && callManager.hasCalls && shell.locked) {
228− // If we are in the middle of a call, make dialer lockedApp and show it.
229− // This can happen if user backs out of dialer back to greeter, then
230− // launches dialer again.
231− greeter.lockedApp = appId;
232− }
233− if (greeter.hasLockedApp) {
234− if (appId === greeter.lockedApp) {
235− lockscreen.hide() // show locked app
236− } else {
237− greeter.startUnlock() // show lockscreen if necessary
238− }
239− }
240− greeter.hide();
241− } else {
242− if (LightDM.Greeter.active) {
243− greeter.startUnlock()
244− }
245− }
246− }
247
215
216+ // This signal is also fired when we try to focus the current app
217+ // again. We rely on this!
248 onFocusedApplicationIdChanged: {
218 onFocusedApplicationIdChanged: {
249− if (greeter.hasLockedApp && greeter.lockedApp !== ApplicationManager.focusedApplicationId) {
219+ var appId = ApplicationManager.focusedApplicationId;
250− greeter.startUnlock()
251− }
252− panel.indicators.hide();
253− }
254−
255− onApplicationAdded: {
256− if (appId != "unity8-dash") {
257− if (greeter.shown) {
258− greeter.startUnlock();
259− }
260
220
221+ if (tutorial.running && appId != "unity8-dash") {
261 // If this happens on first boot, we may be in edge
222 // If this happens on first boot, we may be in edge
262 // tutorial or wizard while receiving a call. But a call
223 // tutorial or wizard while receiving a call. But a call
263 // is more important than wizard so just bail out of those.
224 // is more important than wizard so just bail out of those.
264− if (tutorial.running) {
265− tutorial.finish();
225+ tutorial.finish();
266− wizard.hide();
226+ wizard.hide();
267− }
268 }
227 }
269− if (greeter.narrowMode && greeter.hasLockedApp && appId === greeter.lockedApp) {
228+
270− lockscreen.hide() // show locked app
229+ if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
230+ // If we are in the middle of a call, make dialer lockedApp and show it.
231+ // This can happen if user backs out of dialer back to greeter, then
232+ // launches dialer again.
233+ greeter.lockedApp = appId;
271 }
234 }
235+ greeter.notifyAppFocused(appId);
236+
237+ panel.indicators.hide();
238+ }
239+
240+ onApplicationAdded: {
272 launcher.hide();
241 launcher.hide();
273 }
242 }
274 }
243 }
@@ -292,7 +261,6 @@ Item {
292
261
293 property bool interactive: tutorial.spreadEnabled
262 property bool interactive: tutorial.spreadEnabled
294 && !greeter.shown
263 && !greeter.shown
295− && !lockscreen.shown
296 && panel.indicators.fullyClosed
264 && panel.indicators.fullyClosed
297 && launcher.progress == 0
265 && launcher.progress == 0
298 && !notifications.useModal
266 && !notifications.useModal
@@ -328,7 +296,7 @@ Item {
328 Binding {
296 Binding {
329 target: applicationsDisplayLoader.item
297 target: applicationsDisplayLoader.item
330 property: "inverseProgress"
298 property: "inverseProgress"
331− value: launcher.progress
299+ value: greeter.locked ? 0 : launcher.progress
332 }
300 }
333 Binding {
301 Binding {
334 target: applicationsDisplayLoader.item
302 target: applicationsDisplayLoader.item
@@ -379,299 +347,50 @@ Item {
379 }
347 }
380 }
348 }
381
349
382− Lockscreen {
350+ Greeter {
383− id: lockscreen
351+ id: greeter
384− objectName: "lockscreen"
352+ objectName: "greeter"
385
353
386 hides: [launcher, panel.indicators]
354 hides: [launcher, panel.indicators]
387− shown: false
355+ tabletMode: shell.sideStageEnabled
388− enabled: true
356+ launcherOffset: launcher.progress
389− showAnimation: StandardAnimation { property: "opacity"; to: 1 }
357+ forcedUnlock: tutorial.running
390− hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
391− y: panel.panelHeight
392− visible: required
393− width: parent.width
394− height: parent.height - panel.panelHeight
395 background: shell.background
358 background: shell.background
396− darkenBackground: 0.4
397− alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
398− minPinLength: 4
399− maxPinLength: 4
400−
401− property string promptText
402− infoText: promptText !== "" ? i18n.tr("Enter %1").arg(promptText) :
403− alphaNumeric ? i18n.tr("Enter passphrase") :
404− i18n.tr("Enter passcode")
405− errorText: promptText !== "" ? i18n.tr("Sorry, incorrect %1").arg(promptText) :
406− alphaNumeric ? i18n.tr("Sorry, incorrect passphrase") + "\n" +
407− i18n.tr("Please re-enter") :
408− i18n.tr("Sorry, incorrect passcode")
409−
410− // FIXME: We *should* show emergency dialer if there is a SIM present,
411− // regardless of whether the side stage is enabled. But right now,
412− // the assumption is that narrow screens are phones which have SIMs
413− // and wider screens are tablets which don't. When we do allow this
414− // on devices with a side stage and a SIM, work should be done to
415− // ensure that the main stage is disabled while the dialer is present
416− // in the side stage. See the FIXME in the stage loader in this file.
417− showEmergencyCallButton: !shell.sideStageEnabled
418−
419− onEntered: LightDM.Greeter.respond(passphrase);
420− onCancel: greeter.show()
421− onEmergencyCall: startLockedApp("dialer-app")
422−
423− onShownChanged: if (shown) greeter.lockedApp = ""
424−
425− function maybeShow() {
426− if (!shell.forcedUnlock && !shown) {
427− showNow();
428− }
429− }
430−
431− Timer {
432− id: forcedDelayTimer
433− interval: 1000 * 60
434− onTriggered: {
435− if (lockscreen.delayMinutes > 0) {
436− lockscreen.delayMinutes -= 1
437− if (lockscreen.delayMinutes > 0) {
438− start() // go again
439− }
440− }
441− }
442− }
443−
444− Component.onCompleted: {
445− if (greeter.narrowMode) {
446− LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
447− }
448− }
449− }
450−
451− Connections {
452− target: LightDM.Greeter
453
359
454− onShowGreeter: greeter.show()
455− onHideGreeter: greeter.login()
456−
457− onShowPrompt: {
458− shell.enabled = true;
459− if (!LightDM.Greeter.active) {
460− return; // could happen if hideGreeter() comes in before we prompt
461− }
462− if (greeter.narrowMode) {
463− lockscreen.promptText = isDefaultPrompt ? "" : text.toLowerCase();
464− lockscreen.maybeShow();
465− }
466− }
467−
468− onPromptlessChanged: {
469− if (!LightDM.Greeter.active) {
470− return; // could happen if hideGreeter() comes in before we prompt
471− }
472− if (greeter.narrowMode) {
473− if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
474− lockscreen.hide()
475− } else {
476− lockscreen.reset();
477− lockscreen.maybeShow();
478− }
479− }
480− }
481−
482− onAuthenticationComplete: {
483− shell.enabled = true;
484− if (LightDM.Greeter.authenticated) {
485− AccountsService.failedLogins = 0
486− }
487− // Else only penalize user for a failed login if they actually were
488− // prompted for a password. We do this below after the promptless
489− // early exit.
490−
491− if (LightDM.Greeter.promptless) {
492− return;
493− }
494−
495− if (LightDM.Greeter.authenticated) {
496− greeter.login();
497− } else {
498− AccountsService.failedLogins++
499− if (maxFailedLogins >= 2) { // require at least a warning
500− if (AccountsService.failedLogins === maxFailedLogins - 1) {
501− var title = lockscreen.alphaNumeric ?
502− i18n.tr("Sorry, incorrect passphrase.") :
503− i18n.tr("Sorry, incorrect passcode.")
504− var text = i18n.tr("This will be your last attempt.") + " " +
505− (lockscreen.alphaNumeric ?
506− i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
507− i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
508− lockscreen.showInfoPopup(title, text)
509− } else if (AccountsService.failedLogins >= maxFailedLogins) {
510− SystemImage.factoryReset() // Ouch!
511− }
512− }
513− if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
514− lockscreen.delayMinutes = failedLoginsDelayMinutes
515− forcedDelayTimer.start()
516− }
517−
518− lockscreen.clear(true);
519− if (greeter.narrowMode) {
520− LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
521− }
522− }
523− }
524− }
525−
526− Binding {
527− target: LightDM.Greeter
528− property: "active"
529− value: greeter.shown || lockscreen.shown || greeter.hasLockedApp
530− }
531−
532− Rectangle {
533 anchors.fill: parent
360 anchors.fill: parent
534− color: "black"
535− opacity: greeterWrapper.showProgress * 0.8
536− }
537−
538− Item {
539− // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
540− id: greeterWrapper
541− objectName: "greeterWrapper"
542− x: (greeter.narrowMode && greeter.showProgress > 0) ? launcher.progress : 0
543− y: panel.panelHeight
544− width: parent.width
545− height: parent.height - panel.panelHeight
361+ anchors.topMargin: panel.panelHeight
546
362
547− Behavior on x {
363+ // avoid overlapping with Launcher's edge drag area
548− enabled: !launcher.dashSwipe
364+ // FIXME: Fix TouchRegistry & friends and remove this workaround
549− StandardAnimation {}
365+ // Issue involves launcher's DDA getting disabled on a long
550− }
366+ // left-edge drag
367+ dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
551
368
552− property bool fullyShown: showProgress === 1.0
369+ onSessionStarted: {
553− onFullyShownChanged: {
370+ launcher.hide();
554− // Wait until the greeter is completely covering lockscreen before resetting it.
555− if (greeter.narrowMode && fullyShown && !LightDM.Greeter.authenticated) {
556− lockscreen.reset();
557− lockscreen.maybeShow();
558− }
559 }
371 }
560
372
561− readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
373+ onTease: {
562− onShowProgressChanged: {
374+ if (!tutorial.running) {
563− if (showProgress === 0) {
375+ launcher.tease();
564− if ((LightDM.Greeter.promptless && LightDM.Greeter.authenticated) || shell.forcedUnlock) {
565− greeter.login()
566− } else if (greeter.narrowMode) {
567− lockscreen.clear(false) // to reset focus if necessary
568− }
569 }
376 }
570 }
377 }
571
378
572− Greeter {
379+ onEmergencyCall: startLockedApp("dialer-app")
573− id: greeter
574− objectName: "greeter"
575−
576− signal sessionStarted() // helpful for tests
577−
578− property string lockedApp: ""
579− property bool hasLockedApp: lockedApp !== ""
580−
581− hides: [launcher, panel.indicators]
582− loadContent: required || lockscreen.required // keeps content in memory for quick show()
583−
584− locked: shell.locked
585−
586− background: shell.background
587−
588− width: parent.width
589− height: parent.height
590−
591−
592− // avoid overlapping with Launcher's edge drag area
593− // FIXME: Fix TouchRegistry & friends and remove this workaround
594− // Issue involves launcher's DDA getting disabled on a long
595− // left-edge drag
596− dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
597−
598− function startUnlock() {
599− if (narrowMode) {
600− if (!LightDM.Greeter.authenticated) {
601− lockscreen.maybeShow()
602− }
603− hide()
604− } else {
605− show()
606− tryToUnlock()
607− }
608− }
609−
610− function login() {
611− enabled = false;
612− if (LightDM.Greeter.startSessionSync()) {
613− sessionStarted();
614− greeter.hide();
615− lockscreen.hide();
616− launcher.hide();
617− }
618− enabled = true;
619− }
620−
621− Timer {
622− // See powerConnection for why this is useful
623− id: showGreeterDelayed
624− interval: 1
625− onTriggered: {
626− greeter.showNow();
627− }
628− }
629−
630− onShownChanged: {
631− if (shown) {
632− // Disable everything so that user can't swipe greeter or
633− // launcher until we get the next prompt/authenticate, which
634− // will re-enable the shell.
635− shell.enabled = false;
636−
637− if (greeter.narrowMode) {
638− LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
639− } else {
640− reset()
641− }
642− greeter.lockedApp = "";
643− greeter.forceActiveFocus();
644− }
645− }
646−
647− Component.onCompleted: {
648− Connectivity.unlockAllModems()
649− }
650−
651− onUnlocked: greeter.hide()
652− onSelected: {
653− // Update launcher items for new user
654− var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
655− AccountsService.user = user;
656− LauncherModel.setUser(user);
657− }
658
380
659− onTapped: {
381+ Timer {
660− if (!tutorial.running) {
382+ // See powerConnection for why this is useful
661− launcher.tease();
383+ id: showGreeterDelayed
662− }
384+ interval: 1
663− }
385+ onTriggered: {
664− onDraggingChanged: {
386+ greeter.forceShow();
665− if (dragging && !tutorial.running) {
666− launcher.tease();
667− }
668 }
387 }
388+ }
669
389
670− Binding {
390+ Binding {
671− target: ApplicationManager
391+ target: ApplicationManager
672− property: "suspended"
392+ property: "suspended"
673− value: (greeter.shown && greeterWrapper.showProgress == 1) || lockscreen.shown
393+ value: greeter.shown
674− }
675 }
394 }
676 }
395 }
677
396
@@ -680,7 +399,7 @@ Item {
680 target: callManager
399 target: callManager
681
400
682 onHasCallsChanged: {
401 onHasCallsChanged: {
683− if (shell.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
402+ if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
684 // We just received an incoming call while locked. The
403 // We just received an incoming call while locked. The
685 // indicator will have already launched dialer-app for us, but
404 // indicator will have already launched dialer-app for us, but
686 // there is a race between "hasCalls" changing and the dialer
405 // there is a race between "hasCalls" changing and the dialer
@@ -720,9 +439,7 @@ Item {
720 return
439 return
721 }
440 }
722
441
723− if (LightDM.Greeter.active) {
442+ greeter.notifyAboutToFocusApp("unity8-dash");
724− greeter.startUnlock()
725− }
726
443
727 var animate = !LightDM.Greeter.active && !stages.shown
444 var animate = !LightDM.Greeter.active && !stages.shown
728 dash.setCurrentScope(0, animate, false)
445 dash.setCurrentScope(0, animate, false)
@@ -730,17 +447,11 @@ Item {
730 }
447 }
731
448
732 function showDash() {
449 function showDash() {
733− if (greeter.hasLockedApp || // just in case user gets here
450+ if (greeter.notifyShowingDashFromDrag()) {
734− (!greeter.narrowMode && shell.locked)) {
735− return
736− }
737−
738− if (greeter.shown) {
739− greeter.hideRight();
740 launcher.fadeOut();
451 launcher.fadeOut();
741 }
452 }
742
453
743− if (ApplicationManager.focusedApplicationId != "unity8-dash") {
454+ if (!greeter.locked && ApplicationManager.focusedApplicationId != "unity8-dash") {
744 ApplicationManager.requestFocusApplication("unity8-dash")
455 ApplicationManager.requestFocusApplication("unity8-dash")
745 launcher.fadeOut();
456 launcher.fadeOut();
746 }
457 }
@@ -758,7 +469,9 @@ Item {
758 anchors.fill: parent //because this draws indicator menus
469 anchors.fill: parent //because this draws indicator menus
759 indicators {
470 indicators {
760 hides: [launcher]
471 hides: [launcher]
472+ available: tutorial.panelEnabled
761− available: tutorial.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp
473+ && (!greeter.locked || AccountsService.enableIndicatorsWhileLocked)
474+ && !greeter.hasLockedApp
762 contentEnabled: tutorial.panelContentEnabled
475 contentEnabled: tutorial.panelContentEnabled
763 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
476 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
764
477
@@ -772,7 +485,7 @@ Item {
772 }
485 }
773 }
486 }
774 callHint {
487 callHint {
775− greeterShown: greeter.shown || lockscreen.shown
488+ greeterShown: greeter.shown
776 }
489 }
777
490
778 property bool topmostApplicationIsFullscreen:
491 property bool topmostApplicationIsFullscreen:
@@ -794,7 +507,9 @@ Item {
794 anchors.bottom: parent.bottom
507 anchors.bottom: parent.bottom
795 width: parent.width
508 width: parent.width
796 dragAreaWidth: shell.edgeSize
509 dragAreaWidth: shell.edgeSize
510+ available: tutorial.launcherEnabled
797− available: tutorial.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp
511+ && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
512+ && !greeter.hasLockedApp
798 inverted: usageModeSettings.usageMode === "Staged"
513 inverted: usageModeSettings.usageMode === "Staged"
799 shadeBackground: !tutorial.running
514 shadeBackground: !tutorial.running
800
515
@@ -806,11 +521,10 @@ Item {
806 }
521 }
807 }
522 }
808 onLauncherApplicationSelected: {
523 onLauncherApplicationSelected: {
809− if (greeter.hasLockedApp) {
810− greeter.startUnlock()
811− }
812− if (!tutorial.running)
524+ if (!tutorial.running) {
525+ greeter.notifyAboutToFocusApp(appId);
813 shell.activateApplication(appId)
526 shell.activateApplication(appId)
527+ }
814 }
528 }
815 onShownChanged: {
529 onShownChanged: {
816 if (shown) {
530 if (shown) {
tests/autopilot/unity8/shell/emulators/greeter.py+19−3
@@ -19,6 +19,7 @@
19
19
20import ubuntuuitoolkit
20import ubuntuuitoolkit
21
21
22+from autopilot.utilities import sleep
22from unity8.shell.emulators import UnityEmulatorBase
23from unity8.shell.emulators import UnityEmulatorBase
23
24
24
25
@@ -26,12 +27,27 @@ class Greeter(UnityEmulatorBase):
26 """An emulator that understands the greeter screen."""
27 """An emulator that understands the greeter screen."""
27
28
28 def wait_swiped_away(self):
29 def wait_swiped_away(self):
29− self.showProgress.wait_for(0)
30+ # We have to be careful here, because coverPage can go away at any time
31+ # if there isn't a lockscreen behind it (it hides completely, then
32+ # the greeter disposes it). But if there *is* a lockscreen, then we
33+ # need a different check, by looking at its showProgress. So make our
34+ # own wait_for loop and check both possibilities.
35+ for i in range(10):
36+ if not self.required:
37+ return
38+ coverPage = self.select_single(objectName='coverPage')
39+ if coverPage.showProgress == 0:
40+ return
41+ sleep(1)
42+
43+ raise AssertionError("Greeter cover page still up after 10s")
44+
30
45
31 def swipe(self):
46 def swipe(self):
32 """Swipe the greeter screen away."""
47 """Swipe the greeter screen away."""
33− self.created.wait_for(True)
48+ self.waiting.wait_for(False)
49+ coverPage = self.select_single(objectName='coverPage')
34− self.showProgress.wait_for(1)
50+ coverPage.showProgress.wait_for(1)
35
51
36 rect = self.globalRect
52 rect = self.globalRect
37 start_x = rect[0] + rect[2] - 3
53 start_x = rect[0] + rect[2] - 3
tests/autopilot/unity8/shell/emulators/main_window.py−6
@@ -35,12 +35,6 @@ class QQuickView(emulators.UnityEmulatorBase):
35 def get_greeter(self):
35 def get_greeter(self):
36 return self.select_single(Greeter)
36 return self.select_single(Greeter)
37
37
38− def get_greeter_content_loader(self):
39− return self.wait_select_single(
40− "QQuickLoader",
41− objectName="greeterContentLoader"
42− )
43−
44 def get_login_loader(self):
38 def get_login_loader(self):
45 return self.select_single("QQuickLoader", objectName="loginLoader")
39 return self.select_single("QQuickLoader", objectName="loginLoader")
46
40
tests/autopilot/unity8/shell/tests/__init__.py+2−3
@@ -375,9 +375,8 @@ class UnityTestCase(AutopilotTestCase):
375 self._proxy = None
375 self._proxy = None
376
376
377 def wait_for_unity(self):
377 def wait_for_unity(self):
378− greeter_content_loader = self.main_window.wait_select_single(
378+ greeter = self.main_window.wait_select_single(objectName='greeter')
379− objectName='greeterContentLoader')
379+ greeter.waiting.wait_for(False)
380− greeter_content_loader.progress.wait_for(1)
381
380
382 def get_dash(self):
381 def get_dash(self):
383 pid = process_helpers.get_job_pid('unity8-dash')
382 pid = process_helpers.get_job_pid('unity8-dash')
tests/autopilot/unity8/shell/tests/test_lock_screen.py+8−12
@@ -47,14 +47,13 @@ class TestLockscreen(UnityTestCase):
47 self.launch_unity()
47 self.launch_unity()
48 greeter = self.main_window.get_greeter()
48 greeter = self.main_window.get_greeter()
49
49
50− if greeter.narrowMode:
50+ if not greeter.tabletMode:
51 greeter.swipe()
51 greeter.swipe()
52 lockscreen = self._wait_for_lockscreen()
52 lockscreen = self._wait_for_lockscreen()
53 self.main_window.enter_pin_code("1234")
53 self.main_window.enter_pin_code("1234")
54− self.assertThat(lockscreen.shown, Eventually(Equals(False)))
55 else:
54 else:
56 self._enter_prompt_passphrase("1234\n")
55 self._enter_prompt_passphrase("1234\n")
57− self.assertThat(greeter.shown, Eventually(Equals(False)))
56+ self.assertThat(greeter.shown, Eventually(Equals(False)))
58
57
59 def test_can_unlock_passphrase_screen(self):
58 def test_can_unlock_passphrase_screen(self):
60 """Must be able to unlock the passphrase entry screen."""
59 """Must be able to unlock the passphrase entry screen."""
@@ -63,14 +62,13 @@ class TestLockscreen(UnityTestCase):
63 self.launch_unity()
62 self.launch_unity()
64 greeter = self.main_window.get_greeter()
63 greeter = self.main_window.get_greeter()
65
64
66− if greeter.narrowMode:
65+ if not greeter.tabletMode:
67 greeter.swipe()
66 greeter.swipe()
68 lockscreen = self._wait_for_lockscreen()
67 lockscreen = self._wait_for_lockscreen()
69 self._enter_pin_passphrase("password")
68 self._enter_pin_passphrase("password")
70− self.assertThat(lockscreen.shown, Eventually(Equals(False)))
71 else:
69 else:
72 self._enter_prompt_passphrase("password")
70 self._enter_prompt_passphrase("password")
73− self.assertThat(greeter.shown, Eventually(Equals(False)))
71+ self.assertThat(greeter.shown, Eventually(Equals(False)))
74
72
75 def test_pin_screen_wrong_code(self):
73 def test_pin_screen_wrong_code(self):
76 """Entering the wrong pin code must not dismiss the lock screen."""
74 """Entering the wrong pin code must not dismiss the lock screen."""
@@ -78,18 +76,17 @@ class TestLockscreen(UnityTestCase):
78 self.launch_unity()
76 self.launch_unity()
79 greeter = self.main_window.get_greeter()
77 greeter = self.main_window.get_greeter()
80
78
81− if greeter.narrowMode:
79+ if not greeter.tabletMode:
82 greeter.swipe()
80 greeter.swipe()
83 lockscreen = self._wait_for_lockscreen()
81 lockscreen = self._wait_for_lockscreen()
84 self.main_window.enter_pin_code("4321")
82 self.main_window.enter_pin_code("4321")
85 pinentryField = self.main_window.get_pinentryField()
83 pinentryField = self.main_window.get_pinentryField()
86 self.assertThat(pinentryField.text, Eventually(Equals("")))
84 self.assertThat(pinentryField.text, Eventually(Equals("")))
87− self.assertThat(lockscreen.shown, Eventually(Equals(True)))
88 else:
85 else:
89 self._enter_prompt_passphrase("4231\n")
86 self._enter_prompt_passphrase("4231\n")
90 prompt = self.main_window.get_greeter().get_prompt()
87 prompt = self.main_window.get_greeter().get_prompt()
91 self.assertThat(prompt.text, Eventually(Equals("")))
88 self.assertThat(prompt.text, Eventually(Equals("")))
92− self.assertThat(greeter.shown, Eventually(Equals(True)))
89+ self.assertThat(greeter.shown, Eventually(Equals(True)))
93
90
94 def test_passphrase_screen_wrong_password(self):
91 def test_passphrase_screen_wrong_password(self):
95 """Entering the wrong password must not dismiss the lock screen."""
92 """Entering the wrong password must not dismiss the lock screen."""
@@ -97,18 +94,17 @@ class TestLockscreen(UnityTestCase):
97 self.launch_unity()
94 self.launch_unity()
98 greeter = self.main_window.get_greeter()
95 greeter = self.main_window.get_greeter()
99
96
100− if greeter.narrowMode:
97+ if not greeter.tabletMode:
101 greeter.swipe()
98 greeter.swipe()
102 lockscreen = self._wait_for_lockscreen()
99 lockscreen = self._wait_for_lockscreen()
103 self._enter_pin_passphrase("foobar")
100 self._enter_pin_passphrase("foobar")
104 pinentryField = self.main_window.get_pinentryField()
101 pinentryField = self.main_window.get_pinentryField()
105 self.assertThat(pinentryField.text, Eventually(Equals("")))
102 self.assertThat(pinentryField.text, Eventually(Equals("")))
106− self.assertThat(lockscreen.shown, Eventually(Equals(True)))
107 else:
103 else:
108 self._enter_prompt_passphrase("foobar")
104 self._enter_prompt_passphrase("foobar")
109 prompt = self.main_window.get_greeter().get_prompt()
105 prompt = self.main_window.get_greeter().get_prompt()
110 self.assertThat(prompt.text, Eventually(Equals("")))
106 self.assertThat(prompt.text, Eventually(Equals("")))
111− self.assertThat(greeter.shown, Eventually(Equals(True)))
107+ self.assertThat(greeter.shown, Eventually(Equals(True)))
112
108
113 def _wait_for_lockscreen(self):
109 def _wait_for_lockscreen(self):
114 """Wait for the lock screen to load, and return it."""
110 """Wait for the lock screen to load, and return it."""
tests/plugins/LightDM/CMakeLists.txt+19−4
@@ -4,17 +4,32 @@ add_executable(GreeterDBusTestExec
4 )
4 )
5qt5_use_modules(GreeterDBusTestExec Core DBus Quick Test)
5qt5_use_modules(GreeterDBusTestExec Core DBus Quick Test)
6
6
7+add_executable(GreeterUsersModelTestExec
8+ usersmodel.cpp
9+ ${CMAKE_SOURCE_DIR}/plugins/LightDM/UsersModel.cpp
10+ ${CMAKE_SOURCE_DIR}/plugins/Utils/unitysortfilterproxymodelqml.cpp
11+ )
12+qt5_use_modules(GreeterUsersModelTestExec Core Test)
13+
7include_directories(
14include_directories(
8 ${CMAKE_CURRENT_BINARY_DIR}
15 ${CMAKE_CURRENT_BINARY_DIR}
9 ${CMAKE_SOURCE_DIR}/plugins/LightDM
16 ${CMAKE_SOURCE_DIR}/plugins/LightDM
17+ ${CMAKE_SOURCE_DIR}/plugins/Utils
10 ${CMAKE_SOURCE_DIR}/tests/mocks/LightDM
18 ${CMAKE_SOURCE_DIR}/tests/mocks/LightDM
11− ${LIBLIGHTDM_INCLUDE_DIRS}
12 )
19 )
13
20
14−add_dependencies(GreeterDBusTestExec MockLightDM)
21+target_link_libraries(GreeterDBusTestExec
22+ -L${CMAKE_BINARY_DIR}/tests/mocks/LightDM/liblightdm
23+ -llightdm-qt5-2
24+ )
25+
26+target_link_libraries(GreeterUsersModelTestExec
15−target_link_libraries(GreeterDBusTestExec -L${CMAKE_BINARY_DIR}/tests/mocks/LightDM/liblightdm
27+ -L${CMAKE_BINARY_DIR}/tests/mocks/LightDM/liblightdm
16− -llightdm-qt5-2)
28+ -llightdm-qt5-2
29+ )
17
30
18add_definitions(-DCURRENT_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
31add_definitions(-DCURRENT_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
19
32
20add_binary_qml_test(GreeterDBus "${CMAKE_BINARY_DIR}/tests/mocks/LightDM/liblightdm" MockLightDM "QML2_IMPORT_PATH=${CMAKE_BINARY_DIR}/tests/mocks")
33add_binary_qml_test(GreeterDBus "${CMAKE_BINARY_DIR}/tests/mocks/LightDM/liblightdm" MockLightDM "QML2_IMPORT_PATH=${CMAKE_BINARY_DIR}/tests/mocks")
34+
35+add_binary_qml_test(GreeterUsersModel "${CMAKE_BINARY_DIR}/tests/mocks/LightDM/liblightdm" MockLightDM "LIBLIGHTDM_MOCK_MODE=full")
tests/plugins/LightDM/usersmodel.cppnew file+84
@@ -0,0 +1,84 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "UsersModel.h"
18+
19+#include <QLightDM/UsersModel>
20+#include <QtTest>
21+
22+class GreeterUsersModelTest : public QObject
23+{
24+ Q_OBJECT
25+
26+private:
27+ static int findName(QAbstractItemModel *model, const QString &name)
28+ {
29+ for (int i = 0; i < model->rowCount(QModelIndex()); i++) {
30+ if (model->data(model->index(i, 0), QLightDM::UsersModel::NameRole).toString() == name) {
31+ return i;
32+ }
33+ }
34+ return -1;
35+ }
36+
37+ static QString getStringValue(QAbstractItemModel *model, const QString &name, int role)
38+ {
39+ int i = findName(model, name);
40+ return model->data(model->index(i, 0), role).toString();
41+ }
42+
43+private Q_SLOTS:
44+
45+ void init()
46+ {
47+ model = new UsersModel();
48+ QVERIFY(model);
49+ sourceModel = new QLightDM::UsersModel();
50+ QVERIFY(sourceModel);
51+ }
52+
53+ void cleanup()
54+ {
55+ delete model;
56+ delete sourceModel;
57+ }
58+
59+ void testMangleColor()
60+ {
61+ QString background = getStringValue(sourceModel, "color-background", QLightDM::UsersModel::BackgroundPathRole);
62+ QVERIFY(background == "#dd4814");
63+
64+ background = getStringValue(model, "color-background", QLightDM::UsersModel::BackgroundPathRole);
65+ QVERIFY(background == "data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>");
66+ }
67+
68+ void testMangleEmptyName()
69+ {
70+ QString name = getStringValue(sourceModel, "empty-name", QLightDM::UsersModel::RealNameRole);
71+ QVERIFY(name == "");
72+
73+ name = getStringValue(model, "empty-name", QLightDM::UsersModel::RealNameRole);
74+ QVERIFY(name == "empty-name");
75+ }
76+
77+private:
78+ UsersModel *model;
79+ QLightDM::UsersModel *sourceModel;
80+};
81+
82+QTEST_MAIN(GreeterUsersModelTest)
83+
84+#include "usersmodel.moc"
tests/qmltests/CMakeLists.txt+4−3
@@ -60,10 +60,11 @@ add_qml_test(Dash/ScopeSettings ScopeSettingList)
60add_qml_test(Dash/ScopeSettings ScopeSettingNumber)
60add_qml_test(Dash/ScopeSettings ScopeSettingNumber)
61add_qml_test(Dash/ScopeSettings ScopeSettingString)
61add_qml_test(Dash/ScopeSettings ScopeSettingString)
62add_qml_test(Dash/ScopeSettings ScopeSettingsWidgetFactory)
62add_qml_test(Dash/ScopeSettings ScopeSettingsWidgetFactory)
63−add_qml_test(Greeter MultiGreeter)
64−add_qml_test(Greeter SingleGreeter)
65−add_qml_test(Greeter Infographics)
66add_qml_test(Greeter Clock)
63add_qml_test(Greeter Clock)
64+add_qml_test(Greeter Greeter)
65+add_qml_test(Greeter Infographics)
66+add_qml_test(Greeter NarrowView)
67+add_qml_test(Greeter WideView)
67add_qml_test(Launcher Launcher)
68add_qml_test(Launcher Launcher)
68add_qml_test(Notifications Notifications)
69add_qml_test(Notifications Notifications)
69add_qml_test(Notifications VisualSnapDecisionsQueue)
70add_qml_test(Notifications VisualSnapDecisionsQueue)
tests/qmltests/Greeter/TestView.qmlnew file+99
@@ -0,0 +1,99 @@
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.3
18+import Ubuntu.Components 1.1
19+import "../Components"
20+
21+Item {
22+ objectName: "testView"
23+
24+ property real dragHandleLeftMargin
25+ property real launcherOffset
26+ property int currentIndex
27+ property int delayMinutes
28+ property real backgroundTopMargin
29+ property url background
30+ property bool locked
31+ property bool alphanumeric
32+ property var userModel
33+ property var infographicModel
34+ readonly property bool fullyShown: _fullyShown
35+ readonly property bool required: _required
36+
37+ property bool _fullyShown: true
38+ property bool _required: true
39+
40+ signal selected(int index)
41+ signal responded(string response)
42+ signal tease()
43+ signal emergencyCall()
44+
45+ signal _showMessageCalled(string html)
46+ signal _showPromptCalled(string text, bool isSecret, bool isDefaultPrompt)
47+ signal _showLastChanceCalled()
48+ signal _hideCalled()
49+ signal _notifyAuthenticationSucceededCalled()
50+ signal _notifyAuthenticationFailedCalled()
51+ signal _resetCalled()
52+ signal _tryToUnlockCalled(bool toTheRight)
53+
54+ function showMessage(html) {
55+ _showMessageCalled(html);
56+ }
57+
58+ function showPrompt(text, isSecret, isDefaultPrompt) {
59+ _showPromptCalled(text, isSecret, isDefaultPrompt);
60+ }
61+
62+ function showLastChance() {
63+ _showLastChanceCalled();
64+ }
65+
66+ function hide() {
67+ _hideCalled();
68+ _required = false;
69+ _fullyShown = false;
70+ }
71+
72+ function notifyAuthenticationSucceeded() {
73+ _notifyAuthenticationSucceededCalled();
74+ }
75+
76+ function notifyAuthenticationFailed() {
77+ _notifyAuthenticationFailedCalled();
78+ }
79+
80+ function reset() {
81+ _resetCalled();
82+ }
83+
84+ function tryToUnlock(toTheRight) {
85+ _tryToUnlockCalled(toTheRight);
86+ return true;
87+ }
88+
89+ Rectangle {
90+ anchors.fill: parent
91+ color: "black"
92+
93+ Label {
94+ text: "Fake view, nothing to see here"
95+ color: "white"
96+ anchors.centerIn: parent
97+ }
98+ }
99+}
tests/qmltests/Greeter/tst_Greeter.qmlnew file+472
@@ -0,0 +1,472 @@
1+/*
2+ * Copyright 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.0
18+import QtTest 1.0
19+import ".."
20+import "../../../qml/Greeter"
21+import Ubuntu.Components 0.1
22+import AccountsService 0.1
23+import LightDM 0.1 as LightDM
24+import Unity.Test 0.1 as UT
25+
26+Item {
27+ width: units.gu(120)
28+ height: units.gu(80)
29+
30+ property url defaultBackground: Qt.resolvedUrl("../../../qml/graphics/tablet_background.jpg")
31+
32+ Component.onCompleted: {
33+ // set the mock mode before loading
34+ LightDM.Greeter.mockMode = "full";
35+ LightDM.Users.mockMode = "full";
36+ loader.active = true;
37+ }
38+
39+ Loader {
40+ id: loader
41+
42+ active: false
43+ anchors.fill: parent
44+
45+ property bool itemDestroyed: false
46+ sourceComponent: Component {
47+ Greeter {
48+ width: loader.width
49+ height: loader.height
50+ background: defaultBackground
51+ viewSource: Qt.resolvedUrl("TestView.qml")
52+
53+ Component.onDestruction: {
54+ loader.itemDestroyed = true;
55+ }
56+ }
57+ }
58+ }
59+
60+ SignalSpy {
61+ id: teaseSpy
62+ target: loader.item
63+ signalName: "tease"
64+ }
65+
66+ SignalSpy {
67+ id: sessionStartedSpy
68+ target: loader.item
69+ signalName: "sessionStarted"
70+ }
71+
72+ SignalSpy {
73+ id: emergencyCallSpy
74+ target: loader.item
75+ signalName: "emergencyCall"
76+ }
77+
78+ UT.UnityTestCase {
79+ id: testCase
80+ name: "Greeter"
81+ when: windowShown
82+
83+ property Item greeter: loader.status === Loader.Ready ? loader.item : null
84+ property Item view
85+
86+ SignalSpy {
87+ id: viewShowMessageSpy
88+ target: testCase.view
89+ signalName: "_showMessageCalled"
90+ }
91+
92+ SignalSpy {
93+ id: viewShowPromptSpy
94+ target: testCase.view
95+ signalName: "_showPromptCalled"
96+ }
97+
98+ SignalSpy {
99+ id: viewShowLastChanceSpy
100+ target: testCase.view
101+ signalName: "_showLastChanceCalled"
102+ }
103+
104+ SignalSpy {
105+ id: viewHideSpy
106+ target: testCase.view
107+ signalName: "_hideCalled"
108+ }
109+
110+ SignalSpy {
111+ id: viewAuthenticationSucceededSpy
112+ target: testCase.view
113+ signalName: "_notifyAuthenticationSucceededCalled"
114+ }
115+
116+ SignalSpy {
117+ id: viewAuthenticationFailedSpy
118+ target: testCase.view
119+ signalName: "_notifyAuthenticationFailedCalled"
120+ }
121+
122+ SignalSpy {
123+ id: viewResetSpy
124+ target: testCase.view
125+ signalName: "_resetCalled"
126+ }
127+
128+ SignalSpy {
129+ id: viewTryToUnlockSpy
130+ target: testCase.view
131+ signalName: "_tryToUnlockCalled"
132+ }
133+
134+ function init() {
135+ teaseSpy.clear();
136+ sessionStartedSpy.clear();
137+ emergencyCallSpy.clear();
138+ viewShowMessageSpy.clear();
139+ viewShowPromptSpy.clear();
140+ viewShowLastChanceSpy.clear();
141+ viewHideSpy.clear();
142+ viewAuthenticationSucceededSpy.clear();
143+ viewAuthenticationFailedSpy.clear();
144+ viewResetSpy.clear();
145+ viewTryToUnlockSpy.clear();
146+ tryCompare(greeter, "waiting", false);
147+ view = findChild(greeter, "testView");
148+ verifySelected(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
149+ }
150+
151+ function cleanup() {
152+ loader.itemDestroyed = false;
153+ loader.active = false;
154+ tryCompare(loader, "status", Loader.Null);
155+ tryCompare(loader, "item", null);
156+ tryCompare(loader, "itemDestroyed", true);
157+ loader.active = true;
158+ tryCompare(loader, "status", Loader.Ready);
159+ removeTimeConstraintsFromDirectionalDragAreas(loader.item);
160+ }
161+
162+ function getIndexOf(name) {
163+ for (var i = 0; i < LightDM.Users.count; i++) {
164+ if (name === LightDM.Users.data(i, LightDM.UserRoles.NameRole)) {
165+ return i;
166+ }
167+ }
168+ fail("Didn't find name")
169+ return -1;
170+ }
171+
172+ function selectUser(name) {
173+ var i = getIndexOf(name);
174+ view.selected(i);
175+ verifySelected(name);
176+ return i;
177+ }
178+
179+ function verifySelected(name) {
180+ var i = getIndexOf(name);
181+ compare(view.currentIndex, i);
182+ compare(AccountsService.user, name);
183+ compare(LightDM.Greeter.authenticationUser, name);
184+ }
185+
186+ function verifyLoggedIn() {
187+ tryCompare(sessionStartedSpy, "count", 1);
188+ verify(viewAuthenticationSucceededSpy.count > 0);
189+ compare(LightDM.Greeter.authenticated, true);
190+ compare(greeter.shown, false);
191+ }
192+
193+ function test_unlockPass() {
194+ selectUser("has-password");
195+ tryCompare(viewShowPromptSpy, "count", 1);
196+ compare(viewShowPromptSpy.signalArguments[0][0], "Password");
197+ compare(viewShowPromptSpy.signalArguments[0][1], true);
198+ compare(viewShowPromptSpy.signalArguments[0][2], true);
199+
200+ view.responded("password");
201+ verifyLoggedIn();
202+ }
203+
204+ function test_unlockFail() {
205+ selectUser("has-password");
206+ tryCompare(viewShowPromptSpy, "count", 1);
207+ compare(viewShowPromptSpy.signalArguments[0][0], "Password");
208+ compare(viewShowPromptSpy.signalArguments[0][1], true);
209+ compare(viewShowPromptSpy.signalArguments[0][2], true);
210+
211+ view.responded("wr0ng p4ssw0rd");
212+ tryCompare(viewAuthenticationFailedSpy, "count", 1);
213+
214+ tryCompare(viewShowPromptSpy, "count", 2);
215+ compare(viewShowPromptSpy.signalArguments[0][0], "Password");
216+ compare(viewShowPromptSpy.signalArguments[0][1], true);
217+ compare(viewShowPromptSpy.signalArguments[0][2], true);
218+ }
219+
220+ function test_promptless() {
221+ selectUser("no-password");
222+ tryCompare(viewAuthenticationSucceededSpy, "count", 1);
223+ compare(sessionStartedSpy.count, 1);
224+ compare(viewShowPromptSpy.count, 0);
225+ compare(viewHideSpy.count, 0);
226+ compare(view.locked, false);
227+ }
228+
229+ function test_twoFactorPass() {
230+ selectUser("two-factor");
231+ tryCompare(viewShowPromptSpy, "count", 1);
232+ compare(viewShowPromptSpy.signalArguments[0][0], "Password");
233+ compare(viewShowPromptSpy.signalArguments[0][1], true);
234+ compare(viewShowPromptSpy.signalArguments[0][2], true);
235+
236+ view.responded("password");
237+ tryCompare(viewShowPromptSpy, "count", 2);
238+ compare(viewShowPromptSpy.signalArguments[1][0], "otp");
239+ compare(viewShowPromptSpy.signalArguments[1][1], false);
240+ compare(viewShowPromptSpy.signalArguments[1][2], false);
241+
242+ view.responded("otp");
243+ verifyLoggedIn();
244+ }
245+
246+ function test_twoFactorFailOnFirst() {
247+ selectUser("two-factor");
248+ tryCompare(viewShowPromptSpy, "count", 1);
249+ compare(viewShowPromptSpy.signalArguments[0][0], "Password");
250+ compare(viewShowPromptSpy.signalArguments[0][1], true);
251+ compare(viewShowPromptSpy.signalArguments[0][2], true);
252+
253+ view.responded("wr0ng p4ssw0rd");
254+ tryCompare(viewAuthenticationFailedSpy, "count", 1);
255+
256+ tryCompare(viewShowPromptSpy, "count", 2);
257+ compare(viewShowPromptSpy.signalArguments[0][0], "Password");
258+ compare(viewShowPromptSpy.signalArguments[0][1], true);
259+ compare(viewShowPromptSpy.signalArguments[0][2], true);
260+ }
261+
262+ function test_twoFactorFailOnSecond() {
263+ selectUser("two-factor");
264+ tryCompare(viewShowPromptSpy, "count", 1);
265+ compare(viewShowPromptSpy.signalArguments[0][0], "Password");
266+ compare(viewShowPromptSpy.signalArguments[0][1], true);
267+ compare(viewShowPromptSpy.signalArguments[0][2], true);
268+
269+ view.responded("password");
270+ tryCompare(viewShowPromptSpy, "count", 2);
271+ compare(viewShowPromptSpy.signalArguments[1][0], "otp");
272+ compare(viewShowPromptSpy.signalArguments[1][1], false);
273+ compare(viewShowPromptSpy.signalArguments[1][2], false);
274+
275+ view.responded("wr0ng p4ssw0rd");
276+ tryCompare(viewAuthenticationFailedSpy, "count", 1);
277+
278+ tryCompare(viewShowPromptSpy, "count", 3);
279+ compare(viewShowPromptSpy.signalArguments[0][0], "Password");
280+ compare(viewShowPromptSpy.signalArguments[0][1], true);
281+ compare(viewShowPromptSpy.signalArguments[0][2], true);
282+ }
283+
284+ function test_htmlInfoPrompt() {
285+ selectUser("html-info-prompt");
286+ tryCompare(viewShowPromptSpy, "count", 1);
287+ compare(viewShowMessageSpy.count, 1);
288+ compare(viewShowMessageSpy.signalArguments[0][0], "&lt;b&gt;&amp;&lt;/b&gt;");
289+ }
290+
291+ function test_multiInfoPrompt() {
292+ selectUser("multi-info-prompt");
293+ tryCompare(viewShowPromptSpy, "count", 1);
294+ compare(viewShowMessageSpy.count, 3);
295+ compare(viewShowMessageSpy.signalArguments[0][0], "Welcome to Unity Greeter");
296+ compare(viewShowMessageSpy.signalArguments[1][0], "<font color=\"#df382c\">This is an error</font>");
297+ compare(viewShowMessageSpy.signalArguments[2][0], "You should have seen three messages");
298+ }
299+
300+ function test_waiting() {
301+ // Make sure we unset 'waiting' on prompt
302+ selectUser("has-password");
303+ compare(greeter.waiting, true);
304+ tryCompare(greeter, "waiting", false);
305+
306+ // Make sure we unset 'waiting' on authentication
307+ selectUser("no-password");
308+ compare(greeter.waiting, true);
309+ tryCompare(greeter, "waiting", false);
310+ }
311+
312+ function test_locked() {
313+ selectUser("has-password");
314+ compare(view.locked, true);
315+
316+ LightDM.Greeter.active = false;
317+ compare(view.locked, false);
318+ LightDM.Greeter.active = true;
319+
320+ greeter.forcedUnlock = true;
321+ compare(view.locked, false);
322+ greeter.forcedUnlock = false;
323+
324+ selectUser("no-password");
325+ tryCompare(view, "locked", false);
326+ selectUser("has-password");
327+ }
328+
329+ function test_fullyShown() {
330+ compare(greeter.fullyShown, true);
331+ view.hide();
332+ compare(greeter.fullyShown, false);
333+ }
334+
335+ function test_alphanumeric() {
336+ selectUser("has-password");
337+ compare(view.alphanumeric, true);
338+ selectUser("has-pin");
339+ compare(view.alphanumeric, false);
340+ }
341+
342+ function test_background() {
343+ greeter.background = "testing";
344+ compare(view.background, Qt.resolvedUrl("testing"));
345+ }
346+
347+ function test_notifyAboutToFocusApp() {
348+ greeter.notifyAboutToFocusApp("fake-app");
349+ compare(viewTryToUnlockSpy.count, 1);
350+ compare(viewTryToUnlockSpy.signalArguments[0][0], false);
351+ }
352+
353+ function test_notifyShowingDashFromDrag() {
354+ compare(greeter.notifyShowingDashFromDrag("fake-app"), true);
355+ compare(viewTryToUnlockSpy.count, 1);
356+ compare(viewTryToUnlockSpy.signalArguments[0][0], true);
357+ }
358+
359+ function test_dragHandleLeftMargin() {
360+ compare(view.dragHandleLeftMargin, 0);
361+ greeter.dragHandleLeftMargin = 5;
362+ compare(view.dragHandleLeftMargin, 5);
363+ }
364+
365+ function test_launcherOffset() {
366+ compare(view.launcherOffset, 0);
367+ greeter.launcherOffset = 5;
368+ tryCompare(view, "launcherOffset", 5);
369+ }
370+
371+ function test_laucherOffsetAnimation() {
372+ // Our logic for smoothing launcherOffset when it suddenly goes to
373+ // zero is a bit complicated. Let's just make sure it works here.
374+
375+ launcherOffsetWatcher.target = view;
376+
377+ // should follow immediately
378+ launcherOffsetWatcher.values = [];
379+ greeter.launcherOffset = 100;
380+ compare(view.launcherOffset, 100);
381+ compare(launcherOffsetWatcher.values.length, 1);
382+
383+ // should interpolate values until it reaches 0
384+ launcherOffsetWatcher.values = [];
385+ greeter.launcherOffset = 0;
386+ tryCompare(view, "launcherOffset", 0);
387+ verify(launcherOffsetWatcher.values.length > 1);
388+ for (var i = 0; i < launcherOffsetWatcher.values.length - 1; ++i) {
389+ verify(launcherOffsetWatcher.values[i] > 0.0);
390+ verify(launcherOffsetWatcher.values[i] < 100.0);
391+ }
392+ }
393+ Connections {
394+ id: launcherOffsetWatcher
395+ property var values: []
396+ onLauncherOffsetChanged: {
397+ values.push(target.launcherOffset);
398+ }
399+ }
400+
401+ function test_backgroundTopMargin() {
402+ compare(view.backgroundTopMargin, 0);
403+ greeter.y = 5;
404+ compare(view.backgroundTopMargin, -5);
405+ }
406+
407+ function test_differentPrompt() {
408+ selectUser("different-prompt");
409+ tryCompare(viewShowPromptSpy, "count", 1);
410+ compare(viewShowPromptSpy.signalArguments[0][0], "Secret word");
411+ compare(viewShowPromptSpy.signalArguments[0][1], true);
412+ compare(viewShowPromptSpy.signalArguments[0][2], false);
413+ }
414+
415+ function test_authError() {
416+ selectUser("auth-error");
417+ tryCompare(viewAuthenticationFailedSpy, "count", 1);
418+ compare(viewShowPromptSpy.count, 0);
419+ compare(view.locked, true);
420+ }
421+
422+ function test_statsWelcomeScreen() {
423+ // Test logic in greeter that turns statsWelcomeScreen setting into infographic changes
424+ selectUser("has-password");
425+ compare(LightDM.Infographic.username, "has-password");
426+ AccountsService.statsWelcomeScreen = false;
427+ compare(LightDM.Infographic.username, "");
428+ AccountsService.statsWelcomeScreen = true;
429+ compare(LightDM.Infographic.username, "has-password");
430+ }
431+
432+ function test_dbusRequestAuthenticationUser() {
433+ selectUser("no-password");
434+ LightDM.Greeter.requestAuthenticationUser("has-password");
435+ verifySelected("has-password");
436+ }
437+
438+ function test_dbusHideGreeter() {
439+ compare(view.required, true);
440+ LightDM.Greeter.hideGreeter();
441+ compare(view.required, false);
442+ compare(greeter.required, false);
443+ }
444+
445+ function test_dbusShowGreeterFromHiddenState() {
446+ greeter.hide();
447+ compare(greeter.required, false);
448+
449+ LightDM.Greeter.showGreeter();
450+ compare(greeter.required, true);
451+ compare(greeter.fullyShown, true);
452+ view = findChild(greeter, "testView");
453+ compare(view.required, true);
454+
455+ // Can't test some of the stuff called on 'view' here because
456+ // the view was torn down and created again. So the spies missed
457+ // the good stuff while it down. See next test for more.
458+ }
459+
460+ function test_dbusShowGreeterFromShownState() {
461+ selectUser("has-password");
462+ compare(viewResetSpy.count, 1);
463+ tryCompare(viewShowPromptSpy, "count", 1);
464+
465+ viewResetSpy.clear();
466+ viewShowPromptSpy.clear();
467+
468+ LightDM.Greeter.showGreeter();
469+ compare(viewResetSpy.count, 1);
470+ }
471+ }
472+}
tests/qmltests/Greeter/tst_MultiGreeter.qmldeleted−475
@@ -1,475 +0,0 @@
1−/*
2− * Copyright 2013 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.0
18−import QtQuick.Layouts 1.1
19−import QtTest 1.0
20−import ".."
21−import "../../../qml/Greeter"
22−import Ubuntu.Components 0.1
23−import LightDM 0.1 as LightDM
24−import Unity.Test 0.1 as UT
25−
26−Item {
27− id: root
28− width: units.gu(140)
29− height: units.gu(80)
30−
31− QtObject {
32− id: fakeInputMethod
33− property bool visible: fakeKeyboard.visible
34− property var keyboardRectangle: QtObject {
35− property real x: fakeKeyboard.x
36− property real y: fakeKeyboard.y
37− property real width: fakeKeyboard.width
38− property real height: fakeKeyboard.height
39− }
40− }
41−
42− Binding {
43− target: LightDM.Greeter
44− property: "mockMode"
45− value: "full"
46− }
47− Binding {
48− target: LightDM.Users
49− property: "mockMode"
50− value: "full"
51− }
52−
53− Greeter {
54− id: greeter
55− width: units.gu(120)
56− height: root.height
57− inputMethod: fakeInputMethod
58− locked: !LightDM.Greeter.authenticated
59− }
60−
61− Component {
62− id: greeterComponent
63− Greeter {
64− SignalSpy {
65− objectName: "selectedSpy"
66− target: parent
67− signalName: "selected"
68− }
69− }
70− }
71−
72− Rectangle {
73− id: fakeKeyboard
74− color: "green"
75− opacity: 0.7
76− anchors.bottom: root.bottom
77− width: greeter.width
78− height: greeter.height * 0.6
79− visible: keyboardVisibleCheckbox.checked
80− Text {
81− text: "Keyboard Rectangle"
82− color: "yellow"
83− font.bold: true
84− fontSizeMode: Text.Fit
85− minimumPixelSize: 10; font.pixelSize: 200
86− verticalAlignment: Text.AlignVCenter
87− x: (parent.width - width) / 2
88− y: (parent.height - height) / 2
89− width: parent.width
90− height: parent.height
91− }
92− }
93−
94− Item {
95− anchors {
96− top: root.top
97− bottom: root.bottom
98− left: greeter.right
99− right: root.right
100− }
101− RowLayout {
102− Layout.fillWidth: true
103− CheckBox {
104− id: keyboardVisibleCheckbox
105− }
106− Label { text: "Keyboard Visible"; anchors.verticalCenter: parent.verticalCenter }
107− }
108− }
109−
110− SignalSpy {
111− id: unlockSpy
112− target: greeter
113− signalName: "unlocked"
114− }
115−
116− SignalSpy {
117− id: selectionSpy
118− target: greeter
119− signalName: "selected"
120− }
121−
122− SignalSpy {
123− id: tappedSpy
124− target: greeter
125− signalName: "tapped"
126− }
127−
128− UT.UnityTestCase {
129− name: "MultiGreeter"
130− when: windowShown
131−
132− function cleanup() {
133− keyboardVisibleCheckbox.checked = false;
134− }
135−
136− function select_index(i) {
137− // We could be anywhere in list; find target index to know which direction
138− var userlist = findChild(greeter, "userList")
139− if (userlist.currentIndex == i)
140− keyClick(Qt.Key_Escape) // Reset state if we're not moving
141− while (userlist.currentIndex != i) {
142− var next = userlist.currentIndex + 1
143− if (userlist.currentIndex > i) {
144− next = userlist.currentIndex - 1
145− }
146− var account = findChild(greeter, "username"+next)
147− mouseClick(account, 1, 1)
148− tryCompare(userlist, "currentIndex", next)
149− tryCompare(userlist, "movingInternally", false)
150− }
151− }
152−
153− function select_user(name) {
154− // We could be anywhere in list; find target index to know which direction
155− for (var i = 0; i < greeter.model.count; i++) {
156− if (greeter.model.data(i, LightDM.UserRoles.NameRole) == name) {
157− break
158− }
159− }
160− if (i == greeter.model.count) {
161− fail("Didn't find name")
162− return -1
163− }
164− select_index(i)
165− return i
166− }
167−
168− function test_properties() {
169− compare(greeter.multiUser, true)
170− compare(greeter.narrowMode, false)
171− }
172−
173− function test_cycle_data() {
174− var data = new Array()
175− for (var i = 0; i < greeter.model.count; i++) {
176− data[i] = {tag: greeter.model.data(i, LightDM.UserRoles.NameRole), uid: i }
177− }
178− return data
179− }
180−
181− function test_cycle(data) {
182− selectionSpy.clear();
183− var userList = findChild(greeter, "userList")
184− var waitForSignal = data.uid != 0 && userList.currentIndex != data.uid
185− select_index(data.uid)
186− tryCompare(userList, "currentIndex", data.uid)
187− tryCompare(greeter, "locked", data.tag !== "no-password")
188− if (waitForSignal) {
189− selectionSpy.wait()
190− tryCompare(selectionSpy, "count", 1)
191− }
192− }
193−
194− function test_unlock_password() {
195− select_user("no-password") // to guarantee a selected signal
196− unlockSpy.clear()
197− select_user("has-password")
198− var passwordInput = findChild(greeter, "passwordInput")
199− tryCompare(passwordInput, "opacity", 1)
200− mouseClick(passwordInput, 1, 1)
201− compare(unlockSpy.count, 0)
202− typeString("password")
203− keyClick(Qt.Key_Enter)
204− unlockSpy.wait()
205− }
206−
207− function test_unlock_wrong_password() {
208− select_user("no-password") // to guarantee a selected signal
209− unlockSpy.clear()
210− select_user("has-password")
211− wait(0) // spin event loop to start any pending animations
212− var passwordInput = findChild(greeter, "passwordInput")
213− tryCompare(passwordInput, "opacity", 1) // wait for opacity animation to be finished
214− mouseClick(passwordInput, 1, 1)
215− compare(unlockSpy.count, 0)
216− typeString("wr0ng p4ssw0rd")
217− keyClick(Qt.Key_Enter)
218− compare(unlockSpy.count, 0)
219− }
220−
221− function test_unlock_no_password() {
222− unlockSpy.clear()
223− select_user("no-password")
224− var passwordInput = findChild(greeter, "passwordInput")
225− tryCompare(passwordInput, "opacity", 1)
226− mouseClick(passwordInput, 1, 1)
227− unlockSpy.wait()
228− compare(unlockSpy.count, 1)
229− }
230−
231− function test_empty_name() {
232− for (var i = 0; i < greeter.model.count; i++) {
233− if (greeter.model.data(i, LightDM.UserRoles.NameRole) == "empty-name") {
234− compare(greeter.model.data(i, LightDM.UserRoles.RealNameRole), greeter.model.data(i, LightDM.UserRoles.NameRole))
235− return
236− }
237− }
238− fail("Didn't find empty-name")
239− }
240−
241− function test_auth_error() {
242− select_user("auth-error")
243− var passwordInput = findChild(greeter, "passwordInput")
244− tryCompare(passwordInput, "placeholderText", "Retry")
245− }
246−
247− function test_different_prompt() {
248− select_user("different-prompt")
249− var passwordInput = findChild(greeter, "passwordInput")
250− tryCompare(passwordInput, "placeholderText", "Secret word")
251− }
252−
253− function test_no_response() {
254− unlockSpy.clear()
255− select_user("no-response")
256− var passwordInput = findChild(greeter, "passwordInput")
257− tryCompare(passwordInput, "opacity", 1)
258− mouseClick(passwordInput, 1, 1)
259− compare(unlockSpy.count, 0)
260− typeString("password")
261− keyClick(Qt.Key_Enter)
262− tryCompare(passwordInput, "enabled", false)
263− keyClick(Qt.Key_Escape)
264− tryCompare(passwordInput, "enabled", true)
265− compare(unlockSpy.count, 0)
266− }
267−
268− function test_two_factor_correct() {
269− unlockSpy.clear()
270− select_user("two-factor")
271− var passwordInput = findChild(greeter, "passwordInput")
272− tryCompare(passwordInput, "opacity", 1)
273− tryCompare(passwordInput, "echoMode", TextInput.Password)
274− tryCompare(passwordInput, "placeholderText", "Password")
275− mouseClick(passwordInput, 1, 1)
276− compare(unlockSpy.count, 0)
277− typeString("password")
278− keyClick(Qt.Key_Enter)
279− tryCompare(passwordInput, "echoMode", TextInput.Normal)
280− tryCompare(passwordInput, "placeholderText", "otp")
281− tryCompare(passwordInput, "enabled", true)
282− typeString("otp")
283− keyClick(Qt.Key_Enter)
284− unlockSpy.wait()
285− }
286−
287− function test_two_factor_wrong1() {
288− unlockSpy.clear()
289− select_user("two-factor")
290− var passwordInput = findChild(greeter, "passwordInput")
291− tryCompare(passwordInput, "opacity", 1)
292− tryCompare(passwordInput, "placeholderText", "Password")
293− mouseClick(passwordInput, 1, 1)
294− compare(unlockSpy.count, 0)
295− typeString("wr0ng p4ssw0rd")
296− keyClick(Qt.Key_Enter)
297− tryCompare(passwordInput, "placeholderText", "Password")
298− tryCompare(passwordInput, "enabled", true)
299− compare(unlockSpy.count, 0)
300− }
301−
302− function test_two_factor_wrong2() {
303− unlockSpy.clear()
304− select_user("two-factor")
305− var passwordInput = findChild(greeter, "passwordInput")
306− tryCompare(passwordInput, "opacity", 1)
307− tryCompare(passwordInput, "placeholderText", "Password")
308− mouseClick(passwordInput, 1, 1)
309− compare(unlockSpy.count, 0)
310− typeString("password")
311− keyClick(Qt.Key_Enter)
312− tryCompare(passwordInput, "placeholderText", "otp")
313− tryCompare(passwordInput, "enabled", true)
314− typeString("wr0ng p4ssw0rd")
315− keyClick(Qt.Key_Enter)
316− tryCompare(passwordInput, "placeholderText", "Password")
317− tryCompare(passwordInput, "enabled", true)
318− compare(unlockSpy.count, 0)
319− }
320−
321− function test_unicode() {
322− var index = select_user("unicode")
323− var label = findChild(greeter, "username"+index)
324− tryCompare(label, "text", "가나다라마")
325− }
326−
327− function test_long_name() {
328− var index = select_user("long-name")
329− var label = findChild(greeter, "username"+index)
330− tryCompare(label, "truncated", true)
331− }
332−
333− function test_info_prompt() {
334− select_user("info-prompt")
335− var label = findChild(greeter, "infoLabel")
336− tryCompare(label, "text", "Welcome to Unity Greeter")
337− tryCompare(label, "opacity", 1)
338− tryCompare(label, "clip", true)
339− tryCompareFunction(function() {return label.contentWidth > label.width;}, false) // c.f. wide-info-prompt
340− var passwordInput = findChild(greeter, "passwordInput")
341− mouseClick(passwordInput, 1, 1)
342− keyClick(Qt.Key_Escape)
343− }
344−
345− function test_info_prompt_escape() {
346− select_user("info-prompt")
347− var passwordInput = findChild(greeter, "passwordInput")
348− mouseClick(passwordInput, 1, 1)
349− keyClick(Qt.Key_Escape)
350− var label = findChild(greeter, "infoLabel")
351− tryCompare(label, "text", "Welcome to Unity Greeter")
352− tryCompare(label, "opacity", 1)
353− }
354−
355− function test_wide_info_prompt() {
356− select_user("wide-info-prompt")
357− var label = findChild(greeter, "infoLabel")
358− tryCompare(label, "clip", true)
359− tryCompareFunction(function() {return label.contentWidth > label.width;}, true)
360− }
361−
362− function test_html_info_prompt() {
363− select_user("html-info-prompt")
364− var label = findChild(greeter, "infoLabel")
365− tryCompare(label, "text", "&lt;b&gt;&amp;&lt;/b&gt;")
366− }
367−
368− function test_long_info_prompt() {
369− select_user("long-info-prompt")
370− var label = findChild(greeter, "infoLabel")
371− tryCompare(label, "text", "Welcome to Unity Greeter<br><br>We like to annoy you with super ridiculously long messages.<br>Like this one<br><br>This is the last line of a multiple line message.")
372− tryCompare(label, "textFormat", Text.StyledText) // for parsing above correctly
373− tryCompare(label, "clip", true)
374− tryCompareFunction(function() {return label.contentWidth > label.width;}, true)
375− }
376−
377− function test_multi_info_prompt() {
378− select_user("multi-info-prompt")
379− var label = findChild(greeter, "infoLabel")
380− tryCompare(label, "text", "Welcome to Unity Greeter<br><font color=\"#df382c\">This is an error</font><br>You should have seen three messages")
381− tryCompare(label, "textFormat", Text.StyledText) // for parsing above correctly
382− }
383−
384− function test_bg_color() {
385− var index = select_user("color-background")
386− compare(greeter.model.data(index, LightDM.UserRoles.BackgroundPathRole), "data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>")
387− }
388−
389− function test_bg_none() {
390− var index = select_user("no-background")
391− compare(greeter.model.data(index, LightDM.UserRoles.BackgroundPathRole), "")
392− }
393−
394− function test_tappedSignal_data() {
395− return [
396− {tag: "left", posX: units.gu(2)},
397− {tag: "right", posX: greeter.width - units.gu(2)}
398− ]
399− }
400−
401− function test_tappedSignal(data) {
402− select_user("no-password");
403− tappedSpy.clear();
404− tap(greeter, data.posX, greeter.height - units.gu(1))
405− tryCompare(tappedSpy, "count", 1)
406− }
407−
408− function test_teaseLockedUnlocked_data() {
409− return [
410− {tag: "unlocked", locked: false, narrow: false},
411− {tag: "locked", locked: true, narrow: false},
412− ];
413− }
414−
415− function test_teaseLockedUnlocked(data) {
416− tappedSpy.clear()
417− greeter.locked = data.locked;
418−
419− tap(greeter, greeter.width - units.gu(5), greeter.height - units.gu(1));
420−
421− if (!data.locked || data.narrow) {
422− tappedSpy.wait()
423− tryCompare(tappedSpy, "count", 1);
424− } else {
425− // waiting 100ms to make sure nothing happens
426− wait(100);
427− compare(tappedSpy.count, 0, "Greeter teasing not disabled even though it's locked.");
428− }
429−
430− // Reset value
431− greeter.locked = false;
432− }
433−
434− function test_dbus_set_active_entry() {
435− select_user("no-password") // to guarantee a selected signal
436− selectionSpy.clear()
437− LightDM.Greeter.requestAuthenticationUser("has-password")
438−
439− selectionSpy.wait()
440− tryCompare(selectionSpy, "count", 1)
441−
442− var userlist = findChild(greeter, "userList")
443− compare(greeter.model.data(userlist.currentIndex, LightDM.UserRoles.NameRole), "has-password")
444− }
445−
446− function test_initial_selected_signal() {
447− var greeterObj = greeterComponent.createObject(this)
448− var spy = findChild(greeterObj, "selectedSpy")
449− spy.wait()
450− tryCompare(spy, "count", 1)
451− greeterObj.destroy()
452− }
453−
454− function test_login_list_not_covered_by_keyboard() {
455− var loginList = findChild(greeter, "loginLoader").item;
456− compare(loginList.height, greeter.height);
457−
458− // when the vkb shows up, loginList is moved up to remain fully uncovered
459−
460− keyboardVisibleCheckbox.checked = true;
461−
462− tryCompare(loginList, "height", greeter.height - fakeInputMethod.keyboardRectangle.height);
463− tryCompareFunction( function() {
464− var loginListRect = loginList.mapToItem(greeter, 0, 0, loginList.width, loginList.height);
465− return loginListRect.y + loginListRect.height <= fakeInputMethod.keyboardRectangle.y;
466− }, true);
467−
468− // once the vkb goes away, loginList goes back to its full height
469−
470− keyboardVisibleCheckbox.checked = false;
471−
472− tryCompare(loginList, "height", greeter.height);
473− }
474− }
475−}
tests/qmltests/Greeter/tst_NarrowView.qmlnew file+522
@@ -0,0 +1,522 @@
1+/*
2+ * Copyright 2014 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.0
18+import QtTest 1.0
19+import ".."
20+import "../../../qml/Greeter"
21+import LightDM 0.1 as LightDM
22+import Ubuntu.Components 0.1
23+import Unity.Test 0.1 as UT
24+
25+Item {
26+ id: root
27+ width: units.gu(90)
28+ height: units.gu(80)
29+
30+ Row {
31+ anchors.fill: parent
32+ Loader {
33+ id: loader
34+ width: root.width - controls.width
35+ height: parent.height
36+
37+ property bool itemDestroyed: false
38+ sourceComponent: Component {
39+ NarrowView {
40+ background: Qt.resolvedUrl("../../../qml/graphics/phone_background.jpg")
41+ userModel: LightDM.Users
42+ infographicModel: LightDM.Infographic
43+
44+ launcherOffset: parseFloat(launcherOffsetField.text)
45+ currentIndex: parseInt(currentIndexField.text, 10)
46+ delayMinutes: parseInt(delayMinutesField.text, 10)
47+ backgroundTopMargin: parseFloat(backgroundTopMarginField.text)
48+ locked: lockedCheckBox.checked
49+ alphanumeric: alphanumericCheckBox.checked
50+
51+ Component.onDestruction: {
52+ loader.itemDestroyed = true
53+ }
54+
55+ onSelected: {
56+ currentIndexField.text = index;
57+ }
58+ }
59+ }
60+ }
61+
62+ Rectangle {
63+ id: controls
64+ color: "white"
65+ width: units.gu(40)
66+ height: parent.height
67+
68+ Column {
69+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
70+ spacing: units.gu(1)
71+
72+ Row {
73+ Button {
74+ text: "Show Last Chance"
75+ onClicked: loader.item.showLastChance()
76+ }
77+ }
78+ Row {
79+ Button {
80+ text: "Hide"
81+ onClicked: loader.item.hide()
82+ }
83+ }
84+ Row {
85+ Button {
86+ text: "Reset"
87+ onClicked: loader.item.reset()
88+ }
89+ }
90+ Row {
91+ Button {
92+ text: "Show Message"
93+ onClicked: loader.item.showMessage(messageField.text)
94+ }
95+ TextField {
96+ id: messageField
97+ width: units.gu(10)
98+ text: ""
99+ }
100+ }
101+ Row {
102+ Button {
103+ text: "Show Prompt"
104+ onClicked: loader.item.showPrompt(promptField.text, isSecretCheckBox.checked, isDefaultPromptCheckBox.checked)
105+ }
106+ TextField {
107+ id: promptField
108+ width: units.gu(10)
109+ text: ""
110+ }
111+ CheckBox {
112+ id: isSecretCheckBox
113+ }
114+ Label {
115+ text: "secret"
116+ }
117+ CheckBox {
118+ id: isDefaultPromptCheckBox
119+ }
120+ Label {
121+ text: "default"
122+ }
123+ }
124+ Row {
125+ Button {
126+ text: "Authenticated"
127+ onClicked: {
128+ if (successCheckBox.checked) {
129+ loader.item.notifyAuthenticationSucceeded();
130+ } else {
131+ loader.item.notifyAuthenticationFailed();
132+ }
133+ }
134+ }
135+ CheckBox {
136+ id: successCheckBox
137+ }
138+ Label {
139+ text: "success"
140+ }
141+ }
142+ Row {
143+ Button {
144+ text: "Try To Unlock"
145+ onClicked: loader.item.tryToUnlock(toTheRightCheckBox.checked)
146+ }
147+ CheckBox {
148+ id: toTheRightCheckBox
149+ }
150+ Label {
151+ text: "toTheRight"
152+ }
153+ }
154+ Row {
155+ TextField {
156+ id: launcherOffsetField
157+ width: units.gu(10)
158+ text: "0"
159+ }
160+ Label {
161+ text: "launcherOffset"
162+ }
163+ }
164+ Row {
165+ TextField {
166+ id: currentIndexField
167+ width: units.gu(10)
168+ text: "0"
169+ }
170+ Label {
171+ text: "currentIndex"
172+ }
173+ }
174+ Row {
175+ TextField {
176+ id: delayMinutesField
177+ width: units.gu(10)
178+ text: "0"
179+ }
180+ Label {
181+ text: "delayMinutes"
182+ }
183+ }
184+ Row {
185+ TextField {
186+ id: backgroundTopMarginField
187+ width: units.gu(10)
188+ text: "0"
189+ }
190+ Label {
191+ text: "backgroundTopMargin"
192+ }
193+ }
194+ Row {
195+ CheckBox {
196+ id: lockedCheckBox
197+ }
198+ Label {
199+ text: "locked"
200+ }
201+ }
202+ Row {
203+ CheckBox {
204+ id: alphanumericCheckBox
205+ }
206+ Label {
207+ text: "alphanumeric"
208+ }
209+ }
210+ Row {
211+ Label {
212+ text: "selected: " + selectedSpy.count
213+ }
214+ }
215+ Row {
216+ Label {
217+ text: "responded: " + respondedSpy.count
218+ }
219+ }
220+ Row {
221+ Label {
222+ text: "teased: " + teaseSpy.count
223+ }
224+ }
225+ Row {
226+ Label {
227+ text: "emergency: " + emergencySpy.count
228+ }
229+ }
230+ Row {
231+ Button {
232+ text: "Reload View"
233+ onClicked: {
234+ loader.active = false;
235+ loader.active = true;
236+ }
237+ }
238+ }
239+ }
240+ }
241+ }
242+
243+ Binding {
244+ target: LightDM.Infographic
245+ property: "username"
246+ value: "single"
247+ }
248+
249+ SignalSpy {
250+ id: selectedSpy
251+ target: loader.item
252+ signalName: "selected"
253+ }
254+
255+ SignalSpy {
256+ id: respondedSpy
257+ target: loader.item
258+ signalName: "responded"
259+ }
260+
261+ SignalSpy {
262+ id: teaseSpy
263+ target: loader.item
264+ signalName: "tease"
265+ }
266+
267+ SignalSpy {
268+ id: emergencySpy
269+ target: loader.item
270+ signalName: "emergencyCall"
271+ }
272+
273+ SignalSpy {
274+ id: infographicDataChangedSpy
275+ target: LightDM.Infographic
276+ signalName: "dataChanged"
277+ }
278+
279+ UT.UnityTestCase {
280+ name: "NarrowView"
281+ when: windowShown
282+
283+ property Item view: loader.status === Loader.Ready ? loader.item : null
284+
285+ function init() {
286+ view.currentIndex = 0; // break binding with text field
287+
288+ selectedSpy.clear();
289+ respondedSpy.clear();
290+ teaseSpy.clear();
291+ emergencySpy.clear();
292+ infographicDataChangedSpy.clear();
293+ }
294+
295+ function cleanup() {
296+ loader.itemDestroyed = false;
297+ loader.active = false;
298+ tryCompare(loader, "status", Loader.Null);
299+ tryCompare(loader, "item", null);
300+ tryCompare(loader, "itemDestroyed", true);
301+ loader.active = true;
302+ tryCompare(loader, "status", Loader.Ready);
303+ removeTimeConstraintsFromDirectionalDragAreas(loader.item);
304+ }
305+
306+ function swipeAwayCover(toTheRight) {
307+ if (toTheRight === undefined) {
308+ toTheRight = false;
309+ }
310+
311+ tryCompare(view, "fullyShown", true);
312+ var touchY = view.height / 2;
313+ if (toTheRight) {
314+ touchFlick(view, 0, touchY, view.width, touchY);
315+ } else {
316+ touchFlick(view, view.width, touchY, 0, touchY);
317+ }
318+ var coverPage = findChild(view, "coverPage");
319+ tryCompare(coverPage, "showProgress", 0);
320+ waitForRendering(view);
321+ }
322+
323+ function enterPin(pin) {
324+ for (var i = 0; i < pin.length; ++i) {
325+ var character = pin.charAt(i);
326+ var button = findChild(view, "pinPadButton" + character);
327+ tap(button);
328+ }
329+ }
330+
331+ function test_tease_data() {
332+ return [
333+ {tag: "left", x: 0, offset: 0, count: 1},
334+ {tag: "leftWithOffsetPass", x: 10, offset: 10, count: 1},
335+ {tag: "leftWithOffsetFail", x: 9, offset: 10, count: 0},
336+ {tag: "right", x: view.width, offset: 0, count: 1},
337+ ]
338+ }
339+ function test_tease(data) {
340+ view.dragHandleLeftMargin = data.offset;
341+ tap(view, data.x, 0);
342+ compare(teaseSpy.count, data.count);
343+ }
344+
345+ function test_respondedWithPin() {
346+ view.locked = true;
347+ swipeAwayCover();
348+ enterPin("1234");
349+ compare(respondedSpy.count, 1);
350+ compare(respondedSpy.signalArguments[0][0], "1234");
351+ }
352+
353+ function test_respondedWithPassphrase() {
354+ view.locked = true;
355+ view.alphanumeric = true;
356+ swipeAwayCover();
357+ typeString("test");
358+ keyClick(Qt.Key_Enter);
359+ compare(respondedSpy.count, 1);
360+ compare(respondedSpy.signalArguments[0][0], "test");
361+ }
362+
363+ function test_respondedWithSwipe_data() {
364+ return [
365+ {tag: "left", toTheRight: false, hiddenX: -view.width},
366+ {tag: "right", toTheRight: true, hiddenX: view.width},
367+ ];
368+ }
369+ function test_respondedWithSwipe(data) {
370+ swipeAwayCover(data.toTheRight);
371+ var coverPage = findChild(view, "coverPage");
372+ compare(coverPage.x, data.hiddenX);
373+ compare(respondedSpy.count, 1);
374+ compare(respondedSpy.signalArguments[0][0], "");
375+ }
376+
377+ function test_emergencyCall() {
378+ view.locked = true;
379+ swipeAwayCover();
380+ var emergencyCallLabel = findChild(view, "emergencyCallLabel");
381+ tap(emergencyCallLabel);
382+ compare(emergencySpy.count, 1);
383+ }
384+
385+ function test_fullyShown() {
386+ tryCompare(view, "fullyShown", true);
387+ swipeAwayCover();
388+ tryCompare(view, "fullyShown", false);
389+ view.locked = true;
390+ tryCompare(view, "fullyShown", true);
391+ view.locked = false;
392+ tryCompare(view, "fullyShown", false);
393+ }
394+
395+ function test_required() {
396+ tryCompare(view, "required", true);
397+ swipeAwayCover();
398+ tryCompare(view, "required", false);
399+ view.locked = true;
400+ tryCompare(view, "required", true);
401+ view.locked = false;
402+ tryCompare(view, "required", false);
403+ }
404+
405+ function test_tryToUnlock() {
406+ var coverPage = findChild(view, "coverPage");
407+ tryCompare(coverPage, "showProgress", 1);
408+ compare(view.tryToUnlock(false), true);
409+ tryCompare(coverPage, "showProgress", 0);
410+ compare(view.tryToUnlock(false), false);
411+ }
412+
413+ /*
414+ Regression test for https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1388359
415+ "User metrics can no longer be changed by double tap"
416+ */
417+ function test_doubleTapSwitchesToNextInfographic() {
418+ var infographicPrivate = findInvisibleChild(view, "infographicPrivate");
419+ verify(infographicPrivate);
420+
421+ // wait for the UI to settle down before double tapping it
422+ tryCompare(infographicPrivate, "animating", false);
423+
424+ var dataCircle = findChild(view, "dataCircle");
425+ verify(dataCircle);
426+
427+ tap(dataCircle);
428+ wait(1);
429+ tap(dataCircle);
430+
431+ tryCompare(infographicDataChangedSpy, "count", 1);
432+ }
433+
434+ function test_movesBackIntoPlaceWhenNotDraggedFarEnough() {
435+ var coverPage = findChild(view, "coverPage");
436+
437+ var dragEvaluator = findInvisibleChild(coverPage, "edgeDragEvaluator");
438+ verify(dragEvaluator);
439+
440+ // Make it easier to get a rejection/rollback. Otherwise would have to inject
441+ // a fake timer into dragEvaluator.
442+ // Afterall, we are testing if the CoverPage indeed moves back on a
443+ // rollback decision, not the drag evaluation itself.
444+ dragEvaluator.minDragDistance = dragEvaluator.maxDragDistance / 2;
445+
446+ // it starts as fully shown
447+ compare(coverPage.x, 0);
448+
449+ // then we drag it a bit
450+ var startX = coverPage.width - 1;
451+ var touchY = coverPage.height / 2;
452+ var dragXDelta = -(dragEvaluator.minDragDistance * 0.3);
453+ touchFlick(coverPage,
454+ startX , touchY, // start pos
455+ startX + dragXDelta, touchY, // end pos
456+ true /* beginTouch */, false /* endTouch */);
457+
458+ // which should make it move a bit
459+ tryCompareFunction(function() {return coverPage.x < 0;}, true);
460+
461+ // then we release it
462+ touchRelease(coverPage, startX + dragXDelta, touchY);
463+
464+ // which should make it move back into its original position as it didn't move
465+ // far enough to have it hidden
466+ tryCompare(coverPage, "x", 0);
467+ }
468+
469+ function test_dragToHide_data() {
470+ return [
471+ {tag: "left", startX: view.width * 0.95, endX: view.width * 0.1, hiddenX: -view.width},
472+ {tag: "right", startX: view.width * 0.1, endX: view.width * 0.95, hiddenX: view.width},
473+ ];
474+ }
475+ function test_dragToHide(data) {
476+ var coverPage = findChild(view, "coverPage");
477+ compare(coverPage.x, 0);
478+ compare(coverPage.visible, true);
479+ compare(coverPage.shown, true);
480+ compare(coverPage.showProgress, 1);
481+ compare(view.fullyShown, true);
482+
483+ touchFlick(view,
484+ data.startX, view.height / 2, // start pos
485+ data.endX, view.height / 2); // end pos
486+
487+ tryCompare(coverPage, "x", data.hiddenX);
488+ tryCompare(coverPage, "visible", false);
489+ tryCompare(coverPage, "shown", false);
490+ tryCompare(coverPage, "showProgress", 0);
491+ compare(view.fullyShown, false);
492+ }
493+
494+ function test_hiddenViewRemainsHiddenAfterResize_data() {
495+ return [
496+ {tag: "left", startX: view.width * 0.95, endX: view.width * 0.1},
497+ {tag: "right", startX: view.width * 0.1, endX: view.width * 0.95},
498+ ];
499+ }
500+ function test_hiddenViewRemainsHiddenAfterResize(data) {
501+ touchFlick(view,
502+ data.startX, view.height / 2, // start pos
503+ data.endX, view.height / 2); // end pos
504+
505+ var coverPage = findChild(view, "coverPage");
506+ tryCompare(coverPage, "x", data.tag == "left" ? -view.width : view.width);
507+ tryCompare(coverPage, "visible", false);
508+ tryCompare(coverPage, "shown", false);
509+ tryCompare(coverPage, "showProgress", 0);
510+
511+ // flip dimensions to simulate an orientation change
512+ view.width = loader.height;
513+ view.height = loader.width;
514+
515+ // All properties should remain consistent
516+ tryCompare(coverPage, "x", data.tag == "left" ? -view.width : view.width);
517+ tryCompare(coverPage, "visible", false);
518+ tryCompare(coverPage, "shown", false);
519+ tryCompare(coverPage, "showProgress", 0);
520+ }
521+ }
522+}
tests/qmltests/Greeter/tst_SingleGreeter.qmldeleted−244
@@ -1,244 +0,0 @@
1−/*
2− * Copyright 2013 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.0
18−import QtTest 1.0
19−import ".."
20−import "../../../qml/Greeter"
21−import AccountsService 0.1
22−import LightDM 0.1 as LightDM
23−import Ubuntu.Components 0.1
24−import Unity.Test 0.1 as UT
25−
26−Rectangle {
27− color: "darkblue"
28− width: units.gu(60)
29− height: units.gu(80)
30−
31− Button {
32− anchors.centerIn: parent
33− text: "Show Greeter"
34− onClicked: {
35− if (greeterLoader.item)
36− greeterLoader.item.show();
37− }
38− }
39−
40− Loader {
41− id: greeterLoader
42− anchors.fill: parent
43−
44− property bool itemDestroyed: false
45−
46− sourceComponent: Component {
47− Greeter {
48− width: greeterLoader.width
49− height: greeterLoader.height
50−
51− Component.onDestruction: {
52− greeterLoader.itemDestroyed = true;
53− }
54− SignalSpy {
55− objectName: "selectedSpy"
56− target: parent
57− signalName: "selected"
58− }
59− }
60− }
61− }
62−
63− SignalSpy {
64− id: unlockSpy
65− target: greeterLoader.item
66− signalName: "unlocked"
67− }
68−
69− SignalSpy {
70− id: tappedSpy
71− target: greeterLoader.item
72− signalName: "tapped"
73− }
74−
75− SignalSpy {
76− id: infographicDataChangedSpy
77− target: LightDM.Infographic
78− signalName: "dataChanged"
79− }
80−
81− UT.UnityTestCase {
82− name: "SingleGreeter"
83− when: windowShown
84−
85− property Greeter greeter: greeterLoader.item
86−
87− function cleanup() {
88− AccountsService.statsWelcomeScreen = true
89−
90− // force a reload so that we get a fresh Greeter for the next test
91− greeterLoader.itemDestroyed = false;
92− greeterLoader.active = false;
93− tryCompare(greeterLoader, "itemDestroyed", true);
94−
95− unlockSpy.clear();
96− tappedSpy.clear();
97−
98− greeterLoader.active = true;
99− tryCompare(greeterLoader, "status", Loader.Ready);
100− removeTimeConstraintsFromDirectionalDragAreas(greeterLoader.item);
101− }
102−
103− function test_properties() {
104− compare(greeter.multiUser, false)
105− compare(greeter.narrowMode, true)
106− }
107−
108− function test_teasingArea_data() {
109− return [
110− {tag: "left", posX: units.gu(2), leftPressed: true, rightPressed: false},
111− {tag: "right", posX: greeter.width - units.gu(2), leftPressed: false, rightPressed: true}
112− ]
113− }
114−
115− function test_teasingArea(data) {
116− tappedSpy.clear()
117− tap(greeter, data.posX, greeter.height - units.gu(1))
118− tappedSpy.wait()
119− tryCompare(tappedSpy, "count", 1)
120− }
121−
122− function test_statsWelcomeScreen() {
123− // Test logic in greeter that turns statsWelcomeScreen setting into infographic changes
124− compare(AccountsService.statsWelcomeScreen, true)
125− tryCompare(LightDM.Infographic, "username", "single")
126− AccountsService.statsWelcomeScreen = false
127− tryCompare(LightDM.Infographic, "username", "")
128− AccountsService.statsWelcomeScreen = true
129− tryCompare(LightDM.Infographic, "username", "single")
130− }
131−
132− function test_initial_selected_signal() {
133− var selectedSpy = findChild(greeter, "selectedSpy");
134− selectedSpy.wait();
135− tryCompare(selectedSpy, "count", 1);
136− }
137−
138− /*
139− Regression test for https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1388359
140− "User metrics can no longer be changed by double tap"
141− */
142− function test_doubleTapSwitchesToNextInfographic() {
143− infographicDataChangedSpy.clear();
144−
145− var infographicPrivate = findInvisibleChild(greeter, "infographicPrivate");
146− verify(infographicPrivate);
147−
148− // wait for the UI to settle down before double tapping it
149− tryCompare(infographicPrivate, "animating", false);
150−
151− var dataCircle = findChild(greeter, "dataCircle");
152− verify(dataCircle);
153−
154− tap(dataCircle);
155− wait(1);
156− tap(dataCircle);
157−
158− tryCompare(infographicDataChangedSpy, "count", 1);
159− }
160−
161− function test_movesBackIntoPlaceWhenNotDraggedFarEnough() {
162−
163− var dragEvaluator = findInvisibleChild(greeter, "edgeDragEvaluator");
164− verify(dragEvaluator);
165−
166− // Make it easier to get a rejection/rollback. Otherwise would have to inject
167− // a fake timer into dragEvaluator.
168− // Afterall, we are testing if the Greeter indeed moves back on a
169− // rollback decision, not the drag evaluation itself.
170− dragEvaluator.minDragDistance = dragEvaluator.maxDragDistance / 2;
171−
172− // it starts as fully shown
173− compare(greeter.x, 0);
174−
175− // then we drag it a bit
176− var startX = greeter.width - 1;
177− var touchY = greeter.height / 2;
178− var dragXDelta = -(dragEvaluator.minDragDistance * 0.3);
179− touchFlick(greeter,
180− startX , touchY, // start pos
181− startX + dragXDelta, touchY, // end pos
182− true /* beginTouch */, false /* endTouch */);
183−
184− // which should make it move a bit
185− tryCompareFunction(function(){return greeter.x < 0;}, true);
186−
187− // then we release it
188− touchRelease(greeter, startX + dragXDelta, touchY);
189−
190− // which should make it move back into its original position as it didn't move
191− // far enough to have it hidden
192− tryCompare(greeter, "x", 0);
193− }
194−
195− function test_dragToHide_data() {
196− return [
197− {tag: "left", startX: greeter.width * 0.95, endX: greeter.width * 0.1, hiddenX: -greeter.width},
198− {tag: "right", startX: greeter.width * 0.1, endX: greeter.width * 0.95, hiddenX: greeter.width},
199− ];
200− }
201− function test_dragToHide(data) {
202− compare(greeter.x, 0);
203− compare(greeter.visible, true);
204− compare(greeter.shown, true);
205− compare(greeter.showProgress, 1);
206−
207− touchFlick(greeter,
208− data.startX, greeter.height / 2, // start pos
209− data.endX, greeter.height / 2); // end pos
210−
211− tryCompare(greeter, "x", data.hiddenX);
212− tryCompare(greeter, "visible", false);
213− tryCompare(greeter, "shown", false);
214− tryCompare(greeter, "showProgress", 0);
215− }
216−
217− function test_hiddenGreeterRemainsHiddenAfterResize_data() {
218− return [
219− {tag: "left", startX: greeter.width * 0.95, endX: greeter.width * 0.1},
220− {tag: "right", startX: greeter.width * 0.1, endX: greeter.width * 0.95},
221− ];
222− }
223− function test_hiddenGreeterRemainsHiddenAfterResize(data) {
224− touchFlick(greeter,
225− data.startX, greeter.height / 2, // start pos
226− data.endX, greeter.height / 2); // end pos
227−
228− tryCompare(greeter, "x", data.tag == "left" ? -greeter.width : greeter.width);
229− tryCompare(greeter, "visible", false);
230− tryCompare(greeter, "shown", false);
231− tryCompare(greeter, "showProgress", 0);
232−
233− // flip dimensions to simulate an orientation change
234− greeter.width = greeterLoader.height;
235− greeter.height = greeterLoader.width;
236−
237− // All properties should remain consistent
238− tryCompare(greeter, "x", data.tag == "left" ? -greeter.width : greeter.width);
239− tryCompare(greeter, "visible", false);
240− tryCompare(greeter, "shown", false);
241− tryCompare(greeter, "showProgress", 0);
242− }
243− }
244−}
tests/qmltests/Greeter/tst_WideView.qmlnew file+519
@@ -0,0 +1,519 @@
1+/*
2+ * Copyright 2014 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.0
18+import QtTest 1.0
19+import ".."
20+import "../../../qml/Greeter"
21+import LightDM 0.1 as LightDM
22+import Ubuntu.Components 0.1
23+import Unity.Test 0.1 as UT
24+
25+Item {
26+ id: root
27+ width: units.gu(120)
28+ height: units.gu(80)
29+
30+ Binding {
31+ target: LightDM.Users
32+ property: "mockMode"
33+ value: "full"
34+ }
35+
36+ Row {
37+ anchors.fill: parent
38+ Loader {
39+ id: loader
40+ width: root.width - controls.width
41+ height: parent.height
42+
43+ property bool itemDestroyed: false
44+ sourceComponent: Component {
45+ WideView {
46+ id: view
47+
48+ background: Qt.resolvedUrl("../../../qml/graphics/tablet_background.jpg")
49+ userModel: LightDM.Users
50+ infographicModel: LightDM.Infographic
51+
52+ launcherOffset: parseFloat(launcherOffsetField.text)
53+ currentIndex: parseInt(currentIndexField.text, 10)
54+ delayMinutes: parseInt(delayMinutesField.text, 10)
55+ backgroundTopMargin: parseFloat(backgroundTopMarginField.text)
56+ locked: lockedCheckBox.checked
57+ inputMethod: fakeInputMethod
58+
59+ Component.onDestruction: {
60+ loader.itemDestroyed = true
61+ }
62+
63+ onSelected: {
64+ currentIndexField.text = index;
65+ }
66+
67+ QtObject {
68+ id: fakeInputMethod
69+ property bool visible: fakeKeyboard.visible
70+ property var keyboardRectangle: QtObject {
71+ property real x: fakeKeyboard.x
72+ property real y: fakeKeyboard.y
73+ property real width: fakeKeyboard.width
74+ property real height: fakeKeyboard.height
75+ }
76+ }
77+
78+ Rectangle {
79+ id: fakeKeyboard
80+ color: "green"
81+ opacity: 0.7
82+ anchors.bottom: view.bottom
83+ width: view.width
84+ height: view.height * 0.6
85+ visible: keyboardVisibleCheckBox.checked
86+ Text {
87+ text: "Keyboard Rectangle"
88+ color: "yellow"
89+ font.bold: true
90+ fontSizeMode: Text.Fit
91+ minimumPixelSize: 10; font.pixelSize: 200
92+ verticalAlignment: Text.AlignVCenter
93+ x: (parent.width - width) / 2
94+ y: (parent.height - height) / 2
95+ width: parent.width
96+ height: parent.height
97+ }
98+ }
99+
100+ }
101+ }
102+ }
103+
104+ Rectangle {
105+ id: controls
106+ color: "white"
107+ width: units.gu(40)
108+ height: parent.height
109+
110+ Column {
111+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
112+ spacing: units.gu(1)
113+
114+ Row {
115+ Button {
116+ text: "Show Last Chance"
117+ onClicked: loader.item.showLastChance()
118+ }
119+ }
120+ Row {
121+ Button {
122+ text: "Hide"
123+ onClicked: loader.item.hide()
124+ }
125+ }
126+ Row {
127+ Button {
128+ text: "Reset"
129+ onClicked: loader.item.reset()
130+ }
131+ }
132+ Row {
133+ Button {
134+ text: "Show Message"
135+ onClicked: loader.item.showMessage(messageField.text)
136+ }
137+ TextField {
138+ id: messageField
139+ width: units.gu(10)
140+ text: ""
141+ }
142+ }
143+ Row {
144+ Button {
145+ text: "Show Prompt"
146+ onClicked: loader.item.showPrompt(promptField.text, isSecretCheckBox.checked, isDefaultPromptCheckBox.checked)
147+ }
148+ TextField {
149+ id: promptField
150+ width: units.gu(10)
151+ text: ""
152+ }
153+ CheckBox {
154+ id: isSecretCheckBox
155+ }
156+ Label {
157+ text: "secret"
158+ }
159+ CheckBox {
160+ id: isDefaultPromptCheckBox
161+ }
162+ Label {
163+ text: "default"
164+ }
165+ }
166+ Row {
167+ Button {
168+ text: "Authenticated"
169+ onClicked: {
170+ if (successCheckBox.checked) {
171+ loader.item.notifyAuthenticationSucceeded();
172+ } else {
173+ loader.item.notifyAuthenticationFailed();
174+ }
175+ }
176+ }
177+ CheckBox {
178+ id: successCheckBox
179+ }
180+ Label {
181+ text: "success"
182+ }
183+ }
184+ Row {
185+ Button {
186+ text: "Try To Unlock"
187+ onClicked: loader.item.tryToUnlock(toTheRightCheckBox.checked)
188+ }
189+ CheckBox {
190+ id: toTheRightCheckBox
191+ }
192+ Label {
193+ text: "toTheRight"
194+ }
195+ }
196+ Row {
197+ TextField {
198+ id: launcherOffsetField
199+ width: units.gu(10)
200+ text: "0"
201+ }
202+ Label {
203+ text: "launcherOffset"
204+ }
205+ }
206+ Row {
207+ TextField {
208+ id: currentIndexField
209+ width: units.gu(10)
210+ text: "0"
211+ }
212+ Label {
213+ text: "currentIndex"
214+ }
215+ }
216+ Row {
217+ TextField {
218+ id: delayMinutesField
219+ width: units.gu(10)
220+ text: "0"
221+ }
222+ Label {
223+ text: "delayMinutes"
224+ }
225+ }
226+ Row {
227+ TextField {
228+ id: backgroundTopMarginField
229+ width: units.gu(10)
230+ text: "0"
231+ }
232+ Label {
233+ text: "backgroundTopMargin"
234+ }
235+ }
236+ Row {
237+ CheckBox {
238+ id: lockedCheckBox
239+ }
240+ Label {
241+ text: "locked"
242+ }
243+ }
244+ Row {
245+ Label {
246+ text: "selected: " + selectedSpy.count
247+ }
248+ }
249+ Row {
250+ Label {
251+ text: "responded: " + respondedSpy.count
252+ }
253+ }
254+ Row {
255+ Label {
256+ text: "teased: " + teaseSpy.count
257+ }
258+ }
259+ Row {
260+ Label {
261+ text: "emergency: " + emergencySpy.count
262+ }
263+ }
264+ Row {
265+ Button {
266+ text: "Reload View"
267+ onClicked: {
268+ loader.active = false;
269+ loader.active = true;
270+ }
271+ }
272+ }
273+ Row {
274+ CheckBox {
275+ id: keyboardVisibleCheckBox
276+ }
277+ Label {
278+ text: "Keyboard Visible"
279+ }
280+ }
281+ }
282+ }
283+ }
284+
285+ SignalSpy {
286+ id: selectedSpy
287+ target: loader.item
288+ signalName: "selected"
289+ }
290+
291+ SignalSpy {
292+ id: respondedSpy
293+ target: loader.item
294+ signalName: "responded"
295+ }
296+
297+ SignalSpy {
298+ id: teaseSpy
299+ target: loader.item
300+ signalName: "tease"
301+ }
302+
303+ SignalSpy {
304+ id: emergencySpy
305+ target: loader.item
306+ signalName: "emergencyCall"
307+ }
308+
309+ UT.UnityTestCase {
310+ name: "WideView"
311+ when: windowShown
312+
313+ property Item view: loader.status === Loader.Ready ? loader.item : null
314+
315+ function init() {
316+ view.currentIndex = 0; // break binding with text field
317+ selectedSpy.clear();
318+ respondedSpy.clear();
319+ teaseSpy.clear();
320+ emergencySpy.clear();
321+ }
322+
323+ function cleanup() {
324+ keyboardVisibleCheckBox.checked = false;
325+
326+ loader.itemDestroyed = false;
327+ loader.active = false;
328+ tryCompare(loader, "status", Loader.Null);
329+ tryCompare(loader, "item", null);
330+ tryCompare(loader, "itemDestroyed", true);
331+ loader.active = true;
332+ tryCompare(loader, "status", Loader.Ready);
333+ removeTimeConstraintsFromDirectionalDragAreas(loader.item);
334+ }
335+
336+ function getIndexOf(name) {
337+ for (var i = 0; i < LightDM.Users.count; i++) {
338+ if (name === LightDM.Users.data(i, LightDM.UserRoles.NameRole)) {
339+ return i;
340+ }
341+ }
342+ fail("Didn't find name")
343+ return -1;
344+ }
345+
346+ function selectUser(name) {
347+ var i = getIndexOf(name);
348+ view.currentIndex = i;
349+ return i;
350+ }
351+
352+ function swipeAwayCover() {
353+ tryCompare(view, "fullyShown", true);
354+ var touchY = view.height / 2;
355+ touchFlick(view, view.width, touchY, 0, touchY);
356+ var coverPage = findChild(view, "coverPage");
357+ tryCompare(coverPage, "showProgress", 0);
358+ waitForRendering(view);
359+ }
360+
361+ function test_tease_data() {
362+ return [
363+ {tag: "locked", x: 0, offset: 0, count: 0, locked: true},
364+ {tag: "left", x: 0, offset: 0, count: 1, locked: false},
365+ {tag: "leftWithOffsetPass", x: 10, offset: 10, count: 1, locked: false},
366+ {tag: "leftWithOffsetFail", x: 9, offset: 10, count: 0, locked: false},
367+ {tag: "right", x: view.width, offset: 0, count: 1, locked: false},
368+ ]
369+ }
370+ function test_tease(data) {
371+ view.locked = data.locked;
372+ view.dragHandleLeftMargin = data.offset;
373+ tap(view, data.x, 0);
374+ compare(teaseSpy.count, data.count);
375+ }
376+
377+ function test_selected() {
378+ var delegate = findChild(view, "username2");
379+ tap(delegate);
380+ compare(selectedSpy.count, 1);
381+ compare(selectedSpy.signalArguments[0][0], 2);
382+ compare(view.currentIndex, 0); // confirm we didn't change
383+ }
384+
385+ function test_respondedWithPassword() {
386+ view.locked = true;
387+ view.showPrompt("Prompt", true, true);
388+ var passwordInput = findChild(view, "passwordInput");
389+ compare(passwordInput.placeholderText, "Prompt");
390+ compare(passwordInput.echoMode, TextInput.Password);
391+ tap(passwordInput);
392+ typeString("password");
393+ keyClick(Qt.Key_Enter);
394+ compare(respondedSpy.count, 1);
395+ compare(respondedSpy.signalArguments[0][0], "password");
396+ }
397+
398+ function test_respondedWithNonSecret() {
399+ view.locked = true;
400+ view.showPrompt("otp", false, false);
401+ var passwordInput = findChild(view, "passwordInput");
402+ compare(passwordInput.placeholderText, "otp");
403+ compare(passwordInput.echoMode, TextInput.Normal);
404+ tap(passwordInput);
405+ typeString("foo");
406+ keyClick(Qt.Key_Enter);
407+ compare(respondedSpy.count, 1);
408+ compare(respondedSpy.signalArguments[0][0], "foo");
409+ }
410+
411+ function test_respondedWithSwipe() {
412+ swipeAwayCover();
413+ compare(respondedSpy.count, 1);
414+ compare(respondedSpy.signalArguments[0][0], "");
415+ }
416+
417+ function test_fullyShown() {
418+ tryCompare(view, "fullyShown", true);
419+ swipeAwayCover();
420+ tryCompare(view, "fullyShown", false);
421+ }
422+
423+ function test_required() {
424+ tryCompare(view, "required", true);
425+ swipeAwayCover();
426+ tryCompare(view, "required", false);
427+ }
428+
429+ function test_showMessage() {
430+ view.showMessage("Welcome to Unity Greeter");
431+ view.showMessage("<font color=\"#df382c\">This is an error</font>");
432+ view.showMessage("You should have seen three messages and this is a really long message too. wow so long much length");
433+ var infoLabel = findChild(view, "infoLabel");
434+ compare(infoLabel.text, "Welcome to Unity Greeter<br><font color=\"#df382c\">This is an error</font><br>You should have seen three messages and this is a really long message too. wow so long much length");
435+ compare(infoLabel.textFormat, Text.StyledText);
436+ compare(infoLabel.clip, true);
437+ verify(infoLabel.contentWidth > infoLabel.width);
438+ verify(infoLabel.opacity < 1);
439+ tryCompare(infoLabel, "opacity", 1);
440+ }
441+
442+ // Escape is used to reset the authentication, especially if PAM is unresponsive
443+ function test_escape() {
444+ view.currentIndex = 1;
445+ view.locked = true;
446+ view.showPrompt("Prompt", true, true);
447+ var passwordInput = findChild(view, "passwordInput");
448+ tap(passwordInput);
449+ compare(passwordInput.focus, true);
450+ compare(passwordInput.enabled, true);
451+
452+ typeString("password");
453+ keyClick(Qt.Key_Enter);
454+ compare(passwordInput.focus, false);
455+ compare(passwordInput.enabled, false);
456+
457+ compare(selectedSpy.count, 0);
458+ keyClick(Qt.Key_Escape);
459+ compare(selectedSpy.count, 1);
460+ compare(selectedSpy.signalArguments[0][0], 1);
461+
462+ view.reset();
463+ compare(passwordInput.focus, false);
464+ compare(passwordInput.enabled, true);
465+ }
466+
467+ function test_unicode() {
468+ var index = selectUser("unicode");
469+ var label = findChild(view, "username" + index);
470+ tryCompare(label, "text", "가나다라마");
471+ }
472+
473+ function test_longName() {
474+ var index = selectUser("long-name");
475+ var label = findChild(view, "username" + index);
476+ tryCompare(label, "truncated", true);
477+ }
478+
479+ function test_promptless() {
480+ var passwordInput = findChild(view, "passwordInput");
481+
482+ view.locked = true;
483+ compare(passwordInput.placeholderText, "Retry");
484+ tap(passwordInput);
485+ compare(respondedSpy.count, 0);
486+ compare(selectedSpy.count, 1);
487+ compare(selectedSpy.signalArguments[0][0], 0);
488+ selectedSpy.clear();
489+
490+ view.locked = false;
491+ compare(passwordInput.placeholderText, "Tap to unlock");
492+ tap(passwordInput);
493+ compare(selectedSpy.count, 0);
494+ compare(respondedSpy.count, 1);
495+ compare(respondedSpy.signalArguments[0][0], "");
496+ }
497+
498+ function test_loginListNotCoveredByKeyboard() {
499+ var loginList = findChild(view, "loginList");
500+ compare(loginList.height, view.height);
501+
502+ // when the vkb shows up, loginList is moved up to remain fully uncovered
503+
504+ keyboardVisibleCheckBox.checked = true;
505+
506+ tryCompare(loginList, "height", view.height - view.inputMethod.keyboardRectangle.height);
507+ tryCompareFunction( function() {
508+ var loginListRect = loginList.mapToItem(view, 0, 0, loginList.width, loginList.height);
509+ return loginListRect.y + loginListRect.height <= view.inputMethod.keyboardRectangle.y;
510+ }, true);
511+
512+ // once the vkb goes away, loginList goes back to its full height
513+
514+ keyboardVisibleCheckBox.checked = false;
515+
516+ tryCompare(loginList, "height", view.height);
517+ }
518+ }
519+}
tests/qmltests/Tutorial/tst_Tutorial.qml+5−4
@@ -112,9 +112,9 @@ Item {
112
112
113 function init() {
113 function init() {
114 tryCompare(shell, "enabled", true); // enabled by greeter when ready
114 tryCompare(shell, "enabled", true); // enabled by greeter when ready
115− swipeAwayGreeter();
116 AccountsService.demoEdges = false;
115 AccountsService.demoEdges = false;
117 AccountsService.demoEdges = true;
116 AccountsService.demoEdges = true;
117+ swipeAwayGreeter();
118 }
118 }
119
119
120 function cleanup() {
120 function cleanup() {
@@ -151,13 +151,14 @@ Item {
151 }
151 }
152
152
153 function swipeAwayGreeter() {
153 function swipeAwayGreeter() {
154− var greeter = findChild(shell, "greeter");
154+ var coverPage = findChild(shell, "coverPage");
155− tryCompare(greeter, "showProgress", 1);
155+ tryCompare(coverPage, "showProgress", 1);
156
156
157 touchFlick(shell, halfWidth, halfHeight, shell.width, halfHeight);
157 touchFlick(shell, halfWidth, halfHeight, shell.width, halfHeight);
158
158
159 // wait until the animation has finished
159 // wait until the animation has finished
160+ var greeter = findChild(shell, "greeter");
160− tryCompare(greeter, "showProgress", 0);
161+ tryCompare(greeter, "required", false);
161 waitForRendering(greeter);
162 waitForRendering(greeter);
162 }
163 }
163
164
tests/qmltests/tst_Shell.qml+65−43
@@ -19,9 +19,11 @@
19
19
20import QtQuick 2.0
20import QtQuick 2.0
21import QtTest 1.0
21import QtTest 1.0
22+import AccountsService 0.1
22import GSettings 1.0
23import GSettings 1.0
23import LightDM 0.1 as LightDM
24import LightDM 0.1 as LightDM
24import Ubuntu.Components 1.1
25import Ubuntu.Components 1.1
26+import Ubuntu.Components.ListItems 1.0 as ListItem
25import Ubuntu.Telephony 0.1 as Telephony
27import Ubuntu.Telephony 0.1 as Telephony
26import Unity.Application 0.1
28import Unity.Application 0.1
27import Unity.Connectivity 0.1
29import Unity.Connectivity 0.1
@@ -80,7 +82,7 @@ Item {
80 }
82 }
81
83
82 Rectangle {
84 Rectangle {
83− color: "white"
85+ color: "darkgrey"
84 width: units.gu(30)
86 width: units.gu(30)
85 height: shellLoader.height
87 height: shellLoader.height
86
88
@@ -98,11 +100,23 @@ Item {
98
100
99 var greeter = testCase.findChild(shellLoader.item, "greeter");
101 var greeter = testCase.findChild(shellLoader.item, "greeter");
100 if (!greeter.shown) {
102 if (!greeter.shown) {
101− greeter.show();
103+ LightDM.Greeter.showGreeter();
102 }
104 }
103 }
105 }
104 }
106 }
105 }
107 }
108+ ListItem.ItemSelector {
109+ anchors { left: parent.left; right: parent.right }
110+ activeFocusOnPress: false
111+ text: "LightDM mock mode"
112+ model: ["single", "single-passphrase", "single-pin"]
113+ onSelectedIndexChanged: {
114+ shellLoader.active = false;
115+ LightDM.Greeter.mockMode = model[selectedIndex];
116+ LightDM.Users.mockMode = model[selectedIndex];
117+ shellLoader.active = true;
118+ }
119+ }
106 }
120 }
107 }
121 }
108 }
122 }
@@ -284,11 +298,13 @@ Item {
284
298
285 function test_leftEdgeDrag_data() {
299 function test_leftEdgeDrag_data() {
286 return [
300 return [
287− {tag: "without launcher", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true},
301+ {tag: "without launcher", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true, greeterShown: false},
288− {tag: "with launcher", revealLauncher: true, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true},
302+ {tag: "with launcher", revealLauncher: true, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true, greeterShown: false},
289− {tag: "small swipe", revealLauncher: false, swipeLength: units.gu(25), appHides: false, focusedApp: "dialer-app", launcherHides: false},
303+ {tag: "small swipe", revealLauncher: false, swipeLength: units.gu(25), appHides: false, focusedApp: "dialer-app", launcherHides: false, greeterShown: false},
290− {tag: "long swipe", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true},
304+ {tag: "long swipe", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true, greeterShown: false},
305+ {tag: "small swipe with greeter", revealLauncher: false, swipeLength: units.gu(25), appHides: false, focusedApp: "dialer-app", launcherHides: false, greeterShown: true},
306+ {tag: "long swipe with greeter", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true, greeterShown: true},
291− {tag: "long swipe", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "unity8-dash", launcherHides: false}
307+ {tag: "swipe over dash", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "unity8-dash", launcherHides: false, greeterShown: false},
292 ];
308 ];
293 }
309 }
294
310
@@ -299,6 +315,12 @@ Item {
299 ApplicationManager.focusApplication(data.focusedApp)
315 ApplicationManager.focusApplication(data.focusedApp)
300 waitUntilApplicationWindowIsFullyVisible();
316 waitUntilApplicationWindowIsFullyVisible();
301
317
318+ var greeter = findChild(shell, "greeter");
319+ if (data.greeterShown) {
320+ LightDM.Greeter.showGreeter();
321+ tryCompare(greeter, "fullyShown", true);
322+ }
323+
302 if (data.revealLauncher) {
324 if (data.revealLauncher) {
303 dragLauncherIntoView();
325 dragLauncherIntoView();
304 }
326 }
@@ -306,8 +328,10 @@ Item {
306 swipeFromLeftEdge(data.swipeLength);
328 swipeFromLeftEdge(data.swipeLength);
307 if (data.appHides) {
329 if (data.appHides) {
308 waitUntilDashIsFocused();
330 waitUntilDashIsFocused();
331+ tryCompare(greeter, "shown", false);
309 } else {
332 } else {
310 waitUntilApplicationWindowIsFullyVisible();
333 waitUntilApplicationWindowIsFullyVisible();
334+ compare(greeter.fullyShown, data.greeterShown);
311 }
335 }
312
336
313 var launcher = findChild(shell, "launcherPanel");
337 var launcher = findChild(shell, "launcherPanel");
@@ -335,20 +359,20 @@ Item {
335 // Suspend while call is active...
359 // Suspend while call is active...
336 callManager.foregroundCall = phoneCall;
360 callManager.foregroundCall = phoneCall;
337 Powerd.status = Powerd.Off;
361 Powerd.status = Powerd.Off;
338− tryCompare(greeter, "showProgress", 0);
362+ tryCompare(greeter, "shown", false);
339
363
340 // Now try again after ending call
364 // Now try again after ending call
341 callManager.foregroundCall = null;
365 callManager.foregroundCall = null;
342 Powerd.status = Powerd.On;
366 Powerd.status = Powerd.On;
343 Powerd.status = Powerd.Off;
367 Powerd.status = Powerd.Off;
344− tryCompare(greeter, "showProgress", 1);
368+ tryCompare(greeter, "fullyShown", true);
345
369
346 tryCompare(ApplicationManager, "suspended", true);
370 tryCompare(ApplicationManager, "suspended", true);
347 compare(mainApp.state, ApplicationInfoInterface.Suspended);
371 compare(mainApp.state, ApplicationInfoInterface.Suspended);
348
372
349 // And wake up
373 // And wake up
350 Powerd.status = Powerd.On;
374 Powerd.status = Powerd.On;
351− tryCompare(greeter, "showProgress", 1);
375+ tryCompare(greeter, "fullyShown", true);
352
376
353 // Swipe away greeter to focus app
377 // Swipe away greeter to focus app
354 swipeAwayGreeter();
378 swipeAwayGreeter();
@@ -359,14 +383,14 @@ Item {
359
383
360 function swipeAwayGreeter() {
384 function swipeAwayGreeter() {
361 var greeter = findChild(shell, "greeter");
385 var greeter = findChild(shell, "greeter");
362− tryCompare(greeter, "showProgress", 1);
386+ tryCompare(greeter, "fullyShown", true);
363
387
364 var touchX = shell.width - (shell.edgeSize / 2);
388 var touchX = shell.width - (shell.edgeSize / 2);
365 var touchY = shell.height / 2;
389 var touchY = shell.height / 2;
366 touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY);
390 touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY);
367
391
368 // wait until the animation has finished
392 // wait until the animation has finished
369− tryCompare(greeter, "showProgress", 0);
393+ tryCompare(greeter, "shown", false);
370 waitForRendering(greeter);
394 waitForRendering(greeter);
371 }
395 }
372
396
@@ -608,12 +632,12 @@ Item {
608
632
609 waitUntilDashIsFocused();
633 waitUntilDashIsFocused();
610
634
611− greeter.show();
635+ LightDM.Greeter.showGreeter();
612− tryCompare(greeter, "showProgress", 1);
636+ tryCompare(greeter, "fullyShown", true);
613
637
614 // The main point of this test
638 // The main point of this test
615 ApplicationManager.requestFocusApplication("dialer-app");
639 ApplicationManager.requestFocusApplication("dialer-app");
616− tryCompare(greeter, "showProgress", 0);
640+ tryCompare(greeter, "shown", false);
617 waitForRendering(greeter);
641 waitForRendering(greeter);
618 }
642 }
619
643
@@ -631,24 +655,22 @@ Item {
631
655
632 function test_showAndHideGreeterDBusCalls() {
656 function test_showAndHideGreeterDBusCalls() {
633 var greeter = findChild(shell, "greeter")
657 var greeter = findChild(shell, "greeter")
634− tryCompare(greeter, "showProgress", 0)
658+ tryCompare(greeter, "shown", false)
635 waitForRendering(greeter);
659 waitForRendering(greeter);
636 LightDM.Greeter.showGreeter()
660 LightDM.Greeter.showGreeter()
637 waitForRendering(greeter)
661 waitForRendering(greeter)
638− tryCompare(greeter, "showProgress", 1)
662+ tryCompare(greeter, "fullyShown", true)
639 LightDM.Greeter.hideGreeter()
663 LightDM.Greeter.hideGreeter()
640− tryCompare(greeter, "showProgress", 0)
664+ tryCompare(greeter, "shown", false)
641 }
665 }
642
666
643 function test_login() {
667 function test_login() {
644− sessionSpy.clear()
645−
646 var greeter = findChild(shell, "greeter")
668 var greeter = findChild(shell, "greeter")
647 waitForRendering(greeter)
669 waitForRendering(greeter)
648− greeter.show()
670+ LightDM.Greeter.showGreeter();
649− tryCompare(greeter, "showProgress", 1)
671+ tryCompare(greeter, "fullyShown", true)
650
672
651− tryCompare(sessionSpy, "count", 0)
673+ sessionSpy.clear();
652 swipeAwayGreeter()
674 swipeAwayGreeter()
653 tryCompare(sessionSpy, "count", 1)
675 tryCompare(sessionSpy, "count", 1)
654 }
676 }
@@ -689,7 +711,8 @@ Item {
689
711
690 function test_unlockedProperties() {
712 function test_unlockedProperties() {
691 // Confirm that various properties have the correct values when unlocked
713 // Confirm that various properties have the correct values when unlocked
714+ var greeter = findChild(shell, "greeter");
692− tryCompare(shell, "locked", false)
715+ tryCompare(greeter, "locked", false);
693
716
694 var launcher = findChild(shell, "launcher")
717 var launcher = findChild(shell, "launcher")
695 tryCompare(launcher, "available", true)
718 tryCompare(launcher, "available", true)
@@ -702,25 +725,6 @@ Item {
702 tryCompare(unlockAllModemsSpy, "count", 1)
725 tryCompare(unlockAllModemsSpy, "count", 1)
703 }
726 }
704
727
705− function test_leftEdgeDragOnGreeter_data() {
706− return [
707− {tag: "short swipe", targetX: shell.width / 3, unlocked: false},
708− {tag: "long swipe", targetX: shell.width / 3 * 2, unlocked: true}
709− ]
710− }
711−
712− function test_leftEdgeDragOnGreeter(data) {
713− var greeter = findChild(shell, "greeter");
714− greeter.show();
715− tryCompare(greeter, "showProgress", 1);
716−
717− var touchStartX = 2;
718− var touchStartY = shell.height / 2;
719− touchFlick(shell, touchStartX, touchStartY, data.targetX, touchStartY);
720−
721− tryCompare(greeter, "showProgress", data.unlocked ? 0 : 1);
722− }
723−
724 function test_tapOnRightEdgeReachesApplicationSurface() {
728 function test_tapOnRightEdgeReachesApplicationSurface() {
725 var topmostSpreadDelegate = findChild(shell, "appDelegate0");
729 var topmostSpreadDelegate = findChild(shell, "appDelegate0");
726 var topmostSurface = findChild(topmostSpreadDelegate, "surfaceContainer").surface;
730 var topmostSurface = findChild(topmostSpreadDelegate, "surfaceContainer").surface;
@@ -830,5 +834,23 @@ Item {
830 var launcherPanel = findChild(shell, "launcherPanel");
834 var launcherPanel = findChild(shell, "launcherPanel");
831 tryCompare(launcherPanel, "x", -launcherPanel.width);
835 tryCompare(launcherPanel, "x", -launcherPanel.width);
832 }
836 }
837+
838+ function test_background_data() {
839+ return [
840+ {tag: "color", accounts: Qt.resolvedUrl("data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>"), gsettings: "", output: Qt.resolvedUrl("data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>")},
841+ {tag: "empty", accounts: "", gsettings: "", output: shell.defaultBackground},
842+ {tag: "as-specified", accounts: Qt.resolvedUrl("../data/unity/backgrounds/blue.png"), gsettings: "", output: Qt.resolvedUrl("../data/unity/backgrounds/blue.png")},
843+ {tag: "gs-specified", accounts: "", gsettings: Qt.resolvedUrl("../data/unity/backgrounds/red.png"), output: Qt.resolvedUrl("../data/unity/backgrounds/red.png")},
844+ {tag: "both-specified", accounts: Qt.resolvedUrl("../data/unity/backgrounds/blue.png"), gsettings: Qt.resolvedUrl("../data/unity/backgrounds/red.png"), output: Qt.resolvedUrl("../data/unity/backgrounds/blue.png")},
845+ {tag: "invalid-as", accounts: Qt.resolvedUrl("../data/unity/backgrounds/nope.png"), gsettings: Qt.resolvedUrl("../data/unity/backgrounds/red.png"), output: Qt.resolvedUrl("../data/unity/backgrounds/red.png")},
846+ {tag: "invalid-both", accounts: Qt.resolvedUrl("../data/unity/backgrounds/nope.png"), gsettings: Qt.resolvedUrl("../data/unity/backgrounds/stillnope.png"), output: shell.defaultBackground},
847+ ]
848+ }
849+ function test_background(data) {
850+ AccountsService.backgroundFile = data.accounts;
851+ var backgroundSettings = findInvisibleChild(shell, "backgroundSettings");
852+ backgroundSettings.pictureUri = data.gsettings;
853+ tryCompare(shell, "background", data.output);
854+ }
833 }
855 }
834}
856}
tests/qmltests/tst_ShellWithPin.qml+97−48
@@ -74,7 +74,6 @@ Item {
74 Component.onDestruction: {
74 Component.onDestruction: {
75 shellLoader.itemDestroyed = true
75 shellLoader.itemDestroyed = true
76 }
76 }
77− maxFailedLogins: maxRetriesTextField.text
78 }
77 }
79 }
78 }
80 }
79 }
@@ -90,15 +89,7 @@ Item {
90 Button {
89 Button {
91 anchors { left: parent.left; right: parent.right }
90 anchors { left: parent.left; right: parent.right }
92 text: "Show Greeter"
91 text: "Show Greeter"
93− onClicked: {
92+ onClicked: LightDM.Greeter.showGreeter()
94− if (shellLoader.status !== Loader.Ready)
95− return
96−
97− var greeter = testCase.findChild(shellLoader.item, "greeter")
98− if (!greeter.shown) {
99− greeter.show()
100− }
101− }
102 }
93 }
103
94
104 Label {
95 Label {
@@ -108,6 +99,10 @@ Item {
108 TextField {
99 TextField {
109 id: maxRetriesTextField
100 id: maxRetriesTextField
110 text: "-1"
101 text: "-1"
102+ onTextChanged: {
103+ var greeter = testCase.findChild(shellLoader.item, "greeter");
104+ greeter.maxFailedLogins = text;
105+ }
111 }
106 }
112 }
107 }
113 }
108 }
@@ -144,11 +139,12 @@ Item {
144
139
145 function init() {
140 function init() {
146 tryCompare(shell, "enabled", true); // will be enabled when greeter is all ready
141 tryCompare(shell, "enabled", true); // will be enabled when greeter is all ready
147− sessionSpy.target = findChild(shell, "greeter")
142+ var greeter = findChild(shell, "greeter");
143+ sessionSpy.target = greeter;
148− swipeAwayGreeter()
144+ swipeAwayGreeter(true);
149 waitForLockscreen()
145 waitForLockscreen()
150− shell.failedLoginsDelayAttempts = -1
146+ greeter.failedLoginsDelayAttempts = -1;
151− maxRetriesTextField.text = "-1"
147+ greeter.maxFailedLogins = -1;
152 }
148 }
153
149
154 function cleanup() {
150 function cleanup() {
@@ -192,17 +188,21 @@ Item {
192 compare(ApplicationManager.count, 1)
188 compare(ApplicationManager.count, 1)
193 }
189 }
194
190
195− function swipeAwayGreeter() {
191+ function swipeAwayGreeter(waitForCoverPage) {
196 var greeter = findChild(shell, "greeter");
192 var greeter = findChild(shell, "greeter");
197 waitForRendering(greeter)
193 waitForRendering(greeter)
194+ var coverPage = findChild(shell, "coverPage");
198− tryCompare(greeter, "showProgress", 1);
195+ tryCompare(coverPage, "showProgress", 1);
199
196
200 var touchX = shell.width - (shell.edgeSize / 2);
197 var touchX = shell.width - (shell.edgeSize / 2);
201 var touchY = shell.height / 2;
198 var touchY = shell.height / 2;
202 touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY);
199 touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY);
203
200
201+ if (waitForCoverPage) {
204− // wait until the animation has finished
202+ // wait until the animation has finished
203+ var coverPage = findChild(shell, "coverPage");
205− tryCompare(greeter, "showProgress", 0);
204+ tryCompare(coverPage, "showProgress", 0);
205+ }
206 }
206 }
207
207
208 function waitForLockscreen() {
208 function waitForLockscreen() {
@@ -213,19 +213,17 @@ Item {
213 }
213 }
214
214
215 function enterPin(pin) {
215 function enterPin(pin) {
216− var inputField = findChild(shell, "pinentryField")
217 for (var i = 0; i < pin.length; ++i) {
216 for (var i = 0; i < pin.length; ++i) {
218 var character = pin.charAt(i)
217 var character = pin.charAt(i)
219 var button = findChild(shell, "pinPadButton" + character)
218 var button = findChild(shell, "pinPadButton" + character)
219+ tryCompare(button, "enabled", true);
220 tap(button)
220 tap(button)
221 }
221 }
222 }
222 }
223
223
224 function confirmLockedApp(app) {
224 function confirmLockedApp(app) {
225 var greeter = findChild(shell, "greeter")
225 var greeter = findChild(shell, "greeter")
226− var lockscreen = findChild(shell, "lockscreen")
227 tryCompare(greeter, "shown", false)
226 tryCompare(greeter, "shown", false)
228− tryCompare(lockscreen, "shown", false)
229 tryCompare(greeter, "hasLockedApp", true)
227 tryCompare(greeter, "hasLockedApp", true)
230 tryCompare(greeter, "lockedApp", app)
228 tryCompare(greeter, "lockedApp", app)
231 tryCompare(LightDM.Greeter, "active", true)
229 tryCompare(LightDM.Greeter, "active", true)
@@ -264,13 +262,16 @@ Item {
264 AccountsService.demoEdges = true
262 AccountsService.demoEdges = true
265 tryCompare(lockscreen, "shown", false)
263 tryCompare(lockscreen, "shown", false)
266
264
265+ var greeter = findChild(shell, "greeter");
267− swipeAwayGreeter()
266+ swipeAwayGreeter(false);
267+ tryCompare(greeter, "shown", false);
268 tryCompare(sessionSpy, "count", 1)
268 tryCompare(sessionSpy, "count", 1)
269
269
270 // Lockscreen is only hidden by the edge demo, so if we turn that
270 // Lockscreen is only hidden by the edge demo, so if we turn that
271 // off and show greeter again, lockscreen should appear
271 // off and show greeter again, lockscreen should appear
272 AccountsService.demoEdges = false
272 AccountsService.demoEdges = false
273 LightDM.Greeter.showGreeter()
273 LightDM.Greeter.showGreeter()
274+ lockscreen = findChild(shell, "lockscreen");
274 tryCompare(lockscreen, "shown", true)
275 tryCompare(lockscreen, "shown", true)
275 }
276 }
276
277
@@ -288,18 +289,16 @@ Item {
288
289
289 function test_emergencyCall() {
290 function test_emergencyCall() {
290 var greeter = findChild(shell, "greeter")
291 var greeter = findChild(shell, "greeter")
291− var lockscreen = findChild(shell, "lockscreen")
292− var emergencyButton = findChild(lockscreen, "emergencyCallLabel")
293 var panel = findChild(shell, "panel")
292 var panel = findChild(shell, "panel")
294 var indicators = findChild(shell, "indicators")
293 var indicators = findChild(shell, "indicators")
295 var launcher = findChild(shell, "launcher")
294 var launcher = findChild(shell, "launcher")
296 var stage = findChild(shell, "stage")
295 var stage = findChild(shell, "stage")
297
296
298− tap(emergencyButton)
297+ tap(findChild(greeter, "emergencyCallLabel"));
299
298
300 tryCompare(greeter, "lockedApp", "dialer-app")
299 tryCompare(greeter, "lockedApp", "dialer-app")
301 tryCompare(greeter, "hasLockedApp", true)
300 tryCompare(greeter, "hasLockedApp", true)
302− tryCompare(lockscreen, "shown", false)
301+ tryCompare(greeter, "shown", false);
303 tryCompare(panel, "fullscreenMode", true)
302 tryCompare(panel, "fullscreenMode", true)
304 tryCompare(indicators, "available", false)
303 tryCompare(indicators, "available", false)
305 tryCompare(launcher, "available", false)
304 tryCompare(launcher, "available", false)
@@ -312,7 +311,7 @@ Item {
312 tryCompare(greeter, "shown", true)
311 tryCompare(greeter, "shown", true)
313 tryCompare(greeter, "lockedApp", "")
312 tryCompare(greeter, "lockedApp", "")
314 tryCompare(greeter, "hasLockedApp", false)
313 tryCompare(greeter, "hasLockedApp", false)
315− tryCompare(lockscreen, "shown", true)
314+ tryCompare(greeter, "fullyShown", true);
316 tryCompare(panel, "fullscreenMode", false)
315 tryCompare(panel, "fullscreenMode", false)
317 tryCompare(indicators, "available", true)
316 tryCompare(indicators, "available", true)
318 tryCompare(launcher, "available", true)
317 tryCompare(launcher, "available", true)
@@ -320,23 +319,25 @@ Item {
320 }
319 }
321
320
322 function test_emergencyCallCrash() {
321 function test_emergencyCallCrash() {
323− var lockscreen = findChild(shell, "lockscreen")
322+ var greeter = findChild(shell, "greeter");
324− var emergencyButton = findChild(lockscreen, "emergencyCallLabel")
323+ var emergencyButton = findChild(greeter, "emergencyCallLabel");
325 tap(emergencyButton)
324 tap(emergencyButton)
326
325
327− tryCompare(lockscreen, "shown", false)
326+ tryCompare(greeter, "shown", false);
328 killApps() // kill dialer-app, as if it crashed
327 killApps() // kill dialer-app, as if it crashed
328+ tryCompare(greeter, "shown", true);
329− tryCompare(lockscreen, "shown", true)
329+ tryCompare(findChild(greeter, "lockscreen"), "shown", true);
330+ tryCompare(findChild(greeter, "coverPage"), "shown", false);
330 }
331 }
331
332
332 function test_emergencyCallAppLaunch() {
333 function test_emergencyCallAppLaunch() {
333− var lockscreen = findChild(shell, "lockscreen")
334+ var greeter = findChild(shell, "greeter");
334− var emergencyButton = findChild(lockscreen, "emergencyCallLabel")
335+ var emergencyButton = findChild(greeter, "emergencyCallLabel");
335 tap(emergencyButton)
336 tap(emergencyButton)
336
337
337− tryCompare(lockscreen, "shown", false)
338+ tryCompare(greeter, "shown", false);
338 ApplicationManager.startApplication("gallery-app", ApplicationManager.NoFlag)
339 ApplicationManager.startApplication("gallery-app", ApplicationManager.NoFlag)
339− tryCompare(lockscreen, "shown", true)
340+ tryCompare(greeter, "shown", true);
340 }
341 }
341
342
342 function test_failedLoginsCount() {
343 function test_failedLoginsCount() {
@@ -350,7 +351,8 @@ Item {
350 }
351 }
351
352
352 function test_wrongEntries() {
353 function test_wrongEntries() {
354+ var greeter = findChild(shell, "greeter");
353− shell.failedLoginsDelayAttempts = 3
355+ greeter.failedLoginsDelayAttempts = 3;
354
356
355 var placeHolder = findChild(shell, "wrongNoticeLabel")
357 var placeHolder = findChild(shell, "wrongNoticeLabel")
356 tryCompare(placeHolder, "text", "")
358 tryCompare(placeHolder, "text", "")
@@ -364,7 +366,7 @@ Item {
364 var lockscreen = findChild(shell, "lockscreen")
366 var lockscreen = findChild(shell, "lockscreen")
365 tryCompare(lockscreen, "delayMinutes", 0)
367 tryCompare(lockscreen, "delayMinutes", 0)
366 enterPin("1111")
368 enterPin("1111")
367− tryCompare(lockscreen, "delayMinutes", shell.failedLoginsDelayMinutes)
369+ tryCompare(lockscreen, "delayMinutes", greeter.failedLoginsDelayMinutes);
368 }
370 }
369
371
370 function test_factoryReset() {
372 function test_factoryReset() {
@@ -410,7 +412,7 @@ Item {
410 // And when we kill the app, we go back to locked tablet mode
412 // And when we kill the app, we go back to locked tablet mode
411 killApps()
413 killApps()
412 var greeter = findChild(shell, "greeter")
414 var greeter = findChild(shell, "greeter")
413− tryCompare(greeter, "showProgress", 1)
415+ tryCompare(greeter, "fullyShown", true)
414 tryCompare(shell, "sideStageEnabled", true)
416 tryCompare(shell, "sideStageEnabled", true)
415 tryCompare(applicationsDisplayLoader, "tabletMode", true)
417 tryCompare(applicationsDisplayLoader, "tabletMode", true)
416 }
418 }
@@ -453,11 +455,11 @@ Item {
453 // - Should be back in normal dialer
455 // - Should be back in normal dialer
454 // (we've had a bug where we locked screen in this case)
456 // (we've had a bug where we locked screen in this case)
455
457
456− var lockscreen = findChild(shell, "lockscreen");
458+ var greeter = findChild(shell, "greeter");
457 var panel = findChild(shell, "panel");
459 var panel = findChild(shell, "panel");
458
460
459 enterPin("1234");
461 enterPin("1234");
460− tryCompare(lockscreen, "shown", false);
462+ tryCompare(greeter, "shown", false);
461 tryCompare(LightDM.Greeter, "active", false);
463 tryCompare(LightDM.Greeter, "active", false);
462
464
463 ApplicationManager.startApplication("dialer-app", ApplicationManager.NoFlag);
465 ApplicationManager.startApplication("dialer-app", ApplicationManager.NoFlag);
@@ -472,10 +474,32 @@ Item {
472 ApplicationManager.requestFocusApplication("dialer-app");
474 ApplicationManager.requestFocusApplication("dialer-app");
473
475
474 tryCompare(ApplicationManager, "focusedApplicationId", "dialer-app");
476 tryCompare(ApplicationManager, "focusedApplicationId", "dialer-app");
475− tryCompare(lockscreen, "shown", false);
477+ tryCompare(greeter, "shown", false);
476 tryCompare(LightDM.Greeter, "active", false);
478 tryCompare(LightDM.Greeter, "active", false);
477 }
479 }
478
480
481+ function test_focusRequestedHidesCoverPage() {
482+ LightDM.Greeter.showGreeter();
483+
484+ var app = ApplicationManager.startApplication("gallery-app");
485+ // wait until the app is fully loaded (ie, real surface replaces splash screen)
486+ tryCompareFunction(function() { return app.session !== null && app.session.surface !== null }, true);
487+
488+ // New app hides coverPage?
489+ var greeter = findChild(shell, "greeter");
490+ var coverPage = testCase.findChild(shell, "coverPage");
491+ tryCompare(coverPage, "showProgress", 0);
492+ tryCompare(greeter, "fullyShown", true);
493+
494+ LightDM.Greeter.showGreeter();
495+ tryCompare(coverPage, "showProgress", 1);
496+
497+ // Make sure focusing same app triggers same behavior
498+ ApplicationManager.requestFocusApplication("gallery-app");
499+ tryCompare(coverPage, "showProgress", 0);
500+ tryCompare(greeter, "fullyShown", true);
501+ }
502+
479 function test_suspend() {
503 function test_suspend() {
480 var greeter = findChild(shell, "greeter");
504 var greeter = findChild(shell, "greeter");
481
505
@@ -487,10 +511,10 @@ Item {
487
511
488 // And wake up
512 // And wake up
489 Powerd.status = Powerd.On;
513 Powerd.status = Powerd.On;
490− tryCompare(greeter, "showProgress", 1);
514+ tryCompare(greeter, "fullyShown", true);
491
515
492 // Swipe away greeter to focus app
516 // Swipe away greeter to focus app
493− swipeAwayGreeter();
517+ swipeAwayGreeter(true);
494
518
495 // We have a lockscreen, make sure we're still suspended
519 // We have a lockscreen, make sure we're still suspended
496 tryCompare(ApplicationManager, "suspended", true);
520 tryCompare(ApplicationManager, "suspended", true);
@@ -539,9 +563,9 @@ Item {
539 only after its idle timeout is triggered.
563 only after its idle timeout is triggered.
540 */
564 */
541 function test_shortLeftEdgeSwipeMakesLauncherStayVisible() {
565 function test_shortLeftEdgeSwipeMakesLauncherStayVisible() {
566+ LightDM.Greeter.showGreeter();
542− var greeter = testCase.findChild(shell, "greeter")
567+ var coverPage = testCase.findChild(shell, "coverPage");
543− greeter.show();
544− tryCompare(greeter, "showProgress", 1);
568+ tryCompare(coverPage, "showProgress", 1);
545
569
546 var launcher = testCase.findChild(shell, "launcher")
570 var launcher = testCase.findChild(shell, "launcher")
547 {
571 {
@@ -562,12 +586,12 @@ Item {
562 tryCompare(launcherPanel, "x", 0);
586 tryCompare(launcherPanel, "x", 0);
563
587
564 // Greeter should be displaced
588 // Greeter should be displaced
565− tryCompareFunction(function() { return greeter.mapToItem(shell, 0, 0).x > shell.width*0.2; }, true);
589+ tryCompareFunction(function() { return coverPage.mapToItem(shell, 0, 0).x > shell.width*0.2; }, true);
566
590
567 touchRelease(shell, toX, shell.height * 0.5);
591 touchRelease(shell, toX, shell.height * 0.5);
568
592
569 // Upon release the greeter should have slid back into full view
593 // Upon release the greeter should have slid back into full view
570− tryCompareFunction(function() { return greeter.mapToItem(shell, 0, 0).x === 0; }, true);
594+ tryCompareFunction(function() { return coverPage.mapToItem(shell, 0, 0).x === 0; }, true);
571
595
572 // And the launcher should stay fully shown
596 // And the launcher should stay fully shown
573 for (var i = 0; i < 10; ++i) {
597 for (var i = 0; i < 10; ++i) {
@@ -575,5 +599,30 @@ Item {
575 compare(launcherPanel.x, 0);
599 compare(launcherPanel.x, 0);
576 }
600 }
577 }
601 }
602+
603+ function test_longLeftEdgeDrags() {
604+ var coverPage = findChild(shell, "coverPage");
605+ var lockscreen = findChild(shell, "lockscreen");
606+ var launcher = findChild(shell, "launcherPanel");
607+ ApplicationManager.startApplication("gallery-app");
608+ tryCompare(ApplicationManager, "focusedApplicationId", "gallery-app");
609+
610+ // Show greeter
611+ LightDM.Greeter.showGreeter();
612+ tryCompare(coverPage, "showProgress", 1);
613+
614+ // Swipe cover page away
615+ touchFlick(shell, 2, shell.height / 2, units.gu(27), shell.height / 2);
616+ tryCompare(launcher, "x", -launcher.width);
617+ tryCompare(coverPage, "showProgress", 0);
618+ compare(lockscreen.shown, true);
619+ compare(ApplicationManager.focusedApplicationId, "gallery-app");
620+
621+ // Now attempt a swipe on lockscreen
622+ touchFlick(shell, 2, shell.height / 2, units.gu(27), shell.height / 2);
623+ tryCompare(launcher, "x", 0);
624+ compare(lockscreen.shown, true);
625+ compare(ApplicationManager.focusedApplicationId, "gallery-app");
626+ }
578 }
627 }
579}
628}
tests/qmltests/tst_TabletShell.qml+50−60
@@ -28,9 +28,10 @@ import Powerd 0.1
28
28
29import "../../qml"
29import "../../qml"
30
30
31−Row {
31+Item {
32 id: root
32 id: root
33− spacing: 0
33+ width: units.gu(120)
34+ height: units.gu(80)
34
35
35 Component.onCompleted: {
36 Component.onCompleted: {
36 // must set the mock mode before loading the Shell
37 // must set the mock mode before loading the Shell
@@ -55,49 +56,52 @@ Row {
55 }
56 }
56 }
57 }
57
58
59+ Row {
60+ anchors.fill: parent
58− Loader {
61+ Loader {
59− id: shellLoader
62+ id: shellLoader
60
63
61− active: false
64+ active: false
62− width: units.gu(100)
65+ width: units.gu(100)
63− height: units.gu(80)
66+ height: parent.height
64
67
65− property bool itemDestroyed: false
68+ property bool itemDestroyed: false
66− sourceComponent: Component {
69+ sourceComponent: Component {
67− Shell {
70+ Shell {
68− property string indicatorProfile: "phone"
71+ property string indicatorProfile: "phone"
69
72
70− Component.onDestruction: {
73+ Component.onDestruction: {
71− shellLoader.itemDestroyed = true
74+ shellLoader.itemDestroyed = true
75+ }
72 }
76 }
73 }
77 }
74 }
78 }
75− }
76
79
77− Rectangle {
80+ Rectangle {
78− color: "white"
81+ color: "white"
79− width: units.gu(20)
82+ width: units.gu(20)
80− height: shellLoader.height
83+ height: parent.height
81−
84+
82− Column {
85+ Column {
83− anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
86+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
84− spacing: units.gu(1)
87+ spacing: units.gu(1)
85− Button {
88+ Button {
86− text: "Show Greeter"
89+ text: "Show Greeter"
87− onClicked: {
90+ onClicked: {
88− if (shellLoader.status !== Loader.Ready)
91+ if (shellLoader.status !== Loader.Ready)
89− return
92+ return
90−
93+
91− var greeter = testCase.findChild(shellLoader.item, "greeter")
94+ var greeter = testCase.findChild(shellLoader.item, "greeter")
92− if (!greeter.shown) {
95+ if (!greeter.shown) {
93− greeter.show()
96+ greeter.show()
97+ }
94 }
98 }
95 }
99 }
96− }
97− Button {
100+ Button {
98− text: "Demo edges"
101+ text: "Demo edges"
99− onClicked: {
102+ onClicked: {
100− AccountsService.demoEdges = true
103+ AccountsService.demoEdges = true;
104+ }
101 }
105 }
102 }
106 }
103 }
107 }
@@ -133,6 +137,8 @@ Row {
133
137
134 function init() {
138 function init() {
135 tryCompare(shell, "enabled", true); // will be enabled when greeter is all ready
139 tryCompare(shell, "enabled", true); // will be enabled when greeter is all ready
140+ var userList = findChild(shell, "userList");
141+ tryCompare(userList, "movingInternally", false);
136 sessionSpy.clear()
142 sessionSpy.clear()
137 sessionSpy.target = findChild(shell, "greeter")
143 sessionSpy.target = findChild(shell, "greeter")
138 dashCommunicatorSpy.target = findInvisibleChild(shell, "dashCommunicator")
144 dashCommunicatorSpy.target = findInvisibleChild(shell, "dashCommunicator")
@@ -188,8 +194,7 @@ Row {
188 if (userlist.currentIndex > i) {
194 if (userlist.currentIndex > i) {
189 next = userlist.currentIndex - 1
195 next = userlist.currentIndex - 1
190 }
196 }
191− var account = findChild(greeter, "username"+next)
197+ tap(findChild(greeter, "username"+next));
192− mouseClick(account, 1, 1)
193 tryCompare(userlist, "currentIndex", next)
198 tryCompare(userlist, "currentIndex", next)
194 tryCompare(userlist, "movingInternally", false)
199 tryCompare(userlist, "movingInternally", false)
195 }
200 }
@@ -197,13 +202,12 @@ Row {
197
202
198 function selectUser(name) {
203 function selectUser(name) {
199 // Find index of user with the right name
204 // Find index of user with the right name
200− var greeter = findChild(shell, "greeter")
201− for (var i = 0; i < greeter.model.count; i++) {
205+ for (var i = 0; i < LightDM.Users.count; i++) {
202− if (greeter.model.data(i, LightDM.UserRoles.NameRole) == name) {
206+ if (LightDM.Users.data(i, LightDM.UserRoles.NameRole) == name) {
203 break
207 break
204 }
208 }
205 }
209 }
206− if (i == greeter.model.count) {
210+ if (i == LightDM.Users.count) {
207 fail("Didn't find name")
211 fail("Didn't find name")
208 return -1
212 return -1
209 }
213 }
@@ -213,7 +217,7 @@ Row {
213
217
214 function clickPasswordInput(isButton) {
218 function clickPasswordInput(isButton) {
215 var greeter = findChild(shell, "greeter")
219 var greeter = findChild(shell, "greeter")
216− tryCompare(greeter, "showProgress", 1)
220+ tryCompare(greeter, "fullyShown", true);
217
221
218 var passwordMouseArea = findChild(shell, "passwordMouseArea")
222 var passwordMouseArea = findChild(shell, "passwordMouseArea")
219 tryCompare(passwordMouseArea, "enabled", isButton)
223 tryCompare(passwordMouseArea, "enabled", isButton)
@@ -223,9 +227,9 @@ Row {
223 }
227 }
224
228
225 function confirmLoggedIn(loggedIn) {
229 function confirmLoggedIn(loggedIn) {
226− var greeterWrapper = findChild(shell, "greeterWrapper")
230+ var greeter = findChild(shell, "greeter");
227− tryCompare(greeterWrapper, "showProgress", loggedIn ? 0 : 1)
231+ tryCompare(greeter, "shown", loggedIn ? false : true);
228− tryCompare(sessionSpy, "count", loggedIn ? 1 : 0)
232+ verify(loggedIn ? sessionSpy.count > 0 : sessionSpy.count === 0);
229 }
233 }
230
234
231 function swipeFromLeftEdge(swipeLength) {
235 function swipeFromLeftEdge(swipeLength) {
@@ -234,20 +238,6 @@ Row {
234 touchFlick(shell, touchStartX, touchStartY, swipeLength, touchStartY)
238 touchFlick(shell, touchStartX, touchStartY, swipeLength, touchStartY)
235 }
239 }
236
240
237− function test_noLockscreen() {
238− selectUser("has-password")
239− var lockscreen = findChild(shell, "lockscreen")
240− tryCompare(lockscreen, "shown", false)
241− }
242−
243− function test_showAndHideGreeterDBusCalls() {
244− var greeter = findChild(shell, "greeter")
245− LightDM.Greeter.hideGreeter()
246− tryCompare(greeter, "showProgress", 0)
247− LightDM.Greeter.showGreeter()
248− tryCompare(greeter, "showProgress", 1)
249− }
250−
251 function test_login_data() {
241 function test_login_data() {
252 return [
242 return [
253 {tag: "auth error", user: "auth-error", loggedIn: false, password: ""},
243 {tag: "auth error", user: "auth-error", loggedIn: false, password: ""},