on xenial help sign in
Separate greeter into WideGreeter and NarrowGreeter classes
commit93b318c3df287d5b0b859e5495b2d7a36b4aa1a3 authorMichael Terry <michael.terry@canonical.com> dateThu 11 Dec 2014, 15:01 (12 years ago) parent5545def
12 files changed+708−541
qml/Greeter/GreeterContent.qml → qml/Greeter/CoverPage.qml+50−94 qml/Greeter/Greeter.qml+181−252 qml/Greeter/LoginList.qml+39−58 qml/Greeter/NarrowGreeter.qml+169 qml/Greeter/WideGreeter.qml+123 qml/Shell.qml+14−27 tests/mocks/libusermetrics/UserMetrics.cpp+8 tests/qmltests/Dash/tst_DashContent.qml+1−1 tests/qmltests/Greeter/tst_MultiGreeter.qml+12−11 tests/qmltests/tst_Shell.qml+24−21 tests/qmltests/tst_ShellWithPin.qml+42−35 tests/qmltests/tst_TabletShell.qml+45−42
qml/Greeter/GreeterContent.qml → qml/Greeter/CoverPage.qmlrenamed+50−94
@@ -21,35 +21,50 @@ import Ubuntu.Components 1.1
2121import Ubuntu.Gestures 0.1
2222import "../Components"
2323
24−Item {
24+Showable {
2525 id: root
26− anchors.fill: parent
2726
28− property bool ready: background.source == "" || background.status == Image.Ready || background.status == Image.Error
27+ property real launcherOffset
28+ property alias background: greeterBackground.source
29+ property real backgroundTopMargin
30+ property bool ready: greeterBackground.source == "" || greeterBackground.status == Image.Ready || greeterBackground.status == Image.Error
31+ property int currentIndex
32+ property bool draggable: true
33+
34+ property alias infographics: infographics
35+
36+ readonly property real showProgress: MathUtils.clamp((width - Math.abs(x)) / width, 0, 1)
2937
30− signal selected(int uid)
31− signal unlocked(int uid)
3238 signal tease()
3339
34− function tryToUnlock() {
35− if (loginLoader.item) {
36− loginLoader.item.tryToUnlock()
40+ function hideRight() {
41+ if (shown) {
42+ hideAnimation = d.rightHideAnimation;
43+ hide();
3744 }
3845 }
3946
40− function reset() {
41− if (loginLoader.item) {
42− loginLoader.item.reset()
43− }
47+ QtObject {
48+ id: d
49+
50+ property var showAnimation: StandardAnimation { property: "dragOffset"; to: 0; duration: UbuntuAnimation.FastDuration }
51+ property var leftHideAnimation: StandardAnimation { property: "dragOffset"; to: -width }
52+ property var rightHideAnimation: StandardAnimation { property: "dragOffset"; to: width }
4453 }
4554
55+ property real dragOffset
56+ x: launcherOffset + dragOffset
57+
58+ showAnimation: d.showAnimation
59+ hideAnimation: d.leftHideAnimation
60+
4661 onTease: showLabelAnimation.start()
4762
4863 // Bi-directional revealer
4964 DraggingArea {
5065 id: dragHandle
5166 anchors.fill: parent
52− enabled: greeter.narrowMode || !greeter.locked
67+ enabled: draggable
5368 orientation: Qt.Horizontal
5469 propagateComposedEvents: true
5570
@@ -60,7 +75,7 @@ Item {
6075 }
6176
6277 function maybeTease() {
63− if (!greeter.locked || greeter.narrowMode) {
78+ if (enabled) {
6479 root.tease();
6580 }
6681 }
@@ -70,30 +85,30 @@ Item {
7085 onPressAndHold: {} // eat event, but no need to tease, as drag will cover it
7186
7287 onDragEnd: {
73− if (greeter.dragOffset > 0 && rightEvaluator.shouldAutoComplete()) {
74− greeter.hideRight()
75− } else if (greeter.dragOffset < 0 && leftEvaluator.shouldAutoComplete()) {
76− greeter.hide();
88+ if (root.dragOffset > 0 && rightEvaluator.shouldAutoComplete()) {
89+ root.hideRight();
90+ } else if (root.dragOffset < 0 && leftEvaluator.shouldAutoComplete()) {
91+ root.hide();
7792 } else {
78− greeter.show(); // undo drag
93+ root.show(); // undo drag
7994 }
8095 }
8196
8297 onDragValueChanged: {
8398 // dragValue is kept as a "step" value since we do this adjusting on the fly
84− greeter.dragOffset += dragValue;
99+ root.dragOffset += dragValue;
85100 }
86101
87102 EdgeDragEvaluator {
88103 id: rightEvaluator
89− trackedPosition: dragHandle.dragValue + greeter.dragOffset
104+ trackedPosition: dragHandle.dragValue + root.dragOffset
90105 maxDragDistance: root.width
91106 direction: Direction.Rightwards
92107 }
93108
94109 EdgeDragEvaluator {
95110 id: leftEvaluator
96− trackedPosition: dragHandle.dragValue + greeter.dragOffset
111+ trackedPosition: dragHandle.dragValue + root.dragOffset
97112 maxDragDistance: root.width
98113 direction: Direction.Leftwards
99114 }
@@ -113,17 +128,16 @@ Item {
113128 }
114129
115130 CrossFadeImage {
116− id: background
131+ id: greeterBackground
117132 objectName: "greeterBackground"
118133 anchors {
119134 fill: parent
120− topMargin: backgroundTopMargin
135+ topMargin: root.backgroundTopMargin
121136 }
122137 fillMode: Image.PreserveAspectCrop
123138 // Limit how much memory we'll reserve for this image
124139 sourceSize.height: height
125140 sourceSize.width: width
126− source: greeter.background
127141 }
128142
129143 Rectangle {
@@ -132,91 +146,33 @@ Item {
132146 opacity: 0.4
133147 }
134148
135− Loader {
136− id: loginLoader
137− objectName: "loginLoader"
138− anchors {
139− left: parent.left
140− leftMargin: Math.min(parent.width * 0.16, units.gu(20))
141− verticalCenter: parent.verticalCenter
142− }
143− width: units.gu(29)
144− height: parent.height
145−
146− // TODO: Once we have a system API for determining which mode we are
147− // in, tablet/phone/desktop, that should be used instead of narrowMode.
148− source: greeter.narrowMode ? "" : "LoginList.qml"
149−
150− onLoaded: {
151− item.currentIndex = greeterContentLoader.currentIndex;
152− }
153−
154− Binding {
155− target: loginLoader.item
156− property: "model"
157− value: greeterContentLoader.model
158− }
159−
160− Connections {
161− target: loginLoader.item
162−
163− onSelected: {
164− root.selected(uid);
165− }
166−
167− onUnlocked: {
168− root.unlocked(uid);
169− }
170−
171− onCurrentIndexChanged: {
172− if (greeterContentLoader.currentIndex !== loginLoader.item.currentIndex) {
173− greeterContentLoader.currentIndex = loginLoader.item.currentIndex;
174− }
175− }
176− }
177− }
178−
179149 Infographics {
180150 id: infographics
181151 objectName: "infographics"
182− height: narrowMode ? parent.height : 0.75 * parent.height
183− model: greeterContentLoader.infographicModel
152+ height: parent.height
153+ model: LightDM.Infographic
184154
185− property string selectedUser
186− property string infographicUser: AccountsService.statsWelcomeScreen ? selectedUser : ""
187− onInfographicUserChanged: greeterContentLoader.infographicModel.username = infographicUser
155+ property string selectedUser: LightDM.Users.data(root.currentIndex, LightDM.UserRoles.NameRole)
188156
189157 Component.onCompleted: {
190− selectedUser = greeterContentLoader.model.data(greeterContentLoader.currentIndex, LightDM.UserRoles.NameRole)
191− greeterContentLoader.infographicModel.username = infographicUser
192− greeterContentLoader.infographicModel.readyForDataChange()
158+ LightDM.Infographic.readyForDataChange();
193159 }
194160
195− Connections {
196− target: root
197− onSelected: infographics.selectedUser = greeterContentLoader.model.data(uid, LightDM.UserRoles.NameRole)
161+ Binding {
162+ target: LightDM.Infographic
163+ property: "username"
164+ value: AccountsService.statsWelcomeScreen ? infographics.selectedUser : ""
198165 }
199166
200167 Connections {
201168 target: i18n
202− onLanguageChanged: greeterContentLoader.infographicModel.readyForDataChange()
169+ onLanguageChanged: LightDM.Infographic.readyForDataChange()
203170 }
204171
205172 anchors {
206173 verticalCenter: parent.verticalCenter
207− left: narrowMode ? root.left : loginLoader.right
208− right: root.right
209− }
210− }
211−
212− Clock {
213− id: clock
214− visible: narrowMode
215−
216− anchors {
217− top: parent.top
218− topMargin: units.gu(2)
219− horizontalCenter: parent.horizontalCenter
174+ left: parent.left
175+ right: parent.right
220176 }
221177 }
222178
qml/Greeter/Greeter.qml+181−252
@@ -24,358 +24,287 @@ import "../Components"
2424
2525Showable {
2626 id: greeter
27− created: greeterContentLoader.status == Loader.Ready && greeterContentLoader.item.ready
27+ created: loader.status == Loader.Ready
2828
2929 property url background
3030
3131 // How far to offset the top greeter layer during a launcher left-drag
3232 property real launcherOffset
3333
34− // 1 when fully shown and 0 when fully hidden
35− readonly property real showProgress: MathUtils.clamp((width - Math.abs(greeterContentLoader.x)) / width, 0, 1)
36− readonly property bool fullyShown: showProgress === 1 || lockscreen.shown
37− readonly property bool semiShown: shown || lockscreen.shown
34+ readonly property bool active: shown || hasLockedApp
35+ readonly property bool fullyShown: loader.item ? loader.item.fullyShown : false
3836
3937 // True when the greeter is waiting for PAM or other setup process
40− property bool waiting: true
38+ readonly property alias waiting: d.waiting
4139
4240 property string lockedApp: ""
43− property bool hasLockedApp: lockedApp !== ""
41+ readonly property bool hasLockedApp: lockedApp !== ""
4442
45− showAnimation: d.showAnimation
46− hideAnimation: d.leftHideAnimation
47−
48− readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated && !forcedUnlock
4943 property bool forcedUnlock
50− onForcedUnlockChanged: if (forcedUnlock) lockscreen.hide()
44+ readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated && !forcedUnlock
5145
5246 property bool tabletMode
53− readonly property bool narrowMode: !multiUser && !tabletMode
54− readonly property bool multiUser: LightDM.Users.count > 1
55−
56− property real dragOffset
57−
58− // We define a proxy and "is valid" property for launcherOffset because of
59− // a quirk in Qml. We only want this animation to fire if we are reset
60− // back to zero (on a release of the drag). But by defining a Behavior,
61− // we delay the property from reaching zero until it's too late. So we set
62− // a proxy bound to launcherOffset, which lets us see the target value of
63− // zero as we also slowly adjust the proxy down to zero. But Qml will send
64− // change notifications in declaration order. So unless we define the
65− // proxy first, we need a little "is valid" property defined above the
66− // proxy, so we know when to enable the proxy behavior. Phew!
67− readonly property bool launcherOffsetValid: launcherOffset > 0
68− property real launcherOffsetProxy: shown ? launcherOffset : 0
69− Behavior on launcherOffsetProxy {
70− enabled: !launcherOffsetValid
71− StandardAnimation {}
72− }
47+
48+ property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
49+ property int failedLoginsDelayAttempts: 7 // number of failed logins
50+ property int failedLoginsDelayMinutes: 5 // minutes of forced waiting
7351
7452 signal tease()
7553 signal sessionStarted()
7654 signal emergencyCall()
7755
78− function hideRight() {
79− if (shown) {
80− hideAnimation = d.rightHideAnimation;
81− hide()
82− }
83− }
84−
8556 function notifyAppFocused(appId) {
86− if (narrowMode) {
87− if (hasLockedApp) {
88− if (appId === lockedApp) {
89− lockscreen.hide(); // show locked app
90− } else {
91− startUnlock(); // show lockscreen if necessary
92− }
93− }
94− hide();
95− } else {
96− if (LightDM.Greeter.active) {
97− startUnlock();
98− }
99− }
100− }
101−
102− // Do we need these next two functions, really?
103− function notifyFocusChanged(appId) {
104− if (hasLockedApp && lockedApp !== appId) {
105− startUnlock();
106− }
107− }
108−
109− function notifyAppAdded(appId) {
110− if (shown && appId != "unity8-dash") {
111− startUnlock();
112− }
113− if (narrowMode && hasLockedApp && appId === lockedApp) {
114− lockscreen.hide(); // show locked app
57+ if (!active) {
58+ return;
11559 }
116− }
11760
118− function notifyAboutToStartApp(appId) {
11961 if (hasLockedApp) {
120− startUnlock();
121− }
122− }
123−
124− function startUnlock() {
125− if (narrowMode) {
126− if (!LightDM.Greeter.authenticated) {
127− lockscreen.maybeShow();
128− }
129− hide();
130− } else {
131− show();
132− if (greeterContentLoader.item) {
133− greeterContentLoader.item.tryToUnlock();
62+ if (appId === lockedApp) {
63+ hide(); // show locked app
64+ } else {
65+ show();
66+ d.startUnlock(false);
13467 }
68+ } else if (appId !== "unity8-dash") { // dash isn't started by user
69+ d.startUnlock(false);
13570 }
13671 }
13772
138− onRequiredChanged: {
139− // Reset hide animation to default once we're finished with it
140− if (required) {
141− // Reset hide animation so that a hide() call is reliably left
142− hideAnimation = d.leftHideAnimation;
73+ function notifyAboutToFocusApp(appId) {
74+ if (!active) {
75+ return;
14376 }
144− }
145−
146− onShownChanged: {
147− if (shown) {
148− waiting = true;
14977
150− if (narrowMode) {
151− d.selectUser(greeterContentLoader.currentIndex);
152− } else {
153− d.reset();
154− }
155− lockedApp = "";
156− forceActiveFocus();
157− }
78+ // A hint that we're about to focus an app. This way we can look
79+ // a little more responsive, rather than waiting for the above
80+ // notifyAppFocused call. We also need this in case we have a locked
81+ // app, in order to show lockscreen instead of new app.
82+ d.startUnlock(false);
15883 }
15984
160− onFullyShownChanged: {
161− // Wait until the greeter is completely covering lockscreen before resetting it.
162− if (narrowMode && fullyShown && !LightDM.Greeter.authenticated) {
163− lockscreen.reset();
164− lockscreen.maybeShow();
85+ function notifyShowingDashFromDrag() {
86+ if (!active) {
87+ return;
16588 }
166− }
16789
168− onShowProgressChanged: {
169− if (showProgress === 0) {
170− if ((LightDM.Greeter.promptless && LightDM.Greeter.authenticated) || forcedUnlock) {
171− d.login();
172− } else if (narrowMode) {
173− lockscreen.clear(false); // to reset focus if necessary
174− }
175− }
90+ // This is a just a glorified notifyAboutToFocusApp(), but it does one
91+ // other thing: it hides any cover pages to the RIGHT, because the user
92+ // just came from a launcher drag starting on the left.
93+ d.startUnlock(true);
17694 }
17795
17896 QtObject {
17997 id: d
18098
181− property var showAnimation: StandardAnimation { property: "dragOffset"; to: 0; duration: UbuntuAnimation.FastDuration }
182− property var leftHideAnimation: StandardAnimation { property: "dragOffset"; to: -width }
183− property var rightHideAnimation: StandardAnimation { property: "dragOffset"; to: width }
99+ readonly property bool multiUser: LightDM.Users.count > 1
100+ property int currentIndex
101+ property int delayMinutes
102+ property bool waiting
103+
104+ // We define a proxy and "is valid" property for launcherOffset because of
105+ // a quirk in Qml. We only want this animation to fire if we are reset
106+ // back to zero (on a release of the drag). But by defining a Behavior,
107+ // we delay the property from reaching zero until it's too late. So we set
108+ // a proxy bound to launcherOffset, which lets us see the target value of
109+ // zero as we also slowly adjust the proxy down to zero. But Qml will send
110+ // change notifications in declaration order. So unless we define the
111+ // proxy first, we need a little "is valid" property defined above the
112+ // proxy, so we know when to enable the proxy behavior. Phew!
113+ readonly property bool launcherOffsetValid: launcherOffset > 0
114+ property real launcherOffsetProxy: shown ? launcherOffset : 0
115+ Behavior on launcherOffsetProxy {
116+ enabled: !d.launcherOffsetValid
117+ StandardAnimation {}
118+ }
184119
185120 function selectUser(uid) {
186− // Update launcher items for new user
121+ currentIndex = uid;
187122 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
188123 AccountsService.user = user;
189124 LauncherModel.setUser(user);
190125 LightDM.Greeter.authenticate(user); // always resets auth state
191126 }
192127
193− function reset() {
194− if (greeterContentLoader.item) {
195− greeterContentLoader.item.reset()
196− }
197− }
198−
199128 function login() {
200129 enabled = false;
201130 if (LightDM.Greeter.startSessionSync()) {
202131 sessionStarted();
203− hide();
204− lockscreen.hide();
132+ loader.item.authenticated(true);
133+ } else {
134+ loader.item.authenticated(false);
205135 }
206136 enabled = true;
207137 }
138+
139+ function startUnlock(toTheRight) {
140+ if (loader.item) {
141+ loader.item.tryToUnlock(toTheRight);
142+ }
143+ }
208144 }
209145
210− Lockscreen {
211− id: lockscreen
212− objectName: "lockscreen"
146+ onForcedUnlockChanged: {
147+ if (forcedUnlock && shown) {
148+ // pretend we were just authenticated
149+ loader.item.authenticated(true);
150+ }
151+ }
213152
214− shown: false
215− showAnimation: StandardAnimation { property: "opacity"; to: 1 }
216− hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
217− anchors.fill: parent
218− visible: required
219− background: greeter.background
220− darkenBackground: 0.4
221− alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
222− minPinLength: 4
223− maxPinLength: 4
224−
225− property string promptText
226− infoText: promptText !== "" ? i18n.tr("Enter %1").arg(promptText) :
227− alphaNumeric ? i18n.tr("Enter passphrase") :
228− i18n.tr("Enter passcode")
229− errorText: promptText !== "" ? i18n.tr("Sorry, incorrect %1").arg(promptText) :
230− alphaNumeric ? i18n.tr("Sorry, incorrect passphrase") + "\n" +
231− i18n.tr("Please re-enter") :
232− i18n.tr("Sorry, incorrect passcode")
233−
234− // FIXME: We *should* show emergency dialer if there is a SIM present,
235− // regardless of whether the side stage is enabled. But right now,
236− // the assumption is that narrow screens are phones which have SIMs
237− // and wider screens are tablets which don't. When we do allow this
238− // on devices with a side stage and a SIM, work should be done to
239− // ensure that the main stage is disabled while the dialer is present
240− // in the side stage. See the FIXME in the stage loader in Shell.qml.
241− showEmergencyCallButton: !greeter.tabletMode
242−
243− onEntered: LightDM.Greeter.respond(passphrase);
244− onCancel: greeter.show()
245− onEmergencyCall: greeter.emergencyCall()
246−
247− onShownChanged: if (shown) greeter.lockedApp = ""
248−
249− function maybeShow() {
250− if (!greeter.forcedUnlock) {
251− show();
252− }
153+ onRequiredChanged: {
154+ if (required) {
155+ d.waiting = true;
156+ lockedApp = "";
253157 }
158+ }
254159
255− Timer {
256− id: forcedDelayTimer
257− interval: 1000 * 60
258− onTriggered: {
259− if (lockscreen.delayMinutes > 0) {
260− lockscreen.delayMinutes -= 1
261− if (lockscreen.delayMinutes > 0) {
262− start() // go again
263− }
160+ Timer {
161+ id: forcedDelayTimer
162+ interval: 1000 * 60
163+ onTriggered: {
164+ if (d.delayMinutes > 0) {
165+ d.delayMinutes -= 1;
166+ if (d.delayMinutes > 0) {
167+ start(); // go again
264168 }
265169 }
266170 }
267171 }
268172
269− Rectangle {
270− anchors.fill: parent
271− color: "black"
272− opacity: greeter.showProgress * 0.8
273− }
274−
275173 Loader {
276− id: greeterContentLoader
277− objectName: "greeterContentLoader"
278−
279− x: launcherOffsetProxy + dragOffset
280− width: parent.width
281− height: parent.height
174+ id: loader
175+ objectName: "loader"
282176
283− property var model: LightDM.Users
284− property int currentIndex: 0
285− property var infographicModel: LightDM.Infographic
286− readonly property int backgroundTopMargin: -greeter.y
177+ anchors.fill: parent
287178
288− source: (greeter.required || lockscreen.required) ? "GreeterContent.qml" : ""
179+ source: greeter.required ? ((!d.multiUser && !tabletMode) ? "NarrowGreeter.qml"
180+ : "WideGreeter.qml")
181+ : ""
289182
290183 onLoaded: {
291− d.selectUser(currentIndex);
184+ loader.item.reset();
185+ greeter.lockedApp = "";
186+ greeter.forceActiveFocus();
187+ d.selectUser(d.currentIndex);
292188 }
293189
294190 Connections {
295− target: greeterContentLoader.item
296−
191+ target: loader.item
297192 onSelected: {
298− d.selectUser(uid);
299− greeterContentLoader.currentIndex = uid;
193+ d.selectUser(index);
194+ }
195+ onUnlocked: {
196+ d.login()
300197 }
301− onUnlocked: greeter.hide()
302198 onTease: greeter.tease()
199+ onRequiredChanged: {
200+ if (!loader.item.required) {
201+ greeter.hide();
202+ }
203+ }
204+ }
205+
206+ Binding {
207+ target: loader.item
208+ property: "backgroundTopMargin"
209+ value: -greeter.y
210+ }
211+
212+ Binding {
213+ target: loader.item
214+ property: "launcherOffset"
215+ value: d.launcherOffsetProxy
216+ }
217+
218+ Binding {
219+ target: loader.item
220+ property: "delayMinutes"
221+ value: d.delayMinutes
303222 }
304223 }
305224
306225 Connections {
307226 target: LightDM.Greeter
308227
309− onShowGreeter: greeter.show()
310− onHideGreeter: d.login()
228+ onShowGreeter: {
229+ greeter.show();
230+ loader.item.reset();
231+ d.selectUser(d.currentIndex);
232+ }
311233
312− onShowPrompt: {
313− waiting = false;
234+ onHideGreeter: {
235+ d.login();
236+ loader.item.hide();
237+ }
314238
239+ onShowMessage: {
315240 if (!LightDM.Greeter.active) {
316241 return; // could happen if hideGreeter() comes in before we prompt
317242 }
318243
319− if (greeter.narrowMode) {
320− lockscreen.promptText = isDefaultPrompt ? "" : text.toLowerCase();
321− lockscreen.maybeShow();
244+ // inefficient, but we only rarely deal with messages
245+ var html = text.replace(/&/g, "&amp;")
246+ .replace(/</g, "&lt;")
247+ .replace(/>/g, "&gt;")
248+ .replace(/\n/g, "<br>");
249+ if (isError) {
250+ html = "<font color=\"#df382c\">" + html + "</font>";
322251 }
252+
253+ loader.item.showMessage(html);
323254 }
324255
325− onPromptlessChanged: {
256+ onShowPrompt: {
257+ d.waiting = false;
258+
326259 if (!LightDM.Greeter.active) {
327260 return; // could happen if hideGreeter() comes in before we prompt
328261 }
329− if (greeter.narrowMode) {
330− if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
331− lockscreen.hide()
332− } else {
333− lockscreen.reset();
334− lockscreen.maybeShow();
335− }
336− }
262+
263+ loader.item.showPrompt(text, isSecret, isDefaultPrompt);
337264 }
338265
339266 onAuthenticationComplete: {
340− waiting = false;
341− if (LightDM.Greeter.authenticated) {
342− AccountsService.failedLogins = 0
343− }
344− // Else only penalize user for a failed login if they actually were
345− // prompted for a password. We do this below after the promptless
346− // early exit.
347−
348− if (LightDM.Greeter.promptless) {
349− return;
350− }
267+ d.waiting = false;
351268
352269 if (LightDM.Greeter.authenticated) {
270+ AccountsService.failedLogins = 0;
353271 d.login();
272+ if (!LightDM.Greeter.promptless) {
273+ loader.item.hide();
274+ }
354275 } else {
355− AccountsService.failedLogins++
356−
357− if (greeter.narrowMode) {
358− if (maxFailedLogins >= 2) { // require at least a warning
359− if (AccountsService.failedLogins === maxFailedLogins - 1) {
360− var title = lockscreen.alphaNumeric ?
361− i18n.tr("Sorry, incorrect passphrase.") :
362− i18n.tr("Sorry, incorrect passcode.")
363− var text = i18n.tr("This will be your last attempt.") + " " +
364− (lockscreen.alphaNumeric ?
365− i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
366− i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
367− lockscreen.showInfoPopup(title, text)
368− } else if (AccountsService.failedLogins >= maxFailedLogins) {
369− SystemImage.factoryReset() // Ouch!
370− }
371− }
372− if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
373− lockscreen.delayMinutes = failedLoginsDelayMinutes
374− forcedDelayTimer.start()
276+ if (!LightDM.Greeter.promptless) {
277+ AccountsService.failedLogins++;
278+ }
279+
280+ // Check if we should initiate a factory reset
281+ if (maxFailedLogins >= 2) { // require at least a warning
282+ if (AccountsService.failedLogins === maxFailedLogins - 1) {
283+ loader.item.showLastChance();
284+ } else if (AccountsService.failedLogins >= maxFailedLogins) {
285+ SystemImage.factoryReset(); // Ouch!
375286 }
287+ }
288+
289+ // Check if we should initiate a forced login delay
290+ if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
291+ d.delayMinutes = failedLoginsDelayMinutes;
292+ forcedDelayTimer.start();
293+ }
294+
295+ loader.item.authenticated(false);
296+ if (!LightDM.Greeter.promptless) {
297+ d.selectUser(d.currentIndex);
298+ }
299+ }
300+ }
376301
377− lockscreen.clear(true);
378− d.selectUser(greeterContentLoader.currentIndex);
302+ onRequestAuthenticationUser: {
303+ // Find index for requested user, if it exists
304+ for (var i = 0; i < LightDM.Users.count; i++) {
305+ if (user === LightDM.Users.data(i, LightDM.UserRoles.NameRole)) {
306+ d.selectUser(i);
307+ return;
379308 }
380309 }
381310 }
@@ -384,6 +313,6 @@ Showable {
384313 Binding {
385314 target: LightDM.Greeter
386315 property: "active"
387− value: greeter.semiShown || greeter.hasLockedApp
316+ value: greeter.active
388317 }
389318}
qml/Greeter/LoginList.qml+39−58
@@ -33,25 +33,49 @@ Item {
3333 property bool wasPrompted: false
3434
3535 signal selected(int uid)
36− signal unlocked(int uid)
36+ signal unlocked()
3737
3838 function tryToUnlock() {
3939 if (LightDM.Greeter.promptless) {
4040 if (LightDM.Greeter.authenticated) {
41− root.unlocked(userList.currentIndex)
41+ root.unlocked()
4242 } else {
4343 root.resetAuthentication()
44+ root.selected(currentIndex);
4445 }
4546 } else {
4647 passwordInput.forceActiveFocus()
4748 }
4849 }
4950
51+ function showMessage(html) {
52+ infoLabel.text = html;
53+ }
54+
55+ function showPrompt(text, isSecret, isDefaultPrompt) {
56+ passwordInput.text = "";
57+ passwordInput.promptText = text;
58+ passwordInput.enabled = true;
59+ passwordInput.echoMode = isSecret ? TextInput.Password : TextInput.Normal;
60+ if (wasPrompted) // stay in text field if second prompt
61+ passwordInput.focus = true;
62+ wasPrompted = true;
63+ }
64+
65+ function showError() {
66+ wrongPasswordAnimation.start();
67+ root.resetAuthentication();
68+ passwordInput.focus = true;
69+ }
70+
5071 function reset() {
51− root.resetAuthentication()
72+ root.resetAuthentication();
5273 }
5374
54− Keys.onEscapePressed: root.resetAuthentication()
75+ Keys.onEscapePressed: {
76+ root.resetAuthentication();
77+ selected(currentIndex);
78+ }
5579
5680 Rectangle {
5781 id: highlightItem
@@ -85,6 +109,7 @@ Item {
85109 onCurrentIndexChanged: {
86110 if (LightDM.Greeter.authenticationUser != userList.model.data(currentIndex, LightDM.UserRoles.NameRole)) {
87111 root.resetAuthentication();
112+ root.selected(currentIndex);
88113 }
89114 }
90115
@@ -204,6 +229,11 @@ Item {
204229 width: parent.width - anchors.margins * 2
205230 opacity: userList.movingInternally ? 0 : 1
206231
232+ property string promptText
233+ placeholderText: LightDM.Greeter.promptless ? (LightDM.Greeter.authenticated ? i18n.tr("Tap to unlock")
234+ : i18n.tr("Retry"))
235+ : promptText
236+
207237 Behavior on opacity {
208238 NumberAnimation { duration: 100 }
209239 }
@@ -215,7 +245,10 @@ Item {
215245 enabled = false;
216246 LightDM.Greeter.respond(text);
217247 }
218− Keys.onEscapePressed: root.resetAuthentication()
248+ Keys.onEscapePressed: {
249+ root.resetAuthentication();
250+ root.selected(currentIndex);
251+ }
219252
220253 Image {
221254 anchors {
@@ -256,62 +289,10 @@ Item {
256289 return;
257290 }
258291 infoLabel.text = "";
259− passwordInput.placeholderText = "";
292+ passwordInput.promptText = "";
260293 passwordInput.text = "";
261294 passwordInput.focus = false;
262295 passwordInput.enabled = true;
263296 root.wasPrompted = false;
264− selected(currentIndex);
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− }
316297 }
317298}
qml/Greeter/NarrowGreeter.qmlnew file+169
@@ -0,0 +1,169 @@
1+/*
2+ * Copyright (C) 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.3
18+import AccountsService 0.1
19+import LightDM 0.1 as LightDM
20+import Ubuntu.Components 1.1
21+import "../Components"
22+
23+Item {
24+ id: root
25+
26+ property alias launcherOffset: coverPage.launcherOffset
27+ property int currentIndex // unused
28+ property alias delayMinutes: lockscreen.delayMinutes
29+ property alias backgroundTopMargin: coverPage.backgroundTopMargin
30+ property bool fullyShown: coverPage.showProgress === 1 || lockscreen.shown
31+ property bool required: coverPage.required || lockscreen.required
32+
33+ signal selected(int index)
34+ signal unlocked()
35+ signal tease()
36+
37+ function showMessage(html) {
38+ // TODO
39+ }
40+
41+ function showPrompt(text, isSecret, isDefaultPrompt) {
42+ lockscreen.promptText = isDefaultPrompt ? "" : text.toLowerCase();
43+ lockscreen.maybeShow();
44+ }
45+
46+ function showLastChance() {
47+ var title = lockscreen.alphaNumeric ?
48+ i18n.tr("Sorry, incorrect passphrase.") :
49+ i18n.tr("Sorry, incorrect passcode.");
50+ var text = i18n.tr("This will be your last attempt.") + " " +
51+ (lockscreen.alphaNumeric ?
52+ i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
53+ i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."));
54+ lockscreen.showInfoPopup(title, text);
55+ }
56+
57+ function hide() {
58+ lockscreen.hide();
59+ coverPage.hide();
60+ }
61+
62+ function authenticated(success) {
63+ if (success) {
64+ lockscreen.hide();
65+ } else {
66+ lockscreen.clear(true);
67+ }
68+ }
69+
70+ function reset() {
71+ coverPage.show();
72+ }
73+
74+ function tryToUnlock(toTheRight) {
75+ if (!LightDM.Greeter.authenticated) {
76+ lockscreen.maybeShow();
77+ }
78+ if (toTheRight) {
79+ coverPage.hideRight();
80+ } else {
81+ coverPage.hide();
82+ }
83+ }
84+
85+ QtObject {
86+ id: d
87+ }
88+
89+ Lockscreen {
90+ id: lockscreen
91+ objectName: "lockscreen"
92+
93+ shown: false
94+ showAnimation: StandardAnimation { property: "opacity"; to: 1 }
95+ hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
96+ anchors.fill: parent
97+ visible: required
98+ background: greeter.background
99+ darkenBackground: 0.4
100+ alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
101+ minPinLength: 4
102+ maxPinLength: 4
103+
104+ property string promptText
105+ infoText: promptText !== "" ? i18n.tr("Enter %1").arg(promptText) :
106+ alphaNumeric ? i18n.tr("Enter passphrase") :
107+ i18n.tr("Enter passcode")
108+ errorText: promptText !== "" ? i18n.tr("Sorry, incorrect %1").arg(promptText) :
109+ alphaNumeric ? i18n.tr("Sorry, incorrect passphrase") + "\n" +
110+ i18n.tr("Please re-enter") :
111+ i18n.tr("Sorry, incorrect passcode")
112+
113+ // FIXME: We *should* show emergency dialer if there is a SIM present,
114+ // regardless of whether the side stage is enabled. But right now,
115+ // the assumption is that narrow screens are phones which have SIMs
116+ // and wider screens are tablets which don't. When we do allow this
117+ // on devices with a side stage and a SIM, work should be done to
118+ // ensure that the main stage is disabled while the dialer is present
119+ // in the side stage. See the FIXME in the stage loader in Shell.qml.
120+ showEmergencyCallButton: !greeter.tabletMode
121+
122+ onEntered: LightDM.Greeter.respond(passphrase)
123+ onCancel: coverPage.show()
124+ onEmergencyCall: greeter.emergencyCall()
125+
126+ function maybeShow() {
127+ if (!greeter.forcedUnlock) {
128+ show();
129+ }
130+ }
131+ }
132+
133+ Rectangle {
134+ anchors.fill: parent
135+ color: "black"
136+ opacity: coverPage.showProgress * 0.8
137+ }
138+
139+ CoverPage {
140+ id: coverPage
141+ objectName: "coverPage"
142+ height: parent.height
143+ width: parent.width
144+ background: greeter.background
145+ onTease: root.tease()
146+
147+ onShowProgressChanged: {
148+ if (showProgress === 1) {
149+ lockscreen.reset();
150+ }
151+
152+ if (showProgress === 0) {
153+ if (greeter.locked) {
154+ lockscreen.clear(false); // to reset focus if necessary
155+ } else {
156+ root.unlocked();
157+ }
158+ }
159+ }
160+
161+ Clock {
162+ anchors {
163+ top: parent.top
164+ topMargin: units.gu(2)
165+ horizontalCenter: parent.horizontalCenter
166+ }
167+ }
168+ }
169+}
qml/Greeter/WideGreeter.qmlnew file+123
@@ -0,0 +1,123 @@
1+/*
2+ * Copyright (C) 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.3
18+import LightDM 0.1 as LightDM
19+import Ubuntu.Components 1.1
20+
21+Item {
22+ id: root
23+
24+ property real launcherOffset // unused
25+ property alias currentIndex: loginList.currentIndex
26+ property int delayMinutes // TODO
27+ property alias backgroundTopMargin: coverPage.backgroundTopMargin
28+ property bool fullyShown: coverPage.showProgress === 1
29+ property bool required: coverPage.required
30+
31+ signal selected(int index)
32+ signal unlocked()
33+ signal tease()
34+
35+ function showMessage(html) {
36+ loginList.showMessage(html);
37+ }
38+
39+ function showPrompt(text, isSecret, isDefaultPrompt) {
40+ loginList.showPrompt(text, isSecret, isDefaultPrompt);
41+ }
42+
43+ function showLastChance() {
44+ // TODO
45+ }
46+
47+ function hide() {
48+ coverPage.hide();
49+ }
50+
51+ function authenticated(success) {
52+ if (!success) {
53+ loginList.showError();
54+ }
55+ }
56+
57+ function reset() {
58+ loginList.reset();
59+ }
60+
61+ function tryToUnlock(toTheRight) {
62+ if (LightDM.Greeter.authenticated) {
63+ if (toTheRight) {
64+ coverPage.hideRight();
65+ } else {
66+ coverPage.hide();
67+ }
68+ } else {
69+ coverPage.show();
70+ loginList.tryToUnlock();
71+ }
72+ }
73+
74+ QtObject {
75+ id: d
76+ }
77+
78+ Rectangle {
79+ anchors.fill: parent
80+ color: "black"
81+ opacity: coverPage.showProgress * 0.8
82+ }
83+
84+ CoverPage {
85+ id: coverPage
86+ objectName: "coverPage"
87+ height: parent.height
88+ width: parent.width
89+ background: greeter.background
90+ draggable: !greeter.locked
91+
92+ infographics {
93+ height: 0.75 * parent.height
94+ anchors.leftMargin: loginList.x + loginList.width
95+ }
96+
97+ onTease: root.tease()
98+
99+ onShowProgressChanged: {
100+ if (showProgress === 0 && !greeter.locked) {
101+ root.unlocked();
102+ }
103+ }
104+ }
105+
106+ LoginList {
107+ id: loginList
108+ objectName: "loginList"
109+
110+ anchors {
111+ left: parent.left
112+ leftMargin: Math.min(parent.width * 0.16, units.gu(20))
113+ verticalCenter: parent.verticalCenter
114+ }
115+ width: units.gu(29)
116+ height: parent.height
117+
118+ model: LightDM.Users
119+
120+ onSelected: root.selected(uid)
121+ onUnlocked: coverPage.hide()
122+ }
123+}
qml/Shell.qml+14−27
@@ -59,15 +59,9 @@ Item {
5959 : gsImageTester.status == Image.Ready ? gsImageTester.source : defaultBackground
6060 readonly property real panelHeight: panel.panelHeight
6161
62− readonly property alias locked: greeter.locked
63−
6462 property bool sideStageEnabled: shell.width >= units.gu(100)
6563 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
6664
67− property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
68− property int failedLoginsDelayAttempts: 7 // number of failed logins
69− property int failedLoginsDelayMinutes: 5 // minutes of forced waiting
70−
7165 property int orientation
7266 readonly property int deviceOrientationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
7367 onDeviceOrientationAngleChanged: {
@@ -94,7 +88,7 @@ Item {
9488 }
9589
9690 function startLockedApp(app) {
97− if (shell.locked) {
91+ if (greeter.locked) {
9892 greeter.lockedApp = app;
9993 }
10094 shell.activateApplication(app);
@@ -214,7 +208,7 @@ Item {
214208 }
215209
216210 onFocusedApplicationIdChanged: {
217− greeter.notifyFocusChanged(ApplicationManager.focusedApplicationId);
211+ greeter.notifyAppFocused(ApplicationManager.focusedApplicationId);
218212 panel.indicators.hide();
219213 }
220214
@@ -227,7 +221,7 @@ Item {
227221 wizard.hide();
228222 }
229223
230− greeter.notifyAppAdded(appId);
224+ greeter.notifyAppFocused(appId);
231225 launcher.hide();
232226 }
233227 }
@@ -267,7 +261,7 @@ Item {
267261 Binding {
268262 target: applicationsDisplayLoader.item
269263 property: "interactive"
270− value: edgeDemo.stagesEnabled && !greeter.fullyShown && panel.indicators.fullyClosed && launcher.progress == 0 && !notifications.useModal
264+ value: edgeDemo.stagesEnabled && !greeter.shown && panel.indicators.fullyClosed && launcher.progress == 0 && !notifications.useModal
271265 }
272266 Binding {
273267 target: applicationsDisplayLoader.item
@@ -329,7 +323,7 @@ Item {
329323
330324 hides: [launcher, panel.indicators]
331325 tabletMode: shell.sideStageEnabled
332− launcherOffset: (narrowMode && showProgress > 0) ? launcher.progress : 0
326+ launcherOffset: launcher.progress
333327 forcedUnlock: edgeDemo.running
334328 background: shell.background
335329
@@ -346,7 +340,7 @@ Item {
346340 Binding {
347341 target: ApplicationManager
348342 property: "suspended"
349− value: greeter.fullyShown
343+ value: greeter.shown
350344 }
351345 }
352346
@@ -355,7 +349,7 @@ Item {
355349 target: callManager
356350
357351 onHasCallsChanged: {
358− if (shell.locked && callManager.hasCalls) {
352+ if (greeter.locked && callManager.hasCalls) {
359353 // We just received an incoming call while locked. The
360354 // indicator will have already launched dialer-app for us, but
361355 // there is a race between "hasCalls" changing and the dialer
@@ -386,9 +380,7 @@ Item {
386380 return
387381 }
388382
389− if (LightDM.Greeter.active) {
390− greeter.startUnlock()
391− }
383+ greeter.notifyAboutToFocusApp("unity8-dash");
392384
393385 var animate = !LightDM.Greeter.active && !stages.shown
394386 dash.setCurrentScope(0, animate, false)
@@ -396,13 +388,8 @@ Item {
396388 }
397389
398390 function showDash() {
399− if (greeter.hasLockedApp || // just in case user gets here
400− (!greeter.narrowMode && shell.locked)) {
401− return
402− }
403−
404− if (greeter.shown) {
405− greeter.hideRight();
391+ if (greeter.active) {
392+ greeter.notifyShowingDashFromDrag();
406393 launcher.fadeOut();
407394 }
408395
@@ -424,7 +411,7 @@ Item {
424411 anchors.fill: parent //because this draws indicator menus
425412 indicators {
426413 hides: [launcher]
427− available: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp
414+ available: edgeDemo.panelEnabled && (!greeter.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp
428415 contentEnabled: edgeDemo.panelContentEnabled
429416 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
430417
@@ -440,7 +427,7 @@ Item {
440427 Component.onCompleted: initialise(indicatorProfile)
441428 }
442429 callHint {
443− greeterShown: greeter.fullyShown
430+ greeterShown: greeter.shown
444431 }
445432
446433 property bool topmostApplicationIsFullscreen:
@@ -461,7 +448,7 @@ Item {
461448 anchors.bottom: parent.bottom
462449 width: parent.width
463450 dragAreaWidth: shell.edgeSize
464− available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp
451+ available: edgeDemo.launcherEnabled && (!greeter.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp
465452
466453 onShowDashHome: showHome()
467454 onDash: showDash()
@@ -472,7 +459,7 @@ Item {
472459 }
473460 onLauncherApplicationSelected: {
474461 if (!edgeDemo.running) {
475− greeter.notifyAboutToStartApp(appId);
462+ greeter.notifyAboutToFocusApp(appId);
476463 shell.activateApplication(appId)
477464 }
478465 }
tests/mocks/libusermetrics/UserMetrics.cpp+8
@@ -26,6 +26,7 @@
2626
2727#include <functional>
2828#include <random>
29+#include <QDebug>
2930
3031using namespace UserMetricsOutput;
3132
@@ -289,6 +290,7 @@ void UserMetricsPrivate::generateFakeData()
289290 new UserMetricsData("<b>52km</b> travelled", first, firstMonth,
290291 ninth, secondMonth, this));
291292 m_fakeData.insert("single", data);
293+ m_fakeData.insert("has-pin", data);
292294 }
293295
294296 {
@@ -304,6 +306,7 @@ void UserMetricsPrivate::generateFakeData()
304306 new UserMetricsData("<b>33</b> messages today", second,
305307 firstMonth, eighth, secondMonth, this));
306308 m_fakeData.insert("single", data);
309+ m_fakeData.insert("has-pin", data);
307310 }
308311
309312 {
@@ -319,6 +322,7 @@ void UserMetricsPrivate::generateFakeData()
319322 new UserMetricsData("<b>19</b> minutes talk time", eighth,
320323 firstMonth, second, secondMonth, this));
321324 m_fakeData.insert("single", data);
325+ m_fakeData.insert("has-pin", data);
322326 // Also use same data for some tablet users
323327 m_fakeData.insert("has-password", data);
324328 m_fakeData.insert("no-password", data);
@@ -386,6 +390,10 @@ void UserMetricsPrivate::nextFakeData()
386390 if (m_dataIndex == m_fakeData.constEnd() || m_dataIndex.key() != m_username)
387391 {
388392 m_dataIndex = m_fakeData.constFind(m_username);
393+ if (m_dataIndex == m_fakeData.constEnd())
394+ {
395+ m_dataIndex = m_fakeData.constFind("");
396+ }
389397 }
390398
391399 loadFakeData();
tests/qmltests/Dash/tst_DashContent.qml+1−1
@@ -71,7 +71,7 @@ Item {
7171 scopeLoadedSpy.clear();
7272 scopesModel.load();
7373 tryCompare(scopeLoadedSpy, "count", 6, 15000);
74− tryCompare(scopesModel, "loaded", true);
74+ tryCompare(scopesModel, "loaded", true, 1000, typeof scopesModel.loaded);
7575 tryCompareFunction(function() {
7676 var mockScope1Loader = findChild(shell, "scopeLoader0");
7777 return mockScope1Loader && mockScope1Loader.item != null; },
tests/qmltests/Greeter/tst_MultiGreeter.qml+12−11
@@ -29,6 +29,7 @@ Item {
2929 Greeter {
3030 id: greeter
3131 anchors.fill: parent
32+ tabletMode: true
3233 }
3334
3435 Component {
@@ -83,12 +84,12 @@ Item {
8384
8485 function select_user(name) {
8586 // We could be anywhere in list; find target index to know which direction
86− for (var i = 0; i < greeter.model.count; i++) {
87− if (greeter.model.data(i, LightDM.UserRoles.NameRole) == name) {
87+ for (var i = 0; i < LightDM.Users.count; i++) {
88+ if (LightDM.Users.data(i, LightDM.UserRoles.NameRole) == name) {
8889 break
8990 }
9091 }
91− if (i == greeter.model.count) {
92+ if (i == LightDM.Users.count) {
9293 fail("Didn't find name")
9394 return -1
9495 }
@@ -103,8 +104,8 @@ Item {
103104
104105 function test_cycle_data() {
105106 var data = new Array()
106− for (var i = 0; i < greeter.model.count; i++) {
107− data[i] = {tag: greeter.model.data(i, LightDM.UserRoles.NameRole), uid: i }
107+ for (var i = 0; i < LightDM.Users.count; i++) {
108+ data[i] = {tag: LightDM.Users.data(i, LightDM.UserRoles.NameRole), uid: i }
108109 }
109110 return data
110111 }
@@ -160,9 +161,9 @@ Item {
160161 }
161162
162163 function test_empty_name() {
163− for (var i = 0; i < greeter.model.count; i++) {
164− if (greeter.model.data(i, LightDM.UserRoles.NameRole) == "empty-name") {
165− compare(greeter.model.data(i, LightDM.UserRoles.RealNameRole), greeter.model.data(i, LightDM.UserRoles.NameRole))
164+ for (var i = 0; i < LightDM.Users.count; i++) {
165+ if (LightDM.Users.data(i, LightDM.UserRoles.NameRole) == "empty-name") {
166+ compare(LightDM.Users.data(i, LightDM.UserRoles.RealNameRole), LightDM.Users.data(i, LightDM.UserRoles.NameRole))
166167 return
167168 }
168169 }
@@ -314,12 +315,12 @@ Item {
314315
315316 function test_bg_color() {
316317 var index = select_user("color-background")
317− compare(greeter.model.data(index, LightDM.UserRoles.BackgroundPathRole), "data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>")
318+ compare(LightDM.Users.data(index, LightDM.UserRoles.BackgroundPathRole), "data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>")
318319 }
319320
320321 function test_bg_none() {
321322 var index = select_user("no-background")
322− compare(greeter.model.data(index, LightDM.UserRoles.BackgroundPathRole), "")
323+ compare(LightDM.Users.data(index, LightDM.UserRoles.BackgroundPathRole), "")
323324 }
324325
325326 function test_teasingArea_data() {
@@ -371,7 +372,7 @@ Item {
371372 tryCompare(selectionSpy, "count", 1)
372373
373374 var userlist = findChild(greeter, "userList")
374− compare(greeter.model.data(userlist.currentIndex, LightDM.UserRoles.NameRole), "has-password")
375+ compare(LightDM.Users.data(userlist.currentIndex, LightDM.UserRoles.NameRole), "has-password")
375376 }
376377
377378 function test_initial_selected_signal() {
tests/qmltests/tst_Shell.qml+24−21
@@ -87,7 +87,7 @@ Item {
8787
8888 var greeter = testCase.findChild(shellLoader.item, "greeter");
8989 if (!greeter.shown) {
90− greeter.show();
90+ LightDM.Greeter.showGreeter();
9191 }
9292 }
9393 }
@@ -323,20 +323,20 @@ Item {
323323 // Suspend while call is active...
324324 callManager.foregroundCall = phoneCall;
325325 Powerd.status = Powerd.Off;
326− tryCompare(greeter, "showProgress", 0);
326+ tryCompare(greeter, "shown", false);
327327
328328 // Now try again after ending call
329329 callManager.foregroundCall = null;
330330 Powerd.status = Powerd.On;
331331 Powerd.status = Powerd.Off;
332− tryCompare(greeter, "showProgress", 1);
332+ tryCompare(greeter, "fullyShown", true);
333333
334334 tryCompare(ApplicationManager, "suspended", true);
335335 compare(mainApp.state, ApplicationInfoInterface.Suspended);
336336
337337 // And wake up
338338 Powerd.status = Powerd.On;
339− tryCompare(greeter, "showProgress", 1);
339+ tryCompare(greeter, "fullyShown", true);
340340
341341 // Swipe away greeter to focus app
342342 swipeAwayGreeter();
@@ -347,14 +347,14 @@ Item {
347347
348348 function swipeAwayGreeter() {
349349 var greeter = findChild(shell, "greeter");
350− tryCompare(greeter, "showProgress", 1);
350+ tryCompare(greeter, "fullyShown", true);
351351
352352 var touchX = shell.width - (shell.edgeSize / 2);
353353 var touchY = shell.height / 2;
354354 touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY);
355355
356356 // wait until the animation has finished
357− tryCompare(greeter, "showProgress", 0);
357+ tryCompare(greeter, "shown", false);
358358 waitForRendering(greeter);
359359 }
360360
@@ -559,12 +559,12 @@ Item {
559559
560560 waitUntilDashIsFocused();
561561
562− greeter.show();
563− tryCompare(greeter, "showProgress", 1);
562+ LightDM.Greeter.showGreeter();
563+ tryCompare(greeter, "fullyShown", true);
564564
565565 // The main point of this test
566566 ApplicationManager.requestFocusApplication("dialer-app");
567− tryCompare(greeter, "showProgress", 0);
567+ tryCompare(greeter, "shown", false);
568568 waitForRendering(greeter);
569569 }
570570
@@ -582,24 +582,22 @@ Item {
582582
583583 function test_showAndHideGreeterDBusCalls() {
584584 var greeter = findChild(shell, "greeter")
585− tryCompare(greeter, "showProgress", 0)
585+ tryCompare(greeter, "shown", false)
586586 waitForRendering(greeter);
587587 LightDM.Greeter.showGreeter()
588588 waitForRendering(greeter)
589− tryCompare(greeter, "showProgress", 1)
589+ tryCompare(greeter, "fullyShown", true)
590590 LightDM.Greeter.hideGreeter()
591− tryCompare(greeter, "showProgress", 0)
591+ tryCompare(greeter, "shown", false)
592592 }
593593
594594 function test_login() {
595− sessionSpy.clear()
596−
597595 var greeter = findChild(shell, "greeter")
598596 waitForRendering(greeter)
599− greeter.show()
600− tryCompare(greeter, "showProgress", 1)
597+ LightDM.Greeter.showGreeter();
598+ tryCompare(greeter, "fullyShown", true)
601599
602− tryCompare(sessionSpy, "count", 0)
600+ sessionSpy.clear();
603601 swipeAwayGreeter()
604602 tryCompare(sessionSpy, "count", 1)
605603 }
@@ -640,7 +638,8 @@ Item {
640638
641639 function test_unlockedProperties() {
642640 // Confirm that various properties have the correct values when unlocked
643− tryCompare(shell, "locked", false)
641+ var greeter = findChild(shell, "greeter");
642+ tryCompare(greeter, "locked", false);
644643
645644 var launcher = findChild(shell, "launcher")
646645 tryCompare(launcher, "available", true)
@@ -662,14 +661,18 @@ Item {
662661
663662 function test_leftEdgeDragOnGreeter(data) {
664663 var greeter = findChild(shell, "greeter");
665− greeter.show();
666− tryCompare(greeter, "showProgress", 1);
664+ LightDM.Greeter.showGreeter();
665+ tryCompare(greeter, "fullyShown", true);
667666
668667 var touchStartX = 2;
669668 var touchStartY = shell.height / 2;
670669 touchFlick(shell, touchStartX, touchStartY, data.targetX, touchStartY);
671670
672− tryCompare(greeter, "showProgress", data.unlocked ? 0 : 1);
671+ if (data.unlocked) {
672+ tryCompare(greeter, "shown", false);
673+ } else {
674+ tryCompare(greeter, "fullyShown", true);
675+ }
673676 }
674677
675678 function test_tapOnRightEdgeReachesApplicationSurface() {
tests/qmltests/tst_ShellWithPin.qml+42−35
@@ -66,7 +66,6 @@ Item {
6666 Component.onDestruction: {
6767 shellLoader.itemDestroyed = true
6868 }
69− maxFailedLogins: maxRetriesTextField.text
7069 }
7170 }
7271 }
@@ -100,6 +99,10 @@ Item {
10099 TextField {
101100 id: maxRetriesTextField
102101 text: "-1"
102+ onTextChanged: {
103+ var greeter = testCase.findChild(shellLoader.item, "greeter");
104+ greeter.maxFailedLogins = text;
105+ }
103106 }
104107 }
105108 }
@@ -130,11 +133,12 @@ Item {
130133
131134 function init() {
132135 tryCompare(shell, "enabled", true); // will be enabled when greeter is all ready
133− sessionSpy.target = findChild(shell, "greeter")
134− swipeAwayGreeter()
136+ var greeter = findChild(shell, "greeter");
137+ sessionSpy.target = greeter;
138+ swipeAwayGreeter(true);
135139 waitForLockscreen()
136− shell.failedLoginsDelayAttempts = -1
137− maxRetriesTextField.text = "-1"
140+ greeter.failedLoginsDelayAttempts = -1;
141+ greeter.maxFailedLogins = -1;
138142 }
139143
140144 function cleanup() {
@@ -178,17 +182,20 @@ Item {
178182 compare(ApplicationManager.count, 1)
179183 }
180184
181− function swipeAwayGreeter() {
185+ function swipeAwayGreeter(waitForCoverPage) {
182186 var greeter = findChild(shell, "greeter");
183187 waitForRendering(greeter)
184− tryCompare(greeter, "showProgress", 1);
188+ tryCompare(greeter, "fullyShown", true);
185189
186190 var touchX = shell.width - (shell.edgeSize / 2);
187191 var touchY = shell.height / 2;
188192 touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY);
189193
190− // wait until the animation has finished
191− tryCompare(greeter, "showProgress", 0);
194+ if (waitForCoverPage) {
195+ // wait until the animation has finished
196+ var coverPage = findChild(shell, "coverPage");
197+ tryCompare(coverPage, "showProgress", 0);
198+ }
192199 }
193200
194201 function waitForLockscreen() {
@@ -209,9 +216,7 @@ Item {
209216
210217 function confirmLockedApp(app) {
211218 var greeter = findChild(shell, "greeter")
212− var lockscreen = findChild(shell, "lockscreen")
213219 tryCompare(greeter, "shown", false)
214− tryCompare(lockscreen, "shown", false)
215220 tryCompare(greeter, "hasLockedApp", true)
216221 tryCompare(greeter, "lockedApp", app)
217222 tryCompare(LightDM.Greeter, "active", true)
@@ -234,13 +239,16 @@ Item {
234239 AccountsService.demoEdges = true
235240 tryCompare(lockscreen, "shown", false)
236241
237− swipeAwayGreeter()
242+ var greeter = findChild(shell, "greeter");
243+ swipeAwayGreeter(false);
244+ tryCompare(greeter, "shown", false);
238245 tryCompare(sessionSpy, "count", 1)
239246
240247 // Lockscreen is only hidden by the edge demo, so if we turn that
241248 // off and show greeter again, lockscreen should appear
242249 AccountsService.demoEdges = false
243250 LightDM.Greeter.showGreeter()
251+ lockscreen = findChild(shell, "lockscreen");
244252 tryCompare(lockscreen, "shown", true)
245253 }
246254
@@ -258,18 +266,16 @@ Item {
258266
259267 function test_emergencyCall() {
260268 var greeter = findChild(shell, "greeter")
261− var lockscreen = findChild(shell, "lockscreen")
262− var emergencyButton = findChild(lockscreen, "emergencyCallLabel")
263269 var panel = findChild(shell, "panel")
264270 var indicators = findChild(shell, "indicators")
265271 var launcher = findChild(shell, "launcher")
266272 var stage = findChild(shell, "stage")
267273
268− tap(emergencyButton)
274+ tap(findChild(greeter, "emergencyCallLabel"));
269275
270276 tryCompare(greeter, "lockedApp", "dialer-app")
271277 tryCompare(greeter, "hasLockedApp", true)
272− tryCompare(lockscreen, "shown", false)
278+ tryCompare(greeter, "shown", false);
273279 tryCompare(panel, "fullscreenMode", true)
274280 tryCompare(indicators, "available", false)
275281 tryCompare(launcher, "available", false)
@@ -282,7 +288,7 @@ Item {
282288 tryCompare(greeter, "shown", true)
283289 tryCompare(greeter, "lockedApp", "")
284290 tryCompare(greeter, "hasLockedApp", false)
285− tryCompare(lockscreen, "shown", true)
291+ tryCompare(greeter, "fullyShown", true);
286292 tryCompare(panel, "fullscreenMode", false)
287293 tryCompare(indicators, "available", true)
288294 tryCompare(launcher, "available", true)
@@ -290,23 +296,25 @@ Item {
290296 }
291297
292298 function test_emergencyCallCrash() {
293− var lockscreen = findChild(shell, "lockscreen")
294− var emergencyButton = findChild(lockscreen, "emergencyCallLabel")
299+ var greeter = findChild(shell, "greeter");
300+ var emergencyButton = findChild(greeter, "emergencyCallLabel");
295301 tap(emergencyButton)
296302
297− tryCompare(lockscreen, "shown", false)
303+ tryCompare(greeter, "shown", false);
298304 killApps() // kill dialer-app, as if it crashed
299− tryCompare(lockscreen, "shown", true)
305+ tryCompare(greeter, "shown", true);
306+ tryCompare(findChild(greeter, "lockscreen"), "shown", true);
307+ tryCompare(findChild(greeter, "coverPage"), "shown", false);
300308 }
301309
302310 function test_emergencyCallAppLaunch() {
303− var lockscreen = findChild(shell, "lockscreen")
304− var emergencyButton = findChild(lockscreen, "emergencyCallLabel")
311+ var greeter = findChild(shell, "greeter");
312+ var emergencyButton = findChild(greeter, "emergencyCallLabel");
305313 tap(emergencyButton)
306314
307− tryCompare(lockscreen, "shown", false)
315+ tryCompare(greeter, "shown", false);
308316 ApplicationManager.startApplication("gallery-app", ApplicationManager.NoFlag)
309− tryCompare(lockscreen, "shown", true)
317+ tryCompare(greeter, "shown", true);
310318 }
311319
312320 function test_failedLoginsCount() {
@@ -320,7 +328,8 @@ Item {
320328 }
321329
322330 function test_wrongEntries() {
323− shell.failedLoginsDelayAttempts = 3
331+ var greeter = findChild(shell, "greeter");
332+ greeter.failedLoginsDelayAttempts = 3;
324333
325334 var placeHolder = findChild(shell, "wrongNoticeLabel")
326335 tryCompare(placeHolder, "text", "")
@@ -334,7 +343,7 @@ Item {
334343 var lockscreen = findChild(shell, "lockscreen")
335344 tryCompare(lockscreen, "delayMinutes", 0)
336345 enterPin("1111")
337− tryCompare(lockscreen, "delayMinutes", shell.failedLoginsDelayMinutes)
346+ tryCompare(lockscreen, "delayMinutes", greeter.failedLoginsDelayMinutes);
338347 }
339348
340349 function test_factoryReset() {
@@ -380,7 +389,7 @@ Item {
380389 // And when we kill the app, we go back to locked tablet mode
381390 killApps()
382391 var greeter = findChild(shell, "greeter")
383− tryCompare(greeter, "showProgress", 1)
392+ tryCompare(greeter, "fullyShown", true)
384393 tryCompare(shell, "sideStageEnabled", true)
385394 tryCompare(applicationsDisplayLoader, "tabletMode", true)
386395 }
@@ -401,7 +410,6 @@ Item {
401410 var lockscreen = findChild(shell, "lockscreen");
402411
403412 lockscreen.emergencyCall();
404−console.log("MIKE first locked app");
405413 confirmLockedApp("dialer-app");
406414 callManager.foregroundCall = phoneCall;
407415
@@ -412,7 +420,6 @@ console.log("MIKE first locked app");
412420 // simulate a callHint press, the real thing requires dialer: url support
413421 ApplicationManager.requestFocusApplication("dialer-app");
414422
415−console.log("MIKE second locked app");
416423 confirmLockedApp("dialer-app");
417424 }
418425
@@ -425,11 +432,11 @@ console.log("MIKE second locked app");
425432 // - Should be back in normal dialer
426433 // (we've had a bug where we locked screen in this case)
427434
428− var lockscreen = findChild(shell, "lockscreen");
435+ var greeter = findChild(shell, "greeter");
429436 var panel = findChild(shell, "panel");
430437
431438 enterPin("1234");
432− tryCompare(lockscreen, "shown", false);
439+ tryCompare(greeter, "shown", false);
433440 tryCompare(LightDM.Greeter, "active", false);
434441
435442 ApplicationManager.startApplication("dialer-app", ApplicationManager.NoFlag);
@@ -444,7 +451,7 @@ console.log("MIKE second locked app");
444451 ApplicationManager.requestFocusApplication("dialer-app");
445452
446453 tryCompare(ApplicationManager, "focusedApplicationId", "dialer-app");
447− tryCompare(lockscreen, "shown", false);
454+ tryCompare(greeter, "shown", false);
448455 tryCompare(LightDM.Greeter, "active", false);
449456 }
450457
@@ -459,10 +466,10 @@ console.log("MIKE second locked app");
459466
460467 // And wake up
461468 Powerd.status = Powerd.On;
462− tryCompare(greeter, "showProgress", 1);
469+ tryCompare(greeter, "fullyShown", true);
463470
464471 // Swipe away greeter to focus app
465− swipeAwayGreeter();
472+ swipeAwayGreeter(true);
466473
467474 // We have a lockscreen, make sure we're still suspended
468475 tryCompare(ApplicationManager, "suspended", true);
tests/qmltests/tst_TabletShell.qml+45−42
@@ -28,9 +28,10 @@ import Powerd 0.1
2828
2929import "../../qml"
3030
31−Row {
31+Item {
3232 id: root
33− spacing: 0
33+ width: units.gu(120)
34+ height: units.gu(80)
3435
3536 QtObject {
3637 id: applicationArguments
@@ -48,43 +49,45 @@ Row {
4849 }
4950 }
5051
51− Loader {
52− id: shellLoader
52+ Row {
53+ anchors.fill: parent
54+ Loader {
55+ id: shellLoader
56+ width: units.gu(100)
57+ height: parent.height
5358
54− width: units.gu(100)
55− height: units.gu(80)
59+ property bool itemDestroyed: false
60+ sourceComponent: Component {
61+ Shell {
62+ property string indicatorProfile: "phone"
5663
57− property bool itemDestroyed: false
58− sourceComponent: Component {
59− Shell {
60− property string indicatorProfile: "phone"
61−
62− Component.onDestruction: {
63− shellLoader.itemDestroyed = true
64+ Component.onDestruction: {
65+ shellLoader.itemDestroyed = true
66+ }
6467 }
6568 }
6669 }
67− }
6870
69− Rectangle {
70− color: "white"
71− width: units.gu(20)
72− height: shellLoader.height
73−
74− Column {
75− anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
76− spacing: units.gu(1)
77− Row {
78− anchors { left: parent.left; right: parent.right }
79− Button {
80− text: "Show Greeter"
81− onClicked: {
82− if (shellLoader.status !== Loader.Ready)
83− return
84−
85− var greeter = testCase.findChild(shellLoader.item, "greeter")
86− if (!greeter.shown) {
87− greeter.show()
71+ Rectangle {
72+ color: "white"
73+ width: units.gu(20)
74+ height: parent.height
75+
76+ Column {
77+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
78+ spacing: units.gu(1)
79+ Row {
80+ anchors { left: parent.left; right: parent.right }
81+ Button {
82+ text: "Show Greeter"
83+ onClicked: {
84+ if (shellLoader.status !== Loader.Ready)
85+ return
86+
87+ var greeter = testCase.findChild(shellLoader.item, "greeter")
88+ if (!greeter.shown) {
89+ greeter.show()
90+ }
8891 }
8992 }
9093 }
@@ -187,12 +190,12 @@ Row {
187190 function selectUser(name) {
188191 // Find index of user with the right name
189192 var greeter = findChild(shell, "greeterContentLoader")
190− for (var i = 0; i < greeter.model.count; i++) {
191− if (greeter.model.data(i, LightDM.UserRoles.NameRole) == name) {
193+ for (var i = 0; i < LightDM.Users.count; i++) {
194+ if (LightDM.Users.data(i, LightDM.UserRoles.NameRole) == name) {
192195 break
193196 }
194197 }
195− if (i == greeter.model.count) {
198+ if (i == LightDM.Users.count) {
196199 fail("Didn't find name")
197200 return -1
198201 }
@@ -202,7 +205,7 @@ Row {
202205
203206 function clickPasswordInput(isButton) {
204207 var greeter = findChild(shell, "greeter")
205− tryCompare(greeter, "showProgress", 1)
208+ tryCompare(greeter, "fullyShown", true);
206209
207210 var passwordMouseArea = findChild(shell, "passwordMouseArea")
208211 tryCompare(passwordMouseArea, "enabled", isButton)
@@ -213,8 +216,8 @@ Row {
213216
214217 function confirmLoggedIn(loggedIn) {
215218 var greeter = findChild(shell, "greeter");
216− tryCompare(greeter, "showProgress", loggedIn ? 0 : 1);
217− tryCompare(sessionSpy, "count", loggedIn ? 1 : 0)
219+ tryCompare(greeter, "shown", loggedIn ? false : true);
220+ verify(loggedIn ? sessionSpy.count > 0 : sessionSpy.count === 0);
218221 }
219222
220223 function swipeFromLeftEdge(swipeLength) {
@@ -226,15 +229,15 @@ Row {
226229 function test_noLockscreen() {
227230 selectUser("has-password")
228231 var lockscreen = findChild(shell, "lockscreen")
229− tryCompare(lockscreen, "shown", false)
232+ compare(lockscreen, null);
230233 }
231234
232235 function test_showAndHideGreeterDBusCalls() {
233236 var greeter = findChild(shell, "greeter")
234237 LightDM.Greeter.hideGreeter()
235− tryCompare(greeter, "showProgress", 0)
238+ tryCompare(greeter, "shown", false);
236239 LightDM.Greeter.showGreeter()
237− tryCompare(greeter, "showProgress", 1)
240+ tryCompare(greeter, "fullyShown", true);
238241 }
239242
240243 function test_login_data() {