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.
bfda9dbMichael Terry
qml/Greeter/Greeter.qml+28−24
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013,2014,2015 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -166,31 +166,37 @@ Showable {
166 return -1;
166 return -1;
167 }
167 }
168
168
169− function selectUser(uid, reset) {
169+ function selectUser(index, reset) {
170− if (uid < 0)
170+ if (index < 0 || index >= LightDMService.users.count)
171 return;
171 return;
172 d.waiting = true;
172 d.waiting = true;
173 if (reset) {
173 if (reset) {
174 loader.item.reset();
174 loader.item.reset();
175 }
175 }
176− currentIndex = uid;
176+ currentIndex = index;
177− var user = LightDMService.users.data(uid, LightDMService.userRoles.NameRole);
177+ var user = LightDMService.users.data(index, LightDMService.userRoles.NameRole);
178 AccountsService.user = user;
178 AccountsService.user = user;
179 LauncherModel.setUser(user);
179 LauncherModel.setUser(user);
180 LightDMService.greeter.authenticate(user); // always resets auth state
180 LightDMService.greeter.authenticate(user); // always resets auth state
181 }
181 }
182
182
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+
183 function login() {
191 function login() {
184− enabled = false;
192+ d.waiting = true;
185 if (LightDMService.greeter.startSessionSync()) {
193 if (LightDMService.greeter.startSessionSync()) {
186 sessionStarted();
194 sessionStarted();
187− if (loader.item) {
195+ hideView();
188− loader.item.notifyAuthenticationSucceeded(false /* showFakePassword */);
189− }
190 } else if (loader.item) {
196 } else if (loader.item) {
191 loader.item.notifyAuthenticationFailed();
197 loader.item.notifyAuthenticationFailed();
192 }
198 }
193− enabled = true;
199+ d.waiting = false;
194 }
200 }
195
201
196 function startUnlock(toTheRight) {
202 function startUnlock(toTheRight) {
@@ -202,10 +208,8 @@ Showable {
202 }
208 }
203
209
204 function checkForcedUnlock(hideNow) {
210 function checkForcedUnlock(hideNow) {
205− if (forcedUnlock && shown && loader.item) {
211+ if (forcedUnlock && shown) {
206− // pretend we were just authenticated
212+ hideView();
207− loader.item.notifyAuthenticationSucceeded(false /* showFakePassword */);
208− loader.item.hide();
209 if (hideNow) {
213 if (hideNow) {
210 root.hideNow(); // skip hide animation
214 root.hideNow(); // skip hide animation
211 }
215 }
@@ -326,7 +330,7 @@ Showable {
326
330
327 onLoaded: {
331 onLoaded: {
328 root.lockedApp = "";
332 root.lockedApp = "";
329− root.forceActiveFocus();
333+ item.forceActiveFocus();
330 d.selectUser(d.currentIndex, true);
334 d.selectUser(d.currentIndex, true);
331 LightDMService.infographic.readyForDataChange();
335 LightDMService.infographic.readyForDataChange();
332 }
336 }
@@ -336,13 +340,11 @@ Showable {
336 onSelected: {
340 onSelected: {
337 d.selectUser(index, true);
341 d.selectUser(index, true);
338 }
342 }
339− onPromptlessLogin: d.login();
340 onResponded: {
343 onResponded: {
341 if (root.locked) {
344 if (root.locked) {
342 LightDMService.greeter.respond(response);
345 LightDMService.greeter.respond(response);
343 } else {
346 } else {
344 d.login();
347 d.login();
345− loader.item.hide();
346 }
348 }
347 }
349 }
348 onTease: root.tease()
350 onTease: root.tease()
@@ -390,6 +392,12 @@ Showable {
390 value: root.locked
392 value: root.locked
391 }
393 }
392
394
395+ Binding {
396+ target: loader.item
397+ property: "waiting"
398+ value: d.waiting
399+ }
400+
393 Binding {
401 Binding {
394 target: loader.item
402 target: loader.item
395 property: "alphanumeric"
403 property: "alphanumeric"
@@ -420,10 +428,7 @@ Showable {
420
428
421 onShowGreeter: root.forceShow()
429 onShowGreeter: root.forceShow()
422
430
423− onHideGreeter: {
431+ onHideGreeter: d.login()
424− d.login();
425− loader.item.hide();
426− }
427
432
428 onShowMessage: {
433 onShowMessage: {
429 // inefficient, but we only rarely deal with messages
434 // inefficient, but we only rarely deal with messages
@@ -441,11 +446,11 @@ Showable {
441 }
446 }
442
447
443 onShowPrompt: {
448 onShowPrompt: {
444− d.waiting = false;
445−
446 if (loader.item) {
449 if (loader.item) {
447 loader.item.showPrompt(text, isSecret, isDefaultPrompt);
450 loader.item.showPrompt(text, isSecret, isDefaultPrompt);
448 }
451 }
452+
453+ d.waiting = false;
449 }
454 }
450
455
451 onAuthenticationComplete: {
456 onAuthenticationComplete: {
@@ -454,7 +459,6 @@ Showable {
454 if (LightDMService.greeter.authenticated) {
459 if (LightDMService.greeter.authenticated) {
455 if (!LightDMService.greeter.promptless) {
460 if (!LightDMService.greeter.promptless) {
456 d.login();
461 d.login();
457− loader.item.hide();
458 }
462 }
459 } else {
463 } else {
460 if (!LightDMService.greeter.promptless) {
464 if (!LightDMService.greeter.promptless) {
qml/Greeter/GreeterPrompt.qml+67−12
@@ -16,6 +16,7 @@
16
16
17import QtQuick 2.4
17import QtQuick 2.4
18import Ubuntu.Components 1.3
18import Ubuntu.Components 1.3
19+import "../Components"
19
20
20FocusScope {
21FocusScope {
21 id: root
22 id: root
@@ -33,6 +34,7 @@ FocusScope {
33
34
34 function reset() {
35 function reset() {
35 passwordInput.text = "";
36 passwordInput.text = "";
37+ fakeLabel.text = "";
36 d.enabled = true;
38 d.enabled = true;
37 }
39 }
38
40
@@ -58,14 +60,28 @@ FocusScope {
58 color: "transparent"
60 color: "transparent"
59 }
61 }
60
62
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+
61 AbstractButton {
73 AbstractButton {
74+ id: promptButton
62 objectName: "promptButton"
75 objectName: "promptButton"
63 anchors.fill: parent
76 anchors.fill: parent
64 visible: !root.isPrompt
77 visible: !root.isPrompt
65− enabled: d.enabled
66− focus: visible
67
78
79+ onClicked: {
80+ if (d.enabled) {
81+ d.enabled = false;
68− onClicked: root.clicked()
82+ root.clicked();
83+ }
84+ }
69
85
70 Label {
86 Label {
71 anchors.centerIn: parent
87 anchors.centerIn: parent
@@ -79,10 +95,15 @@ FocusScope {
79 objectName: "promptField"
95 objectName: "promptField"
80 anchors.fill: parent
96 anchors.fill: parent
81 visible: root.isPrompt
97 visible: root.isPrompt
82− enabled: d.enabled
98+ opacity: fakeLabel.visible ? 0 : 1
83− focus: visible
99+
100+ validator: RegExpValidator {
101+ regExp: root.isAlphanumeric ? /^.*$/ : /^\d{4}$/
102+ }
84
103
104+ inputMethodHints: Qt.ImhSensitiveData | Qt.ImhNoPredictiveText |
105+ Qt.ImhMultiLine | // so OSK doesn't close on Enter
85− inputMethodHints: root.isAlphanumeric ? Qt.ImhNone : Qt.ImhDigitsOnly
106+ (root.isAlphanumeric ? Qt.ImhNone : Qt.ImhDigitsOnly)
86 echoMode: root.isSecret ? TextInput.Password : TextInput.Normal
107 echoMode: root.isSecret ? TextInput.Password : TextInput.Normal
87 hasClearButton: false
108 hasClearButton: false
88
109
@@ -107,17 +128,35 @@ FocusScope {
107 width: units.gu(3)
128 width: units.gu(3)
108 color: d.textColor
129 color: d.textColor
109 visible: root.isSecret && false // TODO: detect when caps lock is on
130 visible: root.isSecret && false // TODO: detect when caps lock is on
131+ readonly property real visibleWidth: visible ? width + passwordInput.frameSpacing : 0
110 }
132 }
111 ]
133 ]
112
134
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+
113 onAccepted: {
145 onAccepted: {
114− if (!enabled)
146+ if (d.enabled)
115− return;
147+ respond();
148+ }
149+
150+ function respond() {
116 d.enabled = false;
151 d.enabled = false;
152+ fakeLabel.text = displayText;
117 root.responded(text);
153 root.responded(text);
118 }
154 }
119
155
120− Keys.onEscapePressed: root.canceled()
156+ Keys.onEscapePressed: {
157+ root.canceled();
158+ event.accepted = true;
159+ }
121
160
122 // We use our own custom placeholder label instead of the standard
161 // We use our own custom placeholder label instead of the standard
123 // TextField one because the standard one hardcodes baseText as the
162 // TextField one because the standard one hardcodes baseText as the
@@ -129,9 +168,7 @@ FocusScope {
129 right: parent.right
168 right: parent.right
130 verticalCenter: parent.verticalCenter
169 verticalCenter: parent.verticalCenter
131 leftMargin: units.gu(1.5)
170 leftMargin: units.gu(1.5)
132− rightMargin: anchors.leftMargin +
171+ rightMargin: anchors.leftMargin + capsIcon.visibleWidth
133− (capsIcon.visible ? capsIcon.width + passwordInput.frameSpacing
134− : 0)
135 }
172 }
136 text: root.text
173 text: root.text
137 visible: passwordInput.text == "" && !passwordInput.inputMethodComposing
174 visible: passwordInput.text == "" && !passwordInput.inputMethodComposing
@@ -139,4 +176,22 @@ FocusScope {
139 elide: Text.ElideRight
176 elide: Text.ElideRight
140 }
177 }
141 }
178 }
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+ }
142}
197}
qml/Greeter/LoginList.qml+27−16
@@ -21,11 +21,13 @@ import "../Components"
21
21
22StyledItem {
22StyledItem {
23 id: root
23 id: root
24+ focus: true
24
25
25 property alias model: userList.model
26 property alias model: userList.model
26 property bool alphanumeric: true
27 property bool alphanumeric: true
27 property int currentIndex
28 property int currentIndex
28 property bool locked
29 property bool locked
30+ property bool waiting
29
31
30 readonly property int numAboveBelow: 4
32 readonly property int numAboveBelow: 4
31 readonly property int cellHeight: units.gu(5)
33 readonly property int cellHeight: units.gu(5)
@@ -36,7 +38,6 @@ StyledItem {
36
38
37 signal selected(int index)
39 signal selected(int index)
38 signal responded(string response)
40 signal responded(string response)
39− signal promptlessLogin()
40
41
41 function tryToUnlock() {
42 function tryToUnlock() {
42 if (wasPrompted) {
43 if (wasPrompted) {
@@ -45,7 +46,6 @@ StyledItem {
45 if (root.locked) {
46 if (root.locked) {
46 root.selected(currentIndex);
47 root.selected(currentIndex);
47 } else {
48 } else {
48− promptlessLogin();
49 root.responded("");
49 root.responded("");
50 }
50 }
51 }
51 }
@@ -60,20 +60,18 @@ StyledItem {
60 }
60 }
61
61
62 function showPrompt(text, isSecret, isDefaultPrompt) {
62 function showPrompt(text, isSecret, isDefaultPrompt) {
63+ passwordInput.text = isDefaultPrompt ? alphanumeric ? i18n.tr("Passphrase")
64+ : i18n.tr("Passcode")
63− d.promptText = text;
65+ : text;
64− passwordInput.reset();
66+ passwordInput.isPrompt = true;
65 passwordInput.isSecret = isSecret;
67 passwordInput.isSecret = isSecret;
66− if (wasPrompted) // stay in text field if second prompt
68+ passwordInput.reset();
67− passwordInput.focus = true;
68 wasPrompted = true;
69 wasPrompted = true;
69 }
70 }
70
71
71 function showError() {
72 function showError() {
72 wrongPasswordAnimation.start();
73 wrongPasswordAnimation.start();
73 root.resetAuthentication();
74 root.resetAuthentication();
74− if (wasPrompted) {
75− passwordInput.focus = true;
76− }
77 }
75 }
78
76
79 function reset() {
77 function reset() {
@@ -83,15 +81,33 @@ StyledItem {
83 QtObject {
81 QtObject {
84 id: d
82 id: d
85
83
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+ }
87 }
91 }
88
92
93+ onWaitingChanged: d.checkIfPromptless()
94+ onLockedChanged: d.checkIfPromptless()
95+
89 theme: ThemeSettings {
96 theme: ThemeSettings {
90 name: "Ubuntu.Components.Themes.Ambiance"
97 name: "Ubuntu.Components.Themes.Ambiance"
91 }
98 }
92
99
100+ Keys.onUpPressed: {
101+ selected(currentIndex - 1);
102+ event.accepted = true;
103+ }
104+ Keys.onDownPressed: {
105+ selected(currentIndex + 1);
106+ event.accepted = true;
107+ }
93 Keys.onEscapePressed: {
108 Keys.onEscapePressed: {
94 selected(currentIndex);
109 selected(currentIndex);
110+ event.accepted = true;
95 }
111 }
96
112
97 onCurrentIndexChanged: {
113 onCurrentIndexChanged: {
@@ -137,6 +153,7 @@ StyledItem {
137 highlightRangeMode: ListView.StrictlyEnforceRange
153 highlightRangeMode: ListView.StrictlyEnforceRange
138 highlightMoveDuration: root.moveDuration
154 highlightMoveDuration: root.moveDuration
139 flickDeceleration: 10000
155 flickDeceleration: 10000
156+ interactive: count > 1
140
157
141 readonly property bool movingInternally: moveTimer.running || userList.moving
158 readonly property bool movingInternally: moveTimer.running || userList.moving
142 onMovingInternallyChanged: {
159 onMovingInternallyChanged: {
@@ -254,13 +271,8 @@ StyledItem {
254 width: highlightItem.width - anchors.margins * 2
271 width: highlightItem.width - anchors.margins * 2
255 opacity: userList.movingInternally ? 0 : 1
272 opacity: userList.movingInternally ? 0 : 1
256
273
257− isPrompt: root.wasPrompted
258 isAlphanumeric: root.alphanumeric
274 isAlphanumeric: root.alphanumeric
259
275
260− text: root.wasPrompted ? d.promptText
261− : (root.locked ? i18n.tr("Retry")
262− : i18n.tr("Log In"))
263−
264 onClicked: root.tryToUnlock()
276 onClicked: root.tryToUnlock()
265 onResponded: root.responded(text)
277 onResponded: root.responded(text)
266 onCanceled: root.selected(currentIndex)
278 onCanceled: root.selected(currentIndex)
@@ -280,7 +292,6 @@ StyledItem {
280 return;
292 return;
281 }
293 }
282 infoLabel.text = "";
294 infoLabel.text = "";
283− d.promptText = "";
284 passwordInput.reset();
295 passwordInput.reset();
285 root.wasPrompted = false;
296 root.wasPrompted = false;
286 }
297 }
qml/Greeter/NarrowView.qml+2−1
@@ -31,6 +31,7 @@ FocusScope {
31 property bool alphanumeric
31 property bool alphanumeric
32 property var userModel // unused
32 property var userModel // unused
33 property alias infographicModel: coverPage.infographicModel
33 property alias infographicModel: coverPage.infographicModel
34+ property bool waiting
34 readonly property bool fullyShown: coverPage.showProgress === 1 || lockscreen.shown
35 readonly property bool fullyShown: coverPage.showProgress === 1 || lockscreen.shown
35 readonly property bool required: coverPage.required || lockscreen.required
36 readonly property bool required: coverPage.required || lockscreen.required
36 readonly property bool animating: coverPage.showAnimation.running || coverPage.hideAnimation.running
37 readonly property bool animating: coverPage.showAnimation.running || coverPage.hideAnimation.running
@@ -71,7 +72,6 @@ FocusScope {
71 if (!alphanumeric && showFakePassword) {
72 if (!alphanumeric && showFakePassword) {
72 lockscreen.showText("...."); // actual text doesn't matter, we show bullets
73 lockscreen.showText("...."); // actual text doesn't matter, we show bullets
73 }
74 }
74− lockscreen.hide();
75 }
75 }
76
76
77 function notifyAuthenticationFailed() {
77 function notifyAuthenticationFailed() {
@@ -172,6 +172,7 @@ FocusScope {
172 height: parent.height
172 height: parent.height
173 width: parent.width
173 width: parent.width
174 background: root.background
174 background: root.background
175+ draggable: !root.waiting
175 onTease: root.tease()
176 onTease: root.tease()
176 onClicked: hide()
177 onClicked: hide()
177
178
qml/Greeter/WideView.qml+5−4
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2015-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -19,6 +19,7 @@ import Ubuntu.Components 1.3
19
19
20FocusScope {
20FocusScope {
21 id: root
21 id: root
22+ focus: true
22
23
23 property alias dragHandleLeftMargin: coverPage.dragHandleLeftMargin
24 property alias dragHandleLeftMargin: coverPage.dragHandleLeftMargin
24 property alias launcherOffset: coverPage.launcherOffset
25 property alias launcherOffset: coverPage.launcherOffset
@@ -30,6 +31,7 @@ FocusScope {
30 property alias alphanumeric: loginList.alphanumeric
31 property alias alphanumeric: loginList.alphanumeric
31 property alias userModel: loginList.model
32 property alias userModel: loginList.model
32 property alias infographicModel: coverPage.infographicModel
33 property alias infographicModel: coverPage.infographicModel
34+ property bool waiting
33 readonly property bool fullyShown: coverPage.showProgress === 1
35 readonly property bool fullyShown: coverPage.showProgress === 1
34 readonly property bool required: coverPage.required
36 readonly property bool required: coverPage.required
35 readonly property bool animating: coverPage.showAnimation.running || coverPage.hideAnimation.running
37 readonly property bool animating: coverPage.showAnimation.running || coverPage.hideAnimation.running
@@ -37,7 +39,6 @@ FocusScope {
37 // so that it can be replaced in tests with a mock object
39 // so that it can be replaced in tests with a mock object
38 property var inputMethod: Qt.inputMethod
40 property var inputMethod: Qt.inputMethod
39
41
40− signal promptlessLogin()
41 signal selected(int index)
42 signal selected(int index)
42 signal responded(string response)
43 signal responded(string response)
43 signal tease()
44 signal tease()
@@ -102,7 +103,7 @@ FocusScope {
102 objectName: "coverPage"
103 objectName: "coverPage"
103 height: parent.height
104 height: parent.height
104 width: parent.width
105 width: parent.width
105− draggable: !root.locked
106+ draggable: !root.locked && !root.waiting
106
107
107 infographics {
108 infographics {
108 height: 0.75 * parent.height
109 height: 0.75 * parent.height
@@ -132,8 +133,8 @@ FocusScope {
132 Behavior on height { UbuntuNumberAnimation {} }
133 Behavior on height { UbuntuNumberAnimation {} }
133
134
134 locked: root.locked
135 locked: root.locked
136+ waiting: root.waiting
135
137
136− onPromptlessLogin: root.promptlessLogin()
137 onSelected: root.selected(index)
138 onSelected: root.selected(index)
138 onResponded: root.responded(response)
139 onResponded: root.responded(response)
139 }
140 }
qml/Shell.qml+5−3
@@ -137,9 +137,9 @@ StyledItem {
137 // For autopilot consumption
137 // For autopilot consumption
138 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
138 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
139
139
140− // Disable everything while greeter is waiting, so that the user can't swipe
140+ // Note when greeter is waiting on PAM, so that we can disable edges until
141− // the greeter or launcher until we know whether the session is locked.
141+ // we know which user data to show and whether the session is locked.
142− enabled: greeter && !greeter.waiting
142+ readonly property bool waitingOnGreeter: greeter && greeter.waiting
143
143
144 property real edgeSize: units.gu(settings.edgeDragWidth)
144 property real edgeSize: units.gu(settings.edgeDragWidth)
145
145
@@ -533,6 +533,7 @@ StyledItem {
533 available: tutorial.panelEnabled
533 available: tutorial.panelEnabled
534 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
534 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
535 && (!greeter || !greeter.hasLockedApp)
535 && (!greeter || !greeter.hasLockedApp)
536+ && !shell.waitingOnGreeter
536 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
537 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
537
538
538 minimizedPanelHeight: units.gu(3)
539 minimizedPanelHeight: units.gu(3)
@@ -583,6 +584,7 @@ StyledItem {
583 available: tutorial.launcherEnabled
584 available: tutorial.launcherEnabled
584 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
585 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
585 && !greeter.hasLockedApp
586 && !greeter.hasLockedApp
587+ && !shell.waitingOnGreeter
586 inverted: shell.usageScenario !== "desktop"
588 inverted: shell.usageScenario !== "desktop"
587 superPressed: physicalKeysMapper.superPressed
589 superPressed: physicalKeysMapper.superPressed
588 superTabPressed: physicalKeysMapper.superTabPressed
590 superTabPressed: physicalKeysMapper.superTabPressed
tests/qmltests/Dash/tst_DashShell.qml+6
@@ -91,6 +91,9 @@ Item {
91 var stopY = startY;
91 var stopY = startY;
92 touchFlick(shell, startX, startY, stopX, stopY);
92 touchFlick(shell, startX, startY, stopX, stopY);
93
93
94+ var greeter = findChild(shell, "greeter");
95+ tryCompare(greeter, "shown", false);
96+
94 // bring in the launcher
97 // bring in the launcher
95 var startX = units.gu(1);
98 var startX = units.gu(1);
96 var startY = shell.height / 2;
99 var startY = shell.height / 2;
@@ -131,6 +134,9 @@ Item {
131 var stopY = startY;
134 var stopY = startY;
132 touchFlick(shell, startX, startY, stopX, stopY);
135 touchFlick(shell, startX, startY, stopX, stopY);
133
136
137+ var greeter = findChild(shell, "greeter");
138+ tryCompare(greeter, "shown", false);
139+
134 // Now do a small launcher movement
140 // Now do a small launcher movement
135 touchFlick(shell, stopX, startY, startX, stopY);
141 touchFlick(shell, stopX, startY, startX, stopY);
136 compare(dashContentList.currentIndex, 0, "Small launcher move did not reset dash on non active window");
142 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 {
43 signal responded(string response)
43 signal responded(string response)
44 signal tease()
44 signal tease()
45 signal emergencyCall()
45 signal emergencyCall()
46− signal promptlessLogin()
47
46
48 signal _showMessageCalled(string html)
47 signal _showMessageCalled(string html)
49 signal _showPromptCalled(string text, bool isSecret, bool isDefaultPrompt)
48 signal _showPromptCalled(string text, bool isSecret, bool isDefaultPrompt)
tests/qmltests/Greeter/tst_Greeter.qml+3−13
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright 2015 Canonical Ltd.
2+ * Copyright 2015-2016 Canonical Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -194,6 +194,8 @@ Item {
194 var i = getIndexOf(name);
194 var i = getIndexOf(name);
195 view.selected(i);
195 view.selected(i);
196 verifySelected(name);
196 verifySelected(name);
197+ compare(greeter.waiting, true);
198+ tryCompare(greeter, "waiting", false);
197 return i;
199 return i;
198 }
200 }
199
201
@@ -325,18 +327,6 @@ Item {
325 compare(viewShowMessageSpy.signalArguments[2][0], "You should have seen three messages");
327 compare(viewShowMessageSpy.signalArguments[2][0], "You should have seen three messages");
326 }
328 }
327
329
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−
340 function test_locked() {
330 function test_locked() {
341 selectUser("has-password");
331 selectUser("has-password");
342 compare(view.locked, true);
332 compare(view.locked, true);
tests/qmltests/Greeter/tst_WideView.qml+92−12
@@ -26,6 +26,7 @@ StyledItem {
26 id: root
26 id: root
27 width: units.gu(120)
27 width: units.gu(120)
28 height: units.gu(80)
28 height: units.gu(80)
29+ focus: true
29
30
30 theme.name: "Ubuntu.Components.Themes.SuruDark"
31 theme.name: "Ubuntu.Components.Themes.SuruDark"
31
32
@@ -41,6 +42,7 @@ StyledItem {
41 id: loader
42 id: loader
42 width: root.width - controls.width
43 width: root.width - controls.width
43 height: parent.height
44 height: parent.height
45+ focus: true
44
46
45 property bool itemDestroyed: false
47 property bool itemDestroyed: false
46 sourceComponent: Component {
48 sourceComponent: Component {
@@ -63,7 +65,8 @@ StyledItem {
63 }
65 }
64
66
65 onSelected: {
67 onSelected: {
68+ if (index >= 0)
66− currentIndexField.text = index;
69+ currentIndexField.text = index;
67 }
70 }
68
71
69 QtObject {
72 QtObject {
@@ -392,7 +395,7 @@ StyledItem {
392
395
393 function test_respondedWithPassword() {
396 function test_respondedWithPassword() {
394 view.locked = true;
397 view.locked = true;
395− view.showPrompt("Prompt", true, true);
398+ view.showPrompt("Prompt", true, false);
396 var passwordInput = findChild(view, "passwordInput");
399 var passwordInput = findChild(view, "passwordInput");
397 compare(passwordInput.text, "Prompt");
400 compare(passwordInput.text, "Prompt");
398 verify(passwordInput.isSecret);
401 verify(passwordInput.isSecret);
@@ -454,13 +457,13 @@ StyledItem {
454 view.showPrompt("Prompt", true, true);
457 view.showPrompt("Prompt", true, true);
455 var promptField = findChild(view, "promptField");
458 var promptField = findChild(view, "promptField");
456 tap(promptField);
459 tap(promptField);
457− compare(promptField.focus, true);
460+ verify(promptField.activeFocus);
458− compare(promptField.enabled, true);
461+ compare(promptField.opacity, 1);
459
462
460 typeString("password");
463 typeString("password");
461 keyClick(Qt.Key_Enter);
464 keyClick(Qt.Key_Enter);
462− compare(promptField.focus, true);
465+ verify(promptField.activeFocus);
463− compare(promptField.enabled, false);
466+ compare(promptField.opacity, 0); // hidden by fakeLabel
464
467
465 compare(selectedSpy.count, 0);
468 compare(selectedSpy.count, 0);
466 keyClick(Qt.Key_Escape);
469 keyClick(Qt.Key_Escape);
@@ -468,8 +471,8 @@ StyledItem {
468 compare(selectedSpy.signalArguments[0][0], 1);
471 compare(selectedSpy.signalArguments[0][0], 1);
469
472
470 view.reset();
473 view.reset();
471− compare(promptField.focus, false);
474+ verify(promptField.activeFocus);
472− compare(promptField.enabled, true);
475+ compare(promptField.opacity, 1);
473 }
476 }
474
477
475 function test_unicode() {
478 function test_unicode() {
@@ -489,6 +492,7 @@ StyledItem {
489 compare(selectedSpy.signalArguments[0][0], 0);
492 compare(selectedSpy.signalArguments[0][0], 0);
490 selectedSpy.clear();
493 selectedSpy.clear();
491
494
495+ view.reset();
492 view.locked = false;
496 view.locked = false;
493 compare(passwordInput.text, "Log In");
497 compare(passwordInput.text, "Log In");
494 tap(passwordInput);
498 tap(passwordInput);
@@ -518,13 +522,89 @@ StyledItem {
518 tryCompare(loginList, "height", view.height);
522 tryCompare(loginList, "height", view.height);
519 }
523 }
520
524
521− function test_alphanumeric() {
525+ function test_passphrase() {
522− var passwordInput = findChild(view, "passwordInput");
526+ var promptField = findChild(view, "promptField");
527+ view.showPrompt("", true, true);
523
528
524 verify(view.alphanumeric);
529 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+
526 view.alphanumeric = false;
540 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);
528 }
608 }
529 }
609 }
530}
610}
tests/qmltests/Tutorial/tst_Tutorial.qml+1−1
@@ -296,7 +296,7 @@ Rectangle {
296 }
296 }
297
297
298 function prepareShell() {
298 function prepareShell() {
299− tryCompare(shell, "enabled", true); // enabled by greeter when ready
299+ tryCompare(shell, "waitingOnGreeter", false); // reset by greeter when ready
300
300
301 WindowStateStorage.clear();
301 WindowStateStorage.clear();
302 SurfaceManager.inputMethodSurface.setState(Mir.MinimizedState);
302 SurfaceManager.inputMethodSurface.setState(Mir.MinimizedState);
tests/qmltests/tst_OrientedShell.qml+2−2
@@ -493,7 +493,7 @@ Rectangle {
493 }
493 }
494
494
495 function cleanup() {
495 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
497 shell = null;
497 shell = null;
498 topLevelSurfaceList = null;
498 topLevelSurfaceList = null;
499
499
@@ -1583,7 +1583,7 @@ Rectangle {
1583 var stageLoader = findChild(shell, "applicationsDisplayLoader");
1583 var stageLoader = findChild(shell, "applicationsDisplayLoader");
1584 verify(stageLoader);
1584 verify(stageLoader);
1585
1585
1586− tryCompare(shell, "enabled", true); // enabled by greeter when ready
1586+ tryCompare(shell, "waitingOnGreeter", false); // reset by greeter when ready
1587
1587
1588 waitUntilShellIsInOrientation(root.physicalOrientation0);
1588 waitUntilShellIsInOrientation(root.physicalOrientation0);
1589
1589
tests/qmltests/tst_Shell.qml+3−3
@@ -456,7 +456,7 @@ Rectangle {
456 function cleanup() {
456 function cleanup() {
457 waitForRendering(shell);
457 waitForRendering(shell);
458 mouseEmulation.checked = true;
458 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
460 tearDown();
460 tearDown();
461 WindowStateStorage.clear();
461 WindowStateStorage.clear();
462 }
462 }
@@ -466,7 +466,7 @@ Rectangle {
466 shellLoader.active = true;
466 shellLoader.active = true;
467 tryCompare(shellLoader, "status", Loader.Ready);
467 tryCompare(shellLoader, "status", Loader.Ready);
468 removeTimeConstraintsFromSwipeAreas(shellLoader.item);
468 removeTimeConstraintsFromSwipeAreas(shellLoader.item);
469− tryCompare(shell, "enabled", true); // enabled by greeter when ready
469+ tryCompare(shell, "waitingOnGreeter", false); // reset by greeter when ready
470
470
471 sessionSpy.target = findChild(shell, "greeter")
471 sessionSpy.target = findChild(shell, "greeter")
472 dashCommunicatorSpy.target = findInvisibleChild(shell, "dashCommunicator");
472 dashCommunicatorSpy.target = findInvisibleChild(shell, "dashCommunicator");
@@ -818,7 +818,7 @@ Rectangle {
818 tryCompare(userlist, "currentIndex", next)
818 tryCompare(userlist, "currentIndex", next)
819 tryCompare(userlist, "movingInternally", false)
819 tryCompare(userlist, "movingInternally", false)
820 }
820 }
821− tryCompare(shell, "enabled", true); // wait for PAM to settle
821+ tryCompare(shell, "waitingOnGreeter", false); // wait for PAM to settle
822 }
822 }
823
823
824 function selectUser(name) {
824 function selectUser(name) {
tests/qmltests/tst_ShellWithPin.qml+7−5
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2014-2015 Canonical, Ltd.
2+ * Copyright (C) 2014-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -150,7 +150,7 @@ Item {
150 property Item shell: shellLoader.status === Loader.Ready ? shellLoader.item : null
150 property Item shell: shellLoader.status === Loader.Ready ? shellLoader.item : null
151
151
152 function init() {
152 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
154 var greeter = findChild(shell, "greeter");
154 var greeter = findChild(shell, "greeter");
155 sessionSpy.target = greeter;
155 sessionSpy.target = greeter;
156 swipeAwayGreeter(true);
156 swipeAwayGreeter(true);
@@ -165,7 +165,7 @@ Item {
165 }
165 }
166
166
167 function cleanup() {
167 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
169
169
170 shellLoader.itemDestroyed = false
170 shellLoader.itemDestroyed = false
171
171
@@ -533,10 +533,12 @@ Item {
533
533
534 // Confirm that we start disabled
534 // Confirm that we start disabled
535 compare(promptSpy.count, 0);
535 compare(promptSpy.count, 0);
536− verify(!shell.enabled);
536+ verify(shell.waitingOnGreeter);
537+ var coverPageDragHandle = findChild(shell, "coverPageDragHandle");
538+ verify(!coverPageDragHandle.enabled);
537
539
538 // And that we only become enabled once the lockscreen is up
540 // And that we only become enabled once the lockscreen is up
539− tryCompare(shell, "enabled", true);
541+ tryCompare(shell, "waitingOnGreeter", false);
540 verify(promptSpy.count > 0);
542 verify(promptSpy.count > 0);
541 var lockscreen = findChild(shell, "lockscreen");
543 var lockscreen = findChild(shell, "lockscreen");
542 verify(lockscreen.shown);
544 verify(lockscreen.shown);