@@ -14,36 +14,38 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17−import QtQuick 2.0
18−import Ubuntu.Components 0.1
19−import Ubuntu.Gestures 0.1
17+import QtQuick 2.3
18+import AccountsService 0.1
2019import LightDM 0.1 as LightDM
20+import Ubuntu.Components 1.1
2121import "../Components"
2222
2323Showable {
2424 id: greeter
25− enabled: shown
2625 created: greeterContentLoader.status == Loader.Ready && greeterContentLoader.item.ready
2726
28− x: launcherOffsetProxy + dragOffset
29−
3027 property url background
31− property bool loadContent: required
3228
3329 // How far to offset the top greeter layer during a launcher left-drag
3430 property real launcherOffset
3531
3632 // 1 when fully shown and 0 when fully hidden
37− property real showProgress: MathUtils.clamp((width - Math.abs(x)) / width, 0, 1)
33+ property real showProgress: MathUtils.clamp((width - Math.abs(greeterContentLoader.x)) / width, 0, 1)
34+
35+ // True when the greeter is waiting for PAM or other setup process
36+ property bool waiting: true
3837
3938 showAnimation: StandardAnimation { property: "dragOffset"; to: 0; duration: UbuntuAnimation.FastDuration }
4039 hideAnimation: __leftHideAnimation
4140
42− property alias dragHandleWidth: dragHandle.width
43− property alias model: greeterContentLoader.model
44− property bool locked: true
41+ property alias lockscreen: lockscreen
42+
43+ readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated && !forcedUnlock
44+ property bool forcedUnlock
45+ onForcedUnlockChanged: if (forcedUnlock) lockscreen.hide()
4546
46− readonly property bool narrowMode: !multiUser && height > width
47+ property bool tabletMode
48+ readonly property bool narrowMode: !multiUser && !tabletMode
4749 readonly property bool multiUser: LightDM.Users.count > 1
4850
4951 readonly property int currentIndex: greeterContentLoader.currentIndex
@@ -82,13 +84,13 @@ Showable {
8284 }
8385
8486 function tryToUnlock() {
85− if (created) {
87+ if (greeterContentLoader.item) {
8688 greeterContentLoader.item.tryToUnlock()
8789 }
8890 }
8991
9092 function reset() {
91− if (created) {
93+ if (greeterContentLoader.item) {
9294 greeterContentLoader.item.reset()
9395 }
9496 }
@@ -109,74 +111,188 @@ Showable {
109111 }
110112 }
111113
112− // Bi-directional revealer
113− DraggingArea {
114− id: dragHandle
115− anchors.fill: parent
116− enabled: (greeter.narrowMode || !greeter.locked) && greeter.enabled && greeter.shown
117− orientation: Qt.Horizontal
118− propagateComposedEvents: true
114+ onShownChanged: {
115+ if (shown) {
116+ waiting = true;
119117
120− Component.onCompleted: {
121− // set evaluators to baseline of dragValue == 0
122− leftEvaluator.reset()
123− rightEvaluator.reset()
118+ if (narrowMode) {
119+ LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
120+ } else {
121+ reset();
122+ }
123+ lockedApp = "";
124+ forceActiveFocus();
124125 }
126+ }
125127
126− function maybeTease() {
127− if (!greeter.locked || greeter.narrowMode)
128− greeter.tease();
128+ Lockscreen {
129+ id: lockscreen
130+ objectName: "lockscreen"
131+
132+ shown: false
133+ showAnimation: StandardAnimation { property: "opacity"; to: 1 }
134+ hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
135+ anchors.fill: parent
136+ visible: required
137+ background: greeter.background
138+ darkenBackground: 0.4
139+ alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
140+ minPinLength: 4
141+ maxPinLength: 4
142+
143+ property string promptText
144+ infoText: promptText !== "" ? i18n.tr("Enter %1").arg(promptText) :
145+ alphaNumeric ? i18n.tr("Enter passphrase") :
146+ i18n.tr("Enter passcode")
147+ errorText: promptText !== "" ? i18n.tr("Sorry, incorrect %1").arg(promptText) :
148+ alphaNumeric ? i18n.tr("Sorry, incorrect passphrase") + "\n" +
149+ i18n.tr("Please re-enter") :
150+ i18n.tr("Sorry, incorrect passcode")
151+
152+ // FIXME: We *should* show emergency dialer if there is a SIM present,
153+ // regardless of whether the side stage is enabled. But right now,
154+ // the assumption is that narrow screens are phones which have SIMs
155+ // and wider screens are tablets which don't. When we do allow this
156+ // on devices with a side stage and a SIM, work should be done to
157+ // ensure that the main stage is disabled while the dialer is present
158+ // in the side stage. See the FIXME in the stage loader in Shell.qml.
159+ showEmergencyCallButton: !greeter.tabletMode
160+
161+ onEntered: LightDM.Greeter.respond(passphrase);
162+ onCancel: greeter.show()
163+ onEmergencyCall: startLockedApp("dialer-app")
164+
165+ onShownChanged: if (shown) greeter.lockedApp = ""
166+
167+ function maybeShow() {
168+ if (!greeter.forcedUnlock) {
169+ show();
170+ }
129171 }
130172
131− onClicked: maybeTease()
132− onDragStart: maybeTease()
133− onPressAndHold: {} // eat event, but no need to tease, as drag will cover it
173+ Timer {
174+ id: forcedDelayTimer
175+ interval: 1000 * 60
176+ onTriggered: {
177+ if (lockscreen.delayMinutes > 0) {
178+ lockscreen.delayMinutes -= 1
179+ if (lockscreen.delayMinutes > 0) {
180+ start() // go again
181+ }
182+ }
183+ }
184+ }
134185
135− onDragEnd: {
136− if (greeter.dragOffset > 0 && rightEvaluator.shouldAutoComplete()) {
137− greeter.hideRight()
138− } else if (greeter.dragOffset < 0 && leftEvaluator.shouldAutoComplete()) {
139− greeter.hide();
140− } else {
141− greeter.show(); // undo drag
186+ Component.onCompleted: {
187+ if (greeter.narrowMode) {
188+ LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
142189 }
143190 }
191+ }
192+
193+ Connections {
194+ target: LightDM.Greeter
144195
145− onDragValueChanged: {
146− // dragValue is kept as a "step" value since we do this adjusting on the fly
147− greeter.dragOffset += dragValue;
196+ onShowGreeter: greeter.show()
197+ onHideGreeter: greeter.login()
198+
199+ onShowPrompt: {
200+ waiting = false;
201+
202+ if (!LightDM.Greeter.active) {
203+ return; // could happen if hideGreeter() comes in before we prompt
204+ }
205+
206+ if (greeter.narrowMode) {
207+ lockscreen.promptText = isDefaultPrompt ? "" : text.toLowerCase();
208+ lockscreen.maybeShow();
209+ }
148210 }
149211
150− EdgeDragEvaluator {
151− id: rightEvaluator
152− trackedPosition: dragHandle.dragValue + greeter.dragOffset
153− maxDragDistance: parent.width
154− direction: Direction.Rightwards
212+ onPromptlessChanged: {
213+ if (!LightDM.Greeter.active) {
214+ return; // could happen if hideGreeter() comes in before we prompt
215+ }
216+ if (greeter.narrowMode) {
217+ if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
218+ lockscreen.hide()
219+ } else {
220+ lockscreen.reset();
221+ lockscreen.maybeShow();
222+ }
223+ }
155224 }
156225
157− EdgeDragEvaluator {
158− id: leftEvaluator
159− trackedPosition: dragHandle.dragValue + greeter.dragOffset
160− maxDragDistance: parent.width
161− direction: Direction.Leftwards
226+ onAuthenticationComplete: {
227+ waiting = false;
228+ if (LightDM.Greeter.authenticated) {
229+ AccountsService.failedLogins = 0
230+ }
231+ // Else only penalize user for a failed login if they actually were
232+ // prompted for a password. We do this below after the promptless
233+ // early exit.
234+
235+ if (LightDM.Greeter.promptless) {
236+ return;
237+ }
238+
239+ if (LightDM.Greeter.authenticated) {
240+ greeter.login();
241+ } else {
242+ AccountsService.failedLogins++
243+ if (maxFailedLogins >= 2) { // require at least a warning
244+ if (AccountsService.failedLogins === maxFailedLogins - 1) {
245+ var title = lockscreen.alphaNumeric ?
246+ i18n.tr("Sorry, incorrect passphrase.") :
247+ i18n.tr("Sorry, incorrect passcode.")
248+ var text = i18n.tr("This will be your last attempt.") + " " +
249+ (lockscreen.alphaNumeric ?
250+ i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
251+ i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
252+ lockscreen.showInfoPopup(title, text)
253+ } else if (AccountsService.failedLogins >= maxFailedLogins) {
254+ SystemImage.factoryReset() // Ouch!
255+ }
256+ }
257+ if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
258+ lockscreen.delayMinutes = failedLoginsDelayMinutes
259+ forcedDelayTimer.start()
260+ }
261+
262+ lockscreen.clear(true);
263+ if (greeter.narrowMode) {
264+ LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
265+ }
266+ }
162267 }
163268 }
164− TouchGate {
165− targetItem: dragHandle
166− anchors.fill: targetItem
167− enabled: targetItem.enabled
269+
270+ Binding {
271+ target: LightDM.Greeter
272+ property: "active"
273+ value: greeter.shown || lockscreen.shown || greeter.hasLockedApp
274+ }
275+
276+ Rectangle {
277+ anchors.fill: parent
278+ color: "black"
279+ opacity: greeter.showProgress * 0.8
168280 }
169281
170282 Loader {
171283 id: greeterContentLoader
172284 objectName: "greeterContentLoader"
173− anchors.fill: parent
285+
286+ x: launcherOffsetProxy + dragOffset
287+ width: parent.width
288+ height: parent.height
289+
174290 property var model: LightDM.Users
175291 property int currentIndex: 0
176292 property var infographicModel: LightDM.Infographic
177293 readonly property int backgroundTopMargin: -greeter.y
178294
179− source: loadContent ? "GreeterContent.qml" : ""
295+ source: (greeter.required || lockscreen.required) ? "GreeterContent.qml" : ""
180296
181297 onLoaded: {
182298 selected(currentIndex);
@@ -190,59 +306,7 @@ Showable {
190306 greeterContentLoader.currentIndex = uid;
191307 }
192308 onUnlocked: greeter.unlocked(uid);
309+ onTease: greeter.tease()
193310 }
194311 }
195−
196− onTease: showLabelAnimation.start()
197−
198− Label {
199− id: swipeHint
200− visible: greeter.shown
201− property real baseOpacity: 0.5
202− opacity: 0.0
203− anchors.horizontalCenter: parent.horizontalCenter
204− anchors.bottom: parent.bottom
205− anchors.bottomMargin: units.gu(5)
206− text: "《 " + i18n.tr("Unlock") + " 》"
207− color: "white"
208− font.weight: Font.Light
209−
210− SequentialAnimation on opacity {
211− id: showLabelAnimation
212− running: false
213− loops: 2
214−
215− StandardAnimation {
216− from: 0.0
217− to: swipeHint.baseOpacity
218− duration: UbuntuAnimation.SleepyDuration
219− }
220− PauseAnimation { duration: UbuntuAnimation.BriskDuration }
221− StandardAnimation {
222− from: swipeHint.baseOpacity
223− to: 0.0
224− duration: UbuntuAnimation.SleepyDuration
225− }
226− }
227− }
228−
229− // right side shadow
230− Image {
231− anchors.left: parent.right
232− anchors.top: parent.top
233− anchors.bottom: parent.bottom
234− visible: parent.required
235− fillMode: Image.Tile
236− source: "../graphics/dropshadow_right.png"
237− }
238−
239− // left side shadow
240− Image {
241− anchors.right: parent.left
242− anchors.top: parent.top
243− anchors.bottom: parent.bottom
244− visible: parent.required
245− fillMode: Image.Tile
246− source: "../graphics/dropshadow_left.png"
247− }
248312}