@@ -60,7 +60,7 @@ Item {
6060
6161 property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
6262 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
6464
6565 property int orientation
6666 readonly property int deviceOrientationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
@@ -87,9 +87,9 @@ Item {
8787 }
8888 }
8989
90− function setFakeActiveForApp(app) {
90+ function setLockedApp(app) {
9191 if (shell.locked) {
92− greeter.fakeActiveForApp = app
92+ greeter.lockedApp = app
9393 lockscreen.hide()
9494 }
9595 }
@@ -128,6 +128,13 @@ Item {
128128 objectName: "dashCommunicator"
129129 }
130130
131+ Binding {
132+ target: ApplicationManager
133+ property: "forceDashActive"
134+ value: launcher.shown || launcher.dashSwipe
135+ }
136+
137+
131138 WindowKeysFilter {
132139 // Handle but do not filter out volume keys
133140 Keys.onVolumeUpPressed: { volumeControl.volumeUp(); event.accepted = false; }
@@ -162,32 +169,37 @@ Item {
162169 Connections {
163170 target: ApplicationManager
164171 onFocusRequested: {
165− if (appId === "dialer-app") {
166− // Always let the dialer-app through. Either user asked
167− // for an emergency call or accepted an incoming call.
168− setFakeActiveForApp(appId)
169− } else if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== appId) {
170− lockscreen.show();
172+ if (greeter.narrowMode) {
173+ if (appId === "dialer-app") {
174+ // Always let the dialer-app through. Either user asked
175+ // for an emergency call or accepted an incoming call.
176+ setLockedApp(appId)
177+ } else if (greeter.hasLockedApp && greeter.lockedApp !== appId) {
178+ greeter.startUnlock()
179+ }
180+ greeter.hide();
181+ } else {
182+ if (LightDM.Greeter.active) {
183+ greeter.startUnlock()
184+ }
171185 }
172− greeter.hide();
173− launcher.hide();
174186 }
175187
176188 onFocusedApplicationIdChanged: {
177− if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== ApplicationManager.focusedApplicationId) {
178− lockscreen.show();
189+ if (greeter.hasLockedApp && greeter.lockedApp !== ApplicationManager.focusedApplicationId) {
190+ greeter.startUnlock()
179191 }
180192 panel.indicators.hide();
181193 }
182194
183195 onApplicationAdded: {
184196 if (greeter.shown && appId != "unity8-dash") {
185− greeter.hide();
197+ greeter.startUnlock()
186198 }
187− if (appId === "dialer-app") {
199+ if (greeter.narrowMode && appId === "dialer-app") {
188200 // Always let the dialer-app through. Either user asked
189201 // for an emergency call or accepted an incoming call.
190− setFakeActiveForApp(appId)
202+ setLockedApp(appId)
191203 }
192204 launcher.hide();
193205 }
@@ -195,9 +207,19 @@ Item {
195207
196208 Loader {
197209 id: applicationsDisplayLoader
210+ objectName: "applicationsDisplayLoader"
198211 anchors.fill: parent
199212
200− source: shell.sideStageEnabled ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
213+ // When we have a locked app, we only want to show that one app.
214+ // FIXME: do this in a less traumatic way. We currently only allow
215+ // locked apps in phone mode (see FIXME in Lockscreen component in
216+ // this same file). When that changes, we need to do something
217+ // nicer here. But this code is currently just to prevent a
218+ // theoretical attack where user enters lockedApp mode, then makes
219+ // the screen larger (maybe connects to monitor) and tries to enter
220+ // tablet mode.
221+ property bool tabletMode: shell.sideStageEnabled && !greeter.hasLockedApp
222+ source: tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
201223
202224 Binding {
203225 target: applicationsDisplayLoader.item
@@ -223,7 +245,7 @@ Item {
223245 Binding {
224246 target: applicationsDisplayLoader.item
225247 property: "spreadEnabled"
226− value: edgeDemo.stagesEnabled && greeter.fakeActiveForApp === "" // to support emergency dialer hack
248+ value: edgeDemo.stagesEnabled && !greeter.hasLockedApp
227249 }
228250 Binding {
229251 target: applicationsDisplayLoader.item
@@ -278,8 +300,6 @@ Item {
278300 id: lockscreen
279301 objectName: "lockscreen"
280302
281− readonly property int backgroundTopMargin: -panel.panelHeight
282−
283303 hides: [launcher, panel.indicators]
284304 shown: false
285305 enabled: true
@@ -294,11 +314,33 @@ Item {
294314 minPinLength: 4
295315 maxPinLength: 4
296316
317+ // FIXME: We *should* show emergency dialer if there is a SIM present,
318+ // regardless of whether the side stage is enabled. But right now,
319+ // the assumption is that narrow screens are phones which have SIMs
320+ // and wider screens are tablets which don't. When we do allow this
321+ // on devices with a side stage and a SIM, work should be done to
322+ // ensure that the main stage is disabled while the dialer is present
323+ // in the side stage. See the FIXME in the stage loader in this file.
324+ showEmergencyCallButton: !shell.sideStageEnabled
325+
297326 onEntered: LightDM.Greeter.respond(passphrase);
298327 onCancel: greeter.show()
299− onEmergencyCall: shell.activateApplication("dialer-app") // will automatically enter fake-active mode
300−
301− onShownChanged: if (shown) greeter.fakeActiveForApp = ""
328+ onEmergencyCall: shell.activateApplication("dialer-app") // will automatically enter locked-app mode
329+
330+ onShownChanged: if (shown) greeter.lockedApp = ""
331+
332+ Timer {
333+ id: forcedDelayTimer
334+ interval: 1000 * 60
335+ onTriggered: {
336+ if (lockscreen.delayMinutes > 0) {
337+ lockscreen.delayMinutes -= 1
338+ if (lockscreen.delayMinutes > 0) {
339+ start() // go again
340+ }
341+ }
342+ }
343+ }
302344
303345 Component.onCompleted: {
304346 if (greeter.narrowMode) {
@@ -317,14 +359,15 @@ Item {
317359 if (greeter.narrowMode) {
318360 if (isDefaultPrompt) {
319361 if (lockscreen.alphaNumeric) {
320− lockscreen.infoText = i18n.tr("Enter your passphrase")
321− lockscreen.errorText = i18n.tr("Sorry, incorrect passphrase")
362+ lockscreen.infoText = i18n.tr("Enter passphrase")
363+ lockscreen.errorText = i18n.tr("Sorry, incorrect passphrase") + "\n" +
364+ i18n.tr("Please re-enter")
322365 } else {
323− lockscreen.infoText = i18n.tr("Enter your passcode")
366+ lockscreen.infoText = i18n.tr("Enter passcode")
324367 lockscreen.errorText = i18n.tr("Sorry, incorrect passcode")
325368 }
326369 } else {
327− lockscreen.infoText = i18n.tr("Enter your %1").arg(text.toLowerCase())
370+ lockscreen.infoText = i18n.tr("Enter %1").arg(text.toLowerCase())
328371 lockscreen.errorText = i18n.tr("Sorry, incorrect %1").arg(text.toLowerCase())
329372 }
330373
@@ -333,11 +376,13 @@ Item {
333376 }
334377
335378 onPromptlessChanged: {
336− if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
337− lockscreen.hide()
338− } else {
339− lockscreen.reset();
340− lockscreen.show();
379+ if (greeter.narrowMode) {
380+ if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
381+ lockscreen.hide()
382+ } else {
383+ lockscreen.reset();
384+ lockscreen.show();
385+ }
341386 }
342387 }
343388
@@ -372,7 +417,8 @@ Item {
372417 }
373418 }
374419 if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
375− lockscreen.forceDelay(failedLoginsDelaySeconds * 1000)
420+ lockscreen.delayMinutes = failedLoginsDelayMinutes
421+ forcedDelayTimer.start()
376422 }
377423
378424 lockscreen.clear(true);
@@ -386,7 +432,7 @@ Item {
386432 Binding {
387433 target: LightDM.Greeter
388434 property: "active"
389− value: greeter.shown || lockscreen.shown || greeter.fakeActiveForApp != ""
435+ value: greeter.shown || lockscreen.shown || greeter.hasLockedApp
390436 }
391437
392438 Rectangle {
@@ -398,7 +444,8 @@ Item {
398444 Item {
399445 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
400446 id: greeterWrapper
401− x: launcher.progress
447+ objectName: "greeterWrapper"
448+ x: greeter.narrowMode ? launcher.progress : 0
402449 y: panel.panelHeight
403450 width: parent.width
404451 height: parent.height - panel.panelHeight
@@ -411,14 +458,22 @@ Item {
411458 property bool fullyShown: showProgress === 1.0
412459 onFullyShownChanged: {
413460 // Wait until the greeter is completely covering lockscreen before resetting it.
414− if (fullyShown && !LightDM.Greeter.authenticated) {
461+ if (greeter.narrowMode && fullyShown && !LightDM.Greeter.authenticated) {
415462 lockscreen.reset();
416463 lockscreen.show();
417464 }
418465 }
419466
420467 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
421− onShowProgressChanged: if (LightDM.Greeter.authenticated && showProgress === 0) greeter.login()
468+ onShowProgressChanged: {
469+ if (showProgress === 0) {
470+ if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
471+ greeter.login()
472+ } else if (greeter.narrowMode) {
473+ lockscreen.clear(false) // to reset focus if necessary
474+ }
475+ }
476+ }
422477
423478 Greeter {
424479 id: greeter
@@ -426,13 +481,16 @@ Item {
426481
427482 signal sessionStarted() // helpful for tests
428483
429− property string fakeActiveForApp: ""
484+ property string lockedApp: ""
485+ property bool hasLockedApp: lockedApp !== ""
430486
431487 available: true
432488 hides: [launcher, panel.indicators]
433489 shown: true
434490 loadContent: required || lockscreen.required // keeps content in memory for quick show()
435491
492+ locked: shell.locked
493+
436494 defaultBackground: shell.background
437495
438496 width: parent.width
@@ -440,6 +498,18 @@ Item {
440498
441499 dragHandleWidth: shell.edgeSize
442500
501+ function startUnlock() {
502+ if (narrowMode) {
503+ if (!LightDM.Greeter.authenticated) {
504+ lockscreen.show()
505+ }
506+ hide()
507+ } else {
508+ show()
509+ tryToUnlock()
510+ }
511+ }
512+
443513 function login() {
444514 enabled = false;
445515 if (LightDM.Greeter.startSessionSync()) {
@@ -455,8 +525,10 @@ Item {
455525 if (shown) {
456526 if (greeter.narrowMode) {
457527 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
528+ } else {
529+ reset()
458530 }
459− greeter.fakeActiveForApp = "";
531+ greeter.lockedApp = "";
460532 greeter.forceActiveFocus();
461533 }
462534 }
@@ -501,10 +573,7 @@ Item {
501573 }
502574
503575 if (LightDM.Greeter.active) {
504− if (!LightDM.Greeter.authenticated) {
505− lockscreen.show()
506− }
507− greeter.hide()
576+ greeter.startUnlock()
508577 }
509578
510579 var animate = !LightDM.Greeter.active && !stages.shown
@@ -513,16 +582,20 @@ Item {
513582 }
514583
515584 function showDash() {
516− if (shell.locked) {
517− return;
585+ if (greeter.hasLockedApp || // just in case user gets here
586+ (!greeter.narrowMode && shell.locked)) {
587+ return
518588 }
589+
519590 if (greeter.shown) {
520591 greeter.hideRight();
521592 launcher.fadeOut();
522593 }
523594
524− ApplicationManager.requestFocusApplication("unity8-dash")
525− launcher.fadeOut();
595+ if (ApplicationManager.focusedApplicationId != "unity8-dash") {
596+ ApplicationManager.requestFocusApplication("unity8-dash")
597+ launcher.fadeOut();
598+ }
526599 }
527600
528601 Item {
@@ -537,7 +610,7 @@ Item {
537610 anchors.fill: parent //because this draws indicator menus
538611 indicators {
539612 hides: [launcher]
540− available: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && greeter.fakeActiveForApp === ""
613+ available: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp
541614 contentEnabled: edgeDemo.panelContentEnabled
542615 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
543616 panelHeight: units.gu(3)
@@ -548,7 +621,7 @@ Item {
548621 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
549622
550623 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
551− || greeter.fakeActiveForApp !== ""
624+ || greeter.hasLockedApp
552625 }
553626
554627 Launcher {
@@ -561,26 +634,18 @@ Item {
561634 anchors.bottom: parent.bottom
562635 width: parent.width
563636 dragAreaWidth: shell.edgeSize
564− available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && greeter.fakeActiveForApp === ""
637+ available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp
565638
566639 onShowDashHome: showHome()
567− onDash: {
568− if (ApplicationManager.focusedApplicationId != "unity8-dash") {
569− showDash()
570− }
571− if (greeter.shown) {
572− launcher.fadeOut()
573− greeter.hideRight()
574− }
575− }
640+ onDash: showDash()
576641 onDashSwipeChanged: {
577642 if (dashSwipe) {
578643 dash.setCurrentScope("clickscope", false, true)
579644 }
580645 }
581646 onLauncherApplicationSelected: {
582− if (greeter.fakeActiveForApp !== "") {
583− lockscreen.show()
647+ if (greeter.hasLockedApp) {
648+ greeter.startUnlock()
584649 }
585650 if (!edgeDemo.running)
586651 shell.activateApplication(appId)