Unity 8
Greeter.qml
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  */
16 
17 import QtQuick 2.3
18 import AccountsService 0.1
19 import LightDM 0.1 as LightDM
20 import Ubuntu.Components 1.1
21 import Ubuntu.SystemImage 0.1
22 import Unity.Connectivity 0.1
23 import Unity.Launcher 0.1
24 import "../Components"
25 
26 Showable {
27  id: root
28  created: loader.status == Loader.Ready
29 
30  property real dragHandleLeftMargin: 0
31 
32  property url background
33 
34  // How far to offset the top greeter layer during a launcher left-drag
35  property real launcherOffset
36 
37  readonly property bool active: shown || hasLockedApp
38  readonly property bool fullyShown: loader.item ? loader.item.fullyShown : false
39 
40  // True when the greeter is waiting for PAM or other setup process
41  readonly property alias waiting: d.waiting
42 
43  property string lockedApp: ""
44  readonly property bool hasLockedApp: lockedApp !== ""
45 
46  property bool forcedUnlock
47  readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated && !forcedUnlock
48 
49  property bool tabletMode
50  property url viewSource // only used for testing
51 
52  property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
53  property int failedLoginsDelayAttempts: 7 // number of failed logins
54  property int failedLoginsDelayMinutes: 5 // minutes of forced waiting
55 
56  signal tease()
57  signal sessionStarted()
58  signal emergencyCall()
59 
60  function forceShow() {
61  showNow();
62  loader.item.reset();
63  }
64 
65  function notifyAppFocused(appId) {
66  if (!active) {
67  return;
68  }
69 
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  }
81 
82  function notifyAboutToFocusApp(appId) {
83  if (!active) {
84  return;
85  }
86 
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  }
93 
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  }
104 
105  return d.startUnlock(true /* toTheRight */);
106  }
107 
108  QtObject {
109  id: d
110 
111  readonly property bool multiUser: LightDM.Users.count > 1
112  property int currentIndex
113  property int delayMinutes
114  property bool waiting
115 
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  }
129 
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  }
141 
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  }
152 
153  function startUnlock(toTheRight) {
154  if (loader.item) {
155  return loader.item.tryToUnlock(toTheRight);
156  } else {
157  return false;
158  }
159  }
160  }
161 
162  onLauncherOffsetChanged: {
163  if (launcherOffset > 0) {
164  d.lastKnownPositiveOffset = launcherOffset;
165  }
166  }
167 
168  onForcedUnlockChanged: {
169  if (forcedUnlock && shown) {
170  // pretend we were just authenticated
171  loader.item.notifyAuthenticationSucceeded();
172  }
173  }
174 
175  onRequiredChanged: {
176  if (required) {
177  d.waiting = true;
178  lockedApp = "";
179  }
180  }
181 
182  Component.onCompleted: {
183  Connectivity.unlockAllModems();
184  }
185 
186  Timer {
187  id: forcedDelayTimer
188  interval: 1000 * 60
189  onTriggered: {
190  if (d.delayMinutes > 0) {
191  d.delayMinutes -= 1;
192  if (d.delayMinutes > 0) {
193  start(); // go again
194  }
195  }
196  }
197  }
198 
199  // event eater
200  // Nothing should leak to items behind the greeter
201  MouseArea { anchors.fill: parent }
202 
203  Loader {
204  id: loader
205  objectName: "loader"
206 
207  anchors.fill: parent
208 
209  active: root.required
210  source: root.viewSource.toString() ? root.viewSource :
211  (d.multiUser || root.tabletMode) ? "WideView.qml" : "NarrowView.qml"
212 
213  onLoaded: {
214  root.lockedApp = "";
215  root.forceActiveFocus();
216  d.selectUser(d.currentIndex, true);
217  LightDM.Infographic.readyForDataChange();
218  }
219 
220  Connections {
221  target: loader.item
222  onSelected: {
223  d.selectUser(index, true);
224  }
225  onResponded: {
226  if (root.locked) {
227  LightDM.Greeter.respond(response);
228  } else {
229  if (LightDM.Greeter.active && !LightDM.Greeter.authenticated) { // could happen if forcedUnlock
230  d.login();
231  }
232  loader.item.hide();
233  }
234  }
235  onTease: root.tease()
236  onEmergencyCall: root.emergencyCall()
237  onRequiredChanged: {
238  if (!loader.item.required) {
239  root.hide();
240  }
241  }
242  }
243 
244  Binding {
245  target: loader.item
246  property: "backgroundTopMargin"
247  value: -root.y
248  }
249 
250  Binding {
251  target: loader.item
252  property: "launcherOffset"
253  value: d.launcherOffsetProxy
254  }
255 
256  Binding {
257  target: loader.item
258  property: "dragHandleLeftMargin"
259  value: root.dragHandleLeftMargin
260  }
261 
262  Binding {
263  target: loader.item
264  property: "delayMinutes"
265  value: d.delayMinutes
266  }
267 
268  Binding {
269  target: loader.item
270  property: "background"
271  value: root.background
272  }
273 
274  Binding {
275  target: loader.item
276  property: "locked"
277  value: root.locked
278  }
279 
280  Binding {
281  target: loader.item
282  property: "alphanumeric"
283  value: AccountsService.passwordDisplayHint === AccountsService.Keyboard
284  }
285 
286  Binding {
287  target: loader.item
288  property: "currentIndex"
289  value: d.currentIndex
290  }
291 
292  Binding {
293  target: loader.item
294  property: "userModel"
295  value: LightDM.Users
296  }
297 
298  Binding {
299  target: loader.item
300  property: "infographicModel"
301  value: LightDM.Infographic
302  }
303  }
304 
305  Connections {
306  target: LightDM.Greeter
307 
308  onShowGreeter: root.forceShow()
309 
310  onHideGreeter: {
311  d.login();
312  loader.item.hide();
313  }
314 
315  onShowMessage: {
316  if (!LightDM.Greeter.active) {
317  return; // could happen if hideGreeter() comes in before we prompt
318  }
319 
320  // inefficient, but we only rarely deal with messages
321  var html = text.replace(/&/g, "&amp;")
322  .replace(/</g, "&lt;")
323  .replace(/>/g, "&gt;")
324  .replace(/\n/g, "<br>");
325  if (isError) {
326  html = "<font color=\"#df382c\">" + html + "</font>";
327  }
328 
329  loader.item.showMessage(html);
330  }
331 
332  onShowPrompt: {
333  d.waiting = false;
334 
335  if (!LightDM.Greeter.active) {
336  return; // could happen if hideGreeter() comes in before we prompt
337  }
338 
339  loader.item.showPrompt(text, isSecret, isDefaultPrompt);
340  }
341 
342  onAuthenticationComplete: {
343  d.waiting = false;
344 
345  if (LightDM.Greeter.authenticated) {
346  AccountsService.failedLogins = 0;
347  d.login();
348  if (!LightDM.Greeter.promptless) {
349  loader.item.hide();
350  }
351  } else {
352  if (!LightDM.Greeter.promptless) {
353  AccountsService.failedLogins++;
354  }
355 
356  // Check if we should initiate a factory reset
357  if (maxFailedLogins >= 2) { // require at least a warning
358  if (AccountsService.failedLogins === maxFailedLogins - 1) {
359  loader.item.showLastChance();
360  } else if (AccountsService.failedLogins >= maxFailedLogins) {
361  SystemImage.factoryReset(); // Ouch!
362  }
363  }
364 
365  // Check if we should initiate a forced login delay
366  if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
367  d.delayMinutes = failedLoginsDelayMinutes;
368  forcedDelayTimer.start();
369  }
370 
371  loader.item.notifyAuthenticationFailed();
372  if (!LightDM.Greeter.promptless) {
373  d.selectUser(d.currentIndex, false);
374  }
375  }
376  }
377 
378  onRequestAuthenticationUser: {
379  // Find index for requested user, if it exists
380  for (var i = 0; i < LightDM.Users.count; i++) {
381  if (user === LightDM.Users.data(i, LightDM.UserRoles.NameRole)) {
382  d.selectUser(i, true);
383  return;
384  }
385  }
386  }
387  }
388 
389  Binding {
390  target: LightDM.Greeter
391  property: "active"
392  value: root.active
393  }
394 
395  Binding {
396  target: LightDM.Infographic
397  property: "username"
398  value: AccountsService.statsWelcomeScreen ? LightDM.Users.data(d.currentIndex, LightDM.UserRoles.NameRole) : ""
399  }
400 
401  Connections {
402  target: i18n
403  onLanguageChanged: LightDM.Infographic.readyForDataChange()
404  }
405 }