on xenial help sign in
Fix tablet greeter focus to be always-on, no longer hiding the OSK or forcing a mouse click to type a password.

I've also squeezed some other small fixes in: - Support Up and Down keys in user list. - Don't accept non-digit characters when prompting for passcode (we ask OSK to show only digits, but due to bug 1586435, it shows other characters too; plus if the user has an external keyboard, they can type anything anyway). - When prompting for a passcode in wide-view (tablet/desktop), stop at 4 digits just like we do in narrow-view. - Don't show "Retry" during brief period before a prompt comes in from PAM. - Don't let user drag user list if there's only one entry. Now as for the focus changes... One of the big reasons we lost keyboard focus before was because we set the shell to disabled whenever the greeter was "between PAM events" -- mostly so that the user can't swipe away greeter before we get our first prompt or are otherwise unsure about exactly what authentication is needed. But disabled qml items can't be focused. So I've rejiggered things a bit. Instead of disabling the whole shell, I just disable the launcher, the indicators, and make the greeter non-swipeable. I also do some tricks with the prompt so that it looks disabled (while we check with PAM about the password) but isn't actually. I don't want to make it look to the user that pressing backspace while they wait for PAM does anything, so after the user presses Enter, I present a fake label on top of the prompt that looks like a disabled prompt, while simultaneously hiding the prompt contents. (LP: #1435923)

commitbfda9dba33dfc4c46d3896edf9ea117b1cbf2dc8 authorMichael Terry <michael.terry@canonical.com> dateMon 11 Jul 2016, 17:23 (10 years ago) parentdc1104512d5260
14 files changed+248−97
qml/Greeter/Greeter.qml+28−24 qml/Greeter/GreeterPrompt.qml+67−12 qml/Greeter/LoginList.qml+27−16 qml/Greeter/NarrowView.qml+2−1 qml/Greeter/WideView.qml+5−4 qml/Shell.qml+5−3 tests/qmltests/Dash/tst_DashShell.qml+6 tests/qmltests/Greeter/TestView.qml−1 tests/qmltests/Greeter/tst_Greeter.qml+3−13 tests/qmltests/Greeter/tst_WideView.qml+92−12 tests/qmltests/Tutorial/tst_Tutorial.qml+1−1 tests/qmltests/tst_OrientedShell.qml+2−2 tests/qmltests/tst_Shell.qml+3−3 tests/qmltests/tst_ShellWithPin.qml+7−5
qml/Greeter/Greeter.qml+28−24
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013,2014,2015 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -166,31 +166,37 @@ Showable {
166166 return -1;
167167 }
168168
169− function selectUser(uid, reset) {
170− if (uid < 0)
169+ function selectUser(index, reset) {
170+ if (index < 0 || index >= LightDMService.users.count)
171171 return;
172172 d.waiting = true;
173173 if (reset) {
174174 loader.item.reset();
175175 }
176− currentIndex = uid;
177− var user = LightDMService.users.data(uid, LightDMService.userRoles.NameRole);
176+ currentIndex = index;
177+ var user = LightDMService.users.data(index, LightDMService.userRoles.NameRole);
178178 AccountsService.user = user;
179179 LauncherModel.setUser(user);
180180 LightDMService.greeter.authenticate(user); // always resets auth state
181181 }
182182
183+ function hideView() {
184+ if (loader.item) {
185+ loader.item.enabled = false; // drop OSK and prevent interaction
186+ loader.item.notifyAuthenticationSucceeded(false /* showFakePassword */);
187+ loader.item.hide();
188+ }
189+ }
190+
183191 function login() {
184− enabled = false;
192+ d.waiting = true;
185193 if (LightDMService.greeter.startSessionSync()) {
186194 sessionStarted();
187− if (loader.item) {
188− loader.item.notifyAuthenticationSucceeded(false /* showFakePassword */);
189− }
195+ hideView();
190196 } else if (loader.item) {
191197 loader.item.notifyAuthenticationFailed();
192198 }
193− enabled = true;
199+ d.waiting = false;
194200 }
195201
196202 function startUnlock(toTheRight) {
@@ -202,10 +208,8 @@ Showable {
202208 }
203209
204210 function checkForcedUnlock(hideNow) {
205− if (forcedUnlock && shown && loader.item) {
206− // pretend we were just authenticated
207− loader.item.notifyAuthenticationSucceeded(false /* showFakePassword */);
208− loader.item.hide();
211+ if (forcedUnlock && shown) {
212+ hideView();
209213 if (hideNow) {
210214 root.hideNow(); // skip hide animation
211215 }
@@ -326,7 +330,7 @@ Showable {
326330
327331 onLoaded: {
328332 root.lockedApp = "";
329− root.forceActiveFocus();
333+ item.forceActiveFocus();
330334 d.selectUser(d.currentIndex, true);
331335 LightDMService.infographic.readyForDataChange();
332336 }
@@ -336,13 +340,11 @@ Showable {
336340 onSelected: {
337341 d.selectUser(index, true);
338342 }
339− onPromptlessLogin: d.login();
340343 onResponded: {
341344 if (root.locked) {
342345 LightDMService.greeter.respond(response);
343346 } else {
344347 d.login();
345− loader.item.hide();
346348 }
347349 }
348350 onTease: root.tease()
@@ -390,6 +392,12 @@ Showable {
390392 value: root.locked
391393 }
392394
395+ Binding {
396+ target: loader.item
397+ property: "waiting"
398+ value: d.waiting
399+ }
400+
393401 Binding {
394402 target: loader.item
395403 property: "alphanumeric"
@@ -420,10 +428,7 @@ Showable {
420428
421429 onShowGreeter: root.forceShow()
422430
423− onHideGreeter: {
424− d.login();
425− loader.item.hide();
426− }
431+ onHideGreeter: d.login()
427432
428433 onShowMessage: {
429434 // inefficient, but we only rarely deal with messages
@@ -441,11 +446,11 @@ Showable {
441446 }
442447
443448 onShowPrompt: {
444− d.waiting = false;
445−
446449 if (loader.item) {
447450 loader.item.showPrompt(text, isSecret, isDefaultPrompt);
448451 }
452+
453+ d.waiting = false;
449454 }
450455
451456 onAuthenticationComplete: {
@@ -454,7 +459,6 @@ Showable {
454459 if (LightDMService.greeter.authenticated) {
455460 if (!LightDMService.greeter.promptless) {
456461 d.login();
457− loader.item.hide();
458462 }
459463 } else {
460464 if (!LightDMService.greeter.promptless) {
qml/Greeter/GreeterPrompt.qml+67−12
@@ -16,6 +16,7 @@
1616
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3
19+import "../Components"
1920
2021FocusScope {
2122 id: root
@@ -33,6 +34,7 @@ FocusScope {
3334
3435 function reset() {
3536 passwordInput.text = "";
37+ fakeLabel.text = "";
3638 d.enabled = true;
3739 }
3840
@@ -58,14 +60,28 @@ FocusScope {
5860 color: "transparent"
5961 }
6062
63+ Component.onCompleted: updateFocus()
64+ onIsPromptChanged: updateFocus()
65+ function updateFocus() {
66+ if (root.isPrompt) {
67+ passwordInput.focus = true;
68+ } else {
69+ promptButton.focus = true;
70+ }
71+ }
72+
6173 AbstractButton {
74+ id: promptButton
6275 objectName: "promptButton"
6376 anchors.fill: parent
6477 visible: !root.isPrompt
65− enabled: d.enabled
66− focus: visible
6778
68− onClicked: root.clicked()
79+ onClicked: {
80+ if (d.enabled) {
81+ d.enabled = false;
82+ root.clicked();
83+ }
84+ }
6985
7086 Label {
7187 anchors.centerIn: parent
@@ -79,10 +95,15 @@ FocusScope {
7995 objectName: "promptField"
8096 anchors.fill: parent
8197 visible: root.isPrompt
82− enabled: d.enabled
83− focus: visible
98+ opacity: fakeLabel.visible ? 0 : 1
99+
100+ validator: RegExpValidator {
101+ regExp: root.isAlphanumeric ? /^.*$/ : /^\d{4}$/
102+ }
84103
85− inputMethodHints: root.isAlphanumeric ? Qt.ImhNone : Qt.ImhDigitsOnly
104+ inputMethodHints: Qt.ImhSensitiveData | Qt.ImhNoPredictiveText |
105+ Qt.ImhMultiLine | // so OSK doesn't close on Enter
106+ (root.isAlphanumeric ? Qt.ImhNone : Qt.ImhDigitsOnly)
86107 echoMode: root.isSecret ? TextInput.Password : TextInput.Normal
87108 hasClearButton: false
88109
@@ -107,17 +128,35 @@ FocusScope {
107128 width: units.gu(3)
108129 color: d.textColor
109130 visible: root.isSecret && false // TODO: detect when caps lock is on
131+ readonly property real visibleWidth: visible ? width + passwordInput.frameSpacing : 0
110132 }
111133 ]
112134
135+ onDisplayTextChanged: {
136+ // We use onDisplayTextChanged instead of onTextChanged because
137+ // displayText changes after text and if we did this before it
138+ // updated, we would use the wrong displayText for fakeLabel.
139+ if (!isAlphanumeric && text.length >= 4) {
140+ // hard limit of 4 for passcodes right now
141+ respond();
142+ }
143+ }
144+
113145 onAccepted: {
114− if (!enabled)
115− return;
146+ if (d.enabled)
147+ respond();
148+ }
149+
150+ function respond() {
116151 d.enabled = false;
152+ fakeLabel.text = displayText;
117153 root.responded(text);
118154 }
119155
120− Keys.onEscapePressed: root.canceled()
156+ Keys.onEscapePressed: {
157+ root.canceled();
158+ event.accepted = true;
159+ }
121160
122161 // We use our own custom placeholder label instead of the standard
123162 // TextField one because the standard one hardcodes baseText as the
@@ -129,9 +168,7 @@ FocusScope {
129168 right: parent.right
130169 verticalCenter: parent.verticalCenter
131170 leftMargin: units.gu(1.5)
132− rightMargin: anchors.leftMargin +
133− (capsIcon.visible ? capsIcon.width + passwordInput.frameSpacing
134− : 0)
171+ rightMargin: anchors.leftMargin + capsIcon.visibleWidth
135172 }
136173 text: root.text
137174 visible: passwordInput.text == "" && !passwordInput.inputMethodComposing
@@ -139,4 +176,22 @@ FocusScope {
139176 elide: Text.ElideRight
140177 }
141178 }
179+
180+ // Have a fake label that covers the text field after the user presses
181+ // enter. What we *really* want is a disabled mode that doesn't lose OSK
182+ // focus. Because our goal here is simply to keep the OSK up while
183+ // we wait for PAM to get back to us, and while waiting, we don't want
184+ // the user to be able to edit the field (simply because it would look
185+ // weird if we allowed that). But until we have such a disabled mode,
186+ // we'll fake it by covering the real text field with a label.
187+ FadingLabel {
188+ id: fakeLabel
189+ anchors.verticalCenter: parent.verticalCenter
190+ anchors.left: parent.left
191+ anchors.right: parent.right
192+ anchors.leftMargin: passwordInput.frameSpacing * 2
193+ anchors.rightMargin: passwordInput.frameSpacing * 2 + capsIcon.visibleWidth
194+ color: d.drawColor
195+ visible: root.isPrompt && !d.enabled
196+ }
142197}
qml/Greeter/LoginList.qml+27−16
@@ -21,11 +21,13 @@ import "../Components"
2121
2222StyledItem {
2323 id: root
24+ focus: true
2425
2526 property alias model: userList.model
2627 property bool alphanumeric: true
2728 property int currentIndex
2829 property bool locked
30+ property bool waiting
2931
3032 readonly property int numAboveBelow: 4
3133 readonly property int cellHeight: units.gu(5)
@@ -36,7 +38,6 @@ StyledItem {
3638
3739 signal selected(int index)
3840 signal responded(string response)
39− signal promptlessLogin()
4041
4142 function tryToUnlock() {
4243 if (wasPrompted) {
@@ -45,7 +46,6 @@ StyledItem {
4546 if (root.locked) {
4647 root.selected(currentIndex);
4748 } else {
48− promptlessLogin();
4949 root.responded("");
5050 }
5151 }
@@ -60,20 +60,18 @@ StyledItem {
6060 }
6161
6262 function showPrompt(text, isSecret, isDefaultPrompt) {
63− d.promptText = text;
64− passwordInput.reset();
63+ passwordInput.text = isDefaultPrompt ? alphanumeric ? i18n.tr("Passphrase")
64+ : i18n.tr("Passcode")
65+ : text;
66+ passwordInput.isPrompt = true;
6567 passwordInput.isSecret = isSecret;
66− if (wasPrompted) // stay in text field if second prompt
67− passwordInput.focus = true;
68+ passwordInput.reset();
6869 wasPrompted = true;
6970 }
7071
7172 function showError() {
7273 wrongPasswordAnimation.start();
7374 root.resetAuthentication();
74− if (wasPrompted) {
75− passwordInput.focus = true;
76− }
7775 }
7876
7977 function reset() {
@@ -83,15 +81,33 @@ StyledItem {
8381 QtObject {
8482 id: d
8583
86− property string promptText
84+ function checkIfPromptless() {
85+ if (!waiting && !wasPrompted) {
86+ passwordInput.isPrompt = false;
87+ passwordInput.text = root.locked ? i18n.tr("Retry")
88+ : i18n.tr("Log In")
89+ }
90+ }
8791 }
8892
93+ onWaitingChanged: d.checkIfPromptless()
94+ onLockedChanged: d.checkIfPromptless()
95+
8996 theme: ThemeSettings {
9097 name: "Ubuntu.Components.Themes.Ambiance"
9198 }
9299
100+ Keys.onUpPressed: {
101+ selected(currentIndex - 1);
102+ event.accepted = true;
103+ }
104+ Keys.onDownPressed: {
105+ selected(currentIndex + 1);
106+ event.accepted = true;
107+ }
93108 Keys.onEscapePressed: {
94109 selected(currentIndex);
110+ event.accepted = true;
95111 }
96112
97113 onCurrentIndexChanged: {
@@ -137,6 +153,7 @@ StyledItem {
137153 highlightRangeMode: ListView.StrictlyEnforceRange
138154 highlightMoveDuration: root.moveDuration
139155 flickDeceleration: 10000
156+ interactive: count > 1
140157
141158 readonly property bool movingInternally: moveTimer.running || userList.moving
142159 onMovingInternallyChanged: {
@@ -254,13 +271,8 @@ StyledItem {
254271 width: highlightItem.width - anchors.margins * 2
255272 opacity: userList.movingInternally ? 0 : 1
256273
257− isPrompt: root.wasPrompted
258274 isAlphanumeric: root.alphanumeric
259275
260− text: root.wasPrompted ? d.promptText
261− : (root.locked ? i18n.tr("Retry")
262− : i18n.tr("Log In"))
263−
264276 onClicked: root.tryToUnlock()
265277 onResponded: root.responded(text)
266278 onCanceled: root.selected(currentIndex)
@@ -280,7 +292,6 @@ StyledItem {
280292 return;
281293 }
282294 infoLabel.text = "";
283− d.promptText = "";
284295 passwordInput.reset();
285296 root.wasPrompted = false;
286297 }
qml/Greeter/NarrowView.qml+2−1
@@ -31,6 +31,7 @@ FocusScope {
3131 property bool alphanumeric
3232 property var userModel // unused
3333 property alias infographicModel: coverPage.infographicModel
34+ property bool waiting
3435 readonly property bool fullyShown: coverPage.showProgress === 1 || lockscreen.shown
3536 readonly property bool required: coverPage.required || lockscreen.required
3637 readonly property bool animating: coverPage.showAnimation.running || coverPage.hideAnimation.running
@@ -71,7 +72,6 @@ FocusScope {
7172 if (!alphanumeric && showFakePassword) {
7273 lockscreen.showText("...."); // actual text doesn't matter, we show bullets
7374 }
74− lockscreen.hide();
7575 }
7676
7777 function notifyAuthenticationFailed() {
@@ -172,6 +172,7 @@ FocusScope {
172172 height: parent.height
173173 width: parent.width
174174 background: root.background
175+ draggable: !root.waiting
175176 onTease: root.tease()
176177 onClicked: hide()
177178
qml/Greeter/WideView.qml+5−4
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2015-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -19,6 +19,7 @@ import Ubuntu.Components 1.3
1919
2020FocusScope {
2121 id: root
22+ focus: true
2223
2324 property alias dragHandleLeftMargin: coverPage.dragHandleLeftMargin
2425 property alias launcherOffset: coverPage.launcherOffset
@@ -30,6 +31,7 @@ FocusScope {
3031 property alias alphanumeric: loginList.alphanumeric
3132 property alias userModel: loginList.model
3233 property alias infographicModel: coverPage.infographicModel
34+ property bool waiting
3335 readonly property bool fullyShown: coverPage.showProgress === 1
3436 readonly property bool required: coverPage.required
3537 readonly property bool animating: coverPage.showAnimation.running || coverPage.hideAnimation.running
@@ -37,7 +39,6 @@ FocusScope {
3739 // so that it can be replaced in tests with a mock object
3840 property var inputMethod: Qt.inputMethod
3941
40− signal promptlessLogin()
4142 signal selected(int index)
4243 signal responded(string response)
4344 signal tease()
@@ -102,7 +103,7 @@ FocusScope {
102103 objectName: "coverPage"
103104 height: parent.height
104105 width: parent.width
105− draggable: !root.locked
106+ draggable: !root.locked && !root.waiting
106107
107108 infographics {
108109 height: 0.75 * parent.height
@@ -132,8 +133,8 @@ FocusScope {
132133 Behavior on height { UbuntuNumberAnimation {} }
133134
134135 locked: root.locked
136+ waiting: root.waiting
135137
136− onPromptlessLogin: root.promptlessLogin()
137138 onSelected: root.selected(index)
138139 onResponded: root.responded(response)
139140 }
qml/Shell.qml+5−3
@@ -137,9 +137,9 @@ StyledItem {
137137 // For autopilot consumption
138138 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
139139
140− // Disable everything while greeter is waiting, so that the user can't swipe
141− // the greeter or launcher until we know whether the session is locked.
142− enabled: greeter && !greeter.waiting
140+ // Note when greeter is waiting on PAM, so that we can disable edges until
141+ // we know which user data to show and whether the session is locked.
142+ readonly property bool waitingOnGreeter: greeter && greeter.waiting
143143
144144 property real edgeSize: units.gu(settings.edgeDragWidth)
145145
@@ -533,6 +533,7 @@ StyledItem {
533533 available: tutorial.panelEnabled
534534 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
535535 && (!greeter || !greeter.hasLockedApp)
536+ && !shell.waitingOnGreeter
536537 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
537538
538539 minimizedPanelHeight: units.gu(3)
@@ -583,6 +584,7 @@ StyledItem {
583584 available: tutorial.launcherEnabled
584585 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
585586 && !greeter.hasLockedApp
587+ && !shell.waitingOnGreeter
586588 inverted: shell.usageScenario !== "desktop"
587589 superPressed: physicalKeysMapper.superPressed
588590 superTabPressed: physicalKeysMapper.superTabPressed
tests/qmltests/Dash/tst_DashShell.qml+6
@@ -91,6 +91,9 @@ Item {
9191 var stopY = startY;
9292 touchFlick(shell, startX, startY, stopX, stopY);
9393
94+ var greeter = findChild(shell, "greeter");
95+ tryCompare(greeter, "shown", false);
96+
9497 // bring in the launcher
9598 var startX = units.gu(1);
9699 var startY = shell.height / 2;
@@ -131,6 +134,9 @@ Item {
131134 var stopY = startY;
132135 touchFlick(shell, startX, startY, stopX, stopY);
133136
137+ var greeter = findChild(shell, "greeter");
138+ tryCompare(greeter, "shown", false);
139+
134140 // Now do a small launcher movement
135141 touchFlick(shell, stopX, startY, startX, stopY);
136142 compare(dashContentList.currentIndex, 0, "Small launcher move did not reset dash on non active window");
tests/qmltests/Greeter/TestView.qml−1
@@ -43,7 +43,6 @@ Item {
4343 signal responded(string response)
4444 signal tease()
4545 signal emergencyCall()
46− signal promptlessLogin()
4746
4847 signal _showMessageCalled(string html)
4948 signal _showPromptCalled(string text, bool isSecret, bool isDefaultPrompt)
tests/qmltests/Greeter/tst_Greeter.qml+3−13
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2015 Canonical Ltd.
2+ * Copyright 2015-2016 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -194,6 +194,8 @@ Item {
194194 var i = getIndexOf(name);
195195 view.selected(i);
196196 verifySelected(name);
197+ compare(greeter.waiting, true);
198+ tryCompare(greeter, "waiting", false);
197199 return i;
198200 }
199201
@@ -325,18 +327,6 @@ Item {
325327 compare(viewShowMessageSpy.signalArguments[2][0], "You should have seen three messages");
326328 }
327329
328− function test_waiting() {
329− // Make sure we unset 'waiting' on prompt
330− selectUser("has-password");
331− compare(greeter.waiting, true);
332− tryCompare(greeter, "waiting", false);
333−
334− // Make sure we unset 'waiting' on authentication
335− selectUser("no-password");
336− compare(greeter.waiting, true);
337− tryCompare(greeter, "waiting", false);
338− }
339−
340330 function test_locked() {
341331 selectUser("has-password");
342332 compare(view.locked, true);
tests/qmltests/Greeter/tst_WideView.qml+92−12
@@ -26,6 +26,7 @@ StyledItem {
2626 id: root
2727 width: units.gu(120)
2828 height: units.gu(80)
29+ focus: true
2930
3031 theme.name: "Ubuntu.Components.Themes.SuruDark"
3132
@@ -41,6 +42,7 @@ StyledItem {
4142 id: loader
4243 width: root.width - controls.width
4344 height: parent.height
45+ focus: true
4446
4547 property bool itemDestroyed: false
4648 sourceComponent: Component {
@@ -63,7 +65,8 @@ StyledItem {
6365 }
6466
6567 onSelected: {
66− currentIndexField.text = index;
68+ if (index >= 0)
69+ currentIndexField.text = index;
6770 }
6871
6972 QtObject {
@@ -392,7 +395,7 @@ StyledItem {
392395
393396 function test_respondedWithPassword() {
394397 view.locked = true;
395− view.showPrompt("Prompt", true, true);
398+ view.showPrompt("Prompt", true, false);
396399 var passwordInput = findChild(view, "passwordInput");
397400 compare(passwordInput.text, "Prompt");
398401 verify(passwordInput.isSecret);
@@ -454,13 +457,13 @@ StyledItem {
454457 view.showPrompt("Prompt", true, true);
455458 var promptField = findChild(view, "promptField");
456459 tap(promptField);
457− compare(promptField.focus, true);
458− compare(promptField.enabled, true);
460+ verify(promptField.activeFocus);
461+ compare(promptField.opacity, 1);
459462
460463 typeString("password");
461464 keyClick(Qt.Key_Enter);
462− compare(promptField.focus, true);
463− compare(promptField.enabled, false);
465+ verify(promptField.activeFocus);
466+ compare(promptField.opacity, 0); // hidden by fakeLabel
464467
465468 compare(selectedSpy.count, 0);
466469 keyClick(Qt.Key_Escape);
@@ -468,8 +471,8 @@ StyledItem {
468471 compare(selectedSpy.signalArguments[0][0], 1);
469472
470473 view.reset();
471− compare(promptField.focus, false);
472− compare(promptField.enabled, true);
474+ verify(promptField.activeFocus);
475+ compare(promptField.opacity, 1);
473476 }
474477
475478 function test_unicode() {
@@ -489,6 +492,7 @@ StyledItem {
489492 compare(selectedSpy.signalArguments[0][0], 0);
490493 selectedSpy.clear();
491494
495+ view.reset();
492496 view.locked = false;
493497 compare(passwordInput.text, "Log In");
494498 tap(passwordInput);
@@ -518,13 +522,89 @@ StyledItem {
518522 tryCompare(loginList, "height", view.height);
519523 }
520524
521− function test_alphanumeric() {
522− var passwordInput = findChild(view, "passwordInput");
525+ function test_passphrase() {
526+ var promptField = findChild(view, "promptField");
527+ view.showPrompt("", true, true);
523528
524529 verify(view.alphanumeric);
525− verify(passwordInput.isAlphanumeric);
530+ compare(promptField.inputMethodHints & Qt.ImhDigitsOnly, 0);
531+
532+ keyClick(Qt.Key_D);
533+ compare(promptField.text, "d");
534+ }
535+
536+ function test_passcode() {
537+ var promptField = findChild(view, "promptField");
538+ view.showPrompt("", true, true);
539+
526540 view.alphanumeric = false;
527− verify(!passwordInput.isAlphanumeric);
541+ compare(promptField.inputMethodHints & Qt.ImhDigitsOnly, Qt.ImhDigitsOnly);
542+
543+ keyClick(Qt.Key_D);
544+ compare(promptField.text, "");
545+
546+ keyClick(Qt.Key_0);
547+ keyClick(Qt.Key_0);
548+ keyClick(Qt.Key_0);
549+ keyClick(Qt.Key_0);
550+ compare(promptField.text, "0000");
551+
552+ compare(respondedSpy.count, 1);
553+ compare(respondedSpy.signalArguments[0][0], "0000");
554+
555+ compare(promptField.opacity, 0);
556+ }
557+
558+ function test_loginListMovement_data() {
559+ return [
560+ {tag: "up", key: Qt.Key_Up, result: -1},
561+ {tag: "down", key: Qt.Key_Down, result: 1},
562+ ]
563+ }
564+
565+ function test_loginListMovement(data) {
566+ keyClick(data.key);
567+ compare(selectedSpy.count, 1);
568+ compare(selectedSpy.signalArguments[0][0], data.result);
569+ }
570+
571+ function test_focusStaysActive() {
572+ var promptField = findChild(view, "promptField");
573+ var promptButton = findChild(view, "promptButton");
574+
575+ verify(promptButton.activeFocus);
576+ keyClick(Qt.Key_Enter);
577+ compare(selectedSpy.count, 0);
578+ compare(respondedSpy.count, 1);
579+ compare(respondedSpy.signalArguments[0][0], "");
580+ verify(promptButton.activeFocus);
581+ keyClick(Qt.Key_Enter);
582+ compare(respondedSpy.count, 1);
583+
584+ view.showPrompt("", true, true);
585+ verify(promptField.activeFocus);
586+ keyClick(Qt.Key_D);
587+ keyClick(Qt.Key_Enter);
588+ compare(selectedSpy.count, 0);
589+ compare(respondedSpy.count, 2);
590+ compare(respondedSpy.signalArguments[1][0], "d");
591+ verify(promptField.activeFocus);
592+ keyClick(Qt.Key_Enter);
593+ compare(respondedSpy.count, 2);
594+
595+ view.reset();
596+ view.locked = true;
597+ verify(promptButton.activeFocus);
598+ keyClick(Qt.Key_Enter);
599+ compare(respondedSpy.count, 2);
600+ compare(selectedSpy.count, 1);
601+ compare(selectedSpy.signalArguments[0][0], 0);
602+ verify(promptButton.activeFocus);
603+ keyClick(Qt.Key_Enter);
604+ compare(selectedSpy.count, 1);
605+
606+ view.showPrompt("", true, true);
607+ verify(promptField.activeFocus);
528608 }
529609 }
530610}
tests/qmltests/Tutorial/tst_Tutorial.qml+1−1
@@ -296,7 +296,7 @@ Rectangle {
296296 }
297297
298298 function prepareShell() {
299− tryCompare(shell, "enabled", true); // enabled by greeter when ready
299+ tryCompare(shell, "waitingOnGreeter", false); // reset by greeter when ready
300300
301301 WindowStateStorage.clear();
302302 SurfaceManager.inputMethodSurface.setState(Mir.MinimizedState);
tests/qmltests/tst_OrientedShell.qml+2−2
@@ -493,7 +493,7 @@ Rectangle {
493493 }
494494
495495 function cleanup() {
496− tryCompare(shell, "enabled", true); // make sure greeter didn't leave us in disabled state
496+ tryCompare(shell, "waitingOnGreeter", false); // make sure greeter didn't leave us in disabled state
497497 shell = null;
498498 topLevelSurfaceList = null;
499499
@@ -1583,7 +1583,7 @@ Rectangle {
15831583 var stageLoader = findChild(shell, "applicationsDisplayLoader");
15841584 verify(stageLoader);
15851585
1586− tryCompare(shell, "enabled", true); // enabled by greeter when ready
1586+ tryCompare(shell, "waitingOnGreeter", false); // reset by greeter when ready
15871587
15881588 waitUntilShellIsInOrientation(root.physicalOrientation0);
15891589
tests/qmltests/tst_Shell.qml+3−3
@@ -456,7 +456,7 @@ Rectangle {
456456 function cleanup() {
457457 waitForRendering(shell);
458458 mouseEmulation.checked = true;
459− tryCompare(shell, "enabled", true); // make sure greeter didn't leave us in disabled state
459+ tryCompare(shell, "waitingOnGreeter", false); // make sure greeter didn't leave us in disabled state
460460 tearDown();
461461 WindowStateStorage.clear();
462462 }
@@ -466,7 +466,7 @@ Rectangle {
466466 shellLoader.active = true;
467467 tryCompare(shellLoader, "status", Loader.Ready);
468468 removeTimeConstraintsFromSwipeAreas(shellLoader.item);
469− tryCompare(shell, "enabled", true); // enabled by greeter when ready
469+ tryCompare(shell, "waitingOnGreeter", false); // reset by greeter when ready
470470
471471 sessionSpy.target = findChild(shell, "greeter")
472472 dashCommunicatorSpy.target = findInvisibleChild(shell, "dashCommunicator");
@@ -818,7 +818,7 @@ Rectangle {
818818 tryCompare(userlist, "currentIndex", next)
819819 tryCompare(userlist, "movingInternally", false)
820820 }
821− tryCompare(shell, "enabled", true); // wait for PAM to settle
821+ tryCompare(shell, "waitingOnGreeter", false); // wait for PAM to settle
822822 }
823823
824824 function selectUser(name) {
tests/qmltests/tst_ShellWithPin.qml+7−5
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014-2015 Canonical, Ltd.
2+ * Copyright (C) 2014-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -150,7 +150,7 @@ Item {
150150 property Item shell: shellLoader.status === Loader.Ready ? shellLoader.item : null
151151
152152 function init() {
153− tryCompare(shell, "enabled", true); // will be enabled when greeter is all ready
153+ tryCompare(shell, "waitingOnGreeter", false); // will be set when greeter is all ready
154154 var greeter = findChild(shell, "greeter");
155155 sessionSpy.target = greeter;
156156 swipeAwayGreeter(true);
@@ -165,7 +165,7 @@ Item {
165165 }
166166
167167 function cleanup() {
168− tryCompare(shell, "enabled", true); // make sure greeter didn't leave us in disabled state
168+ tryCompare(shell, "waitingOnGreeter", false); // make sure greeter didn't leave us in disabled state
169169
170170 shellLoader.itemDestroyed = false
171171
@@ -533,10 +533,12 @@ Item {
533533
534534 // Confirm that we start disabled
535535 compare(promptSpy.count, 0);
536− verify(!shell.enabled);
536+ verify(shell.waitingOnGreeter);
537+ var coverPageDragHandle = findChild(shell, "coverPageDragHandle");
538+ verify(!coverPageDragHandle.enabled);
537539
538540 // And that we only become enabled once the lockscreen is up
539− tryCompare(shell, "enabled", true);
541+ tryCompare(shell, "waitingOnGreeter", false);
540542 verify(promptSpy.count > 0);
541543 var lockscreen = findChild(shell, "lockscreen");
542544 verify(lockscreen.shown);