on xenial help sign in
Implement latest visual designs for passphrase lockscreen.
a281d95Michael Terry
qml/Components/DelayedLockscreen.qmlnew file+92
@@ -0,0 +1,92 @@
1+/*
2+ * Copyright (C) 2013,2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+import "../Components"
20+
21+Item {
22+ id: root
23+ anchors.fill: parent
24+
25+ property int delayMinutes
26+ property bool alphaNumeric
27+
28+ function clear(playAnimation) {}
29+
30+ Column {
31+ anchors.left: parent.left
32+ anchors.right: parent.right
33+ anchors.leftMargin: units.gu(4)
34+ anchors.rightMargin: units.gu(4)
35+ anchors.verticalCenter: parent.verticalCenter
36+ spacing: units.gu(2)
37+
38+ Label {
39+ id: deviceLockedLabel
40+ objectName: "deviceLockedLabel"
41+ anchors.left: parent.left
42+ anchors.right: parent.right
43+ horizontalAlignment: Text.AlignHCenter
44+ wrapMode: Text.Wrap
45+ fontSize: "x-large"
46+ color: "#f3f3e7"
47+ text: i18n.tr("Device Locked")
48+ }
49+
50+ Item { // spacer
51+ width: units.gu(1)
52+ height: units.gu(1)
53+ }
54+
55+ Label {
56+ anchors.left: parent.left
57+ anchors.right: parent.right
58+ horizontalAlignment: Text.AlignHCenter
59+ wrapMode: Text.Wrap
60+ color: "#f3f3e7"
61+ text: alphaNumeric ?
62+ i18n.tr("You have been locked out due to too many failed passphrase attempts.") :
63+ i18n.tr("You have been locked out due to too many failed passcode attempts.")
64+ }
65+
66+ Label {
67+ anchors.left: parent.left
68+ anchors.right: parent.right
69+ horizontalAlignment: Text.AlignHCenter
70+ wrapMode: Text.Wrap
71+ color: "#f3f3e7"
72+ text: i18n.tr("Please wait %1 minute and then try again…",
73+ "Please wait %1 minutes and then try again…",
74+ root.delayMinutes).arg(root.delayMinutes)
75+ }
76+
77+ Item { // spacer
78+ width: units.gu(1)
79+ height: units.gu(1)
80+ }
81+
82+ Icon {
83+ // It would be nice to use a less network-specific name,
84+ // but this is the only lock icon we have.
85+ name: "network-secure"
86+ color: "#f3f3e7"
87+ height: units.gu(4)
88+ width: units.gu(4)
89+ anchors.horizontalCenter: parent.horizontalCenter
90+ }
91+ }
92+}
qml/Components/Lockscreen.qml+115−96
@@ -28,13 +28,14 @@ Showable {
28 property string infoText: ""
28 property string infoText: ""
29
29
30 // Retries text (e.g. 3 retries left)
30 // Retries text (e.g. 3 retries left)
31+ // (This is not currently used, but will be necessary for SIM unlock screen)
31 property string retryText: ""
32 property string retryText: ""
32
33
33 // The text to be displayed in case the login failed
34 // The text to be displayed in case the login failed
34 property string errorText: ""
35 property string errorText: ""
35
36
36− // In case the Lockscreen can show a greeter message, this is the username
37+ // If > 0, a forced delay is happening
37− property string username: ""
38+ property int delayMinutes: 0
38
39
39 // Set those to a value greater 0 to restrict the pin length.
40 // Set those to a value greater 0 to restrict the pin length.
40 // If both are unset, the Lockscreen will show a confirm button and allow typing any length of pin before
41 // If both are unset, the Lockscreen will show a confirm button and allow typing any length of pin before
@@ -54,16 +55,7 @@ Showable {
54 signal emergencyCall()
55 signal emergencyCall()
55 signal infoPopupConfirmed()
56 signal infoPopupConfirmed()
56
57
57− onRequiredChanged: {
58+ onActiveFocusChanged: if (activeFocus && pinPadLoader.item) pinPadLoader.item.forceActiveFocus()
58− if (required && pinPadLoader.item) {
59− clear(false)
60− }
61− }
62−
63− function forceDelay(delay) {
64− forcedDelayTimer.interval = delay
65− forcedDelayTimer.start()
66− }
67
59
68 function reset() {
60 function reset() {
69 // This causes the loader below to destry and recreate the source
61 // This causes the loader below to destry and recreate the source
@@ -83,11 +75,6 @@ Showable {
83 PopupUtils.open(infoPopupComponent, root, {title: title, text: text})
75 PopupUtils.open(infoPopupComponent, root, {title: title, text: text})
84 }
76 }
85
77
86− Timer {
87− id: forcedDelayTimer
88− onTriggered: pinPadLoader.showWrongText = false
89− }
90−
91 Rectangle {
78 Rectangle {
92 // In case background fails to load or is undefined
79 // In case background fails to load or is undefined
93 id: backgroundBackup
80 id: backgroundBackup
@@ -107,86 +94,100 @@ Showable {
107
94
108 MouseArea {
95 MouseArea {
109 anchors.fill: root
96 anchors.fill: root
97+ onClicked: {
98+ if (pinPadLoader.item)
99+ pinPadLoader.item.forceActiveFocus()
100+ }
110 }
101 }
111
102
103+ FocusScope {
104+ id: loaderScope
105+ anchors.fill: parent
106+
112− Loader {
107+ Loader {
113− id: pinPadLoader
108+ id: pinPadLoader
114− objectName: "pinPadLoader"
109+ objectName: "pinPadLoader"
115− anchors {
110+ anchors.fill: parent
116− left: parent.left
117− right: parent.right
118− verticalCenter: parent.verticalCenter
119− verticalCenterOffset: root.alphaNumeric ? -units.gu(10) : 0
120− }
121− property bool resetting: false
111+ property bool resetting: false
122− property bool waiting: false
112+ property bool waiting: false
123− property bool showWrongText: false
113+ property bool showWrongText: false
124−
114+
115+ source: {
116+ if (resetting || !root.required) {
117+ return ""
118+ } else if (root.delayMinutes > 0) {
119+ return "DelayedLockscreen.qml"
120+ } else if (root.alphaNumeric) {
125− source: (!resetting && root.required) ? (root.alphaNumeric ? "PassphraseLockscreen.qml" : "PinLockscreen.qml") : ""
121+ return "PassphraseLockscreen.qml"
122+ } else {
123+ return "PinLockscreen.qml"
124+ }
125+ }
126− onSourceChanged: {
126+ onSourceChanged: {
127− waiting = false
127+ waiting = false
128− showWrongText = false
128+ showWrongText = false
129− }
129+ if (loaderScope.activeFocus && pinPadLoader.item)
130− onLoaded: {
131− if (forcedDelayTimer.running) {
132− pinPadLoader.item.clear(true)
130+ pinPadLoader.item.forceActiveFocus()
133 }
131 }
134− }
135
132
136− Connections {
133+ Connections {
137− target: pinPadLoader.item
134+ target: pinPadLoader.item
138
135
139− onEntered: {
136+ onEntered: {
140− pinPadLoader.waiting = true
137+ pinPadLoader.waiting = true
141− root.entered(passphrase);
138+ root.entered(passphrase);
142− }
139+ }
143
140
144− onCancel: {
141+ onCancel: {
145− root.cancel()
142+ root.cancel()
143+ }
146 }
144 }
147− }
148
145
149− Binding {
146+ Binding {
150− target: pinPadLoader.item
147+ target: pinPadLoader.item
151− property: "minPinLength"
148+ property: "minPinLength"
152− value: root.minPinLength
149+ value: root.minPinLength
153− }
150+ }
154− Binding {
151+ Binding {
155− target: pinPadLoader.item
152+ target: pinPadLoader.item
156− property: "maxPinLength"
153+ property: "maxPinLength"
157− value: root.maxPinLength
154+ value: root.maxPinLength
158− }
155+ }
159− Binding {
156+ Binding {
160− target: pinPadLoader.item
157+ target: pinPadLoader.item
161− property: "infoText"
158+ property: "infoText"
162− value: root.infoText
159+ value: root.infoText
163− }
160+ }
164− Binding {
161+ Binding {
165− target: pinPadLoader.item
162+ target: pinPadLoader.item
166− property: "retryText"
163+ property: "retryText"
167− value: forcedDelayTimer.running ? i18n.tr("Please wait") : root.retryText
164+ value: root.retryText
168− }
165+ }
169− Binding {
166+ Binding {
170− target: pinPadLoader.item
167+ target: pinPadLoader.item
171− property: "errorText"
168+ property: "errorText"
172− value: forcedDelayTimer.running ? i18n.tr("Too many incorrect attempts") :
173− (pinPadLoader.showWrongText ? root.errorText : "")
169+ value: pinPadLoader.showWrongText ? root.errorText : ""
174− }
175− Binding {
176− target: pinPadLoader.item
177− property: "username"
178− value: root.username
179− }
170+ }
180− Binding {
171+ Binding {
181− target: pinPadLoader.item
172+ target: pinPadLoader.item
182− property: "entryEnabled"
173+ property: "entryEnabled"
183− value: !pinPadLoader.waiting && !forcedDelayTimer.running
174+ value: !pinPadLoader.waiting
175+ }
176+ Binding {
177+ target: pinPadLoader.item
178+ property: "alphaNumeric"
179+ value: root.alphaNumeric
180+ }
181+ Binding {
182+ target: pinPadLoader.item
183+ property: "delayMinutes"
184+ value: root.delayMinutes
185+ }
184 }
186 }
185 }
187 }
186
188
187− Label {
189+ Item {
188− id: emergencyCallLabel
190+ id: emergencyCallRow
189− objectName: "emergencyCallLabel"
190
191
191 // FIXME: We *should* show emergency dialer if there is a SIM present,
192 // FIXME: We *should* show emergency dialer if there is a SIM present,
192 // regardless of whether the side stage is enabled. But right now,
193 // regardless of whether the side stage is enabled. But right now,
@@ -199,19 +200,37 @@ Showable {
199
200
200 anchors {
201 anchors {
201 bottom: parent.bottom
202 bottom: parent.bottom
202− bottomMargin: units.gu(4)
203+ bottomMargin: units.gu(7) + (Qt.inputMethod.visible ? Qt.inputMethod.keyboardRectangle.height : 0)
203− horizontalCenter: parent.horizontalCenter
204+ left: parent.left
205+ right: parent.right
204 }
206 }
205
207
206− text: i18n.tr("Emergency Call")
208+ Label {
207− color: "#f3f3e7"
209+ id: emergencyCallLabel
208− opacity: 0.6
210+ objectName: "emergencyCallLabel"
209− }
211+ anchors.horizontalCenter: parent.horizontalCenter
210
212
213+ text: i18n.tr("Emergency Call")
214+ color: "#f3f3e7"
215+ }
216+
217+ Icon {
218+ id: emergencyCallIcon
219+ anchors.left: emergencyCallLabel.right
220+ anchors.leftMargin: units.gu(1)
221+ width: emergencyCallLabel.height
222+ height: emergencyCallLabel.height
223+ name: "call-start"
224+ color: "#f3f3e7"
225+ }
226+
211− MouseArea {
227+ MouseArea {
228+ anchors.top: emergencyCallLabel.top
229+ anchors.bottom: emergencyCallLabel.bottom
212− anchors.fill: emergencyCallLabel
230+ anchors.left: emergencyCallLabel.left
231+ anchors.right: emergencyCallIcon.right
213− onClicked: root.emergencyCall()
232+ onClicked: root.emergencyCall()
214− enabled: emergencyCallLabel.visible
233+ }
215 }
234 }
216
235
217 Component {
236 Component {
qml/Components/PassphraseLockscreen.qml+96−43
@@ -20,10 +20,12 @@ import "../Components"
20
20
21Item {
21Item {
22 id: root
22 id: root
23+ anchors.top: parent.top
24+ anchors.topMargin: units.gu(4)
23− height: highlightItem.height
25+ height: shakeContainer.height
24
26
25 property string infoText
27 property string infoText
26− property string username: ""
28+ property string errorText
27 property bool entryEnabled: true
29 property bool entryEnabled: true
28
30
29 signal entered(string passphrase)
31 signal entered(string passphrase)
@@ -31,64 +33,115 @@ Item {
31
33
32 function clear(playAnimation) {
34 function clear(playAnimation) {
33 pinentryField.text = "";
35 pinentryField.text = "";
36+ pinentryField.incorrectOverride = false;
37+ pinentryField.forceActiveFocus();
34 if (playAnimation) {
38 if (playAnimation) {
35 wrongPasswordAnimation.start();
39 wrongPasswordAnimation.start();
36− pinentryField.forceActiveFocus();
37− } else {
38− pinentryField.focus = false
39 }
40 }
40 }
41 }
41
42
42− Rectangle {
43+ onActiveFocusChanged: if (activeFocus) pinentryField.forceActiveFocus()
43− id: highlightItem
44+
44− width: units.gu(32)
45+ Column {
45− height: units.gu(10)
46+ id: shakeContainer
46− anchors.centerIn: parent
47+ anchors.horizontalCenter: parent.horizontalCenter
47− color: Qt.rgba(0.1, 0.1, 0.1, 0.4)
48+ width: parent.width
48− border.color: Qt.rgba(0.4, 0.4, 0.4, 0.4)
49− border.width: units.dp(1)
50− radius: units.gu(1.5)
49+ spacing: units.gu(2)
51− antialiasing: true
52
50
53 Label {
51 Label {
52+ id: infoField
54− objectName: "greeterLabel"
53+ objectName: "infoTextLabel"
55− anchors {
54+ fontSize: "x-large"
56− left: parent.left
55+ color: "#f3f3e7"
57− top: parent.top
56+ anchors.horizontalCenter: parent.horizontalCenter
58− right: parent.right
57+ text: root.infoText
59− margins: units.gu(1.5)
60− }
61− text: root.username.length > 0 ? i18n.tr("Hello %1").arg(root.username) : i18n.tr("Hello")
62− color: "white"
63 }
58 }
64
59
65− TextField {
60+ Item {
61+ id: entryContainer
62+ anchors { left: parent.left; right: parent.right; margins: units.gu(2) }
63+ height: units.gu(4)
64+
65+ TextInput {
66− id: pinentryField
66+ id: pinentryField
67− objectName: "pinentryField"
67+ objectName: "pinentryField"
68+
69+ property bool incorrectOverride: false
70+
68− anchors {
71+ anchors {
69− horizontalCenter: parent.horizontalCenter
72+ top: parent.top
70− bottom: parent.bottom
73+ left: parent.left
71− margins: units.gu(1)
74+ right: parent.right
75+ }
76+ horizontalAlignment: Text.AlignHCenter
77+ font.pixelSize: FontUtils.sizeToPixels("large")
78+ echoMode: TextInput.Password
79+ inputMethodHints: Qt.ImhHiddenText | Qt.ImhSensitiveData |
80+ Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText
81+ color: "#f3f3e7"
82+ cursorDelegate: Item {} // disable cursor
83+ onCursorPositionChanged: {
84+ // And because we don't show the cursor, always position the
85+ // cursor at the end of the string (so backspace works like
86+ // the user expects, even if they've clicked on us and
87+ // thus accidentally moved the cursor)
88+ if (cursorPosition !== length) {
89+ cursorPosition = length
90+ }
91+ }
92+ clip: true
93+
94+ // This is so that we can draw our own dots, for we want
95+ // complete control over the pixel sizes. (The ubuntu font
96+ // has oddly sized password characters that don't scale right)
97+ opacity: 0
98+
99+ // simulate being disabled, but without removing OSK focus
100+ maximumLength: root.entryEnabled ? 32767 : length
101+
102+ onTextChanged: incorrectOverride = true
103+
104+ onAccepted: {
105+ if (pinentryField.text) {
106+ root.entered(pinentryField.text);
107+ }
108+ }
72 }
109 }
73− height: units.gu(4.5)
110+
74− width: parent.width - units.gu(2)
111+ Row {
75− echoMode: TextInput.Password
112+ id: dotRow
76− opacity: 0.9
113+ anchors.centerIn: entryContainer
77− hasClearButton: false
114+
78− enabled: root.entryEnabled
115+ property real dotSize: Math.min(units.gu(2), entryContainer.width / pinentryField.length)
79− placeholderText: root.infoText
116+ spacing: Math.min(units.gu(2), Math.max(0, (entryContainer.width / pinentryField.length) - dotSize))
80−
117+
81− onAccepted: {
118+ Repeater {
82− if (pinentryField.text) {
119+ model: pinentryField.length
83− root.entered(pinentryField.text);
120+ delegate: Rectangle {
121+ color: "#f3f3e7"
122+ width: dotRow.dotSize
123+ height: width
124+ radius: width / 2
125+ }
84 }
126 }
85 }
127 }
128+
129+ Label {
130+ id: wrongNoticeLabel
131+ objectName: "wrongNoticeLabel"
132+ fontSize: "large"
133+ color: "#f3f3e7"
134+ anchors.horizontalCenter: parent.horizontalCenter
135+ horizontalAlignment: Text.AlignHCenter
136+ text: root.errorText
137+ visible: pinentryField.text.length == 0 && !pinentryField.incorrectOverride
138+ }
86 }
139 }
87 }
140 }
88
141
89 WrongPasswordAnimation {
142 WrongPasswordAnimation {
90 id: wrongPasswordAnimation
143 id: wrongPasswordAnimation
91 objectName: "wrongPasswordAnimation"
144 objectName: "wrongPasswordAnimation"
92− target: pinentryField
145+ target: shakeContainer
93 }
146 }
94}
147}
qml/Components/PinLockscreen.qml+7−5
@@ -21,7 +21,9 @@ import "../Components"
21
21
22Column {
22Column {
23 id: root
23 id: root
24− anchors.centerIn: parent
24+ anchors.top: parent.top
25+ anchors.topMargin: units.gu(4)
26+ anchors.horizontalCenter: parent.horizontalCenter
25 spacing: units.gu(2)
27 spacing: units.gu(2)
26
28
27 property string infoText
29 property string infoText
@@ -49,7 +51,7 @@ Column {
49 id: shakeContainer
51 id: shakeContainer
50 anchors.horizontalCenter: parent.horizontalCenter
52 anchors.horizontalCenter: parent.horizontalCenter
51 width: parent.width
53 width: parent.width
52− spacing: units.gu(2)
54+ spacing: units.gu(1)
53
55
54 Label {
56 Label {
55 id: infoField
57 id: infoField
@@ -193,19 +195,19 @@ Column {
193 }
195 }
194 PinPadButton {
196 PinPadButton {
195 iconName: "close"
197 iconName: "close"
196− height: numbersGrid.buttonHeight
198+ height: units.gu(5) // visual spec has this row a little closer in
197 width: numbersGrid.buttonWidth
199 width: numbersGrid.buttonWidth
198
200
199 onClicked: root.cancel()
201 onClicked: root.cancel()
200 }
202 }
201 Item {
203 Item {
202− height: numbersGrid.buttonHeight
204+ height: units.gu(5)
203 width: numbersGrid.buttonWidth
205 width: numbersGrid.buttonWidth
204 }
206 }
205 PinPadButton {
207 PinPadButton {
206 iconName: "tick"
208 iconName: "tick"
207 objectName: "confirmButton"
209 objectName: "confirmButton"
208− height: numbersGrid.buttonHeight
210+ height: units.gu(5)
209 width: numbersGrid.buttonWidth
211 width: numbersGrid.buttonWidth
210 enabled: root.enabled && pinentryField.text.length >= root.minPinLength
212 enabled: root.enabled && pinentryField.text.length >= root.minPinLength
211 visible: root.minPinLength == -1 || root.minPinLength !== root.maxPinLength
213 visible: root.minPinLength == -1 || root.minPinLength !== root.maxPinLength
qml/Greeter/Greeter.qml+1−1
@@ -154,7 +154,7 @@ Showable {
154 anchors.horizontalCenter: parent.horizontalCenter
154 anchors.horizontalCenter: parent.horizontalCenter
155 anchors.bottom: parent.bottom
155 anchors.bottom: parent.bottom
156 anchors.bottomMargin: units.gu(5)
156 anchors.bottomMargin: units.gu(5)
157− text: i18n.tr("Swipe to unlock")
157+ text: "《 " + i18n.tr("Unlock") + " 》"
158 color: "white"
158 color: "white"
159 font.weight: Font.Light
159 font.weight: Font.Light
160
160
qml/Shell.qml+30−9
@@ -60,7 +60,7 @@ Item {
60
60
61 property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
61 property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
62 property int failedLoginsDelayAttempts: 7 // number of failed logins
62 property int failedLoginsDelayAttempts: 7 // number of failed logins
63− property int failedLoginsDelaySeconds: 5 * 60 // seconds of forced waiting
63+ property int failedLoginsDelayMinutes: 5 // minutes of forced waiting
64
64
65 property int orientation
65 property int orientation
66 readonly property int deviceOrientationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
66 readonly property int deviceOrientationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
@@ -278,8 +278,6 @@ Item {
278 id: lockscreen
278 id: lockscreen
279 objectName: "lockscreen"
279 objectName: "lockscreen"
280
280
281− readonly property int backgroundTopMargin: -panel.panelHeight
282−
283 hides: [launcher, panel.indicators]
281 hides: [launcher, panel.indicators]
284 shown: false
282 shown: false
285 enabled: true
283 enabled: true
@@ -300,6 +298,19 @@ Item {
300
298
301 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
299 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
302
300
301+ Timer {
302+ id: forcedDelayTimer
303+ interval: 1000 * 60
304+ onTriggered: {
305+ if (lockscreen.delayMinutes > 0) {
306+ lockscreen.delayMinutes -= 1
307+ if (lockscreen.delayMinutes > 0) {
308+ start() // go again
309+ }
310+ }
311+ }
312+ }
313+
303 Component.onCompleted: {
314 Component.onCompleted: {
304 if (greeter.narrowMode) {
315 if (greeter.narrowMode) {
305 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
316 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
@@ -317,14 +328,15 @@ Item {
317 if (greeter.narrowMode) {
328 if (greeter.narrowMode) {
318 if (isDefaultPrompt) {
329 if (isDefaultPrompt) {
319 if (lockscreen.alphaNumeric) {
330 if (lockscreen.alphaNumeric) {
320− lockscreen.infoText = i18n.tr("Enter your passphrase")
331+ lockscreen.infoText = i18n.tr("Enter passphrase")
321− lockscreen.errorText = i18n.tr("Sorry, incorrect passphrase")
332+ lockscreen.errorText = i18n.tr("Sorry, incorrect passphrase") + "\n" +
333+ i18n.tr("Please re-enter")
322 } else {
334 } else {
323− lockscreen.infoText = i18n.tr("Enter your passcode")
335+ lockscreen.infoText = i18n.tr("Enter passcode")
324 lockscreen.errorText = i18n.tr("Sorry, incorrect passcode")
336 lockscreen.errorText = i18n.tr("Sorry, incorrect passcode")
325 }
337 }
326 } else {
338 } else {
327− lockscreen.infoText = i18n.tr("Enter your %1").arg(text.toLowerCase())
339+ lockscreen.infoText = i18n.tr("Enter %1").arg(text.toLowerCase())
328 lockscreen.errorText = i18n.tr("Sorry, incorrect %1").arg(text.toLowerCase())
340 lockscreen.errorText = i18n.tr("Sorry, incorrect %1").arg(text.toLowerCase())
329 }
341 }
330
342
@@ -372,7 +384,8 @@ Item {
372 }
384 }
373 }
385 }
374 if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
386 if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
375− lockscreen.forceDelay(failedLoginsDelaySeconds * 1000)
387+ lockscreen.delayMinutes = failedLoginsDelayMinutes
388+ forcedDelayTimer.start()
376 }
389 }
377
390
378 lockscreen.clear(true);
391 lockscreen.clear(true);
@@ -418,7 +431,15 @@ Item {
418 }
431 }
419
432
420 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
433 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
434+ onShowProgressChanged: {
435+ if (showProgress === 0) {
421− onShowProgressChanged: if (LightDM.Greeter.authenticated && showProgress === 0) greeter.login()
436+ if (LightDM.Greeter.authenticated) {
437+ greeter.login()
438+ } else if (greeter.narrowMode) {
439+ lockscreen.clear(false) // to reset focus if necessary
440+ }
441+ }
442+ }
422
443
423 Greeter {
444 Greeter {
424 id: greeter
445 id: greeter
tests/autopilot/unity8/shell/tests/test_lock_screen.py+3−1
@@ -132,8 +132,10 @@ class TestLockscreen(UnityTestCase):
132 )
132 )
133
133
134 pin_entry_field = self.main_window.get_pinentryField()
134 pin_entry_field = self.main_window.get_pinentryField()
135− pin_entry_field.write(passphrase)
135+ # pinentryField should automatically have focus
136+ self.keyboard.type(passphrase)
136 logger.debug("Typed passphrase: %s", pin_entry_field.text)
137 logger.debug("Typed passphrase: %s", pin_entry_field.text)
138+ self.assertEqual(pin_entry_field.text, passphrase)
137 self.keyboard.type("\n")
139 self.keyboard.type("\n")
138
140
139 def _enter_prompt_passphrase(self, passphrase):
141 def _enter_prompt_passphrase(self, passphrase):
tests/qmltests/CMakeLists.txt+1−1
@@ -26,6 +26,7 @@ add_qml_test(Components Carousel)
26add_qml_test(Components DraggingArea)
26add_qml_test(Components DraggingArea)
27add_qml_test(Components EdgeDemoOverlay)
27add_qml_test(Components EdgeDemoOverlay)
28add_qml_test(Components LazyImage)
28add_qml_test(Components LazyImage)
29+add_qml_test(Components Lockscreen)
29add_qml_test(Components Rating)
30add_qml_test(Components Rating)
30add_qml_test(Components ResponsiveFlowView)
31add_qml_test(Components ResponsiveFlowView)
31add_qml_test(Components ResponsiveGridView)
32add_qml_test(Components ResponsiveGridView)
@@ -60,7 +61,6 @@ add_qml_test(Dash/ScopeSettings ScopeSettingList)
60add_qml_test(Dash/ScopeSettings ScopeSettingNumber)
61add_qml_test(Dash/ScopeSettings ScopeSettingNumber)
61add_qml_test(Dash/ScopeSettings ScopeSettingString)
62add_qml_test(Dash/ScopeSettings ScopeSettingString)
62add_qml_test(Dash/ScopeSettings ScopeSettingsWidgetFactory)
63add_qml_test(Dash/ScopeSettings ScopeSettingsWidgetFactory)
63−add_qml_test(Greeter Lockscreen ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/libusermetrics:${CMAKE_BINARY_DIR}/tests/mocks/LightDM/full")
64add_qml_test(Greeter MultiGreeter ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/libusermetrics:${CMAKE_BINARY_DIR}/tests/mocks/LightDM/full")
64add_qml_test(Greeter MultiGreeter ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/libusermetrics:${CMAKE_BINARY_DIR}/tests/mocks/LightDM/full")
65add_qml_test(Greeter SingleGreeter ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/libusermetrics:${CMAKE_BINARY_DIR}/tests/mocks/LightDM/single")
65add_qml_test(Greeter SingleGreeter ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/libusermetrics:${CMAKE_BINARY_DIR}/tests/mocks/LightDM/single")
66add_qml_test(Greeter Infographics ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/libusermetrics:${CMAKE_BINARY_DIR}/tests/mocks/LightDM/full")
66add_qml_test(Greeter Infographics ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/libusermetrics:${CMAKE_BINARY_DIR}/tests/mocks/LightDM/full")
tests/qmltests/Greeter/tst_Lockscreen.qml → tests/qmltests/Components/tst_Lockscreen.qmlrenamed+31−51
@@ -18,15 +18,13 @@ import QtQuick 2.0
18import QtTest 1.0
18import QtTest 1.0
19import ".."
19import ".."
20import "../../../qml/Components"
20import "../../../qml/Components"
21−import AccountsService 0.1
22−import LightDM 0.1 as LightDM
23import Ubuntu.Components 0.1
21import Ubuntu.Components 0.1
24import Unity.Test 0.1 as UT
22import Unity.Test 0.1 as UT
25
23
26Rectangle {
24Rectangle {
27 id: root
25 id: root
28 width: units.gu(80)
26 width: units.gu(80)
29− height: units.gu(70)
27+ height: units.gu(76)
30 color: "orange"
28 color: "orange"
31
29
32 Lockscreen {
30 Lockscreen {
@@ -39,7 +37,7 @@ Rectangle {
39 alphaNumeric: pinPadCheckBox.checked
37 alphaNumeric: pinPadCheckBox.checked
40 minPinLength: minPinLengthTextField.text
38 minPinLength: minPinLengthTextField.text
41 maxPinLength: maxPinLengthTextField.text
39 maxPinLength: maxPinLengthTextField.text
42− username: "Lola"
40+ delayMinutes: delayMinutesTextField.text
43 background: "../../../qml/graphics/phone_background.jpg"
41 background: "../../../qml/graphics/phone_background.jpg"
44 }
42 }
45
43
@@ -53,24 +51,6 @@ Rectangle {
53 }
51 }
54 }
52 }
55
53
56− function setUser(username) {
57− AccountsService.user = username
58− LightDM.Greeter.authenticate(username)
59− }
60−
61− Connections {
62− target: LightDM.Greeter
63−
64− onShowPrompt: {
65− if (AccountsService.passwordDisplayHint === AccountsService.Numeric) {
66− pinPadCheckBox.checked = false
67− } else {
68− pinPadCheckBox.checked = true
69− }
70− lockscreen.infoText = text;
71− }
72− }
73−
74 Rectangle {
54 Rectangle {
75 anchors.fill: parent
55 anchors.fill: parent
76 anchors.leftMargin: lockscreen.width
56 anchors.leftMargin: lockscreen.width
@@ -129,6 +109,16 @@ Rectangle {
129 text: "Retries left"
109 text: "Retries left"
130 }
110 }
131 }
111 }
112+ Row {
113+ TextField {
114+ id: delayMinutesTextField
115+ width: units.gu(7)
116+ text: "0"
117+ }
118+ Label {
119+ text: "Delay Minutes"
120+ }
121+ }
132 Label {
122 Label {
133 id: pinLabel
123 id: pinLabel
134 anchors.verticalCenter: parent.verticalCenter
124 anchors.verticalCenter: parent.verticalCenter
@@ -141,17 +131,6 @@ Rectangle {
141 id: enteredLabel
131 id: enteredLabel
142 }
132 }
143 }
133 }
144− Button {
145− text: "start auth (1234)"
146− width: parent.width
147− onClicked: setUser("has-pin")
148− }
149− Button {
150− text: "start auth (password)"
151− width: parent.width
152− onClicked: setUser("has-password")
153− }
154−
155 TextField {
134 TextField {
156 id: infoTextTextField
135 id: infoTextTextField
157 width: parent.width
136 width: parent.width
@@ -193,6 +172,7 @@ Rectangle {
193
172
194 function cleanup() {
173 function cleanup() {
195 lockscreen.clear(false);
174 lockscreen.clear(false);
175+ delayMinutesTextField.text = "0"
196 }
176 }
197
177
198 function waitForLockscreenReady() {
178 function waitForLockscreenReady() {
@@ -237,8 +217,8 @@ Rectangle {
237
217
238 function test_labels_data() {
218 function test_labels_data() {
239 return [
219 return [
240− {tag: "numeric", alphanumeric: false, infoText: "Please enter your PIN", username: "foobar" },
220+ {tag: "numeric", alphanumeric: false, infoText: "Please enter your PIN" },
241− {tag: "alphanumeric", alphanumeric: true, infoText: "Please enter your password", username: "Lola" }
221+ {tag: "alphanumeric", alphanumeric: true, infoText: "Please enter your password" }
242 ]
222 ]
243 }
223 }
244
224
@@ -246,22 +226,16 @@ Rectangle {
246 pinPadCheckBox.checked = data.alphanumeric
226 pinPadCheckBox.checked = data.alphanumeric
247 lockscreen.infoText = data.infoText
227 lockscreen.infoText = data.infoText
248 waitForLockscreenReady();
228 waitForLockscreenReady();
249− if (data.alphanumeric) {
250− compare(findChild(lockscreen, "pinentryField").placeholderText, data.infoText, "Placeholdertext is not what it should be")
251− compare(findChild(lockscreen, "greeterLabel").text, "Hello " + data.username, "Greeter is not set correctly")
252− } else {
253− compare(findChild(lockscreen, "infoTextLabel").text, data.infoText, "Placeholdertext is not what it should be")
229+ compare(findChild(lockscreen, "infoTextLabel").text, data.infoText, "Placeholdertext is not what it should be")
254− }
255 }
230 }
256
231
257−
258 function test_unlock_data() {
232 function test_unlock_data() {
259 return [
233 return [
260− {tag: "numeric", alphanumeric: false, username: "has-pin", password: "1234", minPinLength: 4, maxPinLength: 4},
234+ {tag: "numeric", alphanumeric: false, password: "1234", minPinLength: 4, maxPinLength: 4},
261− {tag: "alphanumeric", alphanumeric: true, username: "has-password", password: "password", minPinLength: -1, maxPinLength: -1},
235+ {tag: "alphanumeric", alphanumeric: true, password: "password", minPinLength: -1, maxPinLength: -1},
262− {tag: "numeric (wrong)", alphanumeric: false, username: "has-pin", password: "4321", minPinLength: 4, maxPinLength: 4},
236+ {tag: "numeric (wrong)", alphanumeric: false, password: "4321", minPinLength: 4, maxPinLength: 4},
263− {tag: "alphanumeric (wrong)", alphanumeric: true, username: "has-password", password: "drowssap", minPinLength: -1, maxPinLength: -1},
237+ {tag: "alphanumeric (wrong)", alphanumeric: true, password: "drowssap", minPinLength: -1, maxPinLength: -1},
264− {tag: "flexible length", alphanumeric: false, username: "has-pin", password: "1234", minPinLength: -1, maxPinLength: -1},
238+ {tag: "flexible length", alphanumeric: false, password: "1234", minPinLength: -1, maxPinLength: -1},
265 ]
239 ]
266 }
240 }
267
241
@@ -269,13 +243,13 @@ Rectangle {
269 enteredLabel.text = ""
243 enteredLabel.text = ""
270 minPinLengthTextField.text = data.minPinLength
244 minPinLengthTextField.text = data.minPinLength
271 maxPinLengthTextField.text = data.maxPinLength
245 maxPinLengthTextField.text = data.maxPinLength
272− setUser(data.username)
246+ pinPadCheckBox.checked = data.alphanumeric
247+ infoTextTextField.text = "Enter " + (data.alphanumeric ? "passphrase" : "passcode")
273 waitForLockscreenReady();
248 waitForLockscreenReady();
274
249
275 var inputField = findChild(lockscreen, "pinentryField")
250 var inputField = findChild(lockscreen, "pinentryField")
276 if (data.alphanumeric) {
251 if (data.alphanumeric) {
277− mouseClick(inputField, units.gu(1), units.gu(1))
278− tryCompare(inputField, "focus", true);
252+ tryCompare(inputField, "activeFocus", true);
279 typeString(data.password)
253 typeString(data.password)
280 keyClick(Qt.Key_Enter)
254 keyClick(Qt.Key_Enter)
281 } else {
255 } else {
@@ -305,7 +279,6 @@ Rectangle {
305
279
306 var inputField = findChild(lockscreen, "pinentryField")
280 var inputField = findChild(lockscreen, "pinentryField")
307 if (data.alphanumeric) {
281 if (data.alphanumeric) {
308− mouseClick(inputField, units.gu(1), units.gu(1))
309 tryCompare(inputField, "activeFocus", true);
282 tryCompare(inputField, "activeFocus", true);
310 typeString("1")
283 typeString("1")
311 } else {
284 } else {
@@ -470,6 +443,13 @@ Rectangle {
470 var label = findChild(lockscreen, "infoTextLabel")
443 var label = findChild(lockscreen, "infoTextLabel")
471 compare(label.text, data.text);
444 compare(label.text, data.text);
472 }
445 }
446+
447+ function test_delayMinutes() {
448+ delayMinutesTextField.text = "4"
449+ waitForLockscreenReady()
450+ var label = findChild(lockscreen, "deviceLockedLabel")
451+ compare(label.text, "Device Locked")
452+ }
473 }
453 }
474
454
475 SignalSpy {
455 SignalSpy {
tests/qmltests/tst_ShellWithPin.qml+3−1
@@ -230,8 +230,10 @@ Item {
230 enterPin("1111")
230 enterPin("1111")
231 tryCompare(placeHolder, "text", "Sorry, incorrect passcode")
231 tryCompare(placeHolder, "text", "Sorry, incorrect passcode")
232
232
233+ var lockscreen = findChild(shell, "lockscreen")
234+ tryCompare(lockscreen, "delayMinutes", 0)
233 enterPin("1111")
235 enterPin("1111")
234− tryCompare(placeHolder, "text", "Too many incorrect attempts")
236+ tryCompare(lockscreen, "delayMinutes", shell.failedLoginsDelayMinutes)
235 }
237 }
236
238
237 function test_factoryReset() {
239 function test_factoryReset() {