on 4e59999 help sign in
unity8/qml/Greeter/Greeter.qml
420 lines 13 kB history raw
1/*
2 * Copyright (C) 2013,2014,2015 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 */
17import QtQuick 2.3
18import AccountsService 0.1
19import LightDM 0.1 as LightDM
20import Ubuntu.Components 1.1
21import Ubuntu.SystemImage 0.1
22import Unity.Launcher 0.1
23import "../Components"
25Showable {
26 id: root
27 created: loader.status == Loader.Ready
29 property real dragHandleLeftMargin: 0
31 property url background
33 // How far to offset the top greeter layer during a launcher left-drag
34 property real launcherOffset
36 readonly property bool active: shown || hasLockedApp
37 readonly property bool fullyShown: loader.item ? loader.item.fullyShown : false
39 // True when the greeter is waiting for PAM or other setup process
40 readonly property alias waiting: d.waiting
42 property string lockedApp: ""
43 readonly property bool hasLockedApp: lockedApp !== ""
45 property bool forcedUnlock
46 readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated && !forcedUnlock
48 property bool tabletMode
49 property url viewSource // only used for testing
51 property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
52 property int failedLoginsDelayAttempts: 7 // number of failed logins
53 property real failedLoginsDelayMinutes: 5 // minutes of forced waiting
55 readonly property bool animating: loader.item ? loader.item.animating : false
57 signal tease()
58 signal sessionStarted()
59 signal emergencyCall()
61 function forceShow() {
62 showNow();
63 loader.item.reset();
64 }
66 function notifyAppFocused(appId) {
67 if (!active) {
68 return;
69 }
71 if (hasLockedApp) {
72 if (appId === lockedApp) {
73 hide(); // show locked app
74 } else {
75 show();
76 d.startUnlock(false /* toTheRight */);
77 }
78 } else if (appId !== "unity8-dash") { // dash isn't started by user
79 d.startUnlock(false /* toTheRight */);
80 }
81 }
83 function notifyAboutToFocusApp(appId) {
84 if (!active) {
85 return;
86 }
88 // A hint that we're about to focus an app. This way we can look
89 // a little more responsive, rather than waiting for the above
90 // notifyAppFocused call. We also need this in case we have a locked
91 // app, in order to show lockscreen instead of new app.
92 d.startUnlock(false /* toTheRight */);
93 }
95 // This is a just a glorified notifyAboutToFocusApp(), but it does one
96 // other thing: it hides any cover pages to the RIGHT, because the user
97 // just came from a launcher drag starting on the left.
98 // It also returns a boolean value, indicating whether there was a visual
99 // change or not (the shell only wants to hide the launcher if there was
100 // a change).
101 function notifyShowingDashFromDrag() {
102 if (!active) {
103 return false;
104 }
106 return d.startUnlock(true /* toTheRight */);
107 }
109 QtObject {
110 id: d
112 readonly property bool multiUser: LightDM.Users.count > 1
113 property int currentIndex
114 property bool waiting
116 // We want 'launcherOffset' to animate down to zero. But not to animate
117 // while being dragged. So ideally we change this only when the user
118 // lets go and launcherOffset drops to zero. But we need to wait for
119 // the behavior to be enabled first. So we cache the last known good
120 // launcherOffset value to cover us during that brief gap between
121 // release and the behavior turning on.
122 property real lastKnownPositiveOffset // set in a launcherOffsetChanged below
123 property real launcherOffsetProxy: (shown && !launcherOffsetProxyBehavior.enabled) ? lastKnownPositiveOffset : 0
124 Behavior on launcherOffsetProxy {
125 id: launcherOffsetProxyBehavior
126 enabled: launcherOffset === 0
127 UbuntuNumberAnimation {}
128 }
130 function selectUser(uid, reset) {
131 d.waiting = true;
132 if (reset) {
133 loader.item.reset();
134 }
135 currentIndex = uid;
136 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
137 AccountsService.user = user;
138 LauncherModel.setUser(user);
139 LightDM.Greeter.authenticate(user); // always resets auth state
140 }
142 function login() {
143 enabled = false;
144 if (LightDM.Greeter.startSessionSync()) {
145 sessionStarted();
146 if (loader.item) {
147 loader.item.notifyAuthenticationSucceeded();
148 }
149 } else if (loader.item) {
150 loader.item.notifyAuthenticationFailed();
151 }
152 enabled = true;
153 }
155 function startUnlock(toTheRight) {
156 if (loader.item) {
157 return loader.item.tryToUnlock(toTheRight);
158 } else {
159 return false;
160 }
161 }
162 }
164 onLauncherOffsetChanged: {
165 if (launcherOffset > 0) {
166 d.lastKnownPositiveOffset = launcherOffset;
167 }
168 }
170 onForcedUnlockChanged: {
171 if (forcedUnlock && shown) {
172 // pretend we were just authenticated
173 loader.item.notifyAuthenticationSucceeded();
174 }
175 }
177 onRequiredChanged: {
178 if (required) {
179 d.waiting = true;
180 lockedApp = "";
181 }
182 }
184 Timer {
185 id: forcedDelayTimer
187 // We use a short interval and check against the system wall clock
188 // because we have to consider the case that the system is suspended
189 // for a few minutes. When we wake up, we want to quickly be correct.
190 interval: 500
192 property var delayTarget;
193 property int delayMinutes;
195 function forceDelay(delay /* in minutes */) {
196 delayTarget = new Date();
197 delayTarget.setTime(delayTarget.getTime() + delay * 60000);
198 delayMinutes = Math.ceil(delay);
199 start();
200 }
202 onTriggered: {
203 var diff = delayTarget - new Date();
204 if (diff > 0) {
205 delayMinutes = Math.ceil(diff / 60000);
206 start(); // go again
207 } else {
208 delayMinutes = 0;
209 }
210 }
211 }
213 // event eater
214 // Nothing should leak to items behind the greeter
215 MouseArea { anchors.fill: parent }
217 Loader {
218 id: loader
219 objectName: "loader"
221 anchors.fill: parent
223 active: root.required
224 source: root.viewSource.toString() ? root.viewSource :
225 (d.multiUser || root.tabletMode) ? "WideView.qml" : "NarrowView.qml"
227 onLoaded: {
228 root.lockedApp = "";
229 root.forceActiveFocus();
230 d.selectUser(d.currentIndex, true);
231 LightDM.Infographic.readyForDataChange();
232 }
234 Connections {
235 target: loader.item
236 onSelected: {
237 d.selectUser(index, true);
238 }
239 onResponded: {
240 if (root.locked) {
241 LightDM.Greeter.respond(response);
242 } else {
243 if (LightDM.Greeter.active && !LightDM.Greeter.authenticated) { // could happen if forcedUnlock
244 d.login();
245 }
246 loader.item.hide();
247 }
248 }
249 onTease: root.tease()
250 onEmergencyCall: root.emergencyCall()
251 onRequiredChanged: {
252 if (!loader.item.required) {
253 root.hide();
254 }
255 }
256 }
258 Binding {
259 target: loader.item
260 property: "backgroundTopMargin"
261 value: -root.y
262 }
264 Binding {
265 target: loader.item
266 property: "launcherOffset"
267 value: d.launcherOffsetProxy
268 }
270 Binding {
271 target: loader.item
272 property: "dragHandleLeftMargin"
273 value: root.dragHandleLeftMargin
274 }
276 Binding {
277 target: loader.item
278 property: "delayMinutes"
279 value: forcedDelayTimer.delayMinutes
280 }
282 Binding {
283 target: loader.item
284 property: "background"
285 value: root.background
286 }
288 Binding {
289 target: loader.item
290 property: "locked"
291 value: root.locked
292 }
294 Binding {
295 target: loader.item
296 property: "alphanumeric"
297 value: AccountsService.passwordDisplayHint === AccountsService.Keyboard
298 }
300 Binding {
301 target: loader.item
302 property: "currentIndex"
303 value: d.currentIndex
304 }
306 Binding {
307 target: loader.item
308 property: "userModel"
309 value: LightDM.Users
310 }
312 Binding {
313 target: loader.item
314 property: "infographicModel"
315 value: LightDM.Infographic
316 }
317 }
319 Connections {
320 target: LightDM.Greeter
322 onShowGreeter: root.forceShow()
324 onHideGreeter: {
325 d.login();
326 loader.item.hide();
327 }
329 onShowMessage: {
330 if (!LightDM.Greeter.active) {
331 return; // could happen if hideGreeter() comes in before we prompt
332 }
334 // inefficient, but we only rarely deal with messages
335 var html = text.replace(/&/g, "&amp;")
336 .replace(/</g, "&lt;")
337 .replace(/>/g, "&gt;")
338 .replace(/\n/g, "<br>");
339 if (isError) {
340 html = "<font color=\"#df382c\">" + html + "</font>";
341 }
343 loader.item.showMessage(html);
344 }
346 onShowPrompt: {
347 d.waiting = false;
349 if (!LightDM.Greeter.active) {
350 return; // could happen if hideGreeter() comes in before we prompt
351 }
353 loader.item.showPrompt(text, isSecret, isDefaultPrompt);
354 }
356 onAuthenticationComplete: {
357 d.waiting = false;
359 if (LightDM.Greeter.authenticated) {
360 AccountsService.failedLogins = 0;
361 d.login();
362 if (!LightDM.Greeter.promptless) {
363 loader.item.hide();
364 }
365 } else {
366 if (!LightDM.Greeter.promptless) {
367 AccountsService.failedLogins++;
368 }
370 // Check if we should initiate a factory reset
371 if (maxFailedLogins >= 2) { // require at least a warning
372 if (AccountsService.failedLogins === maxFailedLogins - 1) {
373 loader.item.showLastChance();
374 } else if (AccountsService.failedLogins >= maxFailedLogins) {
375 SystemImage.factoryReset(); // Ouch!
376 }
377 }
379 // Check if we should initiate a forced login delay
380 if (failedLoginsDelayAttempts > 0
381 && AccountsService.failedLogins > 0
382 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
383 forcedDelayTimer.forceDelay(failedLoginsDelayMinutes);
384 }
386 loader.item.notifyAuthenticationFailed();
387 if (!LightDM.Greeter.promptless) {
388 d.selectUser(d.currentIndex, false);
389 }
390 }
391 }
393 onRequestAuthenticationUser: {
394 // Find index for requested user, if it exists
395 for (var i = 0; i < LightDM.Users.count; i++) {
396 if (user === LightDM.Users.data(i, LightDM.UserRoles.NameRole)) {
397 d.selectUser(i, true);
398 return;
399 }
400 }
401 }
402 }
404 Binding {
405 target: LightDM.Greeter
406 property: "active"
407 value: root.active
408 }
410 Binding {
411 target: LightDM.Infographic
412 property: "username"
413 value: AccountsService.statsWelcomeScreen ? LightDM.Users.data(d.currentIndex, LightDM.UserRoles.NameRole) : ""
414 }
416 Connections {
417 target: i18n
418 onLanguageChanged: LightDM.Infographic.readyForDataChange()
419 }