on cd4bc68 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 Ubuntu.Components 1.1
20import Ubuntu.SystemImage 0.1
21import Unity.Launcher 0.1
22import "../Components"
24Showable {
25 id: root
26 created: loader.status == Loader.Ready
28 property real dragHandleLeftMargin: 0
30 property url background
32 // How far to offset the top greeter layer during a launcher left-drag
33 property real launcherOffset
35 readonly property bool active: shown || hasLockedApp
36 readonly property bool fullyShown: loader.item ? loader.item.fullyShown : false
38 // True when the greeter is waiting for PAM or other setup process
39 readonly property alias waiting: d.waiting
41 property string lockedApp: ""
42 readonly property bool hasLockedApp: lockedApp !== ""
44 property bool forcedUnlock
45 readonly property bool locked: lightDM.greeter.active && !lightDM.greeter.authenticated && !forcedUnlock
47 property bool tabletMode
48 property url viewSource // only used for testing
50 property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
51 property int failedLoginsDelayAttempts: 7 // number of failed logins
52 property real failedLoginsDelayMinutes: 5 // minutes of forced waiting
54 readonly property bool animating: loader.item ? loader.item.animating : false
56 signal tease()
57 signal sessionStarted()
58 signal emergencyCall()
60 function forceShow() {
61 showNow();
62 loader.item.reset();
63 }
65 function notifyAppFocused(appId) {
66 if (!active) {
67 return;
68 }
70 if (hasLockedApp) {
71 if (appId === lockedApp) {
72 hide(); // show locked app
73 } else {
74 show();
75 d.startUnlock(false /* toTheRight */);
76 }
77 } else if (appId !== "unity8-dash") { // dash isn't started by user
78 d.startUnlock(false /* toTheRight */);
79 }
80 }
82 function notifyAboutToFocusApp(appId) {
83 if (!active) {
84 return;
85 }
87 // A hint that we're about to focus an app. This way we can look
88 // a little more responsive, rather than waiting for the above
89 // notifyAppFocused call. We also need this in case we have a locked
90 // app, in order to show lockscreen instead of new app.
91 d.startUnlock(false /* toTheRight */);
92 }
94 // This is a just a glorified notifyAboutToFocusApp(), but it does one
95 // other thing: it hides any cover pages to the RIGHT, because the user
96 // just came from a launcher drag starting on the left.
97 // It also returns a boolean value, indicating whether there was a visual
98 // change or not (the shell only wants to hide the launcher if there was
99 // a change).
100 function notifyShowingDashFromDrag() {
101 if (!active) {
102 return false;
103 }
105 return d.startUnlock(true /* toTheRight */);
106 }
108 LightDM{id:lightDM} // Provide backend access
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 }