on fcccc72 help sign in
unity8/qml/Greeter/Greeter.qml
418 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 loader.item.notifyAuthenticationSucceeded();
147 } else {
148 loader.item.notifyAuthenticationFailed();
149 }
150 enabled = true;
151 }
153 function startUnlock(toTheRight) {
154 if (loader.item) {
155 return loader.item.tryToUnlock(toTheRight);
156 } else {
157 return false;
158 }
159 }
160 }
162 onLauncherOffsetChanged: {
163 if (launcherOffset > 0) {
164 d.lastKnownPositiveOffset = launcherOffset;
165 }
166 }
168 onForcedUnlockChanged: {
169 if (forcedUnlock && shown) {
170 // pretend we were just authenticated
171 loader.item.notifyAuthenticationSucceeded();
172 }
173 }
175 onRequiredChanged: {
176 if (required) {
177 d.waiting = true;
178 lockedApp = "";
179 }
180 }
182 Timer {
183 id: forcedDelayTimer
185 // We use a short interval and check against the system wall clock
186 // because we have to consider the case that the system is suspended
187 // for a few minutes. When we wake up, we want to quickly be correct.
188 interval: 500
190 property var delayTarget;
191 property int delayMinutes;
193 function forceDelay(delay /* in minutes */) {
194 delayTarget = new Date();
195 delayTarget.setTime(delayTarget.getTime() + delay * 60000);
196 delayMinutes = Math.ceil(delay);
197 start();
198 }
200 onTriggered: {
201 var diff = delayTarget - new Date();
202 if (diff > 0) {
203 delayMinutes = Math.ceil(diff / 60000);
204 start(); // go again
205 } else {
206 delayMinutes = 0;
207 }
208 }
209 }
211 // event eater
212 // Nothing should leak to items behind the greeter
213 MouseArea { anchors.fill: parent }
215 Loader {
216 id: loader
217 objectName: "loader"
219 anchors.fill: parent
221 active: root.required
222 source: root.viewSource.toString() ? root.viewSource :
223 (d.multiUser || root.tabletMode) ? "WideView.qml" : "NarrowView.qml"
225 onLoaded: {
226 root.lockedApp = "";
227 root.forceActiveFocus();
228 d.selectUser(d.currentIndex, true);
229 LightDM.Infographic.readyForDataChange();
230 }
232 Connections {
233 target: loader.item
234 onSelected: {
235 d.selectUser(index, true);
236 }
237 onResponded: {
238 if (root.locked) {
239 LightDM.Greeter.respond(response);
240 } else {
241 if (LightDM.Greeter.active && !LightDM.Greeter.authenticated) { // could happen if forcedUnlock
242 d.login();
243 }
244 loader.item.hide();
245 }
246 }
247 onTease: root.tease()
248 onEmergencyCall: root.emergencyCall()
249 onRequiredChanged: {
250 if (!loader.item.required) {
251 root.hide();
252 }
253 }
254 }
256 Binding {
257 target: loader.item
258 property: "backgroundTopMargin"
259 value: -root.y
260 }
262 Binding {
263 target: loader.item
264 property: "launcherOffset"
265 value: d.launcherOffsetProxy
266 }
268 Binding {
269 target: loader.item
270 property: "dragHandleLeftMargin"
271 value: root.dragHandleLeftMargin
272 }
274 Binding {
275 target: loader.item
276 property: "delayMinutes"
277 value: forcedDelayTimer.delayMinutes
278 }
280 Binding {
281 target: loader.item
282 property: "background"
283 value: root.background
284 }
286 Binding {
287 target: loader.item
288 property: "locked"
289 value: root.locked
290 }
292 Binding {
293 target: loader.item
294 property: "alphanumeric"
295 value: AccountsService.passwordDisplayHint === AccountsService.Keyboard
296 }
298 Binding {
299 target: loader.item
300 property: "currentIndex"
301 value: d.currentIndex
302 }
304 Binding {
305 target: loader.item
306 property: "userModel"
307 value: LightDM.Users
308 }
310 Binding {
311 target: loader.item
312 property: "infographicModel"
313 value: LightDM.Infographic
314 }
315 }
317 Connections {
318 target: LightDM.Greeter
320 onShowGreeter: root.forceShow()
322 onHideGreeter: {
323 d.login();
324 loader.item.hide();
325 }
327 onShowMessage: {
328 if (!LightDM.Greeter.active) {
329 return; // could happen if hideGreeter() comes in before we prompt
330 }
332 // inefficient, but we only rarely deal with messages
333 var html = text.replace(/&/g, "&amp;")
334 .replace(/</g, "&lt;")
335 .replace(/>/g, "&gt;")
336 .replace(/\n/g, "<br>");
337 if (isError) {
338 html = "<font color=\"#df382c\">" + html + "</font>";
339 }
341 loader.item.showMessage(html);
342 }
344 onShowPrompt: {
345 d.waiting = false;
347 if (!LightDM.Greeter.active) {
348 return; // could happen if hideGreeter() comes in before we prompt
349 }
351 loader.item.showPrompt(text, isSecret, isDefaultPrompt);
352 }
354 onAuthenticationComplete: {
355 d.waiting = false;
357 if (LightDM.Greeter.authenticated) {
358 AccountsService.failedLogins = 0;
359 d.login();
360 if (!LightDM.Greeter.promptless) {
361 loader.item.hide();
362 }
363 } else {
364 if (!LightDM.Greeter.promptless) {
365 AccountsService.failedLogins++;
366 }
368 // Check if we should initiate a factory reset
369 if (maxFailedLogins >= 2) { // require at least a warning
370 if (AccountsService.failedLogins === maxFailedLogins - 1) {
371 loader.item.showLastChance();
372 } else if (AccountsService.failedLogins >= maxFailedLogins) {
373 SystemImage.factoryReset(); // Ouch!
374 }
375 }
377 // Check if we should initiate a forced login delay
378 if (failedLoginsDelayAttempts > 0
379 && AccountsService.failedLogins > 0
380 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
381 forcedDelayTimer.forceDelay(failedLoginsDelayMinutes);
382 }
384 loader.item.notifyAuthenticationFailed();
385 if (!LightDM.Greeter.promptless) {
386 d.selectUser(d.currentIndex, false);
387 }
388 }
389 }
391 onRequestAuthenticationUser: {
392 // Find index for requested user, if it exists
393 for (var i = 0; i < LightDM.Users.count; i++) {
394 if (user === LightDM.Users.data(i, LightDM.UserRoles.NameRole)) {
395 d.selectUser(i, true);
396 return;
397 }
398 }
399 }
400 }
402 Binding {
403 target: LightDM.Greeter
404 property: "active"
405 value: root.active
406 }
408 Binding {
409 target: LightDM.Infographic
410 property: "username"
411 value: AccountsService.statsWelcomeScreen ? LightDM.Users.data(d.currentIndex, LightDM.UserRoles.NameRole) : ""
412 }
414 Connections {
415 target: i18n
416 onLanguageChanged: LightDM.Infographic.readyForDataChange()
417 }