on 02fa86a help sign in
unity8/qml/Greeter/Greeter.qml
416 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 signal tease()
56 signal sessionStarted()
57 signal emergencyCall()
59 function forceShow() {
60 showNow();
61 loader.item.reset();
62 }
64 function notifyAppFocused(appId) {
65 if (!active) {
66 return;
67 }
69 if (hasLockedApp) {
70 if (appId === lockedApp) {
71 hide(); // show locked app
72 } else {
73 show();
74 d.startUnlock(false /* toTheRight */);
75 }
76 } else if (appId !== "unity8-dash") { // dash isn't started by user
77 d.startUnlock(false /* toTheRight */);
78 }
79 }
81 function notifyAboutToFocusApp(appId) {
82 if (!active) {
83 return;
84 }
86 // A hint that we're about to focus an app. This way we can look
87 // a little more responsive, rather than waiting for the above
88 // notifyAppFocused call. We also need this in case we have a locked
89 // app, in order to show lockscreen instead of new app.
90 d.startUnlock(false /* toTheRight */);
91 }
93 // This is a just a glorified notifyAboutToFocusApp(), but it does one
94 // other thing: it hides any cover pages to the RIGHT, because the user
95 // just came from a launcher drag starting on the left.
96 // It also returns a boolean value, indicating whether there was a visual
97 // change or not (the shell only wants to hide the launcher if there was
98 // a change).
99 function notifyShowingDashFromDrag() {
100 if (!active) {
101 return false;
102 }
104 return d.startUnlock(true /* toTheRight */);
105 }
107 QtObject {
108 id: d
110 readonly property bool multiUser: LightDM.Users.count > 1
111 property int currentIndex
112 property bool waiting
114 // We want 'launcherOffset' to animate down to zero. But not to animate
115 // while being dragged. So ideally we change this only when the user
116 // lets go and launcherOffset drops to zero. But we need to wait for
117 // the behavior to be enabled first. So we cache the last known good
118 // launcherOffset value to cover us during that brief gap between
119 // release and the behavior turning on.
120 property real lastKnownPositiveOffset // set in a launcherOffsetChanged below
121 property real launcherOffsetProxy: (shown && !launcherOffsetProxyBehavior.enabled) ? lastKnownPositiveOffset : 0
122 Behavior on launcherOffsetProxy {
123 id: launcherOffsetProxyBehavior
124 enabled: launcherOffset === 0
125 UbuntuNumberAnimation {}
126 }
128 function selectUser(uid, reset) {
129 d.waiting = true;
130 if (reset) {
131 loader.item.reset();
132 }
133 currentIndex = uid;
134 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
135 AccountsService.user = user;
136 LauncherModel.setUser(user);
137 LightDM.Greeter.authenticate(user); // always resets auth state
138 }
140 function login() {
141 enabled = false;
142 if (LightDM.Greeter.startSessionSync()) {
143 sessionStarted();
144 loader.item.notifyAuthenticationSucceeded();
145 } else {
146 loader.item.notifyAuthenticationFailed();
147 }
148 enabled = true;
149 }
151 function startUnlock(toTheRight) {
152 if (loader.item) {
153 return loader.item.tryToUnlock(toTheRight);
154 } else {
155 return false;
156 }
157 }
158 }
160 onLauncherOffsetChanged: {
161 if (launcherOffset > 0) {
162 d.lastKnownPositiveOffset = launcherOffset;
163 }
164 }
166 onForcedUnlockChanged: {
167 if (forcedUnlock && shown) {
168 // pretend we were just authenticated
169 loader.item.notifyAuthenticationSucceeded();
170 }
171 }
173 onRequiredChanged: {
174 if (required) {
175 d.waiting = true;
176 lockedApp = "";
177 }
178 }
180 Timer {
181 id: forcedDelayTimer
183 // We use a short interval and check against the system wall clock
184 // because we have to consider the case that the system is suspended
185 // for a few minutes. When we wake up, we want to quickly be correct.
186 interval: 500
188 property var delayTarget;
189 property int delayMinutes;
191 function forceDelay(delay /* in minutes */) {
192 delayTarget = new Date();
193 delayTarget.setTime(delayTarget.getTime() + delay * 60000);
194 delayMinutes = Math.ceil(delay);
195 start();
196 }
198 onTriggered: {
199 var diff = delayTarget - new Date();
200 if (diff > 0) {
201 delayMinutes = Math.ceil(diff / 60000);
202 start(); // go again
203 } else {
204 delayMinutes = 0;
205 }
206 }
207 }
209 // event eater
210 // Nothing should leak to items behind the greeter
211 MouseArea { anchors.fill: parent }
213 Loader {
214 id: loader
215 objectName: "loader"
217 anchors.fill: parent
219 active: root.required
220 source: root.viewSource.toString() ? root.viewSource :
221 (d.multiUser || root.tabletMode) ? "WideView.qml" : "NarrowView.qml"
223 onLoaded: {
224 root.lockedApp = "";
225 root.forceActiveFocus();
226 d.selectUser(d.currentIndex, true);
227 LightDM.Infographic.readyForDataChange();
228 }
230 Connections {
231 target: loader.item
232 onSelected: {
233 d.selectUser(index, true);
234 }
235 onResponded: {
236 if (root.locked) {
237 LightDM.Greeter.respond(response);
238 } else {
239 if (LightDM.Greeter.active && !LightDM.Greeter.authenticated) { // could happen if forcedUnlock
240 d.login();
241 }
242 loader.item.hide();
243 }
244 }
245 onTease: root.tease()
246 onEmergencyCall: root.emergencyCall()
247 onRequiredChanged: {
248 if (!loader.item.required) {
249 root.hide();
250 }
251 }
252 }
254 Binding {
255 target: loader.item
256 property: "backgroundTopMargin"
257 value: -root.y
258 }
260 Binding {
261 target: loader.item
262 property: "launcherOffset"
263 value: d.launcherOffsetProxy
264 }
266 Binding {
267 target: loader.item
268 property: "dragHandleLeftMargin"
269 value: root.dragHandleLeftMargin
270 }
272 Binding {
273 target: loader.item
274 property: "delayMinutes"
275 value: forcedDelayTimer.delayMinutes
276 }
278 Binding {
279 target: loader.item
280 property: "background"
281 value: root.background
282 }
284 Binding {
285 target: loader.item
286 property: "locked"
287 value: root.locked
288 }
290 Binding {
291 target: loader.item
292 property: "alphanumeric"
293 value: AccountsService.passwordDisplayHint === AccountsService.Keyboard
294 }
296 Binding {
297 target: loader.item
298 property: "currentIndex"
299 value: d.currentIndex
300 }
302 Binding {
303 target: loader.item
304 property: "userModel"
305 value: LightDM.Users
306 }
308 Binding {
309 target: loader.item
310 property: "infographicModel"
311 value: LightDM.Infographic
312 }
313 }
315 Connections {
316 target: LightDM.Greeter
318 onShowGreeter: root.forceShow()
320 onHideGreeter: {
321 d.login();
322 loader.item.hide();
323 }
325 onShowMessage: {
326 if (!LightDM.Greeter.active) {
327 return; // could happen if hideGreeter() comes in before we prompt
328 }
330 // inefficient, but we only rarely deal with messages
331 var html = text.replace(/&/g, "&amp;")
332 .replace(/</g, "&lt;")
333 .replace(/>/g, "&gt;")
334 .replace(/\n/g, "<br>");
335 if (isError) {
336 html = "<font color=\"#df382c\">" + html + "</font>";
337 }
339 loader.item.showMessage(html);
340 }
342 onShowPrompt: {
343 d.waiting = false;
345 if (!LightDM.Greeter.active) {
346 return; // could happen if hideGreeter() comes in before we prompt
347 }
349 loader.item.showPrompt(text, isSecret, isDefaultPrompt);
350 }
352 onAuthenticationComplete: {
353 d.waiting = false;
355 if (LightDM.Greeter.authenticated) {
356 AccountsService.failedLogins = 0;
357 d.login();
358 if (!LightDM.Greeter.promptless) {
359 loader.item.hide();
360 }
361 } else {
362 if (!LightDM.Greeter.promptless) {
363 AccountsService.failedLogins++;
364 }
366 // Check if we should initiate a factory reset
367 if (maxFailedLogins >= 2) { // require at least a warning
368 if (AccountsService.failedLogins === maxFailedLogins - 1) {
369 loader.item.showLastChance();
370 } else if (AccountsService.failedLogins >= maxFailedLogins) {
371 SystemImage.factoryReset(); // Ouch!
372 }
373 }
375 // Check if we should initiate a forced login delay
376 if (failedLoginsDelayAttempts > 0
377 && AccountsService.failedLogins > 0
378 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
379 forcedDelayTimer.forceDelay(failedLoginsDelayMinutes);
380 }
382 loader.item.notifyAuthenticationFailed();
383 if (!LightDM.Greeter.promptless) {
384 d.selectUser(d.currentIndex, false);
385 }
386 }
387 }
389 onRequestAuthenticationUser: {
390 // Find index for requested user, if it exists
391 for (var i = 0; i < LightDM.Users.count; i++) {
392 if (user === LightDM.Users.data(i, LightDM.UserRoles.NameRole)) {
393 d.selectUser(i, true);
394 return;
395 }
396 }
397 }
398 }
400 Binding {
401 target: LightDM.Greeter
402 property: "active"
403 value: root.active
404 }
406 Binding {
407 target: LightDM.Infographic
408 property: "username"
409 value: AccountsService.statsWelcomeScreen ? LightDM.Users.data(d.currentIndex, LightDM.UserRoles.NameRole) : ""
410 }
412 Connections {
413 target: i18n
414 onLanguageChanged: LightDM.Infographic.readyForDataChange()
415 }