@@ -24,358 +24,287 @@ import "../Components"
2424
2525Showable {
2626 id: greeter
27− created: greeterContentLoader.status == Loader.Ready && greeterContentLoader.item.ready
27+ created: loader.status == Loader.Ready
2828
2929 property url background
3030
3131 // How far to offset the top greeter layer during a launcher left-drag
3232 property real launcherOffset
3333
34− // 1 when fully shown and 0 when fully hidden
35− readonly property real showProgress: MathUtils.clamp((width - Math.abs(greeterContentLoader.x)) / width, 0, 1)
36− readonly property bool fullyShown: showProgress === 1 || lockscreen.shown
37− readonly property bool semiShown: shown || lockscreen.shown
34+ readonly property bool active: shown || hasLockedApp
35+ readonly property bool fullyShown: loader.item ? loader.item.fullyShown : false
3836
3937 // True when the greeter is waiting for PAM or other setup process
40− property bool waiting: true
38+ readonly property alias waiting: d.waiting
4139
4240 property string lockedApp: ""
43− property bool hasLockedApp: lockedApp !== ""
41+ readonly property bool hasLockedApp: lockedApp !== ""
4442
45− showAnimation: d.showAnimation
46− hideAnimation: d.leftHideAnimation
47−
48− readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated && !forcedUnlock
4943 property bool forcedUnlock
50− onForcedUnlockChanged: if (forcedUnlock) lockscreen.hide()
44+ readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated && !forcedUnlock
5145
5246 property bool tabletMode
53− readonly property bool narrowMode: !multiUser && !tabletMode
54− readonly property bool multiUser: LightDM.Users.count > 1
55−
56− property real dragOffset
57−
58− // We define a proxy and "is valid" property for launcherOffset because of
59− // a quirk in Qml. We only want this animation to fire if we are reset
60− // back to zero (on a release of the drag). But by defining a Behavior,
61− // we delay the property from reaching zero until it's too late. So we set
62− // a proxy bound to launcherOffset, which lets us see the target value of
63− // zero as we also slowly adjust the proxy down to zero. But Qml will send
64− // change notifications in declaration order. So unless we define the
65− // proxy first, we need a little "is valid" property defined above the
66− // proxy, so we know when to enable the proxy behavior. Phew!
67− readonly property bool launcherOffsetValid: launcherOffset > 0
68− property real launcherOffsetProxy: shown ? launcherOffset : 0
69− Behavior on launcherOffsetProxy {
70− enabled: !launcherOffsetValid
71− StandardAnimation {}
72− }
47+
48+ property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
49+ property int failedLoginsDelayAttempts: 7 // number of failed logins
50+ property int failedLoginsDelayMinutes: 5 // minutes of forced waiting
7351
7452 signal tease()
7553 signal sessionStarted()
7654 signal emergencyCall()
7755
78− function hideRight() {
79− if (shown) {
80− hideAnimation = d.rightHideAnimation;
81− hide()
82− }
83− }
84−
8556 function notifyAppFocused(appId) {
86− if (narrowMode) {
87− if (hasLockedApp) {
88− if (appId === lockedApp) {
89− lockscreen.hide(); // show locked app
90− } else {
91− startUnlock(); // show lockscreen if necessary
92− }
93− }
94− hide();
95− } else {
96− if (LightDM.Greeter.active) {
97− startUnlock();
98− }
99− }
100− }
101−
102− // Do we need these next two functions, really?
103− function notifyFocusChanged(appId) {
104− if (hasLockedApp && lockedApp !== appId) {
105− startUnlock();
106− }
107− }
108−
109− function notifyAppAdded(appId) {
110− if (shown && appId != "unity8-dash") {
111− startUnlock();
112− }
113− if (narrowMode && hasLockedApp && appId === lockedApp) {
114− lockscreen.hide(); // show locked app
57+ if (!active) {
58+ return;
11559 }
116− }
11760
118− function notifyAboutToStartApp(appId) {
11961 if (hasLockedApp) {
120− startUnlock();
121− }
122− }
123−
124− function startUnlock() {
125− if (narrowMode) {
126− if (!LightDM.Greeter.authenticated) {
127− lockscreen.maybeShow();
128− }
129− hide();
130− } else {
131− show();
132− if (greeterContentLoader.item) {
133− greeterContentLoader.item.tryToUnlock();
62+ if (appId === lockedApp) {
63+ hide(); // show locked app
64+ } else {
65+ show();
66+ d.startUnlock(false);
13467 }
68+ } else if (appId !== "unity8-dash") { // dash isn't started by user
69+ d.startUnlock(false);
13570 }
13671 }
13772
138− onRequiredChanged: {
139− // Reset hide animation to default once we're finished with it
140− if (required) {
141− // Reset hide animation so that a hide() call is reliably left
142− hideAnimation = d.leftHideAnimation;
73+ function notifyAboutToFocusApp(appId) {
74+ if (!active) {
75+ return;
14376 }
144− }
145−
146− onShownChanged: {
147− if (shown) {
148− waiting = true;
14977
150− if (narrowMode) {
151− d.selectUser(greeterContentLoader.currentIndex);
152− } else {
153− d.reset();
154− }
155− lockedApp = "";
156− forceActiveFocus();
157− }
78+ // A hint that we're about to focus an app. This way we can look
79+ // a little more responsive, rather than waiting for the above
80+ // notifyAppFocused call. We also need this in case we have a locked
81+ // app, in order to show lockscreen instead of new app.
82+ d.startUnlock(false);
15883 }
15984
160− onFullyShownChanged: {
161− // Wait until the greeter is completely covering lockscreen before resetting it.
162− if (narrowMode && fullyShown && !LightDM.Greeter.authenticated) {
163− lockscreen.reset();
164− lockscreen.maybeShow();
85+ function notifyShowingDashFromDrag() {
86+ if (!active) {
87+ return;
16588 }
166− }
16789
168− onShowProgressChanged: {
169− if (showProgress === 0) {
170− if ((LightDM.Greeter.promptless && LightDM.Greeter.authenticated) || forcedUnlock) {
171− d.login();
172− } else if (narrowMode) {
173− lockscreen.clear(false); // to reset focus if necessary
174− }
175− }
90+ // This is a just a glorified notifyAboutToFocusApp(), but it does one
91+ // other thing: it hides any cover pages to the RIGHT, because the user
92+ // just came from a launcher drag starting on the left.
93+ d.startUnlock(true);
17694 }
17795
17896 QtObject {
17997 id: d
18098
181− property var showAnimation: StandardAnimation { property: "dragOffset"; to: 0; duration: UbuntuAnimation.FastDuration }
182− property var leftHideAnimation: StandardAnimation { property: "dragOffset"; to: -width }
183− property var rightHideAnimation: StandardAnimation { property: "dragOffset"; to: width }
99+ readonly property bool multiUser: LightDM.Users.count > 1
100+ property int currentIndex
101+ property int delayMinutes
102+ property bool waiting
103+
104+ // We define a proxy and "is valid" property for launcherOffset because of
105+ // a quirk in Qml. We only want this animation to fire if we are reset
106+ // back to zero (on a release of the drag). But by defining a Behavior,
107+ // we delay the property from reaching zero until it's too late. So we set
108+ // a proxy bound to launcherOffset, which lets us see the target value of
109+ // zero as we also slowly adjust the proxy down to zero. But Qml will send
110+ // change notifications in declaration order. So unless we define the
111+ // proxy first, we need a little "is valid" property defined above the
112+ // proxy, so we know when to enable the proxy behavior. Phew!
113+ readonly property bool launcherOffsetValid: launcherOffset > 0
114+ property real launcherOffsetProxy: shown ? launcherOffset : 0
115+ Behavior on launcherOffsetProxy {
116+ enabled: !d.launcherOffsetValid
117+ StandardAnimation {}
118+ }
184119
185120 function selectUser(uid) {
186− // Update launcher items for new user
121+ currentIndex = uid;
187122 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
188123 AccountsService.user = user;
189124 LauncherModel.setUser(user);
190125 LightDM.Greeter.authenticate(user); // always resets auth state
191126 }
192127
193− function reset() {
194− if (greeterContentLoader.item) {
195− greeterContentLoader.item.reset()
196− }
197− }
198−
199128 function login() {
200129 enabled = false;
201130 if (LightDM.Greeter.startSessionSync()) {
202131 sessionStarted();
203− hide();
204− lockscreen.hide();
132+ loader.item.authenticated(true);
133+ } else {
134+ loader.item.authenticated(false);
205135 }
206136 enabled = true;
207137 }
138+
139+ function startUnlock(toTheRight) {
140+ if (loader.item) {
141+ loader.item.tryToUnlock(toTheRight);
142+ }
143+ }
208144 }
209145
210− Lockscreen {
211− id: lockscreen
212− objectName: "lockscreen"
146+ onForcedUnlockChanged: {
147+ if (forcedUnlock && shown) {
148+ // pretend we were just authenticated
149+ loader.item.authenticated(true);
150+ }
151+ }
213152
214− shown: false
215− showAnimation: StandardAnimation { property: "opacity"; to: 1 }
216− hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
217− anchors.fill: parent
218− visible: required
219− background: greeter.background
220− darkenBackground: 0.4
221− alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
222− minPinLength: 4
223− maxPinLength: 4
224−
225− property string promptText
226− infoText: promptText !== "" ? i18n.tr("Enter %1").arg(promptText) :
227− alphaNumeric ? i18n.tr("Enter passphrase") :
228− i18n.tr("Enter passcode")
229− errorText: promptText !== "" ? i18n.tr("Sorry, incorrect %1").arg(promptText) :
230− alphaNumeric ? i18n.tr("Sorry, incorrect passphrase") + "\n" +
231− i18n.tr("Please re-enter") :
232− i18n.tr("Sorry, incorrect passcode")
233−
234− // FIXME: We *should* show emergency dialer if there is a SIM present,
235− // regardless of whether the side stage is enabled. But right now,
236− // the assumption is that narrow screens are phones which have SIMs
237− // and wider screens are tablets which don't. When we do allow this
238− // on devices with a side stage and a SIM, work should be done to
239− // ensure that the main stage is disabled while the dialer is present
240− // in the side stage. See the FIXME in the stage loader in Shell.qml.
241− showEmergencyCallButton: !greeter.tabletMode
242−
243− onEntered: LightDM.Greeter.respond(passphrase);
244− onCancel: greeter.show()
245− onEmergencyCall: greeter.emergencyCall()
246−
247− onShownChanged: if (shown) greeter.lockedApp = ""
248−
249− function maybeShow() {
250− if (!greeter.forcedUnlock) {
251− show();
252− }
153+ onRequiredChanged: {
154+ if (required) {
155+ d.waiting = true;
156+ lockedApp = "";
253157 }
158+ }
254159
255− Timer {
256− id: forcedDelayTimer
257− interval: 1000 * 60
258− onTriggered: {
259− if (lockscreen.delayMinutes > 0) {
260− lockscreen.delayMinutes -= 1
261− if (lockscreen.delayMinutes > 0) {
262− start() // go again
263− }
160+ Timer {
161+ id: forcedDelayTimer
162+ interval: 1000 * 60
163+ onTriggered: {
164+ if (d.delayMinutes > 0) {
165+ d.delayMinutes -= 1;
166+ if (d.delayMinutes > 0) {
167+ start(); // go again
264168 }
265169 }
266170 }
267171 }
268172
269− Rectangle {
270− anchors.fill: parent
271− color: "black"
272− opacity: greeter.showProgress * 0.8
273− }
274−
275173 Loader {
276− id: greeterContentLoader
277− objectName: "greeterContentLoader"
278−
279− x: launcherOffsetProxy + dragOffset
280− width: parent.width
281− height: parent.height
174+ id: loader
175+ objectName: "loader"
282176
283− property var model: LightDM.Users
284− property int currentIndex: 0
285− property var infographicModel: LightDM.Infographic
286− readonly property int backgroundTopMargin: -greeter.y
177+ anchors.fill: parent
287178
288− source: (greeter.required || lockscreen.required) ? "GreeterContent.qml" : ""
179+ source: greeter.required ? ((!d.multiUser && !tabletMode) ? "NarrowGreeter.qml"
180+ : "WideGreeter.qml")
181+ : ""
289182
290183 onLoaded: {
291− d.selectUser(currentIndex);
184+ loader.item.reset();
185+ greeter.lockedApp = "";
186+ greeter.forceActiveFocus();
187+ d.selectUser(d.currentIndex);
292188 }
293189
294190 Connections {
295− target: greeterContentLoader.item
296−
191+ target: loader.item
297192 onSelected: {
298− d.selectUser(uid);
299− greeterContentLoader.currentIndex = uid;
193+ d.selectUser(index);
194+ }
195+ onUnlocked: {
196+ d.login()
300197 }
301− onUnlocked: greeter.hide()
302198 onTease: greeter.tease()
199+ onRequiredChanged: {
200+ if (!loader.item.required) {
201+ greeter.hide();
202+ }
203+ }
204+ }
205+
206+ Binding {
207+ target: loader.item
208+ property: "backgroundTopMargin"
209+ value: -greeter.y
210+ }
211+
212+ Binding {
213+ target: loader.item
214+ property: "launcherOffset"
215+ value: d.launcherOffsetProxy
216+ }
217+
218+ Binding {
219+ target: loader.item
220+ property: "delayMinutes"
221+ value: d.delayMinutes
303222 }
304223 }
305224
306225 Connections {
307226 target: LightDM.Greeter
308227
309− onShowGreeter: greeter.show()
310− onHideGreeter: d.login()
228+ onShowGreeter: {
229+ greeter.show();
230+ loader.item.reset();
231+ d.selectUser(d.currentIndex);
232+ }
311233
312− onShowPrompt: {
313− waiting = false;
234+ onHideGreeter: {
235+ d.login();
236+ loader.item.hide();
237+ }
314238
239+ onShowMessage: {
315240 if (!LightDM.Greeter.active) {
316241 return; // could happen if hideGreeter() comes in before we prompt
317242 }
318243
319− if (greeter.narrowMode) {
320− lockscreen.promptText = isDefaultPrompt ? "" : text.toLowerCase();
321− lockscreen.maybeShow();
244+ // inefficient, but we only rarely deal with messages
245+ var html = text.replace(/&/g, "&")
246+ .replace(/</g, "<")
247+ .replace(/>/g, ">")
248+ .replace(/\n/g, "<br>");
249+ if (isError) {
250+ html = "<font color=\"#df382c\">" + html + "</font>";
322251 }
252+
253+ loader.item.showMessage(html);
323254 }
324255
325− onPromptlessChanged: {
256+ onShowPrompt: {
257+ d.waiting = false;
258+
326259 if (!LightDM.Greeter.active) {
327260 return; // could happen if hideGreeter() comes in before we prompt
328261 }
329− if (greeter.narrowMode) {
330− if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
331− lockscreen.hide()
332− } else {
333− lockscreen.reset();
334− lockscreen.maybeShow();
335− }
336− }
262+
263+ loader.item.showPrompt(text, isSecret, isDefaultPrompt);
337264 }
338265
339266 onAuthenticationComplete: {
340− waiting = false;
341− if (LightDM.Greeter.authenticated) {
342− AccountsService.failedLogins = 0
343− }
344− // Else only penalize user for a failed login if they actually were
345− // prompted for a password. We do this below after the promptless
346− // early exit.
347−
348− if (LightDM.Greeter.promptless) {
349− return;
350− }
267+ d.waiting = false;
351268
352269 if (LightDM.Greeter.authenticated) {
270+ AccountsService.failedLogins = 0;
353271 d.login();
272+ if (!LightDM.Greeter.promptless) {
273+ loader.item.hide();
274+ }
354275 } else {
355− AccountsService.failedLogins++
356−
357− if (greeter.narrowMode) {
358− if (maxFailedLogins >= 2) { // require at least a warning
359− if (AccountsService.failedLogins === maxFailedLogins - 1) {
360− var title = lockscreen.alphaNumeric ?
361− i18n.tr("Sorry, incorrect passphrase.") :
362− i18n.tr("Sorry, incorrect passcode.")
363− var text = i18n.tr("This will be your last attempt.") + " " +
364− (lockscreen.alphaNumeric ?
365− i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
366− i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
367− lockscreen.showInfoPopup(title, text)
368− } else if (AccountsService.failedLogins >= maxFailedLogins) {
369− SystemImage.factoryReset() // Ouch!
370− }
371− }
372− if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
373− lockscreen.delayMinutes = failedLoginsDelayMinutes
374− forcedDelayTimer.start()
276+ if (!LightDM.Greeter.promptless) {
277+ AccountsService.failedLogins++;
278+ }
279+
280+ // Check if we should initiate a factory reset
281+ if (maxFailedLogins >= 2) { // require at least a warning
282+ if (AccountsService.failedLogins === maxFailedLogins - 1) {
283+ loader.item.showLastChance();
284+ } else if (AccountsService.failedLogins >= maxFailedLogins) {
285+ SystemImage.factoryReset(); // Ouch!
375286 }
287+ }
288+
289+ // Check if we should initiate a forced login delay
290+ if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
291+ d.delayMinutes = failedLoginsDelayMinutes;
292+ forcedDelayTimer.start();
293+ }
294+
295+ loader.item.authenticated(false);
296+ if (!LightDM.Greeter.promptless) {
297+ d.selectUser(d.currentIndex);
298+ }
299+ }
300+ }
376301
377− lockscreen.clear(true);
378− d.selectUser(greeterContentLoader.currentIndex);
302+ onRequestAuthenticationUser: {
303+ // Find index for requested user, if it exists
304+ for (var i = 0; i < LightDM.Users.count; i++) {
305+ if (user === LightDM.Users.data(i, LightDM.UserRoles.NameRole)) {
306+ d.selectUser(i);
307+ return;
379308 }
380309 }
381310 }
@@ -384,6 +313,6 @@ Showable {
384313 Binding {
385314 target: LightDM.Greeter
386315 property: "active"
387− value: greeter.semiShown || greeter.hasLockedApp
316+ value: greeter.active
388317 }
389318}